@voltro/cli 0.30.0 → 0.30.2

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 (79) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/dist/{apiBuild-BceikpQf.js → apiBuild-BUS1RfPi.js} +2 -2
  3. package/dist/apiBuild-DMlxvL8h.js +2 -0
  4. package/dist/bin.js +3 -3
  5. package/dist/checkCommand-CARZ36Tr.js +2 -0
  6. package/dist/{checkCommand-BMr-X48w.js → checkCommand-D_wdOFhw.js} +498 -321
  7. package/dist/{commands-D4Y1-Nlu.js → commands-BgGAUcku.js} +2392 -2390
  8. package/dist/{dbCommand-C7hbl5pk.js → dbCommand-CbPRCD1x.js} +56 -19
  9. package/dist/dbCommand-Ht47LrV-.js +2 -0
  10. package/dist/{dev-DmnkH-wr.js → dev-GAOIot9G.js} +2202 -2170
  11. package/dist/{dev-Cr-teYsz.js → dev-tPcOmjPJ.js} +1 -1
  12. package/dist/{frameworkTableAssembly-BzAajuUv.js → frameworkTableAssembly-BwIrO5nv.js} +162 -145
  13. package/dist/frameworkTableAssembly-D-EebUQX.js +2 -0
  14. package/dist/index.js +1 -1
  15. package/dist/{inspectMetrics-BeJdP_3b.js → inspectMetrics-BM1vrsga.js} +316 -314
  16. package/dist/{sdkgen-CqpOq_fy.js → sdkgen-BV4fFucu.js} +1 -1
  17. package/dist/{serveCommand-Ciq_wIY8.js → serveCommand-ClqXCX1y.js} +22 -21
  18. package/dist/serveEntry.js +2 -2
  19. package/dist/{start-ocqB1seX.js → start-CTnNvKlk.js} +370 -368
  20. package/dist/startEntry.js +2 -2
  21. package/package.json +17 -17
  22. package/templates/AGENTS.md +1 -1
  23. package/templates/agent-docs/_index.md +1 -1
  24. package/templates/agent-docs/cli.md +29 -0
  25. package/templates/agent-docs/internationalization.md +25 -3
  26. package/templates/agent-docs/routing.md +14 -0
  27. package/templates/agent-docs/whats-new.md +23 -352
  28. package/templates/agent-docs/workflows.md +2 -0
  29. package/templates/apps/api-ai/package.json +7 -7
  30. package/templates/apps/api-auth/package.json +8 -8
  31. package/templates/apps/api-backend/package.json +7 -7
  32. package/templates/apps/api-backend-deactivation/package.json +7 -7
  33. package/templates/apps/api-backend-mail/package.json +8 -8
  34. package/templates/apps/api-backend-mariadb/package.json +9 -9
  35. package/templates/apps/api-backend-sqlite/package.json +8 -8
  36. package/templates/apps/api-backend-storage/package.json +8 -8
  37. package/templates/apps/api-cms/package.json +10 -10
  38. package/templates/apps/api-collab/package.json +8 -8
  39. package/templates/apps/api-data-advanced/package.json +8 -8
  40. package/templates/apps/api-durable/package.json +8 -8
  41. package/templates/apps/api-feature-flags/package.json +9 -9
  42. package/templates/apps/api-governance/package.json +8 -8
  43. package/templates/apps/api-kv/package.json +8 -8
  44. package/templates/apps/api-moderation/package.json +8 -8
  45. package/templates/apps/api-observability/package.json +8 -8
  46. package/templates/apps/api-ratelimit/package.json +8 -8
  47. package/templates/apps/api-rbac/package.json +8 -8
  48. package/templates/apps/api-rest/package.json +7 -7
  49. package/templates/apps/api-saas/package.json +11 -11
  50. package/templates/apps/api-saas-starter/package.json +10 -10
  51. package/templates/apps/api-search/package.json +8 -8
  52. package/templates/apps/api-status/package.json +8 -8
  53. package/templates/apps/api-versioning/package.json +8 -8
  54. package/templates/apps/api-webhooks/package.json +9 -9
  55. package/templates/apps/changelog/package.json +6 -6
  56. package/templates/apps/edge-functions/package.json +2 -2
  57. package/templates/apps/frontend-admin/package.json +8 -8
  58. package/templates/apps/frontend-app/package.json +8 -8
  59. package/templates/apps/frontend-auth/package.json +8 -8
  60. package/templates/apps/frontend-blank/package.json +7 -7
  61. package/templates/apps/frontend-cms/package.json +9 -9
  62. package/templates/apps/frontend-collab/package.json +9 -9
  63. package/templates/apps/frontend-contact/package.json +7 -7
  64. package/templates/apps/frontend-dashboard/package.json +7 -7
  65. package/templates/apps/frontend-docs/package.json +7 -7
  66. package/templates/apps/frontend-i18n/package.json +6 -6
  67. package/templates/apps/frontend-landing/package.json +7 -7
  68. package/templates/apps/frontend-portal/package.json +8 -8
  69. package/templates/apps/frontend-saas/package.json +8 -8
  70. package/templates/apps/frontend-spa/package.json +7 -7
  71. package/templates/apps/frontend-ssr/package.json +7 -7
  72. package/templates/apps/frontend-ssr-api/package.json +8 -8
  73. package/templates/apps/frontend-static-blog/package.json +6 -6
  74. package/templates/apps/frontend-status/package.json +8 -8
  75. package/templates/apps/mobile-app/package.json +2 -2
  76. package/dist/apiBuild-CxvR1Dwt.js +0 -2
  77. package/dist/checkCommand-BOk9y264.js +0 -2
  78. package/dist/dbCommand-D8nhyppR.js +0 -2
  79. package/dist/frameworkTableAssembly-DU0aOxjL.js +0 -2
@@ -1,7 +1,7 @@
1
- import { R as e, f as t, ht as n, n as r, o as i } from "./inspectMetrics-BeJdP_3b.js";
2
- import { Ut as a, Wt as o } from "./dev-DmnkH-wr.js";
1
+ import { f as e, gt as t, n, o as r, z as i } from "./inspectMetrics-BM1vrsga.js";
2
+ import { Ut as a, Wt as o } from "./dev-GAOIot9G.js";
3
3
  import { C as s, E as c, O as l, T as u, d, h as f, i as p, l as m, m as h, r as g, u as _ } from "./fileConventions-Cof68_BL.js";
4
- import { r as v, u as y } from "./apiBuild-BceikpQf.js";
4
+ import { r as v, u as y } from "./apiBuild-BUS1RfPi.js";
5
5
  import { dirname as b, isAbsolute as ee, join as x, relative as S, resolve as C, sep as w } from "node:path";
6
6
  import { routeDiagnosticsToStderr as te } from "@voltro/logger";
7
7
  import { existsSync as T, readFileSync as E, readdirSync as D, statSync as O, writeFileSync as ne } from "node:fs";
@@ -119,7 +119,7 @@ var re = () => {
119
119
  status: "live",
120
120
  logFile: "",
121
121
  ...e.token === void 0 ? {} : { inspectToken: e.token }
122
- }), le = async (e, t) => t === void 0 ? (await n()).apps.filter((t) => t.kind === "api" && t.status === "live" && t.url.length > 0 && (e === void 0 || t.name === e)) : [ce(t)], ue = (e) => {
122
+ }), le = async (e, n) => n === void 0 ? (await t()).apps.filter((t) => t.kind === "api" && t.status === "live" && t.url.length > 0 && (e === void 0 || t.name === e)) : [ce(n)], ue = (e) => {
123
123
  let t = "pretty", n = [];
124
124
  for (let r = 0; r < e.length; r++) {
125
125
  let i = e[r];
@@ -442,7 +442,7 @@ var re = () => {
442
442
  re: l,
443
443
  needsTest: !1
444
444
  }
445
- ], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, z = (e) => u.test(e), we = /* @__PURE__ */ new Set([
445
+ ], R = (e) => Ce.find((t) => t.re.test(e))?.id ?? null, we = (e) => u.test(e), Te = /* @__PURE__ */ new Set([
446
446
  "useMutation",
447
447
  "useAction",
448
448
  "useUpload",
@@ -452,12 +452,12 @@ var re = () => {
452
452
  "useAgentStream",
453
453
  "useResumableAgentStream",
454
454
  "useWorkflow"
455
- ]), Te = /* @__PURE__ */ new Set(["defineTracking"]), Ee = /* @__PURE__ */ new Set([
455
+ ]), Ee = /* @__PURE__ */ new Set(["defineTracking"]), De = /* @__PURE__ */ new Set([
456
456
  "useSubscription",
457
457
  "useWindowedSubscription",
458
458
  "useRecord",
459
459
  "useQuery"
460
- ]), De = /* @__PURE__ */ new Set([
460
+ ]), Oe = /* @__PURE__ */ new Set([
461
461
  "node_modules",
462
462
  "dist",
463
463
  "build",
@@ -468,7 +468,7 @@ var re = () => {
468
468
  ".git",
469
469
  "coverage",
470
470
  ".publish"
471
- ]), Oe = /\.tsx?$/, B = (e, t = [], n = 0) => {
471
+ ]), ke = /\.tsx?$/, Ae = (e, t = [], n = 0) => {
472
472
  if (n > 12) return t;
473
473
  let r;
474
474
  try {
@@ -477,27 +477,27 @@ var re = () => {
477
477
  return t;
478
478
  }
479
479
  for (let i of r) {
480
- if (De.has(i)) continue;
480
+ if (Oe.has(i)) continue;
481
481
  let r = x(e, i), a;
482
482
  try {
483
483
  a = O(r);
484
484
  } catch {
485
485
  continue;
486
486
  }
487
- a.isDirectory() ? B(r, t, n + 1) : a.isFile() && Oe.test(i) && !i.endsWith(".d.ts") && t.push(r);
487
+ a.isDirectory() ? Ae(r, t, n + 1) : a.isFile() && ke.test(i) && !i.endsWith(".d.ts") && t.push(r);
488
488
  }
489
489
  return t;
490
- }, ke = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, Ae = /\b(?:memo|forwardRef|observer|styled)\b/, V = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), H = (e) => {
491
- if (V(e)) return !0;
492
- if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return H(e.getExpression());
490
+ }, je = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, Me = /\b(?:memo|forwardRef|observer|styled)\b/, z = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), B = (e) => {
491
+ if (z(e)) return !0;
492
+ if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return B(e.getExpression());
493
493
  if (k.isVariableDeclaration(e)) {
494
494
  let t = e.getTypeNode();
495
- if (t !== void 0 && ke.test(t.getText())) return !0;
495
+ if (t !== void 0 && je.test(t.getText())) return !0;
496
496
  let n = e.getInitializer();
497
- return n === void 0 || H(n);
497
+ return n === void 0 || B(n);
498
498
  }
499
- return k.isCallExpression(e) ? Ae.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => V(e) || k.isIdentifier(e)) : k.isTaggedTemplateExpression(e) ? !0 : !(k.isObjectLiteralExpression(e) || k.isArrayLiteralExpression(e) || k.isStringLiteral(e) || k.isNoSubstitutionTemplateLiteral(e) || k.isNumericLiteral(e) || k.isTemplateExpression(e) || k.isNewExpression(e) || k.isPropertyAccessExpression(e) || e.getKind() === j.TrueKeyword || e.getKind() === j.FalseKeyword || e.getKind() === j.NullKeyword);
500
- }, je = (e) => {
499
+ return k.isCallExpression(e) ? Me.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => z(e) || k.isIdentifier(e)) : k.isTaggedTemplateExpression(e) ? !0 : !(k.isObjectLiteralExpression(e) || k.isArrayLiteralExpression(e) || k.isStringLiteral(e) || k.isNoSubstitutionTemplateLiteral(e) || k.isNumericLiteral(e) || k.isTemplateExpression(e) || k.isNewExpression(e) || k.isPropertyAccessExpression(e) || e.getKind() === j.TrueKeyword || e.getKind() === j.FalseKeyword || e.getKind() === j.NullKeyword);
500
+ }, Ne = (e) => {
501
501
  let t = [], n = [], r = [], i = /* @__PURE__ */ new Set();
502
502
  for (let [a, o] of e.getExportedDeclarations()) {
503
503
  let e = o[0];
@@ -508,7 +508,7 @@ var re = () => {
508
508
  n.push(s);
509
509
  continue;
510
510
  }
511
- if (/^[A-Z]/.test(s) && H(e)) {
511
+ if (/^[A-Z]/.test(s) && B(e)) {
512
512
  t.push(s);
513
513
  continue;
514
514
  }
@@ -520,7 +520,7 @@ var re = () => {
520
520
  others: r,
521
521
  hasRuntimeExport: t.length + n.length + r.length > 0
522
522
  };
523
- }, Me = [], Ne = (e, t, n) => {
523
+ }, Pe = [], Fe = (e, t, n) => {
524
524
  let r = b(e), i = [];
525
525
  for (let e = r;; e = b(e)) {
526
526
  let r = n.get(e);
@@ -537,9 +537,9 @@ var re = () => {
537
537
  }
538
538
  if (e === t || b(e) === e) break;
539
539
  }
540
- for (let e of i) n.set(e, Me);
541
- return Me;
542
- }, Pe = (e, t, n) => {
540
+ for (let e of i) n.set(e, Pe);
541
+ return Pe;
542
+ }, Ie = (e, t, n) => {
543
543
  let r = t.startsWith(".") ? C(b(e), t) : ye(t, n);
544
544
  if (r === void 0) return null;
545
545
  for (let e of [
@@ -552,9 +552,9 @@ var re = () => {
552
552
  if (O(e).isFile()) return e;
553
553
  } catch {}
554
554
  return null;
555
- }, Fe = (e, t, n) => {
555
+ }, Le = (e, t, n) => {
556
556
  let r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map(), o = (e) => {
557
- let i = Pe(t, e, n);
557
+ let i = Ie(t, e, n);
558
558
  return i !== null && r.push(i), i;
559
559
  };
560
560
  for (let t of e.getImportDeclarations()) if (o(t.getModuleSpecifierValue()), !t.isTypeOnly()) for (let e of t.getNamedImports()) e.isTypeOnly() || i.add(e.getName());
@@ -581,22 +581,22 @@ var re = () => {
581
581
  namedBindings: i,
582
582
  reExports: a
583
583
  };
584
- }, Ie = (e) => {
584
+ }, Re = (e) => {
585
585
  let t = /* @__PURE__ */ new Map();
586
586
  for (let n of e.getDescendantsOfKind(j.CallExpression)) {
587
587
  let e = n.getExpression(), r = k.isPropertyAccessExpression(e) ? e.getName() : e.getText();
588
588
  t.set(r, (t.get(r) ?? 0) + 1);
589
589
  }
590
590
  return t;
591
- }, Le = (e) => {
591
+ }, ze = (e) => {
592
592
  if (e.length <= 1) return e.length === 1;
593
593
  let t = [...e].sort((e, t) => e.length - t.length)[0];
594
594
  return e.every((e) => e.startsWith(t));
595
- }, Re = (e, t) => {
595
+ }, Be = (e, t) => {
596
596
  let n = b(t);
597
597
  return e.startsWith(`${n}${w}`) || b(e) === n;
598
- }, U = (e) => {
599
- let t = B(e);
598
+ }, V = (e) => {
599
+ let t = Ae(e);
600
600
  if (t.length === 0) return {
601
601
  findings: [],
602
602
  declared: 0,
@@ -627,7 +627,7 @@ var re = () => {
627
627
  } catch {
628
628
  continue;
629
629
  }
630
- s.set(t, je(r)), c.set(t, Fe(r, t, Ne(t, e, u))), l.set(t, Ie(r));
630
+ s.set(t, Ne(r)), c.set(t, Le(r, t, Fe(t, e, u))), l.set(t, Re(r));
631
631
  }
632
632
  let d = /* @__PURE__ */ new Map();
633
633
  for (let [e, t] of c) for (let n of t.localTargets) {
@@ -659,11 +659,11 @@ var re = () => {
659
659
  for (let e of r) {
660
660
  let t = R(e), n = s.get(e), r = c.get(e);
661
661
  if (n === void 0 || r === void 0) continue;
662
- let i = z(e);
662
+ let i = we(e);
663
663
  t !== null && !i && (m += 1);
664
664
  let u = d.get(e) ?? [];
665
665
  if ((t === "component" || t === "component.ui") && !i) {
666
- let t = Le(n.components);
666
+ let t = ze(n.components);
667
667
  (n.components.length === 0 || !t) && a.push({
668
668
  rule: "component/one-per-file",
669
669
  file: o(e),
@@ -701,7 +701,7 @@ var re = () => {
701
701
  ].join(", ")}`,
702
702
  fix: "a *.types.ts has NO runtime export. That is what makes importing it provably free AND makes it impossible for it to sit in a runtime import cycle — the guarantee is the point, so move the value to a sibling module"
703
703
  }), t === "internal") {
704
- let t = u.filter((t) => !Re(t, e));
704
+ let t = u.filter((t) => !Be(t, e));
705
705
  t.length > 0 && a.push({
706
706
  rule: "internal/foreign-import",
707
707
  file: o(e),
@@ -711,7 +711,7 @@ var re = () => {
711
711
  });
712
712
  }
713
713
  if (t === "fixture") {
714
- let t = u.filter((e) => !z(e) && R(e) !== "fixture");
714
+ let t = u.filter((e) => !we(e) && R(e) !== "fixture");
715
715
  t.length > 0 && a.push({
716
716
  rule: "fixture/production-import",
717
717
  file: o(e),
@@ -721,7 +721,7 @@ var re = () => {
721
721
  });
722
722
  }
723
723
  if (t === "component.ui" && !i) {
724
- let t = [...r.namedBindings].filter((e) => we.has(e)).sort();
724
+ let t = [...r.namedBindings].filter((e) => Te.has(e)).sort();
725
725
  t.length > 0 && a.push({
726
726
  rule: "ui/no-write",
727
727
  file: o(e),
@@ -756,7 +756,7 @@ var re = () => {
756
756
  detail: t === 0 ? "declares a store file but calls no defineStore()" : `declares ${t} stores`,
757
757
  fix: "a store file defines exactly ONE store — the SSR seed pass and devtools address stores by name, so two in a file makes both harder to find and neither easier to reason about"
758
758
  });
759
- let n = [...l.get(e)?.keys() ?? []].filter((e) => Ee.has(e)).sort();
759
+ let n = [...l.get(e)?.keys() ?? []].filter((e) => De.has(e)).sort();
760
760
  n.length > 0 && a.push({
761
761
  rule: "store/mirrors-server-state",
762
762
  file: o(e),
@@ -766,7 +766,7 @@ var re = () => {
766
766
  });
767
767
  }
768
768
  if (t !== "tracking" && !i) {
769
- let t = [...l.get(e)?.keys() ?? []].filter((e) => Te.has(e)).sort();
769
+ let t = [...l.get(e)?.keys() ?? []].filter((e) => Ee.has(e)).sort();
770
770
  t.length > 0 && a.push({
771
771
  rule: "tracking/outside-tracking-file",
772
772
  file: o(e),
@@ -798,8 +798,8 @@ var re = () => {
798
798
  scanned: r.length,
799
799
  vendored: n
800
800
  };
801
- }, ze = 10, Be = (e) => {
802
- let { findings: t, declared: n, scanned: r, vendored: i } = U(e);
801
+ }, Ve = 10, He = (e) => {
802
+ let { findings: t, declared: n, scanned: r, vendored: i } = V(e);
803
803
  console.log("");
804
804
  for (let t of Se(e, i)) console.log(`· not ours, exempt: ${t}`);
805
805
  if (r === 0) return console.log("• file-taxonomy scan: no sources here — nothing to check."), 0;
@@ -813,12 +813,12 @@ var re = () => {
813
813
  console.log(`• file taxonomy: ${t.length} finding(s) — ${o} error, ${t.length - o} advisory (${n} declared / ${r} scanned):`);
814
814
  for (let [e, t] of [...a].sort()) {
815
815
  console.log(` ${t[0].severity === "error" ? "✗" : "·"} ${e} (${t.length})`);
816
- for (let e of t.slice(0, ze)) console.log(` ${e.file} — ${e.detail}`);
817
- let n = t.length - ze;
816
+ for (let e of t.slice(0, Ve)) console.log(` ${e.file} — ${e.detail}`);
817
+ let n = t.length - Ve;
818
818
  n > 0 && console.log(` (+${n} more — \`voltro doctor --json\` prints every one.)`), console.log(` → ${t[0].fix}`);
819
819
  }
820
820
  return t.length;
821
- }, Ve = /* @__PURE__ */ new Set([
821
+ }, Ue = /* @__PURE__ */ new Set([
822
822
  "eq",
823
823
  "ne",
824
824
  "gt",
@@ -836,10 +836,10 @@ var re = () => {
836
836
  "endsWith",
837
837
  "between",
838
838
  "jsonField"
839
- ]), He = (e) => {
839
+ ]), We = (e) => {
840
840
  if (k.isPropertyAccessExpression(e)) return e.getName();
841
841
  if (k.isIdentifier(e)) return e.getText();
842
- }, Ue = (e) => {
842
+ }, Ge = (e) => {
843
843
  let t = e;
844
844
  for (; k.isCallExpression(t);) {
845
845
  let e = t.getExpression();
@@ -847,7 +847,7 @@ var re = () => {
847
847
  t = e.getExpression();
848
848
  }
849
849
  return t;
850
- }, We = (e, t, n) => {
850
+ }, Ke = (e, t, n) => {
851
851
  let r = new Map(t.map((e) => [e.name, new Set(e.columns)])), i = new Map(t.map((e) => [e.name, e.columns])), a = [], o = 0, s = new A({
852
852
  useInMemoryFileSystem: !0,
853
853
  skipAddingFilesFromTsConfig: !0
@@ -865,7 +865,7 @@ var re = () => {
865
865
  for (let t of e.getDescendantsOfKind(j.CallExpression)) {
866
866
  let e = t.getExpression();
867
867
  if (!k.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
868
- let n = He(Ue(e.getExpression()));
868
+ let n = We(Ge(e.getExpression()));
869
869
  if (n === void 0 || !r.has(n)) {
870
870
  let t = e.getExpression();
871
871
  if (k.isCallExpression(t)) {
@@ -888,7 +888,7 @@ var re = () => {
888
888
  else for (let e of [u, ...u.getDescendantsOfKind(j.CallExpression)]) {
889
889
  if (!k.isCallExpression(e)) continue;
890
890
  let t = e.getExpression(), n = k.isIdentifier(t) ? t.getText() : k.isPropertyAccessExpression(t) ? t.getName() : void 0;
891
- if (n === void 0 || !Ve.has(n)) continue;
891
+ if (n === void 0 || !Ue.has(n)) continue;
892
892
  let r = e.getArguments()[0];
893
893
  r !== void 0 && k.isStringLiteral(r) && l.push({
894
894
  column: r.getLiteralValue(),
@@ -909,16 +909,16 @@ var re = () => {
909
909
  issues: a,
910
910
  checked: o
911
911
  };
912
- }, W = 12, Ge = (e) => {
912
+ }, H = 12, qe = (e) => {
913
913
  if (e.checked === 0) return null;
914
914
  if (e.issues.length === 0) return `predicate columns: ${e.checked} checked — every filtered column exists on its table`;
915
915
  let t = [`predicate columns: ${e.issues.length} filter${e.issues.length, "s"} on a column that does not exist (${e.checked} checked)`];
916
916
  for (let n of e.issues) {
917
- let e = n.available.slice(0, W).join(", "), r = n.available.length > W ? `, … (${n.available.length - W} more)` : "";
917
+ let e = n.available.slice(0, H).join(", "), r = n.available.length > H ? `, … (${n.available.length - H} more)` : "";
918
918
  t.push(` ${n.file}:${n.line} '${n.table}' has no column '${n.column}'`), t.push(` columns: ${e}${r}`);
919
919
  }
920
920
  return t.push(" This type-checks today and fails at runtime as a bare SQL error."), t.join("\n");
921
- }, Ke = /* @__PURE__ */ new Set([
921
+ }, Je = /* @__PURE__ */ new Set([
922
922
  "id",
923
923
  "tenantId",
924
924
  "tenant_id",
@@ -938,7 +938,7 @@ var re = () => {
938
938
  "idempotency_id",
939
939
  "idempotencyKey",
940
940
  "idempotency_key"
941
- ]), qe = /* @__PURE__ */ new Set([
941
+ ]), Ye = /* @__PURE__ */ new Set([
942
942
  "id",
943
943
  "createdAt",
944
944
  "created_at",
@@ -948,10 +948,10 @@ var re = () => {
948
948
  "deleted_at",
949
949
  "tenantId",
950
950
  "tenant_id"
951
- ]), Je = (e) => Ke.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Ye = (e) => e.type === "reference", Xe = (e) => {
951
+ ]), Xe = (e) => Je.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Ze = (e) => e.type === "reference", Qe = (e) => {
952
952
  let t = [];
953
953
  for (let n of e) {
954
- let e = n.columns.filter((e) => Je(e.name)), r = e.filter((e) => !Ye(e)).map((e) => e.name);
954
+ let e = n.columns.filter((e) => Xe(e.name)), r = e.filter((e) => !Ze(e)).map((e) => e.name);
955
955
  if (r.length === 0) continue;
956
956
  let i = new Set(n.primaryKey), a = r.filter((e) => i.has(e));
957
957
  if (n.primaryKey.length >= 2 && a.length > 0) {
@@ -962,7 +962,7 @@ var re = () => {
962
962
  });
963
963
  continue;
964
964
  }
965
- if (e.some(Ye) && e.length >= 2) {
965
+ if (e.some(Ze) && e.length >= 2) {
966
966
  t.push({
967
967
  table: n.name,
968
968
  columns: r,
@@ -970,7 +970,7 @@ var re = () => {
970
970
  });
971
971
  continue;
972
972
  }
973
- e.length >= 2 && n.columns.filter((e) => !Je(e.name) && !qe.has(e.name)).length === 0 && t.push({
973
+ e.length >= 2 && n.columns.filter((e) => !Xe(e.name) && !Ye.has(e.name)).length === 0 && t.push({
974
974
  table: n.name,
975
975
  columns: r,
976
976
  signal: "link-only"
@@ -980,20 +980,20 @@ var re = () => {
980
980
  issues: t,
981
981
  tablesChecked: e.length
982
982
  };
983
- }, Ze = {
983
+ }, $e = {
984
984
  "composite-pk": "part of a composite primary key",
985
985
  "reference-sibling": "sits beside a wired reference() on this table",
986
986
  "link-only": "this table is nothing but link columns"
987
- }, Qe = (e) => {
987
+ }, et = (e) => {
988
988
  if (e.tablesChecked === 0) return null;
989
989
  if (e.issues.length === 0) return `junction FKs: ${e.tablesChecked} table${e.tablesChecked === 1 ? "" : "s"} checked — every link column is a wired reference()`;
990
990
  let t = e.issues.length, n = [`junction FKs: ${t} junction table${t === 1 ? "" : "s"} with an id column that is a plain text() and not a reference()`];
991
991
  for (let t of e.issues) {
992
992
  let e = t.columns.map((e) => `'${e}'`).join(", ");
993
- n.push(` '${t.table}': ${e} (${Ze[t.signal]})`);
993
+ n.push(` '${t.table}': ${e} (${$e[t.signal]})`);
994
994
  }
995
995
  return n.push(" Declare each as reference(() => <table>): the FK is enforced, the column is auto-indexed,"), n.push(" and the relationship becomes walkable (a plain text() id column is an invisible edge)."), n.join("\n");
996
- }, $e = /* @__PURE__ */ new Set(/* @__PURE__ */ "position.positions.move.moved.moving.cursor.pointer.mouse.drag.dragging.hover.frame.frames.tick.ticks.ping.heartbeat.typing.scroll.scrolling.zoom.pan.coord.coords.coordinate.coordinates.telemetry.sensor.sample.samples.viewport".split(".")), et = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((e) => e.length > 0).map((e) => e.toLowerCase()), tt = (e) => et(e).some((e) => $e.has(e)), nt = (e) => {
996
+ }, tt = /* @__PURE__ */ new Set(/* @__PURE__ */ "position.positions.move.moved.moving.cursor.pointer.mouse.drag.dragging.hover.frame.frames.tick.ticks.ping.heartbeat.typing.scroll.scrolling.zoom.pan.coord.coords.coordinate.coordinates.telemetry.sensor.sample.samples.viewport".split(".")), nt = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((e) => e.length > 0).map((e) => e.toLowerCase()), rt = (e) => nt(e).some((e) => tt.has(e)), it = (e) => {
997
997
  let t = [], n = [];
998
998
  for (let r of e) t.push({
999
999
  event: r.name,
@@ -1002,7 +1002,7 @@ var re = () => {
1002
1002
  event: r.name,
1003
1003
  kind: "wide-key",
1004
1004
  keyFieldCount: r.keyFieldCount
1005
- }), r.hasWebhook && tt(r.name) && n.push({
1005
+ }), r.hasWebhook && rt(r.name) && n.push({
1006
1006
  event: r.name,
1007
1007
  kind: "webhook-high-rate"
1008
1008
  });
@@ -1011,26 +1011,26 @@ var re = () => {
1011
1011
  modes: t,
1012
1012
  warnings: n
1013
1013
  };
1014
- }, rt = (e) => {
1014
+ }, at = (e) => {
1015
1015
  if (e.eventsChecked === 0) return null;
1016
1016
  let t = e.eventsChecked, n = [`event delivery: ${t} declared event${t === 1 ? "" : "s"} — the mode decides what a MISSING message means`];
1017
1017
  for (let t of e.modes) n.push(` '${t.event}': ${t.delivery}`);
1018
1018
  return n.push(" each — every delivery matters; a slow subscriber loses the oldest and is TOLD how many (the default)."), n.push(" latest — a newer delivery supersedes a pending one; a slow subscriber gets the current value, told nothing."), n.push(" Two events with identical route/subscriber/buffer numbers mean OPPOSITE things about a drop — the mode is the difference."), n.join("\n");
1019
- }, it = (e) => {
1019
+ }, ot = (e) => {
1020
1020
  if (e.warnings.length === 0) return null;
1021
1021
  let t = e.warnings.length, n = [`events that may not scale as declared: ${t} finding${t === 1 ? "" : "s"}`];
1022
1022
  for (let t of e.warnings) t.kind === "wide-key" ? (n.push(` '${t.event}': routing key has ${t.keyFieldCount ?? 0} fields — every field FRAGMENTS the subscriber set (distinct routes are the PRODUCT of the fields’ value spaces).`), n.push(" Check each is an ADDRESS the delivery is decided by (arenaId), not a discriminator the handler reads (gameType) — the latter is payload, not key.")) : (n.push(` '${t.event}': a webhook block on a per-frame-looking event — every publish is N HTTP deliveries per second per subscribed target.`), n.push(" The webhook rate limit DEFERS the excess as pending rows rather than failing, so the symptom is a growing table. Publish a coarser event (a summary / state change) for the outside world."));
1023
1023
  return n.push(" WARNING, not a fault — a producer may intend the fan-out. `voltro doctor --json` prints every finding."), n.join("\n");
1024
- }, at = /* @__PURE__ */ new Set([
1024
+ }, st = /* @__PURE__ */ new Set([
1025
1025
  "start",
1026
1026
  "run",
1027
1027
  "child"
1028
- ]), ot = (e) => k.isCallExpression(e) && (() => {
1028
+ ]), ct = (e) => k.isCallExpression(e) && (() => {
1029
1029
  let t = e.getExpression();
1030
- if (!k.isPropertyAccessExpression(t) || !at.has(t.getName())) return !1;
1030
+ if (!k.isPropertyAccessExpression(t) || !st.has(t.getName())) return !1;
1031
1031
  let n = t.getExpression();
1032
1032
  return k.isPropertyAccessExpression(n) ? n.getName() === "workflows" : k.isIdentifier(n) && n.getText() === "workflows";
1033
- })(), st = (e, t, n) => {
1033
+ })(), lt = (e, t, n) => {
1034
1034
  let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new A({
1035
1035
  useInMemoryFileSystem: !0,
1036
1036
  skipAddingFilesFromTsConfig: !0
@@ -1046,7 +1046,7 @@ var re = () => {
1046
1046
  }
1047
1047
  let u = S(n, t);
1048
1048
  for (let t of e.getDescendantsOfKind(j.CallExpression)) {
1049
- if (!ot(t)) continue;
1049
+ if (!ct(t)) continue;
1050
1050
  c++;
1051
1051
  let e = t.getStartLineNumber(), [n, l] = t.getArguments();
1052
1052
  if (n === void 0 || !k.isStringLiteral(n)) {
@@ -1130,7 +1130,7 @@ var re = () => {
1130
1130
  checked: s,
1131
1131
  callSites: c
1132
1132
  };
1133
- }, ct = (e) => {
1133
+ }, ut = (e) => {
1134
1134
  if (e.callSites === 0) return null;
1135
1135
  let t = [];
1136
1136
  t.push(`workflow starts: ${e.checked}/${e.callSites} call sites checked`);
@@ -1143,7 +1143,7 @@ var re = () => {
1143
1143
  for (let n of e.unchecked) t.push(` ${n.file}:${n.line} — ${n.reason}`);
1144
1144
  }
1145
1145
  return e.issues.length === 0 && e.unchecked.length === 0 && t.push(" every call site matches its workflow"), t.join("\n");
1146
- }, lt = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, G = "VOLTRO_FIELD_ENCRYPTION_KEY", ut = (e) => {
1146
+ }, dt = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, U = "VOLTRO_FIELD_ENCRYPTION_KEY", ft = (e) => {
1147
1147
  let t;
1148
1148
  for (let [n, r] of e.sources) if (t === void 0 && /\bfieldEncryption\b/.test(r) && (t = {
1149
1149
  reason: "fieldEncryption",
@@ -1151,13 +1151,13 @@ var re = () => {
1151
1151
  }), t === void 0 && /\.encrypted\s*\(/.test(r) && (t = {
1152
1152
  reason: "encrypted-column",
1153
1153
  file: n
1154
- }), r.includes(G)) return;
1154
+ }), r.includes(U)) return;
1155
1155
  if (t !== void 0) {
1156
- for (let [, t] of e.envFiles) if (t.includes(G)) return;
1156
+ for (let [, t] of e.envFiles) if (t.includes(U)) return;
1157
1157
  return t;
1158
1158
  }
1159
- }, dt = (e) => [
1160
- `✗ ${G} is named nowhere under this app root, and the app encrypts columns at rest`,
1159
+ }, pt = (e) => [
1160
+ `✗ ${U} is named nowhere under this app root, and the app encrypts columns at rest`,
1161
1161
  e.reason === "fieldEncryption" ? ` (\`fieldEncryption\` in ${e.file})` : ` (an \`.encrypted()\` column in ${e.file})`,
1162
1162
  "",
1163
1163
  " A deploy without it does not degrade — it refuses to boot at `onActivate`,",
@@ -1173,7 +1173,7 @@ var re = () => {
1173
1173
  " reaches your deploy target is not knowable from here — a secret manager",
1174
1174
  " or a CI variable is not a file. Naming it nowhere is conclusive; naming",
1175
1175
  " it somewhere is not a guarantee."
1176
- ].join("\n"), ft = (e) => e === "effect" || e.startsWith("@effect/") || e.startsWith("@voltro/") || e === "react" || e === "react-dom", pt = /(^|\/)(\.bin|\.cache|\.pnpm-store|dist|build)(\/|$)/, mt = (e) => {
1176
+ ].join("\n"), mt = (e) => e === "effect" || e.startsWith("@effect/") || e.startsWith("@voltro/") || e === "react" || e === "react-dom", ht = /(^|\/)(\.bin|\.cache|\.pnpm-store|dist|build)(\/|$)/, gt = (e) => {
1177
1177
  let t = [], n = e;
1178
1178
  for (let e = 0; e < 12; e++) {
1179
1179
  let e = x(n, "node_modules");
@@ -1183,14 +1183,14 @@ var re = () => {
1183
1183
  n = r;
1184
1184
  }
1185
1185
  return t;
1186
- }, ht = (e) => {
1186
+ }, _t = (e) => {
1187
1187
  try {
1188
1188
  let t = JSON.parse(E(x(e, "package.json"), "utf8"));
1189
1189
  return typeof t.version == "string" ? t.version : void 0;
1190
1190
  } catch {
1191
1191
  return;
1192
1192
  }
1193
- }, gt = (e) => {
1193
+ }, vt = (e) => {
1194
1194
  let t = [], n;
1195
1195
  try {
1196
1196
  n = D(e);
@@ -1198,7 +1198,7 @@ var re = () => {
1198
1198
  return t;
1199
1199
  }
1200
1200
  for (let r of n) {
1201
- if (r.startsWith(".") || pt.test(r)) continue;
1201
+ if (r.startsWith(".") || ht.test(r)) continue;
1202
1202
  let n = x(e, r);
1203
1203
  if (r.startsWith("@")) {
1204
1204
  let e;
@@ -1224,11 +1224,11 @@ var re = () => {
1224
1224
  });
1225
1225
  }
1226
1226
  return t;
1227
- }, _t = (e) => {
1227
+ }, yt = (e) => {
1228
1228
  let t = /* @__PURE__ */ new Map();
1229
- for (let n of mt(e)) for (let { pkg: e, dir: r } of gt(n)) {
1230
- if (!ft(e)) continue;
1231
- let n = ht(r);
1229
+ for (let n of gt(e)) for (let { pkg: e, dir: r } of vt(n)) {
1230
+ if (!mt(e)) continue;
1231
+ let n = _t(r);
1232
1232
  if (n === void 0) continue;
1233
1233
  let i = t.get(e) ?? /* @__PURE__ */ new Map();
1234
1234
  i.has(n) || i.set(n, r), t.set(e, i);
@@ -1240,7 +1240,7 @@ var re = () => {
1240
1240
  paths: [...i.values()].map((t) => S(e, t)).sort()
1241
1241
  });
1242
1242
  return n.sort((e, t) => e.pkg.localeCompare(t.pkg));
1243
- }, vt = (e) => {
1243
+ }, bt = (e) => {
1244
1244
  if (e.length !== 0) return [
1245
1245
  `• ${e.length} package(s) resolved at more than one version:`,
1246
1246
  ...e.flatMap((e) => [` ${e.pkg} — ${e.versions.join(", ")}`, ...e.paths.map((e) => ` ${e}`)]),
@@ -1258,49 +1258,68 @@ var re = () => {
1258
1258
  " instrument), then reinstall. Do not silence the generated file — it is where",
1259
1259
  " a genuine mistake in your own descriptors surfaces."
1260
1260
  ].join("\n");
1261
- }, yt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), bt = /* @__PURE__ */ new Set([
1261
+ }, xt = (e) => {
1262
+ if (e.catalogCodes.length === 0) return;
1263
+ let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
1264
+ if (n.length !== 0) return {
1265
+ orphaned: n,
1266
+ noneDeclared: t.size === 0
1267
+ };
1268
+ }, St = (e, t) => {
1269
+ let n = e.orphaned.join(", ");
1270
+ return e.noneDeclared ? [
1271
+ `⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
1272
+ " Nothing imports them: no provider is generated and `useT()` would not find a catalog.",
1273
+ " Either declare them in your app config:",
1274
+ "",
1275
+ ` locales: [${e.orphaned.map((e) => `'${e}'`).join(", ")}],`,
1276
+ "",
1277
+ " …or delete the files. A catalog that is never loaded looks from the inside",
1278
+ " exactly like one that works, which is why this is worth a line."
1279
+ ] : [`⚠ ${t} has catalog(s) not in \`locales:\` — ${n} — so they are never imported.`, " Add the code(s) to `locales:` in your app config, or delete the file(s)."];
1280
+ }, Ct = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), wt = /* @__PURE__ */ new Set([
1262
1281
  "requireScope",
1263
1282
  "requireAnyScope",
1264
1283
  "requireAuth",
1265
1284
  "assertScope",
1266
1285
  "assertCan",
1267
1286
  "can"
1268
- ]), xt = (e) => {
1287
+ ]), Tt = (e) => {
1269
1288
  let t = /* @__PURE__ */ new Set();
1270
- for (let n of e) for (let e of yt(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
1289
+ for (let n of e) for (let e of Ct(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
1271
1290
  return t;
1272
- }, St = (e, t) => {
1291
+ }, Et = (e, t) => {
1273
1292
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
1274
- }, Ct = (e) => {
1293
+ }, Dt = (e) => {
1275
1294
  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);
1276
1295
  return t && n;
1277
- }, wt = (e, t, n) => {
1296
+ }, Ot = (e, t, n) => {
1278
1297
  if (e.hasDeclaredGuards) return { kind: "declared-guard" };
1279
- let r = yt(e.src), i = St(r, t);
1280
- return i === void 0 ? Ct(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
1298
+ let r = Ct(e.src), i = Et(r, t);
1299
+ return i === void 0 ? Dt(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
1281
1300
  kind: "vocabulary-guard",
1282
1301
  name: i
1283
1302
  };
1284
- }, Tt = (e, t, n) => {
1303
+ }, kt = (e, t, n) => {
1285
1304
  let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
1286
1305
  return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
1287
- }, Et = (e, t) => {
1306
+ }, At = (e, t, n) => {
1288
1307
  switch (t.kind) {
1289
1308
  case "declared-guard": return "guards: on the descriptor";
1290
1309
  case "vocabulary-guard": return `calls ${t.name}()`;
1291
1310
  case "inline-ownership": return "compares the caller to the row and refuses — correct, but only this file knows it";
1292
1311
  case "allowlisted": return "accepted by a previous run";
1293
- case "unchecked": return e.op === "read" ? "returns a row the client selected, with nothing compared to the caller" : `${e.op}s${e.table ? ` ${e.table}` : ""} with nothing constraining WHICH row`;
1312
+ 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`;
1294
1313
  }
1295
- }, Dt = (e) => {
1314
+ }, jt = (e) => {
1296
1315
  let t = e.executors.map((t) => {
1297
- let n = wt(t, e.guardVocabulary, e.allowlist);
1316
+ let n = Ot(t, e.guardVocabulary, e.allowlist);
1298
1317
  return {
1299
1318
  tag: t.tag,
1300
1319
  path: t.path,
1301
1320
  verdict: n,
1302
- severity: Tt(t, e.tenantScopedTables, e.referencedTables),
1303
- why: Et(t, n)
1321
+ severity: kt(t, e.tenantScopedTables, e.referencedTables),
1322
+ why: At(t, n, e.rowFilterRegistered === !0)
1304
1323
  };
1305
1324
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
1306
1325
  return {
@@ -1314,26 +1333,100 @@ var re = () => {
1314
1333
  },
1315
1334
  unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
1316
1335
  };
1317
- }, Ot = (e) => [
1336
+ }, Mt = [
1318
1337
  "# voltro doctor — authz allowlist",
1319
1338
  "#",
1320
- "# Executors with no access check that a previous run accepted. This is DEBT,",
1321
- "# not approval: every line is an executor nobody has confirmed is safe.",
1339
+ "# Two KINDS of line, and the difference is the point:",
1340
+ "#",
1341
+ "# <tag> DEBT, not approval. Nobody has confirmed",
1342
+ "# this executor is safe; it was accepted so",
1343
+ "# the ratchet could start. Every one of",
1344
+ "# these is owed work.",
1345
+ "#",
1346
+ "# <tag> reviewed=<why> REVIEWED. A human read it and states why it",
1347
+ "# is genuinely open — constrained by something",
1348
+ "# the scanner cannot see. The reason is",
1349
+ "# REQUIRED: \"reviewed\" with no why is the",
1350
+ "# claim without the evidence, and doctor",
1351
+ "# refuses it.",
1322
1352
  "#",
1323
1353
  "# Doctor fails on any executor NOT listed here, so the list can only shrink",
1324
1354
  "# unless someone adds a line deliberately. Delete a line once its executor",
1325
1355
  "# has a guard — doctor reports it as guarded from then on, whether or not the",
1326
1356
  "# line is still here.",
1327
- "",
1328
- ...[...e].sort(),
1357
+ "#",
1358
+ "# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
1359
+ "# lines for executors that are newly unchecked.",
1329
1360
  ""
1330
- ].join("\n"), kt = (e) => new Set(e.split("\n").map((e) => e.trim()).filter((e) => e.length > 0 && !e.startsWith("#"))), At = (e) => {
1361
+ ], Nt = (e) => {
1362
+ let t = [...e].map((e) => typeof e == "string" ? { tag: e } : e), n = t.filter((e) => e.reviewed === void 0).map((e) => e.tag).sort(), r = t.filter((e) => e.reviewed !== void 0).sort((e, t) => e.tag.localeCompare(t.tag));
1363
+ return [
1364
+ ...Mt,
1365
+ ...n,
1366
+ ...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
1367
+ ...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
1368
+ ""
1369
+ ].join("\n");
1370
+ }, Pt = (e, t) => {
1371
+ let n = [...new Set(t)].sort();
1372
+ if (e === void 0) return Nt(n.map((e) => ({ tag: e })));
1373
+ let r = W(e).tags, i = n.filter((e) => !r.has(e));
1374
+ return i.length === 0 ? e : [
1375
+ e.replace(/\n+$/, ""),
1376
+ "",
1377
+ "# added by `voltro doctor --write-authz-allowlist` — newly unchecked executors.",
1378
+ "# Nothing above this line was touched.",
1379
+ ...i,
1380
+ ""
1381
+ ].join("\n");
1382
+ }, Ft = (e, t) => {
1383
+ let n = [...new Set(t)];
1384
+ if (e === void 0) return {
1385
+ added: n.length,
1386
+ kept: 0,
1387
+ removed: 0
1388
+ };
1389
+ let r = W(e).tags;
1390
+ return {
1391
+ added: n.filter((e) => !r.has(e)).length,
1392
+ kept: r.size,
1393
+ removed: 0
1394
+ };
1395
+ }, It = class extends Error {
1396
+ tag;
1397
+ constructor(e) {
1398
+ 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";
1399
+ }
1400
+ }, W = (e) => {
1401
+ let t = [];
1402
+ for (let n of e.split("\n")) {
1403
+ let e = n.trim();
1404
+ if (e.length === 0 || e.startsWith("#")) continue;
1405
+ let r = /^(\S+)(?:\s+reviewed=(.*))?$/.exec(e);
1406
+ if (!r) continue;
1407
+ let i = r[1];
1408
+ if (r[2] === void 0) {
1409
+ t.push({ tag: i });
1410
+ continue;
1411
+ }
1412
+ let a = r[2].trim();
1413
+ if (a.length === 0) throw new It(i);
1414
+ t.push({
1415
+ tag: i,
1416
+ reviewed: a
1417
+ });
1418
+ }
1419
+ return {
1420
+ tags: new Set(t.map((e) => e.tag)),
1421
+ entries: t
1422
+ };
1423
+ }, Lt = (e) => {
1331
1424
  let t = e.indexOf(".");
1332
1425
  return t <= 0 ? void 0 : e.slice(0, t);
1333
- }, jt = (e, t) => {
1426
+ }, Rt = (e, t) => {
1334
1427
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
1335
1428
  for (let e of t.rpcTags) {
1336
- let t = At(e);
1429
+ let t = Lt(e);
1337
1430
  if (t === void 0) continue;
1338
1431
  let n = i.get(t);
1339
1432
  n ? n.push(e) : i.set(t, [e]);
@@ -1362,7 +1455,7 @@ var re = () => {
1362
1455
  });
1363
1456
  continue;
1364
1457
  }
1365
- let s = new Set(o.rpcTags.map(At).filter((e) => e !== void 0));
1458
+ let s = new Set(o.rpcTags.map(Lt).filter((e) => e !== void 0));
1366
1459
  for (let e of s) {
1367
1460
  let t = i.get(e);
1368
1461
  t !== void 0 && a.push({
@@ -1375,12 +1468,12 @@ var re = () => {
1375
1468
  }
1376
1469
  }
1377
1470
  return a;
1378
- }, Mt = (e, t) => {
1471
+ }, zt = (e, t) => {
1379
1472
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
1380
1473
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
1381
1474
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
1382
1475
  return r;
1383
- }, Nt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Pt = (e) => {
1476
+ }, Bt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Vt = (e) => {
1384
1477
  let t = y(e), n = v(e);
1385
1478
  return {
1386
1479
  root: e,
@@ -1389,19 +1482,19 @@ var re = () => {
1389
1482
  apiBundle: n,
1390
1483
  apiBundlePresent: T(n)
1391
1484
  };
1392
- }, Ft = [
1485
+ }, Ht = [
1393
1486
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
1394
1487
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
1395
1488
  "(Generated Dockerfiles from `voltro create-project` already do this.)"
1396
- ].join("\n "), It = (e, t = !1) => {
1397
- let n = Pt(e);
1398
- if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Nt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
1489
+ ].join("\n "), Ut = (e, t = !1) => {
1490
+ let n = Vt(e);
1491
+ if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Bt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
1399
1492
  console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
1400
1493
  let r = process.env.NODE_ENV === "production";
1401
1494
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
1402
1495
  let i = t || r;
1403
- return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Ft}`), +!!i;
1404
- }, Lt = (e, t, n) => {
1496
+ return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Ht}`), +!!i;
1497
+ }, Wt = (e, t, n) => {
1405
1498
  let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
1406
1499
  `${e}.ts`,
1407
1500
  `${e}.tsx`,
@@ -1427,7 +1520,7 @@ var re = () => {
1427
1520
  r !== void 0 && o.push(r);
1428
1521
  }
1429
1522
  return o;
1430
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Ut.test(S(n, e))), l = new Set(c);
1523
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Xt.test(S(n, e))), l = new Set(c);
1431
1524
  for (; c.length > 0;) {
1432
1525
  let e = c.shift();
1433
1526
  if (!s.has(e)) {
@@ -1440,11 +1533,11 @@ var re = () => {
1440
1533
  let d = /* @__PURE__ */ new Set();
1441
1534
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
1442
1535
  return d;
1443
- }, Rt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), zt = (e, t) => (e.match(t) ?? []).length, K = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Bt = (e, t) => {
1536
+ }, Gt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Kt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, qt = (e, t) => {
1444
1537
  if (e.src.includes(t)) return !0;
1445
1538
  let n = t.replace(/^@voltro\/plugin-/, "");
1446
- return n !== t && K(e.file, RegExp(`^${n}Plugin$`)) > 0;
1447
- }, Vt = (e, t) => {
1539
+ return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
1540
+ }, Jt = (e, t) => {
1448
1541
  let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
1449
1542
  for (; (i = r.exec(e)) !== null;) {
1450
1543
  let t = i.index + i[0].length, r = 1, a = t;
@@ -1452,10 +1545,10 @@ var re = () => {
1452
1545
  n.push(e.slice(t, a - 1));
1453
1546
  }
1454
1547
  return n;
1455
- }, Ht = (e, t) => {
1548
+ }, Yt = (e, t) => {
1456
1549
  let n = t.getSymbol()?.getDeclarations()?.[0];
1457
1550
  return n === void 0 ? [] : e.getDescendantsOfKind(j.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
1458
- }, Ut = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Wt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Gt = [
1551
+ }, Xt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Zt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Qt = [
1459
1552
  "Id",
1460
1553
  "ID",
1461
1554
  "Hash",
@@ -1481,7 +1574,7 @@ var re = () => {
1481
1574
  "Comment",
1482
1575
  "Provider",
1483
1576
  "Format"
1484
- ], Kt = RegExp(`(?:${Gt.join("|")})$|_(?:${Gt.map((e) => e.toLowerCase()).join("|")})$`), qt = [
1577
+ ], $t = RegExp(`(?:${Qt.join("|")})$|_(?:${Qt.map((e) => e.toLowerCase()).join("|")})$`), en = [
1485
1578
  {
1486
1579
  id: "hardcoded-framework-cookie",
1487
1580
  scope: "client",
@@ -1494,7 +1587,7 @@ var re = () => {
1494
1587
  scope: "server",
1495
1588
  smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
1496
1589
  use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
1497
- match: ({ src: e, path: t }) => Wt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1590
+ match: ({ src: e, path: t }) => Zt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1498
1591
  },
1499
1592
  {
1500
1593
  id: "subject-write-no-guard",
@@ -1502,24 +1595,24 @@ var re = () => {
1502
1595
  smell: "writes ctx.request.subject.id into the database with no guard on the descriptor — an anonymous caller reaches the write and gets a raw statement failure instead of a typed refusal",
1503
1596
  use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
1504
1597
  match: ({ src: e, path: t }) => {
1505
- if (!Wt.test(t)) return !1;
1506
- let n = Rt(e);
1598
+ if (!Zt.test(t)) return !1;
1599
+ let n = Gt(e);
1507
1600
  return /\bsubject\s*\.\s*id\b/.test(n) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(n) && !/\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(n);
1508
1601
  }
1509
1602
  },
1510
1603
  {
1511
1604
  id: "raw-fetch",
1512
1605
  scope: "server",
1513
- smell: "raw fetch() for outbound HTTP on the server (no SSRF guard, no traceparent, no retry)",
1514
- use: "yield* HttpClient — the framework client. It blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop, injects traceparent, and honours `http.allowHosts` for deliberate internal targets",
1515
- match: ({ file: e, path: t, serverOnly: n }) => (Ut.test(t) || n) && K(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1606
+ smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
1607
+ use: "yield* HttpClient — the framework client. It injects traceparent, retries, and honours `http.allowHosts`. Where the URL is built from anything a caller supplied it ALSO blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop for a hard-coded host that half is redundant, and the first three are still the reason to switch",
1608
+ match: ({ file: e, path: t, serverOnly: n }) => (Xt.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1516
1609
  },
1517
1610
  {
1518
1611
  id: "form",
1519
1612
  scope: "client",
1520
1613
  smell: "per-field useState + a submit flag (hand-rolled form)",
1521
1614
  use: "useFormBinding — fields + validation from the mutation input Schema; server ValidationError({ field }) routes to the field",
1522
- match: ({ src: e, file: t }) => K(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
1615
+ match: ({ src: e, file: t }) => G(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
1523
1616
  },
1524
1617
  {
1525
1618
  id: "table",
@@ -1547,7 +1640,7 @@ var re = () => {
1547
1640
  scope: "client",
1548
1641
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
1549
1642
  use: "useDerived — dependency-tracked, referentially stable",
1550
- match: ({ src: e }) => zt(e, /\buseSubscription\s*[(<]/g) >= 3 && zt(e, /\buseMemo\s*\(/g) >= 3
1643
+ match: ({ src: e }) => Kt(e, /\buseSubscription\s*[(<]/g) >= 3 && Kt(e, /\buseMemo\s*\(/g) >= 3
1551
1644
  },
1552
1645
  {
1553
1646
  id: "next-shim",
@@ -1572,7 +1665,7 @@ var re = () => {
1572
1665
  match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
1573
1666
  let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
1574
1667
  return t === void 0 ? [] : k.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
1575
- }).some((t) => Ht(e, t).some((e) => {
1668
+ }).some((t) => Yt(e, t).some((e) => {
1576
1669
  let t = e.getParent();
1577
1670
  if (k.isBinaryExpression(t)) {
1578
1671
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -1600,7 +1693,7 @@ var re = () => {
1600
1693
  scope: "server",
1601
1694
  smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
1602
1695
  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)",
1603
- match: (e) => K(e.file, /\bstore\.query$/) >= 3 || K(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
1696
+ match: (e) => G(e.file, /\bstore\.query$/) >= 3 || G(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
1604
1697
  },
1605
1698
  {
1606
1699
  id: "store-effect-lift",
@@ -1623,7 +1716,17 @@ var re = () => {
1623
1716
  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)",
1624
1717
  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) => {
1625
1718
  let n = t[1];
1626
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Kt.test(n) || /^vault/i.test(n) ? !1 : !/\.encrypted\s*\(/.test(e);
1719
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || $t.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1720
+ }))
1721
+ },
1722
+ {
1723
+ id: "plaintext-secret-lookup-key",
1724
+ scope: "server",
1725
+ smell: "a credential-shaped column that is ALSO a unique lookup key",
1726
+ use: "you cannot look a value up by equality AND encrypt it non-deterministically — `.encrypted()` would make this column unfindable. Store the HASH as the unique lookup key and keep the secret itself either `.encrypted()` in a second column or nowhere (the framework's own `_voltro_api_keys.hashedKey` is the shape: the presented value is hashed and compared, never read back). A deterministic cipher is the other option and buys equality lookup at the cost of leaking which rows share a value",
1727
+ match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
1728
+ let n = t[1];
1729
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || $t.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1627
1730
  }))
1628
1731
  },
1629
1732
  {
@@ -1687,9 +1790,9 @@ var re = () => {
1687
1790
  scope: "server",
1688
1791
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
1689
1792
  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",
1690
- match: ({ file: e, testSources: t }) => K(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1691
- let t = Rt(e);
1692
- return Vt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1793
+ match: ({ file: e, testSources: t }) => G(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1794
+ let t = Gt(e);
1795
+ return Jt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1693
1796
  })
1694
1797
  },
1695
1798
  {
@@ -1699,15 +1802,15 @@ var re = () => {
1699
1802
  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",
1700
1803
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1701
1804
  }
1702
- ], Jt = (e) => J(e), Yt = (e, t) => {
1805
+ ], tn = (e) => q(e), nn = (e, t) => {
1703
1806
  let n = /* @__PURE__ */ new Set();
1704
1807
  for (let r of t) {
1705
1808
  let t = S(e, r), i = t.split(/[\\/]/)[0];
1706
1809
  n.add(i !== void 0 && i !== t ? i : ".");
1707
1810
  }
1708
1811
  return [...n].sort();
1709
- }, Xt = /\.(tsx?|jsx?)$/, q = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Zt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Qt = (e, t = [], n = 0) => {
1710
- if (n > 12 || q.test(e)) return t;
1812
+ }, rn = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, an = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, on = (e, t = [], n = 0) => {
1813
+ if (n > 12 || K.test(e)) return t;
1711
1814
  let r;
1712
1815
  try {
1713
1816
  r = D(e, { withFileTypes: !0 });
@@ -1718,12 +1821,12 @@ var re = () => {
1718
1821
  let r = x(e, i.name);
1719
1822
  if (i.isDirectory()) {
1720
1823
  if (i.name.startsWith(".")) continue;
1721
- Qt(r, t, n + 1);
1722
- } else lt.test(i.name) && t.push(r);
1824
+ on(r, t, n + 1);
1825
+ } else dt.test(i.name) && t.push(r);
1723
1826
  }
1724
1827
  return t;
1725
- }, J = (e, t = [], n = 0) => {
1726
- if (n > 12 || q.test(e)) return t;
1828
+ }, q = (e, t = [], n = 0) => {
1829
+ if (n > 12 || K.test(e)) return t;
1727
1830
  let r;
1728
1831
  try {
1729
1832
  r = D(e);
@@ -1737,11 +1840,11 @@ var re = () => {
1737
1840
  } catch {
1738
1841
  continue;
1739
1842
  }
1740
- a.isDirectory() ? J(r, t, n + 1) : Xt.test(i) && !/\.(test|spec)\./.test(i) && !Zt.test(i) && t.push(r);
1843
+ a.isDirectory() ? q(r, t, n + 1) : rn.test(i) && !/\.(test|spec)\./.test(i) && !an.test(i) && t.push(r);
1741
1844
  }
1742
1845
  return t;
1743
- }, $t = (e, t = [], n = 0) => {
1744
- if (n > 12 || q.test(e)) return t;
1846
+ }, sn = (e, t = [], n = 0) => {
1847
+ if (n > 12 || K.test(e)) return t;
1745
1848
  let r;
1746
1849
  try {
1747
1850
  r = D(e);
@@ -1755,17 +1858,17 @@ var re = () => {
1755
1858
  } catch {
1756
1859
  continue;
1757
1860
  }
1758
- a.isDirectory() ? $t(r, t, n + 1) : Xt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1861
+ a.isDirectory() ? sn(r, t, n + 1) : rn.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1759
1862
  }
1760
1863
  return t;
1761
- }, en = (e) => {
1762
- let t = Jt(e);
1864
+ }, cn = (e) => {
1865
+ let t = tn(e);
1763
1866
  return {
1764
- dirs: Yt(e, t),
1867
+ dirs: nn(e, t),
1765
1868
  fileCount: t.length
1766
1869
  };
1767
- }, tn = (e) => {
1768
- let t = Jt(e);
1870
+ }, ln = (e) => {
1871
+ let t = tn(e);
1769
1872
  if (t.length === 0) return [];
1770
1873
  let n = new A({
1771
1874
  useInMemoryFileSystem: !0,
@@ -1774,8 +1877,8 @@ var re = () => {
1774
1877
  for (let e of t) try {
1775
1878
  i.set(e, E(e, "utf8"));
1776
1879
  } catch {}
1777
- let a = Lt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1778
- for (let t of $t(e)) try {
1880
+ let a = Wt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1881
+ for (let t of sn(e)) try {
1779
1882
  o.set(t, E(t, "utf8"));
1780
1883
  } catch {}
1781
1884
  for (let s of t) {
@@ -1793,23 +1896,23 @@ var re = () => {
1793
1896
  } catch {
1794
1897
  continue;
1795
1898
  }
1796
- for (let t of qt) {
1797
- if (t.plugin !== void 0 && Bt(c, t.plugin) || !t.match(c)) continue;
1899
+ for (let t of en) {
1900
+ if (t.plugin !== void 0 && qt(c, t.plugin) || !t.match(c)) continue;
1798
1901
  let n = r.get(t.id) ?? [];
1799
1902
  n.push(S(e, s)), r.set(t.id, n);
1800
1903
  }
1801
1904
  }
1802
- return qt.filter((e) => r.has(e.id)).map((e) => ({
1905
+ return en.filter((e) => r.has(e.id)).map((e) => ({
1803
1906
  rule: e,
1804
1907
  files: r.get(e.id)
1805
1908
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1806
- }, nn = (e) => {
1807
- let t = en(e);
1909
+ }, un = (e) => {
1910
+ let t = cn(e);
1808
1911
  return {
1809
1912
  root: e,
1810
1913
  scannedFiles: t.fileCount,
1811
1914
  scannedDirs: t.dirs,
1812
- findings: tn(e).map(({ rule: e, files: t }) => ({
1915
+ findings: ln(e).map(({ rule: e, files: t }) => ({
1813
1916
  id: e.id,
1814
1917
  scope: e.scope,
1815
1918
  smell: e.smell,
@@ -1817,77 +1920,82 @@ var re = () => {
1817
1920
  files: t
1818
1921
  }))
1819
1922
  };
1820
- }, rn = 3, an = (e) => {
1923
+ }, dn = 3, fn = .2, pn = 50, mn = (e, t) => e >= pn && t > 0 && e / t >= fn, hn = (e) => {
1821
1924
  let t = /* @__PURE__ */ new Map();
1822
- for (let n of J(e)) try {
1925
+ for (let n of q(e)) try {
1823
1926
  t.set(S(e, n), E(n, "utf8"));
1824
1927
  } catch {}
1825
1928
  let n = /* @__PURE__ */ new Map();
1826
- for (let t of Qt(e)) try {
1929
+ for (let t of on(e)) try {
1827
1930
  n.set(S(e, t), E(t, "utf8"));
1828
1931
  } catch {}
1829
- let r = ut({
1932
+ let r = ft({
1830
1933
  sources: t,
1831
1934
  envFiles: n
1832
1935
  });
1833
- r !== void 0 && (console.log(""), console.log(dt(r)));
1834
- }, on = (e) => {
1835
- let t = nn(e);
1936
+ r !== void 0 && (console.log(""), console.log(pt(r)));
1937
+ }, gn = (e) => {
1938
+ let t = un(e);
1836
1939
  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;
1837
1940
  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;
1838
1941
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
1839
1942
  let n = 0;
1840
1943
  for (let e of ["server", "client"]) {
1841
1944
  let r = t.findings.filter((t) => t.scope === e);
1842
- if (r.length !== 0) {
1843
- console.log(` [${e}]`);
1844
- for (let e of r) {
1845
- let t = e.files.slice(0, rn).join(", "), r = e.files.length - rn;
1846
- r > 0 && (n += r);
1847
- let i = r > 0 ? `, +${r} more` : "";
1848
- console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${t}${i}`), console.log(` \u2192 ${e.use}`);
1945
+ if (r.length === 0) continue;
1946
+ console.log(` [${e}]`);
1947
+ let i = [...r].sort((e, t) => e.files.length - t.files.length);
1948
+ for (let e of i) {
1949
+ if (mn(e.files.length, t.scannedFiles)) {
1950
+ let n = Math.round(e.files.length / t.scannedFiles * 100);
1951
+ console.log(` ${e.smell} \u2014 ${e.files.length} file(s), ${n}% of those scanned: a codebase-wide pattern rather than a work list`), console.log(` \u2192 ${e.use}`), console.log(" (paths omitted on purpose at this scale — `voltro doctor --json` has every one.)");
1952
+ continue;
1849
1953
  }
1954
+ let r = e.files.slice(0, dn).join(", "), i = e.files.length - dn;
1955
+ i > 0 && (n += i);
1956
+ let a = i > 0 ? `, +${i} more` : "";
1957
+ console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1850
1958
  }
1851
1959
  }
1852
1960
  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;
1853
- }, sn = "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.", cn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, ln = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, un = (e) => {
1854
- let t = cn.exec(e)?.[1];
1961
+ }, _n = "renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.", vn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, yn = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, bn = (e) => {
1962
+ let t = vn.exec(e)?.[1];
1855
1963
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1856
- let n = ln.exec(e)?.[1];
1964
+ let n = yn.exec(e)?.[1];
1857
1965
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1858
- }, dn = async (e) => {
1859
- let n = x(e, "src", "pages");
1860
- if (!T(n)) return [];
1861
- let { pages: a, dirs: o } = await t(n), s = [];
1862
- for (let t of a) {
1863
- if (!i(o, t.dirChain).some((e) => e.hasLayout)) continue;
1966
+ }, xn = async (t) => {
1967
+ let i = x(t, "src", "pages");
1968
+ if (!T(i)) return [];
1969
+ let { pages: a, dirs: o } = await e(i), s = [];
1970
+ for (let e of a) {
1971
+ if (!r(o, e.dirChain).some((e) => e.hasLayout)) continue;
1864
1972
  let a;
1865
1973
  try {
1866
- a = E(x(n, t.file), "utf8");
1974
+ a = E(x(i, e.file), "utf8");
1867
1975
  } catch {
1868
1976
  continue;
1869
1977
  }
1870
- if (r.test(a)) continue;
1871
- let c = un(a);
1978
+ if (n.test(a)) continue;
1979
+ let c = bn(a);
1872
1980
  c !== null && s.push({
1873
- file: S(e, x(n, t.file)),
1874
- pattern: t.pattern,
1981
+ file: S(t, x(i, e.file)),
1982
+ pattern: e.pattern,
1875
1983
  currentMode: c
1876
1984
  });
1877
1985
  }
1878
1986
  return s;
1879
- }, fn = 10, pn = async (e) => {
1987
+ }, Sn = 10, Cn = async (e) => {
1880
1988
  if (console.log(""), !T(x(e, "src", "pages"))) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
1881
- let t = await dn(e);
1989
+ let t = await xn(e);
1882
1990
  if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1883
1991
  console.log(`• renderMode:'spa' candidates (${t.length} page${t.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1884
- for (let e of t.slice(0, fn)) {
1992
+ for (let e of t.slice(0, Sn)) {
1885
1993
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1886
1994
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1887
1995
  }
1888
- let n = t.length - fn;
1889
- return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${sn}`), t.length;
1890
- }, mn = /* @__PURE__ */ new Set([
1996
+ let n = t.length - Sn;
1997
+ return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${_n}`), t.length;
1998
+ }, wn = /* @__PURE__ */ new Set([
1891
1999
  "node_modules",
1892
2000
  "dist",
1893
2001
  "build",
@@ -1895,7 +2003,7 @@ var re = () => {
1895
2003
  ".vite",
1896
2004
  ".framework",
1897
2005
  ".git"
1898
- ]), hn = (e, t, n) => {
2006
+ ]), J = (e, t, n) => {
1899
2007
  let r;
1900
2008
  try {
1901
2009
  r = D(e, { withFileTypes: !0 });
@@ -1903,11 +2011,11 @@ var re = () => {
1903
2011
  return;
1904
2012
  }
1905
2013
  for (let i of r) {
1906
- if (mn.has(i.name)) continue;
2014
+ if (wn.has(i.name)) continue;
1907
2015
  let r = x(e, i.name);
1908
- i.isDirectory() ? hn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
2016
+ i.isDirectory() ? J(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
1909
2017
  }
1910
- }, gn = (e, t) => {
2018
+ }, Tn = (e, t) => {
1911
2019
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1912
2020
  for (let i of t) {
1913
2021
  let t;
@@ -1932,62 +2040,70 @@ var re = () => {
1932
2040
  }
1933
2041
  }
1934
2042
  return n;
1935
- }, _n = (e, t) => {
2043
+ }, En = (e, t) => {
1936
2044
  let n = e.slice(t.length).split(w).slice(0, -1)[0];
1937
2045
  return n !== void 0 && n.startsWith("[");
1938
- }, Y = (t) => {
1939
- let n = [], r = x(t, "src", "pages"), i = x(t, "src");
1940
- if (!T(i)) return n;
2046
+ }, Y = (e) => {
2047
+ let t = [], n = x(e, "src", "pages"), r = x(e, "src");
2048
+ if (!T(r)) return t;
1941
2049
  let a = [];
1942
- hn(i, t, a), a.sort();
1943
- let o = `${S(t, r)}${w}`;
1944
- gn(t, a);
1945
- for (let r of a) {
1946
- let i = r.slice(r.lastIndexOf(w) + 1);
1947
- if (r.startsWith(o)) {
1948
- if (i !== "page.tsx") continue;
1949
- T(x(t, e(r))) || n.push({
2050
+ J(r, e, a), a.sort();
2051
+ let o = `${S(e, n)}${w}`;
2052
+ Tn(e, a);
2053
+ for (let n of a) {
2054
+ let r = n.slice(n.lastIndexOf(w) + 1);
2055
+ if (n.startsWith(o)) {
2056
+ if (r !== "page.tsx") continue;
2057
+ T(x(e, i(n))) || t.push({
1950
2058
  kind: "page-without-test",
1951
- file: r
1952
- }), _n(r, o) && n.push({
2059
+ file: n
2060
+ });
2061
+ try {
2062
+ let r = E(x(e, n), "utf8");
2063
+ !/\bexport\s+(?:const|let|var|function)\s+meta\b/.test(r) && !/\bexport\s*\{[^}]*\bmeta\b/.test(r) && t.push({
2064
+ kind: "page-without-meta",
2065
+ file: n
2066
+ });
2067
+ } catch {}
2068
+ En(n, o) && t.push({
1953
2069
  kind: "top-level-dynamic-route",
1954
- file: r
2070
+ file: n
1955
2071
  });
1956
2072
  continue;
1957
2073
  }
1958
- i === "page.tsx" && n.push({
2074
+ r === "page.tsx" && t.push({
1959
2075
  kind: "suffixed-outside-pages",
1960
- file: r
2076
+ file: n
1961
2077
  });
1962
2078
  }
1963
- return n;
1964
- }, X = 10, Z = (e, t, n) => {
2079
+ return t;
2080
+ }, Dn = 10, X = (e, t, n) => {
1965
2081
  if (t.length === 0) return;
1966
2082
  console.log(`• ${e} (${t.length}):`);
1967
- for (let e of t.slice(0, X)) console.log(` ${e}`);
1968
- let r = t.length - X;
2083
+ for (let e of t.slice(0, Dn)) console.log(` ${e}`);
2084
+ let r = t.length - Dn;
1969
2085
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
1970
- }, vn = (e) => {
2086
+ }, On = (e) => {
1971
2087
  if (console.log(""), !T(x(e, "src", "pages"))) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
1972
2088
  let t = Y(e);
1973
2089
  if (t.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
1974
2090
  let n = (e) => t.filter((t) => t.kind === e).map((e) => e.file);
1975
- return Z("will NEVER route — page.tsx outside src/pages/", n("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), Z("pages with no test beside them", n("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), Z("routes whose FIRST segment is dynamic — they claim every URL of that depth", n("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), t.length;
1976
- }, yn = "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.", bn = /* @__PURE__ */ new Map(), Q = (e) => {
1977
- let t = bn.get(e);
2091
+ return X("will NEVER route — page.tsx outside src/pages/", n("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), X("pages with no test beside them", n("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), X("routes whose FIRST segment is dynamic — they claim every URL of that depth", n("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), X("pages with no `meta` export — they serve the APP name as their title", n("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). Advisory."), t.length;
2092
+ }, kn = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", An = /* @__PURE__ */ new Map(), Z = (e) => {
2093
+ let t = An.get(e);
1978
2094
  if (t !== void 0) return t;
1979
2095
  let n = (async () => {
1980
- let { walk: t, loadDiscovered: n } = await import("./dev-Cr-teYsz.js"), r = await t(e);
2096
+ let { walk: t, loadDiscovered: n } = await import("./dev-tPcOmjPJ.js"), r = await t(e);
1981
2097
  return {
1982
2098
  files: r,
1983
2099
  discovered: await n(r)
1984
2100
  };
1985
2101
  })();
1986
- return bn.set(e, n), n;
1987
- }, xn = async (e) => {
2102
+ return An.set(e, n), n;
2103
+ }, jn = async (e) => {
1988
2104
  let t;
1989
2105
  try {
1990
- let { files: n, discovered: r } = await Q(e), i = r.tables.map((e) => {
2106
+ let { files: n, discovered: r } = await Z(e), i = r.tables.map((e) => {
1991
2107
  let t = e;
1992
2108
  return {
1993
2109
  name: t.tableName,
@@ -1995,12 +2111,12 @@ var re = () => {
1995
2111
  };
1996
2112
  });
1997
2113
  if (i.length === 0) return;
1998
- t = Ge(We(n, i, e));
2114
+ t = qe(Ke(n, i, e));
1999
2115
  } catch {
2000
2116
  return;
2001
2117
  }
2002
2118
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
2003
- }, Sn = (e) => e.map((e) => {
2119
+ }, Mn = (e) => e.map((e) => {
2004
2120
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
2005
2121
  name: e,
2006
2122
  type: typeof t?.type == "string" ? t.type : ""
@@ -2010,22 +2126,22 @@ var re = () => {
2010
2126
  columns: n,
2011
2127
  primaryKey: t.appliedPrimaryKey ?? []
2012
2128
  };
2013
- }), Cn = async (e) => {
2129
+ }), Nn = async (e) => {
2014
2130
  try {
2015
- let { discovered: t } = await Q(e);
2016
- return Xe(Sn(t.tables));
2131
+ let { discovered: t } = await Z(e);
2132
+ return Qe(Mn(t.tables));
2017
2133
  } catch {
2018
2134
  return null;
2019
2135
  }
2020
- }, wn = async (e) => {
2021
- let t = await Cn(e);
2136
+ }, Pn = async (e) => {
2137
+ let t = await Nn(e);
2022
2138
  if (t === null || t.tablesChecked === 0) return;
2023
- let n = Qe(t);
2139
+ let n = et(t);
2024
2140
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
2025
- }, Tn = async (e) => {
2141
+ }, Fn = async (e) => {
2026
2142
  try {
2027
- let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await Q(e);
2028
- return nt(n.events.map((e) => {
2143
+ let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await Z(e);
2144
+ return it(n.events.map((e) => {
2029
2145
  let n = e.descriptor, r = t(n.key);
2030
2146
  return {
2031
2147
  name: n.name,
@@ -2037,19 +2153,19 @@ var re = () => {
2037
2153
  } catch {
2038
2154
  return null;
2039
2155
  }
2040
- }, En = async (e) => {
2041
- let t = await Tn(e);
2156
+ }, In = async (e) => {
2157
+ let t = await Fn(e);
2042
2158
  if (t === null || t.eventsChecked === 0) return;
2043
- let n = rt(t);
2159
+ let n = at(t);
2044
2160
  n !== null && (console.log(""), console.log(`• ${n}`));
2045
- let r = it(t);
2161
+ let r = ot(t);
2046
2162
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
2047
- }, Dn = (e) => {
2163
+ }, Ln = (e) => {
2048
2164
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
2049
2165
  return r === void 0 ? n : `${r}: ${n}`;
2050
- }, On = async (e) => {
2166
+ }, Rn = async (e) => {
2051
2167
  try {
2052
- let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await Q(e)).discovered.serverOnlyLeaks, r = t(n);
2168
+ let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await Z(e)).discovered.serverOnlyLeaks, r = t(n);
2053
2169
  return {
2054
2170
  status: {
2055
2171
  checked: !0,
@@ -2060,17 +2176,17 @@ var re = () => {
2060
2176
  } catch (e) {
2061
2177
  return { status: {
2062
2178
  checked: !1,
2063
- reason: Dn(e)
2179
+ reason: Ln(e)
2064
2180
  } };
2065
2181
  }
2066
- }, kn = async (e) => {
2067
- let { status: t, message: n } = await On(e);
2182
+ }, zn = async (e) => {
2183
+ let { status: t, message: n } = await Rn(e);
2068
2184
  return t.checked ? n === void 0 ? !1 : (console.log(""), console.log(`✗ [serverOnly] ${n}`), !0) : (console.log(""), console.log("• serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), console.log(" This audit is the automated check that a hash or a token cannot reach the wire, so"), console.log(" an app that leans on `.serverOnly()` should treat a persistent skip as a failure."), console.log(" `voltro doctor --json` reports it as `serverOnly.checked: false` for a CI gate."), !1);
2069
- }, An = async (e) => {
2185
+ }, Bn = async (e) => {
2070
2186
  let t;
2071
2187
  try {
2072
- let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await Q(e);
2073
- t = st(r, i.workflows.map((e) => {
2188
+ let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await Z(e);
2189
+ t = lt(r, i.workflows.map((e) => {
2074
2190
  let t = e.definition, r = t.payloadSchema === void 0 ? {
2075
2191
  required: [],
2076
2192
  optional: []
@@ -2084,21 +2200,46 @@ var re = () => {
2084
2200
  } catch {
2085
2201
  return;
2086
2202
  }
2087
- let n = ct(t);
2203
+ let n = ut(t);
2088
2204
  if (n === null) return;
2089
2205
  console.log("");
2090
2206
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
2091
2207
  console.log(`${r} ${n}`);
2092
- }, $ = "voltro-authz-allowlist.txt", jn = (e, t = (e) => {
2208
+ }, Q = "voltro-authz-allowlist.txt", Vn = (e, t = (e) => {
2093
2209
  try {
2094
2210
  return E(e, "utf8");
2095
2211
  } catch {
2096
2212
  return "";
2097
2213
  }
2098
- }) => /* @__PURE__ */ new Set([...bt, ...xt(J(e).map(t))]), Mn = async (e) => {
2099
- let { discovered: t } = await Q(e), n = [];
2214
+ }) => /* @__PURE__ */ new Set([...wt, ...Tt(q(e).map(t))]), Hn = async (e) => {
2215
+ let t = ["src/locales", "locales"], n, r = [];
2216
+ for (let i of t) {
2217
+ let t = x(e, i);
2218
+ if (T(t)) try {
2219
+ r = D(t).filter((e) => /\.tsx?$/.test(e) && !e.endsWith(".d.ts")).map((e) => e.replace(/\.tsx?$/, "")), n = i;
2220
+ break;
2221
+ } catch {}
2222
+ }
2223
+ if (n === void 0 || r.length === 0) return;
2224
+ let i = [];
2225
+ try {
2226
+ let { loadApiConfig: t } = await import("./dev-tPcOmjPJ.js");
2227
+ i = (await t(e))?.locales ?? [];
2228
+ } catch {
2229
+ return;
2230
+ }
2231
+ let a = xt({
2232
+ catalogCodes: r,
2233
+ declaredLocales: i
2234
+ });
2235
+ if (a !== void 0) {
2236
+ console.log("");
2237
+ for (let e of St(a, n)) console.log(e);
2238
+ }
2239
+ }, Un = async (e) => {
2240
+ let { discovered: t } = await Z(e), n = [];
2100
2241
  try {
2101
- let { loadApiConfig: t } = await import("./dev-Cr-teYsz.js");
2242
+ let { loadApiConfig: t } = await import("./dev-tPcOmjPJ.js");
2102
2243
  n = ((await t(e))?.plugins ?? []).map((e) => {
2103
2244
  let t = (e.extendSchema?.tables ?? []).map((e) => {
2104
2245
  let t = e;
@@ -2114,8 +2255,8 @@ var re = () => {
2114
2255
  plugin: e.name,
2115
2256
  tables: t,
2116
2257
  rpcTags: n,
2117
- canDisableTables: Nn.has(e.name),
2118
- canAlias: Pn.has(e.name)
2258
+ canDisableTables: Wn.has(e.name),
2259
+ canAlias: Gn.has(e.name)
2119
2260
  };
2120
2261
  });
2121
2262
  } catch {
@@ -2127,89 +2268,125 @@ var re = () => {
2127
2268
  ...t.mutations,
2128
2269
  ...t.actions,
2129
2270
  ...t.streams ?? []
2130
- ].map((e) => String(e.descriptor.name)), i = jt(n, {
2271
+ ].map((e) => String(e.descriptor.name)), i = Rt(n, {
2131
2272
  tables: t.tables.map((e) => e.tableName),
2132
2273
  rpcTags: r
2133
2274
  });
2134
2275
  console.log();
2135
- for (let e of Mt(n, i)) console.log(e);
2136
- }, Nn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Pn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), Fn = async (e, t) => {
2137
- let { discovered: n } = await Q(e), r = /* @__PURE__ */ new Map(), i = (e) => {
2138
- if (!r.has(e)) try {
2139
- r.set(e, E(e, "utf8"));
2276
+ for (let e of zt(n, i)) console.log(e);
2277
+ }, Wn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Gn = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Kn = async (e) => {
2278
+ let t = await qn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
2279
+ return {
2280
+ executors: t.executorCount,
2281
+ guardVocabulary: [...t.vocabulary].sort(),
2282
+ counts: t.result.counts,
2283
+ allowlist: {
2284
+ exists: t.existing !== void 0,
2285
+ debt: r,
2286
+ reviewed: t.result.counts.allowlisted - r
2287
+ },
2288
+ unchecked: t.result.unchecked
2289
+ };
2290
+ }, qn = async (e) => {
2291
+ let { discovered: t } = await Z(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2292
+ if (!n.has(e)) try {
2293
+ n.set(e, E(e, "utf8"));
2140
2294
  } catch {
2141
- r.set(e, "");
2295
+ n.set(e, "");
2142
2296
  }
2143
- return r.get(e);
2144
- }, a = jn(e, i), o = /* @__PURE__ */ new Set();
2145
- for (let e of n.tables) for (let t of Object.values(e.fields ?? {})) try {
2297
+ return n.get(e);
2298
+ }, i = Vn(e, r), a = /* @__PURE__ */ new Set();
2299
+ for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
2146
2300
  let e = t.references?.();
2147
- e && o.add(e.tableName);
2301
+ e && a.add(e.tableName);
2148
2302
  } catch {}
2149
- let s = [
2303
+ let o = [
2150
2304
  {
2151
- list: n.queries,
2305
+ list: t.queries,
2152
2306
  op: "read"
2153
2307
  },
2154
2308
  {
2155
- list: n.mutations,
2309
+ list: t.mutations,
2156
2310
  op: "update"
2157
2311
  },
2158
2312
  {
2159
- list: n.actions,
2313
+ list: t.actions,
2160
2314
  op: "update"
2161
2315
  },
2162
2316
  {
2163
- list: n.streams,
2317
+ list: t.streams,
2164
2318
  op: "read"
2165
2319
  }
2166
2320
  ].flatMap(({ list: e, op: t }) => e.map((e) => {
2167
- let n = e.descriptor, r = n.target, a = Array.isArray(r) ? r[0] : r, o = e.file.replace(/\.(tsx?)$/, ".server.$1");
2321
+ let n = e.descriptor, i = n.target, a = Array.isArray(i) ? i[0] : i, o = e.file.replace(/\.(tsx?)$/, ".server.$1");
2168
2322
  return {
2169
2323
  tag: String(n.name ?? e.file),
2170
2324
  path: o,
2171
- src: i(o),
2325
+ src: r(o),
2172
2326
  hasDeclaredGuards: Array.isArray(n.guards) && n.guards.length > 0,
2173
2327
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
2174
2328
  ...a?.table === void 0 ? {} : { table: a.table }
2175
2329
  };
2176
- })), c = x(e, $), l = T(c) ? kt(E(c, "utf8")) : void 0, u = Dt({
2177
- executors: s,
2178
- guardVocabulary: a,
2179
- tenantScopedTables: n.tenantScopedTables,
2180
- referencedTables: o,
2181
- allowlist: l ?? /* @__PURE__ */ new Set()
2182
- }), d = u.counts;
2183
- console.log(`\nauthz scan · ${s.length} executor(s)`);
2184
- let f = [...a].filter((e) => !bt.has(e)).sort();
2185
- if (console.log(f.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${f.length} from your source (${f.slice(0, 6).join(", ")}${f.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(d.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(d.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(d.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(d.vocabularyGuard).padStart(4)}`), console.log(` – accepted as recorded debt ${String(d.allowlisted).padStart(4)} (${$})`), t) return ne(c, Ot(u.unchecked.map((e) => e.tag)), "utf8"), console.log(`\nwrote ${u.unchecked.length} tag(s) to ${$}. Doctor now fails on ADDITIONS only.`), !1;
2186
- if (u.unchecked.length === 0) return !1;
2330
+ })), s = x(e, Q), c = T(s) ? W(E(s, "utf8")) : void 0, l = !1;
2331
+ for (let t of q(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
2332
+ l = !0;
2333
+ break;
2334
+ }
2335
+ let u = jt({
2336
+ executors: o,
2337
+ guardVocabulary: i,
2338
+ tenantScopedTables: t.tenantScopedTables,
2339
+ referencedTables: a,
2340
+ allowlist: c?.tags ?? /* @__PURE__ */ new Set(),
2341
+ rowFilterRegistered: l
2342
+ }), d = (c?.entries ?? []).filter((e) => e.reviewed !== void 0);
2343
+ return {
2344
+ result: u,
2345
+ vocabulary: i,
2346
+ rowFilterRegistered: l,
2347
+ existing: c,
2348
+ reviewed: d,
2349
+ executorCount: o.length,
2350
+ allowPath: s
2351
+ };
2352
+ }, Jn = async (e, t) => {
2353
+ let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await qn(e), l = n.counts;
2354
+ console.log(`\nauthz scan · ${o} executor(s)`);
2355
+ let u = [...r].filter((e) => !wt.has(e)).sort();
2356
+ console.log(u.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${u.length} from your source (${u.slice(0, 6).join(", ")}${u.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(l.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(l.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(l.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(l.vocabularyGuard).padStart(4)}`);
2357
+ let d = new Set(a.map((e) => e.tag)), f = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !d.has(e.tag)).length, p = l.allowlisted - f;
2358
+ if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Q})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
2359
+ let e = T(s) ? E(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ft(e, t);
2360
+ return ne(s, Pt(e, t), "utf8"), console.log(`\n${Q}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
2361
+ }
2362
+ if (n.unchecked.length === 0) return !1;
2187
2363
  console.log("");
2188
- for (let e of u.unchecked.slice(0, 20)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
2189
- return u.unchecked.length > 20 && console.log(` … and ${u.unchecked.length - 20} more`), l === void 0 ? (console.log(`\nNo ${$} 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${u.unchecked.length} executor(s) with no access check are not in ${$}.`), 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);
2190
- }, In = async (e) => {
2191
- if (e.includes("--help") || e.includes("-h")) return console.log(yn), 0;
2364
+ for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
2365
+ return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Q} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Q}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
2366
+ }, Yn = async (e) => {
2367
+ if (e.includes("--help") || e.includes("-h")) return console.log(kn), 0;
2192
2368
  let t = e.includes("--strict"), n = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
2193
2369
  if (ie(e)) return re(), M({
2194
- ...nn(n),
2195
- spaCandidates: await dn(n),
2370
+ ...un(n),
2371
+ spaCandidates: await xn(n),
2196
2372
  pageConvention: Y(n),
2197
- fileTaxonomy: U(n).findings,
2198
- junctionFks: (await Cn(n))?.issues ?? [],
2199
- eventDelivery: await Tn(n),
2200
- duplicateInstances: _t(n),
2201
- serverOnly: (await On(n)).status
2373
+ fileTaxonomy: V(n).findings,
2374
+ junctionFks: (await Nn(n))?.issues ?? [],
2375
+ eventDelivery: await Fn(n),
2376
+ duplicateInstances: yt(n),
2377
+ serverOnly: (await Rn(n)).status,
2378
+ authz: await Kn(n)
2202
2379
  }), 0;
2203
- let r = It(n, t);
2204
- on(n), an(n), vn(n), Be(n), await pn(n), await An(n), await xn(n), await wn(n), await En(n), await Mn(n);
2205
- let i = await kn(n), a = await Fn(n, e.includes("--write-authz-allowlist")), o = vt(_t(n));
2380
+ let r = Ut(n, t);
2381
+ gn(n), hn(n), On(n), He(n), await Cn(n), await Bn(n), await jn(n), await Pn(n), await In(n), await Un(n), await Hn(n);
2382
+ let i = await zn(n), a = await Jn(n, e.includes("--write-authz-allowlist")), o = bt(yt(n));
2206
2383
  return o !== void 0 && console.log(`\n${o}`), i || a ? Math.max(r, 1) : r;
2207
- }, Ln = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Rn = /* @__PURE__ */ new Set([
2384
+ }, Xn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Zn = /* @__PURE__ */ new Set([
2208
2385
  "insert",
2209
2386
  "update",
2210
2387
  "delete",
2211
2388
  "upsert"
2212
- ]), zn = (e, t) => {
2389
+ ]), Qn = (e, t) => {
2213
2390
  let n = [], r = new Set(t.exercised), i = /* @__PURE__ */ new Map();
2214
2391
  for (let e of t.edges) {
2215
2392
  let t = i.get(e.tag);
@@ -2221,8 +2398,8 @@ var re = () => {
2221
2398
  a.push(t.tag);
2222
2399
  continue;
2223
2400
  }
2224
- let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(Ln(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
2225
- for (let t of c) if (Rn.has(t.op)) {
2401
+ let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(Xn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
2402
+ for (let t of c) if (Zn.has(t.op)) {
2226
2403
  let r = u.get(t.table);
2227
2404
  r === void 0 ? n.push({
2228
2405
  rule: "observed/undeclared-write",
@@ -2278,7 +2455,7 @@ var re = () => {
2278
2455
  total: e.length
2279
2456
  }
2280
2457
  };
2281
- }, Bn = (e, t = [], n = 0) => {
2458
+ }, $n = (e, t = [], n = 0) => {
2282
2459
  if (n > 12) return t;
2283
2460
  let r;
2284
2461
  try {
@@ -2302,24 +2479,24 @@ var re = () => {
2302
2479
  } catch {
2303
2480
  continue;
2304
2481
  }
2305
- o.isDirectory() ? Bn(r, t, n + 1) : o.isFile() && a.test(i) && t.push(r);
2482
+ o.isDirectory() ? $n(r, t, n + 1) : o.isFile() && a.test(i) && t.push(r);
2306
2483
  }
2307
2484
  return t;
2308
- }, Vn = /* @__PURE__ */ new Set(["actors", "tenants"]), Hn = async (e) => {
2485
+ }, er = /* @__PURE__ */ new Set(["actors", "tenants"]), tr = async (e) => {
2309
2486
  try {
2310
2487
  let { readFile: t } = await import("node:fs/promises"), { join: n } = await import("node:path"), r = await t(n(e, "app.graph.observed.generated.json"), "utf8"), i = JSON.parse(r);
2311
2488
  return i?.version !== 1 || !Array.isArray(i.edges) || !Array.isArray(i.exercised) ? null : i;
2312
2489
  } catch {
2313
2490
  return null;
2314
2491
  }
2315
- }, Un = (e) => {
2492
+ }, nr = (e) => {
2316
2493
  let { exercised: t, total: n } = e.coverage, r = `\nobserved: ${t}/${n} procedures exercised (${n === 0 ? 0 : Math.round(t / n * 100)}%)`;
2317
2494
  if (e.diagnostics.length === 0) return `${r}\n no declared/observed mismatches${e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : ""}`;
2318
2495
  let i = e.diagnostics.map((e) => ` ${e.node}${e.file === void 0 ? "" : ` (${e.file})`}\n` + e.breaks.map((e) => ` ${e}`).join("\n") + `\n fix: ${e.fix}`), a = e.unexercised.length > 0 ? `\n ${e.unexercised.length} unexercised — no recording, so nothing is claimed about them` : "";
2319
2496
  return `${r}\n${i.join("\n")}${a}`;
2320
- }, Wn = (e, t) => ({
2497
+ }, rr = (e, t) => ({
2321
2498
  tables: (t.tables ?? []).map((e) => e.name),
2322
- frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || Vn.has(e.name)).map((e) => e.name),
2499
+ frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || er.has(e.name)).map((e) => e.name),
2323
2500
  scopes: e.scopes ?? [],
2324
2501
  procedures: (e.procedures ?? []).map((e) => {
2325
2502
  let t = (e.guards ?? []).flatMap((e) => e.kind === "scope" ? e.scope ?? [] : []);
@@ -2332,16 +2509,16 @@ var re = () => {
2332
2509
  ...e.guards === void 0 ? {} : { hasGuards: e.guards.length > 0 }
2333
2510
  };
2334
2511
  })
2335
- }), Gn = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
2512
+ }), ir = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
2336
2513
  let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
2337
2514
  return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
2338
- }).join("\n"), Kn = async (e) => {
2339
- let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-Cr-teYsz.js"), { buildRpcEntry: i } = await import("./manifestBuild-Dj8Jjoto.js"), { buildCapabilityManifest: a } = await import("./inspect-mmBuRXmy.js"), { relative: o } = await import("node:path"), s = await n(await t(e)), c = (t, n, r) => i(t, n, o(e, r)), l = [
2515
+ }).join("\n"), ar = async (e) => {
2516
+ let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-tPcOmjPJ.js"), { buildRpcEntry: i } = await import("./manifestBuild-Dj8Jjoto.js"), { buildCapabilityManifest: a } = await import("./inspect-mmBuRXmy.js"), { relative: o } = await import("node:path"), s = await n(await t(e)), c = (t, n, r) => i(t, n, o(e, r)), l = [
2340
2517
  ...s.queries.map((e) => c(e.descriptor, "query", e.file)),
2341
2518
  ...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
2342
2519
  ...s.actions.map((e) => c(e.descriptor, "action", e.file)),
2343
2520
  ...s.streams.map((e) => c(e.descriptor, "stream", e.file))
2344
- ], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-DU0aOxjL.js"), d = await u({ root: e }).catch(() => []), f = new Set(s.tables.map((e) => e.tableName)), p = [...[...f].map((e) => ({ name: e })), ...d.map((e) => e.tableName).filter((e) => !f.has(e)).map((e) => ({
2521
+ ], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-D-EebUQX.js"), d = await u({ root: e }).catch((e) => (console.warn(`\n⚠ plugin tables could not be collected — this report is INCOMPLETE.\n ${e instanceof Error ? e.message.split("\n")[0] : String(e)}\n`), [])), f = new Set(s.tables.map((e) => e.tableName)), p = [...[...f].map((e) => ({ name: e })), ...d.map((e) => e.tableName).filter((e) => !f.has(e)).map((e) => ({
2345
2522
  name: e,
2346
2523
  framework: !0
2347
2524
  }))], m = await r(e).then((e) => e?.plugins ?? []).catch(() => []);
@@ -2352,7 +2529,7 @@ var re = () => {
2352
2529
  })), [], m),
2353
2530
  tables: { tables: p }
2354
2531
  };
2355
- }, qn = async (e) => {
2532
+ }, or = async (e) => {
2356
2533
  let t = ue(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
2357
2534
  if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
2358
2535
  let { process: n, target: r, rest: i } = de(t.rest), a;
@@ -2376,7 +2553,7 @@ var re = () => {
2376
2553
  if (l.length === 0) {
2377
2554
  if (!c && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
2378
2555
  try {
2379
- let e = await Kn(C(s));
2556
+ let e = await ar(C(s));
2380
2557
  u = e.manifest, d = e.tables;
2381
2558
  } catch (e) {
2382
2559
  return console.error(`voltro check: could not assemble the graph from source: ${e.message}`), console.error(" Start `voltro dev` to check against a running api instead."), 1;
@@ -2386,7 +2563,7 @@ var re = () => {
2386
2563
  if (t.kind === "err") return console.error(`voltro check: manifest fetch failed: ${t.error}`), 1;
2387
2564
  u = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
2388
2565
  }
2389
- let f = Wn(u, d), p = he(f, a), m = [...U(s).findings.map((e) => ({
2566
+ let f = rr(u, d), p = he(f, a), m = [...V(s).findings.map((e) => ({
2390
2567
  rule: e.rule,
2391
2568
  node: e.file,
2392
2569
  file: e.file,
@@ -2400,7 +2577,7 @@ var re = () => {
2400
2577
  breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
2401
2578
  fix: e.kind === "suffixed-outside-pages" ? "move it under src/pages/ into the directory whose route it serves, or rename it — page.tsx is reserved" : "write the test with the file — the name is derivable",
2402
2579
  severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
2403
- }))], h = (await o(s, Bn(s))).map((e) => ({
2580
+ }))], h = (await o(s, $n(s))).map((e) => ({
2404
2581
  rule: "client/not-browser-safe",
2405
2582
  node: e.file,
2406
2583
  file: e.file,
@@ -2411,7 +2588,7 @@ var re = () => {
2411
2588
  ...p.diagnostics,
2412
2589
  ...m,
2413
2590
  ...h
2414
- ], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await Hn(C(s)), y = v === null ? null : zn(f.procedures, v);
2591
+ ], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await tr(C(s)), y = v === null ? null : Qn(f.procedures, v);
2415
2592
  return t.format === "json" ? M(y === null ? {
2416
2593
  ...p,
2417
2594
  ok: _,
@@ -2421,7 +2598,7 @@ var re = () => {
2421
2598
  ok: _,
2422
2599
  diagnostics: g,
2423
2600
  observed: y
2424
- }) : (console.log(Gn(g)), y !== null && console.log(Un(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
2601
+ }) : (console.log(ir(g)), y !== null && console.log(nr(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
2425
2602
  };
2426
2603
  //#endregion
2427
- export { re as _, Le as a, I as c, N as d, ue as f, le as g, ce as h, It as i, ve as l, se as m, qn as n, be as o, de as p, In as r, xe as s, Kn as t, he as u, M as v, ie as y };
2604
+ export { re as _, ze as a, I as c, N as d, ue as f, le as g, ce as h, Ut as i, ve as l, se as m, or as n, be as o, de as p, Yn as r, xe as s, ar as t, he as u, M as v, ie as y };