@boboddy/sdk 0.4.2 → 0.5.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 (34) hide show
  1. package/dist/client.js +0 -23
  2. package/dist/definitions/advancement-policies/define-advancement-policy.d.ts +30 -5
  3. package/dist/definitions/advancement-policies/index.js +11 -1
  4. package/dist/definitions/pipelines/bindings.d.ts +62 -0
  5. package/dist/definitions/pipelines/builder-helpers.d.ts +36 -94
  6. package/dist/definitions/pipelines/chain-graph.d.ts +15 -14
  7. package/dist/definitions/pipelines/compile-node-definitions.d.ts +27 -0
  8. package/dist/definitions/pipelines/define-default-pipeline-assignment.d.ts +64 -14
  9. package/dist/definitions/pipelines/define-pipeline.d.ts +101 -154
  10. package/dist/definitions/pipelines/index.d.ts +1 -2
  11. package/dist/definitions/pipelines/index.js +485 -585
  12. package/dist/definitions/pipelines/node-input-ctx.d.ts +31 -0
  13. package/dist/definitions/pipelines/pipeline-definitions-client.d.ts +42 -12
  14. package/dist/definitions/pipelines/pipeline-states.d.ts +104 -0
  15. package/dist/definitions/pipelines/work-item-fields.d.ts +98 -0
  16. package/dist/definitions/steps/define-code-step.d.ts +50 -0
  17. package/dist/definitions/steps/define-step.d.ts +40 -8
  18. package/dist/definitions/steps/index.d.ts +1 -0
  19. package/dist/definitions/steps/index.js +41 -24
  20. package/dist/definitions/steps/step-definitions-client.d.ts +47 -11
  21. package/dist/definitions/validation/index.js +15148 -94
  22. package/dist/definitions/validation/validate-definition-specs.d.ts +19 -1
  23. package/dist/generated/index.d.ts +1 -1
  24. package/dist/generated/sdk.gen.d.ts +1 -4
  25. package/dist/generated/types.gen.d.ts +469 -875
  26. package/dist/index.js +525 -585
  27. package/dist/push/collect-definitions.d.ts +40 -1
  28. package/dist/push/index.d.ts +2 -2
  29. package/dist/push/index.js +756 -664
  30. package/dist/step-execution-plane-client.d.ts +9 -4
  31. package/package.json +2 -2
  32. package/dist/definitions/pipelines/builder.d.ts +0 -80
  33. package/dist/definitions/pipelines/fan-out-builder.d.ts +0 -66
  34. package/dist/definitions/pipelines/input-accessor.d.ts +0 -25
@@ -30,7 +30,7 @@ __export(exports_core2, {
30
30
  safeDecode: () => safeDecode,
31
31
  registry: () => registry,
32
32
  regexes: () => exports_regexes,
33
- process: () => process,
33
+ process: () => process2,
34
34
  prettifyError: () => prettifyError,
35
35
  parseAsync: () => parseAsync,
36
36
  parse: () => parse,
@@ -11017,7 +11017,7 @@ function initializeContext(params) {
11017
11017
  external: params?.external ?? undefined
11018
11018
  };
11019
11019
  }
11020
- function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11020
+ function process2(schema, ctx, _params = { path: [], schemaPath: [] }) {
11021
11021
  var _a3;
11022
11022
  const def = schema._zod.def;
11023
11023
  const seen = ctx.seen.get(schema);
@@ -11054,7 +11054,7 @@ function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11054
11054
  if (parent) {
11055
11055
  if (!result.ref)
11056
11056
  result.ref = parent;
11057
- process(parent, ctx, params);
11057
+ process2(parent, ctx, params);
11058
11058
  ctx.seen.get(parent).isParent = true;
11059
11059
  }
11060
11060
  }
@@ -11337,14 +11337,14 @@ function isTransforming(_schema, _ctx) {
11337
11337
  }
11338
11338
  var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
11339
11339
  const ctx = initializeContext({ ...params, processors });
11340
- process(schema, ctx);
11340
+ process2(schema, ctx);
11341
11341
  extractDefs(ctx, schema);
11342
11342
  return finalize(ctx, schema);
11343
11343
  };
11344
11344
  var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
11345
11345
  const { libraryOptions, target } = params ?? {};
11346
11346
  const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
11347
- process(schema, ctx);
11347
+ process2(schema, ctx);
11348
11348
  extractDefs(ctx, schema);
11349
11349
  return finalize(ctx, schema);
11350
11350
  };
@@ -11584,7 +11584,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
11584
11584
  if (typeof maximum === "number")
11585
11585
  json.maxItems = maximum;
11586
11586
  json.type = "array";
11587
- json.items = process(def.element, ctx, {
11587
+ json.items = process2(def.element, ctx, {
11588
11588
  ...params,
11589
11589
  path: [...params.path, "items"]
11590
11590
  });
@@ -11596,7 +11596,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
11596
11596
  json.properties = {};
11597
11597
  const shape = def.shape;
11598
11598
  for (const key in shape) {
11599
- json.properties[key] = process(shape[key], ctx, {
11599
+ json.properties[key] = process2(shape[key], ctx, {
11600
11600
  ...params,
11601
11601
  path: [...params.path, "properties", key]
11602
11602
  });
@@ -11619,7 +11619,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
11619
11619
  if (ctx.io === "output")
11620
11620
  json.additionalProperties = false;
11621
11621
  } else if (def.catchall) {
11622
- json.additionalProperties = process(def.catchall, ctx, {
11622
+ json.additionalProperties = process2(def.catchall, ctx, {
11623
11623
  ...params,
11624
11624
  path: [...params.path, "additionalProperties"]
11625
11625
  });
@@ -11628,7 +11628,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
11628
11628
  var unionProcessor = (schema, ctx, json, params) => {
11629
11629
  const def = schema._zod.def;
11630
11630
  const isExclusive = def.inclusive === false;
11631
- const options = def.options.map((x, i) => process(x, ctx, {
11631
+ const options = def.options.map((x, i) => process2(x, ctx, {
11632
11632
  ...params,
11633
11633
  path: [...params.path, isExclusive ? "oneOf" : "anyOf", i]
11634
11634
  }));
@@ -11640,11 +11640,11 @@ var unionProcessor = (schema, ctx, json, params) => {
11640
11640
  };
11641
11641
  var intersectionProcessor = (schema, ctx, json, params) => {
11642
11642
  const def = schema._zod.def;
11643
- const a = process(def.left, ctx, {
11643
+ const a = process2(def.left, ctx, {
11644
11644
  ...params,
11645
11645
  path: [...params.path, "allOf", 0]
11646
11646
  });
11647
- const b = process(def.right, ctx, {
11647
+ const b = process2(def.right, ctx, {
11648
11648
  ...params,
11649
11649
  path: [...params.path, "allOf", 1]
11650
11650
  });
@@ -11661,11 +11661,11 @@ var tupleProcessor = (schema, ctx, _json, params) => {
11661
11661
  json.type = "array";
11662
11662
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
11663
11663
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
11664
- const prefixItems = def.items.map((x, i) => process(x, ctx, {
11664
+ const prefixItems = def.items.map((x, i) => process2(x, ctx, {
11665
11665
  ...params,
11666
11666
  path: [...params.path, prefixPath, i]
11667
11667
  }));
11668
- const rest = def.rest ? process(def.rest, ctx, {
11668
+ const rest = def.rest ? process2(def.rest, ctx, {
11669
11669
  ...params,
11670
11670
  path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
11671
11671
  }) : null;
@@ -11705,7 +11705,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
11705
11705
  const keyBag = keyType._zod.bag;
11706
11706
  const patterns = keyBag?.patterns;
11707
11707
  if (def.mode === "loose" && patterns && patterns.size > 0) {
11708
- const valueSchema = process(def.valueType, ctx, {
11708
+ const valueSchema = process2(def.valueType, ctx, {
11709
11709
  ...params,
11710
11710
  path: [...params.path, "patternProperties", "*"]
11711
11711
  });
@@ -11715,12 +11715,12 @@ var recordProcessor = (schema, ctx, _json, params) => {
11715
11715
  }
11716
11716
  } else {
11717
11717
  if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
11718
- json.propertyNames = process(def.keyType, ctx, {
11718
+ json.propertyNames = process2(def.keyType, ctx, {
11719
11719
  ...params,
11720
11720
  path: [...params.path, "propertyNames"]
11721
11721
  });
11722
11722
  }
11723
- json.additionalProperties = process(def.valueType, ctx, {
11723
+ json.additionalProperties = process2(def.valueType, ctx, {
11724
11724
  ...params,
11725
11725
  path: [...params.path, "additionalProperties"]
11726
11726
  });
@@ -11735,7 +11735,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
11735
11735
  };
11736
11736
  var nullableProcessor = (schema, ctx, json, params) => {
11737
11737
  const def = schema._zod.def;
11738
- const inner = process(def.innerType, ctx, params);
11738
+ const inner = process2(def.innerType, ctx, params);
11739
11739
  const seen = ctx.seen.get(schema);
11740
11740
  if (ctx.target === "openapi-3.0") {
11741
11741
  seen.ref = def.innerType;
@@ -11746,20 +11746,20 @@ var nullableProcessor = (schema, ctx, json, params) => {
11746
11746
  };
11747
11747
  var nonoptionalProcessor = (schema, ctx, _json, params) => {
11748
11748
  const def = schema._zod.def;
11749
- process(def.innerType, ctx, params);
11749
+ process2(def.innerType, ctx, params);
11750
11750
  const seen = ctx.seen.get(schema);
11751
11751
  seen.ref = def.innerType;
11752
11752
  };
11753
11753
  var defaultProcessor = (schema, ctx, json, params) => {
11754
11754
  const def = schema._zod.def;
11755
- process(def.innerType, ctx, params);
11755
+ process2(def.innerType, ctx, params);
11756
11756
  const seen = ctx.seen.get(schema);
11757
11757
  seen.ref = def.innerType;
11758
11758
  json.default = JSON.parse(JSON.stringify(def.defaultValue));
11759
11759
  };
11760
11760
  var prefaultProcessor = (schema, ctx, json, params) => {
11761
11761
  const def = schema._zod.def;
11762
- process(def.innerType, ctx, params);
11762
+ process2(def.innerType, ctx, params);
11763
11763
  const seen = ctx.seen.get(schema);
11764
11764
  seen.ref = def.innerType;
11765
11765
  if (ctx.io === "input")
@@ -11767,7 +11767,7 @@ var prefaultProcessor = (schema, ctx, json, params) => {
11767
11767
  };
11768
11768
  var catchProcessor = (schema, ctx, json, params) => {
11769
11769
  const def = schema._zod.def;
11770
- process(def.innerType, ctx, params);
11770
+ process2(def.innerType, ctx, params);
11771
11771
  const seen = ctx.seen.get(schema);
11772
11772
  seen.ref = def.innerType;
11773
11773
  let catchValue;
@@ -11782,32 +11782,32 @@ var pipeProcessor = (schema, ctx, _json, params) => {
11782
11782
  const def = schema._zod.def;
11783
11783
  const inIsTransform = def.in._zod.traits.has("$ZodTransform");
11784
11784
  const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
11785
- process(innerType, ctx, params);
11785
+ process2(innerType, ctx, params);
11786
11786
  const seen = ctx.seen.get(schema);
11787
11787
  seen.ref = innerType;
11788
11788
  };
11789
11789
  var readonlyProcessor = (schema, ctx, json, params) => {
11790
11790
  const def = schema._zod.def;
11791
- process(def.innerType, ctx, params);
11791
+ process2(def.innerType, ctx, params);
11792
11792
  const seen = ctx.seen.get(schema);
11793
11793
  seen.ref = def.innerType;
11794
11794
  json.readOnly = true;
11795
11795
  };
11796
11796
  var promiseProcessor = (schema, ctx, _json, params) => {
11797
11797
  const def = schema._zod.def;
11798
- process(def.innerType, ctx, params);
11798
+ process2(def.innerType, ctx, params);
11799
11799
  const seen = ctx.seen.get(schema);
11800
11800
  seen.ref = def.innerType;
11801
11801
  };
11802
11802
  var optionalProcessor = (schema, ctx, _json, params) => {
11803
11803
  const def = schema._zod.def;
11804
- process(def.innerType, ctx, params);
11804
+ process2(def.innerType, ctx, params);
11805
11805
  const seen = ctx.seen.get(schema);
11806
11806
  seen.ref = def.innerType;
11807
11807
  };
11808
11808
  var lazyProcessor = (schema, ctx, _json, params) => {
11809
11809
  const innerType = schema._zod.innerType;
11810
- process(innerType, ctx, params);
11810
+ process2(innerType, ctx, params);
11811
11811
  const seen = ctx.seen.get(schema);
11812
11812
  seen.ref = innerType;
11813
11813
  };
@@ -11859,7 +11859,7 @@ function toJSONSchema(input, params) {
11859
11859
  const defs = {};
11860
11860
  for (const entry of registry2._idmap.entries()) {
11861
11861
  const [_, schema] = entry;
11862
- process(schema, ctx2);
11862
+ process2(schema, ctx2);
11863
11863
  }
11864
11864
  const schemas = {};
11865
11865
  const external = {
@@ -11882,7 +11882,7 @@ function toJSONSchema(input, params) {
11882
11882
  return { schemas };
11883
11883
  }
11884
11884
  const ctx = initializeContext({ ...params, processors: allProcessors });
11885
- process(input, ctx);
11885
+ process2(input, ctx);
11886
11886
  extractDefs(ctx, input);
11887
11887
  return finalize(ctx, input);
11888
11888
  }
@@ -11928,7 +11928,7 @@ class JSONSchemaGenerator {
11928
11928
  });
11929
11929
  }
11930
11930
  process(schema, _params = { path: [], schemaPath: [] }) {
11931
- return process(schema, this.ctx, _params);
11931
+ return process2(schema, this.ctx, _params);
11932
11932
  }
11933
11933
  emit(schema, _params) {
11934
11934
  if (_params) {
@@ -12080,6 +12080,32 @@ ${feature._promptAddition}` : feature._promptAddition;
12080
12080
  };
12081
12081
  return spec;
12082
12082
  }
12083
+ // src/definitions/steps/define-code-step.ts
12084
+ function codeStep(config2) {
12085
+ const spec = {
12086
+ key: config2.key,
12087
+ name: config2.name,
12088
+ description: config2.description ?? null,
12089
+ version: config2.version ?? 1,
12090
+ kind: "code",
12091
+ status: config2.status ?? "active",
12092
+ prompt: null,
12093
+ inputSchemaJson: config2.inputSchema ? toJSONSchema(config2.inputSchema) : null,
12094
+ resultSchemaJson: config2.resultSchema ? toJSONSchema(config2.resultSchema) : null,
12095
+ signalExtractorDefinitions: (config2.signals ?? []).map((signal) => ({
12096
+ key: signal.key ?? signal.sourcePath,
12097
+ sourcePath: signal.sourcePath,
12098
+ type: signal.type,
12099
+ required: signal.required ?? true,
12100
+ availableWhenResultStatusIn: signal.availableWhenResultStatusIn ?? null
12101
+ })),
12102
+ opencodeMcpJson: null,
12103
+ opencodePluginJson: null,
12104
+ healthChecksJson: null,
12105
+ entrypoint: { fn: config2.fn }
12106
+ };
12107
+ return spec;
12108
+ }
12083
12109
  // src/generated/core/bodySerializer.gen.ts
12084
12110
  var jsonBodySerializer = {
12085
12111
  bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value)
@@ -13159,29 +13185,6 @@ class PipelineDefinitions extends HeyApiClient {
13159
13185
  unarchivePipelineDefinition(options) {
13160
13186
  return (options.client ?? this.client).put({ url: "/api/pipeline-definitions/{pipelineDefinitionId}/unarchive", ...options });
13161
13187
  }
13162
- addPipelineStep(options) {
13163
- return (options.client ?? this.client).post({
13164
- url: "/api/pipeline-definitions/{pipelineDefinitionId}/steps",
13165
- ...options,
13166
- headers: {
13167
- "Content-Type": "application/json",
13168
- ...options.headers
13169
- }
13170
- });
13171
- }
13172
- removePipelineStep(options) {
13173
- return (options.client ?? this.client).delete({ url: "/api/pipeline-definitions/{pipelineDefinitionId}/steps/{pipelineStepDefinitionId}", ...options });
13174
- }
13175
- updatePipelineStep(options) {
13176
- return (options.client ?? this.client).put({
13177
- url: "/api/pipeline-definitions/{pipelineDefinitionId}/steps/{pipelineStepDefinitionId}",
13178
- ...options,
13179
- headers: {
13180
- "Content-Type": "application/json",
13181
- ...options.headers
13182
- }
13183
- });
13184
- }
13185
13188
  setPipelineStepAdvancementPolicy(options) {
13186
13189
  return (options.client ?? this.client).put({
13187
13190
  url: "/api/pipeline-definitions/{pipelineDefinitionId}/steps/{pipelineStepDefinitionId}/advancement-policy",
@@ -13710,8 +13713,21 @@ var buildStepDefinitionsClient = (stepDefinitions) => {
13710
13713
  },
13711
13714
  upsertFromSpec: async (projectId, spec, options) => {
13712
13715
  const body = {
13713
- ...spec,
13716
+ key: spec.key,
13717
+ name: spec.name,
13718
+ description: spec.description,
13714
13719
  prompt: spec.prompt ?? "",
13720
+ version: spec.version,
13721
+ kind: spec.kind,
13722
+ entrypointJson: spec.entrypointJson ?? null,
13723
+ executionMode: spec.executionMode,
13724
+ inputSchemaJson: spec.inputSchemaJson,
13725
+ resultSchemaJson: spec.resultSchemaJson,
13726
+ opencodeMcpJson: spec.opencodeMcpJson,
13727
+ opencodePluginJson: spec.opencodePluginJson,
13728
+ healthChecksJson: spec.healthChecksJson,
13729
+ status: spec.status,
13730
+ signalExtractorDefinitions: spec.signalExtractorDefinitions,
13715
13731
  projectId
13716
13732
  };
13717
13733
  const result = await stepDefinitions.upsertStepDefinition({
@@ -16168,10 +16184,19 @@ function any2(conditions, outcome) {
16168
16184
  return { _tag: "rule", mode: "any", conditions, outcome };
16169
16185
  }
16170
16186
  function when(signal2, operator, value, outcome) {
16187
+ const condition = {
16188
+ _tag: "signal",
16189
+ signal: signal2,
16190
+ operator,
16191
+ value
16192
+ };
16193
+ if (outcome === undefined) {
16194
+ return condition;
16195
+ }
16171
16196
  return {
16172
16197
  _tag: "rule",
16173
16198
  mode: "all",
16174
- conditions: [{ _tag: "signal", signal: signal2, operator, value }],
16199
+ conditions: [condition],
16175
16200
  outcome
16176
16201
  };
16177
16202
  }
@@ -16347,402 +16372,9 @@ function extractInlineStepSignalsListDefinitions(policy) {
16347
16372
  return [...byKey.values()];
16348
16373
  }
16349
16374
 
16350
- // src/definitions/pipelines/chain-graph.ts
16351
- function tryOrderChainNodeDefinitions(nodeDefinitions, dependencyEdges) {
16352
- const nodesByKey = new Map;
16353
- for (const nodeDefinition of nodeDefinitions) {
16354
- nodesByKey.set(nodeDefinition.nodeKey, nodeDefinition);
16355
- }
16356
- const outgoing = new Map;
16357
- const incomingCount = new Map;
16358
- for (const nodeDefinition of nodeDefinitions) {
16359
- incomingCount.set(nodeDefinition.nodeKey, 0);
16360
- }
16361
- for (const edge of dependencyEdges) {
16362
- if (!nodesByKey.has(edge.fromNodeKey) || !nodesByKey.has(edge.toNodeKey)) {
16363
- return null;
16364
- }
16365
- if (outgoing.has(edge.fromNodeKey))
16366
- return null;
16367
- outgoing.set(edge.fromNodeKey, edge.toNodeKey);
16368
- incomingCount.set(edge.toNodeKey, (incomingCount.get(edge.toNodeKey) ?? 0) + 1);
16369
- }
16370
- for (const count of incomingCount.values()) {
16371
- if (count > 1)
16372
- return null;
16373
- }
16374
- if (nodeDefinitions.length === 0)
16375
- return [];
16376
- const roots = nodeDefinitions.filter((nodeDefinition) => (incomingCount.get(nodeDefinition.nodeKey) ?? 0) === 0);
16377
- if (roots.length !== 1)
16378
- return null;
16379
- const rootNode = roots[0];
16380
- if (!rootNode)
16381
- return null;
16382
- const ordered = [];
16383
- const visited = new Set;
16384
- let currentKey = rootNode.nodeKey;
16385
- while (currentKey !== undefined) {
16386
- if (visited.has(currentKey))
16387
- return null;
16388
- visited.add(currentKey);
16389
- const currentNode = nodesByKey.get(currentKey);
16390
- if (!currentNode)
16391
- return null;
16392
- ordered.push(currentNode);
16393
- currentKey = outgoing.get(currentKey);
16394
- }
16395
- if (ordered.length !== nodeDefinitions.length)
16396
- return null;
16397
- return ordered;
16398
- }
16399
- function buildChainDependencyEdges(orderedNodes) {
16400
- const dependencyEdges = [];
16401
- for (let index = 0;index < orderedNodes.length - 1; index += 1) {
16402
- const from = orderedNodes[index];
16403
- const to = orderedNodes[index + 1];
16404
- if (!from || !to)
16405
- continue;
16406
- dependencyEdges.push({ fromNodeKey: from.nodeKey, toNodeKey: to.nodeKey });
16407
- }
16408
- return dependencyEdges;
16409
- }
16410
-
16411
- // src/definitions/pipelines/define-pipeline.ts
16412
- function serializeBinding(binding) {
16413
- if (binding.source === "pipeline_input") {
16414
- return { source: "pipeline_input", path: binding.path };
16415
- }
16416
- if (binding.source === "work_item") {
16417
- return { source: "work_item", field: binding.field };
16418
- }
16419
- if (binding.source === "step_signal") {
16420
- return {
16421
- source: "step_signal",
16422
- stepKey: binding.step.key,
16423
- signalKey: binding.signalKey
16424
- };
16425
- }
16426
- if (binding.source === "literal") {
16427
- return { source: "literal", value: binding.value };
16428
- }
16429
- if (binding.source === "signals_list") {
16430
- return { source: "signals_list", stepKey: binding.fanOutStep.key };
16431
- }
16432
- if (binding.source === "fan_out_item") {
16433
- return { source: "fan_out_item" };
16434
- }
16435
- return { source: "step_output", stepKey: binding.step.key };
16436
- }
16437
- var isFanOutConfig = (node) => ("nodeType" in node) && node.nodeType === "fanOut";
16438
- var isCohortGateConfig = (node) => ("nodeType" in node) && node.nodeType === "cohortGate";
16439
- function serializeInputBindings(input, pipelineInputBindings) {
16440
- const autoBindings = {
16441
- workItemTitle: { source: "work_item", field: "title" },
16442
- workItemDescription: { source: "work_item", field: "description" }
16443
- };
16444
- const pipelineBindings = {};
16445
- for (const [key, binding] of Object.entries(pipelineInputBindings ?? {})) {
16446
- pipelineBindings[key] = serializeBinding(binding);
16447
- }
16448
- const explicitBindings = Object.fromEntries(Object.entries(input ?? {}).filter((entry) => entry[1] !== undefined).map(([key, binding]) => [key, serializeBinding(binding)]));
16449
- return { ...autoBindings, ...pipelineBindings, ...explicitBindings };
16450
- }
16451
- function buildPipelineSpec(config2) {
16452
- const nodes = config2.nodes;
16453
- const stepDefMap = new Map;
16454
- const registerStepDef = (step) => {
16455
- const mapKey = `${step.key}@v${String(step.version)}`;
16456
- if (!stepDefMap.has(mapKey)) {
16457
- stepDefMap.set(mapKey, step);
16458
- }
16459
- };
16460
- for (const node of nodes) {
16461
- if (isFanOutConfig(node)) {
16462
- registerStepDef(node.fanOutStep);
16463
- } else if (!isCohortGateConfig(node)) {
16464
- registerStepDef(node.step);
16465
- }
16466
- }
16467
- let inputSchemaJson = null;
16468
- if (config2.input) {
16469
- try {
16470
- inputSchemaJson = exports_external.toJSONSchema(config2.input);
16471
- } catch {
16472
- inputSchemaJson = null;
16473
- }
16474
- }
16475
- const nodeDefinitions = nodes.map((node) => {
16476
- if (isCohortGateConfig(node)) {
16477
- const serializedPolicy = serializeCohortAdvancementPolicy(node.advanceAll);
16478
- const inlineStepSignalsListDefinitions = extractInlineStepSignalsListDefinitions(node.advanceAll);
16479
- return {
16480
- nodeKey: node.nodeKey,
16481
- kind: "cohortGate",
16482
- advanceAllPolicyDefinition: serializedPolicy,
16483
- stepSignalsListDefinitions: [
16484
- ...inlineStepSignalsListDefinitions,
16485
- ...node.stepSignalsListDefinitions ?? []
16486
- ]
16487
- };
16488
- }
16489
- if (isFanOutConfig(node)) {
16490
- return {
16491
- nodeKey: node.fanOutStep.key,
16492
- kind: "fanOut",
16493
- stepKey: node.fanOutStep.key,
16494
- stepName: node.fanOutStep.name,
16495
- stepDescription: node.fanOutStep.description,
16496
- inputBindingsJson: serializeInputBindings(node.input, config2.pipelineInputBindings),
16497
- timeoutSeconds: node.timeout ?? null,
16498
- overSignalKey: node.overSignalKey,
16499
- advanceEachPolicyDefinition: serializeCohortAdvancementPolicy(node.advanceEach)
16500
- };
16501
- }
16502
- return {
16503
- nodeKey: node.step.key,
16504
- kind: "step",
16505
- stepKey: node.step.key,
16506
- stepName: node.step.name,
16507
- stepDescription: node.step.description,
16508
- inputBindingsJson: serializeInputBindings(node.input, config2.pipelineInputBindings),
16509
- timeoutSeconds: node.timeout ?? null,
16510
- advancementPolicyDefinition: serializeAdvancementPolicy(node.advancement),
16511
- computedSignalDefinitions: extractInlineComputedSignals(node.advancement)
16512
- };
16513
- });
16514
- const dependencyEdges = buildChainDependencyEdges(nodeDefinitions);
16515
- return {
16516
- key: config2.key,
16517
- name: config2.name,
16518
- description: config2.description ?? null,
16519
- version: config2.version ?? 1,
16520
- status: config2.status ?? "active",
16521
- inputSchemaJson,
16522
- _stepDefinitions: [...stepDefMap.values()],
16523
- nodeDefinitions,
16524
- dependencyEdges
16525
- };
16526
- }
16527
- // src/definitions/advancement-policies/fluent-rules.ts
16528
- var LEAF_BRAND = Symbol.for("boboddy.fluentRule.leaf");
16529
- var GROUP_BRAND = Symbol.for("boboddy.fluentRule.group");
16530
- var SIGNAL_KEY = Symbol.for("boboddy.fluentRule.signalKey");
16531
- function createSignalRef(signal2) {
16532
- const leaf = (operator, value) => {
16533
- const condition = {
16534
- _tag: "signal",
16535
- signal: signal2,
16536
- operator,
16537
- value
16538
- };
16539
- return {
16540
- [LEAF_BRAND]: condition,
16541
- then(outcome) {
16542
- return {
16543
- _tag: "rule",
16544
- mode: "all",
16545
- conditions: [condition],
16546
- outcome
16547
- };
16548
- }
16549
- };
16550
- };
16551
- return {
16552
- eq: (v) => leaf("equal", v),
16553
- ne: (v) => leaf("notEqual", v),
16554
- gt: (v) => leaf("greaterThan", v),
16555
- gte: (v) => leaf("greaterThanInclusive", v),
16556
- lt: (v) => leaf("lessThan", v),
16557
- lte: (v) => leaf("lessThanInclusive", v),
16558
- in: (vs) => leaf("in", vs),
16559
- notIn: (vs) => leaf("notIn", vs),
16560
- contains: (v) => leaf("contains", v),
16561
- doesNotContain: (v) => leaf("doesNotContain", v)
16562
- };
16563
- }
16564
- function extractCondition(ref) {
16565
- if (LEAF_BRAND in ref)
16566
- return ref[LEAF_BRAND];
16567
- const group = ref[GROUP_BRAND];
16568
- return group.mode === "all" ? { _tag: "all", conditions: group.conditions } : { _tag: "any", conditions: group.conditions };
16569
- }
16570
- function createGroup(mode, refs) {
16571
- const conditions = refs.map(extractCondition);
16572
- return {
16573
- [GROUP_BRAND]: { mode, conditions },
16574
- then(outcome) {
16575
- return { _tag: "rule", mode, conditions, outcome };
16576
- }
16577
- };
16578
- }
16579
- function resolveComputedArg(arg) {
16580
- return arg[SIGNAL_KEY];
16581
- }
16582
- function makeKeyedSignalRef(key) {
16583
- const ref = createSignalRef(key);
16584
- return Object.assign(ref, {
16585
- [SIGNAL_KEY]: key
16586
- });
16587
- }
16588
- function makeAdvanceCtx() {
16589
- const wrapComputed = (token) => createSignalRef(token);
16590
- return {
16591
- signal: (key) => makeKeyedSignalRef(key),
16592
- stepSignals: new Proxy({}, {
16593
- get(_, key) {
16594
- if (typeof key === "string")
16595
- return makeKeyedSignalRef(key);
16596
- return;
16597
- }
16598
- }),
16599
- avg: (...args) => wrapComputed(Computed.average(args.map(resolveComputedArg))),
16600
- weightedAvg: (...args) => wrapComputed(Computed.weightedAverage(args.map(resolveComputedArg))),
16601
- sum: (...args) => wrapComputed(Computed.sum(args.map(resolveComputedArg))),
16602
- min: (...args) => wrapComputed(Computed.min(args.map(resolveComputedArg))),
16603
- max: (...args) => wrapComputed(Computed.max(args.map(resolveComputedArg))),
16604
- count: (...args) => wrapComputed(Computed.count(args.map(resolveComputedArg))),
16605
- booleanAny: (...args) => wrapComputed(Computed.booleanAny(args.map(resolveComputedArg))),
16606
- booleanAll: (...args) => wrapComputed(Computed.booleanAll(args.map(resolveComputedArg))),
16607
- all: (...refs) => createGroup("all", refs),
16608
- any: (...refs) => createGroup("any", refs),
16609
- route: (pipelineKey, inputJson) => inputJson !== undefined ? { outcome: "route", pipelineKey, inputJson } : { outcome: "route", pipelineKey }
16610
- };
16611
- }
16612
-
16613
- // src/definitions/pipelines/input-accessor.ts
16614
- var ACCESSOR_BRAND = Symbol.for("boboddy.inputAccessor.brand");
16615
- var ACCESSOR_PATH = Symbol.for("boboddy.inputAccessor.path");
16616
- function createInputAccessor(_schema) {
16617
- return createProxy([]);
16618
- }
16619
- function createProxy(path) {
16620
- const pathStr = path.join(".");
16621
- const target = Object.freeze({});
16622
- return new Proxy(target, {
16623
- get(_t, prop) {
16624
- if (prop === ACCESSOR_BRAND)
16625
- return true;
16626
- if (prop === ACCESSOR_PATH)
16627
- return pathStr;
16628
- if (prop === "toJSON") {
16629
- return () => ({
16630
- source: "pipeline_input",
16631
- path: pathStr
16632
- });
16633
- }
16634
- if (prop === Symbol.toPrimitive) {
16635
- return () => {
16636
- throw new Error(`Pipeline input accessor at path "${pathStr || "<root>"}" cannot be coerced to a primitive. ` + `Pass it to a step input field instead of using it in a string/number expression.`);
16637
- };
16638
- }
16639
- if (typeof prop === "symbol")
16640
- return;
16641
- return createProxy([...path, prop]);
16642
- },
16643
- has(_t, prop) {
16644
- return prop === ACCESSOR_BRAND || prop === ACCESSOR_PATH;
16645
- },
16646
- ownKeys() {
16647
- throw new Error(`Pipeline input accessor at path "${pathStr || "<root>"}" cannot be enumerated. ` + `Drill into specific fields instead of spreading the input.`);
16648
- },
16649
- set() {
16650
- throw new Error(`Pipeline input accessor at path "${pathStr || "<root>"}" is read-only.`);
16651
- }
16652
- });
16653
- }
16654
- function isInputAccessor(value) {
16655
- return typeof value === "object" && value !== null && value[ACCESSOR_BRAND] === true;
16656
- }
16657
- function materializeAccessor(accessor) {
16658
- return {
16659
- source: "pipeline_input",
16660
- path: accessor[ACCESSOR_PATH]
16661
- };
16662
- }
16663
-
16664
- // src/definitions/pipelines/builder-helpers.ts
16665
- var WORK_ITEM_ACCESSOR = Object.freeze({
16666
- title: Object.freeze({ source: "work_item", field: "title" }),
16667
- description: Object.freeze({
16668
- source: "work_item",
16669
- field: "description"
16670
- }),
16671
- field: (fieldName) => Object.freeze({ source: "work_item", field: `fields.${fieldName}` })
16672
- });
16673
- var WORK_ITEM_FIELD_BINDINGS = {
16674
- workItemTitle: { source: "work_item", field: "title" },
16675
- workItemDescription: { source: "work_item", field: "description" }
16676
- };
16677
- function makeStepInputCtx(inputSchema) {
16678
- const baseAccessor = createInputAccessor(inputSchema);
16679
- const input = new Proxy(baseAccessor, {
16680
- get(target, prop) {
16681
- if (typeof prop === "string" && prop in WORK_ITEM_FIELD_BINDINGS) {
16682
- return WORK_ITEM_FIELD_BINDINGS[prop];
16683
- }
16684
- return target[prop];
16685
- }
16686
- });
16687
- return {
16688
- input,
16689
- signal(step, key) {
16690
- return { source: "step_signal", step, signalKey: key };
16691
- },
16692
- output(step) {
16693
- return { source: "step_output", step };
16694
- },
16695
- literal: literal2,
16696
- signalsList(fanOutStep) {
16697
- return { source: "signals_list", fanOutStep };
16698
- }
16699
- };
16700
- }
16701
- function literal2(value) {
16702
- return { source: "literal", value };
16703
- }
16704
- function normalizeInputMapping(mapping) {
16705
- if (!mapping)
16706
- return;
16707
- const out = {};
16708
- for (const [key, value] of Object.entries(mapping)) {
16709
- if (value === undefined)
16710
- continue;
16711
- out[key] = isInputAccessor(value) ? materializeAccessor(value) : value;
16712
- }
16713
- return out;
16714
- }
16715
- function resolveAdditionalStepInputBindings(label, definition) {
16716
- if (!definition) {
16717
- return {};
16718
- }
16719
- const raw = definition.bindings({
16720
- workItemField: (fieldName) => ({
16721
- source: "work_item",
16722
- field: `fields.${fieldName}`
16723
- }),
16724
- literal: literal2
16725
- });
16726
- if (definition.schema instanceof exports_external.ZodObject) {
16727
- const validKeys = new Set(Object.keys(definition.schema.shape));
16728
- const unknown2 = Object.keys(raw).filter((key) => !validKeys.has(key));
16729
- if (unknown2.length > 0) {
16730
- throw new Error(`${label}.bindings returned key${unknown2.length > 1 ? "s" : ""} not in schema: ${unknown2.map((key) => `"${key}"`).join(", ")}`);
16731
- }
16732
- }
16733
- return normalizeInputMapping(raw) ?? {};
16734
- }
16735
- function mergeStepBindings(pipelineBindings, explicitBindings) {
16736
- const merged = {
16737
- ...pipelineBindings,
16738
- ...explicitBindings ?? {}
16739
- };
16740
- return Object.keys(merged).length > 0 ? merged : undefined;
16741
- }
16742
-
16743
16375
  // src/definitions/advancement-policies/cohort-fluent-rules.ts
16744
- var LEAF_BRAND2 = Symbol("boboddy.cohortRule.leaf");
16745
- var GROUP_BRAND2 = Symbol("boboddy.cohortRule.group");
16376
+ var LEAF_BRAND = Symbol("boboddy.cohortRule.leaf");
16377
+ var GROUP_BRAND = Symbol("boboddy.cohortRule.group");
16746
16378
  function createCohortSignalRef(signal2) {
16747
16379
  const leaf = (operator, value) => {
16748
16380
  const condition = {
@@ -16752,7 +16384,7 @@ function createCohortSignalRef(signal2) {
16752
16384
  value
16753
16385
  };
16754
16386
  return {
16755
- [LEAF_BRAND2]: condition,
16387
+ [LEAF_BRAND]: condition,
16756
16388
  then(outcome, paramsJson) {
16757
16389
  return {
16758
16390
  _tag: "rule",
@@ -16779,7 +16411,7 @@ function createCohortSignalRef(signal2) {
16779
16411
  }
16780
16412
  function createCohortLeafFromCondition(condition) {
16781
16413
  return {
16782
- [LEAF_BRAND2]: condition,
16414
+ [LEAF_BRAND]: condition,
16783
16415
  then(outcome, paramsJson) {
16784
16416
  return {
16785
16417
  _tag: "rule",
@@ -16792,15 +16424,15 @@ function createCohortLeafFromCondition(condition) {
16792
16424
  };
16793
16425
  }
16794
16426
  function extractCohortCondition(ref) {
16795
- if (LEAF_BRAND2 in ref)
16796
- return ref[LEAF_BRAND2];
16797
- const group = ref[GROUP_BRAND2];
16427
+ if (LEAF_BRAND in ref)
16428
+ return ref[LEAF_BRAND];
16429
+ const group = ref[GROUP_BRAND];
16798
16430
  return group.mode === "all" ? { _tag: "all", conditions: group.conditions } : { _tag: "any", conditions: group.conditions };
16799
16431
  }
16800
16432
  function createCohortGroup(mode, refs) {
16801
16433
  const conditions = refs.map(extractCohortCondition);
16802
16434
  return {
16803
- [GROUP_BRAND2]: { mode, conditions },
16435
+ [GROUP_BRAND]: { mode, conditions },
16804
16436
  then(outcome, paramsJson) {
16805
16437
  return {
16806
16438
  _tag: "rule",
@@ -16905,138 +16537,461 @@ function makeAdvanceAllCtx() {
16905
16537
  };
16906
16538
  }
16907
16539
 
16908
- // src/definitions/pipelines/fan-out-builder.ts
16909
- function beginFanOut(inputSchema, meta3, nodes, pipelineInputBindings, pipelineStepInputBindings, step, config2) {
16910
- const baseCtx = makeStepInputCtx(inputSchema);
16911
- const ctx = {
16912
- ...baseCtx,
16540
+ // src/definitions/pipelines/bindings.ts
16541
+ function serializeBinding(binding) {
16542
+ if (binding.source === "pipeline_input") {
16543
+ return { source: "pipeline_input", path: binding.path };
16544
+ }
16545
+ if (binding.source === "work_item") {
16546
+ return { source: "work_item", field: binding.field };
16547
+ }
16548
+ if (binding.source === "step_signal") {
16549
+ return {
16550
+ source: "step_signal",
16551
+ stepKey: binding.nodeKey,
16552
+ signalKey: binding.signalKey
16553
+ };
16554
+ }
16555
+ if (binding.source === "literal") {
16556
+ return { source: "literal", value: binding.value };
16557
+ }
16558
+ if (binding.source === "signals_list") {
16559
+ return { source: "signals_list", stepKey: binding.nodeKey };
16560
+ }
16561
+ if (binding.source === "fan_out_item") {
16562
+ return { source: "fan_out_item" };
16563
+ }
16564
+ return { source: "step_output", stepKey: binding.nodeKey };
16565
+ }
16566
+ function serializeInputBindings(input) {
16567
+ const autoBindings = {
16568
+ workItemTitle: { source: "work_item", field: "title" },
16569
+ workItemDescription: { source: "work_item", field: "description" }
16570
+ };
16571
+ const explicitBindings = Object.fromEntries(Object.entries(input).filter((entry) => entry[1] !== undefined).map(([key, binding]) => [key, serializeBinding(binding)]));
16572
+ return { ...autoBindings, ...explicitBindings };
16573
+ }
16574
+
16575
+ // src/definitions/pipelines/work-item-fields.ts
16576
+ var WORK_ITEM_TOP_LEVEL_FIELDS = [
16577
+ "id",
16578
+ "projectId",
16579
+ "platform",
16580
+ "platformId",
16581
+ "platformKey",
16582
+ "url",
16583
+ "title",
16584
+ "description",
16585
+ "sourceCreatedAt",
16586
+ "sourceUpdatedAt",
16587
+ "createdByUserId",
16588
+ "parentWorkItemId",
16589
+ "createdAt",
16590
+ "updatedAt"
16591
+ ];
16592
+ var WORK_ITEM_FIELDS_PATH_PREFIX = "fields.";
16593
+ function resolveWorkItemFieldPath(record2, path) {
16594
+ if (typeof record2 !== "object" || record2 === null)
16595
+ return;
16596
+ const asRecord = record2;
16597
+ if (path.startsWith(WORK_ITEM_FIELDS_PATH_PREFIX)) {
16598
+ const fieldName = path.slice(WORK_ITEM_FIELDS_PATH_PREFIX.length);
16599
+ const fields = asRecord["fields"];
16600
+ if (typeof fields !== "object" || fields === null)
16601
+ return;
16602
+ return fields[fieldName];
16603
+ }
16604
+ return asRecord[path];
16605
+ }
16606
+
16607
+ // src/definitions/pipelines/builder-helpers.ts
16608
+ var WORK_ITEM_ACCESSOR = Object.freeze({
16609
+ ...Object.fromEntries(WORK_ITEM_TOP_LEVEL_FIELDS.map((field) => [
16610
+ field,
16611
+ Object.freeze({ source: "work_item", field })
16612
+ ])),
16613
+ field: (fieldName) => Object.freeze({
16614
+ source: "work_item",
16615
+ field: `${WORK_ITEM_FIELDS_PATH_PREFIX}${fieldName}`
16616
+ })
16617
+ });
16618
+ function literal2(value) {
16619
+ return { source: "literal", value };
16620
+ }
16621
+
16622
+ // src/definitions/pipelines/node-input-ctx.ts
16623
+ function makeBaseNodeInputCtx() {
16624
+ return {
16625
+ pipelineInput: (path) => ({ source: "pipeline_input", path }),
16626
+ workItem: WORK_ITEM_ACCESSOR,
16627
+ signal: (nodeKey, signalKey) => ({
16628
+ source: "step_signal",
16629
+ nodeKey,
16630
+ signalKey
16631
+ }),
16632
+ output: (nodeKey) => ({ source: "step_output", nodeKey }),
16633
+ signalsList: (nodeKey) => ({ source: "signals_list", nodeKey }),
16634
+ literal: literal2
16635
+ };
16636
+ }
16637
+ function makeNodeInputCtx() {
16638
+ return makeBaseNodeInputCtx();
16639
+ }
16640
+ function makeFanOutNodeInputCtx() {
16641
+ return {
16642
+ ...makeBaseNodeInputCtx(),
16913
16643
  item: { source: "fan_out_item" }
16914
16644
  };
16915
- const rawInput = config2.input ? config2.input(ctx) : {};
16916
- const input = mergeStepBindings(pipelineStepInputBindings, normalizeInputMapping(rawInput));
16917
- const fanOutNodeConfig = {
16918
- nodeType: "fanOut",
16919
- fanOutStep: step,
16920
- overSignalKey: config2.over,
16921
- ...input ? { input } : {},
16922
- ...config2.timeout !== undefined ? { timeout: config2.timeout } : {}
16923
- };
16924
- const advanceEachCtx = makeAdvanceEachCtx();
16925
- const advanceEachResult = config2.advance(advanceEachCtx);
16645
+ }
16646
+
16647
+ // src/definitions/pipelines/compile-node-definitions.ts
16648
+ function assertTargetExists(ctx, fromKey, toKey) {
16649
+ if (!ctx.stateKeys.has(toKey)) {
16650
+ throw new Error(`Pipeline "${ctx.pipelineKey}": state "${fromKey}" targets unknown state "${toKey}"`);
16651
+ }
16652
+ }
16653
+ function overSignalKeyOf(over) {
16654
+ const lastDot = over.lastIndexOf(".");
16655
+ return lastDot === -1 ? over : over.slice(lastDot + 1);
16656
+ }
16657
+ function compileStepState(stateKey, state, ctx) {
16658
+ ctx.registerStep(state.step);
16659
+ const inputCtx = makeNodeInputCtx();
16660
+ const rawInput = state.input ? state.input(inputCtx) : {};
16661
+ const inputBindingsJson = serializeInputBindings(rawInput);
16662
+ const routeTarget = typeof state.next === "object" ? state.next : null;
16663
+ const defaultOutcome = routeTarget ? {
16664
+ outcome: "route",
16665
+ pipelineKey: routeTarget.routeToPipeline,
16666
+ ...routeTarget.input ? { inputJson: routeTarget.input } : {}
16667
+ } : "continue";
16668
+ const rules = state.blockWhen ? [{ _tag: "rule", mode: "all", conditions: [state.blockWhen], outcome: "block" }] : [];
16669
+ const policy = { defaultOutcome, rules };
16670
+ const nodeDefinitions = [
16671
+ {
16672
+ nodeKey: stateKey,
16673
+ kind: "step",
16674
+ stepKey: state.step.key,
16675
+ stepName: state.step.name,
16676
+ stepDescription: state.step.description,
16677
+ inputBindingsJson,
16678
+ timeoutSeconds: state.timeout ?? null,
16679
+ advancementPolicyDefinition: serializeAdvancementPolicy(policy),
16680
+ computedSignalDefinitions: extractInlineComputedSignals(policy)
16681
+ }
16682
+ ];
16683
+ const edges = [];
16684
+ if (!routeTarget) {
16685
+ const nextKey = state.next;
16686
+ assertTargetExists(ctx, stateKey, nextKey);
16687
+ edges.push({ fromNodeKey: stateKey, toNodeKey: nextKey });
16688
+ }
16689
+ return { nodeDefinitions, edges };
16690
+ }
16691
+ function compileChoiceState(stateKey, state, ctx) {
16692
+ const stateChoices = state.choices ?? [];
16693
+ if (stateChoices.length === 0 && !state.default) {
16694
+ throw new Error(`Pipeline "${ctx.pipelineKey}": choice state "${stateKey}" requires at least one entry in choices or a default target`);
16695
+ }
16696
+ const choices = stateChoices.map((choiceCase) => {
16697
+ assertTargetExists(ctx, stateKey, choiceCase.next);
16698
+ return {
16699
+ conditionJson: serializeCondition(choiceCase.when),
16700
+ targetNodeKey: choiceCase.next
16701
+ };
16702
+ });
16703
+ if (state.default)
16704
+ assertTargetExists(ctx, stateKey, state.default);
16705
+ const edges = stateChoices.map((choiceCase) => ({
16706
+ fromNodeKey: stateKey,
16707
+ toNodeKey: choiceCase.next,
16708
+ discriminantJson: { conditionSummary: serializeCondition(choiceCase.when) }
16709
+ }));
16710
+ if (state.default) {
16711
+ edges.push({
16712
+ fromNodeKey: stateKey,
16713
+ toNodeKey: state.default,
16714
+ discriminantJson: { default: true }
16715
+ });
16716
+ }
16717
+ return {
16718
+ nodeDefinitions: [
16719
+ { nodeKey: stateKey, kind: "choice", choices, default: state.default ?? null }
16720
+ ],
16721
+ edges
16722
+ };
16723
+ }
16724
+ function compileFanOutState(stateKey, state, ctx) {
16725
+ ctx.registerStep(state.step);
16726
+ const inputCtx = makeFanOutNodeInputCtx();
16727
+ const rawInput = state.input ? state.input(inputCtx) : {};
16728
+ const inputBindingsJson = serializeInputBindings(rawInput);
16729
+ const advanceEachResult = state.advanceEach(makeAdvanceEachCtx());
16926
16730
  const advanceEachPolicy = {
16927
16731
  default: advanceEachResult.default,
16928
16732
  ...advanceEachResult.rules !== undefined ? { rules: advanceEachResult.rules } : {}
16929
16733
  };
16930
- fanOutNodeConfig.advanceEach = advanceEachPolicy;
16931
- const cohortGateNodeConfig = {
16932
- nodeType: "cohortGate",
16933
- nodeKey: `${step.key}__cohortGate`
16934
- };
16935
- const advanceAllCtx = makeAdvanceAllCtx();
16936
- const advanceAllResult = config2.advanceAll(advanceAllCtx);
16734
+ const advanceAllResult = state.advanceAll(makeAdvanceAllCtx());
16937
16735
  const advanceAllPolicy = {
16938
16736
  default: advanceAllResult.default,
16939
16737
  ...advanceAllResult.rules !== undefined ? { rules: advanceAllResult.rules } : {}
16940
16738
  };
16941
- cohortGateNodeConfig.advanceAll = advanceAllPolicy;
16942
- nodes.push(fanOutNodeConfig, cohortGateNodeConfig);
16943
- return new PipelineStepBuilder(inputSchema, meta3, nodes, pipelineInputBindings, pipelineStepInputBindings);
16944
- }
16945
-
16946
- // src/definitions/pipelines/builder.ts
16947
- function pushStep(inputSchema, meta3, nodes, pipelineInputBindings, pipelineStepInputBindings, step, rawOptions) {
16948
- const options = rawOptions;
16949
- const ctx = makeStepInputCtx(inputSchema);
16950
- const rawInput = options.input ? options.input(ctx) : {};
16951
- const input = mergeStepBindings(pipelineStepInputBindings, normalizeInputMapping(rawInput));
16952
- const stepConfig = { step, input };
16953
- if (options.timeout !== undefined)
16954
- stepConfig.timeout = options.timeout;
16955
- const advanceCtx = makeAdvanceCtx();
16956
- const result = options.advance(advanceCtx);
16957
- const policy = {
16958
- defaultOutcome: result.default,
16959
- ...result.rules !== undefined ? { rules: result.rules } : {}
16960
- };
16961
- stepConfig.advancement = policy;
16962
- nodes.push(stepConfig);
16963
- return new PipelineStepBuilder(inputSchema, meta3, nodes, pipelineInputBindings, pipelineStepInputBindings);
16964
- }
16965
-
16966
- class PipelineStepBuilder {
16967
- inputSchema;
16968
- meta;
16969
- nodes;
16970
- pipelineInputBindings;
16971
- pipelineStepInputBindings;
16972
- constructor(inputSchema, meta3, nodes, pipelineInputBindings = {}, pipelineStepInputBindings = {}) {
16973
- this.inputSchema = inputSchema;
16974
- this.meta = meta3;
16975
- this.nodes = nodes;
16976
- this.pipelineInputBindings = pipelineInputBindings;
16977
- this.pipelineStepInputBindings = pipelineStepInputBindings;
16978
- }
16979
- step(step, options) {
16980
- return pushStep(this.inputSchema, this.meta, this.nodes, this.pipelineInputBindings, this.pipelineStepInputBindings, step, options);
16981
- }
16982
- fanOutStep(step, config2) {
16983
- return beginFanOut(this.inputSchema, this.meta, this.nodes, this.pipelineInputBindings, this.pipelineStepInputBindings, step, config2);
16984
- }
16985
- build() {
16986
- const config2 = {
16987
- key: this.meta.key,
16988
- name: this.meta.name,
16989
- description: this.meta.description,
16990
- version: this.meta.version,
16991
- status: this.meta.status,
16992
- input: this.inputSchema,
16993
- nodes: this.nodes,
16994
- pipelineInputBindings: this.pipelineInputBindings
16739
+ const gateKey = `${stateKey}__cohortGate`;
16740
+ if (ctx.stateKeys.has(gateKey)) {
16741
+ throw new Error(`Pipeline "${ctx.pipelineKey}": synthesized cohortGate key "${gateKey}" collides with an author-declared state \u2014 rename state "${stateKey}"`);
16742
+ }
16743
+ assertTargetExists(ctx, stateKey, state.next);
16744
+ return {
16745
+ nodeDefinitions: [
16746
+ {
16747
+ nodeKey: stateKey,
16748
+ kind: "fanOut",
16749
+ stepKey: state.step.key,
16750
+ stepName: state.step.name,
16751
+ stepDescription: state.step.description,
16752
+ inputBindingsJson,
16753
+ timeoutSeconds: state.timeout ?? null,
16754
+ overSignalKey: overSignalKeyOf(state.over),
16755
+ advanceEachPolicyDefinition: serializeCohortAdvancementPolicy(advanceEachPolicy),
16756
+ maxConcurrency: state.maxConcurrency ?? null
16757
+ },
16758
+ {
16759
+ nodeKey: gateKey,
16760
+ kind: "cohortGate",
16761
+ advanceAllPolicyDefinition: serializeCohortAdvancementPolicy(advanceAllPolicy),
16762
+ stepSignalsListDefinitions: extractInlineStepSignalsListDefinitions(advanceAllPolicy)
16763
+ }
16764
+ ],
16765
+ edges: [
16766
+ { fromNodeKey: stateKey, toNodeKey: gateKey },
16767
+ { fromNodeKey: gateKey, toNodeKey: state.next }
16768
+ ]
16769
+ };
16770
+ }
16771
+ function compileParallelState(stateKey, state, ctx) {
16772
+ const branchEntries = Object.entries(state.branches);
16773
+ if (branchEntries.length === 0) {
16774
+ throw new Error(`Pipeline "${ctx.pipelineKey}": parallel state "${stateKey}" requires at least one branch`);
16775
+ }
16776
+ const branches = {};
16777
+ for (const [branchKey, branchConfig] of branchEntries) {
16778
+ ctx.registerStep(branchConfig.step);
16779
+ const inputCtx = makeNodeInputCtx();
16780
+ const rawInput = branchConfig.input ? branchConfig.input(inputCtx) : {};
16781
+ branches[branchKey] = {
16782
+ stepKey: branchConfig.step.key,
16783
+ stepName: branchConfig.step.name,
16784
+ stepDescription: branchConfig.step.description,
16785
+ inputBindingsJson: serializeInputBindings(rawInput)
16786
+ };
16787
+ }
16788
+ let advanceAllPolicyDefinition;
16789
+ if (state.advanceAll) {
16790
+ const advanceAllResult = state.advanceAll(makeAdvanceAllCtx());
16791
+ const advanceAllPolicy = {
16792
+ default: advanceAllResult.default,
16793
+ ...advanceAllResult.rules !== undefined ? { rules: advanceAllResult.rules } : {}
16995
16794
  };
16996
- return buildPipelineSpec(config2);
16795
+ advanceAllPolicyDefinition = serializeCohortAdvancementPolicy(advanceAllPolicy);
16997
16796
  }
16797
+ assertTargetExists(ctx, stateKey, state.next);
16798
+ return {
16799
+ nodeDefinitions: [
16800
+ {
16801
+ nodeKey: stateKey,
16802
+ kind: "parallel",
16803
+ branches,
16804
+ ...advanceAllPolicyDefinition ? { advanceAllPolicyDefinition } : {}
16805
+ }
16806
+ ],
16807
+ edges: [{ fromNodeKey: stateKey, toNodeKey: state.next }]
16808
+ };
16998
16809
  }
16999
-
17000
- class PipelineBuilder {
17001
- inputSchema;
17002
- meta;
17003
- nodes = [];
17004
- pipelineInputBindings;
17005
- pipelineStepInputBindings;
17006
- constructor(meta3) {
17007
- const { additionalPipelineInput, additionalStepInput, ...rest } = meta3;
17008
- this.inputSchema = additionalPipelineInput?.schema ?? exports_external.unknown();
17009
- this.meta = rest;
17010
- if (additionalPipelineInput) {
17011
- const raw = additionalPipelineInput.bindings({
17012
- workItem: WORK_ITEM_ACCESSOR,
17013
- literal: literal2
17014
- });
17015
- if (additionalPipelineInput.schema instanceof exports_external.ZodObject) {
17016
- const validKeys = new Set(Object.keys(additionalPipelineInput.schema.shape));
17017
- const unknown2 = Object.keys(raw).filter((k) => !validKeys.has(k));
17018
- if (unknown2.length > 0) {
17019
- throw new Error(`additionalPipelineInput.bindings returned key${unknown2.length > 1 ? "s" : ""} not in schema: ${unknown2.map((k) => `"${k}"`).join(", ")}`);
17020
- }
16810
+ function compileLoopState(stateKey, state, ctx) {
16811
+ ctx.registerStep(state.step);
16812
+ const inputCtx = makeNodeInputCtx();
16813
+ const rawInput = state.input ? state.input(inputCtx) : {};
16814
+ assertTargetExists(ctx, stateKey, state.next);
16815
+ assertTargetExists(ctx, stateKey, state.onExhausted);
16816
+ return {
16817
+ nodeDefinitions: [
16818
+ {
16819
+ nodeKey: stateKey,
16820
+ kind: "loop",
16821
+ stepKey: state.step.key,
16822
+ stepName: state.step.name,
16823
+ stepDescription: state.step.description,
16824
+ inputBindingsJson: serializeInputBindings(rawInput),
16825
+ timeoutSeconds: state.timeout ?? null,
16826
+ maxIterations: state.maxIterations,
16827
+ untilConditionJson: serializeCondition(state.until)
17021
16828
  }
17022
- this.pipelineInputBindings = normalizeInputMapping(raw) ?? {};
17023
- } else {
17024
- this.pipelineInputBindings = {};
17025
- }
17026
- this.pipelineStepInputBindings = resolveAdditionalStepInputBindings("additionalStepInput", additionalStepInput);
16829
+ ],
16830
+ edges: [
16831
+ { fromNodeKey: stateKey, toNodeKey: state.next, discriminantJson: { loopExit: "next" } },
16832
+ {
16833
+ fromNodeKey: stateKey,
16834
+ toNodeKey: state.onExhausted,
16835
+ discriminantJson: { loopExit: "onExhausted" }
16836
+ }
16837
+ ]
16838
+ };
16839
+ }
16840
+ function compileTerminalState(stateKey, kind) {
16841
+ return { nodeDefinitions: [{ nodeKey: stateKey, kind }], edges: [] };
16842
+ }
16843
+ function assertNoIllegalConvergentEdges(pipelineKey, nodeKindByKey, edges) {
16844
+ const incoming = new Map;
16845
+ for (const edge of edges) {
16846
+ const list = incoming.get(edge.toNodeKey) ?? [];
16847
+ list.push(edge);
16848
+ incoming.set(edge.toNodeKey, list);
17027
16849
  }
17028
- step(step, options) {
17029
- return pushStep(this.inputSchema, this.meta, this.nodes, this.pipelineInputBindings, this.pipelineStepInputBindings, step, options);
16850
+ for (const [targetKey, incomingEdges] of incoming) {
16851
+ if (incomingEdges.length <= 1)
16852
+ continue;
16853
+ const hasInvalidSource = incomingEdges.some((edge) => {
16854
+ const kind = nodeKindByKey.get(edge.fromNodeKey);
16855
+ return kind !== "choice" && kind !== "loop";
16856
+ });
16857
+ if (hasInvalidSource) {
16858
+ throw new Error(`Pipeline "${pipelineKey}": state "${targetKey}" has more than one incoming edge, but not every source is a 'choice'/'loop' state (unconditional convergent edges are not allowed \u2014 see docs/research/flat-pipeline-sdk-and-visual-designer.md \xA76).`);
16859
+ }
17030
16860
  }
17031
16861
  }
17032
- function pipeline(meta3) {
17033
- return new PipelineBuilder(meta3);
16862
+
16863
+ // src/definitions/pipelines/define-pipeline.ts
16864
+ function isWorkingNodeDefinition(node) {
16865
+ return node.kind === "step" || node.kind === "fanOut" || node.kind === "loop";
16866
+ }
16867
+ function definePipeline(config2) {
16868
+ const stateEntries = Object.entries(config2.states);
16869
+ if (stateEntries.length === 0) {
16870
+ throw new Error(`Pipeline "${config2.key}" must declare at least one state`);
16871
+ }
16872
+ const startState = config2.states[config2.startAt];
16873
+ if (!startState) {
16874
+ throw new Error(`Pipeline "${config2.key}"'s startAt "${config2.startAt}" does not name a declared state`);
16875
+ }
16876
+ if (startState.kind === "choice" || startState.kind === "succeed" || startState.kind === "fail") {
16877
+ throw new Error(`Pipeline "${config2.key}"'s startAt "${config2.startAt}" names a '${startState.kind}' state, which cannot be an entry point`);
16878
+ }
16879
+ const stateKeys = new Set(Object.keys(config2.states));
16880
+ const stepDefMap = new Map;
16881
+ const registerStep = (step) => {
16882
+ const mapKey = `${step.key}@v${String(step.version)}`;
16883
+ if (!stepDefMap.has(mapKey))
16884
+ stepDefMap.set(mapKey, step);
16885
+ };
16886
+ const compileContext = {
16887
+ pipelineKey: config2.key,
16888
+ stateKeys,
16889
+ registerStep
16890
+ };
16891
+ const nodeDefinitions = [];
16892
+ const dependencyEdges = [];
16893
+ for (const [stateKey, state] of stateEntries) {
16894
+ const compiled = state.kind === "step" ? compileStepState(stateKey, state, compileContext) : state.kind === "choice" ? compileChoiceState(stateKey, state, compileContext) : state.kind === "fanOut" ? compileFanOutState(stateKey, state, compileContext) : state.kind === "parallel" ? compileParallelState(stateKey, state, compileContext) : state.kind === "loop" ? compileLoopState(stateKey, state, compileContext) : compileTerminalState(stateKey, state.kind);
16895
+ nodeDefinitions.push(...compiled.nodeDefinitions);
16896
+ dependencyEdges.push(...compiled.edges);
16897
+ }
16898
+ const nodeKindByKey = new Map(nodeDefinitions.map((node) => [node.nodeKey, node.kind]));
16899
+ assertNoIllegalConvergentEdges(config2.key, nodeKindByKey, dependencyEdges);
16900
+ let inputSchemaJson = null;
16901
+ if (config2.input) {
16902
+ try {
16903
+ inputSchemaJson = exports_external.toJSONSchema(config2.input);
16904
+ } catch {
16905
+ inputSchemaJson = null;
16906
+ }
16907
+ }
16908
+ return {
16909
+ key: config2.key,
16910
+ name: config2.name ?? config2.key,
16911
+ description: config2.description ?? null,
16912
+ version: config2.version ?? 1,
16913
+ status: config2.status ?? "active",
16914
+ inputSchemaJson,
16915
+ _stepDefinitions: [...stepDefMap.values()],
16916
+ nodeDefinitions,
16917
+ dependencyEdges
16918
+ };
17034
16919
  }
17035
16920
  // src/definitions/pipelines/pipeline-definitions-client.ts
17036
16921
  function createPipelineDefinitionsClient(baseUrl) {
17037
16922
  const client2 = createClient({ baseUrl });
17038
16923
  return buildPipelineDefinitionsClient(new PipelineDefinitions({ client: client2 }));
17039
16924
  }
16925
+ function resolveStepRef(pipelineKey, nodeKey, stepKey, stepDefMap) {
16926
+ const stepDef = stepDefMap.get(stepKey);
16927
+ if (!stepDef) {
16928
+ throw new Error(`Step "${stepKey}" referenced by node "${nodeKey}" in pipeline "${pipelineKey}" was not found on ` + `the server. Run \`boboddy steps push\` first to push your step definitions.`);
16929
+ }
16930
+ return {
16931
+ stepDefinitionId: stepDef.id,
16932
+ stepDefinitionVersion: stepDef.version
16933
+ };
16934
+ }
16935
+ function buildConfigJson(pipelineKey, node, stepDefMap) {
16936
+ if (node.kind === "choice") {
16937
+ return { choices: node.choices, default: node.default };
16938
+ }
16939
+ if (node.kind === "loop") {
16940
+ return {
16941
+ maxIterations: node.maxIterations,
16942
+ untilConditionJson: node.untilConditionJson
16943
+ };
16944
+ }
16945
+ if (node.kind === "parallel") {
16946
+ const branches = {};
16947
+ for (const [branchKey, branch] of Object.entries(node.branches)) {
16948
+ branches[branchKey] = {
16949
+ ...resolveStepRef(pipelineKey, node.nodeKey, branch.stepKey, stepDefMap),
16950
+ inputBindingsJson: branch.inputBindingsJson
16951
+ };
16952
+ }
16953
+ return {
16954
+ branches,
16955
+ ...node.advanceAllPolicyDefinition ? { advanceAllPolicyDefinition: node.advanceAllPolicyDefinition } : {}
16956
+ };
16957
+ }
16958
+ if (node.kind === "fanOut") {
16959
+ return {
16960
+ overSignalKey: node.overSignalKey,
16961
+ advanceEachPolicyDefinition: node.advanceEachPolicyDefinition,
16962
+ maxConcurrency: node.maxConcurrency
16963
+ };
16964
+ }
16965
+ if (node.kind === "cohortGate") {
16966
+ return {
16967
+ advanceAllPolicyDefinition: node.advanceAllPolicyDefinition,
16968
+ stepSignalsListDefinitions: node.stepSignalsListDefinitions
16969
+ };
16970
+ }
16971
+ return null;
16972
+ }
16973
+ function buildGraphNodeInput(pipelineKey, node, stepDefMap) {
16974
+ const working = isWorkingNodeDefinition(node) ? node : null;
16975
+ const stepRef = working ? resolveStepRef(pipelineKey, node.nodeKey, working.stepKey, stepDefMap) : null;
16976
+ const policy = node.kind === "step" ? node.advancementPolicyDefinition : undefined;
16977
+ return {
16978
+ key: node.nodeKey,
16979
+ kind: node.kind,
16980
+ name: working?.stepName ?? node.nodeKey,
16981
+ ...stepRef ?? {},
16982
+ description: working?.stepDescription ?? null,
16983
+ inputBindingsJson: working ? working.inputBindingsJson : null,
16984
+ timeoutSeconds: working?.timeoutSeconds ?? null,
16985
+ ...policy ? {
16986
+ advancementPolicyRulesJson: policy.rulesJson,
16987
+ advancementPolicyDefaultEventType: policy.defaultEventType,
16988
+ advancementPolicyDefaultEventParamsJson: policy.defaultEventParamsJson,
16989
+ advancementPolicyAllowedEventTypes: policy.allowedEventTypes
16990
+ } : {},
16991
+ configJson: buildConfigJson(pipelineKey, node, stepDefMap),
16992
+ computedSignalDefinitions: node.kind === "step" ? node.computedSignalDefinitions : []
16993
+ };
16994
+ }
17040
16995
  var buildPipelineDefinitionsClient = (pipelineDefinitions) => {
17041
16996
  return {
17042
16997
  listByProjectId: async (projectId, options) => {
@@ -17056,37 +17011,12 @@ var buildPipelineDefinitionsClient = (pipelineDefinitions) => {
17056
17011
  stepDefMap.set(s.key, s);
17057
17012
  }
17058
17013
  }
17059
- const nonStepNode = spec.nodeDefinitions.find((node) => node.kind !== "step");
17060
- if (nonStepNode) {
17061
- throw new Error(`Pipeline "${spec.key}" contains a "${nonStepNode.kind}" node ("${nonStepNode.nodeKey}"), ` + `but fan-out pipelines cannot be pushed via the current wire contract yet. ` + `Only "step"-only (chain) pipelines can be pushed with \`upsertFromSpec\` today.`);
17062
- }
17063
- const ordered = tryOrderChainNodeDefinitions(spec.nodeDefinitions, spec.dependencyEdges);
17064
- if (ordered === null) {
17065
- throw new Error(`Pipeline "${spec.key}" has a malformed step graph: its node and ` + `dependency-edge definitions do not form a single connected, ` + `acyclic chain. This should not happen from the \`pipeline()\` ` + `builder \u2014 check for hand-edited or generated pipeline specs.`);
17066
- }
17067
- const stepDefinitions = ordered.map((node, index) => {
17068
- const stepKey = node.stepKey;
17069
- if (!stepKey) {
17070
- throw new Error(`Node "${node.nodeKey}" in pipeline "${spec.key}" has no stepKey.`);
17071
- }
17072
- const stepDef = stepDefMap.get(stepKey);
17073
- if (!stepDef) {
17074
- throw new Error(`Step "${stepKey}" referenced in pipeline "${spec.key}" was not found on the server. ` + `Run \`boboddy steps push\` first to push your step definitions.`);
17075
- }
17076
- return {
17077
- stepDefinitionId: stepDef.id,
17078
- stepDefinitionVersion: stepDef.version,
17079
- key: node.nodeKey,
17080
- name: node.stepName ?? "",
17081
- description: node.stepDescription ?? null,
17082
- position: index + 1,
17083
- inputBindingsJson: node.inputBindingsJson ?? {},
17084
- timeoutSeconds: node.timeoutSeconds ?? null,
17085
- retryPolicyJson: null,
17086
- advancementPolicyDefinition: node.advancementPolicyDefinition,
17087
- computedSignalDefinitions: node.computedSignalDefinitions ?? []
17088
- };
17089
- });
17014
+ const nodeDefinitions = spec.nodeDefinitions.map((node) => buildGraphNodeInput(spec.key, node, stepDefMap));
17015
+ const dependencyEdges = spec.dependencyEdges.map((edge) => ({
17016
+ fromNodeKey: edge.fromNodeKey,
17017
+ toNodeKey: edge.toNodeKey,
17018
+ discriminantJson: edge.discriminantJson ?? null
17019
+ }));
17090
17020
  const body = {
17091
17021
  projectId,
17092
17022
  key: spec.key,
@@ -17094,7 +17024,8 @@ var buildPipelineDefinitionsClient = (pipelineDefinitions) => {
17094
17024
  description: spec.description,
17095
17025
  status: spec.status,
17096
17026
  inputSchemaJson: spec.inputSchemaJson,
17097
- stepDefinitions
17027
+ nodeDefinitions,
17028
+ dependencyEdges
17098
17029
  };
17099
17030
  const result = await pipelineDefinitions.upsertPipelineDefinition({
17100
17031
  body,
@@ -17129,7 +17060,7 @@ function makeLeaf(fact, path, operator, value) {
17129
17060
  };
17130
17061
  }
17131
17062
  function makeFieldRef(fact, path) {
17132
- return {
17063
+ const untyped = {
17133
17064
  eq: (v) => makeLeaf(fact, path, "equal", v),
17134
17065
  ne: (v) => makeLeaf(fact, path, "notEqual", v),
17135
17066
  gt: (v) => makeLeaf(fact, path, "greaterThan", v),
@@ -17141,18 +17072,28 @@ function makeFieldRef(fact, path) {
17141
17072
  contains: (v) => makeLeaf(fact, path, "contains", v),
17142
17073
  doesNotContain: (v) => makeLeaf(fact, path, "doesNotContain", v)
17143
17074
  };
17075
+ return untyped;
17076
+ }
17077
+ function buildWorkItemAccessor() {
17078
+ const accessor = {
17079
+ field: (name) => makeFieldRef("workItem", `$.${WORK_ITEM_FIELDS_PATH_PREFIX}${name}`)
17080
+ };
17081
+ for (const key of WORK_ITEM_TOP_LEVEL_FIELDS) {
17082
+ accessor[key] = makeFieldRef("workItem", `$.${key}`);
17083
+ }
17084
+ return accessor;
17144
17085
  }
17145
- function makeAssign(pipeline2) {
17146
- if (typeof pipeline2 !== "object" || typeof pipeline2["key"] !== "string" || !Array.isArray(pipeline2["nodeDefinitions"])) {
17147
- throw new Error("assign() requires a pipeline spec produced by pipeline().build(). " + "Pass the default-exported value from a pipeline definition file.");
17086
+ function makeAssign(pipeline) {
17087
+ if (typeof pipeline !== "object" || typeof pipeline["key"] !== "string" || !Array.isArray(pipeline["nodeDefinitions"])) {
17088
+ throw new Error("assign() requires a pipeline spec produced by definePipeline(). " + "Pass the default-exported value from a pipeline definition file.");
17148
17089
  }
17149
- return { _tag: "assign", pipeline: pipeline2 };
17090
+ return { _tag: "assign", pipeline };
17150
17091
  }
17151
- function extractCondition2(ref) {
17092
+ function extractCondition(ref) {
17152
17093
  return ref._condition;
17153
17094
  }
17154
17095
  function makeGroup(mode, refs) {
17155
- const conditions = refs.map(extractCondition2);
17096
+ const conditions = refs.map(extractCondition);
17156
17097
  const condition = { _tag: "group", mode, conditions };
17157
17098
  return {
17158
17099
  _condition: condition,
@@ -17163,9 +17104,7 @@ function makeGroup(mode, refs) {
17163
17104
  }
17164
17105
  function buildCtx() {
17165
17106
  return {
17166
- workItem: {
17167
- field: (name) => makeFieldRef("workItem", `$.fields.${name}`)
17168
- },
17107
+ workItem: buildWorkItemAccessor(),
17169
17108
  context: {
17170
17109
  isNew: makeFieldRef("context", "$.isNew")
17171
17110
  },
@@ -17466,6 +17405,58 @@ function resolveSourcePath(schema, sourcePath) {
17466
17405
  }
17467
17406
  return { kind: "resolved" };
17468
17407
  }
17408
+ // src/definitions/pipelines/chain-graph.ts
17409
+ function tryComputeTopoRanks(nodeDefinitions, dependencyEdges) {
17410
+ const nodeKeys = new Set(nodeDefinitions.map((node) => node.nodeKey));
17411
+ const outgoing = new Map;
17412
+ const inDegree = new Map;
17413
+ for (const key of nodeKeys) {
17414
+ outgoing.set(key, []);
17415
+ inDegree.set(key, 0);
17416
+ }
17417
+ for (const edge of dependencyEdges) {
17418
+ if (!nodeKeys.has(edge.fromNodeKey) || !nodeKeys.has(edge.toNodeKey)) {
17419
+ return null;
17420
+ }
17421
+ outgoing.get(edge.fromNodeKey)?.push(edge.toNodeKey);
17422
+ inDegree.set(edge.toNodeKey, (inDegree.get(edge.toNodeKey) ?? 0) + 1);
17423
+ }
17424
+ const rank = new Map;
17425
+ const queue = [];
17426
+ for (const key of nodeKeys) {
17427
+ if ((inDegree.get(key) ?? 0) === 0) {
17428
+ rank.set(key, 0);
17429
+ queue.push(key);
17430
+ }
17431
+ }
17432
+ let visitedCount = 0;
17433
+ while (queue.length > 0) {
17434
+ const current = queue.shift();
17435
+ if (current === undefined)
17436
+ break;
17437
+ visitedCount += 1;
17438
+ for (const next of outgoing.get(current) ?? []) {
17439
+ rank.set(next, Math.max(rank.get(next) ?? 0, (rank.get(current) ?? 0) + 1));
17440
+ const remaining = (inDegree.get(next) ?? 0) - 1;
17441
+ inDegree.set(next, remaining);
17442
+ if (remaining === 0)
17443
+ queue.push(next);
17444
+ }
17445
+ }
17446
+ if (visitedCount !== nodeKeys.size)
17447
+ return null;
17448
+ return rank;
17449
+ }
17450
+ function tryOrderNodeDefinitionsByTopoRank(nodeDefinitions, dependencyEdges) {
17451
+ const rankByKey = tryComputeTopoRanks(nodeDefinitions, dependencyEdges);
17452
+ if (rankByKey === null)
17453
+ return null;
17454
+ return nodeDefinitions.map((node, declarationIndex) => ({ node, declarationIndex })).sort((left, right) => {
17455
+ const rankDiff = (rankByKey.get(left.node.nodeKey) ?? 0) - (rankByKey.get(right.node.nodeKey) ?? 0);
17456
+ return rankDiff !== 0 ? rankDiff : left.declarationIndex - right.declarationIndex;
17457
+ }).map(({ node }) => node);
17458
+ }
17459
+
17469
17460
  // src/definitions/validation/validate-definition-specs.ts
17470
17461
  function listPaths(paths, limit = 24) {
17471
17462
  if (paths.length === 0)
@@ -17528,9 +17519,10 @@ function checkHealthChecks(steps) {
17528
17519
  }
17529
17520
  return issues;
17530
17521
  }
17531
- function routeTargets(policy) {
17532
- if (!policy)
17522
+ function routeTargets(node) {
17523
+ if (node.kind !== "step" || !node.advancementPolicyDefinition)
17533
17524
  return [];
17525
+ const policy = node.advancementPolicyDefinition;
17534
17526
  const keys = [];
17535
17527
  if (policy.defaultEventType === "route" && typeof policy.defaultEventParamsJson?.["pipelineKey"] === "string") {
17536
17528
  keys.push(policy.defaultEventParamsJson["pipelineKey"]);
@@ -17545,17 +17537,20 @@ function routeTargets(policy) {
17545
17537
  function checkRouteTargets(pipelines, knownPipelineKeys) {
17546
17538
  const issues = [];
17547
17539
  const known = new Set([
17548
- ...pipelines.map((pipeline2) => pipeline2.key),
17540
+ ...pipelines.map((pipeline) => pipeline.key),
17549
17541
  ...knownPipelineKeys
17550
17542
  ]);
17551
- for (const pipeline2 of pipelines) {
17552
- for (const node of pipeline2.nodeDefinitions) {
17553
- for (const target of routeTargets(node.advancementPolicyDefinition)) {
17543
+ for (const pipeline of pipelines) {
17544
+ for (const node of pipeline.nodeDefinitions) {
17545
+ const stepLabel = (isWorkingNodeDefinition(node) ? node.stepKey : undefined) ?? node.nodeKey;
17546
+ for (const target of routeTargets(node)) {
17554
17547
  if (known.has(target))
17555
17548
  continue;
17556
17549
  issues.push({
17557
17550
  check: "route-target",
17558
- message: `Pipeline "${pipeline2.key}" step "${node.stepKey ?? node.nodeKey}" routes to pipeline ` + `"${target}", but no pipeline with that key was found on the server or ` + `in the current push batch. Push the target pipeline first.`
17551
+ pipelineKey: pipeline.key,
17552
+ nodeKey: node.nodeKey,
17553
+ message: `Pipeline "${pipeline.key}" step "${stepLabel}" routes to pipeline ` + `"${target}", but no pipeline with that key was found on the server or ` + `in the current push batch. Push the target pipeline first.`
17559
17554
  });
17560
17555
  }
17561
17556
  }
@@ -17564,7 +17559,7 @@ function checkRouteTargets(pipelines, knownPipelineKeys) {
17564
17559
  }
17565
17560
  function executionRanks(nodeDefinitions, dependencyEdges) {
17566
17561
  const indexes = nodeDefinitions.map((_, index) => index);
17567
- const orderedNodes = tryOrderChainNodeDefinitions(nodeDefinitions, dependencyEdges);
17562
+ const orderedNodes = tryOrderNodeDefinitionsByTopoRank(nodeDefinitions, dependencyEdges);
17568
17563
  if (orderedNodes === null) {
17569
17564
  return new Map(indexes.map((index, rank) => [index, rank]));
17570
17565
  }
@@ -17574,70 +17569,81 @@ function executionRanks(nodeDefinitions, dependencyEdges) {
17574
17569
  }
17575
17570
  function bindingSource(binding) {
17576
17571
  if (binding.source === "step_signal") {
17577
- return { stepKey: binding.stepKey, signalKey: binding.signalKey, kind: "signal" };
17572
+ return { nodeKey: binding.stepKey, signalKey: binding.signalKey, kind: "signal" };
17578
17573
  }
17579
17574
  if (binding.source === "step_output") {
17580
- return { stepKey: binding.stepKey, signalKey: null, kind: "output" };
17575
+ return { nodeKey: binding.stepKey, signalKey: null, kind: "output" };
17581
17576
  }
17582
17577
  if (binding.source === "signals_list") {
17583
- return { stepKey: binding.stepKey, signalKey: null, kind: "signals_list" };
17578
+ return { nodeKey: binding.stepKey, signalKey: null, kind: "signals_list" };
17584
17579
  }
17585
17580
  return null;
17586
17581
  }
17587
- function declaredSignalKeys(stepKey, stepsByKey, pipelineNodeDefinitions) {
17588
- const specs = stepsByKey.get(stepKey);
17589
- if (!specs || specs.length === 0)
17590
- return null;
17582
+ function declaredSignalKeys(producerNode, stepsByKey) {
17583
+ const stepKey = isWorkingNodeDefinition(producerNode) ? producerNode.stepKey : undefined;
17584
+ const specs = stepKey ? stepsByKey.get(stepKey) : undefined;
17585
+ const computedSignalDefinitions = (producerNode.kind === "step" ? producerNode.computedSignalDefinitions : []) ?? [];
17591
17586
  const keys = new Set;
17592
- for (const spec of specs) {
17587
+ for (const spec of specs ?? []) {
17593
17588
  for (const signal2 of spec.signalExtractorDefinitions)
17594
17589
  keys.add(signal2.key);
17595
17590
  }
17596
- for (const node of pipelineNodeDefinitions) {
17597
- if (node.stepKey !== stepKey)
17598
- continue;
17599
- for (const computed of node.computedSignalDefinitions ?? [])
17600
- keys.add(computed.key);
17591
+ for (const computed of computedSignalDefinitions) {
17592
+ keys.add(computed.key);
17593
+ }
17594
+ if (!specs && computedSignalDefinitions.length === 0) {
17595
+ return null;
17601
17596
  }
17602
17597
  return [...keys];
17603
17598
  }
17604
17599
  function checkSignalBindings(pipelines, stepsByKey) {
17605
17600
  const issues = [];
17606
- for (const pipeline2 of pipelines) {
17607
- const ranks = executionRanks(pipeline2.nodeDefinitions, pipeline2.dependencyEdges);
17608
- const order = [...pipeline2.nodeDefinitions.keys()].sort((left, right) => (ranks.get(left) ?? 0) - (ranks.get(right) ?? 0)).map((index) => pipeline2.nodeDefinitions[index]?.stepKey ?? "");
17609
- const orderHint = `Steps in "${pipeline2.key}", in order: ${order.join(" \u2192 ")}.`;
17610
- pipeline2.nodeDefinitions.forEach((node, index) => {
17601
+ for (const pipeline of pipelines) {
17602
+ const ranks = executionRanks(pipeline.nodeDefinitions, pipeline.dependencyEdges);
17603
+ const order = [...pipeline.nodeDefinitions.keys()].sort((left, right) => (ranks.get(left) ?? 0) - (ranks.get(right) ?? 0)).map((index) => pipeline.nodeDefinitions[index]?.nodeKey ?? "");
17604
+ const orderHint = `Nodes in "${pipeline.key}", in order: ${order.join(" \u2192 ")}.`;
17605
+ const nodeByKey = new Map(pipeline.nodeDefinitions.map((node) => [node.nodeKey, node]));
17606
+ pipeline.nodeDefinitions.forEach((node, index) => {
17611
17607
  const consumerRank = ranks.get(index) ?? index;
17612
- const where = `Pipeline "${pipeline2.key}" step "${node.stepKey ?? node.nodeKey}"`;
17613
- for (const [field, binding] of Object.entries(node.inputBindingsJson ?? {})) {
17608
+ const where = `Pipeline "${pipeline.key}" node "${node.nodeKey}"`;
17609
+ const inputBindingsJson = (isWorkingNodeDefinition(node) ? node.inputBindingsJson : {}) ?? {};
17610
+ for (const [field, binding] of Object.entries(inputBindingsJson)) {
17614
17611
  const source = bindingSource(binding);
17615
17612
  if (!source)
17616
17613
  continue;
17617
- const what = source.kind === "signal" ? `binds input "${field}" to signal "${source.signalKey ?? ""}" of step "${source.stepKey}"` : source.kind === "signals_list" ? `binds input "${field}" to the signals list of fan-out step "${source.stepKey}"` : `binds input "${field}" to the output of step "${source.stepKey}"`;
17618
- const producerRanks = pipeline2.nodeDefinitions.map((candidate, candidateIndex) => candidate.stepKey === source.stepKey ? ranks.get(candidateIndex) ?? candidateIndex : null).filter((rank) => rank !== null);
17619
- if (producerRanks.length === 0) {
17614
+ const what = source.kind === "signal" ? `binds input "${field}" to signal "${source.signalKey ?? ""}" of node "${source.nodeKey}"` : source.kind === "signals_list" ? `binds input "${field}" to the signals list of fan-out node "${source.nodeKey}"` : `binds input "${field}" to the output of node "${source.nodeKey}"`;
17615
+ const issueBase = {
17616
+ pipelineKey: pipeline.key,
17617
+ nodeKey: node.nodeKey,
17618
+ targetNodeKey: source.nodeKey
17619
+ };
17620
+ const producerNode = nodeByKey.get(source.nodeKey);
17621
+ const producerRank = producerNode ? ranks.get(pipeline.nodeDefinitions.indexOf(producerNode)) ?? null : null;
17622
+ if (!producerNode || producerRank === null) {
17620
17623
  issues.push({
17621
17624
  check: "signal-binding",
17622
- message: `${where} ${what}, but no step with that key is in the pipeline. ${orderHint}`
17625
+ ...issueBase,
17626
+ message: `${where} ${what}, but no node with that key is in the pipeline. ${orderHint}`
17623
17627
  });
17624
17628
  continue;
17625
17629
  }
17626
- if (!producerRanks.some((rank) => rank < consumerRank)) {
17630
+ if (producerRank >= consumerRank) {
17627
17631
  issues.push({
17628
17632
  check: "signal-binding",
17629
- message: `${where} ${what}, but that step does not run before it, so the ` + `value will never exist. ${orderHint}`
17633
+ ...issueBase,
17634
+ message: `${where} ${what}, but that node does not run before it, so the ` + `value will never exist. ${orderHint}`
17630
17635
  });
17631
17636
  continue;
17632
17637
  }
17633
17638
  if (source.signalKey === null)
17634
17639
  continue;
17635
- const available = declaredSignalKeys(source.stepKey, stepsByKey, pipeline2.nodeDefinitions);
17640
+ const available = declaredSignalKeys(producerNode, stepsByKey);
17636
17641
  if (available === null || available.includes(source.signalKey))
17637
17642
  continue;
17638
17643
  issues.push({
17639
17644
  check: "signal-binding",
17640
- message: `${where} ${what}, but "${source.stepKey}" declares no such signal. ` + `Signals on "${source.stepKey}": ${available.length > 0 ? listPaths([...available].sort()) : "(none)"}.`
17645
+ ...issueBase,
17646
+ message: `${where} ${what}, but "${source.nodeKey}" declares no such signal. ` + `Signals on "${source.nodeKey}": ${available.length > 0 ? listPaths([...available].sort()) : "(none)"}.`
17641
17647
  });
17642
17648
  }
17643
17649
  });
@@ -17670,14 +17676,38 @@ function assertValidDefinitionSpecs(specs, options = {}) {
17670
17676
  }
17671
17677
  // src/push/collect-definitions.ts
17672
17678
  import { existsSync, readdirSync } from "fs";
17673
- import { join, resolve } from "path";
17679
+ import { join, relative, resolve } from "path";
17674
17680
  import { pathToFileURL } from "url";
17675
- var PUSH_SCRIPT_NAMES = new Set(["push.ts", "push.mjs", "push.js"]);
17681
+ var INTERNAL_SCRIPT_NAMES = new Set([
17682
+ "push.ts",
17683
+ "push.mjs",
17684
+ "push.js",
17685
+ ".boboddy-studio-collect.mjs"
17686
+ ]);
17676
17687
  function isStepDefinitionSpec(value) {
17677
17688
  if (typeof value !== "object" || value === null)
17678
17689
  return false;
17679
17690
  const obj = value;
17680
- return typeof obj["key"] === "string" && typeof obj["name"] === "string" && typeof obj["version"] === "number" && obj["kind"] === "user_defined";
17691
+ return typeof obj["key"] === "string" && typeof obj["name"] === "string" && typeof obj["version"] === "number" && (obj["kind"] === "user_defined" || obj["kind"] === "code");
17692
+ }
17693
+ function resolveCodeStepEntrypoint(spec, mod, absoluteFilePath) {
17694
+ if (spec.kind !== "code")
17695
+ return spec;
17696
+ const fn = spec.entrypoint?.fn;
17697
+ if (!fn) {
17698
+ throw new Error(`Code step "${spec.key}" (kind: "code") has no entrypoint.fn to resolve. ` + `Build it with codeStep({ fn, ... }) from "@boboddy/sdk/definitions/steps".`);
17699
+ }
17700
+ const exportName = Object.entries(mod).find(([, exportedValue]) => exportedValue === fn)?.[0];
17701
+ if (!exportName) {
17702
+ throw new Error(`Code step "${spec.key}"'s fn is not a named export of ${absoluteFilePath}. ` + `codeStep({ fn }) requires fn to be exported by name from the same module ` + `so its entrypoint can be resolved to a portable {sourceFile, exportName} pair.`);
17703
+ }
17704
+ const resolved = { ...spec };
17705
+ delete resolved.entrypoint;
17706
+ resolved.entrypointJson = {
17707
+ sourceFile: relative(process.cwd(), absoluteFilePath),
17708
+ exportName
17709
+ };
17710
+ return resolved;
17681
17711
  }
17682
17712
  function isPipelineDefinitionSpec(value) {
17683
17713
  if (typeof value !== "object" || value === null)
@@ -17688,31 +17718,48 @@ function isPipelineDefinitionSpec(value) {
17688
17718
  async function importModule(path) {
17689
17719
  return await import(pathToFileURL(path).href);
17690
17720
  }
17721
+ function listSourceFiles(absDir) {
17722
+ const allFiles = readdirSync(absDir).filter((file2) => file2.endsWith(".ts") || file2.endsWith(".js"));
17723
+ return allFiles.filter((file2) => !INTERNAL_SCRIPT_NAMES.has(file2) && file2 !== DEFAULT_PIPELINE_ASSIGNMENT_FILENAME);
17724
+ }
17725
+ function fileNameWithoutExtension(file2) {
17726
+ return file2.replace(/\.(ts|js)$/, "");
17727
+ }
17728
+ async function collectFile(absoluteFilePath, pipelines, stepMap) {
17729
+ const mod = await importModule(absoluteFilePath);
17730
+ for (const [exportName, value] of Object.entries(mod)) {
17731
+ if (exportName === "default") {
17732
+ if (isPipelineDefinitionSpec(value))
17733
+ pipelines.push(value);
17734
+ continue;
17735
+ }
17736
+ if (isStepDefinitionSpec(value)) {
17737
+ const resolved = resolveCodeStepEntrypoint(value, mod, absoluteFilePath);
17738
+ stepMap.set(`${resolved.key}@v${String(resolved.version)}`, resolved);
17739
+ }
17740
+ }
17741
+ }
17742
+ function foldEmbeddedSteps(dir, spec, stepMap) {
17743
+ for (const embedded of spec._stepDefinitions ?? []) {
17744
+ const key = `${embedded.key}@v${String(embedded.version)}`;
17745
+ if (stepMap.has(key))
17746
+ continue;
17747
+ if (embedded.kind === "code") {
17748
+ throw new Error(`Code step "${embedded.key}" is only referenced inside a pipeline and is not exported by name from any file in "${dir}". codeStep({ fn }) requires fn to be ` + `a named export so its entrypoint can be resolved \u2014 export the step itself, e.g. ` + `\`export const ${embedded.key} = codeStep({ ... })\`.`);
17749
+ }
17750
+ stepMap.set(key, embedded);
17751
+ }
17752
+ }
17691
17753
  async function collectDefinitionsFromDirectory(dir) {
17692
17754
  const absDir = resolve(dir);
17693
- const allFiles = readdirSync(absDir).filter((file2) => file2.endsWith(".ts") || file2.endsWith(".js"));
17694
- const sourceFiles = allFiles.filter((file2) => !PUSH_SCRIPT_NAMES.has(file2) && file2 !== DEFAULT_PIPELINE_ASSIGNMENT_FILENAME);
17755
+ const sourceFiles = listSourceFiles(absDir);
17695
17756
  const pipelines = [];
17696
17757
  const stepMap = new Map;
17697
17758
  for (const file2 of sourceFiles) {
17698
- const mod = await importModule(join(absDir, file2));
17699
- for (const [exportName, value] of Object.entries(mod)) {
17700
- if (exportName === "default") {
17701
- if (isPipelineDefinitionSpec(value))
17702
- pipelines.push(value);
17703
- continue;
17704
- }
17705
- if (isStepDefinitionSpec(value)) {
17706
- stepMap.set(`${value.key}@v${String(value.version)}`, value);
17707
- }
17708
- }
17759
+ await collectFile(join(absDir, file2), pipelines, stepMap);
17709
17760
  }
17710
17761
  for (const spec of pipelines) {
17711
- for (const embedded of spec._stepDefinitions ?? []) {
17712
- const key = `${embedded.key}@v${String(embedded.version)}`;
17713
- if (!stepMap.has(key))
17714
- stepMap.set(key, embedded);
17715
- }
17762
+ foldEmbeddedSteps(dir, spec, stepMap);
17716
17763
  }
17717
17764
  return {
17718
17765
  pipelines,
@@ -17720,6 +17767,50 @@ async function collectDefinitionsFromDirectory(dir) {
17720
17767
  defaultPipelineAssignment: await collectDefaultPipelineAssignment(absDir)
17721
17768
  };
17722
17769
  }
17770
+ async function collectDefinitionsFromDirectoryTolerant(dir) {
17771
+ const absDir = resolve(dir);
17772
+ const sourceFiles = listSourceFiles(absDir);
17773
+ const pipelines = [];
17774
+ const stepMap = new Map;
17775
+ const brokenPipelines = [];
17776
+ for (const file2 of sourceFiles) {
17777
+ try {
17778
+ await collectFile(join(absDir, file2), pipelines, stepMap);
17779
+ } catch (error51) {
17780
+ brokenPipelines.push({
17781
+ key: fileNameWithoutExtension(file2),
17782
+ message: error51 instanceof Error ? error51.message : String(error51)
17783
+ });
17784
+ }
17785
+ }
17786
+ const okPipelines = [];
17787
+ for (const spec of pipelines) {
17788
+ try {
17789
+ foldEmbeddedSteps(dir, spec, stepMap);
17790
+ okPipelines.push(spec);
17791
+ } catch (error51) {
17792
+ brokenPipelines.push({
17793
+ key: spec.key,
17794
+ message: error51 instanceof Error ? error51.message : String(error51)
17795
+ });
17796
+ }
17797
+ }
17798
+ let defaultPipelineAssignment2 = null;
17799
+ try {
17800
+ defaultPipelineAssignment2 = await collectDefaultPipelineAssignment(absDir);
17801
+ } catch (error51) {
17802
+ brokenPipelines.push({
17803
+ key: fileNameWithoutExtension(DEFAULT_PIPELINE_ASSIGNMENT_FILENAME),
17804
+ message: error51 instanceof Error ? error51.message : String(error51)
17805
+ });
17806
+ }
17807
+ return {
17808
+ pipelines: okPipelines,
17809
+ steps: [...stepMap.values()],
17810
+ defaultPipelineAssignment: defaultPipelineAssignment2,
17811
+ brokenPipelines
17812
+ };
17813
+ }
17723
17814
  async function collectDefaultPipelineAssignment(absDir) {
17724
17815
  const path = join(absDir, DEFAULT_PIPELINE_ASSIGNMENT_FILENAME);
17725
17816
  if (!existsSync(path))
@@ -17742,7 +17833,7 @@ async function pushFromDirectory(dir, opts) {
17742
17833
  log(`Found ${String(pipelines.length)} pipeline(s) and ${String(steps.length)} step(s).`);
17743
17834
  const stepsClient = createStepDefinitionsClient(opts.baseUrl);
17744
17835
  const pipelinesClient = createPipelineDefinitionsClient(opts.baseUrl);
17745
- const serverPipelineKeys = pipelines.length > 0 ? (await pipelinesClient.listByProjectId(opts.projectId, { headers })).map((pipeline2) => pipeline2.key) : [];
17836
+ const serverPipelineKeys = pipelines.length > 0 ? (await pipelinesClient.listByProjectId(opts.projectId, { headers })).map((pipeline) => pipeline.key) : [];
17746
17837
  assertValidDefinitionSpecs({ pipelines, steps }, { knownPipelineKeys: serverPipelineKeys });
17747
17838
  for (const spec of steps) {
17748
17839
  await stepsClient.upsertFromSpec(opts.projectId, spec, { headers });
@@ -17840,5 +17931,6 @@ async function syncDefaultPipelineAssignment(spec, opts, headers, pipelinesClien
17840
17931
  }
17841
17932
  export {
17842
17933
  pushFromDirectory,
17934
+ collectDefinitionsFromDirectoryTolerant,
17843
17935
  collectDefinitionsFromDirectory
17844
17936
  };