capdag 1.198.510 → 1.205.540
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-fab-renderer.js +2 -2
- package/capdag.js +1296 -100
- package/capdag.test.js +740 -413
- package/machine.pegjs +2 -2
- package/package.json +2 -2
package/capdag.test.js
CHANGED
|
@@ -8,6 +8,12 @@ const {
|
|
|
8
8
|
Cap, CapGroup, CapManifest, MediaDef, MediaDefError, MediaDefErrorCodes,
|
|
9
9
|
resolveMediaUrn, buildExtensionIndex, mediaUrnsForExtension, getExtensionMappings,
|
|
10
10
|
CartridgeInfo, CartridgeCapSummary, CartridgeSuggestion, CartridgeRepoClient, CartridgeRepoServer,
|
|
11
|
+
hostPlatform, CompatStatus, primaryPackage, CartridgeCompatibilityResolution,
|
|
12
|
+
registryUrlFromBuildEnv,
|
|
13
|
+
slugForSync, CartridgeInstallSource, validateRegistryUrlScheme, RegistryUrlSchemeResultKind,
|
|
14
|
+
CartridgeJson, CartridgeJsonError, CartridgeJsonErrorKind, hashCartridgeDirectory,
|
|
15
|
+
DiscoveryIdentity, DiscoveredCartridge, discoverCartridges,
|
|
16
|
+
CartridgeAttachmentErrorKind, CartridgeChannel,
|
|
11
17
|
CapFabEdge, CapFabStats, CapFab,
|
|
12
18
|
StdinSource, StdinSourceKind,
|
|
13
19
|
validateNoMediaDefRedefinitionSync,
|
|
@@ -103,7 +109,7 @@ function runTest(name, fn) {
|
|
|
103
109
|
/**
|
|
104
110
|
* Helper function to build test URNs with required in/out media URNs.
|
|
105
111
|
* Uses MEDIA_VOID for in and MEDIA_OBJECT for out, matching the
|
|
106
|
-
* Rust reference test_urn helper: test_urn(tags) => cap:in="media:void";{tags};out="media:record
|
|
112
|
+
* Rust reference test_urn helper: test_urn(tags) => cap:in="media:void";{tags};out="media:enc=utf-8;record"
|
|
107
113
|
*/
|
|
108
114
|
// TEST0051: Urn
|
|
109
115
|
function test0051_Urn(tags) {
|
|
@@ -158,7 +164,7 @@ function test003_directionMatching() {
|
|
|
158
164
|
assert(cap.accepts(request), 'Same direction specs should match');
|
|
159
165
|
|
|
160
166
|
// Different direction should not match
|
|
161
|
-
const requestDiff = CapUrn.fromString('cap:in="media:
|
|
167
|
+
const requestDiff = CapUrn.fromString('cap:in="media:enc=utf-8";generate;out="media:enc=utf-8;record"');
|
|
162
168
|
assert(!cap.accepts(requestDiff), 'Different inSpec should not match');
|
|
163
169
|
|
|
164
170
|
// Wildcard direction matches any
|
|
@@ -170,7 +176,7 @@ function test003_directionMatching() {
|
|
|
170
176
|
// Key lookup is case-insensitive: uppercase variants of `ext` resolve
|
|
171
177
|
// to the same keyed tag.
|
|
172
178
|
function test004_unquotedValuesLowercased() {
|
|
173
|
-
const cap = CapUrn.fromString('cap:ext=pdf;generate;in=media:void;out="media:record
|
|
179
|
+
const cap = CapUrn.fromString('cap:ext=pdf;generate;in=media:void;out="media:enc=utf-8;record"');
|
|
174
180
|
assert(cap.hasMarkerTag('generate'), 'Unquoted value should be lowercased');
|
|
175
181
|
assertEqual(cap.getTag('ext'), 'pdf', 'Unquoted value should be lowercased');
|
|
176
182
|
assertEqual(cap.getTag('EXT'), 'pdf', 'Key lookup should be case-insensitive');
|
|
@@ -458,7 +464,7 @@ function test024_compatibility() {
|
|
|
458
464
|
assert(general.accepts(cap1), 'General request should accept more specific instance');
|
|
459
465
|
assert(!cap1.accepts(general), 'Specific pattern should reject general instance');
|
|
460
466
|
|
|
461
|
-
const broadIn = CapUrn.fromString('cap:in="media:";generate;out="media:record
|
|
467
|
+
const broadIn = CapUrn.fromString('cap:in="media:";generate;out="media:enc=utf-8;record"');
|
|
462
468
|
assert(!cap1.accepts(broadIn), 'Specific input should not accept broad wildcard input');
|
|
463
469
|
assert(broadIn.accepts(cap1), 'Wildcard input should accept specific input');
|
|
464
470
|
}
|
|
@@ -479,11 +485,11 @@ function test025_bestMatch() {
|
|
|
479
485
|
// TEST026: Test merge combines tags from both caps, subset keeps only specified tags
|
|
480
486
|
function test026_mergeAndSubset() {
|
|
481
487
|
const cap1 = CapUrn.fromString(test0051_Urn('generate'));
|
|
482
|
-
const cap2 = CapUrn.fromString('cap:in="media:
|
|
488
|
+
const cap2 = CapUrn.fromString('cap:in="media:enc=utf-8";ext=pdf;format=binary;out="media:"');
|
|
483
489
|
|
|
484
490
|
// Merge (other takes precedence)
|
|
485
491
|
const merged = cap1.merge(cap2);
|
|
486
|
-
assertEqual(merged.getInSpec(), 'media:
|
|
492
|
+
assertEqual(merged.getInSpec(), 'media:enc=utf-8', 'Merge should take inSpec from other');
|
|
487
493
|
assertEqual(merged.getOutSpec(), 'media:', 'Merge should take outSpec from other');
|
|
488
494
|
assert(merged.hasMarkerTag('generate'), 'Merge should keep original tags');
|
|
489
495
|
assertEqual(merged.getTag('ext'), 'pdf', 'Merge should add other tags');
|
|
@@ -492,7 +498,7 @@ function test026_mergeAndSubset() {
|
|
|
492
498
|
const sub = merged.subset(['ext']);
|
|
493
499
|
assertEqual(sub.getTag('ext'), 'pdf', 'Subset should keep ext');
|
|
494
500
|
assert(!sub.hasMarkerTag('generate'), 'Subset should drop the generate marker');
|
|
495
|
-
assertEqual(sub.getInSpec(), 'media:
|
|
501
|
+
assertEqual(sub.getInSpec(), 'media:enc=utf-8', 'Subset should preserve inSpec');
|
|
496
502
|
}
|
|
497
503
|
|
|
498
504
|
// TEST027: Test with_wildcard_tag sets tag to wildcard, including in/out
|
|
@@ -724,7 +730,7 @@ function test890_directionSemanticMatching() {
|
|
|
724
730
|
'cap:in="media:";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
725
731
|
);
|
|
726
732
|
const pdfRequest = CapUrn.fromString(
|
|
727
|
-
'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
733
|
+
'cap:in="media:ext=pdf";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
728
734
|
);
|
|
729
735
|
assert(genericCap.accepts(pdfRequest), 'Generic wildcard cap must accept pdf request');
|
|
730
736
|
|
|
@@ -736,7 +742,7 @@ function test890_directionSemanticMatching() {
|
|
|
736
742
|
|
|
737
743
|
// Reverse: specific pdf cap does NOT accept generic bytes request
|
|
738
744
|
const pdfCap = CapUrn.fromString(
|
|
739
|
-
'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
745
|
+
'cap:in="media:ext=pdf";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
740
746
|
);
|
|
741
747
|
const genericRequest = CapUrn.fromString(
|
|
742
748
|
'cap:in="media:";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
@@ -776,7 +782,7 @@ function test891_directionSemanticSpecificity() {
|
|
|
776
782
|
'cap:in="media:";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
777
783
|
);
|
|
778
784
|
const specificCap = CapUrn.fromString(
|
|
779
|
-
'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
785
|
+
'cap:in="media:ext=pdf";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
780
786
|
);
|
|
781
787
|
|
|
782
788
|
// generic:
|
|
@@ -788,20 +794,20 @@ function test891_directionSemanticSpecificity() {
|
|
|
788
794
|
'out=image;png;thumbnail(6) + in=media:(0) + generate-thumbnail marker(2) = 60002');
|
|
789
795
|
// specific:
|
|
790
796
|
// out=media:image;png;thumbnail -> 6
|
|
791
|
-
// in=media:pdf
|
|
797
|
+
// in=media:ext=pdf -> 4 (ext=pdf is an exact-value tag, not a bare marker)
|
|
792
798
|
// y: generate-thumbnail marker -> 2
|
|
793
|
-
// spec_C = 10000*6 + 100*
|
|
794
|
-
assertEqual(specificCap.specificity(), 10000*6 + 100*
|
|
795
|
-
'out=image;png;thumbnail(6) + in=pdf(
|
|
799
|
+
// spec_C = 10000*6 + 100*4 + 2 = 60402
|
|
800
|
+
assertEqual(specificCap.specificity(), 10000*6 + 100*4 + 2,
|
|
801
|
+
'out=image;png;thumbnail(6) + in=ext=pdf(4) + generate-thumbnail marker(2) = 60402');
|
|
796
802
|
assert(specificCap.specificity() > genericCap.specificity(), 'pdf should be more specific');
|
|
797
803
|
|
|
798
804
|
// CapMatcher should prefer more specific
|
|
799
805
|
const pdfRequest = CapUrn.fromString(
|
|
800
|
-
'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
806
|
+
'cap:in="media:ext=pdf";generate-thumbnail;out="media:image;png;thumbnail"'
|
|
801
807
|
);
|
|
802
808
|
const best = CapMatcher.findBestMatch([genericCap, specificCap], pdfRequest);
|
|
803
809
|
assert(best !== null, 'Should find a match');
|
|
804
|
-
assertEqual(best.getInSpec(), 'media:pdf', 'Should prefer more specific pdf cap');
|
|
810
|
+
assertEqual(best.getInSpec(), 'media:ext=pdf', 'Should prefer more specific pdf cap');
|
|
805
811
|
}
|
|
806
812
|
|
|
807
813
|
// ============================================================================
|
|
@@ -863,21 +869,9 @@ function test060_wrongPrefixFails() {
|
|
|
863
869
|
);
|
|
864
870
|
}
|
|
865
871
|
|
|
866
|
-
// TEST061:
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
assert(MediaUrn.fromString(MEDIA_IDENTITY).isBinary(), 'MEDIA_IDENTITY (media:) should be binary');
|
|
870
|
-
assert(MediaUrn.fromString(MEDIA_PNG).isBinary(), 'MEDIA_PNG should be binary');
|
|
871
|
-
assert(MediaUrn.fromString(MEDIA_PDF).isBinary(), 'MEDIA_PDF should be binary');
|
|
872
|
-
assert(MediaUrn.fromString('media:video').isBinary(), 'media:video should be binary');
|
|
873
|
-
assert(MediaUrn.fromString('media:epub').isBinary(), 'media:epub should be binary');
|
|
874
|
-
// Textable types: is_binary is false
|
|
875
|
-
assert(!MediaUrn.fromString('media:textable').isBinary(), 'media:textable should not be binary');
|
|
876
|
-
assert(!MediaUrn.fromString('media:textable;record').isBinary(), 'textable map should not be binary');
|
|
877
|
-
assert(!MediaUrn.fromString(MEDIA_STRING).isBinary(), 'MEDIA_STRING should not be binary');
|
|
878
|
-
assert(!MediaUrn.fromString(MEDIA_JSON).isBinary(), 'MEDIA_JSON should not be binary');
|
|
879
|
-
assert(!MediaUrn.fromString(MEDIA_MD).isBinary(), 'MEDIA_MD should not be binary');
|
|
880
|
-
}
|
|
872
|
+
// TEST061: REMOVED — the binary/text distinction no longer exists in the
|
|
873
|
+
// vocabulary (isBinary() was deleted from MediaUrn; everything is bytes).
|
|
874
|
+
// Encoding is now expressed by the orthogonal `enc=` tag, exercised by TEST067.
|
|
881
875
|
|
|
882
876
|
// TEST062: Test is_record returns true when record marker tag is present indicating key-value structure
|
|
883
877
|
function test062_isRecord() {
|
|
@@ -885,7 +879,7 @@ function test062_isRecord() {
|
|
|
885
879
|
assert(MediaUrn.fromString('media:custom;record').isRecord(), 'custom;record should be record');
|
|
886
880
|
assert(MediaUrn.fromString(MEDIA_JSON).isRecord(), 'MEDIA_JSON should be record');
|
|
887
881
|
// Without record marker, is_record is false
|
|
888
|
-
assert(!MediaUrn.fromString('media:
|
|
882
|
+
assert(!MediaUrn.fromString('media:enc=utf-8').isRecord(), 'plain text should not be record');
|
|
889
883
|
assert(!MediaUrn.fromString(MEDIA_STRING).isRecord(), 'MEDIA_STRING should not be record');
|
|
890
884
|
assert(!MediaUrn.fromString(MEDIA_STRING_LIST).isRecord(), 'MEDIA_STRING_LIST should not be record');
|
|
891
885
|
}
|
|
@@ -897,7 +891,7 @@ function test063_isScalar() {
|
|
|
897
891
|
assert(MediaUrn.fromString(MEDIA_NUMBER).isScalar(), 'MEDIA_NUMBER should be scalar');
|
|
898
892
|
assert(MediaUrn.fromString(MEDIA_BOOLEAN).isScalar(), 'MEDIA_BOOLEAN should be scalar');
|
|
899
893
|
assert(MediaUrn.fromString(MEDIA_OBJECT).isScalar(), 'MEDIA_OBJECT (record but scalar) should be scalar');
|
|
900
|
-
assert(MediaUrn.fromString('media:
|
|
894
|
+
assert(MediaUrn.fromString('media:enc=utf-8').isScalar(), 'plain text should be scalar');
|
|
901
895
|
// With list marker, is_scalar is false
|
|
902
896
|
assert(!MediaUrn.fromString(MEDIA_STRING_LIST).isScalar(), 'MEDIA_STRING_LIST should not be scalar');
|
|
903
897
|
assert(!MediaUrn.fromString(MEDIA_OBJECT_LIST).isScalar(), 'MEDIA_OBJECT_LIST should not be scalar');
|
|
@@ -917,27 +911,34 @@ function test065_isOpaque() {
|
|
|
917
911
|
assert(MediaUrn.fromString(MEDIA_STRING).isOpaque(), 'MEDIA_STRING should be opaque');
|
|
918
912
|
assert(MediaUrn.fromString(MEDIA_STRING_LIST).isOpaque(), 'MEDIA_STRING_LIST (list but no record) should be opaque');
|
|
919
913
|
assert(MediaUrn.fromString(MEDIA_PDF).isOpaque(), 'MEDIA_PDF should be opaque');
|
|
920
|
-
assert(MediaUrn.fromString('media:
|
|
914
|
+
assert(MediaUrn.fromString('media:enc=utf-8').isOpaque(), 'plain text should be opaque');
|
|
921
915
|
// With record marker, is_opaque is false
|
|
922
916
|
assert(!MediaUrn.fromString(MEDIA_OBJECT).isOpaque(), 'MEDIA_OBJECT should not be opaque');
|
|
923
917
|
assert(!MediaUrn.fromString(MEDIA_JSON).isOpaque(), 'MEDIA_JSON should not be opaque');
|
|
924
918
|
}
|
|
925
919
|
|
|
926
|
-
// TEST066: Test is_json returns true only when json
|
|
920
|
+
// TEST066: Test is_json returns true only when fmt=json content-format tag is present
|
|
927
921
|
function test066_isJson() {
|
|
928
922
|
assert(MediaUrn.fromString(MEDIA_JSON).isJson(), 'MEDIA_JSON should be json');
|
|
923
|
+
assert(MediaUrn.fromString('media:custom;fmt=json').isJson(), 'fmt=json should be json');
|
|
924
|
+
// record alone does not mean JSON representation
|
|
929
925
|
assert(!MediaUrn.fromString(MEDIA_OBJECT).isJson(), 'MEDIA_OBJECT should not be json');
|
|
926
|
+
assert(!MediaUrn.fromString('media:enc=utf-8').isJson(), 'plain text should not be json');
|
|
930
927
|
}
|
|
931
928
|
|
|
932
|
-
// TEST067:
|
|
929
|
+
// TEST067: Text-representability is now carried by the orthogonal `enc=` tag
|
|
930
|
+
// (the old text marker and isText() are gone). A media is "text" iff it
|
|
931
|
+
// declares an encoding.
|
|
933
932
|
function test067_isText() {
|
|
934
|
-
|
|
935
|
-
assert(MediaUrn.fromString(
|
|
936
|
-
assert(MediaUrn.fromString(
|
|
937
|
-
//
|
|
938
|
-
assert(
|
|
939
|
-
assert(
|
|
940
|
-
assert(
|
|
933
|
+
// Has enc= → text-representable
|
|
934
|
+
assert(MediaUrn.fromString(MEDIA_STRING).getTag('enc') !== undefined, 'MEDIA_STRING should have enc');
|
|
935
|
+
assert(MediaUrn.fromString(MEDIA_BOOLEAN).getTag('enc') !== undefined, 'MEDIA_BOOLEAN should have enc');
|
|
936
|
+
// No enc= → not text-representable
|
|
937
|
+
assert(MediaUrn.fromString(MEDIA_INTEGER).getTag('enc') === undefined, 'MEDIA_INTEGER should not have enc');
|
|
938
|
+
assert(MediaUrn.fromString(MEDIA_JSON).getTag('enc') === undefined, 'MEDIA_JSON should not have enc');
|
|
939
|
+
assert(MediaUrn.fromString(MEDIA_IDENTITY).getTag('enc') === undefined, 'MEDIA_IDENTITY should not have enc');
|
|
940
|
+
assert(MediaUrn.fromString(MEDIA_PNG).getTag('enc') === undefined, 'MEDIA_PNG should not have enc');
|
|
941
|
+
assert(MediaUrn.fromString(MEDIA_OBJECT).getTag('enc') === undefined, 'MEDIA_OBJECT should not have enc');
|
|
941
942
|
}
|
|
942
943
|
|
|
943
944
|
// TEST068: Test is_void returns true when void flag or type=void tag is present
|
|
@@ -1006,7 +1007,7 @@ function test075_accepts() {
|
|
|
1006
1007
|
// TEST076: Test specificity increases with more tags for ranking conformance
|
|
1007
1008
|
function test076_specificity() {
|
|
1008
1009
|
const s1 = MediaUrn.fromString('media:');
|
|
1009
|
-
const s2 = MediaUrn.fromString('media:pdf');
|
|
1010
|
+
const s2 = MediaUrn.fromString('media:ext=pdf');
|
|
1010
1011
|
const s3 = MediaUrn.fromString('media:image;png;thumbnail');
|
|
1011
1012
|
assert(s2.specificity() > s1.specificity(), 'pdf should be more specific than wildcard');
|
|
1012
1013
|
assert(s3.specificity() > s2.specificity(), 'image;png;thumbnail should be more specific than pdf');
|
|
@@ -1082,7 +1083,7 @@ function test093_resolveUnresolvableFailsHard() {
|
|
|
1082
1083
|
// TEST097: N/A for JS (Rust validation function)
|
|
1083
1084
|
// TEST098: N/A for JS
|
|
1084
1085
|
|
|
1085
|
-
// TEST099: Test ResolvedMediaDef is_binary returns true when
|
|
1086
|
+
// TEST099: Test ResolvedMediaDef is_binary returns true when enc tag is absent
|
|
1086
1087
|
function test099_resolvedIsBinary() {
|
|
1087
1088
|
const spec = new MediaDef('application/octet-stream', null, null, 'Binary', null, MEDIA_IDENTITY);
|
|
1088
1089
|
assert(spec.isBinary(), 'Resolved binary spec should be binary');
|
|
@@ -1112,7 +1113,7 @@ function test103_resolvedIsJson() {
|
|
|
1112
1113
|
assert(spec.isJSON(), 'Resolved json spec should be JSON');
|
|
1113
1114
|
}
|
|
1114
1115
|
|
|
1115
|
-
// TEST104: Test ResolvedMediaDef is_text returns true when
|
|
1116
|
+
// TEST104: Test ResolvedMediaDef is_text returns true when enc tag is present
|
|
1116
1117
|
function test104_resolvedIsText() {
|
|
1117
1118
|
const spec = new MediaDef('text/plain', null, null, 'String', null, MEDIA_STRING);
|
|
1118
1119
|
assert(spec.isText(), 'Resolved string spec should be text');
|
|
@@ -1164,13 +1165,13 @@ function test106_metadataWithValidation() {
|
|
|
1164
1165
|
function test107_extensionsPropagation() {
|
|
1165
1166
|
const mediaDefs = [
|
|
1166
1167
|
{
|
|
1167
|
-
urn: 'media:pdf',
|
|
1168
|
+
urn: 'media:ext=pdf',
|
|
1168
1169
|
media_type: 'application/pdf',
|
|
1169
1170
|
title: 'PDF Document',
|
|
1170
1171
|
extensions: ['pdf']
|
|
1171
1172
|
}
|
|
1172
1173
|
];
|
|
1173
|
-
const resolved = resolveMediaUrn('media:pdf', mediaDefs);
|
|
1174
|
+
const resolved = resolveMediaUrn('media:ext=pdf', mediaDefs);
|
|
1174
1175
|
assert(Array.isArray(resolved.extensions), 'Extensions should be an array');
|
|
1175
1176
|
assertEqual(resolved.extensions.length, 1, 'Should have one extension');
|
|
1176
1177
|
assertEqual(resolved.extensions[0], 'pdf', 'Should have pdf extension');
|
|
@@ -1179,7 +1180,7 @@ function test107_extensionsPropagation() {
|
|
|
1179
1180
|
// TEST108: Test creating new cap with URN, title, and command verifies correct initialization
|
|
1180
1181
|
function test108_extensionsSerialization() {
|
|
1181
1182
|
// Test that MediaDef can hold extensions correctly
|
|
1182
|
-
const spec = new MediaDef('application/pdf', null, null, 'PDF', null, 'media:pdf', null, null, ['pdf']);
|
|
1183
|
+
const spec = new MediaDef('application/pdf', null, null, 'PDF', null, 'media:ext=pdf', null, null, ['pdf']);
|
|
1183
1184
|
assert(Array.isArray(spec.extensions), 'Extensions should be array');
|
|
1184
1185
|
assertEqual(spec.extensions[0], 'pdf', 'Should have pdf extension');
|
|
1185
1186
|
}
|
|
@@ -1284,9 +1285,9 @@ function test116_capArgConstructors() {
|
|
|
1284
1285
|
function test150_capManifestJsonSerialization() {
|
|
1285
1286
|
const capUrn = CapUrn.fromString(test0051_Urn('extract;target=metadata'));
|
|
1286
1287
|
const cap = new Cap(capUrn, 'Extract Metadata', 'extract-metadata');
|
|
1287
|
-
cap.addArg(new CapArg('media:pdf', true, [new ArgSource({ stdin: 'media:pdf' })]));
|
|
1288
|
+
cap.addArg(new CapArg('media:ext=pdf', true, [new ArgSource({ stdin: 'media:ext=pdf' })]));
|
|
1288
1289
|
cap.addArg(new CapArg(
|
|
1289
|
-
'media:chunk-size;
|
|
1290
|
+
'media:chunk-size;enc=utf-8;numeric',
|
|
1290
1291
|
false,
|
|
1291
1292
|
[new ArgSource({ cli_flag: '--chunk-size' })],
|
|
1292
1293
|
{
|
|
@@ -1296,7 +1297,7 @@ function test150_capManifestJsonSerialization() {
|
|
|
1296
1297
|
}
|
|
1297
1298
|
));
|
|
1298
1299
|
cap.addArg(new CapArg(
|
|
1299
|
-
'media:
|
|
1300
|
+
'media:bool;enc=utf-8;timestamps',
|
|
1300
1301
|
false,
|
|
1301
1302
|
[new ArgSource({ cli_flag: '--timestamps' })],
|
|
1302
1303
|
{
|
|
@@ -1382,34 +1383,34 @@ function test597_capArgWithFullDefinition() {
|
|
|
1382
1383
|
// registry name we passed. This is exactly the shape the renderer depends on.
|
|
1383
1384
|
function test0052_CapFabAddCapPopulatesEdgesAndNodes() {
|
|
1384
1385
|
const graph = new CapFab();
|
|
1385
|
-
const cap = makeGraphCap('media:pdf', 'media:
|
|
1386
|
+
const cap = makeGraphCap('media:ext=pdf', 'media:enc=utf-8', 'PDF to Text');
|
|
1386
1387
|
graph.addCap(cap, 'registry');
|
|
1387
1388
|
|
|
1388
1389
|
const edges = graph.getEdges();
|
|
1389
1390
|
assertEqual(edges.length, 1, 'Graph must have one edge after a single addCap');
|
|
1390
|
-
assertEqual(edges[0].fromUrn, 'media:pdf', 'Edge fromUrn must be cap in_spec');
|
|
1391
|
-
assertEqual(edges[0].toUrn, 'media:
|
|
1391
|
+
assertEqual(edges[0].fromUrn, 'media:ext=pdf', 'Edge fromUrn must be cap in_spec');
|
|
1392
|
+
assertEqual(edges[0].toUrn, 'media:enc=utf-8', 'Edge toUrn must be cap out_spec');
|
|
1392
1393
|
assertEqual(edges[0].registryName, 'registry', 'Edge must carry the registry name passed to addCap');
|
|
1393
1394
|
|
|
1394
1395
|
const nodes = graph.getNodes();
|
|
1395
|
-
assert(nodes.has('media:pdf'), 'from_spec must appear as a node');
|
|
1396
|
-
assert(nodes.has('media:
|
|
1396
|
+
assert(nodes.has('media:ext=pdf'), 'from_spec must appear as a node');
|
|
1397
|
+
assert(nodes.has('media:enc=utf-8'), 'to_spec must appear as a node');
|
|
1397
1398
|
}
|
|
1398
1399
|
|
|
1399
1400
|
// getOutgoing takes a concrete source URN and returns edges whose from_spec
|
|
1400
1401
|
// the source conforms to. It must NOT be a plain string lookup.
|
|
1401
1402
|
function test0053_CapFabGetOutgoingConformsToMatching() {
|
|
1402
1403
|
const graph = new CapFab();
|
|
1403
|
-
graph.addCap(makeGraphCap('media:
|
|
1404
|
+
graph.addCap(makeGraphCap('media:enc=utf-8', 'media:embedding-vector', 'Embed text'), 'registry');
|
|
1404
1405
|
|
|
1405
|
-
// 'media:txt
|
|
1406
|
+
// 'media:enc=utf-8;ext=txt' conforms to 'media:enc=utf-8' — renderer relies on
|
|
1406
1407
|
// this for the browse-mode fan-out from specific source URNs to caps that
|
|
1407
1408
|
// accept a broader media pattern.
|
|
1408
|
-
const outgoingFromSpecific = graph.getOutgoing('media:txt
|
|
1409
|
+
const outgoingFromSpecific = graph.getOutgoing('media:enc=utf-8;ext=txt');
|
|
1409
1410
|
assertEqual(outgoingFromSpecific.length, 1, 'Specific URN must match broader cap input');
|
|
1410
1411
|
|
|
1411
1412
|
// The broad URN still matches its own edge.
|
|
1412
|
-
const outgoingFromBroad = graph.getOutgoing('media:
|
|
1413
|
+
const outgoingFromBroad = graph.getOutgoing('media:enc=utf-8');
|
|
1413
1414
|
assertEqual(outgoingFromBroad.length, 1, 'Exact URN must match');
|
|
1414
1415
|
|
|
1415
1416
|
// A totally unrelated URN must not match.
|
|
@@ -1421,8 +1422,8 @@ function test0053_CapFabGetOutgoingConformsToMatching() {
|
|
|
1421
1422
|
// the renderer colours/groups edges by provenance in browse mode.
|
|
1422
1423
|
function test0057_CapFabDistinctRegistryNames() {
|
|
1423
1424
|
const graph = new CapFab();
|
|
1424
|
-
graph.addCap(makeGraphCap('media:pdf', 'media:
|
|
1425
|
-
graph.addCap(makeGraphCap('media:
|
|
1425
|
+
graph.addCap(makeGraphCap('media:ext=pdf', 'media:enc=utf-8', 'PDF to Text'), 'providers');
|
|
1426
|
+
graph.addCap(makeGraphCap('media:enc=utf-8', 'media:embedding-vector', 'Embed'), 'cartridges');
|
|
1426
1427
|
|
|
1427
1428
|
const edges = graph.getEdges();
|
|
1428
1429
|
assertEqual(edges.length, 2, 'Two caps must produce two edges');
|
|
@@ -1452,7 +1453,7 @@ function test157_stdinSourceFromFileReference() {
|
|
|
1452
1453
|
const trackedFileId = 'tracked-file-123';
|
|
1453
1454
|
const originalPath = '/path/to/original.pdf';
|
|
1454
1455
|
const securityBookmark = new Uint8Array([0x62, 0x6f, 0x6f, 0x6b]);
|
|
1455
|
-
const mediaUrn = 'media:pdf';
|
|
1456
|
+
const mediaUrn = 'media:ext=pdf';
|
|
1456
1457
|
|
|
1457
1458
|
const source = StdinSource.fromFileReference(trackedFileId, originalPath, securityBookmark, mediaUrn);
|
|
1458
1459
|
assert(source !== null, 'Should create source');
|
|
@@ -1488,15 +1489,15 @@ function test159_stdinSourceWithBinaryContent() {
|
|
|
1488
1489
|
|
|
1489
1490
|
// TEST274: Test CapArgumentValue::new stores media_urn and raw byte value
|
|
1490
1491
|
function test274_capArgumentValueNew() {
|
|
1491
|
-
const arg = new CapArgumentValue('media:model-spec
|
|
1492
|
-
assertEqual(arg.mediaUrn, 'media:model-spec
|
|
1492
|
+
const arg = new CapArgumentValue('media:enc=utf-8;model-spec', new Uint8Array([103, 112, 116, 45, 52]));
|
|
1493
|
+
assertEqual(arg.mediaUrn, 'media:enc=utf-8;model-spec', 'mediaUrn must match');
|
|
1493
1494
|
assertEqual(arg.value.length, 5, 'value must have 5 bytes');
|
|
1494
1495
|
}
|
|
1495
1496
|
|
|
1496
1497
|
// TEST275: Test CapArgumentValue::from_str converts string to UTF-8 bytes
|
|
1497
1498
|
function test275_capArgumentValueFromStr() {
|
|
1498
|
-
const arg = CapArgumentValue.fromStr('media:string
|
|
1499
|
-
assertEqual(arg.mediaUrn, 'media:string
|
|
1499
|
+
const arg = CapArgumentValue.fromStr('media:enc=utf-8;string', 'hello world');
|
|
1500
|
+
assertEqual(arg.mediaUrn, 'media:enc=utf-8;string', 'mediaUrn must match');
|
|
1500
1501
|
assertEqual(new TextDecoder().decode(arg.value), 'hello world', 'value must decode correctly');
|
|
1501
1502
|
}
|
|
1502
1503
|
|
|
@@ -1508,7 +1509,7 @@ function test276_capArgumentValueAsStrValid() {
|
|
|
1508
1509
|
|
|
1509
1510
|
// TEST277: Test CapArgumentValue::value_as_str fails for non-UTF-8 binary data
|
|
1510
1511
|
function test277_capArgumentValueAsStrInvalidUtf8() {
|
|
1511
|
-
const arg = new CapArgumentValue('media:pdf', new Uint8Array([0xFF, 0xFE, 0x80]));
|
|
1512
|
+
const arg = new CapArgumentValue('media:ext=pdf', new Uint8Array([0xFF, 0xFE, 0x80]));
|
|
1512
1513
|
let threw = false;
|
|
1513
1514
|
try {
|
|
1514
1515
|
arg.valueAsStr();
|
|
@@ -1539,7 +1540,7 @@ function test283_capArgumentValueLargeBinary() {
|
|
|
1539
1540
|
for (let i = 0; i < 10000; i++) {
|
|
1540
1541
|
data[i] = i % 256;
|
|
1541
1542
|
}
|
|
1542
|
-
const arg = new CapArgumentValue('media:pdf', data);
|
|
1543
|
+
const arg = new CapArgumentValue('media:ext=pdf', data);
|
|
1543
1544
|
assertEqual(arg.value.length, 10000, 'large binary must preserve all bytes');
|
|
1544
1545
|
assertEqual(arg.value[0], 0, 'first byte check');
|
|
1545
1546
|
assertEqual(arg.value[255], 255, 'byte 255 check');
|
|
@@ -1555,9 +1556,8 @@ const { TaggedUrn } = require('tagged-urn');
|
|
|
1555
1556
|
// TEST304: Test MEDIA_AVAILABILITY_OUTPUT constant parses as valid media URN with correct tags
|
|
1556
1557
|
function test304_mediaAvailabilityOutputConstant() {
|
|
1557
1558
|
const urn = TaggedUrn.fromString(MEDIA_AVAILABILITY_OUTPUT);
|
|
1558
|
-
|
|
1559
|
+
assertEqual(urn.getTag('enc'), 'utf-8', 'model-availability must carry enc=utf-8');
|
|
1559
1560
|
assertEqual(urn.getTag('record'), '*', 'model-availability must be record');
|
|
1560
|
-
assert(urn.getTag('textable') !== undefined, 'model-availability must not be binary (has textable)');
|
|
1561
1561
|
const reparsed = TaggedUrn.fromString(urn.toString());
|
|
1562
1562
|
assert(urn.conformsTo(reparsed), 'roundtrip must match original');
|
|
1563
1563
|
}
|
|
@@ -1565,9 +1565,8 @@ function test304_mediaAvailabilityOutputConstant() {
|
|
|
1565
1565
|
// TEST305: Test MEDIA_PATH_OUTPUT constant parses as valid media URN with correct tags
|
|
1566
1566
|
function test305_mediaPathOutputConstant() {
|
|
1567
1567
|
const urn = TaggedUrn.fromString(MEDIA_PATH_OUTPUT);
|
|
1568
|
-
|
|
1568
|
+
assertEqual(urn.getTag('enc'), 'utf-8', 'model-path must carry enc=utf-8');
|
|
1569
1569
|
assertEqual(urn.getTag('record'), '*', 'model-path must be record');
|
|
1570
|
-
assert(urn.getTag('textable') !== undefined, 'model-path must not be binary (has textable)');
|
|
1571
1570
|
const reparsed = TaggedUrn.fromString(urn.toString());
|
|
1572
1571
|
assert(urn.conformsTo(reparsed), 'roundtrip must match original');
|
|
1573
1572
|
}
|
|
@@ -1617,7 +1616,7 @@ function test309_modelAvailabilityAndPathAreDistinct() {
|
|
|
1617
1616
|
assert(avail.toString() !== path.toString(), 'availability and path must be distinct');
|
|
1618
1617
|
}
|
|
1619
1618
|
|
|
1620
|
-
// TEST310: llm_generate_text_urn() produces a valid cap URN with
|
|
1619
|
+
// TEST310: llm_generate_text_urn() produces a valid cap URN with enc=utf-8 in/out specs
|
|
1621
1620
|
function test310_llmGenerateTextUrn() {
|
|
1622
1621
|
const urn = llmGenerateTextUrn();
|
|
1623
1622
|
assert(urn.hasMarkerTag('generate_text'), 'Must have generate_text marker');
|
|
@@ -1663,9 +1662,9 @@ function test312_allUrnBuildersProduceValidUrns() {
|
|
|
1663
1662
|
|
|
1664
1663
|
function test0059_JS_buildExtensionIndex() {
|
|
1665
1664
|
const mediaDefs = [
|
|
1666
|
-
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1665
|
+
{ urn: 'media:ext=pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1667
1666
|
{ urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] },
|
|
1668
|
-
{ urn: 'media:json
|
|
1667
|
+
{ urn: 'media:fmt=json', media_type: 'application/json', extensions: ['json'] }
|
|
1669
1668
|
];
|
|
1670
1669
|
const index = buildExtensionIndex(mediaDefs);
|
|
1671
1670
|
assert(index instanceof Map, 'Should return a Map');
|
|
@@ -1674,15 +1673,15 @@ function test0059_JS_buildExtensionIndex() {
|
|
|
1674
1673
|
assert(index.has('jpg'), 'Should have jpg');
|
|
1675
1674
|
assert(index.has('jpeg'), 'Should have jpeg');
|
|
1676
1675
|
assert(index.has('json'), 'Should have json');
|
|
1677
|
-
assertEqual(index.get('pdf')[0], 'media:pdf', 'pdf should map correctly');
|
|
1676
|
+
assertEqual(index.get('pdf')[0], 'media:ext=pdf', 'pdf should map correctly');
|
|
1678
1677
|
}
|
|
1679
1678
|
|
|
1680
1679
|
// TEST0069: J s media urns for extension
|
|
1681
1680
|
function test0069_JS_mediaUrnsForExtension() {
|
|
1682
1681
|
const mediaDefs = [
|
|
1683
|
-
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1684
|
-
{ urn: 'media:json;
|
|
1685
|
-
{ urn: 'media:json;
|
|
1682
|
+
{ urn: 'media:ext=pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1683
|
+
{ urn: 'media:fmt=json;record', media_type: 'application/json', extensions: ['json'] },
|
|
1684
|
+
{ urn: 'media:fmt=json;list', media_type: 'application/json', extensions: ['json'] }
|
|
1686
1685
|
];
|
|
1687
1686
|
|
|
1688
1687
|
const pdfUrns = mediaUrnsForExtension('pdf', mediaDefs);
|
|
@@ -1709,7 +1708,7 @@ function test0069_JS_mediaUrnsForExtension() {
|
|
|
1709
1708
|
// TEST0070: J s get extension mappings
|
|
1710
1709
|
function test0070_JS_getExtensionMappings() {
|
|
1711
1710
|
const mediaDefs = [
|
|
1712
|
-
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1711
|
+
{ urn: 'media:ext=pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1713
1712
|
{ urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] }
|
|
1714
1713
|
];
|
|
1715
1714
|
const mappings = getExtensionMappings(mediaDefs);
|
|
@@ -1803,7 +1802,7 @@ function test0082_JS_mediaDefDocumentationPropagatesThroughResolve() {
|
|
|
1803
1802
|
const body = '## Markdown body\n\nWith `code` and a [link](https://example.com).';
|
|
1804
1803
|
const mediaDefs = [
|
|
1805
1804
|
{
|
|
1806
|
-
urn: 'media:doc-test;
|
|
1805
|
+
urn: 'media:doc-test;enc=utf-8',
|
|
1807
1806
|
media_type: 'text/plain',
|
|
1808
1807
|
title: 'Documented',
|
|
1809
1808
|
description: 'short desc',
|
|
@@ -1811,20 +1810,20 @@ function test0082_JS_mediaDefDocumentationPropagatesThroughResolve() {
|
|
|
1811
1810
|
}
|
|
1812
1811
|
];
|
|
1813
1812
|
|
|
1814
|
-
const resolved = resolveMediaUrn('media:doc-test;
|
|
1813
|
+
const resolved = resolveMediaUrn('media:doc-test;enc=utf-8', mediaDefs);
|
|
1815
1814
|
assertEqual(resolved.documentation, body, 'documentation must propagate into MediaDef');
|
|
1816
1815
|
// The short description must remain distinct from the long markdown
|
|
1817
1816
|
// body — they are different fields with different semantics.
|
|
1818
1817
|
assertEqual(resolved.description, 'short desc', 'description must remain distinct from documentation');
|
|
1819
1818
|
|
|
1820
1819
|
// Missing documentation must collapse to null, not '' or undefined.
|
|
1821
|
-
const noDoc = resolveMediaUrn('media:doc-test;
|
|
1822
|
-
{ urn: 'media:doc-test;
|
|
1820
|
+
const noDoc = resolveMediaUrn('media:doc-test;enc=utf-8', [
|
|
1821
|
+
{ urn: 'media:doc-test;enc=utf-8', media_type: 'text/plain', title: 'No Doc' }
|
|
1823
1822
|
]);
|
|
1824
1823
|
assertEqual(noDoc.documentation, null, 'Missing documentation must resolve to null');
|
|
1825
1824
|
|
|
1826
|
-
const emptyDoc = resolveMediaUrn('media:doc-test;
|
|
1827
|
-
{ urn: 'media:doc-test;
|
|
1825
|
+
const emptyDoc = resolveMediaUrn('media:doc-test;enc=utf-8', [
|
|
1826
|
+
{ urn: 'media:doc-test;enc=utf-8', media_type: 'text/plain', title: 'Empty', documentation: '' }
|
|
1828
1827
|
]);
|
|
1829
1828
|
assertEqual(emptyDoc.documentation, null, 'Empty documentation string must collapse to null');
|
|
1830
1829
|
}
|
|
@@ -1884,15 +1883,15 @@ const sampleRegistry = {
|
|
|
1884
1883
|
cap_groups: [
|
|
1885
1884
|
{
|
|
1886
1885
|
name: 'pdf-processing',
|
|
1887
|
-
adapter_urns: ['media:pdf'],
|
|
1886
|
+
adapter_urns: ['media:ext=pdf'],
|
|
1888
1887
|
caps: [
|
|
1889
1888
|
{
|
|
1890
|
-
urn: 'cap:in="media:pdf";disbind;out="media:disbound-page;
|
|
1889
|
+
urn: 'cap:in="media:ext=pdf";disbind;out="media:disbound-page;enc=utf-8;list"',
|
|
1891
1890
|
title: 'Disbind PDF',
|
|
1892
1891
|
description: 'Extract pages'
|
|
1893
1892
|
},
|
|
1894
1893
|
{
|
|
1895
|
-
urn: 'cap:in="media:pdf";extract-metadata;out="media:file-metadata;
|
|
1894
|
+
urn: 'cap:in="media:ext=pdf";extract-metadata;out="media:enc=utf-8;file-metadata;record"',
|
|
1896
1895
|
title: 'Extract Metadata',
|
|
1897
1896
|
description: 'Get PDF metadata'
|
|
1898
1897
|
}
|
|
@@ -1929,10 +1928,10 @@ const sampleRegistry = {
|
|
|
1929
1928
|
cap_groups: [
|
|
1930
1929
|
{
|
|
1931
1930
|
name: 'text-processing',
|
|
1932
|
-
adapter_urns: ['media:txt
|
|
1931
|
+
adapter_urns: ['media:enc=utf-8;ext=txt'],
|
|
1933
1932
|
caps: [
|
|
1934
1933
|
{
|
|
1935
|
-
urn: 'cap:in="media:txt
|
|
1934
|
+
urn: 'cap:in="media:enc=utf-8;ext=txt";disbind;out="media:disbound-page;enc=utf-8;list"',
|
|
1936
1935
|
title: 'Disbind Text',
|
|
1937
1936
|
description: 'Extract text pages'
|
|
1938
1937
|
}
|
|
@@ -1974,7 +1973,7 @@ const sampleRegistry = {
|
|
|
1974
1973
|
adapter_urns: ['media:json'],
|
|
1975
1974
|
caps: [
|
|
1976
1975
|
{
|
|
1977
|
-
urn: 'cap:in="media:json";pretty;out="media:json
|
|
1976
|
+
urn: 'cap:in="media:json";pretty;out="media:fmt=json"',
|
|
1978
1977
|
title: 'Pretty Print JSON',
|
|
1979
1978
|
description: 'Format JSON',
|
|
1980
1979
|
command: 'pretty'
|
|
@@ -2252,13 +2251,13 @@ function test328_cartridgeRepoServerGetByCategory() {
|
|
|
2252
2251
|
function test329_cartridgeRepoServerGetByCap() {
|
|
2253
2252
|
const server = new CartridgeRepoServer(sampleRegistry);
|
|
2254
2253
|
|
|
2255
|
-
const disbindCap = 'cap:in="media:pdf";disbind;out="media:disbound-page;
|
|
2254
|
+
const disbindCap = 'cap:in="media:ext=pdf";disbind;out="media:disbound-page;enc=utf-8;list"';
|
|
2256
2255
|
const cartridges = server.getCartridgesByCap(disbindCap);
|
|
2257
2256
|
|
|
2258
2257
|
assert(cartridges.length === 1, 'Should find 1 cartridge with this cap');
|
|
2259
2258
|
assert(cartridges[0].id === 'pdfcartridge', 'Should be pdfcartridge');
|
|
2260
2259
|
|
|
2261
|
-
const metadataCap = 'cap:in="media:pdf";extract-metadata;out="media:file-metadata;
|
|
2260
|
+
const metadataCap = 'cap:in="media:ext=pdf";extract-metadata;out="media:enc=utf-8;file-metadata;record"';
|
|
2262
2261
|
const metadataCartridges = server.getCartridgesByCap(metadataCap);
|
|
2263
2262
|
assert(metadataCartridges.length === 1, 'Should find metadata cap');
|
|
2264
2263
|
}
|
|
@@ -2301,7 +2300,7 @@ function test331_cartridgeRepoClientGetSuggestions() {
|
|
|
2301
2300
|
|
|
2302
2301
|
client.updateCache('https://example.com/api/cartridges', cartridges);
|
|
2303
2302
|
|
|
2304
|
-
const disbindCap = 'cap:in="media:pdf";disbind;out="media:disbound-page;
|
|
2303
|
+
const disbindCap = 'cap:in="media:ext=pdf";disbind;out="media:disbound-page;enc=utf-8;list"';
|
|
2305
2304
|
const suggestions = client.getSuggestionsForCap(disbindCap);
|
|
2306
2305
|
|
|
2307
2306
|
assert(suggestions.length === 1, 'Should find 1 suggestion');
|
|
@@ -2316,7 +2315,7 @@ function test331_cartridgeRepoClientGetSuggestions() {
|
|
|
2316
2315
|
assert(suggestions[0].capTitle === 'Disbind PDF', 'Should have cap title');
|
|
2317
2316
|
|
|
2318
2317
|
// Nightly cap should also surface a suggestion, with channel set to nightly.
|
|
2319
|
-
const prettyCap = 'cap:in="media:json";pretty;out="media:json
|
|
2318
|
+
const prettyCap = 'cap:in="media:json";pretty;out="media:fmt=json"';
|
|
2320
2319
|
const nightlySuggestions = client.getSuggestionsForCap(prettyCap);
|
|
2321
2320
|
assert(nightlySuggestions.length === 1, 'Should find nightly cap suggestion');
|
|
2322
2321
|
assert(nightlySuggestions[0].channel === 'nightly', 'Should report nightly channel');
|
|
@@ -2425,7 +2424,7 @@ function test335_cartridgeRepoServerClientIntegration() {
|
|
|
2425
2424
|
assert(cartridge.buildForPlatform('darwin-arm64') !== null, 'Cartridge should have darwin-arm64 build');
|
|
2426
2425
|
|
|
2427
2426
|
// Client can get suggestions
|
|
2428
|
-
const capUrn = 'cap:in="media:pdf";disbind;out="media:disbound-page;
|
|
2427
|
+
const capUrn = 'cap:in="media:ext=pdf";disbind;out="media:disbound-page;enc=utf-8;list"';
|
|
2429
2428
|
const suggestions = client.getSuggestionsForCap(capUrn);
|
|
2430
2429
|
assert(suggestions.length === 1, 'Should get suggestions');
|
|
2431
2430
|
assert(suggestions[0].cartridgeId === 'pdfcartridge', 'Should suggest correct cartridge');
|
|
@@ -2437,6 +2436,314 @@ function test335_cartridgeRepoServerClientIntegration() {
|
|
|
2437
2436
|
assert(searchResults[0].id === cartridge.id, 'Search and client should agree');
|
|
2438
2437
|
}
|
|
2439
2438
|
|
|
2439
|
+
// ============================================================================
|
|
2440
|
+
// Host-compatibility resolution (cartridge_repo.rs: TEST1849-TEST1853)
|
|
2441
|
+
// ============================================================================
|
|
2442
|
+
|
|
2443
|
+
// Construct a cartridge whose versions/platform-builds are fully specified.
|
|
2444
|
+
// `versions` is given newest-first as [version, [[platform, format, pkgName], ...]];
|
|
2445
|
+
// `version` (the "latest" field) is set to the first entry. Mirrors the Rust
|
|
2446
|
+
// test helper cartridge_with_versions.
|
|
2447
|
+
function cartridgeWithVersions(id, versions) {
|
|
2448
|
+
const versionMap = {};
|
|
2449
|
+
const available = [];
|
|
2450
|
+
for (const [ver, builds] of versions) {
|
|
2451
|
+
available.push(ver);
|
|
2452
|
+
versionMap[ver] = {
|
|
2453
|
+
releaseDate: '2026-02-07',
|
|
2454
|
+
changelog: [],
|
|
2455
|
+
minAppVersion: '',
|
|
2456
|
+
builds: builds.map(([plat, fmt, name]) => ({
|
|
2457
|
+
platform: plat,
|
|
2458
|
+
packages: [{
|
|
2459
|
+
name,
|
|
2460
|
+
sha256: 'deadbeef',
|
|
2461
|
+
size: 4242,
|
|
2462
|
+
url: `https://cartridges.machinefabric.com/${name}`,
|
|
2463
|
+
format: fmt,
|
|
2464
|
+
}],
|
|
2465
|
+
})),
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
const latest = versions.length > 0 ? versions[0][0] : '';
|
|
2469
|
+
return new CartridgeInfo({
|
|
2470
|
+
id,
|
|
2471
|
+
name: id,
|
|
2472
|
+
version: latest,
|
|
2473
|
+
description: '',
|
|
2474
|
+
author: '',
|
|
2475
|
+
teamId: 'TEAM123',
|
|
2476
|
+
signedAt: '2026-02-07T00:00:00Z',
|
|
2477
|
+
minAppVersion: '',
|
|
2478
|
+
pageUrl: '',
|
|
2479
|
+
categories: [],
|
|
2480
|
+
tags: [],
|
|
2481
|
+
cap_groups: [],
|
|
2482
|
+
versions: versionMap,
|
|
2483
|
+
availableVersions: available,
|
|
2484
|
+
channel: 'release',
|
|
2485
|
+
registryUrl: 'https://example.com/cartridges',
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// TEST1849: latest version has a host build → Compatible, resolving to the
|
|
2490
|
+
// latest version and that platform's native-format package.
|
|
2491
|
+
function test1849_resolveForHostCompatibleLatest() {
|
|
2492
|
+
const cartridge = cartridgeWithVersions('c', [
|
|
2493
|
+
['1.2.0', [['darwin-arm64', 'pkg', 'c-1.2.0.pkg'], ['linux-x86_64', 'deb', 'c-1.2.0.deb']]],
|
|
2494
|
+
['1.1.0', [['darwin-arm64', 'pkg', 'c-1.1.0.pkg']]],
|
|
2495
|
+
]);
|
|
2496
|
+
|
|
2497
|
+
const r = cartridge.resolveForHost('linux-x86_64');
|
|
2498
|
+
assertEqual(r.status, CompatStatus.COMPATIBLE, 'latest with host build is Compatible');
|
|
2499
|
+
assertEqual(r.resolvedVersion, '1.2.0', 'resolves to the latest version');
|
|
2500
|
+
assertEqual(r.resolvedPackage.name, 'c-1.2.0.deb', 'resolves to native-format package');
|
|
2501
|
+
assertEqual(r.resolvedPackage.format, 'deb', 'package format is deb');
|
|
2502
|
+
assert(r.reason === null, 'Compatible carries no reason');
|
|
2503
|
+
assertEqual(r.hostPlatform, 'linux-x86_64', 'host platform echoed back');
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
// TEST1850: the latest version lacks a host build but an older version has one
|
|
2507
|
+
// → CompatibleOutdated, resolving to the NEWEST older version with a host build
|
|
2508
|
+
// (not the oldest), with a reason naming both the latest and the resolved.
|
|
2509
|
+
function test1850_resolveForHostCompatibleOutdated() {
|
|
2510
|
+
const cartridge = cartridgeWithVersions('c', [
|
|
2511
|
+
['1.3.0', [['darwin-arm64', 'pkg', 'c-1.3.0.pkg']]],
|
|
2512
|
+
['1.2.0', [['darwin-arm64', 'pkg', 'c-1.2.0.pkg'], ['linux-x86_64', 'deb', 'c-1.2.0.deb']]],
|
|
2513
|
+
['1.1.0', [['linux-x86_64', 'deb', 'c-1.1.0.deb']]],
|
|
2514
|
+
]);
|
|
2515
|
+
|
|
2516
|
+
const r = cartridge.resolveForHost('linux-x86_64');
|
|
2517
|
+
assertEqual(r.status, CompatStatus.COMPATIBLE_OUTDATED, 'latest lacks host build → CompatibleOutdated');
|
|
2518
|
+
assertEqual(r.resolvedVersion, '1.2.0', 'newest-with-host-build is 1.2.0, not oldest 1.1.0');
|
|
2519
|
+
assertEqual(r.resolvedPackage.name, 'c-1.2.0.deb', 'resolves to 1.2.0 deb package');
|
|
2520
|
+
assert(r.reason !== null, 'outdated carries a reason');
|
|
2521
|
+
assert(r.reason.includes('1.3.0'), `reason names the latest: ${r.reason}`);
|
|
2522
|
+
assert(r.reason.includes('1.2.0'), `reason names the resolved: ${r.reason}`);
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
// TEST1851: no version ships a host build → Incompatible, no resolved
|
|
2526
|
+
// version/package, reason states the host platform.
|
|
2527
|
+
function test1851_resolveForHostIncompatible() {
|
|
2528
|
+
const cartridge = cartridgeWithVersions('c', [
|
|
2529
|
+
['1.2.0', [['darwin-arm64', 'pkg', 'c-1.2.0.pkg']]],
|
|
2530
|
+
['1.1.0', [['darwin-arm64', 'pkg', 'c-1.1.0.pkg']]],
|
|
2531
|
+
]);
|
|
2532
|
+
|
|
2533
|
+
const r = cartridge.resolveForHost('windows-x86_64');
|
|
2534
|
+
assertEqual(r.status, CompatStatus.INCOMPATIBLE, 'no host build → Incompatible');
|
|
2535
|
+
assert(r.resolvedVersion === null, 'no resolved version when Incompatible');
|
|
2536
|
+
assert(r.resolvedPackage === null, 'no resolved package when Incompatible');
|
|
2537
|
+
assert(r.reason.includes('windows-x86_64'), `reason names the host platform: ${r.reason}`);
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
// TEST1852: a host build whose packages[] is empty AND has no legacy `package`
|
|
2541
|
+
// ships no installer; resolution must SKIP it (not resolve to an un-downloadable
|
|
2542
|
+
// version) and fall through to an older usable version.
|
|
2543
|
+
function test1852_resolveForHostSkipsBuildWithNoInstaller() {
|
|
2544
|
+
const cartridge = cartridgeWithVersions('c', [
|
|
2545
|
+
['2.0.0', [['linux-x86_64', 'deb', 'c-2.0.0.deb']]],
|
|
2546
|
+
['1.0.0', [['linux-x86_64', 'deb', 'c-1.0.0.deb']]],
|
|
2547
|
+
]);
|
|
2548
|
+
// Make 2.0.0's linux build ship nothing installable.
|
|
2549
|
+
const v2 = cartridge.versions['2.0.0'];
|
|
2550
|
+
v2.builds[0].packages = [];
|
|
2551
|
+
delete v2.builds[0].package;
|
|
2552
|
+
|
|
2553
|
+
const r = cartridge.resolveForHost('linux-x86_64');
|
|
2554
|
+
// 2.0.0 is skipped (no installer); newest USABLE host build is 1.0.0.
|
|
2555
|
+
assertEqual(r.status, CompatStatus.COMPATIBLE_OUTDATED, '2.0.0 skipped → CompatibleOutdated to 1.0.0');
|
|
2556
|
+
assertEqual(r.resolvedVersion, '1.0.0', 'resolves to the older usable version');
|
|
2557
|
+
assertEqual(r.resolvedPackage.name, 'c-1.0.0.deb', 'resolves to 1.0.0 deb package');
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// TEST1853: hostPlatform() returns a normalized {os}-{arch} string with arch
|
|
2561
|
+
// aarch64/arm64 mapped to arm64 — the exact form the registry uses.
|
|
2562
|
+
function test1853_hostPlatformNormalizedForm() {
|
|
2563
|
+
const p = hostPlatform();
|
|
2564
|
+
const dash = p.indexOf('-');
|
|
2565
|
+
assert(dash > 0, `host_platform must be os-arch, got ${p}`);
|
|
2566
|
+
const os = p.slice(0, dash);
|
|
2567
|
+
const arch = p.slice(dash + 1);
|
|
2568
|
+
assert((os === 'darwin' || os === 'linux' || os === 'windows') || os.length > 0, `os segment present: ${os}`);
|
|
2569
|
+
// The registry never uses the raw "aarch64"; it must be normalized.
|
|
2570
|
+
assert(arch !== 'aarch64', 'arch must be normalized to arm64');
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
// ============================================================================
|
|
2574
|
+
// Build-env registry identity (manifest.rs: TEST1872-TEST1874)
|
|
2575
|
+
// ============================================================================
|
|
2576
|
+
|
|
2577
|
+
// TEST1872: a non-empty MFR_CARTRIDGE_REGISTRY_URL passes through verbatim —
|
|
2578
|
+
// a published build reports exactly the URL it was compiled with.
|
|
2579
|
+
function test1872_registryUrlFromBuildEnvPassesThroughNonempty() {
|
|
2580
|
+
const url = 'https://cartridges.machinefabric.com/manifest';
|
|
2581
|
+
assertEqual(registryUrlFromBuildEnv(url), url, 'non-empty URL passes through verbatim');
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// TEST1873: an unset env (null/undefined) yields null — a dev build has no
|
|
2585
|
+
// baked registry and loads only `dev/` cartridges.
|
|
2586
|
+
function test1873_registryUrlFromBuildEnvNoneForDev() {
|
|
2587
|
+
assert(registryUrlFromBuildEnv(null) === null, 'null env → null (dev build)');
|
|
2588
|
+
assert(registryUrlFromBuildEnv(undefined) === null, 'absent env → null (dev build)');
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
// TEST1874: an exported-but-empty env ('') is neither a dev build nor a valid
|
|
2592
|
+
// identity and MUST fail hard, so the build can never silently hash the empty
|
|
2593
|
+
// string into a fake registry slug.
|
|
2594
|
+
function test1874_registryUrlFromBuildEnvRejectsEmptyString() {
|
|
2595
|
+
let threw = false;
|
|
2596
|
+
try {
|
|
2597
|
+
registryUrlFromBuildEnv('');
|
|
2598
|
+
} catch (e) {
|
|
2599
|
+
threw = true;
|
|
2600
|
+
assert(e.message.includes('MFR_CARTRIDGE_REGISTRY_URL must be unset'), `panic message names the variable: ${e.message}`);
|
|
2601
|
+
}
|
|
2602
|
+
assert(threw, 'empty string must fail hard');
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
// ============================================================================
|
|
2606
|
+
// Cartridge discovery (cartridge_discovery.rs: TEST1875-TEST1878)
|
|
2607
|
+
// ============================================================================
|
|
2608
|
+
|
|
2609
|
+
// Lay down `{root}/{slug}/{channel}/{name}/{version}/`. When `cartridgeJson`
|
|
2610
|
+
// is provided, also write it plus an executable `entry` binary so readFromDir
|
|
2611
|
+
// accepts the directory and discovery reaches its own identity checks. Mirrors
|
|
2612
|
+
// the Rust test helper install_fixture.
|
|
2613
|
+
function discoveryInstallFixture(root, slug, channelFolder, name, version, cartridgeJson, entry) {
|
|
2614
|
+
const fs = require('fs');
|
|
2615
|
+
const path = require('path');
|
|
2616
|
+
const dir = path.join(root, slug, channelFolder, name, version);
|
|
2617
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2618
|
+
if (cartridgeJson !== null && cartridgeJson !== undefined) {
|
|
2619
|
+
fs.writeFileSync(path.join(dir, 'cartridge.json'), cartridgeJson);
|
|
2620
|
+
const entryPath = path.join(dir, entry);
|
|
2621
|
+
fs.writeFileSync(entryPath, '#!/bin/sh\nexit 0\n');
|
|
2622
|
+
fs.chmodSync(entryPath, 0o755);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
function discoveryDevCartridgeJson(channel, fabricManifestVersion) {
|
|
2627
|
+
return JSON.stringify({
|
|
2628
|
+
name: 'cart', version: '1.0.0', channel, registry_url: null, entry: 'cart',
|
|
2629
|
+
installed_at: '2024-01-01T00:00:00Z', fabric_manifest_version: fabricManifestVersion,
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
function discoveryRegistryCartridgeJson(url, channel, fmv) {
|
|
2634
|
+
return JSON.stringify({
|
|
2635
|
+
name: 'cart', version: '1.0.0', channel, registry_url: url, entry: 'cart',
|
|
2636
|
+
installed_at: '2024-01-01T00:00:00Z', fabric_manifest_version: fmv,
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
function discoveryMakeTempRoot() {
|
|
2641
|
+
const fs = require('fs');
|
|
2642
|
+
const os = require('os');
|
|
2643
|
+
const path = require('path');
|
|
2644
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'capdag-disc-'));
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
function discoveryRemoveRoot(root) {
|
|
2648
|
+
const fs = require('fs');
|
|
2649
|
+
try { fs.rmSync(root, { recursive: true, force: true }); } catch (_e) { /* best effort */ }
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
function discoveryExpectIncompatible(out, kind) {
|
|
2653
|
+
assertEqual(out.length, 1, 'expected exactly one discovered entry');
|
|
2654
|
+
assertEqual(out[0].kind, 'incompatible', 'entry must be incompatible');
|
|
2655
|
+
assertEqual(out[0].error.kind, kind, `wrong attachment-error kind: ${out[0].error.message}`);
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
// TEST1875: scan-all — a registry slug folder AND the dev slot present on disk
|
|
2659
|
+
// are BOTH scanned, regardless of the host's own baked registry. Both fixtures
|
|
2660
|
+
// lack a real bifaci binary, so both end at HandshakeFailed — proving discovery
|
|
2661
|
+
// REACHED them (was not filtered out by a registry pin). A registry-pin
|
|
2662
|
+
// rejection would instead surface BadInstallation and never probe.
|
|
2663
|
+
async function test1875_scanAllReachesBothDevAndRegistrySlugs() {
|
|
2664
|
+
const root = discoveryMakeTempRoot();
|
|
2665
|
+
try {
|
|
2666
|
+
const url = 'https://cartridges.example.com/manifest';
|
|
2667
|
+
const rslug = slugForSync(url);
|
|
2668
|
+
// Host baked for a DIFFERENT registry than the on-disk registry cartridge.
|
|
2669
|
+
const host = new DiscoveryIdentity({ channel: 'nightly', registryUrl: 'https://other.example.com/manifest', fabricManifestVersion: 1 });
|
|
2670
|
+
discoveryInstallFixture(root, 'dev', 'nightly', 'devcart', '1.0.0', discoveryDevCartridgeJson('nightly', 1), 'cart');
|
|
2671
|
+
discoveryInstallFixture(root, rslug, 'nightly', 'regcart', '1.0.0', discoveryRegistryCartridgeJson(url, 'nightly', 1), 'cart');
|
|
2672
|
+
const out = await discoverCartridges(root, host);
|
|
2673
|
+
assertEqual(out.length, 2, `both slugs must be scanned, got ${out.length}`);
|
|
2674
|
+
for (const c of out) {
|
|
2675
|
+
assertEqual(c.kind, 'incompatible', 'both reach the probe stage');
|
|
2676
|
+
assertEqual(c.error.kind, CartridgeAttachmentErrorKind.HANDSHAKE_FAILED,
|
|
2677
|
+
`both reached the probe (not registry-pin-rejected): ${c.error.message}`);
|
|
2678
|
+
}
|
|
2679
|
+
} finally {
|
|
2680
|
+
discoveryRemoveRoot(root);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
// TEST1876: only the host's channel subtree is scanned. A cartridge under a
|
|
2685
|
+
// slug's `release/` folder is invisible to a nightly host even though the slug
|
|
2686
|
+
// folder is present (its `nightly/` subtree is absent).
|
|
2687
|
+
async function test1876_otherChannelSubtreeIsSkipped() {
|
|
2688
|
+
const root = discoveryMakeTempRoot();
|
|
2689
|
+
try {
|
|
2690
|
+
const url = 'https://cartridges.example.com/manifest';
|
|
2691
|
+
const rslug = slugForSync(url);
|
|
2692
|
+
discoveryInstallFixture(root, rslug, 'release', 'regcart', '1.0.0', discoveryRegistryCartridgeJson(url, 'release', 1), 'cart');
|
|
2693
|
+
const out = await discoverCartridges(root, new DiscoveryIdentity({ channel: 'nightly', registryUrl: null, fabricManifestVersion: 1 }));
|
|
2694
|
+
assertEqual(out.length, 0, 'a release-only slug must be invisible to a nightly host');
|
|
2695
|
+
} finally {
|
|
2696
|
+
discoveryRemoveRoot(root);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
// TEST1877: a registry cartridge hand-copied under the WRONG registry slug
|
|
2701
|
+
// folder fails the three-place rule (BadInstallation) — scan-all does not mean
|
|
2702
|
+
// "accept anywhere"; placement must still be self-consistent.
|
|
2703
|
+
async function test1877_registryCartridgeUnderWrongSlugIsBadInstall() {
|
|
2704
|
+
const root = discoveryMakeTempRoot();
|
|
2705
|
+
try {
|
|
2706
|
+
const url = 'https://cartridges.example.com/manifest';
|
|
2707
|
+
const wrongSlug = slugForSync('https://somewhere-else.example.com/manifest');
|
|
2708
|
+
const json = discoveryRegistryCartridgeJson(url, 'nightly', 1);
|
|
2709
|
+
discoveryInstallFixture(root, wrongSlug, 'nightly', 'cart', '1.0.0', json, 'cart');
|
|
2710
|
+
const out = await discoverCartridges(root, new DiscoveryIdentity({ channel: 'nightly', registryUrl: null, fabricManifestVersion: 1 }));
|
|
2711
|
+
discoveryExpectIncompatible(out, CartridgeAttachmentErrorKind.BAD_INSTALLATION);
|
|
2712
|
+
} finally {
|
|
2713
|
+
discoveryRemoveRoot(root);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
// TEST1878: a cartridge marked `installed_from: bundle` with no baked hash in
|
|
2718
|
+
// BUNDLED_PROVIDER_HASHES (empty in this mirror) is rejected as BadInstallation
|
|
2719
|
+
// — the bundled-integrity gate fires before the probe. Non-macOS only: on macOS
|
|
2720
|
+
// the baked-hash path is intentionally absent (OS code-signature is the guard),
|
|
2721
|
+
// so a bundled provider is accepted there and would instead end at the probe.
|
|
2722
|
+
async function test1878_bundledProviderWithoutBakedHashIsRejected() {
|
|
2723
|
+
if (process.platform === 'darwin') {
|
|
2724
|
+
// Mirrors the Rust #[cfg(not(target_os = "macos"))] gate: on macOS the
|
|
2725
|
+
// baked-hash path does not exist, so this scenario is not exercised.
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
const root = discoveryMakeTempRoot();
|
|
2729
|
+
try {
|
|
2730
|
+
// Dev slug (null registry) but installed_from=bundle — placement is
|
|
2731
|
+
// self-consistent (null→dev), so it passes readFromDir and reaches the
|
|
2732
|
+
// bundled-hash gate, which has no baked entry → BadInstallation.
|
|
2733
|
+
const json = JSON.stringify({
|
|
2734
|
+
name: 'cart', version: '1.0.0', channel: 'nightly', registry_url: null, entry: 'cart',
|
|
2735
|
+
installed_at: '2024-01-01T00:00:00Z', installed_from: 'bundle', fabric_manifest_version: 1,
|
|
2736
|
+
});
|
|
2737
|
+
discoveryInstallFixture(root, 'dev', 'nightly', 'cart', '1.0.0', json, 'cart');
|
|
2738
|
+
const out = await discoverCartridges(root, new DiscoveryIdentity({ channel: 'nightly', registryUrl: null, fabricManifestVersion: 1 }));
|
|
2739
|
+
discoveryExpectIncompatible(out, CartridgeAttachmentErrorKind.BAD_INSTALLATION);
|
|
2740
|
+
assert(out[0].error.message.includes('bundled provider integrity'),
|
|
2741
|
+
`message should name the bundled-integrity failure: ${out[0].error.message}`);
|
|
2742
|
+
} finally {
|
|
2743
|
+
discoveryRemoveRoot(root);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2440
2747
|
// ============================================================================
|
|
2441
2748
|
// media_urn.rs: TEST1294-TEST1302 (MediaUrn predicates)
|
|
2442
2749
|
// ============================================================================
|
|
@@ -2521,37 +2828,36 @@ function test0086_isCollection() {
|
|
|
2521
2828
|
|
|
2522
2829
|
// TEST1302: predicates are consistent with constants — every constant triggers exactly the expected predicates
|
|
2523
2830
|
function test1302_predicateConstantConsistency() {
|
|
2524
|
-
// MEDIA_INTEGER must be numeric,
|
|
2831
|
+
// MEDIA_INTEGER must be numeric, scalar, NOT enc-bearing/bool/image/list.
|
|
2832
|
+
// Integers carry no enc= (a number is not a character-encoded string).
|
|
2525
2833
|
const intUrn = MediaUrn.fromString(MEDIA_INTEGER);
|
|
2526
2834
|
assert(intUrn.isNumeric(), 'MEDIA_INTEGER must be numeric');
|
|
2527
|
-
assert(intUrn.
|
|
2835
|
+
assert(intUrn.getTag('enc') === undefined, 'MEDIA_INTEGER must not carry enc');
|
|
2528
2836
|
assert(intUrn.isScalar(), 'MEDIA_INTEGER must be scalar');
|
|
2529
|
-
assert(!intUrn.isBinary(), 'MEDIA_INTEGER must not be binary');
|
|
2530
2837
|
assert(!intUrn.isBool(), 'MEDIA_INTEGER must not be bool');
|
|
2531
2838
|
assert(!intUrn.isImage(), 'MEDIA_INTEGER must not be image');
|
|
2532
2839
|
assert(!intUrn.isList(), 'MEDIA_INTEGER must not be list');
|
|
2533
2840
|
|
|
2534
|
-
// MEDIA_BOOLEAN must be bool,
|
|
2841
|
+
// MEDIA_BOOLEAN must be bool, enc-bearing, scalar, NOT numeric
|
|
2535
2842
|
const boolUrn = MediaUrn.fromString(MEDIA_BOOLEAN);
|
|
2536
2843
|
assert(boolUrn.isBool(), 'MEDIA_BOOLEAN must be bool');
|
|
2537
|
-
assert(boolUrn.
|
|
2844
|
+
assert(boolUrn.getTag('enc') === 'utf-8', 'MEDIA_BOOLEAN must carry enc=utf-8');
|
|
2538
2845
|
assert(boolUrn.isScalar(), 'MEDIA_BOOLEAN must be scalar');
|
|
2539
2846
|
assert(!boolUrn.isNumeric(), 'MEDIA_BOOLEAN must not be numeric');
|
|
2540
2847
|
|
|
2541
|
-
// MEDIA_JSON must be json,
|
|
2848
|
+
// MEDIA_JSON must be json, record, scalar, NOT list — and carries no enc
|
|
2849
|
+
// (fmt= is the content-format axis, orthogonal to enc=).
|
|
2542
2850
|
const jsonUrn = MediaUrn.fromString(MEDIA_JSON);
|
|
2543
2851
|
assert(jsonUrn.isJson(), 'MEDIA_JSON must be json');
|
|
2544
|
-
assert(jsonUrn.
|
|
2852
|
+
assert(jsonUrn.getTag('enc') === undefined, 'MEDIA_JSON must not carry enc');
|
|
2545
2853
|
assert(jsonUrn.isRecord(), 'MEDIA_JSON must be record');
|
|
2546
2854
|
assert(jsonUrn.isScalar(), 'MEDIA_JSON must be scalar (no list marker)');
|
|
2547
|
-
assert(!jsonUrn.isBinary(), 'MEDIA_JSON must not be binary');
|
|
2548
2855
|
assert(!jsonUrn.isList(), 'MEDIA_JSON must not be list');
|
|
2549
2856
|
|
|
2550
|
-
// MEDIA_VOID is void, NOT
|
|
2857
|
+
// MEDIA_VOID is void, NOT numeric, and carries no enc tag
|
|
2551
2858
|
const voidUrn = MediaUrn.fromString(MEDIA_VOID);
|
|
2552
2859
|
assert(voidUrn.isVoid(), 'MEDIA_VOID must be void');
|
|
2553
|
-
assert(
|
|
2554
|
-
assert(voidUrn.isBinary(), 'MEDIA_VOID must be binary (no textable tag)');
|
|
2860
|
+
assert(voidUrn.getTag('enc') === undefined, 'MEDIA_VOID must not carry enc');
|
|
2555
2861
|
assert(!voidUrn.isNumeric(), 'MEDIA_VOID must not be numeric');
|
|
2556
2862
|
}
|
|
2557
2863
|
|
|
@@ -2593,13 +2899,13 @@ function test1304_withInOutSpec() {
|
|
|
2593
2899
|
assertEqual(changedOut.getOutSpec(), 'media:string', 'withOutSpec should change outSpec');
|
|
2594
2900
|
|
|
2595
2901
|
// Chain both
|
|
2596
|
-
const changedBoth = cap.withInSpec('media:pdf').withOutSpec(MEDIA_TXT);
|
|
2597
|
-
assertEqual(changedBoth.getInSpec(), 'media:pdf', 'Chain should set inSpec');
|
|
2598
|
-
assertEqual(changedBoth.getOutSpec(), 'media:ext=txt
|
|
2902
|
+
const changedBoth = cap.withInSpec('media:ext=pdf').withOutSpec(MEDIA_TXT);
|
|
2903
|
+
assertEqual(changedBoth.getInSpec(), 'media:ext=pdf', 'Chain should set inSpec');
|
|
2904
|
+
assertEqual(changedBoth.getOutSpec(), 'media:enc=utf-8;ext=txt', 'Chain should set outSpec');
|
|
2599
2905
|
|
|
2600
2906
|
const identity = CapUrn.fromString('cap:effect=none');
|
|
2601
2907
|
assertThrows(
|
|
2602
|
-
() => identity.withOutSpec('media:pdf'),
|
|
2908
|
+
() => identity.withOutSpec('media:ext=pdf'),
|
|
2603
2909
|
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2604
2910
|
'withOutSpec must revalidate admissibility'
|
|
2605
2911
|
);
|
|
@@ -2882,7 +3188,7 @@ function test652_capIdentityConstantWorks() {
|
|
|
2882
3188
|
// TEST653: invalid effect=none declarations fail at construction.
|
|
2883
3189
|
function test653_invalidEffectNoneDeclarationRejected() {
|
|
2884
3190
|
assertThrows(
|
|
2885
|
-
() => CapUrn.fromString('cap:in=media:pdf;out=media:
|
|
3191
|
+
() => CapUrn.fromString('cap:in="media:ext=pdf";effect=none;out="media:enc=utf-8"'),
|
|
2886
3192
|
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2887
3193
|
'invalid effect=none declaration must fail at construction'
|
|
2888
3194
|
);
|
|
@@ -2892,7 +3198,7 @@ function test653_invalidEffectNoneDeclarationRejected() {
|
|
|
2892
3198
|
function test654_effectNonePreservesRuntimeMedia() {
|
|
2893
3199
|
const decimate = CapUrn.fromString('cap:decimate-sequence;effect=none');
|
|
2894
3200
|
const png = MediaUrn.fromString('media:image;png');
|
|
2895
|
-
const pdf = MediaUrn.fromString('media:pdf');
|
|
3201
|
+
const pdf = MediaUrn.fromString('media:ext=pdf');
|
|
2896
3202
|
assertEqual(decimate.inferRuntimeOutputMedia(png).toString(), png.toString(), 'effect=none should preserve png');
|
|
2897
3203
|
assertEqual(decimate.inferRuntimeOutputMedia(pdf).toString(), pdf.toString(), 'effect=none should preserve pdf');
|
|
2898
3204
|
}
|
|
@@ -2911,7 +3217,7 @@ function test655_effectDeclaredUsesDeclaredOutput() {
|
|
|
2911
3217
|
// TEST656: invalid effect=none declarations fail hard at construction.
|
|
2912
3218
|
function test656_invalidEffectNoneFailsHard() {
|
|
2913
3219
|
assertThrows(
|
|
2914
|
-
() => CapUrn.fromString('cap:in=media:pdf;out=media:
|
|
3220
|
+
() => CapUrn.fromString('cap:in="media:ext=pdf";effect=none;out="media:enc=utf-8"'),
|
|
2915
3221
|
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2916
3222
|
'invalid effect=none declaration must fail at construction'
|
|
2917
3223
|
);
|
|
@@ -2944,7 +3250,7 @@ function test0088_Machine_whitespaceOnly() {
|
|
|
2944
3250
|
// TEST0089: Machine header only no wirings
|
|
2945
3251
|
function test0089_Machine_headerOnlyNoWirings() {
|
|
2946
3252
|
assertThrowsWithCode(
|
|
2947
|
-
() => Machine.fromString('[extract cap:in="media:pdf";extract;out="media:txt
|
|
3253
|
+
() => Machine.fromString('[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]'),
|
|
2948
3254
|
MachineSyntaxErrorCodes.NO_EDGES
|
|
2949
3255
|
);
|
|
2950
3256
|
}
|
|
@@ -2953,8 +3259,8 @@ function test0089_Machine_headerOnlyNoWirings() {
|
|
|
2953
3259
|
function test0090_Machine_duplicateAlias() {
|
|
2954
3260
|
assertThrowsWithCode(
|
|
2955
3261
|
() => Machine.fromString(
|
|
2956
|
-
'[ex cap:in="media:pdf";extract;out="media:txt
|
|
2957
|
-
'[ex cap:in="media:pdf";summarize;out="media:txt
|
|
3262
|
+
'[ex cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3263
|
+
'[ex cap:in="media:ext=pdf";summarize;out="media:enc=utf-8;ext=txt"]' +
|
|
2958
3264
|
'[a -> ex -> b]'
|
|
2959
3265
|
),
|
|
2960
3266
|
MachineSyntaxErrorCodes.DUPLICATE_ALIAS
|
|
@@ -2964,40 +3270,40 @@ function test0090_Machine_duplicateAlias() {
|
|
|
2964
3270
|
// TEST0094: Machine simple linear chain
|
|
2965
3271
|
function test0094_Machine_simpleLinearChain() {
|
|
2966
3272
|
const g = Machine.fromString(
|
|
2967
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
3273
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
2968
3274
|
'[doc -> extract -> text]'
|
|
2969
3275
|
);
|
|
2970
3276
|
assertEqual(g.edgeCount(), 1);
|
|
2971
3277
|
const edge = g.edges()[0];
|
|
2972
3278
|
assertEqual(edge.sources.length, 1);
|
|
2973
|
-
assert(edge.sources[0].isEquivalent(MediaUrn.fromString('media:pdf')),
|
|
2974
|
-
'Source should be media:pdf');
|
|
2975
|
-
assert(edge.target.isEquivalent(MediaUrn.fromString('media:txt
|
|
2976
|
-
'Target should be media:txt
|
|
3279
|
+
assert(edge.sources[0].isEquivalent(MediaUrn.fromString('media:ext=pdf')),
|
|
3280
|
+
'Source should be media:ext=pdf');
|
|
3281
|
+
assert(edge.target.isEquivalent(MediaUrn.fromString('media:enc=utf-8;ext=txt')),
|
|
3282
|
+
'Target should be media:enc=utf-8;ext=txt');
|
|
2977
3283
|
assertEqual(edge.isLoop, false);
|
|
2978
3284
|
}
|
|
2979
3285
|
|
|
2980
3286
|
// TEST0095: Machine two step chain
|
|
2981
3287
|
function test0095_Machine_twoStepChain() {
|
|
2982
3288
|
const g = Machine.fromString(
|
|
2983
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
2984
|
-
'[embed cap:in="media:txt
|
|
3289
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3290
|
+
'[embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"]' +
|
|
2985
3291
|
'[doc -> extract -> text]' +
|
|
2986
3292
|
'[text -> embed -> vectors]'
|
|
2987
3293
|
);
|
|
2988
3294
|
assertEqual(g.edgeCount(), 2);
|
|
2989
|
-
assert(g.edges()[0].sources[0].isEquivalent(MediaUrn.fromString('media:pdf')),
|
|
2990
|
-
'First edge source should be media:pdf');
|
|
2991
|
-
assert(g.edges()[1].target.isEquivalent(MediaUrn.fromString('media:embedding-vector;record
|
|
2992
|
-
'Second edge target should be media:embedding-vector;record
|
|
3295
|
+
assert(g.edges()[0].sources[0].isEquivalent(MediaUrn.fromString('media:ext=pdf')),
|
|
3296
|
+
'First edge source should be media:ext=pdf');
|
|
3297
|
+
assert(g.edges()[1].target.isEquivalent(MediaUrn.fromString('media:embedding-vector;enc=utf-8;record')),
|
|
3298
|
+
'Second edge target should be media:embedding-vector;enc=utf-8;record');
|
|
2993
3299
|
}
|
|
2994
3300
|
|
|
2995
3301
|
// TEST0096: Machine fan out
|
|
2996
3302
|
function test0096_Machine_fanOut() {
|
|
2997
3303
|
const g = Machine.fromString(
|
|
2998
|
-
'[meta cap:in="media:pdf";extract-metadata;out="media:file-metadata;record
|
|
2999
|
-
'[outline cap:in="media:pdf";extract-outline;out="media:document-outline;record
|
|
3000
|
-
'[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]' +
|
|
3304
|
+
'[meta cap:in="media:ext=pdf";extract-metadata;out="media:enc=utf-8;file-metadata;record"]' +
|
|
3305
|
+
'[outline cap:in="media:ext=pdf";extract-outline;out="media:document-outline;enc=utf-8;record"]' +
|
|
3306
|
+
'[thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:image;png;thumbnail"]' +
|
|
3001
3307
|
'[doc -> meta -> metadata]' +
|
|
3002
3308
|
'[doc -> outline -> outline_data]' +
|
|
3003
3309
|
'[doc -> thumb -> thumbnail]'
|
|
@@ -3005,17 +3311,17 @@ function test0096_Machine_fanOut() {
|
|
|
3005
3311
|
assertEqual(g.edgeCount(), 3);
|
|
3006
3312
|
for (const edge of g.edges()) {
|
|
3007
3313
|
assertEqual(edge.sources.length, 1);
|
|
3008
|
-
assert(edge.sources[0].isEquivalent(MediaUrn.fromString('media:pdf')),
|
|
3009
|
-
'All fan-out sources should be media:pdf');
|
|
3314
|
+
assert(edge.sources[0].isEquivalent(MediaUrn.fromString('media:ext=pdf')),
|
|
3315
|
+
'All fan-out sources should be media:ext=pdf');
|
|
3010
3316
|
}
|
|
3011
3317
|
}
|
|
3012
3318
|
|
|
3013
3319
|
// TEST0097: Machine fan in secondary assigned by prior wiring
|
|
3014
3320
|
function test0097_Machine_fanInSecondaryAssignedByPriorWiring() {
|
|
3015
3321
|
const g = Machine.fromString(
|
|
3016
|
-
'[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]' +
|
|
3017
|
-
'[model_dl cap:in="media:model-spec
|
|
3018
|
-
'[describe cap:in="media:image;png";describe-image;out="media:image-description
|
|
3322
|
+
'[thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:image;png;thumbnail"]' +
|
|
3323
|
+
'[model_dl cap:in="media:enc=utf-8;model-spec";download;out="media:enc=utf-8;model-spec"]' +
|
|
3324
|
+
'[describe cap:in="media:image;png";describe-image;out="media:enc=utf-8;image-description"]' +
|
|
3019
3325
|
'[doc -> thumb -> thumbnail]' +
|
|
3020
3326
|
'[spec_input -> model_dl -> model_spec]' +
|
|
3021
3327
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
@@ -3027,7 +3333,7 @@ function test0097_Machine_fanInSecondaryAssignedByPriorWiring() {
|
|
|
3027
3333
|
// TEST0098: Machine fan in secondary unassigned gets wildcard
|
|
3028
3334
|
function test0098_Machine_fanInSecondaryUnassignedGetsWildcard() {
|
|
3029
3335
|
const g = Machine.fromString(
|
|
3030
|
-
'[describe cap:in="media:image;png";describe-image;out="media:image-description
|
|
3336
|
+
'[describe cap:in="media:image;png";describe-image;out="media:enc=utf-8;image-description"]\n' +
|
|
3031
3337
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
3032
3338
|
);
|
|
3033
3339
|
assertEqual(g.edges().length, 1);
|
|
@@ -3039,7 +3345,7 @@ function test0098_Machine_fanInSecondaryUnassignedGetsWildcard() {
|
|
|
3039
3345
|
// TEST0111: Machine loop edge
|
|
3040
3346
|
function test0111_Machine_loopEdge() {
|
|
3041
3347
|
const g = Machine.fromString(
|
|
3042
|
-
'[p2t cap:in="media:disbound-page;
|
|
3348
|
+
'[p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"]' +
|
|
3043
3349
|
'[pages -> LOOP p2t -> texts]'
|
|
3044
3350
|
);
|
|
3045
3351
|
assertEqual(g.edgeCount(), 1);
|
|
@@ -3058,7 +3364,7 @@ function test0112_Machine_undefinedAliasFails() {
|
|
|
3058
3364
|
function test0113_Machine_nodeAliasCollision() {
|
|
3059
3365
|
assertThrowsWithCode(
|
|
3060
3366
|
() => Machine.fromString(
|
|
3061
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
3367
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3062
3368
|
'[extract -> extract -> text]'
|
|
3063
3369
|
),
|
|
3064
3370
|
MachineSyntaxErrorCodes.NODE_ALIAS_COLLISION
|
|
@@ -3069,8 +3375,8 @@ function test0113_Machine_nodeAliasCollision() {
|
|
|
3069
3375
|
function test0114_Machine_conflictingMediaTypesFail() {
|
|
3070
3376
|
assertThrowsWithCode(
|
|
3071
3377
|
() => Machine.fromString(
|
|
3072
|
-
'[cap1 cap:in="media:txt
|
|
3073
|
-
'[cap2 cap:in="media:audio;wav";b;out="media:txt
|
|
3378
|
+
'[cap1 cap:in="media:enc=utf-8;ext=txt";a;out="media:ext=pdf"]' +
|
|
3379
|
+
'[cap2 cap:in="media:audio;wav";b;out="media:enc=utf-8;ext=txt"]' +
|
|
3074
3380
|
'[src -> cap1 -> mid]' +
|
|
3075
3381
|
'[mid -> cap2 -> dst]'
|
|
3076
3382
|
),
|
|
@@ -3081,8 +3387,8 @@ function test0114_Machine_conflictingMediaTypesFail() {
|
|
|
3081
3387
|
// TEST0117: Machine multiline format
|
|
3082
3388
|
function test0117_Machine_multilineFormat() {
|
|
3083
3389
|
const g = Machine.fromString(
|
|
3084
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
3085
|
-
'[embed cap:in="media:txt
|
|
3390
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n' +
|
|
3391
|
+
'[embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"]\n' +
|
|
3086
3392
|
'[doc -> extract -> text]\n' +
|
|
3087
3393
|
'[text -> embed -> vectors]\n'
|
|
3088
3394
|
);
|
|
@@ -3092,11 +3398,11 @@ function test0117_Machine_multilineFormat() {
|
|
|
3092
3398
|
// TEST0118: Machine different aliases same graph
|
|
3093
3399
|
function test0118_Machine_differentAliasesSameGraph() {
|
|
3094
3400
|
const g1 = Machine.fromString(
|
|
3095
|
-
'[ex cap:in="media:pdf";extract;out="media:txt
|
|
3401
|
+
'[ex cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3096
3402
|
'[a -> ex -> b]'
|
|
3097
3403
|
);
|
|
3098
3404
|
const g2 = Machine.fromString(
|
|
3099
|
-
'[xt cap:in="media:pdf";extract;out="media:txt
|
|
3405
|
+
'[xt cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3100
3406
|
'[x -> xt -> y]'
|
|
3101
3407
|
);
|
|
3102
3408
|
assert(g1.isEquivalent(g2), 'Different aliases should produce equivalent graphs');
|
|
@@ -3113,7 +3419,7 @@ function test0119_Machine_malformedInputFails() {
|
|
|
3113
3419
|
// TEST0120: Machine unterminated bracket fails
|
|
3114
3420
|
function test0120_Machine_unterminatedBracketFails() {
|
|
3115
3421
|
assertThrowsWithCode(
|
|
3116
|
-
() => parseMachine('[extract cap:in=media:pdf'),
|
|
3422
|
+
() => parseMachine('[extract cap:in=media:ext=pdf'),
|
|
3117
3423
|
MachineSyntaxErrorCodes.PARSE_ERROR
|
|
3118
3424
|
);
|
|
3119
3425
|
}
|
|
@@ -3122,22 +3428,22 @@ function test0120_Machine_unterminatedBracketFails() {
|
|
|
3122
3428
|
|
|
3123
3429
|
function test0121_Machine_lineBasedSimpleChain() {
|
|
3124
3430
|
const g = Machine.fromString(
|
|
3125
|
-
'extract cap:in="media:pdf";extract;out="media:txt
|
|
3431
|
+
'extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"\n' +
|
|
3126
3432
|
'doc -> extract -> text'
|
|
3127
3433
|
);
|
|
3128
3434
|
assertEqual(g.edgeCount(), 1);
|
|
3129
3435
|
const edge = g.edges()[0];
|
|
3130
|
-
assert(edge.sources[0].isEquivalent(MediaUrn.fromString('media:pdf')),
|
|
3131
|
-
'Source should be media:pdf');
|
|
3132
|
-
assert(edge.target.isEquivalent(MediaUrn.fromString('media:txt
|
|
3133
|
-
'Target should be media:txt
|
|
3436
|
+
assert(edge.sources[0].isEquivalent(MediaUrn.fromString('media:ext=pdf')),
|
|
3437
|
+
'Source should be media:ext=pdf');
|
|
3438
|
+
assert(edge.target.isEquivalent(MediaUrn.fromString('media:enc=utf-8;ext=txt')),
|
|
3439
|
+
'Target should be media:enc=utf-8;ext=txt');
|
|
3134
3440
|
}
|
|
3135
3441
|
|
|
3136
3442
|
// TEST0122: Machine line based two step chain
|
|
3137
3443
|
function test0122_Machine_lineBasedTwoStepChain() {
|
|
3138
3444
|
const g = Machine.fromString(
|
|
3139
|
-
'extract cap:in="media:pdf";extract;out="media:txt
|
|
3140
|
-
'embed cap:in="media:txt
|
|
3445
|
+
'extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"\n' +
|
|
3446
|
+
'embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"\n' +
|
|
3141
3447
|
'doc -> extract -> text\n' +
|
|
3142
3448
|
'text -> embed -> vectors'
|
|
3143
3449
|
);
|
|
@@ -3147,7 +3453,7 @@ function test0122_Machine_lineBasedTwoStepChain() {
|
|
|
3147
3453
|
// TEST0123: Machine line based loop
|
|
3148
3454
|
function test0123_Machine_lineBasedLoop() {
|
|
3149
3455
|
const g = Machine.fromString(
|
|
3150
|
-
'p2t cap:in="media:disbound-page;
|
|
3456
|
+
'p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"\n' +
|
|
3151
3457
|
'pages -> LOOP p2t -> texts'
|
|
3152
3458
|
);
|
|
3153
3459
|
assertEqual(g.edgeCount(), 1);
|
|
@@ -3157,9 +3463,9 @@ function test0123_Machine_lineBasedLoop() {
|
|
|
3157
3463
|
// TEST0124: Machine line based fan in
|
|
3158
3464
|
function test0124_Machine_lineBasedFanIn() {
|
|
3159
3465
|
const g = Machine.fromString(
|
|
3160
|
-
'thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"\n' +
|
|
3161
|
-
'model_dl cap:in="media:model-spec
|
|
3162
|
-
'describe cap:in="media:image;png";describe-image;out="media:image-description
|
|
3466
|
+
'thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:image;png;thumbnail"\n' +
|
|
3467
|
+
'model_dl cap:in="media:enc=utf-8;model-spec";download;out="media:enc=utf-8;model-spec"\n' +
|
|
3468
|
+
'describe cap:in="media:image;png";describe-image;out="media:enc=utf-8;image-description"\n' +
|
|
3163
3469
|
'doc -> thumb -> thumbnail\n' +
|
|
3164
3470
|
'spec_input -> model_dl -> model_spec\n' +
|
|
3165
3471
|
'(thumbnail, model_spec) -> describe -> description'
|
|
@@ -3171,7 +3477,7 @@ function test0124_Machine_lineBasedFanIn() {
|
|
|
3171
3477
|
// TEST0125: Machine mixed bracketed and line based
|
|
3172
3478
|
function test0125_Machine_mixedBracketedAndLineBased() {
|
|
3173
3479
|
const g = Machine.fromString(
|
|
3174
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
3480
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n' +
|
|
3175
3481
|
'doc -> extract -> text'
|
|
3176
3482
|
);
|
|
3177
3483
|
assertEqual(g.edgeCount(), 1);
|
|
@@ -3180,11 +3486,11 @@ function test0125_Machine_mixedBracketedAndLineBased() {
|
|
|
3180
3486
|
// TEST0126: Machine line based equivalent to bracketed
|
|
3181
3487
|
function test0126_Machine_lineBasedEquivalentToBracketed() {
|
|
3182
3488
|
const g1 = Machine.fromString(
|
|
3183
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
3489
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3184
3490
|
'[doc -> extract -> text]'
|
|
3185
3491
|
);
|
|
3186
3492
|
const g2 = Machine.fromString(
|
|
3187
|
-
'extract cap:in="media:pdf";extract;out="media:txt
|
|
3493
|
+
'extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"\n' +
|
|
3188
3494
|
'doc -> extract -> text'
|
|
3189
3495
|
);
|
|
3190
3496
|
assert(g1.isEquivalent(g2), 'Line-based and bracketed must produce equivalent graphs');
|
|
@@ -3194,9 +3500,9 @@ function test0126_Machine_lineBasedEquivalentToBracketed() {
|
|
|
3194
3500
|
function test0127_Machine_lineBasedFormatSerialization() {
|
|
3195
3501
|
const g = new Machine([
|
|
3196
3502
|
new MachineEdge(
|
|
3197
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3198
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3199
|
-
MediaUrn.fromString('media:txt
|
|
3503
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3504
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3505
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3200
3506
|
false
|
|
3201
3507
|
),
|
|
3202
3508
|
]);
|
|
@@ -3216,15 +3522,15 @@ function test0127_Machine_lineBasedFormatSerialization() {
|
|
|
3216
3522
|
function test0128_Machine_lineBasedAndBracketedParseSameGraph() {
|
|
3217
3523
|
const g = new Machine([
|
|
3218
3524
|
new MachineEdge(
|
|
3219
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3220
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3221
|
-
MediaUrn.fromString('media:txt
|
|
3525
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3526
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3527
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3222
3528
|
false
|
|
3223
3529
|
),
|
|
3224
3530
|
new MachineEdge(
|
|
3225
|
-
[MediaUrn.fromString('media:txt
|
|
3226
|
-
CapUrn.fromString('cap:in="media:txt
|
|
3227
|
-
MediaUrn.fromString('media:embedding-vector;record
|
|
3531
|
+
[MediaUrn.fromString('media:enc=utf-8;ext=txt')],
|
|
3532
|
+
CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
|
|
3533
|
+
MediaUrn.fromString('media:embedding-vector;enc=utf-8;record'),
|
|
3228
3534
|
false
|
|
3229
3535
|
),
|
|
3230
3536
|
]);
|
|
@@ -3241,15 +3547,15 @@ function test0128_Machine_lineBasedAndBracketedParseSameGraph() {
|
|
|
3241
3547
|
|
|
3242
3548
|
function test0129_Machine_edgeEquivalenceSameUrns() {
|
|
3243
3549
|
const e1 = new MachineEdge(
|
|
3244
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3245
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3246
|
-
MediaUrn.fromString('media:txt
|
|
3550
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3551
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3552
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3247
3553
|
false
|
|
3248
3554
|
);
|
|
3249
3555
|
const e2 = new MachineEdge(
|
|
3250
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3251
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3252
|
-
MediaUrn.fromString('media:txt
|
|
3556
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3557
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3558
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3253
3559
|
false
|
|
3254
3560
|
);
|
|
3255
3561
|
assert(e1.isEquivalent(e2), 'Same URNs should be equivalent');
|
|
@@ -3258,15 +3564,15 @@ function test0129_Machine_edgeEquivalenceSameUrns() {
|
|
|
3258
3564
|
// TEST0130: Machine edge equivalence different cap urns
|
|
3259
3565
|
function test0130_Machine_edgeEquivalenceDifferentCapUrns() {
|
|
3260
3566
|
const e1 = new MachineEdge(
|
|
3261
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3262
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3263
|
-
MediaUrn.fromString('media:txt
|
|
3567
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3568
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3569
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3264
3570
|
false
|
|
3265
3571
|
);
|
|
3266
3572
|
const e2 = new MachineEdge(
|
|
3267
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3268
|
-
CapUrn.fromString('cap:in="media:pdf";summarize;out="media:txt
|
|
3269
|
-
MediaUrn.fromString('media:txt
|
|
3573
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3574
|
+
CapUrn.fromString('cap:in="media:ext=pdf";summarize;out="media:enc=utf-8;ext=txt"'),
|
|
3575
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3270
3576
|
false
|
|
3271
3577
|
);
|
|
3272
3578
|
assert(!e1.isEquivalent(e2), 'Different cap URNs should not be equivalent');
|
|
@@ -3275,15 +3581,15 @@ function test0130_Machine_edgeEquivalenceDifferentCapUrns() {
|
|
|
3275
3581
|
// TEST0131: Machine edge equivalence different targets
|
|
3276
3582
|
function test0131_Machine_edgeEquivalenceDifferentTargets() {
|
|
3277
3583
|
const e1 = new MachineEdge(
|
|
3278
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3279
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3280
|
-
MediaUrn.fromString('media:txt
|
|
3584
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3585
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3586
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3281
3587
|
false
|
|
3282
3588
|
);
|
|
3283
3589
|
const e2 = new MachineEdge(
|
|
3284
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3285
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3286
|
-
MediaUrn.fromString('media:json;record
|
|
3590
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3591
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3592
|
+
MediaUrn.fromString('media:fmt=json;record'),
|
|
3287
3593
|
false
|
|
3288
3594
|
);
|
|
3289
3595
|
assert(!e1.isEquivalent(e2), 'Different targets should not be equivalent');
|
|
@@ -3292,15 +3598,15 @@ function test0131_Machine_edgeEquivalenceDifferentTargets() {
|
|
|
3292
3598
|
// TEST0132: Machine edge equivalence different loop flag
|
|
3293
3599
|
function test0132_Machine_edgeEquivalenceDifferentLoopFlag() {
|
|
3294
3600
|
const e1 = new MachineEdge(
|
|
3295
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3296
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3297
|
-
MediaUrn.fromString('media:txt
|
|
3601
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3602
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3603
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3298
3604
|
false
|
|
3299
3605
|
);
|
|
3300
3606
|
const e2 = new MachineEdge(
|
|
3301
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3302
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3303
|
-
MediaUrn.fromString('media:txt
|
|
3607
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3608
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3609
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3304
3610
|
true
|
|
3305
3611
|
);
|
|
3306
3612
|
assert(!e1.isEquivalent(e2), 'Different loop flags should not be equivalent');
|
|
@@ -3309,15 +3615,15 @@ function test0132_Machine_edgeEquivalenceDifferentLoopFlag() {
|
|
|
3309
3615
|
// TEST0133: Machine edge equivalence source order independent
|
|
3310
3616
|
function test0133_Machine_edgeEquivalenceSourceOrderIndependent() {
|
|
3311
3617
|
const e1 = new MachineEdge(
|
|
3312
|
-
[MediaUrn.fromString('media:txt
|
|
3313
|
-
CapUrn.fromString('cap:in="media:txt
|
|
3314
|
-
MediaUrn.fromString('media:embedding-vector;record
|
|
3618
|
+
[MediaUrn.fromString('media:enc=utf-8;ext=txt'), MediaUrn.fromString('media:enc=utf-8;model-spec')],
|
|
3619
|
+
CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
|
|
3620
|
+
MediaUrn.fromString('media:embedding-vector;enc=utf-8;record'),
|
|
3315
3621
|
false
|
|
3316
3622
|
);
|
|
3317
3623
|
const e2 = new MachineEdge(
|
|
3318
|
-
[MediaUrn.fromString('media:model-spec
|
|
3319
|
-
CapUrn.fromString('cap:in="media:txt
|
|
3320
|
-
MediaUrn.fromString('media:embedding-vector;record
|
|
3624
|
+
[MediaUrn.fromString('media:enc=utf-8;model-spec'), MediaUrn.fromString('media:enc=utf-8;ext=txt')],
|
|
3625
|
+
CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
|
|
3626
|
+
MediaUrn.fromString('media:embedding-vector;enc=utf-8;record'),
|
|
3321
3627
|
false
|
|
3322
3628
|
);
|
|
3323
3629
|
assert(e1.isEquivalent(e2), 'Source order should not matter for equivalence');
|
|
@@ -3326,15 +3632,15 @@ function test0133_Machine_edgeEquivalenceSourceOrderIndependent() {
|
|
|
3326
3632
|
// TEST0134: Machine edge equivalence different source count
|
|
3327
3633
|
function test0134_Machine_edgeEquivalenceDifferentSourceCount() {
|
|
3328
3634
|
const e1 = new MachineEdge(
|
|
3329
|
-
[MediaUrn.fromString('media:txt
|
|
3330
|
-
CapUrn.fromString('cap:in="media:txt
|
|
3331
|
-
MediaUrn.fromString('media:embedding-vector;record
|
|
3635
|
+
[MediaUrn.fromString('media:enc=utf-8;ext=txt')],
|
|
3636
|
+
CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
|
|
3637
|
+
MediaUrn.fromString('media:embedding-vector;enc=utf-8;record'),
|
|
3332
3638
|
false
|
|
3333
3639
|
);
|
|
3334
3640
|
const e2 = new MachineEdge(
|
|
3335
|
-
[MediaUrn.fromString('media:txt
|
|
3336
|
-
CapUrn.fromString('cap:in="media:txt
|
|
3337
|
-
MediaUrn.fromString('media:embedding-vector;record
|
|
3641
|
+
[MediaUrn.fromString('media:enc=utf-8;ext=txt'), MediaUrn.fromString('media:enc=utf-8;model-spec')],
|
|
3642
|
+
CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
|
|
3643
|
+
MediaUrn.fromString('media:embedding-vector;enc=utf-8;record'),
|
|
3338
3644
|
false
|
|
3339
3645
|
);
|
|
3340
3646
|
assert(!e1.isEquivalent(e2), 'Different source counts should not be equivalent');
|
|
@@ -3346,12 +3652,12 @@ function test0135_Machine_graphEquivalenceSameEdges() {
|
|
|
3346
3652
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3347
3653
|
);
|
|
3348
3654
|
const g1 = new Machine([
|
|
3349
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3350
|
-
mkEdge('media:txt
|
|
3655
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3656
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3351
3657
|
]);
|
|
3352
3658
|
const g2 = new Machine([
|
|
3353
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3354
|
-
mkEdge('media:txt
|
|
3659
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3660
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3355
3661
|
]);
|
|
3356
3662
|
assert(g1.isEquivalent(g2), 'Same edges should be equivalent');
|
|
3357
3663
|
}
|
|
@@ -3362,12 +3668,12 @@ function test0136_Machine_graphEquivalenceReorderedEdges() {
|
|
|
3362
3668
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3363
3669
|
);
|
|
3364
3670
|
const g1 = new Machine([
|
|
3365
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3366
|
-
mkEdge('media:txt
|
|
3671
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3672
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3367
3673
|
]);
|
|
3368
3674
|
const g2 = new Machine([
|
|
3369
|
-
mkEdge('media:txt
|
|
3370
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3675
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3676
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3371
3677
|
]);
|
|
3372
3678
|
assert(g1.isEquivalent(g2), 'Reordered edges should still be equivalent');
|
|
3373
3679
|
}
|
|
@@ -3378,11 +3684,11 @@ function test0137_Machine_graphNotEquivalentDifferentEdgeCount() {
|
|
|
3378
3684
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3379
3685
|
);
|
|
3380
3686
|
const g1 = new Machine([
|
|
3381
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3687
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3382
3688
|
]);
|
|
3383
3689
|
const g2 = new Machine([
|
|
3384
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3385
|
-
mkEdge('media:txt
|
|
3690
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3691
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3386
3692
|
]);
|
|
3387
3693
|
assert(!g1.isEquivalent(g2), 'Different edge counts should not be equivalent');
|
|
3388
3694
|
}
|
|
@@ -3393,10 +3699,10 @@ function test0138_Machine_graphNotEquivalentDifferentCap() {
|
|
|
3393
3699
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3394
3700
|
);
|
|
3395
3701
|
const g1 = new Machine([
|
|
3396
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3702
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3397
3703
|
]);
|
|
3398
3704
|
const g2 = new Machine([
|
|
3399
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";summarize;out="media:txt
|
|
3705
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";summarize;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3400
3706
|
]);
|
|
3401
3707
|
assert(!g1.isEquivalent(g2), 'Different caps should not be equivalent');
|
|
3402
3708
|
}
|
|
@@ -3421,13 +3727,13 @@ function test0141_Machine_rootSourcesLinearChain() {
|
|
|
3421
3727
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3422
3728
|
);
|
|
3423
3729
|
const g = new Machine([
|
|
3424
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3425
|
-
mkEdge('media:txt
|
|
3730
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3731
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3426
3732
|
]);
|
|
3427
3733
|
const roots = g.rootSources();
|
|
3428
3734
|
assertEqual(roots.length, 1);
|
|
3429
|
-
assert(roots[0].isEquivalent(MediaUrn.fromString('media:pdf')),
|
|
3430
|
-
'Root source should be media:pdf');
|
|
3735
|
+
assert(roots[0].isEquivalent(MediaUrn.fromString('media:ext=pdf')),
|
|
3736
|
+
'Root source should be media:ext=pdf');
|
|
3431
3737
|
}
|
|
3432
3738
|
|
|
3433
3739
|
// TEST0142: Machine leaf targets linear chain
|
|
@@ -3436,21 +3742,21 @@ function test0142_Machine_leafTargetsLinearChain() {
|
|
|
3436
3742
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3437
3743
|
);
|
|
3438
3744
|
const g = new Machine([
|
|
3439
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3440
|
-
mkEdge('media:txt
|
|
3745
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3746
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3441
3747
|
]);
|
|
3442
3748
|
const leaves = g.leafTargets();
|
|
3443
3749
|
assertEqual(leaves.length, 1);
|
|
3444
|
-
assert(leaves[0].isEquivalent(MediaUrn.fromString('media:embedding-vector;record
|
|
3445
|
-
'Leaf target should be media:embedding-vector;record
|
|
3750
|
+
assert(leaves[0].isEquivalent(MediaUrn.fromString('media:embedding-vector;enc=utf-8;record')),
|
|
3751
|
+
'Leaf target should be media:embedding-vector;enc=utf-8;record');
|
|
3446
3752
|
}
|
|
3447
3753
|
|
|
3448
3754
|
// TEST0143: Machine root sources fan in
|
|
3449
3755
|
function test0143_Machine_rootSourcesFanIn() {
|
|
3450
3756
|
const e = new MachineEdge(
|
|
3451
|
-
[MediaUrn.fromString('media:txt
|
|
3452
|
-
CapUrn.fromString('cap:in="media:txt
|
|
3453
|
-
MediaUrn.fromString('media:embedding-vector;record
|
|
3757
|
+
[MediaUrn.fromString('media:enc=utf-8;ext=txt'), MediaUrn.fromString('media:enc=utf-8;model-spec')],
|
|
3758
|
+
CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
|
|
3759
|
+
MediaUrn.fromString('media:embedding-vector;enc=utf-8;record'),
|
|
3454
3760
|
false
|
|
3455
3761
|
);
|
|
3456
3762
|
const g = new Machine([e]);
|
|
@@ -3461,21 +3767,21 @@ function test0143_Machine_rootSourcesFanIn() {
|
|
|
3461
3767
|
// TEST0144: Machine display edge
|
|
3462
3768
|
function test0144_Machine_displayEdge() {
|
|
3463
3769
|
const e = new MachineEdge(
|
|
3464
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3465
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3466
|
-
MediaUrn.fromString('media:txt
|
|
3770
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3771
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3772
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3467
3773
|
false
|
|
3468
3774
|
);
|
|
3469
3775
|
const display = e.toString();
|
|
3470
|
-
assert(display.includes('media:pdf'), 'Display should contain media:pdf');
|
|
3776
|
+
assert(display.includes('media:ext=pdf'), 'Display should contain media:ext=pdf');
|
|
3471
3777
|
}
|
|
3472
3778
|
|
|
3473
3779
|
// TEST0145: Machine display graph
|
|
3474
3780
|
function test0145_Machine_displayGraph() {
|
|
3475
3781
|
const e = new MachineEdge(
|
|
3476
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3477
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3478
|
-
MediaUrn.fromString('media:txt
|
|
3782
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3783
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3784
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3479
3785
|
false
|
|
3480
3786
|
);
|
|
3481
3787
|
assertEqual(new Machine([e]).toString(), 'Machine(1 edges)');
|
|
@@ -3486,9 +3792,9 @@ function test0145_Machine_displayGraph() {
|
|
|
3486
3792
|
|
|
3487
3793
|
function test0146_Machine_serializeSingleEdge() {
|
|
3488
3794
|
const g = new Machine([new MachineEdge(
|
|
3489
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3490
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3491
|
-
MediaUrn.fromString('media:txt
|
|
3795
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3796
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3797
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3492
3798
|
false
|
|
3493
3799
|
)]);
|
|
3494
3800
|
const notation = g.toMachineNotation();
|
|
@@ -3505,8 +3811,8 @@ function test0147_Machine_serializeTwoEdgeChain() {
|
|
|
3505
3811
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3506
3812
|
);
|
|
3507
3813
|
const g = new Machine([
|
|
3508
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3509
|
-
mkEdge('media:txt
|
|
3814
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3815
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3510
3816
|
]);
|
|
3511
3817
|
const notation = g.toMachineNotation();
|
|
3512
3818
|
const bracketCount = (notation.match(/\[/g) || []).length;
|
|
@@ -3521,9 +3827,9 @@ function test0148_Machine_serializeEmptyGraph() {
|
|
|
3521
3827
|
// TEST0149: Machine roundtrip single edge
|
|
3522
3828
|
function test0149_Machine_roundtripSingleEdge() {
|
|
3523
3829
|
const original = new Machine([new MachineEdge(
|
|
3524
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3525
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3526
|
-
MediaUrn.fromString('media:txt
|
|
3830
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3831
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3832
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3527
3833
|
false
|
|
3528
3834
|
)]);
|
|
3529
3835
|
const notation = original.toMachineNotation();
|
|
@@ -3538,8 +3844,8 @@ function test0151_Machine_roundtripTwoEdgeChain() {
|
|
|
3538
3844
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3539
3845
|
);
|
|
3540
3846
|
const original = new Machine([
|
|
3541
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3542
|
-
mkEdge('media:txt
|
|
3847
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3848
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3543
3849
|
]);
|
|
3544
3850
|
const notation = original.toMachineNotation();
|
|
3545
3851
|
const reparsed = Machine.fromString(notation);
|
|
@@ -3553,9 +3859,9 @@ function test0152_Machine_roundtripFanOut() {
|
|
|
3553
3859
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3554
3860
|
);
|
|
3555
3861
|
const original = new Machine([
|
|
3556
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract-metadata;out="media:file-metadata;record
|
|
3557
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract-outline;out="media:document-outline;record
|
|
3558
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"', 'media:image;png;thumbnail'),
|
|
3862
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract-metadata;out="media:enc=utf-8;file-metadata;record"', 'media:enc=utf-8;file-metadata;record'),
|
|
3863
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract-outline;out="media:document-outline;enc=utf-8;record"', 'media:document-outline;enc=utf-8;record'),
|
|
3864
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";generate-thumbnail;out="media:image;png;thumbnail"', 'media:image;png;thumbnail'),
|
|
3559
3865
|
]);
|
|
3560
3866
|
const notation = original.toMachineNotation();
|
|
3561
3867
|
const reparsed = Machine.fromString(notation);
|
|
@@ -3566,9 +3872,9 @@ function test0152_Machine_roundtripFanOut() {
|
|
|
3566
3872
|
// TEST0153: Machine roundtrip loop edge
|
|
3567
3873
|
function test0153_Machine_roundtripLoopEdge() {
|
|
3568
3874
|
const original = new Machine([new MachineEdge(
|
|
3569
|
-
[MediaUrn.fromString('media:disbound-page;
|
|
3570
|
-
CapUrn.fromString('cap:in="media:disbound-page;
|
|
3571
|
-
MediaUrn.fromString('media:txt
|
|
3875
|
+
[MediaUrn.fromString('media:disbound-page;enc=utf-8')],
|
|
3876
|
+
CapUrn.fromString('cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"'),
|
|
3877
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3572
3878
|
true
|
|
3573
3879
|
)]);
|
|
3574
3880
|
const notation = original.toMachineNotation();
|
|
@@ -3583,8 +3889,8 @@ function test0154_Machine_serializationIsDeterministic() {
|
|
|
3583
3889
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3584
3890
|
);
|
|
3585
3891
|
const g = new Machine([
|
|
3586
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3587
|
-
mkEdge('media:txt
|
|
3892
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3893
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3588
3894
|
]);
|
|
3589
3895
|
const n1 = g.toMachineNotation();
|
|
3590
3896
|
const n2 = g.toMachineNotation();
|
|
@@ -3597,12 +3903,12 @@ function test0155_Machine_reorderedEdgesProduceSameNotation() {
|
|
|
3597
3903
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3598
3904
|
);
|
|
3599
3905
|
const g1 = new Machine([
|
|
3600
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3601
|
-
mkEdge('media:txt
|
|
3906
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3907
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3602
3908
|
]);
|
|
3603
3909
|
const g2 = new Machine([
|
|
3604
|
-
mkEdge('media:txt
|
|
3605
|
-
mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt
|
|
3910
|
+
mkEdge('media:enc=utf-8;ext=txt', 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record'),
|
|
3911
|
+
mkEdge('media:ext=pdf', 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt'),
|
|
3606
3912
|
]);
|
|
3607
3913
|
assertEqual(g1.toMachineNotation(), g2.toMachineNotation(),
|
|
3608
3914
|
'Same graph with reordered edges must produce identical notation');
|
|
@@ -3611,9 +3917,9 @@ function test0155_Machine_reorderedEdgesProduceSameNotation() {
|
|
|
3611
3917
|
// TEST0160: Machine multiline serialize format
|
|
3612
3918
|
function test0160_Machine_multilineSerializeFormat() {
|
|
3613
3919
|
const g = new Machine([new MachineEdge(
|
|
3614
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3615
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3616
|
-
MediaUrn.fromString('media:txt
|
|
3920
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3921
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3922
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3617
3923
|
false
|
|
3618
3924
|
)]);
|
|
3619
3925
|
const multi = g.toMachineNotationMultiline();
|
|
@@ -3627,9 +3933,9 @@ function test0160_Machine_multilineSerializeFormat() {
|
|
|
3627
3933
|
// no privileged `op` tag to derive a friendlier name from.
|
|
3628
3934
|
function test0161_Machine_aliasFromOpTag() {
|
|
3629
3935
|
const g = new Machine([new MachineEdge(
|
|
3630
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3631
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3632
|
-
MediaUrn.fromString('media:txt
|
|
3936
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3937
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3938
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3633
3939
|
false
|
|
3634
3940
|
)]);
|
|
3635
3941
|
const notation = g.toMachineNotation();
|
|
@@ -3639,9 +3945,9 @@ function test0161_Machine_aliasFromOpTag() {
|
|
|
3639
3945
|
// TEST0162: Machine alias fallback without op tag
|
|
3640
3946
|
function test0162_Machine_aliasFallbackWithoutOpTag() {
|
|
3641
3947
|
const g = new Machine([new MachineEdge(
|
|
3642
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3643
|
-
CapUrn.fromString('cap:in="media:pdf";out="media:txt
|
|
3644
|
-
MediaUrn.fromString('media:txt
|
|
3948
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3949
|
+
CapUrn.fromString('cap:in="media:ext=pdf";out="media:enc=utf-8;ext=txt"'),
|
|
3950
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3645
3951
|
false
|
|
3646
3952
|
)]);
|
|
3647
3953
|
const notation = g.toMachineNotation();
|
|
@@ -3652,15 +3958,15 @@ function test0162_Machine_aliasFallbackWithoutOpTag() {
|
|
|
3652
3958
|
function test0163_Machine_duplicateOpTagsDisambiguated() {
|
|
3653
3959
|
const g = new Machine([
|
|
3654
3960
|
new MachineEdge(
|
|
3655
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3656
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3657
|
-
MediaUrn.fromString('media:txt
|
|
3961
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3962
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
3963
|
+
MediaUrn.fromString('media:enc=utf-8;ext=txt'),
|
|
3658
3964
|
false
|
|
3659
3965
|
),
|
|
3660
3966
|
new MachineEdge(
|
|
3661
|
-
[MediaUrn.fromString('media:pdf')],
|
|
3662
|
-
CapUrn.fromString('cap:in="media:pdf";extract;out="media:json;record
|
|
3663
|
-
MediaUrn.fromString('media:json;record
|
|
3967
|
+
[MediaUrn.fromString('media:ext=pdf')],
|
|
3968
|
+
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:fmt=json;record"'),
|
|
3969
|
+
MediaUrn.fromString('media:fmt=json;record'),
|
|
3664
3970
|
false
|
|
3665
3971
|
),
|
|
3666
3972
|
]);
|
|
@@ -3674,9 +3980,9 @@ function test0163_Machine_duplicateOpTagsDisambiguated() {
|
|
|
3674
3980
|
function test0164_Machine_builderSingleEdge() {
|
|
3675
3981
|
const builder = new MachineBuilder();
|
|
3676
3982
|
builder.addEdge(
|
|
3677
|
-
['media:pdf'],
|
|
3678
|
-
'cap:in="media:pdf";extract;out="media:txt
|
|
3679
|
-
'media:txt
|
|
3983
|
+
['media:ext=pdf'],
|
|
3984
|
+
'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"',
|
|
3985
|
+
'media:enc=utf-8;ext=txt'
|
|
3680
3986
|
);
|
|
3681
3987
|
const g = builder.build();
|
|
3682
3988
|
assertEqual(g.edgeCount(), 1);
|
|
@@ -3687,9 +3993,9 @@ function test0164_Machine_builderSingleEdge() {
|
|
|
3687
3993
|
function test0165_Machine_builderWithLoop() {
|
|
3688
3994
|
const builder = new MachineBuilder();
|
|
3689
3995
|
builder.addEdge(
|
|
3690
|
-
['media:disbound-page;
|
|
3691
|
-
'cap:in="media:disbound-page;
|
|
3692
|
-
'media:txt
|
|
3996
|
+
['media:disbound-page;enc=utf-8'],
|
|
3997
|
+
'cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"',
|
|
3998
|
+
'media:enc=utf-8;ext=txt',
|
|
3693
3999
|
true
|
|
3694
4000
|
);
|
|
3695
4001
|
const g = builder.build();
|
|
@@ -3699,8 +4005,8 @@ function test0165_Machine_builderWithLoop() {
|
|
|
3699
4005
|
// TEST0166: Machine builder chaining
|
|
3700
4006
|
function test0166_Machine_builderChaining() {
|
|
3701
4007
|
const g = new MachineBuilder()
|
|
3702
|
-
.addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt
|
|
3703
|
-
.addEdge(['media:txt
|
|
4008
|
+
.addEdge(['media:ext=pdf'], 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt')
|
|
4009
|
+
.addEdge(['media:enc=utf-8;ext=txt'], 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record')
|
|
3704
4010
|
.build();
|
|
3705
4011
|
assertEqual(g.edgeCount(), 2);
|
|
3706
4012
|
}
|
|
@@ -3708,11 +4014,11 @@ function test0166_Machine_builderChaining() {
|
|
|
3708
4014
|
// TEST0167: Machine builder equivalent to parsed
|
|
3709
4015
|
function test0167_Machine_builderEquivalentToParsed() {
|
|
3710
4016
|
const parsed = Machine.fromString(
|
|
3711
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
4017
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3712
4018
|
'[doc -> extract -> text]'
|
|
3713
4019
|
);
|
|
3714
4020
|
const built = new MachineBuilder()
|
|
3715
|
-
.addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt
|
|
4021
|
+
.addEdge(['media:ext=pdf'], 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt')
|
|
3716
4022
|
.build();
|
|
3717
4023
|
assert(parsed.isEquivalent(built),
|
|
3718
4024
|
'Builder-constructed graph should be equivalent to parsed graph');
|
|
@@ -3721,8 +4027,8 @@ function test0167_Machine_builderEquivalentToParsed() {
|
|
|
3721
4027
|
// TEST0168: Machine builder round trip
|
|
3722
4028
|
function test0168_Machine_builderRoundTrip() {
|
|
3723
4029
|
const built = new MachineBuilder()
|
|
3724
|
-
.addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt
|
|
3725
|
-
.addEdge(['media:txt
|
|
4030
|
+
.addEdge(['media:ext=pdf'], 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt')
|
|
4031
|
+
.addEdge(['media:enc=utf-8;ext=txt'], 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record')
|
|
3726
4032
|
.build();
|
|
3727
4033
|
const notation = built.toMachineNotation();
|
|
3728
4034
|
const reparsed = Machine.fromString(notation);
|
|
@@ -3732,54 +4038,54 @@ function test0168_Machine_builderRoundTrip() {
|
|
|
3732
4038
|
// --- CapUrn.isEquivalent/isComparable tests ---
|
|
3733
4039
|
|
|
3734
4040
|
function test0169_Machine_capUrnIsEquivalent() {
|
|
3735
|
-
const a = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
3736
|
-
const b = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
4041
|
+
const a = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
4042
|
+
const b = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
3737
4043
|
assert(a.isEquivalent(b), 'Same cap URNs should be equivalent');
|
|
3738
|
-
const c = CapUrn.fromString('cap:in="media:pdf";summarize;out="media:txt
|
|
4044
|
+
const c = CapUrn.fromString('cap:in="media:ext=pdf";summarize;out="media:enc=utf-8;ext=txt"');
|
|
3739
4045
|
assert(!a.isEquivalent(c), 'Different cap URNs should not be equivalent');
|
|
3740
4046
|
}
|
|
3741
4047
|
|
|
3742
4048
|
// TEST0170: Machine cap urn is comparable
|
|
3743
4049
|
function test0170_Machine_capUrnIsComparable() {
|
|
3744
|
-
const general = CapUrn.fromString('cap:in="media:pdf";out="media:txt
|
|
3745
|
-
const specific = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
4050
|
+
const general = CapUrn.fromString('cap:in="media:ext=pdf";out="media:enc=utf-8;ext=txt"');
|
|
4051
|
+
const specific = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
3746
4052
|
assert(general.isComparable(specific), 'General should be comparable to specific');
|
|
3747
4053
|
assert(specific.isComparable(general), 'isComparable should be symmetric');
|
|
3748
4054
|
}
|
|
3749
4055
|
|
|
3750
4056
|
// TEST0171: Machine cap urn in media urn
|
|
3751
4057
|
function test0171_Machine_capUrnInMediaUrn() {
|
|
3752
|
-
const cap = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
4058
|
+
const cap = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
3753
4059
|
const inUrn = cap.inMediaUrn();
|
|
3754
4060
|
assert(inUrn instanceof MediaUrn, 'inMediaUrn should return MediaUrn');
|
|
3755
|
-
assert(inUrn.isEquivalent(MediaUrn.fromString('media:pdf')), 'inMediaUrn should be media:pdf');
|
|
4061
|
+
assert(inUrn.isEquivalent(MediaUrn.fromString('media:ext=pdf')), 'inMediaUrn should be media:ext=pdf');
|
|
3756
4062
|
}
|
|
3757
4063
|
|
|
3758
4064
|
// TEST0172: Machine cap urn out media urn
|
|
3759
4065
|
function test0172_Machine_capUrnOutMediaUrn() {
|
|
3760
|
-
const cap = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt
|
|
4066
|
+
const cap = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
3761
4067
|
const outUrn = cap.outMediaUrn();
|
|
3762
4068
|
assert(outUrn instanceof MediaUrn, 'outMediaUrn should return MediaUrn');
|
|
3763
|
-
assert(outUrn.isEquivalent(MediaUrn.fromString('media:txt
|
|
4069
|
+
assert(outUrn.isEquivalent(MediaUrn.fromString('media:enc=utf-8;ext=txt')), 'outMediaUrn should be media:enc=utf-8;ext=txt');
|
|
3764
4070
|
}
|
|
3765
4071
|
|
|
3766
4072
|
// --- MediaUrn.isEquivalent/isComparable tests ---
|
|
3767
4073
|
|
|
3768
4074
|
function test0173_Machine_mediaUrnIsEquivalent() {
|
|
3769
|
-
const a = MediaUrn.fromString('media:pdf');
|
|
3770
|
-
const b = MediaUrn.fromString('media:pdf');
|
|
4075
|
+
const a = MediaUrn.fromString('media:ext=pdf');
|
|
4076
|
+
const b = MediaUrn.fromString('media:ext=pdf');
|
|
3771
4077
|
assert(a.isEquivalent(b), 'Same media URNs should be equivalent');
|
|
3772
|
-
const c = MediaUrn.fromString('media:txt
|
|
4078
|
+
const c = MediaUrn.fromString('media:enc=utf-8;ext=txt');
|
|
3773
4079
|
assert(!a.isEquivalent(c), 'Different media URNs should not be equivalent');
|
|
3774
4080
|
}
|
|
3775
4081
|
|
|
3776
4082
|
// TEST0174: Machine media urn is comparable
|
|
3777
4083
|
function test0174_Machine_mediaUrnIsComparable() {
|
|
3778
|
-
const general = MediaUrn.fromString('media:
|
|
3779
|
-
const specific = MediaUrn.fromString('media:txt
|
|
4084
|
+
const general = MediaUrn.fromString('media:enc=utf-8');
|
|
4085
|
+
const specific = MediaUrn.fromString('media:enc=utf-8;ext=txt');
|
|
3780
4086
|
assert(general.isComparable(specific), 'General should be comparable to specific');
|
|
3781
4087
|
assert(specific.isComparable(general), 'isComparable should be symmetric');
|
|
3782
|
-
const unrelated = MediaUrn.fromString('media:pdf');
|
|
4088
|
+
const unrelated = MediaUrn.fromString('media:ext=pdf');
|
|
3783
4089
|
assert(!general.isComparable(unrelated), 'Unrelated should not be comparable');
|
|
3784
4090
|
}
|
|
3785
4091
|
|
|
@@ -3788,7 +4094,7 @@ function test0174_Machine_mediaUrnIsComparable() {
|
|
|
3788
4094
|
// ============================================================================
|
|
3789
4095
|
|
|
3790
4096
|
function test0175_Machine_parseMachineWithAST_headerLocation() {
|
|
3791
|
-
const input = '[extract cap:in="media:pdf";extract;out="media:txt
|
|
4097
|
+
const input = '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"][doc -> extract -> text]';
|
|
3792
4098
|
const result = parseMachineWithAST(input);
|
|
3793
4099
|
assert(result.statements.length === 2, 'Should have 2 statements');
|
|
3794
4100
|
const stmt = result.statements[0];
|
|
@@ -3805,7 +4111,7 @@ function test0175_Machine_parseMachineWithAST_headerLocation() {
|
|
|
3805
4111
|
|
|
3806
4112
|
// TEST0176: Machine parse machine with a s t wiring location
|
|
3807
4113
|
function test0176_Machine_parseMachineWithAST_wiringLocation() {
|
|
3808
|
-
const input = '[extract cap:in="media:pdf";extract;out="media:txt
|
|
4114
|
+
const input = '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n[doc -> extract -> text]';
|
|
3809
4115
|
const result = parseMachineWithAST(input);
|
|
3810
4116
|
assert(result.statements.length === 2, 'Should have 2 statements');
|
|
3811
4117
|
const wiring = result.statements[1];
|
|
@@ -3820,7 +4126,7 @@ function test0176_Machine_parseMachineWithAST_wiringLocation() {
|
|
|
3820
4126
|
|
|
3821
4127
|
// TEST0177: Machine parse machine with a s t multiline positions
|
|
3822
4128
|
function test0177_Machine_parseMachineWithAST_multilinePositions() {
|
|
3823
|
-
const input = '[extract cap:in="media:pdf";extract;out="media:txt
|
|
4129
|
+
const input = '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n[doc -> extract -> text]';
|
|
3824
4130
|
const result = parseMachineWithAST(input);
|
|
3825
4131
|
const headerLoc = result.statements[0].location;
|
|
3826
4132
|
const wiringLoc = result.statements[1].location;
|
|
@@ -3831,7 +4137,7 @@ function test0177_Machine_parseMachineWithAST_multilinePositions() {
|
|
|
3831
4137
|
// TEST0178: Machine parse machine with a s t fan in source locations
|
|
3832
4138
|
function test0178_Machine_parseMachineWithAST_fanInSourceLocations() {
|
|
3833
4139
|
const input = [
|
|
3834
|
-
'[describe cap:in="media:image;png";describe-image;out="media:image-description
|
|
4140
|
+
'[describe cap:in="media:image;png";describe-image;out="media:enc=utf-8;image-description"]',
|
|
3835
4141
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
3836
4142
|
].join('\n');
|
|
3837
4143
|
const result = parseMachineWithAST(input);
|
|
@@ -3843,8 +4149,8 @@ function test0178_Machine_parseMachineWithAST_fanInSourceLocations() {
|
|
|
3843
4149
|
// TEST0179: Machine parse machine with a s t alias map
|
|
3844
4150
|
function test0179_Machine_parseMachineWithAST_aliasMap() {
|
|
3845
4151
|
const input = [
|
|
3846
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
3847
|
-
'[embed cap:in="media:txt
|
|
4152
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]',
|
|
4153
|
+
'[embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"]',
|
|
3848
4154
|
'[doc -> extract -> text]',
|
|
3849
4155
|
'[text -> embed -> vectors]',
|
|
3850
4156
|
].join('\n');
|
|
@@ -3862,14 +4168,14 @@ function test0179_Machine_parseMachineWithAST_aliasMap() {
|
|
|
3862
4168
|
// TEST0180: Machine parse machine with a s t node media
|
|
3863
4169
|
function test0180_Machine_parseMachineWithAST_nodeMedia() {
|
|
3864
4170
|
const input = [
|
|
3865
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
4171
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]',
|
|
3866
4172
|
'[doc -> extract -> text]',
|
|
3867
4173
|
].join('\n');
|
|
3868
4174
|
const result = parseMachineWithAST(input);
|
|
3869
4175
|
assert(result.nodeMedia.has('doc'), 'nodeMedia should have doc');
|
|
3870
4176
|
assert(result.nodeMedia.has('text'), 'nodeMedia should have text');
|
|
3871
|
-
assertEqual(result.nodeMedia.get('doc').toString(), 'media:pdf', 'doc should be media:pdf');
|
|
3872
|
-
assertEqual(result.nodeMedia.get('text').toString(), 'media:
|
|
4177
|
+
assertEqual(result.nodeMedia.get('doc').toString(), 'media:ext=pdf', 'doc should be media:ext=pdf');
|
|
4178
|
+
assertEqual(result.nodeMedia.get('text').toString(), 'media:enc=utf-8;ext=txt', 'text should be media:enc=utf-8;ext=txt');
|
|
3873
4179
|
}
|
|
3874
4180
|
|
|
3875
4181
|
// TEST0181: Machine error location parse error
|
|
@@ -3887,8 +4193,8 @@ function test0181_Machine_errorLocation_parseError() {
|
|
|
3887
4193
|
function test0182_Machine_errorLocation_duplicateAlias() {
|
|
3888
4194
|
try {
|
|
3889
4195
|
parseMachine(
|
|
3890
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
3891
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
4196
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
4197
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3892
4198
|
'[doc -> extract -> text]'
|
|
3893
4199
|
);
|
|
3894
4200
|
throw new Error('Expected MachineSyntaxError');
|
|
@@ -3915,7 +4221,7 @@ function test0183_Machine_errorLocation_undefinedAlias() {
|
|
|
3915
4221
|
|
|
3916
4222
|
function test0184_Machine_toMermaid_linearChain() {
|
|
3917
4223
|
const machine = Machine.fromString(
|
|
3918
|
-
'[extract cap:in="media:pdf";extract;out="media:txt
|
|
4224
|
+
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3919
4225
|
'[doc -> extract -> text]'
|
|
3920
4226
|
);
|
|
3921
4227
|
const mermaid = machine.toMermaid();
|
|
@@ -3924,8 +4230,8 @@ function test0184_Machine_toMermaid_linearChain() {
|
|
|
3924
4230
|
// serializer (the input alias name is not preserved in the rendered
|
|
3925
4231
|
// diagram — it's a serialization artefact, not part of the machine).
|
|
3926
4232
|
assert(mermaid.includes('edge_0'), 'Should include edge_0 label');
|
|
3927
|
-
assert(mermaid.includes('media:pdf'), 'Should include media:pdf node');
|
|
3928
|
-
assert(mermaid.includes('media:
|
|
4233
|
+
assert(mermaid.includes('media:ext=pdf'), 'Should include media:ext=pdf node');
|
|
4234
|
+
assert(mermaid.includes('media:enc=utf-8;ext=txt'), 'Should include media:enc=utf-8;ext=txt node');
|
|
3929
4235
|
assert(mermaid.includes('-->'), 'Should include arrow');
|
|
3930
4236
|
// Root source and leaf target should both be stadium shape
|
|
3931
4237
|
assert(mermaid.includes('(['), 'Should have stadium shape nodes');
|
|
@@ -3934,7 +4240,7 @@ function test0184_Machine_toMermaid_linearChain() {
|
|
|
3934
4240
|
// TEST0185: Machine to mermaid loop edge
|
|
3935
4241
|
function test0185_Machine_toMermaid_loopEdge() {
|
|
3936
4242
|
const machine = Machine.fromString(
|
|
3937
|
-
'[p2t cap:in="media:disbound-page;
|
|
4243
|
+
'[p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"]' +
|
|
3938
4244
|
'[pages -> LOOP p2t -> texts]'
|
|
3939
4245
|
);
|
|
3940
4246
|
const mermaid = machine.toMermaid();
|
|
@@ -3953,7 +4259,7 @@ function test0186_Machine_toMermaid_emptyGraph() {
|
|
|
3953
4259
|
// TEST0187: Machine to mermaid fan in
|
|
3954
4260
|
function test0187_Machine_toMermaid_fanIn() {
|
|
3955
4261
|
const machine = Machine.fromString(
|
|
3956
|
-
'[describe cap:in="media:image;png";describe-image;out="media:image-description
|
|
4262
|
+
'[describe cap:in="media:image;png";describe-image;out="media:enc=utf-8;image-description"]' +
|
|
3957
4263
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
3958
4264
|
);
|
|
3959
4265
|
const mermaid = machine.toMermaid();
|
|
@@ -3965,8 +4271,8 @@ function test0187_Machine_toMermaid_fanIn() {
|
|
|
3965
4271
|
// TEST0188: Machine to mermaid fan out
|
|
3966
4272
|
function test0188_Machine_toMermaid_fanOut() {
|
|
3967
4273
|
const input = [
|
|
3968
|
-
'[meta cap:in="media:pdf";extract-metadata;out="media:file-metadata;record
|
|
3969
|
-
'[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]',
|
|
4274
|
+
'[meta cap:in="media:ext=pdf";extract-metadata;out="media:enc=utf-8;file-metadata;record"]',
|
|
4275
|
+
'[thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:image;png;thumbnail"]',
|
|
3970
4276
|
'[doc -> meta -> metadata]',
|
|
3971
4277
|
'[doc -> thumb -> thumbnail]'
|
|
3972
4278
|
].join('');
|
|
@@ -3975,8 +4281,8 @@ function test0188_Machine_toMermaid_fanOut() {
|
|
|
3975
4281
|
// Should have 2 edges
|
|
3976
4282
|
const arrowCount = (mermaid.match(/-->/g) || []).length;
|
|
3977
4283
|
assertEqual(arrowCount, 2, 'Fan-out should produce 2 arrows');
|
|
3978
|
-
// The root source (media:pdf) should appear once as a node definition
|
|
3979
|
-
assert(mermaid.includes('media:pdf'), 'Should include media:pdf');
|
|
4284
|
+
// The root source (media:ext=pdf) should appear once as a node definition
|
|
4285
|
+
assert(mermaid.includes('media:ext=pdf'), 'Should include media:ext=pdf');
|
|
3980
4286
|
}
|
|
3981
4287
|
|
|
3982
4288
|
// ============================================================================
|
|
@@ -3985,36 +4291,36 @@ function test0188_Machine_toMermaid_fanOut() {
|
|
|
3985
4291
|
|
|
3986
4292
|
function test0189_Machine_capRegistryEntry_construction() {
|
|
3987
4293
|
const entry = new FabricRegistryEntry({
|
|
3988
|
-
urn: 'cap:in="media:pdf";extract;out="media:txt
|
|
4294
|
+
urn: 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"',
|
|
3989
4295
|
title: 'PDF Extractor',
|
|
3990
4296
|
command: 'extract',
|
|
3991
4297
|
cap_description: 'Extracts text from PDF',
|
|
3992
|
-
args: [{ media_urn: 'media:pdf', required: true }],
|
|
3993
|
-
output: { media_urn: 'media:txt
|
|
4298
|
+
args: [{ media_urn: 'media:ext=pdf', required: true }],
|
|
4299
|
+
output: { media_urn: 'media:enc=utf-8;ext=txt', output_description: 'Extracted text' },
|
|
3994
4300
|
media_defs: [],
|
|
3995
4301
|
urn_tags: { op: 'extract' },
|
|
3996
|
-
in_spec: 'media:pdf',
|
|
3997
|
-
out_spec: 'media:txt
|
|
4302
|
+
in_spec: 'media:ext=pdf',
|
|
4303
|
+
out_spec: 'media:enc=utf-8;ext=txt',
|
|
3998
4304
|
in_media_title: 'PDF Document',
|
|
3999
4305
|
out_media_title: 'Text'
|
|
4000
4306
|
});
|
|
4001
|
-
assertEqual(entry.urn, 'cap:in="media:pdf";extract;out="media:txt
|
|
4307
|
+
assertEqual(entry.urn, 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'URN should match');
|
|
4002
4308
|
assertEqual(entry.title, 'PDF Extractor', 'Title should match');
|
|
4003
4309
|
assertEqual(entry.description, 'Extracts text from PDF', 'Description should match');
|
|
4004
|
-
assertEqual(entry.inSpec, 'media:pdf', 'inSpec should match');
|
|
4005
|
-
assertEqual(entry.outSpec, 'media:txt
|
|
4310
|
+
assertEqual(entry.inSpec, 'media:ext=pdf', 'inSpec should match');
|
|
4311
|
+
assertEqual(entry.outSpec, 'media:enc=utf-8;ext=txt', 'outSpec should match');
|
|
4006
4312
|
assertEqual(entry.urnTags.op, 'extract', 'op tag should match');
|
|
4007
4313
|
}
|
|
4008
4314
|
|
|
4009
4315
|
// TEST0190: Machine media registry entry construction
|
|
4010
4316
|
function test0190_Machine_mediaRegistryEntry_construction() {
|
|
4011
4317
|
const entry = new MediaRegistryEntry({
|
|
4012
|
-
urn: 'media:pdf',
|
|
4318
|
+
urn: 'media:ext=pdf',
|
|
4013
4319
|
title: 'PDF Document',
|
|
4014
4320
|
media_type: 'application/pdf',
|
|
4015
4321
|
description: 'Portable Document Format'
|
|
4016
4322
|
});
|
|
4017
|
-
assertEqual(entry.urn, 'media:pdf', 'URN should match');
|
|
4323
|
+
assertEqual(entry.urn, 'media:ext=pdf', 'URN should match');
|
|
4018
4324
|
assertEqual(entry.title, 'PDF Document', 'Title should match');
|
|
4019
4325
|
assertEqual(entry.mediaType, 'application/pdf', 'Media type should match');
|
|
4020
4326
|
assertEqual(entry.description, 'Portable Document Format', 'Description should match');
|
|
@@ -4117,7 +4423,7 @@ function test0195_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
|
|
|
4117
4423
|
// A naive implementation that reads args[0] would see `cli-only` (not a
|
|
4118
4424
|
// sequence) and report 1→1 even though the stdin arg is a sequence.
|
|
4119
4425
|
const cap = {
|
|
4120
|
-
urn: 'cap:in="media:
|
|
4426
|
+
urn: 'cap:in="media:enc=utf-8;list";transcribe;out="media:enc=utf-8"',
|
|
4121
4427
|
args: [
|
|
4122
4428
|
{
|
|
4123
4429
|
display_name: 'cli-only',
|
|
@@ -4149,7 +4455,7 @@ function test0196_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing()
|
|
|
4149
4455
|
function test0197_Renderer_cardinalityFromCap_outputOnlySequence() {
|
|
4150
4456
|
// One scalar stdin arg, output is a sequence: expects 1→n.
|
|
4151
4457
|
const cap = {
|
|
4152
|
-
urn: 'cap:in="media:
|
|
4458
|
+
urn: 'cap:in="media:enc=utf-8";generate;out="media:enc=utf-8;list"',
|
|
4153
4459
|
args: [{ sources: [{ stdin: {} }], is_sequence: false }],
|
|
4154
4460
|
output: { is_sequence: true },
|
|
4155
4461
|
};
|
|
@@ -4242,10 +4548,10 @@ function test0204_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles() {
|
|
|
4242
4548
|
try {
|
|
4243
4549
|
rendererBuildBrowseGraphData([
|
|
4244
4550
|
{
|
|
4245
|
-
urn: 'cap:in="media:pdf";extract;out="media:txt
|
|
4551
|
+
urn: 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"',
|
|
4246
4552
|
title: 'Extract Text',
|
|
4247
|
-
in_spec: 'media:pdf',
|
|
4248
|
-
out_spec: 'media:txt
|
|
4553
|
+
in_spec: 'media:ext=pdf',
|
|
4554
|
+
out_spec: 'media:enc=utf-8;ext=txt',
|
|
4249
4555
|
in_media_title: 'PDF',
|
|
4250
4556
|
out_media_title: '',
|
|
4251
4557
|
},
|
|
@@ -4341,10 +4647,10 @@ function test0207_Renderer_classifyStrandCapSteps_capFlags() {
|
|
|
4341
4647
|
// have neither.
|
|
4342
4648
|
const steps = [
|
|
4343
4649
|
makeForEachStep('media:pdf;list'),
|
|
4344
|
-
makeCapStep('cap:in="media:pdf";a;out="media:image;png"', 'a', 'media:pdf', 'media:image;png', false, false),
|
|
4650
|
+
makeCapStep('cap:in="media:ext=pdf";a;out="media:image;png"', 'a', 'media:ext=pdf', 'media:image;png', false, false),
|
|
4345
4651
|
makeCapStep('cap:in="media:image;png";b;out="media:jpg"', 'b', 'media:image;png', 'media:jpg', false, false),
|
|
4346
|
-
makeCapStep('cap:in="media:jpg";c;out="media:txt"', 'c', 'media:jpg', 'media:txt', false, false),
|
|
4347
|
-
makeCollectStep('media:txt'),
|
|
4652
|
+
makeCapStep('cap:in="media:jpg";c;out="media:ext=txt"', 'c', 'media:jpg', 'media:ext=txt', false, false),
|
|
4653
|
+
makeCollectStep('media:ext=txt'),
|
|
4348
4654
|
];
|
|
4349
4655
|
const { capStepIndices, capFlags } = rendererClassifyStrandCapSteps(steps);
|
|
4350
4656
|
assertEqual(capStepIndices.length, 3, 'three cap steps');
|
|
@@ -4455,12 +4761,12 @@ function test0211_Renderer_buildStrandGraphData_foreachCollectSpan() {
|
|
|
4455
4761
|
target_media_urn: 'media:txt;list',
|
|
4456
4762
|
steps: [
|
|
4457
4763
|
makeForEachStep('media:pdf;list'),
|
|
4458
|
-
makeCapStep('cap:in="media:pdf";extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
|
|
4459
|
-
makeCollectStep('media:txt'),
|
|
4764
|
+
makeCapStep('cap:in="media:ext=pdf";extract;out="media:ext=txt"', 'extract', 'media:ext=pdf', 'media:ext=txt', false, false),
|
|
4765
|
+
makeCollectStep('media:ext=txt'),
|
|
4460
4766
|
],
|
|
4461
4767
|
}, {
|
|
4462
4768
|
'media:pdf;list': 'PDF List',
|
|
4463
|
-
'media:txt': 'Plain Text',
|
|
4769
|
+
'media:ext=txt': 'Plain Text',
|
|
4464
4770
|
'media:txt;list': 'Text List',
|
|
4465
4771
|
});
|
|
4466
4772
|
const built = rendererBuildStrandGraphData(payload);
|
|
@@ -4603,12 +4909,12 @@ function test0215_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
|
|
|
4603
4909
|
target_media_urn: 'media:txt;list',
|
|
4604
4910
|
steps: [
|
|
4605
4911
|
makeForEachStep('media:pdf;list'),
|
|
4606
|
-
makeCapStep('cap:in="media:pdf";extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
|
|
4607
|
-
makeCollectStep('media:txt'),
|
|
4912
|
+
makeCapStep('cap:in="media:ext=pdf";extract;out="media:ext=txt"', 'extract', 'media:ext=pdf', 'media:ext=txt', false, false),
|
|
4913
|
+
makeCollectStep('media:ext=txt'),
|
|
4608
4914
|
],
|
|
4609
4915
|
}, {
|
|
4610
4916
|
'media:pdf;list': 'PDF List',
|
|
4611
|
-
'media:txt': 'Plain Text',
|
|
4917
|
+
'media:ext=txt': 'Plain Text',
|
|
4612
4918
|
'media:txt;list': 'Text List',
|
|
4613
4919
|
});
|
|
4614
4920
|
const built = rendererBuildStrandGraphData(payload);
|
|
@@ -4741,7 +5047,7 @@ function test0217_Renderer_collapseStrand_standaloneCollectCollapses() {
|
|
|
4741
5047
|
function test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach() {
|
|
4742
5048
|
// Regression test mirroring the user's real strand:
|
|
4743
5049
|
// [Cap_disbind (output_is_sequence=true), ForEach, Cap_make_decision],
|
|
4744
|
-
// source = media:pdf, target = media:decision (equivalent to
|
|
5050
|
+
// source = media:ext=pdf, target = media:decision (equivalent to
|
|
4745
5051
|
// the last cap's to_spec).
|
|
4746
5052
|
//
|
|
4747
5053
|
// Expected render shape after collapse:
|
|
@@ -4756,15 +5062,15 @@ function test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach() {
|
|
|
4756
5062
|
// No separate output node because step_2's to_spec equals the
|
|
4757
5063
|
// strand target.
|
|
4758
5064
|
const payload = withMediaDisplayNames({
|
|
4759
|
-
source_media_urn: 'media:pdf',
|
|
5065
|
+
source_media_urn: 'media:ext=pdf',
|
|
4760
5066
|
target_media_urn: 'media:decision',
|
|
4761
5067
|
steps: [
|
|
4762
|
-
makeCapStep('cap:in="media:pdf";disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
|
|
5068
|
+
makeCapStep('cap:in="media:ext=pdf";disbind;out="media:page"', 'Disbind', 'media:ext=pdf', 'media:page', false, true),
|
|
4763
5069
|
makeForEachStep('media:page'),
|
|
4764
5070
|
makeCapStep('cap:in="media:page";decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
|
|
4765
5071
|
],
|
|
4766
5072
|
}, {
|
|
4767
|
-
'media:pdf': 'PDF',
|
|
5073
|
+
'media:ext=pdf': 'PDF',
|
|
4768
5074
|
'media:page': 'Page',
|
|
4769
5075
|
'media:decision': 'Decision',
|
|
4770
5076
|
});
|
|
@@ -4904,12 +5210,12 @@ function test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
|
|
|
4904
5210
|
// failures.
|
|
4905
5211
|
const strand = {
|
|
4906
5212
|
source_media_urn: 'media:pdf;list',
|
|
4907
|
-
target_media_urn: 'media:txt',
|
|
5213
|
+
target_media_urn: 'media:ext=txt',
|
|
4908
5214
|
steps: [
|
|
4909
5215
|
makeForEachStep('media:pdf;list'),
|
|
4910
|
-
makeCapStep('cap:in="media:pdf";a;out="media:image;png"', 'a', 'media:pdf', 'media:image;png', false, false),
|
|
4911
|
-
makeCapStep('cap:in="media:image;png";b;out="media:txt"', 'b', 'media:image;png', 'media:txt', false, false),
|
|
4912
|
-
makeCollectStep('media:txt'),
|
|
5216
|
+
makeCapStep('cap:in="media:ext=pdf";a;out="media:image;png"', 'a', 'media:ext=pdf', 'media:image;png', false, false),
|
|
5217
|
+
makeCapStep('cap:in="media:image;png";b;out="media:ext=txt"', 'b', 'media:image;png', 'media:ext=txt', false, false),
|
|
5218
|
+
makeCollectStep('media:ext=txt'),
|
|
4913
5219
|
],
|
|
4914
5220
|
};
|
|
4915
5221
|
const outcomes = [];
|
|
@@ -4924,7 +5230,7 @@ function test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
|
|
|
4924
5230
|
saved_paths: [],
|
|
4925
5231
|
total_bytes: 0,
|
|
4926
5232
|
duration_ms: 0,
|
|
4927
|
-
failed_cap: 'cap:in="media:image;png";b;out="media:txt"',
|
|
5233
|
+
failed_cap: 'cap:in="media:image;png";b;out="media:ext=txt"',
|
|
4928
5234
|
error: 'oom',
|
|
4929
5235
|
});
|
|
4930
5236
|
}
|
|
@@ -4932,9 +5238,9 @@ function test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
|
|
|
4932
5238
|
resolved_strand: strand,
|
|
4933
5239
|
media_display_names: {
|
|
4934
5240
|
'media:pdf;list': 'PDF List',
|
|
4935
|
-
'media:pdf': 'PDF',
|
|
5241
|
+
'media:ext=pdf': 'PDF',
|
|
4936
5242
|
'media:image;png': 'PNG',
|
|
4937
|
-
'media:txt': 'Text',
|
|
5243
|
+
'media:ext=txt': 'Text',
|
|
4938
5244
|
},
|
|
4939
5245
|
body_outcomes: outcomes,
|
|
4940
5246
|
visible_success_count: 3,
|
|
@@ -4971,19 +5277,19 @@ function test0224_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullT
|
|
|
4971
5277
|
// replica emits 1 entry node + 1 body cap node = 2 nodes.
|
|
4972
5278
|
const strand = {
|
|
4973
5279
|
source_media_urn: 'media:pdf;list',
|
|
4974
|
-
target_media_urn: 'media:txt',
|
|
5280
|
+
target_media_urn: 'media:ext=txt',
|
|
4975
5281
|
steps: [
|
|
4976
5282
|
makeForEachStep('media:pdf;list'),
|
|
4977
|
-
makeCapStep('cap:in="media:pdf";a;out="media:txt"', 'a', 'media:pdf', 'media:txt', false, false),
|
|
4978
|
-
makeCollectStep('media:txt'),
|
|
5283
|
+
makeCapStep('cap:in="media:ext=pdf";a;out="media:ext=txt"', 'a', 'media:ext=pdf', 'media:ext=txt', false, false),
|
|
5284
|
+
makeCollectStep('media:ext=txt'),
|
|
4979
5285
|
],
|
|
4980
5286
|
};
|
|
4981
5287
|
const payload = {
|
|
4982
5288
|
resolved_strand: strand,
|
|
4983
5289
|
media_display_names: {
|
|
4984
5290
|
'media:pdf;list': 'PDF List',
|
|
4985
|
-
'media:pdf': 'PDF',
|
|
4986
|
-
'media:txt': 'Text',
|
|
5291
|
+
'media:ext=pdf': 'PDF',
|
|
5292
|
+
'media:ext=txt': 'Text',
|
|
4987
5293
|
},
|
|
4988
5294
|
body_outcomes: [
|
|
4989
5295
|
{ body_index: 0, success: false, cap_urns: [], saved_paths: [], total_bytes: 0, duration_ms: 0, error: 'unknown' },
|
|
@@ -5074,10 +5380,10 @@ function test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode() {
|
|
|
5074
5380
|
// step_2 (Decision, merged target). No separate `for each` or
|
|
5075
5381
|
// `collect` boxes.
|
|
5076
5382
|
const strand = {
|
|
5077
|
-
source_media_urn: 'media:pdf',
|
|
5383
|
+
source_media_urn: 'media:ext=pdf',
|
|
5078
5384
|
target_media_urn: 'media:decision',
|
|
5079
5385
|
steps: [
|
|
5080
|
-
makeCapStep('cap:in="media:pdf";disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
|
|
5386
|
+
makeCapStep('cap:in="media:ext=pdf";disbind;out="media:page"', 'Disbind', 'media:ext=pdf', 'media:page', false, true),
|
|
5081
5387
|
makeForEachStep('media:page'),
|
|
5082
5388
|
makeCapStep('cap:in="media:page";decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
|
|
5083
5389
|
],
|
|
@@ -5085,7 +5391,7 @@ function test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode() {
|
|
|
5085
5391
|
const payload = {
|
|
5086
5392
|
resolved_strand: strand,
|
|
5087
5393
|
media_display_names: {
|
|
5088
|
-
'media:pdf': 'PDF',
|
|
5394
|
+
'media:ext=pdf': 'PDF',
|
|
5089
5395
|
'media:page': 'Page',
|
|
5090
5396
|
'media:decision': 'Decision',
|
|
5091
5397
|
},
|
|
@@ -5123,10 +5429,10 @@ function test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
|
|
|
5123
5429
|
// doesn't see a stale "Decision" placeholder alongside their
|
|
5124
5430
|
// failed replicas.
|
|
5125
5431
|
const strand = {
|
|
5126
|
-
source_media_urn: 'media:pdf',
|
|
5432
|
+
source_media_urn: 'media:ext=pdf',
|
|
5127
5433
|
target_media_urn: 'media:decision',
|
|
5128
5434
|
steps: [
|
|
5129
|
-
makeCapStep('cap:in="media:pdf";disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
|
|
5435
|
+
makeCapStep('cap:in="media:ext=pdf";disbind;out="media:page"', 'Disbind', 'media:ext=pdf', 'media:page', false, true),
|
|
5130
5436
|
makeForEachStep('media:page'),
|
|
5131
5437
|
makeCapStep('cap:in="media:page";decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
|
|
5132
5438
|
],
|
|
@@ -5135,7 +5441,7 @@ function test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
|
|
|
5135
5441
|
const payload = {
|
|
5136
5442
|
resolved_strand: strand,
|
|
5137
5443
|
media_display_names: {
|
|
5138
|
-
'media:pdf': 'PDF',
|
|
5444
|
+
'media:ext=pdf': 'PDF',
|
|
5139
5445
|
'media:page': 'Page',
|
|
5140
5446
|
'media:decision': 'Decision',
|
|
5141
5447
|
},
|
|
@@ -5194,10 +5500,10 @@ function test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
|
|
|
5194
5500
|
// → body_n_0 (per-body Decision)
|
|
5195
5501
|
// (no merge edge back into the backbone)
|
|
5196
5502
|
const strand = {
|
|
5197
|
-
source_media_urn: 'media:pdf',
|
|
5503
|
+
source_media_urn: 'media:ext=pdf',
|
|
5198
5504
|
target_media_urn: 'media:decision',
|
|
5199
5505
|
steps: [
|
|
5200
|
-
makeCapStep('cap:in="media:pdf";disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
|
|
5506
|
+
makeCapStep('cap:in="media:ext=pdf";disbind;out="media:page"', 'Disbind', 'media:ext=pdf', 'media:page', false, true),
|
|
5201
5507
|
makeForEachStep('media:page'),
|
|
5202
5508
|
makeCapStep('cap:in="media:page";decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
|
|
5203
5509
|
],
|
|
@@ -5205,7 +5511,7 @@ function test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
|
|
|
5205
5511
|
const payload = {
|
|
5206
5512
|
resolved_strand: strand,
|
|
5207
5513
|
media_display_names: {
|
|
5208
|
-
'media:pdf': 'PDF',
|
|
5514
|
+
'media:ext=pdf': 'PDF',
|
|
5209
5515
|
'media:page': 'Page',
|
|
5210
5516
|
'media:decision': 'Decision',
|
|
5211
5517
|
},
|
|
@@ -5258,16 +5564,16 @@ function test0229_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollect
|
|
|
5258
5564
|
target_media_urn: 'media:txt;list',
|
|
5259
5565
|
steps: [
|
|
5260
5566
|
makeForEachStep('media:pdf;list'),
|
|
5261
|
-
makeCapStep('cap:in="media:pdf";extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
|
|
5262
|
-
makeCollectStep('media:txt'),
|
|
5567
|
+
makeCapStep('cap:in="media:ext=pdf";extract;out="media:ext=txt"', 'extract', 'media:ext=pdf', 'media:ext=txt', false, false),
|
|
5568
|
+
makeCollectStep('media:ext=txt'),
|
|
5263
5569
|
],
|
|
5264
5570
|
};
|
|
5265
5571
|
const payload = {
|
|
5266
5572
|
resolved_strand: strand,
|
|
5267
5573
|
media_display_names: {
|
|
5268
5574
|
'media:pdf;list': 'PDF List',
|
|
5269
|
-
'media:pdf': 'PDF',
|
|
5270
|
-
'media:txt': 'Text',
|
|
5575
|
+
'media:ext=pdf': 'PDF',
|
|
5576
|
+
'media:ext=txt': 'Text',
|
|
5271
5577
|
'media:txt;list': 'Text List',
|
|
5272
5578
|
},
|
|
5273
5579
|
body_outcomes: [
|
|
@@ -5427,7 +5733,7 @@ function test0235_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
|
|
|
5427
5733
|
// ---------------- resolved-machine builder ----------------
|
|
5428
5734
|
|
|
5429
5735
|
function test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
|
|
5430
|
-
// A single-strand machine: media:pdf → extract → media:txt
|
|
5736
|
+
// A single-strand machine: media:ext=pdf → extract → media:ext=txt
|
|
5431
5737
|
// → embed → media:embedding. Two edges, three nodes, no
|
|
5432
5738
|
// loops, no fan-in. Tests the basic shape — nodes and
|
|
5433
5739
|
// edges flow through verbatim from the resolved machine
|
|
@@ -5436,28 +5742,28 @@ function test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain
|
|
|
5436
5742
|
strands: [
|
|
5437
5743
|
{
|
|
5438
5744
|
nodes: [
|
|
5439
|
-
{ id: 'n0', urn: 'media:pdf', title: 'PDF' },
|
|
5440
|
-
{ id: 'n1', urn: 'media:txt
|
|
5745
|
+
{ id: 'n0', urn: 'media:ext=pdf', title: 'PDF' },
|
|
5746
|
+
{ id: 'n1', urn: 'media:enc=utf-8;ext=txt', title: 'Plain Text' },
|
|
5441
5747
|
{ id: 'n2', urn: 'media:embedding;record', title: 'Embedding Record' },
|
|
5442
5748
|
],
|
|
5443
5749
|
edges: [
|
|
5444
5750
|
{
|
|
5445
5751
|
alias: 'edge_0',
|
|
5446
|
-
cap_urn: 'cap:in=media:pdf;extract;out=media:txt
|
|
5752
|
+
cap_urn: 'cap:in="media:ext=pdf";extract;out=media:enc=utf-8;ext=txt',
|
|
5447
5753
|
title: 'Extract Text',
|
|
5448
5754
|
is_loop: false,
|
|
5449
5755
|
assignment: [
|
|
5450
|
-
{ cap_arg_media_urn: 'media:pdf', source_node: 'n0' },
|
|
5756
|
+
{ cap_arg_media_urn: 'media:ext=pdf', source_node: 'n0' },
|
|
5451
5757
|
],
|
|
5452
5758
|
target_node: 'n1',
|
|
5453
5759
|
},
|
|
5454
5760
|
{
|
|
5455
5761
|
alias: 'edge_1',
|
|
5456
|
-
cap_urn: 'cap:in=media:
|
|
5762
|
+
cap_urn: 'cap:in=media:enc=utf-8;embed;out=media:embedding;record',
|
|
5457
5763
|
title: 'Generate Embedding',
|
|
5458
5764
|
is_loop: false,
|
|
5459
5765
|
assignment: [
|
|
5460
|
-
{ cap_arg_media_urn: 'media:
|
|
5766
|
+
{ cap_arg_media_urn: 'media:enc=utf-8', source_node: 'n1' },
|
|
5461
5767
|
],
|
|
5462
5768
|
target_node: 'n2',
|
|
5463
5769
|
},
|
|
@@ -5493,17 +5799,17 @@ function test0237_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass()
|
|
|
5493
5799
|
strands: [
|
|
5494
5800
|
{
|
|
5495
5801
|
nodes: [
|
|
5496
|
-
{ id: 'n0', urn: 'media:page
|
|
5497
|
-
{ id: 'n1', urn: 'media:decision;json;record
|
|
5802
|
+
{ id: 'n0', urn: 'media:enc=utf-8;page', title: 'Page' },
|
|
5803
|
+
{ id: 'n1', urn: 'media:decision;fmt=json;record', title: 'Decision Record' },
|
|
5498
5804
|
],
|
|
5499
5805
|
edges: [
|
|
5500
5806
|
{
|
|
5501
5807
|
alias: 'edge_0',
|
|
5502
|
-
cap_urn: 'cap:in=media:
|
|
5808
|
+
cap_urn: 'cap:in=media:enc=utf-8;make-decision;out=media:decision;fmt=json;record',
|
|
5503
5809
|
title: 'Make Decision',
|
|
5504
5810
|
is_loop: true,
|
|
5505
5811
|
assignment: [
|
|
5506
|
-
{ cap_arg_media_urn: 'media:
|
|
5812
|
+
{ cap_arg_media_urn: 'media:enc=utf-8', source_node: 'n0' },
|
|
5507
5813
|
],
|
|
5508
5814
|
target_node: 'n1',
|
|
5509
5815
|
},
|
|
@@ -5530,18 +5836,18 @@ function test0238_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAss
|
|
|
5530
5836
|
{
|
|
5531
5837
|
nodes: [
|
|
5532
5838
|
{ id: 'n0', urn: 'media:image;png', title: 'PNG Image' },
|
|
5533
|
-
{ id: 'n1', urn: 'media:model-spec
|
|
5534
|
-
{ id: 'n2', urn: 'media:image-description
|
|
5839
|
+
{ id: 'n1', urn: 'media:enc=utf-8;model-spec', title: 'Model Spec' },
|
|
5840
|
+
{ id: 'n2', urn: 'media:enc=utf-8;image-description', title: 'Image Description' },
|
|
5535
5841
|
],
|
|
5536
5842
|
edges: [
|
|
5537
5843
|
{
|
|
5538
5844
|
alias: 'edge_0',
|
|
5539
|
-
cap_urn: 'cap:in=media:image;png;describe-image;out=media:image-description
|
|
5845
|
+
cap_urn: 'cap:in=media:image;png;describe-image;out=media:enc=utf-8;image-description',
|
|
5540
5846
|
title: 'Describe Image',
|
|
5541
5847
|
is_loop: false,
|
|
5542
5848
|
assignment: [
|
|
5543
5849
|
{ cap_arg_media_urn: 'media:image;png', source_node: 'n0' },
|
|
5544
|
-
{ cap_arg_media_urn: 'media:model-spec
|
|
5850
|
+
{ cap_arg_media_urn: 'media:enc=utf-8;model-spec', source_node: 'n1' },
|
|
5545
5851
|
],
|
|
5546
5852
|
target_node: 'n2',
|
|
5547
5853
|
},
|
|
@@ -5571,17 +5877,17 @@ function test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrands
|
|
|
5571
5877
|
strands: [
|
|
5572
5878
|
{
|
|
5573
5879
|
nodes: [
|
|
5574
|
-
{ id: 'n0', urn: 'media:pdf', title: 'PDF' },
|
|
5575
|
-
{ id: 'n1', urn: 'media:txt
|
|
5880
|
+
{ id: 'n0', urn: 'media:ext=pdf', title: 'PDF' },
|
|
5881
|
+
{ id: 'n1', urn: 'media:enc=utf-8;ext=txt', title: 'Plain Text' },
|
|
5576
5882
|
],
|
|
5577
5883
|
edges: [
|
|
5578
5884
|
{
|
|
5579
5885
|
alias: 'edge_0',
|
|
5580
|
-
cap_urn: 'cap:in=media:pdf;extract;out=media:txt
|
|
5886
|
+
cap_urn: 'cap:in="media:ext=pdf";extract;out=media:enc=utf-8;ext=txt',
|
|
5581
5887
|
title: 'Extract Text',
|
|
5582
5888
|
is_loop: false,
|
|
5583
5889
|
assignment: [
|
|
5584
|
-
{ cap_arg_media_urn: 'media:pdf', source_node: 'n0' },
|
|
5890
|
+
{ cap_arg_media_urn: 'media:ext=pdf', source_node: 'n0' },
|
|
5585
5891
|
],
|
|
5586
5892
|
target_node: 'n1',
|
|
5587
5893
|
},
|
|
@@ -5591,17 +5897,17 @@ function test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrands
|
|
|
5591
5897
|
},
|
|
5592
5898
|
{
|
|
5593
5899
|
nodes: [
|
|
5594
|
-
{ id: 'n2', urn: 'media:json;record
|
|
5595
|
-
{ id: 'n3', urn: 'media:csv;list;record
|
|
5900
|
+
{ id: 'n2', urn: 'media:fmt=json;record', title: 'JSON Record' },
|
|
5901
|
+
{ id: 'n3', urn: 'media:fmt=csv;list;record', title: 'CSV Rows' },
|
|
5596
5902
|
],
|
|
5597
5903
|
edges: [
|
|
5598
5904
|
{
|
|
5599
5905
|
alias: 'edge_1',
|
|
5600
|
-
cap_urn: 'cap:in=media:json;record;
|
|
5906
|
+
cap_urn: 'cap:in=media:fmt=json;record;convert-format;out=media:fmt=csv;list;record',
|
|
5601
5907
|
title: 'Convert Format',
|
|
5602
5908
|
is_loop: false,
|
|
5603
5909
|
assignment: [
|
|
5604
|
-
{ cap_arg_media_urn: 'media:json;record
|
|
5910
|
+
{ cap_arg_media_urn: 'media:fmt=json;record', source_node: 'n2' },
|
|
5605
5911
|
],
|
|
5606
5912
|
target_node: 'n3',
|
|
5607
5913
|
},
|
|
@@ -5633,7 +5939,7 @@ function test0240_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossSt
|
|
|
5633
5939
|
const payload = {
|
|
5634
5940
|
strands: [
|
|
5635
5941
|
{
|
|
5636
|
-
nodes: [{ id: 'n0', urn: 'media:pdf', title: 'PDF' }],
|
|
5942
|
+
nodes: [{ id: 'n0', urn: 'media:ext=pdf', title: 'PDF' }],
|
|
5637
5943
|
edges: [],
|
|
5638
5944
|
input_anchor_nodes: ['n0'],
|
|
5639
5945
|
output_anchor_nodes: ['n0'],
|
|
@@ -5733,7 +6039,7 @@ function test1801_kindSourceWhenInputIsVoid() {
|
|
|
5733
6039
|
const warm = CapUrn.fromString('cap:in=media:void;out="media:model-artifact";warm');
|
|
5734
6040
|
assertEqual(warm.kind(), CapKind.SOURCE, 'warm cap is a Source');
|
|
5735
6041
|
|
|
5736
|
-
const gen = CapUrn.fromString('cap:in=media:void;out=media:
|
|
6042
|
+
const gen = CapUrn.fromString('cap:in=media:void;out="media:enc=utf-8"');
|
|
5737
6043
|
assertEqual(gen.kind(), CapKind.SOURCE, 'in=void with concrete out is a Source');
|
|
5738
6044
|
}
|
|
5739
6045
|
|
|
@@ -5742,7 +6048,7 @@ function test1802_kindSinkWhenOutputIsVoid() {
|
|
|
5742
6048
|
const discard = CapUrn.fromString('cap:discard;in=media:;out=media:void');
|
|
5743
6049
|
assertEqual(discard.kind(), CapKind.SINK, 'discard cap is a Sink');
|
|
5744
6050
|
|
|
5745
|
-
const log = CapUrn.fromString('cap:in="media:json
|
|
6051
|
+
const log = CapUrn.fromString('cap:in="media:fmt=json";log;out=media:void');
|
|
5746
6052
|
assertEqual(log.kind(), CapKind.SINK, 'log cap is a Sink');
|
|
5747
6053
|
}
|
|
5748
6054
|
|
|
@@ -5759,7 +6065,7 @@ function test1803_kindEffectWhenBothSidesVoid() {
|
|
|
5759
6065
|
// TEST1804: Transform classifier — at least one side non-void, and
|
|
5760
6066
|
// the cap is not the bare identity.
|
|
5761
6067
|
function test1804_kindTransformForNormalDataProcessors() {
|
|
5762
|
-
const extract = CapUrn.fromString('cap:extract;in=media:pdf;out="media:record
|
|
6068
|
+
const extract = CapUrn.fromString('cap:extract;in="media:ext=pdf";out="media:enc=utf-8;record"');
|
|
5763
6069
|
assertEqual(extract.kind(), CapKind.TRANSFORM, 'extract is a Transform');
|
|
5764
6070
|
|
|
5765
6071
|
const labeled = CapUrn.fromString('cap:passthrough;in=media:;out=media:');
|
|
@@ -5812,13 +6118,13 @@ function test1805_kindInvariantUnderCanonicalSpellings() {
|
|
|
5812
6118
|
const cases = [
|
|
5813
6119
|
{ a: 'cap:effect=none', b: 'cap:in=media:;out=media:;effect=none', expected: CapKind.IDENTITY },
|
|
5814
6120
|
{
|
|
5815
|
-
a: 'cap:extract;in=media:pdf;out=media:
|
|
5816
|
-
b: 'cap:extract;in="media:pdf";out="media:
|
|
6121
|
+
a: 'cap:extract;in="media:ext=pdf";out="media:enc=utf-8"',
|
|
6122
|
+
b: 'cap:extract;in="media:ext=pdf";out="media:enc=utf-8"',
|
|
5817
6123
|
expected: CapKind.TRANSFORM,
|
|
5818
6124
|
},
|
|
5819
6125
|
{
|
|
5820
|
-
a: 'cap:in=media:void;out=media:
|
|
5821
|
-
b: 'cap:warm;out=media:
|
|
6126
|
+
a: 'cap:in=media:void;out="media:enc=utf-8";warm',
|
|
6127
|
+
b: 'cap:warm;out="media:enc=utf-8";in=media:void',
|
|
5822
6128
|
expected: CapKind.SOURCE,
|
|
5823
6129
|
},
|
|
5824
6130
|
];
|
|
@@ -6017,9 +6323,9 @@ function test1843_rejectInvalidCombinations() {
|
|
|
6017
6323
|
|
|
6018
6324
|
// TEST1844: out-axis difference dominates combined in+y differences.
|
|
6019
6325
|
function test1844_axisWeightingOutDominates() {
|
|
6020
|
-
const bigOut = CapUrn.fromString('cap:in=media:;out="media:record
|
|
6326
|
+
const bigOut = CapUrn.fromString('cap:in=media:;out="media:enc=utf-8;record"');
|
|
6021
6327
|
const bigInAndY = CapUrn.fromString(
|
|
6022
|
-
'cap:in=media:pdf;out=media:record;!constrained;?target;extract;stage!=alpha;target2=metadata;ver?=draft'
|
|
6328
|
+
'cap:in="media:ext=pdf";out=media:record;!constrained;?target;extract;stage!=alpha;target2=metadata;ver?=draft'
|
|
6023
6329
|
);
|
|
6024
6330
|
assert(bigOut.specificity() > bigInAndY.specificity(),
|
|
6025
6331
|
'out-axis difference must dominate combined in+y differences');
|
|
@@ -6027,7 +6333,7 @@ function test1844_axisWeightingOutDominates() {
|
|
|
6027
6333
|
|
|
6028
6334
|
// TEST1845: With equal out, in-axis dominates over y-axis.
|
|
6029
6335
|
function test1845_axisWeightingInDominatesY() {
|
|
6030
|
-
const bigIn = CapUrn.fromString('cap:in=media:pdf;out=media:record');
|
|
6336
|
+
const bigIn = CapUrn.fromString('cap:in="media:ext=pdf";out=media:record');
|
|
6031
6337
|
const bigY = CapUrn.fromString(
|
|
6032
6338
|
'cap:in=media:;out=media:record;!constrained;?target;extract;stage!=alpha;target2=metadata;ver?=draft'
|
|
6033
6339
|
);
|
|
@@ -6049,7 +6355,7 @@ function test1846_axisWeightingDecodedLayout() {
|
|
|
6049
6355
|
|
|
6050
6356
|
// TEST1847: Cap with version=0 round-trips with no `version` key on wire
|
|
6051
6357
|
function test1847_capVersionZeroOmittedOnWire() {
|
|
6052
|
-
const urn = CapUrn.fromString('cap:in="media:void";test-op;out="media:record
|
|
6358
|
+
const urn = CapUrn.fromString('cap:in="media:void";test-op;out="media:enc=utf-8;record"');
|
|
6053
6359
|
const cap = new Cap(urn, 'Test Cap', 'test-op');
|
|
6054
6360
|
// version defaults to 0
|
|
6055
6361
|
assertEqual(cap.version, 0, 'Default version should be 0');
|
|
@@ -6061,7 +6367,7 @@ function test1847_capVersionZeroOmittedOnWire() {
|
|
|
6061
6367
|
|
|
6062
6368
|
// TEST1848: Cap with version=N round-trips with `version: N` on wire
|
|
6063
6369
|
function test1848_capVersionNonZeroOnWire() {
|
|
6064
|
-
const urn = CapUrn.fromString('cap:in="media:void";versioned-op;out="media:record
|
|
6370
|
+
const urn = CapUrn.fromString('cap:in="media:void";versioned-op;out="media:enc=utf-8;record"');
|
|
6065
6371
|
const cap = new Cap(urn, 'Versioned Cap', 'versioned-op');
|
|
6066
6372
|
cap.version = 42;
|
|
6067
6373
|
const json = cap.toJSON();
|
|
@@ -6144,7 +6450,7 @@ async function runTests() {
|
|
|
6144
6450
|
// media_urn.rs: TEST060-TEST078
|
|
6145
6451
|
console.log('\n--- media_urn.rs ---');
|
|
6146
6452
|
runTest('TEST060: wrong_prefix_fails', test060_wrongPrefixFails);
|
|
6147
|
-
|
|
6453
|
+
console.log(' SKIP TEST061: REMOVED (binary/text distinction gone; see TEST067 for enc=)');
|
|
6148
6454
|
runTest('TEST062: is_record', test062_isRecord);
|
|
6149
6455
|
runTest('TEST063: is_scalar', test063_isScalar);
|
|
6150
6456
|
runTest('TEST064: is_list', test064_isList);
|
|
@@ -6258,6 +6564,27 @@ async function runTests() {
|
|
|
6258
6564
|
runTest('TEST334: cartridge_repo_client_needs_sync', test334_cartridgeRepoClientNeedsSync);
|
|
6259
6565
|
runTest('TEST335: cartridge_repo_server_client_integration', test335_cartridgeRepoServerClientIntegration);
|
|
6260
6566
|
|
|
6567
|
+
// cartridge_repo.rs: TEST1849-TEST1853 (host-compatibility resolution)
|
|
6568
|
+
console.log('\n--- cartridge_repo.rs (resolve_for_host) ---');
|
|
6569
|
+
runTest('TEST1849: resolve_for_host_compatible_latest', test1849_resolveForHostCompatibleLatest);
|
|
6570
|
+
runTest('TEST1850: resolve_for_host_compatible_outdated', test1850_resolveForHostCompatibleOutdated);
|
|
6571
|
+
runTest('TEST1851: resolve_for_host_incompatible', test1851_resolveForHostIncompatible);
|
|
6572
|
+
runTest('TEST1852: resolve_for_host_skips_build_with_no_installer', test1852_resolveForHostSkipsBuildWithNoInstaller);
|
|
6573
|
+
runTest('TEST1853: host_platform_normalized_form', test1853_hostPlatformNormalizedForm);
|
|
6574
|
+
|
|
6575
|
+
// manifest.rs: TEST1872-TEST1874 (registry_url_from_build_env)
|
|
6576
|
+
console.log('\n--- manifest.rs (registry_url_from_build_env) ---');
|
|
6577
|
+
runTest('TEST1872: registry_url_from_build_env_passes_through_nonempty', test1872_registryUrlFromBuildEnvPassesThroughNonempty);
|
|
6578
|
+
runTest('TEST1873: registry_url_from_build_env_none_for_dev', test1873_registryUrlFromBuildEnvNoneForDev);
|
|
6579
|
+
runTest('TEST1874: registry_url_from_build_env_rejects_empty_string', test1874_registryUrlFromBuildEnvRejectsEmptyString);
|
|
6580
|
+
|
|
6581
|
+
// cartridge_discovery.rs: TEST1875-TEST1878 (scan-all discovery)
|
|
6582
|
+
console.log('\n--- cartridge_discovery.rs (discover_cartridges) ---');
|
|
6583
|
+
await runTest('TEST1875: scan_all_reaches_both_dev_and_registry_slugs', test1875_scanAllReachesBothDevAndRegistrySlugs);
|
|
6584
|
+
await runTest('TEST1876: other_channel_subtree_is_skipped', test1876_otherChannelSubtreeIsSkipped);
|
|
6585
|
+
await runTest('TEST1877: registry_cartridge_under_wrong_slug_is_bad_install', test1877_registryCartridgeUnderWrongSlugIsBadInstall);
|
|
6586
|
+
await runTest('TEST1878: bundled_provider_without_baked_hash_is_rejected', test1878_bundledProviderWithoutBakedHashIsRejected);
|
|
6587
|
+
|
|
6261
6588
|
// media_urn.rs: TEST1312-TEST1315, TEST1298-TEST1302 (MediaUrn predicates)
|
|
6262
6589
|
console.log('\n--- media_urn.rs (predicates) ---');
|
|
6263
6590
|
runTest('TEST1312: is_image', test1312_isImage);
|