capdag 0.158.370 → 0.161.384

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/capdag.test.js CHANGED
@@ -119,7 +119,7 @@ function makeCap(urnString, title) {
119
119
 
120
120
  // Helper to create caps with specific in/out media URNs for graph testing
121
121
  function makeGraphCap(inUrn, outUrn, title) {
122
- const urnString = `cap:in="${inUrn}";op=convert;out="${outUrn}"`;
122
+ const urnString = `cap:in="${inUrn}";convert;out="${outUrn}"`;
123
123
  const capUrn = CapUrn.fromString(urnString);
124
124
  return new Cap(capUrn, title, 'convert', title);
125
125
  }
@@ -130,7 +130,7 @@ function makeGraphCap(inUrn, outUrn, title) {
130
130
 
131
131
  // TEST001: Test that cap URN is created with tags parsed correctly and direction specs accessible
132
132
  function test001_capUrnCreation() {
133
- const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf;target=thumbnail'));
133
+ const cap = CapUrn.fromString(testUrn('generate;ext=pdf;target=thumbnail'));
134
134
  assertEqual(cap.getTag('op'), 'generate', 'Should get op tag');
135
135
  assertEqual(cap.getTag('target'), 'thumbnail', 'Should get target tag');
136
136
  assertEqual(cap.getTag('ext'), 'pdf', 'Should get ext tag');
@@ -156,11 +156,11 @@ function test003_directionMatching() {
156
156
  assert(cap.accepts(request), 'Same direction specs should match');
157
157
 
158
158
  // Different direction should not match
159
- const requestDiff = CapUrn.fromString('cap:in="media:textable";op=generate;out="media:record;textable"');
159
+ const requestDiff = CapUrn.fromString('cap:in="media:textable";generate;out="media:record;textable"');
160
160
  assert(!cap.accepts(requestDiff), 'Different inSpec should not match');
161
161
 
162
162
  // Wildcard direction matches any
163
- const wildcardCap = CapUrn.fromString('cap:in=*;op=generate;out=*');
163
+ const wildcardCap = CapUrn.fromString('cap:in=*;generate;out=*');
164
164
  assert(wildcardCap.accepts(request), 'Wildcard direction should match any');
165
165
  }
166
166
 
@@ -237,7 +237,7 @@ function test011_serializationSmartQuoting() {
237
237
 
238
238
  // TEST012: Test that simple cap URN round-trips (parse -> serialize -> parse equals original)
239
239
  function test012_roundTripSimple() {
240
- const original = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
240
+ const original = CapUrn.fromString(testUrn('generate;ext=pdf'));
241
241
  const serialized = original.toString();
242
242
  const reparsed = CapUrn.fromString(serialized);
243
243
  assert(original.equals(reparsed), 'Simple round-trip should produce equal URN');
@@ -276,18 +276,18 @@ function test015_capPrefixRequired() {
276
276
 
277
277
  // TEST016: Test that trailing semicolon is equivalent (same hash, same string, matches)
278
278
  function test016_trailingSemicolonEquivalence() {
279
- const cap1 = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
280
- const cap2 = CapUrn.fromString(testUrn('op=generate;ext=pdf') + ';');
279
+ const cap1 = CapUrn.fromString(testUrn('generate;ext=pdf'));
280
+ const cap2 = CapUrn.fromString(testUrn('generate;ext=pdf') + ';');
281
281
  assert(cap1.equals(cap2), 'With/without trailing semicolon should be equal');
282
282
  assertEqual(cap1.toString(), cap2.toString(), 'Canonical forms should match');
283
283
  }
284
284
 
285
285
  // TEST017: Test tag matching: exact match, subset match, wildcard match, value mismatch
286
286
  function test017_tagMatching() {
287
- const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf;target=thumbnail'));
287
+ const cap = CapUrn.fromString(testUrn('generate;ext=pdf;target=thumbnail'));
288
288
 
289
289
  // Exact match — both directions accept
290
- const exact = CapUrn.fromString(testUrn('op=generate;ext=pdf;target=thumbnail'));
290
+ const exact = CapUrn.fromString(testUrn('generate;ext=pdf;target=thumbnail'));
291
291
  assert(cap.accepts(exact), 'Should accept exact match');
292
292
  assert(exact.accepts(cap), 'Exact match should accept in reverse too');
293
293
 
@@ -320,7 +320,7 @@ function test019_missingTagHandling() {
320
320
  assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
321
321
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
322
322
 
323
- const cap2 = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
323
+ const cap2 = CapUrn.fromString(testUrn('generate;ext=pdf'));
324
324
  const request2 = CapUrn.fromString(testUrn('op=generate'));
325
325
  assert(!cap2.accepts(request2), 'Specific pattern should reject instance missing ext');
326
326
  assert(request2.accepts(cap2), 'General request should accept more specific instance');
@@ -386,8 +386,8 @@ function test023_builderPreservesCase() {
386
386
 
387
387
  // TEST024: Directional accepts — pattern's tags are constraints, instance must satisfy
388
388
  function test024_compatibility() {
389
- const cap1 = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
390
- const cap2 = CapUrn.fromString(testUrn('op=generate;format=*'));
389
+ const cap1 = CapUrn.fromString(testUrn('generate;ext=pdf'));
390
+ const cap2 = CapUrn.fromString(testUrn('generate;format=*'));
391
391
  const cap3 = CapUrn.fromString(testUrn('type=image;op=extract'));
392
392
 
393
393
  assert(!cap1.accepts(cap2), 'Pattern requiring ext should reject instance missing ext');
@@ -399,7 +399,7 @@ function test024_compatibility() {
399
399
  assert(general.accepts(cap1), 'General request should accept more specific instance');
400
400
  assert(!cap1.accepts(general), 'Specific pattern should reject general instance');
401
401
 
402
- const broadIn = CapUrn.fromString('cap:in="media:";op=generate;out="media:record;textable"');
402
+ const broadIn = CapUrn.fromString('cap:in="media:";generate;out="media:record;textable"');
403
403
  assert(!cap1.accepts(broadIn), 'Specific input should not accept broad wildcard input');
404
404
  assert(broadIn.accepts(cap1), 'Wildcard input should accept specific input');
405
405
  }
@@ -409,7 +409,7 @@ function test025_bestMatch() {
409
409
  const caps = [
410
410
  CapUrn.fromString('cap:in=*;out=*;op=*'),
411
411
  CapUrn.fromString(testUrn('op=generate')),
412
- CapUrn.fromString(testUrn('op=generate;ext=pdf'))
412
+ CapUrn.fromString(testUrn('generate;ext=pdf'))
413
413
  ];
414
414
  const request = CapUrn.fromString(testUrn('op=generate'));
415
415
  const best = CapMatcher.findBestMatch(caps, request);
@@ -573,67 +573,67 @@ function test039_getTagReturnsDirectionSpecs() {
573
573
 
574
574
  // TEST040: Matching semantics - exact match succeeds
575
575
  function test040_matchingSemanticsExactMatch() {
576
- const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
577
- const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
576
+ const cap = CapUrn.fromString(testUrn('generate;ext=pdf'));
577
+ const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
578
578
  assert(cap.accepts(request), 'Exact match should accept');
579
579
  }
580
580
 
581
581
  // TEST041: Matching semantics - cap missing tag matches (implicit wildcard)
582
582
  function test041_matchingSemanticsCapMissingTag() {
583
583
  const cap = CapUrn.fromString(testUrn('op=generate'));
584
- const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
584
+ const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
585
585
  assert(cap.accepts(request), 'General pattern with only op should accept specific instance');
586
586
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
587
587
  }
588
588
 
589
589
  // TEST042: Pattern rejects instance missing required tags
590
590
  function test042_matchingSemanticsCapHasExtraTag() {
591
- const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf;version=2'));
592
- const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
591
+ const cap = CapUrn.fromString(testUrn('generate;ext=pdf;version=2'));
592
+ const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
593
593
  assert(!cap.accepts(request), 'Pattern requiring version should reject instance missing version');
594
594
  assert(request.accepts(cap), 'General request should accept refined instance');
595
595
  }
596
596
 
597
597
  // TEST043: Matching semantics - request wildcard matches specific cap value
598
598
  function test043_matchingSemanticsRequestHasWildcard() {
599
- const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
600
- const request = CapUrn.fromString(testUrn('op=generate;ext=*'));
599
+ const cap = CapUrn.fromString(testUrn('generate;ext=pdf'));
600
+ const request = CapUrn.fromString(testUrn('generate;ext=*'));
601
601
  assert(cap.accepts(request), 'Request wildcard should match specific cap value');
602
602
  }
603
603
 
604
604
  // TEST044: Matching semantics - cap wildcard matches specific request value
605
605
  function test044_matchingSemanticsCapHasWildcard() {
606
- const cap = CapUrn.fromString(testUrn('op=generate;ext=*'));
607
- const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
606
+ const cap = CapUrn.fromString(testUrn('generate;ext=*'));
607
+ const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
608
608
  assert(cap.accepts(request), 'Cap wildcard should match specific request value');
609
609
  }
610
610
 
611
611
  // TEST045: Matching semantics - value mismatch does not match
612
612
  function test045_matchingSemanticsValueMismatch() {
613
- const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
614
- const request = CapUrn.fromString(testUrn('op=generate;ext=docx'));
613
+ const cap = CapUrn.fromString(testUrn('generate;ext=pdf'));
614
+ const request = CapUrn.fromString(testUrn('generate;ext=docx'));
615
615
  assert(!cap.accepts(request), 'Value mismatch should not accept');
616
616
  }
617
617
 
618
618
  // TEST046: Matching semantics - fallback pattern (cap missing tag = implicit wildcard)
619
619
  function test046_matchingSemanticsFallbackPattern() {
620
- const cap = CapUrn.fromString('cap:in="media:binary";op=generate_thumbnail;out="media:binary"');
621
- const request = CapUrn.fromString('cap:ext=wav;in="media:binary";op=generate_thumbnail;out="media:binary"');
620
+ const cap = CapUrn.fromString('cap:in="media:binary";generate-thumbnail;out="media:binary"');
621
+ const request = CapUrn.fromString('cap:ext=wav;in="media:binary";generate-thumbnail;out="media:binary"');
622
622
  assert(cap.accepts(request), 'General pattern without ext should accept specific instance');
623
623
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
624
624
  }
625
625
 
626
626
  // TEST047: Matching semantics - thumbnail fallback with void input
627
627
  function test047_matchingSemanticsThumbnailVoidInput() {
628
- const cap = CapUrn.fromString('cap:in="media:void";op=generate_thumbnail;out="media:image;png;thumbnail"');
629
- const request = CapUrn.fromString('cap:ext=pdf;in="media:void";op=generate_thumbnail;out="media:image"');
628
+ const cap = CapUrn.fromString('cap:in="media:void";generate-thumbnail;out="media:image;png;thumbnail"');
629
+ const request = CapUrn.fromString('cap:ext=pdf;in="media:void";generate-thumbnail;out="media:image"');
630
630
  assert(cap.accepts(request), 'Void input cap should accept request; cap output conforms to less-specific request output');
631
631
  }
632
632
 
633
633
  // TEST048: Matching semantics - wildcard direction matches anything
634
634
  function test048_matchingSemanticsWildcardDirection() {
635
635
  const cap = CapUrn.fromString('cap:in=*;out=*');
636
- const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
636
+ const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
637
637
  assert(cap.accepts(request), 'Wildcard cap should accept any request');
638
638
  }
639
639
 
@@ -648,10 +648,10 @@ function test049_matchingSemanticsCrossDimension() {
648
648
  // TEST050: Matching semantics - direction mismatch prevents matching
649
649
  function test050_matchingSemanticsDirectionMismatch() {
650
650
  const cap = CapUrn.fromString(
651
- `cap:in="${MEDIA_STRING}";op=generate;out="${MEDIA_OBJECT}"`
651
+ `cap:in="${MEDIA_STRING}";generate;out="${MEDIA_OBJECT}"`
652
652
  );
653
653
  const request = CapUrn.fromString(
654
- `cap:in="${MEDIA_IDENTITY}";op=generate;out="${MEDIA_OBJECT}"`
654
+ `cap:in="${MEDIA_IDENTITY}";generate;out="${MEDIA_OBJECT}"`
655
655
  );
656
656
  assert(!cap.accepts(request), 'Incompatible direction types should not match');
657
657
  }
@@ -660,25 +660,25 @@ function test050_matchingSemanticsDirectionMismatch() {
660
660
  function test890_directionSemanticMatching() {
661
661
  // Generic wildcard cap accepts specific pdf request
662
662
  const genericCap = CapUrn.fromString(
663
- 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
663
+ 'cap:in="media:";generate-thumbnail;out="media:image;png;thumbnail"'
664
664
  );
665
665
  const pdfRequest = CapUrn.fromString(
666
- 'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
666
+ 'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"'
667
667
  );
668
668
  assert(genericCap.accepts(pdfRequest), 'Generic wildcard cap must accept pdf request');
669
669
 
670
670
  // Also accepts epub
671
671
  const epubRequest = CapUrn.fromString(
672
- 'cap:in="media:epub";op=generate_thumbnail;out="media:image;png;thumbnail"'
672
+ 'cap:in="media:epub";generate-thumbnail;out="media:image;png;thumbnail"'
673
673
  );
674
674
  assert(genericCap.accepts(epubRequest), 'Generic wildcard cap must accept epub request');
675
675
 
676
676
  // Reverse: specific pdf cap does NOT accept generic bytes request
677
677
  const pdfCap = CapUrn.fromString(
678
- 'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
678
+ 'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"'
679
679
  );
680
680
  const genericRequest = CapUrn.fromString(
681
- 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
681
+ 'cap:in="media:";generate-thumbnail;out="media:image;png;thumbnail"'
682
682
  );
683
683
  assert(!pdfCap.accepts(genericRequest), 'Specific pdf cap must NOT accept generic wildcard request');
684
684
 
@@ -687,20 +687,20 @@ function test890_directionSemanticMatching() {
687
687
 
688
688
  // Output direction: cap producing more specific output satisfies less specific request
689
689
  const specificOutCap = CapUrn.fromString(
690
- 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
690
+ 'cap:in="media:";generate-thumbnail;out="media:image;png;thumbnail"'
691
691
  );
692
692
  const genericOutRequest = CapUrn.fromString(
693
- 'cap:in="media:";op=generate_thumbnail;out="media:image"'
693
+ 'cap:in="media:";generate-thumbnail;out="media:image"'
694
694
  );
695
695
  assert(specificOutCap.accepts(genericOutRequest),
696
696
  'Cap producing image;png;thumbnail must satisfy request for image');
697
697
 
698
698
  // Reverse output: generic output cap does NOT satisfy specific output request
699
699
  const genericOutCap = CapUrn.fromString(
700
- 'cap:in="media:";op=generate_thumbnail;out="media:image"'
700
+ 'cap:in="media:";generate-thumbnail;out="media:image"'
701
701
  );
702
702
  const specificOutRequest = CapUrn.fromString(
703
- 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
703
+ 'cap:in="media:";generate-thumbnail;out="media:image;png;thumbnail"'
704
704
  );
705
705
  assert(!genericOutCap.accepts(specificOutRequest),
706
706
  'Generic output cap must NOT satisfy specific output request');
@@ -709,10 +709,10 @@ function test890_directionSemanticMatching() {
709
709
  // TEST891: Semantic direction specificity - more media URN tags = higher specificity
710
710
  function test891_directionSemanticSpecificity() {
711
711
  const genericCap = CapUrn.fromString(
712
- 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
712
+ 'cap:in="media:";generate-thumbnail;out="media:image;png;thumbnail"'
713
713
  );
714
714
  const specificCap = CapUrn.fromString(
715
- 'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
715
+ 'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"'
716
716
  );
717
717
 
718
718
  assertEqual(genericCap.specificity(), 4, 'media:(0) + image;png;thumbnail(3) + op(1) = 4');
@@ -721,7 +721,7 @@ function test891_directionSemanticSpecificity() {
721
721
 
722
722
  // CapMatcher should prefer more specific
723
723
  const pdfRequest = CapUrn.fromString(
724
- 'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
724
+ 'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"'
725
725
  );
726
726
  const best = CapMatcher.findBestMatch([genericCap, specificCap], pdfRequest);
727
727
  assert(best !== null, 'Should find a match');
@@ -1494,7 +1494,7 @@ function testJS_getExtensionMappings() {
1494
1494
  }
1495
1495
 
1496
1496
  function testJS_capWithMediaSpecs() {
1497
- const urn = CapUrn.fromString('cap:in="media:string";op=test;out="media:custom"');
1497
+ const urn = CapUrn.fromString('cap:in="media:string";test;out="media:custom"');
1498
1498
  const cap = new Cap(urn, 'Test Cap', 'test_command');
1499
1499
  cap.mediaSpecs = [
1500
1500
  { urn: MEDIA_STRING, media_type: 'text/plain', title: 'String', profile_uri: 'https://capdag.com/schema/str' },
@@ -1664,12 +1664,12 @@ const sampleRegistry = {
1664
1664
  adapter_urns: ['media:pdf'],
1665
1665
  caps: [
1666
1666
  {
1667
- urn: 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;list"',
1667
+ urn: 'cap:in="media:pdf";disbind;out="media:disbound-page;textable;list"',
1668
1668
  title: 'Disbind PDF',
1669
1669
  description: 'Extract pages'
1670
1670
  },
1671
1671
  {
1672
- urn: 'cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;textable;record"',
1672
+ urn: 'cap:in="media:pdf";extract-metadata;out="media:file-metadata;textable;record"',
1673
1673
  title: 'Extract Metadata',
1674
1674
  description: 'Get PDF metadata'
1675
1675
  }
@@ -1709,7 +1709,7 @@ const sampleRegistry = {
1709
1709
  adapter_urns: ['media:txt;textable'],
1710
1710
  caps: [
1711
1711
  {
1712
- urn: 'cap:in="media:txt;textable";op=disbind;out="media:disbound-page;textable;list"',
1712
+ urn: 'cap:in="media:txt;textable";disbind;out="media:disbound-page;textable;list"',
1713
1713
  title: 'Disbind Text',
1714
1714
  description: 'Extract text pages'
1715
1715
  }
@@ -1751,7 +1751,7 @@ const sampleRegistry = {
1751
1751
  adapter_urns: ['media:json'],
1752
1752
  caps: [
1753
1753
  {
1754
- urn: 'cap:in="media:json";op=pretty;out="media:json;textable"',
1754
+ urn: 'cap:in="media:json";pretty;out="media:json;textable"',
1755
1755
  title: 'Pretty Print JSON',
1756
1756
  description: 'Format JSON',
1757
1757
  command: 'pretty'
@@ -1795,7 +1795,7 @@ function test320_cartridgeInfoConstruction() {
1795
1795
  cap_groups: [{
1796
1796
  name: 'test-group',
1797
1797
  adapter_urns: ['media:void'],
1798
- caps: [{urn: 'cap:in="media:void";op=test;out="media:void"', title: 'Test', description: ''}]
1798
+ caps: [{urn: 'cap:in="media:void";test;out="media:void"', title: 'Test', description: ''}]
1799
1799
  }],
1800
1800
  versions: {
1801
1801
  '1.0.0': {
@@ -1811,7 +1811,7 @@ function test320_cartridgeInfoConstruction() {
1811
1811
  assert(cartridge.id === 'testcartridge', 'ID should match');
1812
1812
  assert(cartridge.teamId === 'TEAM123', 'Team ID should match');
1813
1813
  assert(cartridge.cap_groups.length === 1, 'Should have 1 cap_group');
1814
- assert(cartridge.cap_groups[0].caps[0].urn === 'cap:in="media:void";op=test;out="media:void"', 'Cap URN should match');
1814
+ assert(cartridge.cap_groups[0].caps[0].urn === 'cap:in="media:void";test;out="media:void"', 'Cap URN should match');
1815
1815
  assert(cartridge.allCaps().length === 1, 'allCaps() should return 1 cap');
1816
1816
  }
1817
1817
 
@@ -2029,13 +2029,13 @@ function test328_cartridgeRepoServerGetByCategory() {
2029
2029
  function test329_cartridgeRepoServerGetByCap() {
2030
2030
  const server = new CartridgeRepoServer(sampleRegistry);
2031
2031
 
2032
- const disbindCap = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;list"';
2032
+ const disbindCap = 'cap:in="media:pdf";disbind;out="media:disbound-page;textable;list"';
2033
2033
  const cartridges = server.getCartridgesByCap(disbindCap);
2034
2034
 
2035
2035
  assert(cartridges.length === 1, 'Should find 1 cartridge with this cap');
2036
2036
  assert(cartridges[0].id === 'pdfcartridge', 'Should be pdfcartridge');
2037
2037
 
2038
- const metadataCap = 'cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;textable;record"';
2038
+ const metadataCap = 'cap:in="media:pdf";extract-metadata;out="media:file-metadata;textable;record"';
2039
2039
  const metadataCartridges = server.getCartridgesByCap(metadataCap);
2040
2040
  assert(metadataCartridges.length === 1, 'Should find metadata cap');
2041
2041
  }
@@ -2078,7 +2078,7 @@ function test331_cartridgeRepoClientGetSuggestions() {
2078
2078
 
2079
2079
  client.updateCache('https://example.com/api/cartridges', cartridges);
2080
2080
 
2081
- const disbindCap = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;list"';
2081
+ const disbindCap = 'cap:in="media:pdf";disbind;out="media:disbound-page;textable;list"';
2082
2082
  const suggestions = client.getSuggestionsForCap(disbindCap);
2083
2083
 
2084
2084
  assert(suggestions.length === 1, 'Should find 1 suggestion');
@@ -2093,7 +2093,7 @@ function test331_cartridgeRepoClientGetSuggestions() {
2093
2093
  assert(suggestions[0].capTitle === 'Disbind PDF', 'Should have cap title');
2094
2094
 
2095
2095
  // Nightly cap should also surface a suggestion, with channel set to nightly.
2096
- const prettyCap = 'cap:in="media:json";op=pretty;out="media:json;textable"';
2096
+ const prettyCap = 'cap:in="media:json";pretty;out="media:json;textable"';
2097
2097
  const nightlySuggestions = client.getSuggestionsForCap(prettyCap);
2098
2098
  assert(nightlySuggestions.length === 1, 'Should find nightly cap suggestion');
2099
2099
  assert(nightlySuggestions[0].channel === 'nightly', 'Should report nightly channel');
@@ -2202,7 +2202,7 @@ function test335_cartridgeRepoServerClientIntegration() {
2202
2202
  assert(cartridge.buildForPlatform('darwin-arm64') !== null, 'Cartridge should have darwin-arm64 build');
2203
2203
 
2204
2204
  // Client can get suggestions
2205
- const capUrn = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;list"';
2205
+ const capUrn = 'cap:in="media:pdf";disbind;out="media:disbound-page;textable;list"';
2206
2206
  const suggestions = client.getSuggestionsForCap(capUrn);
2207
2207
  assert(suggestions.length === 1, 'Should get suggestions');
2208
2208
  assert(suggestions[0].cartridgeId === 'pdfcartridge', 'Should suggest correct cartridge');
@@ -2338,7 +2338,7 @@ function test1302_predicateConstantConsistency() {
2338
2338
 
2339
2339
  // TEST1303: without_tag removes tag, ignores in/out, case-insensitive for keys
2340
2340
  function test1303_withoutTag() {
2341
- const cap = CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"');
2341
+ const cap = CapUrn.fromString('cap:in="media:void";test;ext=pdf;out="media:void"');
2342
2342
  const removed = cap.withoutTag('ext');
2343
2343
  assertEqual(removed.getTag('ext'), undefined, 'withoutTag should remove ext');
2344
2344
  assertEqual(removed.getTag('op'), 'test', 'withoutTag should preserve op');
@@ -2360,7 +2360,7 @@ function test1303_withoutTag() {
2360
2360
 
2361
2361
  // TEST1304: with_in_spec and with_out_spec change direction specs
2362
2362
  function test1304_withInOutSpec() {
2363
- const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
2363
+ const cap = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
2364
2364
 
2365
2365
  const changedIn = cap.withInSpec('media:');
2366
2366
  assertEqual(changedIn.getInSpec(), 'media:', 'withInSpec should change inSpec');
@@ -2384,15 +2384,15 @@ function test1304_withInOutSpec() {
2384
2384
  // TEST1305: CapMatcher::find_all_matches returns all matching caps sorted by specificity
2385
2385
  function test1305_findAllMatches() {
2386
2386
  const caps = [
2387
- CapUrn.fromString('cap:in="media:void";op=test;out="media:void"'),
2388
- CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"'),
2389
- CapUrn.fromString('cap:in="media:void";op=different;out="media:void"'),
2387
+ CapUrn.fromString('cap:in="media:void";test;out="media:void"'),
2388
+ CapUrn.fromString('cap:in="media:void";test;ext=pdf;out="media:void"'),
2389
+ CapUrn.fromString('cap:in="media:void";different;out="media:void"'),
2390
2390
  ];
2391
2391
 
2392
- const request = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
2392
+ const request = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
2393
2393
  const matches = CapMatcher.findAllMatches(caps, request);
2394
2394
 
2395
- // Should find 2 matches (op=test and op=test;ext=pdf), not op=different
2395
+ // Should find 2 matches (op=test and test;ext=pdf), not op=different
2396
2396
  assertEqual(matches.length, 2, 'Should find 2 matches');
2397
2397
  // Sorted by specificity descending: ext=pdf first (more specific)
2398
2398
  assert(matches[0].specificity() >= matches[1].specificity(), 'First match should be more specific');
@@ -2402,16 +2402,16 @@ function test1305_findAllMatches() {
2402
2402
  // TEST1306: CapMatcher::are_compatible detects bidirectional overlap
2403
2403
  function test1306_areCompatible() {
2404
2404
  const caps1 = [
2405
- CapUrn.fromString('cap:in="media:void";op=test;out="media:void"'),
2405
+ CapUrn.fromString('cap:in="media:void";test;out="media:void"'),
2406
2406
  ];
2407
2407
  const caps2 = [
2408
- CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"'),
2408
+ CapUrn.fromString('cap:in="media:void";test;ext=pdf;out="media:void"'),
2409
2409
  ];
2410
2410
  const caps3 = [
2411
- CapUrn.fromString('cap:in="media:void";op=different;out="media:void"'),
2411
+ CapUrn.fromString('cap:in="media:void";different;out="media:void"'),
2412
2412
  ];
2413
2413
 
2414
- // caps1 (op=test) accepts caps2 (op=test;ext=pdf) -> compatible
2414
+ // caps1 (op=test) accepts caps2 (test;ext=pdf) -> compatible
2415
2415
  assert(CapMatcher.areCompatible(caps1, caps2), 'caps1 and caps2 should be compatible');
2416
2416
 
2417
2417
  // caps1 (op=test) vs caps3 (op=different) -> not compatible
@@ -2426,7 +2426,7 @@ function test1306_areCompatible() {
2426
2426
 
2427
2427
  // TEST1307: with_tag silently ignores in/out keys
2428
2428
  function test1307_withTagIgnoresInOut() {
2429
- const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
2429
+ const cap = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
2430
2430
  // Attempting to set in/out via withTag is silently ignored
2431
2431
  const same = cap.withTag('in', 'media:');
2432
2432
  assertEqual(same.getInSpec(), 'media:void', 'withTag must not change in_spec');
@@ -2437,7 +2437,7 @@ function test1307_withTagIgnoresInOut() {
2437
2437
 
2438
2438
  // TEST1294: RULE11 - void-input cap with stdin source rejected
2439
2439
  function test1294_rule11VoidInputWithStdinRejected() {
2440
- const urn = CapUrn.fromString('cap:in="media:void";op=test;out="media:string"');
2440
+ const urn = CapUrn.fromString('cap:in="media:void";test;out="media:string"');
2441
2441
  const cap = new Cap(urn, 'Test', 'test-cmd');
2442
2442
  const stdinSource = ArgSource.fromJSON({ stdin: 'media:string' });
2443
2443
  cap.args = [new CapArg('media:string', true, [stdinSource])];
@@ -2452,7 +2452,7 @@ function test1294_rule11VoidInputWithStdinRejected() {
2452
2452
 
2453
2453
  // TEST1295: RULE11 - non-void-input cap without stdin source rejected
2454
2454
  function test1295_rule11NonVoidInputWithoutStdinRejected() {
2455
- const urn = CapUrn.fromString('cap:in="media:string";op=test;out="media:string"');
2455
+ const urn = CapUrn.fromString('cap:in="media:string";test;out="media:string"');
2456
2456
  const cap = new Cap(urn, 'Test', 'test-cmd');
2457
2457
  const posSource = ArgSource.fromJSON({ cli_flag: '--name' });
2458
2458
  cap.args = [new CapArg('media:string', true, [posSource])];
@@ -2467,7 +2467,7 @@ function test1295_rule11NonVoidInputWithoutStdinRejected() {
2467
2467
 
2468
2468
  // TEST1296: RULE11 - void-input cap with only cli_flag sources passes
2469
2469
  function test1296_rule11VoidInputCliFlagOnly() {
2470
- const urn = CapUrn.fromString('cap:in="media:void";op=test;out="media:string"');
2470
+ const urn = CapUrn.fromString('cap:in="media:void";test;out="media:string"');
2471
2471
  const cap = new Cap(urn, 'Test', 'test-cmd');
2472
2472
  const flagSource = ArgSource.fromJSON({ cli_flag: '--name' });
2473
2473
  cap.args = [new CapArg('media:string', true, [flagSource])];
@@ -2477,7 +2477,7 @@ function test1296_rule11VoidInputCliFlagOnly() {
2477
2477
 
2478
2478
  // TEST1297: RULE11 - non-void-input cap with stdin source passes
2479
2479
  function test1297_rule11NonVoidInputWithStdin() {
2480
- const urn = CapUrn.fromString('cap:in="media:string";op=test;out="media:string"');
2480
+ const urn = CapUrn.fromString('cap:in="media:string";test;out="media:string"');
2481
2481
  const cap = new Cap(urn, 'Test', 'test-cmd');
2482
2482
  const stdinSource = ArgSource.fromJSON({ stdin: 'media:string' });
2483
2483
  cap.args = [new CapArg('media:string', true, [stdinSource])];
@@ -2602,7 +2602,7 @@ function test651_identityFormsEquivalent() {
2602
2602
  // TEST653: Identity (no tags) does not match specific requests via routing
2603
2603
  function test653_identityRoutingIsolation() {
2604
2604
  const identity = CapUrn.fromString('cap:in=*;out=*');
2605
- const specificRequest = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
2605
+ const specificRequest = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
2606
2606
 
2607
2607
  // Identity has specificity 0 (no tags, wildcard directions)
2608
2608
  assertEqual(identity.specificity(), 0, 'Identity specificity should be 0');
@@ -2612,7 +2612,7 @@ function test653_identityRoutingIsolation() {
2612
2612
  'Specific request should have higher specificity than identity');
2613
2613
 
2614
2614
  // CapMatcher should prefer specific over identity
2615
- const specificCap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
2615
+ const specificCap = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
2616
2616
  const best = CapMatcher.findBestMatch([identity, specificCap], specificRequest);
2617
2617
  assert(best !== null, 'Should find a match');
2618
2618
  assertEqual(best.getTag('op'), 'test', 'CapMatcher should prefer specific cap over identity');
@@ -2634,7 +2634,7 @@ function testMachine_whitespaceOnly() {
2634
2634
 
2635
2635
  function testMachine_headerOnlyNoWirings() {
2636
2636
  assertThrowsWithCode(
2637
- () => Machine.fromString('[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]'),
2637
+ () => Machine.fromString('[extract cap:in="media:pdf";extract;out="media:txt;textable"]'),
2638
2638
  MachineSyntaxErrorCodes.NO_EDGES
2639
2639
  );
2640
2640
  }
@@ -2642,8 +2642,8 @@ function testMachine_headerOnlyNoWirings() {
2642
2642
  function testMachine_duplicateAlias() {
2643
2643
  assertThrowsWithCode(
2644
2644
  () => Machine.fromString(
2645
- '[ex cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
2646
- '[ex cap:in="media:pdf";op=summarize;out="media:txt;textable"]' +
2645
+ '[ex cap:in="media:pdf";extract;out="media:txt;textable"]' +
2646
+ '[ex cap:in="media:pdf";summarize;out="media:txt;textable"]' +
2647
2647
  '[a -> ex -> b]'
2648
2648
  ),
2649
2649
  MachineSyntaxErrorCodes.DUPLICATE_ALIAS
@@ -2652,7 +2652,7 @@ function testMachine_duplicateAlias() {
2652
2652
 
2653
2653
  function testMachine_simpleLinearChain() {
2654
2654
  const g = Machine.fromString(
2655
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
2655
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
2656
2656
  '[doc -> extract -> text]'
2657
2657
  );
2658
2658
  assertEqual(g.edgeCount(), 1);
@@ -2667,8 +2667,8 @@ function testMachine_simpleLinearChain() {
2667
2667
 
2668
2668
  function testMachine_twoStepChain() {
2669
2669
  const g = Machine.fromString(
2670
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
2671
- '[embed cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"]' +
2670
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
2671
+ '[embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"]' +
2672
2672
  '[doc -> extract -> text]' +
2673
2673
  '[text -> embed -> vectors]'
2674
2674
  );
@@ -2681,9 +2681,9 @@ function testMachine_twoStepChain() {
2681
2681
 
2682
2682
  function testMachine_fanOut() {
2683
2683
  const g = Machine.fromString(
2684
- '[meta cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;record;textable"]' +
2685
- '[outline cap:in="media:pdf";op=extract_outline;out="media:document-outline;record;textable"]' +
2686
- '[thumb cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"]' +
2684
+ '[meta cap:in="media:pdf";extract-metadata;out="media:file-metadata;record;textable"]' +
2685
+ '[outline cap:in="media:pdf";extract-outline;out="media:document-outline;record;textable"]' +
2686
+ '[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]' +
2687
2687
  '[doc -> meta -> metadata]' +
2688
2688
  '[doc -> outline -> outline_data]' +
2689
2689
  '[doc -> thumb -> thumbnail]'
@@ -2698,9 +2698,9 @@ function testMachine_fanOut() {
2698
2698
 
2699
2699
  function testMachine_fanInSecondaryAssignedByPriorWiring() {
2700
2700
  const g = Machine.fromString(
2701
- '[thumb cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"]' +
2702
- '[model_dl cap:in="media:model-spec;textable";op=download;out="media:model-spec;textable"]' +
2703
- '[describe cap:in="media:image;png";op=describe_image;out="media:image-description;textable"]' +
2701
+ '[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]' +
2702
+ '[model_dl cap:in="media:model-spec;textable";download;out="media:model-spec;textable"]' +
2703
+ '[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]' +
2704
2704
  '[doc -> thumb -> thumbnail]' +
2705
2705
  '[spec_input -> model_dl -> model_spec]' +
2706
2706
  '[(thumbnail, model_spec) -> describe -> description]'
@@ -2711,7 +2711,7 @@ function testMachine_fanInSecondaryAssignedByPriorWiring() {
2711
2711
 
2712
2712
  function testMachine_fanInSecondaryUnassignedGetsWildcard() {
2713
2713
  const g = Machine.fromString(
2714
- '[describe cap:in="media:image;png";op=describe_image;out="media:image-description;textable"]\n' +
2714
+ '[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]\n' +
2715
2715
  '[(thumbnail, model_spec) -> describe -> description]'
2716
2716
  );
2717
2717
  assertEqual(g.edges().length, 1);
@@ -2722,7 +2722,7 @@ function testMachine_fanInSecondaryUnassignedGetsWildcard() {
2722
2722
 
2723
2723
  function testMachine_loopEdge() {
2724
2724
  const g = Machine.fromString(
2725
- '[p2t cap:in="media:disbound-page;textable";op=page_to_text;out="media:txt;textable"]' +
2725
+ '[p2t cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"]' +
2726
2726
  '[pages -> LOOP p2t -> texts]'
2727
2727
  );
2728
2728
  assertEqual(g.edgeCount(), 1);
@@ -2739,7 +2739,7 @@ function testMachine_undefinedAliasFails() {
2739
2739
  function testMachine_nodeAliasCollision() {
2740
2740
  assertThrowsWithCode(
2741
2741
  () => Machine.fromString(
2742
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
2742
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
2743
2743
  '[extract -> extract -> text]'
2744
2744
  ),
2745
2745
  MachineSyntaxErrorCodes.NODE_ALIAS_COLLISION
@@ -2749,8 +2749,8 @@ function testMachine_nodeAliasCollision() {
2749
2749
  function testMachine_conflictingMediaTypesFail() {
2750
2750
  assertThrowsWithCode(
2751
2751
  () => Machine.fromString(
2752
- '[cap1 cap:in="media:txt;textable";op=a;out="media:pdf"]' +
2753
- '[cap2 cap:in="media:audio;wav";op=b;out="media:txt;textable"]' +
2752
+ '[cap1 cap:in="media:txt;textable";a;out="media:pdf"]' +
2753
+ '[cap2 cap:in="media:audio;wav";b;out="media:txt;textable"]' +
2754
2754
  '[src -> cap1 -> mid]' +
2755
2755
  '[mid -> cap2 -> dst]'
2756
2756
  ),
@@ -2760,8 +2760,8 @@ function testMachine_conflictingMediaTypesFail() {
2760
2760
 
2761
2761
  function testMachine_multilineFormat() {
2762
2762
  const g = Machine.fromString(
2763
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]\n' +
2764
- '[embed cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"]\n' +
2763
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n' +
2764
+ '[embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"]\n' +
2765
2765
  '[doc -> extract -> text]\n' +
2766
2766
  '[text -> embed -> vectors]\n'
2767
2767
  );
@@ -2770,11 +2770,11 @@ function testMachine_multilineFormat() {
2770
2770
 
2771
2771
  function testMachine_differentAliasesSameGraph() {
2772
2772
  const g1 = Machine.fromString(
2773
- '[ex cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
2773
+ '[ex cap:in="media:pdf";extract;out="media:txt;textable"]' +
2774
2774
  '[a -> ex -> b]'
2775
2775
  );
2776
2776
  const g2 = Machine.fromString(
2777
- '[xt cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
2777
+ '[xt cap:in="media:pdf";extract;out="media:txt;textable"]' +
2778
2778
  '[x -> xt -> y]'
2779
2779
  );
2780
2780
  assert(g1.isEquivalent(g2), 'Different aliases should produce equivalent graphs');
@@ -2798,7 +2798,7 @@ function testMachine_unterminatedBracketFails() {
2798
2798
 
2799
2799
  function testMachine_lineBasedSimpleChain() {
2800
2800
  const g = Machine.fromString(
2801
- 'extract cap:in="media:pdf";op=extract;out="media:txt;textable"\n' +
2801
+ 'extract cap:in="media:pdf";extract;out="media:txt;textable"\n' +
2802
2802
  'doc -> extract -> text'
2803
2803
  );
2804
2804
  assertEqual(g.edgeCount(), 1);
@@ -2811,8 +2811,8 @@ function testMachine_lineBasedSimpleChain() {
2811
2811
 
2812
2812
  function testMachine_lineBasedTwoStepChain() {
2813
2813
  const g = Machine.fromString(
2814
- 'extract cap:in="media:pdf";op=extract;out="media:txt;textable"\n' +
2815
- 'embed cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"\n' +
2814
+ 'extract cap:in="media:pdf";extract;out="media:txt;textable"\n' +
2815
+ 'embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"\n' +
2816
2816
  'doc -> extract -> text\n' +
2817
2817
  'text -> embed -> vectors'
2818
2818
  );
@@ -2821,7 +2821,7 @@ function testMachine_lineBasedTwoStepChain() {
2821
2821
 
2822
2822
  function testMachine_lineBasedLoop() {
2823
2823
  const g = Machine.fromString(
2824
- 'p2t cap:in="media:disbound-page;textable";op=page_to_text;out="media:txt;textable"\n' +
2824
+ 'p2t cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"\n' +
2825
2825
  'pages -> LOOP p2t -> texts'
2826
2826
  );
2827
2827
  assertEqual(g.edgeCount(), 1);
@@ -2830,9 +2830,9 @@ function testMachine_lineBasedLoop() {
2830
2830
 
2831
2831
  function testMachine_lineBasedFanIn() {
2832
2832
  const g = Machine.fromString(
2833
- 'thumb cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"\n' +
2834
- 'model_dl cap:in="media:model-spec;textable";op=download;out="media:model-spec;textable"\n' +
2835
- 'describe cap:in="media:image;png";op=describe_image;out="media:image-description;textable"\n' +
2833
+ 'thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"\n' +
2834
+ 'model_dl cap:in="media:model-spec;textable";download;out="media:model-spec;textable"\n' +
2835
+ 'describe cap:in="media:image;png";describe-image;out="media:image-description;textable"\n' +
2836
2836
  'doc -> thumb -> thumbnail\n' +
2837
2837
  'spec_input -> model_dl -> model_spec\n' +
2838
2838
  '(thumbnail, model_spec) -> describe -> description'
@@ -2843,7 +2843,7 @@ function testMachine_lineBasedFanIn() {
2843
2843
 
2844
2844
  function testMachine_mixedBracketedAndLineBased() {
2845
2845
  const g = Machine.fromString(
2846
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]\n' +
2846
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n' +
2847
2847
  'doc -> extract -> text'
2848
2848
  );
2849
2849
  assertEqual(g.edgeCount(), 1);
@@ -2851,11 +2851,11 @@ function testMachine_mixedBracketedAndLineBased() {
2851
2851
 
2852
2852
  function testMachine_lineBasedEquivalentToBracketed() {
2853
2853
  const g1 = Machine.fromString(
2854
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
2854
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
2855
2855
  '[doc -> extract -> text]'
2856
2856
  );
2857
2857
  const g2 = Machine.fromString(
2858
- 'extract cap:in="media:pdf";op=extract;out="media:txt;textable"\n' +
2858
+ 'extract cap:in="media:pdf";extract;out="media:txt;textable"\n' +
2859
2859
  'doc -> extract -> text'
2860
2860
  );
2861
2861
  assert(g1.isEquivalent(g2), 'Line-based and bracketed must produce equivalent graphs');
@@ -2865,7 +2865,7 @@ function testMachine_lineBasedFormatSerialization() {
2865
2865
  const g = new Machine([
2866
2866
  new MachineEdge(
2867
2867
  [MediaUrn.fromString('media:pdf')],
2868
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
2868
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
2869
2869
  MediaUrn.fromString('media:txt;textable'),
2870
2870
  false
2871
2871
  ),
@@ -2885,13 +2885,13 @@ function testMachine_lineBasedAndBracketedParseSameGraph() {
2885
2885
  const g = new Machine([
2886
2886
  new MachineEdge(
2887
2887
  [MediaUrn.fromString('media:pdf')],
2888
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
2888
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
2889
2889
  MediaUrn.fromString('media:txt;textable'),
2890
2890
  false
2891
2891
  ),
2892
2892
  new MachineEdge(
2893
2893
  [MediaUrn.fromString('media:txt;textable')],
2894
- CapUrn.fromString('cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"'),
2894
+ CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
2895
2895
  MediaUrn.fromString('media:embedding-vector;record;textable'),
2896
2896
  false
2897
2897
  ),
@@ -2910,13 +2910,13 @@ function testMachine_lineBasedAndBracketedParseSameGraph() {
2910
2910
  function testMachine_edgeEquivalenceSameUrns() {
2911
2911
  const e1 = new MachineEdge(
2912
2912
  [MediaUrn.fromString('media:pdf')],
2913
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
2913
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
2914
2914
  MediaUrn.fromString('media:txt;textable'),
2915
2915
  false
2916
2916
  );
2917
2917
  const e2 = new MachineEdge(
2918
2918
  [MediaUrn.fromString('media:pdf')],
2919
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
2919
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
2920
2920
  MediaUrn.fromString('media:txt;textable'),
2921
2921
  false
2922
2922
  );
@@ -2926,13 +2926,13 @@ function testMachine_edgeEquivalenceSameUrns() {
2926
2926
  function testMachine_edgeEquivalenceDifferentCapUrns() {
2927
2927
  const e1 = new MachineEdge(
2928
2928
  [MediaUrn.fromString('media:pdf')],
2929
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
2929
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
2930
2930
  MediaUrn.fromString('media:txt;textable'),
2931
2931
  false
2932
2932
  );
2933
2933
  const e2 = new MachineEdge(
2934
2934
  [MediaUrn.fromString('media:pdf')],
2935
- CapUrn.fromString('cap:in="media:pdf";op=summarize;out="media:txt;textable"'),
2935
+ CapUrn.fromString('cap:in="media:pdf";summarize;out="media:txt;textable"'),
2936
2936
  MediaUrn.fromString('media:txt;textable'),
2937
2937
  false
2938
2938
  );
@@ -2942,13 +2942,13 @@ function testMachine_edgeEquivalenceDifferentCapUrns() {
2942
2942
  function testMachine_edgeEquivalenceDifferentTargets() {
2943
2943
  const e1 = new MachineEdge(
2944
2944
  [MediaUrn.fromString('media:pdf')],
2945
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
2945
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
2946
2946
  MediaUrn.fromString('media:txt;textable'),
2947
2947
  false
2948
2948
  );
2949
2949
  const e2 = new MachineEdge(
2950
2950
  [MediaUrn.fromString('media:pdf')],
2951
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
2951
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
2952
2952
  MediaUrn.fromString('media:json;record;textable'),
2953
2953
  false
2954
2954
  );
@@ -2958,13 +2958,13 @@ function testMachine_edgeEquivalenceDifferentTargets() {
2958
2958
  function testMachine_edgeEquivalenceDifferentLoopFlag() {
2959
2959
  const e1 = new MachineEdge(
2960
2960
  [MediaUrn.fromString('media:pdf')],
2961
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
2961
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
2962
2962
  MediaUrn.fromString('media:txt;textable'),
2963
2963
  false
2964
2964
  );
2965
2965
  const e2 = new MachineEdge(
2966
2966
  [MediaUrn.fromString('media:pdf')],
2967
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
2967
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
2968
2968
  MediaUrn.fromString('media:txt;textable'),
2969
2969
  true
2970
2970
  );
@@ -2974,13 +2974,13 @@ function testMachine_edgeEquivalenceDifferentLoopFlag() {
2974
2974
  function testMachine_edgeEquivalenceSourceOrderIndependent() {
2975
2975
  const e1 = new MachineEdge(
2976
2976
  [MediaUrn.fromString('media:txt;textable'), MediaUrn.fromString('media:model-spec;textable')],
2977
- CapUrn.fromString('cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"'),
2977
+ CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
2978
2978
  MediaUrn.fromString('media:embedding-vector;record;textable'),
2979
2979
  false
2980
2980
  );
2981
2981
  const e2 = new MachineEdge(
2982
2982
  [MediaUrn.fromString('media:model-spec;textable'), MediaUrn.fromString('media:txt;textable')],
2983
- CapUrn.fromString('cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"'),
2983
+ CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
2984
2984
  MediaUrn.fromString('media:embedding-vector;record;textable'),
2985
2985
  false
2986
2986
  );
@@ -2990,13 +2990,13 @@ function testMachine_edgeEquivalenceSourceOrderIndependent() {
2990
2990
  function testMachine_edgeEquivalenceDifferentSourceCount() {
2991
2991
  const e1 = new MachineEdge(
2992
2992
  [MediaUrn.fromString('media:txt;textable')],
2993
- CapUrn.fromString('cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"'),
2993
+ CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
2994
2994
  MediaUrn.fromString('media:embedding-vector;record;textable'),
2995
2995
  false
2996
2996
  );
2997
2997
  const e2 = new MachineEdge(
2998
2998
  [MediaUrn.fromString('media:txt;textable'), MediaUrn.fromString('media:model-spec;textable')],
2999
- CapUrn.fromString('cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"'),
2999
+ CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
3000
3000
  MediaUrn.fromString('media:embedding-vector;record;textable'),
3001
3001
  false
3002
3002
  );
@@ -3008,12 +3008,12 @@ function testMachine_graphEquivalenceSameEdges() {
3008
3008
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3009
3009
  );
3010
3010
  const g1 = new Machine([
3011
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3012
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3011
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3012
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3013
3013
  ]);
3014
3014
  const g2 = new Machine([
3015
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3016
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3015
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3016
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3017
3017
  ]);
3018
3018
  assert(g1.isEquivalent(g2), 'Same edges should be equivalent');
3019
3019
  }
@@ -3023,12 +3023,12 @@ function testMachine_graphEquivalenceReorderedEdges() {
3023
3023
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3024
3024
  );
3025
3025
  const g1 = new Machine([
3026
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3027
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3026
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3027
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3028
3028
  ]);
3029
3029
  const g2 = new Machine([
3030
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3031
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3030
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3031
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3032
3032
  ]);
3033
3033
  assert(g1.isEquivalent(g2), 'Reordered edges should still be equivalent');
3034
3034
  }
@@ -3038,11 +3038,11 @@ function testMachine_graphNotEquivalentDifferentEdgeCount() {
3038
3038
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3039
3039
  );
3040
3040
  const g1 = new Machine([
3041
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3041
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3042
3042
  ]);
3043
3043
  const g2 = new Machine([
3044
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3045
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3044
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3045
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3046
3046
  ]);
3047
3047
  assert(!g1.isEquivalent(g2), 'Different edge counts should not be equivalent');
3048
3048
  }
@@ -3052,10 +3052,10 @@ function testMachine_graphNotEquivalentDifferentCap() {
3052
3052
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3053
3053
  );
3054
3054
  const g1 = new Machine([
3055
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3055
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3056
3056
  ]);
3057
3057
  const g2 = new Machine([
3058
- mkEdge('media:pdf', 'cap:in="media:pdf";op=summarize;out="media:txt;textable"', 'media:txt;textable'),
3058
+ mkEdge('media:pdf', 'cap:in="media:pdf";summarize;out="media:txt;textable"', 'media:txt;textable'),
3059
3059
  ]);
3060
3060
  assert(!g1.isEquivalent(g2), 'Different caps should not be equivalent');
3061
3061
  }
@@ -3077,8 +3077,8 @@ function testMachine_rootSourcesLinearChain() {
3077
3077
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3078
3078
  );
3079
3079
  const g = new Machine([
3080
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3081
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3080
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3081
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3082
3082
  ]);
3083
3083
  const roots = g.rootSources();
3084
3084
  assertEqual(roots.length, 1);
@@ -3091,8 +3091,8 @@ function testMachine_leafTargetsLinearChain() {
3091
3091
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3092
3092
  );
3093
3093
  const g = new Machine([
3094
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3095
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3094
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3095
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3096
3096
  ]);
3097
3097
  const leaves = g.leafTargets();
3098
3098
  assertEqual(leaves.length, 1);
@@ -3103,7 +3103,7 @@ function testMachine_leafTargetsLinearChain() {
3103
3103
  function testMachine_rootSourcesFanIn() {
3104
3104
  const e = new MachineEdge(
3105
3105
  [MediaUrn.fromString('media:txt;textable'), MediaUrn.fromString('media:model-spec;textable')],
3106
- CapUrn.fromString('cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"'),
3106
+ CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
3107
3107
  MediaUrn.fromString('media:embedding-vector;record;textable'),
3108
3108
  false
3109
3109
  );
@@ -3115,7 +3115,7 @@ function testMachine_rootSourcesFanIn() {
3115
3115
  function testMachine_displayEdge() {
3116
3116
  const e = new MachineEdge(
3117
3117
  [MediaUrn.fromString('media:pdf')],
3118
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
3118
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
3119
3119
  MediaUrn.fromString('media:txt;textable'),
3120
3120
  false
3121
3121
  );
@@ -3126,7 +3126,7 @@ function testMachine_displayEdge() {
3126
3126
  function testMachine_displayGraph() {
3127
3127
  const e = new MachineEdge(
3128
3128
  [MediaUrn.fromString('media:pdf')],
3129
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
3129
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
3130
3130
  MediaUrn.fromString('media:txt;textable'),
3131
3131
  false
3132
3132
  );
@@ -3139,7 +3139,7 @@ function testMachine_displayGraph() {
3139
3139
  function testMachine_serializeSingleEdge() {
3140
3140
  const g = new Machine([new MachineEdge(
3141
3141
  [MediaUrn.fromString('media:pdf')],
3142
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
3142
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
3143
3143
  MediaUrn.fromString('media:txt;textable'),
3144
3144
  false
3145
3145
  )]);
@@ -3155,8 +3155,8 @@ function testMachine_serializeTwoEdgeChain() {
3155
3155
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3156
3156
  );
3157
3157
  const g = new Machine([
3158
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3159
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3158
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3159
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3160
3160
  ]);
3161
3161
  const notation = g.toMachineNotation();
3162
3162
  const bracketCount = (notation.match(/\[/g) || []).length;
@@ -3170,7 +3170,7 @@ function testMachine_serializeEmptyGraph() {
3170
3170
  function testMachine_roundtripSingleEdge() {
3171
3171
  const original = new Machine([new MachineEdge(
3172
3172
  [MediaUrn.fromString('media:pdf')],
3173
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
3173
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
3174
3174
  MediaUrn.fromString('media:txt;textable'),
3175
3175
  false
3176
3176
  )]);
@@ -3185,8 +3185,8 @@ function testMachine_roundtripTwoEdgeChain() {
3185
3185
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3186
3186
  );
3187
3187
  const original = new Machine([
3188
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3189
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3188
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3189
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3190
3190
  ]);
3191
3191
  const notation = original.toMachineNotation();
3192
3192
  const reparsed = Machine.fromString(notation);
@@ -3199,9 +3199,9 @@ function testMachine_roundtripFanOut() {
3199
3199
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3200
3200
  );
3201
3201
  const original = new Machine([
3202
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;record;textable"', 'media:file-metadata;record;textable'),
3203
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract_outline;out="media:document-outline;record;textable"', 'media:document-outline;record;textable'),
3204
- mkEdge('media:pdf', 'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"', 'media:image;png;thumbnail'),
3202
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract-metadata;out="media:file-metadata;record;textable"', 'media:file-metadata;record;textable'),
3203
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract-outline;out="media:document-outline;record;textable"', 'media:document-outline;record;textable'),
3204
+ mkEdge('media:pdf', 'cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"', 'media:image;png;thumbnail'),
3205
3205
  ]);
3206
3206
  const notation = original.toMachineNotation();
3207
3207
  const reparsed = Machine.fromString(notation);
@@ -3212,7 +3212,7 @@ function testMachine_roundtripFanOut() {
3212
3212
  function testMachine_roundtripLoopEdge() {
3213
3213
  const original = new Machine([new MachineEdge(
3214
3214
  [MediaUrn.fromString('media:disbound-page;textable')],
3215
- CapUrn.fromString('cap:in="media:disbound-page;textable";op=page_to_text;out="media:txt;textable"'),
3215
+ CapUrn.fromString('cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"'),
3216
3216
  MediaUrn.fromString('media:txt;textable'),
3217
3217
  true
3218
3218
  )]);
@@ -3227,8 +3227,8 @@ function testMachine_serializationIsDeterministic() {
3227
3227
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3228
3228
  );
3229
3229
  const g = new Machine([
3230
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3231
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3230
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3231
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3232
3232
  ]);
3233
3233
  const n1 = g.toMachineNotation();
3234
3234
  const n2 = g.toMachineNotation();
@@ -3240,12 +3240,12 @@ function testMachine_reorderedEdgesProduceSameNotation() {
3240
3240
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3241
3241
  );
3242
3242
  const g1 = new Machine([
3243
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3244
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3243
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3244
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3245
3245
  ]);
3246
3246
  const g2 = new Machine([
3247
- mkEdge('media:txt;textable', 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3248
- mkEdge('media:pdf', 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable'),
3247
+ mkEdge('media:txt;textable', 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable'),
3248
+ mkEdge('media:pdf', 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable'),
3249
3249
  ]);
3250
3250
  assertEqual(g1.toMachineNotation(), g2.toMachineNotation(),
3251
3251
  'Same graph with reordered edges must produce identical notation');
@@ -3254,7 +3254,7 @@ function testMachine_reorderedEdgesProduceSameNotation() {
3254
3254
  function testMachine_multilineSerializeFormat() {
3255
3255
  const g = new Machine([new MachineEdge(
3256
3256
  [MediaUrn.fromString('media:pdf')],
3257
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
3257
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
3258
3258
  MediaUrn.fromString('media:txt;textable'),
3259
3259
  false
3260
3260
  )]);
@@ -3268,7 +3268,7 @@ function testMachine_multilineSerializeFormat() {
3268
3268
  function testMachine_aliasFromOpTag() {
3269
3269
  const g = new Machine([new MachineEdge(
3270
3270
  [MediaUrn.fromString('media:pdf')],
3271
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
3271
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
3272
3272
  MediaUrn.fromString('media:txt;textable'),
3273
3273
  false
3274
3274
  )]);
@@ -3291,13 +3291,13 @@ function testMachine_duplicateOpTagsDisambiguated() {
3291
3291
  const g = new Machine([
3292
3292
  new MachineEdge(
3293
3293
  [MediaUrn.fromString('media:pdf')],
3294
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"'),
3294
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
3295
3295
  MediaUrn.fromString('media:txt;textable'),
3296
3296
  false
3297
3297
  ),
3298
3298
  new MachineEdge(
3299
3299
  [MediaUrn.fromString('media:pdf')],
3300
- CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:json;record;textable"'),
3300
+ CapUrn.fromString('cap:in="media:pdf";extract;out="media:json;record;textable"'),
3301
3301
  MediaUrn.fromString('media:json;record;textable'),
3302
3302
  false
3303
3303
  ),
@@ -3313,7 +3313,7 @@ function testMachine_builderSingleEdge() {
3313
3313
  const builder = new MachineBuilder();
3314
3314
  builder.addEdge(
3315
3315
  ['media:pdf'],
3316
- 'cap:in="media:pdf";op=extract;out="media:txt;textable"',
3316
+ 'cap:in="media:pdf";extract;out="media:txt;textable"',
3317
3317
  'media:txt;textable'
3318
3318
  );
3319
3319
  const g = builder.build();
@@ -3325,7 +3325,7 @@ function testMachine_builderWithLoop() {
3325
3325
  const builder = new MachineBuilder();
3326
3326
  builder.addEdge(
3327
3327
  ['media:disbound-page;textable'],
3328
- 'cap:in="media:disbound-page;textable";op=page_to_text;out="media:txt;textable"',
3328
+ 'cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"',
3329
3329
  'media:txt;textable',
3330
3330
  true
3331
3331
  );
@@ -3335,19 +3335,19 @@ function testMachine_builderWithLoop() {
3335
3335
 
3336
3336
  function testMachine_builderChaining() {
3337
3337
  const g = new MachineBuilder()
3338
- .addEdge(['media:pdf'], 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable')
3339
- .addEdge(['media:txt;textable'], 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable')
3338
+ .addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable')
3339
+ .addEdge(['media:txt;textable'], 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable')
3340
3340
  .build();
3341
3341
  assertEqual(g.edgeCount(), 2);
3342
3342
  }
3343
3343
 
3344
3344
  function testMachine_builderEquivalentToParsed() {
3345
3345
  const parsed = Machine.fromString(
3346
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
3346
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
3347
3347
  '[doc -> extract -> text]'
3348
3348
  );
3349
3349
  const built = new MachineBuilder()
3350
- .addEdge(['media:pdf'], 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable')
3350
+ .addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable')
3351
3351
  .build();
3352
3352
  assert(parsed.isEquivalent(built),
3353
3353
  'Builder-constructed graph should be equivalent to parsed graph');
@@ -3355,8 +3355,8 @@ function testMachine_builderEquivalentToParsed() {
3355
3355
 
3356
3356
  function testMachine_builderRoundTrip() {
3357
3357
  const built = new MachineBuilder()
3358
- .addEdge(['media:pdf'], 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'media:txt;textable')
3359
- .addEdge(['media:txt;textable'], 'cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable')
3358
+ .addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable')
3359
+ .addEdge(['media:txt;textable'], 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable')
3360
3360
  .build();
3361
3361
  const notation = built.toMachineNotation();
3362
3362
  const reparsed = Machine.fromString(notation);
@@ -3366,29 +3366,29 @@ function testMachine_builderRoundTrip() {
3366
3366
  // --- CapUrn.isEquivalent/isComparable tests ---
3367
3367
 
3368
3368
  function testMachine_capUrnIsEquivalent() {
3369
- const a = CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"');
3370
- const b = CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"');
3369
+ const a = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3370
+ const b = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3371
3371
  assert(a.isEquivalent(b), 'Same cap URNs should be equivalent');
3372
- const c = CapUrn.fromString('cap:in="media:pdf";op=summarize;out="media:txt;textable"');
3372
+ const c = CapUrn.fromString('cap:in="media:pdf";summarize;out="media:txt;textable"');
3373
3373
  assert(!a.isEquivalent(c), 'Different cap URNs should not be equivalent');
3374
3374
  }
3375
3375
 
3376
3376
  function testMachine_capUrnIsComparable() {
3377
3377
  const general = CapUrn.fromString('cap:in="media:pdf";out="media:txt;textable"');
3378
- const specific = CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"');
3378
+ const specific = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3379
3379
  assert(general.isComparable(specific), 'General should be comparable to specific');
3380
3380
  assert(specific.isComparable(general), 'isComparable should be symmetric');
3381
3381
  }
3382
3382
 
3383
3383
  function testMachine_capUrnInMediaUrn() {
3384
- const cap = CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"');
3384
+ const cap = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3385
3385
  const inUrn = cap.inMediaUrn();
3386
3386
  assert(inUrn instanceof MediaUrn, 'inMediaUrn should return MediaUrn');
3387
3387
  assert(inUrn.isEquivalent(MediaUrn.fromString('media:pdf')), 'inMediaUrn should be media:pdf');
3388
3388
  }
3389
3389
 
3390
3390
  function testMachine_capUrnOutMediaUrn() {
3391
- const cap = CapUrn.fromString('cap:in="media:pdf";op=extract;out="media:txt;textable"');
3391
+ const cap = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3392
3392
  const outUrn = cap.outMediaUrn();
3393
3393
  assert(outUrn instanceof MediaUrn, 'outMediaUrn should return MediaUrn');
3394
3394
  assert(outUrn.isEquivalent(MediaUrn.fromString('media:txt;textable')), 'outMediaUrn should be media:txt;textable');
@@ -3418,7 +3418,7 @@ function testMachine_mediaUrnIsComparable() {
3418
3418
  // ============================================================================
3419
3419
 
3420
3420
  function testMachine_parseMachineWithAST_headerLocation() {
3421
- const input = '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"][doc -> extract -> text]';
3421
+ const input = '[extract cap:in="media:pdf";extract;out="media:txt;textable"][doc -> extract -> text]';
3422
3422
  const result = parseMachineWithAST(input);
3423
3423
  assert(result.statements.length === 2, 'Should have 2 statements');
3424
3424
  const stmt = result.statements[0];
@@ -3434,7 +3434,7 @@ function testMachine_parseMachineWithAST_headerLocation() {
3434
3434
  }
3435
3435
 
3436
3436
  function testMachine_parseMachineWithAST_wiringLocation() {
3437
- const input = '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]\n[doc -> extract -> text]';
3437
+ const input = '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n[doc -> extract -> text]';
3438
3438
  const result = parseMachineWithAST(input);
3439
3439
  assert(result.statements.length === 2, 'Should have 2 statements');
3440
3440
  const wiring = result.statements[1];
@@ -3448,7 +3448,7 @@ function testMachine_parseMachineWithAST_wiringLocation() {
3448
3448
  }
3449
3449
 
3450
3450
  function testMachine_parseMachineWithAST_multilinePositions() {
3451
- const input = '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]\n[doc -> extract -> text]';
3451
+ const input = '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n[doc -> extract -> text]';
3452
3452
  const result = parseMachineWithAST(input);
3453
3453
  const headerLoc = result.statements[0].location;
3454
3454
  const wiringLoc = result.statements[1].location;
@@ -3458,7 +3458,7 @@ function testMachine_parseMachineWithAST_multilinePositions() {
3458
3458
 
3459
3459
  function testMachine_parseMachineWithAST_fanInSourceLocations() {
3460
3460
  const input = [
3461
- '[describe cap:in="media:image;png";op=describe_image;out="media:image-description;textable"]',
3461
+ '[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]',
3462
3462
  '[(thumbnail, model_spec) -> describe -> description]'
3463
3463
  ].join('\n');
3464
3464
  const result = parseMachineWithAST(input);
@@ -3469,8 +3469,8 @@ function testMachine_parseMachineWithAST_fanInSourceLocations() {
3469
3469
 
3470
3470
  function testMachine_parseMachineWithAST_aliasMap() {
3471
3471
  const input = [
3472
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]',
3473
- '[embed cap:in="media:txt;textable";op=embed;out="media:embedding-vector;record;textable"]',
3472
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]',
3473
+ '[embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"]',
3474
3474
  '[doc -> extract -> text]',
3475
3475
  '[text -> embed -> vectors]',
3476
3476
  ].join('\n');
@@ -3487,7 +3487,7 @@ function testMachine_parseMachineWithAST_aliasMap() {
3487
3487
 
3488
3488
  function testMachine_parseMachineWithAST_nodeMedia() {
3489
3489
  const input = [
3490
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]',
3490
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]',
3491
3491
  '[doc -> extract -> text]',
3492
3492
  ].join('\n');
3493
3493
  const result = parseMachineWithAST(input);
@@ -3510,8 +3510,8 @@ function testMachine_errorLocation_parseError() {
3510
3510
  function testMachine_errorLocation_duplicateAlias() {
3511
3511
  try {
3512
3512
  parseMachine(
3513
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
3514
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
3513
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
3514
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
3515
3515
  '[doc -> extract -> text]'
3516
3516
  );
3517
3517
  throw new Error('Expected MachineSyntaxError');
@@ -3537,7 +3537,7 @@ function testMachine_errorLocation_undefinedAlias() {
3537
3537
 
3538
3538
  function testMachine_toMermaid_linearChain() {
3539
3539
  const machine = Machine.fromString(
3540
- '[extract cap:in="media:pdf";op=extract;out="media:txt;textable"]' +
3540
+ '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
3541
3541
  '[doc -> extract -> text]'
3542
3542
  );
3543
3543
  const mermaid = machine.toMermaid();
@@ -3552,7 +3552,7 @@ function testMachine_toMermaid_linearChain() {
3552
3552
 
3553
3553
  function testMachine_toMermaid_loopEdge() {
3554
3554
  const machine = Machine.fromString(
3555
- '[p2t cap:in="media:disbound-page;textable";op=page_to_text;out="media:txt;textable"]' +
3555
+ '[p2t cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"]' +
3556
3556
  '[pages -> LOOP p2t -> texts]'
3557
3557
  );
3558
3558
  const mermaid = machine.toMermaid();
@@ -3569,7 +3569,7 @@ function testMachine_toMermaid_emptyGraph() {
3569
3569
 
3570
3570
  function testMachine_toMermaid_fanIn() {
3571
3571
  const machine = Machine.fromString(
3572
- '[describe cap:in="media:image;png";op=describe_image;out="media:image-description;textable"]' +
3572
+ '[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]' +
3573
3573
  '[(thumbnail, model_spec) -> describe -> description]'
3574
3574
  );
3575
3575
  const mermaid = machine.toMermaid();
@@ -3580,8 +3580,8 @@ function testMachine_toMermaid_fanIn() {
3580
3580
 
3581
3581
  function testMachine_toMermaid_fanOut() {
3582
3582
  const input = [
3583
- '[meta cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;record;textable"]',
3584
- '[thumb cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"]',
3583
+ '[meta cap:in="media:pdf";extract-metadata;out="media:file-metadata;record;textable"]',
3584
+ '[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]',
3585
3585
  '[doc -> meta -> metadata]',
3586
3586
  '[doc -> thumb -> thumbnail]'
3587
3587
  ].join('');
@@ -3600,7 +3600,7 @@ function testMachine_toMermaid_fanOut() {
3600
3600
 
3601
3601
  function testMachine_capRegistryEntry_construction() {
3602
3602
  const entry = new CapRegistryEntry({
3603
- urn: 'cap:in="media:pdf";op=extract;out="media:txt;textable"',
3603
+ urn: 'cap:in="media:pdf";extract;out="media:txt;textable"',
3604
3604
  title: 'PDF Extractor',
3605
3605
  command: 'extract',
3606
3606
  cap_description: 'Extracts text from PDF',
@@ -3613,7 +3613,7 @@ function testMachine_capRegistryEntry_construction() {
3613
3613
  in_media_title: 'PDF Document',
3614
3614
  out_media_title: 'Text'
3615
3615
  });
3616
- assertEqual(entry.urn, 'cap:in="media:pdf";op=extract;out="media:txt;textable"', 'URN should match');
3616
+ assertEqual(entry.urn, 'cap:in="media:pdf";extract;out="media:txt;textable"', 'URN should match');
3617
3617
  assertEqual(entry.title, 'PDF Extractor', 'Title should match');
3618
3618
  assertEqual(entry.description, 'Extracts text from PDF', 'Description should match');
3619
3619
  assertEqual(entry.inSpec, 'media:pdf', 'inSpec should match');
@@ -3643,8 +3643,8 @@ function testMachine_capRegistryClient_construction() {
3643
3643
 
3644
3644
  function testMachine_capRegistryEntry_defaults() {
3645
3645
  // Verify that missing fields default gracefully
3646
- const entry = new CapRegistryEntry({ urn: 'cap:in=media:;op=test;out=media:' });
3647
- assertEqual(entry.urn, 'cap:in=media:;op=test;out=media:', 'URN should match');
3646
+ const entry = new CapRegistryEntry({ urn: 'cap:in=media:;test;out=media:' });
3647
+ assertEqual(entry.urn, 'cap:in=media:;test;out=media:', 'URN should match');
3648
3648
  assertEqual(entry.title, '', 'Title should default to empty');
3649
3649
  assertEqual(entry.description, '', 'Description should default to empty');
3650
3650
  assertEqual(entry.command, '', 'Command should default to empty');
@@ -3726,7 +3726,7 @@ function testRenderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
3726
3726
  // A naive implementation that reads args[0] would see `cli-only` (not a
3727
3727
  // sequence) and report 1→1 even though the stdin arg is a sequence.
3728
3728
  const cap = {
3729
- urn: 'cap:in="media:textable;list";op=transcribe;out="media:textable"',
3729
+ urn: 'cap:in="media:textable;list";transcribe;out="media:textable"',
3730
3730
  args: [
3731
3731
  {
3732
3732
  display_name: 'cli-only',
@@ -3748,7 +3748,7 @@ function testRenderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
3748
3748
  function testRenderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
3749
3749
  // No args and no output: both sides collapse to 1 (scalar default).
3750
3750
  // If a bug makes the function return "n" for missing data, this fails.
3751
- const cap = { urn: 'cap:in="media:";op=noop;out="media:"' };
3751
+ const cap = { urn: 'cap:in="media:";noop;out="media:"' };
3752
3752
  assertEqual(rendererCardinalityFromCap(cap), '1\u21921',
3753
3753
  'missing args/output must default to scalar on both sides');
3754
3754
  }
@@ -3756,7 +3756,7 @@ function testRenderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
3756
3756
  function testRenderer_cardinalityFromCap_outputOnlySequence() {
3757
3757
  // One scalar stdin arg, output is a sequence: expects 1→n.
3758
3758
  const cap = {
3759
- urn: 'cap:in="media:textable";op=generate;out="media:textable;list"',
3759
+ urn: 'cap:in="media:textable";generate;out="media:textable;list"',
3760
3760
  args: [{ sources: [{ stdin: {} }], is_sequence: false }],
3761
3761
  output: { is_sequence: true },
3762
3762
  };
@@ -3769,7 +3769,7 @@ function testRenderer_cardinalityFromCap_rejectsStringIsSequence() {
3769
3769
  // as booleans. "true" is a string, not a boolean — it must NOT be treated
3770
3770
  // as a sequence, because downstream renderers expect boolean semantics.
3771
3771
  const cap = {
3772
- urn: 'cap:in="media:";op=x;out="media:"',
3772
+ urn: 'cap:in="media:";x;out="media:"',
3773
3773
  args: [{ sources: [{ stdin: {} }], is_sequence: 'true' }],
3774
3774
  output: { is_sequence: 'true' },
3775
3775
  };
@@ -3815,7 +3815,7 @@ function testRenderer_canonicalMediaUrn_rejectsCapUrn() {
3815
3815
  // canonicalMediaUrn must fail hard.
3816
3816
  let threw = false;
3817
3817
  try {
3818
- rendererCanonicalMediaUrn('cap:op=x;in="media:";out="media:"');
3818
+ rendererCanonicalMediaUrn('cap:x;in="media:";out="media:"');
3819
3819
  } catch (e) {
3820
3820
  threw = true;
3821
3821
  }
@@ -3842,7 +3842,7 @@ function testRenderer_buildBrowseGraphData_rejectsMissingMediaTitles() {
3842
3842
  try {
3843
3843
  rendererBuildBrowseGraphData([
3844
3844
  {
3845
- urn: 'cap:in="media:pdf";op=extract;out="media:txt;textable"',
3845
+ urn: 'cap:in="media:pdf";extract;out="media:txt;textable"',
3846
3846
  title: 'Extract Text',
3847
3847
  in_spec: 'media:pdf',
3848
3848
  out_spec: 'media:txt;textable',
@@ -3917,7 +3917,7 @@ function testRenderer_validateStrandStep_requiresBooleanIsSequence() {
3917
3917
  try {
3918
3918
  rendererValidateStrandStep({
3919
3919
  step_type: { Cap: {
3920
- cap_urn: 'cap:in="media:a";op=x;out="media:b"',
3920
+ cap_urn: 'cap:in="media:a";x;out="media:b"',
3921
3921
  title: 't',
3922
3922
  input_is_sequence: 1, // number, not boolean
3923
3923
  output_is_sequence: false,
@@ -3938,9 +3938,9 @@ function testRenderer_classifyStrandCapSteps_capFlags() {
3938
3938
  // have neither.
3939
3939
  const steps = [
3940
3940
  makeForEachStep('media:pdf;list'),
3941
- makeCapStep('cap:in="media:pdf";op=a;out="media:png"', 'a', 'media:pdf', 'media:png', false, false),
3942
- makeCapStep('cap:in="media:png";op=b;out="media:jpg"', 'b', 'media:png', 'media:jpg', false, false),
3943
- makeCapStep('cap:in="media:jpg";op=c;out="media:txt"', 'c', 'media:jpg', 'media:txt', false, false),
3941
+ makeCapStep('cap:in="media:pdf";a;out="media:image;png"', 'a', 'media:pdf', 'media:image;png', false, false),
3942
+ makeCapStep('cap:in="media:image;png";b;out="media:jpg"', 'b', 'media:image;png', 'media:jpg', false, false),
3943
+ makeCapStep('cap:in="media:jpg";c;out="media:txt"', 'c', 'media:jpg', 'media:txt', false, false),
3944
3944
  makeCollectStep('media:txt'),
3945
3945
  ];
3946
3946
  const { capStepIndices, capFlags } = rendererClassifyStrandCapSteps(steps);
@@ -3959,11 +3959,11 @@ function testRenderer_classifyStrandCapSteps_nestedForks() {
3959
3959
  // nextCollect (inner), cap3 has nextCollect (outer).
3960
3960
  const steps = [
3961
3961
  makeForEachStep('media:a;list'),
3962
- makeCapStep('cap:in="media:a";op=a;out="media:b"', 'a', 'media:a', 'media:b', false, false),
3962
+ makeCapStep('cap:in="media:a";a;out="media:b"', 'a', 'media:a', 'media:b', false, false),
3963
3963
  makeForEachStep('media:b;list'),
3964
- makeCapStep('cap:in="media:b";op=b;out="media:c"', 'b', 'media:b', 'media:c', false, false),
3964
+ makeCapStep('cap:in="media:b";b;out="media:c"', 'b', 'media:b', 'media:c', false, false),
3965
3965
  makeCollectStep('media:c'),
3966
- makeCapStep('cap:in="media:c";op=c;out="media:d"', 'c', 'media:c', 'media:d', false, false),
3966
+ makeCapStep('cap:in="media:c";c;out="media:d"', 'c', 'media:c', 'media:d', false, false),
3967
3967
  makeCollectStep('media:d'),
3968
3968
  ];
3969
3969
  const { capFlags } = rendererClassifyStrandCapSteps(steps);
@@ -3992,7 +3992,7 @@ function testRenderer_buildStrandGraphData_singleCapPlain() {
3992
3992
  source_spec: 'media:a',
3993
3993
  target_spec: 'media:b',
3994
3994
  steps: [
3995
- makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
3995
+ makeCapStep('cap:in="media:a";x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
3996
3996
  ],
3997
3997
  }, {
3998
3998
  'media:a': 'Source A',
@@ -4017,7 +4017,7 @@ function testRenderer_buildStrandGraphData_sequenceShowsCardinality() {
4017
4017
  source_spec: 'media:a;list',
4018
4018
  target_spec: 'media:b',
4019
4019
  steps: [
4020
- makeCapStep('cap:in="media:a;list";op=x;out="media:b"', 'x', 'media:a;list', 'media:b', true, false),
4020
+ makeCapStep('cap:in="media:a;list";x;out="media:b"', 'x', 'media:a;list', 'media:b', true, false),
4021
4021
  ],
4022
4022
  }, {
4023
4023
  'media:a;list': 'Source A List',
@@ -4048,7 +4048,7 @@ function testRenderer_buildStrandGraphData_foreachCollectSpan() {
4048
4048
  target_spec: 'media:txt;list',
4049
4049
  steps: [
4050
4050
  makeForEachStep('media:pdf;list'),
4051
- makeCapStep('cap:in="media:pdf";op=extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
4051
+ makeCapStep('cap:in="media:pdf";extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
4052
4052
  makeCollectStep('media:txt'),
4053
4053
  ],
4054
4054
  }, {
@@ -4089,7 +4089,7 @@ function testRenderer_buildStrandGraphData_standaloneCollect() {
4089
4089
  source_spec: 'media:a',
4090
4090
  target_spec: 'media:b;list',
4091
4091
  steps: [
4092
- makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
4092
+ makeCapStep('cap:in="media:a";x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
4093
4093
  makeCollectStep('media:b'),
4094
4094
  ],
4095
4095
  }, {
@@ -4117,9 +4117,9 @@ function testRenderer_buildStrandGraphData_unclosedForEachBody() {
4117
4117
  source_spec: 'media:a',
4118
4118
  target_spec: 'media:c',
4119
4119
  steps: [
4120
- makeCapStep('cap:in="media:a";op=a;out="media:b"', 'a', 'media:a', 'media:b', false, false),
4120
+ makeCapStep('cap:in="media:a";a;out="media:b"', 'a', 'media:a', 'media:b', false, false),
4121
4121
  makeForEachStep('media:b'),
4122
- makeCapStep('cap:in="media:b";op=b;out="media:c"', 'b', 'media:b', 'media:c', false, false),
4122
+ makeCapStep('cap:in="media:b";b;out="media:c"', 'b', 'media:b', 'media:c', false, false),
4123
4123
  ],
4124
4124
  }, {
4125
4125
  'media:a': 'Source A',
@@ -4156,7 +4156,7 @@ function testRenderer_buildStrandGraphData_nestedForEachThrows() {
4156
4156
  steps: [
4157
4157
  makeForEachStep('media:a;list'),
4158
4158
  makeForEachStep('media:a'),
4159
- makeCapStep('cap:in="media:a";op=x;out="media:a"', 'x', 'media:a', 'media:a', false, false),
4159
+ makeCapStep('cap:in="media:a";x;out="media:a"', 'x', 'media:a', 'media:a', false, false),
4160
4160
  ],
4161
4161
  }, {
4162
4162
  'media:a;list': 'Source A List',
@@ -4192,7 +4192,7 @@ function testRenderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
4192
4192
  target_spec: 'media:txt;list',
4193
4193
  steps: [
4194
4194
  makeForEachStep('media:pdf;list'),
4195
- makeCapStep('cap:in="media:pdf";op=extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
4195
+ makeCapStep('cap:in="media:pdf";extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
4196
4196
  makeCollectStep('media:txt'),
4197
4197
  ],
4198
4198
  }, {
@@ -4240,9 +4240,9 @@ function testRenderer_collapseStrand_unclosedForEachBodyCollapses() {
4240
4240
  source_spec: 'media:a',
4241
4241
  target_spec: 'media:c',
4242
4242
  steps: [
4243
- makeCapStep('cap:in="media:a";op=a;out="media:b"', 'a', 'media:a', 'media:b', false, false),
4243
+ makeCapStep('cap:in="media:a";a;out="media:b"', 'a', 'media:a', 'media:b', false, false),
4244
4244
  makeForEachStep('media:b'),
4245
- makeCapStep('cap:in="media:b";op=b;out="media:c"', 'b', 'media:b', 'media:c', false, false),
4245
+ makeCapStep('cap:in="media:b";b;out="media:c"', 'b', 'media:b', 'media:c', false, false),
4246
4246
  ],
4247
4247
  }, {
4248
4248
  'media:a': 'Source A',
@@ -4297,7 +4297,7 @@ function testRenderer_collapseStrand_standaloneCollectCollapses() {
4297
4297
  source_spec: 'media:a',
4298
4298
  target_spec: 'media:b;list',
4299
4299
  steps: [
4300
- makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
4300
+ makeCapStep('cap:in="media:a";x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
4301
4301
  makeCollectStep('media:b'),
4302
4302
  ],
4303
4303
  }, {
@@ -4345,9 +4345,9 @@ function testRenderer_collapseStrand_sequenceProducingCapBeforeForeach() {
4345
4345
  source_spec: 'media:pdf',
4346
4346
  target_spec: 'media:decision',
4347
4347
  steps: [
4348
- makeCapStep('cap:in="media:pdf";op=disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
4348
+ makeCapStep('cap:in="media:pdf";disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
4349
4349
  makeForEachStep('media:page'),
4350
- makeCapStep('cap:in="media:page";op=decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
4350
+ makeCapStep('cap:in="media:page";decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
4351
4351
  ],
4352
4352
  }, {
4353
4353
  'media:pdf': 'PDF',
@@ -4400,7 +4400,7 @@ function testRenderer_collapseStrand_plainCapMergesTrailingOutput() {
4400
4400
  source_spec: 'media:a',
4401
4401
  target_spec: 'media:b',
4402
4402
  steps: [
4403
- makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
4403
+ makeCapStep('cap:in="media:a";x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
4404
4404
  ],
4405
4405
  }, {
4406
4406
  'media:a': 'Source A',
@@ -4432,7 +4432,7 @@ function testRenderer_collapseStrand_plainCapDistinctTargetNoMerge() {
4432
4432
  source_spec: 'media:a',
4433
4433
  target_spec: 'media:b;list',
4434
4434
  steps: [
4435
- makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
4435
+ makeCapStep('cap:in="media:a";x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
4436
4436
  ],
4437
4437
  }, {
4438
4438
  'media:a': 'Source A',
@@ -4489,8 +4489,8 @@ function testRenderer_buildRunGraphData_pagesSuccessesAndFailures() {
4489
4489
  target_spec: 'media:txt',
4490
4490
  steps: [
4491
4491
  makeForEachStep('media:pdf;list'),
4492
- makeCapStep('cap:in="media:pdf";op=a;out="media:png"', 'a', 'media:pdf', 'media:png', false, false),
4493
- makeCapStep('cap:in="media:png";op=b;out="media:txt"', 'b', 'media:png', 'media:txt', false, false),
4492
+ makeCapStep('cap:in="media:pdf";a;out="media:image;png"', 'a', 'media:pdf', 'media:image;png', false, false),
4493
+ makeCapStep('cap:in="media:image;png";b;out="media:txt"', 'b', 'media:image;png', 'media:txt', false, false),
4494
4494
  makeCollectStep('media:txt'),
4495
4495
  ],
4496
4496
  };
@@ -4506,7 +4506,7 @@ function testRenderer_buildRunGraphData_pagesSuccessesAndFailures() {
4506
4506
  saved_paths: [],
4507
4507
  total_bytes: 0,
4508
4508
  duration_ms: 0,
4509
- failed_cap: 'cap:in="media:png";op=b;out="media:txt"',
4509
+ failed_cap: 'cap:in="media:image;png";b;out="media:txt"',
4510
4510
  error: 'oom',
4511
4511
  });
4512
4512
  }
@@ -4515,7 +4515,7 @@ function testRenderer_buildRunGraphData_pagesSuccessesAndFailures() {
4515
4515
  media_display_names: {
4516
4516
  'media:pdf;list': 'PDF List',
4517
4517
  'media:pdf': 'PDF',
4518
- 'media:png': 'PNG',
4518
+ 'media:image;png': 'PNG',
4519
4519
  'media:txt': 'Text',
4520
4520
  },
4521
4521
  body_outcomes: outcomes,
@@ -4555,7 +4555,7 @@ function testRenderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace(
4555
4555
  target_spec: 'media:txt',
4556
4556
  steps: [
4557
4557
  makeForEachStep('media:pdf;list'),
4558
- makeCapStep('cap:in="media:pdf";op=a;out="media:txt"', 'a', 'media:pdf', 'media:txt', false, false),
4558
+ makeCapStep('cap:in="media:pdf";a;out="media:txt"', 'a', 'media:pdf', 'media:txt', false, false),
4559
4559
  makeCollectStep('media:txt'),
4560
4560
  ],
4561
4561
  };
@@ -4593,11 +4593,11 @@ function testRenderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
4593
4593
  makeForEachStep('media:a;list'),
4594
4594
  // Canonical form places tags alphabetically: op after in/out.
4595
4595
  makeCapStep(
4596
- 'cap:in="media:a";op=x;out="media:b"',
4596
+ 'cap:in="media:a";x;out="media:b"',
4597
4597
  'x', 'media:a', 'media:b', false, false
4598
4598
  ),
4599
4599
  makeCapStep(
4600
- 'cap:in="media:b";op=y;out="media:c"',
4600
+ 'cap:in="media:b";y;out="media:c"',
4601
4601
  'y', 'media:b', 'media:c', false, false
4602
4602
  ),
4603
4603
  makeCollectStep('media:c'),
@@ -4656,9 +4656,9 @@ function testRenderer_buildRunGraphData_backboneHasNoForeachNode() {
4656
4656
  source_spec: 'media:pdf',
4657
4657
  target_spec: 'media:decision',
4658
4658
  steps: [
4659
- makeCapStep('cap:in="media:pdf";op=disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
4659
+ makeCapStep('cap:in="media:pdf";disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
4660
4660
  makeForEachStep('media:page'),
4661
- makeCapStep('cap:in="media:page";op=decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
4661
+ makeCapStep('cap:in="media:page";decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
4662
4662
  ],
4663
4663
  };
4664
4664
  const payload = {
@@ -4704,12 +4704,12 @@ function testRenderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
4704
4704
  source_spec: 'media:pdf',
4705
4705
  target_spec: 'media:decision',
4706
4706
  steps: [
4707
- makeCapStep('cap:in="media:pdf";op=disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
4707
+ makeCapStep('cap:in="media:pdf";disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
4708
4708
  makeForEachStep('media:page'),
4709
- makeCapStep('cap:in="media:page";op=decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
4709
+ makeCapStep('cap:in="media:page";decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
4710
4710
  ],
4711
4711
  };
4712
- const failedCapUrn = 'cap:in="media:page";op=decide;out="media:decision"';
4712
+ const failedCapUrn = 'cap:in="media:page";decide;out="media:decision"';
4713
4713
  const payload = {
4714
4714
  resolved_strand: strand,
4715
4715
  media_display_names: {
@@ -4774,9 +4774,9 @@ function testRenderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
4774
4774
  source_spec: 'media:pdf',
4775
4775
  target_spec: 'media:decision',
4776
4776
  steps: [
4777
- makeCapStep('cap:in="media:pdf";op=disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
4777
+ makeCapStep('cap:in="media:pdf";disbind;out="media:page"', 'Disbind', 'media:pdf', 'media:page', false, true),
4778
4778
  makeForEachStep('media:page'),
4779
- makeCapStep('cap:in="media:page";op=decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
4779
+ makeCapStep('cap:in="media:page";decide;out="media:decision"', 'Make a Decision', 'media:page', 'media:decision', false, false),
4780
4780
  ],
4781
4781
  };
4782
4782
  const payload = {
@@ -4834,7 +4834,7 @@ function testRenderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarge
4834
4834
  target_spec: 'media:txt;list',
4835
4835
  steps: [
4836
4836
  makeForEachStep('media:pdf;list'),
4837
- makeCapStep('cap:in="media:pdf";op=extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
4837
+ makeCapStep('cap:in="media:pdf";extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
4838
4838
  makeCollectStep('media:txt'),
4839
4839
  ],
4840
4840
  };
@@ -5014,7 +5014,7 @@ function testRenderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
5014
5014
  edges: [
5015
5015
  {
5016
5016
  alias: 'edge_0',
5017
- cap_urn: 'cap:in=media:pdf;op=extract;out=media:txt;textable',
5017
+ cap_urn: 'cap:in=media:pdf;extract;out=media:txt;textable',
5018
5018
  title: 'Extract Text',
5019
5019
  is_loop: false,
5020
5020
  assignment: [
@@ -5024,7 +5024,7 @@ function testRenderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
5024
5024
  },
5025
5025
  {
5026
5026
  alias: 'edge_1',
5027
- cap_urn: 'cap:in=media:textable;op=embed;out=media:embedding;record',
5027
+ cap_urn: 'cap:in=media:textable;embed;out=media:embedding;record',
5028
5028
  title: 'Generate Embedding',
5029
5029
  is_loop: false,
5030
5030
  assignment: [
@@ -5069,7 +5069,7 @@ function testRenderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
5069
5069
  edges: [
5070
5070
  {
5071
5071
  alias: 'edge_0',
5072
- cap_urn: 'cap:in=media:textable;op=make_decision;out=media:decision;json;record;textable',
5072
+ cap_urn: 'cap:in=media:textable;make-decision;out=media:decision;json;record;textable',
5073
5073
  title: 'Make Decision',
5074
5074
  is_loop: true,
5075
5075
  assignment: [
@@ -5105,7 +5105,7 @@ function testRenderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignme
5105
5105
  edges: [
5106
5106
  {
5107
5107
  alias: 'edge_0',
5108
- cap_urn: 'cap:in=media:image;png;op=describe_image;out=media:image-description;textable',
5108
+ cap_urn: 'cap:in=media:image;png;describe-image;out=media:image-description;textable',
5109
5109
  title: 'Describe Image',
5110
5110
  is_loop: false,
5111
5111
  assignment: [
@@ -5145,7 +5145,7 @@ function testRenderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjo
5145
5145
  edges: [
5146
5146
  {
5147
5147
  alias: 'edge_0',
5148
- cap_urn: 'cap:in=media:pdf;op=extract;out=media:txt;textable',
5148
+ cap_urn: 'cap:in=media:pdf;extract;out=media:txt;textable',
5149
5149
  title: 'Extract Text',
5150
5150
  is_loop: false,
5151
5151
  assignment: [
@@ -5165,7 +5165,7 @@ function testRenderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjo
5165
5165
  edges: [
5166
5166
  {
5167
5167
  alias: 'edge_1',
5168
- cap_urn: 'cap:in=media:json;record;textable;op=convert_format;out=media:csv;list;record;textable',
5168
+ cap_urn: 'cap:in=media:json;record;textable;convert-format;out=media:csv;list;record;textable',
5169
5169
  title: 'Convert Format',
5170
5170
  is_loop: false,
5171
5171
  assignment: [