@lunora/advisor 1.0.0-alpha.211 → 1.0.0-alpha.212

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -285,6 +285,11 @@ interface AdvisorCallEdge {
285
285
  kind: "call" | "enqueue" | "invoke" | "publish" | "schedule";
286
286
  /** 1-based line of the call, or `0` when unknown. */
287
287
  line: number;
288
+ /**
289
+ * `true` alongside `conditional` when only a loop's body stands in front of the hop.
290
+ * A queue consumer's batch loop sets it: the hop runs once per message it receives.
291
+ */
292
+ loopOnly?: true;
288
293
  /** Why the target could not be read statically — set exactly when `target` is absent. */
289
294
  reason?: string;
290
295
  /**
@@ -391,6 +396,8 @@ interface AdvisorContainer {
391
396
  enableInternet?: boolean;
392
397
  /** The `lunora/containers.ts` export name, e.g. `transcoder`. */
393
398
  exportName: string;
399
+ /** Declared `hardTimeout` value, when a static literal. Caps total lifetime, idle or not. */
400
+ hardTimeout?: number | string;
394
401
  /** Declared `instanceType`: a named size, or a custom `{ vcpu, memoryMib, diskMb }`. */
395
402
  instanceType?: string | {
396
403
  diskMb?: number;
@@ -402,6 +409,27 @@ interface AdvisorContainer {
402
409
  /** Declared `sleepAfter` value, when a static literal. */
403
410
  sleepAfter?: number | string;
404
411
  }
412
+ /**
413
+ * One `count()` call discovered in a function body — the input the `count_without_index`
414
+ * lint consumes. Produced by the codegen feeder (which reads `ctx.db.count("table", …)` and
415
+ * `ctx.db.<table>.count(…)` from the AST); runtime callers don't supply it, so the lint
416
+ * finds nothing there.
417
+ */
418
+ interface AdvisorCountRead {
419
+ /** Source file the call appears in (relative to the lunora dir, no extension). */
420
+ file: string;
421
+ /**
422
+ * `false` only when the table is known and declares no aggregate index, so the count is a
423
+ * `COUNT(*)` scan. A counter-backed or unknown table reads `true`.
424
+ */
425
+ indexed: boolean;
426
+ /** 1-based line of the `count(...)` call. */
427
+ line: number;
428
+ /** Who the call runs on behalf of. */
429
+ scope: AdvisorCallSiteScope;
430
+ /** Counted table name, or `""` when the table argument is not a string literal. */
431
+ table: string;
432
+ }
405
433
  /**
406
434
  * One CDC export-sink construction discovered in a function body — the input the
407
435
  * `export_sink_misconfigured` lint consumes. Produced by the codegen feeder,
@@ -1303,6 +1331,19 @@ interface AdvisorQueueTuning {
1303
1331
  interface AdvisorQueue {
1304
1332
  /** The `lunora/queues.ts` export name, e.g. `notifications`. */
1305
1333
  exportName: string;
1334
+ /**
1335
+ * The declaring file, lunora-relative without `.ts` (`queues`, or `billing/queues`).
1336
+ * Its handler's own call sites are attributed to `exportName` in this file.
1337
+ */
1338
+ filePath?: string;
1339
+ /**
1340
+ * Where a `handler:` passed by reference is declared, when it is exported from a
1341
+ * lunora file. The handler's call sites then belong to this queue, under that export.
1342
+ */
1343
+ handlerSite?: {
1344
+ exportName: string;
1345
+ file: string;
1346
+ };
1306
1347
  /** How the queue is consumed: `"push"` (a worker `queue()` handler) or `"pull"` (external HTTP). */
1307
1348
  mode: "pull" | "push";
1308
1349
  /**
@@ -2151,6 +2192,8 @@ interface LintContext {
2151
2192
  * the container lints find nothing.
2152
2193
  */
2153
2194
  containers?: ReadonlyArray<AdvisorContainer>;
2195
+ /** Every `count()` call found in function bodies; the `count_without_index` lint reads it. Absent for a runtime caller. */
2196
+ countReads?: ReadonlyArray<AdvisorCountRead>;
2154
2197
  /**
2155
2198
  * The `namespace:export` keys of the function-targeted cron jobs. A cron fires
2156
2199
  * its target without a request, so the target is server-driven even when it is
@@ -3132,6 +3175,18 @@ declare const commitOrderedHardDelete: Lint;
3132
3175
  * finding per arg-derived, unscoped `ctx.containers.*.get` call.
3133
3176
  */
3134
3177
  declare const containerInstanceKeyFromUserInput: Lint;
3178
+ /**
3179
+ * Flags a container whose idle window is longer than an hour. An instance that stops
3180
+ * receiving work keeps running, and billed, until `sleepAfter` elapses, so a long
3181
+ * window is a standing cost for every idle instance. The window an instance can really
3182
+ * idle for is the shorter of `sleepAfter` and `hardTimeout`, since `hardTimeout` stops
3183
+ * the instance however active it is. The check uses that effective window.
3184
+ *
3185
+ * A `sleepAfter` that was not a static literal, or that was omitted (the platform's
3186
+ * 10 minute default), is not flagged. Runs only when the codegen feeder supplied the
3187
+ * container declarations, so a runtime caller flags nothing.
3188
+ */
3189
+ declare const containerLongSleepAfter: Lint;
3135
3190
  /**
3136
3191
  * Flags a container declared on a large instance type. The big `standard-3` /
3137
3192
  * `standard-4` sizes (and large custom shapes) are billed on their provisioned
@@ -3155,6 +3210,18 @@ declare const containerOversizedInstance: Lint;
3155
3210
  declare const containerPublicInternet: Lint;
3156
3211
  declare const containerRuntimeEgressRelaxation: Lint;
3157
3212
  declare const containerStartEnableInternetOverride: Lint;
3213
+ /**
3214
+ * Flags a `count()` on a table that declares no aggregate index. `count()` reads a counter
3215
+ * only when an aggregate index covers it; otherwise it runs `SELECT COUNT(*)` over the
3216
+ * table, reading every row on each call. A count that runs per request or per poll repeats
3217
+ * that scan every time, and its cost grows with the table.
3218
+ *
3219
+ * The verdict is per table. A table that declares an aggregate index is not flagged, even
3220
+ * when a particular `where` falls outside that index, because the feeder cannot judge the
3221
+ * index's coverage. That is a known false negative. The count reads come from the codegen
3222
+ * feeder; runtime callers supply none, so this lint is a no-op there.
3223
+ */
3224
+ declare const countWithoutIndex: Lint;
3158
3225
  /**
3159
3226
  * Flags a function that writes to a table another module declares it owns
3160
3227
  * (`defineModule({ tables })`). Ownership is a boundary the app opted into: a
@@ -3192,7 +3259,7 @@ declare const crossModuleTableWrite: Lint;
3192
3259
  * cancelled or rolled back, drops out. Only hops in an export's own body count
3193
3260
  * — a helper's call may be guarded where the helper is called — and only
3194
3261
  * static targets. Re-arming outside `ctx.scheduler` (a raw Durable Object
3195
- * `setAlarm`, a queue re-enqueue) is not modelled.
3262
+ * `setAlarm`) is not modelled; a queue re-enqueue is `queue_dispatch_cycle`'s.
3196
3263
  *
3197
3264
  * Runs only when the codegen feeder supplied call edges (`context.callEdges`
3198
3265
  * present); a runtime caller flags nothing.
@@ -4164,6 +4231,21 @@ declare const publicMutationWithoutRatelimit: Lint;
4164
4231
  * from codegen, with no feeder required.
4165
4232
  */
4166
4233
  declare const publicTableRlsOptoutConfusion: Lint;
4234
+ /**
4235
+ * Flags a cycle through a queue or a topic in which every hop runs unconditionally: a
4236
+ * queue consumer that (directly, or through a function it runs) sends back to its own
4237
+ * queue, or a topic whose subscriber leads back to a publish. Each message handled then
4238
+ * produces another, so the queue never drains, and every delivery is billed.
4239
+ *
4240
+ * The cycle is built from the same call-site edges `dispatch_cycle` reads, plus the
4241
+ * enqueue, publish and subscription edges it ignores. A queue consumer is a node: its
4242
+ * call sites start from the queue, and its handler's batch loop counts as a loop, not a
4243
+ * guard. A cycle with no queue or topic node is `dispatch_cycle`'s, so this lint reports
4244
+ * only the components that include one.
4245
+ *
4246
+ * Runs only when the codegen feeder supplied call edges and queues; a runtime caller flags nothing.
4247
+ */
4248
+ declare const queueDispatchCycle: Lint;
4167
4249
  /**
4168
4250
  * Flags a declared queue — push or pull — that has no dead-letter queue.
4169
4251
  *
@@ -4333,7 +4415,10 @@ declare const rlsUncoveredTable: Lint;
4333
4415
  *
4334
4416
  * An unconditional self-dispatch is `dispatch_cycle`'s, which already reports it as
4335
4417
  * an error. This rule takes only the conditional ones, so each site gets one finding.
4336
- * A longer delay is left alone: it is a poll, not a spin.
4418
+ *
4419
+ * Two bands. Under {@link SHORT_DELAY_MS} is a spin (WARN): any row change counts as
4420
+ * progress. From there up to {@link POLL_DELAY_MS} is a poll (INFO): a rewrite of a row
4421
+ * on every tick is not progress, so only a removal clears it. Longer is left alone.
4337
4422
  *
4338
4423
  * Runs only when the codegen feeder supplied call edges (`context.callEdges` present).
4339
4424
  */
@@ -5136,4 +5221,4 @@ interface RunAdvisorOptions {
5136
5221
  * `static` lints at build time and defer `runtime` lints to a live shard.
5137
5222
  */
5138
5223
  declare const runAdvisor: (context: LintContext, options?: RunAdvisorOptions) => Finding[];
5139
- export { ALL_LINTS, type AdvisorAdminRoute, type AdvisorAiRawRun, type AdvisorAiToolSideEffect, type AdvisorArgumentDerivedFetch, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorAuthConfig, type AdvisorBrowserUrlAccess, type AdvisorCallEdge, type AdvisorCallSiteScope, type AdvisorConfigCall, type AdvisorContainer, type AdvisorContainerKeyAccess, type AdvisorContainerOverride, type AdvisorExportSink, type AdvisorFailOpenGuard, type AdvisorFlagRead, type AdvisorFlagSecurityDefault, type AdvisorGeoIndexUsage, type AdvisorHttpActionGuard, type AdvisorHttpHeaderWrite, type AdvisorHyperdriveCall, type AdvisorIdentityClaimRead, type AdvisorImageDeliveryUrlAccess, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorKeyedRelation, type AdvisorKvKeyAccess, type AdvisorMailRecipientAccess, type AdvisorMap, type AdvisorMaskProcedure, type AdvisorMaskStrategy, type AdvisorModule, type AdvisorMutatorDeclaration, 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 AdvisorRowEffect, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSoftDeleteRead, type AdvisorSqlInterpolation, type AdvisorStaleMigrationImport, type AdvisorStorageKeyAccess, type AdvisorStorageUpload, type AdvisorTable, type AdvisorTableScan, type AdvisorTableWrite, type AdvisorThroughRelation, type AdvisorUnboundedLoop, type AdvisorVectorNamespaceAccess, type AdvisorWorkflow, type AdvisorWorkflowCall, type AdvisorWranglerVariable, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type BaselineComparison, type CallSiteMetadata, 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, callSiteCallers, callSiteDescription, callSiteLabel, callSiteMetadata, circularFk, classifySensitivity, commitOrderedHardDelete, compareToBaseline, containerInstanceKeyFromUserInput, containerOversizedInstance, containerPublicInternet, containerRuntimeEgressRelaxation, containerStartEnableInternetOverride, crossModuleTableWrite, dedupeCacheKeys, dispatchCycle, duplicateIndex, emptyIndex, errorWithoutCatalog, exportSinkMisconfigured, externalSourceIncrementalNoDeletePath, externalSourceOnGlobal, externalSourceUnscoped, fanOutBreadth, filterAfterIndexScan, filterOnPrimaryKey, filterWithoutIndex, flagGatesSecurityWithUnsafeDefault, flagReadInSubscription, fromServerSchema, geoIndexFieldNotGeopoint, geoIndexUnused, globalTableNearColumnLimit, gradeFromScore, hardcodedSecret, helperRole, hotShard, httpActionMissingAuthGuard, httpActionResponseHeaderInjection, hyperdriveOutsideAction, identityUndeclaredClaimTrusted, imagesUrlSourceFromUserInput, indexReferencesUnknownField, indexUtilization, insertManyUnsafeUserData, isReachableSite, kvUnscopedUserKeyIdor, mailInboundDispatchWithoutVerify, mailRecipientFromRequestInput, maskUncoveredPiiColumn, maskWeakHashStrategyOnPii, maskedRelationLeakViaWith, mutatorFullRowReplace, mutatorWithoutOwnerScope, nondeterministicQueryMutation, normalizeIdUsedAsAuthorization, notifyMissingPushConfig, notifySendOutsideAction, outputProjectionMissingOnPublicRead, ownerFieldFromArgsNotAuth, parseAdvisorMap, paymentCreateWithoutAuthorize, paymentWebhookWideTolerance, plaintextSecretInWranglerVariables, policyReferencesUnknownTable, privilegedDispatchUnvalidatedPayload, privilegedFanoutFromPublicProcedure, procedureWithoutStructuredEvent, publicArgumentUsesAny, publicMutationWithoutRatelimit, publicTableRlsOptoutConfusion, queueWithoutDlq, r2sqlOutsideAction, ratelimitDefaultMemoryStore, ratelimitKeySpoofableOrGlobal, ratelimitMiddlewareFailOpen, readCallSiteCallers, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, scoreAdvisor, selfRescheduleWithoutProgress, shapeTargetsGlobalTable, shapeUnknownTable, signupMutationWithoutDisposableGating, softDeleteIncludeDeletedFromArgs, sqlInjectionRisk, storageGenerateUploadUrlNoContentTypePin, storageKeyFromUserArgs, storagePresignedUrlForPrivateContent, storageUploadWithoutContentTypeAllowlist, storageUploadWithoutMaxSize, tableWithoutInsert, throughRelationMissingUniqueIndex, topicTooManySubscriptions, ttlFieldNotTimestamp, unboundedCollectOnOpenRange, unboundedLoop, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, unrestrictedWhereBranch, userCreatingMutationWithoutCaptcha, vectorsNamespaceFromUserInput, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
5224
+ export { ALL_LINTS, type AdvisorAdminRoute, type AdvisorAiRawRun, type AdvisorAiToolSideEffect, type AdvisorArgumentDerivedFetch, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorAuthConfig, type AdvisorBrowserUrlAccess, type AdvisorCallEdge, type AdvisorCallSiteScope, type AdvisorConfigCall, type AdvisorContainer, type AdvisorContainerKeyAccess, type AdvisorContainerOverride, type AdvisorCountRead, type AdvisorExportSink, type AdvisorFailOpenGuard, type AdvisorFlagRead, type AdvisorFlagSecurityDefault, type AdvisorGeoIndexUsage, type AdvisorHttpActionGuard, type AdvisorHttpHeaderWrite, type AdvisorHyperdriveCall, type AdvisorIdentityClaimRead, type AdvisorImageDeliveryUrlAccess, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorKeyedRelation, type AdvisorKvKeyAccess, type AdvisorMailRecipientAccess, type AdvisorMap, type AdvisorMaskProcedure, type AdvisorMaskStrategy, type AdvisorModule, type AdvisorMutatorDeclaration, 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 AdvisorRowEffect, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSoftDeleteRead, type AdvisorSqlInterpolation, type AdvisorStaleMigrationImport, type AdvisorStorageKeyAccess, type AdvisorStorageUpload, type AdvisorTable, type AdvisorTableScan, type AdvisorTableWrite, type AdvisorThroughRelation, type AdvisorUnboundedLoop, type AdvisorVectorNamespaceAccess, type AdvisorWorkflow, type AdvisorWorkflowCall, type AdvisorWranglerVariable, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type BaselineComparison, type CallSiteMetadata, 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, callSiteCallers, callSiteDescription, callSiteLabel, callSiteMetadata, circularFk, classifySensitivity, commitOrderedHardDelete, compareToBaseline, containerInstanceKeyFromUserInput, containerLongSleepAfter, containerOversizedInstance, containerPublicInternet, containerRuntimeEgressRelaxation, containerStartEnableInternetOverride, countWithoutIndex, crossModuleTableWrite, dedupeCacheKeys, dispatchCycle, duplicateIndex, emptyIndex, errorWithoutCatalog, exportSinkMisconfigured, externalSourceIncrementalNoDeletePath, externalSourceOnGlobal, externalSourceUnscoped, fanOutBreadth, filterAfterIndexScan, filterOnPrimaryKey, filterWithoutIndex, flagGatesSecurityWithUnsafeDefault, flagReadInSubscription, fromServerSchema, geoIndexFieldNotGeopoint, geoIndexUnused, globalTableNearColumnLimit, gradeFromScore, hardcodedSecret, helperRole, hotShard, httpActionMissingAuthGuard, httpActionResponseHeaderInjection, hyperdriveOutsideAction, identityUndeclaredClaimTrusted, imagesUrlSourceFromUserInput, indexReferencesUnknownField, indexUtilization, insertManyUnsafeUserData, isReachableSite, kvUnscopedUserKeyIdor, mailInboundDispatchWithoutVerify, mailRecipientFromRequestInput, maskUncoveredPiiColumn, maskWeakHashStrategyOnPii, maskedRelationLeakViaWith, mutatorFullRowReplace, mutatorWithoutOwnerScope, nondeterministicQueryMutation, normalizeIdUsedAsAuthorization, notifyMissingPushConfig, notifySendOutsideAction, outputProjectionMissingOnPublicRead, ownerFieldFromArgsNotAuth, parseAdvisorMap, paymentCreateWithoutAuthorize, paymentWebhookWideTolerance, plaintextSecretInWranglerVariables, policyReferencesUnknownTable, privilegedDispatchUnvalidatedPayload, privilegedFanoutFromPublicProcedure, procedureWithoutStructuredEvent, publicArgumentUsesAny, publicMutationWithoutRatelimit, publicTableRlsOptoutConfusion, queueDispatchCycle, queueWithoutDlq, r2sqlOutsideAction, ratelimitDefaultMemoryStore, ratelimitKeySpoofableOrGlobal, ratelimitMiddlewareFailOpen, readCallSiteCallers, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, scoreAdvisor, selfRescheduleWithoutProgress, shapeTargetsGlobalTable, shapeUnknownTable, signupMutationWithoutDisposableGating, softDeleteIncludeDeletedFromArgs, sqlInjectionRisk, storageGenerateUploadUrlNoContentTypePin, storageKeyFromUserArgs, storagePresignedUrlForPrivateContent, storageUploadWithoutContentTypeAllowlist, storageUploadWithoutMaxSize, tableWithoutInsert, throughRelationMissingUniqueIndex, topicTooManySubscriptions, ttlFieldNotTimestamp, unboundedCollectOnOpenRange, unboundedLoop, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, unrestrictedWhereBranch, userCreatingMutationWithoutCaptcha, vectorsNamespaceFromUserInput, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
package/dist/index.d.ts CHANGED
@@ -285,6 +285,11 @@ interface AdvisorCallEdge {
285
285
  kind: "call" | "enqueue" | "invoke" | "publish" | "schedule";
286
286
  /** 1-based line of the call, or `0` when unknown. */
287
287
  line: number;
288
+ /**
289
+ * `true` alongside `conditional` when only a loop's body stands in front of the hop.
290
+ * A queue consumer's batch loop sets it: the hop runs once per message it receives.
291
+ */
292
+ loopOnly?: true;
288
293
  /** Why the target could not be read statically — set exactly when `target` is absent. */
289
294
  reason?: string;
290
295
  /**
@@ -391,6 +396,8 @@ interface AdvisorContainer {
391
396
  enableInternet?: boolean;
392
397
  /** The `lunora/containers.ts` export name, e.g. `transcoder`. */
393
398
  exportName: string;
399
+ /** Declared `hardTimeout` value, when a static literal. Caps total lifetime, idle or not. */
400
+ hardTimeout?: number | string;
394
401
  /** Declared `instanceType`: a named size, or a custom `{ vcpu, memoryMib, diskMb }`. */
395
402
  instanceType?: string | {
396
403
  diskMb?: number;
@@ -402,6 +409,27 @@ interface AdvisorContainer {
402
409
  /** Declared `sleepAfter` value, when a static literal. */
403
410
  sleepAfter?: number | string;
404
411
  }
412
+ /**
413
+ * One `count()` call discovered in a function body — the input the `count_without_index`
414
+ * lint consumes. Produced by the codegen feeder (which reads `ctx.db.count("table", …)` and
415
+ * `ctx.db.<table>.count(…)` from the AST); runtime callers don't supply it, so the lint
416
+ * finds nothing there.
417
+ */
418
+ interface AdvisorCountRead {
419
+ /** Source file the call appears in (relative to the lunora dir, no extension). */
420
+ file: string;
421
+ /**
422
+ * `false` only when the table is known and declares no aggregate index, so the count is a
423
+ * `COUNT(*)` scan. A counter-backed or unknown table reads `true`.
424
+ */
425
+ indexed: boolean;
426
+ /** 1-based line of the `count(...)` call. */
427
+ line: number;
428
+ /** Who the call runs on behalf of. */
429
+ scope: AdvisorCallSiteScope;
430
+ /** Counted table name, or `""` when the table argument is not a string literal. */
431
+ table: string;
432
+ }
405
433
  /**
406
434
  * One CDC export-sink construction discovered in a function body — the input the
407
435
  * `export_sink_misconfigured` lint consumes. Produced by the codegen feeder,
@@ -1303,6 +1331,19 @@ interface AdvisorQueueTuning {
1303
1331
  interface AdvisorQueue {
1304
1332
  /** The `lunora/queues.ts` export name, e.g. `notifications`. */
1305
1333
  exportName: string;
1334
+ /**
1335
+ * The declaring file, lunora-relative without `.ts` (`queues`, or `billing/queues`).
1336
+ * Its handler's own call sites are attributed to `exportName` in this file.
1337
+ */
1338
+ filePath?: string;
1339
+ /**
1340
+ * Where a `handler:` passed by reference is declared, when it is exported from a
1341
+ * lunora file. The handler's call sites then belong to this queue, under that export.
1342
+ */
1343
+ handlerSite?: {
1344
+ exportName: string;
1345
+ file: string;
1346
+ };
1306
1347
  /** How the queue is consumed: `"push"` (a worker `queue()` handler) or `"pull"` (external HTTP). */
1307
1348
  mode: "pull" | "push";
1308
1349
  /**
@@ -2151,6 +2192,8 @@ interface LintContext {
2151
2192
  * the container lints find nothing.
2152
2193
  */
2153
2194
  containers?: ReadonlyArray<AdvisorContainer>;
2195
+ /** Every `count()` call found in function bodies; the `count_without_index` lint reads it. Absent for a runtime caller. */
2196
+ countReads?: ReadonlyArray<AdvisorCountRead>;
2154
2197
  /**
2155
2198
  * The `namespace:export` keys of the function-targeted cron jobs. A cron fires
2156
2199
  * its target without a request, so the target is server-driven even when it is
@@ -3132,6 +3175,18 @@ declare const commitOrderedHardDelete: Lint;
3132
3175
  * finding per arg-derived, unscoped `ctx.containers.*.get` call.
3133
3176
  */
3134
3177
  declare const containerInstanceKeyFromUserInput: Lint;
3178
+ /**
3179
+ * Flags a container whose idle window is longer than an hour. An instance that stops
3180
+ * receiving work keeps running, and billed, until `sleepAfter` elapses, so a long
3181
+ * window is a standing cost for every idle instance. The window an instance can really
3182
+ * idle for is the shorter of `sleepAfter` and `hardTimeout`, since `hardTimeout` stops
3183
+ * the instance however active it is. The check uses that effective window.
3184
+ *
3185
+ * A `sleepAfter` that was not a static literal, or that was omitted (the platform's
3186
+ * 10 minute default), is not flagged. Runs only when the codegen feeder supplied the
3187
+ * container declarations, so a runtime caller flags nothing.
3188
+ */
3189
+ declare const containerLongSleepAfter: Lint;
3135
3190
  /**
3136
3191
  * Flags a container declared on a large instance type. The big `standard-3` /
3137
3192
  * `standard-4` sizes (and large custom shapes) are billed on their provisioned
@@ -3155,6 +3210,18 @@ declare const containerOversizedInstance: Lint;
3155
3210
  declare const containerPublicInternet: Lint;
3156
3211
  declare const containerRuntimeEgressRelaxation: Lint;
3157
3212
  declare const containerStartEnableInternetOverride: Lint;
3213
+ /**
3214
+ * Flags a `count()` on a table that declares no aggregate index. `count()` reads a counter
3215
+ * only when an aggregate index covers it; otherwise it runs `SELECT COUNT(*)` over the
3216
+ * table, reading every row on each call. A count that runs per request or per poll repeats
3217
+ * that scan every time, and its cost grows with the table.
3218
+ *
3219
+ * The verdict is per table. A table that declares an aggregate index is not flagged, even
3220
+ * when a particular `where` falls outside that index, because the feeder cannot judge the
3221
+ * index's coverage. That is a known false negative. The count reads come from the codegen
3222
+ * feeder; runtime callers supply none, so this lint is a no-op there.
3223
+ */
3224
+ declare const countWithoutIndex: Lint;
3158
3225
  /**
3159
3226
  * Flags a function that writes to a table another module declares it owns
3160
3227
  * (`defineModule({ tables })`). Ownership is a boundary the app opted into: a
@@ -3192,7 +3259,7 @@ declare const crossModuleTableWrite: Lint;
3192
3259
  * cancelled or rolled back, drops out. Only hops in an export's own body count
3193
3260
  * — a helper's call may be guarded where the helper is called — and only
3194
3261
  * static targets. Re-arming outside `ctx.scheduler` (a raw Durable Object
3195
- * `setAlarm`, a queue re-enqueue) is not modelled.
3262
+ * `setAlarm`) is not modelled; a queue re-enqueue is `queue_dispatch_cycle`'s.
3196
3263
  *
3197
3264
  * Runs only when the codegen feeder supplied call edges (`context.callEdges`
3198
3265
  * present); a runtime caller flags nothing.
@@ -4164,6 +4231,21 @@ declare const publicMutationWithoutRatelimit: Lint;
4164
4231
  * from codegen, with no feeder required.
4165
4232
  */
4166
4233
  declare const publicTableRlsOptoutConfusion: Lint;
4234
+ /**
4235
+ * Flags a cycle through a queue or a topic in which every hop runs unconditionally: a
4236
+ * queue consumer that (directly, or through a function it runs) sends back to its own
4237
+ * queue, or a topic whose subscriber leads back to a publish. Each message handled then
4238
+ * produces another, so the queue never drains, and every delivery is billed.
4239
+ *
4240
+ * The cycle is built from the same call-site edges `dispatch_cycle` reads, plus the
4241
+ * enqueue, publish and subscription edges it ignores. A queue consumer is a node: its
4242
+ * call sites start from the queue, and its handler's batch loop counts as a loop, not a
4243
+ * guard. A cycle with no queue or topic node is `dispatch_cycle`'s, so this lint reports
4244
+ * only the components that include one.
4245
+ *
4246
+ * Runs only when the codegen feeder supplied call edges and queues; a runtime caller flags nothing.
4247
+ */
4248
+ declare const queueDispatchCycle: Lint;
4167
4249
  /**
4168
4250
  * Flags a declared queue — push or pull — that has no dead-letter queue.
4169
4251
  *
@@ -4333,7 +4415,10 @@ declare const rlsUncoveredTable: Lint;
4333
4415
  *
4334
4416
  * An unconditional self-dispatch is `dispatch_cycle`'s, which already reports it as
4335
4417
  * an error. This rule takes only the conditional ones, so each site gets one finding.
4336
- * A longer delay is left alone: it is a poll, not a spin.
4418
+ *
4419
+ * Two bands. Under {@link SHORT_DELAY_MS} is a spin (WARN): any row change counts as
4420
+ * progress. From there up to {@link POLL_DELAY_MS} is a poll (INFO): a rewrite of a row
4421
+ * on every tick is not progress, so only a removal clears it. Longer is left alone.
4337
4422
  *
4338
4423
  * Runs only when the codegen feeder supplied call edges (`context.callEdges` present).
4339
4424
  */
@@ -5136,4 +5221,4 @@ interface RunAdvisorOptions {
5136
5221
  * `static` lints at build time and defer `runtime` lints to a live shard.
5137
5222
  */
5138
5223
  declare const runAdvisor: (context: LintContext, options?: RunAdvisorOptions) => Finding[];
5139
- export { ALL_LINTS, type AdvisorAdminRoute, type AdvisorAiRawRun, type AdvisorAiToolSideEffect, type AdvisorArgumentDerivedFetch, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorAuthConfig, type AdvisorBrowserUrlAccess, type AdvisorCallEdge, type AdvisorCallSiteScope, type AdvisorConfigCall, type AdvisorContainer, type AdvisorContainerKeyAccess, type AdvisorContainerOverride, type AdvisorExportSink, type AdvisorFailOpenGuard, type AdvisorFlagRead, type AdvisorFlagSecurityDefault, type AdvisorGeoIndexUsage, type AdvisorHttpActionGuard, type AdvisorHttpHeaderWrite, type AdvisorHyperdriveCall, type AdvisorIdentityClaimRead, type AdvisorImageDeliveryUrlAccess, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorKeyedRelation, type AdvisorKvKeyAccess, type AdvisorMailRecipientAccess, type AdvisorMap, type AdvisorMaskProcedure, type AdvisorMaskStrategy, type AdvisorModule, type AdvisorMutatorDeclaration, 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 AdvisorRowEffect, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSoftDeleteRead, type AdvisorSqlInterpolation, type AdvisorStaleMigrationImport, type AdvisorStorageKeyAccess, type AdvisorStorageUpload, type AdvisorTable, type AdvisorTableScan, type AdvisorTableWrite, type AdvisorThroughRelation, type AdvisorUnboundedLoop, type AdvisorVectorNamespaceAccess, type AdvisorWorkflow, type AdvisorWorkflowCall, type AdvisorWranglerVariable, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type BaselineComparison, type CallSiteMetadata, 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, callSiteCallers, callSiteDescription, callSiteLabel, callSiteMetadata, circularFk, classifySensitivity, commitOrderedHardDelete, compareToBaseline, containerInstanceKeyFromUserInput, containerOversizedInstance, containerPublicInternet, containerRuntimeEgressRelaxation, containerStartEnableInternetOverride, crossModuleTableWrite, dedupeCacheKeys, dispatchCycle, duplicateIndex, emptyIndex, errorWithoutCatalog, exportSinkMisconfigured, externalSourceIncrementalNoDeletePath, externalSourceOnGlobal, externalSourceUnscoped, fanOutBreadth, filterAfterIndexScan, filterOnPrimaryKey, filterWithoutIndex, flagGatesSecurityWithUnsafeDefault, flagReadInSubscription, fromServerSchema, geoIndexFieldNotGeopoint, geoIndexUnused, globalTableNearColumnLimit, gradeFromScore, hardcodedSecret, helperRole, hotShard, httpActionMissingAuthGuard, httpActionResponseHeaderInjection, hyperdriveOutsideAction, identityUndeclaredClaimTrusted, imagesUrlSourceFromUserInput, indexReferencesUnknownField, indexUtilization, insertManyUnsafeUserData, isReachableSite, kvUnscopedUserKeyIdor, mailInboundDispatchWithoutVerify, mailRecipientFromRequestInput, maskUncoveredPiiColumn, maskWeakHashStrategyOnPii, maskedRelationLeakViaWith, mutatorFullRowReplace, mutatorWithoutOwnerScope, nondeterministicQueryMutation, normalizeIdUsedAsAuthorization, notifyMissingPushConfig, notifySendOutsideAction, outputProjectionMissingOnPublicRead, ownerFieldFromArgsNotAuth, parseAdvisorMap, paymentCreateWithoutAuthorize, paymentWebhookWideTolerance, plaintextSecretInWranglerVariables, policyReferencesUnknownTable, privilegedDispatchUnvalidatedPayload, privilegedFanoutFromPublicProcedure, procedureWithoutStructuredEvent, publicArgumentUsesAny, publicMutationWithoutRatelimit, publicTableRlsOptoutConfusion, queueWithoutDlq, r2sqlOutsideAction, ratelimitDefaultMemoryStore, ratelimitKeySpoofableOrGlobal, ratelimitMiddlewareFailOpen, readCallSiteCallers, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, scoreAdvisor, selfRescheduleWithoutProgress, shapeTargetsGlobalTable, shapeUnknownTable, signupMutationWithoutDisposableGating, softDeleteIncludeDeletedFromArgs, sqlInjectionRisk, storageGenerateUploadUrlNoContentTypePin, storageKeyFromUserArgs, storagePresignedUrlForPrivateContent, storageUploadWithoutContentTypeAllowlist, storageUploadWithoutMaxSize, tableWithoutInsert, throughRelationMissingUniqueIndex, topicTooManySubscriptions, ttlFieldNotTimestamp, unboundedCollectOnOpenRange, unboundedLoop, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, unrestrictedWhereBranch, userCreatingMutationWithoutCaptcha, vectorsNamespaceFromUserInput, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
5224
+ export { ALL_LINTS, type AdvisorAdminRoute, type AdvisorAiRawRun, type AdvisorAiToolSideEffect, type AdvisorArgumentDerivedFetch, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorAuthConfig, type AdvisorBrowserUrlAccess, type AdvisorCallEdge, type AdvisorCallSiteScope, type AdvisorConfigCall, type AdvisorContainer, type AdvisorContainerKeyAccess, type AdvisorContainerOverride, type AdvisorCountRead, type AdvisorExportSink, type AdvisorFailOpenGuard, type AdvisorFlagRead, type AdvisorFlagSecurityDefault, type AdvisorGeoIndexUsage, type AdvisorHttpActionGuard, type AdvisorHttpHeaderWrite, type AdvisorHyperdriveCall, type AdvisorIdentityClaimRead, type AdvisorImageDeliveryUrlAccess, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorKeyedRelation, type AdvisorKvKeyAccess, type AdvisorMailRecipientAccess, type AdvisorMap, type AdvisorMaskProcedure, type AdvisorMaskStrategy, type AdvisorModule, type AdvisorMutatorDeclaration, 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 AdvisorRowEffect, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSoftDeleteRead, type AdvisorSqlInterpolation, type AdvisorStaleMigrationImport, type AdvisorStorageKeyAccess, type AdvisorStorageUpload, type AdvisorTable, type AdvisorTableScan, type AdvisorTableWrite, type AdvisorThroughRelation, type AdvisorUnboundedLoop, type AdvisorVectorNamespaceAccess, type AdvisorWorkflow, type AdvisorWorkflowCall, type AdvisorWranglerVariable, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type BaselineComparison, type CallSiteMetadata, 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, callSiteCallers, callSiteDescription, callSiteLabel, callSiteMetadata, circularFk, classifySensitivity, commitOrderedHardDelete, compareToBaseline, containerInstanceKeyFromUserInput, containerLongSleepAfter, containerOversizedInstance, containerPublicInternet, containerRuntimeEgressRelaxation, containerStartEnableInternetOverride, countWithoutIndex, crossModuleTableWrite, dedupeCacheKeys, dispatchCycle, duplicateIndex, emptyIndex, errorWithoutCatalog, exportSinkMisconfigured, externalSourceIncrementalNoDeletePath, externalSourceOnGlobal, externalSourceUnscoped, fanOutBreadth, filterAfterIndexScan, filterOnPrimaryKey, filterWithoutIndex, flagGatesSecurityWithUnsafeDefault, flagReadInSubscription, fromServerSchema, geoIndexFieldNotGeopoint, geoIndexUnused, globalTableNearColumnLimit, gradeFromScore, hardcodedSecret, helperRole, hotShard, httpActionMissingAuthGuard, httpActionResponseHeaderInjection, hyperdriveOutsideAction, identityUndeclaredClaimTrusted, imagesUrlSourceFromUserInput, indexReferencesUnknownField, indexUtilization, insertManyUnsafeUserData, isReachableSite, kvUnscopedUserKeyIdor, mailInboundDispatchWithoutVerify, mailRecipientFromRequestInput, maskUncoveredPiiColumn, maskWeakHashStrategyOnPii, maskedRelationLeakViaWith, mutatorFullRowReplace, mutatorWithoutOwnerScope, nondeterministicQueryMutation, normalizeIdUsedAsAuthorization, notifyMissingPushConfig, notifySendOutsideAction, outputProjectionMissingOnPublicRead, ownerFieldFromArgsNotAuth, parseAdvisorMap, paymentCreateWithoutAuthorize, paymentWebhookWideTolerance, plaintextSecretInWranglerVariables, policyReferencesUnknownTable, privilegedDispatchUnvalidatedPayload, privilegedFanoutFromPublicProcedure, procedureWithoutStructuredEvent, publicArgumentUsesAny, publicMutationWithoutRatelimit, publicTableRlsOptoutConfusion, queueDispatchCycle, queueWithoutDlq, r2sqlOutsideAction, ratelimitDefaultMemoryStore, ratelimitKeySpoofableOrGlobal, ratelimitMiddlewareFailOpen, readCallSiteCallers, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, scoreAdvisor, selfRescheduleWithoutProgress, shapeTargetsGlobalTable, shapeUnknownTable, signupMutationWithoutDisposableGating, softDeleteIncludeDeletedFromArgs, sqlInjectionRisk, storageGenerateUploadUrlNoContentTypePin, storageKeyFromUserArgs, storagePresignedUrlForPrivateContent, storageUploadWithoutContentTypeAllowlist, storageUploadWithoutMaxSize, tableWithoutInsert, throughRelationMissingUniqueIndex, topicTooManySubscriptions, ttlFieldNotTimestamp, unboundedCollectOnOpenRange, unboundedLoop, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, unrestrictedWhereBranch, userCreatingMutationWithoutCaptcha, vectorsNamespaceFromUserInput, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{dedupeCacheKeys as c}from"./packem_shared/dedupeCacheKeys-DtBOHffV.mjs";import d from"./packem_shared/fanOutBreadth-CBtmZnoh.mjs";import u from"./packem_shared/hotShard-BwGYZ3Tq.mjs";import h from"./packem_shared/indexUtilization-CkVPZcVe.mjs";import g from"./packem_shared/actionFetchSsrf-BK-kvOPO.mjs";import b from"./packem_shared/actionWithoutErrorHandling-4GCBT0_z.mjs";import y from"./packem_shared/adminRouteWithoutGuard-DPE7LuNh.mjs";import S from"./packem_shared/aiRawRunEscapeHatch-BW7a8kZC.mjs";import w from"./packem_shared/aiRunWithoutLogging-CvHHtEN9.mjs";import v from"./packem_shared/aiToolSideEffectPromptInjection-9eJXRlKt.mjs";import R from"./packem_shared/aiUnboundedGenerationPublic-C17h6sMV.mjs";import A from"./packem_shared/allowUnauthenticatedShardAccessEnabled-BQufF5Kb.mjs";import U from"./packem_shared/authApiCallWithoutHeaders-CReJRsGW.mjs";import W from"./packem_shared/authCsrfCheckDisabled-CRIVsvb9.mjs";import T from"./packem_shared/authEmailVerificationDisabled-BjFlnHr2.mjs";import I from"./packem_shared/authScimWithoutTransactions-B5ATiz5A.mjs";import x from"./packem_shared/authSecureCookiesDisabled-By8PVum3.mjs";import C from"./packem_shared/authSessionFreshageZero-DuxX3Ln9.mjs";import k from"./packem_shared/authTrustedOriginsWildcard-BKIh6788.mjs";import N from"./packem_shared/browserAllowPrivateTargets-Cj5sizhv.mjs";import F from"./packem_shared/browserUserUrlWithoutAllowlist-CzDEraJB.mjs";import M from"./packem_shared/circularFk-DtcWFJxK.mjs";import O from"./packem_shared/commitOrderedHardDelete-BPdwOKA7.mjs";import D from"./packem_shared/containerInstanceKeyFromUserInput-Bl-tAzKV.mjs";import P from"./packem_shared/containerOversizedInstance-Bx89uR7E.mjs";import $ from"./packem_shared/containerPublicInternet-BFfZf_P4.mjs";import E from"./packem_shared/containerRuntimeEgressRelaxation-4tMrhq1k.mjs";import q from"./packem_shared/containerStartEnableInternetOverride-mN_kuRtk.mjs";import K from"./packem_shared/crossModuleTableWrite-DjiVTAwK.mjs";import L from"./packem_shared/dispatchCycle-CWc5Q0cd.mjs";import G from"./packem_shared/duplicateIndex-Cip6-Rpu.mjs";import _ from"./packem_shared/emptyIndex-BnHDcXza.mjs";import z from"./packem_shared/errorWithoutCatalog-BTfvaXHR.mjs";import H from"./packem_shared/exportSinkMisconfigured-JfbAx9AI.mjs";import B from"./packem_shared/externalSourceIncrementalNoDeletePath-BCzm3HzF.mjs";import j from"./packem_shared/externalSourceOnGlobal-CH7xbJ49.mjs";import V from"./packem_shared/externalSourceUnscoped-BxU2uSXk.mjs";import Q from"./packem_shared/filterAfterIndexScan-CV8aPq7v.mjs";import X from"./packem_shared/filterOnPrimaryKey-BeHVg4w3.mjs";import Z from"./packem_shared/filterWithoutIndex-BGv3jbgg.mjs";import J from"./packem_shared/flagGatesSecurityWithUnsafeDefault-B5FBDF8c.mjs";import Y from"./packem_shared/flagReadInSubscription-DabIhYGD.mjs";import oo from"./packem_shared/geoIndexFieldNotGeopoint-BMQuok2-.mjs";import eo from"./packem_shared/geoIndexUnused-D7C9Qr4U.mjs";import ro from"./packem_shared/globalTableNearColumnLimit-BFbBBd6A.mjs";import to from"./packem_shared/hardcodedSecret-Bw_4FYrs.mjs";import io from"./packem_shared/httpActionMissingAuthGuard-DhMIs1EI.mjs";import no from"./packem_shared/httpActionResponseHeaderInjection-mcRYd3Zw.mjs";import ao from"./packem_shared/hyperdriveOutsideAction-CPDdGP2g.mjs";import mo from"./packem_shared/identityUndeclaredClaimTrusted-D1sEd5lr.mjs";import lo from"./packem_shared/imagesUrlSourceFromUserInput-BJXotuyO.mjs";import so from"./packem_shared/indexReferencesUnknownField-DKz4-bla.mjs";import po from"./packem_shared/insertManyUnsafeUserData-DWE8d_DF.mjs";import fo from"./packem_shared/kvUnscopedUserKeyIdor-DhymglIq.mjs";import co from"./packem_shared/mailInboundDispatchWithoutVerify-CWwqXyPX.mjs";import uo from"./packem_shared/mailRecipientFromRequestInput-BVXsZ4Xf.mjs";import ho from"./packem_shared/maskUncoveredPiiColumn-axEka4nd.mjs";import go from"./packem_shared/maskWeakHashStrategyOnPii-DJasagzX.mjs";import bo from"./packem_shared/maskedRelationLeakViaWith-CIFij6vE.mjs";import{e as a}from"./packem_shared/finding-NrKO8idM.mjs";import yo from"./packem_shared/mutatorFullRowReplace-BzRpZ47r.mjs";import So from"./packem_shared/mutatorWithoutOwnerScope-BoS81ns0.mjs";import wo from"./packem_shared/nondeterministicQueryMutation-Bbb-AGOS.mjs";import vo from"./packem_shared/normalizeIdUsedAsAuthorization-BXN-Can6.mjs";import Ro from"./packem_shared/notifyMissingPushConfig-DeQMoNwm.mjs";import Ao from"./packem_shared/notifySendOutsideAction-BJrDkr4h.mjs";import Uo from"./packem_shared/outputProjectionMissingOnPublicRead-DWSeo7oC.mjs";import Wo from"./packem_shared/ownerFieldFromArgsNotAuth-J8Xbz5uI.mjs";import To from"./packem_shared/paymentCreateWithoutAuthorize-CdenWpXD.mjs";import Io from"./packem_shared/paymentWebhookWideTolerance-DVhJB0Ip.mjs";import xo from"./packem_shared/plaintextSecretInWranglerVariables-bNkqmNqV.mjs";import Co from"./packem_shared/policyReferencesUnknownTable-CIKuRZ5Y.mjs";import ko from"./packem_shared/privilegedDispatchUnvalidatedPayload-BM3NtT57.mjs";import No from"./packem_shared/privilegedFanoutFromPublicProcedure-DV188gHQ.mjs";import Fo from"./packem_shared/procedureWithoutStructuredEvent-CZ_B23nM.mjs";import Mo from"./packem_shared/publicArgumentUsesAny-BDEvMA8Q.mjs";import Oo from"./packem_shared/publicMutationWithoutRatelimit-Bd-rRxnz.mjs";import Do from"./packem_shared/publicTableRlsOptoutConfusion-CbNk75Xo.mjs";import Po from"./packem_shared/queueWithoutDlq-BvHz3Opg.mjs";import $o from"./packem_shared/r2sqlOutsideAction-BdLiLOAX.mjs";import Eo from"./packem_shared/ratelimitDefaultMemoryStore-Dj0Kk7t9.mjs";import qo from"./packem_shared/ratelimitKeySpoofableOrGlobal-DHMl74hR.mjs";import Ko from"./packem_shared/ratelimitMiddlewareFailOpen-Di2t8pDc.mjs";import Lo from"./packem_shared/relationReferencesUnknownField-Cl6mik8w.mjs";import Go from"./packem_shared/relationReferencesUnknownTable-vYjirSxf.mjs";import _o from"./packem_shared/rlsUncoveredTable-CZ4ie4gX.mjs";import zo from"./packem_shared/selfRescheduleWithoutProgress-Be9d1bQ0.mjs";import Ho from"./packem_shared/shapeTargetsGlobalTable-Bu3eEDic.mjs";import Bo from"./packem_shared/shapeUnknownTable-CREfNnWi.mjs";import jo from"./packem_shared/signupMutationWithoutDisposableGating-BdKwtxDP.mjs";import Vo from"./packem_shared/softDeleteIncludeDeletedFromArgs-C7Ugg4zb.mjs";import Qo from"./packem_shared/sqlInjectionRisk-BYRllgPV.mjs";import Xo from"./packem_shared/storageGenerateUploadUrlNoContentTypePin-CrwFuo83.mjs";import Zo from"./packem_shared/storageKeyFromUserArgs-3vkxmI3v.mjs";import Jo from"./packem_shared/storagePresignedUrlForPrivateContent-C3KeV5tV.mjs";import Yo from"./packem_shared/storageUploadWithoutContentTypeAllowlist-BXoK_QWE.mjs";import oe from"./packem_shared/storageUploadWithoutMaxSize-CW7uKvSi.mjs";import ee from"./packem_shared/tableWithoutInsert-ClJp_jDr.mjs";import re from"./packem_shared/throughRelationMissingUniqueIndex-Cg5e3GkS.mjs";import te from"./packem_shared/topicTooManySubscriptions-CiZhFm9r.mjs";import ie from"./packem_shared/ttlFieldNotTimestamp-lnb5nmM6.mjs";import{s as ne,q as ae,c as me}from"./packem_shared/helpers-DdZ_pyR3.mjs";import le from"./packem_shared/unboundedCollectOnOpenRange-DHy2RUL7.mjs";import se from"./packem_shared/unboundedLoop-DxN3_apu.mjs";import pe from"./packem_shared/unboundedStringArgument-jZnCbwB0.mjs";import fe from"./packem_shared/unindexedForeignKey-Dypgn8uH.mjs";import ce from"./packem_shared/unindexedRelationTarget-Dj2_NOCm.mjs";import de from"./packem_shared/unrestrictedWhereBranch-CcIBmHik.mjs";import ue from"./packem_shared/userCreatingMutationWithoutCaptcha-D4TZEpgz.mjs";import he from"./packem_shared/vectorsNamespaceFromUserInput-BWvGjJzp.mjs";import ge from"./packem_shared/workflowDuplicateStepName-B6eAJzwA.mjs";import be from"./packem_shared/workflowUnknownTarget-DEKB5H0g.mjs";import ye from"./packem_shared/workflowUnused-DHwwE-1P.mjs";import{callSiteCallers as Mt,callSiteDescription as Ot,callSiteLabel as Dt,callSiteMetadata as Pt,helperRole as $t,isReachableSite as Et,readCallSiteCallers as qt}from"./packem_shared/callSiteCallers-DArHfQSU.mjs";import{compareToBaseline as Lt,parseAdvisorMap as Gt}from"./packem_shared/compareToBaseline-CUG59IOy.mjs";import{gradeFromScore as zt}from"./packem_shared/gradeFromScore-KSt58rj1.mjs";import{MAP_VERSION as Bt,byCodepoint as jt,scoreAdvisor as Vt}from"./packem_shared/MAP_VERSION-MQ5W2B6n.mjs";import{default as Xt}from"./packem_shared/classifySensitivity-JnjaTGYi.mjs";import{fromServerSchema as Jt}from"./packem_shared/fromServerSchema-KWJd4PLE.mjs";const Se={convex:"migrating/from-convex",firebase:"migrating/from-firebase",supabase:"migrating/from-supabase"},m={categories:["SCHEMA"],description:"A migrated-away platform's SDK is still imported from `lunora/` source. The code compiles and typechecks, so a half-finished port keeps reading from the old platform at runtime and the two stores silently drift apart.",facing:"INTERNAL",level:"WARN",name:"migration_stale_import",remediation:"Replace the call with its Lunora equivalent and drop the dependency. If the import is deliberate — a second data source you still read from — move it out of `lunora/` so it is not mistaken for an unfinished migration.",run:r=>r.staleMigrationImports===void 0?[]:r.staleMigrationImports.map(o=>a(m,{cacheKey:`migration_stale_import:${o.file}:${o.line.toString()}:${o.moduleSpecifier}`,detail:`\`${o.file}\` (line ${o.line.toString()}) still imports \`${o.moduleSpecifier}\`. Finish the port — see \`${Se[o.platform]}\` — or move the import out of \`lunora/\` if you genuinely still read from ${o.platform}.`,metadata:{file:o.file,line:o.line,moduleSpecifier:o.moduleSpecifier,platform:o.platform}})),source:"static",title:"Stale migration import"},we=new Map([["global",{level:"WARN",scope:r=>`it reads the whole D1 table "${r}" over a cross-region round trip`}],["root",{level:"WARN",scope:r=>`it loads every row of "${r}" from the root Durable Object's SQLite into memory`}],["shardBy",{level:"INFO",scope:r=>`"${r}" is \`.shardBy()\`, so this collects one shard's rows rather than the whole table — bounded by a single tenant's row count`}]]),ve={level:"WARN",scope:r=>`it loads every row of "${r}"`},l={categories:["PERFORMANCE"],description:"A query calls `.collect()` with no `.withIndex()` and no `.filter()`, so it materializes every row of the table. Any live subscription over it also re-sends that whole result to every subscribed client on every write to the table.",facing:"EXTERNAL",level:"WARN",name:"unbounded_collect",remediation:'Narrow the read with `.withIndex("name", (q) => q.eq(...))`, cap it with `.take(n)`, or page it with `.paginate(args.paginationOpts)` so neither the scan nor the subscription payload grows with the table.',run:r=>{const o=[],i=ne(r.schema);for(const e of r.queries??[]){if(e.terminal!=="collect"||e.hasIndex||e.table===""||e.hasFilter)continue;const t=i.get(e.table),{level:n,scope:s}=we.get(t??"")??ve,p=ae(e),f=n==="WARN"?` A live subscription over this query records a whole-table dependency, so every write to "${e.table}" re-runs it and re-sends the full result to each subscribed socket.`:" Cap it with `.take(n)` if that count can grow.";o.push(a(l,{cacheKey:`unbounded_collect:${e.file}:${e.line.toString()}:${e.table}`,detail:`Query on "${e.table}" at ${p} calls .collect() with no index and no filter — ${s(e.table)}.${f}`,level:n,metadata:{...me(e),shardKind:t??"unknown",table:e.table}}))}return o},source:"static",title:"Unbounded collect"},Re=[so,Go,Lo,be,ge,Bo,B,j,V,_,ro,z,oo,eo,H,ie,O,M,K,fe,ce,re,G,ee,ye,Po,te,X,Z,Q,l,le,se,L,zo,Ho,yo,So,wo,ao,$o,U,Co,_o,ho,go,P,$,Oo,de,ue,jo,Mo,pe,to,Qo,y,To,co,Eo,N,No,Fo,po,R,g,b,Wo,Zo,fo,D,S,w,he,uo,F,ko,q,E,I,k,W,x,T,C,lo,qo,Do,A,Yo,oe,Xo,Jo,io,no,Ko,J,Y,v,mo,Io,Vo,bo,Uo,vo,Ao,Ro,xo,m],Ae=[u,h,d],Ue=[...Re,...Ae],kt=(r,o={})=>{const i=o.lints??Ue,e=[];for(const t of i)o.source!==void 0&&t.source!==o.source||e.push(...t.run(r));return c(e)};export{Ue as ALL_LINTS,Bt as MAP_VERSION,Ae as RUNTIME_LINTS,Re as STATIC_LINTS,g as actionFetchSsrf,b as actionWithoutErrorHandling,y as adminRouteWithoutGuard,S as aiRawRunEscapeHatch,w as aiRunWithoutLogging,v as aiToolSideEffectPromptInjection,R as aiUnboundedGenerationPublic,A as allowUnauthenticatedShardAccessEnabled,U as authApiCallWithoutHeaders,W as authCsrfCheckDisabled,T as authEmailVerificationDisabled,I as authScimWithoutTransactions,x as authSecureCookiesDisabled,C as authSessionFreshageZero,k as authTrustedOriginsWildcard,N as browserAllowPrivateTargets,F as browserUserUrlWithoutAllowlist,jt as byCodepoint,Mt as callSiteCallers,Ot as callSiteDescription,Dt as callSiteLabel,Pt as callSiteMetadata,M as circularFk,Xt as classifySensitivity,O as commitOrderedHardDelete,Lt as compareToBaseline,D as containerInstanceKeyFromUserInput,P as containerOversizedInstance,$ as containerPublicInternet,E as containerRuntimeEgressRelaxation,q as containerStartEnableInternetOverride,K as crossModuleTableWrite,c as dedupeCacheKeys,L as dispatchCycle,G as duplicateIndex,_ as emptyIndex,z as errorWithoutCatalog,H as exportSinkMisconfigured,B as externalSourceIncrementalNoDeletePath,j as externalSourceOnGlobal,V as externalSourceUnscoped,d as fanOutBreadth,Q as filterAfterIndexScan,X as filterOnPrimaryKey,Z as filterWithoutIndex,J as flagGatesSecurityWithUnsafeDefault,Y as flagReadInSubscription,Jt as fromServerSchema,oo as geoIndexFieldNotGeopoint,eo as geoIndexUnused,ro as globalTableNearColumnLimit,zt as gradeFromScore,to as hardcodedSecret,$t as helperRole,u as hotShard,io as httpActionMissingAuthGuard,no as httpActionResponseHeaderInjection,ao as hyperdriveOutsideAction,mo as identityUndeclaredClaimTrusted,lo as imagesUrlSourceFromUserInput,so as indexReferencesUnknownField,h as indexUtilization,po as insertManyUnsafeUserData,Et as isReachableSite,fo as kvUnscopedUserKeyIdor,co as mailInboundDispatchWithoutVerify,uo as mailRecipientFromRequestInput,ho as maskUncoveredPiiColumn,go as maskWeakHashStrategyOnPii,bo as maskedRelationLeakViaWith,yo as mutatorFullRowReplace,So as mutatorWithoutOwnerScope,wo as nondeterministicQueryMutation,vo as normalizeIdUsedAsAuthorization,Ro as notifyMissingPushConfig,Ao as notifySendOutsideAction,Uo as outputProjectionMissingOnPublicRead,Wo as ownerFieldFromArgsNotAuth,Gt as parseAdvisorMap,To as paymentCreateWithoutAuthorize,Io as paymentWebhookWideTolerance,xo as plaintextSecretInWranglerVariables,Co as policyReferencesUnknownTable,ko as privilegedDispatchUnvalidatedPayload,No as privilegedFanoutFromPublicProcedure,Fo as procedureWithoutStructuredEvent,Mo as publicArgumentUsesAny,Oo as publicMutationWithoutRatelimit,Do as publicTableRlsOptoutConfusion,Po as queueWithoutDlq,$o as r2sqlOutsideAction,Eo as ratelimitDefaultMemoryStore,qo as ratelimitKeySpoofableOrGlobal,Ko as ratelimitMiddlewareFailOpen,qt as readCallSiteCallers,Lo as relationReferencesUnknownField,Go as relationReferencesUnknownTable,_o as rlsUncoveredTable,kt as runAdvisor,Vt as scoreAdvisor,zo as selfRescheduleWithoutProgress,Ho as shapeTargetsGlobalTable,Bo as shapeUnknownTable,jo as signupMutationWithoutDisposableGating,Vo as softDeleteIncludeDeletedFromArgs,Qo as sqlInjectionRisk,Xo as storageGenerateUploadUrlNoContentTypePin,Zo as storageKeyFromUserArgs,Jo as storagePresignedUrlForPrivateContent,Yo as storageUploadWithoutContentTypeAllowlist,oe as storageUploadWithoutMaxSize,ee as tableWithoutInsert,re as throughRelationMissingUniqueIndex,te as topicTooManySubscriptions,ie as ttlFieldNotTimestamp,le as unboundedCollectOnOpenRange,se as unboundedLoop,pe as unboundedStringArgument,fe as unindexedForeignKey,ce as unindexedRelationTarget,de as unrestrictedWhereBranch,ue as userCreatingMutationWithoutCaptcha,he as vectorsNamespaceFromUserInput,ge as workflowDuplicateStepName,be as workflowUnknownTarget,ye as workflowUnused};
1
+ import{dedupeCacheKeys as c}from"./packem_shared/dedupeCacheKeys-DtBOHffV.mjs";import d from"./packem_shared/fanOutBreadth-CBtmZnoh.mjs";import u from"./packem_shared/hotShard-BwGYZ3Tq.mjs";import h from"./packem_shared/indexUtilization-CkVPZcVe.mjs";import g from"./packem_shared/actionFetchSsrf-BK-kvOPO.mjs";import b from"./packem_shared/actionWithoutErrorHandling-4GCBT0_z.mjs";import y from"./packem_shared/adminRouteWithoutGuard-DPE7LuNh.mjs";import S from"./packem_shared/aiRawRunEscapeHatch-BW7a8kZC.mjs";import w from"./packem_shared/aiRunWithoutLogging-CvHHtEN9.mjs";import v from"./packem_shared/aiToolSideEffectPromptInjection-9eJXRlKt.mjs";import A from"./packem_shared/aiUnboundedGenerationPublic-C17h6sMV.mjs";import R from"./packem_shared/allowUnauthenticatedShardAccessEnabled-BQufF5Kb.mjs";import U from"./packem_shared/authApiCallWithoutHeaders-CReJRsGW.mjs";import W from"./packem_shared/authCsrfCheckDisabled-CRIVsvb9.mjs";import I from"./packem_shared/authEmailVerificationDisabled-BjFlnHr2.mjs";import T from"./packem_shared/authScimWithoutTransactions-B5ATiz5A.mjs";import x from"./packem_shared/authSecureCookiesDisabled-By8PVum3.mjs";import C from"./packem_shared/authSessionFreshageZero-DuxX3Ln9.mjs";import k from"./packem_shared/authTrustedOriginsWildcard-BKIh6788.mjs";import N from"./packem_shared/browserAllowPrivateTargets-Cj5sizhv.mjs";import F from"./packem_shared/browserUserUrlWithoutAllowlist-CzDEraJB.mjs";import M from"./packem_shared/circularFk-DtcWFJxK.mjs";import O from"./packem_shared/commitOrderedHardDelete-BPdwOKA7.mjs";import D from"./packem_shared/containerInstanceKeyFromUserInput-Bl-tAzKV.mjs";import P from"./packem_shared/containerLongSleepAfter-CVvHEGYC.mjs";import $ from"./packem_shared/containerOversizedInstance-Bx89uR7E.mjs";import E from"./packem_shared/containerPublicInternet-BFfZf_P4.mjs";import q from"./packem_shared/containerRuntimeEgressRelaxation-4tMrhq1k.mjs";import L from"./packem_shared/containerStartEnableInternetOverride-mN_kuRtk.mjs";import K from"./packem_shared/countWithoutIndex-Dn6rB2Mb.mjs";import G from"./packem_shared/crossModuleTableWrite-DjiVTAwK.mjs";import _ from"./packem_shared/dispatchCycle-BwuXRDiL.mjs";import z from"./packem_shared/duplicateIndex-Cip6-Rpu.mjs";import H from"./packem_shared/emptyIndex-BnHDcXza.mjs";import B from"./packem_shared/errorWithoutCatalog-BTfvaXHR.mjs";import j from"./packem_shared/exportSinkMisconfigured-JfbAx9AI.mjs";import V from"./packem_shared/externalSourceIncrementalNoDeletePath-BCzm3HzF.mjs";import Q from"./packem_shared/externalSourceOnGlobal-CH7xbJ49.mjs";import X from"./packem_shared/externalSourceUnscoped-BxU2uSXk.mjs";import Z from"./packem_shared/filterAfterIndexScan-CV8aPq7v.mjs";import J from"./packem_shared/filterOnPrimaryKey-BeHVg4w3.mjs";import Y from"./packem_shared/filterWithoutIndex-BGv3jbgg.mjs";import oo from"./packem_shared/flagGatesSecurityWithUnsafeDefault-B5FBDF8c.mjs";import eo from"./packem_shared/flagReadInSubscription-DabIhYGD.mjs";import ro from"./packem_shared/geoIndexFieldNotGeopoint-BMQuok2-.mjs";import to from"./packem_shared/geoIndexUnused-D7C9Qr4U.mjs";import io from"./packem_shared/globalTableNearColumnLimit-BFbBBd6A.mjs";import no from"./packem_shared/hardcodedSecret-Bw_4FYrs.mjs";import ao from"./packem_shared/httpActionMissingAuthGuard-DhMIs1EI.mjs";import mo from"./packem_shared/httpActionResponseHeaderInjection-mcRYd3Zw.mjs";import lo from"./packem_shared/hyperdriveOutsideAction-CPDdGP2g.mjs";import so from"./packem_shared/identityUndeclaredClaimTrusted-D1sEd5lr.mjs";import po from"./packem_shared/imagesUrlSourceFromUserInput-BJXotuyO.mjs";import fo from"./packem_shared/indexReferencesUnknownField-DKz4-bla.mjs";import co from"./packem_shared/insertManyUnsafeUserData-DWE8d_DF.mjs";import uo from"./packem_shared/kvUnscopedUserKeyIdor-DhymglIq.mjs";import ho from"./packem_shared/mailInboundDispatchWithoutVerify-CWwqXyPX.mjs";import go from"./packem_shared/mailRecipientFromRequestInput-BVXsZ4Xf.mjs";import bo from"./packem_shared/maskUncoveredPiiColumn-axEka4nd.mjs";import yo from"./packem_shared/maskWeakHashStrategyOnPii-DJasagzX.mjs";import So from"./packem_shared/maskedRelationLeakViaWith-CIFij6vE.mjs";import{e as a}from"./packem_shared/finding-NrKO8idM.mjs";import wo from"./packem_shared/mutatorFullRowReplace-BzRpZ47r.mjs";import vo from"./packem_shared/mutatorWithoutOwnerScope-BoS81ns0.mjs";import Ao from"./packem_shared/nondeterministicQueryMutation-Bbb-AGOS.mjs";import Ro from"./packem_shared/normalizeIdUsedAsAuthorization-BXN-Can6.mjs";import Uo from"./packem_shared/notifyMissingPushConfig-DeQMoNwm.mjs";import Wo from"./packem_shared/notifySendOutsideAction-BJrDkr4h.mjs";import Io from"./packem_shared/outputProjectionMissingOnPublicRead-DWSeo7oC.mjs";import To from"./packem_shared/ownerFieldFromArgsNotAuth-J8Xbz5uI.mjs";import xo from"./packem_shared/paymentCreateWithoutAuthorize-CdenWpXD.mjs";import Co from"./packem_shared/paymentWebhookWideTolerance-DVhJB0Ip.mjs";import ko from"./packem_shared/plaintextSecretInWranglerVariables-bNkqmNqV.mjs";import No from"./packem_shared/policyReferencesUnknownTable-CIKuRZ5Y.mjs";import Fo from"./packem_shared/privilegedDispatchUnvalidatedPayload-BM3NtT57.mjs";import Mo from"./packem_shared/privilegedFanoutFromPublicProcedure-DV188gHQ.mjs";import Oo from"./packem_shared/procedureWithoutStructuredEvent-CZ_B23nM.mjs";import Do from"./packem_shared/publicArgumentUsesAny-BDEvMA8Q.mjs";import Po from"./packem_shared/publicMutationWithoutRatelimit-Bd-rRxnz.mjs";import $o from"./packem_shared/publicTableRlsOptoutConfusion-CbNk75Xo.mjs";import Eo from"./packem_shared/queueDispatchCycle-84yt1IR-.mjs";import qo from"./packem_shared/queueWithoutDlq-BvHz3Opg.mjs";import Lo from"./packem_shared/r2sqlOutsideAction-BdLiLOAX.mjs";import Ko from"./packem_shared/ratelimitDefaultMemoryStore-Dj0Kk7t9.mjs";import Go from"./packem_shared/ratelimitKeySpoofableOrGlobal-DHMl74hR.mjs";import _o from"./packem_shared/ratelimitMiddlewareFailOpen-Di2t8pDc.mjs";import zo from"./packem_shared/relationReferencesUnknownField-Cl6mik8w.mjs";import Ho from"./packem_shared/relationReferencesUnknownTable-vYjirSxf.mjs";import Bo from"./packem_shared/rlsUncoveredTable-CZ4ie4gX.mjs";import jo from"./packem_shared/selfRescheduleWithoutProgress-CQNZI4FA.mjs";import Vo from"./packem_shared/shapeTargetsGlobalTable-Bu3eEDic.mjs";import Qo from"./packem_shared/shapeUnknownTable-CREfNnWi.mjs";import Xo from"./packem_shared/signupMutationWithoutDisposableGating-BdKwtxDP.mjs";import Zo from"./packem_shared/softDeleteIncludeDeletedFromArgs-C7Ugg4zb.mjs";import Jo from"./packem_shared/sqlInjectionRisk-BYRllgPV.mjs";import Yo from"./packem_shared/storageGenerateUploadUrlNoContentTypePin-CrwFuo83.mjs";import oe from"./packem_shared/storageKeyFromUserArgs-3vkxmI3v.mjs";import ee from"./packem_shared/storagePresignedUrlForPrivateContent-C3KeV5tV.mjs";import re from"./packem_shared/storageUploadWithoutContentTypeAllowlist-BXoK_QWE.mjs";import te from"./packem_shared/storageUploadWithoutMaxSize-CW7uKvSi.mjs";import ie from"./packem_shared/tableWithoutInsert-ClJp_jDr.mjs";import ne from"./packem_shared/throughRelationMissingUniqueIndex-Cg5e3GkS.mjs";import ae from"./packem_shared/topicTooManySubscriptions-CiZhFm9r.mjs";import me from"./packem_shared/ttlFieldNotTimestamp-lnb5nmM6.mjs";import{s as le,q as se,c as pe}from"./packem_shared/helpers-DdZ_pyR3.mjs";import fe from"./packem_shared/unboundedCollectOnOpenRange-DHy2RUL7.mjs";import ce from"./packem_shared/unboundedLoop-DxN3_apu.mjs";import de from"./packem_shared/unboundedStringArgument-jZnCbwB0.mjs";import ue from"./packem_shared/unindexedForeignKey-Dypgn8uH.mjs";import he from"./packem_shared/unindexedRelationTarget-Dj2_NOCm.mjs";import ge from"./packem_shared/unrestrictedWhereBranch-CcIBmHik.mjs";import be from"./packem_shared/userCreatingMutationWithoutCaptcha-D4TZEpgz.mjs";import ye from"./packem_shared/vectorsNamespaceFromUserInput-BWvGjJzp.mjs";import Se from"./packem_shared/workflowDuplicateStepName-B6eAJzwA.mjs";import we from"./packem_shared/workflowUnknownTarget-DEKB5H0g.mjs";import ve from"./packem_shared/workflowUnused-DHwwE-1P.mjs";import{callSiteCallers as qt,callSiteDescription as Lt,callSiteLabel as Kt,callSiteMetadata as Gt,helperRole as _t,isReachableSite as zt,readCallSiteCallers as Ht}from"./packem_shared/callSiteCallers-DArHfQSU.mjs";import{compareToBaseline as jt,parseAdvisorMap as Vt}from"./packem_shared/compareToBaseline-CUG59IOy.mjs";import{gradeFromScore as Xt}from"./packem_shared/gradeFromScore-KSt58rj1.mjs";import{MAP_VERSION as Jt,byCodepoint as Yt,scoreAdvisor as oi}from"./packem_shared/MAP_VERSION-MQ5W2B6n.mjs";import{default as ri}from"./packem_shared/classifySensitivity-JnjaTGYi.mjs";import{fromServerSchema as ii}from"./packem_shared/fromServerSchema-KWJd4PLE.mjs";const Ae={convex:"migrating/from-convex",firebase:"migrating/from-firebase",supabase:"migrating/from-supabase"},m={categories:["SCHEMA"],description:"A migrated-away platform's SDK is still imported from `lunora/` source. The code compiles and typechecks, so a half-finished port keeps reading from the old platform at runtime and the two stores silently drift apart.",facing:"INTERNAL",level:"WARN",name:"migration_stale_import",remediation:"Replace the call with its Lunora equivalent and drop the dependency. If the import is deliberate — a second data source you still read from — move it out of `lunora/` so it is not mistaken for an unfinished migration.",run:r=>r.staleMigrationImports===void 0?[]:r.staleMigrationImports.map(o=>a(m,{cacheKey:`migration_stale_import:${o.file}:${o.line.toString()}:${o.moduleSpecifier}`,detail:`\`${o.file}\` (line ${o.line.toString()}) still imports \`${o.moduleSpecifier}\`. Finish the port — see \`${Ae[o.platform]}\` — or move the import out of \`lunora/\` if you genuinely still read from ${o.platform}.`,metadata:{file:o.file,line:o.line,moduleSpecifier:o.moduleSpecifier,platform:o.platform}})),source:"static",title:"Stale migration import"},Re=new Map([["global",{level:"WARN",scope:r=>`it reads the whole D1 table "${r}" over a cross-region round trip`}],["root",{level:"WARN",scope:r=>`it loads every row of "${r}" from the root Durable Object's SQLite into memory`}],["shardBy",{level:"INFO",scope:r=>`"${r}" is \`.shardBy()\`, so this collects one shard's rows rather than the whole table — bounded by a single tenant's row count`}]]),Ue={level:"WARN",scope:r=>`it loads every row of "${r}"`},l={categories:["PERFORMANCE"],description:"A query calls `.collect()` with no `.withIndex()` and no `.filter()`, so it materializes every row of the table. Any live subscription over it also re-sends that whole result to every subscribed client on every write to the table.",facing:"EXTERNAL",level:"WARN",name:"unbounded_collect",remediation:'Narrow the read with `.withIndex("name", (q) => q.eq(...))`, cap it with `.take(n)`, or page it with `.paginate(args.paginationOpts)` so neither the scan nor the subscription payload grows with the table.',run:r=>{const o=[],i=le(r.schema);for(const e of r.queries??[]){if(e.terminal!=="collect"||e.hasIndex||e.table===""||e.hasFilter)continue;const t=i.get(e.table),{level:n,scope:s}=Re.get(t??"")??Ue,p=se(e),f=n==="WARN"?` A live subscription over this query records a whole-table dependency, so every write to "${e.table}" re-runs it and re-sends the full result to each subscribed socket.`:" Cap it with `.take(n)` if that count can grow.";o.push(a(l,{cacheKey:`unbounded_collect:${e.file}:${e.line.toString()}:${e.table}`,detail:`Query on "${e.table}" at ${p} calls .collect() with no index and no filter — ${s(e.table)}.${f}`,level:n,metadata:{...pe(e),shardKind:t??"unknown",table:e.table}}))}return o},source:"static",title:"Unbounded collect"},We=[fo,Ho,zo,we,Se,Qo,V,Q,X,H,io,B,ro,to,j,me,O,M,K,G,ue,he,ne,z,ie,ve,Eo,qo,ae,J,Y,Z,l,fe,ce,_,jo,Vo,wo,vo,Ao,lo,Lo,U,No,Bo,bo,yo,P,$,E,Po,ge,be,Xo,Do,de,no,Jo,y,xo,ho,Ko,N,Mo,Oo,co,A,g,b,To,oe,uo,D,S,w,ye,go,F,Fo,L,q,T,k,W,x,I,C,po,Go,$o,R,re,te,Yo,ee,ao,mo,_o,oo,eo,v,so,Co,Zo,So,Io,Ro,Wo,Uo,ko,m],Ie=[u,h,d],Te=[...We,...Ie],Pt=(r,o={})=>{const i=o.lints??Te,e=[];for(const t of i)o.source!==void 0&&t.source!==o.source||e.push(...t.run(r));return c(e)};export{Te as ALL_LINTS,Jt as MAP_VERSION,Ie as RUNTIME_LINTS,We as STATIC_LINTS,g as actionFetchSsrf,b as actionWithoutErrorHandling,y as adminRouteWithoutGuard,S as aiRawRunEscapeHatch,w as aiRunWithoutLogging,v as aiToolSideEffectPromptInjection,A as aiUnboundedGenerationPublic,R as allowUnauthenticatedShardAccessEnabled,U as authApiCallWithoutHeaders,W as authCsrfCheckDisabled,I as authEmailVerificationDisabled,T as authScimWithoutTransactions,x as authSecureCookiesDisabled,C as authSessionFreshageZero,k as authTrustedOriginsWildcard,N as browserAllowPrivateTargets,F as browserUserUrlWithoutAllowlist,Yt as byCodepoint,qt as callSiteCallers,Lt as callSiteDescription,Kt as callSiteLabel,Gt as callSiteMetadata,M as circularFk,ri as classifySensitivity,O as commitOrderedHardDelete,jt as compareToBaseline,D as containerInstanceKeyFromUserInput,P as containerLongSleepAfter,$ as containerOversizedInstance,E as containerPublicInternet,q as containerRuntimeEgressRelaxation,L as containerStartEnableInternetOverride,K as countWithoutIndex,G as crossModuleTableWrite,c as dedupeCacheKeys,_ as dispatchCycle,z as duplicateIndex,H as emptyIndex,B as errorWithoutCatalog,j as exportSinkMisconfigured,V as externalSourceIncrementalNoDeletePath,Q as externalSourceOnGlobal,X as externalSourceUnscoped,d as fanOutBreadth,Z as filterAfterIndexScan,J as filterOnPrimaryKey,Y as filterWithoutIndex,oo as flagGatesSecurityWithUnsafeDefault,eo as flagReadInSubscription,ii as fromServerSchema,ro as geoIndexFieldNotGeopoint,to as geoIndexUnused,io as globalTableNearColumnLimit,Xt as gradeFromScore,no as hardcodedSecret,_t as helperRole,u as hotShard,ao as httpActionMissingAuthGuard,mo as httpActionResponseHeaderInjection,lo as hyperdriveOutsideAction,so as identityUndeclaredClaimTrusted,po as imagesUrlSourceFromUserInput,fo as indexReferencesUnknownField,h as indexUtilization,co as insertManyUnsafeUserData,zt as isReachableSite,uo as kvUnscopedUserKeyIdor,ho as mailInboundDispatchWithoutVerify,go as mailRecipientFromRequestInput,bo as maskUncoveredPiiColumn,yo as maskWeakHashStrategyOnPii,So as maskedRelationLeakViaWith,wo as mutatorFullRowReplace,vo as mutatorWithoutOwnerScope,Ao as nondeterministicQueryMutation,Ro as normalizeIdUsedAsAuthorization,Uo as notifyMissingPushConfig,Wo as notifySendOutsideAction,Io as outputProjectionMissingOnPublicRead,To as ownerFieldFromArgsNotAuth,Vt as parseAdvisorMap,xo as paymentCreateWithoutAuthorize,Co as paymentWebhookWideTolerance,ko as plaintextSecretInWranglerVariables,No as policyReferencesUnknownTable,Fo as privilegedDispatchUnvalidatedPayload,Mo as privilegedFanoutFromPublicProcedure,Oo as procedureWithoutStructuredEvent,Do as publicArgumentUsesAny,Po as publicMutationWithoutRatelimit,$o as publicTableRlsOptoutConfusion,Eo as queueDispatchCycle,qo as queueWithoutDlq,Lo as r2sqlOutsideAction,Ko as ratelimitDefaultMemoryStore,Go as ratelimitKeySpoofableOrGlobal,_o as ratelimitMiddlewareFailOpen,Ht as readCallSiteCallers,zo as relationReferencesUnknownField,Ho as relationReferencesUnknownTable,Bo as rlsUncoveredTable,Pt as runAdvisor,oi as scoreAdvisor,jo as selfRescheduleWithoutProgress,Vo as shapeTargetsGlobalTable,Qo as shapeUnknownTable,Xo as signupMutationWithoutDisposableGating,Zo as softDeleteIncludeDeletedFromArgs,Jo as sqlInjectionRisk,Yo as storageGenerateUploadUrlNoContentTypePin,oe as storageKeyFromUserArgs,ee as storagePresignedUrlForPrivateContent,re as storageUploadWithoutContentTypeAllowlist,te as storageUploadWithoutMaxSize,ie as tableWithoutInsert,ne as throughRelationMissingUniqueIndex,ae as topicTooManySubscriptions,me as ttlFieldNotTimestamp,fe as unboundedCollectOnOpenRange,ce as unboundedLoop,de as unboundedStringArgument,ue as unindexedForeignKey,he as unindexedRelationTarget,ge as unrestrictedWhereBranch,be as userCreatingMutationWithoutCaptcha,ye as vectorsNamespaceFromUserInput,Se as workflowDuplicateStepName,we as workflowUnknownTarget,ve as workflowUnused};
@@ -0,0 +1 @@
1
+ import{e as d}from"./finding-NrKO8idM.mjs";const l=3600,f={h:3600,m:60,s:1},p=/^(\d+)([smh])$/u,c=n=>{if(typeof n=="number")return n;const o=p.exec(n);if(o===null)return;const[,e="0",t="s"]=o;return Number(e)*(f[t]??1)},u={categories:["PERFORMANCE"],description:"A container's idle window (its `sleepAfter`, capped by any `hardTimeout`) is longer than an hour, so every idle instance keeps running, and billed, for that whole window.",facing:"INTERNAL",level:"WARN",name:"container_long_sleep_after",remediation:"Lower `sleepAfter` to the idle window the workload needs (the default is 10m), or add a `hardTimeout` that caps how long an instance may live.",run:n=>{const o=[];for(const e of n.containers??[]){const{hardTimeout:t,sleepAfter:i}=e;if(i===void 0)continue;const r=c(i);if(r===void 0)continue;const a=t===void 0?void 0:c(t),s=a===void 0?r:Math.min(r,a);s<=l||o.push(d(u,{cacheKey:`container_long_sleep_after:${e.exportName}`,detail:`Container "${e.exportName}" can sit idle for ${s.toString()} s before it sleeps (sleepAfter ${String(i)}${a===void 0?"":`, capped by hardTimeout ${String(t)}`}); every idle instance is billed for that whole window.`,metadata:{container:e.exportName,effectiveIdleSeconds:s,hardTimeout:t,sleepAfter:i}}))}return o},source:"static",title:"Long container idle window"};export{u as default};
@@ -0,0 +1 @@
1
+ import{e as i}from"./finding-NrKO8idM.mjs";import{s as d,q as h,c as u}from"./helpers-DdZ_pyR3.mjs";const c={categories:["PERFORMANCE"],description:"A `count()` runs on a table with no aggregate index, so every call scans every row of the table to count them. The cost grows with the table, and repeats on every call.",facing:"EXTERNAL",level:"WARN",name:"count_without_index",remediation:"Declare an aggregate index that covers the count (`.aggregateIndex(name, { by: [...] })` on the table, or `defineAggregateIndex` in the schema) so `count()` reads a counter. For a small table or a count that runs rarely, the scan may be acceptable.",run:a=>{const t=[],l=d(a.schema);for(const e of a.countReads??[]){if(e.indexed)continue;const n=h(e),o=l.get(e.table),s={...u(e),shardKind:o??"unknown",table:e.table},r=`count_without_index:${e.file}:${e.line.toString()}:${e.table}`;if(o==="shardBy"){t.push(i(c,{cacheKey:r,detail:`count() on "${e.table}" at ${n} has no aggregate index. "${e.table}" is \`.shardBy()\`, so the count covers one shard's rows, not the whole table. It is bounded by one tenant's row count.`,level:"INFO",metadata:s}));continue}t.push(i(c,{cacheKey:r,detail:`count() on "${e.table}" at ${n} has no aggregate index, so it scans every row of "${e.table}" to count them.`,metadata:s}))}return t},source:"static",title:"Count without index"};export{c as default};
@@ -0,0 +1 @@
1
+ import{s as l}from"./dispatch-namespace-BE_LxN7Z.mjs";import{e as d}from"./finding-NrKO8idM.mjs";import{s as f}from"./graph-CrSMHkOs.mjs";const h=o=>{const i=[];for(const e of o)(e.kind==="call"||e.kind==="schedule")&&e.target!==void 0&&e.conditional!==!0&&e.scope.kind==="export"&&i.push({file:e.file,from:`${l(e.file)}:${e.scope.name}`,kind:e.kind,line:e.line,to:e.target});return i},p=o=>`\`${o.from}\` ${o.kind==="schedule"?"schedules":"calls"} \`${o.to}\` (${o.file}:${o.line.toString()})`,m={categories:["PERFORMANCE"],description:"Functions dispatch each other (or themselves) in a cycle where every hop is unconditional — no guard, loop, `try`, or early return in front of any of them. Once one of them runs, the chain re-schedules itself forever, and every storage read or write along it is billed on every pass.",facing:"EXTERNAL",level:"ERROR",name:"dispatch_cycle",remediation:"Put a stop condition in front of the re-dispatch: reschedule only while there is work left (`if (remaining > 0) await ctx.scheduler.runAfter(…)`), carry an attempt counter in the args and stop at a cap, keep the returned id so a stop path can `ctx.scheduler.cancel` it, or move periodic work to a `cron`. If the chain already stops by throwing from a helper, make that stop condition explicit at the call site.",run:o=>{if(o.callEdges===void 0)return[];const i=h(o.callEdges),e=new Map;for(const n of i){const r=e.get(n.from);r===void 0?e.set(n.from,[n.to]):r.push(n.to)}return f([...e.keys()].toSorted((n,r)=>n.localeCompare(r)),e).flatMap(n=>{const r=new Set(n),a=i.filter(t=>r.has(t.from)&&r.has(t.to)).toSorted((t,s)=>t.from.localeCompare(s.from)||t.file.localeCompare(s.file)||t.line-s.line);if(a.length===0)return[];const c=n.toSorted((t,s)=>t.localeCompare(s));return[d(m,{cacheKey:`dispatch_cycle:${c.join(",")}`,detail:`Unconditional dispatch cycle: ${a.map(t=>p(t)).join("; ")}. No guard stands in front of any hop, so once one of these functions runs the chain never stops.`,metadata:{functions:c,hops:a.map(t=>({file:t.file,from:t.from,kind:t.kind,line:t.line,to:t.to}))}})]})},source:"static",title:"Endless dispatch cycle"};export{m as default};
@@ -0,0 +1 @@
1
+ const f=(a,r)=>{const e=new Map,i=new Set,c=[],l=[],p=n=>{const o={index:e.size,lowLink:e.size};e.set(n,o),c.push(n),i.add(n);for(const s of r.get(n)??[]){const t=e.get(s);t===void 0?o.lowLink=Math.min(o.lowLink,p(s).lowLink):i.has(s)&&(o.lowLink=Math.min(o.lowLink,t.index))}if(o.lowLink===o.index){const s=[];for(let t=c.pop();t!==void 0;t=t===n?void 0:c.pop())i.delete(t),s.push(t);l.push(s)}return o};for(const n of a)e.has(n)||p(n);return l};export{f as s};
@@ -0,0 +1 @@
1
+ import{s as d}from"./dispatch-namespace-BE_LxN7Z.mjs";import{e as m}from"./finding-NrKO8idM.mjs";import{s as h}from"./graph-CrSMHkOs.mjs";const u=e=>`queue:${e}`,f=e=>`topic:${e}`,q=e=>{const s=new Map;for(const i of e)i.filePath!==void 0&&s.set(`${d(i.filePath)}:${i.exportName}`,u(i.exportName)),i.handlerSite!==void 0&&s.set(`${d(i.handlerSite.file)}:${i.handlerSite.exportName}`,u(i.exportName));return s},v=(e,s,i,n,r)=>{const a=`${d(e.file)}:${e.scope.kind==="export"?e.scope.name:""}`,o=i.get(a)??a;switch(e.kind){case"call":case"schedule":return{file:e.file,from:o,kind:e.kind,line:e.line,to:i.get(s)??s};case"enqueue":return n.has(s)?{file:e.file,from:o,kind:"enqueue",line:e.line,to:u(s)}:void 0;case"publish":return r.has(s)?{file:e.file,from:o,kind:"publish",line:e.line,to:f(s)}:void 0;default:return}},y=(e,s)=>{const i=q(s),n=new Set(s.map(o=>o.exportName)),r=new Set(s.flatMap(o=>o.topic===void 0?[]:[o.topic])),a=[];for(const o of e){const c=o.target===void 0||o.scope.kind!=="export"||o.conditional===!0&&o.loopOnly!==!0?void 0:v(o,o.target,i,n,r);c!==void 0&&a.push(c)}for(const o of s)o.topic!==void 0&&a.push({file:o.filePath??"",from:f(o.topic),kind:"subscribe",line:0,to:u(o.exportName)});return a},b={call:"calls",enqueue:"enqueues to",publish:"publishes to",schedule:"schedules",subscribe:"delivers to"},k={categories:["PERFORMANCE"],description:"A queue consumer, or a function it runs, sends back to its own queue (directly or through a topic a subscriber publishes to), on every message it handles. Each message produces another, so the queue never drains.",facing:"EXTERNAL",level:"ERROR",name:"queue_dispatch_cycle",remediation:"Stop the re-enqueue behind a condition: send again only while work remains, carry an attempt count in the message body and stop at a cap, or route the failure to the queue's dead-letter queue instead of back into the same one.",run:e=>{if(e.callEdges===void 0||e.queues===void 0)return[];const s=y(e.callEdges,e.queues),i=new Map;for(const n of s){const r=i.get(n.from);r===void 0?i.set(n.from,[n.to]):r.push(n.to)}return h([...i.keys()].toSorted((n,r)=>n.localeCompare(r)),i).flatMap(n=>{const r=new Set(n),a=n.some(t=>t.startsWith("queue:")||t.startsWith("topic:")),o=s.filter(t=>r.has(t.from)&&r.has(t.to)).toSorted((t,l)=>t.from.localeCompare(l.from)||t.file.localeCompare(l.file)||t.line-l.line);if(!a||o.length===0)return[];const c=n.toSorted((t,l)=>t.localeCompare(l)),p=o.map(t=>`\`${t.from}\` ${b[t.kind]} \`${t.to}\` (${t.file}:${t.line.toString()})`);return[m(k,{cacheKey:`queue_dispatch_cycle:${c.join(",")}`,detail:`Unconditional queue cycle: ${p.join("; ")}. No \`if\`, \`try\`, or early return stands in front of any hop, so once one of these runs the chain never stops.`,metadata:{hops:o.map(t=>({file:t.file,from:t.from,kind:t.kind,line:t.line,to:t.to})),nodes:c}})]})},source:"static",title:"Endless queue cycle"};export{k as default};
@@ -0,0 +1 @@
1
+ import{callSiteCallers as l}from"./callSiteCallers-DArHfQSU.mjs";import{e as c}from"./finding-NrKO8idM.mjs";import{r as d,f as u,g as f}from"./helpers-DdZ_pyR3.mjs";const h=1e3,p=6e4,m=new Set(["patch","patchIf","patchMany","patchWhere"]),w=e=>new Map([...e].map(([n,o])=>[n,o.filter(t=>!m.has(t.method))])),g=/^(?:continue|next|page)?_?cursor$/iu,v=e=>{if(!(e.kind!=="schedule"||e.conditional!==!0||e.target===void 0||e.scope.kind!=="export"))return e.target===u(e.file,e.scope.name)?e.scope.name:void 0},y=e=>{const n=e.schedule?.delayMs,o=e.schedule?.argKeys;if(!(n===void 0||n>=p||o===void 0||o.some(t=>g.test(t))))return n},R=(e,n,o)=>e.some(t=>t.kind==="call"&&t.file===n&&t.scope.kind==="export"&&t.scope.name===o),E=(e,n,o,t)=>{const s=(e.queries??[]).filter(r=>r.file===n&&r.table!==""&&l(r.scope).includes(o));return s.length===0||s.some(r=>f(t,r,void 0,[o]))},S={categories:["PERFORMANCE"],description:"A function re-arms itself with `ctx.scheduler.runAfter` on a delay under a minute, and the re-arming passes no cursor and nothing in the function removes the rows it reads. Each run finds the same rows and does the same work again; a delay under a second is a spin, and a longer one is a poll that rewrites a row on every tick.",facing:"EXTERNAL",level:"WARN",name:"self_reschedule_without_progress",remediation:"Carry the progress forward: pass a cursor (or the last processed id) in the dispatch args, or delete or update the rows the function handled so the next run selects only what is left. Re-arm only while work remains, and use a longer delay or a `cron` for periodic work.",run:e=>{if(e.callEdges===void 0)return[];const{callEdges:n}=e,o=d(e.tableWrites,e.rowEffects),t=[];for(const s of n){const r=v(s),i=y(s);if(r===void 0||i===void 0)continue;const a=i>=h;R(n,s.file,r)||E(e,s.file,r,a?w(o):o)||t.push(c(S,{cacheKey:`self_reschedule_without_progress:${s.file}:${s.line.toString()}:${r}`,detail:a?`\`${r}\` (${s.file}:${s.line.toString()}) polls itself every ${i.toString()} ms with no cursor in its args, and it removes none of the rows it reads. Each tick re-reads the same rows and rewrites in place, and each run is billed.`:`\`${r}\` (${s.file}:${s.line.toString()}) re-arms itself after ${i.toString()} ms with no cursor in its args, and it removes none of the rows it reads. The next run finds the same rows and starts the same work again.`,level:a?"INFO":"WARN",metadata:{delayMs:i,file:s.file,function:r,line:s.line,tier:a?"poll":"spin"}}))}return t},source:"static",title:"Self-reschedule without progress"};export{S as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/advisor",
3
- "version": "1.0.0-alpha.211",
3
+ "version": "1.0.0-alpha.212",
4
4
  "description": "Schema & query lints (splinter-style advisors) for Lunora, feeding the Studio Advisors view",
5
5
  "keywords": [
6
6
  "advisor",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
50
- "@lunora/server": "1.0.0-alpha.195"
50
+ "@lunora/server": "1.0.0-alpha.196"
51
51
  },
52
52
  "engines": {
53
53
  "node": "^22.15.0 || >=24.11.0"
@@ -1 +0,0 @@
1
- import{s as f}from"./dispatch-namespace-BE_LxN7Z.mjs";import{e as h}from"./finding-NrKO8idM.mjs";const p=i=>{const c=[];for(const t of i)(t.kind==="call"||t.kind==="schedule")&&t.target!==void 0&&t.conditional!==!0&&t.scope.kind==="export"&&c.push({file:t.file,from:`${f(t.file)}:${t.scope.name}`,kind:t.kind,line:t.line,to:t.target});return c},m=(i,c)=>{const t=new Map,n=new Set,s=[],a=[],d=e=>{const o={index:t.size,lowLink:t.size};t.set(e,o),s.push(e),n.add(e);for(const l of c.get(e)??[]){const r=t.get(l);r===void 0?o.lowLink=Math.min(o.lowLink,d(l).lowLink):n.has(l)&&(o.lowLink=Math.min(o.lowLink,r.index))}if(o.lowLink===o.index){const l=[];for(let r=s.pop();r!==void 0;r=r===e?void 0:s.pop())n.delete(r),l.push(r);a.push(l)}return o};for(const e of i)t.has(e)||d(e);return a},u=i=>`\`${i.from}\` ${i.kind==="schedule"?"schedules":"calls"} \`${i.to}\` (${i.file}:${i.line.toString()})`,k={categories:["PERFORMANCE"],description:"Functions dispatch each other (or themselves) in a cycle where every hop is unconditional — no guard, loop, `try`, or early return in front of any of them. Once one of them runs, the chain re-schedules itself forever, and every storage read or write along it is billed on every pass.",facing:"EXTERNAL",level:"ERROR",name:"dispatch_cycle",remediation:"Put a stop condition in front of the re-dispatch: reschedule only while there is work left (`if (remaining > 0) await ctx.scheduler.runAfter(…)`), carry an attempt counter in the args and stop at a cap, keep the returned id so a stop path can `ctx.scheduler.cancel` it, or move periodic work to a `cron`. If the chain already stops by throwing from a helper, make that stop condition explicit at the call site.",run:i=>{if(i.callEdges===void 0)return[];const c=p(i.callEdges),t=new Map;for(const n of c){const s=t.get(n.from);s===void 0?t.set(n.from,[n.to]):s.push(n.to)}return m([...t.keys()].toSorted((n,s)=>n.localeCompare(s)),t).flatMap(n=>{const s=new Set(n),a=c.filter(e=>s.has(e.from)&&s.has(e.to)).toSorted((e,o)=>e.from.localeCompare(o.from)||e.file.localeCompare(o.file)||e.line-o.line);if(a.length===0)return[];const d=n.toSorted((e,o)=>e.localeCompare(o));return[h(k,{cacheKey:`dispatch_cycle:${d.join(",")}`,detail:`Unconditional dispatch cycle: ${a.map(e=>u(e)).join("; ")}. No guard stands in front of any hop, so once one of these functions runs the chain never stops.`,metadata:{functions:d,hops:a.map(e=>({file:e.file,from:e.from,kind:e.kind,line:e.line,to:e.to}))}})]})},source:"static",title:"Endless dispatch cycle"};export{k as default};
@@ -1 +0,0 @@
1
- import{callSiteCallers as d}from"./callSiteCallers-DArHfQSU.mjs";import{e as f}from"./finding-NrKO8idM.mjs";import{r as u,f as h,g}from"./helpers-DdZ_pyR3.mjs";const m=1e3,p=/^(?:continue|next|page)?_?cursor$/iu,w={categories:["PERFORMANCE"],description:"A function re-arms itself with `ctx.scheduler.runAfter` on a delay under one second, and the re-arming passes no cursor and nothing in the function removes the rows it reads. Each run finds the same rows and does the same work again.",facing:"EXTERNAL",level:"WARN",name:"self_reschedule_without_progress",remediation:"Carry the progress forward: pass a cursor (or the last processed id) in the dispatch args, or delete or update the rows the function handled so the next run selects only what is left. Re-arm only while work remains, and use a longer delay or a `cron` for periodic work.",run:o=>{if(o.callEdges===void 0)return[];const{callEdges:r}=o,c=u(o.tableWrites,o.rowEffects),i=[];for(const e of r){if(e.kind!=="schedule"||e.conditional!==!0||e.target===void 0||e.scope.kind!=="export")continue;const{name:t}=e.scope;if(e.target!==h(e.file,t))continue;const n=e.schedule?.delayMs,a=e.schedule?.argKeys;if(n===void 0||n>=m||a===void 0||a.some(s=>p.test(s))||r.some(s=>s.kind==="call"&&s.file===e.file&&s.scope.kind==="export"&&s.scope.name===t))continue;const l=(o.queries??[]).filter(s=>s.file===e.file&&s.table!==""&&d(s.scope).includes(t));l.length===0||l.some(s=>g(c,s,void 0,[t]))||i.push(f(w,{cacheKey:`self_reschedule_without_progress:${e.file}:${e.line.toString()}:${t}`,detail:`\`${t}\` (${e.file}:${e.line.toString()}) re-arms itself after ${n.toString()} ms with no cursor in its args, and it removes none of the rows it reads. The next run finds the same rows and starts the same work again.`,metadata:{delayMs:n,file:e.file,function:t,line:e.line}}))}return i},source:"static",title:"Self-reschedule without progress"};export{w as default};