@voltro/cli 0.55.0 → 0.57.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 (175) hide show
  1. package/CHANGELOG.md +691 -0
  2. package/bin/voltro.mjs +24 -0
  3. package/dist/apiBuild-B83Cb2Rv.js +2 -0
  4. package/dist/{apiBuild-CMvLJM_K.js → apiBuild-DDr2aNFd.js} +119 -88
  5. package/dist/bin.js +1 -1
  6. package/dist/{build-S0QOzqPT.js → build-QKP6Bm0J.js} +308 -279
  7. package/dist/buildReport-52gHKgfO.js +64 -0
  8. package/dist/{checkCommand-DNkY5kwF.js → checkCommand-BISqx1OJ.js} +1 -1
  9. package/dist/{checkCommand-fbj9GDjN.js → checkCommand-DUtMWjcR.js} +6 -6
  10. package/dist/{clusterCmd-DrVFCzSj.js → clusterCmd-CdLB1GkT.js} +1 -1
  11. package/dist/{codegen-SIepQtUl.js → codegen-Bth5lUTU.js} +2 -1
  12. package/dist/codegen-DbH7NbCR.js +2 -0
  13. package/dist/{codegenCommand-3TDJezom.js → codegenCommand-DnVuDxwT.js} +11 -11
  14. package/dist/{codemodRunner-C2zxZUIw.js → codemodRunner-BlQPfjzA.js} +222 -0
  15. package/dist/{commands-BBYJ7Q3B.js → commands-CRbxgxv0.js} +35 -35
  16. package/dist/{dashboardCommand-D2kmyCLL.js → dashboardCommand-Bf_-Ne3P.js} +5 -5
  17. package/dist/{dataCommand-BEPPQiTl.js → dataCommand-BoBJJ-Gb.js} +3 -3
  18. package/dist/{dbCommand-DZTmOFT4.js → dbCommand-CMAIz-Bf.js} +457 -441
  19. package/dist/dbCommand-DHi_RuDl.js +2 -0
  20. package/dist/dev-Bl9HqtV7.js +3 -0
  21. package/dist/{dev-Ca_A_S9v.js → dev-DOEJXicj.js} +2623 -2485
  22. package/dist/{doctorCommand-CGZJK_4o.js → doctorCommand-BrWu67JZ.js} +524 -280
  23. package/dist/doctorCommand-DrQv9SL3.js +2 -0
  24. package/dist/{dormancyCommand-DY2rYpTa.js → dormancyCommand-xn2y-pJm.js} +1 -1
  25. package/dist/{embeddingsCommand-BoCqZsgp.js → embeddingsCommand-Cn5MbRDM.js} +1 -1
  26. package/dist/emptyResultHeadline-Csa5fZOF.js +18 -0
  27. package/dist/{envCommand-Bxy2fOjc.js → envCommand-UJmJIbs9.js} +8 -8
  28. package/dist/{evolveCommand-BsbZ-XDg.js → evolveCommand-Db30twUy.js} +2 -2
  29. package/dist/frameworkTableAssembly-B96WCNJA.js +2 -0
  30. package/dist/{frameworkTableAssembly-Do-cf6RJ.js → frameworkTableAssembly-vfkzuzEo.js} +131 -104
  31. package/dist/index.js +1 -1
  32. package/dist/{infoCommand-EmM3jPKD.js → infoCommand-BjVXpMlP.js} +1 -1
  33. package/dist/inspect-B0hL41s0.js +2 -0
  34. package/dist/inspect-DZnan87F.js +1500 -0
  35. package/dist/{inspectCmd-niF97fAq.js → inspectCmd-CP-G0sVK.js} +1 -1
  36. package/dist/{inspectFetch-EMuhTG_9.js → inspectFetch-BU1NyzxV.js} +36 -24
  37. package/dist/{inspectMetrics-CGF94puw.js → inspectMetrics-BY0Sjb2F.js} +19 -19
  38. package/dist/{logsCmd-B6oNsfaZ.js → logsCmd-C5y9HyrG.js} +59 -54
  39. package/dist/{manifestBuild-DjX5MoXy.js → manifestBuild-DnbFKF6w.js} +1 -1
  40. package/dist/manifestBuild-UXrnUcXP.js +2 -0
  41. package/dist/{migrate-CGFZS-1a.js → migrate-DtC3lu7H.js} +4 -4
  42. package/dist/precompressAssets-YhTi1aWp.js +40 -0
  43. package/dist/{probeCommand-Bs3iVBSL.js → probeCommand-C5fuN6Z2.js} +2 -2
  44. package/dist/{runtimeTrace-C1BTpHGQ.js → runtimeTrace-BQL_lfz6.js} +1 -1
  45. package/dist/{scheduleCmd-DQRu6BZC.js → scheduleCmd-DYBUfo_T.js} +1 -1
  46. package/dist/{sdkgen-CXMwLg9n.js → sdkgen-CMUPrDjH.js} +1 -1
  47. package/dist/{seedRunner-Dgsiwk_e.js → seedRunner-DISBKow-.js} +16 -16
  48. package/dist/serveCommand-B_w-Mpb_.js +2544 -0
  49. package/dist/serveCommand-Dtb48ffg.js +2 -0
  50. package/dist/serveEntry.js +1 -1
  51. package/dist/{start-EOV7s1NZ.js → start-YaUehtDV.js} +580 -558
  52. package/dist/{start-DH7cat4-.js → start-s25GAIgn.js} +1 -1
  53. package/dist/startEntry.js +1 -1
  54. package/dist/staticCachePolicy-CIyj6DbS.js +15 -0
  55. package/dist/{test-DO27-x2P.js → test-jipIQ5Mx.js} +1 -1
  56. package/dist/{tracesCmd-DgtgOUdi.js → tracesCmd-C1qKa94g.js} +69 -64
  57. package/dist/{updateCommand-C_jN1w18.js → updateCommand-C9n_Z_oG.js} +8 -2
  58. package/dist/updateCommand-DsXEAHbd.js +2 -0
  59. package/dist/webDev-B6ZMX42w.js +2 -0
  60. package/dist/{webDev-B7vNj4Bq.js → webDev-BgdkyjP6.js} +1379 -1303
  61. package/dist/{webhooksCommand-B1LVcyO3.js → webhooksCommand-CYXTNvXq.js} +2 -2
  62. package/dist/{workflowsCmd-BGF-mRZ5.js → workflowsCmd-g-DNpaUc.js} +1 -1
  63. package/package.json +55 -19
  64. package/templates/AGENTS.core.md +20 -1
  65. package/templates/AGENTS.md +21 -2
  66. package/templates/agent-docs/_index.md +1 -1
  67. package/templates/agent-docs/_manifest.json +2 -2
  68. package/templates/agent-docs/ai.md +1 -1
  69. package/templates/agent-docs/authentication.md +34 -19
  70. package/templates/agent-docs/cli.md +170 -0
  71. package/templates/agent-docs/configuration.md +29 -4
  72. package/templates/agent-docs/data.md +145 -5
  73. package/templates/agent-docs/database/scaling.md +50 -2
  74. package/templates/agent-docs/deployment.md +75 -1
  75. package/templates/agent-docs/internationalization.md +32 -0
  76. package/templates/agent-docs/local-first-mobile.md +9 -2
  77. package/templates/agent-docs/observability.md +227 -0
  78. package/templates/agent-docs/plugins/audit.md +21 -5
  79. package/templates/agent-docs/plugins/billing.md +17 -0
  80. package/templates/agent-docs/plugins/broadcast.md +2 -1
  81. package/templates/agent-docs/plugins/ratelimit.md +6 -1
  82. package/templates/agent-docs/plugins/row-history.md +11 -0
  83. package/templates/agent-docs/plugins.md +19 -0
  84. package/templates/agent-docs/reference.md +1 -0
  85. package/templates/agent-docs/scheduling.md +23 -0
  86. package/templates/agent-docs/schema-driven-ui.md +81 -0
  87. package/templates/agent-docs/templates/appshells.md +3 -3
  88. package/templates/agent-docs/whats-new.md +133 -77
  89. package/templates/apps/api-ai/package.json +6 -6
  90. package/templates/apps/api-auth/package.json +8 -8
  91. package/templates/apps/api-backend/package.json +7 -7
  92. package/templates/apps/api-backend-deactivation/package.json +7 -7
  93. package/templates/apps/api-backend-mail/package.json +8 -8
  94. package/templates/apps/api-backend-mariadb/package.json +9 -9
  95. package/templates/apps/api-backend-sqlite/package.json +8 -8
  96. package/templates/apps/api-backend-storage/package.json +8 -8
  97. package/templates/apps/api-cms/package.json +9 -9
  98. package/templates/apps/api-collab/package.json +8 -8
  99. package/templates/apps/api-data-advanced/package.json +8 -8
  100. package/templates/apps/api-durable/package.json +8 -8
  101. package/templates/apps/api-feature-flags/package.json +9 -9
  102. package/templates/apps/api-governance/package.json +8 -8
  103. package/templates/apps/api-kv/package.json +8 -8
  104. package/templates/apps/api-moderation/package.json +8 -8
  105. package/templates/apps/api-observability/package.json +8 -8
  106. package/templates/apps/api-ratelimit/package.json +8 -8
  107. package/templates/apps/api-rbac/package.json +8 -8
  108. package/templates/apps/api-rest/package.json +7 -7
  109. package/templates/apps/api-row-history/package.json +8 -8
  110. package/templates/apps/api-saas/package.json +11 -11
  111. package/templates/apps/api-saas-starter/package.json +10 -10
  112. package/templates/apps/api-search/package.json +8 -8
  113. package/templates/apps/api-status/package.json +8 -8
  114. package/templates/apps/api-webhooks/package.json +9 -9
  115. package/templates/apps/changelog/package.json +7 -7
  116. package/templates/apps/changelog/src/pages/[locale]/page.tsx +7 -1
  117. package/templates/apps/edge-functions/package.json +2 -2
  118. package/templates/apps/frontend-admin/package.json +7 -8
  119. package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +1 -2
  120. package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +1 -2
  121. package/templates/apps/frontend-app/package.json +8 -9
  122. package/templates/apps/frontend-app/src/pages/layout.tsx +1 -2
  123. package/templates/apps/frontend-auth/package.json +7 -8
  124. package/templates/apps/frontend-auth/src/components/AuthShell.tsx +1 -2
  125. package/templates/apps/frontend-blank/package.json +6 -7
  126. package/templates/apps/frontend-blank/src/pages/layout.tsx +1 -2
  127. package/templates/apps/frontend-cms/package.json +8 -9
  128. package/templates/apps/frontend-cms/src/pages/(app)/layout.tsx +1 -2
  129. package/templates/apps/frontend-collab/README.md +7 -2
  130. package/templates/apps/frontend-collab/package.json +9 -10
  131. package/templates/apps/frontend-collab/src/pages/layout.tsx +1 -2
  132. package/templates/apps/frontend-collab/src/pages/page.test.tsx +8 -4
  133. package/templates/apps/frontend-collab/src/pages/page.tsx +10 -5
  134. package/templates/apps/frontend-contact/package.json +7 -7
  135. package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +1 -1
  136. package/templates/apps/frontend-dashboard/package.json +6 -7
  137. package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +1 -2
  138. package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +1 -2
  139. package/templates/apps/frontend-docs/package.json +8 -8
  140. package/templates/apps/frontend-i18n/package.json +6 -6
  141. package/templates/apps/frontend-landing/package.json +7 -7
  142. package/templates/apps/frontend-portal/package.json +7 -8
  143. package/templates/apps/frontend-portal/src/pages/(portal)/layout.tsx +1 -2
  144. package/templates/apps/frontend-saas/package.json +7 -8
  145. package/templates/apps/frontend-saas/src/pages/(marketing)/layout.tsx +1 -2
  146. package/templates/apps/frontend-saas/src/pages/dashboard/layout.tsx +1 -2
  147. package/templates/apps/frontend-spa/package.json +6 -7
  148. package/templates/apps/frontend-spa/src/pages/layout.tsx +1 -2
  149. package/templates/apps/frontend-ssr/package.json +6 -7
  150. package/templates/apps/frontend-ssr/src/pages/layout.tsx +1 -2
  151. package/templates/apps/frontend-ssr-api/package.json +7 -8
  152. package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +1 -2
  153. package/templates/apps/frontend-static-blog/package.json +8 -8
  154. package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +1 -1
  155. package/templates/apps/frontend-static-blog/src/pages/[locale]/page.tsx +7 -1
  156. package/templates/apps/frontend-status/package.json +7 -8
  157. package/templates/apps/frontend-status/src/pages/layout.tsx +1 -2
  158. package/templates/apps/mobile-app/package.json +4 -4
  159. package/templates/baselines/compose/docker/api.Dockerfile +61 -5
  160. package/templates/baselines/compose/docker/web.Dockerfile +55 -10
  161. package/templates/baselines/compose-mariadb/docker/api.Dockerfile +61 -5
  162. package/templates/baselines/compose-mariadb/docker/web.Dockerfile +55 -10
  163. package/dist/apiBuild-Cl0IDx8c.js +0 -2
  164. package/dist/codegen-CN6vMM4J.js +0 -2
  165. package/dist/dbCommand-BTyBGhIA.js +0 -2
  166. package/dist/dev-DfVZaoys.js +0 -3
  167. package/dist/doctorCommand-djmqEcDC.js +0 -2
  168. package/dist/frameworkTableAssembly-Df2Ymp2f.js +0 -2
  169. package/dist/inspect-DCqILJ1G.js +0 -1197
  170. package/dist/inspect-DGJwpOAb.js +0 -2
  171. package/dist/manifestBuild-CJ2zvPvT.js +0 -2
  172. package/dist/serveCommand-C7IrCD58.js +0 -2445
  173. package/dist/serveCommand-Cjt5S9hD.js +0 -2
  174. package/dist/updateCommand-nnFjDbl4.js +0 -2
  175. package/dist/webDev-1XpVnYkW.js +0 -2
@@ -1,8 +1,8 @@
1
- import { Ht as e, Ut as t, Wt as n } from "./dev-Ca_A_S9v.js";
2
- import { Ft as r, It as i, Lt as a, P as o, Pt as s, l as c, t as l, x as u } from "./webDev-B7vNj4Bq.js";
3
- import { x as d } from "./frameworkTableAssembly-Do-cf6RJ.js";
4
- import { n as f } from "./pageConvention-CMpfDN6r.js";
5
- import { r as p, u as m } from "./apiBuild-CMvLJM_K.js";
1
+ import { Jt as e, Xt as t, Yt as n } from "./dev-DOEJXicj.js";
2
+ import { Bt as r, Lt as i, R as a, Rt as o, S as s, t as c, u as l, zt as u } from "./webDev-BgdkyjP6.js";
3
+ import { r as d, u as f } from "./apiBuild-DDr2aNFd.js";
4
+ import { S as p } from "./frameworkTableAssembly-vfkzuzEo.js";
5
+ import { n as m } from "./pageConvention-CMpfDN6r.js";
6
6
  import { n as h, r as g, t as ee } from "./cliOutput-D1tSBoRM.js";
7
7
  import { n as _, t as te } from "./tsconfigPaths-BWXBWgcl.js";
8
8
  import { n as ne, r as re } from "./fileTaxonomy-CbyMQYx_.js";
@@ -367,15 +367,15 @@ var le = /* @__PURE__ */ new Set([
367
367
  " reaches your deploy target is not knowable from here — a secret manager",
368
368
  " or a CI variable is not a file. Naming it nowhere is conclusive; naming",
369
369
  " it somewhere is not a guarantee."
370
- ].join("\n"), j = {
370
+ ].join("\n"), Pe = {
371
371
  "@voltro/cli": "provides the `voltro` binary; consumed by scripts, never imported",
372
372
  "@voltro/devtools": "mounted by `voltro dev` itself",
373
373
  "@voltro/sql-postgres": "dialect driver, loaded by the framework from config",
374
374
  "@voltro/sql-mysql": "dialect driver, loaded by the framework from config",
375
375
  "@voltro/sql-sqlite": "dialect driver, loaded by the framework from config",
376
376
  "@voltro/sql-mssql": "dialect driver, loaded by the framework from config"
377
- }, M = /* @__PURE__ */ new Set(["@voltro/client", "@voltro/web"]), Pe = /(^|\/)(node_modules|dist|build|\.framework|\.next|\.turbo|\.git|coverage|\.publish)(\/|$)/, Fe = /\.([cm]?[jt]sx?)$/, Ie = /\.generated\.[cm]?[jt]sx?$/, Le = (e, t = [], n = 0) => {
378
- if (n > 12 || Pe.test(e)) return t;
377
+ }, Fe = /* @__PURE__ */ new Set(["@voltro/client", "@voltro/web"]), Ie = /(^|\/)(node_modules|dist|build|\.framework|\.next|\.turbo|\.git|coverage|\.publish)(\/|$)/, Le = /\.([cm]?[jt]sx?)$/, Re = /\.generated\.[cm]?[jt]sx?$/, ze = (e, t = [], n = 0) => {
378
+ if (n > 12 || Ie.test(e)) return t;
379
379
  let r;
380
380
  try {
381
381
  r = w(e);
@@ -389,10 +389,10 @@ var le = /* @__PURE__ */ new Set([
389
389
  } catch {
390
390
  continue;
391
391
  }
392
- a.isDirectory() ? Le(r, t, n + 1) : Fe.test(i) && t.push(r);
392
+ a.isDirectory() ? ze(r, t, n + 1) : Le.test(i) && t.push(r);
393
393
  }
394
394
  return t;
395
- }, Re = /(?:\bfrom\s*|\bimport\s+|\bimport\s*\(\s*|\brequire\s*\(\s*)['"](@voltro\/[a-z0-9-]+)(?:\/[^'"]*)?['"]/g, ze = (e) => e.split("\n").filter((e) => !/^\s*(\/\/|\*|\/\*)/.test(e)).join("\n"), Be = (e) => {
395
+ }, Be = /(?:\bfrom\s*|\bimport\s+|\bimport\s*\(\s*|\brequire\s*\(\s*)['"](@voltro\/[a-z0-9-]+)(?:\/[^'"]*)?['"]/g, Ve = (e) => e.split("\n").filter((e) => !/^\s*(\/\/|\*|\/\*)/.test(e)).join("\n"), He = (e) => {
396
396
  let t;
397
397
  try {
398
398
  t = JSON.parse(C(v(e, "package.json"), "utf8"));
@@ -402,7 +402,7 @@ var le = /* @__PURE__ */ new Set([
402
402
  let n = (e) => Object.keys(t[e] ?? {}).filter((e) => e.startsWith("@voltro/")).map((t) => ({
403
403
  pkg: t,
404
404
  declaredIn: e
405
- })), r = /* @__PURE__ */ new Set(), i = [...n("dependencies"), ...n("devDependencies")].filter((e) => !r.has(e.pkg) && (r.add(e.pkg), !0)), a = Le(e), o = a.some((e) => Ie.test(e)), s = /* @__PURE__ */ new Set();
405
+ })), r = /* @__PURE__ */ new Set(), i = [...n("dependencies"), ...n("devDependencies")].filter((e) => !r.has(e.pkg) && (r.add(e.pkg), !0)), a = ze(e), o = a.some((e) => Re.test(e)), s = /* @__PURE__ */ new Set();
406
406
  for (let e of a) {
407
407
  let t;
408
408
  try {
@@ -410,17 +410,17 @@ var le = /* @__PURE__ */ new Set([
410
410
  } catch {
411
411
  continue;
412
412
  }
413
- for (let e of ze(t).matchAll(Re)) s.add(e[1]);
413
+ for (let e of Ve(t).matchAll(Be)) s.add(e[1]);
414
414
  }
415
- let c = i.filter((e) => !(e.pkg in j) && !s.has(e.pkg));
415
+ let c = i.filter((e) => !(e.pkg in Pe) && !s.has(e.pkg));
416
416
  return {
417
417
  declared: i.map((e) => e.pkg).sort(),
418
- unimported: c.filter((e) => o || !M.has(e.pkg)).sort((e, t) => e.pkg.localeCompare(t.pkg)),
419
- pendingCodegen: o ? [] : c.map((e) => e.pkg).filter((e) => M.has(e)).sort(),
420
- exempt: i.map((e) => e.pkg).filter((e) => e in j).sort(),
418
+ unimported: c.filter((e) => o || !Fe.has(e.pkg)).sort((e, t) => e.pkg.localeCompare(t.pkg)),
419
+ pendingCodegen: o ? [] : c.map((e) => e.pkg).filter((e) => Fe.has(e)).sort(),
420
+ exempt: i.map((e) => e.pkg).filter((e) => e in Pe).sort(),
421
421
  scannedFiles: a.length
422
422
  };
423
- }, Ve = (e) => {
423
+ }, Ue = (e) => {
424
424
  if (e === null) return ["unimported deps · no package.json here — could not check"];
425
425
  let t = `unimported deps · ${e.declared.length} @voltro/* package(s) declared, ${e.scannedFiles} file(s) scanned`;
426
426
  if (e.declared.length === 0) return [t, " no @voltro/* dependencies declared — nothing to check"];
@@ -437,14 +437,14 @@ var le = /* @__PURE__ */ new Set([
437
437
  let i = [t];
438
438
  for (let t of e.unimported) i.push(` ⚠ ${t.pkg} — declared in ${t.declaredIn}, imported nowhere`);
439
439
  return i.push(" a dependency nobody imports still gets installed, updated, and read as"), i.push(" evidence the package is in use — its breaking-change notes included."), i.push(" Remove it, or if it IS imported through an assembled specifier the scan"), i.push(" cannot see, keep it and ignore this line — the rule is advisory."), i.push(...r), n !== "" && i.push(` ·${n.slice(1)}`), i;
440
- }, He = (e) => {
440
+ }, We = (e) => {
441
441
  if (e.catalogCodes.length === 0) return;
442
442
  let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
443
443
  if (n.length !== 0) return {
444
444
  orphaned: n,
445
445
  noneDeclared: t.size === 0
446
446
  };
447
- }, Ue = (e, t) => {
447
+ }, Ge = (e, t) => {
448
448
  let n = e.orphaned.join(", ");
449
449
  return e.noneDeclared ? [
450
450
  `⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
@@ -456,7 +456,7 @@ var le = /* @__PURE__ */ new Set([
456
456
  " …or delete the files. A catalog that is never loaded looks from the inside",
457
457
  " exactly like one that works, which is why this is worth a line."
458
458
  ] : [`⚠ ${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)."];
459
- }, We = (e) => {
459
+ }, Ke = (e) => {
460
460
  let t = e.getProperty("match");
461
461
  if (t === void 0) return null;
462
462
  let n = (t.asKind(O.PropertyAssignment)?.getInitializer())?.asKind(O.ObjectLiteralExpression);
@@ -493,7 +493,7 @@ var le = /* @__PURE__ */ new Set([
493
493
  else return;
494
494
  }
495
495
  return r;
496
- }, Ge = (e, t) => {
496
+ }, qe = (e, t) => {
497
497
  let n = ["middleware.ts", "middleware.tsx"].find((t) => S(v(e, t)));
498
498
  if (n === void 0) return;
499
499
  let r = new D({
@@ -510,7 +510,7 @@ var le = /* @__PURE__ */ new Set([
510
510
  file: n
511
511
  };
512
512
  }
513
- let a = [], s = [], c = 0;
513
+ let o = [], s = [], c = 0;
514
514
  for (let e of i.getVariableDeclarations()) {
515
515
  let t = e.getInitializerIfKind(O.CallExpression);
516
516
  if (t?.getExpression().getText() !== "defineMiddleware") continue;
@@ -520,12 +520,12 @@ var le = /* @__PURE__ */ new Set([
520
520
  s.push(n);
521
521
  continue;
522
522
  }
523
- let i = We(r);
523
+ let i = Ke(r);
524
524
  if (i === void 0) {
525
525
  s.push(n);
526
526
  continue;
527
527
  }
528
- a.push({
528
+ o.push({
529
529
  exportName: n,
530
530
  middleware: {
531
531
  _tag: "VoltroMiddleware",
@@ -535,45 +535,45 @@ var le = /* @__PURE__ */ new Set([
535
535
  });
536
536
  }
537
537
  return {
538
- problems: o(a, t).map((e) => e.message),
538
+ problems: a(o, t).map((e) => e.message),
539
539
  unreadable: s,
540
540
  seen: c,
541
541
  file: n
542
542
  };
543
- }, Ke = (e) => {
543
+ }, Je = (e) => {
544
544
  let t = [];
545
545
  if (e.problems.length > 0) {
546
546
  t.push(`✗ ${e.file}: ${e.problems.length} matcher problem(s) — this REFUSES the boot`);
547
547
  for (let n of e.problems) t.push(` • ${n}`);
548
548
  }
549
549
  return e.unreadable.length > 0 && t.push(`… ${e.file}: ${e.unreadable.length} matcher(s) not statically readable (${e.unreadable.join(", ")}) — doctor did NOT check them. The boot still does.`), e.seen === 0 && t.push(`… ${e.file}: no \`defineMiddleware(...)\` found — doctor checked nothing here. A middleware assigned through a variable or re-exported from another module is invisible to this rule.`), t.length === 0 && e.seen > 0 && t.push(`✓ ${e.file}: ${e.seen} middleware(s), matchers resolve and do not overlap`), t;
550
- }, qe = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), Je = /* @__PURE__ */ new Set([
550
+ }, Ye = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), Xe = /* @__PURE__ */ new Set([
551
551
  "requireScope",
552
552
  "requireAnyScope",
553
553
  "requireAuth",
554
554
  "assertScope",
555
555
  "assertCan",
556
556
  "can"
557
- ]), Ye = (e) => {
557
+ ]), Ze = (e) => {
558
558
  let t = /* @__PURE__ */ new Set();
559
- for (let n of e) for (let e of qe(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
559
+ for (let n of e) for (let e of Ye(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
560
560
  return t;
561
- }, Xe = (e, t) => {
561
+ }, Qe = (e, t) => {
562
562
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
563
- }, Ze = (e) => {
563
+ }, $e = (e) => {
564
564
  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);
565
565
  return t && n;
566
- }, Qe = (e, t, n) => {
566
+ }, et = (e, t, n) => {
567
567
  if (e.hasDeclaredGuards) return { kind: "declared-guard" };
568
- let r = qe(e.src), i = Xe(r, t);
569
- return i === void 0 ? Ze(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
568
+ let r = Ye(e.src), i = Qe(r, t);
569
+ return i === void 0 ? $e(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
570
570
  kind: "vocabulary-guard",
571
571
  name: i
572
572
  };
573
- }, $e = (e, t, n) => {
573
+ }, tt = (e, t, n) => {
574
574
  let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
575
575
  return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
576
- }, et = (e, t, n) => {
576
+ }, nt = (e, t, n) => {
577
577
  switch (t.kind) {
578
578
  case "declared-guard": return "guards: on the descriptor";
579
579
  case "vocabulary-guard": return `calls ${t.name}()`;
@@ -581,15 +581,15 @@ var le = /* @__PURE__ */ new Set([
581
581
  case "allowlisted": return "accepted by a previous run";
582
582
  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`;
583
583
  }
584
- }, tt = (e) => {
584
+ }, rt = (e) => {
585
585
  let t = e.executors.map((t) => {
586
- let n = Qe(t, e.guardVocabulary, e.allowlist);
586
+ let n = et(t, e.guardVocabulary, e.allowlist);
587
587
  return {
588
588
  tag: t.tag,
589
589
  path: t.path,
590
590
  verdict: n,
591
- severity: $e(t, e.tenantScopedTables, e.referencedTables),
592
- why: et(t, n, e.rowFilterRegistered === !0)
591
+ severity: tt(t, e.tenantScopedTables, e.referencedTables),
592
+ why: nt(t, n, e.rowFilterRegistered === !0)
593
593
  };
594
594
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
595
595
  return {
@@ -603,7 +603,7 @@ var le = /* @__PURE__ */ new Set([
603
603
  },
604
604
  unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
605
605
  };
606
- }, nt = [
606
+ }, it = [
607
607
  "# voltro doctor — authz allowlist",
608
608
  "#",
609
609
  "# Two KINDS of line, and the difference is the point:",
@@ -628,19 +628,19 @@ var le = /* @__PURE__ */ new Set([
628
628
  "# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
629
629
  "# lines for executors that are newly unchecked.",
630
630
  ""
631
- ], rt = (e) => {
631
+ ], at = (e) => {
632
632
  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));
633
633
  return [
634
- ...nt,
634
+ ...it,
635
635
  ...n,
636
636
  ...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
637
637
  ...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
638
638
  ""
639
639
  ].join("\n");
640
- }, it = (e, t) => {
640
+ }, ot = (e, t) => {
641
641
  let n = [...new Set(t)].sort();
642
- if (e === void 0) return rt(n.map((e) => ({ tag: e })));
643
- let r = N(e).tags, i = n.filter((e) => !r.has(e));
642
+ if (e === void 0) return at(n.map((e) => ({ tag: e })));
643
+ let r = j(e).tags, i = n.filter((e) => !r.has(e));
644
644
  return i.length === 0 ? e : [
645
645
  e.replace(/\n+$/, ""),
646
646
  "",
@@ -649,25 +649,25 @@ var le = /* @__PURE__ */ new Set([
649
649
  ...i,
650
650
  ""
651
651
  ].join("\n");
652
- }, at = (e, t) => {
652
+ }, st = (e, t) => {
653
653
  let n = [...new Set(t)];
654
654
  if (e === void 0) return {
655
655
  added: n.length,
656
656
  kept: 0,
657
657
  removed: 0
658
658
  };
659
- let r = N(e).tags;
659
+ let r = j(e).tags;
660
660
  return {
661
661
  added: n.filter((e) => !r.has(e)).length,
662
662
  kept: r.size,
663
663
  removed: 0
664
664
  };
665
- }, ot = class extends Error {
665
+ }, ct = class extends Error {
666
666
  tag;
667
667
  constructor(e) {
668
668
  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";
669
669
  }
670
- }, N = (e) => {
670
+ }, j = (e) => {
671
671
  let t = [];
672
672
  for (let n of e.split("\n")) {
673
673
  let e = n.trim();
@@ -680,7 +680,7 @@ var le = /* @__PURE__ */ new Set([
680
680
  continue;
681
681
  }
682
682
  let a = r[2].trim();
683
- if (a.length === 0) throw new ot(i);
683
+ if (a.length === 0) throw new ct(i);
684
684
  t.push({
685
685
  tag: i,
686
686
  reviewed: a
@@ -690,13 +690,13 @@ var le = /* @__PURE__ */ new Set([
690
690
  tags: new Set(t.map((e) => e.tag)),
691
691
  entries: t
692
692
  };
693
- }, st = (e) => {
693
+ }, lt = (e) => {
694
694
  let t = e.indexOf(".");
695
695
  return t <= 0 ? void 0 : e.slice(0, t);
696
- }, ct = (e, t) => {
696
+ }, ut = (e, t) => {
697
697
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
698
698
  for (let e of t.rpcTags) {
699
- let t = st(e);
699
+ let t = lt(e);
700
700
  if (t === void 0) continue;
701
701
  let n = i.get(t);
702
702
  n ? n.push(e) : i.set(t, [e]);
@@ -725,7 +725,7 @@ var le = /* @__PURE__ */ new Set([
725
725
  });
726
726
  continue;
727
727
  }
728
- let s = new Set(o.rpcTags.map(st).filter((e) => e !== void 0));
728
+ let s = new Set(o.rpcTags.map(lt).filter((e) => e !== void 0));
729
729
  for (let e of s) {
730
730
  let t = i.get(e);
731
731
  t !== void 0 && a.push({
@@ -738,12 +738,12 @@ var le = /* @__PURE__ */ new Set([
738
738
  }
739
739
  }
740
740
  return a;
741
- }, lt = (e, t) => {
741
+ }, dt = (e, t) => {
742
742
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
743
743
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
744
744
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
745
745
  return r;
746
- }, ut = async (e) => {
746
+ }, ft = async (e) => {
747
747
  if (!await e.hasWorkflows().catch(() => !1)) return { kind: "not-applicable" };
748
748
  let t;
749
749
  try {
@@ -767,12 +767,12 @@ var le = /* @__PURE__ */ new Set([
767
767
  } finally {
768
768
  await t.close().catch(() => {});
769
769
  }
770
- }, dt = (e) => {
770
+ }, pt = (e) => {
771
771
  let t = Math.round(e / 6e4);
772
772
  if (t < 90) return `${t}m`;
773
773
  let n = Math.round(t / 60);
774
774
  return n < 48 ? `${n}h` : `${Math.round(n / 24)}d`;
775
- }, ft = (e) => {
775
+ }, mt = (e) => {
776
776
  if (e.kind === "not-applicable") return null;
777
777
  if (e.kind === "skipped") return `• stuck runs: NOT CHECKED — ${e.reason}\n This is the one doctor rule that reads the database rather than your source.
778
778
  Run it where the app's DB env vars are set, or ignore this line locally.`;
@@ -780,14 +780,41 @@ var le = /* @__PURE__ */ new Set([
780
780
  if (t.stalled.length === 0) n.push(`✓ stuck runs: none (${t.examined} live run(s) old enough to examine${t.waitingOnTimer > 0 ? `, ${t.waitingOnTimer} waiting on a durable timer` : ""}${t.alreadyReported > 0 ? `, ${t.alreadyReported} already reported` : ""})`);
781
781
  else {
782
782
  n.push(`✗ stuck runs: ${t.stalled.length} run(s) have made no progress`);
783
- for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${dt(e.idleMs)} ${e.reason}`);
783
+ for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${pt(e.idleMs)} ${e.reason}`);
784
784
  t.stalled.length > 20 && n.push(` … and ${t.stalled.length - 20} more`), n.push(" Nothing was changed — this is a SUSPICION, not a verdict. `awaiting-signal` usually means the sender never came.");
785
785
  }
786
786
  t.sawFullRunPage && n.push(" (the examined page filled up — more old runs exist than were read this pass)");
787
787
  for (let e of t.failures) n.push(` ! could not examine ${e.subject}: ${e.detail}`);
788
788
  return n.join("\n");
789
- }, pt = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), mt = (e) => {
790
- let t = m(e), n = p(e);
789
+ }, ht = /(?:^|\n)\s*import\s+(?!type\s)[^;'"]*?from\s+['"]@voltro\/ui-shadcn(?:\/(?!tokens\.css)[^'"]+)?['"]/, gt = /@import\s+["']@voltro\/ui-shadcn\/tokens\.css["']|@import\s+["']tailwindcss["']|from\s+['"]@voltro\/ui-shadcn\/tokens\.css['"]|import\s+['"][^'"]*@voltro\/ui-shadcn\/tokens\.css['"]/, _t = (e) => {
790
+ let t = [], n = [];
791
+ for (let r of e) gt.test(r.source) ? n.push(r.path) : !r.path.endsWith(".css") && ht.test(r.source) && t.push(r.path);
792
+ return {
793
+ kitImporters: t,
794
+ styleEntries: n
795
+ };
796
+ }, vt = (e) => e.kitImporters.length > 0 && e.styleEntries.length === 0, yt = (e) => {
797
+ if (!vt(e)) return [];
798
+ let t = e.kitImporters.slice(0, 5);
799
+ return [
800
+ `⚠ ui-shadcn: ${e.kitImporters.length} file(s) render kit components, and no CSS entry imports the kit's stylesheet.`,
801
+ ...t.map((e) => ` ${e}`),
802
+ ...e.kitImporters.length > t.length ? [` … and ${e.kitImporters.length - t.length} more`] : [],
803
+ "",
804
+ " The kit's components are Tailwind utility classes and nothing else, and",
805
+ " Tailwind v4 emits a utility only when a CSS entry declares it. Without",
806
+ " the stylesheet those classes name rules that do not exist — the markup",
807
+ " renders as bare browser controls. Nothing else reports this: the build",
808
+ " succeeds, `tsc` succeeds, and the page renders.",
809
+ "",
810
+ " Fix — add ONE line to the CSS your root layout imports:",
811
+ " @import \"@voltro/ui-shadcn/tokens.css\";",
812
+ "",
813
+ " Or, if this app is not a kit app, import the unstyled control instead:",
814
+ " import { LocaleSwitcher } from '@voltro/i18n'"
815
+ ];
816
+ }, bt = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), xt = (e) => {
817
+ let t = f(e), n = d(e);
791
818
  return {
792
819
  root: e,
793
820
  serveBundle: t,
@@ -795,22 +822,22 @@ var le = /* @__PURE__ */ new Set([
795
822
  apiBundle: n,
796
823
  apiBundlePresent: S(n)
797
824
  };
798
- }, ht = [
825
+ }, St = [
799
826
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
800
827
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
801
828
  "(Generated Dockerfiles from `voltro create-project` already do this.)"
802
- ].join("\n "), gt = (e, t = !1) => {
803
- let n = mt(e);
804
- if (console.log(`voltro doctor — serve preflight for ${n.root}`), !pt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
829
+ ].join("\n "), Ct = (e, t = !1) => {
830
+ let n = xt(e);
831
+ if (console.log(`voltro doctor — serve preflight for ${n.root}`), !bt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
805
832
  console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
806
833
  let r = process.env.NODE_ENV === "production";
807
834
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
808
835
  let i = t || r;
809
- return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${ht}`), +!!i;
810
- }, _t = (e) => {
836
+ return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${St}`), +!!i;
837
+ }, wt = (e) => {
811
838
  let t = e.absPath.replace(/\.server\.(tsx?)$/, ".$1");
812
839
  if (t !== e.absPath) return e.sources.get(t) ?? e.sources.get(t.replace(/\.ts$/, ".tsx"));
813
- }, vt = (e, t, n) => {
840
+ }, Tt = (e, t, n) => {
814
841
  let r = new Set(e), i = _(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
815
842
  `${e}.ts`,
816
843
  `${e}.tsx`,
@@ -836,7 +863,7 @@ var le = /* @__PURE__ */ new Set([
836
863
  r !== void 0 && o.push(r);
837
864
  }
838
865
  return o;
839
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => St.test(y(n, e))), l = new Set(c);
866
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => zt.test(y(n, e))), l = new Set(c);
840
867
  for (; c.length > 0;) {
841
868
  let e = c.shift();
842
869
  if (!s.has(e)) {
@@ -849,11 +876,11 @@ var le = /* @__PURE__ */ new Set([
849
876
  let d = /* @__PURE__ */ new Set();
850
877
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
851
878
  return d;
852
- }, P = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), F = (e, t) => (e.match(t) ?? []).length, I = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, yt = (e, t) => {
879
+ }, M = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Et = (e, t) => (e.match(t) ?? []).length, N = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Dt = (e, t) => {
853
880
  if (e.src.includes(t)) return !0;
854
881
  let n = t.replace(/^@voltro\/plugin-/, "");
855
- return n !== t && I(e.file, RegExp(`^${n}Plugin$`)) > 0;
856
- }, bt = (e, t) => {
882
+ return n !== t && N(e.file, RegExp(`^${n}Plugin$`)) > 0;
883
+ }, Ot = (e, t) => {
857
884
  let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
858
885
  for (; (i = r.exec(e)) !== null;) {
859
886
  let t = i.index + i[0].length, r = 1, a = t;
@@ -861,10 +888,122 @@ var le = /* @__PURE__ */ new Set([
861
888
  n.push(e.slice(t, a - 1));
862
889
  }
863
890
  return n;
864
- }, xt = (e, t) => {
891
+ }, kt = (e, t) => {
865
892
  let n = t.getSymbol()?.getDeclarations()?.[0];
866
893
  return n === void 0 ? [] : e.getDescendantsOfKind(O.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
867
- }, St = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Ct = /\.(query|mutation|action|stream)\.server\.tsx?$/, wt = [
894
+ }, At = /^(insert|insertMany|insertIgnore|update|updateMany|upsert|delete|deleteMany|hardDelete|patchJson)$/, jt = /^(notify|sendNotification|sendWebhook|dispatchWebhook|sendMail|trigger)\w*$/, Mt = (e, t) => {
895
+ if (!E.isObjectLiteralExpression(e)) return;
896
+ let n = e.getProperty("handler");
897
+ if (n === void 0 || !E.isPropertyAssignment(n)) return;
898
+ let r = n.getInitializer();
899
+ if (r === void 0) return;
900
+ if (E.isArrowFunction(r) || E.isFunctionExpression(r)) return r;
901
+ if (!E.isIdentifier(r)) return;
902
+ let i = t.getFunction(r.getText());
903
+ if (i !== void 0) return i;
904
+ let a = t.getVariableDeclaration(r.getText())?.getInitializer();
905
+ if (a !== void 0 && (E.isArrowFunction(a) || E.isFunctionExpression(a))) return a;
906
+ }, Nt = (e) => {
907
+ let t = [jt], n = (E.isArrowFunction(e) || E.isFunctionExpression(e) || E.isFunctionDeclaration(e) ? e.getParameters() : [])[1];
908
+ if (n === void 0) return t;
909
+ let r = n.getNameNode();
910
+ if (E.isIdentifier(r)) return t.push(RegExp(`^${r.getText()}\\.publish$`)), t.push(RegExp(`^${r.getText()}\\.store\\.${At.source.replace(/^\^|\$$/g, "")}$`)), t;
911
+ if (E.isObjectBindingPattern(r)) for (let e of r.getElements()) {
912
+ let n = e.getPropertyNameNode()?.getText() ?? e.getName(), r = e.getName();
913
+ n === "publish" && t.push(RegExp(`^${r}$`)), n === "store" && t.push(RegExp(`^${r}\\.${At.source.replace(/^\^|\$$/g, "")}$`));
914
+ }
915
+ return t;
916
+ }, Pt = (e, t, n, r = 0, i = /* @__PURE__ */ new Set()) => {
917
+ let a = e.getDescendantsOfKind(O.CallExpression);
918
+ for (let e of a) {
919
+ let t = e.getExpression().getText();
920
+ if (n.some((e) => e.test(t))) return !0;
921
+ }
922
+ if (r >= 2) return !1;
923
+ for (let e of a) {
924
+ let a = e.getExpression().getText();
925
+ if (!/^[A-Za-z_$][\w$]*$/.test(a) || i.has(a)) continue;
926
+ i.add(a);
927
+ let o = t.getFunction(a) ?? (() => {
928
+ let e = t.getVariableDeclaration(a)?.getInitializer();
929
+ return e !== void 0 && (E.isArrowFunction(e) || E.isFunctionExpression(e)) ? e : void 0;
930
+ })();
931
+ if (o !== void 0 && Pt(o, t, [...n, ...Nt(o)], r + 1, i)) return !0;
932
+ }
933
+ return !1;
934
+ }, Ft = (e) => {
935
+ let t = /* @__PURE__ */ new Set();
936
+ for (let n of e.values()) {
937
+ if (!/\bsetRowFilter\s*[<(]/.test(n)) continue;
938
+ let e = /\btables\s*:\s*\[([^\]]*)\]/.exec(n);
939
+ if (e !== null) for (let n of e[1].matchAll(/['"`]([A-Za-z_][\w]*)['"`]/g)) t.add(n[1]);
940
+ }
941
+ return t;
942
+ }, It = (e) => {
943
+ if (!/\.(query|stream|mutation|action)\.server\.tsx?$/.test(e.path)) return !1;
944
+ let t = Ft(e.sources);
945
+ return t.size === 0 || ![...e.src.matchAll(/\btable\s*:\s*['"`]([A-Za-z_][\w]*)['"`]/g)].map((e) => e[1]).some((e) => t.has(e)) ? !1 : /\b(eq|and|inSet)\s*\(/.test(e.src) || /\bwhere\s*:/.test(e.src);
946
+ }, Lt = (e) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").some((t) => {
947
+ let n = t.getArguments()[0];
948
+ if (n === void 0 || !E.isObjectLiteralExpression(n) || n.getProperty("once") !== void 0) return !1;
949
+ let r = Mt(n, e);
950
+ return r !== void 0 && Pt(r, e, Nt(r));
951
+ }), Rt = (e) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").filter((e) => {
952
+ let t = e.getArguments()[0];
953
+ return t !== void 0 && E.isObjectLiteralExpression(t) && t.getProperty("once") === void 0;
954
+ }).length, zt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Bt = (e, t) => {
955
+ let n;
956
+ try {
957
+ n = t.createSourceFile("/descriptor-scan.ts", e, { overwrite: !0 });
958
+ } catch {
959
+ return;
960
+ }
961
+ for (let e of n.getDescendantsOfKind(O.PropertyAssignment)) {
962
+ if (e.getName() !== "output") continue;
963
+ let t = e.getInitializer();
964
+ if (t !== void 0 && E.isCallExpression(t) && /\.Array$/.test(t.getExpression().getText()) && (t = t.getArguments()[0]), t === void 0 || !E.isCallExpression(t) || !/\.Struct$/.test(t.getExpression().getText())) return;
965
+ let n = t.getArguments()[0];
966
+ if (n === void 0 || !E.isObjectLiteralExpression(n)) return;
967
+ let r = [];
968
+ for (let e of [n, ...n.getDescendantsOfKind(O.ObjectLiteralExpression)]) for (let t of e.getProperties()) if (E.isPropertyAssignment(t) || E.isShorthandPropertyAssignment(t)) r.push(t.getName());
969
+ else if (E.isSpreadAssignment(t)) return;
970
+ return r;
971
+ }
972
+ }, Vt = (e, t) => {
973
+ let n = [], r = (e) => {
974
+ e !== void 0 && (E.isObjectLiteralExpression(e) ? n.push(e) : E.isParenthesizedExpression(e) && r(e.getExpression()));
975
+ };
976
+ for (let t of e.getDescendantsOfKind(O.ReturnStatement)) r(t.getExpression());
977
+ for (let t of e.getDescendantsOfKind(O.ArrowFunction)) r(t.getBody());
978
+ let i = /* @__PURE__ */ new Set();
979
+ for (let e of n) {
980
+ let n = [];
981
+ for (let t of e.getProperties()) if (E.isPropertyAssignment(t) || E.isShorthandPropertyAssignment(t)) n.push(t.getName());
982
+ else if (E.isSpreadAssignment(t)) return;
983
+ if (n.some((e) => t.has(e))) for (let e of n) i.add(e);
984
+ }
985
+ return [...i];
986
+ }, Ht = (e, t, n) => {
987
+ let r = RegExp(`\\btable\\(\\s*['"\`]${e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"\`]`);
988
+ for (let [, i] of t) {
989
+ if (!r.test(i)) continue;
990
+ let t;
991
+ try {
992
+ t = n.createSourceFile("/entity-scan.ts", i, { overwrite: !0 });
993
+ } catch {
994
+ continue;
995
+ }
996
+ for (let n of t.getDescendantsOfKind(O.CallExpression)) {
997
+ if (n.getExpression().getText() !== "table") continue;
998
+ let [t, r] = n.getArguments();
999
+ if (t === void 0 || !E.isStringLiteral(t) || t.getLiteralValue() !== e) continue;
1000
+ if (r === void 0 || !E.isObjectLiteralExpression(r)) return;
1001
+ let i = /* @__PURE__ */ new Set();
1002
+ for (let e of r.getProperties()) E.isPropertyAssignment(e) && /\.nullable\s*\(/.test(e.getText()) && i.add(e.getName());
1003
+ return i;
1004
+ }
1005
+ }
1006
+ }, Ut = (e) => /\bNullOr\b|\bSchema\.Null\b|\bnullable\s*\(/.test(e), P = /\.(query|mutation|action|stream)\.server\.tsx?$/, Wt = [
868
1007
  "Id",
869
1008
  "ID",
870
1009
  "Hash",
@@ -890,7 +1029,7 @@ var le = /* @__PURE__ */ new Set([
890
1029
  "Comment",
891
1030
  "Provider",
892
1031
  "Format"
893
- ], Tt = RegExp(`(?:${wt.join("|")})$|_(?:${wt.map((e) => e.toLowerCase()).join("|")})$`), Et = [
1032
+ ], Gt = RegExp(`(?:${Wt.join("|")})$|_(?:${Wt.map((e) => e.toLowerCase()).join("|")})$`), Kt = [
894
1033
  {
895
1034
  id: "hardcoded-framework-cookie",
896
1035
  scope: "client",
@@ -903,7 +1042,7 @@ var le = /* @__PURE__ */ new Set([
903
1042
  scope: "server",
904
1043
  smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
905
1044
  use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
906
- match: ({ src: e, path: t }) => Ct.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1045
+ match: ({ src: e, path: t }) => P.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
907
1046
  },
908
1047
  {
909
1048
  id: "subject-write-no-guard",
@@ -912,12 +1051,12 @@ var le = /* @__PURE__ */ new Set([
912
1051
  use: "declare `guards:` on the descriptor, or — if the procedure really is open — compare the loaded row's owner against `ctx.request.subject.id` and fail with a typed error",
913
1052
  match: (e) => {
914
1053
  let { src: t, path: n } = e;
915
- if (!Ct.test(n)) return !1;
916
- let r = P(t);
1054
+ if (!P.test(n)) return !1;
1055
+ let r = M(t);
917
1056
  if (!(/\bsubject\s*\.\s*id\b/.test(r) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(r)) || /\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(r) || /[=!]==?\s*[^;\n]*\bsubject\s*\.\s*id\b|\bsubject\s*\.\s*id\b\s*[=!]==/.test(r)) return !1;
918
- let i = _t(e);
1057
+ let i = wt(e);
919
1058
  if (i === void 0) return !1;
920
- let a = P(i);
1059
+ let a = M(i);
921
1060
  return /\binternal\s*:\s*true\b/.test(a) || /\bguards\s*:\s*\[/.test(a) || /\.guard\s*\(/.test(a) ? !1 : /\bopenAccess\s*:/.test(a);
922
1061
  }
923
1062
  },
@@ -926,14 +1065,14 @@ var le = /* @__PURE__ */ new Set([
926
1065
  scope: "server",
927
1066
  smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
928
1067
  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",
929
- match: ({ file: e, path: t, serverOnly: n }) => (St.test(t) || n) && I(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1068
+ match: ({ file: e, path: t, serverOnly: n }) => (zt.test(t) || n) && N(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
930
1069
  },
931
1070
  {
932
1071
  id: "form",
933
1072
  scope: "client",
934
1073
  smell: "per-field useState + a submit flag (hand-rolled form)",
935
1074
  use: "useFormBinding — fields + validation from the mutation input Schema; server ValidationError({ field }) routes to the field",
936
- match: ({ src: e, file: t }) => I(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
1075
+ match: ({ src: e, file: t }) => N(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
937
1076
  },
938
1077
  {
939
1078
  id: "table",
@@ -961,7 +1100,7 @@ var le = /* @__PURE__ */ new Set([
961
1100
  scope: "client",
962
1101
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
963
1102
  use: "useDerived — dependency-tracked, referentially stable",
964
- match: ({ src: e }) => F(e, /\buseSubscription\s*[(<]/g) >= 3 && F(e, /\buseMemo\s*\(/g) >= 3
1103
+ match: ({ src: e }) => Et(e, /\buseSubscription\s*[(<]/g) >= 3 && Et(e, /\buseMemo\s*\(/g) >= 3
965
1104
  },
966
1105
  {
967
1106
  id: "next-shim",
@@ -993,7 +1132,7 @@ var le = /* @__PURE__ */ new Set([
993
1132
  match: ({ file: e }) => e.getDescendantsOfKind(O.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
994
1133
  let t = e.getFirstAncestorByKind(O.VariableDeclaration)?.getNameNode();
995
1134
  return t === void 0 ? [] : E.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
996
- }).some((t) => xt(e, t).some((e) => {
1135
+ }).some((t) => kt(e, t).some((e) => {
997
1136
  let t = e.getParent();
998
1137
  if (E.isBinaryExpression(t)) {
999
1138
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -1021,7 +1160,7 @@ var le = /* @__PURE__ */ new Set([
1021
1160
  scope: "server",
1022
1161
  smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
1023
1162
  use: "reads are a parent→child walk on ONE key → relations() in a *.relations.ts + .with({ … }) (ONE JSON-aggregate query, every dialect); reads collect ids from SEVERAL sources → keep the assembly and run the independent leading reads under Effect.all (same queries, same results, only concurrent — zero parity risk)",
1024
- match: (e) => I(e.file, /\bstore\.query$/) >= 3 || I(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
1163
+ match: (e) => N(e.file, /\bstore\.query$/) >= 3 || N(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
1025
1164
  },
1026
1165
  {
1027
1166
  id: "store-effect-lift",
@@ -1044,7 +1183,7 @@ var le = /* @__PURE__ */ new Set([
1044
1183
  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)",
1045
1184
  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) => {
1046
1185
  let n = t[1];
1047
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Tt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1186
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Gt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1048
1187
  }))
1049
1188
  },
1050
1189
  {
@@ -1054,15 +1193,67 @@ var le = /* @__PURE__ */ new Set([
1054
1193
  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",
1055
1194
  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) => {
1056
1195
  let n = t[1];
1057
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Tt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1196
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Gt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1058
1197
  }))
1059
1198
  },
1199
+ {
1200
+ id: "nullable-column-a-mutation-cannot-clear",
1201
+ scope: "server",
1202
+ smell: "a mutation writes a NULLABLE column through an input field that does not accept `null` — the value can be set once and never cleared",
1203
+ use: "declare the field as `Schema.optional(Schema.NullOr(...))` — a column the schema lets be empty needs a wire shape that can say so",
1204
+ match: (e) => {
1205
+ if (!/\.mutation\.tsx?$/.test(e.path)) return !1;
1206
+ let t = /target\s*:\s*\{[^}]*\btable\s*:\s*['"`]([^'"`]+)['"`]/.exec(e.src)?.[1];
1207
+ if (t === void 0) return !1;
1208
+ let n = Ht(t, e.sources, e.file.getProject());
1209
+ if (n === void 0 || n.size === 0) return !1;
1210
+ for (let t of e.file.getDescendantsOfKind(O.PropertyAssignment)) {
1211
+ if (t.getName() !== "input") continue;
1212
+ let e = t.getInitializer();
1213
+ if (e === void 0 || !E.isCallExpression(e) || !/\.Struct$/.test(e.getExpression().getText())) continue;
1214
+ let r = e.getArguments()[0];
1215
+ if (!(r === void 0 || !E.isObjectLiteralExpression(r))) {
1216
+ for (let e of r.getProperties()) if (E.isPropertyAssignment(e) && n.has(e.getName()) && !Ut(e.getText())) return !0;
1217
+ }
1218
+ }
1219
+ return !1;
1220
+ }
1221
+ },
1222
+ {
1223
+ id: "executor-builds-an-undeclared-field",
1224
+ scope: "server",
1225
+ smell: "an executor builds a field its descriptor's `output` does not declare (silently stripped on the way out)",
1226
+ use: "add the field to the descriptor's `output` struct, or stop building it — `output` IS the serializer, so an undeclared key never reaches the caller",
1227
+ match: (e) => {
1228
+ if (!P.test(e.path)) return !1;
1229
+ let t = wt(e);
1230
+ if (t === void 0) return !1;
1231
+ let n = Bt(t, e.file.getProject());
1232
+ if (n === void 0 || n.length === 0) return !1;
1233
+ let r = Vt(e.file, new Set(n));
1234
+ return r === void 0 || r.length === 0 ? !1 : r.some((e) => !n.includes(e));
1235
+ }
1236
+ },
1237
+ {
1238
+ id: "subscriber-effect-without-once",
1239
+ scope: "server",
1240
+ smell: "a *.subscribe.ts handler that WRITES or publishes with no `once:` (it runs on every replica, so the effect repeats once per replica)",
1241
+ use: "once: true on defineSubscriber — one INSERT-wins claim per change, fleet-wide (or `once: (event) => key` for a coarser key). A hand-rolled `insertIgnore` dedupe is this, written out.",
1242
+ match: ({ file: e }) => Lt(e)
1243
+ },
1244
+ {
1245
+ id: "row-filter-duplicated-by-hand",
1246
+ scope: "server",
1247
+ smell: "a handler that constrains a table your registered `setRowFilter` already constrains",
1248
+ use: "drop the hand-written predicate — the filter covers the query, the mutation, the stream AND the subscription delivery. Keep it only if this handler must be NARROWER than the filter, and say so in a comment (two identical predicates ANDed are as tight as one, so this is maintenance, not a leak)",
1249
+ match: (e) => It(e)
1250
+ },
1060
1251
  {
1061
1252
  id: "mutation-tail-effect",
1062
1253
  scope: "server",
1063
1254
  smell: "a notify / webhook helper called at the tail of a mutation",
1064
1255
  use: "defineSubscriber (*.subscribe.ts) or defineReaction (*.reaction.tsx) — declarative, post-commit, with the old/new diff, instead of reactivity hidden inside every executor",
1065
- match: ({ src: e, path: t }) => /\b(notify|sendNotification|sendWebhook|dispatchWebhook|trigger[A-Z]\w*)\s*\(/.test(e) && (/\bexport\s+default\b/.test(e) || /\.(mutation|action)\.server\.tsx?$/.test(t))
1256
+ match: ({ src: e, path: t, file: n }) => /\b(notify|sendNotification|sendWebhook|dispatchWebhook|trigger[A-Z]\w*)\s*\(/.test(e) && (/\bexport\s+default\b/.test(e) || /\.(mutation|action)\.server\.tsx?$/.test(t)) && N(n, /^(defineSubscriber|defineReaction)$/) === 0
1066
1257
  },
1067
1258
  {
1068
1259
  id: "hand-cursor",
@@ -1118,9 +1309,9 @@ var le = /* @__PURE__ */ new Set([
1118
1309
  scope: "server",
1119
1310
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
1120
1311
  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",
1121
- match: ({ file: e, testSources: t }) => I(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1122
- let t = P(e);
1123
- return bt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1312
+ match: ({ file: e, testSources: t }) => N(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1313
+ let t = M(e);
1314
+ return Ot(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1124
1315
  })
1125
1316
  },
1126
1317
  {
@@ -1130,15 +1321,15 @@ var le = /* @__PURE__ */ new Set([
1130
1321
  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",
1131
1322
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1132
1323
  }
1133
- ], Dt = (e) => R(e), Ot = (e, t) => {
1324
+ ], F = (e) => L(e), qt = (e, t) => {
1134
1325
  let n = /* @__PURE__ */ new Set();
1135
1326
  for (let r of t) {
1136
1327
  let t = y(e, r), i = t.split(/[\\/]/)[0];
1137
1328
  n.add(i !== void 0 && i !== t ? i : ".");
1138
1329
  }
1139
1330
  return [...n].sort();
1140
- }, kt = /\.(tsx?|jsx?)$/, L = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, At = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, jt = (e, t = [], n = 0) => {
1141
- if (n > 12 || L.test(e)) return t;
1331
+ }, Jt = /\.(tsx?|jsx?)$/, I = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Yt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Xt = (e, t = [], n = 0) => {
1332
+ if (n > 12 || I.test(e)) return t;
1142
1333
  let r;
1143
1334
  try {
1144
1335
  r = w(e, { withFileTypes: !0 });
@@ -1149,12 +1340,12 @@ var le = /* @__PURE__ */ new Set([
1149
1340
  let r = v(e, i.name);
1150
1341
  if (i.isDirectory()) {
1151
1342
  if (i.name.startsWith(".")) continue;
1152
- jt(r, t, n + 1);
1343
+ Xt(r, t, n + 1);
1153
1344
  } else je.test(i.name) && t.push(r);
1154
1345
  }
1155
1346
  return t;
1156
- }, R = (e, t = [], n = 0) => {
1157
- if (n > 12 || L.test(e)) return t;
1347
+ }, L = (e, t = [], n = 0) => {
1348
+ if (n > 12 || I.test(e)) return t;
1158
1349
  let r;
1159
1350
  try {
1160
1351
  r = w(e);
@@ -1168,11 +1359,11 @@ var le = /* @__PURE__ */ new Set([
1168
1359
  } catch {
1169
1360
  continue;
1170
1361
  }
1171
- a.isDirectory() ? R(r, t, n + 1) : kt.test(i) && !/\.(test|spec)\./.test(i) && !At.test(i) && t.push(r);
1362
+ a.isDirectory() ? L(r, t, n + 1) : Jt.test(i) && !/\.(test|spec)\./.test(i) && !Yt.test(i) && t.push(r);
1172
1363
  }
1173
1364
  return t;
1174
- }, Mt = (e, t = [], n = 0) => {
1175
- if (n > 12 || L.test(e)) return t;
1365
+ }, Zt = (e, t = [], n = 0) => {
1366
+ if (n > 12 || I.test(e)) return t;
1176
1367
  let r;
1177
1368
  try {
1178
1369
  r = w(e);
@@ -1186,17 +1377,17 @@ var le = /* @__PURE__ */ new Set([
1186
1377
  } catch {
1187
1378
  continue;
1188
1379
  }
1189
- a.isDirectory() ? Mt(r, t, n + 1) : kt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1380
+ a.isDirectory() ? Zt(r, t, n + 1) : Jt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1190
1381
  }
1191
1382
  return t;
1192
- }, Nt = (e) => {
1193
- let t = Dt(e);
1383
+ }, Qt = (e) => {
1384
+ let t = F(e);
1194
1385
  return {
1195
- dirs: Ot(e, t),
1386
+ dirs: qt(e, t),
1196
1387
  fileCount: t.length
1197
1388
  };
1198
- }, Pt = (e) => {
1199
- let t = Dt(e);
1389
+ }, $t = (e) => {
1390
+ let t = F(e);
1200
1391
  if (t.length === 0) return [];
1201
1392
  let n = new D({
1202
1393
  useInMemoryFileSystem: !0,
@@ -1205,8 +1396,8 @@ var le = /* @__PURE__ */ new Set([
1205
1396
  for (let e of t) try {
1206
1397
  i.set(e, C(e, "utf8"));
1207
1398
  } catch {}
1208
- let a = vt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1209
- for (let t of Mt(e)) try {
1399
+ let a = Tt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1400
+ for (let t of Zt(e)) try {
1210
1401
  o.set(t, C(t, "utf8"));
1211
1402
  } catch {}
1212
1403
  for (let s of t) {
@@ -1226,23 +1417,34 @@ var le = /* @__PURE__ */ new Set([
1226
1417
  } catch {
1227
1418
  continue;
1228
1419
  }
1229
- for (let t of Et) {
1230
- if (t.plugin !== void 0 && yt(c, t.plugin) || !t.match(c)) continue;
1420
+ for (let t of Kt) {
1421
+ if (t.plugin !== void 0 && Dt(c, t.plugin) || !t.match(c)) continue;
1231
1422
  let n = r.get(t.id) ?? [];
1232
1423
  n.push(y(e, s)), r.set(t.id, n);
1233
1424
  }
1234
1425
  }
1235
- return Et.filter((e) => r.has(e.id)).map((e) => ({
1426
+ return Kt.filter((e) => r.has(e.id)).map((e) => ({
1236
1427
  rule: e,
1237
1428
  files: r.get(e.id)
1238
1429
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1239
- }, z = (e) => {
1240
- let t = Nt(e);
1430
+ }, en = (e) => {
1431
+ let t = new D({
1432
+ useInMemoryFileSystem: !0,
1433
+ skipAddingFilesFromTsConfig: !0
1434
+ }), n = 0;
1435
+ for (let r of F(e)) if (/\.subscribe\.tsx?$/.test(r)) try {
1436
+ let e = t.createSourceFile(`/count${r.endsWith("x") ? ".tsx" : ".ts"}`, C(r, "utf8"), { overwrite: !0 });
1437
+ n += Rt(e);
1438
+ } catch {}
1439
+ return n;
1440
+ }, R = (e) => {
1441
+ let t = Qt(e);
1241
1442
  return {
1242
1443
  root: e,
1243
1444
  scannedFiles: t.fileCount,
1244
1445
  scannedDirs: t.dirs,
1245
- findings: Pt(e).map(({ rule: e, files: t }) => ({
1446
+ subscribersWithoutOnce: en(e),
1447
+ findings: $t(e).map(({ rule: e, files: t }) => ({
1246
1448
  id: e.id,
1247
1449
  scope: e.scope,
1248
1450
  smell: e.smell,
@@ -1250,13 +1452,13 @@ var le = /* @__PURE__ */ new Set([
1250
1452
  files: t
1251
1453
  }))
1252
1454
  };
1253
- }, Ft = 3, It = .2, Lt = 50, Rt = (e, t) => e >= Lt && t > 0 && e / t >= It, zt = (e) => {
1455
+ }, tn = 3, nn = .2, rn = 50, an = (e, t) => e >= rn && t > 0 && e / t >= nn, on = (e) => {
1254
1456
  let t = /* @__PURE__ */ new Map();
1255
- for (let n of R(e)) try {
1457
+ for (let n of L(e)) try {
1256
1458
  t.set(y(e, n), C(n, "utf8"));
1257
1459
  } catch {}
1258
1460
  let n = /* @__PURE__ */ new Map();
1259
- for (let t of jt(e)) try {
1461
+ for (let t of Xt(e)) try {
1260
1462
  n.set(y(e, t), C(t, "utf8"));
1261
1463
  } catch {}
1262
1464
  let r = Me({
@@ -1264,8 +1466,8 @@ var le = /* @__PURE__ */ new Set([
1264
1466
  envFiles: n
1265
1467
  });
1266
1468
  r !== void 0 && (console.log(""), console.log(Ne(r)));
1267
- }, Bt = (e) => {
1268
- let t = z(e);
1469
+ }, sn = (e) => {
1470
+ let t = R(e);
1269
1471
  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;
1270
1472
  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;
1271
1473
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
@@ -1276,37 +1478,43 @@ var le = /* @__PURE__ */ new Set([
1276
1478
  console.log(` [${e}]`);
1277
1479
  let i = [...r].sort((e, t) => e.files.length - t.files.length);
1278
1480
  for (let e of i) {
1279
- if (Rt(e.files.length, t.scannedFiles)) {
1481
+ if (an(e.files.length, t.scannedFiles)) {
1280
1482
  let n = Math.round(e.files.length / t.scannedFiles * 100);
1281
1483
  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.)");
1282
1484
  continue;
1283
1485
  }
1284
- let r = e.files.slice(0, Ft).join(", "), i = e.files.length - Ft;
1486
+ let r = e.files.slice(0, tn).join(", "), i = e.files.length - tn;
1285
1487
  i > 0 && (n += i);
1286
1488
  let a = i > 0 ? `, +${i} more` : "";
1287
1489
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1288
1490
  }
1289
1491
  }
1290
- 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;
1291
- }, Vt = "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.", Ht = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, Ut = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, Wt = (e) => {
1292
- let t = Ht.exec(e)?.[1];
1492
+ n > 0 && console.log(` (${n} more file path${n === 1 ? "" : "s"} not shown \u2014 \`voltro doctor --json\` prints every one.)`);
1493
+ let r = t.findings.find((e) => e.id === "subscriber-effect-without-once");
1494
+ if (t.subscribersWithoutOnce > 0) {
1495
+ let e = r?.files.length ?? 0;
1496
+ console.log(` \u24d8 ${t.subscribersWithoutOnce} subscriber(s) declare no \`once:\`; an effect was found in ${e}. The rule reads each subscriber's OWN file (its handler and the functions that file declares) and does not resolve imports — a write in another module is unexamined, not absent.`);
1497
+ }
1498
+ return console.log(" (See the \"Pick the SERVER primitive\" / \"Pick the CLIENT primitive\" rubrics in AGENTS.md / CLAUDE.md.)"), t.findings.length;
1499
+ }, cn = "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.", ln = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, un = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, dn = (e) => {
1500
+ let t = ln.exec(e)?.[1];
1293
1501
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1294
- let n = Ut.exec(e)?.[1];
1502
+ let n = un.exec(e)?.[1];
1295
1503
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1296
- }, B = async (e) => {
1504
+ }, z = async (e) => {
1297
1505
  let t = v(e, "src", "pages");
1298
1506
  if (!S(t)) return [];
1299
- let { pages: n, dirs: r } = await u(t), i = [];
1507
+ let { pages: n, dirs: r } = await s(t), i = [];
1300
1508
  for (let a of n) {
1301
- if (!c(r, a.dirChain).some((e) => e.hasLayout)) continue;
1509
+ if (!l(r, a.dirChain).some((e) => e.hasLayout)) continue;
1302
1510
  let n;
1303
1511
  try {
1304
1512
  n = C(v(t, a.file), "utf8");
1305
1513
  } catch {
1306
1514
  continue;
1307
1515
  }
1308
- if (l.test(n)) continue;
1309
- let o = Wt(n);
1516
+ if (c.test(n)) continue;
1517
+ let o = dn(n);
1310
1518
  o !== null && i.push({
1311
1519
  file: y(e, v(t, a.file)),
1312
1520
  pattern: a.pattern,
@@ -1314,20 +1522,20 @@ var le = /* @__PURE__ */ new Set([
1314
1522
  });
1315
1523
  }
1316
1524
  return i;
1317
- }, Gt = 10, Kt = async (e) => {
1525
+ }, fn = 10, pn = async (e) => {
1318
1526
  console.log("");
1319
1527
  let t = v(e, "src", "pages");
1320
1528
  if (!S(t)) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
1321
- let n = await B(e);
1529
+ let n = await z(e);
1322
1530
  if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1323
1531
  console.log(`• renderMode:'spa' candidates (${n.length} page${n.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1324
- for (let e of n.slice(0, Gt)) {
1532
+ for (let e of n.slice(0, fn)) {
1325
1533
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1326
1534
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1327
1535
  }
1328
- let r = n.length - Gt;
1329
- return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${Vt}`), n.length;
1330
- }, qt = /* @__PURE__ */ new Set([
1536
+ let r = n.length - fn;
1537
+ return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${cn}`), n.length;
1538
+ }, mn = /* @__PURE__ */ new Set([
1331
1539
  "node_modules",
1332
1540
  "dist",
1333
1541
  "build",
@@ -1335,7 +1543,7 @@ var le = /* @__PURE__ */ new Set([
1335
1543
  ".vite",
1336
1544
  ".framework",
1337
1545
  ".git"
1338
- ]), Jt = (e, t, n) => {
1546
+ ]), hn = (e, t, n) => {
1339
1547
  let r;
1340
1548
  try {
1341
1549
  r = w(e, { withFileTypes: !0 });
@@ -1343,11 +1551,11 @@ var le = /* @__PURE__ */ new Set([
1343
1551
  return;
1344
1552
  }
1345
1553
  for (let i of r) {
1346
- if (qt.has(i.name)) continue;
1554
+ if (mn.has(i.name)) continue;
1347
1555
  let r = v(e, i.name);
1348
- i.isDirectory() ? Jt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1556
+ i.isDirectory() ? hn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1349
1557
  }
1350
- }, Yt = (e, t) => {
1558
+ }, gn = (e, t) => {
1351
1559
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1352
1560
  for (let i of t) {
1353
1561
  let t;
@@ -1372,21 +1580,21 @@ var le = /* @__PURE__ */ new Set([
1372
1580
  }
1373
1581
  }
1374
1582
  return n;
1375
- }, Xt = (e, t) => {
1583
+ }, _n = (e, t) => {
1376
1584
  let n = e.slice(t.length).split(x).slice(0, -1)[0];
1377
1585
  return n !== void 0 && n.startsWith("[");
1378
- }, V = (e) => {
1586
+ }, B = (e) => {
1379
1587
  let t = [], n = v(e, "src", "pages"), r = v(e, "src");
1380
1588
  if (!S(r)) return t;
1381
1589
  let i = [];
1382
- Jt(r, e, i), i.sort();
1590
+ hn(r, e, i), i.sort();
1383
1591
  let a = `${y(e, n)}${x}`;
1384
- Yt(e, i);
1592
+ gn(e, i);
1385
1593
  for (let n of i) {
1386
1594
  let r = n.slice(n.lastIndexOf(x) + 1);
1387
1595
  if (n.startsWith(a)) {
1388
1596
  if (r !== "page.tsx") continue;
1389
- S(v(e, f(n))) || t.push({
1597
+ S(v(e, m(n))) || t.push({
1390
1598
  kind: "page-without-test",
1391
1599
  file: n
1392
1600
  });
@@ -1397,7 +1605,7 @@ var le = /* @__PURE__ */ new Set([
1397
1605
  file: n
1398
1606
  });
1399
1607
  } catch {}
1400
- Xt(n, a) && t.push({
1608
+ _n(n, a) && t.push({
1401
1609
  kind: "top-level-dynamic-route",
1402
1610
  file: n
1403
1611
  });
@@ -1409,35 +1617,35 @@ var le = /* @__PURE__ */ new Set([
1409
1617
  });
1410
1618
  }
1411
1619
  return t;
1412
- }, Zt = 10, H = (e, t, n) => {
1620
+ }, vn = 10, V = (e, t, n) => {
1413
1621
  if (t.length === 0) return;
1414
1622
  console.log(`• ${e} (${t.length}):`);
1415
- for (let e of t.slice(0, Zt)) console.log(` ${e}`);
1416
- let r = t.length - Zt;
1623
+ for (let e of t.slice(0, vn)) console.log(` ${e}`);
1624
+ let r = t.length - vn;
1417
1625
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
1418
- }, Qt = (e) => {
1626
+ }, yn = (e) => {
1419
1627
  console.log("");
1420
1628
  let t = v(e, "src", "pages");
1421
1629
  if (!S(t)) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
1422
- let n = V(e);
1630
+ let n = B(e);
1423
1631
  if (n.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
1424
1632
  let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
1425
- return H("will NEVER route — page.tsx outside src/pages/", r("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."), H("pages with no test beside them", r("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."), H("routes whose FIRST segment is dynamic — they claim every URL of that depth", r("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."), H("pages with no `meta` export — they serve the APP name as their title", r("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."), n.length;
1426
- }, $t = "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.", en = /* @__PURE__ */ new Map(), U = (e) => {
1427
- let t = en.get(e);
1633
+ return V("will NEVER route — page.tsx outside src/pages/", r("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."), V("pages with no test beside them", r("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."), V("routes whose FIRST segment is dynamic — they claim every URL of that depth", r("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."), V("pages with no `meta` export — they serve the APP name as their title", r("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."), n.length;
1634
+ }, bn = "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.", xn = /* @__PURE__ */ new Map(), H = (e) => {
1635
+ let t = xn.get(e);
1428
1636
  if (t !== void 0) return t;
1429
1637
  let n = (async () => {
1430
- let { walk: t, loadDiscovered: n } = await import("./dev-DfVZaoys.js"), r = await t(e);
1638
+ let { walk: t, loadDiscovered: n } = await import("./dev-Bl9HqtV7.js"), r = await t(e);
1431
1639
  return {
1432
1640
  files: r,
1433
- discovered: await n(r)
1641
+ discovered: await n(r, { root: e })
1434
1642
  };
1435
1643
  })();
1436
- return en.set(e, n), n;
1437
- }, tn = async (e) => {
1644
+ return xn.set(e, n), n;
1645
+ }, Sn = async (e) => {
1438
1646
  let t;
1439
1647
  try {
1440
- let { files: n, discovered: r } = await U(e), i = r.tables.map((e) => {
1648
+ let { files: n, discovered: r } = await H(e), i = r.tables.map((e) => {
1441
1649
  let t = e;
1442
1650
  return {
1443
1651
  name: t.tableName,
@@ -1450,7 +1658,7 @@ var le = /* @__PURE__ */ new Set([
1450
1658
  return;
1451
1659
  }
1452
1660
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
1453
- }, nn = (e) => e.map((e) => {
1661
+ }, Cn = (e) => e.map((e) => {
1454
1662
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
1455
1663
  name: e,
1456
1664
  type: typeof t?.type == "string" ? t.type : ""
@@ -1460,13 +1668,13 @@ var le = /* @__PURE__ */ new Set([
1460
1668
  columns: n,
1461
1669
  primaryKey: t.appliedPrimaryKey ?? []
1462
1670
  };
1463
- }), rn = async (e) => {
1671
+ }), wn = async (e) => {
1464
1672
  try {
1465
- let { missingRelationSources: t } = await import("./doctorSourceRelations-c5mlN5HI.js"), { sourceKeys: n } = await import("@voltro/protocol"), { readFile: r } = await import("node:fs/promises"), { discovered: i } = await U(e), a = [], o = [];
1673
+ let { missingRelationSources: t } = await import("./doctorSourceRelations-c5mlN5HI.js"), { sourceKeys: n } = await import("@voltro/protocol"), { readFile: r } = await import("node:fs/promises"), { discovered: i } = await H(e), a = [], o = [];
1466
1674
  for (let e of i.queries) {
1467
1675
  let i = n(e.descriptor.source);
1468
1676
  if (i.length === 0) continue;
1469
- let s = an(e.file);
1677
+ let s = Tn(e.file);
1470
1678
  if (s === void 0) continue;
1471
1679
  let c = await r(s, "utf8").catch(() => null);
1472
1680
  if (c === null) continue;
@@ -1483,8 +1691,8 @@ var le = /* @__PURE__ */ new Set([
1483
1691
  } catch {
1484
1692
  return null;
1485
1693
  }
1486
- }, an = (e) => e.endsWith(".ts") && !e.endsWith(".server.ts") ? `${e.slice(0, -3)}.server.ts` : void 0, on = async (e) => {
1487
- let t = await rn(e);
1694
+ }, Tn = (e) => e.endsWith(".ts") && !e.endsWith(".server.ts") ? `${e.slice(0, -3)}.server.ts` : void 0, En = async (e) => {
1695
+ let t = await wn(e);
1488
1696
  if (t === null) return;
1489
1697
  let n = t.findings;
1490
1698
  if (n.length > 0) {
@@ -1497,21 +1705,21 @@ var le = /* @__PURE__ */ new Set([
1497
1705
  for (let e of t.unresolved) console.log(` ${e.procedure}: ${e.relations.map((e) => `\`${e}\``).join(", ")} resolved against no table`);
1498
1706
  console.log(" declare the base table in `source:` and re-run — relations under it are unchecked until then.");
1499
1707
  }
1500
- }, W = async (e) => {
1708
+ }, U = async (e) => {
1501
1709
  try {
1502
- let { discovered: t } = await U(e);
1503
- return ve(nn(t.tables));
1710
+ let { discovered: t } = await H(e);
1711
+ return ve(Cn(t.tables));
1504
1712
  } catch {
1505
1713
  return null;
1506
1714
  }
1507
- }, sn = async (e) => {
1508
- let t = await W(e);
1715
+ }, Dn = async (e) => {
1716
+ let t = await U(e);
1509
1717
  if (t === null || t.tablesChecked === 0) return;
1510
1718
  let n = be(t);
1511
1719
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
1512
- }, G = async (e) => {
1720
+ }, W = async (e) => {
1513
1721
  try {
1514
- let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await U(e);
1722
+ let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await H(e);
1515
1723
  return we(n.events.map((e) => {
1516
1724
  let n = e.descriptor, r = t(n.key);
1517
1725
  return {
@@ -1524,19 +1732,19 @@ var le = /* @__PURE__ */ new Set([
1524
1732
  } catch {
1525
1733
  return null;
1526
1734
  }
1527
- }, cn = async (e) => {
1528
- let t = await G(e);
1735
+ }, On = async (e) => {
1736
+ let t = await W(e);
1529
1737
  if (t === null || t.eventsChecked === 0) return;
1530
1738
  let n = Te(t);
1531
1739
  n !== null && (console.log(""), console.log(`• ${n}`));
1532
1740
  let r = Ee(t);
1533
1741
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
1534
- }, K = (e) => {
1742
+ }, G = (e) => {
1535
1743
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
1536
1744
  return r === void 0 ? n : `${r}: ${n}`;
1537
- }, q = async (e) => {
1745
+ }, K = async (e) => {
1538
1746
  try {
1539
- let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await U(e)).discovered.serverOnlyLeaks, r = t(n);
1747
+ let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await H(e)).discovered.serverOnlyLeaks, r = t(n);
1540
1748
  return {
1541
1749
  status: {
1542
1750
  checked: !0,
@@ -1547,12 +1755,12 @@ var le = /* @__PURE__ */ new Set([
1547
1755
  } catch (e) {
1548
1756
  return { status: {
1549
1757
  checked: !1,
1550
- reason: K(e)
1758
+ reason: G(e)
1551
1759
  } };
1552
1760
  }
1553
- }, J = async (r) => {
1761
+ }, q = async (r) => {
1554
1762
  try {
1555
- let { loadApiConfig: i } = await import("./dev-DfVZaoys.js"), { discovered: a } = await U(r), o = t(await i(r)), s = {
1763
+ let { loadApiConfig: i } = await import("./dev-Bl9HqtV7.js"), { discovered: a } = await H(r), o = n(await i(r)), s = {
1556
1764
  queries: a.queries,
1557
1765
  mutations: a.mutations,
1558
1766
  actions: a.actions,
@@ -1562,32 +1770,32 @@ var le = /* @__PURE__ */ new Set([
1562
1770
  return {
1563
1771
  checked: !0,
1564
1772
  defaultDeny: o,
1565
- undecided: n(s),
1773
+ undecided: t(s),
1566
1774
  openAccess: e(s)
1567
1775
  };
1568
1776
  } catch (e) {
1569
1777
  return {
1570
1778
  checked: !1,
1571
- reason: K(e),
1779
+ reason: G(e),
1572
1780
  defaultDeny: !0,
1573
1781
  undecided: [],
1574
1782
  openAccess: []
1575
1783
  };
1576
1784
  }
1577
- }, ln = async (e) => {
1578
- let t = await J(e);
1785
+ }, kn = async (e) => {
1786
+ let t = await q(e);
1579
1787
  if (!t.checked) return console.log(""), console.log("• access decisions: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
1580
1788
  console.log(""), console.log(`access decisions · security.defaultDeny ${t.defaultDeny ? "ON" : "OFF"}`), console.log(` ✗ no access decision ${String(t.undecided.length).padStart(4)}`), console.log(` ✓ openAccess, declared on purpose ${String(t.openAccess.length).padStart(4)}`);
1581
1789
  for (let e of t.openAccess) console.log(` ${e.tag} — ${e.openAccess ?? ""}`);
1582
1790
  if (t.undecided.length === 0) return !1;
1583
1791
  console.log("");
1584
1792
  for (let e of t.undecided) console.log(` ✗ ${e.tag} (${e.kind})${e.file === void 0 ? "" : `\n ${e.file}`}`);
1585
- return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), un(), !0) : (console.log(" This app sets `security: { defaultDeny: false }`, so these BOOT — and each one is"), console.log(" callable by any authenticated session. Advisory here for that reason only."), un(), !1);
1586
- }, un = () => {
1793
+ return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), An(), !0) : (console.log(" This app sets `security: { defaultDeny: false }`, so these BOOT — and each one is"), console.log(" callable by any authenticated session. Advisory here for that reason only."), An(), !1);
1794
+ }, An = () => {
1587
1795
  console.log(" `guards: [{ scope: '…' }]` authority the SUBJECT holds globally"), console.log(" `guards: [{ action, resourceType, resource }]` authority per ROW / per resource"), console.log(" — backed by `defineResourcePolicy` + a tuple source you register. If your"), console.log(" relationships already live in your own tables (a `teamMembers` row), register"), console.log(" a tuple source over them; nothing is copied. Fail-closed without a resolver."), console.log(" Docs: Authentication → Authorization."), console.log(" `openAccess: '<why anyone may call it>'` deliberately unauthenticated");
1588
- }, Y = async (e) => {
1796
+ }, J = async (e) => {
1589
1797
  try {
1590
- let { files: t } = await U(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
1798
+ let { files: t } = await H(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
1591
1799
  if (a.length === 0) return {
1592
1800
  checked: !0,
1593
1801
  unverified: [],
@@ -1614,14 +1822,14 @@ var le = /* @__PURE__ */ new Set([
1614
1822
  } catch (e) {
1615
1823
  return {
1616
1824
  checked: !1,
1617
- reason: K(e),
1825
+ reason: G(e),
1618
1826
  unverified: [],
1619
1827
  open: [],
1620
1828
  verified: 0
1621
1829
  };
1622
1830
  }
1623
- }, dn = async (e) => {
1624
- let t = await Y(e);
1831
+ }, jn = async (e) => {
1832
+ let t = await J(e);
1625
1833
  if (!t.checked) return console.log(""), console.log("• webhook verification: NOT CHECKED — the app's webhooks could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
1626
1834
  if (t.verified + t.open.length + t.unverified.length === 0) return !1;
1627
1835
  console.log(""), console.log("incoming webhook verification"), console.log(` ✓ signature / provider verified ${String(t.verified).padStart(4)}`), console.log(` ! deliberately public (none) ${String(t.open.length).padStart(4)}`), console.log(` ✗ nothing verifies the caller ${String(t.unverified.length).padStart(4)}`);
@@ -1630,13 +1838,13 @@ var le = /* @__PURE__ */ new Set([
1630
1838
  console.log("");
1631
1839
  for (let e of t.unverified) console.log(` ✗ ${e.id}\n ${e.file}`);
1632
1840
  return console.log(""), console.log(" These are public POSTs that run your application code, and `voltro dev` /"), console.log(" `voltro serve` REFUSE to mount them. Declare one of: `provider:` (a preset),"), console.log(" `signature:` (your own HMAC scheme), `verification: 'provider'` (your handler"), console.log(" verifies), or `verification: 'none'` (deliberately public)."), !0;
1633
- }, fn = async (e) => {
1634
- let { status: t, message: n } = await q(e);
1841
+ }, Mn = async (e) => {
1842
+ let { status: t, message: n } = await K(e);
1635
1843
  return t.checked ? n !== void 0 && (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);
1636
- }, pn = async (e) => ut({
1637
- hasWorkflows: async () => (await U(e)).discovered.workflows.length > 0,
1844
+ }, Nn = async (e) => ft({
1845
+ hasWorkflows: async () => (await H(e)).discovered.workflows.length > 0,
1638
1846
  openStore: async () => {
1639
- let { loadApiConfig: t, buildStore: n } = await import("./dev-DfVZaoys.js"), r = await t(e);
1847
+ let { loadApiConfig: t, buildStore: n } = await import("./dev-Bl9HqtV7.js"), r = await t(e);
1640
1848
  if (r === null) throw Error("no app.config.ts in this directory");
1641
1849
  let i = await n(r, void 0);
1642
1850
  return {
@@ -1650,13 +1858,13 @@ var le = /* @__PURE__ */ new Set([
1650
1858
  let { sweepStalledRuns: t } = await import("@voltro/workflow");
1651
1859
  return t({ store: e });
1652
1860
  }
1653
- }), mn = async (e) => {
1654
- let t = ft(await pn(e));
1861
+ }), Pn = async (e) => {
1862
+ let t = mt(await Nn(e));
1655
1863
  t !== null && (console.log(""), console.log(t));
1656
- }, hn = async (e) => {
1864
+ }, Fn = async (e) => {
1657
1865
  let t;
1658
1866
  try {
1659
- let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await U(e);
1867
+ let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await H(e);
1660
1868
  t = ke(r, i.workflows.map((e) => {
1661
1869
  let t = e.definition, r = t.payloadSchema === void 0 ? {
1662
1870
  required: [],
@@ -1676,18 +1884,18 @@ var le = /* @__PURE__ */ new Set([
1676
1884
  console.log("");
1677
1885
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
1678
1886
  console.log(`${r} ${n}`);
1679
- }, X = "voltro-authz-allowlist.txt", gn = (e, t = (e) => {
1887
+ }, Y = "voltro-authz-allowlist.txt", In = (e, t = (e) => {
1680
1888
  try {
1681
1889
  return C(e, "utf8");
1682
1890
  } catch {
1683
1891
  return "";
1684
1892
  }
1685
- }) => /* @__PURE__ */ new Set([...Je, ...Ye(R(e).map(t))]), _n = (e, t) => {
1893
+ }) => /* @__PURE__ */ new Set([...Xe, ...Ze(L(e).map(t))]), X = (e, t) => {
1686
1894
  let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1687
1895
  return new RegExp(String.raw`(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=\s*define[A-Za-z]*Event\s*\(\s*\{[^}]*?name\s*:\s*['"\`]${n}['"\`]`, "s").exec(e)?.[1];
1688
1896
  }, Z = async (e) => {
1689
1897
  try {
1690
- let { discovered: t } = await U(e), n = t.events.map((e) => ({
1898
+ let { discovered: t } = await H(e), n = t.events.map((e) => ({
1691
1899
  name: e.descriptor.name,
1692
1900
  file: e.file
1693
1901
  })).filter((e) => typeof e.name == "string");
@@ -1698,25 +1906,25 @@ var le = /* @__PURE__ */ new Set([
1698
1906
  } catch {
1699
1907
  return "";
1700
1908
  }
1701
- }, a = R(e).filter((e) => !r.has(e)).map(i).join("\n");
1909
+ }, a = L(e).filter((e) => !r.has(e)).map(i).join("\n");
1702
1910
  return n.filter(({ name: e, file: t }) => {
1703
1911
  if (a.includes(e)) return !1;
1704
- let n = _n(i(t), e);
1912
+ let n = X(i(t), e);
1705
1913
  return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
1706
1914
  }).map((e) => e.name).sort();
1707
1915
  } catch {
1708
1916
  return [];
1709
1917
  }
1710
- }, vn = async (e) => {
1918
+ }, Ln = async (e) => {
1711
1919
  let t = await Z(e);
1712
1920
  if (t.length !== 0) {
1713
1921
  console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
1714
1922
  for (let e of t) console.log(` ${e}`);
1715
1923
  console.log(""), console.log(" An event you declare is an event partners can subscribe to. One that"), console.log(" never fires is indistinguishable, from their side, from a broken"), console.log(" endpoint of their own — so they debug your contract instead of"), console.log(" reading it."), console.log(" ADVISORY: this looks for the event name AND the identifier its"), console.log(" declaration binds it to, so `emit(ctx, apiKeyCreated, …)` counts. An"), console.log(" emit through an ASSEMBLED name still reads as missing here.");
1716
1924
  }
1717
- }, yn = async (e) => {
1925
+ }, Rn = async (e) => {
1718
1926
  try {
1719
- let { loadApiConfig: t } = await import("./dev-DfVZaoys.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1927
+ let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1720
1928
  if (typeof n != "string") return [];
1721
1929
  let [r, i] = n.split("#");
1722
1930
  if (r === void 0 || i === void 0) return [];
@@ -1733,10 +1941,10 @@ var le = /* @__PURE__ */ new Set([
1733
1941
  findings: []
1734
1942
  });
1735
1943
  try {
1736
- let { offlineManifest: n, toInput: r } = await import("./checkCommand-DNkY5kwF.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CFmFKHb2.js"), o = await n(e), s = r(o.manifest, o.tables), c = await yn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1944
+ let { offlineManifest: n, toInput: r } = await import("./checkCommand-BISqx1OJ.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CFmFKHb2.js"), o = await n(e), s = r(o.manifest, o.tables), c = await Rn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1737
1945
  if (l.length === 0) {
1738
1946
  try {
1739
- let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-Df2Ymp2f.js");
1947
+ let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-B96WCNJA.js");
1740
1948
  await t(e);
1741
1949
  } catch {
1742
1950
  return t("app-not-importable");
@@ -1760,7 +1968,7 @@ var le = /* @__PURE__ */ new Set([
1760
1968
  } catch {
1761
1969
  return t("app-not-importable");
1762
1970
  }
1763
- }, bn = async (e) => {
1971
+ }, zn = async (e) => {
1764
1972
  let { findings: t, evaluated: n, notEvaluated: r } = await Q(e);
1765
1973
  if (!n) {
1766
1974
  if (console.log(""), r === "app-not-importable") {
@@ -1775,7 +1983,43 @@ var le = /* @__PURE__ */ new Set([
1775
1983
  for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
1776
1984
  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}`);
1777
1985
  }
1778
- }, xn = async (e) => {
1986
+ }, Bn = (e) => {
1987
+ let t = v(e, "src");
1988
+ if (!S(t)) return;
1989
+ let n = [], r = (t, i = 0) => {
1990
+ if (i > 12 || I.test(t)) return;
1991
+ let a;
1992
+ try {
1993
+ a = w(t);
1994
+ } catch {
1995
+ return;
1996
+ }
1997
+ for (let o of a) {
1998
+ let a = v(t, o), s;
1999
+ try {
2000
+ s = T(a);
2001
+ } catch {
2002
+ continue;
2003
+ }
2004
+ if (s.isDirectory()) {
2005
+ r(a, i + 1);
2006
+ continue;
2007
+ }
2008
+ if (!(!/\.(tsx?|css)$/.test(o) || /\.(test|spec)\./.test(o))) try {
2009
+ n.push({
2010
+ path: y(e, a).split("\\").join("/"),
2011
+ source: C(a, "utf8")
2012
+ });
2013
+ } catch {}
2014
+ }
2015
+ };
2016
+ r(t);
2017
+ let i = yt(_t(n));
2018
+ if (i.length !== 0) {
2019
+ console.log("");
2020
+ for (let e of i) console.log(e);
2021
+ }
2022
+ }, Vn = async (e) => {
1779
2023
  let t = ["src/locales", "locales"], n, r = [];
1780
2024
  for (let i of t) {
1781
2025
  let t = v(e, i);
@@ -1787,20 +2031,20 @@ var le = /* @__PURE__ */ new Set([
1787
2031
  if (n === void 0 || r.length === 0) return;
1788
2032
  let i = [];
1789
2033
  try {
1790
- let { loadApiConfig: t } = await import("./dev-DfVZaoys.js");
2034
+ let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js");
1791
2035
  i = (await t(e))?.locales ?? [];
1792
2036
  } catch {
1793
2037
  return;
1794
2038
  }
1795
- let a = He({
2039
+ let a = We({
1796
2040
  catalogCodes: r,
1797
2041
  declaredLocales: i
1798
2042
  });
1799
2043
  if (a !== void 0) {
1800
2044
  console.log("");
1801
- for (let e of Ue(a, n)) console.log(e);
2045
+ for (let e of Ge(a, n)) console.log(e);
1802
2046
  }
1803
- }, Sn = async (e) => {
2047
+ }, Hn = async (e) => {
1804
2048
  let t = v(e, "src");
1805
2049
  if (!S(t)) return;
1806
2050
  let n = !1, r = (e) => {
@@ -1819,9 +2063,9 @@ var le = /* @__PURE__ */ new Set([
1819
2063
  return;
1820
2064
  }
1821
2065
  a()?.reason === "binary-missing" ? (console.log("image pipeline: sharp is INSTALLED but its platform binary is missing —"), console.log(" an install that omitted optional dependencies drops the @img/* prebuilds."), console.log(" Fix: reinstall without omitting optional deps, or `pnpm add -D sharp` in this app.")) : (console.log("image pipeline: sharp is NOT installed — ?image imports serve originals (no srcSet, no blur)."), console.log(" Fix: `pnpm add -D sharp` in this app (it is an optional dependency of @voltro/cli;"), console.log(" your installer skipped optional dependencies)."));
1822
- }, Cn = async (e) => {
2066
+ }, Un = async (e) => {
1823
2067
  try {
1824
- let { loadConfig: t } = await import("./webDev-1XpVnYkW.js"), n = (await t(e, { preferSource: !0 }))?.fonts;
2068
+ let { loadConfig: t } = await import("./webDev-B6ZMX42w.js"), n = (await t(e, { preferSource: !0 }))?.fonts;
1825
2069
  if (n === void 0 || n.length === 0) return;
1826
2070
  let { loadFontkit: r, loadSubsetFont: i } = await import("./fontPipeline-LxIHa1vo.js"), a = [];
1827
2071
  if (await r() === null && a.push("fontkit (size-adjust fallback metrics — the CLS guard — are skipped)"), await i() === null && a.push("subset-font (declared `subsets:` ship the full files)"), console.log(""), a.length === 0) {
@@ -1831,25 +2075,25 @@ var le = /* @__PURE__ */ new Set([
1831
2075
  for (let e of a) console.log(`font pipeline: ${e}`);
1832
2076
  console.log(" Fix: reinstall without omitting optional deps, or `pnpm add -D fontkit subset-font` in this app.");
1833
2077
  } catch {}
1834
- }, wn = async (e) => {
2078
+ }, Wn = async (e) => {
1835
2079
  let t = v(e, "src", "pages");
1836
2080
  if (!S(t)) return !1;
1837
2081
  let n = [];
1838
2082
  try {
1839
- let { walkPagesTree: e } = await import("./webDev-1XpVnYkW.js");
2083
+ let { walkPagesTree: e } = await import("./webDev-B6ZMX42w.js");
1840
2084
  n = (await e(t)).pages.map((e) => e.pattern);
1841
2085
  } catch {
1842
2086
  return !1;
1843
2087
  }
1844
- let r = Ge(e, n);
2088
+ let r = qe(e, n);
1845
2089
  if (r === void 0) return !1;
1846
2090
  console.log("");
1847
- for (let e of Ke(r)) console.log(e);
2091
+ for (let e of Je(r)) console.log(e);
1848
2092
  return r.problems.length > 0;
1849
- }, Tn = async (e) => {
1850
- let { discovered: t } = await U(e), n = [];
2093
+ }, Gn = async (e) => {
2094
+ let { discovered: t } = await H(e), n = [];
1851
2095
  try {
1852
- let { loadApiConfig: t } = await import("./dev-DfVZaoys.js");
2096
+ let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js");
1853
2097
  n = ((await t(e))?.plugins ?? []).map((e) => {
1854
2098
  let t = (e.extendSchema?.tables ?? []).map((e) => {
1855
2099
  let t = e;
@@ -1857,13 +2101,13 @@ var le = /* @__PURE__ */ new Set([
1857
2101
  tableName: typeof t.tableName == "string" ? t.tableName : "",
1858
2102
  renamedFrom: typeof t.renamedFrom == "string" ? t.renamedFrom : void 0
1859
2103
  };
1860
- }).filter((e) => e.tableName !== ""), n = (e.rpcClientDescriptors ?? []).map((t) => typeof t.tag == "string" ? d(e, t.tag) : "").filter((e) => e !== ""), r = e.baseName ?? e.name;
2104
+ }).filter((e) => e.tableName !== ""), n = (e.rpcClientDescriptors ?? []).map((t) => typeof t.tag == "string" ? p(e, t.tag) : "").filter((e) => e !== ""), r = e.baseName ?? e.name;
1861
2105
  return {
1862
2106
  plugin: e.name,
1863
2107
  tables: t,
1864
2108
  rpcTags: n,
1865
- canDisableTables: En.has(r),
1866
- canAlias: Dn.has(r)
2109
+ canDisableTables: Kn.has(r),
2110
+ canAlias: qn.has(r)
1867
2111
  };
1868
2112
  });
1869
2113
  } catch {
@@ -1875,18 +2119,18 @@ var le = /* @__PURE__ */ new Set([
1875
2119
  ...t.mutations,
1876
2120
  ...t.actions,
1877
2121
  ...t.streams ?? []
1878
- ].map((e) => String(e.descriptor.name)), i = ct(n, {
2122
+ ].map((e) => String(e.descriptor.name)), i = ut(n, {
1879
2123
  tables: t.tables.map((e) => e.tableName),
1880
2124
  rpcTags: r
1881
2125
  });
1882
2126
  console.log();
1883
- for (let e of lt(n, i)) console.log(e);
1884
- }, En = /* @__PURE__ */ new Set([
2127
+ for (let e of dt(n, i)) console.log(e);
2128
+ }, Kn = /* @__PURE__ */ new Set([
1885
2129
  "@voltro/plugin-rbac",
1886
2130
  "@voltro/plugin-audit",
1887
2131
  "@voltro/plugin-notifications",
1888
2132
  "aiFlows"
1889
- ]), Dn = /* @__PURE__ */ new Set([
2133
+ ]), qn = /* @__PURE__ */ new Set([
1890
2134
  "aiFlows",
1891
2135
  "@voltro/plugin-audit",
1892
2136
  "@voltro/plugin-billing",
@@ -1904,8 +2148,8 @@ var le = /* @__PURE__ */ new Set([
1904
2148
  "@voltro/plugin-sso-saml",
1905
2149
  "@voltro/plugin-storage",
1906
2150
  "@voltro/plugin-row-history"
1907
- ]), $ = 20, On = async (e) => {
1908
- let t = await kn(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;
2151
+ ]), $ = 20, Jn = async (e) => {
2152
+ let t = await Yn(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;
1909
2153
  return {
1910
2154
  executors: t.executorCount,
1911
2155
  guardVocabulary: [...t.vocabulary].sort(),
@@ -1917,15 +2161,15 @@ var le = /* @__PURE__ */ new Set([
1917
2161
  },
1918
2162
  unchecked: t.result.unchecked
1919
2163
  };
1920
- }, kn = async (e) => {
1921
- let { discovered: t } = await U(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2164
+ }, Yn = async (e) => {
2165
+ let { discovered: t } = await H(e), n = /* @__PURE__ */ new Map(), r = (e) => {
1922
2166
  if (!n.has(e)) try {
1923
2167
  n.set(e, C(e, "utf8"));
1924
2168
  } catch {
1925
2169
  n.set(e, "");
1926
2170
  }
1927
2171
  return n.get(e);
1928
- }, i = gn(e, r), a = /* @__PURE__ */ new Set();
2172
+ }, i = In(e, r), a = /* @__PURE__ */ new Set();
1929
2173
  for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
1930
2174
  let e = t.references?.();
1931
2175
  e && a.add(e.tableName);
@@ -1957,12 +2201,12 @@ var le = /* @__PURE__ */ new Set([
1957
2201
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
1958
2202
  ...a?.table === void 0 ? {} : { table: a.table }
1959
2203
  };
1960
- })), s = v(e, X), c = S(s) ? N(C(s, "utf8")) : void 0, l = !1;
1961
- for (let t of R(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
2204
+ })), s = v(e, Y), c = S(s) ? j(C(s, "utf8")) : void 0, l = !1;
2205
+ for (let t of L(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
1962
2206
  l = !0;
1963
2207
  break;
1964
2208
  }
1965
- let u = tt({
2209
+ let u = rt({
1966
2210
  executors: o,
1967
2211
  guardVocabulary: i,
1968
2212
  tenantScopedTables: t.tenantScopedTables,
@@ -1979,23 +2223,23 @@ var le = /* @__PURE__ */ new Set([
1979
2223
  executorCount: o.length,
1980
2224
  allowPath: s
1981
2225
  };
1982
- }, An = async (e, t) => {
1983
- let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await kn(e), l = n.counts;
2226
+ }, Xn = async (e, t) => {
2227
+ let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Yn(e), l = n.counts;
1984
2228
  console.log(`\nauthz scan · ${o} executor(s)`);
1985
- let u = [...r].filter((e) => !Je.has(e)).sort();
2229
+ let u = [...r].filter((e) => !Xe.has(e)).sort();
1986
2230
  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)}`);
1987
2231
  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;
1988
- if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${X})`), 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) {
1989
- let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = at(e, t);
1990
- return oe(s, it(e, t), "utf8"), console.log(`\n${X}: +${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;
2232
+ if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Y})`), 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) {
2233
+ let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = st(e, t);
2234
+ return oe(s, ot(e, t), "utf8"), console.log(`\n${Y}: +${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;
1991
2235
  }
1992
2236
  if (n.unchecked.length === 0) return !1;
1993
2237
  console.log("");
1994
2238
  for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
1995
- 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 ${X} 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 ${X}.`), 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);
1996
- }, jn = async (e) => {
2239
+ 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 ${Y} 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 ${Y}.`), 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);
2240
+ }, Zn = async (e) => {
1997
2241
  try {
1998
- let { loadApiConfig: t } = await import("./dev-DfVZaoys.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
2242
+ let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
1999
2243
  return r == null ? null : n({
2000
2244
  ...r.schema ? { declared: r.schema } : {},
2001
2245
  ...r.store === void 0 ? {} : { store: r.store },
@@ -2004,42 +2248,42 @@ var le = /* @__PURE__ */ new Set([
2004
2248
  } catch {
2005
2249
  return null;
2006
2250
  }
2007
- }, Mn = async (e) => {
2008
- let t = await jn(e);
2251
+ }, Qn = async (e) => {
2252
+ let t = await Zn(e);
2009
2253
  if (t !== null) {
2010
2254
  console.log(""), console.log("• framework tables whose presence is DECIDED rather than implied by your source tree:");
2011
2255
  for (let e of t.text.split("\n")) console.log(` ${e}`);
2012
2256
  }
2013
- }, Nn = async (e) => {
2014
- if (e.includes("--help") || e.includes("-h")) return console.log($t), 0;
2015
- let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), o = b(n ?? process.cwd());
2257
+ }, $n = async (e) => {
2258
+ if (e.includes("--help") || e.includes("-h")) return console.log(bn), 0;
2259
+ let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), a = b(n ?? process.cwd());
2016
2260
  if (g(e)) return ee(), h({
2017
- ...z(o),
2018
- spaCandidates: await B(o),
2019
- pageConvention: V(o),
2020
- fileTaxonomy: re(o).findings,
2021
- junctionFks: (await W(o))?.issues ?? [],
2022
- eventDelivery: await G(o),
2023
- duplicateInstances: s(o),
2024
- versionSkew: r(o) ?? null,
2025
- serverOnly: (await q(o)).status,
2026
- accessDecisions: await J(o),
2027
- webhookVerification: await Y(o),
2028
- authz: await On(o),
2029
- ungrantedScopes: await Q(o),
2030
- neverEmittedEvents: await Z(o),
2031
- unimportedDeps: Be(o),
2032
- stalledRuns: await pn(o),
2033
- decidedSchema: await jn(o)
2261
+ ...R(a),
2262
+ spaCandidates: await z(a),
2263
+ pageConvention: B(a),
2264
+ fileTaxonomy: re(a).findings,
2265
+ junctionFks: (await U(a))?.issues ?? [],
2266
+ eventDelivery: await W(a),
2267
+ duplicateInstances: i(a),
2268
+ versionSkew: o(a) ?? null,
2269
+ serverOnly: (await K(a)).status,
2270
+ accessDecisions: await q(a),
2271
+ webhookVerification: await J(a),
2272
+ authz: await Jn(a),
2273
+ ungrantedScopes: await Q(a),
2274
+ neverEmittedEvents: await Z(a),
2275
+ unimportedDeps: He(a),
2276
+ stalledRuns: await Nn(a),
2277
+ decidedSchema: await Zn(a)
2034
2278
  }), 0;
2035
- let c = gt(o, t);
2036
- Bt(o), zt(o), Qt(o), ne(o), await Kt(o), await hn(o), await mn(o), await tn(o), await sn(o), await on(o), await Mn(o), await cn(o), await Tn(o), await bn(o), await vn(o), console.log("");
2037
- for (let e of Ve(Be(o))) console.log(e);
2038
- await xn(o), await Sn(o), await Cn(o);
2039
- let l = await fn(o), u = await An(o, e.includes("--write-authz-allowlist")), d = await ln(o), f = await dn(o), p = await wn(o), m = a(r(o));
2279
+ let s = Ct(a, t);
2280
+ sn(a), on(a), yn(a), ne(a), await pn(a), await Fn(a), await Pn(a), await Sn(a), await Dn(a), await En(a), await Qn(a), await On(a), await Gn(a), await zn(a), await Ln(a), console.log("");
2281
+ for (let e of Ue(He(a))) console.log(e);
2282
+ await Vn(a), Bn(a), await Hn(a), await Un(a);
2283
+ let c = await Mn(a), l = await Xn(a, e.includes("--write-authz-allowlist")), d = await kn(a), f = await jn(a), p = await Wn(a), m = r(o(a));
2040
2284
  m !== void 0 && (console.log(""), console.log(m));
2041
- let _ = i(s(o));
2042
- return _ !== void 0 && console.log(`\n${_}`), l || u || d || p || f ? Math.max(c, 1) : c;
2285
+ let _ = u(i(a));
2286
+ return _ !== void 0 && console.log(`\n${_}`), c || l || d || p || f ? Math.max(s, 1) : s;
2043
2287
  };
2044
2288
  //#endregion
2045
- export { B as C, Y as E, V as S, Q as T, Qt as _, On as a, gt as b, G as c, Nt as d, mt as f, Bt as g, zt as h, J as i, gn as l, rn as m, ht as n, Z as o, W as p, Vt as r, _n as s, $t as t, z as u, Kt as v, q as w, Pt as x, Nn as y };
2289
+ export { z as C, J as D, Q as E, B as S, Rt as T, yn as _, Jn as a, Ct as b, W as c, Qt as d, xt as f, sn as g, on as h, q as i, In as l, wn as m, St as n, Z as o, U as p, cn as r, X as s, bn as t, R as u, pn as v, K as w, $t as x, $n as y };