@lunora/advisor 1.0.0-alpha.60 → 1.0.0-alpha.61

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 (26) hide show
  1. package/README.md +7 -5
  2. package/dist/index.d.mts +155 -59
  3. package/dist/index.d.ts +155 -59
  4. package/dist/index.mjs +1 -1
  5. package/dist/packem_shared/{MAP_VERSION-BS08jBkS.mjs → MAP_VERSION-DxkFCiLX.mjs} +1 -1
  6. package/dist/packem_shared/aiUnboundedGenerationPublic-iu7crnSa.mjs +1 -0
  7. package/dist/packem_shared/classifySensitivity-B1kkm44P.mjs +1 -0
  8. package/dist/packem_shared/{compareToBaseline-T1_kl8VY.mjs → compareToBaseline-BwXFusQG.mjs} +1 -1
  9. package/dist/packem_shared/errorRateOutlier-CgK42sq0.mjs +1 -0
  10. package/dist/packem_shared/insertManyUnsafeUserData-xLYAkcKj.mjs +1 -0
  11. package/dist/packem_shared/nondeterministicQueryMutation-BZV_biUV.mjs +1 -0
  12. package/dist/packem_shared/privilegedFanoutFromPublicProcedure-sMsZN4uP.mjs +1 -0
  13. package/dist/packem_shared/procedure-protections-DRQotu9I.mjs +1 -0
  14. package/dist/packem_shared/signupMutationWithoutDisposableGating-Tgv9SbsY.mjs +1 -0
  15. package/dist/packem_shared/storageKeyFromUserArgs-CI-6kyte.mjs +1 -0
  16. package/dist/packem_shared/userCreatingMutationWithoutCaptcha-DGiZ3_W7.mjs +1 -0
  17. package/package.json +3 -3
  18. package/dist/packem_shared/AE_METRIC_EVENTS-LP_0D7Uw.mjs +0 -1
  19. package/dist/packem_shared/aiUnboundedGenerationPublic-D3HZrh5_.mjs +0 -1
  20. package/dist/packem_shared/classifySensitivity-DjswRYWd.mjs +0 -1
  21. package/dist/packem_shared/insertManyUnsafeUserData-DrH7PdRs.mjs +0 -1
  22. package/dist/packem_shared/nondeterministicQueryMutation-BaMfbAk_.mjs +0 -1
  23. package/dist/packem_shared/privilegedFanoutFromPublicProcedure-C2j2GSUN.mjs +0 -1
  24. package/dist/packem_shared/signupMutationWithoutDisposableGating-BNzlsE0F.mjs +0 -1
  25. package/dist/packem_shared/storageKeyFromUserArgs-BeS5cp56.mjs +0 -1
  26. package/dist/packem_shared/userCreatingMutationWithoutCaptcha-iDjRkUFO.mjs +0 -1
package/README.md CHANGED
@@ -79,18 +79,20 @@ for (const finding of findings) {
79
79
 
80
80
  ### Runtime lints
81
81
 
82
- The runtime tier (`hot_shard`, `index_utilization`, `constraint_validator`) reads observed signal off the `LintContext` (`shardTraffic`, `tableScans`, `indexHits`, `tableSamples`). The Studio backend fills those from each shard's durable counters. As an alternative feeder, `loadAnalyticsRuntimeMetrics` reconstructs the same arrays from the Analytics Engine SQL API:
82
+ The runtime tier (`hot_shard`, `index_utilization`, `constraint_validator`) reads observed signal off the `LintContext` (`shardTraffic`, `tableScans`, `indexHits`, `tableSamples`). The Studio backend fills those from each shard's durable counters:
83
83
 
84
84
  ```ts
85
- import { fromServerSchema, loadAnalyticsRuntimeMetrics, runAdvisor } from "@lunora/advisor";
85
+ import { fromServerSchema, runAdvisor } from "@lunora/advisor";
86
86
 
87
87
  import schema from "./lunora/schema";
88
88
 
89
- // `client` is an `@lunora/bindings/analytics` SQL client (anything with `query(sql)`).
90
- const metrics = await loadAnalyticsRuntimeMetrics(client, { dataset: "ANALYTICS" });
91
- const findings = runAdvisor({ schema: fromServerSchema(schema), ...metrics }, { source: "runtime" });
89
+ // `shardTraffic` / `tableScans` / `indexHits` / `tableSamples` come from wherever
90
+ // you read your shards' durable counters the Studio backend reads its own.
91
+ const findings = runAdvisor({ schema: fromServerSchema(schema), shardTraffic, tableScans, indexHits, tableSamples }, { source: "runtime" });
92
92
  ```
93
93
 
94
+ An Analytics-Engine-backed alternative feeder (querying AE SQL for `lunora.index.hit`/`lunora.shard.request`/`lunora.table.scan` events instead of the in-DO counters) was quarantined off the package root: nothing in the runtime ever writes those AE events, so `shardTraffic` and `tableScans` always came back empty. `indexHits` came back empty too, unless the caller passed `declaredIndexes` — then it listed every declared index with `reads: 0`, a real zero-reads fact rather than a stand-in for "no data". The `AnalyticsMetricsOptions` / `AnalyticsMetricsSource` / `AnalyticsRuntimeMetrics` types it would have produced are still exported from `@lunora/advisor` — they describe the still-valid, still-optional shape of `runAdvisor`'s runtime input — but the loader function itself is not, until something actually emits those events.
95
+
94
96
  A missing metric degrades to an empty array rather than throwing, so a partially configured read path still returns what it can.
95
97
 
96
98
  ### Observability map (score, coverage, baseline)
package/dist/index.d.mts CHANGED
@@ -367,6 +367,36 @@ interface AdvisorFlagSecurityDefault {
367
367
  /** 1-based line of the `ctx.flags.boolean` call, or `0` when unknown. */
368
368
  line: number;
369
369
  }
370
+ /**
371
+ * One function's observed call/error/latency volume over the window — the input
372
+ * a runtime reliability lint (`error_rate_outlier`, and a future latency
373
+ * counterpart) reads.
374
+ *
375
+ * Not a new collection path: this is the same `FunctionCallStat` row shape
376
+ * `__lunora_admin__:getFunctionStats` already returns and the studio's Advisors
377
+ * panel already fetches (today only its `scannedTables` field feeds
378
+ * `AdvisorTableScan`/`context.tableScans`); this type carries the `calls`/
379
+ * `errors`/`maxDurationMs` columns of that same row instead. Prototype (plan
380
+ * 248) — see `plans/248-runtime-lints-design.md` for the reachability finding
381
+ * and the open scope/threshold questions before productizing further.
382
+ *
383
+ * Scope caveat: `getFunctionStats` reads one shard's durable `__lunora_metrics`
384
+ * table with no cross-shard fan-out (unlike `AdvisorShardTraffic`, which
385
+ * `orchestrateShardTraffic` sums across every shard). A caller feeding this from
386
+ * a sharded function's single selected shard is reporting that shard's rate, not
387
+ * the function's deployment-wide rate — same scope `lunora insights` itself
388
+ * already operates at.
389
+ */
390
+ interface AdvisorFunctionMetrics {
391
+ /** Total dispatches of `path` over the observed window. */
392
+ calls: number;
393
+ /** Dispatches that threw, over the same window. */
394
+ errors: number;
395
+ /** Slowest single dispatch, ms, over the same window. */
396
+ maxDurationMs: number;
397
+ /** The function's registered path (`<file>:<exportName>`). */
398
+ path: string;
399
+ }
370
400
  /**
371
401
  * One `withGeoIndex("name", …)` read discovered in a function body — the use-side
372
402
  * input the `geo_index_unused` lint cross-references against the declared geo
@@ -881,8 +911,23 @@ interface AdvisorPrivilegedDispatch {
881
911
  * feeder; runtime callers don't supply it, so the lints find nothing there.
882
912
  */
883
913
  interface AdvisorProcedureProtection {
884
- /** `true` when the handler references `ctx.mail` / `ctx.email` (sends mail). */
885
- callsMail: boolean;
914
+ /**
915
+ * `true` when the feeder could read the handler body statically (an inline
916
+ * function expression/arrow, or a same-file identifier resolved to one);
917
+ * `false` for a genuinely cross-file handler, in which case every
918
+ * behavioural fact below is `undefined` rather than a proven "not observed".
919
+ * Optional so a feeder predating this field (or a runtime caller) is treated
920
+ * as "unknown" rather than asserting the handler was readable.
921
+ */
922
+ analyzableBody?: boolean;
923
+ /**
924
+ * `true` when the handler references `ctx.mail` / `ctx.email` (sends mail),
925
+ * `undefined` when the feeder couldn't read the handler body (a genuinely
926
+ * cross-file handler). `user_creating_mutation_without_captcha` treats
927
+ * `undefined` the same as `true` — fail-closed, since an unreadable handler
928
+ * might well send mail.
929
+ */
930
+ callsMail?: boolean;
886
931
  /** `true` when the handler emits a structured observability event (`ctx.log` / `ctx.span` / `ctx.trace`). */
887
932
  emitsEvent?: boolean;
888
933
  /**
@@ -895,8 +940,14 @@ interface AdvisorProcedureProtection {
895
940
  exemptReason?: string;
896
941
  /** The exported binding name of the procedure (e.g. `signUp`). */
897
942
  exportName: string;
898
- /** `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). Read by the privileged-fanout lint, paired with public visibility and no rate limit. */
899
- fanOut: boolean;
943
+ /**
944
+ * `true` when the handler fans work out to a privileged, cost-bearing dispatch
945
+ * surface (scheduler `runAfter`/`runAt`, a queue producer send, or a workflow
946
+ * create). Read by the privileged-fanout lint, paired with public visibility
947
+ * and no rate limit. `undefined` when the feeder couldn't read the handler
948
+ * body — the lint treats that as `true` (fail-closed).
949
+ */
950
+ fanOut?: boolean;
900
951
  /** Source file relative to the lunora dir, no extension. */
901
952
  file: string;
902
953
  /** `true` when the handler wraps work in `try`/`catch`. */
@@ -913,20 +964,33 @@ interface AdvisorProcedureProtection {
913
964
  hasEmailArg?: boolean;
914
965
  /** Registration kind — `query` is read-only; `mutation`/`action` are write-shaped. */
915
966
  kind: "action" | "mutation" | "query";
916
- /** `true` when the handler runs any AI generation, bounded or not. */
917
- runsAiGeneration?: boolean;
918
967
  /** `true` when the handler reaches an outbound surface (`ctx.fetch`, mail, queues, storage, sql, ai, …) that can fail. */
919
968
  reachesOutbound?: boolean;
969
+ /** `true` when the handler runs any AI generation, bounded or not. */
970
+ runsAiGeneration?: boolean;
920
971
  /** `true` when the handler throws a bare `new Error(...)` rather than a coded `LunoraError`. */
921
972
  throwsBareError?: boolean;
922
- /** `true` when the handler runs an AI generation (`generateText`/`streamText`/`generateObject`/`streamObject`) with no `maxOutputTokens` bound. Read by the `ai_unbounded_generation_public` lint (paired with public visibility). */
923
- unboundedAiGeneration: boolean;
973
+ /**
974
+ * `true` when the handler runs an AI generation (`generateText`/`streamText`/
975
+ * `generateObject`/`streamObject`) with no `maxOutputTokens` bound. Read by
976
+ * the `ai_unbounded_generation_public` lint (paired with public visibility).
977
+ * `undefined` when the feeder couldn't read the handler body — the lint
978
+ * treats that as `true` (fail-closed), distinct from the call-level opaque-
979
+ * config case, which the feeder always reports as `false` (fail-open by
980
+ * design — see `isUnboundedAiGeneration`).
981
+ */
982
+ unboundedAiGeneration?: boolean;
924
983
  /** `true` when the chain carries `.use(verifyTurnstile(...))` or a `protectPublic({ captcha })` bundle. */
925
984
  usesCaptcha: boolean;
926
985
  /** `true` when the chain carries `.use(emailGateMiddleware(...))` from `@lunora/auth`. Read by the `signup_mutation_without_disposable_gating` lint (paired with public visibility + a user-table write). */
927
986
  usesEmailGate: boolean;
928
- /** `true` when the handler calls `ctx.db.insertManyUnsafe(...)`, which bypasses validators and triggers. Read by the `insert_many_unsafe_user_data` lint (paired with public visibility). */
929
- usesInsertManyUnsafe: boolean;
987
+ /**
988
+ * `true` when the handler calls `ctx.db.insertManyUnsafe(...)`, which bypasses
989
+ * validators and triggers. Read by the `insert_many_unsafe_user_data` lint
990
+ * (paired with public visibility). `undefined` when the feeder couldn't read
991
+ * the handler body — the lint treats that as `true` (fail-closed).
992
+ */
993
+ usesInsertManyUnsafe?: boolean;
930
994
  /** `true` when the chain carries `.use(mask(...))`. */
931
995
  usesMask: boolean;
932
996
  /** `true` when the chain carries `.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle. */
@@ -935,8 +999,14 @@ interface AdvisorProcedureProtection {
935
999
  usesRls: boolean;
936
1000
  /** `"internal"` when the procedure uses `internalQuery` / `internalMutation` / `internalAction`. */
937
1001
  visibility: "internal" | "public";
938
- /** `true` when the handler inserts into a user/session/account-shaped table. */
939
- writesUserTable: boolean;
1002
+ /**
1003
+ * `true` when the handler inserts into a user/session/account-shaped table,
1004
+ * `undefined` when the feeder couldn't read the handler body (a genuinely
1005
+ * cross-file handler). `signup_mutation_without_disposable_gating` and
1006
+ * `user_creating_mutation_without_captcha` treat `undefined` the same as
1007
+ * `true` — fail-closed, since an unreadable handler might well write one.
1008
+ */
1009
+ writesUserTable?: boolean;
940
1010
  }
941
1011
  /**
942
1012
  * One query read discovered in a function body — the input the
@@ -1425,6 +1495,14 @@ interface AdvisorStorageKeyAccess {
1425
1495
  line: number;
1426
1496
  /** The bucket method invoked with the arg-derived key, e.g. `get` / `put` / `delete` / `download`. */
1427
1497
  method: string;
1498
+ /**
1499
+ * Visibility of the enclosing procedure. `internal` procedures are not
1500
+ * reachable by a caller, so the "any caller can read/overwrite/delete
1501
+ * another user's object" premise does not hold there and the finding drops
1502
+ * to `INFO` (mirrors `AdvisorOwnerFieldWrite.visibility`). `undefined` when
1503
+ * the feeder could not attribute the access to a registered procedure.
1504
+ */
1505
+ visibility?: "internal" | "public";
1428
1506
  }
1429
1507
  /**
1430
1508
  * One tracked `ctx.storage.&lt;bucket>.&lt;method>(...)` upload/signing call — the
@@ -1792,6 +1870,16 @@ interface LintContext {
1792
1870
  * codegen feeder; absent for runtime callers, where the lint finds nothing.
1793
1871
  */
1794
1872
  flagSecurityDefaults?: ReadonlyArray<AdvisorFlagSecurityDefault>;
1873
+ /**
1874
+ * Per-function call/error/latency volume observed over the window — the
1875
+ * `error_rate_outlier` runtime lint's input (prototype, plan 248). Sourced
1876
+ * from the same `FunctionCallStat` rows `context.tableScans` already draws
1877
+ * `scannedTables` from (`__lunora_admin__:getFunctionStats`), scoped to
1878
+ * whichever shard the caller queried — see {@link AdvisorFunctionMetrics}.
1879
+ * Supplied by the studio backend; absent for static callers, where the lint
1880
+ * finds nothing.
1881
+ */
1882
+ functionMetrics?: ReadonlyArray<AdvisorFunctionMetrics>;
1795
1883
  /**
1796
1884
  * `withGeoIndex("name", …)` reads discovered in function bodies — the use-side
1797
1885
  * input the `geo_index_unused` lint cross-references against the declared geo
@@ -2202,29 +2290,6 @@ interface AnalyticsMetricsSource {
2202
2290
  rows: ReadonlyArray<Record<string, unknown>>;
2203
2291
  }>;
2204
2292
  }
2205
- /**
2206
- * The AE event-name + dimension-column contract the runtime writes and this
2207
- * reader reads. `blob1` is the event name; dimensions start at `blob2`.
2208
- */
2209
- declare const AE_METRIC_EVENTS: {
2210
- /** `lunora.index.hit` — one row per `(table, index)` use. `blob2`=table, `blob3`=index. */
2211
- readonly indexHit: {
2212
- readonly event: "lunora.index.hit";
2213
- readonly index: "blob3";
2214
- readonly table: "blob2";
2215
- };
2216
- /** `lunora.shard.request` — one row per shard dispatch. `blob2`=shardKey, `blob3`=group. */
2217
- readonly shardRequest: {
2218
- readonly event: "lunora.shard.request";
2219
- readonly group: "blob3";
2220
- readonly shardKey: "blob2";
2221
- };
2222
- /** `lunora.table.scan` — one row per full-scan. `blob2`=table. */
2223
- readonly tableScan: {
2224
- readonly event: "lunora.table.scan";
2225
- readonly table: "blob2";
2226
- };
2227
- };
2228
2293
  /** Options for the AE-backed runtime-metrics feeder. */
2229
2294
  interface AnalyticsMetricsOptions {
2230
2295
  /** The AE dataset (the wrangler `analytics_engine_datasets[].dataset`) to read from. */
@@ -2253,20 +2318,6 @@ interface AnalyticsRuntimeMetrics {
2253
2318
  shardTraffic: AdvisorShardTraffic[];
2254
2319
  tableScans: AdvisorTableScan[];
2255
2320
  }
2256
- /**
2257
- * Reconstruct the runtime-lint input arrays (`shardTraffic` / `tableScans` /
2258
- * `indexHits`) from the Analytics Engine SQL API. The three reads run
2259
- * concurrently; each degrades to an empty array on a query failure, so a
2260
- * partially-misconfigured read path still returns what it can.
2261
- *
2262
- * Feed the result into a {@link LintContext} alongside the declared schema:
2263
- *
2264
- * ```ts
2265
- * const metrics = await loadAnalyticsRuntimeMetrics(client, { dataset: "ANALYTICS" });
2266
- * runAdvisor({ schema, ...metrics }, { source: "runtime" });
2267
- * ```
2268
- */
2269
- declare const loadAnalyticsRuntimeMetrics: (source: AnalyticsMetricsSource, options: AnalyticsMetricsOptions) => Promise<AnalyticsRuntimeMetrics>;
2270
2321
  /**
2271
2322
  * Disambiguate {@link Finding}s that share a `cacheKey`.
2272
2323
  *
@@ -2305,6 +2356,29 @@ declare const dedupeCacheKeys: (findings: ReadonlyArray<Finding>) => Finding[];
2305
2356
  * additional read. When a sample is truncated, findings note the caveat.
2306
2357
  */
2307
2358
  declare const constraintValidator: Lint;
2359
+ /**
2360
+ * `error_rate_outlier` — flag a function whose observed error rate is high
2361
+ * enough to be a genuine reliability signal, not noise.
2362
+ *
2363
+ * PROTOTYPE (plan 248): the read side of the runtime-lint category is thin (3
2364
+ * lints, all traffic/conflict/index shaped) next to the static category's 96.
2365
+ * `lunora insights` already collects and ranks this exact signal
2366
+ * (`errorHotspots`) for a human report; this lint closes the loop by feeding
2367
+ * the same per-function `errors`/`calls` counts into the `lunora advisor` gate
2368
+ * so a CI run can flag a genuinely failing function, not just a report a human
2369
+ * has to remember to read.
2370
+ *
2371
+ * The per-function volume comes from the runtime feeder (`context.
2372
+ * functionMetrics`): the studio backend reads `__lunora_admin__:getFunctionStats`
2373
+ * for the shard in scope — the SAME rows `context.tableScans` already draws
2374
+ * `scannedTables` from, just projecting `errors`/`calls` instead. See
2375
+ * `AdvisorFunctionMetrics` for the scope caveat (one shard, not fanned out
2376
+ * cross-shard like `context.shardTraffic`) and `plans/248-runtime-lints-design.md`
2377
+ * for the reachability finding and open questions this prototype does not
2378
+ * resolve (threshold model, single- vs. cross-shard scope, whether this ships
2379
+ * as its own lint or merges with a future latency counterpart).
2380
+ */
2381
+ declare const errorRateOutlier: Lint;
2308
2382
  /**
2309
2383
  * `hot_shard` — flag a shard whose request share is disproportionately high.
2310
2384
  *
@@ -3242,15 +3316,29 @@ declare const mutatorFullRowReplace: Lint;
3242
3316
  * Flags a non-deterministic API call inside a `query(...)` or `mutation(...)`
3243
3317
  * handler body.
3244
3318
  *
3245
- * Lunora queries and mutations must be **deterministic**: the coordinator may
3246
- * re-run a mutation on optimistic-concurrency (OCC) retry and a query on
3247
- * subscription re-evaluation, so a handler that reads wall-clock time, draws
3248
- * randomness, or hits the network can produce different results on each run —
3249
- * breaking read-your-writes, cache invalidation, and replayable history.
3250
- * `Date.now`, `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues`, and
3251
- * `fetch` are therefore disallowed in query/mutation handlers and belong in an
3252
- * `action(...)`, which runs exactly once and may use ambient/non-deterministic
3253
- * APIs freely (pass the result into a mutation as an argument).
3319
+ * The two procedure kinds do NOT share the same hazard (issue #286).
3320
+ *
3321
+ * A `query` handler genuinely CAN run more than once for the same logical
3322
+ * read: a live subscription re-runs its query whenever a table it reads
3323
+ * changes, so `Date.now()`/`Math.random()`/etc. there can flicker between
3324
+ * re-evaluations this half stays at WARN.
3325
+ *
3326
+ * A `mutation` handler does NOT replay under ordinary dispatch on this
3327
+ * (DO-backed) runtime: a client-issued idempotency key dedups a replayed call
3328
+ * to the **cached** result without re-running the handler, and an
3329
+ * optimistic-concurrency conflict throws back to the caller as an error
3330
+ * rather than triggering an internal retry of the handler. So a mutation
3331
+ * handler body runs at most once per logical write, and `Date.now()` inside
3332
+ * one is stable — the premise this lint used to apply uniformly (borrowed
3333
+ * from Convex's OCC-retries-the-handler model, which does not hold here) was
3334
+ * responsible for 193 of 385 non-INFO findings on one real codebase, all from
3335
+ * the ordinary "stamp `createdAt`" pattern. Dropped to INFO rather than
3336
+ * silenced: a mutation invoked from inside a workflow step or queue consumer
3337
+ * CAN be re-dispatched if the surrounding step/consumer itself replays (a
3338
+ * fresh `ctx.runMutation` call from the step's perspective, not a replay this
3339
+ * lint can distinguish from any other mutation call), so the finding stays as
3340
+ * a breadcrumb rather than disappearing outright. See the "Determinism: what
3341
+ * actually replays" section of the `@lunora/server` docs.
3254
3342
  *
3255
3343
  * This lint runs when the codegen feeder has supplied call evidence
3256
3344
  * (`context.nondeterministicCalls` present); a runtime caller with no evidence
@@ -4350,6 +4438,14 @@ declare const scoreAdvisor: (procedures: ReadonlyArray<AdvisorProcedureProtectio
4350
4438
  * Runs before any rule so a lint can gate on it, and feeds the global weighting:
4351
4439
  * a handler touching identity, mail, or tenant-scoped rows pulls harder on the
4352
4440
  * grade than a plain read. `none` means no signal fired — not "safe".
4441
+ *
4442
+ * A fact reads `undefined` only when the feeder couldn't read the handler body
4443
+ * (a cross-file handler, or — degenerately — a partial payload), and that stays
4444
+ * fail-closed: `level` goes `"high"` the same as a proven `true` would. The
4445
+ * reason text does not, though — asserting "writes an identity table" for a
4446
+ * fact that was never observed would misattribute an unproven claim. A proven
4447
+ * `true` still gets its specific reason; every `undefined` fact collapses into
4448
+ * one shared "could not be read" reason instead of one unproven claim apiece.
4353
4449
  */
4354
4450
  declare const classifySensitivity: (procedure: AdvisorProcedureProtection) => Sensitivity;
4355
4451
  /**
@@ -4383,4 +4479,4 @@ interface RunAdvisorOptions {
4383
4479
  * `static` lints at build time and defer `runtime` lints to a live shard.
4384
4480
  */
4385
4481
  declare const runAdvisor: (context: LintContext, options?: RunAdvisorOptions) => Finding[];
4386
- export { AE_METRIC_EVENTS, ALL_LINTS, type AdvisorAdminRoute, type AdvisorAiRawRun, type AdvisorAiToolSideEffect, type AdvisorArgumentDerivedFetch, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorAuthConfig, type AdvisorBrowserUrlAccess, type AdvisorConfigCall, type AdvisorContainer, type AdvisorContainerKeyAccess, type AdvisorContainerOverride, type AdvisorExportSink, type AdvisorFailOpenGuard, type AdvisorFlagSecurityDefault, type AdvisorGeoIndexUsage, type AdvisorHttpActionGuard, type AdvisorHttpHeaderWrite, type AdvisorHyperdriveCall, type AdvisorIdentityClaimRead, type AdvisorImageDeliveryUrlAccess, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorKvKeyAccess, type AdvisorMailRecipientAccess, type AdvisorMap, type AdvisorMaskProcedure, type AdvisorMaskStrategy, type AdvisorMutatorWrite, type AdvisorNondeterministicCall, type AdvisorNormalizeIdAuthorization, type AdvisorNotifyCall, type AdvisorNotifyConfig, type AdvisorOwnerFieldWrite, type AdvisorPaymentWebhook, type AdvisorPrivilegedDispatch, type AdvisorProcedureProtection, type AdvisorQueryRead, type AdvisorQueue, type AdvisorQueueTuning, type AdvisorR2sqlCall, type AdvisorRatelimitKeySelector, type AdvisorRawRowReturn, type AdvisorRelation, type AdvisorRelationLoad, type AdvisorRlsProcedure, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSoftDeleteRead, type AdvisorSqlInterpolation, type AdvisorStorageKeyAccess, type AdvisorStorageUpload, type AdvisorTable, type AdvisorTableSample, type AdvisorTableScan, type AdvisorVectorNamespaceAccess, type AdvisorWorkflow, type AdvisorWorkflowCall, type AdvisorWranglerVariable, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type BaselineComparison, type Category, type CheckResult, type Coverage, type Facing, type Finding, type Grade, type Level, type Lint, type LintContext, type LintSource, MAP_VERSION, type MapSummary, type ProcedureDelta, type ProcedureScore, type ProjectScore, RUNTIME_LINTS, RunAdvisorOptions, STATIC_LINTS, type ScoreAdvisorOptions, type Sensitivity, type SensitivityLevel, actionFetchSsrf, actionWithoutErrorHandling, adminRouteWithoutGuard, aiRawRunEscapeHatch, aiRunWithoutLogging, aiToolSideEffectPromptInjection, aiUnboundedGenerationPublic, allowUnauthenticatedShardAccessEnabled, authApiCallWithoutHeaders, authCsrfCheckDisabled, authEmailVerificationDisabled, authScimWithoutTransactions, authSecureCookiesDisabled, authSessionFreshageZero, authTrustedOriginsWildcard, browserAllowPrivateTargets, browserUserUrlWithoutAllowlist, byCodepoint, circularFk, classifySensitivity, compareToBaseline, constraintValidator, containerInstanceKeyFromUserInput, containerOversizedInstance, containerPublicInternet, containerRuntimeEgressRelaxation, containerStartEnableInternetOverride, dedupeCacheKeys, duplicateIndex, emptyIndex, errorWithoutCatalog, exportSinkMisconfigured, externalSourceIncrementalNoDeletePath, externalSourceOnGlobal, externalSourceUnscoped, filterOnPrimaryKey, filterWithoutIndex, flagGatesSecurityWithUnsafeDefault, fromServerSchema, geoIndexFieldNotGeopoint, geoIndexUnused, gradeFromScore, hardcodedSecret, hotShard, httpActionMissingAuthGuard, httpActionResponseHeaderInjection, hyperdriveOutsideAction, identityUndeclaredClaimTrusted, imagesUrlSourceFromUserInput, indexReferencesUnknownField, indexUtilization, insertManyUnsafeUserData, kvUnscopedUserKeyIdor, loadAnalyticsRuntimeMetrics, mailInboundDispatchWithoutVerify, mailRecipientFromRequestInput, maskUncoveredPiiColumn, maskWeakHashStrategyOnPii, maskedRelationLeakViaWith, mutatorFullRowReplace, nondeterministicQueryMutation, normalizeIdUsedAsAuthorization, notifyMissingPushConfig, notifySendOutsideAction, outputProjectionMissingOnPublicRead, ownerFieldFromArgsNotAuth, parseAdvisorMap, paymentCreateWithoutAuthorize, paymentWebhookWideTolerance, plaintextSecretInWranglerVariables, policyReferencesUnknownTable, privilegedDispatchUnvalidatedPayload, privilegedFanoutFromPublicProcedure, procedureWithoutStructuredEvent, publicArgumentUsesAny, publicMutationWithoutRatelimit, publicTableRlsOptoutConfusion, queueWithoutDlq, r2sqlOutsideAction, ratelimitDefaultMemoryStore, ratelimitKeySpoofableOrGlobal, ratelimitMiddlewareFailOpen, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, scoreAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, signupMutationWithoutDisposableGating, softDeleteIncludeDeletedFromArgs, sqlInjectionRisk, storageGenerateUploadUrlNoContentTypePin, storageKeyFromUserArgs, storagePresignedUrlForPrivateContent, storageUploadWithoutContentTypeAllowlist, storageUploadWithoutMaxSize, tableWithoutInsert, ttlFieldNotTimestamp, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, unrestrictedWhereBranch, userCreatingMutationWithoutCaptcha, vectorsNamespaceFromUserInput, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
4482
+ export { ALL_LINTS, type AdvisorAdminRoute, type AdvisorAiRawRun, type AdvisorAiToolSideEffect, type AdvisorArgumentDerivedFetch, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorAuthConfig, type AdvisorBrowserUrlAccess, type AdvisorConfigCall, type AdvisorContainer, type AdvisorContainerKeyAccess, type AdvisorContainerOverride, type AdvisorExportSink, type AdvisorFailOpenGuard, type AdvisorFlagSecurityDefault, type AdvisorFunctionMetrics, type AdvisorGeoIndexUsage, type AdvisorHttpActionGuard, type AdvisorHttpHeaderWrite, type AdvisorHyperdriveCall, type AdvisorIdentityClaimRead, type AdvisorImageDeliveryUrlAccess, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorKvKeyAccess, type AdvisorMailRecipientAccess, type AdvisorMap, type AdvisorMaskProcedure, type AdvisorMaskStrategy, type AdvisorMutatorWrite, type AdvisorNondeterministicCall, type AdvisorNormalizeIdAuthorization, type AdvisorNotifyCall, type AdvisorNotifyConfig, type AdvisorOwnerFieldWrite, type AdvisorPaymentWebhook, type AdvisorPrivilegedDispatch, type AdvisorProcedureProtection, type AdvisorQueryRead, type AdvisorQueue, type AdvisorQueueTuning, type AdvisorR2sqlCall, type AdvisorRatelimitKeySelector, type AdvisorRawRowReturn, type AdvisorRelation, type AdvisorRelationLoad, type AdvisorRlsProcedure, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSoftDeleteRead, type AdvisorSqlInterpolation, type AdvisorStorageKeyAccess, type AdvisorStorageUpload, type AdvisorTable, type AdvisorTableSample, type AdvisorTableScan, type AdvisorVectorNamespaceAccess, type AdvisorWorkflow, type AdvisorWorkflowCall, type AdvisorWranglerVariable, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type BaselineComparison, type Category, type CheckResult, type Coverage, type Facing, type Finding, type Grade, type Level, type Lint, type LintContext, type LintSource, MAP_VERSION, type MapSummary, type ProcedureDelta, type ProcedureScore, type ProjectScore, RUNTIME_LINTS, RunAdvisorOptions, STATIC_LINTS, type ScoreAdvisorOptions, type Sensitivity, type SensitivityLevel, actionFetchSsrf, actionWithoutErrorHandling, adminRouteWithoutGuard, aiRawRunEscapeHatch, aiRunWithoutLogging, aiToolSideEffectPromptInjection, aiUnboundedGenerationPublic, allowUnauthenticatedShardAccessEnabled, authApiCallWithoutHeaders, authCsrfCheckDisabled, authEmailVerificationDisabled, authScimWithoutTransactions, authSecureCookiesDisabled, authSessionFreshageZero, authTrustedOriginsWildcard, browserAllowPrivateTargets, browserUserUrlWithoutAllowlist, byCodepoint, circularFk, classifySensitivity, compareToBaseline, constraintValidator, containerInstanceKeyFromUserInput, containerOversizedInstance, containerPublicInternet, containerRuntimeEgressRelaxation, containerStartEnableInternetOverride, dedupeCacheKeys, duplicateIndex, emptyIndex, errorRateOutlier, errorWithoutCatalog, exportSinkMisconfigured, externalSourceIncrementalNoDeletePath, externalSourceOnGlobal, externalSourceUnscoped, filterOnPrimaryKey, filterWithoutIndex, flagGatesSecurityWithUnsafeDefault, fromServerSchema, geoIndexFieldNotGeopoint, geoIndexUnused, gradeFromScore, hardcodedSecret, hotShard, httpActionMissingAuthGuard, httpActionResponseHeaderInjection, hyperdriveOutsideAction, identityUndeclaredClaimTrusted, imagesUrlSourceFromUserInput, indexReferencesUnknownField, indexUtilization, insertManyUnsafeUserData, kvUnscopedUserKeyIdor, mailInboundDispatchWithoutVerify, mailRecipientFromRequestInput, maskUncoveredPiiColumn, maskWeakHashStrategyOnPii, maskedRelationLeakViaWith, mutatorFullRowReplace, nondeterministicQueryMutation, normalizeIdUsedAsAuthorization, notifyMissingPushConfig, notifySendOutsideAction, outputProjectionMissingOnPublicRead, ownerFieldFromArgsNotAuth, parseAdvisorMap, paymentCreateWithoutAuthorize, paymentWebhookWideTolerance, plaintextSecretInWranglerVariables, policyReferencesUnknownTable, privilegedDispatchUnvalidatedPayload, privilegedFanoutFromPublicProcedure, procedureWithoutStructuredEvent, publicArgumentUsesAny, publicMutationWithoutRatelimit, publicTableRlsOptoutConfusion, queueWithoutDlq, r2sqlOutsideAction, ratelimitDefaultMemoryStore, ratelimitKeySpoofableOrGlobal, ratelimitMiddlewareFailOpen, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, scoreAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, signupMutationWithoutDisposableGating, softDeleteIncludeDeletedFromArgs, sqlInjectionRisk, storageGenerateUploadUrlNoContentTypePin, storageKeyFromUserArgs, storagePresignedUrlForPrivateContent, storageUploadWithoutContentTypeAllowlist, storageUploadWithoutMaxSize, tableWithoutInsert, ttlFieldNotTimestamp, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, unrestrictedWhereBranch, userCreatingMutationWithoutCaptcha, vectorsNamespaceFromUserInput, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
package/dist/index.d.ts CHANGED
@@ -367,6 +367,36 @@ interface AdvisorFlagSecurityDefault {
367
367
  /** 1-based line of the `ctx.flags.boolean` call, or `0` when unknown. */
368
368
  line: number;
369
369
  }
370
+ /**
371
+ * One function's observed call/error/latency volume over the window — the input
372
+ * a runtime reliability lint (`error_rate_outlier`, and a future latency
373
+ * counterpart) reads.
374
+ *
375
+ * Not a new collection path: this is the same `FunctionCallStat` row shape
376
+ * `__lunora_admin__:getFunctionStats` already returns and the studio's Advisors
377
+ * panel already fetches (today only its `scannedTables` field feeds
378
+ * `AdvisorTableScan`/`context.tableScans`); this type carries the `calls`/
379
+ * `errors`/`maxDurationMs` columns of that same row instead. Prototype (plan
380
+ * 248) — see `plans/248-runtime-lints-design.md` for the reachability finding
381
+ * and the open scope/threshold questions before productizing further.
382
+ *
383
+ * Scope caveat: `getFunctionStats` reads one shard's durable `__lunora_metrics`
384
+ * table with no cross-shard fan-out (unlike `AdvisorShardTraffic`, which
385
+ * `orchestrateShardTraffic` sums across every shard). A caller feeding this from
386
+ * a sharded function's single selected shard is reporting that shard's rate, not
387
+ * the function's deployment-wide rate — same scope `lunora insights` itself
388
+ * already operates at.
389
+ */
390
+ interface AdvisorFunctionMetrics {
391
+ /** Total dispatches of `path` over the observed window. */
392
+ calls: number;
393
+ /** Dispatches that threw, over the same window. */
394
+ errors: number;
395
+ /** Slowest single dispatch, ms, over the same window. */
396
+ maxDurationMs: number;
397
+ /** The function's registered path (`<file>:<exportName>`). */
398
+ path: string;
399
+ }
370
400
  /**
371
401
  * One `withGeoIndex("name", …)` read discovered in a function body — the use-side
372
402
  * input the `geo_index_unused` lint cross-references against the declared geo
@@ -881,8 +911,23 @@ interface AdvisorPrivilegedDispatch {
881
911
  * feeder; runtime callers don't supply it, so the lints find nothing there.
882
912
  */
883
913
  interface AdvisorProcedureProtection {
884
- /** `true` when the handler references `ctx.mail` / `ctx.email` (sends mail). */
885
- callsMail: boolean;
914
+ /**
915
+ * `true` when the feeder could read the handler body statically (an inline
916
+ * function expression/arrow, or a same-file identifier resolved to one);
917
+ * `false` for a genuinely cross-file handler, in which case every
918
+ * behavioural fact below is `undefined` rather than a proven "not observed".
919
+ * Optional so a feeder predating this field (or a runtime caller) is treated
920
+ * as "unknown" rather than asserting the handler was readable.
921
+ */
922
+ analyzableBody?: boolean;
923
+ /**
924
+ * `true` when the handler references `ctx.mail` / `ctx.email` (sends mail),
925
+ * `undefined` when the feeder couldn't read the handler body (a genuinely
926
+ * cross-file handler). `user_creating_mutation_without_captcha` treats
927
+ * `undefined` the same as `true` — fail-closed, since an unreadable handler
928
+ * might well send mail.
929
+ */
930
+ callsMail?: boolean;
886
931
  /** `true` when the handler emits a structured observability event (`ctx.log` / `ctx.span` / `ctx.trace`). */
887
932
  emitsEvent?: boolean;
888
933
  /**
@@ -895,8 +940,14 @@ interface AdvisorProcedureProtection {
895
940
  exemptReason?: string;
896
941
  /** The exported binding name of the procedure (e.g. `signUp`). */
897
942
  exportName: string;
898
- /** `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). Read by the privileged-fanout lint, paired with public visibility and no rate limit. */
899
- fanOut: boolean;
943
+ /**
944
+ * `true` when the handler fans work out to a privileged, cost-bearing dispatch
945
+ * surface (scheduler `runAfter`/`runAt`, a queue producer send, or a workflow
946
+ * create). Read by the privileged-fanout lint, paired with public visibility
947
+ * and no rate limit. `undefined` when the feeder couldn't read the handler
948
+ * body — the lint treats that as `true` (fail-closed).
949
+ */
950
+ fanOut?: boolean;
900
951
  /** Source file relative to the lunora dir, no extension. */
901
952
  file: string;
902
953
  /** `true` when the handler wraps work in `try`/`catch`. */
@@ -913,20 +964,33 @@ interface AdvisorProcedureProtection {
913
964
  hasEmailArg?: boolean;
914
965
  /** Registration kind — `query` is read-only; `mutation`/`action` are write-shaped. */
915
966
  kind: "action" | "mutation" | "query";
916
- /** `true` when the handler runs any AI generation, bounded or not. */
917
- runsAiGeneration?: boolean;
918
967
  /** `true` when the handler reaches an outbound surface (`ctx.fetch`, mail, queues, storage, sql, ai, …) that can fail. */
919
968
  reachesOutbound?: boolean;
969
+ /** `true` when the handler runs any AI generation, bounded or not. */
970
+ runsAiGeneration?: boolean;
920
971
  /** `true` when the handler throws a bare `new Error(...)` rather than a coded `LunoraError`. */
921
972
  throwsBareError?: boolean;
922
- /** `true` when the handler runs an AI generation (`generateText`/`streamText`/`generateObject`/`streamObject`) with no `maxOutputTokens` bound. Read by the `ai_unbounded_generation_public` lint (paired with public visibility). */
923
- unboundedAiGeneration: boolean;
973
+ /**
974
+ * `true` when the handler runs an AI generation (`generateText`/`streamText`/
975
+ * `generateObject`/`streamObject`) with no `maxOutputTokens` bound. Read by
976
+ * the `ai_unbounded_generation_public` lint (paired with public visibility).
977
+ * `undefined` when the feeder couldn't read the handler body — the lint
978
+ * treats that as `true` (fail-closed), distinct from the call-level opaque-
979
+ * config case, which the feeder always reports as `false` (fail-open by
980
+ * design — see `isUnboundedAiGeneration`).
981
+ */
982
+ unboundedAiGeneration?: boolean;
924
983
  /** `true` when the chain carries `.use(verifyTurnstile(...))` or a `protectPublic({ captcha })` bundle. */
925
984
  usesCaptcha: boolean;
926
985
  /** `true` when the chain carries `.use(emailGateMiddleware(...))` from `@lunora/auth`. Read by the `signup_mutation_without_disposable_gating` lint (paired with public visibility + a user-table write). */
927
986
  usesEmailGate: boolean;
928
- /** `true` when the handler calls `ctx.db.insertManyUnsafe(...)`, which bypasses validators and triggers. Read by the `insert_many_unsafe_user_data` lint (paired with public visibility). */
929
- usesInsertManyUnsafe: boolean;
987
+ /**
988
+ * `true` when the handler calls `ctx.db.insertManyUnsafe(...)`, which bypasses
989
+ * validators and triggers. Read by the `insert_many_unsafe_user_data` lint
990
+ * (paired with public visibility). `undefined` when the feeder couldn't read
991
+ * the handler body — the lint treats that as `true` (fail-closed).
992
+ */
993
+ usesInsertManyUnsafe?: boolean;
930
994
  /** `true` when the chain carries `.use(mask(...))`. */
931
995
  usesMask: boolean;
932
996
  /** `true` when the chain carries `.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle. */
@@ -935,8 +999,14 @@ interface AdvisorProcedureProtection {
935
999
  usesRls: boolean;
936
1000
  /** `"internal"` when the procedure uses `internalQuery` / `internalMutation` / `internalAction`. */
937
1001
  visibility: "internal" | "public";
938
- /** `true` when the handler inserts into a user/session/account-shaped table. */
939
- writesUserTable: boolean;
1002
+ /**
1003
+ * `true` when the handler inserts into a user/session/account-shaped table,
1004
+ * `undefined` when the feeder couldn't read the handler body (a genuinely
1005
+ * cross-file handler). `signup_mutation_without_disposable_gating` and
1006
+ * `user_creating_mutation_without_captcha` treat `undefined` the same as
1007
+ * `true` — fail-closed, since an unreadable handler might well write one.
1008
+ */
1009
+ writesUserTable?: boolean;
940
1010
  }
941
1011
  /**
942
1012
  * One query read discovered in a function body — the input the
@@ -1425,6 +1495,14 @@ interface AdvisorStorageKeyAccess {
1425
1495
  line: number;
1426
1496
  /** The bucket method invoked with the arg-derived key, e.g. `get` / `put` / `delete` / `download`. */
1427
1497
  method: string;
1498
+ /**
1499
+ * Visibility of the enclosing procedure. `internal` procedures are not
1500
+ * reachable by a caller, so the "any caller can read/overwrite/delete
1501
+ * another user's object" premise does not hold there and the finding drops
1502
+ * to `INFO` (mirrors `AdvisorOwnerFieldWrite.visibility`). `undefined` when
1503
+ * the feeder could not attribute the access to a registered procedure.
1504
+ */
1505
+ visibility?: "internal" | "public";
1428
1506
  }
1429
1507
  /**
1430
1508
  * One tracked `ctx.storage.&lt;bucket>.&lt;method>(...)` upload/signing call — the
@@ -1792,6 +1870,16 @@ interface LintContext {
1792
1870
  * codegen feeder; absent for runtime callers, where the lint finds nothing.
1793
1871
  */
1794
1872
  flagSecurityDefaults?: ReadonlyArray<AdvisorFlagSecurityDefault>;
1873
+ /**
1874
+ * Per-function call/error/latency volume observed over the window — the
1875
+ * `error_rate_outlier` runtime lint's input (prototype, plan 248). Sourced
1876
+ * from the same `FunctionCallStat` rows `context.tableScans` already draws
1877
+ * `scannedTables` from (`__lunora_admin__:getFunctionStats`), scoped to
1878
+ * whichever shard the caller queried — see {@link AdvisorFunctionMetrics}.
1879
+ * Supplied by the studio backend; absent for static callers, where the lint
1880
+ * finds nothing.
1881
+ */
1882
+ functionMetrics?: ReadonlyArray<AdvisorFunctionMetrics>;
1795
1883
  /**
1796
1884
  * `withGeoIndex("name", …)` reads discovered in function bodies — the use-side
1797
1885
  * input the `geo_index_unused` lint cross-references against the declared geo
@@ -2202,29 +2290,6 @@ interface AnalyticsMetricsSource {
2202
2290
  rows: ReadonlyArray<Record<string, unknown>>;
2203
2291
  }>;
2204
2292
  }
2205
- /**
2206
- * The AE event-name + dimension-column contract the runtime writes and this
2207
- * reader reads. `blob1` is the event name; dimensions start at `blob2`.
2208
- */
2209
- declare const AE_METRIC_EVENTS: {
2210
- /** `lunora.index.hit` — one row per `(table, index)` use. `blob2`=table, `blob3`=index. */
2211
- readonly indexHit: {
2212
- readonly event: "lunora.index.hit";
2213
- readonly index: "blob3";
2214
- readonly table: "blob2";
2215
- };
2216
- /** `lunora.shard.request` — one row per shard dispatch. `blob2`=shardKey, `blob3`=group. */
2217
- readonly shardRequest: {
2218
- readonly event: "lunora.shard.request";
2219
- readonly group: "blob3";
2220
- readonly shardKey: "blob2";
2221
- };
2222
- /** `lunora.table.scan` — one row per full-scan. `blob2`=table. */
2223
- readonly tableScan: {
2224
- readonly event: "lunora.table.scan";
2225
- readonly table: "blob2";
2226
- };
2227
- };
2228
2293
  /** Options for the AE-backed runtime-metrics feeder. */
2229
2294
  interface AnalyticsMetricsOptions {
2230
2295
  /** The AE dataset (the wrangler `analytics_engine_datasets[].dataset`) to read from. */
@@ -2253,20 +2318,6 @@ interface AnalyticsRuntimeMetrics {
2253
2318
  shardTraffic: AdvisorShardTraffic[];
2254
2319
  tableScans: AdvisorTableScan[];
2255
2320
  }
2256
- /**
2257
- * Reconstruct the runtime-lint input arrays (`shardTraffic` / `tableScans` /
2258
- * `indexHits`) from the Analytics Engine SQL API. The three reads run
2259
- * concurrently; each degrades to an empty array on a query failure, so a
2260
- * partially-misconfigured read path still returns what it can.
2261
- *
2262
- * Feed the result into a {@link LintContext} alongside the declared schema:
2263
- *
2264
- * ```ts
2265
- * const metrics = await loadAnalyticsRuntimeMetrics(client, { dataset: "ANALYTICS" });
2266
- * runAdvisor({ schema, ...metrics }, { source: "runtime" });
2267
- * ```
2268
- */
2269
- declare const loadAnalyticsRuntimeMetrics: (source: AnalyticsMetricsSource, options: AnalyticsMetricsOptions) => Promise<AnalyticsRuntimeMetrics>;
2270
2321
  /**
2271
2322
  * Disambiguate {@link Finding}s that share a `cacheKey`.
2272
2323
  *
@@ -2305,6 +2356,29 @@ declare const dedupeCacheKeys: (findings: ReadonlyArray<Finding>) => Finding[];
2305
2356
  * additional read. When a sample is truncated, findings note the caveat.
2306
2357
  */
2307
2358
  declare const constraintValidator: Lint;
2359
+ /**
2360
+ * `error_rate_outlier` — flag a function whose observed error rate is high
2361
+ * enough to be a genuine reliability signal, not noise.
2362
+ *
2363
+ * PROTOTYPE (plan 248): the read side of the runtime-lint category is thin (3
2364
+ * lints, all traffic/conflict/index shaped) next to the static category's 96.
2365
+ * `lunora insights` already collects and ranks this exact signal
2366
+ * (`errorHotspots`) for a human report; this lint closes the loop by feeding
2367
+ * the same per-function `errors`/`calls` counts into the `lunora advisor` gate
2368
+ * so a CI run can flag a genuinely failing function, not just a report a human
2369
+ * has to remember to read.
2370
+ *
2371
+ * The per-function volume comes from the runtime feeder (`context.
2372
+ * functionMetrics`): the studio backend reads `__lunora_admin__:getFunctionStats`
2373
+ * for the shard in scope — the SAME rows `context.tableScans` already draws
2374
+ * `scannedTables` from, just projecting `errors`/`calls` instead. See
2375
+ * `AdvisorFunctionMetrics` for the scope caveat (one shard, not fanned out
2376
+ * cross-shard like `context.shardTraffic`) and `plans/248-runtime-lints-design.md`
2377
+ * for the reachability finding and open questions this prototype does not
2378
+ * resolve (threshold model, single- vs. cross-shard scope, whether this ships
2379
+ * as its own lint or merges with a future latency counterpart).
2380
+ */
2381
+ declare const errorRateOutlier: Lint;
2308
2382
  /**
2309
2383
  * `hot_shard` — flag a shard whose request share is disproportionately high.
2310
2384
  *
@@ -3242,15 +3316,29 @@ declare const mutatorFullRowReplace: Lint;
3242
3316
  * Flags a non-deterministic API call inside a `query(...)` or `mutation(...)`
3243
3317
  * handler body.
3244
3318
  *
3245
- * Lunora queries and mutations must be **deterministic**: the coordinator may
3246
- * re-run a mutation on optimistic-concurrency (OCC) retry and a query on
3247
- * subscription re-evaluation, so a handler that reads wall-clock time, draws
3248
- * randomness, or hits the network can produce different results on each run —
3249
- * breaking read-your-writes, cache invalidation, and replayable history.
3250
- * `Date.now`, `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues`, and
3251
- * `fetch` are therefore disallowed in query/mutation handlers and belong in an
3252
- * `action(...)`, which runs exactly once and may use ambient/non-deterministic
3253
- * APIs freely (pass the result into a mutation as an argument).
3319
+ * The two procedure kinds do NOT share the same hazard (issue #286).
3320
+ *
3321
+ * A `query` handler genuinely CAN run more than once for the same logical
3322
+ * read: a live subscription re-runs its query whenever a table it reads
3323
+ * changes, so `Date.now()`/`Math.random()`/etc. there can flicker between
3324
+ * re-evaluations this half stays at WARN.
3325
+ *
3326
+ * A `mutation` handler does NOT replay under ordinary dispatch on this
3327
+ * (DO-backed) runtime: a client-issued idempotency key dedups a replayed call
3328
+ * to the **cached** result without re-running the handler, and an
3329
+ * optimistic-concurrency conflict throws back to the caller as an error
3330
+ * rather than triggering an internal retry of the handler. So a mutation
3331
+ * handler body runs at most once per logical write, and `Date.now()` inside
3332
+ * one is stable — the premise this lint used to apply uniformly (borrowed
3333
+ * from Convex's OCC-retries-the-handler model, which does not hold here) was
3334
+ * responsible for 193 of 385 non-INFO findings on one real codebase, all from
3335
+ * the ordinary "stamp `createdAt`" pattern. Dropped to INFO rather than
3336
+ * silenced: a mutation invoked from inside a workflow step or queue consumer
3337
+ * CAN be re-dispatched if the surrounding step/consumer itself replays (a
3338
+ * fresh `ctx.runMutation` call from the step's perspective, not a replay this
3339
+ * lint can distinguish from any other mutation call), so the finding stays as
3340
+ * a breadcrumb rather than disappearing outright. See the "Determinism: what
3341
+ * actually replays" section of the `@lunora/server` docs.
3254
3342
  *
3255
3343
  * This lint runs when the codegen feeder has supplied call evidence
3256
3344
  * (`context.nondeterministicCalls` present); a runtime caller with no evidence
@@ -4350,6 +4438,14 @@ declare const scoreAdvisor: (procedures: ReadonlyArray<AdvisorProcedureProtectio
4350
4438
  * Runs before any rule so a lint can gate on it, and feeds the global weighting:
4351
4439
  * a handler touching identity, mail, or tenant-scoped rows pulls harder on the
4352
4440
  * grade than a plain read. `none` means no signal fired — not "safe".
4441
+ *
4442
+ * A fact reads `undefined` only when the feeder couldn't read the handler body
4443
+ * (a cross-file handler, or — degenerately — a partial payload), and that stays
4444
+ * fail-closed: `level` goes `"high"` the same as a proven `true` would. The
4445
+ * reason text does not, though — asserting "writes an identity table" for a
4446
+ * fact that was never observed would misattribute an unproven claim. A proven
4447
+ * `true` still gets its specific reason; every `undefined` fact collapses into
4448
+ * one shared "could not be read" reason instead of one unproven claim apiece.
4353
4449
  */
4354
4450
  declare const classifySensitivity: (procedure: AdvisorProcedureProtection) => Sensitivity;
4355
4451
  /**
@@ -4383,4 +4479,4 @@ interface RunAdvisorOptions {
4383
4479
  * `static` lints at build time and defer `runtime` lints to a live shard.
4384
4480
  */
4385
4481
  declare const runAdvisor: (context: LintContext, options?: RunAdvisorOptions) => Finding[];
4386
- export { AE_METRIC_EVENTS, ALL_LINTS, type AdvisorAdminRoute, type AdvisorAiRawRun, type AdvisorAiToolSideEffect, type AdvisorArgumentDerivedFetch, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorAuthConfig, type AdvisorBrowserUrlAccess, type AdvisorConfigCall, type AdvisorContainer, type AdvisorContainerKeyAccess, type AdvisorContainerOverride, type AdvisorExportSink, type AdvisorFailOpenGuard, type AdvisorFlagSecurityDefault, type AdvisorGeoIndexUsage, type AdvisorHttpActionGuard, type AdvisorHttpHeaderWrite, type AdvisorHyperdriveCall, type AdvisorIdentityClaimRead, type AdvisorImageDeliveryUrlAccess, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorKvKeyAccess, type AdvisorMailRecipientAccess, type AdvisorMap, type AdvisorMaskProcedure, type AdvisorMaskStrategy, type AdvisorMutatorWrite, type AdvisorNondeterministicCall, type AdvisorNormalizeIdAuthorization, type AdvisorNotifyCall, type AdvisorNotifyConfig, type AdvisorOwnerFieldWrite, type AdvisorPaymentWebhook, type AdvisorPrivilegedDispatch, type AdvisorProcedureProtection, type AdvisorQueryRead, type AdvisorQueue, type AdvisorQueueTuning, type AdvisorR2sqlCall, type AdvisorRatelimitKeySelector, type AdvisorRawRowReturn, type AdvisorRelation, type AdvisorRelationLoad, type AdvisorRlsProcedure, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSoftDeleteRead, type AdvisorSqlInterpolation, type AdvisorStorageKeyAccess, type AdvisorStorageUpload, type AdvisorTable, type AdvisorTableSample, type AdvisorTableScan, type AdvisorVectorNamespaceAccess, type AdvisorWorkflow, type AdvisorWorkflowCall, type AdvisorWranglerVariable, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type BaselineComparison, type Category, type CheckResult, type Coverage, type Facing, type Finding, type Grade, type Level, type Lint, type LintContext, type LintSource, MAP_VERSION, type MapSummary, type ProcedureDelta, type ProcedureScore, type ProjectScore, RUNTIME_LINTS, RunAdvisorOptions, STATIC_LINTS, type ScoreAdvisorOptions, type Sensitivity, type SensitivityLevel, actionFetchSsrf, actionWithoutErrorHandling, adminRouteWithoutGuard, aiRawRunEscapeHatch, aiRunWithoutLogging, aiToolSideEffectPromptInjection, aiUnboundedGenerationPublic, allowUnauthenticatedShardAccessEnabled, authApiCallWithoutHeaders, authCsrfCheckDisabled, authEmailVerificationDisabled, authScimWithoutTransactions, authSecureCookiesDisabled, authSessionFreshageZero, authTrustedOriginsWildcard, browserAllowPrivateTargets, browserUserUrlWithoutAllowlist, byCodepoint, circularFk, classifySensitivity, compareToBaseline, constraintValidator, containerInstanceKeyFromUserInput, containerOversizedInstance, containerPublicInternet, containerRuntimeEgressRelaxation, containerStartEnableInternetOverride, dedupeCacheKeys, duplicateIndex, emptyIndex, errorWithoutCatalog, exportSinkMisconfigured, externalSourceIncrementalNoDeletePath, externalSourceOnGlobal, externalSourceUnscoped, filterOnPrimaryKey, filterWithoutIndex, flagGatesSecurityWithUnsafeDefault, fromServerSchema, geoIndexFieldNotGeopoint, geoIndexUnused, gradeFromScore, hardcodedSecret, hotShard, httpActionMissingAuthGuard, httpActionResponseHeaderInjection, hyperdriveOutsideAction, identityUndeclaredClaimTrusted, imagesUrlSourceFromUserInput, indexReferencesUnknownField, indexUtilization, insertManyUnsafeUserData, kvUnscopedUserKeyIdor, loadAnalyticsRuntimeMetrics, mailInboundDispatchWithoutVerify, mailRecipientFromRequestInput, maskUncoveredPiiColumn, maskWeakHashStrategyOnPii, maskedRelationLeakViaWith, mutatorFullRowReplace, nondeterministicQueryMutation, normalizeIdUsedAsAuthorization, notifyMissingPushConfig, notifySendOutsideAction, outputProjectionMissingOnPublicRead, ownerFieldFromArgsNotAuth, parseAdvisorMap, paymentCreateWithoutAuthorize, paymentWebhookWideTolerance, plaintextSecretInWranglerVariables, policyReferencesUnknownTable, privilegedDispatchUnvalidatedPayload, privilegedFanoutFromPublicProcedure, procedureWithoutStructuredEvent, publicArgumentUsesAny, publicMutationWithoutRatelimit, publicTableRlsOptoutConfusion, queueWithoutDlq, r2sqlOutsideAction, ratelimitDefaultMemoryStore, ratelimitKeySpoofableOrGlobal, ratelimitMiddlewareFailOpen, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, scoreAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, signupMutationWithoutDisposableGating, softDeleteIncludeDeletedFromArgs, sqlInjectionRisk, storageGenerateUploadUrlNoContentTypePin, storageKeyFromUserArgs, storagePresignedUrlForPrivateContent, storageUploadWithoutContentTypeAllowlist, storageUploadWithoutMaxSize, tableWithoutInsert, ttlFieldNotTimestamp, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, unrestrictedWhereBranch, userCreatingMutationWithoutCaptcha, vectorsNamespaceFromUserInput, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
4482
+ export { ALL_LINTS, type AdvisorAdminRoute, type AdvisorAiRawRun, type AdvisorAiToolSideEffect, type AdvisorArgumentDerivedFetch, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorAuthConfig, type AdvisorBrowserUrlAccess, type AdvisorConfigCall, type AdvisorContainer, type AdvisorContainerKeyAccess, type AdvisorContainerOverride, type AdvisorExportSink, type AdvisorFailOpenGuard, type AdvisorFlagSecurityDefault, type AdvisorFunctionMetrics, type AdvisorGeoIndexUsage, type AdvisorHttpActionGuard, type AdvisorHttpHeaderWrite, type AdvisorHyperdriveCall, type AdvisorIdentityClaimRead, type AdvisorImageDeliveryUrlAccess, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorKvKeyAccess, type AdvisorMailRecipientAccess, type AdvisorMap, type AdvisorMaskProcedure, type AdvisorMaskStrategy, type AdvisorMutatorWrite, type AdvisorNondeterministicCall, type AdvisorNormalizeIdAuthorization, type AdvisorNotifyCall, type AdvisorNotifyConfig, type AdvisorOwnerFieldWrite, type AdvisorPaymentWebhook, type AdvisorPrivilegedDispatch, type AdvisorProcedureProtection, type AdvisorQueryRead, type AdvisorQueue, type AdvisorQueueTuning, type AdvisorR2sqlCall, type AdvisorRatelimitKeySelector, type AdvisorRawRowReturn, type AdvisorRelation, type AdvisorRelationLoad, type AdvisorRlsProcedure, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSoftDeleteRead, type AdvisorSqlInterpolation, type AdvisorStorageKeyAccess, type AdvisorStorageUpload, type AdvisorTable, type AdvisorTableSample, type AdvisorTableScan, type AdvisorVectorNamespaceAccess, type AdvisorWorkflow, type AdvisorWorkflowCall, type AdvisorWranglerVariable, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type BaselineComparison, type Category, type CheckResult, type Coverage, type Facing, type Finding, type Grade, type Level, type Lint, type LintContext, type LintSource, MAP_VERSION, type MapSummary, type ProcedureDelta, type ProcedureScore, type ProjectScore, RUNTIME_LINTS, RunAdvisorOptions, STATIC_LINTS, type ScoreAdvisorOptions, type Sensitivity, type SensitivityLevel, actionFetchSsrf, actionWithoutErrorHandling, adminRouteWithoutGuard, aiRawRunEscapeHatch, aiRunWithoutLogging, aiToolSideEffectPromptInjection, aiUnboundedGenerationPublic, allowUnauthenticatedShardAccessEnabled, authApiCallWithoutHeaders, authCsrfCheckDisabled, authEmailVerificationDisabled, authScimWithoutTransactions, authSecureCookiesDisabled, authSessionFreshageZero, authTrustedOriginsWildcard, browserAllowPrivateTargets, browserUserUrlWithoutAllowlist, byCodepoint, circularFk, classifySensitivity, compareToBaseline, constraintValidator, containerInstanceKeyFromUserInput, containerOversizedInstance, containerPublicInternet, containerRuntimeEgressRelaxation, containerStartEnableInternetOverride, dedupeCacheKeys, duplicateIndex, emptyIndex, errorRateOutlier, errorWithoutCatalog, exportSinkMisconfigured, externalSourceIncrementalNoDeletePath, externalSourceOnGlobal, externalSourceUnscoped, filterOnPrimaryKey, filterWithoutIndex, flagGatesSecurityWithUnsafeDefault, fromServerSchema, geoIndexFieldNotGeopoint, geoIndexUnused, gradeFromScore, hardcodedSecret, hotShard, httpActionMissingAuthGuard, httpActionResponseHeaderInjection, hyperdriveOutsideAction, identityUndeclaredClaimTrusted, imagesUrlSourceFromUserInput, indexReferencesUnknownField, indexUtilization, insertManyUnsafeUserData, kvUnscopedUserKeyIdor, mailInboundDispatchWithoutVerify, mailRecipientFromRequestInput, maskUncoveredPiiColumn, maskWeakHashStrategyOnPii, maskedRelationLeakViaWith, mutatorFullRowReplace, nondeterministicQueryMutation, normalizeIdUsedAsAuthorization, notifyMissingPushConfig, notifySendOutsideAction, outputProjectionMissingOnPublicRead, ownerFieldFromArgsNotAuth, parseAdvisorMap, paymentCreateWithoutAuthorize, paymentWebhookWideTolerance, plaintextSecretInWranglerVariables, policyReferencesUnknownTable, privilegedDispatchUnvalidatedPayload, privilegedFanoutFromPublicProcedure, procedureWithoutStructuredEvent, publicArgumentUsesAny, publicMutationWithoutRatelimit, publicTableRlsOptoutConfusion, queueWithoutDlq, r2sqlOutsideAction, ratelimitDefaultMemoryStore, ratelimitKeySpoofableOrGlobal, ratelimitMiddlewareFailOpen, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, scoreAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, signupMutationWithoutDisposableGating, softDeleteIncludeDeletedFromArgs, sqlInjectionRisk, storageGenerateUploadUrlNoContentTypePin, storageKeyFromUserArgs, storagePresignedUrlForPrivateContent, storageUploadWithoutContentTypeAllowlist, storageUploadWithoutMaxSize, tableWithoutInsert, ttlFieldNotTimestamp, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, unrestrictedWhereBranch, userCreatingMutationWithoutCaptcha, vectorsNamespaceFromUserInput, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{dedupeCacheKeys as a}from"./packem_shared/dedupeCacheKeys-7JgxaQWV.mjs";import m from"./packem_shared/constraintValidator-N339mfyx.mjs";import s from"./packem_shared/hotShard-DVs_U17R.mjs";import n from"./packem_shared/indexUtilization-QfbIH_w4.mjs";import p from"./packem_shared/actionFetchSsrf-BbWxy1Yf.mjs";import f from"./packem_shared/actionWithoutErrorHandling-D1DqSPpg.mjs";import l from"./packem_shared/adminRouteWithoutGuard-DSo4t9j4.mjs";import u from"./packem_shared/aiRawRunEscapeHatch-DfhjYm43.mjs";import d from"./packem_shared/aiRunWithoutLogging-CMvQPNBr.mjs";import c from"./packem_shared/aiToolSideEffectPromptInjection-DnWQOoKi.mjs";import $ from"./packem_shared/aiUnboundedGenerationPublic-D3HZrh5_.mjs";import h from"./packem_shared/allowUnauthenticatedShardAccessEnabled-D4aMNBb0.mjs";import g from"./packem_shared/authApiCallWithoutHeaders-BItMMdZP.mjs";import b from"./packem_shared/authCsrfCheckDisabled-De9ammtV.mjs";import U from"./packem_shared/authEmailVerificationDisabled-BFKdJcx4.mjs";import S from"./packem_shared/authScimWithoutTransactions-DCtLXzks.mjs";import y from"./packem_shared/authSecureCookiesDisabled-B4WIzvMm.mjs";import I from"./packem_shared/authSessionFreshageZero-Bpcaf_Pe.mjs";import A from"./packem_shared/authTrustedOriginsWildcard-DjU9NXEm.mjs";import T from"./packem_shared/browserAllowPrivateTargets-Dxyeh6lg.mjs";import W from"./packem_shared/browserUserUrlWithoutAllowlist-BHBS6b0N.mjs";import R from"./packem_shared/circularFk-BMuJKmuZ.mjs";import w from"./packem_shared/containerInstanceKeyFromUserInput-C8S4T3ty.mjs";import x from"./packem_shared/containerOversizedInstance-B9wW_sU5.mjs";import k from"./packem_shared/containerPublicInternet-Bx2J7tha.mjs";import F from"./packem_shared/containerRuntimeEgressRelaxation-BQ2ZD1aV.mjs";import v from"./packem_shared/containerStartEnableInternetOverride-B62WUura.mjs";import C from"./packem_shared/duplicateIndex-DFT7qLQD.mjs";import P from"./packem_shared/emptyIndex-BiXEfyfN.mjs";import M from"./packem_shared/errorWithoutCatalog-DTDVKU4b.mjs";import D from"./packem_shared/exportSinkMisconfigured-DvpAaa6j.mjs";import E from"./packem_shared/externalSourceIncrementalNoDeletePath-DnoL64YY.mjs";import O from"./packem_shared/externalSourceOnGlobal-DNPLoNUg.mjs";import N from"./packem_shared/externalSourceUnscoped-DqBp5jYr.mjs";import G from"./packem_shared/filterOnPrimaryKey-CPPa7fog.mjs";import K from"./packem_shared/filterWithoutIndex-DYLs-Jf2.mjs";import L from"./packem_shared/flagGatesSecurityWithUnsafeDefault-DPGFEHTt.mjs";import V from"./packem_shared/geoIndexFieldNotGeopoint-DAGLZtaH.mjs";import z from"./packem_shared/geoIndexUnused-BgDE6_AR.mjs";import _ from"./packem_shared/hardcodedSecret-rIRc3aNk.mjs";import j from"./packem_shared/httpActionMissingAuthGuard-BZDdeW_D.mjs";import q from"./packem_shared/httpActionResponseHeaderInjection-D063rB7U.mjs";import H from"./packem_shared/hyperdriveOutsideAction-CRmOQz67.mjs";import B from"./packem_shared/identityUndeclaredClaimTrusted-BYRYGQ74.mjs";import Q from"./packem_shared/imagesUrlSourceFromUserInput-Cy5pc_dA.mjs";import Z from"./packem_shared/indexReferencesUnknownField-r3QHlaz6.mjs";import J from"./packem_shared/insertManyUnsafeUserData-DrH7PdRs.mjs";import X from"./packem_shared/kvUnscopedUserKeyIdor-B4lGxRxh.mjs";import Y from"./packem_shared/mailInboundDispatchWithoutVerify-BivYJ5F9.mjs";import rr from"./packem_shared/mailRecipientFromRequestInput-BR_oSh65.mjs";import or from"./packem_shared/maskUncoveredPiiColumn-DqPfZM2O.mjs";import tr from"./packem_shared/maskWeakHashStrategyOnPii-CEiiGgT7.mjs";import ir from"./packem_shared/maskedRelationLeakViaWith-CxYk0MDW.mjs";import er from"./packem_shared/mutatorFullRowReplace-CEgecSGm.mjs";import ar from"./packem_shared/nondeterministicQueryMutation-BaMfbAk_.mjs";import mr from"./packem_shared/normalizeIdUsedAsAuthorization-FjISSXJm.mjs";import sr from"./packem_shared/notifyMissingPushConfig-C_GbVw3S.mjs";import nr from"./packem_shared/notifySendOutsideAction-B0jZuUP3.mjs";import pr from"./packem_shared/outputProjectionMissingOnPublicRead-9Pm9766s.mjs";import fr from"./packem_shared/ownerFieldFromArgsNotAuth-D4K-_0aD.mjs";import lr from"./packem_shared/paymentCreateWithoutAuthorize-CurtqN8K.mjs";import ur from"./packem_shared/paymentWebhookWideTolerance-I7ingfR8.mjs";import dr from"./packem_shared/plaintextSecretInWranglerVariables-DXjKIm8H.mjs";import cr from"./packem_shared/policyReferencesUnknownTable-D3WelAQa.mjs";import $r from"./packem_shared/privilegedDispatchUnvalidatedPayload-74GuGye1.mjs";import hr from"./packem_shared/privilegedFanoutFromPublicProcedure-C2j2GSUN.mjs";import gr from"./packem_shared/procedureWithoutStructuredEvent-D4Qi2vH7.mjs";import br from"./packem_shared/publicArgumentUsesAny-BJb4cTqL.mjs";import Ur from"./packem_shared/publicMutationWithoutRatelimit-D9phluHW.mjs";import Sr from"./packem_shared/publicTableRlsOptoutConfusion-CaWtPGAn.mjs";import yr from"./packem_shared/queueWithoutDlq-BLY3iy9Q.mjs";import Ir from"./packem_shared/r2sqlOutsideAction-CKK_I1Ms.mjs";import Ar from"./packem_shared/ratelimitDefaultMemoryStore-mOdI3Jmb.mjs";import Tr from"./packem_shared/ratelimitKeySpoofableOrGlobal-DaGId_Fh.mjs";import Wr from"./packem_shared/ratelimitMiddlewareFailOpen-CTWVosEc.mjs";import Rr from"./packem_shared/relationReferencesUnknownField-clqyB69Y.mjs";import wr from"./packem_shared/relationReferencesUnknownTable-CLdrhkmS.mjs";import xr from"./packem_shared/rlsUncoveredTable-DYHgnoos.mjs";import kr from"./packem_shared/shapeTargetsGlobalTable-Blz-aSkg.mjs";import Fr from"./packem_shared/shapeUnknownTable-DFxtyFio.mjs";import vr from"./packem_shared/signupMutationWithoutDisposableGating-BNzlsE0F.mjs";import Cr from"./packem_shared/softDeleteIncludeDeletedFromArgs-DgxpQHRA.mjs";import Pr from"./packem_shared/sqlInjectionRisk-D2NaT-5c.mjs";import Mr from"./packem_shared/storageGenerateUploadUrlNoContentTypePin-C9cjl6Jh.mjs";import Dr from"./packem_shared/storageKeyFromUserArgs-BeS5cp56.mjs";import Er from"./packem_shared/storagePresignedUrlForPrivateContent-j7VhwIdu.mjs";import Or from"./packem_shared/storageUploadWithoutContentTypeAllowlist-oxKLecuf.mjs";import Nr from"./packem_shared/storageUploadWithoutMaxSize-DUDxPutk.mjs";import Gr from"./packem_shared/tableWithoutInsert-D3AhaNzR.mjs";import Kr from"./packem_shared/ttlFieldNotTimestamp-BFiRkSZD.mjs";import Lr from"./packem_shared/unboundedStringArgument-AkkIcr-S.mjs";import Vr from"./packem_shared/unindexedForeignKey-DK4eWXkb.mjs";import zr from"./packem_shared/unindexedRelationTarget-B5fFq1E8.mjs";import _r from"./packem_shared/unrestrictedWhereBranch-YKiydie4.mjs";import jr from"./packem_shared/userCreatingMutationWithoutCaptcha-iDjRkUFO.mjs";import qr from"./packem_shared/vectorsNamespaceFromUserInput-Cyq5B2kF.mjs";import Hr from"./packem_shared/workflowDuplicateStepName-cXF6Rpvg.mjs";import Br from"./packem_shared/workflowUnknownTarget-JF8rITLP.mjs";import Qr from"./packem_shared/workflowUnused-Dx_FQwGE.mjs";import{AE_METRIC_EVENTS as Bt,loadAnalyticsRuntimeMetrics as Qt}from"./packem_shared/AE_METRIC_EVENTS-LP_0D7Uw.mjs";import{compareToBaseline as Jt,parseAdvisorMap as Xt}from"./packem_shared/compareToBaseline-T1_kl8VY.mjs";import{gradeFromScore as ri}from"./packem_shared/gradeFromScore-DW82YDVY.mjs";import{MAP_VERSION as ti,byCodepoint as ii,scoreAdvisor as ei}from"./packem_shared/MAP_VERSION-BS08jBkS.mjs";import{default as mi}from"./packem_shared/classifySensitivity-DjswRYWd.mjs";import{fromServerSchema as ni}from"./packem_shared/fromServerSchema-CJ-AQ2kw.mjs";const Zr=[Z,wr,Rr,Br,Hr,Fr,E,O,N,P,M,V,z,D,Kr,R,Vr,zr,C,Gr,Qr,yr,G,K,kr,er,ar,H,Ir,g,cr,xr,or,tr,x,k,Ur,_r,jr,vr,br,Lr,_,Pr,l,lr,Y,Ar,T,hr,gr,J,$,p,f,fr,Dr,X,w,u,d,qr,rr,W,$r,v,F,S,A,b,y,U,I,Q,Tr,Sr,h,Or,Nr,Mr,Er,j,q,Wr,L,c,B,ur,Cr,ir,pr,mr,nr,sr,dr],Jr=[s,n,m],Xr=[...Zr,...Jr],jt=(i,r={})=>{const e=r.lints??Xr,o=[];for(const t of e)r.source!==void 0&&t.source!==r.source||o.push(...t.run(i));return a(o)};export{Bt as AE_METRIC_EVENTS,Xr as ALL_LINTS,ti as MAP_VERSION,Jr as RUNTIME_LINTS,Zr as STATIC_LINTS,p as actionFetchSsrf,f as actionWithoutErrorHandling,l as adminRouteWithoutGuard,u as aiRawRunEscapeHatch,d as aiRunWithoutLogging,c as aiToolSideEffectPromptInjection,$ as aiUnboundedGenerationPublic,h as allowUnauthenticatedShardAccessEnabled,g as authApiCallWithoutHeaders,b as authCsrfCheckDisabled,U as authEmailVerificationDisabled,S as authScimWithoutTransactions,y as authSecureCookiesDisabled,I as authSessionFreshageZero,A as authTrustedOriginsWildcard,T as browserAllowPrivateTargets,W as browserUserUrlWithoutAllowlist,ii as byCodepoint,R as circularFk,mi as classifySensitivity,Jt as compareToBaseline,m as constraintValidator,w as containerInstanceKeyFromUserInput,x as containerOversizedInstance,k as containerPublicInternet,F as containerRuntimeEgressRelaxation,v as containerStartEnableInternetOverride,a as dedupeCacheKeys,C as duplicateIndex,P as emptyIndex,M as errorWithoutCatalog,D as exportSinkMisconfigured,E as externalSourceIncrementalNoDeletePath,O as externalSourceOnGlobal,N as externalSourceUnscoped,G as filterOnPrimaryKey,K as filterWithoutIndex,L as flagGatesSecurityWithUnsafeDefault,ni as fromServerSchema,V as geoIndexFieldNotGeopoint,z as geoIndexUnused,ri as gradeFromScore,_ as hardcodedSecret,s as hotShard,j as httpActionMissingAuthGuard,q as httpActionResponseHeaderInjection,H as hyperdriveOutsideAction,B as identityUndeclaredClaimTrusted,Q as imagesUrlSourceFromUserInput,Z as indexReferencesUnknownField,n as indexUtilization,J as insertManyUnsafeUserData,X as kvUnscopedUserKeyIdor,Qt as loadAnalyticsRuntimeMetrics,Y as mailInboundDispatchWithoutVerify,rr as mailRecipientFromRequestInput,or as maskUncoveredPiiColumn,tr as maskWeakHashStrategyOnPii,ir as maskedRelationLeakViaWith,er as mutatorFullRowReplace,ar as nondeterministicQueryMutation,mr as normalizeIdUsedAsAuthorization,sr as notifyMissingPushConfig,nr as notifySendOutsideAction,pr as outputProjectionMissingOnPublicRead,fr as ownerFieldFromArgsNotAuth,Xt as parseAdvisorMap,lr as paymentCreateWithoutAuthorize,ur as paymentWebhookWideTolerance,dr as plaintextSecretInWranglerVariables,cr as policyReferencesUnknownTable,$r as privilegedDispatchUnvalidatedPayload,hr as privilegedFanoutFromPublicProcedure,gr as procedureWithoutStructuredEvent,br as publicArgumentUsesAny,Ur as publicMutationWithoutRatelimit,Sr as publicTableRlsOptoutConfusion,yr as queueWithoutDlq,Ir as r2sqlOutsideAction,Ar as ratelimitDefaultMemoryStore,Tr as ratelimitKeySpoofableOrGlobal,Wr as ratelimitMiddlewareFailOpen,Rr as relationReferencesUnknownField,wr as relationReferencesUnknownTable,xr as rlsUncoveredTable,jt as runAdvisor,ei as scoreAdvisor,kr as shapeTargetsGlobalTable,Fr as shapeUnknownTable,vr as signupMutationWithoutDisposableGating,Cr as softDeleteIncludeDeletedFromArgs,Pr as sqlInjectionRisk,Mr as storageGenerateUploadUrlNoContentTypePin,Dr as storageKeyFromUserArgs,Er as storagePresignedUrlForPrivateContent,Or as storageUploadWithoutContentTypeAllowlist,Nr as storageUploadWithoutMaxSize,Gr as tableWithoutInsert,Kr as ttlFieldNotTimestamp,Lr as unboundedStringArgument,Vr as unindexedForeignKey,zr as unindexedRelationTarget,_r as unrestrictedWhereBranch,jr as userCreatingMutationWithoutCaptcha,qr as vectorsNamespaceFromUserInput,Hr as workflowDuplicateStepName,Br as workflowUnknownTarget,Qr as workflowUnused};
1
+ import{dedupeCacheKeys as a}from"./packem_shared/dedupeCacheKeys-7JgxaQWV.mjs";import m from"./packem_shared/constraintValidator-N339mfyx.mjs";import s from"./packem_shared/errorRateOutlier-CgK42sq0.mjs";import n from"./packem_shared/hotShard-DVs_U17R.mjs";import p from"./packem_shared/indexUtilization-QfbIH_w4.mjs";import f from"./packem_shared/actionFetchSsrf-BbWxy1Yf.mjs";import l from"./packem_shared/actionWithoutErrorHandling-D1DqSPpg.mjs";import u from"./packem_shared/adminRouteWithoutGuard-DSo4t9j4.mjs";import d from"./packem_shared/aiRawRunEscapeHatch-DfhjYm43.mjs";import c from"./packem_shared/aiRunWithoutLogging-CMvQPNBr.mjs";import $ from"./packem_shared/aiToolSideEffectPromptInjection-DnWQOoKi.mjs";import h from"./packem_shared/aiUnboundedGenerationPublic-iu7crnSa.mjs";import g from"./packem_shared/allowUnauthenticatedShardAccessEnabled-D4aMNBb0.mjs";import b from"./packem_shared/authApiCallWithoutHeaders-BItMMdZP.mjs";import U from"./packem_shared/authCsrfCheckDisabled-De9ammtV.mjs";import S from"./packem_shared/authEmailVerificationDisabled-BFKdJcx4.mjs";import y from"./packem_shared/authScimWithoutTransactions-DCtLXzks.mjs";import I from"./packem_shared/authSecureCookiesDisabled-B4WIzvMm.mjs";import A from"./packem_shared/authSessionFreshageZero-Bpcaf_Pe.mjs";import W from"./packem_shared/authTrustedOriginsWildcard-DjU9NXEm.mjs";import T from"./packem_shared/browserAllowPrivateTargets-Dxyeh6lg.mjs";import R from"./packem_shared/browserUserUrlWithoutAllowlist-BHBS6b0N.mjs";import w from"./packem_shared/circularFk-BMuJKmuZ.mjs";import k from"./packem_shared/containerInstanceKeyFromUserInput-C8S4T3ty.mjs";import x from"./packem_shared/containerOversizedInstance-B9wW_sU5.mjs";import F from"./packem_shared/containerPublicInternet-Bx2J7tha.mjs";import v from"./packem_shared/containerRuntimeEgressRelaxation-BQ2ZD1aV.mjs";import C from"./packem_shared/containerStartEnableInternetOverride-B62WUura.mjs";import P from"./packem_shared/duplicateIndex-DFT7qLQD.mjs";import M from"./packem_shared/emptyIndex-BiXEfyfN.mjs";import O from"./packem_shared/errorWithoutCatalog-DTDVKU4b.mjs";import D from"./packem_shared/exportSinkMisconfigured-DvpAaa6j.mjs";import N from"./packem_shared/externalSourceIncrementalNoDeletePath-DnoL64YY.mjs";import E from"./packem_shared/externalSourceOnGlobal-DNPLoNUg.mjs";import G from"./packem_shared/externalSourceUnscoped-DqBp5jYr.mjs";import K from"./packem_shared/filterOnPrimaryKey-CPPa7fog.mjs";import L from"./packem_shared/filterWithoutIndex-DYLs-Jf2.mjs";import z from"./packem_shared/flagGatesSecurityWithUnsafeDefault-DPGFEHTt.mjs";import V from"./packem_shared/geoIndexFieldNotGeopoint-DAGLZtaH.mjs";import j from"./packem_shared/geoIndexUnused-BgDE6_AR.mjs";import q from"./packem_shared/hardcodedSecret-rIRc3aNk.mjs";import H from"./packem_shared/httpActionMissingAuthGuard-BZDdeW_D.mjs";import _ from"./packem_shared/httpActionResponseHeaderInjection-D063rB7U.mjs";import B from"./packem_shared/hyperdriveOutsideAction-CRmOQz67.mjs";import Q from"./packem_shared/identityUndeclaredClaimTrusted-BYRYGQ74.mjs";import Z from"./packem_shared/imagesUrlSourceFromUserInput-Cy5pc_dA.mjs";import J from"./packem_shared/indexReferencesUnknownField-r3QHlaz6.mjs";import X from"./packem_shared/insertManyUnsafeUserData-xLYAkcKj.mjs";import Y from"./packem_shared/kvUnscopedUserKeyIdor-B4lGxRxh.mjs";import rr from"./packem_shared/mailInboundDispatchWithoutVerify-BivYJ5F9.mjs";import or from"./packem_shared/mailRecipientFromRequestInput-BR_oSh65.mjs";import tr from"./packem_shared/maskUncoveredPiiColumn-DqPfZM2O.mjs";import ir from"./packem_shared/maskWeakHashStrategyOnPii-CEiiGgT7.mjs";import er from"./packem_shared/maskedRelationLeakViaWith-CxYk0MDW.mjs";import ar from"./packem_shared/mutatorFullRowReplace-CEgecSGm.mjs";import mr from"./packem_shared/nondeterministicQueryMutation-BZV_biUV.mjs";import sr from"./packem_shared/normalizeIdUsedAsAuthorization-FjISSXJm.mjs";import nr from"./packem_shared/notifyMissingPushConfig-C_GbVw3S.mjs";import pr from"./packem_shared/notifySendOutsideAction-B0jZuUP3.mjs";import fr from"./packem_shared/outputProjectionMissingOnPublicRead-9Pm9766s.mjs";import lr from"./packem_shared/ownerFieldFromArgsNotAuth-D4K-_0aD.mjs";import ur from"./packem_shared/paymentCreateWithoutAuthorize-CurtqN8K.mjs";import dr from"./packem_shared/paymentWebhookWideTolerance-I7ingfR8.mjs";import cr from"./packem_shared/plaintextSecretInWranglerVariables-DXjKIm8H.mjs";import $r from"./packem_shared/policyReferencesUnknownTable-D3WelAQa.mjs";import hr from"./packem_shared/privilegedDispatchUnvalidatedPayload-74GuGye1.mjs";import gr from"./packem_shared/privilegedFanoutFromPublicProcedure-sMsZN4uP.mjs";import br from"./packem_shared/procedureWithoutStructuredEvent-D4Qi2vH7.mjs";import Ur from"./packem_shared/publicArgumentUsesAny-BJb4cTqL.mjs";import Sr from"./packem_shared/publicMutationWithoutRatelimit-D9phluHW.mjs";import yr from"./packem_shared/publicTableRlsOptoutConfusion-CaWtPGAn.mjs";import Ir from"./packem_shared/queueWithoutDlq-BLY3iy9Q.mjs";import Ar from"./packem_shared/r2sqlOutsideAction-CKK_I1Ms.mjs";import Wr from"./packem_shared/ratelimitDefaultMemoryStore-mOdI3Jmb.mjs";import Tr from"./packem_shared/ratelimitKeySpoofableOrGlobal-DaGId_Fh.mjs";import Rr from"./packem_shared/ratelimitMiddlewareFailOpen-CTWVosEc.mjs";import wr from"./packem_shared/relationReferencesUnknownField-clqyB69Y.mjs";import kr from"./packem_shared/relationReferencesUnknownTable-CLdrhkmS.mjs";import xr from"./packem_shared/rlsUncoveredTable-DYHgnoos.mjs";import Fr from"./packem_shared/shapeTargetsGlobalTable-Blz-aSkg.mjs";import vr from"./packem_shared/shapeUnknownTable-DFxtyFio.mjs";import Cr from"./packem_shared/signupMutationWithoutDisposableGating-Tgv9SbsY.mjs";import Pr from"./packem_shared/softDeleteIncludeDeletedFromArgs-DgxpQHRA.mjs";import Mr from"./packem_shared/sqlInjectionRisk-D2NaT-5c.mjs";import Or from"./packem_shared/storageGenerateUploadUrlNoContentTypePin-C9cjl6Jh.mjs";import Dr from"./packem_shared/storageKeyFromUserArgs-CI-6kyte.mjs";import Nr from"./packem_shared/storagePresignedUrlForPrivateContent-j7VhwIdu.mjs";import Er from"./packem_shared/storageUploadWithoutContentTypeAllowlist-oxKLecuf.mjs";import Gr from"./packem_shared/storageUploadWithoutMaxSize-DUDxPutk.mjs";import Kr from"./packem_shared/tableWithoutInsert-D3AhaNzR.mjs";import Lr from"./packem_shared/ttlFieldNotTimestamp-BFiRkSZD.mjs";import zr from"./packem_shared/unboundedStringArgument-AkkIcr-S.mjs";import Vr from"./packem_shared/unindexedForeignKey-DK4eWXkb.mjs";import jr from"./packem_shared/unindexedRelationTarget-B5fFq1E8.mjs";import qr from"./packem_shared/unrestrictedWhereBranch-YKiydie4.mjs";import Hr from"./packem_shared/userCreatingMutationWithoutCaptcha-DGiZ3_W7.mjs";import _r from"./packem_shared/vectorsNamespaceFromUserInput-Cyq5B2kF.mjs";import Br from"./packem_shared/workflowDuplicateStepName-cXF6Rpvg.mjs";import Qr from"./packem_shared/workflowUnknownTarget-JF8rITLP.mjs";import Zr from"./packem_shared/workflowUnused-Dx_FQwGE.mjs";import{compareToBaseline as Zt,parseAdvisorMap as Jt}from"./packem_shared/compareToBaseline-BwXFusQG.mjs";import{gradeFromScore as Yt}from"./packem_shared/gradeFromScore-DW82YDVY.mjs";import{MAP_VERSION as oi,byCodepoint as ti,scoreAdvisor as ii}from"./packem_shared/MAP_VERSION-DxkFCiLX.mjs";import{default as ai}from"./packem_shared/classifySensitivity-B1kkm44P.mjs";import{fromServerSchema as si}from"./packem_shared/fromServerSchema-CJ-AQ2kw.mjs";const Jr=[J,kr,wr,Qr,Br,vr,N,E,G,M,O,V,j,D,Lr,w,Vr,jr,P,Kr,Zr,Ir,K,L,Fr,ar,mr,B,Ar,b,$r,xr,tr,ir,x,F,Sr,qr,Hr,Cr,Ur,zr,q,Mr,u,ur,rr,Wr,T,gr,br,X,h,f,l,lr,Dr,Y,k,d,c,_r,or,R,hr,C,v,y,W,U,I,S,A,Z,Tr,yr,g,Er,Gr,Or,Nr,H,_,Rr,z,$,Q,dr,Pr,er,fr,sr,pr,nr,cr],Xr=[n,p,m,s],Yr=[...Jr,...Xr],_t=(i,r={})=>{const e=r.lints??Yr,o=[];for(const t of e)r.source!==void 0&&t.source!==r.source||o.push(...t.run(i));return a(o)};export{Yr as ALL_LINTS,oi as MAP_VERSION,Xr as RUNTIME_LINTS,Jr as STATIC_LINTS,f as actionFetchSsrf,l as actionWithoutErrorHandling,u as adminRouteWithoutGuard,d as aiRawRunEscapeHatch,c as aiRunWithoutLogging,$ as aiToolSideEffectPromptInjection,h as aiUnboundedGenerationPublic,g as allowUnauthenticatedShardAccessEnabled,b as authApiCallWithoutHeaders,U as authCsrfCheckDisabled,S as authEmailVerificationDisabled,y as authScimWithoutTransactions,I as authSecureCookiesDisabled,A as authSessionFreshageZero,W as authTrustedOriginsWildcard,T as browserAllowPrivateTargets,R as browserUserUrlWithoutAllowlist,ti as byCodepoint,w as circularFk,ai as classifySensitivity,Zt as compareToBaseline,m as constraintValidator,k as containerInstanceKeyFromUserInput,x as containerOversizedInstance,F as containerPublicInternet,v as containerRuntimeEgressRelaxation,C as containerStartEnableInternetOverride,a as dedupeCacheKeys,P as duplicateIndex,M as emptyIndex,s as errorRateOutlier,O as errorWithoutCatalog,D as exportSinkMisconfigured,N as externalSourceIncrementalNoDeletePath,E as externalSourceOnGlobal,G as externalSourceUnscoped,K as filterOnPrimaryKey,L as filterWithoutIndex,z as flagGatesSecurityWithUnsafeDefault,si as fromServerSchema,V as geoIndexFieldNotGeopoint,j as geoIndexUnused,Yt as gradeFromScore,q as hardcodedSecret,n as hotShard,H as httpActionMissingAuthGuard,_ as httpActionResponseHeaderInjection,B as hyperdriveOutsideAction,Q as identityUndeclaredClaimTrusted,Z as imagesUrlSourceFromUserInput,J as indexReferencesUnknownField,p as indexUtilization,X as insertManyUnsafeUserData,Y as kvUnscopedUserKeyIdor,rr as mailInboundDispatchWithoutVerify,or as mailRecipientFromRequestInput,tr as maskUncoveredPiiColumn,ir as maskWeakHashStrategyOnPii,er as maskedRelationLeakViaWith,ar as mutatorFullRowReplace,mr as nondeterministicQueryMutation,sr as normalizeIdUsedAsAuthorization,nr as notifyMissingPushConfig,pr as notifySendOutsideAction,fr as outputProjectionMissingOnPublicRead,lr as ownerFieldFromArgsNotAuth,Jt as parseAdvisorMap,ur as paymentCreateWithoutAuthorize,dr as paymentWebhookWideTolerance,cr as plaintextSecretInWranglerVariables,$r as policyReferencesUnknownTable,hr as privilegedDispatchUnvalidatedPayload,gr as privilegedFanoutFromPublicProcedure,br as procedureWithoutStructuredEvent,Ur as publicArgumentUsesAny,Sr as publicMutationWithoutRatelimit,yr as publicTableRlsOptoutConfusion,Ir as queueWithoutDlq,Ar as r2sqlOutsideAction,Wr as ratelimitDefaultMemoryStore,Tr as ratelimitKeySpoofableOrGlobal,Rr as ratelimitMiddlewareFailOpen,wr as relationReferencesUnknownField,kr as relationReferencesUnknownTable,xr as rlsUncoveredTable,_t as runAdvisor,ii as scoreAdvisor,Fr as shapeTargetsGlobalTable,vr as shapeUnknownTable,Cr as signupMutationWithoutDisposableGating,Pr as softDeleteIncludeDeletedFromArgs,Mr as sqlInjectionRisk,Or as storageGenerateUploadUrlNoContentTypePin,Dr as storageKeyFromUserArgs,Nr as storagePresignedUrlForPrivateContent,Er as storageUploadWithoutContentTypeAllowlist,Gr as storageUploadWithoutMaxSize,Kr as tableWithoutInsert,Lr as ttlFieldNotTimestamp,zr as unboundedStringArgument,Vr as unindexedForeignKey,jr as unindexedRelationTarget,qr as unrestrictedWhereBranch,Hr as userCreatingMutationWithoutCaptcha,_r as vectorsNamespaceFromUserInput,Br as workflowDuplicateStepName,Qr as workflowUnknownTarget,Zr as workflowUnused};
@@ -1 +1 @@
1
- import{scoreProcedure as h,procedureWeight as y,coverageFromScore as k,scoreGlobal as N,gradeFromScore as b,projectWeight as M,worstLevel as $,weightFor as j}from"./gradeFromScore-DW82YDVY.mjs";import F from"./classifySensitivity-DjswRYWd.mjs";const S=(e,o)=>e===o?0:e<o?-1:1,w=(e,o)=>S(e.name,o.name),x=(e,o)=>{const t=e[o];return typeof t=="string"?t:void 0},p=(e,o)=>`${e}#${o}`,R=(e,o,t,s)=>e===void 0?{level:o,name:t,occurrences:1,weight:s}:{level:$(e.level,o),name:t,occurrences:e.occurrences+1,weight:Math.max(e.weight,s)},A=(e,o,t)=>{const s=new Map;for(const a of e)s.set(p(a.file,a.exportName),new Map);const c=new Map;for(const a of o){const n=x(a.metadata,"file"),l=x(a.metadata,"exportName"),m=(n!==void 0&&l!==void 0?s.get(p(n,l)):void 0)??c;m.set(a.name,R(m.get(a.name),a.level,a.name,t(a)))}return{byProcedure:new Map([...s].map(([a,n])=>[a,[...n.values()].toSorted(w)])),project:[...c.values()].toSorted(w)}},I=(e,o)=>{const t={clean:0,exempt:0,failing:0,warned:0};let s=o.checks.length;for(const c of e)t[c.coverage]+=1,s+=c.checks.length;return{clean:t.clean,exempt:t.exempt,failing:t.failing,procedures:e.length,rulesFired:s,warned:t.warned}},O=1,W=(e,o,t={})=>{const s=new Set(t.exempt),c=A(e,o,r=>j(r.level)),a=e.map(r=>{const i=p(r.file,r.exportName),v=c.byProcedure.get(i)??[],u=h(v),d=s.has(i)||r.exempt===!0,f=F(r);return{checks:v,coverage:d?"exempt":k(u),...d?{exemptReason:r.exemptReason??""}:{},exportName:r.exportName,file:r.file,id:i,kind:r.kind,score:u,sensitivity:f,visibility:r.visibility,weight:d?0:y(r,f.level)}}).toSorted((r,i)=>S(r.id,i.id)),n={checks:c.project,score:h(c.project)},l=a.filter(r=>r.coverage!=="exempt"),m=l.reduce((r,i)=>r+i.weight,0),g=N([...l,{score:n.score,weight:M(m)}]);return{generatedAt:t.generatedAt??new Date().toISOString(),grade:b(g),procedures:a,project:n,score:g,summary:I(a,n),version:O}};export{O as MAP_VERSION,S as byCodepoint,W as scoreAdvisor};
1
+ import{scoreProcedure as h,procedureWeight as y,coverageFromScore as k,scoreGlobal as N,gradeFromScore as $,projectWeight as b,worstLevel as M,weightFor as j}from"./gradeFromScore-DW82YDVY.mjs";import F from"./classifySensitivity-B1kkm44P.mjs";const S=(e,o)=>e===o?0:e<o?-1:1,w=(e,o)=>S(e.name,o.name),x=(e,o)=>{const t=e[o];return typeof t=="string"?t:void 0},p=(e,o)=>`${e}#${o}`,R=(e,o,t,s)=>e===void 0?{level:o,name:t,occurrences:1,weight:s}:{level:M(e.level,o),name:t,occurrences:e.occurrences+1,weight:Math.max(e.weight,s)},A=(e,o,t)=>{const s=new Map;for(const a of e)s.set(p(a.file,a.exportName),new Map);const c=new Map;for(const a of o){const n=x(a.metadata,"file"),l=x(a.metadata,"exportName"),m=(n!==void 0&&l!==void 0?s.get(p(n,l)):void 0)??c;m.set(a.name,R(m.get(a.name),a.level,a.name,t(a)))}return{byProcedure:new Map([...s].map(([a,n])=>[a,[...n.values()].toSorted(w)])),project:[...c.values()].toSorted(w)}},I=(e,o)=>{const t={clean:0,exempt:0,failing:0,warned:0};let s=o.checks.length;for(const c of e)t[c.coverage]+=1,s+=c.checks.length;return{clean:t.clean,exempt:t.exempt,failing:t.failing,procedures:e.length,rulesFired:s,warned:t.warned}},O=1,W=(e,o,t={})=>{const s=new Set(t.exempt),c=A(e,o,r=>j(r.level)),a=e.map(r=>{const i=p(r.file,r.exportName),v=c.byProcedure.get(i)??[],u=h(v),d=s.has(i)||r.exempt===!0,f=F(r);return{checks:v,coverage:d?"exempt":k(u),...d?{exemptReason:r.exemptReason??""}:{},exportName:r.exportName,file:r.file,id:i,kind:r.kind,score:u,sensitivity:f,visibility:r.visibility,weight:d?0:y(r,f.level)}}).toSorted((r,i)=>S(r.id,i.id)),n={checks:c.project,score:h(c.project)},l=a.filter(r=>r.coverage!=="exempt"),m=l.reduce((r,i)=>r+i.weight,0),g=N([...l,{score:n.score,weight:b(m)}]);return{generatedAt:t.generatedAt??new Date().toISOString(),grade:$(g),procedures:a,project:n,score:g,summary:I(a,n),version:O}};export{O as MAP_VERSION,S as byCodepoint,W as scoreAdvisor};
@@ -0,0 +1 @@
1
+ import{i as n}from"./finding-BAVgqnCF.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";const o={categories:["SECURITY"],description:"A public procedure runs an AI generation (`generateText`/`streamText`/`generateObject`/`streamObject`) with no `maxOutputTokens` bound. Generation bills per output token, so an anonymous caller can request arbitrarily long completions in a loop — a denial-of-wallet vector that also ties up worker time on long streams.",facing:"EXTERNAL",level:"WARN",name:"ai_unbounded_generation_public",remediation:"Cap the completion with `maxOutputTokens` in the generation config, and rate-limit the public entry point (`.use(rateLimit(...))`). For expensive models, prefer running generation from an internal, authenticated path rather than exposing it directly to anonymous callers.",run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>i(e.unboundedAiGeneration)&&e.visibility==="public").map(e=>n(o,{cacheKey:`ai_unbounded_generation_public:${e.file}:${e.exportName}`,detail:`\`${e.exportName}\` (${e.file}) is public and runs an AI generation with no \`maxOutputTokens\` bound — an anonymous caller can drive unbounded, billable completions in a loop. Add a \`maxOutputTokens\` cap and rate-limit the entry point.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind}})),source:"static",title:"Public procedure runs unbounded AI generation (no maxOutputTokens)"};export{o as default};
@@ -0,0 +1 @@
1
+ const o=[{fact:e=>e.writesUserTable,reason:"writes an identity table"},{fact:e=>e.callsMail,reason:"sends mail"},{fact:e=>e.fanOut,reason:"fans out to a privileged dispatch surface"},{fact:e=>e.usesInsertManyUnsafe,reason:"bypasses validators with insertManyUnsafe"},{fact:e=>e.unboundedAiGeneration,reason:"runs an unbounded AI generation"}],r="may exhibit sensitive behaviour — its handler body could not be read",i=e=>{const a=[];let n=!1;for(const s of o){const t=s.fact(e);t===!0?a.push(s.reason):t===void 0&&(n=!0)}return n&&a.push(r),{level:a.length===0?"none":"high",reasons:a}};export{i as default};
@@ -1 +1 @@
1
- import{byCodepoint as p}from"./MAP_VERSION-BS08jBkS.mjs";const l=new Set(["clean","exempt","failing","warned"]),d=(e,r)=>{const t=new Map(e.map(s=>[s.name,s.occurrences]));return r.some(s=>s.occurrences>(t.get(s.name)??0))},i=e=>typeof e=="number"&&Number.isFinite(e),g=e=>{if(typeof e!="object"||e===null)return!1;const r=e;return typeof r.id=="string"&&i(r.score)&&typeof r.coverage=="string"&&l.has(r.coverage)},m=e=>{if(typeof e!="object"||e===null)return!1;const r=e;return Array.isArray(r.checks)&&i(r.score)},v=(e,r)=>{if(r.version!==e.version)return{comparable:!1,reason:"version-mismatch"};const t=new Map(r.procedures.map(o=>[o.id,o])),s=[],n=[],a=[];for(const o of e.procedures){if(o.coverage==="exempt")continue;const c=t.get(o.id);c!==void 0&&c.coverage!=="exempt"&&o.score<c.score&&s.push({after:o.score,before:c.score,id:o.id}),o.coverage==="failing"&&c?.coverage!=="failing"&&n.push(o.id),d(c?.checks??[],o.checks)&&a.push(o.id)}s.sort((o,c)=>p(o.id,c.id)),n.sort(p),a.sort(p);const u=e.score-r.score,f=d(r.project.checks,e.project.checks);return{comparable:!0,dropped:s,newFailing:n,projectRegressed:f,regressed:u<0||s.length>0||n.length>0||a.length>0||f,scoreDelta:u,worsened:a}},y=e=>{if(typeof e!="object"||e===null)return;const r=e;if(!(!i(r.version)||!i(r.score))&&!(!Array.isArray(r.procedures)||!r.procedures.every(t=>g(t)))&&m(r.project))return e};export{v as compareToBaseline,y as parseAdvisorMap};
1
+ import{byCodepoint as p}from"./MAP_VERSION-DxkFCiLX.mjs";const l=new Set(["clean","exempt","failing","warned"]),d=(e,r)=>{const t=new Map(e.map(s=>[s.name,s.occurrences]));return r.some(s=>s.occurrences>(t.get(s.name)??0))},i=e=>typeof e=="number"&&Number.isFinite(e),g=e=>{if(typeof e!="object"||e===null)return!1;const r=e;return typeof r.id=="string"&&i(r.score)&&typeof r.coverage=="string"&&l.has(r.coverage)},m=e=>{if(typeof e!="object"||e===null)return!1;const r=e;return Array.isArray(r.checks)&&i(r.score)},v=(e,r)=>{if(r.version!==e.version)return{comparable:!1,reason:"version-mismatch"};const t=new Map(r.procedures.map(o=>[o.id,o])),s=[],n=[],a=[];for(const o of e.procedures){if(o.coverage==="exempt")continue;const c=t.get(o.id);c!==void 0&&c.coverage!=="exempt"&&o.score<c.score&&s.push({after:o.score,before:c.score,id:o.id}),o.coverage==="failing"&&c?.coverage!=="failing"&&n.push(o.id),d(c?.checks??[],o.checks)&&a.push(o.id)}s.sort((o,c)=>p(o.id,c.id)),n.sort(p),a.sort(p);const u=e.score-r.score,f=d(r.project.checks,e.project.checks);return{comparable:!0,dropped:s,newFailing:n,projectRegressed:f,regressed:u<0||s.length>0||n.length>0||a.length>0||f,scoreDelta:u,worsened:a}},y=e=>{if(typeof e!="object"||e===null)return;const r=e;if(!(!i(r.version)||!i(r.score))&&!(!Array.isArray(r.procedures)||!r.procedures.every(t=>g(t)))&&m(r.project))return e};export{v as compareToBaseline,y as parseAdvisorMap};
@@ -0,0 +1 @@
1
+ import{i as n}from"./finding-BAVgqnCF.mjs";const i=20,s=.1,l={categories:["PERFORMANCE"],description:"A function's observed error rate has cleared a threshold high enough to be a genuine reliability signal — the same errorHotspots signal `lunora insights` reports, now gated in `lunora advisor`.",facing:"EXTERNAL",level:"WARN",name:"error_rate_outlier",remediation:"Read the function's `lastErrorMessage` / traces to find the failing dependency or input shape, then fix or guard it; `lunora insights` ranks every function by error rate for the same window.",run:o=>{const t=[];for(const r of o.functionMetrics??[]){if(r.calls<i)continue;const e=r.errors/r.calls;if(e<s)continue;const a=Math.round(e*100);t.push(n(l,{cacheKey:`error_rate_outlier:${r.path}`,detail:`"${r.path}" errored on ${r.errors.toString()} of ${r.calls.toString()} calls (${a.toString()}%) over the observed window.`,metadata:{calls:r.calls,errors:r.errors,path:r.path,rate:e}}))}return t},source:"runtime",title:"Error-rate outlier"};export{l as default};
@@ -0,0 +1 @@
1
+ import{i as r}from"./finding-BAVgqnCF.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";const s={categories:["SECURITY"],description:"A public procedure calls `ctx.db.insertManyUnsafe(...)`, which writes rows straight to storage and bypasses both the per-row validators and the schema's insert triggers (server-trusted columns, ownership stamping, RLS write checks). On a public entry point the rows are request-shaped, so a caller can write columns they shouldn't control and skip every trigger-enforced invariant.",facing:"EXTERNAL",level:"WARN",name:"insert_many_unsafe_user_data",remediation:"Use the validated `ctx.db.insert(...)` path in public procedures so validators and insert triggers run. If you need the unsafe bulk write, move it into an internal function (`internalMutation`/`internalAction`) that is fed only server-constructed rows, never request input.",run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>i(e.usesInsertManyUnsafe)&&e.visibility==="public").map(e=>r(s,{cacheKey:`insert_many_unsafe_user_data:${e.file}:${e.exportName}`,detail:`\`${e.exportName}\` (${e.file}) is public and calls \`ctx.db.insertManyUnsafe(...)\`, bypassing validators and insert triggers on request-shaped rows. Switch to \`ctx.db.insert(...)\`, or move the bulk write into an internal function fed server-built rows.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind}})),source:"static",title:"Public procedure uses insertManyUnsafe (bypasses validators and triggers)"};export{s as default};
@@ -0,0 +1 @@
1
+ import{i as l}from"./finding-BAVgqnCF.mjs";const u={categories:["SCHEMA"],description:"A `query`/`mutation` handler calls a non-deterministic API (`Date.now`, `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues`, or `fetch`). A `query` may be re-run by a live subscription, so non-determinism there can flicker between evaluations (WARN). An ordinary `mutation` handler does not replay on this runtime — it runs at most once per logical write — so this is informational there (INFO) unless the mutation is itself invoked from a workflow step or queue consumer that can replay.",facing:"EXTERNAL",level:"WARN",name:"nondeterministic_query_mutation",remediation:"For a `query`: move the non-deterministic call into an `action(...)` (which runs once and may use ambient APIs), then pass the computed value into the mutation as an argument, or accept that the value may differ across re-evaluations. For an ordinary `mutation`: no action needed — the handler runs at most once per logical write on this runtime. If the mutation is dispatched from inside a workflow step or queue consumer, treat it like an action value instead, since the surrounding step/consumer can replay.",run:a=>{if(a.nondeterministicCalls===void 0)return[];const n=[],o=new Map;for(const e of a.nondeterministicCalls){const t=`${e.file}:${e.line.toString()}:${e.callee}`,i=(o.get(t)??0)+1;o.set(t,i);const r=i>1?`:${i.toString()}`:"",s={callee:e.callee,exportName:e.exportName,file:e.file,kind:e.kind,line:e.line};if(e.kind==="mutation"){n.push(l(u,{cacheKey:`nondeterministic_query_mutation:${t}${r}`,detail:`\`${e.callee}(…)\` in ${e.exportName} (${e.file}:${e.line.toString()}) runs inside a mutation handler. Ordinary mutations don't replay on this runtime (idempotency dedup returns a cached result rather than re-running the handler, and an OCC conflict throws to the caller instead of retrying internally), so this is informational — no action needed unless \`${e.exportName}\` is invoked from a workflow step or queue consumer that can itself replay.`,facing:"INTERNAL",level:"INFO",metadata:s}));continue}n.push(l(u,{cacheKey:`nondeterministic_query_mutation:${t}${r}`,detail:`\`${e.callee}(…)\` in ${e.exportName} (${e.file}:${e.line.toString()}) runs inside a query handler — a live subscription may re-run this query, so the result can differ between evaluations. Compute it in an \`action\` and pass the value into the mutation as an argument.`,metadata:s}))}return n},source:"static",title:"Non-deterministic call in query/mutation handler"};export{u as default};
@@ -0,0 +1 @@
1
+ import{i as a}from"./finding-BAVgqnCF.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";const r={categories:["SECURITY"],description:"A public procedure fans work out to a privileged dispatch surface (`ctx.scheduler.runAfter`/`runAt`, a `ctx.queues.<name>` send, or `ctx.workflows.<name>.create`) with no rate-limit guard. Each dispatch runs later under the system identity and bills the account, so an anonymous caller can amplify cost and flood the async surface in a loop.",facing:"EXTERNAL",level:"WARN",name:"privileged_fanout_from_public_procedure",remediation:"Gate the public entry point with a rate limit (`.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle), or make the fan-out procedure internal and trigger it only from an authenticated, guarded path. Never expose an unbounded scheduler/queue/workflow dispatch to anonymous callers.",run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>i(e.fanOut)&&e.visibility==="public"&&!e.usesRateLimit).map(e=>a(r,{cacheKey:`privileged_fanout_from_public_procedure:${e.file}:${e.exportName}`,detail:`\`${e.exportName}\` (${e.file}) is public and fans work out to a privileged dispatch surface (scheduler/queue/workflow) with no rate limit — an anonymous caller can drive that billable, system-identity dispatch in a loop. Add a rate-limit guard, or make it internal and trigger it from an authenticated path.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind}})),source:"static",title:"Public procedure fans out to a privileged dispatch surface"};export{r as default};
@@ -0,0 +1 @@
1
+ const i=t=>t!==!1;export{i as m};
@@ -0,0 +1 @@
1
+ import{i as s}from"./finding-BAVgqnCF.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";import{i as o}from"./helpers-DwlvEUCh.mjs";const r={categories:["SECURITY"],description:"A public `mutation`/`action` that creates a user/session/account row has no disposable-email gate. Throwaway mailboxes farm free trials, evade bans, and pollute the user table.",facing:"EXTERNAL",level:"WARN",name:"signup_mutation_without_disposable_gating",remediation:"Add `.use(emailGateMiddleware({ email: (ctx) => ctx.args.email }))` from `@lunora/auth/email-guard` to reject disposable domains at signup, or gate better-auth's native signup with `withEmailGate(...)` / `emailGateDatabaseHooks(...)`.",run:a=>{if(a.procedureProtections===void 0)return[];const t=[];for(const e of a.procedureProtections)!o(e)||!i(e.writesUserTable)||e.usesEmailGate||i(e.hasEmailArg)&&t.push(s(r,{cacheKey:`signup_mutation_without_disposable_gating:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) writes a user/session/account table but has no disposable-email gate. Add \`.use(emailGateMiddleware(...))\` from \`@lunora/auth/email-guard\`.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind,writesUserTable:e.writesUserTable}}));return t},source:"static",title:"Account-creating write without a disposable-email gate"};export{r as default};
@@ -0,0 +1 @@
1
+ import{i}from"./finding-BAVgqnCF.mjs";const o={categories:["SECURITY"],description:"A `ctx.storage.*` call uses an R2 object key taken directly from the handler's `args` with no server-side scoping. The bucket methods key by the caller-supplied string, so any caller can read, overwrite, or delete another user's object — object-level IDOR.",facing:"EXTERNAL",level:"ERROR",name:"storage_key_from_user_args",remediation:"Prefix the object key with a server-trusted identity (e.g. `${ctx.auth.userId}/…`) or resolve the object through a record the caller is known to own. Never pass request input straight through as an R2 object key.",run:t=>t.storageKeyAccesses===void 0?[]:t.storageKeyAccesses.map(e=>{const r=`\`ctx.storage.*.${e.method}\` in \`${e.exportName}\` (${e.file}:${e.line.toString()})`,s={exportName:e.exportName,file:e.file,line:e.line,method:e.method,visibility:e.visibility??"unknown"};return e.visibility==="internal"?i(o,{cacheKey:`storage_key_from_user_args:${e.file}:${e.line.toString()}`,detail:`${r} uses an object key derived from \`args\` with no server-side scoping. This is expected for an \`internal\` procedure — no caller can reach it directly, so the key is only ever supplied by trusted server code. Audit the PUBLIC procedures that dispatch to it: if one forwards \`args\` straight through, the IDOR is there.`,facing:"INTERNAL",level:"INFO",metadata:s}):i(o,{cacheKey:`storage_key_from_user_args:${e.file}:${e.line.toString()}`,detail:`${r} uses an object key derived from \`args\` with no server-side scoping — any caller can read/overwrite/delete another user's object (IDOR). Prefix the key with a server-trusted identity (e.g. \`\${ctx.auth.userId}/…\`) or resolve the object through an owned record.`,metadata:s})}),source:"static",title:"R2 object key taken directly from user args (IDOR)"};export{o as default};
@@ -0,0 +1 @@
1
+ import{i as o}from"./finding-BAVgqnCF.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";import{i as n}from"./helpers-DwlvEUCh.mjs";const c=t=>t.writesUserTable===!0?"writes a user/session table":t.callsMail===!0?"sends mail":t.analyzableBody===!1?"may write a user/session table or send mail — its handler body could not be read":"may write a user/session table or send mail — at least one could not be determined",l={categories:["SECURITY"],description:"A public `mutation`/`action` that creates a user/session or sends mail has no CAPTCHA / bot check. Account-creating and mail-sending endpoints are prime automated-abuse targets (credential stuffing, mailbox flooding, disposable-account farming).",facing:"EXTERNAL",level:"WARN",name:"user_creating_mutation_without_captcha",remediation:"Add a server-verified human check: `.use(verifyTurnstile({ secret, token }))` from `@lunora/auth`, or wrap it with `.use(protectPublic({ rateLimit, captcha }))` from `@lunora/server`. Pair with a rate limit for defense in depth.",run:t=>{if(t.procedureProtections===void 0)return[];const a=[];for(const e of t.procedureProtections){const r=i(e.writesUserTable)||i(e.callsMail);if(!n(e)||!r||e.usesCaptcha)continue;const s=c(e);a.push(o(l,{cacheKey:`user_creating_mutation_without_captcha:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) ${s} but has no CAPTCHA check. Add \`.use(verifyTurnstile(...))\` or \`.use(protectPublic({ captcha }))\`.`,metadata:{callsMail:e.callsMail,exportName:e.exportName,file:e.file,kind:e.kind,writesUserTable:e.writesUserTable}}))}return a},source:"static",title:"Account-creating / mail-sending write without a CAPTCHA"};export{l as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/advisor",
3
- "version": "1.0.0-alpha.60",
3
+ "version": "1.0.0-alpha.61",
4
4
  "description": "Schema & query lints (splinter-style advisors) for Lunora, feeding the Studio Advisors view",
5
5
  "keywords": [
6
6
  "advisor",
@@ -46,8 +46,8 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/errors": "1.0.0-alpha.10",
50
- "@lunora/server": "1.0.0-alpha.56"
49
+ "@lunora/errors": "1.0.0-alpha.12",
50
+ "@lunora/server": "1.0.0-alpha.57"
51
51
  },
52
52
  "engines": {
53
53
  "node": "^22.15.0 || >=24.11.0"
@@ -1 +0,0 @@
1
- import{LunoraError as m}from"@lunora/errors";const y=/^[\w.-]+$/u,c={indexHit:{event:"lunora.index.hit",index:"blob3",table:"blob2"},shardRequest:{event:"lunora.shard.request",group:"blob3",shardKey:"blob2"},tableScan:{event:"lunora.table.scan",table:"blob2"}},p=e=>{const a=typeof e=="number"?e:Number(e);return Number.isFinite(a)?a:0},o=e=>typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"||typeof e=="bigint"?String(e):"",$=e=>{if(!y.test(e))throw new m("INTERNAL",`@lunora/advisor: invalid Analytics Engine dataset name "${e}" — expected a bare table identifier.`)},u=e=>`'${e.replaceAll("\\","\\\\").replaceAll("'","''")}'`,h=async(e,a)=>{try{return(await e.query(a)).rows}catch{return[]}},x=async(e,a)=>{const{event:s,group:n,shardKey:r}=c.shardRequest,l=a.group===void 0?"":` AND ${n} = ${u(a.group)}`,i=`SELECT ${r} AS shardKey, ${n} AS shardGroup, sum(_sample_interval) AS requests FROM ${a.dataset} WHERE blob1 = ${u(s)}${l} GROUP BY shardKey, shardGroup`;return(await h(e,i)).map(d=>{const b=o(d.shardGroup);return{...b===""?{}:{group:b},requests:p(d.requests),shardKey:o(d.shardKey)}})},E=async(e,a)=>{const{event:s,table:n}=c.tableScan,r=`SELECT ${n} AS scanTable, sum(_sample_interval) AS scans FROM ${a.dataset} WHERE blob1 = ${u(s)} GROUP BY scanTable`;return(await h(e,r)).map(l=>({scans:p(l.scans),table:o(l.scanTable)}))},S=async(e,a)=>{const{event:s,index:n,table:r}=c.indexHit,l=`SELECT ${r} AS hitTable, ${n} AS hitIndex, sum(_sample_interval) AS reads FROM ${a.dataset} WHERE blob1 = ${u(s)} GROUP BY hitTable, hitIndex`,i=(await h(e,l)).map(t=>({index:o(t.hitIndex),reads:p(t.reads),table:o(t.hitTable)}));if(a.declaredIndexes===void 0)return i;const d=new Set(i.map(t=>`${t.table}\0${t.index}`)),b=a.declaredIndexes.filter(t=>!d.has(`${t.table}\0${t.index}`)).map(t=>({index:t.index,reads:0,table:t.table}));return[...i,...b]},R=async(e,a)=>{$(a.dataset);const[s,n,r]=await Promise.all([x(e,a),E(e,a),S(e,a)]);return{indexHits:r,shardTraffic:s,tableScans:n}};export{c as AE_METRIC_EVENTS,R as loadAnalyticsRuntimeMetrics};
@@ -1 +0,0 @@
1
- import{i as n}from"./finding-BAVgqnCF.mjs";const o={categories:["SECURITY"],description:"A public procedure runs an AI generation (`generateText`/`streamText`/`generateObject`/`streamObject`) with no `maxOutputTokens` bound. Generation bills per output token, so an anonymous caller can request arbitrarily long completions in a loop — a denial-of-wallet vector that also ties up worker time on long streams.",facing:"EXTERNAL",level:"WARN",name:"ai_unbounded_generation_public",remediation:"Cap the completion with `maxOutputTokens` in the generation config, and rate-limit the public entry point (`.use(rateLimit(...))`). For expensive models, prefer running generation from an internal, authenticated path rather than exposing it directly to anonymous callers.",run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>e.unboundedAiGeneration&&e.visibility==="public").map(e=>n(o,{cacheKey:`ai_unbounded_generation_public:${e.file}:${e.exportName}`,detail:`\`${e.exportName}\` (${e.file}) is public and runs an AI generation with no \`maxOutputTokens\` bound — an anonymous caller can drive unbounded, billable completions in a loop. Add a \`maxOutputTokens\` cap and rate-limit the entry point.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind}})),source:"static",title:"Public procedure runs unbounded AI generation (no maxOutputTokens)"};export{o as default};
@@ -1 +0,0 @@
1
- const t=[{reason:"writes an identity table",test:e=>e.writesUserTable},{reason:"sends mail",test:e=>e.callsMail},{reason:"fans out to a privileged dispatch surface",test:e=>e.fanOut},{reason:"bypasses validators with insertManyUnsafe",test:e=>e.usesInsertManyUnsafe},{reason:"runs an unbounded AI generation",test:e=>e.unboundedAiGeneration}],a=e=>{const s=[];for(const n of t)n.test(e)&&s.push(n.reason);return{level:s.length===0?"none":"high",reasons:s}};export{a as default};
@@ -1 +0,0 @@
1
- import{i as r}from"./finding-BAVgqnCF.mjs";const s={categories:["SECURITY"],description:"A public procedure calls `ctx.db.insertManyUnsafe(...)`, which writes rows straight to storage and bypasses both the per-row validators and the schema's insert triggers (server-trusted columns, ownership stamping, RLS write checks). On a public entry point the rows are request-shaped, so a caller can write columns they shouldn't control and skip every trigger-enforced invariant.",facing:"EXTERNAL",level:"WARN",name:"insert_many_unsafe_user_data",remediation:"Use the validated `ctx.db.insert(...)` path in public procedures so validators and insert triggers run. If you need the unsafe bulk write, move it into an internal function (`internalMutation`/`internalAction`) that is fed only server-constructed rows, never request input.",run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>e.usesInsertManyUnsafe&&e.visibility==="public").map(e=>r(s,{cacheKey:`insert_many_unsafe_user_data:${e.file}:${e.exportName}`,detail:`\`${e.exportName}\` (${e.file}) is public and calls \`ctx.db.insertManyUnsafe(...)\`, bypassing validators and insert triggers on request-shaped rows. Switch to \`ctx.db.insert(...)\`, or move the bulk write into an internal function fed server-built rows.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind}})),source:"static",title:"Public procedure uses insertManyUnsafe (bypasses validators and triggers)"};export{s as default};
@@ -1 +0,0 @@
1
- import{i as s}from"./finding-BAVgqnCF.mjs";const l={categories:["SCHEMA"],description:"A `query`/`mutation` handler calls a non-deterministic API (`Date.now`, `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues`, or `fetch`). These handlers may be re-run on OCC retry / subscription re-evaluation, so they must be deterministic — time, randomness, and network belong in an `action`.",facing:"EXTERNAL",level:"WARN",name:"nondeterministic_query_mutation",remediation:"Move the non-deterministic call into an `action(...)` (which runs once and may use ambient APIs), then pass the computed value into the mutation as an argument — e.g. compute `Date.now()` in the action and call `ctx.runMutation(api.…, { now })`. Take generated ids/timestamps as args instead of producing them in the handler.",run:i=>{if(i.nondeterministicCalls===void 0)return[];const a=[],o=new Map;for(const e of i.nondeterministicCalls){const t=`${e.file}:${e.line.toString()}:${e.callee}`,n=(o.get(t)??0)+1;o.set(t,n);const r=n>1?`:${n.toString()}`:"";a.push(s(l,{cacheKey:`nondeterministic_query_mutation:${t}${r}`,detail:`\`${e.callee}(…)\` in ${e.exportName} (${e.file}:${e.line.toString()}) runs inside a ${e.kind} handler — query/mutation handlers must be deterministic. Compute it in an \`action\` and pass the value into the mutation as an argument.`,metadata:{callee:e.callee,exportName:e.exportName,file:e.file,kind:e.kind,line:e.line}}))}return a},source:"static",title:"Non-deterministic call in query/mutation handler"};export{l as default};
@@ -1 +0,0 @@
1
- import{i as a}from"./finding-BAVgqnCF.mjs";const i={categories:["SECURITY"],description:"A public procedure fans work out to a privileged dispatch surface (`ctx.scheduler.runAfter`/`runAt`, a `ctx.queues.<name>` send, or `ctx.workflows.<name>.create`) with no rate-limit guard. Each dispatch runs later under the system identity and bills the account, so an anonymous caller can amplify cost and flood the async surface in a loop.",facing:"EXTERNAL",level:"WARN",name:"privileged_fanout_from_public_procedure",remediation:"Gate the public entry point with a rate limit (`.use(rateLimit(...))` or a `protectPublic({ rateLimit })` bundle), or make the fan-out procedure internal and trigger it only from an authenticated, guarded path. Never expose an unbounded scheduler/queue/workflow dispatch to anonymous callers.",run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>e.fanOut&&e.visibility==="public"&&!e.usesRateLimit).map(e=>a(i,{cacheKey:`privileged_fanout_from_public_procedure:${e.file}:${e.exportName}`,detail:`\`${e.exportName}\` (${e.file}) is public and fans work out to a privileged dispatch surface (scheduler/queue/workflow) with no rate limit — an anonymous caller can drive that billable, system-identity dispatch in a loop. Add a rate-limit guard, or make it internal and trigger it from an authenticated path.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind}})),source:"static",title:"Public procedure fans out to a privileged dispatch surface"};export{i as default};
@@ -1 +0,0 @@
1
- import{i}from"./finding-BAVgqnCF.mjs";import{i as s}from"./helpers-DwlvEUCh.mjs";const o={categories:["SECURITY"],description:"A public `mutation`/`action` that creates a user/session/account row has no disposable-email gate. Throwaway mailboxes farm free trials, evade bans, and pollute the user table.",facing:"EXTERNAL",level:"WARN",name:"signup_mutation_without_disposable_gating",remediation:"Add `.use(emailGateMiddleware({ email: (ctx) => ctx.args.email }))` from `@lunora/auth/email-guard` to reject disposable domains at signup, or gate better-auth's native signup with `withEmailGate(...)` / `emailGateDatabaseHooks(...)`.",run:a=>{if(a.procedureProtections===void 0)return[];const t=[];for(const e of a.procedureProtections)!s(e)||!e.writesUserTable||e.usesEmailGate||e.hasEmailArg!==!1&&t.push(i(o,{cacheKey:`signup_mutation_without_disposable_gating:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) writes a user/session/account table but has no disposable-email gate. Add \`.use(emailGateMiddleware(...))\` from \`@lunora/auth/email-guard\`.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind,writesUserTable:e.writesUserTable}}));return t},source:"static",title:"Account-creating write without a disposable-email gate"};export{o as default};
@@ -1 +0,0 @@
1
- import{m as t}from"./argument-derived-sink-BpO3NPPM.mjs";const o=t({cacheKey:e=>`storage_key_from_user_args:${e.file}:${e.line.toString()}`,categories:["SECURITY"],description:"A `ctx.storage.*` call uses an R2 object key taken directly from the handler's `args` with no server-side scoping. The bucket methods key by the caller-supplied string, so any caller can read, overwrite, or delete another user's object — object-level IDOR.",detail:e=>`\`ctx.storage.*.${e.method}\` in \`${e.exportName}\` (${e.file}:${e.line.toString()}) uses an object key derived from \`args\` with no server-side scoping — any caller can read/overwrite/delete another user's object (IDOR). Prefix the key with a server-trusted identity (e.g. \`\${ctx.auth.userId}/…\`) or resolve the object through an owned record.`,facing:"EXTERNAL",getAccesses:e=>e.storageKeyAccesses,level:"ERROR",metadata:e=>({exportName:e.exportName,file:e.file,line:e.line,method:e.method}),name:"storage_key_from_user_args",remediation:"Prefix the object key with a server-trusted identity (e.g. `${ctx.auth.userId}/…`) or resolve the object through a record the caller is known to own. Never pass request input straight through as an R2 object key.",title:"R2 object key taken directly from user args (IDOR)"});export{o as default};
@@ -1 +0,0 @@
1
- import{i as s}from"./finding-BAVgqnCF.mjs";import{i as o}from"./helpers-DwlvEUCh.mjs";const c={categories:["SECURITY"],description:"A public `mutation`/`action` that creates a user/session or sends mail has no CAPTCHA / bot check. Account-creating and mail-sending endpoints are prime automated-abuse targets (credential stuffing, mailbox flooding, disposable-account farming).",facing:"EXTERNAL",level:"WARN",name:"user_creating_mutation_without_captcha",remediation:"Add a server-verified human check: `.use(verifyTurnstile({ secret, token }))` from `@lunora/auth`, or wrap it with `.use(protectPublic({ rateLimit, captcha }))` from `@lunora/server`. Pair with a rate limit for defense in depth.",run:t=>{if(t.procedureProtections===void 0)return[];const i=[];for(const e of t.procedureProtections){const a=e.writesUserTable||e.callsMail;if(!o(e)||!a||e.usesCaptcha)continue;const r=e.writesUserTable?"writes a user/session table":"sends mail";i.push(s(c,{cacheKey:`user_creating_mutation_without_captcha:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) ${r} but has no CAPTCHA check. Add \`.use(verifyTurnstile(...))\` or \`.use(protectPublic({ captcha }))\`.`,metadata:{callsMail:e.callsMail,exportName:e.exportName,file:e.file,kind:e.kind,writesUserTable:e.writesUserTable}}))}return i},source:"static",title:"Account-creating / mail-sending write without a CAPTCHA"};export{c as default};