@voltro/cli 0.56.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/dist/apiBuild-B83Cb2Rv.js +2 -0
  3. package/dist/{apiBuild-Bdaetr37.js → apiBuild-DDr2aNFd.js} +2 -2
  4. package/dist/bin.js +1 -1
  5. package/dist/{build-CI36wL4R.js → build-QKP6Bm0J.js} +191 -187
  6. package/dist/{checkCommand-D0QV_zM_.js → checkCommand-BISqx1OJ.js} +1 -1
  7. package/dist/{checkCommand-CAwFXrxA.js → checkCommand-DUtMWjcR.js} +4 -4
  8. package/dist/{codegenCommand-CidbQzbv.js → codegenCommand-DnVuDxwT.js} +10 -10
  9. package/dist/{commands-CWjfThXv.js → commands-CRbxgxv0.js} +22 -22
  10. package/dist/{dashboardCommand-BekcY5Ls.js → dashboardCommand-Bf_-Ne3P.js} +5 -5
  11. package/dist/{dataCommand-2pccgbIy.js → dataCommand-BoBJJ-Gb.js} +2 -2
  12. package/dist/{dbCommand-DpK_vQET.js → dbCommand-CMAIz-Bf.js} +3 -3
  13. package/dist/dbCommand-DHi_RuDl.js +2 -0
  14. package/dist/dev-Bl9HqtV7.js +3 -0
  15. package/dist/{dev-Dw263KPu.js → dev-DOEJXicj.js} +2109 -2092
  16. package/dist/{doctorCommand-aR_bFmIi.js → doctorCommand-BrWu67JZ.js} +331 -189
  17. package/dist/doctorCommand-DrQv9SL3.js +2 -0
  18. package/dist/{dormancyCommand-eXTQMbHU.js → dormancyCommand-xn2y-pJm.js} +1 -1
  19. package/dist/{embeddingsCommand-CTmiQvwa.js → embeddingsCommand-Cn5MbRDM.js} +1 -1
  20. package/dist/emptyResultHeadline-Csa5fZOF.js +18 -0
  21. package/dist/{envCommand-BDUgV7EM.js → envCommand-UJmJIbs9.js} +8 -8
  22. package/dist/{evolveCommand-YV8qW1LU.js → evolveCommand-Db30twUy.js} +1 -1
  23. package/dist/frameworkTableAssembly-B96WCNJA.js +2 -0
  24. package/dist/{frameworkTableAssembly-DNOFXfEQ.js → frameworkTableAssembly-vfkzuzEo.js} +104 -85
  25. package/dist/index.js +1 -1
  26. package/dist/inspect-B0hL41s0.js +2 -0
  27. package/dist/{inspect-CNYvNXPU.js → inspect-DZnan87F.js} +68 -52
  28. package/dist/{logsCmd-BU8uCdys.js → logsCmd-C5y9HyrG.js} +58 -53
  29. package/dist/{manifestBuild-CEkjfpwc.js → manifestBuild-DnbFKF6w.js} +1 -1
  30. package/dist/manifestBuild-UXrnUcXP.js +2 -0
  31. package/dist/{migrate-SICulyz1.js → migrate-DtC3lu7H.js} +4 -4
  32. package/dist/{probeCommand-6HxEkNDG.js → probeCommand-C5fuN6Z2.js} +1 -1
  33. package/dist/{runtimeTrace-DgYMc09E.js → runtimeTrace-BQL_lfz6.js} +1 -1
  34. package/dist/{sdkgen-PY-umd6O.js → sdkgen-CMUPrDjH.js} +1 -1
  35. package/dist/{serveCommand-DIJ3ma76.js → serveCommand-B_w-Mpb_.js} +1013 -954
  36. package/dist/serveCommand-Dtb48ffg.js +2 -0
  37. package/dist/serveEntry.js +1 -1
  38. package/dist/{start-B9NGB8gn.js → start-YaUehtDV.js} +564 -571
  39. package/dist/{start-BFQQkL1i.js → start-s25GAIgn.js} +1 -1
  40. package/dist/startEntry.js +1 -1
  41. package/dist/{tracesCmd-BWYDqMy6.js → tracesCmd-C1qKa94g.js} +68 -63
  42. package/dist/{webDev-C2dRz9s5.js → webDev-B6ZMX42w.js} +1 -1
  43. package/dist/{webDev-C53hJdcL.js → webDev-BgdkyjP6.js} +881 -774
  44. package/dist/{webhooksCommand-uuPu8qQX.js → webhooksCommand-CYXTNvXq.js} +2 -2
  45. package/package.json +19 -19
  46. package/templates/AGENTS.md +1 -1
  47. package/templates/agent-docs/_index.md +1 -1
  48. package/templates/agent-docs/_manifest.json +1 -1
  49. package/templates/agent-docs/ai.md +1 -1
  50. package/templates/agent-docs/authentication.md +34 -19
  51. package/templates/agent-docs/cli.md +48 -0
  52. package/templates/agent-docs/configuration.md +29 -4
  53. package/templates/agent-docs/database/scaling.md +10 -1
  54. package/templates/agent-docs/deployment.md +22 -0
  55. package/templates/agent-docs/plugins/audit.md +21 -5
  56. package/templates/agent-docs/plugins/billing.md +2 -0
  57. package/templates/agent-docs/reference.md +1 -0
  58. package/templates/agent-docs/scheduling.md +23 -0
  59. package/templates/agent-docs/schema-driven-ui.md +81 -0
  60. package/templates/agent-docs/whats-new.md +100 -403
  61. package/templates/apps/api-ai/package.json +6 -6
  62. package/templates/apps/api-auth/package.json +8 -8
  63. package/templates/apps/api-backend/package.json +7 -7
  64. package/templates/apps/api-backend-deactivation/package.json +7 -7
  65. package/templates/apps/api-backend-mail/package.json +8 -8
  66. package/templates/apps/api-backend-mariadb/package.json +9 -9
  67. package/templates/apps/api-backend-sqlite/package.json +8 -8
  68. package/templates/apps/api-backend-storage/package.json +8 -8
  69. package/templates/apps/api-cms/package.json +9 -9
  70. package/templates/apps/api-collab/package.json +8 -8
  71. package/templates/apps/api-data-advanced/package.json +8 -8
  72. package/templates/apps/api-durable/package.json +8 -8
  73. package/templates/apps/api-feature-flags/package.json +9 -9
  74. package/templates/apps/api-governance/package.json +8 -8
  75. package/templates/apps/api-kv/package.json +8 -8
  76. package/templates/apps/api-moderation/package.json +8 -8
  77. package/templates/apps/api-observability/package.json +8 -8
  78. package/templates/apps/api-ratelimit/package.json +8 -8
  79. package/templates/apps/api-rbac/package.json +8 -8
  80. package/templates/apps/api-rest/package.json +7 -7
  81. package/templates/apps/api-row-history/package.json +8 -8
  82. package/templates/apps/api-saas/package.json +11 -11
  83. package/templates/apps/api-saas-starter/package.json +10 -10
  84. package/templates/apps/api-search/package.json +8 -8
  85. package/templates/apps/api-status/package.json +8 -8
  86. package/templates/apps/api-webhooks/package.json +9 -9
  87. package/templates/apps/changelog/package.json +7 -7
  88. package/templates/apps/edge-functions/package.json +2 -2
  89. package/templates/apps/frontend-admin/package.json +7 -7
  90. package/templates/apps/frontend-app/package.json +8 -8
  91. package/templates/apps/frontend-auth/package.json +7 -7
  92. package/templates/apps/frontend-blank/package.json +6 -6
  93. package/templates/apps/frontend-cms/package.json +8 -8
  94. package/templates/apps/frontend-collab/package.json +9 -9
  95. package/templates/apps/frontend-contact/package.json +7 -7
  96. package/templates/apps/frontend-dashboard/package.json +6 -6
  97. package/templates/apps/frontend-docs/package.json +8 -8
  98. package/templates/apps/frontend-i18n/package.json +6 -6
  99. package/templates/apps/frontend-landing/package.json +7 -7
  100. package/templates/apps/frontend-portal/package.json +7 -7
  101. package/templates/apps/frontend-saas/package.json +7 -7
  102. package/templates/apps/frontend-spa/package.json +6 -6
  103. package/templates/apps/frontend-ssr/package.json +6 -6
  104. package/templates/apps/frontend-ssr-api/package.json +7 -7
  105. package/templates/apps/frontend-static-blog/package.json +8 -8
  106. package/templates/apps/frontend-status/package.json +7 -7
  107. package/templates/apps/mobile-app/package.json +4 -4
  108. package/dist/apiBuild-Vw1figjO.js +0 -2
  109. package/dist/dbCommand-DrycGWWt.js +0 -2
  110. package/dist/dev-B9Gz0k85.js +0 -3
  111. package/dist/doctorCommand-BMWs6aVm.js +0 -2
  112. package/dist/frameworkTableAssembly-CGNC0qr7.js +0 -2
  113. package/dist/inspect-S2rWy1Ys.js +0 -2
  114. package/dist/manifestBuild-DIa_s6u0.js +0 -2
  115. package/dist/serveCommand-BITS8Hpj.js +0 -2
@@ -1,7 +1,7 @@
1
- import { Ht as e, Ut as t, Wt as n } from "./dev-Dw263KPu.js";
2
- import { F as r, Ft as i, Mt as a, Nt as o, Pt as s, S as c, t as l, u } from "./webDev-C53hJdcL.js";
3
- import { r as d, u as f } from "./apiBuild-Bdaetr37.js";
4
- import { x as p } from "./frameworkTableAssembly-DNOFXfEQ.js";
1
+ import { Jt as e, Xt as t, Yt as n } from "./dev-DOEJXicj.js";
2
+ import { Bt as r, Lt as i, R as a, Rt as o, S as s, t as c, u as l, zt as u } from "./webDev-BgdkyjP6.js";
3
+ import { r as d, u as f } from "./apiBuild-DDr2aNFd.js";
4
+ import { S as p } from "./frameworkTableAssembly-vfkzuzEo.js";
5
5
  import { n as m } from "./pageConvention-CMpfDN6r.js";
6
6
  import { n as h, r as g, t as ee } from "./cliOutput-D1tSBoRM.js";
7
7
  import { n as _, t as te } from "./tsconfigPaths-BWXBWgcl.js";
@@ -496,12 +496,12 @@ var le = /* @__PURE__ */ new Set([
496
496
  }, qe = (e, t) => {
497
497
  let n = ["middleware.ts", "middleware.tsx"].find((t) => S(v(e, t)));
498
498
  if (n === void 0) return;
499
- let i = new D({
499
+ let r = new D({
500
500
  useInMemoryFileSystem: !0,
501
501
  skipAddingFilesFromTsConfig: !0
502
- }), a;
502
+ }), i;
503
503
  try {
504
- a = i.createSourceFile(n, C(v(e, n), "utf8"));
504
+ i = r.createSourceFile(n, C(v(e, n), "utf8"));
505
505
  } catch {
506
506
  return {
507
507
  problems: [],
@@ -511,7 +511,7 @@ var le = /* @__PURE__ */ new Set([
511
511
  };
512
512
  }
513
513
  let o = [], s = [], c = 0;
514
- for (let e of a.getVariableDeclarations()) {
514
+ for (let e of i.getVariableDeclarations()) {
515
515
  let t = e.getInitializerIfKind(O.CallExpression);
516
516
  if (t?.getExpression().getText() !== "defineMiddleware") continue;
517
517
  c += 1;
@@ -535,7 +535,7 @@ var le = /* @__PURE__ */ new Set([
535
535
  });
536
536
  }
537
537
  return {
538
- problems: r(o, t).map((e) => e.message),
538
+ problems: a(o, t).map((e) => e.message),
539
539
  unreadable: s,
540
540
  seen: c,
541
541
  file: n
@@ -863,7 +863,7 @@ var le = /* @__PURE__ */ new Set([
863
863
  r !== void 0 && o.push(r);
864
864
  }
865
865
  return o;
866
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => Ft.test(y(n, e))), l = new Set(c);
866
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => zt.test(y(n, e))), l = new Set(c);
867
867
  for (; c.length > 0;) {
868
868
  let e = c.shift();
869
869
  if (!s.has(e)) {
@@ -891,7 +891,7 @@ var le = /* @__PURE__ */ new Set([
891
891
  }, kt = (e, t) => {
892
892
  let n = t.getSymbol()?.getDeclarations()?.[0];
893
893
  return n === void 0 ? [] : e.getDescendantsOfKind(O.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
894
- }, At = /^(insert|insertMany|insertIgnore|update|updateMany|upsert|delete|deleteMany|hardDelete|patchJson)$/, jt = /^(notify|sendNotification|sendWebhook|dispatchWebhook|sendMail|trigger[A-Z]\w*)$/, Mt = (e, t) => {
894
+ }, At = /^(insert|insertMany|insertIgnore|update|updateMany|upsert|delete|deleteMany|hardDelete|patchJson)$/, jt = /^(notify|sendNotification|sendWebhook|dispatchWebhook|sendMail|trigger)\w*$/, Mt = (e, t) => {
895
895
  if (!E.isObjectLiteralExpression(e)) return;
896
896
  let n = e.getProperty("handler");
897
897
  if (n === void 0 || !E.isPropertyAssignment(n)) return;
@@ -913,17 +913,97 @@ var le = /* @__PURE__ */ new Set([
913
913
  n === "publish" && t.push(RegExp(`^${r}$`)), n === "store" && t.push(RegExp(`^${r}\\.${At.source.replace(/^\^|\$$/g, "")}$`));
914
914
  }
915
915
  return t;
916
- }, Pt = (e) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").some((t) => {
916
+ }, Pt = (e, t, n, r = 0, i = /* @__PURE__ */ new Set()) => {
917
+ let a = e.getDescendantsOfKind(O.CallExpression);
918
+ for (let e of a) {
919
+ let t = e.getExpression().getText();
920
+ if (n.some((e) => e.test(t))) return !0;
921
+ }
922
+ if (r >= 2) return !1;
923
+ for (let e of a) {
924
+ let a = e.getExpression().getText();
925
+ if (!/^[A-Za-z_$][\w$]*$/.test(a) || i.has(a)) continue;
926
+ i.add(a);
927
+ let o = t.getFunction(a) ?? (() => {
928
+ let e = t.getVariableDeclaration(a)?.getInitializer();
929
+ return e !== void 0 && (E.isArrowFunction(e) || E.isFunctionExpression(e)) ? e : void 0;
930
+ })();
931
+ if (o !== void 0 && Pt(o, t, [...n, ...Nt(o)], r + 1, i)) return !0;
932
+ }
933
+ return !1;
934
+ }, Ft = (e) => {
935
+ let t = /* @__PURE__ */ new Set();
936
+ for (let n of e.values()) {
937
+ if (!/\bsetRowFilter\s*[<(]/.test(n)) continue;
938
+ let e = /\btables\s*:\s*\[([^\]]*)\]/.exec(n);
939
+ if (e !== null) for (let n of e[1].matchAll(/['"`]([A-Za-z_][\w]*)['"`]/g)) t.add(n[1]);
940
+ }
941
+ return t;
942
+ }, It = (e) => {
943
+ if (!/\.(query|stream|mutation|action)\.server\.tsx?$/.test(e.path)) return !1;
944
+ let t = Ft(e.sources);
945
+ return t.size === 0 || ![...e.src.matchAll(/\btable\s*:\s*['"`]([A-Za-z_][\w]*)['"`]/g)].map((e) => e[1]).some((e) => t.has(e)) ? !1 : /\b(eq|and|inSet)\s*\(/.test(e.src) || /\bwhere\s*:/.test(e.src);
946
+ }, Lt = (e) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").some((t) => {
917
947
  let n = t.getArguments()[0];
918
948
  if (n === void 0 || !E.isObjectLiteralExpression(n) || n.getProperty("once") !== void 0) return !1;
919
949
  let r = Mt(n, e);
920
- if (r === void 0) return !1;
921
- let i = Nt(r);
922
- return r.getDescendantsOfKind(O.CallExpression).some((e) => {
923
- let t = e.getExpression().getText();
924
- return i.some((e) => e.test(t));
925
- });
926
- }), Ft = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, It = /\.(query|mutation|action|stream)\.server\.tsx?$/, Lt = [
950
+ return r !== void 0 && Pt(r, e, Nt(r));
951
+ }), Rt = (e) => e.getDescendantsOfKind(O.CallExpression).filter((e) => e.getExpression().getText() === "defineSubscriber").filter((e) => {
952
+ let t = e.getArguments()[0];
953
+ return t !== void 0 && E.isObjectLiteralExpression(t) && t.getProperty("once") === void 0;
954
+ }).length, zt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Bt = (e, t) => {
955
+ let n;
956
+ try {
957
+ n = t.createSourceFile("/descriptor-scan.ts", e, { overwrite: !0 });
958
+ } catch {
959
+ return;
960
+ }
961
+ for (let e of n.getDescendantsOfKind(O.PropertyAssignment)) {
962
+ if (e.getName() !== "output") continue;
963
+ let t = e.getInitializer();
964
+ if (t !== void 0 && E.isCallExpression(t) && /\.Array$/.test(t.getExpression().getText()) && (t = t.getArguments()[0]), t === void 0 || !E.isCallExpression(t) || !/\.Struct$/.test(t.getExpression().getText())) return;
965
+ let n = t.getArguments()[0];
966
+ if (n === void 0 || !E.isObjectLiteralExpression(n)) return;
967
+ let r = [];
968
+ for (let e of [n, ...n.getDescendantsOfKind(O.ObjectLiteralExpression)]) for (let t of e.getProperties()) if (E.isPropertyAssignment(t) || E.isShorthandPropertyAssignment(t)) r.push(t.getName());
969
+ else if (E.isSpreadAssignment(t)) return;
970
+ return r;
971
+ }
972
+ }, Vt = (e, t) => {
973
+ let n = [], r = (e) => {
974
+ e !== void 0 && (E.isObjectLiteralExpression(e) ? n.push(e) : E.isParenthesizedExpression(e) && r(e.getExpression()));
975
+ };
976
+ for (let t of e.getDescendantsOfKind(O.ReturnStatement)) r(t.getExpression());
977
+ for (let t of e.getDescendantsOfKind(O.ArrowFunction)) r(t.getBody());
978
+ let i = /* @__PURE__ */ new Set();
979
+ for (let e of n) {
980
+ let n = [];
981
+ for (let t of e.getProperties()) if (E.isPropertyAssignment(t) || E.isShorthandPropertyAssignment(t)) n.push(t.getName());
982
+ else if (E.isSpreadAssignment(t)) return;
983
+ if (n.some((e) => t.has(e))) for (let e of n) i.add(e);
984
+ }
985
+ return [...i];
986
+ }, Ht = (e, t, n) => {
987
+ let r = RegExp(`\\btable\\(\\s*['"\`]${e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"\`]`);
988
+ for (let [, i] of t) {
989
+ if (!r.test(i)) continue;
990
+ let t;
991
+ try {
992
+ t = n.createSourceFile("/entity-scan.ts", i, { overwrite: !0 });
993
+ } catch {
994
+ continue;
995
+ }
996
+ for (let n of t.getDescendantsOfKind(O.CallExpression)) {
997
+ if (n.getExpression().getText() !== "table") continue;
998
+ let [t, r] = n.getArguments();
999
+ if (t === void 0 || !E.isStringLiteral(t) || t.getLiteralValue() !== e) continue;
1000
+ if (r === void 0 || !E.isObjectLiteralExpression(r)) return;
1001
+ let i = /* @__PURE__ */ new Set();
1002
+ for (let e of r.getProperties()) E.isPropertyAssignment(e) && /\.nullable\s*\(/.test(e.getText()) && i.add(e.getName());
1003
+ return i;
1004
+ }
1005
+ }
1006
+ }, Ut = (e) => /\bNullOr\b|\bSchema\.Null\b|\bnullable\s*\(/.test(e), P = /\.(query|mutation|action|stream)\.server\.tsx?$/, Wt = [
927
1007
  "Id",
928
1008
  "ID",
929
1009
  "Hash",
@@ -949,7 +1029,7 @@ var le = /* @__PURE__ */ new Set([
949
1029
  "Comment",
950
1030
  "Provider",
951
1031
  "Format"
952
- ], Rt = RegExp(`(?:${Lt.join("|")})$|_(?:${Lt.map((e) => e.toLowerCase()).join("|")})$`), zt = [
1032
+ ], Gt = RegExp(`(?:${Wt.join("|")})$|_(?:${Wt.map((e) => e.toLowerCase()).join("|")})$`), Kt = [
953
1033
  {
954
1034
  id: "hardcoded-framework-cookie",
955
1035
  scope: "client",
@@ -962,7 +1042,7 @@ var le = /* @__PURE__ */ new Set([
962
1042
  scope: "server",
963
1043
  smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
964
1044
  use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
965
- match: ({ src: e, path: t }) => It.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
1045
+ match: ({ src: e, path: t }) => P.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
966
1046
  },
967
1047
  {
968
1048
  id: "subject-write-no-guard",
@@ -971,7 +1051,7 @@ var le = /* @__PURE__ */ new Set([
971
1051
  use: "declare `guards:` on the descriptor, or — if the procedure really is open — compare the loaded row's owner against `ctx.request.subject.id` and fail with a typed error",
972
1052
  match: (e) => {
973
1053
  let { src: t, path: n } = e;
974
- if (!It.test(n)) return !1;
1054
+ if (!P.test(n)) return !1;
975
1055
  let r = M(t);
976
1056
  if (!(/\bsubject\s*\.\s*id\b/.test(r) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(r)) || /\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(r) || /[=!]==?\s*[^;\n]*\bsubject\s*\.\s*id\b|\bsubject\s*\.\s*id\b\s*[=!]==/.test(r)) return !1;
977
1057
  let i = wt(e);
@@ -985,7 +1065,7 @@ var le = /* @__PURE__ */ new Set([
985
1065
  scope: "server",
986
1066
  smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
987
1067
  use: "yield* HttpClient — the framework client. It injects traceparent, retries, and honours `http.allowHosts`. Where the URL is built from anything a caller supplied it ALSO blocks loopback / RFC-1918 / link-local targets (incl. 169.254.169.254) on the initial request AND every redirect hop — for a hard-coded host that half is redundant, and the first three are still the reason to switch",
988
- match: ({ file: e, path: t, serverOnly: n }) => (Ft.test(t) || n) && N(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
1068
+ match: ({ file: e, path: t, serverOnly: n }) => (zt.test(t) || n) && N(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
989
1069
  },
990
1070
  {
991
1071
  id: "form",
@@ -1103,7 +1183,7 @@ var le = /* @__PURE__ */ new Set([
1103
1183
  use: ".encrypted() on the column — boot fails loudly if no cipher is configured, so it can never silently persist plaintext (a *Hash column and a vault* handle are NOT plaintext secrets: the hash IS the protection — often a unique lookup key — and the handle only names a secret held elsewhere, so both are skipped)",
1104
1184
  match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
1105
1185
  let n = t[1];
1106
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Rt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1186
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Gt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1107
1187
  }))
1108
1188
  },
1109
1189
  {
@@ -1113,15 +1193,60 @@ var le = /* @__PURE__ */ new Set([
1113
1193
  use: "you cannot look a value up by equality AND encrypt it non-deterministically — `.encrypted()` would make this column unfindable. Store the HASH as the unique lookup key and keep the secret itself either `.encrypted()` in a second column or nowhere (the framework's own `_voltro_api_keys.hashedKey` is the shape: the presented value is hashed and compared, never read back). A deterministic cipher is the other option and buys equality lookup at the cost of leaking which rows share a value",
1114
1194
  match: ({ src: e }) => e.split("\n").some((e) => [...e.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*(?:text|varchar|string|longtext)\s*\(/g)].some((t) => {
1115
1195
  let n = t[1];
1116
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Rt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1196
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Gt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1117
1197
  }))
1118
1198
  },
1199
+ {
1200
+ id: "nullable-column-a-mutation-cannot-clear",
1201
+ scope: "server",
1202
+ smell: "a mutation writes a NULLABLE column through an input field that does not accept `null` — the value can be set once and never cleared",
1203
+ use: "declare the field as `Schema.optional(Schema.NullOr(...))` — a column the schema lets be empty needs a wire shape that can say so",
1204
+ match: (e) => {
1205
+ if (!/\.mutation\.tsx?$/.test(e.path)) return !1;
1206
+ let t = /target\s*:\s*\{[^}]*\btable\s*:\s*['"`]([^'"`]+)['"`]/.exec(e.src)?.[1];
1207
+ if (t === void 0) return !1;
1208
+ let n = Ht(t, e.sources, e.file.getProject());
1209
+ if (n === void 0 || n.size === 0) return !1;
1210
+ for (let t of e.file.getDescendantsOfKind(O.PropertyAssignment)) {
1211
+ if (t.getName() !== "input") continue;
1212
+ let e = t.getInitializer();
1213
+ if (e === void 0 || !E.isCallExpression(e) || !/\.Struct$/.test(e.getExpression().getText())) continue;
1214
+ let r = e.getArguments()[0];
1215
+ if (!(r === void 0 || !E.isObjectLiteralExpression(r))) {
1216
+ for (let e of r.getProperties()) if (E.isPropertyAssignment(e) && n.has(e.getName()) && !Ut(e.getText())) return !0;
1217
+ }
1218
+ }
1219
+ return !1;
1220
+ }
1221
+ },
1222
+ {
1223
+ id: "executor-builds-an-undeclared-field",
1224
+ scope: "server",
1225
+ smell: "an executor builds a field its descriptor's `output` does not declare (silently stripped on the way out)",
1226
+ use: "add the field to the descriptor's `output` struct, or stop building it — `output` IS the serializer, so an undeclared key never reaches the caller",
1227
+ match: (e) => {
1228
+ if (!P.test(e.path)) return !1;
1229
+ let t = wt(e);
1230
+ if (t === void 0) return !1;
1231
+ let n = Bt(t, e.file.getProject());
1232
+ if (n === void 0 || n.length === 0) return !1;
1233
+ let r = Vt(e.file, new Set(n));
1234
+ return r === void 0 || r.length === 0 ? !1 : r.some((e) => !n.includes(e));
1235
+ }
1236
+ },
1119
1237
  {
1120
1238
  id: "subscriber-effect-without-once",
1121
1239
  scope: "server",
1122
1240
  smell: "a *.subscribe.ts handler that WRITES or publishes with no `once:` (it runs on every replica, so the effect repeats once per replica)",
1123
1241
  use: "once: true on defineSubscriber — one INSERT-wins claim per change, fleet-wide (or `once: (event) => key` for a coarser key). A hand-rolled `insertIgnore` dedupe is this, written out.",
1124
- match: ({ file: e }) => Pt(e)
1242
+ match: ({ file: e }) => Lt(e)
1243
+ },
1244
+ {
1245
+ id: "row-filter-duplicated-by-hand",
1246
+ scope: "server",
1247
+ smell: "a handler that constrains a table your registered `setRowFilter` already constrains",
1248
+ use: "drop the hand-written predicate — the filter covers the query, the mutation, the stream AND the subscription delivery. Keep it only if this handler must be NARROWER than the filter, and say so in a comment (two identical predicates ANDed are as tight as one, so this is maintenance, not a leak)",
1249
+ match: (e) => It(e)
1125
1250
  },
1126
1251
  {
1127
1252
  id: "mutation-tail-effect",
@@ -1196,14 +1321,14 @@ var le = /* @__PURE__ */ new Set([
1196
1321
  use: "timestampMs / timestampMsOrNull from @voltro/database/wire in the descriptor's output struct — `output` IS the serializer (it is the rpc success schema), so a Date field declared as timestampMs crosses as epoch ms on its own; declaring Schema.Number describes the WIRE type and leaves nothing to convert",
1197
1322
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1198
1323
  }
1199
- ], P = (e) => L(e), Bt = (e, t) => {
1324
+ ], F = (e) => L(e), qt = (e, t) => {
1200
1325
  let n = /* @__PURE__ */ new Set();
1201
1326
  for (let r of t) {
1202
1327
  let t = y(e, r), i = t.split(/[\\/]/)[0];
1203
1328
  n.add(i !== void 0 && i !== t ? i : ".");
1204
1329
  }
1205
1330
  return [...n].sort();
1206
- }, F = /\.(tsx?|jsx?)$/, I = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Vt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Ht = (e, t = [], n = 0) => {
1331
+ }, Jt = /\.(tsx?|jsx?)$/, I = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Yt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Xt = (e, t = [], n = 0) => {
1207
1332
  if (n > 12 || I.test(e)) return t;
1208
1333
  let r;
1209
1334
  try {
@@ -1215,7 +1340,7 @@ var le = /* @__PURE__ */ new Set([
1215
1340
  let r = v(e, i.name);
1216
1341
  if (i.isDirectory()) {
1217
1342
  if (i.name.startsWith(".")) continue;
1218
- Ht(r, t, n + 1);
1343
+ Xt(r, t, n + 1);
1219
1344
  } else je.test(i.name) && t.push(r);
1220
1345
  }
1221
1346
  return t;
@@ -1234,10 +1359,10 @@ var le = /* @__PURE__ */ new Set([
1234
1359
  } catch {
1235
1360
  continue;
1236
1361
  }
1237
- a.isDirectory() ? L(r, t, n + 1) : F.test(i) && !/\.(test|spec)\./.test(i) && !Vt.test(i) && t.push(r);
1362
+ a.isDirectory() ? L(r, t, n + 1) : Jt.test(i) && !/\.(test|spec)\./.test(i) && !Yt.test(i) && t.push(r);
1238
1363
  }
1239
1364
  return t;
1240
- }, Ut = (e, t = [], n = 0) => {
1365
+ }, Zt = (e, t = [], n = 0) => {
1241
1366
  if (n > 12 || I.test(e)) return t;
1242
1367
  let r;
1243
1368
  try {
@@ -1252,17 +1377,17 @@ var le = /* @__PURE__ */ new Set([
1252
1377
  } catch {
1253
1378
  continue;
1254
1379
  }
1255
- a.isDirectory() ? Ut(r, t, n + 1) : F.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1380
+ a.isDirectory() ? Zt(r, t, n + 1) : Jt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1256
1381
  }
1257
1382
  return t;
1258
- }, Wt = (e) => {
1259
- let t = P(e);
1383
+ }, Qt = (e) => {
1384
+ let t = F(e);
1260
1385
  return {
1261
- dirs: Bt(e, t),
1386
+ dirs: qt(e, t),
1262
1387
  fileCount: t.length
1263
1388
  };
1264
- }, Gt = (e) => {
1265
- let t = P(e);
1389
+ }, $t = (e) => {
1390
+ let t = F(e);
1266
1391
  if (t.length === 0) return [];
1267
1392
  let n = new D({
1268
1393
  useInMemoryFileSystem: !0,
@@ -1272,7 +1397,7 @@ var le = /* @__PURE__ */ new Set([
1272
1397
  i.set(e, C(e, "utf8"));
1273
1398
  } catch {}
1274
1399
  let a = Tt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1275
- for (let t of Ut(e)) try {
1400
+ for (let t of Zt(e)) try {
1276
1401
  o.set(t, C(t, "utf8"));
1277
1402
  } catch {}
1278
1403
  for (let s of t) {
@@ -1292,23 +1417,34 @@ var le = /* @__PURE__ */ new Set([
1292
1417
  } catch {
1293
1418
  continue;
1294
1419
  }
1295
- for (let t of zt) {
1420
+ for (let t of Kt) {
1296
1421
  if (t.plugin !== void 0 && Dt(c, t.plugin) || !t.match(c)) continue;
1297
1422
  let n = r.get(t.id) ?? [];
1298
1423
  n.push(y(e, s)), r.set(t.id, n);
1299
1424
  }
1300
1425
  }
1301
- return zt.filter((e) => r.has(e.id)).map((e) => ({
1426
+ return Kt.filter((e) => r.has(e.id)).map((e) => ({
1302
1427
  rule: e,
1303
1428
  files: r.get(e.id)
1304
1429
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1430
+ }, en = (e) => {
1431
+ let t = new D({
1432
+ useInMemoryFileSystem: !0,
1433
+ skipAddingFilesFromTsConfig: !0
1434
+ }), n = 0;
1435
+ for (let r of F(e)) if (/\.subscribe\.tsx?$/.test(r)) try {
1436
+ let e = t.createSourceFile(`/count${r.endsWith("x") ? ".tsx" : ".ts"}`, C(r, "utf8"), { overwrite: !0 });
1437
+ n += Rt(e);
1438
+ } catch {}
1439
+ return n;
1305
1440
  }, R = (e) => {
1306
- let t = Wt(e);
1441
+ let t = Qt(e);
1307
1442
  return {
1308
1443
  root: e,
1309
1444
  scannedFiles: t.fileCount,
1310
1445
  scannedDirs: t.dirs,
1311
- findings: Gt(e).map(({ rule: e, files: t }) => ({
1446
+ subscribersWithoutOnce: en(e),
1447
+ findings: $t(e).map(({ rule: e, files: t }) => ({
1312
1448
  id: e.id,
1313
1449
  scope: e.scope,
1314
1450
  smell: e.smell,
@@ -1316,13 +1452,13 @@ var le = /* @__PURE__ */ new Set([
1316
1452
  files: t
1317
1453
  }))
1318
1454
  };
1319
- }, Kt = 3, qt = .2, Jt = 50, Yt = (e, t) => e >= Jt && t > 0 && e / t >= qt, Xt = (e) => {
1455
+ }, tn = 3, nn = .2, rn = 50, an = (e, t) => e >= rn && t > 0 && e / t >= nn, on = (e) => {
1320
1456
  let t = /* @__PURE__ */ new Map();
1321
1457
  for (let n of L(e)) try {
1322
1458
  t.set(y(e, n), C(n, "utf8"));
1323
1459
  } catch {}
1324
1460
  let n = /* @__PURE__ */ new Map();
1325
- for (let t of Ht(e)) try {
1461
+ for (let t of Xt(e)) try {
1326
1462
  n.set(y(e, t), C(t, "utf8"));
1327
1463
  } catch {}
1328
1464
  let r = Me({
@@ -1330,7 +1466,7 @@ var le = /* @__PURE__ */ new Set([
1330
1466
  envFiles: n
1331
1467
  });
1332
1468
  r !== void 0 && (console.log(""), console.log(Ne(r)));
1333
- }, Zt = (e) => {
1469
+ }, sn = (e) => {
1334
1470
  let t = R(e);
1335
1471
  if (console.log(""), t.scannedFiles === 0) return console.log(`\u2022 hand-roll scan: no source directories found under ${e}.`), console.log(" Walked the app root recursively (node_modules / dist / .framework pruned). Run `voltro doctor <app>` against an app root."), 0;
1336
1472
  if (t.findings.length === 0) return console.log(`\u2713 hand-roll scan: ${t.scannedFiles} file${t.scannedFiles === 1 ? "" : "s"} across ${t.scannedDirs.join(", ")} \u2014 no hand-rolled primitives found.`), 0;
@@ -1342,37 +1478,43 @@ var le = /* @__PURE__ */ new Set([
1342
1478
  console.log(` [${e}]`);
1343
1479
  let i = [...r].sort((e, t) => e.files.length - t.files.length);
1344
1480
  for (let e of i) {
1345
- if (Yt(e.files.length, t.scannedFiles)) {
1481
+ if (an(e.files.length, t.scannedFiles)) {
1346
1482
  let n = Math.round(e.files.length / t.scannedFiles * 100);
1347
1483
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s), ${n}% of those scanned: a codebase-wide pattern rather than a work list`), console.log(` \u2192 ${e.use}`), console.log(" (paths omitted on purpose at this scale — `voltro doctor --json` has every one.)");
1348
1484
  continue;
1349
1485
  }
1350
- let r = e.files.slice(0, Kt).join(", "), i = e.files.length - Kt;
1486
+ let r = e.files.slice(0, tn).join(", "), i = e.files.length - tn;
1351
1487
  i > 0 && (n += i);
1352
1488
  let a = i > 0 ? `, +${i} more` : "";
1353
1489
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1354
1490
  }
1355
1491
  }
1356
- return n > 0 && console.log(` (${n} more file path${n === 1 ? "" : "s"} not shown \u2014 \`voltro doctor --json\` prints every one.)`), console.log(" (See the \"Pick the SERVER primitive\" / \"Pick the CLIENT primitive\" rubrics in AGENTS.md / CLAUDE.md.)"), t.findings.length;
1357
- }, Qt = "renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.", $t = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, en = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, tn = (e) => {
1358
- let t = $t.exec(e)?.[1];
1492
+ n > 0 && console.log(` (${n} more file path${n === 1 ? "" : "s"} not shown \u2014 \`voltro doctor --json\` prints every one.)`);
1493
+ let r = t.findings.find((e) => e.id === "subscriber-effect-without-once");
1494
+ if (t.subscribersWithoutOnce > 0) {
1495
+ let e = r?.files.length ?? 0;
1496
+ console.log(` \u24d8 ${t.subscribersWithoutOnce} subscriber(s) declare no \`once:\`; an effect was found in ${e}. The rule reads each subscriber's OWN file (its handler and the functions that file declares) and does not resolve imports — a write in another module is unexamined, not absent.`);
1497
+ }
1498
+ return console.log(" (See the \"Pick the SERVER primitive\" / \"Pick the CLIENT primitive\" rubrics in AGENTS.md / CLAUDE.md.)"), t.findings.length;
1499
+ }, cn = "renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.", ln = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, un = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, dn = (e) => {
1500
+ let t = ln.exec(e)?.[1];
1359
1501
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1360
- let n = en.exec(e)?.[1];
1502
+ let n = un.exec(e)?.[1];
1361
1503
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1362
1504
  }, z = async (e) => {
1363
1505
  let t = v(e, "src", "pages");
1364
1506
  if (!S(t)) return [];
1365
- let { pages: n, dirs: r } = await c(t), i = [];
1507
+ let { pages: n, dirs: r } = await s(t), i = [];
1366
1508
  for (let a of n) {
1367
- if (!u(r, a.dirChain).some((e) => e.hasLayout)) continue;
1509
+ if (!l(r, a.dirChain).some((e) => e.hasLayout)) continue;
1368
1510
  let n;
1369
1511
  try {
1370
1512
  n = C(v(t, a.file), "utf8");
1371
1513
  } catch {
1372
1514
  continue;
1373
1515
  }
1374
- if (l.test(n)) continue;
1375
- let o = tn(n);
1516
+ if (c.test(n)) continue;
1517
+ let o = dn(n);
1376
1518
  o !== null && i.push({
1377
1519
  file: y(e, v(t, a.file)),
1378
1520
  pattern: a.pattern,
@@ -1380,20 +1522,20 @@ var le = /* @__PURE__ */ new Set([
1380
1522
  });
1381
1523
  }
1382
1524
  return i;
1383
- }, nn = 10, rn = async (e) => {
1525
+ }, fn = 10, pn = async (e) => {
1384
1526
  console.log("");
1385
1527
  let t = v(e, "src", "pages");
1386
1528
  if (!S(t)) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
1387
1529
  let n = await z(e);
1388
1530
  if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1389
1531
  console.log(`• renderMode:'spa' candidates (${n.length} page${n.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1390
- for (let e of n.slice(0, nn)) {
1532
+ for (let e of n.slice(0, fn)) {
1391
1533
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1392
1534
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1393
1535
  }
1394
- let r = n.length - nn;
1395
- return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${Qt}`), n.length;
1396
- }, an = /* @__PURE__ */ new Set([
1536
+ let r = n.length - fn;
1537
+ return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${cn}`), n.length;
1538
+ }, mn = /* @__PURE__ */ new Set([
1397
1539
  "node_modules",
1398
1540
  "dist",
1399
1541
  "build",
@@ -1401,7 +1543,7 @@ var le = /* @__PURE__ */ new Set([
1401
1543
  ".vite",
1402
1544
  ".framework",
1403
1545
  ".git"
1404
- ]), on = (e, t, n) => {
1546
+ ]), hn = (e, t, n) => {
1405
1547
  let r;
1406
1548
  try {
1407
1549
  r = w(e, { withFileTypes: !0 });
@@ -1409,11 +1551,11 @@ var le = /* @__PURE__ */ new Set([
1409
1551
  return;
1410
1552
  }
1411
1553
  for (let i of r) {
1412
- if (an.has(i.name)) continue;
1554
+ if (mn.has(i.name)) continue;
1413
1555
  let r = v(e, i.name);
1414
- i.isDirectory() ? on(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1556
+ i.isDirectory() ? hn(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1415
1557
  }
1416
- }, sn = (e, t) => {
1558
+ }, gn = (e, t) => {
1417
1559
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1418
1560
  for (let i of t) {
1419
1561
  let t;
@@ -1438,16 +1580,16 @@ var le = /* @__PURE__ */ new Set([
1438
1580
  }
1439
1581
  }
1440
1582
  return n;
1441
- }, cn = (e, t) => {
1583
+ }, _n = (e, t) => {
1442
1584
  let n = e.slice(t.length).split(x).slice(0, -1)[0];
1443
1585
  return n !== void 0 && n.startsWith("[");
1444
1586
  }, B = (e) => {
1445
1587
  let t = [], n = v(e, "src", "pages"), r = v(e, "src");
1446
1588
  if (!S(r)) return t;
1447
1589
  let i = [];
1448
- on(r, e, i), i.sort();
1590
+ hn(r, e, i), i.sort();
1449
1591
  let a = `${y(e, n)}${x}`;
1450
- sn(e, i);
1592
+ gn(e, i);
1451
1593
  for (let n of i) {
1452
1594
  let r = n.slice(n.lastIndexOf(x) + 1);
1453
1595
  if (n.startsWith(a)) {
@@ -1463,7 +1605,7 @@ var le = /* @__PURE__ */ new Set([
1463
1605
  file: n
1464
1606
  });
1465
1607
  } catch {}
1466
- cn(n, a) && t.push({
1608
+ _n(n, a) && t.push({
1467
1609
  kind: "top-level-dynamic-route",
1468
1610
  file: n
1469
1611
  });
@@ -1475,35 +1617,35 @@ var le = /* @__PURE__ */ new Set([
1475
1617
  });
1476
1618
  }
1477
1619
  return t;
1478
- }, V = 10, H = (e, t, n) => {
1620
+ }, vn = 10, V = (e, t, n) => {
1479
1621
  if (t.length === 0) return;
1480
1622
  console.log(`• ${e} (${t.length}):`);
1481
- for (let e of t.slice(0, V)) console.log(` ${e}`);
1482
- let r = t.length - V;
1623
+ for (let e of t.slice(0, vn)) console.log(` ${e}`);
1624
+ let r = t.length - vn;
1483
1625
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
1484
- }, ln = (e) => {
1626
+ }, yn = (e) => {
1485
1627
  console.log("");
1486
1628
  let t = v(e, "src", "pages");
1487
1629
  if (!S(t)) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
1488
1630
  let n = B(e);
1489
1631
  if (n.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
1490
1632
  let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
1491
- return H("will NEVER route — page.tsx outside src/pages/", r("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), H("pages with no test beside them", r("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), H("routes whose FIRST segment is dynamic — they claim every URL of that depth", r("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), H("pages with no `meta` export — they serve the APP name as their title", r("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). If such a page also has no real body — it redirects from an effect, say — the missing title is the symptom rather than the bug: on an `ssr` route the server answers 200 with an empty document and the redirect happens only once JavaScript runs. Move it into the loader (`redirect(...)`, a real 303). Advisory."), n.length;
1492
- }, un = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", dn = /* @__PURE__ */ new Map(), U = (e) => {
1493
- let t = dn.get(e);
1633
+ return V("will NEVER route — page.tsx outside src/pages/", r("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), V("pages with no test beside them", r("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), V("routes whose FIRST segment is dynamic — they claim every URL of that depth", r("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), V("pages with no `meta` export — they serve the APP name as their title", r("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). If such a page also has no real body — it redirects from an effect, say — the missing title is the symptom rather than the bug: on an `ssr` route the server answers 200 with an empty document and the redirect happens only once JavaScript runs. Move it into the loader (`redirect(...)`, a real 303). Advisory."), n.length;
1634
+ }, bn = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", xn = /* @__PURE__ */ new Map(), H = (e) => {
1635
+ let t = xn.get(e);
1494
1636
  if (t !== void 0) return t;
1495
1637
  let n = (async () => {
1496
- let { walk: t, loadDiscovered: n } = await import("./dev-B9Gz0k85.js"), r = await t(e);
1638
+ let { walk: t, loadDiscovered: n } = await import("./dev-Bl9HqtV7.js"), r = await t(e);
1497
1639
  return {
1498
1640
  files: r,
1499
- discovered: await n(r)
1641
+ discovered: await n(r, { root: e })
1500
1642
  };
1501
1643
  })();
1502
- return dn.set(e, n), n;
1503
- }, fn = async (e) => {
1644
+ return xn.set(e, n), n;
1645
+ }, Sn = async (e) => {
1504
1646
  let t;
1505
1647
  try {
1506
- let { files: n, discovered: r } = await U(e), i = r.tables.map((e) => {
1648
+ let { files: n, discovered: r } = await H(e), i = r.tables.map((e) => {
1507
1649
  let t = e;
1508
1650
  return {
1509
1651
  name: t.tableName,
@@ -1516,7 +1658,7 @@ var le = /* @__PURE__ */ new Set([
1516
1658
  return;
1517
1659
  }
1518
1660
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
1519
- }, pn = (e) => e.map((e) => {
1661
+ }, Cn = (e) => e.map((e) => {
1520
1662
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
1521
1663
  name: e,
1522
1664
  type: typeof t?.type == "string" ? t.type : ""
@@ -1526,13 +1668,13 @@ var le = /* @__PURE__ */ new Set([
1526
1668
  columns: n,
1527
1669
  primaryKey: t.appliedPrimaryKey ?? []
1528
1670
  };
1529
- }), mn = async (e) => {
1671
+ }), wn = async (e) => {
1530
1672
  try {
1531
- let { missingRelationSources: t } = await import("./doctorSourceRelations-c5mlN5HI.js"), { sourceKeys: n } = await import("@voltro/protocol"), { readFile: r } = await import("node:fs/promises"), { discovered: i } = await U(e), a = [], o = [];
1673
+ let { missingRelationSources: t } = await import("./doctorSourceRelations-c5mlN5HI.js"), { sourceKeys: n } = await import("@voltro/protocol"), { readFile: r } = await import("node:fs/promises"), { discovered: i } = await H(e), a = [], o = [];
1532
1674
  for (let e of i.queries) {
1533
1675
  let i = n(e.descriptor.source);
1534
1676
  if (i.length === 0) continue;
1535
- let s = hn(e.file);
1677
+ let s = Tn(e.file);
1536
1678
  if (s === void 0) continue;
1537
1679
  let c = await r(s, "utf8").catch(() => null);
1538
1680
  if (c === null) continue;
@@ -1549,8 +1691,8 @@ var le = /* @__PURE__ */ new Set([
1549
1691
  } catch {
1550
1692
  return null;
1551
1693
  }
1552
- }, hn = (e) => e.endsWith(".ts") && !e.endsWith(".server.ts") ? `${e.slice(0, -3)}.server.ts` : void 0, gn = async (e) => {
1553
- let t = await mn(e);
1694
+ }, Tn = (e) => e.endsWith(".ts") && !e.endsWith(".server.ts") ? `${e.slice(0, -3)}.server.ts` : void 0, En = async (e) => {
1695
+ let t = await wn(e);
1554
1696
  if (t === null) return;
1555
1697
  let n = t.findings;
1556
1698
  if (n.length > 0) {
@@ -1563,21 +1705,21 @@ var le = /* @__PURE__ */ new Set([
1563
1705
  for (let e of t.unresolved) console.log(` ${e.procedure}: ${e.relations.map((e) => `\`${e}\``).join(", ")} resolved against no table`);
1564
1706
  console.log(" declare the base table in `source:` and re-run — relations under it are unchecked until then.");
1565
1707
  }
1566
- }, W = async (e) => {
1708
+ }, U = async (e) => {
1567
1709
  try {
1568
- let { discovered: t } = await U(e);
1569
- return ve(pn(t.tables));
1710
+ let { discovered: t } = await H(e);
1711
+ return ve(Cn(t.tables));
1570
1712
  } catch {
1571
1713
  return null;
1572
1714
  }
1573
- }, _n = async (e) => {
1574
- let t = await W(e);
1715
+ }, Dn = async (e) => {
1716
+ let t = await U(e);
1575
1717
  if (t === null || t.tablesChecked === 0) return;
1576
1718
  let n = be(t);
1577
1719
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
1578
- }, G = async (e) => {
1720
+ }, W = async (e) => {
1579
1721
  try {
1580
- let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await U(e);
1722
+ let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await H(e);
1581
1723
  return we(n.events.map((e) => {
1582
1724
  let n = e.descriptor, r = t(n.key);
1583
1725
  return {
@@ -1590,19 +1732,19 @@ var le = /* @__PURE__ */ new Set([
1590
1732
  } catch {
1591
1733
  return null;
1592
1734
  }
1593
- }, vn = async (e) => {
1594
- let t = await G(e);
1735
+ }, On = async (e) => {
1736
+ let t = await W(e);
1595
1737
  if (t === null || t.eventsChecked === 0) return;
1596
1738
  let n = Te(t);
1597
1739
  n !== null && (console.log(""), console.log(`• ${n}`));
1598
1740
  let r = Ee(t);
1599
1741
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
1600
- }, K = (e) => {
1742
+ }, G = (e) => {
1601
1743
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
1602
1744
  return r === void 0 ? n : `${r}: ${n}`;
1603
- }, q = async (e) => {
1745
+ }, K = async (e) => {
1604
1746
  try {
1605
- let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await U(e)).discovered.serverOnlyLeaks, r = t(n);
1747
+ let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await H(e)).discovered.serverOnlyLeaks, r = t(n);
1606
1748
  return {
1607
1749
  status: {
1608
1750
  checked: !0,
@@ -1613,12 +1755,12 @@ var le = /* @__PURE__ */ new Set([
1613
1755
  } catch (e) {
1614
1756
  return { status: {
1615
1757
  checked: !1,
1616
- reason: K(e)
1758
+ reason: G(e)
1617
1759
  } };
1618
1760
  }
1619
- }, J = async (r) => {
1761
+ }, q = async (r) => {
1620
1762
  try {
1621
- let { loadApiConfig: i } = await import("./dev-B9Gz0k85.js"), { discovered: a } = await U(r), o = t(await i(r)), s = {
1763
+ let { loadApiConfig: i } = await import("./dev-Bl9HqtV7.js"), { discovered: a } = await H(r), o = n(await i(r)), s = {
1622
1764
  queries: a.queries,
1623
1765
  mutations: a.mutations,
1624
1766
  actions: a.actions,
@@ -1628,32 +1770,32 @@ var le = /* @__PURE__ */ new Set([
1628
1770
  return {
1629
1771
  checked: !0,
1630
1772
  defaultDeny: o,
1631
- undecided: n(s),
1773
+ undecided: t(s),
1632
1774
  openAccess: e(s)
1633
1775
  };
1634
1776
  } catch (e) {
1635
1777
  return {
1636
1778
  checked: !1,
1637
- reason: K(e),
1779
+ reason: G(e),
1638
1780
  defaultDeny: !0,
1639
1781
  undecided: [],
1640
1782
  openAccess: []
1641
1783
  };
1642
1784
  }
1643
- }, yn = async (e) => {
1644
- let t = await J(e);
1785
+ }, kn = async (e) => {
1786
+ let t = await q(e);
1645
1787
  if (!t.checked) return console.log(""), console.log("• access decisions: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
1646
1788
  console.log(""), console.log(`access decisions · security.defaultDeny ${t.defaultDeny ? "ON" : "OFF"}`), console.log(` ✗ no access decision ${String(t.undecided.length).padStart(4)}`), console.log(` ✓ openAccess, declared on purpose ${String(t.openAccess.length).padStart(4)}`);
1647
1789
  for (let e of t.openAccess) console.log(` ${e.tag} — ${e.openAccess ?? ""}`);
1648
1790
  if (t.undecided.length === 0) return !1;
1649
1791
  console.log("");
1650
1792
  for (let e of t.undecided) console.log(` ✗ ${e.tag} (${e.kind})${e.file === void 0 ? "" : `\n ${e.file}`}`);
1651
- return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), bn(), !0) : (console.log(" This app sets `security: { defaultDeny: false }`, so these BOOT — and each one is"), console.log(" callable by any authenticated session. Advisory here for that reason only."), bn(), !1);
1652
- }, bn = () => {
1793
+ return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), An(), !0) : (console.log(" This app sets `security: { defaultDeny: false }`, so these BOOT — and each one is"), console.log(" callable by any authenticated session. Advisory here for that reason only."), An(), !1);
1794
+ }, An = () => {
1653
1795
  console.log(" `guards: [{ scope: '…' }]` authority the SUBJECT holds globally"), console.log(" `guards: [{ action, resourceType, resource }]` authority per ROW / per resource"), console.log(" — backed by `defineResourcePolicy` + a tuple source you register. If your"), console.log(" relationships already live in your own tables (a `teamMembers` row), register"), console.log(" a tuple source over them; nothing is copied. Fail-closed without a resolver."), console.log(" Docs: Authentication → Authorization."), console.log(" `openAccess: '<why anyone may call it>'` deliberately unauthenticated");
1654
- }, Y = async (e) => {
1796
+ }, J = async (e) => {
1655
1797
  try {
1656
- let { files: t } = await U(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
1798
+ let { files: t } = await H(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
1657
1799
  if (a.length === 0) return {
1658
1800
  checked: !0,
1659
1801
  unverified: [],
@@ -1680,14 +1822,14 @@ var le = /* @__PURE__ */ new Set([
1680
1822
  } catch (e) {
1681
1823
  return {
1682
1824
  checked: !1,
1683
- reason: K(e),
1825
+ reason: G(e),
1684
1826
  unverified: [],
1685
1827
  open: [],
1686
1828
  verified: 0
1687
1829
  };
1688
1830
  }
1689
- }, xn = async (e) => {
1690
- let t = await Y(e);
1831
+ }, jn = async (e) => {
1832
+ let t = await J(e);
1691
1833
  if (!t.checked) return console.log(""), console.log("• webhook verification: NOT CHECKED — the app's webhooks could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), !1;
1692
1834
  if (t.verified + t.open.length + t.unverified.length === 0) return !1;
1693
1835
  console.log(""), console.log("incoming webhook verification"), console.log(` ✓ signature / provider verified ${String(t.verified).padStart(4)}`), console.log(` ! deliberately public (none) ${String(t.open.length).padStart(4)}`), console.log(` ✗ nothing verifies the caller ${String(t.unverified.length).padStart(4)}`);
@@ -1696,13 +1838,13 @@ var le = /* @__PURE__ */ new Set([
1696
1838
  console.log("");
1697
1839
  for (let e of t.unverified) console.log(` ✗ ${e.id}\n ${e.file}`);
1698
1840
  return console.log(""), console.log(" These are public POSTs that run your application code, and `voltro dev` /"), console.log(" `voltro serve` REFUSE to mount them. Declare one of: `provider:` (a preset),"), console.log(" `signature:` (your own HMAC scheme), `verification: 'provider'` (your handler"), console.log(" verifies), or `verification: 'none'` (deliberately public)."), !0;
1699
- }, Sn = async (e) => {
1700
- let { status: t, message: n } = await q(e);
1841
+ }, Mn = async (e) => {
1842
+ let { status: t, message: n } = await K(e);
1701
1843
  return t.checked ? n !== void 0 && (console.log(""), console.log(`✗ [serverOnly] ${n}`), !0) : (console.log(""), console.log("• serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), console.log(" This audit is the automated check that a hash or a token cannot reach the wire, so"), console.log(" an app that leans on `.serverOnly()` should treat a persistent skip as a failure."), console.log(" `voltro doctor --json` reports it as `serverOnly.checked: false` for a CI gate."), !1);
1702
- }, Cn = async (e) => ft({
1703
- hasWorkflows: async () => (await U(e)).discovered.workflows.length > 0,
1844
+ }, Nn = async (e) => ft({
1845
+ hasWorkflows: async () => (await H(e)).discovered.workflows.length > 0,
1704
1846
  openStore: async () => {
1705
- let { loadApiConfig: t, buildStore: n } = await import("./dev-B9Gz0k85.js"), r = await t(e);
1847
+ let { loadApiConfig: t, buildStore: n } = await import("./dev-Bl9HqtV7.js"), r = await t(e);
1706
1848
  if (r === null) throw Error("no app.config.ts in this directory");
1707
1849
  let i = await n(r, void 0);
1708
1850
  return {
@@ -1716,13 +1858,13 @@ var le = /* @__PURE__ */ new Set([
1716
1858
  let { sweepStalledRuns: t } = await import("@voltro/workflow");
1717
1859
  return t({ store: e });
1718
1860
  }
1719
- }), wn = async (e) => {
1720
- let t = mt(await Cn(e));
1861
+ }), Pn = async (e) => {
1862
+ let t = mt(await Nn(e));
1721
1863
  t !== null && (console.log(""), console.log(t));
1722
- }, Tn = async (e) => {
1864
+ }, Fn = async (e) => {
1723
1865
  let t;
1724
1866
  try {
1725
- let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await U(e);
1867
+ let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await H(e);
1726
1868
  t = ke(r, i.workflows.map((e) => {
1727
1869
  let t = e.definition, r = t.payloadSchema === void 0 ? {
1728
1870
  required: [],
@@ -1742,18 +1884,18 @@ var le = /* @__PURE__ */ new Set([
1742
1884
  console.log("");
1743
1885
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
1744
1886
  console.log(`${r} ${n}`);
1745
- }, X = "voltro-authz-allowlist.txt", En = (e, t = (e) => {
1887
+ }, Y = "voltro-authz-allowlist.txt", In = (e, t = (e) => {
1746
1888
  try {
1747
1889
  return C(e, "utf8");
1748
1890
  } catch {
1749
1891
  return "";
1750
1892
  }
1751
- }) => /* @__PURE__ */ new Set([...Xe, ...Ze(L(e).map(t))]), Dn = (e, t) => {
1893
+ }) => /* @__PURE__ */ new Set([...Xe, ...Ze(L(e).map(t))]), X = (e, t) => {
1752
1894
  let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1753
1895
  return new RegExp(String.raw`(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*=\s*define[A-Za-z]*Event\s*\(\s*\{[^}]*?name\s*:\s*['"\`]${n}['"\`]`, "s").exec(e)?.[1];
1754
1896
  }, Z = async (e) => {
1755
1897
  try {
1756
- let { discovered: t } = await U(e), n = t.events.map((e) => ({
1898
+ let { discovered: t } = await H(e), n = t.events.map((e) => ({
1757
1899
  name: e.descriptor.name,
1758
1900
  file: e.file
1759
1901
  })).filter((e) => typeof e.name == "string");
@@ -1767,22 +1909,22 @@ var le = /* @__PURE__ */ new Set([
1767
1909
  }, a = L(e).filter((e) => !r.has(e)).map(i).join("\n");
1768
1910
  return n.filter(({ name: e, file: t }) => {
1769
1911
  if (a.includes(e)) return !1;
1770
- let n = Dn(i(t), e);
1912
+ let n = X(i(t), e);
1771
1913
  return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
1772
1914
  }).map((e) => e.name).sort();
1773
1915
  } catch {
1774
1916
  return [];
1775
1917
  }
1776
- }, On = async (e) => {
1918
+ }, Ln = async (e) => {
1777
1919
  let t = await Z(e);
1778
1920
  if (t.length !== 0) {
1779
1921
  console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
1780
1922
  for (let e of t) console.log(` ${e}`);
1781
1923
  console.log(""), console.log(" An event you declare is an event partners can subscribe to. One that"), console.log(" never fires is indistinguishable, from their side, from a broken"), console.log(" endpoint of their own — so they debug your contract instead of"), console.log(" reading it."), console.log(" ADVISORY: this looks for the event name AND the identifier its"), console.log(" declaration binds it to, so `emit(ctx, apiKeyCreated, …)` counts. An"), console.log(" emit through an ASSEMBLED name still reads as missing here.");
1782
1924
  }
1783
- }, kn = async (e) => {
1925
+ }, Rn = async (e) => {
1784
1926
  try {
1785
- let { loadApiConfig: t } = await import("./dev-B9Gz0k85.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1927
+ let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1786
1928
  if (typeof n != "string") return [];
1787
1929
  let [r, i] = n.split("#");
1788
1930
  if (r === void 0 || i === void 0) return [];
@@ -1799,10 +1941,10 @@ var le = /* @__PURE__ */ new Set([
1799
1941
  findings: []
1800
1942
  });
1801
1943
  try {
1802
- let { offlineManifest: n, toInput: r } = await import("./checkCommand-D0QV_zM_.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CFmFKHb2.js"), o = await n(e), s = r(o.manifest, o.tables), c = await kn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1944
+ let { offlineManifest: n, toInput: r } = await import("./checkCommand-BISqx1OJ.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CFmFKHb2.js"), o = await n(e), s = r(o.manifest, o.tables), c = await Rn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1803
1945
  if (l.length === 0) {
1804
1946
  try {
1805
- let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-CGNC0qr7.js");
1947
+ let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-B96WCNJA.js");
1806
1948
  await t(e);
1807
1949
  } catch {
1808
1950
  return t("app-not-importable");
@@ -1826,7 +1968,7 @@ var le = /* @__PURE__ */ new Set([
1826
1968
  } catch {
1827
1969
  return t("app-not-importable");
1828
1970
  }
1829
- }, An = async (e) => {
1971
+ }, zn = async (e) => {
1830
1972
  let { findings: t, evaluated: n, notEvaluated: r } = await Q(e);
1831
1973
  if (!n) {
1832
1974
  if (console.log(""), r === "app-not-importable") {
@@ -1841,7 +1983,7 @@ var le = /* @__PURE__ */ new Set([
1841
1983
  for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
1842
1984
  console.log(""), console.log(" Each of these is uncallable by every user in every role — not"), console.log(" misconfigured, DEAD — and nothing fails at boot to say so. The"), console.log(" usual cause is a typo or a rename that moved one side only."), console.log(` ${t[0].fix}`);
1843
1985
  }
1844
- }, jn = (e) => {
1986
+ }, Bn = (e) => {
1845
1987
  let t = v(e, "src");
1846
1988
  if (!S(t)) return;
1847
1989
  let n = [], r = (t, i = 0) => {
@@ -1877,7 +2019,7 @@ var le = /* @__PURE__ */ new Set([
1877
2019
  console.log("");
1878
2020
  for (let e of i) console.log(e);
1879
2021
  }
1880
- }, Mn = async (e) => {
2022
+ }, Vn = async (e) => {
1881
2023
  let t = ["src/locales", "locales"], n, r = [];
1882
2024
  for (let i of t) {
1883
2025
  let t = v(e, i);
@@ -1889,7 +2031,7 @@ var le = /* @__PURE__ */ new Set([
1889
2031
  if (n === void 0 || r.length === 0) return;
1890
2032
  let i = [];
1891
2033
  try {
1892
- let { loadApiConfig: t } = await import("./dev-B9Gz0k85.js");
2034
+ let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js");
1893
2035
  i = (await t(e))?.locales ?? [];
1894
2036
  } catch {
1895
2037
  return;
@@ -1902,7 +2044,7 @@ var le = /* @__PURE__ */ new Set([
1902
2044
  console.log("");
1903
2045
  for (let e of Ge(a, n)) console.log(e);
1904
2046
  }
1905
- }, Nn = async (e) => {
2047
+ }, Hn = async (e) => {
1906
2048
  let t = v(e, "src");
1907
2049
  if (!S(t)) return;
1908
2050
  let n = !1, r = (e) => {
@@ -1921,9 +2063,9 @@ var le = /* @__PURE__ */ new Set([
1921
2063
  return;
1922
2064
  }
1923
2065
  a()?.reason === "binary-missing" ? (console.log("image pipeline: sharp is INSTALLED but its platform binary is missing —"), console.log(" an install that omitted optional dependencies drops the @img/* prebuilds."), console.log(" Fix: reinstall without omitting optional deps, or `pnpm add -D sharp` in this app.")) : (console.log("image pipeline: sharp is NOT installed — ?image imports serve originals (no srcSet, no blur)."), console.log(" Fix: `pnpm add -D sharp` in this app (it is an optional dependency of @voltro/cli;"), console.log(" your installer skipped optional dependencies)."));
1924
- }, Pn = async (e) => {
2066
+ }, Un = async (e) => {
1925
2067
  try {
1926
- let { loadConfig: t } = await import("./webDev-C2dRz9s5.js"), n = (await t(e, { preferSource: !0 }))?.fonts;
2068
+ let { loadConfig: t } = await import("./webDev-B6ZMX42w.js"), n = (await t(e, { preferSource: !0 }))?.fonts;
1927
2069
  if (n === void 0 || n.length === 0) return;
1928
2070
  let { loadFontkit: r, loadSubsetFont: i } = await import("./fontPipeline-LxIHa1vo.js"), a = [];
1929
2071
  if (await r() === null && a.push("fontkit (size-adjust fallback metrics — the CLS guard — are skipped)"), await i() === null && a.push("subset-font (declared `subsets:` ship the full files)"), console.log(""), a.length === 0) {
@@ -1933,12 +2075,12 @@ var le = /* @__PURE__ */ new Set([
1933
2075
  for (let e of a) console.log(`font pipeline: ${e}`);
1934
2076
  console.log(" Fix: reinstall without omitting optional deps, or `pnpm add -D fontkit subset-font` in this app.");
1935
2077
  } catch {}
1936
- }, Fn = async (e) => {
2078
+ }, Wn = async (e) => {
1937
2079
  let t = v(e, "src", "pages");
1938
2080
  if (!S(t)) return !1;
1939
2081
  let n = [];
1940
2082
  try {
1941
- let { walkPagesTree: e } = await import("./webDev-C2dRz9s5.js");
2083
+ let { walkPagesTree: e } = await import("./webDev-B6ZMX42w.js");
1942
2084
  n = (await e(t)).pages.map((e) => e.pattern);
1943
2085
  } catch {
1944
2086
  return !1;
@@ -1948,10 +2090,10 @@ var le = /* @__PURE__ */ new Set([
1948
2090
  console.log("");
1949
2091
  for (let e of Je(r)) console.log(e);
1950
2092
  return r.problems.length > 0;
1951
- }, In = async (e) => {
1952
- let { discovered: t } = await U(e), n = [];
2093
+ }, Gn = async (e) => {
2094
+ let { discovered: t } = await H(e), n = [];
1953
2095
  try {
1954
- let { loadApiConfig: t } = await import("./dev-B9Gz0k85.js");
2096
+ let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js");
1955
2097
  n = ((await t(e))?.plugins ?? []).map((e) => {
1956
2098
  let t = (e.extendSchema?.tables ?? []).map((e) => {
1957
2099
  let t = e;
@@ -1964,8 +2106,8 @@ var le = /* @__PURE__ */ new Set([
1964
2106
  plugin: e.name,
1965
2107
  tables: t,
1966
2108
  rpcTags: n,
1967
- canDisableTables: Ln.has(r),
1968
- canAlias: Rn.has(r)
2109
+ canDisableTables: Kn.has(r),
2110
+ canAlias: qn.has(r)
1969
2111
  };
1970
2112
  });
1971
2113
  } catch {
@@ -1983,12 +2125,12 @@ var le = /* @__PURE__ */ new Set([
1983
2125
  });
1984
2126
  console.log();
1985
2127
  for (let e of dt(n, i)) console.log(e);
1986
- }, Ln = /* @__PURE__ */ new Set([
2128
+ }, Kn = /* @__PURE__ */ new Set([
1987
2129
  "@voltro/plugin-rbac",
1988
2130
  "@voltro/plugin-audit",
1989
2131
  "@voltro/plugin-notifications",
1990
2132
  "aiFlows"
1991
- ]), Rn = /* @__PURE__ */ new Set([
2133
+ ]), qn = /* @__PURE__ */ new Set([
1992
2134
  "aiFlows",
1993
2135
  "@voltro/plugin-audit",
1994
2136
  "@voltro/plugin-billing",
@@ -2006,8 +2148,8 @@ var le = /* @__PURE__ */ new Set([
2006
2148
  "@voltro/plugin-sso-saml",
2007
2149
  "@voltro/plugin-storage",
2008
2150
  "@voltro/plugin-row-history"
2009
- ]), $ = 20, zn = async (e) => {
2010
- let t = await Bn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
2151
+ ]), $ = 20, Jn = async (e) => {
2152
+ let t = await Yn(e), n = new Set(t.reviewed.map((e) => e.tag)), r = t.result.findings.filter((e) => e.verdict.kind === "allowlisted" && !n.has(e.tag)).length;
2011
2153
  return {
2012
2154
  executors: t.executorCount,
2013
2155
  guardVocabulary: [...t.vocabulary].sort(),
@@ -2019,15 +2161,15 @@ var le = /* @__PURE__ */ new Set([
2019
2161
  },
2020
2162
  unchecked: t.result.unchecked
2021
2163
  };
2022
- }, Bn = async (e) => {
2023
- let { discovered: t } = await U(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2164
+ }, Yn = async (e) => {
2165
+ let { discovered: t } = await H(e), n = /* @__PURE__ */ new Map(), r = (e) => {
2024
2166
  if (!n.has(e)) try {
2025
2167
  n.set(e, C(e, "utf8"));
2026
2168
  } catch {
2027
2169
  n.set(e, "");
2028
2170
  }
2029
2171
  return n.get(e);
2030
- }, i = En(e, r), a = /* @__PURE__ */ new Set();
2172
+ }, i = In(e, r), a = /* @__PURE__ */ new Set();
2031
2173
  for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
2032
2174
  let e = t.references?.();
2033
2175
  e && a.add(e.tableName);
@@ -2059,7 +2201,7 @@ var le = /* @__PURE__ */ new Set([
2059
2201
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
2060
2202
  ...a?.table === void 0 ? {} : { table: a.table }
2061
2203
  };
2062
- })), s = v(e, X), c = S(s) ? j(C(s, "utf8")) : void 0, l = !1;
2204
+ })), s = v(e, Y), c = S(s) ? j(C(s, "utf8")) : void 0, l = !1;
2063
2205
  for (let t of L(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
2064
2206
  l = !0;
2065
2207
  break;
@@ -2081,23 +2223,23 @@ var le = /* @__PURE__ */ new Set([
2081
2223
  executorCount: o.length,
2082
2224
  allowPath: s
2083
2225
  };
2084
- }, Vn = async (e, t) => {
2085
- let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Bn(e), l = n.counts;
2226
+ }, Xn = async (e, t) => {
2227
+ let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Yn(e), l = n.counts;
2086
2228
  console.log(`\nauthz scan · ${o} executor(s)`);
2087
2229
  let u = [...r].filter((e) => !Xe.has(e)).sort();
2088
2230
  console.log(u.length === 0 ? " guard vocabulary: framework names only — no exported require*/assert* found in this app" : ` guard vocabulary: ${u.length} from your source (${u.slice(0, 6).join(", ")}${u.length > 6 ? ", …" : ""})`), console.log(` ✗ no access check ${String(l.unchecked).padStart(4)}`), console.log(` ⚠ inline ownership check, no named guard${String(l.inlineOwnership).padStart(4)} (informational)`), console.log(` ✓ guards: on the descriptor ${String(l.declaredGuard).padStart(4)}`), console.log(` ✓ calls a guard from the vocabulary ${String(l.vocabularyGuard).padStart(4)}`);
2089
2231
  let d = new Set(a.map((e) => e.tag)), f = n.findings.filter((e) => e.verdict.kind === "allowlisted" && !d.has(e.tag)).length, p = l.allowlisted - f;
2090
- if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${X})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
2232
+ if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Y})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
2091
2233
  let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = st(e, t);
2092
- return oe(s, ot(e, t), "utf8"), console.log(`\n${X}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
2234
+ return oe(s, ot(e, t), "utf8"), console.log(`\n${Y}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
2093
2235
  }
2094
2236
  if (n.unchecked.length === 0) return !1;
2095
2237
  console.log("");
2096
2238
  for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
2097
- return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${X} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${X}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
2098
- }, Hn = async (e) => {
2239
+ return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Y} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Y}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
2240
+ }, Zn = async (e) => {
2099
2241
  try {
2100
- let { loadApiConfig: t } = await import("./dev-B9Gz0k85.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
2242
+ let { loadApiConfig: t } = await import("./dev-Bl9HqtV7.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
2101
2243
  return r == null ? null : n({
2102
2244
  ...r.schema ? { declared: r.schema } : {},
2103
2245
  ...r.store === void 0 ? {} : { store: r.store },
@@ -2106,42 +2248,42 @@ var le = /* @__PURE__ */ new Set([
2106
2248
  } catch {
2107
2249
  return null;
2108
2250
  }
2109
- }, Un = async (e) => {
2110
- let t = await Hn(e);
2251
+ }, Qn = async (e) => {
2252
+ let t = await Zn(e);
2111
2253
  if (t !== null) {
2112
2254
  console.log(""), console.log("• framework tables whose presence is DECIDED rather than implied by your source tree:");
2113
2255
  for (let e of t.text.split("\n")) console.log(` ${e}`);
2114
2256
  }
2115
- }, Wn = async (e) => {
2116
- if (e.includes("--help") || e.includes("-h")) return console.log(un), 0;
2117
- let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), r = b(n ?? process.cwd());
2257
+ }, $n = async (e) => {
2258
+ if (e.includes("--help") || e.includes("-h")) return console.log(bn), 0;
2259
+ let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), a = b(n ?? process.cwd());
2118
2260
  if (g(e)) return ee(), h({
2119
- ...R(r),
2120
- spaCandidates: await z(r),
2121
- pageConvention: B(r),
2122
- fileTaxonomy: re(r).findings,
2123
- junctionFks: (await W(r))?.issues ?? [],
2124
- eventDelivery: await G(r),
2125
- duplicateInstances: a(r),
2126
- versionSkew: o(r) ?? null,
2127
- serverOnly: (await q(r)).status,
2128
- accessDecisions: await J(r),
2129
- webhookVerification: await Y(r),
2130
- authz: await zn(r),
2131
- ungrantedScopes: await Q(r),
2132
- neverEmittedEvents: await Z(r),
2133
- unimportedDeps: He(r),
2134
- stalledRuns: await Cn(r),
2135
- decidedSchema: await Hn(r)
2261
+ ...R(a),
2262
+ spaCandidates: await z(a),
2263
+ pageConvention: B(a),
2264
+ fileTaxonomy: re(a).findings,
2265
+ junctionFks: (await U(a))?.issues ?? [],
2266
+ eventDelivery: await W(a),
2267
+ duplicateInstances: i(a),
2268
+ versionSkew: o(a) ?? null,
2269
+ serverOnly: (await K(a)).status,
2270
+ accessDecisions: await q(a),
2271
+ webhookVerification: await J(a),
2272
+ authz: await Jn(a),
2273
+ ungrantedScopes: await Q(a),
2274
+ neverEmittedEvents: await Z(a),
2275
+ unimportedDeps: He(a),
2276
+ stalledRuns: await Nn(a),
2277
+ decidedSchema: await Zn(a)
2136
2278
  }), 0;
2137
- let c = Ct(r, t);
2138
- Zt(r), Xt(r), ln(r), ne(r), await rn(r), await Tn(r), await wn(r), await fn(r), await _n(r), await gn(r), await Un(r), await vn(r), await In(r), await An(r), await On(r), console.log("");
2139
- for (let e of Ue(He(r))) console.log(e);
2140
- await Mn(r), jn(r), await Nn(r), await Pn(r);
2141
- let l = await Sn(r), u = await Vn(r, e.includes("--write-authz-allowlist")), d = await yn(r), f = await xn(r), p = await Fn(r), m = i(o(r));
2279
+ let s = Ct(a, t);
2280
+ sn(a), on(a), yn(a), ne(a), await pn(a), await Fn(a), await Pn(a), await Sn(a), await Dn(a), await En(a), await Qn(a), await On(a), await Gn(a), await zn(a), await Ln(a), console.log("");
2281
+ for (let e of Ue(He(a))) console.log(e);
2282
+ await Vn(a), Bn(a), await Hn(a), await Un(a);
2283
+ let c = await Mn(a), l = await Xn(a, e.includes("--write-authz-allowlist")), d = await kn(a), f = await jn(a), p = await Wn(a), m = r(o(a));
2142
2284
  m !== void 0 && (console.log(""), console.log(m));
2143
- let _ = s(a(r));
2144
- return _ !== void 0 && console.log(`\n${_}`), l || u || d || p || f ? Math.max(c, 1) : c;
2285
+ let _ = u(i(a));
2286
+ return _ !== void 0 && console.log(`\n${_}`), c || l || d || p || f ? Math.max(s, 1) : s;
2145
2287
  };
2146
2288
  //#endregion
2147
- export { z as C, Y as E, B as S, Q as T, ln as _, zn as a, Ct as b, G as c, Wt as d, xt as f, Zt as g, Xt as h, J as i, En as l, mn as m, St as n, Z as o, W as p, Qt as r, Dn as s, un as t, R as u, rn as v, q as w, Gt as x, Wn as y };
2289
+ export { z as C, J as D, Q as E, B as S, Rt as T, yn as _, Jn as a, Ct as b, W as c, Qt as d, xt as f, sn as g, on as h, q as i, In as l, wn as m, St as n, Z as o, U as p, cn as r, X as s, bn as t, R as u, pn as v, K as w, $t as x, $n as y };