@lunora/codegen 1.0.0-alpha.7 → 1.0.0-alpha.71

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 (72) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/index.d.mts +2098 -736
  4. package/dist/index.d.ts +2098 -736
  5. package/dist/index.mjs +1 -29
  6. package/dist/packem_shared/AGENTS_FILENAME-BesaDYkL.mjs +1 -0
  7. package/dist/packem_shared/CONTAINERS_FILENAME-CFwdyaCI.mjs +1 -0
  8. package/dist/packem_shared/CodegenDiagnosticError-DPezpZTz.mjs +1 -0
  9. package/dist/packem_shared/FLAGS_FILENAME-C1GWerCB.mjs +1 -0
  10. package/dist/packem_shared/GENERATED_HEADER-DV-sQj-R.mjs +1 -0
  11. package/dist/packem_shared/LUNORA_ERROR_CODES-Um9hC1gr.mjs +1 -0
  12. package/dist/packem_shared/MUTATORS_FILENAME-BWPAtQ2T.mjs +1 -0
  13. package/dist/packem_shared/NOTIFY_FILENAME-DP6qvFBX.mjs +1 -0
  14. package/dist/packem_shared/OPENRPC_VERSION-CvVFqUlq.mjs +3 -0
  15. package/dist/packem_shared/QUEUES_FILENAME-CSE6i-ld.mjs +1 -0
  16. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-CDCj-7wp.mjs +3 -0
  17. package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-Bpf1FGMG.mjs +15 -0
  18. package/dist/packem_shared/SHAPES_FILENAME-maut3z1K.mjs +1 -0
  19. package/dist/packem_shared/WORKFLOWS_FILENAME-Bk5upkGM.mjs +1 -0
  20. package/dist/packem_shared/buildOpenApiDocument-CXFC-wzM.mjs +3 -0
  21. package/dist/packem_shared/discover-ast-CezKqEhE.mjs +1 -0
  22. package/dist/packem_shared/discoverAuthApiCalls-Ba6W92HB.mjs +1 -0
  23. package/dist/packem_shared/discoverCrons-2nUpw6do.mjs +1 -0
  24. package/dist/packem_shared/discoverFunctions-zrIQk7J8.mjs +1 -0
  25. package/dist/packem_shared/discoverHttpRoutes-Bf_T_03b.mjs +1 -0
  26. package/dist/packem_shared/discoverInserts-DveVeP5h.mjs +1 -0
  27. package/dist/packem_shared/discoverMaskProcedures-BkBquCFR.mjs +1 -0
  28. package/dist/packem_shared/discoverMigrations-T2xb-1rn.mjs +1 -0
  29. package/dist/packem_shared/discoverNondeterministicCalls-BymnOR9k.mjs +1 -0
  30. package/dist/packem_shared/discoverQueries-DNRA-G2a.mjs +1 -0
  31. package/dist/packem_shared/discoverR2sqlCalls-YEjvGcEq.mjs +1 -0
  32. package/dist/packem_shared/discoverRlsMetadata-DO3qlLXX.mjs +1 -0
  33. package/dist/packem_shared/discoverSandboxUsage-JwrmSN9l.mjs +1 -0
  34. package/dist/packem_shared/discoverSchema-Cy4OQ0Cu.mjs +1 -0
  35. package/dist/packem_shared/discoverStorageRulesMetadata-CCztRKjw.mjs +1 -0
  36. package/dist/packem_shared/emit-D7VIjvlm.mjs +2152 -0
  37. package/dist/packem_shared/emitApp-CAeIj6e_.mjs +444 -0
  38. package/dist/packem_shared/formatAdvisories-DZvv-0MG.mjs +2 -0
  39. package/dist/packem_shared/module-specifiers-8FEEiUcv.mjs +1 -0
  40. package/dist/packem_shared/parse-validator-CSDlMEvE.mjs +1 -0
  41. package/dist/packem_shared/paths-BmX5O1sG.mjs +1 -0
  42. package/dist/packem_shared/redact-CQ8-to6l.mjs +1 -0
  43. package/dist/packem_shared/schemaFromIr-R1ZFzVyy.mjs +1 -0
  44. package/package.json +10 -7
  45. package/dist/packem_shared/CONTAINERS_FILENAME-0K-pjNb8.mjs +0 -224
  46. package/dist/packem_shared/CodegenDiagnosticError-54jWDxA9.mjs +0 -22
  47. package/dist/packem_shared/GENERATED_HEADER-DF1hQcix.mjs +0 -2495
  48. package/dist/packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs +0 -61
  49. package/dist/packem_shared/OPENRPC_VERSION-C7zKVeKi.mjs +0 -60
  50. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-g7kpX8hu.mjs +0 -922
  51. package/dist/packem_shared/SCHEMA_SNAPSHOT_VERSION-DzLDbWk3.mjs +0 -233
  52. package/dist/packem_shared/WORKFLOWS_FILENAME-DRDQdhfq.mjs +0 -84
  53. package/dist/packem_shared/buildOpenApiDocument-Cns1EkCQ.mjs +0 -183
  54. package/dist/packem_shared/discover-ast-CT6BgBr4.mjs +0 -13
  55. package/dist/packem_shared/discoverAuthApiCalls-C35R6z0T.mjs +0 -62
  56. package/dist/packem_shared/discoverCrons-BL6iGuJ3.mjs +0 -254
  57. package/dist/packem_shared/discoverFunctions-DEgAcRuD.mjs +0 -460
  58. package/dist/packem_shared/discoverHttpRoutes-C978pBiG.mjs +0 -131
  59. package/dist/packem_shared/discoverInserts-pLBFsSAV.mjs +0 -61
  60. package/dist/packem_shared/discoverMaskProcedures-B64zA740.mjs +0 -217
  61. package/dist/packem_shared/discoverMigrations-Doj_-BAA.mjs +0 -91
  62. package/dist/packem_shared/discoverNondeterministicCalls-4KiPQxQU.mjs +0 -122
  63. package/dist/packem_shared/discoverQueries-BkIi0dBD.mjs +0 -62
  64. package/dist/packem_shared/discoverR2sqlCalls-BpDqvcUn.mjs +0 -80
  65. package/dist/packem_shared/discoverRlsMetadata-DpRB1HMe.mjs +0 -280
  66. package/dist/packem_shared/discoverSchema-DYl0QfDH.mjs +0 -775
  67. package/dist/packem_shared/discoverStorageRulesMetadata-DAqJUxUv.mjs +0 -97
  68. package/dist/packem_shared/emitApp-Ci_hcJNO.mjs +0 -599
  69. package/dist/packem_shared/formatAdvisories-8NIv1k0I.mjs +0 -69
  70. package/dist/packem_shared/parse-validator-tuQtHrsr.mjs +0 -132
  71. package/dist/packem_shared/paths-BRd6JHuF.mjs +0 -11
  72. package/dist/packem_shared/schemaFromIr-DTYsLBaA.mjs +0 -57
package/dist/index.d.mts CHANGED
@@ -1,14 +1,16 @@
1
- import { Finding } from '@lunora/advisor';
1
+ import { AdvisorExportSink, AdvisorGeoIndexUsage, AdvisorNotifyCall, AdvisorNotifyConfig, Finding } from '@lunora/advisor';
2
2
  export type { Finding } from '@lunora/advisor';
3
+ import { LunoraError } from '@lunora/errors';
4
+ export { MESSAGE_SOLUTIONS as LUNORA_SOLUTION_RULES, type Solution as LunoraSolution, type SolutionRule as LunoraSolutionRule, findSolutionByMessage as findLunoraSolution } from '@lunora/errors';
3
5
  import { Node, Project } from 'ts-morph';
4
6
  import { StudioFeaturesResult } from '@lunora/do';
5
7
  import { Schema } from '@lunora/server';
6
8
  import { JsonSchema } from '@lunora/values';
7
9
  /**
8
- * AST-observable subset of a column's modifier chain (`.unique()`, `.default()`,
9
- * …). Function-valued modifiers (`.$defaultFn`/`.$onUpdateFn`) can't be
10
- * serialized, so only their *presence* is recorded.
11
- */
10
+ * AST-observable subset of a column's modifier chain (`.unique()`, `.default()`,
11
+ * …). Function-valued modifiers (`.$defaultFn`/`.$onUpdateFn`) can't be
12
+ * serialized, so only their *presence* is recorded.
13
+ */
12
14
  interface ColumnMetaIR {
13
15
  /** `.default(...)` or `.$defaultFn(...)` present — field is optional on insert. */
14
16
  hasDefault?: boolean;
@@ -25,6 +27,15 @@ interface ValidatorIR {
25
27
  bucket?: string;
26
28
  /** Column modifiers (`.unique()`, `.default()`, `.nullable()`, …) when present. */
27
29
  column?: ColumnMetaIR;
30
+ /**
31
+ * `true` when this validator carries a `.check(...)` refinement. The predicate
32
+ * is a runtime closure the AST→IR step can't represent, so the node keeps its
33
+ * base `kind` but records the refinement's presence here. The AOT args-validator
34
+ * compiler declines any node with this flag (compiling it would silently skip
35
+ * the predicate). `.meta(...)` is pure metadata with no parse effect and does
36
+ * NOT set this.
37
+ */
38
+ hasRefinement?: boolean;
28
39
  /** For `v.optional(inner)` / `v.array(inner)`. */
29
40
  inner?: ValidatorIR;
30
41
  /** For `v.record(key, value)`. */
@@ -49,11 +60,32 @@ interface IndexIR {
49
60
  unique?: boolean;
50
61
  }
51
62
  interface SearchIndexIR {
52
- /** Primary text-search field. */
63
+ /** Primary text-search field; a dot-separated path reads a nested field. */
53
64
  field: string;
54
65
  /** Optional filter fields surfaced alongside the FTS column. */
55
66
  filterFields?: ReadonlyArray<string>;
67
+ /** Text-analysis profile (accent folding + that language's stopwords). */
68
+ language?: string;
56
69
  name: string;
70
+ /** Skip the migration-time backfill of the search companion (large tables index out-of-band). */
71
+ staged?: boolean;
72
+ /** `"native"` opts into the engine's own full-text index where it has one (Postgres). */
73
+ strategy?: string;
74
+ }
75
+ /** A `.geoIndex(name, { field, precision? })` declaration — a geohash companion over a `v.geoPoint()` column. */
76
+ interface GeoIndexIR {
77
+ /** The `v.geoPoint()` column feeding the geohash. */
78
+ field: string;
79
+ name: string;
80
+ /** Geohash precision (characters) maintained on the companion; omitted ⇒ the runtime default. */
81
+ precision?: number;
82
+ }
83
+ /** A `.ttl(field, { after? })` declaration — declarative table-level auto-expiry. */
84
+ interface TtlIR {
85
+ /** Millisecond offset added to `field` to derive the expiry (`field + after`); omitted ⇒ `field` is the absolute expiry. */
86
+ after?: number;
87
+ /** The epoch-millisecond expiry column. */
88
+ field: string;
57
89
  }
58
90
  interface VectorIndexIR {
59
91
  dimensions?: number;
@@ -67,19 +99,19 @@ interface VectorIndexIR {
67
99
  table: string;
68
100
  }
69
101
  /**
70
- * One ordering key on a rank index's `sortBy`: the column and direction.
71
- * Mirrors the runtime `RankSortKey` (defaults `direction` to `"asc"`).
72
- */
102
+ * One ordering key on a rank index's `sortBy`: the column and direction.
103
+ * Mirrors the runtime `RankSortKey` (defaults `direction` to `"asc"`).
104
+ */
73
105
  interface RankSortKeyIR {
74
106
  direction: "asc" | "desc";
75
107
  field: string;
76
108
  }
77
109
  /**
78
- * A `.rankIndex(name, { sortBy, partitionBy?, where? })` declaration. The owning
79
- * table is always the table the index is declared on, so — unlike a vector index
80
- * — there is no separate `on`/`table` reference to carry: it rides along on its
81
- * {@link TableIR}. Only the fields needed for type emission are captured.
82
- */
110
+ * A `.rankIndex(name, { sortBy, partitionBy?, where? })` declaration. The owning
111
+ * table is always the table the index is declared on, so — unlike a vector index
112
+ * — there is no separate `on`/`table` reference to carry: it rides along on its
113
+ * {@link TableIR}. Only the fields needed for type emission are captured.
114
+ */
83
115
  interface RankIndexIR {
84
116
  name: string;
85
117
  /** Columns scoping each ranking; omitted ⇒ one global rank over the table. */
@@ -100,21 +132,79 @@ interface RelationIR {
100
132
  /** Target table name. */
101
133
  table: string;
102
134
  }
135
+ /**
136
+ * Statically-discovered `.source(...)` config (plan 077). Only the bits the
137
+ * advisor lints + DO wiring need are captured; `map`/`tenantBy` are functions and
138
+ * cannot be serialized, so their presence is recorded as `hasTenantBy` rather than
139
+ * the function itself.
140
+ */
141
+ interface ExternalSourceIR {
142
+ /** The wrangler Hyperdrive binding name. */
143
+ binding: string;
144
+ /** Whether a `columns` projection allow-list was given. */
145
+ columns?: ReadonlyArray<string>;
146
+ /** `true` when a `reconcileEveryMs` was given — one of the two incremental delete-visibility paths the `external_source_incremental_no_delete_path` lint checks. */
147
+ hasReconcile?: boolean;
148
+ /** `true` when a `softDeleteColumn` was given — the other incremental delete-visibility path. */
149
+ hasSoftDelete?: boolean;
150
+ /** `true` when a `tenantBy` mapper was given — the tenant-isolation boundary the `external_source_unscoped` lint checks. */
151
+ hasTenantBy: boolean;
152
+ /** The `idColumn` literal, when given (defaults to `"id"` at runtime). */
153
+ idColumn?: string;
154
+ /** Delete-detection mode literal, when given (`"full-pull"` today). */
155
+ mode?: string;
156
+ /** The membership query literal, when statically knowable. */
157
+ query?: string;
158
+ /**
159
+ * `true` when `.source(...)` was present but its argument was **not** a static
160
+ * object literal (e.g. `.source(buildConfig())`), so none of the fields above
161
+ * could be read. The source still exists — this flag lets `hasSourcedTables`
162
+ * (codegen) and the `external_source_*` lints treat it as a source that can't be
163
+ * verified, instead of mistaking it for no `.source()` at all.
164
+ */
165
+ unanalyzable?: boolean;
166
+ }
103
167
  interface TableIR {
104
168
  /**
105
- * `true` when the table chain carried `.externallyManaged()` its rows are
106
- * written outside Lunora's discoverable insert path (adapter/migration/
107
- * middleware), so advisor insert-path lints skip it. Optional: hand-built
108
- * IR and the runtime `fromServerSchema` path default it to `false`.
109
- */
169
+ * The `defineSchemaExtension` key that contributed this table, set when it
170
+ * arrived through `defineSchema(...).extend(...)`. Absent for a table the app
171
+ * declared itself.
172
+ *
173
+ * Drives the generated `AppTableName` union: an add-on's tables
174
+ * (`ratelimit_buckets`, …) are real tables and stay in `TableName`, but an app
175
+ * enumerating "my tables" should not have to know about them.
176
+ */
177
+ extensionKey?: string;
178
+ /**
179
+ * `true` when the table chain carried `.externallyManaged()` — its rows are
180
+ * written outside Lunora's discoverable insert path (adapter/migration/
181
+ * middleware), so advisor insert-path lints skip it. Optional: hand-built
182
+ * IR and the runtime `fromServerSchema` path default it to `false`.
183
+ */
110
184
  externallyManaged?: boolean;
111
185
  /**
112
- * Storage backend for a `.global()` table: `"d1"` (default) or
113
- * `"hyperdrive"` (a Postgres/MySQL database via Cloudflare Hyperdrive). Only
114
- * meaningful when `shardMode === "global"`; absent for sharded/root tables.
115
- */
186
+ * Set when the chain carried `.source(...)` — the table is materialized from an
187
+ * external Hyperdrive-backed database by a system poll loop (plan 077). Carries
188
+ * the statically-knowable bits the advisor lints read; the functions (`map`,
189
+ * `tenantBy`) are not serialized, only their presence (`hasTenantBy`).
190
+ */
191
+ externalSource?: ExternalSourceIR;
192
+ /**
193
+ * Storage backend for a `.global()` table: `"d1"` (default) or
194
+ * `"hyperdrive"` (a Postgres/MySQL database via Cloudflare Hyperdrive). Only
195
+ * meaningful when `shardMode === "global"`; absent for sharded/root tables.
196
+ */
197
+ /** Geospatial indexes declared inline via `.geoIndex(name, …)`. Optional so hand-built IR may omit it (discovery always sets it). */
198
+ geoIndexes?: ReadonlyArray<GeoIndexIR>;
116
199
  globalBackend?: "d1" | "hyperdrive";
117
200
  indexes: ReadonlyArray<IndexIR>;
201
+ /**
202
+ * `true` when the table chain carried `.public()` — an explicit opt-OUT of
203
+ * the schema's `.rls("required")` enforcement for this one table. Optional:
204
+ * hand-built IR and tables that never called `.public()` default it to
205
+ * `false`.
206
+ */
207
+ isPublic?: boolean;
118
208
  name: string;
119
209
  /** Rank indexes declared inline via `.rankIndex(name, …)`. */
120
210
  rankIndexes: ReadonlyArray<RankIndexIR>;
@@ -126,10 +216,32 @@ interface TableIR {
126
216
  field: string;
127
217
  kind: "shardBy";
128
218
  };
219
+ /** Set when the chain carried `.softDelete()` — the marker column's name (default `deletedAt`). The column is injected into `shape` so `Doc_*` carries it. */
220
+ softDelete?: {
221
+ field: string;
222
+ };
223
+ /** Set when the chain carried `.ttl(field, { after? })` — the declarative auto-expiry policy read by the DO alarm sweep. */
224
+ ttl?: TtlIR;
129
225
  /** Vector indexes declared inline via `.vectorize()` (DSL Shape A). */
130
226
  vectorIndexes: ReadonlyArray<VectorIndexIR>;
131
227
  }
228
+ /** The Cloudflare DO data-residency jurisdictions the schema may declare. Canonical literal set for the codegen package. */
229
+ type JurisdictionIR = "eu" | "fedramp" | "us";
132
230
  interface SchemaIR {
231
+ /**
232
+ * Cloudflare data-residency jurisdiction declared via
233
+ * `defineSchema(...).jurisdiction("…")`. Emitted into the generated worker's
234
+ * `createWorker({ jurisdiction })` (and `ctx.scheduler` / `ctx.containers`).
235
+ * Absent ⇒ un-pinned.
236
+ */
237
+ jurisdiction?: JurisdictionIR;
238
+ /**
239
+ * Set when `defineSchema(...).rls("required")` was chained onto the schema —
240
+ * every table's `ctx.db` write path is denied without an RLS-covering
241
+ * procedure unless the table itself is `.public()` (see {@link TableIR.isPublic}).
242
+ * Absent when the schema never called `.rls("required")`.
243
+ */
244
+ rlsMode?: "required";
133
245
  tables: ReadonlyArray<TableIR>;
134
246
  /** All vector indexes (inline Shape A hoisted + standalone Shape B), flattened. */
135
247
  vectorIndexes: ReadonlyArray<VectorIndexIR>;
@@ -137,37 +249,47 @@ interface SchemaIR {
137
249
  interface FunctionIR {
138
250
  args: Record<string, ValidatorIR>;
139
251
  exportName: string;
252
+ /**
253
+ * Set by the `.expose({ rest: true })` builder modifier (plan 167). When
254
+ * `rest` is `true` the function is published on the public REST surface, so the
255
+ * OpenAPI emitter describes it as a real `/_lunora/rest/&lt;namespace>/&lt;fn>` path
256
+ * (the single source of truth the runtime router also derives from). Absent →
257
+ * RPC-only (the default; not on the REST surface).
258
+ */
259
+ expose?: {
260
+ rest?: boolean;
261
+ };
140
262
  /** Path relative to `&lt;projectRoot>/lunora/` without extension, e.g. "messages". */
141
263
  filePath: string;
142
264
  kind: "action" | "mutation" | "query" | "stream";
143
265
  /**
144
- * Set on connection-lifecycle hooks (`onConnect`/`onDisconnect`): the socket
145
- * side the hook fires on. Such a function is also an internal mutation (so it
146
- * lands in `LUNORA_FUNCTIONS` for path dispatch); emit additionally collects
147
- * it into the `LUNORA_LIFECYCLE_HOOKS` manifest keyed by this side. Absent on
148
- * ordinary functions.
149
- */
266
+ * Set on connection-lifecycle hooks (`onConnect`/`onDisconnect`): the socket
267
+ * side the hook fires on. Such a function is also an internal mutation (so it
268
+ * lands in `LUNORA_FUNCTIONS` for path dispatch); emit additionally collects
269
+ * it into the `LUNORA_LIFECYCLE_HOOKS` manifest keyed by this side. Absent on
270
+ * ordinary functions.
271
+ */
150
272
  lifecycle?: "connect" | "disconnect";
151
273
  /**
152
- * Serialized TS source for the handler's return type, with `Promise&lt;T>`
153
- * unwrapped so callers see `T` directly. Defaults to `"unknown"` when
154
- * ts-morph cannot resolve the type (typically because the consuming
155
- * project lacks a tsconfig that can reach `@lunora/server`).
156
- */
274
+ * Serialized TS source for the handler's return type, with `Promise&lt;T>`
275
+ * unwrapped so callers see `T` directly. Defaults to `"unknown"` when
276
+ * ts-morph cannot resolve the type (typically because the consuming
277
+ * project lacks a tsconfig that can reach `@lunora/server`).
278
+ */
157
279
  returnType: string;
158
280
  /**
159
- * Call surface the function is exposed on. Absent (or `"public"`) means it
160
- * lands in the generated `api`; `"internal"` routes it to the separate
161
- * `internal` object and is rejected by the DO's external RPC path.
162
- */
281
+ * Call surface the function is exposed on. Absent (or `"public"`) means it
282
+ * lands in the generated `api`; `"internal"` routes it to the separate
283
+ * `internal` object and is rejected by the DO's external RPC path.
284
+ */
163
285
  visibility?: "internal" | "public";
164
286
  }
165
287
  /**
166
- * A `defineMigration({...})` declaration discovered in the user's lunora
167
- * sources. The emitted `LUNORA_MIGRATIONS` registry keys on {@link MigrationIR.id}; the
168
- * import wiring needs {@link MigrationIR.exportName}/{@link MigrationIR.filePath}. {@link MigrationIR.table} is
169
- * informational (the runtime object carries the authoritative value).
170
- */
288
+ * A `defineMigration({...})` declaration discovered in the user's lunora
289
+ * sources. The emitted `LUNORA_MIGRATIONS` registry keys on {@link MigrationIR.id}; the
290
+ * import wiring needs {@link MigrationIR.exportName}/{@link MigrationIR.filePath}. {@link MigrationIR.table} is
291
+ * informational (the runtime object carries the authoritative value).
292
+ */
171
293
  interface MigrationIR {
172
294
  /** Export binding name, used to reference the module member in generated imports. */
173
295
  exportName: string;
@@ -179,12 +301,109 @@ interface MigrationIR {
179
301
  table: string;
180
302
  }
181
303
  /**
182
- * A single cron job lifted from a `cronJobs()` builder in `lunora/crons.ts`.
183
- * Mirrors `@lunora/scheduler`'s `CronJob`: {@link CronJobIR.cron} is the compiled
184
- * standard cron expression, {@link CronJobIR.functionPath} is the target
185
- * `__lunoraRef` (`namespace:fn`), and {@link CronJobIR.args} is the static
186
- * argument object passed at registration.
187
- */
304
+ * A `defineShape({...})` declaration discovered in `lunora/shapes.ts`
305
+ * (local-first sync engine, Phase 7). The emitted `LUNORA_SHAPES` registry keys
306
+ * on {@link ShapeIR.exportName}; the generated DO's `resolveShape` override
307
+ * dispatches a `shape_subscribe` to the matching registered shape. Discovery is
308
+ * marker-driven (the `__lunoraShape` brand) — no field metadata is lifted here
309
+ * because the runtime object (`columns`/`compileWhere`) carries the authority.
310
+ */
311
+ interface ShapeIR {
312
+ /**
313
+ * The shape's `args` validator map — its partition selector. Lifted so
314
+ * `_generated/collections.ts` can type the selector a caller passes instead of
315
+ * widening it to `Record&lt;string, unknown>`. `{}` for a parameterless shape.
316
+ */
317
+ args: Record<string, ValidatorIR>;
318
+ /** Export binding name — the shape's registry key and import member. */
319
+ exportName: string;
320
+ /** Path relative to `&lt;projectRoot>/lunora/` without extension — always `"shapes"`. */
321
+ filePath: string;
322
+ /**
323
+ * The `table` string literal from the `defineShape({ table })` call, lifted
324
+ * only for static advisor lints (the runtime object stays authoritative).
325
+ * `undefined` when `table` is not a plain string literal — lints skip those.
326
+ */
327
+ table?: string;
328
+ }
329
+ /**
330
+ * The single `defineIdentity({...})` claim contract discovered in
331
+ * `lunora/identity.ts`. Discovery is **marker-driven** (the `__lunoraIdentity`
332
+ * brand, exactly like {@link ShapeIR}) — no claim metadata is lifted here
333
+ * because the emitted `_generated/server.ts` recovers the claim *type* from the
334
+ * declaration itself (`InferIdentity` over the contract's `typeof`), and the
335
+ * runtime object (`validate`/`onInvalid`) carries the authority at the boundary.
336
+ * Exactly one per app; absent ⇒ generated output is byte-identical to today.
337
+ */
338
+ interface IdentityIR {
339
+ /** Export binding name — the namespace member `_generated/server.ts` reads via `typeof`. */
340
+ exportName: string;
341
+ }
342
+ /**
343
+ * The single `defineEnv({...})` contract discovered in `lunora/env.ts`. Like
344
+ * {@link IdentityIR}, only the export binding is lifted — the emitted
345
+ * `_generated/server.ts` recovers the validated shape from the declaration
346
+ * itself (`ReturnType` over the accessor's `typeof`), and the generated ShardDO
347
+ * applies the same accessor to the worker `env` at ctx-build time to populate
348
+ * `ctx.env`. Exactly one per app; absent ⇒ generated output is byte-identical.
349
+ */
350
+ interface EnvIR {
351
+ /** Export binding name — the namespace member `_generated/server.ts` reads via `typeof`. */
352
+ exportName: string;
353
+ }
354
+ /**
355
+ * A `defineMutator({...})` declaration discovered in `lunora/mutators.ts`
356
+ * (local-first sync engine, Phase 7). The emitted registry registers the
357
+ * authoritative `server` impl into the DO's `LUNORA_FUNCTIONS` table (so
358
+ * `handleRpc` transaction-wraps it) and records its path in
359
+ * `LUNORA_MUTATOR_PATHS` so the DO's `isCustomMutator` override routes the
360
+ * client-watermark push protocol. The client `client` impl is split into the
361
+ * browser bundle separately — only the path crosses to the server side.
362
+ */
363
+ interface MutatorIR {
364
+ /**
365
+ * The mutator's `args` validator map, parsed exactly as a procedure's is, so
366
+ * the emitted `api.mutators.&lt;name>` reference carries the arg type a client
367
+ * `defineMutator` infers instead of restating. `{}` for a parameterless
368
+ * mutator (or one whose `args` isn't an inline object literal).
369
+ */
370
+ args: Record<string, ValidatorIR>;
371
+ /** Export binding name — the mutator's registry key and import member. */
372
+ exportName: string;
373
+ /** Path relative to `&lt;projectRoot>/lunora/` without extension — always `"mutators"`. */
374
+ filePath: string;
375
+ /**
376
+ * Serialized TS source for the authoritative `server` impl's return type,
377
+ * `Promise&lt;T>` unwrapped. `"unknown"` when ts-morph can't resolve it — same
378
+ * contract as {@link FunctionIR.returnType}.
379
+ */
380
+ returnType: string;
381
+ }
382
+ /**
383
+ * A whole-row `ctx.db.replace(id, document)` write discovered inside a custom
384
+ * mutator's inline `server` impl (`lunora/mutators.ts`) — the input the
385
+ * `mutator_full_row_replace` advisor lint consumes. A `replace` overwrites the
386
+ * entire row, so a concurrent edit to a different column on a synced table is
387
+ * clobbered; `ctx.db.patch(id, { field })` merges at the column level instead.
388
+ * Structurally identical to `AdvisorMutatorWrite` so it passes straight through
389
+ * to the advisor without conversion, exactly as `InsertWriteIR` does for
390
+ * `AdvisorInsertWrite`.
391
+ */
392
+ interface MutatorWriteIR {
393
+ /** The mutator export whose `server` impl performs the replace, e.g. `renameChannel`. */
394
+ exportName: string;
395
+ /** Openable source path the replace appears in — always `lunora/mutators.ts`. */
396
+ file: string;
397
+ /** 1-based line of the `replace(...)` call. */
398
+ line: number;
399
+ }
400
+ /**
401
+ * A single cron job lifted from a `cronJobs()` builder in `lunora/crons.ts`.
402
+ * Mirrors `@lunora/scheduler`'s `CronJob`: {@link CronJobIR.cron} is the compiled
403
+ * standard cron expression, {@link CronJobIR.functionPath} is the target
404
+ * `__lunoraRef` (`namespace:fn`), and {@link CronJobIR.args} is the static
405
+ * argument object passed at registration.
406
+ */
188
407
  interface CronJobIR {
189
408
  /** Static args object (source-text JSON), defaults to `{}`. */
190
409
  args: Record<string, unknown>;
@@ -195,25 +414,25 @@ interface CronJobIR {
195
414
  /** Unique, human-readable job name. */
196
415
  name: string;
197
416
  /**
198
- * Set when the job targets a durable workflow (a `lunora/workflows.ts`
199
- * export) instead of a function: the workflow's `WORKFLOW_*` binding name
200
- * plus its export name. On each fire the worker starts a new workflow
201
- * INSTANCE (the {@link CronJobIR.args} become its `params`) rather than
202
- * dispatching a one-shot function.
203
- */
417
+ * Set when the job targets a durable workflow (a `lunora/workflows.ts`
418
+ * export) instead of a function: the workflow's `WORKFLOW_*` binding name
419
+ * plus its export name. On each fire the worker starts a new workflow
420
+ * INSTANCE (the {@link CronJobIR.args} become its `params`) rather than
421
+ * dispatching a one-shot function.
422
+ */
204
423
  workflow?: {
205
424
  binding: string;
206
425
  exportName: string;
207
426
  };
208
427
  }
209
428
  /**
210
- * A container lifted from a `defineContainer()` export in
211
- * `lunora/containers.ts`. Carries everything the emitters and the config layer
212
- * need to wire wrangler (`containers[]` + the Durable Object binding +
213
- * migration class) and the generated `_generated/containers.ts` DO class.
214
- * Names are derived via `@lunora/container`'s shared helpers so codegen and
215
- * the config layer can never disagree.
216
- */
429
+ * A container lifted from a `defineContainer()` export in
430
+ * `lunora/containers.ts`. Carries everything the emitters and the config layer
431
+ * need to wire wrangler (`containers[]` + the Durable Object binding +
432
+ * migration class) and the generated `_generated/containers.ts` DO class.
433
+ * Names are derived via `@lunora/container`'s shared helpers so codegen and
434
+ * the config layer can never disagree.
435
+ */
217
436
  interface ContainerIR {
218
437
  /** Durable Object binding name, e.g. `CONTAINER_TRANSCODER`. */
219
438
  bindingName: string;
@@ -222,18 +441,18 @@ interface ContainerIR {
222
441
  /** Generated DO class name, e.g. `TranscoderContainer`. */
223
442
  className: string;
224
443
  /**
225
- * Whether the container may open outbound internet connections, when the
226
- * value was a static literal. `undefined` means the field was omitted (the
227
- * platform default is `true`) or wasn't a literal. Lifted for the advisor.
228
- */
444
+ * Whether the container may open outbound internet connections, when the
445
+ * value was a static literal. `undefined` means the field was omitted (the
446
+ * platform default is `true`) or wasn't a literal. Lifted for the advisor.
447
+ */
229
448
  enableInternet?: boolean;
230
449
  /** The `lunora/containers.ts` export name, e.g. `transcoder`. */
231
450
  exportName: string;
232
451
  /**
233
- * Normalized image source: a local Dockerfile (`dockerfile`), a pre-built
234
- * registry reference (`registry`), or a Railpack source directory (`build`)
235
- * that the deploy step builds and pushes before wrangler runs.
236
- */
452
+ * Normalized image source: a local Dockerfile (`dockerfile`), a pre-built
453
+ * registry reference (`registry`), or a Railpack source directory (`build`)
454
+ * that the deploy step builds and pushes before wrangler runs.
455
+ */
237
456
  image: {
238
457
  buildContext: string;
239
458
  dockerfilePath: string;
@@ -261,20 +480,20 @@ interface ContainerIR {
261
480
  stepPercentage?: number;
262
481
  };
263
482
  /**
264
- * The static `sleepAfter` value, when it was a literal. `undefined` means
265
- * omitted (platform default `"10m"`) or non-literal. Lifted for the advisor.
266
- */
483
+ * The static `sleepAfter` value, when it was a literal. `undefined` means
484
+ * omitted (platform default `"10m"`) or non-literal. Lifted for the advisor.
485
+ */
267
486
  sleepAfter?: number | string;
268
487
  }
269
488
  /**
270
- * A workflow lifted from a `defineWorkflow()` export in `lunora/workflows.ts`.
271
- * Carries what the emitters and the config layer need to wire the wrangler
272
- * `workflows[]` entry and the generated `_generated/workflows.ts`
273
- * `WorkflowEntrypoint` class. Unlike containers, workflows are NOT Durable
274
- * Objects — wrangler gets only a `workflows[]` entry, never a `durable_objects`
275
- * binding or a migration class. Names are derived via `@lunora/workflow`'s
276
- * shared helpers so codegen and the config layer can never disagree.
277
- */
489
+ * A workflow lifted from a `defineWorkflow()` export in `lunora/workflows.ts`.
490
+ * Carries what the emitters and the config layer need to wire the wrangler
491
+ * `workflows[]` entry and the generated `_generated/workflows.ts`
492
+ * `WorkflowEntrypoint` class. Unlike containers, workflows are NOT Durable
493
+ * Objects — wrangler gets only a `workflows[]` entry, never a `durable_objects`
494
+ * binding or a migration class. Names are derived via `@lunora/workflow`'s
495
+ * shared helpers so codegen and the config layer can never disagree.
496
+ */
278
497
  interface WorkflowIR {
279
498
  /** The Cloudflare `Workflow` binding name, e.g. `WORKFLOW_ORDER_PIPELINE`. */
280
499
  bindingName: string;
@@ -283,21 +502,152 @@ interface WorkflowIR {
283
502
  /** The `lunora/workflows.ts` export name, e.g. `orderPipeline`. */
284
503
  exportName: string;
285
504
  /**
286
- * The stable wrangler `workflows[].name`. Defaults to the kebab-cased export
287
- * name (`orderPipeline` → `order-pipeline`); a static `name:` literal in the
288
- * definition overrides it.
289
- */
505
+ * The stable wrangler `workflows[].name`. Defaults to the kebab-cased export
506
+ * name (`orderPipeline` → `order-pipeline`); a static `name:` literal in the
507
+ * definition overrides it.
508
+ */
509
+ name: string;
510
+ /**
511
+ * Durable step labels lifted from the handler body — the first string-literal
512
+ * argument of every `ctx.step.do` / `.sleep` / `.sleepUntil` / `.waitForEvent`
513
+ * call. Feeds the duplicate-step-name lint, which flags a name used twice
514
+ * (Cloudflare memoizes by name, so the second call silently returns the
515
+ * first's cached result). Calls with a non-literal name are omitted (not
516
+ * statically comparable).
517
+ */
518
+ steps: ReadonlyArray<WorkflowStepIR>;
519
+ }
520
+ /**
521
+ * An agent lifted from a `defineAgent()` export in `lunora/agents.ts`. A
522
+ * `defineAgent` compiles its durable tool-loop onto a Cloudflare Workflow, so —
523
+ * like {@link WorkflowIR} — an agent is NOT a Durable Object: wrangler gets only
524
+ * a `workflows[]` entry, never a `durable_objects` binding or a migration class.
525
+ * Carries what the emitters and the config layer need to wire the generated
526
+ * agent `WorkflowEntrypoint` class (e.g. `SupportAgentWorkflow`), the typed
527
+ * per-agent `ctx.agents` producer, and the reconciled wrangler `workflows[]`
528
+ * entry. Names are derived via `@lunora/agent`'s shared helpers so codegen and
529
+ * the config layer can never disagree.
530
+ */
531
+ interface AgentIR {
532
+ /** The Cloudflare `Workflow` binding name, e.g. `AGENT_SUPPORT`. */
533
+ bindingName: string;
534
+ /** Generated `WorkflowEntrypoint` class name, e.g. `SupportAgentWorkflow`. */
535
+ className: string;
536
+ /** The `lunora/agents.ts` export name, e.g. `support`. */
537
+ exportName: string;
538
+ /**
539
+ * The stable wrangler `workflows[].name`. Defaults to the kebab-cased export
540
+ * name (`support` → `agent-support`); a static `name:` literal in the
541
+ * definition overrides it.
542
+ */
543
+ name: string;
544
+ /**
545
+ * Whether the definition declares an `onEmail` mapper on
546
+ * `defineAgent({ onEmail: … })`. When `true` the emitter wires this agent
547
+ * onto the worker's top-level `email()` handler (via `@lunora/agent/inbound`)
548
+ * so inbound mail starts a durable run. Detected by AST PRESENCE — the
549
+ * closure is never evaluated — and written to IR only when present, so
550
+ * email-free agents (and agent-free projects) stay byte-identical.
551
+ */
552
+ onEmail?: boolean;
553
+ /**
554
+ * Whether the definition opted into public run-starts via
555
+ * `defineAgent({ publicRun: true })` — emitted into the `ctx.agents` wiring
556
+ * spec so the public `agents:agentRun` mutation can gate on it fail-closed.
557
+ * Absent (falsy) means server-side starts only; the field is written to IR
558
+ * only when the literal is `true`, so agent-free and non-opted-in output is
559
+ * byte-identical.
560
+ */
561
+ publicRun?: boolean;
562
+ /**
563
+ * Whether the definition opted into a real-time voice session via a `voice`
564
+ * block on `defineAgent({ voice: … })`. Unlike the durable loop (a Workflow),
565
+ * the voice path IS a Durable Object — so when this is `true` the emitter
566
+ * generates the `voiceClassName` `VoiceSessionDO` subclass and the
567
+ * `api.agents.{name}Voice` client reference, and the config layer reconciles
568
+ * a `durable_objects` binding (`voiceBindingName`) + `new_sqlite_classes`
569
+ * migration. Written to IR only when the literal is present, so voice-free
570
+ * agents (and agent-free projects) stay byte-identical.
571
+ */
572
+ voice?: boolean;
573
+ /** The voice DO's Cloudflare `DurableObjectNamespace` binding name, e.g. `VOICE_SUPPORT`. Present only when `voice`. */
574
+ voiceBindingName?: string;
575
+ /** Generated `VoiceSessionDO` subclass name, e.g. `SupportVoiceDO`. Present only when `voice`. */
576
+ voiceClassName?: string;
577
+ }
578
+ /** One durable step call lifted from a workflow handler body (the use side of {@link WorkflowIR.steps}). */
579
+ interface WorkflowStepIR {
580
+ /** 1-based line of the durable step call. */
581
+ line: number;
582
+ /** The native step method invoked: `do` / `sleep` / `sleepUntil` / `waitForEvent`. */
583
+ method: string;
584
+ /** The step's static label (the first string-literal argument). */
290
585
  name: string;
291
586
  }
292
587
  /**
293
- * A `ctx.workflows.get("name")…` call discovered in a function body — the
294
- * use-site analog of {@link WorkflowIR} (which is the declaration side). Feeds
295
- * the `workflow_unused` lint (a declared workflow with zero call sites) and the
296
- * `workflow_unknown_target` lint (a `.get("x")` whose `x` isn't declared a
297
- * typo catcher). {@link WorkflowCallIR.workflow} is `""` when the `get(...)`
298
- * argument is not a string literal (a dynamic name which suppresses the
299
- * unused-workflow heuristic rather than producing a false positive).
300
- */
588
+ * A queue lifted from a `defineQueue()` export in `lunora/queues.ts`. Carries
589
+ * what the emitters and the config layer need to wire the typed `ctx.queues`
590
+ * producer, the generated worker `queue()` dispatch, and the wrangler
591
+ * `queues.producers[]` / `queues.consumers[]` entries. Like workflows, a queue
592
+ * is NOT a Durable Object — wrangler gets only `queues.*` entries. Names are
593
+ * derived via `@lunora/queue`'s shared helpers so codegen and the config layer
594
+ * can never disagree.
595
+ */
596
+ interface QueueIR {
597
+ /** The Cloudflare `Queue` producer binding name, e.g. `QUEUE_EMAIL`. */
598
+ bindingName: string;
599
+ /** The `lunora/queues.ts` export name, e.g. `emailQueue`. */
600
+ exportName: string;
601
+ /** How the queue is consumed: `"push"` (a worker `queue()` handler) or `"pull"` (external HTTP). */
602
+ mode: "pull" | "push";
603
+ /**
604
+ * The stable wrangler queue name (`queues.producers[].queue`). Defaults to
605
+ * the kebab-cased export name (`emailQueue` → `email-queue`); a static
606
+ * `name:` literal in the definition overrides it.
607
+ */
608
+ name: string;
609
+ /** Push-consumer batch/retry tuning, mirrored onto the wrangler `queues.consumers[]` entry. */
610
+ tuning: {
611
+ deadLetterQueue?: string;
612
+ maxBatchSize?: number;
613
+ maxBatchTimeout?: number;
614
+ maxRetries?: number;
615
+ retryDelay?: number;
616
+ };
617
+ }
618
+ /**
619
+ * The feature-flag provider declared by the default export of `lunora/flags.ts`
620
+ * (`defineFlags({ provider, … })`). Discovery is **metadata-only** — codegen
621
+ * imports the real module at runtime for the provider value; this IR exists so
622
+ * the config layer can reconcile/validate the wrangler `flagship` binding when
623
+ * the app uses Flagship in binding mode. A `custom` provider (any other
624
+ * OpenFeature factory) carries no binding to reconcile.
625
+ */
626
+ interface FlagsIR {
627
+ /**
628
+ * The wrangler `flagship[].binding` name — set **only** for a flagship
629
+ * `provider` in binding mode (`flagshipProvider({ binding: "FLAGS" })`). The
630
+ * config layer hints/validates a matching `flagship` binding from this.
631
+ */
632
+ bindingName?: string;
633
+ /**
634
+ * Flagship operating mode — `"binding"` (wrangler binding, needs a
635
+ * `flagship` entry) or `"http"` (no binding); `undefined` for a `custom`
636
+ * provider or when the mode can't be read statically.
637
+ */
638
+ mode?: "binding" | "http";
639
+ /** `"flagship"` when the provider is `flagshipProvider(...)`, else `"custom"` (any other OpenFeature provider factory). */
640
+ provider: "custom" | "flagship";
641
+ }
642
+ /**
643
+ * A `ctx.workflows.get("name")…` call discovered in a function body — the
644
+ * use-site analog of {@link WorkflowIR} (which is the declaration side). Feeds
645
+ * the `workflow_unused` lint (a declared workflow with zero call sites) and the
646
+ * `workflow_unknown_target` lint (a `.get("x")` whose `x` isn't declared — a
647
+ * typo catcher). {@link WorkflowCallIR.workflow} is `""` when the `get(...)`
648
+ * argument is not a string literal (a dynamic name — which suppresses the
649
+ * unused-workflow heuristic rather than producing a false positive).
650
+ */
301
651
  interface WorkflowCallIR {
302
652
  /** Export binding name of the function performing the call, e.g. `create`. */
303
653
  exportName: string;
@@ -309,11 +659,11 @@ interface WorkflowCallIR {
309
659
  workflow: string;
310
660
  }
311
661
  /**
312
- * A `ctx.db.query("table")…` read discovered in a function body, reduced to what
313
- * the `filter_without_index` advisor lint needs: which table, whether the chain
314
- * narrows with an index, and whether it filters. `table` is `""` when the
315
- * `query(...)` argument is not a string literal (a dynamic table — not lintable).
316
- */
662
+ * A `ctx.db.query("table")…` read discovered in a function body, reduced to what
663
+ * the `filter_without_index` advisor lint needs: which table, whether the chain
664
+ * narrows with an index, and whether it filters. `table` is `""` when the
665
+ * `query(...)` argument is not a string literal (a dynamic table — not lintable).
666
+ */
317
667
  interface QueryReadIR {
318
668
  /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
319
669
  file: string;
@@ -327,12 +677,12 @@ interface QueryReadIR {
327
677
  table: string;
328
678
  }
329
679
  /**
330
- * A `ctx.authApi.&lt;method>(...)` call discovered in a function body, attributed
331
- * to the exported function (and its file = api namespace) that performs it.
332
- * Structurally identical to `AdvisorAuthApiCall` so it passes straight through
333
- * to the advisor lint without conversion, exactly as `InsertWriteIR` does for
334
- * `AdvisorInsertWrite`.
335
- */
680
+ * A `ctx.authApi.&lt;method>(...)` call discovered in a function body, attributed
681
+ * to the exported function (and its file = api namespace) that performs it.
682
+ * Structurally identical to `AdvisorAuthApiCall` so it passes straight through
683
+ * to the advisor lint without conversion, exactly as `InsertWriteIR` does for
684
+ * `AdvisorInsertWrite`.
685
+ */
336
686
  interface AuthApiCallIR {
337
687
  /** Export binding name of the function performing the call, e.g. "createOrg". */
338
688
  exportName: string;
@@ -346,12 +696,12 @@ interface AuthApiCallIR {
346
696
  method: string;
347
697
  }
348
698
  /**
349
- * A `ctx.db.insert("table", …)` write discovered in a function body, attributed
350
- * to the exported function (and its file = api namespace) that performs it — the
351
- * write-side analog of {@link QueryReadIR}. Lets tooling wire a table's write
352
- * action by behavior (which function inserts into it) rather than by naming.
353
- * {@link InsertWriteIR.table} is `""` when the argument is not a string literal.
354
- */
699
+ * A `ctx.db.insert("table", …)` write discovered in a function body, attributed
700
+ * to the exported function (and its file = api namespace) that performs it — the
701
+ * write-side analog of {@link QueryReadIR}. Lets tooling wire a table's write
702
+ * action by behavior (which function inserts into it) rather than by naming.
703
+ * {@link InsertWriteIR.table} is `""` when the argument is not a string literal.
704
+ */
355
705
  interface InsertWriteIR {
356
706
  /** Export binding name of the function performing the insert, e.g. "send". */
357
707
  exportName: string;
@@ -363,14 +713,14 @@ interface InsertWriteIR {
363
713
  table: string;
364
714
  }
365
715
  /**
366
- * A non-deterministic API call (`Date.now`, `Math.random`, `crypto.randomUUID`,
367
- * `crypto.getRandomValues`, `fetch`) discovered lexically inside a `query(...)`
368
- * or `mutation(...)` handler body — the `nondeterministic_query_mutation` lint
369
- * input. Structurally identical to `AdvisorNondeterministicCall` so values pass
370
- * straight through to the advisor without conversion, exactly as `AuthApiCallIR`
371
- * does for `AdvisorAuthApiCall`. `action(...)` handlers are never recorded —
372
- * actions are the determinism escape hatch.
373
- */
716
+ * A non-deterministic API call (`Date.now`, `Math.random`, `crypto.randomUUID`,
717
+ * `crypto.getRandomValues`, `fetch`) discovered lexically inside a `query(...)`
718
+ * or `mutation(...)` handler body — the `nondeterministic_query_mutation` lint
719
+ * input. Structurally identical to `AdvisorNondeterministicCall` so values pass
720
+ * straight through to the advisor without conversion, exactly as `AuthApiCallIR`
721
+ * does for `AdvisorAuthApiCall`. `action(...)` handlers are never recorded —
722
+ * actions are the determinism escape hatch.
723
+ */
374
724
  interface NondeterministicCallIR {
375
725
  /** The non-deterministic API invoked, e.g. `Date.now` / `Math.random` / `crypto.randomUUID` / `fetch`. */
376
726
  callee: string;
@@ -384,13 +734,13 @@ interface NondeterministicCallIR {
384
734
  line: number;
385
735
  }
386
736
  /**
387
- * One `ctx.r2sql` access lexically inside a `query`/`mutation` handler — the
388
- * `r2sql_outside_action` advisor lint input. Structurally identical to the
389
- * advisor's `AdvisorR2sqlCall` (same field set) so values pass straight through
390
- * `lintSchema` without conversion, exactly as `NondeterministicCallIR` does.
391
- * Only `query`/`mutation` handlers are recorded; `action(...)` is the intended
392
- * home for `ctx.r2sql` and is skipped.
393
- */
737
+ * One `ctx.r2sql` access lexically inside a `query`/`mutation` handler — the
738
+ * `r2sql_outside_action` advisor lint input. Structurally identical to the
739
+ * advisor's `AdvisorR2sqlCall` (same field set) so values pass straight through
740
+ * `lintSchema` without conversion, exactly as `NondeterministicCallIR` does.
741
+ * Only `query`/`mutation` handlers are recorded; `action(...)` is the intended
742
+ * home for `ctx.r2sql` and is skipped.
743
+ */
394
744
  interface R2sqlCallIR {
395
745
  /** The accessed `ctx.r2sql` surface, e.g. `ctx.r2sql.query` / `ctx.r2sql.from`. */
396
746
  callee: string;
@@ -404,21 +754,21 @@ interface R2sqlCallIR {
404
754
  line: number;
405
755
  }
406
756
  /**
407
- * Per-procedure RLS usage snapshot, produced by `discoverRlsProcedures` for the
408
- * `rls_uncovered_table` advisor lint. Structurally identical to
409
- * `AdvisorRlsProcedure` (they share the same field set) so values pass straight
410
- * through to the advisor without conversion, exactly as `AuthApiCallIR` does for
411
- * `AdvisorAuthApiCall`.
412
- */
757
+ * Per-procedure RLS usage snapshot, produced by `discoverRlsProcedures` for the
758
+ * `rls_uncovered_table` advisor lint. Structurally identical to
759
+ * `AdvisorRlsProcedure` (they share the same field set) so values pass straight
760
+ * through to the advisor without conversion, exactly as `AuthApiCallIR` does for
761
+ * `AdvisorAuthApiCall`.
762
+ */
413
763
  interface RlsProcedureIR {
414
764
  /** Export binding name of the procedure (e.g. `listDocuments`). */
415
765
  exportName: string;
416
766
  /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
417
767
  file: string;
418
768
  /**
419
- * Table names extracted from the `rls(policies)` array literal. Empty when the
420
- * policies argument is not a statically-readable array literal.
421
- */
769
+ * Table names extracted from the `rls(policies)` array literal. Empty when the
770
+ * policies argument is not a statically-readable array literal.
771
+ */
422
772
  rlsTables: string[];
423
773
  /** Tables read by the procedure via `ctx.db.query/findMany/findFirst/…`. */
424
774
  tablesRead: string[];
@@ -430,22 +780,22 @@ interface RlsProcedureIR {
430
780
  visibility: "internal" | "public";
431
781
  }
432
782
  /**
433
- * One procedure reduced to the facts the `mask_uncovered_pii_column` lint needs:
434
- * whether its builder chain includes `.use(mask(...))`, which `(table, column)`
435
- * pairs that mask declares, and which tables the procedure reads/writes. The
436
- * column-level analogue of {@link RlsProcedureIR}. Structurally identical to
437
- * `AdvisorMaskProcedure` so values pass straight through without conversion.
438
- */
783
+ * One procedure reduced to the facts the `mask_uncovered_pii_column` lint needs:
784
+ * whether its builder chain includes `.use(mask(...))`, which `(table, column)`
785
+ * pairs that mask declares, and which tables the procedure reads/writes. The
786
+ * column-level analogue of {@link RlsProcedureIR}. Structurally identical to
787
+ * `AdvisorMaskProcedure` so values pass straight through without conversion.
788
+ */
439
789
  interface MaskProcedureIR {
440
790
  /** Export binding name of the procedure (e.g. `listUsers`). */
441
791
  exportName: string;
442
792
  /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
443
793
  file: string;
444
794
  /**
445
- * `(table, column)` pairs this procedure's `mask(policies)` object literal
446
- * declares. Empty when the policies argument is not a statically-readable
447
- * object literal (conservative: `usesMask` is still `true`).
448
- */
795
+ * `(table, column)` pairs this procedure's `mask(policies)` object literal
796
+ * declares. Empty when the policies argument is not a statically-readable
797
+ * object literal (conservative: `usesMask` is still `true`).
798
+ */
449
799
  maskColumns: {
450
800
  column: string;
451
801
  table: string;
@@ -460,14 +810,14 @@ interface MaskProcedureIR {
460
810
  visibility: "internal" | "public";
461
811
  }
462
812
  /**
463
- * One masked column surfaced to the studio's data-browser mask preview: a
464
- * `(table, column)` pair plus the declared {@link MaskProcedureIR} strategy so
465
- * the preview can pick redact-vs-hash-vs-custom rendering. Aggregated across the
466
- * project's `.use(mask(...))` chains by `discoverMaskMetadata`; the descriptive
467
- * twin of {@link RlsPolicyIR}. `"custom"` covers any non-string strategy (a
468
- * `(value, ctx) => …` function) — its logic is an opaque closure, never read by
469
- * the UI; the preview renders a fixed sentinel for it.
470
- */
813
+ * One masked column surfaced to the studio's data-browser mask preview: a
814
+ * `(table, column)` pair plus the declared {@link MaskProcedureIR} strategy so
815
+ * the preview can pick redact-vs-hash-vs-custom rendering. Aggregated across the
816
+ * project's `.use(mask(...))` chains by `discoverMaskMetadata`; the descriptive
817
+ * twin of {@link RlsPolicyIR}. `"custom"` covers any non-string strategy (a
818
+ * `(value, ctx) => …` function) — its logic is an opaque closure, never read by
819
+ * the UI; the preview renders a fixed sentinel for it.
820
+ */
471
821
  interface MaskColumnMetadataIR {
472
822
  /** Column the mask policy redacts. */
473
823
  column: string;
@@ -477,24 +827,48 @@ interface MaskColumnMetadataIR {
477
827
  table: string;
478
828
  }
479
829
  /**
480
- * Schema-wide masking metadata the codegen emits into the generated ShardDO so
481
- * the studio's data-browser mask toggle can preview what a non-privileged caller
482
- * would see. Aggregated across every `.use(mask(...))` chain in the project —
483
- * purely descriptive (table + column + strategy), never the masking closure. The
484
- * column-level analogue of {@link RlsMetadataIR}.
485
- */
830
+ * Schema-wide masking metadata the codegen emits into the generated ShardDO so
831
+ * the studio's data-browser mask toggle can preview what a non-privileged caller
832
+ * would see. Aggregated across every `.use(mask(...))` chain in the project —
833
+ * purely descriptive (table + column + strategy), never the masking closure. The
834
+ * column-level analogue of {@link RlsMetadataIR}.
835
+ */
486
836
  interface MaskMetadataIR {
487
837
  /** Every statically-discovered masked column, deduped by `(table, column)` (first declaration wins). */
488
838
  columns: MaskColumnMetadataIR[];
489
839
  }
490
840
  /**
491
- * One statically-readable policy entry from an `rls([...])` array literal,
492
- * surfaced to the studio's read-only RLS inspector via the generated
493
- * `rlsPolicies()` hook. Captures the policy's `table` + `on` operation and the
494
- * procedure it guards never the `when` predicate, which is an opaque JS
495
- * closure (its logic stays in code, not the UI). Produced by
496
- * `discoverRlsProcedures` alongside the lint IR.
497
- */
841
+ * One masked column whose `mask(policies)` strategy is a statically-known
842
+ * literal (`"hash"` or `"redact"`) the `mask_weak_hash_strategy_on_pii` lint
843
+ * input. Unlike {@link MaskColumnMetadataIR} (app-wide, deduped by `(table,
844
+ * column)`, studio-preview evidence), this is per declaration site (file + line
845
+ * + enclosing export), undeduped, so the lint can point at the exact
846
+ * `mask(...)` call that applies a weak strategy. A `MaskFn` (custom, non-literal)
847
+ * strategy carries no lint-relevant signal and is never recorded here.
848
+ * Structurally identical to `AdvisorMaskStrategy`.
849
+ */
850
+ interface MaskStrategyIR {
851
+ /** Masked column name. */
852
+ column: string;
853
+ /** Export binding name of the procedure whose `.use(mask(...))` chain declared this column, or `"&lt;module>"` when declared at file scope. */
854
+ exportName: string;
855
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
856
+ file: string;
857
+ /** 1-based line of the masked column's strategy property. */
858
+ line: number;
859
+ /** The statically-known strategy literal: `"hash"` or `"redact"`. */
860
+ strategy: string;
861
+ /** Logical table the masked column belongs to. */
862
+ table: string;
863
+ }
864
+ /**
865
+ * One statically-readable policy entry from an `rls([...])` array literal,
866
+ * surfaced to the studio's read-only RLS inspector via the generated
867
+ * `rlsPolicies()` hook. Captures the policy's `table` + `on` operation and the
868
+ * procedure it guards — never the `when` predicate, which is an opaque JS
869
+ * closure (its logic stays in code, not the UI). Produced by
870
+ * `discoverRlsProcedures` alongside the lint IR.
871
+ */
498
872
  interface RlsPolicyIR {
499
873
  /** Source file (relative to `lunora/`, without extension) the policy is declared in. */
500
874
  file: string;
@@ -506,12 +880,12 @@ interface RlsPolicyIR {
506
880
  table: string;
507
881
  }
508
882
  /**
509
- * One statically-readable role entry from an `rls(policies, { roles: [...] })`
510
- * call, surfaced to the studio's RLS inspector. Captures the role's `name`,
511
- * optional `description`, and the names of the permissions it grants (string
512
- * literals or `definePermission("name")` calls). Produced by
513
- * `discoverRlsProcedures`.
514
- */
883
+ * One statically-readable role entry from an `rls(policies, { roles: [...] })`
884
+ * call, surfaced to the studio's RLS inspector. Captures the role's `name`,
885
+ * optional `description`, and the names of the permissions it grants (string
886
+ * literals or `definePermission("name")` calls). Produced by
887
+ * `discoverRlsProcedures`.
888
+ */
515
889
  interface RlsRoleIR {
516
890
  /** Optional human-readable description from `defineRole(name, { description })`. */
517
891
  description?: string;
@@ -521,11 +895,11 @@ interface RlsRoleIR {
521
895
  permissions: string[];
522
896
  }
523
897
  /**
524
- * Schema-wide RLS metadata the codegen emits into the generated ShardDO so the
525
- * studio's read-only inspector can list, per table, which policies guard it and
526
- * what roles are defined. Aggregated across every `.use(rls(...))` chain in the
527
- * project — purely descriptive, never the predicate logic.
528
- */
898
+ * Schema-wide RLS metadata the codegen emits into the generated ShardDO so the
899
+ * studio's read-only inspector can list, per table, which policies guard it and
900
+ * what roles are defined. Aggregated across every `.use(rls(...))` chain in the
901
+ * project — purely descriptive, never the predicate logic.
902
+ */
529
903
  interface RlsMetadataIR {
530
904
  /** Every statically-discovered policy `(table, on, procedure)` entry. */
531
905
  policies: RlsPolicyIR[];
@@ -544,24 +918,33 @@ interface StorageRuleIR {
544
918
  procedure: string;
545
919
  }
546
920
  /**
547
- * Schema-wide storage-access-rule metadata emitted into the generated ShardDO so
548
- * the studio's read-only inspector can list, per bucket, which operations are
549
- * gated and under what key prefix. Aggregated across every
550
- * `.use(storageRules(...))` chain — descriptive only, never the predicate logic.
551
- */
921
+ * Schema-wide storage-access-rule metadata emitted into the generated ShardDO so
922
+ * the studio's read-only inspector can list, per bucket, which operations are
923
+ * gated and under what key prefix. Aggregated across every
924
+ * `.use(storageRules(...))` chain — descriptive only, never the predicate logic.
925
+ */
552
926
  interface StorageRulesMetadataIR {
553
927
  rules: StorageRuleIR[];
554
928
  }
555
929
  /**
556
- * A typed REST route declared with the `httpRoute.&lt;verb>("/path")…` builder in
557
- * `@lunora/server` and mounted on `httpRouter()`. Captured statically from the
558
- * builder chain so the OpenAPI emitter can render a real `paths` entry: the verb
559
- * + path become the operation's method + URL, and the accumulated validator maps
560
- * become its query parameters, path parameters, and request body.
561
- */
930
+ * A typed REST route declared with the `httpRoute.&lt;verb>("/path")…` builder in
931
+ * `@lunora/server` and mounted on `httpRouter()`. Captured statically from the
932
+ * builder chain so the OpenAPI emitter can render a real `paths` entry: the verb
933
+ * + path become the operation's method + URL, and the accumulated validator maps
934
+ * become its query parameters, path parameters, and request body.
935
+ */
562
936
  interface HttpRouteIR {
563
937
  /** `v.*` validators decoding the JSON request body (`.body({...})`), keyed by field. */
564
938
  body: Record<string, ValidatorIR>;
939
+ /**
940
+ * Rendered TS type of one SSE chunk — the `R` the `.stream(handler)`
941
+ * handler yields — inferred from the handler via the type checker. Present
942
+ * only when {@link HttpRouteIR.stream} is `true`; `"unknown"` when the
943
+ * checker can't resolve enough context. Feeds the emitted
944
+ * `HttpStreamRef&lt;Chunk, …>` so the chunk type flows to the client.
945
+ * @experimental Part of the HTTP-SSE stream surface (the `httpStreams.*` emission).
946
+ */
947
+ chunkType?: string;
565
948
  /** Export binding name of the route handler (used only for diagnostics / dedupe). */
566
949
  exportName: string;
567
950
  /** Path relative to `&lt;projectRoot>/lunora/` without extension, e.g. "http". */
@@ -580,28 +963,36 @@ interface HttpRouteIR {
580
963
  stream: boolean;
581
964
  }
582
965
  /**
583
- * Per-procedure protective-middleware snapshot, produced by
584
- * `discoverProcedureMiddleware` for the security lints
585
- * (`public_mutation_without_ratelimit`, `user_creating_mutation_without_captcha`).
586
- * Records which `.use(...)` guards a procedure's builder chain carries plus the
587
- * behavioural facts that decide whether a guard is *expected* (does it write a
588
- * user/session table, does it send mail). `protectPublic({ rateLimit, captcha })`
589
- * is unwrapped: the bundle's object-literal keys set `usesRateLimit`/`usesCaptcha`
590
- * exactly as the individual `.use(rateLimit(...))` / `.use(verifyTurnstile(...))`
591
- * steps would. Structurally identical to `AdvisorProcedureProtection` so values
592
- * pass straight through to the advisor without conversion.
593
- */
966
+ * Per-procedure protective-middleware snapshot, produced by
967
+ * `discoverProcedureMiddleware` for the security lints
968
+ * (`public_mutation_without_ratelimit`, `user_creating_mutation_without_captcha`).
969
+ * Records which `.use(...)` guards a procedure's builder chain carries plus the
970
+ * behavioural facts that decide whether a guard is *expected* (does it write a
971
+ * user/session table, does it send mail). `protectPublic({ rateLimit, captcha })`
972
+ * is unwrapped: the bundle's object-literal keys set `usesRateLimit`/`usesCaptcha`
973
+ * exactly as the individual `.use(rateLimit(...))` / `.use(verifyTurnstile(...))`
974
+ * steps would. Structurally identical to `AdvisorProcedureProtection` so values
975
+ * pass straight through to the advisor without conversion.
976
+ */
594
977
  interface ProcedureMiddlewareIR {
595
978
  /** `true` when the handler (or a helper inside it) references `ctx.mail` / `ctx.email`. */
596
979
  callsMail: boolean;
597
980
  /** Export binding name of the procedure (e.g. `signUp`). */
598
981
  exportName: string;
982
+ /** `true` when the handler fans work out to a privileged, cost-bearing dispatch surface (scheduler `runAfter`/`runAt`, a queue producer send, or a workflow create). Feeds the privileged-fanout lint. */
983
+ fanOut: boolean;
599
984
  /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
600
985
  file: string;
601
986
  /** Registration kind — only `mutation`/`action` are write-shaped; `query` is read-only. */
602
987
  kind: "action" | "mutation" | "query";
988
+ /** `true` when the handler runs an AI generation (`generateText`/`streamText`/`generateObject`/`streamObject`) with no `maxOutputTokens` bound in its config literal. Feeds the `ai_unbounded_generation_public` lint. */
989
+ unboundedAiGeneration: boolean;
603
990
  /** `true` when the chain carries `.use(verifyTurnstile(...))` or a `protectPublic({ captcha })` bundle. */
604
991
  usesCaptcha: boolean;
992
+ /** `true` when the chain carries `.use(emailGateMiddleware(...))` (`@lunora/auth`). Feeds the `signup_mutation_without_disposable_gating` lint. */
993
+ usesEmailGate: boolean;
994
+ /** `true` when the handler calls `ctx.db.insertManyUnsafe(...)`, bypassing validators and triggers. Feeds the `insert_many_unsafe_user_data` lint. */
995
+ usesInsertManyUnsafe: boolean;
605
996
  /** `true` when the chain carries `.use(mask(...))`. */
606
997
  usesMask: boolean;
607
998
  /** `true` when the chain carries `.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle. */
@@ -614,13 +1005,13 @@ interface ProcedureMiddlewareIR {
614
1005
  writesUserTable: boolean;
615
1006
  }
616
1007
  /**
617
- * Per-procedure argument-validator snapshot, produced by the
618
- * argument-validator discoverer for the input-hardening lints
619
- * (`public_arg_uses_any`, `unbounded_string_arg`). Only public procedures are
620
- * recorded — internal functions take server-trusted input. Structurally identical
621
- * to `AdvisorArgumentValidator` so it passes straight through to the advisor
622
- * without conversion.
623
- */
1008
+ * Per-procedure argument-validator snapshot, produced by the
1009
+ * argument-validator discoverer for the input-hardening lints
1010
+ * (`public_arg_uses_any`, `unbounded_string_arg`). Only public procedures are
1011
+ * recorded — internal functions take server-trusted input. Structurally identical
1012
+ * to `AdvisorArgumentValidator` so it passes straight through to the advisor
1013
+ * without conversion.
1014
+ */
624
1015
  interface ArgumentValidatorIR {
625
1016
  /** Arg names declared as `v.any()` (unvalidated, untyped input). */
626
1017
  anyArgs: string[];
@@ -634,11 +1025,32 @@ interface ArgumentValidatorIR {
634
1025
  unboundedStringArgs: string[];
635
1026
  }
636
1027
  /**
637
- * One secret-shaped string literal discovered in `lunora/` source the
638
- * `hardcoded_secret` lint input. Complements the pre-commit `vis secrets` scan by
639
- * surfacing the same class of finding in-IDE via the studio Advisors table.
640
- * Structurally identical to `AdvisorSecretLiteral`.
641
- */
1028
+ * One factory/constructor call in `lunora/` whose config object literal a
1029
+ * security lint inspects for a present-or-absent key the shared input for the
1030
+ * config-call security lints (payment authorize, inbound-mail verify, rate-limit
1031
+ * store, browser private-targets). Structurally identical to `AdvisorConfigCall`
1032
+ * so it passes straight through to the advisor without conversion.
1033
+ */
1034
+ interface ConfigCallIR {
1035
+ /** `true` when the config argument was a static object literal the feeder could read. */
1036
+ analyzable: boolean;
1037
+ /** The factory function or constructor name at the call site, e.g. `createPayment` / `RateLimiter`. */
1038
+ callee: string;
1039
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1040
+ file: string;
1041
+ /** 1-based line of the call site, or `0` when unknown. */
1042
+ line: number;
1043
+ /** Keys present in the config object literal (empty when not `analyzable`). */
1044
+ presentKeys: string[];
1045
+ /** Keys in the config object literal explicitly assigned the literal `true`. */
1046
+ trueKeys: string[];
1047
+ }
1048
+ /**
1049
+ * One secret-shaped string literal discovered in `lunora/` source — the
1050
+ * `hardcoded_secret` lint input. Complements the pre-commit `vis secrets` scan by
1051
+ * surfacing the same class of finding in-IDE via the studio Advisors table.
1052
+ * Structurally identical to `AdvisorSecretLiteral`.
1053
+ */
642
1054
  interface SecretLiteralIR {
643
1055
  /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
644
1056
  file: string;
@@ -650,14 +1062,14 @@ interface SecretLiteralIR {
650
1062
  preview: string;
651
1063
  }
652
1064
  /**
653
- * One `ctx.sql.query(text, …)` / `ctx.sql.unsafe(text, …)` call whose `text`
654
- * argument is built in place rather than passed as a fixed statement — the
655
- * `sql_injection_risk` lint input. The Hyperdrive driver binds ONLY the `params`
656
- * array; the `text` string is spliced verbatim into the SQL, so a `text` assembled
657
- * from a string concatenation or a substitution template literal is an injection
658
- * vector. A fixed string literal / no-substitution template is safe. Structurally
659
- * identical to `AdvisorSqlInterpolation`.
660
- */
1065
+ * One `ctx.sql.query(text, …)` / `ctx.sql.unsafe(text, …)` call whose `text`
1066
+ * argument is built in place rather than passed as a fixed statement — the
1067
+ * `sql_injection_risk` lint input. The Hyperdrive driver binds ONLY the `params`
1068
+ * array; the `text` string is spliced verbatim into the SQL, so a `text` assembled
1069
+ * from a string concatenation or a substitution template literal is an injection
1070
+ * vector. A fixed string literal / no-substitution template is safe. Structurally
1071
+ * identical to `AdvisorSqlInterpolation`.
1072
+ */
661
1073
  interface SqlInterpolationIR {
662
1074
  /** Export binding name of the procedure performing the `ctx.sql` call. */
663
1075
  exportName: string;
@@ -667,11 +1079,335 @@ interface SqlInterpolationIR {
667
1079
  line: number;
668
1080
  }
669
1081
  /**
670
- * One discovered `httpRoute.&lt;verb>("/admin/…")` route on an admin/privileged-looking
671
- * path, with whether its builder chain references an auth/admin guard — the
672
- * `admin_route_without_guard` lint input. Structurally identical to
673
- * `AdvisorAdminRoute`.
674
- */
1082
+ * One `ctx.fetch(url, …)` call inside an action whose URL argument is derived
1083
+ * from the handler's `args` the `action_fetch_ssrf` lint input. `ctx.fetch` is
1084
+ * the action-only outbound-request escape hatch with no host allowlist, so a URL
1085
+ * assembled from request input is a server-side request forgery vector (cloud
1086
+ * metadata endpoints, internal services). Only arg-derived URLs reach here; a
1087
+ * fixed literal or a URL built from config/`ctx.*` is not recorded. Structurally
1088
+ * identical to `AdvisorArgumentDerivedFetch`.
1089
+ */
1090
+ interface ArgumentDerivedFetchIR {
1091
+ /** Export binding name of the action performing the `ctx.fetch` call. */
1092
+ exportName: string;
1093
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1094
+ file: string;
1095
+ /** 1-based line of the `ctx.fetch` call, or `0` when unknown. */
1096
+ line: number;
1097
+ }
1098
+ /**
1099
+ * One `ctx.kv.&lt;method>(key, …)` call whose namespace key is derived from the
1100
+ * handler's `args` with no server-side scoping — the `kv_unscoped_user_key_idor`
1101
+ * lint input. Workers KV is a single flat namespace, so a key taken straight from
1102
+ * request input lets any caller read, overwrite, or delete another user's entry
1103
+ * (IDOR). Only arg-derived, unscoped keys reach here; a fixed literal, or a key
1104
+ * prefixed with a server-trusted identity (`${ctx.auth.userId}:…` — references
1105
+ * `ctx`, so treated as scoped), is not recorded. `list` is excluded (it takes a
1106
+ * prefix, not a per-entry key). Structurally identical to `AdvisorKvKeyAccess`.
1107
+ */
1108
+ interface KvKeyAccessIR {
1109
+ /** Export binding name of the procedure performing the `ctx.kv` access. */
1110
+ exportName: string;
1111
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1112
+ file: string;
1113
+ /** 1-based line of the `ctx.kv` call, or `0` when unknown. */
1114
+ line: number;
1115
+ /** The `ctx.kv` method invoked: `get` / `getRaw` / `getWithMetadata` / `put` / `delete`. */
1116
+ method: string;
1117
+ }
1118
+ /**
1119
+ * One `ctx.db` write (`insert` / `replace` / `patch` / `insertManyUnsafe`) that sets
1120
+ * an ownership / identity column — `userId`, `ownerId`, `tenantId`, and the like —
1121
+ * from the handler's `args` instead of the server-trusted identity. The
1122
+ * `owner_field_from_args_not_auth` lint input: the ownership column decides who a
1123
+ * row belongs to, so a value taken from request input lets any caller write rows
1124
+ * owned by another user or tenant (the act-as-any-user / cross-tenant IDOR vector).
1125
+ * A column stamped from `ctx.*`, or set to a fixed literal, is not recorded; only an
1126
+ * arg-derived identity write reaches here. Structurally identical to
1127
+ * `AdvisorOwnerFieldWrite`.
1128
+ */
1129
+ /**
1130
+ * One branching `defineShape({ where })` / `definePolicy({ when })` predicate arm
1131
+ * that returns an unrestricted predicate — the `unrestricted_where_branch` lint
1132
+ * input. A denial arm must match NO rows (`deny()` / `{ OR: [] }`); `{}` matches
1133
+ * every row, so the near-miss silently replicates the whole table.
1134
+ */
1135
+ interface UnrestrictedWhereBranchIR {
1136
+ /** Export binding name of the shape / policy the predicate belongs to. */
1137
+ exportName: string;
1138
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1139
+ file: string;
1140
+ /** Which unrestricted form was returned. */
1141
+ form: "empty-object" | "undefined";
1142
+ /** The config key carrying the predicate (`where` for a shape, `when` for a policy). */
1143
+ key: string;
1144
+ /** 1-based line of the offending returned expression. */
1145
+ line: number;
1146
+ /** The declaring call (`defineShape` / `definePolicy`). */
1147
+ owner: string;
1148
+ }
1149
+ interface OwnerFieldWriteIR {
1150
+ /** Export binding name of the procedure performing the write. */
1151
+ exportName: string;
1152
+ /** The identity column being written from `args` (e.g. `userId`). */
1153
+ field: string;
1154
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1155
+ file: string;
1156
+ /** 1-based line of the `ctx.db` write call, or `0` when unknown. */
1157
+ line: number;
1158
+ /** The `ctx.db` write method (`insert` / `replace` / `patch` / `insertManyUnsafe`). */
1159
+ method: string;
1160
+ }
1161
+ /**
1162
+ * One `ctx.storage.&lt;bucket>.&lt;method>(key, …)` call whose R2 object key is derived
1163
+ * from the handler's `args` with no server-side scoping — the
1164
+ * `storage_key_from_user_args` lint input. The bucket read/write/URL/delete methods
1165
+ * key by their first argument, so an object key taken straight from request input is
1166
+ * object-level IDOR (read/overwrite/delete anyone's object). A key referencing a
1167
+ * server-trusted `ctx.*` value (e.g. `${ctx.auth.userId}/…`) is treated as scoped
1168
+ * and is not recorded; only an arg-derived, `ctx`-free key reaches here.
1169
+ * Structurally identical to `AdvisorStorageKeyAccess`.
1170
+ */
1171
+ interface StorageKeyAccessIR {
1172
+ /** Export binding name of the procedure performing the storage call. */
1173
+ exportName: string;
1174
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1175
+ file: string;
1176
+ /** 1-based line of the storage call, or `0` when unknown. */
1177
+ line: number;
1178
+ /** The bucket method invoked with the arg-derived key, e.g. `get` / `put` / `delete` / `download`. */
1179
+ method: string;
1180
+ }
1181
+ /**
1182
+ * One `ctx.containers.&lt;exportName>.get(name, …)` call whose instance key is derived
1183
+ * from the handler's `args` with no server-side scoping — the
1184
+ * `container_instance_key_from_user_input` lint input. Each container definition's
1185
+ * `.get(name)` accessor routes to one instance per `name`, so a key taken straight from
1186
+ * request input lets any caller reach another tenant's container (a cross-tenant IDOR). A
1187
+ * fixed literal key, or one derived from a server-trusted identity (`${ctx.auth.userId}` —
1188
+ * references `ctx`, so treated as scoped), is not recorded; only an arg-derived, unscoped
1189
+ * key reaches here. `.any()`/`.pool()` take no key and are not sinks. Structurally
1190
+ * identical to `AdvisorContainerKeyAccess`.
1191
+ */
1192
+ interface ContainerKeyAccessIR {
1193
+ /** Export binding name of the procedure performing the `ctx.containers` access. */
1194
+ exportName: string;
1195
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1196
+ file: string;
1197
+ /** 1-based line of the `ctx.containers.*.get` call, or `0` when unknown. */
1198
+ line: number;
1199
+ /** The container accessor method invoked — always `get`. */
1200
+ method: string;
1201
+ }
1202
+ /**
1203
+ * One `ctx.ai.run(model, …)` call whose model-id argument is derived from the handler's
1204
+ * `args` with no server-side scoping — the `ai_raw_run_escape_hatch` lint input.
1205
+ * `ctx.ai.run` is the raw Workers AI binding passthrough, bypassing the typed
1206
+ * `ctx.ai.model(...)` + AI-SDK layer (`generateText`/`streamText`/…) that caps output and
1207
+ * enforces a schema, so an arg-derived model id lets any caller select an arbitrary model.
1208
+ * A fixed literal model, or one scoped by a server-trusted `ctx.*` value, is not recorded;
1209
+ * only an arg-derived, unscoped model id reaches here (an arg-derived `inputs` argument is
1210
+ * normal usage and is never inspected). Structurally identical to `AdvisorAiRawRun`.
1211
+ */
1212
+ interface AiRawRunIR {
1213
+ /** Export binding name of the procedure performing the `ctx.ai.run` call. */
1214
+ exportName: string;
1215
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1216
+ file: string;
1217
+ /** 1-based line of the `ctx.ai.run` call, or `0` when unknown. */
1218
+ line: number;
1219
+ }
1220
+ /**
1221
+ * One `ctx.vectors.&lt;method>(indexName, input)` call whose `input.namespace` is derived
1222
+ * from the handler's `args` with no server-side scoping — the
1223
+ * `vectors_namespace_from_user_input` lint input. A Vectorize namespace partitions one
1224
+ * index into isolated sub-collections, so a namespace taken straight from request input
1225
+ * lets any caller read or poison another tenant's vectors. A fixed literal namespace, or
1226
+ * one prefixed with a server-trusted identity (`${ctx.auth.orgId}` — references `ctx`, so
1227
+ * treated as scoped), is not recorded; only an arg-derived, unscoped namespace reaches
1228
+ * here. Structurally identical to `AdvisorVectorNamespaceAccess`.
1229
+ */
1230
+ interface VectorNamespaceAccessIR {
1231
+ /** Export binding name of the procedure performing the `ctx.vectors` access. */
1232
+ exportName: string;
1233
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1234
+ file: string;
1235
+ /** 1-based line of the `ctx.vectors` call, or `0` when unknown. */
1236
+ line: number;
1237
+ /** The `ctx.vectors` method invoked: `query` / `upsert` / `upsertMany`. */
1238
+ method: string;
1239
+ }
1240
+ /**
1241
+ * One `ctx.mail`/`ctx.email` `send`/`queue` call whose recipient field (`to`/`cc`/`bcc`)
1242
+ * is derived from the handler's `args` with no server-side scoping — the
1243
+ * `mail_recipient_from_request_input` lint input. A recipient taken straight from request
1244
+ * input turns the deployment into an open relay / spam amplifier (any caller can direct
1245
+ * mail to an arbitrary address). A fixed literal recipient, or one scoped by a
1246
+ * server-trusted `ctx.*` value (e.g. `ctx.auth.user.email`), is not recorded; only an
1247
+ * arg-derived, unscoped recipient reaches here. Structurally identical to
1248
+ * `AdvisorMailRecipientAccess`.
1249
+ */
1250
+ interface MailRecipientAccessIR {
1251
+ /** Export binding name of the procedure performing the `ctx.mail`/`ctx.email` call. */
1252
+ exportName: string;
1253
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1254
+ file: string;
1255
+ /** 1-based line of the `ctx.mail`/`ctx.email` call, or `0` when unknown. */
1256
+ line: number;
1257
+ /** The mailer method invoked: `send` / `queue`. */
1258
+ method: string;
1259
+ }
1260
+ /**
1261
+ * One `ctx.browser.&lt;method>(url, …)` call whose navigation URL (`arguments[0]`)
1262
+ * is derived from the handler's `args` with no server-side scoping — the
1263
+ * `browser_user_url_without_allowlist` lint input. The lint additionally
1264
+ * cross-references `createBrowser` config-call evidence to suppress findings
1265
+ * when the browser is hardened with an `allowedHosts` allowlist or
1266
+ * `resolveDns`. Structurally identical to `AdvisorBrowserUrlAccess`.
1267
+ */
1268
+ interface BrowserUrlAccessIR {
1269
+ /** Export binding name of the procedure performing the `ctx.browser` call. */
1270
+ exportName: string;
1271
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1272
+ file: string;
1273
+ /** 1-based line of the `ctx.browser` call, or `0` when unknown. */
1274
+ line: number;
1275
+ /** The browser method invoked: `content` / `pdf` / `scrape` / `screenshot`. */
1276
+ method: string;
1277
+ }
1278
+ /**
1279
+ * One runtime container-override call: a `&lt;handle>.start({ enableInternet: true, … })`
1280
+ * launch override, or a `&lt;handle>.egress.&lt;method>(...)` runtime firewall mutation
1281
+ * (`allow` / `deny` / `setAllowed`) — the `container_start_enable_internet_override`
1282
+ * and `container_runtime_egress_relaxation` lint input. Both shapes re-open network
1283
+ * access the static `defineContainer` declaration (and its `container_public_internet`
1284
+ * lint) assumes is locked down. Matched structurally by call shape, independent of the
1285
+ * receiver's resolved type. Structurally identical to `AdvisorContainerOverride`.
1286
+ */
1287
+ interface ContainerOverrideIR {
1288
+ /** e.g. the egress method name, or `"enableInternet: true"`. */
1289
+ detail: string;
1290
+ /** Export binding name of the procedure performing the call. */
1291
+ exportName: string;
1292
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1293
+ file: string;
1294
+ /** Which override shape matched. */
1295
+ kind: "egress_relaxation" | "enable_internet";
1296
+ /** 1-based line of the call, or `0` when unknown. */
1297
+ line: number;
1298
+ }
1299
+ /**
1300
+ * One `buildImageDeliveryUrl({ key, … })` call (`@lunora/bindings/images`) whose
1301
+ * `key` — the CDN transform's source image, an absolute URL or an
1302
+ * origin-relative key — is derived from the handler's `args` with no
1303
+ * server-side scoping — the `images_url_source_from_user_input` lint input.
1304
+ * `ctx.images.transform`/`info` take image *bytes*, never a URL, so they are not
1305
+ * sinks; only the `key` of `buildImageDeliveryUrl` accepts a URL-or-key source
1306
+ * and is inspected. An arg-derived `key` lets any caller point the CDN's
1307
+ * `/cdn-cgi/image/` transform at an attacker-chosen origin (SSRF / open proxy)
1308
+ * or at an arbitrary key under the account's own store. A fixed literal, or a
1309
+ * key scoped by a server-trusted `ctx.*` value, is not recorded. Structurally
1310
+ * identical to `AdvisorImageDeliveryUrlAccess`.
1311
+ */
1312
+ interface ImageDeliveryUrlAccessIR {
1313
+ /** Export binding name of the procedure performing the `buildImageDeliveryUrl` call. */
1314
+ exportName: string;
1315
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1316
+ file: string;
1317
+ /** 1-based line of the `buildImageDeliveryUrl` call, or `0` when unknown. */
1318
+ line: number;
1319
+ }
1320
+ /**
1321
+ * One `createAuth({...})` call's configuration snapshot — the shared input for
1322
+ * the five `auth_*` security lints (trusted-origins wildcard, CSRF check
1323
+ * disabled, secure cookies disabled, email verification disabled, session
1324
+ * freshAge zero). Matched by callee NAME (an `import`-agnostic, fail-closed
1325
+ * convention the other feeders share), so a re-export or alias still resolves.
1326
+ * When the config argument isn't a statically-analyzable object literal (a
1327
+ * top-level spread, or not an object literal at all), `analyzable` is `false`
1328
+ * and every boolean fact defaults to its SAFE (not-flagged) value — an opaque
1329
+ * config can't be relied on either way. Structurally identical to
1330
+ * `AdvisorAuthConfig`.
1331
+ */
1332
+ interface AuthConfigIR {
1333
+ /** `true` when the call's config argument was a static object literal the feeder could read. */
1334
+ analyzable: boolean;
1335
+ /** `advanced.disableCSRFCheck === true`. */
1336
+ disableCsrfCheck: boolean;
1337
+ /** `emailAndPassword.enabled === true`. */
1338
+ emailPasswordEnabled: boolean;
1339
+ /** Export binding name enclosing the `createAuth(...)` call. */
1340
+ exportName: string;
1341
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1342
+ file: string;
1343
+ /** 1-based line of the `createAuth(...)` call, or `0` when unknown. */
1344
+ line: number;
1345
+ /** `emailAndPassword.requireEmailVerification === true` present. */
1346
+ requireEmailVerification: boolean;
1347
+ /** `trustedOrigins` array literal contains a `"*"` element. */
1348
+ /** `plugins` includes `scim(...)` while `database` is a non-transactional Lunora adapter — a combination that throws at runtime. */
1349
+ scimOnNonTransactionalAdapter: boolean;
1350
+ /** `advanced.useSecureCookies === false`. */
1351
+ secureCookiesDisabled: boolean;
1352
+ /** `session.freshAge === 0` (explicit literal). */
1353
+ sessionFreshAgeZero: boolean;
1354
+ trustedOriginsWildcard: boolean;
1355
+ }
1356
+ /**
1357
+ * One `rateLimit`/`dbRateLimit` middleware call (`@lunora/ratelimit`) whose
1358
+ * `key` selector — the per-caller rate-limit sub-key, `(ctx) => string |
1359
+ * undefined` — is derived from the handler's `args` with no server-side
1360
+ * scoping (no reference to the trusted `ctx` binding anywhere in the selector)
1361
+ * — the `ratelimit_key_spoofable_or_global` lint input. A key an attacker
1362
+ * controls lets them rotate it per request and bypass the limit entirely,
1363
+ * defeating its purpose. A selector scoped by `ctx` (e.g. `ctx.auth.userId`,
1364
+ * `ctx.ip`), or one with no `args` reference at all (a fixed/global bucket —
1365
+ * the "no key" case this lint deliberately does not flag, to keep it low-FP),
1366
+ * is not recorded. Structurally identical to `AdvisorRatelimitKeySelector`.
1367
+ */
1368
+ interface RatelimitKeySelectorIR {
1369
+ /** The `rateLimit`/`dbRateLimit` callee invoked. */
1370
+ callee: string;
1371
+ /** Export binding name of the procedure whose `.use(...)` chain carries the call. */
1372
+ exportName: string;
1373
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1374
+ file: string;
1375
+ /** The rate limit's `name` argument (the second positional argument), or `""` when not a string literal. */
1376
+ limitName: string;
1377
+ /** 1-based line of the `rateLimit`/`dbRateLimit` call, or `0` when unknown. */
1378
+ line: number;
1379
+ }
1380
+ /**
1381
+ * One payload-derived privileged dispatch — a `ctx.run`/`context.run` back into a
1382
+ * Lunora function from inside a `defineQueue` push handler or a `defineWorkflow`
1383
+ * handler, whose args reference the handler's untrusted payload (`context.params`
1384
+ * for a workflow, a `for (… of batch.messages)` body for a queue) — the
1385
+ * `privileged_dispatch_unvalidated_payload` lint input. Both handler kinds run
1386
+ * under the **system identity** (RLS disabled), so forwarding attacker-influenced
1387
+ * payload into the dispatch bypasses the target's row policy. The resolved
1388
+ * `targetFile`/`targetExport` let the lint join RLS-procedure evidence and fire
1389
+ * only for RLS-gated targets. Structurally identical to `AdvisorPrivilegedDispatch`.
1390
+ */
1391
+ interface PrivilegedDispatchIR {
1392
+ /** `"queue"` for a `defineQueue` handler, `"workflow"` for a `defineWorkflow` handler. */
1393
+ dispatchKind: "queue" | "workflow";
1394
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1395
+ file: string;
1396
+ /** Export binding name of the handler performing the dispatch. */
1397
+ handlerExport: string;
1398
+ /** 1-based line of the dispatch call, or `0` when unknown. */
1399
+ line: number;
1400
+ /** Export name of the dispatched target (`send` in `api.messages.send`). */
1401
+ targetExport: string;
1402
+ /** File path of the dispatched target relative to `lunora/` (`messages` in `api.messages.send`). */
1403
+ targetFile: string;
1404
+ }
1405
+ /**
1406
+ * One discovered `httpRoute.&lt;verb>("/admin/…")` route on an admin/privileged-looking
1407
+ * path, with whether its builder chain references an auth/admin guard — the
1408
+ * `admin_route_without_guard` lint input. Structurally identical to
1409
+ * `AdvisorAdminRoute`.
1410
+ */
675
1411
  interface AdminRouteIR {
676
1412
  /** Export binding name of the route handler. */
677
1413
  exportName: string;
@@ -684,6 +1420,317 @@ interface AdminRouteIR {
684
1420
  /** `true` when the handler body references an auth/session/admin guard (`ctx.auth`, `getSession`, `requireAdmin`, …). */
685
1421
  usesGuard: boolean;
686
1422
  }
1423
+ /**
1424
+ * One tracked `ctx.storage.&lt;bucket>.&lt;method>(...)` upload/signing call — the
1425
+ * shared input for the storage config-hygiene security lints
1426
+ * (`storage_upload_without_content_type_allowlist`, `storage_upload_without_max_size`,
1427
+ * `storage_generate_upload_url_no_content_type_pin`, `storage_presigned_url_for_private_content`).
1428
+ * `upload`/`store` carry the `UploadOptions` guards (`allowedContentTypes` /
1429
+ * `maxSize`); `generateUploadUrl` carries the signed-PUT `contentType` pin;
1430
+ * `getPresignedUrl`/`getSignedUrl` carry a statically-known `expiresInSeconds`
1431
+ * literal. `presentKeys` is empty (and `expiresInSeconds` unset) when the
1432
+ * options argument was absent, a non-literal, or a spread — see `analyzable`.
1433
+ * Structurally identical to `AdvisorStorageUpload`.
1434
+ */
1435
+ interface StorageUploadIR {
1436
+ /** `true` when the call's options-object argument (or its deliberate absence) was statically resolvable. */
1437
+ analyzable: boolean;
1438
+ /** Numeric literal value of an `expiresInSeconds` option, when statically known (`getSignedUrl` / `getPresignedUrl` only). */
1439
+ expiresInSeconds?: number;
1440
+ /** Export binding name of the procedure performing the call. */
1441
+ exportName: string;
1442
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1443
+ file: string;
1444
+ /** 1-based line of the call, or `0` when unknown. */
1445
+ line: number;
1446
+ /** The `ctx.storage` method invoked. */
1447
+ method: "generateUploadUrl" | "getPresignedUrl" | "getSignedUrl" | "store" | "upload";
1448
+ /** Options-object keys present at the call site (empty when not `analyzable`, or when no options argument was passed). */
1449
+ presentKeys: string[];
1450
+ }
1451
+ /**
1452
+ * One discovered `httpAction`/`httpRoute` handler in `lunora/` that performs a
1453
+ * side effect (`ctx.runMutation` / `ctx.runAction` / a `ctx.db.{insert,patch,
1454
+ * replace,delete,insertManyUnsafe}` write) from the HTTP edge, with whether it
1455
+ * reads `ctx.auth` — the `http_action_missing_auth_guard` lint input. A handler
1456
+ * that mutates state or dispatches an action without ever consulting the request
1457
+ * identity is an unauthenticated write bypassing identity/RLS. Only handlers with
1458
+ * a statically-resolvable inline body and `ctx` binding are recorded (fail-safe
1459
+ * under-report); read-only handlers are never recorded. Structurally identical to
1460
+ * `AdvisorHttpActionGuard`.
1461
+ */
1462
+ interface HttpActionGuardIR {
1463
+ /** Export binding name of the handler (or `"&lt;module>"` when mounted inline / not a named binding). */
1464
+ exportName: string;
1465
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1466
+ file: string;
1467
+ /** Which HTTP surface the handler is: a raw `httpAction` or a typed `httpRoute` route. */
1468
+ kind: "httpAction" | "httpRoute";
1469
+ /** 1-based line of the handler call, or `0` when unknown. */
1470
+ line: number;
1471
+ /** For an `httpRoute`, the uppercased verb (`"POST"`); absent for a raw `httpAction`. */
1472
+ method?: string;
1473
+ /** `true` when the handler reads `ctx.auth` (a direct member access or a `const { auth } = ctx` destructure). */
1474
+ readsAuth: boolean;
1475
+ /** The first side effect found, as a stable label: `runMutation`, `runAction`, or `db.&lt;method>`. */
1476
+ sideEffect: string;
1477
+ }
1478
+ /**
1479
+ * One response-header write, inside an `httpAction` handler, whose value is derived
1480
+ * from raw request input (`request.headers`, `request.url`/query, `await
1481
+ * request.json()`) with no CR/LF sanitizer — the
1482
+ * `http_action_response_header_injection` lint input. A `Request`-derived string
1483
+ * placed verbatim into a response header lets a caller smuggle `\r\n` and inject
1484
+ * extra headers or split the response (header injection / response splitting). Only
1485
+ * sites whose value is request-tainted AND unguarded are recorded: a value routed
1486
+ * through a CR/LF guard (`isSafeHeaderValue`), a URL/URI encoder
1487
+ * (`encodeURIComponent`/`encodeURI`), a numeric coercion (`Number`/`parseInt`/
1488
+ * `parseFloat`), or `btoa` is treated as safe and never recorded (`String(...)` /
1489
+ * `.toString()` are NOT sanitizers — they don't strip CR/LF). Structurally
1490
+ * identical to `AdvisorHttpHeaderWrite`.
1491
+ */
1492
+ interface HttpHeaderWriteIR {
1493
+ /** Export binding name of the enclosing handler, or `"&lt;module>"` when mounted inline. */
1494
+ exportName: string;
1495
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1496
+ file: string;
1497
+ /** The header name being written (`"location"`), or `""` when the key is not a string literal. */
1498
+ headerName: string;
1499
+ /** 1-based line of the request-tainted header value. */
1500
+ line: number;
1501
+ /** How the header was written. */
1502
+ via: "headers-append" | "headers-ctor" | "headers-set" | "response-init";
1503
+ }
1504
+ /**
1505
+ * One rate-limit / Turnstile middleware call in `lunora/` — the
1506
+ * `ratelimit_middleware_fail_open` lint input. `rateLimit`/`dbRateLimit`
1507
+ * (`@lunora/ratelimit`) and `verifyTurnstileMiddleware` (`@lunora/auth`) each
1508
+ * accept a `failOpen` escape hatch that admits every request when the
1509
+ * limiter/siteverify is unavailable; `failOpen` is `true` only when the options
1510
+ * literal set it to the boolean literal `true` (anything else is fail-closed).
1511
+ * The lint escalates a fail-open guard to a finding when the guarded procedure
1512
+ * (`exportName`/`limitName`) looks auth/payment-sensitive. Structurally
1513
+ * identical to `AdvisorFailOpenGuard`.
1514
+ */
1515
+ interface FailOpenGuardIR {
1516
+ /** The middleware factory at the call site: `rateLimit` / `dbRateLimit` / `verifyTurnstileMiddleware`. */
1517
+ callee: string;
1518
+ /** Export binding name of the procedure the guard is attached to, or `"&lt;module>"` at file scope. */
1519
+ exportName: string;
1520
+ /** `true` only when the options literal set `failOpen: true` as a boolean literal; a non-literal or absent option is treated as fail-closed. */
1521
+ failOpen: boolean;
1522
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1523
+ file: string;
1524
+ /** The rate-limit `name` (second string argument) for `rateLimit`/`dbRateLimit`; `""` for `verifyTurnstileMiddleware`. */
1525
+ limitName: string;
1526
+ /** 1-based line of the middleware call, or `0` when unknown. */
1527
+ line: number;
1528
+ }
1529
+ /**
1530
+ * One `ctx.flags.boolean("key", &lt;boolean-literal>)` read in `lunora/` — the
1531
+ * `flag_gates_security_with_unsafe_default` lint input. OpenFeature returns the
1532
+ * `defaultValue` when the provider errors, so a fail-open default on a
1533
+ * security-shaped key silently opens access during an outage. Only reads with a
1534
+ * statically-known string key and boolean-literal default are recorded; the lint
1535
+ * owns the security-shape + polarity judgment. Structurally identical to
1536
+ * `AdvisorFlagSecurityDefault`.
1537
+ */
1538
+ interface FlagSecurityDefaultIR {
1539
+ /** The boolean-literal default returned on a provider outage (fail-open value). */
1540
+ defaultValue: boolean;
1541
+ /** Export binding name of the procedure performing the flag read, or `"&lt;module>"` at file scope. */
1542
+ exportName: string;
1543
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1544
+ file: string;
1545
+ /** The flag key — the first string-literal argument of `ctx.flags.boolean`. */
1546
+ key: string;
1547
+ /** 1-based line of the `ctx.flags.boolean` call, or `0` when unknown. */
1548
+ line: number;
1549
+ }
1550
+ /**
1551
+ * One `generateText` / `streamText` call in `lunora/` whose `tools` reach a
1552
+ * privileged side effect (a DB write, function dispatch, or outbound
1553
+ * fetch/mail/queue send). `userInputDerived` records whether the model input
1554
+ * (`prompt`/`messages`/`system`) flows from the handler's `args`; the
1555
+ * `ai_tool_side_effect_prompt_injection` lint fires only when it does.
1556
+ * Structurally identical to `AdvisorAiToolSideEffect`.
1557
+ */
1558
+ interface AiToolSideEffectIR {
1559
+ /** Export binding name of the procedure performing the call. */
1560
+ exportName: string;
1561
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1562
+ file: string;
1563
+ /** 1-based line of the generation call, or `0` when unknown. */
1564
+ line: number;
1565
+ /** The generation entrypoint invoked. */
1566
+ method: "generateText" | "streamText";
1567
+ /** The privileged side-effect sink a model-callable tool reaches (`ctx.db.insert`, `ctx.run`, `ctx.fetch`, …). */
1568
+ sideEffect: string;
1569
+ /** `true` when a model-input option is derived from the handler's `args` (a bare `args.x`, or a name destructured from `args`). */
1570
+ userInputDerived: boolean;
1571
+ }
1572
+ /**
1573
+ * One `&lt;receiver>.identity.&lt;key>` claim read in `lunora/`, where `&lt;receiver>` is
1574
+ * an RLS/mask policy `auth` (or `ctx.auth`/`context.auth`). `declared` records
1575
+ * whether `&lt;key>` is in the app's `defineIdentity({ ... })` contract (or the
1576
+ * always-present `userId`); the `identity_undeclared_claim_trusted` lint fires on
1577
+ * the undeclared reads. Emitted only when a resolvable identity contract exists.
1578
+ * Structurally identical to `AdvisorIdentityClaimRead`.
1579
+ */
1580
+ interface IdentityClaimReadIR {
1581
+ /** `true` when `key` is a declared claim (in the `defineIdentity` contract, or the always-present `userId`). */
1582
+ declared: boolean;
1583
+ /** Export binding name of the enclosing declaration (`&lt;module>` at file scope). */
1584
+ exportName: string;
1585
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1586
+ file: string;
1587
+ /** The claim key read off the identity bag. */
1588
+ key: string;
1589
+ /** 1-based line of the read, or `0` when unknown. */
1590
+ line: number;
1591
+ }
1592
+ /**
1593
+ * One payment webhook-adapter construction in `lunora/` (`createStripeAdapter` /
1594
+ * `createPolarAdapter` / `createAutumnAdapter` / `createDodoPaymentsAdapter`).
1595
+ * `toleranceSeconds` carries the statically-known `webhookToleranceSeconds`
1596
+ * replay window when it is a plain numeric literal; the payment-webhook
1597
+ * wide-tolerance lint fires when it exceeds a conservative ceiling. Structurally
1598
+ * identical to `AdvisorPaymentWebhook`.
1599
+ */
1600
+ interface PaymentWebhookIR {
1601
+ /** The adapter factory invoked. */
1602
+ callee: "createAutumnAdapter" | "createDodoPaymentsAdapter" | "createPolarAdapter" | "createStripeAdapter";
1603
+ /** Export binding name of the enclosing declaration (`&lt;module>` at file scope). */
1604
+ exportName: string;
1605
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1606
+ file: string;
1607
+ /** 1-based line of the construction, or `0` when unknown. */
1608
+ line: number;
1609
+ /** Statically-known `webhookToleranceSeconds` literal, when present and a plain numeric literal. */
1610
+ toleranceSeconds?: number;
1611
+ }
1612
+ /**
1613
+ * One `ctx.db.&lt;table>.findMany({ includeDeleted })` list read whose
1614
+ * `includeDeleted` is either a hardcoded `true` or derived from the handler's
1615
+ * `args` — the `soft_delete_include_deleted_from_args` lint input. The lint joins
1616
+ * `table` against the schema's soft-delete tables and `visibility` against
1617
+ * `.public()` before flagging. Structurally identical to `AdvisorSoftDeleteRead`
1618
+ * so values pass straight through without conversion.
1619
+ */
1620
+ interface SoftDeleteReadIR {
1621
+ /** Export binding name of the procedure performing the read. */
1622
+ exportName: string;
1623
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1624
+ file: string;
1625
+ /** `true` when `includeDeleted` was derived from the handler's `args` (any caller can flip it). */
1626
+ fromArgs: boolean;
1627
+ /** `true` when `includeDeleted` was a hardcoded `true` literal (always resurfaces soft-deleted rows). */
1628
+ hardcodedTrue: boolean;
1629
+ /** 1-based line of the read call. */
1630
+ line: number;
1631
+ /** Table read, or `""` when the table-arg form's first argument wasn't a string literal. */
1632
+ table: string;
1633
+ /** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
1634
+ visibility: "internal" | "public";
1635
+ }
1636
+ /**
1637
+ * One `ctx.db.&lt;table>.findMany({ with: { &lt;rel> } })` relation-hydrating list read
1638
+ * — the `masked_relation_leak_via_with` lint input. Column masking does not
1639
+ * descend into `with`-hydrated relations, so a masked table surfaced only through
1640
+ * a `with` on an unprotected parent read is returned in the clear. The lint
1641
+ * resolves each relation accessor to its target table and joins it against the
1642
+ * discovered mask evidence before flagging. Structurally identical to
1643
+ * `AdvisorRelationLoad` so values pass straight through without conversion.
1644
+ */
1645
+ interface RelationLoadIR {
1646
+ /** Export binding name of the procedure performing the read. */
1647
+ exportName: string;
1648
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1649
+ file: string;
1650
+ /** 1-based line of the read call. */
1651
+ line: number;
1652
+ /** Parent table the read targets, or `""` when the table-arg form's first argument wasn't a string literal. */
1653
+ parentTable: string;
1654
+ /** Relation accessor names named in the read's `with: { … }` map — matched against the parent table's declared relations. */
1655
+ relations: string[];
1656
+ /** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
1657
+ visibility: "internal" | "public";
1658
+ }
1659
+ /**
1660
+ * One `query` handler whose `return` hands back the raw rows of a table — the
1661
+ * result of a `ctx.db.&lt;table>.findMany()` / `.findFirst()` / `.get()` read, or a
1662
+ * `ctx.db.query("&lt;table>")…collect()` fluent chain — returned directly (or through
1663
+ * one local `const` hop) with no hand-built projection. The
1664
+ * `output_projection_missing_on_public_read` lint keeps only `visibility ===
1665
+ * "public"` rows with no `.output(...)` / `.use(mask(...))` on the chain, then
1666
+ * joins `table` against the schema and flags one whose columns are PII-named.
1667
+ * Structurally identical to `AdvisorRawRowReturn` so values pass straight through
1668
+ * without conversion.
1669
+ */
1670
+ interface RawRowReturnIR {
1671
+ /** Export binding name of the query returning the raw rows. */
1672
+ exportName: string;
1673
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1674
+ file: string;
1675
+ /** 1-based line of the `return` (or concise-body) expression. */
1676
+ line: number;
1677
+ /** Table whose raw rows are returned, or `""` when the read's table wasn't a string literal. */
1678
+ table: string;
1679
+ /** `true` when the procedure's builder chain carries a `.use(mask(...))` step. */
1680
+ usesMask: boolean;
1681
+ /** `true` when the procedure's builder chain carries an `.output(...)` return-shape projection. */
1682
+ usesOutput: boolean;
1683
+ /** `"internal"` for `internalQuery`; `"public"` for `query`. */
1684
+ visibility: "internal" | "public";
1685
+ }
1686
+ /**
1687
+ * One `query`/`mutation` handler that gates a `ctx.db.get`/`patch`/`delete` on a
1688
+ * null-checked `ctx.db.normalizeId(table, id)` result — the
1689
+ * `normalize_id_used_as_authorization` lint input. `normalizeId` validates an id's
1690
+ * structural shape only (it never reads the database), so a non-null result proves
1691
+ * the id is well-formed, never that the caller owns the row; gating access on it is
1692
+ * an IDOR. The lint owns the negative proof — it keeps only `visibility === "public"`
1693
+ * rows with no `.use(rls(...))` and no ownership/identity mention (`mentionsOwnership`),
1694
+ * then joins `table` against the schema's RLS mode before flagging. Structurally
1695
+ * identical to `AdvisorNormalizeIdAuthorization` so values pass straight through.
1696
+ */
1697
+ interface NormalizeIdAuthorizationIR {
1698
+ /** Export binding name of the procedure performing the normalize-then-access. */
1699
+ exportName: string;
1700
+ /** Source file relative to `&lt;projectRoot>/lunora/`, without extension. */
1701
+ file: string;
1702
+ /** 1-based line of the `ctx.db.normalizeId(...)` call the access is gated on. */
1703
+ line: number;
1704
+ /** `true` when the handler anywhere reads an ownership-named identifier or `ctx.auth`/`ctx.identity`/… — an intervening ownership signal. */
1705
+ mentionsOwnership: boolean;
1706
+ /** The id-first `ctx.db` sink the normalized id reaches. */
1707
+ sinkMethod: "delete" | "get" | "patch";
1708
+ /** Table named in the `normalizeId` call, or `""` when its table argument wasn't a string literal. */
1709
+ table: string;
1710
+ /** `true` when the procedure's builder chain carries a `.use(rls(...))` step. */
1711
+ usesRls: boolean;
1712
+ /** `"internal"` for `internalQuery`/`internalMutation`; `"public"` for `query`/`mutation`. */
1713
+ visibility: "internal" | "public";
1714
+ }
1715
+ /**
1716
+ * One committed `wrangler.jsonc` `vars` entry whose value is a plaintext secret —
1717
+ * the `plaintext_secret_in_wrangler_vars` lint input. `vars` are baked into the
1718
+ * deployed Worker in cleartext and checked into source control, so a real API key
1719
+ * / token / private key there ships the secret to every reader of the repo and the
1720
+ * bundle; it belongs in a Secrets Store binding or `wrangler secret put`. Produced
1721
+ * by `@lunora/config` (which reads `wrangler.jsonc`), not a ts-morph feeder —
1722
+ * codegen only passes it through. Structurally identical to `AdvisorWranglerVariable`.
1723
+ */
1724
+ interface WranglerVariableIR {
1725
+ /** The `wrangler.jsonc` file the var was read from, relative to the project root. */
1726
+ file: string;
1727
+ /** The offending `vars` key (e.g. `STRIPE_SECRET_KEY`). */
1728
+ key: string;
1729
+ /** Heuristic that matched, e.g. `stripe_live_key` / `private_key` / `secret_named_var`. */
1730
+ kind: string;
1731
+ /** Redacted preview of the value (first few chars + length) for the finding detail — never the full secret. */
1732
+ preview: string;
1733
+ }
687
1734
  interface ProjectIR {
688
1735
  crons: ReadonlyArray<CronJobIR>;
689
1736
  functions: ReadonlyArray<FunctionIR>;
@@ -693,382 +1740,620 @@ interface ProjectIR {
693
1740
  schema: SchemaIR;
694
1741
  }
695
1742
  /**
696
- * Run the static lints against a discovered {@link SchemaIR} and the reads/writes/calls
697
- * found in function bodies: query reads feed `filter_without_index`, insert writes
698
- * feed `table_without_insert`, authApi calls feed `auth_api_call_without_headers`,
699
- * rls procedure snapshots feed `rls_uncovered_table`, and mask procedure
700
- * snapshots feed `mask_uncovered_pii_column`; declared containers
701
- * feed the `container_*` lints; declared workflows + `ctx.workflows.get(...)` call
702
- * sites feed the `workflow_unused` / `workflow_unknown_target` lints; non-deterministic
703
- * calls inside query/mutation handlers feed the `nondeterministic_query_mutation` lint
704
- * (all default empty for callers that don't analyze functions/containers/workflows).
705
- * The IR types are structurally identical to the advisor's evidence types so they
706
- * pass straight through without conversion. Returns the findings; surfacing them
707
- * (console, error overlay, studio Advisors table) is the caller's choice.
708
- */
709
- declare const lintSchema: (schema: SchemaIR, queries?: ReadonlyArray<QueryReadIR>, inserts?: ReadonlyArray<InsertWriteIR>, authApiCalls?: ReadonlyArray<AuthApiCallIR>, rlsProcedures?: ReadonlyArray<RlsProcedureIR>, containers?: ReadonlyArray<ContainerIR>, workflows?: ReadonlyArray<WorkflowIR>, workflowCalls?: ReadonlyArray<WorkflowCallIR>, maskProcedures?: ReadonlyArray<MaskProcedureIR>, nondeterministicCalls?: ReadonlyArray<NondeterministicCallIR>, procedureProtections?: ReadonlyArray<ProcedureMiddlewareIR>, argumentValidators?: ReadonlyArray<ArgumentValidatorIR>, secretLiterals?: ReadonlyArray<SecretLiteralIR>, sqlInterpolations?: ReadonlyArray<SqlInterpolationIR>, adminRoutes?: ReadonlyArray<AdminRouteIR>, r2sqlCalls?: ReadonlyArray<R2sqlCallIR>) => Finding[];
710
- /**
711
- * Render advisor findings as a single multi-line string for console surfacing:
712
- * a one-line summary header followed by one `[LEVEL] name: detail` line per
713
- * finding. Returns `""` when there are no findings.
714
- */
1743
+ * Run the static lints against a discovered {@link SchemaIR} and the reads/writes/calls
1744
+ * found in function bodies: query reads feed `filter_without_index`, insert writes
1745
+ * feed `table_without_insert`, authApi calls feed `auth_api_call_without_headers`,
1746
+ * rls procedure snapshots feed `rls_uncovered_table`, mask procedure
1747
+ * snapshots feed `mask_uncovered_pii_column`, and per-column mask strategies
1748
+ * feed `mask_weak_hash_strategy_on_pii`; declared containers
1749
+ * feed the `container_*` lints; declared workflows (with their durable step labels)
1750
+ * + `ctx.workflows.get(...)` call sites feed the `workflow_unused` /
1751
+ * `workflow_unknown_target` / duplicate-step-name lints; non-deterministic
1752
+ * calls inside query/mutation handlers feed the `nondeterministic_query_mutation` lint
1753
+ * (all default empty for callers that don't analyze functions/containers/workflows).
1754
+ * The IR types are structurally identical to the advisor's evidence types so they
1755
+ * pass straight through without conversion. Returns the findings; surfacing them
1756
+ * (console, error overlay, studio Advisors table) is the caller's choice.
1757
+ */
1758
+ /**
1759
+ * Named inputs for {@link lintSchema}. Every feeder is a discrete key rather than
1760
+ * a positional argument: the feeder list grows every few releases and many IR
1761
+ * types are structurally similar (`{file, exportName, line}`-shaped evidence),
1762
+ * so a positional call was a silent-transposition hazard — swapping two adjacent
1763
+ * arguments could typecheck yet feed the wrong evidence to the wrong lint and
1764
+ * corrupt a security advisory. `schema` is the only required field; every other
1765
+ * feeder defaults to "not analyzed" when omitted.
1766
+ */
1767
+ interface LintSchemaOptions {
1768
+ adminRoutes?: ReadonlyArray<AdminRouteIR>;
1769
+ aiRawRuns?: ReadonlyArray<AiRawRunIR>;
1770
+ aiToolSideEffects?: ReadonlyArray<AiToolSideEffectIR>;
1771
+ argumentDerivedFetches?: ReadonlyArray<ArgumentDerivedFetchIR>;
1772
+ argumentValidators?: ReadonlyArray<ArgumentValidatorIR>;
1773
+ authApiCalls?: ReadonlyArray<AuthApiCallIR>;
1774
+ authConfigs?: ReadonlyArray<AuthConfigIR>;
1775
+ browserUrlAccesses?: ReadonlyArray<BrowserUrlAccessIR>;
1776
+ configCalls?: ReadonlyArray<ConfigCallIR>;
1777
+ containerKeyAccesses?: ReadonlyArray<ContainerKeyAccessIR>;
1778
+ containerOverrides?: ReadonlyArray<ContainerOverrideIR>;
1779
+ containers?: ReadonlyArray<ContainerIR>;
1780
+ exportSinks?: ReadonlyArray<AdvisorExportSink>;
1781
+ failOpenGuards?: ReadonlyArray<FailOpenGuardIR>;
1782
+ flagSecurityDefaults?: ReadonlyArray<FlagSecurityDefaultIR>;
1783
+ geoIndexUsages?: ReadonlyArray<AdvisorGeoIndexUsage>;
1784
+ httpActionGuards?: ReadonlyArray<HttpActionGuardIR>;
1785
+ httpHeaderWrites?: ReadonlyArray<HttpHeaderWriteIR>;
1786
+ identityClaimReads?: ReadonlyArray<IdentityClaimReadIR>;
1787
+ imageDeliveryUrlAccesses?: ReadonlyArray<ImageDeliveryUrlAccessIR>;
1788
+ inserts?: ReadonlyArray<InsertWriteIR>;
1789
+ kvKeyAccesses?: ReadonlyArray<KvKeyAccessIR>;
1790
+ mailRecipientAccesses?: ReadonlyArray<MailRecipientAccessIR>;
1791
+ maskProcedures?: ReadonlyArray<MaskProcedureIR>;
1792
+ maskStrategies?: ReadonlyArray<MaskStrategyIR>;
1793
+ mutatorWrites?: ReadonlyArray<MutatorWriteIR>;
1794
+ nondeterministicCalls?: ReadonlyArray<NondeterministicCallIR>;
1795
+ normalizeIdAuthorizations?: ReadonlyArray<NormalizeIdAuthorizationIR>;
1796
+ notifyCalls?: ReadonlyArray<AdvisorNotifyCall>;
1797
+ notifyConfig?: AdvisorNotifyConfig;
1798
+ ownerFieldWrites?: ReadonlyArray<OwnerFieldWriteIR>;
1799
+ paymentWebhooks?: ReadonlyArray<PaymentWebhookIR>;
1800
+ privilegedDispatches?: ReadonlyArray<PrivilegedDispatchIR>;
1801
+ procedureProtections?: ReadonlyArray<ProcedureMiddlewareIR>;
1802
+ queries?: ReadonlyArray<QueryReadIR>;
1803
+ queues?: ReadonlyArray<QueueIR>;
1804
+ r2sqlCalls?: ReadonlyArray<R2sqlCallIR>;
1805
+ ratelimitKeySelectors?: ReadonlyArray<RatelimitKeySelectorIR>;
1806
+ rawRowReturns?: ReadonlyArray<RawRowReturnIR>;
1807
+ relationLoads?: ReadonlyArray<RelationLoadIR>;
1808
+ rlsProcedures?: ReadonlyArray<RlsProcedureIR>;
1809
+ schema: SchemaIR;
1810
+ secretLiterals?: ReadonlyArray<SecretLiteralIR>;
1811
+ shapes?: ReadonlyArray<ShapeIR>;
1812
+ softDeleteReads?: ReadonlyArray<SoftDeleteReadIR>;
1813
+ sqlInterpolations?: ReadonlyArray<SqlInterpolationIR>;
1814
+ storageKeyAccesses?: ReadonlyArray<StorageKeyAccessIR>;
1815
+ storageUploads?: ReadonlyArray<StorageUploadIR>;
1816
+ unrestrictedWhereBranches?: ReadonlyArray<UnrestrictedWhereBranchIR>;
1817
+ vectorNamespaceAccesses?: ReadonlyArray<VectorNamespaceAccessIR>;
1818
+ workflowCalls?: ReadonlyArray<WorkflowCallIR>;
1819
+ workflows?: ReadonlyArray<WorkflowIR>;
1820
+ wranglerVariables?: ReadonlyArray<WranglerVariableIR>;
1821
+ }
1822
+ declare const lintSchema: (options: LintSchemaOptions) => Finding[];
1823
+ /**
1824
+ * Render advisor findings as a single multi-line string for console surfacing:
1825
+ * a one-line summary header followed by one `[LEVEL] name: detail` line per
1826
+ * finding. Returns `""` when there are no findings.
1827
+ */
715
1828
  declare const formatAdvisories: (findings: ReadonlyArray<Finding>) => string;
716
1829
  /**
717
- * An error thrown by codegen discovery when the user's schema or function
718
- * source has a structural problem that can be pinpointed to a specific source
719
- * location. The `file`, `line`, and `column` properties mirror what Vite's
720
- * error-overlay `loc` field expects so the browser can display the exact spot.
721
- */
722
- declare class CodegenDiagnosticError extends Error {
1830
+ * An error thrown by codegen discovery when the user's schema or function
1831
+ * source has a structural problem that can be pinpointed to a specific source
1832
+ * location. A `LunoraError` subclass (`code: "CODEGEN_DIAGNOSTIC"`); the `file`,
1833
+ * `line`, and `column` properties (also passed through as the base `loc`) mirror
1834
+ * what Vite's error-overlay `loc` field expects so the browser can display the
1835
+ * exact spot.
1836
+ */
1837
+ declare class CodegenDiagnosticError extends LunoraError {
723
1838
  readonly column: number;
724
1839
  readonly file: string;
725
1840
  readonly line: number;
726
1841
  constructor(message: string, file: string, line: number, column: number);
727
1842
  }
728
1843
  /**
729
- * Build a {@link CodegenDiagnosticError} whose message includes the source
730
- * location and whose `file`/`line`/`column` properties are set from the
731
- * ts-morph `Node`'s position in its source file.
732
- *
733
- * Message format: `@lunora/codegen: &lt;detail> (&lt;file>:&lt;line>:&lt;column>)`
734
- */
735
- declare const diagnosticAt: (node: Node, detail: string) => CodegenDiagnosticError;
736
- /**
737
- * Discover `ctx.authApi.&lt;method>(...)` (and bare `authApi.&lt;method>(...)`) calls
738
- * under the lunora source directory and attribute each to the exported function
739
- * (and file) performing it. Calls outside an exported declaration are dropped.
740
- */
1844
+ * Build a {@link CodegenDiagnosticError} whose message includes the source
1845
+ * location and whose `file`/`line`/`column` properties are set from the
1846
+ * ts-morph `Node`'s position in its source file.
1847
+ *
1848
+ * Message format: `@lunora/codegen: &lt;detail> (&lt;file>:&lt;line>:&lt;column>)`
1849
+ *
1850
+ * `meta` is merged onto the returned error for callers that also carry the
1851
+ * project-wide `LunoraError` envelope (`code`/`name`/`status`) — it never
1852
+ * touches `file`/`line`/`column`, and the error stays an instance of
1853
+ * {@link CodegenDiagnosticError} so the Vite overlay's `instanceof` location
1854
+ * lookup is unaffected.
1855
+ */
1856
+ declare const diagnosticAt: (node: Node, detail: string, meta?: Record<string, unknown>) => CodegenDiagnosticError;
1857
+ /** The only file agents may be declared in — mirrors `lunora/workflows.ts`. */
1858
+ declare const AGENTS_FILENAME = "agents.ts";
1859
+ /**
1860
+ * Discover every agent the project declares: exported `defineAgent()` calls in
1861
+ * `lunora/agents.ts`. Returns `[]` when the file doesn't exist. Only four things
1862
+ * are read statically — the optional `name` override (wrangler `workflows[].name`),
1863
+ * the optional `publicRun` opt-in (the `agents:agentRun` capability gate), the
1864
+ * presence of a `voice` block (which turns on the voice-session Durable Object),
1865
+ * and the presence of an `onEmail` mapper (which wires the worker `email()`
1866
+ * handler); the rest of the agent config (model / tools / memory / voice models /
1867
+ * the `onEmail` closure body) is runtime-only, so codegen never evaluates it.
1868
+ */
1869
+ declare const discoverAgents: (project: Project, lunoraDirectory: string) => AgentIR[];
1870
+ /**
1871
+ * Discover `ctx.authApi.&lt;method>(...)` (and bare `authApi.&lt;method>(...)`) calls
1872
+ * under the lunora source directory and attribute each to the exported function
1873
+ * (and file) performing it. Calls outside an exported declaration are dropped.
1874
+ */
741
1875
  declare const discoverAuthApiCalls: (project: Project, lunoraDirectory: string) => AuthApiCallIR[];
742
1876
  /** The only file containers may be declared in — mirrors `lunora/crons.ts`. */
743
1877
  declare const CONTAINERS_FILENAME = "containers.ts";
744
1878
  /**
745
- * Discover every container the project declares: exported `defineContainer()`
746
- * calls in `lunora/containers.ts`. Returns `[]` when the file doesn't exist.
747
- * Wrangler-relevant fields (`image`, `instanceType`, `maxInstances`, `name`)
748
- * must be static literals; runtime-only fields (`env`, `sleepAfter`, …) may be
749
- * any expression since the generated class imports the definition object.
750
- */
1879
+ * Discover every container the project declares: exported `defineContainer()`
1880
+ * calls in `lunora/containers.ts`. Returns `[]` when the file doesn't exist.
1881
+ * Wrangler-relevant fields (`image`, `instanceType`, `maxInstances`, `name`)
1882
+ * must be static literals; runtime-only fields (`env`, `sleepAfter`, …) may be
1883
+ * any expression since the generated class imports the definition object.
1884
+ */
751
1885
  declare const discoverContainers: (project: Project, lunoraDirectory: string) => ContainerIR[];
752
1886
  /**
753
- * Scan every `.ts` file under `lunoraDir` for `cronJobs()` builder registrations
754
- * (`crons.interval(...)`, `crons.daily(...)`, `crons.cron(...)`, …) and lift them
755
- * into {@link CronJobIR}. Schedules are compiled to standard cron expressions;
756
- * function references are resolved to their `namespace:fn` dispatch path, while a
757
- * bare identifier naming a declared workflow (`workflows`) resolves to a durable
758
- * workflow start. Names must be unique across the project.
759
- */
760
- declare const discoverCrons: (project: Project, lunoraDirectory: string, workflows?: ReadonlyArray<WorkflowIR>) => CronJobIR[];
761
- /**
762
- * Scan all .ts files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
763
- * for top-level `export const x = query/mutation/action({...})` registrations.
764
- */
1887
+ * Scan every `.ts` file under `lunoraDir` for `cronJobs()` builder registrations
1888
+ * (`crons.interval(...)`, `crons.daily(...)`, `crons.cron(...)`, …) and lift them
1889
+ * into {@link CronJobIR}. Schedules are compiled to standard cron expressions;
1890
+ * function references are resolved to their `namespace:fn` dispatch path, while a
1891
+ * `workflows.NAME` / `agents.NAME` reference (or a bare identifier naming a
1892
+ * declared workflow) resolves to a durable workflow start. Names must be unique
1893
+ * across the project.
1894
+ */
1895
+ declare const discoverCrons: (project: Project, lunoraDirectory: string, workflows?: ReadonlyArray<WorkflowIR>, agents?: ReadonlyArray<AgentIR>) => CronJobIR[];
1896
+ /** The only file a feature-flag provider may be declared in — mirrors `lunora/queues.ts`. */
1897
+ declare const FLAGS_FILENAME = "flags.ts";
1898
+ /**
1899
+ * Discover the feature-flag provider a project declares in `lunora/flags.ts`.
1900
+ * Returns `undefined` when the file doesn't exist (the app has no flags). The
1901
+ * read is metadata-only and lenient: codegen wires `ctx.flags` purely from the
1902
+ * file's *existence* (`run-codegen.ts`) and imports the real module for the
1903
+ * provider value — this IR exists solely so the config layer can reconcile the
1904
+ * wrangler `flagship` binding for the Flagship binding-mode provider. Anything
1905
+ * it can't read statically degrades to a `custom` provider (no binding), never
1906
+ * a thrown error.
1907
+ */
1908
+ declare const discoverFlags: (project: Project, lunoraDirectory: string) => FlagsIR | undefined;
1909
+ /**
1910
+ * Scan all .ts files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
1911
+ * for top-level `export const x = query/mutation/action({...})` registrations.
1912
+ */
765
1913
  declare const discoverFunctions: (project: Project, lunoraDirectory: string) => FunctionIR[];
766
1914
  /**
767
- * Scan all `.ts` files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
768
- * for `export const x = httpRoute.&lt;verb>(...)…handler(...)` typed REST routes.
769
- * These are the headline OpenAPI target: each becomes a real `paths` entry.
770
- */
1915
+ * Scan all `.ts` files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
1916
+ * for `export const x = httpRoute.&lt;verb>(...)…handler(...)` typed REST routes.
1917
+ * These are the headline OpenAPI target: each becomes a real `paths` entry.
1918
+ */
771
1919
  declare const discoverHttpRoutes: (project: Project, lunoraDirectory: string) => HttpRouteIR[];
772
1920
  /**
773
- * Discover `ctx.db.insert("table", …)` writes under the lunora source directory
774
- * and attribute each to the exported function (and file) performing it. Calls
775
- * with a non-literal table argument, or outside an exported declaration, are
776
- * dropped (`table === ""` / no enclosing export).
777
- */
1921
+ * Discover `ctx.db.insert("table", …)` writes under the lunora source directory
1922
+ * and attribute each to the exported function (and file) performing it. Calls
1923
+ * with a non-literal table argument, or outside an exported declaration, are
1924
+ * dropped (`table === ""` / no enclosing export).
1925
+ */
778
1926
  declare const discoverInserts: (project: Project, lunoraDirectory: string) => InsertWriteIR[];
779
1927
  /**
780
- * Discover masking usage for every exported Lunora procedure under the lunora
781
- * source directory — the column-level twin of `discoverRlsProcedures`. For each
782
- * procedure, records whether its builder chain includes `.use(mask(...))`, which
783
- * `(table, column)` pairs that mask declares, and which tables it reads/writes
784
- * through `ctx.db`. Feeds the `mask_uncovered_pii_column` advisor lint.
785
- */
1928
+ * Discover masking usage for every exported Lunora procedure under the lunora
1929
+ * source directory — the column-level twin of `discoverRlsProcedures`. For each
1930
+ * procedure, records whether its builder chain includes `.use(mask(...))`, which
1931
+ * `(table, column)` pairs that mask declares, and which tables it reads/writes
1932
+ * through `ctx.db`. Feeds the `mask_uncovered_pii_column` advisor lint.
1933
+ */
786
1934
  declare const discoverMaskProcedures: (project: Project, lunoraDirectory: string) => MaskProcedureIR[];
787
1935
  /**
788
- * Aggregate the schema-wide masking metadata the studio's data-browser mask
789
- * toggle reads: every statically-discovered `(table, column, strategy)` masked
790
- * column across the project's `.use(mask(...))` chains. Walks the same builder
791
- * chains as {@link discoverMaskProcedures} but carries the strategy the preview
792
- * needs to choose redact-vs-hash-vs-custom rendering. Deduped by `(table,
793
- * column)` with the first declaration winning, so a column masked by several
794
- * procedures lists once — the same evidence the advisor lint uses.
795
- */
796
- /**
797
- * Scan all `.ts` files under `lunoraDir` for top-level
798
- * `export const x = defineMigration({...})` declarations and lift them into
799
- * {@link MigrationIR}. `id` must be a static string literal (it's the registry
800
- * key); `table` is best-effort and left `""` when not a literal.
801
- */
1936
+ * Scan all `.ts` files under `lunoraDir` for top-level
1937
+ * `export const x = defineMigration({...})` declarations and lift them into
1938
+ * {@link MigrationIR}. `id` must be a static string literal (it's the registry
1939
+ * key); `table` is best-effort and left `""` when not a literal.
1940
+ */
802
1941
  declare const discoverMigrations: (project: Project, lunoraDirectory: string) => MigrationIR[];
803
- /**
804
- * Discover non-deterministic API calls (`Date.now`, `new Date()`, `Date()`,
805
- * `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues` — including
806
- * `globalThis`/`self`/`window`-prefixed receivers and `fetch`) lexically inside
807
- * the handler body of every exported `query(...)` / `mutation(...)` registration
808
- * under the lunora source directory the `nondeterministic_query_mutation` lint
809
- * input. `action(...)` (and `stream(...)`) registrations are intentionally
810
- * skipped: actions run exactly once and may use ambient APIs freely.
811
- *
812
- * Traversal is scoped to the handler node (not the whole declaration), mirroring
813
- * how the auth-api / insert feeders attribute calls — so a call in a sibling
814
- * helper outside the handler, or in a nested `action(...)` passed elsewhere, is
815
- * not attributed to the query/mutation. One {@link NondeterministicCallIR} is
816
- * produced per call site.
817
- */
1942
+ /** The only file custom mutators may be declared in — mirrors `lunora/queues.ts`. */
1943
+ declare const MUTATORS_FILENAME = "mutators.ts";
1944
+ /**
1945
+ * Discover every custom mutator the project declares: exported
1946
+ * `defineMutator()` calls in `lunora/mutators.ts`. Returns `[]` when the file
1947
+ * doesn't exist. The export binding plus the declared `args` / `server` return
1948
+ * type are lifted — enough to emit a typed `api.mutators.&lt;name>` reference
1949
+ * while the runtime object still carries the authoritative `server` impl +
1950
+ * `handler`, so codegen never evaluates the body. The client `client` impl is
1951
+ * split into the browser bundle separately.
1952
+ */
1953
+ declare const discoverMutators: (project: Project, lunoraDirectory: string) => MutatorIR[];
1954
+ /**
1955
+ * Discover non-deterministic API calls (`Date.now`, `new Date()`, `Date()`,
1956
+ * `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues` — including
1957
+ * `globalThis`/`self`/`window`-prefixed receivers — and `fetch`) lexically inside
1958
+ * the handler body of every exported `query(...)` / `mutation(...)` registration
1959
+ * under the lunora source directory — the `nondeterministic_query_mutation` lint
1960
+ * input. `action(...)` (and `stream(...)`) registrations are intentionally
1961
+ * skipped: actions run exactly once and may use ambient APIs freely.
1962
+ *
1963
+ * Traversal is scoped to the handler node (not the whole declaration), mirroring
1964
+ * how the auth-api / insert feeders attribute calls — so a call in a sibling
1965
+ * helper outside the handler, or in a nested `action(...)` passed elsewhere, is
1966
+ * not attributed to the query/mutation. One {@link NondeterministicCallIR} is
1967
+ * produced per call site.
1968
+ */
818
1969
  declare const discoverNondeterministicCalls: (project: Project, lunoraDirectory: string) => NondeterministicCallIR[];
819
- /**
820
- * Discover `ctx.db.query("table")…` reads under the lunora source directory and
821
- * reduce each to a {@link QueryReadIR}. Only reads that call `.filter()` are
822
- * returned an unfiltered read is never a `filter_without_index` candidate, so
823
- * dropping the rest keeps the lint input small.
824
- */
1970
+ /** The only file a `@lunora/notify` provider may be declared in — mirrors `lunora/flags.ts`. */
1971
+ declare const NOTIFY_FILENAME = "notify.ts";
1972
+ /**
1973
+ * Discover `ctx.notify` / `ctx.push` sends lexically inside the handler body of
1974
+ * every exported `query(...)` / `mutation(...)` registration under the lunora
1975
+ * source directory — the `notify_send_outside_action` lint input. `action(...)`
1976
+ * (and `stream(...)`) registrations are intentionally skipped: a notification
1977
+ * send is external I/O that belongs in actions. One {@link AdvisorNotifyCall} is
1978
+ * produced per send site.
1979
+ */
1980
+ declare const discoverNotifyCalls: (project: Project, lunoraDirectory: string) => AdvisorNotifyCall[];
1981
+ /**
1982
+ * Discover which push channels the project's `lunora/notify.ts` default export
1983
+ * (`defineNotify({...})`) wires plus whether any handler sends a push — the
1984
+ * `notify_missing_push_config` lint input. Returns `undefined` when the file is
1985
+ * absent (the app declares no notify config). The read is metadata-only and
1986
+ * lenient (like `discoverFlags`): a `webPush`/`fcm` property's mere presence
1987
+ * counts as the channel being wired; a non-literal config degrades to "unwired"
1988
+ * rather than throwing.
1989
+ */
1990
+ declare const discoverNotifyConfig: (project: Project, lunoraDirectory: string) => AdvisorNotifyConfig | undefined;
1991
+ /**
1992
+ * Discover `ctx.db.query("table")…` reads under the lunora source directory and
1993
+ * reduce each to a {@link QueryReadIR}. Only reads that call `.filter()` are
1994
+ * returned — an unfiltered read is never a `filter_without_index` candidate, so
1995
+ * dropping the rest keeps the lint input small.
1996
+ */
825
1997
  declare const discoverQueries: (project: Project, lunoraDirectory: string) => QueryReadIR[];
826
- /**
827
- * Discover `ctx.r2sql` accesses lexically inside the handler body of every
828
- * exported `query(...)` / `mutation(...)` registration under the lunora source
829
- * directory the `r2sql_outside_action` lint input. `action(...)` (and
830
- * `stream(...)`) registrations are intentionally skipped: R2 SQL is the
831
- * external, non-reactive surface that belongs in actions.
832
- *
833
- * Traversal is scoped to the handler node (not the whole declaration), mirroring
834
- * `discoverNondeterministicCalls` so a `ctx.r2sql` touch in a sibling helper
835
- * outside the handler is not attributed to the query/mutation. One
836
- * {@link R2sqlCallIR} is produced per access site.
837
- */
1998
+ /** The only file queues may be declared in — mirrors `lunora/workflows.ts`. */
1999
+ declare const QUEUES_FILENAME = "queues.ts";
2000
+ /**
2001
+ * Discover every queue the project declares: exported `defineQueue()` calls in
2002
+ * `lunora/queues.ts`. Returns `[]` when the file doesn't exist. Only the
2003
+ * wrangler-relevant literals (`name`/`mode`/batch tuning) are read; the handler
2004
+ * body is runtime-only, so codegen never evaluates it.
2005
+ */
2006
+ declare const discoverQueues: (project: Project, lunoraDirectory: string) => QueueIR[];
2007
+ /**
2008
+ * Discover `ctx.r2sql` accesses lexically inside the handler body of every
2009
+ * exported `query(...)` / `mutation(...)` registration under the lunora source
2010
+ * directory — the `r2sql_outside_action` lint input. `action(...)` (and
2011
+ * `stream(...)`) registrations are intentionally skipped: R2 SQL is the
2012
+ * external, non-reactive surface that belongs in actions.
2013
+ *
2014
+ * Traversal is scoped to the handler node (not the whole declaration), mirroring
2015
+ * `discoverNondeterministicCalls` — so a `ctx.r2sql` touch in a sibling helper
2016
+ * outside the handler is not attributed to the query/mutation. One
2017
+ * {@link R2sqlCallIR} is produced per access site.
2018
+ */
838
2019
  declare const discoverR2sqlCalls: (project: Project, lunoraDirectory: string) => R2sqlCallIR[];
839
2020
  declare const discoverRlsProcedures: (project: Project, lunoraDirectory: string) => RlsProcedureIR[];
840
2021
  /**
841
- * Aggregate the schema-wide RLS metadata the studio's read-only inspector reads:
842
- * every statically-discovered `(table, on, procedure)` policy entry plus every
843
- * role declared via `rls(policies, { roles })`. Walks the same builder chains as
844
- * {@link discoverRlsProcedures} but extracts the richer `{ on }` operation +
845
- * role/permission shape rather than the lint's table-name set.
846
- *
847
- * Only the **builder** form (`c.use(rls(...)).query(...)`) can declare policies,
848
- * so bare-factory procedures contribute nothing. The `when` predicate is never
849
- * read — it's an opaque JS closure whose logic belongs in code, not the UI.
850
- * Roles are deduped by name (first declaration wins) so a role registered on
851
- * several procedures lists once.
852
- */
2022
+ * Aggregate the schema-wide RLS metadata the studio's read-only inspector reads:
2023
+ * every statically-discovered `(table, on, procedure)` policy entry plus every
2024
+ * role declared via `rls(policies, { roles })`. Walks the same builder chains as
2025
+ * {@link discoverRlsProcedures} but extracts the richer `{ on }` operation +
2026
+ * role/permission shape rather than the lint's table-name set.
2027
+ *
2028
+ * Only the **builder** form (`c.use(rls(...)).query(...)`) can declare policies,
2029
+ * so bare-factory procedures contribute nothing. The `when` predicate is never
2030
+ * read — it's an opaque JS closure whose logic belongs in code, not the UI.
2031
+ * Roles are deduped by name (first declaration wins) so a role registered on
2032
+ * several procedures lists once.
2033
+ */
853
2034
  declare const discoverRlsMetadata: (project: Project, lunoraDirectory: string) => RlsMetadataIR;
854
2035
  /**
855
- * Load `&lt;projectRoot>/lunora/schema.ts`, find `defineSchema({...})`, and
856
- * return a structural IR. Throws if the file or call cannot be found.
857
- */
858
- declare const discoverSchema: (project: Project, schemaPath: string, projectRoot?: string) => SchemaIR;
2036
+ * Which sandbox tools a project imports from `@lunora/agent` (main entry or the
2037
+ * `/sandbox` subpath), detected by NAMED value import. Drives two things:
2038
+ * registering the `sandbox:invoke` dispatcher (either tool) and provisioning the
2039
+ * `BROWSER` wrangler binding (`browserTool` the browser op runs on
2040
+ * `ctx.browser` inside the dispatcher).
2041
+ */
2042
+ interface SandboxUsage {
2043
+ /** `import { browserTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
2044
+ usesSandboxBrowser: boolean;
2045
+ /** `import { containerTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
2046
+ usesSandboxContainer: boolean;
2047
+ }
2048
+ declare const discoverSandboxUsage: (project: Project, lunoraDirectory: string) => SandboxUsage;
859
2049
  /**
860
- * Aggregate the schema-wide storage-rule metadata the studio's inspector reads:
861
- * every statically-discovered `(bucket, on, prefix, procedure)` entry across all
862
- * `.use(storageRules(...))` chains. Only the builder form can declare rules, so
863
- * bare-factory procedures contribute nothing.
864
- */
2050
+ * Load `&lt;projectRoot>/lunora/schema.ts`, find `defineSchema({...})`, and
2051
+ * return a structural IR. Throws if the file or call cannot be found.
2052
+ */
2053
+ declare const discoverSchema: (project: Project, schemaPath: string, projectRoot?: string) => SchemaIR;
2054
+ /** The only file shapes may be declared in — mirrors `lunora/queues.ts`. */
2055
+ declare const SHAPES_FILENAME = "shapes.ts";
2056
+ /**
2057
+ * Discover every replication shape the project declares: exported
2058
+ * `defineShape()` calls in `lunora/shapes.ts`. Returns `[]` when the file
2059
+ * doesn't exist. Only the export binding is lifted — the runtime object carries
2060
+ * the authoritative `table`/`columns`/`compileWhere`, so codegen never
2061
+ * evaluates the predicate.
2062
+ */
2063
+ declare const discoverShapes: (project: Project, lunoraDirectory: string) => ShapeIR[];
2064
+ /**
2065
+ * Aggregate the schema-wide storage-rule metadata the studio's inspector reads:
2066
+ * every statically-discovered `(bucket, on, prefix, procedure)` entry across all
2067
+ * `.use(storageRules(...))` chains. Only the builder form can declare rules, so
2068
+ * bare-factory procedures contribute nothing.
2069
+ */
865
2070
  declare const discoverStorageRulesMetadata: (project: Project, lunoraDirectory: string) => StorageRulesMetadataIR;
866
2071
  /** The only file workflows may be declared in — mirrors `lunora/containers.ts`. */
867
2072
  declare const WORKFLOWS_FILENAME = "workflows.ts";
868
2073
  /**
869
- * Discover every workflow the project declares: exported `defineWorkflow()`
870
- * calls in `lunora/workflows.ts`. Returns `[]` when the file doesn't exist. The
871
- * only wrangler-relevant literal is the optional `name` override; the workflow
872
- * body is runtime-only, so codegen never evaluates it.
873
- */
2074
+ * Discover every workflow the project declares: exported `defineWorkflow()`
2075
+ * calls in `lunora/workflows.ts`. Returns `[]` when the file doesn't exist. The
2076
+ * only wrangler-relevant literal is the optional `name` override; the workflow
2077
+ * body is runtime-only, so codegen never evaluates it.
2078
+ */
874
2079
  declare const discoverWorkflows: (project: Project, lunoraDirectory: string) => WorkflowIR[];
875
2080
  declare const GENERATED_HEADER = "// GENERATED by @lunora/codegen — do not edit.\n// Run `lunora codegen` to regenerate.\n\n";
876
2081
  /** Emit `_generated/dataModel.ts` — `Doc&lt;"name">` + `Id&lt;"name">` for every table. */
877
2082
  declare const emitDataModel: (schema: SchemaIR, useUmbrella?: boolean) => string;
878
2083
  /**
879
- * Emit `_generated/api.ts` — the typed `api.*` registry (public functions), the
880
- * `internal.*` registry, and (when the project declares workflows) the typed
881
- * `workflows.*` reference object. `api`/`internal` are the same `anyApi` proxy
882
- * at runtime (the `__lunoraRef` is identical); visibility is enforced
883
- * server-side at dispatch, not in the reference. Splitting the *types* keeps
884
- * internal functions off the client-facing `api` surface.
885
- */
886
- declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: ReadonlyArray<WorkflowIR>, useUmbrella?: boolean) => string;
887
- /**
888
- * Emit `_generated/seed.ts` — a project-bound `createSeedClient` with this
889
- * schema's `InsertModel` and runtime schema pre-applied, so a test or script
890
- * calls `createSeedClient({ seed: 1 }).users(5)` with full column types and no
891
- * manual wiring. The runtime schema is the default export of `lunora/schema.ts`
892
- * (the same import the generated ShardDO uses).
893
- *
894
- * Returns `""` when `@lunora/seed` is not a declared dependency, so projects
895
- * that don't use it keep a clean `_generated/` and never import the package.
896
- */
2084
+ * Emit `_generated/api.ts` — the typed `api.*` registry (public functions), the
2085
+ * `internal.*` registry, and (when the project declares them) the typed
2086
+ * `workflows.*` / `agents.*` scheduler-target reference objects. `api`/`internal` are the same `anyApi` proxy
2087
+ * at runtime (the `__lunoraRef` is identical); visibility is enforced
2088
+ * server-side at dispatch, not in the reference. Splitting the *types* keeps
2089
+ * internal functions off the client-facing `api` surface.
2090
+ */
2091
+ interface EmitApiOptions {
2092
+ agents?: ReadonlyArray<AgentIR>;
2093
+ functions: ReadonlyArray<FunctionIR>;
2094
+ /** Typed REST routes; only `.stream()` (SSE) routes emit a `httpStreams.*` reference. */
2095
+ httpRoutes?: ReadonlyArray<HttpRouteIR>;
2096
+ /** Custom mutators (`lunora/mutators.ts`) emitted as `api.mutators.*` so a client `serverRef` is compile-checked. */
2097
+ mutators?: ReadonlyArray<MutatorIR>;
2098
+ useUmbrella?: boolean;
2099
+ workflows?: ReadonlyArray<WorkflowIR>;
2100
+ }
2101
+ declare const emitApi: (options: EmitApiOptions) => string;
2102
+ /**
2103
+ * Emit `_generated/collections.ts` — a typed TanStack DB binding per `defineShape`
2104
+ * in `lunora/shapes.ts` (the local-first partial-replication surface).
2105
+ *
2106
+ * Each shape emits **two** entry points.
2107
+ *
2108
+ * `&lt;shape>CollectionOptions(options)` is the composable form: it returns the full
2109
+ * `LunoraCollectionOptions` — `config` for `createCollection`, plus `checkpoints`
2110
+ * (which `bindMutators` gates optimistic overlays on) and `scope`. This is what an app
2111
+ * with custom mutators needs, and what the old single-factory form made impossible: it
2112
+ * built the collection internally and dropped `checkpoints` on the floor, so there was
2113
+ * no way to wire mutators to the collection codegen produced.
2114
+ *
2115
+ * `&lt;shape>Collection(options)` is the convenience form for a read-only collection: it
2116
+ * returns `{ checkpoints, collection, scope }` rather than a bare `Collection`, so the
2117
+ * sync controls stay reachable even from the short path.
2118
+ *
2119
+ * Both are typed: `args` comes from the shape's own validators (a parameterless
2120
+ * shape takes none), rows resolve to `Doc&lt;"table">` when the shape names its table
2121
+ * with a literal, and `shardKey` / `getKey` / `load` / `onError` / `checkpoints` are
2122
+ * all threadable — a sharded table needs `shardKey` for its watermark to land in the
2123
+ * right bucket, and a server-minted `_id` that differs from the app's natural key
2124
+ * needs `getKey`.
2125
+ *
2126
+ * Returns `""` (so `writeIfPresent` skips the file) unless the project both
2127
+ * declares shapes AND installs `@lunora/db` — the add-on that ships
2128
+ * `lunoraCollectionOptions`. `@lunora/db` stays a scoped install even under the
2129
+ * `lunorash` umbrella (an opt-in add-on, like `@lunora/auth`), so its import is
2130
+ * always `@lunora/db/collections`; only the in-umbrella `@lunora/client` import
2131
+ * is remapped to `lunorash/client`.
2132
+ */
2133
+ declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
897
2134
  interface EmitServerOptions {
2135
+ /** Agents declared via `defineAgent` exports — wires the typed `ctx.agents` producers onto Mutation/Action contexts. */
2136
+ agents?: ReadonlyArray<AgentIR>;
898
2137
  containers?: ReadonlyArray<ContainerIR>;
2138
+ /**
2139
+ * The single `defineEnv(...)` contract declared in `lunora/env.ts`. When
2140
+ * present, `ctx.env` is typed as the validated `InferEnv` shape (recovered
2141
+ * via `ReturnType` over the accessor's `typeof`). `undefined` leaves `ctx.env`
2142
+ * the base optional binding record — byte-identical to today.
2143
+ */
2144
+ env?: EnvIR;
2145
+ /**
2146
+ * A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access
2147
+ * facade (`@lunora/cloudflare-access/context`) onto every ctx. Distinct from
2148
+ * `emitApp`'s `hasAccess` (which gates the worker's `.access()` resolveIdentity
2149
+ * method); this one gates the per-request `ctx.access` read surface.
2150
+ */
2151
+ hasAccessFacade?: boolean;
899
2152
  hasAi?: boolean;
900
- /** A `lunora/` source uses `@lunora/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
2153
+ /** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
901
2154
  hasAnalytics?: boolean;
902
2155
  /** A `lunora/` source uses `@lunora/browser` / `ctx.browser` — wires `ctx.browser` onto ActionCtx only. */
903
2156
  hasBrowser?: boolean;
2157
+ /** The project declares `lunora/flags.ts` — wires `ctx.flags` (OpenFeature) onto every ctx. */
2158
+ hasFlags?: boolean;
904
2159
  /** A `lunora/` source uses `@lunora/hyperdrive` / `ctx.sql` — wires `ctx.sql` onto ActionCtx only. */
905
2160
  hasHyperdrive?: boolean;
906
- /** A `lunora/` source uses `@lunora/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
2161
+ /** A `lunora/` source uses `@lunora/bindings/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
907
2162
  hasImages?: boolean;
908
- /** A `lunora/` source uses `@lunora/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
2163
+ /** A `lunora/` source uses `@lunora/bindings/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
909
2164
  hasKv?: boolean;
2165
+ /** The project declares `lunora/notify.ts` — wires `ctx.notify` + its `ctx.push` alias (`@lunora/notify`) onto every ctx. */
2166
+ hasNotify?: boolean;
910
2167
  hasPayments?: boolean;
911
- /** A `lunora/` source uses `@lunora/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
2168
+ /** A `lunora/` source uses `@lunora/bindings/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
912
2169
  hasPipelines?: boolean;
913
- /** A `lunora/` source uses `@lunora/r2sql` / `ctx.r2sql` — wires `ctx.r2sql` onto ActionCtx only. */
2170
+ /** A `lunora/` source uses `@lunora/bindings/r2sql` / `ctx.r2sql` — wires `ctx.r2sql` onto ActionCtx only. */
914
2171
  hasR2sql?: boolean;
2172
+ /** A `lunora/` source uses `@lunora/x402/pay` / `ctx.x402` — wires the agent-wallet pay rail onto ActionCtx only. */
2173
+ hasX402?: boolean;
2174
+ /**
2175
+ * The single `defineIdentity(...)` claim contract declared in
2176
+ * `lunora/identity.ts` (Plan 080). When present, `ctx.auth.getIdentity()`,
2177
+ * the RLS policy `ctx.auth.identity`, and the shard-authorization hooks
2178
+ * narrow to the declared shape (recovered via `InferIdentity` over the
2179
+ * contract's `typeof`). `undefined` keeps the identity an untyped bag —
2180
+ * byte-identical to today.
2181
+ */
2182
+ identity?: IdentityIR;
2183
+ /** Queues declared via `defineQueue` exports — wires the typed `ctx.queues` producers onto Mutation/Action contexts. */
2184
+ queues?: ReadonlyArray<QueueIR>;
915
2185
  schema?: SchemaIR;
916
2186
  storageRuleBuckets?: ReadonlyArray<string>;
917
2187
  /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
918
2188
  useUmbrella?: boolean;
919
2189
  workflows?: ReadonlyArray<WorkflowIR>;
920
2190
  }
921
- declare const emitServer: ({
922
- containers,
923
- hasAi,
924
- hasAnalytics,
925
- hasBrowser,
926
- hasHyperdrive,
927
- hasImages,
928
- hasKv,
929
- hasPayments,
930
- hasPipelines,
931
- hasR2sql,
932
- schema,
933
- storageRuleBuckets,
934
- useUmbrella,
935
- workflows
936
- }?: EmitServerOptions) => string;
937
- declare const emitFunctions: (functions: ReadonlyArray<FunctionIR>, migrations?: ReadonlyArray<MigrationIR>) => string;
938
- /**
939
- * Storage-column map per table for the file browser: `{ table: [field, …] }` for
940
- * every field declared `v.storage(...)` (unwrapping `v.optional(...)`). The
941
- * generated shard hands this to the base `storageColumns` hook so the admin
942
- * `storageReferences` read can join R2 objects back to the rows that own them
943
- * (and flag orphans objects no row references). Only scalar storage columns
944
- * are emitted; an array-of-storage field can't be matched by an equality scan,
945
- * so it is skipped here.
946
- */
947
- /**
948
- * Emit `_generated/containers.ts` one container-enabled Durable Object class
949
- * per `defineContainer` export, each a thin subclass of `LunoraContainer`
950
- * (`@lunora/container/do`) constructed with the user's definition object. The
951
- * worker entry must re-export these classes: wrangler requires every
952
- * `containers[].class_name` to be exported by the deployed worker. Returns ""
953
- * when the project declares no containers (the file is not written then).
954
- */
955
- declare const emitContainers: (containers: ReadonlyArray<ContainerIR>) => string;
956
- /**
957
- * Emit `_generated/workflows.ts` — one `WorkflowEntrypoint` class per
958
- * `defineWorkflow` export, each a thin subclass of `LunoraWorkflow`
959
- * (`@lunora/workflow/do`) constructed with the user's definition object. The
960
- * worker entry must re-export these classes: wrangler requires every
961
- * `workflows[].class_name` to be exported by the deployed worker. Returns ""
962
- * when the project declares no workflows (the file is not written then).
963
- */
2191
+ declare const emitServer: ({ agents, containers, env, hasAccessFacade, hasAi, hasAnalytics, hasBrowser, hasFlags, hasHyperdrive, hasImages, hasKv, hasNotify, hasPayments, hasPipelines, hasR2sql, hasX402, identity, queues, schema, storageRuleBuckets, useUmbrella, workflows }?: EmitServerOptions) => string;
2192
+ interface EmitFunctionsOptions {
2193
+ agents?: ReadonlyArray<AgentIR>;
2194
+ functions: ReadonlyArray<FunctionIR>;
2195
+ migrations?: ReadonlyArray<MigrationIR>;
2196
+ mutators?: ReadonlyArray<MutatorIR>;
2197
+ shapes?: ReadonlyArray<ShapeIR>;
2198
+ /** Import of a sandbox tool (`browserTool`/`containerTool`) auto-registers the `sandbox:invoke` action. */
2199
+ usesSandbox?: boolean;
2200
+ useUmbrella?: boolean;
2201
+ }
2202
+ declare const emitFunctions: (options: EmitFunctionsOptions) => string;
2203
+ /**
2204
+ * Emit `_generated/containers.ts` — one container-enabled Durable Object class
2205
+ * per `defineContainer` export, each a thin subclass of `LunoraContainer`
2206
+ * (`@lunora/container/do`) constructed with the user's definition object. The
2207
+ * worker entry must re-export these classes: wrangler requires every
2208
+ * `containers[].class_name` to be exported by the deployed worker. Returns ""
2209
+ * when the project declares no containers (the file is not written then).
2210
+ */
2211
+ declare const emitContainers: (containers: ReadonlyArray<ContainerIR>, jurisdiction?: JurisdictionIR) => string;
2212
+ /**
2213
+ * Emit `_generated/workflows.ts`one `WorkflowEntrypoint` class per
2214
+ * `defineWorkflow` export, each a thin subclass of `LunoraWorkflow`
2215
+ * (`@lunora/workflow/do`) constructed with the user's definition object. The
2216
+ * worker entry must re-export these classes: wrangler requires every
2217
+ * `workflows[].class_name` to be exported by the deployed worker. Returns ""
2218
+ * when the project declares no workflows (the file is not written then).
2219
+ */
964
2220
  declare const emitWorkflows: (workflows: ReadonlyArray<WorkflowIR>) => string;
2221
+ /**
2222
+ * Emit `_generated/agents.ts` — one `WorkflowEntrypoint` class per `defineAgent`
2223
+ * export, each a thin subclass of `LunoraWorkflow` (`@lunora/workflow/do`)
2224
+ * constructed with the compiled agent tool-loop (`compileAgentWorkflow`). Like
2225
+ * `_generated/workflows.ts`, the worker entry must re-export these classes:
2226
+ * wrangler requires every `workflows[].class_name` to be exported by the
2227
+ * deployed worker. Returns "" when the project declares no agents (the file is
2228
+ * not written then).
2229
+ */
2230
+ declare const emitAgents: (agents: ReadonlyArray<AgentIR>) => string;
965
2231
  interface EmitShardOptions {
966
2232
  advisories?: ReadonlyArray<Finding>;
2233
+ /** Agents declared via `defineAgent` exports in `lunora/agents.ts` — wires the typed `ctx.agents` producers. */
2234
+ agents?: ReadonlyArray<AgentIR>;
967
2235
  containers?: ReadonlyArray<ContainerIR>;
2236
+ /** The single `defineEnv(...)` contract declared in `lunora/env.ts` — applies the accessor to the worker `env` to populate `ctx.env`. */
2237
+ env?: EnvIR;
2238
+ /** Statically-discovered `ctx.flags.&lt;type>("key")` reads — the studio Flags page + reactive evaluation iterate these. */
2239
+ flagKeys?: ReadonlyArray<{
2240
+ key: string;
2241
+ type: "boolean" | "number" | "object" | "string";
2242
+ }>;
2243
+ /** A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access facade onto every ctx. */
2244
+ hasAccessFacade?: boolean;
968
2245
  hasAi?: boolean;
969
2246
  /** A `lunora/` source reads `ctx.analytics` — wires the Analytics Engine write helper onto every ctx. */
970
2247
  hasAnalytics?: boolean;
971
2248
  /** A `lunora/` source reads `ctx.browser` — wires `ctx.browser` onto the ActionCtx only. */
972
2249
  hasBrowser?: boolean;
2250
+ /** The project declares `lunora/flags.ts` — wires `ctx.flags` (OpenFeature) onto every ctx. */
2251
+ hasFlags?: boolean;
973
2252
  /** A `lunora/` source reads `ctx.sql` (Hyperdrive) — wires `ctx.sql` onto the ActionCtx only. */
974
2253
  hasHyperdrive?: boolean;
975
2254
  /** A `lunora/` source reads `ctx.images` — wires `ctx.images` onto the ActionCtx only. */
976
2255
  hasImages?: boolean;
977
2256
  /** A `lunora/` source reads `ctx.kv` — wires `ctx.kv` onto every ctx. */
978
2257
  hasKv?: boolean;
2258
+ /** The project declares `lunora/notify.ts` — wires `ctx.notify` + its `ctx.push` alias (`@lunora/notify`) onto every ctx. */
2259
+ hasNotify?: boolean;
979
2260
  hasPayments?: boolean;
2261
+ /** A `lunora/` source reads `ctx.pipelines` — wires `ctx.pipelines` onto the ActionCtx only. */
2262
+ hasPipelines?: boolean;
980
2263
  /** A `lunora/` source reads `ctx.r2sql` (R2 SQL) — wires `ctx.r2sql` onto the ActionCtx only. */
981
2264
  hasR2sql?: boolean;
2265
+ /** A `lunora/` source reads `ctx.x402` — wires the agent-wallet pay rail onto the ActionCtx only. */
2266
+ hasX402?: boolean;
982
2267
  maskMetadata?: MaskMetadataIR;
2268
+ /** Custom mutators declared via `defineMutator` in `lunora/mutators.ts` — wires the `isCustomMutator` push-protocol override. */
2269
+ mutators?: ReadonlyArray<MutatorIR>;
2270
+ /** Queues declared via `defineQueue` exports in `lunora/queues.ts` — wires the typed `ctx.queues` producers. */
2271
+ queues?: ReadonlyArray<QueueIR>;
983
2272
  rlsMetadata?: RlsMetadataIR;
984
2273
  schema: SchemaIR;
2274
+ /** Replication shapes declared via `defineShape` in `lunora/shapes.ts` — wires the `resolveShape` subscription override. */
2275
+ shapes?: ReadonlyArray<ShapeIR>;
985
2276
  storageRules?: StorageRulesMetadataIR;
986
2277
  studioFeatures?: StudioFeaturesResult;
987
2278
  /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
988
2279
  useUmbrella?: boolean;
989
2280
  workflows?: ReadonlyArray<WorkflowIR>;
990
2281
  }
991
- declare const emitShard: ({
992
- advisories,
993
- containers,
994
- hasAi,
995
- hasAnalytics,
996
- hasBrowser,
997
- hasHyperdrive,
998
- hasImages,
999
- hasKv,
1000
- hasPayments,
1001
- hasR2sql,
1002
- maskMetadata,
1003
- rlsMetadata,
1004
- schema,
1005
- storageRules,
1006
- studioFeatures,
1007
- useUmbrella,
1008
- workflows
1009
- }: EmitShardOptions) => string;
1010
- /**
1011
- * Emit drizzle `sqliteTable` definitions for the project schema, split into
1012
- * `global` (D1-backed) and `shard` (DO-SQLite-backed) buckets. Tables marked
1013
- * `.global()` go in the global file; everything else (default root + `.shardBy()`)
1014
- * goes in the shard file.
1015
- *
1016
- * `searchIndexes` are intentionally not emitted — drizzle has no `sqliteTable`
1017
- * abstraction for FTS5 virtual tables. FTS plumbing is handled by the runtime
1018
- * outside of drizzle.
1019
- */
2282
+ declare const emitShard: ({ advisories, agents, containers, env, flagKeys, hasAccessFacade, hasAi, hasAnalytics, hasBrowser, hasFlags, hasHyperdrive, hasImages, hasKv, hasNotify, hasPayments, hasPipelines, hasR2sql, hasX402, maskMetadata, mutators, queues, rlsMetadata, schema, shapes, storageRules, studioFeatures, useUmbrella, workflows }: EmitShardOptions) => string;
2283
+ /**
2284
+ * Emit drizzle `sqliteTable` definitions for the project schema, split into
2285
+ * `global` (D1-backed) and `shard` (DO-SQLite-backed) buckets. Tables marked
2286
+ * `.global()` go in the global file; everything else (default root + `.shardBy()`)
2287
+ * goes in the shard file.
2288
+ *
2289
+ * `searchIndexes` are intentionally not emitted — drizzle has no `sqliteTable`
2290
+ * abstraction for FTS5 virtual tables. FTS plumbing is handled by the runtime
2291
+ * outside of drizzle.
2292
+ */
1020
2293
  declare const emitDrizzleSchema: (schema: SchemaIR, useUmbrella?: boolean) => {
1021
2294
  global: string;
1022
2295
  shard: string;
1023
2296
  };
1024
2297
  /**
1025
- * Emit `_generated/crons.ts` from the discovered cron jobs.
1026
- *
1027
- * `LUNORA_CRON_TRIGGERS` is the deduplicated schedule array — what lands in
1028
- * wrangler's `triggers.crons` (the vite plugin reconciles it into
1029
- * `wrangler.jsonc`, and {@link emitWranglerCronTriggers} renders the same list
1030
- * for the CLI / docs).
1031
- *
1032
- * `LUNORA_CRONS` is the dispatcher map keyed by cron expression, each value a
1033
- * list of `{ name, functionPath, args }`. Cloudflare's `scheduled()` handler
1034
- * receives only the cron string, so multiple jobs sharing one expression must
1035
- * all fire — hence a list per key rather than a single entry.
1036
- *
1037
- * Jobs arrive pre-sorted by name (deterministic output); the trigger array
1038
- * preserves first-seen order of distinct expressions.
1039
- *
1040
- * Cloudflare caps a Worker at **3 Cron Triggers** (i.e. 3 distinct cron
1041
- * expressions). Because the dispatcher fires every job sharing an expression,
1042
- * many jobs can ride a single trigger — only the count of *distinct* schedules
1043
- * matters. `lunora codegen` warns when that count exceeds the limit; for
1044
- * finer-grained scheduling use Durable Object alarms (`@lunora/scheduler`),
1045
- * which have no such cap.
1046
- */
2298
+ * Emit `_generated/crons.ts` from the discovered cron jobs.
2299
+ *
2300
+ * `LUNORA_CRON_TRIGGERS` is the deduplicated schedule array — what lands in
2301
+ * wrangler's `triggers.crons` (the vite plugin reconciles it into
2302
+ * `wrangler.jsonc`, and {@link emitWranglerCronTriggers} renders the same list
2303
+ * for the CLI / docs).
2304
+ *
2305
+ * `LUNORA_CRONS` is the dispatcher map keyed by cron expression, each value a
2306
+ * list of `{ name, functionPath, args }`. Cloudflare's `scheduled()` handler
2307
+ * receives only the cron string, so multiple jobs sharing one expression must
2308
+ * all fire — hence a list per key rather than a single entry.
2309
+ *
2310
+ * Jobs arrive pre-sorted by name (deterministic output); the trigger array
2311
+ * preserves first-seen order of distinct expressions.
2312
+ *
2313
+ * Cloudflare caps a Worker at **3 Cron Triggers** (i.e. 3 distinct cron
2314
+ * expressions). Because the dispatcher fires every job sharing an expression,
2315
+ * many jobs can ride a single trigger — only the count of *distinct* schedules
2316
+ * matters. `lunora codegen` warns when that count exceeds the limit; for
2317
+ * finer-grained scheduling use Durable Object alarms (`@lunora/scheduler`),
2318
+ * which have no such cap.
2319
+ */
1047
2320
  declare const emitCrons: (crons: ReadonlyArray<CronJobIR>) => string;
1048
2321
  /**
1049
- * Render `_generated/vectors.ts` — the static registry of every vector index
1050
- * declared in `schema.ts` (inline `.vectorize()` columns + standalone
1051
- * `defineVectorIndex()` definitions).
1052
- *
1053
- * Cloudflare Vectorize exposes no way to enumerate an account's indexes at
1054
- * runtime — a binding can `describe()` itself but the worker can't ask "which
1055
- * indexes exist". So this generated array is the source of truth the studio's
1056
- * vector browser lists, and the worker's admin route pairs each entry with its
1057
- * live `describe()` stats. Entries are sorted by name for deterministic output.
1058
- */
2322
+ * Render `_generated/vectors.ts` — the static registry of every vector index
2323
+ * declared in `schema.ts` (inline `.vectorize()` columns + standalone
2324
+ * `defineVectorIndex()` definitions).
2325
+ *
2326
+ * Cloudflare Vectorize exposes no way to enumerate an account's indexes at
2327
+ * runtime — a binding can `describe()` itself but the worker can't ask "which
2328
+ * indexes exist". So this generated array is the source of truth the studio's
2329
+ * vector browser lists, and the worker's admin route pairs each entry with its
2330
+ * live `describe()` stats. Entries are sorted by name for deterministic output.
2331
+ */
1059
2332
  declare const emitVectors: (vectorIndexes: ReadonlyArray<VectorIndexIR>) => string;
1060
2333
  /**
1061
- * Render the deduplicated cron schedules as a JSON fragment suitable for
1062
- * splicing into `wrangler.jsonc`'s `triggers.crons`. The vite plugin uses this
1063
- * (plus the parsed wrangler config) to reconcile generated triggers without the
1064
- * user hand-editing the file.
1065
- */
2334
+ * Render the deduplicated cron schedules as a JSON fragment suitable for
2335
+ * splicing into `wrangler.jsonc`'s `triggers.crons`. The vite plugin uses this
2336
+ * (plus the parsed wrangler config) to reconcile generated triggers without the
2337
+ * user hand-editing the file.
2338
+ */
1066
2339
  declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => string[];
1067
2340
  /** Which capability methods the generated `defineApp` builder exposes — one flag per package-backed feature the app actually uses. */
1068
2341
  interface EmitAppOptions {
2342
+ /**
2343
+ * Inbound-email agents (`defineAgent({ onEmail })`) → wire the worker's
2344
+ * top-level `email()` handler to `dispatchAgentEmail(...)` (from
2345
+ * `@lunora/agent/inbound`), so received mail starts a durable run. Empty/absent
2346
+ * ⇒ no wiring, byte-identical output for email-free (and agent-free) projects.
2347
+ */
2348
+ emailAgents?: ReadonlyArray<{
2349
+ bindingName: string;
2350
+ exportName: string;
2351
+ }>;
2352
+ /** App depends on `@lunora/cloudflare-access` → emit `.access()` (wire the Cloudflare Access `resolveIdentity`, composed ahead of `@lunora/auth` when both are present). */
2353
+ hasAccess: boolean;
1069
2354
  /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
1070
2355
  hasAi: boolean;
1071
- /** App uses `@lunora/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
2356
+ /** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
1072
2357
  hasAnalytics: boolean;
1073
2358
  /** App depends on `@lunora/auth` → emit `.auth()` + the lazy build/migrate dance. */
1074
2359
  hasAuth: boolean;
@@ -1082,13 +2367,17 @@ interface EmitAppOptions {
1082
2367
  hasHyperdrive: boolean;
1083
2368
  /** Schema declares **Hyperdrive-backed** `.global({ backend: "hyperdrive" })` tables → emit `.hyperdriveGlobal()` (reactive Postgres/MySQL ctx-db over Hyperdrive). */
1084
2369
  hasHyperdriveGlobal: boolean;
1085
- /** App uses `@lunora/images` / `ctx.images` → emit `.images()`. */
2370
+ /** App uses `@lunora/bindings/images` / `ctx.images` → emit `.images()`. */
1086
2371
  hasImages: boolean;
1087
- /** App uses `@lunora/kv` / `ctx.kv` → emit `.kv()`. */
2372
+ /** App uses `@lunora/bindings/kv` / `ctx.kv` → emit `.kv()`. */
1088
2373
  hasKv: boolean;
2374
+ /** App declares `lunora/notify.ts` (`@lunora/notify`) → wire `options.notifySubscriptionStore` so the studio Notifications page can read registered devices. */
2375
+ hasNotify: boolean;
1089
2376
  /** App uses `@lunora/payment` / `ctx.payments` → emit `.payment()`. */
1090
2377
  hasPayments: boolean;
1091
- /** App uses `@lunora/r2sql` / `ctx.r2sql` → emit `.r2sql()`. */
2378
+ /** App declares push queues (`defineQueue`)wire `LUNORA_QUEUE_REGISTRY` into the worker's `queue()` consumer entry. */
2379
+ hasQueue: boolean;
2380
+ /** App uses `@lunora/bindings/r2sql` / `ctx.r2sql` → emit `.r2sql()`. */
1092
2381
  hasR2sql: boolean;
1093
2382
  /** App imports `@lunora/scheduler` / declares crons → emit `.scheduler()`. */
1094
2383
  hasScheduler: boolean;
@@ -1098,28 +2387,45 @@ interface EmitAppOptions {
1098
2387
  hasVectors: boolean;
1099
2388
  /** App declares Cloudflare Workflows (`defineWorkflow`) → wire `options.workflowsClient` so the studio's workflow-instance proxy can reach the CF REST API. */
1100
2389
  hasWorkflow: boolean;
2390
+ /** App uses `@lunora/x402/pay` / `ctx.x402` → emit `.x402()` (wire the agent-wallet pay rail). */
2391
+ hasX402: boolean;
2392
+ /** The single `defineIdentity(...)` contract in `lunora/identity.ts` (Plan 080) → import it as a VALUE and wire `options.identity`, so the runtime trust boundary validates every resolved identity before it becomes `ctx.auth`. `undefined` ⇒ no wiring, byte-identical output. */
2393
+ identity?: IdentityIR;
2394
+ /** Schema declares `.jurisdiction("…")` → pin every DO the worker reaches (shards, fan-out, scheduler, containers) to the Cloudflare data-residency jurisdiction. */
2395
+ jurisdiction?: JurisdictionIR;
1101
2396
  /** Project depends on the unscoped `lunorash` umbrella → import the runtime via `lunorash/runtime` instead of `@lunora/runtime`. */
1102
2397
  useUmbrella: boolean;
2398
+ /**
2399
+ * Voice-enabled agents (`defineAgent({ voice: … })`) → wire
2400
+ * `options.voiceAgents`, mapping each agent's export name to its `VOICE_*`
2401
+ * Durable Object namespace binding so the runtime exposes
2402
+ * `/_lunora/voice/&lt;exportName>`. Empty/absent ⇒ no wiring, byte-identical
2403
+ * output for voice-free (and agent-free) projects.
2404
+ */
2405
+ voiceAgents?: ReadonlyArray<{
2406
+ bindingName: string;
2407
+ exportName: string;
2408
+ }>;
1103
2409
  /** An OpenAPI spec is emitted (`openapi.ts`) → wire `openApiSpec` into the worker. */
1104
2410
  wantsOpenApi: boolean;
1105
2411
  /** An OpenRPC spec is emitted (`openrpc.ts`) → wire `openRpcSpec` into the worker. */
1106
2412
  wantsOpenRpc: boolean;
1107
2413
  }
1108
2414
  /**
1109
- * Emit `_generated/app.ts` — a fluent, feature-specialized worker-composition
1110
- * builder. Only the methods for capabilities THIS app uses are emitted, so the
1111
- * builder's type surface (IntelliSense) lists exactly what can be configured.
1112
- *
1113
- * Each capability declaration is fanned into BOTH runtime surfaces: the DO-side
1114
- * `createShardDO(...)` factory that backs `ctx.*`, and the worker-side
1115
- * `createWorker(...)` options that back the studio/admin endpoints — so storage
1116
- * / scheduler / global are declared once instead of twice. The builder is pure
1117
- * sugar over the public `createWorker` / `createShardDO`; both stay usable.
1118
- *
1119
- * Lives in generated code (not `@lunora/runtime`, which is dependency-free) so
1120
- * it can import the add-on packages the app installed (`@lunora/auth`,
1121
- * `@lunora/storage`, …) directly.
1122
- */
2415
+ * Emit `_generated/app.ts` — a fluent, feature-specialized worker-composition
2416
+ * builder. Only the methods for capabilities THIS app uses are emitted, so the
2417
+ * builder's type surface (IntelliSense) lists exactly what can be configured.
2418
+ *
2419
+ * Each capability declaration is fanned into BOTH runtime surfaces: the DO-side
2420
+ * `createShardDO(...)` factory that backs `ctx.*`, and the worker-side
2421
+ * `createWorker(...)` options that back the studio/admin endpoints — so storage
2422
+ * / scheduler / global are declared once instead of twice. The builder is pure
2423
+ * sugar over the public `createWorker` / `createShardDO`; both stay usable.
2424
+ *
2425
+ * Lives in generated code (not `@lunora/runtime`, which is dependency-free) so
2426
+ * it can import the add-on packages the app installed (`@lunora/auth`,
2427
+ * `@lunora/storage`, …) directly.
2428
+ */
1123
2429
  declare const emitApp: (options: EmitAppOptions) => string;
1124
2430
  /** Inputs the OpenAPI emitter needs from a codegen run. */
1125
2431
  interface OpenApiEmitInput {
@@ -1129,40 +2435,40 @@ interface OpenApiEmitInput {
1129
2435
  version?: string;
1130
2436
  }
1131
2437
  /**
1132
- * Emit an OpenAPI 3.1.0 document covering both Lunora function surfaces.
1133
- *
1134
- * `httpRouter()` typed REST routes become real `paths` keyed by their method +
1135
- * URL, with query/path parameters and JSON request bodies derived from their
1136
- * `v.*` validators, and a response schema from `.output()` when declared.
1137
- *
1138
- * RPC `query`/`mutation`/`action` functions become one operation each on
1139
- * `POST /_lunora/rpc` (disambiguated by a `#functionPath` path fragment), with a
1140
- * requestBody pinning `functionPath` + typed `args`. `internal`/`stream`
1141
- * functions are excluded (unreachable / not invocable on the external RPC path).
1142
- *
1143
- * Operations are grouped into `tags` by file namespace, and every operation
1144
- * references a reusable `LunoraError` error-response component enumerating the
1145
- * standard error codes. Borrows oRPC's per-procedure-operation + tag-grouping +
1146
- * internal-filtering structure; the JSON Schema dialect matches `@lunora/values`
1147
- * (Draft 2020-12). Returns the document as a plain object (the single source of
1148
- * truth `emitOpenApi` stringifies and `emitOpenApiModule` inlines, so the
1149
- * `.json` and `.ts` artifacts can never drift).
1150
- */
2438
+ * Emit an OpenAPI 3.1.0 document covering both Lunora function surfaces.
2439
+ *
2440
+ * `httpRouter()` typed REST routes become real `paths` keyed by their method +
2441
+ * URL, with query/path parameters and JSON request bodies derived from their
2442
+ * `v.*` validators, and a response schema from `.output()` when declared.
2443
+ *
2444
+ * RPC `query`/`mutation`/`action` functions become one operation each on
2445
+ * `POST /_lunora/rpc` (disambiguated by a `#functionPath` path fragment), with a
2446
+ * requestBody pinning `functionPath` + typed `args`. `internal`/`stream`
2447
+ * functions are excluded (unreachable / not invocable on the external RPC path).
2448
+ *
2449
+ * Operations are grouped into `tags` by file namespace, and every operation
2450
+ * references a reusable `LunoraError` error-response component enumerating the
2451
+ * standard error codes. Borrows oRPC's per-procedure-operation + tag-grouping +
2452
+ * internal-filtering structure; the JSON Schema dialect matches `@lunora/values`
2453
+ * (Draft 2020-12). Returns the document as a plain object (the single source of
2454
+ * truth `emitOpenApi` stringifies and `emitOpenApiModule` inlines, so the
2455
+ * `.json` and `.ts` artifacts can never drift).
2456
+ */
1151
2457
  declare const buildOpenApiDocument: (input: OpenApiEmitInput) => Record<string, unknown>;
1152
2458
  /**
1153
- * Emit the OpenAPI 3.1 document as a pretty-printed JSON string
1154
- * (`_generated/openapi.json`) — the portable artifact for external tooling.
1155
- */
2459
+ * Emit the OpenAPI 3.1 document as a pretty-printed JSON string
2460
+ * (`_generated/openapi.json`) — the portable artifact for external tooling.
2461
+ */
1156
2462
  declare const emitOpenApi: (input: OpenApiEmitInput) => string;
1157
2463
  /**
1158
- * Emit the OpenAPI document as an importable TS module
1159
- * (`_generated/openapi.ts`) the worker entry imports and passes to
1160
- * `createWorker({ openApiSpec })`. The document object literal is inlined
1161
- * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
1162
- * `.json` are byte-identical content and regenerate together — closing the gap
1163
- * where a Worker cannot read the JSON file at runtime. `document_` is the object
1164
- * returned by {@link buildOpenApiDocument} (reused, never recomputed).
1165
- */
2464
+ * Emit the OpenAPI document as an importable TS module
2465
+ * (`_generated/openapi.ts`) the worker entry imports and passes to
2466
+ * `createWorker({ openApiSpec })`. The document object literal is inlined
2467
+ * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
2468
+ * `.json` are byte-identical content and regenerate together — closing the gap
2469
+ * where a Worker cannot read the JSON file at runtime. `document_` is the object
2470
+ * returned by {@link buildOpenApiDocument} (reused, never recomputed).
2471
+ */
1166
2472
  declare const emitOpenApiModule: (document_: Record<string, unknown>) => string;
1167
2473
  /** The OpenRPC dialect version this emitter targets. */
1168
2474
  declare const OPENRPC_VERSION = "1.3.2";
@@ -1173,37 +2479,37 @@ interface OpenRpcEmitInput {
1173
2479
  version?: string;
1174
2480
  }
1175
2481
  /**
1176
- * Emit an OpenRPC 1.x document describing Lunora's JSON-RPC surface.
1177
- *
1178
- * Only the RPC `query`/`mutation`/`action` functions become `methods` — one per
1179
- * function, `name` = `file:fn`. `internal` (off the external RPC path) and
1180
- * `stream` (not invocable over the RPC envelope) are excluded, the same filter
1181
- * the OpenAPI emitter applies. Each method's single `args` param is typed from
1182
- * the function's `v.*` validators (`argsObjectSchema`); `result` is the
1183
- * `.output()` schema when declared, else a best-effort inferred schema. The
1184
- * standard `LunoraError` codes ride along under each method's `errors`.
1185
- *
1186
- * `httpRouter()` typed REST routes are deliberately omitted — OpenRPC is
1187
- * RPC-only and cannot represent REST paths; the OpenAPI document is the spec
1188
- * that covers the REST surface. Methods are sorted by name for stable output.
1189
- * Returns the document as a plain object (the single source of truth
1190
- * `emitOpenRpc` stringifies and `emitOpenRpcModule` inlines, so the `.json` and
1191
- * `.ts` artifacts can never drift).
1192
- */
2482
+ * Emit an OpenRPC 1.x document describing Lunora's JSON-RPC surface.
2483
+ *
2484
+ * Only the RPC `query`/`mutation`/`action` functions become `methods` — one per
2485
+ * function, `name` = `file:fn`. `internal` (off the external RPC path) and
2486
+ * `stream` (not invocable over the RPC envelope) are excluded, the same filter
2487
+ * the OpenAPI emitter applies. Each method's single `args` param is typed from
2488
+ * the function's `v.*` validators (`argsObjectSchema`); `result` is the
2489
+ * `.output()` schema when declared, else a best-effort inferred schema. The
2490
+ * standard `LunoraError` codes ride along under each method's `errors`.
2491
+ *
2492
+ * `httpRouter()` typed REST routes are deliberately omitted — OpenRPC is
2493
+ * RPC-only and cannot represent REST paths; the OpenAPI document is the spec
2494
+ * that covers the REST surface. Methods are sorted by name for stable output.
2495
+ * Returns the document as a plain object (the single source of truth
2496
+ * `emitOpenRpc` stringifies and `emitOpenRpcModule` inlines, so the `.json` and
2497
+ * `.ts` artifacts can never drift).
2498
+ */
1193
2499
  declare const buildOpenRpcDocument: (input: OpenRpcEmitInput) => Record<string, unknown>;
1194
2500
  /**
1195
- * Emit the OpenRPC 1.x document as a pretty-printed JSON string
1196
- * (`_generated/openrpc.json`) — the portable artifact for external tooling.
1197
- */
2501
+ * Emit the OpenRPC 1.x document as a pretty-printed JSON string
2502
+ * (`_generated/openrpc.json`) — the portable artifact for external tooling.
2503
+ */
1198
2504
  declare const emitOpenRpc: (input: OpenRpcEmitInput) => string;
1199
2505
  /**
1200
- * Emit the OpenRPC document as an importable TS module
1201
- * (`_generated/openrpc.ts`) the worker entry imports and passes to
1202
- * `createWorker({ openRpcSpec })`. The document object literal is inlined
1203
- * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
1204
- * `.json` are byte-identical content and regenerate together. `document_` is
1205
- * the object returned by {@link buildOpenRpcDocument} (reused, never recomputed).
1206
- */
2506
+ * Emit the OpenRPC document as an importable TS module
2507
+ * (`_generated/openrpc.ts`) the worker entry imports and passes to
2508
+ * `createWorker({ openRpcSpec })`. The document object literal is inlined
2509
+ * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
2510
+ * `.json` are byte-identical content and regenerate together. `document_` is
2511
+ * the object returned by {@link buildOpenRpcDocument} (reused, never recomputed).
2512
+ */
1207
2513
  declare const emitOpenRpcModule: (document_: Record<string, unknown>) => string;
1208
2514
  /** Current snapshot format version. Bumped if the structural shape below changes. */
1209
2515
  declare const SCHEMA_SNAPSHOT_VERSION: 1;
@@ -1234,14 +2540,27 @@ interface TableSnapshot {
1234
2540
  /** Relation accessor name → {@link RelationSnapshot}. */
1235
2541
  relations: Record<string, RelationSnapshot>;
1236
2542
  /**
1237
- * `"root"` (default single-DO), `"global"` (D1-replicated), or
1238
- * `"shardBy:&lt;field>"` (partitioned). Encoded as a string so the snapshot
1239
- * stays a plain JSON-stable value.
1240
- */
2543
+ * `"root"` (default single-DO), `"global"` (D1-replicated), or
2544
+ * `"shardBy:&lt;field>"` (partitioned). Encoded as a string so the snapshot
2545
+ * stays a plain JSON-stable value.
2546
+ */
1241
2547
  shardMode: string;
1242
2548
  }
1243
2549
  /** The committed baseline — a deterministic structural view of the whole schema. */
1244
2550
  interface SchemaSnapshot {
2551
+ /**
2552
+ * Cloudflare DO data-residency jurisdiction declared via `.jurisdiction("…")`,
2553
+ * or absent. Tracked because changing it strands all existing Durable Object
2554
+ * data (a DO name maps to a different ID per jurisdiction). Optional, so old
2555
+ * baselines written before this field parse cleanly (absent ⇒ undefined).
2556
+ *
2557
+ * Typed as a plain `string` (not the authoring union) on purpose: this is
2558
+ * STORED data that a newer Lunora may have written with a jurisdiction this
2559
+ * version doesn't yet know. Preserving the raw value keeps the breaking
2560
+ * `changedJurisdiction` diff correct under a downgrade — coercing an unknown
2561
+ * value to `undefined` would fail OPEN and hide the most destructive change.
2562
+ */
2563
+ jurisdiction?: string;
1245
2564
  /** Sorted list of every declared `defineMigration` id at capture time. */
1246
2565
  migrationIds: ReadonlyArray<string>;
1247
2566
  /** Table name → {@link TableSnapshot}, keys sorted for stable serialization. */
@@ -1255,7 +2574,7 @@ interface DriftChange {
1255
2574
  /** Human-readable, actionable description (used in the gate message). */
1256
2575
  summary: string;
1257
2576
  /** A machine-readable change discriminator. */
1258
- type: "addedIndex" | "addedOptionalField" | "addedRelation" | "addedRequiredField" | "addedTable" | "changedFieldKind" | "changedIndex" | "changedShardMode" | "fieldOptionalToRequired" | "fieldRequiredToOptional" | "removedField" | "removedIndex" | "removedRelation" | "removedTable";
2577
+ type: "addedIndex" | "addedOptionalField" | "addedRelation" | "addedRequiredField" | "addedTable" | "changedJurisdiction" | "changedFieldKind" | "changedIndex" | "changedShardMode" | "fieldOptionalToRequired" | "fieldRequiredToOptional" | "removedField" | "removedIndex" | "removedRelation" | "removedTable";
1259
2578
  }
1260
2579
  /** The result of diffing two snapshots: every classified change. */
1261
2580
  interface SchemaDrift {
@@ -1263,36 +2582,41 @@ interface SchemaDrift {
1263
2582
  changes: ReadonlyArray<DriftChange>;
1264
2583
  }
1265
2584
  /**
1266
- * Build a {@link SchemaSnapshot} from a parsed {@link SchemaIR} and the set of
1267
- * declared migration ids. Tables and migration ids are sorted so the emitted
1268
- * JSON is byte-stable across runs (no spurious diffs / churn).
1269
- */
2585
+ * Build a {@link SchemaSnapshot} from a parsed {@link SchemaIR} and the set of
2586
+ * declared migration ids. Tables and migration ids are sorted so the emitted
2587
+ * JSON is byte-stable across runs AND across machines (no spurious diffs /
2588
+ * churn) — see {@link sortKeys} for why that ordering must not be locale-aware.
2589
+ *
2590
+ * Field / index / relation keys are deliberately NOT sorted: they are emitted in
2591
+ * declaration order from the schema source, which is already deterministic for a
2592
+ * given source file and keeps the snapshot readable next to the schema it mirrors.
2593
+ */
1270
2594
  declare const buildSchemaSnapshot: (schema: SchemaIR, migrationIds: ReadonlyArray<string>) => SchemaSnapshot;
1271
2595
  /** Serialize a snapshot to the exact bytes written to `lunora/.lunora-schema.json` (trailing newline). */
1272
2596
  declare const serializeSchemaSnapshot: (snapshot: SchemaSnapshot) => string;
1273
2597
  /**
1274
- * Thrown by {@link parseSchemaSnapshot} when the baseline file exists but is
1275
- * malformed (bad JSON / wrong version / invalid table shape). Lets the CLI gate
1276
- * treat a corrupt baseline as a hard error rather than silently degrading to a
1277
- * "first capture" that would mask drift and then overwrite the bad file.
1278
- */
1279
- declare class SchemaSnapshotParseError extends Error {
1280
- override readonly name = "SchemaSnapshotParseError";
1281
- }
1282
- /**
1283
- * Parse a committed snapshot file. Returns `undefined` ONLY when the content is
1284
- * absent/empty; throws {@link SchemaSnapshotParseError} when content is present
1285
- * but malformed (bad JSON, wrong version, or structurally-invalid tables) so the
1286
- * caller can distinguish "no baseline yet" (a legitimate first capture) from "a
1287
- * corrupt baseline" (which must not be silently treated as a first capture).
1288
- */
2598
+ * Thrown by {@link parseSchemaSnapshot} when the baseline file exists but is
2599
+ * malformed (bad JSON / wrong version / invalid table shape). Lets the CLI gate
2600
+ * treat a corrupt baseline as a hard error rather than silently degrading to a
2601
+ * "first capture" that would mask drift and then overwrite the bad file.
2602
+ */
2603
+ declare class SchemaSnapshotParseError extends LunoraError {
2604
+ constructor(message: string);
2605
+ }
2606
+ /**
2607
+ * Parse a committed snapshot file. Returns `undefined` ONLY when the content is
2608
+ * absent/empty; throws {@link SchemaSnapshotParseError} when content is present
2609
+ * but malformed (bad JSON, wrong version, or structurally-invalid tables) so the
2610
+ * caller can distinguish "no baseline yet" (a legitimate first capture) from "a
2611
+ * corrupt baseline" (which must not be silently treated as a first capture).
2612
+ */
1289
2613
  declare const parseSchemaSnapshot: (content: string | undefined) => SchemaSnapshot | undefined;
1290
2614
  /**
1291
- * Diff the current snapshot against a committed baseline and classify every
1292
- * structural change. Pure — no I/O. When `baseline` is `undefined` (no committed
1293
- * snapshot yet) there is no drift to report: every table is treated as a fresh
1294
- * additive `addedTable`, so a first deploy is never blocked.
1295
- */
2615
+ * Diff the current snapshot against a committed baseline and classify every
2616
+ * structural change. Pure — no I/O. When `baseline` is `undefined` (no committed
2617
+ * snapshot yet) there is no drift to report: every table is treated as a fresh
2618
+ * additive `addedTable`, so a first deploy is never blocked.
2619
+ */
1296
2620
  declare const diffSchemaSnapshots: (baseline: SchemaSnapshot | undefined, current: SchemaSnapshot) => SchemaDrift;
1297
2621
  /** The decision the pre-deploy gate returns. */
1298
2622
  interface SchemaDriftDecision {
@@ -1303,212 +2627,250 @@ interface SchemaDriftDecision {
1303
2627
  /** Migration ids declared now but absent from the baseline — proof a migration was added. */
1304
2628
  newMigrationIds: ReadonlyArray<string>;
1305
2629
  /**
1306
- * A multi-line, actionable explanation. Always present; empty string when
1307
- * there is no drift at all. Mirrors the D1-placeholder guard's message style.
1308
- */
2630
+ * A multi-line, actionable explanation. Always present; empty string when
2631
+ * there is no drift at all. Mirrors the D1-placeholder guard's message style.
2632
+ */
1309
2633
  reason: string;
1310
2634
  }
1311
2635
  /**
1312
- * Decide whether breaking schema drift should block a deploy.
1313
- *
1314
- * Blocks only when the baseline exists (a first-ever capture is never blocking),
1315
- * there is at least one `breaking` change, no NEW migration id was added since
1316
- * the baseline, and the `allowDrift` override is not set. Safe-only drift (or
1317
- * breaking drift accompanied by a new migration id) passes.
1318
- */
2636
+ * Decide whether breaking schema drift should block a deploy.
2637
+ *
2638
+ * Blocks only when the baseline exists (a first-ever capture is never blocking),
2639
+ * there is at least one `breaking` change, no NEW migration id was added since
2640
+ * the baseline, and the `allowDrift` override is not set. Safe-only drift (or
2641
+ * breaking drift accompanied by a new migration id) passes.
2642
+ */
1319
2643
  declare const evaluateSchemaDrift: (options: {
1320
2644
  allowDrift?: boolean;
1321
2645
  baseline: SchemaSnapshot | undefined;
1322
2646
  current: SchemaSnapshot;
1323
2647
  }) => SchemaDriftDecision;
1324
2648
  /**
1325
- * Committed, tracked baseline file holding the blessed structural schema
1326
- * snapshot the pre-deploy drift gate diffs against. Lives in `lunora/` (NOT the
1327
- * gitignored `_generated/`) so it is committed alongside `schema.ts`. Leading
1328
- * dot keeps it tucked away next to the schema it describes.
1329
- */
2649
+ * Committed, tracked baseline file holding the blessed structural schema
2650
+ * snapshot the pre-deploy drift gate diffs against. Lives in `lunora/` (NOT the
2651
+ * gitignored `_generated/`) so it is committed alongside `schema.ts`. Leading
2652
+ * dot keeps it tucked away next to the schema it describes.
2653
+ */
1330
2654
  declare const SCHEMA_SNAPSHOT_FILENAME = ".lunora-schema.json";
1331
2655
  /**
1332
- * Construct the ts-morph `Project` codegen discovers over. Prefers the user's
1333
- * `tsconfig.json` (when one is found walking up from `lunoraDirectory`) so
1334
- * cross-file type resolution and path aliases work; falls back to an isolated
1335
- * project otherwise. This is the exact construction {@link runCodegen} uses
1336
- * when no `project` is injected — exported so a long-lived caller (the Vite
1337
- * dev-loop) can build one once and reuse it across runs via
1338
- * {@link refreshCodegenProject} instead of re-parsing the user's whole TS
1339
- * program on every save.
1340
- */
2656
+ * Construct the ts-morph `Project` codegen discovers over. Prefers the user's
2657
+ * `tsconfig.json` (when one is found walking up from `lunoraDirectory`) so
2658
+ * cross-file type resolution and path aliases work; falls back to an isolated
2659
+ * project otherwise. This is the exact construction {@link runCodegen} uses
2660
+ * when no `project` is injected — exported so a long-lived caller (the Vite
2661
+ * dev-loop) can build one once and reuse it across runs via
2662
+ * {@link refreshCodegenProject} instead of re-parsing the user's whole TS
2663
+ * program on every save.
2664
+ */
1341
2665
  declare const createCodegenProject: (lunoraDirectory: string) => Project;
1342
2666
  /**
1343
- * Synchronise a reused {@link createCodegenProject} Project with the current
1344
- * on-disk state of `lunoraDirectory`, so the next {@link runCodegen} sees the
1345
- * same files a freshly-constructed Project would — without re-parsing the whole
1346
- * TS program. Adds any on-disk source file the Project doesn't yet have, and
1347
- * `refreshFromFileSystemSync()`es the ones it does (picking up edits); then
1348
- * removes Project source files under `lunoraDirectory` that no longer exist on
1349
- * disk (the classic stale-deleted-file cache bug).
1350
- *
1351
- * Files outside `lunoraDirectory` (e.g. those pulled in by the user's tsconfig)
1352
- * are left untouched — they back type resolution and rarely change in the
1353
- * dev-loop; a tsconfig change invalidates the whole cached Project upstream.
1354
- */
2667
+ * Synchronise a reused {@link createCodegenProject} Project with the current
2668
+ * on-disk state of `lunoraDirectory`, so the next {@link runCodegen} sees the
2669
+ * same files a freshly-constructed Project would — without re-parsing the whole
2670
+ * TS program. Adds any on-disk source file the Project doesn't yet have, and
2671
+ * `refreshFromFileSystemSync()`es the ones it does (picking up edits); then
2672
+ * removes Project source files under `lunoraDirectory` that no longer exist on
2673
+ * disk (the classic stale-deleted-file cache bug).
2674
+ *
2675
+ * Files outside `lunoraDirectory` (e.g. those pulled in by the user's tsconfig)
2676
+ * are left untouched — they back type resolution and rarely change in the
2677
+ * dev-loop; a tsconfig change invalidates the whole cached Project upstream.
2678
+ */
1355
2679
  declare const refreshCodegenProject: (project: Project, lunoraDirectory: string) => void;
1356
2680
  /**
1357
- * Top-level codegen entry. Parses `&lt;projectRoot>/lunora/schema.ts` and every
1358
- * function file under `&lt;projectRoot>/lunora/`, then writes
1359
- * `_generated/{api,server,dataModel}.ts` next to them.
1360
- */
2681
+ * Top-level codegen entry. Parses `&lt;projectRoot>/lunora/schema.ts` and every
2682
+ * function file under `&lt;projectRoot>/lunora/`, then writes
2683
+ * `_generated/{api,server,dataModel}.ts` next to them.
2684
+ *
2685
+ * When `LUNORA_CODEGEN_TIMING` is set (truthy), a single diagnostic summary
2686
+ * line is written to stderr with the total wall time and the discovery-vs-emit
2687
+ * split — opt-in instrumentation that is otherwise zero-cost and side-effect-free
2688
+ * on the returned {@link CodegenResult}.
2689
+ */
1361
2690
  declare const runCodegen: (options: CodegenOptions) => CodegenResult;
1362
2691
  interface CodegenOptions {
1363
2692
  /**
1364
- * Which machine-readable API spec(s) to emit into `_generated/`.
1365
- *
1366
- * `"openapi"` (the default) writes only `openapi.json` (OpenAPI 3.1; covers
1367
- * both the RPC functions and `httpRouter()` REST routes). `"openrpc"` writes
1368
- * only `openrpc.json` (OpenRPC 1.x; the RPC functions only — OpenRPC cannot
1369
- * represent REST routes). `"both"` writes both files; `"none"` writes neither.
1370
- *
1371
- * Regardless of the choice, `CodegenResult.generated.openApi` and `.openRpc`
1372
- * always carry the rendered string (computation is cheap and pure); only the
1373
- * on-disk write is gated by this option.
1374
- */
2693
+ * Which machine-readable API spec(s) to emit into `_generated/`.
2694
+ *
2695
+ * `"openapi"` (the default) writes only `openapi.json` (OpenAPI 3.1; covers
2696
+ * both the RPC functions and `httpRouter()` REST routes). `"openrpc"` writes
2697
+ * only `openrpc.json` (OpenRPC 1.x; the RPC functions only — OpenRPC cannot
2698
+ * represent REST routes). `"both"` writes both files; `"none"` writes neither.
2699
+ *
2700
+ * Regardless of the choice, `CodegenResult.generated.openApi` and `.openRpc`
2701
+ * always carry the rendered string (computation is cheap and pure); only the
2702
+ * on-disk write is gated by this option.
2703
+ */
1375
2704
  apiSpec?: "both" | "none" | "openapi" | "openrpc";
1376
2705
  /**
1377
- * When true, run discovery + emit (so any schema/function parse error
1378
- * surfaces) but skip writing files to `_generated/`. The returned
1379
- * `outputDirectory` is still the path that *would* have been written.
1380
- */
2706
+ * When true, run discovery + emit (so any schema/function parse error
2707
+ * surfaces) but skip writing files to `_generated/`. The returned
2708
+ * `outputDirectory` is still the path that *would* have been written.
2709
+ */
1381
2710
  dryRun?: boolean;
1382
2711
  /**
1383
- * Run the static schema advisor (unindexed FKs, …) during codegen.
1384
- * Defaults to `true`. When `false`, `CodegenResult.advisories` is empty.
1385
- * Computed regardless of `dryRun`; codegen never prints them — see
1386
- * {@link CodegenResult.advisories}.
1387
- */
2712
+ * Run the static schema advisor (unindexed FKs, …) during codegen.
2713
+ * Defaults to `true`. When `false`, `CodegenResult.advisories` is empty.
2714
+ * Computed regardless of `dryRun`; codegen never prints them — see
2715
+ * {@link CodegenResult.advisories}.
2716
+ */
1388
2717
  lint?: boolean;
1389
2718
  /** Override the lunora subdirectory name. Defaults to `"lunora"`. */
1390
2719
  lunoraDirectory?: string;
1391
2720
  /**
1392
- * Reuse a previously-constructed ts-morph {@link Project} instead of building
1393
- * a fresh one each run. The caller owns refreshing its source files from disk
1394
- * (see {@link refreshCodegenProject}) — codegen does not re-read changed files
1395
- * off an injected Project. Built via {@link createCodegenProject} when absent.
1396
- * Used by the Vite dev-loop to avoid re-parsing the whole TS program on every
1397
- * save; omit it (CLI one-shot path) to get the default fresh-Project behaviour.
1398
- */
2721
+ * Reuse a previously-constructed ts-morph {@link Project} instead of building
2722
+ * a fresh one each run. The caller owns refreshing its source files from disk
2723
+ * (see {@link refreshCodegenProject}) — codegen does not re-read changed files
2724
+ * off an injected Project. Built via {@link createCodegenProject} when absent.
2725
+ * Used by the Vite dev-loop to avoid re-parsing the whole TS program on every
2726
+ * save; omit it (CLI one-shot path) to get the default fresh-Project behaviour.
2727
+ */
1399
2728
  project?: Project;
1400
2729
  /** Project root containing the `lunora/` directory. */
1401
2730
  projectRoot: string;
1402
2731
  /**
1403
- * Re-bless the committed schema-drift baseline (`lunora/.lunora-schema.json`)
1404
- * with the current structural snapshot. The baseline is ALWAYS written on
1405
- * first capture (when the file is absent); set this to overwrite an existing
1406
- * one — e.g. after the developer has added the data migration that justifies
1407
- * a breaking change. Ignored when `dryRun` is true.
1408
- */
2732
+ * Re-bless the committed schema-drift baseline (`lunora/.lunora-schema.json`)
2733
+ * with the current structural snapshot. The baseline is ALWAYS written on
2734
+ * first capture (when the file is absent); set this to overwrite an existing
2735
+ * one — e.g. after the developer has added the data migration that justifies
2736
+ * a breaking change. Ignored when `dryRun` is true.
2737
+ */
1409
2738
  updateSchemaBaseline?: boolean;
2739
+ /**
2740
+ * Committed `wrangler.jsonc` `vars` entries that hold plaintext secrets — the
2741
+ * `plaintext_secret_in_wrangler_vars` lint input. Produced by `@lunora/config`
2742
+ * (which reads `wrangler.jsonc`) and threaded through by the CLI / Vite plugin;
2743
+ * codegen only forwards it to the advisor. Absent when no wrangler config is
2744
+ * present or the caller doesn't scan it.
2745
+ */
2746
+ wranglerVariables?: ReadonlyArray<WranglerVariableIR>;
1410
2747
  }
1411
2748
  interface CodegenResult {
1412
2749
  /**
1413
- * Static schema advisor findings (e.g. unindexed foreign keys) produced
1414
- * this run. Empty when `lint` is `false` or the schema is clean. Codegen
1415
- * does not print these itself — each caller presents them through its own
1416
- * channel (the CLI logger, the vite overlay, the studio Advisors table).
1417
- * `formatAdvisories` is exported for a plain multi-line rendering.
1418
- */
2750
+ * Static schema advisor findings (e.g. unindexed foreign keys) produced
2751
+ * this run. Empty when `lint` is `false` or the schema is clean. Codegen
2752
+ * does not print these itself — each caller presents them through its own
2753
+ * channel (the CLI logger, the vite overlay, the studio Advisors table).
2754
+ * `formatAdvisories` is exported for a plain multi-line rendering.
2755
+ */
1419
2756
  advisories: ReadonlyArray<Finding>;
1420
2757
  /**
1421
- * Containers discovered from `defineContainer` exports in
1422
- * `lunora/containers.ts` — the list the config layer reconciles into
1423
- * wrangler's `containers[]`, `CONTAINER_*` Durable Object bindings, and
1424
- * migration classes. Empty when the project declares no containers.
1425
- */
2758
+ * Agents discovered from `defineAgent` exports in `lunora/agents.ts` — the
2759
+ * list the config layer reconciles into wrangler's `workflows[]` array (an
2760
+ * agent compiles onto a Cloudflare Workflow). Agents are NOT Durable Objects,
2761
+ * so this adds no binding or migration. Empty when the project declares none.
2762
+ */
2763
+ agents: ReadonlyArray<AgentIR>;
2764
+ /**
2765
+ * Containers discovered from `defineContainer` exports in
2766
+ * `lunora/containers.ts` — the list the config layer reconciles into
2767
+ * wrangler's `containers[]`, `CONTAINER_*` Durable Object bindings, and
2768
+ * migration classes. Empty when the project declares no containers.
2769
+ */
1426
2770
  containers: ReadonlyArray<ContainerIR>;
1427
2771
  /**
1428
- * Deduplicated cron schedules discovered from `cronJobs()` definitions —
1429
- * the array the vite plugin reconciles into `wrangler.jsonc`'s
1430
- * `triggers.crons`. Empty when the project declares no crons.
1431
- */
2772
+ * Deduplicated cron schedules discovered from `cronJobs()` definitions —
2773
+ * the array the vite plugin reconciles into `wrangler.jsonc`'s
2774
+ * `triggers.crons`. Empty when the project declares no crons.
2775
+ */
1432
2776
  cronTriggers: ReadonlyArray<string>;
1433
2777
  generated: {
1434
- api: string; /** Fluent worker-composition builder (`_generated/app.ts`) `defineApp()`. Always written. */
2778
+ /** WorkflowEntrypoint classes for declared agents (`_generated/agents.ts`); `""` (and not written) when no agents are declared. */
2779
+ agents: string;
2780
+ api: string;
2781
+ /** Fluent worker-composition builder (`_generated/app.ts`) — `defineApp()`. Always written. */
1435
2782
  app: string;
2783
+ /** Partial-replication collection factories (`_generated/collections.ts`); `""` (and not written) unless the project declares shapes and installs `@lunora/db`. */
2784
+ collections: string;
1436
2785
  /** Container DO classes (`_generated/containers.ts`); `""` (and not written) when no containers are declared. */
1437
2786
  containers: string;
1438
2787
  crons: string;
1439
2788
  dataModel: string;
1440
2789
  drizzleGlobal: string;
1441
2790
  drizzleShard: string;
1442
- functions: string; /** OpenAPI 3.1.0 document (`_generated/openapi.json`), pretty-printed JSON. */
2791
+ functions: string;
2792
+ /** OpenAPI 3.1.0 document (`_generated/openapi.json`), pretty-printed JSON. */
1443
2793
  openApi: string;
1444
2794
  /**
1445
- * OpenAPI document as an importable TS module (`_generated/openapi.ts`) —
1446
- * `export const openApiSpec`, the worker imports it for
1447
- * `createWorker({ openApiSpec })`. Same document as `openApi`. Written
1448
- * alongside `openapi.json` whenever `apiSpec` includes `openapi`.
1449
- */
2795
+ * OpenAPI document as an importable TS module (`_generated/openapi.ts`) —
2796
+ * `export const openApiSpec`, the worker imports it for
2797
+ * `createWorker({ openApiSpec })`. Same document as `openApi`. Written
2798
+ * alongside `openapi.json` whenever `apiSpec` includes `openapi`.
2799
+ */
1450
2800
  openApiModule: string;
1451
2801
  /** OpenRPC 1.x document (`_generated/openrpc.json`), pretty-printed JSON. Always computed; written only when `apiSpec` includes `openrpc`. */
1452
2802
  openRpc: string;
1453
2803
  /**
1454
- * OpenRPC document as an importable TS module (`_generated/openrpc.ts`) —
1455
- * `export const openRpcSpec`, for `createWorker({ openRpcSpec })`. Same
1456
- * document as `openRpc`. Written alongside `openrpc.json` whenever
1457
- * `apiSpec` includes `openrpc`.
1458
- */
2804
+ * OpenRPC document as an importable TS module (`_generated/openrpc.ts`) —
2805
+ * `export const openRpcSpec`, for `createWorker({ openRpcSpec })`. Same
2806
+ * document as `openRpc`. Written alongside `openrpc.json` whenever
2807
+ * `apiSpec` includes `openrpc`.
2808
+ */
1459
2809
  openRpcModule: string;
2810
+ /** Push-consumer queue registry (`_generated/queues.ts`); `""` (and not written) when no push queues are declared. */
2811
+ queues: string;
1460
2812
  /** Project-bound seed client (`_generated/seed.ts`); `""` (and not written) when `@lunora/seed` is not a declared dependency. */
1461
2813
  seed: string;
1462
2814
  server: string;
1463
- shard: string; /** Static vector-index registry (`_generated/vectors.ts`) — `LUNORA_VECTOR_INDEXES`. Empty array body when the schema declares none. */
2815
+ shard: string;
2816
+ /** Static vector-index registry (`_generated/vectors.ts`) — `LUNORA_VECTOR_INDEXES`. Empty array body when the schema declares none. */
1464
2817
  vectors: string;
1465
2818
  /** WorkflowEntrypoint classes (`_generated/workflows.ts`); `""` (and not written) when no workflows are declared. */
1466
2819
  workflows: string;
1467
2820
  };
1468
2821
  outputDirectory: string;
1469
2822
  /**
1470
- * The CURRENT structural schema snapshot computed this run (tables + field
1471
- * kinds/optionality + indexes/relations/shard mode + declared migration ids).
1472
- * The pre-deploy drift gate diffs this against the committed baseline read
1473
- * from {@link CodegenResult.schemaSnapshotPath}. Always present, even on a
1474
- * `dryRun`.
1475
- */
2823
+ * Queues discovered from `defineQueue` exports in `lunora/queues.ts` the
2824
+ * list the config layer reconciles into wrangler's `queues.producers[]` /
2825
+ * `queues.consumers[]`. Queues are NOT Durable Objects, so this adds no
2826
+ * binding or migration. Empty when the project declares no queues.
2827
+ */
2828
+ queues: ReadonlyArray<QueueIR>;
2829
+ /**
2830
+ * The CURRENT structural schema snapshot computed this run (tables + field
2831
+ * kinds/optionality + indexes/relations/shard mode + declared migration ids).
2832
+ * The pre-deploy drift gate diffs this against the committed baseline read
2833
+ * from {@link CodegenResult.schemaSnapshotPath}. Always present, even on a
2834
+ * `dryRun`.
2835
+ */
1476
2836
  schemaSnapshot: SchemaSnapshot;
1477
2837
  /** Absolute path of the committed baseline file (`lunora/.lunora-schema.json`). */
1478
2838
  schemaSnapshotPath: string;
1479
2839
  /**
1480
- * Workflows discovered from `defineWorkflow` exports in
1481
- * `lunora/workflows.ts` — the list the config layer reconciles into
1482
- * wrangler's `workflows[]` array. Workflows are NOT Durable Objects, so this
1483
- * adds no binding or migration. Empty when the project declares no workflows.
1484
- */
2840
+ * Workflows discovered from `defineWorkflow` exports in
2841
+ * `lunora/workflows.ts` — the list the config layer reconciles into
2842
+ * wrangler's `workflows[]` array. Workflows are NOT Durable Objects, so this
2843
+ * adds no binding or migration. Empty when the project declares no workflows.
2844
+ */
1485
2845
  workflows: ReadonlyArray<WorkflowIR>;
1486
2846
  }
1487
2847
  /**
1488
- * Convert a {@link SchemaIR} into a synthetic runtime {@link Schema} carrying just
1489
- * the `tables[name].shape` surface `@lunora/seed` introspects. System columns
1490
- * (`_id`, `_creationTime`) are absent from the IR shape, exactly as the seed
1491
- * engine expects (it assigns `_id` itself).
1492
- */
2848
+ * Convert a {@link SchemaIR} into a synthetic runtime {@link Schema} carrying just
2849
+ * the `tables[name].shape` surface `@lunora/seed` introspects. System columns
2850
+ * (`_id`, `_creationTime`) are absent from the IR shape, exactly as the seed
2851
+ * engine expects (it assigns `_id` itself).
2852
+ */
1493
2853
  declare const schemaFromIr: (ir: SchemaIR) => Schema;
1494
2854
  /**
1495
- * Convert a codegen {@link ValidatorIR} into a JSON Schema node. A thin wrapper
1496
- * over the shared {@link jsonSchemaFromNode} core (from `@lunora/values`) with the
1497
- * IR-backed {@link irReader}, so the kind→schema mapping is the *same* algorithm
1498
- * `@lunora/values`' `toJsonSchema` runs — codegen never instantiates the runtime
1499
- * `v.*` objects, it only holds the reflected IR. Shared by the OpenAPI and
1500
- * OpenRPC emitters so both surfaces speak one JSON Schema dialect.
1501
- */
2855
+ * Convert a codegen {@link ValidatorIR} into a JSON Schema node. A thin wrapper
2856
+ * over the shared {@link jsonSchemaFromNode} core (from `@lunora/values`) with the
2857
+ * IR-backed {@link irReader}, so the kind→schema mapping is the *same* algorithm
2858
+ * `@lunora/values`' `toJsonSchema` runs — codegen never instantiates the runtime
2859
+ * `v.*` objects, it only holds the reflected IR. Shared by the OpenAPI and
2860
+ * OpenRPC emitters so both surfaces speak one JSON Schema dialect.
2861
+ */
1502
2862
  declare const validatorIrToJsonSchema: (validator: ValidatorIR) => JsonSchema;
1503
- /** Build `{ type: "object", properties, required }` from an IR shape (mirrors `@lunora/values`' object mapping). */
1504
-
1505
- /**
1506
- * The machine-readable `LunoraError` codes Lunora emits on the RPC + REST
1507
- * surfaces, enumerated from `@lunora/server`'s `CODE_STATUS` map plus the
1508
- * runtime/DO dispatch codes (`FUNCTION_NOT_FOUND`, `PAYLOAD_TOO_LARGE`,
1509
- * `METHOD_NOT_ALLOWED`, the `*_NOT_CONFIGURED` admin gates, …). The list documents
1510
- * the contract; clients switch on `error.code`. Kept sorted for stable output.
1511
- */
2863
+ /**
2864
+ * The machine-readable `LunoraError` codes Lunora emits on the RPC + REST
2865
+ * surfaces, enumerated from `@lunora/server`'s `CODE_STATUS` map plus the
2866
+ * runtime/DO dispatch codes (`FUNCTION_NOT_FOUND`, `PAYLOAD_TOO_LARGE`,
2867
+ * `METHOD_NOT_ALLOWED`, the `*_NOT_CONFIGURED` admin gates, …). The list documents
2868
+ * the contract; clients switch on `error.code`. Kept sorted for stable output.
2869
+ */
1512
2870
  declare const LUNORA_ERROR_CODES: ReadonlyArray<string>;
2871
+ /** The matching secret rule's `kind` for a string value, or `undefined` when none matches. */
2872
+ declare const secretKindOf: (value: string) => string | undefined;
2873
+ /** A redacted preview of a secret value — first 4 chars plus its length, never the full value. */
2874
+ declare const redact: (value: string) => string;
1513
2875
  declare const VERSION = "0.0.0";
1514
- export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, type FieldSnapshot, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type MaskProcedureIR, type MigrationIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, type QueryReadIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverNondeterministicCalls, discoverQueries, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverStorageRulesMetadata, discoverWorkflows, emitApi, emitApp, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };
2876
+ export { AGENTS_FILENAME, type AgentIR, type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type LintSchemaOptions, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, NOTIFY_FILENAME, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SHAPES_FILENAME, type SandboxUsage, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, type WranglerVariableIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAgents, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverNotifyCalls, discoverNotifyConfig, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSandboxUsage, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitAgents, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, redact, refreshCodegenProject, runCodegen, schemaFromIr, secretKindOf, serializeSchemaSnapshot, validatorIrToJsonSchema };