capdag 0.184.465 → 0.187.479
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/RULES.md +8 -8
- package/build-browser.js +3 -3
- package/cap-fab-renderer.js +354 -45
- package/capdag.js +198 -188
- package/capdag.test.js +153 -149
- package/package.json +1 -1
package/RULES.md
CHANGED
|
@@ -72,33 +72,33 @@ Examples:
|
|
|
72
72
|
|
|
73
73
|
## Validation Rules
|
|
74
74
|
|
|
75
|
-
### XV5: No Redefinition of Registry Media
|
|
75
|
+
### XV5: No Redefinition of Registry Media Defs
|
|
76
76
|
|
|
77
|
-
Inline media
|
|
77
|
+
Inline media defs in a capability's `media_defs` table must not redefine media defs that already exist in the global registry or built-in specs.
|
|
78
78
|
|
|
79
79
|
```javascript
|
|
80
|
-
const {
|
|
80
|
+
const { validateNoMediaDefRedefinitionSync, MEDIA_STRING } = require('capdag');
|
|
81
81
|
|
|
82
82
|
// This will fail - MEDIA_STRING is a built-in spec
|
|
83
|
-
const
|
|
83
|
+
const mediaDefs = {
|
|
84
84
|
[MEDIA_STRING]: { media_type: 'text/plain', title: 'My String' }
|
|
85
85
|
};
|
|
86
|
-
const result =
|
|
86
|
+
const result = validateNoMediaDefRedefinitionSync(mediaDefs);
|
|
87
87
|
// result: { valid: false, error: 'XV5: ...', redefines: ['media:textable'] }
|
|
88
88
|
|
|
89
89
|
// This is allowed - custom spec that doesn't exist
|
|
90
90
|
const customSpecs = {
|
|
91
91
|
'media:my-custom-type': { media_type: 'application/json', title: 'My Type' }
|
|
92
92
|
};
|
|
93
|
-
const customResult =
|
|
93
|
+
const customResult = validateNoMediaDefRedefinitionSync(customSpecs);
|
|
94
94
|
// result: { valid: true }
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
For server-side validation with registry access, use the async version:
|
|
98
98
|
```javascript
|
|
99
|
-
const {
|
|
99
|
+
const { validateNoMediaDefRedefinition } = require('capdag');
|
|
100
100
|
|
|
101
|
-
const result = await
|
|
101
|
+
const result = await validateNoMediaDefRedefinition(mediaDefs, {
|
|
102
102
|
registryLookup: async (urn) => await mediaStore.get(urn) !== null
|
|
103
103
|
});
|
|
104
104
|
```
|
package/build-browser.js
CHANGED
|
@@ -141,9 +141,9 @@ window.OutputValidator = OutputValidator;
|
|
|
141
141
|
window.CapValidator = CapValidator;
|
|
142
142
|
window.validateCapArgs = validateCapArgs;
|
|
143
143
|
window.RESERVED_CLI_FLAGS = RESERVED_CLI_FLAGS;
|
|
144
|
-
window.
|
|
145
|
-
window.
|
|
146
|
-
window.
|
|
144
|
+
window.MediaDef = MediaDef;
|
|
145
|
+
window.MediaDefError = MediaDefError;
|
|
146
|
+
window.MediaDefErrorCodes = MediaDefErrorCodes;
|
|
147
147
|
window.isBinaryCapUrn = isBinaryCapUrn;
|
|
148
148
|
window.isJSONCapUrn = isJSONCapUrn;
|
|
149
149
|
window.isStructuredCapUrn = isStructuredCapUrn;
|
package/cap-fab-renderer.js
CHANGED
|
@@ -742,7 +742,7 @@ function validateStrandStep(step, path) {
|
|
|
742
742
|
throw new Error(`CapFabRenderer: ${path}.step_type.Cap.output_is_sequence must be a boolean`);
|
|
743
743
|
}
|
|
744
744
|
} else {
|
|
745
|
-
assertString(body.
|
|
745
|
+
assertString(body.media_def, `${path}.step_type.${variant}.media_def`);
|
|
746
746
|
}
|
|
747
747
|
}
|
|
748
748
|
|
|
@@ -750,8 +750,8 @@ function validateStrandPayload(data) {
|
|
|
750
750
|
if (!data || typeof data !== 'object') {
|
|
751
751
|
throw new Error('CapFabRenderer strand mode: data must be an object');
|
|
752
752
|
}
|
|
753
|
-
assertString(data.
|
|
754
|
-
assertString(data.
|
|
753
|
+
assertString(data.source_media_urn, 'strand mode data.source_media_urn');
|
|
754
|
+
assertString(data.target_media_urn, 'strand mode data.target_media_urn');
|
|
755
755
|
assertArray(data.steps, 'strand mode data.steps');
|
|
756
756
|
data.steps.forEach((step, idx) => {
|
|
757
757
|
validateStrandStep(step, `strand mode data.steps[${idx}]`);
|
|
@@ -789,6 +789,18 @@ function validateBodyOutcome(outcome, path) {
|
|
|
789
789
|
}
|
|
790
790
|
}
|
|
791
791
|
|
|
792
|
+
function validateRunIOItem(item, path) {
|
|
793
|
+
assertObject(item, path);
|
|
794
|
+
assertString(item.label, `${path}.label`);
|
|
795
|
+
assertString(item.path, `${path}.path`);
|
|
796
|
+
if (typeof item.is_directory !== 'boolean') {
|
|
797
|
+
throw new Error(`CapFabRenderer run mode: ${path}.is_directory must be a boolean`);
|
|
798
|
+
}
|
|
799
|
+
if (typeof item.file_count !== 'number' || item.file_count < 0) {
|
|
800
|
+
throw new Error(`CapFabRenderer run mode: ${path}.file_count must be a non-negative number`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
792
804
|
function validateRunPayload(data) {
|
|
793
805
|
if (!data || typeof data !== 'object') {
|
|
794
806
|
throw new Error('CapFabRenderer run mode: data must be an object');
|
|
@@ -812,6 +824,23 @@ function validateRunPayload(data) {
|
|
|
812
824
|
if (typeof data.total_body_count !== 'number' || data.total_body_count < 0) {
|
|
813
825
|
throw new Error('CapFabRenderer run mode: data.total_body_count must be a non-negative number');
|
|
814
826
|
}
|
|
827
|
+
if (data.input_items !== undefined) {
|
|
828
|
+
assertArray(data.input_items, 'run mode data.input_items');
|
|
829
|
+
data.input_items.forEach((item, idx) => {
|
|
830
|
+
validateRunIOItem(item, `run mode data.input_items[${idx}]`);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
if (data.input_runs !== undefined) {
|
|
834
|
+
assertArray(data.input_runs, 'run mode data.input_runs');
|
|
835
|
+
data.input_runs.forEach((run, idx) => {
|
|
836
|
+
assertObject(run, `run mode data.input_runs[${idx}]`);
|
|
837
|
+
validateRunIOItem(run.input, `run mode data.input_runs[${idx}].input`);
|
|
838
|
+
assertArray(run.outputs, `run mode data.input_runs[${idx}].outputs`);
|
|
839
|
+
run.outputs.forEach((output, outIdx) => {
|
|
840
|
+
validateRunIOItem(output, `run mode data.input_runs[${idx}].outputs[${outIdx}]`);
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
}
|
|
815
844
|
}
|
|
816
845
|
|
|
817
846
|
function validateEditorGraphPayload(data) {
|
|
@@ -1087,7 +1116,7 @@ function classifyStrandCapSteps(steps) {
|
|
|
1087
1116
|
// edge to body_entry, and prev_node_id becomes body_exit.
|
|
1088
1117
|
//
|
|
1089
1118
|
// Node labels come from the `media_display_names` map keyed by the
|
|
1090
|
-
// step's canonical URN (or
|
|
1119
|
+
// step's canonical URN (or source_media_urn/target_media_urn for the boundary
|
|
1091
1120
|
// nodes). ForEach and Collect nodes display "for each" / "collect".
|
|
1092
1121
|
// Cap edges carry the cap title plus cardinality marker when either
|
|
1093
1122
|
// input or output is a sequence.
|
|
@@ -1095,8 +1124,8 @@ function buildStrandGraphData(data) {
|
|
|
1095
1124
|
validateStrandPayload(data);
|
|
1096
1125
|
|
|
1097
1126
|
const mediaDisplayNames = data.media_display_names || {};
|
|
1098
|
-
const
|
|
1099
|
-
const
|
|
1127
|
+
const sourceMediaUrn = canonicalMediaUrn(data.source_media_urn);
|
|
1128
|
+
const targetMediaUrn = canonicalMediaUrn(data.target_media_urn);
|
|
1100
1129
|
|
|
1101
1130
|
// Look up a display name for a media URN via the host-supplied map.
|
|
1102
1131
|
// Uses `MediaUrn.isEquivalent` so tag-order variation doesn't defeat
|
|
@@ -1151,9 +1180,9 @@ function buildStrandGraphData(data) {
|
|
|
1151
1180
|
edgeCounter++;
|
|
1152
1181
|
}
|
|
1153
1182
|
|
|
1154
|
-
// Entry node — the strand's source media
|
|
1183
|
+
// Entry node — the strand's source media def.
|
|
1155
1184
|
const inputSlotId = 'input_slot';
|
|
1156
|
-
addNode(inputSlotId, displayNameFor(
|
|
1185
|
+
addNode(inputSlotId, displayNameFor(sourceMediaUrn), sourceMediaUrn, 'strand-source');
|
|
1157
1186
|
|
|
1158
1187
|
let prevNodeId = inputSlotId;
|
|
1159
1188
|
|
|
@@ -1311,7 +1340,7 @@ function buildStrandGraphData(data) {
|
|
|
1311
1340
|
|
|
1312
1341
|
// Final output node. Mirrors plan_builder.rs:430-432.
|
|
1313
1342
|
const outputId = 'output';
|
|
1314
|
-
addNode(outputId, displayNameFor(
|
|
1343
|
+
addNode(outputId, displayNameFor(targetMediaUrn), targetMediaUrn, 'strand-target');
|
|
1315
1344
|
addEdge(prevNodeId, outputId, '', '', '', 'strand-cap-edge');
|
|
1316
1345
|
|
|
1317
1346
|
// Return the raw plan-builder topology. Strand mode collapses
|
|
@@ -1319,7 +1348,7 @@ function buildStrandGraphData(data) {
|
|
|
1319
1348
|
// `strandCytoscapeElements`); run mode keeps them as explicit
|
|
1320
1349
|
// nodes because body replicas anchor at the ForEach/Collect
|
|
1321
1350
|
// junctions.
|
|
1322
|
-
return { nodes, edges,
|
|
1351
|
+
return { nodes, edges, sourceMediaUrn, targetMediaUrn };
|
|
1323
1352
|
}
|
|
1324
1353
|
|
|
1325
1354
|
// Transform the plan-builder strand topology into the render shape
|
|
@@ -1349,7 +1378,7 @@ function buildStrandGraphData(data) {
|
|
|
1349
1378
|
// post-collect cap's `input_is_sequence=true` flag.
|
|
1350
1379
|
//
|
|
1351
1380
|
// 4. If the last cap step's `to_spec` is semantically equivalent
|
|
1352
|
-
// to the strand's `
|
|
1381
|
+
// to the strand's `target_media_urn` (via MediaUrn.isEquivalent),
|
|
1353
1382
|
// the separate `output` target node is dropped and the last
|
|
1354
1383
|
// cap edge lands on that merged endpoint. Removes the visible
|
|
1355
1384
|
// duplicate node.
|
|
@@ -1548,8 +1577,255 @@ function stripRunBackboneReplicaNodes(built, dropStepIds) {
|
|
|
1548
1577
|
return {
|
|
1549
1578
|
nodes: keptNodes,
|
|
1550
1579
|
edges: keptEdges,
|
|
1551
|
-
|
|
1552
|
-
|
|
1580
|
+
sourceMediaUrn: built.sourceMediaUrn,
|
|
1581
|
+
targetMediaUrn: built.targetMediaUrn,
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
function emptyRunBackbone() {
|
|
1586
|
+
return {
|
|
1587
|
+
nodes: [],
|
|
1588
|
+
edges: [],
|
|
1589
|
+
sourceMediaUrn: '',
|
|
1590
|
+
targetMediaUrn: '',
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function buildExternalInputRunGraphData(
|
|
1595
|
+
data,
|
|
1596
|
+
inputRuns,
|
|
1597
|
+
allOutcomes,
|
|
1598
|
+
visibleSuccess,
|
|
1599
|
+
visibleFailure,
|
|
1600
|
+
hiddenSuccessCount,
|
|
1601
|
+
hiddenFailureCount,
|
|
1602
|
+
displayNameFor
|
|
1603
|
+
) {
|
|
1604
|
+
if (inputRuns.length <= 1 || allOutcomes.length === 0) return null;
|
|
1605
|
+
|
|
1606
|
+
const capSteps = data.resolved_strand.steps
|
|
1607
|
+
.filter(step => Object.keys(step.step_type)[0] === 'Cap')
|
|
1608
|
+
.map(step => step.step_type.Cap);
|
|
1609
|
+
if (capSteps.length === 0) {
|
|
1610
|
+
throw new Error('CapFabRenderer run mode: external multi-input runs require at least one Cap step in resolved_strand.');
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
const visibleOutcomes = visibleSuccess.concat(visibleFailure);
|
|
1614
|
+
const CapUrn = requireHostDependency('CapUrn');
|
|
1615
|
+
const sourceCanonical = canonicalMediaUrn(data.resolved_strand.source_media_urn);
|
|
1616
|
+
const targetCanonical = canonicalMediaUrn(data.resolved_strand.target_media_urn);
|
|
1617
|
+
const anchorNodeId = 'external-input-anchor';
|
|
1618
|
+
const replicaNodes = [{
|
|
1619
|
+
group: 'nodes',
|
|
1620
|
+
data: {
|
|
1621
|
+
id: anchorNodeId,
|
|
1622
|
+
label: displayNameFor(sourceCanonical),
|
|
1623
|
+
fullUrn: sourceCanonical,
|
|
1624
|
+
},
|
|
1625
|
+
classes: 'strand-source',
|
|
1626
|
+
}];
|
|
1627
|
+
const replicaEdges = [];
|
|
1628
|
+
const showMoreNodes = [];
|
|
1629
|
+
|
|
1630
|
+
for (const outcome of allOutcomes) {
|
|
1631
|
+
if (outcome.body_index >= inputRuns.length) {
|
|
1632
|
+
throw new Error(
|
|
1633
|
+
`CapFabRenderer run mode: body_outcomes[body_index=${outcome.body_index}] exceeds input_runs length ${inputRuns.length}`
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
function buildReplica(outcome) {
|
|
1639
|
+
const runDef = inputRuns[outcome.body_index];
|
|
1640
|
+
const success = outcome.success;
|
|
1641
|
+
const nodeClass = success ? 'body-success' : 'body-failure';
|
|
1642
|
+
const edgeClass = success ? 'body-success' : 'body-failure';
|
|
1643
|
+
const edgeColor = success ? 'var(--graph-body-edge-success)' : 'var(--graph-body-edge-failure)';
|
|
1644
|
+
const bodyKey = `external-body-${outcome.body_index}`;
|
|
1645
|
+
const sourceNodeId = `${bodyKey}-input`;
|
|
1646
|
+
const sourceLabel = typeof outcome.title === 'string' && outcome.title.length > 0
|
|
1647
|
+
? outcome.title
|
|
1648
|
+
: runDef.input.label;
|
|
1649
|
+
|
|
1650
|
+
replicaNodes.push({
|
|
1651
|
+
group: 'nodes',
|
|
1652
|
+
data: {
|
|
1653
|
+
id: sourceNodeId,
|
|
1654
|
+
label: sourceLabel,
|
|
1655
|
+
fullUrn: runDef.input.path,
|
|
1656
|
+
bodyIndex: outcome.body_index,
|
|
1657
|
+
bodyTitle: sourceLabel,
|
|
1658
|
+
},
|
|
1659
|
+
classes: `${nodeClass} run-input-item`,
|
|
1660
|
+
});
|
|
1661
|
+
replicaEdges.push({
|
|
1662
|
+
group: 'edges',
|
|
1663
|
+
data: {
|
|
1664
|
+
id: `${bodyKey}-entry`,
|
|
1665
|
+
source: anchorNodeId,
|
|
1666
|
+
target: sourceNodeId,
|
|
1667
|
+
label: '',
|
|
1668
|
+
title: runDef.input.path,
|
|
1669
|
+
fullUrn: '',
|
|
1670
|
+
color: getCssVar('--graph-edge-color'),
|
|
1671
|
+
bodyIndex: outcome.body_index,
|
|
1672
|
+
},
|
|
1673
|
+
classes: edgeClass,
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
let traceEnd = capSteps.length;
|
|
1677
|
+
if (!success) {
|
|
1678
|
+
if (typeof outcome.failed_cap === 'string' && outcome.failed_cap.length > 0) {
|
|
1679
|
+
const failedCap = CapUrn.fromString(outcome.failed_cap);
|
|
1680
|
+
traceEnd = 0;
|
|
1681
|
+
for (let i = 0; i < capSteps.length; i++) {
|
|
1682
|
+
const candidate = CapUrn.fromString(capSteps[i].cap_urn);
|
|
1683
|
+
if (candidate.isEquivalent(failedCap)) {
|
|
1684
|
+
traceEnd = i + 1;
|
|
1685
|
+
break;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
} else {
|
|
1689
|
+
traceEnd = 0;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
let prevNodeId = sourceNodeId;
|
|
1694
|
+
for (let i = 0; i < traceEnd; i++) {
|
|
1695
|
+
const cap = capSteps[i];
|
|
1696
|
+
const targetMedia = canonicalMediaUrn(i === capSteps.length - 1 ? data.resolved_strand.target_media_urn : data.resolved_strand.steps.filter(step => Object.keys(step.step_type)[0] === 'Cap')[i].to_spec);
|
|
1697
|
+
const isLastExecutedStep = i === traceEnd - 1;
|
|
1698
|
+
const outputs = success ? runDef.outputs : [];
|
|
1699
|
+
|
|
1700
|
+
if (success && isLastExecutedStep && outputs.length > 0) {
|
|
1701
|
+
outputs.forEach((output, outputIdx) => {
|
|
1702
|
+
const outputNodeId = `${bodyKey}-output-${outputIdx}`;
|
|
1703
|
+
replicaNodes.push({
|
|
1704
|
+
group: 'nodes',
|
|
1705
|
+
data: {
|
|
1706
|
+
id: outputNodeId,
|
|
1707
|
+
label: output.label,
|
|
1708
|
+
fullUrn: output.path,
|
|
1709
|
+
bodyIndex: outcome.body_index,
|
|
1710
|
+
bodyTitle: sourceLabel,
|
|
1711
|
+
},
|
|
1712
|
+
classes: nodeClass,
|
|
1713
|
+
});
|
|
1714
|
+
replicaEdges.push({
|
|
1715
|
+
group: 'edges',
|
|
1716
|
+
data: {
|
|
1717
|
+
id: `${bodyKey}-output-edge-${i}-${outputIdx}`,
|
|
1718
|
+
source: prevNodeId,
|
|
1719
|
+
target: outputNodeId,
|
|
1720
|
+
label: outputIdx === 0 ? cap.title : '',
|
|
1721
|
+
title: cap.title,
|
|
1722
|
+
fullUrn: cap.cap_urn,
|
|
1723
|
+
color: edgeColor,
|
|
1724
|
+
bodyIndex: outcome.body_index,
|
|
1725
|
+
},
|
|
1726
|
+
classes: edgeClass,
|
|
1727
|
+
});
|
|
1728
|
+
});
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
const stepNodeId = `${bodyKey}-step-${i}`;
|
|
1733
|
+
replicaNodes.push({
|
|
1734
|
+
group: 'nodes',
|
|
1735
|
+
data: {
|
|
1736
|
+
id: stepNodeId,
|
|
1737
|
+
label: displayNameFor(isLastExecutedStep ? targetCanonical : targetMedia),
|
|
1738
|
+
fullUrn: isLastExecutedStep ? targetCanonical : targetMedia,
|
|
1739
|
+
bodyIndex: outcome.body_index,
|
|
1740
|
+
bodyTitle: sourceLabel,
|
|
1741
|
+
},
|
|
1742
|
+
classes: nodeClass,
|
|
1743
|
+
});
|
|
1744
|
+
replicaEdges.push({
|
|
1745
|
+
group: 'edges',
|
|
1746
|
+
data: {
|
|
1747
|
+
id: `${bodyKey}-step-edge-${i}`,
|
|
1748
|
+
source: prevNodeId,
|
|
1749
|
+
target: stepNodeId,
|
|
1750
|
+
label: cap.title,
|
|
1751
|
+
title: cap.title,
|
|
1752
|
+
fullUrn: cap.cap_urn,
|
|
1753
|
+
color: edgeColor,
|
|
1754
|
+
bodyIndex: outcome.body_index,
|
|
1755
|
+
},
|
|
1756
|
+
classes: edgeClass,
|
|
1757
|
+
});
|
|
1758
|
+
prevNodeId = stepNodeId;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
visibleOutcomes.forEach(buildReplica);
|
|
1763
|
+
|
|
1764
|
+
if (hiddenSuccessCount > 0) {
|
|
1765
|
+
showMoreNodes.push({
|
|
1766
|
+
group: 'nodes',
|
|
1767
|
+
data: {
|
|
1768
|
+
id: 'show-more-success',
|
|
1769
|
+
label: `+${hiddenSuccessCount} more succeeded`,
|
|
1770
|
+
fullUrn: '',
|
|
1771
|
+
showMoreGroup: 'success',
|
|
1772
|
+
hiddenCount: hiddenSuccessCount,
|
|
1773
|
+
},
|
|
1774
|
+
classes: 'show-more body-success',
|
|
1775
|
+
});
|
|
1776
|
+
replicaEdges.push({
|
|
1777
|
+
group: 'edges',
|
|
1778
|
+
data: {
|
|
1779
|
+
id: 'show-more-success-edge',
|
|
1780
|
+
source: anchorNodeId,
|
|
1781
|
+
target: 'show-more-success',
|
|
1782
|
+
label: '',
|
|
1783
|
+
title: '',
|
|
1784
|
+
fullUrn: '',
|
|
1785
|
+
color: 'var(--graph-body-edge-success)',
|
|
1786
|
+
},
|
|
1787
|
+
classes: 'body-success',
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
if (hiddenFailureCount > 0) {
|
|
1791
|
+
showMoreNodes.push({
|
|
1792
|
+
group: 'nodes',
|
|
1793
|
+
data: {
|
|
1794
|
+
id: 'show-more-failure',
|
|
1795
|
+
label: `+${hiddenFailureCount} failed`,
|
|
1796
|
+
fullUrn: '',
|
|
1797
|
+
showMoreGroup: 'failure',
|
|
1798
|
+
hiddenCount: hiddenFailureCount,
|
|
1799
|
+
},
|
|
1800
|
+
classes: 'show-more body-failure',
|
|
1801
|
+
});
|
|
1802
|
+
replicaEdges.push({
|
|
1803
|
+
group: 'edges',
|
|
1804
|
+
data: {
|
|
1805
|
+
id: 'show-more-failure-edge',
|
|
1806
|
+
source: anchorNodeId,
|
|
1807
|
+
target: 'show-more-failure',
|
|
1808
|
+
label: '',
|
|
1809
|
+
title: '',
|
|
1810
|
+
fullUrn: '',
|
|
1811
|
+
color: 'var(--graph-body-edge-failure)',
|
|
1812
|
+
},
|
|
1813
|
+
classes: 'body-failure',
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
return {
|
|
1818
|
+
strandBuilt: emptyRunBackbone(),
|
|
1819
|
+
replicaNodes,
|
|
1820
|
+
replicaEdges,
|
|
1821
|
+
showMoreNodes,
|
|
1822
|
+
totals: {
|
|
1823
|
+
hiddenSuccessCount,
|
|
1824
|
+
hiddenFailureCount,
|
|
1825
|
+
totalBodyCount: data.total_body_count,
|
|
1826
|
+
visibleSuccessCount: visibleSuccess.length,
|
|
1827
|
+
visibleFailureCount: visibleFailure.length,
|
|
1828
|
+
},
|
|
1553
1829
|
};
|
|
1554
1830
|
}
|
|
1555
1831
|
|
|
@@ -1566,19 +1842,36 @@ function buildRunGraphData(data) {
|
|
|
1566
1842
|
const strandBuiltRaw = buildStrandGraphData(strandInput);
|
|
1567
1843
|
let strandBuiltCollapsed = collapseStrandShapeTransitions(strandBuiltRaw);
|
|
1568
1844
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1845
|
+
const inputItems = Array.isArray(data.input_items) ? data.input_items : [];
|
|
1846
|
+
const inputRuns = Array.isArray(data.input_runs) ? data.input_runs : [];
|
|
1847
|
+
const inputReplicaNodes = [];
|
|
1848
|
+
const inputReplicaEdges = [];
|
|
1849
|
+
inputItems.forEach((item, idx) => {
|
|
1850
|
+
const nodeId = `input-item-${idx}`;
|
|
1851
|
+
inputReplicaNodes.push({
|
|
1852
|
+
group: 'nodes',
|
|
1853
|
+
data: {
|
|
1854
|
+
id: nodeId,
|
|
1855
|
+
label: item.label,
|
|
1856
|
+
fullUrn: item.path,
|
|
1857
|
+
inputIndex: idx,
|
|
1858
|
+
inputPath: item.path,
|
|
1859
|
+
},
|
|
1860
|
+
classes: 'run-input-item',
|
|
1861
|
+
});
|
|
1862
|
+
inputReplicaEdges.push({
|
|
1863
|
+
group: 'edges',
|
|
1864
|
+
data: {
|
|
1865
|
+
id: `input-item-edge-${idx}`,
|
|
1866
|
+
source: nodeId,
|
|
1867
|
+
target: 'input_slot',
|
|
1868
|
+
label: '',
|
|
1869
|
+
title: item.path,
|
|
1870
|
+
fullUrn: '',
|
|
1871
|
+
color: getCssVar('--graph-edge-color'),
|
|
1872
|
+
},
|
|
1873
|
+
});
|
|
1874
|
+
});
|
|
1582
1875
|
|
|
1583
1876
|
// Locate the ForEach/Collect span in the raw steps. Positional
|
|
1584
1877
|
// IDs survive the collapse (node IDs are `step_${i}` from the
|
|
@@ -1605,6 +1898,36 @@ function buildRunGraphData(data) {
|
|
|
1605
1898
|
const hiddenFailureCount = failures.length - visibleFailure.length;
|
|
1606
1899
|
const visibleOutcomes = visibleSuccess.concat(visibleFailure);
|
|
1607
1900
|
|
|
1901
|
+
// Look up a display name for a media URN via the host-supplied
|
|
1902
|
+
// `media_display_names` map. Uses `MediaUrn.isEquivalent` for
|
|
1903
|
+
// semantic URN equality.
|
|
1904
|
+
const MediaUrn = requireHostDependency('MediaUrn');
|
|
1905
|
+
const mediaDisplayNames = data.media_display_names || {};
|
|
1906
|
+
const displayEntries = [];
|
|
1907
|
+
for (const [urn, display] of Object.entries(mediaDisplayNames)) {
|
|
1908
|
+
if (typeof display !== 'string' || display.length === 0) continue;
|
|
1909
|
+
try {
|
|
1910
|
+
displayEntries.push({ media: MediaUrn.fromString(urn), display });
|
|
1911
|
+
} catch (_) { /* ignore malformed keys */ }
|
|
1912
|
+
}
|
|
1913
|
+
function displayNameFor(canonicalUrn) {
|
|
1914
|
+
return requireExplicitDisplayName(canonicalUrn, displayEntries, 'run node');
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
const externalInputRunBuilt = buildExternalInputRunGraphData(
|
|
1918
|
+
data,
|
|
1919
|
+
inputRuns,
|
|
1920
|
+
allOutcomes,
|
|
1921
|
+
visibleSuccess,
|
|
1922
|
+
visibleFailure,
|
|
1923
|
+
hiddenSuccessCount,
|
|
1924
|
+
hiddenFailureCount,
|
|
1925
|
+
displayNameFor
|
|
1926
|
+
);
|
|
1927
|
+
if (externalInputRunBuilt !== null) {
|
|
1928
|
+
return externalInputRunBuilt;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1608
1931
|
// Per-body replicas only fire when there's a ForEach AND at
|
|
1609
1932
|
// least one visible outcome. Without outcomes, the strand
|
|
1610
1933
|
// backbone renders the "plan preview" unchanged.
|
|
@@ -1628,8 +1951,8 @@ function buildRunGraphData(data) {
|
|
|
1628
1951
|
if (!shouldExpand) {
|
|
1629
1952
|
return {
|
|
1630
1953
|
strandBuilt: strandBuiltCollapsed,
|
|
1631
|
-
replicaNodes:
|
|
1632
|
-
replicaEdges:
|
|
1954
|
+
replicaNodes: inputReplicaNodes,
|
|
1955
|
+
replicaEdges: inputReplicaEdges,
|
|
1633
1956
|
showMoreNodes: [],
|
|
1634
1957
|
totals: {
|
|
1635
1958
|
hiddenSuccessCount,
|
|
@@ -1664,7 +1987,7 @@ function buildRunGraphData(data) {
|
|
|
1664
1987
|
// labels the anchor→entry edge on the first body.
|
|
1665
1988
|
//
|
|
1666
1989
|
// Without such a preceding sequence cap, the source itself is
|
|
1667
|
-
// already a list (e.g. `media:pdf;list`
|
|
1990
|
+
// already a list (e.g. `media:pdf;list` source_media_urn) and the
|
|
1668
1991
|
// ForEach iterates it directly.
|
|
1669
1992
|
let seqProducerStepIdx = -1;
|
|
1670
1993
|
let seqProducerStep = null;
|
|
@@ -1729,24 +2052,10 @@ function buildRunGraphData(data) {
|
|
|
1729
2052
|
|
|
1730
2053
|
const replicaNodes = [];
|
|
1731
2054
|
const replicaEdges = [];
|
|
2055
|
+
replicaNodes.push(...inputReplicaNodes);
|
|
2056
|
+
replicaEdges.push(...inputReplicaEdges);
|
|
1732
2057
|
let replicasBuiltCount = 0;
|
|
1733
2058
|
|
|
1734
|
-
// Look up a display name for a media URN via the host-supplied
|
|
1735
|
-
// `media_display_names` map. Uses `MediaUrn.isEquivalent` for
|
|
1736
|
-
// semantic URN equality.
|
|
1737
|
-
const MediaUrn = requireHostDependency('MediaUrn');
|
|
1738
|
-
const mediaDisplayNames = data.media_display_names || {};
|
|
1739
|
-
const displayEntries = [];
|
|
1740
|
-
for (const [urn, display] of Object.entries(mediaDisplayNames)) {
|
|
1741
|
-
if (typeof display !== 'string' || display.length === 0) continue;
|
|
1742
|
-
try {
|
|
1743
|
-
displayEntries.push({ media: MediaUrn.fromString(urn), display });
|
|
1744
|
-
} catch (_) { /* ignore malformed keys */ }
|
|
1745
|
-
}
|
|
1746
|
-
function displayNameFor(canonicalUrn) {
|
|
1747
|
-
return requireExplicitDisplayName(canonicalUrn, displayEntries, 'run node');
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
2059
|
// The per-body "entry" node represents one item of the
|
|
1751
2060
|
// sequence being iterated. Its URN is:
|
|
1752
2061
|
// * the sequence producer cap's `to_spec` (if such a cap
|