tubeless 0.2.2 → 0.2.3
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/README.md +5 -5
- package/dist/core/child-execution.js +8 -1
- package/dist/core/child-execution.js.map +1 -1
- package/dist/core/lifecycle.js +2 -0
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core/pipeline-execute.d.ts +2 -0
- package/dist/core/pipeline-execute.js +132 -395
- package/dist/core/pipeline-execute.js.map +1 -1
- package/dist/core/pipeline-plan.js +96 -34
- package/dist/core/pipeline-plan.js.map +1 -1
- package/dist/core/pipeline-run-state.d.ts +49 -0
- package/dist/core/pipeline-run-state.js +284 -0
- package/dist/core/pipeline-run-state.js.map +1 -0
- package/dist/core/pipeline-step-executor.d.ts +20 -0
- package/dist/core/pipeline-step-executor.js +52 -0
- package/dist/core/pipeline-step-executor.js.map +1 -0
- package/dist/core/pipeline-types.d.ts +11 -2
- package/dist/core/pipeline-types.js.map +1 -1
- package/dist/core/pipeline-validation.d.ts +6 -0
- package/dist/core/pipeline-validation.js +41 -0
- package/dist/core/pipeline-validation.js.map +1 -0
- package/dist/run-store/run-store-ndjson.js +2 -295
- package/dist/run-store/run-store-ndjson.js.map +1 -1
- package/dist/run-store/run-store-sqlite.js +91 -42
- package/dist/run-store/run-store-sqlite.js.map +1 -1
- package/dist/run-store/run-store.d.ts +3 -2
- package/dist/run-store/run-store.js +38 -195
- package/dist/run-store/run-store.js.map +1 -1
- package/dist/studio/run-store-ui-client-controller.d.ts +9 -0
- package/dist/studio/run-store-ui-client-controller.js +479 -0
- package/dist/studio/run-store-ui-client-controller.js.map +1 -0
- package/dist/studio/run-store-ui-client-form.d.ts +9 -0
- package/dist/studio/run-store-ui-client-form.js +44 -0
- package/dist/studio/run-store-ui-client-form.js.map +1 -0
- package/dist/studio/run-store-ui-client-model.d.ts +40 -0
- package/dist/studio/run-store-ui-client-model.js +211 -0
- package/dist/studio/run-store-ui-client-model.js.map +1 -0
- package/dist/studio/run-store-ui-client-source.js +1 -1
- package/dist/studio/run-store-ui-client-transport.d.ts +25 -0
- package/dist/studio/run-store-ui-client-transport.js +322 -0
- package/dist/studio/run-store-ui-client-transport.js.map +1 -0
- package/dist/studio/run-store-ui-client-views.d.ts +24 -0
- package/dist/studio/run-store-ui-client-views.js +676 -0
- package/dist/studio/run-store-ui-client-views.js.map +1 -0
- package/dist/studio/run-store-ui-client.d.ts +2 -14
- package/dist/studio/run-store-ui-client.js +7 -1360
- package/dist/studio/run-store-ui-client.js.map +1 -1
- package/dist/studio/run-store-ui-page.d.ts +1 -1
- package/dist/studio/run-store-ui.js +44 -25
- package/dist/studio/run-store-ui.js.map +1 -1
- package/dist/tracing/tracing-codec.d.ts +6 -0
- package/dist/tracing/tracing-codec.js +829 -0
- package/dist/tracing/tracing-codec.js.map +1 -0
- package/dist/tracing/tracing-constants.d.ts +4 -0
- package/dist/tracing/tracing-constants.js +5 -0
- package/dist/tracing/tracing-constants.js.map +1 -0
- package/dist/tracing/tracing-internal.js +116 -81
- package/dist/tracing/tracing-internal.js.map +1 -1
- package/dist/tracing/tracing-otel.js +19 -3
- package/dist/tracing/tracing-otel.js.map +1 -1
- package/dist/tracing/tracing.d.ts +149 -10
- package/dist/tracing/tracing.js.map +1 -1
- package/dist/workbench/workbench-history.js +8 -1
- package/dist/workbench/workbench-history.js.map +1 -1
- package/dist/workbench/workbench-ui.js +26 -6
- package/dist/workbench/workbench-ui.js.map +1 -1
- package/docs/README.md +21 -15
- package/docs/agent-guide.md +48 -35
- package/docs/agent-skills.md +59 -0
- package/docs/api-reference.md +28 -12
- package/docs/api-report.json +29 -13
- package/docs/child-pipeline-composition.md +142 -193
- package/docs/cli.md +83 -61
- package/docs/comparison.md +63 -62
- package/docs/concepts.md +210 -272
- package/docs/getting-started.md +59 -48
- package/docs/llms.txt +7 -4
- package/docs/recipes.md +21 -18
- package/docs/remote-step-composition.md +69 -74
- package/docs/studio.md +146 -71
- package/examples/catalog/pipelines/enrich.ts +2 -2
- package/examples/catalog/tubeless.project.ts +4 -2
- package/examples/host-embedding.ts +4 -5
- package/examples/remote-steps.ts +8 -5
- package/package.json +11 -10
- package/skills/tubeless/SKILL.md +83 -0
- package/skills/tubeless-make-pipeline/SKILL.md +126 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { RUN_MODEL_VERSION } from "./pipeline-ids.js";
|
|
2
|
+
export function isCancellationOnly(errors) {
|
|
3
|
+
return errors.length > 0 && errors.every(({ kind }) => kind === "cancellation");
|
|
4
|
+
}
|
|
5
|
+
function terminalRunStatus(errors) {
|
|
6
|
+
if (errors.length === 0)
|
|
7
|
+
return "completed";
|
|
8
|
+
return isCancellationOnly(errors) ? "cancelled" : "failed";
|
|
9
|
+
}
|
|
10
|
+
function stepReportFromStatus(event) {
|
|
11
|
+
const base = {
|
|
12
|
+
id: event.id,
|
|
13
|
+
name: event.name,
|
|
14
|
+
description: event.description,
|
|
15
|
+
finishedAtMs: event.finishedAtMs,
|
|
16
|
+
};
|
|
17
|
+
switch (event.status) {
|
|
18
|
+
case "completed":
|
|
19
|
+
return {
|
|
20
|
+
...base,
|
|
21
|
+
attemptId: event.attemptId,
|
|
22
|
+
startedAtMs: event.startedAtMs,
|
|
23
|
+
status: "completed",
|
|
24
|
+
};
|
|
25
|
+
case "failed":
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
attemptId: event.attemptId,
|
|
29
|
+
error: event.error,
|
|
30
|
+
startedAtMs: event.startedAtMs,
|
|
31
|
+
status: "failed",
|
|
32
|
+
};
|
|
33
|
+
case "cancelled": {
|
|
34
|
+
const report = {
|
|
35
|
+
...base,
|
|
36
|
+
error: event.error,
|
|
37
|
+
status: "cancelled",
|
|
38
|
+
};
|
|
39
|
+
if (event.attemptId)
|
|
40
|
+
report.attemptId = event.attemptId;
|
|
41
|
+
if (event.startedAtMs !== undefined)
|
|
42
|
+
report.startedAtMs = event.startedAtMs;
|
|
43
|
+
return report;
|
|
44
|
+
}
|
|
45
|
+
case "skipped": {
|
|
46
|
+
const report = {
|
|
47
|
+
...base,
|
|
48
|
+
reason: event.reason,
|
|
49
|
+
status: "skipped",
|
|
50
|
+
};
|
|
51
|
+
if (event.attemptId)
|
|
52
|
+
report.attemptId = event.attemptId;
|
|
53
|
+
if (event.startedAtMs !== undefined)
|
|
54
|
+
report.startedAtMs = event.startedAtMs;
|
|
55
|
+
if (event.dependencyId)
|
|
56
|
+
report.dependencyId = event.dependencyId;
|
|
57
|
+
if (event.message)
|
|
58
|
+
report.message = event.message;
|
|
59
|
+
return report;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class PipelineRunState {
|
|
64
|
+
pipelineId;
|
|
65
|
+
dryRun;
|
|
66
|
+
startedAtMs;
|
|
67
|
+
identity;
|
|
68
|
+
now;
|
|
69
|
+
lifecycle;
|
|
70
|
+
#currentStepStatuses = new Map();
|
|
71
|
+
#errors = [];
|
|
72
|
+
#outputs = new Map();
|
|
73
|
+
#reports = [];
|
|
74
|
+
#reportsByStepId = new Map();
|
|
75
|
+
#finalizationAttempted = false;
|
|
76
|
+
#finalized = false;
|
|
77
|
+
#nextAttemptSequence = 0;
|
|
78
|
+
#phase = "created";
|
|
79
|
+
#value;
|
|
80
|
+
constructor(pipelineId, dryRun, startedAtMs, identity, now, lifecycle) {
|
|
81
|
+
this.pipelineId = pipelineId;
|
|
82
|
+
this.dryRun = dryRun;
|
|
83
|
+
this.startedAtMs = startedAtMs;
|
|
84
|
+
this.identity = identity;
|
|
85
|
+
this.now = now;
|
|
86
|
+
this.lifecycle = lifecycle;
|
|
87
|
+
}
|
|
88
|
+
get errors() {
|
|
89
|
+
return this.#errors;
|
|
90
|
+
}
|
|
91
|
+
get outputs() {
|
|
92
|
+
return this.#outputs;
|
|
93
|
+
}
|
|
94
|
+
get reportsByStepId() {
|
|
95
|
+
return this.#reportsByStepId;
|
|
96
|
+
}
|
|
97
|
+
start(plan, targetIds) {
|
|
98
|
+
this.#expectPhase("created");
|
|
99
|
+
this.#phase = "running";
|
|
100
|
+
this.lifecycle.pipelineStart(plan, targetIds);
|
|
101
|
+
}
|
|
102
|
+
planStep(step) {
|
|
103
|
+
this.#applyStepStatus({ pipelineId: this.pipelineId, status: "planned", step });
|
|
104
|
+
}
|
|
105
|
+
beginAttempt(step, startedAtMs = this.now()) {
|
|
106
|
+
const attempt = {
|
|
107
|
+
attemptId: `${this.identity.runId}:attempt:${(this.#nextAttemptSequence += 1).toString(36)}`,
|
|
108
|
+
startedAtMs,
|
|
109
|
+
};
|
|
110
|
+
this.#applyStepStatus({
|
|
111
|
+
attemptId: attempt.attemptId,
|
|
112
|
+
pipelineId: this.pipelineId,
|
|
113
|
+
status: "running",
|
|
114
|
+
step,
|
|
115
|
+
});
|
|
116
|
+
return attempt;
|
|
117
|
+
}
|
|
118
|
+
reportProgress(step, attempt, progress) {
|
|
119
|
+
this.#applyStepStatus({
|
|
120
|
+
attemptId: attempt.attemptId,
|
|
121
|
+
pipelineId: this.pipelineId,
|
|
122
|
+
progress,
|
|
123
|
+
status: "running",
|
|
124
|
+
step,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
completeStep(step, attempt, value) {
|
|
128
|
+
const report = {
|
|
129
|
+
attemptId: attempt.attemptId,
|
|
130
|
+
id: step.id,
|
|
131
|
+
name: step.name,
|
|
132
|
+
description: step.description,
|
|
133
|
+
finishedAtMs: this.now(),
|
|
134
|
+
startedAtMs: attempt.startedAtMs,
|
|
135
|
+
status: "completed",
|
|
136
|
+
};
|
|
137
|
+
this.#applyStepStatus({ ...report, pipelineId: this.pipelineId, step }, { output: { value } });
|
|
138
|
+
}
|
|
139
|
+
skipStep(step, input) {
|
|
140
|
+
const report = {
|
|
141
|
+
id: step.id,
|
|
142
|
+
name: step.name,
|
|
143
|
+
description: step.description,
|
|
144
|
+
finishedAtMs: this.now(),
|
|
145
|
+
reason: input.reason,
|
|
146
|
+
status: "skipped",
|
|
147
|
+
};
|
|
148
|
+
if (input.attempt) {
|
|
149
|
+
report.attemptId = input.attempt.attemptId;
|
|
150
|
+
report.startedAtMs = input.attempt.startedAtMs;
|
|
151
|
+
}
|
|
152
|
+
if (input.dependencyId)
|
|
153
|
+
report.dependencyId = input.dependencyId;
|
|
154
|
+
if (input.message)
|
|
155
|
+
report.message = input.message;
|
|
156
|
+
this.#applyStepStatus({ ...report, pipelineId: this.pipelineId, step }, input.output ? { output: input.output } : undefined);
|
|
157
|
+
}
|
|
158
|
+
failStep(step, attempt, error) {
|
|
159
|
+
if (error.kind === "cancellation") {
|
|
160
|
+
this.cancelStep(step, error, true, attempt);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const report = {
|
|
164
|
+
attemptId: attempt.attemptId,
|
|
165
|
+
id: step.id,
|
|
166
|
+
name: step.name,
|
|
167
|
+
description: step.description,
|
|
168
|
+
error,
|
|
169
|
+
finishedAtMs: this.now(),
|
|
170
|
+
startedAtMs: attempt.startedAtMs,
|
|
171
|
+
status: "failed",
|
|
172
|
+
};
|
|
173
|
+
this.#applyStepStatus({ ...report, pipelineId: this.pipelineId, step }, { error: { record: true, value: error } });
|
|
174
|
+
}
|
|
175
|
+
cancelStep(step, error, recordError, attempt) {
|
|
176
|
+
const report = {
|
|
177
|
+
id: step.id,
|
|
178
|
+
name: step.name,
|
|
179
|
+
description: step.description,
|
|
180
|
+
error,
|
|
181
|
+
finishedAtMs: this.now(),
|
|
182
|
+
status: "cancelled",
|
|
183
|
+
};
|
|
184
|
+
if (attempt) {
|
|
185
|
+
report.attemptId = attempt.attemptId;
|
|
186
|
+
report.startedAtMs = attempt.startedAtMs;
|
|
187
|
+
}
|
|
188
|
+
this.#applyStepStatus({ ...report, pipelineId: this.pipelineId, step }, { error: { record: recordError, value: error } });
|
|
189
|
+
}
|
|
190
|
+
recordRunErrors(errors) {
|
|
191
|
+
this.#expectPhase("running");
|
|
192
|
+
this.#errors.push(...errors);
|
|
193
|
+
}
|
|
194
|
+
beginFinalization() {
|
|
195
|
+
this.#expectPhase("running");
|
|
196
|
+
this.#expectAllStepsTerminal();
|
|
197
|
+
if (this.#finalizationAttempted)
|
|
198
|
+
throw new Error("Pipeline finalization already attempted");
|
|
199
|
+
this.#finalizationAttempted = true;
|
|
200
|
+
this.#phase = "finalizing";
|
|
201
|
+
this.lifecycle.finalizeStart();
|
|
202
|
+
}
|
|
203
|
+
completeFinalization(value, durationMs) {
|
|
204
|
+
this.#expectPhase("finalizing");
|
|
205
|
+
this.#value = value;
|
|
206
|
+
this.#finalized = true;
|
|
207
|
+
this.#phase = "running";
|
|
208
|
+
this.lifecycle.finalizeComplete(durationMs, value);
|
|
209
|
+
}
|
|
210
|
+
failFinalization(error, durationMs) {
|
|
211
|
+
this.#expectPhase("finalizing");
|
|
212
|
+
this.#errors.push(error);
|
|
213
|
+
this.#phase = "running";
|
|
214
|
+
this.lifecycle.finalizeError(error, durationMs);
|
|
215
|
+
}
|
|
216
|
+
async finish() {
|
|
217
|
+
this.#expectPhase("running");
|
|
218
|
+
this.#expectAllStepsTerminal();
|
|
219
|
+
this.#phase = "finished";
|
|
220
|
+
const result = {
|
|
221
|
+
pipelineId: this.pipelineId,
|
|
222
|
+
dryRun: this.dryRun,
|
|
223
|
+
errors: this.#errors,
|
|
224
|
+
finalized: this.#finalized,
|
|
225
|
+
finishedAtMs: this.now(),
|
|
226
|
+
runId: this.identity.runId,
|
|
227
|
+
startedAtMs: this.startedAtMs,
|
|
228
|
+
status: terminalRunStatus(this.#errors),
|
|
229
|
+
steps: this.#reports,
|
|
230
|
+
value: this.#value,
|
|
231
|
+
version: RUN_MODEL_VERSION,
|
|
232
|
+
};
|
|
233
|
+
if (this.identity.correlationId !== undefined) {
|
|
234
|
+
result.correlationId = this.identity.correlationId;
|
|
235
|
+
}
|
|
236
|
+
if (this.identity.parentRunId)
|
|
237
|
+
result.parentRunId = this.identity.parentRunId;
|
|
238
|
+
this.lifecycle.pipelineComplete(result);
|
|
239
|
+
await this.lifecycle.flush();
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
#applyStepStatus(event, effects = {}) {
|
|
243
|
+
this.#expectPhase("running");
|
|
244
|
+
const previous = this.#currentStepStatuses.get(event.step.id);
|
|
245
|
+
const valid = (previous === undefined && event.status === "planned") ||
|
|
246
|
+
(previous === "planned" &&
|
|
247
|
+
(event.status === "running" ||
|
|
248
|
+
event.status === "skipped" ||
|
|
249
|
+
event.status === "cancelled")) ||
|
|
250
|
+
(previous === "running" &&
|
|
251
|
+
(event.status === "running" ||
|
|
252
|
+
event.status === "completed" ||
|
|
253
|
+
event.status === "skipped" ||
|
|
254
|
+
event.status === "cancelled" ||
|
|
255
|
+
event.status === "failed"));
|
|
256
|
+
if (!valid) {
|
|
257
|
+
throw new Error(`Invalid step status transition for ${event.step.id}: ${previous ?? "unobserved"} -> ${event.status}`);
|
|
258
|
+
}
|
|
259
|
+
this.#currentStepStatuses.set(event.step.id, event.status);
|
|
260
|
+
if (event.status !== "planned" && event.status !== "running") {
|
|
261
|
+
const report = stepReportFromStatus(event);
|
|
262
|
+
this.#reports.push(report);
|
|
263
|
+
this.#reportsByStepId.set(event.step.id, report);
|
|
264
|
+
}
|
|
265
|
+
if (effects.output)
|
|
266
|
+
this.#outputs.set(event.step.id, effects.output.value);
|
|
267
|
+
if (effects.error?.record)
|
|
268
|
+
this.#errors.push(effects.error.value);
|
|
269
|
+
this.lifecycle.stepStatus(event);
|
|
270
|
+
}
|
|
271
|
+
#expectPhase(expected) {
|
|
272
|
+
if (this.#phase !== expected) {
|
|
273
|
+
throw new Error(`Invalid pipeline run transition: expected ${expected}, found ${this.#phase}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
#expectAllStepsTerminal() {
|
|
277
|
+
for (const [stepId, status] of this.#currentStepStatuses) {
|
|
278
|
+
if (status === "planned" || status === "running") {
|
|
279
|
+
throw new Error(`Cannot finish pipeline run while step ${stepId} is ${status}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=pipeline-run-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-run-state.js","sourceRoot":"","sources":["../../src/core/pipeline-run-state.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PipelineLifecycleObserver } from \"./lifecycle.js\";\nimport { RUN_MODEL_VERSION } from \"./pipeline-ids.js\";\nimport type {\n PipelineError,\n PipelinePlan,\n PipelinePlanStep,\n PipelineRun,\n PipelineRunStatus,\n PipelineStepCancelledReport,\n PipelineStepCompleteReport,\n PipelineStepFailedReport,\n PipelineStepProgress,\n PipelineStepReport,\n PipelineStepSkipReason,\n PipelineStepSkippedReport,\n PipelineStepStatus,\n} from \"./pipeline-types.js\";\n\nexport interface PipelineStepAttempt {\n attemptId: string;\n startedAtMs: number;\n}\n\ntype RunPhase = \"created\" | \"finalizing\" | \"finished\" | \"running\";\n\ntype StepTransitionEffects = {\n error?: { record: boolean; value: PipelineError };\n output?: { value: unknown };\n};\n\nexport function isCancellationOnly(errors: readonly PipelineError[]): boolean {\n return errors.length > 0 && errors.every(({ kind }) => kind === \"cancellation\");\n}\n\nfunction terminalRunStatus(errors: readonly PipelineError[]): PipelineRunStatus {\n if (errors.length === 0) return \"completed\";\n return isCancellationOnly(errors) ? \"cancelled\" : \"failed\";\n}\n\nfunction stepReportFromStatus(\n event: Exclude<PipelineStepStatus, { status: \"planned\" | \"running\" }>\n): PipelineStepReport {\n const base = {\n id: event.id,\n name: event.name,\n description: event.description,\n finishedAtMs: event.finishedAtMs,\n };\n switch (event.status) {\n case \"completed\":\n return {\n ...base,\n attemptId: event.attemptId,\n startedAtMs: event.startedAtMs,\n status: \"completed\",\n };\n case \"failed\":\n return {\n ...base,\n attemptId: event.attemptId,\n error: event.error,\n startedAtMs: event.startedAtMs,\n status: \"failed\",\n };\n case \"cancelled\": {\n const report: PipelineStepCancelledReport = {\n ...base,\n error: event.error,\n status: \"cancelled\",\n };\n if (event.attemptId) report.attemptId = event.attemptId;\n if (event.startedAtMs !== undefined) report.startedAtMs = event.startedAtMs;\n return report;\n }\n case \"skipped\": {\n const report: PipelineStepSkippedReport = {\n ...base,\n reason: event.reason,\n status: \"skipped\",\n };\n if (event.attemptId) report.attemptId = event.attemptId;\n if (event.startedAtMs !== undefined) report.startedAtMs = event.startedAtMs;\n if (event.dependencyId) report.dependencyId = event.dependencyId;\n if (event.message) report.message = event.message;\n return report;\n }\n }\n}\n\nexport class PipelineRunState<TResult> {\n readonly #currentStepStatuses = new Map<string, PipelineStepStatus[\"status\"]>();\n readonly #errors: PipelineError[] = [];\n readonly #outputs = new Map<string, unknown>();\n readonly #reports: PipelineStepReport[] = [];\n readonly #reportsByStepId = new Map<string, PipelineStepReport>();\n #finalizationAttempted = false;\n #finalized = false;\n #nextAttemptSequence = 0;\n #phase: RunPhase = \"created\";\n #value: TResult | undefined;\n\n constructor(\n readonly pipelineId: string,\n readonly dryRun: boolean,\n readonly startedAtMs: number,\n readonly identity: { correlationId?: string; parentRunId?: string; runId: string },\n readonly now: () => number,\n readonly lifecycle: PipelineLifecycleObserver\n ) {}\n\n get errors(): readonly PipelineError[] {\n return this.#errors;\n }\n\n get outputs(): ReadonlyMap<string, unknown> {\n return this.#outputs;\n }\n\n get reportsByStepId(): ReadonlyMap<string, PipelineStepReport> {\n return this.#reportsByStepId;\n }\n\n start(plan: PipelinePlan, targetIds: readonly string[]): void {\n this.#expectPhase(\"created\");\n this.#phase = \"running\";\n this.lifecycle.pipelineStart(plan, targetIds);\n }\n\n planStep(step: PipelinePlanStep): void {\n this.#applyStepStatus({ pipelineId: this.pipelineId, status: \"planned\", step });\n }\n\n beginAttempt(step: PipelinePlanStep, startedAtMs = this.now()): PipelineStepAttempt {\n const attempt = {\n attemptId: `${this.identity.runId}:attempt:${(this.#nextAttemptSequence += 1).toString(36)}`,\n startedAtMs,\n };\n this.#applyStepStatus({\n attemptId: attempt.attemptId,\n pipelineId: this.pipelineId,\n status: \"running\",\n step,\n });\n return attempt;\n }\n\n reportProgress(\n step: PipelinePlanStep,\n attempt: PipelineStepAttempt,\n progress: PipelineStepProgress\n ): void {\n this.#applyStepStatus({\n attemptId: attempt.attemptId,\n pipelineId: this.pipelineId,\n progress,\n status: \"running\",\n step,\n });\n }\n\n completeStep(step: PipelinePlanStep, attempt: PipelineStepAttempt, value: unknown): void {\n const report: PipelineStepCompleteReport = {\n attemptId: attempt.attemptId,\n id: step.id,\n name: step.name,\n description: step.description,\n finishedAtMs: this.now(),\n startedAtMs: attempt.startedAtMs,\n status: \"completed\",\n };\n this.#applyStepStatus({ ...report, pipelineId: this.pipelineId, step }, { output: { value } });\n }\n\n skipStep(\n step: PipelinePlanStep,\n input: {\n attempt?: PipelineStepAttempt;\n dependencyId?: string;\n message?: string;\n output?: { value: unknown };\n reason: PipelineStepSkipReason;\n }\n ): void {\n const report: PipelineStepSkippedReport = {\n id: step.id,\n name: step.name,\n description: step.description,\n finishedAtMs: this.now(),\n reason: input.reason,\n status: \"skipped\",\n };\n if (input.attempt) {\n report.attemptId = input.attempt.attemptId;\n report.startedAtMs = input.attempt.startedAtMs;\n }\n if (input.dependencyId) report.dependencyId = input.dependencyId;\n if (input.message) report.message = input.message;\n this.#applyStepStatus(\n { ...report, pipelineId: this.pipelineId, step },\n input.output ? { output: input.output } : undefined\n );\n }\n\n failStep(step: PipelinePlanStep, attempt: PipelineStepAttempt, error: PipelineError): void {\n if (error.kind === \"cancellation\") {\n this.cancelStep(step, error, true, attempt);\n return;\n }\n const report: PipelineStepFailedReport = {\n attemptId: attempt.attemptId,\n id: step.id,\n name: step.name,\n description: step.description,\n error,\n finishedAtMs: this.now(),\n startedAtMs: attempt.startedAtMs,\n status: \"failed\",\n };\n this.#applyStepStatus(\n { ...report, pipelineId: this.pipelineId, step },\n { error: { record: true, value: error } }\n );\n }\n\n cancelStep(\n step: PipelinePlanStep,\n error: PipelineError,\n recordError: boolean,\n attempt?: PipelineStepAttempt\n ): void {\n const report: PipelineStepCancelledReport = {\n id: step.id,\n name: step.name,\n description: step.description,\n error,\n finishedAtMs: this.now(),\n status: \"cancelled\",\n };\n if (attempt) {\n report.attemptId = attempt.attemptId;\n report.startedAtMs = attempt.startedAtMs;\n }\n this.#applyStepStatus(\n { ...report, pipelineId: this.pipelineId, step },\n { error: { record: recordError, value: error } }\n );\n }\n\n recordRunErrors(errors: readonly PipelineError[]): void {\n this.#expectPhase(\"running\");\n this.#errors.push(...errors);\n }\n\n beginFinalization(): void {\n this.#expectPhase(\"running\");\n this.#expectAllStepsTerminal();\n if (this.#finalizationAttempted) throw new Error(\"Pipeline finalization already attempted\");\n this.#finalizationAttempted = true;\n this.#phase = \"finalizing\";\n this.lifecycle.finalizeStart();\n }\n\n completeFinalization(value: TResult, durationMs: number): void {\n this.#expectPhase(\"finalizing\");\n this.#value = value;\n this.#finalized = true;\n this.#phase = \"running\";\n this.lifecycle.finalizeComplete(durationMs, value);\n }\n\n failFinalization(error: PipelineError, durationMs: number): void {\n this.#expectPhase(\"finalizing\");\n this.#errors.push(error);\n this.#phase = \"running\";\n this.lifecycle.finalizeError(error, durationMs);\n }\n\n async finish(): Promise<PipelineRun<TResult>> {\n this.#expectPhase(\"running\");\n this.#expectAllStepsTerminal();\n this.#phase = \"finished\";\n const result: PipelineRun<TResult> = {\n pipelineId: this.pipelineId,\n dryRun: this.dryRun,\n errors: this.#errors,\n finalized: this.#finalized,\n finishedAtMs: this.now(),\n runId: this.identity.runId,\n startedAtMs: this.startedAtMs,\n status: terminalRunStatus(this.#errors),\n steps: this.#reports,\n value: this.#value,\n version: RUN_MODEL_VERSION,\n };\n if (this.identity.correlationId !== undefined) {\n result.correlationId = this.identity.correlationId;\n }\n if (this.identity.parentRunId) result.parentRunId = this.identity.parentRunId;\n this.lifecycle.pipelineComplete(result);\n await this.lifecycle.flush();\n return result;\n }\n\n #applyStepStatus(event: PipelineStepStatus, effects: StepTransitionEffects = {}): void {\n this.#expectPhase(\"running\");\n const previous = this.#currentStepStatuses.get(event.step.id);\n const valid =\n (previous === undefined && event.status === \"planned\") ||\n (previous === \"planned\" &&\n (event.status === \"running\" ||\n event.status === \"skipped\" ||\n event.status === \"cancelled\")) ||\n (previous === \"running\" &&\n (event.status === \"running\" ||\n event.status === \"completed\" ||\n event.status === \"skipped\" ||\n event.status === \"cancelled\" ||\n event.status === \"failed\"));\n if (!valid) {\n throw new Error(\n `Invalid step status transition for ${event.step.id}: ${previous ?? \"unobserved\"} -> ${event.status}`\n );\n }\n\n this.#currentStepStatuses.set(event.step.id, event.status);\n if (event.status !== \"planned\" && event.status !== \"running\") {\n const report = stepReportFromStatus(event);\n this.#reports.push(report);\n this.#reportsByStepId.set(event.step.id, report);\n }\n if (effects.output) this.#outputs.set(event.step.id, effects.output.value);\n if (effects.error?.record) this.#errors.push(effects.error.value);\n this.lifecycle.stepStatus(event);\n }\n\n #expectPhase(expected: RunPhase): void {\n if (this.#phase !== expected) {\n throw new Error(\n `Invalid pipeline run transition: expected ${expected}, found ${this.#phase}`\n );\n }\n }\n\n #expectAllStepsTerminal(): void {\n for (const [stepId, status] of this.#currentStepStatuses) {\n if (status === \"planned\" || status === \"running\") {\n throw new Error(`Cannot finish pipeline run while step ${stepId} is ${status}`);\n }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AnyStep } from "./pipeline-steps.js";
|
|
2
|
+
import type { PipelineExecutionContext, PipelineLogger, PipelineStepProgress } from "./pipeline-types.js";
|
|
3
|
+
import type { PipelineTraceAttributes } from "../tracing/tracing.js";
|
|
4
|
+
export interface NormalizedStepSkipDecision {
|
|
5
|
+
reason: string;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare function evaluateStepSkip<TOptions extends object>(step: AnyStep<TOptions>, inputs: Record<string, unknown>, context: PipelineExecutionContext<TOptions>): Promise<NormalizedStepSkipDecision | null>;
|
|
9
|
+
export declare function validateStepOutput<TOptions extends object>(step: AnyStep<TOptions>, value: unknown, boundary: string): Promise<unknown>;
|
|
10
|
+
export declare function executeStepAttempt<TOptions extends object>(input: {
|
|
11
|
+
attemptId: string;
|
|
12
|
+
context: PipelineExecutionContext<TOptions>;
|
|
13
|
+
dryRun: boolean;
|
|
14
|
+
inputs: Record<string, unknown>;
|
|
15
|
+
log: PipelineLogger;
|
|
16
|
+
onProgress(progress: PipelineStepProgress): void;
|
|
17
|
+
onReportAttempt(attempt: number, attributes?: PipelineTraceAttributes): void;
|
|
18
|
+
outputBoundary: string;
|
|
19
|
+
step: AnyStep<TOptions>;
|
|
20
|
+
}): Promise<unknown>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { validateStandardSchema } from "./pipeline-validation.js";
|
|
2
|
+
function normalizeStepSkipDecision(decision) {
|
|
3
|
+
if (decision == null || decision === false)
|
|
4
|
+
return null;
|
|
5
|
+
if (typeof decision === "string") {
|
|
6
|
+
const reason = decision.trim();
|
|
7
|
+
return reason.length > 0 ? { reason } : null;
|
|
8
|
+
}
|
|
9
|
+
if (typeof decision === "object" && typeof decision.reason === "string") {
|
|
10
|
+
const reason = decision.reason.trim();
|
|
11
|
+
if (reason.length === 0)
|
|
12
|
+
return null;
|
|
13
|
+
return { reason, value: decision.value };
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
export async function evaluateStepSkip(step, inputs, context) {
|
|
18
|
+
if (typeof step.skip !== "function")
|
|
19
|
+
return null;
|
|
20
|
+
return normalizeStepSkipDecision(await step.skip(inputs, context));
|
|
21
|
+
}
|
|
22
|
+
export async function validateStepOutput(step, value, boundary) {
|
|
23
|
+
return step.outputSchema
|
|
24
|
+
? validateStandardSchema(step.outputSchema, value, boundary)
|
|
25
|
+
: Promise.resolve(value);
|
|
26
|
+
}
|
|
27
|
+
export async function executeStepAttempt(input) {
|
|
28
|
+
const { step } = input;
|
|
29
|
+
let acceptsProgress = true;
|
|
30
|
+
const stepContext = {
|
|
31
|
+
...input.context,
|
|
32
|
+
attemptId: input.attemptId,
|
|
33
|
+
log: input.log,
|
|
34
|
+
reportAttempt: input.onReportAttempt,
|
|
35
|
+
reportProgress: (progress) => {
|
|
36
|
+
if (acceptsProgress)
|
|
37
|
+
input.onProgress(progress);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
let output;
|
|
41
|
+
try {
|
|
42
|
+
output =
|
|
43
|
+
input.dryRun && typeof step.dryRun === "function"
|
|
44
|
+
? await step.dryRun(input.inputs, stepContext)
|
|
45
|
+
: await step.run(input.inputs, stepContext);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
acceptsProgress = false;
|
|
49
|
+
}
|
|
50
|
+
return validateStepOutput(step, output, input.outputBoundary);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=pipeline-step-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-step-executor.js","sourceRoot":"","sources":["../../src/core/pipeline-step-executor.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAOlE,SAAS,yBAAyB,CAAC,QAA0B;IAC3D,IAAI,QAAQ,IAAI,IAAI,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACxD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/C,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACrC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC3C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAuB,EACvB,MAA+B,EAC/B,OAA2C;IAE3C,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IACjD,OAAO,yBAAyB,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAuB,EACvB,KAAc,EACd,QAAgB;IAEhB,OAAO,IAAI,CAAC,YAAY;QACtB,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,QAAQ,CAAC;QAC5D,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAA0B,KAUjE;IACC,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACvB,IAAI,eAAe,GAAG,IAAI,CAAC;IAC3B,MAAM,WAAW,GAAG;QAClB,GAAG,KAAK,CAAC,OAAO;QAChB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,aAAa,EAAE,KAAK,CAAC,eAAe;QACpC,cAAc,EAAE,CAAC,QAA8B,EAAE,EAAE;YACjD,IAAI,eAAe;gBAAE,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAClD,CAAC;KACF,CAAC;IACF,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM;YACJ,KAAK,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU;gBAC/C,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC;gBAC9C,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAClD,CAAC;YAAS,CAAC;QACT,eAAe,GAAG,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;AAChE,CAAC","sourcesContent":["import type { AnyStep } from \"./pipeline-steps.js\";\nimport type {\n PipelineExecutionContext,\n PipelineLogger,\n PipelineStepProgress,\n StepSkipDecision,\n} from \"./pipeline-types.js\";\nimport type { PipelineTraceAttributes } from \"../tracing/tracing.js\";\nimport { validateStandardSchema } from \"./pipeline-validation.js\";\n\nexport interface NormalizedStepSkipDecision {\n reason: string;\n value?: unknown;\n}\n\nfunction normalizeStepSkipDecision(decision: StepSkipDecision): NormalizedStepSkipDecision | null {\n if (decision == null || decision === false) return null;\n if (typeof decision === \"string\") {\n const reason = decision.trim();\n return reason.length > 0 ? { reason } : null;\n }\n if (typeof decision === \"object\" && typeof decision.reason === \"string\") {\n const reason = decision.reason.trim();\n if (reason.length === 0) return null;\n return { reason, value: decision.value };\n }\n return null;\n}\n\nexport async function evaluateStepSkip<TOptions extends object>(\n step: AnyStep<TOptions>,\n inputs: Record<string, unknown>,\n context: PipelineExecutionContext<TOptions>\n): Promise<NormalizedStepSkipDecision | null> {\n if (typeof step.skip !== \"function\") return null;\n return normalizeStepSkipDecision(await step.skip(inputs, context));\n}\n\nexport async function validateStepOutput<TOptions extends object>(\n step: AnyStep<TOptions>,\n value: unknown,\n boundary: string\n): Promise<unknown> {\n return step.outputSchema\n ? validateStandardSchema(step.outputSchema, value, boundary)\n : Promise.resolve(value);\n}\n\nexport async function executeStepAttempt<TOptions extends object>(input: {\n attemptId: string;\n context: PipelineExecutionContext<TOptions>;\n dryRun: boolean;\n inputs: Record<string, unknown>;\n log: PipelineLogger;\n onProgress(progress: PipelineStepProgress): void;\n onReportAttempt(attempt: number, attributes?: PipelineTraceAttributes): void;\n outputBoundary: string;\n step: AnyStep<TOptions>;\n}): Promise<unknown> {\n const { step } = input;\n let acceptsProgress = true;\n const stepContext = {\n ...input.context,\n attemptId: input.attemptId,\n log: input.log,\n reportAttempt: input.onReportAttempt,\n reportProgress: (progress: PipelineStepProgress) => {\n if (acceptsProgress) input.onProgress(progress);\n },\n };\n let output: unknown;\n try {\n output =\n input.dryRun && typeof step.dryRun === \"function\"\n ? await step.dryRun(input.inputs, stepContext)\n : await step.run(input.inputs, stepContext);\n } finally {\n acceptsProgress = false;\n }\n return validateStepOutput(step, output, input.outputBoundary);\n}\n"]}
|
|
@@ -6,15 +6,20 @@ export interface PipelineLogger {
|
|
|
6
6
|
warn(message?: unknown, ...optionalParams: unknown[]): void;
|
|
7
7
|
}
|
|
8
8
|
export interface PipelineContext {
|
|
9
|
+
/** Optional caller-owned identifier used to correlate separate executions. */
|
|
10
|
+
correlationId?: string;
|
|
9
11
|
cwd: string;
|
|
10
12
|
/** Hook sets run in order and failures are isolated per set. */
|
|
11
13
|
hooks?: PipelineHooks | readonly PipelineHooks[];
|
|
12
14
|
log: PipelineLogger;
|
|
13
15
|
/** Timestamp source for persisted run records. Defaults to `Date.now`. */
|
|
14
16
|
now?: () => number;
|
|
15
|
-
/** Optional
|
|
17
|
+
/** Optional package-generated execution identity of this run's parent. */
|
|
16
18
|
parentRunId?: string;
|
|
17
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated Use `correlationId`. This value is treated as reusable external
|
|
21
|
+
* correlation and never as the execution's unique `runId`.
|
|
22
|
+
*/
|
|
18
23
|
runId?: string;
|
|
19
24
|
signal?: AbortSignal;
|
|
20
25
|
sleep?: (durationMs: number, signal?: AbortSignal) => Promise<void>;
|
|
@@ -44,6 +49,8 @@ export interface PipelineRuntime extends PipelineContext {
|
|
|
44
49
|
sleep: (durationMs: number, signal?: AbortSignal) => Promise<void>;
|
|
45
50
|
}
|
|
46
51
|
export interface PipelineExecutionContext<TOptions extends object> extends PipelineRuntime {
|
|
52
|
+
/** Caller-owned identifier shared by related executions, when supplied. */
|
|
53
|
+
correlationId?: string;
|
|
47
54
|
dryRun: boolean;
|
|
48
55
|
options: TOptions;
|
|
49
56
|
/** Stable identity for this execution, whether or not tracing is configured. */
|
|
@@ -226,6 +233,8 @@ export type PipelineStepReportStatus = PipelineStepReport["status"];
|
|
|
226
233
|
export type PipelineRunStatus = "cancelled" | "completed" | "failed";
|
|
227
234
|
/** Versioned public record returned for one pipeline execution. */
|
|
228
235
|
export interface PipelineRun<TResult = unknown> {
|
|
236
|
+
/** Caller-owned identifier shared by related executions, when supplied. */
|
|
237
|
+
correlationId?: string;
|
|
229
238
|
pipelineId: string;
|
|
230
239
|
dryRun: boolean;
|
|
231
240
|
errors: PipelineError[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-types.js","sourceRoot":"","sources":["../../src/core/pipeline-types.ts"],"names":[],"mappings":"AA6cA,0DAA0D;AAC1D,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC","sourcesContent":["import type { RUN_MODEL_VERSION } from \"./pipeline-ids.js\";\nimport type {\n PipelineTraceAttributes,\n PipelineTraceContext,\n PipelineTracingOptions,\n} from \"../tracing/tracing.js\";\n\nexport interface PipelineLogger {\n error(message?: unknown, ...optionalParams: unknown[]): void;\n log(message?: unknown, ...optionalParams: unknown[]): void;\n warn(message?: unknown, ...optionalParams: unknown[]): void;\n}\n\nexport interface PipelineContext {\n cwd: string;\n /** Hook sets run in order and failures are isolated per set. */\n hooks?: PipelineHooks | readonly PipelineHooks[];\n log: PipelineLogger;\n /** Timestamp source for persisted run records. Defaults to `Date.now`. */\n now?: () => number;\n /** Optional caller-owned parent run identity for nested or resumed orchestration. */\n parentRunId?: string;\n /** Optional caller-owned run identity. A unique ID is generated when omitted. */\n runId?: string;\n signal?: AbortSignal;\n sleep?: (durationMs: number, signal?: AbortSignal) => Promise<void>;\n /** Optional structured lifecycle tracing configuration for this run. */\n tracing?: PipelineTracingOptions;\n}\n\nexport interface PipelineRunControls<\n TStepId extends string = string,\n TTargetId extends string = string,\n> {\n continueOnError?: boolean;\n dryRun?: boolean;\n /**\n * Run only these steps; omitted or undefined runs every step. An empty array\n * is invalid and fails planning/execution instead of silently running nothing.\n */\n stepIds?: readonly TStepId[];\n /**\n * Run these declared pipeline targets and the upstream work required to\n * reach them. Required inputs and failure gates are included recursively;\n * optional-only inputs are not. Cannot be combined with exact `stepIds`\n * filtering.\n */\n targets?: readonly TTargetId[];\n}\n\n/** Domain input plus built-in controls. Child `mapOptions` still returns this mix. */\nexport type PipelineRunOptions<\n TOptions extends object = object,\n TStepId extends string = string,\n TTargetId extends string = string,\n> = TOptions & PipelineRunControls<TStepId, TTargetId>;\nexport interface PipelineRuntime extends PipelineContext {\n now: () => number;\n sleep: (durationMs: number, signal?: AbortSignal) => Promise<void>;\n}\n\nexport interface PipelineExecutionContext<TOptions extends object> extends PipelineRuntime {\n dryRun: boolean;\n options: TOptions;\n /** Stable identity for this execution, whether or not tracing is configured. */\n runId: string;\n /** Parent execution identity when this run was started by another run. */\n parentRunId?: string;\n /** Stable identities for this traced run; absent unless `context.tracing` is configured. */\n trace?: PipelineTraceContext;\n}\n\n/**\n * Optional indented detail row under a parent step's progress.\n * Domain-agnostic: mapped children, nested work units, per-file status, etc.\n */\nexport type PipelineStepProgressDetailStatus =\n | \"cancelled\"\n | \"completed\"\n | \"failed\"\n | \"pending\"\n | \"running\"\n | \"skipped\";\n\nexport interface PipelineStepProgressDetail {\n /** Stable identity for the row (item key, path, job id, …). */\n id: string;\n /** Optional display name; identity remains `id`. */\n name?: string;\n /** Indentation relative to direct child rows (0). Rows are in tree preorder. */\n depth?: number;\n /** Inner work counts, independent of the parent's lifecycle count. */\n completed?: number;\n total?: number;\n /** Short status text shown after the id. */\n label?: string;\n /** Controls the detail row symbol. Defaults to `running`. */\n status?: PipelineStepProgressDetailStatus;\n}\n\nexport interface PipelineStepProgress {\n /** Work completed so far. Values outside the reported total are allowed but renderers may clamp them. */\n completed: number;\n /** Total work when known. Omit for indeterminate progress. */\n total?: number;\n /** Optional short status shown next to the step. Prefer one-line summaries. */\n message?: string;\n /**\n * Optional multi-line detail rows for capable reporters (interactive TTY).\n * Keep the parent `message` as a summary; put per-item status here so high\n * concurrency does not collapse onto one truncated line.\n */\n details?: readonly PipelineStepProgressDetail[];\n}\n\nexport interface PipelineStepContext<\n TOptions extends object,\n> extends PipelineExecutionContext<TOptions> {\n /** Stable identity for this execution of the current step. */\n attemptId: string;\n /** Emit a retry/attempt trace event for this step when tracing is enabled. */\n reportAttempt(attempt: number, attributes?: PipelineTraceAttributes): void;\n /** Publish the latest progress snapshot for this step. */\n reportProgress(progress: PipelineStepProgress): void;\n}\n\nexport interface RemoteStepAdapter<TOptions extends object, TPayload, TResult> {\n /** Presentation only. The kernel never switches on this. */\n readonly engine: string;\n /** Function name, workflow type, URL, queue. Presentation only. */\n readonly target?: string;\n invoke(payload: TPayload, context: PipelineStepContext<TOptions>): Promise<TResult>;\n}\n\nexport type PipelineErrorPhase = \"definition\" | \"execution\" | \"finalization\" | \"planning\";\n\nexport type PipelineErrorKind =\n | \"cancellation\"\n | \"child\"\n | \"definition\"\n | \"finalization\"\n | \"selection\"\n | \"step\"\n | \"validation\";\n\n/** Stable package-owned codes for pipeline errors. */\nexport type PipelineErrorCode =\n | \"TUBELESS_CHILD_FAILED\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_CONTRADICTORY\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_CYCLE\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_DUPLICATE\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_SELF_REFERENCE\"\n | \"TUBELESS_DEFINITION_FINALIZER_STEP_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_PIPELINE_ID_BLANK\"\n | \"TUBELESS_DEFINITION_OPTIONS_SCHEMA_CONFLICT\"\n | \"TUBELESS_DEFINITION_STEP_ID_BLANK\"\n | \"TUBELESS_DEFINITION_STEP_ID_RESERVED\"\n | \"TUBELESS_DEFINITION_STEP_IDS_DUPLICATE\"\n | \"TUBELESS_DEFINITION_STEP_NAME_BLANK\"\n | \"TUBELESS_DEFINITION_TARGET_FINALIZER_MISMATCH\"\n | \"TUBELESS_DEFINITION_TARGET_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_TARGETS_DUPLICATE\"\n | \"TUBELESS_FINALIZATION_CANCELLED\"\n | \"TUBELESS_FINALIZATION_FAILED\"\n | \"TUBELESS_FINAL_RESULT_VALIDATION_FAILED\"\n | \"TUBELESS_OPTIONS_VALIDATION_FAILED\"\n | \"TUBELESS_PLANNING_SELECTION_CONFLICT\"\n | \"TUBELESS_PLANNING_STEP_SELECTION_DUPLICATE\"\n | \"TUBELESS_PLANNING_STEP_SELECTION_EMPTY\"\n | \"TUBELESS_PLANNING_STEP_UNKNOWN\"\n | \"TUBELESS_PLANNING_TARGET_SELECTION_DUPLICATE\"\n | \"TUBELESS_PLANNING_TARGET_SELECTION_EMPTY\"\n | \"TUBELESS_PLANNING_TARGET_UNDECLARED\"\n | \"TUBELESS_PLANNING_TARGET_UNKNOWN\"\n | \"TUBELESS_RUN_CANCELLED\"\n | \"TUBELESS_STEP_OUTPUT_VALIDATION_FAILED\"\n | \"TUBELESS_STEP_FAILED\";\n\n/** One dependency-free Standard Schema issue normalized for reports and traces. */\nexport interface PipelineValidationIssue {\n message: string;\n path?: readonly (number | string)[];\n}\n\n/** Bounded diagnostics for a failed or cancelled runtime fan-out. */\nexport interface PipelineFanOutFailure {\n /** Original input position, including when the key is truncated. */\n index: number;\n /** At most 1024 UTF-16 code units; do not rerun by key when keyTruncated is true. */\n key: string;\n keyTruncated: boolean;\n cancelled: boolean;\n error: PipelineErrorCause;\n}\n\nexport interface PipelineFanOutDiagnostics {\n /** Failed started items, in input order; at most 32 entries. */\n failures: readonly PipelineFanOutFailure[];\n /** Total failed started items, including omitted diagnostics. */\n failureCount: number;\n omittedFailureCount: number;\n /** Scheduler failure is separate and never assigned an item key. */\n schedulerError?: PipelineErrorCause;\n}\n\nexport interface PipelineError {\n /** Present for aggregated fan-out failures; absent for setup errors. */\n fanOut?: PipelineFanOutDiagnostics;\n /** Bounded, JSON-safe snapshot of the thrown error's cause chain. */\n cause?: PipelineErrorCause;\n code: PipelineErrorCode;\n kind: PipelineErrorKind;\n message: string;\n phase: PipelineErrorPhase;\n /** Present when a Standard Schema rejected a boundary value. */\n issues?: readonly PipelineValidationIssue[];\n stepId?: string;\n /** Machine-readable code copied from the original thrown error, when present. */\n sourceCode?: string;\n stack?: string;\n}\n\n/** Dependency-free subset of the Standard Schema V1 protocol. */\nexport interface StandardSchemaV1<TInput = unknown, TOutput = TInput> {\n readonly \"~standard\": StandardSchemaV1Props<TInput, TOutput>;\n}\n\nexport interface StandardSchemaV1Props<TInput = unknown, TOutput = TInput> {\n readonly types?: { readonly input: TInput; readonly output: TOutput };\n readonly validate: (\n value: unknown,\n options?: { readonly libraryOptions?: Record<string, unknown> }\n ) => StandardSchemaV1Result<TOutput> | Promise<StandardSchemaV1Result<TOutput>>;\n readonly vendor: string;\n readonly version: 1;\n}\n\nexport type StandardSchemaV1Result<TOutput> =\n | { readonly issues?: undefined; readonly value: TOutput }\n | { readonly issues: readonly StandardSchemaV1Issue[] };\n\nexport interface StandardSchemaV1Issue {\n readonly message: string;\n readonly path?: readonly (PropertyKey | { readonly key: PropertyKey })[];\n}\n\n/** Infer the accepted input type from a Standard Schema without importing its package. */\nexport type InferSchemaInput<TSchema extends StandardSchemaV1> = NonNullable<\n TSchema[\"~standard\"][\"types\"]\n>[\"input\"];\n\n/** Infer the validated output type from a Standard Schema without importing its package. */\nexport type InferSchemaOutput<TSchema extends StandardSchemaV1> = NonNullable<\n TSchema[\"~standard\"][\"types\"]\n>[\"output\"];\n\nexport interface PipelineErrorCause {\n cause?: PipelineErrorCause;\n message: string;\n name?: string;\n /** Machine-readable code copied from this cause, when present. */\n sourceCode?: string;\n}\n\ninterface PipelineStepReportBase {\n /** Present when the step started an execution attempt. */\n attemptId?: string;\n id: string;\n /** Optional human-facing display name. Stable machine identity remains `id`. */\n name?: string;\n description?: string;\n /** Wall-clock time when this terminal report was recorded. */\n finishedAtMs: number;\n /** Wall-clock time when execution began; absent when the step never started. */\n startedAtMs?: number;\n}\n\nexport interface PipelineStepCompleteReport extends PipelineStepReportBase {\n attemptId: string;\n startedAtMs: number;\n status: \"completed\";\n}\n\nexport interface PipelineStepSkippedReport extends PipelineStepReportBase {\n /** Dependency that blocked this step, when applicable. */\n dependencyId?: string;\n /** Human-readable skip detail, especially for policy and fail-fast skips. */\n message?: string;\n reason: PipelineStepSkipReason;\n status: \"skipped\";\n}\n\nexport interface PipelineStepCancelledReport extends PipelineStepReportBase {\n error: PipelineError;\n status: \"cancelled\";\n}\n\nexport interface PipelineStepFailedReport extends PipelineStepReportBase {\n attemptId: string;\n error: PipelineError;\n startedAtMs: number;\n status: \"failed\";\n}\n\n/** Terminal state recorded for one step after a run. */\nexport type PipelineStepReport =\n | PipelineStepCancelledReport\n | PipelineStepFailedReport\n | PipelineStepSkippedReport\n | PipelineStepCompleteReport;\n\nexport type PipelineStepReportStatus = PipelineStepReport[\"status\"];\n\n/** Terminal disposition of a completed run record. */\nexport type PipelineRunStatus = \"cancelled\" | \"completed\" | \"failed\";\n\n/** Versioned public record returned for one pipeline execution. */\nexport interface PipelineRun<TResult = unknown> {\n pipelineId: string;\n dryRun: boolean;\n errors: PipelineError[];\n finalized: boolean;\n finishedAtMs: number;\n parentRunId?: string;\n runId: string;\n startedAtMs: number;\n status: PipelineRunStatus;\n steps: PipelineStepReport[];\n value?: TResult;\n version: typeof RUN_MODEL_VERSION;\n}\n\n/**\n * Why a step did not run. Built-in structural reasons keep dependency semantics;\n * `\"policy\"` is an intentional skip from `step.skip()` (still unlocks dependents).\n */\nexport type PipelineStepSkipReason =\n | \"dry-run\"\n | \"failed-dependency\"\n | \"fail-fast\"\n | \"filtered\"\n | \"policy\"\n | \"unmet-dependency\";\n\n/**\n * Decision from an optional `skip` predicate.\n * - falsy / `false` → run the step\n * - non-empty string → skip with that message (`reason: \"policy\"`); the step's\n * published output is `undefined`\n * - `{ reason, value? }` → skip and optionally publish `value` for dependents\n * (`value` may be intentionally `undefined` if passed explicitly)\n *\n * Policy skips unlock required dependents. Because a bare-string skip (or a\n * `{ reason }` without `value`) publishes `undefined`, any step that declares\n * `skip` is typed so dependents see `TOut | undefined`. Prefer returning\n * `{ reason, value }` on every skip path when dependents need a real output.\n */\nexport type StepSkipDecision<TOut = unknown> =\n | false\n | null\n | undefined\n | string\n | { reason: string; value?: TOut };\n\n/** One observable status in a step's planned → running → terminal lifecycle. */\nexport type PipelineStepStatus =\n | { pipelineId: string; status: \"planned\"; step: PipelinePlanStep }\n | {\n attemptId: string;\n pipelineId: string;\n progress?: PipelineStepProgress;\n status: \"running\";\n step: PipelinePlanStep;\n }\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepCancelledReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepFailedReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepSkippedReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepCompleteReport);\n\nexport type PipelineStepLifecycleStatus = PipelineStepStatus[\"status\"];\n\nexport type PipelineStepPlannedEvent = Extract<PipelineStepStatus, { status: \"planned\" }>;\ntype PipelineStepRunningStatus = Extract<PipelineStepStatus, { status: \"running\" }>;\nexport type PipelineStepStartEvent = Omit<PipelineStepRunningStatus, \"progress\"> & {\n progress?: undefined;\n};\nexport type PipelineStepProgressEvent = Omit<PipelineStepRunningStatus, \"progress\"> & {\n progress: PipelineStepProgress;\n};\nexport type PipelineStepCancelledEvent = Extract<PipelineStepStatus, { status: \"cancelled\" }>;\nexport type PipelineStepFailedEvent = Extract<PipelineStepStatus, { status: \"failed\" }>;\nexport type PipelineStepSkippedEvent = Extract<PipelineStepStatus, { status: \"skipped\" }>;\nexport type PipelineStepCompleteEvent = Extract<PipelineStepStatus, { status: \"completed\" }>;\n\nexport interface PipelineHooks<TResult = unknown> {\n onFinalizeComplete?(event: { durationMs: number; pipelineId: string; value: TResult }): void;\n onFinalizeError?(event: { durationMs: number; error: PipelineError; pipelineId: string }): void;\n onFinalizeStart?(event: { pipelineId: string }): void;\n onPipelineComplete?(event: PipelineRun<TResult>): void;\n onPipelineStart?(event: PipelinePlan): void;\n onStepCancel?(event: PipelineStepCancelledEvent): void;\n onStepFail?(event: PipelineStepFailedEvent): void;\n onStepPlan?(event: PipelineStepPlannedEvent): void;\n onStepProgress?(event: PipelineStepProgressEvent): void;\n onStepSkip?(event: PipelineStepSkippedEvent): void;\n onStepStart?(event: PipelineStepStartEvent): void;\n onStepStatus?(event: PipelineStepStatus): void;\n onStepComplete?(event: PipelineStepCompleteEvent): void;\n}\n\n/** Machine-readable explanation of why a planned step was included or omitted. */\nexport type PipelineStepSelectionReason =\n | { kind: \"all\" }\n | { kind: \"exact\" }\n | { kind: \"target\"; targetId: string }\n | { dependentId: string; kind: \"required-dependency\"; targetId: string }\n | { dependentId: string; kind: \"failure-gate\"; targetId: string }\n | { dependentId: string; kind: \"optional-only\"; targetId: string }\n | { kind: \"outside-target-closure\" }\n | { kind: \"not-selected\" };\n\nexport interface PipelinePlanStep {\n dependencies: string[];\n description?: string;\n /** How this step behaves when the pipeline is run with `dryRun: true`. */\n dryRun: \"custom\" | \"run\" | \"skip\";\n id: string;\n /** Optional human-facing display name. Stable machine identity remains `id`. */\n name?: string;\n /** Static child structure when this opaque step executes another pipeline. */\n nestedPipeline?: {\n /** One child execution or one child execution per runtime item. */\n mode: \"single\" | \"for-each\";\n pipelineId: string;\n /** All declared child step ids; runtime selection may execute only a subset. */\n stepIds: readonly string[];\n };\n /** Static remote adapter metadata when this opaque step calls an external engine. */\n remote?: {\n engine: string;\n target?: string;\n };\n optionalDependencies: string[];\n /** True when the step's runtime policy may elect not to run it. */\n runtimeSkipPossible: boolean;\n selected: boolean;\n /** Stable reasons for selection or omission; shared prerequisites can have several. */\n selectionReasons: readonly PipelineStepSelectionReason[];\n skipAfterFailureOf: string[];\n skipReason?: PipelineStepSkipReason;\n}\n\nexport interface PipelinePlan {\n dryRun: boolean;\n errors: PipelineError[];\n ok: boolean;\n pipelineId: string;\n steps: PipelinePlanStep[];\n}\n\n/** Layout directions for generated Mermaid flowcharts. */\nexport const PIPELINE_MERMAID_DIRECTIONS = [\"BT\", \"LR\", \"RL\", \"TB\", \"TD\"] as const;\nexport type PipelineMermaidDirection = (typeof PIPELINE_MERMAID_DIRECTIONS)[number];\n\nexport interface PipelineMermaidOptions {\n /** Mermaid flowchart direction. Defaults to top-down (`TD`). */\n direction?: PipelineMermaidDirection;\n /** Append each operational description to its node label. Defaults to false. */\n includeDescriptions?: boolean;\n}\n\nexport interface Pipeline<\n TOptions extends object,\n TResult,\n TStepId extends string = string,\n TTargetId extends string = TStepId,\n> {\n readonly id: string;\n /** Stable definition-order step ids for discovery surfaces such as CLI help. */\n readonly stepIds: readonly TStepId[];\n /** Stable declared goal ids that support dependency-aware target execution. */\n readonly targetIds: readonly TTargetId[];\n plan(controls?: PipelineRunControls<TStepId, TTargetId>): PipelinePlan;\n run(\n options: TOptions,\n controls?: PipelineRunControls<TStepId, TTargetId>,\n context?: Partial<PipelineContext>\n ): Promise<PipelineRun<TResult>>;\n runOrThrow(\n options: TOptions,\n controls?: PipelineRunControls<TStepId, TTargetId>,\n context?: Partial<PipelineContext>\n ): Promise<TResult>;\n /** Generate a static Mermaid flowchart without running or planning the pipeline. */\n toMermaid(options?: PipelineMermaidOptions): string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pipeline-types.js","sourceRoot":"","sources":["../../src/core/pipeline-types.ts"],"names":[],"mappings":"AAsdA,0DAA0D;AAC1D,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC","sourcesContent":["import type { RUN_MODEL_VERSION } from \"./pipeline-ids.js\";\nimport type {\n PipelineTraceAttributes,\n PipelineTraceContext,\n PipelineTracingOptions,\n} from \"../tracing/tracing.js\";\n\nexport interface PipelineLogger {\n error(message?: unknown, ...optionalParams: unknown[]): void;\n log(message?: unknown, ...optionalParams: unknown[]): void;\n warn(message?: unknown, ...optionalParams: unknown[]): void;\n}\n\nexport interface PipelineContext {\n /** Optional caller-owned identifier used to correlate separate executions. */\n correlationId?: string;\n cwd: string;\n /** Hook sets run in order and failures are isolated per set. */\n hooks?: PipelineHooks | readonly PipelineHooks[];\n log: PipelineLogger;\n /** Timestamp source for persisted run records. Defaults to `Date.now`. */\n now?: () => number;\n /** Optional package-generated execution identity of this run's parent. */\n parentRunId?: string;\n /**\n * @deprecated Use `correlationId`. This value is treated as reusable external\n * correlation and never as the execution's unique `runId`.\n */\n runId?: string;\n signal?: AbortSignal;\n sleep?: (durationMs: number, signal?: AbortSignal) => Promise<void>;\n /** Optional structured lifecycle tracing configuration for this run. */\n tracing?: PipelineTracingOptions;\n}\n\nexport interface PipelineRunControls<\n TStepId extends string = string,\n TTargetId extends string = string,\n> {\n continueOnError?: boolean;\n dryRun?: boolean;\n /**\n * Run only these steps; omitted or undefined runs every step. An empty array\n * is invalid and fails planning/execution instead of silently running nothing.\n */\n stepIds?: readonly TStepId[];\n /**\n * Run these declared pipeline targets and the upstream work required to\n * reach them. Required inputs and failure gates are included recursively;\n * optional-only inputs are not. Cannot be combined with exact `stepIds`\n * filtering.\n */\n targets?: readonly TTargetId[];\n}\n\n/** Domain input plus built-in controls. Child `mapOptions` still returns this mix. */\nexport type PipelineRunOptions<\n TOptions extends object = object,\n TStepId extends string = string,\n TTargetId extends string = string,\n> = TOptions & PipelineRunControls<TStepId, TTargetId>;\nexport interface PipelineRuntime extends PipelineContext {\n now: () => number;\n sleep: (durationMs: number, signal?: AbortSignal) => Promise<void>;\n}\n\nexport interface PipelineExecutionContext<TOptions extends object> extends PipelineRuntime {\n /** Caller-owned identifier shared by related executions, when supplied. */\n correlationId?: string;\n dryRun: boolean;\n options: TOptions;\n /** Stable identity for this execution, whether or not tracing is configured. */\n runId: string;\n /** Parent execution identity when this run was started by another run. */\n parentRunId?: string;\n /** Stable identities for this traced run; absent unless `context.tracing` is configured. */\n trace?: PipelineTraceContext;\n}\n\n/**\n * Optional indented detail row under a parent step's progress.\n * Domain-agnostic: mapped children, nested work units, per-file status, etc.\n */\nexport type PipelineStepProgressDetailStatus =\n | \"cancelled\"\n | \"completed\"\n | \"failed\"\n | \"pending\"\n | \"running\"\n | \"skipped\";\n\nexport interface PipelineStepProgressDetail {\n /** Stable identity for the row (item key, path, job id, …). */\n id: string;\n /** Optional display name; identity remains `id`. */\n name?: string;\n /** Indentation relative to direct child rows (0). Rows are in tree preorder. */\n depth?: number;\n /** Inner work counts, independent of the parent's lifecycle count. */\n completed?: number;\n total?: number;\n /** Short status text shown after the id. */\n label?: string;\n /** Controls the detail row symbol. Defaults to `running`. */\n status?: PipelineStepProgressDetailStatus;\n}\n\nexport interface PipelineStepProgress {\n /** Work completed so far. Values outside the reported total are allowed but renderers may clamp them. */\n completed: number;\n /** Total work when known. Omit for indeterminate progress. */\n total?: number;\n /** Optional short status shown next to the step. Prefer one-line summaries. */\n message?: string;\n /**\n * Optional multi-line detail rows for capable reporters (interactive TTY).\n * Keep the parent `message` as a summary; put per-item status here so high\n * concurrency does not collapse onto one truncated line.\n */\n details?: readonly PipelineStepProgressDetail[];\n}\n\nexport interface PipelineStepContext<\n TOptions extends object,\n> extends PipelineExecutionContext<TOptions> {\n /** Stable identity for this execution of the current step. */\n attemptId: string;\n /** Emit a retry/attempt trace event for this step when tracing is enabled. */\n reportAttempt(attempt: number, attributes?: PipelineTraceAttributes): void;\n /** Publish the latest progress snapshot for this step. */\n reportProgress(progress: PipelineStepProgress): void;\n}\n\nexport interface RemoteStepAdapter<TOptions extends object, TPayload, TResult> {\n /** Presentation only. The kernel never switches on this. */\n readonly engine: string;\n /** Function name, workflow type, URL, queue. Presentation only. */\n readonly target?: string;\n invoke(payload: TPayload, context: PipelineStepContext<TOptions>): Promise<TResult>;\n}\n\nexport type PipelineErrorPhase = \"definition\" | \"execution\" | \"finalization\" | \"planning\";\n\nexport type PipelineErrorKind =\n | \"cancellation\"\n | \"child\"\n | \"definition\"\n | \"finalization\"\n | \"selection\"\n | \"step\"\n | \"validation\";\n\n/** Stable package-owned codes for pipeline errors. */\nexport type PipelineErrorCode =\n | \"TUBELESS_CHILD_FAILED\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_CONTRADICTORY\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_CYCLE\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_DUPLICATE\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_SELF_REFERENCE\"\n | \"TUBELESS_DEFINITION_FINALIZER_STEP_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_PIPELINE_ID_BLANK\"\n | \"TUBELESS_DEFINITION_OPTIONS_SCHEMA_CONFLICT\"\n | \"TUBELESS_DEFINITION_STEP_ID_BLANK\"\n | \"TUBELESS_DEFINITION_STEP_ID_RESERVED\"\n | \"TUBELESS_DEFINITION_STEP_IDS_DUPLICATE\"\n | \"TUBELESS_DEFINITION_STEP_NAME_BLANK\"\n | \"TUBELESS_DEFINITION_TARGET_FINALIZER_MISMATCH\"\n | \"TUBELESS_DEFINITION_TARGET_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_TARGETS_DUPLICATE\"\n | \"TUBELESS_FINALIZATION_CANCELLED\"\n | \"TUBELESS_FINALIZATION_FAILED\"\n | \"TUBELESS_FINAL_RESULT_VALIDATION_FAILED\"\n | \"TUBELESS_OPTIONS_VALIDATION_FAILED\"\n | \"TUBELESS_PLANNING_SELECTION_CONFLICT\"\n | \"TUBELESS_PLANNING_STEP_SELECTION_DUPLICATE\"\n | \"TUBELESS_PLANNING_STEP_SELECTION_EMPTY\"\n | \"TUBELESS_PLANNING_STEP_UNKNOWN\"\n | \"TUBELESS_PLANNING_TARGET_SELECTION_DUPLICATE\"\n | \"TUBELESS_PLANNING_TARGET_SELECTION_EMPTY\"\n | \"TUBELESS_PLANNING_TARGET_UNDECLARED\"\n | \"TUBELESS_PLANNING_TARGET_UNKNOWN\"\n | \"TUBELESS_RUN_CANCELLED\"\n | \"TUBELESS_STEP_OUTPUT_VALIDATION_FAILED\"\n | \"TUBELESS_STEP_FAILED\";\n\n/** One dependency-free Standard Schema issue normalized for reports and traces. */\nexport interface PipelineValidationIssue {\n message: string;\n path?: readonly (number | string)[];\n}\n\n/** Bounded diagnostics for a failed or cancelled runtime fan-out. */\nexport interface PipelineFanOutFailure {\n /** Original input position, including when the key is truncated. */\n index: number;\n /** At most 1024 UTF-16 code units; do not rerun by key when keyTruncated is true. */\n key: string;\n keyTruncated: boolean;\n cancelled: boolean;\n error: PipelineErrorCause;\n}\n\nexport interface PipelineFanOutDiagnostics {\n /** Failed started items, in input order; at most 32 entries. */\n failures: readonly PipelineFanOutFailure[];\n /** Total failed started items, including omitted diagnostics. */\n failureCount: number;\n omittedFailureCount: number;\n /** Scheduler failure is separate and never assigned an item key. */\n schedulerError?: PipelineErrorCause;\n}\n\nexport interface PipelineError {\n /** Present for aggregated fan-out failures; absent for setup errors. */\n fanOut?: PipelineFanOutDiagnostics;\n /** Bounded, JSON-safe snapshot of the thrown error's cause chain. */\n cause?: PipelineErrorCause;\n code: PipelineErrorCode;\n kind: PipelineErrorKind;\n message: string;\n phase: PipelineErrorPhase;\n /** Present when a Standard Schema rejected a boundary value. */\n issues?: readonly PipelineValidationIssue[];\n stepId?: string;\n /** Machine-readable code copied from the original thrown error, when present. */\n sourceCode?: string;\n stack?: string;\n}\n\n/** Dependency-free subset of the Standard Schema V1 protocol. */\nexport interface StandardSchemaV1<TInput = unknown, TOutput = TInput> {\n readonly \"~standard\": StandardSchemaV1Props<TInput, TOutput>;\n}\n\nexport interface StandardSchemaV1Props<TInput = unknown, TOutput = TInput> {\n readonly types?: { readonly input: TInput; readonly output: TOutput };\n readonly validate: (\n value: unknown,\n options?: { readonly libraryOptions?: Record<string, unknown> }\n ) => StandardSchemaV1Result<TOutput> | Promise<StandardSchemaV1Result<TOutput>>;\n readonly vendor: string;\n readonly version: 1;\n}\n\nexport type StandardSchemaV1Result<TOutput> =\n | { readonly issues?: undefined; readonly value: TOutput }\n | { readonly issues: readonly StandardSchemaV1Issue[] };\n\nexport interface StandardSchemaV1Issue {\n readonly message: string;\n readonly path?: readonly (PropertyKey | { readonly key: PropertyKey })[];\n}\n\n/** Infer the accepted input type from a Standard Schema without importing its package. */\nexport type InferSchemaInput<TSchema extends StandardSchemaV1> = NonNullable<\n TSchema[\"~standard\"][\"types\"]\n>[\"input\"];\n\n/** Infer the validated output type from a Standard Schema without importing its package. */\nexport type InferSchemaOutput<TSchema extends StandardSchemaV1> = NonNullable<\n TSchema[\"~standard\"][\"types\"]\n>[\"output\"];\n\nexport interface PipelineErrorCause {\n cause?: PipelineErrorCause;\n message: string;\n name?: string;\n /** Machine-readable code copied from this cause, when present. */\n sourceCode?: string;\n}\n\ninterface PipelineStepReportBase {\n /** Present when the step started an execution attempt. */\n attemptId?: string;\n id: string;\n /** Optional human-facing display name. Stable machine identity remains `id`. */\n name?: string;\n description?: string;\n /** Wall-clock time when this terminal report was recorded. */\n finishedAtMs: number;\n /** Wall-clock time when execution began; absent when the step never started. */\n startedAtMs?: number;\n}\n\nexport interface PipelineStepCompleteReport extends PipelineStepReportBase {\n attemptId: string;\n startedAtMs: number;\n status: \"completed\";\n}\n\nexport interface PipelineStepSkippedReport extends PipelineStepReportBase {\n /** Dependency that blocked this step, when applicable. */\n dependencyId?: string;\n /** Human-readable skip detail, especially for policy and fail-fast skips. */\n message?: string;\n reason: PipelineStepSkipReason;\n status: \"skipped\";\n}\n\nexport interface PipelineStepCancelledReport extends PipelineStepReportBase {\n error: PipelineError;\n status: \"cancelled\";\n}\n\nexport interface PipelineStepFailedReport extends PipelineStepReportBase {\n attemptId: string;\n error: PipelineError;\n startedAtMs: number;\n status: \"failed\";\n}\n\n/** Terminal state recorded for one step after a run. */\nexport type PipelineStepReport =\n | PipelineStepCancelledReport\n | PipelineStepFailedReport\n | PipelineStepSkippedReport\n | PipelineStepCompleteReport;\n\nexport type PipelineStepReportStatus = PipelineStepReport[\"status\"];\n\n/** Terminal disposition of a completed run record. */\nexport type PipelineRunStatus = \"cancelled\" | \"completed\" | \"failed\";\n\n/** Versioned public record returned for one pipeline execution. */\nexport interface PipelineRun<TResult = unknown> {\n /** Caller-owned identifier shared by related executions, when supplied. */\n correlationId?: string;\n pipelineId: string;\n dryRun: boolean;\n errors: PipelineError[];\n finalized: boolean;\n finishedAtMs: number;\n parentRunId?: string;\n runId: string;\n startedAtMs: number;\n status: PipelineRunStatus;\n steps: PipelineStepReport[];\n value?: TResult;\n version: typeof RUN_MODEL_VERSION;\n}\n\n/**\n * Why a step did not run. Built-in structural reasons keep dependency semantics;\n * `\"policy\"` is an intentional skip from `step.skip()` (still unlocks dependents).\n */\nexport type PipelineStepSkipReason =\n | \"dry-run\"\n | \"failed-dependency\"\n | \"fail-fast\"\n | \"filtered\"\n | \"policy\"\n | \"unmet-dependency\";\n\n/**\n * Decision from an optional `skip` predicate.\n * - falsy / `false` → run the step\n * - non-empty string → skip with that message (`reason: \"policy\"`); the step's\n * published output is `undefined`\n * - `{ reason, value? }` → skip and optionally publish `value` for dependents\n * (`value` may be intentionally `undefined` if passed explicitly)\n *\n * Policy skips unlock required dependents. Because a bare-string skip (or a\n * `{ reason }` without `value`) publishes `undefined`, any step that declares\n * `skip` is typed so dependents see `TOut | undefined`. Prefer returning\n * `{ reason, value }` on every skip path when dependents need a real output.\n */\nexport type StepSkipDecision<TOut = unknown> =\n | false\n | null\n | undefined\n | string\n | { reason: string; value?: TOut };\n\n/** One observable status in a step's planned → running → terminal lifecycle. */\nexport type PipelineStepStatus =\n | { pipelineId: string; status: \"planned\"; step: PipelinePlanStep }\n | {\n attemptId: string;\n pipelineId: string;\n progress?: PipelineStepProgress;\n status: \"running\";\n step: PipelinePlanStep;\n }\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepCancelledReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepFailedReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepSkippedReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepCompleteReport);\n\nexport type PipelineStepLifecycleStatus = PipelineStepStatus[\"status\"];\n\nexport type PipelineStepPlannedEvent = Extract<PipelineStepStatus, { status: \"planned\" }>;\ntype PipelineStepRunningStatus = Extract<PipelineStepStatus, { status: \"running\" }>;\nexport type PipelineStepStartEvent = Omit<PipelineStepRunningStatus, \"progress\"> & {\n progress?: undefined;\n};\nexport type PipelineStepProgressEvent = Omit<PipelineStepRunningStatus, \"progress\"> & {\n progress: PipelineStepProgress;\n};\nexport type PipelineStepCancelledEvent = Extract<PipelineStepStatus, { status: \"cancelled\" }>;\nexport type PipelineStepFailedEvent = Extract<PipelineStepStatus, { status: \"failed\" }>;\nexport type PipelineStepSkippedEvent = Extract<PipelineStepStatus, { status: \"skipped\" }>;\nexport type PipelineStepCompleteEvent = Extract<PipelineStepStatus, { status: \"completed\" }>;\n\nexport interface PipelineHooks<TResult = unknown> {\n onFinalizeComplete?(event: { durationMs: number; pipelineId: string; value: TResult }): void;\n onFinalizeError?(event: { durationMs: number; error: PipelineError; pipelineId: string }): void;\n onFinalizeStart?(event: { pipelineId: string }): void;\n onPipelineComplete?(event: PipelineRun<TResult>): void;\n onPipelineStart?(event: PipelinePlan): void;\n onStepCancel?(event: PipelineStepCancelledEvent): void;\n onStepFail?(event: PipelineStepFailedEvent): void;\n onStepPlan?(event: PipelineStepPlannedEvent): void;\n onStepProgress?(event: PipelineStepProgressEvent): void;\n onStepSkip?(event: PipelineStepSkippedEvent): void;\n onStepStart?(event: PipelineStepStartEvent): void;\n onStepStatus?(event: PipelineStepStatus): void;\n onStepComplete?(event: PipelineStepCompleteEvent): void;\n}\n\n/** Machine-readable explanation of why a planned step was included or omitted. */\nexport type PipelineStepSelectionReason =\n | { kind: \"all\" }\n | { kind: \"exact\" }\n | { kind: \"target\"; targetId: string }\n | { dependentId: string; kind: \"required-dependency\"; targetId: string }\n | { dependentId: string; kind: \"failure-gate\"; targetId: string }\n | { dependentId: string; kind: \"optional-only\"; targetId: string }\n | { kind: \"outside-target-closure\" }\n | { kind: \"not-selected\" };\n\nexport interface PipelinePlanStep {\n dependencies: string[];\n description?: string;\n /** How this step behaves when the pipeline is run with `dryRun: true`. */\n dryRun: \"custom\" | \"run\" | \"skip\";\n id: string;\n /** Optional human-facing display name. Stable machine identity remains `id`. */\n name?: string;\n /** Static child structure when this opaque step executes another pipeline. */\n nestedPipeline?: {\n /** One child execution or one child execution per runtime item. */\n mode: \"single\" | \"for-each\";\n pipelineId: string;\n /** All declared child step ids; runtime selection may execute only a subset. */\n stepIds: readonly string[];\n };\n /** Static remote adapter metadata when this opaque step calls an external engine. */\n remote?: {\n engine: string;\n target?: string;\n };\n optionalDependencies: string[];\n /** True when the step's runtime policy may elect not to run it. */\n runtimeSkipPossible: boolean;\n selected: boolean;\n /** Stable reasons for selection or omission; shared prerequisites can have several. */\n selectionReasons: readonly PipelineStepSelectionReason[];\n skipAfterFailureOf: string[];\n skipReason?: PipelineStepSkipReason;\n}\n\nexport interface PipelinePlan {\n dryRun: boolean;\n errors: PipelineError[];\n ok: boolean;\n pipelineId: string;\n steps: PipelinePlanStep[];\n}\n\n/** Layout directions for generated Mermaid flowcharts. */\nexport const PIPELINE_MERMAID_DIRECTIONS = [\"BT\", \"LR\", \"RL\", \"TB\", \"TD\"] as const;\nexport type PipelineMermaidDirection = (typeof PIPELINE_MERMAID_DIRECTIONS)[number];\n\nexport interface PipelineMermaidOptions {\n /** Mermaid flowchart direction. Defaults to top-down (`TD`). */\n direction?: PipelineMermaidDirection;\n /** Append each operational description to its node label. Defaults to false. */\n includeDescriptions?: boolean;\n}\n\nexport interface Pipeline<\n TOptions extends object,\n TResult,\n TStepId extends string = string,\n TTargetId extends string = TStepId,\n> {\n readonly id: string;\n /** Stable definition-order step ids for discovery surfaces such as CLI help. */\n readonly stepIds: readonly TStepId[];\n /** Stable declared goal ids that support dependency-aware target execution. */\n readonly targetIds: readonly TTargetId[];\n plan(controls?: PipelineRunControls<TStepId, TTargetId>): PipelinePlan;\n run(\n options: TOptions,\n controls?: PipelineRunControls<TStepId, TTargetId>,\n context?: Partial<PipelineContext>\n ): Promise<PipelineRun<TResult>>;\n runOrThrow(\n options: TOptions,\n controls?: PipelineRunControls<TStepId, TTargetId>,\n context?: Partial<PipelineContext>\n ): Promise<TResult>;\n /** Generate a static Mermaid flowchart without running or planning the pipeline. */\n toMermaid(options?: PipelineMermaidOptions): string;\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { InferSchemaOutput, PipelineValidationIssue, StandardSchemaV1 } from "./pipeline-types.js";
|
|
2
|
+
export declare class PipelineBoundaryValidationError extends Error {
|
|
3
|
+
readonly issues: readonly PipelineValidationIssue[];
|
|
4
|
+
constructor(message: string, issues: readonly PipelineValidationIssue[], cause?: unknown);
|
|
5
|
+
}
|
|
6
|
+
export declare function validateStandardSchema<TSchema extends StandardSchemaV1>(schema: TSchema, value: unknown, boundary: string): Promise<InferSchemaOutput<TSchema>>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class PipelineBoundaryValidationError extends Error {
|
|
2
|
+
issues;
|
|
3
|
+
constructor(message, issues, cause) {
|
|
4
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
5
|
+
this.issues = issues;
|
|
6
|
+
this.name = "PipelineBoundaryValidationError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function normalizeValidationPath(path) {
|
|
10
|
+
if (!path || path.length === 0)
|
|
11
|
+
return undefined;
|
|
12
|
+
return path.map((segment) => {
|
|
13
|
+
const key = typeof segment === "object" && segment !== null ? segment.key : segment;
|
|
14
|
+
return typeof key === "number" || typeof key === "string" ? key : String(key);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function renderValidationIssue(issue) {
|
|
18
|
+
const location = issue.path?.map(String).join(".");
|
|
19
|
+
return location ? `${location}: ${issue.message}` : issue.message;
|
|
20
|
+
}
|
|
21
|
+
export async function validateStandardSchema(schema, value, boundary) {
|
|
22
|
+
let result;
|
|
23
|
+
try {
|
|
24
|
+
result = await schema["~standard"].validate(value);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new PipelineBoundaryValidationError(`${boundary} schema (${schema["~standard"].vendor}) threw while validating`, [], error);
|
|
28
|
+
}
|
|
29
|
+
if (result.issues) {
|
|
30
|
+
const issues = result.issues.map((issue) => {
|
|
31
|
+
const path = normalizeValidationPath(issue.path);
|
|
32
|
+
const normalized = { message: issue.message };
|
|
33
|
+
if (path)
|
|
34
|
+
normalized.path = path;
|
|
35
|
+
return normalized;
|
|
36
|
+
});
|
|
37
|
+
throw new PipelineBoundaryValidationError(`${boundary} validation failed: ${issues.map(renderValidationIssue).join("; ")}`, issues);
|
|
38
|
+
}
|
|
39
|
+
return result.value;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=pipeline-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-validation.js","sourceRoot":"","sources":["../../src/core/pipeline-validation.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,+BAAgC,SAAQ,KAAK;IAG7C,MAAM;IAFjB,YACE,OAAe,EACN,MAA0C,EACnD,KAAe;QAEf,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;sBAHnD,MAAM;QAIf,IAAI,CAAC,IAAI,GAAG,iCAAiC,CAAC;IAChD,CAAC;CACF;AAED,SAAS,uBAAuB,CAC9B,IAAmC;IAEnC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACjD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC1B,MAAM,GAAG,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;QACpF,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,qBAAqB,CAAC,KAA8B;IAC3D,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,OAAO,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAe,EACf,KAAc,EACd,QAAgB;IAEhB,IAAI,MAA0D,CAAC;IAC/D,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,+BAA+B,CACvC,GAAG,QAAQ,YAAY,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,0BAA0B,EAC3E,EAAE,EACF,KAAK,CACN,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACzC,MAAM,IAAI,GAAG,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjD,MAAM,UAAU,GAA4B,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YACvE,IAAI,IAAI;gBAAE,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC;YACjC,OAAO,UAAU,CAAC;QACpB,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,+BAA+B,CACvC,GAAG,QAAQ,uBAAuB,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAChF,MAAM,CACP,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,CAAC","sourcesContent":["import type {\n InferSchemaOutput,\n PipelineValidationIssue,\n StandardSchemaV1,\n StandardSchemaV1Issue,\n StandardSchemaV1Result,\n} from \"./pipeline-types.js\";\n\nexport class PipelineBoundaryValidationError extends Error {\n constructor(\n message: string,\n readonly issues: readonly PipelineValidationIssue[],\n cause?: unknown\n ) {\n super(message, cause === undefined ? undefined : { cause });\n this.name = \"PipelineBoundaryValidationError\";\n }\n}\n\nfunction normalizeValidationPath(\n path: StandardSchemaV1Issue[\"path\"]\n): readonly (number | string)[] | undefined {\n if (!path || path.length === 0) return undefined;\n return path.map((segment) => {\n const key = typeof segment === \"object\" && segment !== null ? segment.key : segment;\n return typeof key === \"number\" || typeof key === \"string\" ? key : String(key);\n });\n}\n\nfunction renderValidationIssue(issue: PipelineValidationIssue): string {\n const location = issue.path?.map(String).join(\".\");\n return location ? `${location}: ${issue.message}` : issue.message;\n}\n\nexport async function validateStandardSchema<TSchema extends StandardSchemaV1>(\n schema: TSchema,\n value: unknown,\n boundary: string\n): Promise<InferSchemaOutput<TSchema>> {\n let result: StandardSchemaV1Result<InferSchemaOutput<TSchema>>;\n try {\n result = await schema[\"~standard\"].validate(value);\n } catch (error) {\n throw new PipelineBoundaryValidationError(\n `${boundary} schema (${schema[\"~standard\"].vendor}) threw while validating`,\n [],\n error\n );\n }\n if (result.issues) {\n const issues = result.issues.map((issue) => {\n const path = normalizeValidationPath(issue.path);\n const normalized: PipelineValidationIssue = { message: issue.message };\n if (path) normalized.path = path;\n return normalized;\n });\n throw new PipelineBoundaryValidationError(\n `${boundary} validation failed: ${issues.map(renderValidationIssue).join(\"; \")}`,\n issues\n );\n }\n return result.value;\n}\n"]}
|