@voltro/cli 0.39.1 → 0.41.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 (110) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/dist/agentsMd-Bu_XQgVf.js +2 -0
  3. package/dist/{agentsMd-7zI2h5l9.js → agentsMd-D6yD7IQv.js} +31 -14
  4. package/dist/{apiBuild-D9FB13_2.js → apiBuild-B_iH24qf.js} +2 -2
  5. package/dist/apiBuild-RM5W5YXk.js +2 -0
  6. package/dist/bin.js +1 -1
  7. package/dist/{build-DORV5mLM.js → build-DUdi6F_Q.js} +100 -97
  8. package/dist/{checkCommand-D9qK_gtA.js → checkCommand-B2RWrpWi.js} +1 -1
  9. package/dist/{checkCommand-Bf6oMao4.js → checkCommand-DWL2_PPR.js} +2 -2
  10. package/dist/{cloudCmd-C42gaO8s.js → cloudCmd-F4YJeqM3.js} +1 -1
  11. package/dist/{codegenCommand-CxOyKmc1.js → codegenCommand-DkjoJ30Q.js} +2 -2
  12. package/dist/{codemodRunner-D-jTyvWo.js → codemodRunner-sFuWDETH.js} +520 -357
  13. package/dist/{commands-D6aW_6AR.js → commands-D0VR1yBB.js} +32 -32
  14. package/dist/{dashboardCommand-CwTZh-XZ.js → dashboardCommand-Bro7Jwts.js} +3 -3
  15. package/dist/{dataCommand-Cc9RiLQZ.js → dataCommand-OKC5FscP.js} +183 -172
  16. package/dist/{dbCommand-DSwGv9wS.js → dbCommand-BHRamDsy.js} +441 -414
  17. package/dist/dbCommand-C9iE1pd9.js +2 -0
  18. package/dist/{dev-3mgc8c-H.js → dev-D4l_YOzI.js} +1570 -1570
  19. package/dist/{dev-CjJAasDs.js → dev-DuqBJC7g.js} +1 -1
  20. package/dist/doctorCommand-Bw3V4L1j.js +2 -0
  21. package/dist/{doctorCommand-oLuzc-aC.js → doctorCommand-TmZTAepc.js} +386 -280
  22. package/dist/{dormancyCommand-Bxe4WtHU.js → dormancyCommand-CUKhRmGG.js} +1 -1
  23. package/dist/{embeddingsCommand-CyNQkFIc.js → embeddingsCommand-Db-iQM3l.js} +1 -1
  24. package/dist/{envCommand-CAkSgBbh.js → envCommand-DQ_URZq9.js} +14 -14
  25. package/dist/{evolveCommand-CWhBoe_P.js → evolveCommand-CghRPrGO.js} +2 -2
  26. package/dist/index.js +1 -1
  27. package/dist/{infoCommand-CTo8Jnhq.js → infoCommand-BCs_ZFnl.js} +1 -1
  28. package/dist/{metaCommands-DUYR--Ts.js → metaCommands-CfRLra0s.js} +1 -1
  29. package/dist/{migrate-BnPw2zC8.js → migrate-DLtPv2_D.js} +1 -1
  30. package/dist/{projectScaffold-BEhhHjPr.js → projectScaffold-BIl97_E6.js} +1 -1
  31. package/dist/{projectScaffold-BvhLOrLq.js → projectScaffold-mIX_DpSe.js} +1 -1
  32. package/dist/{runtimeTrace-gNXeLf6H.js → runtimeTrace-jFKvOPBz.js} +1 -1
  33. package/dist/{sdkgen-C8zz10-X.js → sdkgen-COQqHB9V.js} +1 -1
  34. package/dist/{serveCommand-BpK8-m1N.js → serveCommand-Cba_M2Su.js} +656 -656
  35. package/dist/serveCommand-DRbYK646.js +2 -0
  36. package/dist/serveEntry.js +1 -1
  37. package/dist/{start-ft_KzFTd.js → start-BjcuFME_.js} +421 -361
  38. package/dist/{start-BNuTWdRd.js → start-fifc0xpW.js} +1 -1
  39. package/dist/startEntry.js +1 -1
  40. package/dist/updateCommand-B2alql9k.js +2 -0
  41. package/dist/{updateCommand-CHBmCB17.js → updateCommand-HZQVBU3T.js} +1 -1
  42. package/dist/webDev-D6QeXw3P.js +2 -0
  43. package/dist/{webDev-CTpSY-e_.js → webDev-Dk8lFUzf.js} +672 -501
  44. package/dist/{webhooksCommand-EccPZQZ4.js → webhooksCommand-D7ZpH7w0.js} +1 -1
  45. package/package.json +35 -17
  46. package/templates/AGENTS.md +1 -1
  47. package/templates/agent-docs/_index.md +1 -1
  48. package/templates/agent-docs/_manifest.json +1 -1
  49. package/templates/agent-docs/cli.md +6 -0
  50. package/templates/agent-docs/data.md +43 -0
  51. package/templates/agent-docs/plugins/governance.md +8 -0
  52. package/templates/agent-docs/routing.md +153 -0
  53. package/templates/agent-docs/security.md +24 -0
  54. package/templates/agent-docs/whats-new.md +49 -27
  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-durable/tests/orders.place.test.ts +17 -4
  68. package/templates/apps/api-feature-flags/package.json +9 -9
  69. package/templates/apps/api-governance/package.json +8 -8
  70. package/templates/apps/api-kv/package.json +8 -8
  71. package/templates/apps/api-moderation/package.json +8 -8
  72. package/templates/apps/api-observability/package.json +8 -8
  73. package/templates/apps/api-ratelimit/package.json +8 -8
  74. package/templates/apps/api-rbac/package.json +8 -8
  75. package/templates/apps/api-rest/package.json +7 -7
  76. package/templates/apps/api-saas/package.json +11 -11
  77. package/templates/apps/api-saas-starter/package.json +10 -10
  78. package/templates/apps/api-search/package.json +8 -8
  79. package/templates/apps/api-status/package.json +8 -8
  80. package/templates/apps/api-versioning/package.json +8 -8
  81. package/templates/apps/api-webhooks/package.json +9 -9
  82. package/templates/apps/changelog/package.json +6 -6
  83. package/templates/apps/changelog/src/pages/[slug]/page.test.tsx +8 -1
  84. package/templates/apps/edge-functions/package.json +2 -2
  85. package/templates/apps/frontend-admin/package.json +8 -8
  86. package/templates/apps/frontend-app/package.json +9 -9
  87. package/templates/apps/frontend-auth/package.json +8 -8
  88. package/templates/apps/frontend-blank/package.json +7 -7
  89. package/templates/apps/frontend-cms/package.json +9 -9
  90. package/templates/apps/frontend-collab/package.json +10 -10
  91. package/templates/apps/frontend-contact/package.json +7 -7
  92. package/templates/apps/frontend-dashboard/package.json +7 -7
  93. package/templates/apps/frontend-docs/package.json +7 -7
  94. package/templates/apps/frontend-i18n/package.json +6 -6
  95. package/templates/apps/frontend-landing/package.json +7 -7
  96. package/templates/apps/frontend-portal/package.json +8 -8
  97. package/templates/apps/frontend-saas/package.json +8 -8
  98. package/templates/apps/frontend-spa/package.json +7 -7
  99. package/templates/apps/frontend-ssr/package.json +7 -7
  100. package/templates/apps/frontend-ssr-api/package.json +8 -8
  101. package/templates/apps/frontend-static-blog/package.json +6 -6
  102. package/templates/apps/frontend-status/package.json +8 -8
  103. package/templates/apps/mobile-app/package.json +4 -4
  104. package/dist/agentsMd-BFCXh2gl.js +0 -2
  105. package/dist/apiBuild-BmR_bIxN.js +0 -2
  106. package/dist/dbCommand-DcqEyxju.js +0 -2
  107. package/dist/doctorCommand-Cghssndo.js +0 -2
  108. package/dist/serveCommand-CW-goXvh.js +0 -2
  109. package/dist/updateCommand-6FMU2klq.js +0 -2
  110. package/dist/webDev-id3I5PvG.js +0 -2
@@ -1,18 +1,18 @@
1
- import { Bt as e, Ht as t, Vt as n } from "./dev-3mgc8c-H.js";
2
- import { $ as r, Q as i, X as a, Z as o, s, t as c, y as l } from "./webDev-CTpSY-e_.js";
3
- import { w as u } from "./frameworkTableAssembly-oMPBKqlE.js";
4
- import { n as d } from "./pageConvention-cEiRxdab.js";
5
- import { r as f, u as p } from "./apiBuild-D9FB13_2.js";
6
- import { n as m, r as h, t as g } from "./cliOutput-D1tSBoRM.js";
7
- import { n as _, t as ee } from "./tsconfigPaths-BWXBWgcl.js";
8
- import { n as te, r as ne } from "./fileTaxonomy-CJfgOllU.js";
9
- import { basename as re, dirname as ie, join as v, relative as y, resolve as b, sep as x } from "node:path";
10
- import { existsSync as S, readFileSync as C, readdirSync as w, statSync as T, writeFileSync as ae } from "node:fs";
11
- import { pathToFileURL as oe } from "node:url";
12
- import { createLogger as se } from "@voltro/logger";
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";
3
+ import { w as d } from "./frameworkTableAssembly-oMPBKqlE.js";
4
+ import { n as f } from "./pageConvention-cEiRxdab.js";
5
+ import { r as p, u as m } from "./apiBuild-B_iH24qf.js";
6
+ import { n as h, r as g, t as ee } from "./cliOutput-D1tSBoRM.js";
7
+ import { n as _, t as te } from "./tsconfigPaths-BWXBWgcl.js";
8
+ import { n as ne, r as re } from "./fileTaxonomy-CJfgOllU.js";
9
+ import { basename as ie, dirname as ae, join as v, relative as y, resolve as b, sep as x } from "node:path";
10
+ import { existsSync as S, readFileSync as C, readdirSync as w, statSync as T, writeFileSync as oe } from "node:fs";
11
+ import { pathToFileURL as se } from "node:url";
12
+ import { createLogger as ce } from "@voltro/logger";
13
13
  import { Node as E, Project as D, SyntaxKind as O } from "ts-morph";
14
14
  //#region src/predicateColumnAudit.ts
15
- var ce = /* @__PURE__ */ new Set([
15
+ var le = /* @__PURE__ */ new Set([
16
16
  "eq",
17
17
  "ne",
18
18
  "gt",
@@ -30,10 +30,10 @@ var ce = /* @__PURE__ */ new Set([
30
30
  "endsWith",
31
31
  "between",
32
32
  "jsonField"
33
- ]), le = (e) => {
33
+ ]), ue = (e) => {
34
34
  if (E.isPropertyAccessExpression(e)) return e.getName();
35
35
  if (E.isIdentifier(e)) return e.getText();
36
- }, ue = (e) => {
36
+ }, de = (e) => {
37
37
  let t = e;
38
38
  for (; E.isCallExpression(t);) {
39
39
  let e = t.getExpression();
@@ -41,7 +41,7 @@ var ce = /* @__PURE__ */ new Set([
41
41
  t = e.getExpression();
42
42
  }
43
43
  return t;
44
- }, de = (e, t, n) => {
44
+ }, fe = (e, t, n) => {
45
45
  let r = new Map(t.map((e) => [e.name, new Set(e.columns)])), i = new Map(t.map((e) => [e.name, e.columns])), a = [], o = 0, s = new D({
46
46
  useInMemoryFileSystem: !0,
47
47
  skipAddingFilesFromTsConfig: !0
@@ -59,7 +59,7 @@ var ce = /* @__PURE__ */ new Set([
59
59
  for (let t of e.getDescendantsOfKind(O.CallExpression)) {
60
60
  let e = t.getExpression();
61
61
  if (!E.isPropertyAccessExpression(e) || e.getName() !== "where") continue;
62
- let n = le(ue(e.getExpression()));
62
+ let n = ue(de(e.getExpression()));
63
63
  if (n === void 0 || !r.has(n)) {
64
64
  let t = e.getExpression();
65
65
  if (E.isCallExpression(t)) {
@@ -82,7 +82,7 @@ var ce = /* @__PURE__ */ new Set([
82
82
  else for (let e of [u, ...u.getDescendantsOfKind(O.CallExpression)]) {
83
83
  if (!E.isCallExpression(e)) continue;
84
84
  let t = e.getExpression(), n = E.isIdentifier(t) ? t.getText() : E.isPropertyAccessExpression(t) ? t.getName() : void 0;
85
- if (n === void 0 || !ce.has(n)) continue;
85
+ if (n === void 0 || !le.has(n)) continue;
86
86
  let r = e.getArguments()[0];
87
87
  r !== void 0 && E.isStringLiteral(r) && l.push({
88
88
  column: r.getLiteralValue(),
@@ -103,7 +103,7 @@ var ce = /* @__PURE__ */ new Set([
103
103
  issues: a,
104
104
  checked: o
105
105
  };
106
- }, k = 12, fe = (e) => {
106
+ }, k = 12, pe = (e) => {
107
107
  if (e.checked === 0) return null;
108
108
  if (e.issues.length === 0) return `predicate columns: ${e.checked} checked — every filtered column exists on its table`;
109
109
  let t = [`predicate columns: ${e.issues.length} filter${e.issues.length, "s"} on a column that does not exist (${e.checked} checked)`];
@@ -112,7 +112,7 @@ var ce = /* @__PURE__ */ new Set([
112
112
  t.push(` ${n.file}:${n.line} '${n.table}' has no column '${n.column}'`), t.push(` columns: ${e}${r}`);
113
113
  }
114
114
  return t.push(" This type-checks today and fails at runtime as a bare SQL error."), t.join("\n");
115
- }, pe = /* @__PURE__ */ new Set([
115
+ }, me = /* @__PURE__ */ new Set([
116
116
  "id",
117
117
  "tenantId",
118
118
  "tenant_id",
@@ -132,7 +132,7 @@ var ce = /* @__PURE__ */ new Set([
132
132
  "idempotency_id",
133
133
  "idempotencyKey",
134
134
  "idempotency_key"
135
- ]), me = /* @__PURE__ */ new Set([
135
+ ]), he = /* @__PURE__ */ new Set([
136
136
  "id",
137
137
  "createdAt",
138
138
  "created_at",
@@ -142,10 +142,10 @@ var ce = /* @__PURE__ */ new Set([
142
142
  "deleted_at",
143
143
  "tenantId",
144
144
  "tenant_id"
145
- ]), he = (e) => pe.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), ge = (e) => e.type === "reference", _e = (e) => {
145
+ ]), ge = (e) => me.has(e) ? !1 : /[a-z0-9]Id$/.test(e) || /_id$/.test(e), _e = (e) => e.type === "reference", ve = (e) => {
146
146
  let t = [];
147
147
  for (let n of e) {
148
- let e = n.columns.filter((e) => he(e.name)), r = e.filter((e) => !ge(e)).map((e) => e.name);
148
+ let e = n.columns.filter((e) => ge(e.name)), r = e.filter((e) => !_e(e)).map((e) => e.name);
149
149
  if (r.length === 0) continue;
150
150
  let i = new Set(n.primaryKey), a = r.filter((e) => i.has(e));
151
151
  if (n.primaryKey.length >= 2 && a.length > 0) {
@@ -156,7 +156,7 @@ var ce = /* @__PURE__ */ new Set([
156
156
  });
157
157
  continue;
158
158
  }
159
- if (e.some(ge) && e.length >= 2) {
159
+ if (e.some(_e) && e.length >= 2) {
160
160
  t.push({
161
161
  table: n.name,
162
162
  columns: r,
@@ -164,7 +164,7 @@ var ce = /* @__PURE__ */ new Set([
164
164
  });
165
165
  continue;
166
166
  }
167
- e.length >= 2 && n.columns.filter((e) => !he(e.name) && !me.has(e.name)).length === 0 && t.push({
167
+ e.length >= 2 && n.columns.filter((e) => !ge(e.name) && !he.has(e.name)).length === 0 && t.push({
168
168
  table: n.name,
169
169
  columns: r,
170
170
  signal: "link-only"
@@ -174,20 +174,20 @@ var ce = /* @__PURE__ */ new Set([
174
174
  issues: t,
175
175
  tablesChecked: e.length
176
176
  };
177
- }, ve = {
177
+ }, ye = {
178
178
  "composite-pk": "part of a composite primary key",
179
179
  "reference-sibling": "sits beside a wired reference() on this table",
180
180
  "link-only": "this table is nothing but link columns"
181
- }, ye = (e) => {
181
+ }, be = (e) => {
182
182
  if (e.tablesChecked === 0) return null;
183
183
  if (e.issues.length === 0) return `junction FKs: ${e.tablesChecked} table${e.tablesChecked === 1 ? "" : "s"} checked — every link column is a wired reference()`;
184
184
  let t = e.issues.length, n = [`junction FKs: ${t} junction table${t === 1 ? "" : "s"} with an id column that is a plain text() and not a reference()`];
185
185
  for (let t of e.issues) {
186
186
  let e = t.columns.map((e) => `'${e}'`).join(", ");
187
- n.push(` '${t.table}': ${e} (${ve[t.signal]})`);
187
+ n.push(` '${t.table}': ${e} (${ye[t.signal]})`);
188
188
  }
189
189
  return n.push(" Declare each as reference(() => <table>): the FK is enforced, the column is auto-indexed,"), n.push(" and the relationship becomes walkable (a plain text() id column is an invisible edge)."), n.join("\n");
190
- }, be = /* @__PURE__ */ new Set(/* @__PURE__ */ "position.positions.move.moved.moving.cursor.pointer.mouse.drag.dragging.hover.frame.frames.tick.ticks.ping.heartbeat.typing.scroll.scrolling.zoom.pan.coord.coords.coordinate.coordinates.telemetry.sensor.sample.samples.viewport".split(".")), xe = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((e) => e.length > 0).map((e) => e.toLowerCase()), Se = (e) => xe(e).some((e) => be.has(e)), Ce = (e) => {
190
+ }, xe = /* @__PURE__ */ new Set(/* @__PURE__ */ "position.positions.move.moved.moving.cursor.pointer.mouse.drag.dragging.hover.frame.frames.tick.ticks.ping.heartbeat.typing.scroll.scrolling.zoom.pan.coord.coords.coordinate.coordinates.telemetry.sensor.sample.samples.viewport".split(".")), Se = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((e) => e.length > 0).map((e) => e.toLowerCase()), Ce = (e) => Se(e).some((e) => xe.has(e)), we = (e) => {
191
191
  let t = [], n = [];
192
192
  for (let r of e) t.push({
193
193
  event: r.name,
@@ -196,7 +196,7 @@ var ce = /* @__PURE__ */ new Set([
196
196
  event: r.name,
197
197
  kind: "wide-key",
198
198
  keyFieldCount: r.keyFieldCount
199
- }), r.hasWebhook && Se(r.name) && n.push({
199
+ }), r.hasWebhook && Ce(r.name) && n.push({
200
200
  event: r.name,
201
201
  kind: "webhook-high-rate"
202
202
  });
@@ -205,26 +205,26 @@ var ce = /* @__PURE__ */ new Set([
205
205
  modes: t,
206
206
  warnings: n
207
207
  };
208
- }, we = (e) => {
208
+ }, Te = (e) => {
209
209
  if (e.eventsChecked === 0) return null;
210
210
  let t = e.eventsChecked, n = [`event delivery: ${t} declared event${t === 1 ? "" : "s"} — the mode decides what a MISSING message means`];
211
211
  for (let t of e.modes) n.push(` '${t.event}': ${t.delivery}`);
212
212
  return n.push(" each — every delivery matters; a slow subscriber loses the oldest and is TOLD how many (the default)."), n.push(" latest — a newer delivery supersedes a pending one; a slow subscriber gets the current value, told nothing."), n.push(" Two events with identical route/subscriber/buffer numbers mean OPPOSITE things about a drop — the mode is the difference."), n.join("\n");
213
- }, Te = (e) => {
213
+ }, Ee = (e) => {
214
214
  if (e.warnings.length === 0) return null;
215
215
  let t = e.warnings.length, n = [`events that may not scale as declared: ${t} finding${t === 1 ? "" : "s"}`];
216
216
  for (let t of e.warnings) t.kind === "wide-key" ? (n.push(` '${t.event}': routing key has ${t.keyFieldCount ?? 0} fields — every field FRAGMENTS the subscriber set (distinct routes are the PRODUCT of the fields’ value spaces).`), n.push(" Check each is an ADDRESS the delivery is decided by (arenaId), not a discriminator the handler reads (gameType) — the latter is payload, not key.")) : (n.push(` '${t.event}': a webhook block on a per-frame-looking event — every publish is N HTTP deliveries per second per subscribed target.`), n.push(" The webhook rate limit DEFERS the excess as pending rows rather than failing, so the symptom is a growing table. Publish a coarser event (a summary / state change) for the outside world."));
217
217
  return n.push(" WARNING, not a fault — a producer may intend the fan-out. `voltro doctor --json` prints every finding."), n.join("\n");
218
- }, Ee = /* @__PURE__ */ new Set([
218
+ }, De = /* @__PURE__ */ new Set([
219
219
  "start",
220
220
  "run",
221
221
  "child"
222
- ]), De = (e) => E.isCallExpression(e) && (() => {
222
+ ]), Oe = (e) => E.isCallExpression(e) && (() => {
223
223
  let t = e.getExpression();
224
- if (!E.isPropertyAccessExpression(t) || !Ee.has(t.getName())) return !1;
224
+ if (!E.isPropertyAccessExpression(t) || !De.has(t.getName())) return !1;
225
225
  let n = t.getExpression();
226
226
  return E.isPropertyAccessExpression(n) ? n.getName() === "workflows" : E.isIdentifier(n) && n.getText() === "workflows";
227
- })(), Oe = (e, t, n) => {
227
+ })(), ke = (e, t, n) => {
228
228
  let r = new Map(t.map((e) => [e.name, e])), i = [...r.keys()].sort(), a = [], o = [], s = 0, c = 0, l = new D({
229
229
  useInMemoryFileSystem: !0,
230
230
  skipAddingFilesFromTsConfig: !0
@@ -240,7 +240,7 @@ var ce = /* @__PURE__ */ new Set([
240
240
  }
241
241
  let u = y(n, t);
242
242
  for (let t of e.getDescendantsOfKind(O.CallExpression)) {
243
- if (!De(t)) continue;
243
+ if (!Oe(t)) continue;
244
244
  c++;
245
245
  let e = t.getStartLineNumber(), [n, l] = t.getArguments();
246
246
  if (n === void 0 || !E.isStringLiteral(n)) {
@@ -324,7 +324,7 @@ var ce = /* @__PURE__ */ new Set([
324
324
  checked: s,
325
325
  callSites: c
326
326
  };
327
- }, ke = (e) => {
327
+ }, Ae = (e) => {
328
328
  if (e.callSites === 0) return null;
329
329
  let t = [];
330
330
  t.push(`workflow starts: ${e.checked}/${e.callSites} call sites checked`);
@@ -337,7 +337,7 @@ var ce = /* @__PURE__ */ new Set([
337
337
  for (let n of e.unchecked) t.push(` ${n.file}:${n.line} — ${n.reason}`);
338
338
  }
339
339
  return e.issues.length === 0 && e.unchecked.length === 0 && t.push(" every call site matches its workflow"), t.join("\n");
340
- }, Ae = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, A = "VOLTRO_FIELD_ENCRYPTION_KEY", je = (e) => {
340
+ }, je = /(^|[/\\])(\.env(\.[\w.-]+)?|.*\.ya?ml|Dockerfile|.*\.tf|.*\.toml|Procfile)$/i, A = "VOLTRO_FIELD_ENCRYPTION_KEY", Me = (e) => {
341
341
  let t;
342
342
  for (let [n, r] of e.sources) if (t === void 0 && /\bfieldEncryption\b/.test(r) && (t = {
343
343
  reason: "fieldEncryption",
@@ -350,7 +350,7 @@ var ce = /* @__PURE__ */ new Set([
350
350
  for (let [, t] of e.envFiles) if (t.includes(A)) return;
351
351
  return t;
352
352
  }
353
- }, Me = (e) => [
353
+ }, Ne = (e) => [
354
354
  `✗ ${A} is named nowhere under this app root, and the app encrypts columns at rest`,
355
355
  e.reason === "fieldEncryption" ? ` (\`fieldEncryption\` in ${e.file})` : ` (an \`.encrypted()\` column in ${e.file})`,
356
356
  "",
@@ -367,14 +367,14 @@ var ce = /* @__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"), Ne = (e) => {
370
+ ].join("\n"), Pe = (e) => {
371
371
  if (e.catalogCodes.length === 0) return;
372
372
  let t = new Set(e.declaredLocales), n = e.catalogCodes.filter((e) => !t.has(e)).sort();
373
373
  if (n.length !== 0) return {
374
374
  orphaned: n,
375
375
  noneDeclared: t.size === 0
376
376
  };
377
- }, Pe = (e, t) => {
377
+ }, Fe = (e, t) => {
378
378
  let n = e.orphaned.join(", ");
379
379
  return e.noneDeclared ? [
380
380
  `⚠ ${t} has ${e.orphaned.length} catalog(s) — ${n} — and the app declares no \`locales:\`.`,
@@ -386,6 +386,97 @@ var ce = /* @__PURE__ */ new Set([
386
386
  " …or delete the files. A catalog that is never loaded looks from the inside",
387
387
  " exactly like one that works, which is why this is worth a line."
388
388
  ] : [`⚠ ${t} has catalog(s) not in \`locales:\` — ${n} — so they are never imported.`, " Add the code(s) to `locales:` in your app config, or delete the file(s)."];
389
+ }, Ie = (e) => {
390
+ let t = e.getProperty("match");
391
+ if (t === void 0) return null;
392
+ let n = (t.asKind(O.PropertyAssignment)?.getInitializer())?.asKind(O.ObjectLiteralExpression);
393
+ if (n === void 0) return;
394
+ let r = {};
395
+ for (let e of n.getProperties()) {
396
+ let t = e.asKind(O.PropertyAssignment);
397
+ if (t === void 0) return;
398
+ let n = t.getName().replace(/['"]/g, ""), i = t.getInitializer();
399
+ if (i === void 0) return;
400
+ if (n === "assets") {
401
+ let e = i.getKind();
402
+ if (e !== O.TrueKeyword && e !== O.FalseKeyword) return;
403
+ r.assets = e === O.TrueKeyword;
404
+ continue;
405
+ }
406
+ let a = i.asKind(O.StringLiteral);
407
+ if (a !== void 0) {
408
+ if (n !== "under") return;
409
+ r.under = a.getLiteralValue();
410
+ continue;
411
+ }
412
+ let o = i.asKind(O.ArrayLiteralExpression);
413
+ if (o === void 0) return;
414
+ let s = [];
415
+ for (let e of o.getElements()) {
416
+ let t = e.asKind(O.StringLiteral);
417
+ if (t === void 0) return;
418
+ s.push(t.getLiteralValue());
419
+ }
420
+ if (n === "under") r.under = s;
421
+ else if (n === "routes") r.routes = s;
422
+ else if (n === "except") r.except = s;
423
+ else return;
424
+ }
425
+ return r;
426
+ }, Le = (e, t) => {
427
+ let n = ["middleware.ts", "middleware.tsx"].find((t) => S(v(e, t)));
428
+ if (n === void 0) return;
429
+ let r = new D({
430
+ useInMemoryFileSystem: !0,
431
+ skipAddingFilesFromTsConfig: !0
432
+ }), i;
433
+ try {
434
+ i = r.createSourceFile(n, C(v(e, n), "utf8"));
435
+ } catch {
436
+ return {
437
+ problems: [],
438
+ unreadable: [n],
439
+ seen: 0,
440
+ file: n
441
+ };
442
+ }
443
+ let a = [], o = [], s = 0;
444
+ for (let e of i.getVariableDeclarations()) {
445
+ let t = e.getInitializerIfKind(O.CallExpression);
446
+ if (t?.getExpression().getText() !== "defineMiddleware") continue;
447
+ s += 1;
448
+ let n = e.getName(), r = t.getArguments()[0]?.asKind(O.ObjectLiteralExpression);
449
+ if (r === void 0) {
450
+ o.push(n);
451
+ continue;
452
+ }
453
+ let i = Ie(r);
454
+ if (i === void 0) {
455
+ o.push(n);
456
+ continue;
457
+ }
458
+ a.push({
459
+ exportName: n,
460
+ middleware: {
461
+ _tag: "VoltroMiddleware",
462
+ run: () => void 0,
463
+ ...i === null ? {} : { match: i }
464
+ }
465
+ });
466
+ }
467
+ return {
468
+ problems: l(a, t).map((e) => e.message),
469
+ unreadable: o,
470
+ seen: s,
471
+ file: n
472
+ };
473
+ }, Re = (e) => {
474
+ let t = [];
475
+ if (e.problems.length > 0) {
476
+ t.push(`✗ ${e.file}: ${e.problems.length} matcher problem(s) — this REFUSES the boot`);
477
+ for (let n of e.problems) t.push(` • ${n}`);
478
+ }
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;
389
480
  }, j = (e) => e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/[^\n]*/g, "$1"), M = /* @__PURE__ */ new Set([
390
481
  "requireScope",
391
482
  "requireAnyScope",
@@ -393,26 +484,26 @@ var ce = /* @__PURE__ */ new Set([
393
484
  "assertScope",
394
485
  "assertCan",
395
486
  "can"
396
- ]), Fe = (e) => {
487
+ ]), ze = (e) => {
397
488
  let t = /* @__PURE__ */ new Set();
398
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]);
399
490
  return t;
400
- }, Ie = (e, t) => {
491
+ }, Be = (e, t) => {
401
492
  for (let n of t) if (RegExp(`\\b${n}\\s*\\(`).test(e)) return n;
402
- }, Le = (e) => {
493
+ }, Ve = (e) => {
403
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);
404
495
  return t && n;
405
- }, Re = (e, t, n) => {
496
+ }, He = (e, t, n) => {
406
497
  if (e.hasDeclaredGuards) return { kind: "declared-guard" };
407
- let r = j(e.src), i = Ie(r, t);
408
- return i === void 0 ? Le(r) ? { kind: "inline-ownership" } : n.has(e.tag) ? { kind: "allowlisted" } : { kind: "unchecked" } : {
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" } : {
409
500
  kind: "vocabulary-guard",
410
501
  name: i
411
502
  };
412
- }, ze = (e, t, n) => {
503
+ }, Ue = (e, t, n) => {
413
504
  let r = e.op === "delete" ? 3 : e.op === "update" ? 2 : 1;
414
505
  return e.table !== void 0 && (t.has(e.table) && (r += 2), n.has(e.table) && e.op === "delete" && (r += 2)), r;
415
- }, Be = (e, t, n) => {
506
+ }, We = (e, t, n) => {
416
507
  switch (t.kind) {
417
508
  case "declared-guard": return "guards: on the descriptor";
418
509
  case "vocabulary-guard": return `calls ${t.name}()`;
@@ -420,15 +511,15 @@ var ce = /* @__PURE__ */ new Set([
420
511
  case "allowlisted": return "accepted by a previous run";
421
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`;
422
513
  }
423
- }, Ve = (e) => {
514
+ }, Ge = (e) => {
424
515
  let t = e.executors.map((t) => {
425
- let n = Re(t, e.guardVocabulary, e.allowlist);
516
+ let n = He(t, e.guardVocabulary, e.allowlist);
426
517
  return {
427
518
  tag: t.tag,
428
519
  path: t.path,
429
520
  verdict: n,
430
- severity: ze(t, e.tenantScopedTables, e.referencedTables),
431
- why: Be(t, n, e.rowFilterRegistered === !0)
521
+ severity: Ue(t, e.tenantScopedTables, e.referencedTables),
522
+ why: We(t, n, e.rowFilterRegistered === !0)
432
523
  };
433
524
  }), n = (e) => t.filter((t) => t.verdict.kind === e).length;
434
525
  return {
@@ -442,7 +533,7 @@ var ce = /* @__PURE__ */ new Set([
442
533
  },
443
534
  unchecked: t.filter((e) => e.verdict.kind === "unchecked").sort((e, t) => t.severity - e.severity || e.tag.localeCompare(t.tag))
444
535
  };
445
- }, He = [
536
+ }, Ke = [
446
537
  "# voltro doctor — authz allowlist",
447
538
  "#",
448
539
  "# Two KINDS of line, and the difference is the point:",
@@ -467,18 +558,18 @@ var ce = /* @__PURE__ */ new Set([
467
558
  "# `--write-authz-allowlist` PRESERVES reviewed= lines. It only ever adds debt",
468
559
  "# lines for executors that are newly unchecked.",
469
560
  ""
470
- ], Ue = (e) => {
561
+ ], qe = (e) => {
471
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));
472
563
  return [
473
- ...He,
564
+ ...Ke,
474
565
  ...n,
475
566
  ...r.length > 0 ? ["", "# reviewed — read by a human, open on purpose"] : [],
476
567
  ...r.map((e) => `${e.tag} reviewed=${e.reviewed}`),
477
568
  ""
478
569
  ].join("\n");
479
- }, We = (e, t) => {
570
+ }, Je = (e, t) => {
480
571
  let n = [...new Set(t)].sort();
481
- if (e === void 0) return Ue(n.map((e) => ({ tag: e })));
572
+ if (e === void 0) return qe(n.map((e) => ({ tag: e })));
482
573
  let r = N(e).tags, i = n.filter((e) => !r.has(e));
483
574
  return i.length === 0 ? e : [
484
575
  e.replace(/\n+$/, ""),
@@ -488,7 +579,7 @@ var ce = /* @__PURE__ */ new Set([
488
579
  ...i,
489
580
  ""
490
581
  ].join("\n");
491
- }, Ge = (e, t) => {
582
+ }, Ye = (e, t) => {
492
583
  let n = [...new Set(t)];
493
584
  if (e === void 0) return {
494
585
  added: n.length,
@@ -501,7 +592,7 @@ var ce = /* @__PURE__ */ new Set([
501
592
  kept: r.size,
502
593
  removed: 0
503
594
  };
504
- }, Ke = class extends Error {
595
+ }, Xe = class extends Error {
505
596
  tag;
506
597
  constructor(e) {
507
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";
@@ -519,7 +610,7 @@ var ce = /* @__PURE__ */ new Set([
519
610
  continue;
520
611
  }
521
612
  let a = r[2].trim();
522
- if (a.length === 0) throw new Ke(i);
613
+ if (a.length === 0) throw new Xe(i);
523
614
  t.push({
524
615
  tag: i,
525
616
  reviewed: a
@@ -529,13 +620,13 @@ var ce = /* @__PURE__ */ new Set([
529
620
  tags: new Set(t.map((e) => e.tag)),
530
621
  entries: t
531
622
  };
532
- }, P = (e) => {
623
+ }, Ze = (e) => {
533
624
  let t = e.indexOf(".");
534
625
  return t <= 0 ? void 0 : e.slice(0, t);
535
- }, qe = (e, t) => {
626
+ }, Qe = (e, t) => {
536
627
  let n = new Set(t.tables), r = new Set(t.rpcTags), i = /* @__PURE__ */ new Map();
537
628
  for (let e of t.rpcTags) {
538
- let t = P(e);
629
+ let t = Ze(e);
539
630
  if (t === void 0) continue;
540
631
  let n = i.get(t);
541
632
  n ? n.push(e) : i.set(t, [e]);
@@ -564,7 +655,7 @@ var ce = /* @__PURE__ */ new Set([
564
655
  });
565
656
  continue;
566
657
  }
567
- let s = new Set(o.rpcTags.map(P).filter((e) => e !== void 0));
658
+ let s = new Set(o.rpcTags.map(Ze).filter((e) => e !== void 0));
568
659
  for (let e of s) {
569
660
  let t = i.get(e);
570
661
  t !== void 0 && a.push({
@@ -577,12 +668,12 @@ var ce = /* @__PURE__ */ new Set([
577
668
  }
578
669
  }
579
670
  return a;
580
- }, Je = (e, t) => {
671
+ }, $e = (e, t) => {
581
672
  let n = e.reduce((e, t) => e + t.tables.length, 0), r = [`plugin surface · ${e.length} plugin(s) contribute ${n} table(s)`];
582
673
  if (t.length === 0) return r.push(" ✓ no overlap with your own tables or rpc namespaces"), r;
583
674
  for (let e of t) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.plugin} — ${e.detail}`), r.push(` ${e.fix}`);
584
675
  return r;
585
- }, Ye = async (e) => {
676
+ }, et = async (e) => {
586
677
  if (!await e.hasWorkflows().catch(() => !1)) return { kind: "not-applicable" };
587
678
  let t;
588
679
  try {
@@ -606,12 +697,12 @@ var ce = /* @__PURE__ */ new Set([
606
697
  } finally {
607
698
  await t.close().catch(() => {});
608
699
  }
609
- }, Xe = (e) => {
700
+ }, tt = (e) => {
610
701
  let t = Math.round(e / 6e4);
611
702
  if (t < 90) return `${t}m`;
612
703
  let n = Math.round(t / 60);
613
704
  return n < 48 ? `${n}h` : `${Math.round(n / 24)}d`;
614
- }, Ze = (e) => {
705
+ }, nt = (e) => {
615
706
  if (e.kind === "not-applicable") return null;
616
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.
617
708
  Run it where the app's DB env vars are set, or ignore this line locally.`;
@@ -619,14 +710,14 @@ var ce = /* @__PURE__ */ new Set([
619
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` : ""})`);
620
711
  else {
621
712
  n.push(`✗ stuck runs: ${t.stalled.length} run(s) have made no progress`);
622
- for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${Xe(e.idleMs)} ${e.reason}`);
713
+ for (let e of t.stalled.slice(0, 20)) n.push(` ${e.tag} ${e.runId} idle ${tt(e.idleMs)} ${e.reason}`);
623
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.");
624
715
  }
625
716
  t.sawFullRunPage && n.push(" (the examined page filled up — more old runs exist than were read this pass)");
626
717
  for (let e of t.failures) n.push(` ! could not examine ${e.subject}: ${e.detail}`);
627
718
  return n.join("\n");
628
- }, Qe = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), $e = (e) => {
629
- let t = p(e), n = f(e);
719
+ }, rt = (e) => S(b(e, "app.config.ts")) || S(b(e, "app.config.js")), it = (e) => {
720
+ let t = m(e), n = p(e);
630
721
  return {
631
722
  root: e,
632
723
  serveBundle: t,
@@ -634,19 +725,19 @@ var ce = /* @__PURE__ */ new Set([
634
725
  apiBundle: n,
635
726
  apiBundlePresent: S(n)
636
727
  };
637
- }, et = [
728
+ }, at = [
638
729
  "Production `voltro serve` requires a precompiled serve bundle and never transpiles on demand.",
639
730
  "Add a `voltro build .` step BEFORE `voltro serve .` in your Dockerfile / start script.",
640
731
  "(Generated Dockerfiles from `voltro create-project` already do this.)"
641
- ].join("\n "), tt = (e, t = !1) => {
642
- let n = $e(e);
643
- if (console.log(`voltro doctor — serve preflight for ${n.root}`), !Qe(e)) return console.log("• no app.config found here — nothing to check (run against an app root)."), 0;
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;
644
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})`);
645
736
  let r = process.env.NODE_ENV === "production";
646
737
  if (n.serveBundlePresent) return console.log("✓ serve preflight: ready for `voltro serve`."), 0;
647
738
  let i = t || r;
648
- return console.error(`${i ? "✗" : "•"} serve bundle missing — ${i ? "production `voltro serve` will FAIL." : "a non-production `voltro serve` still runs via tsx."}`), console.error(` ${et}`), +!!i;
649
- }, nt = (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(` ${at}`), +!!i;
740
+ }, st = (e, t, n) => {
650
741
  let r = new Set(e), i = _(n), a = (e) => (/\.(tsx?|jsx?)$/.test(e) ? [e] : [
651
742
  `${e}.ts`,
652
743
  `${e}.tsx`,
@@ -666,13 +757,13 @@ var ce = /* @__PURE__ */ new Set([
666
757
  }
667
758
  let o = [];
668
759
  for (let t of r) {
669
- let n = t.startsWith(".") ? b(ie(e), t) : ee(t, i);
760
+ let n = t.startsWith(".") ? b(ae(e), t) : te(t, i);
670
761
  if (n === void 0) continue;
671
762
  let r = a(n);
672
763
  r !== void 0 && o.push(r);
673
764
  }
674
765
  return o;
675
- }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => ct.test(y(n, e))), l = new Set(c);
766
+ }, s = /* @__PURE__ */ new Set(), c = e.filter((e) => ft.test(y(n, e))), l = new Set(c);
676
767
  for (; c.length > 0;) {
677
768
  let e = c.shift();
678
769
  if (!s.has(e)) {
@@ -685,11 +776,11 @@ var ce = /* @__PURE__ */ new Set([
685
776
  let d = /* @__PURE__ */ new Set();
686
777
  for (let e of s) l.has(e) || u.has(e) || d.add(e);
687
778
  return d;
688
- }, rt = (e) => e.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/[^\n]*/g, "$1").replace(/'(?:[^'\\\n]|\\.)*'/g, "''").replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\""), it = (e, t) => (e.match(t) ?? []).length, F = (e, t) => e.getDescendantsOfKind(O.CallExpression).filter((e) => t.test(e.getExpression().getText())).length, at = (e, t) => {
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) => {
689
780
  if (e.src.includes(t)) return !0;
690
781
  let n = t.replace(/^@voltro\/plugin-/, "");
691
782
  return n !== t && F(e.file, RegExp(`^${n}Plugin$`)) > 0;
692
- }, ot = (e, t) => {
783
+ }, ut = (e, t) => {
693
784
  let n = [], r = RegExp(`\\b${t}\\s*(?:<[^()]*>)?\\s*\\(`, "g"), i;
694
785
  for (; (i = r.exec(e)) !== null;) {
695
786
  let t = i.index + i[0].length, r = 1, a = t;
@@ -697,10 +788,10 @@ var ce = /* @__PURE__ */ new Set([
697
788
  n.push(e.slice(t, a - 1));
698
789
  }
699
790
  return n;
700
- }, st = (e, t) => {
791
+ }, dt = (e, t) => {
701
792
  let n = t.getSymbol()?.getDeclarations()?.[0];
702
793
  return n === void 0 ? [] : e.getDescendantsOfKind(O.Identifier).filter((e) => e.getSymbol()?.getDeclarations()?.[0] === n);
703
- }, ct = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, lt = /\.(query|mutation|action|stream)\.server\.tsx?$/, ut = [
794
+ }, ft = /\.(server|cron|workflow\.server|subscribe|reaction|outbox|startup|trigger|webhook|migration|seed)\.tsx?$/, pt = /\.(query|mutation|action|stream)\.server\.tsx?$/, mt = [
704
795
  "Id",
705
796
  "ID",
706
797
  "Hash",
@@ -726,7 +817,7 @@ var ce = /* @__PURE__ */ new Set([
726
817
  "Comment",
727
818
  "Provider",
728
819
  "Format"
729
- ], dt = RegExp(`(?:${ut.join("|")})$|_(?:${ut.map((e) => e.toLowerCase()).join("|")})$`), ft = [
820
+ ], ht = RegExp(`(?:${mt.join("|")})$|_(?:${mt.map((e) => e.toLowerCase()).join("|")})$`), gt = [
730
821
  {
731
822
  id: "hardcoded-framework-cookie",
732
823
  scope: "client",
@@ -739,7 +830,7 @@ var ce = /* @__PURE__ */ new Set([
739
830
  scope: "server",
740
831
  smell: "an executor that never names its own descriptor — its input type is hand-written, so it can drift from the contract it implements",
741
832
  use: "import the descriptor and type the input as `ExecutorInput<typeof descriptor>`, so the wire contract and the handler cannot disagree",
742
- match: ({ src: e, path: t }) => lt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
833
+ match: ({ src: e, path: t }) => pt.test(t) && !/\bimport\s[^;]*\bfrom\s*['"][^'"]*['"]/.test((e.match(/^\s*import\s[^;]*from\s*['"]\.[^'"]*['"]/gm) ?? []).join("\n"))
743
834
  },
744
835
  {
745
836
  id: "subject-write-no-guard",
@@ -747,8 +838,8 @@ var ce = /* @__PURE__ */ new Set([
747
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",
748
839
  use: "add a guard (e.g. `.guard(requireScope('…'))`) or an explicit auth check, so an unauthenticated caller is refused BEFORE the write",
749
840
  match: ({ src: e, path: t }) => {
750
- if (!lt.test(t)) return !1;
751
- let n = rt(e);
841
+ if (!pt.test(t)) return !1;
842
+ let n = P(e);
752
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);
753
844
  }
754
845
  },
@@ -757,7 +848,7 @@ var ce = /* @__PURE__ */ new Set([
757
848
  scope: "server",
758
849
  smell: "raw fetch() for outbound HTTP on the server (no traceparent, no retry, and no SSRF guard where the URL is not a constant)",
759
850
  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",
760
- match: ({ file: e, path: t, serverOnly: n }) => (ct.test(t) || n) && F(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
851
+ match: ({ file: e, path: t, serverOnly: n }) => (ft.test(t) || n) && F(e, /^(?:globalThis\.|window\.|self\.)?fetch$/) > 0
761
852
  },
762
853
  {
763
854
  id: "form",
@@ -792,7 +883,7 @@ var ce = /* @__PURE__ */ new Set([
792
883
  scope: "client",
793
884
  smell: "useMemo fanning in multiple subscriptions (hand-maintained dep array)",
794
885
  use: "useDerived — dependency-tracked, referentially stable",
795
- match: ({ src: e }) => it(e, /\buseSubscription\s*[(<]/g) >= 3 && it(e, /\buseMemo\s*\(/g) >= 3
886
+ match: ({ src: e }) => ct(e, /\buseSubscription\s*[(<]/g) >= 3 && ct(e, /\buseMemo\s*\(/g) >= 3
796
887
  },
797
888
  {
798
889
  id: "next-shim",
@@ -817,7 +908,7 @@ var ce = /* @__PURE__ */ new Set([
817
908
  match: ({ file: e }) => e.getDescendantsOfKind(O.CallExpression).filter((e) => /(^|\.)useSubscription$/.test(e.getExpression().getText())).flatMap((e) => {
818
909
  let t = e.getFirstAncestorByKind(O.VariableDeclaration)?.getNameNode();
819
910
  return t === void 0 ? [] : E.isObjectBindingPattern(t) ? t.getElements().filter((e) => (e.getPropertyNameNode() ?? e.getNameNode()).getText() === "data").map((e) => e.getNameNode()) : [];
820
- }).some((t) => st(e, t).some((e) => {
911
+ }).some((t) => dt(e, t).some((e) => {
821
912
  let t = e.getParent();
822
913
  if (E.isBinaryExpression(t)) {
823
914
  let n = t.getOperatorToken().getText(), r = t.getLeft() === e ? t.getRight() : t.getLeft();
@@ -868,7 +959,7 @@ var ce = /* @__PURE__ */ new Set([
868
959
  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)",
869
960
  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) => {
870
961
  let n = t[1];
871
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || dt.test(n) || /^vault/i.test(n) || /\.unique\s*\(/.test(e) ? !1 : !/\.encrypted\s*\(/.test(e);
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);
872
963
  }))
873
964
  },
874
965
  {
@@ -878,7 +969,7 @@ var ce = /* @__PURE__ */ new Set([
878
969
  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",
879
970
  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) => {
880
971
  let n = t[1];
881
- return !/token|secret|credential|password|apikey|accesskey/i.test(n) || dt.test(n) || /^vault/i.test(n) ? !1 : /\.unique\s*\(/.test(e) && !/\.encrypted\s*\(/.test(e);
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);
882
973
  }))
883
974
  },
884
975
  {
@@ -943,8 +1034,8 @@ var ce = /* @__PURE__ */ new Set([
943
1034
  smell: "setRowFilter is registered, but no test passes `rowFilter:` to makeTestContext",
944
1035
  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",
945
1036
  match: ({ file: e, testSources: t }) => F(e, /^setRowFilter$/) > 0 && ![...t.values()].some((e) => {
946
- let t = rt(e);
947
- return ot(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
1037
+ let t = P(e);
1038
+ return ut(t, "makeTestContext").length > 0 && /\browFilter\b/.test(t);
948
1039
  })
949
1040
  },
950
1041
  {
@@ -954,15 +1045,15 @@ var ce = /* @__PURE__ */ new Set([
954
1045
  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",
955
1046
  match: ({ src: e }) => /\.(getTime|toISOString)\s*\(\s*\)/.test(e) && /\.map\s*\(|=>\s*\(\{/.test(e) && /\bexport\s+default\b/.test(e)
956
1047
  }
957
- ], pt = (e) => R(e), mt = (e, t) => {
1048
+ ], _t = (e) => L(e), vt = (e, t) => {
958
1049
  let n = /* @__PURE__ */ new Set();
959
1050
  for (let r of t) {
960
1051
  let t = y(e, r), i = t.split(/[\\/]/)[0];
961
1052
  n.add(i !== void 0 && i !== t ? i : ".");
962
1053
  }
963
1054
  return [...n].sort();
964
- }, I = /\.(tsx?|jsx?)$/, L = /(^|\/)(node_modules|dist|build|\.framework|\.next|coverage)(\/|$)/, ht = /(^|[\\/])[^\\/]*\.generated\.[cm]?[jt]sx?$|\.d\.[cm]?ts$/, gt = (e, t = [], n = 0) => {
965
- if (n > 12 || L.test(e)) return t;
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;
966
1057
  let r;
967
1058
  try {
968
1059
  r = w(e, { withFileTypes: !0 });
@@ -973,12 +1064,12 @@ var ce = /* @__PURE__ */ new Set([
973
1064
  let r = v(e, i.name);
974
1065
  if (i.isDirectory()) {
975
1066
  if (i.name.startsWith(".")) continue;
976
- gt(r, t, n + 1);
977
- } else Ae.test(i.name) && t.push(r);
1067
+ xt(r, t, n + 1);
1068
+ } else je.test(i.name) && t.push(r);
978
1069
  }
979
1070
  return t;
980
- }, R = (e, t = [], n = 0) => {
981
- if (n > 12 || L.test(e)) return t;
1071
+ }, L = (e, t = [], n = 0) => {
1072
+ if (n > 12 || I.test(e)) return t;
982
1073
  let r;
983
1074
  try {
984
1075
  r = w(e);
@@ -992,11 +1083,11 @@ var ce = /* @__PURE__ */ new Set([
992
1083
  } catch {
993
1084
  continue;
994
1085
  }
995
- a.isDirectory() ? R(r, t, n + 1) : I.test(i) && !/\.(test|spec)\./.test(i) && !ht.test(i) && t.push(r);
1086
+ a.isDirectory() ? L(r, t, n + 1) : yt.test(i) && !/\.(test|spec)\./.test(i) && !bt.test(i) && t.push(r);
996
1087
  }
997
1088
  return t;
998
- }, _t = (e, t = [], n = 0) => {
999
- if (n > 12 || L.test(e)) return t;
1089
+ }, St = (e, t = [], n = 0) => {
1090
+ if (n > 12 || I.test(e)) return t;
1000
1091
  let r;
1001
1092
  try {
1002
1093
  r = w(e);
@@ -1010,17 +1101,17 @@ var ce = /* @__PURE__ */ new Set([
1010
1101
  } catch {
1011
1102
  continue;
1012
1103
  }
1013
- a.isDirectory() ? _t(r, t, n + 1) : I.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1104
+ a.isDirectory() ? St(r, t, n + 1) : yt.test(i) && /\.(test|spec)\./.test(i) && t.push(r);
1014
1105
  }
1015
1106
  return t;
1016
- }, vt = (e) => {
1017
- let t = pt(e);
1107
+ }, Ct = (e) => {
1108
+ let t = _t(e);
1018
1109
  return {
1019
- dirs: mt(e, t),
1110
+ dirs: vt(e, t),
1020
1111
  fileCount: t.length
1021
1112
  };
1022
- }, yt = (e) => {
1023
- let t = pt(e);
1113
+ }, wt = (e) => {
1114
+ let t = _t(e);
1024
1115
  if (t.length === 0) return [];
1025
1116
  let n = new D({
1026
1117
  useInMemoryFileSystem: !0,
@@ -1029,8 +1120,8 @@ var ce = /* @__PURE__ */ new Set([
1029
1120
  for (let e of t) try {
1030
1121
  i.set(e, C(e, "utf8"));
1031
1122
  } catch {}
1032
- let a = nt([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1033
- for (let t of _t(e)) try {
1123
+ let a = st([...i.keys()], i, e), o = /* @__PURE__ */ new Map();
1124
+ for (let t of St(e)) try {
1034
1125
  o.set(t, C(t, "utf8"));
1035
1126
  } catch {}
1036
1127
  for (let s of t) {
@@ -1048,23 +1139,23 @@ var ce = /* @__PURE__ */ new Set([
1048
1139
  } catch {
1049
1140
  continue;
1050
1141
  }
1051
- for (let t of ft) {
1052
- if (t.plugin !== void 0 && at(c, t.plugin) || !t.match(c)) continue;
1142
+ for (let t of gt) {
1143
+ if (t.plugin !== void 0 && lt(c, t.plugin) || !t.match(c)) continue;
1053
1144
  let n = r.get(t.id) ?? [];
1054
1145
  n.push(y(e, s)), r.set(t.id, n);
1055
1146
  }
1056
1147
  }
1057
- return ft.filter((e) => r.has(e.id)).map((e) => ({
1148
+ return gt.filter((e) => r.has(e.id)).map((e) => ({
1058
1149
  rule: e,
1059
1150
  files: r.get(e.id)
1060
1151
  })).sort((e, t) => t.files.length - e.files.length || e.rule.id.localeCompare(t.rule.id));
1061
- }, z = (e) => {
1062
- let t = vt(e);
1152
+ }, R = (e) => {
1153
+ let t = Ct(e);
1063
1154
  return {
1064
1155
  root: e,
1065
1156
  scannedFiles: t.fileCount,
1066
1157
  scannedDirs: t.dirs,
1067
- findings: yt(e).map(({ rule: e, files: t }) => ({
1158
+ findings: wt(e).map(({ rule: e, files: t }) => ({
1068
1159
  id: e.id,
1069
1160
  scope: e.scope,
1070
1161
  smell: e.smell,
@@ -1072,22 +1163,22 @@ var ce = /* @__PURE__ */ new Set([
1072
1163
  files: t
1073
1164
  }))
1074
1165
  };
1075
- }, bt = 3, xt = .2, St = 50, Ct = (e, t) => e >= St && t > 0 && e / t >= xt, wt = (e) => {
1166
+ }, Tt = 3, Et = .2, Dt = 50, Ot = (e, t) => e >= Dt && t > 0 && e / t >= Et, kt = (e) => {
1076
1167
  let t = /* @__PURE__ */ new Map();
1077
- for (let n of R(e)) try {
1168
+ for (let n of L(e)) try {
1078
1169
  t.set(y(e, n), C(n, "utf8"));
1079
1170
  } catch {}
1080
1171
  let n = /* @__PURE__ */ new Map();
1081
- for (let t of gt(e)) try {
1172
+ for (let t of xt(e)) try {
1082
1173
  n.set(y(e, t), C(t, "utf8"));
1083
1174
  } catch {}
1084
- let r = je({
1175
+ let r = Me({
1085
1176
  sources: t,
1086
1177
  envFiles: n
1087
1178
  });
1088
- r !== void 0 && (console.log(""), console.log(Me(r)));
1089
- }, Tt = (e) => {
1090
- let t = z(e);
1179
+ r !== void 0 && (console.log(""), console.log(Ne(r)));
1180
+ }, At = (e) => {
1181
+ let t = R(e);
1091
1182
  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;
1092
1183
  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;
1093
1184
  console.log(`\u2022 Shipped primitives you may be hand-rolling (scanned ${t.scannedFiles} files across ${t.scannedDirs.join(", ")}):`);
@@ -1098,29 +1189,29 @@ var ce = /* @__PURE__ */ new Set([
1098
1189
  console.log(` [${e}]`);
1099
1190
  let i = [...r].sort((e, t) => e.files.length - t.files.length);
1100
1191
  for (let e of i) {
1101
- if (Ct(e.files.length, t.scannedFiles)) {
1192
+ if (Ot(e.files.length, t.scannedFiles)) {
1102
1193
  let n = Math.round(e.files.length / t.scannedFiles * 100);
1103
1194
  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.)");
1104
1195
  continue;
1105
1196
  }
1106
- let r = e.files.slice(0, bt).join(", "), i = e.files.length - bt;
1197
+ let r = e.files.slice(0, Tt).join(", "), i = e.files.length - Tt;
1107
1198
  i > 0 && (n += i);
1108
1199
  let a = i > 0 ? `, +${i} more` : "";
1109
1200
  console.log(` ${e.smell} \u2014 ${e.files.length} file(s): ${r}${a}`), console.log(` \u2192 ${e.use}`);
1110
1201
  }
1111
1202
  }
1112
1203
  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;
1113
- }, Et = "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.", Dt = /export\s+const\s+renderMode\s*(?::[^=]+)?=\s*['"]([^'"]*)['"]/, Ot = /export\s+const\s+dynamic\s*=\s*['"]force-(dynamic|static)['"]/, kt = (e) => {
1114
- let t = Dt.exec(e)?.[1];
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];
1115
1206
  if (t !== void 0) return t === "ssr" ? "ssr" : null;
1116
- let n = Ot.exec(e)?.[1];
1207
+ let n = Nt.exec(e)?.[1];
1117
1208
  return n === "dynamic" ? "ssr" : n === "static" ? null : "default";
1118
- }, B = async (e) => {
1209
+ }, z = async (e) => {
1119
1210
  let t = v(e, "src", "pages");
1120
1211
  if (!S(t)) return [];
1121
- let { pages: n, dirs: r } = await l(t), i = [];
1212
+ let { pages: n, dirs: r } = await u(t), i = [];
1122
1213
  for (let a of n) {
1123
- if (!s(r, a.dirChain).some((e) => e.hasLayout)) continue;
1214
+ if (!o(r, a.dirChain).some((e) => e.hasLayout)) continue;
1124
1215
  let n;
1125
1216
  try {
1126
1217
  n = C(v(t, a.file), "utf8");
@@ -1128,28 +1219,28 @@ var ce = /* @__PURE__ */ new Set([
1128
1219
  continue;
1129
1220
  }
1130
1221
  if (c.test(n)) continue;
1131
- let o = kt(n);
1132
- o !== null && i.push({
1222
+ let s = Pt(n);
1223
+ s !== null && i.push({
1133
1224
  file: y(e, v(t, a.file)),
1134
1225
  pattern: a.pattern,
1135
- currentMode: o
1226
+ currentMode: s
1136
1227
  });
1137
1228
  }
1138
1229
  return i;
1139
- }, At = 10, jt = async (e) => {
1230
+ }, Ft = 10, It = async (e) => {
1140
1231
  console.log("");
1141
1232
  let t = v(e, "src", "pages");
1142
1233
  if (!S(t)) return console.log("• spa-candidate scan: no src/pages here — nothing to check (web apps only)."), 0;
1143
- let n = await B(e);
1234
+ let n = await z(e);
1144
1235
  if (n.length === 0) return console.log("✓ spa-candidate scan: no loader-free, layout-backed pages to flag."), 0;
1145
1236
  console.log(`• renderMode:'spa' candidates (${n.length} page${n.length === 1 ? "" : "s"} — loader-free, under a layout, currently ssr/default):`);
1146
- for (let e of n.slice(0, At)) {
1237
+ for (let e of n.slice(0, Ft)) {
1147
1238
  let t = e.currentMode === "ssr" ? "renderMode:'ssr'" : "default renderMode";
1148
1239
  console.log(` ${e.file} (${e.pattern}) — ${t}`);
1149
1240
  }
1150
- let r = n.length - At;
1151
- return r > 0 && console.log(` (+${r} more page${r === 1 ? "" : "s"} not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${Et}`), n.length;
1152
- }, Mt = /* @__PURE__ */ new Set([
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([
1153
1244
  "node_modules",
1154
1245
  "dist",
1155
1246
  "build",
@@ -1157,7 +1248,7 @@ var ce = /* @__PURE__ */ new Set([
1157
1248
  ".vite",
1158
1249
  ".framework",
1159
1250
  ".git"
1160
- ]), Nt = (e, t, n) => {
1251
+ ]), Rt = (e, t, n) => {
1161
1252
  let r;
1162
1253
  try {
1163
1254
  r = w(e, { withFileTypes: !0 });
@@ -1165,11 +1256,11 @@ var ce = /* @__PURE__ */ new Set([
1165
1256
  return;
1166
1257
  }
1167
1258
  for (let i of r) {
1168
- if (Mt.has(i.name)) continue;
1259
+ if (Lt.has(i.name)) continue;
1169
1260
  let r = v(e, i.name);
1170
- i.isDirectory() ? Nt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1261
+ i.isDirectory() ? Rt(r, t, n) : i.isFile() && i.name.endsWith(".tsx") && n.push(y(t, r));
1171
1262
  }
1172
- }, Pt = (e, t) => {
1263
+ }, zt = (e, t) => {
1173
1264
  let n = /* @__PURE__ */ new Set(), r = /(?:from|import)\s*\(?\s*['"](\.[^'"]*)['"]/g;
1174
1265
  for (let i of t) {
1175
1266
  let t;
@@ -1178,7 +1269,7 @@ var ce = /* @__PURE__ */ new Set([
1178
1269
  } catch {
1179
1270
  continue;
1180
1271
  }
1181
- let a = ie(v(e, i));
1272
+ let a = ae(v(e, i));
1182
1273
  for (let i of t.matchAll(r)) {
1183
1274
  let t = b(a, i[1]);
1184
1275
  for (let r of [
@@ -1194,21 +1285,21 @@ var ce = /* @__PURE__ */ new Set([
1194
1285
  }
1195
1286
  }
1196
1287
  return n;
1197
- }, Ft = (e, t) => {
1288
+ }, Bt = (e, t) => {
1198
1289
  let n = e.slice(t.length).split(x).slice(0, -1)[0];
1199
1290
  return n !== void 0 && n.startsWith("[");
1200
- }, V = (e) => {
1291
+ }, B = (e) => {
1201
1292
  let t = [], n = v(e, "src", "pages"), r = v(e, "src");
1202
1293
  if (!S(r)) return t;
1203
1294
  let i = [];
1204
- Nt(r, e, i), i.sort();
1295
+ Rt(r, e, i), i.sort();
1205
1296
  let a = `${y(e, n)}${x}`;
1206
- Pt(e, i);
1297
+ zt(e, i);
1207
1298
  for (let n of i) {
1208
1299
  let r = n.slice(n.lastIndexOf(x) + 1);
1209
1300
  if (n.startsWith(a)) {
1210
1301
  if (r !== "page.tsx") continue;
1211
- S(v(e, d(n))) || t.push({
1302
+ S(v(e, f(n))) || t.push({
1212
1303
  kind: "page-without-test",
1213
1304
  file: n
1214
1305
  });
@@ -1219,7 +1310,7 @@ var ce = /* @__PURE__ */ new Set([
1219
1310
  file: n
1220
1311
  });
1221
1312
  } catch {}
1222
- Ft(n, a) && t.push({
1313
+ Bt(n, a) && t.push({
1223
1314
  kind: "top-level-dynamic-route",
1224
1315
  file: n
1225
1316
  });
@@ -1231,35 +1322,35 @@ var ce = /* @__PURE__ */ new Set([
1231
1322
  });
1232
1323
  }
1233
1324
  return t;
1234
- }, It = 10, H = (e, t, n) => {
1325
+ }, Vt = 10, V = (e, t, n) => {
1235
1326
  if (t.length === 0) return;
1236
1327
  console.log(`• ${e} (${t.length}):`);
1237
- for (let e of t.slice(0, It)) console.log(` ${e}`);
1238
- let r = t.length - It;
1328
+ for (let e of t.slice(0, Vt)) console.log(` ${e}`);
1329
+ let r = t.length - Vt;
1239
1330
  r > 0 && console.log(` (+${r} more not shown — \`voltro doctor --json\` prints every one.)`), console.log(` → ${n}`);
1240
- }, Lt = (e) => {
1331
+ }, Ht = (e) => {
1241
1332
  console.log("");
1242
1333
  let t = v(e, "src", "pages");
1243
1334
  if (!S(t)) return console.log("• page-convention scan: no src/pages here — nothing to check (web apps only)."), 0;
1244
- let n = V(e);
1335
+ let n = B(e);
1245
1336
  if (n.length === 0) return console.log("✓ page-convention scan: every route is a page.tsx in its own directory, with a test beside it."), 0;
1246
1337
  let r = (e) => n.filter((t) => t.kind === e).map((e) => e.file);
1247
- return H("will NEVER route — page.tsx outside src/pages/", r("suffixed-outside-pages"), "the name says \"route\", the location says otherwise, and the location wins: the URL comes from the directory chain under src/pages/. Move it into the directory whose route it serves, or rename it."), H("pages with no test beside them", r("page-without-test"), "a page and its test belong together — `users/[id].page.tsx` → `users/[id].page.test.tsx`. Advisory only; a missing test is a gap, not a defect."), H("routes whose FIRST segment is dynamic — they claim every URL of that depth", r("top-level-dynamic-route"), "a route like `[id]/[code]` answers `/api/health`, `/internal/readiness` and anything else with that many segments on this origin. That is what the pattern means, so it is not a bug — but it is invisible until something requests such a URL, and then the page renders, its loader runs, and the failure reads as an application error rather than as a route claiming a path nobody meant it to. Give the route a literal first segment (`scan/[id]/[code]`) if you did not intend to own the whole origin at that depth. Advisory."), H("pages with no `meta` export — they serve the APP name as their title", r("page-without-meta"), "a page with no `meta` sets no title, so the response carries `<title>{your app.config name}</title>` — the same string on every such page, to every reader that does not run JavaScript: link previews, crawlers, a bookmark taken before hydration, a screen reader announcing the document. The client router fixes the tab after hydration, which is exactly why this is easy to miss. Add `export const meta = { title: \"…\" }` (or a function of params/loaderData). If such a page also has no real body — it redirects from an effect, say — the missing title is the symptom rather than the bug: on an `ssr` route the server answers 200 with an empty document and the redirect happens only once JavaScript runs. Move it into the loader (`redirect(...)`, a real 303). Advisory."), n.length;
1248
- }, Rt = "voltro doctor [app] [flags]\n\nPreflight an app root: verify it can boot under production `voltro serve`,\nreport shipped primitives the source looks to be hand-rolling, and flag pages\nthat are safe `renderMode:'spa'` candidates.\n\nArguments:\n [app] App directory to check (default: the current directory).\n\nFlags:\n --strict Treat a missing serve bundle as blocking regardless of NODE_ENV\n (the `voltro serve --preflight` behavior). Without it, a missing\n bundle only fails when NODE_ENV=production.\n --json Print the complete scan as JSON — hand-roll findings AND spa\n candidates, every file path, nothing elided. Skips the preflight output.\n --write-authz-allowlist\n Record today's executors that reference NO access check into\n voltro-authz-allowlist.txt, so every later run fails only on\n ADDITIONS. The list is DEBT, not approval — an entry stops\n mattering as soon as its executor gains a guard.\n -h, --help Print this help.", zt = /* @__PURE__ */ new Map(), U = (e) => {
1249
- let t = zt.get(e);
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);
1250
1341
  if (t !== void 0) return t;
1251
1342
  let n = (async () => {
1252
- let { walk: t, loadDiscovered: n } = await import("./dev-CjJAasDs.js"), r = await t(e);
1343
+ let { walk: t, loadDiscovered: n } = await import("./dev-DuqBJC7g.js"), r = await t(e);
1253
1344
  return {
1254
1345
  files: r,
1255
1346
  discovered: await n(r)
1256
1347
  };
1257
1348
  })();
1258
- return zt.set(e, n), n;
1259
- }, Bt = async (e) => {
1349
+ return Wt.set(e, n), n;
1350
+ }, Gt = async (e) => {
1260
1351
  let t;
1261
1352
  try {
1262
- let { files: n, discovered: r } = await U(e), i = r.tables.map((e) => {
1353
+ let { files: n, discovered: r } = await H(e), i = r.tables.map((e) => {
1263
1354
  let t = e;
1264
1355
  return {
1265
1356
  name: t.tableName,
@@ -1267,12 +1358,12 @@ var ce = /* @__PURE__ */ new Set([
1267
1358
  };
1268
1359
  });
1269
1360
  if (i.length === 0) return;
1270
- t = fe(de(n, i, e));
1361
+ t = pe(fe(n, i, e));
1271
1362
  } catch {
1272
1363
  return;
1273
1364
  }
1274
1365
  t !== null && (console.log(""), console.log(`${t.includes("does not exist") ? "✗" : "✓"} ${t}`));
1275
- }, Vt = (e) => e.map((e) => {
1366
+ }, Kt = (e) => e.map((e) => {
1276
1367
  let t = e, n = Object.entries(t.fields ?? {}).map(([e, t]) => ({
1277
1368
  name: e,
1278
1369
  type: typeof t?.type == "string" ? t.type : ""
@@ -1282,22 +1373,22 @@ var ce = /* @__PURE__ */ new Set([
1282
1373
  columns: n,
1283
1374
  primaryKey: t.appliedPrimaryKey ?? []
1284
1375
  };
1285
- }), W = async (e) => {
1376
+ }), U = async (e) => {
1286
1377
  try {
1287
- let { discovered: t } = await U(e);
1288
- return _e(Vt(t.tables));
1378
+ let { discovered: t } = await H(e);
1379
+ return ve(Kt(t.tables));
1289
1380
  } catch {
1290
1381
  return null;
1291
1382
  }
1292
- }, Ht = async (e) => {
1293
- let t = await W(e);
1383
+ }, qt = async (e) => {
1384
+ let t = await U(e);
1294
1385
  if (t === null || t.tablesChecked === 0) return;
1295
- let n = ye(t);
1386
+ let n = be(t);
1296
1387
  n !== null && (console.log(""), console.log(`${t.issues.length > 0 ? "✗" : "✓"} ${n}`));
1297
- }, G = async (e) => {
1388
+ }, W = async (e) => {
1298
1389
  try {
1299
- let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await U(e);
1300
- return Ce(n.events.map((e) => {
1390
+ let { payloadPropertyNames: t } = await import("@voltro/runtime"), { discovered: n } = await H(e);
1391
+ return we(n.events.map((e) => {
1301
1392
  let n = e.descriptor, r = t(n.key);
1302
1393
  return {
1303
1394
  name: n.name,
@@ -1309,19 +1400,19 @@ var ce = /* @__PURE__ */ new Set([
1309
1400
  } catch {
1310
1401
  return null;
1311
1402
  }
1312
- }, Ut = async (e) => {
1313
- let t = await G(e);
1403
+ }, Jt = async (e) => {
1404
+ let t = await W(e);
1314
1405
  if (t === null || t.eventsChecked === 0) return;
1315
- let n = we(t);
1406
+ let n = Te(t);
1316
1407
  n !== null && (console.log(""), console.log(`• ${n}`));
1317
- let r = Te(t);
1408
+ let r = Ee(t);
1318
1409
  r !== null && (console.log(""), console.log(`⚠️ ${r}`));
1319
- }, K = (e) => {
1410
+ }, G = (e) => {
1320
1411
  let t = e, n = typeof t?.message == "string" && t.message !== "" ? t.message : String(e), r = typeof t?.code == "string" ? t.code : void 0;
1321
1412
  return r === void 0 ? n : `${r}: ${n}`;
1322
- }, q = async (e) => {
1413
+ }, K = async (e) => {
1323
1414
  try {
1324
- let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await U(e)).discovered.serverOnlyLeaks, r = t(n);
1415
+ let { formatServerOnlyLeaks: t } = await import("@voltro/runtime"), n = (await H(e)).discovered.serverOnlyLeaks, r = t(n);
1325
1416
  return {
1326
1417
  status: {
1327
1418
  checked: !0,
@@ -1332,12 +1423,12 @@ var ce = /* @__PURE__ */ new Set([
1332
1423
  } catch (e) {
1333
1424
  return { status: {
1334
1425
  checked: !1,
1335
- reason: K(e)
1426
+ reason: G(e)
1336
1427
  } };
1337
1428
  }
1338
- }, J = async (r) => {
1429
+ }, q = async (r) => {
1339
1430
  try {
1340
- let { loadApiConfig: i } = await import("./dev-CjJAasDs.js"), { discovered: a } = await U(r), o = n(await i(r)), s = {
1431
+ let { loadApiConfig: i } = await import("./dev-DuqBJC7g.js"), { discovered: a } = await H(r), o = n(await i(r)), s = {
1341
1432
  queries: a.queries,
1342
1433
  mutations: a.mutations,
1343
1434
  actions: a.actions,
@@ -1353,33 +1444,33 @@ var ce = /* @__PURE__ */ new Set([
1353
1444
  } catch (e) {
1354
1445
  return {
1355
1446
  checked: !1,
1356
- reason: K(e),
1447
+ reason: G(e),
1357
1448
  defaultDeny: !0,
1358
1449
  undecided: [],
1359
1450
  openAccess: []
1360
1451
  };
1361
1452
  }
1362
- }, Wt = async (e) => {
1363
- let t = await J(e);
1453
+ }, Yt = async (e) => {
1454
+ let t = await q(e);
1364
1455
  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;
1365
1456
  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)}`);
1366
1457
  for (let e of t.openAccess) console.log(` ${e.tag} — ${e.openAccess ?? ""}`);
1367
1458
  if (t.undecided.length === 0) return !1;
1368
1459
  console.log("");
1369
1460
  for (let e of t.undecided) console.log(` ✗ ${e.tag} (${e.kind})${e.file === void 0 ? "" : `\n ${e.file}`}`);
1370
- return console.log(""), t.defaultDeny ? (console.log(" `voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:"), Gt(), !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."), Gt(), !1);
1371
- }, Gt = () => {
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 = () => {
1372
1463
  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");
1373
- }, Y = async (e) => {
1464
+ }, J = async (e) => {
1374
1465
  try {
1375
- let { files: t } = await U(e), { WEBHOOK_PATTERN: n, loadWebhookFiles: r } = await import("./webhookDiscovery-il9ti-HE.js"), { incomingWebhookVerification: i } = await import("@voltro/plugin-webhooks"), a = t.filter((e) => n.test(re(e)));
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)));
1376
1467
  if (a.length === 0) return {
1377
1468
  checked: !0,
1378
1469
  unverified: [],
1379
1470
  open: [],
1380
1471
  verified: 0
1381
1472
  };
1382
- let o = await r(a, se({ scope: "voltro:doctor" })), s = [], c = [], l = 0;
1473
+ let o = await r(a, ce({ scope: "voltro:doctor" })), s = [], c = [], l = 0;
1383
1474
  for (let e of o.incoming) {
1384
1475
  let t = i(e.descriptor);
1385
1476
  t === null ? s.push({
@@ -1399,14 +1490,14 @@ var ce = /* @__PURE__ */ new Set([
1399
1490
  } catch (e) {
1400
1491
  return {
1401
1492
  checked: !1,
1402
- reason: K(e),
1493
+ reason: G(e),
1403
1494
  unverified: [],
1404
1495
  open: [],
1405
1496
  verified: 0
1406
1497
  };
1407
1498
  }
1408
- }, Kt = async (e) => {
1409
- let t = await Y(e);
1499
+ }, Zt = async (e) => {
1500
+ let t = await J(e);
1410
1501
  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;
1411
1502
  if (t.verified + t.open.length + t.unverified.length === 0) return !1;
1412
1503
  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)}`);
@@ -1415,13 +1506,13 @@ var ce = /* @__PURE__ */ new Set([
1415
1506
  console.log("");
1416
1507
  for (let e of t.unverified) console.log(` ✗ ${e.id}\n ${e.file}`);
1417
1508
  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;
1418
- }, qt = async (e) => {
1419
- let { status: t, message: n } = await q(e);
1509
+ }, Qt = async (e) => {
1510
+ let { status: t, message: n } = await K(e);
1420
1511
  return t.checked ? n !== void 0 && (console.log(""), console.log(`✗ [serverOnly] ${n}`), !0) : (console.log(""), console.log("• serverOnly: NOT CHECKED — the app's descriptors could not be loaded (not a pass)"), console.log(` reason: ${t.reason ?? "unknown"}`), console.log(" This audit is the automated check that a hash or a token cannot reach the wire, so"), console.log(" an app that leans on `.serverOnly()` should treat a persistent skip as a failure."), console.log(" `voltro doctor --json` reports it as `serverOnly.checked: false` for a CI gate."), !1);
1421
- }, Jt = async (e) => Ye({
1422
- hasWorkflows: async () => (await U(e)).discovered.workflows.length > 0,
1512
+ }, $t = async (e) => et({
1513
+ hasWorkflows: async () => (await H(e)).discovered.workflows.length > 0,
1423
1514
  openStore: async () => {
1424
- let { loadApiConfig: t, buildStore: n } = await import("./dev-CjJAasDs.js"), r = await t(e);
1515
+ let { loadApiConfig: t, buildStore: n } = await import("./dev-DuqBJC7g.js"), r = await t(e);
1425
1516
  if (r === null) throw Error("no app.config.ts in this directory");
1426
1517
  let i = await n(r, void 0);
1427
1518
  return {
@@ -1435,14 +1526,14 @@ var ce = /* @__PURE__ */ new Set([
1435
1526
  let { sweepStalledRuns: t } = await import("@voltro/workflow");
1436
1527
  return t({ store: e });
1437
1528
  }
1438
- }), Yt = async (e) => {
1439
- let t = Ze(await Jt(e));
1529
+ }), en = async (e) => {
1530
+ let t = nt(await $t(e));
1440
1531
  t !== null && (console.log(""), console.log(t));
1441
- }, Xt = async (e) => {
1532
+ }, tn = async (e) => {
1442
1533
  let t;
1443
1534
  try {
1444
- let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await U(e);
1445
- t = Oe(r, i.workflows.map((e) => {
1535
+ let { payloadPropertyNames: n } = await import("@voltro/runtime"), { files: r, discovered: i } = await H(e);
1536
+ t = ke(r, i.workflows.map((e) => {
1446
1537
  let t = e.definition, r = t.payloadSchema === void 0 ? {
1447
1538
  required: [],
1448
1539
  optional: []
@@ -1456,23 +1547,23 @@ var ce = /* @__PURE__ */ new Set([
1456
1547
  } catch {
1457
1548
  return;
1458
1549
  }
1459
- let n = ke(t);
1550
+ let n = Ae(t);
1460
1551
  if (n === null) return;
1461
1552
  console.log("");
1462
1553
  let r = t.issues.length > 0 ? "✗" : t.unchecked.length > 0 ? "•" : "✓";
1463
1554
  console.log(`${r} ${n}`);
1464
- }, X = "voltro-authz-allowlist.txt", Zt = (e, t = (e) => {
1555
+ }, Y = "voltro-authz-allowlist.txt", nn = (e, t = (e) => {
1465
1556
  try {
1466
1557
  return C(e, "utf8");
1467
1558
  } catch {
1468
1559
  return "";
1469
1560
  }
1470
- }) => /* @__PURE__ */ new Set([...M, ...Fe(R(e).map(t))]), Qt = (e, t) => {
1561
+ }) => /* @__PURE__ */ new Set([...M, ...ze(L(e).map(t))]), rn = (e, t) => {
1471
1562
  let n = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1472
1563
  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];
1473
- }, Z = async (e) => {
1564
+ }, X = async (e) => {
1474
1565
  try {
1475
- let { discovered: t } = await U(e), n = t.events.map((e) => ({
1566
+ let { discovered: t } = await H(e), n = t.events.map((e) => ({
1476
1567
  name: e.descriptor.name,
1477
1568
  file: e.file
1478
1569
  })).filter((e) => typeof e.name == "string");
@@ -1483,34 +1574,34 @@ var ce = /* @__PURE__ */ new Set([
1483
1574
  } catch {
1484
1575
  return "";
1485
1576
  }
1486
- }, a = R(e).filter((e) => !r.has(e)).map(i).join("\n");
1577
+ }, a = L(e).filter((e) => !r.has(e)).map(i).join("\n");
1487
1578
  return n.filter(({ name: e, file: t }) => {
1488
1579
  if (a.includes(e)) return !1;
1489
- let n = Qt(i(t), e);
1580
+ let n = rn(i(t), e);
1490
1581
  return n === void 0 || !new RegExp(String.raw`\b${n}\b`).test(a);
1491
1582
  }).map((e) => e.name).sort();
1492
1583
  } catch {
1493
1584
  return [];
1494
1585
  }
1495
- }, $t = async (e) => {
1496
- let t = await Z(e);
1586
+ }, an = async (e) => {
1587
+ let t = await X(e);
1497
1588
  if (t.length !== 0) {
1498
1589
  console.log(""), console.log(`• ${t.length} declared event(s) with no emit call site found:`);
1499
1590
  for (let e of t) console.log(` ${e}`);
1500
1591
  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.");
1501
1592
  }
1502
- }, en = async (e) => {
1593
+ }, on = async (e) => {
1503
1594
  try {
1504
- let { loadApiConfig: t } = await import("./dev-CjJAasDs.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1595
+ let { loadApiConfig: t } = await import("./dev-DuqBJC7g.js"), n = (await t(e))?.doctor?.scopeVocabulary;
1505
1596
  if (typeof n != "string") return [];
1506
1597
  let [r, i] = n.split("#");
1507
1598
  if (r === void 0 || i === void 0) return [];
1508
- let a = (await import(oe(b(e, r)).href))[i];
1599
+ let a = (await import(se(b(e, r)).href))[i];
1509
1600
  return Array.isArray(a) ? a.filter((e) => typeof e == "string") : [];
1510
1601
  } catch {
1511
1602
  return [];
1512
1603
  }
1513
- }, Q = async (e) => {
1604
+ }, Z = async (e) => {
1514
1605
  let t = (e) => ({
1515
1606
  evaluated: !1,
1516
1607
  notEvaluated: e,
@@ -1518,7 +1609,7 @@ var ce = /* @__PURE__ */ new Set([
1518
1609
  findings: []
1519
1610
  });
1520
1611
  try {
1521
- let { offlineManifest: n, toInput: r } = await import("./checkCommand-D9qK_gtA.js"), { buildAppGraph: i, checkAppGraph: a } = await import("./appGraph-zuMGKVYX.js"), o = await n(e), s = r(o.manifest, o.tables), c = await en(e), l = [.../* @__PURE__ */ new Set([...s.scopes ?? [], ...c])];
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])];
1522
1613
  if (l.length === 0) {
1523
1614
  try {
1524
1615
  let { loadAppSchemaInputs: t } = await import("./frameworkTableAssembly-DgylYh9_.js");
@@ -1545,8 +1636,8 @@ var ce = /* @__PURE__ */ new Set([
1545
1636
  } catch {
1546
1637
  return t("app-not-importable");
1547
1638
  }
1548
- }, tn = async (e) => {
1549
- let { findings: t, evaluated: n, notEvaluated: r } = await Q(e);
1639
+ }, sn = async (e) => {
1640
+ let { findings: t, evaluated: n, notEvaluated: r } = await Z(e);
1550
1641
  if (!n) {
1551
1642
  if (console.log(""), r === "app-not-importable") {
1552
1643
  console.log("• scope vocabulary: NOT EVALUATED — `app.config.ts` could not be imported."), console.log(" This is not a statement about your scopes: the rule never got to read"), console.log(" them. The commonest cause is a config that demands its environment while"), console.log(" it is evaluated — an auth strategy reading a secret, say — so it imports"), console.log(" inside the container and not on a workstation."), console.log(""), console.log(" Run doctor where that environment exists, or make the config importable"), console.log(" without it. The same import failure is reported above for plugin tables;"), console.log(" both sections have this one cause.");
@@ -1560,7 +1651,7 @@ var ce = /* @__PURE__ */ new Set([
1560
1651
  for (let e of t) console.log(` ${e.node}${e.file === void 0 ? "" : ` ${e.file}`}`), console.log(` ${e.detail}`);
1561
1652
  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}`);
1562
1653
  }
1563
- }, nn = async (e) => {
1654
+ }, cn = async (e) => {
1564
1655
  let t = ["src/locales", "locales"], n, r = [];
1565
1656
  for (let i of t) {
1566
1657
  let t = v(e, i);
@@ -1572,23 +1663,38 @@ var ce = /* @__PURE__ */ new Set([
1572
1663
  if (n === void 0 || r.length === 0) return;
1573
1664
  let i = [];
1574
1665
  try {
1575
- let { loadApiConfig: t } = await import("./dev-CjJAasDs.js");
1666
+ let { loadApiConfig: t } = await import("./dev-DuqBJC7g.js");
1576
1667
  i = (await t(e))?.locales ?? [];
1577
1668
  } catch {
1578
1669
  return;
1579
1670
  }
1580
- let a = Ne({
1671
+ let a = Pe({
1581
1672
  catalogCodes: r,
1582
1673
  declaredLocales: i
1583
1674
  });
1584
1675
  if (a !== void 0) {
1585
1676
  console.log("");
1586
- for (let e of Pe(a, n)) console.log(e);
1677
+ for (let e of Fe(a, n)) console.log(e);
1587
1678
  }
1588
- }, rn = async (e) => {
1589
- let { discovered: t } = await U(e), n = [];
1679
+ }, ln = async (e) => {
1680
+ let t = v(e, "src", "pages");
1681
+ if (!S(t)) return !1;
1682
+ let n = [];
1590
1683
  try {
1591
- let { loadApiConfig: t } = await import("./dev-CjJAasDs.js");
1684
+ let { walkPagesTree: e } = await import("./webDev-D6QeXw3P.js");
1685
+ n = (await e(t)).pages.map((e) => e.pattern);
1686
+ } catch {
1687
+ return !1;
1688
+ }
1689
+ let r = Le(e, n);
1690
+ if (r === void 0) return !1;
1691
+ console.log("");
1692
+ for (let e of Re(r)) console.log(e);
1693
+ return r.problems.length > 0;
1694
+ }, un = async (e) => {
1695
+ let { discovered: t } = await H(e), n = [];
1696
+ try {
1697
+ let { loadApiConfig: t } = await import("./dev-DuqBJC7g.js");
1592
1698
  n = ((await t(e))?.plugins ?? []).map((e) => {
1593
1699
  let t = (e.extendSchema?.tables ?? []).map((e) => {
1594
1700
  let t = e;
@@ -1596,13 +1702,13 @@ var ce = /* @__PURE__ */ new Set([
1596
1702
  tableName: typeof t.tableName == "string" ? t.tableName : "",
1597
1703
  renamedFrom: typeof t.renamedFrom == "string" ? t.renamedFrom : void 0
1598
1704
  };
1599
- }).filter((e) => e.tableName !== ""), n = (e.rpcClientDescriptors ?? []).map((t) => typeof t.tag == "string" ? u(e, t.tag) : "").filter((e) => e !== ""), r = e.baseName ?? e.name;
1705
+ }).filter((e) => e.tableName !== ""), n = (e.rpcClientDescriptors ?? []).map((t) => typeof t.tag == "string" ? d(e, t.tag) : "").filter((e) => e !== ""), r = e.baseName ?? e.name;
1600
1706
  return {
1601
1707
  plugin: e.name,
1602
1708
  tables: t,
1603
1709
  rpcTags: n,
1604
- canDisableTables: an.has(r),
1605
- canAlias: on.has(r)
1710
+ canDisableTables: dn.has(r),
1711
+ canAlias: fn.has(r)
1606
1712
  };
1607
1713
  });
1608
1714
  } catch {
@@ -1614,18 +1720,18 @@ var ce = /* @__PURE__ */ new Set([
1614
1720
  ...t.mutations,
1615
1721
  ...t.actions,
1616
1722
  ...t.streams ?? []
1617
- ].map((e) => String(e.descriptor.name)), i = qe(n, {
1723
+ ].map((e) => String(e.descriptor.name)), i = Qe(n, {
1618
1724
  tables: t.tables.map((e) => e.tableName),
1619
1725
  rpcTags: r
1620
1726
  });
1621
1727
  console.log();
1622
- for (let e of Je(n, i)) console.log(e);
1623
- }, an = /* @__PURE__ */ new Set([
1728
+ for (let e of $e(n, i)) console.log(e);
1729
+ }, dn = /* @__PURE__ */ new Set([
1624
1730
  "@voltro/plugin-rbac",
1625
1731
  "@voltro/plugin-audit",
1626
1732
  "@voltro/plugin-notifications",
1627
1733
  "aiFlows"
1628
- ]), on = /* @__PURE__ */ new Set([
1734
+ ]), fn = /* @__PURE__ */ new Set([
1629
1735
  "aiFlows",
1630
1736
  "@voltro/plugin-audit",
1631
1737
  "@voltro/plugin-billing",
@@ -1642,8 +1748,8 @@ var ce = /* @__PURE__ */ new Set([
1642
1748
  "@voltro/plugin-sso-saml",
1643
1749
  "@voltro/plugin-storage",
1644
1750
  "@voltro/plugin-versioning"
1645
- ]), $ = 20, sn = async (e) => {
1646
- let t = await cn(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;
1751
+ ]), Q = 20, pn = async (e) => {
1752
+ 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;
1647
1753
  return {
1648
1754
  executors: t.executorCount,
1649
1755
  guardVocabulary: [...t.vocabulary].sort(),
@@ -1655,15 +1761,15 @@ var ce = /* @__PURE__ */ new Set([
1655
1761
  },
1656
1762
  unchecked: t.result.unchecked
1657
1763
  };
1658
- }, cn = async (e) => {
1659
- let { discovered: t } = await U(e), n = /* @__PURE__ */ new Map(), r = (e) => {
1764
+ }, $ = async (e) => {
1765
+ let { discovered: t } = await H(e), n = /* @__PURE__ */ new Map(), r = (e) => {
1660
1766
  if (!n.has(e)) try {
1661
1767
  n.set(e, C(e, "utf8"));
1662
1768
  } catch {
1663
1769
  n.set(e, "");
1664
1770
  }
1665
1771
  return n.get(e);
1666
- }, i = Zt(e, r), a = /* @__PURE__ */ new Set();
1772
+ }, i = nn(e, r), a = /* @__PURE__ */ new Set();
1667
1773
  for (let e of t.tables) for (let t of Object.values(e.fields ?? {})) try {
1668
1774
  let e = t.references?.();
1669
1775
  e && a.add(e.tableName);
@@ -1695,12 +1801,12 @@ var ce = /* @__PURE__ */ new Set([
1695
1801
  op: a?.op === "delete" || a?.op === "insert" ? a.op : t,
1696
1802
  ...a?.table === void 0 ? {} : { table: a.table }
1697
1803
  };
1698
- })), s = v(e, X), c = S(s) ? N(C(s, "utf8")) : void 0, l = !1;
1699
- for (let t of R(e)) if (/\bsetRowFilter\s*[<(]/.test(r(t))) {
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))) {
1700
1806
  l = !0;
1701
1807
  break;
1702
1808
  }
1703
- let u = Ve({
1809
+ let u = Ge({
1704
1810
  executors: o,
1705
1811
  guardVocabulary: i,
1706
1812
  tenantScopedTables: t.tenantScopedTables,
@@ -1717,46 +1823,46 @@ var ce = /* @__PURE__ */ new Set([
1717
1823
  executorCount: o.length,
1718
1824
  allowPath: s
1719
1825
  };
1720
- }, ln = async (e, t) => {
1721
- let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await cn(e), l = n.counts;
1826
+ }, mn = async (e, t) => {
1827
+ let { result: n, vocabulary: r, existing: i, reviewed: a, executorCount: o, allowPath: s, rowFilterRegistered: c } = await $(e), l = n.counts;
1722
1828
  console.log(`\nauthz scan · ${o} executor(s)`);
1723
1829
  let u = [...r].filter((e) => !M.has(e)).sort();
1724
1830
  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)}`);
1725
1831
  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;
1726
- if (console.log(` – accepted as recorded debt ${String(f).padStart(4)} (${X})`), console.log(` ✓ reviewed, open on purpose ${String(p).padStart(4)} (\`reviewed=<why>\` in that file)`), c && console.log(" ⓘ this app registers setRowFilter — the scan CANNOT see through it. A finding below may\n already be constrained; verify the filter covers that table and record it as `reviewed=`."), t) {
1727
- let e = S(s) ? C(s, "utf8") : void 0, t = n.unchecked.map((e) => e.tag), r = Ge(e, t);
1728
- return ae(s, We(e, t), "utf8"), console.log(`\n${X}: +${r.added} new, ${r.kept} kept, ${r.removed} removed. Doctor now fails on ADDITIONS only.`), r.added === 0 && console.log(" (nothing new was unchecked — the file is unchanged.)"), !1;
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;
1729
1835
  }
1730
1836
  if (n.unchecked.length === 0) return !1;
1731
1837
  console.log("");
1732
- for (let e of n.unchecked.slice(0, $)) console.log(` ✗ ${e.tag} — ${e.why}\n ${e.path}`);
1733
- return n.unchecked.length > $ && console.log(` … and ${n.unchecked.length - $} more — \`voltro doctor --json\` prints every one (\`authz.unchecked\`).`), i === void 0 ? (console.log(`\nNo ${X} yet, so this run is advisory. To start the ratchet:`), console.log("\n voltro doctor --write-authz-allowlist\n"), console.log("That records today's unchecked handlers as DEBT and fails every run after on"), console.log("anything NEW. Fix a handler and its line stops mattering — the scan reports it as"), console.log("guarded whether or not the line is still there."), !1) : (console.log(`\n${n.unchecked.length} executor(s) with no access check are not in ${X}.`), console.log("Add a guard, or add the tag to that file if it is genuinely open."), console.log("\nBefore hand-rolling another check: `guards: [{ action, resourceType, resource }]`"), console.log("answers \"may this subject act on THIS row\" declaratively, and an app whose"), console.log("relationships live in its own tables registers its own tuple source rather than"), console.log("copying data into a framework table. See the authorization docs."), !0);
1734
- }, un = async (e) => {
1735
- if (e.includes("--help") || e.includes("-h")) return console.log(Rt), 0;
1736
- let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), s = b(n ?? process.cwd());
1737
- if (h(e)) return g(), m({
1738
- ...z(s),
1739
- spaCandidates: await B(s),
1740
- pageConvention: V(s),
1741
- fileTaxonomy: ne(s).findings,
1742
- junctionFks: (await W(s))?.issues ?? [],
1743
- eventDelivery: await G(s),
1744
- duplicateInstances: a(s),
1745
- versionSkew: o(s) ?? null,
1746
- serverOnly: (await q(s)).status,
1747
- accessDecisions: await J(s),
1748
- webhookVerification: await Y(s),
1749
- authz: await sn(s),
1750
- ungrantedScopes: await Q(s),
1751
- neverEmittedEvents: await Z(s),
1752
- stalledRuns: await Jt(s)
1838
+ 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;
1842
+ let t = e.includes("--strict"), n = e.find((e) => !e.startsWith("-")), o = b(n ?? process.cwd());
1843
+ if (g(e)) return ee(), h({
1844
+ ...R(o),
1845
+ spaCandidates: await z(o),
1846
+ pageConvention: B(o),
1847
+ fileTaxonomy: re(o).findings,
1848
+ junctionFks: (await U(o))?.issues ?? [],
1849
+ eventDelivery: await W(o),
1850
+ duplicateInstances: i(o),
1851
+ 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),
1856
+ ungrantedScopes: await Z(o),
1857
+ neverEmittedEvents: await X(o),
1858
+ stalledRuns: await $t(o)
1753
1859
  }), 0;
1754
- let c = tt(s, t);
1755
- Tt(s), wt(s), Lt(s), te(s), await jt(s), await Xt(s), await Yt(s), await Bt(s), await Ht(s), await Ut(s), await rn(s), await tn(s), await $t(s), await nn(s);
1756
- let l = await qt(s), u = await ln(s, e.includes("--write-authz-allowlist")), d = await Wt(s), f = await Kt(s), p = r(o(s));
1757
- p !== void 0 && (console.log(""), console.log(p));
1758
- let _ = i(a(s));
1759
- return _ !== void 0 && console.log(`\n${_}`), l || u || d || f ? Math.max(c, 1) : c;
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));
1863
+ m !== void 0 && (console.log(""), console.log(m));
1864
+ let _ = a(i(o));
1865
+ return _ !== void 0 && console.log(`\n${_}`), l || u || d || p || f ? Math.max(c, 1) : c;
1760
1866
  };
1761
1867
  //#endregion
1762
- export { q as C, B as S, Y as T, jt as _, sn as a, yt as b, G as c, vt as d, $e as f, Lt as g, Tt as h, J as i, Zt as l, wt as m, et as n, Z as o, W as p, Et as r, Qt as s, Rt as t, z as u, un as v, Q as w, V as x, tt as y };
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 };