@lunora/advisor 1.0.0-alpha.8 → 1.0.0-alpha.9
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
|
@@ -577,10 +577,27 @@ interface AdvisorTableSample {
|
|
|
577
577
|
* how `AdvisorContainer` tracks `ContainerIR` and `AdvisorInsertWrite` tracks
|
|
578
578
|
* `InsertWriteIR`).
|
|
579
579
|
*/
|
|
580
|
+
/** One durable step call lifted from a workflow handler body — the input the duplicate-step-name lint compares. Structural subset of codegen's `WorkflowStepIR`. */
|
|
581
|
+
interface AdvisorWorkflowStep {
|
|
582
|
+
/** 1-based line of the durable step call, or `0` when unknown. */
|
|
583
|
+
line: number;
|
|
584
|
+
/** The native step method invoked: `do` / `sleep` / `sleepUntil` / `waitForEvent`. */
|
|
585
|
+
method: string;
|
|
586
|
+
/** The step's static label (the first string-literal argument). */
|
|
587
|
+
name: string;
|
|
588
|
+
}
|
|
580
589
|
/** One workflow declared via a `defineWorkflow()` export in `lunora/workflows.ts`. */
|
|
581
590
|
interface AdvisorWorkflow {
|
|
582
591
|
/** The `lunora/workflows.ts` export name, e.g. `orderPipeline`. */
|
|
583
592
|
exportName: string;
|
|
593
|
+
/**
|
|
594
|
+
* The durable step labels discovered in the handler body, in source order —
|
|
595
|
+
* the duplicate-step-name input. Cloudflare memoizes a step by its name, so a
|
|
596
|
+
* name used twice makes the second call silently return the first's cached
|
|
597
|
+
* result. Supplied by the codegen feeder; `undefined` for runtime callers,
|
|
598
|
+
* where the lint finds nothing.
|
|
599
|
+
*/
|
|
600
|
+
steps?: ReadonlyArray<AdvisorWorkflowStep>;
|
|
584
601
|
}
|
|
585
602
|
/** One `ctx.workflows.get("name")` call discovered in a function body. */
|
|
586
603
|
interface AdvisorWorkflowCall {
|
|
@@ -1523,6 +1540,25 @@ declare const unindexedRelationTarget: Lint;
|
|
|
1523
1540
|
*/
|
|
1524
1541
|
declare const userCreatingMutationWithoutCaptcha: Lint;
|
|
1525
1542
|
/**
|
|
1543
|
+
* Flags a durable step name reused within one workflow.
|
|
1544
|
+
*
|
|
1545
|
+
* Cloudflare Workflows memoizes every `step.do` / `step.sleep` / `step.sleepUntil`
|
|
1546
|
+
* / `step.waitForEvent` call by its name: on replay the runtime returns the cached
|
|
1547
|
+
* result for a name it has already seen. Two distinct steps that share a name are
|
|
1548
|
+
* therefore a silent bug — the second call never runs its body and instead yields
|
|
1549
|
+
* the first's result, skipping the work (a charge, a write, an external wait)
|
|
1550
|
+
* without error. Hence `ERROR`/`INTERNAL`: it is a developer-facing correctness
|
|
1551
|
+
* defect in the workflow's own code, not a runtime-data nit.
|
|
1552
|
+
*
|
|
1553
|
+
* Only the first string-literal argument of each step call is compared; a step
|
|
1554
|
+
* named dynamically (`step.do(\`load-${id}\`, …)`) is omitted by the feeder, so a
|
|
1555
|
+
* deliberately-parameterized fan-out is never flagged. `ctx.runStep(stepDef, …)`
|
|
1556
|
+
* names (which come from `defineStep` in another file) are out of scope here.
|
|
1557
|
+
* Only runs when the declaration feeder supplied step evidence
|
|
1558
|
+
* (`workflow.steps` present); a runtime caller flags nothing.
|
|
1559
|
+
*/
|
|
1560
|
+
declare const workflowDuplicateStepName: Lint;
|
|
1561
|
+
/**
|
|
1526
1562
|
* A correctness lint: every `ctx.workflows.get("name")` call must reference a
|
|
1527
1563
|
* workflow that exists — i.e. a `defineWorkflow` export in `lunora/workflows.ts`.
|
|
1528
1564
|
* A `.get("x")` whose `"x"` resolves to no declared workflow is a typo or a
|
|
@@ -1584,4 +1620,4 @@ interface RunAdvisorOptions {
|
|
|
1584
1620
|
* `static` lints at build time and defer `runtime` lints to a live shard.
|
|
1585
1621
|
*/
|
|
1586
1622
|
declare const runAdvisor: (context: LintContext, options?: RunAdvisorOptions) => Finding[];
|
|
1587
|
-
export { AE_METRIC_EVENTS, ALL_LINTS, type AdvisorAdminRoute, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorContainer, type AdvisorHyperdriveCall, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorMaskProcedure, type AdvisorMutatorWrite, type AdvisorNondeterministicCall, type AdvisorProcedureProtection, type AdvisorQueryRead, type AdvisorR2sqlCall, type AdvisorRelation, type AdvisorRlsProcedure, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSqlInterpolation, type AdvisorTable, type AdvisorTableSample, type AdvisorTableScan, type AdvisorWorkflow, type AdvisorWorkflowCall, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type Category, type Facing, type Finding, type Level, type Lint, type LintContext, type LintSource, RUNTIME_LINTS, RunAdvisorOptions, STATIC_LINTS, adminRouteWithoutGuard, authApiCallWithoutHeaders, circularFk, constraintValidator, containerOversizedInstance, containerPublicInternet, duplicateIndex, emptyIndex, filterWithoutIndex, fromServerSchema, hardcodedSecret, hotShard, hyperdriveOutsideAction, indexReferencesUnknownField, indexUtilization, loadAnalyticsRuntimeMetrics, maskUncoveredPiiColumn, mutatorFullRowReplace, nondeterministicQueryMutation, policyReferencesUnknownTable, publicArgumentUsesAny, publicMutationWithoutRatelimit, r2sqlOutsideAction, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, sqlInjectionRisk, tableWithoutInsert, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, userCreatingMutationWithoutCaptcha, workflowUnknownTarget, workflowUnused };
|
|
1623
|
+
export { AE_METRIC_EVENTS, ALL_LINTS, type AdvisorAdminRoute, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorContainer, type AdvisorHyperdriveCall, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorMaskProcedure, type AdvisorMutatorWrite, type AdvisorNondeterministicCall, type AdvisorProcedureProtection, type AdvisorQueryRead, type AdvisorR2sqlCall, type AdvisorRelation, type AdvisorRlsProcedure, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSqlInterpolation, type AdvisorTable, type AdvisorTableSample, type AdvisorTableScan, type AdvisorWorkflow, type AdvisorWorkflowCall, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type Category, type Facing, type Finding, type Level, type Lint, type LintContext, type LintSource, RUNTIME_LINTS, RunAdvisorOptions, STATIC_LINTS, adminRouteWithoutGuard, authApiCallWithoutHeaders, circularFk, constraintValidator, containerOversizedInstance, containerPublicInternet, duplicateIndex, emptyIndex, filterWithoutIndex, fromServerSchema, hardcodedSecret, hotShard, hyperdriveOutsideAction, indexReferencesUnknownField, indexUtilization, loadAnalyticsRuntimeMetrics, maskUncoveredPiiColumn, mutatorFullRowReplace, nondeterministicQueryMutation, policyReferencesUnknownTable, publicArgumentUsesAny, publicMutationWithoutRatelimit, r2sqlOutsideAction, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, sqlInjectionRisk, tableWithoutInsert, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, userCreatingMutationWithoutCaptcha, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
|
package/dist/index.d.ts
CHANGED
|
@@ -577,10 +577,27 @@ interface AdvisorTableSample {
|
|
|
577
577
|
* how `AdvisorContainer` tracks `ContainerIR` and `AdvisorInsertWrite` tracks
|
|
578
578
|
* `InsertWriteIR`).
|
|
579
579
|
*/
|
|
580
|
+
/** One durable step call lifted from a workflow handler body — the input the duplicate-step-name lint compares. Structural subset of codegen's `WorkflowStepIR`. */
|
|
581
|
+
interface AdvisorWorkflowStep {
|
|
582
|
+
/** 1-based line of the durable step call, or `0` when unknown. */
|
|
583
|
+
line: number;
|
|
584
|
+
/** The native step method invoked: `do` / `sleep` / `sleepUntil` / `waitForEvent`. */
|
|
585
|
+
method: string;
|
|
586
|
+
/** The step's static label (the first string-literal argument). */
|
|
587
|
+
name: string;
|
|
588
|
+
}
|
|
580
589
|
/** One workflow declared via a `defineWorkflow()` export in `lunora/workflows.ts`. */
|
|
581
590
|
interface AdvisorWorkflow {
|
|
582
591
|
/** The `lunora/workflows.ts` export name, e.g. `orderPipeline`. */
|
|
583
592
|
exportName: string;
|
|
593
|
+
/**
|
|
594
|
+
* The durable step labels discovered in the handler body, in source order —
|
|
595
|
+
* the duplicate-step-name input. Cloudflare memoizes a step by its name, so a
|
|
596
|
+
* name used twice makes the second call silently return the first's cached
|
|
597
|
+
* result. Supplied by the codegen feeder; `undefined` for runtime callers,
|
|
598
|
+
* where the lint finds nothing.
|
|
599
|
+
*/
|
|
600
|
+
steps?: ReadonlyArray<AdvisorWorkflowStep>;
|
|
584
601
|
}
|
|
585
602
|
/** One `ctx.workflows.get("name")` call discovered in a function body. */
|
|
586
603
|
interface AdvisorWorkflowCall {
|
|
@@ -1523,6 +1540,25 @@ declare const unindexedRelationTarget: Lint;
|
|
|
1523
1540
|
*/
|
|
1524
1541
|
declare const userCreatingMutationWithoutCaptcha: Lint;
|
|
1525
1542
|
/**
|
|
1543
|
+
* Flags a durable step name reused within one workflow.
|
|
1544
|
+
*
|
|
1545
|
+
* Cloudflare Workflows memoizes every `step.do` / `step.sleep` / `step.sleepUntil`
|
|
1546
|
+
* / `step.waitForEvent` call by its name: on replay the runtime returns the cached
|
|
1547
|
+
* result for a name it has already seen. Two distinct steps that share a name are
|
|
1548
|
+
* therefore a silent bug — the second call never runs its body and instead yields
|
|
1549
|
+
* the first's result, skipping the work (a charge, a write, an external wait)
|
|
1550
|
+
* without error. Hence `ERROR`/`INTERNAL`: it is a developer-facing correctness
|
|
1551
|
+
* defect in the workflow's own code, not a runtime-data nit.
|
|
1552
|
+
*
|
|
1553
|
+
* Only the first string-literal argument of each step call is compared; a step
|
|
1554
|
+
* named dynamically (`step.do(\`load-${id}\`, …)`) is omitted by the feeder, so a
|
|
1555
|
+
* deliberately-parameterized fan-out is never flagged. `ctx.runStep(stepDef, …)`
|
|
1556
|
+
* names (which come from `defineStep` in another file) are out of scope here.
|
|
1557
|
+
* Only runs when the declaration feeder supplied step evidence
|
|
1558
|
+
* (`workflow.steps` present); a runtime caller flags nothing.
|
|
1559
|
+
*/
|
|
1560
|
+
declare const workflowDuplicateStepName: Lint;
|
|
1561
|
+
/**
|
|
1526
1562
|
* A correctness lint: every `ctx.workflows.get("name")` call must reference a
|
|
1527
1563
|
* workflow that exists — i.e. a `defineWorkflow` export in `lunora/workflows.ts`.
|
|
1528
1564
|
* A `.get("x")` whose `"x"` resolves to no declared workflow is a typo or a
|
|
@@ -1584,4 +1620,4 @@ interface RunAdvisorOptions {
|
|
|
1584
1620
|
* `static` lints at build time and defer `runtime` lints to a live shard.
|
|
1585
1621
|
*/
|
|
1586
1622
|
declare const runAdvisor: (context: LintContext, options?: RunAdvisorOptions) => Finding[];
|
|
1587
|
-
export { AE_METRIC_EVENTS, ALL_LINTS, type AdvisorAdminRoute, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorContainer, type AdvisorHyperdriveCall, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorMaskProcedure, type AdvisorMutatorWrite, type AdvisorNondeterministicCall, type AdvisorProcedureProtection, type AdvisorQueryRead, type AdvisorR2sqlCall, type AdvisorRelation, type AdvisorRlsProcedure, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSqlInterpolation, type AdvisorTable, type AdvisorTableSample, type AdvisorTableScan, type AdvisorWorkflow, type AdvisorWorkflowCall, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type Category, type Facing, type Finding, type Level, type Lint, type LintContext, type LintSource, RUNTIME_LINTS, RunAdvisorOptions, STATIC_LINTS, adminRouteWithoutGuard, authApiCallWithoutHeaders, circularFk, constraintValidator, containerOversizedInstance, containerPublicInternet, duplicateIndex, emptyIndex, filterWithoutIndex, fromServerSchema, hardcodedSecret, hotShard, hyperdriveOutsideAction, indexReferencesUnknownField, indexUtilization, loadAnalyticsRuntimeMetrics, maskUncoveredPiiColumn, mutatorFullRowReplace, nondeterministicQueryMutation, policyReferencesUnknownTable, publicArgumentUsesAny, publicMutationWithoutRatelimit, r2sqlOutsideAction, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, sqlInjectionRisk, tableWithoutInsert, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, userCreatingMutationWithoutCaptcha, workflowUnknownTarget, workflowUnused };
|
|
1623
|
+
export { AE_METRIC_EVENTS, ALL_LINTS, type AdvisorAdminRoute, type AdvisorArgumentValidator, type AdvisorAuthApiCall, type AdvisorContainer, type AdvisorHyperdriveCall, type AdvisorIndex, type AdvisorIndexHit, type AdvisorInsertWrite, type AdvisorMaskProcedure, type AdvisorMutatorWrite, type AdvisorNondeterministicCall, type AdvisorProcedureProtection, type AdvisorQueryRead, type AdvisorR2sqlCall, type AdvisorRelation, type AdvisorRlsProcedure, type AdvisorSchema, type AdvisorSecretLiteral, type AdvisorShape, type AdvisorShardTraffic, type AdvisorSqlInterpolation, type AdvisorTable, type AdvisorTableSample, type AdvisorTableScan, type AdvisorWorkflow, type AdvisorWorkflowCall, type AnalyticsMetricsOptions, type AnalyticsMetricsSource, type AnalyticsRuntimeMetrics, type Category, type Facing, type Finding, type Level, type Lint, type LintContext, type LintSource, RUNTIME_LINTS, RunAdvisorOptions, STATIC_LINTS, adminRouteWithoutGuard, authApiCallWithoutHeaders, circularFk, constraintValidator, containerOversizedInstance, containerPublicInternet, duplicateIndex, emptyIndex, filterWithoutIndex, fromServerSchema, hardcodedSecret, hotShard, hyperdriveOutsideAction, indexReferencesUnknownField, indexUtilization, loadAnalyticsRuntimeMetrics, maskUncoveredPiiColumn, mutatorFullRowReplace, nondeterministicQueryMutation, policyReferencesUnknownTable, publicArgumentUsesAny, publicMutationWithoutRatelimit, r2sqlOutsideAction, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, sqlInjectionRisk, tableWithoutInsert, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, userCreatingMutationWithoutCaptcha, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
|
package/dist/index.mjs
CHANGED
|
@@ -30,6 +30,7 @@ import unboundedStringArgument from './packem_shared/unboundedStringArgument-DTh
|
|
|
30
30
|
import unindexedForeignKey from './packem_shared/unindexedForeignKey-BgJbKyqK.mjs';
|
|
31
31
|
import unindexedRelationTarget from './packem_shared/unindexedRelationTarget-D6eyj6Xx.mjs';
|
|
32
32
|
import userCreatingMutationWithoutCaptcha from './packem_shared/userCreatingMutationWithoutCaptcha-CH31YsUZ.mjs';
|
|
33
|
+
import workflowDuplicateStepName from './packem_shared/workflowDuplicateStepName-ioBxPBCy.mjs';
|
|
33
34
|
import workflowUnknownTarget from './packem_shared/workflowUnknownTarget-Cdd7WhKQ.mjs';
|
|
34
35
|
import workflowUnused from './packem_shared/workflowUnused-D0jHxdz9.mjs';
|
|
35
36
|
export { AE_METRIC_EVENTS, loadAnalyticsRuntimeMetrics } from './packem_shared/AE_METRIC_EVENTS-DexctYv6.mjs';
|
|
@@ -40,6 +41,7 @@ const STATIC_LINTS = [
|
|
|
40
41
|
relationReferencesUnknownTable,
|
|
41
42
|
relationReferencesUnknownField,
|
|
42
43
|
workflowUnknownTarget,
|
|
44
|
+
workflowDuplicateStepName,
|
|
43
45
|
shapeUnknownTable,
|
|
44
46
|
emptyIndex,
|
|
45
47
|
circularFk,
|
|
@@ -82,4 +84,4 @@ const runAdvisor = (context, options = {}) => {
|
|
|
82
84
|
return findings;
|
|
83
85
|
};
|
|
84
86
|
|
|
85
|
-
export { ALL_LINTS, RUNTIME_LINTS, STATIC_LINTS, adminRouteWithoutGuard, authApiCallWithoutHeaders, circularFk, constraintValidator, containerOversizedInstance, containerPublicInternet, duplicateIndex, emptyIndex, filterWithoutIndex, hardcodedSecret, hotShard, hyperdriveOutsideAction, indexReferencesUnknownField, indexUtilization, maskUncoveredPiiColumn, mutatorFullRowReplace, nondeterministicQueryMutation, policyReferencesUnknownTable, publicArgumentUsesAny, publicMutationWithoutRatelimit, r2sqlOutsideAction, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, sqlInjectionRisk, tableWithoutInsert, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, userCreatingMutationWithoutCaptcha, workflowUnknownTarget, workflowUnused };
|
|
87
|
+
export { ALL_LINTS, RUNTIME_LINTS, STATIC_LINTS, adminRouteWithoutGuard, authApiCallWithoutHeaders, circularFk, constraintValidator, containerOversizedInstance, containerPublicInternet, duplicateIndex, emptyIndex, filterWithoutIndex, hardcodedSecret, hotShard, hyperdriveOutsideAction, indexReferencesUnknownField, indexUtilization, maskUncoveredPiiColumn, mutatorFullRowReplace, nondeterministicQueryMutation, policyReferencesUnknownTable, publicArgumentUsesAny, publicMutationWithoutRatelimit, r2sqlOutsideAction, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, sqlInjectionRisk, tableWithoutInsert, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, userCreatingMutationWithoutCaptcha, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const workflowDuplicateStepName = {
|
|
4
|
+
categories: ["SCHEMA"],
|
|
5
|
+
description: "Two durable steps in this workflow share a name. Cloudflare memoizes a step by its name, so on replay the second call returns the first step's cached result instead of running its body — silently skipping the work without an error.",
|
|
6
|
+
facing: "INTERNAL",
|
|
7
|
+
level: "ERROR",
|
|
8
|
+
// eslint-disable-next-line no-secrets/no-secrets -- the lint's rule id, not a credential
|
|
9
|
+
name: "workflow_duplicate_step_name",
|
|
10
|
+
remediation: "Give every `step.do` / `step.sleep` / `step.sleepUntil` / `step.waitForEvent` call in the workflow a unique name. If a step legitimately repeats (e.g. a loop), make the name distinct per iteration by interpolating the item id into the step name.",
|
|
11
|
+
run: (context) => {
|
|
12
|
+
if (context.workflows === void 0) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const findings = [];
|
|
16
|
+
for (const workflow of context.workflows) {
|
|
17
|
+
const { steps } = workflow;
|
|
18
|
+
if (steps === void 0 || steps.length === 0) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const firstLineByName = /* @__PURE__ */ new Map();
|
|
22
|
+
const reported = /* @__PURE__ */ new Set();
|
|
23
|
+
for (const step of steps) {
|
|
24
|
+
const firstLine = firstLineByName.get(step.name);
|
|
25
|
+
if (firstLine === void 0) {
|
|
26
|
+
firstLineByName.set(step.name, step.line);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (reported.has(step.name)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
reported.add(step.name);
|
|
33
|
+
findings.push(
|
|
34
|
+
emit(workflowDuplicateStepName, {
|
|
35
|
+
cacheKey: `workflow_duplicate_step_name:${workflow.exportName}:${step.name}`,
|
|
36
|
+
detail: `Workflow "${workflow.exportName}" reuses the durable step name "${step.name}" (first at line ${String(firstLine)}, again at line ${String(step.line)}). The second call returns the first's cached result instead of running.`,
|
|
37
|
+
metadata: { firstLine, line: step.line, stepName: step.name, workflow: workflow.exportName }
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return findings;
|
|
43
|
+
},
|
|
44
|
+
source: "static",
|
|
45
|
+
title: "Duplicate durable step name in workflow"
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export { workflowDuplicateStepName as default };
|