capdag 0.138.305 → 0.140.310
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/cap-graph-renderer.js +35 -35
- package/capdag.js +8 -23
- package/capdag.test.js +176 -83
- package/package.json +1 -1
package/cap-graph-renderer.js
CHANGED
|
@@ -108,24 +108,24 @@ function canonicalMediaUrn(mediaUrnString) {
|
|
|
108
108
|
return MediaUrn.fromString(mediaUrnString).toString();
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
lines.push(`${key}: ${value}`);
|
|
126
|
-
}
|
|
111
|
+
// Graph labels must be provided explicitly by the host. The renderer is not
|
|
112
|
+
// allowed to synthesize user-facing labels from URNs.
|
|
113
|
+
function mediaNodeLabel() {
|
|
114
|
+
throw new Error(
|
|
115
|
+
'CapGraphRenderer: mediaNodeLabel() is no longer supported. ' +
|
|
116
|
+
'Pass explicit media titles/display names to the renderer.'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function requireExplicitDisplayName(canonicalUrn, displayEntries, context) {
|
|
121
|
+
const MediaUrn = requireHostDependency('MediaUrn');
|
|
122
|
+
const candidate = MediaUrn.fromString(canonicalUrn);
|
|
123
|
+
for (const entry of displayEntries) {
|
|
124
|
+
if (candidate.isEquivalent(entry.media)) return entry.display;
|
|
127
125
|
}
|
|
128
|
-
|
|
126
|
+
throw new Error(
|
|
127
|
+
`CapGraphRenderer: missing explicit display name for ${context} '${canonicalUrn}'`
|
|
128
|
+
);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// =============================================================================
|
|
@@ -461,6 +461,9 @@ function validateBrowseData(data) {
|
|
|
461
461
|
assertString(cap.urn, `browse mode data[${idx}].urn`);
|
|
462
462
|
assertString(cap.in_spec, `browse mode data[${idx}].in_spec (cap urn: ${cap.urn})`);
|
|
463
463
|
assertString(cap.out_spec, `browse mode data[${idx}].out_spec (cap urn: ${cap.urn})`);
|
|
464
|
+
assertString(cap.title, `browse mode data[${idx}].title (cap urn: ${cap.urn})`);
|
|
465
|
+
assertString(cap.in_media_title, `browse mode data[${idx}].in_media_title (cap urn: ${cap.urn})`);
|
|
466
|
+
assertString(cap.out_media_title, `browse mode data[${idx}].out_media_title (cap urn: ${cap.urn})`);
|
|
464
467
|
});
|
|
465
468
|
}
|
|
466
469
|
|
|
@@ -631,6 +634,7 @@ function validateResolvedMachinePayload(data) {
|
|
|
631
634
|
}
|
|
632
635
|
assertString(n.id, `machine mode data.strands[${sIdx}].nodes[${nIdx}].id`);
|
|
633
636
|
assertString(n.urn, `machine mode data.strands[${sIdx}].nodes[${nIdx}].urn`);
|
|
637
|
+
assertString(n.title, `machine mode data.strands[${sIdx}].nodes[${nIdx}].title`);
|
|
634
638
|
});
|
|
635
639
|
assertArray(strand.edges, `machine mode data.strands[${sIdx}].edges`);
|
|
636
640
|
strand.edges.forEach((e, eIdx) => {
|
|
@@ -639,6 +643,7 @@ function validateResolvedMachinePayload(data) {
|
|
|
639
643
|
}
|
|
640
644
|
assertString(e.alias, `machine mode data.strands[${sIdx}].edges[${eIdx}].alias`);
|
|
641
645
|
assertString(e.cap_urn, `machine mode data.strands[${sIdx}].edges[${eIdx}].cap_urn`);
|
|
646
|
+
assertString(e.title, `machine mode data.strands[${sIdx}].edges[${eIdx}].title`);
|
|
642
647
|
if (typeof e.is_loop !== 'boolean') {
|
|
643
648
|
throw new Error(`CapGraphRenderer machine mode: data.strands[${sIdx}].edges[${eIdx}].is_loop must be boolean`);
|
|
644
649
|
}
|
|
@@ -744,6 +749,13 @@ function buildBrowseGraphData(capabilities) {
|
|
|
744
749
|
});
|
|
745
750
|
|
|
746
751
|
const nodes = Array.from(nodesMap.values());
|
|
752
|
+
for (const node of nodes) {
|
|
753
|
+
if (!mediaTitles.has(node.id)) {
|
|
754
|
+
throw new Error(
|
|
755
|
+
`CapGraphRenderer browse mode: missing explicit media title for '${node.id}'`
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
747
759
|
|
|
748
760
|
const adjacency = new Map();
|
|
749
761
|
const reverseAdj = new Map();
|
|
@@ -762,8 +774,8 @@ function browseCytoscapeElements(built) {
|
|
|
762
774
|
group: 'nodes',
|
|
763
775
|
data: {
|
|
764
776
|
id: node.id,
|
|
765
|
-
label:
|
|
766
|
-
mediaTitle: built.mediaTitles.get(node.id)
|
|
777
|
+
label: built.mediaTitles.get(node.id),
|
|
778
|
+
mediaTitle: built.mediaTitles.get(node.id),
|
|
767
779
|
fullUrn: node.id,
|
|
768
780
|
},
|
|
769
781
|
}));
|
|
@@ -863,11 +875,7 @@ function buildStrandGraphData(data) {
|
|
|
863
875
|
}
|
|
864
876
|
}
|
|
865
877
|
function displayNameFor(canonicalUrn) {
|
|
866
|
-
|
|
867
|
-
for (const entry of displayEntries) {
|
|
868
|
-
if (candidate.isEquivalent(entry.media)) return entry.display;
|
|
869
|
-
}
|
|
870
|
-
return mediaNodeLabel(canonicalUrn);
|
|
878
|
+
return requireExplicitDisplayName(canonicalUrn, displayEntries, 'strand node');
|
|
871
879
|
}
|
|
872
880
|
|
|
873
881
|
const nodes = [];
|
|
@@ -1497,11 +1505,7 @@ function buildRunGraphData(data) {
|
|
|
1497
1505
|
} catch (_) { /* ignore malformed keys */ }
|
|
1498
1506
|
}
|
|
1499
1507
|
function displayNameFor(canonicalUrn) {
|
|
1500
|
-
|
|
1501
|
-
for (const entry of displayEntries) {
|
|
1502
|
-
if (candidate.isEquivalent(entry.media)) return entry.display;
|
|
1503
|
-
}
|
|
1504
|
-
return mediaNodeLabel(canonicalUrn);
|
|
1508
|
+
return requireExplicitDisplayName(canonicalUrn, displayEntries, 'run node');
|
|
1505
1509
|
}
|
|
1506
1510
|
|
|
1507
1511
|
// The per-body "entry" node represents one item of the
|
|
@@ -1946,7 +1950,7 @@ function buildResolvedMachineGraphData(data) {
|
|
|
1946
1950
|
group: 'nodes',
|
|
1947
1951
|
data: {
|
|
1948
1952
|
id: node.id,
|
|
1949
|
-
label:
|
|
1953
|
+
label: node.title,
|
|
1950
1954
|
fullUrn: node.urn,
|
|
1951
1955
|
strandIndex: strandIdx,
|
|
1952
1956
|
},
|
|
@@ -1957,11 +1961,7 @@ function buildResolvedMachineGraphData(data) {
|
|
|
1957
1961
|
for (const edge of strand.edges) {
|
|
1958
1962
|
const cardinality = edge.is_loop ? 'n\u21921' : '1\u21921';
|
|
1959
1963
|
const capUrn = edge.cap_urn;
|
|
1960
|
-
|
|
1961
|
-
// pre-resolve a friendlier title via `step_titles` on the
|
|
1962
|
-
// proto message and use it elsewhere in the UI; the graph
|
|
1963
|
-
// renderer is intentionally registry-free.
|
|
1964
|
-
const capTitle = capUrn;
|
|
1964
|
+
const capTitle = edge.title;
|
|
1965
1965
|
const label = cardinality === '1\u21921'
|
|
1966
1966
|
? edge.alias
|
|
1967
1967
|
: `${edge.alias} (${cardinality})`;
|
package/capdag.js
CHANGED
|
@@ -890,11 +890,10 @@ const MEDIA_YAML_LIST_RECORD = 'media:list;record;textable;yaml';
|
|
|
890
890
|
const MEDIA_CSV = 'media:csv;list;record;textable';
|
|
891
891
|
const MEDIA_CSV_LIST = 'media:csv;list;textable';
|
|
892
892
|
|
|
893
|
-
// File path
|
|
894
|
-
//
|
|
893
|
+
// File path type — for arguments that represent filesystem paths.
|
|
894
|
+
// There is a single media URN; cardinality (single file vs many files)
|
|
895
|
+
// is carried on the wire via is_sequence, not via URN tags.
|
|
895
896
|
const MEDIA_FILE_PATH = 'media:file-path;textable';
|
|
896
|
-
// Media URN for an array of file paths - textable with list marker
|
|
897
|
-
const MEDIA_FILE_PATH_ARRAY = 'media:file-path;list;textable';
|
|
898
897
|
|
|
899
898
|
// Semantic text input types - distinguished by their purpose/context
|
|
900
899
|
// Media URN for model spec (provider:model format, HuggingFace name, etc.) - scalar by default
|
|
@@ -1095,25 +1094,12 @@ class MediaUrn {
|
|
|
1095
1094
|
}
|
|
1096
1095
|
|
|
1097
1096
|
/**
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1097
|
+
* True if this URN specializes `media:file-path`. There is a single
|
|
1098
|
+
* file-path media URN; cardinality (single file vs many) is carried on
|
|
1099
|
+
* the wire via `is_sequence`, not via URN tags.
|
|
1100
1100
|
* @returns {boolean}
|
|
1101
1101
|
*/
|
|
1102
|
-
isFilePath() { return this._hasMarkerTag('file-path')
|
|
1103
|
-
|
|
1104
|
-
/**
|
|
1105
|
-
* Check if this represents a file path array type.
|
|
1106
|
-
* Returns true if the "file-path" marker tag is present AND has list marker.
|
|
1107
|
-
* @returns {boolean}
|
|
1108
|
-
*/
|
|
1109
|
-
isFilePathArray() { return this._hasMarkerTag('file-path') && this.isList(); }
|
|
1110
|
-
|
|
1111
|
-
/**
|
|
1112
|
-
* Check if this represents any file path type (single or array).
|
|
1113
|
-
* Returns true if "file-path" marker tag is present.
|
|
1114
|
-
* @returns {boolean}
|
|
1115
|
-
*/
|
|
1116
|
-
isAnyFilePath() { return this._hasMarkerTag('file-path'); }
|
|
1102
|
+
isFilePath() { return this._hasMarkerTag('file-path'); }
|
|
1117
1103
|
|
|
1118
1104
|
/**
|
|
1119
1105
|
* Check if this represents a collection type.
|
|
@@ -5675,9 +5661,8 @@ module.exports = {
|
|
|
5675
5661
|
MEDIA_LLM_INFERENCE_OUTPUT,
|
|
5676
5662
|
MEDIA_IMAGE_DESCRIPTION,
|
|
5677
5663
|
MEDIA_TRANSCRIPTION_OUTPUT,
|
|
5678
|
-
// File path
|
|
5664
|
+
// File path type — single URN; cardinality lives on is_sequence.
|
|
5679
5665
|
MEDIA_FILE_PATH,
|
|
5680
|
-
MEDIA_FILE_PATH_ARRAY,
|
|
5681
5666
|
MEDIA_MLX_MODEL_PATH,
|
|
5682
5667
|
// Collection types
|
|
5683
5668
|
MEDIA_COLLECTION,
|
package/capdag.test.js
CHANGED
|
@@ -24,7 +24,7 @@ const {
|
|
|
24
24
|
MEDIA_HTML, MEDIA_XML, MEDIA_JSON, MEDIA_YAML, MEDIA_JSON_SCHEMA,
|
|
25
25
|
MEDIA_MODEL_SPEC, MEDIA_AVAILABILITY_OUTPUT, MEDIA_PATH_OUTPUT,
|
|
26
26
|
MEDIA_LLM_INFERENCE_OUTPUT,
|
|
27
|
-
MEDIA_FILE_PATH,
|
|
27
|
+
MEDIA_FILE_PATH,
|
|
28
28
|
MEDIA_COLLECTION, MEDIA_COLLECTION_LIST,
|
|
29
29
|
MEDIA_DECISION,
|
|
30
30
|
MEDIA_AUDIO_SPEECH
|
|
@@ -2453,34 +2453,15 @@ function test1298_isBool() {
|
|
|
2453
2453
|
assert(!MediaUrn.fromString(MEDIA_IDENTITY).isBool(), 'MEDIA_IDENTITY should not be bool');
|
|
2454
2454
|
}
|
|
2455
2455
|
|
|
2456
|
-
// TEST1299:
|
|
2456
|
+
// TEST1299: isFilePath returns true for the single file-path media URN,
|
|
2457
|
+
// false for everything else. There is no "array" variant — cardinality is
|
|
2458
|
+
// carried by is_sequence on the wire, not by URN tags.
|
|
2457
2459
|
function test1299_isFilePath() {
|
|
2458
2460
|
assert(MediaUrn.fromString(MEDIA_FILE_PATH).isFilePath(), 'MEDIA_FILE_PATH should be file-path');
|
|
2459
|
-
// Array file-path is NOT isFilePath (it's isFilePathArray)
|
|
2460
|
-
assert(!MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isFilePath(), 'MEDIA_FILE_PATH_ARRAY should not be isFilePath');
|
|
2461
|
-
// Non-file-path types
|
|
2462
2461
|
assert(!MediaUrn.fromString(MEDIA_STRING).isFilePath(), 'MEDIA_STRING should not be file-path');
|
|
2463
2462
|
assert(!MediaUrn.fromString(MEDIA_IDENTITY).isFilePath(), 'MEDIA_IDENTITY should not be file-path');
|
|
2464
2463
|
}
|
|
2465
2464
|
|
|
2466
|
-
// TEST1300: is_file_path_array returns true for list file-path, false for scalar
|
|
2467
|
-
function test1300_isFilePathArray() {
|
|
2468
|
-
assert(MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isFilePathArray(), 'MEDIA_FILE_PATH_ARRAY should be file-path-array');
|
|
2469
|
-
// Scalar file-path is NOT isFilePathArray
|
|
2470
|
-
assert(!MediaUrn.fromString(MEDIA_FILE_PATH).isFilePathArray(), 'MEDIA_FILE_PATH should not be isFilePathArray');
|
|
2471
|
-
// Non-file-path types
|
|
2472
|
-
assert(!MediaUrn.fromString(MEDIA_STRING_LIST).isFilePathArray(), 'MEDIA_STRING_LIST should not be file-path-array');
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
// TEST1301: is_any_file_path returns true for both scalar and array file-path
|
|
2476
|
-
function test1301_isAnyFilePath() {
|
|
2477
|
-
assert(MediaUrn.fromString(MEDIA_FILE_PATH).isAnyFilePath(), 'MEDIA_FILE_PATH should be any-file-path');
|
|
2478
|
-
assert(MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isAnyFilePath(), 'MEDIA_FILE_PATH_ARRAY should be any-file-path');
|
|
2479
|
-
// Non-file-path types
|
|
2480
|
-
assert(!MediaUrn.fromString(MEDIA_STRING).isAnyFilePath(), 'MEDIA_STRING should not be any-file-path');
|
|
2481
|
-
assert(!MediaUrn.fromString(MEDIA_STRING_LIST).isAnyFilePath(), 'MEDIA_STRING_LIST should not be any-file-path');
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
2465
|
// Mirror-specific coverage: isCollection returns true when collection marker tag is present
|
|
2485
2466
|
// Mirror-specific coverage: N/A for JS (MEDIA_COLLECTION constants removed - no longer exists)
|
|
2486
2467
|
function testisCollection() {
|
|
@@ -3872,6 +3853,7 @@ const {
|
|
|
3872
3853
|
cardinalityFromCap: rendererCardinalityFromCap,
|
|
3873
3854
|
canonicalMediaUrn: rendererCanonicalMediaUrn,
|
|
3874
3855
|
mediaNodeLabel: rendererMediaNodeLabel,
|
|
3856
|
+
buildBrowseGraphData: rendererBuildBrowseGraphData,
|
|
3875
3857
|
buildStrandGraphData: rendererBuildStrandGraphData,
|
|
3876
3858
|
collapseStrandShapeTransitions: rendererCollapseStrandShapeTransitions,
|
|
3877
3859
|
buildRunGraphData: rendererBuildRunGraphData,
|
|
@@ -4018,24 +4000,41 @@ function testRenderer_canonicalMediaUrn_rejectsCapUrn() {
|
|
|
4018
4000
|
assert(threw, 'canonicalMediaUrn must reject non-media URNs');
|
|
4019
4001
|
}
|
|
4020
4002
|
|
|
4021
|
-
function
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4003
|
+
function testRenderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
|
|
4004
|
+
let threw = false;
|
|
4005
|
+
let message = '';
|
|
4006
|
+
try {
|
|
4007
|
+
rendererMediaNodeLabel('media:video;quant=q4');
|
|
4008
|
+
} catch (e) {
|
|
4009
|
+
threw = true;
|
|
4010
|
+
message = e.message || '';
|
|
4011
|
+
}
|
|
4012
|
+
assert(threw, 'mediaNodeLabel must reject URN-derived labels');
|
|
4013
|
+
assert(message.includes('no longer supported'),
|
|
4014
|
+
'error must explain that explicit titles are required');
|
|
4030
4015
|
}
|
|
4031
4016
|
|
|
4032
|
-
function
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4017
|
+
function testRenderer_buildBrowseGraphData_rejectsMissingMediaTitles() {
|
|
4018
|
+
let threw = false;
|
|
4019
|
+
let message = '';
|
|
4020
|
+
try {
|
|
4021
|
+
rendererBuildBrowseGraphData([
|
|
4022
|
+
{
|
|
4023
|
+
urn: 'cap:in="media:pdf";op=extract;out="media:txt;textable"',
|
|
4024
|
+
title: 'Extract Text',
|
|
4025
|
+
in_spec: 'media:pdf',
|
|
4026
|
+
out_spec: 'media:txt;textable',
|
|
4027
|
+
in_media_title: 'PDF',
|
|
4028
|
+
out_media_title: '',
|
|
4029
|
+
},
|
|
4030
|
+
]);
|
|
4031
|
+
} catch (e) {
|
|
4032
|
+
threw = true;
|
|
4033
|
+
message = e.message || '';
|
|
4034
|
+
}
|
|
4035
|
+
assert(threw, 'browse builder must reject missing explicit media titles');
|
|
4036
|
+
assert(message.includes('out_media_title'),
|
|
4037
|
+
'error must identify the missing explicit media title field');
|
|
4039
4038
|
}
|
|
4040
4039
|
|
|
4041
4040
|
// ---------------- strand builder ----------------
|
|
@@ -4156,18 +4155,27 @@ function findEdge(edges, source, target) {
|
|
|
4156
4155
|
return edges.find(e => e.source === source && e.target === target);
|
|
4157
4156
|
}
|
|
4158
4157
|
|
|
4158
|
+
function withMediaDisplayNames(payload, mediaDisplayNames) {
|
|
4159
|
+
return Object.assign({}, payload, {
|
|
4160
|
+
media_display_names: Object.assign({}, mediaDisplayNames),
|
|
4161
|
+
});
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4159
4164
|
function testRenderer_buildStrandGraphData_singleCapPlain() {
|
|
4160
4165
|
// Minimal strand with one plain 1→1 cap. Plan builder produces:
|
|
4161
4166
|
// input_slot → step_0 (cap) → output
|
|
4162
4167
|
// (two edges, three nodes). No cardinality marker in the cap label
|
|
4163
4168
|
// because input_is_sequence == output_is_sequence == false.
|
|
4164
|
-
const payload = {
|
|
4169
|
+
const payload = withMediaDisplayNames({
|
|
4165
4170
|
source_spec: 'media:a',
|
|
4166
4171
|
target_spec: 'media:b',
|
|
4167
4172
|
steps: [
|
|
4168
4173
|
makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
|
|
4169
4174
|
],
|
|
4170
|
-
}
|
|
4175
|
+
}, {
|
|
4176
|
+
'media:a': 'Source A',
|
|
4177
|
+
'media:b': 'Target B',
|
|
4178
|
+
});
|
|
4171
4179
|
const built = rendererBuildStrandGraphData(payload);
|
|
4172
4180
|
const nodeIds = built.nodes.map(n => n.id).sort();
|
|
4173
4181
|
assertEqual(JSON.stringify(nodeIds), JSON.stringify(['input_slot', 'output', 'step_0']),
|
|
@@ -4183,13 +4191,16 @@ function testRenderer_buildStrandGraphData_singleCapPlain() {
|
|
|
4183
4191
|
function testRenderer_buildStrandGraphData_sequenceShowsCardinality() {
|
|
4184
4192
|
// A cap with input_is_sequence=true MUST emit "(n→1)" on its edge
|
|
4185
4193
|
// label.
|
|
4186
|
-
const payload = {
|
|
4194
|
+
const payload = withMediaDisplayNames({
|
|
4187
4195
|
source_spec: 'media:a;list',
|
|
4188
4196
|
target_spec: 'media:b',
|
|
4189
4197
|
steps: [
|
|
4190
4198
|
makeCapStep('cap:in="media:a;list";op=x;out="media:b"', 'x', 'media:a;list', 'media:b', true, false),
|
|
4191
4199
|
],
|
|
4192
|
-
}
|
|
4200
|
+
}, {
|
|
4201
|
+
'media:a;list': 'Source A List',
|
|
4202
|
+
'media:b': 'Target B',
|
|
4203
|
+
});
|
|
4193
4204
|
const built = rendererBuildStrandGraphData(payload);
|
|
4194
4205
|
const capEdge = findEdge(built.edges, 'input_slot', 'step_0');
|
|
4195
4206
|
assert(capEdge !== undefined, 'cap edge exists');
|
|
@@ -4210,7 +4221,7 @@ function testRenderer_buildStrandGraphData_foreachCollectSpan() {
|
|
|
4210
4221
|
// edges.) ForEach and Collect are REAL nodes in the graph, not
|
|
4211
4222
|
// labels on cap edges — they're distinct processing units in the
|
|
4212
4223
|
// plan. This mirrors capdag's plan_builder.rs exactly.
|
|
4213
|
-
const payload = {
|
|
4224
|
+
const payload = withMediaDisplayNames({
|
|
4214
4225
|
source_spec: 'media:pdf;list',
|
|
4215
4226
|
target_spec: 'media:txt;list',
|
|
4216
4227
|
steps: [
|
|
@@ -4218,7 +4229,11 @@ function testRenderer_buildStrandGraphData_foreachCollectSpan() {
|
|
|
4218
4229
|
makeCapStep('cap:in="media:pdf";op=extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
|
|
4219
4230
|
makeCollectStep('media:txt'),
|
|
4220
4231
|
],
|
|
4221
|
-
}
|
|
4232
|
+
}, {
|
|
4233
|
+
'media:pdf;list': 'PDF List',
|
|
4234
|
+
'media:txt': 'Plain Text',
|
|
4235
|
+
'media:txt;list': 'Text List',
|
|
4236
|
+
});
|
|
4222
4237
|
const built = rendererBuildStrandGraphData(payload);
|
|
4223
4238
|
const nodeIds = built.nodes.map(n => n.id).sort();
|
|
4224
4239
|
assertEqual(JSON.stringify(nodeIds),
|
|
@@ -4248,14 +4263,18 @@ function testRenderer_buildStrandGraphData_standaloneCollect() {
|
|
|
4248
4263
|
// Strand with a standalone Collect (no enclosing ForEach). Plan
|
|
4249
4264
|
// builder creates a Collect node consuming prev directly — plain
|
|
4250
4265
|
// direct edge, no iteration/collection semantics.
|
|
4251
|
-
const payload = {
|
|
4266
|
+
const payload = withMediaDisplayNames({
|
|
4252
4267
|
source_spec: 'media:a',
|
|
4253
4268
|
target_spec: 'media:b;list',
|
|
4254
4269
|
steps: [
|
|
4255
4270
|
makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
|
|
4256
4271
|
makeCollectStep('media:b'),
|
|
4257
4272
|
],
|
|
4258
|
-
}
|
|
4273
|
+
}, {
|
|
4274
|
+
'media:a': 'Source A',
|
|
4275
|
+
'media:b': 'Target B',
|
|
4276
|
+
'media:b;list': 'Target B List',
|
|
4277
|
+
});
|
|
4259
4278
|
const built = rendererBuildStrandGraphData(payload);
|
|
4260
4279
|
assert(findEdge(built.edges, 'input_slot', 'step_0') !== undefined,
|
|
4261
4280
|
'cap edge input_slot → step_0');
|
|
@@ -4272,7 +4291,7 @@ function testRenderer_buildStrandGraphData_unclosedForEachBody() {
|
|
|
4272
4291
|
// builder's "unclosed ForEach" branch creates a ForEach node
|
|
4273
4292
|
// connecting Cap_a to Cap_b via iteration, with prev becoming the
|
|
4274
4293
|
// body exit (Cap_b).
|
|
4275
|
-
const payload = {
|
|
4294
|
+
const payload = withMediaDisplayNames({
|
|
4276
4295
|
source_spec: 'media:a',
|
|
4277
4296
|
target_spec: 'media:c',
|
|
4278
4297
|
steps: [
|
|
@@ -4280,7 +4299,11 @@ function testRenderer_buildStrandGraphData_unclosedForEachBody() {
|
|
|
4280
4299
|
makeForEachStep('media:b'),
|
|
4281
4300
|
makeCapStep('cap:in="media:b";op=b;out="media:c"', 'b', 'media:b', 'media:c', false, false),
|
|
4282
4301
|
],
|
|
4283
|
-
}
|
|
4302
|
+
}, {
|
|
4303
|
+
'media:a': 'Source A',
|
|
4304
|
+
'media:b': 'Intermediate B',
|
|
4305
|
+
'media:c': 'Target C',
|
|
4306
|
+
});
|
|
4284
4307
|
const built = rendererBuildStrandGraphData(payload);
|
|
4285
4308
|
// Cap_a connects from input_slot.
|
|
4286
4309
|
assert(findEdge(built.edges, 'input_slot', 'step_0') !== undefined,
|
|
@@ -4305,7 +4328,7 @@ function testRenderer_buildStrandGraphData_nestedForEachThrows() {
|
|
|
4305
4328
|
// ForEach is an illegal nesting per plan_builder. The renderer
|
|
4306
4329
|
// must throw the same error to surface the issue rather than
|
|
4307
4330
|
// render a malformed graph.
|
|
4308
|
-
const payload = {
|
|
4331
|
+
const payload = withMediaDisplayNames({
|
|
4309
4332
|
source_spec: 'media:a;list',
|
|
4310
4333
|
target_spec: 'media:a',
|
|
4311
4334
|
steps: [
|
|
@@ -4313,7 +4336,10 @@ function testRenderer_buildStrandGraphData_nestedForEachThrows() {
|
|
|
4313
4336
|
makeForEachStep('media:a'),
|
|
4314
4337
|
makeCapStep('cap:in="media:a";op=x;out="media:a"', 'x', 'media:a', 'media:a', false, false),
|
|
4315
4338
|
],
|
|
4316
|
-
}
|
|
4339
|
+
}, {
|
|
4340
|
+
'media:a;list': 'Source A List',
|
|
4341
|
+
'media:a': 'Source A',
|
|
4342
|
+
});
|
|
4317
4343
|
let threw = false;
|
|
4318
4344
|
try {
|
|
4319
4345
|
rendererBuildStrandGraphData(payload);
|
|
@@ -4339,7 +4365,7 @@ function testRenderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
|
|
|
4339
4365
|
// Expected render shape: 3 nodes (input_slot, step_1, output),
|
|
4340
4366
|
// with the entry edge labeled "extract" and an unlabeled
|
|
4341
4367
|
// connector bridge to the output.
|
|
4342
|
-
const payload = {
|
|
4368
|
+
const payload = withMediaDisplayNames({
|
|
4343
4369
|
source_spec: 'media:pdf;list',
|
|
4344
4370
|
target_spec: 'media:txt;list',
|
|
4345
4371
|
steps: [
|
|
@@ -4347,7 +4373,11 @@ function testRenderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
|
|
|
4347
4373
|
makeCapStep('cap:in="media:pdf";op=extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
|
|
4348
4374
|
makeCollectStep('media:txt'),
|
|
4349
4375
|
],
|
|
4350
|
-
}
|
|
4376
|
+
}, {
|
|
4377
|
+
'media:pdf;list': 'PDF List',
|
|
4378
|
+
'media:txt': 'Plain Text',
|
|
4379
|
+
'media:txt;list': 'Text List',
|
|
4380
|
+
});
|
|
4351
4381
|
const built = rendererBuildStrandGraphData(payload);
|
|
4352
4382
|
const collapsed = rendererCollapseStrandShapeTransitions(built);
|
|
4353
4383
|
|
|
@@ -4384,7 +4414,7 @@ function testRenderer_collapseStrand_unclosedForEachBodyCollapses() {
|
|
|
4384
4414
|
// no relabeling.
|
|
4385
4415
|
//
|
|
4386
4416
|
// Final: 3 nodes (input_slot, step_0, step_2), 2 edges.
|
|
4387
|
-
const payload = {
|
|
4417
|
+
const payload = withMediaDisplayNames({
|
|
4388
4418
|
source_spec: 'media:a',
|
|
4389
4419
|
target_spec: 'media:c',
|
|
4390
4420
|
steps: [
|
|
@@ -4392,7 +4422,11 @@ function testRenderer_collapseStrand_unclosedForEachBodyCollapses() {
|
|
|
4392
4422
|
makeForEachStep('media:b'),
|
|
4393
4423
|
makeCapStep('cap:in="media:b";op=b;out="media:c"', 'b', 'media:b', 'media:c', false, false),
|
|
4394
4424
|
],
|
|
4395
|
-
}
|
|
4425
|
+
}, {
|
|
4426
|
+
'media:a': 'Source A',
|
|
4427
|
+
'media:b': 'Intermediate B',
|
|
4428
|
+
'media:c': 'Target C',
|
|
4429
|
+
});
|
|
4396
4430
|
const built = rendererBuildStrandGraphData(payload);
|
|
4397
4431
|
const collapsed = rendererCollapseStrandShapeTransitions(built);
|
|
4398
4432
|
|
|
@@ -4437,14 +4471,18 @@ function testRenderer_collapseStrand_standaloneCollectCollapses() {
|
|
|
4437
4471
|
// cap is not inside any foreach body.
|
|
4438
4472
|
//
|
|
4439
4473
|
// Final: 3 nodes (input_slot, step_0, output), 2 edges.
|
|
4440
|
-
const payload = {
|
|
4474
|
+
const payload = withMediaDisplayNames({
|
|
4441
4475
|
source_spec: 'media:a',
|
|
4442
4476
|
target_spec: 'media:b;list',
|
|
4443
4477
|
steps: [
|
|
4444
4478
|
makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
|
|
4445
4479
|
makeCollectStep('media:b'),
|
|
4446
4480
|
],
|
|
4447
|
-
}
|
|
4481
|
+
}, {
|
|
4482
|
+
'media:a': 'Source A',
|
|
4483
|
+
'media:b': 'Target B',
|
|
4484
|
+
'media:b;list': 'Target B List',
|
|
4485
|
+
});
|
|
4448
4486
|
const built = rendererBuildStrandGraphData(payload);
|
|
4449
4487
|
const collapsed = rendererCollapseStrandShapeTransitions(built);
|
|
4450
4488
|
|
|
@@ -4481,7 +4519,7 @@ function testRenderer_collapseStrand_sequenceProducingCapBeforeForeach() {
|
|
|
4481
4519
|
// (Disbind), NOT the cap that consumes one item from it.
|
|
4482
4520
|
// No separate output node because step_2's to_spec equals the
|
|
4483
4521
|
// strand target.
|
|
4484
|
-
const payload = {
|
|
4522
|
+
const payload = withMediaDisplayNames({
|
|
4485
4523
|
source_spec: 'media:pdf',
|
|
4486
4524
|
target_spec: 'media:decision',
|
|
4487
4525
|
steps: [
|
|
@@ -4489,7 +4527,11 @@ function testRenderer_collapseStrand_sequenceProducingCapBeforeForeach() {
|
|
|
4489
4527
|
makeForEachStep('media:page'),
|
|
4490
4528
|
makeCapStep('cap:in="media:page";op=decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
|
|
4491
4529
|
],
|
|
4492
|
-
}
|
|
4530
|
+
}, {
|
|
4531
|
+
'media:pdf': 'PDF',
|
|
4532
|
+
'media:page': 'Page',
|
|
4533
|
+
'media:decision': 'Decision',
|
|
4534
|
+
});
|
|
4493
4535
|
const built = rendererBuildStrandGraphData(payload);
|
|
4494
4536
|
const collapsed = rendererCollapseStrandShapeTransitions(built);
|
|
4495
4537
|
|
|
@@ -4532,13 +4574,16 @@ function testRenderer_collapseStrand_plainCapMergesTrailingOutput() {
|
|
|
4532
4574
|
// step_0 and output represent the same URN (media:b).
|
|
4533
4575
|
//
|
|
4534
4576
|
// Final: 2 nodes (input_slot, step_0), 1 edge.
|
|
4535
|
-
const payload = {
|
|
4577
|
+
const payload = withMediaDisplayNames({
|
|
4536
4578
|
source_spec: 'media:a',
|
|
4537
4579
|
target_spec: 'media:b',
|
|
4538
4580
|
steps: [
|
|
4539
4581
|
makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
|
|
4540
4582
|
],
|
|
4541
|
-
}
|
|
4583
|
+
}, {
|
|
4584
|
+
'media:a': 'Source A',
|
|
4585
|
+
'media:b': 'Target B',
|
|
4586
|
+
});
|
|
4542
4587
|
const built = rendererBuildStrandGraphData(payload);
|
|
4543
4588
|
const collapsed = rendererCollapseStrandShapeTransitions(built);
|
|
4544
4589
|
|
|
@@ -4561,13 +4606,17 @@ function testRenderer_collapseStrand_plainCapDistinctTargetNoMerge() {
|
|
|
4561
4606
|
// A strand with a single plain cap whose to_spec is NOT
|
|
4562
4607
|
// equivalent to target_spec. The output node must be retained
|
|
4563
4608
|
// and the trailing connector edge preserved.
|
|
4564
|
-
const payload = {
|
|
4609
|
+
const payload = withMediaDisplayNames({
|
|
4565
4610
|
source_spec: 'media:a',
|
|
4566
4611
|
target_spec: 'media:b;list',
|
|
4567
4612
|
steps: [
|
|
4568
4613
|
makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
|
|
4569
4614
|
],
|
|
4570
|
-
}
|
|
4615
|
+
}, {
|
|
4616
|
+
'media:a': 'Source A',
|
|
4617
|
+
'media:b': 'Target B',
|
|
4618
|
+
'media:b;list': 'Target B List',
|
|
4619
|
+
});
|
|
4571
4620
|
const built = rendererBuildStrandGraphData(payload);
|
|
4572
4621
|
const collapsed = rendererCollapseStrandShapeTransitions(built);
|
|
4573
4622
|
|
|
@@ -4641,6 +4690,12 @@ function testRenderer_buildRunGraphData_pagesSuccessesAndFailures() {
|
|
|
4641
4690
|
}
|
|
4642
4691
|
const payload = {
|
|
4643
4692
|
resolved_strand: strand,
|
|
4693
|
+
media_display_names: {
|
|
4694
|
+
'media:pdf;list': 'PDF List',
|
|
4695
|
+
'media:pdf': 'PDF',
|
|
4696
|
+
'media:png': 'PNG',
|
|
4697
|
+
'media:txt': 'Text',
|
|
4698
|
+
},
|
|
4644
4699
|
body_outcomes: outcomes,
|
|
4645
4700
|
visible_success_count: 3,
|
|
4646
4701
|
visible_failure_count: 2,
|
|
@@ -4684,6 +4739,11 @@ function testRenderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace(
|
|
|
4684
4739
|
};
|
|
4685
4740
|
const payload = {
|
|
4686
4741
|
resolved_strand: strand,
|
|
4742
|
+
media_display_names: {
|
|
4743
|
+
'media:pdf;list': 'PDF List',
|
|
4744
|
+
'media:pdf': 'PDF',
|
|
4745
|
+
'media:txt': 'Text',
|
|
4746
|
+
},
|
|
4687
4747
|
body_outcomes: [
|
|
4688
4748
|
{ body_index: 0, success: false, cap_urns: [], saved_paths: [], total_bytes: 0, duration_ms: 0, error: 'unknown' },
|
|
4689
4749
|
],
|
|
@@ -4726,6 +4786,12 @@ function testRenderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
|
|
|
4726
4786
|
// will match. Feed a fully-specified form that is equivalent.
|
|
4727
4787
|
const payload = {
|
|
4728
4788
|
resolved_strand: strand,
|
|
4789
|
+
media_display_names: {
|
|
4790
|
+
'media:a': 'Source A',
|
|
4791
|
+
'media:a;list': 'Source A List',
|
|
4792
|
+
'media:b': 'Intermediate B',
|
|
4793
|
+
'media:c': 'Target C',
|
|
4794
|
+
},
|
|
4729
4795
|
body_outcomes: [
|
|
4730
4796
|
{
|
|
4731
4797
|
body_index: 0,
|
|
@@ -4775,6 +4841,11 @@ function testRenderer_buildRunGraphData_backboneHasNoForeachNode() {
|
|
|
4775
4841
|
};
|
|
4776
4842
|
const payload = {
|
|
4777
4843
|
resolved_strand: strand,
|
|
4844
|
+
media_display_names: {
|
|
4845
|
+
'media:pdf': 'PDF',
|
|
4846
|
+
'media:page': 'Page',
|
|
4847
|
+
'media:decision': 'Decision',
|
|
4848
|
+
},
|
|
4778
4849
|
body_outcomes: [],
|
|
4779
4850
|
visible_success_count: 0,
|
|
4780
4851
|
visible_failure_count: 0,
|
|
@@ -4819,6 +4890,11 @@ function testRenderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
|
|
|
4819
4890
|
const failedCapUrn = 'cap:in="media:page";op=decide;out="media:decision"';
|
|
4820
4891
|
const payload = {
|
|
4821
4892
|
resolved_strand: strand,
|
|
4893
|
+
media_display_names: {
|
|
4894
|
+
'media:pdf': 'PDF',
|
|
4895
|
+
'media:page': 'Page',
|
|
4896
|
+
'media:decision': 'Decision',
|
|
4897
|
+
},
|
|
4822
4898
|
body_outcomes: [
|
|
4823
4899
|
{ body_index: 0, success: false, cap_urns: [], saved_paths: [], total_bytes: 0, duration_ms: 0, failed_cap: failedCapUrn, error: 'boom' },
|
|
4824
4900
|
{ body_index: 1, success: false, cap_urns: [], saved_paths: [], total_bytes: 0, duration_ms: 0, failed_cap: failedCapUrn, error: 'boom' },
|
|
@@ -4883,6 +4959,11 @@ function testRenderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
|
|
|
4883
4959
|
};
|
|
4884
4960
|
const payload = {
|
|
4885
4961
|
resolved_strand: strand,
|
|
4962
|
+
media_display_names: {
|
|
4963
|
+
'media:pdf': 'PDF',
|
|
4964
|
+
'media:page': 'Page',
|
|
4965
|
+
'media:decision': 'Decision',
|
|
4966
|
+
},
|
|
4886
4967
|
body_outcomes: [
|
|
4887
4968
|
{ body_index: 0, success: true, cap_urns: [], saved_paths: [], total_bytes: 0, duration_ms: 0 },
|
|
4888
4969
|
],
|
|
@@ -4937,6 +5018,12 @@ function testRenderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarge
|
|
|
4937
5018
|
};
|
|
4938
5019
|
const payload = {
|
|
4939
5020
|
resolved_strand: strand,
|
|
5021
|
+
media_display_names: {
|
|
5022
|
+
'media:pdf;list': 'PDF List',
|
|
5023
|
+
'media:pdf': 'PDF',
|
|
5024
|
+
'media:txt': 'Text',
|
|
5025
|
+
'media:txt;list': 'Text List',
|
|
5026
|
+
},
|
|
4940
5027
|
body_outcomes: [
|
|
4941
5028
|
{ body_index: 0, success: true, cap_urns: [], saved_paths: [], total_bytes: 0, duration_ms: 0 },
|
|
4942
5029
|
],
|
|
@@ -5098,14 +5185,15 @@ function testRenderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
|
|
|
5098
5185
|
strands: [
|
|
5099
5186
|
{
|
|
5100
5187
|
nodes: [
|
|
5101
|
-
{ id: 'n0', urn: 'media:pdf' },
|
|
5102
|
-
{ id: 'n1', urn: 'media:txt;textable' },
|
|
5103
|
-
{ id: 'n2', urn: 'media:embedding;record' },
|
|
5188
|
+
{ id: 'n0', urn: 'media:pdf', title: 'PDF' },
|
|
5189
|
+
{ id: 'n1', urn: 'media:txt;textable', title: 'Plain Text' },
|
|
5190
|
+
{ id: 'n2', urn: 'media:embedding;record', title: 'Embedding Record' },
|
|
5104
5191
|
],
|
|
5105
5192
|
edges: [
|
|
5106
5193
|
{
|
|
5107
5194
|
alias: 'edge_0',
|
|
5108
5195
|
cap_urn: 'cap:in=media:pdf;op=extract;out=media:txt;textable',
|
|
5196
|
+
title: 'Extract Text',
|
|
5109
5197
|
is_loop: false,
|
|
5110
5198
|
assignment: [
|
|
5111
5199
|
{ cap_arg_media_urn: 'media:pdf', source_node: 'n0' },
|
|
@@ -5115,6 +5203,7 @@ function testRenderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
|
|
|
5115
5203
|
{
|
|
5116
5204
|
alias: 'edge_1',
|
|
5117
5205
|
cap_urn: 'cap:in=media:textable;op=embed;out=media:embedding;record',
|
|
5206
|
+
title: 'Generate Embedding',
|
|
5118
5207
|
is_loop: false,
|
|
5119
5208
|
assignment: [
|
|
5120
5209
|
{ cap_arg_media_urn: 'media:textable', source_node: 'n1' },
|
|
@@ -5137,6 +5226,7 @@ function testRenderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
|
|
|
5137
5226
|
// Anchor nodes carry the strand-source / strand-target classes.
|
|
5138
5227
|
const n0 = built.nodes.find(n => n.data.id === 'n0');
|
|
5139
5228
|
const n2 = built.nodes.find(n => n.data.id === 'n2');
|
|
5229
|
+
assertEqual(n0.data.label, 'PDF', 'node label must come from explicit title');
|
|
5140
5230
|
assert(n0.classes.indexOf('strand-source') >= 0,
|
|
5141
5231
|
'input anchor node carries strand-source class');
|
|
5142
5232
|
assert(n2.classes.indexOf('strand-target') >= 0,
|
|
@@ -5151,13 +5241,14 @@ function testRenderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
|
|
|
5151
5241
|
strands: [
|
|
5152
5242
|
{
|
|
5153
5243
|
nodes: [
|
|
5154
|
-
{ id: 'n0', urn: 'media:page;textable' },
|
|
5155
|
-
{ id: 'n1', urn: 'media:decision;json;record;textable' },
|
|
5244
|
+
{ id: 'n0', urn: 'media:page;textable', title: 'Page' },
|
|
5245
|
+
{ id: 'n1', urn: 'media:decision;json;record;textable', title: 'Decision Record' },
|
|
5156
5246
|
],
|
|
5157
5247
|
edges: [
|
|
5158
5248
|
{
|
|
5159
5249
|
alias: 'edge_0',
|
|
5160
5250
|
cap_urn: 'cap:in=media:textable;op=make_decision;out=media:decision;json;record;textable',
|
|
5251
|
+
title: 'Make Decision',
|
|
5161
5252
|
is_loop: true,
|
|
5162
5253
|
assignment: [
|
|
5163
5254
|
{ cap_arg_media_urn: 'media:textable', source_node: 'n0' },
|
|
@@ -5185,14 +5276,15 @@ function testRenderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignme
|
|
|
5185
5276
|
strands: [
|
|
5186
5277
|
{
|
|
5187
5278
|
nodes: [
|
|
5188
|
-
{ id: 'n0', urn: 'media:image;png' },
|
|
5189
|
-
{ id: 'n1', urn: 'media:model-spec;textable' },
|
|
5190
|
-
{ id: 'n2', urn: 'media:image-description;textable' },
|
|
5279
|
+
{ id: 'n0', urn: 'media:image;png', title: 'PNG Image' },
|
|
5280
|
+
{ id: 'n1', urn: 'media:model-spec;textable', title: 'Model Spec' },
|
|
5281
|
+
{ id: 'n2', urn: 'media:image-description;textable', title: 'Image Description' },
|
|
5191
5282
|
],
|
|
5192
5283
|
edges: [
|
|
5193
5284
|
{
|
|
5194
5285
|
alias: 'edge_0',
|
|
5195
5286
|
cap_urn: 'cap:in=media:image;png;op=describe_image;out=media:image-description;textable',
|
|
5287
|
+
title: 'Describe Image',
|
|
5196
5288
|
is_loop: false,
|
|
5197
5289
|
assignment: [
|
|
5198
5290
|
{ cap_arg_media_urn: 'media:image;png', source_node: 'n0' },
|
|
@@ -5225,13 +5317,14 @@ function testRenderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjo
|
|
|
5225
5317
|
strands: [
|
|
5226
5318
|
{
|
|
5227
5319
|
nodes: [
|
|
5228
|
-
{ id: 'n0', urn: 'media:pdf' },
|
|
5229
|
-
{ id: 'n1', urn: 'media:txt;textable' },
|
|
5320
|
+
{ id: 'n0', urn: 'media:pdf', title: 'PDF' },
|
|
5321
|
+
{ id: 'n1', urn: 'media:txt;textable', title: 'Plain Text' },
|
|
5230
5322
|
],
|
|
5231
5323
|
edges: [
|
|
5232
5324
|
{
|
|
5233
5325
|
alias: 'edge_0',
|
|
5234
5326
|
cap_urn: 'cap:in=media:pdf;op=extract;out=media:txt;textable',
|
|
5327
|
+
title: 'Extract Text',
|
|
5235
5328
|
is_loop: false,
|
|
5236
5329
|
assignment: [
|
|
5237
5330
|
{ cap_arg_media_urn: 'media:pdf', source_node: 'n0' },
|
|
@@ -5244,13 +5337,14 @@ function testRenderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjo
|
|
|
5244
5337
|
},
|
|
5245
5338
|
{
|
|
5246
5339
|
nodes: [
|
|
5247
|
-
{ id: 'n2', urn: 'media:json;record;textable' },
|
|
5248
|
-
{ id: 'n3', urn: 'media:csv;list;record;textable' },
|
|
5340
|
+
{ id: 'n2', urn: 'media:json;record;textable', title: 'JSON Record' },
|
|
5341
|
+
{ id: 'n3', urn: 'media:csv;list;record;textable', title: 'CSV Rows' },
|
|
5249
5342
|
],
|
|
5250
5343
|
edges: [
|
|
5251
5344
|
{
|
|
5252
5345
|
alias: 'edge_1',
|
|
5253
5346
|
cap_urn: 'cap:in=media:json;record;textable;op=convert_format;out=media:csv;list;record;textable',
|
|
5347
|
+
title: 'Convert Format',
|
|
5254
5348
|
is_loop: false,
|
|
5255
5349
|
assignment: [
|
|
5256
5350
|
{ cap_arg_media_urn: 'media:json;record;textable', source_node: 'n2' },
|
|
@@ -5284,13 +5378,13 @@ function testRenderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrands
|
|
|
5284
5378
|
const payload = {
|
|
5285
5379
|
strands: [
|
|
5286
5380
|
{
|
|
5287
|
-
nodes: [{ id: 'n0', urn: 'media:pdf' }],
|
|
5381
|
+
nodes: [{ id: 'n0', urn: 'media:pdf', title: 'PDF' }],
|
|
5288
5382
|
edges: [],
|
|
5289
5383
|
input_anchor_nodes: ['n0'],
|
|
5290
5384
|
output_anchor_nodes: ['n0'],
|
|
5291
5385
|
},
|
|
5292
5386
|
{
|
|
5293
|
-
nodes: [{ id: 'n0', urn: 'media:html' }],
|
|
5387
|
+
nodes: [{ id: 'n0', urn: 'media:html', title: 'HTML' }],
|
|
5294
5388
|
edges: [],
|
|
5295
5389
|
input_anchor_nodes: ['n0'],
|
|
5296
5390
|
output_anchor_nodes: ['n0'],
|
|
@@ -5318,13 +5412,14 @@ function testRenderer_validateResolvedMachinePayload_rejectsMissingFields() {
|
|
|
5318
5412
|
const cases = [
|
|
5319
5413
|
{ strands: 'not-an-array' },
|
|
5320
5414
|
{ strands: [{ nodes: [], edges: [], input_anchor_nodes: [] /* missing output_anchor_nodes */ }] },
|
|
5321
|
-
{ strands: [{ nodes: [{ id: 'n0' /* missing urn */ }], edges: [], input_anchor_nodes: [], output_anchor_nodes: [] }] },
|
|
5415
|
+
{ strands: [{ nodes: [{ id: 'n0' /* missing urn/title */ }], edges: [], input_anchor_nodes: [], output_anchor_nodes: [] }] },
|
|
5322
5416
|
{
|
|
5323
5417
|
strands: [{
|
|
5324
|
-
nodes: [{ id: 'n0', urn: 'media:x' }],
|
|
5418
|
+
nodes: [{ id: 'n0', urn: 'media:x', title: 'X' }],
|
|
5325
5419
|
edges: [{
|
|
5326
5420
|
alias: 'edge_0',
|
|
5327
5421
|
cap_urn: 'cap:in=...;out=...',
|
|
5422
|
+
title: 'Edge 0',
|
|
5328
5423
|
is_loop: false,
|
|
5329
5424
|
assignment: [{ cap_arg_media_urn: 'media:x' /* missing source_node */ }],
|
|
5330
5425
|
target_node: 'n0',
|
|
@@ -5546,8 +5641,6 @@ async function runTests() {
|
|
|
5546
5641
|
runTest('TEST1315: is_numeric', test1315_isNumeric);
|
|
5547
5642
|
runTest('TEST1298: is_bool', test1298_isBool);
|
|
5548
5643
|
runTest('TEST1299: is_file_path', test1299_isFilePath);
|
|
5549
|
-
runTest('TEST1300: is_file_path_array', test1300_isFilePathArray);
|
|
5550
|
-
runTest('TEST1301: is_any_file_path', test1301_isAnyFilePath);
|
|
5551
5644
|
runTest('TEST1302: predicate_constant_consistency', test1302_predicateConstantConsistency);
|
|
5552
5645
|
|
|
5553
5646
|
// cap_urn.rs: TEST1303-TEST1307 (CapUrn tier tests)
|
|
@@ -5703,8 +5796,8 @@ async function runTests() {
|
|
|
5703
5796
|
runTest('RENDERER: canonicalMediaUrn_normalizesTagOrder', testRenderer_canonicalMediaUrn_normalizesTagOrder);
|
|
5704
5797
|
runTest('RENDERER: canonicalMediaUrn_preservesValueTags', testRenderer_canonicalMediaUrn_preservesValueTags);
|
|
5705
5798
|
runTest('RENDERER: canonicalMediaUrn_rejectsCapUrn', testRenderer_canonicalMediaUrn_rejectsCapUrn);
|
|
5706
|
-
runTest('RENDERER:
|
|
5707
|
-
runTest('RENDERER:
|
|
5799
|
+
runTest('RENDERER: mediaNodeLabel_rejectsUrnDerived', testRenderer_mediaNodeLabel_rejectsUrnDerivedLabels);
|
|
5800
|
+
runTest('RENDERER: buildBrowse_rejectsMissingMediaTitles', testRenderer_buildBrowseGraphData_rejectsMissingMediaTitles);
|
|
5708
5801
|
|
|
5709
5802
|
console.log('\n--- cap-graph-renderer strand builder ---');
|
|
5710
5803
|
runTest('RENDERER: validateStrandStep_unknownVariant', testRenderer_validateStrandStep_rejectsUnknownVariant);
|
package/package.json
CHANGED