@lunora/codegen 1.0.0-alpha.81 → 1.0.0-alpha.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/index.d.mts +1125 -1074
  2. package/dist/index.d.ts +1125 -1074
  3. package/dist/index.mjs +1 -1
  4. package/dist/packem_shared/DEFAULT_TARGET-CH5disA3.mjs +1 -0
  5. package/dist/packem_shared/GENERATED_HEADER-oy7DTIDi.mjs +1 -0
  6. package/dist/packem_shared/{OPENRPC_VERSION-DfKtsdPS.mjs → OPENRPC_VERSION-K3RTK2CP.mjs} +1 -1
  7. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-ZIqaP_TZ.mjs +9 -0
  8. package/dist/packem_shared/SchemaSnapshotParseError-BJkhTEpi.mjs +16 -0
  9. package/dist/packem_shared/{buildOpenApiDocument-D46nZ7D_.mjs → buildOpenApiDocument-Blf3mpIz.mjs} +1 -1
  10. package/dist/packem_shared/describeErrorLevelFindings-IhNx9gPr.mjs +1 -0
  11. package/dist/packem_shared/discover-queries-Bhu9SNsy.mjs +1 -0
  12. package/dist/packem_shared/discoverCrons-DLwFH_K5.mjs +1 -0
  13. package/dist/packem_shared/discoverMaskProcedures-ZewKJLXx.mjs +1 -0
  14. package/dist/packem_shared/{discoverQueries-2A0lbpxV.mjs → discoverQueries-g4qZzLpl.mjs} +1 -1
  15. package/dist/packem_shared/discoverSchema-4nJgoLaJ.mjs +1 -0
  16. package/dist/packem_shared/{emit-CCw5fiXq.mjs → emit-B7IUlWwi.mjs} +371 -248
  17. package/dist/packem_shared/{emitApp-DEHN578T.mjs → emitApp-jRD2D9kI.mjs} +112 -37
  18. package/dist/packem_shared/readPackageDependencies-CogJX1Ia.mjs +1 -0
  19. package/package.json +10 -10
  20. package/dist/packem_shared/DEFAULT_TARGET-BYPEwgZu.mjs +0 -1
  21. package/dist/packem_shared/GENERATED_HEADER-CR3xA8yO.mjs +0 -1
  22. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-DjpfF4p7.mjs +0 -12
  23. package/dist/packem_shared/SchemaSnapshotParseError-0KRzSjo4.mjs +0 -14
  24. package/dist/packem_shared/discover-queries-Ctpk9SA-.mjs +0 -1
  25. package/dist/packem_shared/discoverCrons-CwtPaymU.mjs +0 -1
  26. package/dist/packem_shared/discoverMaskProcedures-DQ-4yUUK.mjs +0 -1
  27. package/dist/packem_shared/discoverSchema-DUl8rbP3.mjs +0 -1
package/dist/index.d.mts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { AdvisorExportSink, AdvisorGeoIndexUsage, AdvisorNotifyCall, AdvisorNotifyConfig, Finding, LintContext, AdvisorProcedureProtection } from '@lunora/advisor';
2
2
  export type { Finding } from '@lunora/advisor';
3
+ import '@lunora/platform';
4
+ import { Project, Node } from 'ts-morph';
3
5
  import { LunoraError } from '@lunora/errors';
4
6
  export { MESSAGE_SOLUTIONS as LUNORA_SOLUTION_RULES, type Solution as LunoraSolution, type SolutionRule as LunoraSolutionRule, findSolutionByMessage as findLunoraSolution } from '@lunora/errors';
5
- import { Node, Project } from 'ts-morph';
6
7
  import { StudioFeaturesResult } from '@lunora/shard-engine';
7
- import '@lunora/platform';
8
8
  import { Schema } from '@lunora/server';
9
9
  import { JsonSchema } from '@lunora/values';
10
10
  /**
@@ -1140,22 +1140,31 @@ interface HttpRouteIR {
1140
1140
  * pass straight through to the advisor without conversion.
1141
1141
  */
1142
1142
  interface ProcedureMiddlewareIR {
1143
- /** `true` when the handler (or a helper inside it) references `ctx.mail` / `ctx.email`. */
1144
- callsMail: boolean;
1143
+ /**
1144
+ * `true` when the handler body could be read statically — an inline function
1145
+ * expression/arrow, or a same-file identifier resolved to one. `false` for a
1146
+ * genuinely cross-file handler (an imported function, or an identifier that
1147
+ * doesn't resolve in this file), in which case every behavioural fact below
1148
+ * is `undefined` rather than a false "not observed" — the feeder never saw
1149
+ * the body, so it has nothing to report.
1150
+ */
1151
+ analyzableBody: boolean;
1152
+ /** `true` when the handler (or a helper inside it) references `ctx.mail` / `ctx.email`. `undefined` when `analyzableBody` is `false`. */
1153
+ callsMail?: boolean;
1145
1154
  /** `true` when the handler emits a structured observability event (`ctx.log` / `ctx.span` / `ctx.trace`). */
1146
- emitsEvent: boolean;
1155
+ emitsEvent?: boolean;
1147
1156
  /** `true` when a `// lunora-advisor-exempt` directive sits above the export. */
1148
1157
  exempt: boolean;
1149
1158
  /** The `-- reason` from that directive, or `""`. */
1150
1159
  exemptReason: string;
1151
1160
  /** Export binding name of the procedure (e.g. `signUp`). */
1152
1161
  exportName: string;
1153
- /** `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. */
1154
- fanOut: boolean;
1162
+ /** `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`. */
1163
+ fanOut?: boolean;
1155
1164
  /** Source file relative to `<projectRoot>/lunora/`, without extension. */
1156
1165
  file: string;
1157
1166
  /** `true` when the handler wraps work in `try`/`catch`. */
1158
- handlesErrors: boolean;
1167
+ handlesErrors?: boolean;
1159
1168
  /**
1160
1169
  * `true` when the procedure declares an email-shaped argument (`email`,
1161
1170
  * `emailAddress`, `userEmail`, …), `false` when it provably declares none,
@@ -1169,19 +1178,19 @@ interface ProcedureMiddlewareIR {
1169
1178
  hasEmailArg?: boolean;
1170
1179
  kind: "action" | "mutation" | "query";
1171
1180
  /** `true` when the handler reaches an outbound surface (`ctx.fetch`, mail, queues, storage, sql, ai, …) that can fail. */
1172
- reachesOutbound: boolean;
1181
+ reachesOutbound?: boolean;
1173
1182
  /** `true` when the handler runs any AI generation, bounded or not. */
1174
- runsAiGeneration: boolean;
1183
+ runsAiGeneration?: boolean;
1175
1184
  /** `true` when the handler throws a bare `new Error(...)` rather than a coded `LunoraError`. */
1176
- throwsBareError: boolean;
1177
- /** `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. */
1178
- unboundedAiGeneration: boolean;
1185
+ throwsBareError?: boolean;
1186
+ /** `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`. */
1187
+ unboundedAiGeneration?: boolean;
1179
1188
  /** `true` when the chain carries `.use(verifyTurnstile(...))` or a `protectPublic({ captcha })` bundle. */
1180
1189
  usesCaptcha: boolean;
1181
1190
  /** `true` when the chain carries `.use(emailGateMiddleware(...))` (`@lunora/auth`). Feeds the `signup_mutation_without_disposable_gating` lint. */
1182
1191
  usesEmailGate: boolean;
1183
- /** `true` when the handler calls `ctx.db.insertManyUnsafe(...)`, bypassing validators and triggers. Feeds the `insert_many_unsafe_user_data` lint. */
1184
- usesInsertManyUnsafe: boolean;
1192
+ /** `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`. */
1193
+ usesInsertManyUnsafe?: boolean;
1185
1194
  /** `true` when the chain carries `.use(mask(...))`. */
1186
1195
  usesMask: boolean;
1187
1196
  /** `true` when the chain carries `.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle. */
@@ -1190,8 +1199,8 @@ interface ProcedureMiddlewareIR {
1190
1199
  usesRls: boolean;
1191
1200
  /** `"internal"` for `internalQuery` / `internalMutation` / `internalAction`. */
1192
1201
  visibility: "internal" | "public";
1193
- /** `true` when the handler inserts into a user/session/account-shaped table. */
1194
- writesUserTable: boolean;
1202
+ /** `true` when the handler inserts into a user/session/account-shaped table. `undefined` when `analyzableBody` is `false`. */
1203
+ writesUserTable?: boolean;
1195
1204
  }
1196
1205
  /**
1197
1206
  * Per-procedure argument-validator snapshot, produced by the
@@ -1374,6 +1383,14 @@ interface StorageKeyAccessIR {
1374
1383
  line: number;
1375
1384
  /** The bucket method invoked with the arg-derived key, e.g. `get` / `put` / `delete` / `download`. */
1376
1385
  method: string;
1386
+ /**
1387
+ * Visibility of the enclosing procedure. `internal` procedures have no
1388
+ * untrusted caller by construction — see `owner_field_from_args_not_auth`'s
1389
+ * identical split — so `storage_key_from_user_args` drops the finding to
1390
+ * INFO rather than ERROR there. `undefined` when the access sits outside any
1391
+ * registered procedure the feeder could attribute it to.
1392
+ */
1393
+ visibility?: "internal" | "public";
1377
1394
  }
1378
1395
  /**
1379
1396
  * One `ctx.containers.<exportName>.get(name, …)` call whose instance key is derived
@@ -2040,1152 +2057,1184 @@ declare const lintSchema: (options: LintSchemaOptions) => Finding[];
2040
2057
  */
2041
2058
  declare const formatAdvisories: (findings: ReadonlyArray<Finding>) => string;
2042
2059
  /**
2043
- * An error thrown by codegen discovery when the user's schema or function
2044
- * source has a structural problem that can be pinpointed to a specific source
2045
- * location. A `LunoraError` subclass (`code: "CODEGEN_DIAGNOSTIC"`); the `file`,
2046
- * `line`, and `column` properties (also passed through as the base `loc`) mirror
2047
- * what Vite's error-overlay `loc` field expects so the browser can display the
2048
- * exact spot.
2060
+ * The canonical capability list. **Order is load-bearing** for the `emit-app.ts`
2061
+ * long-tail: the fluent methods are emitted in the order the `appMethod`-bearing
2062
+ * rows appear here, so this array is ordered to reproduce the original
2063
+ * `LONG_TAIL` sequence (ai, analytics, browser, hyperdrive, images, kv, payment,
2064
+ * r2sql, vectors). The `serverCtxField` rows are referenced by name in the ctx
2065
+ * interface templates, so their order here is not output-affecting.
2049
2066
  */
2050
- declare class CodegenDiagnosticError extends LunoraError {
2051
- readonly column: number;
2052
- readonly file: string;
2053
- readonly line: number;
2054
- constructor(message: string, file: string, line: number, column: number);
2055
- }
2067
+ declare const CAPABILITY_ROWS: readonly [{
2068
+ readonly contextProperty: "access";
2069
+ readonly key: "access";
2070
+ readonly moduleSpecifier: "@lunora/cloudflare-access";
2071
+ }, {
2072
+ readonly appMethod: {
2073
+ readonly configKey: "ai";
2074
+ readonly doc: "Override the Workers AI binding backing `ctx.ai` (defaults to `env.AI`).";
2075
+ readonly method: "ai";
2076
+ };
2077
+ readonly contextProperty: "ai";
2078
+ readonly key: "ai";
2079
+ readonly moduleSpecifier: "@lunora/ai";
2080
+ }, {
2081
+ readonly appMethod: {
2082
+ readonly configKey: "analytics";
2083
+ readonly doc: "Override the Analytics Engine dataset backing `ctx.analytics` (defaults to `env.ANALYTICS`).";
2084
+ readonly method: "analytics";
2085
+ };
2086
+ readonly contextProperty: "analytics";
2087
+ readonly key: "analytics";
2088
+ readonly moduleSpecifier: "@lunora/bindings/analytics";
2089
+ readonly serverCtxField: {
2090
+ readonly field: "\n /** Analytics Engine telemetry sink. Fire-and-forget and sampled; do not read it back in-handler. */\n readonly analytics: import(\"@lunora/bindings/analytics\").AnalyticsClient;";
2091
+ readonly tier: "every";
2092
+ };
2093
+ }, {
2094
+ readonly appMethod: {
2095
+ readonly configKey: "browser";
2096
+ readonly doc: "Override the Browser Rendering binding backing `ctx.browser` (defaults to `env.BROWSER`).";
2097
+ readonly method: "browser";
2098
+ };
2099
+ readonly contextProperty: "browser";
2100
+ readonly key: "browser";
2101
+ readonly moduleSpecifier: "@lunora/browser";
2102
+ readonly serverCtxField: {
2103
+ readonly field: "\n /** Browser Rendering (screenshots/PDF/scrape). Non-deterministic — available only in actions. */\n readonly browser: import(\"@lunora/browser\").Browser;";
2104
+ readonly tier: "action";
2105
+ };
2106
+ }, {
2107
+ readonly contextProperty: "containers";
2108
+ readonly key: "container";
2109
+ readonly moduleSpecifier: "@lunora/container";
2110
+ }, {
2111
+ readonly contextProperty: "flags";
2112
+ readonly key: "flags";
2113
+ readonly moduleSpecifier: "@lunora/flags";
2114
+ }, {
2115
+ readonly appMethod: {
2116
+ readonly configKey: "sql";
2117
+ readonly doc: "Wire the Hyperdrive SQL client backing `ctx.sql` — build it with `createHyperdrive` + `fromPostgresJs`/`fromNodePg`/`fromMysql2`.";
2118
+ readonly method: "hyperdrive";
2119
+ };
2120
+ readonly contextProperty: "sql";
2121
+ readonly key: "hyperdrive";
2122
+ readonly moduleSpecifier: "@lunora/hyperdrive";
2123
+ readonly serverCtxField: {
2124
+ readonly field: "\n /**\n * External database access via Hyperdrive. Non-deterministic — available only in actions. Writes here are NOT tracked by Lunora live queries; subscriptions will not re-run on external DB changes.\n */\n readonly sql: import(\"@lunora/hyperdrive\").SqlClient;";
2125
+ readonly tier: "action";
2126
+ };
2127
+ }, {
2128
+ readonly appMethod: {
2129
+ readonly configKey: "images";
2130
+ readonly doc: "Override the Images binding backing `ctx.images` (defaults to `env.IMAGES`).";
2131
+ readonly method: "images";
2132
+ };
2133
+ readonly contextProperty: "images";
2134
+ readonly key: "images";
2135
+ readonly moduleSpecifier: "@lunora/bindings/images";
2136
+ readonly serverCtxField: {
2137
+ readonly field: "\n /** Cloudflare Images transforms (resize/format/optimize). Non-deterministic — available only in actions. */\n readonly images: import(\"@lunora/bindings/images\").Images;";
2138
+ readonly tier: "action";
2139
+ };
2140
+ }, {
2141
+ readonly appMethod: {
2142
+ readonly configKey: "kv";
2143
+ readonly doc: "Override the Workers KV binding backing `ctx.kv` (defaults to `env.KV`).";
2144
+ readonly method: "kv";
2145
+ };
2146
+ readonly contextProperty: "kv";
2147
+ readonly key: "kv";
2148
+ readonly moduleSpecifier: "@lunora/bindings/kv";
2149
+ readonly serverCtxField: {
2150
+ readonly field: "\n readonly kv: import(\"@lunora/bindings/kv\").Kv;";
2151
+ readonly tier: "every";
2152
+ };
2153
+ }, {
2154
+ readonly key: "mail";
2155
+ readonly moduleSpecifier: "@lunora/mail";
2156
+ }, {
2157
+ readonly contextProperty: "notify";
2158
+ readonly key: "notify";
2159
+ readonly moduleSpecifier: "@lunora/notify";
2160
+ }, {
2161
+ readonly appMethod: {
2162
+ readonly configKey: "payment";
2163
+ readonly doc: "Wire the payment options backing `ctx.payments`.";
2164
+ readonly method: "payment";
2165
+ };
2166
+ readonly contextProperty: "payments";
2167
+ readonly key: "payments";
2168
+ readonly moduleSpecifier: "@lunora/payment";
2169
+ }, {
2170
+ readonly appMethod: {
2171
+ readonly configKey: "x402";
2172
+ readonly doc: "Wire the x402 agent-wallet pay rail backing `ctx.x402` — a payment-enabled `fetch` that answers `402` challenges under a mandatory spend policy (ActionCtx-only; spends real funds).";
2173
+ readonly method: "x402";
2174
+ };
2175
+ readonly contextProperty: "x402";
2176
+ readonly key: "x402";
2177
+ readonly moduleSpecifier: "@lunora/x402/pay";
2178
+ }, {
2179
+ readonly contextProperty: "pipelines";
2180
+ readonly key: "pipelines";
2181
+ readonly moduleSpecifier: "@lunora/bindings/pipelines";
2182
+ readonly serverCtxField: {
2183
+ readonly field: "\n /** Pipelines ingestion sink (durable, R2-backed). Fire-and-forget and batched; do not read it back in-handler. */\n readonly pipelines: import(\"@lunora/bindings/pipelines\").PipelineClient;";
2184
+ readonly tier: "action";
2185
+ };
2186
+ }, {
2187
+ readonly appMethod: {
2188
+ readonly configKey: "r2sql";
2189
+ readonly doc: "Wire the R2 SQL client backing `ctx.r2sql` — build it with `createR2Sql({ accountId, apiToken, bucket })` (defaults to env `R2_SQL_TOKEN` / `R2_SQL_ACCOUNT_ID` / `R2_SQL_BUCKET`).";
2190
+ readonly method: "r2sql";
2191
+ };
2192
+ readonly contextProperty: "r2sql";
2193
+ readonly key: "r2sql";
2194
+ readonly moduleSpecifier: "@lunora/bindings/r2sql";
2195
+ readonly serverCtxField: {
2196
+ readonly field: "\n /**\n * R2 SQL over Apache Iceberg tables (window functions, DISTINCT, set operations). Non-deterministic — available only in actions. Reads here are NOT tracked by Lunora live queries.\n */\n readonly r2sql: import(\"@lunora/bindings/r2sql\").R2SqlClient;";
2197
+ readonly tier: "action";
2198
+ };
2199
+ }, {
2200
+ readonly contextProperty: "scheduler";
2201
+ readonly key: "scheduler";
2202
+ readonly moduleSpecifier: "@lunora/scheduler";
2203
+ }, {
2204
+ readonly contextProperty: "storage";
2205
+ readonly key: "storage";
2206
+ readonly moduleSpecifier: "@lunora/storage";
2207
+ }, {
2208
+ readonly appMethod: {
2209
+ readonly configKey: "vectors";
2210
+ readonly doc: "Wire the Vectorize index map backing `ctx.vectors`.";
2211
+ readonly method: "vectors";
2212
+ };
2213
+ readonly contextProperty: "vectors";
2214
+ readonly key: "vectors";
2215
+ readonly moduleSpecifier: "@lunora/bindings/vectors";
2216
+ }, {
2217
+ readonly contextProperty: "workflows";
2218
+ readonly key: "workflows";
2219
+ readonly moduleSpecifier: "@lunora/workflow";
2220
+ }];
2221
+ /** The literal union of every capability id — the single source of truth for `FeatureUsage`'s keys (so they cannot drift). */
2222
+ type CapabilityKey = (typeof CAPABILITY_ROWS)[number]["key"];
2223
+ /** The default codegen target — today's behavior, byte-identical goldens. */
2224
+ declare const DEFAULT_TARGET = "cloudflare";
2056
2225
  /**
2057
- * Build a {@link CodegenDiagnosticError} whose message includes the source
2058
- * location and whose `file`/`line`/`column` properties are set from the
2059
- * ts-morph `Node`'s position in its source file.
2226
+ * Read `target` from `&lt;projectRoot>/lunora.json`.
2060
2227
  *
2061
- * Message format: `@lunora/codegen: &lt;detail> (&lt;file>:&lt;line>:&lt;column>)`
2228
+ * This lives in `@lunora/codegen` rather than `@lunora/config` — where the rest
2229
+ * of the `lunora.json` reading lives — because `@lunora/config` depends on
2230
+ * `@lunora/codegen`, not the reverse. Putting it there and importing it here
2231
+ * would invert that edge, so config delegates to this instead and there is
2232
+ * still exactly one parser for the key.
2062
2233
  *
2063
- * `meta` is merged onto the returned error for callers that also carry the
2064
- * project-wide `LunoraError` envelope (`code`/`name`/`status`) it never
2065
- * touches `file`/`line`/`column`, and the error stays an instance of
2066
- * {@link CodegenDiagnosticError} so the Vite overlay's `instanceof` location
2067
- * lookup is unaffected.
2234
+ * Best-effort and deliberately unvalidated: a missing file, malformed JSONC, or
2235
+ * a non-string value all collapse to `undefined`, because those are shape
2236
+ * errors rather than a name the user meant. An unrecognized *name* is returned
2237
+ * as-is so the caller's registry lookup rejects it — swallowing a typo into the
2238
+ * default would ship an app to the wrong provider.
2239
+ * @param projectRoot Directory containing `lunora.json`.
2240
+ * @returns the declared target, or `undefined` when none is usable.
2068
2241
  */
2069
- declare const diagnosticAt: (node: Node, detail: string, meta?: Record<string, unknown>) => CodegenDiagnosticError;
2070
- /** The only file agents may be declared in — mirrors `lunora/workflows.ts`. */
2071
- declare const AGENTS_FILENAME = "agents.ts";
2242
+ declare const readProjectTarget: (projectRoot: string) => string | undefined;
2072
2243
  /**
2073
- * Discover every agent the project declares: exported `defineAgent()` calls in
2074
- * `lunora/agents.ts`. Returns `[]` when the file doesn't exist. Only four things
2075
- * are read statically — the optional `name` override (wrangler `workflows[].name`),
2076
- * the optional `publicRun` opt-in (the `agents:agentRun` capability gate), the
2077
- * presence of a `voice` block (which turns on the voice-session Durable Object),
2078
- * and the presence of an `onEmail` mapper (which wires the worker `email()`
2079
- * handler); the rest of the agent config (model / tools / memory / voice models /
2080
- * the `onEmail` closure body) is runtime-only, so codegen never evaluates it.
2244
+ * The target codegen should emit for: an explicit option wins, then
2245
+ * `lunora.json`, then the default.
2246
+ *
2247
+ * `runCodegen` applies this itself so a caller that forgets to pass a target
2248
+ * still emits the surface the project declared. That default matters more than
2249
+ * it looks: a call site that silently omits the target emits the *default*
2250
+ * surface with no diagnostic to notice, and the mismatch only shows up at
2251
+ * runtime on the deployed app.
2252
+ * @param projectRoot Directory containing `lunora.json`.
2253
+ * @param explicit A caller-supplied target, if any.
2254
+ * @returns the resolved target id — not guaranteed to be registered.
2081
2255
  */
2082
- declare const discoverAgents: (project: Project, lunoraDirectory: string) => AgentIR[];
2256
+ declare const resolveCodegenTarget: (projectRoot: string, explicit?: string) => string;
2083
2257
  /**
2084
- * Discover `ctx.authApi.&lt;method>(...)` (and bare `authApi.&lt;method>(...)`) calls
2085
- * under the lunora source directory and attribute each to the exported function
2086
- * (and file) performing it. Calls outside an exported declaration are dropped.
2258
+ * The target ids codegen can gate against.
2259
+ *
2260
+ * `@lunora/config`'s driver registry (`deployTargetIds`) used to assert
2261
+ * equality against this — "two id spaces for one concept" — on the theory that
2262
+ * a target with a matrix but no driver "gates a surface nothing can deploy."
2263
+ * Plan 234 found that reasoning incomplete by registering `node` here: a
2264
+ * codegen-gateable target and a deployable target are genuinely different
2265
+ * questions, and a spike/dev-only host answers the first "yes" and the second
2266
+ * "not yet" without that being a bug in either registry. See
2267
+ * `plans/234-node-host-findings.md` for the finding and `@lunora/config`'s
2268
+ * `project-config.test.ts` for where the now-relaxed invariant lives.
2269
+ * @returns the registered matrix ids, sorted.
2087
2270
  */
2088
- declare const discoverAuthApiCalls: (project: Project, lunoraDirectory: string) => AuthApiCallIR[];
2089
- /** The only file containers may be declared in — mirrors `lunora/crons.ts`. */
2090
- declare const CONTAINERS_FILENAME = "containers.ts";
2271
+ declare const platformMatrixIds: () => ReadonlyArray<string>;
2272
+ /** An advisor-style diagnostic about a target's platform capabilities. */
2273
+ interface PlatformDiagnostic {
2274
+ /** The codegen capability this concerns, when it is feature-specific. */
2275
+ feature?: CapabilityKey;
2276
+ /** Severity. All three names are errors — each drops or misdirects an emitted surface. */
2277
+ level: "error" | "warn";
2278
+ /** Human-readable explanation of the gap. */
2279
+ message: string;
2280
+ /** The lint id: `platform_unsupported_feature`, `platform_undeclared_feature`, or `platform_unknown_target`. */
2281
+ name: "platform_undeclared_feature" | "platform_unknown_target" | "platform_unsupported_feature";
2282
+ /** How to resolve it. */
2283
+ remediation: string;
2284
+ /** The requested deploy target. */
2285
+ target: string;
2286
+ }
2091
2287
  /**
2092
- * Discover every container the project declares: exported `defineContainer()`
2093
- * calls in `lunora/containers.ts`. Returns `[]` when the file doesn't exist.
2094
- * Wrangler-relevant fields (`image`, `instanceType`, `maxInstances`, `name`)
2095
- * must be static literals; runtime-only fields (`env`, `sleepAfter`, …) may be
2096
- * any expression since the generated class imports the definition object.
2097
- */
2098
- declare const discoverContainers: (project: Project, lunoraDirectory: string) => ContainerIR[];
2099
- /**
2100
- * Scan every `.ts` file under `lunoraDir` for `cronJobs()` builder registrations
2101
- * (`crons.interval(...)`, `crons.daily(...)`, `crons.cron(...)`, …) and lift them
2102
- * into {@link CronJobIR}. Schedules are compiled to standard cron expressions;
2103
- * function references are resolved to their `namespace:fn` dispatch path, while a
2104
- * `workflows.NAME` / `agents.NAME` reference (or a bare identifier naming a
2105
- * declared workflow) resolves to a durable workflow start. Names must be unique
2106
- * across the project.
2107
- */
2108
- declare const discoverCrons: (project: Project, lunoraDirectory: string, workflows?: ReadonlyArray<WorkflowIR>, agents?: ReadonlyArray<AgentIR>) => CronJobIR[];
2109
- /** The only file a feature-flag provider may be declared in — mirrors `lunora/queues.ts`. */
2110
- declare const FLAGS_FILENAME = "flags.ts";
2111
- /**
2112
- * Discover the feature-flag provider a project declares in `lunora/flags.ts`.
2113
- * Returns `undefined` when the file doesn't exist (the app has no flags). The
2114
- * read is metadata-only and lenient: codegen wires `ctx.flags` purely from the
2115
- * file's *existence* (`run-codegen.ts`) and imports the real module for the
2116
- * provider value — this IR exists solely so the config layer can reconcile the
2117
- * wrangler `flagship` binding for the Flagship binding-mode provider. Anything
2118
- * it can't read statically degrades to a `custom` provider (no binding), never
2119
- * a thrown error.
2120
- */
2121
- declare const discoverFlags: (project: Project, lunoraDirectory: string) => FlagsIR | undefined;
2122
- /**
2123
- * Scan all .ts files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
2124
- * for top-level `export const x = query/mutation/action({...})` registrations.
2125
- */
2126
- declare const discoverFunctions: (project: Project, lunoraDirectory: string) => FunctionIR[];
2127
- /**
2128
- * Scan all `.ts` files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
2129
- * for `export const x = httpRoute.&lt;verb>(...)…handler(...)` typed REST routes.
2130
- * These are the headline OpenAPI target: each becomes a real `paths` entry.
2131
- */
2132
- declare const discoverHttpRoutes: (project: Project, lunoraDirectory: string) => HttpRouteIR[];
2133
- /**
2134
- * Discover `ctx.db.insert("table", …)` writes under the lunora source directory
2135
- * and attribute each to the exported function (and file) performing it. Calls
2136
- * with a non-literal table argument, or outside an exported declaration, are
2137
- * dropped (`table === ""` / no enclosing export).
2138
- */
2139
- declare const discoverInserts: (project: Project, lunoraDirectory: string) => InsertWriteIR[];
2140
- /**
2141
- * Discover masking usage for every exported Lunora procedure under the lunora
2142
- * source directory — the column-level twin of `discoverRlsProcedures`. For each
2143
- * procedure, records whether its builder chain includes `.use(mask(...))`, which
2144
- * `(table, column)` pairs that mask declares, and which tables it reads/writes
2145
- * through `ctx.db`. Feeds the `mask_uncovered_pii_column` advisor lint.
2146
- */
2147
- declare const discoverMaskProcedures: (project: Project, lunoraDirectory: string) => MaskProcedureIR[];
2148
- /**
2149
- * Scan all `.ts` files under `lunoraDir` for top-level
2150
- * `export const x = defineMigration({...})` declarations and lift them into
2151
- * {@link MigrationIR}. `id` must be a static string literal (it's the registry
2152
- * key); `table` is best-effort and left `""` when not a literal.
2153
- */
2154
- declare const discoverMigrations: (project: Project, lunoraDirectory: string) => MigrationIR[];
2155
- /** The only file custom mutators may be declared in — mirrors `lunora/queues.ts`. */
2156
- declare const MUTATORS_FILENAME = "mutators.ts";
2157
- /**
2158
- * Discover every custom mutator the project declares: exported
2159
- * `defineMutator()` calls in `lunora/mutators.ts`. Returns `[]` when the file
2160
- * doesn't exist. The export binding plus the declared `args` / `server` return
2161
- * type are lifted — enough to emit a typed `api.mutators.&lt;name>` reference —
2162
- * while the runtime object still carries the authoritative `server` impl +
2163
- * `handler`, so codegen never evaluates the body. The client `client` impl is
2164
- * split into the browser bundle separately.
2165
- */
2166
- declare const discoverMutators: (project: Project, lunoraDirectory: string) => MutatorIR[];
2167
- /**
2168
- * Discover non-deterministic API calls (`Date.now`, `new Date()`, `Date()`,
2169
- * `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues` — including
2170
- * `globalThis`/`self`/`window`-prefixed receivers — and `fetch`) lexically inside
2171
- * the handler body of every exported `query(...)` / `mutation(...)` registration
2172
- * under the lunora source directory — the `nondeterministic_query_mutation` lint
2173
- * input. `action(...)` (and `stream(...)`) registrations are intentionally
2174
- * skipped: actions run exactly once and may use ambient APIs freely.
2175
- *
2176
- * Traversal is scoped to the handler node (not the whole declaration), mirroring
2177
- * how the auth-api / insert feeders attribute calls — so a call in a sibling
2178
- * helper outside the handler, or in a nested `action(...)` passed elsewhere, is
2179
- * not attributed to the query/mutation. One {@link NondeterministicCallIR} is
2180
- * produced per call site.
2181
- */
2182
- declare const discoverNondeterministicCalls: (project: Project, lunoraDirectory: string) => NondeterministicCallIR[];
2183
- /** The only file a `@lunora/notify` provider may be declared in — mirrors `lunora/flags.ts`. */
2184
- declare const NOTIFY_FILENAME = "notify.ts";
2185
- /**
2186
- * Discover `ctx.notify` / `ctx.push` sends lexically inside the handler body of
2187
- * every exported `query(...)` / `mutation(...)` registration under the lunora
2188
- * source directory — the `notify_send_outside_action` lint input. `action(...)`
2189
- * (and `stream(...)`) registrations are intentionally skipped: a notification
2190
- * send is external I/O that belongs in actions. One {@link AdvisorNotifyCall} is
2191
- * produced per send site.
2192
- */
2193
- declare const discoverNotifyCalls: (project: Project, lunoraDirectory: string) => AdvisorNotifyCall[];
2194
- /**
2195
- * Discover which push channels the project's `lunora/notify.ts` default export
2196
- * (`defineNotify({...})`) wires plus whether any handler sends a push — the
2197
- * `notify_missing_push_config` lint input. Returns `undefined` when the file is
2198
- * absent (the app declares no notify config). The read is metadata-only and
2199
- * lenient (like `discoverFlags`): a `webPush`/`fcm` property's mere presence
2200
- * counts as the channel being wired; a non-literal config degrades to "unwired"
2201
- * rather than throwing.
2202
- */
2203
- declare const discoverNotifyConfig: (project: Project, lunoraDirectory: string) => AdvisorNotifyConfig | undefined;
2204
- /**
2205
- * Discover `ctx.db.query("table")…` reads under the lunora source directory and
2206
- * reduce each to a {@link QueryReadIR}. Only reads that call `.filter()` are
2207
- * returned — an unfiltered read is never a `filter_without_index` candidate, so
2208
- * dropping the rest keeps the lint input small.
2288
+ * Committed, tracked baseline file holding the blessed structural schema
2289
+ * snapshot the pre-deploy drift gate diffs against. Lives in `lunora/` (NOT the
2290
+ * gitignored `_generated/`) so it is committed alongside `schema.ts`. Leading
2291
+ * dot keeps it tucked away next to the schema it describes.
2209
2292
  */
2210
- declare const discoverQueries: (project: Project, lunoraDirectory: string) => QueryReadIR[];
2211
- /** The only file queues may be declared in — mirrors `lunora/workflows.ts`. */
2212
- declare const QUEUES_FILENAME = "queues.ts";
2293
+ declare const SCHEMA_SNAPSHOT_FILENAME = ".lunora-schema.json";
2213
2294
  /**
2214
- * Discover every queue the project declares: exported `defineQueue()` calls in
2215
- * `lunora/queues.ts`. Returns `[]` when the file doesn't exist. Only the
2216
- * wrangler-relevant literals (`name`/`mode`/batch tuning) are read; the handler
2217
- * body is runtime-only, so codegen never evaluates it.
2295
+ * Construct the ts-morph `Project` codegen discovers over. Prefers the user's
2296
+ * `tsconfig.json` (when one is found walking up from `lunoraDirectory`) so
2297
+ * cross-file type resolution and path aliases work; falls back to an isolated
2298
+ * project otherwise. This is the exact construction {@link runCodegen} uses
2299
+ * when no `project` is injected — exported so a long-lived caller (the Vite
2300
+ * dev-loop) can build one once and reuse it across runs via
2301
+ * {@link refreshCodegenProject} instead of re-parsing the user's whole TS
2302
+ * program on every save.
2218
2303
  */
2219
- declare const discoverQueues: (project: Project, lunoraDirectory: string) => QueueIR[];
2304
+ declare const createCodegenProject: (lunoraDirectory: string) => Project;
2220
2305
  /**
2221
- * Discover `ctx.r2sql` accesses lexically inside the handler body of every
2222
- * exported `query(...)` / `mutation(...)` registration under the lunora source
2223
- * directory the `r2sql_outside_action` lint input. `action(...)` (and
2224
- * `stream(...)`) registrations are intentionally skipped: R2 SQL is the
2225
- * external, non-reactive surface that belongs in actions.
2306
+ * Synchronise a reused {@link createCodegenProject} Project with the current
2307
+ * on-disk state of `lunoraDirectory`, so the next {@link runCodegen} sees the
2308
+ * same files a freshly-constructed Project would without re-parsing the whole
2309
+ * TS program. Adds any on-disk source file the Project doesn't yet have, and
2310
+ * `refreshFromFileSystemSync()`es the ones it does (picking up edits); then
2311
+ * removes Project source files under `lunoraDirectory` that no longer exist on
2312
+ * disk (the classic stale-deleted-file cache bug).
2226
2313
  *
2227
- * Traversal is scoped to the handler node (not the whole declaration), mirroring
2228
- * `discoverNondeterministicCalls`so a `ctx.r2sql` touch in a sibling helper
2229
- * outside the handler is not attributed to the query/mutation. One
2230
- * {@link R2sqlCallIR} is produced per access site.
2314
+ * Files outside `lunoraDirectory` (e.g. those pulled in by the user's tsconfig)
2315
+ * are left untouched they back type resolution and rarely change in the
2316
+ * dev-loop; a tsconfig change invalidates the whole cached Project upstream.
2231
2317
  */
2232
- declare const discoverR2sqlCalls: (project: Project, lunoraDirectory: string) => R2sqlCallIR[];
2233
- declare const discoverRlsProcedures: (project: Project, lunoraDirectory: string) => RlsProcedureIR[];
2318
+ declare const refreshCodegenProject: (project: Project, lunoraDirectory: string) => void;
2234
2319
  /**
2235
- * Aggregate the schema-wide RLS metadata the studio's read-only inspector reads:
2236
- * every statically-discovered `(table, on, procedure)` policy entry plus every
2237
- * role declared via `rls(policies, { roles })`. Walks the same builder chains as
2238
- * {@link discoverRlsProcedures} but extracts the richer `{ on }` operation +
2239
- * role/permission shape rather than the lint's table-name set.
2320
+ * Top-level codegen entry. Parses `&lt;projectRoot>/lunora/schema.ts` and every
2321
+ * function file under `&lt;projectRoot>/lunora/`, then writes
2322
+ * `_generated/{api,server,dataModel}.ts` next to them.
2240
2323
  *
2241
- * Only the **builder** form (`c.use(rls(...)).query(...)`) can declare policies,
2242
- * so bare-factory procedures contribute nothing. The `when` predicate is never
2243
- * readit's an opaque JS closure whose logic belongs in code, not the UI.
2244
- * Roles are deduped by name (first declaration wins) so a role registered on
2245
- * several procedures lists once.
2246
- */
2247
- declare const discoverRlsMetadata: (project: Project, lunoraDirectory: string) => RlsMetadataIR;
2248
- /**
2249
- * Which sandbox tools a project imports from `@lunora/agent` (main entry or the
2250
- * `/sandbox` subpath), detected by NAMED value import. Drives two things:
2251
- * registering the `sandbox:invoke` dispatcher (either tool) and provisioning the
2252
- * `BROWSER` wrangler binding (`browserTool` — the browser op runs on
2253
- * `ctx.browser` inside the dispatcher).
2324
+ * When `LUNORA_CODEGEN_TIMING` is set (truthy), a single diagnostic summary
2325
+ * line is written to stderr with the total wall time and the discovery-vs-emit
2326
+ * splitopt-in instrumentation that is otherwise zero-cost and side-effect-free
2327
+ * on the returned {@link CodegenResult}.
2254
2328
  */
2255
- interface SandboxUsage {
2256
- /** `import { browserTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
2257
- usesSandboxBrowser: boolean;
2258
- /** `import { containerTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
2259
- usesSandboxContainer: boolean;
2329
+ declare const runCodegen: (options: CodegenOptions) => CodegenResult;
2330
+ interface CodegenOptions {
2331
+ /**
2332
+ * Which machine-readable API spec(s) to emit into `_generated/`.
2333
+ *
2334
+ * `"openapi"` (the default) writes only `openapi.json` (OpenAPI 3.1; covers
2335
+ * both the RPC functions and `httpRouter()` REST routes). `"openrpc"` writes
2336
+ * only `openrpc.json` (OpenRPC 1.x; the RPC functions only — OpenRPC cannot
2337
+ * represent REST routes). `"both"` writes both files; `"none"` writes neither.
2338
+ *
2339
+ * Regardless of the choice, `CodegenResult.generated.openApi` and `.openRpc`
2340
+ * always carry the rendered string (computation is cheap and pure); only the
2341
+ * on-disk write is gated by this option.
2342
+ */
2343
+ apiSpec?: "both" | "none" | "openapi" | "openrpc";
2344
+ /**
2345
+ * When true, run discovery + emit (so any schema/function parse error
2346
+ * surfaces) but skip writing files to `_generated/`. The returned
2347
+ * `outputDirectory` is still the path that *would* have been written.
2348
+ */
2349
+ dryRun?: boolean;
2350
+ /**
2351
+ * Run the static schema advisor (unindexed FKs, …) during codegen.
2352
+ * Defaults to `true`. When `false`, `CodegenResult.advisories` is empty.
2353
+ * Computed regardless of `dryRun`; codegen never prints them — see
2354
+ * {@link CodegenResult.advisories}.
2355
+ */
2356
+ lint?: boolean;
2357
+ /** Override the lunora subdirectory name. Defaults to `"lunora"`. */
2358
+ lunoraDirectory?: string;
2359
+ /**
2360
+ * Reuse a previously-constructed ts-morph {@link Project} instead of building
2361
+ * a fresh one each run. The caller owns refreshing its source files from disk
2362
+ * (see {@link refreshCodegenProject}) — codegen does not re-read changed files
2363
+ * off an injected Project. Built via {@link createCodegenProject} when absent.
2364
+ * Used by the Vite dev-loop to avoid re-parsing the whole TS program on every
2365
+ * save; omit it (CLI one-shot path) to get the default fresh-Project behaviour.
2366
+ */
2367
+ project?: Project;
2368
+ /** Project root containing the `lunora/` directory. */
2369
+ projectRoot: string;
2370
+ /**
2371
+ * The deploy target codegen tailors the emitted `ctx.*` surface to.
2372
+ * Defaults to `"cloudflare"` — whose capability matrix marks every feature
2373
+ * native or emulated, so the default output is unchanged (byte-identical
2374
+ * goldens). A target that marks a used feature unsupported omits its
2375
+ * `ctx.*` surface and reports it in {@link CodegenResult.platformDiagnostics}.
2376
+ * Only `"cloudflare"` is registered until other per-target `@lunora/platform`
2377
+ * matrices land; an unknown target emits the full surface un-gated and a
2378
+ * `platform_unknown_target` diagnostic.
2379
+ */
2380
+ target?: string;
2381
+ /**
2382
+ * Re-bless the committed schema-drift baseline (`lunora/.lunora-schema.json`)
2383
+ * with the current structural snapshot. The baseline is ALWAYS written on
2384
+ * first capture (when the file is absent); set this to overwrite an existing
2385
+ * one — e.g. after the developer has added the data migration that justifies
2386
+ * a breaking change. Ignored when `dryRun` is true.
2387
+ */
2388
+ updateSchemaBaseline?: boolean;
2389
+ /**
2390
+ * Committed `wrangler.jsonc` `vars` entries that hold plaintext secrets — the
2391
+ * `plaintext_secret_in_wrangler_vars` lint input. Produced by `@lunora/config`
2392
+ * (which reads `wrangler.jsonc`) and threaded through by the CLI / Vite plugin;
2393
+ * codegen only forwards it to the advisor. Absent when no wrangler config is
2394
+ * present or the caller doesn't scan it.
2395
+ */
2396
+ wranglerVariables?: ReadonlyArray<WranglerVariableIR>;
2260
2397
  }
2261
- declare const discoverSandboxUsage: (project: Project, lunoraDirectory: string) => SandboxUsage;
2398
+ interface CodegenResult {
2399
+ /**
2400
+ * The normalized advisor evidence the findings were produced from, so a
2401
+ * caller can score it into a health map (`scoreAdvisor`) without re-running
2402
+ * discovery. `undefined` under `lint: false`.
2403
+ *
2404
+ * Deliberately not scored here: the map carries a `generatedAt` stamp, and
2405
+ * codegen's result stays a pure function of the sources.
2406
+ */
2407
+ advisorContext?: LintContext;
2408
+ /**
2409
+ * Static schema advisor findings (e.g. unindexed foreign keys) produced
2410
+ * this run. Empty when `lint` is `false` or the schema is clean. Codegen
2411
+ * does not print these itself — each caller presents them through its own
2412
+ * channel (the CLI logger, the vite overlay, the studio Advisors table).
2413
+ * `formatAdvisories` is exported for a plain multi-line rendering.
2414
+ */
2415
+ advisories: ReadonlyArray<Finding>;
2416
+ /**
2417
+ * Agents discovered from `defineAgent` exports in `lunora/agents.ts` — the
2418
+ * list the config layer reconciles into wrangler's `workflows[]` array (an
2419
+ * agent compiles onto a Cloudflare Workflow). Agents are NOT Durable Objects,
2420
+ * so this adds no binding or migration. Empty when the project declares none.
2421
+ */
2422
+ agents: ReadonlyArray<AgentIR>;
2423
+ /**
2424
+ * Containers discovered from `defineContainer` exports in
2425
+ * `lunora/containers.ts` — the list the config layer reconciles into
2426
+ * wrangler's `containers[]`, `CONTAINER_*` Durable Object bindings, and
2427
+ * migration classes. Empty when the project declares no containers.
2428
+ */
2429
+ containers: ReadonlyArray<ContainerIR>;
2430
+ /**
2431
+ * Deduplicated cron schedules discovered from `cronJobs()` definitions —
2432
+ * the array the vite plugin reconciles into `wrangler.jsonc`'s
2433
+ * `triggers.crons`. Empty when the project declares no crons.
2434
+ */
2435
+ cronTriggers: ReadonlyArray<string>;
2436
+ generated: {
2437
+ /** WorkflowEntrypoint classes for declared agents (`_generated/agents.ts`); `""` (and not written) when no agents are declared. */
2438
+ agents: string;
2439
+ api: string;
2440
+ /** Fluent worker-composition builder (`_generated/app.ts`) — `defineApp()`. Always written. */
2441
+ app: string;
2442
+ /** Partial-replication collection factories (`_generated/collections.ts`); `""` (and not written) unless the project declares shapes and installs `@lunora/db`. */
2443
+ collections: string;
2444
+ /** Container DO classes (`_generated/containers.ts`); `""` (and not written) when no containers are declared. */
2445
+ containers: string;
2446
+ crons: string;
2447
+ dataModel: string;
2448
+ drizzleGlobal: string;
2449
+ drizzleShard: string;
2450
+ functions: string;
2451
+ /** OpenAPI 3.1.0 document (`_generated/openapi.json`), pretty-printed JSON. */
2452
+ openApi: string;
2453
+ /**
2454
+ * OpenAPI document as an importable TS module (`_generated/openapi.ts`) —
2455
+ * `export const openApiSpec`, the worker imports it for
2456
+ * `createWorker({ openApiSpec })`. Same document as `openApi`. Written
2457
+ * alongside `openapi.json` whenever `apiSpec` includes `openapi`.
2458
+ */
2459
+ openApiModule: string;
2460
+ /** OpenRPC 1.x document (`_generated/openrpc.json`), pretty-printed JSON. Always computed; written only when `apiSpec` includes `openrpc`. */
2461
+ openRpc: string;
2462
+ /**
2463
+ * OpenRPC document as an importable TS module (`_generated/openrpc.ts`) —
2464
+ * `export const openRpcSpec`, for `createWorker({ openRpcSpec })`. Same
2465
+ * document as `openRpc`. Written alongside `openrpc.json` whenever
2466
+ * `apiSpec` includes `openrpc`.
2467
+ */
2468
+ openRpcModule: string;
2469
+ /** Push-consumer queue registry (`_generated/queues.ts`); `""` (and not written) when no push queues are declared. */
2470
+ queues: string;
2471
+ /** Project-bound seed client (`_generated/seed.ts`); `""` (and not written) when `@lunora/seed` is not a declared dependency. */
2472
+ seed: string;
2473
+ server: string;
2474
+ shard: string;
2475
+ /** Static vector-index registry (`_generated/vectors.ts`) — `LUNORA_VECTOR_INDEXES`. Empty array body when the schema declares none. */
2476
+ vectors: string;
2477
+ /** WorkflowEntrypoint classes (`_generated/workflows.ts`); `""` (and not written) when no workflows are declared. */
2478
+ workflows: string;
2479
+ };
2480
+ outputDirectory: string;
2481
+ /**
2482
+ * Portability diagnostics for the requested {@link CodegenOptions.target}:
2483
+ * `ctx.*` features the app uses that the target does not support (omitted
2484
+ * from the emitted surface), or an unknown target. Empty for a
2485
+ * fully-supported app on the default Cloudflare target. Presentation is the
2486
+ * caller's job, like {@link CodegenResult.advisories}.
2487
+ */
2488
+ platformDiagnostics: ReadonlyArray<PlatformDiagnostic>;
2489
+ /**
2490
+ * Queues discovered from `defineQueue` exports in `lunora/queues.ts` — the
2491
+ * list the config layer reconciles into wrangler's `queues.producers[]` /
2492
+ * `queues.consumers[]`. Queues are NOT Durable Objects, so this adds no
2493
+ * binding or migration. Empty when the project declares no queues.
2494
+ */
2495
+ queues: ReadonlyArray<QueueIR>;
2496
+ /**
2497
+ * The CURRENT structural schema snapshot computed this run (tables + field
2498
+ * kinds/optionality + indexes/relations/shard mode + declared migration ids).
2499
+ * The pre-deploy drift gate diffs this against the committed baseline read
2500
+ * from {@link CodegenResult.schemaSnapshotPath}. Always present, even on a
2501
+ * `dryRun`.
2502
+ */
2503
+ schemaSnapshot: SchemaSnapshot;
2504
+ /** Absolute path of the committed baseline file (`lunora/.lunora-schema.json`). */
2505
+ schemaSnapshotPath: string;
2506
+ /**
2507
+ * Workflows discovered from `defineWorkflow` exports in
2508
+ * `lunora/workflows.ts` — the list the config layer reconciles into
2509
+ * wrangler's `workflows[]` array. Workflows are NOT Durable Objects, so this
2510
+ * adds no binding or migration. Empty when the project declares no workflows.
2511
+ */
2512
+ workflows: ReadonlyArray<WorkflowIR>;
2513
+ }
2514
+ /** Deduplicated, sorted names of every ERROR-level advisory in `advisories`. */
2515
+ declare const errorAdvisoryNames: (advisories: ReadonlyArray<Pick<Finding, "level" | "name">>) => ReadonlyArray<string>;
2516
+ /** Deduplicated, sorted names of every error-level platform diagnostic in `platformDiagnostics`. */
2517
+ declare const errorPlatformDiagnosticNames: (platformDiagnostics: ReadonlyArray<Pick<PlatformDiagnostic, "level" | "name">>) => ReadonlyArray<string>;
2262
2518
  /**
2263
- * Load `&lt;projectRoot>/lunora/schema.ts`, find `defineSchema({...})`, and
2264
- * return a structural IR. Throws if the file or call cannot be found.
2519
+ * Convenience read combining both categories from a full {@link CodegenResult}
2520
+ * the shape the Vite plugin's `buildBlockingMessage` needs, which (unlike
2521
+ * the CLI's `lunora codegen`/`lunora deploy`) folds ERROR-level advisories and
2522
+ * platform diagnostics into a single blocking message with no strict/CI
2523
+ * opt-out.
2265
2524
  */
2266
- declare const discoverSchema: (project: Project, schemaPath: string, projectRoot?: string) => SchemaIR;
2267
- /** The only file shapes may be declared in — mirrors `lunora/queues.ts`. */
2268
- declare const SHAPES_FILENAME = "shapes.ts";
2525
+ declare const describeErrorLevelFindings: (result: Pick<CodegenResult, "advisories" | "platformDiagnostics">) => {
2526
+ advisoryNames: ReadonlyArray<string>;
2527
+ platformDiagnosticNames: ReadonlyArray<string>;
2528
+ };
2269
2529
  /**
2270
- * Discover every replication shape the project declares: exported
2271
- * `defineShape()` calls in `lunora/shapes.ts`. Returns `[]` when the file
2272
- * doesn't exist. Only the export binding is lifted — the runtime object carries
2273
- * the authoritative `table`/`columns`/`compileWhere`, so codegen never
2274
- * evaluates the predicate.
2530
+ * An error thrown by codegen discovery when the user's schema or function
2531
+ * source has a structural problem that can be pinpointed to a specific source
2532
+ * location. A `LunoraError` subclass (`code: "CODEGEN_DIAGNOSTIC"`); the `file`,
2533
+ * `line`, and `column` properties (also passed through as the base `loc`) mirror
2534
+ * what Vite's error-overlay `loc` field expects so the browser can display the
2535
+ * exact spot.
2275
2536
  */
2276
- declare const discoverShapes: (project: Project, lunoraDirectory: string) => ShapeIR[];
2537
+ declare class CodegenDiagnosticError extends LunoraError {
2538
+ readonly column: number;
2539
+ readonly file: string;
2540
+ readonly line: number;
2541
+ constructor(message: string, file: string, line: number, column: number);
2542
+ }
2277
2543
  /**
2278
- * Aggregate the schema-wide storage-rule metadata the studio's inspector reads:
2279
- * every statically-discovered `(bucket, on, prefix, procedure)` entry across all
2280
- * `.use(storageRules(...))` chains. Only the builder form can declare rules, so
2281
- * bare-factory procedures contribute nothing.
2544
+ * Build a {@link CodegenDiagnosticError} whose message includes the source
2545
+ * location and whose `file`/`line`/`column` properties are set from the
2546
+ * ts-morph `Node`'s position in its source file.
2547
+ *
2548
+ * Message format: `@lunora/codegen: &lt;detail> (&lt;file>:&lt;line>:&lt;column>)`
2549
+ *
2550
+ * `meta` is merged onto the returned error for callers that also carry the
2551
+ * project-wide `LunoraError` envelope (`code`/`name`/`status`) — it never
2552
+ * touches `file`/`line`/`column`, and the error stays an instance of
2553
+ * {@link CodegenDiagnosticError} so the Vite overlay's `instanceof` location
2554
+ * lookup is unaffected.
2282
2555
  */
2283
- declare const discoverStorageRulesMetadata: (project: Project, lunoraDirectory: string) => StorageRulesMetadataIR;
2284
- /** The only file workflows may be declared in — mirrors `lunora/containers.ts`. */
2285
- declare const WORKFLOWS_FILENAME = "workflows.ts";
2556
+ declare const diagnosticAt: (node: Node, detail: string, meta?: Record<string, unknown>) => CodegenDiagnosticError;
2557
+ /** The only file agents may be declared in — mirrors `lunora/workflows.ts`. */
2558
+ declare const AGENTS_FILENAME = "agents.ts";
2286
2559
  /**
2287
- * Discover every workflow the project declares: exported `defineWorkflow()`
2288
- * calls in `lunora/workflows.ts`. Returns `[]` when the file doesn't exist. The
2289
- * only wrangler-relevant literal is the optional `name` override; the workflow
2290
- * body is runtime-only, so codegen never evaluates it.
2560
+ * Discover every agent the project declares: exported `defineAgent()` calls in
2561
+ * `lunora/agents.ts`. Returns `[]` when the file doesn't exist. Only four things
2562
+ * are read statically the optional `name` override (wrangler `workflows[].name`),
2563
+ * the optional `publicRun` opt-in (the `agents:agentRun` capability gate), the
2564
+ * presence of a `voice` block (which turns on the voice-session Durable Object),
2565
+ * and the presence of an `onEmail` mapper (which wires the worker `email()`
2566
+ * handler); the rest of the agent config (model / tools / memory / voice models /
2567
+ * the `onEmail` closure body) is runtime-only, so codegen never evaluates it.
2291
2568
  */
2292
- declare const discoverWorkflows: (project: Project, lunoraDirectory: string) => WorkflowIR[];
2293
- declare const GENERATED_HEADER = "// GENERATED by @lunora/codegen — do not edit.\n// Run `lunora codegen` to regenerate.\n\n";
2294
- /** Emit `_generated/dataModel.ts` — `Doc&lt;"name">` + `Id&lt;"name">` for every table. */
2295
- declare const emitDataModel: (schema: SchemaIR, useUmbrella?: boolean) => string;
2569
+ declare const discoverAgents: (project: Project, lunoraDirectory: string) => AgentIR[];
2296
2570
  /**
2297
- * Emit `_generated/api.ts` the typed `api.*` registry (public functions), the
2298
- * `internal.*` registry, and (when the project declares them) the typed
2299
- * `workflows.*` / `agents.*` scheduler-target reference objects. `api`/`internal` are the same `anyApi` proxy
2300
- * at runtime (the `__lunoraRef` is identical); visibility is enforced
2301
- * server-side at dispatch, not in the reference. Splitting the *types* keeps
2302
- * internal functions off the client-facing `api` surface.
2571
+ * Discover `ctx.authApi.&lt;method>(...)` (and bare `authApi.&lt;method>(...)`) calls
2572
+ * under the lunora source directory and attribute each to the exported function
2573
+ * (and file) performing it. Calls outside an exported declaration are dropped.
2303
2574
  */
2304
- interface EmitApiOptions {
2305
- agents?: ReadonlyArray<AgentIR>;
2306
- functions: ReadonlyArray<FunctionIR>;
2307
- /** Typed REST routes; only `.stream()` (SSE) routes emit a `httpStreams.*` reference. */
2308
- httpRoutes?: ReadonlyArray<HttpRouteIR>;
2309
- /** Custom mutators (`lunora/mutators.ts`) — emitted as `api.mutators.*` so a client `serverRef` is compile-checked. */
2310
- mutators?: ReadonlyArray<MutatorIR>;
2311
- useUmbrella?: boolean;
2312
- workflows?: ReadonlyArray<WorkflowIR>;
2313
- }
2314
- declare const emitApi: (options: EmitApiOptions) => string;
2575
+ declare const discoverAuthApiCalls: (project: Project, lunoraDirectory: string) => AuthApiCallIR[];
2576
+ /** The only file containers may be declared in — mirrors `lunora/crons.ts`. */
2577
+ declare const CONTAINERS_FILENAME = "containers.ts";
2315
2578
  /**
2316
- * Emit `_generated/collections.ts` a typed TanStack DB binding per `defineShape`
2317
- * in `lunora/shapes.ts` (the local-first partial-replication surface).
2318
- *
2319
- * Each shape emits **two** entry points.
2320
- *
2321
- * `&lt;shape>CollectionOptions(options)` is the composable form: it returns the full
2322
- * `LunoraCollectionOptions` — `config` for `createCollection`, plus `checkpoints`
2323
- * (which `bindMutators` gates optimistic overlays on) and `scope`. This is what an app
2324
- * with custom mutators needs, and what the old single-factory form made impossible: it
2325
- * built the collection internally and dropped `checkpoints` on the floor, so there was
2326
- * no way to wire mutators to the collection codegen produced.
2327
- *
2328
- * `&lt;shape>Collection(options)` is the convenience form for a read-only collection: it
2329
- * returns `{ checkpoints, collection, scope }` rather than a bare `Collection`, so the
2330
- * sync controls stay reachable even from the short path.
2331
- *
2332
- * Both are typed: `args` comes from the shape's own validators (a parameterless
2333
- * shape takes none), rows resolve to `Doc&lt;"table">` when the shape names its table
2334
- * with a literal, and `shardKey` / `getKey` / `load` / `onError` / `checkpoints` are
2335
- * all threadable — a sharded table needs `shardKey` for its watermark to land in the
2336
- * right bucket, and a server-minted `_id` that differs from the app's natural key
2337
- * needs `getKey`.
2338
- *
2339
- * Returns `""` (so `writeIfPresent` skips the file) unless the project both
2340
- * declares shapes AND installs `@lunora/db` — the add-on that ships
2341
- * `lunoraCollectionOptions`. `@lunora/db` stays a scoped install even under the
2342
- * `lunorash` umbrella (an opt-in add-on, like `@lunora/auth`), so its import is
2343
- * always `@lunora/db/collections`; only the in-umbrella `@lunora/client` import
2344
- * is remapped to `lunorash/client`.
2579
+ * Discover every container the project declares: exported `defineContainer()`
2580
+ * calls in `lunora/containers.ts`. Returns `[]` when the file doesn't exist.
2581
+ * Wrangler-relevant fields (`image`, `instanceType`, `maxInstances`, `name`)
2582
+ * must be static literals; runtime-only fields (`env`, `sleepAfter`, …) may be
2583
+ * any expression since the generated class imports the definition object.
2345
2584
  */
2346
- declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
2347
- interface EmitServerOptions {
2348
- /** Agents declared via `defineAgent` exports — wires the typed `ctx.agents` producers onto Mutation/Action contexts. */
2349
- agents?: ReadonlyArray<AgentIR>;
2350
- containers?: ReadonlyArray<ContainerIR>;
2351
- /**
2352
- * The single `defineEnv(...)` contract declared in `lunora/env.ts`. When
2353
- * present, `ctx.env` is typed as the validated `InferEnv` shape (recovered
2354
- * via `ReturnType` over the accessor's `typeof`). `undefined` leaves `ctx.env`
2355
- * the base optional binding record — byte-identical to today.
2356
- */
2357
- env?: EnvIR;
2358
- /**
2359
- * A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access
2360
- * facade (`@lunora/cloudflare-access/context`) onto every ctx. Distinct from
2361
- * `emitApp`'s `hasAccess` (which gates the worker's `.access()` resolveIdentity
2362
- * method); this one gates the per-request `ctx.access` read surface.
2363
- */
2364
- hasAccessFacade?: boolean;
2365
- hasAi?: boolean;
2366
- /** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
2367
- hasAnalytics?: boolean;
2368
- /** A `lunora/` source uses `@lunora/browser` / `ctx.browser` — wires `ctx.browser` onto ActionCtx only. */
2369
- hasBrowser?: boolean;
2370
- /** The project declares `lunora/flags.ts` — wires `ctx.flags` (OpenFeature) onto every ctx. */
2371
- hasFlags?: boolean;
2372
- /** A `lunora/` source uses `@lunora/hyperdrive` / `ctx.sql` — wires `ctx.sql` onto ActionCtx only. */
2373
- hasHyperdrive?: boolean;
2374
- /** A `lunora/` source uses `@lunora/bindings/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
2375
- hasImages?: boolean;
2376
- /** A `lunora/` source uses `@lunora/bindings/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
2377
- hasKv?: boolean;
2378
- /** The project declares `lunora/notify.ts` — wires `ctx.notify` + its `ctx.push` alias (`@lunora/notify`) onto every ctx. */
2379
- hasNotify?: boolean;
2380
- hasPayments?: boolean;
2381
- /** A `lunora/` source uses `@lunora/bindings/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
2382
- hasPipelines?: boolean;
2383
- /** A `lunora/` source uses `@lunora/bindings/r2sql` / `ctx.r2sql` — wires `ctx.r2sql` onto ActionCtx only. */
2384
- hasR2sql?: boolean;
2385
- /** A `lunora/` source uses `@lunora/x402/pay` / `ctx.x402` — wires the agent-wallet pay rail onto ActionCtx only. */
2386
- hasX402?: boolean;
2387
- /**
2388
- * The single `defineIdentity(...)` claim contract declared in
2389
- * `lunora/identity.ts` (Plan 080). When present, `ctx.auth.getIdentity()`,
2390
- * the RLS policy `ctx.auth.identity`, and the shard-authorization hooks
2391
- * narrow to the declared shape (recovered via `InferIdentity` over the
2392
- * contract's `typeof`). `undefined` keeps the identity an untyped bag —
2393
- * byte-identical to today.
2394
- */
2395
- identity?: IdentityIR;
2396
- /** Queues declared via `defineQueue` exports — wires the typed `ctx.queues` producers onto Mutation/Action contexts. */
2397
- queues?: ReadonlyArray<QueueIR>;
2398
- schema?: SchemaIR;
2399
- storageRuleBuckets?: ReadonlyArray<string>;
2400
- /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
2401
- useUmbrella?: boolean;
2402
- workflows?: ReadonlyArray<WorkflowIR>;
2403
- }
2404
- declare const emitServer: ({ agents, containers, env, hasAccessFacade, hasAi, hasAnalytics, hasBrowser, hasFlags, hasHyperdrive, hasImages, hasKv, hasNotify, hasPayments, hasPipelines, hasR2sql, hasX402, identity, queues, schema, storageRuleBuckets, useUmbrella, workflows }?: EmitServerOptions) => string;
2405
- interface EmitFunctionsOptions {
2406
- agents?: ReadonlyArray<AgentIR>;
2407
- functions: ReadonlyArray<FunctionIR>;
2408
- migrations?: ReadonlyArray<MigrationIR>;
2409
- mutators?: ReadonlyArray<MutatorIR>;
2410
- shapes?: ReadonlyArray<ShapeIR>;
2411
- /** Import of a sandbox tool (`browserTool`/`containerTool`) auto-registers the `sandbox:invoke` action. */
2412
- usesSandbox?: boolean;
2413
- useUmbrella?: boolean;
2414
- }
2415
- declare const emitFunctions: (options: EmitFunctionsOptions) => string;
2585
+ declare const discoverContainers: (project: Project, lunoraDirectory: string) => ContainerIR[];
2416
2586
  /**
2417
- * Emit `_generated/containers.ts` one container-enabled Durable Object class
2418
- * per `defineContainer` export, each a thin subclass of `LunoraContainer`
2419
- * (`@lunora/container/do`) constructed with the user's definition object. The
2420
- * worker entry must re-export these classes: wrangler requires every
2421
- * `containers[].class_name` to be exported by the deployed worker. Returns ""
2422
- * when the project declares no containers (the file is not written then).
2587
+ * Scan every `.ts` file under `lunoraDir` for `cronJobs()` builder registrations
2588
+ * (`crons.interval(...)`, `crons.daily(...)`, `crons.cron(...)`, …) and lift them
2589
+ * into {@link CronJobIR}. Schedules are compiled to standard cron expressions;
2590
+ * function references are resolved to their `namespace:fn` dispatch path, while a
2591
+ * `workflows.NAME` / `agents.NAME` reference (or a bare identifier naming a
2592
+ * declared workflow) resolves to a durable workflow start. Names must be unique
2593
+ * across the project.
2423
2594
  */
2424
- declare const emitContainers: (containers: ReadonlyArray<ContainerIR>, jurisdiction?: JurisdictionIR) => string;
2595
+ declare const discoverCrons: (project: Project, lunoraDirectory: string, workflows?: ReadonlyArray<WorkflowIR>, agents?: ReadonlyArray<AgentIR>) => CronJobIR[];
2596
+ /** The only file a feature-flag provider may be declared in — mirrors `lunora/queues.ts`. */
2597
+ declare const FLAGS_FILENAME = "flags.ts";
2425
2598
  /**
2426
- * Emit `_generated/workflows.ts` one `WorkflowEntrypoint` class per
2427
- * `defineWorkflow` export, each a thin subclass of `LunoraWorkflow`
2428
- * (`@lunora/workflow/do`) constructed with the user's definition object. The
2429
- * worker entry must re-export these classes: wrangler requires every
2430
- * `workflows[].class_name` to be exported by the deployed worker. Returns ""
2431
- * when the project declares no workflows (the file is not written then).
2599
+ * Discover the feature-flag provider a project declares in `lunora/flags.ts`.
2600
+ * Returns `undefined` when the file doesn't exist (the app has no flags). The
2601
+ * read is metadata-only and lenient: codegen wires `ctx.flags` purely from the
2602
+ * file's *existence* (`run-codegen.ts`) and imports the real module for the
2603
+ * provider value this IR exists solely so the config layer can reconcile the
2604
+ * wrangler `flagship` binding for the Flagship binding-mode provider. Anything
2605
+ * it can't read statically degrades to a `custom` provider (no binding), never
2606
+ * a thrown error.
2432
2607
  */
2433
- declare const emitWorkflows: (workflows: ReadonlyArray<WorkflowIR>) => string;
2608
+ declare const discoverFlags: (project: Project, lunoraDirectory: string) => FlagsIR | undefined;
2434
2609
  /**
2435
- * Emit `_generated/agents.ts` one `WorkflowEntrypoint` class per `defineAgent`
2436
- * export, each a thin subclass of `LunoraWorkflow` (`@lunora/workflow/do`)
2437
- * constructed with the compiled agent tool-loop (`compileAgentWorkflow`). Like
2438
- * `_generated/workflows.ts`, the worker entry must re-export these classes:
2439
- * wrangler requires every `workflows[].class_name` to be exported by the
2440
- * deployed worker. Returns "" when the project declares no agents (the file is
2441
- * not written then).
2610
+ * Scan all .ts files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
2611
+ * for top-level `export const x = query/mutation/action({...})` registrations.
2442
2612
  */
2443
- declare const emitAgents: (agents: ReadonlyArray<AgentIR>) => string;
2444
- interface EmitShardOptions {
2445
- advisories?: ReadonlyArray<Finding>;
2446
- /** Every declared procedure — the health map's denominator, served via `getAdvisorProcedures`. */
2447
- advisorProcedures?: ReadonlyArray<AdvisorProcedureProtection>;
2448
- /** Agents declared via `defineAgent` exports in `lunora/agents.ts` — wires the typed `ctx.agents` producers. */
2449
- agents?: ReadonlyArray<AgentIR>;
2450
- containers?: ReadonlyArray<ContainerIR>;
2451
- /** The single `defineEnv(...)` contract declared in `lunora/env.ts` — applies the accessor to the worker `env` to populate `ctx.env`. */
2452
- env?: EnvIR;
2453
- /** Statically-discovered `ctx.flags.&lt;type>("key")` reads — the studio Flags page + reactive evaluation iterate these. */
2454
- flagKeys?: ReadonlyArray<{
2455
- key: string;
2456
- type: "boolean" | "number" | "object" | "string";
2457
- }>;
2458
- /** A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access facade onto every ctx. */
2459
- hasAccessFacade?: boolean;
2460
- hasAi?: boolean;
2461
- /** A `lunora/` source reads `ctx.analytics` — wires the Analytics Engine write helper onto every ctx. */
2462
- hasAnalytics?: boolean;
2463
- /** A `lunora/` source reads `ctx.browser` — wires `ctx.browser` onto the ActionCtx only. */
2464
- hasBrowser?: boolean;
2465
- /** The project declares `lunora/flags.ts` — wires `ctx.flags` (OpenFeature) onto every ctx. */
2466
- hasFlags?: boolean;
2467
- /** A `lunora/` source reads `ctx.sql` (Hyperdrive) — wires `ctx.sql` onto the ActionCtx only. */
2468
- hasHyperdrive?: boolean;
2469
- /** A `lunora/` source reads `ctx.images` — wires `ctx.images` onto the ActionCtx only. */
2470
- hasImages?: boolean;
2471
- /** A `lunora/` source reads `ctx.kv` — wires `ctx.kv` onto every ctx. */
2472
- hasKv?: boolean;
2473
- /** The project declares `lunora/notify.ts` — wires `ctx.notify` + its `ctx.push` alias (`@lunora/notify`) onto every ctx. */
2474
- hasNotify?: boolean;
2475
- hasPayments?: boolean;
2476
- /** A `lunora/` source reads `ctx.pipelines` — wires `ctx.pipelines` onto the ActionCtx only. */
2477
- hasPipelines?: boolean;
2478
- /** A `lunora/` source reads `ctx.r2sql` (R2 SQL) — wires `ctx.r2sql` onto the ActionCtx only. */
2479
- hasR2sql?: boolean;
2480
- /** A `lunora/` source reads `ctx.x402` — wires the agent-wallet pay rail onto the ActionCtx only. */
2481
- hasX402?: boolean;
2482
- maskMetadata?: MaskMetadataIR;
2483
- /** Custom mutators declared via `defineMutator` in `lunora/mutators.ts` — wires the `isCustomMutator` push-protocol override. */
2484
- mutators?: ReadonlyArray<MutatorIR>;
2485
- /** Queues declared via `defineQueue` exports in `lunora/queues.ts` — wires the typed `ctx.queues` producers. */
2486
- queues?: ReadonlyArray<QueueIR>;
2487
- rlsMetadata?: RlsMetadataIR;
2488
- schema: SchemaIR;
2489
- /**
2490
- * The structural snapshot the pre-deploy drift gate diffs against, threaded
2491
- * in so the emitted DO records it in `__lunora_schema_history` on cold start
2492
- * (plan 200 — the Studio's schema-version timeline). Optional so an emitter
2493
- * caller that has no snapshot (tests, fixtures) emits the pre-ledger shape
2494
- * unchanged.
2495
- */
2496
- schemaSnapshot?: SchemaSnapshot;
2497
- /** Replication shapes declared via `defineShape` in `lunora/shapes.ts` — wires the `resolveShape` subscription override. */
2498
- shapes?: ReadonlyArray<ShapeIR>;
2499
- storageRules?: StorageRulesMetadataIR;
2500
- studioFeatures?: StudioFeaturesResult;
2501
- /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
2502
- useUmbrella?: boolean;
2503
- workflows?: ReadonlyArray<WorkflowIR>;
2504
- }
2505
- declare const emitShard: ({ advisories, advisorProcedures, agents, containers, env, flagKeys, hasAccessFacade, hasAi, hasAnalytics, hasBrowser, hasFlags, hasHyperdrive, hasImages, hasKv, hasNotify, hasPayments, hasPipelines, hasR2sql, hasX402, maskMetadata, mutators, queues, rlsMetadata, schema, schemaSnapshot, shapes, storageRules, studioFeatures, useUmbrella, workflows }: EmitShardOptions) => string;
2613
+ declare const discoverFunctions: (project: Project, lunoraDirectory: string) => FunctionIR[];
2506
2614
  /**
2507
- * Emit drizzle `sqliteTable` definitions for the project schema, split into
2508
- * `global` (D1-backed) and `shard` (DO-SQLite-backed) buckets. Tables marked
2509
- * `.global()` go in the global file; everything else (default root + `.shardBy()`)
2510
- * goes in the shard file.
2511
- *
2512
- * `searchIndexes` are intentionally not emitted — drizzle has no `sqliteTable`
2513
- * abstraction for FTS5 virtual tables. FTS plumbing is handled by the runtime
2514
- * outside of drizzle.
2615
+ * Scan all `.ts` files under `lunoraDir` (skipping `_generated/` and `schema.ts`)
2616
+ * for `export const x = httpRoute.&lt;verb>(...)…handler(...)` typed REST routes.
2617
+ * These are the headline OpenAPI target: each becomes a real `paths` entry.
2515
2618
  */
2516
- declare const emitDrizzleSchema: (schema: SchemaIR, useUmbrella?: boolean) => {
2517
- global: string;
2518
- shard: string;
2519
- };
2619
+ declare const discoverHttpRoutes: (project: Project, lunoraDirectory: string) => HttpRouteIR[];
2520
2620
  /**
2521
- * Emit `_generated/crons.ts` from the discovered cron jobs.
2522
- *
2523
- * `LUNORA_CRON_TRIGGERS` is the deduplicated schedule array what lands in
2524
- * wrangler's `triggers.crons` (the vite plugin reconciles it into
2525
- * `wrangler.jsonc`, and {@link emitWranglerCronTriggers} renders the same list
2526
- * for the CLI / docs).
2527
- *
2528
- * `LUNORA_CRONS` is the dispatcher map keyed by cron expression, each value a
2529
- * list of `{ name, functionPath, args }`. Cloudflare's `scheduled()` handler
2530
- * receives only the cron string, so multiple jobs sharing one expression must
2531
- * all fire — hence a list per key rather than a single entry.
2532
- *
2533
- * Jobs arrive pre-sorted by name (deterministic output); the trigger array
2534
- * preserves first-seen order of distinct expressions.
2535
- *
2536
- * Cloudflare caps a Worker at **3 Cron Triggers** (i.e. 3 distinct cron
2537
- * expressions). Because the dispatcher fires every job sharing an expression,
2538
- * many jobs can ride a single trigger — only the count of *distinct* schedules
2539
- * matters. `lunora codegen` warns when that count exceeds the limit; for
2540
- * finer-grained scheduling use Durable Object alarms (`@lunora/scheduler`),
2541
- * which have no such cap.
2621
+ * Discover `ctx.db.insert("table", …)` writes under the lunora source directory
2622
+ * and attribute each to the exported function (and file) performing it. Calls
2623
+ * with a non-literal table argument, or outside an exported declaration, are
2624
+ * dropped (`table === ""` / no enclosing export).
2542
2625
  */
2543
- declare const emitCrons: (crons: ReadonlyArray<CronJobIR>) => string;
2626
+ declare const discoverInserts: (project: Project, lunoraDirectory: string) => InsertWriteIR[];
2544
2627
  /**
2545
- * Render `_generated/vectors.ts` the static registry of every vector index
2546
- * declared in `schema.ts` (inline `.vectorize()` columns + standalone
2547
- * `defineVectorIndex()` definitions).
2548
- *
2549
- * Cloudflare Vectorize exposes no way to enumerate an account's indexes at
2550
- * runtime — a binding can `describe()` itself but the worker can't ask "which
2551
- * indexes exist". So this generated array is the source of truth the studio's
2552
- * vector browser lists, and the worker's admin route pairs each entry with its
2553
- * live `describe()` stats. Entries are sorted by name for deterministic output.
2628
+ * Discover masking usage for every exported Lunora procedure under the lunora
2629
+ * source directory the column-level twin of `discoverRlsProcedures`. For each
2630
+ * procedure, records whether its builder chain includes `.use(mask(...))`, which
2631
+ * `(table, column)` pairs that mask declares, and which tables it reads/writes
2632
+ * through `ctx.db`. Feeds the `mask_uncovered_pii_column` advisor lint.
2554
2633
  */
2555
- declare const emitVectors: (vectorIndexes: ReadonlyArray<VectorIndexIR>) => string;
2634
+ declare const discoverMaskProcedures: (project: Project, lunoraDirectory: string) => MaskProcedureIR[];
2556
2635
  /**
2557
- * Render the deduplicated cron schedules as a JSON fragment suitable for
2558
- * splicing into `wrangler.jsonc`'s `triggers.crons`. The vite plugin uses this
2559
- * (plus the parsed wrangler config) to reconcile generated triggers without the
2560
- * user hand-editing the file.
2636
+ * Scan all `.ts` files under `lunoraDir` for top-level
2637
+ * `export const x = defineMigration({...})` declarations and lift them into
2638
+ * {@link MigrationIR}. `id` must be a static string literal (it's the registry
2639
+ * key); `table` is best-effort and left `""` when not a literal.
2561
2640
  */
2562
- declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => string[];
2563
- /** Which capability methods the generated `defineApp` builder exposesone flag per package-backed feature the app actually uses. */
2564
- interface EmitAppOptions {
2565
- /**
2566
- * Inbound-email agents (`defineAgent({ onEmail })`) → wire the worker's
2567
- * top-level `email()` handler to `dispatchAgentEmail(...)` (from
2568
- * `@lunora/agent/inbound`), so received mail starts a durable run. Empty/absent
2569
- * ⇒ no wiring, byte-identical output for email-free (and agent-free) projects.
2570
- */
2571
- emailAgents?: ReadonlyArray<{
2572
- bindingName: string;
2573
- exportName: string;
2574
- }>;
2575
- /** App depends on `@lunora/cloudflare-access` → emit `.access()` (wire the Cloudflare Access `resolveIdentity`, composed ahead of `@lunora/auth` when both are present). */
2576
- hasAccess: boolean;
2577
- /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
2578
- hasAi: boolean;
2579
- /** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
2580
- hasAnalytics: boolean;
2581
- /** App depends on `@lunora/auth` → emit `.auth()` + the lazy build/migrate dance. */
2582
- hasAuth: boolean;
2583
- /** App uses `@lunora/browser` / `ctx.browser` → emit `.browser()`. */
2584
- hasBrowser: boolean;
2585
- /** App depends on a worker-composition framework adapter (`@lunora/astro`/`@lunora/svelte`/`@lunora/vue`) → emit `.buildFrameworkWorker(host)`. */
2586
- hasFramework: boolean;
2587
- /** Schema declares **D1-backed** `.global()` tables → emit `.global()` (D1 ctx-db + studio introspector + cross-shard relations). */
2588
- hasGlobal: boolean;
2589
- /** App uses `@lunora/hyperdrive` / `ctx.sql` → emit `.hyperdrive()`. */
2590
- hasHyperdrive: boolean;
2591
- /** Schema declares **Hyperdrive-backed** `.global({ backend: "hyperdrive" })` tables → emit `.hyperdriveGlobal()` (reactive Postgres/MySQL ctx-db over Hyperdrive). */
2592
- hasHyperdriveGlobal: boolean;
2593
- /** App uses `@lunora/bindings/images` / `ctx.images` → emit `.images()`. */
2594
- hasImages: boolean;
2595
- /** App uses `@lunora/bindings/kv` / `ctx.kv` → emit `.kv()`. */
2596
- hasKv: boolean;
2597
- /** App declares `lunora/notify.ts` (`@lunora/notify`) → wire `options.notifySubscriptionStore` so the studio Notifications page can read registered devices. */
2598
- hasNotify: boolean;
2599
- /** App uses `@lunora/payment` / `ctx.payments` → emit `.payment()`. */
2600
- hasPayments: boolean;
2601
- /** App declares push queues (`defineQueue`) → wire `LUNORA_QUEUE_REGISTRY` into the worker's `queue()` consumer entry. */
2602
- hasQueue: boolean;
2603
- /** App uses `@lunora/bindings/r2sql` / `ctx.r2sql` → emit `.r2sql()`. */
2604
- hasR2sql: boolean;
2605
- /** App imports `@lunora/scheduler` / declares crons → emit `.scheduler()`. */
2606
- hasScheduler: boolean;
2607
- /** App uses `@lunora/storage` → emit `.storage()` (DO `ctx.storage` + studio file browser). */
2608
- hasStorage: boolean;
2609
- /** Schema declares vector indexes → emit `.vectors()` (the Vectorize index map backing `ctx.vectors`). */
2610
- hasVectors: boolean;
2611
- /** App declares Cloudflare Workflows (`defineWorkflow`) → wire `options.workflowsClient` so the studio's workflow-instance proxy can reach the CF REST API. */
2612
- hasWorkflow: boolean;
2613
- /** App uses `@lunora/x402/pay` / `ctx.x402` → emit `.x402()` (wire the agent-wallet pay rail). */
2614
- hasX402: boolean;
2615
- /** The single `defineIdentity(...)` contract in `lunora/identity.ts` (Plan 080) → import it as a VALUE and wire `options.identity`, so the runtime trust boundary validates every resolved identity before it becomes `ctx.auth`. `undefined` ⇒ no wiring, byte-identical output. */
2616
- identity?: IdentityIR;
2617
- /** Schema declares `.jurisdiction("…")` → pin every DO the worker reaches (shards, fan-out, scheduler, containers) to the Cloudflare data-residency jurisdiction. */
2618
- jurisdiction?: JurisdictionIR;
2619
- /** Project depends on the unscoped `lunorash` umbrella → import the runtime via `lunorash/runtime` instead of `@lunora/runtime`. */
2620
- useUmbrella: boolean;
2621
- /**
2622
- * Voice-enabled agents (`defineAgent({ voice: … })`) → wire
2623
- * `options.voiceAgents`, mapping each agent's export name to its `VOICE_*`
2624
- * Durable Object namespace binding so the runtime exposes
2625
- * `/_lunora/voice/&lt;exportName>`. Empty/absent ⇒ no wiring, byte-identical
2626
- * output for voice-free (and agent-free) projects.
2627
- */
2628
- voiceAgents?: ReadonlyArray<{
2629
- bindingName: string;
2630
- exportName: string;
2631
- }>;
2632
- /** An OpenAPI spec is emitted (`openapi.ts`) → wire `openApiSpec` into the worker. */
2633
- wantsOpenApi: boolean;
2634
- /** An OpenRPC spec is emitted (`openrpc.ts`) → wire `openRpcSpec` into the worker. */
2635
- wantsOpenRpc: boolean;
2636
- }
2641
+ declare const discoverMigrations: (project: Project, lunoraDirectory: string) => MigrationIR[];
2642
+ /** The only file custom mutators may be declared in mirrors `lunora/queues.ts`. */
2643
+ declare const MUTATORS_FILENAME = "mutators.ts";
2637
2644
  /**
2638
- * Emit `_generated/app.ts` a fluent, feature-specialized worker-composition
2639
- * builder. Only the methods for capabilities THIS app uses are emitted, so the
2640
- * builder's type surface (IntelliSense) lists exactly what can be configured.
2641
- *
2642
- * Each capability declaration is fanned into BOTH runtime surfaces: the DO-side
2643
- * `createShardDO(...)` factory that backs `ctx.*`, and the worker-side
2644
- * `createWorker(...)` options that back the studio/admin endpoints — so storage
2645
- * / scheduler / global are declared once instead of twice. The builder is pure
2646
- * sugar over the public `createWorker` / `createShardDO`; both stay usable.
2647
- *
2648
- * Lives in generated code (not `@lunora/runtime`, which is dependency-free) so
2649
- * it can import the add-on packages the app installed (`@lunora/auth`,
2650
- * `@lunora/storage`, …) directly.
2645
+ * Discover every custom mutator the project declares: exported
2646
+ * `defineMutator()` calls in `lunora/mutators.ts`. Returns `[]` when the file
2647
+ * doesn't exist. The export binding plus the declared `args` / `server` return
2648
+ * type are lifted — enough to emit a typed `api.mutators.&lt;name>` reference —
2649
+ * while the runtime object still carries the authoritative `server` impl +
2650
+ * `handler`, so codegen never evaluates the body. The client `client` impl is
2651
+ * split into the browser bundle separately.
2651
2652
  */
2652
- declare const emitApp: (options: EmitAppOptions) => string;
2653
- /** Inputs the OpenAPI emitter needs from a codegen run. */
2654
- interface OpenApiEmitInput {
2655
- functions: ReadonlyArray<FunctionIR>;
2656
- httpRoutes: ReadonlyArray<HttpRouteIR>;
2657
- /** `info.version`; defaults to `"0.0.0"` with a TODO when the project version is unknown. */
2658
- version?: string;
2659
- }
2653
+ declare const discoverMutators: (project: Project, lunoraDirectory: string) => MutatorIR[];
2660
2654
  /**
2661
- * Emit an OpenAPI 3.1.0 document covering both Lunora function surfaces.
2662
- *
2663
- * `httpRouter()` typed REST routes become real `paths` keyed by their method +
2664
- * URL, with query/path parameters and JSON request bodies derived from their
2665
- * `v.*` validators, and a response schema from `.output()` when declared.
2655
+ * Discover non-deterministic API calls (`Date.now`, `new Date()`, `Date()`,
2656
+ * `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues` — including
2657
+ * `globalThis`/`self`/`window`-prefixed receivers and `fetch`) lexically inside
2658
+ * the handler body of every exported `query(...)` / `mutation(...)` registration
2659
+ * under the lunora source directory the `nondeterministic_query_mutation` lint
2660
+ * input. `action(...)` (and `stream(...)`) registrations are intentionally
2661
+ * skipped: actions run exactly once and may use ambient APIs freely.
2666
2662
  *
2667
- * RPC `query`/`mutation`/`action` functions become one operation each on
2668
- * `POST /_lunora/rpc` (disambiguated by a `#functionPath` path fragment), with a
2669
- * requestBody pinning `functionPath` + typed `args`. `internal`/`stream`
2670
- * functions are excluded (unreachable / not invocable on the external RPC path).
2663
+ * Traversal is scoped to the handler node (not the whole declaration), mirroring
2664
+ * how the auth-api / insert feeders attribute calls so a call in a sibling
2665
+ * helper outside the handler, or in a nested `action(...)` passed elsewhere, is
2666
+ * not attributed to the query/mutation. One {@link NondeterministicCallIR} is
2667
+ * produced per call site.
2668
+ */
2669
+ declare const discoverNondeterministicCalls: (project: Project, lunoraDirectory: string) => NondeterministicCallIR[];
2670
+ /** The only file a `@lunora/notify` provider may be declared in — mirrors `lunora/flags.ts`. */
2671
+ declare const NOTIFY_FILENAME = "notify.ts";
2672
+ /**
2673
+ * Discover `ctx.notify` / `ctx.push` sends lexically inside the handler body of
2674
+ * every exported `query(...)` / `mutation(...)` registration under the lunora
2675
+ * source directory — the `notify_send_outside_action` lint input. `action(...)`
2676
+ * (and `stream(...)`) registrations are intentionally skipped: a notification
2677
+ * send is external I/O that belongs in actions. One {@link AdvisorNotifyCall} is
2678
+ * produced per send site.
2679
+ */
2680
+ declare const discoverNotifyCalls: (project: Project, lunoraDirectory: string) => AdvisorNotifyCall[];
2681
+ /**
2682
+ * Discover which push channels the project's `lunora/notify.ts` default export
2683
+ * (`defineNotify({...})`) wires plus whether any handler sends a push — the
2684
+ * `notify_missing_push_config` lint input. Returns `undefined` when the file is
2685
+ * absent (the app declares no notify config). The read is metadata-only and
2686
+ * lenient (like `discoverFlags`): a `webPush`/`fcm` property's mere presence
2687
+ * counts as the channel being wired; a non-literal config degrades to "unwired"
2688
+ * rather than throwing.
2689
+ */
2690
+ declare const discoverNotifyConfig: (project: Project, lunoraDirectory: string) => AdvisorNotifyConfig | undefined;
2691
+ /**
2692
+ * Every package name the project declares a dependency on, read from the
2693
+ * `package.json` at the project root across all four dependency fields
2694
+ * (`dependencies`, `devDependencies`, `peerDependencies`,
2695
+ * `optionalDependencies`).
2671
2696
  *
2672
- * Operations are grouped into `tags` by file namespace, and every operation
2673
- * references a reusable `LunoraError` error-response component enumerating the
2674
- * standard error codes. Borrows oRPC's per-procedure-operation + tag-grouping +
2675
- * internal-filtering structure; the JSON Schema dialect matches `@lunora/values`
2676
- * (Draft 2020-12). Returns the document as a plain object (the single source of
2677
- * truth `emitOpenApi` stringifies and `emitOpenApiModule` inlines, so the
2678
- * `.json` and `.ts` artifacts can never drift).
2697
+ * Returns `undefined` NOT an empty set when the manifest is absent or
2698
+ * unparseable, which is the whole reason this exists. Studio nav gating can
2699
+ * treat "declares nothing" and "cannot tell" the same way, but a check that
2700
+ * errors on a missing package cannot: conflating them makes it fire on every
2701
+ * project without a root `package.json` (the codegen fixtures, an embedded
2702
+ * schema, a tool driving `runCodegen` directly).
2679
2703
  */
2680
- declare const buildOpenApiDocument: (input: OpenApiEmitInput) => Record<string, unknown>;
2704
+ declare const readPackageDependencies: (projectRoot: string) => Set<string> | undefined;
2681
2705
  /**
2682
- * Emit the OpenAPI 3.1 document as a pretty-printed JSON string
2683
- * (`_generated/openapi.json`) the portable artifact for external tooling.
2706
+ * Discover `ctx.db.query("table")…` reads under the lunora source directory and
2707
+ * reduce each to a {@link QueryReadIR}. Only reads that call `.filter()` are
2708
+ * returned — an unfiltered read is never a `filter_without_index` candidate, so
2709
+ * dropping the rest keeps the lint input small.
2684
2710
  */
2685
- declare const emitOpenApi: (input: OpenApiEmitInput) => string;
2711
+ declare const discoverQueries: (project: Project, lunoraDirectory: string) => QueryReadIR[];
2712
+ /** The only file queues may be declared in — mirrors `lunora/workflows.ts`. */
2713
+ declare const QUEUES_FILENAME = "queues.ts";
2686
2714
  /**
2687
- * Emit the OpenAPI document as an importable TS module
2688
- * (`_generated/openapi.ts`) the worker entry imports and passes to
2689
- * `createWorker({ openApiSpec })`. The document object literal is inlined
2690
- * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
2691
- * `.json` are byte-identical content and regenerate together — closing the gap
2692
- * where a Worker cannot read the JSON file at runtime. `document_` is the object
2693
- * returned by {@link buildOpenApiDocument} (reused, never recomputed).
2715
+ * Discover every queue the project declares: exported `defineQueue()` calls in
2716
+ * `lunora/queues.ts`. Returns `[]` when the file doesn't exist. Only the
2717
+ * wrangler-relevant literals (`name`/`mode`/batch tuning) are read; the handler
2718
+ * body is runtime-only, so codegen never evaluates it.
2694
2719
  */
2695
- declare const emitOpenApiModule: (document_: Record<string, unknown>) => string;
2696
- /** The OpenRPC dialect version this emitter targets. */
2697
- declare const OPENRPC_VERSION = "1.3.2";
2698
- /** Inputs the OpenRPC emitter needs from a codegen run. */
2699
- interface OpenRpcEmitInput {
2700
- functions: ReadonlyArray<FunctionIR>;
2701
- /** `info.version`; defaults to `"0.0.0"` with a TODO when the project version is unknown. */
2702
- version?: string;
2703
- }
2720
+ declare const discoverQueues: (project: Project, lunoraDirectory: string) => QueueIR[];
2704
2721
  /**
2705
- * Emit an OpenRPC 1.x document describing Lunora's JSON-RPC surface.
2722
+ * Discover `ctx.r2sql` accesses lexically inside the handler body of every
2723
+ * exported `query(...)` / `mutation(...)` registration under the lunora source
2724
+ * directory — the `r2sql_outside_action` lint input. `action(...)` (and
2725
+ * `stream(...)`) registrations are intentionally skipped: R2 SQL is the
2726
+ * external, non-reactive surface that belongs in actions.
2706
2727
  *
2707
- * Only the RPC `query`/`mutation`/`action` functions become `methods` one per
2708
- * function, `name` = `file:fn`. `internal` (off the external RPC path) and
2709
- * `stream` (not invocable over the RPC envelope) are excluded, the same filter
2710
- * the OpenAPI emitter applies. Each method's single `args` param is typed from
2711
- * the function's `v.*` validators (`argsObjectSchema`); `result` is the
2712
- * `.output()` schema when declared, else a best-effort inferred schema. The
2713
- * standard `LunoraError` codes ride along under each method's `errors`.
2728
+ * Traversal is scoped to the handler node (not the whole declaration), mirroring
2729
+ * `discoverNondeterministicCalls` so a `ctx.r2sql` touch in a sibling helper
2730
+ * outside the handler is not attributed to the query/mutation. One
2731
+ * {@link R2sqlCallIR} is produced per access site.
2732
+ */
2733
+ declare const discoverR2sqlCalls: (project: Project, lunoraDirectory: string) => R2sqlCallIR[];
2734
+ declare const discoverRlsProcedures: (project: Project, lunoraDirectory: string) => RlsProcedureIR[];
2735
+ /**
2736
+ * Aggregate the schema-wide RLS metadata the studio's read-only inspector reads:
2737
+ * every statically-discovered `(table, on, procedure)` policy entry plus every
2738
+ * role declared via `rls(policies, { roles })`. Walks the same builder chains as
2739
+ * {@link discoverRlsProcedures} but extracts the richer `{ on }` operation +
2740
+ * role/permission shape rather than the lint's table-name set.
2714
2741
  *
2715
- * `httpRouter()` typed REST routes are deliberately omitted — OpenRPC is
2716
- * RPC-only and cannot represent REST paths; the OpenAPI document is the spec
2717
- * that covers the REST surface. Methods are sorted by name for stable output.
2718
- * Returns the document as a plain object (the single source of truth
2719
- * `emitOpenRpc` stringifies and `emitOpenRpcModule` inlines, so the `.json` and
2720
- * `.ts` artifacts can never drift).
2742
+ * Only the **builder** form (`c.use(rls(...)).query(...)`) can declare policies,
2743
+ * so bare-factory procedures contribute nothing. The `when` predicate is never
2744
+ * read it's an opaque JS closure whose logic belongs in code, not the UI.
2745
+ * Roles are deduped by name (first declaration wins) so a role registered on
2746
+ * several procedures lists once.
2721
2747
  */
2722
- declare const buildOpenRpcDocument: (input: OpenRpcEmitInput) => Record<string, unknown>;
2748
+ declare const discoverRlsMetadata: (project: Project, lunoraDirectory: string) => RlsMetadataIR;
2723
2749
  /**
2724
- * Emit the OpenRPC 1.x document as a pretty-printed JSON string
2725
- * (`_generated/openrpc.json`) the portable artifact for external tooling.
2750
+ * Which sandbox tools a project imports from `@lunora/agent` (main entry or the
2751
+ * `/sandbox` subpath), detected by NAMED value import. Drives two things:
2752
+ * registering the `sandbox:invoke` dispatcher (either tool) and provisioning the
2753
+ * `BROWSER` wrangler binding (`browserTool` — the browser op runs on
2754
+ * `ctx.browser` inside the dispatcher).
2726
2755
  */
2727
- declare const emitOpenRpc: (input: OpenRpcEmitInput) => string;
2756
+ interface SandboxUsage {
2757
+ /** `import { browserTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
2758
+ usesSandboxBrowser: boolean;
2759
+ /** `import { containerTool } from "@lunora/agent"` (or `/sandbox`) appears in `lunora/`. */
2760
+ usesSandboxContainer: boolean;
2761
+ }
2762
+ declare const discoverSandboxUsage: (project: Project, lunoraDirectory: string) => SandboxUsage;
2728
2763
  /**
2729
- * Emit the OpenRPC document as an importable TS module
2730
- * (`_generated/openrpc.ts`) the worker entry imports and passes to
2731
- * `createWorker({ openRpcSpec })`. The document object literal is inlined
2732
- * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
2733
- * `.json` are byte-identical content and regenerate together. `document_` is
2734
- * the object returned by {@link buildOpenRpcDocument} (reused, never recomputed).
2764
+ * Load `&lt;projectRoot>/lunora/schema.ts`, find `defineSchema({...})`, and
2765
+ * return a structural IR. Throws if the file or call cannot be found.
2735
2766
  */
2736
- declare const emitOpenRpcModule: (document_: Record<string, unknown>) => string;
2767
+ declare const discoverSchema: (project: Project, schemaPath: string, projectRoot?: string) => SchemaIR;
2768
+ /** The only file shapes may be declared in — mirrors `lunora/queues.ts`. */
2769
+ declare const SHAPES_FILENAME = "shapes.ts";
2737
2770
  /**
2738
- * The canonical capability list. **Order is load-bearing** for the `emit-app.ts`
2739
- * long-tail: the fluent methods are emitted in the order the `appMethod`-bearing
2740
- * rows appear here, so this array is ordered to reproduce the original
2741
- * `LONG_TAIL` sequence (ai, analytics, browser, hyperdrive, images, kv, payment,
2742
- * r2sql, vectors). The `serverCtxField` rows are referenced by name in the ctx
2743
- * interface templates, so their order here is not output-affecting.
2771
+ * Discover every replication shape the project declares: exported
2772
+ * `defineShape()` calls in `lunora/shapes.ts`. Returns `[]` when the file
2773
+ * doesn't exist. Only the export binding is lifted the runtime object carries
2774
+ * the authoritative `table`/`columns`/`compileWhere`, so codegen never
2775
+ * evaluates the predicate.
2744
2776
  */
2745
- declare const CAPABILITY_ROWS: readonly [{
2746
- readonly contextProperty: "access";
2747
- readonly key: "access";
2748
- readonly moduleSpecifier: "@lunora/cloudflare-access";
2749
- }, {
2750
- readonly appMethod: {
2751
- readonly configKey: "ai";
2752
- readonly doc: "Override the Workers AI binding backing `ctx.ai` (defaults to `env.AI`).";
2753
- readonly method: "ai";
2754
- };
2755
- readonly contextProperty: "ai";
2756
- readonly key: "ai";
2757
- readonly moduleSpecifier: "@lunora/ai";
2758
- }, {
2759
- readonly appMethod: {
2760
- readonly configKey: "analytics";
2761
- readonly doc: "Override the Analytics Engine dataset backing `ctx.analytics` (defaults to `env.ANALYTICS`).";
2762
- readonly method: "analytics";
2763
- };
2764
- readonly contextProperty: "analytics";
2765
- readonly key: "analytics";
2766
- readonly moduleSpecifier: "@lunora/bindings/analytics";
2767
- readonly serverCtxField: {
2768
- readonly field: "\n /** Analytics Engine telemetry sink. Fire-and-forget and sampled; do not read it back in-handler. */\n readonly analytics: import(\"@lunora/bindings/analytics\").AnalyticsClient;";
2769
- readonly tier: "every";
2770
- };
2771
- }, {
2772
- readonly appMethod: {
2773
- readonly configKey: "browser";
2774
- readonly doc: "Override the Browser Rendering binding backing `ctx.browser` (defaults to `env.BROWSER`).";
2775
- readonly method: "browser";
2776
- };
2777
- readonly contextProperty: "browser";
2778
- readonly key: "browser";
2779
- readonly moduleSpecifier: "@lunora/browser";
2780
- readonly serverCtxField: {
2781
- readonly field: "\n /** Browser Rendering (screenshots/PDF/scrape). Non-deterministic — available only in actions. */\n readonly browser: import(\"@lunora/browser\").Browser;";
2782
- readonly tier: "action";
2783
- };
2784
- }, {
2785
- readonly contextProperty: "containers";
2786
- readonly key: "container";
2787
- readonly moduleSpecifier: "@lunora/container";
2788
- }, {
2789
- readonly contextProperty: "flags";
2790
- readonly key: "flags";
2791
- readonly moduleSpecifier: "@lunora/flags";
2792
- }, {
2793
- readonly appMethod: {
2794
- readonly configKey: "sql";
2795
- readonly doc: "Wire the Hyperdrive SQL client backing `ctx.sql` — build it with `createHyperdrive` + `fromPostgresJs`/`fromNodePg`/`fromMysql2`.";
2796
- readonly method: "hyperdrive";
2797
- };
2798
- readonly contextProperty: "sql";
2799
- readonly key: "hyperdrive";
2800
- readonly moduleSpecifier: "@lunora/hyperdrive";
2801
- readonly serverCtxField: {
2802
- readonly field: "\n /**\n * External database access via Hyperdrive. Non-deterministic — available only in actions. Writes here are NOT tracked by Lunora live queries; subscriptions will not re-run on external DB changes.\n */\n readonly sql: import(\"@lunora/hyperdrive\").SqlClient;";
2803
- readonly tier: "action";
2804
- };
2805
- }, {
2806
- readonly appMethod: {
2807
- readonly configKey: "images";
2808
- readonly doc: "Override the Images binding backing `ctx.images` (defaults to `env.IMAGES`).";
2809
- readonly method: "images";
2810
- };
2811
- readonly contextProperty: "images";
2812
- readonly key: "images";
2813
- readonly moduleSpecifier: "@lunora/bindings/images";
2814
- readonly serverCtxField: {
2815
- readonly field: "\n /** Cloudflare Images transforms (resize/format/optimize). Non-deterministic — available only in actions. */\n readonly images: import(\"@lunora/bindings/images\").Images;";
2816
- readonly tier: "action";
2817
- };
2818
- }, {
2819
- readonly appMethod: {
2820
- readonly configKey: "kv";
2821
- readonly doc: "Override the Workers KV binding backing `ctx.kv` (defaults to `env.KV`).";
2822
- readonly method: "kv";
2823
- };
2824
- readonly contextProperty: "kv";
2825
- readonly key: "kv";
2826
- readonly moduleSpecifier: "@lunora/bindings/kv";
2827
- readonly serverCtxField: {
2828
- readonly field: "\n readonly kv: import(\"@lunora/bindings/kv\").Kv;";
2829
- readonly tier: "every";
2830
- };
2831
- }, {
2832
- readonly key: "mail";
2833
- readonly moduleSpecifier: "@lunora/mail";
2834
- }, {
2835
- readonly contextProperty: "notify";
2836
- readonly key: "notify";
2837
- readonly moduleSpecifier: "@lunora/notify";
2838
- }, {
2839
- readonly appMethod: {
2840
- readonly configKey: "payment";
2841
- readonly doc: "Wire the payment options backing `ctx.payments`.";
2842
- readonly method: "payment";
2843
- };
2844
- readonly contextProperty: "payments";
2845
- readonly key: "payments";
2846
- readonly moduleSpecifier: "@lunora/payment";
2847
- }, {
2848
- readonly appMethod: {
2849
- readonly configKey: "x402";
2850
- readonly doc: "Wire the x402 agent-wallet pay rail backing `ctx.x402` — a payment-enabled `fetch` that answers `402` challenges under a mandatory spend policy (ActionCtx-only; spends real funds).";
2851
- readonly method: "x402";
2852
- };
2853
- readonly contextProperty: "x402";
2854
- readonly key: "x402";
2855
- readonly moduleSpecifier: "@lunora/x402/pay";
2856
- }, {
2857
- readonly contextProperty: "pipelines";
2858
- readonly key: "pipelines";
2859
- readonly moduleSpecifier: "@lunora/bindings/pipelines";
2860
- readonly serverCtxField: {
2861
- readonly field: "\n /** Pipelines ingestion sink (durable, R2-backed). Fire-and-forget and batched; do not read it back in-handler. */\n readonly pipelines: import(\"@lunora/bindings/pipelines\").PipelineClient;";
2862
- readonly tier: "action";
2863
- };
2864
- }, {
2865
- readonly appMethod: {
2866
- readonly configKey: "r2sql";
2867
- readonly doc: "Wire the R2 SQL client backing `ctx.r2sql` — build it with `createR2Sql({ accountId, apiToken, bucket })` (defaults to env `R2_SQL_TOKEN` / `R2_SQL_ACCOUNT_ID` / `R2_SQL_BUCKET`).";
2868
- readonly method: "r2sql";
2869
- };
2870
- readonly contextProperty: "r2sql";
2871
- readonly key: "r2sql";
2872
- readonly moduleSpecifier: "@lunora/bindings/r2sql";
2873
- readonly serverCtxField: {
2874
- readonly field: "\n /**\n * R2 SQL over Apache Iceberg tables (window functions, DISTINCT, set operations). Non-deterministic — available only in actions. Reads here are NOT tracked by Lunora live queries.\n */\n readonly r2sql: import(\"@lunora/bindings/r2sql\").R2SqlClient;";
2875
- readonly tier: "action";
2876
- };
2877
- }, {
2878
- readonly contextProperty: "scheduler";
2879
- readonly key: "scheduler";
2880
- readonly moduleSpecifier: "@lunora/scheduler";
2881
- }, {
2882
- readonly contextProperty: "storage";
2883
- readonly key: "storage";
2884
- readonly moduleSpecifier: "@lunora/storage";
2885
- }, {
2886
- readonly appMethod: {
2887
- readonly configKey: "vectors";
2888
- readonly doc: "Wire the Vectorize index map backing `ctx.vectors`.";
2889
- readonly method: "vectors";
2890
- };
2891
- readonly contextProperty: "vectors";
2892
- readonly key: "vectors";
2893
- readonly moduleSpecifier: "@lunora/bindings/vectors";
2894
- }, {
2895
- readonly contextProperty: "workflows";
2896
- readonly key: "workflows";
2897
- readonly moduleSpecifier: "@lunora/workflow";
2898
- }];
2899
- /** The literal union of every capability id — the single source of truth for `FeatureUsage`'s keys (so they cannot drift). */
2900
- type CapabilityKey = (typeof CAPABILITY_ROWS)[number]["key"];
2901
- /** The default codegen target — today's behavior, byte-identical goldens. */
2902
- declare const DEFAULT_TARGET = "cloudflare";
2777
+ declare const discoverShapes: (project: Project, lunoraDirectory: string) => ShapeIR[];
2903
2778
  /**
2904
- * Read `target` from `&lt;projectRoot>/lunora.json`.
2905
- *
2906
- * This lives in `@lunora/codegen` rather than `@lunora/config` where the rest
2907
- * of the `lunora.json` reading lives — because `@lunora/config` depends on
2908
- * `@lunora/codegen`, not the reverse. Putting it there and importing it here
2909
- * would invert that edge, so config delegates to this instead and there is
2910
- * still exactly one parser for the key.
2911
- *
2912
- * Best-effort and deliberately unvalidated: a missing file, malformed JSONC, or
2913
- * a non-string value all collapse to `undefined`, because those are shape
2914
- * errors rather than a name the user meant. An unrecognized *name* is returned
2915
- * as-is so the caller's registry lookup rejects it — swallowing a typo into the
2916
- * default would ship an app to the wrong provider.
2917
- * @param projectRoot Directory containing `lunora.json`.
2918
- * @returns the declared target, or `undefined` when none is usable.
2779
+ * Aggregate the schema-wide storage-rule metadata the studio's inspector reads:
2780
+ * every statically-discovered `(bucket, on, prefix, procedure)` entry across all
2781
+ * `.use(storageRules(...))` chains. Only the builder form can declare rules, so
2782
+ * bare-factory procedures contribute nothing.
2919
2783
  */
2920
- declare const readProjectTarget: (projectRoot: string) => string | undefined;
2784
+ declare const discoverStorageRulesMetadata: (project: Project, lunoraDirectory: string) => StorageRulesMetadataIR;
2785
+ /** The only file workflows may be declared in — mirrors `lunora/containers.ts`. */
2786
+ declare const WORKFLOWS_FILENAME = "workflows.ts";
2921
2787
  /**
2922
- * The target codegen should emit for: an explicit option wins, then
2923
- * `lunora.json`, then the default.
2924
- *
2925
- * `runCodegen` applies this itself so a caller that forgets to pass a target
2926
- * still emits the surface the project declared. That default matters more than
2927
- * it looks: a call site that silently omits the target emits the *default*
2928
- * surface with no diagnostic to notice, and the mismatch only shows up at
2929
- * runtime on the deployed app.
2930
- * @param projectRoot Directory containing `lunora.json`.
2931
- * @param explicit A caller-supplied target, if any.
2932
- * @returns the resolved target id — not guaranteed to be registered.
2788
+ * Discover every workflow the project declares: exported `defineWorkflow()`
2789
+ * calls in `lunora/workflows.ts`. Returns `[]` when the file doesn't exist. The
2790
+ * only wrangler-relevant literal is the optional `name` override; the workflow
2791
+ * body is runtime-only, so codegen never evaluates it.
2933
2792
  */
2934
- declare const resolveCodegenTarget: (projectRoot: string, explicit?: string) => string;
2793
+ declare const discoverWorkflows: (project: Project, lunoraDirectory: string) => WorkflowIR[];
2794
+ declare const GENERATED_HEADER = "// GENERATED by @lunora/codegen — do not edit.\n// Run `lunora codegen` to regenerate.\n\n";
2795
+ /** Emit `_generated/dataModel.ts` — `Doc&lt;"name">` + `Id&lt;"name">` for every table. */
2796
+ declare const emitDataModel: (schema: SchemaIR, useUmbrella?: boolean) => string;
2935
2797
  /**
2936
- * The target ids codegen can gate against.
2937
- *
2938
- * Exported so `@lunora/config` can assert that its driver registry and this
2939
- * capability-matrix registry name the same targets. They are two id spaces for one
2940
- * concept: a target that ships a driver but no matrix passes the CLI's
2941
- * validation and then emits an un-gated surface, and one with a matrix but no
2942
- * driver gates a surface nothing can deploy. Today both hold exactly
2943
- * `cloudflare`, which is why nothing has noticed.
2944
- * @returns the registered matrix ids, sorted.
2798
+ * Emit `_generated/api.ts` the typed `api.*` registry (public functions), the
2799
+ * `internal.*` registry, and (when the project declares them) the typed
2800
+ * `workflows.*` / `agents.*` scheduler-target reference objects. `api`/`internal` are the same `anyApi` proxy
2801
+ * at runtime (the `__lunoraRef` is identical); visibility is enforced
2802
+ * server-side at dispatch, not in the reference. Splitting the *types* keeps
2803
+ * internal functions off the client-facing `api` surface.
2945
2804
  */
2946
- declare const platformMatrixIds: () => ReadonlyArray<string>;
2947
- /** An advisor-style diagnostic about a target's platform capabilities. */
2948
- interface PlatformDiagnostic {
2949
- /** The codegen capability this concerns, when it is feature-specific. */
2950
- feature?: CapabilityKey;
2951
- /** Severity. `platform_unsupported_feature` and `platform_unknown_target` are both errors each drops or misdirects an emitted surface. */
2952
- level: "error" | "warn";
2953
- /** Human-readable explanation of the gap. */
2954
- message: string;
2955
- /** The lint id: `platform_unsupported_feature` or `platform_unknown_target`. */
2956
- name: "platform_unknown_target" | "platform_unsupported_feature";
2957
- /** How to resolve it. */
2958
- remediation: string;
2959
- /** The requested deploy target. */
2960
- target: string;
2805
+ interface EmitApiOptions {
2806
+ agents?: ReadonlyArray<AgentIR>;
2807
+ functions: ReadonlyArray<FunctionIR>;
2808
+ /** Typed REST routes; only `.stream()` (SSE) routes emit a `httpStreams.*` reference. */
2809
+ httpRoutes?: ReadonlyArray<HttpRouteIR>;
2810
+ /** Custom mutators (`lunora/mutators.ts`) emitted as `api.mutators.*` so a client `serverRef` is compile-checked. */
2811
+ mutators?: ReadonlyArray<MutatorIR>;
2812
+ useUmbrella?: boolean;
2813
+ workflows?: ReadonlyArray<WorkflowIR>;
2961
2814
  }
2815
+ declare const emitApi: (options: EmitApiOptions) => string;
2962
2816
  /**
2963
- * Committed, tracked baseline file holding the blessed structural schema
2964
- * snapshot the pre-deploy drift gate diffs against. Lives in `lunora/` (NOT the
2965
- * gitignored `_generated/`) so it is committed alongside `schema.ts`. Leading
2966
- * dot keeps it tucked away next to the schema it describes.
2967
- */
2968
- declare const SCHEMA_SNAPSHOT_FILENAME = ".lunora-schema.json";
2969
- /**
2970
- * Construct the ts-morph `Project` codegen discovers over. Prefers the user's
2971
- * `tsconfig.json` (when one is found walking up from `lunoraDirectory`) so
2972
- * cross-file type resolution and path aliases work; falls back to an isolated
2973
- * project otherwise. This is the exact construction {@link runCodegen} uses
2974
- * when no `project` is injected — exported so a long-lived caller (the Vite
2975
- * dev-loop) can build one once and reuse it across runs via
2976
- * {@link refreshCodegenProject} instead of re-parsing the user's whole TS
2977
- * program on every save.
2978
- */
2979
- declare const createCodegenProject: (lunoraDirectory: string) => Project;
2980
- /**
2981
- * Synchronise a reused {@link createCodegenProject} Project with the current
2982
- * on-disk state of `lunoraDirectory`, so the next {@link runCodegen} sees the
2983
- * same files a freshly-constructed Project would — without re-parsing the whole
2984
- * TS program. Adds any on-disk source file the Project doesn't yet have, and
2985
- * `refreshFromFileSystemSync()`es the ones it does (picking up edits); then
2986
- * removes Project source files under `lunoraDirectory` that no longer exist on
2987
- * disk (the classic stale-deleted-file cache bug).
2817
+ * Emit `_generated/collections.ts` a typed TanStack DB binding per `defineShape`
2818
+ * in `lunora/shapes.ts` (the local-first partial-replication surface).
2988
2819
  *
2989
- * Files outside `lunoraDirectory` (e.g. those pulled in by the user's tsconfig)
2990
- * are left untouched — they back type resolution and rarely change in the
2991
- * dev-loop; a tsconfig change invalidates the whole cached Project upstream.
2992
- */
2993
- declare const refreshCodegenProject: (project: Project, lunoraDirectory: string) => void;
2994
- /**
2995
- * Top-level codegen entry. Parses `&lt;projectRoot>/lunora/schema.ts` and every
2996
- * function file under `&lt;projectRoot>/lunora/`, then writes
2997
- * `_generated/{api,server,dataModel}.ts` next to them.
2820
+ * Each shape emits **two** entry points.
2998
2821
  *
2999
- * When `LUNORA_CODEGEN_TIMING` is set (truthy), a single diagnostic summary
3000
- * line is written to stderr with the total wall time and the discovery-vs-emit
3001
- * split opt-in instrumentation that is otherwise zero-cost and side-effect-free
3002
- * on the returned {@link CodegenResult}.
3003
- */
3004
- declare const runCodegen: (options: CodegenOptions) => CodegenResult;
3005
- interface CodegenOptions {
3006
- /**
3007
- * Which machine-readable API spec(s) to emit into `_generated/`.
3008
- *
3009
- * `"openapi"` (the default) writes only `openapi.json` (OpenAPI 3.1; covers
3010
- * both the RPC functions and `httpRouter()` REST routes). `"openrpc"` writes
3011
- * only `openrpc.json` (OpenRPC 1.x; the RPC functions only — OpenRPC cannot
3012
- * represent REST routes). `"both"` writes both files; `"none"` writes neither.
3013
- *
3014
- * Regardless of the choice, `CodegenResult.generated.openApi` and `.openRpc`
3015
- * always carry the rendered string (computation is cheap and pure); only the
3016
- * on-disk write is gated by this option.
3017
- */
3018
- apiSpec?: "both" | "none" | "openapi" | "openrpc";
3019
- /**
3020
- * When true, run discovery + emit (so any schema/function parse error
3021
- * surfaces) but skip writing files to `_generated/`. The returned
3022
- * `outputDirectory` is still the path that *would* have been written.
3023
- */
3024
- dryRun?: boolean;
3025
- /**
3026
- * Run the static schema advisor (unindexed FKs, …) during codegen.
3027
- * Defaults to `true`. When `false`, `CodegenResult.advisories` is empty.
3028
- * Computed regardless of `dryRun`; codegen never prints them — see
3029
- * {@link CodegenResult.advisories}.
3030
- */
3031
- lint?: boolean;
3032
- /** Override the lunora subdirectory name. Defaults to `"lunora"`. */
3033
- lunoraDirectory?: string;
3034
- /**
3035
- * Reuse a previously-constructed ts-morph {@link Project} instead of building
3036
- * a fresh one each run. The caller owns refreshing its source files from disk
3037
- * (see {@link refreshCodegenProject}) — codegen does not re-read changed files
3038
- * off an injected Project. Built via {@link createCodegenProject} when absent.
3039
- * Used by the Vite dev-loop to avoid re-parsing the whole TS program on every
3040
- * save; omit it (CLI one-shot path) to get the default fresh-Project behaviour.
3041
- */
3042
- project?: Project;
3043
- /** Project root containing the `lunora/` directory. */
3044
- projectRoot: string;
2822
+ * `&lt;shape>CollectionOptions(options)` is the composable form: it returns the full
2823
+ * `LunoraCollectionOptions` `config` for `createCollection`, plus `checkpoints`
2824
+ * (which `bindMutators` gates optimistic overlays on) and `scope`. This is what an app
2825
+ * with custom mutators needs, and what the old single-factory form made impossible: it
2826
+ * built the collection internally and dropped `checkpoints` on the floor, so there was
2827
+ * no way to wire mutators to the collection codegen produced.
2828
+ *
2829
+ * `&lt;shape>Collection(options)` is the convenience form for a read-only collection: it
2830
+ * returns `{ checkpoints, collection, scope }` rather than a bare `Collection`, so the
2831
+ * sync controls stay reachable even from the short path.
2832
+ *
2833
+ * Both are typed: `args` comes from the shape's own validators (a parameterless
2834
+ * shape takes none), rows resolve to `Doc&lt;"table">` when the shape names its table
2835
+ * with a literal, and `shardKey` / `getKey` / `load` / `onError` / `checkpoints` are
2836
+ * all threadable — a sharded table needs `shardKey` for its watermark to land in the
2837
+ * right bucket, and a server-minted `_id` that differs from the app's natural key
2838
+ * needs `getKey`.
2839
+ *
2840
+ * Returns `""` (so `writeIfPresent` skips the file) unless the project both
2841
+ * declares shapes AND installs `@lunora/db` — the add-on that ships
2842
+ * `lunoraCollectionOptions`. `@lunora/db` stays a scoped install even under the
2843
+ * `lunorash` umbrella (an opt-in add-on, like `@lunora/auth`), so its import is
2844
+ * always `@lunora/db/collections`; only the in-umbrella `@lunora/client` import
2845
+ * is remapped to `lunorash/client`.
2846
+ */
2847
+ declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
2848
+ interface EmitServerOptions {
2849
+ /** Agents declared via `defineAgent` exports wires the typed `ctx.agents` producers onto Mutation/Action contexts. */
2850
+ agents?: ReadonlyArray<AgentIR>;
2851
+ containers?: ReadonlyArray<ContainerIR>;
3045
2852
  /**
3046
- * The deploy target codegen tailors the emitted `ctx.*` surface to.
3047
- * Defaults to `"cloudflare"` whose capability matrix marks every feature
3048
- * native or emulated, so the default output is unchanged (byte-identical
3049
- * goldens). A target that marks a used feature unsupported omits its
3050
- * `ctx.*` surface and reports it in {@link CodegenResult.platformDiagnostics}.
3051
- * Only `"cloudflare"` is registered until other per-target `@lunora/platform`
3052
- * matrices land; an unknown target emits the full surface un-gated and a
3053
- * `platform_unknown_target` diagnostic.
2853
+ * The single `defineEnv(...)` contract declared in `lunora/env.ts`. When
2854
+ * present, `ctx.env` is typed as the validated `InferEnv` shape (recovered
2855
+ * via `ReturnType` over the accessor's `typeof`). `undefined` leaves `ctx.env`
2856
+ * the base optional binding record byte-identical to today.
3054
2857
  */
3055
- target?: string;
2858
+ env?: EnvIR;
3056
2859
  /**
3057
- * Re-bless the committed schema-drift baseline (`lunora/.lunora-schema.json`)
3058
- * with the current structural snapshot. The baseline is ALWAYS written on
3059
- * first capture (when the file is absent); set this to overwrite an existing
3060
- * one e.g. after the developer has added the data migration that justifies
3061
- * a breaking change. Ignored when `dryRun` is true.
2860
+ * A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access
2861
+ * facade (`@lunora/cloudflare-access/context`) onto every ctx. Distinct from
2862
+ * `emitApp`'s `hasAccess` (which gates the worker's `.access()` resolveIdentity
2863
+ * method); this one gates the per-request `ctx.access` read surface.
3062
2864
  */
3063
- updateSchemaBaseline?: boolean;
2865
+ hasAccessFacade?: boolean;
2866
+ hasAi?: boolean;
2867
+ /** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
2868
+ hasAnalytics?: boolean;
2869
+ /** A `lunora/` source uses `@lunora/browser` / `ctx.browser` — wires `ctx.browser` onto ActionCtx only. */
2870
+ hasBrowser?: boolean;
2871
+ /** The project declares `lunora/flags.ts` — wires `ctx.flags` (OpenFeature) onto every ctx. */
2872
+ hasFlags?: boolean;
2873
+ /** A `lunora/` source uses `@lunora/hyperdrive` / `ctx.sql` — wires `ctx.sql` onto ActionCtx only. */
2874
+ hasHyperdrive?: boolean;
2875
+ /** A `lunora/` source uses `@lunora/bindings/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
2876
+ hasImages?: boolean;
2877
+ /** A `lunora/` source uses `@lunora/bindings/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
2878
+ hasKv?: boolean;
2879
+ /** The project declares `lunora/notify.ts` — wires `ctx.notify` + its `ctx.push` alias (`@lunora/notify`) onto every ctx. */
2880
+ hasNotify?: boolean;
2881
+ hasPayments?: boolean;
2882
+ /** A `lunora/` source uses `@lunora/bindings/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
2883
+ hasPipelines?: boolean;
2884
+ /** A `lunora/` source uses `@lunora/bindings/r2sql` / `ctx.r2sql` — wires `ctx.r2sql` onto ActionCtx only. */
2885
+ hasR2sql?: boolean;
2886
+ /** A `lunora/` source uses `@lunora/x402/pay` / `ctx.x402` — wires the agent-wallet pay rail onto ActionCtx only. */
2887
+ hasX402?: boolean;
3064
2888
  /**
3065
- * Committed `wrangler.jsonc` `vars` entries that hold plaintext secrets — the
3066
- * `plaintext_secret_in_wrangler_vars` lint input. Produced by `@lunora/config`
3067
- * (which reads `wrangler.jsonc`) and threaded through by the CLI / Vite plugin;
3068
- * codegen only forwards it to the advisor. Absent when no wrangler config is
3069
- * present or the caller doesn't scan it.
2889
+ * The single `defineIdentity(...)` claim contract declared in
2890
+ * `lunora/identity.ts` (Plan 080). When present, `ctx.auth.getIdentity()`,
2891
+ * the RLS policy `ctx.auth.identity`, and the shard-authorization hooks
2892
+ * narrow to the declared shape (recovered via `InferIdentity` over the
2893
+ * contract's `typeof`). `undefined` keeps the identity an untyped bag —
2894
+ * byte-identical to today.
3070
2895
  */
3071
- wranglerVariables?: ReadonlyArray<WranglerVariableIR>;
2896
+ identity?: IdentityIR;
2897
+ /** Queues declared via `defineQueue` exports — wires the typed `ctx.queues` producers onto Mutation/Action contexts. */
2898
+ queues?: ReadonlyArray<QueueIR>;
2899
+ schema?: SchemaIR;
2900
+ storageRuleBuckets?: ReadonlyArray<string>;
2901
+ /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
2902
+ useUmbrella?: boolean;
2903
+ workflows?: ReadonlyArray<WorkflowIR>;
3072
2904
  }
3073
- interface CodegenResult {
3074
- /**
3075
- * The normalized advisor evidence the findings were produced from, so a
3076
- * caller can score it into a health map (`scoreAdvisor`) without re-running
3077
- * discovery. `undefined` under `lint: false`.
3078
- *
3079
- * Deliberately not scored here: the map carries a `generatedAt` stamp, and
3080
- * codegen's result stays a pure function of the sources.
3081
- */
3082
- advisorContext?: LintContext;
3083
- /**
3084
- * Static schema advisor findings (e.g. unindexed foreign keys) produced
3085
- * this run. Empty when `lint` is `false` or the schema is clean. Codegen
3086
- * does not print these itself each caller presents them through its own
3087
- * channel (the CLI logger, the vite overlay, the studio Advisors table).
3088
- * `formatAdvisories` is exported for a plain multi-line rendering.
3089
- */
3090
- advisories: ReadonlyArray<Finding>;
3091
- /**
3092
- * Agents discovered from `defineAgent` exports in `lunora/agents.ts` — the
3093
- * list the config layer reconciles into wrangler's `workflows[]` array (an
3094
- * agent compiles onto a Cloudflare Workflow). Agents are NOT Durable Objects,
3095
- * so this adds no binding or migration. Empty when the project declares none.
3096
- */
3097
- agents: ReadonlyArray<AgentIR>;
2905
+ declare const emitServer: ({ agents, containers, env, hasAccessFacade, hasAi, hasAnalytics, hasBrowser, hasFlags, hasHyperdrive, hasImages, hasKv, hasNotify, hasPayments, hasPipelines, hasR2sql, hasX402, identity, queues, schema, storageRuleBuckets, useUmbrella, workflows }?: EmitServerOptions) => string;
2906
+ interface EmitFunctionsOptions {
2907
+ agents?: ReadonlyArray<AgentIR>;
2908
+ functions: ReadonlyArray<FunctionIR>;
2909
+ migrations?: ReadonlyArray<MigrationIR>;
2910
+ mutators?: ReadonlyArray<MutatorIR>;
2911
+ shapes?: ReadonlyArray<ShapeIR>;
2912
+ /** Import of a sandbox tool (`browserTool`/`containerTool`) auto-registers the `sandbox:invoke` action. */
2913
+ usesSandbox?: boolean;
2914
+ useUmbrella?: boolean;
2915
+ }
2916
+ declare const emitFunctions: (options: EmitFunctionsOptions) => string;
2917
+ /**
2918
+ * Emit `_generated/containers.ts`one container-enabled Durable Object class
2919
+ * per `defineContainer` export, each a thin subclass of `LunoraContainer`
2920
+ * (`@lunora/container/do`) constructed with the user's definition object. The
2921
+ * worker entry must re-export these classes: wrangler requires every
2922
+ * `containers[].class_name` to be exported by the deployed worker. Returns ""
2923
+ * when the project declares no containers (the file is not written then).
2924
+ */
2925
+ declare const emitContainers: (containers: ReadonlyArray<ContainerIR>, jurisdiction?: JurisdictionIR) => string;
2926
+ /**
2927
+ * Emit `_generated/workflows.ts` one `WorkflowEntrypoint` class per
2928
+ * `defineWorkflow` export, each a thin subclass of `LunoraWorkflow`
2929
+ * (`@lunora/workflow/do`) constructed with the user's definition object. The
2930
+ * worker entry must re-export these classes: wrangler requires every
2931
+ * `workflows[].class_name` to be exported by the deployed worker. Returns ""
2932
+ * when the project declares no workflows (the file is not written then).
2933
+ */
2934
+ declare const emitWorkflows: (workflows: ReadonlyArray<WorkflowIR>) => string;
2935
+ /**
2936
+ * Emit `_generated/agents.ts` — one `WorkflowEntrypoint` class per `defineAgent`
2937
+ * export, each a thin subclass of `LunoraWorkflow` (`@lunora/workflow/do`)
2938
+ * constructed with the compiled agent tool-loop (`compileAgentWorkflow`). Like
2939
+ * `_generated/workflows.ts`, the worker entry must re-export these classes:
2940
+ * wrangler requires every `workflows[].class_name` to be exported by the
2941
+ * deployed worker. Returns "" when the project declares no agents (the file is
2942
+ * not written then).
2943
+ */
2944
+ declare const emitAgents: (agents: ReadonlyArray<AgentIR>) => string;
2945
+ interface EmitShardOptions {
2946
+ advisories?: ReadonlyArray<Finding>;
2947
+ /** Every declared procedure — the health map's denominator, served via `getAdvisorProcedures`. */
2948
+ advisorProcedures?: ReadonlyArray<AdvisorProcedureProtection>;
2949
+ /** Agents declared via `defineAgent` exports in `lunora/agents.ts` — wires the typed `ctx.agents` producers. */
2950
+ agents?: ReadonlyArray<AgentIR>;
2951
+ containers?: ReadonlyArray<ContainerIR>;
2952
+ /** The single `defineEnv(...)` contract declared in `lunora/env.ts` — applies the accessor to the worker `env` to populate `ctx.env`. */
2953
+ env?: EnvIR;
2954
+ /** Statically-discovered `ctx.flags.&lt;type>("key")` reads — the studio Flags page + reactive evaluation iterate these. */
2955
+ flagKeys?: ReadonlyArray<{
2956
+ key: string;
2957
+ type: "boolean" | "number" | "object" | "string";
2958
+ }>;
2959
+ /** A `lunora/` source reads `ctx.access` — wires the verified Cloudflare Access facade onto every ctx. */
2960
+ hasAccessFacade?: boolean;
2961
+ hasAi?: boolean;
2962
+ /** A `lunora/` source reads `ctx.analytics` — wires the Analytics Engine write helper onto every ctx. */
2963
+ hasAnalytics?: boolean;
2964
+ /** A `lunora/` source reads `ctx.browser` — wires `ctx.browser` onto the ActionCtx only. */
2965
+ hasBrowser?: boolean;
2966
+ /** The project declares `lunora/flags.ts` — wires `ctx.flags` (OpenFeature) onto every ctx. */
2967
+ hasFlags?: boolean;
2968
+ /** A `lunora/` source reads `ctx.sql` (Hyperdrive) — wires `ctx.sql` onto the ActionCtx only. */
2969
+ hasHyperdrive?: boolean;
2970
+ /** A `lunora/` source reads `ctx.images` — wires `ctx.images` onto the ActionCtx only. */
2971
+ hasImages?: boolean;
2972
+ /** A `lunora/` source reads `ctx.kv` — wires `ctx.kv` onto every ctx. */
2973
+ hasKv?: boolean;
2974
+ /** The project declares `lunora/notify.ts` — wires `ctx.notify` + its `ctx.push` alias (`@lunora/notify`) onto every ctx. */
2975
+ hasNotify?: boolean;
2976
+ hasPayments?: boolean;
2977
+ /** A `lunora/` source reads `ctx.pipelines` — wires `ctx.pipelines` onto the ActionCtx only. */
2978
+ hasPipelines?: boolean;
2979
+ /** A `lunora/` source reads `ctx.r2sql` (R2 SQL) — wires `ctx.r2sql` onto the ActionCtx only. */
2980
+ hasR2sql?: boolean;
2981
+ /** A `lunora/` source reads `ctx.x402` — wires the agent-wallet pay rail onto the ActionCtx only. */
2982
+ hasX402?: boolean;
2983
+ maskMetadata?: MaskMetadataIR;
2984
+ /** Custom mutators declared via `defineMutator` in `lunora/mutators.ts` — wires the `isCustomMutator` push-protocol override. */
2985
+ mutators?: ReadonlyArray<MutatorIR>;
2986
+ /** Queues declared via `defineQueue` exports in `lunora/queues.ts` — wires the typed `ctx.queues` producers. */
2987
+ queues?: ReadonlyArray<QueueIR>;
2988
+ rlsMetadata?: RlsMetadataIR;
2989
+ schema: SchemaIR;
3098
2990
  /**
3099
- * Containers discovered from `defineContainer` exports in
3100
- * `lunora/containers.ts` the list the config layer reconciles into
3101
- * wrangler's `containers[]`, `CONTAINER_*` Durable Object bindings, and
3102
- * migration classes. Empty when the project declares no containers.
2991
+ * The structural snapshot the pre-deploy drift gate diffs against, threaded
2992
+ * in so the emitted DO records it in `__lunora_schema_history` on cold start
2993
+ * (plan 200 — the Studio's schema-version timeline). Optional so an emitter
2994
+ * caller that has no snapshot (tests, fixtures) emits the pre-ledger shape
2995
+ * unchanged.
3103
2996
  */
3104
- containers: ReadonlyArray<ContainerIR>;
3105
- /**
3106
- * Deduplicated cron schedules discovered from `cronJobs()` definitions —
3107
- * the array the vite plugin reconciles into `wrangler.jsonc`'s
3108
- * `triggers.crons`. Empty when the project declares no crons.
3109
- */
3110
- cronTriggers: ReadonlyArray<string>;
3111
- generated: {
3112
- /** WorkflowEntrypoint classes for declared agents (`_generated/agents.ts`); `""` (and not written) when no agents are declared. */
3113
- agents: string;
3114
- api: string;
3115
- /** Fluent worker-composition builder (`_generated/app.ts`) `defineApp()`. Always written. */
3116
- app: string;
3117
- /** Partial-replication collection factories (`_generated/collections.ts`); `""` (and not written) unless the project declares shapes and installs `@lunora/db`. */
3118
- collections: string;
3119
- /** Container DO classes (`_generated/containers.ts`); `""` (and not written) when no containers are declared. */
3120
- containers: string;
3121
- crons: string;
3122
- dataModel: string;
3123
- drizzleGlobal: string;
3124
- drizzleShard: string;
3125
- functions: string;
3126
- /** OpenAPI 3.1.0 document (`_generated/openapi.json`), pretty-printed JSON. */
3127
- openApi: string;
3128
- /**
3129
- * OpenAPI document as an importable TS module (`_generated/openapi.ts`)
3130
- * `export const openApiSpec`, the worker imports it for
3131
- * `createWorker({ openApiSpec })`. Same document as `openApi`. Written
3132
- * alongside `openapi.json` whenever `apiSpec` includes `openapi`.
3133
- */
3134
- openApiModule: string;
3135
- /** OpenRPC 1.x document (`_generated/openrpc.json`), pretty-printed JSON. Always computed; written only when `apiSpec` includes `openrpc`. */
3136
- openRpc: string;
3137
- /**
3138
- * OpenRPC document as an importable TS module (`_generated/openrpc.ts`)
3139
- * `export const openRpcSpec`, for `createWorker({ openRpcSpec })`. Same
3140
- * document as `openRpc`. Written alongside `openrpc.json` whenever
3141
- * `apiSpec` includes `openrpc`.
3142
- */
3143
- openRpcModule: string;
3144
- /** Push-consumer queue registry (`_generated/queues.ts`); `""` (and not written) when no push queues are declared. */
3145
- queues: string;
3146
- /** Project-bound seed client (`_generated/seed.ts`); `""` (and not written) when `@lunora/seed` is not a declared dependency. */
3147
- seed: string;
3148
- server: string;
3149
- shard: string;
3150
- /** Static vector-index registry (`_generated/vectors.ts`) — `LUNORA_VECTOR_INDEXES`. Empty array body when the schema declares none. */
3151
- vectors: string;
3152
- /** WorkflowEntrypoint classes (`_generated/workflows.ts`); `""` (and not written) when no workflows are declared. */
3153
- workflows: string;
3154
- };
3155
- outputDirectory: string;
3156
- /**
3157
- * Portability diagnostics for the requested {@link CodegenOptions.target}:
3158
- * `ctx.*` features the app uses that the target does not support (omitted
3159
- * from the emitted surface), or an unknown target. Empty for a
3160
- * fully-supported app on the default Cloudflare target. Presentation is the
3161
- * caller's job, like {@link CodegenResult.advisories}.
3162
- */
3163
- platformDiagnostics: ReadonlyArray<PlatformDiagnostic>;
3164
- /**
3165
- * Queues discovered from `defineQueue` exports in `lunora/queues.ts` the
3166
- * list the config layer reconciles into wrangler's `queues.producers[]` /
3167
- * `queues.consumers[]`. Queues are NOT Durable Objects, so this adds no
3168
- * binding or migration. Empty when the project declares no queues.
3169
- */
3170
- queues: ReadonlyArray<QueueIR>;
2997
+ schemaSnapshot?: SchemaSnapshot;
2998
+ /** Replication shapes declared via `defineShape` in `lunora/shapes.ts` — wires the `resolveShape` subscription override. */
2999
+ shapes?: ReadonlyArray<ShapeIR>;
3000
+ storageRules?: StorageRulesMetadataIR;
3001
+ studioFeatures?: StudioFeaturesResult;
3002
+ /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
3003
+ useUmbrella?: boolean;
3004
+ workflows?: ReadonlyArray<WorkflowIR>;
3005
+ }
3006
+ declare const emitShard: ({ advisories, advisorProcedures, agents, containers, env, flagKeys, hasAccessFacade, hasAi, hasAnalytics, hasBrowser, hasFlags, hasHyperdrive, hasImages, hasKv, hasNotify, hasPayments, hasPipelines, hasR2sql, hasX402, maskMetadata, mutators, queues, rlsMetadata, schema, schemaSnapshot, shapes, storageRules, studioFeatures, useUmbrella, workflows }: EmitShardOptions) => string;
3007
+ /**
3008
+ * Emit drizzle `sqliteTable` definitions for the project schema, split into
3009
+ * `global` (D1-backed) and `shard` (DO-SQLite-backed) buckets. Tables marked
3010
+ * `.global()` go in the global file; everything else (default root + `.shardBy()`)
3011
+ * goes in the shard file.
3012
+ *
3013
+ * `searchIndexes` are intentionally not emitted — drizzle has no `sqliteTable`
3014
+ * abstraction for FTS5 virtual tables. FTS plumbing is handled by the runtime
3015
+ * outside of drizzle.
3016
+ */
3017
+ declare const emitDrizzleSchema: (schema: SchemaIR, useUmbrella?: boolean) => {
3018
+ global: string;
3019
+ shard: string;
3020
+ };
3021
+ /**
3022
+ * Emit `_generated/crons.ts` from the discovered cron jobs.
3023
+ *
3024
+ * `LUNORA_CRON_TRIGGERS` is the deduplicated schedule array what lands in
3025
+ * wrangler's `triggers.crons` (the vite plugin reconciles it into
3026
+ * `wrangler.jsonc`, and {@link emitWranglerCronTriggers} renders the same list
3027
+ * for the CLI / docs).
3028
+ *
3029
+ * `LUNORA_CRONS` is the dispatcher map keyed by cron expression, each value a
3030
+ * list of `{ name, functionPath, args }`. Cloudflare's `scheduled()` handler
3031
+ * receives only the cron string, so multiple jobs sharing one expression must
3032
+ * all fire hence a list per key rather than a single entry.
3033
+ *
3034
+ * Jobs arrive pre-sorted by name (deterministic output); the trigger array
3035
+ * preserves first-seen order of distinct expressions.
3036
+ *
3037
+ * Cloudflare caps a Worker at **3 Cron Triggers** (i.e. 3 distinct cron
3038
+ * expressions). Because the dispatcher fires every job sharing an expression,
3039
+ * many jobs can ride a single trigger only the count of *distinct* schedules
3040
+ * matters. `lunora codegen` warns when that count exceeds the limit; for
3041
+ * finer-grained scheduling use Durable Object alarms (`@lunora/scheduler`),
3042
+ * which have no such cap.
3043
+ */
3044
+ declare const emitCrons: (crons: ReadonlyArray<CronJobIR>) => string;
3045
+ /**
3046
+ * Render `_generated/vectors.ts` — the static registry of every vector index
3047
+ * declared in `schema.ts` (inline `.vectorize()` columns + standalone
3048
+ * `defineVectorIndex()` definitions).
3049
+ *
3050
+ * Cloudflare Vectorize exposes no way to enumerate an account's indexes at
3051
+ * runtime a binding can `describe()` itself but the worker can't ask "which
3052
+ * indexes exist". So this generated array is the source of truth the studio's
3053
+ * vector browser lists, and the worker's admin route pairs each entry with its
3054
+ * live `describe()` stats. Entries are sorted by name for deterministic output.
3055
+ */
3056
+ declare const emitVectors: (vectorIndexes: ReadonlyArray<VectorIndexIR>) => string;
3057
+ /**
3058
+ * Render the deduplicated cron schedules as a JSON fragment suitable for
3059
+ * splicing into `wrangler.jsonc`'s `triggers.crons`. The vite plugin uses this
3060
+ * (plus the parsed wrangler config) to reconcile generated triggers without the
3061
+ * user hand-editing the file.
3062
+ */
3063
+ declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => string[];
3064
+ /** Which capability methods the generated `defineApp` builder exposes — one flag per package-backed feature the app actually uses. */
3065
+ interface EmitAppOptions {
3171
3066
  /**
3172
- * The CURRENT structural schema snapshot computed this run (tables + field
3173
- * kinds/optionality + indexes/relations/shard mode + declared migration ids).
3174
- * The pre-deploy drift gate diffs this against the committed baseline read
3175
- * from {@link CodegenResult.schemaSnapshotPath}. Always present, even on a
3176
- * `dryRun`.
3067
+ * Inbound-email agents (`defineAgent({ onEmail })`) wire the worker's
3068
+ * top-level `email()` handler to `dispatchAgentEmail(...)` (from
3069
+ * `@lunora/agent/inbound`), so received mail starts a durable run. Empty/absent
3070
+ * no wiring, byte-identical output for email-free (and agent-free) projects.
3177
3071
  */
3178
- schemaSnapshot: SchemaSnapshot;
3179
- /** Absolute path of the committed baseline file (`lunora/.lunora-schema.json`). */
3180
- schemaSnapshotPath: string;
3072
+ emailAgents?: ReadonlyArray<{
3073
+ bindingName: string;
3074
+ exportName: string;
3075
+ }>;
3076
+ /** App depends on `@lunora/cloudflare-access` → emit `.access()` (wire the Cloudflare Access `resolveIdentity`, composed ahead of `@lunora/auth` when both are present). */
3077
+ hasAccess: boolean;
3078
+ /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
3079
+ hasAi: boolean;
3080
+ /** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
3081
+ hasAnalytics: boolean;
3082
+ /** App depends on `@lunora/auth` → emit `.auth()` + the lazy build/migrate dance. */
3083
+ hasAuth: boolean;
3084
+ /** App uses `@lunora/browser` / `ctx.browser` → emit `.browser()`. */
3085
+ hasBrowser: boolean;
3086
+ /** App depends on a worker-composition framework adapter (`@lunora/astro`/`@lunora/svelte`/`@lunora/vue`) → emit `.buildFrameworkWorker(host)`. */
3087
+ hasFramework: boolean;
3088
+ /** Schema declares **D1-backed** `.global()` tables → emit `.global()` (D1 ctx-db + studio introspector + cross-shard relations). */
3089
+ hasGlobal: boolean;
3090
+ /** App uses `@lunora/hyperdrive` / `ctx.sql` → emit `.hyperdrive()`. */
3091
+ hasHyperdrive: boolean;
3092
+ /** Schema declares **Hyperdrive-backed** `.global({ backend: "hyperdrive" })` tables → emit `.hyperdriveGlobal()` (reactive Postgres/MySQL ctx-db over Hyperdrive). */
3093
+ hasHyperdriveGlobal: boolean;
3094
+ /** App uses `@lunora/bindings/images` / `ctx.images` → emit `.images()`. */
3095
+ hasImages: boolean;
3096
+ /** App uses `@lunora/bindings/kv` / `ctx.kv` → emit `.kv()`. */
3097
+ hasKv: boolean;
3098
+ /** App declares `lunora/notify.ts` (`@lunora/notify`) → wire `options.notifySubscriptionStore` so the studio Notifications page can read registered devices. */
3099
+ hasNotify: boolean;
3100
+ /** App uses `@lunora/payment` / `ctx.payments` → emit `.payment()`. */
3101
+ hasPayments: boolean;
3102
+ /** App declares push queues (`defineQueue`) → wire `LUNORA_QUEUE_REGISTRY` into the worker's `queue()` consumer entry. */
3103
+ hasQueue: boolean;
3104
+ /** App uses `@lunora/bindings/r2sql` / `ctx.r2sql` → emit `.r2sql()`. */
3105
+ hasR2sql: boolean;
3106
+ /** App imports `@lunora/scheduler` / declares crons → emit `.scheduler()`. */
3107
+ hasScheduler: boolean;
3108
+ /** App uses `@lunora/storage` → emit `.storage()` (DO `ctx.storage` + studio file browser). */
3109
+ hasStorage: boolean;
3110
+ /** Schema declares vector indexes → emit `.vectors()` (the Vectorize index map backing `ctx.vectors`). */
3111
+ hasVectors: boolean;
3112
+ /** App declares Cloudflare Workflows (`defineWorkflow`) → wire `options.workflowsClient` so the studio's workflow-instance proxy can reach the CF REST API. */
3113
+ hasWorkflow: boolean;
3114
+ /** App uses `@lunora/x402/pay` / `ctx.x402` → emit `.x402()` (wire the agent-wallet pay rail). */
3115
+ hasX402: boolean;
3116
+ /** The single `defineIdentity(...)` contract in `lunora/identity.ts` (Plan 080) → import it as a VALUE and wire `options.identity`, so the runtime trust boundary validates every resolved identity before it becomes `ctx.auth`. `undefined` ⇒ no wiring, byte-identical output. */
3117
+ identity?: IdentityIR;
3118
+ /** Schema declares `.jurisdiction("…")` → pin every DO the worker reaches (shards, fan-out, scheduler, containers) to the Cloudflare data-residency jurisdiction. */
3119
+ jurisdiction?: JurisdictionIR;
3120
+ /** Project depends on the unscoped `lunorash` umbrella → import the runtime via `lunorash/runtime` instead of `@lunora/runtime`. */
3121
+ useUmbrella: boolean;
3181
3122
  /**
3182
- * Workflows discovered from `defineWorkflow` exports in
3183
- * `lunora/workflows.ts` the list the config layer reconciles into
3184
- * wrangler's `workflows[]` array. Workflows are NOT Durable Objects, so this
3185
- * adds no binding or migration. Empty when the project declares no workflows.
3123
+ * Voice-enabled agents (`defineAgent({ voice: … })`) wire
3124
+ * `options.voiceAgents`, mapping each agent's export name to its `VOICE_*`
3125
+ * Durable Object namespace binding so the runtime exposes
3126
+ * `/_lunora/voice/&lt;exportName>`. Empty/absent no wiring, byte-identical
3127
+ * output for voice-free (and agent-free) projects.
3186
3128
  */
3187
- workflows: ReadonlyArray<WorkflowIR>;
3129
+ voiceAgents?: ReadonlyArray<{
3130
+ bindingName: string;
3131
+ exportName: string;
3132
+ }>;
3133
+ /** An OpenAPI spec is emitted (`openapi.ts`) → wire `openApiSpec` into the worker. */
3134
+ wantsOpenApi: boolean;
3135
+ /** An OpenRPC spec is emitted (`openrpc.ts`) → wire `openRpcSpec` into the worker. */
3136
+ wantsOpenRpc: boolean;
3188
3137
  }
3138
+ /**
3139
+ * Emit `_generated/app.ts` — a fluent, feature-specialized worker-composition
3140
+ * builder. Only the methods for capabilities THIS app uses are emitted, so the
3141
+ * builder's type surface (IntelliSense) lists exactly what can be configured.
3142
+ *
3143
+ * Each capability declaration is fanned into BOTH runtime surfaces: the DO-side
3144
+ * `createShardDO(...)` factory that backs `ctx.*`, and the worker-side
3145
+ * `createWorker(...)` options that back the studio/admin endpoints — so storage
3146
+ * / scheduler / global are declared once instead of twice. The builder is pure
3147
+ * sugar over the public `createWorker` / `createShardDO`; both stay usable.
3148
+ *
3149
+ * Lives in generated code (not `@lunora/runtime`, which is dependency-free) so
3150
+ * it can import the add-on packages the app installed (`@lunora/auth`,
3151
+ * `@lunora/storage`, …) directly.
3152
+ */
3153
+ declare const emitApp: (options: EmitAppOptions) => string;
3154
+ /** Inputs the OpenAPI emitter needs from a codegen run. */
3155
+ interface OpenApiEmitInput {
3156
+ functions: ReadonlyArray<FunctionIR>;
3157
+ httpRoutes: ReadonlyArray<HttpRouteIR>;
3158
+ /** `info.version`; defaults to `"0.0.0"` with a TODO when the project version is unknown. */
3159
+ version?: string;
3160
+ }
3161
+ /**
3162
+ * Emit an OpenAPI 3.1.0 document covering both Lunora function surfaces.
3163
+ *
3164
+ * `httpRouter()` typed REST routes become real `paths` keyed by their method +
3165
+ * URL, with query/path parameters and JSON request bodies derived from their
3166
+ * `v.*` validators, and a response schema from `.output()` when declared.
3167
+ *
3168
+ * RPC `query`/`mutation`/`action` functions become one operation each on
3169
+ * `POST /_lunora/rpc` (disambiguated by a `#functionPath` path fragment), with a
3170
+ * requestBody pinning `functionPath` + typed `args`. `internal`/`stream`
3171
+ * functions are excluded (unreachable / not invocable on the external RPC path).
3172
+ *
3173
+ * Operations are grouped into `tags` by file namespace, and every operation
3174
+ * references a reusable `LunoraError` error-response component enumerating the
3175
+ * standard error codes. Borrows oRPC's per-procedure-operation + tag-grouping +
3176
+ * internal-filtering structure; the JSON Schema dialect matches `@lunora/values`
3177
+ * (Draft 2020-12). Returns the document as a plain object (the single source of
3178
+ * truth `emitOpenApi` stringifies and `emitOpenApiModule` inlines, so the
3179
+ * `.json` and `.ts` artifacts can never drift).
3180
+ */
3181
+ declare const buildOpenApiDocument: (input: OpenApiEmitInput) => Record<string, unknown>;
3182
+ /**
3183
+ * Emit the OpenAPI 3.1 document as a pretty-printed JSON string
3184
+ * (`_generated/openapi.json`) — the portable artifact for external tooling.
3185
+ */
3186
+ declare const emitOpenApi: (input: OpenApiEmitInput) => string;
3187
+ /**
3188
+ * Emit the OpenAPI document as an importable TS module
3189
+ * (`_generated/openapi.ts`) the worker entry imports and passes to
3190
+ * `createWorker({ openApiSpec })`. The document object literal is inlined
3191
+ * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
3192
+ * `.json` are byte-identical content and regenerate together — closing the gap
3193
+ * where a Worker cannot read the JSON file at runtime. `document_` is the object
3194
+ * returned by {@link buildOpenApiDocument} (reused, never recomputed).
3195
+ */
3196
+ declare const emitOpenApiModule: (document_: Record<string, unknown>) => string;
3197
+ /** The OpenRPC dialect version this emitter targets. */
3198
+ declare const OPENRPC_VERSION = "1.3.2";
3199
+ /** Inputs the OpenRPC emitter needs from a codegen run. */
3200
+ interface OpenRpcEmitInput {
3201
+ functions: ReadonlyArray<FunctionIR>;
3202
+ /** `info.version`; defaults to `"0.0.0"` with a TODO when the project version is unknown. */
3203
+ version?: string;
3204
+ }
3205
+ /**
3206
+ * Emit an OpenRPC 1.x document describing Lunora's JSON-RPC surface.
3207
+ *
3208
+ * Only the RPC `query`/`mutation`/`action` functions become `methods` — one per
3209
+ * function, `name` = `file:fn`. `internal` (off the external RPC path) and
3210
+ * `stream` (not invocable over the RPC envelope) are excluded, the same filter
3211
+ * the OpenAPI emitter applies. Each method's single `args` param is typed from
3212
+ * the function's `v.*` validators (`argsObjectSchema`); `result` is the
3213
+ * `.output()` schema when declared, else a best-effort inferred schema. The
3214
+ * standard `LunoraError` codes ride along under each method's `errors`.
3215
+ *
3216
+ * `httpRouter()` typed REST routes are deliberately omitted — OpenRPC is
3217
+ * RPC-only and cannot represent REST paths; the OpenAPI document is the spec
3218
+ * that covers the REST surface. Methods are sorted by name for stable output.
3219
+ * Returns the document as a plain object (the single source of truth
3220
+ * `emitOpenRpc` stringifies and `emitOpenRpcModule` inlines, so the `.json` and
3221
+ * `.ts` artifacts can never drift).
3222
+ */
3223
+ declare const buildOpenRpcDocument: (input: OpenRpcEmitInput) => Record<string, unknown>;
3224
+ /**
3225
+ * Emit the OpenRPC 1.x document as a pretty-printed JSON string
3226
+ * (`_generated/openrpc.json`) — the portable artifact for external tooling.
3227
+ */
3228
+ declare const emitOpenRpc: (input: OpenRpcEmitInput) => string;
3229
+ /**
3230
+ * Emit the OpenRPC document as an importable TS module
3231
+ * (`_generated/openrpc.ts`) the worker entry imports and passes to
3232
+ * `createWorker({ openRpcSpec })`. The document object literal is inlined
3233
+ * verbatim (same `JSON.stringify` form the `.json` uses), so the `.ts` and
3234
+ * `.json` are byte-identical content and regenerate together. `document_` is
3235
+ * the object returned by {@link buildOpenRpcDocument} (reused, never recomputed).
3236
+ */
3237
+ declare const emitOpenRpcModule: (document_: Record<string, unknown>) => string;
3189
3238
  /**
3190
3239
  * Build a {@link SchemaSnapshot} from a parsed {@link SchemaIR} and the set of
3191
3240
  * declared migration ids. Tables and migration ids are sorted so the emitted
@@ -3244,6 +3293,8 @@ interface SchemaDriftDecision {
3244
3293
  declare const evaluateSchemaDrift: (options: {
3245
3294
  allowDrift?: boolean;
3246
3295
  baseline: SchemaSnapshot | undefined;
3296
+ /** The command printing the remediation, so it names only flags that command accepts. */
3297
+ command?: string;
3247
3298
  current: SchemaSnapshot;
3248
3299
  }) => SchemaDriftDecision;
3249
3300
  /**
@@ -3275,4 +3326,4 @@ declare const secretKindOf: (value: string) => string | undefined;
3275
3326
  /** A redacted preview of a secret value — first 4 chars plus its length, never the full value. */
3276
3327
  declare const redact: (value: string) => string;
3277
3328
  declare const VERSION = "0.0.0";
3278
- export { AGENTS_FILENAME, type AgentIR, type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, DEFAULT_TARGET, type DriftChange, type DriftScope, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type LintSchemaOptions, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, NOTIFY_FILENAME, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type PlatformDiagnostic, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SHAPES_FILENAME, type SandboxUsage, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, type WranglerVariableIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAgents, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverNotifyCalls, discoverNotifyConfig, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSandboxUsage, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitAgents, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, platformMatrixIds, readProjectTarget, redact, refreshCodegenProject, resolveCodegenTarget, runCodegen, schemaFromIr, secretKindOf, serializeSchemaSnapshot, toAdvisorContext, validatorIrToJsonSchema };
3329
+ export { AGENTS_FILENAME, type AgentIR, type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, DEFAULT_TARGET, type DriftChange, type DriftScope, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type LintSchemaOptions, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, NOTIFY_FILENAME, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type PlatformDiagnostic, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SHAPES_FILENAME, type SandboxUsage, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, type WranglerVariableIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, describeErrorLevelFindings, diagnosticAt, diffSchemaSnapshots, discoverAgents, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverNotifyCalls, discoverNotifyConfig, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSandboxUsage, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitAgents, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, errorAdvisoryNames, errorPlatformDiagnosticNames, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, platformMatrixIds, readPackageDependencies, readProjectTarget, redact, refreshCodegenProject, resolveCodegenTarget, runCodegen, schemaFromIr, secretKindOf, serializeSchemaSnapshot, toAdvisorContext, validatorIrToJsonSchema };