@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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/advisor",
3
- "version": "1.0.0-alpha.8",
3
+ "version": "1.0.0-alpha.9",
4
4
  "description": "Schema & query lints (splinter-style advisors) for Lunora, feeding the Studio Advisors view",
5
5
  "keywords": [
6
6
  "advisor",