@lunora/codegen 1.0.0-alpha.291 → 1.0.0-alpha.292
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.
- package/dist/index.d.mts +1993 -1926
- package/dist/index.d.ts +1993 -1926
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/{AGENTS_FILENAME-CwwSEW6a.mjs → AGENTS_FILENAME-B6-kjzM3.mjs} +1 -1
- package/dist/packem_shared/{CONTAINERS_FILENAME-BtX5vWh8.mjs → CONTAINERS_FILENAME-Bk0u9QUu.mjs} +1 -1
- package/dist/packem_shared/{MUTATORS_FILENAME-Dl1tElqi.mjs → MUTATORS_FILENAME-Bw0YtaO8.mjs} +1 -1
- package/dist/packem_shared/{NOTIFY_FILENAME-BeTurUSC.mjs → NOTIFY_FILENAME-CPjbiHQl.mjs} +1 -1
- package/dist/packem_shared/{QUEUES_FILENAME-D588u733.mjs → QUEUES_FILENAME-CsRy4VTF.mjs} +1 -1
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-DndbpsGL.mjs +11 -0
- package/dist/packem_shared/{SHAPES_FILENAME-D-Q8lSEw.mjs → SHAPES_FILENAME-DkBUN9dK.mjs} +1 -1
- package/dist/packem_shared/{WORKFLOWS_FILENAME-BGMSNTDC.mjs → WORKFLOWS_FILENAME-CQR8fE9B.mjs} +1 -1
- package/dist/packem_shared/{attribution-BUciBs1M.mjs → attribution-hVBriwzy.mjs} +1 -1
- package/dist/packem_shared/chain-CRDaTj2c.mjs +1 -0
- package/dist/packem_shared/classify-procedure-call-BAj0D1u2.mjs +1 -0
- package/dist/packem_shared/{context-property-calls-DPnUYWSX.mjs → context-property-calls-DJ_A1R5i.mjs} +1 -1
- package/dist/packem_shared/{context-root-Dlo21ybw.mjs → context-root-wBahDTzk.mjs} +1 -1
- package/dist/packem_shared/createCodegenProject-V51_yBw3.mjs +1 -0
- package/dist/packem_shared/{database-calls-ONnjnkMd.mjs → database-calls-CHZbBUCZ.mjs} +1 -1
- package/dist/packem_shared/{discoverAuthApiCalls-DAaK0HUK.mjs → discoverAuthApiCalls-xs0ONfI-.mjs} +1 -1
- package/dist/packem_shared/{discoverFunctions-DsQwrwWq.mjs → discoverFunctions-DaEZl3uE.mjs} +1 -1
- package/dist/packem_shared/{discoverHttpRoutes-Cv-h-Yuc.mjs → discoverHttpRoutes-C_u9YiFb.mjs} +1 -1
- package/dist/packem_shared/discoverHyperdriveCalls-BUrA_96X.mjs +1 -0
- package/dist/packem_shared/{discoverInserts-CQsoloGk.mjs → discoverInserts-OT_fiEGq.mjs} +1 -1
- package/dist/packem_shared/discoverMaskProcedures--hMyCNud.mjs +1 -0
- package/dist/packem_shared/{discoverMigrations-uxm9qHrr.mjs → discoverMigrations-C9mLIhGZ.mjs} +1 -1
- package/dist/packem_shared/{discoverNondeterministicCalls-Dm7_PJoA.mjs → discoverNondeterministicCalls-9ZDpRptb.mjs} +1 -1
- package/dist/packem_shared/discoverQueries-C8V4cMIP.mjs +1 -0
- package/dist/packem_shared/discoverR2sqlCalls-COKmDnih.mjs +1 -0
- package/dist/packem_shared/{discoverRlsMetadata-Cu4bc5KZ.mjs → discoverRlsMetadata-Cb8JR1b4.mjs} +1 -1
- package/dist/packem_shared/{discoverRlsProcedures-Bkohn4ZR.mjs → discoverRlsProcedures-BMqosvx6.mjs} +1 -1
- package/dist/packem_shared/{discoverStorageRulesMetadata-D6rjgKYC.mjs → discoverStorageRulesMetadata-BPJplDfz.mjs} +1 -1
- package/dist/packem_shared/emitAgents-D_UUdNe1.mjs +1 -0
- package/dist/packem_shared/{emitApp-D7G7gmV7.mjs → emitApp-DAZCUDbr.mjs} +1 -1
- package/dist/packem_shared/{emitCrons-Gd8jD8jF.mjs → emitCrons-BQW2lviZ.mjs} +1 -1
- package/dist/packem_shared/emitServer-D1vA5_X1.mjs +1 -0
- package/dist/packem_shared/emitShard-DY0iaXec.mjs +1 -0
- package/dist/packem_shared/{exported-procedure-chains-BZk1-hro.mjs → exported-procedure-chains-DKK5HoK3.mjs} +1 -1
- package/dist/packem_shared/{handler-reference-DXkpwoAq.mjs → handler-reference-vIeqkhV_.mjs} +1 -1
- package/dist/packem_shared/{index-BoPXSW7g.mjs → index-BW4hJ4Wm.mjs} +1 -1
- package/dist/packem_shared/platform-8oj7iXQy.mjs +1 -0
- package/dist/packem_shared/schema-BgM8A3XV.mjs +1 -0
- package/dist/packem_shared/{server-DSUfd6Tc.mjs → server-DAvi_bv_.mjs} +2 -2
- package/dist/packem_shared/{shard-BNhX5R-a.mjs → shard-DfBMIpi9.mjs} +6 -6
- package/dist/packem_shared/{shard-runtime-CkaEBSdL.mjs → shard-runtime-BX0xmo0P.mjs} +3 -3
- package/package.json +6 -6
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-3FEkspbx.mjs +0 -11
- package/dist/packem_shared/chain-BUxIVIw_.mjs +0 -1
- package/dist/packem_shared/classify-procedure-call-BJdf_-M6.mjs +0 -1
- package/dist/packem_shared/discoverHyperdriveCalls-xy2G6igG.mjs +0 -1
- package/dist/packem_shared/discoverMaskProcedures-Dr1UccD6.mjs +0 -1
- package/dist/packem_shared/discoverQueries-BqECbkm9.mjs +0 -1
- package/dist/packem_shared/discoverR2sqlCalls-I9IqJFcy.mjs +0 -1
- package/dist/packem_shared/emitAgents-BedQefBj.mjs +0 -1
- package/dist/packem_shared/emitServer-DlOheWku.mjs +0 -1
- package/dist/packem_shared/emitShard-DguoZWGv.mjs +0 -1
- package/dist/packem_shared/ir-DBSvhNa-.mjs +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -251,2237 +251,2301 @@ declare const serializeSchemaSnapshot: (snapshot: SchemaSnapshot) => string;
|
|
|
251
251
|
*/
|
|
252
252
|
declare const diffSchemaSnapshots: (baseline: SchemaSnapshot | undefined, current: SchemaSnapshot) => SchemaDrift;
|
|
253
253
|
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
254
|
+
* Who a discovered call site runs on behalf of — the one attribution every
|
|
255
|
+
* per-call-site IR carries (see `discover/attribution`).
|
|
256
|
+
*
|
|
257
|
+
* `export`: lexically inside an exported top-level declaration. `name` is the
|
|
258
|
+
* EXPORTED name — `export const send = …` → `send`, `export default …` →
|
|
259
|
+
* `default`, `const run = …; export { run as start }` → `start`.
|
|
260
|
+
*
|
|
261
|
+
* `helper`: inside a top-level, non-exported `function` / `const` of the same
|
|
262
|
+
* file. `callers` are the exports that reach it through same-file value
|
|
263
|
+
* references (a call, or the helper passed on as a value), transitively and
|
|
264
|
+
* sorted; a type-only reference (`typeof helper`) does not count. Empty for an
|
|
265
|
+
* orphan helper no export reaches. `untracked` is set when code attribution
|
|
266
|
+
* cannot follow — module scope, a class body, a destructured declaration — also
|
|
267
|
+
* reaches the helper, so `callers` is incomplete.
|
|
268
|
+
*
|
|
269
|
+
* `module`: outside every top-level `function` / `const` — module scope, or a
|
|
270
|
+
* class body.
|
|
271
|
+
*
|
|
272
|
+
* A site is recorded once; consumers fan out over its callers. The advisor's
|
|
273
|
+
* `AdvisorCallSiteScope` is the same type, so feeder rows pass straight through.
|
|
257
274
|
*/
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
/** Reflective representation of a single validator call from the schema. */
|
|
269
|
-
interface ValidatorIR {
|
|
270
|
-
/** For `v.storage(bucket?)` — the named typed bucket the object key lives in, when given. */
|
|
271
|
-
bucket?: string;
|
|
272
|
-
/** Column modifiers (`.unique()`, `.default()`, `.nullable()`, …) when present. */
|
|
273
|
-
column?: ColumnMetaIR;
|
|
274
|
-
/**
|
|
275
|
-
* `true` when this validator carries a refinement (`.check(...)`, `.max(n)`,
|
|
276
|
-
* `.email()`, …) — anything that narrows the accepted values without changing
|
|
277
|
-
* the kind. `schema-drift` hashes this, so a bound counts here even when it is
|
|
278
|
-
* modelled below. `.meta(...)` is pure metadata with no parse effect and does
|
|
279
|
-
* NOT set it.
|
|
280
|
-
*/
|
|
281
|
-
hasRefinement?: boolean;
|
|
282
|
-
/** For `v.optional(inner)` / `v.array(inner)`. */
|
|
283
|
-
inner?: ValidatorIR;
|
|
284
|
-
/** For `v.record(key, value)`. */
|
|
285
|
-
keyType?: ValidatorIR;
|
|
286
|
-
/** Kind of validator (string, number, id, object, optional, array, union, literal, record, ...). */
|
|
287
|
-
kind: string;
|
|
288
|
-
/** For `v.literal(value)` — the literal value as source text. */
|
|
289
|
-
literalValue?: string;
|
|
290
|
-
/** For `v.union(a, b, ...)`. */
|
|
291
|
-
members?: ValidatorIR[];
|
|
292
|
-
/** For `v.object({...})`. */
|
|
293
|
-
shape?: Record<string, ValidatorIR>;
|
|
294
|
-
/** Verbatim source text — used in emitted code when we can't reconstruct from AST. */
|
|
295
|
-
sourceText?: string;
|
|
296
|
-
/**
|
|
297
|
-
* The `n` of a `v.string().max(n)` written with a numeric literal — the one
|
|
298
|
-
* refinement predicate the IR can represent exactly (`value.length <= n`).
|
|
299
|
-
* The AOT compiler emits it as a guard instead of declining the node, which
|
|
300
|
-
* is what keeps a length-bounded public argument on the fast path.
|
|
301
|
-
*/
|
|
302
|
-
stringMaxLength?: number;
|
|
303
|
-
/** For `v.id("table")` — the table name. */
|
|
304
|
-
tableName?: string;
|
|
275
|
+
type CallSiteScope = AdvisorCallSiteScope;
|
|
276
|
+
/**
|
|
277
|
+
* One call-site edge of the architecture graph, attributed through its
|
|
278
|
+
* {@link CallSiteScope}.
|
|
279
|
+
* Exactly one of `target` / `reason` is set: `target` is a `namespace:export`
|
|
280
|
+
* function key (`call` / `schedule`) or a queue / topic export name (`enqueue` /
|
|
281
|
+
* `publish`); `reason` says why the target could not be read statically.
|
|
282
|
+
*/
|
|
283
|
+
interface CallEdgeIR {
|
|
305
284
|
/**
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* Absent when it could not be recovered safely, in which case the emitted
|
|
310
|
-
* type falls back to `unknown`.
|
|
285
|
+
* `true` when the hop may not take effect on every pass of its function —
|
|
286
|
+
* see `isConditionalSite` in `discover/call-edges.ts` for the rules. Set
|
|
287
|
+
* only, never `false`. The `dispatch_cycle` lint's false-positive gate.
|
|
311
288
|
*/
|
|
312
|
-
|
|
289
|
+
conditional?: true;
|
|
290
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
291
|
+
file: string;
|
|
292
|
+
kind: "call" | "enqueue" | "invoke" | "publish" | "schedule";
|
|
293
|
+
/** 1-based line of the call. */
|
|
294
|
+
line: number;
|
|
295
|
+
reason?: string;
|
|
313
296
|
/**
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
297
|
+
* The facts of a scheduled dispatch, present on `schedule` edges only. `delayMs`
|
|
298
|
+
* is the literal `runAfter` delay (absent for `runAt` or a non-literal delay).
|
|
299
|
+
* `argKeys` are the top-level property names of the dispatch's args object:
|
|
300
|
+
* `[]` when there are no args, and `undefined` when the args are not an object
|
|
301
|
+
* literal (or spread into one), so a lint can tell "no cursor" from "cannot tell".
|
|
318
302
|
*/
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
interface SearchIndexIR {
|
|
328
|
-
/** Primary text-search field; a dot-separated path reads a nested field. */
|
|
329
|
-
field: string;
|
|
330
|
-
/** Optional filter fields surfaced alongside the FTS column. */
|
|
331
|
-
filterFields?: ReadonlyArray<string>;
|
|
332
|
-
/** Text-analysis profile (accent folding + that language's stopwords). */
|
|
333
|
-
language?: string;
|
|
334
|
-
name: string;
|
|
335
|
-
/** Skip the migration-time backfill of the search companion (large tables index out-of-band). */
|
|
336
|
-
staged?: boolean;
|
|
337
|
-
/** `"native"` opts into the engine's own full-text index where it has one (Postgres). */
|
|
338
|
-
strategy?: string;
|
|
339
|
-
}
|
|
340
|
-
/** A `.geoIndex(name, { field, precision? })` declaration — a geohash companion over a `v.geoPoint()` column. */
|
|
341
|
-
interface GeoIndexIR {
|
|
342
|
-
/** The `v.geoPoint()` column feeding the geohash. */
|
|
343
|
-
field: string;
|
|
344
|
-
name: string;
|
|
345
|
-
/** Geohash precision (characters) maintained on the companion; omitted ⇒ the runtime default. */
|
|
346
|
-
precision?: number;
|
|
347
|
-
}
|
|
348
|
-
/** A `.ttl(field, { after? })` declaration — declarative table-level auto-expiry. */
|
|
349
|
-
interface TtlIR {
|
|
350
|
-
/** Millisecond offset added to `field` to derive the expiry (`field + after`); omitted ⇒ `field` is the absolute expiry. */
|
|
351
|
-
after?: number;
|
|
352
|
-
/** The epoch-millisecond expiry column. */
|
|
353
|
-
field: string;
|
|
354
|
-
}
|
|
355
|
-
interface VectorIndexIR {
|
|
356
|
-
dimensions?: number;
|
|
357
|
-
/** Shape A: the single source column. Shape B: undefined (derived via a `select` fn). */
|
|
358
|
-
field?: string;
|
|
359
|
-
/** Shape A: metadata field names mirrored into Vectorize. */
|
|
360
|
-
metadata?: ReadonlyArray<string>;
|
|
361
|
-
metric?: "cosine" | "dot-product" | "euclidean";
|
|
362
|
-
name: string;
|
|
363
|
-
/** Owning table the vectors are sourced from. */
|
|
364
|
-
table: string;
|
|
303
|
+
schedule?: {
|
|
304
|
+
argKeys?: string[];
|
|
305
|
+
delayMs?: number;
|
|
306
|
+
};
|
|
307
|
+
/** Who the call runs on behalf of. */
|
|
308
|
+
scope: CallSiteScope;
|
|
309
|
+
target?: string;
|
|
365
310
|
}
|
|
366
311
|
/**
|
|
367
|
-
*
|
|
368
|
-
*
|
|
312
|
+
* A literal-infinite loop in `lunora/` source — `while (true)`, `for (;;)` /
|
|
313
|
+
* `for (; true;)`, `do { … } while (true)` — with no statically reachable way
|
|
314
|
+
* out: no `break` bound to this loop, no `return`/`throw` outside a nested
|
|
315
|
+
* function, no labeled `break` escaping it. One record per offending loop; a
|
|
316
|
+
* loop that can exit records nothing, so the `unbounded_loop` lint has no
|
|
317
|
+
* bounded loop to sift through.
|
|
369
318
|
*/
|
|
370
|
-
interface
|
|
371
|
-
|
|
372
|
-
|
|
319
|
+
interface UnboundedLoopIR {
|
|
320
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
321
|
+
file: string;
|
|
322
|
+
/** The loop form that never falls out. */
|
|
323
|
+
kind: "do" | "for" | "while";
|
|
324
|
+
/** 1-based line of the `loop` keyword. */
|
|
325
|
+
line: number;
|
|
326
|
+
/** Who the loop runs on behalf of. */
|
|
327
|
+
scope: CallSiteScope;
|
|
373
328
|
}
|
|
374
329
|
/**
|
|
375
|
-
* A
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
330
|
+
* A `ctx.workflows.get("name")…` call discovered in a function body — the
|
|
331
|
+
* use-site analog of {@link WorkflowIR} (which is the declaration side). Feeds
|
|
332
|
+
* the `workflow_unused` lint (a declared workflow with zero call sites) and the
|
|
333
|
+
* `workflow_unknown_target` lint (a `.get("x")` whose `x` isn't declared — a
|
|
334
|
+
* typo catcher). {@link WorkflowCallIR.workflow} is `""` when the `get(...)`
|
|
335
|
+
* argument is not a string literal (a dynamic name — which suppresses the
|
|
336
|
+
* unused-workflow heuristic rather than producing a false positive).
|
|
379
337
|
*/
|
|
380
|
-
interface
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
field: string;
|
|
390
|
-
kind: "many" | "one";
|
|
391
|
-
/** Accessor name the relation is loaded under (the `with` key). */
|
|
392
|
-
name: string;
|
|
393
|
-
/** FK behaviour applied to holder rows when the referenced parent is deleted. */
|
|
394
|
-
onDelete?: "cascade" | "restrict" | "set null";
|
|
395
|
-
/** Referenced column (defaults to `_id`). */
|
|
396
|
-
references: string;
|
|
397
|
-
/** Target table name. */
|
|
398
|
-
table: string;
|
|
338
|
+
interface WorkflowCallIR {
|
|
339
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
340
|
+
file: string;
|
|
341
|
+
/** 1-based line of the `get(...)` call. */
|
|
342
|
+
line: number;
|
|
343
|
+
/** Who the call runs on behalf of. */
|
|
344
|
+
scope: CallSiteScope;
|
|
345
|
+
/** The referenced workflow export name, or `""` when the argument is not a string literal. */
|
|
346
|
+
workflow: string;
|
|
399
347
|
}
|
|
400
348
|
/**
|
|
401
|
-
*
|
|
402
|
-
* advisor lints
|
|
403
|
-
*
|
|
404
|
-
* the
|
|
349
|
+
* A `ctx.db.query("table")…` read discovered in a function body, reduced to what
|
|
350
|
+
* the query advisor lints need: which table, whether the chain narrows with an
|
|
351
|
+
* index, whether it filters, and which terminal materializes the result.
|
|
352
|
+
* `table` is `""` when the `query(...)` argument is not a string literal (a
|
|
353
|
+
* dynamic table — not lintable).
|
|
405
354
|
*/
|
|
406
|
-
interface
|
|
407
|
-
/**
|
|
408
|
-
|
|
409
|
-
/** Whether a `columns` projection allow-list was given. */
|
|
410
|
-
columns?: ReadonlyArray<string>;
|
|
411
|
-
/** `true` when a `reconcileEveryMs` was given — one of the two incremental delete-visibility paths the `external_source_incremental_no_delete_path` lint checks. */
|
|
412
|
-
hasReconcile?: boolean;
|
|
413
|
-
/** `true` when a `softDeleteColumn` was given — the other incremental delete-visibility path. */
|
|
414
|
-
hasSoftDelete?: boolean;
|
|
415
|
-
/** `true` when a `tenantBy` mapper was given — the tenant-isolation boundary the `external_source_unscoped` lint checks. */
|
|
416
|
-
hasTenantBy: boolean;
|
|
417
|
-
/** The `idColumn` literal, when given (defaults to `"id"` at runtime). */
|
|
418
|
-
idColumn?: string;
|
|
419
|
-
/** Delete-detection mode literal, when given (`"full-pull"` today). */
|
|
420
|
-
mode?: string;
|
|
421
|
-
/** The membership query literal, when statically knowable. */
|
|
422
|
-
query?: string;
|
|
355
|
+
interface QueryReadIR {
|
|
356
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
357
|
+
file: string;
|
|
423
358
|
/**
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
* verified, instead of mistaking it for no `.source()` at all.
|
|
359
|
+
* The fields the chain's `.filter()` predicate reads off its row parameter
|
|
360
|
+
* (`(r) => r.a < cutoff && r.b === x` → `["a", "b"]`). `undefined` when the
|
|
361
|
+
* chain has no filter or the predicate is not a function literal. Optional so
|
|
362
|
+
* a feeder predating this field still typechecks.
|
|
429
363
|
*/
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
interface TableIR {
|
|
364
|
+
filterFields?: string[];
|
|
433
365
|
/**
|
|
434
|
-
*
|
|
435
|
-
* `
|
|
436
|
-
*
|
|
437
|
-
*
|
|
366
|
+
* True when the chain's `.filter()` predicate compares `_id`
|
|
367
|
+
* (`(d) => d._id === args.id`) — a full scan for a row that `ctx.db.get`
|
|
368
|
+
* addresses directly. Optional so a feeder predating this field still
|
|
369
|
+
* typechecks; absent is treated as "not a primary-key filter".
|
|
438
370
|
*/
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
*/
|
|
445
|
-
extensionFromPackage?: true;
|
|
446
|
-
/**
|
|
447
|
-
* The `defineSchemaExtension` key that contributed this table, set when it
|
|
448
|
-
* arrived through `defineSchema(...).extend(...)`. Absent for a table the app
|
|
449
|
-
* declared itself.
|
|
450
|
-
*
|
|
451
|
-
* Drives the generated `AppTableName` union: an add-on's tables
|
|
452
|
-
* (`ratelimit_buckets`, …) are real tables and stay in `TableName`, but an app
|
|
453
|
-
* enumerating "my tables" should not have to know about them.
|
|
454
|
-
*/
|
|
455
|
-
extensionKey?: string;
|
|
456
|
-
/**
|
|
457
|
-
* `true` when the table chain carried `.externallyManaged()` — its rows are
|
|
458
|
-
* written outside Lunora's discoverable insert path (adapter/migration/
|
|
459
|
-
* middleware), so advisor insert-path lints skip it. Optional: hand-built
|
|
460
|
-
* IR and the runtime `fromServerSchema` path default it to `false`.
|
|
461
|
-
*/
|
|
462
|
-
externallyManaged?: boolean;
|
|
463
|
-
/**
|
|
464
|
-
* Set when the chain carried `.source(...)` — the table is materialized from an
|
|
465
|
-
* external Hyperdrive-backed database by a system poll loop (plan 077). Carries
|
|
466
|
-
* the statically-knowable bits the advisor lints read; the functions (`map`,
|
|
467
|
-
* `tenantBy`) are not serialized, only their presence (`hasTenantBy`).
|
|
468
|
-
*/
|
|
469
|
-
externalSource?: ExternalSourceIR;
|
|
470
|
-
/** Geospatial indexes declared inline via `.geoIndex(name, …)`. Optional so hand-built IR may omit it (discovery always sets it). */
|
|
471
|
-
geoIndexes?: ReadonlyArray<GeoIndexIR>;
|
|
472
|
-
/**
|
|
473
|
-
* Storage backend for a `.global()` table: `"d1"` (default) or
|
|
474
|
-
* `"hyperdrive"` (a Postgres/MySQL database via Cloudflare Hyperdrive). Only
|
|
475
|
-
* meaningful when `shardMode === "global"`; absent for sharded/root tables.
|
|
476
|
-
*
|
|
477
|
-
* Read it through `isD1GlobalTable` / `isHyperdriveGlobalTable` rather than
|
|
478
|
-
* comparing here — discovery normalises the absent case to `"d1"`, and only
|
|
479
|
-
* those predicates encode which comparison is right for hand-built IR.
|
|
480
|
-
*/
|
|
481
|
-
globalBackend?: "d1" | "hyperdrive";
|
|
482
|
-
indexes: ReadonlyArray<IndexIR>;
|
|
483
|
-
/**
|
|
484
|
-
* `true` when the table chain carried `.public()` — an explicit opt-OUT of
|
|
485
|
-
* the schema's `.rls("required")` enforcement for this one table. Optional:
|
|
486
|
-
* hand-built IR and tables that never called `.public()` default it to
|
|
487
|
-
* `false`.
|
|
488
|
-
*/
|
|
489
|
-
isPublic?: boolean;
|
|
371
|
+
filtersPrimaryKey?: boolean;
|
|
372
|
+
/** The chain calls `.filter(...)`. */
|
|
373
|
+
hasFilter: boolean;
|
|
374
|
+
/** The chain narrows with `.withIndex(...)` or `.withSearchIndex(...)`. */
|
|
375
|
+
hasIndex: boolean;
|
|
490
376
|
/**
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
377
|
+
* The chain's `.withIndex(…)`, present exactly when the chain has one. `name` is
|
|
378
|
+
* the literal index name, or `undefined` when it is not a string literal.
|
|
379
|
+
* `bounds` are the comparators its range callback calls on the index parameter,
|
|
380
|
+
* sorted and de-duplicated: `[]` for a bare `.withIndex(name)` (the widest range),
|
|
381
|
+
* `undefined` when the callback exists but cannot be read.
|
|
495
382
|
*/
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
rankIndexes: ReadonlyArray<RankIndexIR>;
|
|
500
|
-
/** Declared relations (via `.relations((r) => …)`), keyed in source order. */
|
|
501
|
-
relations: ReadonlyArray<RelationIR>;
|
|
502
|
-
searchIndexes: ReadonlyArray<SearchIndexIR>;
|
|
503
|
-
shape: Record<string, ValidatorIR>;
|
|
504
|
-
shardMode: "global" | "root" | {
|
|
505
|
-
field: string;
|
|
506
|
-
kind: "shardBy";
|
|
507
|
-
};
|
|
508
|
-
/** Set when the chain carried `.softDelete()` — the marker column's name (default `deletedAt`). The column is injected into `shape` so `Doc_*` carries it. */
|
|
509
|
-
softDelete?: {
|
|
510
|
-
field: string;
|
|
383
|
+
index?: {
|
|
384
|
+
bounds?: string[];
|
|
385
|
+
name?: string;
|
|
511
386
|
};
|
|
512
|
-
/**
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
type JurisdictionIR = "eu" | "fedramp" | "us";
|
|
519
|
-
interface SchemaIR {
|
|
520
|
-
/**
|
|
521
|
-
* Cloudflare data-residency jurisdiction declared via
|
|
522
|
-
* `defineSchema(...).jurisdiction("…")`. Emitted into the generated worker's
|
|
523
|
-
* `createWorker({ jurisdiction })` (and `ctx.scheduler` / `ctx.containers`).
|
|
524
|
-
* Absent ⇒ un-pinned.
|
|
525
|
-
*/
|
|
526
|
-
jurisdiction?: JurisdictionIR;
|
|
387
|
+
/** 1-based line of the `query(...)` call. */
|
|
388
|
+
line: number;
|
|
389
|
+
/** Who the read runs on behalf of. */
|
|
390
|
+
scope: CallSiteScope;
|
|
391
|
+
/** Queried table name, or `""` when the argument is not a string literal. */
|
|
392
|
+
table: string;
|
|
527
393
|
/**
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
394
|
+
* The materializing call the chain ends in — `"collect"`, `"take"`,
|
|
395
|
+
* `"paginate"`, `"first"`, `"unique"`, … — i.e. how much of the narrowed set
|
|
396
|
+
* the read actually loads.
|
|
397
|
+
*
|
|
398
|
+
* `undefined` when the chain reaches no recognised terminal (a reader passed
|
|
399
|
+
* on, a bare `query(...)`) AND when a feeder predating this field produced
|
|
400
|
+
* the read. The two are deliberately not distinguished: no consumer could act
|
|
401
|
+
* on the difference, so the terminal-shaped lints skip the read either way
|
|
402
|
+
* rather than guessing a terminal.
|
|
532
403
|
*/
|
|
533
|
-
|
|
404
|
+
terminal?: string;
|
|
534
405
|
/**
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
* Absent when the schema never called `.rls("required")`.
|
|
406
|
+
* Visibility of the procedure the read runs on behalf of: `internal` when every
|
|
407
|
+
* caller is internal, `public` when any caller is public. Stamped from the
|
|
408
|
+
* registered functions, so it is absent when the feeder could not read them.
|
|
539
409
|
*/
|
|
540
|
-
|
|
541
|
-
tables: ReadonlyArray<TableIR>;
|
|
542
|
-
/** All vector indexes (inline Shape A hoisted + standalone Shape B), flattened. */
|
|
543
|
-
vectorIndexes: ReadonlyArray<VectorIndexIR>;
|
|
410
|
+
visibility?: "internal" | "public";
|
|
544
411
|
}
|
|
545
412
|
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
413
|
+
* A `ctx.authApi.<method>(...)` call discovered in a function body, attributed
|
|
414
|
+
* to the exported function (and its file = api namespace) that performs it.
|
|
415
|
+
* Structurally identical to `AdvisorAuthApiCall` so it passes straight through
|
|
416
|
+
* to the advisor lint without conversion, exactly as `InsertWriteIR` does for
|
|
417
|
+
* `AdvisorInsertWrite`.
|
|
549
418
|
*/
|
|
550
|
-
interface
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
419
|
+
interface AuthApiCallIR {
|
|
420
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
421
|
+
file: string;
|
|
422
|
+
/** True when the call's argument object includes a `headers` property. */
|
|
423
|
+
hasHeaders: boolean;
|
|
424
|
+
/** 1-based line of the call, or `0` when unknown. */
|
|
425
|
+
line: number;
|
|
426
|
+
/** The better-auth method invoked (e.g. `banUser`); empty when not statically known. */
|
|
427
|
+
method: string;
|
|
428
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
429
|
+
scope: CallSiteScope;
|
|
556
430
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
exportName: string;
|
|
565
|
-
/**
|
|
566
|
-
* Set by the `.expose({ rest: true })` builder modifier (plan 167). When
|
|
567
|
-
* `rest` is `true` the function is published on the public REST surface, so the
|
|
568
|
-
* OpenAPI emitter describes it as a real `/_lunora/rest/<namespace>/<fn>` path
|
|
569
|
-
* (the single source of truth the runtime router also derives from). Absent →
|
|
570
|
-
* RPC-only (the default; not on the REST surface).
|
|
571
|
-
*
|
|
572
|
-
* `cache` mirrors the `RestCacheConfig` the runtime turns into response
|
|
573
|
-
* headers, so the emitted spec can document the `Cache-Control` a caller will
|
|
574
|
-
* actually observe. Only statically-readable literal fields are carried; a
|
|
575
|
-
* computed value is simply absent (the spec under-documents rather than lies).
|
|
576
|
-
*/
|
|
577
|
-
expose?: {
|
|
578
|
-
cache?: ExposeCacheIR;
|
|
579
|
-
rest?: boolean;
|
|
580
|
-
};
|
|
581
|
-
/** Path relative to `<projectRoot>/lunora/` without extension, e.g. "messages". */
|
|
582
|
-
filePath: string;
|
|
583
|
-
kind: "action" | "mutation" | "query" | "stream";
|
|
584
|
-
/**
|
|
585
|
-
* Set on lifecycle hooks (`onConnect`/`onDisconnect`/`onShardInit`/
|
|
586
|
-
* `onQueryChange`/`onWhisper`): the moment the hook fires on. Such a function
|
|
587
|
-
* is also an internal registration (so it lands in `LUNORA_FUNCTIONS` for path
|
|
588
|
-
* dispatch) — a mutation for every moment except `whisper`, which is a query;
|
|
589
|
-
* emit additionally collects it into the `LUNORA_LIFECYCLE_HOOKS` manifest
|
|
590
|
-
* keyed by this moment. Absent on ordinary functions.
|
|
591
|
-
*/
|
|
592
|
-
lifecycle?: "connect" | "disconnect" | "init" | "reactor" | "whisper";
|
|
593
|
-
/**
|
|
594
|
-
* The `.output(validator)` declaration, when the chain has one.
|
|
595
|
-
*
|
|
596
|
-
* Takes precedence over {@link FunctionIR.returnType} (the handler's
|
|
597
|
-
* inferred type) for the emitted `FunctionReference`. `.output()` is what
|
|
598
|
-
* validates at runtime and what a reader takes as the contract, so the two
|
|
599
|
-
* must agree — see the emit-side note for what went wrong when they did not.
|
|
600
|
-
*/
|
|
601
|
-
output?: ValidatorIR;
|
|
431
|
+
/**
|
|
432
|
+
* A table write other than a plain `ctx.db.insert(...)` (see {@link InsertWriteIR}):
|
|
433
|
+
* a by-id `patch`/`replace`/`delete`/`hardDelete`/`restore`, a batch write, or a
|
|
434
|
+
* `ctx.db.<table>.*` facade write. Feeds the architecture manifest's `write`
|
|
435
|
+
* edges and the `cross_module_table_write` lint.
|
|
436
|
+
*/
|
|
437
|
+
interface TableWriteIR {
|
|
602
438
|
/**
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
439
|
+
* The fields a `patch` / `patchMany` writes, read off its object-literal
|
|
440
|
+
* argument. `undefined` for every other method, and for a patch whose
|
|
441
|
+
* argument is not an object literal or spreads one: the feeder then cannot
|
|
442
|
+
* say which fields change.
|
|
607
443
|
*/
|
|
608
|
-
|
|
444
|
+
fields?: string[];
|
|
445
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
446
|
+
file: string;
|
|
447
|
+
/** 1-based line of the call. */
|
|
448
|
+
line: number;
|
|
449
|
+
/** The writer method called, e.g. `patch`, `deleteMany`, `upsert`. */
|
|
450
|
+
method: string;
|
|
451
|
+
/** Who the write runs on behalf of. */
|
|
452
|
+
scope: CallSiteScope;
|
|
609
453
|
/**
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
* `internal` object and is rejected by the DO's external RPC path.
|
|
454
|
+
* Target table; `""` when it can't be read (an untyped id, a non-literal
|
|
455
|
+
* name). A union id (`Id<"a"> | Id<"b">`) gives one record per table.
|
|
613
456
|
*/
|
|
614
|
-
|
|
457
|
+
table: string;
|
|
615
458
|
}
|
|
616
459
|
/**
|
|
617
|
-
* A
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
* tables with breaking drift, so a migration left at `""` covers nothing.
|
|
460
|
+
* A row-changing write a function performs directly or through the functions it
|
|
461
|
+
* reaches, attributed to the EXPORT that reaches it. A write in a helper in
|
|
462
|
+
* another file is included under the export that calls the helper, so a sweep
|
|
463
|
+
* that deletes through a helper counts as progress. The fields follow
|
|
464
|
+
* {@link TableWriteIR.fields}.
|
|
623
465
|
*/
|
|
624
|
-
interface
|
|
625
|
-
/**
|
|
466
|
+
interface RowEffectIR {
|
|
467
|
+
/** The export the write is reachable from. */
|
|
626
468
|
exportName: string;
|
|
627
|
-
/**
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
|
|
631
|
-
/**
|
|
469
|
+
/** See {@link TableWriteIR.fields}. */
|
|
470
|
+
fields?: string[];
|
|
471
|
+
/** Source file of that export, relative to `<projectRoot>/lunora/`, without extension. */
|
|
472
|
+
file: string;
|
|
473
|
+
/** The write method, as on {@link TableWriteIR.method}. */
|
|
474
|
+
method: string;
|
|
475
|
+
/** The table the write targets. Never `""`: an unreadable table is not evidence of progress. */
|
|
632
476
|
table: string;
|
|
633
477
|
}
|
|
634
478
|
/**
|
|
635
|
-
* A `
|
|
636
|
-
* (
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
* because the runtime object (`columns`/`compileWhere`) carries the authority.
|
|
479
|
+
* A `ctx.db.insert("table", …)` write discovered in a function body, attributed
|
|
480
|
+
* to the exported function (and its file = api namespace) that performs it — the
|
|
481
|
+
* write-side analog of {@link QueryReadIR}. Lets tooling wire a table's write
|
|
482
|
+
* action by behavior (which function inserts into it) rather than by naming.
|
|
483
|
+
* {@link InsertWriteIR.table} is `""` when the argument is not a string literal.
|
|
641
484
|
*/
|
|
642
|
-
interface
|
|
643
|
-
/**
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
|
|
651
|
-
/** Path relative to `<projectRoot>/lunora/` without extension — always `"shapes"`. */
|
|
652
|
-
filePath: string;
|
|
653
|
-
/**
|
|
654
|
-
* The `table` string literal from the `defineShape({ table })` call, lifted
|
|
655
|
-
* only for static advisor lints (the runtime object stays authoritative).
|
|
656
|
-
* `undefined` when `table` is not a plain string literal — lints skip those.
|
|
657
|
-
*/
|
|
658
|
-
table?: string;
|
|
485
|
+
interface InsertWriteIR {
|
|
486
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
487
|
+
file: string;
|
|
488
|
+
/** 1-based line of the `insert(...)` call. */
|
|
489
|
+
line: number;
|
|
490
|
+
/** Who the insert runs on behalf of. */
|
|
491
|
+
scope: CallSiteScope;
|
|
492
|
+
/** Target table name, or `""` when the argument is not a string literal. */
|
|
493
|
+
table: string;
|
|
659
494
|
}
|
|
660
495
|
/**
|
|
661
|
-
*
|
|
662
|
-
* `
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
496
|
+
* A non-deterministic API call (`Date.now`, `Math.random`, `crypto.randomUUID`,
|
|
497
|
+
* `crypto.getRandomValues`, `fetch`) discovered lexically inside a `query(...)`
|
|
498
|
+
* or `mutation(...)` handler body — the `nondeterministic_query_mutation` lint
|
|
499
|
+
* input. Structurally identical to `AdvisorNondeterministicCall` so values pass
|
|
500
|
+
* straight through to the advisor without conversion, exactly as `AuthApiCallIR`
|
|
501
|
+
* does for `AdvisorAuthApiCall`. `action(...)` handlers are never recorded —
|
|
502
|
+
* actions are the determinism escape hatch.
|
|
668
503
|
*/
|
|
669
|
-
interface
|
|
670
|
-
/**
|
|
504
|
+
interface NondeterministicCallIR {
|
|
505
|
+
/** The non-deterministic API invoked, e.g. `Date.now` / `Math.random` / `crypto.randomUUID` / `fetch`. */
|
|
506
|
+
callee: string;
|
|
507
|
+
/** Export binding name of the function performing the call, e.g. `sendMessage`. */
|
|
671
508
|
exportName: string;
|
|
509
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
510
|
+
file: string;
|
|
511
|
+
/** Which procedure kind the call lives in — only `query`/`mutation` handlers are recorded. */
|
|
512
|
+
kind: "mutation" | "query";
|
|
513
|
+
/** 1-based line of the call, or `0` when unknown. */
|
|
514
|
+
line: number;
|
|
672
515
|
}
|
|
673
516
|
/**
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
* `
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
*
|
|
517
|
+
* One `ctx.<property>` access lexically inside a `query`/`mutation` handler —
|
|
518
|
+
* the feeder shape behind every "action-only surface used outside an action"
|
|
519
|
+
* advisor lint (`hyperdrive_outside_action` for `ctx.sql`,
|
|
520
|
+
* `r2sql_outside_action` for `ctx.r2sql`). Structurally identical to the
|
|
521
|
+
* advisor's `AdvisorHyperdriveCall` / `AdvisorR2sqlCall` (same field set) so
|
|
522
|
+
* values pass straight through `lintSchema` without conversion, exactly as
|
|
523
|
+
* {@link NondeterministicCallIR} does. Only `query`/`mutation` handlers are
|
|
524
|
+
* recorded; `action(...)` is the only context where these surfaces are even
|
|
525
|
+
* typed, so it is the intended home and is skipped.
|
|
680
526
|
*/
|
|
681
|
-
interface
|
|
682
|
-
/**
|
|
527
|
+
interface ContextPropertyCallIR {
|
|
528
|
+
/** The accessed surface, e.g. `ctx.sql.query` / `ctx.r2sql.from` — the property, suffixed with the method when one is called on it. */
|
|
529
|
+
callee: string;
|
|
530
|
+
/** Export binding name of the function performing the access. */
|
|
683
531
|
exportName: string;
|
|
532
|
+
/** Source file relative to the lunora dir, without extension (the api namespace). */
|
|
533
|
+
file: string;
|
|
534
|
+
/** Which procedure kind the access lives in — only `query`/`mutation` handlers are recorded. */
|
|
535
|
+
kind: "mutation" | "query";
|
|
536
|
+
/** 1-based line of the access. */
|
|
537
|
+
line: number;
|
|
684
538
|
}
|
|
685
539
|
/**
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
540
|
+
* One `ctx.kv.<method>(key, …)` call whose namespace key is derived from the
|
|
541
|
+
* handler's `args` with no server-side scoping — the `kv_unscoped_user_key_idor`
|
|
542
|
+
* lint input. Workers KV is a single flat namespace, so a key taken straight from
|
|
543
|
+
* request input lets any caller read, overwrite, or delete another user's entry
|
|
544
|
+
* (IDOR). Only arg-derived, unscoped keys reach here; a fixed literal, or a key
|
|
545
|
+
* prefixed with a server-trusted identity (`${ctx.auth.userId}:…` — references
|
|
546
|
+
* `ctx`, so treated as scoped), is not recorded. `list` is excluded (it takes a
|
|
547
|
+
* prefix, not a per-entry key). Structurally identical to `AdvisorKvKeyAccess`.
|
|
693
548
|
*/
|
|
694
|
-
interface
|
|
695
|
-
/**
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
* `defineMutator` infers instead of restating. `{}` for a parameterless
|
|
699
|
-
* mutator (or one whose `args` isn't an inline object literal).
|
|
700
|
-
*/
|
|
701
|
-
args: Record<string, ValidatorIR>;
|
|
702
|
-
/** Export binding name — the mutator's registry key and import member. */
|
|
703
|
-
exportName: string;
|
|
704
|
-
/** Path relative to `<projectRoot>/lunora/` without extension — always `"mutators"`. */
|
|
705
|
-
filePath: string;
|
|
706
|
-
/** 1-based line of the `defineMutator(...)` call, for diagnostics and advisor findings. */
|
|
549
|
+
interface KvKeyAccessIR {
|
|
550
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
551
|
+
file: string;
|
|
552
|
+
/** 1-based line of the `ctx.kv` call, or `0` when unknown. */
|
|
707
553
|
line: number;
|
|
554
|
+
/** The `ctx.kv` method invoked: `get` / `getRaw` / `getWithMetadata` / `put` / `delete`. */
|
|
555
|
+
method: string;
|
|
556
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
557
|
+
scope: CallSiteScope;
|
|
708
558
|
/**
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*/
|
|
715
|
-
owner?: string;
|
|
716
|
-
/**
|
|
717
|
-
* Serialized TS source for the authoritative `server` impl's return type,
|
|
718
|
-
* `Promise<T>` unwrapped. `"unknown"` when ts-morph can't resolve it — same
|
|
719
|
-
* contract as {@link FunctionIR.returnType}.
|
|
559
|
+
* Visibility of the enclosing procedure. `internal` procedures have no
|
|
560
|
+
* untrusted caller by construction — see `owner_field_from_args_not_auth`'s
|
|
561
|
+
* identical split — so `kv_unscoped_user_key_idor` drops the finding to
|
|
562
|
+
* INFO rather than ERROR there. `undefined` when the access sits outside any
|
|
563
|
+
* registered procedure the feeder could attribute it to.
|
|
720
564
|
*/
|
|
721
|
-
|
|
565
|
+
visibility?: "internal" | "public";
|
|
722
566
|
}
|
|
723
567
|
/**
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
* `
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
* to
|
|
731
|
-
*
|
|
568
|
+
* One `ctx.db` write (`insert` / `replace` / `patch` / `insertManyUnsafe`) that sets
|
|
569
|
+
* an ownership / identity column — `userId`, `ownerId`, `tenantId`, and the like —
|
|
570
|
+
* from the handler's `args` instead of the server-trusted identity. The
|
|
571
|
+
* `owner_field_from_args_not_auth` lint input: the ownership column decides who a
|
|
572
|
+
* row belongs to, so a value taken from request input lets any caller write rows
|
|
573
|
+
* owned by another user or tenant (the act-as-any-user / cross-tenant IDOR vector).
|
|
574
|
+
* A column stamped from `ctx.*`, or set to a fixed literal, is not recorded; only an
|
|
575
|
+
* arg-derived identity write reaches here. Structurally identical to
|
|
576
|
+
* `AdvisorOwnerFieldWrite`.
|
|
732
577
|
*/
|
|
733
|
-
|
|
734
|
-
|
|
578
|
+
/**
|
|
579
|
+
* One branching `defineShape({ where })` / `definePolicy({ when })` predicate arm
|
|
580
|
+
* that returns an unrestricted predicate — the `unrestricted_where_branch` lint
|
|
581
|
+
* input. A denial arm must match NO rows (`deny()` / `{ OR: [] }`); `{}` matches
|
|
582
|
+
* every row, so the near-miss silently replicates the whole table.
|
|
583
|
+
*/
|
|
584
|
+
interface UnrestrictedWhereBranchIR {
|
|
585
|
+
/** Export binding name of the shape / policy the predicate belongs to. */
|
|
735
586
|
exportName: string;
|
|
736
|
-
/**
|
|
587
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
737
588
|
file: string;
|
|
738
|
-
/**
|
|
589
|
+
/** Which unrestricted form was returned. */
|
|
590
|
+
form: "empty-object" | "undefined";
|
|
591
|
+
/** The config key carrying the predicate (`where` for a shape, `when` for a policy). */
|
|
592
|
+
key: string;
|
|
593
|
+
/** 1-based line of the offending returned expression. */
|
|
739
594
|
line: number;
|
|
595
|
+
/** The declaring call (`defineShape` / `definePolicy`). */
|
|
596
|
+
owner: string;
|
|
740
597
|
}
|
|
741
|
-
|
|
742
|
-
* A single cron job lifted from a `cronJobs()` builder in `lunora/crons.ts`.
|
|
743
|
-
* Mirrors `@lunora/scheduler`'s `CronJob`: {@link CronJobIR.cron} is the compiled
|
|
744
|
-
* standard cron expression, {@link CronJobIR.functionPath} is the target
|
|
745
|
-
* `__lunoraRef` (`namespace:fn`), and {@link CronJobIR.args} is the static
|
|
746
|
-
* argument object passed at registration.
|
|
747
|
-
*/
|
|
748
|
-
interface CronJobIR {
|
|
749
|
-
/** Static args object (source-text JSON), defaults to `{}`. */
|
|
750
|
-
args: Record<string, unknown>;
|
|
751
|
-
/** Compiled standard cron expression, e.g. `"0 9 * * *"`. */
|
|
752
|
-
cron: string;
|
|
753
|
-
/** Target function ref `namespace:fn`. Present for a function target; absent when {@link CronJobIR.workflow} is set. */
|
|
754
|
-
functionPath?: string;
|
|
755
|
-
/** Unique, human-readable job name. */
|
|
756
|
-
name: string;
|
|
598
|
+
interface OwnerFieldWriteIR {
|
|
757
599
|
/**
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
* `ctx.exports` key) plus its export name. On each fire the worker starts a new workflow
|
|
761
|
-
* INSTANCE (the {@link CronJobIR.args} become its `params`) rather than
|
|
762
|
-
* dispatching a one-shot function.
|
|
600
|
+
* Every procedure that reaches the write carries `.use(platformAdmin(...))`
|
|
601
|
+
* (see {@link FunctionIR.adminOnly}): the caller is a platform admin by design.
|
|
763
602
|
*/
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
/** A normalized container image source, as written into `wrangler.jsonc`. */
|
|
770
|
-
type ContainerImageIR = {
|
|
771
|
-
buildContext: string;
|
|
772
|
-
dockerfilePath: string;
|
|
773
|
-
kind: "dockerfile";
|
|
774
|
-
} | {
|
|
775
|
-
buildDir: string;
|
|
776
|
-
kind: "build";
|
|
777
|
-
} | {
|
|
778
|
-
kind: "registry";
|
|
779
|
-
reference: string;
|
|
780
|
-
};
|
|
781
|
-
/** A `durable_object` container's named image — a Dockerfile or a registry digest, never a Railpack build. */
|
|
782
|
-
type ContainerNamedImageIR = Exclude<ContainerImageIR, {
|
|
783
|
-
kind: "build";
|
|
784
|
-
}>;
|
|
785
|
-
/** The fields every container carries, whichever scheduling policy it uses. */
|
|
786
|
-
interface ContainerIRBase {
|
|
787
|
-
/** Durable Object binding name, e.g. `CONTAINER_TRANSCODER`. */
|
|
788
|
-
bindingName: string;
|
|
789
|
-
/** Static Dockerfile build args (wrangler `image_vars` / named-image `build_vars`), when declared as literals. */
|
|
790
|
-
buildArgs?: Record<string, string>;
|
|
791
|
-
/** Generated DO class name, e.g. `TranscoderContainer`. */
|
|
792
|
-
className: string;
|
|
603
|
+
adminOnly?: true;
|
|
604
|
+
/** The identity column being written from `args` (e.g. `userId`). */
|
|
605
|
+
field: string;
|
|
606
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
607
|
+
file: string;
|
|
793
608
|
/**
|
|
794
|
-
*
|
|
795
|
-
*
|
|
796
|
-
*
|
|
609
|
+
* The handler proves the value it writes equals the server identity before the
|
|
610
|
+
* write: an `if (<args.field> !== <ctx identity>) throw` (also inside an `&&` or
|
|
611
|
+
* `||` condition), or an `assert*(...)` call given both the argument and the
|
|
612
|
+
* identity. The write is then validated, so `owner_field_from_args_not_auth`
|
|
613
|
+
* declines to report it. Only facts that hold before the write count.
|
|
797
614
|
*/
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
|
|
803
|
-
diskMb?: number;
|
|
804
|
-
memoryMib?: number;
|
|
805
|
-
vcpu?: number;
|
|
806
|
-
};
|
|
807
|
-
/** Static wrangler `containers[].name` override, when declared. */
|
|
808
|
-
name?: string;
|
|
615
|
+
guarded?: true;
|
|
616
|
+
/** 1-based line of the `ctx.db` write call, or `0` when unknown. */
|
|
617
|
+
line: number;
|
|
618
|
+
/** The `ctx.db` write method (`insert` / `replace` / `patch` / `insertManyUnsafe`). */
|
|
619
|
+
method: string;
|
|
809
620
|
/**
|
|
810
|
-
* `
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
621
|
+
* The enclosing `defineMutator` declared this very column as its `owner`, AND
|
|
622
|
+
* the value written resolves, by symbol, to that same `args[owner]` of the
|
|
623
|
+
* `server` impl's own 2nd parameter. A nested closure's or a helper's own
|
|
624
|
+
* `args` (or any binding shadowing it) never qualifies.
|
|
625
|
+
*
|
|
626
|
+
* `applyOwnerScope` requires a verified identity, rejects a client-supplied
|
|
627
|
+
* value that disagrees with it, and overwrites the column with the verified
|
|
628
|
+
* one before `server` runs — so on this exact shape `args[owner]` IS the
|
|
629
|
+
* server identity, and it is what the docs prescribe. Recorded rather than
|
|
630
|
+
* dropped at discovery: the write did happen, and the feeder is otherwise the
|
|
631
|
+
* only place that knows. `owner_field_from_args_not_auth` is what declines to
|
|
632
|
+
* report it.
|
|
633
|
+
*
|
|
634
|
+
* Deliberately NOT set when only the column NAME matches: `owner: "userId"`
|
|
635
|
+
* launders `args.userId` and nothing else, so `{ userId: args.targetUserId }`
|
|
636
|
+
* is a real IDOR and stays reportable.
|
|
814
637
|
*/
|
|
815
|
-
|
|
638
|
+
ownerScoped?: true;
|
|
639
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
640
|
+
scope: CallSiteScope;
|
|
816
641
|
/**
|
|
817
|
-
*
|
|
818
|
-
*
|
|
642
|
+
* Visibility of the enclosing procedure. `internal` procedures are not
|
|
643
|
+
* reachable by a caller, so the lint's premise ("any caller can write rows
|
|
644
|
+
* owned by another user") does not hold there — see
|
|
645
|
+
* `owner_field_from_args_not_auth`. `undefined` when the write sits outside
|
|
646
|
+
* any recognised procedure (a bare helper).
|
|
819
647
|
*/
|
|
820
|
-
|
|
648
|
+
visibility?: "internal" | "public";
|
|
821
649
|
}
|
|
822
|
-
/**
|
|
823
|
-
|
|
650
|
+
/**
|
|
651
|
+
* One `ctx.storage.<bucket>.<method>(key, …)` call whose R2 object key is derived
|
|
652
|
+
* from the handler's `args` with no server-side scoping — the
|
|
653
|
+
* `storage_key_from_user_args` lint input. The bucket read/write/URL/delete methods
|
|
654
|
+
* key by their first argument, so an object key taken straight from request input is
|
|
655
|
+
* object-level IDOR (read/overwrite/delete anyone's object). A key referencing a
|
|
656
|
+
* server-trusted `ctx.*` value (e.g. `${ctx.auth.userId}/…`) is treated as scoped
|
|
657
|
+
* and is not recorded; only an arg-derived, `ctx`-free key reaches here.
|
|
658
|
+
* Structurally identical to `AdvisorStorageKeyAccess`.
|
|
659
|
+
*/
|
|
660
|
+
interface StorageKeyAccessIR {
|
|
661
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
662
|
+
file: string;
|
|
663
|
+
/** 1-based line of the storage call, or `0` when unknown. */
|
|
664
|
+
line: number;
|
|
665
|
+
/** The bucket method invoked with the arg-derived key, e.g. `get` / `put` / `delete` / `download`. */
|
|
666
|
+
method: string;
|
|
667
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
668
|
+
scope: CallSiteScope;
|
|
824
669
|
/**
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
670
|
+
* Visibility of the enclosing procedure. `internal` procedures have no
|
|
671
|
+
* untrusted caller by construction — see `owner_field_from_args_not_auth`'s
|
|
672
|
+
* identical split — so `storage_key_from_user_args` drops the finding to
|
|
673
|
+
* INFO rather than ERROR there. `undefined` when the access sits outside any
|
|
674
|
+
* registered procedure the feeder could attribute it to.
|
|
828
675
|
*/
|
|
829
|
-
|
|
830
|
-
/** Static `maxInstances`, when declared. */
|
|
831
|
-
maxInstances?: number;
|
|
832
|
-
/** Static rolling-deploy tuning, when declared as literals. */
|
|
833
|
-
rollout?: {
|
|
834
|
-
gracePeriodSeconds?: number;
|
|
835
|
-
stepPercentage?: number;
|
|
836
|
-
};
|
|
837
|
-
/** Absent under the default policy — the union's discriminant. */
|
|
838
|
-
schedulingPolicy?: never;
|
|
676
|
+
visibility?: "internal" | "public";
|
|
839
677
|
}
|
|
840
678
|
/**
|
|
841
|
-
*
|
|
842
|
-
* `
|
|
843
|
-
*
|
|
679
|
+
* One `ctx.ai.run(model, …)` call whose model-id argument is derived from the handler's
|
|
680
|
+
* `args` with no server-side scoping — the `ai_raw_run_escape_hatch` lint input.
|
|
681
|
+
* `ctx.ai.run` is the raw Workers AI binding passthrough, bypassing the typed
|
|
682
|
+
* `ctx.ai.model(...)` + AI-SDK layer (`generateText`/`streamText`/…) that caps output and
|
|
683
|
+
* enforces a schema, so an arg-derived model id lets any caller select an arbitrary model.
|
|
684
|
+
* A fixed literal model, or one scoped by a server-trusted `ctx.*` value, is not recorded;
|
|
685
|
+
* only an arg-derived, unscoped model id reaches here (an arg-derived `inputs` argument is
|
|
686
|
+
* normal usage and is never inspected). Structurally identical to `AdvisorAiRawRun`.
|
|
844
687
|
*/
|
|
845
|
-
interface
|
|
846
|
-
/**
|
|
847
|
-
|
|
848
|
-
|
|
688
|
+
interface AiRawRunIR {
|
|
689
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
690
|
+
file: string;
|
|
691
|
+
/** 1-based line of the `ctx.ai.run` call, or `0` when unknown. */
|
|
692
|
+
line: number;
|
|
693
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
694
|
+
scope: CallSiteScope;
|
|
849
695
|
}
|
|
850
696
|
/**
|
|
851
|
-
*
|
|
852
|
-
* `
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
697
|
+
* One `ctx.vectors.<method>(indexName, input)` call whose `input.namespace` is derived
|
|
698
|
+
* from the handler's `args` with no server-side scoping — the
|
|
699
|
+
* `vectors_namespace_from_user_input` lint input. A Vectorize namespace partitions one
|
|
700
|
+
* index into isolated sub-collections, so a namespace taken straight from request input
|
|
701
|
+
* lets any caller read or poison another tenant's vectors. A fixed literal namespace, or
|
|
702
|
+
* one prefixed with a server-trusted identity (`${ctx.auth.orgId}` — references `ctx`, so
|
|
703
|
+
* treated as scoped), is not recorded; only an arg-derived, unscoped namespace reaches
|
|
704
|
+
* here. Structurally identical to `AdvisorVectorNamespaceAccess`.
|
|
858
705
|
*/
|
|
859
|
-
|
|
706
|
+
interface VectorNamespaceAccessIR {
|
|
707
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
708
|
+
file: string;
|
|
709
|
+
/** 1-based line of the `ctx.vectors` call, or `0` when unknown. */
|
|
710
|
+
line: number;
|
|
711
|
+
/** The `ctx.vectors` method invoked: `query` / `upsert` / `upsertNow`. */
|
|
712
|
+
method: string;
|
|
713
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
714
|
+
scope: CallSiteScope;
|
|
715
|
+
}
|
|
860
716
|
/**
|
|
861
|
-
*
|
|
862
|
-
*
|
|
863
|
-
* `
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
717
|
+
* One `ctx.mail`/`ctx.email` `send`/`queue` call whose recipient field (`to`/`cc`/`bcc`)
|
|
718
|
+
* is derived from the handler's `args` with no server-side scoping — the
|
|
719
|
+
* `mail_recipient_from_request_input` lint input. A recipient taken straight from request
|
|
720
|
+
* input turns the deployment into an open relay / spam amplifier (any caller can direct
|
|
721
|
+
* mail to an arbitrary address). A fixed literal recipient, or one scoped by a
|
|
722
|
+
* server-trusted `ctx.*` value (e.g. `ctx.auth.user.email`), is not recorded; only an
|
|
723
|
+
* arg-derived, unscoped recipient reaches here. Structurally identical to
|
|
724
|
+
* `AdvisorMailRecipientAccess`.
|
|
868
725
|
*/
|
|
869
|
-
interface
|
|
870
|
-
/**
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
exportName: string;
|
|
879
|
-
/**
|
|
880
|
-
* The file declaring the `handler:` when it is a function passed by reference
|
|
881
|
-
* and declared in another file — the file {@link WorkflowIR.steps}' lines are
|
|
882
|
-
* in. Lunora-relative inside `lunora/`, project-relative outside it.
|
|
883
|
-
*/
|
|
884
|
-
handlerFile?: string;
|
|
885
|
-
/** See {@link HandlerSiteIR}. */
|
|
886
|
-
handlerSite?: HandlerSiteIR;
|
|
887
|
-
/** Static `limits` literal → `exports.<Class>.limits`. */
|
|
888
|
-
limits?: {
|
|
889
|
-
steps?: number;
|
|
890
|
-
};
|
|
891
|
-
/**
|
|
892
|
-
* The stable wrangler `exports.<Class>.name`. Defaults to the kebab-cased export
|
|
893
|
-
* name (`orderPipeline` → `order-pipeline`); a static `name:` literal in the
|
|
894
|
-
* definition overrides it.
|
|
895
|
-
*/
|
|
896
|
-
name: string;
|
|
897
|
-
/**
|
|
898
|
-
* Static `schedules` cron literals → `exports.<Class>.schedules`. Each one
|
|
899
|
-
* starts an instance on Cloudflare; gated by the `workflowSchedules`
|
|
900
|
-
* capability, since a host that ignores them would never run the workflow.
|
|
901
|
-
*/
|
|
902
|
-
schedules?: ReadonlyArray<string>;
|
|
903
|
-
/**
|
|
904
|
-
* Durable step labels lifted from the handler body — the first string-literal
|
|
905
|
-
* argument of every `ctx.step.do` / `.sleep` / `.sleepUntil` / `.waitForEvent`
|
|
906
|
-
* call. Feeds the duplicate-step-name lint, which flags a name used twice
|
|
907
|
-
* (Cloudflare memoizes by name, so the second call silently returns the
|
|
908
|
-
* first's cached result). Calls with a non-literal name are omitted (not
|
|
909
|
-
* statically comparable). A handler passed by reference is followed to its
|
|
910
|
-
* declaration; the lines are then in {@link WorkflowIR.handlerFile}.
|
|
911
|
-
*/
|
|
912
|
-
steps: ReadonlyArray<WorkflowStepIR>;
|
|
726
|
+
interface MailRecipientAccessIR {
|
|
727
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
728
|
+
file: string;
|
|
729
|
+
/** 1-based line of the `ctx.mail`/`ctx.email` call, or `0` when unknown. */
|
|
730
|
+
line: number;
|
|
731
|
+
/** The mailer method invoked: `send` / `queue`. */
|
|
732
|
+
method: string;
|
|
733
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
734
|
+
scope: CallSiteScope;
|
|
913
735
|
}
|
|
914
736
|
/**
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
*
|
|
918
|
-
*
|
|
919
|
-
*
|
|
920
|
-
*
|
|
921
|
-
* per-agent `ctx.agents` producer, and the reconciled wrangler `workflows[]`
|
|
922
|
-
* entry. Names are derived via `@lunora/agent`'s shared helpers so codegen and
|
|
923
|
-
* the config layer can never disagree.
|
|
737
|
+
* One `ctx.browser.<method>(url, …)` call whose navigation URL (`arguments[0]`)
|
|
738
|
+
* is derived from the handler's `args` with no server-side scoping — the
|
|
739
|
+
* `browser_user_url_without_allowlist` lint input. The lint additionally
|
|
740
|
+
* cross-references `createBrowser` config-call evidence to suppress findings
|
|
741
|
+
* when the browser is hardened with an `allowedHosts` allowlist or
|
|
742
|
+
* `resolveDns`. Structurally identical to `AdvisorBrowserUrlAccess`.
|
|
924
743
|
*/
|
|
925
|
-
interface
|
|
926
|
-
/**
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
exportName: string;
|
|
930
|
-
/**
|
|
931
|
-
* The stable wrangler `exports.<Class>.name`. Defaults to the kebab-cased export
|
|
932
|
-
* name (`support` → `agent-support`); a static `name:` literal in the
|
|
933
|
-
* definition overrides it.
|
|
934
|
-
*/
|
|
935
|
-
name: string;
|
|
936
|
-
/**
|
|
937
|
-
* Whether the definition declares an `onEmail` mapper on
|
|
938
|
-
* `defineAgent({ onEmail: … })`. When `true` the emitter wires this agent
|
|
939
|
-
* onto the worker's top-level `email()` handler (via `@lunora/agent/inbound`)
|
|
940
|
-
* so inbound mail starts a durable run. Detected by AST PRESENCE — the
|
|
941
|
-
* closure is never evaluated — and written to IR only when present, so
|
|
942
|
-
* email-free agents (and agent-free projects) stay byte-identical.
|
|
943
|
-
*/
|
|
944
|
-
onEmail?: boolean;
|
|
945
|
-
/**
|
|
946
|
-
* Whether the definition opted into public run-starts via
|
|
947
|
-
* `defineAgent({ publicRun: true })` — emitted into the `ctx.agents` wiring
|
|
948
|
-
* spec so the public `agents:agentRun` mutation can gate on it fail-closed.
|
|
949
|
-
* Absent (falsy) means server-side starts only; the field is written to IR
|
|
950
|
-
* only when the literal is `true`, so agent-free and non-opted-in output is
|
|
951
|
-
* byte-identical.
|
|
952
|
-
*/
|
|
953
|
-
publicRun?: boolean;
|
|
954
|
-
/**
|
|
955
|
-
* Whether the definition opted into a real-time voice session via a `voice`
|
|
956
|
-
* block on `defineAgent({ voice: … })`. Unlike the durable loop (a Workflow),
|
|
957
|
-
* the voice path IS a Durable Object — so when this is `true` the emitter
|
|
958
|
-
* generates the `voiceClassName` `VoiceSessionDO` subclass and the
|
|
959
|
-
* `api.agents.{name}Voice` client reference, and the config layer reconciles
|
|
960
|
-
* a `durable_objects` binding (`voiceBindingName`) + `new_sqlite_classes`
|
|
961
|
-
* migration. Written to IR only when the literal is present, so voice-free
|
|
962
|
-
* agents (and agent-free projects) stay byte-identical.
|
|
963
|
-
*/
|
|
964
|
-
voice?: boolean;
|
|
965
|
-
/** The voice DO's Cloudflare `DurableObjectNamespace` binding name, e.g. `VOICE_SUPPORT`. Present only when `voice`. */
|
|
966
|
-
voiceBindingName?: string;
|
|
967
|
-
/** Generated `VoiceSessionDO` subclass name, e.g. `SupportVoiceDO`. Present only when `voice`. */
|
|
968
|
-
voiceClassName?: string;
|
|
969
|
-
}
|
|
970
|
-
/** One durable step call lifted from a workflow handler body (the use side of {@link WorkflowIR.steps}). */
|
|
971
|
-
interface WorkflowStepIR {
|
|
972
|
-
/** 1-based line of the durable step call. */
|
|
744
|
+
interface BrowserUrlAccessIR {
|
|
745
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
746
|
+
file: string;
|
|
747
|
+
/** 1-based line of the `ctx.browser` call, or `0` when unknown. */
|
|
973
748
|
line: number;
|
|
974
|
-
/** The
|
|
749
|
+
/** The browser method invoked: `content` / `pdf` / `scrape` / `screenshot`. */
|
|
975
750
|
method: string;
|
|
976
|
-
/**
|
|
977
|
-
|
|
751
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
752
|
+
scope: CallSiteScope;
|
|
978
753
|
}
|
|
979
754
|
/**
|
|
980
|
-
*
|
|
981
|
-
*
|
|
982
|
-
*
|
|
983
|
-
* `
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
/** The Cloudflare `Queue` producer binding name, e.g. `QUEUE_EMAIL`. */
|
|
990
|
-
bindingName: string;
|
|
991
|
-
/** The `lunora/queues.ts` export name, e.g. `emailQueue`. */
|
|
992
|
-
exportName: string;
|
|
993
|
-
/** The declaring file, lunora-relative without `.ts`: `queues`, or `billing/queues` for a module's own. */
|
|
994
|
-
filePath: string;
|
|
995
|
-
/** See {@link HandlerSiteIR}. */
|
|
996
|
-
handlerSite?: HandlerSiteIR;
|
|
997
|
-
/** How the queue is consumed: `"push"` (a worker `queue()` handler) or `"pull"` (external HTTP). */
|
|
998
|
-
mode: "pull" | "push";
|
|
999
|
-
/**
|
|
1000
|
-
* The stable wrangler queue name (`queues.producers[].queue`). Defaults to
|
|
1001
|
-
* the kebab-cased export name (`emailQueue` → `email-queue`); a static
|
|
1002
|
-
* `name:` literal in the definition overrides it.
|
|
1003
|
-
*/
|
|
1004
|
-
name: string;
|
|
1005
|
-
/**
|
|
1006
|
-
* Set for a `defineSubscription(topic, …)` export: the `defineTopic` export it
|
|
1007
|
-
* consumes. A subscription is an ordinary push queue everywhere except
|
|
1008
|
-
* `ctx.*` — it is published to through `ctx.topics.<topic>`, never `ctx.queues`.
|
|
1009
|
-
*/
|
|
1010
|
-
topic?: string;
|
|
1011
|
-
/** Push-consumer batch/retry tuning, mirrored onto the wrangler `queues.consumers[]` entry. */
|
|
1012
|
-
tuning: {
|
|
1013
|
-
deadLetterQueue?: string;
|
|
1014
|
-
maxBatchSize?: number;
|
|
1015
|
-
maxBatchTimeout?: number;
|
|
1016
|
-
maxConcurrency?: number;
|
|
1017
|
-
maxRetries?: number;
|
|
1018
|
-
retryDelay?: number;
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
/**
|
|
1022
|
-
* A `defineTopic()` export in `lunora/queues.ts`. A topic deploys nothing of its
|
|
1023
|
-
* own: each subscription is a {@link QueueIR} whose `topic` names this export, and
|
|
1024
|
-
* that is the only record of the link (see {@link subscriptionsOf}).
|
|
755
|
+
* One payload-derived privileged dispatch — a `ctx.run`/`context.run` back into a
|
|
756
|
+
* Lunora function from inside a `defineQueue` push handler or a `defineWorkflow`
|
|
757
|
+
* handler, whose args reference the handler's untrusted payload (`context.params`
|
|
758
|
+
* for a workflow, a `for (… of batch.messages)` body for a queue) — the
|
|
759
|
+
* `privileged_dispatch_unvalidated_payload` lint input. Both handler kinds run
|
|
760
|
+
* under the **system identity** (RLS disabled), so forwarding attacker-influenced
|
|
761
|
+
* payload into the dispatch bypasses the target's row policy. The resolved
|
|
762
|
+
* `targetFile`/`targetExport` let the lint join RLS-procedure evidence and fire
|
|
763
|
+
* only for RLS-gated targets. Structurally identical to `AdvisorPrivilegedDispatch`.
|
|
1025
764
|
*/
|
|
1026
|
-
interface
|
|
1027
|
-
/**
|
|
1028
|
-
|
|
1029
|
-
/**
|
|
1030
|
-
|
|
765
|
+
interface PrivilegedDispatchIR {
|
|
766
|
+
/** `"queue"` for a `defineQueue` handler, `"workflow"` for a `defineWorkflow` handler. */
|
|
767
|
+
dispatchKind: "queue" | "workflow";
|
|
768
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
769
|
+
file: string;
|
|
770
|
+
/** 1-based line of the dispatch call, or `0` when unknown. */
|
|
771
|
+
line: number;
|
|
772
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
773
|
+
scope: CallSiteScope;
|
|
774
|
+
/** Export name of the dispatched target (`send` in `api.messages.send`). */
|
|
775
|
+
targetExport: string;
|
|
776
|
+
/** File path of the dispatched target relative to `lunora/` (`messages` in `api.messages.send`). */
|
|
777
|
+
targetFile: string;
|
|
1031
778
|
}
|
|
1032
779
|
/**
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
780
|
+
* One tracked `ctx.storage.<bucket>.<method>(...)` upload/signing call — the
|
|
781
|
+
* shared input for the storage config-hygiene security lints
|
|
782
|
+
* (`storage_upload_without_content_type_allowlist`, `storage_upload_without_max_size`,
|
|
783
|
+
* `storage_generate_upload_url_no_content_type_pin`, `storage_presigned_url_for_private_content`).
|
|
784
|
+
* `upload`/`store` carry the `UploadOptions` guards (`allowedContentTypes` /
|
|
785
|
+
* `maxSize`); `generateUploadUrl` carries the signed-PUT `contentType` pin;
|
|
786
|
+
* `getPresignedUrl`/`getSignedUrl` carry a statically-known `expiresInSeconds`
|
|
787
|
+
* literal. `presentKeys` is empty (and `expiresInSeconds` unset) when the
|
|
788
|
+
* options argument was absent, a non-literal, or a spread — see `analyzable`.
|
|
789
|
+
* Structurally identical to `AdvisorStorageUpload`.
|
|
1036
790
|
*/
|
|
1037
|
-
interface
|
|
1038
|
-
/**
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
/** The
|
|
1047
|
-
|
|
1048
|
-
/**
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
*/
|
|
1053
|
-
ownsFolder?: false;
|
|
1054
|
-
/** Tables the module declares it owns (`defineModule({ tables })`); empty when it declares none. */
|
|
1055
|
-
tables: ReadonlyArray<string>;
|
|
791
|
+
interface StorageUploadIR {
|
|
792
|
+
/** `true` when the call's options-object argument (or its deliberate absence) was statically resolvable. */
|
|
793
|
+
analyzable: boolean;
|
|
794
|
+
/** Numeric literal value of an `expiresInSeconds` option, when statically known (`getSignedUrl` / `getPresignedUrl` only). */
|
|
795
|
+
expiresInSeconds?: number;
|
|
796
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
797
|
+
file: string;
|
|
798
|
+
/** 1-based line of the call, or `0` when unknown. */
|
|
799
|
+
line: number;
|
|
800
|
+
/** The `ctx.storage` method invoked. */
|
|
801
|
+
method: "generateUploadUrl" | "getPresignedUrl" | "getSignedUrl" | "store" | "upload";
|
|
802
|
+
/** Options-object keys present at the call site (empty when not `analyzable`, or when no options argument was passed). */
|
|
803
|
+
presentKeys: string[];
|
|
804
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
805
|
+
scope: CallSiteScope;
|
|
1056
806
|
}
|
|
1057
807
|
/**
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
* `
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
* references (a call, or the helper passed on as a value), transitively and
|
|
1068
|
-
* sorted; a type-only reference (`typeof helper`) does not count. Empty for an
|
|
1069
|
-
* orphan helper no export reaches. `untracked` is set when code attribution
|
|
1070
|
-
* cannot follow — module scope, a class body, a destructured declaration — also
|
|
1071
|
-
* reaches the helper, so `callers` is incomplete.
|
|
1072
|
-
*
|
|
1073
|
-
* `module`: outside every top-level `function` / `const` — module scope, or a
|
|
1074
|
-
* class body.
|
|
1075
|
-
*
|
|
1076
|
-
* A site is recorded once; consumers fan out over its callers. The advisor's
|
|
1077
|
-
* `AdvisorCallSiteScope` is the same type, so feeder rows pass straight through.
|
|
808
|
+
* One discovered `httpAction`/`httpRoute` handler in `lunora/` that performs a
|
|
809
|
+
* side effect (`ctx.runMutation` / `ctx.runAction` / a `ctx.db.{insert,patch,
|
|
810
|
+
* replace,delete,insertManyUnsafe}` write) from the HTTP edge, with whether it
|
|
811
|
+
* reads `ctx.auth` — the `http_action_missing_auth_guard` lint input. A handler
|
|
812
|
+
* that mutates state or dispatches an action without ever consulting the request
|
|
813
|
+
* identity is an unauthenticated write bypassing identity/RLS. Only handlers with
|
|
814
|
+
* a statically-resolvable inline body and `ctx` binding are recorded (fail-safe
|
|
815
|
+
* under-report); read-only handlers are never recorded. Structurally identical to
|
|
816
|
+
* `AdvisorHttpActionGuard`.
|
|
1078
817
|
*/
|
|
1079
|
-
|
|
818
|
+
interface HttpActionGuardIR {
|
|
819
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
820
|
+
file: string;
|
|
821
|
+
/** Which HTTP surface the handler is: a raw `httpAction` or a typed `httpRoute` route. */
|
|
822
|
+
kind: "httpAction" | "httpRoute";
|
|
823
|
+
/** 1-based line of the handler call, or `0` when unknown. */
|
|
824
|
+
line: number;
|
|
825
|
+
/** For an `httpRoute`, the uppercased verb (`"POST"`); absent for a raw `httpAction`. */
|
|
826
|
+
method?: string;
|
|
827
|
+
/** `true` when the handler reads `ctx.auth` (a direct member access or a `const { auth } = ctx` destructure). */
|
|
828
|
+
readsAuth: boolean;
|
|
829
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
830
|
+
scope: CallSiteScope;
|
|
831
|
+
/** The first side effect found, as a stable label: `runMutation`, `runAction`, or `db.<method>`. */
|
|
832
|
+
sideEffect: string;
|
|
833
|
+
}
|
|
1080
834
|
/**
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1083
|
-
* `
|
|
1084
|
-
* `
|
|
1085
|
-
*
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
835
|
+
* One response-header write, inside an `httpAction` handler, whose value is derived
|
|
836
|
+
* from raw request input (`request.headers`, `request.url`/query, `await
|
|
837
|
+
* request.json()`) with no CR/LF sanitizer — the
|
|
838
|
+
* `http_action_response_header_injection` lint input. A `Request`-derived string
|
|
839
|
+
* placed verbatim into a response header lets a caller smuggle `\r\n` and inject
|
|
840
|
+
* extra headers or split the response (header injection / response splitting). Only
|
|
841
|
+
* sites whose value is request-tainted AND unguarded are recorded: a value routed
|
|
842
|
+
* through a CR/LF guard (`isSafeHeaderValue`), a URL/URI encoder
|
|
843
|
+
* (`encodeURIComponent`/`encodeURI`), a numeric coercion (`Number`/`parseInt`/
|
|
844
|
+
* `parseFloat`), or `btoa` is treated as safe and never recorded (`String(...)` /
|
|
845
|
+
* `.toString()` are NOT sanitizers — they don't strip CR/LF). Structurally
|
|
846
|
+
* identical to `AdvisorHttpHeaderWrite`.
|
|
1088
847
|
*/
|
|
1089
|
-
interface
|
|
1090
|
-
exportName: string;
|
|
848
|
+
interface HttpHeaderWriteIR {
|
|
1091
849
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1092
850
|
file: string;
|
|
851
|
+
/** The header name being written (`"location"`), or `""` when the key is not a string literal. */
|
|
852
|
+
headerName: string;
|
|
853
|
+
/** 1-based line of the request-tainted header value. */
|
|
854
|
+
line: number;
|
|
855
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
856
|
+
scope: CallSiteScope;
|
|
857
|
+
/** How the header was written. */
|
|
858
|
+
via: "headers-append" | "headers-ctor" | "headers-set" | "response-init";
|
|
1093
859
|
}
|
|
1094
860
|
/**
|
|
1095
|
-
* One
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
* `
|
|
861
|
+
* One rate-limit / Turnstile middleware call in `lunora/` — the
|
|
862
|
+
* `ratelimit_middleware_fail_open` lint input. `rateLimit`/`dbRateLimit`
|
|
863
|
+
* (`@lunora/ratelimit`) and `verifyTurnstileMiddleware` (`@lunora/auth`) each
|
|
864
|
+
* accept a `failOpen` escape hatch that admits every request when the
|
|
865
|
+
* limiter/siteverify is unavailable; `failOpen` is `true` only when the options
|
|
866
|
+
* literal set it to the boolean literal `true` (anything else is fail-closed).
|
|
867
|
+
* The lint escalates a fail-open guard to a finding when the guarded procedure
|
|
868
|
+
* (`exportName`/`limitName`) looks auth/payment-sensitive. Structurally
|
|
869
|
+
* identical to `AdvisorFailOpenGuard`.
|
|
1100
870
|
*/
|
|
1101
|
-
interface
|
|
1102
|
-
/**
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
*/
|
|
1107
|
-
conditional?: true;
|
|
871
|
+
interface FailOpenGuardIR {
|
|
872
|
+
/** The middleware factory at the call site: `rateLimit` / `dbRateLimit` / `verifyTurnstileMiddleware`. */
|
|
873
|
+
callee: string;
|
|
874
|
+
/** `true` only when the options literal set `failOpen: true` as a boolean literal; a non-literal or absent option is treated as fail-closed. */
|
|
875
|
+
failOpen: boolean;
|
|
1108
876
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1109
877
|
file: string;
|
|
1110
|
-
|
|
1111
|
-
|
|
878
|
+
/** The rate-limit `name` (second string argument) for `rateLimit`/`dbRateLimit`; `""` for `verifyTurnstileMiddleware`. */
|
|
879
|
+
limitName: string;
|
|
880
|
+
/** 1-based line of the middleware call, or `0` when unknown. */
|
|
1112
881
|
line: number;
|
|
1113
|
-
|
|
1114
|
-
/** Who the call runs on behalf of. */
|
|
882
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1115
883
|
scope: CallSiteScope;
|
|
1116
|
-
target?: string;
|
|
1117
884
|
}
|
|
1118
885
|
/**
|
|
1119
|
-
*
|
|
1120
|
-
* `
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
886
|
+
* One `ctx.flags.boolean("key", <boolean-literal>)` read in `lunora/` — the
|
|
887
|
+
* `flag_gates_security_with_unsafe_default` lint input. OpenFeature returns the
|
|
888
|
+
* `defaultValue` when the provider errors, so a fail-open default on a
|
|
889
|
+
* security-shaped key silently opens access during an outage. Only reads with a
|
|
890
|
+
* statically-known string key and boolean-literal default are recorded; the lint
|
|
891
|
+
* owns the security-shape + polarity judgment. Structurally identical to
|
|
892
|
+
* AdvisorFlagSecurityDefault.
|
|
1125
893
|
*/
|
|
1126
|
-
interface
|
|
894
|
+
interface FlagSecurityDefaultIR {
|
|
895
|
+
/** The boolean-literal default returned on a provider outage (fail-open value). */
|
|
896
|
+
defaultValue: boolean;
|
|
1127
897
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1128
898
|
file: string;
|
|
1129
|
-
/** The
|
|
1130
|
-
|
|
1131
|
-
/** 1-based line of the `
|
|
899
|
+
/** The flag key — the first string-literal argument of `ctx.flags.boolean`. */
|
|
900
|
+
key: string;
|
|
901
|
+
/** 1-based line of the `ctx.flags.boolean` call, or `0` when unknown. */
|
|
1132
902
|
line: number;
|
|
1133
|
-
/** Who the
|
|
903
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1134
904
|
scope: CallSiteScope;
|
|
1135
905
|
}
|
|
1136
906
|
/**
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1139
|
-
* a
|
|
1140
|
-
*
|
|
907
|
+
* One `ctx.flags` read lexically inside a `query(...)` handler — the
|
|
908
|
+
* flag_read_in_subscription advisor lint input. Flag changes append nothing to
|
|
909
|
+
* `__cdc_log`, so a subscribed query is never re-run when a flag flips and keeps
|
|
910
|
+
* serving the branch it last picked; `useFlag` is the reactive path. Structurally
|
|
911
|
+
* identical to the advisor's `AdvisorFlagRead` (same field set) so values pass
|
|
912
|
+
* straight through `lintSchema` without conversion, exactly as `ContextPropertyCallIR` does.
|
|
913
|
+
*
|
|
914
|
+
* Only `query` handlers are recorded — unlike `ContextPropertyCallIR` / `NondeterministicCallIR`
|
|
915
|
+
* there is no `kind` field, because `mutation`/`action` handlers run once and have
|
|
916
|
+
* no subscription staleness to warn about, so the feeder drops them rather than
|
|
917
|
+
* handing the lint rows it would discard.
|
|
1141
918
|
*/
|
|
1142
|
-
interface
|
|
1143
|
-
/** The `
|
|
1144
|
-
|
|
1145
|
-
/**
|
|
1146
|
-
|
|
1147
|
-
/**
|
|
1148
|
-
* The Worker name per `env.<name>` block the service's own wrangler config
|
|
1149
|
-
* sets a `name` in. An environment missing here deploys as wrangler's
|
|
1150
|
-
* default `<worker>-<env>`.
|
|
1151
|
-
*/
|
|
1152
|
-
envWorkers: Readonly<Record<string, string>>;
|
|
1153
|
-
/** Absolute path of the service's entry module (its wrangler `main`) — what the RPC type is read from. */
|
|
1154
|
-
main: string;
|
|
1155
|
-
/** The `ctx.services.<name>` key, e.g. `documentParser`. */
|
|
1156
|
-
name: string;
|
|
1157
|
-
/**
|
|
1158
|
-
* The scopes in which the service still serves on `*.workers.dev` with no
|
|
1159
|
-
* route of its own: `""` for the top level, else the environment name.
|
|
1160
|
-
*/
|
|
1161
|
-
publicScopes: ReadonlyArray<string>;
|
|
1162
|
-
/**
|
|
1163
|
-
* The `WorkerEntrypoint` class the service is typed — and wired — as an RPC
|
|
1164
|
-
* stub of. Equal to {@link ServiceBindingIR.entrypoint} for an RPC service;
|
|
1165
|
-
* absent for a fetch service, including a named entrypoint declared
|
|
1166
|
-
* `rpc: false`, which is called with plain `fetch` and so needs no import of
|
|
1167
|
-
* the service's sources.
|
|
1168
|
-
*/
|
|
1169
|
-
rpcEntrypoint?: string;
|
|
1170
|
-
/** The Worker's own `name` from its wrangler config — the `services[].service` target. */
|
|
1171
|
-
worker: string;
|
|
1172
|
-
/** Absolute path of the service's wrangler config. */
|
|
1173
|
-
wranglerPath: string;
|
|
1174
|
-
}
|
|
1175
|
-
/**
|
|
1176
|
-
* The feature-flag provider declared by the default export of `lunora/flags.ts`
|
|
1177
|
-
* (`defineFlags({ provider, … })`). Discovery is **metadata-only** — codegen
|
|
1178
|
-
* imports the real module at runtime for the provider value; this IR exists so
|
|
1179
|
-
* the config layer can reconcile/validate the wrangler `flagship` binding when
|
|
1180
|
-
* the app uses Flagship in binding mode. A `custom` provider (any other
|
|
1181
|
-
* OpenFeature factory) carries no binding to reconcile.
|
|
1182
|
-
*/
|
|
1183
|
-
interface FlagsIR {
|
|
1184
|
-
/**
|
|
1185
|
-
* The wrangler `flagship[].binding` name — set **only** for a flagship
|
|
1186
|
-
* `provider` in binding mode (`flagshipProvider({ binding: "FLAGS" })`). The
|
|
1187
|
-
* config layer hints/validates a matching `flagship` binding from this.
|
|
1188
|
-
*/
|
|
1189
|
-
bindingName?: string;
|
|
1190
|
-
/**
|
|
1191
|
-
* Flagship operating mode — `"binding"` (wrangler binding, needs a
|
|
1192
|
-
* `flagship` entry) or `"http"` (no binding); `undefined` for a `custom`
|
|
1193
|
-
* provider or when the mode can't be read statically.
|
|
1194
|
-
*/
|
|
1195
|
-
mode?: "binding" | "http";
|
|
1196
|
-
/** `"flagship"` when the provider is `flagshipProvider(...)`, else `"custom"` (any other OpenFeature provider factory). */
|
|
1197
|
-
provider: "custom" | "flagship";
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* A `ctx.workflows.get("name")…` call discovered in a function body — the
|
|
1201
|
-
* use-site analog of {@link WorkflowIR} (which is the declaration side). Feeds
|
|
1202
|
-
* the `workflow_unused` lint (a declared workflow with zero call sites) and the
|
|
1203
|
-
* `workflow_unknown_target` lint (a `.get("x")` whose `x` isn't declared — a
|
|
1204
|
-
* typo catcher). {@link WorkflowCallIR.workflow} is `""` when the `get(...)`
|
|
1205
|
-
* argument is not a string literal (a dynamic name — which suppresses the
|
|
1206
|
-
* unused-workflow heuristic rather than producing a false positive).
|
|
1207
|
-
*/
|
|
1208
|
-
interface WorkflowCallIR {
|
|
919
|
+
interface FlagReadIR {
|
|
920
|
+
/** The accessed `ctx.flags` surface, e.g. `ctx.flags.boolean` / `ctx.flags.details.string`. */
|
|
921
|
+
callee: string;
|
|
922
|
+
/** Export binding name of the query performing the read. */
|
|
923
|
+
exportName: string;
|
|
1209
924
|
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
1210
925
|
file: string;
|
|
1211
|
-
/** 1-based line of the `
|
|
926
|
+
/** 1-based line of the read, or `0` when unknown. */
|
|
1212
927
|
line: number;
|
|
1213
|
-
/** Who the call runs on behalf of. */
|
|
1214
|
-
scope: CallSiteScope;
|
|
1215
|
-
/** The referenced workflow export name, or `""` when the argument is not a string literal. */
|
|
1216
|
-
workflow: string;
|
|
1217
928
|
}
|
|
1218
929
|
/**
|
|
1219
|
-
*
|
|
1220
|
-
*
|
|
1221
|
-
*
|
|
1222
|
-
* `
|
|
1223
|
-
*
|
|
930
|
+
* One `generateText` / `streamText` call in `lunora/` whose `tools` reach a
|
|
931
|
+
* privileged side effect (a DB write, function dispatch, or outbound
|
|
932
|
+
* fetch/mail/queue send). `userInputDerived` records whether the model input
|
|
933
|
+
* (`prompt`/`messages`/`system`) flows from the handler's `args`; the
|
|
934
|
+
* `ai_tool_side_effect_prompt_injection` lint fires only when it does.
|
|
935
|
+
* Structurally identical to `AdvisorAiToolSideEffect`.
|
|
1224
936
|
*/
|
|
1225
|
-
interface
|
|
937
|
+
interface AiToolSideEffectIR {
|
|
1226
938
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1227
939
|
file: string;
|
|
1228
|
-
/**
|
|
1229
|
-
* True when the chain's `.filter()` predicate compares `_id`
|
|
1230
|
-
* (`(d) => d._id === args.id`) — a full scan for a row that `ctx.db.get`
|
|
1231
|
-
* addresses directly. Optional so a feeder predating this field still
|
|
1232
|
-
* typechecks; absent is treated as "not a primary-key filter".
|
|
1233
|
-
*/
|
|
1234
|
-
filtersPrimaryKey?: boolean;
|
|
1235
|
-
/** The chain calls `.filter(...)`. */
|
|
1236
|
-
hasFilter: boolean;
|
|
1237
|
-
/** The chain narrows with `.withIndex(...)` or `.withSearchIndex(...)`. */
|
|
1238
|
-
hasIndex: boolean;
|
|
1239
|
-
/** 1-based line of the `query(...)` call. */
|
|
940
|
+
/** 1-based line of the generation call, or `0` when unknown. */
|
|
1240
941
|
line: number;
|
|
1241
|
-
/**
|
|
942
|
+
/** The generation entrypoint invoked. */
|
|
943
|
+
method: "generateText" | "streamText";
|
|
944
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1242
945
|
scope: CallSiteScope;
|
|
1243
|
-
/**
|
|
1244
|
-
|
|
1245
|
-
/**
|
|
1246
|
-
|
|
1247
|
-
* `"paginate"`, `"first"`, `"unique"`, … — i.e. how much of the narrowed set
|
|
1248
|
-
* the read actually loads.
|
|
1249
|
-
*
|
|
1250
|
-
* `undefined` when the chain reaches no recognised terminal (a reader passed
|
|
1251
|
-
* on, a bare `query(...)`) AND when a feeder predating this field produced
|
|
1252
|
-
* the read. The two are deliberately not distinguished: no consumer could act
|
|
1253
|
-
* on the difference, so the terminal-shaped lints skip the read either way
|
|
1254
|
-
* rather than guessing a terminal.
|
|
1255
|
-
*/
|
|
1256
|
-
terminal?: string;
|
|
946
|
+
/** The privileged side-effect sink a model-callable tool reaches (`ctx.db.insert`, `ctx.run`, `ctx.fetch`, …). */
|
|
947
|
+
sideEffect: string;
|
|
948
|
+
/** `true` when a model-input option is derived from the handler's `args` (a bare `args.x`, or a name destructured from `args`). */
|
|
949
|
+
userInputDerived: boolean;
|
|
1257
950
|
}
|
|
1258
951
|
/**
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
952
|
+
* One `<receiver>.identity.<key>` claim read in `lunora/`, where `<receiver>` is
|
|
953
|
+
* an RLS/mask policy `auth` (or `ctx.auth`/`context.auth`). `declared` records
|
|
954
|
+
* whether `<key>` is in the app's `defineIdentity({ ... })` contract (or the
|
|
955
|
+
* always-present `userId`); the `identity_undeclared_claim_trusted` lint fires on
|
|
956
|
+
* the undeclared reads. Emitted only when a resolvable identity contract exists.
|
|
957
|
+
* Structurally identical to `AdvisorIdentityClaimRead`.
|
|
1264
958
|
*/
|
|
1265
|
-
interface
|
|
959
|
+
interface IdentityClaimReadIR {
|
|
960
|
+
/** `true` when `key` is a declared claim (in the `defineIdentity` contract, or the always-present `userId`). */
|
|
961
|
+
declared: boolean;
|
|
1266
962
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1267
963
|
file: string;
|
|
1268
|
-
/**
|
|
1269
|
-
|
|
1270
|
-
/** 1-based line of the
|
|
964
|
+
/** The claim key read off the identity bag. */
|
|
965
|
+
key: string;
|
|
966
|
+
/** 1-based line of the read, or `0` when unknown. */
|
|
1271
967
|
line: number;
|
|
1272
|
-
/** The better-auth method invoked (e.g. `banUser`); empty when not statically known. */
|
|
1273
|
-
method: string;
|
|
1274
968
|
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1275
969
|
scope: CallSiteScope;
|
|
1276
970
|
}
|
|
1277
971
|
/**
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1280
|
-
* `
|
|
1281
|
-
*
|
|
972
|
+
* One payment webhook-adapter construction in `lunora/` (`createStripeAdapter` /
|
|
973
|
+
* `createPolarAdapter` / `createAutumnAdapter` / `createDodoPaymentsAdapter`).
|
|
974
|
+
* `toleranceSeconds` carries the statically-known `webhookToleranceSeconds`
|
|
975
|
+
* replay window when it is a plain numeric literal; the payment-webhook
|
|
976
|
+
* wide-tolerance lint fires when it exceeds a conservative ceiling. Structurally
|
|
977
|
+
* identical to `AdvisorPaymentWebhook`.
|
|
1282
978
|
*/
|
|
1283
|
-
interface
|
|
979
|
+
interface PaymentWebhookIR {
|
|
980
|
+
/** The adapter factory invoked. */
|
|
981
|
+
callee: "createAutumnAdapter" | "createDodoPaymentsAdapter" | "createPolarAdapter" | "createStripeAdapter";
|
|
1284
982
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1285
983
|
file: string;
|
|
1286
|
-
/** 1-based line of the
|
|
984
|
+
/** 1-based line of the construction, or `0` when unknown. */
|
|
1287
985
|
line: number;
|
|
1288
|
-
/**
|
|
1289
|
-
method: string;
|
|
1290
|
-
/** Who the write runs on behalf of. */
|
|
986
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1291
987
|
scope: CallSiteScope;
|
|
1292
|
-
/**
|
|
1293
|
-
|
|
1294
|
-
* name). A union id (`Id<"a"> | Id<"b">`) gives one record per table.
|
|
1295
|
-
*/
|
|
1296
|
-
table: string;
|
|
988
|
+
/** Statically-known `webhookToleranceSeconds` literal, when present and a plain numeric literal. */
|
|
989
|
+
toleranceSeconds?: number;
|
|
1297
990
|
}
|
|
1298
991
|
/**
|
|
1299
|
-
*
|
|
1300
|
-
*
|
|
1301
|
-
*
|
|
1302
|
-
*
|
|
1303
|
-
*
|
|
992
|
+
* One `query`/`mutation` handler that gates a `ctx.db.get`/`patch`/`delete` on a
|
|
993
|
+
* null-checked `ctx.db.normalizeId(table, id)` result — the
|
|
994
|
+
* `normalize_id_used_as_authorization` lint input. `normalizeId` validates an id's
|
|
995
|
+
* structural shape only (it never reads the database), so a non-null result proves
|
|
996
|
+
* the id is well-formed, never that the caller owns the row; gating access on it is
|
|
997
|
+
* an IDOR. The lint owns the negative proof — it keeps only `visibility === "public"`
|
|
998
|
+
* rows with no `.use(rls(...))` and no ownership/identity mention (`mentionsOwnership`),
|
|
999
|
+
* then joins `table` against the schema's RLS mode before flagging. Structurally
|
|
1000
|
+
* identical to AdvisorNormalizeIdAuthorization so values pass straight through.
|
|
1304
1001
|
*/
|
|
1305
|
-
interface
|
|
1306
|
-
/**
|
|
1002
|
+
interface NormalizeIdAuthorizationIR {
|
|
1003
|
+
/** Export binding name of the procedure performing the normalize-then-access. */
|
|
1004
|
+
exportName: string;
|
|
1005
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1307
1006
|
file: string;
|
|
1308
|
-
/** 1-based line of the `
|
|
1007
|
+
/** 1-based line of the `ctx.db.normalizeId(...)` call the access is gated on. */
|
|
1309
1008
|
line: number;
|
|
1310
|
-
/**
|
|
1311
|
-
|
|
1312
|
-
/**
|
|
1009
|
+
/** `true` when the handler anywhere reads an ownership-named identifier or `ctx.auth`/`ctx.identity`/… — an intervening ownership signal. */
|
|
1010
|
+
mentionsOwnership: boolean;
|
|
1011
|
+
/** The id-first `ctx.db` sink the normalized id reaches. */
|
|
1012
|
+
sinkMethod: "delete" | "get" | "patch";
|
|
1013
|
+
/** Table named in the `normalizeId` call, or `""` when its table argument wasn't a string literal. */
|
|
1313
1014
|
table: string;
|
|
1015
|
+
/** `true` when the procedure's builder chain carries a `.use(rls(...))` step. */
|
|
1016
|
+
usesRls: boolean;
|
|
1017
|
+
/** `"internal"` for `internalQuery`/`internalMutation`; `"public"` for `query`/`mutation`. */
|
|
1018
|
+
visibility: "internal" | "public";
|
|
1314
1019
|
}
|
|
1315
1020
|
/**
|
|
1316
|
-
*
|
|
1317
|
-
* `crypto.getRandomValues`, `fetch`) discovered lexically inside a `query(...)`
|
|
1318
|
-
* or `mutation(...)` handler body — the `nondeterministic_query_mutation` lint
|
|
1319
|
-
* input. Structurally identical to `AdvisorNondeterministicCall` so values pass
|
|
1320
|
-
* straight through to the advisor without conversion, exactly as `AuthApiCallIR`
|
|
1321
|
-
* does for `AdvisorAuthApiCall`. `action(...)` handlers are never recorded —
|
|
1322
|
-
* actions are the determinism escape hatch.
|
|
1021
|
+
* Schema, tables, indexes, relations, migrations: what the app declares about its data.
|
|
1323
1022
|
*/
|
|
1324
|
-
interface NondeterministicCallIR {
|
|
1325
|
-
/** The non-deterministic API invoked, e.g. `Date.now` / `Math.random` / `crypto.randomUUID` / `fetch`. */
|
|
1326
|
-
callee: string;
|
|
1327
|
-
/** Export binding name of the function performing the call, e.g. `sendMessage`. */
|
|
1328
|
-
exportName: string;
|
|
1329
|
-
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
1330
|
-
file: string;
|
|
1331
|
-
/** Which procedure kind the call lives in — only `query`/`mutation` handlers are recorded. */
|
|
1332
|
-
kind: "mutation" | "query";
|
|
1333
|
-
/** 1-based line of the call, or `0` when unknown. */
|
|
1334
|
-
line: number;
|
|
1335
|
-
}
|
|
1336
1023
|
/**
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
* `r2sql_outside_action` for `ctx.r2sql`). Structurally identical to the
|
|
1341
|
-
* advisor's `AdvisorHyperdriveCall` / `AdvisorR2sqlCall` (same field set) so
|
|
1342
|
-
* values pass straight through `lintSchema` without conversion, exactly as
|
|
1343
|
-
* {@link NondeterministicCallIR} does. Only `query`/`mutation` handlers are
|
|
1344
|
-
* recorded; `action(...)` is the only context where these surfaces are even
|
|
1345
|
-
* typed, so it is the intended home and is skipped.
|
|
1024
|
+
* AST-observable subset of a column's modifier chain (`.unique()`, `.default()`,
|
|
1025
|
+
* …). Function-valued modifiers (`.$defaultFn`/`.$onUpdateFn`) can't be
|
|
1026
|
+
* serialized, so only their *presence* is recorded.
|
|
1346
1027
|
*/
|
|
1347
|
-
interface
|
|
1348
|
-
/**
|
|
1349
|
-
|
|
1350
|
-
/**
|
|
1351
|
-
|
|
1352
|
-
/**
|
|
1353
|
-
|
|
1354
|
-
/**
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
/**
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
* through to the advisor without conversion, exactly as `AuthApiCallIR` does for
|
|
1364
|
-
* `AdvisorAuthApiCall`.
|
|
1365
|
-
*/
|
|
1366
|
-
interface RlsProcedureIR {
|
|
1367
|
-
/** Export binding name of the procedure (e.g. `listDocuments`). */
|
|
1368
|
-
exportName: string;
|
|
1369
|
-
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1370
|
-
file: string;
|
|
1028
|
+
interface ColumnMetaIR {
|
|
1029
|
+
/** `.default(...)` or `.$defaultFn(...)` present — field is optional on insert. */
|
|
1030
|
+
hasDefault?: boolean;
|
|
1031
|
+
/** `.$onUpdateFn(...)` present. */
|
|
1032
|
+
hasOnUpdate?: boolean;
|
|
1033
|
+
/** Default `true`; `.nullable()` flips it to `false`. */
|
|
1034
|
+
notNull: boolean;
|
|
1035
|
+
/** `.unique()` present. */
|
|
1036
|
+
unique?: boolean;
|
|
1037
|
+
}
|
|
1038
|
+
/** Reflective representation of a single validator call from the schema. */
|
|
1039
|
+
interface ValidatorIR {
|
|
1040
|
+
/** For `v.storage(bucket?)` — the named typed bucket the object key lives in, when given. */
|
|
1041
|
+
bucket?: string;
|
|
1042
|
+
/** Column modifiers (`.unique()`, `.default()`, `.nullable()`, …) when present. */
|
|
1043
|
+
column?: ColumnMetaIR;
|
|
1371
1044
|
/**
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1045
|
+
* `true` when this validator carries a refinement (`.check(...)`, `.max(n)`,
|
|
1046
|
+
* `.email()`, …) — anything that narrows the accepted values without changing
|
|
1047
|
+
* the kind. `schema-drift` hashes this, so a bound counts here even when it is
|
|
1048
|
+
* modelled below. `.meta(...)` is pure metadata with no parse effect and does
|
|
1049
|
+
* NOT set it.
|
|
1374
1050
|
*/
|
|
1375
|
-
|
|
1376
|
-
/**
|
|
1377
|
-
|
|
1378
|
-
/**
|
|
1379
|
-
|
|
1380
|
-
/**
|
|
1381
|
-
|
|
1382
|
-
/** `
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
* `AdvisorMaskProcedure` so values pass straight through without conversion.
|
|
1391
|
-
*/
|
|
1392
|
-
interface MaskProcedureIR {
|
|
1393
|
-
/** Export binding name of the procedure (e.g. `listUsers`). */
|
|
1394
|
-
exportName: string;
|
|
1395
|
-
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1396
|
-
file: string;
|
|
1051
|
+
hasRefinement?: boolean;
|
|
1052
|
+
/** For `v.optional(inner)` / `v.array(inner)`. */
|
|
1053
|
+
inner?: ValidatorIR;
|
|
1054
|
+
/** For `v.record(key, value)`. */
|
|
1055
|
+
keyType?: ValidatorIR;
|
|
1056
|
+
/** Kind of validator (string, number, id, object, optional, array, union, literal, record, ...). */
|
|
1057
|
+
kind: string;
|
|
1058
|
+
/** For `v.literal(value)` — the literal value as source text. */
|
|
1059
|
+
literalValue?: string;
|
|
1060
|
+
/** For `v.union(a, b, ...)`. */
|
|
1061
|
+
members?: ValidatorIR[];
|
|
1062
|
+
/** For `v.object({...})`. */
|
|
1063
|
+
shape?: Record<string, ValidatorIR>;
|
|
1064
|
+
/** Verbatim source text — used in emitted code when we can't reconstruct from AST. */
|
|
1065
|
+
sourceText?: string;
|
|
1397
1066
|
/**
|
|
1398
|
-
*
|
|
1399
|
-
*
|
|
1400
|
-
*
|
|
1067
|
+
* The `n` of a `v.string().max(n)` written with a numeric literal — the one
|
|
1068
|
+
* refinement predicate the IR can represent exactly (`value.length <= n`).
|
|
1069
|
+
* The AOT compiler emits it as a guard instead of declining the node, which
|
|
1070
|
+
* is what keeps a length-bounded public argument on the fast path.
|
|
1401
1071
|
*/
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1072
|
+
stringMaxLength?: number;
|
|
1073
|
+
/** For `v.id("table")` — the table name. */
|
|
1074
|
+
tableName?: string;
|
|
1075
|
+
/**
|
|
1076
|
+
* For `v.from(externalSchema)` — the wrapped Standard Schema's inferred type,
|
|
1077
|
+
* rendered as TS source. Recovered through the type checker from
|
|
1078
|
+
* `~standard.types.output`, mirroring the runtime's `InferStandardOutput`.
|
|
1079
|
+
* Absent when it could not be recovered safely, in which case the emitted
|
|
1080
|
+
* type falls back to `unknown`.
|
|
1081
|
+
*/
|
|
1082
|
+
tsType?: string;
|
|
1083
|
+
/**
|
|
1084
|
+
* `true` when this validator carries a refinement the IR could NOT model — a
|
|
1085
|
+
* `.check(...)` closure, `.email()`, `.pattern(re)`, a `.max()` whose bound is
|
|
1086
|
+
* not a literal. The AOT args-validator compiler declines any node with this
|
|
1087
|
+
* flag, because compiling it would silently skip the predicate.
|
|
1088
|
+
*/
|
|
1089
|
+
unmodelledRefinement?: boolean;
|
|
1090
|
+
valueType?: ValidatorIR;
|
|
1414
1091
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
* project's `.use(mask(...))` chains by `discoverMaskMetadata`; the descriptive
|
|
1420
|
-
* twin of {@link RlsPolicyIR}. `"custom"` covers any non-string strategy (a
|
|
1421
|
-
* `(value, ctx) => …` function) — its logic is an opaque closure, never read by
|
|
1422
|
-
* the UI; the preview renders a fixed sentinel for it.
|
|
1423
|
-
*/
|
|
1424
|
-
interface MaskColumnMetadataIR {
|
|
1425
|
-
/** Column the mask policy redacts. */
|
|
1426
|
-
column: string;
|
|
1427
|
-
/** Declared masking strategy: `"redact"`/`"hash"` string literals, else `"custom"` for a `MaskFn`. */
|
|
1428
|
-
strategy: "custom" | "hash" | "redact";
|
|
1429
|
-
/** Logical table the masked column belongs to. */
|
|
1430
|
-
table: string;
|
|
1092
|
+
interface IndexIR {
|
|
1093
|
+
fields: ReadonlyArray<string>;
|
|
1094
|
+
name: string;
|
|
1095
|
+
unique?: boolean;
|
|
1431
1096
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
/**
|
|
1441
|
-
|
|
1097
|
+
interface SearchIndexIR {
|
|
1098
|
+
/** Primary text-search field; a dot-separated path reads a nested field. */
|
|
1099
|
+
field: string;
|
|
1100
|
+
/** Optional filter fields surfaced alongside the FTS column. */
|
|
1101
|
+
filterFields?: ReadonlyArray<string>;
|
|
1102
|
+
/** Text-analysis profile (accent folding + that language's stopwords). */
|
|
1103
|
+
language?: string;
|
|
1104
|
+
name: string;
|
|
1105
|
+
/** Skip the migration-time backfill of the search companion (large tables index out-of-band). */
|
|
1106
|
+
staged?: boolean;
|
|
1107
|
+
/** `"native"` opts into the engine's own full-text index where it has one (Postgres). */
|
|
1108
|
+
strategy?: string;
|
|
1442
1109
|
}
|
|
1443
|
-
/**
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
column
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
/**
|
|
1461
|
-
|
|
1462
|
-
/**
|
|
1463
|
-
|
|
1464
|
-
|
|
1110
|
+
/** A `.geoIndex(name, { field, precision? })` declaration — a geohash companion over a `v.geoPoint()` column. */
|
|
1111
|
+
interface GeoIndexIR {
|
|
1112
|
+
/** The `v.geoPoint()` column feeding the geohash. */
|
|
1113
|
+
field: string;
|
|
1114
|
+
name: string;
|
|
1115
|
+
/** Geohash precision (characters) maintained on the companion; omitted ⇒ the runtime default. */
|
|
1116
|
+
precision?: number;
|
|
1117
|
+
}
|
|
1118
|
+
/** A `.ttl(field, { after? })` declaration — declarative table-level auto-expiry. */
|
|
1119
|
+
interface TtlIR {
|
|
1120
|
+
/** Millisecond offset added to `field` to derive the expiry (`field + after`); omitted ⇒ `field` is the absolute expiry. */
|
|
1121
|
+
after?: number;
|
|
1122
|
+
/** The epoch-millisecond expiry column. */
|
|
1123
|
+
field: string;
|
|
1124
|
+
}
|
|
1125
|
+
interface VectorIndexIR {
|
|
1126
|
+
dimensions?: number;
|
|
1127
|
+
/** Shape A: the single source column. Shape B: undefined (derived via a `select` fn). */
|
|
1128
|
+
field?: string;
|
|
1129
|
+
/** Shape A: metadata field names mirrored into Vectorize. */
|
|
1130
|
+
metadata?: ReadonlyArray<string>;
|
|
1131
|
+
metric?: "cosine" | "dot-product" | "euclidean";
|
|
1132
|
+
name: string;
|
|
1133
|
+
/** Owning table the vectors are sourced from. */
|
|
1465
1134
|
table: string;
|
|
1466
1135
|
}
|
|
1467
1136
|
/**
|
|
1468
|
-
* One
|
|
1469
|
-
*
|
|
1470
|
-
* `rlsPolicies()` hook. Captures the policy's `table` + `on` operation and the
|
|
1471
|
-
* procedure it guards — never the `when` predicate, which is an opaque JS
|
|
1472
|
-
* closure (its logic stays in code, not the UI). Produced by
|
|
1473
|
-
* `discoverRlsProcedures` alongside the lint IR.
|
|
1137
|
+
* One ordering key on a rank index's `sortBy`: the column and direction.
|
|
1138
|
+
* Mirrors the runtime `RankSortKey` (defaults `direction` to `"asc"`).
|
|
1474
1139
|
*/
|
|
1475
|
-
interface
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
/** Operation the policy gates: `read` covers get/query/findMany, the rest are writes. */
|
|
1479
|
-
on: "delete" | "insert" | "read" | "update";
|
|
1480
|
-
/** Export name of the procedure whose `.use(rls(...))` chain declared this policy. */
|
|
1481
|
-
procedure: string;
|
|
1482
|
-
/** Logical table the policy applies to. Empty when not a string literal. */
|
|
1483
|
-
table: string;
|
|
1140
|
+
interface RankSortKeyIR {
|
|
1141
|
+
direction: "asc" | "desc";
|
|
1142
|
+
field: string;
|
|
1484
1143
|
}
|
|
1485
1144
|
/**
|
|
1486
|
-
*
|
|
1487
|
-
*
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
* `discoverRlsProcedures`.
|
|
1145
|
+
* A `.rankIndex(name, { sortBy, partitionBy?, where? })` declaration. The owning
|
|
1146
|
+
* table is always the table the index is declared on, so — unlike a vector index
|
|
1147
|
+
* — there is no separate `on`/`table` reference to carry: it rides along on its
|
|
1148
|
+
* {@link TableIR}. Only the fields needed for type emission are captured.
|
|
1491
1149
|
*/
|
|
1492
|
-
interface
|
|
1493
|
-
/** Optional human-readable description from `defineRole(name, { description })`. */
|
|
1494
|
-
description?: string;
|
|
1495
|
-
/** Role label attached to the request identity (e.g. `"admin"`). */
|
|
1150
|
+
interface RankIndexIR {
|
|
1496
1151
|
name: string;
|
|
1497
|
-
/**
|
|
1498
|
-
|
|
1152
|
+
/** Columns scoping each ranking; omitted ⇒ one global rank over the table. */
|
|
1153
|
+
partitionBy?: ReadonlyArray<string>;
|
|
1154
|
+
/** Ordered sort keys driving the rank. */
|
|
1155
|
+
sortBy: ReadonlyArray<RankSortKeyIR>;
|
|
1156
|
+
}
|
|
1157
|
+
interface RelationIR {
|
|
1158
|
+
/** FK column: on this table for `one`, on the target table for `many`. */
|
|
1159
|
+
field: string;
|
|
1160
|
+
kind: "many" | "one";
|
|
1161
|
+
/** Accessor name the relation is loaded under (the `with` key). */
|
|
1162
|
+
name: string;
|
|
1163
|
+
/** FK behaviour applied to holder rows when the referenced parent is deleted. */
|
|
1164
|
+
onDelete?: "cascade" | "restrict" | "set null";
|
|
1165
|
+
/** Referenced column (defaults to `_id`). */
|
|
1166
|
+
references: string;
|
|
1167
|
+
/** Target table name. */
|
|
1168
|
+
table: string;
|
|
1499
1169
|
}
|
|
1500
1170
|
/**
|
|
1501
|
-
*
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1171
|
+
* Statically-discovered `.source(...)` config (plan 077). Only the bits the
|
|
1172
|
+
* advisor lints + DO wiring need are captured; `map`/`tenantBy` are functions and
|
|
1173
|
+
* cannot be serialized, so their presence is recorded as `hasTenantBy` rather than
|
|
1174
|
+
* the function itself.
|
|
1505
1175
|
*/
|
|
1506
|
-
interface
|
|
1507
|
-
/**
|
|
1508
|
-
|
|
1509
|
-
/**
|
|
1510
|
-
|
|
1176
|
+
interface ExternalSourceIR {
|
|
1177
|
+
/** The wrangler Hyperdrive binding name. */
|
|
1178
|
+
binding: string;
|
|
1179
|
+
/** Whether a `columns` projection allow-list was given. */
|
|
1180
|
+
columns?: ReadonlyArray<string>;
|
|
1181
|
+
/** `true` when a `reconcileEveryMs` was given — one of the two incremental delete-visibility paths the `external_source_incremental_no_delete_path` lint checks. */
|
|
1182
|
+
hasReconcile?: boolean;
|
|
1183
|
+
/** `true` when a `softDeleteColumn` was given — the other incremental delete-visibility path. */
|
|
1184
|
+
hasSoftDelete?: boolean;
|
|
1185
|
+
/** `true` when a `tenantBy` mapper was given — the tenant-isolation boundary the `external_source_unscoped` lint checks. */
|
|
1186
|
+
hasTenantBy: boolean;
|
|
1187
|
+
/** The `idColumn` literal, when given (defaults to `"id"` at runtime). */
|
|
1188
|
+
idColumn?: string;
|
|
1189
|
+
/** Delete-detection mode literal, when given (`"full-pull"` today). */
|
|
1190
|
+
mode?: string;
|
|
1191
|
+
/** The membership query literal, when statically knowable. */
|
|
1192
|
+
query?: string;
|
|
1193
|
+
/**
|
|
1194
|
+
* `true` when `.source(...)` was present but its argument was **not** a static
|
|
1195
|
+
* object literal (e.g. `.source(buildConfig())`), so none of the fields above
|
|
1196
|
+
* could be read. The source still exists — this flag lets `hasSourcedTables`
|
|
1197
|
+
* (codegen) and the `external_source_*` lints treat it as a source that can't be
|
|
1198
|
+
* verified, instead of mistaking it for no `.source()` at all.
|
|
1199
|
+
*/
|
|
1200
|
+
unanalyzable?: boolean;
|
|
1511
1201
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
/**
|
|
1521
|
-
|
|
1202
|
+
interface TableIR {
|
|
1203
|
+
/**
|
|
1204
|
+
* `true` when the table chain carried `.commitOrdered()` — every row carries
|
|
1205
|
+
* `_commitSeq`, a per-shard integer allocated once per mutation and strictly
|
|
1206
|
+
* increasing in commit order. Optional: hand-built IR and tables that never
|
|
1207
|
+
* called `.commitOrdered()` default it to `false`.
|
|
1208
|
+
*/
|
|
1209
|
+
commitOrdered?: boolean;
|
|
1210
|
+
/**
|
|
1211
|
+
* `true` when that extension was resolved from a package in `node_modules`
|
|
1212
|
+
* rather than from source under `lunora/` (a registry copy-in). Decides
|
|
1213
|
+
* whether the component's module owns a `lunora/<key>/` folder.
|
|
1214
|
+
*/
|
|
1215
|
+
extensionFromPackage?: true;
|
|
1216
|
+
/**
|
|
1217
|
+
* The `defineSchemaExtension` key that contributed this table, set when it
|
|
1218
|
+
* arrived through `defineSchema(...).extend(...)`. Absent for a table the app
|
|
1219
|
+
* declared itself.
|
|
1220
|
+
*
|
|
1221
|
+
* Drives the generated `AppTableName` union: an add-on's tables
|
|
1222
|
+
* (`ratelimit_buckets`, …) are real tables and stay in `TableName`, but an app
|
|
1223
|
+
* enumerating "my tables" should not have to know about them.
|
|
1224
|
+
*/
|
|
1225
|
+
extensionKey?: string;
|
|
1226
|
+
/**
|
|
1227
|
+
* `true` when the table chain carried `.externallyManaged()` — its rows are
|
|
1228
|
+
* written outside Lunora's discoverable insert path (adapter/migration/
|
|
1229
|
+
* middleware), so advisor insert-path lints skip it. Optional: hand-built
|
|
1230
|
+
* IR and the runtime `fromServerSchema` path default it to `false`.
|
|
1231
|
+
*/
|
|
1232
|
+
externallyManaged?: boolean;
|
|
1233
|
+
/**
|
|
1234
|
+
* Set when the chain carried `.source(...)` — the table is materialized from an
|
|
1235
|
+
* external Hyperdrive-backed database by a system poll loop (plan 077). Carries
|
|
1236
|
+
* the statically-knowable bits the advisor lints read; the functions (`map`,
|
|
1237
|
+
* `tenantBy`) are not serialized, only their presence (`hasTenantBy`).
|
|
1238
|
+
*/
|
|
1239
|
+
externalSource?: ExternalSourceIR;
|
|
1240
|
+
/** Geospatial indexes declared inline via `.geoIndex(name, …)`. Optional so hand-built IR may omit it (discovery always sets it). */
|
|
1241
|
+
geoIndexes?: ReadonlyArray<GeoIndexIR>;
|
|
1242
|
+
/**
|
|
1243
|
+
* Storage backend for a `.global()` table: `"d1"` (default) or
|
|
1244
|
+
* `"hyperdrive"` (a Postgres/MySQL database via Cloudflare Hyperdrive). Only
|
|
1245
|
+
* meaningful when `shardMode === "global"`; absent for sharded/root tables.
|
|
1246
|
+
*
|
|
1247
|
+
* Read it through `isD1GlobalTable` / `isHyperdriveGlobalTable` rather than
|
|
1248
|
+
* comparing here — discovery normalises the absent case to `"d1"`, and only
|
|
1249
|
+
* those predicates encode which comparison is right for hand-built IR.
|
|
1250
|
+
*/
|
|
1251
|
+
globalBackend?: "d1" | "hyperdrive";
|
|
1252
|
+
indexes: ReadonlyArray<IndexIR>;
|
|
1253
|
+
/**
|
|
1254
|
+
* `true` when the table chain carried `.public()` — an explicit opt-OUT of
|
|
1255
|
+
* the schema's `.rls("required")` enforcement for this one table. Optional:
|
|
1256
|
+
* hand-built IR and tables that never called `.public()` default it to
|
|
1257
|
+
* `false`.
|
|
1258
|
+
*/
|
|
1259
|
+
isPublic?: boolean;
|
|
1260
|
+
/**
|
|
1261
|
+
* `true` when the table chain carried `.memory()` — its rows are cleared on
|
|
1262
|
+
* every Durable Object cold start and never reach the CDC changelog.
|
|
1263
|
+
* Optional: hand-built IR and tables that never called `.memory()` default it
|
|
1264
|
+
* to `false`.
|
|
1265
|
+
*/
|
|
1266
|
+
memory?: boolean;
|
|
1267
|
+
name: string;
|
|
1268
|
+
/** Rank indexes declared inline via `.rankIndex(name, …)`. */
|
|
1269
|
+
rankIndexes: ReadonlyArray<RankIndexIR>;
|
|
1270
|
+
/** Declared relations (via `.relations((r) => …)`), keyed in source order. */
|
|
1271
|
+
relations: ReadonlyArray<RelationIR>;
|
|
1272
|
+
searchIndexes: ReadonlyArray<SearchIndexIR>;
|
|
1273
|
+
shape: Record<string, ValidatorIR>;
|
|
1274
|
+
shardMode: "global" | "root" | {
|
|
1275
|
+
field: string;
|
|
1276
|
+
kind: "shardBy";
|
|
1277
|
+
};
|
|
1278
|
+
/** Set when the chain carried `.softDelete()` — the marker column's name (default `deletedAt`). The column is injected into `shape` so `Doc_*` carries it. */
|
|
1279
|
+
softDelete?: {
|
|
1280
|
+
field: string;
|
|
1281
|
+
};
|
|
1282
|
+
/** Set when the chain carried `.ttl(field, { after? })` — the declarative auto-expiry policy read by the DO alarm sweep. */
|
|
1283
|
+
ttl?: TtlIR;
|
|
1284
|
+
/** Vector indexes declared inline via `.vectorize()` (DSL Shape A). */
|
|
1285
|
+
vectorIndexes: ReadonlyArray<VectorIndexIR>;
|
|
1286
|
+
}
|
|
1287
|
+
/** The Cloudflare DO data-residency jurisdictions the schema may declare. Canonical literal set for the codegen package. */
|
|
1288
|
+
type JurisdictionIR = "eu" | "fedramp" | "us";
|
|
1289
|
+
interface SchemaIR {
|
|
1290
|
+
/**
|
|
1291
|
+
* Cloudflare data-residency jurisdiction declared via
|
|
1292
|
+
* `defineSchema(...).jurisdiction("…")`. Emitted into the generated worker's
|
|
1293
|
+
* `createWorker({ jurisdiction })` (and `ctx.scheduler` / `ctx.containers`).
|
|
1294
|
+
* Absent ⇒ un-pinned.
|
|
1295
|
+
*/
|
|
1296
|
+
jurisdiction?: JurisdictionIR;
|
|
1297
|
+
/**
|
|
1298
|
+
* `.jurisdiction("…", { pinAuth: true })` — the acknowledgement that
|
|
1299
|
+
* DO-backed auth, unpinned before, moves into the jurisdiction (and so to a
|
|
1300
|
+
* new, empty object until its rows are copied). Without it codegen refuses a
|
|
1301
|
+
* project that has DO-backed auth, and does not pin it.
|
|
1302
|
+
*/
|
|
1303
|
+
jurisdictionPinsAuth?: true;
|
|
1304
|
+
/**
|
|
1305
|
+
* Set when `defineSchema(...).rls("required")` was chained onto the schema —
|
|
1306
|
+
* every table's `ctx.db` write path is denied without an RLS-covering
|
|
1307
|
+
* procedure unless the table itself is `.public()` (see {@link TableIR.isPublic}).
|
|
1308
|
+
* Absent when the schema never called `.rls("required")`.
|
|
1309
|
+
*/
|
|
1310
|
+
rlsMode?: "required";
|
|
1311
|
+
tables: ReadonlyArray<TableIR>;
|
|
1312
|
+
/** All vector indexes (inline Shape A hoisted + standalone Shape B), flattened. */
|
|
1313
|
+
vectorIndexes: ReadonlyArray<VectorIndexIR>;
|
|
1522
1314
|
}
|
|
1523
1315
|
/**
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1527
|
-
* `.use(storageRules(...))` chain — descriptive only, never the predicate logic.
|
|
1316
|
+
* Statically-read mirror of `@lunora/server`'s `RestCacheConfig`. Every field is
|
|
1317
|
+
* optional because discovery only records literals it could actually read off the
|
|
1318
|
+
* `.expose({ cache: … })` object.
|
|
1528
1319
|
*/
|
|
1529
|
-
interface
|
|
1530
|
-
|
|
1320
|
+
interface ExposeCacheIR {
|
|
1321
|
+
maxAge?: number;
|
|
1322
|
+
scope?: "private" | "public";
|
|
1323
|
+
staleWhileRevalidate?: number;
|
|
1324
|
+
tag?: string;
|
|
1325
|
+
vary?: string;
|
|
1531
1326
|
}
|
|
1532
1327
|
/**
|
|
1533
|
-
* A
|
|
1534
|
-
*
|
|
1535
|
-
*
|
|
1536
|
-
*
|
|
1537
|
-
*
|
|
1328
|
+
* A `defineMigration({...})` declaration discovered in the user's lunora
|
|
1329
|
+
* sources. The emitted `LUNORA_MIGRATIONS` registry keys on {@link MigrationIR.id}; the
|
|
1330
|
+
* import wiring needs {@link MigrationIR.exportName}/{@link MigrationIR.filePath}. The runtime object
|
|
1331
|
+
* carries the authoritative {@link MigrationIR.table}, but the lifted value is
|
|
1332
|
+
* load-bearing at build time: the schema-drift gate matches it against the
|
|
1333
|
+
* tables with breaking drift, so a migration left at `""` covers nothing.
|
|
1538
1334
|
*/
|
|
1539
|
-
interface
|
|
1540
|
-
/**
|
|
1541
|
-
body: Record<string, ValidatorIR>;
|
|
1542
|
-
/**
|
|
1543
|
-
* Rendered TS type of one SSE chunk — the `R` the `.stream(handler)`
|
|
1544
|
-
* handler yields — inferred from the handler via the type checker. Present
|
|
1545
|
-
* only when {@link HttpRouteIR.stream} is `true`; `"unknown"` when the
|
|
1546
|
-
* checker can't resolve enough context. Feeds the emitted
|
|
1547
|
-
* `HttpStreamRef<Chunk, …>` so the chunk type flows to the client.
|
|
1548
|
-
* @experimental Part of the HTTP-SSE stream surface (the `httpStreams.*` emission).
|
|
1549
|
-
*/
|
|
1550
|
-
chunkType?: string;
|
|
1551
|
-
/** Export binding name of the route handler (used only for diagnostics / dedupe). */
|
|
1335
|
+
interface MigrationIR {
|
|
1336
|
+
/** Export binding name, used to reference the module member in generated imports. */
|
|
1552
1337
|
exportName: string;
|
|
1553
|
-
/** Path relative to `<projectRoot>/lunora/` without extension, e.g. "
|
|
1338
|
+
/** Path relative to `<projectRoot>/lunora/` without extension, e.g. "migrations". */
|
|
1554
1339
|
filePath: string;
|
|
1555
|
-
/**
|
|
1556
|
-
|
|
1557
|
-
/**
|
|
1558
|
-
|
|
1559
|
-
/** `v.*` validators decoding the hono path params (`.params({...})`), keyed by `:name`. */
|
|
1560
|
-
params: Record<string, ValidatorIR>;
|
|
1561
|
-
/** The route path passed to `httpRoute.<verb>(path)`, e.g. `/api/todos/:id`. */
|
|
1562
|
-
path: string;
|
|
1563
|
-
/** `v.*` validators decoding the URL query string (`.searchParams({...})`), keyed by name. */
|
|
1564
|
-
searchParams: Record<string, ValidatorIR>;
|
|
1565
|
-
/** `true` when declared via the terminal `.stream(...)` (Server-Sent Events) rather than `.handler(...)`. */
|
|
1566
|
-
stream: boolean;
|
|
1340
|
+
/** Stable migration id — the registry key and per-shard run-state key. */
|
|
1341
|
+
id: string;
|
|
1342
|
+
/** Table the migration iterates; `""` when not a static string literal. */
|
|
1343
|
+
table: string;
|
|
1567
1344
|
}
|
|
1568
1345
|
/**
|
|
1569
|
-
*
|
|
1570
|
-
* `
|
|
1571
|
-
*
|
|
1572
|
-
*
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
1575
|
-
* is unwrapped: the bundle's object-literal keys set `usesRateLimit`/`usesCaptcha`
|
|
1576
|
-
* exactly as the individual `.use(rateLimit(...))` / `.use(verifyTurnstile(...))`
|
|
1577
|
-
* steps would. Structurally identical to `AdvisorProcedureProtection` so values
|
|
1578
|
-
* pass straight through to the advisor without conversion.
|
|
1346
|
+
* One `ctx.db.<table>.findMany({ includeDeleted })` list read whose
|
|
1347
|
+
* `includeDeleted` is either a hardcoded `true` or derived from the handler's
|
|
1348
|
+
* `args` — the `soft_delete_include_deleted_from_args` lint input. The lint joins
|
|
1349
|
+
* `table` against the schema's soft-delete tables and `visibility` against
|
|
1350
|
+
* `.public()` before flagging. Structurally identical to `AdvisorSoftDeleteRead`
|
|
1351
|
+
* so values pass straight through without conversion.
|
|
1579
1352
|
*/
|
|
1580
|
-
interface
|
|
1581
|
-
/**
|
|
1582
|
-
* `true` when the handler body could be read statically — an inline function
|
|
1583
|
-
* expression/arrow, or a same-file identifier resolved to one. `false` for a
|
|
1584
|
-
* genuinely cross-file handler (an imported function, or an identifier that
|
|
1585
|
-
* doesn't resolve in this file), in which case every behavioural fact below
|
|
1586
|
-
* is `undefined` rather than a false "not observed" — the feeder never saw
|
|
1587
|
-
* the body, so it has nothing to report.
|
|
1588
|
-
*/
|
|
1589
|
-
analyzableBody: boolean;
|
|
1590
|
-
/** `true` when the handler (or a helper inside it) references `ctx.mail` / `ctx.email`. `undefined` when `analyzableBody` is `false`. */
|
|
1591
|
-
callsMail?: boolean;
|
|
1592
|
-
/** `true` when the handler emits a structured observability event (`ctx.log` / `ctx.span` / `ctx.trace`). */
|
|
1593
|
-
emitsEvent?: boolean;
|
|
1594
|
-
/** `true` when a `// lunora-advisor-exempt` directive sits above the export. */
|
|
1595
|
-
exempt: boolean;
|
|
1596
|
-
/** The `-- reason` from that directive, or `""`. */
|
|
1597
|
-
exemptReason: string;
|
|
1598
|
-
/** Export binding name of the procedure (e.g. `signUp`). */
|
|
1353
|
+
interface SoftDeleteReadIR {
|
|
1354
|
+
/** Export binding name of the procedure performing the read. */
|
|
1599
1355
|
exportName: string;
|
|
1600
|
-
/** `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. `undefined` when `analyzableBody` is `false`. */
|
|
1601
|
-
fanOut?: boolean;
|
|
1602
1356
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1603
1357
|
file: string;
|
|
1604
|
-
/** `true` when the handler
|
|
1605
|
-
|
|
1606
|
-
/**
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
* only be actioned when there is an address to gate — so "unreadable" must
|
|
1613
|
-
* stay distinguishable from "none", or the lint would clear itself on a
|
|
1614
|
-
* registration that may well expose one.
|
|
1615
|
-
*/
|
|
1616
|
-
hasEmailArg?: boolean;
|
|
1617
|
-
kind: "action" | "mutation" | "query";
|
|
1618
|
-
/** `true` when the handler reaches an outbound surface (`ctx.fetch`, mail, queues, storage, sql, ai, …) that can fail. */
|
|
1619
|
-
reachesOutbound?: boolean;
|
|
1620
|
-
/** `true` when the handler runs any AI generation, bounded or not. */
|
|
1621
|
-
runsAiGeneration?: boolean;
|
|
1622
|
-
/** `true` when the handler throws a bare `new Error(...)` rather than a coded `LunoraError`. */
|
|
1623
|
-
throwsBareError?: boolean;
|
|
1624
|
-
/** `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. `undefined` when `analyzableBody` is `false`. */
|
|
1625
|
-
unboundedAiGeneration?: boolean;
|
|
1626
|
-
/** `true` when the chain carries `.use(verifyTurnstile(...))` or a `protectPublic({ captcha })` bundle. */
|
|
1627
|
-
usesCaptcha: boolean;
|
|
1628
|
-
/** `true` when the chain carries `.use(emailGateMiddleware(...))` (`@lunora/auth`). Feeds the `signup_mutation_without_disposable_gating` lint. */
|
|
1629
|
-
usesEmailGate: boolean;
|
|
1630
|
-
/** `true` when the handler calls `ctx.db.insertManyUnsafe(...)`, bypassing validators and triggers. Feeds the `insert_many_unsafe_user_data` lint. `undefined` when `analyzableBody` is `false`. */
|
|
1631
|
-
usesInsertManyUnsafe?: boolean;
|
|
1632
|
-
/** `true` when the chain carries `.use(mask(...))`. */
|
|
1633
|
-
usesMask: boolean;
|
|
1634
|
-
/** `true` when the chain carries `.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle. */
|
|
1635
|
-
usesRateLimit: boolean;
|
|
1636
|
-
/** `true` when the chain carries `.use(rls(...))`. */
|
|
1637
|
-
usesRls: boolean;
|
|
1358
|
+
/** `true` when `includeDeleted` was derived from the handler's `args` (any caller can flip it). */
|
|
1359
|
+
fromArgs: boolean;
|
|
1360
|
+
/** `true` when `includeDeleted` was a hardcoded `true` literal (always resurfaces soft-deleted rows). */
|
|
1361
|
+
hardcodedTrue: boolean;
|
|
1362
|
+
/** 1-based line of the read call. */
|
|
1363
|
+
line: number;
|
|
1364
|
+
/** Table read, or `""` when the table-arg form's first argument wasn't a string literal. */
|
|
1365
|
+
table: string;
|
|
1638
1366
|
/** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
|
|
1639
1367
|
visibility: "internal" | "public";
|
|
1640
|
-
/** `true` when the handler inserts into a user/session/account-shaped table. `undefined` when `analyzableBody` is `false`. */
|
|
1641
|
-
writesUserTable?: boolean;
|
|
1642
1368
|
}
|
|
1643
1369
|
/**
|
|
1644
|
-
*
|
|
1645
|
-
*
|
|
1646
|
-
*
|
|
1647
|
-
*
|
|
1648
|
-
* to
|
|
1649
|
-
*
|
|
1370
|
+
* One `ctx.db.<table>.findMany({ with: { <rel> } })` relation-hydrating list read
|
|
1371
|
+
* — the `masked_relation_leak_via_with` lint input. Column masking does not
|
|
1372
|
+
* descend into `with`-hydrated relations, so a masked table surfaced only through
|
|
1373
|
+
* a `with` on an unprotected parent read is returned in the clear. The lint
|
|
1374
|
+
* resolves each relation accessor to its target table and joins it against the
|
|
1375
|
+
* discovered mask evidence before flagging. Structurally identical to
|
|
1376
|
+
* `AdvisorRelationLoad` so values pass straight through without conversion.
|
|
1650
1377
|
*/
|
|
1651
|
-
interface
|
|
1652
|
-
/**
|
|
1653
|
-
anyArgs: string[];
|
|
1654
|
-
/** Export binding name of the procedure (e.g. `updateProfile`). */
|
|
1378
|
+
interface RelationLoadIR {
|
|
1379
|
+
/** Export binding name of the procedure performing the read. */
|
|
1655
1380
|
exportName: string;
|
|
1656
1381
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1657
1382
|
file: string;
|
|
1658
|
-
/** 1-based line of the
|
|
1383
|
+
/** 1-based line of the read call. */
|
|
1659
1384
|
line: number;
|
|
1660
|
-
/**
|
|
1661
|
-
|
|
1385
|
+
/** Parent table the read targets, or `""` when the table-arg form's first argument wasn't a string literal. */
|
|
1386
|
+
parentTable: string;
|
|
1387
|
+
/** Relation accessor names named in the read's `with: { … }` map — matched against the parent table's declared relations. */
|
|
1388
|
+
relations: string[];
|
|
1389
|
+
/** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
|
|
1390
|
+
visibility: "internal" | "public";
|
|
1662
1391
|
}
|
|
1663
1392
|
/**
|
|
1664
|
-
* One
|
|
1665
|
-
*
|
|
1666
|
-
*
|
|
1667
|
-
* store, browser private-targets). Structurally identical to `AdvisorConfigCall`
|
|
1668
|
-
* so it passes straight through to the advisor without conversion.
|
|
1393
|
+
* One import of a migrated-away platform's SDK still present in `lunora/`
|
|
1394
|
+
* source. Structurally identical to the advisor's `AdvisorStaleMigrationImport`
|
|
1395
|
+
* so it passes through the feeder without conversion.
|
|
1669
1396
|
*/
|
|
1670
|
-
interface
|
|
1671
|
-
/**
|
|
1672
|
-
analyzable: boolean;
|
|
1673
|
-
/** The factory function or constructor name at the call site, e.g. `createPayment` / `RateLimiter`. */
|
|
1674
|
-
callee: string;
|
|
1675
|
-
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1397
|
+
interface StaleMigrationImportIR {
|
|
1398
|
+
/** Source file relative to the lunora dir, no extension. */
|
|
1676
1399
|
file: string;
|
|
1677
|
-
/** 1-based line of the
|
|
1400
|
+
/** 1-based line of the import, or `0` when unknown. */
|
|
1678
1401
|
line: number;
|
|
1679
|
-
/**
|
|
1680
|
-
|
|
1681
|
-
/**
|
|
1682
|
-
|
|
1402
|
+
/** The imported module, e.g. `@supabase/supabase-js`. */
|
|
1403
|
+
moduleSpecifier: string;
|
|
1404
|
+
/** Which migration guide covers this platform. */
|
|
1405
|
+
platform: "convex" | "firebase" | "supabase";
|
|
1406
|
+
}
|
|
1407
|
+
interface FunctionIR {
|
|
1408
|
+
/**
|
|
1409
|
+
* Set when the chain carries `.use(platformAdmin(...))`: only a platform admin
|
|
1410
|
+
* can reach the procedure, so its writes are trusted to name any account.
|
|
1411
|
+
*/
|
|
1412
|
+
adminOnly?: true;
|
|
1413
|
+
args: Record<string, ValidatorIR>;
|
|
1414
|
+
exportName: string;
|
|
1415
|
+
/**
|
|
1416
|
+
* Set by the `.expose({ rest: true })` builder modifier (plan 167). When
|
|
1417
|
+
* `rest` is `true` the function is published on the public REST surface, so the
|
|
1418
|
+
* OpenAPI emitter describes it as a real `/_lunora/rest/<namespace>/<fn>` path
|
|
1419
|
+
* (the single source of truth the runtime router also derives from). Absent →
|
|
1420
|
+
* RPC-only (the default; not on the REST surface).
|
|
1421
|
+
*
|
|
1422
|
+
* `cache` mirrors the `RestCacheConfig` the runtime turns into response
|
|
1423
|
+
* headers, so the emitted spec can document the `Cache-Control` a caller will
|
|
1424
|
+
* actually observe. Only statically-readable literal fields are carried; a
|
|
1425
|
+
* computed value is simply absent (the spec under-documents rather than lies).
|
|
1426
|
+
*/
|
|
1427
|
+
expose?: {
|
|
1428
|
+
cache?: ExposeCacheIR;
|
|
1429
|
+
rest?: boolean;
|
|
1430
|
+
};
|
|
1431
|
+
/** Path relative to `<projectRoot>/lunora/` without extension, e.g. "messages". */
|
|
1432
|
+
filePath: string;
|
|
1433
|
+
kind: "action" | "mutation" | "query" | "stream";
|
|
1434
|
+
/**
|
|
1435
|
+
* Set on lifecycle hooks (`onConnect`/`onDisconnect`/`onShardInit`/
|
|
1436
|
+
* `onQueryChange`/`onWhisper`): the moment the hook fires on. Such a function
|
|
1437
|
+
* is also an internal registration (so it lands in `LUNORA_FUNCTIONS` for path
|
|
1438
|
+
* dispatch) — a mutation for every moment except `whisper`, which is a query;
|
|
1439
|
+
* emit additionally collects it into the `LUNORA_LIFECYCLE_HOOKS` manifest
|
|
1440
|
+
* keyed by this moment. Absent on ordinary functions.
|
|
1441
|
+
*/
|
|
1442
|
+
lifecycle?: "connect" | "disconnect" | "init" | "reactor" | "whisper";
|
|
1443
|
+
/**
|
|
1444
|
+
* The `.output(validator)` declaration, when the chain has one.
|
|
1445
|
+
*
|
|
1446
|
+
* Takes precedence over {@link FunctionIR.returnType} (the handler's
|
|
1447
|
+
* inferred type) for the emitted `FunctionReference`. `.output()` is what
|
|
1448
|
+
* validates at runtime and what a reader takes as the contract, so the two
|
|
1449
|
+
* must agree — see the emit-side note for what went wrong when they did not.
|
|
1450
|
+
*/
|
|
1451
|
+
output?: ValidatorIR;
|
|
1452
|
+
/**
|
|
1453
|
+
* Serialized TS source for the handler's return type, with `Promise<T>`
|
|
1454
|
+
* unwrapped so callers see `T` directly. Defaults to `"unknown"` when
|
|
1455
|
+
* ts-morph cannot resolve the type (typically because the consuming
|
|
1456
|
+
* project lacks a tsconfig that can reach `@lunora/server`).
|
|
1457
|
+
*/
|
|
1458
|
+
returnType: string;
|
|
1459
|
+
/**
|
|
1460
|
+
* Call surface the function is exposed on. Absent (or `"public"`) means it
|
|
1461
|
+
* lands in the generated `api`; `"internal"` routes it to the separate
|
|
1462
|
+
* `internal` object and is rejected by the DO's external RPC path.
|
|
1463
|
+
*/
|
|
1464
|
+
visibility?: "internal" | "public";
|
|
1683
1465
|
}
|
|
1684
1466
|
/**
|
|
1685
|
-
*
|
|
1686
|
-
*
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1467
|
+
* A `defineShape({...})` declaration discovered in `lunora/shapes.ts`
|
|
1468
|
+
* (local-first sync engine, Phase 7). The emitted `LUNORA_SHAPES` registry keys
|
|
1469
|
+
* on {@link ShapeIR.exportName}; the generated DO's `resolveShape` override
|
|
1470
|
+
* dispatches a `shape_subscribe` to the matching registered shape. Discovery is
|
|
1471
|
+
* marker-driven (the `__lunoraShape` brand) — no field metadata is lifted here
|
|
1472
|
+
* because the runtime object (`columns`/`compileWhere`) carries the authority.
|
|
1689
1473
|
*/
|
|
1690
|
-
interface
|
|
1691
|
-
/**
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
/**
|
|
1698
|
-
|
|
1474
|
+
interface ShapeIR {
|
|
1475
|
+
/**
|
|
1476
|
+
* The shape's `args` validator map — its partition selector. Lifted so
|
|
1477
|
+
* `_generated/collections.ts` can type the selector a caller passes instead of
|
|
1478
|
+
* widening it to `Record<string, unknown>`. `{}` for a parameterless shape.
|
|
1479
|
+
*/
|
|
1480
|
+
args: Record<string, ValidatorIR>;
|
|
1481
|
+
/** Export binding name — the shape's registry key and import member. */
|
|
1482
|
+
exportName: string;
|
|
1483
|
+
/** Path relative to `<projectRoot>/lunora/` without extension — always `"shapes"`. */
|
|
1484
|
+
filePath: string;
|
|
1485
|
+
/**
|
|
1486
|
+
* The `table` string literal from the `defineShape({ table })` call, lifted
|
|
1487
|
+
* only for static advisor lints (the runtime object stays authoritative).
|
|
1488
|
+
* `undefined` when `table` is not a plain string literal — lints skip those.
|
|
1489
|
+
*/
|
|
1490
|
+
table?: string;
|
|
1699
1491
|
}
|
|
1700
1492
|
/**
|
|
1701
|
-
*
|
|
1702
|
-
*
|
|
1703
|
-
*
|
|
1704
|
-
*
|
|
1705
|
-
*
|
|
1706
|
-
*
|
|
1707
|
-
* identical to
|
|
1493
|
+
* The single `defineIdentity({...})` claim contract discovered in
|
|
1494
|
+
* `lunora/identity.ts`. Discovery is **marker-driven** (the `__lunoraIdentity`
|
|
1495
|
+
* brand, exactly like {@link ShapeIR}) — no claim metadata is lifted here
|
|
1496
|
+
* because the emitted `_generated/server.ts` recovers the claim *type* from the
|
|
1497
|
+
* declaration itself (`InferIdentity` over the contract's `typeof`), and the
|
|
1498
|
+
* runtime object (`validate`/`onInvalid`) carries the authority at the boundary.
|
|
1499
|
+
* Exactly one per app; absent ⇒ generated output is byte-identical to today.
|
|
1708
1500
|
*/
|
|
1709
|
-
interface
|
|
1710
|
-
/**
|
|
1711
|
-
|
|
1712
|
-
/** 1-based line of the interpolation, or `0` when unknown. */
|
|
1713
|
-
line: number;
|
|
1714
|
-
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1715
|
-
scope: CallSiteScope;
|
|
1501
|
+
interface IdentityIR {
|
|
1502
|
+
/** Export binding name — the namespace member `_generated/server.ts` reads via `typeof`. */
|
|
1503
|
+
exportName: string;
|
|
1716
1504
|
}
|
|
1717
1505
|
/**
|
|
1718
|
-
*
|
|
1719
|
-
*
|
|
1720
|
-
*
|
|
1721
|
-
*
|
|
1722
|
-
*
|
|
1723
|
-
*
|
|
1724
|
-
* identical to `AdvisorArgumentDerivedFetch`.
|
|
1506
|
+
* The single `defineEnv({...})` contract discovered in `lunora/env.ts`. Like
|
|
1507
|
+
* {@link IdentityIR}, only the export binding is lifted — the emitted
|
|
1508
|
+
* `_generated/server.ts` recovers the validated shape from the declaration
|
|
1509
|
+
* itself (`ReturnType` over the accessor's `typeof`), and the generated ShardDO
|
|
1510
|
+
* applies the same accessor to the worker `env` at ctx-build time to populate
|
|
1511
|
+
* `ctx.env`. Exactly one per app; absent ⇒ generated output is byte-identical.
|
|
1725
1512
|
*/
|
|
1726
|
-
interface
|
|
1727
|
-
/**
|
|
1728
|
-
|
|
1729
|
-
/** 1-based line of the `ctx.fetch` call, or `0` when unknown. */
|
|
1730
|
-
line: number;
|
|
1731
|
-
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1732
|
-
scope: CallSiteScope;
|
|
1513
|
+
interface EnvIR {
|
|
1514
|
+
/** Export binding name — the namespace member `_generated/server.ts` reads via `typeof`. */
|
|
1515
|
+
exportName: string;
|
|
1733
1516
|
}
|
|
1734
1517
|
/**
|
|
1735
|
-
*
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1739
|
-
*
|
|
1740
|
-
*
|
|
1741
|
-
*
|
|
1742
|
-
* prefix, not a per-entry key). Structurally identical to `AdvisorKvKeyAccess`.
|
|
1518
|
+
* A `defineMutator({...})` declaration discovered in `lunora/mutators.ts`
|
|
1519
|
+
* (local-first sync engine, Phase 7). The emitted registry registers the
|
|
1520
|
+
* authoritative `server` impl into the DO's `LUNORA_FUNCTIONS` table (so
|
|
1521
|
+
* `handleRpc` transaction-wraps it) and records its path in
|
|
1522
|
+
* `LUNORA_MUTATOR_PATHS` so the DO's `isCustomMutator` override routes the
|
|
1523
|
+
* client-watermark push protocol. The client `client` impl is split into the
|
|
1524
|
+
* browser bundle separately — only the path crosses to the server side.
|
|
1743
1525
|
*/
|
|
1744
|
-
interface
|
|
1745
|
-
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1746
|
-
file: string;
|
|
1747
|
-
/** 1-based line of the `ctx.kv` call, or `0` when unknown. */
|
|
1748
|
-
line: number;
|
|
1749
|
-
/** The `ctx.kv` method invoked: `get` / `getRaw` / `getWithMetadata` / `put` / `delete`. */
|
|
1750
|
-
method: string;
|
|
1751
|
-
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1752
|
-
scope: CallSiteScope;
|
|
1526
|
+
interface MutatorIR {
|
|
1753
1527
|
/**
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1758
|
-
* registered procedure the feeder could attribute it to.
|
|
1528
|
+
* The mutator's `args` validator map, parsed exactly as a procedure's is, so
|
|
1529
|
+
* the emitted `api.mutators.<name>` reference carries the arg type a client
|
|
1530
|
+
* `defineMutator` infers instead of restating. `{}` for a parameterless
|
|
1531
|
+
* mutator (or one whose `args` isn't an inline object literal).
|
|
1759
1532
|
*/
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
/**
|
|
1763
|
-
* One `ctx.db` write (`insert` / `replace` / `patch` / `insertManyUnsafe`) that sets
|
|
1764
|
-
* an ownership / identity column — `userId`, `ownerId`, `tenantId`, and the like —
|
|
1765
|
-
* from the handler's `args` instead of the server-trusted identity. The
|
|
1766
|
-
* `owner_field_from_args_not_auth` lint input: the ownership column decides who a
|
|
1767
|
-
* row belongs to, so a value taken from request input lets any caller write rows
|
|
1768
|
-
* owned by another user or tenant (the act-as-any-user / cross-tenant IDOR vector).
|
|
1769
|
-
* A column stamped from `ctx.*`, or set to a fixed literal, is not recorded; only an
|
|
1770
|
-
* arg-derived identity write reaches here. Structurally identical to
|
|
1771
|
-
* `AdvisorOwnerFieldWrite`.
|
|
1772
|
-
*/
|
|
1773
|
-
/**
|
|
1774
|
-
* One branching `defineShape({ where })` / `definePolicy({ when })` predicate arm
|
|
1775
|
-
* that returns an unrestricted predicate — the `unrestricted_where_branch` lint
|
|
1776
|
-
* input. A denial arm must match NO rows (`deny()` / `{ OR: [] }`); `{}` matches
|
|
1777
|
-
* every row, so the near-miss silently replicates the whole table.
|
|
1778
|
-
*/
|
|
1779
|
-
interface UnrestrictedWhereBranchIR {
|
|
1780
|
-
/** Export binding name of the shape / policy the predicate belongs to. */
|
|
1533
|
+
args: Record<string, ValidatorIR>;
|
|
1534
|
+
/** Export binding name — the mutator's registry key and import member. */
|
|
1781
1535
|
exportName: string;
|
|
1782
|
-
/**
|
|
1783
|
-
|
|
1784
|
-
/**
|
|
1785
|
-
form: "empty-object" | "undefined";
|
|
1786
|
-
/** The config key carrying the predicate (`where` for a shape, `when` for a policy). */
|
|
1787
|
-
key: string;
|
|
1788
|
-
/** 1-based line of the offending returned expression. */
|
|
1789
|
-
line: number;
|
|
1790
|
-
/** The declaring call (`defineShape` / `definePolicy`). */
|
|
1791
|
-
owner: string;
|
|
1792
|
-
}
|
|
1793
|
-
interface OwnerFieldWriteIR {
|
|
1794
|
-
/**
|
|
1795
|
-
* Every procedure that reaches the write carries `.use(platformAdmin(...))`
|
|
1796
|
-
* (see {@link FunctionIR.adminOnly}): the caller is a platform admin by design.
|
|
1797
|
-
*/
|
|
1798
|
-
adminOnly?: true;
|
|
1799
|
-
/** The identity column being written from `args` (e.g. `userId`). */
|
|
1800
|
-
field: string;
|
|
1801
|
-
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1802
|
-
file: string;
|
|
1803
|
-
/**
|
|
1804
|
-
* The handler proves the value it writes equals the server identity before the
|
|
1805
|
-
* write: an `if (<args.field> !== <ctx identity>) throw` (also inside an `&&` or
|
|
1806
|
-
* `||` condition), or an `assert*(...)` call given both the argument and the
|
|
1807
|
-
* identity. The write is then validated, so `owner_field_from_args_not_auth`
|
|
1808
|
-
* declines to report it. Only facts that hold before the write count.
|
|
1809
|
-
*/
|
|
1810
|
-
guarded?: true;
|
|
1811
|
-
/** 1-based line of the `ctx.db` write call, or `0` when unknown. */
|
|
1536
|
+
/** Path relative to `<projectRoot>/lunora/` without extension — always `"mutators"`. */
|
|
1537
|
+
filePath: string;
|
|
1538
|
+
/** 1-based line of the `defineMutator(...)` call, for diagnostics and advisor findings. */
|
|
1812
1539
|
line: number;
|
|
1813
|
-
/** The `ctx.db` write method (`insert` / `replace` / `patch` / `insertManyUnsafe`). */
|
|
1814
|
-
method: string;
|
|
1815
1540
|
/**
|
|
1816
|
-
* The
|
|
1817
|
-
*
|
|
1818
|
-
*
|
|
1819
|
-
*
|
|
1820
|
-
*
|
|
1821
|
-
* `applyOwnerScope` requires a verified identity, rejects a client-supplied
|
|
1822
|
-
* value that disagrees with it, and overwrites the column with the verified
|
|
1823
|
-
* one before `server` runs — so on this exact shape `args[owner]` IS the
|
|
1824
|
-
* server identity, and it is what the docs prescribe. Recorded rather than
|
|
1825
|
-
* dropped at discovery: the write did happen, and the feeder is otherwise the
|
|
1826
|
-
* only place that knows. `owner_field_from_args_not_auth` is what declines to
|
|
1827
|
-
* report it.
|
|
1828
|
-
*
|
|
1829
|
-
* Deliberately NOT set when only the column NAME matches: `owner: "userId"`
|
|
1830
|
-
* launders `args.userId` and nothing else, so `{ userId: args.targetUserId }`
|
|
1831
|
-
* is a real IDOR and stays reportable.
|
|
1541
|
+
* The ownership column the declaration scopes the write to
|
|
1542
|
+
* (`defineMutator({ owner: "userId" })`), or `undefined` when it declares
|
|
1543
|
+
* none — which is also what the `mutator_without_owner_scope` lint reports,
|
|
1544
|
+
* since an unowned mutator is a public write endpoint. Only a string-literal
|
|
1545
|
+
* `owner` is lifted; a computed one reads as `undefined` rather than guessed.
|
|
1832
1546
|
*/
|
|
1833
|
-
|
|
1834
|
-
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1835
|
-
scope: CallSiteScope;
|
|
1547
|
+
owner?: string;
|
|
1836
1548
|
/**
|
|
1837
|
-
*
|
|
1838
|
-
*
|
|
1839
|
-
*
|
|
1840
|
-
* `owner_field_from_args_not_auth`. `undefined` when the write sits outside
|
|
1841
|
-
* any recognised procedure (a bare helper).
|
|
1549
|
+
* Serialized TS source for the authoritative `server` impl's return type,
|
|
1550
|
+
* `Promise<T>` unwrapped. `"unknown"` when ts-morph can't resolve it — same
|
|
1551
|
+
* contract as {@link FunctionIR.returnType}.
|
|
1842
1552
|
*/
|
|
1843
|
-
|
|
1553
|
+
returnType: string;
|
|
1844
1554
|
}
|
|
1845
1555
|
/**
|
|
1846
|
-
*
|
|
1847
|
-
*
|
|
1848
|
-
* `
|
|
1849
|
-
*
|
|
1850
|
-
*
|
|
1851
|
-
*
|
|
1852
|
-
*
|
|
1853
|
-
*
|
|
1556
|
+
* A whole-row `ctx.db.replace(id, document)` write discovered inside a custom
|
|
1557
|
+
* mutator's inline `server` impl (`lunora/mutators.ts`) — the input the
|
|
1558
|
+
* `mutator_full_row_replace` advisor lint consumes. A `replace` overwrites the
|
|
1559
|
+
* entire row, so a concurrent edit to a different column on a synced table is
|
|
1560
|
+
* clobbered; `ctx.db.patch(id, { field })` merges at the column level instead.
|
|
1561
|
+
* Structurally identical to `AdvisorMutatorWrite` so it passes straight through
|
|
1562
|
+
* to the advisor without conversion, exactly as `InsertWriteIR` does for
|
|
1563
|
+
* `AdvisorInsertWrite`.
|
|
1854
1564
|
*/
|
|
1855
|
-
interface
|
|
1856
|
-
/**
|
|
1565
|
+
interface MutatorWriteIR {
|
|
1566
|
+
/** The mutator export whose `server` impl performs the replace, e.g. `renameChannel`. */
|
|
1567
|
+
exportName: string;
|
|
1568
|
+
/** Openable source path the replace appears in — always `lunora/mutators.ts`. */
|
|
1857
1569
|
file: string;
|
|
1858
|
-
/** 1-based line of the
|
|
1570
|
+
/** 1-based line of the `replace(...)` call. */
|
|
1859
1571
|
line: number;
|
|
1860
|
-
/** The bucket method invoked with the arg-derived key, e.g. `get` / `put` / `delete` / `download`. */
|
|
1861
|
-
method: string;
|
|
1862
|
-
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1863
|
-
scope: CallSiteScope;
|
|
1864
|
-
/**
|
|
1865
|
-
* Visibility of the enclosing procedure. `internal` procedures have no
|
|
1866
|
-
* untrusted caller by construction — see `owner_field_from_args_not_auth`'s
|
|
1867
|
-
* identical split — so `storage_key_from_user_args` drops the finding to
|
|
1868
|
-
* INFO rather than ERROR there. `undefined` when the access sits outside any
|
|
1869
|
-
* registered procedure the feeder could attribute it to.
|
|
1870
|
-
*/
|
|
1871
|
-
visibility?: "internal" | "public";
|
|
1872
1572
|
}
|
|
1873
1573
|
/**
|
|
1874
|
-
*
|
|
1875
|
-
* from
|
|
1876
|
-
* `
|
|
1877
|
-
*
|
|
1878
|
-
*
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
1881
|
-
* key reaches here. `.any()`/`.pool()` take no key and are not sinks. Structurally
|
|
1882
|
-
* identical to `AdvisorContainerKeyAccess`.
|
|
1574
|
+
* Where a queue or workflow `handler:` passed by reference is declared, when it is
|
|
1575
|
+
* exported from a lunora source file — `defineWorkflow({ handler: onboard })` with
|
|
1576
|
+
* `onboard` exported from `lunora/onboarding/flow.ts` gives
|
|
1577
|
+
* `{ exportName: "onboard", file: "onboarding/flow" }`. The architecture graph
|
|
1578
|
+
* attributes that export's own call sites to the queue or workflow node, so its
|
|
1579
|
+
* calls are not reported as "not a registered function". `exportName` is the
|
|
1580
|
+
* exported name (`export { run as start }` → `start`).
|
|
1883
1581
|
*/
|
|
1884
|
-
interface
|
|
1582
|
+
interface HandlerSiteIR {
|
|
1583
|
+
exportName: string;
|
|
1885
1584
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1886
1585
|
file: string;
|
|
1887
|
-
/** 1-based line of the `ctx.containers.*.get` call, or `0` when unknown. */
|
|
1888
|
-
line: number;
|
|
1889
|
-
/** The container accessor method invoked — always `get`. */
|
|
1890
|
-
method: string;
|
|
1891
|
-
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1892
|
-
scope: CallSiteScope;
|
|
1893
1586
|
}
|
|
1894
1587
|
/**
|
|
1895
|
-
*
|
|
1896
|
-
* `
|
|
1897
|
-
*
|
|
1898
|
-
*
|
|
1899
|
-
*
|
|
1900
|
-
* A fixed literal model, or one scoped by a server-trusted `ctx.*` value, is not recorded;
|
|
1901
|
-
* only an arg-derived, unscoped model id reaches here (an arg-derived `inputs` argument is
|
|
1902
|
-
* normal usage and is never inspected). Structurally identical to `AdvisorAiRawRun`.
|
|
1588
|
+
* A typed REST route declared with the `httpRoute.<verb>("/path")…` builder in
|
|
1589
|
+
* `@lunora/server` and mounted on `httpRouter()`. Captured statically from the
|
|
1590
|
+
* builder chain so the OpenAPI emitter can render a real `paths` entry: the verb
|
|
1591
|
+
* + path become the operation's method + URL, and the accumulated validator maps
|
|
1592
|
+
* become its query parameters, path parameters, and request body.
|
|
1903
1593
|
*/
|
|
1904
|
-
interface
|
|
1905
|
-
/**
|
|
1906
|
-
|
|
1907
|
-
/**
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1594
|
+
interface HttpRouteIR {
|
|
1595
|
+
/** `v.*` validators decoding the JSON request body (`.body({...})`), keyed by field. */
|
|
1596
|
+
body: Record<string, ValidatorIR>;
|
|
1597
|
+
/**
|
|
1598
|
+
* Rendered TS type of one SSE chunk — the `R` the `.stream(handler)`
|
|
1599
|
+
* handler yields — inferred from the handler via the type checker. Present
|
|
1600
|
+
* only when {@link HttpRouteIR.stream} is `true`; `"unknown"` when the
|
|
1601
|
+
* checker can't resolve enough context. Feeds the emitted
|
|
1602
|
+
* `HttpStreamRef<Chunk, …>` so the chunk type flows to the client.
|
|
1603
|
+
* @experimental Part of the HTTP-SSE stream surface (the `httpStreams.*` emission).
|
|
1604
|
+
*/
|
|
1605
|
+
chunkType?: string;
|
|
1606
|
+
/** Export binding name of the route handler (used only for diagnostics / dedupe). */
|
|
1607
|
+
exportName: string;
|
|
1608
|
+
/** Path relative to `<projectRoot>/lunora/` without extension, e.g. "http". */
|
|
1609
|
+
filePath: string;
|
|
1610
|
+
/** HTTP verb the route binds to (uppercased), e.g. `"GET"`. */
|
|
1611
|
+
method: string;
|
|
1612
|
+
/** The `.output(validator)` return schema when declared; absent ⇒ TS-inferred / best-effort. */
|
|
1613
|
+
output?: ValidatorIR;
|
|
1614
|
+
/** `v.*` validators decoding the hono path params (`.params({...})`), keyed by `:name`. */
|
|
1615
|
+
params: Record<string, ValidatorIR>;
|
|
1616
|
+
/** The route path passed to `httpRoute.<verb>(path)`, e.g. `/api/todos/:id`. */
|
|
1617
|
+
path: string;
|
|
1618
|
+
/** `v.*` validators decoding the URL query string (`.searchParams({...})`), keyed by name. */
|
|
1619
|
+
searchParams: Record<string, ValidatorIR>;
|
|
1620
|
+
/** `true` when declared via the terminal `.stream(...)` (Server-Sent Events) rather than `.handler(...)`. */
|
|
1621
|
+
stream: boolean;
|
|
1911
1622
|
}
|
|
1912
1623
|
/**
|
|
1913
|
-
*
|
|
1914
|
-
*
|
|
1915
|
-
* `
|
|
1916
|
-
*
|
|
1917
|
-
*
|
|
1918
|
-
*
|
|
1919
|
-
*
|
|
1920
|
-
*
|
|
1624
|
+
* Per-procedure protective-middleware snapshot, produced by
|
|
1625
|
+
* `discoverProcedureMiddleware` for the security lints
|
|
1626
|
+
* (`public_mutation_without_ratelimit`, `user_creating_mutation_without_captcha`).
|
|
1627
|
+
* Records which `.use(...)` guards a procedure's builder chain carries plus the
|
|
1628
|
+
* behavioural facts that decide whether a guard is *expected* (does it write a
|
|
1629
|
+
* user/session table, does it send mail). `protectPublic({ rateLimit, captcha })`
|
|
1630
|
+
* is unwrapped: the bundle's object-literal keys set `usesRateLimit`/`usesCaptcha`
|
|
1631
|
+
* exactly as the individual `.use(rateLimit(...))` / `.use(verifyTurnstile(...))`
|
|
1632
|
+
* steps would. Structurally identical to `AdvisorProcedureProtection` so values
|
|
1633
|
+
* pass straight through to the advisor without conversion.
|
|
1921
1634
|
*/
|
|
1922
|
-
interface
|
|
1635
|
+
interface ProcedureMiddlewareIR {
|
|
1636
|
+
/**
|
|
1637
|
+
* `true` when the handler body could be read statically — an inline function
|
|
1638
|
+
* expression/arrow, or a same-file identifier resolved to one. `false` for a
|
|
1639
|
+
* genuinely cross-file handler (an imported function, or an identifier that
|
|
1640
|
+
* doesn't resolve in this file), in which case every behavioural fact below
|
|
1641
|
+
* is `undefined` rather than a false "not observed" — the feeder never saw
|
|
1642
|
+
* the body, so it has nothing to report.
|
|
1643
|
+
*/
|
|
1644
|
+
analyzableBody: boolean;
|
|
1645
|
+
/** `true` when the handler (or a helper inside it) references `ctx.mail` / `ctx.email`. `undefined` when `analyzableBody` is `false`. */
|
|
1646
|
+
callsMail?: boolean;
|
|
1647
|
+
/** `true` when the handler emits a structured observability event (`ctx.log` / `ctx.span` / `ctx.trace`). */
|
|
1648
|
+
emitsEvent?: boolean;
|
|
1649
|
+
/** `true` when a `// lunora-advisor-exempt` directive sits above the export. */
|
|
1650
|
+
exempt: boolean;
|
|
1651
|
+
/** The `-- reason` from that directive, or `""`. */
|
|
1652
|
+
exemptReason: string;
|
|
1653
|
+
/** Export binding name of the procedure (e.g. `signUp`). */
|
|
1654
|
+
exportName: string;
|
|
1655
|
+
/** `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. `undefined` when `analyzableBody` is `false`. */
|
|
1656
|
+
fanOut?: boolean;
|
|
1923
1657
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1924
1658
|
file: string;
|
|
1925
|
-
/**
|
|
1926
|
-
|
|
1927
|
-
/**
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
*/
|
|
1942
|
-
|
|
1943
|
-
/**
|
|
1944
|
-
|
|
1945
|
-
/**
|
|
1946
|
-
|
|
1947
|
-
/**
|
|
1948
|
-
|
|
1949
|
-
/**
|
|
1950
|
-
|
|
1659
|
+
/** `true` when the handler wraps work in `try`/`catch`. */
|
|
1660
|
+
handlesErrors?: boolean;
|
|
1661
|
+
/**
|
|
1662
|
+
* `true` when the procedure declares an email-shaped argument (`email`,
|
|
1663
|
+
* `emailAddress`, `userEmail`, …), `false` when it provably declares none,
|
|
1664
|
+
* and **absent** when the argument list can't be read statically (a
|
|
1665
|
+
* `.input(sharedSchema)`, a spread, or a factory whose `args` comes from a
|
|
1666
|
+
* variable). Feeds `signup_mutation_without_disposable_gating`, which can
|
|
1667
|
+
* only be actioned when there is an address to gate — so "unreadable" must
|
|
1668
|
+
* stay distinguishable from "none", or the lint would clear itself on a
|
|
1669
|
+
* registration that may well expose one.
|
|
1670
|
+
*/
|
|
1671
|
+
hasEmailArg?: boolean;
|
|
1672
|
+
kind: "action" | "mutation" | "query";
|
|
1673
|
+
/** `true` when the handler reaches an outbound surface (`ctx.fetch`, mail, queues, storage, sql, ai, …) that can fail. */
|
|
1674
|
+
reachesOutbound?: boolean;
|
|
1675
|
+
/** `true` when the handler runs any AI generation, bounded or not. */
|
|
1676
|
+
runsAiGeneration?: boolean;
|
|
1677
|
+
/** `true` when the handler throws a bare `new Error(...)` rather than a coded `LunoraError`. */
|
|
1678
|
+
throwsBareError?: boolean;
|
|
1679
|
+
/** `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. `undefined` when `analyzableBody` is `false`. */
|
|
1680
|
+
unboundedAiGeneration?: boolean;
|
|
1681
|
+
/** `true` when the chain carries `.use(verifyTurnstile(...))` or a `protectPublic({ captcha })` bundle. */
|
|
1682
|
+
usesCaptcha: boolean;
|
|
1683
|
+
/** `true` when the chain carries `.use(emailGateMiddleware(...))` (`@lunora/auth`). Feeds the `signup_mutation_without_disposable_gating` lint. */
|
|
1684
|
+
usesEmailGate: boolean;
|
|
1685
|
+
/** `true` when the handler calls `ctx.db.insertManyUnsafe(...)`, bypassing validators and triggers. Feeds the `insert_many_unsafe_user_data` lint. `undefined` when `analyzableBody` is `false`. */
|
|
1686
|
+
usesInsertManyUnsafe?: boolean;
|
|
1687
|
+
/** `true` when the chain carries `.use(mask(...))`. */
|
|
1688
|
+
usesMask: boolean;
|
|
1689
|
+
/** `true` when the chain carries `.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle. */
|
|
1690
|
+
usesRateLimit: boolean;
|
|
1691
|
+
/** `true` when the chain carries `.use(rls(...))`. */
|
|
1692
|
+
usesRls: boolean;
|
|
1693
|
+
/** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
|
|
1694
|
+
visibility: "internal" | "public";
|
|
1695
|
+
/** `true` when the handler inserts into a user/session/account-shaped table. `undefined` when `analyzableBody` is `false`. */
|
|
1696
|
+
writesUserTable?: boolean;
|
|
1951
1697
|
}
|
|
1952
1698
|
/**
|
|
1953
|
-
*
|
|
1954
|
-
*
|
|
1955
|
-
* `
|
|
1956
|
-
*
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1699
|
+
* Per-procedure argument-validator snapshot, produced by the
|
|
1700
|
+
* argument-validator discoverer for the input-hardening lints
|
|
1701
|
+
* (`public_arg_uses_any`, `unbounded_string_arg`). Only public procedures are
|
|
1702
|
+
* recorded — internal functions take server-trusted input. Structurally identical
|
|
1703
|
+
* to `AdvisorArgumentValidator` so it passes straight through to the advisor
|
|
1704
|
+
* without conversion.
|
|
1959
1705
|
*/
|
|
1960
|
-
interface
|
|
1706
|
+
interface ArgumentValidatorIR {
|
|
1707
|
+
/** Arg names declared as `v.any()` (unvalidated, untyped input). */
|
|
1708
|
+
anyArgs: string[];
|
|
1709
|
+
/** Export binding name of the procedure (e.g. `updateProfile`). */
|
|
1710
|
+
exportName: string;
|
|
1961
1711
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1962
1712
|
file: string;
|
|
1963
|
-
/** 1-based line of the
|
|
1713
|
+
/** 1-based line of the registration call, or `0` when unknown. */
|
|
1964
1714
|
line: number;
|
|
1965
|
-
/**
|
|
1966
|
-
|
|
1967
|
-
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
1968
|
-
scope: CallSiteScope;
|
|
1715
|
+
/** Arg names declared as `v.string()` with no statically-visible max-length bound. */
|
|
1716
|
+
unboundedStringArgs: string[];
|
|
1969
1717
|
}
|
|
1970
1718
|
/**
|
|
1971
|
-
* One
|
|
1972
|
-
*
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
1976
|
-
* lint) assumes is locked down. Matched structurally by call shape, independent of the
|
|
1977
|
-
* receiver's resolved type. Structurally identical to `AdvisorContainerOverride`.
|
|
1719
|
+
* One factory/constructor call in `lunora/` whose config object literal a
|
|
1720
|
+
* security lint inspects for a present-or-absent key — the shared input for the
|
|
1721
|
+
* config-call security lints (payment authorize, inbound-mail verify, rate-limit
|
|
1722
|
+
* store, browser private-targets). Structurally identical to `AdvisorConfigCall`
|
|
1723
|
+
* so it passes straight through to the advisor without conversion.
|
|
1978
1724
|
*/
|
|
1979
|
-
interface
|
|
1980
|
-
/**
|
|
1981
|
-
|
|
1725
|
+
interface ConfigCallIR {
|
|
1726
|
+
/** `true` when the config argument was a static object literal the feeder could read. */
|
|
1727
|
+
analyzable: boolean;
|
|
1728
|
+
/** The factory function or constructor name at the call site, e.g. `createPayment` / `RateLimiter`. */
|
|
1729
|
+
callee: string;
|
|
1982
1730
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
1983
1731
|
file: string;
|
|
1984
|
-
/**
|
|
1985
|
-
kind: "egress_relaxation" | "enable_internet";
|
|
1986
|
-
/** 1-based line of the call, or `0` when unknown. */
|
|
1732
|
+
/** 1-based line of the call site, or `0` when unknown. */
|
|
1987
1733
|
line: number;
|
|
1988
|
-
/**
|
|
1989
|
-
|
|
1734
|
+
/** Keys present in the config object literal (empty when not `analyzable`). */
|
|
1735
|
+
presentKeys: string[];
|
|
1736
|
+
/** Keys in the config object literal explicitly assigned the literal `true`. */
|
|
1737
|
+
trueKeys: string[];
|
|
1990
1738
|
}
|
|
1991
1739
|
/**
|
|
1992
|
-
* One
|
|
1993
|
-
* `
|
|
1994
|
-
*
|
|
1995
|
-
*
|
|
1996
|
-
* `ctx.images.transform`/`info` take image *bytes*, never a URL, so they are not
|
|
1997
|
-
* sinks; only the `key` of `buildImageDeliveryUrl` accepts a URL-or-key source
|
|
1998
|
-
* and is inspected. An arg-derived `key` lets any caller point the CDN's
|
|
1999
|
-
* `/cdn-cgi/image/` transform at an attacker-chosen origin (SSRF / open proxy)
|
|
2000
|
-
* or at an arbitrary key under the account's own store. A fixed literal, or a
|
|
2001
|
-
* key scoped by a server-trusted `ctx.*` value, is not recorded. Structurally
|
|
2002
|
-
* identical to `AdvisorImageDeliveryUrlAccess`.
|
|
1740
|
+
* One secret-shaped string literal discovered in `lunora/` source — the
|
|
1741
|
+
* `hardcoded_secret` lint input. Complements the pre-commit `vis secrets` scan by
|
|
1742
|
+
* surfacing the same class of finding in-IDE via the studio Advisors table.
|
|
1743
|
+
* Structurally identical to `AdvisorSecretLiteral`.
|
|
2003
1744
|
*/
|
|
2004
|
-
interface
|
|
1745
|
+
interface SecretLiteralIR {
|
|
2005
1746
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2006
1747
|
file: string;
|
|
2007
|
-
/**
|
|
1748
|
+
/** Heuristic that matched, e.g. `stripe_live_key` / `aws_access_key` / `pem_private_key` / `high_entropy`. */
|
|
1749
|
+
kind: string;
|
|
1750
|
+
/** 1-based line of the literal, or `0` when unknown. */
|
|
2008
1751
|
line: number;
|
|
2009
|
-
/**
|
|
2010
|
-
|
|
1752
|
+
/** Redacted preview of the literal (first few chars + length) for the finding detail — never the full secret. */
|
|
1753
|
+
preview: string;
|
|
2011
1754
|
}
|
|
2012
1755
|
/**
|
|
2013
|
-
* One `
|
|
2014
|
-
*
|
|
2015
|
-
*
|
|
2016
|
-
*
|
|
2017
|
-
*
|
|
2018
|
-
*
|
|
2019
|
-
*
|
|
2020
|
-
* and every boolean fact defaults to its SAFE (not-flagged) value — an opaque
|
|
2021
|
-
* config can't be relied on either way. Structurally identical to
|
|
2022
|
-
* `AdvisorAuthConfig`.
|
|
1756
|
+
* One `ctx.sql.query(text, …)` / `ctx.sql.unsafe(text, …)` call whose `text`
|
|
1757
|
+
* argument is built in place rather than passed as a fixed statement — the
|
|
1758
|
+
* `sql_injection_risk` lint input. The Hyperdrive driver binds ONLY the `params`
|
|
1759
|
+
* array; the `text` string is spliced verbatim into the SQL, so a `text` assembled
|
|
1760
|
+
* from a string concatenation or a substitution template literal is an injection
|
|
1761
|
+
* vector. A fixed string literal / no-substitution template is safe. Structurally
|
|
1762
|
+
* identical to `AdvisorSqlInterpolation`.
|
|
2023
1763
|
*/
|
|
2024
|
-
interface
|
|
2025
|
-
/** `true` when the call's config argument was a static object literal the feeder could read. */
|
|
2026
|
-
analyzable: boolean;
|
|
2027
|
-
/** `advanced.disableCSRFCheck === true`. */
|
|
2028
|
-
disableCsrfCheck: boolean;
|
|
2029
|
-
/** `emailAndPassword.enabled === true`. */
|
|
2030
|
-
emailPasswordEnabled: boolean;
|
|
1764
|
+
interface SqlInterpolationIR {
|
|
2031
1765
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2032
1766
|
file: string;
|
|
2033
|
-
/** 1-based line of the
|
|
1767
|
+
/** 1-based line of the interpolation, or `0` when unknown. */
|
|
2034
1768
|
line: number;
|
|
2035
|
-
/** `emailAndPassword.requireEmailVerification === true` present. */
|
|
2036
|
-
requireEmailVerification: boolean;
|
|
2037
|
-
/** `trustedOrigins` array literal contains a `"*"` element. */
|
|
2038
|
-
/** `plugins` includes `scim(...)` while `database` is a non-transactional Lunora adapter — a combination that throws at runtime. */
|
|
2039
|
-
scimOnNonTransactionalAdapter: boolean;
|
|
2040
1769
|
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
2041
1770
|
scope: CallSiteScope;
|
|
2042
|
-
/** `advanced.useSecureCookies === false`. */
|
|
2043
|
-
secureCookiesDisabled: boolean;
|
|
2044
|
-
/** `session.freshAge === 0` (explicit literal). */
|
|
2045
|
-
sessionFreshAgeZero: boolean;
|
|
2046
|
-
trustedOriginsWildcard: boolean;
|
|
2047
1771
|
}
|
|
2048
1772
|
/**
|
|
2049
|
-
* One `
|
|
2050
|
-
* `
|
|
2051
|
-
*
|
|
2052
|
-
*
|
|
2053
|
-
*
|
|
2054
|
-
*
|
|
2055
|
-
*
|
|
2056
|
-
* `ctx.ip`), or one with no `args` reference at all (a fixed/global bucket —
|
|
2057
|
-
* the "no key" case this lint deliberately does not flag, to keep it low-FP),
|
|
2058
|
-
* is not recorded. Structurally identical to `AdvisorRatelimitKeySelector`.
|
|
1773
|
+
* One `ctx.fetch(url, …)` call inside an action whose URL argument is derived
|
|
1774
|
+
* from the handler's `args` — the `action_fetch_ssrf` lint input. `ctx.fetch` is
|
|
1775
|
+
* the action-only outbound-request escape hatch with no host allowlist, so a URL
|
|
1776
|
+
* assembled from request input is a server-side request forgery vector (cloud
|
|
1777
|
+
* metadata endpoints, internal services). Only arg-derived URLs reach here; a
|
|
1778
|
+
* fixed literal or a URL built from config/`ctx.*` is not recorded. Structurally
|
|
1779
|
+
* identical to `AdvisorArgumentDerivedFetch`.
|
|
2059
1780
|
*/
|
|
2060
|
-
interface
|
|
2061
|
-
/** The `rateLimit`/`dbRateLimit` callee invoked. */
|
|
2062
|
-
callee: string;
|
|
1781
|
+
interface ArgumentDerivedFetchIR {
|
|
2063
1782
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2064
1783
|
file: string;
|
|
2065
|
-
/**
|
|
2066
|
-
limitName: string;
|
|
2067
|
-
/** 1-based line of the `rateLimit`/`dbRateLimit` call, or `0` when unknown. */
|
|
1784
|
+
/** 1-based line of the `ctx.fetch` call, or `0` when unknown. */
|
|
2068
1785
|
line: number;
|
|
2069
1786
|
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
2070
1787
|
scope: CallSiteScope;
|
|
2071
1788
|
}
|
|
2072
1789
|
/**
|
|
2073
|
-
* One
|
|
2074
|
-
*
|
|
2075
|
-
*
|
|
2076
|
-
*
|
|
2077
|
-
*
|
|
2078
|
-
*
|
|
2079
|
-
*
|
|
2080
|
-
*
|
|
2081
|
-
*
|
|
1790
|
+
* One `query` handler whose `return` hands back the raw rows of a table — the
|
|
1791
|
+
* result of a `ctx.db.<table>.findMany()` / `.findFirst()` / `.get()` read, or a
|
|
1792
|
+
* `ctx.db.query("<table>")…collect()` fluent chain — returned directly (or through
|
|
1793
|
+
* one local `const` hop) with no hand-built projection. The
|
|
1794
|
+
* output_projection_missing_on_public_read lint keeps only `visibility ===
|
|
1795
|
+
* "public"` rows with no `.output(...)` / `.use(mask(...))` on the chain, then
|
|
1796
|
+
* joins `table` against the schema and flags one whose columns are PII-named.
|
|
1797
|
+
* Structurally identical to `AdvisorRawRowReturn` so values pass straight through
|
|
1798
|
+
* without conversion.
|
|
2082
1799
|
*/
|
|
2083
|
-
interface
|
|
2084
|
-
/**
|
|
2085
|
-
|
|
1800
|
+
interface RawRowReturnIR {
|
|
1801
|
+
/** Export binding name of the query returning the raw rows. */
|
|
1802
|
+
exportName: string;
|
|
2086
1803
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2087
1804
|
file: string;
|
|
2088
|
-
/** 1-based line of the
|
|
1805
|
+
/** 1-based line of the `return` (or concise-body) expression. */
|
|
2089
1806
|
line: number;
|
|
2090
|
-
/**
|
|
2091
|
-
|
|
2092
|
-
/**
|
|
2093
|
-
|
|
2094
|
-
/**
|
|
2095
|
-
|
|
1807
|
+
/** Table whose raw rows are returned, or `""` when the read's table wasn't a string literal. */
|
|
1808
|
+
table: string;
|
|
1809
|
+
/** `true` when the procedure's builder chain carries a `.use(mask(...))` step. */
|
|
1810
|
+
usesMask: boolean;
|
|
1811
|
+
/** `true` when the procedure's builder chain carries an `.output(...)` return-shape projection. */
|
|
1812
|
+
usesOutput: boolean;
|
|
1813
|
+
/** `"internal"` for `internalQuery`; `"public"` for `query`. */
|
|
1814
|
+
visibility: "internal" | "public";
|
|
2096
1815
|
}
|
|
2097
1816
|
/**
|
|
2098
|
-
*
|
|
2099
|
-
*
|
|
2100
|
-
*
|
|
2101
|
-
* `
|
|
1817
|
+
* A single cron job lifted from a `cronJobs()` builder in `lunora/crons.ts`.
|
|
1818
|
+
* Mirrors `@lunora/scheduler`'s `CronJob`: {@link CronJobIR.cron} is the compiled
|
|
1819
|
+
* standard cron expression, {@link CronJobIR.functionPath} is the target
|
|
1820
|
+
* `__lunoraRef` (`namespace:fn`), and {@link CronJobIR.args} is the static
|
|
1821
|
+
* argument object passed at registration.
|
|
2102
1822
|
*/
|
|
2103
|
-
interface
|
|
2104
|
-
/**
|
|
2105
|
-
|
|
2106
|
-
/**
|
|
2107
|
-
|
|
2108
|
-
/**
|
|
1823
|
+
interface CronJobIR {
|
|
1824
|
+
/** Static args object (source-text JSON), defaults to `{}`. */
|
|
1825
|
+
args: Record<string, unknown>;
|
|
1826
|
+
/** Compiled standard cron expression, e.g. `"0 9 * * *"`. */
|
|
1827
|
+
cron: string;
|
|
1828
|
+
/** Target function ref `namespace:fn`. Present for a function target; absent when {@link CronJobIR.workflow} is set. */
|
|
1829
|
+
functionPath?: string;
|
|
1830
|
+
/** Unique, human-readable job name. */
|
|
1831
|
+
name: string;
|
|
1832
|
+
/**
|
|
1833
|
+
* Set when the job targets a durable workflow (a `lunora/workflows.ts`
|
|
1834
|
+
* export) instead of a function: the workflow's class name (its
|
|
1835
|
+
* `ctx.exports` key) plus its export name. On each fire the worker starts a new workflow
|
|
1836
|
+
* INSTANCE (the {@link CronJobIR.args} become its `params`) rather than
|
|
1837
|
+
* dispatching a one-shot function.
|
|
1838
|
+
*/
|
|
1839
|
+
workflow?: {
|
|
1840
|
+
className: string;
|
|
1841
|
+
exportName: string;
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
/** A normalized container image source, as written into `wrangler.jsonc`. */
|
|
1845
|
+
type ContainerImageIR = {
|
|
1846
|
+
buildContext: string;
|
|
1847
|
+
dockerfilePath: string;
|
|
1848
|
+
kind: "dockerfile";
|
|
1849
|
+
} | {
|
|
1850
|
+
buildDir: string;
|
|
1851
|
+
kind: "build";
|
|
1852
|
+
} | {
|
|
1853
|
+
kind: "registry";
|
|
1854
|
+
reference: string;
|
|
1855
|
+
};
|
|
1856
|
+
/** A `durable_object` container's named image — a Dockerfile or a registry digest, never a Railpack build. */
|
|
1857
|
+
type ContainerNamedImageIR = Exclude<ContainerImageIR, {
|
|
1858
|
+
kind: "build";
|
|
1859
|
+
}>;
|
|
1860
|
+
/** The fields every container carries, whichever scheduling policy it uses. */
|
|
1861
|
+
interface ContainerIRBase {
|
|
1862
|
+
/** Durable Object binding name, e.g. `CONTAINER_TRANSCODER`. */
|
|
1863
|
+
bindingName: string;
|
|
1864
|
+
/** Static Dockerfile build args (wrangler `image_vars` / named-image `build_vars`), when declared as literals. */
|
|
1865
|
+
buildArgs?: Record<string, string>;
|
|
1866
|
+
/** Generated DO class name, e.g. `TranscoderContainer`. */
|
|
1867
|
+
className: string;
|
|
1868
|
+
/**
|
|
1869
|
+
* Whether the container may open outbound internet connections, when the
|
|
1870
|
+
* value was a static literal. `undefined` means the field was omitted (the
|
|
1871
|
+
* platform default is `true`) or wasn't a literal. Lifted for the advisor.
|
|
1872
|
+
*/
|
|
1873
|
+
enableInternet?: boolean;
|
|
1874
|
+
/** The `lunora/containers.ts` export name, e.g. `transcoder`. */
|
|
1875
|
+
exportName: string;
|
|
1876
|
+
/** Static `instanceType`, when declared (the default size, under `durable_object`). */
|
|
1877
|
+
instanceType?: string | {
|
|
1878
|
+
diskMb?: number;
|
|
1879
|
+
memoryMib?: number;
|
|
1880
|
+
vcpu?: number;
|
|
1881
|
+
};
|
|
1882
|
+
/** Static wrangler `containers[].name` override, when declared. */
|
|
1883
|
+
name?: string;
|
|
1884
|
+
/**
|
|
1885
|
+
* `true` when the definition opts in to the Sandbox SDK helpers
|
|
1886
|
+
* (`defineContainer({ sandbox: true })`). Must be a literal: it decides
|
|
1887
|
+
* whether the generated `containers.ts` exports the sandbox gateways, and
|
|
1888
|
+
* it raises the `containerSandboxTools` platform signal.
|
|
1889
|
+
*/
|
|
1890
|
+
sandbox?: boolean;
|
|
1891
|
+
/**
|
|
1892
|
+
* The static `sleepAfter` value, when it was a literal. `undefined` means
|
|
1893
|
+
* omitted (platform default `"10m"`) or non-literal. Lifted for the advisor.
|
|
1894
|
+
*/
|
|
1895
|
+
sleepAfter?: number | string;
|
|
1896
|
+
}
|
|
1897
|
+
/** A container under the default scheduling policy: one application image, a cap, and a rollout. */
|
|
1898
|
+
interface DefaultScheduledContainerIR extends ContainerIRBase {
|
|
1899
|
+
/**
|
|
1900
|
+
* Normalized image source: a local Dockerfile (`dockerfile`), a pre-built
|
|
1901
|
+
* registry reference (`registry`), or a Railpack source directory (`build`)
|
|
1902
|
+
* that the deploy step builds and pushes before wrangler runs.
|
|
1903
|
+
*/
|
|
1904
|
+
image: ContainerImageIR;
|
|
1905
|
+
/** Static `maxInstances`, when declared. */
|
|
1906
|
+
maxInstances?: number;
|
|
1907
|
+
/** Static rolling-deploy tuning, when declared as literals. */
|
|
1908
|
+
rollout?: {
|
|
1909
|
+
gracePeriodSeconds?: number;
|
|
1910
|
+
stepPercentage?: number;
|
|
1911
|
+
};
|
|
1912
|
+
/** Absent under the default policy — the union's discriminant. */
|
|
1913
|
+
schedulingPolicy?: never;
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* A container under the `durable_object` scheduling policy (wrangler
|
|
1917
|
+
* `scheduling_policy`): each instance picks its image and size per start. Also
|
|
1918
|
+
* the `containerRuntimeScheduling` platform signal.
|
|
1919
|
+
*/
|
|
1920
|
+
interface DurableObjectScheduledContainerIR extends ContainerIRBase {
|
|
1921
|
+
/** The named images an instance can start from (wrangler `containers[].images`). */
|
|
1922
|
+
images?: Record<string, ContainerNamedImageIR>;
|
|
1923
|
+
schedulingPolicy: "durable_object";
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* A container lifted from a `defineContainer()` export in
|
|
1927
|
+
* `lunora/containers.ts`, discriminated on its scheduling policy. Carries
|
|
1928
|
+
* everything the emitters and the config layer need to wire wrangler
|
|
1929
|
+
* (`containers[]` + the Durable Object binding + migration class) and the
|
|
1930
|
+
* generated `_generated/containers.ts` DO class. Names are derived via
|
|
1931
|
+
* `@lunora/container`'s shared helpers so codegen and the config layer can
|
|
1932
|
+
* never disagree.
|
|
1933
|
+
*/
|
|
1934
|
+
type ContainerIR = DefaultScheduledContainerIR | DurableObjectScheduledContainerIR;
|
|
1935
|
+
/**
|
|
1936
|
+
* A workflow lifted from a `defineWorkflow()` export in `lunora/workflows.ts`.
|
|
1937
|
+
* Carries what the emitters and the config layer need to wire the wrangler
|
|
1938
|
+
* `workflows[]` entry and the generated `_generated/workflows.ts`
|
|
1939
|
+
* `WorkflowEntrypoint` class. Unlike containers, workflows are NOT Durable
|
|
1940
|
+
* Objects — wrangler gets only a `workflows[]` entry, never a `durable_objects`
|
|
1941
|
+
* binding or a migration class. Names are derived via `@lunora/workflow`'s
|
|
1942
|
+
* shared helpers so codegen and the config layer can never disagree.
|
|
1943
|
+
*/
|
|
1944
|
+
interface WorkflowIR {
|
|
1945
|
+
/** Generated `WorkflowEntrypoint` class name, e.g. `OrderPipelineWorkflow` — also its `exports` key and `ctx.exports` property. */
|
|
1946
|
+
className: string;
|
|
1947
|
+
/** Static `defaultRetention` literal → `exports.<Class>.default_retention`. */
|
|
1948
|
+
defaultRetention?: {
|
|
1949
|
+
errorRetention?: string;
|
|
1950
|
+
successRetention?: string;
|
|
1951
|
+
};
|
|
1952
|
+
/** The `lunora/workflows.ts` export name, e.g. `orderPipeline`. */
|
|
1953
|
+
exportName: string;
|
|
1954
|
+
/**
|
|
1955
|
+
* The file declaring the `handler:` when it is a function passed by reference
|
|
1956
|
+
* and declared in another file — the file {@link WorkflowIR.steps}' lines are
|
|
1957
|
+
* in. Lunora-relative inside `lunora/`, project-relative outside it.
|
|
1958
|
+
*/
|
|
1959
|
+
handlerFile?: string;
|
|
1960
|
+
/** See {@link HandlerSiteIR}. */
|
|
1961
|
+
handlerSite?: HandlerSiteIR;
|
|
1962
|
+
/** Static `limits` literal → `exports.<Class>.limits`. */
|
|
1963
|
+
limits?: {
|
|
1964
|
+
steps?: number;
|
|
1965
|
+
};
|
|
1966
|
+
/**
|
|
1967
|
+
* The stable wrangler `exports.<Class>.name`. Defaults to the kebab-cased export
|
|
1968
|
+
* name (`orderPipeline` → `order-pipeline`); a static `name:` literal in the
|
|
1969
|
+
* definition overrides it.
|
|
1970
|
+
*/
|
|
1971
|
+
name: string;
|
|
1972
|
+
/**
|
|
1973
|
+
* Static `schedules` cron literals → `exports.<Class>.schedules`. Each one
|
|
1974
|
+
* starts an instance on Cloudflare; gated by the `workflowSchedules`
|
|
1975
|
+
* capability, since a host that ignores them would never run the workflow.
|
|
1976
|
+
*/
|
|
1977
|
+
schedules?: ReadonlyArray<string>;
|
|
1978
|
+
/**
|
|
1979
|
+
* Durable step labels lifted from the handler body — the first string-literal
|
|
1980
|
+
* argument of every `ctx.step.do` / `.sleep` / `.sleepUntil` / `.waitForEvent`
|
|
1981
|
+
* call. Feeds the duplicate-step-name lint, which flags a name used twice
|
|
1982
|
+
* (Cloudflare memoizes by name, so the second call silently returns the
|
|
1983
|
+
* first's cached result). Calls with a non-literal name are omitted (not
|
|
1984
|
+
* statically comparable). A handler passed by reference is followed to its
|
|
1985
|
+
* declaration; the lines are then in {@link WorkflowIR.handlerFile}.
|
|
1986
|
+
*/
|
|
1987
|
+
steps: ReadonlyArray<WorkflowStepIR>;
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* An agent lifted from a `defineAgent()` export in `lunora/agents.ts`. A
|
|
1991
|
+
* `defineAgent` compiles its durable tool-loop onto a Cloudflare Workflow, so —
|
|
1992
|
+
* like {@link WorkflowIR} — an agent is NOT a Durable Object: wrangler gets only
|
|
1993
|
+
* a `workflows[]` entry, never a `durable_objects` binding or a migration class.
|
|
1994
|
+
* Carries what the emitters and the config layer need to wire the generated
|
|
1995
|
+
* agent `WorkflowEntrypoint` class (e.g. `SupportAgentWorkflow`), the typed
|
|
1996
|
+
* per-agent `ctx.agents` producer, and the reconciled wrangler `workflows[]`
|
|
1997
|
+
* entry. Names are derived via `@lunora/agent`'s shared helpers so codegen and
|
|
1998
|
+
* the config layer can never disagree.
|
|
1999
|
+
*/
|
|
2000
|
+
interface AgentIR {
|
|
2001
|
+
/** Generated `WorkflowEntrypoint` class name, e.g. `SupportAgentWorkflow` — also its `exports` key and `ctx.exports` property. */
|
|
2002
|
+
className: string;
|
|
2003
|
+
/** The `lunora/agents.ts` export name, e.g. `support`. */
|
|
2004
|
+
exportName: string;
|
|
2005
|
+
/**
|
|
2006
|
+
* The stable wrangler `exports.<Class>.name`. Defaults to the kebab-cased export
|
|
2007
|
+
* name (`support` → `agent-support`); a static `name:` literal in the
|
|
2008
|
+
* definition overrides it.
|
|
2009
|
+
*/
|
|
2010
|
+
name: string;
|
|
2011
|
+
/**
|
|
2012
|
+
* Whether the definition declares an `onEmail` mapper on
|
|
2013
|
+
* `defineAgent({ onEmail: … })`. When `true` the emitter wires this agent
|
|
2014
|
+
* onto the worker's top-level `email()` handler (via `@lunora/agent/inbound`)
|
|
2015
|
+
* so inbound mail starts a durable run. Detected by AST PRESENCE — the
|
|
2016
|
+
* closure is never evaluated — and written to IR only when present, so
|
|
2017
|
+
* email-free agents (and agent-free projects) stay byte-identical.
|
|
2018
|
+
*/
|
|
2019
|
+
onEmail?: boolean;
|
|
2020
|
+
/**
|
|
2021
|
+
* Whether the definition opted into public run-starts via
|
|
2022
|
+
* `defineAgent({ publicRun: true })` — emitted into the `ctx.agents` wiring
|
|
2023
|
+
* spec so the public `agents:agentRun` mutation can gate on it fail-closed.
|
|
2024
|
+
* Absent (falsy) means server-side starts only; the field is written to IR
|
|
2025
|
+
* only when the literal is `true`, so agent-free and non-opted-in output is
|
|
2026
|
+
* byte-identical.
|
|
2027
|
+
*/
|
|
2028
|
+
publicRun?: boolean;
|
|
2029
|
+
/**
|
|
2030
|
+
* Whether the definition opted into a real-time voice session via a `voice`
|
|
2031
|
+
* block on `defineAgent({ voice: … })`. Unlike the durable loop (a Workflow),
|
|
2032
|
+
* the voice path IS a Durable Object — so when this is `true` the emitter
|
|
2033
|
+
* generates the `voiceClassName` `VoiceSessionDO` subclass and the
|
|
2034
|
+
* `api.agents.{name}Voice` client reference, and the config layer reconciles
|
|
2035
|
+
* a `durable_objects` binding (`voiceBindingName`) + `new_sqlite_classes`
|
|
2036
|
+
* migration. Written to IR only when the literal is present, so voice-free
|
|
2037
|
+
* agents (and agent-free projects) stay byte-identical.
|
|
2038
|
+
*/
|
|
2039
|
+
voice?: boolean;
|
|
2040
|
+
/** The voice DO's Cloudflare `DurableObjectNamespace` binding name, e.g. `VOICE_SUPPORT`. Present only when `voice`. */
|
|
2041
|
+
voiceBindingName?: string;
|
|
2042
|
+
/** Generated `VoiceSessionDO` subclass name, e.g. `SupportVoiceDO`. Present only when `voice`. */
|
|
2043
|
+
voiceClassName?: string;
|
|
2044
|
+
}
|
|
2045
|
+
/** One durable step call lifted from a workflow handler body (the use side of {@link WorkflowIR.steps}). */
|
|
2046
|
+
interface WorkflowStepIR {
|
|
2047
|
+
/** 1-based line of the durable step call. */
|
|
2048
|
+
line: number;
|
|
2049
|
+
/** The native step method invoked: `do` / `sleep` / `sleepUntil` / `waitForEvent`. */
|
|
2109
2050
|
method: string;
|
|
2110
|
-
/** The
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2051
|
+
/** The step's static label (the first string-literal argument). */
|
|
2052
|
+
name: string;
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* A queue lifted from a `defineQueue()` export in `lunora/queues.ts`. Carries
|
|
2056
|
+
* what the emitters and the config layer need to wire the typed `ctx.queues`
|
|
2057
|
+
* producer, the generated worker `queue()` dispatch, and the wrangler
|
|
2058
|
+
* `queues.producers[]` / `queues.consumers[]` entries. Like workflows, a queue
|
|
2059
|
+
* is NOT a Durable Object — wrangler gets only `queues.*` entries. Names are
|
|
2060
|
+
* derived via `@lunora/queue`'s shared helpers so codegen and the config layer
|
|
2061
|
+
* can never disagree.
|
|
2062
|
+
*/
|
|
2063
|
+
interface QueueIR {
|
|
2064
|
+
/** The Cloudflare `Queue` producer binding name, e.g. `QUEUE_EMAIL`. */
|
|
2065
|
+
bindingName: string;
|
|
2066
|
+
/** The `lunora/queues.ts` export name, e.g. `emailQueue`. */
|
|
2067
|
+
exportName: string;
|
|
2068
|
+
/** The declaring file, lunora-relative without `.ts`: `queues`, or `billing/queues` for a module's own. */
|
|
2069
|
+
filePath: string;
|
|
2070
|
+
/** See {@link HandlerSiteIR}. */
|
|
2071
|
+
handlerSite?: HandlerSiteIR;
|
|
2072
|
+
/** How the queue is consumed: `"push"` (a worker `queue()` handler) or `"pull"` (external HTTP). */
|
|
2073
|
+
mode: "pull" | "push";
|
|
2074
|
+
/**
|
|
2075
|
+
* The stable wrangler queue name (`queues.producers[].queue`). Defaults to
|
|
2076
|
+
* the kebab-cased export name (`emailQueue` → `email-queue`); a static
|
|
2077
|
+
* `name:` literal in the definition overrides it.
|
|
2078
|
+
*/
|
|
2079
|
+
name: string;
|
|
2080
|
+
/**
|
|
2081
|
+
* Set for a `defineSubscription(topic, …)` export: the `defineTopic` export it
|
|
2082
|
+
* consumes. A subscription is an ordinary push queue everywhere except
|
|
2083
|
+
* `ctx.*` — it is published to through `ctx.topics.<topic>`, never `ctx.queues`.
|
|
2084
|
+
*/
|
|
2085
|
+
topic?: string;
|
|
2086
|
+
/** Push-consumer batch/retry tuning, mirrored onto the wrangler `queues.consumers[]` entry. */
|
|
2087
|
+
tuning: {
|
|
2088
|
+
deadLetterQueue?: string;
|
|
2089
|
+
maxBatchSize?: number;
|
|
2090
|
+
maxBatchTimeout?: number;
|
|
2091
|
+
maxConcurrency?: number;
|
|
2092
|
+
maxRetries?: number;
|
|
2093
|
+
retryDelay?: number;
|
|
2094
|
+
};
|
|
2114
2095
|
}
|
|
2115
2096
|
/**
|
|
2116
|
-
*
|
|
2117
|
-
*
|
|
2118
|
-
* (
|
|
2119
|
-
* `storage_generate_upload_url_no_content_type_pin`, `storage_presigned_url_for_private_content`).
|
|
2120
|
-
* `upload`/`store` carry the `UploadOptions` guards (`allowedContentTypes` /
|
|
2121
|
-
* `maxSize`); `generateUploadUrl` carries the signed-PUT `contentType` pin;
|
|
2122
|
-
* `getPresignedUrl`/`getSignedUrl` carry a statically-known `expiresInSeconds`
|
|
2123
|
-
* literal. `presentKeys` is empty (and `expiresInSeconds` unset) when the
|
|
2124
|
-
* options argument was absent, a non-literal, or a spread — see `analyzable`.
|
|
2125
|
-
* Structurally identical to `AdvisorStorageUpload`.
|
|
2097
|
+
* A `defineTopic()` export in `lunora/queues.ts`. A topic deploys nothing of its
|
|
2098
|
+
* own: each subscription is a {@link QueueIR} whose `topic` names this export, and
|
|
2099
|
+
* that is the only record of the link (see {@link subscriptionsOf}).
|
|
2126
2100
|
*/
|
|
2127
|
-
interface
|
|
2128
|
-
/** `
|
|
2129
|
-
|
|
2130
|
-
/**
|
|
2131
|
-
|
|
2132
|
-
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2133
|
-
file: string;
|
|
2134
|
-
/** 1-based line of the call, or `0` when unknown. */
|
|
2135
|
-
line: number;
|
|
2136
|
-
/** The `ctx.storage` method invoked. */
|
|
2137
|
-
method: "generateUploadUrl" | "getPresignedUrl" | "getSignedUrl" | "store" | "upload";
|
|
2138
|
-
/** Options-object keys present at the call site (empty when not `analyzable`, or when no options argument was passed). */
|
|
2139
|
-
presentKeys: string[];
|
|
2140
|
-
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
2141
|
-
scope: CallSiteScope;
|
|
2101
|
+
interface TopicIR {
|
|
2102
|
+
/** The `lunora/queues.ts` export name, e.g. `signups`. */
|
|
2103
|
+
exportName: string;
|
|
2104
|
+
/** The declaring file, as {@link QueueIR.filePath}. */
|
|
2105
|
+
filePath: string;
|
|
2142
2106
|
}
|
|
2143
2107
|
/**
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
2147
|
-
* reads `ctx.auth` — the `http_action_missing_auth_guard` lint input. A handler
|
|
2148
|
-
* that mutates state or dispatches an action without ever consulting the request
|
|
2149
|
-
* identity is an unauthenticated write bypassing identity/RLS. Only handlers with
|
|
2150
|
-
* a statically-resolvable inline body and `ctx` binding are recorded (fail-safe
|
|
2151
|
-
* under-report); read-only handlers are never recorded. Structurally identical to
|
|
2152
|
-
* `AdvisorHttpActionGuard`.
|
|
2108
|
+
* A module: a folder under `lunora/` whose `module.ts` default-exports
|
|
2109
|
+
* `defineModule(...)`. Metadata for the Studio catalog, the architecture
|
|
2110
|
+
* manifest and the `cross_module_table_write` lint — it changes no `api.*` path.
|
|
2153
2111
|
*/
|
|
2154
|
-
interface
|
|
2155
|
-
/**
|
|
2156
|
-
|
|
2157
|
-
/**
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
/**
|
|
2164
|
-
|
|
2165
|
-
/**
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2112
|
+
interface ModuleIR {
|
|
2113
|
+
/** One-line description from `defineModule({ description })`. */
|
|
2114
|
+
description?: string;
|
|
2115
|
+
/**
|
|
2116
|
+
* `true` for an installed component (a `defineSchemaExtension` key merged with
|
|
2117
|
+
* `.extend(...)`) treated as a module: it owns its prefixed tables and the
|
|
2118
|
+
* `lunora/<key>/` folder its copy-in code lives in. Absent for a declared module.
|
|
2119
|
+
*/
|
|
2120
|
+
installed?: true;
|
|
2121
|
+
/** The folder path relative to `lunora/`, e.g. `billing` — also the module's name. */
|
|
2122
|
+
name: string;
|
|
2123
|
+
/**
|
|
2124
|
+
* `false` for an installed component shipped as a package: its code is in
|
|
2125
|
+
* `node_modules`, so it owns its tables but no `lunora/<key>/` folder (an app
|
|
2126
|
+
* folder of that name stays the app's). Absent means the module owns its folder.
|
|
2127
|
+
*/
|
|
2128
|
+
ownsFolder?: false;
|
|
2129
|
+
/** Tables the module declares it owns (`defineModule({ tables })`); empty when it declares none. */
|
|
2130
|
+
tables: ReadonlyArray<string>;
|
|
2169
2131
|
}
|
|
2170
2132
|
/**
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
2174
|
-
*
|
|
2175
|
-
* placed verbatim into a response header lets a caller smuggle `\r\n` and inject
|
|
2176
|
-
* extra headers or split the response (header injection / response splitting). Only
|
|
2177
|
-
* sites whose value is request-tainted AND unguarded are recorded: a value routed
|
|
2178
|
-
* through a CR/LF guard (`isSafeHeaderValue`), a URL/URI encoder
|
|
2179
|
-
* (`encodeURIComponent`/`encodeURI`), a numeric coercion (`Number`/`parseInt`/
|
|
2180
|
-
* `parseFloat`), or `btoa` is treated as safe and never recorded (`String(...)` /
|
|
2181
|
-
* `.toString()` are NOT sanitizers — they don't strip CR/LF). Structurally
|
|
2182
|
-
* identical to `AdvisorHttpHeaderWrite`.
|
|
2133
|
+
* A sibling Worker the app calls through a Cloudflare service binding, declared in
|
|
2134
|
+
* `lunora.config.*` `services` (plan 457). Becomes a wrangler `services[]` entry,
|
|
2135
|
+
* a typed `ctx.services.<name>` on actions, and a `service` node in the
|
|
2136
|
+
* architecture manifest.
|
|
2183
2137
|
*/
|
|
2184
|
-
interface
|
|
2185
|
-
/**
|
|
2186
|
-
|
|
2187
|
-
/** The
|
|
2188
|
-
|
|
2189
|
-
/**
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2138
|
+
interface ServiceBindingIR {
|
|
2139
|
+
/** The `services[].binding` Lunora writes, e.g. `SERVICE_DOCUMENT_PARSER`. */
|
|
2140
|
+
binding: string;
|
|
2141
|
+
/** The exported `WorkerEntrypoint` class the binding targets; absent for the Worker's default export. */
|
|
2142
|
+
entrypoint?: string;
|
|
2143
|
+
/**
|
|
2144
|
+
* The Worker name per `env.<name>` block the service's own wrangler config
|
|
2145
|
+
* sets a `name` in. An environment missing here deploys as wrangler's
|
|
2146
|
+
* default `<worker>-<env>`.
|
|
2147
|
+
*/
|
|
2148
|
+
envWorkers: Readonly<Record<string, string>>;
|
|
2149
|
+
/** Absolute path of the service's entry module (its wrangler `main`) — what the RPC type is read from. */
|
|
2150
|
+
main: string;
|
|
2151
|
+
/** The `ctx.services.<name>` key, e.g. `documentParser`. */
|
|
2152
|
+
name: string;
|
|
2153
|
+
/**
|
|
2154
|
+
* The scopes in which the service still serves on `*.workers.dev` with no
|
|
2155
|
+
* route of its own: `""` for the top level, else the environment name.
|
|
2156
|
+
*/
|
|
2157
|
+
publicScopes: ReadonlyArray<string>;
|
|
2158
|
+
/**
|
|
2159
|
+
* The `WorkerEntrypoint` class the service is typed — and wired — as an RPC
|
|
2160
|
+
* stub of. Equal to {@link ServiceBindingIR.entrypoint} for an RPC service;
|
|
2161
|
+
* absent for a fetch service, including a named entrypoint declared
|
|
2162
|
+
* `rpc: false`, which is called with plain `fetch` and so needs no import of
|
|
2163
|
+
* the service's sources.
|
|
2164
|
+
*/
|
|
2165
|
+
rpcEntrypoint?: string;
|
|
2166
|
+
/** The Worker's own `name` from its wrangler config — the `services[].service` target. */
|
|
2167
|
+
worker: string;
|
|
2168
|
+
/** Absolute path of the service's wrangler config. */
|
|
2169
|
+
wranglerPath: string;
|
|
2195
2170
|
}
|
|
2196
2171
|
/**
|
|
2197
|
-
*
|
|
2198
|
-
* `
|
|
2199
|
-
*
|
|
2200
|
-
*
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2203
|
-
* The lint escalates a fail-open guard to a finding when the guarded procedure
|
|
2204
|
-
* (`exportName`/`limitName`) looks auth/payment-sensitive. Structurally
|
|
2205
|
-
* identical to `AdvisorFailOpenGuard`.
|
|
2172
|
+
* The feature-flag provider declared by the default export of `lunora/flags.ts`
|
|
2173
|
+
* (`defineFlags({ provider, … })`). Discovery is **metadata-only** — codegen
|
|
2174
|
+
* imports the real module at runtime for the provider value; this IR exists so
|
|
2175
|
+
* the config layer can reconcile/validate the wrangler `flagship` binding when
|
|
2176
|
+
* the app uses Flagship in binding mode. A `custom` provider (any other
|
|
2177
|
+
* OpenFeature factory) carries no binding to reconcile.
|
|
2206
2178
|
*/
|
|
2207
|
-
interface
|
|
2208
|
-
/**
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
/**
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2179
|
+
interface FlagsIR {
|
|
2180
|
+
/**
|
|
2181
|
+
* The wrangler `flagship[].binding` name — set **only** for a flagship
|
|
2182
|
+
* `provider` in binding mode (`flagshipProvider({ binding: "FLAGS" })`). The
|
|
2183
|
+
* config layer hints/validates a matching `flagship` binding from this.
|
|
2184
|
+
*/
|
|
2185
|
+
bindingName?: string;
|
|
2186
|
+
/**
|
|
2187
|
+
* Flagship operating mode — `"binding"` (wrangler binding, needs a
|
|
2188
|
+
* `flagship` entry) or `"http"` (no binding); `undefined` for a `custom`
|
|
2189
|
+
* provider or when the mode can't be read statically.
|
|
2190
|
+
*/
|
|
2191
|
+
mode?: "binding" | "http";
|
|
2192
|
+
/** `"flagship"` when the provider is `flagshipProvider(...)`, else `"custom"` (any other OpenFeature provider factory). */
|
|
2193
|
+
provider: "custom" | "flagship";
|
|
2220
2194
|
}
|
|
2221
2195
|
/**
|
|
2222
|
-
* One `ctx.
|
|
2223
|
-
* `
|
|
2224
|
-
* `
|
|
2225
|
-
*
|
|
2226
|
-
*
|
|
2227
|
-
*
|
|
2228
|
-
* `
|
|
2196
|
+
* One `ctx.containers.<exportName>.get(name, …)` call whose instance key is derived
|
|
2197
|
+
* from the handler's `args` with no server-side scoping — the
|
|
2198
|
+
* `container_instance_key_from_user_input` lint input. Each container definition's
|
|
2199
|
+
* `.get(name)` accessor routes to one instance per `name`, so a key taken straight from
|
|
2200
|
+
* request input lets any caller reach another tenant's container (a cross-tenant IDOR). A
|
|
2201
|
+
* fixed literal key, or one derived from a server-trusted identity (`${ctx.auth.userId}` —
|
|
2202
|
+
* references `ctx`, so treated as scoped), is not recorded; only an arg-derived, unscoped
|
|
2203
|
+
* key reaches here. `.any()`/`.pool()` take no key and are not sinks. Structurally
|
|
2204
|
+
* identical to `AdvisorContainerKeyAccess`.
|
|
2229
2205
|
*/
|
|
2230
|
-
interface
|
|
2231
|
-
/** The boolean-literal default returned on a provider outage (fail-open value). */
|
|
2232
|
-
defaultValue: boolean;
|
|
2206
|
+
interface ContainerKeyAccessIR {
|
|
2233
2207
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2234
2208
|
file: string;
|
|
2235
|
-
/**
|
|
2236
|
-
key: string;
|
|
2237
|
-
/** 1-based line of the `ctx.flags.boolean` call, or `0` when unknown. */
|
|
2209
|
+
/** 1-based line of the `ctx.containers.*.get` call, or `0` when unknown. */
|
|
2238
2210
|
line: number;
|
|
2211
|
+
/** The container accessor method invoked — always `get`. */
|
|
2212
|
+
method: string;
|
|
2239
2213
|
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
2240
2214
|
scope: CallSiteScope;
|
|
2241
2215
|
}
|
|
2242
2216
|
/**
|
|
2243
|
-
* One
|
|
2244
|
-
*
|
|
2245
|
-
* `
|
|
2246
|
-
*
|
|
2247
|
-
*
|
|
2248
|
-
*
|
|
2249
|
-
*
|
|
2250
|
-
* Only `query` handlers are recorded — unlike `ContextPropertyCallIR` / `NondeterministicCallIR`
|
|
2251
|
-
* there is no `kind` field, because `mutation`/`action` handlers run once and have
|
|
2252
|
-
* no subscription staleness to warn about, so the feeder drops them rather than
|
|
2253
|
-
* handing the lint rows it would discard.
|
|
2254
|
-
*/
|
|
2255
|
-
interface FlagReadIR {
|
|
2256
|
-
/** The accessed `ctx.flags` surface, e.g. `ctx.flags.boolean` / `ctx.flags.details.string`. */
|
|
2257
|
-
callee: string;
|
|
2258
|
-
/** Export binding name of the query performing the read. */
|
|
2259
|
-
exportName: string;
|
|
2260
|
-
/** Source file relative to `<projectRoot>/lunora/`, without extension (the api namespace). */
|
|
2261
|
-
file: string;
|
|
2262
|
-
/** 1-based line of the read, or `0` when unknown. */
|
|
2263
|
-
line: number;
|
|
2264
|
-
}
|
|
2265
|
-
/**
|
|
2266
|
-
* One `generateText` / `streamText` call in `lunora/` whose `tools` reach a
|
|
2267
|
-
* privileged side effect (a DB write, function dispatch, or outbound
|
|
2268
|
-
* fetch/mail/queue send). `userInputDerived` records whether the model input
|
|
2269
|
-
* (`prompt`/`messages`/`system`) flows from the handler's `args`; the
|
|
2270
|
-
* `ai_tool_side_effect_prompt_injection` lint fires only when it does.
|
|
2271
|
-
* Structurally identical to `AdvisorAiToolSideEffect`.
|
|
2217
|
+
* One runtime container-override call: a `<handle>.start({ enableInternet: true, … })`
|
|
2218
|
+
* launch override, or a `<handle>.egress.<method>(...)` runtime firewall mutation
|
|
2219
|
+
* (`allow` / `deny` / `setAllowed`) — the `container_start_enable_internet_override`
|
|
2220
|
+
* and `container_runtime_egress_relaxation` lint input. Both shapes re-open network
|
|
2221
|
+
* access the static `defineContainer` declaration (and its `container_public_internet`
|
|
2222
|
+
* lint) assumes is locked down. Matched structurally by call shape, independent of the
|
|
2223
|
+
* receiver's resolved type. Structurally identical to `AdvisorContainerOverride`.
|
|
2272
2224
|
*/
|
|
2273
|
-
interface
|
|
2225
|
+
interface ContainerOverrideIR {
|
|
2226
|
+
/** e.g. the egress method name, or `"enableInternet: true"`. */
|
|
2227
|
+
detail: string;
|
|
2274
2228
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2275
2229
|
file: string;
|
|
2276
|
-
/**
|
|
2277
|
-
|
|
2278
|
-
/**
|
|
2279
|
-
|
|
2230
|
+
/** Which override shape matched. */
|
|
2231
|
+
kind: "egress_relaxation" | "enable_internet";
|
|
2232
|
+
/** 1-based line of the call, or `0` when unknown. */
|
|
2233
|
+
line: number;
|
|
2280
2234
|
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
2281
2235
|
scope: CallSiteScope;
|
|
2282
|
-
/** The privileged side-effect sink a model-callable tool reaches (`ctx.db.insert`, `ctx.run`, `ctx.fetch`, …). */
|
|
2283
|
-
sideEffect: string;
|
|
2284
|
-
/** `true` when a model-input option is derived from the handler's `args` (a bare `args.x`, or a name destructured from `args`). */
|
|
2285
|
-
userInputDerived: boolean;
|
|
2286
2236
|
}
|
|
2287
2237
|
/**
|
|
2288
|
-
* One
|
|
2289
|
-
*
|
|
2290
|
-
*
|
|
2291
|
-
*
|
|
2292
|
-
*
|
|
2293
|
-
*
|
|
2238
|
+
* One `buildImageDeliveryUrl({ key, … })` call (`@lunora/bindings/images`) whose
|
|
2239
|
+
* `key` — the CDN transform's source image, an absolute URL or an
|
|
2240
|
+
* origin-relative key — is derived from the handler's `args` with no
|
|
2241
|
+
* server-side scoping — the `images_url_source_from_user_input` lint input.
|
|
2242
|
+
* `ctx.images.transform`/`info` take image *bytes*, never a URL, so they are not
|
|
2243
|
+
* sinks; only the `key` of `buildImageDeliveryUrl` accepts a URL-or-key source
|
|
2244
|
+
* and is inspected. An arg-derived `key` lets any caller point the CDN's
|
|
2245
|
+
* `/cdn-cgi/image/` transform at an attacker-chosen origin (SSRF / open proxy)
|
|
2246
|
+
* or at an arbitrary key under the account's own store. A fixed literal, or a
|
|
2247
|
+
* key scoped by a server-trusted `ctx.*` value, is not recorded. Structurally
|
|
2248
|
+
* identical to `AdvisorImageDeliveryUrlAccess`.
|
|
2294
2249
|
*/
|
|
2295
|
-
interface
|
|
2296
|
-
/** `true` when `key` is a declared claim (in the `defineIdentity` contract, or the always-present `userId`). */
|
|
2297
|
-
declared: boolean;
|
|
2250
|
+
interface ImageDeliveryUrlAccessIR {
|
|
2298
2251
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2299
2252
|
file: string;
|
|
2300
|
-
/**
|
|
2301
|
-
key: string;
|
|
2302
|
-
/** 1-based line of the read, or `0` when unknown. */
|
|
2253
|
+
/** 1-based line of the `buildImageDeliveryUrl` call, or `0` when unknown. */
|
|
2303
2254
|
line: number;
|
|
2304
2255
|
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
2305
2256
|
scope: CallSiteScope;
|
|
2306
2257
|
}
|
|
2307
2258
|
/**
|
|
2308
|
-
* One
|
|
2309
|
-
*
|
|
2310
|
-
*
|
|
2311
|
-
*
|
|
2312
|
-
*
|
|
2313
|
-
*
|
|
2259
|
+
* One `createAuth({...})` call's configuration snapshot — the shared input for
|
|
2260
|
+
* the five `auth_*` security lints (trusted-origins wildcard, CSRF check
|
|
2261
|
+
* disabled, secure cookies disabled, email verification disabled, session
|
|
2262
|
+
* freshAge zero). Matched by callee NAME (an `import`-agnostic, fail-closed
|
|
2263
|
+
* convention the other feeders share), so a re-export or alias still resolves.
|
|
2264
|
+
* When the config argument isn't a statically-analyzable object literal (a
|
|
2265
|
+
* top-level spread, or not an object literal at all), `analyzable` is `false`
|
|
2266
|
+
* and every boolean fact defaults to its SAFE (not-flagged) value — an opaque
|
|
2267
|
+
* config can't be relied on either way. Structurally identical to
|
|
2268
|
+
* `AdvisorAuthConfig`.
|
|
2314
2269
|
*/
|
|
2315
|
-
interface
|
|
2316
|
-
/**
|
|
2317
|
-
|
|
2270
|
+
interface AuthConfigIR {
|
|
2271
|
+
/** `true` when the call's config argument was a static object literal the feeder could read. */
|
|
2272
|
+
analyzable: boolean;
|
|
2273
|
+
/** `advanced.disableCSRFCheck === true`. */
|
|
2274
|
+
disableCsrfCheck: boolean;
|
|
2275
|
+
/** `emailAndPassword.enabled === true`. */
|
|
2276
|
+
emailPasswordEnabled: boolean;
|
|
2318
2277
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2319
2278
|
file: string;
|
|
2320
|
-
/** 1-based line of the
|
|
2279
|
+
/** 1-based line of the `createAuth(...)` call, or `0` when unknown. */
|
|
2321
2280
|
line: number;
|
|
2281
|
+
/** `emailAndPassword.requireEmailVerification === true` present. */
|
|
2282
|
+
requireEmailVerification: boolean;
|
|
2283
|
+
/** `trustedOrigins` array literal contains a `"*"` element. */
|
|
2284
|
+
/** `plugins` includes `scim(...)` while `database` is a non-transactional Lunora adapter — a combination that throws at runtime. */
|
|
2285
|
+
scimOnNonTransactionalAdapter: boolean;
|
|
2322
2286
|
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
2323
2287
|
scope: CallSiteScope;
|
|
2324
|
-
/**
|
|
2325
|
-
|
|
2288
|
+
/** `advanced.useSecureCookies === false`. */
|
|
2289
|
+
secureCookiesDisabled: boolean;
|
|
2290
|
+
/** `session.freshAge === 0` (explicit literal). */
|
|
2291
|
+
sessionFreshAgeZero: boolean;
|
|
2292
|
+
trustedOriginsWildcard: boolean;
|
|
2326
2293
|
}
|
|
2327
2294
|
/**
|
|
2328
|
-
* One `
|
|
2329
|
-
*
|
|
2330
|
-
* `
|
|
2331
|
-
* `
|
|
2332
|
-
* `.public()` before flagging. Structurally identical to `AdvisorSoftDeleteRead`
|
|
2333
|
-
* so values pass straight through without conversion.
|
|
2295
|
+
* One discovered `httpRoute.<verb>("/admin/…")` route on an admin/privileged-looking
|
|
2296
|
+
* path, with whether its builder chain references an auth/admin guard — the
|
|
2297
|
+
* `admin_route_without_guard` lint input. Structurally identical to
|
|
2298
|
+
* `AdvisorAdminRoute`.
|
|
2334
2299
|
*/
|
|
2335
|
-
interface
|
|
2336
|
-
/** Export binding name of the
|
|
2300
|
+
interface AdminRouteIR {
|
|
2301
|
+
/** Export binding name of the route handler. */
|
|
2337
2302
|
exportName: string;
|
|
2338
2303
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2339
2304
|
file: string;
|
|
2340
|
-
/**
|
|
2341
|
-
|
|
2342
|
-
/**
|
|
2343
|
-
|
|
2344
|
-
/**
|
|
2345
|
-
|
|
2346
|
-
/** Table read, or `""` when the table-arg form's first argument wasn't a string literal. */
|
|
2347
|
-
table: string;
|
|
2348
|
-
/** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
|
|
2349
|
-
visibility: "internal" | "public";
|
|
2305
|
+
/** HTTP verb the route binds to (uppercased), e.g. `"POST"`. */
|
|
2306
|
+
method: string;
|
|
2307
|
+
/** The route path, e.g. `/admin/users`. */
|
|
2308
|
+
path: string;
|
|
2309
|
+
/** `true` when the handler body references an auth/session/admin guard (`ctx.auth`, `getSession`, `requireAdmin`, …). */
|
|
2310
|
+
usesGuard: boolean;
|
|
2350
2311
|
}
|
|
2351
2312
|
/**
|
|
2352
|
-
* One `
|
|
2353
|
-
*
|
|
2354
|
-
*
|
|
2355
|
-
*
|
|
2356
|
-
*
|
|
2357
|
-
*
|
|
2358
|
-
*
|
|
2313
|
+
* One committed `wrangler.jsonc` `vars` entry whose value is a plaintext secret —
|
|
2314
|
+
* the `plaintext_secret_in_wrangler_vars` lint input. `vars` are baked into the
|
|
2315
|
+
* deployed Worker in cleartext and checked into source control, so a real API key
|
|
2316
|
+
* / token / private key there ships the secret to every reader of the repo and the
|
|
2317
|
+
* bundle; it belongs in a Secrets Store binding or `wrangler secret put`. Produced
|
|
2318
|
+
* by `@lunora/config` (which reads `wrangler.jsonc`), not a ts-morph feeder —
|
|
2319
|
+
* codegen only passes it through. Structurally identical to `AdvisorWranglerVariable`.
|
|
2359
2320
|
*/
|
|
2360
|
-
interface
|
|
2361
|
-
/**
|
|
2321
|
+
interface WranglerVariableIR {
|
|
2322
|
+
/** The `wrangler.jsonc` file the var was read from, relative to the project root. */
|
|
2323
|
+
file: string;
|
|
2324
|
+
/** The offending `vars` key (e.g. `STRIPE_SECRET_KEY`). */
|
|
2325
|
+
key: string;
|
|
2326
|
+
/** Heuristic that matched, e.g. `stripe_live_key` / `private_key` / `secret_named_var`. */
|
|
2327
|
+
kind: string;
|
|
2328
|
+
/** Redacted preview of the value (first few chars + length) for the finding detail — never the full secret. */
|
|
2329
|
+
preview: string;
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* One `queues.producers[]` entry of `wrangler.jsonc`, from the top level or an
|
|
2333
|
+
* `env.<name>` block — how codegen learns a queue's per-environment name. Produced
|
|
2334
|
+
* by `@lunora/config` (which reads `wrangler.jsonc`); codegen only matches it to a
|
|
2335
|
+
* declared queue by `binding`.
|
|
2336
|
+
*/
|
|
2337
|
+
interface WranglerQueueProducerIR {
|
|
2338
|
+
/** The `Queue` producer binding, e.g. `QUEUE_JOBS`. */
|
|
2339
|
+
binding: string;
|
|
2340
|
+
/** The `env.<name>` block the entry sits in; absent for the top level. */
|
|
2341
|
+
env?: string;
|
|
2342
|
+
/** The queue name that binding sends to in that scope, e.g. `jobs-preview`. */
|
|
2343
|
+
queue: string;
|
|
2344
|
+
}
|
|
2345
|
+
interface ProjectIR {
|
|
2346
|
+
crons: ReadonlyArray<CronJobIR>;
|
|
2347
|
+
functions: ReadonlyArray<FunctionIR>;
|
|
2348
|
+
/** Typed REST routes discovered from `httpRoute.<verb>(...)` builder chains. */
|
|
2349
|
+
httpRoutes: ReadonlyArray<HttpRouteIR>;
|
|
2350
|
+
migrations: ReadonlyArray<MigrationIR>;
|
|
2351
|
+
schema: SchemaIR;
|
|
2352
|
+
}
|
|
2353
|
+
/**
|
|
2354
|
+
* Per-procedure RLS usage snapshot, produced by `discoverRlsProcedures` for the
|
|
2355
|
+
* `rls_uncovered_table` advisor lint. Structurally identical to
|
|
2356
|
+
* `AdvisorRlsProcedure` (they share the same field set) so values pass straight
|
|
2357
|
+
* through to the advisor without conversion, exactly as `AuthApiCallIR` does for
|
|
2358
|
+
* `AdvisorAuthApiCall`.
|
|
2359
|
+
*/
|
|
2360
|
+
interface RlsProcedureIR {
|
|
2361
|
+
/** Export binding name of the procedure (e.g. `listDocuments`). */
|
|
2362
2362
|
exportName: string;
|
|
2363
2363
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2364
2364
|
file: string;
|
|
2365
|
-
/**
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2365
|
+
/**
|
|
2366
|
+
* Table names extracted from the `rls(policies)` array literal. Empty when the
|
|
2367
|
+
* policies argument is not a statically-readable array literal.
|
|
2368
|
+
*/
|
|
2369
|
+
rlsTables: string[];
|
|
2370
|
+
/** Tables read by the procedure via `ctx.db.query/findMany/findFirst/…`. */
|
|
2371
|
+
tablesRead: string[];
|
|
2372
|
+
/** Tables written by the procedure via `ctx.db.insert/patch/replace/delete`. */
|
|
2373
|
+
tablesWritten: string[];
|
|
2374
|
+
/** `true` when the procedure's builder chain includes `.use(rls(...))`. */
|
|
2375
|
+
usesRls: boolean;
|
|
2371
2376
|
/** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
|
|
2372
2377
|
visibility: "internal" | "public";
|
|
2373
2378
|
}
|
|
2374
2379
|
/**
|
|
2375
|
-
* One
|
|
2376
|
-
*
|
|
2377
|
-
*
|
|
2378
|
-
*
|
|
2379
|
-
* `
|
|
2380
|
-
* "public"` rows with no `.output(...)` / `.use(mask(...))` on the chain, then
|
|
2381
|
-
* joins `table` against the schema and flags one whose columns are PII-named.
|
|
2382
|
-
* Structurally identical to `AdvisorRawRowReturn` so values pass straight through
|
|
2383
|
-
* without conversion.
|
|
2380
|
+
* One procedure reduced to the facts the `mask_uncovered_pii_column` lint needs:
|
|
2381
|
+
* whether its builder chain includes `.use(mask(...))`, which `(table, column)`
|
|
2382
|
+
* pairs that mask declares, and which tables the procedure reads/writes. The
|
|
2383
|
+
* column-level analogue of {@link RlsProcedureIR}. Structurally identical to
|
|
2384
|
+
* `AdvisorMaskProcedure` so values pass straight through without conversion.
|
|
2384
2385
|
*/
|
|
2385
|
-
interface
|
|
2386
|
-
/** Export binding name of the
|
|
2386
|
+
interface MaskProcedureIR {
|
|
2387
|
+
/** Export binding name of the procedure (e.g. `listUsers`). */
|
|
2387
2388
|
exportName: string;
|
|
2388
2389
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2389
2390
|
file: string;
|
|
2390
|
-
/**
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2391
|
+
/**
|
|
2392
|
+
* `(table, column)` pairs this procedure's `mask(policies)` object literal
|
|
2393
|
+
* declares. Empty when the policies argument is not a statically-readable
|
|
2394
|
+
* object literal (conservative: `usesMask` is still `true`).
|
|
2395
|
+
*/
|
|
2396
|
+
maskColumns: {
|
|
2397
|
+
column: string;
|
|
2398
|
+
table: string;
|
|
2399
|
+
}[];
|
|
2400
|
+
/** Tables read by the procedure via `ctx.db.query/findMany/findFirst/get`. */
|
|
2401
|
+
tablesRead: string[];
|
|
2402
|
+
/** Tables written by the procedure via `ctx.db.insert/patch/replace/delete`. */
|
|
2403
|
+
tablesWritten: string[];
|
|
2404
|
+
/** `true` when the procedure's builder chain includes `.use(mask(...))`. */
|
|
2395
2405
|
usesMask: boolean;
|
|
2396
|
-
/** `
|
|
2397
|
-
usesOutput: boolean;
|
|
2398
|
-
/** `"internal"` for `internalQuery`; `"public"` for `query`. */
|
|
2406
|
+
/** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
|
|
2399
2407
|
visibility: "internal" | "public";
|
|
2400
2408
|
}
|
|
2401
2409
|
/**
|
|
2402
|
-
* One
|
|
2403
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
*
|
|
2406
|
-
*
|
|
2407
|
-
*
|
|
2408
|
-
*
|
|
2409
|
-
|
|
2410
|
-
|
|
2410
|
+
* One masked column surfaced to the studio's data-browser mask preview: a
|
|
2411
|
+
* `(table, column)` pair plus the declared {@link MaskProcedureIR} strategy so
|
|
2412
|
+
* the preview can pick redact-vs-hash-vs-custom rendering. Aggregated across the
|
|
2413
|
+
* project's `.use(mask(...))` chains by `discoverMaskMetadata`; the descriptive
|
|
2414
|
+
* twin of {@link RlsPolicyIR}. `"custom"` covers any non-string strategy (a
|
|
2415
|
+
* `(value, ctx) => …` function) — its logic is an opaque closure, never read by
|
|
2416
|
+
* the UI; the preview renders a fixed sentinel for it.
|
|
2417
|
+
*/
|
|
2418
|
+
interface MaskColumnMetadataIR {
|
|
2419
|
+
/** Column the mask policy redacts. */
|
|
2420
|
+
column: string;
|
|
2421
|
+
/** Declared masking strategy: `"redact"`/`"hash"` string literals, else `"custom"` for a `MaskFn`. */
|
|
2422
|
+
strategy: "custom" | "hash" | "redact";
|
|
2423
|
+
/** Logical table the masked column belongs to. */
|
|
2424
|
+
table: string;
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Schema-wide masking metadata the codegen emits into the generated ShardDO so
|
|
2428
|
+
* the studio's data-browser mask toggle can preview what a non-privileged caller
|
|
2429
|
+
* would see. Aggregated across every `.use(mask(...))` chain in the project —
|
|
2430
|
+
* purely descriptive (table + column + strategy), never the masking closure. The
|
|
2431
|
+
* column-level analogue of {@link RlsMetadataIR}.
|
|
2432
|
+
*/
|
|
2433
|
+
interface MaskMetadataIR {
|
|
2434
|
+
/** Every statically-discovered masked column, deduped by `(table, column)` (first declaration wins). */
|
|
2435
|
+
columns: MaskColumnMetadataIR[];
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* One masked column whose `mask(policies)` strategy is a statically-known
|
|
2439
|
+
* literal (`"hash"` or `"redact"`) — the `mask_weak_hash_strategy_on_pii` lint
|
|
2440
|
+
* input. Unlike {@link MaskColumnMetadataIR} (app-wide, deduped by `(table,
|
|
2441
|
+
* column)`, studio-preview evidence), this is per declaration site (file + line
|
|
2442
|
+
* + enclosing export), undeduped, so the lint can point at the exact
|
|
2443
|
+
* `mask(...)` call that applies a weak strategy. A `MaskFn` (custom, non-literal)
|
|
2444
|
+
* strategy carries no lint-relevant signal and is never recorded here.
|
|
2445
|
+
* Structurally identical to `AdvisorMaskStrategy`.
|
|
2411
2446
|
*/
|
|
2412
|
-
interface
|
|
2413
|
-
/**
|
|
2447
|
+
interface MaskStrategyIR {
|
|
2448
|
+
/** Masked column name. */
|
|
2449
|
+
column: string;
|
|
2450
|
+
/** Export binding name of the procedure whose `.use(mask(...))` chain declared this column, or `"<module>"` when declared at file scope. */
|
|
2414
2451
|
exportName: string;
|
|
2415
2452
|
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2416
2453
|
file: string;
|
|
2417
|
-
/** 1-based line of the
|
|
2454
|
+
/** 1-based line of the masked column's strategy property. */
|
|
2418
2455
|
line: number;
|
|
2419
|
-
/**
|
|
2420
|
-
|
|
2421
|
-
/**
|
|
2422
|
-
sinkMethod: "delete" | "get" | "patch";
|
|
2423
|
-
/** Table named in the `normalizeId` call, or `""` when its table argument wasn't a string literal. */
|
|
2456
|
+
/** The statically-known strategy literal: `"hash"` or `"redact"`. */
|
|
2457
|
+
strategy: string;
|
|
2458
|
+
/** Logical table the masked column belongs to. */
|
|
2424
2459
|
table: string;
|
|
2425
|
-
/** `true` when the procedure's builder chain carries a `.use(rls(...))` step. */
|
|
2426
|
-
usesRls: boolean;
|
|
2427
|
-
/** `"internal"` for `internalQuery`/`internalMutation`; `"public"` for `query`/`mutation`. */
|
|
2428
|
-
visibility: "internal" | "public";
|
|
2429
2460
|
}
|
|
2430
2461
|
/**
|
|
2431
|
-
* One
|
|
2432
|
-
*
|
|
2433
|
-
*
|
|
2434
|
-
*
|
|
2435
|
-
*
|
|
2436
|
-
*
|
|
2437
|
-
* codegen only passes it through. Structurally identical to `AdvisorWranglerVariable`.
|
|
2462
|
+
* One statically-readable policy entry from an `rls([...])` array literal,
|
|
2463
|
+
* surfaced to the studio's read-only RLS inspector via the generated
|
|
2464
|
+
* `rlsPolicies()` hook. Captures the policy's `table` + `on` operation and the
|
|
2465
|
+
* procedure it guards — never the `when` predicate, which is an opaque JS
|
|
2466
|
+
* closure (its logic stays in code, not the UI). Produced by
|
|
2467
|
+
* `discoverRlsProcedures` alongside the lint IR.
|
|
2438
2468
|
*/
|
|
2439
|
-
interface
|
|
2440
|
-
/**
|
|
2469
|
+
interface RlsPolicyIR {
|
|
2470
|
+
/** Source file (relative to `lunora/`, without extension) the policy is declared in. */
|
|
2441
2471
|
file: string;
|
|
2442
|
-
/**
|
|
2443
|
-
|
|
2444
|
-
/**
|
|
2445
|
-
|
|
2446
|
-
/**
|
|
2447
|
-
|
|
2472
|
+
/** Operation the policy gates: `read` covers get/query/findMany, the rest are writes. */
|
|
2473
|
+
on: "delete" | "insert" | "read" | "update";
|
|
2474
|
+
/** Export name of the procedure whose `.use(rls(...))` chain declared this policy. */
|
|
2475
|
+
procedure: string;
|
|
2476
|
+
/** Logical table the policy applies to. Empty when not a string literal. */
|
|
2477
|
+
table: string;
|
|
2448
2478
|
}
|
|
2449
2479
|
/**
|
|
2450
|
-
* One
|
|
2451
|
-
*
|
|
2452
|
-
*
|
|
2453
|
-
*
|
|
2480
|
+
* One statically-readable role entry from an `rls(policies, { roles: [...] })`
|
|
2481
|
+
* call, surfaced to the studio's RLS inspector. Captures the role's `name`,
|
|
2482
|
+
* optional `description`, and the names of the permissions it grants (string
|
|
2483
|
+
* literals or `definePermission("name")` calls). Produced by
|
|
2484
|
+
* `discoverRlsProcedures`.
|
|
2454
2485
|
*/
|
|
2455
|
-
interface
|
|
2456
|
-
/**
|
|
2457
|
-
|
|
2458
|
-
/**
|
|
2459
|
-
|
|
2460
|
-
/**
|
|
2461
|
-
|
|
2486
|
+
interface RlsRoleIR {
|
|
2487
|
+
/** Optional human-readable description from `defineRole(name, { description })`. */
|
|
2488
|
+
description?: string;
|
|
2489
|
+
/** Role label attached to the request identity (e.g. `"admin"`). */
|
|
2490
|
+
name: string;
|
|
2491
|
+
/** Permission names this role grants, deduped and in declared order. */
|
|
2492
|
+
permissions: string[];
|
|
2462
2493
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2494
|
+
/**
|
|
2495
|
+
* Schema-wide RLS metadata the codegen emits into the generated ShardDO so the
|
|
2496
|
+
* studio's read-only inspector can list, per table, which policies guard it and
|
|
2497
|
+
* what roles are defined. Aggregated across every `.use(rls(...))` chain in the
|
|
2498
|
+
* project — purely descriptive, never the predicate logic.
|
|
2499
|
+
*/
|
|
2500
|
+
interface RlsMetadataIR {
|
|
2501
|
+
/** Every statically-discovered policy `(table, on, procedure)` entry. */
|
|
2502
|
+
policies: RlsPolicyIR[];
|
|
2503
|
+
/** Every statically-discovered role, deduped by name (first declaration wins). */
|
|
2504
|
+
roles: RlsRoleIR[];
|
|
2505
|
+
}
|
|
2506
|
+
/** One statically-discovered `defineStorageRule({ bucket, on, prefix })` entry from a `.use(storageRules(...))` chain. */
|
|
2507
|
+
interface StorageRuleIR {
|
|
2508
|
+
bucket: string;
|
|
2509
|
+
/** Relative to `lunora/`. */
|
|
2510
|
+
file: string;
|
|
2511
|
+
on: "delete" | "list" | "read" | "write";
|
|
2512
|
+
/** Optional key-prefix scope; absent ⇒ the whole bucket. */
|
|
2513
|
+
prefix?: string;
|
|
2514
|
+
/** The exported procedure that installed the rule. */
|
|
2515
|
+
procedure: string;
|
|
2470
2516
|
}
|
|
2471
2517
|
/**
|
|
2472
|
-
*
|
|
2473
|
-
*
|
|
2474
|
-
*
|
|
2518
|
+
* Schema-wide storage-access-rule metadata emitted into the generated ShardDO so
|
|
2519
|
+
* the studio's read-only inspector can list, per bucket, which operations are
|
|
2520
|
+
* gated and under what key prefix. Aggregated across every
|
|
2521
|
+
* `.use(storageRules(...))` chain — descriptive only, never the predicate logic.
|
|
2475
2522
|
*/
|
|
2476
|
-
interface
|
|
2477
|
-
|
|
2523
|
+
interface StorageRulesMetadataIR {
|
|
2524
|
+
rules: StorageRuleIR[];
|
|
2525
|
+
}
|
|
2526
|
+
/**
|
|
2527
|
+
* One `rateLimit`/`dbRateLimit` middleware call (`@lunora/ratelimit`) whose
|
|
2528
|
+
* `key` selector — the per-caller rate-limit sub-key, `(ctx) => string |
|
|
2529
|
+
* undefined` — is derived from the handler's `args` with no server-side
|
|
2530
|
+
* scoping (no reference to the trusted `ctx` binding anywhere in the selector)
|
|
2531
|
+
* — the `ratelimit_key_spoofable_or_global` lint input. A key an attacker
|
|
2532
|
+
* controls lets them rotate it per request and bypass the limit entirely,
|
|
2533
|
+
* defeating its purpose. A selector scoped by `ctx` (e.g. `ctx.auth.userId`,
|
|
2534
|
+
* `ctx.ip`), or one with no `args` reference at all (a fixed/global bucket —
|
|
2535
|
+
* the "no key" case this lint deliberately does not flag, to keep it low-FP),
|
|
2536
|
+
* is not recorded. Structurally identical to `AdvisorRatelimitKeySelector`.
|
|
2537
|
+
*/
|
|
2538
|
+
interface RatelimitKeySelectorIR {
|
|
2539
|
+
/** The `rateLimit`/`dbRateLimit` callee invoked. */
|
|
2540
|
+
callee: string;
|
|
2541
|
+
/** Source file relative to `<projectRoot>/lunora/`, without extension. */
|
|
2478
2542
|
file: string;
|
|
2479
|
-
/**
|
|
2543
|
+
/** The rate limit's `name` argument (the second positional argument), or `""` when not a string literal. */
|
|
2544
|
+
limitName: string;
|
|
2545
|
+
/** 1-based line of the `rateLimit`/`dbRateLimit` call, or `0` when unknown. */
|
|
2480
2546
|
line: number;
|
|
2481
|
-
/**
|
|
2482
|
-
|
|
2483
|
-
/** Which migration guide covers this platform. */
|
|
2484
|
-
platform: "convex" | "firebase" | "supabase";
|
|
2547
|
+
/** Who the site runs on behalf of — see {@link CallSiteScope}. */
|
|
2548
|
+
scope: CallSiteScope;
|
|
2485
2549
|
}
|
|
2486
2550
|
/**
|
|
2487
2551
|
* Named inputs for {@link lintSchema}. Every feeder is a discrete key rather than
|
|
@@ -2506,6 +2570,7 @@ interface LintSchemaOptions {
|
|
|
2506
2570
|
containerKeyAccesses?: ReadonlyArray<ContainerKeyAccessIR>;
|
|
2507
2571
|
containerOverrides?: ReadonlyArray<ContainerOverrideIR>;
|
|
2508
2572
|
containers?: ReadonlyArray<ContainerIR>;
|
|
2573
|
+
cronTargets?: ReadonlyArray<string>;
|
|
2509
2574
|
exportSinks?: ReadonlyArray<AdvisorExportSink>;
|
|
2510
2575
|
failOpenGuards?: ReadonlyArray<FailOpenGuardIR>;
|
|
2511
2576
|
flagReads?: ReadonlyArray<FlagReadIR>;
|
|
@@ -2539,6 +2604,7 @@ interface LintSchemaOptions {
|
|
|
2539
2604
|
rawRowReturns?: ReadonlyArray<RawRowReturnIR>;
|
|
2540
2605
|
relationLoads?: ReadonlyArray<RelationLoadIR>;
|
|
2541
2606
|
rlsProcedures?: ReadonlyArray<RlsProcedureIR>;
|
|
2607
|
+
rowEffects?: ReadonlyArray<RowEffectIR>;
|
|
2542
2608
|
schema: SchemaIR;
|
|
2543
2609
|
secretLiterals?: ReadonlyArray<SecretLiteralIR>;
|
|
2544
2610
|
shapes?: ReadonlyArray<ShapeIR>;
|
|
@@ -3050,67 +3116,6 @@ interface PlatformDiagnostic {
|
|
|
3050
3116
|
/** The requested deploy target. */
|
|
3051
3117
|
target: string;
|
|
3052
3118
|
}
|
|
3053
|
-
/**
|
|
3054
|
-
* Committed, tracked baseline file holding the blessed structural schema
|
|
3055
|
-
* snapshot the pre-deploy drift gate diffs against. Lives in `lunora/` (NOT the
|
|
3056
|
-
* gitignored `_generated/`) so it is committed alongside `schema.ts`. Leading
|
|
3057
|
-
* dot keeps it tucked away next to the schema it describes.
|
|
3058
|
-
*/
|
|
3059
|
-
declare const SCHEMA_SNAPSHOT_FILENAME = ".lunora-schema.json";
|
|
3060
|
-
/**
|
|
3061
|
-
* Walk up from `startPath` until we find a `tsconfig.json` or hit the file
|
|
3062
|
-
* system root. Returns the absolute path to the tsconfig, or `undefined`.
|
|
3063
|
-
*
|
|
3064
|
-
* Exported (see the bottom-of-file `export { findTsconfig }`) so a long-lived
|
|
3065
|
-
* caller (the Vite dev-loop's cached-Project invalidation) can ask "which
|
|
3066
|
-
* tsconfig would {@link createCodegenProject} resolve right now?" without
|
|
3067
|
-
* duplicating the walk — recomputing it per call is a handful of `existsSync`
|
|
3068
|
-
* checks, negligible next to a Project rebuild.
|
|
3069
|
-
*/
|
|
3070
|
-
declare const findTsconfig: (startPath: string) => string | undefined;
|
|
3071
|
-
/**
|
|
3072
|
-
* Construct the ts-morph `Project` codegen discovers over. Prefers the user's
|
|
3073
|
-
* `tsconfig.json` (when one is found walking up from `lunoraDirectory`) so
|
|
3074
|
-
* cross-file type resolution and path aliases work; falls back to an isolated
|
|
3075
|
-
* project otherwise. This is the exact construction {@link runCodegen} uses
|
|
3076
|
-
* when no `project` is injected — exported so a long-lived caller (the Vite
|
|
3077
|
-
* dev-loop) can build one once and reuse it across runs via
|
|
3078
|
-
* {@link refreshCodegenProject} instead of re-parsing the user's whole TS
|
|
3079
|
-
* program on every save.
|
|
3080
|
-
*/
|
|
3081
|
-
declare const createCodegenProject: (lunoraDirectory: string) => Project;
|
|
3082
|
-
/**
|
|
3083
|
-
* Synchronise a reused {@link createCodegenProject} Project with the current
|
|
3084
|
-
* on-disk state of `lunoraDirectory`, so the next {@link runCodegen} sees the
|
|
3085
|
-
* same files a freshly-constructed Project would — without re-parsing the whole
|
|
3086
|
-
* TS program. Adds any on-disk source file the Project doesn't yet have, and
|
|
3087
|
-
* `refreshFromFileSystemSync()`es the ones it does (picking up edits); then
|
|
3088
|
-
* removes Project source files under `lunoraDirectory` that no longer exist on
|
|
3089
|
-
* disk (the classic stale-deleted-file cache bug).
|
|
3090
|
-
*
|
|
3091
|
-
* Files outside `lunoraDirectory` — e.g. a shared validator or type pulled in
|
|
3092
|
-
* via the user's tsconfig — are also `refreshFromFileSystemSync()`ed, but only
|
|
3093
|
-
* the ones already loaded into the Project; none are added. `resolveValidatorAlias`
|
|
3094
|
-
* (parse-validator.ts) follows `getAliasedSymbol()` across module boundaries, so a
|
|
3095
|
-
* validator defined outside `lunoraDirectory` is genuinely read from whatever
|
|
3096
|
-
* source the Project currently holds — leaving those files stale made a reused
|
|
3097
|
-
* Project (the Vite dev loop) silently disagree with a fresh one (`lunora
|
|
3098
|
-
* codegen`) about the same source. `node_modules` is excluded: its `.d.ts` set
|
|
3099
|
-
* dominates the file count and never changes mid dev-loop, so refreshing it would
|
|
3100
|
-
* reinstate close to the full re-parse cost this cache exists to avoid.
|
|
3101
|
-
*/
|
|
3102
|
-
declare const refreshCodegenProject: (project: Project, lunoraDirectory: string) => void;
|
|
3103
|
-
/**
|
|
3104
|
-
* Top-level codegen entry. Parses `<projectRoot>/lunora/schema.ts` and every
|
|
3105
|
-
* function file under `<projectRoot>/lunora/`, then writes
|
|
3106
|
-
* `_generated/{api,server,dataModel}.ts` next to them.
|
|
3107
|
-
*
|
|
3108
|
-
* When `LUNORA_CODEGEN_TIMING` is set (truthy), a single diagnostic summary
|
|
3109
|
-
* line is written to stderr with the total wall time and the discovery-vs-emit
|
|
3110
|
-
* split — opt-in instrumentation that is otherwise zero-cost and side-effect-free
|
|
3111
|
-
* on the returned {@link CodegenResult}.
|
|
3112
|
-
*/
|
|
3113
|
-
declare const runCodegen: (options: CodegenOptions) => CodegenResult;
|
|
3114
3119
|
interface CodegenOptions {
|
|
3115
3120
|
/**
|
|
3116
3121
|
* Which machine-readable API spec(s) to emit into `_generated/`.
|
|
@@ -3331,6 +3336,68 @@ interface CodegenResult {
|
|
|
3331
3336
|
*/
|
|
3332
3337
|
writtenFiles: ReadonlyArray<string>;
|
|
3333
3338
|
}
|
|
3339
|
+
/**
|
|
3340
|
+
* Walk up from `startPath` until we find a `tsconfig.json` or hit the file
|
|
3341
|
+
* system root. Returns the absolute path to the tsconfig, or `undefined`.
|
|
3342
|
+
*
|
|
3343
|
+
*
|
|
3344
|
+
* Exported (see the bottom-of-file `export { findTsconfig }`) so a long-lived
|
|
3345
|
+
* caller (the Vite dev-loop's cached-Project invalidation) can ask "which
|
|
3346
|
+
* tsconfig would `createCodegenProject` resolve right now?" without
|
|
3347
|
+
* duplicating the walk — recomputing it per call is a handful of `existsSync`
|
|
3348
|
+
* checks, negligible next to a Project rebuild.
|
|
3349
|
+
*/
|
|
3350
|
+
declare const findTsconfig: (startPath: string) => string | undefined;
|
|
3351
|
+
/**
|
|
3352
|
+
* Construct the ts-morph `Project` codegen discovers over. Prefers the user's
|
|
3353
|
+
* `tsconfig.json` (when one is found walking up from `lunoraDirectory`) so
|
|
3354
|
+
* cross-file type resolution and path aliases work; falls back to an isolated
|
|
3355
|
+
* project otherwise. This is the exact construction `runCodegen` uses
|
|
3356
|
+
* when no `project` is injected — exported so a long-lived caller (the Vite
|
|
3357
|
+
* dev-loop) can build one once and reuse it across runs via
|
|
3358
|
+
* {@link refreshCodegenProject} instead of re-parsing the user's whole TS
|
|
3359
|
+
* program on every save.
|
|
3360
|
+
*/
|
|
3361
|
+
declare const createCodegenProject: (lunoraDirectory: string) => Project;
|
|
3362
|
+
/**
|
|
3363
|
+
* Synchronise a reused `createCodegenProject` Project with the current
|
|
3364
|
+
* on-disk state of `lunoraDirectory`, so the next `runCodegen` sees the
|
|
3365
|
+
* same files a freshly-constructed Project would — without re-parsing the whole
|
|
3366
|
+
* TS program. Adds any on-disk source file the Project doesn't yet have, and
|
|
3367
|
+
* `refreshFromFileSystemSync()`es the ones it does (picking up edits); then
|
|
3368
|
+
* removes Project source files under `lunoraDirectory` that no longer exist on
|
|
3369
|
+
* disk (the classic stale-deleted-file cache bug).
|
|
3370
|
+
*
|
|
3371
|
+
* Files outside `lunoraDirectory` — e.g. a shared validator or type pulled in
|
|
3372
|
+
* via the user's tsconfig — are also `refreshFromFileSystemSync()`ed, but only
|
|
3373
|
+
* the ones already loaded into the Project; none are added. `resolveValidatorAlias`
|
|
3374
|
+
* (parse-validator.ts) follows `getAliasedSymbol()` across module boundaries, so a
|
|
3375
|
+
* validator defined outside `lunoraDirectory` is genuinely read from whatever
|
|
3376
|
+
* source the Project currently holds — leaving those files stale made a reused
|
|
3377
|
+
* Project (the Vite dev loop) silently disagree with a fresh one (`lunora
|
|
3378
|
+
* codegen`) about the same source. `node_modules` is excluded: its `.d.ts` set
|
|
3379
|
+
* dominates the file count and never changes mid dev-loop, so refreshing it would
|
|
3380
|
+
* reinstate close to the full re-parse cost this cache exists to avoid.
|
|
3381
|
+
*/
|
|
3382
|
+
declare const refreshCodegenProject: (project: Project, lunoraDirectory: string) => void;
|
|
3383
|
+
/**
|
|
3384
|
+
* Committed, tracked baseline file holding the blessed structural schema
|
|
3385
|
+
* snapshot the pre-deploy drift gate diffs against. Lives in `lunora/` (NOT the
|
|
3386
|
+
* gitignored `_generated/`) so it is committed alongside `schema.ts`. Leading
|
|
3387
|
+
* dot keeps it tucked away next to the schema it describes.
|
|
3388
|
+
*/
|
|
3389
|
+
declare const SCHEMA_SNAPSHOT_FILENAME = ".lunora-schema.json";
|
|
3390
|
+
/**
|
|
3391
|
+
* Top-level codegen entry. Parses `<projectRoot>/lunora/schema.ts` and every
|
|
3392
|
+
* function file under `<projectRoot>/lunora/`, then writes
|
|
3393
|
+
* `_generated/{api,server,dataModel}.ts` next to them.
|
|
3394
|
+
*
|
|
3395
|
+
* When `LUNORA_CODEGEN_TIMING` is set (truthy), a single diagnostic summary
|
|
3396
|
+
* line is written to stderr with the total wall time and the discovery-vs-emit
|
|
3397
|
+
* split — opt-in instrumentation that is otherwise zero-cost and side-effect-free
|
|
3398
|
+
* on the returned {@link CodegenResult}.
|
|
3399
|
+
*/
|
|
3400
|
+
declare const runCodegen: (options: CodegenOptions) => CodegenResult;
|
|
3334
3401
|
declare const sortedUniqueNames: (names: Iterable<string>) => ReadonlyArray<string>;
|
|
3335
3402
|
/** Deduplicated, sorted names of every ERROR-level advisory in `advisories`. */
|
|
3336
3403
|
declare const errorAdvisoryNames: (advisories: ReadonlyArray<Pick<Finding, "level" | "name">>) => ReadonlyArray<string>;
|
|
@@ -3586,7 +3653,7 @@ declare const readPackageDependencies: (projectRoot: string) => Set<string> | un
|
|
|
3586
3653
|
* unfiltered, unindexed `.collect()` is the read `unbounded_collect` exists for
|
|
3587
3654
|
* — and dropping it here is precisely why nothing could see it.
|
|
3588
3655
|
*/
|
|
3589
|
-
declare const discoverQueries: (project: Project, lunoraDirectory: string) => QueryReadIR[];
|
|
3656
|
+
declare const discoverQueries: (project: Project, lunoraDirectory: string, functions?: ReadonlyArray<FunctionIR>) => QueryReadIR[];
|
|
3590
3657
|
/** The file queues are declared in: `lunora/queues.ts`, and the same name directly inside a module folder. */
|
|
3591
3658
|
declare const QUEUES_FILENAME = "queues.ts";
|
|
3592
3659
|
/** The queues half of {@link discoverQueueDeclarations} — what the config layer reconciles into wrangler. */
|