@owlmeans/agent-common 0.1.18-rc.47 → 0.1.18-rc.49

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 (62) hide show
  1. package/README.md +174 -13
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/agent-common/SKILL.md +1 -1
  4. package/build/consts.d.ts +20 -0
  5. package/build/consts.d.ts.map +1 -1
  6. package/build/consts.js +103 -0
  7. package/build/consts.js.map +1 -1
  8. package/build/errors.d.ts +1 -1
  9. package/build/errors.js +1 -1
  10. package/build/helpers/conversation.d.ts +0 -9
  11. package/build/helpers/conversation.d.ts.map +1 -1
  12. package/build/helpers/conversation.js +0 -28
  13. package/build/helpers/conversation.js.map +1 -1
  14. package/build/helpers/truncate.d.ts +10 -0
  15. package/build/helpers/truncate.d.ts.map +1 -0
  16. package/build/helpers/truncate.js +29 -0
  17. package/build/helpers/truncate.js.map +1 -0
  18. package/build/index.d.ts +4 -1
  19. package/build/index.d.ts.map +1 -1
  20. package/build/index.js +2 -1
  21. package/build/index.js.map +1 -1
  22. package/build/models/pipeline-spec/types.d.ts +57 -0
  23. package/build/models/pipeline-spec/types.d.ts.map +1 -0
  24. package/build/models/pipeline-spec/types.js +2 -0
  25. package/build/models/pipeline-spec/types.js.map +1 -0
  26. package/build/models/pipeline-spec.d.ts +4 -0
  27. package/build/models/pipeline-spec.d.ts.map +1 -0
  28. package/build/models/pipeline-spec.js +171 -0
  29. package/build/models/pipeline-spec.js.map +1 -0
  30. package/build/pipeline.d.ts +4 -43
  31. package/build/pipeline.d.ts.map +1 -1
  32. package/build/pipeline.js +9 -205
  33. package/build/pipeline.js.map +1 -1
  34. package/build/results/types.d.ts +211 -0
  35. package/build/results/types.d.ts.map +1 -0
  36. package/build/results/types.js +2 -0
  37. package/build/results/types.js.map +1 -0
  38. package/build/results.d.ts +3 -55
  39. package/build/results.d.ts.map +1 -1
  40. package/build/results.js +202 -250
  41. package/build/results.js.map +1 -1
  42. package/build/types.d.ts +1 -137
  43. package/build/types.d.ts.map +1 -1
  44. package/package.json +5 -5
  45. package/src/consts.ts +113 -0
  46. package/src/errors.ts +1 -1
  47. package/src/helpers/conversation.ts +0 -32
  48. package/src/helpers/truncate.ts +31 -0
  49. package/src/index.ts +4 -1
  50. package/src/models/pipeline-spec/types.ts +58 -0
  51. package/src/models/pipeline-spec.ts +202 -0
  52. package/src/pipeline.ts +10 -231
  53. package/src/results/types.ts +221 -0
  54. package/src/results.ts +229 -274
  55. package/src/types.ts +2 -147
  56. package/tests/pipeline.spec.ts +26 -29
  57. package/tests/results.spec.ts +24 -26
  58. package/build/flows.d.ts +0 -21
  59. package/build/flows.d.ts.map +0 -1
  60. package/build/flows.js +0 -105
  61. package/build/flows.js.map +0 -1
  62. package/src/flows.ts +0 -113
package/build/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ResourceRecord } from '@owlmeans/resource';
2
- import { type AgentRunStatus, RESULTS_EVERY_STEP, CumulativeResultSource, PipelineRunStatus } from './consts.js';
2
+ import { type AgentRunStatus, PipelineRunStatus } from './consts.js';
3
3
  import type { Inquiry } from '@owlmeans/llm-common';
4
4
  /**
5
5
  * What a conversation is, as an address.
@@ -216,140 +216,4 @@ export interface PipelineProgress {
216
216
  skipped?: boolean;
217
217
  note?: string;
218
218
  }
219
- /**
220
- * ONE authoritative fact about what a step produced, small enough to be one line of a prompt.
221
- *
222
- * Every field is optional past `kind` and `name`, because facts differ in what matters about them:
223
- * a type has a shape and an import specifier, an endpoint an alias, a method, a path and a guard, a
224
- * resource a table and an accessor, a file only its path. Put scalars that have no field of their
225
- * own into `attrs`.
226
- */
227
- export interface CumulativeResultFact {
228
- /** What the fact is about — see {@link CumulativeFactKind}. */
229
- kind: string;
230
- /** The name to use VERBATIM. */
231
- name: string;
232
- /** The file it lives in, relative to the project root. */
233
- path?: string;
234
- /** The module specifier it is imported from. */
235
- specifier?: string;
236
- /** Its shape or signature, on one line. */
237
- signature?: string;
238
- /** Fields, parameters or columns, when there is no signature to say it. */
239
- members?: string[];
240
- /** Another fact's name this one points at — a returned type, a mapped table. */
241
- ref?: string;
242
- /** Scalars without a field of their own — `method`, `path`, `guard`, `table`. */
243
- attrs?: Record<string, string>;
244
- }
245
- /**
246
- * One step's contribution to a ledger.
247
- *
248
- * Keyed by `(ledger, runId, step)`. `order` places it in the ledger: the topological index of the
249
- * step in its own pipeline, preceded by the index path of every step it was composed under, so a
250
- * run composed as a step of another sorts inside that step, ahead of the composing step's own
251
- * entry. A single number cannot say that: two pipelines' indexes are not comparable.
252
- *
253
- * `full` and `compact` are rendered once, when the entry is written, and never contain a timestamp,
254
- * a run id or anything else a model would copy without meaning to.
255
- */
256
- export interface CumulativeResultEntry extends ResourceRecord {
257
- /** What entries can see each other — by default the root run of the composition. */
258
- ledger: string;
259
- pipeline: string;
260
- runId: string;
261
- step: string;
262
- /** How the entry is headed in a prompt: the step, behind the path of the steps it runs under. */
263
- label: string;
264
- order: number[];
265
- /** Bumped every time the entry is re-extracted after a later step rewrote one of its files. */
266
- revision: number;
267
- source: CumulativeResultSource;
268
- /** An `optional` step that failed: whatever it left behind, and a heading that says so. */
269
- partial?: boolean;
270
- facts: CumulativeResultFact[];
271
- /** The files the facts were read from — what a later rewrite is matched against. */
272
- files: string[];
273
- /** A model's summary, rendered and capped. NOT verified, and always labelled so. */
274
- summary?: string;
275
- full: string;
276
- compact: string;
277
- /** Consumers that always get the full entry, by step name, or every later step. */
278
- fullFor?: string[] | typeof RESULTS_EVERY_STEP;
279
- /** Consumers that never see the entry in their prompt. */
280
- omitFor?: string[];
281
- /** How many edges back the entry is still rendered in full by default. */
282
- window?: number;
283
- updatedAt: string;
284
- }
285
- /**
286
- * The optional model-written summary of a step. OFF unless a step declares one.
287
- *
288
- * Facts are the product; a summary is for what code cannot extract — an intent, a decision taken.
289
- * It is bounded by `maxChars` in code after the model answers, rendered deterministically from the
290
- * schema-shaped answer, and always labelled as unverified wherever it is shown.
291
- */
292
- export interface ResultSummarySpec {
293
- /** Which model — interpreted by the caller's summarize callback (a model role, typically). */
294
- role?: string;
295
- instructions: string;
296
- /** JSON schema of the answer. Absent means `{ summary: string }`. */
297
- schema?: Record<string, unknown>;
298
- /** Defaults to `DEFAULT_RESULT_SUMMARY_CHARS`. */
299
- maxChars?: number;
300
- }
301
- /**
302
- * What one step contributes, and to whom. JSON-safe, so a declaration can live in data.
303
- *
304
- * Extractors are named, never inlined: a declaration names what runs, and the runtime holds the
305
- * code. A consumer not named in `full` or `omit` gets the full entry within `window` edges and the
306
- * names only beyond it.
307
- */
308
- export interface StepResultsSpec {
309
- /** Named deterministic extractors, run in this order. */
310
- extractors?: string[];
311
- /** Later steps that get the FULL entry wherever they sit, or `'*'` for all of them. */
312
- full?: string[] | typeof RESULTS_EVERY_STEP;
313
- /** Later steps that are never shown the entry. */
314
- omit?: string[];
315
- /** Overrides {@link CumulativeResultsSpec.window} for this entry. */
316
- window?: number;
317
- /** Cap on the full rendering; facts are dropped whole, never cut. */
318
- maxChars?: number;
319
- /** Cap on the names-only rendering. */
320
- maxCompactChars?: number;
321
- /** Re-extract this entry when a later step rewrites one of its files. Defaults to the spec's. */
322
- refresh?: boolean;
323
- summary?: ResultSummarySpec;
324
- }
325
- /** The pipeline-level declaration — merged from a pipeline's own and any overlay with {@link mergeResultsSpecs}. */
326
- export interface CumulativeResultsSpec {
327
- /** Per step, by step name. A step with no entry contributes only what it records itself. */
328
- steps?: Record<string, StepResultsSpec>;
329
- /** Default {@link DEFAULT_RESULTS_WINDOW}. */
330
- window?: number;
331
- /** Total characters of one view. Default `DEFAULT_RESULTS_MAX_CHARS`. */
332
- maxChars?: number;
333
- /** Default cap of one full entry. Default {@link DEFAULT_RESULT_ENTRY_CHARS}. */
334
- maxEntryChars?: number;
335
- /** Default cap of one names-only entry. Default {@link DEFAULT_RESULT_COMPACT_CHARS}. */
336
- maxCompactChars?: number;
337
- /** Default for {@link StepResultsSpec.refresh}. Defaults to `true`. */
338
- refresh?: boolean;
339
- }
340
- /** What a facts query may narrow by. Every field is an exact match. */
341
- export interface ResultFactQuery {
342
- kind?: string;
343
- name?: string;
344
- /** A producing step, by its name or by its label. */
345
- step?: string;
346
- }
347
- /** What {@link renderResultEntry} needs of an entry. */
348
- export type RenderableResultEntry = Pick<CumulativeResultEntry, 'label' | 'source' | 'partial' | 'facts' | 'summary'>;
349
- export interface ResultRenderCaps {
350
- /** Cap on the full rendering. Default {@link DEFAULT_RESULT_ENTRY_CHARS}. */
351
- maxChars?: number;
352
- /** Cap on the names-only rendering. Default {@link DEFAULT_RESULT_COMPACT_CHARS}. */
353
- maxCompactChars?: number;
354
- }
355
219
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/agent-common",
3
- "version": "0.1.18-rc.47",
3
+ "version": "0.1.18-rc.49",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -29,10 +29,10 @@
29
29
  "typescript": "^7.0.2"
30
30
  },
31
31
  "dependencies": {
32
- "@owlmeans/error": "^0.1.18-rc.37",
33
- "@owlmeans/flow": "^0.1.18-rc.44",
34
- "@owlmeans/llm-common": "^0.1.18-rc.38",
35
- "@owlmeans/resource": "^0.1.18-rc.39"
32
+ "@owlmeans/error": "^0.1.18-rc.38",
33
+ "@owlmeans/flow": "^0.1.18-rc.45",
34
+ "@owlmeans/llm-common": "^0.1.18-rc.39",
35
+ "@owlmeans/resource": "^0.1.18-rc.40"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
package/src/consts.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { ShallowFlow } from '@owlmeans/flow'
2
+
1
3
  /**
2
4
  * The agent service alias.
3
5
  *
@@ -172,3 +174,114 @@ export enum CumulativeFactKind {
172
174
  Resource = 'resource',
173
175
  File = 'file',
174
176
  }
177
+
178
+ /**
179
+ * The lifecycle of one agent run.
180
+ *
181
+ * Read it as "how far did this run get", not "what did it say". Everything conversational happens
182
+ * inside `Working`; the steps exist so a plugin reading `onFinish` can say where a run ended.
183
+ * `Fail` is reachable from every working step and is TERMINAL — see the note on that step.
184
+ *
185
+ * `service` is left as the flow name on every step. A flow step normally binds to a service or an
186
+ * entrypoint, but an agent run is driven by whoever holds the model — there is no second party to
187
+ * hand control to, and inventing one would put a name in the serialized state that nothing
188
+ * resolves.
189
+ *
190
+ * Each working step keeps exactly ONE non-explicit outgoing transition, so `FlowModel.next()`
191
+ * always has an unambiguous answer: that is what lets a driver advance the run without knowing the
192
+ * vocabulary. `Fail` is marked explicit precisely so it never becomes that automatic answer.
193
+ */
194
+ export const agentRunFlow: ShallowFlow = {
195
+ flow: AGENT_RUN_FLOW,
196
+ initialStep: AgentRunStep.Received,
197
+
198
+ steps: {
199
+ [AgentRunStep.Received]: {
200
+ index: 0,
201
+ step: AgentRunStep.Received,
202
+ service: AGENT_RUN_FLOW,
203
+ initial: true,
204
+ transitions: {
205
+ [AgentRunTransition.Prepare]: {
206
+ transition: AgentRunTransition.Prepare,
207
+ step: AgentRunStep.Prepared,
208
+ },
209
+ [AgentRunTransition.Fail]: {
210
+ transition: AgentRunTransition.Fail,
211
+ step: AgentRunStep.Failed,
212
+ explicit: true,
213
+ },
214
+ },
215
+ },
216
+
217
+ [AgentRunStep.Prepared]: {
218
+ index: 1,
219
+ step: AgentRunStep.Prepared,
220
+ service: AGENT_RUN_FLOW,
221
+ transitions: {
222
+ [AgentRunTransition.Work]: {
223
+ transition: AgentRunTransition.Work,
224
+ step: AgentRunStep.Working,
225
+ },
226
+ [AgentRunTransition.Fail]: {
227
+ transition: AgentRunTransition.Fail,
228
+ step: AgentRunStep.Failed,
229
+ explicit: true,
230
+ },
231
+ },
232
+ },
233
+
234
+ [AgentRunStep.Working]: {
235
+ index: 2,
236
+ step: AgentRunStep.Working,
237
+ service: AGENT_RUN_FLOW,
238
+ transitions: {
239
+ [AgentRunTransition.Finalize]: {
240
+ transition: AgentRunTransition.Finalize,
241
+ step: AgentRunStep.Finalizing,
242
+ },
243
+ [AgentRunTransition.Fail]: {
244
+ transition: AgentRunTransition.Fail,
245
+ step: AgentRunStep.Failed,
246
+ explicit: true,
247
+ },
248
+ },
249
+ },
250
+
251
+ [AgentRunStep.Finalizing]: {
252
+ index: 3,
253
+ step: AgentRunStep.Finalizing,
254
+ service: AGENT_RUN_FLOW,
255
+ transitions: {
256
+ [AgentRunTransition.Finish]: {
257
+ transition: AgentRunTransition.Finish,
258
+ step: AgentRunStep.Finished,
259
+ },
260
+ [AgentRunTransition.Fail]: {
261
+ transition: AgentRunTransition.Fail,
262
+ step: AgentRunStep.Failed,
263
+ explicit: true,
264
+ },
265
+ },
266
+ },
267
+
268
+ [AgentRunStep.Finished]: {
269
+ index: 4,
270
+ step: AgentRunStep.Finished,
271
+ service: AGENT_RUN_FLOW,
272
+ transitions: {},
273
+ },
274
+
275
+ [AgentRunStep.Failed]: {
276
+ index: 5,
277
+ step: AgentRunStep.Failed,
278
+ service: AGENT_RUN_FLOW,
279
+ // Terminal. Nothing resumes a ReAct run — its tool results are side effects already applied
280
+ // to the world, so re-entering it re-applies them. Resumable work is a pipeline.
281
+ transitions: {},
282
+ },
283
+ },
284
+ }
285
+
286
+ /** Every flow this package declares, for a provider to serve. */
287
+ export const agentFlows: ShallowFlow[] = [agentRunFlow]
package/src/errors.ts CHANGED
@@ -21,7 +21,7 @@ export class AgentRunStateError extends AgentCommonError {
21
21
  /**
22
22
  * A pipeline declaration is not runnable.
23
23
  *
24
- * Thrown by {@link import('./pipeline.js').validatePipelineSpec} and by the runner's own build
24
+ * Thrown by {@link import('./models/pipeline-spec/types.js').PipelineSpecModel.validate} and by the runner's own build
25
25
  * step, and it always names EVERY fault it found rather than the first: a spec is authored once and
26
26
  * fixed once, and reporting faults one per build turns a five-minute edit into five builds.
27
27
  */
@@ -28,35 +28,3 @@ export const conversationFor = (
28
28
  scope: override?.scope ?? (target !== '' ? target : 'anonymous'),
29
29
  }
30
30
  }
31
-
32
- /**
33
- * Cut `text` to `max` characters on a boundary a reader will not trip over.
34
- *
35
- * Every cap in this package lands here, because a model asked for "at most N characters" answers
36
- * with N plus whatever it felt was needed. Truncating mid-word reads as corruption and truncating
37
- * mid-sentence reads as a bug report, so the cut prefers the last paragraph, then line, then
38
- * sentence, then word break inside the last quarter of the budget, and always marks itself.
39
- */
40
- export const truncateAt = (text: string, max: number): string => {
41
- const trimmed = text.trim()
42
- if (trimmed.length <= max) {
43
- return trimmed
44
- }
45
- if (max <= 1) {
46
- return trimmed.slice(0, Math.max(0, max))
47
- }
48
-
49
- const ellipsis = '…'
50
- const budget = max - ellipsis.length
51
- const head = trimmed.slice(0, budget)
52
- const floor = Math.floor(budget * 0.75)
53
-
54
- for (const boundary of ['\n\n', '\n', '. ', ' ']) {
55
- const at = head.lastIndexOf(boundary)
56
- if (at >= floor) {
57
- return head.slice(0, boundary === '. ' ? at + 1 : at).trimEnd() + ellipsis
58
- }
59
- }
60
-
61
- return head.trimEnd() + ellipsis
62
- }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Cut `text` to `max` characters on a boundary a reader will not trip over.
3
+ *
4
+ * Every cap in this package lands here, because a model asked for "at most N characters" answers
5
+ * with N plus whatever it felt was needed. Truncating mid-word reads as corruption and truncating
6
+ * mid-sentence reads as a bug report, so the cut prefers the last paragraph, then line, then
7
+ * sentence, then word break inside the last quarter of the budget, and always marks itself.
8
+ */
9
+ export const truncateAt = (text: string, max: number): string => {
10
+ const trimmed = text.trim()
11
+ if (trimmed.length <= max) {
12
+ return trimmed
13
+ }
14
+ if (max <= 1) {
15
+ return trimmed.slice(0, Math.max(0, max))
16
+ }
17
+
18
+ const ellipsis = '…'
19
+ const budget = max - ellipsis.length
20
+ const head = trimmed.slice(0, budget)
21
+ const floor = Math.floor(budget * 0.75)
22
+
23
+ for (const boundary of ['\n\n', '\n', '. ', ' ']) {
24
+ const at = head.lastIndexOf(boundary)
25
+ if (at >= floor) {
26
+ return head.slice(0, boundary === '. ' ? at + 1 : at).trimEnd() + ellipsis
27
+ }
28
+ }
29
+
30
+ return head.trimEnd() + ellipsis
31
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  export * from './consts.js'
2
2
  export * from './errors.js'
3
3
  export type * from './types.js'
4
+ export type * from './results/types.js'
5
+ export type * from './models/pipeline-spec/types.js'
6
+ export * from './models/pipeline-spec.js'
4
7
  export * from './pipeline.js'
5
8
  export * from './results.js'
6
- export * from './flows.js'
7
9
  export * from './helpers/conversation.js'
10
+ export * from './helpers/truncate.js'
@@ -0,0 +1,58 @@
1
+ import type { PipelineSpec, PipelineStepSpec } from '../../types.js'
2
+
3
+ /** One step a step transitively waits on, with the fewest `after` edges between the two. */
4
+ export interface PipelineStepDepth {
5
+ step: string
6
+ depth: number
7
+ }
8
+
9
+ /** The rules of one pipeline declaration: its validity, its order and the graph of its steps. */
10
+ export interface PipelineSpecModel {
11
+ readonly record: PipelineSpec
12
+ /**
13
+ * Every fault in one message, or nothing.
14
+ *
15
+ * Total and IO-free, so a spec can be checked by a test in microseconds and by the runner at build
16
+ * time. It reports EVERY fault it finds rather than the first, because a spec is authored once and
17
+ * a build that names one fault per run turns a five-minute edit into five builds.
18
+ *
19
+ * @throws {PipelineSpecError}
20
+ */
21
+ validate: () => void
22
+ /**
23
+ * The steps in an order that respects every `after`, with declaration order as the tie-break.
24
+ *
25
+ * Deterministic on purpose: the order decides what a progress report calls "step 3 of 16" and what
26
+ * a resume considers already behind it, and an order that depends on a `Set`'s iteration would make
27
+ * both drift between processes.
28
+ *
29
+ * @throws {PipelineSpecError} on a cycle or an unknown `after`.
30
+ */
31
+ orderSteps: () => string[]
32
+ /**
33
+ * `step` and everything that transitively waits on it.
34
+ *
35
+ * What a resume has to re-run when an operator names a step: re-entering a step without re-entering
36
+ * what read its output leaves the run describing a state that no longer follows from itself.
37
+ *
38
+ * @throws {PipelineUnknownStepError} when the spec does not declare `step`.
39
+ */
40
+ descendants: (step: string) => string[]
41
+ /**
42
+ * Every step `step` transitively waits on, with its DEPTH — the fewest `after` edges between the
43
+ * two — in topological order.
44
+ *
45
+ * The mirror of {@link PipelineSpecModel.descendants}, with one deliberate asymmetry: the named step
46
+ * itself is NOT included. A re-run re-enters the step it names, so the descendants include it;
47
+ * nothing is its own predecessor, so the ancestors do not. The depth is what a consumer uses to
48
+ * decide how much of a predecessor's output it needs to be told — a direct predecessor is depth 1
49
+ * however far apart the two sit in the topological order.
50
+ *
51
+ * @throws {PipelineUnknownStepError} when the spec does not declare `step`.
52
+ */
53
+ ancestry: (step: string) => PipelineStepDepth[]
54
+ /** The names from {@link PipelineSpecModel.ancestry}: what must finish before `step` may start, in order. */
55
+ ancestors: (step: string) => string[]
56
+ /** The declaration of one step, by name. `null` when the spec does not declare it. */
57
+ stepOf: (step: string) => PipelineStepSpec | null
58
+ }
@@ -0,0 +1,202 @@
1
+ import { DEFAULT_STEP_ATTEMPTS } from '../consts.js'
2
+ import { PipelineSpecError, PipelineUnknownStepError } from '../errors.js'
3
+ import type { PipelineSpec, PipelineStepSpec } from '../types.js'
4
+ import type { PipelineSpecModel, PipelineStepDepth } from './pipeline-spec/types.js'
5
+
6
+ export const makePipelineSpecModel = (spec: PipelineSpec): PipelineSpecModel => {
7
+ const fault = (faults: string[], message: string): void => {
8
+ faults.push(message)
9
+ }
10
+
11
+ const validate = (): void => {
12
+ const faults: string[] = []
13
+
14
+ if (spec.alias == null || spec.alias.trim() === '') {
15
+ fault(faults, 'alias is empty')
16
+ }
17
+ if (!Number.isInteger(spec.version) || spec.version < 1) {
18
+ fault(faults, `version must be a positive integer, got ${String(spec.version)}`)
19
+ }
20
+ if (!Array.isArray(spec.steps) || spec.steps.length === 0) {
21
+ fault(faults, 'no steps declared')
22
+ throw new PipelineSpecError(`${spec.alias ?? '?'}: ${faults.join('; ')}`)
23
+ }
24
+
25
+ const seen = new Set<string>()
26
+ for (const step of spec.steps) {
27
+ if (step.step == null || step.step.trim() === '') {
28
+ fault(faults, 'a step has an empty name')
29
+ continue
30
+ }
31
+ if (seen.has(step.step)) {
32
+ fault(faults, `duplicate step "${step.step}"`)
33
+ }
34
+ seen.add(step.step)
35
+ }
36
+
37
+ for (const step of spec.steps) {
38
+ for (const dependency of step.after ?? []) {
39
+ if (dependency === step.step) {
40
+ fault(faults, `step "${step.step}" declares itself in \`after\``)
41
+ } else if (!seen.has(dependency)) {
42
+ fault(faults, `step "${step.step}" waits on unknown step "${dependency}"`)
43
+ }
44
+ }
45
+ const attempts = step.attempts ?? DEFAULT_STEP_ATTEMPTS
46
+ if (!Number.isInteger(attempts) || attempts < 1) {
47
+ fault(faults, `step "${step.step}" declares attempts ${String(step.attempts)}`)
48
+ }
49
+ if (step.nonIdempotent === true && attempts > 1) {
50
+ fault(
51
+ faults,
52
+ `step "${step.step}" is nonIdempotent and declares attempts ${attempts} — an automatic `
53
+ + 'retry is exactly what that flag says must not happen',
54
+ )
55
+ }
56
+ if (step.timeout != null && (!Number.isFinite(step.timeout) || step.timeout <= 0)) {
57
+ fault(faults, `step "${step.step}" declares timeout ${String(step.timeout)}`)
58
+ }
59
+ }
60
+
61
+ // A cycle is reported here rather than left to the sort, so one message carries every fault.
62
+ if (faults.every(entry => !entry.includes('unknown step'))) {
63
+ const cycle = detectCycle()
64
+ if (cycle != null) {
65
+ fault(faults, `cycle: ${cycle.join(' → ')}`)
66
+ }
67
+ }
68
+
69
+ if (faults.length > 0) {
70
+ throw new PipelineSpecError(`${spec.alias}: ${faults.join('; ')}`)
71
+ }
72
+ }
73
+
74
+ const detectCycle = (): string[] | null => {
75
+ const after = new Map(spec.steps.map(step => [step.step, step.after ?? []]))
76
+ const state = new Map<string, 0 | 1 | 2>()
77
+ const path: string[] = []
78
+
79
+ const walk = (name: string): string[] | null => {
80
+ const mark = state.get(name)
81
+ if (mark === 2) return null
82
+ if (mark === 1) return [...path.slice(path.indexOf(name)), name]
83
+
84
+ state.set(name, 1)
85
+ path.push(name)
86
+ for (const dependency of after.get(name) ?? []) {
87
+ const found = walk(dependency)
88
+ if (found != null) return found
89
+ }
90
+ path.pop()
91
+ state.set(name, 2)
92
+
93
+ return null
94
+ }
95
+
96
+ for (const step of spec.steps) {
97
+ const found = walk(step.step)
98
+ if (found != null) return found.reverse()
99
+ }
100
+
101
+ return null
102
+ }
103
+
104
+ const orderSteps = (): string[] => {
105
+ const names = spec.steps.map(step => step.step)
106
+ const known = new Set(names)
107
+ const pending = new Map<string, Set<string>>()
108
+ const dependents = new Map<string, string[]>()
109
+
110
+ for (const step of spec.steps) {
111
+ const dependencies = new Set<string>()
112
+ for (const dependency of step.after ?? []) {
113
+ if (!known.has(dependency)) {
114
+ throw new PipelineSpecError(
115
+ `${spec.alias}: step "${step.step}" waits on unknown step "${dependency}"`,
116
+ )
117
+ }
118
+ dependencies.add(dependency)
119
+ dependents.set(dependency, [...(dependents.get(dependency) ?? []), step.step])
120
+ }
121
+ pending.set(step.step, dependencies)
122
+ }
123
+
124
+ const ordered: string[] = []
125
+ const emitted = new Set<string>()
126
+
127
+ while (ordered.length < names.length) {
128
+ // Declaration order among everything currently ready — never a set's own order.
129
+ const ready = names.filter(name => !emitted.has(name) && (pending.get(name)?.size ?? 0) === 0)
130
+ if (ready.length === 0) {
131
+ const cycle = detectCycle()
132
+ throw new PipelineSpecError(
133
+ `${spec.alias}: cycle: ${(cycle ?? names.filter(name => !emitted.has(name))).join(' → ')}`,
134
+ )
135
+ }
136
+ for (const name of ready) {
137
+ ordered.push(name)
138
+ emitted.add(name)
139
+ for (const dependent of dependents.get(name) ?? []) {
140
+ pending.get(dependent)?.delete(name)
141
+ }
142
+ }
143
+ }
144
+
145
+ return ordered
146
+ }
147
+
148
+ const descendants = (step: string): string[] => {
149
+ if (!spec.steps.some(entry => entry.step === step)) {
150
+ throw new PipelineUnknownStepError(`${spec.alias}:${step}`)
151
+ }
152
+
153
+ const reached = new Set<string>([step])
154
+ let grew = true
155
+ while (grew) {
156
+ grew = false
157
+ for (const entry of spec.steps) {
158
+ if (reached.has(entry.step)) continue
159
+ if ((entry.after ?? []).some(dependency => reached.has(dependency))) {
160
+ reached.add(entry.step)
161
+ grew = true
162
+ }
163
+ }
164
+ }
165
+
166
+ return orderSteps().filter(name => reached.has(name))
167
+ }
168
+
169
+ const ancestry = (step: string): PipelineStepDepth[] => {
170
+ if (!spec.steps.some(entry => entry.step === step)) {
171
+ throw new PipelineUnknownStepError(`${spec.alias}:${step}`)
172
+ }
173
+
174
+ const after = new Map(spec.steps.map(entry => [entry.step, entry.after ?? []]))
175
+ const depths = new Map<string, number>()
176
+ let frontier = [step]
177
+ for (let depth = 1; frontier.length > 0; ++depth) {
178
+ const next: string[] = []
179
+ for (const name of frontier) {
180
+ for (const dependency of after.get(name) ?? []) {
181
+ if (dependency !== step && !depths.has(dependency)) {
182
+ depths.set(dependency, depth)
183
+ next.push(dependency)
184
+ }
185
+ }
186
+ }
187
+ frontier = next
188
+ }
189
+
190
+ return orderSteps()
191
+ .filter(name => depths.has(name))
192
+ .map(name => ({ step: name, depth: depths.get(name)! }))
193
+ }
194
+
195
+ const ancestors = (step: string): string[] =>
196
+ ancestry(step).map(entry => entry.step)
197
+
198
+ const stepOf = (step: string): PipelineStepSpec | null =>
199
+ spec.steps.find(entry => entry.step === step) ?? null
200
+
201
+ return { record: spec, validate, orderSteps, descendants, ancestry, ancestors, stepOf }
202
+ }