@voltro/cli 0.30.1 → 0.30.2

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