@pipelex/mthds-ui 0.27.0 → 0.29.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.
@@ -3,9 +3,11 @@ import {
3
3
  parsePipeRef
4
4
  } from "../chunk-2N65P32H.js";
5
5
  import {
6
+ BINDING_STEP_TYPE,
6
7
  KNOWN_PIPE_TYPES,
8
+ isBindingStepSpec,
7
9
  isPluralMultiplicity
8
- } from "../chunk-VCO4GZDZ.js";
10
+ } from "../chunk-3I3ZPYMW.js";
9
11
  import {
10
12
  __spreadProps,
11
13
  __spreadValues
@@ -33,28 +35,117 @@ function boolOrNull(value) {
33
35
 
34
36
  // src/static-graph/conceptRefs.ts
35
37
  var NATIVE_DOMAIN = "native";
36
- var NATIVE_CONCEPT_DESCRIPTIONS = {
37
- Dynamic: "A dynamic concept",
38
- Text: "A text",
39
- Image: "An image",
40
- Document: "A document",
41
- Html: "HTML content",
42
- TextAndImages: "A text and an image",
43
- Number: "A number",
44
- YesNo: "The answer to a yes/no question",
45
- Date: "A calendar date, optionally with a time of day \u2014 as precise as its source states.",
46
- Time: "A time of day, optionally with a UTC offset \u2014 as precise as its source states.",
47
- Page: "The content of a page of a document, comprising text and linked images and an optional page view image",
48
- JSON: "A JSON object",
49
- SearchResult: "A search result with answer and sources",
50
- Anything: "Anything",
51
- Composite: "A named composition of contents"
38
+ var field = (type) => ({
39
+ type,
40
+ conceptRef: null,
41
+ itemType: null,
42
+ itemConceptRef: null
43
+ });
44
+ var conceptField = (conceptRef) => ({
45
+ type: "concept",
46
+ conceptRef,
47
+ itemType: null,
48
+ itemConceptRef: null
49
+ });
50
+ var conceptListField = (itemConceptRef) => ({
51
+ type: "list",
52
+ conceptRef: null,
53
+ itemType: "concept",
54
+ itemConceptRef
55
+ });
56
+ var NATIVE_CONCEPTS = {
57
+ Dynamic: { description: "A dynamic concept", fields: null },
58
+ Text: { description: "A text", fields: { text: field("text") } },
59
+ Markdown: {
60
+ description: "A text written in Markdown",
61
+ refines: "native.Text",
62
+ fields: { text: field("text") }
63
+ },
64
+ Image: {
65
+ description: "An image",
66
+ fields: {
67
+ url: field("text"),
68
+ public_url: field("text"),
69
+ source_prompt: field("text"),
70
+ source_negative_prompt: field("text"),
71
+ caption: field("text"),
72
+ mime_type: field("text"),
73
+ width: field("integer"),
74
+ height: field("integer"),
75
+ filename: field("text")
76
+ }
77
+ },
78
+ Document: {
79
+ description: "A document",
80
+ fields: {
81
+ url: field("text"),
82
+ public_url: field("text"),
83
+ mime_type: field("text"),
84
+ filename: field("text"),
85
+ title: field("text"),
86
+ snippet: field("text")
87
+ }
88
+ },
89
+ Html: {
90
+ description: "HTML content",
91
+ fields: { inner_html: field("text"), css_class: field("text") }
92
+ },
93
+ TextAndImages: {
94
+ description: "A text and an image",
95
+ fields: {
96
+ text: conceptField("native.Text"),
97
+ images: conceptListField("native.Image"),
98
+ raw_html: field("text")
99
+ }
100
+ },
101
+ Number: { description: "A number", fields: { number: field("number") } },
102
+ YesNo: {
103
+ description: "The answer to a yes/no question",
104
+ fields: { yes_no: field("boolean"), probability: field("number") }
105
+ },
106
+ Choice: {
107
+ description: "One option picked out of a declared set",
108
+ fields: { choice: field("text"), confidence: field("number"), probabilities: field("dict") }
109
+ },
110
+ Rating: {
111
+ description: "A position on an ordered scale of described levels",
112
+ fields: {
113
+ level: field("integer"),
114
+ confidence: field("number"),
115
+ probabilities: field("dict"),
116
+ position: field("number")
117
+ }
118
+ },
119
+ Date: {
120
+ description: "A calendar date, optionally with a time of day \u2014 as precise as its source states.",
121
+ fields: { date: field("date"), time: field("time") }
122
+ },
123
+ Time: {
124
+ description: "A time of day, optionally with a UTC offset \u2014 as precise as its source states.",
125
+ fields: { time: field("time") }
126
+ },
127
+ Page: {
128
+ description: "The content of a page of a document, comprising text and linked images and an optional page view image",
129
+ fields: {
130
+ text_and_images: conceptField("native.TextAndImages"),
131
+ page_view: conceptField("native.Image")
132
+ }
133
+ },
134
+ JSON: { description: "A JSON object", fields: { json_obj: field("dict") } },
135
+ SearchResult: {
136
+ description: "A search result with answer and sources",
137
+ fields: { answer: field("text"), sources: conceptListField("native.Document") }
138
+ },
139
+ Anything: { description: "Anything", fields: null },
140
+ Composite: { description: "A named composition of contents", fields: null }
52
141
  };
53
- var NATIVE_CONCEPT_CODES = new Set(
54
- Object.keys(NATIVE_CONCEPT_DESCRIPTIONS)
55
- );
142
+ var NATIVE_CONCEPT_CODES = new Set(Object.keys(NATIVE_CONCEPTS));
56
143
  function isNativeConceptCode(code) {
57
- return Object.hasOwn(NATIVE_CONCEPT_DESCRIPTIONS, code);
144
+ return Object.hasOwn(NATIVE_CONCEPTS, code);
145
+ }
146
+ function nativeConceptFields(code) {
147
+ const definition = NATIVE_CONCEPTS[code];
148
+ return definition.fields;
58
149
  }
59
150
  var CONCEPT_REF_RE = /^(?:([A-Za-z0-9_][A-Za-z0-9_.]*)\.)?([A-Za-z0-9_]+)(?:\[(\d*)\])?([?!])?$/;
60
151
  function presenceFromSymbol(symbol) {
@@ -76,40 +167,50 @@ function parseConceptRef(raw) {
76
167
  }
77
168
  var INPUT_SLOT_KEYS = /* @__PURE__ */ new Set(["concept", "hints"]);
78
169
  var CONCEPT_CODE_RE = /^[A-Z][A-Za-z0-9]*$/;
79
- function looksLikeUnquotedDottedName(raw, unknownKeys) {
80
- if (raw.concept !== void 0 || unknownKeys.length === 0) return false;
81
- return unknownKeys.every((key) => {
82
- const parts = parseConceptRef(raw[key]);
83
- return parts !== null && CONCEPT_CODE_RE.test(parts.code);
84
- });
170
+ function isConceptCodeRef(raw) {
171
+ const parts = parseConceptRef(raw);
172
+ return parts !== null && CONCEPT_CODE_RE.test(parts.code);
173
+ }
174
+ function unquotedDottedInputNames(name, raw) {
175
+ if (!isPlainObject(raw) || raw.concept !== void 0) return null;
176
+ const keys = Object.keys(raw);
177
+ if (keys.length === 0) return null;
178
+ const dottedNames = [];
179
+ for (const key of keys) {
180
+ const value = raw[key];
181
+ const dottedName = `${name}.${key}`;
182
+ if (isConceptCodeRef(value) || isPlainObject(value) && isConceptCodeRef(value.concept)) {
183
+ dottedNames.push(dottedName);
184
+ continue;
185
+ }
186
+ const nested = unquotedDottedInputNames(dottedName, value);
187
+ if (nested === null) return null;
188
+ dottedNames.push(...nested);
189
+ }
190
+ return dottedNames;
85
191
  }
86
192
  function parseInputSlot(raw) {
87
193
  if (!isPlainObject(raw)) {
88
- return {
89
- ref: parseConceptRef(raw),
90
- missingConcept: false,
91
- unknownKeys: [],
92
- dottedName: false
93
- };
194
+ return { ref: parseConceptRef(raw), missingConcept: false, unknownKeys: [] };
94
195
  }
95
- const unknownKeys = Object.keys(raw).filter((key) => !INPUT_SLOT_KEYS.has(key));
96
196
  return {
97
197
  ref: parseConceptRef(raw.concept),
98
198
  missingConcept: raw.concept === void 0,
99
- unknownKeys,
100
- dottedName: looksLikeUnquotedDottedName(raw, unknownKeys)
199
+ unknownKeys: Object.keys(raw).filter((key) => !INPUT_SLOT_KEYS.has(key))
101
200
  };
102
201
  }
103
202
  function qualifiedStructureClassName(domain, code) {
104
203
  return `${domain.replaceAll(".", "\xB7")}__${code}`;
105
204
  }
106
205
  function nativeConceptInfo(code) {
206
+ var _a;
207
+ const definition = NATIVE_CONCEPTS[code];
107
208
  return {
108
209
  code,
109
210
  domain_code: NATIVE_DOMAIN,
110
- description: NATIVE_CONCEPT_DESCRIPTIONS[code],
211
+ description: definition.description,
111
212
  structure_class_name: `${code}Content`,
112
- refines: null
213
+ refines: (_a = definition.refines) != null ? _a : null
113
214
  };
114
215
  }
115
216
  function stubConceptInfo(code, domain) {
@@ -146,12 +247,11 @@ function resolveStuffSpec(raw, currentDomain, localConcepts) {
146
247
  return parts === null ? null : stuffSpecFromParts(parts, currentDomain, localConcepts);
147
248
  }
148
249
  function resolveInputSlot(raw, currentDomain, localConcepts) {
149
- const { ref, missingConcept, unknownKeys, dottedName } = parseInputSlot(raw);
250
+ const { ref, missingConcept, unknownKeys } = parseInputSlot(raw);
150
251
  return {
151
252
  spec: ref === null ? null : stuffSpecFromParts(ref, currentDomain, localConcepts),
152
253
  missingConcept,
153
- unknownKeys,
154
- dottedName
254
+ unknownKeys
155
255
  };
156
256
  }
157
257
 
@@ -174,6 +274,25 @@ function stringArrayOrNull(value) {
174
274
  if (!Array.isArray(value)) return null;
175
275
  return value.filter((item) => typeof item === "string");
176
276
  }
277
+ var PLAIN_NAME_RE = /^[a-z][a-z0-9_]*$/;
278
+ function bindingStepFor(dottedPath) {
279
+ const plainName = dottedPath.slice(dottedPath.lastIndexOf(".") + 1);
280
+ return `{ from = "${dottedPath}", result = "${plainName}" }`;
281
+ }
282
+ function dottedInputNameRemedy(dottedName) {
283
+ const root = dottedName.slice(0, dottedName.indexOf("."));
284
+ return `an input names one whole value, so its name cannot reach into a field with a dot; declare "${root}" with its whole concept and read the field through it, or have the calling sequence bind the field to a plain name with a binding step (${bindingStepFor(dottedName)}) and declare that name`;
285
+ }
286
+ function invalidInputNameReason(name) {
287
+ const segments = name.split(".");
288
+ if (segments.length > 1 && segments.every((segment) => PLAIN_NAME_RE.test(segment))) {
289
+ return `is not a plain input name: ${dottedInputNameRemedy(name)}`;
290
+ }
291
+ return "is not a plain input name, which is a lowercase letter followed by lowercase letters, digits and underscores";
292
+ }
293
+ function tomlKey(key) {
294
+ return /^[A-Za-z0-9_-]+$/.test(key) ? key : JSON.stringify(key);
295
+ }
177
296
  function normalizeInputs(raw, pipeCode, ctx) {
178
297
  const inputs = authoredRecord();
179
298
  if (raw === void 0 || raw === null) return inputs;
@@ -187,26 +306,33 @@ function normalizeInputs(raw, pipeCode, ctx) {
187
306
  return inputs;
188
307
  }
189
308
  for (const [name, slot] of Object.entries(raw)) {
190
- const { spec, missingConcept, unknownKeys, dottedName } = resolveInputSlot(
191
- slot,
192
- ctx.domain,
193
- ctx.concepts
194
- );
195
- if (dottedName) {
309
+ const path = `pipe.${pipeCode}.inputs.${tomlKey(name)}`;
310
+ if (!PLAIN_NAME_RE.test(name)) {
196
311
  ctx.diagnostics.push({
197
312
  severity: "warning",
198
- code: "invalid-concept-ref",
199
- message: `pipe "${pipeCode}": input "${name}" reads as a slot table but looks like an unquoted dotted input name \u2014 write it as one quoted key ("${name}.${unknownKeys.join(".")}" = \u2026) \u2014 skipped`,
200
- path: `pipe.${pipeCode}.inputs.${name}`
313
+ code: "invalid-input-name",
314
+ message: `pipe "${pipeCode}": input "${name}" ${invalidInputNameReason(name)} \u2014 skipped`,
315
+ path
201
316
  });
202
317
  continue;
203
318
  }
319
+ const dottedNames = unquotedDottedInputNames(name, slot);
320
+ if (dottedNames !== null) {
321
+ ctx.diagnostics.push({
322
+ severity: "warning",
323
+ code: "invalid-input-name",
324
+ message: `pipe "${pipeCode}": input "${name}" is a table with no "concept", which is how TOML reads the unquoted dotted input ${dottedNames.length === 1 ? "name" : "names"} ${dottedNames.map((dotted) => `"${dotted}"`).join(", ")}; ${dottedInputNameRemedy(dottedNames[0])} \u2014 skipped`,
325
+ path
326
+ });
327
+ continue;
328
+ }
329
+ const { spec, missingConcept, unknownKeys } = resolveInputSlot(slot, ctx.domain, ctx.concepts);
204
330
  if (unknownKeys.length > 0) {
205
331
  ctx.diagnostics.push({
206
332
  severity: "warning",
207
333
  code: "unknown-input-slot-key",
208
334
  message: `pipe "${pipeCode}": input "${name}" declares ${unknownKeys.map((key) => `"${key}"`).join(", ")}, which the input slot form does not define \u2014 ignored here, and the runtime refuses the bundle`,
209
- path: `pipe.${pipeCode}.inputs.${name}`
335
+ path
210
336
  });
211
337
  }
212
338
  if (spec === null) {
@@ -214,7 +340,7 @@ function normalizeInputs(raw, pipeCode, ctx) {
214
340
  severity: "warning",
215
341
  code: "invalid-concept-ref",
216
342
  message: missingConcept ? `pipe "${pipeCode}": input "${name}" is a slot table with no "concept" \u2014 the key is required \u2014 skipped` : `pipe "${pipeCode}": input "${name}" has an uninterpretable concept ref \u2014 skipped`,
217
- path: `pipe.${pipeCode}.inputs.${name}`
343
+ path
218
344
  });
219
345
  continue;
220
346
  }
@@ -235,7 +361,7 @@ function normalizeOutput(raw, pipeCode, ctx) {
235
361
  }
236
362
  function normalizeSubPipe(raw, pipeCode, path, ctx) {
237
363
  var _a;
238
- if (!isPlainObject(raw) || strOrNull(raw.pipe) === null) {
364
+ if (strOrNull(raw.pipe) === null) {
239
365
  ctx.diagnostics.push({
240
366
  severity: "warning",
241
367
  code: "invalid-sub-pipe",
@@ -266,21 +392,168 @@ function normalizeSubPipe(raw, pipeCode, path, ctx) {
266
392
  batch_params: batchParams
267
393
  };
268
394
  }
269
- function normalizeSubPipeList(raw, pipeCode, key, ctx) {
395
+ var BINDING_PATH_RE = /^[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)*$/;
396
+ var BINDING_STEP_KEYS = /* @__PURE__ */ new Set(["from", "result"]);
397
+ var PRIVATE_BINDING_NAME_PREFIX = "_bound_";
398
+ function normalizeBindingStep(raw, pipeCode, path, ctx) {
399
+ const skip = (why) => {
400
+ ctx.diagnostics.push({
401
+ severity: "warning",
402
+ code: "invalid-binding-step",
403
+ message: `pipe "${pipeCode}": binding step ${why} \u2014 skipped`,
404
+ path
405
+ });
406
+ return null;
407
+ };
408
+ if (raw.pipe !== void 0) return skip(`carries both "from" and "pipe"`);
409
+ const fromPath = strOrNull(raw.from);
410
+ if (fromPath === null || !BINDING_PATH_RE.test(fromPath)) {
411
+ return skip(`has a "from" that is not a path of identifiers separated by dots`);
412
+ }
413
+ const result = strOrNull(raw.result);
414
+ if (result === null) return skip(`binds "${fromPath}" under no "result"`);
415
+ if (!PLAIN_NAME_RE.test(result)) {
416
+ return skip(
417
+ result.startsWith(PRIVATE_BINDING_NAME_PREFIX) ? `binds "${fromPath}" under "${result}", which takes the prefix "${PRIVATE_BINDING_NAME_PREFIX}" the runtime reserves for its own names` : `binds "${fromPath}" under "${result}", which is not a plain name (lowercase letters, digits and underscores, starting with a letter)`
418
+ );
419
+ }
420
+ const extraKeys = Object.keys(raw).filter((key) => !BINDING_STEP_KEYS.has(key));
421
+ if (extraKeys.length > 0) {
422
+ ctx.diagnostics.push({
423
+ severity: "warning",
424
+ code: "invalid-binding-step",
425
+ message: `pipe "${pipeCode}": binding step of "${fromPath}" carries ${extraKeys.map((key) => `"${key}"`).join(", ")}, which a binding step does not take \u2014 ignored`,
426
+ path
427
+ });
428
+ }
429
+ return { from_path: fromPath, output_name: result, is_dotted_batch_over: false };
430
+ }
431
+ function namesInSequence(inputNames, rawSteps) {
432
+ const names = new Set(inputNames);
433
+ for (const step of rawSteps) {
434
+ if (!isPlainObject(step)) continue;
435
+ if (step.from !== void 0) {
436
+ const fromPath = strOrNull(step.from);
437
+ if (fromPath !== null) names.add(fromPath.split(".")[0]);
438
+ const result = strOrNull(step.result);
439
+ if (result !== null) names.add(result);
440
+ continue;
441
+ }
442
+ for (const name of [strOrNull(step.result), strOrNull(step.batch_as)]) {
443
+ if (name !== null) names.add(name);
444
+ }
445
+ const batchOver = strOrNull(step.batch_over);
446
+ if (batchOver !== null) names.add(batchOver.split(".")[0]);
447
+ }
448
+ return names;
449
+ }
450
+ function makePrivateBindingName(path, takenNames) {
451
+ const baseName = `${PRIVATE_BINDING_NAME_PREFIX}${path.replaceAll(".", "_")}`;
452
+ let name = baseName;
453
+ for (let suffix = 2; takenNames.has(name); suffix++) name = `${baseName}_${suffix}`;
454
+ return name;
455
+ }
456
+ function normalizeSequenceSteps(raw, pipeCode, inputNames, ctx) {
457
+ if (!Array.isArray(raw)) {
458
+ if (raw !== void 0) {
459
+ ctx.diagnostics.push({
460
+ severity: "warning",
461
+ code: "invalid-pipe-entry",
462
+ message: `pipe "${pipeCode}": steps is not an array \u2014 treated as empty`,
463
+ path: `pipe.${pipeCode}.steps`
464
+ });
465
+ }
466
+ return [];
467
+ }
468
+ const takenNames = namesInSequence(inputNames, raw);
469
+ const steps = [];
470
+ raw.forEach((entry, index) => {
471
+ var _a;
472
+ const path = `pipe.${pipeCode}.steps[${index}]`;
473
+ if (isPlainObject(entry) && entry.from !== void 0) {
474
+ const binding = normalizeBindingStep(entry, pipeCode, path, ctx);
475
+ if (binding !== null) steps.push(binding);
476
+ return;
477
+ }
478
+ if (!isPlainObject(entry)) {
479
+ ctx.diagnostics.push({
480
+ severity: "warning",
481
+ code: "invalid-sub-pipe",
482
+ message: `pipe "${pipeCode}": sub-pipe entry without a "pipe" ref \u2014 skipped`,
483
+ path
484
+ });
485
+ return;
486
+ }
487
+ const step = normalizeSubPipe(entry, pipeCode, path, ctx);
488
+ if (step === null) return;
489
+ const listName = (_a = step.batch_params) == null ? void 0 : _a.input_list_stuff_name;
490
+ if (step.batch_params != null && listName !== void 0 && listName.includes(".")) {
491
+ if (!BINDING_PATH_RE.test(listName)) {
492
+ ctx.diagnostics.push({
493
+ severity: "warning",
494
+ code: "invalid-binding-step",
495
+ message: `pipe "${pipeCode}": dotted batch_over "${listName}" is not a path of identifiers separated by dots \u2014 batching over it as a name`,
496
+ path
497
+ });
498
+ } else {
499
+ const privateName = makePrivateBindingName(listName, takenNames);
500
+ takenNames.add(privateName);
501
+ steps.push({ from_path: listName, output_name: privateName, is_dotted_batch_over: true });
502
+ step.batch_params = __spreadProps(__spreadValues({}, step.batch_params), { input_list_stuff_name: privateName });
503
+ }
504
+ }
505
+ steps.push(step);
506
+ });
507
+ return steps;
508
+ }
509
+ function normalizeBranchList(raw, pipeCode, ctx) {
270
510
  if (!Array.isArray(raw)) {
271
511
  if (raw !== void 0) {
272
512
  ctx.diagnostics.push({
273
513
  severity: "warning",
274
514
  code: "invalid-pipe-entry",
275
- message: `pipe "${pipeCode}": ${key} is not an array \u2014 treated as empty`,
276
- path: `pipe.${pipeCode}.${key}`
515
+ message: `pipe "${pipeCode}": branches is not an array \u2014 treated as empty`,
516
+ path: `pipe.${pipeCode}.branches`
277
517
  });
278
518
  }
279
519
  return [];
280
520
  }
281
- return raw.map(
282
- (entry, index) => normalizeSubPipe(entry, pipeCode, `pipe.${pipeCode}.${key}[${index}]`, ctx)
283
- ).filter((entry) => entry !== null);
521
+ const branches = [];
522
+ raw.forEach((entry, index) => {
523
+ var _a;
524
+ const path = `pipe.${pipeCode}.branches[${index}]`;
525
+ if (!isPlainObject(entry)) {
526
+ ctx.diagnostics.push({
527
+ severity: "warning",
528
+ code: "invalid-sub-pipe",
529
+ message: `pipe "${pipeCode}": sub-pipe entry without a "pipe" ref \u2014 skipped`,
530
+ path
531
+ });
532
+ return;
533
+ }
534
+ if (entry.from !== void 0) {
535
+ ctx.diagnostics.push({
536
+ severity: "warning",
537
+ code: "invalid-binding-step",
538
+ message: `pipe "${pipeCode}": a parallel branch cannot be a binding step \u2014 bind in a sequence step before the parallel \u2014 skipped`,
539
+ path
540
+ });
541
+ return;
542
+ }
543
+ const branch = normalizeSubPipe(entry, pipeCode, path, ctx);
544
+ if (branch === null) return;
545
+ if ((_a = branch.batch_params) == null ? void 0 : _a.input_list_stuff_name.includes(".")) {
546
+ ctx.diagnostics.push({
547
+ severity: "warning",
548
+ code: "invalid-binding-step",
549
+ message: `pipe "${pipeCode}": a parallel branch cannot batch over the dotted path "${branch.batch_params.input_list_stuff_name}" \u2014 bind it in a sequence step before the parallel \u2014 skipped`,
550
+ path
551
+ });
552
+ return;
553
+ }
554
+ branches.push(branch);
555
+ });
556
+ return branches;
284
557
  }
285
558
  function normalizeConstructField(value) {
286
559
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value)) {
@@ -306,11 +579,36 @@ function normalizeConstruct(raw) {
306
579
  if (!isPlainObject(raw)) return null;
307
580
  const fields = {};
308
581
  for (const [name, value] of Object.entries(raw)) {
309
- const field = normalizeConstructField(value);
310
- if (field !== null) fields[name] = field;
582
+ const field2 = normalizeConstructField(value);
583
+ if (field2 !== null) fields[name] = field2;
311
584
  }
312
585
  return Object.keys(fields).length > 0 ? { fields } : null;
313
586
  }
587
+ function normalizeJudgmentQuestion(raw) {
588
+ var _a, _b;
589
+ const instructions = (_b = (_a = strOrNull(raw.question)) != null ? _a : strOrNull(raw.prompt)) != null ? _b : "";
590
+ if (isPlainObject(raw.options)) {
591
+ const options = {};
592
+ for (const [key, description] of Object.entries(raw.options)) {
593
+ options[key] = typeof description === "string" ? description : null;
594
+ }
595
+ return { kind: "choice", instructions, options };
596
+ }
597
+ if (Array.isArray(raw.levels)) {
598
+ return {
599
+ kind: "rating",
600
+ instructions,
601
+ levels: raw.levels.filter((level) => typeof level === "string")
602
+ };
603
+ }
604
+ const criteria = isPlainObject(raw.criteria) ? raw.criteria : {};
605
+ return {
606
+ kind: "yes_no",
607
+ instructions,
608
+ yes_criterion: strOrNull(criteria.yes),
609
+ no_criterion: strOrNull(criteria.no)
610
+ };
611
+ }
314
612
  var SIGNATURE_ONLY_KEYS = /* @__PURE__ */ new Set([
315
613
  "description",
316
614
  "inputs",
@@ -343,7 +641,7 @@ function resolvePipeTypeTag(code, raw, ctx) {
343
641
  return null;
344
642
  }
345
643
  function normalizePipe(code, raw, ctx) {
346
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
644
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
347
645
  const typeValue = resolvePipeTypeTag(code, raw, ctx);
348
646
  if (typeValue === null) return null;
349
647
  const type = typeValue;
@@ -359,13 +657,18 @@ function normalizePipe(code, raw, ctx) {
359
657
  return __spreadProps(__spreadValues({}, base), {
360
658
  type,
361
659
  pipe_category: "PipeController",
362
- sequential_sub_pipes: normalizeSubPipeList(raw.steps, code, "steps", ctx)
660
+ sequential_sub_pipes: normalizeSequenceSteps(
661
+ raw.steps,
662
+ code,
663
+ Object.keys(base.inputs),
664
+ ctx
665
+ )
363
666
  });
364
667
  case "PipeParallel":
365
668
  return __spreadProps(__spreadValues({}, base), {
366
669
  type,
367
670
  pipe_category: "PipeController",
368
- parallel_sub_pipes: normalizeSubPipeList(raw.branches, code, "branches", ctx),
671
+ parallel_sub_pipes: normalizeBranchList(raw.branches, code, ctx),
369
672
  add_each_output: raw.add_each_output === true,
370
673
  combined_output: strOrNull(raw.combined_output)
371
674
  });
@@ -527,6 +830,24 @@ function normalizePipe(code, raw, ctx) {
527
830
  }
528
831
  case "PipeFunc":
529
832
  return __spreadProps(__spreadValues({}, base), { type, pipe_category: "PipeOperator" });
833
+ case "PipeJudge":
834
+ return __spreadProps(__spreadValues({}, base), {
835
+ type,
836
+ pipe_category: "PipeOperator",
837
+ judgment_choice: isPlainObject(raw.model) ? raw.model : strOrNull(raw.model),
838
+ judgment_question: normalizeJudgmentQuestion(raw),
839
+ threshold: typeof raw.threshold === "number" ? raw.threshold : null
840
+ });
841
+ case "PipeDocGen":
842
+ return __spreadProps(__spreadValues({}, base), {
843
+ type,
844
+ pipe_category: "PipeOperator",
845
+ doc_gen_format: (_m = strOrNull(raw.format)) != null ? _m : "",
846
+ doc_gen_choice: isPlainObject(raw.model) ? raw.model : strOrNull(raw.model),
847
+ template: strOrNull(raw.template),
848
+ template_file: strOrNull(raw.template_file),
849
+ filename: strOrNull(raw.filename)
850
+ });
530
851
  case "PipeSignature": {
531
852
  const signatureFor = strOrNull(raw.signature_for);
532
853
  return __spreadProps(__spreadValues({}, base), {
@@ -580,6 +901,7 @@ function deriveJsonSchema(structure, title, description) {
580
901
  for (const [name, spec] of Object.entries(structure)) {
581
902
  if (typeof spec === "string") {
582
903
  properties[name] = { type: "string", description: spec };
904
+ required.push(name);
583
905
  continue;
584
906
  }
585
907
  if (!isPlainObject(spec)) continue;
@@ -611,10 +933,28 @@ function qualifyRefines(raw, domain, declaredCodes, conceptCode, diagnostics) {
611
933
  }
612
934
  return `${domain}.${parts.code}`;
613
935
  }
936
+ function parseStructureFields(structure) {
937
+ const fields = authoredRecord();
938
+ for (const [name, spec] of Object.entries(structure)) {
939
+ if (typeof spec === "string") {
940
+ fields[name] = { type: "text", conceptRef: null, itemType: null, itemConceptRef: null };
941
+ continue;
942
+ }
943
+ if (!isPlainObject(spec)) continue;
944
+ fields[name] = {
945
+ type: strOrNull(spec.type),
946
+ conceptRef: strOrNull(spec.concept_ref),
947
+ itemType: strOrNull(spec.item_type),
948
+ itemConceptRef: strOrNull(spec.item_concept_ref)
949
+ };
950
+ }
951
+ return fields;
952
+ }
614
953
  function parseConcepts(raw, domain, diagnostics) {
615
954
  var _a, _b;
616
955
  const concepts = authoredRecord();
617
- if (raw === void 0) return concepts;
956
+ const structures = authoredRecord();
957
+ if (raw === void 0) return { concepts, structures };
618
958
  if (!isPlainObject(raw)) {
619
959
  diagnostics.push({
620
960
  severity: "warning",
@@ -622,7 +962,7 @@ function parseConcepts(raw, domain, diagnostics) {
622
962
  message: "concept section is not a table \u2014 ignored",
623
963
  path: "concept"
624
964
  });
625
- return concepts;
965
+ return { concepts, structures };
626
966
  }
627
967
  const declaredCodes = new Set(Object.keys(raw));
628
968
  for (const [code, entry] of Object.entries(raw)) {
@@ -655,8 +995,13 @@ function parseConcepts(raw, domain, diagnostics) {
655
995
  refines: qualifyRefines(entry.refines, domain, declaredCodes, code, diagnostics),
656
996
  json_schema: deriveJsonSchema(entry.structure, structureClassName, description)
657
997
  };
998
+ if (isPlainObject(entry.structure)) {
999
+ structures[code] = parseStructureFields(entry.structure);
1000
+ } else if (typeof entry.structure === "string") {
1001
+ structures[code] = entry.structure;
1002
+ }
658
1003
  }
659
- return concepts;
1004
+ return { concepts, structures };
660
1005
  }
661
1006
  function emptyBundle() {
662
1007
  return {
@@ -665,6 +1010,7 @@ function emptyBundle() {
665
1010
  main_pipe: null,
666
1011
  system_prompt: null,
667
1012
  concepts: authoredRecord(),
1013
+ structures: authoredRecord(),
668
1014
  pipes: authoredRecord()
669
1015
  };
670
1016
  }
@@ -696,7 +1042,9 @@ function parseMthdsBundle(tomlText) {
696
1042
  });
697
1043
  }
698
1044
  const domain = (_a = bundle.domain) != null ? _a : UNKNOWN_DOMAIN;
699
- bundle.concepts = parseConcepts(root.concept, domain, diagnostics);
1045
+ const { concepts, structures } = parseConcepts(root.concept, domain, diagnostics);
1046
+ bundle.concepts = concepts;
1047
+ bundle.structures = structures;
700
1048
  const pipeTable = root.pipe;
701
1049
  if (pipeTable !== void 0) {
702
1050
  if (!isPlainObject(pipeTable)) {
@@ -754,6 +1102,7 @@ function mergeBundles(bundles) {
754
1102
  const namespace = (_b = domains[domain]) != null ? _b : domains[domain] = {
755
1103
  domain,
756
1104
  concepts: authoredRecord(),
1105
+ structures: authoredRecord(),
757
1106
  pipes: authoredRecord()
758
1107
  };
759
1108
  if (mainDomain === null && bundle.domain !== null) mainDomain = bundle.domain;
@@ -774,6 +1123,9 @@ function mergeBundles(bundles) {
774
1123
  continue;
775
1124
  }
776
1125
  namespace.concepts[code] = concept;
1126
+ if (Object.hasOwn(bundle.structures, code)) {
1127
+ namespace.structures[code] = bundle.structures[code];
1128
+ }
777
1129
  }
778
1130
  for (const [code, pipe] of Object.entries(bundle.pipes)) {
779
1131
  const existing = namespace.pipes[code];
@@ -816,6 +1168,197 @@ function mergeBundles(bundles) {
816
1168
  return { domains, mainDomain, mainPipe, description, diagnostics };
817
1169
  }
818
1170
 
1171
+ // src/static-graph/bindingWalk.ts
1172
+ var SINGLE_FIELD_NATIVE_REASON = "holds its value in a single field";
1173
+ var DESCRIBED_ONLY_REASON = "is declared with neither a structure nor refines";
1174
+ function splitRef(ref) {
1175
+ const dot = ref.lastIndexOf(".");
1176
+ return { domain: ref.slice(0, dot), code: ref.slice(dot + 1) };
1177
+ }
1178
+ function qualifyFieldRef(ref, domain) {
1179
+ if (ref.includes("->")) return ref;
1180
+ if (ref.startsWith(`${NATIVE_DOMAIN}.`)) return ref;
1181
+ if (!ref.includes("."))
1182
+ return isNativeConceptCode(ref) ? `${NATIVE_DOMAIN}.${ref}` : `${domain}.${ref}`;
1183
+ return ref;
1184
+ }
1185
+ function resolveWalkable(set, ref, visited = /* @__PURE__ */ new Set()) {
1186
+ if (ref.includes("->")) {
1187
+ return {
1188
+ shape: "no-structure",
1189
+ ref,
1190
+ reason: "belongs to a dependency, which a method's own bundles do not show"
1191
+ };
1192
+ }
1193
+ const { domain, code } = splitRef(ref);
1194
+ if (domain === NATIVE_DOMAIN) {
1195
+ if (!isNativeConceptCode(code)) {
1196
+ return { shape: "no-structure", ref, reason: "is not a native concept" };
1197
+ }
1198
+ const fields = nativeConceptFields(code);
1199
+ if (fields === null) {
1200
+ return { shape: "no-structure", ref, reason: "is structureless by definition" };
1201
+ }
1202
+ if (Object.keys(fields).length === 1) {
1203
+ return { shape: "value", ref, reason: SINGLE_FIELD_NATIVE_REASON };
1204
+ }
1205
+ return { shape: "structure", ref, fields };
1206
+ }
1207
+ if (visited.has(ref))
1208
+ return { shape: "no-structure", ref, reason: "refines itself through a cycle" };
1209
+ const namespace = set.domains[domain];
1210
+ const concept = namespace !== void 0 && Object.hasOwn(namespace.concepts, code) ? namespace.concepts[code] : void 0;
1211
+ if (namespace === void 0 || concept === void 0) {
1212
+ return { shape: "no-structure", ref, reason: "is not declared in this method" };
1213
+ }
1214
+ const structure = Object.hasOwn(namespace.structures, code) ? namespace.structures[code] : void 0;
1215
+ if (typeof structure === "string") {
1216
+ return {
1217
+ shape: "no-structure",
1218
+ ref,
1219
+ reason: "has a Python class for a structure, which a bundle does not show"
1220
+ };
1221
+ }
1222
+ if (structure !== void 0) {
1223
+ const fields = /* @__PURE__ */ Object.create(null);
1224
+ for (const [name, declared] of Object.entries(structure)) {
1225
+ fields[name] = __spreadProps(__spreadValues({}, declared), {
1226
+ conceptRef: declared.conceptRef === null ? null : qualifyFieldRef(declared.conceptRef, domain),
1227
+ itemConceptRef: declared.itemConceptRef === null ? null : qualifyFieldRef(declared.itemConceptRef, domain)
1228
+ });
1229
+ }
1230
+ return { shape: "structure", ref, fields };
1231
+ }
1232
+ if (concept.refines != null) {
1233
+ const refined = resolveWalkable(set, concept.refines, /* @__PURE__ */ new Set([...visited, ref]));
1234
+ if (refined.shape === "structure") return { shape: "structure", ref, fields: refined.fields };
1235
+ const reason = `refines '${refined.ref}', which ${refined.reason}`;
1236
+ return refined.shape === "value" ? { shape: "value", ref, reason } : { shape: "no-structure", ref, reason };
1237
+ }
1238
+ return { shape: "no-structure", ref, reason: DESCRIBED_ONLY_REASON };
1239
+ }
1240
+ var SCALAR_NATIVE = {
1241
+ text: "Text",
1242
+ number: "Number",
1243
+ integer: "Number",
1244
+ boolean: "YesNo",
1245
+ date: "Date",
1246
+ datetime: "Date",
1247
+ time: "Time",
1248
+ dict: "JSON"
1249
+ };
1250
+ function fieldStep(declared) {
1251
+ var _a;
1252
+ const type = (_a = declared.type) != null ? _a : "text";
1253
+ if (type === "concept") return { kind: "concept", ref: declared.conceptRef, crossesList: false };
1254
+ if (type === "list") {
1255
+ const itemType = declared.itemType;
1256
+ if (itemType === null) {
1257
+ return { kind: "underivable", crossesList: true, reason: "the list declares no item_type" };
1258
+ }
1259
+ if (itemType === "concept") {
1260
+ return { kind: "concept", ref: declared.itemConceptRef, crossesList: true };
1261
+ }
1262
+ const nativeCode2 = Object.hasOwn(SCALAR_NATIVE, itemType) ? SCALAR_NATIVE[itemType] : null;
1263
+ if (nativeCode2 === null) {
1264
+ return {
1265
+ kind: "underivable",
1266
+ crossesList: true,
1267
+ reason: `the list's item_type '${itemType}' derives no concept`
1268
+ };
1269
+ }
1270
+ return { kind: "leaf", nativeCode: nativeCode2, crossesList: true, description: `a list of ${itemType}` };
1271
+ }
1272
+ const nativeCode = Object.hasOwn(SCALAR_NATIVE, type) ? SCALAR_NATIVE[type] : null;
1273
+ if (nativeCode === null) {
1274
+ return {
1275
+ kind: "underivable",
1276
+ crossesList: false,
1277
+ reason: `its type '${type}' derives no concept`
1278
+ };
1279
+ }
1280
+ return { kind: "leaf", nativeCode, crossesList: false, description: `a ${type} field` };
1281
+ }
1282
+ function conceptInfoForRef(set, ref) {
1283
+ var _a, _b;
1284
+ const { domain, code } = splitRef(ref);
1285
+ return resolveConceptInfo(
1286
+ { domain, code, multiplicity: null, presence: "plain" },
1287
+ domain,
1288
+ (_b = (_a = set.domains[domain]) == null ? void 0 : _a.concepts) != null ? _b : {}
1289
+ );
1290
+ }
1291
+ function conceptRefOf(concept) {
1292
+ return `${concept.domain_code}.${concept.code}`;
1293
+ }
1294
+ function deriveBinding(set, path, root) {
1295
+ const segments = path.split(".").slice(1);
1296
+ if (segments.length === 0) {
1297
+ return { kind: "derived", concept: root.concept, multiplicity: root.multiplicity };
1298
+ }
1299
+ let crossesAnyList = isPluralMultiplicity(root.multiplicity);
1300
+ let reachedPath = path.split(".")[0];
1301
+ let current = resolveWalkable(set, conceptRefOf(root.concept));
1302
+ let leafDescription = null;
1303
+ let derivedRef = conceptRefOf(root.concept);
1304
+ const fail = (reason) => ({
1305
+ kind: "unresolved",
1306
+ reason: `cannot bind '${path}': ${reason}`
1307
+ });
1308
+ for (const [index, segment] of segments.entries()) {
1309
+ if (leafDescription !== null) {
1310
+ return fail(
1311
+ `'${reachedPath}' is ${leafDescription}, a leaf holding a plain value, so the segment '${segment}' cannot follow it`
1312
+ );
1313
+ }
1314
+ if (current === null) {
1315
+ return fail(
1316
+ `nothing is known of '${reachedPath}', so the segment '${segment}' cannot be walked`
1317
+ );
1318
+ }
1319
+ if (current.shape === "value") {
1320
+ return fail(
1321
+ `'${reachedPath}' holds a '${current.ref}', which ${current.reason}, so it is a leaf and the segment '${segment}' cannot follow it`
1322
+ );
1323
+ }
1324
+ if (current.shape === "no-structure") {
1325
+ return fail(
1326
+ `'${reachedPath}' holds a '${current.ref}', which ${current.reason}, so the segment '${segment}' has no structure to walk`
1327
+ );
1328
+ }
1329
+ const declared = Object.hasOwn(current.fields, segment) ? current.fields[segment] : void 0;
1330
+ if (declared === void 0) {
1331
+ const names = Object.keys(current.fields);
1332
+ return fail(
1333
+ `'${reachedPath}' holds a '${current.ref}', which has no field '${segment}'` + (names.length > 0 ? ` (its fields are ${names.map((name) => `'${name}'`).join(", ")})` : "")
1334
+ );
1335
+ }
1336
+ reachedPath = `${reachedPath}.${segment}`;
1337
+ const isLast = index === segments.length - 1;
1338
+ const step = fieldStep(declared);
1339
+ if (step.crossesList) crossesAnyList = true;
1340
+ switch (step.kind) {
1341
+ case "underivable":
1342
+ return fail(`'${reachedPath}' derives no concept, since ${step.reason}`);
1343
+ case "concept":
1344
+ if (step.ref === null) return fail(`the concept field '${reachedPath}' names no concept`);
1345
+ derivedRef = step.ref;
1346
+ current = isLast ? null : resolveWalkable(set, step.ref);
1347
+ break;
1348
+ case "leaf":
1349
+ derivedRef = `${NATIVE_DOMAIN}.${step.nativeCode}`;
1350
+ leafDescription = step.description;
1351
+ current = null;
1352
+ break;
1353
+ }
1354
+ }
1355
+ return {
1356
+ kind: "derived",
1357
+ concept: conceptInfoForRef(set, derivedRef),
1358
+ multiplicity: crossesAnyList ? true : null
1359
+ };
1360
+ }
1361
+
819
1362
  // src/static-graph/buildStaticGraphSpec.ts
820
1363
  function buildStaticGraphSpec(set, options = {}) {
821
1364
  var _a;
@@ -893,7 +1436,9 @@ function referencedPipeCodes(namespace) {
893
1436
  for (const pipe of Object.values(namespace.pipes)) {
894
1437
  switch (pipe.type) {
895
1438
  case "PipeSequence":
896
- for (const sub of pipe.sequential_sub_pipes) add(sub.pipe_code);
1439
+ for (const step of pipe.sequential_sub_pipes) {
1440
+ if (!isBindingStepSpec(step)) add(step.pipe_code);
1441
+ }
897
1442
  break;
898
1443
  case "PipeParallel":
899
1444
  for (const sub of pipe.parallel_sub_pipes) add(sub.pipe_code);
@@ -1017,23 +1562,13 @@ function formatBatchMultiplicity(multiplicity) {
1017
1562
  if (multiplicity === true) return "xmany";
1018
1563
  return "x?";
1019
1564
  }
1020
- function lookupScope(scope, name) {
1021
- let candidate = name;
1022
- for (; ; ) {
1023
- const found = scope.get(candidate);
1024
- if (found !== void 0) return found;
1025
- const dot = candidate.lastIndexOf(".");
1026
- if (dot <= 0) return void 0;
1027
- candidate = candidate.slice(0, dot);
1028
- }
1029
- }
1030
1565
  function conceptKey(concept) {
1031
1566
  return `${concept.domain_code}.${concept.code}`;
1032
1567
  }
1033
1568
  function bindInputs(ctx, blueprint, scope) {
1034
1569
  const ioInputs = [];
1035
1570
  for (const [name, spec] of Object.entries(blueprint.inputs)) {
1036
- let bound = lookupScope(scope, name);
1571
+ let bound = scope.get(name);
1037
1572
  if (bound === void 0) {
1038
1573
  bound = mintStuff(ctx, `input:${name}`, name, spec.concept, spec.multiplicity);
1039
1574
  scope.set(name, bound);
@@ -1179,11 +1714,18 @@ function emitOpaqueLeaf(ctx, ref, currentDomain, nodeId, parentId, inv) {
1179
1714
  function finishSequence(ctx, node, blueprint, nodeId, scope, inv) {
1180
1715
  let lastOutput = null;
1181
1716
  blueprint.sequential_sub_pipes.forEach((sub, index) => {
1717
+ const stepId = `${nodeId}/step_${index + 1}`;
1718
+ if (isBindingStepSpec(sub)) {
1719
+ const bound = walkBindingStep(ctx, sub, blueprint, stepId, nodeId, scope);
1720
+ scope.set(sub.output_name, bound);
1721
+ lastOutput = bound;
1722
+ return;
1723
+ }
1182
1724
  const result = walkSubPipe(
1183
1725
  ctx,
1184
1726
  sub,
1185
1727
  blueprint.domain_code,
1186
- `${nodeId}/step_${index + 1}`,
1728
+ stepId,
1187
1729
  nodeId,
1188
1730
  scope,
1189
1731
  inv.outputMultiplicity
@@ -1198,6 +1740,54 @@ function finishSequence(ctx, node, blueprint, nodeId, scope, inv) {
1198
1740
  node.io.outputs = lastOutput === null ? [] : [ioItem(lastOutput, inv.resultName)];
1199
1741
  return { nodeId, output: lastOutput, eachOutputs: [] };
1200
1742
  }
1743
+ function walkBindingStep(ctx, step, sequence, nodeId, parentId, scope) {
1744
+ const rootName = step.from_path.split(".")[0];
1745
+ let root = scope.get(rootName);
1746
+ if (root === void 0) {
1747
+ root = mintStuff(ctx, `input:${rootName}`, rootName, nativeConceptInfo("Anything"), null);
1748
+ scope.set(rootName, root);
1749
+ }
1750
+ const node = emitNode(ctx, {
1751
+ id: nodeId,
1752
+ kind: "binding",
1753
+ pipeCode: step.from_path,
1754
+ pipeType: BINDING_STEP_TYPE,
1755
+ description: `Binds '${step.from_path}' to '${step.output_name}'`,
1756
+ domainCode: sequence.domain_code,
1757
+ ioInputs: [ioItem(root, rootName)],
1758
+ parentId,
1759
+ inv: { resultName: step.output_name, outputMultiplicity: null }
1760
+ });
1761
+ node.execution_data = { from: step.from_path, result: step.output_name };
1762
+ const derivation = deriveBinding(ctx.set, step.from_path, {
1763
+ concept: root.concept,
1764
+ multiplicity: root.multiplicity
1765
+ });
1766
+ let concept;
1767
+ let multiplicity;
1768
+ if (derivation.kind === "derived") {
1769
+ ({ concept, multiplicity } = derivation);
1770
+ } else {
1771
+ ctx.diagnostics.push({
1772
+ severity: "warning",
1773
+ code: "binding-path-unresolved",
1774
+ message: `pipe "${sequence.code}": ${derivation.reason} \u2014 bound as native.Anything`,
1775
+ path: nodeId,
1776
+ domain_code: sequence.domain_code
1777
+ });
1778
+ concept = nativeConceptInfo("Anything");
1779
+ multiplicity = isPluralMultiplicity(root.multiplicity) ? true : null;
1780
+ }
1781
+ const output = mintStuff(
1782
+ ctx,
1783
+ `${nodeId}:${step.output_name}`,
1784
+ step.output_name,
1785
+ concept,
1786
+ multiplicity
1787
+ );
1788
+ node.io.outputs = [ioItem(output)];
1789
+ return output;
1790
+ }
1201
1791
  function finishParallel(ctx, node, blueprint, nodeId, scope, inv) {
1202
1792
  var _a, _b;
1203
1793
  const branchResults = [];
@@ -1240,13 +1830,13 @@ function finishParallel(ctx, node, blueprint, nodeId, scope, inv) {
1240
1830
  return { nodeId, output: combined, eachOutputs };
1241
1831
  }
1242
1832
  function finishCondition(ctx, node, blueprint, nodeId, scope, inv) {
1243
- var _a, _b, _c, _d;
1833
+ var _a, _b;
1244
1834
  const conditionScope = new Map(scope);
1245
1835
  if (blueprint.add_alias_from_expression_to != null) {
1246
1836
  const alias = blueprint.add_alias_from_expression_to;
1247
1837
  conditionScope.set(
1248
1838
  alias,
1249
- mintStuff(ctx, `${nodeId}:${alias}`, alias, nativeConceptInfo("Dynamic"), null)
1839
+ mintStuff(ctx, `${nodeId}:alias:${alias}`, alias, nativeConceptInfo("Dynamic"), null)
1250
1840
  );
1251
1841
  }
1252
1842
  const targets = [];
@@ -1289,16 +1879,47 @@ function finishCondition(ctx, node, blueprint, nodeId, scope, inv) {
1289
1879
  );
1290
1880
  if (result !== null) results.push({ entry, result });
1291
1881
  }
1292
- const primary = (_d = (_c = (_a = results.find(({ entry, result }) => entry.viaDefault && result.output !== null)) == null ? void 0 : _a.result.output) != null ? _c : (_b = results.find(({ result }) => result.output !== null)) == null ? void 0 : _b.result.output) != null ? _d : null;
1293
- node.io.outputs = primary === null ? [] : [ioItem(primary, inv.resultName)];
1294
- return { nodeId, output: primary, eachOutputs: [] };
1882
+ const outputs = results.flatMap(({ result }) => result.output === null ? [] : [result.output]);
1883
+ const representative = (_b = (_a = results.find(({ entry, result }) => entry.viaDefault && result.output !== null)) == null ? void 0 : _a.result.output) != null ? _b : outputs[0];
1884
+ if (representative === void 0) {
1885
+ node.io.outputs = [];
1886
+ return { nodeId, output: null, eachOutputs: [] };
1887
+ }
1888
+ const outcomesAgree = outputs.every(
1889
+ (output) => conceptKey(output.concept) === conceptKey(representative.concept) && output.multiplicity === representative.multiplicity
1890
+ );
1891
+ const declaredMultiplicity = typeof inv.outputMultiplicity === "number" || inv.outputMultiplicity === true ? inv.outputMultiplicity : blueprint.output.multiplicity;
1892
+ const shared = mintStuff(
1893
+ ctx,
1894
+ `${nodeId}:${representative.name}`,
1895
+ representative.name,
1896
+ outcomesAgree ? representative.concept : blueprint.output.concept,
1897
+ outcomesAgree ? representative.multiplicity : declaredMultiplicity
1898
+ );
1899
+ for (const output of outputs) {
1900
+ if (output.digest.startsWith(`${nodeId}/`)) renameStuff(ctx, output.digest, shared);
1901
+ }
1902
+ node.io.outputs = [ioItem(shared, inv.resultName)];
1903
+ return { nodeId, output: shared, eachOutputs: [] };
1904
+ }
1905
+ function renameStuff(ctx, from, to) {
1906
+ for (const node of ctx.nodes) {
1907
+ for (const item of [...node.io.inputs, ...node.io.outputs]) {
1908
+ if (item.digest === from) item.digest = to.digest;
1909
+ }
1910
+ }
1911
+ for (const edge of ctx.edges) {
1912
+ if (edge.source_stuff_digest === from) edge.source_stuff_digest = to.digest;
1913
+ if (edge.target_stuff_digest === from) edge.target_stuff_digest = to.digest;
1914
+ }
1915
+ ctx.stuffByDigest.delete(from);
1295
1916
  }
1296
1917
  function finishBatch(ctx, node, blueprint, nodeId, scope, inv) {
1297
1918
  var _a, _b, _c;
1298
1919
  const params = blueprint.batch_params;
1299
1920
  let listStuff = null;
1300
1921
  if (params.input_list_stuff_name !== "") {
1301
- const bound = lookupScope(scope, params.input_list_stuff_name);
1922
+ const bound = scope.get(params.input_list_stuff_name);
1302
1923
  if (bound !== void 0) {
1303
1924
  listStuff = bound;
1304
1925
  } else {
@@ -1368,7 +1989,7 @@ function walkInlineBatch(ctx, sub, domain, nodeId, parentId, scope) {
1368
1989
  const branchTail = refTail.slice(refTail.lastIndexOf(".") + 1);
1369
1990
  const branchResolution = resolvePipeRef(ctx.set, sub.pipe_code, domain);
1370
1991
  const branchBlueprint = branchResolution.kind === "resolved" ? branchResolution.blueprint : null;
1371
- const listBinding = lookupScope(scope, params.input_list_stuff_name);
1992
+ const listBinding = scope.get(params.input_list_stuff_name);
1372
1993
  const listConcept = (_c = (_b = listBinding == null ? void 0 : listBinding.concept) != null ? _b : (_a = branchBlueprint == null ? void 0 : branchBlueprint.inputs[params.input_item_stuff_name]) == null ? void 0 : _a.concept) != null ? _c : nativeConceptInfo("Anything");
1373
1994
  const outputSpec = (_d = branchBlueprint == null ? void 0 : branchBlueprint.output) != null ? _d : {
1374
1995
  concept: nativeConceptInfo("Anything"),