tubeless 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +52 -52
  2. package/dist/cli/cli-types.d.ts +1 -1
  3. package/dist/cli/cli-types.js.map +1 -1
  4. package/dist/cli/cli.d.ts +2 -4
  5. package/dist/cli/cli.js +1 -3
  6. package/dist/cli/cli.js.map +1 -1
  7. package/dist/core/child-execution.js +9 -4
  8. package/dist/core/child-execution.js.map +1 -1
  9. package/dist/core/lifecycle.d.ts +1 -1
  10. package/dist/core/lifecycle.js +2 -0
  11. package/dist/core/lifecycle.js.map +1 -1
  12. package/dist/core/pipeline-compiler.d.ts +17 -0
  13. package/dist/core/pipeline-compiler.js +44 -0
  14. package/dist/core/pipeline-compiler.js.map +1 -0
  15. package/dist/core/pipeline-definition-validation.d.ts +4 -0
  16. package/dist/core/pipeline-definition-validation.js +101 -0
  17. package/dist/core/pipeline-definition-validation.js.map +1 -0
  18. package/dist/core/pipeline-definition.d.ts +28 -0
  19. package/dist/core/pipeline-definition.js +2 -0
  20. package/dist/core/pipeline-definition.js.map +1 -0
  21. package/dist/core/pipeline-disposition.d.ts +26 -0
  22. package/dist/core/pipeline-disposition.js +44 -0
  23. package/dist/core/pipeline-disposition.js.map +1 -0
  24. package/dist/core/pipeline-errors.d.ts +8 -0
  25. package/dist/core/pipeline-errors.js +20 -0
  26. package/dist/core/pipeline-errors.js.map +1 -0
  27. package/dist/core/pipeline-execute.d.ts +3 -1
  28. package/dist/core/pipeline-execute.js +136 -396
  29. package/dist/core/pipeline-execute.js.map +1 -1
  30. package/dist/core/pipeline-finalizer.d.ts +18 -0
  31. package/dist/core/pipeline-finalizer.js +37 -0
  32. package/dist/core/pipeline-finalizer.js.map +1 -0
  33. package/dist/core/pipeline-graph.d.ts +23 -0
  34. package/dist/core/pipeline-graph.js +135 -0
  35. package/dist/core/pipeline-graph.js.map +1 -0
  36. package/dist/core/pipeline-identity.d.ts +5 -0
  37. package/dist/core/pipeline-identity.js +11 -0
  38. package/dist/core/pipeline-identity.js.map +1 -0
  39. package/dist/core/pipeline-mermaid.d.ts +4 -0
  40. package/dist/core/pipeline-mermaid.js +56 -0
  41. package/dist/core/pipeline-mermaid.js.map +1 -0
  42. package/dist/core/pipeline-plan.d.ts +5 -94
  43. package/dist/core/pipeline-plan.js +5 -392
  44. package/dist/core/pipeline-plan.js.map +1 -1
  45. package/dist/core/pipeline-run-state.d.ts +49 -0
  46. package/dist/core/pipeline-run-state.js +284 -0
  47. package/dist/core/pipeline-run-state.js.map +1 -0
  48. package/dist/core/pipeline-step-executor.d.ts +20 -0
  49. package/dist/core/pipeline-step-executor.js +52 -0
  50. package/dist/core/pipeline-step-executor.js.map +1 -0
  51. package/dist/core/pipeline-step-metadata.d.ts +4 -0
  52. package/dist/core/pipeline-step-metadata.js +5 -0
  53. package/dist/core/pipeline-step-metadata.js.map +1 -0
  54. package/dist/core/pipeline-steps.d.ts +1 -1
  55. package/dist/core/pipeline-steps.js +1 -1
  56. package/dist/core/pipeline-steps.js.map +1 -1
  57. package/dist/core/pipeline-types.d.ts +17 -8
  58. package/dist/core/pipeline-types.js +8 -0
  59. package/dist/core/pipeline-types.js.map +1 -1
  60. package/dist/core/pipeline-validation.d.ts +6 -0
  61. package/dist/core/pipeline-validation.js +41 -0
  62. package/dist/core/pipeline-validation.js.map +1 -0
  63. package/dist/core/pipeline.d.ts +8 -9
  64. package/dist/core/pipeline.js +9 -6
  65. package/dist/core/pipeline.js.map +1 -1
  66. package/dist/node/node.d.ts +0 -3
  67. package/dist/node/node.js +0 -3
  68. package/dist/node/node.js.map +1 -1
  69. package/dist/reporter/reporter-entry.d.ts +1 -2
  70. package/dist/reporter/reporter-entry.js +1 -2
  71. package/dist/reporter/reporter-entry.js.map +1 -1
  72. package/dist/run-store/run-store-codec.d.ts +4 -0
  73. package/dist/run-store/run-store-codec.js +31 -0
  74. package/dist/run-store/run-store-codec.js.map +1 -0
  75. package/dist/run-store/run-store-ndjson.js +2 -295
  76. package/dist/run-store/run-store-ndjson.js.map +1 -1
  77. package/dist/run-store/run-store-sqlite.js +41 -38
  78. package/dist/run-store/run-store-sqlite.js.map +1 -1
  79. package/dist/run-store/run-store.d.ts +4 -3
  80. package/dist/run-store/run-store.js +38 -195
  81. package/dist/run-store/run-store.js.map +1 -1
  82. package/dist/studio/run-store-ui-client-app.d.ts +12 -0
  83. package/dist/studio/run-store-ui-client-app.js +224 -0
  84. package/dist/studio/run-store-ui-client-app.js.map +1 -0
  85. package/dist/studio/run-store-ui-client-browser.d.ts +1 -0
  86. package/dist/studio/run-store-ui-client-browser.js +2 -0
  87. package/dist/studio/run-store-ui-client-commands.d.ts +18 -0
  88. package/dist/studio/run-store-ui-client-commands.js +98 -0
  89. package/dist/studio/run-store-ui-client-commands.js.map +1 -0
  90. package/dist/studio/run-store-ui-client-form.d.ts +11 -0
  91. package/dist/studio/run-store-ui-client-form.js +54 -0
  92. package/dist/studio/run-store-ui-client-form.js.map +1 -0
  93. package/dist/studio/run-store-ui-client-modals.d.ts +20 -0
  94. package/dist/studio/run-store-ui-client-modals.js +154 -0
  95. package/dist/studio/run-store-ui-client-modals.js.map +1 -0
  96. package/dist/studio/run-store-ui-client-model.d.ts +19 -0
  97. package/dist/studio/run-store-ui-client-model.js +189 -0
  98. package/dist/studio/run-store-ui-client-model.js.map +1 -0
  99. package/dist/studio/run-store-ui-client-runs.d.ts +24 -0
  100. package/dist/studio/run-store-ui-client-runs.js +78 -0
  101. package/dist/studio/run-store-ui-client-runs.js.map +1 -0
  102. package/dist/studio/run-store-ui-client-shared.d.ts +22 -0
  103. package/dist/studio/run-store-ui-client-shared.js +63 -0
  104. package/dist/studio/run-store-ui-client-shared.js.map +1 -0
  105. package/dist/studio/run-store-ui-client-transport.d.ts +25 -0
  106. package/dist/studio/run-store-ui-client-transport.js +317 -0
  107. package/dist/studio/run-store-ui-client-transport.js.map +1 -0
  108. package/dist/studio/run-store-ui-client.d.ts +2 -14
  109. package/dist/studio/run-store-ui-client.js +9 -1360
  110. package/dist/studio/run-store-ui-client.js.map +1 -1
  111. package/dist/studio/run-store-ui-page.d.ts +2 -2
  112. package/dist/studio/run-store-ui-page.js +7 -71
  113. package/dist/studio/run-store-ui-page.js.map +1 -1
  114. package/dist/studio/run-store-ui.d.ts +1 -1
  115. package/dist/studio/run-store-ui.js +43 -25
  116. package/dist/studio/run-store-ui.js.map +1 -1
  117. package/dist/tracing/tracing-codec.d.ts +5 -0
  118. package/dist/tracing/tracing-codec.js +13 -0
  119. package/dist/tracing/tracing-codec.js.map +1 -0
  120. package/dist/tracing/tracing-constants.d.ts +4 -0
  121. package/dist/tracing/tracing-constants.js +5 -0
  122. package/dist/tracing/tracing-constants.js.map +1 -0
  123. package/dist/tracing/tracing-contracts.d.ts +31 -0
  124. package/dist/tracing/tracing-contracts.js +2 -0
  125. package/dist/tracing/tracing-contracts.js.map +1 -0
  126. package/dist/tracing/tracing-internal.d.ts +1 -1
  127. package/dist/tracing/tracing-internal.js +116 -81
  128. package/dist/tracing/tracing-internal.js.map +1 -1
  129. package/dist/tracing/tracing-schema.d.ts +809 -0
  130. package/dist/tracing/tracing-schema.js +429 -0
  131. package/dist/tracing/tracing-schema.js.map +1 -0
  132. package/dist/tracing/tracing.d.ts +2 -54
  133. package/dist/tracing/tracing.js.map +1 -1
  134. package/dist/tracing/wire-schema.d.ts +50 -0
  135. package/dist/tracing/wire-schema.js +193 -0
  136. package/dist/tracing/wire-schema.js.map +1 -0
  137. package/dist/workbench/pipeline-module.js +4 -4
  138. package/dist/workbench/pipeline-module.js.map +1 -1
  139. package/dist/workbench/workbench-entry.d.ts +2 -0
  140. package/dist/workbench/workbench-entry.js +3 -0
  141. package/dist/workbench/workbench-entry.js.map +1 -0
  142. package/dist/workbench/workbench-history.js +8 -1
  143. package/dist/workbench/workbench-history.js.map +1 -1
  144. package/dist/workbench/workbench-ui.js +39 -43
  145. package/dist/workbench/workbench-ui.js.map +1 -1
  146. package/docs/README.md +22 -15
  147. package/docs/agent-guide.md +62 -68
  148. package/docs/agent-skills.md +59 -0
  149. package/docs/api-reference.md +14 -178
  150. package/docs/api-report.json +13 -215
  151. package/docs/child-pipeline-composition.md +139 -193
  152. package/docs/cli.md +86 -64
  153. package/docs/comparison.md +64 -62
  154. package/docs/concepts.md +210 -281
  155. package/docs/getting-started.md +61 -49
  156. package/docs/llms.txt +8 -8
  157. package/docs/recipes.md +42 -45
  158. package/docs/remote-step-composition.md +69 -74
  159. package/docs/studio.md +110 -73
  160. package/examples/catalog/pipelines/enrich.ts +2 -2
  161. package/examples/catalog/scripts/enrich.ts +1 -1
  162. package/examples/catalog/scripts/import.ts +1 -1
  163. package/examples/catalog/scripts/publish.ts +1 -1
  164. package/examples/catalog/tubeless.project.ts +6 -3
  165. package/examples/cli-job.ts +1 -1
  166. package/examples/host-embedding.ts +4 -5
  167. package/examples/live-tui.ts +1 -1
  168. package/examples/peloton.ts +1 -1
  169. package/examples/remote-steps.ts +8 -5
  170. package/examples/tracing.ts +96 -5
  171. package/package.json +17 -64
  172. package/skills/tubeless/SKILL.md +85 -0
  173. package/skills/tubeless-make-pipeline/SKILL.md +126 -0
  174. package/dist/node/env.d.ts +0 -1
  175. package/dist/node/env.js +0 -8
  176. package/dist/node/env.js.map +0 -1
  177. package/dist/node/file-utils.d.ts +0 -3
  178. package/dist/node/file-utils.js +0 -17
  179. package/dist/node/file-utils.js.map +0 -1
  180. package/dist/node/paths.d.ts +0 -8
  181. package/dist/node/paths.js +0 -19
  182. package/dist/node/paths.js.map +0 -1
  183. package/dist/reporter/prompt-select.d.ts +0 -80
  184. package/dist/reporter/prompt-select.js +0 -197
  185. package/dist/reporter/prompt-select.js.map +0 -1
  186. package/dist/studio/run-store-ui-client-source.d.ts +0 -1
  187. package/dist/studio/run-store-ui-client-source.js +0 -1
  188. package/dist/tracing/tracing-json.d.ts +0 -11
  189. package/dist/tracing/tracing-json.js +0 -8
  190. package/dist/tracing/tracing-json.js.map +0 -1
  191. package/dist/tracing/tracing-otel.d.ts +0 -35
  192. package/dist/tracing/tracing-otel.js +0 -60
  193. package/dist/tracing/tracing-otel.js.map +0 -1
  194. package/dist/workbench/workbench-studio.d.ts +0 -29
  195. package/dist/workbench/workbench-studio.js +0 -61
  196. package/dist/workbench/workbench-studio.js.map +0 -1
  197. package/examples/catalog/tubeless.studio.ts +0 -20
  198. package/examples/local-observability.ts +0 -105
  199. package/examples/rendering.ts +0 -34
@@ -1,97 +1,8 @@
1
- import type { AnyStep, Step, StepOutput } from "./pipeline-steps.js";
2
- import { type InferSchemaInput, type PipelineError, type PipelineExecutionContext, type PipelineMermaidOptions, type PipelinePlan, type PipelinePlanStep, type PipelineRunControls, type PipelineStepReport, type PipelineStepSelectionReason, type PipelineStepSkipReason, type StandardSchemaV1 } from "./pipeline-types.js";
3
- export declare const PIPELINE_FINALIZE_STEP_ID = "__finalize__";
4
- export declare const STEP_OPTIONS_SCHEMA: unique symbol;
5
- export declare const STEP_NESTED_PIPELINE: unique symbol;
6
- export declare const STEP_REMOTE: unique symbol;
7
- export declare const EXECUTE_COMPILED_RUN: unique symbol;
8
- /** Marks the exact object returned by `definePipeline` as a compiled pipeline. */
9
- export declare function brandCompiledPipeline(pipeline: object): void;
10
- /** True only for the object `definePipeline` returned, not `Object.create` wrappers. */
11
- export declare function isCompiledPipeline(pipeline: object): boolean;
12
- export interface CompiledStepGraph<TOptions extends object = object> {
13
- readonly dependsOn: readonly AnyStep<TOptions>[];
14
- readonly optionalDependsOn: readonly AnyStep<TOptions>[];
15
- readonly skipAfterFailureOf: readonly AnyStep<TOptions>[];
16
- }
17
- export declare function compiledStepGraph<TOptions extends object>(compiled: Pick<CompiledPipeline, "stepGraph">, step: AnyStep<TOptions>): CompiledStepGraph<TOptions>;
18
- /**
19
- * Recovers the options type from the steps themselves rather than taking it as its own
20
- * generic parameter. A `TOptions` parameter constrained by `TSteps extends
21
- * readonly AnyStep<TOptions>[]` is self-referential — TypeScript can't solve that at a
22
- * `definePipeline(...)` call site and silently widens `TOptions` to its bare
23
- * `object` constraint (context.options would lose every pipeline-specific
24
- * field). Structuring it as "derive TOptions from TSteps" instead of "constrain TSteps by
25
- * TOptions" avoids the cycle, so callers never write out any of these type parameters.
26
- */
27
- export type StepsOptions<TSteps extends readonly AnyStep[]> = TSteps[number] extends AnyStep<infer TOptions> ? TOptions : object;
28
- export type StepsInputOptions<TSteps extends readonly AnyStep[]> = TSteps[number] extends Step<string, unknown, object, infer TInputOptions, infer _TRunOut> ? TInputOptions : StepsOptions<TSteps>;
29
- export type StepIds<TSteps extends readonly AnyStep[]> = TSteps[number]["id"];
30
- export type TargetIds<TTargets extends readonly AnyStep[]> = TTargets[number]["id"];
31
- type StepDispositionInput<TOptions extends object> = {
32
- dryRun: boolean;
33
- graph: CompiledStepGraph<TOptions>;
34
- planned: PipelinePlanStep;
35
- reportsByStepId: ReadonlyMap<string, PipelineStepReport>;
36
- step: AnyStep<TOptions>;
37
- };
38
- type StepDisposition = {
39
- kind: "run";
40
- } | {
41
- kind: "skip";
42
- reason: PipelineStepSkipReason;
43
- dependencyId?: string;
44
- message?: string;
45
- };
46
- /**
47
- * Single skip ladder for plan and live execution.
48
- * Policy skips stay outside: they run only after a step is scheduled.
49
- * Remainder copies plan `skipReason` for structural skips and marks planned-run
50
- * steps fail-fast or cancelled, so this ladder has no abort/fail-fast mode.
51
- */
52
- export declare function decideStepDisposition<TOptions extends object>(input: StepDispositionInput<TOptions>): StepDisposition;
53
- export declare function renderPipelineMermaid<TOptions extends object>(steps: readonly AnyStep<TOptions>[], options: PipelineMermaidOptions, stepGraph?: ReadonlyMap<AnyStep, CompiledStepGraph>): string;
1
+ import type { CompiledPipeline } from "./pipeline-compiler.js";
2
+ import type { StepIds, TargetIds } from "./pipeline-definition.js";
3
+ import { type CompiledStepGraph } from "./pipeline-graph.js";
4
+ import type { AnyStep } from "./pipeline-steps.js";
5
+ import type { PipelinePlan, PipelinePlanStep, PipelineRunControls, PipelineStepSelectionReason, PipelineStepSkipReason, StandardSchemaV1 } from "./pipeline-types.js";
54
6
  export declare function stepToPlanStep<TOptions extends object>(step: AnyStep<TOptions>, selected: boolean, skipReason?: PipelineStepSkipReason, selectionReasons?: readonly PipelineStepSelectionReason[], graph?: CompiledStepGraph<TOptions>): PipelinePlanStep;
55
7
  export declare function buildPipelinePlan<TSteps extends readonly AnyStep[], TResult, TTargets extends readonly TSteps[number][], TResultSchema extends StandardSchemaV1 | undefined>(compiled: CompiledPipeline<TSteps, TResult, TTargets, TResultSchema>, controls: PipelineRunControls<StepIds<TSteps>, TargetIds<TTargets>>): PipelinePlan;
56
8
  export declare function planStepById(plan: PipelinePlan): Map<string, PipelinePlanStep>;
57
- /** Programmer error raised immediately when a pipeline graph is invalid. */
58
- export declare class PipelineDefinitionError extends Error {
59
- readonly pipelineId: string;
60
- readonly errors: readonly PipelineError[];
61
- constructor(pipelineId: string, errors: readonly PipelineError[]);
62
- }
63
- export interface CompiledPipeline<TSteps extends readonly AnyStep[] = readonly AnyStep[], TResult = unknown, TTargets extends readonly TSteps[number][] = readonly [], TResultSchema extends StandardSchemaV1 | undefined = undefined> {
64
- readonly declaredTargets: readonly AnyStep<StepsOptions<TSteps>>[];
65
- readonly finalize: PipelineDefinition<TSteps, TResult, TTargets, TResultSchema>["finalize"];
66
- readonly id: string;
67
- readonly optionsSchema: StandardSchemaV1 | undefined;
68
- readonly orderedSteps: readonly AnyStep<StepsOptions<TSteps>>[];
69
- readonly requiredFinalizerSteps: readonly AnyStep<StepsOptions<TSteps>>[] | undefined;
70
- readonly stepGraph: ReadonlyMap<AnyStep, CompiledStepGraph>;
71
- readonly resultSchema: TResultSchema | undefined;
72
- readonly stepIds: readonly string[];
73
- readonly targetIds: readonly string[];
74
- }
75
- export declare function compilePipeline<TSteps extends readonly AnyStep[], TResult, TTargets extends readonly TSteps[number][], TResultSchema extends StandardSchemaV1 | undefined>(definition: PipelineDefinition<TSteps, TResult, TTargets, TResultSchema>): CompiledPipeline<TSteps, TResult, TTargets, TResultSchema>;
76
- type PipelineOutputs<TSteps extends readonly AnyStep[]> = {
77
- [S in TSteps[number] as S["id"]]: StepOutput<S>;
78
- };
79
- type RequiredPipelineOutputs<TSteps extends readonly AnyStep[]> = {
80
- [S in TSteps[number] as S["id"]]-?: StepOutput<S>;
81
- };
82
- /**
83
- * Build a finalizer that only runs when every listed step published an output.
84
- * Presence is checked by output slot, so a successfully published `undefined`
85
- * remains distinct from a structural skip or failure.
86
- */
87
- export declare function requireOutputs<const TRequiredSteps extends readonly AnyStep[], TResult>(requiredSteps: TRequiredSteps, finalize: (outputs: RequiredPipelineOutputs<TRequiredSteps>, context: PipelineExecutionContext<StepsOptions<TRequiredSteps>>) => TResult | Promise<TResult>): (outputs: Partial<RequiredPipelineOutputs<TRequiredSteps>>, context: PipelineExecutionContext<StepsOptions<TRequiredSteps>>) => TResult | Promise<TResult>;
88
- export interface PipelineDefinition<TSteps extends readonly AnyStep[], TResult, TTargets extends readonly TSteps[number][] = readonly [], TResultSchema extends StandardSchemaV1 | undefined = undefined> {
89
- id: string;
90
- steps: TSteps;
91
- /** Public downstream goals that callers may select with `targets`. */
92
- targets?: TTargets;
93
- /** Optional Standard Schema for the finalized result. */
94
- resultSchema?: TResultSchema;
95
- finalize(outputs: Partial<PipelineOutputs<TSteps>>, context: PipelineExecutionContext<StepsOptions<TSteps>>): (TResultSchema extends StandardSchemaV1 ? InferSchemaInput<TResultSchema> : TResult) | Promise<TResultSchema extends StandardSchemaV1 ? InferSchemaInput<TResultSchema> : TResult>;
96
- }
97
- export {};
@@ -1,221 +1,8 @@
1
1
  import { duplicateValues } from "../utilities/collections.js";
2
- import { formatPipelineError } from "./pipeline-diagnostics.js";
3
- import { brandTubelessError } from "../utilities/tubeless-error.js";
4
- import { PIPELINE_MERMAID_DIRECTIONS, } from "./pipeline-types.js";
5
- export const PIPELINE_FINALIZE_STEP_ID = "__finalize__";
6
- export const STEP_OPTIONS_SCHEMA = Symbol("tubeless.stepOptionsSchema");
7
- export const STEP_NESTED_PIPELINE = Symbol("tubeless.stepNestedPipeline");
8
- export const STEP_REMOTE = Symbol("tubeless.stepRemote");
9
- const REQUIRED_FINALIZER_OUTPUTS = Symbol("tubeless.requiredFinalizerOutputs");
10
- export const EXECUTE_COMPILED_RUN = Symbol("tubeless.executeCompiledRun");
11
- const compiledPipelines = new WeakSet();
12
- /** Marks the exact object returned by `definePipeline` as a compiled pipeline. */
13
- export function brandCompiledPipeline(pipeline) {
14
- compiledPipelines.add(pipeline);
15
- }
16
- /** True only for the object `definePipeline` returned, not `Object.create` wrappers. */
17
- export function isCompiledPipeline(pipeline) {
18
- return compiledPipelines.has(pipeline);
19
- }
20
- function liveStepGraph(step) {
21
- return {
22
- dependsOn: step.dependsOn ?? [],
23
- optionalDependsOn: step.optionalDependsOn ?? [],
24
- skipAfterFailureOf: step.skipAfterFailureOf ?? [],
25
- };
26
- }
27
- function freezeStepList(steps) {
28
- return Object.freeze([...(steps ?? [])]);
29
- }
30
- function snapshotCompiledStepGraph(step) {
31
- return Object.freeze({
32
- dependsOn: freezeStepList(step.dependsOn),
33
- optionalDependsOn: freezeStepList(step.optionalDependsOn),
34
- skipAfterFailureOf: freezeStepList(step.skipAfterFailureOf),
35
- });
36
- }
37
- export function compiledStepGraph(compiled, step) {
38
- const graph = compiled.stepGraph.get(step);
39
- if (graph === undefined) {
40
- throw new Error(`Compiled pipeline graph is missing step ${step.id}`);
41
- }
42
- // SAFETY: compile stores each original step object as the map key.
43
- return graph;
44
- }
45
- function pipelineDiagnostic(code, phase, kind, message, fields = {}) {
46
- return { code, kind, message, phase, ...fields };
47
- }
48
- /** Required dependencies are met when completed, or intentionally policy-skipped. */
49
- function isRequiredDependencyMet(report) {
50
- if (!report)
51
- return false;
52
- if (report.status === "completed")
53
- return true;
54
- return report.status === "skipped" && report.reason === "policy";
55
- }
56
- /**
57
- * Single skip ladder for plan and live execution.
58
- * Policy skips stay outside: they run only after a step is scheduled.
59
- * Remainder copies plan `skipReason` for structural skips and marks planned-run
60
- * steps fail-fast or cancelled, so this ladder has no abort/fail-fast mode.
61
- */
62
- export function decideStepDisposition(input) {
63
- const { dryRun, graph, planned, reportsByStepId, step } = input;
64
- if (!planned.selected) {
65
- return { kind: "skip", reason: "filtered" };
66
- }
67
- const unsuccessfulAncestor = graph.skipAfterFailureOf.find((dep) => {
68
- const status = reportsByStepId.get(dep.id)?.status;
69
- return status === "failed" || status === "cancelled";
70
- });
71
- if (unsuccessfulAncestor) {
72
- return {
73
- kind: "skip",
74
- reason: "failed-dependency",
75
- dependencyId: unsuccessfulAncestor.id,
76
- };
77
- }
78
- const unmetDependency = graph.dependsOn.find((dep) => !isRequiredDependencyMet(reportsByStepId.get(dep.id)));
79
- if (unmetDependency) {
80
- return {
81
- kind: "skip",
82
- reason: "unmet-dependency",
83
- dependencyId: unmetDependency.id,
84
- };
85
- }
86
- if (dryRun && skipsInDryRun(step)) {
87
- return { kind: "skip", reason: "dry-run" };
88
- }
89
- return { kind: "run" };
90
- }
91
- /**
92
- * Deduplicated by object identity: the same dependency commonly appears in more than one
93
- * of these arrays (e.g. a step that's both `optionalDependsOn` and `skipAfterFailureOf` the
94
- * same upstream step). Without dedup, `topologicalSort`'s in-degree count would no longer
95
- * equal the number of distinct prerequisites — harmless there since the inflated count and
96
- * the duplicated `dependentsOf` entries cancel out, but confusing to read and a latent trap
97
- * for future callers of this helper that assume it returns distinct edges.
98
- */
99
- function stepEdges(step, graph = liveStepGraph(step)) {
100
- return [
101
- ...new Set([...graph.dependsOn, ...graph.optionalDependsOn, ...graph.skipAfterFailureOf]),
102
- ];
103
- }
104
- /** Required data and failure-gate closure for dependency-aware targets. */
105
- function targetClosure(targets) {
106
- const selected = new Set();
107
- const include = (step) => {
108
- if (selected.has(step))
109
- return;
110
- selected.add(step);
111
- for (const prerequisite of [...(step.dependsOn ?? []), ...(step.skipAfterFailureOf ?? [])]) {
112
- include(prerequisite);
113
- }
114
- };
115
- for (const target of targets)
116
- include(target);
117
- return selected;
118
- }
119
- /**
120
- * Orders `steps` so every dependency runs before its dependents, using each step's
121
- * original array position to break ties. For an array that's already in valid dependency
122
- * order (true of every hand-ordered pipeline today) this provably reproduces that same
123
- * order — each step becomes eligible (in-degree 0) at the same point it would've been
124
- * reached by a plain declaration-order walk. What it adds is tolerance for a `steps` array
125
- * that ISN'T already ordered, and a real error instead of silent misbehavior on a cycle.
126
- * Returns null if the graph has a cycle (shouldn't happen given `const`-reference
127
- * construction, but the executor should never spin or drop steps if it somehow does).
128
- */
129
- function topologicalSort(steps) {
130
- const indexById = new Map(steps.map((step, index) => [step.id, index]));
131
- const remainingInDegree = new Map(steps.map((step) => [step.id, stepEdges(step).length]));
132
- const dependentsOf = new Map();
133
- for (const step of steps) {
134
- for (const dep of stepEdges(step)) {
135
- const dependents = dependentsOf.get(dep.id) ?? [];
136
- dependents.push(step.id);
137
- dependentsOf.set(dep.id, dependents);
138
- }
139
- }
140
- const ready = steps.filter((step) => remainingInDegree.get(step.id) === 0).map((step) => step.id);
141
- const orderedIds = [];
142
- while (ready.length > 0) {
143
- ready.sort((left, right) => (indexById.get(left) ?? 0) - (indexById.get(right) ?? 0));
144
- const nextId = ready.shift();
145
- if (nextId === undefined) {
146
- break;
147
- }
148
- orderedIds.push(nextId);
149
- for (const dependentId of dependentsOf.get(nextId) ?? []) {
150
- const remaining = (remainingInDegree.get(dependentId) ?? 0) - 1;
151
- remainingInDegree.set(dependentId, remaining);
152
- if (remaining === 0) {
153
- ready.push(dependentId);
154
- }
155
- }
156
- }
157
- if (orderedIds.length !== steps.length) {
158
- return null;
159
- }
160
- const stepsById = new Map(steps.map((step) => [step.id, step]));
161
- // SAFETY: every id in `orderedIds` was produced by walking the same `steps`
162
- // array, so the map lookup always finds the matching `AnyStep<TOptions>`.
163
- return orderedIds.map((id) => stepsById.get(id));
164
- }
165
- function escapeMermaidLabel(value) {
166
- return value
167
- .replace(/\r\n?|\n/g, " ")
168
- .replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
169
- .replace(/#/g, "#35;")
170
- .replace(/&/g, "#38;")
171
- .replace(/"/g, "#quot;")
172
- .replace(/</g, "#60;")
173
- .replace(/>/g, "#62;");
174
- }
175
- export function renderPipelineMermaid(steps, options, stepGraph) {
176
- const direction = options.direction ?? "TD";
177
- if (!PIPELINE_MERMAID_DIRECTIONS.includes(direction)) {
178
- throw new Error(`Invalid Mermaid flowchart direction: ${direction}`);
179
- }
180
- const nodeIdByStep = new Map(steps.map((step, index) => [step, `step${index}`]));
181
- const lines = [`flowchart ${direction}`];
182
- for (const step of steps) {
183
- const nodeId = nodeIdByStep.get(step);
184
- const displayName = step.name ?? step.id;
185
- const label = options.includeDescriptions && step.description
186
- ? `${displayName} — ${step.description}`
187
- : displayName;
188
- lines.push(` ${nodeId}["${escapeMermaidLabel(label)}"]`);
189
- }
190
- const edgeLines = [];
191
- for (const step of steps) {
192
- const graph = stepGraph?.get(step) ?? liveStepGraph(step);
193
- const targetId = nodeIdByStep.get(step);
194
- const required = new Set(graph.dependsOn);
195
- const optional = new Set(graph.optionalDependsOn);
196
- const failureGates = new Set(graph.skipAfterFailureOf);
197
- for (const dependency of stepEdges(step, graph)) {
198
- const sourceId = nodeIdByStep.get(dependency);
199
- if (required.has(dependency)) {
200
- edgeLines.push(` ${sourceId} --> ${targetId}`);
201
- continue;
202
- }
203
- if (optional.has(dependency) && failureGates.has(dependency)) {
204
- edgeLines.push(` ${sourceId} -. optional input + failure gate .-> ${targetId}`);
205
- continue;
206
- }
207
- if (optional.has(dependency)) {
208
- edgeLines.push(` ${sourceId} -. optional input .-> ${targetId}`);
209
- continue;
210
- }
211
- edgeLines.push(` ${sourceId} -. failure gate .-> ${targetId}`);
212
- }
213
- }
214
- if (edgeLines.length > 0) {
215
- lines.push("", ...edgeLines);
216
- }
217
- return `${lines.join("\n")}\n`;
218
- }
2
+ import { decideStepDisposition } from "./pipeline-disposition.js";
3
+ import { pipelineDiagnostic } from "./pipeline-errors.js";
4
+ import { compiledStepGraph, liveStepGraph } from "./pipeline-graph.js";
5
+ import { STEP_NESTED_PIPELINE, STEP_REMOTE } from "./pipeline-step-metadata.js";
219
6
  export function stepToPlanStep(step, selected, skipReason, selectionReasons, graph = liveStepGraph(step)) {
220
7
  const reasons = selectionReasons ?? [{ kind: "all" }];
221
8
  const planStep = {
@@ -291,107 +78,6 @@ function targetSelectionReasons(targets, stepGraph) {
291
78
  function selectsStep(reason) {
292
79
  return !["not-selected", "optional-only", "outside-target-closure"].includes(reason.kind);
293
80
  }
294
- function skipsInDryRun(step) {
295
- return step.dryRun === "skip";
296
- }
297
- function validatePipelineDefinition(definition) {
298
- const steps = definition.steps;
299
- const errors = [];
300
- if (definition.id.trim().length === 0) {
301
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_PIPELINE_ID_BLANK", "definition", "definition", "Pipeline id must not be blank"));
302
- }
303
- const stepIds = steps.map((step) => step.id);
304
- const duplicateStepIds = duplicateValues(stepIds);
305
- if (duplicateStepIds.length > 0) {
306
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_STEP_IDS_DUPLICATE", "definition", "definition", `Pipeline ${definition.id} has duplicate step ids: ${duplicateStepIds.join(", ")}`));
307
- }
308
- const optionsSchemas = new Set(steps.map((step) => step[STEP_OPTIONS_SCHEMA]));
309
- if (optionsSchemas.size > 1 && [...optionsSchemas].some(Boolean)) {
310
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_OPTIONS_SCHEMA_CONFLICT", "definition", "definition", `Pipeline ${definition.id} mixes steps from different options-schema scopes`));
311
- }
312
- for (const step of steps) {
313
- if (step.id.trim().length === 0) {
314
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_STEP_ID_BLANK", "definition", "definition", `Pipeline ${definition.id} has a step with a blank id`, { stepId: step.id }));
315
- }
316
- if (step.id === PIPELINE_FINALIZE_STEP_ID) {
317
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_STEP_ID_RESERVED", "definition", "definition", `Pipeline ${definition.id} uses reserved step id ${PIPELINE_FINALIZE_STEP_ID}`, { stepId: step.id }));
318
- }
319
- if (step.name !== undefined && step.name.trim().length === 0) {
320
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_STEP_NAME_BLANK", "definition", "definition", `Pipeline ${definition.id} step ${step.id} has a blank display name`, { stepId: step.id }));
321
- }
322
- const dependencyGroups = [
323
- ["dependsOn", step.dependsOn ?? []],
324
- ["optionalDependsOn", step.optionalDependsOn ?? []],
325
- ["skipAfterFailureOf", step.skipAfterFailureOf ?? []],
326
- ];
327
- for (const [field, dependencies] of dependencyGroups) {
328
- const duplicates = duplicateValues(dependencies.map((dependency) => dependency.id));
329
- if (duplicates.length > 0) {
330
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_DUPLICATE", "definition", "definition", `Pipeline ${definition.id} step ${step.id} repeats ${field}: ${duplicates.join(", ")}`, { stepId: step.id }));
331
- }
332
- if (dependencies.includes(step)) {
333
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_SELF_REFERENCE", "definition", "definition", `Pipeline ${definition.id} step ${step.id} cannot reference itself in ${field}`, { stepId: step.id }));
334
- }
335
- }
336
- const required = new Set(step.dependsOn ?? []);
337
- const contradictory = (step.optionalDependsOn ?? []).filter((dependency) => required.has(dependency));
338
- if (contradictory.length > 0) {
339
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_CONTRADICTORY", "definition", "definition", `Pipeline ${definition.id} step ${step.id} declares dependencies as both required and optional: ${contradictory.map(({ id }) => id).join(", ")}`, { stepId: step.id }));
340
- }
341
- }
342
- // SAFETY: `steps` is `TSteps extends readonly AnyStep[]`; the cast restores
343
- // the `TOptions` generic that the tuple erased, without changing the values.
344
- const knownSteps = new Set(steps);
345
- // SAFETY: targets are a subset of `TSteps[number]`, each an `AnyStep<TOptions>`.
346
- const declaredTargets = (definition.targets ?? []);
347
- const duplicateTargetIds = duplicateValues(declaredTargets.map((target) => target.id));
348
- if (duplicateTargetIds.length > 0) {
349
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_TARGETS_DUPLICATE", "definition", "definition", `Pipeline ${definition.id} declares duplicate targets: ${duplicateTargetIds.join(", ")}`));
350
- }
351
- const missingTargets = declaredTargets.filter((target) => !knownSteps.has(target));
352
- if (missingTargets.length > 0) {
353
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_TARGET_NOT_IN_STEPS", "definition", "definition", `Pipeline ${definition.id} declares target step(s) not included in its steps list: ${missingTargets.map(({ id }) => id).join(", ")}`));
354
- }
355
- const missingReferences = [];
356
- // SAFETY: `steps` is `TSteps extends readonly AnyStep[]`; the cast restores the
357
- // `TOptions` generic that the tuple erased, without changing the values.
358
- for (const step of steps) {
359
- for (const dependency of stepEdges(step)) {
360
- if (!knownSteps.has(dependency)) {
361
- missingReferences.push(`${step.id} -> ${dependency.id}`);
362
- }
363
- }
364
- }
365
- if (missingReferences.length > 0) {
366
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_NOT_IN_STEPS", "definition", "definition", `Pipeline ${definition.id} references step(s) not included in its steps list: ${missingReferences.join(", ")}`));
367
- }
368
- // SAFETY: `requireOutputs` stamps the required step ids onto the finalizer
369
- // function under `REQUIRED_FINALIZER_OUTPUTS`; the intersection only widens
370
- // the function type to expose that optional property.
371
- const requiredFinalizerSteps = definition.finalize[REQUIRED_FINALIZER_OUTPUTS];
372
- const missingFinalizerSteps = (requiredFinalizerSteps ?? []).filter((step) => !knownSteps.has(step));
373
- if (missingFinalizerSteps.length > 0) {
374
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_FINALIZER_STEP_NOT_IN_STEPS", "definition", "definition", `Pipeline ${definition.id} requires finalizer output from step(s) not included in its steps list: ${missingFinalizerSteps.map(({ id }) => id).join(", ")}`));
375
- }
376
- if (requiredFinalizerSteps &&
377
- missingTargets.length === 0 &&
378
- missingReferences.length === 0 &&
379
- missingFinalizerSteps.length === 0) {
380
- for (const target of declaredTargets) {
381
- const selected = targetClosure([target]);
382
- const missingRequiredSteps = requiredFinalizerSteps.filter((step) => !selected.has(step));
383
- if (missingRequiredSteps.length > 0) {
384
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_TARGET_FINALIZER_MISMATCH", "definition", "definition", `Pipeline ${definition.id} target ${target.id} cannot satisfy required finalizer output(s): ${missingRequiredSteps.map(({ id }) => id).join(", ")}`, { stepId: target.id }));
385
- }
386
- }
387
- }
388
- // SAFETY: `steps` is `TSteps extends readonly AnyStep[]`; the cast restores the
389
- // `TOptions` generic that the tuple erased, without changing the values.
390
- if (errors.length === 0 && !topologicalSort(steps)) {
391
- errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_CYCLE", "definition", "definition", `Pipeline ${definition.id} has a dependency cycle`));
392
- }
393
- return errors;
394
- }
395
81
  export function buildPipelinePlan(compiled, controls) {
396
82
  const dryRun = controls.dryRun === true;
397
83
  const steps = compiled.orderedSteps;
@@ -429,7 +115,6 @@ export function buildPipelinePlan(compiled, controls) {
429
115
  if (undeclaredTargets.length > 0) {
430
116
  errors.push(pipelineDiagnostic("TUBELESS_PLANNING_TARGET_UNDECLARED", "planning", "selection", `Pipeline ${compiled.id} requested undeclared targets: ${undeclaredTargets.join(", ")}`));
431
117
  }
432
- const orderedSteps = steps;
433
118
  if (errors.length > 0) {
434
119
  return {
435
120
  dryRun,
@@ -470,7 +155,7 @@ export function buildPipelinePlan(compiled, controls) {
470
155
  // Accumulated planned skip/complete reports so later steps see the same
471
156
  // unmet-dependency chain as today's skipReason walk (not one empty map).
472
157
  const plannedReportsByStepId = new Map();
473
- for (const step of orderedSteps) {
158
+ for (const step of steps) {
474
159
  const selectionReasons = selectionReasonsByStepId.get(step.id);
475
160
  const selected = selectionReasons.some(selectsStep);
476
161
  const graph = compiledStepGraph(compiled, step);
@@ -524,76 +209,4 @@ export function buildPipelinePlan(compiled, controls) {
524
209
  export function planStepById(plan) {
525
210
  return new Map(plan.steps.map((step) => [step.id, step]));
526
211
  }
527
- /** Programmer error raised immediately when a pipeline graph is invalid. */
528
- export class PipelineDefinitionError extends Error {
529
- pipelineId;
530
- errors;
531
- constructor(pipelineId, errors) {
532
- super(errors.length > 0
533
- ? `Invalid pipeline ${pipelineId}: ${errors.map((error) => formatPipelineError(error)).join("; ")}`
534
- : `Invalid pipeline ${pipelineId}`);
535
- this.pipelineId = pipelineId;
536
- this.errors = errors;
537
- this.name = "PipelineDefinitionError";
538
- brandTubelessError(this, "pipeline-definition");
539
- }
540
- }
541
- export function compilePipeline(definition) {
542
- const errors = validatePipelineDefinition(definition);
543
- if (errors.length > 0) {
544
- throw new PipelineDefinitionError(definition.id, errors);
545
- }
546
- // SAFETY: `steps` is `TSteps extends readonly AnyStep[]`; the cast restores
547
- // the `TOptions` generic that the tuple erased, without changing the values.
548
- const orderedSteps = topologicalSort(definition.steps);
549
- // Keep the author's step objects so class private accessors and prototype
550
- // `run`/`skip` keep working. Graph arrays live in compiled storage so frozen
551
- // or later-mutated caller-owned steps cannot change planning.
552
- const stepGraph = new Map(orderedSteps.map((step) => [step, snapshotCompiledStepGraph(step)]));
553
- // SAFETY: `requireOutputs` stamps the required step ids onto the finalizer
554
- // function under `REQUIRED_FINALIZER_OUTPUTS`; the intersection only widens
555
- // the function type to expose that optional property.
556
- const requiredFinalizerSteps = definition.finalize[REQUIRED_FINALIZER_OUTPUTS];
557
- // SAFETY: targets are a subset of `TSteps[number]`, each an `AnyStep<TOptions>`.
558
- const declaredTargets = (definition.targets ?? []);
559
- return Object.freeze({
560
- declaredTargets: Object.freeze([...declaredTargets]),
561
- // Invoke on the author's definition so method-style finalizers keep `this`.
562
- // SAFETY: the wrapper only forwards to `definition.finalize`.
563
- finalize: ((outputs, context) => definition.finalize(outputs, context)),
564
- id: definition.id,
565
- optionsSchema: definition.steps[0]?.[STEP_OPTIONS_SCHEMA],
566
- orderedSteps: Object.freeze([...orderedSteps]),
567
- stepGraph,
568
- requiredFinalizerSteps: requiredFinalizerSteps
569
- ? Object.freeze([...requiredFinalizerSteps])
570
- : undefined,
571
- resultSchema: definition.resultSchema,
572
- stepIds: Object.freeze(definition.steps.map((step) => step.id)),
573
- targetIds: Object.freeze((definition.targets ?? []).map((step) => step.id)),
574
- });
575
- }
576
- /**
577
- * Build a finalizer that only runs when every listed step published an output.
578
- * Presence is checked by output slot, so a successfully published `undefined`
579
- * remains distinct from a structural skip or failure.
580
- */
581
- export function requireOutputs(requiredSteps, finalize) {
582
- const uniqueRequiredSteps = [...new Set(requiredSteps)];
583
- const requiredFinalizer = (outputs, context) => {
584
- const missingStepIds = uniqueRequiredSteps
585
- .filter((step) => !Object.prototype.hasOwnProperty.call(outputs, step.id))
586
- .map((step) => step.id);
587
- if (missingStepIds.length > 0) {
588
- throw new Error(`Required pipeline outputs missing: ${missingStepIds.join(", ")}`);
589
- }
590
- // SAFETY: every required step id was verified present above, so the partial
591
- // outputs contain all required keys and are a complete required-outputs map.
592
- return finalize(outputs, context);
593
- };
594
- Object.defineProperty(requiredFinalizer, REQUIRED_FINALIZER_OUTPUTS, {
595
- value: uniqueRequiredSteps,
596
- });
597
- return requiredFinalizer;
598
- }
599
212
  //# sourceMappingURL=pipeline-plan.js.map