capdag 0.127.280 → 0.138.305

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/capdag.js +147 -90
  2. package/capdag.test.js +359 -270
  3. package/package.json +2 -2
package/capdag.test.js CHANGED
@@ -12,7 +12,7 @@ const {
12
12
  CapGraphEdge, CapGraphStats, CapGraph,
13
13
  StdinSource, StdinSourceKind,
14
14
  validateNoMediaSpecRedefinitionSync,
15
- CapArgumentValue,
15
+ CapArgumentValue, CapArg, ArgSource, validateCapArgs, ValidationError,
16
16
  llmGenerateTextUrn, modelAvailabilityUrn, modelPathUrn,
17
17
  MachineSyntaxError, MachineSyntaxErrorCodes, MachineEdge, Machine, MachineBuilder, parseMachine, parseMachineWithAST,
18
18
  CapRegistryEntry, MediaRegistryEntry, CapRegistryClient,
@@ -151,7 +151,7 @@ function matrixTestUrn(tags) {
151
151
  // cap_urn.rs: TEST001-TEST050, TEST890-TEST891
152
152
  // ============================================================================
153
153
 
154
- // TEST001: Cap URN created with tags, direction specs accessible
154
+ // TEST001: Test that cap URN is created with tags parsed correctly and direction specs accessible
155
155
  function test001_capUrnCreation() {
156
156
  const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf;target=thumbnail'));
157
157
  assertEqual(cap.getTag('op'), 'generate', 'Should get op tag');
@@ -161,21 +161,18 @@ function test001_capUrnCreation() {
161
161
  assertEqual(cap.getOutSpec(), MEDIA_OBJECT, 'Should get outSpec');
162
162
  }
163
163
 
164
- // TEST002: Missing in -> MissingInSpec, missing out -> MissingOutSpec
164
+ // TEST002: Test that missing 'in' or 'out' defaults to media: wildcard
165
165
  function test002_directionSpecsRequired() {
166
- assertThrows(
167
- () => CapUrn.fromString('cap:out="media:void";op=test'),
168
- ErrorCodes.MISSING_IN_SPEC,
169
- 'Missing in should fail with MISSING_IN_SPEC'
170
- );
171
- assertThrows(
172
- () => CapUrn.fromString('cap:in="media:void";op=test'),
173
- ErrorCodes.MISSING_OUT_SPEC,
174
- 'Missing out should fail with MISSING_OUT_SPEC'
175
- );
166
+ const missingIn = CapUrn.fromString('cap:out="media:void";op=test');
167
+ assertEqual(missingIn.getInSpec(), MEDIA_IDENTITY, 'Missing in should default to media:');
168
+ assertEqual(missingIn.getOutSpec(), MEDIA_VOID, 'Explicit out should be preserved');
169
+
170
+ const missingOut = CapUrn.fromString('cap:in="media:void";op=test');
171
+ assertEqual(missingOut.getInSpec(), MEDIA_VOID, 'Explicit in should be preserved');
172
+ assertEqual(missingOut.getOutSpec(), MEDIA_IDENTITY, 'Missing out should default to media:');
176
173
  }
177
174
 
178
- // TEST003: Direction specs must match exactly; wildcard matches any
175
+ // TEST003: Test that direction specs must match exactly, different in/out types don't match, wildcard matches any
179
176
  function test003_directionMatching() {
180
177
  const cap = CapUrn.fromString(testUrn('op=generate'));
181
178
  const request = CapUrn.fromString(testUrn('op=generate'));
@@ -190,7 +187,7 @@ function test003_directionMatching() {
190
187
  assert(wildcardCap.accepts(request), 'Wildcard direction should match any');
191
188
  }
192
189
 
193
- // TEST004: Unquoted keys/values normalized to lowercase
190
+ // TEST004: Test that unquoted keys and values are normalized to lowercase
194
191
  function test004_unquotedValuesLowercased() {
195
192
  const cap = CapUrn.fromString('cap:IN="media:void";OP=Generate;EXT=PDF;OUT="media:record;textable"');
196
193
  assertEqual(cap.getTag('op'), 'generate', 'Unquoted value should be lowercased');
@@ -199,33 +196,33 @@ function test004_unquotedValuesLowercased() {
199
196
  assertEqual(cap.getTag('OP'), 'generate', 'Key lookup should be case-insensitive');
200
197
  }
201
198
 
202
- // TEST005: Quoted values preserve case
199
+ // TEST005: Test that quoted values preserve case while unquoted are lowercased
203
200
  function test005_quotedValuesPreserveCase() {
204
201
  const cap = CapUrn.fromString('cap:in="media:void";key="HelloWorld";out="media:void"');
205
202
  assertEqual(cap.getTag('key'), 'HelloWorld', 'Quoted value should preserve case');
206
203
  }
207
204
 
208
- // TEST006: Semicolons, equals, spaces in quoted values
205
+ // TEST006: Test that quoted values can contain special characters (semicolons, equals, spaces)
209
206
  function test006_quotedValueSpecialChars() {
210
207
  const cap = CapUrn.fromString('cap:in="media:void";key="val;ue=with spaces";out="media:void"');
211
208
  assertEqual(cap.getTag('key'), 'val;ue=with spaces', 'Quoted value should preserve special chars');
212
209
  }
213
210
 
214
- // TEST007: Escaped quotes and backslashes in quoted values
211
+ // TEST007: Test that escape sequences in quoted values (\" and \\) are parsed correctly
215
212
  function test007_quotedValueEscapeSequences() {
216
213
  const s = String.raw`cap:in="media:void";key="val\"ue\\test";out="media:void"`;
217
214
  const cap = CapUrn.fromString(s);
218
215
  assertEqual(cap.getTag('key'), 'val"ue\\test', 'Escaped quote and backslash should be unescaped');
219
216
  }
220
217
 
221
- // TEST008: Mix of quoted and unquoted values
218
+ // TEST008: Test that mixed quoted and unquoted values in same URN parse correctly
222
219
  function test008_mixedQuotedUnquoted() {
223
220
  const cap = CapUrn.fromString('cap:a=simple;b="Quoted";in="media:void";out="media:void"');
224
221
  assertEqual(cap.getTag('a'), 'simple', 'Unquoted value should be lowercase');
225
222
  assertEqual(cap.getTag('b'), 'Quoted', 'Quoted value should preserve case');
226
223
  }
227
224
 
228
- // TEST009: Unterminated quote produces error
225
+ // TEST009: Test that unterminated quote produces UnterminatedQuote error
229
226
  function test009_unterminatedQuoteError() {
230
227
  let threw = false;
231
228
  try {
@@ -238,7 +235,7 @@ function test009_unterminatedQuoteError() {
238
235
  assert(threw, 'Unterminated quote should produce CapUrnError');
239
236
  }
240
237
 
241
- // TEST010: Invalid escape sequences produce error
238
+ // TEST010: Test that invalid escape sequences (like \n, \x) produce InvalidEscapeSequence error
242
239
  function test010_invalidEscapeSequenceError() {
243
240
  let threw = false;
244
241
  try {
@@ -252,7 +249,7 @@ function test010_invalidEscapeSequenceError() {
252
249
  assert(threw, 'Invalid escape sequence should produce CapUrnError');
253
250
  }
254
251
 
255
- // TEST011: Smart quoting: no quotes for simple lowercase, quotes for special
252
+ // TEST011: Test that serialization uses smart quoting (no quotes for simple lowercase, quotes for special chars/uppercase)
256
253
  function test011_serializationSmartQuoting() {
257
254
  const cap = CapUrn.fromString('cap:a=simple;b="Has Space";in="media:void";out="media:void"');
258
255
  const s = cap.toString();
@@ -261,7 +258,7 @@ function test011_serializationSmartQuoting() {
261
258
  assert(s.includes('b="Has Space"'), 'Value with space should be quoted');
262
259
  }
263
260
 
264
- // TEST012: Simple cap URN parse -> serialize -> parse equals original
261
+ // TEST012: Test that simple cap URN round-trips (parse -> serialize -> parse equals original)
265
262
  function test012_roundTripSimple() {
266
263
  const original = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
267
264
  const serialized = original.toString();
@@ -269,7 +266,7 @@ function test012_roundTripSimple() {
269
266
  assert(original.equals(reparsed), 'Simple round-trip should produce equal URN');
270
267
  }
271
268
 
272
- // TEST013: Quoted values round-trip preserving case
269
+ // TEST013: Test that quoted values round-trip preserving case and spaces
273
270
  function test013_roundTripQuoted() {
274
271
  const original = CapUrn.fromString('cap:in="media:void";key="HelloWorld";out="media:void"');
275
272
  const serialized = original.toString();
@@ -278,7 +275,7 @@ function test013_roundTripQuoted() {
278
275
  assertEqual(reparsed.getTag('key'), 'HelloWorld', 'Quoted value should survive round-trip');
279
276
  }
280
277
 
281
- // TEST014: Escape sequences round-trip correctly
278
+ // TEST014: Test that escape sequences round-trip correctly
282
279
  function test014_roundTripEscapes() {
283
280
  const s = String.raw`cap:in="media:void";key="val\"ue\\test";out="media:void"`;
284
281
  const original = CapUrn.fromString(s);
@@ -288,7 +285,7 @@ function test014_roundTripEscapes() {
288
285
  assertEqual(reparsed.getTag('key'), 'val"ue\\test', 'Escaped value should survive round-trip');
289
286
  }
290
287
 
291
- // TEST015: cap: prefix required, case-insensitive
288
+ // TEST015: Test that cap: prefix is required and case-insensitive
292
289
  function test015_capPrefixRequired() {
293
290
  assertThrows(
294
291
  () => CapUrn.fromString('in="media:void";out="media:void";op=generate'),
@@ -300,7 +297,7 @@ function test015_capPrefixRequired() {
300
297
  assertEqual(cap.getTag('op'), 'generate', 'Should parse with valid cap: prefix');
301
298
  }
302
299
 
303
- // TEST016: With/without trailing semicolon are equivalent
300
+ // TEST016: Test that trailing semicolon is equivalent (same hash, same string, matches)
304
301
  function test016_trailingSemicolonEquivalence() {
305
302
  const cap1 = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
306
303
  const cap2 = CapUrn.fromString(testUrn('op=generate;ext=pdf') + ';');
@@ -308,49 +305,51 @@ function test016_trailingSemicolonEquivalence() {
308
305
  assertEqual(cap1.toString(), cap2.toString(), 'Canonical forms should match');
309
306
  }
310
307
 
311
- // TEST017: Exact match, subset match, wildcard match, value mismatch
308
+ // TEST017: Test tag matching: exact match, subset match, wildcard match, value mismatch
312
309
  function test017_tagMatching() {
313
310
  const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf;target=thumbnail'));
314
311
 
315
- // Exact match
312
+ // Exact match — both directions accept
316
313
  const exact = CapUrn.fromString(testUrn('op=generate;ext=pdf;target=thumbnail'));
317
314
  assert(cap.accepts(exact), 'Should accept exact match');
315
+ assert(exact.accepts(cap), 'Exact match should accept in reverse too');
318
316
 
319
- // Subset match
317
+ // Routing direction: request(op=generate) accepts cap(op,ext,target)
320
318
  const subset = CapUrn.fromString(testUrn('op=generate'));
321
- assert(cap.accepts(subset), 'Should accept subset request');
319
+ assert(subset.accepts(cap), 'General request should accept more specific instance');
320
+ assert(!cap.accepts(subset), 'Specific pattern should reject subset instance');
322
321
 
323
- // Wildcard match
322
+ // Routing direction: request(ext=*) accepts cap(ext=pdf)
324
323
  const wildcard = CapUrn.fromString(testUrn('ext=*'));
325
- assert(cap.accepts(wildcard), 'Should accept wildcard request');
324
+ assert(wildcard.accepts(cap), 'Wildcard request should accept specific instance');
326
325
 
327
- // Value mismatch
326
+ // Conflicting value — neither direction accepts
328
327
  const mismatch = CapUrn.fromString(testUrn('op=extract'));
329
328
  assert(!cap.accepts(mismatch), 'Should not accept value mismatch');
329
+ assert(!mismatch.accepts(cap), 'Reverse mismatch should also reject');
330
330
  }
331
331
 
332
- // TEST018: Quoted uppercase values don't match lowercase (case sensitive)
332
+ // TEST018: Test that quoted values with different case do NOT match (case-sensitive)
333
333
  function test018_matchingCaseSensitiveValues() {
334
334
  const cap = CapUrn.fromString('cap:in="media:void";key="HelloWorld";out="media:void"');
335
335
  const request = CapUrn.fromString('cap:in="media:void";key=helloworld;out="media:void"');
336
336
  assert(!cap.accepts(request), 'Quoted HelloWorld should not match unquoted helloworld');
337
337
  }
338
338
 
339
- // TEST019: Missing tags treated as wildcards
339
+ // TEST019: Missing tag in instance causes rejection — pattern's tags are constraints
340
340
  function test019_missingTagHandling() {
341
341
  const cap = CapUrn.fromString(testUrn('op=generate'));
342
-
343
- // Request with tag cap doesn't have -> cap's missing tag is implicit wildcard
344
342
  const request = CapUrn.fromString(testUrn('ext=pdf'));
345
- assert(cap.accepts(request), 'Missing tag in cap should be treated as wildcard');
343
+ assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
344
+ assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
346
345
 
347
- // Cap with extra tags can accept subset requests
348
346
  const cap2 = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
349
347
  const request2 = CapUrn.fromString(testUrn('op=generate'));
350
- assert(cap2.accepts(request2), 'Cap with extra tags should accept subset request');
348
+ assert(!cap2.accepts(request2), 'Specific pattern should reject instance missing ext');
349
+ assert(request2.accepts(cap2), 'General request should accept more specific instance');
351
350
  }
352
351
 
353
- // TEST020: Direction specs use MediaUrn tag count, other tags count non-wildcard
352
+ // TEST020: Test specificity calculation (direction specs use MediaUrn tag count, wildcards don't count)
354
353
  function test020_specificity() {
355
354
  // Direction specs contribute their MediaUrn tag count:
356
355
  // MEDIA_VOID = "media:void" -> 1 tag (void)
@@ -369,7 +368,7 @@ function test020_specificity() {
369
368
  assertEqual(cap4.specificity(), 2, 'record(1) + op(1) (in wildcard doesn\'t count)');
370
369
  }
371
370
 
372
- // TEST021: CapUrnBuilder creates valid URN
371
+ // TEST021: Test builder creates cap URN with correct tags and direction specs
373
372
  function test021_builder() {
374
373
  const cap = new CapUrnBuilder()
375
374
  .inSpec('media:void')
@@ -383,7 +382,7 @@ function test021_builder() {
383
382
  assertEqual(cap.getOutSpec(), 'media:object', 'Builder should set outSpec');
384
383
  }
385
384
 
386
- // TEST022: Builder requires both inSpec and outSpec
385
+ // TEST022: Test builder requires both in_spec and out_spec
387
386
  function test022_builderRequiresDirection() {
388
387
  assertThrows(
389
388
  () => new CapUrnBuilder().tag('op', 'test').build(),
@@ -397,7 +396,7 @@ function test022_builderRequiresDirection() {
397
396
  );
398
397
  }
399
398
 
400
- // TEST023: Builder lowercases keys but preserves value case
399
+ // TEST023: Test builder lowercases keys but preserves value case
401
400
  function test023_builderPreservesCase() {
402
401
  const cap = new CapUrnBuilder()
403
402
  .inSpec('media:void')
@@ -408,27 +407,27 @@ function test023_builderPreservesCase() {
408
407
  assertEqual(cap.getTag('MyKey'), 'MyValue', 'getTag should be case-insensitive for keys');
409
408
  }
410
409
 
411
- // TEST024: Directional accepts checks
410
+ // TEST024: Directional accepts — pattern's tags are constraints, instance must satisfy
412
411
  function test024_compatibility() {
413
- // General cap accepts specific request (missing tags = wildcards)
414
- const general = CapUrn.fromString(testUrn('op=generate'));
415
- const specific = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
416
- assert(general.accepts(specific), 'General cap should accept specific request');
417
- // Specific cap also accepts general request (cap has extra tag, not blocking)
418
- assert(specific.accepts(general), 'Specific cap accepts general request (extra tags ok)');
419
-
420
- // Different op values: neither accepts the other
421
412
  const cap1 = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
413
+ const cap2 = CapUrn.fromString(testUrn('op=generate;format=*'));
422
414
  const cap3 = CapUrn.fromString(testUrn('type=image;op=extract'));
415
+
416
+ assert(!cap1.accepts(cap2), 'Pattern requiring ext should reject instance missing ext');
417
+ assert(!cap2.accepts(cap1), 'Pattern requiring format should reject instance missing format');
423
418
  assert(!cap1.accepts(cap3), 'Different op should not accept');
424
- assert(!cap3.accepts(cap1), 'Different op should not accept (reverse)');
419
+ assert(!cap3.accepts(cap1), 'Different op should not accept in reverse');
425
420
 
426
- // Different in/out should not accept
427
- const cap5 = CapUrn.fromString('cap:in="media:textable";out="media:object";op=generate');
428
- assert(!cap1.accepts(cap5), 'Different inSpec should not accept');
421
+ const general = CapUrn.fromString(testUrn('op=generate'));
422
+ assert(general.accepts(cap1), 'General request should accept more specific instance');
423
+ assert(!cap1.accepts(general), 'Specific pattern should reject general instance');
424
+
425
+ const broadIn = CapUrn.fromString('cap:in="media:";op=generate;out="media:record;textable"');
426
+ assert(!cap1.accepts(broadIn), 'Specific input should not accept broad wildcard input');
427
+ assert(broadIn.accepts(cap1), 'Wildcard input should accept specific input');
429
428
  }
430
429
 
431
- // TEST025: CapMatcher.findBestMatch returns most specific
430
+ // TEST025: Test find_best_match returns most specific matching cap
432
431
  function test025_bestMatch() {
433
432
  const caps = [
434
433
  CapUrn.fromString('cap:in=*;out=*;op=*'),
@@ -441,7 +440,7 @@ function test025_bestMatch() {
441
440
  assertEqual(best.getTag('ext'), 'pdf', 'Best match should be the most specific (ext=pdf)');
442
441
  }
443
442
 
444
- // TEST026: merge combines tags, subset keeps only specified
443
+ // TEST026: Test merge combines tags from both caps, subset keeps only specified tags
445
444
  function test026_mergeAndSubset() {
446
445
  const cap1 = CapUrn.fromString(testUrn('op=generate'));
447
446
  const cap2 = CapUrn.fromString('cap:in="media:textable";ext=pdf;format=binary;out="media:"');
@@ -460,7 +459,7 @@ function test026_mergeAndSubset() {
460
459
  assertEqual(sub.getInSpec(), 'media:textable', 'Subset should preserve inSpec');
461
460
  }
462
461
 
463
- // TEST027: withWildcardTag sets tag to wildcard including in/out
462
+ // TEST027: Test with_wildcard_tag sets tag to wildcard, including in/out
464
463
  function test027_wildcardTag() {
465
464
  const cap = CapUrn.fromString(testUrn('ext=pdf'));
466
465
  const wildcardExt = cap.withWildcardTag('ext');
@@ -473,16 +472,14 @@ function test027_wildcardTag() {
473
472
  assertEqual(wildcardOut.getOutSpec(), '*', 'Should set out to wildcard');
474
473
  }
475
474
 
476
- // TEST028: Empty cap URN without in/out fails (MissingInSpec)
475
+ // TEST028: Test empty cap URN defaults to media: wildcard
477
476
  function test028_emptyCapUrnNotAllowed() {
478
- assertThrows(
479
- () => CapUrn.fromString('cap:'),
480
- ErrorCodes.MISSING_IN_SPEC,
481
- 'Empty cap URN should fail with MISSING_IN_SPEC'
482
- );
477
+ const empty = CapUrn.fromString('cap:');
478
+ assertEqual(empty.getInSpec(), MEDIA_IDENTITY, 'Empty cap should default in to media:');
479
+ assertEqual(empty.getOutSpec(), MEDIA_IDENTITY, 'Empty cap should default out to media:');
483
480
  }
484
481
 
485
- // TEST029: Minimal valid cap URN: just in and out, empty tags
482
+ // TEST029: Test minimal valid cap URN has just in and out, empty tags
486
483
  function test029_minimalCapUrn() {
487
484
  const minimal = CapUrn.fromString('cap:in="media:void";out="media:void"');
488
485
  assertEqual(Object.keys(minimal.tags).length, 0, 'Should have no other tags');
@@ -490,14 +487,14 @@ function test029_minimalCapUrn() {
490
487
  assertEqual(minimal.getOutSpec(), 'media:void', 'Should have outSpec');
491
488
  }
492
489
 
493
- // TEST030: Forward slashes and colons in tag values
490
+ // TEST030: Test extended characters (forward slashes, colons) in tag values
494
491
  function test030_extendedCharacterSupport() {
495
492
  const cap = CapUrn.fromString(testUrn('url=https://example_org/api;path=/some/file'));
496
493
  assertEqual(cap.getTag('url'), 'https://example_org/api', 'Should support colons and slashes');
497
494
  assertEqual(cap.getTag('path'), '/some/file', 'Should support forward slashes');
498
495
  }
499
496
 
500
- // TEST031: Wildcard rejected in keys, accepted in values
497
+ // TEST031: Test wildcard rejected in keys but accepted in values
501
498
  function test031_wildcardRestrictions() {
502
499
  assertThrows(
503
500
  () => CapUrn.fromString(testUrn('*=value')),
@@ -509,13 +506,13 @@ function test031_wildcardRestrictions() {
509
506
  const cap = CapUrn.fromString(testUrn('key=*'));
510
507
  assertEqual(cap.getTag('key'), '*', 'Should accept wildcard in value');
511
508
 
512
- // Wildcard in in/out
509
+ // Wildcard in in/out normalizes to media:
513
510
  const capWild = CapUrn.fromString('cap:in=*;out=*;key=value');
514
- assertEqual(capWild.getInSpec(), '*', 'Should accept wildcard in inSpec');
515
- assertEqual(capWild.getOutSpec(), '*', 'Should accept wildcard in outSpec');
511
+ assertEqual(capWild.getInSpec(), MEDIA_IDENTITY, 'Wildcard inSpec should normalize to media:');
512
+ assertEqual(capWild.getOutSpec(), MEDIA_IDENTITY, 'Wildcard outSpec should normalize to media:');
516
513
  }
517
514
 
518
- // TEST032: Duplicate keys rejected
515
+ // TEST032: Test duplicate keys are rejected with DuplicateKey error
519
516
  function test032_duplicateKeyRejection() {
520
517
  assertThrows(
521
518
  () => CapUrn.fromString(testUrn('key=value1;key=value2')),
@@ -524,7 +521,7 @@ function test032_duplicateKeyRejection() {
524
521
  );
525
522
  }
526
523
 
527
- // TEST033: Pure numeric keys rejected, mixed alphanumeric OK
524
+ // TEST033: Test pure numeric keys rejected, mixed alphanumeric allowed, numeric values allowed
528
525
  function test033_numericKeyRestriction() {
529
526
  assertThrows(
530
527
  () => CapUrn.fromString(testUrn('123=value')),
@@ -538,7 +535,7 @@ function test033_numericKeyRestriction() {
538
535
  assertEqual(cap2.getTag('x123key'), 'value', 'Mixed alphanumeric key should be allowed');
539
536
  }
540
537
 
541
- // TEST034: key= (empty value) is rejected
538
+ // TEST034: Test empty values are rejected
542
539
  function test034_emptyValueError() {
543
540
  let threw = false;
544
541
  try {
@@ -551,7 +548,7 @@ function test034_emptyValueError() {
551
548
  assert(threw, 'Empty value (key=) should be rejected');
552
549
  }
553
550
 
554
- // TEST035: hasTag case-sensitive for values, case-insensitive for keys, works for in/out
551
+ // TEST035: Test has_tag is case-sensitive for values, case-insensitive for keys, works for in/out
555
552
  function test035_hasTagCaseSensitive() {
556
553
  const cap = CapUrn.fromString('cap:in="media:void";key="Value";out="media:void"');
557
554
  assert(cap.hasTag('key', 'Value'), 'hasTag should match exact value');
@@ -563,26 +560,24 @@ function test035_hasTagCaseSensitive() {
563
560
  assert(cap.hasTag('out', 'media:void'), 'hasTag should work for out');
564
561
  }
565
562
 
566
- // TEST036: withTag preserves value case
563
+ // TEST036: Test with_tag preserves value case
567
564
  function test036_withTagPreservesValue() {
568
565
  const cap = CapUrn.fromString('cap:in="media:void";out="media:void"');
569
566
  const modified = cap.withTag('key', 'MyValue');
570
567
  assertEqual(modified.getTag('key'), 'MyValue', 'withTag should preserve value case');
571
568
  }
572
569
 
573
- // TEST037: withTag('key', '') -> error
574
- // Note: In JS, withTag does not currently reject empty values (it stores them).
575
- // The Rust implementation rejects empty values. We test the JS behavior as-is.
570
+ // TEST037: Test with_tag rejects empty value
576
571
  function test037_withTagRejectsEmptyValue() {
577
- // The JS implementation does not throw for empty values in withTag.
578
- // This test verifies the current behavior: withTag stores empty string.
579
- // If the implementation is updated to reject empty values, update this test.
580
572
  const cap = CapUrn.fromString('cap:in="media:void";out="media:void"');
581
- const modified = cap.withTag('key', '');
582
- assertEqual(modified.getTag('key'), '', 'withTag stores empty string (JS behavior)');
573
+ assertThrows(
574
+ () => cap.withTag('key', ''),
575
+ ErrorCodes.EMPTY_VALUE,
576
+ 'withTag should reject empty string values'
577
+ );
583
578
  }
584
579
 
585
- // TEST038: Unquoted 'simple' == quoted '"simple"' (lowercase)
580
+ // TEST038: Test semantic equivalence of unquoted and quoted simple lowercase values
586
581
  function test038_semanticEquivalence() {
587
582
  const c1 = CapUrn.fromString('cap:in="media:void";key=simple;out="media:void"');
588
583
  const c2 = CapUrn.fromString('cap:in="media:void";key="simple";out="media:void"');
@@ -590,7 +585,7 @@ function test038_semanticEquivalence() {
590
585
  assertEqual(c1.getTag('key'), c2.getTag('key'), 'Values should be identical');
591
586
  }
592
587
 
593
- // TEST039: getTag('in') and getTag('out') work, case-insensitive
588
+ // TEST039: Test get_tag returns direction specs (in/out) with case-insensitive lookup
594
589
  function test039_getTagReturnsDirectionSpecs() {
595
590
  const cap = CapUrn.fromString(`cap:in="${MEDIA_VOID}";out="${MEDIA_OBJECT}"`);
596
591
  assertEqual(cap.getTag('in'), MEDIA_VOID, 'getTag(in) should return inSpec');
@@ -599,77 +594,81 @@ function test039_getTagReturnsDirectionSpecs() {
599
594
  assertEqual(cap.getTag('OUT'), MEDIA_OBJECT, 'getTag(OUT) should return outSpec (case-insensitive)');
600
595
  }
601
596
 
602
- // TEST040: Cap and request same tags -> accepts=true
597
+ // TEST040: Matching semantics - exact match succeeds
603
598
  function test040_matchingSemanticsExactMatch() {
604
599
  const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
605
600
  const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
606
601
  assert(cap.accepts(request), 'Exact match should accept');
607
602
  }
608
603
 
609
- // TEST041: Cap missing tag -> implicit wildcard -> accepts=true
604
+ // TEST041: Matching semantics - cap missing tag matches (implicit wildcard)
610
605
  function test041_matchingSemanticsCapMissingTag() {
611
606
  const cap = CapUrn.fromString(testUrn('op=generate'));
612
607
  const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
613
- assert(cap.accepts(request), 'Cap missing ext tag should accept (implicit wildcard)');
608
+ assert(cap.accepts(request), 'General pattern with only op should accept specific instance');
609
+ assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
614
610
  }
615
611
 
616
- // TEST042: Cap extra tag -> still matches
612
+ // TEST042: Pattern rejects instance missing required tags
617
613
  function test042_matchingSemanticsCapHasExtraTag() {
618
614
  const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf;version=2'));
619
615
  const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
620
- assert(cap.accepts(request), 'Cap with extra tag should still accept');
616
+ assert(!cap.accepts(request), 'Pattern requiring version should reject instance missing version');
617
+ assert(request.accepts(cap), 'General request should accept refined instance');
621
618
  }
622
619
 
623
- // TEST043: Request ext=* matches cap ext=pdf
620
+ // TEST043: Matching semantics - request wildcard matches specific cap value
624
621
  function test043_matchingSemanticsRequestHasWildcard() {
625
622
  const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
626
623
  const request = CapUrn.fromString(testUrn('op=generate;ext=*'));
627
624
  assert(cap.accepts(request), 'Request wildcard should match specific cap value');
628
625
  }
629
626
 
630
- // TEST044: Cap ext=* matches request ext=pdf
627
+ // TEST044: Matching semantics - cap wildcard matches specific request value
631
628
  function test044_matchingSemanticsCapHasWildcard() {
632
629
  const cap = CapUrn.fromString(testUrn('op=generate;ext=*'));
633
630
  const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
634
631
  assert(cap.accepts(request), 'Cap wildcard should match specific request value');
635
632
  }
636
633
 
637
- // TEST045: ext=pdf vs ext=docx -> no match
634
+ // TEST045: Matching semantics - value mismatch does not match
638
635
  function test045_matchingSemanticsValueMismatch() {
639
636
  const cap = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
640
637
  const request = CapUrn.fromString(testUrn('op=generate;ext=docx'));
641
638
  assert(!cap.accepts(request), 'Value mismatch should not accept');
642
639
  }
643
640
 
644
- // TEST046: Cap without ext matches request with ext=wav (uses media:binary directions)
641
+ // TEST046: Matching semantics - fallback pattern (cap missing tag = implicit wildcard)
645
642
  function test046_matchingSemanticsFallbackPattern() {
646
643
  const cap = CapUrn.fromString('cap:in="media:binary";op=generate_thumbnail;out="media:binary"');
647
644
  const request = CapUrn.fromString('cap:ext=wav;in="media:binary";op=generate_thumbnail;out="media:binary"');
648
- assert(cap.accepts(request), 'Cap missing ext should accept (implicit wildcard for ext)');
645
+ assert(cap.accepts(request), 'General pattern without ext should accept specific instance');
646
+ assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
649
647
  }
650
648
 
651
- // TEST047: Thumbnail with void input matches specific ext request
649
+ // TEST047: Matching semantics - thumbnail fallback with void input
652
650
  function test047_matchingSemanticsThumbnailVoidInput() {
653
651
  const cap = CapUrn.fromString('cap:in="media:void";op=generate_thumbnail;out="media:image;png;thumbnail"');
654
652
  const request = CapUrn.fromString('cap:ext=pdf;in="media:void";op=generate_thumbnail;out="media:image"');
655
653
  assert(cap.accepts(request), 'Void input cap should accept request; cap output conforms to less-specific request output');
656
654
  }
657
655
 
658
- // TEST048: Cap in=* out=* matches any request
656
+ // TEST048: Matching semantics - wildcard direction matches anything
659
657
  function test048_matchingSemanticsWildcardDirection() {
660
658
  const cap = CapUrn.fromString('cap:in=*;out=*');
661
659
  const request = CapUrn.fromString(testUrn('op=generate;ext=pdf'));
662
660
  assert(cap.accepts(request), 'Wildcard cap should accept any request');
663
661
  }
664
662
 
665
- // TEST049: Cap op=generate accepts request ext=pdf (independent tags)
663
+ // TEST049: Non-overlapping tags neither direction accepts
666
664
  function test049_matchingSemanticsCrossDimension() {
667
665
  const cap = CapUrn.fromString(testUrn('op=generate'));
668
666
  const request = CapUrn.fromString(testUrn('ext=pdf'));
669
- assert(cap.accepts(request), 'Independent tags should not block matching');
667
+ assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
668
+ assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
670
669
  }
671
670
 
672
- // TEST050: media:string vs media: (wildcard) -> no match
671
+ // TEST050: Matching semantics - direction mismatch prevents matching
673
672
  function test050_matchingSemanticsDirectionMismatch() {
674
673
  const cap = CapUrn.fromString(
675
674
  `cap:in="${MEDIA_STRING}";op=generate;out="${MEDIA_OBJECT}"`
@@ -758,7 +757,7 @@ function test891_directionSemanticSpecificity() {
758
757
 
759
758
  // TEST053: N/A for JS (Rust-only validation infrastructure)
760
759
 
761
- // TEST054: Inline media spec redefinition of registry spec is detected
760
+ // TEST054: XV5 - Test inline media spec redefinition of existing registry spec is detected and rejected
762
761
  function test054_xv5InlineSpecRedefinitionDetected() {
763
762
  const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
764
763
  const mediaSpecs = [
@@ -775,7 +774,7 @@ function test054_xv5InlineSpecRedefinitionDetected() {
775
774
  assert(result.redefines && result.redefines.includes(MEDIA_STRING), 'Should identify MEDIA_STRING as redefined');
776
775
  }
777
776
 
778
- // TEST055: New inline media spec not in registry is allowed
777
+ // TEST055: XV5 - Test new inline media spec (not in registry) is allowed
779
778
  function test055_xv5NewInlineSpecAllowed() {
780
779
  const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
781
780
  const mediaSpecs = [
@@ -790,7 +789,7 @@ function test055_xv5NewInlineSpecAllowed() {
790
789
  assert(result.valid, 'New spec not in registry should pass validation');
791
790
  }
792
791
 
793
- // TEST056: Empty/null media_specs passes validation
792
+ // TEST056: XV5 - Test empty media_specs (no inline specs) passes XV5 validation
794
793
  function test056_xv5EmptyMediaSpecsAllowed() {
795
794
  const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
796
795
  assert(validateNoMediaSpecRedefinitionSync({}, registryLookup).valid, 'Empty object should pass');
@@ -802,7 +801,7 @@ function test056_xv5EmptyMediaSpecsAllowed() {
802
801
  // media_urn.rs: TEST060-TEST078
803
802
  // ============================================================================
804
803
 
805
- // TEST060: MediaUrn.fromString('cap:string') -> INVALID_PREFIX error
804
+ // TEST060: Test wrong prefix fails with InvalidPrefix error showing expected and actual prefix
806
805
  function test060_wrongPrefixFails() {
807
806
  assertThrowsMediaUrn(
808
807
  () => MediaUrn.fromString('cap:string'),
@@ -811,7 +810,7 @@ function test060_wrongPrefixFails() {
811
810
  );
812
811
  }
813
812
 
814
- // TEST061: isBinary true when textable tag is absent (binary = not textable)
813
+ // TEST061: Test is_binary returns true when textable tag is absent (binary = not textable)
815
814
  function test061_isBinary() {
816
815
  // Binary types: no textable tag
817
816
  assert(MediaUrn.fromString(MEDIA_IDENTITY).isBinary(), 'MEDIA_IDENTITY (media:) should be binary');
@@ -827,8 +826,7 @@ function test061_isBinary() {
827
826
  assert(!MediaUrn.fromString(MEDIA_MD).isBinary(), 'MEDIA_MD should not be binary');
828
827
  }
829
828
 
830
- // TEST062: isMap true for MEDIA_OBJECT (record); false for MEDIA_STRING (form=scalar), MEDIA_STRING_LIST (list)
831
- // TEST062: is_record returns true if record marker tag is present (key-value structure)
829
+ // TEST062: Test is_record returns true when record marker tag is present indicating key-value structure
832
830
  function test062_isRecord() {
833
831
  assert(MediaUrn.fromString(MEDIA_OBJECT).isRecord(), 'MEDIA_OBJECT should be record');
834
832
  assert(MediaUrn.fromString('media:custom;record').isRecord(), 'custom;record should be record');
@@ -839,7 +837,7 @@ function test062_isRecord() {
839
837
  assert(!MediaUrn.fromString(MEDIA_STRING_LIST).isRecord(), 'MEDIA_STRING_LIST should not be record');
840
838
  }
841
839
 
842
- // TEST063: is_scalar returns true if NO list marker (scalar is default cardinality)
840
+ // TEST063: Test is_scalar returns true when list marker tag is absent (scalar is default)
843
841
  function test063_isScalar() {
844
842
  assert(MediaUrn.fromString(MEDIA_STRING).isScalar(), 'MEDIA_STRING should be scalar');
845
843
  assert(MediaUrn.fromString(MEDIA_INTEGER).isScalar(), 'MEDIA_INTEGER should be scalar');
@@ -852,8 +850,7 @@ function test063_isScalar() {
852
850
  assert(!MediaUrn.fromString(MEDIA_OBJECT_LIST).isScalar(), 'MEDIA_OBJECT_LIST should not be scalar');
853
851
  }
854
852
 
855
- // TEST064: isList true for MEDIA_STRING_LIST, MEDIA_INTEGER_LIST, MEDIA_OBJECT_LIST;
856
- // false for MEDIA_STRING, MEDIA_OBJECT
853
+ // TEST064: Test is_list returns true when list marker tag is present indicating ordered collection
857
854
  function test064_isList() {
858
855
  assert(MediaUrn.fromString(MEDIA_STRING_LIST).isList(), 'MEDIA_STRING_LIST should be list');
859
856
  assert(MediaUrn.fromString(MEDIA_INTEGER_LIST).isList(), 'MEDIA_INTEGER_LIST should be list');
@@ -862,7 +859,7 @@ function test064_isList() {
862
859
  assert(!MediaUrn.fromString(MEDIA_OBJECT).isList(), 'MEDIA_OBJECT should not be list');
863
860
  }
864
861
 
865
- // TEST065: is_opaque returns true if NO record marker (opaque is default structure)
862
+ // TEST065: Test is_opaque returns true when record marker is absent (opaque is default)
866
863
  function test065_isOpaque() {
867
864
  assert(MediaUrn.fromString(MEDIA_STRING).isOpaque(), 'MEDIA_STRING should be opaque');
868
865
  assert(MediaUrn.fromString(MEDIA_STRING_LIST).isOpaque(), 'MEDIA_STRING_LIST (list but no record) should be opaque');
@@ -873,13 +870,13 @@ function test065_isOpaque() {
873
870
  assert(!MediaUrn.fromString(MEDIA_JSON).isOpaque(), 'MEDIA_JSON should not be opaque');
874
871
  }
875
872
 
876
- // TEST066: isJson true for MEDIA_JSON; false for MEDIA_OBJECT (map but not json)
873
+ // TEST066: Test is_json returns true only when json marker tag is present for JSON representation
877
874
  function test066_isJson() {
878
875
  assert(MediaUrn.fromString(MEDIA_JSON).isJson(), 'MEDIA_JSON should be json');
879
876
  assert(!MediaUrn.fromString(MEDIA_OBJECT).isJson(), 'MEDIA_OBJECT should not be json');
880
877
  }
881
878
 
882
- // TEST067: is_text returns true only if "textable" marker tag is present
879
+ // TEST067: Test is_text returns true only when textable marker tag is present
883
880
  function test067_isText() {
884
881
  assert(MediaUrn.fromString(MEDIA_STRING).isText(), 'MEDIA_STRING should be text');
885
882
  assert(MediaUrn.fromString(MEDIA_INTEGER).isText(), 'MEDIA_INTEGER should be text');
@@ -890,7 +887,7 @@ function test067_isText() {
890
887
  assert(!MediaUrn.fromString(MEDIA_OBJECT).isText(), 'MEDIA_OBJECT (no textable) should not be text');
891
888
  }
892
889
 
893
- // TEST068: isVoid true for media:void; false for media:string
890
+ // TEST068: Test is_void returns true when void flag or type=void tag is present
894
891
  function test068_isVoid() {
895
892
  assert(MediaUrn.fromString('media:void').isVoid(), 'media:void should be void');
896
893
  assert(!MediaUrn.fromString(MEDIA_STRING).isVoid(), 'MEDIA_STRING should not be void');
@@ -898,7 +895,7 @@ function test068_isVoid() {
898
895
 
899
896
  // TEST069-TEST070: N/A for JS (Rust-only binary_media_urn_for_ext/text_media_urn_for_ext)
900
897
 
901
- // TEST071: Parse -> toString -> parse equals original
898
+ // TEST071: Test to_string roundtrip ensures serialization and deserialization preserve URN structure
902
899
  function test071_toStringRoundtrip() {
903
900
  const constants = [MEDIA_STRING, MEDIA_INTEGER, MEDIA_OBJECT, MEDIA_IDENTITY, MEDIA_PDF, MEDIA_JSON];
904
901
  for (const constant of constants) {
@@ -908,7 +905,7 @@ function test071_toStringRoundtrip() {
908
905
  }
909
906
  }
910
907
 
911
- // TEST072: All MEDIA_* constants parse as valid MediaUrns
908
+ // TEST072: Test all media URN constants parse successfully as valid media URNs
912
909
  function test072_constantsParse() {
913
910
  const constants = [
914
911
  MEDIA_STRING, MEDIA_INTEGER, MEDIA_NUMBER, MEDIA_BOOLEAN,
@@ -928,7 +925,7 @@ function test072_constantsParse() {
928
925
 
929
926
  // TEST073: N/A for JS (Rust has binary_media_urn_for_ext/text_media_urn_for_ext)
930
927
 
931
- // TEST074: MEDIA_PDF (media:pdf) conformsTo media:pdf; MEDIA_MD conformsTo media:md; same URNs conform
928
+ // TEST074: Test media URN conforms_to using tagged URN semantics with specific and generic requirements
932
929
  function test074_mediaUrnMatching() {
933
930
  const pdfUrn = MediaUrn.fromString(MEDIA_PDF);
934
931
  const pdfPattern = MediaUrn.fromString('media:pdf');
@@ -942,7 +939,7 @@ function test074_mediaUrnMatching() {
942
939
  assert(pdfUrn.conformsTo(pdfUrn), 'Same URN should conform to itself');
943
940
  }
944
941
 
945
- // TEST075: handler accepts same request, general handler accepts request
942
+ // TEST075: Test accepts with implicit wildcards where handlers with fewer tags can handle more requests
946
943
  function test075_accepts() {
947
944
  const handler = MediaUrn.fromString(MEDIA_PDF);
948
945
  const sameReq = MediaUrn.fromString(MEDIA_PDF);
@@ -953,7 +950,7 @@ function test075_accepts() {
953
950
  assert(generalHandler.accepts(specificReq), 'General handler should accept specific request');
954
951
  }
955
952
 
956
- // TEST076: More tags = higher specificity
953
+ // TEST076: Test specificity increases with more tags for ranking conformance
957
954
  function test076_specificity() {
958
955
  const s1 = MediaUrn.fromString('media:');
959
956
  const s2 = MediaUrn.fromString('media:pdf');
@@ -962,7 +959,7 @@ function test076_specificity() {
962
959
  assert(s3.specificity() > s2.specificity(), 'image;png;thumbnail should be more specific than pdf');
963
960
  }
964
961
 
965
- // TEST077: N/A for JS (Rust serde) - but we test JSON.stringify round-trip
962
+ // TEST077: Test serde roundtrip serializes to JSON string and deserializes back correctly
966
963
  function test077_serdeRoundtrip() {
967
964
  const original = MediaUrn.fromString(MEDIA_PDF);
968
965
  const json = JSON.stringify({ urn: original.toString() });
@@ -971,7 +968,7 @@ function test077_serdeRoundtrip() {
971
968
  assert(original.equals(restored), 'JSON round-trip should preserve MediaUrn');
972
969
  }
973
970
 
974
- // TEST078: MEDIA_OBJECT does NOT conform to MEDIA_STRING
971
+ // TEST078: conforms_to behavior between MEDIA_OBJECT and MEDIA_STRING
975
972
  function test078_debugMatchingBehavior() {
976
973
  const objUrn = MediaUrn.fromString(MEDIA_OBJECT);
977
974
  const strUrn = MediaUrn.fromString(MEDIA_STRING);
@@ -986,7 +983,7 @@ function test078_debugMatchingBehavior() {
986
983
  // TEST089: N/A for JS
987
984
  // TEST090: N/A for JS
988
985
 
989
- // TEST091: resolveMediaUrn resolves custom from local mediaSpecs
986
+ // TEST091: Test resolving custom media URN from local media_specs takes precedence over registry
990
987
  function test091_resolveCustomMediaSpec() {
991
988
  const mediaSpecs = [
992
989
  { urn: 'media:custom-json', media_type: 'application/json', title: 'Custom JSON', profile_uri: 'https://example.com/schema/custom' }
@@ -996,7 +993,7 @@ function test091_resolveCustomMediaSpec() {
996
993
  assertEqual(spec.profile, 'https://example.com/schema/custom', 'Should have custom profile');
997
994
  }
998
995
 
999
- // TEST092: resolveMediaUrn resolves with schema from local mediaSpecs
996
+ // TEST092: Test resolving custom record media spec with schema from local media_specs
1000
997
  function test092_resolveCustomWithSchema() {
1001
998
  const mediaSpecs = [
1002
999
  {
@@ -1013,7 +1010,7 @@ function test092_resolveCustomWithSchema() {
1013
1010
  assertEqual(spec.schema.type, 'object', 'Schema should have correct type');
1014
1011
  }
1015
1012
 
1016
- // TEST093: resolveMediaUrn fails hard on unknown URN
1013
+ // TEST093: Test resolving unknown media URN fails with UnresolvableMediaUrn error
1017
1014
  function test093_resolveUnresolvableFailsHard() {
1018
1015
  let caught = false;
1019
1016
  try {
@@ -1032,43 +1029,43 @@ function test093_resolveUnresolvableFailsHard() {
1032
1029
  // TEST097: N/A for JS (Rust validation function)
1033
1030
  // TEST098: N/A for JS
1034
1031
 
1035
- // TEST099: MediaSpec with media: (no textable tag) -> isBinary() true
1032
+ // TEST099: Test ResolvedMediaSpec is_binary returns true when textable tag is absent
1036
1033
  function test099_resolvedIsBinary() {
1037
1034
  const spec = new MediaSpec('application/octet-stream', null, null, 'Binary', null, MEDIA_IDENTITY);
1038
1035
  assert(spec.isBinary(), 'Resolved binary spec should be binary');
1039
1036
  }
1040
1037
 
1041
- // TEST100: MediaSpec with record -> isRecord() true
1038
+ // TEST100: Test ResolvedMediaSpec is_record returns true when record marker is present
1042
1039
  function test100_resolvedIsRecord() {
1043
1040
  const spec = new MediaSpec('application/json', null, null, 'Object', null, MEDIA_OBJECT);
1044
1041
  assert(spec.isRecord(), 'Resolved object spec should be record');
1045
1042
  }
1046
1043
 
1047
- // TEST101: MediaSpec with form=scalar -> isScalar() true
1044
+ // TEST101: Test ResolvedMediaSpec is_scalar returns true when list marker is absent
1048
1045
  function test101_resolvedIsScalar() {
1049
1046
  const spec = new MediaSpec('text/plain', null, null, 'String', null, MEDIA_STRING);
1050
1047
  assert(spec.isScalar(), 'Resolved string spec should be scalar');
1051
1048
  }
1052
1049
 
1053
- // TEST102: MediaSpec with list -> isList() true
1050
+ // TEST102: Test ResolvedMediaSpec is_list returns true when list marker is present
1054
1051
  function test102_resolvedIsList() {
1055
1052
  const spec = new MediaSpec('text/plain', null, null, 'String List', null, MEDIA_STRING_LIST);
1056
1053
  assert(spec.isList(), 'Resolved string_list spec should be list');
1057
1054
  }
1058
1055
 
1059
- // TEST103: MediaSpec with json tag -> isJSON() true
1056
+ // TEST103: Test ResolvedMediaSpec is_json returns true when json tag is present
1060
1057
  function test103_resolvedIsJson() {
1061
1058
  const spec = new MediaSpec('application/json', null, null, 'JSON', null, MEDIA_JSON);
1062
1059
  assert(spec.isJSON(), 'Resolved json spec should be JSON');
1063
1060
  }
1064
1061
 
1065
- // TEST104: MediaSpec with textable tag -> isText() true
1062
+ // TEST104: Test ResolvedMediaSpec is_text returns true when textable tag is present
1066
1063
  function test104_resolvedIsText() {
1067
1064
  const spec = new MediaSpec('text/plain', null, null, 'String', null, MEDIA_STRING);
1068
1065
  assert(spec.isText(), 'Resolved string spec should be text');
1069
1066
  }
1070
1067
 
1071
- // TEST105: Metadata propagated from media spec definition
1068
+ // TEST105: Test metadata propagates from media spec def to resolved media spec
1072
1069
  function test105_metadataPropagation() {
1073
1070
  const mediaSpecs = [
1074
1071
  {
@@ -1091,7 +1088,7 @@ function test105_metadataPropagation() {
1091
1088
  assertEqual(resolved.metadata.display_index, 5, 'Should propagate display_index');
1092
1089
  }
1093
1090
 
1094
- // TEST106: Metadata and validation coexist
1091
+ // TEST106: Test metadata and validation can coexist in media spec definition
1095
1092
  function test106_metadataWithValidation() {
1096
1093
  const mediaSpecs = [
1097
1094
  {
@@ -1110,7 +1107,7 @@ function test106_metadataWithValidation() {
1110
1107
  assertEqual(resolved.metadata.category_key, 'inference', 'Should have category_key');
1111
1108
  }
1112
1109
 
1113
- // TEST107: Extensions field propagated
1110
+ // TEST107: Test extensions field propagates from media spec def to resolved
1114
1111
  function test107_extensionsPropagation() {
1115
1112
  const mediaSpecs = [
1116
1113
  {
@@ -1126,7 +1123,7 @@ function test107_extensionsPropagation() {
1126
1123
  assertEqual(resolved.extensions[0], 'pdf', 'Should have pdf extension');
1127
1124
  }
1128
1125
 
1129
- // TEST108: N/A for JS (Rust serde) - but we test MediaSpec with extensions
1126
+ // TEST108: Test creating new cap with URN, title, and command verifies correct initialization
1130
1127
  function test108_extensionsSerialization() {
1131
1128
  // Test that MediaSpec can hold extensions correctly
1132
1129
  const spec = new MediaSpec('application/pdf', null, null, 'PDF', null, 'media:pdf', null, null, ['pdf']);
@@ -1134,7 +1131,7 @@ function test108_extensionsSerialization() {
1134
1131
  assertEqual(spec.extensions[0], 'pdf', 'Should have pdf extension');
1135
1132
  }
1136
1133
 
1137
- // TEST109: Extensions coexist with metadata and validation
1134
+ // TEST109: Test creating cap with metadata initializes and retrieves metadata correctly
1138
1135
  function test109_extensionsWithMetadataAndValidation() {
1139
1136
  const mediaSpecs = [
1140
1137
  {
@@ -1153,7 +1150,7 @@ function test109_extensionsWithMetadataAndValidation() {
1153
1150
  assertEqual(resolved.extensions[0], 'json', 'Should have json extension');
1154
1151
  }
1155
1152
 
1156
- // TEST110: Multiple extensions in a media spec
1153
+ // TEST110: Test cap matching with subset semantics for request fulfillment
1157
1154
  function test110_multipleExtensions() {
1158
1155
  const mediaSpecs = [
1159
1156
  {
@@ -1173,7 +1170,7 @@ function test110_multipleExtensions() {
1173
1170
  // cap_matrix.rs: TEST117-TEST131
1174
1171
  // ============================================================================
1175
1172
 
1176
- // TEST117: CapBlock finds more specific cap across registries
1173
+ // TEST117: Test registering cap set and finding by exact and subset matching
1177
1174
  function test117_capBlockMoreSpecificWins() {
1178
1175
  const providerRegistry = new CapMatrix();
1179
1176
  const cartridgeRegistry = new CapMatrix();
@@ -1203,7 +1200,7 @@ function test117_capBlockMoreSpecificWins() {
1203
1200
  assertEqual(best.cap.title, 'Cartridge PDF Thumbnail Generator (specific)', 'Should get cartridge cap');
1204
1201
  }
1205
1202
 
1206
- // TEST118: CapBlock tie-breaking prefers first registry in order
1203
+ // TEST118: Test selecting best cap set based on specificity ranking With is_dispatchable semantics: - Provider must satisfy ALL request constraints - General request matches specific provider (provider refines request) - Specific request does NOT match general provider (provider lacks constraints)
1207
1204
  function test118_capBlockTieGoesToFirst() {
1208
1205
  const registry1 = new CapMatrix();
1209
1206
  const registry2 = new CapMatrix();
@@ -1224,7 +1221,7 @@ function test118_capBlockTieGoesToFirst() {
1224
1221
  assertEqual(best.registryName, 'first', 'On tie, first registry should win');
1225
1222
  }
1226
1223
 
1227
- // TEST119: CapBlock polls all registries to find best match
1224
+ // TEST119: Test invalid URN returns InvalidUrn error
1228
1225
  function test119_capBlockPollsAll() {
1229
1226
  const registry1 = new CapMatrix();
1230
1227
  const registry2 = new CapMatrix();
@@ -1251,7 +1248,7 @@ function test119_capBlockPollsAll() {
1251
1248
  assertEqual(best.registryName, 'r3', 'Most specific registry should win');
1252
1249
  }
1253
1250
 
1254
- // TEST120: CapBlock returns error when no cap matches request
1251
+ // TEST120: Test accepts_request checks if registry can handle a capability request
1255
1252
  function test120_capBlockNoMatch() {
1256
1253
  const registry = new CapMatrix();
1257
1254
  const composite = new CapBlock();
@@ -1266,7 +1263,7 @@ function test120_capBlockNoMatch() {
1266
1263
  }
1267
1264
  }
1268
1265
 
1269
- // TEST121: CapBlock fallback scenario where generic cap handles unknown file types
1266
+ // TEST121: Test CapBlock selects more specific cap over less specific regardless of registry order
1270
1267
  function test121_capBlockFallbackScenario() {
1271
1268
  const providerRegistry = new CapMatrix();
1272
1269
  const cartridgeRegistry = new CapMatrix();
@@ -1298,7 +1295,7 @@ function test121_capBlockFallbackScenario() {
1298
1295
  assertEqual(bestWav.registryName, 'providers', 'Provider should win for wav (fallback)');
1299
1296
  }
1300
1297
 
1301
- // TEST122: CapBlock can method returns execution info and acceptsRequest checks capability
1298
+ // TEST122: Test CapBlock breaks specificity ties by first registered registry
1302
1299
  function test122_capBlockCanMethod() {
1303
1300
  const providerRegistry = new CapMatrix();
1304
1301
  const providerHost = new MockCapSet('test_provider');
@@ -1315,7 +1312,7 @@ function test122_capBlockCanMethod() {
1315
1312
  assert(!composite.acceptsRequest(matrixTestUrn('op=nonexistent')), 'Should not accept non-matching cap');
1316
1313
  }
1317
1314
 
1318
- // TEST123: CapBlock registry management add, get, remove operations
1315
+ // TEST123: Test CapBlock polls all registries to find most specific match
1319
1316
  function test123_capBlockRegistryManagement() {
1320
1317
  const composite = new CapBlock();
1321
1318
  const registry1 = new CapMatrix();
@@ -1334,7 +1331,7 @@ function test123_capBlockRegistryManagement() {
1334
1331
  assertEqual(composite.getRegistry('nonexistent'), null, 'Should return null for non-existent');
1335
1332
  }
1336
1333
 
1337
- // TEST124: CapGraph basic construction builds nodes and edges from caps
1334
+ // TEST124: Test CapBlock returns error when no registries match the request
1338
1335
  function test124_capGraphBasicConstruction() {
1339
1336
  const registry = new CapMatrix();
1340
1337
  const mockHost = { executeCap: async () => ({ textOutput: 'mock' }) };
@@ -1353,7 +1350,7 @@ function test124_capGraphBasicConstruction() {
1353
1350
  assertEqual(graph.stats().edgeCount, 2, 'Expected 2 edges in stats');
1354
1351
  }
1355
1352
 
1356
- // TEST125: CapGraph getOutgoing and getIncoming return correct edges for media URN
1353
+ // TEST125: Test CapBlock prefers specific cartridge over generic provider fallback
1357
1354
  function test125_capGraphOutgoingIncoming() {
1358
1355
  const registry = new CapMatrix();
1359
1356
  const mockHost = { executeCap: async () => ({ textOutput: 'mock' }) };
@@ -1371,7 +1368,7 @@ function test125_capGraphOutgoingIncoming() {
1371
1368
  assertEqual(graph.getIncoming('media:object').length, 1, 'object should have 1 incoming');
1372
1369
  }
1373
1370
 
1374
- // TEST126: CapGraph canConvert checks direct and transitive conversion paths
1371
+ // TEST126: Test composite can method returns CapCaller for capability execution
1375
1372
  function test126_capGraphCanConvert() {
1376
1373
  const registry = new CapMatrix();
1377
1374
  const mockHost = { executeCap: async () => ({ textOutput: 'mock' }) };
@@ -1392,7 +1389,7 @@ function test126_capGraphCanConvert() {
1392
1389
  assert(!graph.canConvert('media:nonexistent', 'media:string'), 'Nonexistent node');
1393
1390
  }
1394
1391
 
1395
- // TEST127: CapGraph findPath returns shortest path between media URNs
1392
+ // TEST127: Test CapGraph adds nodes and edges from capability definitions
1396
1393
  function test127_capGraphFindPath() {
1397
1394
  const registry = new CapMatrix();
1398
1395
  const mockHost = { executeCap: async () => ({ textOutput: 'mock' }) };
@@ -1425,7 +1422,7 @@ function test127_capGraphFindPath() {
1425
1422
  assertEqual(path.length, 0, 'Same spec path should be empty');
1426
1423
  }
1427
1424
 
1428
- // TEST128: CapGraph findAllPaths returns all paths sorted by length
1425
+ // TEST128: Test CapGraph tracks outgoing and incoming edges for spec conversions
1429
1426
  function test128_capGraphFindAllPaths() {
1430
1427
  const registry = new CapMatrix();
1431
1428
  const mockHost = { executeCap: async () => ({ textOutput: 'mock' }) };
@@ -1445,7 +1442,7 @@ function test128_capGraphFindAllPaths() {
1445
1442
  assertEqual(paths[1].length, 2, 'Longer path second (via string)');
1446
1443
  }
1447
1444
 
1448
- // TEST129: CapGraph getDirectEdges returns edges sorted by specificity
1445
+ // TEST129: Test CapGraph detects direct and indirect conversion paths between specs
1449
1446
  function test129_capGraphGetDirectEdges() {
1450
1447
  const registry1 = new CapMatrix();
1451
1448
  const registry2 = new CapMatrix();
@@ -1470,7 +1467,7 @@ function test129_capGraphGetDirectEdges() {
1470
1467
  assert(edges[0].specificity > edges[1].specificity, 'First edge should have higher specificity');
1471
1468
  }
1472
1469
 
1473
- // TEST130: CapGraph stats returns node count, edge count, input/output URN counts
1470
+ // TEST130: Test CapGraph finds shortest path for spec conversion chain
1474
1471
  function test130_capGraphStats() {
1475
1472
  const registry = new CapMatrix();
1476
1473
  const mockHost = { executeCap: async () => ({ textOutput: 'mock' }) };
@@ -1491,7 +1488,7 @@ function test130_capGraphStats() {
1491
1488
  assertEqual(stats.outputUrnCount, 3, '3 output URNs');
1492
1489
  }
1493
1490
 
1494
- // TEST131: CapGraph with CapBlock builds graph from multiple registries
1491
+ // TEST131: Test CapGraph finds all conversion paths sorted by length
1495
1492
  function test131_capGraphWithCapBlock() {
1496
1493
  const providerRegistry = new CapMatrix();
1497
1494
  const cartridgeRegistry = new CapMatrix();
@@ -1525,7 +1522,7 @@ function test131_capGraphWithCapBlock() {
1525
1522
  // caller.rs: TEST156-TEST159
1526
1523
  // ============================================================================
1527
1524
 
1528
- // TEST156: Creating StdinSource Data variant with byte vector
1525
+ // TEST156: Test creating StdinSource Data variant with byte vector
1529
1526
  function test156_stdinSourceFromData() {
1530
1527
  const testData = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // "Hello"
1531
1528
  const source = StdinSource.fromData(testData);
@@ -1536,7 +1533,7 @@ function test156_stdinSourceFromData() {
1536
1533
  assertEqual(source.data, testData, 'Should store data');
1537
1534
  }
1538
1535
 
1539
- // TEST157: Creating StdinSource FileReference variant with all required fields
1536
+ // TEST157: Test creating StdinSource FileReference variant with all required fields
1540
1537
  function test157_stdinSourceFromFileReference() {
1541
1538
  const trackedFileId = 'tracked-file-123';
1542
1539
  const originalPath = '/path/to/original.pdf';
@@ -1553,7 +1550,7 @@ function test157_stdinSourceFromFileReference() {
1553
1550
  assertEqual(source.mediaUrn, mediaUrn, 'Should store mediaUrn');
1554
1551
  }
1555
1552
 
1556
- // TEST158: StdinSource Data with empty vector stores and retrieves correctly
1553
+ // TEST158: Test StdinSource Data with empty vector stores and retrieves correctly
1557
1554
  function test158_stdinSourceWithEmptyData() {
1558
1555
  const emptyData = new Uint8Array(0);
1559
1556
  const source = StdinSource.fromData(emptyData);
@@ -1561,7 +1558,7 @@ function test158_stdinSourceWithEmptyData() {
1561
1558
  assertEqual(source.data.length, 0, 'Data length should be 0');
1562
1559
  }
1563
1560
 
1564
- // TEST159: StdinSource Data with binary content like PNG header bytes
1561
+ // TEST159: Test StdinSource Data with binary content like PNG header bytes
1565
1562
  function test159_stdinSourceWithBinaryContent() {
1566
1563
  const pngHeader = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1567
1564
  const source = StdinSource.fromData(pngHeader);
@@ -1575,27 +1572,27 @@ function test159_stdinSourceWithBinaryContent() {
1575
1572
  // caller.rs: TEST274-TEST283
1576
1573
  // ============================================================================
1577
1574
 
1578
- // TEST274: CapArgumentValue constructor stores media_urn and raw byte value
1575
+ // TEST274: Test CapArgumentValue::new stores media_urn and raw byte value
1579
1576
  function test274_capArgumentValueNew() {
1580
1577
  const arg = new CapArgumentValue('media:model-spec;textable', new Uint8Array([103, 112, 116, 45, 52]));
1581
1578
  assertEqual(arg.mediaUrn, 'media:model-spec;textable', 'mediaUrn must match');
1582
1579
  assertEqual(arg.value.length, 5, 'value must have 5 bytes');
1583
1580
  }
1584
1581
 
1585
- // TEST275: CapArgumentValue.fromStr converts string to UTF-8 bytes
1582
+ // TEST275: Test CapArgumentValue::from_str converts string to UTF-8 bytes
1586
1583
  function test275_capArgumentValueFromStr() {
1587
1584
  const arg = CapArgumentValue.fromStr('media:string;textable', 'hello world');
1588
1585
  assertEqual(arg.mediaUrn, 'media:string;textable', 'mediaUrn must match');
1589
1586
  assertEqual(new TextDecoder().decode(arg.value), 'hello world', 'value must decode correctly');
1590
1587
  }
1591
1588
 
1592
- // TEST276: CapArgumentValue.valueAsStr succeeds for UTF-8 data
1589
+ // TEST276: Test CapArgumentValue::value_as_str succeeds for UTF-8 data
1593
1590
  function test276_capArgumentValueAsStrValid() {
1594
1591
  const arg = CapArgumentValue.fromStr('media:string', 'test');
1595
1592
  assertEqual(arg.valueAsStr(), 'test', 'valueAsStr must return test');
1596
1593
  }
1597
1594
 
1598
- // TEST277: CapArgumentValue.valueAsStr fails for non-UTF-8 binary data
1595
+ // TEST277: Test CapArgumentValue::value_as_str fails for non-UTF-8 binary data
1599
1596
  function test277_capArgumentValueAsStrInvalidUtf8() {
1600
1597
  const arg = new CapArgumentValue('media:pdf', new Uint8Array([0xFF, 0xFE, 0x80]));
1601
1598
  let threw = false;
@@ -1607,7 +1604,7 @@ function test277_capArgumentValueAsStrInvalidUtf8() {
1607
1604
  assert(threw, 'non-UTF-8 data must fail on valueAsStr with fatal decoder');
1608
1605
  }
1609
1606
 
1610
- // TEST278: CapArgumentValue with empty value stores empty Uint8Array
1607
+ // TEST278: Test CapArgumentValue::new with empty value stores empty vec
1611
1608
  function test278_capArgumentValueEmpty() {
1612
1609
  const arg = new CapArgumentValue('media:void', new Uint8Array([]));
1613
1610
  assertEqual(arg.value.length, 0, 'empty value must have 0 bytes');
@@ -1616,13 +1613,13 @@ function test278_capArgumentValueEmpty() {
1616
1613
 
1617
1614
  // TEST279-281: N/A for JS (Rust Debug/Clone/Send traits)
1618
1615
 
1619
- // TEST282: CapArgumentValue.fromStr with Unicode string preserves all characters
1616
+ // TEST282: Test CapArgumentValue::from_str with Unicode string preserves all characters
1620
1617
  function test282_capArgumentValueUnicode() {
1621
1618
  const arg = CapArgumentValue.fromStr('media:string', 'hello \u4e16\u754c \ud83c\udf0d');
1622
1619
  assertEqual(arg.valueAsStr(), 'hello \u4e16\u754c \ud83c\udf0d', 'Unicode must roundtrip');
1623
1620
  }
1624
1621
 
1625
- // TEST283: CapArgumentValue with large binary payload preserves all bytes
1622
+ // TEST283: Test CapArgumentValue with large binary payload preserves all bytes
1626
1623
  function test283_capArgumentValueLargeBinary() {
1627
1624
  const data = new Uint8Array(10000);
1628
1625
  for (let i = 0; i < 10000; i++) {
@@ -1641,7 +1638,7 @@ function test283_capArgumentValueLargeBinary() {
1641
1638
 
1642
1639
  const { TaggedUrn } = require('tagged-urn');
1643
1640
 
1644
- // TEST304: MEDIA_AVAILABILITY_OUTPUT constant parses as valid media URN with correct tags
1641
+ // TEST304: Test MEDIA_AVAILABILITY_OUTPUT constant parses as valid media URN with correct tags
1645
1642
  function test304_mediaAvailabilityOutputConstant() {
1646
1643
  const urn = TaggedUrn.fromString(MEDIA_AVAILABILITY_OUTPUT);
1647
1644
  assert(urn.getTag('textable') !== undefined, 'model-availability must be textable');
@@ -1651,7 +1648,7 @@ function test304_mediaAvailabilityOutputConstant() {
1651
1648
  assert(urn.conformsTo(reparsed), 'roundtrip must match original');
1652
1649
  }
1653
1650
 
1654
- // TEST305: MEDIA_PATH_OUTPUT constant parses as valid media URN with correct tags
1651
+ // TEST305: Test MEDIA_PATH_OUTPUT constant parses as valid media URN with correct tags
1655
1652
  function test305_mediaPathOutputConstant() {
1656
1653
  const urn = TaggedUrn.fromString(MEDIA_PATH_OUTPUT);
1657
1654
  assert(urn.getTag('textable') !== undefined, 'model-path must be textable');
@@ -1661,7 +1658,7 @@ function test305_mediaPathOutputConstant() {
1661
1658
  assert(urn.conformsTo(reparsed), 'roundtrip must match original');
1662
1659
  }
1663
1660
 
1664
- // TEST306: MEDIA_AVAILABILITY_OUTPUT and MEDIA_PATH_OUTPUT are distinct URNs
1661
+ // TEST306: Test MEDIA_AVAILABILITY_OUTPUT and MEDIA_PATH_OUTPUT are distinct URNs
1665
1662
  function test306_availabilityAndPathOutputDistinct() {
1666
1663
  assert(MEDIA_AVAILABILITY_OUTPUT !== MEDIA_PATH_OUTPUT, 'Must be distinct');
1667
1664
  const avail = TaggedUrn.fromString(MEDIA_AVAILABILITY_OUTPUT);
@@ -1675,7 +1672,7 @@ function test306_availabilityAndPathOutputDistinct() {
1675
1672
  assert(!matchResult, 'availability must not conform to path');
1676
1673
  }
1677
1674
 
1678
- // TEST307: model_availability_urn builds valid cap URN with correct op and media specs
1675
+ // TEST307: Test model_availability_urn builds valid cap URN with correct op and media specs
1679
1676
  function test307_modelAvailabilityUrn() {
1680
1677
  const urn = modelAvailabilityUrn();
1681
1678
  assert(urn.hasTag('op', 'model-availability'), 'Must have op=model-availability');
@@ -1687,7 +1684,7 @@ function test307_modelAvailabilityUrn() {
1687
1684
  assert(outSpec.conformsTo(expectedOut), 'output must conform to MEDIA_AVAILABILITY_OUTPUT');
1688
1685
  }
1689
1686
 
1690
- // TEST308: model_path_urn builds valid cap URN with correct op and media specs
1687
+ // TEST308: Test model_path_urn builds valid cap URN with correct op and media specs
1691
1688
  function test308_modelPathUrn() {
1692
1689
  const urn = modelPathUrn();
1693
1690
  assert(urn.hasTag('op', 'model-path'), 'Must have op=model-path');
@@ -1699,23 +1696,27 @@ function test308_modelPathUrn() {
1699
1696
  assert(outSpec.conformsTo(expectedOut), 'output must conform to MEDIA_PATH_OUTPUT');
1700
1697
  }
1701
1698
 
1702
- // TEST309: model_availability_urn and model_path_urn produce distinct URNs
1699
+ // TEST309: Test model_availability_urn and model_path_urn produce distinct URNs
1703
1700
  function test309_modelAvailabilityAndPathAreDistinct() {
1704
1701
  const avail = modelAvailabilityUrn();
1705
1702
  const path = modelPathUrn();
1706
1703
  assert(avail.toString() !== path.toString(), 'availability and path must be distinct');
1707
1704
  }
1708
1705
 
1709
- // TEST310: llm_generate_text_urn has correct op and ml-model tags
1706
+ // TEST310: llm_generate_text_urn() produces a valid cap URN with textable in/out specs
1710
1707
  function test310_llmGenerateTextUrn() {
1711
1708
  const urn = llmGenerateTextUrn();
1712
1709
  assert(urn.hasTag('op', 'generate_text'), 'Must have op=generate_text');
1713
1710
  assert(urn.getTag('llm') !== undefined, 'Must have llm tag');
1714
1711
  assert(urn.getTag('ml-model') !== undefined, 'Must have ml-model tag');
1712
+ assert(TaggedUrn.fromString(urn.getInSpec()).conformsTo(TaggedUrn.fromString(MEDIA_STRING)),
1713
+ 'in_spec must conform to MEDIA_STRING');
1714
+ assert(TaggedUrn.fromString(urn.getOutSpec()).conformsTo(TaggedUrn.fromString(MEDIA_STRING)),
1715
+ 'out_spec must conform to MEDIA_STRING');
1715
1716
  }
1716
1717
 
1717
- // TEST311: llm_generate_text_urn in/out specs match MEDIA_STRING
1718
- function test311_llmGenerateTextUrnSpecs() {
1718
+ // Mirror-specific coverage: llm_generate_text_urn input/output specs conform to MEDIA_STRING
1719
+ function testLlmGenerateTextUrnSpecs() {
1719
1720
  const urn = llmGenerateTextUrn();
1720
1721
  const inSpec = TaggedUrn.fromString(urn.getInSpec());
1721
1722
  const expectedIn = TaggedUrn.fromString(MEDIA_STRING);
@@ -1725,7 +1726,7 @@ function test311_llmGenerateTextUrnSpecs() {
1725
1726
  assert(outSpec.conformsTo(expectedOut), 'out_spec must conform to MEDIA_STRING');
1726
1727
  }
1727
1728
 
1728
- // TEST312: All URN builders produce parseable cap URNs
1729
+ // TEST312: Test all URN builders produce parseable cap URNs
1729
1730
  function test312_allUrnBuildersProduceValidUrns() {
1730
1731
  const avail = modelAvailabilityUrn();
1731
1732
  const path = modelPathUrn();
@@ -2090,7 +2091,7 @@ const sampleRegistry = {
2090
2091
  }
2091
2092
  };
2092
2093
 
2093
- // TEST320: Cartridge info construction
2094
+ // TEST320-335: CartridgeRepoServer and CartridgeRepoClient tests
2094
2095
  function test320_cartridgeInfoConstruction() {
2095
2096
  const data = {
2096
2097
  id: 'testcartridge',
@@ -2117,7 +2118,7 @@ function test320_cartridgeInfoConstruction() {
2117
2118
  assert(cartridge.caps[0].urn === 'cap:in="media:void";op=test;out="media:void"', 'Cap URN should match');
2118
2119
  }
2119
2120
 
2120
- // TEST321: Cartridge info is signed check
2121
+ // TEST321: CartridgeInfo.is_signed() returns true when signature is present
2121
2122
  function test321_cartridgeInfoIsSigned() {
2122
2123
  const signed = new CartridgeInfo({id: 'test', teamId: 'TEAM', signedAt: '2026-01-01', caps: []});
2123
2124
  assert(signed.isSigned() === true, 'Cartridge with teamId and signedAt should be signed');
@@ -2129,7 +2130,7 @@ function test321_cartridgeInfoIsSigned() {
2129
2130
  assert(unsigned2.isSigned() === false, 'Cartridge without signedAt should not be signed');
2130
2131
  }
2131
2132
 
2132
- // TEST322: Cartridge info build for platform and available platforms
2133
+ // TEST322: CartridgeInfo.build_for_platform() returns the build matching the current platform
2133
2134
  function test322_cartridgeInfoBuildForPlatform() {
2134
2135
  const withBuilds = new CartridgeInfo({
2135
2136
  id: 'test', version: '1.0.0', caps: [],
@@ -2163,7 +2164,7 @@ function test322_cartridgeInfoBuildForPlatform() {
2163
2164
  assert(noBuilds.availablePlatforms().length === 0, 'Should have no platforms');
2164
2165
  }
2165
2166
 
2166
- // TEST323: CartridgeRepoServer validate registry
2167
+ // TEST323: CartridgeRepoServer validates registry JSON schema version
2167
2168
  function test323_cartridgeRepoServerValidateRegistry() {
2168
2169
  // Valid registry
2169
2170
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2190,7 +2191,7 @@ function test323_cartridgeRepoServerValidateRegistry() {
2190
2191
  assert(threw, 'Should throw for missing cartridges');
2191
2192
  }
2192
2193
 
2193
- // TEST324: CartridgeRepoServer transform to array
2194
+ // TEST324: CartridgeRepoServer transforms v3 registry JSON into flat cartridge array
2194
2195
  function test324_cartridgeRepoServerTransformToArray() {
2195
2196
  const server = new CartridgeRepoServer(sampleRegistry);
2196
2197
  const cartridges = server.transformToCartridgeArray();
@@ -2215,7 +2216,7 @@ function test324_cartridgeRepoServerTransformToArray() {
2215
2216
  assert(pdf.caps.length === 2, 'Should have 2 caps');
2216
2217
  }
2217
2218
 
2218
- // TEST325: CartridgeRepoServer get cartridges
2219
+ // TEST325: CartridgeRepoServer.get_cartridges() returns all parsed cartridges
2219
2220
  function test325_cartridgeRepoServerGetCartridges() {
2220
2221
  const server = new CartridgeRepoServer(sampleRegistry);
2221
2222
  const response = server.getCartridges();
@@ -2225,7 +2226,7 @@ function test325_cartridgeRepoServerGetCartridges() {
2225
2226
  assert(response.cartridges.length === 2, 'Should have 2 cartridges');
2226
2227
  }
2227
2228
 
2228
- // TEST326: CartridgeRepoServer get cartridge by ID
2229
+ // TEST326: CartridgeRepoServer.get_cartridge() returns cartridge matching the given ID
2229
2230
  function test326_cartridgeRepoServerGetCartridgeById() {
2230
2231
  const server = new CartridgeRepoServer(sampleRegistry);
2231
2232
 
@@ -2237,7 +2238,7 @@ function test326_cartridgeRepoServerGetCartridgeById() {
2237
2238
  assert(notFound === undefined, 'Should return undefined for missing cartridge');
2238
2239
  }
2239
2240
 
2240
- // TEST327: CartridgeRepoServer search cartridges
2241
+ // TEST327: CartridgeRepoServer.search_cartridges() filters by text query against name and description
2241
2242
  function test327_cartridgeRepoServerSearchCartridges() {
2242
2243
  const server = new CartridgeRepoServer(sampleRegistry);
2243
2244
 
@@ -2252,7 +2253,7 @@ function test327_cartridgeRepoServerSearchCartridges() {
2252
2253
  assert(noResults.length === 0, 'Should return empty for no matches');
2253
2254
  }
2254
2255
 
2255
- // TEST328: CartridgeRepoServer get by category
2256
+ // TEST328: CartridgeRepoServer.get_by_category() filters cartridges by category tag
2256
2257
  function test328_cartridgeRepoServerGetByCategory() {
2257
2258
  const server = new CartridgeRepoServer(sampleRegistry);
2258
2259
 
@@ -2265,7 +2266,7 @@ function test328_cartridgeRepoServerGetByCategory() {
2265
2266
  assert(textCartridges[0].id === 'txtcartridge', 'Should be txtcartridge');
2266
2267
  }
2267
2268
 
2268
- // TEST329: CartridgeRepoServer get by cap
2269
+ // TEST329: CartridgeRepoServer.get_suggestions_for_cap() finds cartridges providing a given cap URN
2269
2270
  function test329_cartridgeRepoServerGetByCap() {
2270
2271
  const server = new CartridgeRepoServer(sampleRegistry);
2271
2272
 
@@ -2280,7 +2281,7 @@ function test329_cartridgeRepoServerGetByCap() {
2280
2281
  assert(metadataCartridges.length === 1, 'Should find metadata cap');
2281
2282
  }
2282
2283
 
2283
- // TEST330: CartridgeRepoClient update cache
2284
+ // TEST330: CartridgeRepoClient updates its local cache from server response
2284
2285
  function test330_cartridgeRepoClientUpdateCache() {
2285
2286
  const client = new CartridgeRepoClient(3600);
2286
2287
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2294,7 +2295,7 @@ function test330_cartridgeRepoClientUpdateCache() {
2294
2295
  assert(cache.capToCartridges.size > 0, 'Should have cap mappings');
2295
2296
  }
2296
2297
 
2297
- // TEST331: CartridgeRepoClient get suggestions
2298
+ // TEST331: CartridgeRepoClient.get_suggestions_for_cap() returns cartridge suggestions for a cap URN
2298
2299
  function test331_cartridgeRepoClientGetSuggestions() {
2299
2300
  const client = new CartridgeRepoClient(3600);
2300
2301
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2311,7 +2312,7 @@ function test331_cartridgeRepoClientGetSuggestions() {
2311
2312
  assert(suggestions[0].capTitle === 'Disbind PDF', 'Should have cap title');
2312
2313
  }
2313
2314
 
2314
- // TEST332: CartridgeRepoClient get cartridge
2315
+ // TEST332: CartridgeRepoClient.get_cartridge() retrieves a specific cartridge by ID from cache
2315
2316
  function test332_cartridgeRepoClientGetCartridge() {
2316
2317
  const client = new CartridgeRepoClient(3600);
2317
2318
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2327,7 +2328,7 @@ function test332_cartridgeRepoClientGetCartridge() {
2327
2328
  assert(notFound === null, 'Should return null for missing cartridge');
2328
2329
  }
2329
2330
 
2330
- // TEST333: CartridgeRepoClient get all caps
2331
+ // TEST333: CartridgeRepoClient.get_all_caps() returns aggregate cap URNs from all cached cartridges
2331
2332
  function test333_cartridgeRepoClientGetAllCaps() {
2332
2333
  const client = new CartridgeRepoClient(3600);
2333
2334
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2341,7 +2342,7 @@ function test333_cartridgeRepoClientGetAllCaps() {
2341
2342
  assert(caps.every(c => typeof c === 'string'), 'All caps should be strings');
2342
2343
  }
2343
2344
 
2344
- // TEST334: CartridgeRepoClient needs sync
2345
+ // TEST334: CartridgeRepoClient.needs_sync() returns true when cache TTL has expired
2345
2346
  function test334_cartridgeRepoClientNeedsSync() {
2346
2347
  const client = new CartridgeRepoClient(1); // 1 second TTL
2347
2348
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2362,7 +2363,7 @@ function test334_cartridgeRepoClientNeedsSync() {
2362
2363
  // Note: Can't test this synchronously, would need async test
2363
2364
  }
2364
2365
 
2365
- // TEST335: CartridgeRepoServer and Client integration
2366
+ // TEST335: Server creates registry response and client consumes it end-to-end
2366
2367
  function test335_cartridgeRepoServerClientIntegration() {
2367
2368
  // Server creates API response
2368
2369
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2392,11 +2393,11 @@ function test335_cartridgeRepoServerClientIntegration() {
2392
2393
  }
2393
2394
 
2394
2395
  // ============================================================================
2395
- // media_urn.rs: TEST546-TEST558 (MediaUrn predicates)
2396
+ // media_urn.rs: TEST1294-TEST1302 (MediaUrn predicates)
2396
2397
  // ============================================================================
2397
2398
 
2398
- // TEST546: isImage returns true only when image marker tag is present
2399
- function test546_isImage() {
2399
+ // TEST1312: is_image returns true only when image marker tag is present
2400
+ function test1312_isImage() {
2400
2401
  assert(MediaUrn.fromString(MEDIA_PNG).isImage(), 'MEDIA_PNG should be image');
2401
2402
  assert(MediaUrn.fromString('media:image;png;thumbnail').isImage(), 'media:image;png;thumbnail should be image');
2402
2403
  assert(MediaUrn.fromString('media:image;jpg').isImage(), 'media:image;jpg should be image');
@@ -2407,8 +2408,8 @@ function test546_isImage() {
2407
2408
  assert(!MediaUrn.fromString(MEDIA_VIDEO).isImage(), 'MEDIA_VIDEO should not be image');
2408
2409
  }
2409
2410
 
2410
- // TEST547: isAudio returns true only when audio marker tag is present
2411
- function test547_isAudio() {
2411
+ // TEST1313: is_audio returns true only when audio marker tag is present
2412
+ function test1313_isAudio() {
2412
2413
  assert(MediaUrn.fromString(MEDIA_AUDIO).isAudio(), 'MEDIA_AUDIO should be audio');
2413
2414
  assert(MediaUrn.fromString(MEDIA_AUDIO_SPEECH).isAudio(), 'MEDIA_AUDIO_SPEECH should be audio');
2414
2415
  assert(MediaUrn.fromString('media:audio;mp3').isAudio(), 'media:audio;mp3 should be audio');
@@ -2418,8 +2419,8 @@ function test547_isAudio() {
2418
2419
  assert(!MediaUrn.fromString(MEDIA_STRING).isAudio(), 'MEDIA_STRING should not be audio');
2419
2420
  }
2420
2421
 
2421
- // TEST548: isVideo returns true only when video marker tag is present
2422
- function test548_isVideo() {
2422
+ // TEST1314: is_video returns true only when video marker tag is present
2423
+ function test1314_isVideo() {
2423
2424
  assert(MediaUrn.fromString(MEDIA_VIDEO).isVideo(), 'MEDIA_VIDEO should be video');
2424
2425
  assert(MediaUrn.fromString('media:video;mp4').isVideo(), 'media:video;mp4 should be video');
2425
2426
  // Non-video types
@@ -2428,8 +2429,8 @@ function test548_isVideo() {
2428
2429
  assert(!MediaUrn.fromString(MEDIA_STRING).isVideo(), 'MEDIA_STRING should not be video');
2429
2430
  }
2430
2431
 
2431
- // TEST549: isNumeric returns true only when numeric marker tag is present
2432
- function test549_isNumeric() {
2432
+ // TEST1315: is_numeric returns true only when numeric marker tag is present
2433
+ function test1315_isNumeric() {
2433
2434
  assert(MediaUrn.fromString(MEDIA_INTEGER).isNumeric(), 'MEDIA_INTEGER should be numeric');
2434
2435
  assert(MediaUrn.fromString(MEDIA_NUMBER).isNumeric(), 'MEDIA_NUMBER should be numeric');
2435
2436
  assert(MediaUrn.fromString(MEDIA_INTEGER_LIST).isNumeric(), 'MEDIA_INTEGER_LIST should be numeric');
@@ -2440,8 +2441,8 @@ function test549_isNumeric() {
2440
2441
  assert(!MediaUrn.fromString(MEDIA_IDENTITY).isNumeric(), 'MEDIA_IDENTITY should not be numeric');
2441
2442
  }
2442
2443
 
2443
- // TEST550: isBool returns true only when bool marker tag is present
2444
- function test550_isBool() {
2444
+ // TEST1298: is_bool returns true only when bool marker tag is present
2445
+ function test1298_isBool() {
2445
2446
  assert(MediaUrn.fromString(MEDIA_BOOLEAN).isBool(), 'MEDIA_BOOLEAN should be bool');
2446
2447
  assert(MediaUrn.fromString(MEDIA_BOOLEAN_LIST).isBool(), 'MEDIA_BOOLEAN_LIST should be bool');
2447
2448
  // MEDIA_DECISION is now a JSON record type (not bool)
@@ -2452,8 +2453,8 @@ function test550_isBool() {
2452
2453
  assert(!MediaUrn.fromString(MEDIA_IDENTITY).isBool(), 'MEDIA_IDENTITY should not be bool');
2453
2454
  }
2454
2455
 
2455
- // TEST551: isFilePath returns true for scalar file-path, false for array
2456
- function test551_isFilePath() {
2456
+ // TEST1299: is_file_path returns true for scalar file-path, false for array
2457
+ function test1299_isFilePath() {
2457
2458
  assert(MediaUrn.fromString(MEDIA_FILE_PATH).isFilePath(), 'MEDIA_FILE_PATH should be file-path');
2458
2459
  // Array file-path is NOT isFilePath (it's isFilePathArray)
2459
2460
  assert(!MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isFilePath(), 'MEDIA_FILE_PATH_ARRAY should not be isFilePath');
@@ -2462,8 +2463,8 @@ function test551_isFilePath() {
2462
2463
  assert(!MediaUrn.fromString(MEDIA_IDENTITY).isFilePath(), 'MEDIA_IDENTITY should not be file-path');
2463
2464
  }
2464
2465
 
2465
- // TEST552: isFilePathArray returns true for list file-path, false for scalar
2466
- function test552_isFilePathArray() {
2466
+ // TEST1300: is_file_path_array returns true for list file-path, false for scalar
2467
+ function test1300_isFilePathArray() {
2467
2468
  assert(MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isFilePathArray(), 'MEDIA_FILE_PATH_ARRAY should be file-path-array');
2468
2469
  // Scalar file-path is NOT isFilePathArray
2469
2470
  assert(!MediaUrn.fromString(MEDIA_FILE_PATH).isFilePathArray(), 'MEDIA_FILE_PATH should not be isFilePathArray');
@@ -2471,8 +2472,8 @@ function test552_isFilePathArray() {
2471
2472
  assert(!MediaUrn.fromString(MEDIA_STRING_LIST).isFilePathArray(), 'MEDIA_STRING_LIST should not be file-path-array');
2472
2473
  }
2473
2474
 
2474
- // TEST553: isAnyFilePath returns true for both scalar and array file-path
2475
- function test553_isAnyFilePath() {
2475
+ // TEST1301: is_any_file_path returns true for both scalar and array file-path
2476
+ function test1301_isAnyFilePath() {
2476
2477
  assert(MediaUrn.fromString(MEDIA_FILE_PATH).isAnyFilePath(), 'MEDIA_FILE_PATH should be any-file-path');
2477
2478
  assert(MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isAnyFilePath(), 'MEDIA_FILE_PATH_ARRAY should be any-file-path');
2478
2479
  // Non-file-path types
@@ -2480,9 +2481,9 @@ function test553_isAnyFilePath() {
2480
2481
  assert(!MediaUrn.fromString(MEDIA_STRING_LIST).isAnyFilePath(), 'MEDIA_STRING_LIST should not be any-file-path');
2481
2482
  }
2482
2483
 
2483
- // TEST554: isCollection returns true when collection marker tag is present
2484
- // TEST554: N/A for JS (MEDIA_COLLECTION constants removed - no longer exists)
2485
- function test554_isCollection() {
2484
+ // Mirror-specific coverage: isCollection returns true when collection marker tag is present
2485
+ // Mirror-specific coverage: N/A for JS (MEDIA_COLLECTION constants removed - no longer exists)
2486
+ function testisCollection() {
2486
2487
  // Skip - collection types removed from capdag
2487
2488
  }
2488
2489
 
@@ -2492,8 +2493,8 @@ function test554_isCollection() {
2492
2493
 
2493
2494
  // TEST557: N/A for JS (audio_media_urn_for_ext helper not in JS)
2494
2495
 
2495
- // TEST558: predicates are consistent with constants - every constant triggers exactly the expected predicates
2496
- function test558_predicateConstantConsistency() {
2496
+ // TEST1302: predicates are consistent with constants every constant triggers exactly the expected predicates
2497
+ function test1302_predicateConstantConsistency() {
2497
2498
  // MEDIA_INTEGER must be numeric, text, scalar, NOT binary/bool/image/audio/video
2498
2499
  const intUrn = MediaUrn.fromString(MEDIA_INTEGER);
2499
2500
  assert(intUrn.isNumeric(), 'MEDIA_INTEGER must be numeric');
@@ -2529,11 +2530,11 @@ function test558_predicateConstantConsistency() {
2529
2530
  }
2530
2531
 
2531
2532
  // ============================================================================
2532
- // cap_urn.rs: TEST559-TEST567 (CapUrn tier tests)
2533
+ // cap_urn.rs: TEST1303-TEST1307 (CapUrn tier tests)
2533
2534
  // ============================================================================
2534
2535
 
2535
- // TEST559: withoutTag removes tag, ignores in/out, case-insensitive for keys
2536
- function test559_withoutTag() {
2536
+ // TEST1303: without_tag removes tag, ignores in/out, case-insensitive for keys
2537
+ function test1303_withoutTag() {
2537
2538
  const cap = CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"');
2538
2539
  const removed = cap.withoutTag('ext');
2539
2540
  assertEqual(removed.getTag('ext'), undefined, 'withoutTag should remove ext');
@@ -2554,8 +2555,8 @@ function test559_withoutTag() {
2554
2555
  assert(same3.equals(cap), 'Removing non-existent tag is no-op');
2555
2556
  }
2556
2557
 
2557
- // TEST560: withInSpec and withOutSpec change direction specs
2558
- function test560_withInOutSpec() {
2558
+ // TEST1304: with_in_spec and with_out_spec change direction specs
2559
+ function test1304_withInOutSpec() {
2559
2560
  const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
2560
2561
 
2561
2562
  const changedIn = cap.withInSpec('media:');
@@ -2568,17 +2569,17 @@ function test560_withInOutSpec() {
2568
2569
  assertEqual(changedOut.getOutSpec(), 'media:string', 'withOutSpec should change outSpec');
2569
2570
 
2570
2571
  // Chain both
2571
- const changedBoth = cap.withInSpec('media:pdf').withOutSpec('media:txt;textable');
2572
+ const changedBoth = cap.withInSpec('media:pdf').withOutSpec(MEDIA_TXT);
2572
2573
  assertEqual(changedBoth.getInSpec(), 'media:pdf', 'Chain should set inSpec');
2573
- assertEqual(changedBoth.getOutSpec(), 'media:txt;textable', 'Chain should set outSpec');
2574
+ assertEqual(changedBoth.getOutSpec(), MEDIA_TXT, 'Chain should set outSpec');
2574
2575
  }
2575
2576
 
2576
2577
  // TEST561: N/A for JS (in_media_urn/out_media_urn not in JS CapUrn)
2577
2578
 
2578
2579
  // TEST562: N/A for JS (canonical_option not in JS CapUrn)
2579
2580
 
2580
- // TEST563: CapMatcher.findAllMatches returns all matching caps sorted by specificity
2581
- function test563_findAllMatches() {
2581
+ // TEST1305: CapMatcher::find_all_matches returns all matching caps sorted by specificity
2582
+ function test1305_findAllMatches() {
2582
2583
  const caps = [
2583
2584
  CapUrn.fromString('cap:in="media:void";op=test;out="media:void"'),
2584
2585
  CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"'),
@@ -2595,8 +2596,8 @@ function test563_findAllMatches() {
2595
2596
  assertEqual(matches[0].getTag('ext'), 'pdf', 'Most specific match should have ext=pdf');
2596
2597
  }
2597
2598
 
2598
- // TEST564: CapMatcher.areCompatible detects bidirectional overlap
2599
- function test564_areCompatible() {
2599
+ // TEST1306: CapMatcher::are_compatible detects bidirectional overlap
2600
+ function test1306_areCompatible() {
2600
2601
  const caps1 = [
2601
2602
  CapUrn.fromString('cap:in="media:void";op=test;out="media:void"'),
2602
2603
  ];
@@ -2620,8 +2621,8 @@ function test564_areCompatible() {
2620
2621
 
2621
2622
  // TEST565: N/A for JS (tags_to_string not in JS CapUrn)
2622
2623
 
2623
- // TEST566: withTag silently ignores in/out keys
2624
- function test566_withTagIgnoresInOut() {
2624
+ // TEST1307: with_tag silently ignores in/out keys
2625
+ function test1307_withTagIgnoresInOut() {
2625
2626
  const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
2626
2627
  // Attempting to set in/out via withTag is silently ignored
2627
2628
  const same = cap.withTag('in', 'media:');
@@ -2631,41 +2632,129 @@ function test566_withTagIgnoresInOut() {
2631
2632
  assertEqual(same2.getOutSpec(), 'media:void', 'withTag must not change out_spec');
2632
2633
  }
2633
2634
 
2635
+ // TEST1294: RULE11 - void-input cap with stdin source rejected
2636
+ function test1294_rule11VoidInputWithStdinRejected() {
2637
+ const urn = CapUrn.fromString('cap:in="media:void";op=test;out="media:string"');
2638
+ const cap = new Cap(urn, 'Test', 'test-cmd');
2639
+ const stdinSource = ArgSource.fromJSON({ stdin: 'media:string' });
2640
+ cap.args = [new CapArg('media:string', true, [stdinSource])];
2641
+ try {
2642
+ validateCapArgs(cap);
2643
+ assert(false, 'Should have thrown RULE11 for void input with stdin');
2644
+ } catch (e) {
2645
+ assert(e instanceof ValidationError, 'Should be ValidationError');
2646
+ assert(e.message.includes('RULE11'), 'Should mention RULE11: ' + e.message);
2647
+ }
2648
+ }
2649
+
2650
+ // TEST1295: RULE11 - non-void-input cap without stdin source rejected
2651
+ function test1295_rule11NonVoidInputWithoutStdinRejected() {
2652
+ const urn = CapUrn.fromString('cap:in="media:string";op=test;out="media:string"');
2653
+ const cap = new Cap(urn, 'Test', 'test-cmd');
2654
+ const posSource = ArgSource.fromJSON({ cli_flag: '--name' });
2655
+ cap.args = [new CapArg('media:string', true, [posSource])];
2656
+ try {
2657
+ validateCapArgs(cap);
2658
+ assert(false, 'Should have thrown RULE11 for non-void input without stdin');
2659
+ } catch (e) {
2660
+ assert(e instanceof ValidationError, 'Should be ValidationError');
2661
+ assert(e.message.includes('RULE11'), 'Should mention RULE11: ' + e.message);
2662
+ }
2663
+ }
2664
+
2665
+ // TEST1296: RULE11 - void-input cap with only cli_flag sources passes
2666
+ function test1296_rule11VoidInputCliFlagOnly() {
2667
+ const urn = CapUrn.fromString('cap:in="media:void";op=test;out="media:string"');
2668
+ const cap = new Cap(urn, 'Test', 'test-cmd');
2669
+ const flagSource = ArgSource.fromJSON({ cli_flag: '--name' });
2670
+ cap.args = [new CapArg('media:string', true, [flagSource])];
2671
+ // Should not throw
2672
+ validateCapArgs(cap);
2673
+ }
2674
+
2675
+ // TEST1297: RULE11 - non-void-input cap with stdin source passes
2676
+ function test1297_rule11NonVoidInputWithStdin() {
2677
+ const urn = CapUrn.fromString('cap:in="media:string";op=test;out="media:string"');
2678
+ const cap = new Cap(urn, 'Test', 'test-cmd');
2679
+ const stdinSource = ArgSource.fromJSON({ stdin: 'media:string' });
2680
+ cap.args = [new CapArg('media:string', true, [stdinSource])];
2681
+ // Should not throw
2682
+ validateCapArgs(cap);
2683
+ }
2684
+
2634
2685
  // TEST567: N/A for JS (conforms_to_str/accepts_str not in JS CapUrn)
2635
2686
 
2636
2687
  // ============================================================================
2637
2688
  // cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
2638
2689
  // ============================================================================
2639
2690
 
2640
- // Note: Rust allows missing in/out to default to "media:" wildcard.
2641
- // JS currently requires in/out (throws MISSING_IN_SPEC/MISSING_OUT_SPEC).
2642
- // The following tests cover the wildcard behavior that IS applicable to JS.
2691
+ // TEST639: cap: (empty) defaults to in=media:;out=media:
2692
+ function test639_emptyCapDefaultsToMediaWildcard() {
2693
+ const cap = CapUrn.fromString('cap:');
2694
+ assertEqual(cap.getInSpec(), MEDIA_IDENTITY, 'Empty cap should default in to media:');
2695
+ assertEqual(cap.getOutSpec(), MEDIA_IDENTITY, 'Empty cap should default out to media:');
2696
+ assertEqual(Object.keys(cap.tags).length, 0, 'Empty cap should have no extra tags');
2697
+ }
2643
2698
 
2644
- // TEST639-642: N/A for JS (JS requires in/out, does not default to media: wildcard)
2699
+ // TEST640: cap:in defaults out to media:
2700
+ function test640_inOnlyDefaultsOutToMedia() {
2701
+ const cap = CapUrn.fromString('cap:in');
2702
+ assertEqual(cap.getInSpec(), MEDIA_IDENTITY, 'Bare in should normalize to media:');
2703
+ assertEqual(cap.getOutSpec(), MEDIA_IDENTITY, 'Missing out should default to media:');
2704
+ }
2645
2705
 
2646
- // TEST643: cap:in=*;out=* treated as wildcards
2706
+ // TEST641: cap:out defaults in to media:
2707
+ function test641_outOnlyDefaultsInToMedia() {
2708
+ const cap = CapUrn.fromString('cap:out');
2709
+ assertEqual(cap.getInSpec(), MEDIA_IDENTITY, 'Missing in should default to media:');
2710
+ assertEqual(cap.getOutSpec(), MEDIA_IDENTITY, 'Bare out should normalize to media:');
2711
+ }
2712
+
2713
+ // TEST642: cap:in;out both become media:
2714
+ function test642_inOutWithoutValuesBecomeMedia() {
2715
+ const cap = CapUrn.fromString('cap:in;out');
2716
+ assertEqual(cap.getInSpec(), MEDIA_IDENTITY, 'Bare in should normalize to media:');
2717
+ assertEqual(cap.getOutSpec(), MEDIA_IDENTITY, 'Bare out should normalize to media:');
2718
+ }
2719
+
2720
+ // TEST643: cap:in=*;out=* becomes media:
2647
2721
  function test643_explicitAsteriskIsWildcard() {
2648
2722
  const cap = CapUrn.fromString('cap:in=*;out=*');
2649
- assertEqual(cap.getInSpec(), '*', 'in=* should be stored as wildcard');
2650
- assertEqual(cap.getOutSpec(), '*', 'out=* should be stored as wildcard');
2723
+ assertEqual(cap.getInSpec(), MEDIA_IDENTITY, 'in=* should normalize to media:');
2724
+ assertEqual(cap.getOutSpec(), MEDIA_IDENTITY, 'out=* should normalize to media:');
2651
2725
  }
2652
2726
 
2653
2727
  // TEST644: cap:in=media:;out=* has specific in, wildcard out
2654
2728
  function test644_specificInWildcardOut() {
2655
2729
  const cap = CapUrn.fromString('cap:in=media:;out=*');
2656
2730
  assertEqual(cap.getInSpec(), 'media:', 'Should have specific in');
2657
- assertEqual(cap.getOutSpec(), '*', 'Should have wildcard out');
2731
+ assertEqual(cap.getOutSpec(), 'media:', 'Wildcard out should normalize to media:');
2658
2732
  }
2659
2733
 
2660
2734
  // TEST645: cap:in=*;out=media:text has wildcard in, specific out
2661
2735
  function test645_wildcardInSpecificOut() {
2662
2736
  const cap = CapUrn.fromString('cap:in=*;out=media:text');
2663
- assertEqual(cap.getInSpec(), '*', 'Should have wildcard in');
2737
+ assertEqual(cap.getInSpec(), 'media:', 'Wildcard in should normalize to media:');
2664
2738
  assertEqual(cap.getOutSpec(), 'media:text', 'Should have specific out');
2665
2739
  }
2666
2740
 
2667
- // TEST646: N/A for JS (JS allows in=foo since it just checks for media: or *)
2668
- // TEST647: N/A for JS (JS allows out=bar since it just checks for media: or *)
2741
+ // TEST646: cap:in=foo fails (invalid media URN)
2742
+ function test646_invalidInSpecFails() {
2743
+ assertThrows(
2744
+ () => CapUrn.fromString('cap:in=foo;out=media:'),
2745
+ ErrorCodes.INVALID_IN_SPEC,
2746
+ 'Invalid in spec should fail hard'
2747
+ );
2748
+ }
2749
+
2750
+ // TEST647: cap:in=media:;out=bar fails (invalid media URN)
2751
+ function test647_invalidOutSpecFails() {
2752
+ assertThrows(
2753
+ () => CapUrn.fromString('cap:in=media:;out=bar'),
2754
+ ErrorCodes.INVALID_OUT_SPEC,
2755
+ 'Invalid out spec should fail hard'
2756
+ );
2757
+ }
2669
2758
 
2670
2759
  // TEST648: Wildcard in/out match specific caps
2671
2760
  function test648_wildcardAcceptsSpecific() {
@@ -2687,7 +2776,7 @@ function test649_specificityScoring() {
2687
2776
 
2688
2777
  // TEST650: N/A for JS (JS requires in/out, cap:in;out;op=test would fail parsing)
2689
2778
 
2690
- // TEST651: All identity forms with explicit wildcards produce the same CapUrn
2779
+ // TEST651: All identity forms produce the same CapUrn
2691
2780
  function test651_identityFormsEquivalent() {
2692
2781
  const forms = [
2693
2782
  'cap:in=*;out=*',
@@ -2707,7 +2796,7 @@ function test651_identityFormsEquivalent() {
2707
2796
 
2708
2797
  // TEST652: N/A for JS (CAP_IDENTITY constant not in JS)
2709
2798
 
2710
- // TEST653: Identity (no extra tags) does not steal routes from specific handlers
2799
+ // TEST653: Identity (no tags) does not match specific requests via routing
2711
2800
  function test653_identityRoutingIsolation() {
2712
2801
  const identity = CapUrn.fromString('cap:in=*;out=*');
2713
2802
  const specificRequest = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
@@ -4217,11 +4306,11 @@ function testRenderer_buildStrandGraphData_nestedForEachThrows() {
4217
4306
  // must throw the same error to surface the issue rather than
4218
4307
  // render a malformed graph.
4219
4308
  const payload = {
4220
- source_spec: 'media:a;list;list',
4309
+ source_spec: 'media:a;list',
4221
4310
  target_spec: 'media:a',
4222
4311
  steps: [
4223
- makeForEachStep('media:a;list;list'),
4224
4312
  makeForEachStep('media:a;list'),
4313
+ makeForEachStep('media:a'),
4225
4314
  makeCapStep('cap:in="media:a";op=x;out="media:a"', 'x', 'media:a', 'media:a', false, false),
4226
4315
  ],
4227
4316
  };
@@ -5409,7 +5498,7 @@ async function runTests() {
5409
5498
  runTest('TEST308: model_path_urn', test308_modelPathUrn);
5410
5499
  runTest('TEST309: model_availability_and_path_are_distinct', test309_modelAvailabilityAndPathAreDistinct);
5411
5500
  runTest('TEST310: llm_generate_text_urn', test310_llmGenerateTextUrn);
5412
- runTest('TEST311: llm_generate_text_urn_specs', test311_llmGenerateTextUrnSpecs);
5501
+ runTest('llm_generate_text_urn_specs', testLlmGenerateTextUrnSpecs);
5413
5502
  runTest('TEST312: all_urn_builders_produce_valid_urns', test312_allUrnBuildersProduceValidUrns);
5414
5503
 
5415
5504
  // JS-specific tests (no Rust number)
@@ -5449,41 +5538,41 @@ async function runTests() {
5449
5538
  runTest('TEST334: cartridge_repo_client_needs_sync', test334_cartridgeRepoClientNeedsSync);
5450
5539
  runTest('TEST335: cartridge_repo_server_client_integration', test335_cartridgeRepoServerClientIntegration);
5451
5540
 
5452
- // media_urn.rs: TEST546-TEST558 (MediaUrn predicates)
5541
+ // media_urn.rs: TEST1312-TEST1315, TEST1298-TEST1302 (MediaUrn predicates)
5453
5542
  console.log('\n--- media_urn.rs (predicates) ---');
5454
- runTest('TEST546: is_image', test546_isImage);
5455
- runTest('TEST547: is_audio', test547_isAudio);
5456
- runTest('TEST548: is_video', test548_isVideo);
5457
- runTest('TEST549: is_numeric', test549_isNumeric);
5458
- runTest('TEST550: is_bool', test550_isBool);
5459
- runTest('TEST551: is_file_path', test551_isFilePath);
5460
- runTest('TEST552: is_file_path_array', test552_isFilePathArray);
5461
- runTest('TEST553: is_any_file_path', test553_isAnyFilePath);
5462
- console.log(' SKIP TEST554: N/A for JS (collection types removed from capdag)');
5463
- console.log(' SKIP TEST555: N/A for JS (with_tag/without_tag on MediaUrn)');
5464
- console.log(' SKIP TEST556: N/A for JS (image_media_urn_for_ext helper)');
5465
- console.log(' SKIP TEST557: N/A for JS (audio_media_urn_for_ext helper)');
5466
- runTest('TEST558: predicate_constant_consistency', test558_predicateConstantConsistency);
5467
-
5468
- // cap_urn.rs: TEST559-TEST567 (CapUrn tier tests)
5543
+ runTest('TEST1312: is_image', test1312_isImage);
5544
+ runTest('TEST1313: is_audio', test1313_isAudio);
5545
+ runTest('TEST1314: is_video', test1314_isVideo);
5546
+ runTest('TEST1315: is_numeric', test1315_isNumeric);
5547
+ runTest('TEST1298: is_bool', test1298_isBool);
5548
+ runTest('TEST1299: is_file_path', test1299_isFilePath);
5549
+ runTest('TEST1300: is_file_path_array', test1300_isFilePathArray);
5550
+ runTest('TEST1301: is_any_file_path', test1301_isAnyFilePath);
5551
+ runTest('TEST1302: predicate_constant_consistency', test1302_predicateConstantConsistency);
5552
+
5553
+ // cap_urn.rs: TEST1303-TEST1307 (CapUrn tier tests)
5469
5554
  console.log('\n--- cap_urn.rs (tier tests) ---');
5470
- runTest('TEST559: without_tag', test559_withoutTag);
5471
- runTest('TEST560: with_in_out_spec', test560_withInOutSpec);
5472
- console.log(' SKIP TEST561: N/A for JS (in_media_urn/out_media_urn)');
5473
- console.log(' SKIP TEST562: N/A for JS (canonical_option)');
5474
- runTest('TEST563: find_all_matches', test563_findAllMatches);
5475
- runTest('TEST564: are_compatible', test564_areCompatible);
5476
- console.log(' SKIP TEST565: N/A for JS (tags_to_string)');
5477
- runTest('TEST566: with_tag_ignores_in_out', test566_withTagIgnoresInOut);
5478
- console.log(' SKIP TEST567: N/A for JS (conforms_to_str/accepts_str)');
5555
+ runTest('TEST1303: without_tag', test1303_withoutTag);
5556
+ runTest('TEST1304: with_in_out_spec', test1304_withInOutSpec);
5557
+ runTest('TEST1305: find_all_matches', test1305_findAllMatches);
5558
+ runTest('TEST1306: are_compatible', test1306_areCompatible);
5559
+ runTest('TEST1307: with_tag_ignores_in_out', test1307_withTagIgnoresInOut);
5560
+ runTest('TEST1294: rule11_void_input_with_stdin_rejected', test1294_rule11VoidInputWithStdinRejected);
5561
+ runTest('TEST1295: rule11_non_void_input_without_stdin_rejected', test1295_rule11NonVoidInputWithoutStdinRejected);
5562
+ runTest('TEST1296: rule11_void_input_cli_flag_only', test1296_rule11VoidInputCliFlagOnly);
5563
+ runTest('TEST1297: rule11_non_void_input_with_stdin', test1297_rule11NonVoidInputWithStdin);
5479
5564
 
5480
5565
  // cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
5481
5566
  console.log('\n--- cap_urn.rs (wildcard tests) ---');
5482
- console.log(' SKIP TEST639-642: N/A for JS (implicit wildcard defaults)');
5567
+ runTest('TEST639: empty_cap_defaults_to_media_wildcard', test639_emptyCapDefaultsToMediaWildcard);
5568
+ runTest('TEST640: in_only_defaults_out_to_media', test640_inOnlyDefaultsOutToMedia);
5569
+ runTest('TEST641: out_only_defaults_in_to_media', test641_outOnlyDefaultsInToMedia);
5570
+ runTest('TEST642: in_out_without_values_become_media', test642_inOutWithoutValuesBecomeMedia);
5483
5571
  runTest('TEST643: explicit_asterisk_is_wildcard', test643_explicitAsteriskIsWildcard);
5484
5572
  runTest('TEST644: specific_in_wildcard_out', test644_specificInWildcardOut);
5485
5573
  runTest('TEST645: wildcard_in_specific_out', test645_wildcardInSpecificOut);
5486
- console.log(' SKIP TEST646-647: N/A for JS (invalid spec validation differs)');
5574
+ runTest('TEST646: invalid_in_spec_fails', test646_invalidInSpecFails);
5575
+ runTest('TEST647: invalid_out_spec_fails', test647_invalidOutSpecFails);
5487
5576
  runTest('TEST648: wildcard_accepts_specific', test648_wildcardAcceptsSpecific);
5488
5577
  runTest('TEST649: specificity_scoring', test649_specificityScoring);
5489
5578
  console.log(' SKIP TEST650: N/A for JS (requires in/out)');
@@ -5628,7 +5717,7 @@ async function runTests() {
5628
5717
  runTest('RENDERER: buildStrand_standaloneCollect', testRenderer_buildStrandGraphData_standaloneCollect);
5629
5718
  runTest('RENDERER: buildStrand_unclosedForEachBody', testRenderer_buildStrandGraphData_unclosedForEachBody);
5630
5719
  runTest('RENDERER: buildStrand_nestedForEachThrows', testRenderer_buildStrandGraphData_nestedForEachThrows);
5631
- runTest('RENDERER: collapseStrand_singleCapBody', testRenderer_collapseStrand_singleCapBodyShowsCapTitleWithIterCollectMarker);
5720
+ runTest('RENDERER: collapseStrand_singleCapBody', testRenderer_collapseStrand_singleCapBodyKeepsCapOwnLabel);
5632
5721
  runTest('RENDERER: collapseStrand_unclosedForEachBody', testRenderer_collapseStrand_unclosedForEachBodyCollapses);
5633
5722
  runTest('RENDERER: collapseStrand_standaloneCollect', testRenderer_collapseStrand_standaloneCollectCollapses);
5634
5723
  runTest('RENDERER: collapseStrand_seqCapBeforeForeach', testRenderer_collapseStrand_sequenceProducingCapBeforeForeach);