@boboddy/sdk 0.2.9-alpha → 0.2.12-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -886,12 +886,12 @@ class Projects extends HeyApiClient {
886
886
  listProjectWorkItems(options) {
887
887
  return (options.client ?? this.client).get({ url: "/api/projects/{projectId}/work-items", ...options });
888
888
  }
889
+ listProjectWorkItemFieldOptions(options) {
890
+ return (options.client ?? this.client).get({ url: "/api/projects/{projectId}/work-item-field-options", ...options });
891
+ }
889
892
  getProject(options) {
890
893
  return (options.client ?? this.client).get({ url: "/api/projects/{projectId}", ...options });
891
894
  }
892
- getProjectWorkItemSignalScores(options) {
893
- return (options.client ?? this.client).get({ url: "/api/projects/{projectId}/work-item-signal-scores", ...options });
894
- }
895
895
  updateProjectDefaultPipelineAssignment(options) {
896
896
  return (options.client ?? this.client).put({
897
897
  url: "/api/projects/{projectId}/default-pipeline-assignment",
@@ -1183,8 +1183,14 @@ class PipelineExecutions extends HeyApiClient {
1183
1183
  getPipelineExecution(options) {
1184
1184
  return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/{linearPipelineExecutionId}", ...options });
1185
1185
  }
1186
- listPipelineExecutionsByDefinition(options) {
1187
- return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/by-definition/{linearPipelineDefinitionId}", ...options });
1186
+ countPipelineExecutionsByPipeline(options) {
1187
+ return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/counts/by-pipeline", ...options });
1188
+ }
1189
+ listPipelineDefinitionStepRollups(options) {
1190
+ return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/step-rollups/by-definition/{linearPipelineDefinitionId}", ...options });
1191
+ }
1192
+ listPipelineDefinitionStepRuns(options) {
1193
+ return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/step-runs/by-definition/{linearPipelineDefinitionId}", ...options });
1188
1194
  }
1189
1195
  }
1190
1196
 
@@ -1219,9 +1225,6 @@ class WorkItems extends HeyApiClient {
1219
1225
  }
1220
1226
  });
1221
1227
  }
1222
- getWorkItems(options) {
1223
- return (options.client ?? this.client).get({ url: "/api/work-items/batch", ...options });
1224
- }
1225
1228
  createWorkItems(options) {
1226
1229
  return (options.client ?? this.client).post({
1227
1230
  url: "/api/work-items/batch",
@@ -15708,12 +15708,12 @@ class Projects extends HeyApiClient {
15708
15708
  listProjectWorkItems(options) {
15709
15709
  return (options.client ?? this.client).get({ url: "/api/projects/{projectId}/work-items", ...options });
15710
15710
  }
15711
+ listProjectWorkItemFieldOptions(options) {
15712
+ return (options.client ?? this.client).get({ url: "/api/projects/{projectId}/work-item-field-options", ...options });
15713
+ }
15711
15714
  getProject(options) {
15712
15715
  return (options.client ?? this.client).get({ url: "/api/projects/{projectId}", ...options });
15713
15716
  }
15714
- getProjectWorkItemSignalScores(options) {
15715
- return (options.client ?? this.client).get({ url: "/api/projects/{projectId}/work-item-signal-scores", ...options });
15716
- }
15717
15717
  updateProjectDefaultPipelineAssignment(options) {
15718
15718
  return (options.client ?? this.client).put({
15719
15719
  url: "/api/projects/{projectId}/default-pipeline-assignment",
@@ -16005,8 +16005,14 @@ class PipelineExecutions extends HeyApiClient {
16005
16005
  getPipelineExecution(options) {
16006
16006
  return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/{linearPipelineExecutionId}", ...options });
16007
16007
  }
16008
- listPipelineExecutionsByDefinition(options) {
16009
- return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/by-definition/{linearPipelineDefinitionId}", ...options });
16008
+ countPipelineExecutionsByPipeline(options) {
16009
+ return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/counts/by-pipeline", ...options });
16010
+ }
16011
+ listPipelineDefinitionStepRollups(options) {
16012
+ return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/step-rollups/by-definition/{linearPipelineDefinitionId}", ...options });
16013
+ }
16014
+ listPipelineDefinitionStepRuns(options) {
16015
+ return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/step-runs/by-definition/{linearPipelineDefinitionId}", ...options });
16010
16016
  }
16011
16017
  }
16012
16018
 
@@ -16041,9 +16047,6 @@ class WorkItems extends HeyApiClient {
16041
16047
  }
16042
16048
  });
16043
16049
  }
16044
- getWorkItems(options) {
16045
- return (options.client ?? this.client).get({ url: "/api/work-items/batch", ...options });
16046
- }
16047
16050
  createWorkItems(options) {
16048
16051
  return (options.client ?? this.client).post({
16049
16052
  url: "/api/work-items/batch",
@@ -121,7 +121,7 @@ export declare function defineStep<TInput extends ZodType = ZodType, TResult ext
121
121
  sourcePath: string;
122
122
  key?: string;
123
123
  }> = never[], const TFeatures extends ReadonlyArray<AnyStepFeature> = never[]>(config: Omit<DefineStepInput<TInput, TResult>, "signals" | "features"> & {
124
- signals?: TSignals;
124
+ signals?: TSignals & readonly SignalSpecInput<TResult["_output"]>[];
125
125
  features?: TFeatures;
126
126
  }): TypedStepDefinitionSpec<TInput["_output"], TResult["_output"] & FeatureResultExtensions<TFeatures>, SignalKeysOf<TSignals> | FeatureSignalKeys<TFeatures>, SignalTypeMapOf<TSignals, TResult["_output"]>>;
127
127
  export {};
@@ -12951,12 +12951,12 @@ class Projects extends HeyApiClient {
12951
12951
  listProjectWorkItems(options) {
12952
12952
  return (options.client ?? this.client).get({ url: "/api/projects/{projectId}/work-items", ...options });
12953
12953
  }
12954
+ listProjectWorkItemFieldOptions(options) {
12955
+ return (options.client ?? this.client).get({ url: "/api/projects/{projectId}/work-item-field-options", ...options });
12956
+ }
12954
12957
  getProject(options) {
12955
12958
  return (options.client ?? this.client).get({ url: "/api/projects/{projectId}", ...options });
12956
12959
  }
12957
- getProjectWorkItemSignalScores(options) {
12958
- return (options.client ?? this.client).get({ url: "/api/projects/{projectId}/work-item-signal-scores", ...options });
12959
- }
12960
12960
  updateProjectDefaultPipelineAssignment(options) {
12961
12961
  return (options.client ?? this.client).put({
12962
12962
  url: "/api/projects/{projectId}/default-pipeline-assignment",
@@ -13248,8 +13248,14 @@ class PipelineExecutions extends HeyApiClient {
13248
13248
  getPipelineExecution(options) {
13249
13249
  return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/{linearPipelineExecutionId}", ...options });
13250
13250
  }
13251
- listPipelineExecutionsByDefinition(options) {
13252
- return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/by-definition/{linearPipelineDefinitionId}", ...options });
13251
+ countPipelineExecutionsByPipeline(options) {
13252
+ return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/counts/by-pipeline", ...options });
13253
+ }
13254
+ listPipelineDefinitionStepRollups(options) {
13255
+ return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/step-rollups/by-definition/{linearPipelineDefinitionId}", ...options });
13256
+ }
13257
+ listPipelineDefinitionStepRuns(options) {
13258
+ return (options.client ?? this.client).get({ url: "/api/linear-pipeline-executions/step-runs/by-definition/{linearPipelineDefinitionId}", ...options });
13253
13259
  }
13254
13260
  }
13255
13261
 
@@ -13284,9 +13290,6 @@ class WorkItems extends HeyApiClient {
13284
13290
  }
13285
13291
  });
13286
13292
  }
13287
- getWorkItems(options) {
13288
- return (options.client ?? this.client).get({ url: "/api/work-items/batch", ...options });
13289
- }
13290
13293
  createWorkItems(options) {
13291
13294
  return (options.client ?? this.client).post({
13292
13295
  url: "/api/work-items/batch",
@@ -0,0 +1,2 @@
1
+ export * from "./json-schema-paths";
2
+ export * from "./validate-definition-specs";
@@ -0,0 +1,409 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+
17
+ // src/definitions/validation/json-schema-paths.ts
18
+ var SEGMENT_PATTERN = /([^.[\]]+)|(\[(\d+)\])/g;
19
+ var NUMERIC_SEGMENT = /^\d+$/;
20
+ var SCALAR_TYPES = new Set([
21
+ "string",
22
+ "number",
23
+ "integer",
24
+ "boolean",
25
+ "null"
26
+ ]);
27
+ var MAX_REF_HOPS = 16;
28
+ var MAX_CANDIDATES = 64;
29
+ function parseSourcePath(sourcePath) {
30
+ const trimmed = sourcePath.trim();
31
+ const normalized = trimmed === "$" ? "" : trimmed.startsWith("$.") ? trimmed.slice(2) : trimmed;
32
+ if (!normalized)
33
+ return [];
34
+ return [...normalized.matchAll(SEGMENT_PATTERN)].map((match) => match[1] ?? match[3]).filter((segment) => Boolean(segment));
35
+ }
36
+ function isSchemaNode(value) {
37
+ return typeof value === "boolean" || typeof value === "object" && value !== null && !Array.isArray(value);
38
+ }
39
+ function asRecord(node) {
40
+ return typeof node === "boolean" ? null : node;
41
+ }
42
+ function resolveRef(root, ref) {
43
+ if (!ref.startsWith("#"))
44
+ return null;
45
+ const pointer = ref.slice(1);
46
+ if (pointer === "" || pointer === "/")
47
+ return root;
48
+ if (!pointer.startsWith("/"))
49
+ return null;
50
+ let current = root;
51
+ for (const rawToken of pointer.slice(1).split("/")) {
52
+ const token = rawToken.replace(/~1/g, "/").replace(/~0/g, "~");
53
+ if (typeof current !== "object" || current === null)
54
+ return null;
55
+ current = current[token];
56
+ }
57
+ return isSchemaNode(current) ? current : null;
58
+ }
59
+ function flatten(node, root) {
60
+ const out = [];
61
+ const queue = [
62
+ { node, hops: 0 }
63
+ ];
64
+ while (queue.length > 0) {
65
+ const entry = queue.shift();
66
+ if (!entry)
67
+ break;
68
+ if (out.length >= MAX_CANDIDATES)
69
+ return null;
70
+ if (entry.hops > MAX_REF_HOPS)
71
+ return null;
72
+ const record = asRecord(entry.node);
73
+ if (!record) {
74
+ out.push(entry.node);
75
+ continue;
76
+ }
77
+ const ref = record["$ref"];
78
+ if (typeof ref === "string") {
79
+ const target = resolveRef(root, ref);
80
+ if (!target)
81
+ return null;
82
+ queue.push({ node: target, hops: entry.hops + 1 });
83
+ continue;
84
+ }
85
+ const branches = ["anyOf", "oneOf", "allOf"].flatMap((keyword) => {
86
+ const value = record[keyword];
87
+ return Array.isArray(value) ? value.filter(isSchemaNode) : [];
88
+ });
89
+ if (branches.length > 0) {
90
+ for (const branch of branches) {
91
+ queue.push({ node: branch, hops: entry.hops + 1 });
92
+ }
93
+ continue;
94
+ }
95
+ out.push(entry.node);
96
+ }
97
+ return out;
98
+ }
99
+ function typeNames(record) {
100
+ const raw = record["type"];
101
+ if (typeof raw === "string")
102
+ return new Set([raw]);
103
+ if (Array.isArray(raw)) {
104
+ return new Set(raw.filter((entry) => typeof entry === "string"));
105
+ }
106
+ return new Set;
107
+ }
108
+ var INDETERMINATE = { kind: "indeterminate" };
109
+ function stepIntoObject(record, segment) {
110
+ const properties = asRecord(isSchemaNode(record["properties"]) ? record["properties"] : {});
111
+ const declared = properties ?? {};
112
+ const child = declared[segment];
113
+ if (isSchemaNode(child))
114
+ return { kind: "child", node: child };
115
+ if (record["patternProperties"] !== undefined)
116
+ return INDETERMINATE;
117
+ if (record["additionalProperties"] !== false)
118
+ return INDETERMINATE;
119
+ return {
120
+ kind: "invalid",
121
+ reason: "unknown-property",
122
+ availablePaths: Object.keys(declared).sort()
123
+ };
124
+ }
125
+ function stepIntoArray(record, segment) {
126
+ if (!NUMERIC_SEGMENT.test(segment)) {
127
+ return {
128
+ kind: "invalid",
129
+ reason: "not-an-array-index",
130
+ availablePaths: []
131
+ };
132
+ }
133
+ const prefixItems = record["prefixItems"];
134
+ if (Array.isArray(prefixItems)) {
135
+ const positional = prefixItems[Number(segment)];
136
+ if (isSchemaNode(positional))
137
+ return { kind: "child", node: positional };
138
+ }
139
+ const items = record["items"];
140
+ if (isSchemaNode(items))
141
+ return { kind: "child", node: items };
142
+ return INDETERMINATE;
143
+ }
144
+ function stepInto(node, segment) {
145
+ const record = asRecord(node);
146
+ if (!record || Object.keys(record).length === 0)
147
+ return INDETERMINATE;
148
+ const types = typeNames(record);
149
+ const objectLike = types.has("object") || record["properties"] !== undefined || record["patternProperties"] !== undefined || record["additionalProperties"] !== undefined;
150
+ const arrayLike = types.has("array") || record["items"] !== undefined || record["prefixItems"] !== undefined;
151
+ const outcomes = [];
152
+ if (objectLike)
153
+ outcomes.push(stepIntoObject(record, segment));
154
+ if (arrayLike)
155
+ outcomes.push(stepIntoArray(record, segment));
156
+ if (outcomes.length === 0) {
157
+ if (types.size > 0 && [...types].every((name) => SCALAR_TYPES.has(name))) {
158
+ return {
159
+ kind: "invalid",
160
+ reason: "scalar-has-no-members",
161
+ availablePaths: []
162
+ };
163
+ }
164
+ return INDETERMINATE;
165
+ }
166
+ return combine(outcomes);
167
+ }
168
+ function combine(outcomes) {
169
+ const children = outcomes.filter((outcome) => outcome.kind === "child");
170
+ if (children.length > 0)
171
+ return children[0] ?? INDETERMINATE;
172
+ if (outcomes.some((outcome) => outcome.kind === "indeterminate")) {
173
+ return INDETERMINATE;
174
+ }
175
+ const invalid = outcomes.filter((outcome) => outcome.kind === "invalid");
176
+ const first = invalid[0];
177
+ if (!first)
178
+ return INDETERMINATE;
179
+ return {
180
+ kind: "invalid",
181
+ reason: first.reason,
182
+ availablePaths: [
183
+ ...new Set(invalid.flatMap((outcome) => outcome.availablePaths))
184
+ ].sort()
185
+ };
186
+ }
187
+ function enumeratePaths(node, root, maxDepth = 3, limit = 40) {
188
+ const out = [];
189
+ const visit = (current, prefix, depth) => {
190
+ if (out.length >= limit || depth > maxDepth)
191
+ return;
192
+ for (const branch of flatten(current, root) ?? []) {
193
+ const record = asRecord(branch);
194
+ const properties = record ? asRecord(isSchemaNode(record["properties"]) ? record["properties"] : {}) : null;
195
+ if (!properties)
196
+ continue;
197
+ for (const [key, child] of Object.entries(properties)) {
198
+ if (out.length >= limit)
199
+ return;
200
+ const path = prefix ? `${prefix}.${key}` : key;
201
+ out.push(path);
202
+ if (isSchemaNode(child))
203
+ visit(child, path, depth + 1);
204
+ }
205
+ }
206
+ };
207
+ visit(node, "", 1);
208
+ return [...new Set(out)].sort();
209
+ }
210
+ function resolveSourcePath(schema, sourcePath) {
211
+ const segments = parseSourcePath(sourcePath);
212
+ if (segments.length === 0)
213
+ return { kind: "resolved" };
214
+ let candidates = [schema];
215
+ let resolvedPrefix = "";
216
+ for (const segment of segments) {
217
+ const expanded = candidates.flatMap((node) => flatten(node, schema) ?? []);
218
+ if (expanded.length === 0)
219
+ return { kind: "indeterminate" };
220
+ const outcome = combine(expanded.map((node) => stepInto(node, segment)));
221
+ if (outcome.kind === "indeterminate")
222
+ return { kind: "indeterminate" };
223
+ if (outcome.kind === "invalid") {
224
+ const availablePaths = [
225
+ ...new Set(expanded.flatMap((node) => enumeratePaths(node, schema)))
226
+ ].sort();
227
+ return {
228
+ kind: "invalid",
229
+ resolvedPrefix,
230
+ segment,
231
+ reason: outcome.reason,
232
+ availablePaths
233
+ };
234
+ }
235
+ candidates = [outcome.node];
236
+ resolvedPrefix = resolvedPrefix ? `${resolvedPrefix}.${segment}` : segment;
237
+ }
238
+ return { kind: "resolved" };
239
+ }
240
+ // src/definitions/validation/validate-definition-specs.ts
241
+ function listPaths(paths, limit = 24) {
242
+ if (paths.length === 0)
243
+ return "";
244
+ if (paths.length <= limit)
245
+ return paths.join(", ");
246
+ return `${paths.slice(0, limit).join(", ")}, \u2026 (${String(paths.length - limit)} more)`;
247
+ }
248
+ function quotedOrRoot(prefix) {
249
+ return prefix ? `"${prefix}"` : "the result root";
250
+ }
251
+ function checkSignalSourcePaths(steps) {
252
+ const issues = [];
253
+ for (const step of steps) {
254
+ const schema = step.resultSchemaJson ?? null;
255
+ if (!schema)
256
+ continue;
257
+ for (const signal of step.signalExtractorDefinitions) {
258
+ const resolution = resolveSourcePath(schema, signal.sourcePath);
259
+ if (resolution.kind !== "invalid")
260
+ continue;
261
+ const { resolvedPrefix, segment, reason, availablePaths } = resolution;
262
+ const cause = reason === "not-an-array-index" ? `${quotedOrRoot(resolvedPrefix)} is an array, so "${segment}" can never index it \u2014 array segments must be numeric (e.g. "${resolvedPrefix}[0]")` : reason === "scalar-has-no-members" ? `${quotedOrRoot(resolvedPrefix)} is a scalar, so it has no property "${segment}"` : `${quotedOrRoot(resolvedPrefix)} has no property "${segment}"`;
263
+ const suffix = availablePaths.length > 0 ? ` Valid sourcePaths ${resolvedPrefix ? `under "${resolvedPrefix}"` : "for this step"}: ${listPaths(availablePaths)}.` : "";
264
+ issues.push({
265
+ check: "signal-source-path",
266
+ message: `Step "${step.key}" declares signal "${signal.key}" with sourcePath ` + `"${signal.sourcePath}", which can never resolve against the step's ` + `result schema: ${cause}.${suffix}`
267
+ });
268
+ }
269
+ }
270
+ return issues;
271
+ }
272
+ function routeTargets(policy) {
273
+ const keys = [];
274
+ if (policy.defaultEventType === "route" && typeof policy.defaultEventParamsJson?.["pipelineKey"] === "string") {
275
+ keys.push(policy.defaultEventParamsJson["pipelineKey"]);
276
+ }
277
+ for (const rule of policy.rulesJson.rules) {
278
+ if (rule.event.type === "route" && typeof rule.event.params?.["pipelineKey"] === "string") {
279
+ keys.push(rule.event.params["pipelineKey"]);
280
+ }
281
+ }
282
+ return keys;
283
+ }
284
+ function checkRouteTargets(pipelines, knownPipelineKeys) {
285
+ const issues = [];
286
+ const known = new Set([
287
+ ...pipelines.map((pipeline) => pipeline.key),
288
+ ...knownPipelineKeys
289
+ ]);
290
+ for (const pipeline of pipelines) {
291
+ for (const step of pipeline.steps) {
292
+ for (const target of routeTargets(step.advancementPolicyDefinition)) {
293
+ if (known.has(target))
294
+ continue;
295
+ issues.push({
296
+ check: "route-target",
297
+ message: `Pipeline "${pipeline.key}" step "${step.stepKey}" 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.`
298
+ });
299
+ }
300
+ }
301
+ }
302
+ return issues;
303
+ }
304
+ function executionRanks(steps) {
305
+ const positions = steps.map((step) => step.position);
306
+ const usable = positions.every((value) => Number.isInteger(value) && value > 0) && new Set(positions).size === positions.length;
307
+ const indexes = steps.map((_, index) => index);
308
+ const ordered = usable ? [...indexes].sort((left, right) => (positions[left] ?? 0) - (positions[right] ?? 0)) : indexes;
309
+ return new Map(ordered.map((index, rank) => [index, rank]));
310
+ }
311
+ function bindingSource(binding) {
312
+ if (binding.source === "step_signal") {
313
+ return { stepKey: binding.stepKey, signalKey: binding.signalKey };
314
+ }
315
+ if (binding.source === "step_output") {
316
+ return { stepKey: binding.stepKey, signalKey: null };
317
+ }
318
+ return null;
319
+ }
320
+ function declaredSignalKeys(stepKey, stepsByKey, pipelineSteps) {
321
+ const specs = stepsByKey.get(stepKey);
322
+ if (!specs || specs.length === 0)
323
+ return null;
324
+ const keys = new Set;
325
+ for (const spec of specs) {
326
+ for (const signal of spec.signalExtractorDefinitions)
327
+ keys.add(signal.key);
328
+ }
329
+ for (const step of pipelineSteps) {
330
+ if (step.stepKey !== stepKey)
331
+ continue;
332
+ for (const computed of step.computedSignalDefinitions)
333
+ keys.add(computed.key);
334
+ }
335
+ return [...keys];
336
+ }
337
+ function checkSignalBindings(pipelines, stepsByKey) {
338
+ const issues = [];
339
+ for (const pipeline of pipelines) {
340
+ const ranks = executionRanks(pipeline.steps);
341
+ const order = [...pipeline.steps.keys()].sort((left, right) => (ranks.get(left) ?? 0) - (ranks.get(right) ?? 0)).map((index) => pipeline.steps[index]?.stepKey ?? "");
342
+ const orderHint = `Steps in "${pipeline.key}", in order: ${order.join(" \u2192 ")}.`;
343
+ pipeline.steps.forEach((step, index) => {
344
+ const consumerRank = ranks.get(index) ?? index;
345
+ const where = `Pipeline "${pipeline.key}" step "${step.stepKey}"`;
346
+ for (const [field, binding] of Object.entries(step.inputBindingsJson)) {
347
+ const source = bindingSource(binding);
348
+ if (!source)
349
+ continue;
350
+ const what = source.signalKey ? `binds input "${field}" to signal "${source.signalKey}" of step "${source.stepKey}"` : `binds input "${field}" to the output of step "${source.stepKey}"`;
351
+ const producerRanks = pipeline.steps.map((candidate, candidateIndex) => candidate.stepKey === source.stepKey ? ranks.get(candidateIndex) ?? candidateIndex : null).filter((rank) => rank !== null);
352
+ if (producerRanks.length === 0) {
353
+ issues.push({
354
+ check: "signal-binding",
355
+ message: `${where} ${what}, but no step with that key is in the pipeline. ${orderHint}`
356
+ });
357
+ continue;
358
+ }
359
+ if (!producerRanks.some((rank) => rank < consumerRank)) {
360
+ issues.push({
361
+ check: "signal-binding",
362
+ message: `${where} ${what}, but that step does not run before it, so the ` + `value will never exist. ${orderHint}`
363
+ });
364
+ continue;
365
+ }
366
+ if (source.signalKey === null)
367
+ continue;
368
+ const available = declaredSignalKeys(source.stepKey, stepsByKey, pipeline.steps);
369
+ if (available === null || available.includes(source.signalKey))
370
+ continue;
371
+ issues.push({
372
+ check: "signal-binding",
373
+ message: `${where} ${what}, but "${source.stepKey}" declares no such signal. ` + `Signals on "${source.stepKey}": ${available.length > 0 ? listPaths([...available].sort()) : "(none)"}.`
374
+ });
375
+ }
376
+ });
377
+ }
378
+ return issues;
379
+ }
380
+ function validateDefinitionSpecs(specs, options = {}) {
381
+ const stepsByKey = new Map;
382
+ for (const step of specs.steps) {
383
+ const existing = stepsByKey.get(step.key);
384
+ if (existing)
385
+ existing.push(step);
386
+ else
387
+ stepsByKey.set(step.key, [step]);
388
+ }
389
+ return [
390
+ ...checkSignalSourcePaths(specs.steps),
391
+ ...checkRouteTargets(specs.pipelines, options.knownPipelineKeys ?? []),
392
+ ...checkSignalBindings(specs.pipelines, stepsByKey)
393
+ ];
394
+ }
395
+ function assertValidDefinitionSpecs(specs, options = {}) {
396
+ const issues = validateDefinitionSpecs(specs, options);
397
+ if (issues.length === 0)
398
+ return;
399
+ const header = issues.length === 1 ? "Definition validation failed:" : `Definition validation failed with ${String(issues.length)} problems:`;
400
+ throw new Error([header, ...issues.map((issue) => ` \u2022 ${issue.message}`)].join(`
401
+ `));
402
+ }
403
+ export {
404
+ validateDefinitionSpecs,
405
+ resolveSourcePath,
406
+ parseSourcePath,
407
+ enumeratePaths,
408
+ assertValidDefinitionSpecs
409
+ };
@@ -0,0 +1,45 @@
1
+ /** A JSON Schema node. `true` / `false` are the boolean schema forms. */
2
+ export type JsonSchemaNode = boolean | {
3
+ readonly [key: string]: unknown;
4
+ };
5
+ export type PathFailureReason =
6
+ /** The parent is a closed object with no such property. */
7
+ "unknown-property"
8
+ /** The parent is an array and the segment is not a numeric index. */
9
+ | "not-an-array-index"
10
+ /** The parent is a scalar, so it has no members at all. */
11
+ | "scalar-has-no-members";
12
+ export type PathResolution =
13
+ /** The whole path resolves to a node in the schema. */
14
+ {
15
+ readonly kind: "resolved";
16
+ }
17
+ /** Neither provably valid nor provably invalid — the schema is too loose. */
18
+ | {
19
+ readonly kind: "indeterminate";
20
+ } | {
21
+ readonly kind: "invalid";
22
+ /** Dot path of the prefix that did resolve; `""` at the root. */
23
+ readonly resolvedPrefix: string;
24
+ /** The first segment that could not resolve. */
25
+ readonly segment: string;
26
+ readonly reason: PathFailureReason;
27
+ /** Paths that DO resolve below `resolvedPrefix`, relative to it. */
28
+ readonly availablePaths: readonly string[];
29
+ };
30
+ /** Splits a `sourcePath` exactly as the runtime signal extractor does. */
31
+ export declare function parseSourcePath(sourcePath: string): string[];
32
+ /**
33
+ * Enumerates dot paths that resolve below `node`, for error messages.
34
+ *
35
+ * Depth-limited and count-limited: this is a hint for a human reading a push
36
+ * failure, not an exhaustive schema dump. Arrays are treated as leaves.
37
+ */
38
+ export declare function enumeratePaths(node: JsonSchemaNode, root: JsonSchemaNode, maxDepth?: number, limit?: number): string[];
39
+ /**
40
+ * Whether `sourcePath` can resolve against `schema`.
41
+ *
42
+ * Pass the step's `resultSchemaJson` as both the node and the document root;
43
+ * `$ref`s are resolved against the root.
44
+ */
45
+ export declare function resolveSourcePath(schema: JsonSchemaNode, sourcePath: string): PathResolution;
@@ -0,0 +1,25 @@
1
+ import type { PipelineDefinitionSpec } from "../pipelines/define-pipeline";
2
+ import type { StepDefinitionSpec } from "../steps/define-step";
3
+ export type DefinitionSpecSet = {
4
+ readonly pipelines: readonly PipelineDefinitionSpec[];
5
+ readonly steps: readonly StepDefinitionSpec[];
6
+ };
7
+ export type ValidateDefinitionSpecsOptions = {
8
+ /**
9
+ * Pipeline keys that exist outside this batch — in practice, the keys already
10
+ * on the server. Route targets may name these as well as keys in
11
+ * `specs.pipelines`. Omit it and only the batch counts.
12
+ */
13
+ readonly knownPipelineKeys?: readonly string[];
14
+ };
15
+ export type DefinitionValidationIssue = {
16
+ readonly check: "signal-source-path" | "route-target" | "signal-binding";
17
+ readonly message: string;
18
+ };
19
+ /**
20
+ * Runs every offline check over a batch of definitions and returns the issues
21
+ * found, in check order. An empty array means the batch is clean.
22
+ */
23
+ export declare function validateDefinitionSpecs(specs: DefinitionSpecSet, options?: ValidateDefinitionSpecsOptions): DefinitionValidationIssue[];
24
+ /** `validateDefinitionSpecs`, but throws a single aggregated error. */
25
+ export declare function assertValidDefinitionSpecs(specs: DefinitionSpecSet, options?: ValidateDefinitionSpecsOptions): void;