@voltro/cli 0.51.0 → 0.52.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 (164) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/dist/{agentsMd-D6yD7IQv.js → agentsMd-0l980yhL.js} +4 -1
  3. package/dist/agentsMd-SDDSkyl4.js +2 -0
  4. package/dist/apiBuild-BYBpL7Pz.js +2 -0
  5. package/dist/{apiBuild-CPDHXF72.js → apiBuild-CSFI8QGq.js} +3 -3
  6. package/dist/bin.js +1 -1
  7. package/dist/build-CPgcMQug.js +793 -0
  8. package/dist/checkCommand-2SbqzukH.js +2 -0
  9. package/dist/{checkCommand-DNuPiWMc.js → checkCommand-COmqc2cB.js} +92 -46
  10. package/dist/{cloudCmd-F4YJeqM3.js → cloudCmd-QUXh-b5w.js} +1 -1
  11. package/dist/codegen-CctkDO-1.js +2 -0
  12. package/dist/{codegen-CrMXs4hb.js → codegen-VF479Cnb.js} +1 -1
  13. package/dist/{codegenCommand-BNBHcNNj.js → codegenCommand-DCdG2JN-.js} +12 -12
  14. package/dist/{codemodRunner-BDVixlSw.js → codemodRunner-r7J9lIa7.js} +588 -540
  15. package/dist/{commands-B1OiS9bX.js → commands-Cc_nV8WI.js} +35 -35
  16. package/dist/{dashboardCommand-C-vvPY1B.js → dashboardCommand-C-gKvwqh.js} +5 -5
  17. package/dist/{dataCommand-C1GxXW5q.js → dataCommand-BgpBHnlB.js} +27 -27
  18. package/dist/dbCommand-DNb6yeOG.js +2 -0
  19. package/dist/{dbCommand-If4Y1xQ-.js → dbCommand-sHedr-NJ.js} +2 -2
  20. package/dist/dev--A3nsxA3.js +3 -0
  21. package/dist/{dev-kdAg9Q7l.js → dev-CRHoCEiy.js} +2142 -2103
  22. package/dist/doctorCommand-CqoWA2p5.js +2 -0
  23. package/dist/{doctorCommand-nKmeW78u.js → doctorCommand-DtfJ3FA6.js} +314 -234
  24. package/dist/{dormancyCommand-CY3wa_SW.js → dormancyCommand-Drn7o0No.js} +1 -1
  25. package/dist/{embeddingsCommand-BDLIgje_.js → embeddingsCommand-Z-jO1fWN.js} +1 -1
  26. package/dist/{envCommand-C6V_xVlT.js → envCommand-D4gCrrTZ.js} +8 -8
  27. package/dist/{evolveCommand-D3c4DSfN.js → evolveCommand-CMROeKeA.js} +2 -2
  28. package/dist/fileConventions-DOqD3lPS.js +34 -0
  29. package/dist/{fileTaxonomy-CJfgOllU.js → fileTaxonomy-DvDUV9wq.js} +1 -1
  30. package/dist/frameworkTableAssembly-C6ETawPR.js +2 -0
  31. package/dist/{frameworkTableAssembly-BwJVEKLr.js → frameworkTableAssembly-w-XnLa3q.js} +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/{infoCommand-BnRFEF1o.js → infoCommand-DXM868o_.js} +1 -1
  34. package/dist/inspectMetrics-CGF94puw.js +143 -0
  35. package/dist/{metaCommands-CfRLra0s.js → metaCommands-C6RFmF1r.js} +2 -2
  36. package/dist/{migrate-DehuBakM.js → migrate-D0F-eTlK.js} +2 -2
  37. package/dist/{pageConvention-cEiRxdab.js → pageConvention-CzUiSbtU.js} +1 -1
  38. package/dist/{privacyCommand-XejDMvmu.js → privacyCommand-DGdopOI6.js} +1 -1
  39. package/dist/{projectScaffold-mIX_DpSe.js → projectScaffold-B4dmTlwT.js} +1 -1
  40. package/dist/{projectScaffold-BIl97_E6.js → projectScaffold-EzlErR4E.js} +1 -1
  41. package/dist/{renderModeScan-D7J1B7Kw.js → renderModeScan-CcH2X1_D.js} +25 -10
  42. package/dist/{renderProfile-1OWWAAtx.js → renderProfile-Ck32Fzxr.js} +2 -2
  43. package/dist/{runtimeTrace-ZsBU7Tkx.js → runtimeTrace-BPQyCmC5.js} +1 -1
  44. package/dist/{sdkgen-O4XqWOjM.js → sdkgen-Se88ifTd.js} +1 -1
  45. package/dist/serveCommand-DLc-BznW.js +2 -0
  46. package/dist/{serveCommand-DdiYNBBu.js → serveCommand-DkP3OT0W.js} +885 -868
  47. package/dist/serveEntry.js +1 -1
  48. package/dist/start-DfL3fOiN.js +3 -0
  49. package/dist/start-jw89Xbqy.js +1339 -0
  50. package/dist/startEntry.js +1 -1
  51. package/dist/{staticCommand-Dr2M6tpU.js → staticCommand-BwNEDlSU.js} +1 -1
  52. package/dist/{test-rXFq4S76.js → test-f3amja6a.js} +1 -1
  53. package/dist/updateCommand-5gFVfK5q.js +2 -0
  54. package/dist/{updateCommand-Bs322Q78.js → updateCommand-BMk2e4ky.js} +1 -1
  55. package/dist/{webDev-B-ubQEMX.js → webDev-BgWL9gKV.js} +1156 -835
  56. package/dist/webDev-CZbTsDcH.js +2 -0
  57. package/dist/{webhooksCommand-FLYY9IXh.js → webhooksCommand-CoIO3jbj.js} +1 -1
  58. package/package.json +30 -17
  59. package/templates/AGENTS.core.md +11 -0
  60. package/templates/AGENTS.md +15 -4
  61. package/templates/agent-docs/_index.md +4 -4
  62. package/templates/agent-docs/_manifest.json +11 -11
  63. package/templates/agent-docs/cli.md +96 -14
  64. package/templates/agent-docs/data.md +210 -7
  65. package/templates/agent-docs/database/schema.md +1 -1
  66. package/templates/agent-docs/database/seedsdialects.md +1 -1
  67. package/templates/agent-docs/deployment.md +22 -3
  68. package/templates/agent-docs/introduction.md +46 -0
  69. package/templates/agent-docs/local-first-mobile.md +34 -7
  70. package/templates/agent-docs/plugins/ai-flows.md +1 -1
  71. package/templates/agent-docs/plugins/audit.md +5 -5
  72. package/templates/agent-docs/plugins/cdc-out.md +2 -2
  73. package/templates/agent-docs/plugins/{versioning.md → row-history.md} +22 -22
  74. package/templates/agent-docs/plugins/storage.md +2 -2
  75. package/templates/agent-docs/plugins.md +29 -7
  76. package/templates/agent-docs/reference.md +39 -2
  77. package/templates/agent-docs/routing.md +341 -47
  78. package/templates/agent-docs/schema-driven-ui.md +78 -2
  79. package/templates/agent-docs/security.md +125 -8
  80. package/templates/agent-docs/templates/apibackends.md +14 -14
  81. package/templates/agent-docs/templates/overview.md +1 -1
  82. package/templates/agent-docs/whats-new.md +76 -53
  83. package/templates/apps/api-ai/package.json +6 -7
  84. package/templates/apps/api-ai/tests/summarize.test.ts +1 -1
  85. package/templates/apps/api-auth/package.json +8 -8
  86. package/templates/apps/api-backend/package.json +7 -7
  87. package/templates/apps/api-backend-deactivation/package.json +7 -7
  88. package/templates/apps/api-backend-mail/package.json +8 -8
  89. package/templates/apps/api-backend-mariadb/package.json +9 -9
  90. package/templates/apps/api-backend-sqlite/package.json +8 -8
  91. package/templates/apps/api-backend-storage/package.json +8 -8
  92. package/templates/apps/api-cms/package.json +9 -10
  93. package/templates/apps/api-collab/package.json +8 -8
  94. package/templates/apps/api-data-advanced/package.json +8 -8
  95. package/templates/apps/api-durable/package.json +8 -8
  96. package/templates/apps/api-feature-flags/package.json +9 -9
  97. package/templates/apps/api-governance/package.json +8 -8
  98. package/templates/apps/api-kv/package.json +8 -8
  99. package/templates/apps/api-moderation/package.json +8 -8
  100. package/templates/apps/api-observability/package.json +8 -8
  101. package/templates/apps/api-ratelimit/package.json +8 -8
  102. package/templates/apps/api-rbac/package.json +8 -8
  103. package/templates/apps/api-rest/package.json +7 -7
  104. package/templates/apps/{api-versioning → api-row-history}/README.md +3 -3
  105. package/templates/apps/{api-versioning → api-row-history}/actions/documents.asOf.action.server.ts +1 -1
  106. package/templates/apps/{api-versioning → api-row-history}/actions/documents.history.action.server.ts +1 -1
  107. package/templates/apps/{api-versioning → api-row-history}/app.config.ts +3 -3
  108. package/templates/apps/{api-versioning → api-row-history}/database/schema.ts +1 -1
  109. package/templates/apps/{api-versioning → api-row-history}/mutations/documents.create.mutation.server.ts +1 -1
  110. package/templates/apps/{api-versioning → api-row-history}/mutations/documents.create.mutation.ts +1 -1
  111. package/templates/apps/{api-versioning → api-row-history}/mutations/documents.update.mutation.ts +1 -1
  112. package/templates/apps/{api-versioning → api-row-history}/package.json +8 -8
  113. package/templates/apps/api-row-history/template.json +6 -0
  114. package/templates/apps/{api-versioning → api-row-history}/tests/documents.create.test.ts +1 -1
  115. package/templates/apps/api-saas/app.config.ts +1 -0
  116. package/templates/apps/api-saas/package.json +10 -11
  117. package/templates/apps/api-saas-starter/package.json +10 -10
  118. package/templates/apps/api-search/package.json +8 -8
  119. package/templates/apps/api-status/package.json +8 -8
  120. package/templates/apps/api-webhooks/package.json +9 -9
  121. package/templates/apps/changelog/package.json +6 -6
  122. package/templates/apps/edge-functions/package.json +2 -2
  123. package/templates/apps/frontend-admin/package.json +8 -8
  124. package/templates/apps/frontend-app/package.json +9 -9
  125. package/templates/apps/frontend-auth/package.json +8 -8
  126. package/templates/apps/frontend-blank/package.json +7 -7
  127. package/templates/apps/frontend-cms/package.json +9 -9
  128. package/templates/apps/frontend-collab/package.json +10 -10
  129. package/templates/apps/frontend-collab/src/pages/page.test.tsx +10 -9
  130. package/templates/apps/frontend-collab/src/pages/page.tsx +42 -65
  131. package/templates/apps/frontend-contact/package.json +7 -7
  132. package/templates/apps/frontend-dashboard/package.json +7 -7
  133. package/templates/apps/frontend-docs/package.json +6 -7
  134. package/templates/apps/frontend-i18n/package.json +6 -6
  135. package/templates/apps/frontend-landing/package.json +6 -7
  136. package/templates/apps/frontend-portal/package.json +8 -8
  137. package/templates/apps/frontend-saas/package.json +8 -8
  138. package/templates/apps/frontend-spa/package.json +7 -7
  139. package/templates/apps/frontend-ssr/package.json +7 -7
  140. package/templates/apps/frontend-ssr-api/package.json +8 -8
  141. package/templates/apps/frontend-static-blog/package.json +6 -6
  142. package/templates/apps/frontend-status/package.json +8 -8
  143. package/templates/apps/mobile-app/package.json +4 -4
  144. package/dist/agentsMd-Bu_XQgVf.js +0 -2
  145. package/dist/apiBuild-GDKuGOMV.js +0 -2
  146. package/dist/build-DETLZAFt.js +0 -752
  147. package/dist/checkCommand-CWcnDArJ.js +0 -2
  148. package/dist/codegen-DiMn2KkZ.js +0 -2
  149. package/dist/dbCommand-C27HIsGE.js +0 -2
  150. package/dist/dev-CK522MV5.js +0 -3
  151. package/dist/doctorCommand-BK4l18eG.js +0 -2
  152. package/dist/fileConventions-Cof68_BL.js +0 -33
  153. package/dist/frameworkTableAssembly-CVDB2hCq.js +0 -2
  154. package/dist/inspectMetrics-CfdKLh6t.js +0 -72
  155. package/dist/serveCommand-BRnPCxVd.js +0 -2
  156. package/dist/start-BLNmWkLa.js +0 -1154
  157. package/dist/start-Dzicuyw8.js +0 -3
  158. package/dist/updateCommand-eXB35SEv.js +0 -2
  159. package/dist/webDev-DposiF3j.js +0 -2
  160. package/templates/apps/api-versioning/template.json +0 -6
  161. /package/templates/apps/{api-versioning → api-row-history}/actions/documents.asOf.action.ts +0 -0
  162. /package/templates/apps/{api-versioning → api-row-history}/actions/documents.history.action.ts +0 -0
  163. /package/templates/apps/{api-versioning → api-row-history}/mutations/documents.update.mutation.server.ts +0 -0
  164. /package/templates/apps/{api-versioning → api-row-history}/tsconfig.json +0 -0
@@ -1,11 +1,11 @@
1
- import { Ht as e, Ut as t, Vt as n } from "./dev-kdAg9Q7l.js";
2
- import { at as r, ct as i, ot as a, s as o, st as s, t as c, w as l, y as u } from "./webDev-B-ubQEMX.js";
3
- import { x as d } from "./frameworkTableAssembly-BwJVEKLr.js";
4
- import { n as f } from "./pageConvention-cEiRxdab.js";
5
- import { r as p, u as m } from "./apiBuild-CPDHXF72.js";
1
+ import { Ht as e, Ut as t, Wt as n } from "./dev-CRHoCEiy.js";
2
+ import { D as r, _t as i, b as a, c as o, gt as s, t as c, vt as l, yt as u } from "./webDev-BgWL9gKV.js";
3
+ import { x as d } from "./frameworkTableAssembly-w-XnLa3q.js";
4
+ import { n as f } from "./pageConvention-CzUiSbtU.js";
5
+ import { r as p, u as m } from "./apiBuild-CSFI8QGq.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";
8
- import { n as ne, r as re } from "./fileTaxonomy-CJfgOllU.js";
8
+ import { n as ne, r as re } from "./fileTaxonomy-DvDUV9wq.js";
9
9
  import { basename as ie, dirname as ae, join as v, relative as y, resolve as b, sep as x } from "node:path";
10
10
  import { existsSync as S, readFileSync as C, readdirSync as w, statSync as T, writeFileSync as oe } from "node:fs";
11
11
  import { pathToFileURL as se } from "node:url";
@@ -367,14 +367,84 @@ 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"), j = {
371
+ "@voltro/cli": "provides the `voltro` binary; consumed by scripts, never imported",
372
+ "@voltro/devtools": "mounted by `voltro dev` itself",
373
+ "@voltro/sql-postgres": "dialect driver, loaded by the framework from config",
374
+ "@voltro/sql-mysql": "dialect driver, loaded by the framework from config",
375
+ "@voltro/sql-sqlite": "dialect driver, loaded by the framework from config",
376
+ "@voltro/sql-mssql": "dialect driver, loaded by the framework from config"
377
+ }, M = /* @__PURE__ */ new Set(["@voltro/client", "@voltro/web"]), Pe = /(^|\/)(node_modules|dist|build|\.framework|\.next|\.turbo|\.git|coverage|\.publish)(\/|$)/, Fe = /\.([cm]?[jt]sx?)$/, Ie = /\.generated\.[cm]?[jt]sx?$/, Le = (e, t = [], n = 0) => {
378
+ if (n > 12 || Pe.test(e)) return t;
379
+ let r;
380
+ try {
381
+ r = w(e);
382
+ } catch {
383
+ return t;
384
+ }
385
+ for (let i of r) {
386
+ let r = v(e, i), a;
387
+ try {
388
+ a = T(r);
389
+ } catch {
390
+ continue;
391
+ }
392
+ a.isDirectory() ? Le(r, t, n + 1) : Fe.test(i) && t.push(r);
393
+ }
394
+ return t;
395
+ }, Re = /(?:\bfrom\s*|\bimport\s+|\bimport\s*\(\s*|\brequire\s*\(\s*)['"](@voltro\/[a-z0-9-]+)(?:\/[^'"]*)?['"]/g, ze = (e) => e.split("\n").filter((e) => !/^\s*(\/\/|\*|\/\*)/.test(e)).join("\n"), Be = (e) => {
396
+ let t;
397
+ try {
398
+ t = JSON.parse(C(v(e, "package.json"), "utf8"));
399
+ } catch {
400
+ return null;
401
+ }
402
+ let n = (e) => Object.keys(t[e] ?? {}).filter((e) => e.startsWith("@voltro/")).map((t) => ({
403
+ pkg: t,
404
+ declaredIn: e
405
+ })), r = /* @__PURE__ */ new Set(), i = [...n("dependencies"), ...n("devDependencies")].filter((e) => !r.has(e.pkg) && (r.add(e.pkg), !0)), a = Le(e), o = a.some((e) => Ie.test(e)), s = /* @__PURE__ */ new Set();
406
+ for (let e of a) {
407
+ let t;
408
+ try {
409
+ t = C(e, "utf8");
410
+ } catch {
411
+ continue;
412
+ }
413
+ for (let e of ze(t).matchAll(Re)) s.add(e[1]);
414
+ }
415
+ let c = i.filter((e) => !(e.pkg in j) && !s.has(e.pkg));
416
+ return {
417
+ declared: i.map((e) => e.pkg).sort(),
418
+ unimported: c.filter((e) => o || !M.has(e.pkg)).sort((e, t) => e.pkg.localeCompare(t.pkg)),
419
+ pendingCodegen: o ? [] : c.map((e) => e.pkg).filter((e) => M.has(e)).sort(),
420
+ exempt: i.map((e) => e.pkg).filter((e) => e in j).sort(),
421
+ scannedFiles: a.length
422
+ };
423
+ }, Ve = (e) => {
424
+ if (e === null) return ["unimported deps · no package.json here — could not check"];
425
+ let t = `unimported deps · ${e.declared.length} @voltro/* package(s) declared, ${e.scannedFiles} file(s) scanned`;
426
+ if (e.declared.length === 0) return [t, " no @voltro/* dependencies declared — nothing to check"];
427
+ let n = e.exempt.length > 0 ? ` (${e.exempt.length} loaded by the framework itself: ${e.exempt.join(", ")})` : "", r = e.pendingCodegen.length > 0 ? [
428
+ ` · ${e.pendingCodegen.length} not measurable yet (${e.pendingCodegen.join(", ")}) — their import`,
429
+ " site lives in generated code, and codegen has not run here. Run `voltro dev`",
430
+ " once, then re-check."
431
+ ] : [];
432
+ if (e.unimported.length === 0) return [
433
+ t,
434
+ ` ✓ all ${e.declared.length - e.exempt.length - e.pendingCodegen.length} measurable package(s) have an import site${n}`,
435
+ ...r
436
+ ];
437
+ let i = [t];
438
+ for (let t of e.unimported) i.push(` ⚠ ${t.pkg} — declared in ${t.declaredIn}, imported nowhere`);
439
+ return i.push(" a dependency nobody imports still gets installed, updated, and read as"), i.push(" evidence the package is in use — its breaking-change notes included."), i.push(" Remove it, or if it IS imported through an assembled specifier the scan"), i.push(" cannot see, keep it and ignore this line — the rule is advisory."), i.push(...r), n !== "" && i.push(` ·${n.slice(1)}`), i;
440
+ }, He = (e) => {
371
441
  if (e.catalogCodes.length === 0) return;
372
442
  let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
373
443
  if (n.length !== 0) return {
374
444
  orphaned: n,
375
445
  noneDeclared: t.size === 0
376
446
  };
377
- }, Fe = (e, t) => {
447
+ }, Ue = (e, t) => {
378
448
  let n = e.orphaned.join(", ");
379
449
  return e.noneDeclared ? [
380
450
  `⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
@@ -386,7 +456,7 @@ var le = /* @__PURE__ */ new Set([
386
456
  " …or delete the files. A catalog that is never loaded looks from the inside",
387
457
  " exactly like one that works, which is why this is worth a line."
388
458
  ] : [`⚠ ${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
- }, Ie = (e) => {
459
+ }, We = (e) => {
390
460
  let t = e.getProperty("match");
391
461
  if (t === void 0) return null;
392
462
  let n = (t.asKind(O.PropertyAssignment)?.getInitializer())?.asKind(O.ObjectLiteralExpression);
@@ -423,15 +493,15 @@ var le = /* @__PURE__ */ new Set([
423
493
  else return;
424
494
  }
425
495
  return r;
426
- }, Le = (e, t) => {
496
+ }, Ge = (e, t) => {
427
497
  let n = ["middleware.ts", "middleware.tsx"].find((t) => S(v(e, t)));
428
498
  if (n === void 0) return;
429
- let r = new D({
499
+ let i = new D({
430
500
  useInMemoryFileSystem: !0,
431
501
  skipAddingFilesFromTsConfig: !0
432
- }), i;
502
+ }), a;
433
503
  try {
434
- i = r.createSourceFile(n, C(v(e, n), "utf8"));
504
+ a = i.createSourceFile(n, C(v(e, n), "utf8"));
435
505
  } catch {
436
506
  return {
437
507
  problems: [],
@@ -440,22 +510,22 @@ var le = /* @__PURE__ */ new Set([
440
510
  file: n
441
511
  };
442
512
  }
443
- let a = [], o = [], s = 0;
444
- for (let e of i.getVariableDeclarations()) {
513
+ let o = [], s = [], c = 0;
514
+ for (let e of a.getVariableDeclarations()) {
445
515
  let t = e.getInitializerIfKind(O.CallExpression);
446
516
  if (t?.getExpression().getText() !== "defineMiddleware") continue;
447
- s += 1;
517
+ c += 1;
448
518
  let n = e.getName(), r = t.getArguments()[0]?.asKind(O.ObjectLiteralExpression);
449
519
  if (r === void 0) {
450
- o.push(n);
520
+ s.push(n);
451
521
  continue;
452
522
  }
453
- let i = Ie(r);
523
+ let i = We(r);
454
524
  if (i === void 0) {
455
- o.push(n);
525
+ s.push(n);
456
526
  continue;
457
527
  }
458
- a.push({
528
+ o.push({
459
529
  exportName: n,
460
530
  middleware: {
461
531
  _tag: "VoltroMiddleware",
@@ -465,45 +535,45 @@ var le = /* @__PURE__ */ new Set([
465
535
  });
466
536
  }
467
537
  return {
468
- problems: l(a, t).map((e) => e.message),
469
- unreadable: o,
470
- seen: s,
538
+ problems: r(o, t).map((e) => e.message),
539
+ unreadable: s,
540
+ seen: c,
471
541
  file: n
472
542
  };
473
- }, Re = (e) => {
543
+ }, Ke = (e) => {
474
544
  let t = [];
475
545
  if (e.problems.length > 0) {
476
546
  t.push(`✗ ${e.file}: ${e.problems.length} matcher problem(s) — this REFUSES the boot`);
477
547
  for (let n of e.problems) t.push(` • ${n}`);
478
548
  }
479
549
  return e.unreadable.length > 0 && t.push(`… ${e.file}: ${e.unreadable.length} matcher(s) not statically readable (${e.unreadable.join(", ")}) — doctor did NOT check them. The boot still does.`), e.seen === 0 && t.push(`… ${e.file}: no \`defineMiddleware(...)\` found — doctor checked nothing here. A middleware assigned through a variable or re-exported from another module is invisible to this rule.`), t.length === 0 && e.seen > 0 && t.push(`✓ ${e.file}: ${e.seen} middleware(s), matchers resolve and do not overlap`), t;
480
- }, ze = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), Be = /* @__PURE__ */ new Set([
550
+ }, qe = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), Je = /* @__PURE__ */ new Set([
481
551
  "requireScope",
482
552
  "requireAnyScope",
483
553
  "requireAuth",
484
554
  "assertScope",
485
555
  "assertCan",
486
556
  "can"
487
- ]), Ve = (e) => {
557
+ ]), Ye = (e) => {
488
558
  let t = /* @__PURE__ */ new Set();
489
- for (let n of e) for (let e of ze(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
559
+ for (let n of e) for (let e of qe(n).matchAll(/\bexport\s+(?:const|function|async\s+function)\s+((?:require|assert)[A-Z]\w*)/g)) t.add(e[1]);
490
560
  return t;
491
- }, He = (e, t) => {
561
+ }, Xe = (e, t) => {
492
562
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
493
- }, Ue = (e) => {
563
+ }, Ze = (e) => {
494
564
  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);
495
565
  return t && n;
496
- }, We = (e, t, n) => {
566
+ }, Qe = (e, t, n) => {
497
567
  if (e.hasDeclaredGuards) return { kind: "declared-guard" };
498
- let r = ze(e.src), i = He(r, t);
499
- return i === void 0 ? Ue(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
568
+ let r = qe(e.src), i = Xe(r, t);
569
+ return i === void 0 ? Ze(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
500
570
  kind: "vocabulary-guard",
501
571
  name: i
502
572
  };
503
- }, Ge = (e, t, n) => {
573
+ }, $e = (e, t, n) => {
504
574
  let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
505
575
  return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
506
- }, Ke = (e, t, n) => {
576
+ }, et = (e, t, n) => {
507
577
  switch (t.kind) {
508
578
  case "declared-guard": return "guards: on the descriptor";
509
579
  case "vocabulary-guard": return `calls ${t.name}()`;
@@ -511,15 +581,15 @@ var le = /* @__PURE__ */ new Set([
511
581
  case "allowlisted": return "accepted by a previous run";
512
582
  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`;
513
583
  }
514
- }, qe = (e) => {
584
+ }, tt = (e) => {
515
585
  let t = e.executors.map((t) => {
516
- let n = We(t, e.guardVocabulary, e.allowlist);
586
+ let n = Qe(t, e.guardVocabulary, e.allowlist);
517
587
  return {
518
588
  tag: t.tag,
519
589
  path: t.path,
520
590
  verdict: n,
521
- severity: Ge(t, e.tenantScopedTables, e.referencedTables),
522
- why: Ke(t, n, e.rowFilterRegistered === !0)
591
+ severity: $e(t, e.tenantScopedTables, e.referencedTables),
592
+ why: et(t, n, e.rowFilterRegistered === !0)
523
593
  };
524
594
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
525
595
  return {
@@ -533,7 +603,7 @@ var le = /* @__PURE__ */ new Set([
533
603
  },
534
604
  unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
535
605
  };
536
- }, Je = [
606
+ }, nt = [
537
607
  "# voltro doctor — authz allowlist",
538
608
  "#",
539
609
  "# Two KINDS of line, and the difference is the point:",
@@ -558,19 +628,19 @@ var le = /* @__PURE__ */ new Set([
558
628
  "# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
559
629
  "# lines for executors that are newly unchecked.",
560
630
  ""
561
- ], Ye = (e) => {
631
+ ], rt = (e) => {
562
632
  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));
563
633
  return [
564
- ...Je,
634
+ ...nt,
565
635
  ...n,
566
636
  ...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
567
637
  ...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
568
638
  ""
569
639
  ].join("\n");
570
- }, Xe = (e, t) => {
640
+ }, it = (e, t) => {
571
641
  let n = [...new Set(t)].sort();
572
- if (e === void 0) return Ye(n.map((e) => ({ tag: e })));
573
- let r = j(e).tags, i = n.filter((e) => !r.has(e));
642
+ if (e === void 0) return rt(n.map((e) => ({ tag: e })));
643
+ let r = N(e).tags, i = n.filter((e) => !r.has(e));
574
644
  return i.length === 0 ? e : [
575
645
  e.replace(/\n+$/, ""),
576
646
  "",
@@ -579,25 +649,25 @@ var le = /* @__PURE__ */ new Set([
579
649
  ...i,
580
650
  ""
581
651
  ].join("\n");
582
- }, Ze = (e, t) => {
652
+ }, at = (e, t) => {
583
653
  let n = [...new Set(t)];
584
654
  if (e === void 0) return {
585
655
  added: n.length,
586
656
  kept: 0,
587
657
  removed: 0
588
658
  };
589
- let r = j(e).tags;
659
+ let r = N(e).tags;
590
660
  return {
591
661
  added: n.filter((e) => !r.has(e)).length,
592
662
  kept: r.size,
593
663
  removed: 0
594
664
  };
595
- }, Qe = class extends Error {
665
+ }, ot = class extends Error {
596
666
  tag;
597
667
  constructor(e) {
598
668
  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";
599
669
  }
600
- }, j = (e) => {
670
+ }, N = (e) => {
601
671
  let t = [];
602
672
  for (let n of e.split("\n")) {
603
673
  let e = n.trim();
@@ -610,7 +680,7 @@ var le = /* @__PURE__ */ new Set([
610
680
  continue;
611
681
  }
612
682
  let a = r[2].trim();
613
- if (a.length === 0) throw new Qe(i);
683
+ if (a.length === 0) throw new ot(i);
614
684
  t.push({
615
685
  tag: i,
616
686
  reviewed: a
@@ -620,13 +690,13 @@ var le = /* @__PURE__ */ new Set([
620
690
  tags: new Set(t.map((e) => e.tag)),
621
691
  entries: t
622
692
  };
623
- }, $e = (e) => {
693
+ }, st = (e) => {
624
694
  let t = e.indexOf(".");
625
695
  return t <= 0 ? void 0 : e.slice(0, t);
626
- }, et = (e, t) => {
696
+ }, ct = (e, t) => {
627
697
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
628
698
  for (let e of t.rpcTags) {
629
- let t = $e(e);
699
+ let t = st(e);
630
700
  if (t === void 0) continue;
631
701
  let n = i.get(t);
632
702
  n ? n.push(e) : i.set(t, [e]);
@@ -655,7 +725,7 @@ var le = /* @__PURE__ */ new Set([
655
725
  });
656
726
  continue;
657
727
  }
658
- let s = new Set(o.rpcTags.map($e).filter((e) => e !== void 0));
728
+ let s = new Set(o.rpcTags.map(st).filter((e) => e !== void 0));
659
729
  for (let e of s) {
660
730
  let t = i.get(e);
661
731
  t !== void 0 && a.push({
@@ -668,12 +738,12 @@ var le = /* @__PURE__ */ new Set([
668
738
  }
669
739
  }
670
740
  return a;
671
- }, tt = (e, t) => {
741
+ }, lt = (e, t) => {
672
742
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
673
743
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
674
744
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
675
745
  return r;
676
- }, nt = async (e) => {
746
+ }, ut = async (e) => {
677
747
  if (!await e.hasWorkflows().catch(() => !1)) return { kind: "not-applicable" };
678
748
  let t;
679
749
  try {
@@ -697,12 +767,12 @@ var le = /* @__PURE__ */ new Set([
697
767
  } finally {
698
768
  await t.close().catch(() => {});
699
769
  }
700
- }, rt = (e) => {
770
+ }, dt = (e) => {
701
771
  let t = Math.round(e / 6e4);
702
772
  if (t < 90) return `${t}m`;
703
773
  let n = Math.round(t / 60);
704
774
  return n < 48 ? `${n}h` : `${Math.round(n / 24)}d`;
705
- }, it = (e) => {
775
+ }, ft = (e) => {
706
776
  if (e.kind === "not-applicable") return null;
707
777
  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.
708
778
  Run it where the app's DB env vars are set, or ignore this line locally.`;
@@ -710,13 +780,13 @@ var le = /* @__PURE__ */ new Set([
710
780
  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` : ""})`);
711
781
  else {
712
782
  n.push(`✗ stuck runs: ${t.stalled.length} run(s) have made no progress`);
713
- for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${rt(e.idleMs)} ${e.reason}`);
783
+ for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${dt(e.idleMs)} ${e.reason}`);
714
784
  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.");
715
785
  }
716
786
  t.sawFullRunPage && n.push(" (the examined page filled up — more old runs exist than were read this pass)");
717
787
  for (let e of t.failures) n.push(` ! could not examine ${e.subject}: ${e.detail}`);
718
788
  return n.join("\n");
719
- }, at = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), ot = (e) => {
789
+ }, pt = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), mt = (e) => {
720
790
  let t = m(e), n = p(e);
721
791
  return {
722
792
  root: e,
@@ -725,22 +795,22 @@ var le = /* @__PURE__ */ new Set([
725
795
  apiBundle: n,
726
796
  apiBundlePresent: S(n)
727
797
  };
728
- }, st = [
798
+ }, ht = [
729
799
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
730
800
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
731
801
  "(Generated Dockerfiles from `voltro create-project` already do this.)"
732
- ].join("\n "), ct = (e, t = !1) => {
733
- let n = ot(e);
734
- if (console.log(`voltro doctor — serve preflight for ${n.root}`), !at(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
802
+ ].join("\n "), gt = (e, t = !1) => {
803
+ let n = mt(e);
804
+ if (console.log(`voltro doctor — serve preflight for ${n.root}`), !pt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
735
805
  console.log(`${n.serveBundlePresent ? "✓" : "✗"} serve bundle: ${n.serveBundlePresent ? "present" : "MISSING"} (${n.serveBundle})`), console.log(`${n.apiBundlePresent ? "✓" : "•"} api bundle: ${n.apiBundlePresent ? "present" : "absent"} (${n.apiBundle})`);
736
806
  let r = process.env.NODE_ENV === "production";
737
807
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
738
808
  let i = t || r;
739
- return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${st}`), +!!i;
740
- }, lt = (e) => {
809
+ return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${ht}`), +!!i;
810
+ }, _t = (e) => {
741
811
  let t = e.absPath.replace(/\.server\.(tsx?)$/, ".$1");
742
812
  if (t !== e.absPath) return e.sources.get(t) ?? e.sources.get(t.replace(/\.ts$/, ".tsx"));
743
- }, ut = (e, t, n) => {
813
+ }, vt = (e, t, n) => {
744
814
  let r = new Set(e), i = _(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
745
815
  `${e}.ts`,
746
816
  `${e}.tsx`,
@@ -766,7 +836,7 @@ var le = /* @__PURE__ */ new Set([
766
836
  r !== void 0 && o.push(r);
767
837
  }
768
838
  return o;
769
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => ht.test(y(n, e))), l = new Set(c);
839
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => St.test(y(n, e))), l = new Set(c);
770
840
  for (; c.length > 0;) {
771
841
  let e = c.shift();
772
842
  if (!s.has(e)) {
@@ -779,11 +849,11 @@ var le = /* @__PURE__ */ new Set([
779
849
  let d = /* @__PURE__ */ new Set();
780
850
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
781
851
  return d;
782
- }, M = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), dt = (e, t) => (e.match(t) ?? []).length, N = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, ft = (e, t) => {
852
+ }, P = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), F = (e, t) => (e.match(t) ?? []).length, I = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, yt = (e, t) => {
783
853
  if (e.src.includes(t)) return !0;
784
854
  let n = t.replace(/^@voltro\/plugin-/, "");
785
- return n !== t && N(e.file, RegExp(`^${n}Plugin$`)) > 0;
786
- }, pt = (e, t) => {
855
+ return n !== t && I(e.file, RegExp(`^${n}Plugin$`)) > 0;
856
+ }, bt = (e, t) => {
787
857
  let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
788
858
  for (; (i = r.exec(e)) !== null;) {
789
859
  let t = i.index + i[0].length, r = 1, a = t;
@@ -791,10 +861,10 @@ var le = /* @__PURE__ */ new Set([
791
861
  n.push(e.slice(t, a - 1));
792
862
  }
793
863
  return n;
794
- }, mt = (e, t) => {
864
+ }, xt = (e, t) => {
795
865
  let n = t.getSymbol()?.getDeclarations()?.[0];
796
866
  return n === void 0 ? [] : e.getDescendantsOfKind(O.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
797
- }, ht = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, gt = /\.(query|mutation|action|stream)\.server\.tsx?$/, _t = [
867
+ }, St = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, Ct = /\.(query|mutation|action|stream)\.server\.tsx?$/, wt = [
798
868
  "Id",
799
869
  "ID",
800
870
  "Hash",
@@ -820,7 +890,7 @@ var le = /* @__PURE__ */ new Set([
820
890
  "Comment",
821
891
  "Provider",
822
892
  "Format"
823
- ], vt = RegExp(`(?:${_t.join("|")})$|_(?:${_t.map((e) => e.toLowerCase()).join("|")})$`), yt = [
893
+ ], Tt = RegExp(`(?:${wt.join("|")})$|_(?:${wt.map((e) => e.toLowerCase()).join("|")})$`), Et = [
824
894
  {
825
895
  id: "hardcoded-framework-cookie",
826
896
  scope: "client",
@@ -833,7 +903,7 @@ var le = /* @__PURE__ */ new Set([
833
903
  scope: "server",
834
904
  smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
835
905
  use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
836
- match: ({ src: e, path: t }) => gt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
906
+ match: ({ src: e, path: t }) => Ct.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
837
907
  },
838
908
  {
839
909
  id: "subject-write-no-guard",
@@ -842,12 +912,12 @@ var le = /* @__PURE__ */ new Set([
842
912
  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",
843
913
  match: (e) => {
844
914
  let { src: t, path: n } = e;
845
- if (!gt.test(n)) return !1;
846
- let r = M(t);
915
+ if (!Ct.test(n)) return !1;
916
+ let r = P(t);
847
917
  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;
848
- let i = lt(e);
918
+ let i = _t(e);
849
919
  if (i === void 0) return !1;
850
- let a = M(i);
920
+ let a = P(i);
851
921
  return /\binternal\s*:\s*true\b/.test(a) || /\bguards\s*:\s*\[/.test(a) || /\.guard\s*\(/.test(a) ? !1 : /\bopenAccess\s*:/.test(a);
852
922
  }
853
923
  },
@@ -856,14 +926,14 @@ var le = /* @__PURE__ */ new Set([
856
926
  scope: "server",
857
927
  smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
858
928
  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",
859
- match: ({ file: e, path: t, serverOnly: n }) => (ht.test(t) || n) && N(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
929
+ match: ({ file: e, path: t, serverOnly: n }) => (St.test(t) || n) && I(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
860
930
  },
861
931
  {
862
932
  id: "form",
863
933
  scope: "client",
864
934
  smell: "per-field useState + a submit flag (hand-rolled form)",
865
935
  use: "useFormBinding — fields + validation from the mutation input Schema; server ValidationError({ field }) routes to the field",
866
- match: ({ src: e, file: t }) => N(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
936
+ match: ({ src: e, file: t }) => I(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
867
937
  },
868
938
  {
869
939
  id: "table",
@@ -891,7 +961,7 @@ var le = /* @__PURE__ */ new Set([
891
961
  scope: "client",
892
962
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
893
963
  use: "useDerived — dependency-tracked, referentially stable",
894
- match: ({ src: e }) => dt(e, /\buseSubscription\s*[(<]/g) >= 3 && dt(e, /\buseMemo\s*\(/g) >= 3
964
+ match: ({ src: e }) => F(e, /\buseSubscription\s*[(<]/g) >= 3 && F(e, /\buseMemo\s*\(/g) >= 3
895
965
  },
896
966
  {
897
967
  id: "next-shim",
@@ -900,6 +970,13 @@ var le = /* @__PURE__ */ new Set([
900
970
  use: "the native @voltro/web exports (useSearchParams, redirect, notFound, useServerRequest, …)",
901
971
  match: ({ src: e }) => /from\s+['"][^'"]*next-(compat|shims)/.test(e)
902
972
  },
973
+ {
974
+ id: "untyped-search-params",
975
+ scope: "client",
976
+ smell: "a page exporting a `searchParams` schema but reading the query untyped (zero-arg useSearchParams)",
977
+ use: "useSearchParams(searchParams) — the schema this page already declares, decoded with defaults applied",
978
+ match: (e) => /^export const searchParams\b/m.test(e.src) && e.file.getDescendantsOfKind(O.CallExpression).some((e) => e.getExpression().getText() === "useSearchParams" && e.getArguments().length === 0)
979
+ },
903
980
  {
904
981
  id: "presence",
905
982
  scope: "client",
@@ -916,7 +993,7 @@ var le = /* @__PURE__ */ new Set([
916
993
  match: ({ file: e }) => e.getDescendantsOfKind(O.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
917
994
  let t = e.getFirstAncestorByKind(O.VariableDeclaration)?.getNameNode();
918
995
  return t === void 0 ? [] : E.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
919
- }).some((t) => mt(e, t).some((e) => {
996
+ }).some((t) => xt(e, t).some((e) => {
920
997
  let t = e.getParent();
921
998
  if (E.isBinaryExpression(t)) {
922
999
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -944,7 +1021,7 @@ var le = /* @__PURE__ */ new Set([
944
1021
  scope: "server",
945
1022
  smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
946
1023
  use: "reads are a parent→child walk on ONE key → relations() in a *.relations.ts + .with({ … }) (ONE JSON-aggregate query, every dialect); reads collect ids from SEVERAL sources → keep the assembly and run the independent leading reads under Effect.all (same queries, same results, only concurrent — zero parity risk)",
947
- match: (e) => N(e.file, /\bstore\.query$/) >= 3 || N(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
1024
+ match: (e) => I(e.file, /\bstore\.query$/) >= 3 || I(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
948
1025
  },
949
1026
  {
950
1027
  id: "store-effect-lift",
@@ -967,7 +1044,7 @@ var le = /* @__PURE__ */ new Set([
967
1044
  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)",
968
1045
  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) => {
969
1046
  let n = t[1];
970
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || vt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
1047
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Tt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
971
1048
  }))
972
1049
  },
973
1050
  {
@@ -977,7 +1054,7 @@ var le = /* @__PURE__ */ new Set([
977
1054
  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",
978
1055
  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) => {
979
1056
  let n = t[1];
980
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || vt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
1057
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || Tt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
981
1058
  }))
982
1059
  },
983
1060
  {
@@ -1041,9 +1118,9 @@ var le = /* @__PURE__ */ new Set([
1041
1118
  scope: "server",
1042
1119
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
1043
1120
  use: "wire the filter into at least one test (`makeTestContext({ subject, store, rowFilter })`) and sabotage the predicate once to watch it go red — an untested row filter is a visibility rule nothing verifies",
1044
- match: ({ file: e, testSources: t }) => N(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1045
- let t = M(e);
1046
- return pt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1121
+ match: ({ file: e, testSources: t }) => I(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1122
+ let t = P(e);
1123
+ return bt(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1047
1124
  })
1048
1125
  },
1049
1126
  {
@@ -1053,15 +1130,15 @@ var le = /* @__PURE__ */ new Set([
1053
1130
  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",
1054
1131
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1055
1132
  }
1056
- ], bt = (e) => F(e), xt = (e, t) => {
1133
+ ], Dt = (e) => R(e), Ot = (e, t) => {
1057
1134
  let n = /* @__PURE__ */ new Set();
1058
1135
  for (let r of t) {
1059
1136
  let t = y(e, r), i = t.split(/[\\/]/)[0];
1060
1137
  n.add(i !== void 0 && i !== t ? i : ".");
1061
1138
  }
1062
1139
  return [...n].sort();
1063
- }, St = /\.(tsx?|jsx?)$/, P = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, Ct = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, wt = (e, t = [], n = 0) => {
1064
- if (n > 12 || P.test(e)) return t;
1140
+ }, kt = /\.(tsx?|jsx?)$/, L = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, At = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, jt = (e, t = [], n = 0) => {
1141
+ if (n > 12 || L.test(e)) return t;
1065
1142
  let r;
1066
1143
  try {
1067
1144
  r = w(e, { withFileTypes: !0 });
@@ -1072,12 +1149,12 @@ var le = /* @__PURE__ */ new Set([
1072
1149
  let r = v(e, i.name);
1073
1150
  if (i.isDirectory()) {
1074
1151
  if (i.name.startsWith(".")) continue;
1075
- wt(r, t, n + 1);
1152
+ jt(r, t, n + 1);
1076
1153
  } else je.test(i.name) && t.push(r);
1077
1154
  }
1078
1155
  return t;
1079
- }, F = (e, t = [], n = 0) => {
1080
- if (n > 12 || P.test(e)) return t;
1156
+ }, R = (e, t = [], n = 0) => {
1157
+ if (n > 12 || L.test(e)) return t;
1081
1158
  let r;
1082
1159
  try {
1083
1160
  r = w(e);
@@ -1091,11 +1168,11 @@ var le = /* @__PURE__ */ new Set([
1091
1168
  } catch {
1092
1169
  continue;
1093
1170
  }
1094
- a.isDirectory() ? F(r, t, n + 1) : St.test(i) && !/\.(test|spec)\./.test(i) && !Ct.test(i) && t.push(r);
1171
+ a.isDirectory() ? R(r, t, n + 1) : kt.test(i) && !/\.(test|spec)\./.test(i) && !At.test(i) && t.push(r);
1095
1172
  }
1096
1173
  return t;
1097
- }, I = (e, t = [], n = 0) => {
1098
- if (n > 12 || P.test(e)) return t;
1174
+ }, Mt = (e, t = [], n = 0) => {
1175
+ if (n > 12 || L.test(e)) return t;
1099
1176
  let r;
1100
1177
  try {
1101
1178
  r = w(e);
@@ -1109,17 +1186,17 @@ var le = /* @__PURE__ */ new Set([
1109
1186
  } catch {
1110
1187
  continue;
1111
1188
  }
1112
- a.isDirectory() ? I(r, t, n + 1) : St.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1189
+ a.isDirectory() ? Mt(r, t, n + 1) : kt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1113
1190
  }
1114
1191
  return t;
1115
- }, L = (e) => {
1116
- let t = bt(e);
1192
+ }, Nt = (e) => {
1193
+ let t = Dt(e);
1117
1194
  return {
1118
- dirs: xt(e, t),
1195
+ dirs: Ot(e, t),
1119
1196
  fileCount: t.length
1120
1197
  };
1121
- }, Tt = (e) => {
1122
- let t = bt(e);
1198
+ }, Pt = (e) => {
1199
+ let t = Dt(e);
1123
1200
  if (t.length === 0) return [];
1124
1201
  let n = new D({
1125
1202
  useInMemoryFileSystem: !0,
@@ -1128,8 +1205,8 @@ var le = /* @__PURE__ */ new Set([
1128
1205
  for (let e of t) try {
1129
1206
  i.set(e, C(e, "utf8"));
1130
1207
  } catch {}
1131
- let a = ut([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1132
- for (let t of I(e)) try {
1208
+ let a = vt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1209
+ for (let t of Mt(e)) try {
1133
1210
  o.set(t, C(t, "utf8"));
1134
1211
  } catch {}
1135
1212
  for (let s of t) {
@@ -1149,23 +1226,23 @@ var le = /* @__PURE__ */ new Set([
1149
1226
  } catch {
1150
1227
  continue;
1151
1228
  }
1152
- for (let t of yt) {
1153
- if (t.plugin !== void 0 && ft(c, t.plugin) || !t.match(c)) continue;
1229
+ for (let t of Et) {
1230
+ if (t.plugin !== void 0 && yt(c, t.plugin) || !t.match(c)) continue;
1154
1231
  let n = r.get(t.id) ?? [];
1155
1232
  n.push(y(e, s)), r.set(t.id, n);
1156
1233
  }
1157
1234
  }
1158
- return yt.filter((e) => r.has(e.id)).map((e) => ({
1235
+ return Et.filter((e) => r.has(e.id)).map((e) => ({
1159
1236
  rule: e,
1160
1237
  files: r.get(e.id)
1161
1238
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1162
- }, R = (e) => {
1163
- let t = L(e);
1239
+ }, z = (e) => {
1240
+ let t = Nt(e);
1164
1241
  return {
1165
1242
  root: e,
1166
1243
  scannedFiles: t.fileCount,
1167
1244
  scannedDirs: t.dirs,
1168
- findings: Tt(e).map(({ rule: e, files: t }) => ({
1245
+ findings: Pt(e).map(({ rule: e, files: t }) => ({
1169
1246
  id: e.id,
1170
1247
  scope: e.scope,
1171
1248
  smell: e.smell,
@@ -1173,13 +1250,13 @@ var le = /* @__PURE__ */ new Set([
1173
1250
  files: t
1174
1251
  }))
1175
1252
  };
1176
- }, Et = 3, Dt = .2, Ot = 50, kt = (e, t) => e >= Ot && t > 0 && e / t >= Dt, At = (e) => {
1253
+ }, Ft = 3, It = .2, Lt = 50, Rt = (e, t) => e >= Lt && t > 0 && e / t >= It, zt = (e) => {
1177
1254
  let t = /* @__PURE__ */ new Map();
1178
- for (let n of F(e)) try {
1255
+ for (let n of R(e)) try {
1179
1256
  t.set(y(e, n), C(n, "utf8"));
1180
1257
  } catch {}
1181
1258
  let n = /* @__PURE__ */ new Map();
1182
- for (let t of wt(e)) try {
1259
+ for (let t of jt(e)) try {
1183
1260
  n.set(y(e, t), C(t, "utf8"));
1184
1261
  } catch {}
1185
1262
  let r = Me({
@@ -1187,8 +1264,8 @@ var le = /* @__PURE__ */ new Set([
1187
1264
  envFiles: n
1188
1265
  });
1189
1266
  r !== void 0 && (console.log(""), console.log(Ne(r)));
1190
- }, z = (e) => {
1191
- let t = R(e);
1267
+ }, Bt = (e) => {
1268
+ let t = z(e);
1192
1269
  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;
1193
1270
  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;
1194
1271
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
@@ -1199,27 +1276,27 @@ var le = /* @__PURE__ */ new Set([
1199
1276
  console.log(` [${e}]`);
1200
1277
  let i = [...r].sort((e, t) => e.files.length - t.files.length);
1201
1278
  for (let e of i) {
1202
- if (kt(e.files.length, t.scannedFiles)) {
1279
+ if (Rt(e.files.length, t.scannedFiles)) {
1203
1280
  let n = Math.round(e.files.length / t.scannedFiles * 100);
1204
1281
  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.)");
1205
1282
  continue;
1206
1283
  }
1207
- let r = e.files.slice(0, Et).join(", "), i = e.files.length - Et;
1284
+ let r = e.files.slice(0, Ft).join(", "), i = e.files.length - Ft;
1208
1285
  i > 0 && (n += i);
1209
1286
  let a = i > 0 ? `, +${i} more` : "";
1210
1287
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1211
1288
  }
1212
1289
  }
1213
1290
  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;
1214
- }, jt = "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.", Mt = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, Nt = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, Pt = (e) => {
1215
- let t = Mt.exec(e)?.[1];
1291
+ }, Vt = "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.", Ht = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, Ut = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, Wt = (e) => {
1292
+ let t = Ht.exec(e)?.[1];
1216
1293
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1217
- let n = Nt.exec(e)?.[1];
1294
+ let n = Ut.exec(e)?.[1];
1218
1295
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1219
1296
  }, B = async (e) => {
1220
1297
  let t = v(e, "src", "pages");
1221
1298
  if (!S(t)) return [];
1222
- let { pages: n, dirs: r } = await u(t), i = [];
1299
+ let { pages: n, dirs: r } = await a(t), i = [];
1223
1300
  for (let a of n) {
1224
1301
  if (!o(r, a.dirChain).some((e) => e.hasLayout)) continue;
1225
1302
  let n;
@@ -1229,7 +1306,7 @@ var le = /* @__PURE__ */ new Set([
1229
1306
  continue;
1230
1307
  }
1231
1308
  if (c.test(n)) continue;
1232
- let s = Pt(n);
1309
+ let s = Wt(n);
1233
1310
  s !== null && i.push({
1234
1311
  file: y(e, v(t, a.file)),
1235
1312
  pattern: a.pattern,
@@ -1237,20 +1314,20 @@ var le = /* @__PURE__ */ new Set([
1237
1314
  });
1238
1315
  }
1239
1316
  return i;
1240
- }, Ft = 10, It = async (e) => {
1317
+ }, Gt = 10, Kt = async (e) => {
1241
1318
  console.log("");
1242
1319
  let t = v(e, "src", "pages");
1243
1320
  if (!S(t)) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
1244
1321
  let n = await B(e);
1245
1322
  if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1246
1323
  console.log(`• renderMode:'spa' candidates (${n.length} page${n.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1247
- for (let e of n.slice(0, Ft)) {
1324
+ for (let e of n.slice(0, Gt)) {
1248
1325
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1249
1326
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1250
1327
  }
1251
- let r = n.length - Ft;
1252
- return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${jt}`), n.length;
1253
- }, Lt = /* @__PURE__ */ new Set([
1328
+ let r = n.length - Gt;
1329
+ return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${Vt}`), n.length;
1330
+ }, qt = /* @__PURE__ */ new Set([
1254
1331
  "node_modules",
1255
1332
  "dist",
1256
1333
  "build",
@@ -1258,7 +1335,7 @@ var le = /* @__PURE__ */ new Set([
1258
1335
  ".vite",
1259
1336
  ".framework",
1260
1337
  ".git"
1261
- ]), Rt = (e, t, n) => {
1338
+ ]), Jt = (e, t, n) => {
1262
1339
  let r;
1263
1340
  try {
1264
1341
  r = w(e, { withFileTypes: !0 });
@@ -1266,11 +1343,11 @@ var le = /* @__PURE__ */ new Set([
1266
1343
  return;
1267
1344
  }
1268
1345
  for (let i of r) {
1269
- if (Lt.has(i.name)) continue;
1346
+ if (qt.has(i.name)) continue;
1270
1347
  let r = v(e, i.name);
1271
- i.isDirectory() ? Rt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1348
+ i.isDirectory() ? Jt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1272
1349
  }
1273
- }, zt = (e, t) => {
1350
+ }, Yt = (e, t) => {
1274
1351
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1275
1352
  for (let i of t) {
1276
1353
  let t;
@@ -1295,16 +1372,16 @@ var le = /* @__PURE__ */ new Set([
1295
1372
  }
1296
1373
  }
1297
1374
  return n;
1298
- }, Bt = (e, t) => {
1375
+ }, Xt = (e, t) => {
1299
1376
  let n = e.slice(t.length).split(x).slice(0, -1)[0];
1300
1377
  return n !== void 0 && n.startsWith("[");
1301
1378
  }, V = (e) => {
1302
1379
  let t = [], n = v(e, "src", "pages"), r = v(e, "src");
1303
1380
  if (!S(r)) return t;
1304
1381
  let i = [];
1305
- Rt(r, e, i), i.sort();
1382
+ Jt(r, e, i), i.sort();
1306
1383
  let a = `${y(e, n)}${x}`;
1307
- zt(e, i);
1384
+ Yt(e, i);
1308
1385
  for (let n of i) {
1309
1386
  let r = n.slice(n.lastIndexOf(x) + 1);
1310
1387
  if (n.startsWith(a)) {
@@ -1320,7 +1397,7 @@ var le = /* @__PURE__ */ new Set([
1320
1397
  file: n
1321
1398
  });
1322
1399
  } catch {}
1323
- Bt(n, a) && t.push({
1400
+ Xt(n, a) && t.push({
1324
1401
  kind: "top-level-dynamic-route",
1325
1402
  file: n
1326
1403
  });
@@ -1332,13 +1409,13 @@ var le = /* @__PURE__ */ new Set([
1332
1409
  });
1333
1410
  }
1334
1411
  return t;
1335
- }, Vt = 10, H = (e, t, n) => {
1412
+ }, Zt = 10, H = (e, t, n) => {
1336
1413
  if (t.length === 0) return;
1337
1414
  console.log(`• ${e} (${t.length}):`);
1338
- for (let e of t.slice(0, Vt)) console.log(` ${e}`);
1339
- let r = t.length - Vt;
1415
+ for (let e of t.slice(0, Zt)) console.log(` ${e}`);
1416
+ let r = t.length - Zt;
1340
1417
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
1341
- }, Ht = (e) => {
1418
+ }, Qt = (e) => {
1342
1419
  console.log("");
1343
1420
  let t = v(e, "src", "pages");
1344
1421
  if (!S(t)) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
@@ -1346,18 +1423,18 @@ var le = /* @__PURE__ */ new Set([
1346
1423
  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;
1347
1424
  let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
1348
1425
  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;
1349
- }, Ut = "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.", Wt = /* @__PURE__ */ new Map(), U = (e) => {
1350
- let t = Wt.get(e);
1426
+ }, $t = "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.", en = /* @__PURE__ */ new Map(), U = (e) => {
1427
+ let t = en.get(e);
1351
1428
  if (t !== void 0) return t;
1352
1429
  let n = (async () => {
1353
- let { walk: t, loadDiscovered: n } = await import("./dev-CK522MV5.js"), r = await t(e);
1430
+ let { walk: t, loadDiscovered: n } = await import("./dev--A3nsxA3.js"), r = await t(e);
1354
1431
  return {
1355
1432
  files: r,
1356
1433
  discovered: await n(r)
1357
1434
  };
1358
1435
  })();
1359
- return Wt.set(e, n), n;
1360
- }, Gt = async (e) => {
1436
+ return en.set(e, n), n;
1437
+ }, tn = async (e) => {
1361
1438
  let t;
1362
1439
  try {
1363
1440
  let { files: n, discovered: r } = await U(e), i = r.tables.map((e) => {
@@ -1373,7 +1450,7 @@ var le = /* @__PURE__ */ new Set([
1373
1450
  return;
1374
1451
  }
1375
1452
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
1376
- }, Kt = (e) => e.map((e) => {
1453
+ }, nn = (e) => e.map((e) => {
1377
1454
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
1378
1455
  name: e,
1379
1456
  type: typeof t?.type == "string" ? t.type : ""
@@ -1383,13 +1460,13 @@ var le = /* @__PURE__ */ new Set([
1383
1460
  columns: n,
1384
1461
  primaryKey: t.appliedPrimaryKey ?? []
1385
1462
  };
1386
- }), qt = async (e) => {
1463
+ }), rn = async (e) => {
1387
1464
  try {
1388
1465
  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 = [];
1389
1466
  for (let e of i.queries) {
1390
1467
  let i = n(e.descriptor.source);
1391
1468
  if (i.length === 0) continue;
1392
- let s = Jt(e.file);
1469
+ let s = an(e.file);
1393
1470
  if (s === void 0) continue;
1394
1471
  let c = await r(s, "utf8").catch(() => null);
1395
1472
  if (c === null) continue;
@@ -1406,8 +1483,8 @@ var le = /* @__PURE__ */ new Set([
1406
1483
  } catch {
1407
1484
  return null;
1408
1485
  }
1409
- }, Jt = (e) => e.endsWith(".ts") && !e.endsWith(".server.ts") ? `${e.slice(0, -3)}.server.ts` : void 0, Yt = async (e) => {
1410
- let t = await qt(e);
1486
+ }, an = (e) => e.endsWith(".ts") && !e.endsWith(".server.ts") ? `${e.slice(0, -3)}.server.ts` : void 0, on = async (e) => {
1487
+ let t = await rn(e);
1411
1488
  if (t === null) return;
1412
1489
  let n = t.findings;
1413
1490
  if (n.length > 0) {
@@ -1423,11 +1500,11 @@ var le = /* @__PURE__ */ new Set([
1423
1500
  }, W = async (e) => {
1424
1501
  try {
1425
1502
  let { discovered: t } = await U(e);
1426
- return ve(Kt(t.tables));
1503
+ return ve(nn(t.tables));
1427
1504
  } catch {
1428
1505
  return null;
1429
1506
  }
1430
- }, Xt = async (e) => {
1507
+ }, sn = async (e) => {
1431
1508
  let t = await W(e);
1432
1509
  if (t === null || t.tablesChecked === 0) return;
1433
1510
  let n = be(t);
@@ -1447,7 +1524,7 @@ var le = /* @__PURE__ */ new Set([
1447
1524
  } catch {
1448
1525
  return null;
1449
1526
  }
1450
- }, Zt = async (e) => {
1527
+ }, cn = async (e) => {
1451
1528
  let t = await G(e);
1452
1529
  if (t === null || t.eventsChecked === 0) return;
1453
1530
  let n = Te(t);
@@ -1475,7 +1552,7 @@ var le = /* @__PURE__ */ new Set([
1475
1552
  }
1476
1553
  }, J = async (r) => {
1477
1554
  try {
1478
- let { loadApiConfig: i } = await import("./dev-CK522MV5.js"), { discovered: a } = await U(r), o = e(await i(r)), s = {
1555
+ let { loadApiConfig: i } = await import("./dev--A3nsxA3.js"), { discovered: a } = await U(r), o = t(await i(r)), s = {
1479
1556
  queries: a.queries,
1480
1557
  mutations: a.mutations,
1481
1558
  actions: a.actions,
@@ -1485,8 +1562,8 @@ var le = /* @__PURE__ */ new Set([
1485
1562
  return {
1486
1563
  checked: !0,
1487
1564
  defaultDeny: o,
1488
- undecided: t(s),
1489
- openAccess: n(s)
1565
+ undecided: n(s),
1566
+ openAccess: e(s)
1490
1567
  };
1491
1568
  } catch (e) {
1492
1569
  return {
@@ -1497,7 +1574,7 @@ var le = /* @__PURE__ */ new Set([
1497
1574
  openAccess: []
1498
1575
  };
1499
1576
  }
1500
- }, Qt = async (e) => {
1577
+ }, ln = async (e) => {
1501
1578
  let t = await J(e);
1502
1579
  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;
1503
1580
  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)}`);
@@ -1505,8 +1582,8 @@ var le = /* @__PURE__ */ new Set([
1505
1582
  if (t.undecided.length === 0) return !1;
1506
1583
  console.log("");
1507
1584
  for (let e of t.undecided) console.log(` ✗ ${e.tag} (${e.kind})${e.file === void 0 ? "" : `\n ${e.file}`}`);
1508
- return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), $t(), !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."), $t(), !1);
1509
- }, $t = () => {
1585
+ return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), un(), !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."), un(), !1);
1586
+ }, un = () => {
1510
1587
  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");
1511
1588
  }, Y = async (e) => {
1512
1589
  try {
@@ -1543,7 +1620,7 @@ var le = /* @__PURE__ */ new Set([
1543
1620
  verified: 0
1544
1621
  };
1545
1622
  }
1546
- }, en = async (e) => {
1623
+ }, dn = async (e) => {
1547
1624
  let t = await Y(e);
1548
1625
  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;
1549
1626
  if (t.verified + t.open.length + t.unverified.length === 0) return !1;
@@ -1553,13 +1630,13 @@ var le = /* @__PURE__ */ new Set([
1553
1630
  console.log("");
1554
1631
  for (let e of t.unverified) console.log(` ✗ ${e.id}\n ${e.file}`);
1555
1632
  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;
1556
- }, tn = async (e) => {
1633
+ }, fn = async (e) => {
1557
1634
  let { status: t, message: n } = await q(e);
1558
1635
  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);
1559
- }, nn = async (e) => nt({
1636
+ }, pn = async (e) => ut({
1560
1637
  hasWorkflows: async () => (await U(e)).discovered.workflows.length > 0,
1561
1638
  openStore: async () => {
1562
- let { loadApiConfig: t, buildStore: n } = await import("./dev-CK522MV5.js"), r = await t(e);
1639
+ let { loadApiConfig: t, buildStore: n } = await import("./dev--A3nsxA3.js"), r = await t(e);
1563
1640
  if (r === null) throw Error("no app.config.ts in this directory");
1564
1641
  let i = await n(r, void 0);
1565
1642
  return {
@@ -1573,10 +1650,10 @@ var le = /* @__PURE__ */ new Set([
1573
1650
  let { sweepStalledRuns: t } = await import("@voltro/workflow");
1574
1651
  return t({ store: e });
1575
1652
  }
1576
- }), rn = async (e) => {
1577
- let t = it(await nn(e));
1653
+ }), mn = async (e) => {
1654
+ let t = ft(await pn(e));
1578
1655
  t !== null && (console.log(""), console.log(t));
1579
- }, an = async (e) => {
1656
+ }, hn = async (e) => {
1580
1657
  let t;
1581
1658
  try {
1582
1659
  let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await U(e);
@@ -1599,13 +1676,13 @@ var le = /* @__PURE__ */ new Set([
1599
1676
  console.log("");
1600
1677
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
1601
1678
  console.log(`${r} ${n}`);
1602
- }, X = "voltro-authz-allowlist.txt", on = (e, t = (e) => {
1679
+ }, X = "voltro-authz-allowlist.txt", gn = (e, t = (e) => {
1603
1680
  try {
1604
1681
  return C(e, "utf8");
1605
1682
  } catch {
1606
1683
  return "";
1607
1684
  }
1608
- }) => /* @__PURE__ */ new Set([...Be, ...Ve(F(e).map(t))]), sn = (e, t) => {
1685
+ }) => /* @__PURE__ */ new Set([...Je, ...Ye(R(e).map(t))]), _n = (e, t) => {
1609
1686
  let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1610
1687
  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];
1611
1688
  }, Z = async (e) => {
@@ -1621,25 +1698,25 @@ var le = /* @__PURE__ */ new Set([
1621
1698
  } catch {
1622
1699
  return "";
1623
1700
  }
1624
- }, a = F(e).filter((e) => !r.has(e)).map(i).join("\n");
1701
+ }, a = R(e).filter((e) => !r.has(e)).map(i).join("\n");
1625
1702
  return n.filter(({ name: e, file: t }) => {
1626
1703
  if (a.includes(e)) return !1;
1627
- let n = sn(i(t), e);
1704
+ let n = _n(i(t), e);
1628
1705
  return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
1629
1706
  }).map((e) => e.name).sort();
1630
1707
  } catch {
1631
1708
  return [];
1632
1709
  }
1633
- }, cn = async (e) => {
1710
+ }, vn = async (e) => {
1634
1711
  let t = await Z(e);
1635
1712
  if (t.length !== 0) {
1636
1713
  console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
1637
1714
  for (let e of t) console.log(` ${e}`);
1638
1715
  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.");
1639
1716
  }
1640
- }, ln = async (e) => {
1717
+ }, yn = async (e) => {
1641
1718
  try {
1642
- let { loadApiConfig: t } = await import("./dev-CK522MV5.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1719
+ let { loadApiConfig: t } = await import("./dev--A3nsxA3.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1643
1720
  if (typeof n != "string") return [];
1644
1721
  let [r, i] = n.split("#");
1645
1722
  if (r === void 0 || i === void 0) return [];
@@ -1656,10 +1733,10 @@ var le = /* @__PURE__ */ new Set([
1656
1733
  findings: []
1657
1734
  });
1658
1735
  try {
1659
- let { offlineManifest: n, toInput: r } = await import("./checkCommand-CWcnDArJ.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CFmFKHb2.js"), o = await n(e), s = r(o.manifest, o.tables), c = await ln(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1736
+ let { offlineManifest: n, toInput: r } = await import("./checkCommand-2SbqzukH.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-CFmFKHb2.js"), o = await n(e), s = r(o.manifest, o.tables), c = await yn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1660
1737
  if (l.length === 0) {
1661
1738
  try {
1662
- let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-CVDB2hCq.js");
1739
+ let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-C6ETawPR.js");
1663
1740
  await t(e);
1664
1741
  } catch {
1665
1742
  return t("app-not-importable");
@@ -1683,7 +1760,7 @@ var le = /* @__PURE__ */ new Set([
1683
1760
  } catch {
1684
1761
  return t("app-not-importable");
1685
1762
  }
1686
- }, un = async (e) => {
1763
+ }, bn = async (e) => {
1687
1764
  let { findings: t, evaluated: n, notEvaluated: r } = await Q(e);
1688
1765
  if (!n) {
1689
1766
  if (console.log(""), r === "app-not-importable") {
@@ -1698,7 +1775,7 @@ var le = /* @__PURE__ */ new Set([
1698
1775
  for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
1699
1776
  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}`);
1700
1777
  }
1701
- }, dn = async (e) => {
1778
+ }, xn = async (e) => {
1702
1779
  let t = ["src/locales", "locales"], n, r = [];
1703
1780
  for (let i of t) {
1704
1781
  let t = v(e, i);
@@ -1710,38 +1787,38 @@ var le = /* @__PURE__ */ new Set([
1710
1787
  if (n === void 0 || r.length === 0) return;
1711
1788
  let i = [];
1712
1789
  try {
1713
- let { loadApiConfig: t } = await import("./dev-CK522MV5.js");
1790
+ let { loadApiConfig: t } = await import("./dev--A3nsxA3.js");
1714
1791
  i = (await t(e))?.locales ?? [];
1715
1792
  } catch {
1716
1793
  return;
1717
1794
  }
1718
- let a = Pe({
1795
+ let a = He({
1719
1796
  catalogCodes: r,
1720
1797
  declaredLocales: i
1721
1798
  });
1722
1799
  if (a !== void 0) {
1723
1800
  console.log("");
1724
- for (let e of Fe(a, n)) console.log(e);
1801
+ for (let e of Ue(a, n)) console.log(e);
1725
1802
  }
1726
- }, fn = async (e) => {
1803
+ }, Sn = async (e) => {
1727
1804
  let t = v(e, "src", "pages");
1728
1805
  if (!S(t)) return !1;
1729
1806
  let n = [];
1730
1807
  try {
1731
- let { walkPagesTree: e } = await import("./webDev-DposiF3j.js");
1808
+ let { walkPagesTree: e } = await import("./webDev-CZbTsDcH.js");
1732
1809
  n = (await e(t)).pages.map((e) => e.pattern);
1733
1810
  } catch {
1734
1811
  return !1;
1735
1812
  }
1736
- let r = Le(e, n);
1813
+ let r = Ge(e, n);
1737
1814
  if (r === void 0) return !1;
1738
1815
  console.log("");
1739
- for (let e of Re(r)) console.log(e);
1816
+ for (let e of Ke(r)) console.log(e);
1740
1817
  return r.problems.length > 0;
1741
- }, pn = async (e) => {
1818
+ }, Cn = async (e) => {
1742
1819
  let { discovered: t } = await U(e), n = [];
1743
1820
  try {
1744
- let { loadApiConfig: t } = await import("./dev-CK522MV5.js");
1821
+ let { loadApiConfig: t } = await import("./dev--A3nsxA3.js");
1745
1822
  n = ((await t(e))?.plugins ?? []).map((e) => {
1746
1823
  let t = (e.extendSchema?.tables ?? []).map((e) => {
1747
1824
  let t = e;
@@ -1754,8 +1831,8 @@ var le = /* @__PURE__ */ new Set([
1754
1831
  plugin: e.name,
1755
1832
  tables: t,
1756
1833
  rpcTags: n,
1757
- canDisableTables: mn.has(r),
1758
- canAlias: hn.has(r)
1834
+ canDisableTables: wn.has(r),
1835
+ canAlias: Tn.has(r)
1759
1836
  };
1760
1837
  });
1761
1838
  } catch {
@@ -1767,18 +1844,18 @@ var le = /* @__PURE__ */ new Set([
1767
1844
  ...t.mutations,
1768
1845
  ...t.actions,
1769
1846
  ...t.streams ?? []
1770
- ].map((e) => String(e.descriptor.name)), i = et(n, {
1847
+ ].map((e) => String(e.descriptor.name)), i = ct(n, {
1771
1848
  tables: t.tables.map((e) => e.tableName),
1772
1849
  rpcTags: r
1773
1850
  });
1774
1851
  console.log();
1775
- for (let e of tt(n, i)) console.log(e);
1776
- }, mn = /* @__PURE__ */ new Set([
1852
+ for (let e of lt(n, i)) console.log(e);
1853
+ }, wn = /* @__PURE__ */ new Set([
1777
1854
  "@voltro/plugin-rbac",
1778
1855
  "@voltro/plugin-audit",
1779
1856
  "@voltro/plugin-notifications",
1780
1857
  "aiFlows"
1781
- ]), hn = /* @__PURE__ */ new Set([
1858
+ ]), Tn = /* @__PURE__ */ new Set([
1782
1859
  "aiFlows",
1783
1860
  "@voltro/plugin-audit",
1784
1861
  "@voltro/plugin-billing",
@@ -1794,9 +1871,9 @@ var le = /* @__PURE__ */ new Set([
1794
1871
  "@voltro/plugin-search",
1795
1872
  "@voltro/plugin-sso-saml",
1796
1873
  "@voltro/plugin-storage",
1797
- "@voltro/plugin-versioning"
1798
- ]), $ = 20, gn = async (e) => {
1799
- let t = await _n(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;
1874
+ "@voltro/plugin-row-history"
1875
+ ]), $ = 20, En = async (e) => {
1876
+ let t = await Dn(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;
1800
1877
  return {
1801
1878
  executors: t.executorCount,
1802
1879
  guardVocabulary: [...t.vocabulary].sort(),
@@ -1808,7 +1885,7 @@ var le = /* @__PURE__ */ new Set([
1808
1885
  },
1809
1886
  unchecked: t.result.unchecked
1810
1887
  };
1811
- }, _n = async (e) => {
1888
+ }, Dn = async (e) => {
1812
1889
  let { discovered: t } = await U(e), n = /* @__PURE__ */ new Map(), r = (e) => {
1813
1890
  if (!n.has(e)) try {
1814
1891
  n.set(e, C(e, "utf8"));
@@ -1816,7 +1893,7 @@ var le = /* @__PURE__ */ new Set([
1816
1893
  n.set(e, "");
1817
1894
  }
1818
1895
  return n.get(e);
1819
- }, i = on(e, r), a = /* @__PURE__ */ new Set();
1896
+ }, i = gn(e, r), a = /* @__PURE__ */ new Set();
1820
1897
  for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
1821
1898
  let e = t.references?.();
1822
1899
  e && a.add(e.tableName);
@@ -1848,12 +1925,12 @@ var le = /* @__PURE__ */ new Set([
1848
1925
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
1849
1926
  ...a?.table === void 0 ? {} : { table: a.table }
1850
1927
  };
1851
- })), s = v(e, X), c = S(s) ? j(C(s, "utf8")) : void 0, l = !1;
1852
- for (let t of F(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
1928
+ })), s = v(e, X), c = S(s) ? N(C(s, "utf8")) : void 0, l = !1;
1929
+ for (let t of R(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
1853
1930
  l = !0;
1854
1931
  break;
1855
1932
  }
1856
- let u = qe({
1933
+ let u = tt({
1857
1934
  executors: o,
1858
1935
  guardVocabulary: i,
1859
1936
  tenantScopedTables: t.tenantScopedTables,
@@ -1870,23 +1947,23 @@ var le = /* @__PURE__ */ new Set([
1870
1947
  executorCount: o.length,
1871
1948
  allowPath: s
1872
1949
  };
1873
- }, vn = async (e, t) => {
1874
- let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await _n(e), l = n.counts;
1950
+ }, On = async (e, t) => {
1951
+ let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await Dn(e), l = n.counts;
1875
1952
  console.log(`\nauthz scan · ${o} executor(s)`);
1876
- let u = [...r].filter((e) => !Be.has(e)).sort();
1953
+ let u = [...r].filter((e) => !Je.has(e)).sort();
1877
1954
  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)}`);
1878
1955
  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;
1879
1956
  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) {
1880
- let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ze(e, t);
1881
- return oe(s, Xe(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;
1957
+ let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = at(e, t);
1958
+ return oe(s, it(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;
1882
1959
  }
1883
1960
  if (n.unchecked.length === 0) return !1;
1884
1961
  console.log("");
1885
1962
  for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
1886
1963
  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);
1887
- }, yn = async (e) => {
1964
+ }, kn = async (e) => {
1888
1965
  try {
1889
- let { loadApiConfig: t } = await import("./dev-CK522MV5.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
1966
+ let { loadApiConfig: t } = await import("./dev--A3nsxA3.js"), { decidedSchemaReport: n } = await import("./doctorDecidedSchema-BlbWgCD_.js"), r = await t(e);
1890
1967
  return r == null ? null : n({
1891
1968
  ...r.schema ? { declared: r.schema } : {},
1892
1969
  ...r.store === void 0 ? {} : { store: r.store },
@@ -1895,39 +1972,42 @@ var le = /* @__PURE__ */ new Set([
1895
1972
  } catch {
1896
1973
  return null;
1897
1974
  }
1898
- }, bn = async (e) => {
1899
- let t = await yn(e);
1975
+ }, An = async (e) => {
1976
+ let t = await kn(e);
1900
1977
  if (t !== null) {
1901
1978
  console.log(""), console.log("• framework tables whose presence is DECIDED rather than implied by your source tree:");
1902
1979
  for (let e of t.text.split("\n")) console.log(` ${e}`);
1903
1980
  }
1904
- }, xn = async (e) => {
1905
- if (e.includes("--help") || e.includes("-h")) return console.log(Ut), 0;
1906
- let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), o = b(n ?? process.cwd());
1981
+ }, jn = async (e) => {
1982
+ if (e.includes("--help") || e.includes("-h")) return console.log($t), 0;
1983
+ let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), r = b(n ?? process.cwd());
1907
1984
  if (g(e)) return ee(), h({
1908
- ...R(o),
1909
- spaCandidates: await B(o),
1910
- pageConvention: V(o),
1911
- fileTaxonomy: re(o).findings,
1912
- junctionFks: (await W(o))?.issues ?? [],
1913
- eventDelivery: await G(o),
1914
- duplicateInstances: r(o),
1915
- versionSkew: a(o) ?? null,
1916
- serverOnly: (await q(o)).status,
1917
- accessDecisions: await J(o),
1918
- webhookVerification: await Y(o),
1919
- authz: await gn(o),
1920
- ungrantedScopes: await Q(o),
1921
- neverEmittedEvents: await Z(o),
1922
- stalledRuns: await nn(o),
1923
- decidedSchema: await yn(o)
1985
+ ...z(r),
1986
+ spaCandidates: await B(r),
1987
+ pageConvention: V(r),
1988
+ fileTaxonomy: re(r).findings,
1989
+ junctionFks: (await W(r))?.issues ?? [],
1990
+ eventDelivery: await G(r),
1991
+ duplicateInstances: s(r),
1992
+ versionSkew: i(r) ?? null,
1993
+ serverOnly: (await q(r)).status,
1994
+ accessDecisions: await J(r),
1995
+ webhookVerification: await Y(r),
1996
+ authz: await En(r),
1997
+ ungrantedScopes: await Q(r),
1998
+ neverEmittedEvents: await Z(r),
1999
+ unimportedDeps: Be(r),
2000
+ stalledRuns: await pn(r),
2001
+ decidedSchema: await kn(r)
1924
2002
  }), 0;
1925
- let c = ct(o, t);
1926
- z(o), At(o), Ht(o), ne(o), await It(o), await an(o), await rn(o), await Gt(o), await Xt(o), await Yt(o), await bn(o), await Zt(o), await pn(o), await un(o), await cn(o), await dn(o);
1927
- let l = await tn(o), u = await vn(o, e.includes("--write-authz-allowlist")), d = await Qt(o), f = await en(o), p = await fn(o), m = i(a(o));
2003
+ let a = gt(r, t);
2004
+ Bt(r), zt(r), Qt(r), ne(r), await Kt(r), await hn(r), await mn(r), await tn(r), await sn(r), await on(r), await An(r), await cn(r), await Cn(r), await bn(r), await vn(r), console.log("");
2005
+ for (let e of Ve(Be(r))) console.log(e);
2006
+ await xn(r);
2007
+ let o = await fn(r), c = await On(r, e.includes("--write-authz-allowlist")), d = await ln(r), f = await dn(r), p = await Sn(r), m = u(i(r));
1928
2008
  m !== void 0 && (console.log(""), console.log(m));
1929
- let _ = s(r(o));
1930
- return _ !== void 0 && console.log(`\n${_}`), l || u || d || p || f ? Math.max(c, 1) : c;
2009
+ let _ = l(s(r));
2010
+ return _ !== void 0 && console.log(`\n${_}`), o || c || d || p || f ? Math.max(a, 1) : a;
1931
2011
  };
1932
2012
  //#endregion
1933
- export { B as C, Y as E, V as S, Q as T, Ht as _, gn as a, ct as b, G as c, L as d, ot as f, z as g, At as h, J as i, on as l, qt as m, st as n, Z as o, W as p, jt as r, sn as s, Ut as t, R as u, It as v, q as w, Tt as x, xn as y };
2013
+ export { B as C, Y as E, V as S, Q as T, Qt as _, En as a, gt as b, G as c, Nt as d, mt as f, Bt as g, zt as h, J as i, gn as l, rn as m, ht as n, Z as o, W as p, Vt as r, _n as s, $t as t, z as u, Kt as v, q as w, Pt as x, jn as y };