@voltro/cli 0.30.0 → 0.30.1

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 (78) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/apiBuild-Ca6Sq5jy.js +2 -0
  3. package/dist/{apiBuild-BceikpQf.js → apiBuild-DmkzRyv7.js} +2 -2
  4. package/dist/bin.js +3 -3
  5. package/dist/{checkCommand-BMr-X48w.js → checkCommand-C4konUy9.js} +422 -298
  6. package/dist/checkCommand-DHD0aBeR.js +2 -0
  7. package/dist/{commands-D4Y1-Nlu.js → commands-BkJft9K2.js} +1609 -1607
  8. package/dist/{dbCommand-C7hbl5pk.js → dbCommand-qn__kLEt.js} +1 -1
  9. package/dist/dbCommand-wfGi3FgG.js +2 -0
  10. package/dist/{dev-DmnkH-wr.js → dev-C_NPcLDp.js} +1266 -1250
  11. package/dist/{dev-Cr-teYsz.js → dev-DXhhZ7-f.js} +1 -1
  12. package/dist/frameworkTableAssembly-Bn_c4Mnl.js +2 -0
  13. package/dist/{frameworkTableAssembly-BzAajuUv.js → frameworkTableAssembly-Cpgn2e88.js} +128 -130
  14. package/dist/index.js +1 -1
  15. package/dist/{inspectMetrics-BeJdP_3b.js → inspectMetrics-CkB2J0Zn.js} +2 -0
  16. package/dist/{sdkgen-CqpOq_fy.js → sdkgen-wsWLrq4l.js} +1 -1
  17. package/dist/{serveCommand-Ciq_wIY8.js → serveCommand-HtI4X5kj.js} +14 -14
  18. package/dist/serveEntry.js +2 -2
  19. package/dist/{start-ocqB1seX.js → start-egkbpugc.js} +177 -175
  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 +27 -0
  25. package/templates/agent-docs/routing.md +14 -0
  26. package/templates/agent-docs/whats-new.md +36 -342
  27. package/templates/agent-docs/workflows.md +2 -0
  28. package/templates/apps/api-ai/package.json +7 -7
  29. package/templates/apps/api-auth/package.json +8 -8
  30. package/templates/apps/api-backend/package.json +7 -7
  31. package/templates/apps/api-backend-deactivation/package.json +7 -7
  32. package/templates/apps/api-backend-mail/package.json +8 -8
  33. package/templates/apps/api-backend-mariadb/package.json +9 -9
  34. package/templates/apps/api-backend-sqlite/package.json +8 -8
  35. package/templates/apps/api-backend-storage/package.json +8 -8
  36. package/templates/apps/api-cms/package.json +10 -10
  37. package/templates/apps/api-collab/package.json +8 -8
  38. package/templates/apps/api-data-advanced/package.json +8 -8
  39. package/templates/apps/api-durable/package.json +8 -8
  40. package/templates/apps/api-feature-flags/package.json +9 -9
  41. package/templates/apps/api-governance/package.json +8 -8
  42. package/templates/apps/api-kv/package.json +8 -8
  43. package/templates/apps/api-moderation/package.json +8 -8
  44. package/templates/apps/api-observability/package.json +8 -8
  45. package/templates/apps/api-ratelimit/package.json +8 -8
  46. package/templates/apps/api-rbac/package.json +8 -8
  47. package/templates/apps/api-rest/package.json +7 -7
  48. package/templates/apps/api-saas/package.json +11 -11
  49. package/templates/apps/api-saas-starter/package.json +10 -10
  50. package/templates/apps/api-search/package.json +8 -8
  51. package/templates/apps/api-status/package.json +8 -8
  52. package/templates/apps/api-versioning/package.json +8 -8
  53. package/templates/apps/api-webhooks/package.json +9 -9
  54. package/templates/apps/changelog/package.json +6 -6
  55. package/templates/apps/edge-functions/package.json +2 -2
  56. package/templates/apps/frontend-admin/package.json +8 -8
  57. package/templates/apps/frontend-app/package.json +8 -8
  58. package/templates/apps/frontend-auth/package.json +8 -8
  59. package/templates/apps/frontend-blank/package.json +7 -7
  60. package/templates/apps/frontend-cms/package.json +9 -9
  61. package/templates/apps/frontend-collab/package.json +9 -9
  62. package/templates/apps/frontend-contact/package.json +7 -7
  63. package/templates/apps/frontend-dashboard/package.json +7 -7
  64. package/templates/apps/frontend-docs/package.json +7 -7
  65. package/templates/apps/frontend-i18n/package.json +6 -6
  66. package/templates/apps/frontend-landing/package.json +7 -7
  67. package/templates/apps/frontend-portal/package.json +8 -8
  68. package/templates/apps/frontend-saas/package.json +8 -8
  69. package/templates/apps/frontend-spa/package.json +7 -7
  70. package/templates/apps/frontend-ssr/package.json +7 -7
  71. package/templates/apps/frontend-ssr-api/package.json +8 -8
  72. package/templates/apps/frontend-static-blog/package.json +6 -6
  73. package/templates/apps/frontend-status/package.json +8 -8
  74. package/templates/apps/mobile-app/package.json +2 -2
  75. package/dist/apiBuild-CxvR1Dwt.js +0 -2
  76. package/dist/checkCommand-BOk9y264.js +0 -2
  77. package/dist/dbCommand-D8nhyppR.js +0 -2
  78. 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 { R as e, f as t, ht as n, n as r, o as i } from "./inspectMetrics-CkB2J0Zn.js";
2
+ import { Ut as a, Wt as o } from "./dev-C_NPcLDp.js";
3
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-DmkzRyv7.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";
@@ -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?$/, z = (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() ? z(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
+ }, Ae = /\b(?:FC|FunctionComponent|ComponentType|ForwardRefExoticComponent|MemoExoticComponent)\b/, je = /\b(?:memo|forwardRef|observer|styled)\b/, B = (e) => k.isArrowFunction(e) || k.isFunctionExpression(e) || k.isFunctionDeclaration(e) || k.isClassDeclaration(e) || k.isClassExpression(e), V = (e) => {
491
+ if (B(e)) return !0;
492
+ if (k.isExportAssignment(e) || k.isAsExpression(e) || k.isSatisfiesExpression(e) || k.isParenthesizedExpression(e)) return V(e.getExpression());
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 && Ae.test(t.getText())) return !0;
496
496
  let n = e.getInitializer();
497
- return n === void 0 || H(n);
497
+ return n === void 0 || V(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) ? je.test(e.getExpression().getText()) ? !0 : e.getArguments().some((e) => B(e) || k.isIdentifier(e)) : k.isTaggedTemplateExpression(e) ? !0 : !(k.isObjectLiteralExpression(e) || k.isArrayLiteralExpression(e) || k.isStringLiteral(e) || k.isNoSubstitutionTemplateLiteral(e) || k.isNumericLiteral(e) || k.isTemplateExpression(e) || k.isNewExpression(e) || k.isPropertyAccessExpression(e) || e.getKind() === j.TrueKeyword || e.getKind() === j.FalseKeyword || e.getKind() === j.NullKeyword);
500
+ }, Me = (e) => {
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) && V(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
+ }, Ne = [], Pe = (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, Ne);
541
+ return Ne;
542
+ }, Fe = (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
+ }, Ie = (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 = Fe(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
+ }, Le = (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
+ }, Re = (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
+ }, ze = (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
+ }, H = (e) => {
599
+ let t = z(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, Me(r)), c.set(t, Ie(r, t, Pe(t, e, u))), l.set(t, Le(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 = Re(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) => !ze(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
+ }, Be = 10, Ve = (e) => {
802
+ let { findings: t, declared: n, scanned: r, vendored: i } = H(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, Be)) console.log(` ${e.file} — ${e.detail}`);
817
+ let n = t.length - Be;
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
+ }, He = /* @__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
+ ]), Ue = (e) => {
840
840
  if (k.isPropertyAccessExpression(e)) return e.getName();
841
841
  if (k.isIdentifier(e)) return e.getText();
842
- }, Ue = (e) => {
842
+ }, We = (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
+ }, Ge = (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 = Ue(We(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 || !He.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
+ }, U = 12, Ke = (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, U).join(", "), r = n.available.length > U ? `, … (${n.available.length - U} 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
+ }, qe = /* @__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
+ ]), Je = /* @__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
+ ]), Ye = (e) => qe.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), Xe = (e) => e.type === "reference", Ze = (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) => Ye(e.name)), r = e.filter((e) => !Xe(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(Xe) && 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) => !Ye(e.name) && !Je.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
+ }, Qe = {
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
+ }, $e = (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} (${Qe[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
+ }, et = /* @__PURE__ */ new Set(/* @__PURE__ */ "position.positions.move.moved.moving.cursor.pointer.mouse.drag.dragging.hover.frame.frames.tick.ticks.ping.heartbeat.typing.scroll.scrolling.zoom.pan.coord.coords.coordinate.coordinates.telemetry.sensor.sample.samples.viewport".split(".")), tt = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((e) => e.length > 0).map((e) => e.toLowerCase()), nt = (e) => tt(e).some((e) => et.has(e)), rt = (e) => {
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 && nt(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
+ }, it = (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
+ }, at = (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
+ }, ot = /* @__PURE__ */ new Set([
1025
1025
  "start",
1026
1026
  "run",
1027
1027
  "child"
1028
- ]), ot = (e) => k.isCallExpression(e) && (() => {
1028
+ ]), st = (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) || !ot.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
+ })(), ct = (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 (!st(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
+ }, lt = (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
+ }, ut = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, W = "VOLTRO_FIELD_ENCRYPTION_KEY", dt = (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(W)) 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(W)) 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
+ }, ft = (e) => [
1160
+ `✗ ${W} 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"), pt = (e) => e === "effect" || e.startsWith("@effect/") || e.startsWith("@voltro/") || e === "react" || e === "react-dom", mt = /(^|\/)(\.bin|\.cache|\.pnpm-store|dist|build)(\/|$)/, ht = (e) => {
1177
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
+ }, gt = (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
+ }, _t = (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(".") || mt.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
+ }, vt = (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 ht(e)) for (let { pkg: e, dir: r } of _t(n)) {
1230
+ if (!pt(e)) continue;
1231
+ let n = gt(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
+ }, yt = (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,33 +1258,52 @@ 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
+ }, bt = (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
+ }, xt = (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
+ }, St = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), Ct = /* @__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
+ ]), wt = (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 St(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
+ }, Tt = (e, t) => {
1273
1292
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
1274
- }, Ct = (e) => {
1293
+ }, Et = (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
+ }, Dt = (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 = St(e.src), i = Tt(r, t);
1299
+ return i === void 0 ? Et(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
1281
1300
  kind: "vocabulary-guard",
1282
1301
  name: i
1283
1302
  };
1284
- }, Tt = (e, t, n) => {
1303
+ }, Ot = (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
+ }, kt = (e, t) => {
1288
1307
  switch (t.kind) {
1289
1308
  case "declared-guard": return "guards: on the descriptor";
1290
1309
  case "vocabulary-guard": return `calls ${t.name}()`;
@@ -1292,15 +1311,15 @@ var re = () => {
1292
1311
  case "allowlisted": return "accepted by a previous run";
1293
1312
  case "unchecked": return e.op === "read" ? "returns a row the client selected, with nothing compared to the caller" : `${e.op}s${e.table ? ` ${e.table}` : ""} with nothing constraining WHICH row`;
1294
1313
  }
1295
- }, Dt = (e) => {
1314
+ }, At = (e) => {
1296
1315
  let t = e.executors.map((t) => {
1297
- let n = wt(t, e.guardVocabulary, e.allowlist);
1316
+ let n = Dt(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: Ot(t, e.tenantScopedTables, e.referencedTables),
1322
+ why: kt(t, n)
1304
1323
  };
1305
1324
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
1306
1325
  return {
@@ -1314,26 +1333,75 @@ 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
+ }, jt = [
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
+ ], Mt = (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
+ ...jt,
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
+ }, Nt = class extends Error {
1371
+ tag;
1372
+ constructor(e) {
1373
+ super(`authz allowlist: '${e}' is marked reviewed with no reason. Write \`${e} reviewed=<why it is genuinely open>\`. A bare tag is the other, honest option — it records the executor as DEBT, which is what an unexplained exemption is.`), this.tag = e, this.name = "AllowlistReasonMissingError";
1374
+ }
1375
+ }, Pt = (e) => {
1376
+ let t = [];
1377
+ for (let n of e.split("\n")) {
1378
+ let e = n.trim();
1379
+ if (e.length === 0 || e.startsWith("#")) continue;
1380
+ let r = /^(\S+)(?:\s+reviewed=(.*))?$/.exec(e);
1381
+ if (!r) continue;
1382
+ let i = r[1];
1383
+ if (r[2] === void 0) {
1384
+ t.push({ tag: i });
1385
+ continue;
1386
+ }
1387
+ let a = r[2].trim();
1388
+ if (a.length === 0) throw new Nt(i);
1389
+ t.push({
1390
+ tag: i,
1391
+ reviewed: a
1392
+ });
1393
+ }
1394
+ return {
1395
+ tags: new Set(t.map((e) => e.tag)),
1396
+ entries: t
1397
+ };
1398
+ }, Ft = (e) => {
1331
1399
  let t = e.indexOf(".");
1332
1400
  return t <= 0 ? void 0 : e.slice(0, t);
1333
- }, jt = (e, t) => {
1401
+ }, It = (e, t) => {
1334
1402
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
1335
1403
  for (let e of t.rpcTags) {
1336
- let t = At(e);
1404
+ let t = Ft(e);
1337
1405
  if (t === void 0) continue;
1338
1406
  let n = i.get(t);
1339
1407
  n ? n.push(e) : i.set(t, [e]);
@@ -1362,7 +1430,7 @@ var re = () => {
1362
1430
  });
1363
1431
  continue;
1364
1432
  }
1365
- let s = new Set(o.rpcTags.map(At).filter((e) => e !== void 0));
1433
+ let s = new Set(o.rpcTags.map(Ft).filter((e) => e !== void 0));
1366
1434
  for (let e of s) {
1367
1435
  let t = i.get(e);
1368
1436
  t !== void 0 && a.push({
@@ -1375,12 +1443,12 @@ var re = () => {
1375
1443
  }
1376
1444
  }
1377
1445
  return a;
1378
- }, Mt = (e, t) => {
1446
+ }, Lt = (e, t) => {
1379
1447
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
1380
1448
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
1381
1449
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
1382
1450
  return r;
1383
- }, Nt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), Pt = (e) => {
1451
+ }, Rt = (e) => T(C(e, "app.config.ts")) || T(C(e, "app.config.js")), zt = (e) => {
1384
1452
  let t = y(e), n = v(e);
1385
1453
  return {
1386
1454
  root: e,
@@ -1389,19 +1457,19 @@ var re = () => {
1389
1457
  apiBundle: n,
1390
1458
  apiBundlePresent: T(n)
1391
1459
  };
1392
- }, Ft = [
1460
+ }, Bt = [
1393
1461
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
1394
1462
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
1395
1463
  "(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;
1464
+ ].join("\n "), Vt = (e, t = !1) => {
1465
+ let n = zt(e);
1466
+ if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Rt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
1399
1467
  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
1468
  let r = process.env.NODE_ENV === "production";
1401
1469
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
1402
1470
  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) => {
1471
+ return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${Bt}`), +!!i;
1472
+ }, Ht = (e, t, n) => {
1405
1473
  let r = new Set(e), i = I(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
1406
1474
  `${e}.ts`,
1407
1475
  `${e}.tsx`,
@@ -1427,7 +1495,7 @@ var re = () => {
1427
1495
  r !== void 0 && o.push(r);
1428
1496
  }
1429
1497
  return o;
1430
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Ut.test(S(n, e))), l = new Set(c);
1498
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Jt.test(S(n, e))), l = new Set(c);
1431
1499
  for (; c.length > 0;) {
1432
1500
  let e = c.shift();
1433
1501
  if (!s.has(e)) {
@@ -1440,11 +1508,11 @@ var re = () => {
1440
1508
  let d = /* @__PURE__ */ new Set();
1441
1509
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
1442
1510
  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) => {
1511
+ }, Ut = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), Wt = (e, t) => (e.match(t) ?? []).length, G = (e, t) => e.getDescendantsOfKind(j.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, Gt = (e, t) => {
1444
1512
  if (e.src.includes(t)) return !0;
1445
1513
  let n = t.replace(/^@voltro\/plugin-/, "");
1446
- return n !== t && K(e.file, RegExp(`^${n}Plugin$`)) > 0;
1447
- }, Vt = (e, t) => {
1514
+ return n !== t && G(e.file, RegExp(`^${n}Plugin$`)) > 0;
1515
+ }, Kt = (e, t) => {
1448
1516
  let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
1449
1517
  for (; (i = r.exec(e)) !== null;) {
1450
1518
  let t = i.index + i[0].length, r = 1, a = t;
@@ -1452,10 +1520,10 @@ var re = () => {
1452
1520
  n.push(e.slice(t, a - 1));
1453
1521
  }
1454
1522
  return n;
1455
- }, Ht = (e, t) => {
1523
+ }, qt = (e, t) => {
1456
1524
  let n = t.getSymbol()?.getDeclarations()?.[0];
1457
1525
  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 = [
1526
+ }, Jt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Yt = /\.(query|mutation|action|stream)\.server\.tsx?$/, Xt = [
1459
1527
  "Id",
1460
1528
  "ID",
1461
1529
  "Hash",
@@ -1481,7 +1549,7 @@ var re = () => {
1481
1549
  "Comment",
1482
1550
  "Provider",
1483
1551
  "Format"
1484
- ], Kt = RegExp(`(?:${Gt.join("|")})$|_(?:${Gt.map((e) => e.toLowerCase()).join("|")})$`), qt = [
1552
+ ], Zt = RegExp(`(?:${Xt.join("|")})$|_(?:${Xt.map((e) => e.toLowerCase()).join("|")})$`), Qt = [
1485
1553
  {
1486
1554
  id: "hardcoded-framework-cookie",
1487
1555
  scope: "client",
@@ -1494,7 +1562,7 @@ var re = () => {
1494
1562
  scope: "server",
1495
1563
  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
1564
  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"))
1565
+ match: ({ src: e, path: t }) => Yt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1498
1566
  },
1499
1567
  {
1500
1568
  id: "subject-write-no-guard",
@@ -1502,8 +1570,8 @@ var re = () => {
1502
1570
  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
1571
  use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
1504
1572
  match: ({ src: e, path: t }) => {
1505
- if (!Wt.test(t)) return !1;
1506
- let n = Rt(e);
1573
+ if (!Yt.test(t)) return !1;
1574
+ let n = Ut(e);
1507
1575
  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
1576
  }
1509
1577
  },
@@ -1512,14 +1580,14 @@ var re = () => {
1512
1580
  scope: "server",
1513
1581
  smell: "raw fetch() for outbound HTTP on the server (no SSRF guard, no traceparent, no retry)",
1514
1582
  use: "yield* HttpClient — the framework client. It blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop, injects traceparent, and honours `http.allowHosts` for deliberate internal targets",
1515
- match: ({ file: e, path: t, serverOnly: n }) => (Ut.test(t) || n) && K(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1583
+ match: ({ file: e, path: t, serverOnly: n }) => (Jt.test(t) || n) && G(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1516
1584
  },
1517
1585
  {
1518
1586
  id: "form",
1519
1587
  scope: "client",
1520
1588
  smell: "per-field useState + a submit flag (hand-rolled form)",
1521
1589
  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)
1590
+ match: ({ src: e, file: t }) => G(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
1523
1591
  },
1524
1592
  {
1525
1593
  id: "table",
@@ -1547,7 +1615,7 @@ var re = () => {
1547
1615
  scope: "client",
1548
1616
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
1549
1617
  use: "useDerived — dependency-tracked, referentially stable",
1550
- match: ({ src: e }) => zt(e, /\buseSubscription\s*[(<]/g) >= 3 && zt(e, /\buseMemo\s*\(/g) >= 3
1618
+ match: ({ src: e }) => Wt(e, /\buseSubscription\s*[(<]/g) >= 3 && Wt(e, /\buseMemo\s*\(/g) >= 3
1551
1619
  },
1552
1620
  {
1553
1621
  id: "next-shim",
@@ -1572,7 +1640,7 @@ var re = () => {
1572
1640
  match: ({ file: e }) => e.getDescendantsOfKind(j.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
1573
1641
  let t = e.getFirstAncestorByKind(j.VariableDeclaration)?.getNameNode();
1574
1642
  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) => {
1643
+ }).some((t) => qt(e, t).some((e) => {
1576
1644
  let t = e.getParent();
1577
1645
  if (k.isBinaryExpression(t)) {
1578
1646
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -1600,7 +1668,7 @@ var re = () => {
1600
1668
  scope: "server",
1601
1669
  smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
1602
1670
  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)
1671
+ match: (e) => G(e.file, /\bstore\.query$/) >= 3 || G(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
1604
1672
  },
1605
1673
  {
1606
1674
  id: "store-effect-lift",
@@ -1623,7 +1691,7 @@ var re = () => {
1623
1691
  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
1692
  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
1693
  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);
1694
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Zt.test(n) || /^vault/i.test(n) ? !1 : !/\.encrypted\s*\(/.test(e);
1627
1695
  }))
1628
1696
  },
1629
1697
  {
@@ -1687,9 +1755,9 @@ var re = () => {
1687
1755
  scope: "server",
1688
1756
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
1689
1757
  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);
1758
+ match: ({ file: e, testSources: t }) => G(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1759
+ let t = Ut(e);
1760
+ return Kt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1693
1761
  })
1694
1762
  },
1695
1763
  {
@@ -1699,15 +1767,15 @@ var re = () => {
1699
1767
  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
1768
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1701
1769
  }
1702
- ], Jt = (e) => J(e), Yt = (e, t) => {
1770
+ ], $t = (e) => q(e), en = (e, t) => {
1703
1771
  let n = /* @__PURE__ */ new Set();
1704
1772
  for (let r of t) {
1705
1773
  let t = S(e, r), i = t.split(/[\\/]/)[0];
1706
1774
  n.add(i !== void 0 && i !== t ? i : ".");
1707
1775
  }
1708
1776
  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;
1777
+ }, tn = /\.(tsx?|jsx?)$/, K = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, nn = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, rn = (e, t = [], n = 0) => {
1778
+ if (n > 12 || K.test(e)) return t;
1711
1779
  let r;
1712
1780
  try {
1713
1781
  r = D(e, { withFileTypes: !0 });
@@ -1718,12 +1786,12 @@ var re = () => {
1718
1786
  let r = x(e, i.name);
1719
1787
  if (i.isDirectory()) {
1720
1788
  if (i.name.startsWith(".")) continue;
1721
- Qt(r, t, n + 1);
1722
- } else lt.test(i.name) && t.push(r);
1789
+ rn(r, t, n + 1);
1790
+ } else ut.test(i.name) && t.push(r);
1723
1791
  }
1724
1792
  return t;
1725
- }, J = (e, t = [], n = 0) => {
1726
- if (n > 12 || q.test(e)) return t;
1793
+ }, q = (e, t = [], n = 0) => {
1794
+ if (n > 12 || K.test(e)) return t;
1727
1795
  let r;
1728
1796
  try {
1729
1797
  r = D(e);
@@ -1737,11 +1805,11 @@ var re = () => {
1737
1805
  } catch {
1738
1806
  continue;
1739
1807
  }
1740
- a.isDirectory() ? J(r, t, n + 1) : Xt.test(i) && !/\.(test|spec)\./.test(i) && !Zt.test(i) && t.push(r);
1808
+ a.isDirectory() ? q(r, t, n + 1) : tn.test(i) && !/\.(test|spec)\./.test(i) && !nn.test(i) && t.push(r);
1741
1809
  }
1742
1810
  return t;
1743
- }, $t = (e, t = [], n = 0) => {
1744
- if (n > 12 || q.test(e)) return t;
1811
+ }, an = (e, t = [], n = 0) => {
1812
+ if (n > 12 || K.test(e)) return t;
1745
1813
  let r;
1746
1814
  try {
1747
1815
  r = D(e);
@@ -1755,17 +1823,17 @@ var re = () => {
1755
1823
  } catch {
1756
1824
  continue;
1757
1825
  }
1758
- a.isDirectory() ? $t(r, t, n + 1) : Xt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1826
+ a.isDirectory() ? an(r, t, n + 1) : tn.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1759
1827
  }
1760
1828
  return t;
1761
- }, en = (e) => {
1762
- let t = Jt(e);
1829
+ }, on = (e) => {
1830
+ let t = $t(e);
1763
1831
  return {
1764
- dirs: Yt(e, t),
1832
+ dirs: en(e, t),
1765
1833
  fileCount: t.length
1766
1834
  };
1767
- }, tn = (e) => {
1768
- let t = Jt(e);
1835
+ }, sn = (e) => {
1836
+ let t = $t(e);
1769
1837
  if (t.length === 0) return [];
1770
1838
  let n = new A({
1771
1839
  useInMemoryFileSystem: !0,
@@ -1774,8 +1842,8 @@ var re = () => {
1774
1842
  for (let e of t) try {
1775
1843
  i.set(e, E(e, "utf8"));
1776
1844
  } catch {}
1777
- let a = Lt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1778
- for (let t of $t(e)) try {
1845
+ let a = Ht([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1846
+ for (let t of an(e)) try {
1779
1847
  o.set(t, E(t, "utf8"));
1780
1848
  } catch {}
1781
1849
  for (let s of t) {
@@ -1793,23 +1861,23 @@ var re = () => {
1793
1861
  } catch {
1794
1862
  continue;
1795
1863
  }
1796
- for (let t of qt) {
1797
- if (t.plugin !== void 0 && Bt(c, t.plugin) || !t.match(c)) continue;
1864
+ for (let t of Qt) {
1865
+ if (t.plugin !== void 0 && Gt(c, t.plugin) || !t.match(c)) continue;
1798
1866
  let n = r.get(t.id) ?? [];
1799
1867
  n.push(S(e, s)), r.set(t.id, n);
1800
1868
  }
1801
1869
  }
1802
- return qt.filter((e) => r.has(e.id)).map((e) => ({
1870
+ return Qt.filter((e) => r.has(e.id)).map((e) => ({
1803
1871
  rule: e,
1804
1872
  files: r.get(e.id)
1805
1873
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1806
- }, nn = (e) => {
1807
- let t = en(e);
1874
+ }, cn = (e) => {
1875
+ let t = on(e);
1808
1876
  return {
1809
1877
  root: e,
1810
1878
  scannedFiles: t.fileCount,
1811
1879
  scannedDirs: t.dirs,
1812
- findings: tn(e).map(({ rule: e, files: t }) => ({
1880
+ findings: sn(e).map(({ rule: e, files: t }) => ({
1813
1881
  id: e.id,
1814
1882
  scope: e.scope,
1815
1883
  smell: e.smell,
@@ -1817,45 +1885,50 @@ var re = () => {
1817
1885
  files: t
1818
1886
  }))
1819
1887
  };
1820
- }, rn = 3, an = (e) => {
1888
+ }, ln = 3, un = .2, dn = 50, fn = (e, t) => e >= dn && t > 0 && e / t >= un, pn = (e) => {
1821
1889
  let t = /* @__PURE__ */ new Map();
1822
- for (let n of J(e)) try {
1890
+ for (let n of q(e)) try {
1823
1891
  t.set(S(e, n), E(n, "utf8"));
1824
1892
  } catch {}
1825
1893
  let n = /* @__PURE__ */ new Map();
1826
- for (let t of Qt(e)) try {
1894
+ for (let t of rn(e)) try {
1827
1895
  n.set(S(e, t), E(t, "utf8"));
1828
1896
  } catch {}
1829
- let r = ut({
1897
+ let r = dt({
1830
1898
  sources: t,
1831
1899
  envFiles: n
1832
1900
  });
1833
- r !== void 0 && (console.log(""), console.log(dt(r)));
1834
- }, on = (e) => {
1835
- let t = nn(e);
1901
+ r !== void 0 && (console.log(""), console.log(ft(r)));
1902
+ }, mn = (e) => {
1903
+ let t = cn(e);
1836
1904
  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
1905
  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
1906
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
1839
1907
  let n = 0;
1840
1908
  for (let e of ["server", "client"]) {
1841
1909
  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}`);
1910
+ if (r.length === 0) continue;
1911
+ console.log(` [${e}]`);
1912
+ let i = [...r].sort((e, t) => e.files.length - t.files.length);
1913
+ for (let e of i) {
1914
+ if (fn(e.files.length, t.scannedFiles)) {
1915
+ let n = Math.round(e.files.length / t.scannedFiles * 100);
1916
+ console.log(` ${e.smell} \u2014 ${e.files.length} file(s), ${n}% of those scanned: a codebase-wide pattern rather than a work list`), console.log(` \u2192 ${e.use}`), console.log(" (paths omitted on purpose at this scale — `voltro doctor --json` has every one.)");
1917
+ continue;
1849
1918
  }
1919
+ let r = e.files.slice(0, ln).join(", "), i = e.files.length - ln;
1920
+ i > 0 && (n += i);
1921
+ let a = i > 0 ? `, +${i} more` : "";
1922
+ console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1850
1923
  }
1851
1924
  }
1852
1925
  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];
1926
+ }, hn = "renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.", gn = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, _n = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, vn = (e) => {
1927
+ let t = gn.exec(e)?.[1];
1855
1928
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1856
- let n = ln.exec(e)?.[1];
1929
+ let n = _n.exec(e)?.[1];
1857
1930
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1858
- }, dn = async (e) => {
1931
+ }, yn = async (e) => {
1859
1932
  let n = x(e, "src", "pages");
1860
1933
  if (!T(n)) return [];
1861
1934
  let { pages: a, dirs: o } = await t(n), s = [];
@@ -1868,7 +1941,7 @@ var re = () => {
1868
1941
  continue;
1869
1942
  }
1870
1943
  if (r.test(a)) continue;
1871
- let c = un(a);
1944
+ let c = vn(a);
1872
1945
  c !== null && s.push({
1873
1946
  file: S(e, x(n, t.file)),
1874
1947
  pattern: t.pattern,
@@ -1876,18 +1949,18 @@ var re = () => {
1876
1949
  });
1877
1950
  }
1878
1951
  return s;
1879
- }, fn = 10, pn = async (e) => {
1952
+ }, bn = 10, xn = async (e) => {
1880
1953
  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);
1954
+ let t = await yn(e);
1882
1955
  if (t.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1883
1956
  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)) {
1957
+ for (let e of t.slice(0, bn)) {
1885
1958
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1886
1959
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1887
1960
  }
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([
1961
+ let n = t.length - bn;
1962
+ return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${hn}`), t.length;
1963
+ }, Sn = /* @__PURE__ */ new Set([
1891
1964
  "node_modules",
1892
1965
  "dist",
1893
1966
  "build",
@@ -1895,7 +1968,7 @@ var re = () => {
1895
1968
  ".vite",
1896
1969
  ".framework",
1897
1970
  ".git"
1898
- ]), hn = (e, t, n) => {
1971
+ ]), Cn = (e, t, n) => {
1899
1972
  let r;
1900
1973
  try {
1901
1974
  r = D(e, { withFileTypes: !0 });
@@ -1903,11 +1976,11 @@ var re = () => {
1903
1976
  return;
1904
1977
  }
1905
1978
  for (let i of r) {
1906
- if (mn.has(i.name)) continue;
1979
+ if (Sn.has(i.name)) continue;
1907
1980
  let r = x(e, i.name);
1908
- i.isDirectory() ? hn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
1981
+ i.isDirectory() ? Cn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(S(t, r));
1909
1982
  }
1910
- }, gn = (e, t) => {
1983
+ }, wn = (e, t) => {
1911
1984
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1912
1985
  for (let i of t) {
1913
1986
  let t;
@@ -1932,16 +2005,16 @@ var re = () => {
1932
2005
  }
1933
2006
  }
1934
2007
  return n;
1935
- }, _n = (e, t) => {
2008
+ }, Tn = (e, t) => {
1936
2009
  let n = e.slice(t.length).split(w).slice(0, -1)[0];
1937
2010
  return n !== void 0 && n.startsWith("[");
1938
- }, Y = (t) => {
2011
+ }, J = (t) => {
1939
2012
  let n = [], r = x(t, "src", "pages"), i = x(t, "src");
1940
2013
  if (!T(i)) return n;
1941
2014
  let a = [];
1942
- hn(i, t, a), a.sort();
2015
+ Cn(i, t, a), a.sort();
1943
2016
  let o = `${S(t, r)}${w}`;
1944
- gn(t, a);
2017
+ wn(t, a);
1945
2018
  for (let r of a) {
1946
2019
  let i = r.slice(r.lastIndexOf(w) + 1);
1947
2020
  if (r.startsWith(o)) {
@@ -1949,7 +2022,7 @@ var re = () => {
1949
2022
  T(x(t, e(r))) || n.push({
1950
2023
  kind: "page-without-test",
1951
2024
  file: r
1952
- }), _n(r, o) && n.push({
2025
+ }), Tn(r, o) && n.push({
1953
2026
  kind: "top-level-dynamic-route",
1954
2027
  file: r
1955
2028
  });
@@ -1961,33 +2034,33 @@ var re = () => {
1961
2034
  });
1962
2035
  }
1963
2036
  return n;
1964
- }, X = 10, Z = (e, t, n) => {
2037
+ }, Y = 10, X = (e, t, n) => {
1965
2038
  if (t.length === 0) return;
1966
2039
  console.log(`• ${e} (${t.length}):`);
1967
- for (let e of t.slice(0, X)) console.log(` ${e}`);
1968
- let r = t.length - X;
2040
+ for (let e of t.slice(0, Y)) console.log(` ${e}`);
2041
+ let r = t.length - Y;
1969
2042
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
1970
- }, vn = (e) => {
2043
+ }, En = (e) => {
1971
2044
  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
- let t = Y(e);
2045
+ let t = J(e);
1973
2046
  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
2047
  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);
2048
+ return X("will NEVER route — page.tsx outside src/pages/", n("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), X("pages with no test beside them", n("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), X("routes whose FIRST segment is dynamic — they claim every URL of that depth", n("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), t.length;
2049
+ }, Dn = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", On = /* @__PURE__ */ new Map(), Z = (e) => {
2050
+ let t = On.get(e);
1978
2051
  if (t !== void 0) return t;
1979
2052
  let n = (async () => {
1980
- let { walk: t, loadDiscovered: n } = await import("./dev-Cr-teYsz.js"), r = await t(e);
2053
+ let { walk: t, loadDiscovered: n } = await import("./dev-DXhhZ7-f.js"), r = await t(e);
1981
2054
  return {
1982
2055
  files: r,
1983
2056
  discovered: await n(r)
1984
2057
  };
1985
2058
  })();
1986
- return bn.set(e, n), n;
1987
- }, xn = async (e) => {
2059
+ return On.set(e, n), n;
2060
+ }, kn = async (e) => {
1988
2061
  let t;
1989
2062
  try {
1990
- let { files: n, discovered: r } = await Q(e), i = r.tables.map((e) => {
2063
+ let { files: n, discovered: r } = await Z(e), i = r.tables.map((e) => {
1991
2064
  let t = e;
1992
2065
  return {
1993
2066
  name: t.tableName,
@@ -1995,12 +2068,12 @@ var re = () => {
1995
2068
  };
1996
2069
  });
1997
2070
  if (i.length === 0) return;
1998
- t = Ge(We(n, i, e));
2071
+ t = Ke(Ge(n, i, e));
1999
2072
  } catch {
2000
2073
  return;
2001
2074
  }
2002
2075
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
2003
- }, Sn = (e) => e.map((e) => {
2076
+ }, An = (e) => e.map((e) => {
2004
2077
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
2005
2078
  name: e,
2006
2079
  type: typeof t?.type == "string" ? t.type : ""
@@ -2010,22 +2083,22 @@ var re = () => {
2010
2083
  columns: n,
2011
2084
  primaryKey: t.appliedPrimaryKey ?? []
2012
2085
  };
2013
- }), Cn = async (e) => {
2086
+ }), jn = async (e) => {
2014
2087
  try {
2015
- let { discovered: t } = await Q(e);
2016
- return Xe(Sn(t.tables));
2088
+ let { discovered: t } = await Z(e);
2089
+ return Ze(An(t.tables));
2017
2090
  } catch {
2018
2091
  return null;
2019
2092
  }
2020
- }, wn = async (e) => {
2021
- let t = await Cn(e);
2093
+ }, Mn = async (e) => {
2094
+ let t = await jn(e);
2022
2095
  if (t === null || t.tablesChecked === 0) return;
2023
- let n = Qe(t);
2096
+ let n = $e(t);
2024
2097
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
2025
- }, Tn = async (e) => {
2098
+ }, Nn = async (e) => {
2026
2099
  try {
2027
- let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await Q(e);
2028
- return nt(n.events.map((e) => {
2100
+ let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await Z(e);
2101
+ return rt(n.events.map((e) => {
2029
2102
  let n = e.descriptor, r = t(n.key);
2030
2103
  return {
2031
2104
  name: n.name,
@@ -2037,19 +2110,19 @@ var re = () => {
2037
2110
  } catch {
2038
2111
  return null;
2039
2112
  }
2040
- }, En = async (e) => {
2041
- let t = await Tn(e);
2113
+ }, Pn = async (e) => {
2114
+ let t = await Nn(e);
2042
2115
  if (t === null || t.eventsChecked === 0) return;
2043
- let n = rt(t);
2116
+ let n = it(t);
2044
2117
  n !== null && (console.log(""), console.log(`• ${n}`));
2045
- let r = it(t);
2118
+ let r = at(t);
2046
2119
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
2047
- }, Dn = (e) => {
2120
+ }, Fn = (e) => {
2048
2121
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
2049
2122
  return r === void 0 ? n : `${r}: ${n}`;
2050
- }, On = async (e) => {
2123
+ }, In = async (e) => {
2051
2124
  try {
2052
- let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await Q(e)).discovered.serverOnlyLeaks, r = t(n);
2125
+ let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await Z(e)).discovered.serverOnlyLeaks, r = t(n);
2053
2126
  return {
2054
2127
  status: {
2055
2128
  checked: !0,
@@ -2060,17 +2133,17 @@ var re = () => {
2060
2133
  } catch (e) {
2061
2134
  return { status: {
2062
2135
  checked: !1,
2063
- reason: Dn(e)
2136
+ reason: Fn(e)
2064
2137
  } };
2065
2138
  }
2066
- }, kn = async (e) => {
2067
- let { status: t, message: n } = await On(e);
2139
+ }, Ln = async (e) => {
2140
+ let { status: t, message: n } = await In(e);
2068
2141
  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) => {
2142
+ }, Rn = async (e) => {
2070
2143
  let t;
2071
2144
  try {
2072
- let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await Q(e);
2073
- t = st(r, i.workflows.map((e) => {
2145
+ let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await Z(e);
2146
+ t = ct(r, i.workflows.map((e) => {
2074
2147
  let t = e.definition, r = t.payloadSchema === void 0 ? {
2075
2148
  required: [],
2076
2149
  optional: []
@@ -2084,21 +2157,46 @@ var re = () => {
2084
2157
  } catch {
2085
2158
  return;
2086
2159
  }
2087
- let n = ct(t);
2160
+ let n = lt(t);
2088
2161
  if (n === null) return;
2089
2162
  console.log("");
2090
2163
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
2091
2164
  console.log(`${r} ${n}`);
2092
- }, $ = "voltro-authz-allowlist.txt", jn = (e, t = (e) => {
2165
+ }, Q = "voltro-authz-allowlist.txt", zn = (e, t = (e) => {
2093
2166
  try {
2094
2167
  return E(e, "utf8");
2095
2168
  } catch {
2096
2169
  return "";
2097
2170
  }
2098
- }) => /* @__PURE__ */ new Set([...bt, ...xt(J(e).map(t))]), Mn = async (e) => {
2099
- let { discovered: t } = await Q(e), n = [];
2171
+ }) => /* @__PURE__ */ new Set([...Ct, ...wt(q(e).map(t))]), Bn = async (e) => {
2172
+ let t = ["src/locales", "locales"], n, r = [];
2173
+ for (let i of t) {
2174
+ let t = x(e, i);
2175
+ if (T(t)) try {
2176
+ r = D(t).filter((e) => /\.tsx?$/.test(e) && !e.endsWith(".d.ts")).map((e) => e.replace(/\.tsx?$/, "")), n = i;
2177
+ break;
2178
+ } catch {}
2179
+ }
2180
+ if (n === void 0 || r.length === 0) return;
2181
+ let i = [];
2100
2182
  try {
2101
- let { loadApiConfig: t } = await import("./dev-Cr-teYsz.js");
2183
+ let { loadApiConfig: t } = await import("./dev-DXhhZ7-f.js");
2184
+ i = (await t(e))?.locales ?? [];
2185
+ } catch {
2186
+ return;
2187
+ }
2188
+ let a = bt({
2189
+ catalogCodes: r,
2190
+ declaredLocales: i
2191
+ });
2192
+ if (a !== void 0) {
2193
+ console.log("");
2194
+ for (let e of xt(a, n)) console.log(e);
2195
+ }
2196
+ }, Vn = async (e) => {
2197
+ let { discovered: t } = await Z(e), n = [];
2198
+ try {
2199
+ let { loadApiConfig: t } = await import("./dev-DXhhZ7-f.js");
2102
2200
  n = ((await t(e))?.plugins ?? []).map((e) => {
2103
2201
  let t = (e.extendSchema?.tables ?? []).map((e) => {
2104
2202
  let t = e;
@@ -2114,8 +2212,8 @@ var re = () => {
2114
2212
  plugin: e.name,
2115
2213
  tables: t,
2116
2214
  rpcTags: n,
2117
- canDisableTables: Nn.has(e.name),
2118
- canAlias: Pn.has(e.name)
2215
+ canDisableTables: Hn.has(e.name),
2216
+ canAlias: Un.has(e.name)
2119
2217
  };
2120
2218
  });
2121
2219
  } catch {
@@ -2127,89 +2225,115 @@ var re = () => {
2127
2225
  ...t.mutations,
2128
2226
  ...t.actions,
2129
2227
  ...t.streams ?? []
2130
- ].map((e) => String(e.descriptor.name)), i = jt(n, {
2228
+ ].map((e) => String(e.descriptor.name)), i = It(n, {
2131
2229
  tables: t.tables.map((e) => e.tableName),
2132
2230
  rpcTags: r
2133
2231
  });
2134
2232
  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"));
2233
+ for (let e of Lt(n, i)) console.log(e);
2234
+ }, Hn = /* @__PURE__ */ new Set(["@voltro/plugin-rbac"]), Un = /* @__PURE__ */ new Set(["@voltro/plugin-ai-flows"]), $ = 20, Wn = async (e) => {
2235
+ let t = await Gn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
2236
+ return {
2237
+ executors: t.executorCount,
2238
+ guardVocabulary: [...t.vocabulary].sort(),
2239
+ counts: t.result.counts,
2240
+ allowlist: {
2241
+ exists: t.existing !== void 0,
2242
+ debt: r,
2243
+ reviewed: t.result.counts.allowlisted - r
2244
+ },
2245
+ unchecked: t.result.unchecked
2246
+ };
2247
+ }, Gn = async (e) => {
2248
+ let { discovered: t } = await Z(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2249
+ if (!n.has(e)) try {
2250
+ n.set(e, E(e, "utf8"));
2140
2251
  } catch {
2141
- r.set(e, "");
2252
+ n.set(e, "");
2142
2253
  }
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 {
2254
+ return n.get(e);
2255
+ }, i = zn(e, r), a = /* @__PURE__ */ new Set();
2256
+ for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
2146
2257
  let e = t.references?.();
2147
- e && o.add(e.tableName);
2258
+ e && a.add(e.tableName);
2148
2259
  } catch {}
2149
- let s = [
2260
+ let o = [
2150
2261
  {
2151
- list: n.queries,
2262
+ list: t.queries,
2152
2263
  op: "read"
2153
2264
  },
2154
2265
  {
2155
- list: n.mutations,
2266
+ list: t.mutations,
2156
2267
  op: "update"
2157
2268
  },
2158
2269
  {
2159
- list: n.actions,
2270
+ list: t.actions,
2160
2271
  op: "update"
2161
2272
  },
2162
2273
  {
2163
- list: n.streams,
2274
+ list: t.streams,
2164
2275
  op: "read"
2165
2276
  }
2166
2277
  ].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");
2278
+ let n = e.descriptor, i = n.target, a = Array.isArray(i) ? i[0] : i, o = e.file.replace(/\.(tsx?)$/, ".server.$1");
2168
2279
  return {
2169
2280
  tag: String(n.name ?? e.file),
2170
2281
  path: o,
2171
- src: i(o),
2282
+ src: r(o),
2172
2283
  hasDeclaredGuards: Array.isArray(n.guards) && n.guards.length > 0,
2173
2284
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
2174
2285
  ...a?.table === void 0 ? {} : { table: a.table }
2175
2286
  };
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;
2287
+ })), s = x(e, Q), c = T(s) ? Pt(E(s, "utf8")) : void 0;
2288
+ return {
2289
+ result: At({
2290
+ executors: o,
2291
+ guardVocabulary: i,
2292
+ tenantScopedTables: t.tenantScopedTables,
2293
+ referencedTables: a,
2294
+ allowlist: c?.tags ?? /* @__PURE__ */ new Set()
2295
+ }),
2296
+ vocabulary: i,
2297
+ existing: c,
2298
+ reviewed: (c?.entries ?? []).filter((e) => e.reviewed !== void 0),
2299
+ executorCount: o.length,
2300
+ allowPath: s
2301
+ };
2302
+ }, Kn = async (e, t) => {
2303
+ let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s } = await Gn(e), c = n.counts;
2304
+ console.log(`\nauthz scan · ${o} executor(s)`);
2305
+ let l = [...r].filter((e) => !Ct.has(e)).sort();
2306
+ console.log(l.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${l.length} from your source (${l.slice(0, 6).join(", ")}${l.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(c.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(c.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(c.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(c.vocabularyGuard).padStart(4)}`);
2307
+ let u = new Set(a.map((e) => e.tag)), d = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !u.has(e.tag)).length, f = c.allowlisted - d;
2308
+ if (console.log(` – accepted as recorded debt ${String(d).padStart(4)} (${Q})`), console.log(` ✓ reviewed, open on purpose ${String(f).padStart(4)} (\`reviewed=<why>\` in that file)`), t) return ne(s, Mt([...a, ...n.unchecked.map((e) => ({ tag: e.tag }))]), "utf8"), console.log(`\nwrote ${n.unchecked.length} debt tag(s) to ${Q}${a.length > 0 ? `, keeping ${a.length} reviewed line(s)` : ""}. Doctor now fails on ADDITIONS only.`), !1;
2309
+ if (n.unchecked.length === 0) return !1;
2187
2310
  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;
2311
+ for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
2312
+ return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Q} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Q}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
2313
+ }, qn = async (e) => {
2314
+ if (e.includes("--help") || e.includes("-h")) return console.log(Dn), 0;
2192
2315
  let t = e.includes("--strict"), n = C(e.find((e) => !e.startsWith("-")) ?? process.cwd());
2193
2316
  if (ie(e)) return re(), M({
2194
- ...nn(n),
2195
- spaCandidates: await dn(n),
2196
- 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
2317
+ ...cn(n),
2318
+ spaCandidates: await yn(n),
2319
+ pageConvention: J(n),
2320
+ fileTaxonomy: H(n).findings,
2321
+ junctionFks: (await jn(n))?.issues ?? [],
2322
+ eventDelivery: await Nn(n),
2323
+ duplicateInstances: vt(n),
2324
+ serverOnly: (await In(n)).status,
2325
+ authz: await Wn(n)
2202
2326
  }), 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));
2327
+ let r = Vt(n, t);
2328
+ mn(n), pn(n), En(n), Ve(n), await xn(n), await Rn(n), await kn(n), await Mn(n), await Pn(n), await Vn(n), await Bn(n);
2329
+ let i = await Ln(n), a = await Kn(n, e.includes("--write-authz-allowlist")), o = yt(vt(n));
2206
2330
  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([
2331
+ }, Jn = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, Yn = /* @__PURE__ */ new Set([
2208
2332
  "insert",
2209
2333
  "update",
2210
2334
  "delete",
2211
2335
  "upsert"
2212
- ]), zn = (e, t) => {
2336
+ ]), Xn = (e, t) => {
2213
2337
  let n = [], r = new Set(t.exercised), i = /* @__PURE__ */ new Map();
2214
2338
  for (let e of t.edges) {
2215
2339
  let t = i.get(e.tag);
@@ -2221,8 +2345,8 @@ var re = () => {
2221
2345
  a.push(t.tag);
2222
2346
  continue;
2223
2347
  }
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)) {
2348
+ let e = `${t.kind}(${t.tag})`, s = t.file, c = i.get(t.tag) ?? [], l = new Set(Jn(t.source)), u = new Map((t.targets ?? []).map((e) => [e.table, e.op]));
2349
+ for (let t of c) if (Yn.has(t.op)) {
2226
2350
  let r = u.get(t.table);
2227
2351
  r === void 0 ? n.push({
2228
2352
  rule: "observed/undeclared-write",
@@ -2278,7 +2402,7 @@ var re = () => {
2278
2402
  total: e.length
2279
2403
  }
2280
2404
  };
2281
- }, Bn = (e, t = [], n = 0) => {
2405
+ }, Zn = (e, t = [], n = 0) => {
2282
2406
  if (n > 12) return t;
2283
2407
  let r;
2284
2408
  try {
@@ -2302,24 +2426,24 @@ var re = () => {
2302
2426
  } catch {
2303
2427
  continue;
2304
2428
  }
2305
- o.isDirectory() ? Bn(r, t, n + 1) : o.isFile() && a.test(i) && t.push(r);
2429
+ o.isDirectory() ? Zn(r, t, n + 1) : o.isFile() && a.test(i) && t.push(r);
2306
2430
  }
2307
2431
  return t;
2308
- }, Vn = /* @__PURE__ */ new Set(["actors", "tenants"]), Hn = async (e) => {
2432
+ }, Qn = /* @__PURE__ */ new Set(["actors", "tenants"]), $n = async (e) => {
2309
2433
  try {
2310
2434
  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
2435
  return i?.version !== 1 || !Array.isArray(i.edges) || !Array.isArray(i.exercised) ? null : i;
2312
2436
  } catch {
2313
2437
  return null;
2314
2438
  }
2315
- }, Un = (e) => {
2439
+ }, er = (e) => {
2316
2440
  let { exercised: t, total: n } = e.coverage, r = `\nobserved: ${t}/${n} procedures exercised (${n === 0 ? 0 : Math.round(t / n * 100)}%)`;
2317
2441
  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
2442
  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
2443
  return `${r}\n${i.join("\n")}${a}`;
2320
- }, Wn = (e, t) => ({
2444
+ }, tr = (e, t) => ({
2321
2445
  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),
2446
+ frameworkOwnedTables: (t.tables ?? []).filter((e) => e.framework === !0 || e.name.startsWith("_voltro_") || Qn.has(e.name)).map((e) => e.name),
2323
2447
  scopes: e.scopes ?? [],
2324
2448
  procedures: (e.procedures ?? []).map((e) => {
2325
2449
  let t = (e.guards ?? []).flatMap((e) => e.kind === "scope" ? e.scope ?? [] : []);
@@ -2332,16 +2456,16 @@ var re = () => {
2332
2456
  ...e.guards === void 0 ? {} : { hasGuards: e.guards.length > 0 }
2333
2457
  };
2334
2458
  })
2335
- }), Gn = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
2459
+ }), nr = (e) => e.length === 0 ? "✓ voltro check: no issues" : e.map((e) => {
2336
2460
  let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
2337
2461
  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 = [
2462
+ }).join("\n"), rr = async (e) => {
2463
+ let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-DXhhZ7-f.js"), { buildRpcEntry: i } = await import("./manifestBuild-Dj8Jjoto.js"), { buildCapabilityManifest: a } = await import("./inspect-mmBuRXmy.js"), { relative: o } = await import("node:path"), s = await n(await t(e)), c = (t, n, r) => i(t, n, o(e, r)), l = [
2340
2464
  ...s.queries.map((e) => c(e.descriptor, "query", e.file)),
2341
2465
  ...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
2342
2466
  ...s.actions.map((e) => c(e.descriptor, "action", e.file)),
2343
2467
  ...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) => ({
2468
+ ], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-Bn_c4Mnl.js"), d = await u({ root: e }).catch(() => []), f = new Set(s.tables.map((e) => e.tableName)), p = [...[...f].map((e) => ({ name: e })), ...d.map((e) => e.tableName).filter((e) => !f.has(e)).map((e) => ({
2345
2469
  name: e,
2346
2470
  framework: !0
2347
2471
  }))], m = await r(e).then((e) => e?.plugins ?? []).catch(() => []);
@@ -2352,7 +2476,7 @@ var re = () => {
2352
2476
  })), [], m),
2353
2477
  tables: { tables: p }
2354
2478
  };
2355
- }, qn = async (e) => {
2479
+ }, ir = async (e) => {
2356
2480
  let t = ue(e.flatMap((e) => e === "--json" ? ["--format", "json"] : [e]));
2357
2481
  if ("error" in t) return console.error(`voltro check: ${t.error}`), 1;
2358
2482
  let { process: n, target: r, rest: i } = de(t.rest), a;
@@ -2376,7 +2500,7 @@ var re = () => {
2376
2500
  if (l.length === 0) {
2377
2501
  if (!c && r !== void 0) return console.error("voltro check: --url given but no api reachable there."), 1;
2378
2502
  try {
2379
- let e = await Kn(C(s));
2503
+ let e = await rr(C(s));
2380
2504
  u = e.manifest, d = e.tables;
2381
2505
  } catch (e) {
2382
2506
  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,21 +2510,21 @@ var re = () => {
2386
2510
  if (t.kind === "err") return console.error(`voltro check: manifest fetch failed: ${t.error}`), 1;
2387
2511
  u = t.data, d = n.kind === "ok" ? n.data : { tables: [] };
2388
2512
  }
2389
- let f = Wn(u, d), p = he(f, a), m = [...U(s).findings.map((e) => ({
2513
+ let f = tr(u, d), p = he(f, a), m = [...H(s).findings.map((e) => ({
2390
2514
  rule: e.rule,
2391
2515
  node: e.file,
2392
2516
  file: e.file,
2393
2517
  breaks: [e.detail],
2394
2518
  fix: e.fix,
2395
2519
  severity: e.severity
2396
- })), ...Y(s).map((e) => ({
2520
+ })), ...J(s).map((e) => ({
2397
2521
  rule: `page/${e.kind}`,
2398
2522
  node: e.file,
2399
2523
  file: e.file,
2400
2524
  breaks: [e.kind === "suffixed-outside-pages" ? "a page.tsx outside src/pages/ never routes" : "no test beside it"],
2401
2525
  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
2526
  severity: e.kind === "suffixed-outside-pages" ? "error" : "warn"
2403
- }))], h = (await o(s, Bn(s))).map((e) => ({
2527
+ }))], h = (await o(s, Zn(s))).map((e) => ({
2404
2528
  rule: "client/not-browser-safe",
2405
2529
  node: e.file,
2406
2530
  file: e.file,
@@ -2411,7 +2535,7 @@ var re = () => {
2411
2535
  ...p.diagnostics,
2412
2536
  ...m,
2413
2537
  ...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);
2538
+ ], _ = p.ok && !m.some((e) => e.severity === "error") && h.length === 0, v = await $n(C(s)), y = v === null ? null : Xn(f.procedures, v);
2415
2539
  return t.format === "json" ? M(y === null ? {
2416
2540
  ...p,
2417
2541
  ok: _,
@@ -2421,7 +2545,7 @@ var re = () => {
2421
2545
  ok: _,
2422
2546
  diagnostics: g,
2423
2547
  observed: y
2424
- }) : (console.log(Gn(g)), y !== null && console.log(Un(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
2548
+ }) : (console.log(nr(g)), y !== null && console.log(er(y)), console.log(_ ? "\nvoltro check: OK" : "\nvoltro check: FAILED (errors above)")), +!_;
2425
2549
  };
2426
2550
  //#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 };
2551
+ export { re as _, Re as a, I as c, N as d, ue as f, le as g, ce as h, Vt as i, ve as l, se as m, ir as n, be as o, de as p, qn as r, xe as s, rr as t, he as u, M as v, ie as y };