tagged-urn 0.34.83 → 0.35.86

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/README.md CHANGED
@@ -26,8 +26,8 @@ npm install tagged-urn
26
26
  const { TaggedUrn, TaggedUrnBuilder, UrnMatcher } = require('tagged-urn');
27
27
 
28
28
  // Create from string
29
- const urn = TaggedUrn.fromString('cap:op=generate;ext=pdf');
30
- console.log(urn.toString()); // "cap:ext=pdf;op=generate"
29
+ const urn = TaggedUrn.fromString('cap:generate;ext=pdf');
30
+ console.log(urn.toString()); // "cap:ext=pdf;generate"
31
31
 
32
32
  // Use builder pattern
33
33
  const built = new TaggedUrnBuilder()
@@ -43,10 +43,10 @@ console.log(urn.conformsTo(request)); // true
43
43
  const urns = [
44
44
  TaggedUrn.fromString('cap:op=*'),
45
45
  TaggedUrn.fromString('cap:op=generate'),
46
- TaggedUrn.fromString('cap:op=generate;ext=pdf')
46
+ TaggedUrn.fromString('cap:generate;ext=pdf')
47
47
  ];
48
48
  const best = UrnMatcher.findBestMatch(urns, request);
49
- console.log(best.toString()); // "cap:ext=pdf;op=generate" (most specific)
49
+ console.log(best.toString()); // "cap:ext=pdf;generate" (most specific)
50
50
  ```
51
51
 
52
52
  ## API Reference
package/package.json CHANGED
@@ -28,5 +28,5 @@
28
28
  "scripts": {
29
29
  "test": "node tagged-urn.test.js"
30
30
  },
31
- "version": "0.34.83"
31
+ "version": "0.35.86"
32
32
  }
@@ -61,7 +61,7 @@ function assertThrowsAny(fn, message) {
61
61
 
62
62
  // TEST501: Verify basic URN creation from string with multiple tags
63
63
  function test501_tagged_urn_creation() {
64
- const urn = TaggedUrn.fromString('cap:op=generate;ext=pdf;target=thumbnail');
64
+ const urn = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
65
65
  assertEqual(urn.getPrefix(), 'cap', 'Should get prefix');
66
66
  assertEqual(urn.getTag('op'), 'generate', 'Should get op tag');
67
67
  assertEqual(urn.getTag('target'), 'thumbnail', 'Should get target tag');
@@ -70,7 +70,7 @@ function test501_tagged_urn_creation() {
70
70
 
71
71
  // TEST502: Verify custom prefixes work and tags are sorted alphabetically
72
72
  function test502_custom_prefix() {
73
- const urn = TaggedUrn.fromString('myapp:op=generate;ext=pdf');
73
+ const urn = TaggedUrn.fromString('myapp:generate;ext=pdf');
74
74
  assertEqual(urn.getPrefix(), 'myapp', 'Should get custom prefix');
75
75
  assertEqual(urn.getTag('op'), 'generate', 'Should get op tag');
76
76
  assertEqual(urn.toString(), 'myapp:ext=pdf;op=generate', 'Should sort tags alphabetically');
@@ -125,7 +125,7 @@ function test506_unquoted_values_lowercased() {
125
125
  assertEqual(urn.getTag('Op'), 'generate', 'Should lookup with mixed case key');
126
126
 
127
127
  // Normalized URN equals explicitly lowercase URN
128
- const urn2 = TaggedUrn.fromString('cap:op=generate;ext=pdf;target=thumbnail');
128
+ const urn2 = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
129
129
  assertEqual(urn.toString(), urn2.toString(), 'Should produce same canonical form');
130
130
  assert(urn.equals(urn2), 'Should be equal after normalization');
131
131
  }
@@ -266,7 +266,7 @@ function test516_round_trip_escapes() {
266
266
  // TEST517: Verify missing prefix causes error
267
267
  function test517_prefix_required() {
268
268
  assertThrows(
269
- () => TaggedUrn.fromString('op=generate;ext=pdf'),
269
+ () => TaggedUrn.fromString('generate;ext=pdf'),
270
270
  ErrorCodes.MISSING_PREFIX,
271
271
  'Should require prefix'
272
272
  );
@@ -277,7 +277,7 @@ function test517_prefix_required() {
277
277
  'Should reject empty prefix'
278
278
  );
279
279
 
280
- const urn = TaggedUrn.fromString('cap:op=generate;ext=pdf');
280
+ const urn = TaggedUrn.fromString('cap:generate;ext=pdf');
281
281
  assertEqual(urn.getTag('op'), 'generate', 'Should parse with valid prefix');
282
282
 
283
283
  // Case-insensitive prefix
@@ -287,8 +287,8 @@ function test517_prefix_required() {
287
287
 
288
288
  // TEST518: Verify trailing semicolon is optional and doesn't affect equality
289
289
  function test518_trailing_semicolon_equivalence() {
290
- const urn1 = TaggedUrn.fromString('cap:op=generate;ext=pdf');
291
- const urn2 = TaggedUrn.fromString('cap:op=generate;ext=pdf;');
290
+ const urn1 = TaggedUrn.fromString('cap:generate;ext=pdf');
291
+ const urn2 = TaggedUrn.fromString('cap:generate;ext=pdf;');
292
292
 
293
293
  assert(urn1.equals(urn2), 'Should be equal with/without trailing semicolon');
294
294
  assertEqual(urn1.toString(), urn2.toString(), 'Should have same canonical form');
@@ -302,8 +302,8 @@ function test518_trailing_semicolon_equivalence() {
302
302
 
303
303
  // TEST519: Verify canonical form: alphabetically sorted tags, no trailing semicolon
304
304
  function test519_canonical_string_format() {
305
- const urn = TaggedUrn.fromString('cap:op=generate;target=thumbnail;ext=pdf');
306
- assertEqual(urn.toString(), 'cap:ext=pdf;op=generate;target=thumbnail', 'Should be alphabetically sorted');
305
+ const urn = TaggedUrn.fromString('cap:generate;target=thumbnail;ext=pdf');
306
+ assertEqual(urn.toString(), 'cap:ext=pdf;generate;target=thumbnail', 'Should be alphabetically sorted');
307
307
  }
308
308
 
309
309
  // ============================================================================
@@ -312,9 +312,9 @@ function test519_canonical_string_format() {
312
312
 
313
313
  // TEST520: Verify hasTag and getTag methods work correctly
314
314
  function test520_tag_matching() {
315
- const cap = TaggedUrn.fromString('cap:op=generate;ext=pdf;target=thumbnail');
315
+ const cap = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
316
316
 
317
- const request1 = TaggedUrn.fromString('cap:op=generate;ext=pdf;target=thumbnail');
317
+ const request1 = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
318
318
  assert(cap.conformsTo(request1), 'Should match exact request');
319
319
 
320
320
  const request2 = TaggedUrn.fromString('cap:op=generate');
@@ -330,7 +330,7 @@ function test520_tag_matching() {
330
330
  // TEST521: Verify value matching is case-sensitive
331
331
  function test521_matching_case_sensitive_values() {
332
332
  const cap1 = TaggedUrn.fromString('cap:OP=Generate;EXT=PDF;Target=Thumbnail');
333
- const cap2 = TaggedUrn.fromString('cap:op=generate;ext=pdf;target=thumbnail');
333
+ const cap2 = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
334
334
 
335
335
  // Unquoted values are lowercased, so these should be equal
336
336
  assert(cap1.equals(cap2), 'URNs with unquoted case differences should be equal');
@@ -352,7 +352,7 @@ function test522_missing_tag_handling() {
352
352
  assert(!instance.conformsTo(pattern1), 'Should NOT match when instance missing pattern-required tag');
353
353
 
354
354
  // Pattern missing tag = no constraint: MATCH
355
- const instance2 = TaggedUrn.fromString('cap:op=generate;ext=pdf');
355
+ const instance2 = TaggedUrn.fromString('cap:generate;ext=pdf');
356
356
  const pattern2 = TaggedUrn.fromString('cap:op=generate');
357
357
  assert(instance2.conformsTo(pattern2), 'Should match subset pattern');
358
358
 
@@ -419,17 +419,17 @@ function test525_builder_preserves_case() {
419
419
  function test526_compatibility() {
420
420
  // General pattern accepts specific instance
421
421
  const general = TaggedUrn.fromString('cap:op=generate');
422
- const specific = TaggedUrn.fromString('cap:op=generate;ext=pdf');
422
+ const specific = TaggedUrn.fromString('cap:generate;ext=pdf');
423
423
  assert(general.accepts(specific), 'General pattern accepts specific instance');
424
424
  assert(!specific.accepts(general), 'Specific does not accept general');
425
425
 
426
426
  // Wildcard pattern accepts any value
427
- const wildcard = TaggedUrn.fromString('cap:op=generate;format=*');
428
- const withFormat = TaggedUrn.fromString('cap:op=generate;format=json');
427
+ const wildcard = TaggedUrn.fromString('cap:generate;format=*');
428
+ const withFormat = TaggedUrn.fromString('cap:generate;format=json');
429
429
  assert(wildcard.accepts(withFormat), 'Wildcard accepts specific value');
430
430
 
431
431
  // Different op values: neither accepts the other
432
- const cap1 = TaggedUrn.fromString('cap:op=generate;ext=pdf');
432
+ const cap1 = TaggedUrn.fromString('cap:generate;ext=pdf');
433
433
  const cap3 = TaggedUrn.fromString('cap:image;op=extract');
434
434
  assert(!cap1.accepts(cap3), 'Different op should not accept');
435
435
  assert(!cap3.accepts(cap1), 'Different op should not accept (reverse)');
@@ -440,7 +440,7 @@ function test527_best_match() {
440
440
  const caps = [
441
441
  TaggedUrn.fromString('cap:op=*'),
442
442
  TaggedUrn.fromString('cap:op=generate'),
443
- TaggedUrn.fromString('cap:op=generate;ext=pdf')
443
+ TaggedUrn.fromString('cap:generate;ext=pdf')
444
444
  ];
445
445
 
446
446
  const request = TaggedUrn.fromString('cap:op=generate');
@@ -462,7 +462,7 @@ function test528_merge_and_subset() {
462
462
  const cap1 = TaggedUrn.fromString('cap:op=generate');
463
463
  const cap2 = TaggedUrn.fromString('cap:ext=pdf;output=binary');
464
464
  const merged = cap1.merge(cap2);
465
- assertEqual(merged.toString(), 'cap:ext=pdf;op=generate;output=binary', 'Should merge correctly');
465
+ assertEqual(merged.toString(), 'cap:ext=pdf;generate;output=binary', 'Should merge correctly');
466
466
 
467
467
  // Subset
468
468
  const subset = merged.subset(['type', 'ext']);
@@ -502,7 +502,7 @@ function test531_empty_tagged_urn() {
502
502
  assertEqual(Object.keys(empty.tags).length, 0, 'Should have no tags');
503
503
  assertEqual(empty.toString(), 'cap:', 'Should have correct string representation');
504
504
 
505
- const specific = TaggedUrn.fromString('cap:op=generate;ext=pdf');
505
+ const specific = TaggedUrn.fromString('cap:generate;ext=pdf');
506
506
 
507
507
  // Empty instance vs specific pattern: NO MATCH
508
508
  assert(!empty.conformsTo(specific), 'Empty instance should NOT match pattern with requirements');
@@ -642,67 +642,67 @@ function test542_semantic_equivalence() {
642
642
 
643
643
  // TEST543: Instance and pattern have same tag/value - matches
644
644
  function test543_matching_semantics_exact_match() {
645
- const cap = TaggedUrn.fromString('cap:op=generate;ext=pdf');
646
- const request = TaggedUrn.fromString('cap:op=generate;ext=pdf');
645
+ const cap = TaggedUrn.fromString('cap:generate;ext=pdf');
646
+ const request = TaggedUrn.fromString('cap:generate;ext=pdf');
647
647
  assert(cap.conformsTo(request), 'Exact match should succeed');
648
648
  }
649
649
 
650
650
  // TEST544: Pattern requires tag but instance doesn't have it - no match
651
651
  function test544_matching_semantics_instance_missing_tag() {
652
652
  const instance = TaggedUrn.fromString('cap:op=generate');
653
- const pattern = TaggedUrn.fromString('cap:op=generate;ext=pdf');
653
+ const pattern = TaggedUrn.fromString('cap:generate;ext=pdf');
654
654
  assert(!instance.conformsTo(pattern), 'Instance missing tag should NOT match');
655
655
 
656
- const patternOptional = TaggedUrn.fromString('cap:op=generate;ext=?');
656
+ const patternOptional = TaggedUrn.fromString('cap:generate;ext=?');
657
657
  assert(instance.conformsTo(patternOptional), 'Pattern with ext=? should match instance without ext');
658
658
  }
659
659
 
660
660
  // TEST545: Instance has extra tag not in pattern - still matches
661
661
  function test545_matching_semantics_extra_tag() {
662
- const cap = TaggedUrn.fromString('cap:op=generate;ext=pdf;version=2');
663
- const request = TaggedUrn.fromString('cap:op=generate;ext=pdf');
662
+ const cap = TaggedUrn.fromString('cap:generate;ext=pdf;version=2');
663
+ const request = TaggedUrn.fromString('cap:generate;ext=pdf');
664
664
  assert(cap.conformsTo(request), 'Cap with extra tag should match');
665
665
  }
666
666
 
667
667
  // TEST546: Pattern has wildcard - matches any value
668
668
  function test546_matching_semantics_request_wildcard() {
669
- const cap = TaggedUrn.fromString('cap:op=generate;ext=pdf');
670
- const request = TaggedUrn.fromString('cap:op=generate;ext=*');
669
+ const cap = TaggedUrn.fromString('cap:generate;ext=pdf');
670
+ const request = TaggedUrn.fromString('cap:generate;ext=*');
671
671
  assert(cap.conformsTo(request), 'Request wildcard should match');
672
672
  }
673
673
 
674
674
  // TEST547: Instance has wildcard - matches any pattern constraint
675
675
  function test547_matching_semantics_cap_wildcard() {
676
- const cap = TaggedUrn.fromString('cap:op=generate;ext=*');
677
- const request = TaggedUrn.fromString('cap:op=generate;ext=pdf');
676
+ const cap = TaggedUrn.fromString('cap:generate;ext=*');
677
+ const request = TaggedUrn.fromString('cap:generate;ext=pdf');
678
678
  assert(cap.conformsTo(request), 'Cap wildcard should match');
679
679
  }
680
680
 
681
681
  // TEST548: Instance and pattern have same key but different values - no match
682
682
  function test548_matching_semantics_value_mismatch() {
683
- const cap = TaggedUrn.fromString('cap:op=generate;ext=pdf');
684
- const request = TaggedUrn.fromString('cap:op=generate;ext=docx');
683
+ const cap = TaggedUrn.fromString('cap:generate;ext=pdf');
684
+ const request = TaggedUrn.fromString('cap:generate;ext=docx');
685
685
  assert(!cap.conformsTo(request), 'Value mismatch should not match');
686
686
  }
687
687
 
688
688
  // TEST549: Pattern has constraint instance doesn't have - no match
689
689
  function test549_matching_semantics_pattern_extra_tag() {
690
- const instance = TaggedUrn.fromString('cap:op=generate_thumbnail;out="media:binary"');
691
- const pattern = TaggedUrn.fromString('cap:op=generate_thumbnail;out="media:binary";ext=wav');
690
+ const instance = TaggedUrn.fromString('cap:generate-thumbnail;out="media:binary"');
691
+ const pattern = TaggedUrn.fromString('cap:generate-thumbnail;out="media:binary";ext=wav');
692
692
  assert(!instance.conformsTo(pattern), 'Instance missing ext should NOT match pattern requiring ext=wav');
693
693
 
694
- const patternNoExt = TaggedUrn.fromString('cap:op=generate_thumbnail;out="media:binary"');
694
+ const patternNoExt = TaggedUrn.fromString('cap:generate-thumbnail;out="media:binary"');
695
695
  assert(instance.conformsTo(patternNoExt), 'Should match pattern without ext constraint');
696
696
  }
697
697
 
698
698
  // TEST550: Empty pattern matches any instance
699
699
  function test550_matching_semantics_empty_pattern() {
700
- const instance = TaggedUrn.fromString('cap:op=generate;ext=pdf');
700
+ const instance = TaggedUrn.fromString('cap:generate;ext=pdf');
701
701
  const emptyPattern = TaggedUrn.fromString('cap:');
702
702
  assert(instance.conformsTo(emptyPattern), 'Any instance should match empty pattern');
703
703
 
704
704
  const emptyInstance = TaggedUrn.fromString('cap:');
705
- const pattern = TaggedUrn.fromString('cap:op=generate;ext=pdf');
705
+ const pattern = TaggedUrn.fromString('cap:generate;ext=pdf');
706
706
  assert(!emptyInstance.conformsTo(pattern), 'Empty instance should NOT match pattern with requirements');
707
707
  }
708
708
 
@@ -712,7 +712,7 @@ function test551_matching_semantics_cross_dimension() {
712
712
  const pattern = TaggedUrn.fromString('cap:ext=pdf');
713
713
  assert(!instance.conformsTo(pattern), 'Instance without ext should NOT match pattern requiring ext');
714
714
 
715
- const instance2 = TaggedUrn.fromString('cap:op=generate;ext=pdf');
715
+ const instance2 = TaggedUrn.fromString('cap:generate;ext=pdf');
716
716
  const pattern2 = TaggedUrn.fromString('cap:ext=pdf');
717
717
  assert(instance2.conformsTo(pattern2), 'Instance with ext=pdf should match');
718
718
  }
@@ -763,20 +763,20 @@ function test554_valueless_tag_parsing_multiple() {
763
763
 
764
764
  // TEST555: Mix of valueless and valued tags works
765
765
  function test555_valueless_tag_mixed_with_valued() {
766
- const urn = TaggedUrn.fromString('cap:op=generate;optimize;ext=pdf;secure');
766
+ const urn = TaggedUrn.fromString('cap:generate;optimize;ext=pdf;secure');
767
767
  assertEqual(urn.getTag('op'), 'generate', 'Should parse valued tag');
768
768
  assertEqual(urn.getTag('optimize'), '*', 'Should parse value-less tag');
769
769
  assertEqual(urn.getTag('ext'), 'pdf', 'Should parse valued tag');
770
770
  assertEqual(urn.getTag('secure'), '*', 'Should parse value-less tag');
771
- assertEqual(urn.toString(), 'cap:ext=pdf;op=generate;optimize;secure', 'Should serialize alphabetically');
771
+ assertEqual(urn.toString(), 'cap:ext=pdf;generate;optimize;secure', 'Should serialize alphabetically');
772
772
  }
773
773
 
774
774
  // TEST556: Valueless tag at end (no trailing semicolon) works
775
775
  function test556_valueless_tag_at_end() {
776
- const urn = TaggedUrn.fromString('cap:op=generate;optimize');
776
+ const urn = TaggedUrn.fromString('cap:generate;optimize');
777
777
  assertEqual(urn.getTag('op'), 'generate', 'Should parse valued tag');
778
778
  assertEqual(urn.getTag('optimize'), '*', 'Should parse value-less tag');
779
- assertEqual(urn.toString(), 'cap:op=generate;optimize', 'Should serialize correctly');
779
+ assertEqual(urn.toString(), 'cap:generate;optimize', 'Should serialize correctly');
780
780
  }
781
781
 
782
782
  // TEST557: Valueless tag is equivalent to explicit wildcard
@@ -790,10 +790,10 @@ function test557_valueless_tag_equivalence_to_wildcard() {
790
790
 
791
791
  // TEST558: Valueless tag (wildcard) matches any value
792
792
  function test558_valueless_tag_matching() {
793
- const urn = TaggedUrn.fromString('cap:op=generate;ext');
794
- const requestPdf = TaggedUrn.fromString('cap:op=generate;ext=pdf');
795
- const requestDocx = TaggedUrn.fromString('cap:op=generate;ext=docx');
796
- const requestAny = TaggedUrn.fromString('cap:op=generate;ext=anything');
793
+ const urn = TaggedUrn.fromString('cap:generate;ext');
794
+ const requestPdf = TaggedUrn.fromString('cap:generate;ext=pdf');
795
+ const requestDocx = TaggedUrn.fromString('cap:generate;ext=docx');
796
+ const requestAny = TaggedUrn.fromString('cap:generate;ext=anything');
797
797
 
798
798
  assert(urn.conformsTo(requestPdf), 'Should match pdf');
799
799
  assert(urn.conformsTo(requestDocx), 'Should match docx');
@@ -802,24 +802,24 @@ function test558_valueless_tag_matching() {
802
802
 
803
803
  // TEST559: Pattern with valueless tag requires instance to have tag (any value)
804
804
  function test559_valueless_tag_in_pattern() {
805
- const pattern = TaggedUrn.fromString('cap:op=generate;ext');
806
- const instancePdf = TaggedUrn.fromString('cap:op=generate;ext=pdf');
807
- const instanceDocx = TaggedUrn.fromString('cap:op=generate;ext=docx');
805
+ const pattern = TaggedUrn.fromString('cap:generate;ext');
806
+ const instancePdf = TaggedUrn.fromString('cap:generate;ext=pdf');
807
+ const instanceDocx = TaggedUrn.fromString('cap:generate;ext=docx');
808
808
  const instanceMissing = TaggedUrn.fromString('cap:op=generate');
809
809
 
810
810
  assert(instancePdf.conformsTo(pattern), 'Should match pdf instance');
811
811
  assert(instanceDocx.conformsTo(pattern), 'Should match docx instance');
812
812
  assert(!instanceMissing.conformsTo(pattern), 'Should NOT match instance without ext');
813
813
 
814
- const patternOptional = TaggedUrn.fromString('cap:op=generate;ext=?');
814
+ const patternOptional = TaggedUrn.fromString('cap:generate;ext=?');
815
815
  assert(instanceMissing.conformsTo(patternOptional), 'Instance should match pattern with ext=?');
816
816
  }
817
817
 
818
818
  // TEST560: Valueless tag contributes 2 points to specificity
819
819
  function test560_valueless_tag_specificity() {
820
820
  const urn1 = TaggedUrn.fromString('cap:op=generate');
821
- const urn2 = TaggedUrn.fromString('cap:op=generate;optimize');
822
- const urn3 = TaggedUrn.fromString('cap:op=generate;ext=pdf');
821
+ const urn2 = TaggedUrn.fromString('cap:generate;optimize');
822
+ const urn3 = TaggedUrn.fromString('cap:generate;ext=pdf');
823
823
 
824
824
  assertEqual(urn1.specificity(), 3, '1 exact = 3');
825
825
  assertEqual(urn2.specificity(), 5, '1 exact + 1 * = 3 + 2 = 5');
@@ -828,7 +828,7 @@ function test560_valueless_tag_specificity() {
828
828
 
829
829
  // TEST561: Valueless tags round-trip correctly (serialize as just key)
830
830
  function test561_valueless_tag_roundtrip() {
831
- const original = 'cap:ext=pdf;op=generate;optimize;secure';
831
+ const original = 'cap:ext=pdf;generate;optimize;secure';
832
832
  const urn = TaggedUrn.fromString(original);
833
833
  const serialized = urn.toString();
834
834
  const reparsed = TaggedUrn.fromString(serialized);
@@ -861,9 +861,9 @@ function test563_empty_value_still_error() {
861
861
 
862
862
  // TEST564: Valueless tags (wildcard) accept any specific value
863
863
  function test564_valueless_tag_compatibility() {
864
- const wildcard = TaggedUrn.fromString('cap:op=generate;ext');
865
- const pdf = TaggedUrn.fromString('cap:op=generate;ext=pdf');
866
- const docx = TaggedUrn.fromString('cap:op=generate;ext=docx');
864
+ const wildcard = TaggedUrn.fromString('cap:generate;ext');
865
+ const pdf = TaggedUrn.fromString('cap:generate;ext=pdf');
866
+ const docx = TaggedUrn.fromString('cap:generate;ext=docx');
867
867
 
868
868
  // Wildcard ext accepts both pdf and docx instances
869
869
  assert(wildcard.accepts(pdf), 'Wildcard ext should accept pdf');
@@ -881,7 +881,7 @@ function test565_valueless_numeric_key_still_rejected() {
881
881
  'Should reject numeric key'
882
882
  );
883
883
  assertThrows(
884
- () => TaggedUrn.fromString('cap:op=generate;456'),
884
+ () => TaggedUrn.fromString('cap:generate;456'),
885
885
  ErrorCodes.NUMERIC_KEY,
886
886
  'Should reject numeric key'
887
887
  );
@@ -1149,7 +1149,7 @@ function test577_specificity_with_special_values() {
1149
1149
 
1150
1150
  // JS-only: Test op tag is used instead of deprecated action tag
1151
1151
  function testJsOnly_op_tag_rename() {
1152
- const cap = TaggedUrn.fromString('cap:op=generate;format=json');
1152
+ const cap = TaggedUrn.fromString('cap:generate;format=json');
1153
1153
  assertEqual(cap.getTag('op'), 'generate', 'Should have op tag');
1154
1154
  assertEqual(cap.getTag('action'), undefined, 'Should not have action tag');
1155
1155
 
@@ -1166,7 +1166,7 @@ function testJsOnly_op_tag_rename() {
1166
1166
 
1167
1167
  // Test conformsToStr convenience method
1168
1168
  function testConformsToStr() {
1169
- const urn = TaggedUrn.fromString('cap:op=generate;ext=pdf');
1169
+ const urn = TaggedUrn.fromString('cap:generate;ext=pdf');
1170
1170
  assert(urn.conformsToStr('cap:op=generate'), 'Should match subset pattern string');
1171
1171
  assert(!urn.conformsToStr('cap:op=extract'), 'Should not match conflicting pattern string');
1172
1172
  }
@@ -1174,15 +1174,15 @@ function testConformsToStr() {
1174
1174
  // Test acceptsStr convenience method
1175
1175
  function testAcceptsStr() {
1176
1176
  const pattern = TaggedUrn.fromString('cap:op=generate');
1177
- assert(pattern.acceptsStr('cap:op=generate;ext=pdf'), 'Should accept more specific instance string');
1177
+ assert(pattern.acceptsStr('cap:generate;ext=pdf'), 'Should accept more specific instance string');
1178
1178
  assert(!pattern.acceptsStr('cap:op=extract'), 'Should not accept conflicting instance string');
1179
1179
  }
1180
1180
 
1181
1181
  // Test canonical static method
1182
1182
  function testCanonical() {
1183
1183
  assertEqual(
1184
- TaggedUrn.canonical('cap:op=generate;ext=pdf;target=thumbnail'),
1185
- 'cap:ext=pdf;op=generate;target=thumbnail',
1184
+ TaggedUrn.canonical('cap:generate;ext=pdf;target=thumbnail'),
1185
+ 'cap:ext=pdf;generate;target=thumbnail',
1186
1186
  'Should return canonical form'
1187
1187
  );
1188
1188
  }
@@ -1190,7 +1190,7 @@ function testCanonical() {
1190
1190
  // Test canonicalOption static method
1191
1191
  function testCanonicalOption() {
1192
1192
  assertEqual(
1193
- TaggedUrn.canonicalOption('cap:op=generate;ext=pdf'),
1193
+ TaggedUrn.canonicalOption('cap:generate;ext=pdf'),
1194
1194
  'cap:ext=pdf;op=generate',
1195
1195
  'Should return canonical form for valid input'
1196
1196
  );
@@ -1345,7 +1345,7 @@ module.exports = { runTests };
1345
1345
 
1346
1346
  // TEST578: Equivalent URNs with identical tag sets
1347
1347
  (() => {
1348
- const a = TaggedUrn.fromString('cap:op=generate;ext=pdf');
1348
+ const a = TaggedUrn.fromString('cap:generate;ext=pdf');
1349
1349
  const b = TaggedUrn.fromString('cap:ext=pdf;op=generate');
1350
1350
  assert(a.isEquivalent(b), 'TEST578: Equivalent should be true');
1351
1351
  assert(b.isEquivalent(a), 'TEST578: Equivalent should be symmetric');
@@ -1377,8 +1377,8 @@ module.exports = { runTests };
1377
1377
 
1378
1378
  // TEST582: Equivalent implies comparable
1379
1379
  (() => {
1380
- const a = TaggedUrn.fromString('cap:op=test;ext=pdf');
1381
- const b = TaggedUrn.fromString('cap:op=test;ext=pdf');
1380
+ const a = TaggedUrn.fromString('cap:test;ext=pdf');
1381
+ const b = TaggedUrn.fromString('cap:test;ext=pdf');
1382
1382
  assert(a.isEquivalent(b), 'TEST582: Should be equivalent');
1383
1383
  assert(a.isComparable(b), 'TEST582: Equivalent implies comparable');
1384
1384
  })();
@@ -1422,7 +1422,7 @@ module.exports = { runTests };
1422
1422
  .soloTag('ext')
1423
1423
  .soloTag('quality')
1424
1424
  .build();
1425
- assertEqual(urn.toString(), 'cap:ext;op=convert;quality', 'TEST593: toString with wildcards');
1425
+ assertEqual(urn.toString(), 'cap:ext;convert;quality', 'TEST593: toString with wildcards');
1426
1426
  assertEqual(urn.specificity(), 7, 'TEST593: specificity');
1427
1427
  })();
1428
1428