@voltro/cli 0.30.2 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/dist/apiBuild-BOqL1dlg.js +2 -0
  3. package/dist/{apiBuild-BUS1RfPi.js → apiBuild-CLPt-pX6.js} +2 -2
  4. package/dist/appGraph-CvQCte0z.js +2 -0
  5. package/dist/appGraph-gQ_6GkQQ.js +110 -0
  6. package/dist/bin.js +3 -3
  7. package/dist/checkCommand-ClRvH_SN.js +2 -0
  8. package/dist/{checkCommand-D_wdOFhw.js → checkCommand-D7r6_pk7.js} +309 -453
  9. package/dist/{commands-BgGAUcku.js → commands-CNVVv03h.js} +2566 -2505
  10. package/dist/{dbCommand-CbPRCD1x.js → dbCommand-BH8GAYuf.js} +22 -2
  11. package/dist/dbCommand-BwQ_Jl98.js +2 -0
  12. package/dist/{dev-tPcOmjPJ.js → dev-DOZVMVfi.js} +1 -1
  13. package/dist/{dev-GAOIot9G.js → dev-DdQEn2Ia.js} +1774 -1771
  14. package/dist/index.js +1 -1
  15. package/dist/{inspectMetrics-BM1vrsga.js → inspectMetrics-BeEAnjqw.js} +727 -480
  16. package/dist/{sdkgen-BV4fFucu.js → sdkgen-v9WV8zRi.js} +1 -1
  17. package/dist/{serveCommand-ClqXCX1y.js → serveCommand-KsKXXWn4.js} +289 -289
  18. package/dist/serveEntry.js +2 -2
  19. package/dist/{start-CTnNvKlk.js → start-CI2QdKy9.js} +204 -199
  20. package/dist/startEntry.js +2 -2
  21. package/package.json +17 -17
  22. package/templates/AGENTS.md +43 -43
  23. package/templates/agent-docs/_index.md +43 -43
  24. package/templates/agent-docs/_manifest.json +84 -42
  25. package/templates/agent-docs/database/migrations.md +14 -1
  26. package/templates/agent-docs/internationalization.md +29 -0
  27. package/templates/agent-docs/plugins/ai-flows.md +148 -0
  28. package/templates/agent-docs/plugins/analytics-postgres.md +90 -0
  29. package/templates/agent-docs/plugins/atlassian.md +378 -0
  30. package/templates/agent-docs/plugins/audit.md +324 -0
  31. package/templates/agent-docs/plugins/auth-auth0.md +92 -0
  32. package/templates/agent-docs/plugins/auth-clerk.md +90 -0
  33. package/templates/agent-docs/plugins/auth-kinde.md +88 -0
  34. package/templates/agent-docs/plugins/auth-oidc.md +95 -0
  35. package/templates/agent-docs/plugins/auth-supabase.md +103 -0
  36. package/templates/agent-docs/plugins/auth-workos.md +105 -0
  37. package/templates/agent-docs/plugins/auth.md +227 -0
  38. package/templates/agent-docs/plugins/billing.md +324 -0
  39. package/templates/agent-docs/plugins/broadcast.md +144 -0
  40. package/templates/agent-docs/plugins/cdc-out.md +116 -0
  41. package/templates/agent-docs/plugins/clickhouse.md +91 -0
  42. package/templates/agent-docs/plugins/datadog.md +105 -0
  43. package/templates/agent-docs/plugins/deactivation.md +114 -0
  44. package/templates/agent-docs/plugins/duckdb.md +82 -0
  45. package/templates/agent-docs/plugins/flags.md +146 -0
  46. package/templates/agent-docs/plugins/governance.md +143 -0
  47. package/templates/agent-docs/plugins/licensing.md +74 -0
  48. package/templates/agent-docs/plugins/logship.md +56 -0
  49. package/templates/agent-docs/plugins/mail.md +334 -0
  50. package/templates/agent-docs/plugins/moderation.md +65 -0
  51. package/templates/agent-docs/plugins/multitenancy.md +99 -0
  52. package/templates/agent-docs/plugins/notifications.md +252 -0
  53. package/templates/agent-docs/plugins/openapi.md +94 -0
  54. package/templates/agent-docs/plugins/postgis.md +140 -0
  55. package/templates/agent-docs/plugins/posthog.md +91 -0
  56. package/templates/agent-docs/plugins/presence.md +115 -0
  57. package/templates/agent-docs/plugins/prometheus.md +124 -0
  58. package/templates/agent-docs/plugins/ratelimit.md +274 -0
  59. package/templates/agent-docs/plugins/rbac.md +270 -0
  60. package/templates/agent-docs/plugins/scim.md +63 -0
  61. package/templates/agent-docs/plugins/search.md +116 -0
  62. package/templates/agent-docs/plugins/sentry.md +192 -0
  63. package/templates/agent-docs/plugins/soft-delete.md +72 -0
  64. package/templates/agent-docs/plugins/sso-saml.md +132 -0
  65. package/templates/agent-docs/plugins/storage.md +709 -0
  66. package/templates/agent-docs/plugins/tinybird.md +92 -0
  67. package/templates/agent-docs/plugins/versioning.md +226 -0
  68. package/templates/agent-docs/plugins/webhooks.md +585 -0
  69. package/templates/agent-docs/security.md +12 -1
  70. package/templates/agent-docs/whats-new.md +90 -23
  71. package/templates/apps/api-ai/package.json +7 -7
  72. package/templates/apps/api-auth/package.json +8 -8
  73. package/templates/apps/api-backend/package.json +7 -7
  74. package/templates/apps/api-backend-deactivation/package.json +7 -7
  75. package/templates/apps/api-backend-mail/package.json +8 -8
  76. package/templates/apps/api-backend-mariadb/package.json +9 -9
  77. package/templates/apps/api-backend-sqlite/package.json +8 -8
  78. package/templates/apps/api-backend-storage/package.json +8 -8
  79. package/templates/apps/api-cms/package.json +10 -10
  80. package/templates/apps/api-collab/package.json +8 -8
  81. package/templates/apps/api-data-advanced/package.json +8 -8
  82. package/templates/apps/api-durable/package.json +8 -8
  83. package/templates/apps/api-feature-flags/package.json +9 -9
  84. package/templates/apps/api-governance/package.json +8 -8
  85. package/templates/apps/api-kv/package.json +8 -8
  86. package/templates/apps/api-moderation/package.json +8 -8
  87. package/templates/apps/api-observability/package.json +8 -8
  88. package/templates/apps/api-ratelimit/package.json +8 -8
  89. package/templates/apps/api-rbac/package.json +8 -8
  90. package/templates/apps/api-rest/package.json +7 -7
  91. package/templates/apps/api-saas/package.json +11 -11
  92. package/templates/apps/api-saas-starter/package.json +10 -10
  93. package/templates/apps/api-search/package.json +8 -8
  94. package/templates/apps/api-status/package.json +8 -8
  95. package/templates/apps/api-versioning/package.json +8 -8
  96. package/templates/apps/api-webhooks/package.json +9 -9
  97. package/templates/apps/changelog/package.json +6 -6
  98. package/templates/apps/edge-functions/package.json +2 -2
  99. package/templates/apps/frontend-admin/package.json +8 -8
  100. package/templates/apps/frontend-app/package.json +8 -8
  101. package/templates/apps/frontend-auth/package.json +8 -8
  102. package/templates/apps/frontend-blank/package.json +7 -7
  103. package/templates/apps/frontend-cms/package.json +9 -9
  104. package/templates/apps/frontend-collab/package.json +9 -9
  105. package/templates/apps/frontend-contact/package.json +7 -7
  106. package/templates/apps/frontend-dashboard/package.json +7 -7
  107. package/templates/apps/frontend-docs/package.json +7 -7
  108. package/templates/apps/frontend-i18n/package.json +6 -6
  109. package/templates/apps/frontend-landing/package.json +7 -7
  110. package/templates/apps/frontend-portal/package.json +8 -8
  111. package/templates/apps/frontend-saas/package.json +8 -8
  112. package/templates/apps/frontend-spa/package.json +7 -7
  113. package/templates/apps/frontend-ssr/package.json +7 -7
  114. package/templates/apps/frontend-ssr-api/package.json +8 -8
  115. package/templates/apps/frontend-static-blog/package.json +6 -6
  116. package/templates/apps/frontend-status/package.json +8 -8
  117. package/templates/apps/mobile-app/package.json +2 -2
  118. package/dist/apiBuild-DMlxvL8h.js +0 -2
  119. package/dist/checkCommand-CARZ36Tr.js +0 -2
  120. package/dist/dbCommand-Ht47LrV-.js +0 -2
@@ -1,30 +1,31 @@
1
- import { f as e, gt as t, n, o as r, z as i } from "./inspectMetrics-BM1vrsga.js";
2
- import { Ut as a, Wt as o } from "./dev-GAOIot9G.js";
3
- import { C as s, E as c, O as l, T as u, d, h as f, i as p, l as m, m as h, r as g, u as _ } from "./fileConventions-Cof68_BL.js";
4
- import { r as v, u as y } from "./apiBuild-BUS1RfPi.js";
5
- import { dirname as b, isAbsolute as ee, join as x, relative as S, resolve as C, sep as w } from "node:path";
6
- import { routeDiagnosticsToStderr as te } from "@voltro/logger";
7
- import { existsSync as T, readFileSync as E, readdirSync as D, statSync as O, writeFileSync as ne } from "node:fs";
1
+ import { Ct as e, et as t, f as n, n as r, nt as i, o as a, rt as o, tt as s, z as c } from "./inspectMetrics-BeEAnjqw.js";
2
+ import { Ht as l, Ut as u } from "./dev-DdQEn2Ia.js";
3
+ import { C as d, E as f, O as p, T as m, d as h, h as g, i as _, l as v, m as y, r as ee, u as te } from "./fileConventions-Cof68_BL.js";
4
+ import { r as ne, u as re } from "./apiBuild-CLPt-pX6.js";
5
+ import { r as ie } from "./appGraph-gQ_6GkQQ.js";
6
+ import { dirname as b, isAbsolute as ae, join as x, relative as S, resolve as C, sep as w } from "node:path";
7
+ import { routeDiagnosticsToStderr as oe } from "@voltro/logger";
8
+ import { existsSync as T, readFileSync as E, readdirSync as D, statSync as O, writeFileSync as se } from "node:fs";
8
9
  import { Node as k, Project as A, SyntaxKind as j } from "ts-morph";
9
10
  //#region src/cliOutput.ts
10
- var re = () => {
11
- te();
11
+ var ce = () => {
12
+ oe();
12
13
  }, M = (e, t = {}) => {
13
14
  console.log(JSON.stringify(e, null, t.pretty === !1 ? 0 : 2));
14
- }, ie = (e) => {
15
+ }, le = (e) => {
15
16
  for (let t = 0; t < e.length; t++) {
16
17
  let n = e[t];
17
18
  if (n === "--json" || n === "--format=json" || n === "--format" && e[t + 1] === "json") return !0;
18
19
  }
19
20
  return !1;
20
- }, ae = (e, t, n = !1) => {
21
+ }, ue = (e, t, n = !1) => {
21
22
  let r = { ...e ?? {} }, i = t ?? process.env.VOLTRO_INSPECT_TOKEN;
22
23
  if (i !== void 0 && i !== "" && (r.authorization = `Bearer ${i}`), n) {
23
24
  let e = process.env.VOLTRO_INSPECT_WRITE_TOKEN;
24
25
  e !== void 0 && e !== "" && (r["x-voltro-inspect-write"] = e);
25
26
  }
26
27
  return r;
27
- }, oe = /* @__PURE__ */ new Set([
28
+ }, de = /* @__PURE__ */ new Set([
28
29
  "POST",
29
30
  "PUT",
30
31
  "PATCH",
@@ -34,7 +35,7 @@ var re = () => {
34
35
  try {
35
36
  let t = await fetch(r, {
36
37
  method: i,
37
- headers: ae(a ? { "content-type": "application/json" } : void 0, e.inspectToken, oe.has(i)),
38
+ headers: ue(a ? { "content-type": "application/json" } : void 0, e.inspectToken, de.has(i)),
38
39
  ...a ? { body: JSON.stringify(n.body) } : {}
39
40
  }), o = await t.text(), s;
40
41
  if (o.length > 0) try {
@@ -43,7 +44,7 @@ var re = () => {
43
44
  s = o;
44
45
  }
45
46
  if (!t.ok) {
46
- let n = s, r = typeof n == "object" && n ? String(n.message ?? n.reason ?? n.error ?? `HTTP ${t.status}`) : `HTTP ${t.status}`, a = oe.has(i) && (process.env.VOLTRO_INSPECT_WRITE_TOKEN ?? "") === "" && /write/i.test(r);
47
+ let n = s, r = typeof n == "object" && n ? String(n.message ?? n.reason ?? n.error ?? `HTTP ${t.status}`) : `HTTP ${t.status}`, a = de.has(i) && (process.env.VOLTRO_INSPECT_WRITE_TOKEN ?? "") === "" && /write/i.test(r);
47
48
  return {
48
49
  kind: "err",
49
50
  name: e.name,
@@ -63,7 +64,7 @@ var re = () => {
63
64
  error: t.message || String(t)
64
65
  };
65
66
  }
66
- }, se = (e, t = process.env) => {
67
+ }, P = (e, t = process.env) => {
67
68
  let n = [], r, i;
68
69
  for (let t = 0; t < e.length; t++) {
69
70
  let a = e[t];
@@ -108,7 +109,7 @@ var re = () => {
108
109
  },
109
110
  rest: n
110
111
  };
111
- }, ce = (e) => ({
112
+ }, fe = (e) => ({
112
113
  id: `remote:${e.url}`,
113
114
  name: e.name,
114
115
  kind: "api",
@@ -119,7 +120,7 @@ var re = () => {
119
120
  status: "live",
120
121
  logFile: "",
121
122
  ...e.token === void 0 ? {} : { inspectToken: e.token }
122
- }), le = async (e, n) => n === void 0 ? (await t()).apps.filter((t) => t.kind === "api" && t.status === "live" && t.url.length > 0 && (e === void 0 || t.name === e)) : [ce(n)], ue = (e) => {
123
+ }), pe = async (t, n) => n === void 0 ? (await e()).apps.filter((e) => e.kind === "api" && e.status === "live" && e.url.length > 0 && (t === void 0 || e.name === t)) : [fe(n)], me = (e) => {
123
124
  let t = "pretty", n = [];
124
125
  for (let r = 0; r < e.length; r++) {
125
126
  let i = e[r];
@@ -143,8 +144,8 @@ var re = () => {
143
144
  format: t,
144
145
  rest: n
145
146
  };
146
- }, de = (e, t = process.env) => {
147
- let { target: n, rest: r, error: i } = se(e, t), a, o = [];
147
+ }, he = (e, t = process.env) => {
148
+ let { target: n, rest: r, error: i } = P(e, t), a, o = [];
148
149
  for (let e = 0; e < r.length; e++) {
149
150
  let t = r[e];
150
151
  if (t === "--process") {
@@ -163,112 +164,6 @@ var re = () => {
163
164
  rest: o,
164
165
  ...i === void 0 ? {} : { error: i }
165
166
  };
166
- }, P = (e) => e.source === void 0 ? [] : typeof e.source == "string" ? [e.source] : e.source, fe = (e) => ({
167
- tables: new Set(e.tables),
168
- procedures: new Map(e.procedures.map((e) => [e.tag, e])),
169
- scopes: new Set(e.scopes ?? []),
170
- input: e
171
- }), pe = (e) => {
172
- let t = [], n = /* @__PURE__ */ new Set();
173
- for (let r of e.input.procedures) {
174
- for (let i of P(r)) n.add(i), e.tables.has(i) || t.push({
175
- rule: "reference/dangling-source",
176
- node: `${r.kind}(${r.tag})`,
177
- ...r.file === void 0 ? {} : { file: r.file },
178
- breaks: [`reads table '${i}' which does not exist`],
179
- fix: `declare a '${i}.entity.ts' table or fix the query's source`,
180
- severity: "error"
181
- });
182
- for (let i of r.targets ?? []) n.add(i.table), e.tables.has(i.table) || t.push({
183
- rule: "reference/dangling-target",
184
- node: `${r.kind}(${r.tag})`,
185
- ...r.file === void 0 ? {} : { file: r.file },
186
- breaks: [`targets table '${i.table}' (op '${i.op}') which does not exist`],
187
- fix: `declare a '${i.table}.entity.ts' table or fix the mutation's target`,
188
- severity: "error"
189
- });
190
- for (let n of r.requiresScopes ?? []) e.scopes.size > 0 && !e.scopes.has(n) && t.push({
191
- rule: "rbac/unknown-scope",
192
- node: `${r.kind}(${r.tag})`,
193
- ...r.file === void 0 ? {} : { file: r.file },
194
- breaks: [`requires scope '${n}' which is not declared`],
195
- fix: `declare the scope '${n}' or fix the requireScope(...) call`,
196
- severity: "error"
197
- });
198
- r.kind === "mutation" && r.hasGuards === !1 && t.push({
199
- rule: "rbac/unguarded-mutation",
200
- node: `${r.kind}(${r.tag})`,
201
- ...r.file === void 0 ? {} : { file: r.file },
202
- breaks: [`'${r.tag}' changes state but declares no guard — any caller who can reach the rpc surface can invoke it`],
203
- fix: "add a guard (e.g. `.guard(requireScope('…'))`), or confirm it is intentionally public",
204
- severity: "warn"
205
- });
206
- }
207
- for (let n of e.input.routes ?? []) for (let r of n.binds) e.procedures.has(r) || t.push({
208
- rule: "binding/unknown-tag",
209
- node: `route(${n.pattern})`,
210
- ...n.file === void 0 ? {} : { file: n.file },
211
- breaks: [`binds rpc tag '${r}' which does not exist`],
212
- fix: `bind an existing tag, or restore '${r}' (it may have been renamed/removed)`,
213
- severity: "error"
214
- });
215
- for (let t of e.input.procedures) for (let e of t.targets ?? []) n.add(e.table);
216
- let r = new Set(e.input.frameworkOwnedTables ?? []), i = e.input.procedures.filter((e) => e.kind === "action" && P(e).length === 0 && (e.targets ?? []).length === 0).length;
217
- for (let a of e.tables) r.has(a) || n.has(a) || t.push({
218
- rule: "orphan/unread-table",
219
- node: `table(${a})`,
220
- breaks: i > 0 ? [`no query reads and no mutation writes '${a}'`, `${i} action(s) declare neither source nor target, so one of them may still use it`] : [`no query reads and no mutation writes '${a}'`],
221
- fix: i > 0 ? "declare `source`/`target` on your actions so this can be answered, then re-run" : `add a query/mutation for '${a}', or remove the table if it's dead`,
222
- severity: "warn"
223
- });
224
- if (e.scopes.size > 0) {
225
- let n = /* @__PURE__ */ new Set();
226
- for (let t of e.input.procedures) for (let e of t.requiresScopes ?? []) n.add(e);
227
- for (let r of [...e.scopes].sort()) r === "admin:full" || n.has(r) || t.push({
228
- rule: "rbac/unenforced-scope",
229
- node: `scope(${r})`,
230
- breaks: [`'${r}' is granted by a role but NO query/mutation/action/stream guards on it`, "a scope nobody checks grants nothing and withholds nothing — the role map reads stricter than the app is"],
231
- fix: `add \`.guard(requireScope('${r}'))\` to the handlers it is meant to protect — or, if a plugin route, a REST route, or a UI-only affordance enforces it, say so where the role is declared`,
232
- severity: "warn"
233
- });
234
- }
235
- return t;
236
- }, me = (e, t) => {
237
- let n = [];
238
- if (t.kind === "removeTable") for (let r of e.input.procedures) {
239
- let e = P(r).includes(t.name), i = (r.targets ?? []).some((e) => e.table === t.name);
240
- (e || i) && n.push({
241
- rule: "blast/table-removed",
242
- node: `${r.kind}(${r.tag})`,
243
- ...r.file === void 0 ? {} : { file: r.file },
244
- breaks: [`${e ? "reads" : "writes"} table '${t.name}' — would break if removed`],
245
- fix: `migrate or remove ${r.tag} before dropping '${t.name}'`,
246
- severity: "error"
247
- });
248
- }
249
- else if (t.kind === "removeProcedure") for (let r of e.input.routes ?? []) r.binds.includes(t.tag) && n.push({
250
- rule: "blast/procedure-removed",
251
- node: `route(${r.pattern})`,
252
- ...r.file === void 0 ? {} : { file: r.file },
253
- breaks: [`binds '${t.tag}' — would break if removed`],
254
- fix: `rebind ${r.pattern} to another tag before removing '${t.tag}'`,
255
- severity: "error"
256
- });
257
- else for (let r of e.input.procedures) (r.requiresScopes ?? []).includes(t.name) && n.push({
258
- rule: "blast/scope-removed",
259
- node: `${r.kind}(${r.tag})`,
260
- ...r.file === void 0 ? {} : { file: r.file },
261
- breaks: [`requires scope '${t.name}' — would become unguarded/dangling if removed`],
262
- fix: `remove the requireScope('${t.name}') call or keep the scope`,
263
- severity: "error"
264
- });
265
- return n;
266
- }, he = (e, t) => {
267
- let n = fe(e), r = [...pe(n), ...t === void 0 ? [] : me(n, t)];
268
- return {
269
- ok: !r.some((e) => e.severity === "error"),
270
- diagnostics: r
271
- };
272
167
  }, ge = (e) => {
273
168
  let t = "", n = !1;
274
169
  for (let r = 0; r < e.length; r++) {
@@ -343,7 +238,7 @@ var re = () => {
343
238
  }
344
239
  } };
345
240
  }, ve = (e, t = "tsconfig.json") => {
346
- let n = ee(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
241
+ let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
347
242
  if (i === void 0) return;
348
243
  let a = {};
349
244
  for (let [e, t] of Object.entries(i)) Array.isArray(t) && (a[e] = t.filter((e) => typeof e == "string"));
@@ -352,7 +247,7 @@ var re = () => {
352
247
  paths: a
353
248
  };
354
249
  }, I = (e, t = "tsconfig.json") => {
355
- let n = ee(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
250
+ let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
356
251
  if (i === void 0) return [];
357
252
  let a = C(b(n), r?.compilerOptions?.baseUrl ?? "."), o = [];
358
253
  for (let [e, t] of Object.entries(i)) {
@@ -404,45 +299,45 @@ var re = () => {
404
299
  }, xe = (e, t) => t.some((t) => e === t.dir || e.startsWith(`${t.dir}${w}`)), Se = (e, t) => t.map((t) => `${S(e, t.dir) || "."} (${t.source})`), Ce = [
405
300
  {
406
301
  id: "component.ui",
407
- re: p,
302
+ re: _,
408
303
  needsTest: !0
409
304
  },
410
305
  {
411
306
  id: "component",
412
- re: g,
307
+ re: ee,
413
308
  needsTest: !0
414
309
  },
415
310
  {
416
311
  id: "hook",
417
- re: _,
312
+ re: te,
418
313
  needsTest: !0
419
314
  },
420
315
  {
421
316
  id: "tracking",
422
- re: c,
317
+ re: f,
423
318
  needsTest: !0
424
319
  },
425
320
  {
426
321
  id: "store",
427
- re: s,
322
+ re: d,
428
323
  needsTest: !0
429
324
  },
430
325
  {
431
326
  id: "internal",
432
- re: d,
327
+ re: h,
433
328
  needsTest: !0
434
329
  },
435
330
  {
436
331
  id: "fixture",
437
- re: m,
332
+ re: v,
438
333
  needsTest: !1
439
334
  },
440
335
  {
441
336
  id: "types",
442
- re: l,
337
+ re: p,
443
338
  needsTest: !1
444
339
  }
445
- ], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, we = (e) => u.test(e), Te = /* @__PURE__ */ new Set([
340
+ ], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, we = (e) => m.test(e), Te = /* @__PURE__ */ new Set([
446
341
  "useMutation",
447
342
  "useAction",
448
343
  "useUpload",
@@ -487,17 +382,17 @@ var re = () => {
487
382
  a.isDirectory() ? Ae(r, t, n + 1) : a.isFile() && ke.test(i) && !i.endsWith(".d.ts") && t.push(r);
488
383
  }
489
384
  return t;
490
- }, je = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, Me = /\b(?:memo|forwardRef|observer|styled)\b/, z = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), B = (e) => {
491
- if (z(e)) return !0;
492
- if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return B(e.getExpression());
385
+ }, je = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, Me = /\b(?:memo|forwardRef|observer|styled)\b/, Ne = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), z = (e) => {
386
+ if (Ne(e)) return !0;
387
+ if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return z(e.getExpression());
493
388
  if (k.isVariableDeclaration(e)) {
494
389
  let t = e.getTypeNode();
495
390
  if (t !== void 0 && je.test(t.getText())) return !0;
496
391
  let n = e.getInitializer();
497
- return n === void 0 || B(n);
392
+ return n === void 0 || z(n);
498
393
  }
499
- return k.isCallExpression(e) ? Me.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => z(e) || k.isIdentifier(e)) : k.isTaggedTemplateExpression(e) ? !0 : !(k.isObjectLiteralExpression(e) || k.isArrayLiteralExpression(e) || k.isStringLiteral(e) || k.isNoSubstitutionTemplateLiteral(e) || k.isNumericLiteral(e) || k.isTemplateExpression(e) || k.isNewExpression(e) || k.isPropertyAccessExpression(e) || e.getKind() === j.TrueKeyword || e.getKind() === j.FalseKeyword || e.getKind() === j.NullKeyword);
500
- }, Ne = (e) => {
394
+ return k.isCallExpression(e) ? Me.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => Ne(e) || k.isIdentifier(e)) : k.isTaggedTemplateExpression(e) ? !0 : !(k.isObjectLiteralExpression(e) || k.isArrayLiteralExpression(e) || k.isStringLiteral(e) || k.isNoSubstitutionTemplateLiteral(e) || k.isNumericLiteral(e) || k.isTemplateExpression(e) || k.isNewExpression(e) || k.isPropertyAccessExpression(e) || e.getKind() === j.TrueKeyword || e.getKind() === j.FalseKeyword || e.getKind() === j.NullKeyword);
395
+ }, Pe = (e) => {
501
396
  let t = [], n = [], r = [], i = /* @__PURE__ */ new Set();
502
397
  for (let [a, o] of e.getExportedDeclarations()) {
503
398
  let e = o[0];
@@ -508,7 +403,7 @@ var re = () => {
508
403
  n.push(s);
509
404
  continue;
510
405
  }
511
- if (/^[A-Z]/.test(s) && B(e)) {
406
+ if (/^[A-Z]/.test(s) && z(e)) {
512
407
  t.push(s);
513
408
  continue;
514
409
  }
@@ -520,7 +415,7 @@ var re = () => {
520
415
  others: r,
521
416
  hasRuntimeExport: t.length + n.length + r.length > 0
522
417
  };
523
- }, Pe = [], Fe = (e, t, n) => {
418
+ }, B = [], Fe = (e, t, n) => {
524
419
  let r = b(e), i = [];
525
420
  for (let e = r;; e = b(e)) {
526
421
  let r = n.get(e);
@@ -537,8 +432,8 @@ var re = () => {
537
432
  }
538
433
  if (e === t || b(e) === e) break;
539
434
  }
540
- for (let e of i) n.set(e, Pe);
541
- return Pe;
435
+ for (let e of i) n.set(e, B);
436
+ return B;
542
437
  }, Ie = (e, t, n) => {
543
438
  let r = t.startsWith(".") ? C(b(e), t) : ye(t, n);
544
439
  if (r === void 0) return null;
@@ -627,14 +522,14 @@ var re = () => {
627
522
  } catch {
628
523
  continue;
629
524
  }
630
- s.set(t, Ne(r)), c.set(t, Le(r, t, Fe(t, e, u))), l.set(t, Re(r));
525
+ s.set(t, Pe(r)), c.set(t, Le(r, t, Fe(t, e, u))), l.set(t, Re(r));
631
526
  }
632
527
  let d = /* @__PURE__ */ new Map();
633
528
  for (let [e, t] of c) for (let n of t.localTargets) {
634
529
  let t = d.get(n) ?? [];
635
530
  t.push(e), d.set(n, t);
636
531
  }
637
- let p = (e) => {
532
+ let f = (e) => {
638
533
  let t = s.get(e), n = /* @__PURE__ */ new Set([
639
534
  ...t?.components ?? [],
640
535
  ...t?.hooks ?? [],
@@ -655,12 +550,12 @@ var re = () => {
655
550
  }
656
551
  };
657
552
  return a(e, null), [...r];
658
- }, m = 0;
553
+ }, p = 0;
659
554
  for (let e of r) {
660
555
  let t = R(e), n = s.get(e), r = c.get(e);
661
556
  if (n === void 0 || r === void 0) continue;
662
557
  let i = we(e);
663
- t !== null && !i && (m += 1);
558
+ t !== null && !i && (p += 1);
664
559
  let u = d.get(e) ?? [];
665
560
  if ((t === "component" || t === "component.ui") && !i) {
666
561
  let t = ze(n.components);
@@ -729,9 +624,9 @@ var re = () => {
729
624
  detail: `imports write hook(s): ${t.join(", ")}`,
730
625
  fix: "a *.component.ui.tsx renders; it does not write. Lift the mutation into the *.component.tsx that owns it and pass a handler down. Reading (useT / useCan / usePermissions) stays allowed — threading those through props is prop-drilling, not reusability"
731
626
  });
732
- let n = p(e), i = n.filter((e) => {
627
+ let n = f(e), i = n.filter((e) => {
733
628
  let t = R(e);
734
- return t === "component" || t === "component.ui" || h.test(e) || f.has(e.slice(e.lastIndexOf(w) + 1));
629
+ return t === "component" || t === "component.ui" || y.test(e) || g.has(e.slice(e.lastIndexOf(w) + 1));
735
630
  });
736
631
  n.length === 0 ? a.push({
737
632
  rule: "ui/orphaned",
@@ -794,7 +689,7 @@ var re = () => {
794
689
  }
795
690
  return a.sort((e, t) => e.file === t.file ? e.rule.localeCompare(t.rule) : e.file.localeCompare(t.file)), {
796
691
  findings: a,
797
- declared: m,
692
+ declared: p,
798
693
  scanned: r.length,
799
694
  vendored: n
800
695
  };
@@ -1173,99 +1068,14 @@ var re = () => {
1173
1068
  " reaches your deploy target is not knowable from here — a secret manager",
1174
1069
  " or a CI variable is not a file. Naming it nowhere is conclusive; naming",
1175
1070
  " it somewhere is not a guarantee."
1176
- ].join("\n"), mt = (e) => e === "effect" || e.startsWith("@effect/") || e.startsWith("@voltro/") || e === "react" || e === "react-dom", ht = /(^|\/)(\.bin|\.cache|\.pnpm-store|dist|build)(\/|$)/, gt = (e) => {
1177
- let t = [], n = e;
1178
- for (let e = 0; e < 12; e++) {
1179
- let e = x(n, "node_modules");
1180
- T(e) && t.push(e);
1181
- let r = b(n);
1182
- if (r === n) break;
1183
- n = r;
1184
- }
1185
- return t;
1186
- }, _t = (e) => {
1187
- try {
1188
- let t = JSON.parse(E(x(e, "package.json"), "utf8"));
1189
- return typeof t.version == "string" ? t.version : void 0;
1190
- } catch {
1191
- return;
1192
- }
1193
- }, vt = (e) => {
1194
- let t = [], n;
1195
- try {
1196
- n = D(e);
1197
- } catch {
1198
- return t;
1199
- }
1200
- for (let r of n) {
1201
- if (r.startsWith(".") || ht.test(r)) continue;
1202
- let n = x(e, r);
1203
- if (r.startsWith("@")) {
1204
- let e;
1205
- try {
1206
- e = D(n);
1207
- } catch {
1208
- continue;
1209
- }
1210
- for (let i of e) t.push({
1211
- pkg: `${r}/${i}`,
1212
- dir: x(n, i)
1213
- });
1214
- continue;
1215
- }
1216
- try {
1217
- if (!O(n).isDirectory()) continue;
1218
- } catch {
1219
- continue;
1220
- }
1221
- t.push({
1222
- pkg: r,
1223
- dir: n
1224
- });
1225
- }
1226
- return t;
1227
- }, yt = (e) => {
1228
- let t = /* @__PURE__ */ new Map();
1229
- for (let n of gt(e)) for (let { pkg: e, dir: r } of vt(n)) {
1230
- if (!mt(e)) continue;
1231
- let n = _t(r);
1232
- if (n === void 0) continue;
1233
- let i = t.get(e) ?? /* @__PURE__ */ new Map();
1234
- i.has(n) || i.set(n, r), t.set(e, i);
1235
- }
1236
- let n = [];
1237
- for (let [r, i] of t) i.size < 2 || n.push({
1238
- pkg: r,
1239
- versions: [...i.keys()].sort(),
1240
- paths: [...i.values()].map((t) => S(e, t)).sort()
1241
- });
1242
- return n.sort((e, t) => e.pkg.localeCompare(t.pkg));
1243
- }, bt = (e) => {
1244
- if (e.length !== 0) return [
1245
- `• ${e.length} package(s) resolved at more than one version:`,
1246
- ...e.flatMap((e) => [` ${e.pkg} — ${e.versions.join(", ")}`, ...e.paths.map((e) => ` ${e}`)]),
1247
- "",
1248
- " Two copies of `effect` (or `@effect/*`) is the usual cause of type errors",
1249
- " inside the GENERATED rpcGroup.generated.ts that look like framework bugs:",
1250
- " \"Property '[TypeId]' is missing\", \"typeof Never is not assignable to All\",",
1251
- " or an `Rpc<…, Stream<…>, …>` that will not go where `Any` is expected.",
1252
- " Effect types are nominal, so a Schema built by one copy is not the same",
1253
- " type as a Schema expected by the other — and the RUNTIME often still works,",
1254
- " which is why this shows up as a red tsc against a green server.",
1255
- "",
1256
- " Fix it in the install, not the code: align the version across the workspace",
1257
- " (a root `pnpm.overrides` / `resolutions` entry for `effect` is the blunt",
1258
- " instrument), then reinstall. Do not silence the generated file — it is where",
1259
- " a genuine mistake in your own descriptors surfaces."
1260
- ].join("\n");
1261
- }, xt = (e) => {
1071
+ ].join("\n"), mt = (e) => {
1262
1072
  if (e.catalogCodes.length === 0) return;
1263
1073
  let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
1264
1074
  if (n.length !== 0) return {
1265
1075
  orphaned: n,
1266
1076
  noneDeclared: t.size === 0
1267
1077
  };
1268
- }, St = (e, t) => {
1078
+ }, ht = (e, t) => {
1269
1079
  let n = e.orphaned.join(", ");
1270
1080
  return e.noneDeclared ? [
1271
1081
  `⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
@@ -1277,33 +1087,33 @@ var re = () => {
1277
1087
  " …or delete the files. A catalog that is never loaded looks from the inside",
1278
1088
  " exactly like one that works, which is why this is worth a line."
1279
1089
  ] : [`⚠ ${t} has catalog(s) not in \`locales:\` — ${n} — so they are never imported.`, " Add the code(s) to `locales:` in your app config, or delete the file(s)."];
1280
- }, Ct = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), wt = /* @__PURE__ */ new Set([
1090
+ }, gt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), _t = /* @__PURE__ */ new Set([
1281
1091
  "requireScope",
1282
1092
  "requireAnyScope",
1283
1093
  "requireAuth",
1284
1094
  "assertScope",
1285
1095
  "assertCan",
1286
1096
  "can"
1287
- ]), Tt = (e) => {
1097
+ ]), vt = (e) => {
1288
1098
  let t = /* @__PURE__ */ new Set();
1289
- for (let n of e) for (let e of Ct(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
1099
+ for (let n of e) for (let e of gt(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
1290
1100
  return t;
1291
- }, Et = (e, t) => {
1101
+ }, yt = (e, t) => {
1292
1102
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
1293
- }, Dt = (e) => {
1103
+ }, bt = (e) => {
1294
1104
  let t = /\bsubject\s*\.\s*id\b[^\n]*?(?:!==|===|!=|==)|(?:!==|===|!=|==)[^\n]*?\bsubject\s*\.\s*id\b/.test(e), n = /(?:Error|Denied|Forbidden|Unauthorized)\b|\b(?:fail|die|throw)\b/.test(e);
1295
1105
  return t && n;
1296
- }, Ot = (e, t, n) => {
1106
+ }, xt = (e, t, n) => {
1297
1107
  if (e.hasDeclaredGuards) return { kind: "declared-guard" };
1298
- let r = Ct(e.src), i = Et(r, t);
1299
- return i === void 0 ? Dt(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
1108
+ let r = gt(e.src), i = yt(r, t);
1109
+ return i === void 0 ? bt(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
1300
1110
  kind: "vocabulary-guard",
1301
1111
  name: i
1302
1112
  };
1303
- }, kt = (e, t, n) => {
1113
+ }, St = (e, t, n) => {
1304
1114
  let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
1305
1115
  return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
1306
- }, At = (e, t, n) => {
1116
+ }, Ct = (e, t, n) => {
1307
1117
  switch (t.kind) {
1308
1118
  case "declared-guard": return "guards: on the descriptor";
1309
1119
  case "vocabulary-guard": return `calls ${t.name}()`;
@@ -1311,15 +1121,15 @@ var re = () => {
1311
1121
  case "allowlisted": return "accepted by a previous run";
1312
1122
  case "unchecked": return n ? e.op === "read" ? "no check in this file — this app registers a row filter; VERIFY it covers this table, then record it as reviewed= rather than debt" : `${e.op}s${e.table ? ` ${e.table}` : ""} with no check in this file — this app registers a row filter; VERIFY it covers this table, then record it as reviewed= rather than debt` : e.op === "read" ? "returns a row the client selected, with nothing compared to the caller" : `${e.op}s${e.table ? ` ${e.table}` : ""} with nothing constraining WHICH row`;
1313
1123
  }
1314
- }, jt = (e) => {
1124
+ }, wt = (e) => {
1315
1125
  let t = e.executors.map((t) => {
1316
- let n = Ot(t, e.guardVocabulary, e.allowlist);
1126
+ let n = xt(t, e.guardVocabulary, e.allowlist);
1317
1127
  return {
1318
1128
  tag: t.tag,
1319
1129
  path: t.path,
1320
1130
  verdict: n,
1321
- severity: kt(t, e.tenantScopedTables, e.referencedTables),
1322
- why: At(t, n, e.rowFilterRegistered === !0)
1131
+ severity: St(t, e.tenantScopedTables, e.referencedTables),
1132
+ why: Ct(t, n, e.rowFilterRegistered === !0)
1323
1133
  };
1324
1134
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
1325
1135
  return {
@@ -1333,7 +1143,7 @@ var re = () => {
1333
1143
  },
1334
1144
  unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
1335
1145
  };
1336
- }, Mt = [
1146
+ }, Tt = [
1337
1147
  "# voltro doctor — authz allowlist",
1338
1148
  "#",
1339
1149
  "# Two KINDS of line, and the difference is the point:",
@@ -1358,18 +1168,18 @@ var re = () => {
1358
1168
  "# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
1359
1169
  "# lines for executors that are newly unchecked.",
1360
1170
  ""
1361
- ], Nt = (e) => {
1171
+ ], Et = (e) => {
1362
1172
  let t = [...e].map((e) => typeof e == "string" ? { tag: e } : e), n = t.filter((e) => e.reviewed === void 0).map((e) => e.tag).sort(), r = t.filter((e) => e.reviewed !== void 0).sort((e, t) => e.tag.localeCompare(t.tag));
1363
1173
  return [
1364
- ...Mt,
1174
+ ...Tt,
1365
1175
  ...n,
1366
1176
  ...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
1367
1177
  ...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
1368
1178
  ""
1369
1179
  ].join("\n");
1370
- }, Pt = (e, t) => {
1180
+ }, Dt = (e, t) => {
1371
1181
  let n = [...new Set(t)].sort();
1372
- if (e === void 0) return Nt(n.map((e) => ({ tag: e })));
1182
+ if (e === void 0) return Et(n.map((e) => ({ tag: e })));
1373
1183
  let r = W(e).tags, i = n.filter((e) => !r.has(e));
1374
1184
  return i.length === 0 ? e : [
1375
1185
  e.replace(/\n+$/, ""),
@@ -1379,7 +1189,7 @@ var re = () => {
1379
1189
  ...i,
1380
1190
  ""
1381
1191
  ].join("\n");
1382
- }, Ft = (e, t) => {
1192
+ }, Ot = (e, t) => {
1383
1193
  let n = [...new Set(t)];
1384
1194
  if (e === void 0) return {
1385
1195
  added: n.length,
@@ -1392,7 +1202,7 @@ var re = () => {
1392
1202
  kept: r.size,
1393
1203
  removed: 0
1394
1204
  };
1395
- }, It = class extends Error {
1205
+ }, kt = class extends Error {
1396
1206
  tag;
1397
1207
  constructor(e) {
1398
1208
  super(`authz allowlist: '${e}' is marked reviewed with no reason. Write \`${e} reviewed=<why it is genuinely open>\`. A bare tag is the other, honest option — it records the executor as DEBT, which is what an unexplained exemption is.`), this.tag = e, this.name = "AllowlistReasonMissingError";
@@ -1410,7 +1220,7 @@ var re = () => {
1410
1220
  continue;
1411
1221
  }
1412
1222
  let a = r[2].trim();
1413
- if (a.length === 0) throw new It(i);
1223
+ if (a.length === 0) throw new kt(i);
1414
1224
  t.push({
1415
1225
  tag: i,
1416
1226
  reviewed: a
@@ -1420,13 +1230,13 @@ var re = () => {
1420
1230
  tags: new Set(t.map((e) => e.tag)),
1421
1231
  entries: t
1422
1232
  };
1423
- }, Lt = (e) => {
1233
+ }, At = (e) => {
1424
1234
  let t = e.indexOf(".");
1425
1235
  return t <= 0 ? void 0 : e.slice(0, t);
1426
- }, Rt = (e, t) => {
1236
+ }, jt = (e, t) => {
1427
1237
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
1428
1238
  for (let e of t.rpcTags) {
1429
- let t = Lt(e);
1239
+ let t = At(e);
1430
1240
  if (t === void 0) continue;
1431
1241
  let n = i.get(t);
1432
1242
  n ? n.push(e) : i.set(t, [e]);
@@ -1455,7 +1265,7 @@ var re = () => {
1455
1265
  });
1456
1266
  continue;
1457
1267
  }
1458
- let s = new Set(o.rpcTags.map(Lt).filter((e) => e !== void 0));
1268
+ let s = new Set(o.rpcTags.map(At).filter((e) => e !== void 0));
1459
1269
  for (let e of s) {
1460
1270
  let t = i.get(e);
1461
1271
  t !== void 0 && a.push({
@@ -1468,13 +1278,13 @@ var re = () => {
1468
1278
  }
1469
1279
  }
1470
1280
  return a;
1471
- }, zt = (e, t) => {
1281
+ }, Mt = (e, t) => {
1472
1282
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
1473
1283
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
1474
1284
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
1475
1285
  return r;
1476
- }, Bt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Vt = (e) => {
1477
- let t = y(e), n = v(e);
1286
+ }, Nt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Pt = (e) => {
1287
+ let t = re(e), n = ne(e);
1478
1288
  return {
1479
1289
  root: e,
1480
1290
  serveBundle: t,
@@ -1482,19 +1292,19 @@ var re = () => {
1482
1292
  apiBundle: n,
1483
1293
  apiBundlePresent: T(n)
1484
1294
  };
1485
- }, Ht = [
1295
+ }, Ft = [
1486
1296
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
1487
1297
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
1488
1298
  "(Generated Dockerfiles from `voltro create-project` already do this.)"
1489
- ].join("\n "), Ut = (e, t = !1) => {
1490
- let n = Vt(e);
1491
- if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Bt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
1299
+ ].join("\n "), It = (e, t = !1) => {
1300
+ let n = Pt(e);
1301
+ if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Nt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
1492
1302
  console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
1493
1303
  let r = process.env.NODE_ENV === "production";
1494
1304
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
1495
1305
  let i = t || r;
1496
- return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Ht}`), +!!i;
1497
- }, Wt = (e, t, n) => {
1306
+ return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Ft}`), +!!i;
1307
+ }, Lt = (e, t, n) => {
1498
1308
  let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
1499
1309
  `${e}.ts`,
1500
1310
  `${e}.tsx`,
@@ -1520,7 +1330,7 @@ var re = () => {
1520
1330
  r !== void 0 && o.push(r);
1521
1331
  }
1522
1332
  return o;
1523
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Xt.test(S(n, e))), l = new Set(c);
1333
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Ut.test(S(n, e))), l = new Set(c);
1524
1334
  for (; c.length > 0;) {
1525
1335
  let e = c.shift();
1526
1336
  if (!s.has(e)) {
@@ -1533,11 +1343,11 @@ var re = () => {
1533
1343
  let d = /* @__PURE__ */ new Set();
1534
1344
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
1535
1345
  return d;
1536
- }, Gt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Kt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, qt = (e, t) => {
1346
+ }, Rt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), zt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Bt = (e, t) => {
1537
1347
  if (e.src.includes(t)) return !0;
1538
1348
  let n = t.replace(/^@voltro\/plugin-/, "");
1539
1349
  return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
1540
- }, Jt = (e, t) => {
1350
+ }, Vt = (e, t) => {
1541
1351
  let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
1542
1352
  for (; (i = r.exec(e)) !== null;) {
1543
1353
  let t = i.index + i[0].length, r = 1, a = t;
@@ -1545,10 +1355,10 @@ var re = () => {
1545
1355
  n.push(e.slice(t, a - 1));
1546
1356
  }
1547
1357
  return n;
1548
- }, Yt = (e, t) => {
1358
+ }, Ht = (e, t) => {
1549
1359
  let n = t.getSymbol()?.getDeclarations()?.[0];
1550
1360
  return n === void 0 ? [] : e.getDescendantsOfKind(j.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
1551
- }, Xt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Zt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Qt = [
1361
+ }, Ut = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Wt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Gt = [
1552
1362
  "Id",
1553
1363
  "ID",
1554
1364
  "Hash",
@@ -1574,12 +1384,12 @@ var re = () => {
1574
1384
  "Comment",
1575
1385
  "Provider",
1576
1386
  "Format"
1577
- ], $t = RegExp(`(?:${Qt.join("|")})$|_(?:${Qt.map((e) => e.toLowerCase()).join("|")})$`), en = [
1387
+ ], Kt = RegExp(`(?:${Gt.join("|")})$|_(?:${Gt.map((e) => e.toLowerCase()).join("|")})$`), qt = [
1578
1388
  {
1579
1389
  id: "hardcoded-framework-cookie",
1580
1390
  scope: "client",
1581
1391
  smell: "a framework cookie name written as a string literal instead of imported",
1582
- use: "import the constant — `LOCALE_COOKIE` / `THEME_COOKIE` from `@voltro/ui-shadcn` — so a rename in the framework is a compile error in your app rather than a preference that quietly stops working",
1392
+ use: "import the constant — `LOCALE_COOKIE` / `THEME_COOKIE` from `@voltro/i18n` (or from `@voltro/ui-shadcn` if you use the kit) — so a rename in the framework is a compile error in your app rather than a preference that quietly stops working",
1583
1393
  match: ({ src: e }) => /['"`]voltro:(?:locale|theme|lang)['"`]/.test(e)
1584
1394
  },
1585
1395
  {
@@ -1587,7 +1397,7 @@ var re = () => {
1587
1397
  scope: "server",
1588
1398
  smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
1589
1399
  use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
1590
- match: ({ src: e, path: t }) => Zt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1400
+ match: ({ src: e, path: t }) => Wt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1591
1401
  },
1592
1402
  {
1593
1403
  id: "subject-write-no-guard",
@@ -1595,8 +1405,8 @@ var re = () => {
1595
1405
  smell: "writes ctx.request.subject.id into the database with no guard on the descriptor — an anonymous caller reaches the write and gets a raw statement failure instead of a typed refusal",
1596
1406
  use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
1597
1407
  match: ({ src: e, path: t }) => {
1598
- if (!Zt.test(t)) return !1;
1599
- let n = Gt(e);
1408
+ if (!Wt.test(t)) return !1;
1409
+ let n = Rt(e);
1600
1410
  return /\bsubject\s*\.\s*id\b/.test(n) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(n) && !/\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(n);
1601
1411
  }
1602
1412
  },
@@ -1605,7 +1415,7 @@ var re = () => {
1605
1415
  scope: "server",
1606
1416
  smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
1607
1417
  use: "yield* HttpClient — the framework client. It injects traceparent, retries, and honours `http.allowHosts`. Where the URL is built from anything a caller supplied it ALSO blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop — for a hard-coded host that half is redundant, and the first three are still the reason to switch",
1608
- match: ({ file: e, path: t, serverOnly: n }) => (Xt.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1418
+ match: ({ file: e, path: t, serverOnly: n }) => (Ut.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1609
1419
  },
1610
1420
  {
1611
1421
  id: "form",
@@ -1640,7 +1450,7 @@ var re = () => {
1640
1450
  scope: "client",
1641
1451
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
1642
1452
  use: "useDerived — dependency-tracked, referentially stable",
1643
- match: ({ src: e }) => Kt(e, /\buseSubscription\s*[(<]/g) >= 3 && Kt(e, /\buseMemo\s*\(/g) >= 3
1453
+ match: ({ src: e }) => zt(e, /\buseSubscription\s*[(<]/g) >= 3 && zt(e, /\buseMemo\s*\(/g) >= 3
1644
1454
  },
1645
1455
  {
1646
1456
  id: "next-shim",
@@ -1665,7 +1475,7 @@ var re = () => {
1665
1475
  match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
1666
1476
  let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
1667
1477
  return t === void 0 ? [] : k.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
1668
- }).some((t) => Yt(e, t).some((e) => {
1478
+ }).some((t) => Ht(e, t).some((e) => {
1669
1479
  let t = e.getParent();
1670
1480
  if (k.isBinaryExpression(t)) {
1671
1481
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -1716,7 +1526,7 @@ var re = () => {
1716
1526
  use: ".encrypted() on the column — boot fails loudly if no cipher is configured, so it can never silently persist plaintext (a *Hash column and a vault* handle are NOT plaintext secrets: the hash IS the protection — often a unique lookup key — and the handle only names a secret held elsewhere, so both are skipped)",
1717
1527
  match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
1718
1528
  let n = t[1];
1719
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || $t.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1529
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Kt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1720
1530
  }))
1721
1531
  },
1722
1532
  {
@@ -1726,7 +1536,7 @@ var re = () => {
1726
1536
  use: "you cannot look a value up by equality AND encrypt it non-deterministically — `.encrypted()` would make this column unfindable. Store the HASH as the unique lookup key and keep the secret itself either `.encrypted()` in a second column or nowhere (the framework's own `_voltro_api_keys.hashedKey` is the shape: the presented value is hashed and compared, never read back). A deterministic cipher is the other option and buys equality lookup at the cost of leaking which rows share a value",
1727
1537
  match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
1728
1538
  let n = t[1];
1729
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || $t.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1539
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Kt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1730
1540
  }))
1731
1541
  },
1732
1542
  {
@@ -1791,8 +1601,8 @@ var re = () => {
1791
1601
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
1792
1602
  use: "wire the filter into at least one test (`makeTestContext({ subject, store, rowFilter })`) and sabotage the predicate once to watch it go red — an untested row filter is a visibility rule nothing verifies",
1793
1603
  match: ({ file: e, testSources: t }) => G(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1794
- let t = Gt(e);
1795
- return Jt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1604
+ let t = Rt(e);
1605
+ return Vt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1796
1606
  })
1797
1607
  },
1798
1608
  {
@@ -1802,14 +1612,14 @@ var re = () => {
1802
1612
  use: "timestampMs / timestampMsOrNull from @voltro/database/wire in the descriptor's output struct — `output` IS the serializer (it is the rpc success schema), so a Date field declared as timestampMs crosses as epoch ms on its own; declaring Schema.Number describes the WIRE type and leaves nothing to convert",
1803
1613
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1804
1614
  }
1805
- ], tn = (e) => q(e), nn = (e, t) => {
1615
+ ], Jt = (e) => q(e), Yt = (e, t) => {
1806
1616
  let n = /* @__PURE__ */ new Set();
1807
1617
  for (let r of t) {
1808
1618
  let t = S(e, r), i = t.split(/[\\/]/)[0];
1809
1619
  n.add(i !== void 0 && i !== t ? i : ".");
1810
1620
  }
1811
1621
  return [...n].sort();
1812
- }, rn = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, an = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, on = (e, t = [], n = 0) => {
1622
+ }, Xt = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Zt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Qt = (e, t = [], n = 0) => {
1813
1623
  if (n > 12 || K.test(e)) return t;
1814
1624
  let r;
1815
1625
  try {
@@ -1821,7 +1631,7 @@ var re = () => {
1821
1631
  let r = x(e, i.name);
1822
1632
  if (i.isDirectory()) {
1823
1633
  if (i.name.startsWith(".")) continue;
1824
- on(r, t, n + 1);
1634
+ Qt(r, t, n + 1);
1825
1635
  } else dt.test(i.name) && t.push(r);
1826
1636
  }
1827
1637
  return t;
@@ -1840,10 +1650,10 @@ var re = () => {
1840
1650
  } catch {
1841
1651
  continue;
1842
1652
  }
1843
- a.isDirectory() ? q(r, t, n + 1) : rn.test(i) && !/\.(test|spec)\./.test(i) && !an.test(i) && t.push(r);
1653
+ a.isDirectory() ? q(r, t, n + 1) : Xt.test(i) && !/\.(test|spec)\./.test(i) && !Zt.test(i) && t.push(r);
1844
1654
  }
1845
1655
  return t;
1846
- }, sn = (e, t = [], n = 0) => {
1656
+ }, $t = (e, t = [], n = 0) => {
1847
1657
  if (n > 12 || K.test(e)) return t;
1848
1658
  let r;
1849
1659
  try {
@@ -1858,17 +1668,17 @@ var re = () => {
1858
1668
  } catch {
1859
1669
  continue;
1860
1670
  }
1861
- a.isDirectory() ? sn(r, t, n + 1) : rn.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1671
+ a.isDirectory() ? $t(r, t, n + 1) : Xt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1862
1672
  }
1863
1673
  return t;
1864
- }, cn = (e) => {
1865
- let t = tn(e);
1674
+ }, en = (e) => {
1675
+ let t = Jt(e);
1866
1676
  return {
1867
- dirs: nn(e, t),
1677
+ dirs: Yt(e, t),
1868
1678
  fileCount: t.length
1869
1679
  };
1870
- }, ln = (e) => {
1871
- let t = tn(e);
1680
+ }, tn = (e) => {
1681
+ let t = Jt(e);
1872
1682
  if (t.length === 0) return [];
1873
1683
  let n = new A({
1874
1684
  useInMemoryFileSystem: !0,
@@ -1877,8 +1687,8 @@ var re = () => {
1877
1687
  for (let e of t) try {
1878
1688
  i.set(e, E(e, "utf8"));
1879
1689
  } catch {}
1880
- let a = Wt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1881
- for (let t of sn(e)) try {
1690
+ let a = Lt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1691
+ for (let t of $t(e)) try {
1882
1692
  o.set(t, E(t, "utf8"));
1883
1693
  } catch {}
1884
1694
  for (let s of t) {
@@ -1896,23 +1706,23 @@ var re = () => {
1896
1706
  } catch {
1897
1707
  continue;
1898
1708
  }
1899
- for (let t of en) {
1900
- if (t.plugin !== void 0 && qt(c, t.plugin) || !t.match(c)) continue;
1709
+ for (let t of qt) {
1710
+ if (t.plugin !== void 0 && Bt(c, t.plugin) || !t.match(c)) continue;
1901
1711
  let n = r.get(t.id) ?? [];
1902
1712
  n.push(S(e, s)), r.set(t.id, n);
1903
1713
  }
1904
1714
  }
1905
- return en.filter((e) => r.has(e.id)).map((e) => ({
1715
+ return qt.filter((e) => r.has(e.id)).map((e) => ({
1906
1716
  rule: e,
1907
1717
  files: r.get(e.id)
1908
1718
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1909
- }, un = (e) => {
1910
- let t = cn(e);
1719
+ }, nn = (e) => {
1720
+ let t = en(e);
1911
1721
  return {
1912
1722
  root: e,
1913
1723
  scannedFiles: t.fileCount,
1914
1724
  scannedDirs: t.dirs,
1915
- findings: ln(e).map(({ rule: e, files: t }) => ({
1725
+ findings: tn(e).map(({ rule: e, files: t }) => ({
1916
1726
  id: e.id,
1917
1727
  scope: e.scope,
1918
1728
  smell: e.smell,
@@ -1920,13 +1730,13 @@ var re = () => {
1920
1730
  files: t
1921
1731
  }))
1922
1732
  };
1923
- }, dn = 3, fn = .2, pn = 50, mn = (e, t) => e >= pn && t > 0 && e / t >= fn, hn = (e) => {
1733
+ }, rn = 3, an = .2, on = 50, sn = (e, t) => e >= on && t > 0 && e / t >= an, cn = (e) => {
1924
1734
  let t = /* @__PURE__ */ new Map();
1925
1735
  for (let n of q(e)) try {
1926
1736
  t.set(S(e, n), E(n, "utf8"));
1927
1737
  } catch {}
1928
1738
  let n = /* @__PURE__ */ new Map();
1929
- for (let t of on(e)) try {
1739
+ for (let t of Qt(e)) try {
1930
1740
  n.set(S(e, t), E(t, "utf8"));
1931
1741
  } catch {}
1932
1742
  let r = ft({
@@ -1934,8 +1744,8 @@ var re = () => {
1934
1744
  envFiles: n
1935
1745
  });
1936
1746
  r !== void 0 && (console.log(""), console.log(pt(r)));
1937
- }, gn = (e) => {
1938
- let t = un(e);
1747
+ }, ln = (e) => {
1748
+ let t = nn(e);
1939
1749
  if (console.log(""), t.scannedFiles === 0) return console.log(`\u2022 hand-roll scan: no source directories found under ${e}.`), console.log(" Walked the app root recursively (node_modules / dist / .framework pruned). Run `voltro doctor <app>` against an app root."), 0;
1940
1750
  if (t.findings.length === 0) return console.log(`\u2713 hand-roll scan: ${t.scannedFiles} file${t.scannedFiles === 1 ? "" : "s"} across ${t.scannedDirs.join(", ")} \u2014 no hand-rolled primitives found.`), 0;
1941
1751
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
@@ -1946,56 +1756,56 @@ var re = () => {
1946
1756
  console.log(` [${e}]`);
1947
1757
  let i = [...r].sort((e, t) => e.files.length - t.files.length);
1948
1758
  for (let e of i) {
1949
- if (mn(e.files.length, t.scannedFiles)) {
1759
+ if (sn(e.files.length, t.scannedFiles)) {
1950
1760
  let n = Math.round(e.files.length / t.scannedFiles * 100);
1951
1761
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s), ${n}% of those scanned: a codebase-wide pattern rather than a work list`), console.log(` \u2192 ${e.use}`), console.log(" (paths omitted on purpose at this scale — `voltro doctor --json` has every one.)");
1952
1762
  continue;
1953
1763
  }
1954
- let r = e.files.slice(0, dn).join(", "), i = e.files.length - dn;
1764
+ let r = e.files.slice(0, rn).join(", "), i = e.files.length - rn;
1955
1765
  i > 0 && (n += i);
1956
1766
  let a = i > 0 ? `, +${i} more` : "";
1957
1767
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1958
1768
  }
1959
1769
  }
1960
1770
  return n > 0 && console.log(` (${n} more file path${n === 1 ? "" : "s"} not shown \u2014 \`voltro doctor --json\` prints every one.)`), console.log(" (See the \"Pick the SERVER primitive\" / \"Pick the CLIENT primitive\" rubrics in AGENTS.md / CLAUDE.md.)"), t.findings.length;
1961
- }, _n = "renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.", vn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, yn = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, bn = (e) => {
1962
- let t = vn.exec(e)?.[1];
1771
+ }, un = "renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.", dn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, fn = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, pn = (e) => {
1772
+ let t = dn.exec(e)?.[1];
1963
1773
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1964
- let n = yn.exec(e)?.[1];
1774
+ let n = fn.exec(e)?.[1];
1965
1775
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1966
- }, xn = async (t) => {
1967
- let i = x(t, "src", "pages");
1968
- if (!T(i)) return [];
1969
- let { pages: a, dirs: o } = await e(i), s = [];
1970
- for (let e of a) {
1971
- if (!r(o, e.dirChain).some((e) => e.hasLayout)) continue;
1972
- let a;
1776
+ }, mn = async (e) => {
1777
+ let t = x(e, "src", "pages");
1778
+ if (!T(t)) return [];
1779
+ let { pages: i, dirs: o } = await n(t), s = [];
1780
+ for (let n of i) {
1781
+ if (!a(o, n.dirChain).some((e) => e.hasLayout)) continue;
1782
+ let i;
1973
1783
  try {
1974
- a = E(x(i, e.file), "utf8");
1784
+ i = E(x(t, n.file), "utf8");
1975
1785
  } catch {
1976
1786
  continue;
1977
1787
  }
1978
- if (n.test(a)) continue;
1979
- let c = bn(a);
1788
+ if (r.test(i)) continue;
1789
+ let c = pn(i);
1980
1790
  c !== null && s.push({
1981
- file: S(t, x(i, e.file)),
1982
- pattern: e.pattern,
1791
+ file: S(e, x(t, n.file)),
1792
+ pattern: n.pattern,
1983
1793
  currentMode: c
1984
1794
  });
1985
1795
  }
1986
1796
  return s;
1987
- }, Sn = 10, Cn = async (e) => {
1797
+ }, hn = 10, gn = async (e) => {
1988
1798
  if (console.log(""), !T(x(e, "src", "pages"))) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
1989
- let t = await xn(e);
1799
+ let t = await mn(e);
1990
1800
  if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1991
1801
  console.log(`• renderMode:'spa' candidates (${t.length} page${t.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1992
- for (let e of t.slice(0, Sn)) {
1802
+ for (let e of t.slice(0, hn)) {
1993
1803
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1994
1804
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1995
1805
  }
1996
- let n = t.length - Sn;
1997
- return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${_n}`), t.length;
1998
- }, wn = /* @__PURE__ */ new Set([
1806
+ let n = t.length - hn;
1807
+ return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${un}`), t.length;
1808
+ }, _n = /* @__PURE__ */ new Set([
1999
1809
  "node_modules",
2000
1810
  "dist",
2001
1811
  "build",
@@ -2003,7 +1813,7 @@ var re = () => {
2003
1813
  ".vite",
2004
1814
  ".framework",
2005
1815
  ".git"
2006
- ]), J = (e, t, n) => {
1816
+ ]), vn = (e, t, n) => {
2007
1817
  let r;
2008
1818
  try {
2009
1819
  r = D(e, { withFileTypes: !0 });
@@ -2011,11 +1821,11 @@ var re = () => {
2011
1821
  return;
2012
1822
  }
2013
1823
  for (let i of r) {
2014
- if (wn.has(i.name)) continue;
1824
+ if (_n.has(i.name)) continue;
2015
1825
  let r = x(e, i.name);
2016
- i.isDirectory() ? J(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
1826
+ i.isDirectory() ? vn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
2017
1827
  }
2018
- }, Tn = (e, t) => {
1828
+ }, yn = (e, t) => {
2019
1829
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
2020
1830
  for (let i of t) {
2021
1831
  let t;
@@ -2040,21 +1850,21 @@ var re = () => {
2040
1850
  }
2041
1851
  }
2042
1852
  return n;
2043
- }, En = (e, t) => {
1853
+ }, bn = (e, t) => {
2044
1854
  let n = e.slice(t.length).split(w).slice(0, -1)[0];
2045
1855
  return n !== void 0 && n.startsWith("[");
2046
- }, Y = (e) => {
1856
+ }, J = (e) => {
2047
1857
  let t = [], n = x(e, "src", "pages"), r = x(e, "src");
2048
1858
  if (!T(r)) return t;
2049
- let a = [];
2050
- J(r, e, a), a.sort();
2051
- let o = `${S(e, n)}${w}`;
2052
- Tn(e, a);
2053
- for (let n of a) {
1859
+ let i = [];
1860
+ vn(r, e, i), i.sort();
1861
+ let a = `${S(e, n)}${w}`;
1862
+ yn(e, i);
1863
+ for (let n of i) {
2054
1864
  let r = n.slice(n.lastIndexOf(w) + 1);
2055
- if (n.startsWith(o)) {
1865
+ if (n.startsWith(a)) {
2056
1866
  if (r !== "page.tsx") continue;
2057
- T(x(e, i(n))) || t.push({
1867
+ T(x(e, c(n))) || t.push({
2058
1868
  kind: "page-without-test",
2059
1869
  file: n
2060
1870
  });
@@ -2065,7 +1875,7 @@ var re = () => {
2065
1875
  file: n
2066
1876
  });
2067
1877
  } catch {}
2068
- En(n, o) && t.push({
1878
+ bn(n, a) && t.push({
2069
1879
  kind: "top-level-dynamic-route",
2070
1880
  file: n
2071
1881
  });
@@ -2077,33 +1887,33 @@ var re = () => {
2077
1887
  });
2078
1888
  }
2079
1889
  return t;
2080
- }, Dn = 10, X = (e, t, n) => {
1890
+ }, xn = 10, Y = (e, t, n) => {
2081
1891
  if (t.length === 0) return;
2082
1892
  console.log(`• ${e} (${t.length}):`);
2083
- for (let e of t.slice(0, Dn)) console.log(` ${e}`);
2084
- let r = t.length - Dn;
1893
+ for (let e of t.slice(0, xn)) console.log(` ${e}`);
1894
+ let r = t.length - xn;
2085
1895
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
2086
- }, On = (e) => {
1896
+ }, Sn = (e) => {
2087
1897
  if (console.log(""), !T(x(e, "src", "pages"))) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
2088
- let t = Y(e);
1898
+ let t = J(e);
2089
1899
  if (t.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
2090
1900
  let n = (e) => t.filter((t) => t.kind === e).map((e) => e.file);
2091
- return X("will NEVER route — page.tsx outside src/pages/", n("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), X("pages with no test beside them", n("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), X("routes whose FIRST segment is dynamic — they claim every URL of that depth", n("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), X("pages with no `meta` export — they serve the APP name as their title", n("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). Advisory."), t.length;
2092
- }, kn = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", An = /* @__PURE__ */ new Map(), Z = (e) => {
2093
- let t = An.get(e);
1901
+ return Y("will NEVER route — page.tsx outside src/pages/", n("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), Y("pages with no test beside them", n("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), Y("routes whose FIRST segment is dynamic — they claim every URL of that depth", n("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), Y("pages with no `meta` export — they serve the APP name as their title", n("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). If such a page also has no real body — it redirects from an effect, say — the missing title is the symptom rather than the bug: on an `ssr` route the server answers 200 with an empty document and the redirect happens only once JavaScript runs. Move it into the loader (`redirect(...)`, a real 303). Advisory."), t.length;
1902
+ }, Cn = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", wn = /* @__PURE__ */ new Map(), X = (e) => {
1903
+ let t = wn.get(e);
2094
1904
  if (t !== void 0) return t;
2095
1905
  let n = (async () => {
2096
- let { walk: t, loadDiscovered: n } = await import("./dev-tPcOmjPJ.js"), r = await t(e);
1906
+ let { walk: t, loadDiscovered: n } = await import("./dev-DOZVMVfi.js"), r = await t(e);
2097
1907
  return {
2098
1908
  files: r,
2099
1909
  discovered: await n(r)
2100
1910
  };
2101
1911
  })();
2102
- return An.set(e, n), n;
2103
- }, jn = async (e) => {
1912
+ return wn.set(e, n), n;
1913
+ }, Tn = async (e) => {
2104
1914
  let t;
2105
1915
  try {
2106
- let { files: n, discovered: r } = await Z(e), i = r.tables.map((e) => {
1916
+ let { files: n, discovered: r } = await X(e), i = r.tables.map((e) => {
2107
1917
  let t = e;
2108
1918
  return {
2109
1919
  name: t.tableName,
@@ -2116,7 +1926,7 @@ var re = () => {
2116
1926
  return;
2117
1927
  }
2118
1928
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
2119
- }, Mn = (e) => e.map((e) => {
1929
+ }, En = (e) => e.map((e) => {
2120
1930
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
2121
1931
  name: e,
2122
1932
  type: typeof t?.type == "string" ? t.type : ""
@@ -2126,21 +1936,21 @@ var re = () => {
2126
1936
  columns: n,
2127
1937
  primaryKey: t.appliedPrimaryKey ?? []
2128
1938
  };
2129
- }), Nn = async (e) => {
1939
+ }), Z = async (e) => {
2130
1940
  try {
2131
- let { discovered: t } = await Z(e);
2132
- return Qe(Mn(t.tables));
1941
+ let { discovered: t } = await X(e);
1942
+ return Qe(En(t.tables));
2133
1943
  } catch {
2134
1944
  return null;
2135
1945
  }
2136
- }, Pn = async (e) => {
2137
- let t = await Nn(e);
1946
+ }, Dn = async (e) => {
1947
+ let t = await Z(e);
2138
1948
  if (t === null || t.tablesChecked === 0) return;
2139
1949
  let n = et(t);
2140
1950
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
2141
- }, Fn = async (e) => {
1951
+ }, On = async (e) => {
2142
1952
  try {
2143
- let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await Z(e);
1953
+ let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await X(e);
2144
1954
  return it(n.events.map((e) => {
2145
1955
  let n = e.descriptor, r = t(n.key);
2146
1956
  return {
@@ -2153,19 +1963,19 @@ var re = () => {
2153
1963
  } catch {
2154
1964
  return null;
2155
1965
  }
2156
- }, In = async (e) => {
2157
- let t = await Fn(e);
1966
+ }, kn = async (e) => {
1967
+ let t = await On(e);
2158
1968
  if (t === null || t.eventsChecked === 0) return;
2159
1969
  let n = at(t);
2160
1970
  n !== null && (console.log(""), console.log(`• ${n}`));
2161
1971
  let r = ot(t);
2162
1972
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
2163
- }, Ln = (e) => {
1973
+ }, An = (e) => {
2164
1974
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
2165
1975
  return r === void 0 ? n : `${r}: ${n}`;
2166
- }, Rn = async (e) => {
1976
+ }, jn = async (e) => {
2167
1977
  try {
2168
- let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await Z(e)).discovered.serverOnlyLeaks, r = t(n);
1978
+ let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await X(e)).discovered.serverOnlyLeaks, r = t(n);
2169
1979
  return {
2170
1980
  status: {
2171
1981
  checked: !0,
@@ -2176,16 +1986,16 @@ var re = () => {
2176
1986
  } catch (e) {
2177
1987
  return { status: {
2178
1988
  checked: !1,
2179
- reason: Ln(e)
1989
+ reason: An(e)
2180
1990
  } };
2181
1991
  }
2182
- }, zn = async (e) => {
2183
- let { status: t, message: n } = await Rn(e);
1992
+ }, Mn = async (e) => {
1993
+ let { status: t, message: n } = await jn(e);
2184
1994
  return t.checked ? n === void 0 ? !1 : (console.log(""), console.log(`✗ [serverOnly] ${n}`), !0) : (console.log(""), console.log("• serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), console.log(" This audit is the automated check that a hash or a token cannot reach the wire, so"), console.log(" an app that leans on `.serverOnly()` should treat a persistent skip as a failure."), console.log(" `voltro doctor --json` reports it as `serverOnly.checked: false` for a CI gate."), !1);
2185
- }, Bn = async (e) => {
1995
+ }, Nn = async (e) => {
2186
1996
  let t;
2187
1997
  try {
2188
- let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await Z(e);
1998
+ let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await X(e);
2189
1999
  t = lt(r, i.workflows.map((e) => {
2190
2000
  let t = e.definition, r = t.payloadSchema === void 0 ? {
2191
2001
  required: [],
@@ -2205,13 +2015,54 @@ var re = () => {
2205
2015
  console.log("");
2206
2016
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
2207
2017
  console.log(`${r} ${n}`);
2208
- }, Q = "voltro-authz-allowlist.txt", Vn = (e, t = (e) => {
2018
+ }, Q = "voltro-authz-allowlist.txt", Pn = (e, t = (e) => {
2209
2019
  try {
2210
2020
  return E(e, "utf8");
2211
2021
  } catch {
2212
2022
  return "";
2213
2023
  }
2214
- }) => /* @__PURE__ */ new Set([...wt, ...Tt(q(e).map(t))]), Hn = async (e) => {
2024
+ }) => /* @__PURE__ */ new Set([..._t, ...vt(q(e).map(t))]), Fn = async (e) => {
2025
+ try {
2026
+ let { discovered: t } = await X(e), n = t.events.map((e) => e.descriptor.name).filter((e) => typeof e == "string");
2027
+ if (n.length === 0) return [];
2028
+ let r = new Set(t.events.map((e) => e.file)), i = q(e).filter((e) => !r.has(e)).map((e) => {
2029
+ try {
2030
+ return E(e, "utf8");
2031
+ } catch {
2032
+ return "";
2033
+ }
2034
+ }).join("\n");
2035
+ return n.filter((e) => !i.includes(e)).sort();
2036
+ } catch {
2037
+ return [];
2038
+ }
2039
+ }, In = async (e) => {
2040
+ let t = await Fn(e);
2041
+ if (t.length !== 0) {
2042
+ console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
2043
+ for (let e of t) console.log(` ${e}`);
2044
+ console.log(""), console.log(" An event you declare is an event partners can subscribe to. One that"), console.log(" never fires is indistinguishable, from their side, from a broken"), console.log(" endpoint of their own — so they debug your contract instead of"), console.log(" reading it."), console.log(" ADVISORY: this looks for the event NAME in your sources. An emit"), console.log(" through a variable or an assembled name will read as missing here.");
2045
+ }
2046
+ }, Ln = async (e) => {
2047
+ try {
2048
+ let { offlineManifest: t, toInput: n } = await import("./checkCommand-ClRvH_SN.js"), { buildAppGraph: r, checkAppGraph: i } = await import("./appGraph-CvQCte0z.js"), a = await t(e);
2049
+ return i(r(n(a.manifest, a.tables))).filter((e) => e.rule === "rbac/unknown-scope").map((e) => ({
2050
+ node: e.node,
2051
+ ...e.file === void 0 ? {} : { file: e.file },
2052
+ detail: e.breaks.join("; "),
2053
+ fix: e.fix
2054
+ }));
2055
+ } catch {
2056
+ return [];
2057
+ }
2058
+ }, Rn = async (e) => {
2059
+ let t = await Ln(e);
2060
+ if (t.length !== 0) {
2061
+ console.log(""), console.log(`• ${t.length} procedure(s) require a scope no declared role grants:`);
2062
+ for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
2063
+ console.log(""), console.log(" Each of these is uncallable by every user in every role — not"), console.log(" misconfigured, DEAD — and nothing fails at boot to say so. The"), console.log(" usual cause is a typo or a rename that moved one side only."), console.log(` ${t[0].fix}`);
2064
+ }
2065
+ }, zn = async (e) => {
2215
2066
  let t = ["src/locales", "locales"], n, r = [];
2216
2067
  for (let i of t) {
2217
2068
  let t = x(e, i);
@@ -2223,23 +2074,23 @@ var re = () => {
2223
2074
  if (n === void 0 || r.length === 0) return;
2224
2075
  let i = [];
2225
2076
  try {
2226
- let { loadApiConfig: t } = await import("./dev-tPcOmjPJ.js");
2077
+ let { loadApiConfig: t } = await import("./dev-DOZVMVfi.js");
2227
2078
  i = (await t(e))?.locales ?? [];
2228
2079
  } catch {
2229
2080
  return;
2230
2081
  }
2231
- let a = xt({
2082
+ let a = mt({
2232
2083
  catalogCodes: r,
2233
2084
  declaredLocales: i
2234
2085
  });
2235
2086
  if (a !== void 0) {
2236
2087
  console.log("");
2237
- for (let e of St(a, n)) console.log(e);
2088
+ for (let e of ht(a, n)) console.log(e);
2238
2089
  }
2239
- }, Un = async (e) => {
2240
- let { discovered: t } = await Z(e), n = [];
2090
+ }, Bn = async (e) => {
2091
+ let { discovered: t } = await X(e), n = [];
2241
2092
  try {
2242
- let { loadApiConfig: t } = await import("./dev-tPcOmjPJ.js");
2093
+ let { loadApiConfig: t } = await import("./dev-DOZVMVfi.js");
2243
2094
  n = ((await t(e))?.plugins ?? []).map((e) => {
2244
2095
  let t = (e.extendSchema?.tables ?? []).map((e) => {
2245
2096
  let t = e;
@@ -2255,8 +2106,8 @@ var re = () => {
2255
2106
  plugin: e.name,
2256
2107
  tables: t,
2257
2108
  rpcTags: n,
2258
- canDisableTables: Wn.has(e.name),
2259
- canAlias: Gn.has(e.name)
2109
+ canDisableTables: Vn.has(e.name),
2110
+ canAlias: Hn.has(e.name)
2260
2111
  };
2261
2112
  });
2262
2113
  } catch {
@@ -2268,14 +2119,14 @@ var re = () => {
2268
2119
  ...t.mutations,
2269
2120
  ...t.actions,
2270
2121
  ...t.streams ?? []
2271
- ].map((e) => String(e.descriptor.name)), i = Rt(n, {
2122
+ ].map((e) => String(e.descriptor.name)), i = jt(n, {
2272
2123
  tables: t.tables.map((e) => e.tableName),
2273
2124
  rpcTags: r
2274
2125
  });
2275
2126
  console.log();
2276
- for (let e of zt(n, i)) console.log(e);
2277
- }, Wn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Gn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Kn = async (e) => {
2278
- let t = await qn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
2127
+ for (let e of Mt(n, i)) console.log(e);
2128
+ }, Vn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Hn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Un = async (e) => {
2129
+ let t = await Wn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
2279
2130
  return {
2280
2131
  executors: t.executorCount,
2281
2132
  guardVocabulary: [...t.vocabulary].sort(),
@@ -2287,15 +2138,15 @@ var re = () => {
2287
2138
  },
2288
2139
  unchecked: t.result.unchecked
2289
2140
  };
2290
- }, qn = async (e) => {
2291
- let { discovered: t } = await Z(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2141
+ }, Wn = async (e) => {
2142
+ let { discovered: t } = await X(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2292
2143
  if (!n.has(e)) try {
2293
2144
  n.set(e, E(e, "utf8"));
2294
2145
  } catch {
2295
2146
  n.set(e, "");
2296
2147
  }
2297
2148
  return n.get(e);
2298
- }, i = Vn(e, r), a = /* @__PURE__ */ new Set();
2149
+ }, i = Pn(e, r), a = /* @__PURE__ */ new Set();
2299
2150
  for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
2300
2151
  let e = t.references?.();
2301
2152
  e && a.add(e.tableName);
@@ -2332,7 +2183,7 @@ var re = () => {
2332
2183
  l = !0;
2333
2184
  break;
2334
2185
  }
2335
- let u = jt({
2186
+ let u = wt({
2336
2187
  executors: o,
2337
2188
  guardVocabulary: i,
2338
2189
  tenantScopedTables: t.tenantScopedTables,
@@ -2349,44 +2200,49 @@ var re = () => {
2349
2200
  executorCount: o.length,
2350
2201
  allowPath: s
2351
2202
  };
2352
- }, Jn = async (e, t) => {
2353
- let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await qn(e), l = n.counts;
2203
+ }, Gn = async (e, t) => {
2204
+ let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Wn(e), l = n.counts;
2354
2205
  console.log(`\nauthz scan · ${o} executor(s)`);
2355
- let u = [...r].filter((e) => !wt.has(e)).sort();
2206
+ let u = [...r].filter((e) => !_t.has(e)).sort();
2356
2207
  console.log(u.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${u.length} from your source (${u.slice(0, 6).join(", ")}${u.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(l.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(l.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(l.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(l.vocabularyGuard).padStart(4)}`);
2357
2208
  let d = new Set(a.map((e) => e.tag)), f = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !d.has(e.tag)).length, p = l.allowlisted - f;
2358
2209
  if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Q})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
2359
- let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ft(e, t);
2360
- return ne(s, Pt(e, t), "utf8"), console.log(`\n${Q}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
2210
+ let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ot(e, t);
2211
+ return se(s, Dt(e, t), "utf8"), console.log(`\n${Q}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
2361
2212
  }
2362
2213
  if (n.unchecked.length === 0) return !1;
2363
2214
  console.log("");
2364
2215
  for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
2365
2216
  return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Q} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Q}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
2366
- }, Yn = async (e) => {
2367
- if (e.includes("--help") || e.includes("-h")) return console.log(kn), 0;
2368
- let t = e.includes("--strict"), n = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
2369
- if (ie(e)) return re(), M({
2370
- ...un(n),
2371
- spaCandidates: await xn(n),
2372
- pageConvention: Y(n),
2373
- fileTaxonomy: V(n).findings,
2374
- junctionFks: (await Nn(n))?.issues ?? [],
2375
- eventDelivery: await Fn(n),
2376
- duplicateInstances: yt(n),
2377
- serverOnly: (await Rn(n)).status,
2378
- authz: await Kn(n)
2217
+ }, Kn = async (e) => {
2218
+ if (e.includes("--help") || e.includes("-h")) return console.log(Cn), 0;
2219
+ let n = e.includes("--strict"), r = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
2220
+ if (le(e)) return ce(), M({
2221
+ ...nn(r),
2222
+ spaCandidates: await mn(r),
2223
+ pageConvention: J(r),
2224
+ fileTaxonomy: V(r).findings,
2225
+ junctionFks: (await Z(r))?.issues ?? [],
2226
+ eventDelivery: await On(r),
2227
+ duplicateInstances: t(r),
2228
+ versionSkew: s(r) ?? null,
2229
+ serverOnly: (await jn(r)).status,
2230
+ authz: await Un(r),
2231
+ ungrantedScopes: await Ln(r),
2232
+ neverEmittedEvents: await Fn(r)
2379
2233
  }), 0;
2380
- let r = Ut(n, t);
2381
- gn(n), hn(n), On(n), He(n), await Cn(n), await Bn(n), await jn(n), await Pn(n), await In(n), await Un(n), await Hn(n);
2382
- let i = await zn(n), a = await Jn(n, e.includes("--write-authz-allowlist")), o = bt(yt(n));
2383
- return o !== void 0 && console.log(`\n${o}`), i || a ? Math.max(r, 1) : r;
2384
- }, Xn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Zn = /* @__PURE__ */ new Set([
2234
+ let a = It(r, n);
2235
+ ln(r), cn(r), Sn(r), He(r), await gn(r), await Nn(r), await Tn(r), await Dn(r), await kn(r), await Bn(r), await Rn(r), await In(r), await zn(r);
2236
+ let c = await Mn(r), l = await Gn(r, e.includes("--write-authz-allowlist")), u = o(s(r));
2237
+ u !== void 0 && (console.log(""), console.log(u));
2238
+ let d = i(t(r));
2239
+ return d !== void 0 && console.log(`\n${d}`), c || l ? Math.max(a, 1) : a;
2240
+ }, qn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Jn = /* @__PURE__ */ new Set([
2385
2241
  "insert",
2386
2242
  "update",
2387
2243
  "delete",
2388
2244
  "upsert"
2389
- ]), Qn = (e, t) => {
2245
+ ]), Yn = (e, t) => {
2390
2246
  let n = [], r = new Set(t.exercised), i = /* @__PURE__ */ new Map();
2391
2247
  for (let e of t.edges) {
2392
2248
  let t = i.get(e.tag);
@@ -2398,8 +2254,8 @@ var re = () => {
2398
2254
  a.push(t.tag);
2399
2255
  continue;
2400
2256
  }
2401
- let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(Xn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
2402
- for (let t of c) if (Zn.has(t.op)) {
2257
+ let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(qn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
2258
+ for (let t of c) if (Jn.has(t.op)) {
2403
2259
  let r = u.get(t.table);
2404
2260
  r === void 0 ? n.push({
2405
2261
  rule: "observed/undeclared-write",
@@ -2455,7 +2311,7 @@ var re = () => {
2455
2311
  total: e.length
2456
2312
  }
2457
2313
  };
2458
- }, $n = (e, t = [], n = 0) => {
2314
+ }, Xn = (e, t = [], n = 0) => {
2459
2315
  if (n > 12) return t;
2460
2316
  let r;
2461
2317
  try {
@@ -2473,30 +2329,30 @@ var re = () => {
2473
2329
  ".git",
2474
2330
  "coverage"
2475
2331
  ].includes(i)) continue;
2476
- let r = `${e}/${i}`, o;
2332
+ let r = `${e}/${i}`, a;
2477
2333
  try {
2478
- o = O(r);
2334
+ a = O(r);
2479
2335
  } catch {
2480
2336
  continue;
2481
2337
  }
2482
- o.isDirectory() ? $n(r, t, n + 1) : o.isFile() && a.test(i) && t.push(r);
2338
+ a.isDirectory() ? Xn(r, t, n + 1) : a.isFile() && l.test(i) && t.push(r);
2483
2339
  }
2484
2340
  return t;
2485
- }, er = /* @__PURE__ */ new Set(["actors", "tenants"]), tr = async (e) => {
2341
+ }, Zn = /* @__PURE__ */ new Set(["actors", "tenants"]), Qn = async (e) => {
2486
2342
  try {
2487
2343
  let { readFile: t } = await import("node:fs/promises"), { join: n } = await import("node:path"), r = await t(n(e, "app.graph.observed.generated.json"), "utf8"), i = JSON.parse(r);
2488
2344
  return i?.version !== 1 || !Array.isArray(i.edges) || !Array.isArray(i.exercised) ? null : i;
2489
2345
  } catch {
2490
2346
  return null;
2491
2347
  }
2492
- }, nr = (e) => {
2348
+ }, $n = (e) => {
2493
2349
  let { exercised: t, total: n } = e.coverage, r = `\nobserved: ${t}/${n} procedures exercised (${n === 0 ? 0 : Math.round(t / n * 100)}%)`;
2494
2350
  if (e.diagnostics.length === 0) return `${r}\n no declared/observed mismatches${e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : ""}`;
2495
2351
  let i = e.diagnostics.map((e) => ` ${e.node}${e.file === void 0 ? "" : ` (${e.file})`}\n` + e.breaks.map((e) => ` ${e}`).join("\n") + `\n fix: ${e.fix}`), a = e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : "";
2496
2352
  return `${r}\n${i.join("\n")}${a}`;
2497
- }, rr = (e, t) => ({
2353
+ }, er = (e, t) => ({
2498
2354
  tables: (t.tables ?? []).map((e) => e.name),
2499
- frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || er.has(e.name)).map((e) => e.name),
2355
+ frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || Zn.has(e.name)).map((e) => e.name),
2500
2356
  scopes: e.scopes ?? [],
2501
2357
  procedures: (e.procedures ?? []).map((e) => {
2502
2358
  let t = (e.guards ?? []).flatMap((e) => e.kind === "scope" ? e.scope ?? [] : []);
@@ -2509,11 +2365,11 @@ var re = () => {
2509
2365
  ...e.guards === void 0 ? {} : { hasGuards: e.guards.length > 0 }
2510
2366
  };
2511
2367
  })
2512
- }), ir = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
2368
+ }), tr = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
2513
2369
  let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
2514
2370
  return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
2515
- }).join("\n"), ar = async (e) => {
2516
- let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-tPcOmjPJ.js"), { buildRpcEntry: i } = await import("./manifestBuild-Dj8Jjoto.js"), { buildCapabilityManifest: a } = await import("./inspect-mmBuRXmy.js"), { relative: o } = await import("node:path"), s = await n(await t(e)), c = (t, n, r) => i(t, n, o(e, r)), l = [
2371
+ }).join("\n"), nr = async (e) => {
2372
+ let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-DOZVMVfi.js"), { buildRpcEntry: i } = await import("./manifestBuild-Dj8Jjoto.js"), { buildCapabilityManifest: a } = await import("./inspect-mmBuRXmy.js"), { relative: o } = await import("node:path"), s = await n(await t(e)), c = (t, n, r) => i(t, n, o(e, r)), l = [
2517
2373
  ...s.queries.map((e) => c(e.descriptor, "query", e.file)),
2518
2374
  ...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
2519
2375
  ...s.actions.map((e) => c(e.descriptor, "action", e.file)),
@@ -2529,10 +2385,10 @@ var re = () => {
2529
2385
  })), [], m),
2530
2386
  tables: { tables: p }
2531
2387
  };
2532
- }, or = async (e) => {
2533
- let t = ue(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
2388
+ }, rr = async (e) => {
2389
+ let t = me(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
2534
2390
  if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
2535
- let { process: n, target: r, rest: i } = de(t.rest), a;
2391
+ let { process: n, target: r, rest: i } = he(t.rest), a;
2536
2392
  for (let e = 0; e < i.length; e++) if (i[e] === "--diff") {
2537
2393
  let [t, n] = (i[++e] ?? "").split(":");
2538
2394
  if (t === "removeTable" && n) a = {
@@ -2549,35 +2405,35 @@ var re = () => {
2549
2405
  };
2550
2406
  else return console.error("voltro check --diff: expected removeTable:<name> | removeProcedure:<tag> | removeScope:<name>"), 1;
2551
2407
  }
2552
- let s = i.find((e) => !e.startsWith("-")) ?? process.cwd(), c = i.includes("--offline"), l = c ? [] : await le(n, r), u, d;
2553
- if (l.length === 0) {
2554
- if (!c && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
2408
+ let o = i.find((e) => !e.startsWith("-")) ?? process.cwd(), s = i.includes("--offline"), c = s ? [] : await pe(n, r), l, d;
2409
+ if (c.length === 0) {
2410
+ if (!s && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
2555
2411
  try {
2556
- let e = await ar(C(s));
2557
- u = e.manifest, d = e.tables;
2412
+ let e = await nr(C(o));
2413
+ l = e.manifest, d = e.tables;
2558
2414
  } catch (e) {
2559
2415
  return console.error(`voltro check: could not assemble the graph from source: ${e.message}`), console.error(" Start `voltro dev` to check against a running api instead."), 1;
2560
2416
  }
2561
2417
  } else {
2562
- let e = l[0], [t, n] = await Promise.all([N(e, "/_voltro/inspect/manifest"), N(e, "/_voltro/inspect/data/tables")]);
2418
+ let e = c[0], [t, n] = await Promise.all([N(e, "/_voltro/inspect/manifest"), N(e, "/_voltro/inspect/data/tables")]);
2563
2419
  if (t.kind === "err") return console.error(`voltro check: manifest fetch failed: ${t.error}`), 1;
2564
- u = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
2420
+ l = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
2565
2421
  }
2566
- let f = rr(u, d), p = he(f, a), m = [...V(s).findings.map((e) => ({
2422
+ let f = er(l, d), p = ie(f, a), m = [...V(o).findings.map((e) => ({
2567
2423
  rule: e.rule,
2568
2424
  node: e.file,
2569
2425
  file: e.file,
2570
2426
  breaks: [e.detail],
2571
2427
  fix: e.fix,
2572
2428
  severity: e.severity
2573
- })), ...Y(s).map((e) => ({
2429
+ })), ...J(o).map((e) => ({
2574
2430
  rule: `page/${e.kind}`,
2575
2431
  node: e.file,
2576
2432
  file: e.file,
2577
2433
  breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
2578
2434
  fix: e.kind === "suffixed-outside-pages" ? "move it under src/pages/ into the directory whose route it serves, or rename it — page.tsx is reserved" : "write the test with the file — the name is derivable",
2579
2435
  severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
2580
- }))], h = (await o(s, $n(s))).map((e) => ({
2436
+ }))], h = (await u(o, Xn(o))).map((e) => ({
2581
2437
  rule: "client/not-browser-safe",
2582
2438
  node: e.file,
2583
2439
  file: e.file,
@@ -2588,7 +2444,7 @@ var re = () => {
2588
2444
  ...p.diagnostics,
2589
2445
  ...m,
2590
2446
  ...h
2591
- ], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await tr(C(s)), y = v === null ? null : Qn(f.procedures, v);
2447
+ ], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await Qn(C(o)), y = v === null ? null : Yn(f.procedures, v);
2592
2448
  return t.format === "json" ? M(y === null ? {
2593
2449
  ...p,
2594
2450
  ok: _,
@@ -2598,7 +2454,7 @@ var re = () => {
2598
2454
  ok: _,
2599
2455
  diagnostics: g,
2600
2456
  observed: y
2601
- }) : (console.log(ir(g)), y !== null && console.log(nr(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
2457
+ }) : (console.log(tr(g)), y !== null && console.log($n(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
2602
2458
  };
2603
2459
  //#endregion
2604
- export { re as _, ze as a, I as c, N as d, ue as f, le as g, ce as h, Ut as i, ve as l, se as m, or as n, be as o, de as p, Yn as r, xe as s, ar as t, he as u, M as v, ie as y };
2460
+ export { ce as _, It as a, xe as c, N as d, me as f, pe as g, fe as h, Kn as i, I as l, P as m, rr as n, ze as o, he as p, er as r, be as s, nr as t, ve as u, M as v, le as y };