stageflow 0.15.0 → 0.18.0

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 (114) hide show
  1. package/README.md +2 -4
  2. package/dist/agent/activity.d.ts +3 -0
  3. package/dist/agent/claudeAdapter.js +84 -7
  4. package/dist/agent/claudeSession.d.ts +3 -0
  5. package/dist/agent/claudeTools.d.ts +1 -4
  6. package/dist/agent/claudeTools.js +1 -6
  7. package/dist/agent/fakeAgent.js +4 -5
  8. package/dist/agent/piAdapter.d.ts +2 -0
  9. package/dist/agent/piAdapter.js +30 -19
  10. package/dist/agent/port.d.ts +3 -0
  11. package/dist/cli/runOutput.js +32 -8
  12. package/dist/config/cloneChain.d.ts +12 -0
  13. package/dist/config/cloneChain.js +322 -0
  14. package/dist/config/createPipeline.js +101 -21
  15. package/dist/config/createStage.js +7 -0
  16. package/dist/config/loadOutcome.d.ts +1 -0
  17. package/dist/config/loadPipeline.d.ts +2 -0
  18. package/dist/config/loadPipeline.js +34 -22
  19. package/dist/config/loadStage.d.ts +2 -0
  20. package/dist/config/loadStage.js +135 -125
  21. package/dist/config/migrateYaml.js +14 -3
  22. package/dist/config/normalizePipelineStageEntry.d.ts +5 -9
  23. package/dist/config/normalizePipelineStageEntry.js +133 -60
  24. package/dist/config/parsePreEmitChecks.js +2 -2
  25. package/dist/config/pipelineNeeds.d.ts +15 -2
  26. package/dist/config/pipelineNeeds.js +86 -4
  27. package/dist/config/pipelineRoute.d.ts +46 -0
  28. package/dist/config/pipelineRoute.js +215 -0
  29. package/dist/config/pipelineStageKeys.d.ts +2 -0
  30. package/dist/config/pipelineStageKeys.js +15 -4
  31. package/dist/config/printTargetYaml.js +18 -12
  32. package/dist/config/resolveForkEmitContext.d.ts +1 -1
  33. package/dist/config/resolveForkEmitContext.js +3 -26
  34. package/dist/config/resolvePipelineDag.d.ts +1 -3
  35. package/dist/config/resolvePipelineDag.js +143 -186
  36. package/dist/config/routeIf.d.ts +24 -0
  37. package/dist/config/routeIf.js +247 -0
  38. package/dist/config/validateCatalog.d.ts +1 -1
  39. package/dist/config/validateCatalog.js +5 -3
  40. package/dist/config/yamlDialect.d.ts +5 -8
  41. package/dist/config/yamlDialect.js +37 -29
  42. package/dist/envelope/check.d.ts +0 -2
  43. package/dist/envelope/check.js +1 -57
  44. package/dist/envelope/feedbackLoop.js +0 -3
  45. package/dist/envelope/forkChoice.js +1 -1
  46. package/dist/envelope/payloadSchema.d.ts +14 -0
  47. package/dist/envelope/payloadSchema.js +87 -2
  48. package/dist/mcp/catalogTools.js +1 -2
  49. package/dist/package-meta.d.ts +1 -1
  50. package/dist/package-meta.js +1 -1
  51. package/dist/projection/projectRun.d.ts +0 -2
  52. package/dist/projection/projectRun.js +0 -3
  53. package/dist/prompt/priorEnvelope.js +1 -1
  54. package/dist/runstore/port.d.ts +10 -0
  55. package/dist/runstore/runProjection.js +9 -0
  56. package/dist/runstore/sqlite/SqliteRunStore.js +27 -4
  57. package/dist/runstore/sqlite/schema.d.ts +1 -1
  58. package/dist/runstore/sqlite/schema.js +2 -0
  59. package/dist/runstore/stageExecution.d.ts +3 -0
  60. package/dist/runstore/stageExecution.js +6 -2
  61. package/dist/runstore/stageInstanceId.d.ts +1 -0
  62. package/dist/runstore/stageInstanceId.js +9 -0
  63. package/dist/runstore/stageSnapshot.js +11 -0
  64. package/dist/runtime/cloneSchedule.d.ts +16 -33
  65. package/dist/runtime/cloneSchedule.js +155 -236
  66. package/dist/runtime/envelopeRouting.js +43 -94
  67. package/dist/runtime/forkGeneration.d.ts +2 -2
  68. package/dist/runtime/forkGeneration.js +29 -15
  69. package/dist/runtime/joinReadiness.d.ts +16 -0
  70. package/dist/runtime/joinReadiness.js +156 -0
  71. package/dist/runtime/pipelineScheduler.d.ts +0 -3
  72. package/dist/runtime/pipelineScheduler.js +81 -65
  73. package/dist/runtime/routeIfEval.d.ts +3 -0
  74. package/dist/runtime/routeIfEval.js +94 -0
  75. package/dist/runtime/runRetryCoordinator.js +0 -4
  76. package/dist/runtime/stageAttemptBootstrap.js +60 -11
  77. package/dist/runtime/stageRunner.js +2 -2
  78. package/dist/runtime/taskInput.js +1 -13
  79. package/dist/tools/emitStageEnvelope.d.ts +2 -3
  80. package/dist/tools/emitStageEnvelope.js +1 -65
  81. package/dist/types/envelope.d.ts +0 -2
  82. package/dist/types/forkChoice.d.ts +0 -16
  83. package/dist/types/pipeline.d.ts +86 -27
  84. package/dist/types/stage.d.ts +11 -7
  85. package/dist/types/usage.d.ts +16 -0
  86. package/dist/types/usage.js +24 -0
  87. package/dist/ui/assets/{index-uVHiae8s.css → index-DsgVjLUS.css} +1 -1
  88. package/dist/ui/assets/index-DwKGaQvQ.js +119 -0
  89. package/dist/ui/index.html +2 -2
  90. package/package.json +2 -1
  91. package/skills/stageflow-author/SKILL.md +4 -4
  92. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -2
  93. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -4
  94. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +6 -4
  95. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +9 -1
  96. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +3 -0
  97. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +8 -0
  98. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +5 -2
  99. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +8 -0
  100. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +3 -0
  101. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +5 -2
  102. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +8 -0
  103. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +8 -0
  104. package/skills/stageflow-author/references/catalog-mapping.md +159 -50
  105. package/skills/stageflow-author/references/catalog-write-conventions.md +3 -3
  106. package/skills/stageflow-author/references/stage-prompt-template.md +57 -17
  107. package/skills/stageflow-session-capture/SKILL.md +2 -2
  108. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +3 -1
  109. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +15 -0
  110. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +3 -0
  111. package/skills/stageflow-session-capture/references/catalog-authoring.md +4 -2
  112. package/dist/envelope/cloneForks.d.ts +0 -2
  113. package/dist/envelope/cloneForks.js +0 -77
  114. package/dist/ui/assets/index-GtyiWiZg.js +0 -118
@@ -179,10 +179,21 @@ function compileNode(node, path, ctx) {
179
179
  schema.minItems < 0)) {
180
180
  throw new Error(`${path}: minItems must be a non-negative integer when present`);
181
181
  }
182
+ if (schema.maxItems !== undefined &&
183
+ (typeof schema.maxItems !== "number" ||
184
+ !Number.isInteger(schema.maxItems) ||
185
+ schema.maxItems < 0)) {
186
+ throw new Error(`${path}: maxItems must be a non-negative integer when present`);
187
+ }
182
188
  const items = compileNode(schema.items, `${path}.items`, childCtx);
189
+ const arrayOpts = {};
190
+ if (schema.minItems !== undefined)
191
+ arrayOpts.minItems = schema.minItems;
192
+ if (schema.maxItems !== undefined)
193
+ arrayOpts.maxItems = schema.maxItems;
183
194
  base =
184
- schema.minItems !== undefined
185
- ? Type.Array(items, { minItems: schema.minItems })
195
+ Object.keys(arrayOpts).length > 0
196
+ ? Type.Array(items, arrayOpts)
186
197
  : Type.Array(items);
187
198
  break;
188
199
  }
@@ -420,6 +431,61 @@ export function expandPayloadSchemaRefs(raw, options) {
420
431
  stack: [],
421
432
  });
422
433
  }
434
+ const INDEX_SEGMENT = /^\d+$/;
435
+ function schemaNodeType(schema) {
436
+ return typeof schema?.type === "string" ? schema.type : undefined;
437
+ }
438
+ function tryExpandSchema(node, options) {
439
+ try {
440
+ return expandPayloadSchemaRefs(node, options);
441
+ }
442
+ catch {
443
+ return node;
444
+ }
445
+ }
446
+ export function resolvePayloadSchemaFieldPath(schema, field, options) {
447
+ const segments = field.split(".");
448
+ if (segments.length === 0 || segments.some((segment) => segment === "")) {
449
+ return { ok: false, issue: "empty_segment" };
450
+ }
451
+ if (segments.some((segment) => INDEX_SEGMENT.test(segment))) {
452
+ return { ok: false, issue: "array_index" };
453
+ }
454
+ let current = tryExpandSchema(schema, options);
455
+ for (const segment of segments) {
456
+ if (!isPlainSchemaObject(current)) {
457
+ return { ok: false, issue: "missing" };
458
+ }
459
+ const node = current;
460
+ if (node.$ref !== undefined && node.properties === undefined) {
461
+ return { ok: false, issue: "missing" };
462
+ }
463
+ const typeName = schemaNodeType(node);
464
+ if (typeName === "array" ||
465
+ (node.items !== undefined && typeName !== "object")) {
466
+ return { ok: false, issue: "array_index" };
467
+ }
468
+ const properties = readProperties(node);
469
+ if (!Object.prototype.hasOwnProperty.call(properties, segment)) {
470
+ return { ok: false, issue: "missing" };
471
+ }
472
+ if (!readRequiredKeys(node).includes(segment)) {
473
+ return { ok: false, issue: "optional" };
474
+ }
475
+ current = properties[segment];
476
+ }
477
+ const leaf = isPlainSchemaObject(current)
478
+ ? tryExpandSchema(current, options)
479
+ : undefined;
480
+ if (leaf?.$ref !== undefined && leaf.properties === undefined) {
481
+ return { ok: false, issue: "untyped" };
482
+ }
483
+ const fieldType = schemaNodeType(leaf);
484
+ if (fieldType === undefined) {
485
+ return { ok: false, issue: "untyped" };
486
+ }
487
+ return { ok: true, type: fieldType };
488
+ }
423
489
  function expandSchemaNode(node, path, ctx) {
424
490
  const { schema, stack } = derefSchemaNode(node, path, ctx);
425
491
  const childCtx = { schemas: ctx.schemas, stack };
@@ -496,3 +562,22 @@ export function assertCloneAssignmentPayload(envelope, cloneInputSchema, success
496
562
  : `${pathLead}clone assignment payload does not match io.input.schema for ${successorId}`);
497
563
  }
498
564
  }
565
+ /**
566
+ * Validate a predecessor success payload against the child's IR
567
+ * `clone_input_schema` (YAML: `io.input.schema`) on a normal pipeline edge.
568
+ */
569
+ export function assertPriorInputPayload(envelope, cloneInputSchema, childId, options) {
570
+ if (envelope.status !== "success") {
571
+ return;
572
+ }
573
+ const schema = compilePayloadSchema(cloneInputSchema, options);
574
+ if (envelope.payload === undefined) {
575
+ throw new EnvelopeError(`prior payload is required by io.input.schema for ${childId}`);
576
+ }
577
+ if (!Value.Check(schema, envelope.payload)) {
578
+ const details = payloadSchemaMismatchDetails(schema, envelope.payload);
579
+ throw new EnvelopeError(details
580
+ ? `prior payload does not match io.input.schema for ${childId}: ${details}`
581
+ : `prior payload does not match io.input.schema for ${childId}`);
582
+ }
583
+ }
@@ -153,7 +153,7 @@ export function registerCatalogTools(server, deps) {
153
153
  }
154
154
  });
155
155
  server.registerTool("describe_pipeline", {
156
- description: "Describe a pipeline DAG: stages with needs, fork, clonable, clone_cap, and gate_kinds. Input is a filesystem pipeline path (same as start_run).",
156
+ description: "Describe a pipeline DAG: stages with needs, fork, and gate_kinds. Input is a filesystem pipeline path (same as start_run).",
157
157
  inputSchema: z.object({
158
158
  pipeline: z.string(),
159
159
  }),
@@ -170,7 +170,6 @@ export function registerCatalogTools(server, deps) {
170
170
  ? node.needsEdges.map((edge) => ({ id: edge.id, on: [...edge.on] }))
171
171
  : node.needs,
172
172
  ...(node.fork !== undefined ? { fork: node.fork } : {}),
173
- ...(node.clonable !== undefined ? { clonable: node.clonable } : {}),
174
173
  ...(node.clone_cap !== undefined ? { clone_cap: node.clone_cap } : {}),
175
174
  ...(gateById.get(node.id) !== undefined
176
175
  ? { gate_kinds: gateById.get(node.id) }
@@ -1,2 +1,2 @@
1
1
  export declare const PACKAGE_NAME = "stageflow";
2
- export declare const PACKAGE_VERSION = "0.15.0";
2
+ export declare const PACKAGE_VERSION = "0.18.0";
@@ -1,2 +1,2 @@
1
1
  export const PACKAGE_NAME = "stageflow";
2
- export const PACKAGE_VERSION = "0.15.0";
2
+ export const PACKAGE_VERSION = "0.18.0";
@@ -2,7 +2,6 @@ import type { FeedbackLoopHistory, FeedbackLoopRecord, RunDetail, StageSnapshot
2
2
  import type { PipelineTrackProjection, RunSummary } from "../runstore/port.js";
3
3
  import type { AskOperatorPrompt } from "../tools/askOperator.js";
4
4
  import type { StageEnvelope } from "../types/envelope.js";
5
- import type { CloneForkItem } from "../types/forkChoice.js";
6
5
  export type StageProjection = {
7
6
  stage_id: string;
8
7
  status: StageSnapshot["status"];
@@ -12,7 +11,6 @@ export type StageProjection = {
12
11
  artifacts: string[];
13
12
  payload?: Record<string, unknown>;
14
13
  fork_choice?: string[];
15
- clone_forks?: CloneForkItem[];
16
14
  feedback_loop?: StageEnvelope["feedback_loop"];
17
15
  } | null;
18
16
  artifacts: string[];
@@ -59,9 +59,6 @@ export function projectRun(detail) {
59
59
  ...(stage.envelope.fork_choice !== undefined
60
60
  ? { fork_choice: stage.envelope.fork_choice }
61
61
  : {}),
62
- ...(stage.envelope.clone_forks !== undefined
63
- ? { clone_forks: stage.envelope.clone_forks }
64
- : {}),
65
62
  ...(stage.envelope.feedback_loop !== undefined
66
63
  ? { feedback_loop: stage.envelope.feedback_loop }
67
64
  : {}),
@@ -3,7 +3,7 @@ export function formatPriorEnvelope(priorEnvelope, priorEnvelopes, priorEnvelope
3
3
  return `Prior envelopes by stage (declaration order):\n${JSON.stringify(priorEnvelopesByStage, null, 2)}`;
4
4
  }
5
5
  if (priorEnvelopes !== undefined && priorEnvelopes.length > 0) {
6
- return `Prior envelopes (${priorEnvelopes.length} clones, clone-list order):\n${JSON.stringify(priorEnvelopes, null, 2)}`;
6
+ return `Prior envelopes (${priorEnvelopes.length}):\n${JSON.stringify(priorEnvelopes, null, 2)}`;
7
7
  }
8
8
  return priorEnvelope === null
9
9
  ? "No prior envelope (first stage)."
@@ -4,6 +4,7 @@ import type { StageEnvelope } from "../types/envelope.js";
4
4
  import type { FeedbackLoopConfig, ResolvedPipelineDag } from "../types/pipeline.js";
5
5
  import type { CompletionCheck } from "../types/completion.js";
6
6
  import type { StageGateKind } from "../types/stage.js";
7
+ import type { StageUsage } from "../types/usage.js";
7
8
  export type RunStatus = "created" | "running" | "succeeded" | "failed";
8
9
  export type RunPipelineDagSnapshot = ResolvedPipelineDag & {
9
10
  stage_ids: string[];
@@ -70,6 +71,8 @@ export type StageSnapshot = {
70
71
  last_at?: string;
71
72
  pending_prompt?: AskOperatorPrompt;
72
73
  attempt_count: number;
74
+ /** Total $ spent on this stage across every attempt; omitted when no attempt reported usage. */
75
+ cost_usd?: number;
73
76
  };
74
77
  export type StageExecution = {
75
78
  run_id: string;
@@ -80,6 +83,8 @@ export type StageExecution = {
80
83
  started_at?: string;
81
84
  finished_at?: string;
82
85
  envelope: StageEnvelope | null;
86
+ cost_usd?: number;
87
+ usage?: StageUsage;
83
88
  };
84
89
  export type StageExecutionPatch = {
85
90
  status?: StageSnapshot["status"];
@@ -87,6 +92,8 @@ export type StageExecutionPatch = {
87
92
  started_at?: string;
88
93
  finished_at?: string;
89
94
  envelope?: StageEnvelope | null;
95
+ cost_usd?: number;
96
+ usage?: StageUsage;
90
97
  };
91
98
  /** The durable disposition of completion verification for one stage attempt. */
92
99
  export type VerificationOutcome = "not_run" | "passed" | "failed" | "error";
@@ -220,6 +227,7 @@ export type CompactStage = {
220
227
  status: StageSnapshot["status"];
221
228
  attempt_count: number;
222
229
  definition_id?: string;
230
+ cost_usd?: number;
223
231
  };
224
232
  export type RunSummary = {
225
233
  run_id: string;
@@ -246,6 +254,8 @@ export type RunSummary = {
246
254
  failed_reason?: string;
247
255
  /** The active loop, when a run is currently replaying or awaiting a decision. */
248
256
  active_feedback_loop?: FeedbackLoopRecord;
257
+ /** Sum of every stage's cost_usd; omitted when no stage reported usage. */
258
+ total_cost_usd?: number;
249
259
  };
250
260
  export type RunDetail = Omit<RunSummary, "stages"> & {
251
261
  task_yaml: string;
@@ -26,8 +26,15 @@ function compactStages(stages) {
26
26
  status: s.status,
27
27
  attempt_count: s.attempt_count,
28
28
  ...(s.definition_id !== undefined ? { definition_id: s.definition_id } : {}),
29
+ ...(s.cost_usd !== undefined ? { cost_usd: s.cost_usd } : {}),
29
30
  }));
30
31
  }
32
+ function totalCostUsd(stages) {
33
+ const costs = stages.map((s) => s.cost_usd).filter((c) => c !== undefined);
34
+ if (costs.length === 0)
35
+ return undefined;
36
+ return costs.reduce((sum, c) => sum + c, 0);
37
+ }
31
38
  function waitingSummaryFromPrompt(prompt) {
32
39
  if (!prompt)
33
40
  return "Waiting for input";
@@ -89,6 +96,7 @@ function resolveListedStatus(stages, meta, dag) {
89
96
  export function projectRunSummary(meta, stages, feedback) {
90
97
  const dag = meta.pipeline_dag;
91
98
  const waiting = waitingFieldsFromStages(stages);
99
+ const cost = totalCostUsd(stages);
92
100
  if (feedback?.active_feedback_loop?.state === "waiting_for_human" &&
93
101
  waiting.waiting_stage_id !== undefined &&
94
102
  waiting.waiting_kind === undefined) {
@@ -114,6 +122,7 @@ export function projectRunSummary(meta, stages, feedback) {
114
122
  ...(feedback?.active_feedback_loop !== undefined
115
123
  ? { active_feedback_loop: feedback.active_feedback_loop }
116
124
  : {}),
125
+ ...(cost !== undefined ? { total_cost_usd: cost } : {}),
117
126
  };
118
127
  }
119
128
  export function projectRunDetail(meta, stages, task_yaml, dagSnapshot, feedback) {
@@ -78,6 +78,18 @@ function ensureStageExecutionVerificationOutcomeColumn(db) {
78
78
  db.exec(`ALTER TABLE stage_executions ADD COLUMN verification_outcome TEXT NOT NULL DEFAULT 'not_run'`);
79
79
  }
80
80
  }
81
+ function ensureStageExecutionCostColumns(db) {
82
+ const cols = db
83
+ .prepare(`PRAGMA table_info(stage_executions)`)
84
+ .all();
85
+ const names = new Set(cols.map((c) => c.name));
86
+ if (!names.has("cost_usd")) {
87
+ db.exec(`ALTER TABLE stage_executions ADD COLUMN cost_usd REAL`);
88
+ }
89
+ if (!names.has("usage_json")) {
90
+ db.exec(`ALTER TABLE stage_executions ADD COLUMN usage_json TEXT`);
91
+ }
92
+ }
81
93
  function backfillVerificationOutcomes(db) {
82
94
  db.exec(`
83
95
  UPDATE stage_executions
@@ -184,6 +196,8 @@ function executionFromRow(row) {
184
196
  envelope: row.envelope_json
185
197
  ? JSON.parse(row.envelope_json)
186
198
  : null,
199
+ ...(row.cost_usd != null ? { cost_usd: row.cost_usd } : {}),
200
+ ...(row.usage_json != null ? { usage: JSON.parse(row.usage_json) } : {}),
187
201
  };
188
202
  }
189
203
  function verificationCheckResultFromRow(row) {
@@ -292,6 +306,7 @@ export class SqliteRunStore {
292
306
  ensureRunLocatorColumns(this.db);
293
307
  ensureStageExecutionsTable(this.db);
294
308
  ensureStageExecutionVerificationOutcomeColumn(this.db);
309
+ ensureStageExecutionCostColumns(this.db);
295
310
  ensureStageEventsAttemptColumn(this.db);
296
311
  ensureVerificationCheckResultsTable(this.db);
297
312
  ensureFeedbackReplayStagePassEnvelopeColumn(this.db);
@@ -432,7 +447,7 @@ export class SqliteRunStore {
432
447
  verification_outcome: "not_run",
433
448
  });
434
449
  const row = this.db
435
- .prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
450
+ .prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
436
451
  FROM stage_executions
437
452
  WHERE run_id = ? AND stage_id = ? AND attempt = ?`)
438
453
  .get(runId, stageId, attempt);
@@ -442,7 +457,7 @@ export class SqliteRunStore {
442
457
  async listStageExecutions(runId, stageId) {
443
458
  await this.ready();
444
459
  const rows = this.db
445
- .prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
460
+ .prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
446
461
  FROM stage_executions
447
462
  WHERE run_id = ? AND stage_id = ?
448
463
  ORDER BY attempt ASC`)
@@ -452,7 +467,7 @@ export class SqliteRunStore {
452
467
  async getLatestStageExecution(runId, stageId) {
453
468
  await this.ready();
454
469
  const row = this.db
455
- .prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
470
+ .prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
456
471
  FROM stage_executions
457
472
  WHERE run_id = ? AND stage_id = ?
458
473
  ORDER BY attempt DESC
@@ -471,7 +486,7 @@ export class SqliteRunStore {
471
486
  async getStageExecution(runId, stageId, attempt) {
472
487
  await this.ready();
473
488
  const row = this.db
474
- .prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
489
+ .prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json, cost_usd, usage_json
475
490
  FROM stage_executions
476
491
  WHERE run_id = ? AND stage_id = ? AND attempt = ?`)
477
492
  .get(runId, stageId, attempt);
@@ -509,6 +524,14 @@ export class SqliteRunStore {
509
524
  params.envelope_json =
510
525
  patch.envelope != null ? JSON.stringify(patch.envelope) : null;
511
526
  }
527
+ if (patch.cost_usd !== undefined) {
528
+ sets.push("cost_usd = @cost_usd");
529
+ params.cost_usd = patch.cost_usd;
530
+ }
531
+ if (patch.usage !== undefined) {
532
+ sets.push("usage_json = @usage_json");
533
+ params.usage_json = JSON.stringify(patch.usage);
534
+ }
512
535
  if (sets.length === 0)
513
536
  return;
514
537
  const result = this.db
@@ -1 +1 @@
1
- export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT,\n git_sha TEXT,\n ci_pr_url TEXT,\n ci_job_url TEXT,\n pipeline_path TEXT,\n task_path TEXT,\n project_root TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n verification_outcome TEXT NOT NULL DEFAULT 'not_run',\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n\nCREATE TABLE IF NOT EXISTS verification_check_results (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n check_id TEXT NOT NULL,\n check_type TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n evidence_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt, check_id),\n FOREIGN KEY (run_id, stage_id, attempt)\n REFERENCES stage_executions(run_id, stage_id, attempt)\n);\n\nCREATE INDEX IF NOT EXISTS idx_verification_check_results_execution\n ON verification_check_results (run_id, stage_id, attempt, check_id);\n\nCREATE TABLE IF NOT EXISTS feedback_loops (\n run_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n policy_json TEXT NOT NULL,\n state TEXT NOT NULL,\n current_replay_id TEXT,\n current_replay_number INTEGER,\n deferred_send_back_json TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, loop_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_loops_run_state\n ON feedback_loops (run_id, state, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replays (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n target_stage_id TEXT NOT NULL,\n replay_number INTEGER NOT NULL,\n max_replays INTEGER NOT NULL,\n replay_session TEXT NOT NULL,\n route_stage_ids_json TEXT NOT NULL,\n feedback_envelope_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, replay_id),\n FOREIGN KEY (run_id, loop_id) REFERENCES feedback_loops(run_id, loop_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_replays_loop_number\n ON feedback_replays (run_id, loop_id, replay_number);\nCREATE INDEX IF NOT EXISTS idx_feedback_replays_run_loop\n ON feedback_replays (run_id, loop_id, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replay_stage_passes (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n stage_attempt INTEGER NOT NULL,\n session_origin_attempt INTEGER,\n session_mode TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n emitted_envelope_json TEXT,\n PRIMARY KEY (run_id, replay_id, stage_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_replay_stage_passes_replay\n ON feedback_replay_stage_passes (run_id, replay_id, stage_id);\n\nCREATE TABLE IF NOT EXISTS fork_generations (\n run_id TEXT NOT NULL,\n generation_id TEXT NOT NULL,\n replay_id TEXT,\n fork_parent_stage_id TEXT NOT NULL,\n generation_number INTEGER NOT NULL,\n clone_stage_ids_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, generation_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_fork_generations_parent_number\n ON fork_generations (run_id, fork_parent_stage_id, generation_number);\nCREATE INDEX IF NOT EXISTS idx_fork_generations_run_replay\n ON fork_generations (run_id, replay_id, created_at);\n";
1
+ export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT,\n git_sha TEXT,\n ci_pr_url TEXT,\n ci_job_url TEXT,\n pipeline_path TEXT,\n task_path TEXT,\n project_root TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n verification_outcome TEXT NOT NULL DEFAULT 'not_run',\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n cost_usd REAL,\n usage_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n\nCREATE TABLE IF NOT EXISTS verification_check_results (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n check_id TEXT NOT NULL,\n check_type TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n evidence_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt, check_id),\n FOREIGN KEY (run_id, stage_id, attempt)\n REFERENCES stage_executions(run_id, stage_id, attempt)\n);\n\nCREATE INDEX IF NOT EXISTS idx_verification_check_results_execution\n ON verification_check_results (run_id, stage_id, attempt, check_id);\n\nCREATE TABLE IF NOT EXISTS feedback_loops (\n run_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n policy_json TEXT NOT NULL,\n state TEXT NOT NULL,\n current_replay_id TEXT,\n current_replay_number INTEGER,\n deferred_send_back_json TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, loop_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_loops_run_state\n ON feedback_loops (run_id, state, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replays (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n loop_id TEXT NOT NULL,\n source_stage_id TEXT NOT NULL,\n source_attempt INTEGER NOT NULL,\n target_stage_id TEXT NOT NULL,\n replay_number INTEGER NOT NULL,\n max_replays INTEGER NOT NULL,\n replay_session TEXT NOT NULL,\n route_stage_ids_json TEXT NOT NULL,\n feedback_envelope_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, replay_id),\n FOREIGN KEY (run_id, loop_id) REFERENCES feedback_loops(run_id, loop_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_replays_loop_number\n ON feedback_replays (run_id, loop_id, replay_number);\nCREATE INDEX IF NOT EXISTS idx_feedback_replays_run_loop\n ON feedback_replays (run_id, loop_id, created_at);\n\nCREATE TABLE IF NOT EXISTS feedback_replay_stage_passes (\n run_id TEXT NOT NULL,\n replay_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n stage_attempt INTEGER NOT NULL,\n session_origin_attempt INTEGER,\n session_mode TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n emitted_envelope_json TEXT,\n PRIMARY KEY (run_id, replay_id, stage_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_feedback_replay_stage_passes_replay\n ON feedback_replay_stage_passes (run_id, replay_id, stage_id);\n\nCREATE TABLE IF NOT EXISTS fork_generations (\n run_id TEXT NOT NULL,\n generation_id TEXT NOT NULL,\n replay_id TEXT,\n fork_parent_stage_id TEXT NOT NULL,\n generation_number INTEGER NOT NULL,\n clone_stage_ids_json TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (run_id, generation_id),\n FOREIGN KEY (run_id, replay_id) REFERENCES feedback_replays(run_id, replay_id)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_fork_generations_parent_number\n ON fork_generations (run_id, fork_parent_stage_id, generation_number);\nCREATE INDEX IF NOT EXISTS idx_fork_generations_run_replay\n ON fork_generations (run_id, replay_id, created_at);\n";
@@ -57,6 +57,8 @@ CREATE TABLE IF NOT EXISTS stage_executions (
57
57
  started_at TEXT,
58
58
  finished_at TEXT,
59
59
  envelope_json TEXT,
60
+ cost_usd REAL,
61
+ usage_json TEXT,
60
62
  PRIMARY KEY (run_id, stage_id, attempt),
61
63
  FOREIGN KEY (run_id) REFERENCES runs(run_id)
62
64
  );
@@ -1,10 +1,13 @@
1
1
  import type { StageEnvelope } from "../types/envelope.js";
2
+ import type { StageUsage } from "../types/usage.js";
2
3
  import type { StageLogEvent, StageSnapshot } from "./port.js";
3
4
  export type StageExecutionPatch = {
4
5
  status?: StageSnapshot["status"];
5
6
  started_at?: string;
6
7
  finished_at?: string;
7
8
  envelope?: StageEnvelope | null;
9
+ cost_usd?: number;
10
+ usage?: StageUsage;
8
11
  };
9
12
  export declare function executionStatusFromEvents(events: StageLogEvent[]): StageSnapshot["status"];
10
13
  export declare function deriveExecutionPatchFromEvent(event: StageLogEvent, current: Pick<StageSnapshot, "status"> & {
@@ -14,9 +14,13 @@ export function deriveExecutionPatchFromEvent(event, current) {
14
14
  else if (eventName === "waiting_for_input") {
15
15
  patch.status = "waiting_for_input";
16
16
  }
17
- else if (eventName === "succeeded" || eventName === "failed") {
18
- patch.status = eventName;
17
+ else if (event.event === "succeeded" || event.event === "failed") {
18
+ patch.status = event.event;
19
19
  patch.finished_at = at;
20
+ if (event.usage) {
21
+ patch.cost_usd = event.usage.costUsd;
22
+ patch.usage = event.usage;
23
+ }
20
24
  }
21
25
  return patch;
22
26
  }
@@ -1,4 +1,5 @@
1
1
  import type { RunPipelineDagSnapshot } from "./port.js";
2
2
  export declare function mintCloneInstanceId(catalogId: string, n: number): string;
3
3
  export declare function mintCloneInstanceIds(catalogId: string, count: number, startAt?: number): string[];
4
+ export declare function cloneInstanceOrdinal(instanceId: string, catalogId: string): number | undefined;
4
5
  export declare function definitionIdForInstance(snapshot: RunPipelineDagSnapshot | null | undefined, instanceId: string): string;
@@ -16,6 +16,15 @@ export function mintCloneInstanceIds(catalogId, count, startAt = 1) {
16
16
  guardPositiveInteger(startAt, "startAt");
17
17
  return Array.from({ length: count }, (_, i) => mintCloneInstanceId(catalogId, startAt + i));
18
18
  }
19
+ export function cloneInstanceOrdinal(instanceId, catalogId) {
20
+ const prefix = `${catalogId}~`;
21
+ if (!instanceId.startsWith(prefix))
22
+ return undefined;
23
+ const n = Number(instanceId.slice(prefix.length));
24
+ if (!Number.isInteger(n) || n < 1)
25
+ return undefined;
26
+ return n;
27
+ }
19
28
  export function definitionIdForInstance(snapshot, instanceId) {
20
29
  const node = snapshot?.nodes.find((n) => n.id === instanceId);
21
30
  if (!node)
@@ -18,11 +18,14 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
18
18
  let status;
19
19
  let envelope;
20
20
  let attempt_count;
21
+ let cost_usd;
21
22
  if (latest !== null) {
22
23
  events = await store.listStageEvents(runId, stageId, latest.attempt);
23
24
  status = stageStatusFromEvents(events);
24
25
  envelope = await tryReadEnvelope(store, runId, stageId, latest);
25
26
  attempt_count = await store.countStageAttempts(runId, stageId);
27
+ const executions = await store.listStageExecutions(runId, stageId);
28
+ cost_usd = sumStageExecutionCost(executions);
26
29
  }
27
30
  else {
28
31
  events = await store.listStageEvents(runId, stageId);
@@ -42,5 +45,13 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
42
45
  last_at,
43
46
  attempt_count,
44
47
  ...(pending ? { pending_prompt: pending } : {}),
48
+ ...(cost_usd !== undefined ? { cost_usd } : {}),
45
49
  };
46
50
  }
51
+ /** Sum cost across every attempt — retries are real spend too, so a stage's total isn't just its latest attempt. */
52
+ function sumStageExecutionCost(executions) {
53
+ const costs = executions.map((e) => e.cost_usd).filter((c) => c !== undefined);
54
+ if (costs.length === 0)
55
+ return undefined;
56
+ return costs.reduce((sum, c) => sum + c, 0);
57
+ }
@@ -1,30 +1,26 @@
1
1
  import type { RunPipelineDagSnapshot } from "../runstore/port.js";
2
2
  import type { StageEnvelope } from "../types/envelope.js";
3
3
  import type { NeedTerminalState, ResolvedPipelineDag } from "../types/pipeline.js";
4
- import { type ActiveCohort } from "./forkGeneration.js";
4
+ import type { ResolvePriorEnvelopeResult } from "./envelopeRouting.js";
5
5
  import type { StageScheduleState } from "./pipelineScheduler.js";
6
6
  export declare function isCloneInstance(dag: ResolvedPipelineDag, stageId: string): boolean;
7
7
  export declare function sequentialLaterCloneIds(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): string[];
8
8
  export declare function skipRejectedNeedDependents(dag: ResolvedPipelineDag, parentId: string, states: Map<string, StageScheduleState>, observed: NeedTerminalState, onSkip: (stageId: string) => void): void;
9
9
  export declare function failureIsAccepted(dag: ResolvedPipelineDag, failedId: string, states: Map<string, StageScheduleState>): boolean;
10
- /**
11
- * A fresh clone_forks fanout targeting a catalog id that already has
12
- * instances is only safe when every existing instance was put back to
13
- * `pending` by the current retry cascade (`retryDownstreamIds`) — that means
14
- * an ancestor was legitimately retried and this parent re-emitted the same
15
- * fanout for reactivation. Instances that are still active/terminal, or
16
- * merely `pending` without having gone through a retry reset (e.g. an
17
- * envelope processed twice in one live run), are rejected as before.
18
- *
19
- * Feedback-loop replay is different: the prior cohort was superseded
20
- * (`skipped`) and a new generation must mint fresh `~N` ids, so
21
- * `forceFreshCloneIds` bypasses the conflict.
22
- */
23
- export declare function cloneFanoutConflict(dag: ResolvedPipelineDag, envelope: StageEnvelope, states: Map<string, StageScheduleState>, retryDownstreamIds: ReadonlySet<string>, options?: {
24
- forceFreshCloneIds?: boolean;
25
- }): string | undefined;
26
- export declare function nextFreeCloneSuffix(snapshot: RunPipelineDagSnapshot, catalogId: string): number;
27
- export declare function protectedClonableChildIds(dag: ResolvedPipelineDag, predecessorId: string, envelope: StageEnvelope): Set<string>;
10
+ export type MintResult = {
11
+ kind: "none";
12
+ } | {
13
+ kind: "error";
14
+ reason: string;
15
+ } | {
16
+ kind: "minted";
17
+ dag: RunPipelineDagSnapshot;
18
+ catalogChildId: string;
19
+ instanceIds: string[];
20
+ };
21
+ export declare function mint(dag: ResolvedPipelineDag, emitterId: string, envelope: StageEnvelope): MintResult;
22
+ export declare function assignment(dag: ResolvedPipelineDag, stageId: string, definitionId: string, completedEnvelopes: Map<string, StageEnvelope>): ResolvePriorEnvelopeResult | undefined;
23
+ export declare function protectedClonableChildIds(dag: ResolvedPipelineDag, predecessorId: string, _envelope: StageEnvelope): Set<string>;
28
24
  /**
29
25
  * Downstream closure for a retry, resolved through clone-instance ids and
30
26
  * static multi-parent edges. Catalog-level children (e.g. a join stage) are
@@ -38,17 +34,4 @@ export declare function protectedClonableChildIds(dag: ResolvedPipelineDag, pred
38
34
  export declare function cloneRetryDownstream(dag: ResolvedPipelineDag, stageIds: string[]): Set<string>;
39
35
  export declare function cloneFailureContinuesSchedule(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): boolean;
40
36
  export declare function cloneFailFastSkipIds(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): string[];
41
- export declare function cloneScheduleAllowsRun(dag: ResolvedPipelineDag, stageId: string, states: Map<string, StageScheduleState>, completedEnvelopes: Map<string, StageEnvelope>, options?: {
42
- activeCohortForNeeds?: (needsId: string) => ActiveCohort;
43
- activeCloneIdsForNeeds?: (needsId: string) => Set<string> | null;
44
- }): boolean;
45
- export type ApplyCloneForksResult = {
46
- dag: RunPipelineDagSnapshot;
47
- skippedIds: string[];
48
- /** Freshly minted instance ids keyed by clonable catalog successor id. */
49
- mintedBySuccessor?: Map<string, string[]>;
50
- };
51
- export declare function applyCloneForksToSchedule(dag: ResolvedPipelineDag, predecessorId: string, envelope: StageEnvelope, states: Map<string, StageScheduleState>, options?: {
52
- forceFreshCloneIds?: boolean;
53
- }): ApplyCloneForksResult;
54
- export declare function applyCloneForksFromEnvelopes(dag: ResolvedPipelineDag, states: Map<string, StageScheduleState>, completedEnvelopes: Map<string, StageEnvelope>): ApplyCloneForksResult;
37
+ export declare function cloneScheduleAllowsRun(dag: ResolvedPipelineDag, stageId: string, states: Map<string, StageScheduleState>, completedEnvelopes: Map<string, StageEnvelope>): boolean;