@voltro/cli 0.30.2 → 0.32.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 +278 -0
  2. package/dist/{apiBuild-BUS1RfPi.js → apiBuild-C4uEbs3s.js} +2 -2
  3. package/dist/apiBuild-OJEjtwcn.js +2 -0
  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-CwMrzAgV.js +2 -0
  8. package/dist/{checkCommand-D_wdOFhw.js → checkCommand-dm7OHtPt.js} +401 -505
  9. package/dist/{commands-BgGAUcku.js → commands-C0nEePif.js} +2601 -2498
  10. package/dist/dbCommand-By__Ev0R.js +2 -0
  11. package/dist/{dbCommand-CbPRCD1x.js → dbCommand-ifOMafuG.js} +112 -84
  12. package/dist/{dev-GAOIot9G.js → dev-5ficNnvF.js} +1843 -1832
  13. package/dist/{dev-tPcOmjPJ.js → dev-rc3fwPSZ.js} +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/{inspectMetrics-BM1vrsga.js → inspectMetrics-BqO4E9G0.js} +1008 -719
  16. package/dist/{sdkgen-BV4fFucu.js → sdkgen-B_5mHQS2.js} +1 -1
  17. package/dist/{serveCommand-ClqXCX1y.js → serveCommand-PxMmn96o.js} +454 -454
  18. package/dist/serveEntry.js +2 -2
  19. package/dist/{start-CTnNvKlk.js → start-D1-8eKrO.js} +204 -199
  20. package/dist/startEntry.js +2 -2
  21. package/package.json +417 -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 +348 -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 +21 -1
  70. package/templates/agent-docs/whats-new.md +137 -22
  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,32 @@
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-BqO4E9G0.js";
2
+ import { Ht as l, Ut as u } from "./dev-5ficNnvF.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-C4uEbs3s.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 { pathToFileURL as oe } from "node:url";
8
+ import { routeDiagnosticsToStderr as se } from "@voltro/logger";
9
+ import { existsSync as T, readFileSync as E, readdirSync as D, statSync as O, writeFileSync as ce } from "node:fs";
8
10
  import { Node as k, Project as A, SyntaxKind as j } from "ts-morph";
9
11
  //#region src/cliOutput.ts
10
- var re = () => {
11
- te();
12
+ var le = () => {
13
+ se();
12
14
  }, M = (e, t = {}) => {
13
15
  console.log(JSON.stringify(e, null, t.pretty === !1 ? 0 : 2));
14
- }, ie = (e) => {
16
+ }, ue = (e) => {
15
17
  for (let t = 0; t < e.length; t++) {
16
18
  let n = e[t];
17
19
  if (n === "--json" || n === "--format=json" || n === "--format" && e[t + 1] === "json") return !0;
18
20
  }
19
21
  return !1;
20
- }, ae = (e, t, n = !1) => {
22
+ }, de = (e, t, n = !1) => {
21
23
  let r = { ...e ?? {} }, i = t ?? process.env.VOLTRO_INSPECT_TOKEN;
22
24
  if (i !== void 0 && i !== "" && (r.authorization = `Bearer ${i}`), n) {
23
25
  let e = process.env.VOLTRO_INSPECT_WRITE_TOKEN;
24
26
  e !== void 0 && e !== "" && (r["x-voltro-inspect-write"] = e);
25
27
  }
26
28
  return r;
27
- }, oe = /* @__PURE__ */ new Set([
29
+ }, fe = /* @__PURE__ */ new Set([
28
30
  "POST",
29
31
  "PUT",
30
32
  "PATCH",
@@ -34,7 +36,7 @@ var re = () => {
34
36
  try {
35
37
  let t = await fetch(r, {
36
38
  method: i,
37
- headers: ae(a ? { "content-type": "application/json" } : void 0, e.inspectToken, oe.has(i)),
39
+ headers: de(a ? { "content-type": "application/json" } : void 0, e.inspectToken, fe.has(i)),
38
40
  ...a ? { body: JSON.stringify(n.body) } : {}
39
41
  }), o = await t.text(), s;
40
42
  if (o.length > 0) try {
@@ -43,7 +45,7 @@ var re = () => {
43
45
  s = o;
44
46
  }
45
47
  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);
48
+ let n = s, r = typeof n == "object" && n ? String(n.message ?? n.reason ?? n.error ?? `HTTP ${t.status}`) : `HTTP ${t.status}`, a = fe.has(i) && (process.env.VOLTRO_INSPECT_WRITE_TOKEN ?? "") === "" && /write/i.test(r);
47
49
  return {
48
50
  kind: "err",
49
51
  name: e.name,
@@ -63,7 +65,7 @@ var re = () => {
63
65
  error: t.message || String(t)
64
66
  };
65
67
  }
66
- }, se = (e, t = process.env) => {
68
+ }, P = (e, t = process.env) => {
67
69
  let n = [], r, i;
68
70
  for (let t = 0; t < e.length; t++) {
69
71
  let a = e[t];
@@ -108,7 +110,7 @@ var re = () => {
108
110
  },
109
111
  rest: n
110
112
  };
111
- }, ce = (e) => ({
113
+ }, pe = (e) => ({
112
114
  id: `remote:${e.url}`,
113
115
  name: e.name,
114
116
  kind: "api",
@@ -119,7 +121,7 @@ var re = () => {
119
121
  status: "live",
120
122
  logFile: "",
121
123
  ...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) => {
124
+ }), me = 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)) : [pe(n)], he = (e) => {
123
125
  let t = "pretty", n = [];
124
126
  for (let r = 0; r < e.length; r++) {
125
127
  let i = e[r];
@@ -143,8 +145,8 @@ var re = () => {
143
145
  format: t,
144
146
  rest: n
145
147
  };
146
- }, de = (e, t = process.env) => {
147
- let { target: n, rest: r, error: i } = se(e, t), a, o = [];
148
+ }, ge = (e, t = process.env) => {
149
+ let { target: n, rest: r, error: i } = P(e, t), a, o = [];
148
150
  for (let e = 0; e < r.length; e++) {
149
151
  let t = r[e];
150
152
  if (t === "--process") {
@@ -163,113 +165,7 @@ var re = () => {
163
165
  rest: o,
164
166
  ...i === void 0 ? {} : { error: i }
165
167
  };
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
- }, ge = (e) => {
168
+ }, _e = (e) => {
273
169
  let t = "", n = !1;
274
170
  for (let r = 0; r < e.length; r++) {
275
171
  let i = e[r];
@@ -297,8 +193,8 @@ var re = () => {
297
193
  }
298
194
  t += i;
299
195
  }
300
- return JSON.parse(_e(t));
301
- }, _e = (e) => {
196
+ return JSON.parse(ve(t));
197
+ }, ve = (e) => {
302
198
  let t = "", n = !1;
303
199
  for (let r = 0; r < e.length; r++) {
304
200
  let i = e[r];
@@ -326,7 +222,7 @@ var re = () => {
326
222
  t.add(e);
327
223
  let n;
328
224
  try {
329
- n = ge(E(e, "utf8"));
225
+ n = _e(E(e, "utf8"));
330
226
  } catch {
331
227
  return;
332
228
  }
@@ -342,8 +238,8 @@ var re = () => {
342
238
  ...n.compilerOptions?.paths
343
239
  }
344
240
  } };
345
- }, 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
+ }, ye = (e, t = "tsconfig.json") => {
242
+ let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
347
243
  if (i === void 0) return;
348
244
  let a = {};
349
245
  for (let [e, t] of Object.entries(i)) Array.isArray(t) && (a[e] = t.filter((e) => typeof e == "string"));
@@ -352,7 +248,7 @@ var re = () => {
352
248
  paths: a
353
249
  };
354
250
  }, I = (e, t = "tsconfig.json") => {
355
- let n = ee(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
251
+ let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
356
252
  if (i === void 0) return [];
357
253
  let a = C(b(n), r?.compilerOptions?.baseUrl ?? "."), o = [];
358
254
  for (let [e, t] of Object.entries(i)) {
@@ -366,11 +262,11 @@ var re = () => {
366
262
  });
367
263
  }
368
264
  return o.sort((e, t) => t.prefix.length - e.prefix.length);
369
- }, ye = (e, t) => {
265
+ }, be = (e, t) => {
370
266
  for (let n of t) if (n.wildcard) {
371
267
  if (e.startsWith(n.prefix)) return x(n.target, e.slice(n.prefix.length));
372
268
  } else if (e === n.prefix) return n.target;
373
- }, L = ".voltro-vendored", be = (e, t) => {
269
+ }, L = ".voltro-vendored", xe = (e, t) => {
374
270
  let n = [], r = /* @__PURE__ */ new Set(), i = (e, t) => {
375
271
  let i = C(e);
376
272
  r.has(i) || !T(i) || (r.add(i), n.push({
@@ -401,48 +297,48 @@ var re = () => {
401
297
  }
402
298
  }
403
299
  return n;
404
- }, 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 = [
300
+ }, Se = (e, t) => t.some((t) => e === t.dir || e.startsWith(`${t.dir}${w}`)), Ce = (e, t) => t.map((t) => `${S(e, t.dir) || "."} (${t.source})`), we = [
405
301
  {
406
302
  id: "component.ui",
407
- re: p,
303
+ re: _,
408
304
  needsTest: !0
409
305
  },
410
306
  {
411
307
  id: "component",
412
- re: g,
308
+ re: ee,
413
309
  needsTest: !0
414
310
  },
415
311
  {
416
312
  id: "hook",
417
- re: _,
313
+ re: te,
418
314
  needsTest: !0
419
315
  },
420
316
  {
421
317
  id: "tracking",
422
- re: c,
318
+ re: f,
423
319
  needsTest: !0
424
320
  },
425
321
  {
426
322
  id: "store",
427
- re: s,
323
+ re: d,
428
324
  needsTest: !0
429
325
  },
430
326
  {
431
327
  id: "internal",
432
- re: d,
328
+ re: h,
433
329
  needsTest: !0
434
330
  },
435
331
  {
436
332
  id: "fixture",
437
- re: m,
333
+ re: v,
438
334
  needsTest: !1
439
335
  },
440
336
  {
441
337
  id: "types",
442
- re: l,
338
+ re: p,
443
339
  needsTest: !1
444
340
  }
445
- ], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, we = (e) => u.test(e), Te = /* @__PURE__ */ new Set([
341
+ ], R = (e) => we.find((t) => t.re.test(e))?.id ?? null, Te = (e) => m.test(e), Ee = /* @__PURE__ */ new Set([
446
342
  "useMutation",
447
343
  "useAction",
448
344
  "useUpload",
@@ -452,12 +348,12 @@ var re = () => {
452
348
  "useAgentStream",
453
349
  "useResumableAgentStream",
454
350
  "useWorkflow"
455
- ]), Ee = /* @__PURE__ */ new Set(["defineTracking"]), De = /* @__PURE__ */ new Set([
351
+ ]), De = /* @__PURE__ */ new Set(["defineTracking"]), Oe = /* @__PURE__ */ new Set([
456
352
  "useSubscription",
457
353
  "useWindowedSubscription",
458
354
  "useRecord",
459
355
  "useQuery"
460
- ]), Oe = /* @__PURE__ */ new Set([
356
+ ]), ke = /* @__PURE__ */ new Set([
461
357
  "node_modules",
462
358
  "dist",
463
359
  "build",
@@ -468,7 +364,7 @@ var re = () => {
468
364
  ".git",
469
365
  "coverage",
470
366
  ".publish"
471
- ]), ke = /\.tsx?$/, Ae = (e, t = [], n = 0) => {
367
+ ]), Ae = /\.tsx?$/, je = (e, t = [], n = 0) => {
472
368
  if (n > 12) return t;
473
369
  let r;
474
370
  try {
@@ -477,27 +373,27 @@ var re = () => {
477
373
  return t;
478
374
  }
479
375
  for (let i of r) {
480
- if (Oe.has(i)) continue;
376
+ if (ke.has(i)) continue;
481
377
  let r = x(e, i), a;
482
378
  try {
483
379
  a = O(r);
484
380
  } catch {
485
381
  continue;
486
382
  }
487
- a.isDirectory() ? Ae(r, t, n + 1) : a.isFile() && ke.test(i) && !i.endsWith(".d.ts") && t.push(r);
383
+ a.isDirectory() ? je(r, t, n + 1) : a.isFile() && Ae.test(i) && !i.endsWith(".d.ts") && t.push(r);
488
384
  }
489
385
  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());
386
+ }, Me = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, Ne = /\b(?:memo|forwardRef|observer|styled)\b/, Pe = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), z = (e) => {
387
+ if (Pe(e)) return !0;
388
+ if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return z(e.getExpression());
493
389
  if (k.isVariableDeclaration(e)) {
494
390
  let t = e.getTypeNode();
495
- if (t !== void 0 && je.test(t.getText())) return !0;
391
+ if (t !== void 0 && Me.test(t.getText())) return !0;
496
392
  let n = e.getInitializer();
497
- return n === void 0 || B(n);
393
+ return n === void 0 || z(n);
498
394
  }
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) => {
395
+ return k.isCallExpression(e) ? Ne.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => Pe(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);
396
+ }, Fe = (e) => {
501
397
  let t = [], n = [], r = [], i = /* @__PURE__ */ new Set();
502
398
  for (let [a, o] of e.getExportedDeclarations()) {
503
399
  let e = o[0];
@@ -508,7 +404,7 @@ var re = () => {
508
404
  n.push(s);
509
405
  continue;
510
406
  }
511
- if (/^[A-Z]/.test(s) && B(e)) {
407
+ if (/^[A-Z]/.test(s) && z(e)) {
512
408
  t.push(s);
513
409
  continue;
514
410
  }
@@ -520,7 +416,7 @@ var re = () => {
520
416
  others: r,
521
417
  hasRuntimeExport: t.length + n.length + r.length > 0
522
418
  };
523
- }, Pe = [], Fe = (e, t, n) => {
419
+ }, B = [], Ie = (e, t, n) => {
524
420
  let r = b(e), i = [];
525
421
  for (let e = r;; e = b(e)) {
526
422
  let r = n.get(e);
@@ -537,10 +433,10 @@ var re = () => {
537
433
  }
538
434
  if (e === t || b(e) === e) break;
539
435
  }
540
- for (let e of i) n.set(e, Pe);
541
- return Pe;
542
- }, Ie = (e, t, n) => {
543
- let r = t.startsWith(".") ? C(b(e), t) : ye(t, n);
436
+ for (let e of i) n.set(e, B);
437
+ return B;
438
+ }, Le = (e, t, n) => {
439
+ let r = t.startsWith(".") ? C(b(e), t) : be(t, n);
544
440
  if (r === void 0) return null;
545
441
  for (let e of [
546
442
  r,
@@ -552,9 +448,9 @@ var re = () => {
552
448
  if (O(e).isFile()) return e;
553
449
  } catch {}
554
450
  return null;
555
- }, Le = (e, t, n) => {
451
+ }, Re = (e, t, n) => {
556
452
  let r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = (e) => {
557
- let i = Ie(t, e, n);
453
+ let i = Le(t, e, n);
558
454
  return i !== null && r.push(i), i;
559
455
  };
560
456
  for (let t of e.getImportDeclarations()) if (o(t.getModuleSpecifierValue()), !t.isTypeOnly()) for (let e of t.getNamedImports()) e.isTypeOnly() || i.add(e.getName());
@@ -581,29 +477,29 @@ var re = () => {
581
477
  namedBindings: i,
582
478
  reExports: a
583
479
  };
584
- }, Re = (e) => {
480
+ }, ze = (e) => {
585
481
  let t = /* @__PURE__ */ new Map();
586
482
  for (let n of e.getDescendantsOfKind(j.CallExpression)) {
587
483
  let e = n.getExpression(), r = k.isPropertyAccessExpression(e) ? e.getName() : e.getText();
588
484
  t.set(r, (t.get(r) ?? 0) + 1);
589
485
  }
590
486
  return t;
591
- }, ze = (e) => {
487
+ }, Be = (e) => {
592
488
  if (e.length <= 1) return e.length === 1;
593
489
  let t = [...e].sort((e, t) => e.length - t.length)[0];
594
490
  return e.every((e) => e.startsWith(t));
595
- }, Be = (e, t) => {
491
+ }, Ve = (e, t) => {
596
492
  let n = b(t);
597
493
  return e.startsWith(`${n}${w}`) || b(e) === n;
598
494
  }, V = (e) => {
599
- let t = Ae(e);
495
+ let t = je(e);
600
496
  if (t.length === 0) return {
601
497
  findings: [],
602
498
  declared: 0,
603
499
  scanned: 0,
604
500
  vendored: []
605
501
  };
606
- let n = be(e, t), r = t.filter((e) => !xe(e, n));
502
+ let n = xe(e, t), r = t.filter((e) => !Se(e, n));
607
503
  if (r.length === 0) return {
608
504
  findings: [],
609
505
  declared: 0,
@@ -627,14 +523,14 @@ var re = () => {
627
523
  } catch {
628
524
  continue;
629
525
  }
630
- s.set(t, Ne(r)), c.set(t, Le(r, t, Fe(t, e, u))), l.set(t, Re(r));
526
+ s.set(t, Fe(r)), c.set(t, Re(r, t, Ie(t, e, u))), l.set(t, ze(r));
631
527
  }
632
528
  let d = /* @__PURE__ */ new Map();
633
529
  for (let [e, t] of c) for (let n of t.localTargets) {
634
530
  let t = d.get(n) ?? [];
635
531
  t.push(e), d.set(n, t);
636
532
  }
637
- let p = (e) => {
533
+ let f = (e) => {
638
534
  let t = s.get(e), n = /* @__PURE__ */ new Set([
639
535
  ...t?.components ?? [],
640
536
  ...t?.hooks ?? [],
@@ -655,15 +551,15 @@ var re = () => {
655
551
  }
656
552
  };
657
553
  return a(e, null), [...r];
658
- }, m = 0;
554
+ }, p = 0;
659
555
  for (let e of r) {
660
556
  let t = R(e), n = s.get(e), r = c.get(e);
661
557
  if (n === void 0 || r === void 0) continue;
662
- let i = we(e);
663
- t !== null && !i && (m += 1);
558
+ let i = Te(e);
559
+ t !== null && !i && (p += 1);
664
560
  let u = d.get(e) ?? [];
665
561
  if ((t === "component" || t === "component.ui") && !i) {
666
- let t = ze(n.components);
562
+ let t = Be(n.components);
667
563
  (n.components.length === 0 || !t) && a.push({
668
564
  rule: "component/one-per-file",
669
565
  file: o(e),
@@ -701,7 +597,7 @@ var re = () => {
701
597
  ].join(", ")}`,
702
598
  fix: "a *.types.ts has NO runtime export. That is what makes importing it provably free AND makes it impossible for it to sit in a runtime import cycle — the guarantee is the point, so move the value to a sibling module"
703
599
  }), t === "internal") {
704
- let t = u.filter((t) => !Be(t, e));
600
+ let t = u.filter((t) => !Ve(t, e));
705
601
  t.length > 0 && a.push({
706
602
  rule: "internal/foreign-import",
707
603
  file: o(e),
@@ -711,7 +607,7 @@ var re = () => {
711
607
  });
712
608
  }
713
609
  if (t === "fixture") {
714
- let t = u.filter((e) => !we(e) && R(e) !== "fixture");
610
+ let t = u.filter((e) => !Te(e) && R(e) !== "fixture");
715
611
  t.length > 0 && a.push({
716
612
  rule: "fixture/production-import",
717
613
  file: o(e),
@@ -721,7 +617,7 @@ var re = () => {
721
617
  });
722
618
  }
723
619
  if (t === "component.ui" && !i) {
724
- let t = [...r.namedBindings].filter((e) => Te.has(e)).sort();
620
+ let t = [...r.namedBindings].filter((e) => Ee.has(e)).sort();
725
621
  t.length > 0 && a.push({
726
622
  rule: "ui/no-write",
727
623
  file: o(e),
@@ -729,9 +625,9 @@ var re = () => {
729
625
  detail: `imports write hook(s): ${t.join(", ")}`,
730
626
  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
627
  });
732
- let n = p(e), i = n.filter((e) => {
628
+ let n = f(e), i = n.filter((e) => {
733
629
  let t = R(e);
734
- return t === "component" || t === "component.ui" || h.test(e) || f.has(e.slice(e.lastIndexOf(w) + 1));
630
+ return t === "component" || t === "component.ui" || y.test(e) || g.has(e.slice(e.lastIndexOf(w) + 1));
735
631
  });
736
632
  n.length === 0 ? a.push({
737
633
  rule: "ui/orphaned",
@@ -756,7 +652,7 @@ var re = () => {
756
652
  detail: t === 0 ? "declares a store file but calls no defineStore()" : `declares ${t} stores`,
757
653
  fix: "a store file defines exactly ONE store — the SSR seed pass and devtools address stores by name, so two in a file makes both harder to find and neither easier to reason about"
758
654
  });
759
- let n = [...l.get(e)?.keys() ?? []].filter((e) => De.has(e)).sort();
655
+ let n = [...l.get(e)?.keys() ?? []].filter((e) => Oe.has(e)).sort();
760
656
  n.length > 0 && a.push({
761
657
  rule: "store/mirrors-server-state",
762
658
  file: o(e),
@@ -766,7 +662,7 @@ var re = () => {
766
662
  });
767
663
  }
768
664
  if (t !== "tracking" && !i) {
769
- let t = [...l.get(e)?.keys() ?? []].filter((e) => Ee.has(e)).sort();
665
+ let t = [...l.get(e)?.keys() ?? []].filter((e) => De.has(e)).sort();
770
666
  t.length > 0 && a.push({
771
667
  rule: "tracking/outside-tracking-file",
772
668
  file: o(e),
@@ -775,7 +671,7 @@ var re = () => {
775
671
  fix: "move the defineTracking(...) spec into a *.tracking.ts and import it here. The event catalogue — names, property bags, the decisions about what personal data goes out — belongs in one place you can list; calling useTracking with a spec from there is fine"
776
672
  });
777
673
  }
778
- if (Ce.find((e) => e.id === t)?.needsTest === !0 && !i) {
674
+ if (we.find((e) => e.id === t)?.needsTest === !0 && !i) {
779
675
  let t = e.replace(/\.tsx?$/, ""), n = e.endsWith("x") ? "tsx" : "ts";
780
676
  ["test", "spec"].flatMap((e) => [`${t}.${e}.ts`, `${t}.${e}.tsx`]).some((e) => {
781
677
  try {
@@ -794,14 +690,14 @@ var re = () => {
794
690
  }
795
691
  return a.sort((e, t) => e.file === t.file ? e.rule.localeCompare(t.rule) : e.file.localeCompare(t.file)), {
796
692
  findings: a,
797
- declared: m,
693
+ declared: p,
798
694
  scanned: r.length,
799
695
  vendored: n
800
696
  };
801
- }, Ve = 10, He = (e) => {
697
+ }, He = 10, Ue = (e) => {
802
698
  let { findings: t, declared: n, scanned: r, vendored: i } = V(e);
803
699
  console.log("");
804
- for (let t of Se(e, i)) console.log(`· not ours, exempt: ${t}`);
700
+ for (let t of Ce(e, i)) console.log(`· not ours, exempt: ${t}`);
805
701
  if (r === 0) return console.log("• file-taxonomy scan: no sources here — nothing to check."), 0;
806
702
  if (t.length === 0) return console.log(`✓ file-taxonomy scan: ${n} declared file(s) keep their contract (${r} scanned).`), 0;
807
703
  let a = /* @__PURE__ */ new Map();
@@ -813,12 +709,12 @@ var re = () => {
813
709
  console.log(`• file taxonomy: ${t.length} finding(s) — ${o} error, ${t.length - o} advisory (${n} declared / ${r} scanned):`);
814
710
  for (let [e, t] of [...a].sort()) {
815
711
  console.log(` ${t[0].severity === "error" ? "✗" : "·"} ${e} (${t.length})`);
816
- for (let e of t.slice(0, Ve)) console.log(` ${e.file} — ${e.detail}`);
817
- let n = t.length - Ve;
712
+ for (let e of t.slice(0, He)) console.log(` ${e.file} — ${e.detail}`);
713
+ let n = t.length - He;
818
714
  n > 0 && console.log(` (+${n} more — \`voltro doctor --json\` prints every one.)`), console.log(` → ${t[0].fix}`);
819
715
  }
820
716
  return t.length;
821
- }, Ue = /* @__PURE__ */ new Set([
717
+ }, We = /* @__PURE__ */ new Set([
822
718
  "eq",
823
719
  "ne",
824
720
  "gt",
@@ -836,10 +732,10 @@ var re = () => {
836
732
  "endsWith",
837
733
  "between",
838
734
  "jsonField"
839
- ]), We = (e) => {
735
+ ]), Ge = (e) => {
840
736
  if (k.isPropertyAccessExpression(e)) return e.getName();
841
737
  if (k.isIdentifier(e)) return e.getText();
842
- }, Ge = (e) => {
738
+ }, Ke = (e) => {
843
739
  let t = e;
844
740
  for (; k.isCallExpression(t);) {
845
741
  let e = t.getExpression();
@@ -847,7 +743,7 @@ var re = () => {
847
743
  t = e.getExpression();
848
744
  }
849
745
  return t;
850
- }, Ke = (e, t, n) => {
746
+ }, qe = (e, t, n) => {
851
747
  let r = new Map(t.map((e) => [e.name, new Set(e.columns)])), i = new Map(t.map((e) => [e.name, e.columns])), a = [], o = 0, s = new A({
852
748
  useInMemoryFileSystem: !0,
853
749
  skipAddingFilesFromTsConfig: !0
@@ -865,7 +761,7 @@ var re = () => {
865
761
  for (let t of e.getDescendantsOfKind(j.CallExpression)) {
866
762
  let e = t.getExpression();
867
763
  if (!k.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
868
- let n = We(Ge(e.getExpression()));
764
+ let n = Ge(Ke(e.getExpression()));
869
765
  if (n === void 0 || !r.has(n)) {
870
766
  let t = e.getExpression();
871
767
  if (k.isCallExpression(t)) {
@@ -888,7 +784,7 @@ var re = () => {
888
784
  else for (let e of [u, ...u.getDescendantsOfKind(j.CallExpression)]) {
889
785
  if (!k.isCallExpression(e)) continue;
890
786
  let t = e.getExpression(), n = k.isIdentifier(t) ? t.getText() : k.isPropertyAccessExpression(t) ? t.getName() : void 0;
891
- if (n === void 0 || !Ue.has(n)) continue;
787
+ if (n === void 0 || !We.has(n)) continue;
892
788
  let r = e.getArguments()[0];
893
789
  r !== void 0 && k.isStringLiteral(r) && l.push({
894
790
  column: r.getLiteralValue(),
@@ -909,7 +805,7 @@ var re = () => {
909
805
  issues: a,
910
806
  checked: o
911
807
  };
912
- }, H = 12, qe = (e) => {
808
+ }, H = 12, Je = (e) => {
913
809
  if (e.checked === 0) return null;
914
810
  if (e.issues.length === 0) return `predicate columns: ${e.checked} checked — every filtered column exists on its table`;
915
811
  let t = [`predicate columns: ${e.issues.length} filter${e.issues.length, "s"} on a column that does not exist (${e.checked} checked)`];
@@ -918,7 +814,7 @@ var re = () => {
918
814
  t.push(` ${n.file}:${n.line} '${n.table}' has no column '${n.column}'`), t.push(` columns: ${e}${r}`);
919
815
  }
920
816
  return t.push(" This type-checks today and fails at runtime as a bare SQL error."), t.join("\n");
921
- }, Je = /* @__PURE__ */ new Set([
817
+ }, Ye = /* @__PURE__ */ new Set([
922
818
  "id",
923
819
  "tenantId",
924
820
  "tenant_id",
@@ -938,7 +834,7 @@ var re = () => {
938
834
  "idempotency_id",
939
835
  "idempotencyKey",
940
836
  "idempotency_key"
941
- ]), Ye = /* @__PURE__ */ new Set([
837
+ ]), Xe = /* @__PURE__ */ new Set([
942
838
  "id",
943
839
  "createdAt",
944
840
  "created_at",
@@ -948,10 +844,10 @@ var re = () => {
948
844
  "deleted_at",
949
845
  "tenantId",
950
846
  "tenant_id"
951
- ]), Xe = (e) => Je.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Ze = (e) => e.type === "reference", Qe = (e) => {
847
+ ]), Ze = (e) => Ye.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Qe = (e) => e.type === "reference", $e = (e) => {
952
848
  let t = [];
953
849
  for (let n of e) {
954
- let e = n.columns.filter((e) => Xe(e.name)), r = e.filter((e) => !Ze(e)).map((e) => e.name);
850
+ let e = n.columns.filter((e) => Ze(e.name)), r = e.filter((e) => !Qe(e)).map((e) => e.name);
955
851
  if (r.length === 0) continue;
956
852
  let i = new Set(n.primaryKey), a = r.filter((e) => i.has(e));
957
853
  if (n.primaryKey.length >= 2 && a.length > 0) {
@@ -962,7 +858,7 @@ var re = () => {
962
858
  });
963
859
  continue;
964
860
  }
965
- if (e.some(Ze) && e.length >= 2) {
861
+ if (e.some(Qe) && e.length >= 2) {
966
862
  t.push({
967
863
  table: n.name,
968
864
  columns: r,
@@ -970,7 +866,7 @@ var re = () => {
970
866
  });
971
867
  continue;
972
868
  }
973
- e.length >= 2 && n.columns.filter((e) => !Xe(e.name) && !Ye.has(e.name)).length === 0 && t.push({
869
+ e.length >= 2 && n.columns.filter((e) => !Ze(e.name) && !Xe.has(e.name)).length === 0 && t.push({
974
870
  table: n.name,
975
871
  columns: r,
976
872
  signal: "link-only"
@@ -980,20 +876,20 @@ var re = () => {
980
876
  issues: t,
981
877
  tablesChecked: e.length
982
878
  };
983
- }, $e = {
879
+ }, et = {
984
880
  "composite-pk": "part of a composite primary key",
985
881
  "reference-sibling": "sits beside a wired reference() on this table",
986
882
  "link-only": "this table is nothing but link columns"
987
- }, et = (e) => {
883
+ }, tt = (e) => {
988
884
  if (e.tablesChecked === 0) return null;
989
885
  if (e.issues.length === 0) return `junction FKs: ${e.tablesChecked} table${e.tablesChecked === 1 ? "" : "s"} checked — every link column is a wired reference()`;
990
886
  let t = e.issues.length, n = [`junction FKs: ${t} junction table${t === 1 ? "" : "s"} with an id column that is a plain text() and not a reference()`];
991
887
  for (let t of e.issues) {
992
888
  let e = t.columns.map((e) => `'${e}'`).join(", ");
993
- n.push(` '${t.table}': ${e} (${$e[t.signal]})`);
889
+ n.push(` '${t.table}': ${e} (${et[t.signal]})`);
994
890
  }
995
891
  return n.push(" Declare each as reference(() => <table>): the FK is enforced, the column is auto-indexed,"), n.push(" and the relationship becomes walkable (a plain text() id column is an invisible edge)."), n.join("\n");
996
- }, tt = /* @__PURE__ */ new Set(/* @__PURE__ */ "position.positions.move.moved.moving.cursor.pointer.mouse.drag.dragging.hover.frame.frames.tick.ticks.ping.heartbeat.typing.scroll.scrolling.zoom.pan.coord.coords.coordinate.coordinates.telemetry.sensor.sample.samples.viewport".split(".")), nt = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((e) => e.length > 0).map((e) => e.toLowerCase()), rt = (e) => nt(e).some((e) => tt.has(e)), it = (e) => {
892
+ }, nt = /* @__PURE__ */ new Set(/* @__PURE__ */ "position.positions.move.moved.moving.cursor.pointer.mouse.drag.dragging.hover.frame.frames.tick.ticks.ping.heartbeat.typing.scroll.scrolling.zoom.pan.coord.coords.coordinate.coordinates.telemetry.sensor.sample.samples.viewport".split(".")), rt = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((e) => e.length > 0).map((e) => e.toLowerCase()), it = (e) => rt(e).some((e) => nt.has(e)), at = (e) => {
997
893
  let t = [], n = [];
998
894
  for (let r of e) t.push({
999
895
  event: r.name,
@@ -1002,7 +898,7 @@ var re = () => {
1002
898
  event: r.name,
1003
899
  kind: "wide-key",
1004
900
  keyFieldCount: r.keyFieldCount
1005
- }), r.hasWebhook && rt(r.name) && n.push({
901
+ }), r.hasWebhook && it(r.name) && n.push({
1006
902
  event: r.name,
1007
903
  kind: "webhook-high-rate"
1008
904
  });
@@ -1011,26 +907,26 @@ var re = () => {
1011
907
  modes: t,
1012
908
  warnings: n
1013
909
  };
1014
- }, at = (e) => {
910
+ }, ot = (e) => {
1015
911
  if (e.eventsChecked === 0) return null;
1016
912
  let t = e.eventsChecked, n = [`event delivery: ${t} declared event${t === 1 ? "" : "s"} — the mode decides what a MISSING message means`];
1017
913
  for (let t of e.modes) n.push(` '${t.event}': ${t.delivery}`);
1018
914
  return n.push(" each — every delivery matters; a slow subscriber loses the oldest and is TOLD how many (the default)."), n.push(" latest — a newer delivery supersedes a pending one; a slow subscriber gets the current value, told nothing."), n.push(" Two events with identical route/subscriber/buffer numbers mean OPPOSITE things about a drop — the mode is the difference."), n.join("\n");
1019
- }, ot = (e) => {
915
+ }, st = (e) => {
1020
916
  if (e.warnings.length === 0) return null;
1021
917
  let t = e.warnings.length, n = [`events that may not scale as declared: ${t} finding${t === 1 ? "" : "s"}`];
1022
918
  for (let t of e.warnings) t.kind === "wide-key" ? (n.push(` '${t.event}': routing key has ${t.keyFieldCount ?? 0} fields — every field FRAGMENTS the subscriber set (distinct routes are the PRODUCT of the fields’ value spaces).`), n.push(" Check each is an ADDRESS the delivery is decided by (arenaId), not a discriminator the handler reads (gameType) — the latter is payload, not key.")) : (n.push(` '${t.event}': a webhook block on a per-frame-looking event — every publish is N HTTP deliveries per second per subscribed target.`), n.push(" The webhook rate limit DEFERS the excess as pending rows rather than failing, so the symptom is a growing table. Publish a coarser event (a summary / state change) for the outside world."));
1023
919
  return n.push(" WARNING, not a fault — a producer may intend the fan-out. `voltro doctor --json` prints every finding."), n.join("\n");
1024
- }, st = /* @__PURE__ */ new Set([
920
+ }, ct = /* @__PURE__ */ new Set([
1025
921
  "start",
1026
922
  "run",
1027
923
  "child"
1028
- ]), ct = (e) => k.isCallExpression(e) && (() => {
924
+ ]), lt = (e) => k.isCallExpression(e) && (() => {
1029
925
  let t = e.getExpression();
1030
- if (!k.isPropertyAccessExpression(t) || !st.has(t.getName())) return !1;
926
+ if (!k.isPropertyAccessExpression(t) || !ct.has(t.getName())) return !1;
1031
927
  let n = t.getExpression();
1032
928
  return k.isPropertyAccessExpression(n) ? n.getName() === "workflows" : k.isIdentifier(n) && n.getText() === "workflows";
1033
- })(), lt = (e, t, n) => {
929
+ })(), ut = (e, t, n) => {
1034
930
  let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new A({
1035
931
  useInMemoryFileSystem: !0,
1036
932
  skipAddingFilesFromTsConfig: !0
@@ -1046,7 +942,7 @@ var re = () => {
1046
942
  }
1047
943
  let u = S(n, t);
1048
944
  for (let t of e.getDescendantsOfKind(j.CallExpression)) {
1049
- if (!ct(t)) continue;
945
+ if (!lt(t)) continue;
1050
946
  c++;
1051
947
  let e = t.getStartLineNumber(), [n, l] = t.getArguments();
1052
948
  if (n === void 0 || !k.isStringLiteral(n)) {
@@ -1130,7 +1026,7 @@ var re = () => {
1130
1026
  checked: s,
1131
1027
  callSites: c
1132
1028
  };
1133
- }, ut = (e) => {
1029
+ }, dt = (e) => {
1134
1030
  if (e.callSites === 0) return null;
1135
1031
  let t = [];
1136
1032
  t.push(`workflow starts: ${e.checked}/${e.callSites} call sites checked`);
@@ -1143,7 +1039,7 @@ var re = () => {
1143
1039
  for (let n of e.unchecked) t.push(` ${n.file}:${n.line} — ${n.reason}`);
1144
1040
  }
1145
1041
  return e.issues.length === 0 && e.unchecked.length === 0 && t.push(" every call site matches its workflow"), t.join("\n");
1146
- }, dt = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, U = "VOLTRO_FIELD_ENCRYPTION_KEY", ft = (e) => {
1042
+ }, ft = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, U = "VOLTRO_FIELD_ENCRYPTION_KEY", pt = (e) => {
1147
1043
  let t;
1148
1044
  for (let [n, r] of e.sources) if (t === void 0 && /\bfieldEncryption\b/.test(r) && (t = {
1149
1045
  reason: "fieldEncryption",
@@ -1156,7 +1052,7 @@ var re = () => {
1156
1052
  for (let [, t] of e.envFiles) if (t.includes(U)) return;
1157
1053
  return t;
1158
1054
  }
1159
- }, pt = (e) => [
1055
+ }, mt = (e) => [
1160
1056
  `✗ ${U} is named nowhere under this app root, and the app encrypts columns at rest`,
1161
1057
  e.reason === "fieldEncryption" ? ` (\`fieldEncryption\` in ${e.file})` : ` (an \`.encrypted()\` column in ${e.file})`,
1162
1058
  "",
@@ -1173,99 +1069,14 @@ var re = () => {
1173
1069
  " reaches your deploy target is not knowable from here — a secret manager",
1174
1070
  " or a CI variable is not a file. Naming it nowhere is conclusive; naming",
1175
1071
  " 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) => {
1072
+ ].join("\n"), ht = (e) => {
1262
1073
  if (e.catalogCodes.length === 0) return;
1263
1074
  let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
1264
1075
  if (n.length !== 0) return {
1265
1076
  orphaned: n,
1266
1077
  noneDeclared: t.size === 0
1267
1078
  };
1268
- }, St = (e, t) => {
1079
+ }, gt = (e, t) => {
1269
1080
  let n = e.orphaned.join(", ");
1270
1081
  return e.noneDeclared ? [
1271
1082
  `⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
@@ -1277,33 +1088,33 @@ var re = () => {
1277
1088
  " …or delete the files. A catalog that is never loaded looks from the inside",
1278
1089
  " exactly like one that works, which is why this is worth a line."
1279
1090
  ] : [`⚠ ${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([
1091
+ }, _t = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), vt = /* @__PURE__ */ new Set([
1281
1092
  "requireScope",
1282
1093
  "requireAnyScope",
1283
1094
  "requireAuth",
1284
1095
  "assertScope",
1285
1096
  "assertCan",
1286
1097
  "can"
1287
- ]), Tt = (e) => {
1098
+ ]), yt = (e) => {
1288
1099
  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]);
1100
+ for (let n of e) for (let e of _t(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
1290
1101
  return t;
1291
- }, Et = (e, t) => {
1102
+ }, bt = (e, t) => {
1292
1103
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
1293
- }, Dt = (e) => {
1104
+ }, xt = (e) => {
1294
1105
  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
1106
  return t && n;
1296
- }, Ot = (e, t, n) => {
1107
+ }, St = (e, t, n) => {
1297
1108
  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" } : {
1109
+ let r = _t(e.src), i = bt(r, t);
1110
+ return i === void 0 ? xt(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
1300
1111
  kind: "vocabulary-guard",
1301
1112
  name: i
1302
1113
  };
1303
- }, kt = (e, t, n) => {
1114
+ }, Ct = (e, t, n) => {
1304
1115
  let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
1305
1116
  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) => {
1117
+ }, wt = (e, t, n) => {
1307
1118
  switch (t.kind) {
1308
1119
  case "declared-guard": return "guards: on the descriptor";
1309
1120
  case "vocabulary-guard": return `calls ${t.name}()`;
@@ -1311,15 +1122,15 @@ var re = () => {
1311
1122
  case "allowlisted": return "accepted by a previous run";
1312
1123
  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
1124
  }
1314
- }, jt = (e) => {
1125
+ }, Tt = (e) => {
1315
1126
  let t = e.executors.map((t) => {
1316
- let n = Ot(t, e.guardVocabulary, e.allowlist);
1127
+ let n = St(t, e.guardVocabulary, e.allowlist);
1317
1128
  return {
1318
1129
  tag: t.tag,
1319
1130
  path: t.path,
1320
1131
  verdict: n,
1321
- severity: kt(t, e.tenantScopedTables, e.referencedTables),
1322
- why: At(t, n, e.rowFilterRegistered === !0)
1132
+ severity: Ct(t, e.tenantScopedTables, e.referencedTables),
1133
+ why: wt(t, n, e.rowFilterRegistered === !0)
1323
1134
  };
1324
1135
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
1325
1136
  return {
@@ -1333,7 +1144,7 @@ var re = () => {
1333
1144
  },
1334
1145
  unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
1335
1146
  };
1336
- }, Mt = [
1147
+ }, Et = [
1337
1148
  "# voltro doctor — authz allowlist",
1338
1149
  "#",
1339
1150
  "# Two KINDS of line, and the difference is the point:",
@@ -1358,18 +1169,18 @@ var re = () => {
1358
1169
  "# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
1359
1170
  "# lines for executors that are newly unchecked.",
1360
1171
  ""
1361
- ], Nt = (e) => {
1172
+ ], Dt = (e) => {
1362
1173
  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
1174
  return [
1364
- ...Mt,
1175
+ ...Et,
1365
1176
  ...n,
1366
1177
  ...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
1367
1178
  ...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
1368
1179
  ""
1369
1180
  ].join("\n");
1370
- }, Pt = (e, t) => {
1181
+ }, Ot = (e, t) => {
1371
1182
  let n = [...new Set(t)].sort();
1372
- if (e === void 0) return Nt(n.map((e) => ({ tag: e })));
1183
+ if (e === void 0) return Dt(n.map((e) => ({ tag: e })));
1373
1184
  let r = W(e).tags, i = n.filter((e) => !r.has(e));
1374
1185
  return i.length === 0 ? e : [
1375
1186
  e.replace(/\n+$/, ""),
@@ -1379,7 +1190,7 @@ var re = () => {
1379
1190
  ...i,
1380
1191
  ""
1381
1192
  ].join("\n");
1382
- }, Ft = (e, t) => {
1193
+ }, kt = (e, t) => {
1383
1194
  let n = [...new Set(t)];
1384
1195
  if (e === void 0) return {
1385
1196
  added: n.length,
@@ -1392,7 +1203,7 @@ var re = () => {
1392
1203
  kept: r.size,
1393
1204
  removed: 0
1394
1205
  };
1395
- }, It = class extends Error {
1206
+ }, At = class extends Error {
1396
1207
  tag;
1397
1208
  constructor(e) {
1398
1209
  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 +1221,7 @@ var re = () => {
1410
1221
  continue;
1411
1222
  }
1412
1223
  let a = r[2].trim();
1413
- if (a.length === 0) throw new It(i);
1224
+ if (a.length === 0) throw new At(i);
1414
1225
  t.push({
1415
1226
  tag: i,
1416
1227
  reviewed: a
@@ -1420,13 +1231,13 @@ var re = () => {
1420
1231
  tags: new Set(t.map((e) => e.tag)),
1421
1232
  entries: t
1422
1233
  };
1423
- }, Lt = (e) => {
1234
+ }, jt = (e) => {
1424
1235
  let t = e.indexOf(".");
1425
1236
  return t <= 0 ? void 0 : e.slice(0, t);
1426
- }, Rt = (e, t) => {
1237
+ }, Mt = (e, t) => {
1427
1238
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
1428
1239
  for (let e of t.rpcTags) {
1429
- let t = Lt(e);
1240
+ let t = jt(e);
1430
1241
  if (t === void 0) continue;
1431
1242
  let n = i.get(t);
1432
1243
  n ? n.push(e) : i.set(t, [e]);
@@ -1455,7 +1266,7 @@ var re = () => {
1455
1266
  });
1456
1267
  continue;
1457
1268
  }
1458
- let s = new Set(o.rpcTags.map(Lt).filter((e) => e !== void 0));
1269
+ let s = new Set(o.rpcTags.map(jt).filter((e) => e !== void 0));
1459
1270
  for (let e of s) {
1460
1271
  let t = i.get(e);
1461
1272
  t !== void 0 && a.push({
@@ -1468,13 +1279,13 @@ var re = () => {
1468
1279
  }
1469
1280
  }
1470
1281
  return a;
1471
- }, zt = (e, t) => {
1282
+ }, Nt = (e, t) => {
1472
1283
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
1473
1284
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
1474
1285
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
1475
1286
  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);
1287
+ }, Pt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Ft = (e) => {
1288
+ let t = re(e), n = ne(e);
1478
1289
  return {
1479
1290
  root: e,
1480
1291
  serveBundle: t,
@@ -1482,19 +1293,19 @@ var re = () => {
1482
1293
  apiBundle: n,
1483
1294
  apiBundlePresent: T(n)
1484
1295
  };
1485
- }, Ht = [
1296
+ }, It = [
1486
1297
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
1487
1298
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
1488
1299
  "(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;
1300
+ ].join("\n "), Lt = (e, t = !1) => {
1301
+ let n = Ft(e);
1302
+ if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Pt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
1492
1303
  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
1304
  let r = process.env.NODE_ENV === "production";
1494
1305
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
1495
1306
  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) => {
1307
+ return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${It}`), +!!i;
1308
+ }, Rt = (e, t, n) => {
1498
1309
  let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
1499
1310
  `${e}.ts`,
1500
1311
  `${e}.tsx`,
@@ -1514,13 +1325,13 @@ var re = () => {
1514
1325
  }
1515
1326
  let o = [];
1516
1327
  for (let t of r) {
1517
- let n = t.startsWith(".") ? C(b(e), t) : ye(t, i);
1328
+ let n = t.startsWith(".") ? C(b(e), t) : be(t, i);
1518
1329
  if (n === void 0) continue;
1519
1330
  let r = a(n);
1520
1331
  r !== void 0 && o.push(r);
1521
1332
  }
1522
1333
  return o;
1523
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Xt.test(S(n, e))), l = new Set(c);
1334
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Wt.test(S(n, e))), l = new Set(c);
1524
1335
  for (; c.length > 0;) {
1525
1336
  let e = c.shift();
1526
1337
  if (!s.has(e)) {
@@ -1533,11 +1344,11 @@ var re = () => {
1533
1344
  let d = /* @__PURE__ */ new Set();
1534
1345
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
1535
1346
  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) => {
1347
+ }, zt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Bt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Vt = (e, t) => {
1537
1348
  if (e.src.includes(t)) return !0;
1538
1349
  let n = t.replace(/^@voltro\/plugin-/, "");
1539
1350
  return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
1540
- }, Jt = (e, t) => {
1351
+ }, Ht = (e, t) => {
1541
1352
  let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
1542
1353
  for (; (i = r.exec(e)) !== null;) {
1543
1354
  let t = i.index + i[0].length, r = 1, a = t;
@@ -1545,10 +1356,10 @@ var re = () => {
1545
1356
  n.push(e.slice(t, a - 1));
1546
1357
  }
1547
1358
  return n;
1548
- }, Yt = (e, t) => {
1359
+ }, Ut = (e, t) => {
1549
1360
  let n = t.getSymbol()?.getDeclarations()?.[0];
1550
1361
  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 = [
1362
+ }, Wt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Gt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Kt = [
1552
1363
  "Id",
1553
1364
  "ID",
1554
1365
  "Hash",
@@ -1574,12 +1385,12 @@ var re = () => {
1574
1385
  "Comment",
1575
1386
  "Provider",
1576
1387
  "Format"
1577
- ], $t = RegExp(`(?:${Qt.join("|")})$|_(?:${Qt.map((e) => e.toLowerCase()).join("|")})$`), en = [
1388
+ ], qt = RegExp(`(?:${Kt.join("|")})$|_(?:${Kt.map((e) => e.toLowerCase()).join("|")})$`), Jt = [
1578
1389
  {
1579
1390
  id: "hardcoded-framework-cookie",
1580
1391
  scope: "client",
1581
1392
  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",
1393
+ 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
1394
  match: ({ src: e }) => /['"`]voltro:(?:locale|theme|lang)['"`]/.test(e)
1584
1395
  },
1585
1396
  {
@@ -1587,7 +1398,7 @@ var re = () => {
1587
1398
  scope: "server",
1588
1399
  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
1400
  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"))
1401
+ match: ({ src: e, path: t }) => Gt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1591
1402
  },
1592
1403
  {
1593
1404
  id: "subject-write-no-guard",
@@ -1595,8 +1406,8 @@ var re = () => {
1595
1406
  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
1407
  use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
1597
1408
  match: ({ src: e, path: t }) => {
1598
- if (!Zt.test(t)) return !1;
1599
- let n = Gt(e);
1409
+ if (!Gt.test(t)) return !1;
1410
+ let n = zt(e);
1600
1411
  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
1412
  }
1602
1413
  },
@@ -1605,7 +1416,7 @@ var re = () => {
1605
1416
  scope: "server",
1606
1417
  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
1418
  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
1419
+ match: ({ file: e, path: t, serverOnly: n }) => (Wt.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1609
1420
  },
1610
1421
  {
1611
1422
  id: "form",
@@ -1640,7 +1451,7 @@ var re = () => {
1640
1451
  scope: "client",
1641
1452
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
1642
1453
  use: "useDerived — dependency-tracked, referentially stable",
1643
- match: ({ src: e }) => Kt(e, /\buseSubscription\s*[(<]/g) >= 3 && Kt(e, /\buseMemo\s*\(/g) >= 3
1454
+ match: ({ src: e }) => Bt(e, /\buseSubscription\s*[(<]/g) >= 3 && Bt(e, /\buseMemo\s*\(/g) >= 3
1644
1455
  },
1645
1456
  {
1646
1457
  id: "next-shim",
@@ -1665,7 +1476,7 @@ var re = () => {
1665
1476
  match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
1666
1477
  let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
1667
1478
  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) => {
1479
+ }).some((t) => Ut(e, t).some((e) => {
1669
1480
  let t = e.getParent();
1670
1481
  if (k.isBinaryExpression(t)) {
1671
1482
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -1716,7 +1527,7 @@ var re = () => {
1716
1527
  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
1528
  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
1529
  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);
1530
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || qt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1720
1531
  }))
1721
1532
  },
1722
1533
  {
@@ -1726,7 +1537,7 @@ var re = () => {
1726
1537
  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
1538
  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
1539
  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);
1540
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || qt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1730
1541
  }))
1731
1542
  },
1732
1543
  {
@@ -1791,8 +1602,8 @@ var re = () => {
1791
1602
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
1792
1603
  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
1604
  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);
1605
+ let t = zt(e);
1606
+ return Ht(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1796
1607
  })
1797
1608
  },
1798
1609
  {
@@ -1802,14 +1613,14 @@ var re = () => {
1802
1613
  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
1614
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1804
1615
  }
1805
- ], tn = (e) => q(e), nn = (e, t) => {
1616
+ ], Yt = (e) => q(e), Xt = (e, t) => {
1806
1617
  let n = /* @__PURE__ */ new Set();
1807
1618
  for (let r of t) {
1808
1619
  let t = S(e, r), i = t.split(/[\\/]/)[0];
1809
1620
  n.add(i !== void 0 && i !== t ? i : ".");
1810
1621
  }
1811
1622
  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) => {
1623
+ }, Zt = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Qt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, $t = (e, t = [], n = 0) => {
1813
1624
  if (n > 12 || K.test(e)) return t;
1814
1625
  let r;
1815
1626
  try {
@@ -1821,8 +1632,8 @@ var re = () => {
1821
1632
  let r = x(e, i.name);
1822
1633
  if (i.isDirectory()) {
1823
1634
  if (i.name.startsWith(".")) continue;
1824
- on(r, t, n + 1);
1825
- } else dt.test(i.name) && t.push(r);
1635
+ $t(r, t, n + 1);
1636
+ } else ft.test(i.name) && t.push(r);
1826
1637
  }
1827
1638
  return t;
1828
1639
  }, q = (e, t = [], n = 0) => {
@@ -1840,10 +1651,10 @@ var re = () => {
1840
1651
  } catch {
1841
1652
  continue;
1842
1653
  }
1843
- a.isDirectory() ? q(r, t, n + 1) : rn.test(i) && !/\.(test|spec)\./.test(i) && !an.test(i) && t.push(r);
1654
+ a.isDirectory() ? q(r, t, n + 1) : Zt.test(i) && !/\.(test|spec)\./.test(i) && !Qt.test(i) && t.push(r);
1844
1655
  }
1845
1656
  return t;
1846
- }, sn = (e, t = [], n = 0) => {
1657
+ }, en = (e, t = [], n = 0) => {
1847
1658
  if (n > 12 || K.test(e)) return t;
1848
1659
  let r;
1849
1660
  try {
@@ -1858,17 +1669,17 @@ var re = () => {
1858
1669
  } catch {
1859
1670
  continue;
1860
1671
  }
1861
- a.isDirectory() ? sn(r, t, n + 1) : rn.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1672
+ a.isDirectory() ? en(r, t, n + 1) : Zt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1862
1673
  }
1863
1674
  return t;
1864
- }, cn = (e) => {
1865
- let t = tn(e);
1675
+ }, tn = (e) => {
1676
+ let t = Yt(e);
1866
1677
  return {
1867
- dirs: nn(e, t),
1678
+ dirs: Xt(e, t),
1868
1679
  fileCount: t.length
1869
1680
  };
1870
- }, ln = (e) => {
1871
- let t = tn(e);
1681
+ }, nn = (e) => {
1682
+ let t = Yt(e);
1872
1683
  if (t.length === 0) return [];
1873
1684
  let n = new A({
1874
1685
  useInMemoryFileSystem: !0,
@@ -1877,8 +1688,8 @@ var re = () => {
1877
1688
  for (let e of t) try {
1878
1689
  i.set(e, E(e, "utf8"));
1879
1690
  } catch {}
1880
- let a = Wt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1881
- for (let t of sn(e)) try {
1691
+ let a = Rt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1692
+ for (let t of en(e)) try {
1882
1693
  o.set(t, E(t, "utf8"));
1883
1694
  } catch {}
1884
1695
  for (let s of t) {
@@ -1896,23 +1707,23 @@ var re = () => {
1896
1707
  } catch {
1897
1708
  continue;
1898
1709
  }
1899
- for (let t of en) {
1900
- if (t.plugin !== void 0 && qt(c, t.plugin) || !t.match(c)) continue;
1710
+ for (let t of Jt) {
1711
+ if (t.plugin !== void 0 && Vt(c, t.plugin) || !t.match(c)) continue;
1901
1712
  let n = r.get(t.id) ?? [];
1902
1713
  n.push(S(e, s)), r.set(t.id, n);
1903
1714
  }
1904
1715
  }
1905
- return en.filter((e) => r.has(e.id)).map((e) => ({
1716
+ return Jt.filter((e) => r.has(e.id)).map((e) => ({
1906
1717
  rule: e,
1907
1718
  files: r.get(e.id)
1908
1719
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1909
- }, un = (e) => {
1910
- let t = cn(e);
1720
+ }, rn = (e) => {
1721
+ let t = tn(e);
1911
1722
  return {
1912
1723
  root: e,
1913
1724
  scannedFiles: t.fileCount,
1914
1725
  scannedDirs: t.dirs,
1915
- findings: ln(e).map(({ rule: e, files: t }) => ({
1726
+ findings: nn(e).map(({ rule: e, files: t }) => ({
1916
1727
  id: e.id,
1917
1728
  scope: e.scope,
1918
1729
  smell: e.smell,
@@ -1920,22 +1731,22 @@ var re = () => {
1920
1731
  files: t
1921
1732
  }))
1922
1733
  };
1923
- }, dn = 3, fn = .2, pn = 50, mn = (e, t) => e >= pn && t > 0 && e / t >= fn, hn = (e) => {
1734
+ }, an = 3, on = .2, sn = 50, cn = (e, t) => e >= sn && t > 0 && e / t >= on, ln = (e) => {
1924
1735
  let t = /* @__PURE__ */ new Map();
1925
1736
  for (let n of q(e)) try {
1926
1737
  t.set(S(e, n), E(n, "utf8"));
1927
1738
  } catch {}
1928
1739
  let n = /* @__PURE__ */ new Map();
1929
- for (let t of on(e)) try {
1740
+ for (let t of $t(e)) try {
1930
1741
  n.set(S(e, t), E(t, "utf8"));
1931
1742
  } catch {}
1932
- let r = ft({
1743
+ let r = pt({
1933
1744
  sources: t,
1934
1745
  envFiles: n
1935
1746
  });
1936
- r !== void 0 && (console.log(""), console.log(pt(r)));
1937
- }, gn = (e) => {
1938
- let t = un(e);
1747
+ r !== void 0 && (console.log(""), console.log(mt(r)));
1748
+ }, un = (e) => {
1749
+ let t = rn(e);
1939
1750
  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
1751
  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
1752
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
@@ -1946,56 +1757,56 @@ var re = () => {
1946
1757
  console.log(` [${e}]`);
1947
1758
  let i = [...r].sort((e, t) => e.files.length - t.files.length);
1948
1759
  for (let e of i) {
1949
- if (mn(e.files.length, t.scannedFiles)) {
1760
+ if (cn(e.files.length, t.scannedFiles)) {
1950
1761
  let n = Math.round(e.files.length / t.scannedFiles * 100);
1951
1762
  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
1763
  continue;
1953
1764
  }
1954
- let r = e.files.slice(0, dn).join(", "), i = e.files.length - dn;
1765
+ let r = e.files.slice(0, an).join(", "), i = e.files.length - an;
1955
1766
  i > 0 && (n += i);
1956
1767
  let a = i > 0 ? `, +${i} more` : "";
1957
1768
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1958
1769
  }
1959
1770
  }
1960
1771
  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];
1772
+ }, dn = "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.", fn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, pn = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, mn = (e) => {
1773
+ let t = fn.exec(e)?.[1];
1963
1774
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1964
- let n = yn.exec(e)?.[1];
1775
+ let n = pn.exec(e)?.[1];
1965
1776
  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;
1777
+ }, hn = async (e) => {
1778
+ let t = x(e, "src", "pages");
1779
+ if (!T(t)) return [];
1780
+ let { pages: i, dirs: o } = await n(t), s = [];
1781
+ for (let n of i) {
1782
+ if (!a(o, n.dirChain).some((e) => e.hasLayout)) continue;
1783
+ let i;
1973
1784
  try {
1974
- a = E(x(i, e.file), "utf8");
1785
+ i = E(x(t, n.file), "utf8");
1975
1786
  } catch {
1976
1787
  continue;
1977
1788
  }
1978
- if (n.test(a)) continue;
1979
- let c = bn(a);
1789
+ if (r.test(i)) continue;
1790
+ let c = mn(i);
1980
1791
  c !== null && s.push({
1981
- file: S(t, x(i, e.file)),
1982
- pattern: e.pattern,
1792
+ file: S(e, x(t, n.file)),
1793
+ pattern: n.pattern,
1983
1794
  currentMode: c
1984
1795
  });
1985
1796
  }
1986
1797
  return s;
1987
- }, Sn = 10, Cn = async (e) => {
1798
+ }, gn = 10, _n = async (e) => {
1988
1799
  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);
1800
+ let t = await hn(e);
1990
1801
  if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1991
1802
  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)) {
1803
+ for (let e of t.slice(0, gn)) {
1993
1804
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1994
1805
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1995
1806
  }
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([
1807
+ let n = t.length - gn;
1808
+ return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${dn}`), t.length;
1809
+ }, vn = /* @__PURE__ */ new Set([
1999
1810
  "node_modules",
2000
1811
  "dist",
2001
1812
  "build",
@@ -2003,7 +1814,7 @@ var re = () => {
2003
1814
  ".vite",
2004
1815
  ".framework",
2005
1816
  ".git"
2006
- ]), J = (e, t, n) => {
1817
+ ]), yn = (e, t, n) => {
2007
1818
  let r;
2008
1819
  try {
2009
1820
  r = D(e, { withFileTypes: !0 });
@@ -2011,11 +1822,11 @@ var re = () => {
2011
1822
  return;
2012
1823
  }
2013
1824
  for (let i of r) {
2014
- if (wn.has(i.name)) continue;
1825
+ if (vn.has(i.name)) continue;
2015
1826
  let r = x(e, i.name);
2016
- i.isDirectory() ? J(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
1827
+ i.isDirectory() ? yn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
2017
1828
  }
2018
- }, Tn = (e, t) => {
1829
+ }, bn = (e, t) => {
2019
1830
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
2020
1831
  for (let i of t) {
2021
1832
  let t;
@@ -2040,21 +1851,21 @@ var re = () => {
2040
1851
  }
2041
1852
  }
2042
1853
  return n;
2043
- }, En = (e, t) => {
1854
+ }, xn = (e, t) => {
2044
1855
  let n = e.slice(t.length).split(w).slice(0, -1)[0];
2045
1856
  return n !== void 0 && n.startsWith("[");
2046
- }, Y = (e) => {
1857
+ }, J = (e) => {
2047
1858
  let t = [], n = x(e, "src", "pages"), r = x(e, "src");
2048
1859
  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) {
1860
+ let i = [];
1861
+ yn(r, e, i), i.sort();
1862
+ let a = `${S(e, n)}${w}`;
1863
+ bn(e, i);
1864
+ for (let n of i) {
2054
1865
  let r = n.slice(n.lastIndexOf(w) + 1);
2055
- if (n.startsWith(o)) {
1866
+ if (n.startsWith(a)) {
2056
1867
  if (r !== "page.tsx") continue;
2057
- T(x(e, i(n))) || t.push({
1868
+ T(x(e, c(n))) || t.push({
2058
1869
  kind: "page-without-test",
2059
1870
  file: n
2060
1871
  });
@@ -2065,7 +1876,7 @@ var re = () => {
2065
1876
  file: n
2066
1877
  });
2067
1878
  } catch {}
2068
- En(n, o) && t.push({
1879
+ xn(n, a) && t.push({
2069
1880
  kind: "top-level-dynamic-route",
2070
1881
  file: n
2071
1882
  });
@@ -2077,33 +1888,33 @@ var re = () => {
2077
1888
  });
2078
1889
  }
2079
1890
  return t;
2080
- }, Dn = 10, X = (e, t, n) => {
1891
+ }, Sn = 10, Y = (e, t, n) => {
2081
1892
  if (t.length === 0) return;
2082
1893
  console.log(`• ${e} (${t.length}):`);
2083
- for (let e of t.slice(0, Dn)) console.log(` ${e}`);
2084
- let r = t.length - Dn;
1894
+ for (let e of t.slice(0, Sn)) console.log(` ${e}`);
1895
+ let r = t.length - Sn;
2085
1896
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
2086
- }, On = (e) => {
1897
+ }, Cn = (e) => {
2087
1898
  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);
1899
+ let t = J(e);
2089
1900
  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
1901
  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);
1902
+ 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;
1903
+ }, wn = "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.", Tn = /* @__PURE__ */ new Map(), X = (e) => {
1904
+ let t = Tn.get(e);
2094
1905
  if (t !== void 0) return t;
2095
1906
  let n = (async () => {
2096
- let { walk: t, loadDiscovered: n } = await import("./dev-tPcOmjPJ.js"), r = await t(e);
1907
+ let { walk: t, loadDiscovered: n } = await import("./dev-rc3fwPSZ.js"), r = await t(e);
2097
1908
  return {
2098
1909
  files: r,
2099
1910
  discovered: await n(r)
2100
1911
  };
2101
1912
  })();
2102
- return An.set(e, n), n;
2103
- }, jn = async (e) => {
1913
+ return Tn.set(e, n), n;
1914
+ }, En = async (e) => {
2104
1915
  let t;
2105
1916
  try {
2106
- let { files: n, discovered: r } = await Z(e), i = r.tables.map((e) => {
1917
+ let { files: n, discovered: r } = await X(e), i = r.tables.map((e) => {
2107
1918
  let t = e;
2108
1919
  return {
2109
1920
  name: t.tableName,
@@ -2111,12 +1922,12 @@ var re = () => {
2111
1922
  };
2112
1923
  });
2113
1924
  if (i.length === 0) return;
2114
- t = qe(Ke(n, i, e));
1925
+ t = Je(qe(n, i, e));
2115
1926
  } catch {
2116
1927
  return;
2117
1928
  }
2118
1929
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
2119
- }, Mn = (e) => e.map((e) => {
1930
+ }, Dn = (e) => e.map((e) => {
2120
1931
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
2121
1932
  name: e,
2122
1933
  type: typeof t?.type == "string" ? t.type : ""
@@ -2126,22 +1937,22 @@ var re = () => {
2126
1937
  columns: n,
2127
1938
  primaryKey: t.appliedPrimaryKey ?? []
2128
1939
  };
2129
- }), Nn = async (e) => {
1940
+ }), Z = async (e) => {
2130
1941
  try {
2131
- let { discovered: t } = await Z(e);
2132
- return Qe(Mn(t.tables));
1942
+ let { discovered: t } = await X(e);
1943
+ return $e(Dn(t.tables));
2133
1944
  } catch {
2134
1945
  return null;
2135
1946
  }
2136
- }, Pn = async (e) => {
2137
- let t = await Nn(e);
1947
+ }, On = async (e) => {
1948
+ let t = await Z(e);
2138
1949
  if (t === null || t.tablesChecked === 0) return;
2139
- let n = et(t);
1950
+ let n = tt(t);
2140
1951
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
2141
- }, Fn = async (e) => {
1952
+ }, kn = async (e) => {
2142
1953
  try {
2143
- let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await Z(e);
2144
- return it(n.events.map((e) => {
1954
+ let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await X(e);
1955
+ return at(n.events.map((e) => {
2145
1956
  let n = e.descriptor, r = t(n.key);
2146
1957
  return {
2147
1958
  name: n.name,
@@ -2153,19 +1964,19 @@ var re = () => {
2153
1964
  } catch {
2154
1965
  return null;
2155
1966
  }
2156
- }, In = async (e) => {
2157
- let t = await Fn(e);
1967
+ }, An = async (e) => {
1968
+ let t = await kn(e);
2158
1969
  if (t === null || t.eventsChecked === 0) return;
2159
- let n = at(t);
1970
+ let n = ot(t);
2160
1971
  n !== null && (console.log(""), console.log(`• ${n}`));
2161
- let r = ot(t);
1972
+ let r = st(t);
2162
1973
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
2163
- }, Ln = (e) => {
1974
+ }, jn = (e) => {
2164
1975
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
2165
1976
  return r === void 0 ? n : `${r}: ${n}`;
2166
- }, Rn = async (e) => {
1977
+ }, Mn = async (e) => {
2167
1978
  try {
2168
- let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await Z(e)).discovered.serverOnlyLeaks, r = t(n);
1979
+ let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await X(e)).discovered.serverOnlyLeaks, r = t(n);
2169
1980
  return {
2170
1981
  status: {
2171
1982
  checked: !0,
@@ -2176,17 +1987,17 @@ var re = () => {
2176
1987
  } catch (e) {
2177
1988
  return { status: {
2178
1989
  checked: !1,
2179
- reason: Ln(e)
1990
+ reason: jn(e)
2180
1991
  } };
2181
1992
  }
2182
- }, zn = async (e) => {
2183
- let { status: t, message: n } = await Rn(e);
1993
+ }, Nn = async (e) => {
1994
+ let { status: t, message: n } = await Mn(e);
2184
1995
  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) => {
1996
+ }, Pn = async (e) => {
2186
1997
  let t;
2187
1998
  try {
2188
- let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await Z(e);
2189
- t = lt(r, i.workflows.map((e) => {
1999
+ let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await X(e);
2000
+ t = ut(r, i.workflows.map((e) => {
2190
2001
  let t = e.definition, r = t.payloadSchema === void 0 ? {
2191
2002
  required: [],
2192
2003
  optional: []
@@ -2200,18 +2011,98 @@ var re = () => {
2200
2011
  } catch {
2201
2012
  return;
2202
2013
  }
2203
- let n = ut(t);
2014
+ let n = dt(t);
2204
2015
  if (n === null) return;
2205
2016
  console.log("");
2206
2017
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
2207
2018
  console.log(`${r} ${n}`);
2208
- }, Q = "voltro-authz-allowlist.txt", Vn = (e, t = (e) => {
2019
+ }, Q = "voltro-authz-allowlist.txt", Fn = (e, t = (e) => {
2209
2020
  try {
2210
2021
  return E(e, "utf8");
2211
2022
  } catch {
2212
2023
  return "";
2213
2024
  }
2214
- }) => /* @__PURE__ */ new Set([...wt, ...Tt(q(e).map(t))]), Hn = async (e) => {
2025
+ }) => /* @__PURE__ */ new Set([...vt, ...yt(q(e).map(t))]), In = (e, t) => {
2026
+ let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2027
+ return new RegExp(String.raw`(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=\s*define[A-Za-z]*Event\s*\(\s*\{[^}]*?name\s*:\s*['"\`]${n}['"\`]`, "s").exec(e)?.[1];
2028
+ }, Ln = async (e) => {
2029
+ try {
2030
+ let { discovered: t } = await X(e), n = t.events.map((e) => ({
2031
+ name: e.descriptor.name,
2032
+ file: e.file
2033
+ })).filter((e) => typeof e.name == "string");
2034
+ if (n.length === 0) return [];
2035
+ let r = new Set(t.events.map((e) => e.file)), i = (e) => {
2036
+ try {
2037
+ return E(e, "utf8");
2038
+ } catch {
2039
+ return "";
2040
+ }
2041
+ }, a = q(e).filter((e) => !r.has(e)).map(i).join("\n");
2042
+ return n.filter(({ name: e, file: t }) => {
2043
+ if (a.includes(e)) return !1;
2044
+ let n = In(i(t), e);
2045
+ return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
2046
+ }).map((e) => e.name).sort();
2047
+ } catch {
2048
+ return [];
2049
+ }
2050
+ }, Rn = async (e) => {
2051
+ let t = await Ln(e);
2052
+ if (t.length !== 0) {
2053
+ console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
2054
+ for (let e of t) console.log(` ${e}`);
2055
+ 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 AND the identifier its"), console.log(" declaration binds it to, so `emit(ctx, apiKeyCreated, …)` counts. An"), console.log(" emit through an ASSEMBLED name still reads as missing here.");
2056
+ }
2057
+ }, zn = async (e) => {
2058
+ try {
2059
+ let { loadApiConfig: t } = await import("./dev-rc3fwPSZ.js"), n = (await t(e))?.doctor?.scopeVocabulary;
2060
+ if (typeof n != "string") return [];
2061
+ let [r, i] = n.split("#");
2062
+ if (r === void 0 || i === void 0) return [];
2063
+ let a = (await import(oe(C(e, r)).href))[i];
2064
+ return Array.isArray(a) ? a.filter((e) => typeof e == "string") : [];
2065
+ } catch {
2066
+ return [];
2067
+ }
2068
+ }, Bn = async (e) => {
2069
+ let t = {
2070
+ evaluated: !1,
2071
+ vocabularySize: 0,
2072
+ findings: []
2073
+ };
2074
+ try {
2075
+ let { offlineManifest: n, toInput: r } = await import("./checkCommand-CwMrzAgV.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CvQCte0z.js"), o = await n(e), s = r(o.manifest, o.tables), c = await zn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
2076
+ if (l.length === 0) return t;
2077
+ let u = i({
2078
+ ...s,
2079
+ scopes: l
2080
+ });
2081
+ return {
2082
+ evaluated: !0,
2083
+ vocabularySize: l.length,
2084
+ findings: a(u).filter((e) => e.rule === "rbac/unknown-scope").map((e) => ({
2085
+ node: e.node,
2086
+ ...e.file === void 0 ? {} : { file: e.file },
2087
+ detail: e.breaks.join("; "),
2088
+ fix: e.fix
2089
+ }))
2090
+ };
2091
+ } catch {
2092
+ return t;
2093
+ }
2094
+ }, Vn = async (e) => {
2095
+ let { findings: t, evaluated: n } = await Bn(e);
2096
+ if (!n) {
2097
+ console.log(""), console.log("• scope vocabulary: none found — the unknown-scope rule did NOT run."), console.log(" It compares each procedure's required scopes against the scopes your"), console.log(" roles actually grant, and needs somewhere to read that set from."), console.log(" `@voltro/plugin-rbac` publishes it from its `roles` map; an app doing"), console.log(" RBAC without the plugin can point at its own:"), console.log(""), console.log(" // app.config.ts"), console.log(" doctor: { scopeVocabulary: './lib/teamRoles.ts#ALL_TEAM_SCOPES' }"), console.log(""), console.log(" Until then this section is NOT a clean bill of health — a procedure"), console.log(" requiring a scope no role grants is uncallable by everyone, and"), console.log(" nothing here would have told you.");
2098
+ return;
2099
+ }
2100
+ if (t.length !== 0) {
2101
+ console.log(""), console.log(`• ${t.length} procedure(s) require a scope no declared role grants:`);
2102
+ for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
2103
+ 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}`);
2104
+ }
2105
+ }, Hn = async (e) => {
2215
2106
  let t = ["src/locales", "locales"], n, r = [];
2216
2107
  for (let i of t) {
2217
2108
  let t = x(e, i);
@@ -2223,23 +2114,23 @@ var re = () => {
2223
2114
  if (n === void 0 || r.length === 0) return;
2224
2115
  let i = [];
2225
2116
  try {
2226
- let { loadApiConfig: t } = await import("./dev-tPcOmjPJ.js");
2117
+ let { loadApiConfig: t } = await import("./dev-rc3fwPSZ.js");
2227
2118
  i = (await t(e))?.locales ?? [];
2228
2119
  } catch {
2229
2120
  return;
2230
2121
  }
2231
- let a = xt({
2122
+ let a = ht({
2232
2123
  catalogCodes: r,
2233
2124
  declaredLocales: i
2234
2125
  });
2235
2126
  if (a !== void 0) {
2236
2127
  console.log("");
2237
- for (let e of St(a, n)) console.log(e);
2128
+ for (let e of gt(a, n)) console.log(e);
2238
2129
  }
2239
2130
  }, Un = async (e) => {
2240
- let { discovered: t } = await Z(e), n = [];
2131
+ let { discovered: t } = await X(e), n = [];
2241
2132
  try {
2242
- let { loadApiConfig: t } = await import("./dev-tPcOmjPJ.js");
2133
+ let { loadApiConfig: t } = await import("./dev-rc3fwPSZ.js");
2243
2134
  n = ((await t(e))?.plugins ?? []).map((e) => {
2244
2135
  let t = (e.extendSchema?.tables ?? []).map((e) => {
2245
2136
  let t = e;
@@ -2268,12 +2159,12 @@ var re = () => {
2268
2159
  ...t.mutations,
2269
2160
  ...t.actions,
2270
2161
  ...t.streams ?? []
2271
- ].map((e) => String(e.descriptor.name)), i = Rt(n, {
2162
+ ].map((e) => String(e.descriptor.name)), i = Mt(n, {
2272
2163
  tables: t.tables.map((e) => e.tableName),
2273
2164
  rpcTags: r
2274
2165
  });
2275
2166
  console.log();
2276
- for (let e of zt(n, i)) console.log(e);
2167
+ for (let e of Nt(n, i)) console.log(e);
2277
2168
  }, Wn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Gn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Kn = async (e) => {
2278
2169
  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;
2279
2170
  return {
@@ -2288,14 +2179,14 @@ var re = () => {
2288
2179
  unchecked: t.result.unchecked
2289
2180
  };
2290
2181
  }, qn = async (e) => {
2291
- let { discovered: t } = await Z(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2182
+ let { discovered: t } = await X(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2292
2183
  if (!n.has(e)) try {
2293
2184
  n.set(e, E(e, "utf8"));
2294
2185
  } catch {
2295
2186
  n.set(e, "");
2296
2187
  }
2297
2188
  return n.get(e);
2298
- }, i = Vn(e, r), a = /* @__PURE__ */ new Set();
2189
+ }, i = Fn(e, r), a = /* @__PURE__ */ new Set();
2299
2190
  for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
2300
2191
  let e = t.references?.();
2301
2192
  e && a.add(e.tableName);
@@ -2332,7 +2223,7 @@ var re = () => {
2332
2223
  l = !0;
2333
2224
  break;
2334
2225
  }
2335
- let u = jt({
2226
+ let u = Tt({
2336
2227
  executors: o,
2337
2228
  guardVocabulary: i,
2338
2229
  tenantScopedTables: t.tenantScopedTables,
@@ -2352,35 +2243,40 @@ var re = () => {
2352
2243
  }, Jn = async (e, t) => {
2353
2244
  let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await qn(e), l = n.counts;
2354
2245
  console.log(`\nauthz scan · ${o} executor(s)`);
2355
- let u = [...r].filter((e) => !wt.has(e)).sort();
2246
+ let u = [...r].filter((e) => !vt.has(e)).sort();
2356
2247
  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
2248
  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
2249
  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;
2250
+ let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = kt(e, t);
2251
+ return ce(s, Ot(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
2252
  }
2362
2253
  if (n.unchecked.length === 0) return !1;
2363
2254
  console.log("");
2364
2255
  for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
2365
2256
  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
2257
  }, 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)
2258
+ if (e.includes("--help") || e.includes("-h")) return console.log(wn), 0;
2259
+ let n = e.includes("--strict"), r = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
2260
+ if (ue(e)) return le(), M({
2261
+ ...rn(r),
2262
+ spaCandidates: await hn(r),
2263
+ pageConvention: J(r),
2264
+ fileTaxonomy: V(r).findings,
2265
+ junctionFks: (await Z(r))?.issues ?? [],
2266
+ eventDelivery: await kn(r),
2267
+ duplicateInstances: t(r),
2268
+ versionSkew: s(r) ?? null,
2269
+ serverOnly: (await Mn(r)).status,
2270
+ authz: await Kn(r),
2271
+ ungrantedScopes: await Bn(r),
2272
+ neverEmittedEvents: await Ln(r)
2379
2273
  }), 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;
2274
+ let a = Lt(r, n);
2275
+ un(r), ln(r), Cn(r), Ue(r), await _n(r), await Pn(r), await En(r), await On(r), await An(r), await Un(r), await Vn(r), await Rn(r), await Hn(r);
2276
+ let c = await Nn(r), l = await Jn(r, e.includes("--write-authz-allowlist")), u = o(s(r));
2277
+ u !== void 0 && (console.log(""), console.log(u));
2278
+ let d = i(t(r));
2279
+ return d !== void 0 && console.log(`\n${d}`), c || l ? Math.max(a, 1) : a;
2384
2280
  }, Xn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Zn = /* @__PURE__ */ new Set([
2385
2281
  "insert",
2386
2282
  "update",
@@ -2473,13 +2369,13 @@ var re = () => {
2473
2369
  ".git",
2474
2370
  "coverage"
2475
2371
  ].includes(i)) continue;
2476
- let r = `${e}/${i}`, o;
2372
+ let r = `${e}/${i}`, a;
2477
2373
  try {
2478
- o = O(r);
2374
+ a = O(r);
2479
2375
  } catch {
2480
2376
  continue;
2481
2377
  }
2482
- o.isDirectory() ? $n(r, t, n + 1) : o.isFile() && a.test(i) && t.push(r);
2378
+ a.isDirectory() ? $n(r, t, n + 1) : a.isFile() && l.test(i) && t.push(r);
2483
2379
  }
2484
2380
  return t;
2485
2381
  }, er = /* @__PURE__ */ new Set(["actors", "tenants"]), tr = async (e) => {
@@ -2513,7 +2409,7 @@ var re = () => {
2513
2409
  let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
2514
2410
  return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
2515
2411
  }).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 = [
2412
+ let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-rc3fwPSZ.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
2413
  ...s.queries.map((e) => c(e.descriptor, "query", e.file)),
2518
2414
  ...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
2519
2415
  ...s.actions.map((e) => c(e.descriptor, "action", e.file)),
@@ -2530,9 +2426,9 @@ var re = () => {
2530
2426
  tables: { tables: p }
2531
2427
  };
2532
2428
  }, or = async (e) => {
2533
- let t = ue(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
2429
+ let t = he(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
2534
2430
  if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
2535
- let { process: n, target: r, rest: i } = de(t.rest), a;
2431
+ let { process: n, target: r, rest: i } = ge(t.rest), a;
2536
2432
  for (let e = 0; e < i.length; e++) if (i[e] === "--diff") {
2537
2433
  let [t, n] = (i[++e] ?? "").split(":");
2538
2434
  if (t === "removeTable" && n) a = {
@@ -2549,35 +2445,35 @@ var re = () => {
2549
2445
  };
2550
2446
  else return console.error("voltro check --diff: expected removeTable:<name> | removeProcedure:<tag> | removeScope:<name>"), 1;
2551
2447
  }
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;
2448
+ let o = i.find((e) => !e.startsWith("-")) ?? process.cwd(), s = i.includes("--offline"), c = s ? [] : await me(n, r), l, d;
2449
+ if (c.length === 0) {
2450
+ if (!s && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
2555
2451
  try {
2556
- let e = await ar(C(s));
2557
- u = e.manifest, d = e.tables;
2452
+ let e = await ar(C(o));
2453
+ l = e.manifest, d = e.tables;
2558
2454
  } catch (e) {
2559
2455
  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
2456
  }
2561
2457
  } else {
2562
- let e = l[0], [t, n] = await Promise.all([N(e, "/_voltro/inspect/manifest"), N(e, "/_voltro/inspect/data/tables")]);
2458
+ let e = c[0], [t, n] = await Promise.all([N(e, "/_voltro/inspect/manifest"), N(e, "/_voltro/inspect/data/tables")]);
2563
2459
  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: [] };
2460
+ l = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
2565
2461
  }
2566
- let f = rr(u, d), p = he(f, a), m = [...V(s).findings.map((e) => ({
2462
+ let f = rr(l, d), p = ie(f, a), m = [...V(o).findings.map((e) => ({
2567
2463
  rule: e.rule,
2568
2464
  node: e.file,
2569
2465
  file: e.file,
2570
2466
  breaks: [e.detail],
2571
2467
  fix: e.fix,
2572
2468
  severity: e.severity
2573
- })), ...Y(s).map((e) => ({
2469
+ })), ...J(o).map((e) => ({
2574
2470
  rule: `page/${e.kind}`,
2575
2471
  node: e.file,
2576
2472
  file: e.file,
2577
2473
  breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
2578
2474
  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
2475
  severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
2580
- }))], h = (await o(s, $n(s))).map((e) => ({
2476
+ }))], h = (await u(o, $n(o))).map((e) => ({
2581
2477
  rule: "client/not-browser-safe",
2582
2478
  node: e.file,
2583
2479
  file: e.file,
@@ -2588,7 +2484,7 @@ var re = () => {
2588
2484
  ...p.diagnostics,
2589
2485
  ...m,
2590
2486
  ...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);
2487
+ ], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await tr(C(o)), y = v === null ? null : Qn(f.procedures, v);
2592
2488
  return t.format === "json" ? M(y === null ? {
2593
2489
  ...p,
2594
2490
  ok: _,
@@ -2601,4 +2497,4 @@ var re = () => {
2601
2497
  }) : (console.log(ir(g)), y !== null && console.log(nr(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
2602
2498
  };
2603
2499
  //#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 };
2500
+ export { le as _, Lt as a, Se as c, N as d, he as f, me as g, pe as h, Yn as i, I as l, P as m, or as n, Be as o, ge as p, rr as r, xe as s, ar as t, ye as u, M as v, ue as y };