capns 0.84.18923 → 0.85.19364

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.
Files changed (3) hide show
  1. package/capns.js +15 -15
  2. package/capns.test.js +89 -82
  3. package/package.json +2 -2
package/capns.js CHANGED
@@ -732,18 +732,18 @@ const MEDIA_INTEGER_ARRAY = 'media:integer;textable;numeric;form=list';
732
732
  const MEDIA_NUMBER_ARRAY = 'media:textable;numeric;form=list';
733
733
  const MEDIA_BOOLEAN_ARRAY = 'media:bool;textable;form=list';
734
734
  const MEDIA_OBJECT_ARRAY = 'media:form=list;textable';
735
- const MEDIA_BINARY = 'media:bytes';
735
+ const MEDIA_BINARY = 'media:';
736
736
  const MEDIA_VOID = 'media:void';
737
737
  // Semantic content types
738
- const MEDIA_PNG = 'media:image;png;bytes';
739
- const MEDIA_AUDIO = 'media:wav;audio;bytes;';
740
- const MEDIA_VIDEO = 'media:video;bytes';
738
+ const MEDIA_PNG = 'media:image;png';
739
+ const MEDIA_AUDIO = 'media:wav;audio';
740
+ const MEDIA_VIDEO = 'media:video';
741
741
  // Semantic AI input types
742
- const MEDIA_AUDIO_SPEECH = 'media:audio;wav;bytes;speech';
743
- const MEDIA_IMAGE_THUMBNAIL = 'media:image;png;bytes;thumbnail';
742
+ const MEDIA_AUDIO_SPEECH = 'media:audio;wav;speech';
743
+ const MEDIA_IMAGE_THUMBNAIL = 'media:image;png;thumbnail';
744
744
  // Document types (PRIMARY naming - type IS the format)
745
- const MEDIA_PDF = 'media:pdf;bytes';
746
- const MEDIA_EPUB = 'media:epub;bytes';
745
+ const MEDIA_PDF = 'media:pdf';
746
+ const MEDIA_EPUB = 'media:epub';
747
747
  // Text format types (PRIMARY naming - type IS the format)
748
748
  const MEDIA_MD = 'media:md;textable';
749
749
  const MEDIA_TXT = 'media:txt;textable';
@@ -775,8 +775,8 @@ const MEDIA_LLM_INFERENCE_OUTPUT = 'media:generated-text;textable;form=map';
775
775
  const MEDIA_FILE_PATH = 'media:file-path;textable;form=scalar';
776
776
  const MEDIA_FILE_PATH_ARRAY = 'media:file-path;textable;form=list';
777
777
  // Collection types
778
- const MEDIA_COLLECTION = 'media:collection;form=map';
779
- const MEDIA_COLLECTION_LIST = 'media:collection;form=list';
778
+ const MEDIA_COLLECTION = 'media:collection;textable;form=map';
779
+ const MEDIA_COLLECTION_LIST = 'media:collection;textable;form=list';
780
780
 
781
781
  // =============================================================================
782
782
  // STANDARD CAP URN CONSTANTS
@@ -825,7 +825,7 @@ class MediaUrn {
825
825
 
826
826
  /**
827
827
  * Parse a media URN string. Validates the prefix is 'media'.
828
- * @param {string} str - The media URN string (e.g., "media:bytes")
828
+ * @param {string} str - The media URN string (e.g., "media:pdf")
829
829
  * @returns {MediaUrn}
830
830
  * @throws {MediaUrnError} If prefix is not 'media'
831
831
  */
@@ -834,8 +834,8 @@ class MediaUrn {
834
834
  return new MediaUrn(urn);
835
835
  }
836
836
 
837
- /** @returns {boolean} True if the "bytes" marker tag is present */
838
- isBinary() { return this._urn.getTag('bytes') !== undefined; }
837
+ /** @returns {boolean} True if the "textable" marker tag is NOT present (binary = not textable) */
838
+ isBinary() { return this._urn.getTag('textable') === undefined; }
839
839
 
840
840
  /** @returns {boolean} True if form=map tag is present */
841
841
  isMap() { return this._urn.hasTag('form', 'map'); }
@@ -1073,7 +1073,7 @@ class MediaSpec {
1073
1073
  return this._parsedMediaUrn;
1074
1074
  }
1075
1075
 
1076
- /** @returns {boolean} True if binary (bytes marker tag present) */
1076
+ /** @returns {boolean} True if binary (textable marker tag absent) */
1077
1077
  isBinary() {
1078
1078
  const mu = this.parsedMediaUrn();
1079
1079
  return mu ? mu.isBinary() : false;
@@ -1280,7 +1280,7 @@ function buildExtensionIndex(mediaSpecs) {
1280
1280
  *
1281
1281
  * @example
1282
1282
  * const urns = mediaUrnsForExtension('pdf', mediaSpecs);
1283
- * // May return ['media:pdf;bytes']
1283
+ * // May return ['media:pdf']
1284
1284
  */
1285
1285
  function mediaUrnsForExtension(extension, mediaSpecs) {
1286
1286
  const index = buildExtensionIndex(mediaSpecs);
package/capns.test.js CHANGED
@@ -441,12 +441,12 @@ function test025_bestMatch() {
441
441
  // TEST026: merge combines tags, subset keeps only specified
442
442
  function test026_mergeAndSubset() {
443
443
  const cap1 = CapUrn.fromString(testUrn('op=generate'));
444
- const cap2 = CapUrn.fromString('cap:in="media:textable;form=scalar";ext=pdf;format=binary;out="media:bytes"');
444
+ const cap2 = CapUrn.fromString('cap:in="media:textable;form=scalar";ext=pdf;format=binary;out="media:"');
445
445
 
446
446
  // Merge (other takes precedence)
447
447
  const merged = cap1.merge(cap2);
448
448
  assertEqual(merged.getInSpec(), 'media:textable;form=scalar', 'Merge should take inSpec from other');
449
- assertEqual(merged.getOutSpec(), 'media:bytes', 'Merge should take outSpec from other');
449
+ assertEqual(merged.getOutSpec(), 'media:', 'Merge should take outSpec from other');
450
450
  assertEqual(merged.getTag('op'), 'generate', 'Merge should keep original tags');
451
451
  assertEqual(merged.getTag('ext'), 'pdf', 'Merge should add other tags');
452
452
 
@@ -647,8 +647,8 @@ function test046_matchingSemanticsFallbackPattern() {
647
647
 
648
648
  // TEST047: Thumbnail with void input matches specific ext request
649
649
  function test047_matchingSemanticsThumbnailVoidInput() {
650
- const cap = CapUrn.fromString('cap:in="media:void";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"');
651
- const request = CapUrn.fromString('cap:ext=pdf;in="media:void";op=generate_thumbnail;out="media:image;bytes"');
650
+ const cap = CapUrn.fromString('cap:in="media:void";op=generate_thumbnail;out="media:image;png;thumbnail"');
651
+ const request = CapUrn.fromString('cap:ext=pdf;in="media:void";op=generate_thumbnail;out="media:image"');
652
652
  assert(cap.accepts(request), 'Void input cap should accept request; cap output conforms to less-specific request output');
653
653
  }
654
654
 
@@ -666,7 +666,7 @@ function test049_matchingSemanticsCrossDimension() {
666
666
  assert(cap.accepts(request), 'Independent tags should not block matching');
667
667
  }
668
668
 
669
- // TEST050: media:string vs media:bytes -> no match
669
+ // TEST050: media:string vs media: (wildcard) -> no match
670
670
  function test050_matchingSemanticsDirectionMismatch() {
671
671
  const cap = CapUrn.fromString(
672
672
  `cap:in="${MEDIA_STRING}";op=generate;out="${MEDIA_OBJECT}"`
@@ -677,79 +677,79 @@ function test050_matchingSemanticsDirectionMismatch() {
677
677
  assert(!cap.accepts(request), 'Incompatible direction types should not match');
678
678
  }
679
679
 
680
- // TEST051: Generic media:bytes provider accepts media:pdf;bytes request;
681
- // specific pdf cap rejects generic bytes request;
680
+ // TEST051: Generic media: (wildcard) provider accepts media:pdf request;
681
+ // specific pdf cap rejects generic wildcard request;
682
682
  // output direction: more specific output satisfies less specific request
683
683
  function test051_directionSemanticMatching() {
684
- // Generic bytes cap accepts specific pdf;bytes request
684
+ // Generic wildcard cap accepts specific pdf request
685
685
  const genericCap = CapUrn.fromString(
686
- 'cap:in="media:bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
686
+ 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
687
687
  );
688
688
  const pdfRequest = CapUrn.fromString(
689
- 'cap:in="media:pdf;bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
689
+ 'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
690
690
  );
691
- assert(genericCap.accepts(pdfRequest), 'Generic bytes cap must accept pdf;bytes request');
691
+ assert(genericCap.accepts(pdfRequest), 'Generic wildcard cap must accept pdf request');
692
692
 
693
- // Also accepts epub;bytes
693
+ // Also accepts epub
694
694
  const epubRequest = CapUrn.fromString(
695
- 'cap:in="media:epub;bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
695
+ 'cap:in="media:epub";op=generate_thumbnail;out="media:image;png;thumbnail"'
696
696
  );
697
- assert(genericCap.accepts(epubRequest), 'Generic bytes cap must accept epub;bytes request');
697
+ assert(genericCap.accepts(epubRequest), 'Generic wildcard cap must accept epub request');
698
698
 
699
699
  // Reverse: specific pdf cap does NOT accept generic bytes request
700
700
  const pdfCap = CapUrn.fromString(
701
- 'cap:in="media:pdf;bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
701
+ 'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
702
702
  );
703
703
  const genericRequest = CapUrn.fromString(
704
- 'cap:in="media:bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
704
+ 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
705
705
  );
706
- assert(!pdfCap.accepts(genericRequest), 'Specific pdf cap must NOT accept generic bytes request');
706
+ assert(!pdfCap.accepts(genericRequest), 'Specific pdf cap must NOT accept generic wildcard request');
707
707
 
708
708
  // PDF cap does NOT accept epub request
709
709
  assert(!pdfCap.accepts(epubRequest), 'PDF cap must NOT accept epub request');
710
710
 
711
711
  // Output direction: cap producing more specific output satisfies less specific request
712
712
  const specificOutCap = CapUrn.fromString(
713
- 'cap:in="media:bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
713
+ 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
714
714
  );
715
715
  const genericOutRequest = CapUrn.fromString(
716
- 'cap:in="media:bytes";op=generate_thumbnail;out="media:image;bytes"'
716
+ 'cap:in="media:";op=generate_thumbnail;out="media:image"'
717
717
  );
718
718
  assert(specificOutCap.accepts(genericOutRequest),
719
- 'Cap producing image;png;bytes;thumbnail must satisfy request for image;bytes');
719
+ 'Cap producing image;png;thumbnail must satisfy request for image');
720
720
 
721
721
  // Reverse output: generic output cap does NOT satisfy specific output request
722
722
  const genericOutCap = CapUrn.fromString(
723
- 'cap:in="media:bytes";op=generate_thumbnail;out="media:image;bytes"'
723
+ 'cap:in="media:";op=generate_thumbnail;out="media:image"'
724
724
  );
725
725
  const specificOutRequest = CapUrn.fromString(
726
- 'cap:in="media:bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
726
+ 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
727
727
  );
728
728
  assert(!genericOutCap.accepts(specificOutRequest),
729
729
  'Generic output cap must NOT satisfy specific output request');
730
730
  }
731
731
 
732
- // TEST052: Specificity: media:bytes(1 tag) + image;png;bytes;thumbnail(4 tags) + op(1) = 6;
733
- // pdf;bytes(2) = 7; CapMatcher prefers higher
732
+ // TEST052: Specificity: media:(0 tags) + image;png;thumbnail(3 tags) + op(1) = 4;
733
+ // pdf(1) + image;png;thumbnail(3) + op(1) = 5; CapMatcher prefers higher
734
734
  function test052_directionSemanticSpecificity() {
735
735
  const genericCap = CapUrn.fromString(
736
- 'cap:in="media:bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
736
+ 'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
737
737
  );
738
738
  const specificCap = CapUrn.fromString(
739
- 'cap:in="media:pdf;bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
739
+ 'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
740
740
  );
741
741
 
742
- assertEqual(genericCap.specificity(), 6, 'bytes(1) + image;png;bytes;thumbnail(4) + op(1) = 6');
743
- assertEqual(specificCap.specificity(), 7, 'pdf;bytes(2) + image;png;bytes;thumbnail(4) + op(1) = 7');
744
- assert(specificCap.specificity() > genericCap.specificity(), 'pdf;bytes should be more specific');
742
+ assertEqual(genericCap.specificity(), 4, 'media:(0) + image;png;thumbnail(3) + op(1) = 4');
743
+ assertEqual(specificCap.specificity(), 5, 'pdf(1) + image;png;thumbnail(3) + op(1) = 5');
744
+ assert(specificCap.specificity() > genericCap.specificity(), 'pdf should be more specific');
745
745
 
746
746
  // CapMatcher should prefer more specific
747
747
  const pdfRequest = CapUrn.fromString(
748
- 'cap:in="media:pdf;bytes";op=generate_thumbnail;out="media:image;png;bytes;thumbnail"'
748
+ 'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
749
749
  );
750
750
  const best = CapMatcher.findBestMatch([genericCap, specificCap], pdfRequest);
751
751
  assert(best !== null, 'Should find a match');
752
- assertEqual(best.getInSpec(), 'media:pdf;bytes', 'Should prefer more specific pdf;bytes cap');
752
+ assertEqual(best.getInSpec(), 'media:pdf', 'Should prefer more specific pdf cap');
753
753
  }
754
754
 
755
755
  // ============================================================================
@@ -811,13 +811,20 @@ function test060_wrongPrefixFails() {
811
811
  );
812
812
  }
813
813
 
814
- // TEST061: isBinary true for media:bytes, MEDIA_PNG, MEDIA_PDF, MEDIA_BINARY; false for media:textable
814
+ // TEST061: isBinary true when textable tag is absent (binary = not textable)
815
815
  function test061_isBinary() {
816
- assert(MediaUrn.fromString('media:bytes').isBinary(), 'media:bytes should be binary');
816
+ // Binary types: no textable tag
817
+ assert(MediaUrn.fromString(MEDIA_BINARY).isBinary(), 'MEDIA_BINARY (media:) should be binary');
817
818
  assert(MediaUrn.fromString(MEDIA_PNG).isBinary(), 'MEDIA_PNG should be binary');
818
819
  assert(MediaUrn.fromString(MEDIA_PDF).isBinary(), 'MEDIA_PDF should be binary');
819
- assert(MediaUrn.fromString(MEDIA_BINARY).isBinary(), 'MEDIA_BINARY should be binary');
820
+ assert(MediaUrn.fromString('media:video').isBinary(), 'media:video should be binary');
821
+ assert(MediaUrn.fromString('media:epub').isBinary(), 'media:epub should be binary');
822
+ // Textable types: is_binary is false
820
823
  assert(!MediaUrn.fromString('media:textable').isBinary(), 'media:textable should not be binary');
824
+ assert(!MediaUrn.fromString('media:textable;form=map').isBinary(), 'textable map should not be binary');
825
+ assert(!MediaUrn.fromString(MEDIA_STRING).isBinary(), 'MEDIA_STRING should not be binary');
826
+ assert(!MediaUrn.fromString(MEDIA_JSON).isBinary(), 'MEDIA_JSON should not be binary');
827
+ assert(!MediaUrn.fromString(MEDIA_MD).isBinary(), 'MEDIA_MD should not be binary');
821
828
  }
822
829
 
823
830
  // TEST062: isMap true for MEDIA_OBJECT (form=map); false for MEDIA_STRING (form=scalar), MEDIA_STRING_ARRAY (form=list)
@@ -911,7 +918,7 @@ function test072_constantsParse() {
911
918
 
912
919
  // TEST073: N/A for JS (Rust has binary_media_urn_for_ext/text_media_urn_for_ext)
913
920
 
914
- // TEST074: MEDIA_PDF (media:pdf;bytes) conformsTo media:pdf; MEDIA_MD conformsTo media:md; same URNs conform
921
+ // TEST074: MEDIA_PDF (media:pdf) conformsTo media:pdf; MEDIA_MD conformsTo media:md; same URNs conform
915
922
  function test074_mediaUrnMatching() {
916
923
  const pdfUrn = MediaUrn.fromString(MEDIA_PDF);
917
924
  const pdfPattern = MediaUrn.fromString('media:pdf');
@@ -938,11 +945,11 @@ function test075_accepts() {
938
945
 
939
946
  // TEST076: More tags = higher specificity
940
947
  function test076_specificity() {
941
- const s1 = MediaUrn.fromString('media:bytes');
942
- const s2 = MediaUrn.fromString('media:pdf;bytes');
943
- const s3 = MediaUrn.fromString('media:image;png;bytes;thumbnail');
944
- assert(s2.specificity() > s1.specificity(), 'pdf;bytes should be more specific than bytes');
945
- assert(s3.specificity() > s2.specificity(), 'image;png;bytes;thumbnail should be more specific than pdf;bytes');
948
+ const s1 = MediaUrn.fromString('media:');
949
+ const s2 = MediaUrn.fromString('media:pdf');
950
+ const s3 = MediaUrn.fromString('media:image;png;thumbnail');
951
+ assert(s2.specificity() > s1.specificity(), 'pdf should be more specific than wildcard');
952
+ assert(s3.specificity() > s2.specificity(), 'image;png;thumbnail should be more specific than pdf');
946
953
  }
947
954
 
948
955
  // TEST077: N/A for JS (Rust serde) - but we test JSON.stringify round-trip
@@ -1015,7 +1022,7 @@ function test093_resolveUnresolvableFailsHard() {
1015
1022
  // TEST097: N/A for JS (Rust validation function)
1016
1023
  // TEST098: N/A for JS
1017
1024
 
1018
- // TEST099: MediaSpec with media:bytes -> isBinary() true
1025
+ // TEST099: MediaSpec with media: (no textable tag) -> isBinary() true
1019
1026
  function test099_resolvedIsBinary() {
1020
1027
  const spec = new MediaSpec('application/octet-stream', null, null, 'Binary', null, MEDIA_BINARY);
1021
1028
  assert(spec.isBinary(), 'Resolved binary spec should be binary');
@@ -1097,13 +1104,13 @@ function test106_metadataWithValidation() {
1097
1104
  function test107_extensionsPropagation() {
1098
1105
  const mediaSpecs = [
1099
1106
  {
1100
- urn: 'media:pdf;bytes',
1107
+ urn: 'media:pdf',
1101
1108
  media_type: 'application/pdf',
1102
1109
  title: 'PDF Document',
1103
1110
  extensions: ['pdf']
1104
1111
  }
1105
1112
  ];
1106
- const resolved = resolveMediaUrn('media:pdf;bytes', mediaSpecs);
1113
+ const resolved = resolveMediaUrn('media:pdf', mediaSpecs);
1107
1114
  assert(Array.isArray(resolved.extensions), 'Extensions should be an array');
1108
1115
  assertEqual(resolved.extensions.length, 1, 'Should have one extension');
1109
1116
  assertEqual(resolved.extensions[0], 'pdf', 'Should have pdf extension');
@@ -1112,7 +1119,7 @@ function test107_extensionsPropagation() {
1112
1119
  // TEST108: N/A for JS (Rust serde) - but we test MediaSpec with extensions
1113
1120
  function test108_extensionsSerialization() {
1114
1121
  // Test that MediaSpec can hold extensions correctly
1115
- const spec = new MediaSpec('application/pdf', null, null, 'PDF', null, 'media:pdf;bytes', null, null, ['pdf']);
1122
+ const spec = new MediaSpec('application/pdf', null, null, 'PDF', null, 'media:pdf', null, null, ['pdf']);
1116
1123
  assert(Array.isArray(spec.extensions), 'Extensions should be array');
1117
1124
  assertEqual(spec.extensions[0], 'pdf', 'Should have pdf extension');
1118
1125
  }
@@ -1140,13 +1147,13 @@ function test109_extensionsWithMetadataAndValidation() {
1140
1147
  function test110_multipleExtensions() {
1141
1148
  const mediaSpecs = [
1142
1149
  {
1143
- urn: 'media:image;jpeg;bytes',
1150
+ urn: 'media:image;jpeg',
1144
1151
  media_type: 'image/jpeg',
1145
1152
  title: 'JPEG Image',
1146
1153
  extensions: ['jpg', 'jpeg']
1147
1154
  }
1148
1155
  ];
1149
- const resolved = resolveMediaUrn('media:image;jpeg;bytes', mediaSpecs);
1156
+ const resolved = resolveMediaUrn('media:image;jpeg', mediaSpecs);
1150
1157
  assertEqual(resolved.extensions.length, 2, 'Should have two extensions');
1151
1158
  assertEqual(resolved.extensions[0], 'jpg', 'First extension should be jpg');
1152
1159
  assertEqual(resolved.extensions[1], 'jpeg', 'Second extension should be jpeg');
@@ -1524,7 +1531,7 @@ function test157_stdinSourceFromFileReference() {
1524
1531
  const trackedFileId = 'tracked-file-123';
1525
1532
  const originalPath = '/path/to/original.pdf';
1526
1533
  const securityBookmark = new Uint8Array([0x62, 0x6f, 0x6f, 0x6b]);
1527
- const mediaUrn = 'media:pdf;bytes';
1534
+ const mediaUrn = 'media:pdf';
1528
1535
 
1529
1536
  const source = StdinSource.fromFileReference(trackedFileId, originalPath, securityBookmark, mediaUrn);
1530
1537
  assert(source !== null, 'Should create source');
@@ -1580,7 +1587,7 @@ function test276_capArgumentValueAsStrValid() {
1580
1587
 
1581
1588
  // TEST277: CapArgumentValue.valueAsStr fails for non-UTF-8 binary data
1582
1589
  function test277_capArgumentValueAsStrInvalidUtf8() {
1583
- const arg = new CapArgumentValue('media:pdf;bytes', new Uint8Array([0xFF, 0xFE, 0x80]));
1590
+ const arg = new CapArgumentValue('media:pdf', new Uint8Array([0xFF, 0xFE, 0x80]));
1584
1591
  let threw = false;
1585
1592
  try {
1586
1593
  arg.valueAsStr();
@@ -1611,7 +1618,7 @@ function test283_capArgumentValueLargeBinary() {
1611
1618
  for (let i = 0; i < 10000; i++) {
1612
1619
  data[i] = i % 256;
1613
1620
  }
1614
- const arg = new CapArgumentValue('media:pdf;bytes', data);
1621
+ const arg = new CapArgumentValue('media:pdf', data);
1615
1622
  assertEqual(arg.value.length, 10000, 'large binary must preserve all bytes');
1616
1623
  assertEqual(arg.value[0], 0, 'first byte check');
1617
1624
  assertEqual(arg.value[255], 255, 'byte 255 check');
@@ -1629,7 +1636,7 @@ function test304_mediaAvailabilityOutputConstant() {
1629
1636
  const urn = TaggedUrn.fromString(MEDIA_AVAILABILITY_OUTPUT);
1630
1637
  assert(urn.getTag('textable') !== undefined, 'model-availability must be textable');
1631
1638
  assertEqual(urn.getTag('form'), 'map', 'model-availability must be form=map');
1632
- assert(urn.getTag('bytes') === undefined, 'model-availability must not be binary');
1639
+ assert(urn.getTag('textable') !== undefined, 'model-availability must not be binary (has textable)');
1633
1640
  const reparsed = TaggedUrn.fromString(urn.toString());
1634
1641
  assert(urn.conformsTo(reparsed), 'roundtrip must match original');
1635
1642
  }
@@ -1639,7 +1646,7 @@ function test305_mediaPathOutputConstant() {
1639
1646
  const urn = TaggedUrn.fromString(MEDIA_PATH_OUTPUT);
1640
1647
  assert(urn.getTag('textable') !== undefined, 'model-path must be textable');
1641
1648
  assertEqual(urn.getTag('form'), 'map', 'model-path must be form=map');
1642
- assert(urn.getTag('bytes') === undefined, 'model-path must not be binary');
1649
+ assert(urn.getTag('textable') !== undefined, 'model-path must not be binary (has textable)');
1643
1650
  const reparsed = TaggedUrn.fromString(urn.toString());
1644
1651
  assert(urn.conformsTo(reparsed), 'roundtrip must match original');
1645
1652
  }
@@ -1731,8 +1738,8 @@ function test312_allUrnBuildersProduceValidUrns() {
1731
1738
 
1732
1739
  function testJS_buildExtensionIndex() {
1733
1740
  const mediaSpecs = [
1734
- { urn: 'media:pdf;bytes', media_type: 'application/pdf', extensions: ['pdf'] },
1735
- { urn: 'media:image;jpeg;bytes', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] },
1741
+ { urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
1742
+ { urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] },
1736
1743
  { urn: 'media:json;textable', media_type: 'application/json', extensions: ['json'] }
1737
1744
  ];
1738
1745
  const index = buildExtensionIndex(mediaSpecs);
@@ -1742,12 +1749,12 @@ function testJS_buildExtensionIndex() {
1742
1749
  assert(index.has('jpg'), 'Should have jpg');
1743
1750
  assert(index.has('jpeg'), 'Should have jpeg');
1744
1751
  assert(index.has('json'), 'Should have json');
1745
- assertEqual(index.get('pdf')[0], 'media:pdf;bytes', 'pdf should map correctly');
1752
+ assertEqual(index.get('pdf')[0], 'media:pdf', 'pdf should map correctly');
1746
1753
  }
1747
1754
 
1748
1755
  function testJS_mediaUrnsForExtension() {
1749
1756
  const mediaSpecs = [
1750
- { urn: 'media:pdf;bytes', media_type: 'application/pdf', extensions: ['pdf'] },
1757
+ { urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
1751
1758
  { urn: 'media:json;textable;form=map', media_type: 'application/json', extensions: ['json'] },
1752
1759
  { urn: 'media:json;textable;form=list', media_type: 'application/json', extensions: ['json'] }
1753
1760
  ];
@@ -1775,8 +1782,8 @@ function testJS_mediaUrnsForExtension() {
1775
1782
 
1776
1783
  function testJS_getExtensionMappings() {
1777
1784
  const mediaSpecs = [
1778
- { urn: 'media:pdf;bytes', media_type: 'application/pdf', extensions: ['pdf'] },
1779
- { urn: 'media:image;jpeg;bytes', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] }
1785
+ { urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
1786
+ { urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] }
1780
1787
  ];
1781
1788
  const mappings = getExtensionMappings(mediaSpecs);
1782
1789
  assert(Array.isArray(mappings), 'Should return an array');
@@ -1885,7 +1892,7 @@ function testJS_binaryArgPassedToExecuteCap() {
1885
1892
  const cube = new CapBlock();
1886
1893
  cube.addRegistry('test', registry);
1887
1894
 
1888
- const binaryArg = new CapArgumentValue('media:pdf;bytes', new Uint8Array([0x89, 0x50, 0x4E, 0x47]));
1895
+ const binaryArg = new CapArgumentValue('media:pdf', new Uint8Array([0x89, 0x50, 0x4E, 0x47]));
1889
1896
  const { compositeHost } = cube.can('cap:in="media:void";op=test;out="media:string"');
1890
1897
 
1891
1898
  return compositeHost.executeCap(
@@ -1893,7 +1900,7 @@ function testJS_binaryArgPassedToExecuteCap() {
1893
1900
  [binaryArg]
1894
1901
  ).then(() => {
1895
1902
  assert(receivedArgs !== null, 'Should receive arguments');
1896
- assertEqual(receivedArgs[0].mediaUrn, 'media:pdf;bytes', 'Correct mediaUrn');
1903
+ assertEqual(receivedArgs[0].mediaUrn, 'media:pdf', 'Correct mediaUrn');
1897
1904
  assertEqual(receivedArgs[0].value[0], 0x89, 'First byte check');
1898
1905
  assertEqual(receivedArgs[0].value.length, 4, 'Correct data length');
1899
1906
  });
@@ -1930,12 +1937,12 @@ const sampleRegistry = {
1930
1937
  tags: ['pdf', 'extractor'],
1931
1938
  caps: [
1932
1939
  {
1933
- urn: 'cap:in="media:pdf;bytes";op=disbind;out="media:disbound-page;textable;form=list"',
1940
+ urn: 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"',
1934
1941
  title: 'Disbind PDF',
1935
1942
  description: 'Extract pages'
1936
1943
  },
1937
1944
  {
1938
- urn: 'cap:in="media:pdf;bytes";op=extract_metadata;out="media:file-metadata;textable;form=map"',
1945
+ urn: 'cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;textable;form=map"',
1939
1946
  title: 'Extract Metadata',
1940
1947
  description: 'Get PDF metadata'
1941
1948
  }
@@ -2143,13 +2150,13 @@ function test328_pluginRepoServerGetByCategory() {
2143
2150
  function test329_pluginRepoServerGetByCap() {
2144
2151
  const server = new PluginRepoServer(sampleRegistry);
2145
2152
 
2146
- const disbindCap = 'cap:in="media:pdf;bytes";op=disbind;out="media:disbound-page;textable;form=list"';
2153
+ const disbindCap = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"';
2147
2154
  const plugins = server.getPluginsByCap(disbindCap);
2148
2155
 
2149
2156
  assert(plugins.length === 1, 'Should find 1 plugin with this cap');
2150
2157
  assert(plugins[0].id === 'pdfcartridge', 'Should be pdfcartridge');
2151
2158
 
2152
- const metadataCap = 'cap:in="media:pdf;bytes";op=extract_metadata;out="media:file-metadata;textable;form=map"';
2159
+ const metadataCap = 'cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;textable;form=map"';
2153
2160
  const metadataPlugins = server.getPluginsByCap(metadataCap);
2154
2161
  assert(metadataPlugins.length === 1, 'Should find metadata cap');
2155
2162
  }
@@ -2176,7 +2183,7 @@ function test331_pluginRepoClientGetSuggestions() {
2176
2183
 
2177
2184
  client.updateCache('https://example.com/api/plugins', plugins);
2178
2185
 
2179
- const disbindCap = 'cap:in="media:pdf;bytes";op=disbind;out="media:disbound-page;textable;form=list"';
2186
+ const disbindCap = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"';
2180
2187
  const suggestions = client.getSuggestionsForCap(disbindCap);
2181
2188
 
2182
2189
  assert(suggestions.length === 1, 'Should find 1 suggestion');
@@ -2254,7 +2261,7 @@ function test335_pluginRepoServerClientIntegration() {
2254
2261
  assert(plugin.hasBinary(), 'Plugin should have binary');
2255
2262
 
2256
2263
  // Client can get suggestions
2257
- const capUrn = 'cap:in="media:pdf;bytes";op=disbind;out="media:disbound-page;textable;form=list"';
2264
+ const capUrn = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"';
2258
2265
  const suggestions = client.getSuggestionsForCap(capUrn);
2259
2266
  assert(suggestions.length === 1, 'Should get suggestions');
2260
2267
  assert(suggestions[0].pluginId === 'pdfcartridge', 'Should suggest correct plugin');
@@ -2273,7 +2280,7 @@ function test335_pluginRepoServerClientIntegration() {
2273
2280
  function test546_isImage() {
2274
2281
  assert(MediaUrn.fromString(MEDIA_PNG).isImage(), 'MEDIA_PNG should be image');
2275
2282
  assert(MediaUrn.fromString(MEDIA_IMAGE_THUMBNAIL).isImage(), 'MEDIA_IMAGE_THUMBNAIL should be image');
2276
- assert(MediaUrn.fromString('media:image;jpg;bytes').isImage(), 'media:image;jpg;bytes should be image');
2283
+ assert(MediaUrn.fromString('media:image;jpg').isImage(), 'media:image;jpg should be image');
2277
2284
  // Non-image types
2278
2285
  assert(!MediaUrn.fromString(MEDIA_PDF).isImage(), 'MEDIA_PDF should not be image');
2279
2286
  assert(!MediaUrn.fromString(MEDIA_STRING).isImage(), 'MEDIA_STRING should not be image');
@@ -2285,7 +2292,7 @@ function test546_isImage() {
2285
2292
  function test547_isAudio() {
2286
2293
  assert(MediaUrn.fromString(MEDIA_AUDIO).isAudio(), 'MEDIA_AUDIO should be audio');
2287
2294
  assert(MediaUrn.fromString(MEDIA_AUDIO_SPEECH).isAudio(), 'MEDIA_AUDIO_SPEECH should be audio');
2288
- assert(MediaUrn.fromString('media:audio;mp3;bytes').isAudio(), 'media:audio;mp3;bytes should be audio');
2295
+ assert(MediaUrn.fromString('media:audio;mp3').isAudio(), 'media:audio;mp3 should be audio');
2289
2296
  // Non-audio types
2290
2297
  assert(!MediaUrn.fromString(MEDIA_VIDEO).isAudio(), 'MEDIA_VIDEO should not be audio');
2291
2298
  assert(!MediaUrn.fromString(MEDIA_PNG).isAudio(), 'MEDIA_PNG should not be audio');
@@ -2295,7 +2302,7 @@ function test547_isAudio() {
2295
2302
  // TEST548: isVideo returns true only when video marker tag is present
2296
2303
  function test548_isVideo() {
2297
2304
  assert(MediaUrn.fromString(MEDIA_VIDEO).isVideo(), 'MEDIA_VIDEO should be video');
2298
- assert(MediaUrn.fromString('media:video;mp4;bytes').isVideo(), 'media:video;mp4;bytes should be video');
2305
+ assert(MediaUrn.fromString('media:video;mp4').isVideo(), 'media:video;mp4 should be video');
2299
2306
  // Non-video types
2300
2307
  assert(!MediaUrn.fromString(MEDIA_AUDIO).isVideo(), 'MEDIA_AUDIO should not be video');
2301
2308
  assert(!MediaUrn.fromString(MEDIA_PNG).isVideo(), 'MEDIA_PNG should not be video');
@@ -2397,11 +2404,11 @@ function test558_predicateConstantConsistency() {
2397
2404
  assert(!jsonUrn.isBinary(), 'MEDIA_JSON must not be binary');
2398
2405
  assert(!jsonUrn.isList(), 'MEDIA_JSON must not be list');
2399
2406
 
2400
- // MEDIA_VOID is void, NOT anything else
2407
+ // MEDIA_VOID is void, NOT text/numeric — but IS binary (no textable tag)
2401
2408
  const voidUrn = MediaUrn.fromString(MEDIA_VOID);
2402
2409
  assert(voidUrn.isVoid(), 'MEDIA_VOID must be void');
2403
2410
  assert(!voidUrn.isText(), 'MEDIA_VOID must not be text');
2404
- assert(!voidUrn.isBinary(), 'MEDIA_VOID must not be binary');
2411
+ assert(voidUrn.isBinary(), 'MEDIA_VOID must be binary (no textable tag)');
2405
2412
  assert(!voidUrn.isNumeric(), 'MEDIA_VOID must not be numeric');
2406
2413
  }
2407
2414
 
@@ -2435,8 +2442,8 @@ function test559_withoutTag() {
2435
2442
  function test560_withInOutSpec() {
2436
2443
  const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
2437
2444
 
2438
- const changedIn = cap.withInSpec('media:bytes');
2439
- assertEqual(changedIn.getInSpec(), 'media:bytes', 'withInSpec should change inSpec');
2445
+ const changedIn = cap.withInSpec('media:');
2446
+ assertEqual(changedIn.getInSpec(), 'media:', 'withInSpec should change inSpec');
2440
2447
  assertEqual(changedIn.getOutSpec(), 'media:void', 'withInSpec should preserve outSpec');
2441
2448
  assertEqual(changedIn.getTag('op'), 'test', 'withInSpec should preserve tags');
2442
2449
 
@@ -2445,8 +2452,8 @@ function test560_withInOutSpec() {
2445
2452
  assertEqual(changedOut.getOutSpec(), 'media:string', 'withOutSpec should change outSpec');
2446
2453
 
2447
2454
  // Chain both
2448
- const changedBoth = cap.withInSpec('media:pdf;bytes').withOutSpec('media:txt;textable');
2449
- assertEqual(changedBoth.getInSpec(), 'media:pdf;bytes', 'Chain should set inSpec');
2455
+ const changedBoth = cap.withInSpec('media:pdf').withOutSpec('media:txt;textable');
2456
+ assertEqual(changedBoth.getInSpec(), 'media:pdf', 'Chain should set inSpec');
2450
2457
  assertEqual(changedBoth.getOutSpec(), 'media:txt;textable', 'Chain should set outSpec');
2451
2458
  }
2452
2459
 
@@ -2501,10 +2508,10 @@ function test564_areCompatible() {
2501
2508
  function test566_withTagIgnoresInOut() {
2502
2509
  const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
2503
2510
  // Attempting to set in/out via withTag is silently ignored
2504
- const same = cap.withTag('in', 'media:bytes');
2511
+ const same = cap.withTag('in', 'media:');
2505
2512
  assertEqual(same.getInSpec(), 'media:void', 'withTag must not change in_spec');
2506
2513
 
2507
- const same2 = cap.withTag('out', 'media:bytes');
2514
+ const same2 = cap.withTag('out', 'media:');
2508
2515
  assertEqual(same2.getOutSpec(), 'media:void', 'withTag must not change out_spec');
2509
2516
  }
2510
2517
 
@@ -2527,10 +2534,10 @@ function test643_explicitAsteriskIsWildcard() {
2527
2534
  assertEqual(cap.getOutSpec(), '*', 'out=* should be stored as wildcard');
2528
2535
  }
2529
2536
 
2530
- // TEST644: cap:in=media:bytes;out=* has specific in, wildcard out
2537
+ // TEST644: cap:in=media:;out=* has specific in, wildcard out
2531
2538
  function test644_specificInWildcardOut() {
2532
- const cap = CapUrn.fromString('cap:in=media:bytes;out=*');
2533
- assertEqual(cap.getInSpec(), 'media:bytes', 'Should have specific in');
2539
+ const cap = CapUrn.fromString('cap:in=media:;out=*');
2540
+ assertEqual(cap.getInSpec(), 'media:', 'Should have specific in');
2534
2541
  assertEqual(cap.getOutSpec(), '*', 'Should have wildcard out');
2535
2542
  }
2536
2543
 
@@ -2547,7 +2554,7 @@ function test645_wildcardInSpecificOut() {
2547
2554
  // TEST648: Wildcard in/out match specific caps
2548
2555
  function test648_wildcardAcceptsSpecific() {
2549
2556
  const wildcard = CapUrn.fromString('cap:in=*;out=*');
2550
- const specific = CapUrn.fromString('cap:in="media:bytes";out="media:text"');
2557
+ const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
2551
2558
 
2552
2559
  assert(wildcard.accepts(specific), 'Wildcard should accept specific');
2553
2560
  assert(specific.conformsTo(wildcard), 'Specific should conform to wildcard');
@@ -2556,7 +2563,7 @@ function test648_wildcardAcceptsSpecific() {
2556
2563
  // TEST649: Specificity - wildcard has 0, specific has tag count
2557
2564
  function test649_specificityScoring() {
2558
2565
  const wildcard = CapUrn.fromString('cap:in=*;out=*');
2559
- const specific = CapUrn.fromString('cap:in="media:bytes";out="media:text"');
2566
+ const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
2560
2567
 
2561
2568
  assertEqual(wildcard.specificity(), 0, 'Wildcard cap should have 0 specificity');
2562
2569
  assert(specific.specificity() > 0, 'Specific cap should have non-zero specificity');
@@ -2576,7 +2583,7 @@ function test651_identityFormsEquivalent() {
2576
2583
  for (let i = 1; i < forms.length; i++) {
2577
2584
  const cap = CapUrn.fromString(forms[i]);
2578
2585
  // Both should accept specific caps
2579
- const specific = CapUrn.fromString('cap:in="media:bytes";out="media:text"');
2586
+ const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
2580
2587
  assert(first.accepts(specific), `Form 0 should accept specific`);
2581
2588
  assert(cap.accepts(specific), `Form ${i} should accept specific`);
2582
2589
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Bahram Joharshamshiri",
3
3
  "dependencies": {
4
- "tagged-urn": "^0.26.4740"
4
+ "tagged-urn": "^0.28.4918"
5
5
  },
6
6
  "description": "JavaScript implementation of Cap URN (Capability Uniform Resource Names) with strict validation and matching",
7
7
  "engines": {
@@ -32,5 +32,5 @@
32
32
  "scripts": {
33
33
  "test": "node capns.test.js"
34
34
  },
35
- "version": "0.84.18923"
35
+ "version": "0.85.19364"
36
36
  }