@voltro/cli 0.38.0 → 0.39.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/CHANGELOG.md +276 -0
  2. package/THIRD-PARTY-NOTICES.md +80 -45
  3. package/dist/{addCommand-BNeoeSxe.js → addCommand-C05L9tZh.js} +2 -2
  4. package/dist/addCommand-C3MdJG-a.js +2 -0
  5. package/dist/{agentsMd-mhQMF1bx.js → agentsMd-7zI2h5l9.js} +3 -3
  6. package/dist/agentsMd-BFCXh2gl.js +2 -0
  7. package/dist/apiBuild-BmR_bIxN.js +2 -0
  8. package/dist/{apiBuild-CvacV4zA.js → apiBuild-D9FB13_2.js} +3 -3
  9. package/dist/baselineCommand-CXv680Dc.js +2 -0
  10. package/dist/{baselineCommand-C6NMt-oa.js → baselineCommand-Ck2Xm8M7.js} +18 -18
  11. package/dist/bin.js +1 -1
  12. package/dist/{build-_LoTC1s0.js → build-DORV5mLM.js} +107 -100
  13. package/dist/{capabilitiesCommand-nq_pz5xd.js → capabilitiesCommand-D5gucwYa.js} +8 -8
  14. package/dist/{checkCommand-Pjk5sBl2.js → checkCommand-Bf6oMao4.js} +35 -35
  15. package/dist/{checkCommand-wibtQx-N.js → checkCommand-D9qK_gtA.js} +1 -1
  16. package/dist/{cloudCmd-NQSwe_Qk.js → cloudCmd-C42gaO8s.js} +16 -16
  17. package/dist/{clusterCmd-D5wsCmA_.js → clusterCmd-DrVFCzSj.js} +8 -4
  18. package/dist/{codegen-GYYcdpCg.js → codegen-CbpGCWLG.js} +1 -1
  19. package/dist/codegen-i8QGcHsi.js +2 -0
  20. package/dist/codegenCommand-CxOyKmc1.js +129 -0
  21. package/dist/{codemodRunner-CXgQ-ecJ.js → codemodRunner-D-jTyvWo.js} +622 -551
  22. package/dist/{commands-DaZVi9wi.js → commands-D6aW_6AR.js} +48 -48
  23. package/dist/{connectionConfig-UFlIEiys.js → connectionConfig-Bk9IC7D0.js} +1 -1
  24. package/dist/{dashboardCommand-Dxy_kmoW.js → dashboardCommand-CwTZh-XZ.js} +3 -3
  25. package/dist/{dataCommand-D6-Ha2lf.js → dataCommand-Cc9RiLQZ.js} +14 -11
  26. package/dist/{dbCommand-C4UW8cVN.js → dbCommand-DSwGv9wS.js} +50 -49
  27. package/dist/dbCommand-DcqEyxju.js +2 -0
  28. package/dist/{dev-E7Eo5GHc.js → dev-3mgc8c-H.js} +2260 -2184
  29. package/dist/dev-CjJAasDs.js +3 -0
  30. package/dist/{dialectDriver-CgXnDfec.js → dialectDriver-czCHYpeH.js} +2 -1
  31. package/dist/doctorCommand-Cghssndo.js +2 -0
  32. package/dist/{doctorCommand-gmDyALFL.js → doctorCommand-oLuzc-aC.js} +261 -244
  33. package/dist/{dormancyCommand-CE6wG8lj.js → dormancyCommand-Bxe4WtHU.js} +1 -1
  34. package/dist/{e2eCmd-BRabZww-.js → e2eCmd-uFHig1hV.js} +8 -2
  35. package/dist/{embeddingsCommand-C3uAuf6N.js → embeddingsCommand-CyNQkFIc.js} +6 -6
  36. package/dist/{envCommand-DzuWh8ya.js → envCommand-CAkSgBbh.js} +19 -19
  37. package/dist/{evolveCommand-DPLqcYb2.js → evolveCommand-CWhBoe_P.js} +6 -8
  38. package/dist/frameworkTableAssembly-DgylYh9_.js +2 -0
  39. package/dist/{generateCommand-DbgcUpGw.js → generateCommand-CcyvH2ve.js} +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/{infoCommand-CmczGj_l.js → infoCommand-CTo8Jnhq.js} +7 -7
  42. package/dist/inspect-B7U7Cl_Z.js +1192 -0
  43. package/dist/inspect-DUze25t0.js +2 -0
  44. package/dist/{inspectCmd-EHFZ9yYu.js → inspectCmd-niF97fAq.js} +5 -5
  45. package/dist/inspectGateHint-BjnFubmH.js +7 -0
  46. package/dist/{logFileSink-C_D2wRN1.js → logFileSink-B4uP8pvf.js} +1 -1
  47. package/dist/{logsCmd-D36xK7Zu.js → logsCmd-B6oNsfaZ.js} +10 -7
  48. package/dist/{manifestBuild-hpPLaGxV.js → manifestBuild-BK42hu0k.js} +1 -1
  49. package/dist/manifestBuild-j0n109tt.js +2 -0
  50. package/dist/{metaCommands-CIoQNlaQ.js → metaCommands-DUYR--Ts.js} +1 -1
  51. package/dist/{migrate-RwgUWXfc.js → migrate-BnPw2zC8.js} +7 -4
  52. package/dist/{packageCommand-Cug_3Ogl.js → packageCommand-9SVyvsSo.js} +3 -2
  53. package/dist/{probeCommand-BPEpwT32.js → probeCommand-C9gazU0H.js} +35 -24
  54. package/dist/{projectScaffold-CxgtJvlb.js → projectScaffold-BEhhHjPr.js} +1 -1
  55. package/dist/{projectScaffold-DDgWbNLe.js → projectScaffold-BvhLOrLq.js} +17 -15
  56. package/dist/{runtimeTrace-6jfznSzr.js → runtimeTrace-gNXeLf6H.js} +1 -1
  57. package/dist/{scheduleManifestCmd-D2x0CTTY.js → scheduleManifestCmd-kmWzrO_w.js} +6 -9
  58. package/dist/{sdkgen-CEJZZQUA.js → sdkgen-C8zz10-X.js} +4 -4
  59. package/dist/{seedRunner-ZmLSqNe2.js → seedRunner-IdHEprqf.js} +1 -4
  60. package/dist/serveCommand-BpK8-m1N.js +1770 -0
  61. package/dist/serveCommand-CW-goXvh.js +2 -0
  62. package/dist/serveEntry.js +2 -2
  63. package/dist/{serverlessCommand-CfJZy6dS.js → serverlessCommand-DbJ7Plg9.js} +3 -3
  64. package/dist/{start-CidwCcjl.js → start-BNuTWdRd.js} +1 -1
  65. package/dist/{start-RCh6qKFe.js → start-ft_KzFTd.js} +257 -254
  66. package/dist/startEntry.js +1 -1
  67. package/dist/{test-DIQ0jlkQ.js → test-CLYXrZ2F.js} +1 -1
  68. package/dist/{tracesCmd-DStmCJPi.js → tracesCmd-DgtgOUdi.js} +7 -4
  69. package/dist/{typecheckCommand-BlsWiCNq.js → typecheckCommand-BuCtT92o.js} +7 -7
  70. package/dist/updateCommand-6FMU2klq.js +2 -0
  71. package/dist/{updateCommand-CmksX7m_.js → updateCommand-CHBmCB17.js} +5 -3
  72. package/dist/{webDev-BKdD7c_a.js → webDev-CTpSY-e_.js} +813 -775
  73. package/dist/webDev-id3I5PvG.js +2 -0
  74. package/dist/{webhookDiscovery-D7VaeMlz.js → webhookDiscovery-CphsQe59.js} +4 -6
  75. package/dist/{webhookDiscovery-CrGAfhIG.js → webhookDiscovery-il9ti-HE.js} +1 -1
  76. package/dist/{webhooksCommand-BHeaF1ZJ.js → webhooksCommand-EccPZQZ4.js} +1 -1
  77. package/package.json +27 -21
  78. package/templates/AGENTS.md +2 -2
  79. package/templates/agent-docs/_index.md +2 -2
  80. package/templates/agent-docs/_manifest.json +2 -2
  81. package/templates/agent-docs/authentication.md +43 -4
  82. package/templates/agent-docs/cli.md +17 -13
  83. package/templates/agent-docs/data.md +44 -1
  84. package/templates/agent-docs/database/migrations.md +2 -2
  85. package/templates/agent-docs/database/seedsdialects.md +2 -2
  86. package/templates/agent-docs/deployment.md +21 -7
  87. package/templates/agent-docs/internationalization.md +10 -3
  88. package/templates/agent-docs/local-first-mobile.md +141 -20
  89. package/templates/agent-docs/plugins/mail.md +1 -1
  90. package/templates/agent-docs/plugins/ratelimit.md +1 -1
  91. package/templates/agent-docs/plugins.md +3 -1
  92. package/templates/agent-docs/releases.md +130 -1
  93. package/templates/agent-docs/routing.md +34 -2
  94. package/templates/agent-docs/templates/apibackends.md +1 -1
  95. package/templates/agent-docs/templates/mobile.md +1 -1
  96. package/templates/agent-docs/whats-new.md +26 -66
  97. package/templates/apps/api-ai/package.json +7 -7
  98. package/templates/apps/api-auth/package.json +8 -8
  99. package/templates/apps/api-backend/package.json +7 -7
  100. package/templates/apps/api-backend-deactivation/package.json +7 -7
  101. package/templates/apps/api-backend-mail/package.json +8 -8
  102. package/templates/apps/api-backend-mariadb/package.json +9 -9
  103. package/templates/apps/api-backend-sqlite/package.json +8 -8
  104. package/templates/apps/api-backend-storage/package.json +8 -8
  105. package/templates/apps/api-cms/package.json +10 -10
  106. package/templates/apps/api-collab/package.json +8 -8
  107. package/templates/apps/api-data-advanced/package.json +8 -8
  108. package/templates/apps/api-durable/package.json +8 -8
  109. package/templates/apps/api-durable/startup/warm.startup.tsx +16 -2
  110. package/templates/apps/api-feature-flags/package.json +9 -9
  111. package/templates/apps/api-governance/package.json +8 -8
  112. package/templates/apps/api-kv/package.json +8 -8
  113. package/templates/apps/api-moderation/package.json +8 -8
  114. package/templates/apps/api-observability/package.json +8 -8
  115. package/templates/apps/api-ratelimit/package.json +8 -8
  116. package/templates/apps/api-rbac/authz.ts +16 -1
  117. package/templates/apps/api-rbac/package.json +8 -8
  118. package/templates/apps/api-rest/package.json +7 -7
  119. package/templates/apps/api-saas/package.json +11 -11
  120. package/templates/apps/api-saas-starter/package.json +10 -10
  121. package/templates/apps/api-search/package.json +8 -8
  122. package/templates/apps/api-status/package.json +8 -8
  123. package/templates/apps/api-versioning/package.json +8 -8
  124. package/templates/apps/api-webhooks/package.json +9 -9
  125. package/templates/apps/changelog/package.json +6 -6
  126. package/templates/apps/edge-functions/package.json +2 -2
  127. package/templates/apps/frontend-admin/package.json +8 -8
  128. package/templates/apps/frontend-app/package.json +9 -9
  129. package/templates/apps/frontend-auth/package.json +8 -8
  130. package/templates/apps/frontend-blank/package.json +7 -7
  131. package/templates/apps/frontend-cms/package.json +9 -9
  132. package/templates/apps/frontend-collab/package.json +10 -10
  133. package/templates/apps/frontend-contact/package.json +7 -7
  134. package/templates/apps/frontend-dashboard/package.json +7 -7
  135. package/templates/apps/frontend-docs/package.json +7 -7
  136. package/templates/apps/frontend-i18n/package.json +6 -6
  137. package/templates/apps/frontend-landing/package.json +7 -7
  138. package/templates/apps/frontend-portal/package.json +8 -8
  139. package/templates/apps/frontend-saas/package.json +8 -8
  140. package/templates/apps/frontend-spa/package.json +7 -7
  141. package/templates/apps/frontend-ssr/package.json +7 -7
  142. package/templates/apps/frontend-ssr-api/package.json +8 -8
  143. package/templates/apps/frontend-static-blog/package.json +6 -6
  144. package/templates/apps/frontend-status/package.json +8 -8
  145. package/templates/apps/mobile-app/README.md +41 -17
  146. package/templates/apps/mobile-app/app.config.ts +1 -1
  147. package/templates/apps/mobile-app/package.json +15 -11
  148. package/templates/apps/mobile-app/src/app/_layout.tsx +44 -13
  149. package/templates/apps/mobile-app/src/app/index.tsx +1 -1
  150. package/templates/apps/mobile-app/src/app/settings.tsx +4 -1
  151. package/templates/apps/mobile-app/src/client.ts +74 -57
  152. package/templates/apps/mobile-app/src/lib/api.ts +4 -4
  153. package/templates/apps/mobile-app/src/lib/deeplinks.ts +6 -6
  154. package/templates/apps/mobile-app/src/persistence.ts +24 -3
  155. package/templates/apps/mobile-app/tsconfig.json +17 -3
  156. package/dist/addCommand-aXSQveak.js +0 -2
  157. package/dist/agentsMd-BTchIZku.js +0 -2
  158. package/dist/apiBuild-ttSDDOGp.js +0 -2
  159. package/dist/baselineCommand-Cfw2Afwm.js +0 -2
  160. package/dist/codegen-uYrrDzQv.js +0 -2
  161. package/dist/codegenCommand-C60m_LCp.js +0 -30
  162. package/dist/dbCommand-CC-qNzpz.js +0 -2
  163. package/dist/dev-CAOoXOkc.js +0 -3
  164. package/dist/doctorCommand-Cjl8Yii8.js +0 -2
  165. package/dist/frameworkTableAssembly-YyVe32Cb.js +0 -2
  166. package/dist/inspect-CjTYzAs_.js +0 -1190
  167. package/dist/inspect-P4pxoMaV.js +0 -2
  168. package/dist/inspectGateHint-BF6608UT.js +0 -4
  169. package/dist/manifestBuild-COkJoyAr.js +0 -2
  170. package/dist/serveCommand-DNRF2nDI.js +0 -2
  171. package/dist/serveCommand-DnY4VS3D.js +0 -1767
  172. package/dist/updateCommand-B0J_T9Vs.js +0 -2
  173. package/dist/webDev-CcJfdSjl2.js +0 -2
@@ -1,18 +1,18 @@
1
- import { Bt as e, Rt as t, zt as n } from "./dev-E7Eo5GHc.js";
2
- import { $ as r, Q as i, X as a, Z as o, s, t as c, y as l } from "./webDev-BKdD7c_a.js";
1
+ import { Bt as e, Ht as t, Vt as n } from "./dev-3mgc8c-H.js";
2
+ import { $ as r, Q as i, X as a, Z as o, s, t as c, y as l } from "./webDev-CTpSY-e_.js";
3
3
  import { w as u } from "./frameworkTableAssembly-oMPBKqlE.js";
4
4
  import { n as d } from "./pageConvention-cEiRxdab.js";
5
- import { r as f, u as p } from "./apiBuild-CvacV4zA.js";
5
+ import { r as f, u as p } from "./apiBuild-D9FB13_2.js";
6
6
  import { n as m, r as h, t as g } from "./cliOutput-D1tSBoRM.js";
7
- import { n as ee, t as te } from "./tsconfigPaths-BWXBWgcl.js";
8
- import { n as ne, r as re } from "./fileTaxonomy-CJfgOllU.js";
9
- import { basename as ie, dirname as ae, join as _, relative as v, resolve as y, sep as b } from "node:path";
10
- import { existsSync as x, readFileSync as S, readdirSync as C, statSync as w, writeFileSync as oe } from "node:fs";
11
- import { pathToFileURL as se } from "node:url";
12
- import { createLogger as ce } from "@voltro/logger";
13
- import { Node as T, Project as E, SyntaxKind as D } from "ts-morph";
7
+ import { n as _, t as ee } from "./tsconfigPaths-BWXBWgcl.js";
8
+ import { n as te, r as ne } from "./fileTaxonomy-CJfgOllU.js";
9
+ import { basename as re, dirname as ie, join as v, relative as y, resolve as b, sep as x } from "node:path";
10
+ import { existsSync as S, readFileSync as C, readdirSync as w, statSync as T, writeFileSync as ae } from "node:fs";
11
+ import { pathToFileURL as oe } from "node:url";
12
+ import { createLogger as se } from "@voltro/logger";
13
+ import { Node as E, Project as D, SyntaxKind as O } from "ts-morph";
14
14
  //#region src/predicateColumnAudit.ts
15
- var le = /* @__PURE__ */ new Set([
15
+ var ce = /* @__PURE__ */ new Set([
16
16
  "eq",
17
17
  "ne",
18
18
  "gt",
@@ -30,43 +30,43 @@ var le = /* @__PURE__ */ new Set([
30
30
  "endsWith",
31
31
  "between",
32
32
  "jsonField"
33
- ]), ue = (e) => {
34
- if (T.isPropertyAccessExpression(e)) return e.getName();
35
- if (T.isIdentifier(e)) return e.getText();
36
- }, de = (e) => {
33
+ ]), le = (e) => {
34
+ if (E.isPropertyAccessExpression(e)) return e.getName();
35
+ if (E.isIdentifier(e)) return e.getText();
36
+ }, ue = (e) => {
37
37
  let t = e;
38
- for (; T.isCallExpression(t);) {
38
+ for (; E.isCallExpression(t);) {
39
39
  let e = t.getExpression();
40
- if (!T.isPropertyAccessExpression(e)) break;
40
+ if (!E.isPropertyAccessExpression(e)) break;
41
41
  t = e.getExpression();
42
42
  }
43
43
  return t;
44
- }, fe = (e, t, n) => {
45
- 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 E({
44
+ }, de = (e, t, n) => {
45
+ 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 D({
46
46
  useInMemoryFileSystem: !0,
47
47
  skipAddingFilesFromTsConfig: !0
48
48
  });
49
49
  for (let t of e) {
50
50
  let e;
51
51
  try {
52
- let n = S(t, "utf8");
52
+ let n = C(t, "utf8");
53
53
  if (!n.includes(".where(") && !n.includes(".select(")) continue;
54
54
  e = s.createSourceFile(`/scan${t.endsWith("x") ? ".tsx" : ".ts"}`, n, { overwrite: !0 });
55
55
  } catch {
56
56
  continue;
57
57
  }
58
- let c = v(n, t);
59
- for (let t of e.getDescendantsOfKind(D.CallExpression)) {
58
+ let c = y(n, t);
59
+ for (let t of e.getDescendantsOfKind(O.CallExpression)) {
60
60
  let e = t.getExpression();
61
- if (!T.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
62
- let n = ue(de(e.getExpression()));
61
+ if (!E.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
62
+ let n = le(ue(e.getExpression()));
63
63
  if (n === void 0 || !r.has(n)) {
64
64
  let t = e.getExpression();
65
- if (T.isCallExpression(t)) {
65
+ if (E.isCallExpression(t)) {
66
66
  let e = t.getExpression();
67
- if (T.isPropertyAccessExpression(e) && e.getName() === "select") {
67
+ if (E.isPropertyAccessExpression(e) && e.getName() === "select") {
68
68
  let e = t.getArguments()[0];
69
- e !== void 0 && T.isStringLiteral(e) && (n = e.getLiteralValue());
69
+ e !== void 0 && E.isStringLiteral(e) && (n = e.getLiteralValue());
70
70
  }
71
71
  }
72
72
  }
@@ -75,16 +75,16 @@ var le = /* @__PURE__ */ new Set([
75
75
  if (s === void 0) continue;
76
76
  let l = [], [u] = t.getArguments();
77
77
  if (u !== void 0) {
78
- if (T.isStringLiteral(u)) l.push({
78
+ if (E.isStringLiteral(u)) l.push({
79
79
  column: u.getLiteralValue(),
80
80
  line: u.getStartLineNumber()
81
81
  });
82
- else for (let e of [u, ...u.getDescendantsOfKind(D.CallExpression)]) {
83
- if (!T.isCallExpression(e)) continue;
84
- let t = e.getExpression(), n = T.isIdentifier(t) ? t.getText() : T.isPropertyAccessExpression(t) ? t.getName() : void 0;
85
- if (n === void 0 || !le.has(n)) continue;
82
+ else for (let e of [u, ...u.getDescendantsOfKind(O.CallExpression)]) {
83
+ if (!E.isCallExpression(e)) continue;
84
+ let t = e.getExpression(), n = E.isIdentifier(t) ? t.getText() : E.isPropertyAccessExpression(t) ? t.getName() : void 0;
85
+ if (n === void 0 || !ce.has(n)) continue;
86
86
  let r = e.getArguments()[0];
87
- r !== void 0 && T.isStringLiteral(r) && l.push({
87
+ r !== void 0 && E.isStringLiteral(r) && l.push({
88
88
  column: r.getLiteralValue(),
89
89
  line: r.getStartLineNumber()
90
90
  });
@@ -103,16 +103,16 @@ var le = /* @__PURE__ */ new Set([
103
103
  issues: a,
104
104
  checked: o
105
105
  };
106
- }, O = 12, pe = (e) => {
106
+ }, k = 12, fe = (e) => {
107
107
  if (e.checked === 0) return null;
108
108
  if (e.issues.length === 0) return `predicate columns: ${e.checked} checked — every filtered column exists on its table`;
109
109
  let t = [`predicate columns: ${e.issues.length} filter${e.issues.length, "s"} on a column that does not exist (${e.checked} checked)`];
110
110
  for (let n of e.issues) {
111
- let e = n.available.slice(0, O).join(", "), r = n.available.length > O ? `, … (${n.available.length - O} more)` : "";
111
+ let e = n.available.slice(0, k).join(", "), r = n.available.length > k ? `, … (${n.available.length - k} more)` : "";
112
112
  t.push(` ${n.file}:${n.line} '${n.table}' has no column '${n.column}'`), t.push(` columns: ${e}${r}`);
113
113
  }
114
114
  return t.push(" This type-checks today and fails at runtime as a bare SQL error."), t.join("\n");
115
- }, me = /* @__PURE__ */ new Set([
115
+ }, pe = /* @__PURE__ */ new Set([
116
116
  "id",
117
117
  "tenantId",
118
118
  "tenant_id",
@@ -132,7 +132,7 @@ var le = /* @__PURE__ */ new Set([
132
132
  "idempotency_id",
133
133
  "idempotencyKey",
134
134
  "idempotency_key"
135
- ]), he = /* @__PURE__ */ new Set([
135
+ ]), me = /* @__PURE__ */ new Set([
136
136
  "id",
137
137
  "createdAt",
138
138
  "created_at",
@@ -142,10 +142,10 @@ var le = /* @__PURE__ */ new Set([
142
142
  "deleted_at",
143
143
  "tenantId",
144
144
  "tenant_id"
145
- ]), ge = (e) => me.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), _e = (e) => e.type === "reference", ve = (e) => {
145
+ ]), he = (e) => pe.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), ge = (e) => e.type === "reference", _e = (e) => {
146
146
  let t = [];
147
147
  for (let n of e) {
148
- let e = n.columns.filter((e) => ge(e.name)), r = e.filter((e) => !_e(e)).map((e) => e.name);
148
+ let e = n.columns.filter((e) => he(e.name)), r = e.filter((e) => !ge(e)).map((e) => e.name);
149
149
  if (r.length === 0) continue;
150
150
  let i = new Set(n.primaryKey), a = r.filter((e) => i.has(e));
151
151
  if (n.primaryKey.length >= 2 && a.length > 0) {
@@ -156,7 +156,7 @@ var le = /* @__PURE__ */ new Set([
156
156
  });
157
157
  continue;
158
158
  }
159
- if (e.some(_e) && e.length >= 2) {
159
+ if (e.some(ge) && e.length >= 2) {
160
160
  t.push({
161
161
  table: n.name,
162
162
  columns: r,
@@ -164,7 +164,7 @@ var le = /* @__PURE__ */ new Set([
164
164
  });
165
165
  continue;
166
166
  }
167
- e.length >= 2 && n.columns.filter((e) => !ge(e.name) && !he.has(e.name)).length === 0 && t.push({
167
+ e.length >= 2 && n.columns.filter((e) => !he(e.name) && !me.has(e.name)).length === 0 && t.push({
168
168
  table: n.name,
169
169
  columns: r,
170
170
  signal: "link-only"
@@ -174,20 +174,20 @@ var le = /* @__PURE__ */ new Set([
174
174
  issues: t,
175
175
  tablesChecked: e.length
176
176
  };
177
- }, ye = {
177
+ }, ve = {
178
178
  "composite-pk": "part of a composite primary key",
179
179
  "reference-sibling": "sits beside a wired reference() on this table",
180
180
  "link-only": "this table is nothing but link columns"
181
- }, be = (e) => {
181
+ }, ye = (e) => {
182
182
  if (e.tablesChecked === 0) return null;
183
183
  if (e.issues.length === 0) return `junction FKs: ${e.tablesChecked} table${e.tablesChecked === 1 ? "" : "s"} checked — every link column is a wired reference()`;
184
184
  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()`];
185
185
  for (let t of e.issues) {
186
186
  let e = t.columns.map((e) => `'${e}'`).join(", ");
187
- n.push(` '${t.table}': ${e} (${ye[t.signal]})`);
187
+ n.push(` '${t.table}': ${e} (${ve[t.signal]})`);
188
188
  }
189
189
  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");
190
- }, xe = /* @__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(".")), Se = (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()), Ce = (e) => Se(e).some((e) => xe.has(e)), we = (e) => {
190
+ }, be = /* @__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(".")), xe = (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()), Se = (e) => xe(e).some((e) => be.has(e)), Ce = (e) => {
191
191
  let t = [], n = [];
192
192
  for (let r of e) t.push({
193
193
  event: r.name,
@@ -196,7 +196,7 @@ var le = /* @__PURE__ */ new Set([
196
196
  event: r.name,
197
197
  kind: "wide-key",
198
198
  keyFieldCount: r.keyFieldCount
199
- }), r.hasWebhook && Ce(r.name) && n.push({
199
+ }), r.hasWebhook && Se(r.name) && n.push({
200
200
  event: r.name,
201
201
  kind: "webhook-high-rate"
202
202
  });
@@ -205,45 +205,45 @@ var le = /* @__PURE__ */ new Set([
205
205
  modes: t,
206
206
  warnings: n
207
207
  };
208
- }, Te = (e) => {
208
+ }, we = (e) => {
209
209
  if (e.eventsChecked === 0) return null;
210
210
  let t = e.eventsChecked, n = [`event delivery: ${t} declared event${t === 1 ? "" : "s"} — the mode decides what a MISSING message means`];
211
211
  for (let t of e.modes) n.push(` '${t.event}': ${t.delivery}`);
212
212
  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");
213
- }, Ee = (e) => {
213
+ }, Te = (e) => {
214
214
  if (e.warnings.length === 0) return null;
215
215
  let t = e.warnings.length, n = [`events that may not scale as declared: ${t} finding${t === 1 ? "" : "s"}`];
216
216
  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."));
217
217
  return n.push(" WARNING, not a fault — a producer may intend the fan-out. `voltro doctor --json` prints every finding."), n.join("\n");
218
- }, De = /* @__PURE__ */ new Set([
218
+ }, Ee = /* @__PURE__ */ new Set([
219
219
  "start",
220
220
  "run",
221
221
  "child"
222
- ]), Oe = (e) => T.isCallExpression(e) && (() => {
222
+ ]), De = (e) => E.isCallExpression(e) && (() => {
223
223
  let t = e.getExpression();
224
- if (!T.isPropertyAccessExpression(t) || !De.has(t.getName())) return !1;
224
+ if (!E.isPropertyAccessExpression(t) || !Ee.has(t.getName())) return !1;
225
225
  let n = t.getExpression();
226
- return T.isPropertyAccessExpression(n) ? n.getName() === "workflows" : T.isIdentifier(n) && n.getText() === "workflows";
227
- })(), ke = (e, t, n) => {
228
- let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new E({
226
+ return E.isPropertyAccessExpression(n) ? n.getName() === "workflows" : E.isIdentifier(n) && n.getText() === "workflows";
227
+ })(), Oe = (e, t, n) => {
228
+ let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new D({
229
229
  useInMemoryFileSystem: !0,
230
230
  skipAddingFilesFromTsConfig: !0
231
231
  });
232
232
  for (let t of e) {
233
233
  let e;
234
234
  try {
235
- let n = S(t, "utf8");
235
+ let n = C(t, "utf8");
236
236
  if (!n.includes("workflows.")) continue;
237
237
  e = l.createSourceFile(`/scan${t.endsWith("x") ? ".tsx" : ".ts"}`, n, { overwrite: !0 });
238
238
  } catch {
239
239
  continue;
240
240
  }
241
- let u = v(n, t);
242
- for (let t of e.getDescendantsOfKind(D.CallExpression)) {
243
- if (!Oe(t)) continue;
241
+ let u = y(n, t);
242
+ for (let t of e.getDescendantsOfKind(O.CallExpression)) {
243
+ if (!De(t)) continue;
244
244
  c++;
245
245
  let e = t.getStartLineNumber(), [n, l] = t.getArguments();
246
- if (n === void 0 || !T.isStringLiteral(n)) {
246
+ if (n === void 0 || !E.isStringLiteral(n)) {
247
247
  o.push({
248
248
  file: u,
249
249
  line: e,
@@ -262,7 +262,7 @@ var le = /* @__PURE__ */ new Set([
262
262
  });
263
263
  continue;
264
264
  }
265
- if (l === void 0 || !T.isObjectLiteralExpression(l)) {
265
+ if (l === void 0 || !E.isObjectLiteralExpression(l)) {
266
266
  o.push({
267
267
  file: u,
268
268
  line: e,
@@ -270,7 +270,7 @@ var le = /* @__PURE__ */ new Set([
270
270
  });
271
271
  continue;
272
272
  }
273
- if (l.getProperties().some((e) => T.isSpreadAssignment(e))) {
273
+ if (l.getProperties().some((e) => E.isSpreadAssignment(e))) {
274
274
  o.push({
275
275
  file: u,
276
276
  line: e,
@@ -279,14 +279,14 @@ var le = /* @__PURE__ */ new Set([
279
279
  continue;
280
280
  }
281
281
  let p = /* @__PURE__ */ new Set(), m = !1;
282
- for (let e of l.getProperties()) if (T.isPropertyAssignment(e) || T.isShorthandPropertyAssignment(e)) {
283
- let t = (T.isPropertyAssignment(e), e.getNameNode());
284
- if (T.isComputedPropertyName(t)) {
282
+ for (let e of l.getProperties()) if (E.isPropertyAssignment(e) || E.isShorthandPropertyAssignment(e)) {
283
+ let t = (E.isPropertyAssignment(e), e.getNameNode());
284
+ if (E.isComputedPropertyName(t)) {
285
285
  m = !0;
286
286
  break;
287
287
  }
288
288
  p.add(e.getName().replace(/^['"]|['"]$/g, ""));
289
- } else (T.isMethodDeclaration(e) || T.isGetAccessorDeclaration(e)) && p.add(e.getName());
289
+ } else (E.isMethodDeclaration(e) || E.isGetAccessorDeclaration(e)) && p.add(e.getName());
290
290
  if (m) {
291
291
  o.push({
292
292
  file: u,
@@ -324,7 +324,7 @@ var le = /* @__PURE__ */ new Set([
324
324
  checked: s,
325
325
  callSites: c
326
326
  };
327
- }, Ae = (e) => {
327
+ }, ke = (e) => {
328
328
  if (e.callSites === 0) return null;
329
329
  let t = [];
330
330
  t.push(`workflow starts: ${e.checked}/${e.callSites} call sites checked`);
@@ -337,7 +337,7 @@ var le = /* @__PURE__ */ new Set([
337
337
  for (let n of e.unchecked) t.push(` ${n.file}:${n.line} — ${n.reason}`);
338
338
  }
339
339
  return e.issues.length === 0 && e.unchecked.length === 0 && t.push(" every call site matches its workflow"), t.join("\n");
340
- }, je = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, k = "VOLTRO_FIELD_ENCRYPTION_KEY", Me = (e) => {
340
+ }, Ae = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, A = "VOLTRO_FIELD_ENCRYPTION_KEY", je = (e) => {
341
341
  let t;
342
342
  for (let [n, r] of e.sources) if (t === void 0 && /\bfieldEncryption\b/.test(r) && (t = {
343
343
  reason: "fieldEncryption",
@@ -345,13 +345,13 @@ var le = /* @__PURE__ */ new Set([
345
345
  }), t === void 0 && /\.encrypted\s*\(/.test(r) && (t = {
346
346
  reason: "encrypted-column",
347
347
  file: n
348
- }), r.includes(k)) return;
348
+ }), r.includes(A)) return;
349
349
  if (t !== void 0) {
350
- for (let [, t] of e.envFiles) if (t.includes(k)) return;
350
+ for (let [, t] of e.envFiles) if (t.includes(A)) return;
351
351
  return t;
352
352
  }
353
- }, Ne = (e) => [
354
- `✗ ${k} is named nowhere under this app root, and the app encrypts columns at rest`,
353
+ }, Me = (e) => [
354
+ `✗ ${A} is named nowhere under this app root, and the app encrypts columns at rest`,
355
355
  e.reason === "fieldEncryption" ? ` (\`fieldEncryption\` in ${e.file})` : ` (an \`.encrypted()\` column in ${e.file})`,
356
356
  "",
357
357
  " A deploy without it does not degrade — it refuses to boot at `onActivate`,",
@@ -367,14 +367,14 @@ var le = /* @__PURE__ */ new Set([
367
367
  " reaches your deploy target is not knowable from here — a secret manager",
368
368
  " or a CI variable is not a file. Naming it nowhere is conclusive; naming",
369
369
  " it somewhere is not a guarantee."
370
- ].join("\n"), Pe = (e) => {
370
+ ].join("\n"), Ne = (e) => {
371
371
  if (e.catalogCodes.length === 0) return;
372
372
  let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
373
373
  if (n.length !== 0) return {
374
374
  orphaned: n,
375
375
  noneDeclared: t.size === 0
376
376
  };
377
- }, Fe = (e, t) => {
377
+ }, Pe = (e, t) => {
378
378
  let n = e.orphaned.join(", ");
379
379
  return e.noneDeclared ? [
380
380
  `⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
@@ -386,33 +386,33 @@ var le = /* @__PURE__ */ new Set([
386
386
  " …or delete the files. A catalog that is never loaded looks from the inside",
387
387
  " exactly like one that works, which is why this is worth a line."
388
388
  ] : [`⚠ ${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)."];
389
- }, A = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), j = /* @__PURE__ */ new Set([
389
+ }, j = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), M = /* @__PURE__ */ new Set([
390
390
  "requireScope",
391
391
  "requireAnyScope",
392
392
  "requireAuth",
393
393
  "assertScope",
394
394
  "assertCan",
395
395
  "can"
396
- ]), Ie = (e) => {
396
+ ]), Fe = (e) => {
397
397
  let t = /* @__PURE__ */ new Set();
398
- for (let n of e) for (let e of A(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
398
+ for (let n of e) for (let e of j(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
399
399
  return t;
400
- }, Le = (e, t) => {
400
+ }, Ie = (e, t) => {
401
401
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
402
- }, Re = (e) => {
402
+ }, Le = (e) => {
403
403
  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);
404
404
  return t && n;
405
- }, ze = (e, t, n) => {
405
+ }, Re = (e, t, n) => {
406
406
  if (e.hasDeclaredGuards) return { kind: "declared-guard" };
407
- let r = A(e.src), i = Le(r, t);
408
- return i === void 0 ? Re(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
407
+ let r = j(e.src), i = Ie(r, t);
408
+ return i === void 0 ? Le(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
409
409
  kind: "vocabulary-guard",
410
410
  name: i
411
411
  };
412
- }, Be = (e, t, n) => {
412
+ }, ze = (e, t, n) => {
413
413
  let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
414
414
  return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
415
- }, Ve = (e, t, n) => {
415
+ }, Be = (e, t, n) => {
416
416
  switch (t.kind) {
417
417
  case "declared-guard": return "guards: on the descriptor";
418
418
  case "vocabulary-guard": return `calls ${t.name}()`;
@@ -420,15 +420,15 @@ var le = /* @__PURE__ */ new Set([
420
420
  case "allowlisted": return "accepted by a previous run";
421
421
  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`;
422
422
  }
423
- }, He = (e) => {
423
+ }, Ve = (e) => {
424
424
  let t = e.executors.map((t) => {
425
- let n = ze(t, e.guardVocabulary, e.allowlist);
425
+ let n = Re(t, e.guardVocabulary, e.allowlist);
426
426
  return {
427
427
  tag: t.tag,
428
428
  path: t.path,
429
429
  verdict: n,
430
- severity: Be(t, e.tenantScopedTables, e.referencedTables),
431
- why: Ve(t, n, e.rowFilterRegistered === !0)
430
+ severity: ze(t, e.tenantScopedTables, e.referencedTables),
431
+ why: Be(t, n, e.rowFilterRegistered === !0)
432
432
  };
433
433
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
434
434
  return {
@@ -442,7 +442,7 @@ var le = /* @__PURE__ */ new Set([
442
442
  },
443
443
  unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
444
444
  };
445
- }, Ue = [
445
+ }, He = [
446
446
  "# voltro doctor — authz allowlist",
447
447
  "#",
448
448
  "# Two KINDS of line, and the difference is the point:",
@@ -467,19 +467,19 @@ var le = /* @__PURE__ */ new Set([
467
467
  "# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
468
468
  "# lines for executors that are newly unchecked.",
469
469
  ""
470
- ], We = (e) => {
470
+ ], Ue = (e) => {
471
471
  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));
472
472
  return [
473
- ...Ue,
473
+ ...He,
474
474
  ...n,
475
475
  ...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
476
476
  ...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
477
477
  ""
478
478
  ].join("\n");
479
- }, Ge = (e, t) => {
479
+ }, We = (e, t) => {
480
480
  let n = [...new Set(t)].sort();
481
- if (e === void 0) return We(n.map((e) => ({ tag: e })));
482
- let r = M(e).tags, i = n.filter((e) => !r.has(e));
481
+ if (e === void 0) return Ue(n.map((e) => ({ tag: e })));
482
+ let r = N(e).tags, i = n.filter((e) => !r.has(e));
483
483
  return i.length === 0 ? e : [
484
484
  e.replace(/\n+$/, ""),
485
485
  "",
@@ -488,25 +488,25 @@ var le = /* @__PURE__ */ new Set([
488
488
  ...i,
489
489
  ""
490
490
  ].join("\n");
491
- }, Ke = (e, t) => {
491
+ }, Ge = (e, t) => {
492
492
  let n = [...new Set(t)];
493
493
  if (e === void 0) return {
494
494
  added: n.length,
495
495
  kept: 0,
496
496
  removed: 0
497
497
  };
498
- let r = M(e).tags;
498
+ let r = N(e).tags;
499
499
  return {
500
500
  added: n.filter((e) => !r.has(e)).length,
501
501
  kept: r.size,
502
502
  removed: 0
503
503
  };
504
- }, qe = class extends Error {
504
+ }, Ke = class extends Error {
505
505
  tag;
506
506
  constructor(e) {
507
507
  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";
508
508
  }
509
- }, M = (e) => {
509
+ }, N = (e) => {
510
510
  let t = [];
511
511
  for (let n of e.split("\n")) {
512
512
  let e = n.trim();
@@ -519,7 +519,7 @@ var le = /* @__PURE__ */ new Set([
519
519
  continue;
520
520
  }
521
521
  let a = r[2].trim();
522
- if (a.length === 0) throw new qe(i);
522
+ if (a.length === 0) throw new Ke(i);
523
523
  t.push({
524
524
  tag: i,
525
525
  reviewed: a
@@ -529,13 +529,13 @@ var le = /* @__PURE__ */ new Set([
529
529
  tags: new Set(t.map((e) => e.tag)),
530
530
  entries: t
531
531
  };
532
- }, N = (e) => {
532
+ }, P = (e) => {
533
533
  let t = e.indexOf(".");
534
534
  return t <= 0 ? void 0 : e.slice(0, t);
535
- }, Je = (e, t) => {
535
+ }, qe = (e, t) => {
536
536
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
537
537
  for (let e of t.rpcTags) {
538
- let t = N(e);
538
+ let t = P(e);
539
539
  if (t === void 0) continue;
540
540
  let n = i.get(t);
541
541
  n ? n.push(e) : i.set(t, [e]);
@@ -564,7 +564,7 @@ var le = /* @__PURE__ */ new Set([
564
564
  });
565
565
  continue;
566
566
  }
567
- let s = new Set(o.rpcTags.map(N).filter((e) => e !== void 0));
567
+ let s = new Set(o.rpcTags.map(P).filter((e) => e !== void 0));
568
568
  for (let e of s) {
569
569
  let t = i.get(e);
570
570
  t !== void 0 && a.push({
@@ -577,12 +577,12 @@ var le = /* @__PURE__ */ new Set([
577
577
  }
578
578
  }
579
579
  return a;
580
- }, Ye = (e, t) => {
580
+ }, Je = (e, t) => {
581
581
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
582
582
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
583
583
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
584
584
  return r;
585
- }, Xe = async (e) => {
585
+ }, Ye = async (e) => {
586
586
  if (!await e.hasWorkflows().catch(() => !1)) return { kind: "not-applicable" };
587
587
  let t;
588
588
  try {
@@ -606,12 +606,12 @@ var le = /* @__PURE__ */ new Set([
606
606
  } finally {
607
607
  await t.close().catch(() => {});
608
608
  }
609
- }, Ze = (e) => {
609
+ }, Xe = (e) => {
610
610
  let t = Math.round(e / 6e4);
611
611
  if (t < 90) return `${t}m`;
612
612
  let n = Math.round(t / 60);
613
613
  return n < 48 ? `${n}h` : `${Math.round(n / 24)}d`;
614
- }, Qe = (e) => {
614
+ }, Ze = (e) => {
615
615
  if (e.kind === "not-applicable") return null;
616
616
  if (e.kind === "skipped") return `• stuck runs: NOT CHECKED — ${e.reason}\n This is the one doctor rule that reads the database rather than your source.
617
617
  Run it where the app's DB env vars are set, or ignore this line locally.`;
@@ -619,41 +619,41 @@ var le = /* @__PURE__ */ new Set([
619
619
  if (t.stalled.length === 0) n.push(`✓ stuck runs: none (${t.examined} live run(s) old enough to examine${t.waitingOnTimer > 0 ? `, ${t.waitingOnTimer} waiting on a durable timer` : ""}${t.alreadyReported > 0 ? `, ${t.alreadyReported} already reported` : ""})`);
620
620
  else {
621
621
  n.push(`✗ stuck runs: ${t.stalled.length} run(s) have made no progress`);
622
- for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${Ze(e.idleMs)} ${e.reason}`);
622
+ for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${Xe(e.idleMs)} ${e.reason}`);
623
623
  t.stalled.length > 20 && n.push(` … and ${t.stalled.length - 20} more`), n.push(" Nothing was changed — this is a SUSPICION, not a verdict. `awaiting-signal` usually means the sender never came.");
624
624
  }
625
625
  t.sawFullRunPage && n.push(" (the examined page filled up — more old runs exist than were read this pass)");
626
626
  for (let e of t.failures) n.push(` ! could not examine ${e.subject}: ${e.detail}`);
627
627
  return n.join("\n");
628
- }, $e = (e) => x(y(e, "app.config.ts")) || x(y(e, "app.config.js")), P = (e) => {
628
+ }, Qe = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), $e = (e) => {
629
629
  let t = p(e), n = f(e);
630
630
  return {
631
631
  root: e,
632
632
  serveBundle: t,
633
- serveBundlePresent: x(t),
633
+ serveBundlePresent: S(t),
634
634
  apiBundle: n,
635
- apiBundlePresent: x(n)
635
+ apiBundlePresent: S(n)
636
636
  };
637
637
  }, et = [
638
638
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
639
639
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
640
640
  "(Generated Dockerfiles from `voltro create-project` already do this.)"
641
641
  ].join("\n "), tt = (e, t = !1) => {
642
- let n = P(e);
643
- if (console.log(`voltro doctor — serve preflight for ${n.root}`), !$e(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
642
+ let n = $e(e);
643
+ if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Qe(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
644
644
  console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
645
645
  let r = process.env.NODE_ENV === "production";
646
646
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
647
647
  let i = t || r;
648
648
  return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${et}`), +!!i;
649
649
  }, nt = (e, t, n) => {
650
- let r = new Set(e), i = ee(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
650
+ let r = new Set(e), i = _(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
651
651
  `${e}.ts`,
652
652
  `${e}.tsx`,
653
653
  `${e}.js`,
654
654
  `${e}.jsx`,
655
- _(e, "index.ts"),
656
- _(e, "index.tsx")
655
+ v(e, "index.ts"),
656
+ v(e, "index.tsx")
657
657
  ]).find((e) => r.has(e)), o = (e) => {
658
658
  let n = (t.get(e) ?? "").replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), r = [];
659
659
  for (let e of [
@@ -666,13 +666,13 @@ var le = /* @__PURE__ */ new Set([
666
666
  }
667
667
  let o = [];
668
668
  for (let t of r) {
669
- let n = t.startsWith(".") ? y(ae(e), t) : te(t, i);
669
+ let n = t.startsWith(".") ? b(ie(e), t) : ee(t, i);
670
670
  if (n === void 0) continue;
671
671
  let r = a(n);
672
672
  r !== void 0 && o.push(r);
673
673
  }
674
674
  return o;
675
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => ct.test(v(n, e))), l = new Set(c);
675
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => ct.test(y(n, e))), l = new Set(c);
676
676
  for (; c.length > 0;) {
677
677
  let e = c.shift();
678
678
  if (!s.has(e)) {
@@ -685,7 +685,7 @@ var le = /* @__PURE__ */ new Set([
685
685
  let d = /* @__PURE__ */ new Set();
686
686
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
687
687
  return d;
688
- }, rt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), it = (e, t) => (e.match(t) ?? []).length, F = (e, t) => e.getDescendantsOfKind(D.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, at = (e, t) => {
688
+ }, rt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), it = (e, t) => (e.match(t) ?? []).length, F = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, at = (e, t) => {
689
689
  if (e.src.includes(t)) return !0;
690
690
  let n = t.replace(/^@voltro\/plugin-/, "");
691
691
  return n !== t && F(e.file, RegExp(`^${n}Plugin$`)) > 0;
@@ -699,7 +699,7 @@ var le = /* @__PURE__ */ new Set([
699
699
  return n;
700
700
  }, st = (e, t) => {
701
701
  let n = t.getSymbol()?.getDeclarations()?.[0];
702
- return n === void 0 ? [] : e.getDescendantsOfKind(D.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
702
+ return n === void 0 ? [] : e.getDescendantsOfKind(O.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
703
703
  }, ct = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, lt = /\.(query|mutation|action|stream)\.server\.tsx?$/, ut = [
704
704
  "Id",
705
705
  "ID",
@@ -814,16 +814,16 @@ var le = /* @__PURE__ */ new Set([
814
814
  scope: "client",
815
815
  smell: "checking a subscription's `data === undefined` (or `!data`) instead of its `loading` / `idle` flag",
816
816
  use: "branch on `loading` (and `idle` when you pass `skip`) — `loading` narrows `data`, so the settled branch needs no `?? []` and no `!`",
817
- match: ({ file: e }) => e.getDescendantsOfKind(D.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
818
- let t = e.getFirstAncestorByKind(D.VariableDeclaration)?.getNameNode();
819
- return t === void 0 ? [] : T.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
817
+ match: ({ file: e }) => e.getDescendantsOfKind(O.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
818
+ let t = e.getFirstAncestorByKind(O.VariableDeclaration)?.getNameNode();
819
+ return t === void 0 ? [] : E.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
820
820
  }).some((t) => st(e, t).some((e) => {
821
821
  let t = e.getParent();
822
- if (T.isBinaryExpression(t)) {
822
+ if (E.isBinaryExpression(t)) {
823
823
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
824
824
  return /^[!=]==?$/.test(n) && /^(undefined|null)$/.test(r.getText());
825
825
  }
826
- return T.isPrefixUnaryExpression(t) && t.getOperatorToken() === D.ExclamationToken;
826
+ return E.isPrefixUnaryExpression(t) && t.getOperatorToken() === O.ExclamationToken;
827
827
  }))
828
828
  },
829
829
  {
@@ -957,7 +957,7 @@ var le = /* @__PURE__ */ new Set([
957
957
  ], pt = (e) => R(e), mt = (e, t) => {
958
958
  let n = /* @__PURE__ */ new Set();
959
959
  for (let r of t) {
960
- let t = v(e, r), i = t.split(/[\\/]/)[0];
960
+ let t = y(e, r), i = t.split(/[\\/]/)[0];
961
961
  n.add(i !== void 0 && i !== t ? i : ".");
962
962
  }
963
963
  return [...n].sort();
@@ -965,30 +965,30 @@ var le = /* @__PURE__ */ new Set([
965
965
  if (n > 12 || L.test(e)) return t;
966
966
  let r;
967
967
  try {
968
- r = C(e, { withFileTypes: !0 });
968
+ r = w(e, { withFileTypes: !0 });
969
969
  } catch {
970
970
  return t;
971
971
  }
972
972
  for (let i of r) {
973
- let r = _(e, i.name);
973
+ let r = v(e, i.name);
974
974
  if (i.isDirectory()) {
975
975
  if (i.name.startsWith(".")) continue;
976
976
  gt(r, t, n + 1);
977
- } else je.test(i.name) && t.push(r);
977
+ } else Ae.test(i.name) && t.push(r);
978
978
  }
979
979
  return t;
980
980
  }, R = (e, t = [], n = 0) => {
981
981
  if (n > 12 || L.test(e)) return t;
982
982
  let r;
983
983
  try {
984
- r = C(e);
984
+ r = w(e);
985
985
  } catch {
986
986
  return t;
987
987
  }
988
988
  for (let i of r) {
989
- let r = _(e, i), a;
989
+ let r = v(e, i), a;
990
990
  try {
991
- a = w(r);
991
+ a = T(r);
992
992
  } catch {
993
993
  continue;
994
994
  }
@@ -999,14 +999,14 @@ var le = /* @__PURE__ */ new Set([
999
999
  if (n > 12 || L.test(e)) return t;
1000
1000
  let r;
1001
1001
  try {
1002
- r = C(e);
1002
+ r = w(e);
1003
1003
  } catch {
1004
1004
  return t;
1005
1005
  }
1006
1006
  for (let i of r) {
1007
- let r = _(e, i), a;
1007
+ let r = v(e, i), a;
1008
1008
  try {
1009
- a = w(r);
1009
+ a = T(r);
1010
1010
  } catch {
1011
1011
  continue;
1012
1012
  }
@@ -1022,16 +1022,16 @@ var le = /* @__PURE__ */ new Set([
1022
1022
  }, yt = (e) => {
1023
1023
  let t = pt(e);
1024
1024
  if (t.length === 0) return [];
1025
- let n = new E({
1025
+ let n = new D({
1026
1026
  useInMemoryFileSystem: !0,
1027
1027
  skipAddingFilesFromTsConfig: !0
1028
1028
  }), r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map();
1029
1029
  for (let e of t) try {
1030
- i.set(e, S(e, "utf8"));
1030
+ i.set(e, C(e, "utf8"));
1031
1031
  } catch {}
1032
1032
  let a = nt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1033
1033
  for (let t of _t(e)) try {
1034
- o.set(t, S(t, "utf8"));
1034
+ o.set(t, C(t, "utf8"));
1035
1035
  } catch {}
1036
1036
  for (let s of t) {
1037
1037
  let t = i.get(s);
@@ -1041,7 +1041,7 @@ var le = /* @__PURE__ */ new Set([
1041
1041
  c = {
1042
1042
  src: t,
1043
1043
  file: n.createSourceFile(`/scan${s.endsWith("x") ? ".tsx" : ".ts"}`, t, { overwrite: !0 }),
1044
- path: v(e, s),
1044
+ path: y(e, s),
1045
1045
  serverOnly: a.has(s),
1046
1046
  testSources: o
1047
1047
  };
@@ -1051,7 +1051,7 @@ var le = /* @__PURE__ */ new Set([
1051
1051
  for (let t of ft) {
1052
1052
  if (t.plugin !== void 0 && at(c, t.plugin) || !t.match(c)) continue;
1053
1053
  let n = r.get(t.id) ?? [];
1054
- n.push(v(e, s)), r.set(t.id, n);
1054
+ n.push(y(e, s)), r.set(t.id, n);
1055
1055
  }
1056
1056
  }
1057
1057
  return ft.filter((e) => r.has(e.id)).map((e) => ({
@@ -1075,17 +1075,17 @@ var le = /* @__PURE__ */ new Set([
1075
1075
  }, bt = 3, xt = .2, St = 50, Ct = (e, t) => e >= St && t > 0 && e / t >= xt, wt = (e) => {
1076
1076
  let t = /* @__PURE__ */ new Map();
1077
1077
  for (let n of R(e)) try {
1078
- t.set(v(e, n), S(n, "utf8"));
1078
+ t.set(y(e, n), C(n, "utf8"));
1079
1079
  } catch {}
1080
1080
  let n = /* @__PURE__ */ new Map();
1081
1081
  for (let t of gt(e)) try {
1082
- n.set(v(e, t), S(t, "utf8"));
1082
+ n.set(y(e, t), C(t, "utf8"));
1083
1083
  } catch {}
1084
- let r = Me({
1084
+ let r = je({
1085
1085
  sources: t,
1086
1086
  envFiles: n
1087
1087
  });
1088
- r !== void 0 && (console.log(""), console.log(Ne(r)));
1088
+ r !== void 0 && (console.log(""), console.log(Me(r)));
1089
1089
  }, Tt = (e) => {
1090
1090
  let t = z(e);
1091
1091
  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;
@@ -1116,37 +1116,39 @@ var le = /* @__PURE__ */ new Set([
1116
1116
  let n = Ot.exec(e)?.[1];
1117
1117
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1118
1118
  }, B = async (e) => {
1119
- let t = _(e, "src", "pages");
1120
- if (!x(t)) return [];
1119
+ let t = v(e, "src", "pages");
1120
+ if (!S(t)) return [];
1121
1121
  let { pages: n, dirs: r } = await l(t), i = [];
1122
1122
  for (let a of n) {
1123
1123
  if (!s(r, a.dirChain).some((e) => e.hasLayout)) continue;
1124
1124
  let n;
1125
1125
  try {
1126
- n = S(_(t, a.file), "utf8");
1126
+ n = C(v(t, a.file), "utf8");
1127
1127
  } catch {
1128
1128
  continue;
1129
1129
  }
1130
1130
  if (c.test(n)) continue;
1131
1131
  let o = kt(n);
1132
1132
  o !== null && i.push({
1133
- file: v(e, _(t, a.file)),
1133
+ file: y(e, v(t, a.file)),
1134
1134
  pattern: a.pattern,
1135
1135
  currentMode: o
1136
1136
  });
1137
1137
  }
1138
1138
  return i;
1139
1139
  }, At = 10, jt = async (e) => {
1140
- if (console.log(""), !x(_(e, "src", "pages"))) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
1141
- let t = await B(e);
1142
- if (t.length === 0) return console.log(" spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1143
- console.log(`• renderMode:'spa' candidates (${t.length} page${t.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1144
- for (let e of t.slice(0, At)) {
1140
+ console.log("");
1141
+ let t = v(e, "src", "pages");
1142
+ if (!S(t)) return console.log(" spa-candidate scan: no src/pages here nothing to check (web apps only)."), 0;
1143
+ let n = await B(e);
1144
+ if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1145
+ console.log(`• renderMode:'spa' candidates (${n.length} page${n.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1146
+ for (let e of n.slice(0, At)) {
1145
1147
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1146
1148
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1147
1149
  }
1148
- let n = t.length - At;
1149
- return n > 0 && console.log(` (+${n} more page${n === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${Et}`), t.length;
1150
+ let r = n.length - At;
1151
+ return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${Et}`), n.length;
1150
1152
  }, Mt = /* @__PURE__ */ new Set([
1151
1153
  "node_modules",
1152
1154
  "dist",
@@ -1158,60 +1160,60 @@ var le = /* @__PURE__ */ new Set([
1158
1160
  ]), Nt = (e, t, n) => {
1159
1161
  let r;
1160
1162
  try {
1161
- r = C(e, { withFileTypes: !0 });
1163
+ r = w(e, { withFileTypes: !0 });
1162
1164
  } catch {
1163
1165
  return;
1164
1166
  }
1165
1167
  for (let i of r) {
1166
1168
  if (Mt.has(i.name)) continue;
1167
- let r = _(e, i.name);
1168
- i.isDirectory() ? Nt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(v(t, r));
1169
+ let r = v(e, i.name);
1170
+ i.isDirectory() ? Nt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1169
1171
  }
1170
1172
  }, Pt = (e, t) => {
1171
1173
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1172
1174
  for (let i of t) {
1173
1175
  let t;
1174
1176
  try {
1175
- t = S(_(e, i), "utf8");
1177
+ t = C(v(e, i), "utf8");
1176
1178
  } catch {
1177
1179
  continue;
1178
1180
  }
1179
- let a = ae(_(e, i));
1181
+ let a = ie(v(e, i));
1180
1182
  for (let i of t.matchAll(r)) {
1181
- let t = y(a, i[1]);
1183
+ let t = b(a, i[1]);
1182
1184
  for (let r of [
1183
1185
  t,
1184
1186
  `${t}.tsx`,
1185
1187
  `${t}.ts`,
1186
- _(t, "index.tsx"),
1187
- _(t, "index.ts")
1188
- ]) if (x(r) && w(r).isFile()) {
1189
- n.add(v(e, r));
1188
+ v(t, "index.tsx"),
1189
+ v(t, "index.ts")
1190
+ ]) if (S(r) && T(r).isFile()) {
1191
+ n.add(y(e, r));
1190
1192
  break;
1191
1193
  }
1192
1194
  }
1193
1195
  }
1194
1196
  return n;
1195
1197
  }, Ft = (e, t) => {
1196
- let n = e.slice(t.length).split(b).slice(0, -1)[0];
1198
+ let n = e.slice(t.length).split(x).slice(0, -1)[0];
1197
1199
  return n !== void 0 && n.startsWith("[");
1198
1200
  }, V = (e) => {
1199
- let t = [], n = _(e, "src", "pages"), r = _(e, "src");
1200
- if (!x(r)) return t;
1201
+ let t = [], n = v(e, "src", "pages"), r = v(e, "src");
1202
+ if (!S(r)) return t;
1201
1203
  let i = [];
1202
1204
  Nt(r, e, i), i.sort();
1203
- let a = `${v(e, n)}${b}`;
1205
+ let a = `${y(e, n)}${x}`;
1204
1206
  Pt(e, i);
1205
1207
  for (let n of i) {
1206
- let r = n.slice(n.lastIndexOf(b) + 1);
1208
+ let r = n.slice(n.lastIndexOf(x) + 1);
1207
1209
  if (n.startsWith(a)) {
1208
1210
  if (r !== "page.tsx") continue;
1209
- x(_(e, d(n))) || t.push({
1211
+ S(v(e, d(n))) || t.push({
1210
1212
  kind: "page-without-test",
1211
1213
  file: n
1212
1214
  });
1213
1215
  try {
1214
- let r = S(_(e, n), "utf8");
1216
+ let r = C(v(e, n), "utf8");
1215
1217
  !/\bexport\s+(?:const|let|var|function)\s+meta\b/.test(r) && !/\bexport\s*\{[^}]*\bmeta\b/.test(r) && t.push({
1216
1218
  kind: "page-without-meta",
1217
1219
  file: n
@@ -1236,16 +1238,18 @@ var le = /* @__PURE__ */ new Set([
1236
1238
  let r = t.length - It;
1237
1239
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
1238
1240
  }, Lt = (e) => {
1239
- if (console.log(""), !x(_(e, "src", "pages"))) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
1240
- let t = V(e);
1241
- 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;
1242
- let n = (e) => t.filter((t) => t.kind === e).map((e) => e.file);
1243
- return H("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."), H("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."), H("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."), H("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). 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."), t.length;
1241
+ console.log("");
1242
+ let t = v(e, "src", "pages");
1243
+ if (!S(t)) return console.log(" page-convention scan: no src/pages here nothing to check (web apps only)."), 0;
1244
+ let n = V(e);
1245
+ 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;
1246
+ let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
1247
+ 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;
1244
1248
  }, Rt = "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.", zt = /* @__PURE__ */ new Map(), U = (e) => {
1245
1249
  let t = zt.get(e);
1246
1250
  if (t !== void 0) return t;
1247
1251
  let n = (async () => {
1248
- let { walk: t, loadDiscovered: n } = await import("./dev-CAOoXOkc.js"), r = await t(e);
1252
+ let { walk: t, loadDiscovered: n } = await import("./dev-CjJAasDs.js"), r = await t(e);
1249
1253
  return {
1250
1254
  files: r,
1251
1255
  discovered: await n(r)
@@ -1263,7 +1267,7 @@ var le = /* @__PURE__ */ new Set([
1263
1267
  };
1264
1268
  });
1265
1269
  if (i.length === 0) return;
1266
- t = pe(fe(n, i, e));
1270
+ t = fe(de(n, i, e));
1267
1271
  } catch {
1268
1272
  return;
1269
1273
  }
@@ -1281,19 +1285,19 @@ var le = /* @__PURE__ */ new Set([
1281
1285
  }), W = async (e) => {
1282
1286
  try {
1283
1287
  let { discovered: t } = await U(e);
1284
- return ve(Vt(t.tables));
1288
+ return _e(Vt(t.tables));
1285
1289
  } catch {
1286
1290
  return null;
1287
1291
  }
1288
1292
  }, Ht = async (e) => {
1289
1293
  let t = await W(e);
1290
1294
  if (t === null || t.tablesChecked === 0) return;
1291
- let n = be(t);
1295
+ let n = ye(t);
1292
1296
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
1293
1297
  }, G = async (e) => {
1294
1298
  try {
1295
1299
  let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await U(e);
1296
- return we(n.events.map((e) => {
1300
+ return Ce(n.events.map((e) => {
1297
1301
  let n = e.descriptor, r = t(n.key);
1298
1302
  return {
1299
1303
  name: n.name,
@@ -1308,9 +1312,9 @@ var le = /* @__PURE__ */ new Set([
1308
1312
  }, Ut = async (e) => {
1309
1313
  let t = await G(e);
1310
1314
  if (t === null || t.eventsChecked === 0) return;
1311
- let n = Te(t);
1315
+ let n = we(t);
1312
1316
  n !== null && (console.log(""), console.log(`• ${n}`));
1313
- let r = Ee(t);
1317
+ let r = Te(t);
1314
1318
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
1315
1319
  }, K = (e) => {
1316
1320
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
@@ -1333,7 +1337,7 @@ var le = /* @__PURE__ */ new Set([
1333
1337
  }
1334
1338
  }, J = async (r) => {
1335
1339
  try {
1336
- let { loadApiConfig: i } = await import("./dev-CAOoXOkc.js"), { discovered: a } = await U(r), o = n(await i(r)), s = {
1340
+ let { loadApiConfig: i } = await import("./dev-CjJAasDs.js"), { discovered: a } = await U(r), o = n(await i(r)), s = {
1337
1341
  queries: a.queries,
1338
1342
  mutations: a.mutations,
1339
1343
  actions: a.actions,
@@ -1343,8 +1347,8 @@ var le = /* @__PURE__ */ new Set([
1343
1347
  return {
1344
1348
  checked: !0,
1345
1349
  defaultDeny: o,
1346
- undecided: e(s),
1347
- openAccess: t(s)
1350
+ undecided: t(s),
1351
+ openAccess: e(s)
1348
1352
  };
1349
1353
  } catch (e) {
1350
1354
  return {
@@ -1368,14 +1372,14 @@ var le = /* @__PURE__ */ new Set([
1368
1372
  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");
1369
1373
  }, Y = async (e) => {
1370
1374
  try {
1371
- let { files: t } = await U(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-CrGAfhIG.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
1375
+ 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(re(e)));
1372
1376
  if (a.length === 0) return {
1373
1377
  checked: !0,
1374
1378
  unverified: [],
1375
1379
  open: [],
1376
1380
  verified: 0
1377
1381
  };
1378
- let o = await r(a, ce({ scope: "voltro:doctor" })), s = [], c = [], l = 0;
1382
+ let o = await r(a, se({ scope: "voltro:doctor" })), s = [], c = [], l = 0;
1379
1383
  for (let e of o.incoming) {
1380
1384
  let t = i(e.descriptor);
1381
1385
  t === null ? s.push({
@@ -1413,11 +1417,11 @@ var le = /* @__PURE__ */ new Set([
1413
1417
  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;
1414
1418
  }, qt = async (e) => {
1415
1419
  let { status: t, message: n } = await q(e);
1416
- 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);
1417
- }, Jt = async (e) => Xe({
1420
+ 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);
1421
+ }, Jt = async (e) => Ye({
1418
1422
  hasWorkflows: async () => (await U(e)).discovered.workflows.length > 0,
1419
1423
  openStore: async () => {
1420
- let { loadApiConfig: t, buildStore: n } = await import("./dev-CAOoXOkc.js"), r = await t(e);
1424
+ let { loadApiConfig: t, buildStore: n } = await import("./dev-CjJAasDs.js"), r = await t(e);
1421
1425
  if (r === null) throw Error("no app.config.ts in this directory");
1422
1426
  let i = await n(r, void 0);
1423
1427
  return {
@@ -1432,13 +1436,13 @@ var le = /* @__PURE__ */ new Set([
1432
1436
  return t({ store: e });
1433
1437
  }
1434
1438
  }), Yt = async (e) => {
1435
- let t = Qe(await Jt(e));
1439
+ let t = Ze(await Jt(e));
1436
1440
  t !== null && (console.log(""), console.log(t));
1437
1441
  }, Xt = async (e) => {
1438
1442
  let t;
1439
1443
  try {
1440
1444
  let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await U(e);
1441
- t = ke(r, i.workflows.map((e) => {
1445
+ t = Oe(r, i.workflows.map((e) => {
1442
1446
  let t = e.definition, r = t.payloadSchema === void 0 ? {
1443
1447
  required: [],
1444
1448
  optional: []
@@ -1452,18 +1456,18 @@ var le = /* @__PURE__ */ new Set([
1452
1456
  } catch {
1453
1457
  return;
1454
1458
  }
1455
- let n = Ae(t);
1459
+ let n = ke(t);
1456
1460
  if (n === null) return;
1457
1461
  console.log("");
1458
1462
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
1459
1463
  console.log(`${r} ${n}`);
1460
1464
  }, X = "voltro-authz-allowlist.txt", Zt = (e, t = (e) => {
1461
1465
  try {
1462
- return S(e, "utf8");
1466
+ return C(e, "utf8");
1463
1467
  } catch {
1464
1468
  return "";
1465
1469
  }
1466
- }) => /* @__PURE__ */ new Set([...j, ...Ie(R(e).map(t))]), Qt = (e, t) => {
1470
+ }) => /* @__PURE__ */ new Set([...M, ...Fe(R(e).map(t))]), Qt = (e, t) => {
1467
1471
  let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1468
1472
  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];
1469
1473
  }, Z = async (e) => {
@@ -1475,7 +1479,7 @@ var le = /* @__PURE__ */ new Set([
1475
1479
  if (n.length === 0) return [];
1476
1480
  let r = new Set(t.events.map((e) => e.file)), i = (e) => {
1477
1481
  try {
1478
- return S(e, "utf8");
1482
+ return C(e, "utf8");
1479
1483
  } catch {
1480
1484
  return "";
1481
1485
  }
@@ -1497,24 +1501,33 @@ var le = /* @__PURE__ */ new Set([
1497
1501
  }
1498
1502
  }, en = async (e) => {
1499
1503
  try {
1500
- let { loadApiConfig: t } = await import("./dev-CAOoXOkc.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1504
+ let { loadApiConfig: t } = await import("./dev-CjJAasDs.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1501
1505
  if (typeof n != "string") return [];
1502
1506
  let [r, i] = n.split("#");
1503
1507
  if (r === void 0 || i === void 0) return [];
1504
- let a = (await import(se(y(e, r)).href))[i];
1508
+ let a = (await import(oe(b(e, r)).href))[i];
1505
1509
  return Array.isArray(a) ? a.filter((e) => typeof e == "string") : [];
1506
1510
  } catch {
1507
1511
  return [];
1508
1512
  }
1509
1513
  }, Q = async (e) => {
1510
- let t = {
1514
+ let t = (e) => ({
1511
1515
  evaluated: !1,
1516
+ notEvaluated: e,
1512
1517
  vocabularySize: 0,
1513
1518
  findings: []
1514
- };
1519
+ });
1515
1520
  try {
1516
- let { offlineManifest: n, toInput: r } = await import("./checkCommand-wibtQx-N.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-zuMGKVYX.js"), o = await n(e), s = r(o.manifest, o.tables), c = await en(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1517
- if (l.length === 0) return t;
1521
+ let { offlineManifest: n, toInput: r } = await import("./checkCommand-D9qK_gtA.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-zuMGKVYX.js"), o = await n(e), s = r(o.manifest, o.tables), c = await en(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1522
+ if (l.length === 0) {
1523
+ try {
1524
+ let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-DgylYh9_.js");
1525
+ await t(e);
1526
+ } catch {
1527
+ return t("app-not-importable");
1528
+ }
1529
+ return t("no-vocabulary");
1530
+ }
1518
1531
  let u = i({
1519
1532
  ...s,
1520
1533
  scopes: l
@@ -1530,12 +1543,16 @@ var le = /* @__PURE__ */ new Set([
1530
1543
  }))
1531
1544
  };
1532
1545
  } catch {
1533
- return t;
1546
+ return t("app-not-importable");
1534
1547
  }
1535
1548
  }, tn = async (e) => {
1536
- let { findings: t, evaluated: n } = await Q(e);
1549
+ let { findings: t, evaluated: n, notEvaluated: r } = await Q(e);
1537
1550
  if (!n) {
1538
- console.log(""), console.log("• scope vocabulary: none found — the unknown-scope rule did NOT run."), console.log(" It compares each procedure's required scopes against the scopes your"), console.log(" roles actually grant, and needs somewhere to read that set from."), console.log(" `@voltro/plugin-rbac` publishes it from its `roles` map; an app doing"), console.log(" RBAC without the plugin can point at its own:"), console.log(""), console.log(" // app.config.ts"), console.log(" doctor: { scopeVocabulary: './lib/teamRoles.ts#ALL_TEAM_SCOPES' }"), console.log(""), console.log(" Until then this section is NOT a clean bill of health — a procedure"), console.log(" requiring a scope no role grants is uncallable by everyone, and"), console.log(" nothing here would have told you.");
1551
+ if (console.log(""), r === "app-not-importable") {
1552
+ console.log("• scope vocabulary: NOT EVALUATED — `app.config.ts` could not be imported."), console.log(" This is not a statement about your scopes: the rule never got to read"), console.log(" them. The commonest cause is a config that demands its environment while"), console.log(" it is evaluated — an auth strategy reading a secret, say — so it imports"), console.log(" inside the container and not on a workstation."), console.log(""), console.log(" Run doctor where that environment exists, or make the config importable"), console.log(" without it. The same import failure is reported above for plugin tables;"), console.log(" both sections have this one cause.");
1553
+ return;
1554
+ }
1555
+ console.log("• scope vocabulary: none found — the unknown-scope rule did NOT run."), console.log(" It compares each procedure's required scopes against the scopes your"), console.log(" roles actually grant, and needs somewhere to read that set from."), console.log(" `@voltro/plugin-rbac` publishes it from its `roles` map; an app doing"), console.log(" RBAC without the plugin can point at its own:"), console.log(""), console.log(" // app.config.ts"), console.log(" doctor: { scopeVocabulary: './lib/teamRoles.ts#ALL_TEAM_SCOPES' }"), console.log(""), console.log(" Until then this section is NOT a clean bill of health — a procedure"), console.log(" requiring a scope no role grants is uncallable by everyone, and"), console.log(" nothing here would have told you.");
1539
1556
  return;
1540
1557
  }
1541
1558
  if (t.length !== 0) {
@@ -1546,32 +1563,32 @@ var le = /* @__PURE__ */ new Set([
1546
1563
  }, nn = async (e) => {
1547
1564
  let t = ["src/locales", "locales"], n, r = [];
1548
1565
  for (let i of t) {
1549
- let t = _(e, i);
1550
- if (x(t)) try {
1551
- r = C(t).filter((e) => /\.tsx?$/.test(e) && !e.endsWith(".d.ts")).map((e) => e.replace(/\.tsx?$/, "")), n = i;
1566
+ let t = v(e, i);
1567
+ if (S(t)) try {
1568
+ r = w(t).filter((e) => /\.tsx?$/.test(e) && !e.endsWith(".d.ts")).map((e) => e.replace(/\.tsx?$/, "")), n = i;
1552
1569
  break;
1553
1570
  } catch {}
1554
1571
  }
1555
1572
  if (n === void 0 || r.length === 0) return;
1556
1573
  let i = [];
1557
1574
  try {
1558
- let { loadApiConfig: t } = await import("./dev-CAOoXOkc.js");
1575
+ let { loadApiConfig: t } = await import("./dev-CjJAasDs.js");
1559
1576
  i = (await t(e))?.locales ?? [];
1560
1577
  } catch {
1561
1578
  return;
1562
1579
  }
1563
- let a = Pe({
1580
+ let a = Ne({
1564
1581
  catalogCodes: r,
1565
1582
  declaredLocales: i
1566
1583
  });
1567
1584
  if (a !== void 0) {
1568
1585
  console.log("");
1569
- for (let e of Fe(a, n)) console.log(e);
1586
+ for (let e of Pe(a, n)) console.log(e);
1570
1587
  }
1571
1588
  }, rn = async (e) => {
1572
1589
  let { discovered: t } = await U(e), n = [];
1573
1590
  try {
1574
- let { loadApiConfig: t } = await import("./dev-CAOoXOkc.js");
1591
+ let { loadApiConfig: t } = await import("./dev-CjJAasDs.js");
1575
1592
  n = ((await t(e))?.plugins ?? []).map((e) => {
1576
1593
  let t = (e.extendSchema?.tables ?? []).map((e) => {
1577
1594
  let t = e;
@@ -1597,12 +1614,12 @@ var le = /* @__PURE__ */ new Set([
1597
1614
  ...t.mutations,
1598
1615
  ...t.actions,
1599
1616
  ...t.streams ?? []
1600
- ].map((e) => String(e.descriptor.name)), i = Je(n, {
1617
+ ].map((e) => String(e.descriptor.name)), i = qe(n, {
1601
1618
  tables: t.tables.map((e) => e.tableName),
1602
1619
  rpcTags: r
1603
1620
  });
1604
1621
  console.log();
1605
- for (let e of Ye(n, i)) console.log(e);
1622
+ for (let e of Je(n, i)) console.log(e);
1606
1623
  }, an = /* @__PURE__ */ new Set([
1607
1624
  "@voltro/plugin-rbac",
1608
1625
  "@voltro/plugin-audit",
@@ -1641,7 +1658,7 @@ var le = /* @__PURE__ */ new Set([
1641
1658
  }, cn = async (e) => {
1642
1659
  let { discovered: t } = await U(e), n = /* @__PURE__ */ new Map(), r = (e) => {
1643
1660
  if (!n.has(e)) try {
1644
- n.set(e, S(e, "utf8"));
1661
+ n.set(e, C(e, "utf8"));
1645
1662
  } catch {
1646
1663
  n.set(e, "");
1647
1664
  }
@@ -1678,12 +1695,12 @@ var le = /* @__PURE__ */ new Set([
1678
1695
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
1679
1696
  ...a?.table === void 0 ? {} : { table: a.table }
1680
1697
  };
1681
- })), s = _(e, X), c = x(s) ? M(S(s, "utf8")) : void 0, l = !1;
1698
+ })), s = v(e, X), c = S(s) ? N(C(s, "utf8")) : void 0, l = !1;
1682
1699
  for (let t of R(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
1683
1700
  l = !0;
1684
1701
  break;
1685
1702
  }
1686
- let u = He({
1703
+ let u = Ve({
1687
1704
  executors: o,
1688
1705
  guardVocabulary: i,
1689
1706
  tenantScopedTables: t.tenantScopedTables,
@@ -1703,12 +1720,12 @@ var le = /* @__PURE__ */ new Set([
1703
1720
  }, ln = async (e, t) => {
1704
1721
  let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await cn(e), l = n.counts;
1705
1722
  console.log(`\nauthz scan · ${o} executor(s)`);
1706
- let u = [...r].filter((e) => !j.has(e)).sort();
1723
+ let u = [...r].filter((e) => !M.has(e)).sort();
1707
1724
  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)}`);
1708
1725
  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;
1709
1726
  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) {
1710
- let e = x(s) ? S(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ke(e, t);
1711
- return oe(s, Ge(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;
1727
+ let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ge(e, t);
1728
+ return ae(s, We(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;
1712
1729
  }
1713
1730
  if (n.unchecked.length === 0) return !1;
1714
1731
  console.log("");
@@ -1716,30 +1733,30 @@ var le = /* @__PURE__ */ new Set([
1716
1733
  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);
1717
1734
  }, un = async (e) => {
1718
1735
  if (e.includes("--help") || e.includes("-h")) return console.log(Rt), 0;
1719
- let t = e.includes("--strict"), n = y(e.find((e) => !e.startsWith("-")) ?? process.cwd());
1736
+ let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), s = b(n ?? process.cwd());
1720
1737
  if (h(e)) return g(), m({
1721
- ...z(n),
1722
- spaCandidates: await B(n),
1723
- pageConvention: V(n),
1724
- fileTaxonomy: re(n).findings,
1725
- junctionFks: (await W(n))?.issues ?? [],
1726
- eventDelivery: await G(n),
1727
- duplicateInstances: a(n),
1728
- versionSkew: o(n) ?? null,
1729
- serverOnly: (await q(n)).status,
1730
- accessDecisions: await J(n),
1731
- webhookVerification: await Y(n),
1732
- authz: await sn(n),
1733
- ungrantedScopes: await Q(n),
1734
- neverEmittedEvents: await Z(n),
1735
- stalledRuns: await Jt(n)
1738
+ ...z(s),
1739
+ spaCandidates: await B(s),
1740
+ pageConvention: V(s),
1741
+ fileTaxonomy: ne(s).findings,
1742
+ junctionFks: (await W(s))?.issues ?? [],
1743
+ eventDelivery: await G(s),
1744
+ duplicateInstances: a(s),
1745
+ versionSkew: o(s) ?? null,
1746
+ serverOnly: (await q(s)).status,
1747
+ accessDecisions: await J(s),
1748
+ webhookVerification: await Y(s),
1749
+ authz: await sn(s),
1750
+ ungrantedScopes: await Q(s),
1751
+ neverEmittedEvents: await Z(s),
1752
+ stalledRuns: await Jt(s)
1736
1753
  }), 0;
1737
- let s = tt(n, t);
1738
- Tt(n), wt(n), Lt(n), ne(n), await jt(n), await Xt(n), await Yt(n), await Bt(n), await Ht(n), await Ut(n), await rn(n), await tn(n), await $t(n), await nn(n);
1739
- let c = await qt(n), l = await ln(n, e.includes("--write-authz-allowlist")), u = await Wt(n), d = await Kt(n), f = r(o(n));
1740
- f !== void 0 && (console.log(""), console.log(f));
1741
- let p = i(a(n));
1742
- return p !== void 0 && console.log(`\n${p}`), c || l || u || d ? Math.max(s, 1) : s;
1754
+ let c = tt(s, t);
1755
+ Tt(s), wt(s), Lt(s), te(s), await jt(s), await Xt(s), await Yt(s), await Bt(s), await Ht(s), await Ut(s), await rn(s), await tn(s), await $t(s), await nn(s);
1756
+ let l = await qt(s), u = await ln(s, e.includes("--write-authz-allowlist")), d = await Wt(s), f = await Kt(s), p = r(o(s));
1757
+ p !== void 0 && (console.log(""), console.log(p));
1758
+ let _ = i(a(s));
1759
+ return _ !== void 0 && console.log(`\n${_}`), l || u || d || f ? Math.max(c, 1) : c;
1743
1760
  };
1744
1761
  //#endregion
1745
- export { q as C, B as S, Y as T, jt as _, sn as a, yt as b, G as c, vt as d, P as f, Lt as g, Tt as h, J as i, Zt as l, wt as m, et as n, Z as o, W as p, Et as r, Qt as s, Rt as t, z as u, un as v, Q as w, V as x, tt as y };
1762
+ export { q as C, B as S, Y as T, jt as _, sn as a, yt as b, G as c, vt as d, $e as f, Lt as g, Tt as h, J as i, Zt as l, wt as m, et as n, Z as o, W as p, Et as r, Qt as s, Rt as t, z as u, un as v, Q as w, V as x, tt as y };