@voltro/cli 0.56.0 → 0.58.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 (128) hide show
  1. package/CHANGELOG.md +395 -0
  2. package/dist/apiBuild-CDE4NXNi.js +2 -0
  3. package/dist/{apiBuild-Bdaetr37.js → apiBuild-CkpsCqIu.js} +2 -2
  4. package/dist/bin.js +1 -1
  5. package/dist/build-51qBWfYY.js +981 -0
  6. package/dist/{checkCommand-D0QV_zM_.js → checkCommand-C1EbNmwl.js} +1 -1
  7. package/dist/{checkCommand-CAwFXrxA.js → checkCommand-C30J2F18.js} +4 -4
  8. package/dist/{codegenCommand-CidbQzbv.js → codegenCommand-kS3Twuc9.js} +12 -12
  9. package/dist/{codemodRunner-BlQPfjzA.js → codemodRunner-BKjCwGiN.js} +1127 -895
  10. package/dist/{commands-CWjfThXv.js → commands-DowWkYtd.js} +25 -25
  11. package/dist/{dashboardCommand-BekcY5Ls.js → dashboardCommand-gG1y4PFG.js} +3 -3
  12. package/dist/{dataCommand-2pccgbIy.js → dataCommand-hNOGKz4K.js} +7 -7
  13. package/dist/{dbCommand-DpK_vQET.js → dbCommand-CMAIz-Bf.js} +3 -3
  14. package/dist/dbCommand-DHi_RuDl.js +2 -0
  15. package/dist/{dev-Dw263KPu.js → dev-Ce2gEziv.js} +2411 -2391
  16. package/dist/dev-Dff_pdik.js +3 -0
  17. package/dist/{doctorCommand-aR_bFmIi.js → doctorCommand-5ULMpL10.js} +652 -289
  18. package/dist/doctorCommand-B8H2MYCE.js +2 -0
  19. package/dist/{dormancyCommand-eXTQMbHU.js → dormancyCommand-DYgI0Kf-.js} +3 -3
  20. package/dist/{embeddingsCommand-CTmiQvwa.js → embeddingsCommand-yJivQSrI.js} +2 -2
  21. package/dist/emptyResultHeadline-Csa5fZOF.js +18 -0
  22. package/dist/{envCommand-BDUgV7EM.js → envCommand-ORJUf8r_.js} +2 -2
  23. package/dist/{evolveCommand-YV8qW1LU.js → evolveCommand-DTawtTxf.js} +2 -2
  24. package/dist/frameworkTableAssembly-B96WCNJA.js +2 -0
  25. package/dist/{frameworkTableAssembly-DNOFXfEQ.js → frameworkTableAssembly-vfkzuzEo.js} +104 -85
  26. package/dist/index.js +1 -1
  27. package/dist/{infoCommand-BjVXpMlP.js → infoCommand-BFksHunl.js} +1 -1
  28. package/dist/inspect-CZvsmp8t.js +2 -0
  29. package/dist/{inspect-CNYvNXPU.js → inspect-PcC5J-Dl.js} +74 -59
  30. package/dist/{logsCmd-BU8uCdys.js → logsCmd-C5y9HyrG.js} +58 -53
  31. package/dist/manifestBuild-AUh31bFU.js +2 -0
  32. package/dist/{manifestBuild-CEkjfpwc.js → manifestBuild-BwiS4iYm.js} +1 -1
  33. package/dist/{migrate-SICulyz1.js → migrate-DtC3lu7H.js} +4 -4
  34. package/dist/{probeCommand-6HxEkNDG.js → probeCommand-BUYzc0CT.js} +1 -1
  35. package/dist/{runtimeTrace-DgYMc09E.js → runtimeTrace-BUk5zMXY.js} +1 -1
  36. package/dist/{sdkgen-PY-umd6O.js → sdkgen-CAhmmbjx.js} +1 -1
  37. package/dist/serveCommand-BUXUtSF5.js +2 -0
  38. package/dist/serveCommand-DhtC7WBq.js +2545 -0
  39. package/dist/serveEntry.js +1 -1
  40. package/dist/{start-BFQQkL1i.js → start-C75w6ftA.js} +1 -1
  41. package/dist/{start-B9NGB8gn.js → start-DNtIlBcn.js} +527 -502
  42. package/dist/startEntry.js +1 -1
  43. package/dist/{tracesCmd-BWYDqMy6.js → tracesCmd-C1qKa94g.js} +68 -63
  44. package/dist/{updateCommand-C9n_Z_oG.js → updateCommand-CtuD5r1t.js} +1 -1
  45. package/dist/updateCommand-eVKVHj3N.js +2 -0
  46. package/dist/webDev-Bzb8ZFus.js +2 -0
  47. package/dist/{webDev-C53hJdcL.js → webDev-CBN9KkyQ.js} +949 -834
  48. package/dist/{webhooksCommand-uuPu8qQX.js → webhooksCommand-BPJQOGTe.js} +2 -2
  49. package/package.json +61 -19
  50. package/templates/AGENTS.md +1 -1
  51. package/templates/agent-docs/_index.md +1 -1
  52. package/templates/agent-docs/_manifest.json +2 -2
  53. package/templates/agent-docs/ai.md +175 -1
  54. package/templates/agent-docs/authentication.md +68 -19
  55. package/templates/agent-docs/cli.md +48 -0
  56. package/templates/agent-docs/configuration.md +29 -4
  57. package/templates/agent-docs/data.md +30 -0
  58. package/templates/agent-docs/database/scaling.md +10 -1
  59. package/templates/agent-docs/database/transactions.md +48 -0
  60. package/templates/agent-docs/deployment.md +22 -0
  61. package/templates/agent-docs/observability.md +13 -2
  62. package/templates/agent-docs/plugins/audit.md +21 -5
  63. package/templates/agent-docs/plugins/auth-supabase.md +21 -0
  64. package/templates/agent-docs/plugins/billing.md +2 -0
  65. package/templates/agent-docs/plugins/sentry.md +91 -8
  66. package/templates/agent-docs/reference.md +1 -0
  67. package/templates/agent-docs/scheduling.md +23 -0
  68. package/templates/agent-docs/schema-driven-ui.md +110 -0
  69. package/templates/agent-docs/whats-new.md +103 -399
  70. package/templates/apps/api-ai/package.json +6 -6
  71. package/templates/apps/api-auth/package.json +8 -8
  72. package/templates/apps/api-backend/package.json +7 -7
  73. package/templates/apps/api-backend-deactivation/package.json +7 -7
  74. package/templates/apps/api-backend-mail/package.json +8 -8
  75. package/templates/apps/api-backend-mariadb/package.json +9 -9
  76. package/templates/apps/api-backend-sqlite/package.json +8 -8
  77. package/templates/apps/api-backend-storage/package.json +8 -8
  78. package/templates/apps/api-cms/package.json +9 -9
  79. package/templates/apps/api-collab/package.json +8 -8
  80. package/templates/apps/api-data-advanced/package.json +8 -8
  81. package/templates/apps/api-durable/package.json +8 -8
  82. package/templates/apps/api-feature-flags/package.json +9 -9
  83. package/templates/apps/api-governance/package.json +8 -8
  84. package/templates/apps/api-kv/package.json +8 -8
  85. package/templates/apps/api-moderation/package.json +8 -8
  86. package/templates/apps/api-observability/package.json +8 -8
  87. package/templates/apps/api-ratelimit/package.json +8 -8
  88. package/templates/apps/api-rbac/package.json +8 -8
  89. package/templates/apps/api-rest/package.json +7 -7
  90. package/templates/apps/api-row-history/package.json +8 -8
  91. package/templates/apps/api-saas/package.json +11 -11
  92. package/templates/apps/api-saas-starter/package.json +10 -10
  93. package/templates/apps/api-search/package.json +8 -8
  94. package/templates/apps/api-status/package.json +8 -8
  95. package/templates/apps/api-webhooks/package.json +9 -9
  96. package/templates/apps/changelog/package.json +7 -7
  97. package/templates/apps/edge-functions/package.json +2 -2
  98. package/templates/apps/frontend-admin/package.json +7 -7
  99. package/templates/apps/frontend-app/package.json +8 -8
  100. package/templates/apps/frontend-auth/package.json +7 -7
  101. package/templates/apps/frontend-blank/package.json +6 -6
  102. package/templates/apps/frontend-cms/package.json +8 -8
  103. package/templates/apps/frontend-collab/package.json +9 -9
  104. package/templates/apps/frontend-contact/package.json +7 -7
  105. package/templates/apps/frontend-dashboard/package.json +6 -6
  106. package/templates/apps/frontend-docs/package.json +8 -8
  107. package/templates/apps/frontend-i18n/package.json +6 -6
  108. package/templates/apps/frontend-landing/package.json +7 -7
  109. package/templates/apps/frontend-portal/package.json +7 -7
  110. package/templates/apps/frontend-saas/package.json +7 -7
  111. package/templates/apps/frontend-spa/package.json +6 -6
  112. package/templates/apps/frontend-ssr/package.json +6 -6
  113. package/templates/apps/frontend-ssr-api/package.json +7 -7
  114. package/templates/apps/frontend-static-blog/package.json +8 -8
  115. package/templates/apps/frontend-status/package.json +7 -7
  116. package/templates/apps/mobile-app/package.json +4 -4
  117. package/dist/apiBuild-Vw1figjO.js +0 -2
  118. package/dist/build-CI36wL4R.js +0 -868
  119. package/dist/dbCommand-DrycGWWt.js +0 -2
  120. package/dist/dev-B9Gz0k85.js +0 -3
  121. package/dist/doctorCommand-BMWs6aVm.js +0 -2
  122. package/dist/frameworkTableAssembly-CGNC0qr7.js +0 -2
  123. package/dist/inspect-S2rWy1Ys.js +0 -2
  124. package/dist/manifestBuild-DIa_s6u0.js +0 -2
  125. package/dist/serveCommand-BITS8Hpj.js +0 -2
  126. package/dist/serveCommand-DIJ3ma76.js +0 -2485
  127. package/dist/updateCommand-DsXEAHbd.js +0 -2
  128. package/dist/webDev-C2dRz9s5.js +0 -2
@@ -2,13 +2,13 @@ import { A as e, D as t, I as n, O as r, _ as i, d as a, f as o, i as s, m as c,
2
2
  import { t as d } from "./pageConvention-CMpfDN6r.js";
3
3
  import { r as f } from "./tsconfigPaths-BWXBWgcl.js";
4
4
  import { a as p, i as m, t as h } from "./fileTaxonomy-CbyMQYx_.js";
5
- import { basename as g, dirname as _, join as v, relative as ee } from "node:path";
6
- import { existsSync as y, readFileSync as te, statSync as ne } from "node:fs";
7
- import { execFileSync as re } from "node:child_process";
8
- import { Node as b, Project as ie, QuoteKind as ae, SyntaxKind as x } from "ts-morph";
9
- import { globSync as oe } from "tinyglobby";
5
+ import { basename as g, dirname as _, join as v, relative as y } from "node:path";
6
+ import { existsSync as b, readFileSync as ee, statSync as te } from "node:fs";
7
+ import { execFileSync as ne } from "node:child_process";
8
+ import { Node as x, Project as re, QuoteKind as ie, SyntaxKind as S } from "ts-morph";
9
+ import { globSync as ae } from "tinyglobby";
10
10
  //#region src/codemodHelpers.ts
11
- var S = (e, t, n) => {
11
+ var C = (e, t, n) => {
12
12
  let r = [];
13
13
  for (let i of e.getImportDeclarations()) if (i.getModuleSpecifierValue() === t) for (let e of i.getNamedImports()) {
14
14
  if (e.getName() !== n) continue;
@@ -16,70 +16,70 @@ var S = (e, t, n) => {
16
16
  r.push(t ? t.getText() : e.getName());
17
17
  }
18
18
  return r;
19
- }, C = (e, t) => {
19
+ }, w = (e, t) => {
20
20
  let n = e.getExpression();
21
- return b.isIdentifier(n) && t.includes(n.getText());
22
- }, se = (e, { module: t, from: n, to: r }) => {
21
+ return x.isIdentifier(n) && t.includes(n.getText());
22
+ }, oe = (e, { module: t, from: n, to: r }) => {
23
23
  let i = 0;
24
24
  for (let a of e.getSourceFiles()) for (let e of a.getImportDeclarations()) if (e.getModuleSpecifierValue() === t) for (let t of e.getNamedImports()) {
25
25
  if (t.getName() !== n) continue;
26
26
  let e = t.getNameNode();
27
- !t.getAliasNode() && b.isIdentifier(e) ? e.rename(r) : e.replaceWithText(r), i++;
27
+ !t.getAliasNode() && x.isIdentifier(e) ? e.rename(r) : e.replaceWithText(r), i++;
28
28
  }
29
29
  return i;
30
- }, ce = (e, { from: t, to: n }) => {
30
+ }, se = (e, { from: t, to: n }) => {
31
31
  let r = 0;
32
32
  for (let i of e.getSourceFiles()) {
33
33
  for (let e of i.getImportDeclarations()) e.getModuleSpecifierValue() === t && (e.setModuleSpecifier(n), r++);
34
34
  for (let e of i.getExportDeclarations()) e.getModuleSpecifierValue() === t && (e.setModuleSpecifier(n), r++);
35
- for (let e of i.getDescendantsOfKind(x.CallExpression)) {
36
- if (e.getExpression().getKind() !== x.ImportKeyword) continue;
35
+ for (let e of i.getDescendantsOfKind(S.CallExpression)) {
36
+ if (e.getExpression().getKind() !== S.ImportKeyword) continue;
37
37
  let i = e.getArguments()[0];
38
- i && b.isStringLiteral(i) && i.getLiteralValue() === t && (i.replaceWithText(JSON.stringify(n)), r++);
38
+ i && x.isStringLiteral(i) && i.getLiteralValue() === t && (i.replaceWithText(JSON.stringify(n)), r++);
39
39
  }
40
40
  }
41
41
  return r;
42
- }, le = (e, { component: t, from: n, to: r }) => {
42
+ }, ce = (e, { component: t, from: n, to: r }) => {
43
43
  let i = 0;
44
44
  for (let a of e.getSourceFiles()) {
45
- let e = [...a.getDescendantsOfKind(x.JsxOpeningElement), ...a.getDescendantsOfKind(x.JsxSelfClosingElement)];
46
- for (let a of e) if (a.getTagNameNode().getText() === t) for (let e of a.getAttributes()) b.isJsxAttribute(e) && e.getNameNode().getText() === n && (e.getNameNode().replaceWithText(r), i++);
45
+ let e = [...a.getDescendantsOfKind(S.JsxOpeningElement), ...a.getDescendantsOfKind(S.JsxSelfClosingElement)];
46
+ for (let a of e) if (a.getTagNameNode().getText() === t) for (let e of a.getAttributes()) x.isJsxAttribute(e) && e.getNameNode().getText() === n && (e.getNameNode().replaceWithText(r), i++);
47
47
  }
48
48
  return i;
49
- }, ue = (e, { inCallTo: t, module: n, from: r, to: i }) => {
49
+ }, le = (e, { inCallTo: t, module: n, from: r, to: i }) => {
50
50
  let a = 0;
51
51
  for (let o of e.getSourceFiles()) {
52
- let e = S(o, n, t);
53
- if (e.length !== 0) for (let t of o.getDescendantsOfKind(x.CallExpression).reverse()) {
54
- if (!C(t, e)) continue;
52
+ let e = C(o, n, t);
53
+ if (e.length !== 0) for (let t of o.getDescendantsOfKind(S.CallExpression).reverse()) {
54
+ if (!w(t, e)) continue;
55
55
  let n = t.getArguments()[0];
56
- if (!(!n || !b.isObjectLiteralExpression(n))) for (let e of n.getProperties()) {
57
- if (!b.isPropertyAssignment(e) && !b.isShorthandPropertyAssignment(e)) continue;
56
+ if (!(!n || !x.isObjectLiteralExpression(n))) for (let e of n.getProperties()) {
57
+ if (!x.isPropertyAssignment(e) && !x.isShorthandPropertyAssignment(e)) continue;
58
58
  let t = e.getNameNode();
59
- t.getText() === r && (b.isShorthandPropertyAssignment(e) ? e.replaceWithText(`${i}: ${r}`) : t.replaceWithText(i), a++);
59
+ t.getText() === r && (x.isShorthandPropertyAssignment(e) ? e.replaceWithText(`${i}: ${r}`) : t.replaceWithText(i), a++);
60
60
  }
61
61
  }
62
62
  }
63
63
  return a;
64
- }, de = (e, { module: t, name: n }) => {
64
+ }, ue = (e, { module: t, name: n }) => {
65
65
  let r = 0;
66
66
  for (let i of e.getSourceFiles()) {
67
67
  let e = i.getImportDeclaration((e) => e.getModuleSpecifierValue() === t);
68
68
  e && (e.getNamedImports().some((e) => e.getName() === n) || (e.addNamedImport(n), r++));
69
69
  }
70
70
  return r;
71
- }, fe = (e, t, n) => {
71
+ }, de = (e, t, n) => {
72
72
  let r = e.getImportDeclaration((e) => e.getModuleSpecifierValue() === t);
73
73
  r ? r.getNamedImports().some((e) => e.getName() === n) || r.addNamedImport(n) : e.addImportDeclaration({
74
74
  moduleSpecifier: t,
75
75
  namedImports: [n]
76
76
  });
77
- }, pe = {
78
- renameImport: se,
79
- renameModuleSpecifier: ce,
80
- renameJsxProp: le,
81
- renameObjectKey: ue,
82
- addImport: de,
77
+ }, fe = {
78
+ renameImport: oe,
79
+ renameModuleSpecifier: se,
80
+ renameJsxProp: ce,
81
+ renameObjectKey: le,
82
+ addImport: ue,
83
83
  removeImport: (e, { module: t, name: n }) => {
84
84
  let r = 0;
85
85
  for (let i of e.getSourceFiles()) for (let e of i.getImportDeclarations()) {
@@ -92,9 +92,9 @@ var S = (e, t, n) => {
92
92
  changeCallArgs: (e, { module: t, callee: n, reorder: r, drop: i, toObject: a }) => {
93
93
  let o = 0;
94
94
  for (let s of e.getSourceFiles()) {
95
- let e = S(s, t, n);
96
- if (e.length !== 0) for (let t of s.getDescendantsOfKind(x.CallExpression).reverse()) {
97
- if (!C(t, e)) continue;
95
+ let e = C(s, t, n);
96
+ if (e.length !== 0) for (let t of s.getDescendantsOfKind(S.CallExpression).reverse()) {
97
+ if (!w(t, e)) continue;
98
98
  let n = t.getArguments().map((e) => e.getText());
99
99
  if (r && (n = r.map((e) => n[e]).filter((e) => e !== void 0)), i) {
100
100
  let e = new Set(i);
@@ -113,31 +113,31 @@ var S = (e, t, n) => {
113
113
  wrapCall: (e, { module: t, callee: n, wrapper: r, wrapperModule: i }) => {
114
114
  let a = 0;
115
115
  for (let o of e.getSourceFiles()) {
116
- let e = S(o, t, n);
116
+ let e = C(o, t, n);
117
117
  if (e.length === 0) continue;
118
118
  let s = !1;
119
- for (let t of o.getDescendantsOfKind(x.CallExpression).reverse()) C(t, e) && (t.replaceWithText(`${r}(${t.getText()})`), s = !0, a++);
120
- s && fe(o, i, r);
119
+ for (let t of o.getDescendantsOfKind(S.CallExpression).reverse()) w(t, e) && (t.replaceWithText(`${r}(${t.getText()})`), s = !0, a++);
120
+ s && de(o, i, r);
121
121
  }
122
122
  return a;
123
123
  },
124
124
  annotate: (e, { module: t, symbol: n, comment: r }) => {
125
125
  let i = 0;
126
126
  for (let a of e.getSourceFiles()) {
127
- let e = S(a, t, n);
127
+ let e = C(a, t, n);
128
128
  if (e.length === 0) continue;
129
129
  let o = /* @__PURE__ */ new Map();
130
- for (let t of a.getDescendantsOfKind(x.Identifier)) {
130
+ for (let t of a.getDescendantsOfKind(S.Identifier)) {
131
131
  if (!e.includes(t.getText())) continue;
132
- let n = t.getFirstAncestorByKind(x.ExpressionStatement) ?? t.getFirstAncestorByKind(x.VariableStatement) ?? t.getFirstAncestorByKind(x.ReturnStatement);
132
+ let n = t.getFirstAncestorByKind(S.ExpressionStatement) ?? t.getFirstAncestorByKind(S.VariableStatement) ?? t.getFirstAncestorByKind(S.ReturnStatement);
133
133
  n && o.set(n.getStart(), n);
134
134
  }
135
135
  for (let e of [...o.values()].sort((e, t) => t.getStart() - e.getStart())) e.replaceWithText(`// TODO(voltro-migration): ${r}\n${e.getText()}`), i++;
136
136
  }
137
137
  return i;
138
138
  }
139
- }, me = /^\d+\.\d+\.\d+$/, w = (e) => {
140
- if (!me.test(e.version)) throw Error(`codemod "${e.id}": version must be x.y.z (got "${e.version}")`);
139
+ }, pe = /^\d+\.\d+\.\d+$/, T = (e) => {
140
+ if (!pe.test(e.version)) throw Error(`codemod "${e.id}": version must be x.y.z (got "${e.version}")`);
141
141
  if (!e.id.startsWith(`${e.version}/`)) throw Error(`codemod "${e.id}": id must start with "${e.version}/" (the version it migrates to)`);
142
142
  if (e.kind === "transform" && typeof e.transform != "function") throw Error(`codemod "${e.id}": kind:'transform' requires a transform() function`);
143
143
  if (e.kind === "manual" && (e.note === void 0 || e.note.trim() === "")) throw Error(`codemod "${e.id}": kind:'manual' requires a non-empty note`);
@@ -148,14 +148,14 @@ var S = (e, t, n) => {
148
148
  __codemod: !0,
149
149
  seq: n
150
150
  };
151
- }, T = (e) => RegExp(`\\b${e}\\s*(?:<[^()]*>)?\\s*\\(`), he = (e, t) => {
151
+ }, E = (e) => RegExp(`\\b${e}\\s*(?:<[^()]*>)?\\s*\\(`), me = (e, t) => {
152
152
  let n = e.version.split(".").map(Number), r = t.version.split(".").map(Number);
153
153
  for (let e = 0; e < 3; e++) {
154
154
  let t = (n[e] ?? 0) - (r[e] ?? 0);
155
155
  if (t !== 0) return t;
156
156
  }
157
157
  return e.seq === t.seq ? e.id < t.id ? -1 : +(e.id > t.id) : e.seq - t.seq;
158
- }, ge = w({
158
+ }, he = T({
159
159
  version: "0.3.0",
160
160
  id: "0.3.0/01_prod-serve-requires-build",
161
161
  title: "Production `voltro serve` requires a precompiled bundle (build before serve)",
@@ -175,7 +175,7 @@ var S = (e, t, n) => {
175
175
  "",
176
176
  "`voltro dev` and a non-production local `voltro serve` are unchanged (still tsx)."
177
177
  ].join("\n")
178
- }), _e = w({
178
+ }), ge = T({
179
179
  version: "0.4.0",
180
180
  id: "0.4.0/01_rbac-forbidden-to-scopeerror",
181
181
  title: "@voltro/plugin-rbac: `Forbidden` → protocol `ScopeError` (one denial tag)",
@@ -201,36 +201,36 @@ var S = (e, t, n) => {
201
201
  let a = r + i;
202
202
  a > 0 && n(`rbac Forbidden → ScopeError: rewrote ${a} import site(s). Check any \`new Forbidden({ reason })\` (→ \`new ScopeError({ message })\`) and \`catchTag('Forbidden')\` / \`_tag === 'Forbidden'\` (→ 'ScopeError') by hand.`);
203
203
  }
204
- }), E = "@voltro/plugin-rbac/web", ve = "@voltro/client", ye = w({
204
+ }), D = "@voltro/plugin-rbac/web", _e = "@voltro/client", ve = T({
205
205
  version: "0.4.0",
206
206
  id: "0.4.0/02_rbac-web-to-client-permissions",
207
207
  title: "@voltro/plugin-rbac/web removed — the UI gate lives in @voltro/client",
208
208
  kind: "transform",
209
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getImportDeclarations().some((e) => e.getModuleSpecifierValue() === E)),
209
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getImportDeclarations().some((e) => e.getModuleSpecifierValue() === D)),
210
210
  transform: ({ project: e, helpers: t, log: n }) => {
211
211
  let r = t.annotate(e, {
212
- module: E,
212
+ module: D,
213
213
  symbol: "RbacScopeContext",
214
214
  comment: "voltro 0.4.0: RbacScopeContext is gone. Read scopes with `usePermissions()` from '@voltro/client' — it returns { scopes }, not the bare array."
215
215
  }), i = t.renameImport(e, {
216
- module: E,
216
+ module: D,
217
217
  from: "RbacScopeProvider",
218
218
  to: "PermissionProvider"
219
219
  }) + t.renameImport(e, {
220
- module: E,
220
+ module: D,
221
221
  from: "canFromScopes",
222
222
  to: "canCall"
223
223
  }) + t.renameImport(e, {
224
- module: E,
224
+ module: D,
225
225
  from: "canAnyFromScopes",
226
226
  to: "canCallAny"
227
227
  }), a = t.renameModuleSpecifier(e, {
228
- from: E,
229
- to: ve
228
+ from: D,
229
+ to: _e
230
230
  });
231
231
  a > 0 && n(`@voltro/plugin-rbac/web → @voltro/client: repointed ${a} import(s)` + (i > 0 ? `, renamed ${i} symbol(s)` : "") + ". Mount <PermissionProvider scopes={…}> where <RbacScopeProvider> was — same prop, and it now feeds useCan across the whole app." + (r > 0 ? ` ${r} RbacScopeContext site(s) were annotated: switch them to usePermissions().` : ""));
232
232
  }
233
- }), be = w({
233
+ }), ye = T({
234
234
  version: "0.5.0",
235
235
  id: "0.5.0/01_one-terminal-exactly-one",
236
236
  title: ".one() now fails on MORE than one row; NoRowFound is a declarable typed error",
@@ -263,7 +263,7 @@ var S = (e, t, n) => {
263
263
  " new OptimisticLockError({ table: 'notes', expected: 3 })",
264
264
  " • `instanceof` and `err._tag === 'NoRowFound'` checks are unaffected."
265
265
  ].join("\n")
266
- }), xe = w({
266
+ }), be = T({
267
267
  version: "0.5.0",
268
268
  id: "0.5.0/02_typed-store-rows",
269
269
  title: "store.query() now returns the row type — casts on row fields may stop compiling",
@@ -302,14 +302,14 @@ var S = (e, t, n) => {
302
302
  "A hand-built descriptor still resolves to `Row`, so untyped call sites are",
303
303
  "unaffected."
304
304
  ].join("\n")
305
- }), Se = /VOLTRO_DEV_SESSION_SECRET|voltro-dev-session-secret/, Ce = /VOLTRO_SESSION_SECRET\s*(?:\?\?|\|\|)\s*['"`]/, we = w({
305
+ }), xe = /VOLTRO_DEV_SESSION_SECRET|voltro-dev-session-secret/, Se = /VOLTRO_SESSION_SECRET\s*(?:\?\?|\|\|)\s*['"`]/, Ce = T({
306
306
  version: "0.6.0",
307
307
  id: "0.6.0/01_no-dev-session-secret",
308
308
  title: "The built-in dev session secret is removed; VOLTRO_SESSION_SECRET is required",
309
309
  kind: "manual",
310
310
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
311
311
  let t = e.getFullText();
312
- return Se.test(t) || Ce.test(t);
312
+ return xe.test(t) || Se.test(t);
313
313
  }),
314
314
  note: [
315
315
  "The framework no longer has a built-in fallback session secret.",
@@ -353,14 +353,14 @@ var S = (e, t, n) => {
353
353
  " voltro secret generate session",
354
354
  " `voltro dev` minting one for you locally does NOT cover your deployment."
355
355
  ].join("\n")
356
- }), Te = /computeProration|roundHalfAwayFromZero|ProrationInput|ProrationResult|DunningState|DunningCycleResult|DunningSchedule|DEFAULT_DUNNING_SCHEDULE|validateDunningSchedule|retryTimeFor|openDunning|recoverDunning|advanceDunning|runDunningCycle|recordPaymentFailure|recordPaymentSuccess|dunningSchedule|DunningStore|memoryDunningStore|dataStoreDunningStore/, Ee = w({
356
+ }), we = /computeProration|roundHalfAwayFromZero|ProrationInput|ProrationResult|DunningState|DunningCycleResult|DunningSchedule|DEFAULT_DUNNING_SCHEDULE|validateDunningSchedule|retryTimeFor|openDunning|recoverDunning|advanceDunning|runDunningCycle|recordPaymentFailure|recordPaymentSuccess|dunningSchedule|DunningStore|memoryDunningStore|dataStoreDunningStore/, Te = T({
357
357
  version: "0.6.0",
358
358
  id: "0.6.0/03_billing-on-stripe-sdk",
359
359
  title: "Billing moved onto the Stripe SDK; proration + dunning are now Stripe’s",
360
360
  kind: "manual",
361
361
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
362
362
  let t = e.getFullText();
363
- return Te.test(t) || /@voltro\/plugin-billing/.test(t);
363
+ return we.test(t) || /@voltro\/plugin-billing/.test(t);
364
364
  }),
365
365
  note: [
366
366
  "`@voltro/plugin-billing` now talks to Stripe through the official SDK, and two",
@@ -416,7 +416,7 @@ var S = (e, t, n) => {
416
416
  " Set a `priceId` on every paid plan. A subscription created in the Stripe",
417
417
  " Dashboard is now recognised via its price id, which requires the mapping."
418
418
  ].join("\n")
419
- }), De = w({
419
+ }), Ee = T({
420
420
  version: "0.6.0",
421
421
  id: "0.6.0/02_subscription-state-union",
422
422
  title: "SubscriptionState is a discriminated union — `loading` now narrows `data`",
@@ -463,7 +463,7 @@ var S = (e, t, n) => {
463
463
  "`error` set. If you branch only on `loading`, check `error` too, or that",
464
464
  "case renders a skeleton forever."
465
465
  ].join("\n")
466
- }), Oe = w({
466
+ }), De = T({
467
467
  version: "0.6.0",
468
468
  id: "0.6.0/04_relation-key-validation",
469
469
  title: "relation key options are validated at boot; `one` now fails on more than one match",
@@ -504,28 +504,28 @@ var S = (e, t, n) => {
504
504
  "read it now rather than waiting: `sourceKey` is almost always what a",
505
505
  "\"parent\" relation means."
506
506
  ].join("\n")
507
- }), ke = "@voltro/plugin-atlassian", Ae = "atlassianPlugin", D = (e) => {
507
+ }), Oe = "@voltro/plugin-atlassian", ke = "atlassianPlugin", Ae = (e) => {
508
508
  let t = [];
509
- for (let n of e.getImportDeclarations()) if (n.getModuleSpecifierValue() === ke) for (let e of n.getNamedImports()) e.getName() === Ae && t.push(e.getAliasNode()?.getText() ?? e.getName());
509
+ for (let n of e.getImportDeclarations()) if (n.getModuleSpecifierValue() === Oe) for (let e of n.getNamedImports()) e.getName() === ke && t.push(e.getAliasNode()?.getText() ?? e.getName());
510
510
  return t;
511
- }, je = w({
511
+ }, je = T({
512
512
  version: "0.6.0",
513
513
  id: "0.6.0/05_atlassian-avatar-mode",
514
514
  title: "atlassianPlugin's `avatar` config requires an explicit `mode: 'service' | 'perUser'`",
515
515
  kind: "transform",
516
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => D(e).length > 0),
516
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Ae(e).length > 0),
517
517
  transform: ({ project: e, log: t }) => {
518
518
  let n = 0;
519
519
  for (let t of e.getSourceFiles()) {
520
- let e = D(t);
521
- if (e.length !== 0) for (let r of t.getDescendantsOfKind(x.CallExpression)) {
520
+ let e = Ae(t);
521
+ if (e.length !== 0) for (let r of t.getDescendantsOfKind(S.CallExpression)) {
522
522
  if (!e.includes(r.getExpression().getText())) continue;
523
523
  let t = r.getArguments()[0];
524
- if (!t || !b.isObjectLiteralExpression(t)) continue;
524
+ if (!t || !x.isObjectLiteralExpression(t)) continue;
525
525
  let i = t.getProperty("avatar");
526
- if (!i || !b.isPropertyAssignment(i)) continue;
526
+ if (!i || !x.isPropertyAssignment(i)) continue;
527
527
  let a = i.getInitializer();
528
- !a || !b.isObjectLiteralExpression(a) || a.getProperty("mode") || (a.insertPropertyAssignment(0, {
528
+ !a || !x.isObjectLiteralExpression(a) || a.getProperty("mode") || (a.insertPropertyAssignment(0, {
529
529
  name: "mode",
530
530
  initializer: "'service'"
531
531
  }), n++);
@@ -533,7 +533,7 @@ var S = (e, t, n) => {
533
533
  }
534
534
  n > 0 && (t(`atlassian avatar: added mode:'service' to ${n} config${n === 1 ? "" : "s"}`), t(" → to use the viewing user's own PAT instead, switch to mode:'perUser' and add resolveSubject"));
535
535
  }
536
- }), Me = w({
536
+ }), Me = T({
537
537
  version: "0.6.0",
538
538
  id: "0.6.0/06_ui-strings-connect-account",
539
539
  title: "UiStrings gained a required `connectAccount` section",
@@ -577,7 +577,7 @@ var S = (e, t, n) => {
577
577
  "Alternatively, switch the annotation to `PartialUiStrings` if the object",
578
578
  "was only ever an override bag."
579
579
  ].join("\n")
580
- }), Ne = T("setRowFilter"), Pe = /\bresolveRowFilterScope\b/, Fe = w({
580
+ }), Ne = E("setRowFilter"), Pe = /\bresolveRowFilterScope\b/, Fe = T({
581
581
  version: "0.7.0",
582
582
  id: "0.7.0/01_row-filter-load-failure",
583
583
  title: "A row filter that fails to load now errors instead of returning zero rows",
@@ -655,14 +655,14 @@ var S = (e, t, n) => {
655
655
  " \"every row you could see was just deleted\". Make sure your subscription",
656
656
  " error handling surfaces this rather than swallowing it."
657
657
  ].join("\n")
658
- }), Ie = w({
658
+ }), Ie = T({
659
659
  version: "0.7.0",
660
660
  id: "0.7.0/02_testing-invoke-effect-handlers",
661
661
  title: "@voltro/testing: invoke() runs Effect handlers; executor types widened",
662
662
  kind: "manual",
663
663
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
664
664
  let t = e.getFullText();
665
- return t.includes("@voltro/testing") ? T("invoke").test(t) || /\bProcedureExecutor\b/.test(t) : !1;
665
+ return t.includes("@voltro/testing") ? E("invoke").test(t) || /\bProcedureExecutor\b/.test(t) : !1;
666
666
  }),
667
667
  note: [
668
668
  "`invoke(descriptor, executor, input, ctx)` now DETECTS an Effect-returning",
@@ -710,7 +710,7 @@ var S = (e, t, n) => {
710
710
  "transaction, the deadlock replay, `afterCommit` and the plugin interceptor",
711
711
  "chain wrap an Effect handler exactly as they wrap an async one."
712
712
  ].join("\n")
713
- }), Le = /\bpasswordHash\b/, Re = /\b(UserStore|UserRecord|memoryUserStore|postgresUserStore)\b/, ze = w({
713
+ }), Le = /\bpasswordHash\b/, Re = /\b(UserStore|UserRecord|memoryUserStore|postgresUserStore)\b/, ze = T({
714
714
  version: "0.8.0",
715
715
  id: "0.8.0/01_optional-password-hash",
716
716
  title: "`UserRecord.passwordHash` is optional — password-less identities are now first-class",
@@ -788,7 +788,7 @@ var S = (e, t, n) => {
788
788
  " to match and make sure your read path maps a NULL column to `null`/",
789
789
  " `undefined` rather than to `''`-by-coercion."
790
790
  ].join("\n")
791
- }), Be = w({
791
+ }), Be = T({
792
792
  version: "0.8.0",
793
793
  id: "0.8.0/02_ssr-streams-and-loaderdata",
794
794
  title: "`ssr` pages stream; `useLoaderData<T>()` returns `LoaderData<T>`",
@@ -840,7 +840,7 @@ var S = (e, t, n) => {
840
840
  "docs. `defer()` requires `renderMode: 'ssr'` and `interactive: 'full'`;",
841
841
  "every other combination is a hard error naming the page."
842
842
  ].join("\n")
843
- }), Ve = /renderMode\s*[=:]\s*['"](spa|client)['"]/, He = /(^|[/\\])pages[/\\].*[/\\]?layout\.tsx$/, Ue = w({
843
+ }), Ve = /renderMode\s*[=:]\s*['"](spa|client)['"]/, He = /(^|[/\\])pages[/\\].*[/\\]?layout\.tsx$/, Ue = T({
844
844
  version: "0.9.0",
845
845
  id: "0.9.0/01_ssr-layout-shell-for-spa",
846
846
  title: "A `renderMode:'spa'` page under a layout now server-renders that layout",
@@ -885,35 +885,35 @@ var S = (e, t, n) => {
885
885
  "Nothing changes for `static` / `ssr` / `isr` pages, or for a spa page that",
886
886
  "has no layout (still a pure client mount)."
887
887
  ].join("\n")
888
- }), O = "client", k = "spa", A = (e) => {
888
+ }), O = "client", We = "spa", k = (e) => {
889
889
  if (!e) return null;
890
- let t = b.isAsExpression(e) ? e.getExpression() : e;
891
- return b.isStringLiteral(t) ? t : null;
892
- }, j = (e) => {
890
+ let t = x.isAsExpression(e) ? e.getExpression() : e;
891
+ return x.isStringLiteral(t) ? t : null;
892
+ }, Ge = (e) => {
893
893
  let t = [];
894
- for (let n of e.getDescendantsOfKind(x.VariableDeclaration)) {
894
+ for (let n of e.getDescendantsOfKind(S.VariableDeclaration)) {
895
895
  if (n.getName() !== "renderMode") continue;
896
- let e = A(n.getInitializer());
896
+ let e = k(n.getInitializer());
897
897
  e && t.push(e);
898
898
  }
899
- for (let n of e.getDescendantsOfKind(x.PropertyAssignment)) {
899
+ for (let n of e.getDescendantsOfKind(S.PropertyAssignment)) {
900
900
  if (n.getName() !== "renderMode") continue;
901
- let e = A(n.getInitializer());
901
+ let e = k(n.getInitializer());
902
902
  e && t.push(e);
903
903
  }
904
904
  return t;
905
- }, We = w({
905
+ }, Ke = T({
906
906
  version: "0.10.0",
907
907
  id: "0.10.0/01_render-mode-client-to-spa",
908
908
  title: "renderMode is a closed set — `'client'` becomes `'spa'`",
909
909
  kind: "transform",
910
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => j(e).some((e) => e.getLiteralValue() === O)),
910
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Ge(e).some((e) => e.getLiteralValue() === O)),
911
911
  transform: ({ project: e, log: t }) => {
912
912
  let n = 0, r = /* @__PURE__ */ new Set();
913
- for (let t of e.getSourceFiles()) for (let e of j(t)) e.getLiteralValue() === O && (e.setLiteralValue(k), n++, r.add(t.getFilePath()));
914
- n > 0 && (t(`renderMode: rewrote ${n} \`'${O}'\` → \`'${k}'\` in ${r.size} file${r.size === 1 ? "" : "s"}`), t(" → 'spa' is the mode those pages already behaved as: no prerender, client-rendered"), t(" → new in 0.9.0: if such a page sits under a layout, that layout now server-renders (see the note above)"));
913
+ for (let t of e.getSourceFiles()) for (let e of Ge(t)) e.getLiteralValue() === O && (e.setLiteralValue(We), n++, r.add(t.getFilePath()));
914
+ n > 0 && (t(`renderMode: rewrote ${n} \`'${O}'\` → \`'${We}'\` in ${r.size} file${r.size === 1 ? "" : "s"}`), t(" → 'spa' is the mode those pages already behaved as: no prerender, client-rendered"), t(" → new in 0.9.0: if such a page sits under a layout, that layout now server-renders (see the note above)"));
915
915
  }
916
- }), Ge = w({
916
+ }), qe = T({
917
917
  version: "0.10.0",
918
918
  id: "0.10.0/02_router-state-carries-its-path",
919
919
  title: "`renderRouterStateScript` now requires the `path` it rendered",
@@ -950,7 +950,7 @@ var S = (e, t, n) => {
950
950
  "Trailing slashes, a trailing `/index.html`, and percent-encoding are normalised",
951
951
  "before the comparison, so the usual static-host shapes still hydrate."
952
952
  ].join("\n")
953
- }), Ke = w({
953
+ }), Je = T({
954
954
  version: "0.10.0",
955
955
  id: "0.10.0/03_workflow-caller-context-resolved",
956
956
  title: "workflow `buildContext` receives a RESOLVED caller context",
@@ -996,7 +996,7 @@ var S = (e, t, n) => {
996
996
  "it receives the resolved context too, so `callerContext?.subject ?? …` there",
997
997
  "becomes `callerContext.subject`."
998
998
  ].join("\n")
999
- }), qe = w({
999
+ }), Ye = T({
1000
1000
  version: "0.10.0",
1001
1001
  id: "0.10.0/04_seed-run-status-skipped",
1002
1002
  title: "seed run status gained `skipped`",
@@ -1037,7 +1037,7 @@ var S = (e, t, n) => {
1037
1037
  "`Schema.Literal`, a zod enum, a DB check constraint), widen it there too — an",
1038
1038
  "unlisted third state is a DECODE failure, not a rendering oddity."
1039
1039
  ].join("\n")
1040
- }), Je = w({
1040
+ }), Xe = T({
1041
1041
  version: "0.10.0",
1042
1042
  id: "0.10.0/05_subscription-idle-state",
1043
1043
  title: "`useSubscription` gained an idle state for `skip`",
@@ -1089,7 +1089,7 @@ var S = (e, t, n) => {
1089
1089
  "time's data), hold it in your own state — the subscription no longer claims to",
1090
1090
  "have data it was told to stop fetching."
1091
1091
  ].join("\n")
1092
- }), Ye = w({
1092
+ }), Ze = T({
1093
1093
  version: "0.11.0",
1094
1094
  id: "0.11.0/01_step-options-type-params",
1095
1095
  title: "`StepOptions` takes the context type first",
@@ -1117,7 +1117,7 @@ var S = (e, t, n) => {
1117
1117
  "If the step reads nothing from earlier steps, `StepOptions<Record<never, never>,",
1118
1118
  "Result>` is the honest spelling."
1119
1119
  ].join("\n")
1120
- }), Xe = w({
1120
+ }), Qe = T({
1121
1121
  version: "0.12.0",
1122
1122
  id: "0.12.0/01_outbound-http-ssrf-guard",
1123
1123
  title: "Outbound HttpClient blocks internal targets by default (add `http.allowHosts` if you call one)",
@@ -1157,7 +1157,7 @@ var S = (e, t, n) => {
1157
1157
  "private address (DNS rebinding) still passes. Pair with network-layer egress",
1158
1158
  "control if that is in your threat model."
1159
1159
  ].join("\n")
1160
- }), Ze = w({
1160
+ }), $e = T({
1161
1161
  version: "0.12.0",
1162
1162
  id: "0.12.0/02_api-key-revoke-tenant-scope",
1163
1163
  title: "apiKeyService.revoke/rotate now require the caller's tenantId",
@@ -1186,7 +1186,7 @@ var S = (e, t, n) => {
1186
1186
  "The framework's own `/v1/api-keys/revoke` route already passes the caller's",
1187
1187
  "tenant — no change needed if you only use the shipped routes."
1188
1188
  ].join("\n")
1189
- }), Qe = w({
1189
+ }), et = T({
1190
1190
  version: "0.12.0",
1191
1191
  id: "0.12.0/03_api-key-on-behalf-of",
1192
1192
  title: "API keys: createdBy is provenance, onBehalfOf is ownership",
@@ -1220,7 +1220,7 @@ var S = (e, t, n) => {
1220
1220
  "specific one against what the body asks for. Grant them in your role map. A key",
1221
1221
  "can never carry scopes its issuer does not hold."
1222
1222
  ].join("\n")
1223
- }), $e = w({
1223
+ }), tt = T({
1224
1224
  version: "0.12.0",
1225
1225
  id: "0.12.0/04_inspect-fail-closed",
1226
1226
  title: "The inspect surface requires VOLTRO_INSPECT_TOKEN (previously open when unset)",
@@ -1249,7 +1249,7 @@ var S = (e, t, n) => {
1249
1249
  "",
1250
1250
  "To turn the surface off entirely instead, set `VOLTRO_INSPECT=off`."
1251
1251
  ].join("\n")
1252
- }), et = w({
1252
+ }), nt = T({
1253
1253
  version: "0.13.0",
1254
1254
  id: "0.13.0/01_bearer-gates-fail-closed",
1255
1255
  title: "checkBearer is fail-closed; scimPlugin refuses an empty token",
@@ -1286,7 +1286,7 @@ var S = (e, t, n) => {
1286
1286
  " • `@voltro/plugin-prometheus` already passes `{ openWhenUnset: true }` — its",
1287
1287
  " `PROMETHEUS_TOKEN` is documented as optional. Nothing to do there."
1288
1288
  ].join("\n")
1289
- }), tt = w({
1289
+ }), rt = T({
1290
1290
  version: "0.13.0",
1291
1291
  id: "0.13.0/02_storage-grants-owner-only",
1292
1292
  title: "storage.share / revoke / listGrants require ownership",
@@ -1317,7 +1317,7 @@ var S = (e, t, n) => {
1317
1317
  " returning the grant or null. `storage.revoke` is handed a grant id, so it has",
1318
1318
  " to resolve the object before it can check the owner."
1319
1319
  ].join("\n")
1320
- }), nt = w({
1320
+ }), it = T({
1321
1321
  version: "0.13.0",
1322
1322
  id: "0.13.0/03_apply-plan-convergence-proof",
1323
1323
  title: "applyPlan requires a `replan` convergence proof",
@@ -1350,7 +1350,7 @@ var S = (e, t, n) => {
1350
1350
  " • `AppliedMigration` gained `appliedOps` (operations actually EXECUTED, from",
1351
1351
  " the per-op results). If you construct that value in a test fixture, add it."
1352
1352
  ].join("\n")
1353
- }), rt = w({
1353
+ }), at = T({
1354
1354
  version: "0.14.0",
1355
1355
  id: "0.14.0/01_resolve-scopes-store",
1356
1356
  title: "auth.resolveScopes receives the app DataStore",
@@ -1388,19 +1388,19 @@ var S = (e, t, n) => {
1388
1388
  " deliberately does not, because only you know how fast a role change must",
1389
1389
  " take effect."
1390
1390
  ].join("\n")
1391
- }), M = (e) => {
1392
- if (!b.isCallExpression(e)) return !1;
1391
+ }), ot = (e) => {
1392
+ if (!x.isCallExpression(e)) return !1;
1393
1393
  let t = e.getExpression();
1394
- return b.isPropertyAccessExpression(t) && t.getName() === "reactive" && e.getArguments().length === 0;
1395
- }, it = (e) => e.getDescendantsOfKind(x.CallExpression).some(M), at = (e) => {
1396
- let t = 0, n = e.getDescendantsOfKind(x.CallExpression);
1394
+ return x.isPropertyAccessExpression(t) && t.getName() === "reactive" && e.getArguments().length === 0;
1395
+ }, st = (e) => e.getDescendantsOfKind(S.CallExpression).some(ot), ct = (e) => {
1396
+ let t = 0, n = e.getDescendantsOfKind(S.CallExpression);
1397
1397
  for (let e of n) {
1398
- if (e.wasForgotten() || !M(e)) continue;
1398
+ if (e.wasForgotten() || !ot(e)) continue;
1399
1399
  let n = e.getExpression();
1400
- b.isPropertyAccessExpression(n) && (e.replaceWithText(n.getExpression().getText()), t++);
1400
+ x.isPropertyAccessExpression(n) && (e.replaceWithText(n.getExpression().getText()), t++);
1401
1401
  }
1402
1402
  return t;
1403
- }, ot = w({
1403
+ }, lt = T({
1404
1404
  version: "0.14.0",
1405
1405
  id: "0.14.0/02_reactive-by-default",
1406
1406
  title: "Reactivity is the default; .reactive() is removed",
@@ -1418,16 +1418,16 @@ var S = (e, t, n) => {
1418
1418
  "real saving: REPLICA IDENTITY FULL widens every UPDATE/DELETE in the WAL.",
1419
1419
  "`voltro dev` warns if you opt out a table a query still reads."
1420
1420
  ].join("\n"),
1421
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => it(e)),
1421
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => st(e)),
1422
1422
  transform: ({ project: e, log: t }) => {
1423
1423
  let n = 0, r = 0;
1424
1424
  for (let t of e.getSourceFiles()) {
1425
- let e = at(t);
1425
+ let e = ct(t);
1426
1426
  e !== 0 && (n += e, r++);
1427
1427
  }
1428
1428
  return n > 0 && t(`removed ${n} .reactive() call(s) across ${r} file(s) — reactivity is the default`), n > 0;
1429
1429
  }
1430
- }), st = w({
1430
+ }), ut = T({
1431
1431
  version: "0.14.0",
1432
1432
  id: "0.14.0/07_dev-remote-file-migrations",
1433
1433
  title: "voltro dev refuses unattended file migrations against a remote database",
@@ -1470,19 +1470,19 @@ var S = (e, t, n) => {
1470
1470
  "Unchanged: `voltro db files`, `voltro db apply` and `voltro serve`. This gates the",
1471
1471
  "AUTOMATIC boot path only; an explicit command is already an explicit decision."
1472
1472
  ].join("\n")
1473
- }), ct = (e) => [...e.getImportDeclarations(), ...e.getExportDeclarations()], N = (e) => g(e).replace(/\.tsx?$/, ""), lt = (e, t, n) => {
1474
- let r = N(t), i = N(n);
1473
+ }), dt = (e) => [...e.getImportDeclarations(), ...e.getExportDeclarations()], A = (e) => g(e).replace(/\.tsx?$/, ""), ft = (e, t, n) => {
1474
+ let r = A(t), i = A(n);
1475
1475
  if (r === i) return;
1476
1476
  let a = /\.tsx?$/.exec(e)?.[0] ?? "", o = a === "" ? e : e.slice(0, -a.length);
1477
1477
  if (!o.endsWith(`/${r}`) && o !== r) return;
1478
1478
  let s = a === "" ? "" : /\.tsx$/.test(n) ? ".tsx" : ".ts";
1479
1479
  return `${o.slice(0, o.length - r.length)}${i}${s}`;
1480
- }, P = (e, t) => {
1480
+ }, j = (e, t) => {
1481
1481
  let n = e.getFilePath(), r = [];
1482
- for (let i of e.getProject().getSourceFiles()) for (let e of ct(i)) {
1482
+ for (let i of e.getProject().getSourceFiles()) for (let e of dt(i)) {
1483
1483
  let i = e.getModuleSpecifierValue();
1484
1484
  if (i === void 0 || i.startsWith(".") || e.getModuleSpecifierSourceFile()?.getFilePath() !== n) continue;
1485
- let a = lt(i, n, t);
1485
+ let a = ft(i, n, t);
1486
1486
  a !== void 0 && r.push({
1487
1487
  decl: e,
1488
1488
  spec: a
@@ -1491,19 +1491,19 @@ var S = (e, t, n) => {
1491
1491
  e.move(t);
1492
1492
  for (let e of r) e.decl.setModuleSpecifier(e.spec);
1493
1493
  return r.length;
1494
- }, ut = (e) => {
1494
+ }, pt = (e) => {
1495
1495
  let t = e.getProject().getCompilerOptions().paths;
1496
1496
  if (t === void 0) return;
1497
- let n = N(e.getFilePath()), r = e.getFilePath().slice(0, e.getFilePath().lastIndexOf("/"));
1497
+ let n = A(e.getFilePath()), r = e.getFilePath().slice(0, e.getFilePath().lastIndexOf("/"));
1498
1498
  for (let [e, i] of Object.entries(t)) if (!e.endsWith("*")) for (let t of i ?? []) {
1499
1499
  if (!/\.tsx?$/.test(t)) continue;
1500
- let i = N(t), a = t.slice(0, t.lastIndexOf("/"));
1500
+ let i = A(t), a = t.slice(0, t.lastIndexOf("/"));
1501
1501
  if (i === n && r.endsWith(a.replace(/^\.\//, ""))) return e;
1502
1502
  }
1503
- }, dt = /\.page\.tsx$/, F = (e) => {
1503
+ }, mt = /\.page\.tsx$/, M = (e) => {
1504
1504
  let t = e.includes("\\src\\pages\\") ? "\\src\\pages\\" : "/src/pages/", n = e.indexOf(t);
1505
1505
  return n === -1 ? null : e.slice(0, n);
1506
- }, ft = (e) => e.endsWith(".tsx") && !i.has(e) && !d(e) && !/\.island\.tsx$/.test(e) && !dt.test(e), pt = w({
1506
+ }, ht = (e) => e.endsWith(".tsx") && !i.has(e) && !d(e) && !/\.island\.tsx$/.test(e) && !mt.test(e), gt = T({
1507
1507
  version: "0.14.0",
1508
1508
  id: "0.14.0/03_pages-suffix",
1509
1509
  title: "Pages are declared with a .page.tsx suffix",
@@ -1518,8 +1518,8 @@ var S = (e, t, n) => {
1518
1518
  transform: ({ project: e, log: t }) => {
1519
1519
  let n = /* @__PURE__ */ new Map(), r = e.getSourceFiles().filter((t) => {
1520
1520
  let r = t.getFilePath();
1521
- if (!ft(g(r))) return !1;
1522
- let i = F(r);
1521
+ if (!ht(g(r))) return !1;
1522
+ let i = M(r);
1523
1523
  if (i === null) return !1;
1524
1524
  let a = n.get(i);
1525
1525
  return a === void 0 && (a = e.getSourceFile(v(i, "app.config.ts")) !== void 0 || e.getSourceFile(v(i, "app.config.tsx")) !== void 0, n.set(i, a)), a;
@@ -1537,13 +1537,13 @@ var S = (e, t, n) => {
1537
1537
  let n = t.getFilePath(), r = g(n), s = _(n), c = r.slice(0, -4);
1538
1538
  for (let t of [".test.tsx", ".spec.tsx"]) {
1539
1539
  let n = v(s, `${c}${t}`), r = e.getSourceFile(n);
1540
- r !== void 0 && (P(r, v(s, `${c}.page${t}`)), o += 1);
1540
+ r !== void 0 && (j(r, v(s, `${c}.page${t}`)), o += 1);
1541
1541
  }
1542
- P(t, v(s, `${c}.page.tsx`));
1542
+ j(t, v(s, `${c}.page.tsx`));
1543
1543
  let l = i.get(n);
1544
1544
  if (l !== void 0 && l.length > 0 && !l.every((e) => d(g(e)))) {
1545
- let e = F(n) ?? _(n), t = [...new Set(l)].map((t) => ee(e, t));
1546
- a.push(`${ee(e, n)} ← imported by ${t.join(", ")}`);
1545
+ let e = M(n) ?? _(n), t = [...new Set(l)].map((t) => y(e, t));
1546
+ a.push(`${y(e, n)} ← imported by ${t.join(", ")}`);
1547
1547
  }
1548
1548
  }
1549
1549
  if (t(`renamed ${r.length} route file(s) to *.page.tsx${o > 0 ? ` (+${o} co-located test file(s))` : ""}`), a.length > 0) {
@@ -1551,7 +1551,7 @@ var S = (e, t, n) => {
1551
1551
  for (let e of a) t(` ${e}`);
1552
1552
  }
1553
1553
  }
1554
- }), mt = [
1554
+ }), _t = [
1555
1555
  /\.page\.tsx$/,
1556
1556
  c,
1557
1557
  s,
@@ -1562,24 +1562,24 @@ var S = (e, t, n) => {
1562
1562
  a,
1563
1563
  r,
1564
1564
  t
1565
- ], ht = (e) => {
1565
+ ], vt = (e) => {
1566
1566
  let t = g(e);
1567
- return !/\.tsx?$/.test(t) || i.has(t) || n(t) || t === "index.ts" || t === "index.tsx" || t === "app.config.ts" ? !1 : !mt.some((e) => e.test(t));
1568
- }, gt = (e) => {
1567
+ return !/\.tsx?$/.test(t) || i.has(t) || n(t) || t === "index.ts" || t === "index.tsx" || t === "app.config.ts" ? !1 : !_t.some((e) => e.test(t));
1568
+ }, yt = (e) => {
1569
1569
  let t = [], n = [], r = [], i = [];
1570
1570
  for (let [a, o] of e.getExportedDeclarations()) {
1571
1571
  let e = o[0];
1572
1572
  if (e === void 0) continue;
1573
- if (b.isInterfaceDeclaration(e) || b.isTypeAliasDeclaration(e)) {
1573
+ if (x.isInterfaceDeclaration(e) || x.isTypeAliasDeclaration(e)) {
1574
1574
  i.push(a);
1575
1575
  continue;
1576
1576
  }
1577
1577
  if (a === "default") {
1578
- if (!(b.isFunctionDeclaration(e) || b.isClassDeclaration(e) || b.isArrowFunction(e) || b.isFunctionExpression(e))) {
1578
+ if (!(x.isFunctionDeclaration(e) || x.isClassDeclaration(e) || x.isArrowFunction(e) || x.isFunctionExpression(e))) {
1579
1579
  r.push("default");
1580
1580
  continue;
1581
1581
  }
1582
- let n = b.isFunctionDeclaration(e) || b.isClassDeclaration(e) ? e.getName() : void 0;
1582
+ let n = x.isFunctionDeclaration(e) || x.isClassDeclaration(e) ? e.getName() : void 0;
1583
1583
  t.push(n ?? "Default");
1584
1584
  continue;
1585
1585
  }
@@ -1592,10 +1592,10 @@ var S = (e, t, n) => {
1592
1592
  others: r,
1593
1593
  types: i
1594
1594
  };
1595
- }, _t = (e, t) => {
1595
+ }, bt = (e, t) => {
1596
1596
  let { components: n, hooks: r, others: i, types: a } = e;
1597
1597
  return n.length + r.length + i.length === 0 ? a.length > 0 ? ".types" : null : t && h(n) && r.length === 0 && i.length === 0 ? ".component.tsx" : n.length === 0 && r.length === 1 && i.length === 0 ? ".hook" : null;
1598
- }, vt = w({
1598
+ }, xt = T({
1599
1599
  version: "0.14.0",
1600
1600
  id: "0.14.0/04_file-taxonomy",
1601
1601
  title: "Web files declare their contract (*.component.tsx / *.hook.ts / *.types.ts)",
@@ -1608,19 +1608,19 @@ var S = (e, t, n) => {
1608
1608
  if (s.length > 0) for (let e of s) t(` exempt (not ours): ${e.dir} — ${e.source}`);
1609
1609
  for (let t of e.getSourceFiles()) {
1610
1610
  let e = t.getFilePath();
1611
- if (!ht(e) || p(e, s)) continue;
1612
- let o = ut(t);
1611
+ if (!vt(e) || p(e, s)) continue;
1612
+ let o = pt(t);
1613
1613
  if (o !== void 0) {
1614
1614
  a.push(`${g(e)} (tsconfig paths: "${o}")`);
1615
1615
  continue;
1616
1616
  }
1617
- let c = e.endsWith(".tsx"), l = gt(t), u = _t(l, c);
1617
+ let c = e.endsWith(".tsx"), l = yt(t), u = bt(l, c);
1618
1618
  if (u === null) {
1619
1619
  l.components.length > 0 && l.hooks.length > 0 && r.push(`${g(e)} (component ${l.components.join("/")} + hook ${l.hooks.join("/")})`);
1620
1620
  continue;
1621
1621
  }
1622
1622
  let d = g(e).replace(/\.tsx?$/, ""), f = u === ".component.tsx" ? ".component.tsx" : `${u}${c ? ".tsx" : ".ts"}`;
1623
- i += P(t, v(_(e), `${d}${f}`)), n.push(`${d}${f}`);
1623
+ i += j(t, v(_(e), `${d}${f}`)), n.push(`${d}${f}`);
1624
1624
  }
1625
1625
  if (n.length > 0 && (t(`declared ${n.length} file(s): component / hook / types`), i > 0 && t(` rewrote ${i} aliased import specifier(s) (@/… and friends)`)), a.length > 0) {
1626
1626
  t(""), t(`left alone — a tsconfig \`paths\` entry names ${a.length} of these files directly:`);
@@ -1633,7 +1633,7 @@ var S = (e, t, n) => {
1633
1633
  r.length > 20 && t(` (+${r.length - 20} more)`);
1634
1634
  }
1635
1635
  }
1636
- }), yt = w({
1636
+ }), St = T({
1637
1637
  version: "0.14.0",
1638
1638
  id: "0.14.0/05_typed-tracking",
1639
1639
  title: "Tracking catalogues are typed against the component props",
@@ -1670,7 +1670,7 @@ var S = (e, t, n) => {
1670
1670
  "Read each one before changing it: a cast that no longer compiles is sometimes",
1671
1671
  "a real type disagreement the old signature was hiding."
1672
1672
  ].join("\n")
1673
- }), bt = w({
1673
+ }), Ct = T({
1674
1674
  version: "0.14.0",
1675
1675
  id: "0.14.0/06_voltro-test-skips-e2e",
1676
1676
  title: "`voltro test` leaves `e2e/` to `voltro e2e`",
@@ -1692,13 +1692,13 @@ var S = (e, t, n) => {
1692
1692
  " code it tests, or",
1693
1693
  " • pass your own `--exclude`, which overrides the framework default outright."
1694
1694
  ].join("\n")
1695
- }), xt = (e) => {
1695
+ }), wt = (e) => {
1696
1696
  let t = e.replace(/\.component(\.ui)?(\.tsx?)$/, "$2");
1697
1697
  return t === e ? null : n(t) ? t : null;
1698
- }, St = (e) => {
1698
+ }, Tt = (e) => {
1699
1699
  for (let [, t] of e.getExportedDeclarations()) if (t[0] !== void 0) return !1;
1700
1700
  return e.getExportDeclarations().length === 0 && e.getExportAssignments().length === 0;
1701
- }, Ct = w({
1701
+ }, Et = T({
1702
1702
  version: "0.15.0",
1703
1703
  id: "0.15.0/01_undo-mistaken-taxonomy-renames",
1704
1704
  title: "Undo the two file renames 0.14.0 got wrong (framework primitives, side-effect modules)",
@@ -1715,14 +1715,14 @@ var S = (e, t, n) => {
1715
1715
  transform: ({ project: e, log: t }) => {
1716
1716
  let n = [], r = [];
1717
1717
  for (let t of e.getSourceFiles()) {
1718
- let e = t.getFilePath(), i = g(e), a = xt(i);
1718
+ let e = t.getFilePath(), i = g(e), a = wt(i);
1719
1719
  if (a !== null) {
1720
- P(t, v(_(e), a)), n.push(`${i} → ${a}`);
1720
+ j(t, v(_(e), a)), n.push(`${i} → ${a}`);
1721
1721
  continue;
1722
1722
  }
1723
- if (/\.types\.tsx?$/.test(i) && St(t)) {
1723
+ if (/\.types\.tsx?$/.test(i) && Tt(t)) {
1724
1724
  let n = i.replace(/\.types(\.tsx?)$/, "$1");
1725
- P(t, v(_(e), n)), r.push(`${i} → ${n}`);
1725
+ j(t, v(_(e), n)), r.push(`${i} → ${n}`);
1726
1726
  }
1727
1727
  }
1728
1728
  if (n.length > 0) {
@@ -1736,54 +1736,54 @@ var S = (e, t, n) => {
1736
1736
  }
1737
1737
  n.length === 0 && r.length === 0 && t("nothing to undo — 0.14.0 renamed neither a framework file nor a side-effect module here.");
1738
1738
  }
1739
- }), I = ["@voltro/client", "@voltro/web"], L = "@voltro/protocol", wt = w({
1739
+ }), N = ["@voltro/client", "@voltro/web"], P = "@voltro/protocol", Dt = T({
1740
1740
  version: "0.15.0",
1741
1741
  id: "0.15.0/02_error-tag-in-protocol",
1742
1742
  title: "`errorTag` is imported from @voltro/protocol",
1743
1743
  kind: "transform",
1744
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getImportDeclarations().some((e) => I.includes(e.getModuleSpecifierValue()) && e.getNamedImports().some((e) => e.getName() === "errorTag"))),
1744
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getImportDeclarations().some((e) => N.includes(e.getModuleSpecifierValue()) && e.getNamedImports().some((e) => e.getName() === "errorTag"))),
1745
1745
  transform: ({ project: e, log: t }) => {
1746
1746
  let n = 0;
1747
1747
  for (let t of e.getSourceFiles()) for (let e of t.getImportDeclarations()) {
1748
- if (!I.includes(e.getModuleSpecifierValue())) continue;
1748
+ if (!N.includes(e.getModuleSpecifierValue())) continue;
1749
1749
  let r = e.getNamedImports().find((e) => e.getName() === "errorTag");
1750
1750
  if (r === void 0) continue;
1751
1751
  let i = r.getAliasNode()?.getText(), a = e.isTypeOnly() || r.isTypeOnly();
1752
1752
  r.remove();
1753
- let o = t.getImportDeclarations().find((e) => e.getModuleSpecifierValue() === L && e.isTypeOnly() === a), s = {
1753
+ let o = t.getImportDeclarations().find((e) => e.getModuleSpecifierValue() === P && e.isTypeOnly() === a), s = {
1754
1754
  name: "errorTag",
1755
1755
  ...i === void 0 ? {} : { alias: i }
1756
1756
  };
1757
1757
  o === void 0 ? t.addImportDeclaration({
1758
- moduleSpecifier: L,
1758
+ moduleSpecifier: P,
1759
1759
  isTypeOnly: a,
1760
1760
  namedImports: [s]
1761
1761
  }) : o.addNamedImport(s), e.getNamedImports().length === 0 && e.getDefaultImport() === void 0 && e.getNamespaceImport() === void 0 && e.remove(), n += 1;
1762
1762
  }
1763
- n > 0 && t(`errorTag: ${n} import(s) repointed to ${L}`);
1763
+ n > 0 && t(`errorTag: ${n} import(s) repointed to ${P}`);
1764
1764
  }
1765
- }), R = /\.page\.tsx$/, Tt = /\.page\.(test|spec)\.tsx$/, Et = (e) => /(^|\/)src\/pages\//.test(e), Dt = (e) => {
1766
- let t = _(e), n = g(e).replace(R, "");
1765
+ }), F = /\.page\.tsx$/, Ot = /\.page\.(test|spec)\.tsx$/, kt = (e) => /(^|\/)src\/pages\//.test(e), At = (e) => {
1766
+ let t = _(e), n = g(e).replace(F, "");
1767
1767
  return n === "index" ? v(t, "page.tsx") : v(t, n, "page.tsx");
1768
- }, Ot = (e) => {
1769
- let t = _(e), n = g(e), r = /\.page\.spec\.tsx$/.test(n) ? "spec" : "test", i = n.replace(Tt, "");
1768
+ }, jt = (e) => {
1769
+ let t = _(e), n = g(e), r = /\.page\.spec\.tsx$/.test(n) ? "spec" : "test", i = n.replace(Ot, "");
1770
1770
  return i === "index" ? v(t, `page.${r}.tsx`) : v(t, i, `page.${r}.tsx`);
1771
- }, kt = w({
1771
+ }, Mt = T({
1772
1772
  version: "0.17.0",
1773
1773
  id: "0.17.0/01_pages-are-directories",
1774
1774
  title: "A directory is a route segment; its route is page.tsx",
1775
1775
  kind: "transform",
1776
1776
  scope: ["**/src/pages/**/*.ts", "**/src/pages/**/*.tsx"],
1777
1777
  needsImporters: !0,
1778
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Et(e.getFilePath()) && R.test(g(e.getFilePath()))),
1778
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => kt(e.getFilePath()) && F.test(g(e.getFilePath()))),
1779
1779
  transform: ({ project: e, log: t }) => {
1780
1780
  let n = e.getSourceFiles(), r = [];
1781
1781
  for (let e of n) {
1782
1782
  let t = String(e.getFilePath());
1783
- if (!Et(t)) continue;
1784
- let n = g(t), i = Tt.test(n);
1785
- if (!i && !R.test(n)) continue;
1786
- let a = i ? Ot(t) : Dt(t);
1783
+ if (!kt(t)) continue;
1784
+ let n = g(t), i = Ot.test(n);
1785
+ if (!i && !F.test(n)) continue;
1786
+ let a = i ? jt(t) : At(t);
1787
1787
  a !== t && r.push({
1788
1788
  sf: e,
1789
1789
  from: t,
@@ -1802,7 +1802,7 @@ var S = (e, t, n) => {
1802
1802
  }
1803
1803
  }
1804
1804
  let l = [], u = 0;
1805
- for (let e of r) s.has(e.from) || (u += P(e.sf, e.to), e.isTest || l.push(o(e.to)));
1805
+ for (let e of r) s.has(e.from) || (u += j(e.sf, e.to), e.isTest || l.push(o(e.to)));
1806
1806
  if (l.length > 0 && (t(`moved ${l.length} route(s) into their own directory`), u > 0 && t(` rewrote ${u} aliased import specifier(s)`)), c.length > 0) {
1807
1807
  t(""), t(`LEFT ALONE — ${c.length} case(s) where two files claim one route:`);
1808
1808
  for (let e of c) t(` ${e}`);
@@ -1810,12 +1810,12 @@ var S = (e, t, n) => {
1810
1810
  }
1811
1811
  l.length === 0 && c.length === 0 && t("every route already lives in its own directory — nothing to move.");
1812
1812
  }
1813
- }), At = /,\s*(['"])like\1\s*,/, jt = w({
1813
+ }), Nt = /,\s*(['"])like\1\s*,/, Pt = T({
1814
1814
  version: "0.18.0",
1815
1815
  id: "0.18.0/01_where-like-removed",
1816
1816
  title: "the `'like'` where-operator is removed — pick `contains` or `startsWith`",
1817
1817
  kind: "manual",
1818
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => At.test(e.getFullText())),
1818
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Nt.test(e.getFullText())),
1819
1819
  note: [
1820
1820
  "The `'like'` operator is removed from the ergonomic `.where(col, op, value)` form.",
1821
1821
  "",
@@ -1853,12 +1853,12 @@ var S = (e, t, n) => {
1853
1853
  "in a raw query (`ctx.store.raw`); the DSL has no arbitrary-pattern operator, and",
1854
1854
  "adding one that cannot use an index is not on the roadmap."
1855
1855
  ].join("\n")
1856
- }), Mt = /\.serverOnly\s*\(\s*\)/, Nt = w({
1856
+ }), Ft = /\.serverOnly\s*\(\s*\)/, It = T({
1857
1857
  version: "0.19.0",
1858
1858
  id: "0.19.0/01_server-only-gates-the-boot",
1859
1859
  title: "`.serverOnly()` now FAILS the boot under `voltro serve` — it used to only warn",
1860
1860
  kind: "manual",
1861
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Mt.test(e.getFullText())),
1861
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Ft.test(e.getFullText())),
1862
1862
  note: [
1863
1863
  "`.serverOnly()` now GATES. Before 0.19.0 it emitted one `log.warn` and let the",
1864
1864
  "boot continue — in every command, including `voltro serve`.",
@@ -1905,12 +1905,12 @@ var S = (e, t, n) => {
1905
1905
  "hatch is that people delete the marker instead — and a check whose only way out",
1906
1906
  "is to disable it gets disabled."
1907
1907
  ].join("\n")
1908
- }), Pt = /\bauditPlugin\s*\(/, Ft = w({
1908
+ }), Lt = /\bauditPlugin\s*\(/, Rt = T({
1909
1909
  version: "0.19.0",
1910
1910
  id: "0.19.0/02_audit-redact-input-default",
1911
1911
  title: "`auditPlugin` now redacts mutation input by default (`redactInput: 'all'`)",
1912
1912
  kind: "manual",
1913
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Pt.test(e.getFullText())),
1913
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Lt.test(e.getFullText())),
1914
1914
  note: [
1915
1915
  "`auditPlugin`'s `redactInput` now defaults to `'all'`. Audit rows written from",
1916
1916
  "now on carry `input: { __redacted: 'all' }` — which still proves a payload",
@@ -1946,12 +1946,12 @@ var S = (e, t, n) => {
1946
1946
  "those markers live on TABLE COLUMNS, and this is a mutation's INPUT.",
1947
1947
  "`changePassword({ oldPassword, newPassword })` has no column to consult."
1948
1948
  ].join("\n")
1949
- }), It = T("registerWriteRecorder"), Lt = w({
1949
+ }), zt = E("registerWriteRecorder"), Bt = T({
1950
1950
  version: "0.20.0",
1951
1951
  id: "0.20.0/01_write-recorder-port",
1952
1952
  title: "`WriteRecorder` takes a port (`{ append, maxOf }`) instead of a bare `append`",
1953
1953
  kind: "manual",
1954
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => It.test(e.getFullText())),
1954
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => zt.test(e.getFullText())),
1955
1955
  note: [
1956
1956
  "If you wrote your own in-transaction write recorder, its first parameter is",
1957
1957
  "now an object rather than a function:",
@@ -1990,12 +1990,12 @@ var S = (e, t, n) => {
1990
1990
  "nested transaction, and a throw from either operation still rolls the",
1991
1991
  "caller's write back. That is the guarantee the timing exists for."
1992
1992
  ].join("\n")
1993
- }), Rt = /\.maxLength\s*\(/, zt = w({
1993
+ }), Vt = /\.maxLength\s*\(/, Ht = T({
1994
1994
  version: "0.21.0",
1995
1995
  id: "0.21.0/01_maxlength-now-migrates",
1996
1996
  title: "`text().maxLength(n)` now migrates EXISTING columns — plan before you apply",
1997
1997
  kind: "manual",
1998
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Rt.test(e.getFullText())),
1998
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Vt.test(e.getFullText())),
1999
1999
  note: [
2000
2000
  "Your source does not change. `text().maxLength(n)` compiles exactly as before",
2001
2001
  "and every bound you already declared stays as written.",
@@ -2037,12 +2037,12 @@ var S = (e, t, n) => {
2037
2037
  "framework guarantees, and an upgrade-time ALTER that fails on your data is a",
2038
2038
  "worse outcome than the index-size concern it would fix."
2039
2039
  ].join("\n")
2040
- }), Bt = /\bMigrationOperation\b|\bPlannedOperation\b|\bInspectMigrationOperationKind\b|\bOperationKind\b/, Vt = w({
2040
+ }), Ut = /\bMigrationOperation\b|\bPlannedOperation\b|\bInspectMigrationOperationKind\b|\bOperationKind\b/, Wt = T({
2041
2041
  version: "0.22.0",
2042
2042
  id: "0.22.0/01_rename-index-op",
2043
2043
  title: "`MigrationOperation` gained a `rename-index` kind",
2044
2044
  kind: "manual",
2045
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Bt.test(e.getFullText())),
2045
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Ut.test(e.getFullText())),
2046
2046
  note: [
2047
2047
  "The migration planner can now emit a `rename-index` operation:",
2048
2048
  "",
@@ -2074,12 +2074,12 @@ var S = (e, t, n) => {
2074
2074
  "match where two same-shaped indexes were renamed at once. Those still plan",
2075
2075
  "as drop + create, exactly as before."
2076
2076
  ].join("\n")
2077
- }), Ht = /@voltro\/plugin-(notifications|scim|ai-flows)/, Ut = w({
2077
+ }), Gt = /@voltro\/plugin-(notifications|scim|ai-flows)/, Kt = T({
2078
2078
  version: "0.22.0",
2079
2079
  id: "0.22.0/02_plugin-tables-voltro-prefixed",
2080
2080
  title: "plugin-owned tables are `_voltro_`-prefixed (the rows move themselves)",
2081
2081
  kind: "manual",
2082
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Ht.test(e.getFullText())),
2082
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Gt.test(e.getFullText())),
2083
2083
  note: [
2084
2084
  "Ten tables owned by @voltro/plugin-notifications, @voltro/plugin-scim and",
2085
2085
  "@voltro/plugin-ai-flows moved out of your table namespace and into the",
@@ -2129,12 +2129,12 @@ var S = (e, t, n) => {
2129
2129
  "gained a `rename-table` kind. An exhaustive `switch` needs an arm; don't add",
2130
2130
  "a `default:`, which would silence this and every future kind."
2131
2131
  ].join("\n")
2132
- }), Wt = /QueryCacheScope|QueryCacheConfig|scope\s*:\s*['"](?:subject|global)['"]/, Gt = w({
2132
+ }), qt = /QueryCacheScope|QueryCacheConfig|scope\s*:\s*['"](?:subject|global)['"]/, Jt = T({
2133
2133
  version: "0.22.0",
2134
2134
  id: "0.22.0/03_cache-scope-gained-tenant",
2135
2135
  title: "`cache.scope` gained 'tenant' — an exhaustive switch over it needs a branch",
2136
2136
  kind: "manual",
2137
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Wt.test(e.getFullText())),
2137
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => qt.test(e.getFullText())),
2138
2138
  note: [
2139
2139
  "`cache: { scope }` now accepts a third value, `'tenant'`: one cache entry per",
2140
2140
  "org, none shared across orgs.",
@@ -2163,12 +2163,12 @@ var S = (e, t, n) => {
2163
2163
  "Rubric: does the resolved predicate depend on the caller? On the PERSON →",
2164
2164
  "`subject`; on their ORG only → `tenant`; on neither → `global`."
2165
2165
  ].join("\n")
2166
- }), Kt = /envTokenAuthResolver|InspectAuthResolver|authResolver/, qt = w({
2166
+ }), Yt = /envTokenAuthResolver|InspectAuthResolver|authResolver/, Xt = T({
2167
2167
  version: "0.22.0",
2168
2168
  id: "0.22.0/04_inspect-write-credential",
2169
2169
  title: "Mutating `/_voltro/inspect/*` endpoints need VOLTRO_INSPECT_WRITE_TOKEN",
2170
2170
  kind: "manual",
2171
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Kt.test(e.getFullText())),
2171
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Yt.test(e.getFullText())),
2172
2172
  note: [
2173
2173
  "A POST / PATCH / PUT / DELETE to `/_voltro/inspect/*` now needs a SECOND",
2174
2174
  "credential: `VOLTRO_INSPECT_WRITE_TOKEN`, sent as the `x-voltro-inspect-write`",
@@ -2206,12 +2206,12 @@ var S = (e, t, n) => {
2206
2206
  "IMPLEMENTING a resolver is unaffected — `(headers) => …` still satisfies the",
2207
2207
  "type. Only callers are obliged."
2208
2208
  ].join("\n")
2209
- }), Jt = /versioningPlugin\s*\(/, Yt = w({
2209
+ }), Zt = /versioningPlugin\s*\(/, Qt = T({
2210
2210
  version: "0.23.0",
2211
2211
  id: "0.23.0/01_versioning-is-opt-out",
2212
2212
  title: "versioningPlugin: `tables: string[]` → opt-out by default, `include` / `exclude` by value",
2213
2213
  kind: "manual",
2214
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Jt.test(e.getFullText())),
2214
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Zt.test(e.getFullText())),
2215
2215
  note: [
2216
2216
  "`versioningPlugin({ tables: [...] })` no longer compiles. Row history is now",
2217
2217
  "ON by default for every table your app declares.",
@@ -2251,12 +2251,12 @@ var S = (e, t, n) => {
2251
2251
  "surprises you, `exclude` is the knob, and the retention sweep",
2252
2252
  "(`VOLTRO_ROW_HISTORY_TTL_HOURS`) still bounds age."
2253
2253
  ].join("\n")
2254
- }), Xt = /@voltro\/ai|\.agent\.tsx|agent_(threads|messages)/, Zt = w({
2254
+ }), $t = /@voltro\/ai|\.agent\.tsx|agent_(threads|messages)/, en = T({
2255
2255
  version: "0.24.0",
2256
2256
  id: "0.24.0/01_agent-tables-voltro-prefixed",
2257
2257
  title: "the two agent tables are `_voltro_`-prefixed (the rows move themselves)",
2258
2258
  kind: "manual",
2259
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Xt.test(e.getFullText())),
2259
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => $t.test(e.getFullText())),
2260
2260
  note: [
2261
2261
  "The two tables owned by @voltro/ai moved out of your table namespace and",
2262
2262
  "into the framework's:",
@@ -2309,12 +2309,12 @@ var S = (e, t, n) => {
2309
2309
  "declared by something. You get a fresh empty `_voltro_…` table alongside it,",
2310
2310
  "which is the collision this ends."
2311
2311
  ].join("\n")
2312
- }), Qt = /\.events\s*\.\s*emit\s*\(/, $t = w({
2312
+ }), tn = /\.events\s*\.\s*emit\s*\(/, nn = T({
2313
2313
  version: "0.25.0",
2314
2314
  id: "0.25.0/01_events-emit-to-publish",
2315
2315
  title: "`ctx.events.emit(name, data)` → `ctx.events.publish(descriptor, key, payload)`",
2316
2316
  kind: "manual",
2317
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Qt.test(e.getFullText())),
2317
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => tn.test(e.getFullText())),
2318
2318
  note: [
2319
2319
  "`ctx.events.emit('name', data)` is gone. Declare the event and publish it:",
2320
2320
  "",
@@ -2358,53 +2358,53 @@ var S = (e, t, n) => {
2358
2358
  "",
2359
2359
  "Docs: https://docs.voltro.dev/docs/data/events"
2360
2360
  ].join("\n")
2361
- }), en = /* @__PURE__ */ new Set([
2361
+ }), rn = /* @__PURE__ */ new Set([
2362
2362
  "description",
2363
2363
  "version",
2364
2364
  "globalRateLimit"
2365
- ]), tn = ["defaultRetry", "defaultSigning"], nn = (e) => e.getDescendantsOfKind(x.CallExpression).filter((e) => e.getExpression().getText() === "defineOutgoingEvent"), rn = w({
2365
+ ]), an = ["defaultRetry", "defaultSigning"], on = (e) => e.getDescendantsOfKind(S.CallExpression).filter((e) => e.getExpression().getText() === "defineOutgoingEvent"), sn = T({
2366
2366
  version: "0.25.0",
2367
2367
  id: "0.25.0/02_outgoing-event-to-define-event",
2368
2368
  title: "`defineOutgoingEvent` → `defineEvent({ …, webhook: { … } })`",
2369
2369
  kind: "transform",
2370
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => nn(e).length > 0),
2370
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => on(e).length > 0),
2371
2371
  transform: ({ project: e, log: t }) => {
2372
2372
  let n = 0, r = /* @__PURE__ */ new Set(), i = [];
2373
- for (let t of e.getSourceFiles()) for (let e of nn(t)) {
2373
+ for (let t of e.getSourceFiles()) for (let e of on(t)) {
2374
2374
  let a = e.getArguments()[0];
2375
- if (a === void 0 || !a.isKind(x.ObjectLiteralExpression)) continue;
2376
- let o = a.getProperties().filter((e) => e.isKind(x.PropertyAssignment)), s = (e) => o.find((t) => t.asKindOrThrow(x.PropertyAssignment).getName() === e)?.asKindOrThrow(x.PropertyAssignment).getInitializer()?.getText(), c = s("id"), l = s("payload");
2375
+ if (a === void 0 || !a.isKind(S.ObjectLiteralExpression)) continue;
2376
+ let o = a.getProperties().filter((e) => e.isKind(S.PropertyAssignment)), s = (e) => o.find((t) => t.asKindOrThrow(S.PropertyAssignment).getName() === e)?.asKindOrThrow(S.PropertyAssignment).getInitializer()?.getText(), c = s("id"), l = s("payload");
2377
2377
  if (c === void 0 || l === void 0) continue;
2378
- let u = [...en].map((e) => {
2378
+ let u = [...rn].map((e) => {
2379
2379
  let t = s(e);
2380
2380
  if (t !== void 0) return `${e === "globalRateLimit" ? "rateLimit" : e}: ${t}`;
2381
2381
  }).filter((e) => e !== void 0);
2382
- for (let e of tn) s(e) !== void 0 && i.push(`${t.getBaseName()}: ${e}`);
2382
+ for (let e of an) s(e) !== void 0 && i.push(`${t.getBaseName()}: ${e}`);
2383
2383
  let d = u.length > 0 ? `, webhook: { ${u.join(", ")} }` : ", webhook: {}";
2384
2384
  e.replaceWithText(`defineEvent({ name: ${c}, key: Schema.Struct({}), payload: ${l}${d} })`), n++, r.add(t.getFilePath());
2385
2385
  }
2386
2386
  n !== 0 && (t(`webhooks: rewrote ${n} \`defineOutgoingEvent\` → \`defineEvent\` in ${r.size} file${r.size === 1 ? "" : "s"}`), t(" → `key: Schema.Struct({})` is CORRECT, not a placeholder: a webhook event goes to"), t(" subscribed targets, not to a routing key. Give it a key only if you also want"), t(" connected clients to subscribe per-something with `useEvent`."), t(" → import `defineEvent` from `@voltro/protocol` and `Schema` from `effect`;"), t(" the file is now a declaration, so `*.event.ts` is its home."), t(" → `ctx.webhooks.emit(descriptor, payload)` still works, and"), t(" `ctx.events.publish(descriptor, key, payload)` now reaches the HTTP targets too."), i.length > 0 && (t(` → NOT carried across (${i.length}): ${[...new Set(i)].join(", ")}`), t(" The plugin's retry/signing shapes are richer than the declared event can hold,"), t(" so they are left in place as a compile error rather than dropped silently."), t(" Configure them at subscribe time, where the full shape is typed.")));
2387
2387
  }
2388
- }), an = (e) => e.getDescendantsOfKind(x.CallExpression).filter((e) => {
2388
+ }), cn = (e) => e.getDescendantsOfKind(S.CallExpression).filter((e) => {
2389
2389
  let t = e.getExpression().getText();
2390
2390
  return t === "broadcast" || t === "broadcastPlugin";
2391
- }), on = (e) => {
2391
+ }), I = (e) => {
2392
2392
  let t = e.getArguments()[0];
2393
- if (!(t === void 0 || !t.isKind(x.ObjectLiteralExpression))) return t.getProperties().filter((e) => e.isKind(x.PropertyAssignment)).map((e) => e.asKindOrThrow(x.PropertyAssignment)).find((e) => e.getName() === "channel");
2394
- }, sn = w({
2393
+ if (!(t === void 0 || !t.isKind(S.ObjectLiteralExpression))) return t.getProperties().filter((e) => e.isKind(S.PropertyAssignment)).map((e) => e.asKindOrThrow(S.PropertyAssignment)).find((e) => e.getName() === "channel");
2394
+ }, ln = T({
2395
2395
  version: "0.25.0",
2396
2396
  id: "0.25.0/03_broadcast-channel-to-namespace",
2397
2397
  title: "`broadcast({ channel })` → `broadcast({ namespace })`",
2398
2398
  kind: "transform",
2399
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => an(e).some((e) => on(e) !== void 0)),
2399
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => cn(e).some((e) => I(e) !== void 0)),
2400
2400
  transform: ({ project: e, log: t }) => {
2401
2401
  let n = 0, r = [], i = [], a = /* @__PURE__ */ new Set();
2402
- for (let t of e.getSourceFiles()) for (let e of an(t)) {
2403
- let o = on(e);
2402
+ for (let t of e.getSourceFiles()) for (let e of cn(t)) {
2403
+ let o = I(e);
2404
2404
  if (o === void 0) continue;
2405
2405
  let s = o.getInitializer();
2406
2406
  if (s === void 0) continue;
2407
- let c = s.getText(), l = s.isKind(x.StringLiteral) ? s.getLiteralValue() : void 0;
2407
+ let c = s.getText(), l = s.isKind(S.StringLiteral) ? s.getLiteralValue() : void 0;
2408
2408
  if (l !== void 0 && l.endsWith(":changes")) {
2409
2409
  let e = l.slice(0, -8);
2410
2410
  o.replaceWithText(`namespace: '${e}'`), r.push(`${t.getBaseName()}: '${l}' → '${e}'`);
@@ -2417,7 +2417,7 @@ var S = (e, t, n) => {
2417
2417
  for (let e of i) t(` ! ${e} — carried verbatim; CHECK IT: the value is now a PREFIX the framework`), t(" appends ':changes' / ':events:<name>' / ':members' / ':presence' to.");
2418
2418
  }
2419
2419
  }
2420
- }), cn = w({
2420
+ }), un = T({
2421
2421
  version: "0.26.0",
2422
2422
  id: "0.26.0/01_event-webhook-retry-removed",
2423
2423
  title: "`defineEvent({ webhook: { retry } })` is gone — it never did anything",
@@ -2451,7 +2451,7 @@ var S = (e, t, n) => {
2451
2451
  "If you were relying on the declared value to take effect, note that it never",
2452
2452
  "did — your deliveries have been using the target default all along."
2453
2453
  ].join("\n")
2454
- }), ln = w({
2454
+ }), dn = T({
2455
2455
  version: "0.26.0",
2456
2456
  id: "0.26.0/02_step-retry-enforced",
2457
2457
  title: "workflow step `retry:` is now ENFORCED (was metadata) — drop any redundant manual retry",
@@ -2504,12 +2504,12 @@ var S = (e, t, n) => {
2504
2504
  "(retry transient, fail permanent), `maxElapsed: '5 minutes'` (a total time budget,",
2505
2505
  "not just a count), `jitter` (on by default), and `respectRetryAfter` (honor a 429)."
2506
2506
  ].join("\n")
2507
- }), un = /\bmakeSubjectId\b/, dn = w({
2507
+ }), fn = /\bmakeSubjectId\b/, pn = T({
2508
2508
  version: "0.28.0",
2509
2509
  id: "0.28.0/01_notifications-resolve-subject-id-async",
2510
2510
  title: "`makeSubjectId` returns a Promise; `resolveSubjectId` may be async",
2511
2511
  kind: "manual",
2512
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => un.test(e.getFullText())),
2512
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => fn.test(e.getFullText())),
2513
2513
  note: [
2514
2514
  "`makeSubjectId` from @voltro/plugin-notifications now returns a Promise.",
2515
2515
  "",
@@ -2541,12 +2541,12 @@ var S = (e, t, n) => {
2541
2541
  " reconnect. Memoise inside your own resolver if that trade suits your app —",
2542
2542
  " you know your invalidation and we do not."
2543
2543
  ].join("\n")
2544
- }), fn = /\bPluginRefChange\b|\bisSoftDelete\s*\(|\bapplyPluginRefRules\s*\(/, pn = w({
2544
+ }), mn = /\bPluginRefChange\b|\bisSoftDelete\s*\(|\bapplyPluginRefRules\s*\(/, hn = T({
2545
2545
  version: "0.28.0",
2546
2546
  id: "0.28.0/02_plugin-ref-change-event",
2547
2547
  title: "`PluginRefChange` is removed — the change channel's `ChangeEvent` is the one shape",
2548
2548
  kind: "manual",
2549
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => fn.test(e.getFullText())),
2549
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => mn.test(e.getFullText())),
2550
2550
  note: [
2551
2551
  "`PluginRefChange` no longer exists. `isSoftDelete` and `applyPluginRefRules`",
2552
2552
  "take `ChangeEvent` from @voltro/database — the type the change channel actually",
@@ -2588,12 +2588,12 @@ var S = (e, t, n) => {
2588
2588
  " not know that writes a delete, and the test passes against a stream the",
2589
2589
  " framework never emits. That is the defect this change removes."
2590
2590
  ].join("\n")
2591
- }), mn = /credentialsResolver\s*:/, hn = w({
2591
+ }), gn = /credentialsResolver\s*:/, _n = T({
2592
2592
  version: "0.28.0",
2593
2593
  id: "0.28.0/03_atlassian-credentials-context",
2594
2594
  title: "`credentialsResolver` receives `{ subject, store }` instead of a bare Subject",
2595
2595
  kind: "manual",
2596
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => mn.test(e.getFullText())),
2596
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => gn.test(e.getFullText())),
2597
2597
  note: [
2598
2598
  "@voltro/plugin-atlassian: `credentialsResolver` now takes a context object.",
2599
2599
  "",
@@ -2663,12 +2663,12 @@ var S = (e, t, n) => {
2663
2663
  "",
2664
2664
  "and rotate anything you find — a redacted future does not un-leak a past row."
2665
2665
  ].join("\n")
2666
- }), gn = /auditPlugin\s*\(/, _n = w({
2666
+ }), vn = /auditPlugin\s*\(/, yn = T({
2667
2667
  version: "0.28.0",
2668
2668
  id: "0.28.0/04_audit-redacts-subject-metadata",
2669
2669
  title: "`auditPlugin` redacts `subject.metadata` by default",
2670
2670
  kind: "manual",
2671
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => gn.test(e.getFullText())),
2671
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => vn.test(e.getFullText())),
2672
2672
  note: [
2673
2673
  "`auditPlugin` now redacts `subject.metadata` before the sink sees the event.",
2674
2674
  "",
@@ -2724,12 +2724,12 @@ var S = (e, t, n) => {
2724
2724
  " keeps the token in the framework vault, and the resolver's new `store`",
2725
2725
  " handle covers an app with its own token table."
2726
2726
  ].join("\n")
2727
- }), vn = T("useLoaderData"), yn = w({
2727
+ }), bn = E("useLoaderData"), xn = T({
2728
2728
  version: "0.29.0",
2729
2729
  id: "0.29.0/01_loader-data-absence",
2730
2730
  title: "`useLoaderData()` throws at a level that declares no `loader`",
2731
2731
  kind: "manual",
2732
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => vn.test(e.getFullText())),
2732
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => bn.test(e.getFullText())),
2733
2733
  note: [
2734
2734
  "`useLoaderData()` used to return `undefined` at a level with no `loader`,",
2735
2735
  "which then died at `Cannot destructure property 'x' of undefined` — a",
@@ -2765,38 +2765,38 @@ var S = (e, t, n) => {
2765
2765
  "that legitimately resolves to `undefined` does not throw — only a missing",
2766
2766
  "`loader` does."
2767
2767
  ].join("\n")
2768
- }), z = "@voltro/ui-shadcn", B = "voltro:lang", bn = "voltro:locale", xn = "parsePreferenceCookies", Sn = (e, t) => {
2768
+ }), L = "@voltro/ui-shadcn", R = "voltro:lang", Sn = "voltro:locale", z = "parsePreferenceCookies", Cn = (e, t) => {
2769
2769
  let n = [];
2770
- for (let r of e.getImportDeclarations()) if (r.getModuleSpecifierValue() === z) for (let e of r.getNamedImports()) e.getName() === t && n.push(e.getAliasNode()?.getText() ?? e.getName());
2770
+ for (let r of e.getImportDeclarations()) if (r.getModuleSpecifierValue() === L) for (let e of r.getNamedImports()) e.getName() === t && n.push(e.getAliasNode()?.getText() ?? e.getName());
2771
2771
  return n;
2772
- }, Cn = (e) => {
2772
+ }, wn = (e) => {
2773
2773
  let t = 0;
2774
- for (let n of e.getSourceFiles()) n.getFullText().includes(B) && n.forEachDescendant((e) => {
2775
- if (b.isStringLiteral(e) || b.isNoSubstitutionTemplateLiteral(e) || b.isTemplateHead(e) || b.isTemplateMiddle(e) || b.isTemplateTail(e) || b.isRegularExpressionLiteral(e)) {
2774
+ for (let n of e.getSourceFiles()) n.getFullText().includes(R) && n.forEachDescendant((e) => {
2775
+ if (x.isStringLiteral(e) || x.isNoSubstitutionTemplateLiteral(e) || x.isTemplateHead(e) || x.isTemplateMiddle(e) || x.isTemplateTail(e) || x.isRegularExpressionLiteral(e)) {
2776
2776
  let n = e.getText();
2777
- if (!n.includes(B)) return;
2778
- e.replaceWithText(n.split(B).join(bn)), t++;
2777
+ if (!n.includes(R)) return;
2778
+ e.replaceWithText(n.split(R).join(Sn)), t++;
2779
2779
  }
2780
2780
  });
2781
2781
  return t;
2782
- }, wn = (e) => {
2782
+ }, Tn = (e) => {
2783
2783
  let t = 0, n = 0;
2784
2784
  for (let r of e.getSourceFiles()) {
2785
- let e = Sn(r, xn);
2785
+ let e = Cn(r, z);
2786
2786
  e.length !== 0 && r.forEachDescendant((r) => {
2787
- if (!b.isCallExpression(r) || !e.includes(r.getExpression().getText())) return;
2787
+ if (!x.isCallExpression(r) || !e.includes(r.getExpression().getText())) return;
2788
2788
  let i = r.getParent();
2789
- if (b.isPropertyAccessExpression(i) && i.getName() === "lang") {
2789
+ if (x.isPropertyAccessExpression(i) && i.getName() === "lang") {
2790
2790
  i.getNameNode().replaceWithText("locale"), t++;
2791
2791
  return;
2792
2792
  }
2793
- if (b.isVariableDeclaration(i)) {
2793
+ if (x.isVariableDeclaration(i)) {
2794
2794
  let e = i.getNameNode();
2795
- if (!b.isObjectBindingPattern(e)) return;
2795
+ if (!x.isObjectBindingPattern(e)) return;
2796
2796
  for (let t of e.getElements()) {
2797
2797
  if (t.getPropertyNameNode() !== void 0) continue;
2798
2798
  let e = t.getNameNode();
2799
- !b.isIdentifier(e) || e.getText() !== "lang" || (t.replaceWithText(`locale: ${t.getText()}`), n++);
2799
+ !x.isIdentifier(e) || e.getText() !== "lang" || (t.replaceWithText(`locale: ${t.getText()}`), n++);
2800
2800
  }
2801
2801
  }
2802
2802
  });
@@ -2805,29 +2805,29 @@ var S = (e, t, n) => {
2805
2805
  access: t,
2806
2806
  destructured: n
2807
2807
  };
2808
- }, Tn = w({
2808
+ }, En = T({
2809
2809
  version: "0.30.0",
2810
2810
  id: "0.30.0/01_locale-cookie-rename",
2811
2811
  title: "the language-preference cookie is `voltro:locale` (was `voltro:lang`)",
2812
2812
  kind: "transform",
2813
2813
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
2814
2814
  let t = e.getFullText();
2815
- return t.includes(B) || t.includes(z) && (t.includes("LANG_COOKIE") || t.includes(xn));
2815
+ return t.includes(R) || t.includes(L) && (t.includes("LANG_COOKIE") || t.includes(z));
2816
2816
  }),
2817
2817
  transform: ({ project: e, helpers: t, log: n }) => {
2818
2818
  let r = t.renameImport(e, {
2819
- module: z,
2819
+ module: L,
2820
2820
  from: "LANG_COOKIE",
2821
2821
  to: "LOCALE_COOKIE"
2822
- }), i = Cn(e), { access: a, destructured: o } = wn(e), s = [];
2822
+ }), i = wn(e), { access: a, destructured: o } = Tn(e), s = [];
2823
2823
  r > 0 && s.push(`LANG_COOKIE → LOCALE_COOKIE (${r} import(s))`), i > 0 && s.push(`'voltro:lang' → 'voltro:locale' (${i} literal(s))`), a + o > 0 && s.push(`parsePreferenceCookies().lang → .locale (${a + o} site(s))`), s.length !== 0 && n(`locale cookie rename: ${s.join("; ")}. Your SOURCE is migrated. Your USERS' cookies are not — the old \`voltro:lang\` cookie in an already-visited browser is no longer read, so each user falls back to Accept-Language / defaultLocale once and re-picks their language. Nothing errors. If that reset is unacceptable, copy the old value forward at your own boot — the codemod file has the three-line bridge.` + (o > 0 ? ` ${o} destructure(s) were rewritten as \`{ locale: lang }\` to keep the local name safe from a scope collision — rename the binding yourself if you want the vocabulary to match.` : ""));
2824
2824
  }
2825
- }), En = /auditPlugin\s*\(|versioningPlugin\s*\(|atlassianPlugin\s*\(/, Dn = w({
2825
+ }), Dn = /auditPlugin\s*\(|versioningPlugin\s*\(|atlassianPlugin\s*\(/, On = T({
2826
2826
  version: "0.30.0",
2827
2827
  id: "0.30.0/02_audit-purge-query-recheck",
2828
2828
  title: "re-run the credential-purge query — the 0.28.0 spelling under-reported on MySQL/MariaDB",
2829
2829
  kind: "manual",
2830
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => En.test(e.getFullText())),
2830
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Dn.test(e.getFullText())),
2831
2831
  note: [
2832
2832
  "IF YOU ALREADY RAN THE 0.28.0 PURGE CHECK ON MySQL OR MariaDB, RUN IT AGAIN.",
2833
2833
  "The query that note shipped under-reported, and it under-reported by",
@@ -2874,14 +2874,14 @@ var S = (e, t, n) => {
2874
2874
  " `connectionCredentials(...)` keeps it in the framework vault",
2875
2875
  " (see `0.28.0/03_atlassian-credentials-context`)."
2876
2876
  ].join("\n")
2877
- }), On = /\.webhooks\s*\.\s*emit\s*\(|useWebhooks\s*\(/, kn = w({
2877
+ }), kn = /\.webhooks\s*\.\s*emit\s*\(|useWebhooks\s*\(/, An = T({
2878
2878
  version: "0.30.0",
2879
2879
  id: "0.30.0/03_webhook-emit-durable-deferral",
2880
2880
  title: "a deferred `webhooks.emit` rides the transactional outbox; `{ immediate: true }` opts out",
2881
2881
  kind: "manual",
2882
2882
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
2883
2883
  let t = e.getFullText();
2884
- return On.test(t) || T("emit").test(t);
2884
+ return kn.test(t) || E("emit").test(t);
2885
2885
  }),
2886
2886
  note: [
2887
2887
  "A `ctx.webhooks.emit(...)` inside a MUTATION now enqueues a transactional",
@@ -2931,14 +2931,14 @@ var S = (e, t, n) => {
2931
2931
  "created by the declarative differ on the next `voltro dev` boot or",
2932
2932
  "`voltro db apply` — there is nothing to run by hand."
2933
2933
  ].join("\n")
2934
- }), An = /\.executionId\b|workflows\s*\.\s*start\s*\(|workflows\s*\.\s*child\s*\(/, jn = w({
2934
+ }), jn = /\.executionId\b|workflows\s*\.\s*start\s*\(|workflows\s*\.\s*child\s*\(/, Mn = T({
2935
2935
  version: "0.30.0",
2936
2936
  id: "0.30.0/04_workflow-run-handle-nullable-execution",
2937
2937
  title: "`WorkflowRunHandle.executionId` is `string | null`; `status` gains queued/dropped/skipped",
2938
2938
  kind: "manual",
2939
2939
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
2940
2940
  let t = e.getFullText();
2941
- return An.test(t) || T("start").test(t);
2941
+ return jn.test(t) || E("start").test(t);
2942
2942
  }),
2943
2943
  note: [
2944
2944
  "A workflow start no longer always becomes a run. With declarative flow",
@@ -3003,12 +3003,12 @@ var S = (e, t, n) => {
3003
3003
  " and `singleton: { mode: 'skip' }` throws `WorkflowSingletonHeldError`",
3004
3004
  " carrying the incumbent's execution id, so you can wait on THAT run."
3005
3005
  ].join("\n")
3006
- }), Mn = /auditPlugin\s*\(|versioningPlugin\s*\(|atlassianPlugin\s*\(/, Nn = w({
3006
+ }), Nn = /auditPlugin\s*\(|versioningPlugin\s*\(|atlassianPlugin\s*\(/, Pn = T({
3007
3007
  version: "0.31.0",
3008
3008
  id: "0.31.0/01_rerun-credential-scan",
3009
3009
  title: "re-run `voltro db scan-credentials` — on 0.30.0–0.30.2 it skipped the column credentials land in",
3010
3010
  kind: "manual",
3011
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Mn.test(e.getFullText())),
3011
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Nn.test(e.getFullText())),
3012
3012
  note: [
3013
3013
  "IF YOU RAN `voltro db scan-credentials` ON 0.30.0, 0.30.1 OR 0.30.2, RUN IT",
3014
3014
  "AGAIN. Its answer did not cover the column a leaked credential is most",
@@ -3042,7 +3042,7 @@ var S = (e, t, n) => {
3042
3042
  "credential on the Subject — `connectionCredentials(...)` keeps it in the",
3043
3043
  "framework vault."
3044
3044
  ].join("\n")
3045
- }), Pn = w({
3045
+ }), Fn = T({
3046
3046
  version: "0.32.0",
3047
3047
  id: "0.32.0/01_audit-redacts-outcome",
3048
3048
  title: "`auditPlugin` now redacts `outcome` by default — opt out if your trail reads it",
@@ -3083,7 +3083,7 @@ var S = (e, t, n) => {
3083
3083
  "purge the rows AND rotate the credentials — assume anything written to a",
3084
3084
  "log table has been read."
3085
3085
  ].join("\n")
3086
- }), Fn = w({
3086
+ }), In = T({
3087
3087
  version: "0.33.0",
3088
3088
  id: "0.33.0/01_opentelemetry-optional-peer",
3089
3089
  title: "`@effect/opentelemetry` is now an optional peer — install it if you use tracing",
@@ -3115,7 +3115,7 @@ var S = (e, t, n) => {
3115
3115
  "never wrote down. It is a startup failure, not a silent loss of traces —",
3116
3116
  "losing telemetry quietly would be the worse outcome."
3117
3117
  ].join("\n")
3118
- }), In = w({
3118
+ }), Ln = T({
3119
3119
  version: "0.33.0",
3120
3120
  id: "0.33.0/02_coordinated-handle-gained-wake",
3121
3121
  title: "`CoordinatedScheduleHandle` gained `wake()`, `currentIntervalMs()` and `isArmed()` — update hand-written test doubles",
@@ -3165,7 +3165,7 @@ var S = (e, t, n) => {
3165
3165
  "Reporting nothing is the conservative default on purpose — a task that",
3166
3166
  "cannot tell whether it was idle must not be slowed down on a guess."
3167
3167
  ].join("\n")
3168
- }), Ln = w({
3168
+ }), Rn = T({
3169
3169
  version: "0.34.0",
3170
3170
  id: "0.34.0/01_serve-does-mount-inspect",
3171
3171
  title: "Correction: `voltro serve` DOES mount an inspect surface (the 0.12.0 note said it mounts none)",
@@ -3206,12 +3206,12 @@ var S = (e, t, n) => {
3206
3206
  "place, everyone already past 0.12.0 — which is everyone the wrong sentence",
3207
3207
  "could have misled — would never have seen it."
3208
3208
  ].join("\n")
3209
- }), Rn = (e) => /\.with\(\s*[\w.]*tenant\(/.test(e) || /\.serverOnly\(\s*\)/.test(e), zn = w({
3209
+ }), zn = (e) => /\.with\(\s*[\w.]*tenant\(/.test(e) || /\.serverOnly\(\s*\)/.test(e), Bn = T({
3210
3210
  version: "0.34.0",
3211
3211
  id: "0.34.0/02_keyed-writes-are-tenant-scoped",
3212
3212
  title: "Keyed-by-pk writes are tenant-enforced, and `crud` refuses `.serverOnly()` input",
3213
3213
  kind: "manual",
3214
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Rn(e.getFullText())),
3214
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => zn(e.getFullText())),
3215
3215
  note: [
3216
3216
  "TWO WRITE-SIDE REFUSALS ARE NEW. Neither changes a type, so `tsc` will not",
3217
3217
  "point at the call sites — these are runtime behaviour changes.",
@@ -3275,30 +3275,30 @@ var S = (e, t, n) => {
3275
3275
  " an attack look like a no-op and leaves an honest caller debugging a",
3276
3276
  " value that quietly did not land."
3277
3277
  ].join("\n")
3278
- }), Bn = /* @__PURE__ */ new Set([
3278
+ }), Vn = /* @__PURE__ */ new Set([
3279
3279
  "defineQuery",
3280
3280
  "defineMutation",
3281
3281
  "defineAction",
3282
3282
  "defineStream"
3283
- ]), Vn = (e) => {
3283
+ ]), Hn = (e) => {
3284
3284
  let t = e.getArguments()[0];
3285
- if (t === void 0 || !t.isKind(x.ObjectLiteralExpression)) return /* @__PURE__ */ new Set();
3285
+ if (t === void 0 || !t.isKind(S.ObjectLiteralExpression)) return /* @__PURE__ */ new Set();
3286
3286
  let n = /* @__PURE__ */ new Set();
3287
3287
  for (let e of t.getProperties()) {
3288
- let t = (e.asKind(x.PropertyAssignment) ?? e.asKind(x.ShorthandPropertyAssignment))?.getName?.();
3288
+ let t = (e.asKind(S.PropertyAssignment) ?? e.asKind(S.ShorthandPropertyAssignment))?.getName?.();
3289
3289
  t !== void 0 && n.add(t);
3290
3290
  }
3291
3291
  return n;
3292
- }, Hn = (e, t) => /define(Query|Mutation|Action|Stream)\s*\(/.test(e) ? t.some((e) => {
3293
- if (!Bn.has(e.getExpression().getText())) return !1;
3294
- let t = Vn(e);
3292
+ }, Un = (e, t) => /define(Query|Mutation|Action|Stream)\s*\(/.test(e) ? t.some((e) => {
3293
+ if (!Vn.has(e.getExpression().getText())) return !1;
3294
+ let t = Hn(e);
3295
3295
  return !t.has("internal") && !t.has("guards") && !t.has("openAccess");
3296
- }) : !1, Un = w({
3296
+ }) : !1, Wn = T({
3297
3297
  version: "0.34.0",
3298
3298
  id: "0.34.0/03_procedure-access-decision",
3299
3299
  title: "Every wire-exposed procedure declares an access decision (`guards:` or `openAccess:`)",
3300
3300
  kind: "manual",
3301
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Hn(e.getFullText(), e.getDescendantsOfKind(x.CallExpression))),
3301
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Un(e.getFullText(), e.getDescendantsOfKind(S.CallExpression))),
3302
3302
  note: [
3303
3303
  "YOUR APP WILL NOT BOOT UNTIL EVERY WIRE-EXPOSED PROCEDURE DECIDES WHO MAY",
3304
3304
  "CALL IT. This is the single largest behaviour change in this release, and it",
@@ -3343,18 +3343,18 @@ var S = (e, t, n) => {
3343
3343
  "procedures your PLUGINS declare — the gate reads your app's own discovered",
3344
3344
  "files only, so adopting this does not turn into a plugin bug report."
3345
3345
  ].join("\n")
3346
- }), Wn = [
3346
+ }), Gn = [
3347
3347
  "workosAuthorizationUrl",
3348
3348
  "workosAuthenticateWithCode",
3349
3349
  "workosBeginLogin"
3350
- ], Gn = w({
3350
+ ], Kn = T({
3351
3351
  version: "0.34.0",
3352
3352
  id: "0.34.0/04_workos-oauth-state-pkce",
3353
3353
  title: "WorkOS hosted login: `workosAuthorizationUrl` → `workosBeginLogin`, with mandatory `state` + PKCE",
3354
3354
  kind: "manual",
3355
3355
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
3356
3356
  let t = e.getFullText();
3357
- return t.includes("@voltro/plugin-auth-workos") && Wn.some((e) => t.includes(e));
3357
+ return t.includes("@voltro/plugin-auth-workos") && Gn.some((e) => t.includes(e));
3358
3358
  }),
3359
3359
  note: [
3360
3360
  "YOUR WORKOS HOSTED-LOGIN FLOW HAD NO CSRF PROTECTION BY DEFAULT, AND NO PKCE.",
@@ -3412,12 +3412,12 @@ var S = (e, t, n) => {
3412
3412
  "are accepted on the existing endpoints; your registered redirect URI is",
3413
3413
  "unchanged."
3414
3414
  ].join("\n")
3415
- }), Kn = /mirrorTables|_?voltro_mirror_/, qn = w({
3415
+ }), qn = /mirrorTables|_?voltro_mirror_/, Jn = T({
3416
3416
  version: "0.34.0",
3417
3417
  id: "0.34.0/05_analytics-mirror-versioned-writes",
3418
3418
  title: "Analytics CDC-mirror: version-ordered writes, tombstoned deletes, retry + repair",
3419
3419
  kind: "manual",
3420
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Kn.test(e.getFullText())),
3420
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => qn.test(e.getFullText())),
3421
3421
  note: [
3422
3422
  "The analytics CDC-mirror (`mirrorTables`) now orders every write and",
3423
3423
  "retries the ones that fail. Three things to do.",
@@ -3472,7 +3472,7 @@ var S = (e, t, n) => {
3472
3472
  " VOLTRO_ANALYTICS_MIRROR_REPAIR_INTERVAL_MS (60000, 0 disables)",
3473
3473
  " VOLTRO_ANALYTICS_MIRROR_REPAIR_QUEUE_LIMIT (10000)"
3474
3474
  ].join("\n")
3475
- }), Jn = w({
3475
+ }), Yn = T({
3476
3476
  version: "0.34.0",
3477
3477
  id: "0.34.0/06_origin-guard-and-trusted-proxies",
3478
3478
  title: "Cross-site protection, trusted proxies and security headers are ON by default on the api listener",
@@ -3596,7 +3596,7 @@ var S = (e, t, n) => {
3596
3596
  " a `Transfer-Encoding: chunked` body skipped it and was buffered without",
3597
3597
  " bound. No action needed; VOLTRO_MAX_RPC_BODY_BYTES still tunes it."
3598
3598
  ].join("\n")
3599
- }), Yn = w({
3599
+ }), Xn = T({
3600
3600
  version: "0.34.0",
3601
3601
  id: "0.34.0/07_incoming-webhook-declares-verification",
3602
3602
  title: "An incoming webhook with no signature scheme now refuses to boot",
@@ -3645,7 +3645,7 @@ var S = (e, t, n) => {
3645
3645
  "value — the sender holds the other half of it, so a generated one would",
3646
3646
  "authenticate nobody."
3647
3647
  ].join("\n")
3648
- }), Xn = w({
3648
+ }), Zn = T({
3649
3649
  version: "0.34.0",
3650
3650
  id: "0.34.0/08_init-initialises-the-workspace",
3651
3651
  title: "`voltro init` initialises the workspace root; scaffolding is `create-project` only",
@@ -3697,20 +3697,20 @@ var S = (e, t, n) => {
3697
3697
  "all 18 of its scripts — including the `pnpm db:up` the CLI then printed as a",
3698
3698
  "next step. Run `voltro init` in the root first."
3699
3699
  ].join("\n")
3700
- }), Zn = (e, t) => (e.getProperty(t)?.asKind(x.PropertyAssignment))?.getInitializer()?.asKind(x.ObjectLiteralExpression), Qn = (e) => {
3700
+ }), Qn = (e, t) => (e.getProperty(t)?.asKind(S.PropertyAssignment))?.getInitializer()?.asKind(S.ObjectLiteralExpression), $n = (e) => {
3701
3701
  if (e.getExpression().getText() !== "workflow") return !1;
3702
- let t = e.getArguments()[0]?.asKind(x.ObjectLiteralExpression);
3702
+ let t = e.getArguments()[0]?.asKind(S.ObjectLiteralExpression);
3703
3703
  if (t === void 0) return !1;
3704
- let n = Zn(t, "messages");
3704
+ let n = Qn(t, "messages");
3705
3705
  return n !== void 0 && n.getProperty("queries") !== void 0;
3706
- }, $n = w({
3706
+ }, er = T({
3707
3707
  version: "0.34.0",
3708
3708
  id: "0.34.0/09_workflow-messages-queries-removed",
3709
3709
  title: "`workflow({ messages: { queries } })` is removed — it never had a send path",
3710
3710
  kind: "manual",
3711
3711
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
3712
3712
  let t = e.getFullText();
3713
- return !t.includes("queries") || !/workflow\s*\(/.test(t) ? !1 : e.getDescendantsOfKind(x.CallExpression).some(Qn);
3713
+ return !t.includes("queries") || !/workflow\s*\(/.test(t) ? !1 : e.getDescendantsOfKind(S.CallExpression).some($n);
3714
3714
  }),
3715
3715
  note: [
3716
3716
  "YOU DECLARED `messages: { queries: { … } }` ON A WORKFLOW. That channel is",
@@ -3753,7 +3753,7 @@ var S = (e, t, n) => {
3753
3753
  "in-flight runs finish on the old path after a deploy. See the Versioning",
3754
3754
  "page in the docs."
3755
3755
  ].join("\n")
3756
- }), er = w({
3756
+ }), tr = T({
3757
3757
  version: "0.34.0",
3758
3758
  id: "0.34.0/11_ai-flows-per-step-human-signal",
3759
3759
  title: "`HUMAN_RESPONSE_SIGNAL` removed — a human step's signal name is now per-step",
@@ -3797,23 +3797,23 @@ var S = (e, t, n) => {
3797
3797
  "per flow (`humanTimeoutMs`), per app (`aiFlowsPlugin({ humanReviewTimeoutMs })`)",
3798
3798
  "or per deploy (`VOLTRO_AI_FLOW_HUMAN_REVIEW_TIMEOUT_HOURS`). `0` = wait forever."
3799
3799
  ].join("\n")
3800
- }), tr = /* @__PURE__ */ new Set([
3800
+ }), nr = /* @__PURE__ */ new Set([
3801
3801
  "@voltro/plugin-auth",
3802
3802
  "@voltro/protocol/session",
3803
3803
  "@voltro/plugin-sso-saml"
3804
- ]), nr = /* @__PURE__ */ new Set(["issueSession", "signSession"]), rr = (e) => {
3804
+ ]), rr = /* @__PURE__ */ new Set(["issueSession", "signSession"]), ir = (e) => {
3805
3805
  let t = e.getFullText();
3806
3806
  if (!/issueSession|signSession|resolveScopes|@voltro\/(plugin-auth|protocol\/session|plugin-sso-saml)/.test(t)) return !1;
3807
- for (let t of e.getImportDeclarations()) if (tr.has(t.getModuleSpecifierValue())) return !0;
3808
- for (let t of e.getDescendantsOfKind(x.CallExpression)) if (nr.has(t.getExpression().getText())) return !0;
3809
- for (let t of e.getDescendantsOfKind(x.PropertyAssignment)) if (t.getName() === "resolveScopes") return !0;
3807
+ for (let t of e.getImportDeclarations()) if (nr.has(t.getModuleSpecifierValue())) return !0;
3808
+ for (let t of e.getDescendantsOfKind(S.CallExpression)) if (rr.has(t.getExpression().getText())) return !0;
3809
+ for (let t of e.getDescendantsOfKind(S.PropertyAssignment)) if (t.getName() === "resolveScopes") return !0;
3810
3810
  return !1;
3811
- }, ir = w({
3811
+ }, ar = T({
3812
3812
  version: "0.34.0",
3813
3813
  id: "0.34.0/12_session-cookie-carries-identity",
3814
3814
  title: "The session cookie carries identity only — authority resolves per request",
3815
3815
  kind: "manual",
3816
- appliesTo: ({ project: e }) => e.getSourceFiles().some(rr),
3816
+ appliesTo: ({ project: e }) => e.getSourceFiles().some(ir),
3817
3817
  note: [
3818
3818
  "YOUR USERS WILL SIGN IN ONCE MORE, AND A SESSION COOKIE CAN NO LONGER",
3819
3819
  "CARRY PERMISSIONS.",
@@ -3904,12 +3904,12 @@ var S = (e, t, n) => {
3904
3904
  "not have: an authoritative resolver now overrides a token's own scope",
3905
3905
  "claim, which union-only could never do."
3906
3906
  ].join("\n")
3907
- }), ar = /\bMockEmail\b|\.email\.(sent|lastTo|clear)\b|\bSentEmail\b/, or = w({
3907
+ }), or = /\bMockEmail\b|\.email\.(sent|lastTo|clear)\b|\bSentEmail\b/, sr = T({
3908
3908
  version: "0.34.0",
3909
3909
  id: "0.34.0/10_testing-mail-is-a-service",
3910
3910
  title: "`ctx.email` / `MockEmail` removed — assert mail through the mail plugin",
3911
3911
  kind: "manual",
3912
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => ar.test(e.getFullText())),
3912
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => or.test(e.getFullText())),
3913
3913
  note: [
3914
3914
  "`@voltro/testing` no longer ships `MockEmail`, and `makeTestContext()` no",
3915
3915
  "longer returns `ctx.email`. Your tests that touch it will not compile.",
@@ -3978,12 +3978,12 @@ var S = (e, t, n) => {
3978
3978
  " • `makeTestContext({ layers })` — your app's own `app.config.ts`",
3979
3979
  " `layers:` now resolve for an Effect-mode handler under test."
3980
3980
  ].join("\n")
3981
- }), sr = /from\s+['"]@voltro\/plugin-billing(?:\/[\w-]+)?['"]/, cr = w({
3981
+ }), cr = /from\s+['"]@voltro\/plugin-billing(?:\/[\w-]+)?['"]/, lr = T({
3982
3982
  version: "0.34.0",
3983
3983
  id: "0.34.0/13_billing-provider-dunning",
3984
3984
  title: "Billing dunning — `BillingProvider.fetchSubscription` is required, and `plan()` now honours a grace period",
3985
3985
  kind: "manual",
3986
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => sr.test(e.getFullText())),
3986
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => cr.test(e.getFullText())),
3987
3987
  note: [
3988
3988
  "BILLING GAINED A DUNNING SEQUENCE. TWO THINGS CHANGED UNDER YOU.",
3989
3989
  "",
@@ -4059,12 +4059,12 @@ var S = (e, t, n) => {
4059
4059
  "`pastDueSince` / `statusEventAt` columns ride the declarative differ — they",
4060
4060
  "are applied by `voltro db apply` and by a `voltro dev` boot, on every dialect."
4061
4061
  ].join("\n")
4062
- }), lr = /from\s+['"]@voltro\/plugin-search(?:\/[\w-]+)?['"]/, ur = w({
4062
+ }), ur = /from\s+['"]@voltro\/plugin-search(?:\/[\w-]+)?['"]/, dr = T({
4063
4063
  version: "0.34.0",
4064
4064
  id: "0.34.0/14_search-memory-backend-refuses-production",
4065
4065
  title: "`plugin-search` on the in-memory backend now refuses to boot in production",
4066
4066
  kind: "manual",
4067
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => lr.test(e.getFullText())),
4067
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => ur.test(e.getFullText())),
4068
4068
  note: [
4069
4069
  "THE ZERO-CONFIG SEARCH BACKEND NO LONGER BOOTS IN PRODUCTION.",
4070
4070
  "",
@@ -4133,7 +4133,7 @@ var S = (e, t, n) => {
4133
4133
  "(`ts_headline` / `snippet()` / …) that `SearchQuery` promises. Degrading",
4134
4134
  "those silently would replace one lie with another."
4135
4135
  ].join("\n")
4136
- }), dr = w({
4136
+ }), fr = T({
4137
4137
  version: "0.34.0",
4138
4138
  id: "0.34.0/15_mysql-mssql-tls-honoured",
4139
4139
  title: "mysql/mariadb and mssql connections honour ?ssl= / ?sslmode= — or refuse to boot",
@@ -4205,7 +4205,7 @@ var S = (e, t, n) => {
4205
4205
  "spins in-tick and starves the event loop. Set it only if you know nothing in",
4206
4206
  "your process does that, and set it well above their peak concurrency."
4207
4207
  ].join("\n")
4208
- }), fr = w({
4208
+ }), pr = T({
4209
4209
  version: "0.34.0",
4210
4210
  id: "0.34.0/16_declare-node-env-for-migration-commands",
4211
4211
  title: "`voltro db apply` / `voltro migrate` now treat an unset NODE_ENV as production",
@@ -4269,12 +4269,12 @@ var S = (e, t, n) => {
4269
4269
  "process environment if you want a non-production `voltro serve` without a",
4270
4270
  "built bundle. Everything downstream of the launcher does honour `.env`."
4271
4271
  ].join("\n")
4272
- }), pr = /\/(?:src\/)?pages\//, mr = /\bcacheInvalidatesOn\b/, hr = w({
4272
+ }), mr = /\/(?:src\/)?pages\//, hr = /\bcacheInvalidatesOn\b/, gr = T({
4273
4273
  version: "0.34.0",
4274
4274
  id: "0.34.0/17_isr-postgres-refuses-without-a-database",
4275
4275
  title: "The postgres ISR cache + CDC invalidator read DB_URL, and no longer fall back silently",
4276
4276
  kind: "manual",
4277
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => mr.test(e.getFullText()) || pr.test(e.getFilePath())),
4277
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => hr.test(e.getFullText()) || mr.test(e.getFilePath())),
4278
4278
  note: [
4279
4279
  "YOUR WEB PROCESS MAY HAVE BEEN USING THE MEMORY ISR CACHE ALL ALONG.",
4280
4280
  "",
@@ -4319,12 +4319,12 @@ var S = (e, t, n) => {
4319
4319
  " until their `revalidate` window expires. Give the web process a `DB_URL`",
4320
4320
  " or remove the declaration."
4321
4321
  ].join("\n")
4322
- }), gr = /\.migration\.ts$/, _r = w({
4322
+ }), _r = /\.migration\.ts$/, vr = T({
4323
4323
  version: "0.34.0",
4324
4324
  id: "0.34.0/18_serve-refuses-pending-file-migrations",
4325
4325
  title: "`voltro serve` refuses to boot with pending file-based migrations",
4326
4326
  kind: "manual",
4327
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => gr.test(e.getFilePath())),
4327
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => _r.test(e.getFilePath())),
4328
4328
  note: [
4329
4329
  "YOUR DEPLOY MUST RUN `migrations/*.migration.ts` BEFORE `voltro serve`.",
4330
4330
  "",
@@ -4367,11 +4367,11 @@ var S = (e, t, n) => {
4367
4367
  "fingerprint check — it is the one switch for \"no boot-time schema checks\",",
4368
4368
  "and it now covers both."
4369
4369
  ].join("\n")
4370
- }), vr = /* @__PURE__ */ new Set([
4370
+ }), yr = /* @__PURE__ */ new Set([
4371
4371
  "@voltro/plugin-auth",
4372
4372
  "@voltro/plugin-auth/schema",
4373
4373
  "@voltro/plugin-auth/plugin"
4374
- ]), yr = /* @__PURE__ */ new Set([
4374
+ ]), br = /* @__PURE__ */ new Set([
4375
4375
  "UserStore",
4376
4376
  "authTables",
4377
4377
  "SendEmail",
@@ -4379,20 +4379,20 @@ var S = (e, t, n) => {
4379
4379
  "memoryUserStore",
4380
4380
  "postgresUserStore",
4381
4381
  "authRoutesPlugin"
4382
- ]), br = (e) => {
4382
+ ]), xr = (e) => {
4383
4383
  if (!e.getFullText().includes("@voltro/plugin-auth")) return !1;
4384
- for (let t of e.getImportDeclarations()) if (vr.has(t.getModuleSpecifierValue())) {
4385
- for (let e of t.getNamedImports()) if (yr.has(e.getName())) return !0;
4384
+ for (let t of e.getImportDeclarations()) if (yr.has(t.getModuleSpecifierValue())) {
4385
+ for (let e of t.getNamedImports()) if (br.has(e.getName())) return !0;
4386
4386
  if (t.getNamespaceImport() !== void 0) return !0;
4387
4387
  }
4388
- for (let t of e.getDescendantsOfKind(x.TypeReference)) if (t.getTypeName().getText() === "UserStore") return !0;
4388
+ for (let t of e.getDescendantsOfKind(S.TypeReference)) if (t.getTypeName().getText() === "UserStore") return !0;
4389
4389
  return !1;
4390
- }, xr = w({
4390
+ }, Sr = T({
4391
4391
  version: "0.34.0",
4392
4392
  id: "0.34.0/20_user-store-auth-feature-methods",
4393
4393
  title: "UserStore gained the email-verification, invitation and impersonation methods",
4394
4394
  kind: "manual",
4395
- appliesTo: ({ project: e }) => e.getSourceFiles().some(br),
4395
+ appliesTo: ({ project: e }) => e.getSourceFiles().some(xr),
4396
4396
  note: [
4397
4397
  "IF YOU USE memoryUserStore OR postgresUserStore, THERE IS NOTHING TO DO.",
4398
4398
  "Both already implement everything below. Read on only if you wrote your",
@@ -4472,7 +4472,7 @@ var S = (e, t, n) => {
4472
4472
  " is the difference between \"new signups must confirm\" and \"everyone is",
4473
4473
  " locked out until they check their mail\"."
4474
4474
  ].join("\n")
4475
- }), Sr = /* @__PURE__ */ new Set([
4475
+ }), B = /* @__PURE__ */ new Set([
4476
4476
  "bindConnectionSubject",
4477
4477
  "getConnectionSubject",
4478
4478
  "unbindConnectionSubject",
@@ -4481,12 +4481,12 @@ var S = (e, t, n) => {
4481
4481
  ]), Cr = (e) => {
4482
4482
  let t = e.getFullText();
4483
4483
  if (!/ConnectionSubject|connectionSubjectsSnapshot|rebind/.test(t)) return !1;
4484
- for (let t of e.getDescendantsOfKind(x.CallExpression)) if (Sr.has(t.getExpression().getText())) return !0;
4485
- for (let t of e.getImportDeclarations()) for (let e of t.getNamedImports()) if (Sr.has(e.getName())) return !0;
4486
- for (let t of e.getDescendantsOfKind(x.PropertyAssignment)) if (t.getName() === "rebind") return !0;
4487
- for (let t of e.getDescendantsOfKind(x.ShorthandPropertyAssignment)) if (t.getName() === "rebind") return !0;
4484
+ for (let t of e.getDescendantsOfKind(S.CallExpression)) if (B.has(t.getExpression().getText())) return !0;
4485
+ for (let t of e.getImportDeclarations()) for (let e of t.getNamedImports()) if (B.has(e.getName())) return !0;
4486
+ for (let t of e.getDescendantsOfKind(S.PropertyAssignment)) if (t.getName() === "rebind") return !0;
4487
+ for (let t of e.getDescendantsOfKind(S.ShorthandPropertyAssignment)) if (t.getName() === "rebind") return !0;
4488
4488
  return !1;
4489
- }, wr = w({
4489
+ }, wr = T({
4490
4490
  version: "0.34.0",
4491
4491
  id: "0.34.0/19_connection-override-carries-a-credential",
4492
4492
  title: "Soft re-auth presents a credential — it no longer hands the server a Subject",
@@ -4580,10 +4580,10 @@ var S = (e, t, n) => {
4580
4580
  if (/\.workflow(\.server)?\.tsx?$/.test(e.getBaseName())) return !0;
4581
4581
  let t = e.getFullText();
4582
4582
  if (!/resolveScopes|defineWorkflow|ctx\.workflows/.test(t)) return !1;
4583
- for (let t of e.getDescendantsOfKind(x.PropertyAssignment)) if (t.getName() === "resolveScopes") return !0;
4584
- for (let t of e.getDescendantsOfKind(x.CallExpression)) if (t.getExpression().getText() === "defineWorkflow") return !0;
4583
+ for (let t of e.getDescendantsOfKind(S.PropertyAssignment)) if (t.getName() === "resolveScopes") return !0;
4584
+ for (let t of e.getDescendantsOfKind(S.CallExpression)) if (t.getExpression().getText() === "defineWorkflow") return !0;
4585
4585
  return !1;
4586
- }, Er = w({
4586
+ }, Er = T({
4587
4587
  version: "0.34.0",
4588
4588
  id: "0.34.0/20_workflow-authority-resolves-at-resume",
4589
4589
  title: "A resumed workflow re-resolves its caller's authority instead of restoring it",
@@ -4668,9 +4668,9 @@ var S = (e, t, n) => {
4668
4668
  let t = e.getName();
4669
4669
  if (t === "EntityAdminSpec" || t === "deriveEntityAdmins") return !0;
4670
4670
  }
4671
- for (let t of e.getDescendantsOfKind(x.PropertyAccessExpression)) if (Dr.has(t.getName())) return !0;
4671
+ for (let t of e.getDescendantsOfKind(S.PropertyAccessExpression)) if (Dr.has(t.getName())) return !0;
4672
4672
  return !1;
4673
- }, kr = w({
4673
+ }, kr = T({
4674
4674
  version: "0.34.0",
4675
4675
  id: "0.34.0/21_admin-gates-on-declared-access",
4676
4676
  title: "The derived admin gates on the api's DECLARED access — the invented `<table>:create` scopes are gone",
@@ -4765,7 +4765,7 @@ var S = (e, t, n) => {
4765
4765
  "from \"undeclared\", so an intentionally public action rendered as forbidden",
4766
4766
  "to everybody."
4767
4767
  ].join("\n")
4768
- }), Ar = /\b(useMutation|useAction|useWorkflow|useWorkflowSignal|useWorkflowUpdate|useUpload)\b/, jr = /FiberFailure|Cause\.squash|squashWith/, Mr = w({
4768
+ }), Ar = /\b(useMutation|useAction|useWorkflow|useWorkflowSignal|useWorkflowUpdate|useUpload)\b/, jr = /FiberFailure|Cause\.squash|squashWith/, Mr = T({
4769
4769
  version: "0.34.0",
4770
4770
  id: "0.34.0/22_write-rejections-carry-the-typed-error",
4771
4771
  title: "A rejected mutate/run/signal/upload carries the typed error, not a FiberFailure",
@@ -4825,7 +4825,7 @@ var S = (e, t, n) => {
4825
4825
  "contract, reject from a real rpc client, or assert on the value your",
4826
4826
  "component branches on rather than on the literal the test wrote."
4827
4827
  ].join("\n")
4828
- }), Nr = /@voltro\/plugin-sso-saml|samlSsoPlugin/, Pr = w({
4828
+ }), Nr = /@voltro\/plugin-sso-saml|samlSsoPlugin/, Pr = T({
4829
4829
  version: "0.34.0",
4830
4830
  id: "0.34.0/23_saml-signature-and-replay-defaults",
4831
4831
  title: "SAML: response-signature requirement relaxed + surfaced; replay protection ON by default",
@@ -4921,14 +4921,14 @@ var S = (e, t, n) => {
4921
4921
  "request, and the envelope is unauthenticated. If you must run unsolicited,",
4922
4922
  "set `wantAuthnResponseSigned: true`."
4923
4923
  ].join("\n")
4924
- }), Fr = /\b(signPayload|verifySignature)\s*\(/, Ir = w({
4924
+ }), Fr = /\b(signPayload|verifySignature)\s*\(/, Ir = T({
4925
4925
  version: "0.34.0",
4926
4926
  id: "0.34.0/24_webhook-signing-renders-headers",
4927
4927
  title: "Webhook signing: `signPayload`/`verifySignature` → `signRequest`/`verifyRequest`",
4928
4928
  kind: "manual",
4929
4929
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Fr.test(e.getFullText())),
4930
4930
  note: "A webhook signature scheme now renders a header MAP rather than one header,\nand verifies against the request's whole header map.\n\n - signPayload(scheme, rawBody, secret)\n → signRequest(scheme, { rawBody, secret, messageId })\n returns { headers } instead of { headerName, headerValue }\n\n - verifySignature(scheme, rawBody, secret, headerValue)\n → verifyRequest(scheme, { rawBody, secret, headers })\n\nRewrite each call site:\n\n // before\n const { headerName, headerValue } = signPayload(scheme, body, secret)\n await fetch(url, { headers: { [headerName]: headerValue }, body })\n\n // after\n const { headers } = signRequest(scheme, { rawBody: body, secret, messageId: deliveryId })\n await fetch(url, { headers, body })\n\nWHY `messageId` IS REQUIRED, AND WHY THIS IS NOT AUTOMATED:\n\n Standard Webhooks signs over `<messageId>.<timestamp>.<body>` and sends the\n id as `webhook-id` — which is also the value the spec tells consumers to\n use as their IDEMPOTENCY KEY. Pass the id that identifies the message to\n THAT receiver, stable across retries (the framework passes its delivery\n id). A fresh `randomUUID()` per attempt compiles and reads fine, and\n quietly makes every retry look like a new message to the receiver.\n\nTHE DEFAULT OUTGOING SCHEME CHANGED:\n\n `defaultOutgoingSignature()` now returns Standard Webhooks v1.0.0 —\n `webhook-id` / `webhook-timestamp` / `webhook-signature: v1,<base64>` — so\n a NEW subscription signs to an interoperable spec every conformant\n consumer library already verifies. Existing target rows are untouched:\n each stores the scheme it was created with.\n\n Its key serialization differs too: `whsec_` + base64 of 24..64 random\n bytes, HMAC-ing the DECODED bytes. `subscribe()` mints the right shape\n automatically; passing your own hex secret for a standard-webhooks target\n is REFUSED at subscribe rather than producing signatures no consumer\n accepts.\n\n To keep the previous house format on a new subscription, pass it\n explicitly: `webhooks.subscribe({ …, signing: genericHmacSignature() })`\n — the same shape as before, under a name that says it is a choice.\n\nALSO IN THIS RELEASE, no code change needed:\n\n - a 3xx response is a failure and redirects are no longer FOLLOWED (they\n were, by fetch's default — which also walked past the SSRF guard);\n - a `410 Gone` disables the target immediately, whatever the\n consecutive-failure threshold says;\n - each attempt has a 30s wire timeout (`VOLTRO_WEBHOOK_TIMEOUT_MS`, or\n `timeoutMs`). There was none, so a receiver that accepted the connection\n and never answered held the delivery workflow open indefinitely.\n\nAnd `slackSignature()` is now a REAL verifier. It shipped as a stub that\nreturned false unconditionally, workable only through a header-packing\nconvention in the route mounter. A scheme now receives the whole header map\nand reads `x-slack-request-timestamp` itself."
4931
- }), Lr = /from\s+['"]@voltro\/plugin-presence(?:\/[a-z]+)?['"]/, Rr = /\b(lastSeen|presenceTable|memoryPresenceStore|isOnline|filterOnline|staleKeys|PresenceStore|PresenceEntry|PresenceTracker)\b/, zr = /_voltro_presence/, Br = w({
4931
+ }), Lr = /from\s+['"]@voltro\/plugin-presence(?:\/[a-z]+)?['"]/, Rr = /\b(lastSeen|presenceTable|memoryPresenceStore|isOnline|filterOnline|staleKeys|PresenceStore|PresenceEntry|PresenceTracker)\b/, zr = /_voltro_presence/, Br = T({
4932
4932
  version: "0.34.0",
4933
4933
  id: "0.34.0/25_presence-has-no-table",
4934
4934
  title: "Presence: `_voltro_presence` is gone, and so is `lastSeen` on the roster",
@@ -4940,10 +4940,10 @@ var S = (e, t, n) => {
4940
4940
  note: "@voltro/plugin-presence no longer declares a table. Four things moved.\n\n1. A roster member is now { key, meta } — there is NO `lastSeen`.\n\n It was the OWNING replica's clock, so on more than one replica \"active 3\n minutes ago\" rendered from it was wrong by the skew between two pods, with\n nothing to say so. It was also the only field a plain heartbeat changed,\n which is what made the roster push on every beat instead of on every change.\n\n // before\n members.map((m) => `${m.key} — ${ago(m.lastSeen)}`)\n // after: put anything time-shaped you need into meta\n usePresence(channel, { meta: { joinedAt: Date.now() } })\n members.map((m) => `${m.key} — ${ago(m.meta?.joinedAt)}`)\n\n2. `PresenceTracker.track()` and `.untrack()` return { delta, changed }.\n\n // before\n bus.announce(tracker.track({ channel, tenantId, key }))\n // after — announce ALWAYS (a peer's sweep needs the liveness signal),\n // push only on a change\n const write = tracker.track({ channel, tenantId, key })\n bus.announce(write.delta)\n if (write.changed) publishReactivity(store, presenceRoster)\n\n Do not blind-append `.delta` and move on: dropping `changed` restores the\n per-heartbeat push this release removed, and nothing will report it.\n\n `attachPresenceBus` also takes an optional `onRosterChanged` now — call it\n if you drive the bus yourself, or a peer's join and a peer's death will\n update your tracker and push nobody.\n\n3. These exports are DELETED, with no replacement:\n\n presenceTable, PresenceStore, PresenceEntry,\n memoryPresenceStore, isOnline, filterOnline, staleKeys\n\n They were the table era's read model — an abstraction over rows plus the\n staleness filter a row needed because it outlived the client that wrote it.\n Presence has neither. Reading the roster is `presence.list` / `usePresence`;\n the staleness sweep is the plugin's own and needs nothing from you.\n\n4. If your `source:` names '_voltro_presence' as a string, point it at the\n channel object instead:\n\n import { presenceRoster } from '@voltro/plugin-presence'\n defineQuery({ name: 'room.summary', source: presenceRoster, ... })\n\n And to push it yourself:\n\n import { publishReactivity } from '@voltro/protocol'\n publishReactivity(ctx.store, presenceRoster)\n\nTHE TABLE IS NOT DROPPED FOR YOU. The declarative differ never plans a drop for\na framework table no app declares — deliberately, so a live `_voltro_*` table is\nnever destroyed by an app that simply does not know about it. So an existing\n`_voltro_presence` stays, empty, until you remove it:\n\n DROP TABLE IF EXISTS _voltro_presence;\n\nIt holds no data anyone can use: nothing has written a row to it since presence\nmoved into memory."
4941
4941
  }), Vr = (e) => {
4942
4942
  let t = e.getArguments()[0];
4943
- if (t === void 0 || !t.isKind(x.ObjectLiteralExpression)) return /* @__PURE__ */ new Set();
4943
+ if (t === void 0 || !t.isKind(S.ObjectLiteralExpression)) return /* @__PURE__ */ new Set();
4944
4944
  let n = /* @__PURE__ */ new Set();
4945
4945
  for (let e of t.getProperties()) {
4946
- let t = (e.asKind(x.PropertyAssignment) ?? e.asKind(x.ShorthandPropertyAssignment))?.getName?.();
4946
+ let t = (e.asKind(S.PropertyAssignment) ?? e.asKind(S.ShorthandPropertyAssignment))?.getName?.();
4947
4947
  t !== void 0 && n.add(t);
4948
4948
  }
4949
4949
  return n;
@@ -4951,21 +4951,21 @@ var S = (e, t, n) => {
4951
4951
  if (e.getExpression().getText() !== "defineEvent") return !1;
4952
4952
  let t = Vr(e);
4953
4953
  return !t.has("guards") && !t.has("openAccess");
4954
- }) : !1, Ur = w({
4954
+ }) : !1, Ur = T({
4955
4955
  version: "0.35.0",
4956
4956
  id: "0.35.0/01_event-access-decision",
4957
4957
  title: "Every declared event decides who may listen (`guards:` or `openAccess:`)",
4958
4958
  kind: "manual",
4959
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Hr(e.getFullText(), e.getDescendantsOfKind(x.CallExpression))),
4959
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Hr(e.getFullText(), e.getDescendantsOfKind(S.CallExpression))),
4960
4960
  note: (/* @__PURE__ */ "YOUR APP WILL NOT BOOT UNTIL EVERY DECLARED EVENT DECIDES WHO MAY LISTEN.(This closes the sibling of 0.34.0's procedure gate: `defineEvent`'s `guards:`(was optional and enforcement skipped an empty list, so an event with no(decision was silently subscribable by ANY authenticated session that could(open the socket.((WHAT TO DO, IN THIS ORDER:((1) LIST THEM. `voltro doctor` prints every undecided event with its tag and( file — the same set, from the same function, that the boot refuses on.( `voltro doctor --json` carries it as `accessDecisions.undecided` for CI.((2) DECIDE EACH ONE. There are exactly two answers and they are not the same( claim:(( guards: [{ scope: 'display:read' }] a subscriber must hold a scope( openAccess: 'public scoreboard' anyone may listen — and why(( `openAccess` takes a REASON, not a boolean. It is what a reviewer reads( later, and it is what makes \"we decided this is open\" distinguishable( from \"nobody looked\".((3) DO NOT RUBBER-STAMP. Reaching for a scope every caller already holds( satisfies the gate, reads as protection, and enforces nothing. If the( event is open, say `openAccess`. If it should not be open, this gate just( found a real hole — an event often carries exactly the data its table( guards behind a scoped query.((IF YOU NEED TO SHIP FIRST. The same field that covers procedures covers(events, in `app.config.ts`:(( export default { security: { defaultDeny: false } }((That restores the old default-allow for the WHOLE app, in one place a(reviewer can see. There is no env var for it, on purpose. `voltro doctor`(keeps listing the undecided events while it is off, marked advisory.((NOT AFFECTED: apps already running `security: { defaultDeny: false }`, and(events your PLUGINS declare — the gate reads your app's own discovered(`*.event.ts` files only.".split("(")).join("\n")
4961
- }), Wr = /\brouteHttp\s*\(/, Gr = w({
4961
+ }), Wr = /\brouteHttp\s*\(/, Gr = T({
4962
4962
  version: "0.34.0",
4963
4963
  id: "0.34.0/26_route-http-is-async",
4964
4964
  title: "`routeHttp` returns a Promise",
4965
4965
  kind: "manual",
4966
4966
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Wr.test(e.getFullText())),
4967
4967
  note: "`routeHttp` (@voltro/mcp) is async now:\n\n routeHttp(options): HttpOutcome // before\n routeHttp(options): Promise<HttpOutcome> // after\n\nAwait it, and make the enclosing function async if it is not already:\n\n // before\n const outcome = routeHttp({ method, url, body, tools })\n if (outcome.kind === 'json') respond(outcome)\n\n // after\n const outcome = await routeHttp({ method, url, body, tools })\n if (outcome.kind === 'json') respond(outcome)\n\nWhy it moved: `tools/list` now folds in the app's agent tools and `tools/call`\nexecutes one, so two of the methods reach the app. The rest still resolve without\ntouching anything, and the function is still pure in the sense that matters — it\nbinds no socket, and a test supplies a `live` double.\n\nIf you use the shipped `serveHttp` transport there is nothing to do; it already\nawaits."
4968
- }), Kr = /@voltro\/plugin-(billing|governance|storage)/, qr = /* @__PURE__ */ RegExp("\\b(billing\\.(startCheckout|portalUrl|previewChange|changePlan|changeSeats|invoices|reportUsage)|governance\\.(export|erase)|storage\\.(mintUploadUrl|ingestUrl|listRefs))\\b"), Jr = /\bdefinePluginRoute\s*\(|\broutes\s*:\s*\[/, Yr = w({
4968
+ }), Kr = /@voltro\/plugin-(billing|governance|storage)/, qr = /* @__PURE__ */ RegExp("\\b(billing\\.(startCheckout|portalUrl|previewChange|changePlan|changeSeats|invoices|reportUsage)|governance\\.(export|erase)|storage\\.(mintUploadUrl|ingestUrl|listRefs))\\b"), Jr = /\bdefinePluginRoute\s*\(|\broutes\s*:\s*\[/, Yr = T({
4969
4969
  version: "0.35.0",
4970
4970
  id: "0.35.0/02_plugin-procedures-carry-access-decisions",
4971
4971
  title: "First-party plugin procedures carry access decisions; twelve now require a scope",
@@ -5037,7 +5037,7 @@ var S = (e, t, n) => {
5037
5037
  "`openAccess` with the reason each is safe (self-scoped rows, signed",
5038
5038
  "subject-bound tickets, per-object grants), and behave exactly as before."
5039
5039
  ].join("\n")
5040
- }), Xr = /AnalyticsMirrorImpl/, Zr = w({
5040
+ }), Xr = /AnalyticsMirrorImpl/, Zr = T({
5041
5041
  version: "0.35.0",
5042
5042
  id: "0.35.0/03_analytics-mirror-max-version",
5043
5043
  title: "Custom analytics mirror sinks answer `maxVersion` (fleet-scope seed read)",
@@ -5067,21 +5067,21 @@ var S = (e, t, n) => {
5067
5067
  "",
5068
5068
  "The read is issued at most once per key per process, off the commit path."
5069
5069
  ].join("\n")
5070
- }), Qr = (e) => /RunStepStatus/.test(e) || /['"]succeeded['"]/.test(e) && /['"]waiting['"]/.test(e) && /RunStep|runStep|flow/i.test(e), $r = w({
5070
+ }), Qr = (e) => /RunStepStatus/.test(e) || /['"]succeeded['"]/.test(e) && /['"]waiting['"]/.test(e) && /RunStep|runStep|flow/i.test(e), $r = T({
5071
5071
  version: "0.35.0",
5072
5072
  id: "0.35.0/04_flow-run-step-skipped",
5073
5073
  title: "A flow run step can now be 'skipped' — handle it where you render a run timeline",
5074
5074
  kind: "manual",
5075
5075
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Qr(e.getFullText())),
5076
5076
  note: (/* @__PURE__ */ "A FLOW RUN STEP CAN NOW BE 'skipped'.((`when:` on a flow step gates it on a prior value, and a step whose condition(is false is SKIPPED rather than run. That is a sixth member of(`RunStepStatus`, so an exhaustive switch over it no longer compiles, and a(lookup keyed by status now has a hole.((WHAT TO DO:((1) FIND IT. Anywhere you render or branch on a run step status — a timeline( component, a status badge map, a progress calculation.((2) DECIDE WHAT A SKIPPED STEP LOOKS LIKE. It is NOT a failure and NOT a( success: it did not run, on purpose, and it produced no output. The( step carries `skipReason` — the rendered condition, e.g.( `{{mode}} equals \"full\"` — so the timeline can say WHY.((3) DO NOT DEFAULT IT TO INVISIBLE. Dropping the row compiles and reads fine( in review, and it reproduces exactly the problem `skipReason` exists to( prevent: a step that vanished is indistinguishable from a step nobody( declared, which is the first thing a user reports as a bug.((A PROGRESS COUNT probably needs a second look too: `succeeded / total` now(under-reports a completed run that skipped a step. Counting(`succeeded + skipped` against the total is usually what was meant.((If your app does not render a flow timeline, there is nothing to do — this(note fires on any file that names the status type or its literals.".split("(")).join("\n")
5077
- }), ei = w({
5077
+ }), ei = T({
5078
5078
  version: "0.35.0",
5079
5079
  id: "0.35.0/05_declared-schema-drops-runtime-flags",
5080
5080
  title: "The declared schema no longer reads `CDC` / `VOLTRO_UNDO` / `VOLTRO_TRACING_PERSIST`",
5081
5081
  kind: "manual",
5082
5082
  appliesTo: () => !0,
5083
5083
  note: (/* @__PURE__ */ "The set of framework tables your app DECLARES is hashed into the schema(fingerprint. A pre-deploy `voltro db apply` records that hash; every serving(container recomputes it and refuses to boot if it differs. Until this release(three RUNTIME flags fed into that set:(( CDC=0 removed `_voltro_cdc_offsets`( VOLTRO_UNDO=on added `_voltro_undo_log`( VOLTRO_TRACING_PERSIST=all added `_voltro_traces`((All three read as pure runtime knobs, so they tend to live on the PODS and(not on the pre-upgrade migrate Job — which is a green migrate followed by(every pod in CrashLoopBackOff. This was reported against a Helm deployment(where `CDC: \"0\"` was in all three overlays' pod blocks and none of their(jobs; it had been latent for months and would have fired on the next deploy.((WHAT YOU MUST DO — only if you set one of these to turn a table ON in(production:(( * `VOLTRO_UNDO=on` in production → add to app.config.ts:( export default { schema: { undo: true }, … }( * `VOLTRO_TRACING_PERSIST` keeping trace history in production → add:( export default { schema: { traces: true }, … }(( Without the declaration, `voltro serve` now REFUSES to boot and names the( field. That refusal is the point: before, the same configuration either( wrote to a table nobody created or produced a fingerprint the migrate job( could not reproduce.(( Turning capture OFF still needs no declaration and never will — a declared( table nobody writes to is harmless. Only turning it ON is a schema claim.((WHAT HAPPENS WITHOUT ANY ACTION:(( * On mariadb or mssql you will see ONE additive `create-table` in your next( plan: `_voltro_cdc_offsets` is now declared by the DIALECT rather than by( `CDC`, so it exists whether or not that process drives CDC. It is an( offsets table — a handful of rows — and the alternative was leaving a( runtime flag able to split your fleet.( * On postgres/sqlite nothing changes: those dialects have no offsets table.((AND ONE THING WORTH DOING ANYWAY: if you are upgrading a deployment that has(a separate migrate job, diff that job's `env:` against your pods' once. The(framework can now guarantee its OWN tables do not depend on a runtime flag;(it cannot make the same guarantee for a plugin's `extendSchema.tables`, which(is app code and may read anything. A `prod-mismatch` refusal now prints which(tables this process decided and from which input, so the comparison is quick.".split("(")).join("\n")
5084
- }), V = "session_expired", ti = w({
5084
+ }), V = "session_expired", ti = T({
5085
5085
  version: "0.35.1",
5086
5086
  id: "0.35.1/01_unauthorized-replaces-session-expired",
5087
5087
  title: "A 401 is `code: 'unauthorized'`; the vault's own failure is `'credential_unusable'`",
@@ -5097,14 +5097,14 @@ var S = (e, t, n) => {
5097
5097
  }
5098
5098
  n.length !== 0 && t(`rewrote '${V}' to 'unauthorized' in ${n.length} file(s).\n A 401 now means only "the credential was refused" — expired, revoked, wrong\n scope and MALFORMED all produce that status, and the old name asserted the\n first one. If a rewritten branch was meant to catch a credential your\n CONNECTION VAULT could not produce (no grant, revoked grant, failed refresh),\n change it to 'credential_unusable' — that is the code the vault raises, and it\n is the one where re-auth is genuinely the right action.\n A health check that logs the user out on this code should read 'unauthorized'\n as "ask again", not as "the session is gone".\n` + n.map((e) => ` ${e}`).join("\n"));
5099
5099
  }
5100
- }), ni = /\(\s*\{[^}]*\.\.\./, ri = /\bquery\??\.?\(\s*['"`]/, ii = w({
5100
+ }), ni = /\(\s*\{[^}]*\.\.\./, ri = /\bquery\??\.?\(\s*['"`]/, ii = T({
5101
5101
  version: "0.37.0",
5102
5102
  id: "0.37.0/01_procedure-input-rejects-undeclared-fields",
5103
5103
  title: "a procedure input rejects a field it does not declare (it used to drop it)",
5104
5104
  kind: "manual",
5105
5105
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
5106
5106
  let t = e.getFullText();
5107
- return ni.test(t) || ri.test(t) || T("query").test(t);
5107
+ return ni.test(t) || ri.test(t) || E("query").test(t);
5108
5108
  }),
5109
5109
  note: [
5110
5110
  "A procedure call whose input carries a field the schema does not declare",
@@ -5166,33 +5166,33 @@ var S = (e, t, n) => {
5166
5166
  "two, the only safe direction is to refuse — and a warning would have to be",
5167
5167
  "read by someone, in a log, after the wrong rows were already served."
5168
5168
  ].join("\n")
5169
- }), ai = "@voltro/plugin-auth", H = "@voltro/protocol", U = "IMPERSONATION_METADATA_KEY", oi = w({
5169
+ }), H = "@voltro/plugin-auth", U = "@voltro/protocol", W = "IMPERSONATION_METADATA_KEY", ai = T({
5170
5170
  version: "0.38.0",
5171
5171
  id: "0.38.0/01_impersonation-key-in-protocol",
5172
- title: `\`${U}\` is imported from @voltro/protocol`,
5172
+ title: `\`${W}\` is imported from @voltro/protocol`,
5173
5173
  kind: "transform",
5174
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getImportDeclarations().some((e) => e.getModuleSpecifierValue() === ai && e.getNamedImports().some((e) => e.getName() === U))),
5174
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getImportDeclarations().some((e) => e.getModuleSpecifierValue() === H && e.getNamedImports().some((e) => e.getName() === W))),
5175
5175
  transform: ({ project: e, log: t }) => {
5176
5176
  let n = 0;
5177
5177
  for (let t of e.getSourceFiles()) for (let e of t.getImportDeclarations()) {
5178
- if (e.getModuleSpecifierValue() !== ai) continue;
5179
- let r = e.getNamedImports().find((e) => e.getName() === U);
5178
+ if (e.getModuleSpecifierValue() !== H) continue;
5179
+ let r = e.getNamedImports().find((e) => e.getName() === W);
5180
5180
  if (r === void 0) continue;
5181
5181
  let i = r.getAliasNode()?.getText(), a = e.isTypeOnly() || r.isTypeOnly();
5182
5182
  r.remove();
5183
- let o = t.getImportDeclarations().find((e) => e.getModuleSpecifierValue() === H && e.isTypeOnly() === a), s = {
5184
- name: U,
5183
+ let o = t.getImportDeclarations().find((e) => e.getModuleSpecifierValue() === U && e.isTypeOnly() === a), s = {
5184
+ name: W,
5185
5185
  ...i === void 0 ? {} : { alias: i }
5186
5186
  };
5187
5187
  o === void 0 ? t.addImportDeclaration({
5188
- moduleSpecifier: H,
5188
+ moduleSpecifier: U,
5189
5189
  isTypeOnly: a,
5190
5190
  namedImports: [s]
5191
5191
  }) : o.addNamedImport(s), e.getNamedImports().length === 0 && e.getDefaultImport() === void 0 && e.getNamespaceImport() === void 0 && e.remove(), n += 1;
5192
5192
  }
5193
- n > 0 && t(`${U}: ${n} import(s) repointed to ${H}`);
5193
+ n > 0 && t(`${W}: ${n} import(s) repointed to ${U}`);
5194
5194
  }
5195
- }), si = w({
5195
+ }), oi = T({
5196
5196
  version: "0.38.0",
5197
5197
  id: "0.38.0/02_startup-failure-refuses-boot",
5198
5198
  title: "a `*.startup.ts` that fails refuses the boot (it used to warn and continue)",
@@ -5277,12 +5277,12 @@ var S = (e, t, n) => {
5277
5277
  "long work after, so a failure in the long half cannot hide a missing",
5278
5278
  "registration."
5279
5279
  ].join("\n")
5280
- }), ci = /\bStruct\(\s*\{\s*\}\s*\)/, li = w({
5280
+ }), si = /\bStruct\(\s*\{\s*\}\s*\)/, ci = T({
5281
5281
  version: "0.39.0",
5282
5282
  id: "0.39.0/01_empty-input-schema-rejects-every-field",
5283
5283
  title: "a procedure declaring `Schema.Struct({})` used to accept every field and now accepts none",
5284
5284
  kind: "manual",
5285
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => ci.test(e.getFullText())),
5285
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => si.test(e.getFullText())),
5286
5286
  note: [
5287
5287
  "CHECK YOUR `Schema.Struct({})` PROCEDURES FIRST. They have been accepting",
5288
5288
  "every field anyone sent them, and they now accept none — the one jump in",
@@ -5336,14 +5336,14 @@ var S = (e, t, n) => {
5336
5336
  "A procedure that genuinely takes no input keeps `Schema.Struct({})` and is",
5337
5337
  "correct as it stands — callers must send `{}` or nothing."
5338
5338
  ].join("\n")
5339
- }), ui = w({
5339
+ }), li = T({
5340
5340
  version: "0.40.0",
5341
5341
  id: "0.40.0/01_cold-start-failure-is-its-own-state",
5342
5342
  title: "a failed cold start is `failed: true`, not `loading: true`",
5343
5343
  kind: "manual",
5344
5344
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
5345
5345
  let t = e.getFullText();
5346
- return T("useSubscription").test(t) || T("usePreloadedSubscription").test(t) || /\buseWorkflow\w*\(/.test(t);
5346
+ return E("useSubscription").test(t) || E("usePreloadedSubscription").test(t) || /\buseWorkflow\w*\(/.test(t);
5347
5347
  }),
5348
5348
  note: [
5349
5349
  "A subscription whose COLD START failed no longer reports `loading: true`.",
@@ -5401,14 +5401,14 @@ var S = (e, t, n) => {
5401
5401
  " reconnect discards the error and re-subscribes, so the entry returns to",
5402
5402
  " pending on its own."
5403
5403
  ].join("\n")
5404
- }), di = w({
5404
+ }), ui = T({
5405
5405
  version: "0.40.0",
5406
5406
  id: "0.40.0/02_rejected-credential-widens-guard-results",
5407
5407
  title: "a rejected credential answers `Unauthenticated`, widening three guard results",
5408
5408
  kind: "manual",
5409
5409
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
5410
5410
  let t = e.getFullText();
5411
- return T("checkGuards").test(t) || T("checkGuardsEffect").test(t) || T("bindEvent").test(t);
5411
+ return E("checkGuards").test(t) || E("checkGuardsEffect").test(t) || E("bindEvent").test(t);
5412
5412
  }),
5413
5413
  note: [
5414
5414
  "A guard refusing a caller whose CREDENTIAL was rejected now answers",
@@ -5437,16 +5437,16 @@ var S = (e, t, n) => {
5437
5437
  "`openAccess` procedures are untouched: the stamp only travels, and only a",
5438
5438
  "guard that actually refuses ever spends it."
5439
5439
  ].join("\n")
5440
- }), fi = "@voltro/web/middleware", pi = /(^|[\\/])middleware\.tsx?$/, mi = w({
5440
+ }), di = "@voltro/web/middleware", fi = /(^|[\\/])middleware\.tsx?$/, pi = T({
5441
5441
  version: "0.41.0",
5442
5442
  id: "0.41.0/01_middleware-defines-where-it-runs",
5443
5443
  title: "`middleware.ts` exports `defineMiddleware(...)` and can say where it runs",
5444
5444
  kind: "transform",
5445
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => pi.test(e.getFilePath()) ? e.getExportAssignment((e) => !e.isExportEquals()) !== void 0 || e.getFunctions().some((e) => e.isDefaultExport()) : !1),
5445
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => fi.test(e.getFilePath()) ? e.getExportAssignment((e) => !e.isExportEquals()) !== void 0 || e.getFunctions().some((e) => e.isDefaultExport()) : !1),
5446
5446
  transform: ({ project: e, log: t }) => {
5447
5447
  let n = 0;
5448
5448
  for (let t of e.getSourceFiles()) {
5449
- if (!pi.test(t.getFilePath()) || t.getFullText().includes("defineMiddleware")) continue;
5449
+ if (!fi.test(t.getFilePath()) || t.getFullText().includes("defineMiddleware")) continue;
5450
5450
  let e = t.getExportAssignment((e) => !e.isExportEquals());
5451
5451
  if (e !== void 0) {
5452
5452
  let t = e.getExpression().getText();
@@ -5457,9 +5457,9 @@ var S = (e, t, n) => {
5457
5457
  let r = e.getText().replace(/^\s*export\s+default\s+/, "");
5458
5458
  e.replaceWithText(`export default defineMiddleware({ run: ${r} })`), n += 1;
5459
5459
  }
5460
- let r = t.getImportDeclarations().find((e) => e.getModuleSpecifierValue() === fi);
5460
+ let r = t.getImportDeclarations().find((e) => e.getModuleSpecifierValue() === di);
5461
5461
  r === void 0 ? t.insertImportDeclaration(0, {
5462
- moduleSpecifier: fi,
5462
+ moduleSpecifier: di,
5463
5463
  namedImports: [{ name: "defineMiddleware" }]
5464
5464
  }) : r.getNamedImports().some((e) => e.getName() === "defineMiddleware") || r.addNamedImport({ name: "defineMiddleware" });
5465
5465
  }
@@ -5496,7 +5496,7 @@ var S = (e, t, n) => {
5496
5496
  "Non-page requests (assets, unknown paths) are NOT matched unless you ask with",
5497
5497
  "`assets: true`. There is no render there, so only `setCookies` takes effect."
5498
5498
  ].join("\n")
5499
- }), hi = w({
5499
+ }), mi = T({
5500
5500
  version: "0.43.0",
5501
5501
  id: "0.43.0/01_dangling-reference-to-rows-refused",
5502
5502
  title: "`DanglingReferenceError` → `RowsRefusedError` (named for the outcome, not a guessed cause)",
@@ -5510,7 +5510,7 @@ var S = (e, t, n) => {
5510
5510
  });
5511
5511
  return r > 0 && n(`renamed ${r} import(s) of DanglingReferenceError → RowsRefusedError. A tag STRING (Effect.catchTag('DanglingReferenceError', …) or err._tag === '…') is NOT rewritten — change it to 'RowsRefusedError'. The payload also gained primaryCount, and each row a boolean derived.`), r;
5512
5512
  }
5513
- }), gi = w({
5513
+ }), hi = T({
5514
5514
  version: "0.44.0",
5515
5515
  id: "0.44.0/01_import-outcome",
5516
5516
  title: "`runImport` returns an ImportOutcome — read `.manifest` for the bundle",
@@ -5531,7 +5531,7 @@ var S = (e, t, n) => {
5531
5531
  "emptied of its old rows and holding part of the new ones. If you relied on per-table resume for a replace,",
5532
5532
  "that is the non-atomic path."
5533
5533
  ].join("\n")
5534
- }), _i = w({
5534
+ }), gi = T({
5535
5535
  version: "0.45.0",
5536
5536
  id: "0.45.0/01_typed-source-table-name",
5537
5537
  title: "`source:` is typechecked against the app’s tables, from the next `voltro dev`",
@@ -5571,14 +5571,14 @@ var S = (e, t, n) => {
5571
5571
  "To opt out entirely, delete `voltro-tables.generated.d.ts` and exclude it",
5572
5572
  "from your tsconfig; `source:` widens back to `string`."
5573
5573
  ].join("\n")
5574
- }), vi = w({
5574
+ }), _i = T({
5575
5575
  version: "0.46.0",
5576
5576
  id: "0.46.0/01_typed-aggregate-source",
5577
5577
  title: "an aggregate’s `incremental.source` is typechecked against the app’s tables",
5578
5578
  kind: "manual",
5579
5579
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => /\bincremental\s*:/.test(e.getFullText())),
5580
5580
  note: (/* @__PURE__ */ "An aggregate that opts into incremental maintenance names the table whose(CDC deltas drive it:(( incremental: { source: 'tasks', op: 'count' }((That name is now checked against your app’s tables, the same way a query’s(`source:` has been since 0.45.0.((WHY IT MATTERS MORE HERE((A name matching no table does not error. The runner subscribes to something(nothing writes, no delta ever arrives, and the aggregate quietly stops(tracking its input — while every read of it still succeeds and still returns(a number. There is no failing request to notice.((WHEN IT STARTS APPLYING((At your next `voltro dev` or `voltro codegen`, not at this upgrade — those(write `voltro-tables.generated.d.ts`, and that file is what narrows the type.(Until it exists the field is `string`, exactly as it is today.((WHAT TO DO((Boot once and read what `tsc` flags. A flagged name is either misspelled or(renamed away — a real defect, and that aggregate has not been maintaining —(or it is computed at runtime, in which case annotate it `string`.((The boot also warns now: an aggregate whose CDC source resolves to no table(is named in the same stale-`source:` warning queries and streams appear in.".split("(")).join("\n")
5581
- }), yi = w({
5581
+ }), vi = T({
5582
5582
  version: "0.49.0",
5583
5583
  id: "0.49.0/01_data-imports-to-transfers",
5584
5584
  title: "`voltro data imports` → `voltro data transfers`",
@@ -5611,7 +5611,7 @@ var S = (e, t, n) => {
5611
5611
  "marker, so `voltro db apply` (or a `voltro dev` boot) moves it with its rows on",
5612
5612
  "every dialect. If you query it directly, the new name is the one to use."
5613
5613
  ].join("\n")
5614
- }), bi = ["@voltro/database", "voltro/database"], W = [
5614
+ }), yi = ["@voltro/database", "voltro/database"], bi = [
5615
5615
  ["ImportRun", "DataTransferRun"],
5616
5616
  ["describeImportRun", "describeTransferRun"],
5617
5617
  ["IMPORT_RUNS_TABLE", "DATA_TRANSFERS_TABLE"],
@@ -5619,25 +5619,25 @@ var S = (e, t, n) => {
5619
5619
  ], G = "_voltro_data_imports", xi = "_voltro_data_transfers", Si = (e) => {
5620
5620
  let t = 0;
5621
5621
  for (let n of e.getSourceFiles()) n.getFullText().includes(G) && n.forEachDescendant((e) => {
5622
- if (b.isStringLiteral(e) || b.isNoSubstitutionTemplateLiteral(e) || b.isTemplateHead(e) || b.isTemplateMiddle(e) || b.isTemplateTail(e) || b.isRegularExpressionLiteral(e)) {
5622
+ if (x.isStringLiteral(e) || x.isNoSubstitutionTemplateLiteral(e) || x.isTemplateHead(e) || x.isTemplateMiddle(e) || x.isTemplateTail(e) || x.isRegularExpressionLiteral(e)) {
5623
5623
  let n = e.getText();
5624
5624
  if (!n.includes(G)) return;
5625
5625
  e.replaceWithText(n.split(G).join(xi)), t++;
5626
5626
  }
5627
5627
  });
5628
5628
  return t;
5629
- }, Ci = w({
5629
+ }, Ci = T({
5630
5630
  version: "0.49.0",
5631
5631
  id: "0.49.0/02_import-run-symbols",
5632
5632
  title: "`ImportRun` / `describeImportRun` / `IMPORT_RUNS_TABLE` → their `…TransferRun` names",
5633
5633
  kind: "transform",
5634
5634
  appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
5635
5635
  let t = e.getFullText();
5636
- return t.includes(G) ? !0 : bi.some((e) => t.includes(e)) ? W.some(([e]) => t.includes(e)) : !1;
5636
+ return t.includes(G) ? !0 : yi.some((e) => t.includes(e)) ? bi.some(([e]) => t.includes(e)) : !1;
5637
5637
  }),
5638
5638
  transform: ({ project: e, helpers: t, log: n }) => {
5639
5639
  let r = 0;
5640
- for (let n of bi) for (let [i, a] of W) r += t.renameImport(e, {
5640
+ for (let n of yi) for (let [i, a] of bi) r += t.renameImport(e, {
5641
5641
  module: n,
5642
5642
  from: i,
5643
5643
  to: a
@@ -5647,7 +5647,7 @@ var S = (e, t, n) => {
5647
5647
  let a = [];
5648
5648
  r > 0 && a.push(`${r} import(s) renamed to their \`…TransferRun\` names`), i > 0 && a.push(`${i} hand-spelled \`${G}\` → \`${xi}\``), n(`data-transfer run history: ${a.join("; ")}. An export writes to this record now, so the old names described half of what the table holds. The table itself carries a rename marker — \`voltro db apply\` (or a \`voltro dev\` boot) moves it with its rows on every dialect, so no data is lost and nothing is required of you there.`);
5649
5649
  }
5650
- }), wi = w({
5650
+ }), wi = T({
5651
5651
  version: "0.50.0",
5652
5652
  id: "0.50.0/01_transfer-runs-endpoint",
5653
5653
  title: "`GET /_voltro/admin/imports` → `GET /_voltro/admin/transfers`",
@@ -5671,7 +5671,7 @@ var S = (e, t, n) => {
5671
5671
  "hand is anything that calls it directly — a monitoring check, an uptime probe,",
5672
5672
  "a curl in a runbook, a dashboard datasource."
5673
5673
  ].join("\n")
5674
- }), Ti = w({
5674
+ }), Ti = T({
5675
5675
  version: "0.50.0",
5676
5676
  id: "0.50.0/02_reissue-data-imports-rename",
5677
5677
  title: "RE-CHECK: `voltro data imports` → `voltro data transfers` (0.49.0's note could not see your scripts)",
@@ -5703,7 +5703,7 @@ var S = (e, t, n) => {
5703
5703
  "`voltro db apply` moved `_voltro_data_imports` → `_voltro_data_transfers`",
5704
5704
  "with its rows."
5705
5705
  ].join("\n")
5706
- }), Ei = w({
5706
+ }), Ei = T({
5707
5707
  version: "0.50.0",
5708
5708
  id: "0.50.0/03_interrupted-replace-tables-nullable",
5709
5709
  title: "`InterruptedReplace.tables` is `number | null`",
@@ -5725,7 +5725,7 @@ var S = (e, t, n) => {
5725
5725
  "not to do is coerce it, because `Number(null)` is `0` and the sentence then",
5726
5726
  "reports a measurement that was never taken."
5727
5727
  ].join("\n")
5728
- }), Di = w({
5728
+ }), Di = T({
5729
5729
  version: "0.51.0",
5730
5730
  id: "0.51.0/01_computed-source-keeps-its-names",
5731
5731
  title: "A COMPUTED `source:` can keep its typed names — do not widen the whole registry",
@@ -5779,7 +5779,7 @@ var S = (e, t, n) => {
5779
5779
  "literal — a name read from a config file, or one assembled at runtime. Use it",
5780
5780
  "when there is no key set to keep, not when recovering one takes two lines."
5781
5781
  ].join("\n")
5782
- }), Oi = w({
5782
+ }), Oi = T({
5783
5783
  version: "0.51.0",
5784
5784
  id: "0.51.0/02_restore-refuses-without-marker",
5785
5785
  title: "`voltro data restore` refuses when it cannot write the in-progress marker",
@@ -5787,7 +5787,7 @@ var S = (e, t, n) => {
5787
5787
  reach: "beyond-source",
5788
5788
  appliesTo: ({ text: e }) => e.find("voltro data restore").length > 0,
5789
5789
  note: (/* @__PURE__ */ "`voltro data restore` now REFUSES when it cannot write the in-progress(marker, instead of restoring without one. A run that used to exit 0 can now(exit 1.((WHY((The marker is one row written before the first destructive statement, so a(restore that dies half-way refuses the next boot instead of serving a(half-loaded database. When it could not be written, the restore proceeded —(and then reported \"the next boot will REFUSE, by design\" over a database(with no marker at all. The next boot did not refuse.((WHICH RUNS CHANGE(( • the bookkeeping store will not open — wrong credentials, an unreachable( database, a missing env var, a server configuration the driver refuses;( • there is no `app.config.ts` from the working directory.((The second one surprises people: `restore` reads its TARGET from the(environment, so it looks like it needs no project. It does not, for the dump.(It needs one for the marker.((WHAT TO DO((Check every place you invoke it — cron entries, CI jobs, Makefiles, runbooks:(( grep -rn \"voltro data restore\" .((For each one, decide which it is. If the connection or the working directory(is wrong, fix that — the refusal names the reason on its own line. If the run(genuinely means to restore without the guard, say so:(( voltro data restore <dir> --no-marker((It warns every time and names why the marker was unavailable. Do not add it(everywhere as a migration step: it turns the refusal back off, which is the(behaviour this change removed.".split("(")).join("\n")
5790
- }), ki = w({
5790
+ }), ki = T({
5791
5791
  version: "0.51.0",
5792
5792
  id: "0.51.0/03_restore-assets-returns-counts",
5793
5793
  title: "`restoreAssetsFromCas` returns `{ count, objects }`, not a number",
@@ -5821,35 +5821,35 @@ var S = (e, t, n) => {
5821
5821
  "with `totalBytes` and `objectBytes` beside them), so a report can state all",
5822
5822
  "three rather than implying one."
5823
5823
  ].join("\n")
5824
- }), Ai = "@voltro/i18n", ji = "I18nProvider", Mi = (e) => {
5824
+ }), K = "@voltro/i18n", Ai = "I18nProvider", ji = (e) => {
5825
5825
  let t = [];
5826
5826
  for (let n of e.getImportDeclarations()) {
5827
5827
  let e = n.getModuleSpecifierValue();
5828
- if (e === Ai || e === `${Ai}/server`) for (let e of n.getNamedImports()) e.getName() === ji && t.push(e.getAliasNode()?.getText() ?? e.getName());
5828
+ if (e === K || e === `${K}/server`) for (let e of n.getNamedImports()) e.getName() === Ai && t.push(e.getAliasNode()?.getText() ?? e.getName());
5829
5829
  }
5830
5830
  return t;
5831
- }, Ni = (e, t) => [...e.getDescendantsOfKind(x.JsxOpeningElement), ...e.getDescendantsOfKind(x.JsxSelfClosingElement)].filter((e) => t.includes(e.getTagNameNode().getText())), Pi = w({
5831
+ }, Mi = (e, t) => [...e.getDescendantsOfKind(S.JsxOpeningElement), ...e.getDescendantsOfKind(S.JsxSelfClosingElement)].filter((e) => t.includes(e.getTagNameNode().getText())), Ni = T({
5832
5832
  version: "0.51.0",
5833
5833
  id: "0.51.0/04_i18n-timezone-prop",
5834
5834
  title: "`<I18nProvider>`: `timeZone` is its own prop, not an `intlConfig` member",
5835
5835
  kind: "transform",
5836
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => Mi(e).length > 0),
5836
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => ji(e).length > 0),
5837
5837
  transform: ({ project: e, log: t }) => {
5838
5838
  let n = 0, r = 0;
5839
5839
  for (let i of e.getSourceFiles()) {
5840
- let e = Mi(i);
5841
- if (e.length !== 0) for (let a of Ni(i, e)) {
5840
+ let e = ji(i);
5841
+ if (e.length !== 0) for (let a of Mi(i, e)) {
5842
5842
  let e = a.getAttribute("intlConfig");
5843
- if (!e || !b.isJsxAttribute(e)) continue;
5843
+ if (!e || !x.isJsxAttribute(e)) continue;
5844
5844
  let o = e.getInitializer();
5845
- if (!o || !b.isJsxExpression(o)) continue;
5845
+ if (!o || !x.isJsxExpression(o)) continue;
5846
5846
  let s = o.getExpression();
5847
- if (!s || !b.isObjectLiteralExpression(s)) {
5847
+ if (!s || !x.isObjectLiteralExpression(s)) {
5848
5848
  r++, t(` ? ${i.getBaseName()}:${a.getStartLineNumber()} — intlConfig is not an object literal; check it by hand for a \`timeZone\``);
5849
5849
  continue;
5850
5850
  }
5851
5851
  let c = s.getProperty("timeZone");
5852
- if (!c || !b.isPropertyAssignment(c)) continue;
5852
+ if (!c || !x.isPropertyAssignment(c)) continue;
5853
5853
  if (a.getAttribute("timeZone")) {
5854
5854
  c.remove(), n++;
5855
5855
  continue;
@@ -5863,16 +5863,620 @@ var S = (e, t, n) => {
5863
5863
  }
5864
5864
  n > 0 && (t(`I18nProvider: moved ${n} \`timeZone\` out of \`intlConfig\` onto its own prop`), t(" → it now reaches useFormatDate/useRelativeTime too, not only react-intl")), r > 0 && t(`I18nProvider: ${r} \`intlConfig\` value(s) could not be read — listed above`), n === 0 && r === 0 && (t("I18nProvider: no `intlConfig.timeZone` found — nothing to move"), t(" → if this app renders dates under SSR, set `timeZone` in the web app.config.ts"));
5865
5865
  }
5866
- }), K = "@voltro/plugin-versioning", q = "@voltro/plugin-row-history", Fi = [
5866
+ }), q = "@voltro/plugin-versioning", J = "@voltro/plugin-row-history", Pi = T({
5867
+ version: "0.52.0",
5868
+ id: "0.52.0/01_plugin-versioning-to-row-history",
5869
+ title: "@voltro/plugin-versioning → @voltro/plugin-row-history (the name now says what it does)",
5870
+ kind: "transform",
5871
+ appliesTo: ({ project: e, text: t }) => t.find(q).length > 0 || e.getSourceFiles().some((e) => e.getImportDeclarations().some((e) => e.getModuleSpecifierValue() === q)),
5872
+ transform: ({ project: e, helpers: t, log: n }) => {
5873
+ let r = t.renameImport(e, {
5874
+ module: q,
5875
+ from: "versioningPlugin",
5876
+ to: "rowHistoryPlugin"
5877
+ }) + t.renameImport(e, {
5878
+ module: q,
5879
+ from: "VersioningPluginOptions",
5880
+ to: "RowHistoryPluginOptions"
5881
+ });
5882
+ n(`${q} → ${J}: repointed ${t.renameModuleSpecifier(e, {
5883
+ from: q,
5884
+ to: J
5885
+ })} import(s)` + (r > 0 ? `, renamed ${r} versioningPlugin call site(s) to rowHistoryPlugin` : "") + `. Replace the dependency in package.json ("${q}" → "${J}") and reinstall — the table (_voltro_row_history) and VOLTRO_ROW_HISTORY_TTL_HOURS already carry the new name, so nothing else changes. If you passed an explicit alias: to the plugin it is kept as-is; only the DEFAULT alias moved from 'versioning' to 'row-history' (inspect endpoint paths + boot log lines).`);
5886
+ }
5887
+ }), Fi = T({
5888
+ version: "0.52.0",
5889
+ id: "0.52.0/02_http-interceptor-fail-closed",
5890
+ title: "A throwing `onHttpRequest` interceptor now refuses the request (500) instead of being ignored",
5891
+ kind: "manual",
5892
+ appliesTo: ({ text: e }) => e.find("onHttpRequest").length > 0,
5893
+ note: [
5894
+ "The HTTP interceptor chain is fail-closed now: if your `onHttpRequest`",
5895
+ "implementation throws, the request is REFUSED with 500 (previously the",
5896
+ "throw was swallowed and the request continued — a crashed security gate",
5897
+ "silently stopped guarding).",
5898
+ "",
5899
+ "Decide, per interceptor, what a failure of YOUR dependency should mean:",
5900
+ "",
5901
+ " · a security gate (auth fence, tenant guard): let the throw propagate —",
5902
+ " fail-closed is exactly what you want;",
5903
+ " · protection with a dependency (a rate-limit store, a lookup cache):",
5904
+ " catch INSIDE the interceptor and degrade loudly, e.g.",
5905
+ "",
5906
+ " try { decision = await store.consume(…) }",
5907
+ " catch (cause) { console.warn(\"… ALLOWED without limit\", cause); return next() }",
5908
+ "",
5909
+ " (that is what plugin-ratelimit's httpShield does now — a Redis outage",
5910
+ " must not become a self-inflicted API outage)."
5911
+ ].join("\n")
5912
+ }), Ii = T({
5913
+ version: "0.53.0",
5914
+ id: "0.53.0/01_conflict-columns-take-the-table",
5915
+ title: "`missingConflictColumns` takes the table and classifies absent vs NULL",
5916
+ kind: "manual",
5917
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getFullText().includes("missingConflictColumns")),
5918
+ note: [
5919
+ "`missingConflictColumns(conflictColumns, row)` is now",
5920
+ "`missingConflictColumns(table, conflictColumns, row)`, and it returns",
5921
+ "`{ column, reason: \"absent\" | \"null\" }` entries instead of bare names.",
5922
+ "",
5923
+ "Pass the table definition (the same value `missingRequiredColumns` takes).",
5924
+ "With it, DB-generated columns (`generatedAs`) are skipped: the database",
5925
+ "computes those and rejects an explicit value, so their absence from a",
5926
+ "payload is correct — the old signature could not know that and flagged",
5927
+ "them. Pass `undefined` only when no definition is available; nothing is",
5928
+ "skipped then.",
5929
+ "",
5930
+ "The `reason` field separates two different bugs the old flat list merged:",
5931
+ "`absent` (the payload never set the key) and `null` (the key is present but",
5932
+ "NULL — and NULL never matches a unique conflict target, so the write would",
5933
+ "always INSERT; use a plain insert for NULL-keyed rows, or make the column",
5934
+ "NOT NULL)."
5935
+ ].join("\n")
5936
+ }), Li = T({
5937
+ version: "0.53.0",
5938
+ id: "0.53.0/02_empty-struct-input-flipped",
5939
+ title: "`input: Schema.Struct({})` procedures flipped from accept-everything to reject-everything in 0.37.0",
5940
+ kind: "manual",
5941
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => /input:\s*Schema\.Struct\(\{\s*\}\)/.test(e.getFullText())),
5942
+ note: [
5943
+ "This repository declares procedures with `input: Schema.Struct({})`. Since",
5944
+ "0.37.0 (an undeclared input field rejects the call), that shape flipped",
5945
+ "HARDER than any other: an empty struct used to accept every payload, and",
5946
+ "now rejects every non-empty one. A caller that always sent a field —",
5947
+ "a table hook sending `{ limit }`, say — fails on the live subscription and",
5948
+ "the SSR seed of the same page at once.",
5949
+ "",
5950
+ "Check those procedures first — the list takes a minute to produce and",
5951
+ "yields the most expensive hits:",
5952
+ "",
5953
+ " grep -rn 'input: Schema.Struct({})' <api root>",
5954
+ "",
5955
+ "For each: either the callers genuinely send nothing (fine — the empty",
5956
+ "struct is the strongest declaration a procedure can make), or a caller",
5957
+ "sends fields the schema must now declare."
5958
+ ].join("\n")
5959
+ }), Ri = T({
5960
+ version: "0.53.0",
5961
+ id: "0.53.0/03_validation-messages-are-ids",
5962
+ title: "Form validation messages are ids + catalog now, not effect developer text",
5963
+ kind: "manual",
5964
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
5965
+ let t = e.getFullText();
5966
+ return /Expected .*, actual|is missing/.test(t) || /\.errors\[|useFormBinding\(/.test(t);
5967
+ }),
5968
+ note: [
5969
+ "Form validation messages changed shape:",
5970
+ "",
5971
+ "- `errors[field]` values are TRANSLATED texts from a message catalog",
5972
+ " (built-in en/de, locale from `<html lang>` or the `locale:` option),",
5973
+ " no longer effect developer sentences. If you laid regexes over",
5974
+ " \"Expected string, actual undefined\" to derive translation keys,",
5975
+ " delete them and wire your catalog instead:",
5976
+ "",
5977
+ " useFormBinding('users.create', { messages: (id, params) => t(id, params) })",
5978
+ "",
5979
+ "- `errors` keys are the FULL dotted path now (`address.city`,",
5980
+ " `entries.0.startsAt`) — a nested issue no longer collapses to its",
5981
+ " top-level segment. Code reading `errors.address` for a nested struct",
5982
+ " must read the leaf path it renders.",
5983
+ "",
5984
+ "- `validateFields` also returns `issues` — `{ path, id, params, fallback }`",
5985
+ " per failure — for widget kits that resolve messages themselves."
5986
+ ].join("\n")
5987
+ }), zi = T({
5988
+ version: "0.53.0",
5989
+ id: "0.53.0/04_widget-kind-gained-array",
5990
+ title: "WidgetKind gained 'array' and 'reference' — total widget registries need two new entries",
5991
+ kind: "manual",
5992
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => /Record<WidgetKind/.test(e.getFullText())),
5993
+ note: [
5994
+ "This repository types a widget registry as `Record<WidgetKind, …>`.",
5995
+ "`WidgetKind` gained `'array'` (arrays of structs are field arrays now)",
5996
+ "and `'reference'` (a formField({ reference }) table picker), so the total",
5997
+ "map needs two new entries. With no dedicated UI yet, map both to your",
5998
+ "custom/render-prop widget — that is what the built-in registry does:",
5999
+ "",
6000
+ " array: CustomWidget,",
6001
+ " reference: CustomWidget,",
6002
+ "",
6003
+ "Partial registries (`Partial<Record<WidgetKind, …>>` or per-kind lookups)",
6004
+ "need nothing."
6005
+ ].join("\n")
6006
+ }), Bi = T({
6007
+ version: "0.55.0",
6008
+ id: "0.55.0/01_target-relations-declare-columns",
6009
+ title: "A target's `relations:` names the junction's two columns now, not just the table",
6010
+ kind: "manual",
6011
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
6012
+ let t = e.getFullText();
6013
+ return /relations:\s*\{[^}{]*:\s*['"`]/.test(t);
6014
+ }),
6015
+ note: [
6016
+ "A write target declares its many-to-many relations as objects now:",
6017
+ "",
6018
+ " // before",
6019
+ " target: { table: 'employees', op: 'update',",
6020
+ " relations: { assignedStores: 'employee_assigned_stores' } }",
6021
+ "",
6022
+ " // after",
6023
+ " target: { table: 'employees', op: 'update',",
6024
+ " relations: { assignedStores: {",
6025
+ " junction: 'employee_assigned_stores',",
6026
+ " anchorColumn: 'employeeId', // the junction reference() pointing at `employees`",
6027
+ " targetColumn: 'storeId', // the junction's other reference()",
6028
+ " } } }",
6029
+ "",
6030
+ "Take both column names from the junction table's own `reference()`",
6031
+ "columns in your `db/` schema — `anchorColumn` is the one pointing at the",
6032
+ "target's table, `targetColumn` is the other one.",
6033
+ "",
6034
+ "Why: the SAME declaration now drives the optimistic update in the browser,",
6035
+ "so a multi-reference field flips as instantly as a scalar one instead of",
6036
+ "waiting for the server delta. The browser cannot import your schema, so the",
6037
+ "columns have to be stated. They are not taken on trust: before it writes,",
6038
+ "the server compares them against the junction's real reference columns and",
6039
+ "refuses — naming the correct pair — if they disagree."
6040
+ ].join("\n")
6041
+ }), Vi = T({
6042
+ version: "0.55.0",
6043
+ id: "0.55.0/02_widget-kind-gained-rich-text",
6044
+ title: "WidgetKind gained 'rich-text' — total widget registries need one new entry",
6045
+ kind: "manual",
6046
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => /Record<WidgetKind/.test(e.getFullText())),
6047
+ note: [
6048
+ "This repository types a widget registry as `Record<WidgetKind, …>`.",
6049
+ "`WidgetKind` gained `'rich-text'`, so the total map needs one new entry.",
6050
+ "The framework ships a default you can point at:",
6051
+ "",
6052
+ " import { RichTextWidget } from '@voltro/web'",
6053
+ " // …",
6054
+ " 'rich-text': RichTextWidget,",
6055
+ "",
6056
+ "Partial registries (`Partial<Record<WidgetKind, …>>` or per-kind lookups)",
6057
+ "need nothing."
6058
+ ].join("\n")
6059
+ }), Hi = T({
6060
+ version: "0.56.0",
6061
+ id: "0.56.0/01_inspect_answers_are_observations",
6062
+ title: "inspect answers are `Observation`s — the payload is `.data`",
6063
+ kind: "manual",
6064
+ reach: "beyond-source",
6065
+ appliesTo: ({ text: e }) => e.find("/_voltro/inspect").length > 0,
6066
+ note: [
6067
+ "Every 2xx `/_voltro/inspect/*` response is now an envelope:",
6068
+ "",
6069
+ " { data, scope, origin, completeness, capturedAt }",
6070
+ "",
6071
+ "What you read before is `.data`. So:",
6072
+ "",
6073
+ " curl …/_voltro/inspect/subscriptions | jq .resume",
6074
+ " curl …/_voltro/inspect/subscriptions | jq .data.resume # now",
6075
+ "",
6076
+ "The other four fields are the point of the change, not decoration:",
6077
+ "",
6078
+ " • `scope.kind` — `process` (this replica only), `shared-store` (every",
6079
+ " replica would answer the same), `fleet` (assembled), `declaration`",
6080
+ " (from the source tree). A `process` answer on a multi-replica",
6081
+ " deployment is a SAMPLE, and it used to look identical to a fleet-wide",
6082
+ " one.",
6083
+ " • `origin.replicaId` / `origin.instanceId` — which process answered, and",
6084
+ " which generation of it. Query twice and you can now tell whether you",
6085
+ " reached the same one.",
6086
+ " • `completeness.fleetSize` — how many replicas exist. ABSENT when it",
6087
+ " could not be determined; never `1` as a guess.",
6088
+ " • `capturedAt` — the ORIGIN’s clock. Do not compare it with another",
6089
+ " replica’s.",
6090
+ "",
6091
+ "Check anything that reads these endpoints outside your TypeScript sources:",
6092
+ "dashboards, `jq` filters in alerts, runbook commands, CI health checks,",
6093
+ "scrape configs. A reader that pulls a field straight off the top level now",
6094
+ "reads `undefined` — which in a health check is the failure mode worth",
6095
+ "looking for first, because `undefined` and \"zero\" render the same in most",
6096
+ "alerting rules."
6097
+ ].join("\n")
6098
+ }), Ui = T({
6099
+ version: "0.56.0",
6100
+ id: "0.56.0/02_broadcast_gap_is_one_object",
6101
+ title: "attachBroadcastBus's onGap now receives a single BroadcastGap object",
6102
+ kind: "manual",
6103
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getFullText().includes("attachBroadcastBus")),
6104
+ note: [
6105
+ "`attachBroadcastBus({ onGap })` now receives ONE argument — a `BroadcastGap`",
6106
+ "— instead of `(origin, missed)`.",
6107
+ "",
6108
+ " before: onGap: (origin, missed) => refreshAll(`missed ${missed} from ${origin}`)",
6109
+ " after: onGap: (gap) => refreshAll(gap.reason)",
6110
+ "",
6111
+ "`gap.reason` is a ready-made sentence for your log line. `gap.origin` and",
6112
+ "`gap.missed` are still there and still EXACT — but they are optional now,",
6113
+ "because the bus reports two kinds of hole and only one of them has a peer",
6114
+ "to blame:",
6115
+ "",
6116
+ " • a peer's serial jumped → `{ reason, origin, missed }`",
6117
+ " • this replica was not subscribed for a while, or the transport re-dialled",
6118
+ " → `{ reason }` alone",
6119
+ "",
6120
+ "The recovery is the same for both: re-run every live query. If your handler",
6121
+ "reads `origin`/`missed`, guard them — a hole with no peer is now reachable.",
6122
+ "",
6123
+ "Related, and needing no change from you: `attachBroadcastBus` no longer",
6124
+ "rejects when the broker is unreachable at attach time. It warns, keeps the",
6125
+ "local path working, retries in the background, and reports the time it spent",
6126
+ "unsubscribed as a gap. If you wrapped the call in a try/catch to survive a",
6127
+ "restarting broker, that catch is now dead code."
6128
+ ].join("\n")
6129
+ }), Wi = T({
6130
+ version: "0.56.0",
6131
+ id: "0.56.0/03_outbox_drain_needs_an_identity",
6132
+ title: "drainOutbox now requires claimedBy — the fleet-wide exactly-once gate",
6133
+ kind: "manual",
6134
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getFullText().includes("drainOutbox")),
6135
+ note: [
6136
+ "`drainOutbox({ … })` now requires a `claimedBy: string`.",
6137
+ "",
6138
+ "Why: the drain used to read every `pending` row and run its handler. Every",
6139
+ "replica runs the drain, so every effect was dispatched once PER REPLICA — on",
6140
+ "the happy path, every time. Rows are now claimed atomically",
6141
+ "(`pending → delivering`) and a racing replica loses the row instead of",
6142
+ "duplicating the side effect.",
6143
+ "",
6144
+ "What to pass: an id for this PROCESS, not for the replica. A replica name",
6145
+ "survives a restart (`POD_NAME` on a StatefulSet, `VOLTRO_REPLICA_ID` by",
6146
+ "definition) while the work does not, so a pod that died mid-delivery would",
6147
+ "look like it still held its own claim:",
6148
+ "",
6149
+ " import { instanceId } from '@voltro/protocol/identity'",
6150
+ " await drainOutbox({ store, handlers, claimedBy: instanceId() })",
6151
+ "",
6152
+ "Also new and optional: `claimLeaseMs` (default 5 minutes) — how long a claim",
6153
+ "stands before another replica may take the row. Set it ABOVE your slowest",
6154
+ "handler. A lease shorter than a real delivery hands the same row to a second",
6155
+ "replica while the first is still working, which is the duplicate this",
6156
+ "mechanism removes, reintroduced by a number.",
6157
+ "",
6158
+ "If you do NOT call `drainOutbox` yourself, there is nothing to do: `voltro",
6159
+ "dev` and `voltro serve` pass the identity for you."
6160
+ ].join("\n")
6161
+ }), Gi = T({
6162
+ version: "0.56.0",
6163
+ id: "0.56.0/04_reaction_dedupe_is_one_claim",
6164
+ title: "runReaction's dedupe is a single atomic claim",
6165
+ kind: "manual",
6166
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
6167
+ let t = e.getFullText();
6168
+ return t.includes("runReaction") || t.includes("ReactionRunDeps");
6169
+ }),
6170
+ note: [
6171
+ "`ReactionRunDeps.dedupe` changed from `{ has, add }` to `{ claim }`:",
6172
+ "",
6173
+ " // before",
6174
+ " dedupe: { has: (k) => seen.has(k), add: (k) => { seen.add(k) } }",
6175
+ " // after",
6176
+ " dedupe: { claim: (k) => (seen.has(k) ? false : (seen.add(k), true)) }",
6177
+ "",
6178
+ "Why it is not a rename. `has` then `add` is a read followed by a write, and",
6179
+ "two callers both read \"not seen\" before either writes — so `dedupeKey`, the",
6180
+ "field `defineReaction` refuses to boot without, let the same change act once",
6181
+ "per replica. Every single-instance run confirmed it worked, which is why it",
6182
+ "survived: the gate was real in exactly the configuration that cannot observe",
6183
+ "it.",
6184
+ "",
6185
+ "The claim is also taken BEFORE the act now, not after. That is what makes it",
6186
+ "a gate rather than a report, and it has a cost worth knowing: an act that",
6187
+ "THROWS has already consumed its key and is not re-run by a later duplicate.",
6188
+ "Durability belongs to the workflow the act starts, not to the trigger.",
6189
+ "",
6190
+ "If you do NOT call `runReaction` yourself, there is nothing to do. `voltro",
6191
+ "dev` and `voltro serve` now pass a durable, fleet-wide claimer",
6192
+ "(`_voltro_change_claims`), so a reaction acts once across the whole fleet",
6193
+ "instead of once per replica — no code change on your side."
6194
+ ].join("\n")
6195
+ }), Ki = T({
6196
+ version: "0.56.0",
6197
+ id: "0.56.0/05_plugin_bind_context_claims_a_change",
6198
+ title: "PluginBindContext requires claimChange; change identity moved to @voltro/database",
6199
+ kind: "manual",
6200
+ appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
6201
+ let t = e.getFullText();
6202
+ return t.includes("PluginBindContext") || t.includes("changeDigest") || t.includes("OccurrenceCounter") || t.includes("composeChangeKey") || t.includes("parseChangeKey");
6203
+ }),
6204
+ note: [
6205
+ "Two changes, both narrow.",
6206
+ "",
6207
+ "1. `PluginBindContext` now has a REQUIRED `claimChange(scope, key) =>",
6208
+ " Promise<boolean>`. The framework supplies it on both boot paths, so a",
6209
+ " plugin only READS it — nothing to do unless you construct a",
6210
+ " `PluginBindContext` yourself, which in practice means a unit-test",
6211
+ " harness:",
6212
+ "",
6213
+ " const ctx: PluginBindContext = {",
6214
+ " scheduleCoordinated: …,",
6215
+ " claimChange: () => Promise.resolve(true), // add this",
6216
+ " }",
6217
+ "",
6218
+ " In a real harness, decide what the case is testing before reaching for",
6219
+ " `true`: a stub that always claims asserts the SINGLE-replica behaviour.",
6220
+ " To exercise a fleet, share one claim ledger across the stubs — a `Set`",
6221
+ " of `${scope}/${key}` is enough.",
6222
+ "",
6223
+ " Why it is required rather than optional: an absent gate reads exactly",
6224
+ " like a gate that passed, and every consumer of this field is counting or",
6225
+ " sending something. `plugin-billing` accrued a usage unit per metered row",
6226
+ " change on EVERY replica, so a tenant on two pods was invoiced twice.",
6227
+ "",
6228
+ "2. The change-identity helpers moved from `@voltro/plugin-cdc-out` to",
6229
+ " `@voltro/database`, beside `ChangeEvent` itself:",
6230
+ "",
6231
+ " -import { changeDigest, composeChangeKey, OccurrenceCounter } from '@voltro/plugin-cdc-out'",
6232
+ " +import { changeDigest, composeChangeKey, OccurrenceCounter } from '@voltro/database'",
6233
+ "",
6234
+ " `parseChangeKey` moved with them. `canonicalJson` did NOT become public:",
6235
+ " it is internal to the digest, and `@voltro/runtime` already exports a",
6236
+ " different function of that name."
6237
+ ].join("\n")
6238
+ }), qi = T({
6239
+ version: "0.56.0",
6240
+ id: "0.56.0/06_serve_is_api_only",
6241
+ title: "`voltro serve` on a WEB app now refuses — use `voltro start`",
6242
+ kind: "manual",
6243
+ reach: "beyond-source",
6244
+ appliesTo: (e) => e.text.scanned === 0 || e.text.find("voltro serve").length > 0,
6245
+ note: [
6246
+ "`voltro serve` is the production server for an API app. On a WEB app it now",
6247
+ "refuses with a one-line message instead of what it used to do.",
6248
+ "",
6249
+ " web app, production voltro start . (unchanged)",
6250
+ " web app, development voltro dev . (unchanged)",
6251
+ " api app voltro serve . (unchanged)",
6252
+ "",
6253
+ "Nothing changes for an api. For a web app the old behaviour was not a working",
6254
+ "alternative that is being taken away:",
6255
+ "",
6256
+ " • In production it already failed — the serve fast path requires an api",
6257
+ " serve bundle, which a web build never produces, so it exited 1 pointing",
6258
+ " at an artefact that cannot exist.",
6259
+ " • Below production it ran a SECOND vite build without Tailwind, without the",
6260
+ " image pipeline and without the islands entries, then served it through",
6261
+ " `vite preview`. With Tailwind that build aborts. Without it, it succeeds",
6262
+ " and DELETES `dist/server`, the island shells and every pre-rendered page —",
6263
+ " after which `voltro start` cannot boot at all.",
6264
+ "",
6265
+ "Search your Dockerfiles, compose files, CI jobs, Procfiles and runbooks:",
6266
+ "",
6267
+ " grep -rn \"voltro serve\" .",
6268
+ "",
6269
+ "and change the ones that point at a WEB app to `voltro start`. An api entry",
6270
+ "stays as it is. (The shipped Dockerfiles already run the precompiled bundle",
6271
+ "directly — `node .framework/dist-web/startBundle/startEntry.js` for web,",
6272
+ "`node .framework/dist-api/serveBundle/serveEntry.js` for an api — which is",
6273
+ "faster still and needs no CLI in the runtime image.)"
6274
+ ].join("\n")
6275
+ }), Ji = (e) => /\brunAsSystem\b/.test(e.getFullText()), Yi = T({
6276
+ version: "0.58.0",
6277
+ id: "0.58.0/01_system_writes_stamp_no_actor",
6278
+ title: "A `runAsSystem` write stamps no actor id instead of \"system\"",
6279
+ kind: "manual",
6280
+ appliesTo: ({ project: e }) => e.getSourceFiles().some(Ji),
6281
+ note: [
6282
+ "A WRITE MADE BY THE FRAMEWORK ACTING AS ITSELF NOW ATTRIBUTES TO NOBODY.",
6283
+ "",
6284
+ "WHAT CHANGED. Inside `runAsSystem`, the `audit()` mixin stamped",
6285
+ "`createdBy` / `updatedBy` with the subject id — `'system'` by default, or",
6286
+ "the job name you passed. It now stamps `null`, and `ChangeEvent.subjectId`",
6287
+ "carries `null` for the same writes.",
6288
+ "",
6289
+ "WHY. `createdBy` in the audit mixin is `reference(requireActors())` — a",
6290
+ "foreign key into YOUR `actors` table. The framework seeds no row for itself,",
6291
+ "so any id it stamps there is an id nothing points at. On a schema that",
6292
+ "declares that reference the insert fails outright; on one that does not, the",
6293
+ "column holds a value no join resolves. `null` is the answer the write-",
6294
+ "attribution contract already stated for this case: \"a resolved subject with",
6295
+ "no acting user (an anonymous or system call)\".",
6296
+ "",
6297
+ "WHAT TO DO:",
6298
+ "",
6299
+ "1) IF YOU QUERY FOR SYSTEM-WRITTEN ROWS BY THAT VALUE — a report, a filter,",
6300
+ " a cleanup — it stops matching new rows. Rows written before this release",
6301
+ " keep whatever id was stamped then; nothing rewrites them, so a query that",
6302
+ " must cover both reads `createdBy IS NULL OR createdBy = 'system'`.",
6303
+ "",
6304
+ "2) IF YOU NEED TO KNOW WHICH SYSTEM JOB WROTE A ROW, the identity is not",
6305
+ " lost, it is just not in the row stamp: the audit sink row sharing the",
6306
+ " write's `traceId` names it. That is the same place an API key's identity",
6307
+ " has always been recovered from rather than duplicated into the row.",
6308
+ "",
6309
+ "3) IF YOU SEEDED AN `actors` ROW WITH ID 'system' to make the foreign key",
6310
+ " resolve, it is no longer referenced by new writes. Keep it for the",
6311
+ " historical rows above, or drop it once those are gone."
6312
+ ].join("\n")
6313
+ }), Xi = (e) => /\bResolvedWorkflowCallerContext\b/.test(e.getFullText()), Zi = T({
6314
+ version: "0.58.0",
6315
+ id: "0.58.0/02_workflow_caller_context_carries_a_scope",
6316
+ title: "ResolvedWorkflowCallerContext carries the run's resolved row-filter scope",
6317
+ kind: "manual",
6318
+ appliesTo: ({ project: e }) => e.getSourceFiles().some(Xi),
6319
+ note: [
6320
+ "A WORKFLOW RUN'S CALLER CONTEXT NOW CARRIES THE ROW-FILTER SCOPE ITS STEPS",
6321
+ "READ THROUGH.",
6322
+ "",
6323
+ "WHAT CHANGED. `ResolvedWorkflowCallerContext` has a required `rowFilter`,",
6324
+ "resolved for the run's subject at EXECUTION time — beside `subject`, which",
6325
+ "`resolveAuthority` already re-resolves there. Authority is what a three-day-",
6326
+ "old run may DO; this is what it may SEE. Restoring either from the start-",
6327
+ "context row would be a fact frozen at start time and made durable.",
6328
+ "",
6329
+ "WHY IT HAD TO BE REQUIRED. The store refuses a context carrying neither a",
6330
+ "resolved scope nor a system subject. A BOOTSTRAP run is the system and",
6331
+ "passed; a run started BY A USER is not, so in any app that calls",
6332
+ "`setRowFilter` every workflow step threw before its executor ran. Making the",
6333
+ "field optional would leave that reachable from any future boot path.",
6334
+ "",
6335
+ "WHAT TO DO. Only code that CONSTRUCTS one of these needs a change — in",
6336
+ "practice a test double for a context builder. Nothing that merely receives",
6337
+ "the object is affected.",
6338
+ "",
6339
+ " // a double standing in for SYSTEM work (a bootstrap/resumed run)",
6340
+ " { subject: SYSTEM_SUBJECT, traceId: 't', rowFilter: NO_ROW_FILTER }",
6341
+ "",
6342
+ " // a double standing in for a USER's run — resolve it, do not bypass it",
6343
+ " { subject: caller, traceId: 't',",
6344
+ " rowFilter: await Effect.runPromise(resolveRowFilterScopeFor(getRowFilter(), caller)) }",
6345
+ "",
6346
+ "Reaching for `NO_ROW_FILTER` in the second case makes the test assert",
6347
+ "unfiltered reads while claiming to cover a filtered run."
6348
+ ].join("\n")
6349
+ }), Qi = (e) => /\bFleetMergeResult\b|\bfleetObservations\b/.test(e.getFullText()), $i = T({
6350
+ version: "0.58.0",
6351
+ id: "0.58.0/03_fleet_view_reports_departed_replicas",
6352
+ title: "A fleet view separates DEPARTED replicas from stale ones",
6353
+ kind: "manual",
6354
+ appliesTo: ({ project: e }) => e.getSourceFiles().some(Qi),
6355
+ note: (/* @__PURE__ */ "A FLEET VIEW NOW SEPARATES ROWS LEFT BY PROCESSES THAT ARE GONE FROM(ANSWERS THAT ARE MERELY OLD.((WHAT CHANGED. `FleetMergeResult` has a required `departed`, and the(`/_voltro/inspect/...?scope=fleet` envelope carries it beside `stale`.((WHY THEY ARE NOT THE SAME FACT. A STALE row belongs to a replica the(membership roster still lists: it is running and has stopped refreshing,(which is a fault worth reporting and a reason for `complete: false`. A(DEPARTED row belongs to a replica the roster no longer lists: that process(is gone, which is what a scale-down looks like, and it is not a reason for(anything. Counting the second as the first made `complete` unreachable(after any replica had ever exited, and — worse — let a dead process's(framework version report a version SPLIT across a fleet that had none.((They are still REPORTED. Dropping them would leave a reader unable to tell(a fleet that scaled down from a table being written by something nobody is(tracking.((WHAT TO DO. Only code that CONSTRUCTS a fleet result needs a change — in(practice a test double for `fleetObservations`. Add `departed: []`, or a(row when that is what the double is there to exercise:(( { byReplica, missing: [], responded: 2, expected: 2, stale: [],( departed: [], versions: undefined }((If you assert on `complete` or on `stale` in a test that leaves a replica(out of its membership roster, that replica is now DEPARTED and those(assertions move accordingly.".split("(")).join("\n")
6356
+ }), ea = (e) => /\bsubscribeClientErrors\b|\bClientErrorEvent\b/.test(e.getFullText()), ta = T({
6357
+ version: "0.58.0",
6358
+ id: "0.58.0/04_rpc_errors_reach_reporters",
6359
+ title: "rpc failures are published on the client error bus",
6360
+ kind: "manual",
6361
+ appliesTo: ({ project: e }) => e.getSourceFiles().some(ea),
6362
+ note: [
6363
+ "AN RPC CALL THAT FAILS IN THE CLIENT NOW REACHES YOUR ERROR REPORTERS.",
6364
+ "",
6365
+ "WHAT CHANGED. `RpcErrorBus.emit` also publishes on `clientErrorBus`, the",
6366
+ "module-global seam a reporter subscribes to (the Sentry browser",
6367
+ "integration listens there, and so does anything you wired with",
6368
+ "`subscribeClientErrors`). `ClientErrorEvent.source` therefore gained",
6369
+ "`'rpc.mutation' | 'rpc.action' | 'rpc.subscription'`.",
6370
+ "",
6371
+ "WHY. The two buses had different subscriber sets, so a call that failed in",
6372
+ "the client reached cross-cutting POLICY (`useOnRpcError`) and never reached",
6373
+ "REPORTING. A rejected subscription is the expensive one: it leaves",
6374
+ "`data: undefined`, `loading: false` and an empty list — which is what",
6375
+ "\"nothing matched\" looks like too, so there is nothing in the UI to notice.",
6376
+ "",
6377
+ "WHAT TO DO:",
6378
+ "",
6379
+ "1) IF YOU SWITCH EXHAUSTIVELY ON `event.source`, add the three members. A",
6380
+ " `default: assertNever(source)` is where the compiler will point.",
6381
+ "",
6382
+ "2) IF YOU BRIDGED THE TWO BUSES BY HAND — a `useOnRpcError` listener that",
6383
+ " calls `reportClientError` — DELETE it, or you will report twice. Keep any",
6384
+ " throttling you put around it: the framework publishes everything, and a",
6385
+ " ceiling is the reporter's policy, not the bus's.",
6386
+ "",
6387
+ "3) EXPECT MORE EVENTS. A reconnect storm now produces one per failed call.",
6388
+ " If that is too loud for your reporter, gate it in the subscriber — where",
6389
+ " you can see which failures matter to you."
6390
+ ].join("\n")
6391
+ }), na = (e) => /\bsentryPlugin\b|@voltro\/plugin-sentry/.test(e.getFullText()), ra = T({
6392
+ version: "0.58.0",
6393
+ id: "0.58.0/05_sentry_traces_on_by_default",
6394
+ title: "Sentry performance traces are on by default",
6395
+ kind: "manual",
6396
+ appliesTo: ({ project: e }) => e.getSourceFiles().some(na),
6397
+ note: [
6398
+ "SENTRY PERFORMANCE TRACES ARE NOW ON BY DEFAULT ON THE API.",
6399
+ "",
6400
+ "WHAT CHANGED. `sentryPlugin({ traces })` defaulted to `false`; it defaults",
6401
+ "to `true`. With a DSN, the framework's OTel spans are routed to Sentry as",
6402
+ "transactions at `tracesSampleRate` (default 1.0).",
6403
+ "",
6404
+ "WHY. The BROWSER half already defaulted to tracing every page load,",
6405
+ "navigation and rpc call at 1.0. With the api half off, each of those spans",
6406
+ "was emitted and paid for and belonged to a trace with no server side — a",
6407
+ "browser hop hanging off nothing. Both-off would have been coherent;",
6408
+ "both-on is what the two halves were built for, and the docs already told",
6409
+ "you to pass `traces: true` to get it.",
6410
+ "",
6411
+ "WHAT TO DO — one of these, deliberately:",
6412
+ "",
6413
+ "1) NOTHING, if you want end-to-end traces. Watch your Sentry quota on the",
6414
+ " first day: 1.0 means every request becomes a transaction.",
6415
+ "",
6416
+ "2) LOWER THE RATE, which is the usual production answer:",
6417
+ "",
6418
+ " sentryPlugin({ tracesSampleRate: 0.1 })",
6419
+ "",
6420
+ "3) TURN THEM OFF and keep errors + breadcrumbs, which is the whole of the",
6421
+ " plugin for an app with no trace budget:",
6422
+ "",
6423
+ " sentryPlugin({ traces: false })",
6424
+ "",
6425
+ "The boot line names both, so a first boot after upgrading shows what the",
6426
+ "app is about to send: `sentry active traces=true tracesSampleRate=1`.",
6427
+ "",
6428
+ "ALSO: the web entry now initialises browser Sentry whenever",
6429
+ "`@voltro/plugin-sentry` is a dependency of the web app — a `sentry:` block",
6430
+ "in app.config.ts is only for overrides. Without a DSN from either that block",
6431
+ "or `VOLTRO_PUBLIC_SENTRY_DSN`, the init returns immediately, so this changes",
6432
+ "nothing for an app that has not set one."
6433
+ ].join("\n")
6434
+ }), ia = (e) => /\bjwtSecret\b/.test(e.getFullText()), aa = T({
6435
+ version: "0.58.0",
6436
+ id: "0.58.0/06_jwt_secret_may_be_a_function",
6437
+ title: "jwtSecret may be a function, so a build needs no production credential",
6438
+ kind: "manual",
6439
+ appliesTo: ({ project: e }) => e.getSourceFiles().some(ia),
6440
+ note: [
6441
+ "`jwtSecret` NOW ACCEPTS A FUNCTION — AND YOU PROBABLY WANT ONE.",
6442
+ "",
6443
+ "WHY IT EXISTS. `voltro build` imports `app.config.ts`, so everything the",
6444
+ "config CONSTRUCTS runs at build time. That made",
6445
+ "`jwtSecret: process.env.SUPABASE_JWT_SECRET` require a PRODUCTION credential",
6446
+ "to build an image that will not contain one — and a placeholder is not an",
6447
+ "option, since the framework ships no secret values and an",
6448
+ "`ARG SUPABASE_JWT_SECRET` in a Dockerfile is exactly that.",
6449
+ "",
6450
+ "WHAT TO WRITE:",
6451
+ "",
6452
+ " supabaseStrategy({ jwtSecret: () => process.env.SUPABASE_JWT_SECRET })",
6453
+ "",
6454
+ "The build never calls it. The BOOT does, once, through the strategy pass",
6455
+ "`voltro dev` and `voltro serve` share — so an unset variable still refuses",
6456
+ "to start, naming the strategy. Deferring the read does NOT defer the failure",
6457
+ "to the first request: a strategy that cannot verify a token accepts nobody,",
6458
+ "and a process that started anyway serves a login that can never succeed.",
6459
+ "",
6460
+ "WHAT MIGHT STOP COMPILING. Only code that READS the field off a typed",
6461
+ "config:",
6462
+ "",
6463
+ " const cfg: JwtBearerSecretConfig = { … }",
6464
+ " useSecret(cfg.jwtSecret) // string | (() => string | undefined)",
6465
+ "",
6466
+ "Narrow it where you read it (`typeof s === 'function' ? s() : s`), or pass",
6467
+ "the config along instead of the field. A `jwtSecret: 'literal'` you wrote",
6468
+ "yourself is unaffected — its own inferred type never widened."
6469
+ ].join("\n")
6470
+ }), oa = (e) => /\brerank\s*\(/.test(e.getFullText()), sa = [
6471
+ he,
5867
6472
  ge,
5868
- _e,
6473
+ ve,
5869
6474
  ye,
5870
6475
  be,
5871
- xe,
5872
- we,
6476
+ Ce,
6477
+ Te,
5873
6478
  Ee,
5874
6479
  De,
5875
- Oe,
5876
6480
  je,
5877
6481
  Me,
5878
6482
  Fe,
@@ -5880,8 +6484,6 @@ var S = (e, t, n) => {
5880
6484
  ze,
5881
6485
  Be,
5882
6486
  Ue,
5883
- We,
5884
- Ge,
5885
6487
  Ke,
5886
6488
  qe,
5887
6489
  Je,
@@ -5893,66 +6495,68 @@ var S = (e, t, n) => {
5893
6495
  et,
5894
6496
  tt,
5895
6497
  nt,
5896
- st,
5897
6498
  rt,
5898
- ot,
5899
- pt,
5900
- vt,
5901
- yt,
5902
- bt,
6499
+ it,
6500
+ ut,
6501
+ at,
6502
+ lt,
6503
+ gt,
6504
+ xt,
6505
+ St,
5903
6506
  Ct,
5904
- wt,
5905
- kt,
5906
- jt,
5907
- Nt,
5908
- Ft,
5909
- Lt,
5910
- zt,
5911
- Vt,
5912
- Ut,
5913
- Gt,
5914
- qt,
5915
- Yt,
5916
- Zt,
5917
- $t,
5918
- rn,
6507
+ Et,
6508
+ Dt,
6509
+ Mt,
6510
+ Pt,
6511
+ It,
6512
+ Rt,
6513
+ Bt,
6514
+ Ht,
6515
+ Wt,
6516
+ Kt,
6517
+ Jt,
6518
+ Xt,
6519
+ Qt,
6520
+ en,
6521
+ nn,
5919
6522
  sn,
5920
- cn,
5921
6523
  ln,
6524
+ un,
5922
6525
  dn,
5923
6526
  pn,
5924
6527
  hn,
5925
6528
  _n,
5926
6529
  yn,
5927
- Tn,
5928
- Dn,
5929
- kn,
5930
- jn,
5931
- Nn,
6530
+ xn,
6531
+ En,
6532
+ On,
6533
+ An,
6534
+ Mn,
5932
6535
  Pn,
5933
6536
  Fn,
5934
6537
  In,
5935
6538
  Ln,
5936
- zn,
5937
- Un,
5938
- Gn,
5939
- qn,
6539
+ Rn,
6540
+ Bn,
6541
+ Wn,
6542
+ Kn,
5940
6543
  Jn,
5941
6544
  Yn,
5942
6545
  Xn,
5943
- $n,
5944
- or,
6546
+ Zn,
5945
6547
  er,
5946
- ir,
5947
- cr,
5948
- ur,
6548
+ sr,
6549
+ tr,
6550
+ ar,
6551
+ lr,
5949
6552
  dr,
5950
6553
  fr,
5951
- hr,
5952
- _r,
6554
+ pr,
6555
+ gr,
6556
+ vr,
5953
6557
  wr,
5954
6558
  Er,
5955
- xr,
6559
+ Sr,
5956
6560
  kr,
5957
6561
  Mr,
5958
6562
  Pr,
@@ -5966,17 +6570,17 @@ var S = (e, t, n) => {
5966
6570
  ei,
5967
6571
  ti,
5968
6572
  ii,
6573
+ ai,
5969
6574
  oi,
5970
- si,
6575
+ ci,
5971
6576
  li,
5972
6577
  ui,
5973
- di,
6578
+ pi,
5974
6579
  mi,
5975
6580
  hi,
5976
6581
  gi,
5977
6582
  _i,
5978
6583
  vi,
5979
- yi,
5980
6584
  Ci,
5981
6585
  wi,
5982
6586
  Ti,
@@ -5984,431 +6588,59 @@ var S = (e, t, n) => {
5984
6588
  Di,
5985
6589
  Oi,
5986
6590
  ki,
6591
+ Ni,
5987
6592
  Pi,
5988
- w({
5989
- version: "0.52.0",
5990
- id: "0.52.0/01_plugin-versioning-to-row-history",
5991
- title: "@voltro/plugin-versioning → @voltro/plugin-row-history (the name now says what it does)",
5992
- kind: "transform",
5993
- appliesTo: ({ project: e, text: t }) => t.find(K).length > 0 || e.getSourceFiles().some((e) => e.getImportDeclarations().some((e) => e.getModuleSpecifierValue() === K)),
5994
- transform: ({ project: e, helpers: t, log: n }) => {
5995
- let r = t.renameImport(e, {
5996
- module: K,
5997
- from: "versioningPlugin",
5998
- to: "rowHistoryPlugin"
5999
- }) + t.renameImport(e, {
6000
- module: K,
6001
- from: "VersioningPluginOptions",
6002
- to: "RowHistoryPluginOptions"
6003
- });
6004
- n(`${K} → ${q}: repointed ${t.renameModuleSpecifier(e, {
6005
- from: K,
6006
- to: q
6007
- })} import(s)` + (r > 0 ? `, renamed ${r} versioningPlugin call site(s) to rowHistoryPlugin` : "") + `. Replace the dependency in package.json ("${K}" → "${q}") and reinstall — the table (_voltro_row_history) and VOLTRO_ROW_HISTORY_TTL_HOURS already carry the new name, so nothing else changes. If you passed an explicit alias: to the plugin it is kept as-is; only the DEFAULT alias moved from 'versioning' to 'row-history' (inspect endpoint paths + boot log lines).`);
6008
- }
6009
- }),
6010
- w({
6011
- version: "0.52.0",
6012
- id: "0.52.0/02_http-interceptor-fail-closed",
6013
- title: "A throwing `onHttpRequest` interceptor now refuses the request (500) instead of being ignored",
6014
- kind: "manual",
6015
- appliesTo: ({ text: e }) => e.find("onHttpRequest").length > 0,
6016
- note: [
6017
- "The HTTP interceptor chain is fail-closed now: if your `onHttpRequest`",
6018
- "implementation throws, the request is REFUSED with 500 (previously the",
6019
- "throw was swallowed and the request continued — a crashed security gate",
6020
- "silently stopped guarding).",
6021
- "",
6022
- "Decide, per interceptor, what a failure of YOUR dependency should mean:",
6023
- "",
6024
- " · a security gate (auth fence, tenant guard): let the throw propagate —",
6025
- " fail-closed is exactly what you want;",
6026
- " · protection with a dependency (a rate-limit store, a lookup cache):",
6027
- " catch INSIDE the interceptor and degrade loudly, e.g.",
6028
- "",
6029
- " try { decision = await store.consume(…) }",
6030
- " catch (cause) { console.warn(\"… ALLOWED without limit\", cause); return next() }",
6031
- "",
6032
- " (that is what plugin-ratelimit's httpShield does now — a Redis outage",
6033
- " must not become a self-inflicted API outage)."
6034
- ].join("\n")
6035
- }),
6036
- w({
6037
- version: "0.53.0",
6038
- id: "0.53.0/01_conflict-columns-take-the-table",
6039
- title: "`missingConflictColumns` takes the table and classifies absent vs NULL",
6040
- kind: "manual",
6041
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getFullText().includes("missingConflictColumns")),
6042
- note: [
6043
- "`missingConflictColumns(conflictColumns, row)` is now",
6044
- "`missingConflictColumns(table, conflictColumns, row)`, and it returns",
6045
- "`{ column, reason: \"absent\" | \"null\" }` entries instead of bare names.",
6046
- "",
6047
- "Pass the table definition (the same value `missingRequiredColumns` takes).",
6048
- "With it, DB-generated columns (`generatedAs`) are skipped: the database",
6049
- "computes those and rejects an explicit value, so their absence from a",
6050
- "payload is correct — the old signature could not know that and flagged",
6051
- "them. Pass `undefined` only when no definition is available; nothing is",
6052
- "skipped then.",
6053
- "",
6054
- "The `reason` field separates two different bugs the old flat list merged:",
6055
- "`absent` (the payload never set the key) and `null` (the key is present but",
6056
- "NULL — and NULL never matches a unique conflict target, so the write would",
6057
- "always INSERT; use a plain insert for NULL-keyed rows, or make the column",
6058
- "NOT NULL)."
6059
- ].join("\n")
6060
- }),
6061
- w({
6062
- version: "0.53.0",
6063
- id: "0.53.0/02_empty-struct-input-flipped",
6064
- title: "`input: Schema.Struct({})` procedures flipped from accept-everything to reject-everything in 0.37.0",
6065
- kind: "manual",
6066
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => /input:\s*Schema\.Struct\(\{\s*\}\)/.test(e.getFullText())),
6067
- note: [
6068
- "This repository declares procedures with `input: Schema.Struct({})`. Since",
6069
- "0.37.0 (an undeclared input field rejects the call), that shape flipped",
6070
- "HARDER than any other: an empty struct used to accept every payload, and",
6071
- "now rejects every non-empty one. A caller that always sent a field —",
6072
- "a table hook sending `{ limit }`, say — fails on the live subscription and",
6073
- "the SSR seed of the same page at once.",
6074
- "",
6075
- "Check those procedures first — the list takes a minute to produce and",
6076
- "yields the most expensive hits:",
6077
- "",
6078
- " grep -rn 'input: Schema.Struct({})' <api root>",
6079
- "",
6080
- "For each: either the callers genuinely send nothing (fine — the empty",
6081
- "struct is the strongest declaration a procedure can make), or a caller",
6082
- "sends fields the schema must now declare."
6083
- ].join("\n")
6084
- }),
6085
- w({
6086
- version: "0.53.0",
6087
- id: "0.53.0/03_validation-messages-are-ids",
6088
- title: "Form validation messages are ids + catalog now, not effect developer text",
6089
- kind: "manual",
6090
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
6091
- let t = e.getFullText();
6092
- return /Expected .*, actual|is missing/.test(t) || /\.errors\[|useFormBinding\(/.test(t);
6093
- }),
6094
- note: [
6095
- "Form validation messages changed shape:",
6096
- "",
6097
- "- `errors[field]` values are TRANSLATED texts from a message catalog",
6098
- " (built-in en/de, locale from `<html lang>` or the `locale:` option),",
6099
- " no longer effect developer sentences. If you laid regexes over",
6100
- " \"Expected string, actual undefined\" to derive translation keys,",
6101
- " delete them and wire your catalog instead:",
6102
- "",
6103
- " useFormBinding('users.create', { messages: (id, params) => t(id, params) })",
6104
- "",
6105
- "- `errors` keys are the FULL dotted path now (`address.city`,",
6106
- " `entries.0.startsAt`) — a nested issue no longer collapses to its",
6107
- " top-level segment. Code reading `errors.address` for a nested struct",
6108
- " must read the leaf path it renders.",
6109
- "",
6110
- "- `validateFields` also returns `issues` — `{ path, id, params, fallback }`",
6111
- " per failure — for widget kits that resolve messages themselves."
6112
- ].join("\n")
6113
- }),
6114
- w({
6115
- version: "0.53.0",
6116
- id: "0.53.0/04_widget-kind-gained-array",
6117
- title: "WidgetKind gained 'array' and 'reference' — total widget registries need two new entries",
6118
- kind: "manual",
6119
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => /Record<WidgetKind/.test(e.getFullText())),
6120
- note: [
6121
- "This repository types a widget registry as `Record<WidgetKind, …>`.",
6122
- "`WidgetKind` gained `'array'` (arrays of structs are field arrays now)",
6123
- "and `'reference'` (a formField({ reference }) table picker), so the total",
6124
- "map needs two new entries. With no dedicated UI yet, map both to your",
6125
- "custom/render-prop widget — that is what the built-in registry does:",
6126
- "",
6127
- " array: CustomWidget,",
6128
- " reference: CustomWidget,",
6129
- "",
6130
- "Partial registries (`Partial<Record<WidgetKind, …>>` or per-kind lookups)",
6131
- "need nothing."
6132
- ].join("\n")
6133
- }),
6134
- w({
6135
- version: "0.55.0",
6136
- id: "0.55.0/01_target-relations-declare-columns",
6137
- title: "A target's `relations:` names the junction's two columns now, not just the table",
6593
+ Fi,
6594
+ Ii,
6595
+ Li,
6596
+ Ri,
6597
+ zi,
6598
+ Bi,
6599
+ Vi,
6600
+ Hi,
6601
+ Ui,
6602
+ Wi,
6603
+ Gi,
6604
+ Ki,
6605
+ qi,
6606
+ Yi,
6607
+ Zi,
6608
+ $i,
6609
+ ta,
6610
+ ra,
6611
+ aa,
6612
+ T({
6613
+ version: "0.58.0",
6614
+ id: "0.58.0/07_rerank_takes_a_provider_config",
6615
+ title: "rerank reaches the gateway through a ProviderConfig",
6138
6616
  kind: "manual",
6139
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
6140
- let t = e.getFullText();
6141
- return /relations:\s*\{[^}{]*:\s*['"`]/.test(t);
6142
- }),
6617
+ appliesTo: ({ project: e }) => e.getSourceFiles().some(oa),
6143
6618
  note: [
6144
- "A write target declares its many-to-many relations as objects now:",
6145
- "",
6146
- " // before",
6147
- " target: { table: 'employees', op: 'update',",
6148
- " relations: { assignedStores: 'employee_assigned_stores' } }",
6149
- "",
6150
- " // after",
6151
- " target: { table: 'employees', op: 'update',",
6152
- " relations: { assignedStores: {",
6153
- " junction: 'employee_assigned_stores',",
6154
- " anchorColumn: 'employeeId', // the junction reference() pointing at `employees`",
6155
- " targetColumn: 'storeId', // the junction's other reference()",
6156
- " } } }",
6157
- "",
6158
- "Take both column names from the junction table's own `reference()`",
6159
- "columns in your `db/` schema — `anchorColumn` is the one pointing at the",
6160
- "target's table, `targetColumn` is the other one.",
6161
- "",
6162
- "Why: the SAME declaration now drives the optimistic update in the browser,",
6163
- "so a multi-reference field flips as instantly as a scalar one instead of",
6164
- "waiting for the server delta. The browser cannot import your schema, so the",
6165
- "columns have to be stated. They are not taken on trust: before it writes,",
6166
- "the server compares them against the junction's real reference columns and",
6167
- "refuses — naming the correct pair — if they disagree."
6168
- ].join("\n")
6169
- }),
6170
- w({
6171
- version: "0.55.0",
6172
- id: "0.55.0/02_widget-kind-gained-rich-text",
6173
- title: "WidgetKind gained 'rich-text' — total widget registries need one new entry",
6174
- kind: "manual",
6175
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => /Record<WidgetKind/.test(e.getFullText())),
6176
- note: [
6177
- "This repository types a widget registry as `Record<WidgetKind, …>`.",
6178
- "`WidgetKind` gained `'rich-text'`, so the total map needs one new entry.",
6179
- "The framework ships a default you can point at:",
6180
- "",
6181
- " import { RichTextWidget } from '@voltro/web'",
6182
- " // …",
6183
- " 'rich-text': RichTextWidget,",
6184
- "",
6185
- "Partial registries (`Partial<Record<WidgetKind, …>>` or per-kind lookups)",
6186
- "need nothing."
6187
- ].join("\n")
6188
- }),
6189
- w({
6190
- version: "0.56.0",
6191
- id: "0.56.0/01_inspect_answers_are_observations",
6192
- title: "inspect answers are `Observation`s — the payload is `.data`",
6193
- kind: "manual",
6194
- reach: "beyond-source",
6195
- appliesTo: ({ text: e }) => e.find("/_voltro/inspect").length > 0,
6196
- note: [
6197
- "Every 2xx `/_voltro/inspect/*` response is now an envelope:",
6198
- "",
6199
- " { data, scope, origin, completeness, capturedAt }",
6200
- "",
6201
- "What you read before is `.data`. So:",
6202
- "",
6203
- " curl …/_voltro/inspect/subscriptions | jq .resume",
6204
- " curl …/_voltro/inspect/subscriptions | jq .data.resume # now",
6205
- "",
6206
- "The other four fields are the point of the change, not decoration:",
6207
- "",
6208
- " • `scope.kind` — `process` (this replica only), `shared-store` (every",
6209
- " replica would answer the same), `fleet` (assembled), `declaration`",
6210
- " (from the source tree). A `process` answer on a multi-replica",
6211
- " deployment is a SAMPLE, and it used to look identical to a fleet-wide",
6212
- " one.",
6213
- " • `origin.replicaId` / `origin.instanceId` — which process answered, and",
6214
- " which generation of it. Query twice and you can now tell whether you",
6215
- " reached the same one.",
6216
- " • `completeness.fleetSize` — how many replicas exist. ABSENT when it",
6217
- " could not be determined; never `1` as a guess.",
6218
- " • `capturedAt` — the ORIGIN’s clock. Do not compare it with another",
6219
- " replica’s.",
6220
- "",
6221
- "Check anything that reads these endpoints outside your TypeScript sources:",
6222
- "dashboards, `jq` filters in alerts, runbook commands, CI health checks,",
6223
- "scrape configs. A reader that pulls a field straight off the top level now",
6224
- "reads `undefined` — which in a health check is the failure mode worth",
6225
- "looking for first, because `undefined` and \"zero\" render the same in most",
6226
- "alerting rules."
6227
- ].join("\n")
6228
- }),
6229
- w({
6230
- version: "0.56.0",
6231
- id: "0.56.0/02_broadcast_gap_is_one_object",
6232
- title: "attachBroadcastBus's onGap now receives a single BroadcastGap object",
6233
- kind: "manual",
6234
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getFullText().includes("attachBroadcastBus")),
6235
- note: [
6236
- "`attachBroadcastBus({ onGap })` now receives ONE argument — a `BroadcastGap`",
6237
- "— instead of `(origin, missed)`.",
6238
- "",
6239
- " before: onGap: (origin, missed) => refreshAll(`missed ${missed} from ${origin}`)",
6240
- " after: onGap: (gap) => refreshAll(gap.reason)",
6241
- "",
6242
- "`gap.reason` is a ready-made sentence for your log line. `gap.origin` and",
6243
- "`gap.missed` are still there and still EXACT — but they are optional now,",
6244
- "because the bus reports two kinds of hole and only one of them has a peer",
6245
- "to blame:",
6246
- "",
6247
- " • a peer's serial jumped → `{ reason, origin, missed }`",
6248
- " • this replica was not subscribed for a while, or the transport re-dialled",
6249
- " → `{ reason }` alone",
6250
- "",
6251
- "The recovery is the same for both: re-run every live query. If your handler",
6252
- "reads `origin`/`missed`, guard them — a hole with no peer is now reachable.",
6253
- "",
6254
- "Related, and needing no change from you: `attachBroadcastBus` no longer",
6255
- "rejects when the broker is unreachable at attach time. It warns, keeps the",
6256
- "local path working, retries in the background, and reports the time it spent",
6257
- "unsubscribed as a gap. If you wrapped the call in a try/catch to survive a",
6258
- "restarting broker, that catch is now dead code."
6259
- ].join("\n")
6260
- }),
6261
- w({
6262
- version: "0.56.0",
6263
- id: "0.56.0/03_outbox_drain_needs_an_identity",
6264
- title: "drainOutbox now requires claimedBy — the fleet-wide exactly-once gate",
6265
- kind: "manual",
6266
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => e.getFullText().includes("drainOutbox")),
6267
- note: [
6268
- "`drainOutbox({ … })` now requires a `claimedBy: string`.",
6269
- "",
6270
- "Why: the drain used to read every `pending` row and run its handler. Every",
6271
- "replica runs the drain, so every effect was dispatched once PER REPLICA — on",
6272
- "the happy path, every time. Rows are now claimed atomically",
6273
- "(`pending → delivering`) and a racing replica loses the row instead of",
6274
- "duplicating the side effect.",
6275
- "",
6276
- "What to pass: an id for this PROCESS, not for the replica. A replica name",
6277
- "survives a restart (`POD_NAME` on a StatefulSet, `VOLTRO_REPLICA_ID` by",
6278
- "definition) while the work does not, so a pod that died mid-delivery would",
6279
- "look like it still held its own claim:",
6280
- "",
6281
- " import { instanceId } from '@voltro/protocol/identity'",
6282
- " await drainOutbox({ store, handlers, claimedBy: instanceId() })",
6283
- "",
6284
- "Also new and optional: `claimLeaseMs` (default 5 minutes) — how long a claim",
6285
- "stands before another replica may take the row. Set it ABOVE your slowest",
6286
- "handler. A lease shorter than a real delivery hands the same row to a second",
6287
- "replica while the first is still working, which is the duplicate this",
6288
- "mechanism removes, reintroduced by a number.",
6289
- "",
6290
- "If you do NOT call `drainOutbox` yourself, there is nothing to do: `voltro",
6291
- "dev` and `voltro serve` pass the identity for you."
6292
- ].join("\n")
6293
- }),
6294
- w({
6295
- version: "0.56.0",
6296
- id: "0.56.0/04_reaction_dedupe_is_one_claim",
6297
- title: "runReaction's dedupe is a single atomic claim",
6298
- kind: "manual",
6299
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
6300
- let t = e.getFullText();
6301
- return t.includes("runReaction") || t.includes("ReactionRunDeps");
6302
- }),
6303
- note: [
6304
- "`ReactionRunDeps.dedupe` changed from `{ has, add }` to `{ claim }`:",
6305
- "",
6306
- " // before",
6307
- " dedupe: { has: (k) => seen.has(k), add: (k) => { seen.add(k) } }",
6308
- " // after",
6309
- " dedupe: { claim: (k) => (seen.has(k) ? false : (seen.add(k), true)) }",
6310
- "",
6311
- "Why it is not a rename. `has` then `add` is a read followed by a write, and",
6312
- "two callers both read \"not seen\" before either writes — so `dedupeKey`, the",
6313
- "field `defineReaction` refuses to boot without, let the same change act once",
6314
- "per replica. Every single-instance run confirmed it worked, which is why it",
6315
- "survived: the gate was real in exactly the configuration that cannot observe",
6316
- "it.",
6317
- "",
6318
- "The claim is also taken BEFORE the act now, not after. That is what makes it",
6319
- "a gate rather than a report, and it has a cost worth knowing: an act that",
6320
- "THROWS has already consumed its key and is not re-run by a later duplicate.",
6321
- "Durability belongs to the workflow the act starts, not to the trigger.",
6322
- "",
6323
- "If you do NOT call `runReaction` yourself, there is nothing to do. `voltro",
6324
- "dev` and `voltro serve` now pass a durable, fleet-wide claimer",
6325
- "(`_voltro_change_claims`), so a reaction acts once across the whole fleet",
6326
- "instead of once per replica — no code change on your side."
6327
- ].join("\n")
6328
- }),
6329
- w({
6330
- version: "0.56.0",
6331
- id: "0.56.0/05_plugin_bind_context_claims_a_change",
6332
- title: "PluginBindContext requires claimChange; change identity moved to @voltro/database",
6333
- kind: "manual",
6334
- appliesTo: ({ project: e }) => e.getSourceFiles().some((e) => {
6335
- let t = e.getFullText();
6336
- return t.includes("PluginBindContext") || t.includes("changeDigest") || t.includes("OccurrenceCounter") || t.includes("composeChangeKey") || t.includes("parseChangeKey");
6337
- }),
6338
- note: [
6339
- "Two changes, both narrow.",
6340
- "",
6341
- "1. `PluginBindContext` now has a REQUIRED `claimChange(scope, key) =>",
6342
- " Promise<boolean>`. The framework supplies it on both boot paths, so a",
6343
- " plugin only READS it — nothing to do unless you construct a",
6344
- " `PluginBindContext` yourself, which in practice means a unit-test",
6345
- " harness:",
6346
- "",
6347
- " const ctx: PluginBindContext = {",
6348
- " scheduleCoordinated: …,",
6349
- " claimChange: () => Promise.resolve(true), // add this",
6350
- " }",
6351
- "",
6352
- " In a real harness, decide what the case is testing before reaching for",
6353
- " `true`: a stub that always claims asserts the SINGLE-replica behaviour.",
6354
- " To exercise a fleet, share one claim ledger across the stubs — a `Set`",
6355
- " of `${scope}/${key}` is enough.",
6356
- "",
6357
- " Why it is required rather than optional: an absent gate reads exactly",
6358
- " like a gate that passed, and every consumer of this field is counting or",
6359
- " sending something. `plugin-billing` accrued a usage unit per metered row",
6360
- " change on EVERY replica, so a tenant on two pods was invoiced twice.",
6361
- "",
6362
- "2. The change-identity helpers moved from `@voltro/plugin-cdc-out` to",
6363
- " `@voltro/database`, beside `ChangeEvent` itself:",
6364
- "",
6365
- " -import { changeDigest, composeChangeKey, OccurrenceCounter } from '@voltro/plugin-cdc-out'",
6366
- " +import { changeDigest, composeChangeKey, OccurrenceCounter } from '@voltro/database'",
6367
- "",
6368
- " `parseChangeKey` moved with them. `canonicalJson` did NOT become public:",
6369
- " it is internal to the digest, and `@voltro/runtime` already exports a",
6370
- " different function of that name."
6371
- ].join("\n")
6372
- }),
6373
- w({
6374
- version: "0.56.0",
6375
- id: "0.56.0/06_serve_is_api_only",
6376
- title: "`voltro serve` on a WEB app now refuses — use `voltro start`",
6377
- kind: "manual",
6378
- reach: "beyond-source",
6379
- appliesTo: (e) => e.text.scanned === 0 || e.text.find("voltro serve").length > 0,
6380
- note: [
6381
- "`voltro serve` is the production server for an API app. On a WEB app it now",
6382
- "refuses with a one-line message instead of what it used to do.",
6383
- "",
6384
- " web app, production voltro start . (unchanged)",
6385
- " web app, development voltro dev . (unchanged)",
6386
- " api app voltro serve . (unchanged)",
6619
+ "`rerank` NOW TAKES A `ProviderConfig`, LIKE EVERY OTHER PRIMITIVE.",
6387
6620
  "",
6388
- "Nothing changes for an api. For a web app the old behaviour was not a working",
6389
- "alternative that is being taken away:",
6621
+ "WHY. Its `provider` was `'mock' | 'cohere' | 'voyage'` the same two",
6622
+ "vendors the gateway serves, addressed by their OWN keys, with no way to say",
6623
+ "\"through the gateway\". So five reranking models in the catalog",
6624
+ "(`cohere/rerank-v3.5`, `-v4-fast`, `-v4-pro`, `voyage/rerank-2.5`,",
6625
+ "`-2.5-lite`) were unreachable with a gateway key.",
6390
6626
  "",
6391
- " In production it already failed the serve fast path requires an api",
6392
- " serve bundle, which a web build never produces, so it exited 1 pointing",
6393
- " at an artefact that cannot exist.",
6394
- " • Below production it ran a SECOND vite build without Tailwind, without the",
6395
- " image pipeline and without the islands entries, then served it through",
6396
- " `vite preview`. With Tailwind that build aborts. Without it, it succeeds",
6397
- " and DELETES `dist/server`, the island shells and every pre-rendered page —",
6398
- " after which `voltro start` cannot boot at all.",
6627
+ "WHAT TO WRITE, if you want the gateway route:",
6399
6628
  "",
6400
- "Search your Dockerfiles, compose files, CI jobs, Procfiles and runbooks:",
6629
+ " rerank({ query, documents, provider: {",
6630
+ " name: 'gateway', model: 'cohere/rerank-v4-pro',",
6631
+ " }})",
6401
6632
  "",
6402
- " grep -rn \"voltro serve\" .",
6633
+ "The string shorthands stay for the direct-key route, and are deliberately",
6634
+ "NOT folded into `ProviderConfig`: that union is shared with `generateText`,",
6635
+ "and adding `cohere` there would let you name a provider with no language",
6636
+ "model — a config error the type currently catches.",
6403
6637
  "",
6404
- "and change the ones that point at a WEB app to `voltro start`. An api entry",
6405
- "stays as it is. (The shipped Dockerfiles already run the precompiled bundle",
6406
- "directly `node .framework/dist-web/startBundle/startEntry.js` for web,",
6407
- "`node .framework/dist-api/serveBundle/serveEntry.js` for an api — which is",
6408
- "faster still and needs no CLI in the runtime image.)"
6638
+ "WHAT MIGHT STOP COMPILING. Only code that READS the field off a typed",
6639
+ "options object, which is now `string | ProviderConfig`. Narrow it where you",
6640
+ "read it (`typeof p === 'string'`)."
6409
6641
  ].join("\n")
6410
6642
  })
6411
- ], J = /* @__PURE__ */ new Set([
6643
+ ], Y = /* @__PURE__ */ new Set([
6412
6644
  "node_modules",
6413
6645
  ".git",
6414
6646
  "dist",
@@ -6420,42 +6652,42 @@ var S = (e, t, n) => {
6420
6652
  ".turbo",
6421
6653
  ".cache",
6422
6654
  ".output"
6423
- ]), Ii = () => [...[...J].map((e) => `**/${e}/**`), "**/.voltro-*/**"], Y = /(?:from|import)\s*\(?\s*['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\)/g, Li = /\.(?:tsx?|jsx?|mjs|cjs)$/, Ri = (e) => g(e).replace(Li, ""), zi = (e) => {
6655
+ ]), ca = () => [...[...Y].map((e) => `**/${e}/**`), "**/.voltro-*/**"], X = /(?:from|import)\s*\(?\s*['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\)/g, la = /\.(?:tsx?|jsx?|mjs|cjs)$/, ua = (e) => g(e).replace(la, ""), da = (e) => {
6424
6656
  let t = e.split("/").filter((e) => e.length > 0).pop();
6425
6657
  if (t === void 0 || t === "." || t === "..") return;
6426
- let n = t.replace(Li, "");
6658
+ let n = t.replace(la, "");
6427
6659
  return n.length > 0 ? n : void 0;
6428
- }, Bi = (e) => {
6660
+ }, fa = (e) => {
6429
6661
  let t = /* @__PURE__ */ new Map();
6430
6662
  for (let n of e) {
6431
6663
  let e;
6432
6664
  try {
6433
- e = te(n, "utf8");
6665
+ e = ee(n, "utf8");
6434
6666
  } catch {
6435
6667
  continue;
6436
6668
  }
6437
- Y.lastIndex = 0;
6438
- for (let r = Y.exec(e); r !== null; r = Y.exec(e)) {
6439
- let e = zi(r[1] ?? r[2] ?? "");
6669
+ X.lastIndex = 0;
6670
+ for (let r = X.exec(e); r !== null; r = X.exec(e)) {
6671
+ let e = da(r[1] ?? r[2] ?? "");
6440
6672
  if (e === void 0) continue;
6441
6673
  let i = t.get(e);
6442
6674
  i === void 0 ? t.set(e, /* @__PURE__ */ new Set([n])) : i.add(n);
6443
6675
  }
6444
6676
  }
6445
6677
  return t;
6446
- }, Vi = (e, t) => {
6678
+ }, pa = (e, t) => {
6447
6679
  let n = new Set(e);
6448
- for (let r of e) for (let e of [Ri(r), g(_(r))]) {
6680
+ for (let r of e) for (let e of [ua(r), g(_(r))]) {
6449
6681
  let r = t.get(e);
6450
6682
  if (r !== void 0) for (let e of r) n.add(e);
6451
6683
  }
6452
6684
  return n;
6453
- }, Hi = /* @__PURE__ */ ".ts,.tsx,.mts,.cts,.js,.jsx,.mjs,.cjs,.yml,.yaml,.toml,.json,.jsonc,.ini,.cfg,.conf,.sh,.bash,.zsh,.fish,.ps1,.bat,.cmd,.md,.mdx,.rst,.txt,.adoc,Makefile,makefile,GNUmakefile,Justfile,justfile,Dockerfile,Containerfile,Procfile,Jenkinsfile,Rakefile,Taskfile".split(","), Ui = 2097152, Wi = 4e4, Gi = (e) => {
6685
+ }, ma = /* @__PURE__ */ ".ts,.tsx,.mts,.cts,.js,.jsx,.mjs,.cjs,.yml,.yaml,.toml,.json,.jsonc,.ini,.cfg,.conf,.sh,.bash,.zsh,.fish,.ps1,.bat,.cmd,.md,.mdx,.rst,.txt,.adoc,Makefile,makefile,GNUmakefile,Justfile,justfile,Dockerfile,Containerfile,Procfile,Jenkinsfile,Rakefile,Taskfile".split(","), ha = 2097152, ga = 4e4, _a = (e) => {
6454
6686
  let t = e.slice(e.lastIndexOf("/") + 1);
6455
- return Hi.some((e) => e.startsWith(".") ? t.endsWith(e) : t === e);
6456
- }, Ki = (e) => {
6687
+ return ma.some((e) => e.startsWith(".") ? t.endsWith(e) : t === e);
6688
+ }, va = (e) => {
6457
6689
  try {
6458
- let t = re("git", [
6690
+ let t = ne("git", [
6459
6691
  "ls-files",
6460
6692
  "--cached",
6461
6693
  "--others",
@@ -6474,23 +6706,23 @@ var S = (e, t, n) => {
6474
6706
  } catch {
6475
6707
  return;
6476
6708
  }
6477
- }, X = (e, t = Ki) => {
6709
+ }, Z = (e, t = va) => {
6478
6710
  let n = t(e);
6479
- if (n === void 0) return qi;
6711
+ if (n === void 0) return ya;
6480
6712
  let r = /* @__PURE__ */ new Map(), i = [];
6481
6713
  for (let t of n) {
6482
- if (t.split("/").some((e) => J.has(e)) || !Gi(t)) continue;
6483
- if (r.size >= Wi) {
6714
+ if (t.split("/").some((e) => Y.has(e)) || !_a(t)) continue;
6715
+ if (r.size >= ga) {
6484
6716
  i.push(t);
6485
6717
  continue;
6486
6718
  }
6487
6719
  let n = v(e, t);
6488
6720
  try {
6489
- if (ne(n).size > Ui) {
6721
+ if (te(n).size > ha) {
6490
6722
  i.push(t);
6491
6723
  continue;
6492
6724
  }
6493
- let e = te(n, "utf8");
6725
+ let e = ee(n, "utf8");
6494
6726
  if (e.includes("\0")) {
6495
6727
  i.push(t);
6496
6728
  continue;
@@ -6510,47 +6742,47 @@ var S = (e, t, n) => {
6510
6742
  scanned: r.size,
6511
6743
  skipped: i
6512
6744
  };
6513
- }, qi = {
6745
+ }, ya = {
6514
6746
  find: () => [],
6515
6747
  scanned: 0,
6516
6748
  skipped: []
6517
- }, Ji = [
6749
+ }, ba = [
6518
6750
  "**/*.ts",
6519
6751
  "**/*.tsx",
6520
6752
  "!**/node_modules/**",
6521
6753
  "!**/dist/**",
6522
6754
  "!**/.framework/**",
6523
6755
  "!**/*.d.ts"
6524
- ], Z = (e, t, n = Zi) => {
6756
+ ], Q = (e, t, n = Ca) => {
6525
6757
  let r = /* @__PURE__ */ new Set();
6526
- for (let e of t) for (let t of e.scope ?? Ji) r.add(t);
6758
+ for (let e of t) for (let t of e.scope ?? ba) r.add(t);
6527
6759
  let i = n(e);
6528
6760
  if (i !== void 0) {
6529
- let t = [...r].filter((e) => !e.startsWith("!")).map((e) => Xi(e)), n = [...r].filter((e) => e.startsWith("!")).map((e) => Xi(e.slice(1))), a = [...J], o = [];
6761
+ let t = [...r].filter((e) => !e.startsWith("!")).map((e) => Sa(e)), n = [...r].filter((e) => e.startsWith("!")).map((e) => Sa(e.slice(1))), a = [...Y], o = [];
6530
6762
  for (let r of i) {
6531
6763
  if (a.some((e) => r === e || r.startsWith(`${e}/`) || r.includes(`/${e}/`)) || !t.some((e) => e(r)) || n.some((e) => e(r))) continue;
6532
6764
  let i = v(e, r);
6533
- y(i) && o.push(i);
6765
+ b(i) && o.push(i);
6534
6766
  }
6535
- return Yi(o.length, e), o;
6767
+ return xa(o.length, e), o;
6536
6768
  }
6537
- let a = oe([...r], {
6769
+ let a = ae([...r], {
6538
6770
  cwd: e,
6539
6771
  absolute: !0,
6540
6772
  expandDirectories: !1,
6541
6773
  followSymbolicLinks: !1,
6542
- ignore: [...Ii()]
6774
+ ignore: [...ca()]
6543
6775
  });
6544
- return Yi(a.length, e), a;
6545
- }, Yi = (e, t) => {
6546
- let n = Qi();
6776
+ return xa(a.length, e), a;
6777
+ }, xa = (e, t) => {
6778
+ let n = wa();
6547
6779
  if (!(e <= n)) throw Error(`voltro update: the codemod scan matched ${e} files under ${t}, past the ${n} ceiling.\n Every matched file is parsed into one ts-morph project, so the heap needed grows with\n this number — past the ceiling it runs out of memory, and V8 aborts without naming a\n limit, which is why this stops here instead. Three ways out, in order:\n • put non-source directories in .gitignore (inside a git repo the scan then never\n sees them at all — a build cache, a data dump, a virtualenv),\n • run \`voltro update\` from the app directory rather than the workspace root, or\n • take one codemod at a time: \`voltro update --dry-run\` lists the ids, then\n \`voltro update --codemods-only --only <id>\` runs a single one.\n If every one of those files really is source this pass must rewrite, raise the ceiling\n with VOLTRO_CODEMOD_MAX_FILES and give node room: NODE_OPTIONS=--max-old-space-size=8192.`);
6548
- }, Xi = (e) => {
6780
+ }, Sa = (e) => {
6549
6781
  let t = e.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\u0000/g, "(?:.*/)?").replace(/\\\{([^}]*)\\\}/g, (e, t) => `(?:${t.split(",").join("|")})`);
6550
6782
  return (e) => RegExp(`^${t}$`).test(e);
6551
- }, Zi = (e) => {
6783
+ }, Ca = (e) => {
6552
6784
  try {
6553
- let t = re("git", [
6785
+ let t = ne("git", [
6554
6786
  "ls-files",
6555
6787
  "--cached",
6556
6788
  "--others",
@@ -6569,17 +6801,17 @@ var S = (e, t, n) => {
6569
6801
  } catch {
6570
6802
  return;
6571
6803
  }
6572
- }, Qi = () => {
6804
+ }, wa = () => {
6573
6805
  let e = Number(process.env.VOLTRO_CODEMOD_MAX_FILES);
6574
6806
  return Number.isInteger(e) && e > 0 ? e : 2e4;
6575
- }, Q = (e, t) => {
6807
+ }, $ = (e, t) => {
6576
6808
  let n = e.split(".").map(Number), r = t.split(".").map(Number);
6577
6809
  for (let e = 0; e < 3; e++) {
6578
6810
  let t = (n[e] ?? 0) - (r[e] ?? 0);
6579
6811
  if (t !== 0) return t;
6580
6812
  }
6581
6813
  return 0;
6582
- }, $i = (e, t, n = Fi) => n.filter((n) => Q(e, n.version) < 0 && Q(n.version, t) <= 0).slice().sort(he), $ = (e, t, n = () => {}, r = qi) => {
6814
+ }, Ta = (e, t, n = sa) => n.filter((n) => $(e, n.version) < 0 && $(n.version, t) <= 0).slice().sort(me), Ea = (e, t, n = () => {}, r = ya) => {
6583
6815
  let i = [], a = [], o = [];
6584
6816
  for (let s of t) {
6585
6817
  let t = !s.appliesTo || s.appliesTo({
@@ -6602,7 +6834,7 @@ var S = (e, t, n) => {
6602
6834
  let l = new Map(e.getSourceFiles().map((e) => [e.getFilePath(), e.getFullText()]));
6603
6835
  s.transform?.({
6604
6836
  project: e,
6605
- helpers: pe,
6837
+ helpers: fe,
6606
6838
  log: n
6607
6839
  });
6608
6840
  let u = new Map(e.getSourceFiles().map((e) => [e.getFilePath(), e.getFullText()])), d = /* @__PURE__ */ new Map();
@@ -6630,8 +6862,8 @@ var S = (e, t, n) => {
6630
6862
  manual: a,
6631
6863
  skipped: o
6632
6864
  };
6633
- }, ea = async (e) => {
6634
- let t = e.log ?? (() => {}), n = $i(e.from, e.to, e.codemods ?? Fi), r = e.only === void 0 || e.only.length === 0 ? n : (() => {
6865
+ }, Da = async (e) => {
6866
+ let t = e.log ?? (() => {}), n = Ta(e.from, e.to, e.codemods ?? sa), r = e.only === void 0 || e.only.length === 0 ? n : (() => {
6635
6867
  let t = n.filter((t) => e.only.includes(t.id)), r = e.only.filter((e) => !n.some((t) => t.id === e));
6636
6868
  if (r.length > 0) throw Error(`voltro update --only: no codemod in ${e.from} → ${e.to} has id ${r.map((e) => `"${e}"`).join(", ")}.\n Available for this jump:\n${n.map((e) => ` ${e.id}`).join("\n")}`);
6637
6869
  return t;
@@ -6643,39 +6875,39 @@ var S = (e, t, n) => {
6643
6875
  };
6644
6876
  let i = f(e.root);
6645
6877
  i !== void 0 && t(`path aliases: ${Object.keys(i.paths).join(", ")}`);
6646
- let a = () => new ie({
6878
+ let a = () => new re({
6647
6879
  skipAddingFilesFromTsConfig: !0,
6648
- manipulationSettings: { quoteKind: ae.Single },
6880
+ manipulationSettings: { quoteKind: ie.Single },
6649
6881
  ...i === void 0 ? {} : { compilerOptions: {
6650
6882
  baseUrl: i.baseUrl,
6651
6883
  paths: i.paths
6652
6884
  } }
6653
- }), o, s = Z(e.root, r), c = r.filter((e) => e.needsImporters === !0 && e.scope !== void 0), l = [], u = [], d = [], p, m = {
6654
- find: (t) => (p ??= X(e.root)).find(t),
6885
+ }), o, s = Q(e.root, r), c = r.filter((e) => e.needsImporters === !0 && e.scope !== void 0), l = [], u = [], d = [], p, m = {
6886
+ find: (t) => (p ??= Z(e.root)).find(t),
6655
6887
  get scanned() {
6656
- return (p ??= X(e.root)).scanned;
6888
+ return (p ??= Z(e.root)).scanned;
6657
6889
  },
6658
6890
  get skipped() {
6659
- return (p ??= X(e.root)).skipped;
6891
+ return (p ??= Z(e.root)).skipped;
6660
6892
  }
6661
- }, h = c.length > 0 ? Bi(Z(e.root, [{
6893
+ }, h = c.length > 0 ? fa(Q(e.root, [{
6662
6894
  ...c[0],
6663
- scope: Ji
6895
+ scope: ba
6664
6896
  }])) : void 0;
6665
6897
  for (let n of r) {
6666
6898
  if (c.includes(n)) {
6667
- let r = a(), i = Z(e.root, [n]);
6668
- for (let e of Vi(i, h)) y(e) && r.addSourceFileAtPath(e);
6669
- let s = $(r, [n], t, m);
6899
+ let r = a(), i = Q(e.root, [n]);
6900
+ for (let e of pa(i, h)) b(e) && r.addSourceFileAtPath(e);
6901
+ let s = Ea(r, [n], t, m);
6670
6902
  e.dryRun || await r.save(), l.push(...s.applied), u.push(...s.manual), d.push(...s.skipped), o = void 0;
6671
6903
  continue;
6672
6904
  }
6673
6905
  o ??= (() => {
6674
6906
  let e = a();
6675
- for (let t of s) y(t) && e.addSourceFileAtPath(t);
6907
+ for (let t of s) b(t) && e.addSourceFileAtPath(t);
6676
6908
  return e;
6677
6909
  })();
6678
- let r = $(o, [n], t, m);
6910
+ let r = Ea(o, [n], t, m);
6679
6911
  e.dryRun || await o.save(), l.push(...r.applied), u.push(...r.manual), d.push(...r.skipped);
6680
6912
  }
6681
6913
  return {
@@ -6685,4 +6917,4 @@ var S = (e, t, n) => {
6685
6917
  };
6686
6918
  };
6687
6919
  //#endregion
6688
- export { w as i, ea as n, J as r, Q as t };
6920
+ export { T as i, Da as n, Y as r, $ as t };