@voltro/cli 0.30.1 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/dist/apiBuild-BOqL1dlg.js +2 -0
  3. package/dist/{apiBuild-DmkzRyv7.js → apiBuild-CLPt-pX6.js} +2 -2
  4. package/dist/appGraph-CvQCte0z.js +2 -0
  5. package/dist/appGraph-gQ_6GkQQ.js +110 -0
  6. package/dist/bin.js +3 -3
  7. package/dist/checkCommand-ClRvH_SN.js +2 -0
  8. package/dist/{checkCommand-C4konUy9.js → checkCommand-D7r6_pk7.js} +439 -530
  9. package/dist/{commands-BkJft9K2.js → commands-CNVVv03h.js} +1941 -1880
  10. package/dist/{dbCommand-qn__kLEt.js → dbCommand-BH8GAYuf.js} +76 -19
  11. package/dist/dbCommand-BwQ_Jl98.js +2 -0
  12. package/dist/{dev-DXhhZ7-f.js → dev-DOZVMVfi.js} +1 -1
  13. package/dist/{dev-C_NPcLDp.js → dev-DdQEn2Ia.js} +1986 -1967
  14. package/dist/{frameworkTableAssembly-Cpgn2e88.js → frameworkTableAssembly-BwIrO5nv.js} +122 -103
  15. package/dist/frameworkTableAssembly-D-EebUQX.js +2 -0
  16. package/dist/index.js +1 -1
  17. package/dist/{inspectMetrics-CkB2J0Zn.js → inspectMetrics-BeEAnjqw.js} +774 -527
  18. package/dist/{sdkgen-wsWLrq4l.js → sdkgen-v9WV8zRi.js} +1 -1
  19. package/dist/{serveCommand-HtI4X5kj.js → serveCommand-KsKXXWn4.js} +294 -293
  20. package/dist/serveEntry.js +2 -2
  21. package/dist/{start-egkbpugc.js → start-CI2QdKy9.js} +351 -346
  22. package/dist/startEntry.js +2 -2
  23. package/package.json +17 -17
  24. package/templates/AGENTS.md +43 -43
  25. package/templates/agent-docs/_index.md +43 -43
  26. package/templates/agent-docs/_manifest.json +84 -42
  27. package/templates/agent-docs/cli.md +4 -2
  28. package/templates/agent-docs/database/migrations.md +14 -1
  29. package/templates/agent-docs/internationalization.md +54 -3
  30. package/templates/agent-docs/plugins/ai-flows.md +148 -0
  31. package/templates/agent-docs/plugins/analytics-postgres.md +90 -0
  32. package/templates/agent-docs/plugins/atlassian.md +378 -0
  33. package/templates/agent-docs/plugins/audit.md +324 -0
  34. package/templates/agent-docs/plugins/auth-auth0.md +92 -0
  35. package/templates/agent-docs/plugins/auth-clerk.md +90 -0
  36. package/templates/agent-docs/plugins/auth-kinde.md +88 -0
  37. package/templates/agent-docs/plugins/auth-oidc.md +95 -0
  38. package/templates/agent-docs/plugins/auth-supabase.md +103 -0
  39. package/templates/agent-docs/plugins/auth-workos.md +105 -0
  40. package/templates/agent-docs/plugins/auth.md +227 -0
  41. package/templates/agent-docs/plugins/billing.md +324 -0
  42. package/templates/agent-docs/plugins/broadcast.md +144 -0
  43. package/templates/agent-docs/plugins/cdc-out.md +116 -0
  44. package/templates/agent-docs/plugins/clickhouse.md +91 -0
  45. package/templates/agent-docs/plugins/datadog.md +105 -0
  46. package/templates/agent-docs/plugins/deactivation.md +114 -0
  47. package/templates/agent-docs/plugins/duckdb.md +82 -0
  48. package/templates/agent-docs/plugins/flags.md +146 -0
  49. package/templates/agent-docs/plugins/governance.md +143 -0
  50. package/templates/agent-docs/plugins/licensing.md +74 -0
  51. package/templates/agent-docs/plugins/logship.md +56 -0
  52. package/templates/agent-docs/plugins/mail.md +334 -0
  53. package/templates/agent-docs/plugins/moderation.md +65 -0
  54. package/templates/agent-docs/plugins/multitenancy.md +99 -0
  55. package/templates/agent-docs/plugins/notifications.md +252 -0
  56. package/templates/agent-docs/plugins/openapi.md +94 -0
  57. package/templates/agent-docs/plugins/postgis.md +140 -0
  58. package/templates/agent-docs/plugins/posthog.md +91 -0
  59. package/templates/agent-docs/plugins/presence.md +115 -0
  60. package/templates/agent-docs/plugins/prometheus.md +124 -0
  61. package/templates/agent-docs/plugins/ratelimit.md +274 -0
  62. package/templates/agent-docs/plugins/rbac.md +270 -0
  63. package/templates/agent-docs/plugins/scim.md +63 -0
  64. package/templates/agent-docs/plugins/search.md +116 -0
  65. package/templates/agent-docs/plugins/sentry.md +192 -0
  66. package/templates/agent-docs/plugins/soft-delete.md +72 -0
  67. package/templates/agent-docs/plugins/sso-saml.md +132 -0
  68. package/templates/agent-docs/plugins/storage.md +709 -0
  69. package/templates/agent-docs/plugins/tinybird.md +92 -0
  70. package/templates/agent-docs/plugins/versioning.md +226 -0
  71. package/templates/agent-docs/plugins/webhooks.md +585 -0
  72. package/templates/agent-docs/security.md +12 -1
  73. package/templates/agent-docs/whats-new.md +81 -37
  74. package/templates/apps/api-ai/package.json +7 -7
  75. package/templates/apps/api-auth/package.json +8 -8
  76. package/templates/apps/api-backend/package.json +7 -7
  77. package/templates/apps/api-backend-deactivation/package.json +7 -7
  78. package/templates/apps/api-backend-mail/package.json +8 -8
  79. package/templates/apps/api-backend-mariadb/package.json +9 -9
  80. package/templates/apps/api-backend-sqlite/package.json +8 -8
  81. package/templates/apps/api-backend-storage/package.json +8 -8
  82. package/templates/apps/api-cms/package.json +10 -10
  83. package/templates/apps/api-collab/package.json +8 -8
  84. package/templates/apps/api-data-advanced/package.json +8 -8
  85. package/templates/apps/api-durable/package.json +8 -8
  86. package/templates/apps/api-feature-flags/package.json +9 -9
  87. package/templates/apps/api-governance/package.json +8 -8
  88. package/templates/apps/api-kv/package.json +8 -8
  89. package/templates/apps/api-moderation/package.json +8 -8
  90. package/templates/apps/api-observability/package.json +8 -8
  91. package/templates/apps/api-ratelimit/package.json +8 -8
  92. package/templates/apps/api-rbac/package.json +8 -8
  93. package/templates/apps/api-rest/package.json +7 -7
  94. package/templates/apps/api-saas/package.json +11 -11
  95. package/templates/apps/api-saas-starter/package.json +10 -10
  96. package/templates/apps/api-search/package.json +8 -8
  97. package/templates/apps/api-status/package.json +8 -8
  98. package/templates/apps/api-versioning/package.json +8 -8
  99. package/templates/apps/api-webhooks/package.json +9 -9
  100. package/templates/apps/changelog/package.json +6 -6
  101. package/templates/apps/edge-functions/package.json +2 -2
  102. package/templates/apps/frontend-admin/package.json +8 -8
  103. package/templates/apps/frontend-app/package.json +8 -8
  104. package/templates/apps/frontend-auth/package.json +8 -8
  105. package/templates/apps/frontend-blank/package.json +7 -7
  106. package/templates/apps/frontend-cms/package.json +9 -9
  107. package/templates/apps/frontend-collab/package.json +9 -9
  108. package/templates/apps/frontend-contact/package.json +7 -7
  109. package/templates/apps/frontend-dashboard/package.json +7 -7
  110. package/templates/apps/frontend-docs/package.json +7 -7
  111. package/templates/apps/frontend-i18n/package.json +6 -6
  112. package/templates/apps/frontend-landing/package.json +7 -7
  113. package/templates/apps/frontend-portal/package.json +8 -8
  114. package/templates/apps/frontend-saas/package.json +8 -8
  115. package/templates/apps/frontend-spa/package.json +7 -7
  116. package/templates/apps/frontend-ssr/package.json +7 -7
  117. package/templates/apps/frontend-ssr-api/package.json +8 -8
  118. package/templates/apps/frontend-static-blog/package.json +6 -6
  119. package/templates/apps/frontend-status/package.json +8 -8
  120. package/templates/apps/mobile-app/package.json +2 -2
  121. package/dist/apiBuild-Ca6Sq5jy.js +0 -2
  122. package/dist/checkCommand-DHD0aBeR.js +0 -2
  123. package/dist/dbCommand-wfGi3FgG.js +0 -2
  124. package/dist/frameworkTableAssembly-Bn_c4Mnl.js +0 -2
@@ -1,30 +1,31 @@
1
- import { R as e, f as t, ht as n, n as r, o as i } from "./inspectMetrics-CkB2J0Zn.js";
2
- import { Ut as a, Wt as o } from "./dev-C_NPcLDp.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-DmkzRyv7.js";
5
- import { dirname as b, isAbsolute as ee, join as x, relative as S, resolve as C, sep as w } from "node:path";
6
- import { routeDiagnosticsToStderr as te } from "@voltro/logger";
7
- import { existsSync as T, readFileSync as E, readdirSync as D, statSync as O, writeFileSync as ne } from "node:fs";
1
+ import { Ct as e, et as t, f as n, n as r, nt as i, o as a, rt as o, tt as s, z as c } from "./inspectMetrics-BeEAnjqw.js";
2
+ import { Ht as l, Ut as u } from "./dev-DdQEn2Ia.js";
3
+ import { C as d, E as f, O as p, T as m, d as h, h as g, i as _, l as v, m as y, r as ee, u as te } from "./fileConventions-Cof68_BL.js";
4
+ import { r as ne, u as re } from "./apiBuild-CLPt-pX6.js";
5
+ import { r as ie } from "./appGraph-gQ_6GkQQ.js";
6
+ import { dirname as b, isAbsolute as ae, join as x, relative as S, resolve as C, sep as w } from "node:path";
7
+ import { routeDiagnosticsToStderr as oe } from "@voltro/logger";
8
+ import { existsSync as T, readFileSync as E, readdirSync as D, statSync as O, writeFileSync as se } from "node:fs";
8
9
  import { Node as k, Project as A, SyntaxKind as j } from "ts-morph";
9
10
  //#region src/cliOutput.ts
10
- var re = () => {
11
- te();
11
+ var ce = () => {
12
+ oe();
12
13
  }, M = (e, t = {}) => {
13
14
  console.log(JSON.stringify(e, null, t.pretty === !1 ? 0 : 2));
14
- }, ie = (e) => {
15
+ }, le = (e) => {
15
16
  for (let t = 0; t < e.length; t++) {
16
17
  let n = e[t];
17
18
  if (n === "--json" || n === "--format=json" || n === "--format" && e[t + 1] === "json") return !0;
18
19
  }
19
20
  return !1;
20
- }, ae = (e, t, n = !1) => {
21
+ }, ue = (e, t, n = !1) => {
21
22
  let r = { ...e ?? {} }, i = t ?? process.env.VOLTRO_INSPECT_TOKEN;
22
23
  if (i !== void 0 && i !== "" && (r.authorization = `Bearer ${i}`), n) {
23
24
  let e = process.env.VOLTRO_INSPECT_WRITE_TOKEN;
24
25
  e !== void 0 && e !== "" && (r["x-voltro-inspect-write"] = e);
25
26
  }
26
27
  return r;
27
- }, oe = /* @__PURE__ */ new Set([
28
+ }, de = /* @__PURE__ */ new Set([
28
29
  "POST",
29
30
  "PUT",
30
31
  "PATCH",
@@ -34,7 +35,7 @@ var re = () => {
34
35
  try {
35
36
  let t = await fetch(r, {
36
37
  method: i,
37
- headers: ae(a ? { "content-type": "application/json" } : void 0, e.inspectToken, oe.has(i)),
38
+ headers: ue(a ? { "content-type": "application/json" } : void 0, e.inspectToken, de.has(i)),
38
39
  ...a ? { body: JSON.stringify(n.body) } : {}
39
40
  }), o = await t.text(), s;
40
41
  if (o.length > 0) try {
@@ -43,7 +44,7 @@ var re = () => {
43
44
  s = o;
44
45
  }
45
46
  if (!t.ok) {
46
- let n = s, r = typeof n == "object" && n ? String(n.message ?? n.reason ?? n.error ?? `HTTP ${t.status}`) : `HTTP ${t.status}`, a = oe.has(i) && (process.env.VOLTRO_INSPECT_WRITE_TOKEN ?? "") === "" && /write/i.test(r);
47
+ let n = s, r = typeof n == "object" && n ? String(n.message ?? n.reason ?? n.error ?? `HTTP ${t.status}`) : `HTTP ${t.status}`, a = de.has(i) && (process.env.VOLTRO_INSPECT_WRITE_TOKEN ?? "") === "" && /write/i.test(r);
47
48
  return {
48
49
  kind: "err",
49
50
  name: e.name,
@@ -63,7 +64,7 @@ var re = () => {
63
64
  error: t.message || String(t)
64
65
  };
65
66
  }
66
- }, se = (e, t = process.env) => {
67
+ }, P = (e, t = process.env) => {
67
68
  let n = [], r, i;
68
69
  for (let t = 0; t < e.length; t++) {
69
70
  let a = e[t];
@@ -108,7 +109,7 @@ var re = () => {
108
109
  },
109
110
  rest: n
110
111
  };
111
- }, ce = (e) => ({
112
+ }, fe = (e) => ({
112
113
  id: `remote:${e.url}`,
113
114
  name: e.name,
114
115
  kind: "api",
@@ -119,7 +120,7 @@ var re = () => {
119
120
  status: "live",
120
121
  logFile: "",
121
122
  ...e.token === void 0 ? {} : { inspectToken: e.token }
122
- }), le = async (e, t) => t === void 0 ? (await n()).apps.filter((t) => t.kind === "api" && t.status === "live" && t.url.length > 0 && (e === void 0 || t.name === e)) : [ce(t)], ue = (e) => {
123
+ }), pe = async (t, n) => n === void 0 ? (await e()).apps.filter((e) => e.kind === "api" && e.status === "live" && e.url.length > 0 && (t === void 0 || e.name === t)) : [fe(n)], me = (e) => {
123
124
  let t = "pretty", n = [];
124
125
  for (let r = 0; r < e.length; r++) {
125
126
  let i = e[r];
@@ -143,8 +144,8 @@ var re = () => {
143
144
  format: t,
144
145
  rest: n
145
146
  };
146
- }, de = (e, t = process.env) => {
147
- let { target: n, rest: r, error: i } = se(e, t), a, o = [];
147
+ }, he = (e, t = process.env) => {
148
+ let { target: n, rest: r, error: i } = P(e, t), a, o = [];
148
149
  for (let e = 0; e < r.length; e++) {
149
150
  let t = r[e];
150
151
  if (t === "--process") {
@@ -163,112 +164,6 @@ var re = () => {
163
164
  rest: o,
164
165
  ...i === void 0 ? {} : { error: i }
165
166
  };
166
- }, P = (e) => e.source === void 0 ? [] : typeof e.source == "string" ? [e.source] : e.source, fe = (e) => ({
167
- tables: new Set(e.tables),
168
- procedures: new Map(e.procedures.map((e) => [e.tag, e])),
169
- scopes: new Set(e.scopes ?? []),
170
- input: e
171
- }), pe = (e) => {
172
- let t = [], n = /* @__PURE__ */ new Set();
173
- for (let r of e.input.procedures) {
174
- for (let i of P(r)) n.add(i), e.tables.has(i) || t.push({
175
- rule: "reference/dangling-source",
176
- node: `${r.kind}(${r.tag})`,
177
- ...r.file === void 0 ? {} : { file: r.file },
178
- breaks: [`reads table '${i}' which does not exist`],
179
- fix: `declare a '${i}.entity.ts' table or fix the query's source`,
180
- severity: "error"
181
- });
182
- for (let i of r.targets ?? []) n.add(i.table), e.tables.has(i.table) || t.push({
183
- rule: "reference/dangling-target",
184
- node: `${r.kind}(${r.tag})`,
185
- ...r.file === void 0 ? {} : { file: r.file },
186
- breaks: [`targets table '${i.table}' (op '${i.op}') which does not exist`],
187
- fix: `declare a '${i.table}.entity.ts' table or fix the mutation's target`,
188
- severity: "error"
189
- });
190
- for (let n of r.requiresScopes ?? []) e.scopes.size > 0 && !e.scopes.has(n) && t.push({
191
- rule: "rbac/unknown-scope",
192
- node: `${r.kind}(${r.tag})`,
193
- ...r.file === void 0 ? {} : { file: r.file },
194
- breaks: [`requires scope '${n}' which is not declared`],
195
- fix: `declare the scope '${n}' or fix the requireScope(...) call`,
196
- severity: "error"
197
- });
198
- r.kind === "mutation" && r.hasGuards === !1 && t.push({
199
- rule: "rbac/unguarded-mutation",
200
- node: `${r.kind}(${r.tag})`,
201
- ...r.file === void 0 ? {} : { file: r.file },
202
- breaks: [`'${r.tag}' changes state but declares no guard — any caller who can reach the rpc surface can invoke it`],
203
- fix: "add a guard (e.g. `.guard(requireScope('…'))`), or confirm it is intentionally public",
204
- severity: "warn"
205
- });
206
- }
207
- for (let n of e.input.routes ?? []) for (let r of n.binds) e.procedures.has(r) || t.push({
208
- rule: "binding/unknown-tag",
209
- node: `route(${n.pattern})`,
210
- ...n.file === void 0 ? {} : { file: n.file },
211
- breaks: [`binds rpc tag '${r}' which does not exist`],
212
- fix: `bind an existing tag, or restore '${r}' (it may have been renamed/removed)`,
213
- severity: "error"
214
- });
215
- for (let t of e.input.procedures) for (let e of t.targets ?? []) n.add(e.table);
216
- let r = new Set(e.input.frameworkOwnedTables ?? []), i = e.input.procedures.filter((e) => e.kind === "action" && P(e).length === 0 && (e.targets ?? []).length === 0).length;
217
- for (let a of e.tables) r.has(a) || n.has(a) || t.push({
218
- rule: "orphan/unread-table",
219
- node: `table(${a})`,
220
- breaks: i > 0 ? [`no query reads and no mutation writes '${a}'`, `${i} action(s) declare neither source nor target, so one of them may still use it`] : [`no query reads and no mutation writes '${a}'`],
221
- fix: i > 0 ? "declare `source`/`target` on your actions so this can be answered, then re-run" : `add a query/mutation for '${a}', or remove the table if it's dead`,
222
- severity: "warn"
223
- });
224
- if (e.scopes.size > 0) {
225
- let n = /* @__PURE__ */ new Set();
226
- for (let t of e.input.procedures) for (let e of t.requiresScopes ?? []) n.add(e);
227
- for (let r of [...e.scopes].sort()) r === "admin:full" || n.has(r) || t.push({
228
- rule: "rbac/unenforced-scope",
229
- node: `scope(${r})`,
230
- breaks: [`'${r}' is granted by a role but NO query/mutation/action/stream guards on it`, "a scope nobody checks grants nothing and withholds nothing — the role map reads stricter than the app is"],
231
- fix: `add \`.guard(requireScope('${r}'))\` to the handlers it is meant to protect — or, if a plugin route, a REST route, or a UI-only affordance enforces it, say so where the role is declared`,
232
- severity: "warn"
233
- });
234
- }
235
- return t;
236
- }, me = (e, t) => {
237
- let n = [];
238
- if (t.kind === "removeTable") for (let r of e.input.procedures) {
239
- let e = P(r).includes(t.name), i = (r.targets ?? []).some((e) => e.table === t.name);
240
- (e || i) && n.push({
241
- rule: "blast/table-removed",
242
- node: `${r.kind}(${r.tag})`,
243
- ...r.file === void 0 ? {} : { file: r.file },
244
- breaks: [`${e ? "reads" : "writes"} table '${t.name}' — would break if removed`],
245
- fix: `migrate or remove ${r.tag} before dropping '${t.name}'`,
246
- severity: "error"
247
- });
248
- }
249
- else if (t.kind === "removeProcedure") for (let r of e.input.routes ?? []) r.binds.includes(t.tag) && n.push({
250
- rule: "blast/procedure-removed",
251
- node: `route(${r.pattern})`,
252
- ...r.file === void 0 ? {} : { file: r.file },
253
- breaks: [`binds '${t.tag}' — would break if removed`],
254
- fix: `rebind ${r.pattern} to another tag before removing '${t.tag}'`,
255
- severity: "error"
256
- });
257
- else for (let r of e.input.procedures) (r.requiresScopes ?? []).includes(t.name) && n.push({
258
- rule: "blast/scope-removed",
259
- node: `${r.kind}(${r.tag})`,
260
- ...r.file === void 0 ? {} : { file: r.file },
261
- breaks: [`requires scope '${t.name}' — would become unguarded/dangling if removed`],
262
- fix: `remove the requireScope('${t.name}') call or keep the scope`,
263
- severity: "error"
264
- });
265
- return n;
266
- }, he = (e, t) => {
267
- let n = fe(e), r = [...pe(n), ...t === void 0 ? [] : me(n, t)];
268
- return {
269
- ok: !r.some((e) => e.severity === "error"),
270
- diagnostics: r
271
- };
272
167
  }, ge = (e) => {
273
168
  let t = "", n = !1;
274
169
  for (let r = 0; r < e.length; r++) {
@@ -343,7 +238,7 @@ var re = () => {
343
238
  }
344
239
  } };
345
240
  }, ve = (e, t = "tsconfig.json") => {
346
- let n = ee(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
241
+ let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
347
242
  if (i === void 0) return;
348
243
  let a = {};
349
244
  for (let [e, t] of Object.entries(i)) Array.isArray(t) && (a[e] = t.filter((e) => typeof e == "string"));
@@ -352,7 +247,7 @@ var re = () => {
352
247
  paths: a
353
248
  };
354
249
  }, I = (e, t = "tsconfig.json") => {
355
- let n = ee(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
250
+ let n = ae(t) ? t : C(e, t), r = F(n, /* @__PURE__ */ new Set()), i = r?.compilerOptions?.paths;
356
251
  if (i === void 0) return [];
357
252
  let a = C(b(n), r?.compilerOptions?.baseUrl ?? "."), o = [];
358
253
  for (let [e, t] of Object.entries(i)) {
@@ -404,45 +299,45 @@ var re = () => {
404
299
  }, xe = (e, t) => t.some((t) => e === t.dir || e.startsWith(`${t.dir}${w}`)), Se = (e, t) => t.map((t) => `${S(e, t.dir) || "."} (${t.source})`), Ce = [
405
300
  {
406
301
  id: "component.ui",
407
- re: p,
302
+ re: _,
408
303
  needsTest: !0
409
304
  },
410
305
  {
411
306
  id: "component",
412
- re: g,
307
+ re: ee,
413
308
  needsTest: !0
414
309
  },
415
310
  {
416
311
  id: "hook",
417
- re: _,
312
+ re: te,
418
313
  needsTest: !0
419
314
  },
420
315
  {
421
316
  id: "tracking",
422
- re: c,
317
+ re: f,
423
318
  needsTest: !0
424
319
  },
425
320
  {
426
321
  id: "store",
427
- re: s,
322
+ re: d,
428
323
  needsTest: !0
429
324
  },
430
325
  {
431
326
  id: "internal",
432
- re: d,
327
+ re: h,
433
328
  needsTest: !0
434
329
  },
435
330
  {
436
331
  id: "fixture",
437
- re: m,
332
+ re: v,
438
333
  needsTest: !1
439
334
  },
440
335
  {
441
336
  id: "types",
442
- re: l,
337
+ re: p,
443
338
  needsTest: !1
444
339
  }
445
- ], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, we = (e) => u.test(e), Te = /* @__PURE__ */ new Set([
340
+ ], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, we = (e) => m.test(e), Te = /* @__PURE__ */ new Set([
446
341
  "useMutation",
447
342
  "useAction",
448
343
  "useUpload",
@@ -468,7 +363,7 @@ var re = () => {
468
363
  ".git",
469
364
  "coverage",
470
365
  ".publish"
471
- ]), ke = /\.tsx?$/, z = (e, t = [], n = 0) => {
366
+ ]), ke = /\.tsx?$/, Ae = (e, t = [], n = 0) => {
472
367
  if (n > 12) return t;
473
368
  let r;
474
369
  try {
@@ -484,20 +379,20 @@ var re = () => {
484
379
  } catch {
485
380
  continue;
486
381
  }
487
- a.isDirectory() ? z(r, t, n + 1) : a.isFile() && ke.test(i) && !i.endsWith(".d.ts") && t.push(r);
382
+ a.isDirectory() ? Ae(r, t, n + 1) : a.isFile() && ke.test(i) && !i.endsWith(".d.ts") && t.push(r);
488
383
  }
489
384
  return t;
490
- }, Ae = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, je = /\b(?:memo|forwardRef|observer|styled)\b/, B = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), V = (e) => {
491
- if (B(e)) return !0;
492
- if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return V(e.getExpression());
385
+ }, je = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, Me = /\b(?:memo|forwardRef|observer|styled)\b/, Ne = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), z = (e) => {
386
+ if (Ne(e)) return !0;
387
+ if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return z(e.getExpression());
493
388
  if (k.isVariableDeclaration(e)) {
494
389
  let t = e.getTypeNode();
495
- if (t !== void 0 && Ae.test(t.getText())) return !0;
390
+ if (t !== void 0 && je.test(t.getText())) return !0;
496
391
  let n = e.getInitializer();
497
- return n === void 0 || V(n);
392
+ return n === void 0 || z(n);
498
393
  }
499
- return k.isCallExpression(e) ? je.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => B(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
- }, Me = (e) => {
394
+ return k.isCallExpression(e) ? Me.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => Ne(e) || k.isIdentifier(e)) : k.isTaggedTemplateExpression(e) ? !0 : !(k.isObjectLiteralExpression(e) || k.isArrayLiteralExpression(e) || k.isStringLiteral(e) || k.isNoSubstitutionTemplateLiteral(e) || k.isNumericLiteral(e) || k.isTemplateExpression(e) || k.isNewExpression(e) || k.isPropertyAccessExpression(e) || e.getKind() === j.TrueKeyword || e.getKind() === j.FalseKeyword || e.getKind() === j.NullKeyword);
395
+ }, Pe = (e) => {
501
396
  let t = [], n = [], r = [], i = /* @__PURE__ */ new Set();
502
397
  for (let [a, o] of e.getExportedDeclarations()) {
503
398
  let e = o[0];
@@ -508,7 +403,7 @@ var re = () => {
508
403
  n.push(s);
509
404
  continue;
510
405
  }
511
- if (/^[A-Z]/.test(s) && V(e)) {
406
+ if (/^[A-Z]/.test(s) && z(e)) {
512
407
  t.push(s);
513
408
  continue;
514
409
  }
@@ -520,7 +415,7 @@ var re = () => {
520
415
  others: r,
521
416
  hasRuntimeExport: t.length + n.length + r.length > 0
522
417
  };
523
- }, Ne = [], Pe = (e, t, n) => {
418
+ }, B = [], Fe = (e, t, n) => {
524
419
  let r = b(e), i = [];
525
420
  for (let e = r;; e = b(e)) {
526
421
  let r = n.get(e);
@@ -537,9 +432,9 @@ var re = () => {
537
432
  }
538
433
  if (e === t || b(e) === e) break;
539
434
  }
540
- for (let e of i) n.set(e, Ne);
541
- return Ne;
542
- }, Fe = (e, t, n) => {
435
+ for (let e of i) n.set(e, B);
436
+ return B;
437
+ }, Ie = (e, t, n) => {
543
438
  let r = t.startsWith(".") ? C(b(e), t) : ye(t, n);
544
439
  if (r === void 0) return null;
545
440
  for (let e of [
@@ -552,9 +447,9 @@ var re = () => {
552
447
  if (O(e).isFile()) return e;
553
448
  } catch {}
554
449
  return null;
555
- }, Ie = (e, t, n) => {
450
+ }, Le = (e, t, n) => {
556
451
  let r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = (e) => {
557
- let i = Fe(t, e, n);
452
+ let i = Ie(t, e, n);
558
453
  return i !== null && r.push(i), i;
559
454
  };
560
455
  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,22 +476,22 @@ var re = () => {
581
476
  namedBindings: i,
582
477
  reExports: a
583
478
  };
584
- }, Le = (e) => {
479
+ }, Re = (e) => {
585
480
  let t = /* @__PURE__ */ new Map();
586
481
  for (let n of e.getDescendantsOfKind(j.CallExpression)) {
587
482
  let e = n.getExpression(), r = k.isPropertyAccessExpression(e) ? e.getName() : e.getText();
588
483
  t.set(r, (t.get(r) ?? 0) + 1);
589
484
  }
590
485
  return t;
591
- }, Re = (e) => {
486
+ }, ze = (e) => {
592
487
  if (e.length <= 1) return e.length === 1;
593
488
  let t = [...e].sort((e, t) => e.length - t.length)[0];
594
489
  return e.every((e) => e.startsWith(t));
595
- }, ze = (e, t) => {
490
+ }, Be = (e, t) => {
596
491
  let n = b(t);
597
492
  return e.startsWith(`${n}${w}`) || b(e) === n;
598
- }, H = (e) => {
599
- let t = z(e);
493
+ }, V = (e) => {
494
+ let t = Ae(e);
600
495
  if (t.length === 0) return {
601
496
  findings: [],
602
497
  declared: 0,
@@ -627,14 +522,14 @@ var re = () => {
627
522
  } catch {
628
523
  continue;
629
524
  }
630
- s.set(t, Me(r)), c.set(t, Ie(r, t, Pe(t, e, u))), l.set(t, Le(r));
525
+ s.set(t, Pe(r)), c.set(t, Le(r, t, Fe(t, e, u))), l.set(t, Re(r));
631
526
  }
632
527
  let d = /* @__PURE__ */ new Map();
633
528
  for (let [e, t] of c) for (let n of t.localTargets) {
634
529
  let t = d.get(n) ?? [];
635
530
  t.push(e), d.set(n, t);
636
531
  }
637
- let p = (e) => {
532
+ let f = (e) => {
638
533
  let t = s.get(e), n = /* @__PURE__ */ new Set([
639
534
  ...t?.components ?? [],
640
535
  ...t?.hooks ?? [],
@@ -655,15 +550,15 @@ var re = () => {
655
550
  }
656
551
  };
657
552
  return a(e, null), [...r];
658
- }, m = 0;
553
+ }, p = 0;
659
554
  for (let e of r) {
660
555
  let t = R(e), n = s.get(e), r = c.get(e);
661
556
  if (n === void 0 || r === void 0) continue;
662
557
  let i = we(e);
663
- t !== null && !i && (m += 1);
558
+ t !== null && !i && (p += 1);
664
559
  let u = d.get(e) ?? [];
665
560
  if ((t === "component" || t === "component.ui") && !i) {
666
- let t = Re(n.components);
561
+ let t = ze(n.components);
667
562
  (n.components.length === 0 || !t) && a.push({
668
563
  rule: "component/one-per-file",
669
564
  file: o(e),
@@ -701,7 +596,7 @@ var re = () => {
701
596
  ].join(", ")}`,
702
597
  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
598
  }), t === "internal") {
704
- let t = u.filter((t) => !ze(t, e));
599
+ let t = u.filter((t) => !Be(t, e));
705
600
  t.length > 0 && a.push({
706
601
  rule: "internal/foreign-import",
707
602
  file: o(e),
@@ -729,9 +624,9 @@ var re = () => {
729
624
  detail: `imports write hook(s): ${t.join(", ")}`,
730
625
  fix: "a *.component.ui.tsx renders; it does not write. Lift the mutation into the *.component.tsx that owns it and pass a handler down. Reading (useT / useCan / usePermissions) stays allowed — threading those through props is prop-drilling, not reusability"
731
626
  });
732
- let n = p(e), i = n.filter((e) => {
627
+ let n = f(e), i = n.filter((e) => {
733
628
  let t = R(e);
734
- return t === "component" || t === "component.ui" || h.test(e) || f.has(e.slice(e.lastIndexOf(w) + 1));
629
+ return t === "component" || t === "component.ui" || y.test(e) || g.has(e.slice(e.lastIndexOf(w) + 1));
735
630
  });
736
631
  n.length === 0 ? a.push({
737
632
  rule: "ui/orphaned",
@@ -794,12 +689,12 @@ var re = () => {
794
689
  }
795
690
  return a.sort((e, t) => e.file === t.file ? e.rule.localeCompare(t.rule) : e.file.localeCompare(t.file)), {
796
691
  findings: a,
797
- declared: m,
692
+ declared: p,
798
693
  scanned: r.length,
799
694
  vendored: n
800
695
  };
801
- }, Be = 10, Ve = (e) => {
802
- let { findings: t, declared: n, scanned: r, vendored: i } = H(e);
696
+ }, Ve = 10, He = (e) => {
697
+ let { findings: t, declared: n, scanned: r, vendored: i } = V(e);
803
698
  console.log("");
804
699
  for (let t of Se(e, i)) console.log(`· not ours, exempt: ${t}`);
805
700
  if (r === 0) return console.log("• file-taxonomy scan: no sources here — nothing to check."), 0;
@@ -813,12 +708,12 @@ var re = () => {
813
708
  console.log(`• file taxonomy: ${t.length} finding(s) — ${o} error, ${t.length - o} advisory (${n} declared / ${r} scanned):`);
814
709
  for (let [e, t] of [...a].sort()) {
815
710
  console.log(` ${t[0].severity === "error" ? "✗" : "·"} ${e} (${t.length})`);
816
- for (let e of t.slice(0, Be)) console.log(` ${e.file} — ${e.detail}`);
817
- let n = t.length - Be;
711
+ for (let e of t.slice(0, Ve)) console.log(` ${e.file} — ${e.detail}`);
712
+ let n = t.length - Ve;
818
713
  n > 0 && console.log(` (+${n} more — \`voltro doctor --json\` prints every one.)`), console.log(` → ${t[0].fix}`);
819
714
  }
820
715
  return t.length;
821
- }, He = /* @__PURE__ */ new Set([
716
+ }, Ue = /* @__PURE__ */ new Set([
822
717
  "eq",
823
718
  "ne",
824
719
  "gt",
@@ -836,10 +731,10 @@ var re = () => {
836
731
  "endsWith",
837
732
  "between",
838
733
  "jsonField"
839
- ]), Ue = (e) => {
734
+ ]), We = (e) => {
840
735
  if (k.isPropertyAccessExpression(e)) return e.getName();
841
736
  if (k.isIdentifier(e)) return e.getText();
842
- }, We = (e) => {
737
+ }, Ge = (e) => {
843
738
  let t = e;
844
739
  for (; k.isCallExpression(t);) {
845
740
  let e = t.getExpression();
@@ -847,7 +742,7 @@ var re = () => {
847
742
  t = e.getExpression();
848
743
  }
849
744
  return t;
850
- }, Ge = (e, t, n) => {
745
+ }, Ke = (e, t, n) => {
851
746
  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
747
  useInMemoryFileSystem: !0,
853
748
  skipAddingFilesFromTsConfig: !0
@@ -865,7 +760,7 @@ var re = () => {
865
760
  for (let t of e.getDescendantsOfKind(j.CallExpression)) {
866
761
  let e = t.getExpression();
867
762
  if (!k.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
868
- let n = Ue(We(e.getExpression()));
763
+ let n = We(Ge(e.getExpression()));
869
764
  if (n === void 0 || !r.has(n)) {
870
765
  let t = e.getExpression();
871
766
  if (k.isCallExpression(t)) {
@@ -888,7 +783,7 @@ var re = () => {
888
783
  else for (let e of [u, ...u.getDescendantsOfKind(j.CallExpression)]) {
889
784
  if (!k.isCallExpression(e)) continue;
890
785
  let t = e.getExpression(), n = k.isIdentifier(t) ? t.getText() : k.isPropertyAccessExpression(t) ? t.getName() : void 0;
891
- if (n === void 0 || !He.has(n)) continue;
786
+ if (n === void 0 || !Ue.has(n)) continue;
892
787
  let r = e.getArguments()[0];
893
788
  r !== void 0 && k.isStringLiteral(r) && l.push({
894
789
  column: r.getLiteralValue(),
@@ -909,16 +804,16 @@ var re = () => {
909
804
  issues: a,
910
805
  checked: o
911
806
  };
912
- }, U = 12, Ke = (e) => {
807
+ }, H = 12, qe = (e) => {
913
808
  if (e.checked === 0) return null;
914
809
  if (e.issues.length === 0) return `predicate columns: ${e.checked} checked — every filtered column exists on its table`;
915
810
  let t = [`predicate columns: ${e.issues.length} filter${e.issues.length, "s"} on a column that does not exist (${e.checked} checked)`];
916
811
  for (let n of e.issues) {
917
- let e = n.available.slice(0, U).join(", "), r = n.available.length > U ? `, … (${n.available.length - U} more)` : "";
812
+ let e = n.available.slice(0, H).join(", "), r = n.available.length > H ? `, … (${n.available.length - H} more)` : "";
918
813
  t.push(` ${n.file}:${n.line} '${n.table}' has no column '${n.column}'`), t.push(` columns: ${e}${r}`);
919
814
  }
920
815
  return t.push(" This type-checks today and fails at runtime as a bare SQL error."), t.join("\n");
921
- }, qe = /* @__PURE__ */ new Set([
816
+ }, Je = /* @__PURE__ */ new Set([
922
817
  "id",
923
818
  "tenantId",
924
819
  "tenant_id",
@@ -938,7 +833,7 @@ var re = () => {
938
833
  "idempotency_id",
939
834
  "idempotencyKey",
940
835
  "idempotency_key"
941
- ]), Je = /* @__PURE__ */ new Set([
836
+ ]), Ye = /* @__PURE__ */ new Set([
942
837
  "id",
943
838
  "createdAt",
944
839
  "created_at",
@@ -948,10 +843,10 @@ var re = () => {
948
843
  "deleted_at",
949
844
  "tenantId",
950
845
  "tenant_id"
951
- ]), Ye = (e) => qe.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Xe = (e) => e.type === "reference", Ze = (e) => {
846
+ ]), Xe = (e) => Je.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Ze = (e) => e.type === "reference", Qe = (e) => {
952
847
  let t = [];
953
848
  for (let n of e) {
954
- let e = n.columns.filter((e) => Ye(e.name)), r = e.filter((e) => !Xe(e)).map((e) => e.name);
849
+ let e = n.columns.filter((e) => Xe(e.name)), r = e.filter((e) => !Ze(e)).map((e) => e.name);
955
850
  if (r.length === 0) continue;
956
851
  let i = new Set(n.primaryKey), a = r.filter((e) => i.has(e));
957
852
  if (n.primaryKey.length >= 2 && a.length > 0) {
@@ -962,7 +857,7 @@ var re = () => {
962
857
  });
963
858
  continue;
964
859
  }
965
- if (e.some(Xe) && e.length >= 2) {
860
+ if (e.some(Ze) && e.length >= 2) {
966
861
  t.push({
967
862
  table: n.name,
968
863
  columns: r,
@@ -970,7 +865,7 @@ var re = () => {
970
865
  });
971
866
  continue;
972
867
  }
973
- e.length >= 2 && n.columns.filter((e) => !Ye(e.name) && !Je.has(e.name)).length === 0 && t.push({
868
+ e.length >= 2 && n.columns.filter((e) => !Xe(e.name) && !Ye.has(e.name)).length === 0 && t.push({
974
869
  table: n.name,
975
870
  columns: r,
976
871
  signal: "link-only"
@@ -980,20 +875,20 @@ var re = () => {
980
875
  issues: t,
981
876
  tablesChecked: e.length
982
877
  };
983
- }, Qe = {
878
+ }, $e = {
984
879
  "composite-pk": "part of a composite primary key",
985
880
  "reference-sibling": "sits beside a wired reference() on this table",
986
881
  "link-only": "this table is nothing but link columns"
987
- }, $e = (e) => {
882
+ }, et = (e) => {
988
883
  if (e.tablesChecked === 0) return null;
989
884
  if (e.issues.length === 0) return `junction FKs: ${e.tablesChecked} table${e.tablesChecked === 1 ? "" : "s"} checked — every link column is a wired reference()`;
990
885
  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
886
  for (let t of e.issues) {
992
887
  let e = t.columns.map((e) => `'${e}'`).join(", ");
993
- n.push(` '${t.table}': ${e} (${Qe[t.signal]})`);
888
+ n.push(` '${t.table}': ${e} (${$e[t.signal]})`);
994
889
  }
995
890
  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
- }, et = /* @__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(".")), tt = (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()), nt = (e) => tt(e).some((e) => et.has(e)), rt = (e) => {
891
+ }, 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) => {
997
892
  let t = [], n = [];
998
893
  for (let r of e) t.push({
999
894
  event: r.name,
@@ -1002,7 +897,7 @@ var re = () => {
1002
897
  event: r.name,
1003
898
  kind: "wide-key",
1004
899
  keyFieldCount: r.keyFieldCount
1005
- }), r.hasWebhook && nt(r.name) && n.push({
900
+ }), r.hasWebhook && rt(r.name) && n.push({
1006
901
  event: r.name,
1007
902
  kind: "webhook-high-rate"
1008
903
  });
@@ -1011,26 +906,26 @@ var re = () => {
1011
906
  modes: t,
1012
907
  warnings: n
1013
908
  };
1014
- }, it = (e) => {
909
+ }, at = (e) => {
1015
910
  if (e.eventsChecked === 0) return null;
1016
911
  let t = e.eventsChecked, n = [`event delivery: ${t} declared event${t === 1 ? "" : "s"} — the mode decides what a MISSING message means`];
1017
912
  for (let t of e.modes) n.push(` '${t.event}': ${t.delivery}`);
1018
913
  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
- }, at = (e) => {
914
+ }, ot = (e) => {
1020
915
  if (e.warnings.length === 0) return null;
1021
916
  let t = e.warnings.length, n = [`events that may not scale as declared: ${t} finding${t === 1 ? "" : "s"}`];
1022
917
  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
918
  return n.push(" WARNING, not a fault — a producer may intend the fan-out. `voltro doctor --json` prints every finding."), n.join("\n");
1024
- }, ot = /* @__PURE__ */ new Set([
919
+ }, st = /* @__PURE__ */ new Set([
1025
920
  "start",
1026
921
  "run",
1027
922
  "child"
1028
- ]), st = (e) => k.isCallExpression(e) && (() => {
923
+ ]), ct = (e) => k.isCallExpression(e) && (() => {
1029
924
  let t = e.getExpression();
1030
- if (!k.isPropertyAccessExpression(t) || !ot.has(t.getName())) return !1;
925
+ if (!k.isPropertyAccessExpression(t) || !st.has(t.getName())) return !1;
1031
926
  let n = t.getExpression();
1032
927
  return k.isPropertyAccessExpression(n) ? n.getName() === "workflows" : k.isIdentifier(n) && n.getText() === "workflows";
1033
- })(), ct = (e, t, n) => {
928
+ })(), lt = (e, t, n) => {
1034
929
  let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new A({
1035
930
  useInMemoryFileSystem: !0,
1036
931
  skipAddingFilesFromTsConfig: !0
@@ -1046,7 +941,7 @@ var re = () => {
1046
941
  }
1047
942
  let u = S(n, t);
1048
943
  for (let t of e.getDescendantsOfKind(j.CallExpression)) {
1049
- if (!st(t)) continue;
944
+ if (!ct(t)) continue;
1050
945
  c++;
1051
946
  let e = t.getStartLineNumber(), [n, l] = t.getArguments();
1052
947
  if (n === void 0 || !k.isStringLiteral(n)) {
@@ -1130,7 +1025,7 @@ var re = () => {
1130
1025
  checked: s,
1131
1026
  callSites: c
1132
1027
  };
1133
- }, lt = (e) => {
1028
+ }, ut = (e) => {
1134
1029
  if (e.callSites === 0) return null;
1135
1030
  let t = [];
1136
1031
  t.push(`workflow starts: ${e.checked}/${e.callSites} call sites checked`);
@@ -1143,7 +1038,7 @@ var re = () => {
1143
1038
  for (let n of e.unchecked) t.push(` ${n.file}:${n.line} — ${n.reason}`);
1144
1039
  }
1145
1040
  return e.issues.length === 0 && e.unchecked.length === 0 && t.push(" every call site matches its workflow"), t.join("\n");
1146
- }, ut = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, W = "VOLTRO_FIELD_ENCRYPTION_KEY", dt = (e) => {
1041
+ }, dt = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, U = "VOLTRO_FIELD_ENCRYPTION_KEY", ft = (e) => {
1147
1042
  let t;
1148
1043
  for (let [n, r] of e.sources) if (t === void 0 && /\bfieldEncryption\b/.test(r) && (t = {
1149
1044
  reason: "fieldEncryption",
@@ -1151,13 +1046,13 @@ var re = () => {
1151
1046
  }), t === void 0 && /\.encrypted\s*\(/.test(r) && (t = {
1152
1047
  reason: "encrypted-column",
1153
1048
  file: n
1154
- }), r.includes(W)) return;
1049
+ }), r.includes(U)) return;
1155
1050
  if (t !== void 0) {
1156
- for (let [, t] of e.envFiles) if (t.includes(W)) return;
1051
+ for (let [, t] of e.envFiles) if (t.includes(U)) return;
1157
1052
  return t;
1158
1053
  }
1159
- }, ft = (e) => [
1160
- `✗ ${W} is named nowhere under this app root, and the app encrypts columns at rest`,
1054
+ }, pt = (e) => [
1055
+ `✗ ${U} is named nowhere under this app root, and the app encrypts columns at rest`,
1161
1056
  e.reason === "fieldEncryption" ? ` (\`fieldEncryption\` in ${e.file})` : ` (an \`.encrypted()\` column in ${e.file})`,
1162
1057
  "",
1163
1058
  " A deploy without it does not degrade — it refuses to boot at `onActivate`,",
@@ -1173,99 +1068,14 @@ var re = () => {
1173
1068
  " reaches your deploy target is not knowable from here — a secret manager",
1174
1069
  " or a CI variable is not a file. Naming it nowhere is conclusive; naming",
1175
1070
  " it somewhere is not a guarantee."
1176
- ].join("\n"), pt = (e) => e === "effect" || e.startsWith("@effect/") || e.startsWith("@voltro/") || e === "react" || e === "react-dom", mt = /(^|\/)(\.bin|\.cache|\.pnpm-store|dist|build)(\/|$)/, ht = (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
- }, gt = (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
- }, _t = (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(".") || mt.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
- }, vt = (e) => {
1228
- let t = /* @__PURE__ */ new Map();
1229
- for (let n of ht(e)) for (let { pkg: e, dir: r } of _t(n)) {
1230
- if (!pt(e)) continue;
1231
- let n = gt(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
- }, yt = (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
- }, bt = (e) => {
1071
+ ].join("\n"), mt = (e) => {
1262
1072
  if (e.catalogCodes.length === 0) return;
1263
1073
  let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
1264
1074
  if (n.length !== 0) return {
1265
1075
  orphaned: n,
1266
1076
  noneDeclared: t.size === 0
1267
1077
  };
1268
- }, xt = (e, t) => {
1078
+ }, ht = (e, t) => {
1269
1079
  let n = e.orphaned.join(", ");
1270
1080
  return e.noneDeclared ? [
1271
1081
  `⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
@@ -1277,49 +1087,49 @@ var re = () => {
1277
1087
  " …or delete the files. A catalog that is never loaded looks from the inside",
1278
1088
  " exactly like one that works, which is why this is worth a line."
1279
1089
  ] : [`⚠ ${t} has catalog(s) not in \`locales:\` — ${n} — so they are never imported.`, " Add the code(s) to `locales:` in your app config, or delete the file(s)."];
1280
- }, St = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), Ct = /* @__PURE__ */ new Set([
1090
+ }, gt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), _t = /* @__PURE__ */ new Set([
1281
1091
  "requireScope",
1282
1092
  "requireAnyScope",
1283
1093
  "requireAuth",
1284
1094
  "assertScope",
1285
1095
  "assertCan",
1286
1096
  "can"
1287
- ]), wt = (e) => {
1097
+ ]), vt = (e) => {
1288
1098
  let t = /* @__PURE__ */ new Set();
1289
- for (let n of e) for (let e of St(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
1099
+ for (let n of e) for (let e of gt(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
1290
1100
  return t;
1291
- }, Tt = (e, t) => {
1101
+ }, yt = (e, t) => {
1292
1102
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
1293
- }, Et = (e) => {
1103
+ }, bt = (e) => {
1294
1104
  let t = /\bsubject\s*\.\s*id\b[^\n]*?(?:!==|===|!=|==)|(?:!==|===|!=|==)[^\n]*?\bsubject\s*\.\s*id\b/.test(e), n = /(?:Error|Denied|Forbidden|Unauthorized)\b|\b(?:fail|die|throw)\b/.test(e);
1295
1105
  return t && n;
1296
- }, Dt = (e, t, n) => {
1106
+ }, xt = (e, t, n) => {
1297
1107
  if (e.hasDeclaredGuards) return { kind: "declared-guard" };
1298
- let r = St(e.src), i = Tt(r, t);
1299
- return i === void 0 ? Et(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
1108
+ let r = gt(e.src), i = yt(r, t);
1109
+ return i === void 0 ? bt(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
1300
1110
  kind: "vocabulary-guard",
1301
1111
  name: i
1302
1112
  };
1303
- }, Ot = (e, t, n) => {
1113
+ }, St = (e, t, n) => {
1304
1114
  let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
1305
1115
  return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
1306
- }, kt = (e, t) => {
1116
+ }, Ct = (e, t, n) => {
1307
1117
  switch (t.kind) {
1308
1118
  case "declared-guard": return "guards: on the descriptor";
1309
1119
  case "vocabulary-guard": return `calls ${t.name}()`;
1310
1120
  case "inline-ownership": return "compares the caller to the row and refuses — correct, but only this file knows it";
1311
1121
  case "allowlisted": return "accepted by a previous run";
1312
- case "unchecked": return 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`;
1122
+ case "unchecked": return n ? e.op === "read" ? "no check in this file — this app registers a row filter; VERIFY it covers this table, then record it as reviewed= rather than debt" : `${e.op}s${e.table ? ` ${e.table}` : ""} with no check in this file — this app registers a row filter; VERIFY it covers this table, then record it as reviewed= rather than debt` : e.op === "read" ? "returns a row the client selected, with nothing compared to the caller" : `${e.op}s${e.table ? ` ${e.table}` : ""} with nothing constraining WHICH row`;
1313
1123
  }
1314
- }, At = (e) => {
1124
+ }, wt = (e) => {
1315
1125
  let t = e.executors.map((t) => {
1316
- let n = Dt(t, e.guardVocabulary, e.allowlist);
1126
+ let n = xt(t, e.guardVocabulary, e.allowlist);
1317
1127
  return {
1318
1128
  tag: t.tag,
1319
1129
  path: t.path,
1320
1130
  verdict: n,
1321
- severity: Ot(t, e.tenantScopedTables, e.referencedTables),
1322
- why: kt(t, n)
1131
+ severity: St(t, e.tenantScopedTables, e.referencedTables),
1132
+ why: Ct(t, n, e.rowFilterRegistered === !0)
1323
1133
  };
1324
1134
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
1325
1135
  return {
@@ -1333,7 +1143,7 @@ var re = () => {
1333
1143
  },
1334
1144
  unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
1335
1145
  };
1336
- }, jt = [
1146
+ }, Tt = [
1337
1147
  "# voltro doctor — authz allowlist",
1338
1148
  "#",
1339
1149
  "# Two KINDS of line, and the difference is the point:",
@@ -1358,21 +1168,46 @@ var re = () => {
1358
1168
  "# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
1359
1169
  "# lines for executors that are newly unchecked.",
1360
1170
  ""
1361
- ], Mt = (e) => {
1171
+ ], Et = (e) => {
1362
1172
  let t = [...e].map((e) => typeof e == "string" ? { tag: e } : e), n = t.filter((e) => e.reviewed === void 0).map((e) => e.tag).sort(), r = t.filter((e) => e.reviewed !== void 0).sort((e, t) => e.tag.localeCompare(t.tag));
1363
1173
  return [
1364
- ...jt,
1174
+ ...Tt,
1365
1175
  ...n,
1366
1176
  ...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
1367
1177
  ...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
1368
1178
  ""
1369
1179
  ].join("\n");
1370
- }, Nt = class extends Error {
1180
+ }, Dt = (e, t) => {
1181
+ let n = [...new Set(t)].sort();
1182
+ if (e === void 0) return Et(n.map((e) => ({ tag: e })));
1183
+ let r = W(e).tags, i = n.filter((e) => !r.has(e));
1184
+ return i.length === 0 ? e : [
1185
+ e.replace(/\n+$/, ""),
1186
+ "",
1187
+ "# added by `voltro doctor --write-authz-allowlist` — newly unchecked executors.",
1188
+ "# Nothing above this line was touched.",
1189
+ ...i,
1190
+ ""
1191
+ ].join("\n");
1192
+ }, Ot = (e, t) => {
1193
+ let n = [...new Set(t)];
1194
+ if (e === void 0) return {
1195
+ added: n.length,
1196
+ kept: 0,
1197
+ removed: 0
1198
+ };
1199
+ let r = W(e).tags;
1200
+ return {
1201
+ added: n.filter((e) => !r.has(e)).length,
1202
+ kept: r.size,
1203
+ removed: 0
1204
+ };
1205
+ }, kt = class extends Error {
1371
1206
  tag;
1372
1207
  constructor(e) {
1373
1208
  super(`authz allowlist: '${e}' is marked reviewed with no reason. Write \`${e} reviewed=<why it is genuinely open>\`. A bare tag is the other, honest option — it records the executor as DEBT, which is what an unexplained exemption is.`), this.tag = e, this.name = "AllowlistReasonMissingError";
1374
1209
  }
1375
- }, Pt = (e) => {
1210
+ }, W = (e) => {
1376
1211
  let t = [];
1377
1212
  for (let n of e.split("\n")) {
1378
1213
  let e = n.trim();
@@ -1385,7 +1220,7 @@ var re = () => {
1385
1220
  continue;
1386
1221
  }
1387
1222
  let a = r[2].trim();
1388
- if (a.length === 0) throw new Nt(i);
1223
+ if (a.length === 0) throw new kt(i);
1389
1224
  t.push({
1390
1225
  tag: i,
1391
1226
  reviewed: a
@@ -1395,13 +1230,13 @@ var re = () => {
1395
1230
  tags: new Set(t.map((e) => e.tag)),
1396
1231
  entries: t
1397
1232
  };
1398
- }, Ft = (e) => {
1233
+ }, At = (e) => {
1399
1234
  let t = e.indexOf(".");
1400
1235
  return t <= 0 ? void 0 : e.slice(0, t);
1401
- }, It = (e, t) => {
1236
+ }, jt = (e, t) => {
1402
1237
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
1403
1238
  for (let e of t.rpcTags) {
1404
- let t = Ft(e);
1239
+ let t = At(e);
1405
1240
  if (t === void 0) continue;
1406
1241
  let n = i.get(t);
1407
1242
  n ? n.push(e) : i.set(t, [e]);
@@ -1430,7 +1265,7 @@ var re = () => {
1430
1265
  });
1431
1266
  continue;
1432
1267
  }
1433
- let s = new Set(o.rpcTags.map(Ft).filter((e) => e !== void 0));
1268
+ let s = new Set(o.rpcTags.map(At).filter((e) => e !== void 0));
1434
1269
  for (let e of s) {
1435
1270
  let t = i.get(e);
1436
1271
  t !== void 0 && a.push({
@@ -1443,13 +1278,13 @@ var re = () => {
1443
1278
  }
1444
1279
  }
1445
1280
  return a;
1446
- }, Lt = (e, t) => {
1281
+ }, Mt = (e, t) => {
1447
1282
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
1448
1283
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
1449
1284
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
1450
1285
  return r;
1451
- }, Rt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), zt = (e) => {
1452
- let t = y(e), n = v(e);
1286
+ }, Nt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Pt = (e) => {
1287
+ let t = re(e), n = ne(e);
1453
1288
  return {
1454
1289
  root: e,
1455
1290
  serveBundle: t,
@@ -1457,19 +1292,19 @@ var re = () => {
1457
1292
  apiBundle: n,
1458
1293
  apiBundlePresent: T(n)
1459
1294
  };
1460
- }, Bt = [
1295
+ }, Ft = [
1461
1296
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
1462
1297
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
1463
1298
  "(Generated Dockerfiles from `voltro create-project` already do this.)"
1464
- ].join("\n "), Vt = (e, t = !1) => {
1465
- let n = zt(e);
1466
- if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Rt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
1299
+ ].join("\n "), It = (e, t = !1) => {
1300
+ let n = Pt(e);
1301
+ if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Nt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
1467
1302
  console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
1468
1303
  let r = process.env.NODE_ENV === "production";
1469
1304
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
1470
1305
  let i = t || r;
1471
- return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Bt}`), +!!i;
1472
- }, Ht = (e, t, n) => {
1306
+ return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Ft}`), +!!i;
1307
+ }, Lt = (e, t, n) => {
1473
1308
  let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
1474
1309
  `${e}.ts`,
1475
1310
  `${e}.tsx`,
@@ -1495,7 +1330,7 @@ var re = () => {
1495
1330
  r !== void 0 && o.push(r);
1496
1331
  }
1497
1332
  return o;
1498
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Jt.test(S(n, e))), l = new Set(c);
1333
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Ut.test(S(n, e))), l = new Set(c);
1499
1334
  for (; c.length > 0;) {
1500
1335
  let e = c.shift();
1501
1336
  if (!s.has(e)) {
@@ -1508,11 +1343,11 @@ var re = () => {
1508
1343
  let d = /* @__PURE__ */ new Set();
1509
1344
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
1510
1345
  return d;
1511
- }, Ut = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Wt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Gt = (e, t) => {
1346
+ }, Rt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), zt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Bt = (e, t) => {
1512
1347
  if (e.src.includes(t)) return !0;
1513
1348
  let n = t.replace(/^@voltro\/plugin-/, "");
1514
1349
  return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
1515
- }, Kt = (e, t) => {
1350
+ }, Vt = (e, t) => {
1516
1351
  let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
1517
1352
  for (; (i = r.exec(e)) !== null;) {
1518
1353
  let t = i.index + i[0].length, r = 1, a = t;
@@ -1520,10 +1355,10 @@ var re = () => {
1520
1355
  n.push(e.slice(t, a - 1));
1521
1356
  }
1522
1357
  return n;
1523
- }, qt = (e, t) => {
1358
+ }, Ht = (e, t) => {
1524
1359
  let n = t.getSymbol()?.getDeclarations()?.[0];
1525
1360
  return n === void 0 ? [] : e.getDescendantsOfKind(j.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
1526
- }, Jt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Yt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Xt = [
1361
+ }, Ut = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Wt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Gt = [
1527
1362
  "Id",
1528
1363
  "ID",
1529
1364
  "Hash",
@@ -1549,12 +1384,12 @@ var re = () => {
1549
1384
  "Comment",
1550
1385
  "Provider",
1551
1386
  "Format"
1552
- ], Zt = RegExp(`(?:${Xt.join("|")})$|_(?:${Xt.map((e) => e.toLowerCase()).join("|")})$`), Qt = [
1387
+ ], Kt = RegExp(`(?:${Gt.join("|")})$|_(?:${Gt.map((e) => e.toLowerCase()).join("|")})$`), qt = [
1553
1388
  {
1554
1389
  id: "hardcoded-framework-cookie",
1555
1390
  scope: "client",
1556
1391
  smell: "a framework cookie name written as a string literal instead of imported",
1557
- use: "import the constant — `LOCALE_COOKIE` / `THEME_COOKIE` from `@voltro/ui-shadcn` — so a rename in the framework is a compile error in your app rather than a preference that quietly stops working",
1392
+ use: "import the constant — `LOCALE_COOKIE` / `THEME_COOKIE` from `@voltro/i18n` (or from `@voltro/ui-shadcn` if you use the kit) — so a rename in the framework is a compile error in your app rather than a preference that quietly stops working",
1558
1393
  match: ({ src: e }) => /['"`]voltro:(?:locale|theme|lang)['"`]/.test(e)
1559
1394
  },
1560
1395
  {
@@ -1562,7 +1397,7 @@ var re = () => {
1562
1397
  scope: "server",
1563
1398
  smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
1564
1399
  use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
1565
- match: ({ src: e, path: t }) => Yt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1400
+ match: ({ src: e, path: t }) => Wt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1566
1401
  },
1567
1402
  {
1568
1403
  id: "subject-write-no-guard",
@@ -1570,17 +1405,17 @@ var re = () => {
1570
1405
  smell: "writes ctx.request.subject.id into the database with no guard on the descriptor — an anonymous caller reaches the write and gets a raw statement failure instead of a typed refusal",
1571
1406
  use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
1572
1407
  match: ({ src: e, path: t }) => {
1573
- if (!Yt.test(t)) return !1;
1574
- let n = Ut(e);
1408
+ if (!Wt.test(t)) return !1;
1409
+ let n = Rt(e);
1575
1410
  return /\bsubject\s*\.\s*id\b/.test(n) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(n) && !/\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(n);
1576
1411
  }
1577
1412
  },
1578
1413
  {
1579
1414
  id: "raw-fetch",
1580
1415
  scope: "server",
1581
- smell: "raw fetch() for outbound HTTP on the server (no SSRF guard, no traceparent, no retry)",
1582
- use: "yield* HttpClient — the framework client. It blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop, injects traceparent, and honours `http.allowHosts` for deliberate internal targets",
1583
- match: ({ file: e, path: t, serverOnly: n }) => (Jt.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1416
+ smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
1417
+ use: "yield* HttpClient — the framework client. It injects traceparent, retries, and honours `http.allowHosts`. Where the URL is built from anything a caller supplied it ALSO blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop for a hard-coded host that half is redundant, and the first three are still the reason to switch",
1418
+ match: ({ file: e, path: t, serverOnly: n }) => (Ut.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1584
1419
  },
1585
1420
  {
1586
1421
  id: "form",
@@ -1615,7 +1450,7 @@ var re = () => {
1615
1450
  scope: "client",
1616
1451
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
1617
1452
  use: "useDerived — dependency-tracked, referentially stable",
1618
- match: ({ src: e }) => Wt(e, /\buseSubscription\s*[(<]/g) >= 3 && Wt(e, /\buseMemo\s*\(/g) >= 3
1453
+ match: ({ src: e }) => zt(e, /\buseSubscription\s*[(<]/g) >= 3 && zt(e, /\buseMemo\s*\(/g) >= 3
1619
1454
  },
1620
1455
  {
1621
1456
  id: "next-shim",
@@ -1640,7 +1475,7 @@ var re = () => {
1640
1475
  match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
1641
1476
  let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
1642
1477
  return t === void 0 ? [] : k.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
1643
- }).some((t) => qt(e, t).some((e) => {
1478
+ }).some((t) => Ht(e, t).some((e) => {
1644
1479
  let t = e.getParent();
1645
1480
  if (k.isBinaryExpression(t)) {
1646
1481
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -1691,7 +1526,17 @@ var re = () => {
1691
1526
  use: ".encrypted() on the column — boot fails loudly if no cipher is configured, so it can never silently persist plaintext (a *Hash column and a vault* handle are NOT plaintext secrets: the hash IS the protection — often a unique lookup key — and the handle only names a secret held elsewhere, so both are skipped)",
1692
1527
  match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
1693
1528
  let n = t[1];
1694
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Zt.test(n) || /^vault/i.test(n) ? !1 : !/\.encrypted\s*\(/.test(e);
1529
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Kt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1530
+ }))
1531
+ },
1532
+ {
1533
+ id: "plaintext-secret-lookup-key",
1534
+ scope: "server",
1535
+ smell: "a credential-shaped column that is ALSO a unique lookup key",
1536
+ use: "you cannot look a value up by equality AND encrypt it non-deterministically — `.encrypted()` would make this column unfindable. Store the HASH as the unique lookup key and keep the secret itself either `.encrypted()` in a second column or nowhere (the framework's own `_voltro_api_keys.hashedKey` is the shape: the presented value is hashed and compared, never read back). A deterministic cipher is the other option and buys equality lookup at the cost of leaking which rows share a value",
1537
+ match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
1538
+ let n = t[1];
1539
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Kt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1695
1540
  }))
1696
1541
  },
1697
1542
  {
@@ -1756,8 +1601,8 @@ var re = () => {
1756
1601
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
1757
1602
  use: "wire the filter into at least one test (`makeTestContext({ subject, store, rowFilter })`) and sabotage the predicate once to watch it go red — an untested row filter is a visibility rule nothing verifies",
1758
1603
  match: ({ file: e, testSources: t }) => G(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1759
- let t = Ut(e);
1760
- return Kt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1604
+ let t = Rt(e);
1605
+ return Vt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1761
1606
  })
1762
1607
  },
1763
1608
  {
@@ -1767,14 +1612,14 @@ var re = () => {
1767
1612
  use: "timestampMs / timestampMsOrNull from @voltro/database/wire in the descriptor's output struct — `output` IS the serializer (it is the rpc success schema), so a Date field declared as timestampMs crosses as epoch ms on its own; declaring Schema.Number describes the WIRE type and leaves nothing to convert",
1768
1613
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1769
1614
  }
1770
- ], $t = (e) => q(e), en = (e, t) => {
1615
+ ], Jt = (e) => q(e), Yt = (e, t) => {
1771
1616
  let n = /* @__PURE__ */ new Set();
1772
1617
  for (let r of t) {
1773
1618
  let t = S(e, r), i = t.split(/[\\/]/)[0];
1774
1619
  n.add(i !== void 0 && i !== t ? i : ".");
1775
1620
  }
1776
1621
  return [...n].sort();
1777
- }, tn = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, nn = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, rn = (e, t = [], n = 0) => {
1622
+ }, Xt = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Zt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Qt = (e, t = [], n = 0) => {
1778
1623
  if (n > 12 || K.test(e)) return t;
1779
1624
  let r;
1780
1625
  try {
@@ -1786,8 +1631,8 @@ var re = () => {
1786
1631
  let r = x(e, i.name);
1787
1632
  if (i.isDirectory()) {
1788
1633
  if (i.name.startsWith(".")) continue;
1789
- rn(r, t, n + 1);
1790
- } else ut.test(i.name) && t.push(r);
1634
+ Qt(r, t, n + 1);
1635
+ } else dt.test(i.name) && t.push(r);
1791
1636
  }
1792
1637
  return t;
1793
1638
  }, q = (e, t = [], n = 0) => {
@@ -1805,10 +1650,10 @@ var re = () => {
1805
1650
  } catch {
1806
1651
  continue;
1807
1652
  }
1808
- a.isDirectory() ? q(r, t, n + 1) : tn.test(i) && !/\.(test|spec)\./.test(i) && !nn.test(i) && t.push(r);
1653
+ a.isDirectory() ? q(r, t, n + 1) : Xt.test(i) && !/\.(test|spec)\./.test(i) && !Zt.test(i) && t.push(r);
1809
1654
  }
1810
1655
  return t;
1811
- }, an = (e, t = [], n = 0) => {
1656
+ }, $t = (e, t = [], n = 0) => {
1812
1657
  if (n > 12 || K.test(e)) return t;
1813
1658
  let r;
1814
1659
  try {
@@ -1823,17 +1668,17 @@ var re = () => {
1823
1668
  } catch {
1824
1669
  continue;
1825
1670
  }
1826
- a.isDirectory() ? an(r, t, n + 1) : tn.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1671
+ a.isDirectory() ? $t(r, t, n + 1) : Xt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1827
1672
  }
1828
1673
  return t;
1829
- }, on = (e) => {
1830
- let t = $t(e);
1674
+ }, en = (e) => {
1675
+ let t = Jt(e);
1831
1676
  return {
1832
- dirs: en(e, t),
1677
+ dirs: Yt(e, t),
1833
1678
  fileCount: t.length
1834
1679
  };
1835
- }, sn = (e) => {
1836
- let t = $t(e);
1680
+ }, tn = (e) => {
1681
+ let t = Jt(e);
1837
1682
  if (t.length === 0) return [];
1838
1683
  let n = new A({
1839
1684
  useInMemoryFileSystem: !0,
@@ -1842,8 +1687,8 @@ var re = () => {
1842
1687
  for (let e of t) try {
1843
1688
  i.set(e, E(e, "utf8"));
1844
1689
  } catch {}
1845
- let a = Ht([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1846
- for (let t of an(e)) try {
1690
+ let a = Lt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1691
+ for (let t of $t(e)) try {
1847
1692
  o.set(t, E(t, "utf8"));
1848
1693
  } catch {}
1849
1694
  for (let s of t) {
@@ -1861,23 +1706,23 @@ var re = () => {
1861
1706
  } catch {
1862
1707
  continue;
1863
1708
  }
1864
- for (let t of Qt) {
1865
- if (t.plugin !== void 0 && Gt(c, t.plugin) || !t.match(c)) continue;
1709
+ for (let t of qt) {
1710
+ if (t.plugin !== void 0 && Bt(c, t.plugin) || !t.match(c)) continue;
1866
1711
  let n = r.get(t.id) ?? [];
1867
1712
  n.push(S(e, s)), r.set(t.id, n);
1868
1713
  }
1869
1714
  }
1870
- return Qt.filter((e) => r.has(e.id)).map((e) => ({
1715
+ return qt.filter((e) => r.has(e.id)).map((e) => ({
1871
1716
  rule: e,
1872
1717
  files: r.get(e.id)
1873
1718
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1874
- }, cn = (e) => {
1875
- let t = on(e);
1719
+ }, nn = (e) => {
1720
+ let t = en(e);
1876
1721
  return {
1877
1722
  root: e,
1878
1723
  scannedFiles: t.fileCount,
1879
1724
  scannedDirs: t.dirs,
1880
- findings: sn(e).map(({ rule: e, files: t }) => ({
1725
+ findings: tn(e).map(({ rule: e, files: t }) => ({
1881
1726
  id: e.id,
1882
1727
  scope: e.scope,
1883
1728
  smell: e.smell,
@@ -1885,22 +1730,22 @@ var re = () => {
1885
1730
  files: t
1886
1731
  }))
1887
1732
  };
1888
- }, ln = 3, un = .2, dn = 50, fn = (e, t) => e >= dn && t > 0 && e / t >= un, pn = (e) => {
1733
+ }, rn = 3, an = .2, on = 50, sn = (e, t) => e >= on && t > 0 && e / t >= an, cn = (e) => {
1889
1734
  let t = /* @__PURE__ */ new Map();
1890
1735
  for (let n of q(e)) try {
1891
1736
  t.set(S(e, n), E(n, "utf8"));
1892
1737
  } catch {}
1893
1738
  let n = /* @__PURE__ */ new Map();
1894
- for (let t of rn(e)) try {
1739
+ for (let t of Qt(e)) try {
1895
1740
  n.set(S(e, t), E(t, "utf8"));
1896
1741
  } catch {}
1897
- let r = dt({
1742
+ let r = ft({
1898
1743
  sources: t,
1899
1744
  envFiles: n
1900
1745
  });
1901
- r !== void 0 && (console.log(""), console.log(ft(r)));
1902
- }, mn = (e) => {
1903
- let t = cn(e);
1746
+ r !== void 0 && (console.log(""), console.log(pt(r)));
1747
+ }, ln = (e) => {
1748
+ let t = nn(e);
1904
1749
  if (console.log(""), t.scannedFiles === 0) return console.log(`\u2022 hand-roll scan: no source directories found under ${e}.`), console.log(" Walked the app root recursively (node_modules / dist / .framework pruned). Run `voltro doctor <app>` against an app root."), 0;
1905
1750
  if (t.findings.length === 0) return console.log(`\u2713 hand-roll scan: ${t.scannedFiles} file${t.scannedFiles === 1 ? "" : "s"} across ${t.scannedDirs.join(", ")} \u2014 no hand-rolled primitives found.`), 0;
1906
1751
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
@@ -1911,56 +1756,56 @@ var re = () => {
1911
1756
  console.log(` [${e}]`);
1912
1757
  let i = [...r].sort((e, t) => e.files.length - t.files.length);
1913
1758
  for (let e of i) {
1914
- if (fn(e.files.length, t.scannedFiles)) {
1759
+ if (sn(e.files.length, t.scannedFiles)) {
1915
1760
  let n = Math.round(e.files.length / t.scannedFiles * 100);
1916
1761
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s), ${n}% of those scanned: a codebase-wide pattern rather than a work list`), console.log(` \u2192 ${e.use}`), console.log(" (paths omitted on purpose at this scale — `voltro doctor --json` has every one.)");
1917
1762
  continue;
1918
1763
  }
1919
- let r = e.files.slice(0, ln).join(", "), i = e.files.length - ln;
1764
+ let r = e.files.slice(0, rn).join(", "), i = e.files.length - rn;
1920
1765
  i > 0 && (n += i);
1921
1766
  let a = i > 0 ? `, +${i} more` : "";
1922
1767
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1923
1768
  }
1924
1769
  }
1925
1770
  return n > 0 && console.log(` (${n} more file path${n === 1 ? "" : "s"} not shown \u2014 \`voltro doctor --json\` prints every one.)`), console.log(" (See the \"Pick the SERVER primitive\" / \"Pick the CLIENT primitive\" rubrics in AGENTS.md / CLAUDE.md.)"), t.findings.length;
1926
- }, hn = "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.", gn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, _n = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, vn = (e) => {
1927
- let t = gn.exec(e)?.[1];
1771
+ }, un = "renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.", dn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, fn = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, pn = (e) => {
1772
+ let t = dn.exec(e)?.[1];
1928
1773
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1929
- let n = _n.exec(e)?.[1];
1774
+ let n = fn.exec(e)?.[1];
1930
1775
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1931
- }, yn = async (e) => {
1932
- let n = x(e, "src", "pages");
1933
- if (!T(n)) return [];
1934
- let { pages: a, dirs: o } = await t(n), s = [];
1935
- for (let t of a) {
1936
- if (!i(o, t.dirChain).some((e) => e.hasLayout)) continue;
1937
- let a;
1776
+ }, mn = async (e) => {
1777
+ let t = x(e, "src", "pages");
1778
+ if (!T(t)) return [];
1779
+ let { pages: i, dirs: o } = await n(t), s = [];
1780
+ for (let n of i) {
1781
+ if (!a(o, n.dirChain).some((e) => e.hasLayout)) continue;
1782
+ let i;
1938
1783
  try {
1939
- a = E(x(n, t.file), "utf8");
1784
+ i = E(x(t, n.file), "utf8");
1940
1785
  } catch {
1941
1786
  continue;
1942
1787
  }
1943
- if (r.test(a)) continue;
1944
- let c = vn(a);
1788
+ if (r.test(i)) continue;
1789
+ let c = pn(i);
1945
1790
  c !== null && s.push({
1946
- file: S(e, x(n, t.file)),
1947
- pattern: t.pattern,
1791
+ file: S(e, x(t, n.file)),
1792
+ pattern: n.pattern,
1948
1793
  currentMode: c
1949
1794
  });
1950
1795
  }
1951
1796
  return s;
1952
- }, bn = 10, xn = async (e) => {
1797
+ }, hn = 10, gn = async (e) => {
1953
1798
  if (console.log(""), !T(x(e, "src", "pages"))) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
1954
- let t = await yn(e);
1799
+ let t = await mn(e);
1955
1800
  if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1956
1801
  console.log(`• renderMode:'spa' candidates (${t.length} page${t.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1957
- for (let e of t.slice(0, bn)) {
1802
+ for (let e of t.slice(0, hn)) {
1958
1803
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1959
1804
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1960
1805
  }
1961
- let n = t.length - bn;
1962
- return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${hn}`), t.length;
1963
- }, Sn = /* @__PURE__ */ new Set([
1806
+ let n = t.length - hn;
1807
+ return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${un}`), t.length;
1808
+ }, _n = /* @__PURE__ */ new Set([
1964
1809
  "node_modules",
1965
1810
  "dist",
1966
1811
  "build",
@@ -1968,7 +1813,7 @@ var re = () => {
1968
1813
  ".vite",
1969
1814
  ".framework",
1970
1815
  ".git"
1971
- ]), Cn = (e, t, n) => {
1816
+ ]), vn = (e, t, n) => {
1972
1817
  let r;
1973
1818
  try {
1974
1819
  r = D(e, { withFileTypes: !0 });
@@ -1976,11 +1821,11 @@ var re = () => {
1976
1821
  return;
1977
1822
  }
1978
1823
  for (let i of r) {
1979
- if (Sn.has(i.name)) continue;
1824
+ if (_n.has(i.name)) continue;
1980
1825
  let r = x(e, i.name);
1981
- i.isDirectory() ? Cn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
1826
+ i.isDirectory() ? vn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
1982
1827
  }
1983
- }, wn = (e, t) => {
1828
+ }, yn = (e, t) => {
1984
1829
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1985
1830
  for (let i of t) {
1986
1831
  let t;
@@ -2005,62 +1850,70 @@ var re = () => {
2005
1850
  }
2006
1851
  }
2007
1852
  return n;
2008
- }, Tn = (e, t) => {
1853
+ }, bn = (e, t) => {
2009
1854
  let n = e.slice(t.length).split(w).slice(0, -1)[0];
2010
1855
  return n !== void 0 && n.startsWith("[");
2011
- }, J = (t) => {
2012
- let n = [], r = x(t, "src", "pages"), i = x(t, "src");
2013
- if (!T(i)) return n;
2014
- let a = [];
2015
- Cn(i, t, a), a.sort();
2016
- let o = `${S(t, r)}${w}`;
2017
- wn(t, a);
2018
- for (let r of a) {
2019
- let i = r.slice(r.lastIndexOf(w) + 1);
2020
- if (r.startsWith(o)) {
2021
- if (i !== "page.tsx") continue;
2022
- T(x(t, e(r))) || n.push({
1856
+ }, J = (e) => {
1857
+ let t = [], n = x(e, "src", "pages"), r = x(e, "src");
1858
+ if (!T(r)) return t;
1859
+ let i = [];
1860
+ vn(r, e, i), i.sort();
1861
+ let a = `${S(e, n)}${w}`;
1862
+ yn(e, i);
1863
+ for (let n of i) {
1864
+ let r = n.slice(n.lastIndexOf(w) + 1);
1865
+ if (n.startsWith(a)) {
1866
+ if (r !== "page.tsx") continue;
1867
+ T(x(e, c(n))) || t.push({
2023
1868
  kind: "page-without-test",
2024
- file: r
2025
- }), Tn(r, o) && n.push({
1869
+ file: n
1870
+ });
1871
+ try {
1872
+ let r = E(x(e, n), "utf8");
1873
+ !/\bexport\s+(?:const|let|var|function)\s+meta\b/.test(r) && !/\bexport\s*\{[^}]*\bmeta\b/.test(r) && t.push({
1874
+ kind: "page-without-meta",
1875
+ file: n
1876
+ });
1877
+ } catch {}
1878
+ bn(n, a) && t.push({
2026
1879
  kind: "top-level-dynamic-route",
2027
- file: r
1880
+ file: n
2028
1881
  });
2029
1882
  continue;
2030
1883
  }
2031
- i === "page.tsx" && n.push({
1884
+ r === "page.tsx" && t.push({
2032
1885
  kind: "suffixed-outside-pages",
2033
- file: r
1886
+ file: n
2034
1887
  });
2035
1888
  }
2036
- return n;
2037
- }, Y = 10, X = (e, t, n) => {
1889
+ return t;
1890
+ }, xn = 10, Y = (e, t, n) => {
2038
1891
  if (t.length === 0) return;
2039
1892
  console.log(`• ${e} (${t.length}):`);
2040
- for (let e of t.slice(0, Y)) console.log(` ${e}`);
2041
- let r = t.length - Y;
1893
+ for (let e of t.slice(0, xn)) console.log(` ${e}`);
1894
+ let r = t.length - xn;
2042
1895
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
2043
- }, En = (e) => {
1896
+ }, Sn = (e) => {
2044
1897
  if (console.log(""), !T(x(e, "src", "pages"))) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
2045
1898
  let t = J(e);
2046
1899
  if (t.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
2047
1900
  let n = (e) => t.filter((t) => t.kind === e).map((e) => e.file);
2048
- 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."), t.length;
2049
- }, Dn = "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.", On = /* @__PURE__ */ new Map(), Z = (e) => {
2050
- let t = On.get(e);
1901
+ return Y("will NEVER route — page.tsx outside src/pages/", n("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), Y("pages with no test beside them", n("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), Y("routes whose FIRST segment is dynamic — they claim every URL of that depth", n("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), Y("pages with no `meta` export — they serve the APP name as their title", n("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). If such a page also has no real body — it redirects from an effect, say — the missing title is the symptom rather than the bug: on an `ssr` route the server answers 200 with an empty document and the redirect happens only once JavaScript runs. Move it into the loader (`redirect(...)`, a real 303). Advisory."), t.length;
1902
+ }, Cn = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", wn = /* @__PURE__ */ new Map(), X = (e) => {
1903
+ let t = wn.get(e);
2051
1904
  if (t !== void 0) return t;
2052
1905
  let n = (async () => {
2053
- let { walk: t, loadDiscovered: n } = await import("./dev-DXhhZ7-f.js"), r = await t(e);
1906
+ let { walk: t, loadDiscovered: n } = await import("./dev-DOZVMVfi.js"), r = await t(e);
2054
1907
  return {
2055
1908
  files: r,
2056
1909
  discovered: await n(r)
2057
1910
  };
2058
1911
  })();
2059
- return On.set(e, n), n;
2060
- }, kn = async (e) => {
1912
+ return wn.set(e, n), n;
1913
+ }, Tn = async (e) => {
2061
1914
  let t;
2062
1915
  try {
2063
- let { files: n, discovered: r } = await Z(e), i = r.tables.map((e) => {
1916
+ let { files: n, discovered: r } = await X(e), i = r.tables.map((e) => {
2064
1917
  let t = e;
2065
1918
  return {
2066
1919
  name: t.tableName,
@@ -2068,12 +1921,12 @@ var re = () => {
2068
1921
  };
2069
1922
  });
2070
1923
  if (i.length === 0) return;
2071
- t = Ke(Ge(n, i, e));
1924
+ t = qe(Ke(n, i, e));
2072
1925
  } catch {
2073
1926
  return;
2074
1927
  }
2075
1928
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
2076
- }, An = (e) => e.map((e) => {
1929
+ }, En = (e) => e.map((e) => {
2077
1930
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
2078
1931
  name: e,
2079
1932
  type: typeof t?.type == "string" ? t.type : ""
@@ -2083,22 +1936,22 @@ var re = () => {
2083
1936
  columns: n,
2084
1937
  primaryKey: t.appliedPrimaryKey ?? []
2085
1938
  };
2086
- }), jn = async (e) => {
1939
+ }), Z = async (e) => {
2087
1940
  try {
2088
- let { discovered: t } = await Z(e);
2089
- return Ze(An(t.tables));
1941
+ let { discovered: t } = await X(e);
1942
+ return Qe(En(t.tables));
2090
1943
  } catch {
2091
1944
  return null;
2092
1945
  }
2093
- }, Mn = async (e) => {
2094
- let t = await jn(e);
1946
+ }, Dn = async (e) => {
1947
+ let t = await Z(e);
2095
1948
  if (t === null || t.tablesChecked === 0) return;
2096
- let n = $e(t);
1949
+ let n = et(t);
2097
1950
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
2098
- }, Nn = async (e) => {
1951
+ }, On = async (e) => {
2099
1952
  try {
2100
- let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await Z(e);
2101
- return rt(n.events.map((e) => {
1953
+ let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await X(e);
1954
+ return it(n.events.map((e) => {
2102
1955
  let n = e.descriptor, r = t(n.key);
2103
1956
  return {
2104
1957
  name: n.name,
@@ -2110,19 +1963,19 @@ var re = () => {
2110
1963
  } catch {
2111
1964
  return null;
2112
1965
  }
2113
- }, Pn = async (e) => {
2114
- let t = await Nn(e);
1966
+ }, kn = async (e) => {
1967
+ let t = await On(e);
2115
1968
  if (t === null || t.eventsChecked === 0) return;
2116
- let n = it(t);
1969
+ let n = at(t);
2117
1970
  n !== null && (console.log(""), console.log(`• ${n}`));
2118
- let r = at(t);
1971
+ let r = ot(t);
2119
1972
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
2120
- }, Fn = (e) => {
1973
+ }, An = (e) => {
2121
1974
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
2122
1975
  return r === void 0 ? n : `${r}: ${n}`;
2123
- }, In = async (e) => {
1976
+ }, jn = async (e) => {
2124
1977
  try {
2125
- let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await Z(e)).discovered.serverOnlyLeaks, r = t(n);
1978
+ let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await X(e)).discovered.serverOnlyLeaks, r = t(n);
2126
1979
  return {
2127
1980
  status: {
2128
1981
  checked: !0,
@@ -2133,17 +1986,17 @@ var re = () => {
2133
1986
  } catch (e) {
2134
1987
  return { status: {
2135
1988
  checked: !1,
2136
- reason: Fn(e)
1989
+ reason: An(e)
2137
1990
  } };
2138
1991
  }
2139
- }, Ln = async (e) => {
2140
- let { status: t, message: n } = await In(e);
1992
+ }, Mn = async (e) => {
1993
+ let { status: t, message: n } = await jn(e);
2141
1994
  return t.checked ? n === void 0 ? !1 : (console.log(""), console.log(`✗ [serverOnly] ${n}`), !0) : (console.log(""), console.log("• serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), console.log(" This audit is the automated check that a hash or a token cannot reach the wire, so"), console.log(" an app that leans on `.serverOnly()` should treat a persistent skip as a failure."), console.log(" `voltro doctor --json` reports it as `serverOnly.checked: false` for a CI gate."), !1);
2142
- }, Rn = async (e) => {
1995
+ }, Nn = async (e) => {
2143
1996
  let t;
2144
1997
  try {
2145
- let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await Z(e);
2146
- t = ct(r, i.workflows.map((e) => {
1998
+ let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await X(e);
1999
+ t = lt(r, i.workflows.map((e) => {
2147
2000
  let t = e.definition, r = t.payloadSchema === void 0 ? {
2148
2001
  required: [],
2149
2002
  optional: []
@@ -2157,18 +2010,59 @@ var re = () => {
2157
2010
  } catch {
2158
2011
  return;
2159
2012
  }
2160
- let n = lt(t);
2013
+ let n = ut(t);
2161
2014
  if (n === null) return;
2162
2015
  console.log("");
2163
2016
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
2164
2017
  console.log(`${r} ${n}`);
2165
- }, Q = "voltro-authz-allowlist.txt", zn = (e, t = (e) => {
2018
+ }, Q = "voltro-authz-allowlist.txt", Pn = (e, t = (e) => {
2166
2019
  try {
2167
2020
  return E(e, "utf8");
2168
2021
  } catch {
2169
2022
  return "";
2170
2023
  }
2171
- }) => /* @__PURE__ */ new Set([...Ct, ...wt(q(e).map(t))]), Bn = async (e) => {
2024
+ }) => /* @__PURE__ */ new Set([..._t, ...vt(q(e).map(t))]), Fn = async (e) => {
2025
+ try {
2026
+ let { discovered: t } = await X(e), n = t.events.map((e) => e.descriptor.name).filter((e) => typeof e == "string");
2027
+ if (n.length === 0) return [];
2028
+ let r = new Set(t.events.map((e) => e.file)), i = q(e).filter((e) => !r.has(e)).map((e) => {
2029
+ try {
2030
+ return E(e, "utf8");
2031
+ } catch {
2032
+ return "";
2033
+ }
2034
+ }).join("\n");
2035
+ return n.filter((e) => !i.includes(e)).sort();
2036
+ } catch {
2037
+ return [];
2038
+ }
2039
+ }, In = async (e) => {
2040
+ let t = await Fn(e);
2041
+ if (t.length !== 0) {
2042
+ console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
2043
+ for (let e of t) console.log(` ${e}`);
2044
+ console.log(""), console.log(" An event you declare is an event partners can subscribe to. One that"), console.log(" never fires is indistinguishable, from their side, from a broken"), console.log(" endpoint of their own — so they debug your contract instead of"), console.log(" reading it."), console.log(" ADVISORY: this looks for the event NAME in your sources. An emit"), console.log(" through a variable or an assembled name will read as missing here.");
2045
+ }
2046
+ }, Ln = async (e) => {
2047
+ try {
2048
+ let { offlineManifest: t, toInput: n } = await import("./checkCommand-ClRvH_SN.js"), { buildAppGraph: r, checkAppGraph: i } = await import("./appGraph-CvQCte0z.js"), a = await t(e);
2049
+ return i(r(n(a.manifest, a.tables))).filter((e) => e.rule === "rbac/unknown-scope").map((e) => ({
2050
+ node: e.node,
2051
+ ...e.file === void 0 ? {} : { file: e.file },
2052
+ detail: e.breaks.join("; "),
2053
+ fix: e.fix
2054
+ }));
2055
+ } catch {
2056
+ return [];
2057
+ }
2058
+ }, Rn = async (e) => {
2059
+ let t = await Ln(e);
2060
+ if (t.length !== 0) {
2061
+ console.log(""), console.log(`• ${t.length} procedure(s) require a scope no declared role grants:`);
2062
+ for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
2063
+ console.log(""), console.log(" Each of these is uncallable by every user in every role — not"), console.log(" misconfigured, DEAD — and nothing fails at boot to say so. The"), console.log(" usual cause is a typo or a rename that moved one side only."), console.log(` ${t[0].fix}`);
2064
+ }
2065
+ }, zn = async (e) => {
2172
2066
  let t = ["src/locales", "locales"], n, r = [];
2173
2067
  for (let i of t) {
2174
2068
  let t = x(e, i);
@@ -2180,23 +2074,23 @@ var re = () => {
2180
2074
  if (n === void 0 || r.length === 0) return;
2181
2075
  let i = [];
2182
2076
  try {
2183
- let { loadApiConfig: t } = await import("./dev-DXhhZ7-f.js");
2077
+ let { loadApiConfig: t } = await import("./dev-DOZVMVfi.js");
2184
2078
  i = (await t(e))?.locales ?? [];
2185
2079
  } catch {
2186
2080
  return;
2187
2081
  }
2188
- let a = bt({
2082
+ let a = mt({
2189
2083
  catalogCodes: r,
2190
2084
  declaredLocales: i
2191
2085
  });
2192
2086
  if (a !== void 0) {
2193
2087
  console.log("");
2194
- for (let e of xt(a, n)) console.log(e);
2088
+ for (let e of ht(a, n)) console.log(e);
2195
2089
  }
2196
- }, Vn = async (e) => {
2197
- let { discovered: t } = await Z(e), n = [];
2090
+ }, Bn = async (e) => {
2091
+ let { discovered: t } = await X(e), n = [];
2198
2092
  try {
2199
- let { loadApiConfig: t } = await import("./dev-DXhhZ7-f.js");
2093
+ let { loadApiConfig: t } = await import("./dev-DOZVMVfi.js");
2200
2094
  n = ((await t(e))?.plugins ?? []).map((e) => {
2201
2095
  let t = (e.extendSchema?.tables ?? []).map((e) => {
2202
2096
  let t = e;
@@ -2212,8 +2106,8 @@ var re = () => {
2212
2106
  plugin: e.name,
2213
2107
  tables: t,
2214
2108
  rpcTags: n,
2215
- canDisableTables: Hn.has(e.name),
2216
- canAlias: Un.has(e.name)
2109
+ canDisableTables: Vn.has(e.name),
2110
+ canAlias: Hn.has(e.name)
2217
2111
  };
2218
2112
  });
2219
2113
  } catch {
@@ -2225,14 +2119,14 @@ var re = () => {
2225
2119
  ...t.mutations,
2226
2120
  ...t.actions,
2227
2121
  ...t.streams ?? []
2228
- ].map((e) => String(e.descriptor.name)), i = It(n, {
2122
+ ].map((e) => String(e.descriptor.name)), i = jt(n, {
2229
2123
  tables: t.tables.map((e) => e.tableName),
2230
2124
  rpcTags: r
2231
2125
  });
2232
2126
  console.log();
2233
- for (let e of Lt(n, i)) console.log(e);
2234
- }, Hn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Un = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Wn = async (e) => {
2235
- let t = await Gn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
2127
+ for (let e of Mt(n, i)) console.log(e);
2128
+ }, Vn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Hn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Un = async (e) => {
2129
+ let t = await Wn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
2236
2130
  return {
2237
2131
  executors: t.executorCount,
2238
2132
  guardVocabulary: [...t.vocabulary].sort(),
@@ -2244,15 +2138,15 @@ var re = () => {
2244
2138
  },
2245
2139
  unchecked: t.result.unchecked
2246
2140
  };
2247
- }, Gn = async (e) => {
2248
- let { discovered: t } = await Z(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2141
+ }, Wn = async (e) => {
2142
+ let { discovered: t } = await X(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2249
2143
  if (!n.has(e)) try {
2250
2144
  n.set(e, E(e, "utf8"));
2251
2145
  } catch {
2252
2146
  n.set(e, "");
2253
2147
  }
2254
2148
  return n.get(e);
2255
- }, i = zn(e, r), a = /* @__PURE__ */ new Set();
2149
+ }, i = Pn(e, r), a = /* @__PURE__ */ new Set();
2256
2150
  for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
2257
2151
  let e = t.references?.();
2258
2152
  e && a.add(e.tableName);
@@ -2284,56 +2178,71 @@ var re = () => {
2284
2178
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
2285
2179
  ...a?.table === void 0 ? {} : { table: a.table }
2286
2180
  };
2287
- })), s = x(e, Q), c = T(s) ? Pt(E(s, "utf8")) : void 0;
2181
+ })), s = x(e, Q), c = T(s) ? W(E(s, "utf8")) : void 0, l = !1;
2182
+ for (let t of q(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
2183
+ l = !0;
2184
+ break;
2185
+ }
2186
+ let u = wt({
2187
+ executors: o,
2188
+ guardVocabulary: i,
2189
+ tenantScopedTables: t.tenantScopedTables,
2190
+ referencedTables: a,
2191
+ allowlist: c?.tags ?? /* @__PURE__ */ new Set(),
2192
+ rowFilterRegistered: l
2193
+ }), d = (c?.entries ?? []).filter((e) => e.reviewed !== void 0);
2288
2194
  return {
2289
- result: At({
2290
- executors: o,
2291
- guardVocabulary: i,
2292
- tenantScopedTables: t.tenantScopedTables,
2293
- referencedTables: a,
2294
- allowlist: c?.tags ?? /* @__PURE__ */ new Set()
2295
- }),
2195
+ result: u,
2296
2196
  vocabulary: i,
2197
+ rowFilterRegistered: l,
2297
2198
  existing: c,
2298
- reviewed: (c?.entries ?? []).filter((e) => e.reviewed !== void 0),
2199
+ reviewed: d,
2299
2200
  executorCount: o.length,
2300
2201
  allowPath: s
2301
2202
  };
2302
- }, Kn = async (e, t) => {
2303
- let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s } = await Gn(e), c = n.counts;
2203
+ }, Gn = async (e, t) => {
2204
+ let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Wn(e), l = n.counts;
2304
2205
  console.log(`\nauthz scan · ${o} executor(s)`);
2305
- let l = [...r].filter((e) => !Ct.has(e)).sort();
2306
- console.log(l.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${l.length} from your source (${l.slice(0, 6).join(", ")}${l.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(c.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(c.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(c.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(c.vocabularyGuard).padStart(4)}`);
2307
- let u = new Set(a.map((e) => e.tag)), d = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !u.has(e.tag)).length, f = c.allowlisted - d;
2308
- if (console.log(` – accepted as recorded debt ${String(d).padStart(4)} (${Q})`), console.log(` ✓ reviewed, open on purpose ${String(f).padStart(4)} (\`reviewed=<why>\` in that file)`), t) return ne(s, Mt([...a, ...n.unchecked.map((e) => ({ tag: e.tag }))]), "utf8"), console.log(`\nwrote ${n.unchecked.length} debt tag(s) to ${Q}${a.length > 0 ? `, keeping ${a.length} reviewed line(s)` : ""}. Doctor now fails on ADDITIONS only.`), !1;
2206
+ let u = [...r].filter((e) => !_t.has(e)).sort();
2207
+ console.log(u.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${u.length} from your source (${u.slice(0, 6).join(", ")}${u.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(l.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(l.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(l.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(l.vocabularyGuard).padStart(4)}`);
2208
+ let d = new Set(a.map((e) => e.tag)), f = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !d.has(e.tag)).length, p = l.allowlisted - f;
2209
+ if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Q})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
2210
+ let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ot(e, t);
2211
+ return se(s, Dt(e, t), "utf8"), console.log(`\n${Q}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
2212
+ }
2309
2213
  if (n.unchecked.length === 0) return !1;
2310
2214
  console.log("");
2311
2215
  for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
2312
2216
  return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Q} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Q}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
2313
- }, qn = async (e) => {
2314
- if (e.includes("--help") || e.includes("-h")) return console.log(Dn), 0;
2315
- let t = e.includes("--strict"), n = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
2316
- if (ie(e)) return re(), M({
2317
- ...cn(n),
2318
- spaCandidates: await yn(n),
2319
- pageConvention: J(n),
2320
- fileTaxonomy: H(n).findings,
2321
- junctionFks: (await jn(n))?.issues ?? [],
2322
- eventDelivery: await Nn(n),
2323
- duplicateInstances: vt(n),
2324
- serverOnly: (await In(n)).status,
2325
- authz: await Wn(n)
2217
+ }, Kn = async (e) => {
2218
+ if (e.includes("--help") || e.includes("-h")) return console.log(Cn), 0;
2219
+ let n = e.includes("--strict"), r = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
2220
+ if (le(e)) return ce(), M({
2221
+ ...nn(r),
2222
+ spaCandidates: await mn(r),
2223
+ pageConvention: J(r),
2224
+ fileTaxonomy: V(r).findings,
2225
+ junctionFks: (await Z(r))?.issues ?? [],
2226
+ eventDelivery: await On(r),
2227
+ duplicateInstances: t(r),
2228
+ versionSkew: s(r) ?? null,
2229
+ serverOnly: (await jn(r)).status,
2230
+ authz: await Un(r),
2231
+ ungrantedScopes: await Ln(r),
2232
+ neverEmittedEvents: await Fn(r)
2326
2233
  }), 0;
2327
- let r = Vt(n, t);
2328
- mn(n), pn(n), En(n), Ve(n), await xn(n), await Rn(n), await kn(n), await Mn(n), await Pn(n), await Vn(n), await Bn(n);
2329
- let i = await Ln(n), a = await Kn(n, e.includes("--write-authz-allowlist")), o = yt(vt(n));
2330
- return o !== void 0 && console.log(`\n${o}`), i || a ? Math.max(r, 1) : r;
2331
- }, Jn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Yn = /* @__PURE__ */ new Set([
2234
+ let a = It(r, n);
2235
+ ln(r), cn(r), Sn(r), He(r), await gn(r), await Nn(r), await Tn(r), await Dn(r), await kn(r), await Bn(r), await Rn(r), await In(r), await zn(r);
2236
+ let c = await Mn(r), l = await Gn(r, e.includes("--write-authz-allowlist")), u = o(s(r));
2237
+ u !== void 0 && (console.log(""), console.log(u));
2238
+ let d = i(t(r));
2239
+ return d !== void 0 && console.log(`\n${d}`), c || l ? Math.max(a, 1) : a;
2240
+ }, qn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Jn = /* @__PURE__ */ new Set([
2332
2241
  "insert",
2333
2242
  "update",
2334
2243
  "delete",
2335
2244
  "upsert"
2336
- ]), Xn = (e, t) => {
2245
+ ]), Yn = (e, t) => {
2337
2246
  let n = [], r = new Set(t.exercised), i = /* @__PURE__ */ new Map();
2338
2247
  for (let e of t.edges) {
2339
2248
  let t = i.get(e.tag);
@@ -2345,8 +2254,8 @@ var re = () => {
2345
2254
  a.push(t.tag);
2346
2255
  continue;
2347
2256
  }
2348
- let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(Jn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
2349
- for (let t of c) if (Yn.has(t.op)) {
2257
+ let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(qn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
2258
+ for (let t of c) if (Jn.has(t.op)) {
2350
2259
  let r = u.get(t.table);
2351
2260
  r === void 0 ? n.push({
2352
2261
  rule: "observed/undeclared-write",
@@ -2402,7 +2311,7 @@ var re = () => {
2402
2311
  total: e.length
2403
2312
  }
2404
2313
  };
2405
- }, Zn = (e, t = [], n = 0) => {
2314
+ }, Xn = (e, t = [], n = 0) => {
2406
2315
  if (n > 12) return t;
2407
2316
  let r;
2408
2317
  try {
@@ -2420,30 +2329,30 @@ var re = () => {
2420
2329
  ".git",
2421
2330
  "coverage"
2422
2331
  ].includes(i)) continue;
2423
- let r = `${e}/${i}`, o;
2332
+ let r = `${e}/${i}`, a;
2424
2333
  try {
2425
- o = O(r);
2334
+ a = O(r);
2426
2335
  } catch {
2427
2336
  continue;
2428
2337
  }
2429
- o.isDirectory() ? Zn(r, t, n + 1) : o.isFile() && a.test(i) && t.push(r);
2338
+ a.isDirectory() ? Xn(r, t, n + 1) : a.isFile() && l.test(i) && t.push(r);
2430
2339
  }
2431
2340
  return t;
2432
- }, Qn = /* @__PURE__ */ new Set(["actors", "tenants"]), $n = async (e) => {
2341
+ }, Zn = /* @__PURE__ */ new Set(["actors", "tenants"]), Qn = async (e) => {
2433
2342
  try {
2434
2343
  let { readFile: t } = await import("node:fs/promises"), { join: n } = await import("node:path"), r = await t(n(e, "app.graph.observed.generated.json"), "utf8"), i = JSON.parse(r);
2435
2344
  return i?.version !== 1 || !Array.isArray(i.edges) || !Array.isArray(i.exercised) ? null : i;
2436
2345
  } catch {
2437
2346
  return null;
2438
2347
  }
2439
- }, er = (e) => {
2348
+ }, $n = (e) => {
2440
2349
  let { exercised: t, total: n } = e.coverage, r = `\nobserved: ${t}/${n} procedures exercised (${n === 0 ? 0 : Math.round(t / n * 100)}%)`;
2441
2350
  if (e.diagnostics.length === 0) return `${r}\n no declared/observed mismatches${e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : ""}`;
2442
2351
  let i = e.diagnostics.map((e) => ` ${e.node}${e.file === void 0 ? "" : ` (${e.file})`}\n` + e.breaks.map((e) => ` ${e}`).join("\n") + `\n fix: ${e.fix}`), a = e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : "";
2443
2352
  return `${r}\n${i.join("\n")}${a}`;
2444
- }, tr = (e, t) => ({
2353
+ }, er = (e, t) => ({
2445
2354
  tables: (t.tables ?? []).map((e) => e.name),
2446
- frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || Qn.has(e.name)).map((e) => e.name),
2355
+ frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || Zn.has(e.name)).map((e) => e.name),
2447
2356
  scopes: e.scopes ?? [],
2448
2357
  procedures: (e.procedures ?? []).map((e) => {
2449
2358
  let t = (e.guards ?? []).flatMap((e) => e.kind === "scope" ? e.scope ?? [] : []);
@@ -2456,16 +2365,16 @@ var re = () => {
2456
2365
  ...e.guards === void 0 ? {} : { hasGuards: e.guards.length > 0 }
2457
2366
  };
2458
2367
  })
2459
- }), nr = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
2368
+ }), tr = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
2460
2369
  let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
2461
2370
  return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
2462
- }).join("\n"), rr = async (e) => {
2463
- let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-DXhhZ7-f.js"), { buildRpcEntry: i } = await import("./manifestBuild-Dj8Jjoto.js"), { buildCapabilityManifest: a } = await import("./inspect-mmBuRXmy.js"), { relative: o } = await import("node:path"), s = await n(await t(e)), c = (t, n, r) => i(t, n, o(e, r)), l = [
2371
+ }).join("\n"), nr = async (e) => {
2372
+ let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-DOZVMVfi.js"), { buildRpcEntry: i } = await import("./manifestBuild-Dj8Jjoto.js"), { buildCapabilityManifest: a } = await import("./inspect-mmBuRXmy.js"), { relative: o } = await import("node:path"), s = await n(await t(e)), c = (t, n, r) => i(t, n, o(e, r)), l = [
2464
2373
  ...s.queries.map((e) => c(e.descriptor, "query", e.file)),
2465
2374
  ...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
2466
2375
  ...s.actions.map((e) => c(e.descriptor, "action", e.file)),
2467
2376
  ...s.streams.map((e) => c(e.descriptor, "stream", e.file))
2468
- ], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-Bn_c4Mnl.js"), d = await u({ root: e }).catch(() => []), f = new Set(s.tables.map((e) => e.tableName)), p = [...[...f].map((e) => ({ name: e })), ...d.map((e) => e.tableName).filter((e) => !f.has(e)).map((e) => ({
2377
+ ], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-D-EebUQX.js"), d = await u({ root: e }).catch((e) => (console.warn(`\n⚠ plugin tables could not be collected — this report is INCOMPLETE.\n ${e instanceof Error ? e.message.split("\n")[0] : String(e)}\n`), [])), f = new Set(s.tables.map((e) => e.tableName)), p = [...[...f].map((e) => ({ name: e })), ...d.map((e) => e.tableName).filter((e) => !f.has(e)).map((e) => ({
2469
2378
  name: e,
2470
2379
  framework: !0
2471
2380
  }))], m = await r(e).then((e) => e?.plugins ?? []).catch(() => []);
@@ -2476,10 +2385,10 @@ var re = () => {
2476
2385
  })), [], m),
2477
2386
  tables: { tables: p }
2478
2387
  };
2479
- }, ir = async (e) => {
2480
- let t = ue(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
2388
+ }, rr = async (e) => {
2389
+ let t = me(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
2481
2390
  if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
2482
- let { process: n, target: r, rest: i } = de(t.rest), a;
2391
+ let { process: n, target: r, rest: i } = he(t.rest), a;
2483
2392
  for (let e = 0; e < i.length; e++) if (i[e] === "--diff") {
2484
2393
  let [t, n] = (i[++e] ?? "").split(":");
2485
2394
  if (t === "removeTable" && n) a = {
@@ -2496,35 +2405,35 @@ var re = () => {
2496
2405
  };
2497
2406
  else return console.error("voltro check --diff: expected removeTable:<name> | removeProcedure:<tag> | removeScope:<name>"), 1;
2498
2407
  }
2499
- let s = i.find((e) => !e.startsWith("-")) ?? process.cwd(), c = i.includes("--offline"), l = c ? [] : await le(n, r), u, d;
2500
- if (l.length === 0) {
2501
- if (!c && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
2408
+ let o = i.find((e) => !e.startsWith("-")) ?? process.cwd(), s = i.includes("--offline"), c = s ? [] : await pe(n, r), l, d;
2409
+ if (c.length === 0) {
2410
+ if (!s && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
2502
2411
  try {
2503
- let e = await rr(C(s));
2504
- u = e.manifest, d = e.tables;
2412
+ let e = await nr(C(o));
2413
+ l = e.manifest, d = e.tables;
2505
2414
  } catch (e) {
2506
2415
  return console.error(`voltro check: could not assemble the graph from source: ${e.message}`), console.error(" Start `voltro dev` to check against a running api instead."), 1;
2507
2416
  }
2508
2417
  } else {
2509
- let e = l[0], [t, n] = await Promise.all([N(e, "/_voltro/inspect/manifest"), N(e, "/_voltro/inspect/data/tables")]);
2418
+ let e = c[0], [t, n] = await Promise.all([N(e, "/_voltro/inspect/manifest"), N(e, "/_voltro/inspect/data/tables")]);
2510
2419
  if (t.kind === "err") return console.error(`voltro check: manifest fetch failed: ${t.error}`), 1;
2511
- u = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
2420
+ l = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
2512
2421
  }
2513
- let f = tr(u, d), p = he(f, a), m = [...H(s).findings.map((e) => ({
2422
+ let f = er(l, d), p = ie(f, a), m = [...V(o).findings.map((e) => ({
2514
2423
  rule: e.rule,
2515
2424
  node: e.file,
2516
2425
  file: e.file,
2517
2426
  breaks: [e.detail],
2518
2427
  fix: e.fix,
2519
2428
  severity: e.severity
2520
- })), ...J(s).map((e) => ({
2429
+ })), ...J(o).map((e) => ({
2521
2430
  rule: `page/${e.kind}`,
2522
2431
  node: e.file,
2523
2432
  file: e.file,
2524
2433
  breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
2525
2434
  fix: e.kind === "suffixed-outside-pages" ? "move it under src/pages/ into the directory whose route it serves, or rename it — page.tsx is reserved" : "write the test with the file — the name is derivable",
2526
2435
  severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
2527
- }))], h = (await o(s, Zn(s))).map((e) => ({
2436
+ }))], h = (await u(o, Xn(o))).map((e) => ({
2528
2437
  rule: "client/not-browser-safe",
2529
2438
  node: e.file,
2530
2439
  file: e.file,
@@ -2535,7 +2444,7 @@ var re = () => {
2535
2444
  ...p.diagnostics,
2536
2445
  ...m,
2537
2446
  ...h
2538
- ], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await $n(C(s)), y = v === null ? null : Xn(f.procedures, v);
2447
+ ], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await Qn(C(o)), y = v === null ? null : Yn(f.procedures, v);
2539
2448
  return t.format === "json" ? M(y === null ? {
2540
2449
  ...p,
2541
2450
  ok: _,
@@ -2545,7 +2454,7 @@ var re = () => {
2545
2454
  ok: _,
2546
2455
  diagnostics: g,
2547
2456
  observed: y
2548
- }) : (console.log(nr(g)), y !== null && console.log(er(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
2457
+ }) : (console.log(tr(g)), y !== null && console.log($n(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
2549
2458
  };
2550
2459
  //#endregion
2551
- export { re as _, Re as a, I as c, N as d, ue as f, le as g, ce as h, Vt as i, ve as l, se as m, ir as n, be as o, de as p, qn as r, xe as s, rr as t, he as u, M as v, ie as y };
2460
+ export { ce as _, It as a, xe as c, N as d, me as f, pe as g, fe as h, Kn as i, I as l, P as m, rr as n, ze as o, he as p, er as r, be as s, nr as t, ve as u, M as v, le as y };