@voltro/cli 0.42.0 → 0.43.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 (106) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/dist/apiBuild-B7NgSMhX.js +2 -0
  3. package/dist/{apiBuild-B_iH24qf.js → apiBuild-DF0f69Gf.js} +2 -2
  4. package/dist/bin.js +1 -1
  5. package/dist/{build-DUdi6F_Q.js → build-LC_IJaHL.js} +7 -7
  6. package/dist/{checkCommand-B2RWrpWi.js → checkCommand-DBpTSK8d.js} +1 -1
  7. package/dist/{checkCommand-DWL2_PPR.js → checkCommand-DDxwJCja.js} +2 -2
  8. package/dist/{codegenCommand-DkjoJ30Q.js → codegenCommand-LjxfK4Am.js} +2 -2
  9. package/dist/{codemodRunner-sFuWDETH.js → codemodRunner-Cnnz4-I7.js} +19 -4
  10. package/dist/{commands-DoTClnSk.js → commands-CbQcuWsE.js} +20 -20
  11. package/dist/{dashboardCommand-Bro7Jwts.js → dashboardCommand-CAHjCe_9.js} +3 -3
  12. package/dist/{dataCommand-Rj57y1vg.js → dataCommand-CrqvbVOv.js} +143 -133
  13. package/dist/{dev-D4l_YOzI.js → dev-B2rZfqxw.js} +2 -2
  14. package/dist/{dev-DuqBJC7g.js → dev-D0E5x2Ue.js} +1 -1
  15. package/dist/doctorCommand-CdsctSwg.js +2 -0
  16. package/dist/{doctorCommand-TmZTAepc.js → doctorCommand-Ct_3BHgd.js} +226 -216
  17. package/dist/{dormancyCommand-CUKhRmGG.js → dormancyCommand-D1DKnYce.js} +1 -1
  18. package/dist/{embeddingsCommand-Db-iQM3l.js → embeddingsCommand-1sZRBKlM.js} +1 -1
  19. package/dist/{envCommand-DQ_URZq9.js → envCommand-7qjlQgLD.js} +2 -2
  20. package/dist/{evolveCommand-CghRPrGO.js → evolveCommand-D5opB48I.js} +2 -2
  21. package/dist/index.js +1 -1
  22. package/dist/{infoCommand-BCs_ZFnl.js → infoCommand-Bs_8RU22.js} +1 -1
  23. package/dist/{runtimeTrace-jFKvOPBz.js → runtimeTrace-Wmi7V-wN.js} +1 -1
  24. package/dist/{sdkgen-COQqHB9V.js → sdkgen-DbTFGCjg.js} +1 -1
  25. package/dist/{serveCommand-C512kzZA.js → serveCommand-C8xzYCwa.js} +4 -4
  26. package/dist/serveCommand-CGNTXafv.js +2 -0
  27. package/dist/serveEntry.js +1 -1
  28. package/dist/{start-fifc0xpW.js → start-BAlGcjF5.js} +1 -1
  29. package/dist/{start-BjcuFME_.js → start-Ds3xyn8R.js} +1 -1
  30. package/dist/startEntry.js +1 -1
  31. package/dist/startup.d.ts +1 -1
  32. package/dist/updateCommand-BvkKlV7D.js +2 -0
  33. package/dist/{updateCommand-HZQVBU3T.js → updateCommand-CrI9B4d7.js} +1 -1
  34. package/dist/{webDev-Dk8lFUzf.js → webDev-101csg6Q.js} +2 -2
  35. package/dist/webDev-BLKBO34K.js +2 -0
  36. package/dist/{webhooksCommand-D7ZpH7w0.js → webhooksCommand-BXUP5YjW.js} +1 -1
  37. package/package.json +23 -17
  38. package/templates/AGENTS.md +1 -1
  39. package/templates/agent-docs/_index.md +1 -1
  40. package/templates/agent-docs/authentication.md +1 -1
  41. package/templates/agent-docs/cli.md +2 -2
  42. package/templates/agent-docs/data.md +14 -12
  43. package/templates/agent-docs/database/migrations.md +1 -1
  44. package/templates/agent-docs/internationalization.md +2 -2
  45. package/templates/agent-docs/observability.md +1 -1
  46. package/templates/agent-docs/plugins/openapi.md +1 -1
  47. package/templates/agent-docs/plugins/versioning.md +1 -1
  48. package/templates/agent-docs/plugins/webhooks.md +1 -1
  49. package/templates/agent-docs/plugins.md +1 -1
  50. package/templates/agent-docs/routing.md +1 -1
  51. package/templates/agent-docs/scheduling.md +1 -1
  52. package/templates/agent-docs/security.md +2 -2
  53. package/templates/agent-docs/templates/apibackends.md +1 -1
  54. package/templates/agent-docs/whats-new.md +38 -66
  55. package/templates/apps/api-ai/package.json +7 -7
  56. package/templates/apps/api-auth/package.json +8 -8
  57. package/templates/apps/api-backend/package.json +7 -7
  58. package/templates/apps/api-backend-deactivation/package.json +7 -7
  59. package/templates/apps/api-backend-mail/package.json +8 -8
  60. package/templates/apps/api-backend-mariadb/package.json +9 -9
  61. package/templates/apps/api-backend-sqlite/package.json +8 -8
  62. package/templates/apps/api-backend-storage/package.json +8 -8
  63. package/templates/apps/api-cms/package.json +10 -10
  64. package/templates/apps/api-collab/package.json +8 -8
  65. package/templates/apps/api-data-advanced/package.json +8 -8
  66. package/templates/apps/api-durable/package.json +8 -8
  67. package/templates/apps/api-feature-flags/package.json +9 -9
  68. package/templates/apps/api-governance/package.json +8 -8
  69. package/templates/apps/api-kv/package.json +8 -8
  70. package/templates/apps/api-moderation/package.json +8 -8
  71. package/templates/apps/api-observability/package.json +8 -8
  72. package/templates/apps/api-ratelimit/package.json +8 -8
  73. package/templates/apps/api-rbac/package.json +8 -8
  74. package/templates/apps/api-rest/package.json +7 -7
  75. package/templates/apps/api-saas/package.json +11 -11
  76. package/templates/apps/api-saas-starter/package.json +10 -10
  77. package/templates/apps/api-search/package.json +8 -8
  78. package/templates/apps/api-status/package.json +8 -8
  79. package/templates/apps/api-versioning/package.json +8 -8
  80. package/templates/apps/api-webhooks/package.json +9 -9
  81. package/templates/apps/changelog/package.json +6 -6
  82. package/templates/apps/edge-functions/package.json +2 -2
  83. package/templates/apps/frontend-admin/package.json +8 -8
  84. package/templates/apps/frontend-app/package.json +9 -9
  85. package/templates/apps/frontend-auth/package.json +8 -8
  86. package/templates/apps/frontend-blank/package.json +7 -7
  87. package/templates/apps/frontend-cms/package.json +9 -9
  88. package/templates/apps/frontend-collab/package.json +10 -10
  89. package/templates/apps/frontend-contact/package.json +7 -7
  90. package/templates/apps/frontend-dashboard/package.json +7 -7
  91. package/templates/apps/frontend-docs/package.json +7 -7
  92. package/templates/apps/frontend-i18n/package.json +6 -6
  93. package/templates/apps/frontend-landing/package.json +7 -7
  94. package/templates/apps/frontend-portal/package.json +8 -8
  95. package/templates/apps/frontend-saas/package.json +8 -8
  96. package/templates/apps/frontend-spa/package.json +7 -7
  97. package/templates/apps/frontend-ssr/package.json +7 -7
  98. package/templates/apps/frontend-ssr-api/package.json +8 -8
  99. package/templates/apps/frontend-static-blog/package.json +6 -6
  100. package/templates/apps/frontend-status/package.json +8 -8
  101. package/templates/apps/mobile-app/package.json +4 -4
  102. package/dist/apiBuild-RM5W5YXk.js +0 -2
  103. package/dist/doctorCommand-Bw3V4L1j.js +0 -2
  104. package/dist/serveCommand-gj7MsqXY.js +0 -2
  105. package/dist/updateCommand-B2alql9k.js +0 -2
  106. package/dist/webDev-D6QeXw3P.js +0 -2
@@ -1,8 +1,8 @@
1
- import { Bt as e, Ht as t, Vt as n } from "./dev-D4l_YOzI.js";
2
- import { at as r, it as i, ot as a, s as o, st as s, t as c, w as l, y as u } from "./webDev-Dk8lFUzf.js";
1
+ import { Bt as e, Ht as t, Vt as n } from "./dev-B2rZfqxw.js";
2
+ import { at as r, it as i, ot as a, s as o, st as s, t as c, w as l, y as u } from "./webDev-101csg6Q.js";
3
3
  import { w as d } from "./frameworkTableAssembly-oMPBKqlE.js";
4
4
  import { n as f } from "./pageConvention-cEiRxdab.js";
5
- import { r as p, u as m } from "./apiBuild-B_iH24qf.js";
5
+ import { r as p, u as m } from "./apiBuild-DF0f69Gf.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
8
  import { n as ne, r as re } from "./fileTaxonomy-CJfgOllU.js";
@@ -477,33 +477,33 @@ var le = /* @__PURE__ */ new Set([
477
477
  for (let n of e.problems) t.push(` • ${n}`);
478
478
  }
479
479
  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
- }, j = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), M = /* @__PURE__ */ new Set([
480
+ }, j = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), ze = /* @__PURE__ */ new Set([
481
481
  "requireScope",
482
482
  "requireAnyScope",
483
483
  "requireAuth",
484
484
  "assertScope",
485
485
  "assertCan",
486
486
  "can"
487
- ]), ze = (e) => {
487
+ ]), Be = (e) => {
488
488
  let t = /* @__PURE__ */ new Set();
489
489
  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]);
490
490
  return t;
491
- }, Be = (e, t) => {
491
+ }, Ve = (e, t) => {
492
492
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
493
- }, Ve = (e) => {
493
+ }, He = (e) => {
494
494
  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
495
  return t && n;
496
- }, He = (e, t, n) => {
496
+ }, Ue = (e, t, n) => {
497
497
  if (e.hasDeclaredGuards) return { kind: "declared-guard" };
498
- let r = j(e.src), i = Be(r, t);
499
- return i === void 0 ? Ve(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
498
+ let r = j(e.src), i = Ve(r, t);
499
+ return i === void 0 ? He(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
500
500
  kind: "vocabulary-guard",
501
501
  name: i
502
502
  };
503
- }, Ue = (e, t, n) => {
503
+ }, We = (e, t, n) => {
504
504
  let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
505
505
  return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
506
- }, We = (e, t, n) => {
506
+ }, Ge = (e, t, n) => {
507
507
  switch (t.kind) {
508
508
  case "declared-guard": return "guards: on the descriptor";
509
509
  case "vocabulary-guard": return `calls ${t.name}()`;
@@ -511,15 +511,15 @@ var le = /* @__PURE__ */ new Set([
511
511
  case "allowlisted": return "accepted by a previous run";
512
512
  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
513
  }
514
- }, Ge = (e) => {
514
+ }, Ke = (e) => {
515
515
  let t = e.executors.map((t) => {
516
- let n = He(t, e.guardVocabulary, e.allowlist);
516
+ let n = Ue(t, e.guardVocabulary, e.allowlist);
517
517
  return {
518
518
  tag: t.tag,
519
519
  path: t.path,
520
520
  verdict: n,
521
- severity: Ue(t, e.tenantScopedTables, e.referencedTables),
522
- why: We(t, n, e.rowFilterRegistered === !0)
521
+ severity: We(t, e.tenantScopedTables, e.referencedTables),
522
+ why: Ge(t, n, e.rowFilterRegistered === !0)
523
523
  };
524
524
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
525
525
  return {
@@ -533,7 +533,7 @@ var le = /* @__PURE__ */ new Set([
533
533
  },
534
534
  unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
535
535
  };
536
- }, Ke = [
536
+ }, qe = [
537
537
  "# voltro doctor — authz allowlist",
538
538
  "#",
539
539
  "# Two KINDS of line, and the difference is the point:",
@@ -558,19 +558,19 @@ var le = /* @__PURE__ */ new Set([
558
558
  "# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
559
559
  "# lines for executors that are newly unchecked.",
560
560
  ""
561
- ], qe = (e) => {
561
+ ], Je = (e) => {
562
562
  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
563
  return [
564
- ...Ke,
564
+ ...qe,
565
565
  ...n,
566
566
  ...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
567
567
  ...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
568
568
  ""
569
569
  ].join("\n");
570
- }, Je = (e, t) => {
570
+ }, Ye = (e, t) => {
571
571
  let n = [...new Set(t)].sort();
572
- if (e === void 0) return qe(n.map((e) => ({ tag: e })));
573
- let r = N(e).tags, i = n.filter((e) => !r.has(e));
572
+ if (e === void 0) return Je(n.map((e) => ({ tag: e })));
573
+ let r = M(e).tags, i = n.filter((e) => !r.has(e));
574
574
  return i.length === 0 ? e : [
575
575
  e.replace(/\n+$/, ""),
576
576
  "",
@@ -579,25 +579,25 @@ var le = /* @__PURE__ */ new Set([
579
579
  ...i,
580
580
  ""
581
581
  ].join("\n");
582
- }, Ye = (e, t) => {
582
+ }, Xe = (e, t) => {
583
583
  let n = [...new Set(t)];
584
584
  if (e === void 0) return {
585
585
  added: n.length,
586
586
  kept: 0,
587
587
  removed: 0
588
588
  };
589
- let r = N(e).tags;
589
+ let r = M(e).tags;
590
590
  return {
591
591
  added: n.filter((e) => !r.has(e)).length,
592
592
  kept: r.size,
593
593
  removed: 0
594
594
  };
595
- }, Xe = class extends Error {
595
+ }, Ze = class extends Error {
596
596
  tag;
597
597
  constructor(e) {
598
598
  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
599
  }
600
- }, N = (e) => {
600
+ }, M = (e) => {
601
601
  let t = [];
602
602
  for (let n of e.split("\n")) {
603
603
  let e = n.trim();
@@ -610,7 +610,7 @@ var le = /* @__PURE__ */ new Set([
610
610
  continue;
611
611
  }
612
612
  let a = r[2].trim();
613
- if (a.length === 0) throw new Xe(i);
613
+ if (a.length === 0) throw new Ze(i);
614
614
  t.push({
615
615
  tag: i,
616
616
  reviewed: a
@@ -620,13 +620,13 @@ var le = /* @__PURE__ */ new Set([
620
620
  tags: new Set(t.map((e) => e.tag)),
621
621
  entries: t
622
622
  };
623
- }, Ze = (e) => {
623
+ }, Qe = (e) => {
624
624
  let t = e.indexOf(".");
625
625
  return t <= 0 ? void 0 : e.slice(0, t);
626
- }, Qe = (e, t) => {
626
+ }, $e = (e, t) => {
627
627
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
628
628
  for (let e of t.rpcTags) {
629
- let t = Ze(e);
629
+ let t = Qe(e);
630
630
  if (t === void 0) continue;
631
631
  let n = i.get(t);
632
632
  n ? n.push(e) : i.set(t, [e]);
@@ -655,7 +655,7 @@ var le = /* @__PURE__ */ new Set([
655
655
  });
656
656
  continue;
657
657
  }
658
- let s = new Set(o.rpcTags.map(Ze).filter((e) => e !== void 0));
658
+ let s = new Set(o.rpcTags.map(Qe).filter((e) => e !== void 0));
659
659
  for (let e of s) {
660
660
  let t = i.get(e);
661
661
  t !== void 0 && a.push({
@@ -668,12 +668,12 @@ var le = /* @__PURE__ */ new Set([
668
668
  }
669
669
  }
670
670
  return a;
671
- }, $e = (e, t) => {
671
+ }, et = (e, t) => {
672
672
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
673
673
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
674
674
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
675
675
  return r;
676
- }, et = async (e) => {
676
+ }, tt = async (e) => {
677
677
  if (!await e.hasWorkflows().catch(() => !1)) return { kind: "not-applicable" };
678
678
  let t;
679
679
  try {
@@ -697,12 +697,12 @@ var le = /* @__PURE__ */ new Set([
697
697
  } finally {
698
698
  await t.close().catch(() => {});
699
699
  }
700
- }, tt = (e) => {
700
+ }, nt = (e) => {
701
701
  let t = Math.round(e / 6e4);
702
702
  if (t < 90) return `${t}m`;
703
703
  let n = Math.round(t / 60);
704
704
  return n < 48 ? `${n}h` : `${Math.round(n / 24)}d`;
705
- }, nt = (e) => {
705
+ }, rt = (e) => {
706
706
  if (e.kind === "not-applicable") return null;
707
707
  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
708
  Run it where the app's DB env vars are set, or ignore this line locally.`;
@@ -710,13 +710,13 @@ var le = /* @__PURE__ */ new Set([
710
710
  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
711
  else {
712
712
  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 ${tt(e.idleMs)} ${e.reason}`);
713
+ for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${nt(e.idleMs)} ${e.reason}`);
714
714
  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
715
  }
716
716
  t.sawFullRunPage && n.push(" (the examined page filled up — more old runs exist than were read this pass)");
717
717
  for (let e of t.failures) n.push(` ! could not examine ${e.subject}: ${e.detail}`);
718
718
  return n.join("\n");
719
- }, rt = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), it = (e) => {
719
+ }, it = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), at = (e) => {
720
720
  let t = m(e), n = p(e);
721
721
  return {
722
722
  root: e,
@@ -725,19 +725,22 @@ var le = /* @__PURE__ */ new Set([
725
725
  apiBundle: n,
726
726
  apiBundlePresent: S(n)
727
727
  };
728
- }, at = [
728
+ }, ot = [
729
729
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
730
730
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
731
731
  "(Generated Dockerfiles from `voltro create-project` already do this.)"
732
- ].join("\n "), ot = (e, t = !1) => {
733
- let n = it(e);
734
- if (console.log(`voltro doctor — serve preflight for ${n.root}`), !rt(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
732
+ ].join("\n "), st = (e, t = !1) => {
733
+ let n = at(e);
734
+ if (console.log(`voltro doctor — serve preflight for ${n.root}`), !it(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
735
735
  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
736
  let r = process.env.NODE_ENV === "production";
737
737
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
738
738
  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(` ${at}`), +!!i;
740
- }, st = (e, t, n) => {
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(` ${ot}`), +!!i;
740
+ }, ct = (e) => {
741
+ let t = e.absPath.replace(/\.server\.(tsx?)$/, ".$1");
742
+ if (t !== e.absPath) return e.sources.get(t) ?? e.sources.get(t.replace(/\.ts$/, ".tsx"));
743
+ }, lt = (e, t, n) => {
741
744
  let r = new Set(e), i = _(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
742
745
  `${e}.ts`,
743
746
  `${e}.tsx`,
@@ -763,7 +766,7 @@ var le = /* @__PURE__ */ new Set([
763
766
  r !== void 0 && o.push(r);
764
767
  }
765
768
  return o;
766
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => ft.test(y(n, e))), l = new Set(c);
769
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => mt.test(y(n, e))), l = new Set(c);
767
770
  for (; c.length > 0;) {
768
771
  let e = c.shift();
769
772
  if (!s.has(e)) {
@@ -776,11 +779,11 @@ var le = /* @__PURE__ */ new Set([
776
779
  let d = /* @__PURE__ */ new Set();
777
780
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
778
781
  return d;
779
- }, P = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), ct = (e, t) => (e.match(t) ?? []).length, F = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, lt = (e, t) => {
782
+ }, N = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), ut = (e, t) => (e.match(t) ?? []).length, P = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, dt = (e, t) => {
780
783
  if (e.src.includes(t)) return !0;
781
784
  let n = t.replace(/^@voltro\/plugin-/, "");
782
- return n !== t && F(e.file, RegExp(`^${n}Plugin$`)) > 0;
783
- }, ut = (e, t) => {
785
+ return n !== t && P(e.file, RegExp(`^${n}Plugin$`)) > 0;
786
+ }, ft = (e, t) => {
784
787
  let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
785
788
  for (; (i = r.exec(e)) !== null;) {
786
789
  let t = i.index + i[0].length, r = 1, a = t;
@@ -788,10 +791,10 @@ var le = /* @__PURE__ */ new Set([
788
791
  n.push(e.slice(t, a - 1));
789
792
  }
790
793
  return n;
791
- }, dt = (e, t) => {
794
+ }, pt = (e, t) => {
792
795
  let n = t.getSymbol()?.getDeclarations()?.[0];
793
796
  return n === void 0 ? [] : e.getDescendantsOfKind(O.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
794
- }, ft = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, pt = /\.(query|mutation|action|stream)\.server\.tsx?$/, mt = [
797
+ }, mt = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, ht = /\.(query|mutation|action|stream)\.server\.tsx?$/, gt = [
795
798
  "Id",
796
799
  "ID",
797
800
  "Hash",
@@ -817,7 +820,7 @@ var le = /* @__PURE__ */ new Set([
817
820
  "Comment",
818
821
  "Provider",
819
822
  "Format"
820
- ], ht = RegExp(`(?:${mt.join("|")})$|_(?:${mt.map((e) => e.toLowerCase()).join("|")})$`), gt = [
823
+ ], _t = RegExp(`(?:${gt.join("|")})$|_(?:${gt.map((e) => e.toLowerCase()).join("|")})$`), vt = [
821
824
  {
822
825
  id: "hardcoded-framework-cookie",
823
826
  scope: "client",
@@ -830,17 +833,22 @@ var le = /* @__PURE__ */ new Set([
830
833
  scope: "server",
831
834
  smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
832
835
  use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
833
- match: ({ src: e, path: t }) => pt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
836
+ match: ({ src: e, path: t }) => ht.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
834
837
  },
835
838
  {
836
839
  id: "subject-write-no-guard",
837
840
  scope: "server",
838
- smell: "writes ctx.request.subject.id into the database with no guard on the descriptor — an anonymous caller reaches the write and gets a raw statement failure instead of a typed refusal",
839
- use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
840
- match: ({ src: e, path: t }) => {
841
- if (!pt.test(t)) return !1;
842
- let n = P(e);
843
- return /\bsubject\s*\.\s*id\b/.test(n) && /\b(?:insert|update|upsert|insertMany|updateMany|insertRow|upsertRow|updateManyRow)\s*\(/.test(n) && !/\b(?:guard|guards|requireScope|requireAnyScope|assertCan|assertScope|requireAuth|Unauthenticated)\b/.test(n);
841
+ smell: "an openAccess procedure writes ctx.request.subject.id into the database and never checks whose row it is — an anonymous caller reaches the write and gets a raw statement failure instead of a typed refusal",
842
+ 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
+ match: (e) => {
844
+ let { src: t, path: n } = e;
845
+ if (!ht.test(n)) return !1;
846
+ let r = N(t);
847
+ 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 = ct(e);
849
+ if (i === void 0) return !1;
850
+ let a = N(i);
851
+ return /\binternal\s*:\s*true\b/.test(a) || /\bguards\s*:\s*\[/.test(a) || /\.guard\s*\(/.test(a) ? !1 : /\bopenAccess\s*:/.test(a);
844
852
  }
845
853
  },
846
854
  {
@@ -848,14 +856,14 @@ var le = /* @__PURE__ */ new Set([
848
856
  scope: "server",
849
857
  smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
850
858
  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",
851
- match: ({ file: e, path: t, serverOnly: n }) => (ft.test(t) || n) && F(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
859
+ match: ({ file: e, path: t, serverOnly: n }) => (mt.test(t) || n) && P(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
852
860
  },
853
861
  {
854
862
  id: "form",
855
863
  scope: "client",
856
864
  smell: "per-field useState + a submit flag (hand-rolled form)",
857
865
  use: "useFormBinding — fields + validation from the mutation input Schema; server ValidationError({ field }) routes to the field",
858
- match: ({ src: e, file: t }) => F(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
866
+ match: ({ src: e, file: t }) => P(t, /^useState$/) >= 4 && /onSubmit|handleSubmit|isSubmitting/.test(e)
859
867
  },
860
868
  {
861
869
  id: "table",
@@ -883,7 +891,7 @@ var le = /* @__PURE__ */ new Set([
883
891
  scope: "client",
884
892
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
885
893
  use: "useDerived — dependency-tracked, referentially stable",
886
- match: ({ src: e }) => ct(e, /\buseSubscription\s*[(<]/g) >= 3 && ct(e, /\buseMemo\s*\(/g) >= 3
894
+ match: ({ src: e }) => ut(e, /\buseSubscription\s*[(<]/g) >= 3 && ut(e, /\buseMemo\s*\(/g) >= 3
887
895
  },
888
896
  {
889
897
  id: "next-shim",
@@ -908,7 +916,7 @@ var le = /* @__PURE__ */ new Set([
908
916
  match: ({ file: e }) => e.getDescendantsOfKind(O.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
909
917
  let t = e.getFirstAncestorByKind(O.VariableDeclaration)?.getNameNode();
910
918
  return t === void 0 ? [] : E.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
911
- }).some((t) => dt(e, t).some((e) => {
919
+ }).some((t) => pt(e, t).some((e) => {
912
920
  let t = e.getParent();
913
921
  if (E.isBinaryExpression(t)) {
914
922
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -936,7 +944,7 @@ var le = /* @__PURE__ */ new Set([
936
944
  scope: "server",
937
945
  smell: "several sequential store reads assembling related data (an N+1 you are writing by hand)",
938
946
  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)",
939
- match: (e) => F(e.file, /\bstore\.query$/) >= 3 || F(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
947
+ match: (e) => P(e.file, /\bstore\.query$/) >= 3 || P(e.file, /\bstore\.query$/) >= 2 && /\bnew Map\s*\(/.test(e.src)
940
948
  },
941
949
  {
942
950
  id: "store-effect-lift",
@@ -959,7 +967,7 @@ var le = /* @__PURE__ */ new Set([
959
967
  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)",
960
968
  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) => {
961
969
  let n = t[1];
962
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || ht.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
970
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || _t.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
963
971
  }))
964
972
  },
965
973
  {
@@ -969,7 +977,7 @@ var le = /* @__PURE__ */ new Set([
969
977
  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",
970
978
  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) => {
971
979
  let n = t[1];
972
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || ht.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
980
+ return !/token|secret|credential|password|apikey|accesskey/i.test(n) || _t.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
973
981
  }))
974
982
  },
975
983
  {
@@ -1033,9 +1041,9 @@ var le = /* @__PURE__ */ new Set([
1033
1041
  scope: "server",
1034
1042
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
1035
1043
  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",
1036
- match: ({ file: e, testSources: t }) => F(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1037
- let t = P(e);
1038
- return ut(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1044
+ match: ({ file: e, testSources: t }) => P(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
1045
+ let t = N(e);
1046
+ return ft(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1039
1047
  })
1040
1048
  },
1041
1049
  {
@@ -1045,15 +1053,15 @@ var le = /* @__PURE__ */ new Set([
1045
1053
  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",
1046
1054
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
1047
1055
  }
1048
- ], _t = (e) => L(e), vt = (e, t) => {
1056
+ ], yt = (e) => I(e), bt = (e, t) => {
1049
1057
  let n = /* @__PURE__ */ new Set();
1050
1058
  for (let r of t) {
1051
1059
  let t = y(e, r), i = t.split(/[\\/]/)[0];
1052
1060
  n.add(i !== void 0 && i !== t ? i : ".");
1053
1061
  }
1054
1062
  return [...n].sort();
1055
- }, yt = /\.(tsx?|jsx?)$/, I = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, bt = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, xt = (e, t = [], n = 0) => {
1056
- if (n > 12 || I.test(e)) return t;
1063
+ }, xt = /\.(tsx?|jsx?)$/, F = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, St = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, Ct = (e, t = [], n = 0) => {
1064
+ if (n > 12 || F.test(e)) return t;
1057
1065
  let r;
1058
1066
  try {
1059
1067
  r = w(e, { withFileTypes: !0 });
@@ -1064,12 +1072,12 @@ var le = /* @__PURE__ */ new Set([
1064
1072
  let r = v(e, i.name);
1065
1073
  if (i.isDirectory()) {
1066
1074
  if (i.name.startsWith(".")) continue;
1067
- xt(r, t, n + 1);
1075
+ Ct(r, t, n + 1);
1068
1076
  } else je.test(i.name) && t.push(r);
1069
1077
  }
1070
1078
  return t;
1071
- }, L = (e, t = [], n = 0) => {
1072
- if (n > 12 || I.test(e)) return t;
1079
+ }, I = (e, t = [], n = 0) => {
1080
+ if (n > 12 || F.test(e)) return t;
1073
1081
  let r;
1074
1082
  try {
1075
1083
  r = w(e);
@@ -1083,11 +1091,11 @@ var le = /* @__PURE__ */ new Set([
1083
1091
  } catch {
1084
1092
  continue;
1085
1093
  }
1086
- a.isDirectory() ? L(r, t, n + 1) : yt.test(i) && !/\.(test|spec)\./.test(i) && !bt.test(i) && t.push(r);
1094
+ a.isDirectory() ? I(r, t, n + 1) : xt.test(i) && !/\.(test|spec)\./.test(i) && !St.test(i) && t.push(r);
1087
1095
  }
1088
1096
  return t;
1089
- }, St = (e, t = [], n = 0) => {
1090
- if (n > 12 || I.test(e)) return t;
1097
+ }, wt = (e, t = [], n = 0) => {
1098
+ if (n > 12 || F.test(e)) return t;
1091
1099
  let r;
1092
1100
  try {
1093
1101
  r = w(e);
@@ -1101,17 +1109,17 @@ var le = /* @__PURE__ */ new Set([
1101
1109
  } catch {
1102
1110
  continue;
1103
1111
  }
1104
- a.isDirectory() ? St(r, t, n + 1) : yt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1112
+ a.isDirectory() ? wt(r, t, n + 1) : xt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1105
1113
  }
1106
1114
  return t;
1107
- }, Ct = (e) => {
1108
- let t = _t(e);
1115
+ }, Tt = (e) => {
1116
+ let t = yt(e);
1109
1117
  return {
1110
- dirs: vt(e, t),
1118
+ dirs: bt(e, t),
1111
1119
  fileCount: t.length
1112
1120
  };
1113
- }, wt = (e) => {
1114
- let t = _t(e);
1121
+ }, Et = (e) => {
1122
+ let t = yt(e);
1115
1123
  if (t.length === 0) return [];
1116
1124
  let n = new D({
1117
1125
  useInMemoryFileSystem: !0,
@@ -1120,8 +1128,8 @@ var le = /* @__PURE__ */ new Set([
1120
1128
  for (let e of t) try {
1121
1129
  i.set(e, C(e, "utf8"));
1122
1130
  } catch {}
1123
- let a = st([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1124
- for (let t of St(e)) try {
1131
+ let a = lt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1132
+ for (let t of wt(e)) try {
1125
1133
  o.set(t, C(t, "utf8"));
1126
1134
  } catch {}
1127
1135
  for (let s of t) {
@@ -1134,28 +1142,30 @@ var le = /* @__PURE__ */ new Set([
1134
1142
  file: n.createSourceFile(`/scan${s.endsWith("x") ? ".tsx" : ".ts"}`, t, { overwrite: !0 }),
1135
1143
  path: y(e, s),
1136
1144
  serverOnly: a.has(s),
1137
- testSources: o
1145
+ testSources: o,
1146
+ sources: i,
1147
+ absPath: s
1138
1148
  };
1139
1149
  } catch {
1140
1150
  continue;
1141
1151
  }
1142
- for (let t of gt) {
1143
- if (t.plugin !== void 0 && lt(c, t.plugin) || !t.match(c)) continue;
1152
+ for (let t of vt) {
1153
+ if (t.plugin !== void 0 && dt(c, t.plugin) || !t.match(c)) continue;
1144
1154
  let n = r.get(t.id) ?? [];
1145
1155
  n.push(y(e, s)), r.set(t.id, n);
1146
1156
  }
1147
1157
  }
1148
- return gt.filter((e) => r.has(e.id)).map((e) => ({
1158
+ return vt.filter((e) => r.has(e.id)).map((e) => ({
1149
1159
  rule: e,
1150
1160
  files: r.get(e.id)
1151
1161
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1152
- }, R = (e) => {
1153
- let t = Ct(e);
1162
+ }, L = (e) => {
1163
+ let t = Tt(e);
1154
1164
  return {
1155
1165
  root: e,
1156
1166
  scannedFiles: t.fileCount,
1157
1167
  scannedDirs: t.dirs,
1158
- findings: wt(e).map(({ rule: e, files: t }) => ({
1168
+ findings: Et(e).map(({ rule: e, files: t }) => ({
1159
1169
  id: e.id,
1160
1170
  scope: e.scope,
1161
1171
  smell: e.smell,
@@ -1163,13 +1173,13 @@ var le = /* @__PURE__ */ new Set([
1163
1173
  files: t
1164
1174
  }))
1165
1175
  };
1166
- }, Tt = 3, Et = .2, Dt = 50, Ot = (e, t) => e >= Dt && t > 0 && e / t >= Et, kt = (e) => {
1176
+ }, Dt = 3, Ot = .2, kt = 50, At = (e, t) => e >= kt && t > 0 && e / t >= Ot, jt = (e) => {
1167
1177
  let t = /* @__PURE__ */ new Map();
1168
- for (let n of L(e)) try {
1178
+ for (let n of I(e)) try {
1169
1179
  t.set(y(e, n), C(n, "utf8"));
1170
1180
  } catch {}
1171
1181
  let n = /* @__PURE__ */ new Map();
1172
- for (let t of xt(e)) try {
1182
+ for (let t of Ct(e)) try {
1173
1183
  n.set(y(e, t), C(t, "utf8"));
1174
1184
  } catch {}
1175
1185
  let r = Me({
@@ -1177,8 +1187,8 @@ var le = /* @__PURE__ */ new Set([
1177
1187
  envFiles: n
1178
1188
  });
1179
1189
  r !== void 0 && (console.log(""), console.log(Ne(r)));
1180
- }, At = (e) => {
1181
- let t = R(e);
1190
+ }, Mt = (e) => {
1191
+ let t = L(e);
1182
1192
  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;
1183
1193
  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;
1184
1194
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
@@ -1189,24 +1199,24 @@ var le = /* @__PURE__ */ new Set([
1189
1199
  console.log(` [${e}]`);
1190
1200
  let i = [...r].sort((e, t) => e.files.length - t.files.length);
1191
1201
  for (let e of i) {
1192
- if (Ot(e.files.length, t.scannedFiles)) {
1202
+ if (At(e.files.length, t.scannedFiles)) {
1193
1203
  let n = Math.round(e.files.length / t.scannedFiles * 100);
1194
1204
  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.)");
1195
1205
  continue;
1196
1206
  }
1197
- let r = e.files.slice(0, Tt).join(", "), i = e.files.length - Tt;
1207
+ let r = e.files.slice(0, Dt).join(", "), i = e.files.length - Dt;
1198
1208
  i > 0 && (n += i);
1199
1209
  let a = i > 0 ? `, +${i} more` : "";
1200
1210
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1201
1211
  }
1202
1212
  }
1203
1213
  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;
1204
- }, 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) => {
1205
- let t = Mt.exec(e)?.[1];
1214
+ }, Nt = "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.", Pt = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, Ft = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, It = (e) => {
1215
+ let t = Pt.exec(e)?.[1];
1206
1216
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1207
- let n = Nt.exec(e)?.[1];
1217
+ let n = Ft.exec(e)?.[1];
1208
1218
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1209
- }, z = async (e) => {
1219
+ }, R = async (e) => {
1210
1220
  let t = v(e, "src", "pages");
1211
1221
  if (!S(t)) return [];
1212
1222
  let { pages: n, dirs: r } = await u(t), i = [];
@@ -1219,7 +1229,7 @@ var le = /* @__PURE__ */ new Set([
1219
1229
  continue;
1220
1230
  }
1221
1231
  if (c.test(n)) continue;
1222
- let s = Pt(n);
1232
+ let s = It(n);
1223
1233
  s !== null && i.push({
1224
1234
  file: y(e, v(t, a.file)),
1225
1235
  pattern: a.pattern,
@@ -1227,20 +1237,20 @@ var le = /* @__PURE__ */ new Set([
1227
1237
  });
1228
1238
  }
1229
1239
  return i;
1230
- }, Ft = 10, It = async (e) => {
1240
+ }, Lt = 10, Rt = async (e) => {
1231
1241
  console.log("");
1232
1242
  let t = v(e, "src", "pages");
1233
1243
  if (!S(t)) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
1234
- let n = await z(e);
1244
+ let n = await R(e);
1235
1245
  if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1236
1246
  console.log(`• renderMode:'spa' candidates (${n.length} page${n.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1237
- for (let e of n.slice(0, Ft)) {
1247
+ for (let e of n.slice(0, Lt)) {
1238
1248
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1239
1249
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1240
1250
  }
1241
- let r = n.length - Ft;
1242
- return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${jt}`), n.length;
1243
- }, Lt = /* @__PURE__ */ new Set([
1251
+ let r = n.length - Lt;
1252
+ return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${Nt}`), n.length;
1253
+ }, zt = /* @__PURE__ */ new Set([
1244
1254
  "node_modules",
1245
1255
  "dist",
1246
1256
  "build",
@@ -1248,7 +1258,7 @@ var le = /* @__PURE__ */ new Set([
1248
1258
  ".vite",
1249
1259
  ".framework",
1250
1260
  ".git"
1251
- ]), Rt = (e, t, n) => {
1261
+ ]), Bt = (e, t, n) => {
1252
1262
  let r;
1253
1263
  try {
1254
1264
  r = w(e, { withFileTypes: !0 });
@@ -1256,11 +1266,11 @@ var le = /* @__PURE__ */ new Set([
1256
1266
  return;
1257
1267
  }
1258
1268
  for (let i of r) {
1259
- if (Lt.has(i.name)) continue;
1269
+ if (zt.has(i.name)) continue;
1260
1270
  let r = v(e, i.name);
1261
- i.isDirectory() ? Rt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1271
+ i.isDirectory() ? Bt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1262
1272
  }
1263
- }, zt = (e, t) => {
1273
+ }, Vt = (e, t) => {
1264
1274
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1265
1275
  for (let i of t) {
1266
1276
  let t;
@@ -1285,16 +1295,16 @@ var le = /* @__PURE__ */ new Set([
1285
1295
  }
1286
1296
  }
1287
1297
  return n;
1288
- }, Bt = (e, t) => {
1298
+ }, Ht = (e, t) => {
1289
1299
  let n = e.slice(t.length).split(x).slice(0, -1)[0];
1290
1300
  return n !== void 0 && n.startsWith("[");
1291
- }, B = (e) => {
1301
+ }, z = (e) => {
1292
1302
  let t = [], n = v(e, "src", "pages"), r = v(e, "src");
1293
1303
  if (!S(r)) return t;
1294
1304
  let i = [];
1295
- Rt(r, e, i), i.sort();
1305
+ Bt(r, e, i), i.sort();
1296
1306
  let a = `${y(e, n)}${x}`;
1297
- zt(e, i);
1307
+ Vt(e, i);
1298
1308
  for (let n of i) {
1299
1309
  let r = n.slice(n.lastIndexOf(x) + 1);
1300
1310
  if (n.startsWith(a)) {
@@ -1310,7 +1320,7 @@ var le = /* @__PURE__ */ new Set([
1310
1320
  file: n
1311
1321
  });
1312
1322
  } catch {}
1313
- Bt(n, a) && t.push({
1323
+ Ht(n, a) && t.push({
1314
1324
  kind: "top-level-dynamic-route",
1315
1325
  file: n
1316
1326
  });
@@ -1322,35 +1332,35 @@ var le = /* @__PURE__ */ new Set([
1322
1332
  });
1323
1333
  }
1324
1334
  return t;
1325
- }, Vt = 10, V = (e, t, n) => {
1335
+ }, Ut = 10, B = (e, t, n) => {
1326
1336
  if (t.length === 0) return;
1327
1337
  console.log(`• ${e} (${t.length}):`);
1328
- for (let e of t.slice(0, Vt)) console.log(` ${e}`);
1329
- let r = t.length - Vt;
1338
+ for (let e of t.slice(0, Ut)) console.log(` ${e}`);
1339
+ let r = t.length - Ut;
1330
1340
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
1331
- }, Ht = (e) => {
1341
+ }, Wt = (e) => {
1332
1342
  console.log("");
1333
1343
  let t = v(e, "src", "pages");
1334
1344
  if (!S(t)) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
1335
- let n = B(e);
1345
+ let n = z(e);
1336
1346
  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;
1337
1347
  let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
1338
- return V("will NEVER route — page.tsx outside src/pages/", r("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), V("pages with no test beside them", r("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), V("routes whose FIRST segment is dynamic — they claim every URL of that depth", r("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), V("pages with no `meta` export — they serve the APP name as their title", r("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). If such a page also has no real body — it redirects from an effect, say — the missing title is the symptom rather than the bug: on an `ssr` route the server answers 200 with an empty document and the redirect happens only once JavaScript runs. Move it into the loader (`redirect(...)`, a real 303). Advisory."), n.length;
1339
- }, 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(), H = (e) => {
1340
- let t = Wt.get(e);
1348
+ return B("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."), B("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."), B("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."), B("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
+ }, Gt = "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.", Kt = /* @__PURE__ */ new Map(), V = (e) => {
1350
+ let t = Kt.get(e);
1341
1351
  if (t !== void 0) return t;
1342
1352
  let n = (async () => {
1343
- let { walk: t, loadDiscovered: n } = await import("./dev-DuqBJC7g.js"), r = await t(e);
1353
+ let { walk: t, loadDiscovered: n } = await import("./dev-D0E5x2Ue.js"), r = await t(e);
1344
1354
  return {
1345
1355
  files: r,
1346
1356
  discovered: await n(r)
1347
1357
  };
1348
1358
  })();
1349
- return Wt.set(e, n), n;
1350
- }, Gt = async (e) => {
1359
+ return Kt.set(e, n), n;
1360
+ }, qt = async (e) => {
1351
1361
  let t;
1352
1362
  try {
1353
- let { files: n, discovered: r } = await H(e), i = r.tables.map((e) => {
1363
+ let { files: n, discovered: r } = await V(e), i = r.tables.map((e) => {
1354
1364
  let t = e;
1355
1365
  return {
1356
1366
  name: t.tableName,
@@ -1363,7 +1373,7 @@ var le = /* @__PURE__ */ new Set([
1363
1373
  return;
1364
1374
  }
1365
1375
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
1366
- }, Kt = (e) => e.map((e) => {
1376
+ }, Jt = (e) => e.map((e) => {
1367
1377
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
1368
1378
  name: e,
1369
1379
  type: typeof t?.type == "string" ? t.type : ""
@@ -1373,21 +1383,21 @@ var le = /* @__PURE__ */ new Set([
1373
1383
  columns: n,
1374
1384
  primaryKey: t.appliedPrimaryKey ?? []
1375
1385
  };
1376
- }), U = async (e) => {
1386
+ }), H = async (e) => {
1377
1387
  try {
1378
- let { discovered: t } = await H(e);
1379
- return ve(Kt(t.tables));
1388
+ let { discovered: t } = await V(e);
1389
+ return ve(Jt(t.tables));
1380
1390
  } catch {
1381
1391
  return null;
1382
1392
  }
1383
- }, qt = async (e) => {
1384
- let t = await U(e);
1393
+ }, Yt = async (e) => {
1394
+ let t = await H(e);
1385
1395
  if (t === null || t.tablesChecked === 0) return;
1386
1396
  let n = be(t);
1387
1397
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
1388
- }, W = async (e) => {
1398
+ }, U = async (e) => {
1389
1399
  try {
1390
- let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await H(e);
1400
+ let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await V(e);
1391
1401
  return we(n.events.map((e) => {
1392
1402
  let n = e.descriptor, r = t(n.key);
1393
1403
  return {
@@ -1400,19 +1410,19 @@ var le = /* @__PURE__ */ new Set([
1400
1410
  } catch {
1401
1411
  return null;
1402
1412
  }
1403
- }, Jt = async (e) => {
1404
- let t = await W(e);
1413
+ }, Xt = async (e) => {
1414
+ let t = await U(e);
1405
1415
  if (t === null || t.eventsChecked === 0) return;
1406
1416
  let n = Te(t);
1407
1417
  n !== null && (console.log(""), console.log(`• ${n}`));
1408
1418
  let r = Ee(t);
1409
1419
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
1410
- }, G = (e) => {
1420
+ }, W = (e) => {
1411
1421
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
1412
1422
  return r === void 0 ? n : `${r}: ${n}`;
1413
- }, K = async (e) => {
1423
+ }, G = async (e) => {
1414
1424
  try {
1415
- let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await H(e)).discovered.serverOnlyLeaks, r = t(n);
1425
+ let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await V(e)).discovered.serverOnlyLeaks, r = t(n);
1416
1426
  return {
1417
1427
  status: {
1418
1428
  checked: !0,
@@ -1423,12 +1433,12 @@ var le = /* @__PURE__ */ new Set([
1423
1433
  } catch (e) {
1424
1434
  return { status: {
1425
1435
  checked: !1,
1426
- reason: G(e)
1436
+ reason: W(e)
1427
1437
  } };
1428
1438
  }
1429
- }, q = async (r) => {
1439
+ }, K = async (r) => {
1430
1440
  try {
1431
- let { loadApiConfig: i } = await import("./dev-DuqBJC7g.js"), { discovered: a } = await H(r), o = n(await i(r)), s = {
1441
+ let { loadApiConfig: i } = await import("./dev-D0E5x2Ue.js"), { discovered: a } = await V(r), o = n(await i(r)), s = {
1432
1442
  queries: a.queries,
1433
1443
  mutations: a.mutations,
1434
1444
  actions: a.actions,
@@ -1444,26 +1454,26 @@ var le = /* @__PURE__ */ new Set([
1444
1454
  } catch (e) {
1445
1455
  return {
1446
1456
  checked: !1,
1447
- reason: G(e),
1457
+ reason: W(e),
1448
1458
  defaultDeny: !0,
1449
1459
  undecided: [],
1450
1460
  openAccess: []
1451
1461
  };
1452
1462
  }
1453
- }, Yt = async (e) => {
1454
- let t = await q(e);
1463
+ }, Zt = async (e) => {
1464
+ let t = await K(e);
1455
1465
  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;
1456
1466
  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)}`);
1457
1467
  for (let e of t.openAccess) console.log(` ${e.tag} — ${e.openAccess ?? ""}`);
1458
1468
  if (t.undecided.length === 0) return !1;
1459
1469
  console.log("");
1460
1470
  for (let e of t.undecided) console.log(` ✗ ${e.tag} (${e.kind})${e.file === void 0 ? "" : `\n ${e.file}`}`);
1461
- return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), Xt(), !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."), Xt(), !1);
1462
- }, Xt = () => {
1471
+ return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), Qt(), !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."), Qt(), !1);
1472
+ }, Qt = () => {
1463
1473
  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");
1464
- }, J = async (e) => {
1474
+ }, q = async (e) => {
1465
1475
  try {
1466
- let { files: t } = await H(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
1476
+ let { files: t } = await V(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(ie(e)));
1467
1477
  if (a.length === 0) return {
1468
1478
  checked: !0,
1469
1479
  unverified: [],
@@ -1490,14 +1500,14 @@ var le = /* @__PURE__ */ new Set([
1490
1500
  } catch (e) {
1491
1501
  return {
1492
1502
  checked: !1,
1493
- reason: G(e),
1503
+ reason: W(e),
1494
1504
  unverified: [],
1495
1505
  open: [],
1496
1506
  verified: 0
1497
1507
  };
1498
1508
  }
1499
- }, Zt = async (e) => {
1500
- let t = await J(e);
1509
+ }, $t = async (e) => {
1510
+ let t = await q(e);
1501
1511
  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;
1502
1512
  if (t.verified + t.open.length + t.unverified.length === 0) return !1;
1503
1513
  console.log(""), console.log("incoming webhook verification"), console.log(` ✓ signature / provider verified ${String(t.verified).padStart(4)}`), console.log(` ! deliberately public (none) ${String(t.open.length).padStart(4)}`), console.log(` ✗ nothing verifies the caller ${String(t.unverified.length).padStart(4)}`);
@@ -1506,13 +1516,13 @@ var le = /* @__PURE__ */ new Set([
1506
1516
  console.log("");
1507
1517
  for (let e of t.unverified) console.log(` ✗ ${e.id}\n ${e.file}`);
1508
1518
  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;
1509
- }, Qt = async (e) => {
1510
- let { status: t, message: n } = await K(e);
1519
+ }, en = async (e) => {
1520
+ let { status: t, message: n } = await G(e);
1511
1521
  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);
1512
- }, $t = async (e) => et({
1513
- hasWorkflows: async () => (await H(e)).discovered.workflows.length > 0,
1522
+ }, tn = async (e) => tt({
1523
+ hasWorkflows: async () => (await V(e)).discovered.workflows.length > 0,
1514
1524
  openStore: async () => {
1515
- let { loadApiConfig: t, buildStore: n } = await import("./dev-DuqBJC7g.js"), r = await t(e);
1525
+ let { loadApiConfig: t, buildStore: n } = await import("./dev-D0E5x2Ue.js"), r = await t(e);
1516
1526
  if (r === null) throw Error("no app.config.ts in this directory");
1517
1527
  let i = await n(r, void 0);
1518
1528
  return {
@@ -1526,13 +1536,13 @@ var le = /* @__PURE__ */ new Set([
1526
1536
  let { sweepStalledRuns: t } = await import("@voltro/workflow");
1527
1537
  return t({ store: e });
1528
1538
  }
1529
- }), en = async (e) => {
1530
- let t = nt(await $t(e));
1539
+ }), nn = async (e) => {
1540
+ let t = rt(await tn(e));
1531
1541
  t !== null && (console.log(""), console.log(t));
1532
- }, tn = async (e) => {
1542
+ }, rn = async (e) => {
1533
1543
  let t;
1534
1544
  try {
1535
- let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await H(e);
1545
+ let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await V(e);
1536
1546
  t = ke(r, i.workflows.map((e) => {
1537
1547
  let t = e.definition, r = t.payloadSchema === void 0 ? {
1538
1548
  required: [],
@@ -1552,18 +1562,18 @@ var le = /* @__PURE__ */ new Set([
1552
1562
  console.log("");
1553
1563
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
1554
1564
  console.log(`${r} ${n}`);
1555
- }, Y = "voltro-authz-allowlist.txt", nn = (e, t = (e) => {
1565
+ }, J = "voltro-authz-allowlist.txt", an = (e, t = (e) => {
1556
1566
  try {
1557
1567
  return C(e, "utf8");
1558
1568
  } catch {
1559
1569
  return "";
1560
1570
  }
1561
- }) => /* @__PURE__ */ new Set([...M, ...ze(L(e).map(t))]), rn = (e, t) => {
1571
+ }) => /* @__PURE__ */ new Set([...ze, ...Be(I(e).map(t))]), Y = (e, t) => {
1562
1572
  let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1563
1573
  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];
1564
1574
  }, X = async (e) => {
1565
1575
  try {
1566
- let { discovered: t } = await H(e), n = t.events.map((e) => ({
1576
+ let { discovered: t } = await V(e), n = t.events.map((e) => ({
1567
1577
  name: e.descriptor.name,
1568
1578
  file: e.file
1569
1579
  })).filter((e) => typeof e.name == "string");
@@ -1574,25 +1584,25 @@ var le = /* @__PURE__ */ new Set([
1574
1584
  } catch {
1575
1585
  return "";
1576
1586
  }
1577
- }, a = L(e).filter((e) => !r.has(e)).map(i).join("\n");
1587
+ }, a = I(e).filter((e) => !r.has(e)).map(i).join("\n");
1578
1588
  return n.filter(({ name: e, file: t }) => {
1579
1589
  if (a.includes(e)) return !1;
1580
- let n = rn(i(t), e);
1590
+ let n = Y(i(t), e);
1581
1591
  return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
1582
1592
  }).map((e) => e.name).sort();
1583
1593
  } catch {
1584
1594
  return [];
1585
1595
  }
1586
- }, an = async (e) => {
1596
+ }, on = async (e) => {
1587
1597
  let t = await X(e);
1588
1598
  if (t.length !== 0) {
1589
1599
  console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
1590
1600
  for (let e of t) console.log(` ${e}`);
1591
1601
  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.");
1592
1602
  }
1593
- }, on = async (e) => {
1603
+ }, sn = async (e) => {
1594
1604
  try {
1595
- let { loadApiConfig: t } = await import("./dev-DuqBJC7g.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1605
+ let { loadApiConfig: t } = await import("./dev-D0E5x2Ue.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1596
1606
  if (typeof n != "string") return [];
1597
1607
  let [r, i] = n.split("#");
1598
1608
  if (r === void 0 || i === void 0) return [];
@@ -1609,7 +1619,7 @@ var le = /* @__PURE__ */ new Set([
1609
1619
  findings: []
1610
1620
  });
1611
1621
  try {
1612
- let { offlineManifest: n, toInput: r } = await import("./checkCommand-B2RWrpWi.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-zuMGKVYX.js"), o = await n(e), s = r(o.manifest, o.tables), c = await on(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1622
+ let { offlineManifest: n, toInput: r } = await import("./checkCommand-DBpTSK8d.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-zuMGKVYX.js"), o = await n(e), s = r(o.manifest, o.tables), c = await sn(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
1613
1623
  if (l.length === 0) {
1614
1624
  try {
1615
1625
  let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-DgylYh9_.js");
@@ -1636,7 +1646,7 @@ var le = /* @__PURE__ */ new Set([
1636
1646
  } catch {
1637
1647
  return t("app-not-importable");
1638
1648
  }
1639
- }, sn = async (e) => {
1649
+ }, cn = async (e) => {
1640
1650
  let { findings: t, evaluated: n, notEvaluated: r } = await Z(e);
1641
1651
  if (!n) {
1642
1652
  if (console.log(""), r === "app-not-importable") {
@@ -1651,7 +1661,7 @@ var le = /* @__PURE__ */ new Set([
1651
1661
  for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
1652
1662
  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}`);
1653
1663
  }
1654
- }, cn = async (e) => {
1664
+ }, ln = async (e) => {
1655
1665
  let t = ["src/locales", "locales"], n, r = [];
1656
1666
  for (let i of t) {
1657
1667
  let t = v(e, i);
@@ -1663,7 +1673,7 @@ var le = /* @__PURE__ */ new Set([
1663
1673
  if (n === void 0 || r.length === 0) return;
1664
1674
  let i = [];
1665
1675
  try {
1666
- let { loadApiConfig: t } = await import("./dev-DuqBJC7g.js");
1676
+ let { loadApiConfig: t } = await import("./dev-D0E5x2Ue.js");
1667
1677
  i = (await t(e))?.locales ?? [];
1668
1678
  } catch {
1669
1679
  return;
@@ -1676,12 +1686,12 @@ var le = /* @__PURE__ */ new Set([
1676
1686
  console.log("");
1677
1687
  for (let e of Fe(a, n)) console.log(e);
1678
1688
  }
1679
- }, ln = async (e) => {
1689
+ }, un = async (e) => {
1680
1690
  let t = v(e, "src", "pages");
1681
1691
  if (!S(t)) return !1;
1682
1692
  let n = [];
1683
1693
  try {
1684
- let { walkPagesTree: e } = await import("./webDev-D6QeXw3P.js");
1694
+ let { walkPagesTree: e } = await import("./webDev-BLKBO34K.js");
1685
1695
  n = (await e(t)).pages.map((e) => e.pattern);
1686
1696
  } catch {
1687
1697
  return !1;
@@ -1691,10 +1701,10 @@ var le = /* @__PURE__ */ new Set([
1691
1701
  console.log("");
1692
1702
  for (let e of Re(r)) console.log(e);
1693
1703
  return r.problems.length > 0;
1694
- }, un = async (e) => {
1695
- let { discovered: t } = await H(e), n = [];
1704
+ }, dn = async (e) => {
1705
+ let { discovered: t } = await V(e), n = [];
1696
1706
  try {
1697
- let { loadApiConfig: t } = await import("./dev-DuqBJC7g.js");
1707
+ let { loadApiConfig: t } = await import("./dev-D0E5x2Ue.js");
1698
1708
  n = ((await t(e))?.plugins ?? []).map((e) => {
1699
1709
  let t = (e.extendSchema?.tables ?? []).map((e) => {
1700
1710
  let t = e;
@@ -1707,8 +1717,8 @@ var le = /* @__PURE__ */ new Set([
1707
1717
  plugin: e.name,
1708
1718
  tables: t,
1709
1719
  rpcTags: n,
1710
- canDisableTables: dn.has(r),
1711
- canAlias: fn.has(r)
1720
+ canDisableTables: fn.has(r),
1721
+ canAlias: pn.has(r)
1712
1722
  };
1713
1723
  });
1714
1724
  } catch {
@@ -1720,18 +1730,18 @@ var le = /* @__PURE__ */ new Set([
1720
1730
  ...t.mutations,
1721
1731
  ...t.actions,
1722
1732
  ...t.streams ?? []
1723
- ].map((e) => String(e.descriptor.name)), i = Qe(n, {
1733
+ ].map((e) => String(e.descriptor.name)), i = $e(n, {
1724
1734
  tables: t.tables.map((e) => e.tableName),
1725
1735
  rpcTags: r
1726
1736
  });
1727
1737
  console.log();
1728
- for (let e of $e(n, i)) console.log(e);
1729
- }, dn = /* @__PURE__ */ new Set([
1738
+ for (let e of et(n, i)) console.log(e);
1739
+ }, fn = /* @__PURE__ */ new Set([
1730
1740
  "@voltro/plugin-rbac",
1731
1741
  "@voltro/plugin-audit",
1732
1742
  "@voltro/plugin-notifications",
1733
1743
  "aiFlows"
1734
- ]), fn = /* @__PURE__ */ new Set([
1744
+ ]), pn = /* @__PURE__ */ new Set([
1735
1745
  "aiFlows",
1736
1746
  "@voltro/plugin-audit",
1737
1747
  "@voltro/plugin-billing",
@@ -1748,7 +1758,7 @@ var le = /* @__PURE__ */ new Set([
1748
1758
  "@voltro/plugin-sso-saml",
1749
1759
  "@voltro/plugin-storage",
1750
1760
  "@voltro/plugin-versioning"
1751
- ]), Q = 20, pn = async (e) => {
1761
+ ]), Q = 20, mn = async (e) => {
1752
1762
  let t = await $(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;
1753
1763
  return {
1754
1764
  executors: t.executorCount,
@@ -1762,14 +1772,14 @@ var le = /* @__PURE__ */ new Set([
1762
1772
  unchecked: t.result.unchecked
1763
1773
  };
1764
1774
  }, $ = async (e) => {
1765
- let { discovered: t } = await H(e), n = /* @__PURE__ */ new Map(), r = (e) => {
1775
+ let { discovered: t } = await V(e), n = /* @__PURE__ */ new Map(), r = (e) => {
1766
1776
  if (!n.has(e)) try {
1767
1777
  n.set(e, C(e, "utf8"));
1768
1778
  } catch {
1769
1779
  n.set(e, "");
1770
1780
  }
1771
1781
  return n.get(e);
1772
- }, i = nn(e, r), a = /* @__PURE__ */ new Set();
1782
+ }, i = an(e, r), a = /* @__PURE__ */ new Set();
1773
1783
  for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
1774
1784
  let e = t.references?.();
1775
1785
  e && a.add(e.tableName);
@@ -1801,12 +1811,12 @@ var le = /* @__PURE__ */ new Set([
1801
1811
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
1802
1812
  ...a?.table === void 0 ? {} : { table: a.table }
1803
1813
  };
1804
- })), s = v(e, Y), c = S(s) ? N(C(s, "utf8")) : void 0, l = !1;
1805
- for (let t of L(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
1814
+ })), s = v(e, J), c = S(s) ? M(C(s, "utf8")) : void 0, l = !1;
1815
+ for (let t of I(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
1806
1816
  l = !0;
1807
1817
  break;
1808
1818
  }
1809
- let u = Ge({
1819
+ let u = Ke({
1810
1820
  executors: o,
1811
1821
  guardVocabulary: i,
1812
1822
  tenantScopedTables: t.tenantScopedTables,
@@ -1823,46 +1833,46 @@ var le = /* @__PURE__ */ new Set([
1823
1833
  executorCount: o.length,
1824
1834
  allowPath: s
1825
1835
  };
1826
- }, mn = async (e, t) => {
1836
+ }, hn = async (e, t) => {
1827
1837
  let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await $(e), l = n.counts;
1828
1838
  console.log(`\nauthz scan · ${o} executor(s)`);
1829
- let u = [...r].filter((e) => !M.has(e)).sort();
1839
+ let u = [...r].filter((e) => !ze.has(e)).sort();
1830
1840
  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)}`);
1831
1841
  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;
1832
- if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${Y})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
1833
- let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ye(e, t);
1834
- return oe(s, Je(e, t), "utf8"), console.log(`\n${Y}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
1842
+ if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${J})`), 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) {
1843
+ let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Xe(e, t);
1844
+ return oe(s, Ye(e, t), "utf8"), console.log(`\n${J}: +${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;
1835
1845
  }
1836
1846
  if (n.unchecked.length === 0) return !1;
1837
1847
  console.log("");
1838
1848
  for (let e of n.unchecked.slice(0, Q)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
1839
- return n.unchecked.length > Q && console.log(` … and ${n.unchecked.length - Q} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${Y} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${Y}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
1840
- }, hn = async (e) => {
1841
- if (e.includes("--help") || e.includes("-h")) return console.log(Ut), 0;
1849
+ return n.unchecked.length > Q && console.log(` … and ${n.unchecked.length - Q} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${J} 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 ${J}.`), 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);
1850
+ }, gn = async (e) => {
1851
+ if (e.includes("--help") || e.includes("-h")) return console.log(Gt), 0;
1842
1852
  let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), o = b(n ?? process.cwd());
1843
1853
  if (g(e)) return ee(), h({
1844
- ...R(o),
1845
- spaCandidates: await z(o),
1846
- pageConvention: B(o),
1854
+ ...L(o),
1855
+ spaCandidates: await R(o),
1856
+ pageConvention: z(o),
1847
1857
  fileTaxonomy: re(o).findings,
1848
- junctionFks: (await U(o))?.issues ?? [],
1849
- eventDelivery: await W(o),
1858
+ junctionFks: (await H(o))?.issues ?? [],
1859
+ eventDelivery: await U(o),
1850
1860
  duplicateInstances: i(o),
1851
1861
  versionSkew: r(o) ?? null,
1852
- serverOnly: (await K(o)).status,
1853
- accessDecisions: await q(o),
1854
- webhookVerification: await J(o),
1855
- authz: await pn(o),
1862
+ serverOnly: (await G(o)).status,
1863
+ accessDecisions: await K(o),
1864
+ webhookVerification: await q(o),
1865
+ authz: await mn(o),
1856
1866
  ungrantedScopes: await Z(o),
1857
1867
  neverEmittedEvents: await X(o),
1858
- stalledRuns: await $t(o)
1868
+ stalledRuns: await tn(o)
1859
1869
  }), 0;
1860
- let c = ot(o, t);
1861
- At(o), kt(o), Ht(o), ne(o), await It(o), await tn(o), await en(o), await Gt(o), await qt(o), await Jt(o), await un(o), await sn(o), await an(o), await cn(o);
1862
- let l = await Qt(o), u = await mn(o, e.includes("--write-authz-allowlist")), d = await Yt(o), f = await Zt(o), p = await ln(o), m = s(r(o));
1870
+ let c = st(o, t);
1871
+ Mt(o), jt(o), Wt(o), ne(o), await Rt(o), await rn(o), await nn(o), await qt(o), await Yt(o), await Xt(o), await dn(o), await cn(o), await on(o), await ln(o);
1872
+ let l = await en(o), u = await hn(o, e.includes("--write-authz-allowlist")), d = await Zt(o), f = await $t(o), p = await un(o), m = s(r(o));
1863
1873
  m !== void 0 && (console.log(""), console.log(m));
1864
1874
  let _ = a(i(o));
1865
1875
  return _ !== void 0 && console.log(`\n${_}`), l || u || d || p || f ? Math.max(c, 1) : c;
1866
1876
  };
1867
1877
  //#endregion
1868
- export { K as C, z as S, J as T, It as _, pn as a, wt as b, W as c, Ct as d, it as f, Ht as g, At as h, q as i, nn as l, kt as m, at as n, X as o, U as p, jt as r, rn as s, Ut as t, R as u, hn as v, Z as w, B as x, ot as y };
1878
+ export { G as C, R as S, q as T, Rt as _, mn as a, Et as b, U as c, Tt as d, at as f, Wt as g, Mt as h, K as i, an as l, jt as m, ot as n, X as o, H as p, Nt as r, Y as s, Gt as t, L as u, gn as v, Z as w, z as x, st as y };