@voltro/cli 0.54.0 → 0.56.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 (173) hide show
  1. package/CHANGELOG.md +639 -2
  2. package/bin/voltro.mjs +24 -0
  3. package/dist/{apiBuild-DTWp0S_q.js → apiBuild-Bdaetr37.js} +119 -88
  4. package/dist/apiBuild-Vw1figjO.js +2 -0
  5. package/dist/bin.js +1 -1
  6. package/dist/{build-D4ygSbnV.js → build-CI36wL4R.js} +339 -314
  7. package/dist/buildReport-52gHKgfO.js +64 -0
  8. package/dist/{checkCommand-Dg1G7Gwd.js → checkCommand-CAwFXrxA.js} +6 -6
  9. package/dist/{checkCommand-L7DTlpIF.js → checkCommand-D0QV_zM_.js} +1 -1
  10. package/dist/{clusterCmd-DrVFCzSj.js → clusterCmd-CdLB1GkT.js} +1 -1
  11. package/dist/{codegen-DSLM8Su9.js → codegen-Bth5lUTU.js} +76 -64
  12. package/dist/codegen-DbH7NbCR.js +2 -0
  13. package/dist/{codegenCommand-CG_Vx4lc.js → codegenCommand-CidbQzbv.js} +15 -14
  14. package/dist/{codemodRunner-Cd4xkC6u.js → codemodRunner-BlQPfjzA.js} +277 -0
  15. package/dist/{commands-6Kzi92Np.js → commands-CWjfThXv.js} +35 -35
  16. package/dist/{dashboardCommand-Cq1PWvI1.js → dashboardCommand-BekcY5Ls.js} +3 -3
  17. package/dist/{dataCommand-DYzW8vkv.js → dataCommand-2pccgbIy.js} +267 -195
  18. package/dist/{dbCommand-B4NWZtGL.js → dbCommand-DpK_vQET.js} +457 -441
  19. package/dist/dbCommand-DrycGWWt.js +2 -0
  20. package/dist/{dev-cKUiZZsB.js → dev-B9Gz0k85.js} +1 -1
  21. package/dist/{dev-CmuvUKRq.js → dev-Dw263KPu.js} +2598 -2452
  22. package/dist/doctorCommand-BMWs6aVm.js +2 -0
  23. package/dist/{doctorCommand-DCiFVMtZ.js → doctorCommand-aR_bFmIi.js} +353 -251
  24. package/dist/{dormancyCommand-w1TrmgYP.js → dormancyCommand-eXTQMbHU.js} +1 -1
  25. package/dist/{embeddingsCommand-CMgPyRTr.js → embeddingsCommand-CTmiQvwa.js} +1 -1
  26. package/dist/{envCommand-Cyynmcfa.js → envCommand-BDUgV7EM.js} +12 -12
  27. package/dist/{evolveCommand-BwvQ8dVH.js → evolveCommand-YV8qW1LU.js} +2 -2
  28. package/dist/frameworkTableAssembly-CGNC0qr7.js +2 -0
  29. package/dist/{frameworkTableAssembly-D7LJuALW.js → frameworkTableAssembly-DNOFXfEQ.js} +102 -100
  30. package/dist/index.js +1 -1
  31. package/dist/{infoCommand-DlYlUPqs.js → infoCommand-BjVXpMlP.js} +1 -1
  32. package/dist/inspect-CNYvNXPU.js +1484 -0
  33. package/dist/inspect-S2rWy1Ys.js +2 -0
  34. package/dist/{inspectCmd-niF97fAq.js → inspectCmd-CP-G0sVK.js} +1 -1
  35. package/dist/{inspectFetch-EMuhTG_9.js → inspectFetch-BU1NyzxV.js} +36 -24
  36. package/dist/{inspectMetrics-CGF94puw.js → inspectMetrics-BY0Sjb2F.js} +19 -19
  37. package/dist/interruptedReplace-CwnkBb2X.js +41 -0
  38. package/dist/interruptedReplace-qzmFI020.js +2 -0
  39. package/dist/{logsCmd-B6oNsfaZ.js → logsCmd-BU8uCdys.js} +1 -1
  40. package/dist/{manifestBuild-Cqgsx2bM.js → manifestBuild-CEkjfpwc.js} +1 -1
  41. package/dist/manifestBuild-DIa_s6u0.js +2 -0
  42. package/dist/{migrate-BK_Bbx-_.js → migrate-SICulyz1.js} +2 -2
  43. package/dist/precompressAssets-YhTi1aWp.js +40 -0
  44. package/dist/{probeCommand-_C0YU207.js → probeCommand-6HxEkNDG.js} +2 -2
  45. package/dist/{runtimeTrace-CGWx1Q6l.js → runtimeTrace-DgYMc09E.js} +1 -1
  46. package/dist/{scheduleCmd-DQRu6BZC.js → scheduleCmd-DYBUfo_T.js} +1 -1
  47. package/dist/{sdkgen-CDGHQUFj.js → sdkgen-PY-umd6O.js} +1 -1
  48. package/dist/{seedRunner-Dgsiwk_e.js → seedRunner-DISBKow-.js} +16 -16
  49. package/dist/serveCommand-BITS8Hpj.js +2 -0
  50. package/dist/{serveCommand-Bje09q1v.js → serveCommand-DIJ3ma76.js} +951 -909
  51. package/dist/serveEntry.js +1 -1
  52. package/dist/{start-Clz-1BHB.js → start-B9NGB8gn.js} +627 -588
  53. package/dist/{start-B0bnJgxI.js → start-BFQQkL1i.js} +1 -1
  54. package/dist/startEntry.js +1 -1
  55. package/dist/staticCachePolicy-CIyj6DbS.js +15 -0
  56. package/dist/{test-D_kW4KMj.js → test-jipIQ5Mx.js} +1 -1
  57. package/dist/{tracesCmd-DgtgOUdi.js → tracesCmd-BWYDqMy6.js} +1 -1
  58. package/dist/{updateCommand-CRJlAOaM.js → updateCommand-C9n_Z_oG.js} +8 -2
  59. package/dist/updateCommand-DsXEAHbd.js +2 -0
  60. package/dist/webDev-C2dRz9s5.js +2 -0
  61. package/dist/{webDev-DSI9SOhs.js → webDev-C53hJdcL.js} +1265 -1288
  62. package/dist/{webhooksCommand-BvzXNHji.js → webhooksCommand-uuPu8qQX.js} +1 -1
  63. package/dist/{workflowsCmd-BGF-mRZ5.js → workflowsCmd-g-DNpaUc.js} +1 -1
  64. package/package.json +67 -19
  65. package/templates/AGENTS.core.md +20 -1
  66. package/templates/AGENTS.md +21 -2
  67. package/templates/agent-docs/_index.md +1 -1
  68. package/templates/agent-docs/_manifest.json +1 -1
  69. package/templates/agent-docs/authentication.md +49 -6
  70. package/templates/agent-docs/cli.md +216 -11
  71. package/templates/agent-docs/data.md +209 -18
  72. package/templates/agent-docs/database/scaling.md +40 -1
  73. package/templates/agent-docs/deployment.md +53 -1
  74. package/templates/agent-docs/internationalization.md +32 -0
  75. package/templates/agent-docs/local-first-mobile.md +9 -2
  76. package/templates/agent-docs/observability.md +227 -0
  77. package/templates/agent-docs/plugins/billing.md +15 -0
  78. package/templates/agent-docs/plugins/broadcast.md +2 -1
  79. package/templates/agent-docs/plugins/ratelimit.md +6 -1
  80. package/templates/agent-docs/plugins/row-history.md +11 -0
  81. package/templates/agent-docs/plugins.md +65 -8
  82. package/templates/agent-docs/reference.md +5 -3
  83. package/templates/agent-docs/routing.md +18 -0
  84. package/templates/agent-docs/schema-driven-ui.md +125 -0
  85. package/templates/agent-docs/templates/appshells.md +3 -3
  86. package/templates/agent-docs/whats-new.md +408 -106
  87. package/templates/apps/api-ai/package.json +6 -6
  88. package/templates/apps/api-auth/package.json +8 -8
  89. package/templates/apps/api-backend/package.json +7 -7
  90. package/templates/apps/api-backend-deactivation/package.json +7 -7
  91. package/templates/apps/api-backend-mail/package.json +8 -8
  92. package/templates/apps/api-backend-mariadb/package.json +9 -9
  93. package/templates/apps/api-backend-sqlite/package.json +8 -8
  94. package/templates/apps/api-backend-storage/package.json +8 -8
  95. package/templates/apps/api-cms/package.json +9 -9
  96. package/templates/apps/api-collab/package.json +8 -8
  97. package/templates/apps/api-data-advanced/package.json +8 -8
  98. package/templates/apps/api-durable/package.json +8 -8
  99. package/templates/apps/api-feature-flags/package.json +9 -9
  100. package/templates/apps/api-governance/package.json +8 -8
  101. package/templates/apps/api-kv/package.json +8 -8
  102. package/templates/apps/api-moderation/package.json +8 -8
  103. package/templates/apps/api-observability/package.json +8 -8
  104. package/templates/apps/api-ratelimit/package.json +8 -8
  105. package/templates/apps/api-rbac/package.json +8 -8
  106. package/templates/apps/api-rest/package.json +7 -7
  107. package/templates/apps/api-row-history/package.json +8 -8
  108. package/templates/apps/api-saas/package.json +11 -11
  109. package/templates/apps/api-saas-starter/package.json +10 -10
  110. package/templates/apps/api-search/package.json +8 -8
  111. package/templates/apps/api-status/package.json +8 -8
  112. package/templates/apps/api-webhooks/package.json +9 -9
  113. package/templates/apps/changelog/package.json +7 -7
  114. package/templates/apps/changelog/src/pages/[locale]/page.tsx +7 -1
  115. package/templates/apps/edge-functions/package.json +2 -2
  116. package/templates/apps/frontend-admin/package.json +7 -8
  117. package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +1 -2
  118. package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +1 -2
  119. package/templates/apps/frontend-app/package.json +8 -9
  120. package/templates/apps/frontend-app/src/pages/layout.tsx +1 -2
  121. package/templates/apps/frontend-auth/package.json +7 -8
  122. package/templates/apps/frontend-auth/src/components/AuthShell.tsx +1 -2
  123. package/templates/apps/frontend-blank/package.json +6 -7
  124. package/templates/apps/frontend-blank/src/pages/layout.tsx +1 -2
  125. package/templates/apps/frontend-cms/package.json +8 -9
  126. package/templates/apps/frontend-cms/src/pages/(app)/layout.tsx +1 -2
  127. package/templates/apps/frontend-collab/README.md +7 -2
  128. package/templates/apps/frontend-collab/package.json +9 -10
  129. package/templates/apps/frontend-collab/src/pages/layout.tsx +1 -2
  130. package/templates/apps/frontend-collab/src/pages/page.test.tsx +8 -4
  131. package/templates/apps/frontend-collab/src/pages/page.tsx +10 -5
  132. package/templates/apps/frontend-contact/package.json +7 -7
  133. package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +1 -1
  134. package/templates/apps/frontend-dashboard/package.json +6 -7
  135. package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +1 -2
  136. package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +1 -2
  137. package/templates/apps/frontend-docs/package.json +8 -8
  138. package/templates/apps/frontend-i18n/package.json +6 -6
  139. package/templates/apps/frontend-landing/package.json +7 -7
  140. package/templates/apps/frontend-portal/package.json +7 -8
  141. package/templates/apps/frontend-portal/src/pages/(portal)/layout.tsx +1 -2
  142. package/templates/apps/frontend-saas/package.json +7 -8
  143. package/templates/apps/frontend-saas/src/pages/(marketing)/layout.tsx +1 -2
  144. package/templates/apps/frontend-saas/src/pages/dashboard/layout.tsx +1 -2
  145. package/templates/apps/frontend-spa/package.json +6 -7
  146. package/templates/apps/frontend-spa/src/pages/layout.tsx +1 -2
  147. package/templates/apps/frontend-ssr/package.json +6 -7
  148. package/templates/apps/frontend-ssr/src/pages/layout.tsx +1 -2
  149. package/templates/apps/frontend-ssr-api/package.json +7 -8
  150. package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +1 -2
  151. package/templates/apps/frontend-static-blog/package.json +8 -8
  152. package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +1 -1
  153. package/templates/apps/frontend-static-blog/src/pages/[locale]/page.tsx +7 -1
  154. package/templates/apps/frontend-status/package.json +7 -8
  155. package/templates/apps/frontend-status/src/pages/layout.tsx +1 -2
  156. package/templates/apps/mobile-app/package.json +4 -4
  157. package/templates/baselines/compose/docker/api.Dockerfile +61 -5
  158. package/templates/baselines/compose/docker/web.Dockerfile +55 -10
  159. package/templates/baselines/compose-mariadb/docker/api.Dockerfile +61 -5
  160. package/templates/baselines/compose-mariadb/docker/web.Dockerfile +55 -10
  161. package/dist/apiBuild-CeUN55uk.js +0 -2
  162. package/dist/codegen-DjgxEOnD.js +0 -2
  163. package/dist/dbCommand-CSFWs9ev.js +0 -2
  164. package/dist/doctorCommand-J3qu4E0Y.js +0 -2
  165. package/dist/frameworkTableAssembly-IPD1pUnZ.js +0 -2
  166. package/dist/inspect-Bd8-9wsi.js +0 -1193
  167. package/dist/inspect-CuoDInfZ.js +0 -2
  168. package/dist/interruptedReplace-C3O3M1MM.js +0 -28
  169. package/dist/interruptedReplace-CvmiAM9K.js +0 -2
  170. package/dist/manifestBuild-C4-J1-m_.js +0 -2
  171. package/dist/serveCommand-BiPe8BJm.js +0 -2
  172. package/dist/updateCommand-CIoVDKnj.js +0 -2
  173. package/dist/webDev-DlvZO30c.js +0 -2
@@ -1,8 +1,8 @@
1
- import { Ht as e, Ut as t, Wt as n } from "./dev-CmuvUKRq.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-DSI9SOhs.js";
3
- import { x as d } from "./frameworkTableAssembly-D7LJuALW.js";
4
- import { n as f } from "./pageConvention-CMpfDN6r.js";
5
- import { r as p, u as m } from "./apiBuild-DTWp0S_q.js";
1
+ import { Ht as e, Ut as t, Wt as n } from "./dev-Dw263KPu.js";
2
+ import { F as r, Ft as i, Mt as a, Nt as o, Pt as s, S as c, t as l, u } from "./webDev-C53hJdcL.js";
3
+ import { r as d, u as f } from "./apiBuild-Bdaetr37.js";
4
+ import { x as p } from "./frameworkTableAssembly-DNOFXfEQ.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,15 +493,15 @@ 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
- let r = new D({
499
+ let i = new D({
500
500
  useInMemoryFileSystem: !0,
501
501
  skipAddingFilesFromTsConfig: !0
502
- }), i;
502
+ }), a;
503
503
  try {
504
- i = r.createSourceFile(n, C(v(e, n), "utf8"));
504
+ a = i.createSourceFile(n, C(v(e, n), "utf8"));
505
505
  } catch {
506
506
  return {
507
507
  problems: [],
@@ -510,8 +510,8 @@ var le = /* @__PURE__ */ new Set([
510
510
  file: n
511
511
  };
512
512
  }
513
- let a = [], s = [], c = 0;
514
- for (let e of i.getVariableDeclarations()) {
513
+ let o = [], s = [], c = 0;
514
+ for (let e of a.getVariableDeclarations()) {
515
515
  let t = e.getInitializerIfKind(O.CallExpression);
516
516
  if (t?.getExpression().getText() !== "defineMiddleware") continue;
517
517
  c += 1;
@@ -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: r(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) => Ft.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,42 @@ 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[A-Z]\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) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").some((t) => {
917
+ let n = t.getArguments()[0];
918
+ if (n === void 0 || !E.isObjectLiteralExpression(n) || n.getProperty("once") !== void 0) return !1;
919
+ let r = Mt(n, e);
920
+ if (r === void 0) return !1;
921
+ let i = Nt(r);
922
+ return r.getDescendantsOfKind(O.CallExpression).some((e) => {
923
+ let t = e.getExpression().getText();
924
+ return i.some((e) => e.test(t));
925
+ });
926
+ }), Ft = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, It = /\.(query|mutation|action|stream)\.server\.tsx?$/, Lt = [
868
927
  "Id",
869
928
  "ID",
870
929
  "Hash",
@@ -890,7 +949,7 @@ var le = /* @__PURE__ */ new Set([
890
949
  "Comment",
891
950
  "Provider",
892
951
  "Format"
893
- ], Tt = RegExp(`(?:${wt.join("|")})$|_(?:${wt.map((e) => e.toLowerCase()).join("|")})$`), Et = [
952
+ ], Rt = RegExp(`(?:${Lt.join("|")})$|_(?:${Lt.map((e) => e.toLowerCase()).join("|")})$`), zt = [
894
953
  {
895
954
  id: "hardcoded-framework-cookie",
896
955
  scope: "client",
@@ -903,7 +962,7 @@ var le = /* @__PURE__ */ new Set([
903
962
  scope: "server",
904
963
  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
964
  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"))
965
+ match: ({ src: e, path: t }) => It.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
907
966
  },
908
967
  {
909
968
  id: "subject-write-no-guard",
@@ -912,12 +971,12 @@ var le = /* @__PURE__ */ new Set([
912
971
  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
972
  match: (e) => {
914
973
  let { src: t, path: n } = e;
915
- if (!Ct.test(n)) return !1;
916
- let r = P(t);
974
+ if (!It.test(n)) return !1;
975
+ let r = M(t);
917
976
  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);
977
+ let i = wt(e);
919
978
  if (i === void 0) return !1;
920
- let a = P(i);
979
+ let a = M(i);
921
980
  return /\binternal\s*:\s*true\b/.test(a) || /\bguards\s*:\s*\[/.test(a) || /\.guard\s*\(/.test(a) ? !1 : /\bopenAccess\s*:/.test(a);
922
981
  }
923
982
  },
@@ -926,14 +985,14 @@ var le = /* @__PURE__ */ new Set([
926
985
  scope: "server",
927
986
  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
987
  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
988
+ match: ({ file: e, path: t, serverOnly: n }) => (Ft.test(t) || n) && N(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
930
989
  },
931
990
  {
932
991
  id: "form",
933
992
  scope: "client",
934
993
  smell: "per-field useState + a submit flag (hand-rolled form)",
935
994
  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)
995
+ match: ({ src: e, file: t }) => N(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
937
996
  },
938
997
  {
939
998
  id: "table",
@@ -961,7 +1020,7 @@ var le = /* @__PURE__ */ new Set([
961
1020
  scope: "client",
962
1021
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
963
1022
  use: "useDerived — dependency-tracked, referentially stable",
964
- match: ({ src: e }) => F(e, /\buseSubscription\s*[(<]/g) >= 3 && F(e, /\buseMemo\s*\(/g) >= 3
1023
+ match: ({ src: e }) => Et(e, /\buseSubscription\s*[(<]/g) >= 3 && Et(e, /\buseMemo\s*\(/g) >= 3
965
1024
  },
966
1025
  {
967
1026
  id: "next-shim",
@@ -993,7 +1052,7 @@ var le = /* @__PURE__ */ new Set([
993
1052
  match: ({ file: e }) => e.getDescendantsOfKind(O.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
994
1053
  let t = e.getFirstAncestorByKind(O.VariableDeclaration)?.getNameNode();
995
1054
  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) => {
1055
+ }).some((t) => kt(e, t).some((e) => {
997
1056
  let t = e.getParent();
998
1057
  if (E.isBinaryExpression(t)) {
999
1058
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -1021,7 +1080,7 @@ var le = /* @__PURE__ */ new Set([
1021
1080
  scope: "server",
1022
1081
  smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
1023
1082
  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)
1083
+ match: (e) => N(e.file, /\bstore\.query$/) >= 3 || N(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
1025
1084
  },
1026
1085
  {
1027
1086
  id: "store-effect-lift",
@@ -1044,7 +1103,7 @@ var le = /* @__PURE__ */ new Set([
1044
1103
  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
1104
  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
1105
  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);
1106
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Rt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1048
1107
  }))
1049
1108
  },
1050
1109
  {
@@ -1054,15 +1113,22 @@ var le = /* @__PURE__ */ new Set([
1054
1113
  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
1114
  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
1115
  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);
1116
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Rt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1058
1117
  }))
1059
1118
  },
1119
+ {
1120
+ id: "subscriber-effect-without-once",
1121
+ scope: "server",
1122
+ smell: "a *.subscribe.ts handler that WRITES or publishes with no `once:` (it runs on every replica, so the effect repeats once per replica)",
1123
+ 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.",
1124
+ match: ({ file: e }) => Pt(e)
1125
+ },
1060
1126
  {
1061
1127
  id: "mutation-tail-effect",
1062
1128
  scope: "server",
1063
1129
  smell: "a notify / webhook helper called at the tail of a mutation",
1064
1130
  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))
1131
+ 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
1132
  },
1067
1133
  {
1068
1134
  id: "hand-cursor",
@@ -1118,9 +1184,9 @@ var le = /* @__PURE__ */ new Set([
1118
1184
  scope: "server",
1119
1185
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
1120
1186
  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);
1187
+ match: ({ file: e, testSources: t }) => N(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1188
+ let t = M(e);
1189
+ return Ot(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1124
1190
  })
1125
1191
  },
1126
1192
  {
@@ -1130,15 +1196,15 @@ var le = /* @__PURE__ */ new Set([
1130
1196
  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
1197
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1132
1198
  }
1133
- ], Dt = (e) => R(e), Ot = (e, t) => {
1199
+ ], P = (e) => L(e), Bt = (e, t) => {
1134
1200
  let n = /* @__PURE__ */ new Set();
1135
1201
  for (let r of t) {
1136
1202
  let t = y(e, r), i = t.split(/[\\/]/)[0];
1137
1203
  n.add(i !== void 0 && i !== t ? i : ".");
1138
1204
  }
1139
1205
  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;
1206
+ }, F = /\.(tsx?|jsx?)$/, I = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Vt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Ht = (e, t = [], n = 0) => {
1207
+ if (n > 12 || I.test(e)) return t;
1142
1208
  let r;
1143
1209
  try {
1144
1210
  r = w(e, { withFileTypes: !0 });
@@ -1149,12 +1215,12 @@ var le = /* @__PURE__ */ new Set([
1149
1215
  let r = v(e, i.name);
1150
1216
  if (i.isDirectory()) {
1151
1217
  if (i.name.startsWith(".")) continue;
1152
- jt(r, t, n + 1);
1218
+ Ht(r, t, n + 1);
1153
1219
  } else je.test(i.name) && t.push(r);
1154
1220
  }
1155
1221
  return t;
1156
- }, R = (e, t = [], n = 0) => {
1157
- if (n > 12 || L.test(e)) return t;
1222
+ }, L = (e, t = [], n = 0) => {
1223
+ if (n > 12 || I.test(e)) return t;
1158
1224
  let r;
1159
1225
  try {
1160
1226
  r = w(e);
@@ -1168,11 +1234,11 @@ var le = /* @__PURE__ */ new Set([
1168
1234
  } catch {
1169
1235
  continue;
1170
1236
  }
1171
- a.isDirectory() ? R(r, t, n + 1) : kt.test(i) && !/\.(test|spec)\./.test(i) && !At.test(i) && t.push(r);
1237
+ a.isDirectory() ? L(r, t, n + 1) : F.test(i) && !/\.(test|spec)\./.test(i) && !Vt.test(i) && t.push(r);
1172
1238
  }
1173
1239
  return t;
1174
- }, Mt = (e, t = [], n = 0) => {
1175
- if (n > 12 || L.test(e)) return t;
1240
+ }, Ut = (e, t = [], n = 0) => {
1241
+ if (n > 12 || I.test(e)) return t;
1176
1242
  let r;
1177
1243
  try {
1178
1244
  r = w(e);
@@ -1186,17 +1252,17 @@ var le = /* @__PURE__ */ new Set([
1186
1252
  } catch {
1187
1253
  continue;
1188
1254
  }
1189
- a.isDirectory() ? Mt(r, t, n + 1) : kt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1255
+ a.isDirectory() ? Ut(r, t, n + 1) : F.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1190
1256
  }
1191
1257
  return t;
1192
- }, Nt = (e) => {
1193
- let t = Dt(e);
1258
+ }, Wt = (e) => {
1259
+ let t = P(e);
1194
1260
  return {
1195
- dirs: Ot(e, t),
1261
+ dirs: Bt(e, t),
1196
1262
  fileCount: t.length
1197
1263
  };
1198
- }, Pt = (e) => {
1199
- let t = Dt(e);
1264
+ }, Gt = (e) => {
1265
+ let t = P(e);
1200
1266
  if (t.length === 0) return [];
1201
1267
  let n = new D({
1202
1268
  useInMemoryFileSystem: !0,
@@ -1205,8 +1271,8 @@ var le = /* @__PURE__ */ new Set([
1205
1271
  for (let e of t) try {
1206
1272
  i.set(e, C(e, "utf8"));
1207
1273
  } catch {}
1208
- let a = vt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1209
- for (let t of Mt(e)) try {
1274
+ let a = Tt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1275
+ for (let t of Ut(e)) try {
1210
1276
  o.set(t, C(t, "utf8"));
1211
1277
  } catch {}
1212
1278
  for (let s of t) {
@@ -1226,23 +1292,23 @@ var le = /* @__PURE__ */ new Set([
1226
1292
  } catch {
1227
1293
  continue;
1228
1294
  }
1229
- for (let t of Et) {
1230
- if (t.plugin !== void 0 && yt(c, t.plugin) || !t.match(c)) continue;
1295
+ for (let t of zt) {
1296
+ if (t.plugin !== void 0 && Dt(c, t.plugin) || !t.match(c)) continue;
1231
1297
  let n = r.get(t.id) ?? [];
1232
1298
  n.push(y(e, s)), r.set(t.id, n);
1233
1299
  }
1234
1300
  }
1235
- return Et.filter((e) => r.has(e.id)).map((e) => ({
1301
+ return zt.filter((e) => r.has(e.id)).map((e) => ({
1236
1302
  rule: e,
1237
1303
  files: r.get(e.id)
1238
1304
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1239
- }, z = (e) => {
1240
- let t = Nt(e);
1305
+ }, R = (e) => {
1306
+ let t = Wt(e);
1241
1307
  return {
1242
1308
  root: e,
1243
1309
  scannedFiles: t.fileCount,
1244
1310
  scannedDirs: t.dirs,
1245
- findings: Pt(e).map(({ rule: e, files: t }) => ({
1311
+ findings: Gt(e).map(({ rule: e, files: t }) => ({
1246
1312
  id: e.id,
1247
1313
  scope: e.scope,
1248
1314
  smell: e.smell,
@@ -1250,13 +1316,13 @@ var le = /* @__PURE__ */ new Set([
1250
1316
  files: t
1251
1317
  }))
1252
1318
  };
1253
- }, Ft = 3, It = .2, Lt = 50, Rt = (e, t) => e >= Lt && t > 0 && e / t >= It, zt = (e) => {
1319
+ }, Kt = 3, qt = .2, Jt = 50, Yt = (e, t) => e >= Jt && t > 0 && e / t >= qt, Xt = (e) => {
1254
1320
  let t = /* @__PURE__ */ new Map();
1255
- for (let n of R(e)) try {
1321
+ for (let n of L(e)) try {
1256
1322
  t.set(y(e, n), C(n, "utf8"));
1257
1323
  } catch {}
1258
1324
  let n = /* @__PURE__ */ new Map();
1259
- for (let t of jt(e)) try {
1325
+ for (let t of Ht(e)) try {
1260
1326
  n.set(y(e, t), C(t, "utf8"));
1261
1327
  } catch {}
1262
1328
  let r = Me({
@@ -1264,8 +1330,8 @@ var le = /* @__PURE__ */ new Set([
1264
1330
  envFiles: n
1265
1331
  });
1266
1332
  r !== void 0 && (console.log(""), console.log(Ne(r)));
1267
- }, Bt = (e) => {
1268
- let t = z(e);
1333
+ }, Zt = (e) => {
1334
+ let t = R(e);
1269
1335
  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
1336
  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
1337
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
@@ -1276,29 +1342,29 @@ var le = /* @__PURE__ */ new Set([
1276
1342
  console.log(` [${e}]`);
1277
1343
  let i = [...r].sort((e, t) => e.files.length - t.files.length);
1278
1344
  for (let e of i) {
1279
- if (Rt(e.files.length, t.scannedFiles)) {
1345
+ if (Yt(e.files.length, t.scannedFiles)) {
1280
1346
  let n = Math.round(e.files.length / t.scannedFiles * 100);
1281
1347
  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
1348
  continue;
1283
1349
  }
1284
- let r = e.files.slice(0, Ft).join(", "), i = e.files.length - Ft;
1350
+ let r = e.files.slice(0, Kt).join(", "), i = e.files.length - Kt;
1285
1351
  i > 0 && (n += i);
1286
1352
  let a = i > 0 ? `, +${i} more` : "";
1287
1353
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1288
1354
  }
1289
1355
  }
1290
1356
  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];
1357
+ }, Qt = "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.", $t = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, en = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, tn = (e) => {
1358
+ let t = $t.exec(e)?.[1];
1293
1359
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1294
- let n = Ut.exec(e)?.[1];
1360
+ let n = en.exec(e)?.[1];
1295
1361
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1296
- }, B = async (e) => {
1362
+ }, z = async (e) => {
1297
1363
  let t = v(e, "src", "pages");
1298
1364
  if (!S(t)) return [];
1299
- let { pages: n, dirs: r } = await u(t), i = [];
1365
+ let { pages: n, dirs: r } = await c(t), i = [];
1300
1366
  for (let a of n) {
1301
- if (!c(r, a.dirChain).some((e) => e.hasLayout)) continue;
1367
+ if (!u(r, a.dirChain).some((e) => e.hasLayout)) continue;
1302
1368
  let n;
1303
1369
  try {
1304
1370
  n = C(v(t, a.file), "utf8");
@@ -1306,7 +1372,7 @@ var le = /* @__PURE__ */ new Set([
1306
1372
  continue;
1307
1373
  }
1308
1374
  if (l.test(n)) continue;
1309
- let o = Wt(n);
1375
+ let o = tn(n);
1310
1376
  o !== null && i.push({
1311
1377
  file: y(e, v(t, a.file)),
1312
1378
  pattern: a.pattern,
@@ -1314,20 +1380,20 @@ var le = /* @__PURE__ */ new Set([
1314
1380
  });
1315
1381
  }
1316
1382
  return i;
1317
- }, Gt = 10, Kt = async (e) => {
1383
+ }, nn = 10, rn = async (e) => {
1318
1384
  console.log("");
1319
1385
  let t = v(e, "src", "pages");
1320
1386
  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);
1387
+ let n = await z(e);
1322
1388
  if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1323
1389
  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)) {
1390
+ for (let e of n.slice(0, nn)) {
1325
1391
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1326
1392
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1327
1393
  }
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([
1394
+ let r = n.length - nn;
1395
+ return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${Qt}`), n.length;
1396
+ }, an = /* @__PURE__ */ new Set([
1331
1397
  "node_modules",
1332
1398
  "dist",
1333
1399
  "build",
@@ -1335,7 +1401,7 @@ var le = /* @__PURE__ */ new Set([
1335
1401
  ".vite",
1336
1402
  ".framework",
1337
1403
  ".git"
1338
- ]), Jt = (e, t, n) => {
1404
+ ]), on = (e, t, n) => {
1339
1405
  let r;
1340
1406
  try {
1341
1407
  r = w(e, { withFileTypes: !0 });
@@ -1343,11 +1409,11 @@ var le = /* @__PURE__ */ new Set([
1343
1409
  return;
1344
1410
  }
1345
1411
  for (let i of r) {
1346
- if (qt.has(i.name)) continue;
1412
+ if (an.has(i.name)) continue;
1347
1413
  let r = v(e, i.name);
1348
- i.isDirectory() ? Jt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1414
+ i.isDirectory() ? on(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1349
1415
  }
1350
- }, Yt = (e, t) => {
1416
+ }, sn = (e, t) => {
1351
1417
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1352
1418
  for (let i of t) {
1353
1419
  let t;
@@ -1372,21 +1438,21 @@ var le = /* @__PURE__ */ new Set([
1372
1438
  }
1373
1439
  }
1374
1440
  return n;
1375
- }, Xt = (e, t) => {
1441
+ }, cn = (e, t) => {
1376
1442
  let n = e.slice(t.length).split(x).slice(0, -1)[0];
1377
1443
  return n !== void 0 && n.startsWith("[");
1378
- }, V = (e) => {
1444
+ }, B = (e) => {
1379
1445
  let t = [], n = v(e, "src", "pages"), r = v(e, "src");
1380
1446
  if (!S(r)) return t;
1381
1447
  let i = [];
1382
- Jt(r, e, i), i.sort();
1448
+ on(r, e, i), i.sort();
1383
1449
  let a = `${y(e, n)}${x}`;
1384
- Yt(e, i);
1450
+ sn(e, i);
1385
1451
  for (let n of i) {
1386
1452
  let r = n.slice(n.lastIndexOf(x) + 1);
1387
1453
  if (n.startsWith(a)) {
1388
1454
  if (r !== "page.tsx") continue;
1389
- S(v(e, f(n))) || t.push({
1455
+ S(v(e, m(n))) || t.push({
1390
1456
  kind: "page-without-test",
1391
1457
  file: n
1392
1458
  });
@@ -1397,7 +1463,7 @@ var le = /* @__PURE__ */ new Set([
1397
1463
  file: n
1398
1464
  });
1399
1465
  } catch {}
1400
- Xt(n, a) && t.push({
1466
+ cn(n, a) && t.push({
1401
1467
  kind: "top-level-dynamic-route",
1402
1468
  file: n
1403
1469
  });
@@ -1409,32 +1475,32 @@ var le = /* @__PURE__ */ new Set([
1409
1475
  });
1410
1476
  }
1411
1477
  return t;
1412
- }, Zt = 10, H = (e, t, n) => {
1478
+ }, V = 10, H = (e, t, n) => {
1413
1479
  if (t.length === 0) return;
1414
1480
  console.log(`• ${e} (${t.length}):`);
1415
- for (let e of t.slice(0, Zt)) console.log(` ${e}`);
1416
- let r = t.length - Zt;
1481
+ for (let e of t.slice(0, V)) console.log(` ${e}`);
1482
+ let r = t.length - V;
1417
1483
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
1418
- }, Qt = (e) => {
1484
+ }, ln = (e) => {
1419
1485
  console.log("");
1420
1486
  let t = v(e, "src", "pages");
1421
1487
  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);
1488
+ let n = B(e);
1423
1489
  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
1490
  let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
1425
1491
  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);
1492
+ }, un = "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.", dn = /* @__PURE__ */ new Map(), U = (e) => {
1493
+ let t = dn.get(e);
1428
1494
  if (t !== void 0) return t;
1429
1495
  let n = (async () => {
1430
- let { walk: t, loadDiscovered: n } = await import("./dev-cKUiZZsB.js"), r = await t(e);
1496
+ let { walk: t, loadDiscovered: n } = await import("./dev-B9Gz0k85.js"), r = await t(e);
1431
1497
  return {
1432
1498
  files: r,
1433
1499
  discovered: await n(r)
1434
1500
  };
1435
1501
  })();
1436
- return en.set(e, n), n;
1437
- }, tn = async (e) => {
1502
+ return dn.set(e, n), n;
1503
+ }, fn = async (e) => {
1438
1504
  let t;
1439
1505
  try {
1440
1506
  let { files: n, discovered: r } = await U(e), i = r.tables.map((e) => {
@@ -1450,7 +1516,7 @@ var le = /* @__PURE__ */ new Set([
1450
1516
  return;
1451
1517
  }
1452
1518
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
1453
- }, nn = (e) => e.map((e) => {
1519
+ }, pn = (e) => e.map((e) => {
1454
1520
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
1455
1521
  name: e,
1456
1522
  type: typeof t?.type == "string" ? t.type : ""
@@ -1460,13 +1526,13 @@ var le = /* @__PURE__ */ new Set([
1460
1526
  columns: n,
1461
1527
  primaryKey: t.appliedPrimaryKey ?? []
1462
1528
  };
1463
- }), rn = async (e) => {
1529
+ }), mn = async (e) => {
1464
1530
  try {
1465
1531
  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 = [];
1466
1532
  for (let e of i.queries) {
1467
1533
  let i = n(e.descriptor.source);
1468
1534
  if (i.length === 0) continue;
1469
- let s = an(e.file);
1535
+ let s = hn(e.file);
1470
1536
  if (s === void 0) continue;
1471
1537
  let c = await r(s, "utf8").catch(() => null);
1472
1538
  if (c === null) continue;
@@ -1483,8 +1549,8 @@ var le = /* @__PURE__ */ new Set([
1483
1549
  } catch {
1484
1550
  return null;
1485
1551
  }
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);
1552
+ }, hn = (e) => e.endsWith(".ts") && !e.endsWith(".server.ts") ? `${e.slice(0, -3)}.server.ts` : void 0, gn = async (e) => {
1553
+ let t = await mn(e);
1488
1554
  if (t === null) return;
1489
1555
  let n = t.findings;
1490
1556
  if (n.length > 0) {
@@ -1500,11 +1566,11 @@ var le = /* @__PURE__ */ new Set([
1500
1566
  }, W = async (e) => {
1501
1567
  try {
1502
1568
  let { discovered: t } = await U(e);
1503
- return ve(nn(t.tables));
1569
+ return ve(pn(t.tables));
1504
1570
  } catch {
1505
1571
  return null;
1506
1572
  }
1507
- }, sn = async (e) => {
1573
+ }, _n = async (e) => {
1508
1574
  let t = await W(e);
1509
1575
  if (t === null || t.tablesChecked === 0) return;
1510
1576
  let n = be(t);
@@ -1524,7 +1590,7 @@ var le = /* @__PURE__ */ new Set([
1524
1590
  } catch {
1525
1591
  return null;
1526
1592
  }
1527
- }, cn = async (e) => {
1593
+ }, vn = async (e) => {
1528
1594
  let t = await G(e);
1529
1595
  if (t === null || t.eventsChecked === 0) return;
1530
1596
  let n = Te(t);
@@ -1552,7 +1618,7 @@ var le = /* @__PURE__ */ new Set([
1552
1618
  }
1553
1619
  }, J = async (r) => {
1554
1620
  try {
1555
- let { loadApiConfig: i } = await import("./dev-cKUiZZsB.js"), { discovered: a } = await U(r), o = t(await i(r)), s = {
1621
+ let { loadApiConfig: i } = await import("./dev-B9Gz0k85.js"), { discovered: a } = await U(r), o = t(await i(r)), s = {
1556
1622
  queries: a.queries,
1557
1623
  mutations: a.mutations,
1558
1624
  actions: a.actions,
@@ -1574,7 +1640,7 @@ var le = /* @__PURE__ */ new Set([
1574
1640
  openAccess: []
1575
1641
  };
1576
1642
  }
1577
- }, ln = async (e) => {
1643
+ }, yn = async (e) => {
1578
1644
  let t = await J(e);
1579
1645
  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
1646
  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)}`);
@@ -1582,8 +1648,8 @@ var le = /* @__PURE__ */ new Set([
1582
1648
  if (t.undecided.length === 0) return !1;
1583
1649
  console.log("");
1584
1650
  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 = () => {
1651
+ return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), bn(), !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."), bn(), !1);
1652
+ }, bn = () => {
1587
1653
  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
1654
  }, Y = async (e) => {
1589
1655
  try {
@@ -1620,7 +1686,7 @@ var le = /* @__PURE__ */ new Set([
1620
1686
  verified: 0
1621
1687
  };
1622
1688
  }
1623
- }, dn = async (e) => {
1689
+ }, xn = async (e) => {
1624
1690
  let t = await Y(e);
1625
1691
  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
1692
  if (t.verified + t.open.length + t.unverified.length === 0) return !1;
@@ -1630,13 +1696,13 @@ var le = /* @__PURE__ */ new Set([
1630
1696
  console.log("");
1631
1697
  for (let e of t.unverified) console.log(` ✗ ${e.id}\n ${e.file}`);
1632
1698
  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) => {
1699
+ }, Sn = async (e) => {
1634
1700
  let { status: t, message: n } = await q(e);
1635
1701
  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({
1702
+ }, Cn = async (e) => ft({
1637
1703
  hasWorkflows: async () => (await U(e)).discovered.workflows.length > 0,
1638
1704
  openStore: async () => {
1639
- let { loadApiConfig: t, buildStore: n } = await import("./dev-cKUiZZsB.js"), r = await t(e);
1705
+ let { loadApiConfig: t, buildStore: n } = await import("./dev-B9Gz0k85.js"), r = await t(e);
1640
1706
  if (r === null) throw Error("no app.config.ts in this directory");
1641
1707
  let i = await n(r, void 0);
1642
1708
  return {
@@ -1650,10 +1716,10 @@ var le = /* @__PURE__ */ new Set([
1650
1716
  let { sweepStalledRuns: t } = await import("@voltro/workflow");
1651
1717
  return t({ store: e });
1652
1718
  }
1653
- }), mn = async (e) => {
1654
- let t = ft(await pn(e));
1719
+ }), wn = async (e) => {
1720
+ let t = mt(await Cn(e));
1655
1721
  t !== null && (console.log(""), console.log(t));
1656
- }, hn = async (e) => {
1722
+ }, Tn = async (e) => {
1657
1723
  let t;
1658
1724
  try {
1659
1725
  let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await U(e);
@@ -1676,13 +1742,13 @@ var le = /* @__PURE__ */ new Set([
1676
1742
  console.log("");
1677
1743
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
1678
1744
  console.log(`${r} ${n}`);
1679
- }, X = "voltro-authz-allowlist.txt", gn = (e, t = (e) => {
1745
+ }, X = "voltro-authz-allowlist.txt", En = (e, t = (e) => {
1680
1746
  try {
1681
1747
  return C(e, "utf8");
1682
1748
  } catch {
1683
1749
  return "";
1684
1750
  }
1685
- }) => /* @__PURE__ */ new Set([...Je, ...Ye(R(e).map(t))]), _n = (e, t) => {
1751
+ }) => /* @__PURE__ */ new Set([...Xe, ...Ze(L(e).map(t))]), Dn = (e, t) => {
1686
1752
  let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1687
1753
  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
1754
  }, Z = async (e) => {
@@ -1698,25 +1764,25 @@ var le = /* @__PURE__ */ new Set([
1698
1764
  } catch {
1699
1765
  return "";
1700
1766
  }
1701
- }, a = R(e).filter((e) => !r.has(e)).map(i).join("\n");
1767
+ }, a = L(e).filter((e) => !r.has(e)).map(i).join("\n");
1702
1768
  return n.filter(({ name: e, file: t }) => {
1703
1769
  if (a.includes(e)) return !1;
1704
- let n = _n(i(t), e);
1770
+ let n = Dn(i(t), e);
1705
1771
  return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
1706
1772
  }).map((e) => e.name).sort();
1707
1773
  } catch {
1708
1774
  return [];
1709
1775
  }
1710
- }, vn = async (e) => {
1776
+ }, On = async (e) => {
1711
1777
  let t = await Z(e);
1712
1778
  if (t.length !== 0) {
1713
1779
  console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
1714
1780
  for (let e of t) console.log(` ${e}`);
1715
1781
  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
1782
  }
1717
- }, yn = async (e) => {
1783
+ }, kn = async (e) => {
1718
1784
  try {
1719
- let { loadApiConfig: t } = await import("./dev-cKUiZZsB.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1785
+ let { loadApiConfig: t } = await import("./dev-B9Gz0k85.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1720
1786
  if (typeof n != "string") return [];
1721
1787
  let [r, i] = n.split("#");
1722
1788
  if (r === void 0 || i === void 0) return [];
@@ -1733,10 +1799,10 @@ var le = /* @__PURE__ */ new Set([
1733
1799
  findings: []
1734
1800
  });
1735
1801
  try {
1736
- let { offlineManifest: n, toInput: r } = await import("./checkCommand-L7DTlpIF.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])];
1802
+ let { offlineManifest: n, toInput: r } = await import("./checkCommand-D0QV_zM_.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CFmFKHb2.js"), o = await n(e), s = r(o.manifest, o.tables), c = await kn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1737
1803
  if (l.length === 0) {
1738
1804
  try {
1739
- let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-IPD1pUnZ.js");
1805
+ let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-CGNC0qr7.js");
1740
1806
  await t(e);
1741
1807
  } catch {
1742
1808
  return t("app-not-importable");
@@ -1760,7 +1826,7 @@ var le = /* @__PURE__ */ new Set([
1760
1826
  } catch {
1761
1827
  return t("app-not-importable");
1762
1828
  }
1763
- }, bn = async (e) => {
1829
+ }, An = async (e) => {
1764
1830
  let { findings: t, evaluated: n, notEvaluated: r } = await Q(e);
1765
1831
  if (!n) {
1766
1832
  if (console.log(""), r === "app-not-importable") {
@@ -1775,7 +1841,43 @@ var le = /* @__PURE__ */ new Set([
1775
1841
  for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
1776
1842
  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
1843
  }
1778
- }, xn = async (e) => {
1844
+ }, jn = (e) => {
1845
+ let t = v(e, "src");
1846
+ if (!S(t)) return;
1847
+ let n = [], r = (t, i = 0) => {
1848
+ if (i > 12 || I.test(t)) return;
1849
+ let a;
1850
+ try {
1851
+ a = w(t);
1852
+ } catch {
1853
+ return;
1854
+ }
1855
+ for (let o of a) {
1856
+ let a = v(t, o), s;
1857
+ try {
1858
+ s = T(a);
1859
+ } catch {
1860
+ continue;
1861
+ }
1862
+ if (s.isDirectory()) {
1863
+ r(a, i + 1);
1864
+ continue;
1865
+ }
1866
+ if (!(!/\.(tsx?|css)$/.test(o) || /\.(test|spec)\./.test(o))) try {
1867
+ n.push({
1868
+ path: y(e, a).split("\\").join("/"),
1869
+ source: C(a, "utf8")
1870
+ });
1871
+ } catch {}
1872
+ }
1873
+ };
1874
+ r(t);
1875
+ let i = yt(_t(n));
1876
+ if (i.length !== 0) {
1877
+ console.log("");
1878
+ for (let e of i) console.log(e);
1879
+ }
1880
+ }, Mn = async (e) => {
1779
1881
  let t = ["src/locales", "locales"], n, r = [];
1780
1882
  for (let i of t) {
1781
1883
  let t = v(e, i);
@@ -1787,20 +1889,20 @@ var le = /* @__PURE__ */ new Set([
1787
1889
  if (n === void 0 || r.length === 0) return;
1788
1890
  let i = [];
1789
1891
  try {
1790
- let { loadApiConfig: t } = await import("./dev-cKUiZZsB.js");
1892
+ let { loadApiConfig: t } = await import("./dev-B9Gz0k85.js");
1791
1893
  i = (await t(e))?.locales ?? [];
1792
1894
  } catch {
1793
1895
  return;
1794
1896
  }
1795
- let a = He({
1897
+ let a = We({
1796
1898
  catalogCodes: r,
1797
1899
  declaredLocales: i
1798
1900
  });
1799
1901
  if (a !== void 0) {
1800
1902
  console.log("");
1801
- for (let e of Ue(a, n)) console.log(e);
1903
+ for (let e of Ge(a, n)) console.log(e);
1802
1904
  }
1803
- }, Sn = async (e) => {
1905
+ }, Nn = async (e) => {
1804
1906
  let t = v(e, "src");
1805
1907
  if (!S(t)) return;
1806
1908
  let n = !1, r = (e) => {
@@ -1819,9 +1921,9 @@ var le = /* @__PURE__ */ new Set([
1819
1921
  return;
1820
1922
  }
1821
1923
  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) => {
1924
+ }, Pn = async (e) => {
1823
1925
  try {
1824
- let { loadConfig: t } = await import("./webDev-DlvZO30c.js"), n = (await t(e, { preferSource: !0 }))?.fonts;
1926
+ let { loadConfig: t } = await import("./webDev-C2dRz9s5.js"), n = (await t(e, { preferSource: !0 }))?.fonts;
1825
1927
  if (n === void 0 || n.length === 0) return;
1826
1928
  let { loadFontkit: r, loadSubsetFont: i } = await import("./fontPipeline-LxIHa1vo.js"), a = [];
1827
1929
  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 +1933,25 @@ var le = /* @__PURE__ */ new Set([
1831
1933
  for (let e of a) console.log(`font pipeline: ${e}`);
1832
1934
  console.log(" Fix: reinstall without omitting optional deps, or `pnpm add -D fontkit subset-font` in this app.");
1833
1935
  } catch {}
1834
- }, wn = async (e) => {
1936
+ }, Fn = async (e) => {
1835
1937
  let t = v(e, "src", "pages");
1836
1938
  if (!S(t)) return !1;
1837
1939
  let n = [];
1838
1940
  try {
1839
- let { walkPagesTree: e } = await import("./webDev-DlvZO30c.js");
1941
+ let { walkPagesTree: e } = await import("./webDev-C2dRz9s5.js");
1840
1942
  n = (await e(t)).pages.map((e) => e.pattern);
1841
1943
  } catch {
1842
1944
  return !1;
1843
1945
  }
1844
- let r = Ge(e, n);
1946
+ let r = qe(e, n);
1845
1947
  if (r === void 0) return !1;
1846
1948
  console.log("");
1847
- for (let e of Ke(r)) console.log(e);
1949
+ for (let e of Je(r)) console.log(e);
1848
1950
  return r.problems.length > 0;
1849
- }, Tn = async (e) => {
1951
+ }, In = async (e) => {
1850
1952
  let { discovered: t } = await U(e), n = [];
1851
1953
  try {
1852
- let { loadApiConfig: t } = await import("./dev-cKUiZZsB.js");
1954
+ let { loadApiConfig: t } = await import("./dev-B9Gz0k85.js");
1853
1955
  n = ((await t(e))?.plugins ?? []).map((e) => {
1854
1956
  let t = (e.extendSchema?.tables ?? []).map((e) => {
1855
1957
  let t = e;
@@ -1857,13 +1959,13 @@ var le = /* @__PURE__ */ new Set([
1857
1959
  tableName: typeof t.tableName == "string" ? t.tableName : "",
1858
1960
  renamedFrom: typeof t.renamedFrom == "string" ? t.renamedFrom : void 0
1859
1961
  };
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;
1962
+ }).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
1963
  return {
1862
1964
  plugin: e.name,
1863
1965
  tables: t,
1864
1966
  rpcTags: n,
1865
- canDisableTables: En.has(r),
1866
- canAlias: Dn.has(r)
1967
+ canDisableTables: Ln.has(r),
1968
+ canAlias: Rn.has(r)
1867
1969
  };
1868
1970
  });
1869
1971
  } catch {
@@ -1875,18 +1977,18 @@ var le = /* @__PURE__ */ new Set([
1875
1977
  ...t.mutations,
1876
1978
  ...t.actions,
1877
1979
  ...t.streams ?? []
1878
- ].map((e) => String(e.descriptor.name)), i = ct(n, {
1980
+ ].map((e) => String(e.descriptor.name)), i = ut(n, {
1879
1981
  tables: t.tables.map((e) => e.tableName),
1880
1982
  rpcTags: r
1881
1983
  });
1882
1984
  console.log();
1883
- for (let e of lt(n, i)) console.log(e);
1884
- }, En = /* @__PURE__ */ new Set([
1985
+ for (let e of dt(n, i)) console.log(e);
1986
+ }, Ln = /* @__PURE__ */ new Set([
1885
1987
  "@voltro/plugin-rbac",
1886
1988
  "@voltro/plugin-audit",
1887
1989
  "@voltro/plugin-notifications",
1888
1990
  "aiFlows"
1889
- ]), Dn = /* @__PURE__ */ new Set([
1991
+ ]), Rn = /* @__PURE__ */ new Set([
1890
1992
  "aiFlows",
1891
1993
  "@voltro/plugin-audit",
1892
1994
  "@voltro/plugin-billing",
@@ -1904,8 +2006,8 @@ var le = /* @__PURE__ */ new Set([
1904
2006
  "@voltro/plugin-sso-saml",
1905
2007
  "@voltro/plugin-storage",
1906
2008
  "@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;
2009
+ ]), $ = 20, zn = async (e) => {
2010
+ let t = await Bn(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
2011
  return {
1910
2012
  executors: t.executorCount,
1911
2013
  guardVocabulary: [...t.vocabulary].sort(),
@@ -1917,7 +2019,7 @@ var le = /* @__PURE__ */ new Set([
1917
2019
  },
1918
2020
  unchecked: t.result.unchecked
1919
2021
  };
1920
- }, kn = async (e) => {
2022
+ }, Bn = async (e) => {
1921
2023
  let { discovered: t } = await U(e), n = /* @__PURE__ */ new Map(), r = (e) => {
1922
2024
  if (!n.has(e)) try {
1923
2025
  n.set(e, C(e, "utf8"));
@@ -1925,7 +2027,7 @@ var le = /* @__PURE__ */ new Set([
1925
2027
  n.set(e, "");
1926
2028
  }
1927
2029
  return n.get(e);
1928
- }, i = gn(e, r), a = /* @__PURE__ */ new Set();
2030
+ }, i = En(e, r), a = /* @__PURE__ */ new Set();
1929
2031
  for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
1930
2032
  let e = t.references?.();
1931
2033
  e && a.add(e.tableName);
@@ -1957,12 +2059,12 @@ var le = /* @__PURE__ */ new Set([
1957
2059
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
1958
2060
  ...a?.table === void 0 ? {} : { table: a.table }
1959
2061
  };
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))) {
2062
+ })), s = v(e, X), c = S(s) ? j(C(s, "utf8")) : void 0, l = !1;
2063
+ for (let t of L(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
1962
2064
  l = !0;
1963
2065
  break;
1964
2066
  }
1965
- let u = tt({
2067
+ let u = rt({
1966
2068
  executors: o,
1967
2069
  guardVocabulary: i,
1968
2070
  tenantScopedTables: t.tenantScopedTables,
@@ -1979,23 +2081,23 @@ var le = /* @__PURE__ */ new Set([
1979
2081
  executorCount: o.length,
1980
2082
  allowPath: s
1981
2083
  };
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;
2084
+ }, Vn = async (e, t) => {
2085
+ let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Bn(e), l = n.counts;
1984
2086
  console.log(`\nauthz scan · ${o} executor(s)`);
1985
- let u = [...r].filter((e) => !Je.has(e)).sort();
2087
+ let u = [...r].filter((e) => !Xe.has(e)).sort();
1986
2088
  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
2089
  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
2090
  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;
2091
+ let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = st(e, t);
2092
+ return oe(s, ot(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;
1991
2093
  }
1992
2094
  if (n.unchecked.length === 0) return !1;
1993
2095
  console.log("");
1994
2096
  for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
1995
2097
  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) => {
2098
+ }, Hn = async (e) => {
1997
2099
  try {
1998
- let { loadApiConfig: t } = await import("./dev-cKUiZZsB.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
2100
+ let { loadApiConfig: t } = await import("./dev-B9Gz0k85.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
1999
2101
  return r == null ? null : n({
2000
2102
  ...r.schema ? { declared: r.schema } : {},
2001
2103
  ...r.store === void 0 ? {} : { store: r.store },
@@ -2004,42 +2106,42 @@ var le = /* @__PURE__ */ new Set([
2004
2106
  } catch {
2005
2107
  return null;
2006
2108
  }
2007
- }, Mn = async (e) => {
2008
- let t = await jn(e);
2109
+ }, Un = async (e) => {
2110
+ let t = await Hn(e);
2009
2111
  if (t !== null) {
2010
2112
  console.log(""), console.log("• framework tables whose presence is DECIDED rather than implied by your source tree:");
2011
2113
  for (let e of t.text.split("\n")) console.log(` ${e}`);
2012
2114
  }
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());
2115
+ }, Wn = async (e) => {
2116
+ if (e.includes("--help") || e.includes("-h")) return console.log(un), 0;
2117
+ let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), r = b(n ?? process.cwd());
2016
2118
  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)
2119
+ ...R(r),
2120
+ spaCandidates: await z(r),
2121
+ pageConvention: B(r),
2122
+ fileTaxonomy: re(r).findings,
2123
+ junctionFks: (await W(r))?.issues ?? [],
2124
+ eventDelivery: await G(r),
2125
+ duplicateInstances: a(r),
2126
+ versionSkew: o(r) ?? null,
2127
+ serverOnly: (await q(r)).status,
2128
+ accessDecisions: await J(r),
2129
+ webhookVerification: await Y(r),
2130
+ authz: await zn(r),
2131
+ ungrantedScopes: await Q(r),
2132
+ neverEmittedEvents: await Z(r),
2133
+ unimportedDeps: He(r),
2134
+ stalledRuns: await Cn(r),
2135
+ decidedSchema: await Hn(r)
2034
2136
  }), 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));
2137
+ let c = Ct(r, t);
2138
+ Zt(r), Xt(r), ln(r), ne(r), await rn(r), await Tn(r), await wn(r), await fn(r), await _n(r), await gn(r), await Un(r), await vn(r), await In(r), await An(r), await On(r), console.log("");
2139
+ for (let e of Ue(He(r))) console.log(e);
2140
+ await Mn(r), jn(r), await Nn(r), await Pn(r);
2141
+ let l = await Sn(r), u = await Vn(r, e.includes("--write-authz-allowlist")), d = await yn(r), f = await xn(r), p = await Fn(r), m = i(o(r));
2040
2142
  m !== void 0 && (console.log(""), console.log(m));
2041
- let _ = i(s(o));
2143
+ let _ = s(a(r));
2042
2144
  return _ !== void 0 && console.log(`\n${_}`), l || u || d || p || f ? Math.max(c, 1) : c;
2043
2145
  };
2044
2146
  //#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 };
2147
+ export { z as C, Y as E, B as S, Q as T, ln as _, zn as a, Ct as b, G as c, Wt as d, xt as f, Zt as g, Xt as h, J as i, En as l, mn as m, St as n, Z as o, W as p, Qt as r, Dn as s, un as t, R as u, rn as v, q as w, Gt as x, Wn as y };