capdag 1.195.502-nightly → 1.198.510

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 (2) hide show
  1. package/capdag.test.js +480 -354
  2. package/package.json +2 -2
package/capdag.test.js CHANGED
@@ -105,7 +105,8 @@ function runTest(name, fn) {
105
105
  * Uses MEDIA_VOID for in and MEDIA_OBJECT for out, matching the
106
106
  * Rust reference test_urn helper: test_urn(tags) => cap:in="media:void";{tags};out="media:record;textable"
107
107
  */
108
- function testUrn(tags) {
108
+ // TEST0051: Urn
109
+ function test0051_Urn(tags) {
109
110
  if (!tags || tags === '') {
110
111
  return `cap:in="${MEDIA_VOID}";out="${MEDIA_OBJECT}"`;
111
112
  }
@@ -131,7 +132,7 @@ function makeGraphCap(inUrn, outUrn, title) {
131
132
 
132
133
  // TEST001: Test that cap URN is created with tags parsed correctly and direction specs accessible
133
134
  function test001_capUrnCreation() {
134
- const cap = CapUrn.fromString(testUrn('generate;ext=pdf;target=thumbnail'));
135
+ const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf;target=thumbnail'));
135
136
  assert(cap.hasMarkerTag('generate'), 'Should get op tag');
136
137
  assertEqual(cap.getTag('target'), 'thumbnail', 'Should get target tag');
137
138
  assertEqual(cap.getTag('ext'), 'pdf', 'Should get ext tag');
@@ -152,8 +153,8 @@ function test002_directionSpecsRequired() {
152
153
 
153
154
  // TEST003: Test that direction specs must match exactly, different in/out types don't match, wildcard matches any
154
155
  function test003_directionMatching() {
155
- const cap = CapUrn.fromString(testUrn('generate'));
156
- const request = CapUrn.fromString(testUrn('generate'));
156
+ const cap = CapUrn.fromString(test0051_Urn('generate'));
157
+ const request = CapUrn.fromString(test0051_Urn('generate'));
157
158
  assert(cap.accepts(request), 'Same direction specs should match');
158
159
 
159
160
  // Different direction should not match
@@ -239,7 +240,7 @@ function test011_serializationSmartQuoting() {
239
240
 
240
241
  // TEST012: Test that simple cap URN round-trips (parse -> serialize -> parse equals original)
241
242
  function test012_roundTripSimple() {
242
- const original = CapUrn.fromString(testUrn('generate;ext=pdf'));
243
+ const original = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
243
244
  const serialized = original.toString();
244
245
  const reparsed = CapUrn.fromString(serialized);
245
246
  assert(original.equals(reparsed), 'Simple round-trip should produce equal URN');
@@ -272,14 +273,14 @@ function test015_capPrefixRequired() {
272
273
  'Should require cap: prefix'
273
274
  );
274
275
  // Valid cap: prefix should work
275
- const cap = CapUrn.fromString(testUrn('generate'));
276
+ const cap = CapUrn.fromString(test0051_Urn('generate'));
276
277
  assert(cap.hasMarkerTag('generate'), 'Should parse with valid cap: prefix');
277
278
  }
278
279
 
279
280
  // TEST016: Test that trailing semicolon is equivalent (same hash, same string, matches)
280
281
  function test016_trailingSemicolonEquivalence() {
281
- const cap1 = CapUrn.fromString(testUrn('generate;ext=pdf'));
282
- const cap2 = CapUrn.fromString(testUrn('generate;ext=pdf') + ';');
282
+ const cap1 = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
283
+ const cap2 = CapUrn.fromString(test0051_Urn('generate;ext=pdf') + ';');
283
284
  assert(cap1.equals(cap2), 'With/without trailing semicolon should be equal');
284
285
  assertEqual(cap1.toString(), cap2.toString(), 'Canonical forms should match');
285
286
  }
@@ -323,24 +324,24 @@ function test939_capUrnCanonicalFormDropsWildcardInOut() {
323
324
 
324
325
  // TEST017: Test tag matching: exact match, subset match, wildcard match, value mismatch
325
326
  function test017_tagMatching() {
326
- const cap = CapUrn.fromString(testUrn('generate;ext=pdf;target=thumbnail'));
327
+ const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf;target=thumbnail'));
327
328
 
328
329
  // Exact match — both directions accept
329
- const exact = CapUrn.fromString(testUrn('generate;ext=pdf;target=thumbnail'));
330
+ const exact = CapUrn.fromString(test0051_Urn('generate;ext=pdf;target=thumbnail'));
330
331
  assert(cap.accepts(exact), 'Should accept exact match');
331
332
  assert(exact.accepts(cap), 'Exact match should accept in reverse too');
332
333
 
333
334
  // Routing direction: request(generate) accepts cap(op,ext,target)
334
- const subset = CapUrn.fromString(testUrn('generate'));
335
+ const subset = CapUrn.fromString(test0051_Urn('generate'));
335
336
  assert(subset.accepts(cap), 'General request should accept more specific instance');
336
337
  assert(!cap.accepts(subset), 'Specific pattern should reject subset instance');
337
338
 
338
339
  // Routing direction: request(ext=*) accepts cap(ext=pdf)
339
- const wildcard = CapUrn.fromString(testUrn('ext=*'));
340
+ const wildcard = CapUrn.fromString(test0051_Urn('ext=*'));
340
341
  assert(wildcard.accepts(cap), 'Wildcard request should accept specific instance');
341
342
 
342
343
  // Conflicting value — neither direction accepts
343
- const mismatch = CapUrn.fromString(testUrn('extract'));
344
+ const mismatch = CapUrn.fromString(test0051_Urn('extract'));
344
345
  assert(!cap.accepts(mismatch), 'Should not accept value mismatch');
345
346
  assert(!mismatch.accepts(cap), 'Reverse mismatch should also reject');
346
347
  }
@@ -354,13 +355,13 @@ function test018_matchingCaseSensitiveValues() {
354
355
 
355
356
  // TEST019: Missing tag in instance causes rejection — pattern's tags are constraints
356
357
  function test019_missingTagHandling() {
357
- const cap = CapUrn.fromString(testUrn('generate'));
358
- const request = CapUrn.fromString(testUrn('ext=pdf'));
358
+ const cap = CapUrn.fromString(test0051_Urn('generate'));
359
+ const request = CapUrn.fromString(test0051_Urn('ext=pdf'));
359
360
  assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
360
361
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
361
362
 
362
- const cap2 = CapUrn.fromString(testUrn('generate;ext=pdf'));
363
- const request2 = CapUrn.fromString(testUrn('generate'));
363
+ const cap2 = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
364
+ const request2 = CapUrn.fromString(test0051_Urn('generate'));
364
365
  assert(!cap2.accepts(request2), 'Specific pattern should reject instance missing ext');
365
366
  assert(request2.accepts(cap2), 'General request should accept more specific instance');
366
367
  }
@@ -370,27 +371,27 @@ function test019_missingTagHandling() {
370
371
  // (must-have-any), exact `key=value` tags score 3, missing/`?` score
371
372
  // 0, `!` scores 1.
372
373
  //
373
- // testUrn() builds "cap:in=media:void;out=media:record;<tags>" so
374
+ // test0051_Urn() builds "cap:in=media:void;out=media:record;<tags>" so
374
375
  // the directional baseline is:
375
376
  // in: media:void -> {void=*} -> 2
376
377
  // out: media:record -> {record=*} -> 2
377
378
  // Total directional baseline: 4.
378
379
  function test020_specificity() {
379
- // testUrn() prepends in="media:void" (1 marker, score 2) and
380
+ // test0051_Urn() prepends in="media:void" (1 marker, score 2) and
380
381
  // out="media:record" (1 marker, score 2). Cap-URN spec is
381
382
  // 10000*spec_U(out) + 100*spec_U(in) + spec_U(y).
382
383
 
383
- const cap1 = CapUrn.fromString(testUrn('type=general'));
384
+ const cap1 = CapUrn.fromString(test0051_Urn('type=general'));
384
385
  // out=2, in=2, y=4 (type=general exact)
385
386
  assertEqual(cap1.specificity(), 10000*2 + 100*2 + 4,
386
387
  'out=2, in=2, y=type=general exact=4 -> 20204');
387
388
 
388
- const cap2 = CapUrn.fromString(testUrn('generate'));
389
+ const cap2 = CapUrn.fromString(test0051_Urn('generate'));
389
390
  // out=2, in=2, y=2 (generate marker = must-have-any)
390
391
  assertEqual(cap2.specificity(), 10000*2 + 100*2 + 2,
391
392
  'out=2, in=2, y=generate marker=2 -> 20202');
392
393
 
393
- const cap3 = CapUrn.fromString(testUrn('op;ext=pdf'));
394
+ const cap3 = CapUrn.fromString(test0051_Urn('op;ext=pdf'));
394
395
  // out=2, in=2, y=2+4 (op marker, ext=pdf exact)
395
396
  assertEqual(cap3.specificity(), 10000*2 + 100*2 + 6,
396
397
  'out=2, in=2, y=op marker(2)+ext=pdf exact(4) -> 20206');
@@ -444,16 +445,16 @@ function test023_builderPreservesCase() {
444
445
 
445
446
  // TEST024: Directional accepts — pattern's tags are constraints, instance must satisfy
446
447
  function test024_compatibility() {
447
- const cap1 = CapUrn.fromString(testUrn('generate;ext=pdf'));
448
- const cap2 = CapUrn.fromString(testUrn('generate;format=*'));
449
- const cap3 = CapUrn.fromString(testUrn('type=image;extract'));
448
+ const cap1 = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
449
+ const cap2 = CapUrn.fromString(test0051_Urn('generate;format=*'));
450
+ const cap3 = CapUrn.fromString(test0051_Urn('type=image;extract'));
450
451
 
451
452
  assert(!cap1.accepts(cap2), 'Pattern requiring ext should reject instance missing ext');
452
453
  assert(!cap2.accepts(cap1), 'Pattern requiring format should reject instance missing format');
453
454
  assert(!cap1.accepts(cap3), 'Different op should not accept');
454
455
  assert(!cap3.accepts(cap1), 'Different op should not accept in reverse');
455
456
 
456
- const general = CapUrn.fromString(testUrn('generate'));
457
+ const general = CapUrn.fromString(test0051_Urn('generate'));
457
458
  assert(general.accepts(cap1), 'General request should accept more specific instance');
458
459
  assert(!cap1.accepts(general), 'Specific pattern should reject general instance');
459
460
 
@@ -466,10 +467,10 @@ function test024_compatibility() {
466
467
  function test025_bestMatch() {
467
468
  const caps = [
468
469
  CapUrn.fromString('cap:in=*;out=*;op'),
469
- CapUrn.fromString(testUrn('generate')),
470
- CapUrn.fromString(testUrn('generate;ext=pdf'))
470
+ CapUrn.fromString(test0051_Urn('generate')),
471
+ CapUrn.fromString(test0051_Urn('generate;ext=pdf'))
471
472
  ];
472
- const request = CapUrn.fromString(testUrn('generate'));
473
+ const request = CapUrn.fromString(test0051_Urn('generate'));
473
474
  const best = CapMatcher.findBestMatch(caps, request);
474
475
  assert(best !== null, 'Should find a best match');
475
476
  assertEqual(best.getTag('ext'), 'pdf', 'Best match should be the most specific (ext=pdf)');
@@ -477,7 +478,7 @@ function test025_bestMatch() {
477
478
 
478
479
  // TEST026: Test merge combines tags from both caps, subset keeps only specified tags
479
480
  function test026_mergeAndSubset() {
480
- const cap1 = CapUrn.fromString(testUrn('generate'));
481
+ const cap1 = CapUrn.fromString(test0051_Urn('generate'));
481
482
  const cap2 = CapUrn.fromString('cap:in="media:textable";ext=pdf;format=binary;out="media:"');
482
483
 
483
484
  // Merge (other takes precedence)
@@ -496,7 +497,7 @@ function test026_mergeAndSubset() {
496
497
 
497
498
  // TEST027: Test with_wildcard_tag sets tag to wildcard, including in/out
498
499
  function test027_wildcardTag() {
499
- const cap = CapUrn.fromString(testUrn('ext=pdf'));
500
+ const cap = CapUrn.fromString(test0051_Urn('ext=pdf'));
500
501
  const wildcardExt = cap.withWildcardTag('ext');
501
502
  assertEqual(wildcardExt.getTag('ext'), '*', 'Should set ext to wildcard');
502
503
 
@@ -526,7 +527,7 @@ function test029_minimalCapUrn() {
526
527
 
527
528
  // TEST030: Test extended characters (forward slashes, colons) in tag values
528
529
  function test030_extendedCharacterSupport() {
529
- const cap = CapUrn.fromString(testUrn('url=https://example_org/api;path=/some/file'));
530
+ const cap = CapUrn.fromString(test0051_Urn('url=https://example_org/api;path=/some/file'));
530
531
  assertEqual(cap.getTag('url'), 'https://example_org/api', 'Should support colons and slashes');
531
532
  assertEqual(cap.getTag('path'), '/some/file', 'Should support forward slashes');
532
533
  }
@@ -534,13 +535,13 @@ function test030_extendedCharacterSupport() {
534
535
  // TEST031: Test wildcard rejected in keys but accepted in values
535
536
  function test031_wildcardRestrictions() {
536
537
  assertThrows(
537
- () => CapUrn.fromString(testUrn('*=value')),
538
+ () => CapUrn.fromString(test0051_Urn('*=value')),
538
539
  ErrorCodes.INVALID_CHARACTER,
539
540
  'Should reject wildcard in key'
540
541
  );
541
542
 
542
543
  // Wildcard accepted in values
543
- const cap = CapUrn.fromString(testUrn('key=*'));
544
+ const cap = CapUrn.fromString(test0051_Urn('key=*'));
544
545
  assertEqual(cap.getTag('key'), '*', 'Should accept wildcard in value');
545
546
 
546
547
  // Wildcard in in/out normalizes to media:
@@ -552,7 +553,7 @@ function test031_wildcardRestrictions() {
552
553
  // TEST032: Test duplicate keys are rejected with DuplicateKey error
553
554
  function test032_duplicateKeyRejection() {
554
555
  assertThrows(
555
- () => CapUrn.fromString(testUrn('key=value1;key=value2')),
556
+ () => CapUrn.fromString(test0051_Urn('key=value1;key=value2')),
556
557
  ErrorCodes.DUPLICATE_KEY,
557
558
  'Should reject duplicate keys'
558
559
  );
@@ -561,14 +562,14 @@ function test032_duplicateKeyRejection() {
561
562
  // TEST033: Test pure numeric keys rejected, mixed alphanumeric allowed, numeric values allowed
562
563
  function test033_numericKeyRestriction() {
563
564
  assertThrows(
564
- () => CapUrn.fromString(testUrn('123=value')),
565
+ () => CapUrn.fromString(test0051_Urn('123=value')),
565
566
  ErrorCodes.NUMERIC_KEY,
566
567
  'Should reject pure numeric keys'
567
568
  );
568
569
  // Mixed alphanumeric allowed
569
- const cap1 = CapUrn.fromString(testUrn('key123=value'));
570
+ const cap1 = CapUrn.fromString(test0051_Urn('key123=value'));
570
571
  assertEqual(cap1.getTag('key123'), 'value', 'Mixed alphanumeric key should be allowed');
571
- const cap2 = CapUrn.fromString(testUrn('x123key=value'));
572
+ const cap2 = CapUrn.fromString(test0051_Urn('x123key=value'));
572
573
  assertEqual(cap2.getTag('x123key'), 'value', 'Mixed alphanumeric key should be allowed');
573
574
  }
574
575
 
@@ -633,45 +634,45 @@ function test039_getTagReturnsDirectionSpecs() {
633
634
 
634
635
  // TEST040: Matching semantics - exact match succeeds
635
636
  function test040_matchingSemanticsExactMatch() {
636
- const cap = CapUrn.fromString(testUrn('generate;ext=pdf'));
637
- const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
637
+ const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
638
+ const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
638
639
  assert(cap.accepts(request), 'Exact match should accept');
639
640
  }
640
641
 
641
642
  // TEST041: Matching semantics - cap missing tag matches (implicit wildcard)
642
643
  function test041_matchingSemanticsCapMissingTag() {
643
- const cap = CapUrn.fromString(testUrn('generate'));
644
- const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
644
+ const cap = CapUrn.fromString(test0051_Urn('generate'));
645
+ const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
645
646
  assert(cap.accepts(request), 'General pattern with only op should accept specific instance');
646
647
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
647
648
  }
648
649
 
649
650
  // TEST042: Pattern rejects instance missing required tags
650
651
  function test042_matchingSemanticsCapHasExtraTag() {
651
- const cap = CapUrn.fromString(testUrn('generate;ext=pdf;version=2'));
652
- const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
652
+ const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf;version=2'));
653
+ const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
653
654
  assert(!cap.accepts(request), 'Pattern requiring version should reject instance missing version');
654
655
  assert(request.accepts(cap), 'General request should accept refined instance');
655
656
  }
656
657
 
657
658
  // TEST043: Matching semantics - request wildcard matches specific cap value
658
659
  function test043_matchingSemanticsRequestHasWildcard() {
659
- const cap = CapUrn.fromString(testUrn('generate;ext=pdf'));
660
- const request = CapUrn.fromString(testUrn('generate;ext=*'));
660
+ const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
661
+ const request = CapUrn.fromString(test0051_Urn('generate;ext=*'));
661
662
  assert(cap.accepts(request), 'Request wildcard should match specific cap value');
662
663
  }
663
664
 
664
665
  // TEST044: Matching semantics - cap wildcard matches specific request value
665
666
  function test044_matchingSemanticsCapHasWildcard() {
666
- const cap = CapUrn.fromString(testUrn('generate;ext=*'));
667
- const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
667
+ const cap = CapUrn.fromString(test0051_Urn('generate;ext=*'));
668
+ const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
668
669
  assert(cap.accepts(request), 'Cap wildcard should match specific request value');
669
670
  }
670
671
 
671
672
  // TEST045: Matching semantics - value mismatch does not match
672
673
  function test045_matchingSemanticsValueMismatch() {
673
- const cap = CapUrn.fromString(testUrn('generate;ext=pdf'));
674
- const request = CapUrn.fromString(testUrn('generate;ext=docx'));
674
+ const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
675
+ const request = CapUrn.fromString(test0051_Urn('generate;ext=docx'));
675
676
  assert(!cap.accepts(request), 'Value mismatch should not accept');
676
677
  }
677
678
 
@@ -693,14 +694,14 @@ function test047_matchingSemanticsThumbnailVoidInput() {
693
694
  // TEST048: Matching semantics - wildcard direction matches anything
694
695
  function test048_matchingSemanticsWildcardDirection() {
695
696
  const cap = CapUrn.fromString('cap:generate');
696
- const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
697
+ const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
697
698
  assert(cap.accepts(request), 'Generic declared directions should accept a more specific matching request');
698
699
  }
699
700
 
700
701
  // TEST049: Non-overlapping tags — neither direction accepts
701
702
  function test049_matchingSemanticsCrossDimension() {
702
- const cap = CapUrn.fromString(testUrn('generate'));
703
- const request = CapUrn.fromString(testUrn('ext=pdf'));
703
+ const cap = CapUrn.fromString(test0051_Urn('generate'));
704
+ const request = CapUrn.fromString(test0051_Urn('ext=pdf'));
704
705
  assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
705
706
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
706
707
  }
@@ -1281,7 +1282,7 @@ function test116_capArgConstructors() {
1281
1282
 
1282
1283
  // TEST150: JSON roundtrip
1283
1284
  function test150_capManifestJsonSerialization() {
1284
- const capUrn = CapUrn.fromString(testUrn('extract;target=metadata'));
1285
+ const capUrn = CapUrn.fromString(test0051_Urn('extract;target=metadata'));
1285
1286
  const cap = new Cap(capUrn, 'Extract Metadata', 'extract-metadata');
1286
1287
  cap.addArg(new CapArg('media:pdf', true, [new ArgSource({ stdin: 'media:pdf' })]));
1287
1288
  cap.addArg(new CapArg(
@@ -1379,7 +1380,7 @@ function test597_capArgWithFullDefinition() {
1379
1380
 
1380
1381
  // Add a cap and check it becomes an edge with from/to nodes and carries the
1381
1382
  // registry name we passed. This is exactly the shape the renderer depends on.
1382
- function testCapFabAddCapPopulatesEdgesAndNodes() {
1383
+ function test0052_CapFabAddCapPopulatesEdgesAndNodes() {
1383
1384
  const graph = new CapFab();
1384
1385
  const cap = makeGraphCap('media:pdf', 'media:textable', 'PDF to Text');
1385
1386
  graph.addCap(cap, 'registry');
@@ -1397,7 +1398,7 @@ function testCapFabAddCapPopulatesEdgesAndNodes() {
1397
1398
 
1398
1399
  // getOutgoing takes a concrete source URN and returns edges whose from_spec
1399
1400
  // the source conforms to. It must NOT be a plain string lookup.
1400
- function testCapFabGetOutgoingConformsToMatching() {
1401
+ function test0053_CapFabGetOutgoingConformsToMatching() {
1401
1402
  const graph = new CapFab();
1402
1403
  graph.addCap(makeGraphCap('media:textable', 'media:embedding-vector', 'Embed text'), 'registry');
1403
1404
 
@@ -1418,7 +1419,7 @@ function testCapFabGetOutgoingConformsToMatching() {
1418
1419
 
1419
1420
  // Each edge must carry the registry name it was added with. This is how
1420
1421
  // the renderer colours/groups edges by provenance in browse mode.
1421
- function testCapFabDistinctRegistryNames() {
1422
+ function test0057_CapFabDistinctRegistryNames() {
1422
1423
  const graph = new CapFab();
1423
1424
  graph.addCap(makeGraphCap('media:pdf', 'media:textable', 'PDF to Text'), 'providers');
1424
1425
  graph.addCap(makeGraphCap('media:textable', 'media:embedding-vector', 'Embed'), 'cartridges');
@@ -1629,7 +1630,7 @@ function test310_llmGenerateTextUrn() {
1629
1630
  }
1630
1631
 
1631
1632
  // Mirror-specific coverage: llm_generate_text_urn input/output specs conform to MEDIA_STRING
1632
- function testLlmGenerateTextUrnSpecs() {
1633
+ function test0058_LlmGenerateTextUrnSpecs() {
1633
1634
  const urn = llmGenerateTextUrn();
1634
1635
  const inSpec = TaggedUrn.fromString(urn.getInSpec());
1635
1636
  const expectedIn = TaggedUrn.fromString(MEDIA_STRING);
@@ -1660,7 +1661,7 @@ function test312_allUrnBuildersProduceValidUrns() {
1660
1661
  // These tests cover JS-specific functionality not in the Rust numbering scheme
1661
1662
  // but are important for capdag-js correctness.
1662
1663
 
1663
- function testJS_buildExtensionIndex() {
1664
+ function test0059_JS_buildExtensionIndex() {
1664
1665
  const mediaDefs = [
1665
1666
  { urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
1666
1667
  { urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] },
@@ -1676,7 +1677,8 @@ function testJS_buildExtensionIndex() {
1676
1677
  assertEqual(index.get('pdf')[0], 'media:pdf', 'pdf should map correctly');
1677
1678
  }
1678
1679
 
1679
- function testJS_mediaUrnsForExtension() {
1680
+ // TEST0069: J s media urns for extension
1681
+ function test0069_JS_mediaUrnsForExtension() {
1680
1682
  const mediaDefs = [
1681
1683
  { urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
1682
1684
  { urn: 'media:json;textable;record', media_type: 'application/json', extensions: ['json'] },
@@ -1704,7 +1706,8 @@ function testJS_mediaUrnsForExtension() {
1704
1706
  assert(thrownError instanceof MediaDefError, 'Should throw MediaDefError for unknown ext');
1705
1707
  }
1706
1708
 
1707
- function testJS_getExtensionMappings() {
1709
+ // TEST0070: J s get extension mappings
1710
+ function test0070_JS_getExtensionMappings() {
1708
1711
  const mediaDefs = [
1709
1712
  { urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
1710
1713
  { urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] }
@@ -1714,7 +1717,8 @@ function testJS_getExtensionMappings() {
1714
1717
  assertEqual(mappings.length, 3, 'Should have 3 mappings');
1715
1718
  }
1716
1719
 
1717
- function testJS_resolveMediaUrnFromSpecs() {
1720
+ // TEST0073: J s resolve media urn from specs
1721
+ function test0073_JS_resolveMediaUrnFromSpecs() {
1718
1722
  const mediaDefs = [
1719
1723
  { urn: MEDIA_STRING, media_type: 'text/plain', title: 'String', profile_uri: 'https://capdag.com/schema/str' },
1720
1724
  { urn: 'media:custom', media_type: 'application/json', title: 'Custom Output', schema: { type: 'object' } }
@@ -1726,8 +1730,9 @@ function testJS_resolveMediaUrnFromSpecs() {
1726
1730
  assert(outputSpec.schema !== null, 'Should have schema');
1727
1731
  }
1728
1732
 
1729
- function testJS_capJSONSerialization() {
1730
- const urn = CapUrn.fromString(testUrn('test'));
1733
+ // TEST0079: J s cap j s o n serialization
1734
+ function test0079_JS_capJSONSerialization() {
1735
+ const urn = CapUrn.fromString(test0051_Urn('test'));
1731
1736
  const cap = new Cap(urn, 'Test Cap', 'test_command');
1732
1737
  cap.arguments = {
1733
1738
  required: [{ name: 'input', media_urn: MEDIA_STRING }],
@@ -1749,8 +1754,8 @@ function testJS_capJSONSerialization() {
1749
1754
  // backticks, embedded quotes, Unicode) so escaping mismatches between
1750
1755
  // JSON.stringify on this side and the Rust serializer on the other side
1751
1756
  // surface as failures here.
1752
- function testJS_capDocumentationRoundTrip() {
1753
- const urn = CapUrn.fromString(testUrn('documented'));
1757
+ function test0080_JS_capDocumentationRoundTrip() {
1758
+ const urn = CapUrn.fromString(test0051_Urn('documented'));
1754
1759
  const cap = new Cap(urn, 'Documented Cap', 'documented');
1755
1760
  const body = '# Documented Cap\r\n\nDoes the thing.\n\n```bash\necho "hi"\n```\n\nSee also: \u2605\n';
1756
1761
  cap.setDocumentation(body);
@@ -1771,8 +1776,8 @@ function testJS_capDocumentationRoundTrip() {
1771
1776
  // toDictionary behaviour. A regression where null is emitted as
1772
1777
  // `documentation: null` would break the symmetric round-trip with Rust
1773
1778
  // (which has no null sentinel) and pollute generated JSON.
1774
- function testJS_capDocumentationOmittedWhenNull() {
1775
- const urn = CapUrn.fromString(testUrn('undocumented'));
1779
+ function test0081_JS_capDocumentationOmittedWhenNull() {
1780
+ const urn = CapUrn.fromString(test0051_Urn('undocumented'));
1776
1781
  const cap = new Cap(urn, 'Undocumented Cap', 'undocumented');
1777
1782
  assertEqual(cap.getDocumentation(), null, 'Default documentation must be null');
1778
1783
 
@@ -1794,7 +1799,7 @@ function testJS_capDocumentationOmittedWhenNull() {
1794
1799
  // resolveMediaUrn into the resolved MediaDef. Mirrors TEST924 on the Rust
1795
1800
  // side. This is the path every UI consumer uses, so a break here makes the
1796
1801
  // new field invisible everywhere downstream.
1797
- function testJS_mediaDefDocumentationPropagatesThroughResolve() {
1802
+ function test0082_JS_mediaDefDocumentationPropagatesThroughResolve() {
1798
1803
  const body = '## Markdown body\n\nWith `code` and a [link](https://example.com).';
1799
1804
  const mediaDefs = [
1800
1805
  {
@@ -1824,20 +1829,23 @@ function testJS_mediaDefDocumentationPropagatesThroughResolve() {
1824
1829
  assertEqual(emptyDoc.documentation, null, 'Empty documentation string must collapse to null');
1825
1830
  }
1826
1831
 
1827
- function testJS_stdinSourceKindConstants() {
1832
+ // TEST0083: J s stdin source kind constants
1833
+ function test0083_JS_stdinSourceKindConstants() {
1828
1834
  assert(StdinSourceKind.DATA !== undefined, 'DATA kind should be defined');
1829
1835
  assert(StdinSourceKind.FILE_REFERENCE !== undefined, 'FILE_REFERENCE kind should be defined');
1830
1836
  assert(StdinSourceKind.DATA !== StdinSourceKind.FILE_REFERENCE, 'Kind values should be distinct');
1831
1837
  }
1832
1838
 
1833
- function testJS_stdinSourceNullData() {
1839
+ // TEST0084: J s stdin source null data
1840
+ function test0084_JS_stdinSourceNullData() {
1834
1841
  const source = StdinSource.fromData(null);
1835
1842
  assert(source !== null, 'Should create source');
1836
1843
  assert(source.isData(), 'Should be data source');
1837
1844
  assertEqual(source.data, null, 'Data should be null');
1838
1845
  }
1839
1846
 
1840
- function testJS_mediaDefConstruction() {
1847
+ // TEST0085: J s media def construction
1848
+ function test0085_JS_mediaDefConstruction() {
1841
1849
  const spec1 = new MediaDef('text/plain', 'https://capdag.com/schema/str', null, 'String', null, 'media:string');
1842
1850
  assertEqual(spec1.contentType, 'text/plain', 'Should have content type');
1843
1851
  assertEqual(spec1.profile, 'https://capdag.com/schema/str', 'Should have profile');
@@ -2501,7 +2509,7 @@ function test1299_isFilePath() {
2501
2509
 
2502
2510
  // Mirror-specific coverage: isCollection returns true when collection marker tag is present
2503
2511
  // Mirror-specific coverage: N/A for JS (MEDIA_COLLECTION constants removed - no longer exists)
2504
- function testisCollection() {
2512
+ function test0086_isCollection() {
2505
2513
  // Skip - collection types removed from capdag
2506
2514
  }
2507
2515
 
@@ -2924,22 +2932,25 @@ function test657_effectDispatchRequiresExplicitWildcard() {
2924
2932
 
2925
2933
  // --- Machine parser tests (mirrors parser.rs tests) ---
2926
2934
 
2927
- function testMachine_emptyInput() {
2935
+ function test0087_Machine_emptyInput() {
2928
2936
  assertThrowsWithCode(() => parseMachine(''), MachineSyntaxErrorCodes.EMPTY);
2929
2937
  }
2930
2938
 
2931
- function testMachine_whitespaceOnly() {
2939
+ // TEST0088: Machine whitespace only
2940
+ function test0088_Machine_whitespaceOnly() {
2932
2941
  assertThrowsWithCode(() => parseMachine(' \n \t '), MachineSyntaxErrorCodes.EMPTY);
2933
2942
  }
2934
2943
 
2935
- function testMachine_headerOnlyNoWirings() {
2944
+ // TEST0089: Machine header only no wirings
2945
+ function test0089_Machine_headerOnlyNoWirings() {
2936
2946
  assertThrowsWithCode(
2937
2947
  () => Machine.fromString('[extract cap:in="media:pdf";extract;out="media:txt;textable"]'),
2938
2948
  MachineSyntaxErrorCodes.NO_EDGES
2939
2949
  );
2940
2950
  }
2941
2951
 
2942
- function testMachine_duplicateAlias() {
2952
+ // TEST0090: Machine duplicate alias
2953
+ function test0090_Machine_duplicateAlias() {
2943
2954
  assertThrowsWithCode(
2944
2955
  () => Machine.fromString(
2945
2956
  '[ex cap:in="media:pdf";extract;out="media:txt;textable"]' +
@@ -2950,7 +2961,8 @@ function testMachine_duplicateAlias() {
2950
2961
  );
2951
2962
  }
2952
2963
 
2953
- function testMachine_simpleLinearChain() {
2964
+ // TEST0094: Machine simple linear chain
2965
+ function test0094_Machine_simpleLinearChain() {
2954
2966
  const g = Machine.fromString(
2955
2967
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
2956
2968
  '[doc -> extract -> text]'
@@ -2965,7 +2977,8 @@ function testMachine_simpleLinearChain() {
2965
2977
  assertEqual(edge.isLoop, false);
2966
2978
  }
2967
2979
 
2968
- function testMachine_twoStepChain() {
2980
+ // TEST0095: Machine two step chain
2981
+ function test0095_Machine_twoStepChain() {
2969
2982
  const g = Machine.fromString(
2970
2983
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
2971
2984
  '[embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"]' +
@@ -2979,7 +2992,8 @@ function testMachine_twoStepChain() {
2979
2992
  'Second edge target should be media:embedding-vector;record;textable');
2980
2993
  }
2981
2994
 
2982
- function testMachine_fanOut() {
2995
+ // TEST0096: Machine fan out
2996
+ function test0096_Machine_fanOut() {
2983
2997
  const g = Machine.fromString(
2984
2998
  '[meta cap:in="media:pdf";extract-metadata;out="media:file-metadata;record;textable"]' +
2985
2999
  '[outline cap:in="media:pdf";extract-outline;out="media:document-outline;record;textable"]' +
@@ -2996,7 +3010,8 @@ function testMachine_fanOut() {
2996
3010
  }
2997
3011
  }
2998
3012
 
2999
- function testMachine_fanInSecondaryAssignedByPriorWiring() {
3013
+ // TEST0097: Machine fan in secondary assigned by prior wiring
3014
+ function test0097_Machine_fanInSecondaryAssignedByPriorWiring() {
3000
3015
  const g = Machine.fromString(
3001
3016
  '[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]' +
3002
3017
  '[model_dl cap:in="media:model-spec;textable";download;out="media:model-spec;textable"]' +
@@ -3009,7 +3024,8 @@ function testMachine_fanInSecondaryAssignedByPriorWiring() {
3009
3024
  assertEqual(g.edges()[2].sources.length, 2);
3010
3025
  }
3011
3026
 
3012
- function testMachine_fanInSecondaryUnassignedGetsWildcard() {
3027
+ // TEST0098: Machine fan in secondary unassigned gets wildcard
3028
+ function test0098_Machine_fanInSecondaryUnassignedGetsWildcard() {
3013
3029
  const g = Machine.fromString(
3014
3030
  '[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]\n' +
3015
3031
  '[(thumbnail, model_spec) -> describe -> description]'
@@ -3020,7 +3036,8 @@ function testMachine_fanInSecondaryUnassignedGetsWildcard() {
3020
3036
  assertEqual(g.edges()[0].sources[1].toString(), 'media:');
3021
3037
  }
3022
3038
 
3023
- function testMachine_loopEdge() {
3039
+ // TEST0111: Machine loop edge
3040
+ function test0111_Machine_loopEdge() {
3024
3041
  const g = Machine.fromString(
3025
3042
  '[p2t cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"]' +
3026
3043
  '[pages -> LOOP p2t -> texts]'
@@ -3029,14 +3046,16 @@ function testMachine_loopEdge() {
3029
3046
  assertEqual(g.edges()[0].isLoop, true);
3030
3047
  }
3031
3048
 
3032
- function testMachine_undefinedAliasFails() {
3049
+ // TEST0112: Machine undefined alias fails
3050
+ function test0112_Machine_undefinedAliasFails() {
3033
3051
  assertThrowsWithCode(
3034
3052
  () => Machine.fromString('[doc -> nonexistent -> text]'),
3035
3053
  MachineSyntaxErrorCodes.UNDEFINED_ALIAS
3036
3054
  );
3037
3055
  }
3038
3056
 
3039
- function testMachine_nodeAliasCollision() {
3057
+ // TEST0113: Machine node alias collision
3058
+ function test0113_Machine_nodeAliasCollision() {
3040
3059
  assertThrowsWithCode(
3041
3060
  () => Machine.fromString(
3042
3061
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
@@ -3046,7 +3065,8 @@ function testMachine_nodeAliasCollision() {
3046
3065
  );
3047
3066
  }
3048
3067
 
3049
- function testMachine_conflictingMediaTypesFail() {
3068
+ // TEST0114: Machine conflicting media types fail
3069
+ function test0114_Machine_conflictingMediaTypesFail() {
3050
3070
  assertThrowsWithCode(
3051
3071
  () => Machine.fromString(
3052
3072
  '[cap1 cap:in="media:txt;textable";a;out="media:pdf"]' +
@@ -3058,7 +3078,8 @@ function testMachine_conflictingMediaTypesFail() {
3058
3078
  );
3059
3079
  }
3060
3080
 
3061
- function testMachine_multilineFormat() {
3081
+ // TEST0117: Machine multiline format
3082
+ function test0117_Machine_multilineFormat() {
3062
3083
  const g = Machine.fromString(
3063
3084
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n' +
3064
3085
  '[embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"]\n' +
@@ -3068,7 +3089,8 @@ function testMachine_multilineFormat() {
3068
3089
  assertEqual(g.edgeCount(), 2);
3069
3090
  }
3070
3091
 
3071
- function testMachine_differentAliasesSameGraph() {
3092
+ // TEST0118: Machine different aliases same graph
3093
+ function test0118_Machine_differentAliasesSameGraph() {
3072
3094
  const g1 = Machine.fromString(
3073
3095
  '[ex cap:in="media:pdf";extract;out="media:txt;textable"]' +
3074
3096
  '[a -> ex -> b]'
@@ -3080,14 +3102,16 @@ function testMachine_differentAliasesSameGraph() {
3080
3102
  assert(g1.isEquivalent(g2), 'Different aliases should produce equivalent graphs');
3081
3103
  }
3082
3104
 
3083
- function testMachine_malformedInputFails() {
3105
+ // TEST0119: Machine malformed input fails
3106
+ function test0119_Machine_malformedInputFails() {
3084
3107
  assertThrowsWithCode(
3085
3108
  () => parseMachine('not valid machine notation'),
3086
3109
  MachineSyntaxErrorCodes.PARSE_ERROR
3087
3110
  );
3088
3111
  }
3089
3112
 
3090
- function testMachine_unterminatedBracketFails() {
3113
+ // TEST0120: Machine unterminated bracket fails
3114
+ function test0120_Machine_unterminatedBracketFails() {
3091
3115
  assertThrowsWithCode(
3092
3116
  () => parseMachine('[extract cap:in=media:pdf'),
3093
3117
  MachineSyntaxErrorCodes.PARSE_ERROR
@@ -3096,7 +3120,7 @@ function testMachine_unterminatedBracketFails() {
3096
3120
 
3097
3121
  // --- Machine parser line-based mode tests ---
3098
3122
 
3099
- function testMachine_lineBasedSimpleChain() {
3123
+ function test0121_Machine_lineBasedSimpleChain() {
3100
3124
  const g = Machine.fromString(
3101
3125
  'extract cap:in="media:pdf";extract;out="media:txt;textable"\n' +
3102
3126
  'doc -> extract -> text'
@@ -3109,7 +3133,8 @@ function testMachine_lineBasedSimpleChain() {
3109
3133
  'Target should be media:txt;textable');
3110
3134
  }
3111
3135
 
3112
- function testMachine_lineBasedTwoStepChain() {
3136
+ // TEST0122: Machine line based two step chain
3137
+ function test0122_Machine_lineBasedTwoStepChain() {
3113
3138
  const g = Machine.fromString(
3114
3139
  'extract cap:in="media:pdf";extract;out="media:txt;textable"\n' +
3115
3140
  'embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"\n' +
@@ -3119,7 +3144,8 @@ function testMachine_lineBasedTwoStepChain() {
3119
3144
  assertEqual(g.edgeCount(), 2);
3120
3145
  }
3121
3146
 
3122
- function testMachine_lineBasedLoop() {
3147
+ // TEST0123: Machine line based loop
3148
+ function test0123_Machine_lineBasedLoop() {
3123
3149
  const g = Machine.fromString(
3124
3150
  'p2t cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"\n' +
3125
3151
  'pages -> LOOP p2t -> texts'
@@ -3128,7 +3154,8 @@ function testMachine_lineBasedLoop() {
3128
3154
  assertEqual(g.edges()[0].isLoop, true);
3129
3155
  }
3130
3156
 
3131
- function testMachine_lineBasedFanIn() {
3157
+ // TEST0124: Machine line based fan in
3158
+ function test0124_Machine_lineBasedFanIn() {
3132
3159
  const g = Machine.fromString(
3133
3160
  'thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"\n' +
3134
3161
  'model_dl cap:in="media:model-spec;textable";download;out="media:model-spec;textable"\n' +
@@ -3141,7 +3168,8 @@ function testMachine_lineBasedFanIn() {
3141
3168
  assertEqual(g.edges()[2].sources.length, 2);
3142
3169
  }
3143
3170
 
3144
- function testMachine_mixedBracketedAndLineBased() {
3171
+ // TEST0125: Machine mixed bracketed and line based
3172
+ function test0125_Machine_mixedBracketedAndLineBased() {
3145
3173
  const g = Machine.fromString(
3146
3174
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n' +
3147
3175
  'doc -> extract -> text'
@@ -3149,7 +3177,8 @@ function testMachine_mixedBracketedAndLineBased() {
3149
3177
  assertEqual(g.edgeCount(), 1);
3150
3178
  }
3151
3179
 
3152
- function testMachine_lineBasedEquivalentToBracketed() {
3180
+ // TEST0126: Machine line based equivalent to bracketed
3181
+ function test0126_Machine_lineBasedEquivalentToBracketed() {
3153
3182
  const g1 = Machine.fromString(
3154
3183
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
3155
3184
  '[doc -> extract -> text]'
@@ -3161,7 +3190,8 @@ function testMachine_lineBasedEquivalentToBracketed() {
3161
3190
  assert(g1.isEquivalent(g2), 'Line-based and bracketed must produce equivalent graphs');
3162
3191
  }
3163
3192
 
3164
- function testMachine_lineBasedFormatSerialization() {
3193
+ // TEST0127: Machine line based format serialization
3194
+ function test0127_Machine_lineBasedFormatSerialization() {
3165
3195
  const g = new Machine([
3166
3196
  new MachineEdge(
3167
3197
  [MediaUrn.fromString('media:pdf')],
@@ -3182,7 +3212,8 @@ function testMachine_lineBasedFormatSerialization() {
3182
3212
  assert(g.isEquivalent(reparsed), 'Line-based round-trip must produce equivalent graph');
3183
3213
  }
3184
3214
 
3185
- function testMachine_lineBasedAndBracketedParseSameGraph() {
3215
+ // TEST0128: Machine line based and bracketed parse same graph
3216
+ function test0128_Machine_lineBasedAndBracketedParseSameGraph() {
3186
3217
  const g = new Machine([
3187
3218
  new MachineEdge(
3188
3219
  [MediaUrn.fromString('media:pdf')],
@@ -3208,7 +3239,7 @@ function testMachine_lineBasedAndBracketedParseSameGraph() {
3208
3239
 
3209
3240
  // --- Machine graph tests (mirrors graph.rs tests) ---
3210
3241
 
3211
- function testMachine_edgeEquivalenceSameUrns() {
3242
+ function test0129_Machine_edgeEquivalenceSameUrns() {
3212
3243
  const e1 = new MachineEdge(
3213
3244
  [MediaUrn.fromString('media:pdf')],
3214
3245
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3224,7 +3255,8 @@ function testMachine_edgeEquivalenceSameUrns() {
3224
3255
  assert(e1.isEquivalent(e2), 'Same URNs should be equivalent');
3225
3256
  }
3226
3257
 
3227
- function testMachine_edgeEquivalenceDifferentCapUrns() {
3258
+ // TEST0130: Machine edge equivalence different cap urns
3259
+ function test0130_Machine_edgeEquivalenceDifferentCapUrns() {
3228
3260
  const e1 = new MachineEdge(
3229
3261
  [MediaUrn.fromString('media:pdf')],
3230
3262
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3240,7 +3272,8 @@ function testMachine_edgeEquivalenceDifferentCapUrns() {
3240
3272
  assert(!e1.isEquivalent(e2), 'Different cap URNs should not be equivalent');
3241
3273
  }
3242
3274
 
3243
- function testMachine_edgeEquivalenceDifferentTargets() {
3275
+ // TEST0131: Machine edge equivalence different targets
3276
+ function test0131_Machine_edgeEquivalenceDifferentTargets() {
3244
3277
  const e1 = new MachineEdge(
3245
3278
  [MediaUrn.fromString('media:pdf')],
3246
3279
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3256,7 +3289,8 @@ function testMachine_edgeEquivalenceDifferentTargets() {
3256
3289
  assert(!e1.isEquivalent(e2), 'Different targets should not be equivalent');
3257
3290
  }
3258
3291
 
3259
- function testMachine_edgeEquivalenceDifferentLoopFlag() {
3292
+ // TEST0132: Machine edge equivalence different loop flag
3293
+ function test0132_Machine_edgeEquivalenceDifferentLoopFlag() {
3260
3294
  const e1 = new MachineEdge(
3261
3295
  [MediaUrn.fromString('media:pdf')],
3262
3296
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3272,7 +3306,8 @@ function testMachine_edgeEquivalenceDifferentLoopFlag() {
3272
3306
  assert(!e1.isEquivalent(e2), 'Different loop flags should not be equivalent');
3273
3307
  }
3274
3308
 
3275
- function testMachine_edgeEquivalenceSourceOrderIndependent() {
3309
+ // TEST0133: Machine edge equivalence source order independent
3310
+ function test0133_Machine_edgeEquivalenceSourceOrderIndependent() {
3276
3311
  const e1 = new MachineEdge(
3277
3312
  [MediaUrn.fromString('media:txt;textable'), MediaUrn.fromString('media:model-spec;textable')],
3278
3313
  CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
@@ -3288,7 +3323,8 @@ function testMachine_edgeEquivalenceSourceOrderIndependent() {
3288
3323
  assert(e1.isEquivalent(e2), 'Source order should not matter for equivalence');
3289
3324
  }
3290
3325
 
3291
- function testMachine_edgeEquivalenceDifferentSourceCount() {
3326
+ // TEST0134: Machine edge equivalence different source count
3327
+ function test0134_Machine_edgeEquivalenceDifferentSourceCount() {
3292
3328
  const e1 = new MachineEdge(
3293
3329
  [MediaUrn.fromString('media:txt;textable')],
3294
3330
  CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
@@ -3304,7 +3340,8 @@ function testMachine_edgeEquivalenceDifferentSourceCount() {
3304
3340
  assert(!e1.isEquivalent(e2), 'Different source counts should not be equivalent');
3305
3341
  }
3306
3342
 
3307
- function testMachine_graphEquivalenceSameEdges() {
3343
+ // TEST0135: Machine graph equivalence same edges
3344
+ function test0135_Machine_graphEquivalenceSameEdges() {
3308
3345
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3309
3346
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3310
3347
  );
@@ -3319,7 +3356,8 @@ function testMachine_graphEquivalenceSameEdges() {
3319
3356
  assert(g1.isEquivalent(g2), 'Same edges should be equivalent');
3320
3357
  }
3321
3358
 
3322
- function testMachine_graphEquivalenceReorderedEdges() {
3359
+ // TEST0136: Machine graph equivalence reordered edges
3360
+ function test0136_Machine_graphEquivalenceReorderedEdges() {
3323
3361
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3324
3362
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3325
3363
  );
@@ -3334,7 +3372,8 @@ function testMachine_graphEquivalenceReorderedEdges() {
3334
3372
  assert(g1.isEquivalent(g2), 'Reordered edges should still be equivalent');
3335
3373
  }
3336
3374
 
3337
- function testMachine_graphNotEquivalentDifferentEdgeCount() {
3375
+ // TEST0137: Machine graph not equivalent different edge count
3376
+ function test0137_Machine_graphNotEquivalentDifferentEdgeCount() {
3338
3377
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3339
3378
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3340
3379
  );
@@ -3348,7 +3387,8 @@ function testMachine_graphNotEquivalentDifferentEdgeCount() {
3348
3387
  assert(!g1.isEquivalent(g2), 'Different edge counts should not be equivalent');
3349
3388
  }
3350
3389
 
3351
- function testMachine_graphNotEquivalentDifferentCap() {
3390
+ // TEST0138: Machine graph not equivalent different cap
3391
+ function test0138_Machine_graphNotEquivalentDifferentCap() {
3352
3392
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3353
3393
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3354
3394
  );
@@ -3361,19 +3401,22 @@ function testMachine_graphNotEquivalentDifferentCap() {
3361
3401
  assert(!g1.isEquivalent(g2), 'Different caps should not be equivalent');
3362
3402
  }
3363
3403
 
3364
- function testMachine_graphEmpty() {
3404
+ // TEST0139: Machine graph empty
3405
+ function test0139_Machine_graphEmpty() {
3365
3406
  const g = Machine.empty();
3366
3407
  assert(g.isEmpty(), 'Empty graph should be empty');
3367
3408
  assertEqual(g.edgeCount(), 0);
3368
3409
  }
3369
3410
 
3370
- function testMachine_graphEmptyEquivalence() {
3411
+ // TEST0140: Machine graph empty equivalence
3412
+ function test0140_Machine_graphEmptyEquivalence() {
3371
3413
  const g1 = Machine.empty();
3372
3414
  const g2 = Machine.empty();
3373
3415
  assert(g1.isEquivalent(g2), 'Two empty graphs should be equivalent');
3374
3416
  }
3375
3417
 
3376
- function testMachine_rootSourcesLinearChain() {
3418
+ // TEST0141: Machine root sources linear chain
3419
+ function test0141_Machine_rootSourcesLinearChain() {
3377
3420
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3378
3421
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3379
3422
  );
@@ -3387,7 +3430,8 @@ function testMachine_rootSourcesLinearChain() {
3387
3430
  'Root source should be media:pdf');
3388
3431
  }
3389
3432
 
3390
- function testMachine_leafTargetsLinearChain() {
3433
+ // TEST0142: Machine leaf targets linear chain
3434
+ function test0142_Machine_leafTargetsLinearChain() {
3391
3435
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3392
3436
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3393
3437
  );
@@ -3401,7 +3445,8 @@ function testMachine_leafTargetsLinearChain() {
3401
3445
  'Leaf target should be media:embedding-vector;record;textable');
3402
3446
  }
3403
3447
 
3404
- function testMachine_rootSourcesFanIn() {
3448
+ // TEST0143: Machine root sources fan in
3449
+ function test0143_Machine_rootSourcesFanIn() {
3405
3450
  const e = new MachineEdge(
3406
3451
  [MediaUrn.fromString('media:txt;textable'), MediaUrn.fromString('media:model-spec;textable')],
3407
3452
  CapUrn.fromString('cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"'),
@@ -3413,7 +3458,8 @@ function testMachine_rootSourcesFanIn() {
3413
3458
  assertEqual(roots.length, 2);
3414
3459
  }
3415
3460
 
3416
- function testMachine_displayEdge() {
3461
+ // TEST0144: Machine display edge
3462
+ function test0144_Machine_displayEdge() {
3417
3463
  const e = new MachineEdge(
3418
3464
  [MediaUrn.fromString('media:pdf')],
3419
3465
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3424,7 +3470,8 @@ function testMachine_displayEdge() {
3424
3470
  assert(display.includes('media:pdf'), 'Display should contain media:pdf');
3425
3471
  }
3426
3472
 
3427
- function testMachine_displayGraph() {
3473
+ // TEST0145: Machine display graph
3474
+ function test0145_Machine_displayGraph() {
3428
3475
  const e = new MachineEdge(
3429
3476
  [MediaUrn.fromString('media:pdf')],
3430
3477
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3437,7 +3484,7 @@ function testMachine_displayGraph() {
3437
3484
 
3438
3485
  // --- Machine serializer tests (mirrors serializer.rs tests) ---
3439
3486
 
3440
- function testMachine_serializeSingleEdge() {
3487
+ function test0146_Machine_serializeSingleEdge() {
3441
3488
  const g = new Machine([new MachineEdge(
3442
3489
  [MediaUrn.fromString('media:pdf')],
3443
3490
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3452,7 +3499,8 @@ function testMachine_serializeSingleEdge() {
3452
3499
  assert(notation.includes('-> n1]'), 'Should use n1 for target: ' + notation);
3453
3500
  }
3454
3501
 
3455
- function testMachine_serializeTwoEdgeChain() {
3502
+ // TEST0147: Machine serialize two edge chain
3503
+ function test0147_Machine_serializeTwoEdgeChain() {
3456
3504
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3457
3505
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3458
3506
  );
@@ -3465,11 +3513,13 @@ function testMachine_serializeTwoEdgeChain() {
3465
3513
  assertEqual(bracketCount, 4, 'Should have 4 brackets (2 headers + 2 wirings)');
3466
3514
  }
3467
3515
 
3468
- function testMachine_serializeEmptyGraph() {
3516
+ // TEST0148: Machine serialize empty graph
3517
+ function test0148_Machine_serializeEmptyGraph() {
3469
3518
  assertEqual(Machine.empty().toMachineNotation(), '');
3470
3519
  }
3471
3520
 
3472
- function testMachine_roundtripSingleEdge() {
3521
+ // TEST0149: Machine roundtrip single edge
3522
+ function test0149_Machine_roundtripSingleEdge() {
3473
3523
  const original = new Machine([new MachineEdge(
3474
3524
  [MediaUrn.fromString('media:pdf')],
3475
3525
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3482,7 +3532,8 @@ function testMachine_roundtripSingleEdge() {
3482
3532
  'Single edge round-trip failed: ' + notation);
3483
3533
  }
3484
3534
 
3485
- function testMachine_roundtripTwoEdgeChain() {
3535
+ // TEST0151: Machine roundtrip two edge chain
3536
+ function test0151_Machine_roundtripTwoEdgeChain() {
3486
3537
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3487
3538
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3488
3539
  );
@@ -3496,7 +3547,8 @@ function testMachine_roundtripTwoEdgeChain() {
3496
3547
  'Two-edge chain round-trip failed: ' + notation);
3497
3548
  }
3498
3549
 
3499
- function testMachine_roundtripFanOut() {
3550
+ // TEST0152: Machine roundtrip fan out
3551
+ function test0152_Machine_roundtripFanOut() {
3500
3552
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3501
3553
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3502
3554
  );
@@ -3511,7 +3563,8 @@ function testMachine_roundtripFanOut() {
3511
3563
  'Fan-out round-trip failed: ' + notation);
3512
3564
  }
3513
3565
 
3514
- function testMachine_roundtripLoopEdge() {
3566
+ // TEST0153: Machine roundtrip loop edge
3567
+ function test0153_Machine_roundtripLoopEdge() {
3515
3568
  const original = new Machine([new MachineEdge(
3516
3569
  [MediaUrn.fromString('media:disbound-page;textable')],
3517
3570
  CapUrn.fromString('cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"'),
@@ -3524,7 +3577,8 @@ function testMachine_roundtripLoopEdge() {
3524
3577
  assertEqual(reparsed.edges()[0].isLoop, true, 'isLoop must be preserved');
3525
3578
  }
3526
3579
 
3527
- function testMachine_serializationIsDeterministic() {
3580
+ // TEST0154: Machine serialization is deterministic
3581
+ function test0154_Machine_serializationIsDeterministic() {
3528
3582
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3529
3583
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3530
3584
  );
@@ -3537,7 +3591,8 @@ function testMachine_serializationIsDeterministic() {
3537
3591
  assertEqual(n1, n2, 'Serialization must be deterministic');
3538
3592
  }
3539
3593
 
3540
- function testMachine_reorderedEdgesProduceSameNotation() {
3594
+ // TEST0155: Machine reordered edges produce same notation
3595
+ function test0155_Machine_reorderedEdgesProduceSameNotation() {
3541
3596
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3542
3597
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3543
3598
  );
@@ -3553,7 +3608,8 @@ function testMachine_reorderedEdgesProduceSameNotation() {
3553
3608
  'Same graph with reordered edges must produce identical notation');
3554
3609
  }
3555
3610
 
3556
- function testMachine_multilineSerializeFormat() {
3611
+ // TEST0160: Machine multiline serialize format
3612
+ function test0160_Machine_multilineSerializeFormat() {
3557
3613
  const g = new Machine([new MachineEdge(
3558
3614
  [MediaUrn.fromString('media:pdf')],
3559
3615
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3569,7 +3625,7 @@ function testMachine_multilineSerializeFormat() {
3569
3625
 
3570
3626
  // Aliases are pure-index `edge_<N>` regardless of the cap's tags; there is
3571
3627
  // no privileged `op` tag to derive a friendlier name from.
3572
- function testMachine_aliasFromOpTag() {
3628
+ function test0161_Machine_aliasFromOpTag() {
3573
3629
  const g = new Machine([new MachineEdge(
3574
3630
  [MediaUrn.fromString('media:pdf')],
3575
3631
  CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"'),
@@ -3580,7 +3636,8 @@ function testMachine_aliasFromOpTag() {
3580
3636
  assert(notation.includes('[edge_0 '), 'Expected edge_0 alias, got: ' + notation);
3581
3637
  }
3582
3638
 
3583
- function testMachine_aliasFallbackWithoutOpTag() {
3639
+ // TEST0162: Machine alias fallback without op tag
3640
+ function test0162_Machine_aliasFallbackWithoutOpTag() {
3584
3641
  const g = new Machine([new MachineEdge(
3585
3642
  [MediaUrn.fromString('media:pdf')],
3586
3643
  CapUrn.fromString('cap:in="media:pdf";out="media:txt;textable"'),
@@ -3592,7 +3649,7 @@ function testMachine_aliasFallbackWithoutOpTag() {
3592
3649
  }
3593
3650
 
3594
3651
  // Pure-index aliases inherently disambiguate edges that share a marker tag.
3595
- function testMachine_duplicateOpTagsDisambiguated() {
3652
+ function test0163_Machine_duplicateOpTagsDisambiguated() {
3596
3653
  const g = new Machine([
3597
3654
  new MachineEdge(
3598
3655
  [MediaUrn.fromString('media:pdf')],
@@ -3614,7 +3671,7 @@ function testMachine_duplicateOpTagsDisambiguated() {
3614
3671
 
3615
3672
  // --- Machine builder tests ---
3616
3673
 
3617
- function testMachine_builderSingleEdge() {
3674
+ function test0164_Machine_builderSingleEdge() {
3618
3675
  const builder = new MachineBuilder();
3619
3676
  builder.addEdge(
3620
3677
  ['media:pdf'],
@@ -3626,7 +3683,8 @@ function testMachine_builderSingleEdge() {
3626
3683
  assertEqual(g.edges()[0].isLoop, false);
3627
3684
  }
3628
3685
 
3629
- function testMachine_builderWithLoop() {
3686
+ // TEST0165: Machine builder with loop
3687
+ function test0165_Machine_builderWithLoop() {
3630
3688
  const builder = new MachineBuilder();
3631
3689
  builder.addEdge(
3632
3690
  ['media:disbound-page;textable'],
@@ -3638,7 +3696,8 @@ function testMachine_builderWithLoop() {
3638
3696
  assertEqual(g.edges()[0].isLoop, true);
3639
3697
  }
3640
3698
 
3641
- function testMachine_builderChaining() {
3699
+ // TEST0166: Machine builder chaining
3700
+ function test0166_Machine_builderChaining() {
3642
3701
  const g = new MachineBuilder()
3643
3702
  .addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable')
3644
3703
  .addEdge(['media:txt;textable'], 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable')
@@ -3646,7 +3705,8 @@ function testMachine_builderChaining() {
3646
3705
  assertEqual(g.edgeCount(), 2);
3647
3706
  }
3648
3707
 
3649
- function testMachine_builderEquivalentToParsed() {
3708
+ // TEST0167: Machine builder equivalent to parsed
3709
+ function test0167_Machine_builderEquivalentToParsed() {
3650
3710
  const parsed = Machine.fromString(
3651
3711
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
3652
3712
  '[doc -> extract -> text]'
@@ -3658,7 +3718,8 @@ function testMachine_builderEquivalentToParsed() {
3658
3718
  'Builder-constructed graph should be equivalent to parsed graph');
3659
3719
  }
3660
3720
 
3661
- function testMachine_builderRoundTrip() {
3721
+ // TEST0168: Machine builder round trip
3722
+ function test0168_Machine_builderRoundTrip() {
3662
3723
  const built = new MachineBuilder()
3663
3724
  .addEdge(['media:pdf'], 'cap:in="media:pdf";extract;out="media:txt;textable"', 'media:txt;textable')
3664
3725
  .addEdge(['media:txt;textable'], 'cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"', 'media:embedding-vector;record;textable')
@@ -3670,7 +3731,7 @@ function testMachine_builderRoundTrip() {
3670
3731
 
3671
3732
  // --- CapUrn.isEquivalent/isComparable tests ---
3672
3733
 
3673
- function testMachine_capUrnIsEquivalent() {
3734
+ function test0169_Machine_capUrnIsEquivalent() {
3674
3735
  const a = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3675
3736
  const b = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3676
3737
  assert(a.isEquivalent(b), 'Same cap URNs should be equivalent');
@@ -3678,21 +3739,24 @@ function testMachine_capUrnIsEquivalent() {
3678
3739
  assert(!a.isEquivalent(c), 'Different cap URNs should not be equivalent');
3679
3740
  }
3680
3741
 
3681
- function testMachine_capUrnIsComparable() {
3742
+ // TEST0170: Machine cap urn is comparable
3743
+ function test0170_Machine_capUrnIsComparable() {
3682
3744
  const general = CapUrn.fromString('cap:in="media:pdf";out="media:txt;textable"');
3683
3745
  const specific = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3684
3746
  assert(general.isComparable(specific), 'General should be comparable to specific');
3685
3747
  assert(specific.isComparable(general), 'isComparable should be symmetric');
3686
3748
  }
3687
3749
 
3688
- function testMachine_capUrnInMediaUrn() {
3750
+ // TEST0171: Machine cap urn in media urn
3751
+ function test0171_Machine_capUrnInMediaUrn() {
3689
3752
  const cap = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3690
3753
  const inUrn = cap.inMediaUrn();
3691
3754
  assert(inUrn instanceof MediaUrn, 'inMediaUrn should return MediaUrn');
3692
3755
  assert(inUrn.isEquivalent(MediaUrn.fromString('media:pdf')), 'inMediaUrn should be media:pdf');
3693
3756
  }
3694
3757
 
3695
- function testMachine_capUrnOutMediaUrn() {
3758
+ // TEST0172: Machine cap urn out media urn
3759
+ function test0172_Machine_capUrnOutMediaUrn() {
3696
3760
  const cap = CapUrn.fromString('cap:in="media:pdf";extract;out="media:txt;textable"');
3697
3761
  const outUrn = cap.outMediaUrn();
3698
3762
  assert(outUrn instanceof MediaUrn, 'outMediaUrn should return MediaUrn');
@@ -3701,7 +3765,7 @@ function testMachine_capUrnOutMediaUrn() {
3701
3765
 
3702
3766
  // --- MediaUrn.isEquivalent/isComparable tests ---
3703
3767
 
3704
- function testMachine_mediaUrnIsEquivalent() {
3768
+ function test0173_Machine_mediaUrnIsEquivalent() {
3705
3769
  const a = MediaUrn.fromString('media:pdf');
3706
3770
  const b = MediaUrn.fromString('media:pdf');
3707
3771
  assert(a.isEquivalent(b), 'Same media URNs should be equivalent');
@@ -3709,7 +3773,8 @@ function testMachine_mediaUrnIsEquivalent() {
3709
3773
  assert(!a.isEquivalent(c), 'Different media URNs should not be equivalent');
3710
3774
  }
3711
3775
 
3712
- function testMachine_mediaUrnIsComparable() {
3776
+ // TEST0174: Machine media urn is comparable
3777
+ function test0174_Machine_mediaUrnIsComparable() {
3713
3778
  const general = MediaUrn.fromString('media:textable');
3714
3779
  const specific = MediaUrn.fromString('media:txt;textable');
3715
3780
  assert(general.isComparable(specific), 'General should be comparable to specific');
@@ -3722,7 +3787,7 @@ function testMachine_mediaUrnIsComparable() {
3722
3787
  // Phase 0A: Position tracking tests
3723
3788
  // ============================================================================
3724
3789
 
3725
- function testMachine_parseMachineWithAST_headerLocation() {
3790
+ function test0175_Machine_parseMachineWithAST_headerLocation() {
3726
3791
  const input = '[extract cap:in="media:pdf";extract;out="media:txt;textable"][doc -> extract -> text]';
3727
3792
  const result = parseMachineWithAST(input);
3728
3793
  assert(result.statements.length === 2, 'Should have 2 statements');
@@ -3738,7 +3803,8 @@ function testMachine_parseMachineWithAST_headerLocation() {
3738
3803
  assertEqual(stmt.alias, 'extract', 'Alias should be extract');
3739
3804
  }
3740
3805
 
3741
- function testMachine_parseMachineWithAST_wiringLocation() {
3806
+ // TEST0176: Machine parse machine with a s t wiring location
3807
+ function test0176_Machine_parseMachineWithAST_wiringLocation() {
3742
3808
  const input = '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n[doc -> extract -> text]';
3743
3809
  const result = parseMachineWithAST(input);
3744
3810
  assert(result.statements.length === 2, 'Should have 2 statements');
@@ -3752,7 +3818,8 @@ function testMachine_parseMachineWithAST_wiringLocation() {
3752
3818
  assertEqual(wiring.target, 'text', 'Target should be text');
3753
3819
  }
3754
3820
 
3755
- function testMachine_parseMachineWithAST_multilinePositions() {
3821
+ // TEST0177: Machine parse machine with a s t multiline positions
3822
+ function test0177_Machine_parseMachineWithAST_multilinePositions() {
3756
3823
  const input = '[extract cap:in="media:pdf";extract;out="media:txt;textable"]\n[doc -> extract -> text]';
3757
3824
  const result = parseMachineWithAST(input);
3758
3825
  const headerLoc = result.statements[0].location;
@@ -3761,7 +3828,8 @@ function testMachine_parseMachineWithAST_multilinePositions() {
3761
3828
  assertEqual(wiringLoc.start.line, 2, 'Wiring should be on line 2');
3762
3829
  }
3763
3830
 
3764
- function testMachine_parseMachineWithAST_fanInSourceLocations() {
3831
+ // TEST0178: Machine parse machine with a s t fan in source locations
3832
+ function test0178_Machine_parseMachineWithAST_fanInSourceLocations() {
3765
3833
  const input = [
3766
3834
  '[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]',
3767
3835
  '[(thumbnail, model_spec) -> describe -> description]'
@@ -3772,7 +3840,8 @@ function testMachine_parseMachineWithAST_fanInSourceLocations() {
3772
3840
  assert(wiring.sourceLocations.length === 2, 'Should have 2 source locations');
3773
3841
  }
3774
3842
 
3775
- function testMachine_parseMachineWithAST_aliasMap() {
3843
+ // TEST0179: Machine parse machine with a s t alias map
3844
+ function test0179_Machine_parseMachineWithAST_aliasMap() {
3776
3845
  const input = [
3777
3846
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]',
3778
3847
  '[embed cap:in="media:txt;textable";embed;out="media:embedding-vector;record;textable"]',
@@ -3790,7 +3859,8 @@ function testMachine_parseMachineWithAST_aliasMap() {
3790
3859
  assert(extractEntry.capUrnLocation !== undefined, 'Alias entry should have capUrnLocation');
3791
3860
  }
3792
3861
 
3793
- function testMachine_parseMachineWithAST_nodeMedia() {
3862
+ // TEST0180: Machine parse machine with a s t node media
3863
+ function test0180_Machine_parseMachineWithAST_nodeMedia() {
3794
3864
  const input = [
3795
3865
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]',
3796
3866
  '[doc -> extract -> text]',
@@ -3802,7 +3872,8 @@ function testMachine_parseMachineWithAST_nodeMedia() {
3802
3872
  assertEqual(result.nodeMedia.get('text').toString(), 'media:textable;txt', 'text should be media:textable;txt');
3803
3873
  }
3804
3874
 
3805
- function testMachine_errorLocation_parseError() {
3875
+ // TEST0181: Machine error location parse error
3876
+ function test0181_Machine_errorLocation_parseError() {
3806
3877
  try {
3807
3878
  parseMachine('[this is not valid');
3808
3879
  throw new Error('Expected MachineSyntaxError');
@@ -3812,7 +3883,8 @@ function testMachine_errorLocation_parseError() {
3812
3883
  }
3813
3884
  }
3814
3885
 
3815
- function testMachine_errorLocation_duplicateAlias() {
3886
+ // TEST0182: Machine error location duplicate alias
3887
+ function test0182_Machine_errorLocation_duplicateAlias() {
3816
3888
  try {
3817
3889
  parseMachine(
3818
3890
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
@@ -3826,7 +3898,8 @@ function testMachine_errorLocation_duplicateAlias() {
3826
3898
  }
3827
3899
  }
3828
3900
 
3829
- function testMachine_errorLocation_undefinedAlias() {
3901
+ // TEST0183: Machine error location undefined alias
3902
+ function test0183_Machine_errorLocation_undefinedAlias() {
3830
3903
  try {
3831
3904
  parseMachine('[doc -> nonexistent -> text]');
3832
3905
  throw new Error('Expected MachineSyntaxError');
@@ -3840,7 +3913,7 @@ function testMachine_errorLocation_undefinedAlias() {
3840
3913
  // Phase 0C: Machine.toMermaid() tests
3841
3914
  // ============================================================================
3842
3915
 
3843
- function testMachine_toMermaid_linearChain() {
3916
+ function test0184_Machine_toMermaid_linearChain() {
3844
3917
  const machine = Machine.fromString(
3845
3918
  '[extract cap:in="media:pdf";extract;out="media:txt;textable"]' +
3846
3919
  '[doc -> extract -> text]'
@@ -3858,7 +3931,8 @@ function testMachine_toMermaid_linearChain() {
3858
3931
  assert(mermaid.includes('(['), 'Should have stadium shape nodes');
3859
3932
  }
3860
3933
 
3861
- function testMachine_toMermaid_loopEdge() {
3934
+ // TEST0185: Machine to mermaid loop edge
3935
+ function test0185_Machine_toMermaid_loopEdge() {
3862
3936
  const machine = Machine.fromString(
3863
3937
  '[p2t cap:in="media:disbound-page;textable";page-to-text;out="media:txt;textable"]' +
3864
3938
  '[pages -> LOOP p2t -> texts]'
@@ -3869,13 +3943,15 @@ function testMachine_toMermaid_loopEdge() {
3869
3943
  assert(mermaid.includes('.->'), 'Should use dotted arrow for LOOP');
3870
3944
  }
3871
3945
 
3872
- function testMachine_toMermaid_emptyGraph() {
3946
+ // TEST0186: Machine to mermaid empty graph
3947
+ function test0186_Machine_toMermaid_emptyGraph() {
3873
3948
  const machine = Machine.empty();
3874
3949
  const mermaid = machine.toMermaid();
3875
3950
  assert(mermaid.includes('empty graph'), 'Should indicate empty graph');
3876
3951
  }
3877
3952
 
3878
- function testMachine_toMermaid_fanIn() {
3953
+ // TEST0187: Machine to mermaid fan in
3954
+ function test0187_Machine_toMermaid_fanIn() {
3879
3955
  const machine = Machine.fromString(
3880
3956
  '[describe cap:in="media:image;png";describe-image;out="media:image-description;textable"]' +
3881
3957
  '[(thumbnail, model_spec) -> describe -> description]'
@@ -3886,7 +3962,8 @@ function testMachine_toMermaid_fanIn() {
3886
3962
  assertEqual(arrowCount, 2, 'Fan-in should produce 2 arrows');
3887
3963
  }
3888
3964
 
3889
- function testMachine_toMermaid_fanOut() {
3965
+ // TEST0188: Machine to mermaid fan out
3966
+ function test0188_Machine_toMermaid_fanOut() {
3890
3967
  const input = [
3891
3968
  '[meta cap:in="media:pdf";extract-metadata;out="media:file-metadata;record;textable"]',
3892
3969
  '[thumb cap:in="media:pdf";generate-thumbnail;out="media:image;png;thumbnail"]',
@@ -3906,7 +3983,7 @@ function testMachine_toMermaid_fanOut() {
3906
3983
  // Phase 0B: FabricRegistryClient tests
3907
3984
  // ============================================================================
3908
3985
 
3909
- function testMachine_capRegistryEntry_construction() {
3986
+ function test0189_Machine_capRegistryEntry_construction() {
3910
3987
  const entry = new FabricRegistryEntry({
3911
3988
  urn: 'cap:in="media:pdf";extract;out="media:txt;textable"',
3912
3989
  title: 'PDF Extractor',
@@ -3929,7 +4006,8 @@ function testMachine_capRegistryEntry_construction() {
3929
4006
  assertEqual(entry.urnTags.op, 'extract', 'op tag should match');
3930
4007
  }
3931
4008
 
3932
- function testMachine_mediaRegistryEntry_construction() {
4009
+ // TEST0190: Machine media registry entry construction
4010
+ function test0190_Machine_mediaRegistryEntry_construction() {
3933
4011
  const entry = new MediaRegistryEntry({
3934
4012
  urn: 'media:pdf',
3935
4013
  title: 'PDF Document',
@@ -3942,14 +4020,16 @@ function testMachine_mediaRegistryEntry_construction() {
3942
4020
  assertEqual(entry.description, 'Portable Document Format', 'Description should match');
3943
4021
  }
3944
4022
 
3945
- function testMachine_capRegistryClient_construction() {
4023
+ // TEST0191: Machine cap registry client construction
4024
+ function test0191_Machine_capRegistryClient_construction() {
3946
4025
  const client = new FabricRegistryClient('https://example.com', 600);
3947
4026
  assert(client !== null, 'Client should be constructed');
3948
4027
  // Invalidate should not throw
3949
4028
  client.invalidate();
3950
4029
  }
3951
4030
 
3952
- function testMachine_capRegistryEntry_defaults() {
4031
+ // TEST0192: Machine cap registry entry defaults
4032
+ function test0192_Machine_capRegistryEntry_defaults() {
3953
4033
  // Verify that missing fields default gracefully
3954
4034
  const entry = new FabricRegistryEntry({ urn: 'cap:in=media:;test;out=media:' });
3955
4035
  assertEqual(entry.urn, 'cap:in=media:;test;out=media:', 'URN should match');
@@ -4014,14 +4094,16 @@ if (typeof global.createCap === 'undefined') {
4014
4094
  global.createCap = require('./capdag.js').createCap;
4015
4095
  }
4016
4096
 
4017
- function testRenderer_cardinalityLabel_allFourCases() {
4097
+ // TEST0193: Renderer cardinality label all four cases
4098
+ function test0193_Renderer_cardinalityLabel_allFourCases() {
4018
4099
  assertEqual(rendererCardinalityLabel(false, false), '1\u21921', 'scalar-to-scalar');
4019
4100
  assertEqual(rendererCardinalityLabel(true, false), 'n\u21921', 'sequence-to-scalar');
4020
4101
  assertEqual(rendererCardinalityLabel(false, true), '1\u2192n', 'scalar-to-sequence');
4021
4102
  assertEqual(rendererCardinalityLabel(true, true), 'n\u2192n', 'sequence-to-sequence');
4022
4103
  }
4023
4104
 
4024
- function testRenderer_cardinalityLabel_usesUnicodeArrow() {
4105
+ // TEST0194: Renderer cardinality label uses unicode arrow
4106
+ function test0194_Renderer_cardinalityLabel_usesUnicodeArrow() {
4025
4107
  // The label must use the real rightwards arrow (U+2192), not ASCII "->".
4026
4108
  // Downstream styling and tests depend on this glyph.
4027
4109
  const label = rendererCardinalityLabel(false, true);
@@ -4029,7 +4111,8 @@ function testRenderer_cardinalityLabel_usesUnicodeArrow() {
4029
4111
  assert(!label.includes('->'), 'label must not contain the ASCII replacement "->"');
4030
4112
  }
4031
4113
 
4032
- function testRenderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
4114
+ // TEST0195: Renderer cardinality from cap finds stdin arg not first arg
4115
+ function test0195_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
4033
4116
  // The main input arg is the one whose sources include a stdin source.
4034
4117
  // A naive implementation that reads args[0] would see `cli-only` (not a
4035
4118
  // sequence) and report 1→1 even though the stdin arg is a sequence.
@@ -4053,7 +4136,8 @@ function testRenderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
4053
4136
  'must pick the arg that has a stdin source, not args[0]');
4054
4137
  }
4055
4138
 
4056
- function testRenderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
4139
+ // TEST0196: Renderer cardinality from cap scalar defaults when fields missing
4140
+ function test0196_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
4057
4141
  // No args and no output: both sides collapse to 1 (scalar default).
4058
4142
  // If a bug makes the function return "n" for missing data, this fails.
4059
4143
  const cap = { urn: 'cap:in="media:";noop;out="media:"' };
@@ -4061,7 +4145,8 @@ function testRenderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
4061
4145
  'missing args/output must default to scalar on both sides');
4062
4146
  }
4063
4147
 
4064
- function testRenderer_cardinalityFromCap_outputOnlySequence() {
4148
+ // TEST0197: Renderer cardinality from cap output only sequence
4149
+ function test0197_Renderer_cardinalityFromCap_outputOnlySequence() {
4065
4150
  // One scalar stdin arg, output is a sequence: expects 1→n.
4066
4151
  const cap = {
4067
4152
  urn: 'cap:in="media:textable";generate;out="media:textable;list"',
@@ -4072,7 +4157,8 @@ function testRenderer_cardinalityFromCap_outputOnlySequence() {
4072
4157
  'scalar stdin with sequence output must yield 1→n');
4073
4158
  }
4074
4159
 
4075
- function testRenderer_cardinalityFromCap_rejectsStringIsSequence() {
4160
+ // TEST0198: Renderer cardinality from cap rejects string is sequence
4161
+ function test0198_Renderer_cardinalityFromCap_rejectsStringIsSequence() {
4076
4162
  // The function must use strict `=== true` to avoid treating truthy strings
4077
4163
  // as booleans. "true" is a string, not a boolean — it must NOT be treated
4078
4164
  // as a sequence, because downstream renderers expect boolean semantics.
@@ -4085,7 +4171,8 @@ function testRenderer_cardinalityFromCap_rejectsStringIsSequence() {
4085
4171
  'string "true" must not be treated as boolean true');
4086
4172
  }
4087
4173
 
4088
- function testRenderer_cardinalityFromCap_throwsOnNonObject() {
4174
+ // TEST0199: Renderer cardinality from cap throws on non object
4175
+ function test0199_Renderer_cardinalityFromCap_throwsOnNonObject() {
4089
4176
  // Fail-hard on invalid input; no fallback to a default cardinality.
4090
4177
  let threw = false;
4091
4178
  try {
@@ -4104,7 +4191,8 @@ function testRenderer_cardinalityFromCap_throwsOnNonObject() {
4104
4191
  assert(threw, 'cardinalityFromCap(string) must throw');
4105
4192
  }
4106
4193
 
4107
- function testRenderer_canonicalMediaUrn_normalizesTagOrder() {
4194
+ // TEST0200: Renderer canonical media urn normalizes tag order
4195
+ function test0200_Renderer_canonicalMediaUrn_normalizesTagOrder() {
4108
4196
  // Two media URNs with identical tags in different input orders must
4109
4197
  // produce the same canonical string. If canonicalization is bypassed,
4110
4198
  // the two strings remain different and this test exposes it.
@@ -4113,12 +4201,14 @@ function testRenderer_canonicalMediaUrn_normalizesTagOrder() {
4113
4201
  assertEqual(a, b, 'tag-order differences must not survive canonicalization');
4114
4202
  }
4115
4203
 
4116
- function testRenderer_canonicalMediaUrn_preservesValueTags() {
4204
+ // TEST0201: Renderer canonical media urn preserves value tags
4205
+ function test0201_Renderer_canonicalMediaUrn_preservesValueTags() {
4117
4206
  const c = rendererCanonicalMediaUrn('media:model;quant=q4');
4118
4207
  assert(c.includes('quant=q4'), 'value tag must be preserved');
4119
4208
  }
4120
4209
 
4121
- function testRenderer_canonicalMediaUrn_rejectsCapUrn() {
4210
+ // TEST0202: Renderer canonical media urn rejects cap urn
4211
+ function test0202_Renderer_canonicalMediaUrn_rejectsCapUrn() {
4122
4212
  // MediaUrn.fromString enforces the media: prefix. Feeding a cap URN to
4123
4213
  // canonicalMediaUrn must fail hard.
4124
4214
  let threw = false;
@@ -4130,7 +4220,8 @@ function testRenderer_canonicalMediaUrn_rejectsCapUrn() {
4130
4220
  assert(threw, 'canonicalMediaUrn must reject non-media URNs');
4131
4221
  }
4132
4222
 
4133
- function testRenderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
4223
+ // TEST0203: Renderer media node label rejects urn derived labels
4224
+ function test0203_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
4134
4225
  let threw = false;
4135
4226
  let message = '';
4136
4227
  try {
@@ -4144,7 +4235,8 @@ function testRenderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
4144
4235
  'error must explain that explicit titles are required');
4145
4236
  }
4146
4237
 
4147
- function testRenderer_buildBrowseGraphData_rejectsMissingMediaTitles() {
4238
+ // TEST0204: Renderer build browse graph data rejects missing media titles
4239
+ function test0204_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles() {
4148
4240
  let threw = false;
4149
4241
  let message = '';
4150
4242
  try {
@@ -4201,7 +4293,8 @@ function makeCollectStep(mediaDef) {
4201
4293
  };
4202
4294
  }
4203
4295
 
4204
- function testRenderer_validateStrandStep_rejectsUnknownVariant() {
4296
+ // TEST0205: Renderer validate strand step rejects unknown variant
4297
+ function test0205_Renderer_validateStrandStep_rejectsUnknownVariant() {
4205
4298
  // A step with an unknown variant must fail hard at validation; no
4206
4299
  // silent coercion.
4207
4300
  let threw = false;
@@ -4218,7 +4311,8 @@ function testRenderer_validateStrandStep_rejectsUnknownVariant() {
4218
4311
  assert(threw, 'unknown variant must throw');
4219
4312
  }
4220
4313
 
4221
- function testRenderer_validateStrandStep_requiresBooleanIsSequence() {
4314
+ // TEST0206: Renderer validate strand step requires boolean is sequence
4315
+ function test0206_Renderer_validateStrandStep_requiresBooleanIsSequence() {
4222
4316
  // A Cap variant must have boolean is_sequence fields; number or string
4223
4317
  // must reject.
4224
4318
  let threw = false;
@@ -4240,7 +4334,8 @@ function testRenderer_validateStrandStep_requiresBooleanIsSequence() {
4240
4334
  assert(threw, 'non-boolean is_sequence must throw');
4241
4335
  }
4242
4336
 
4243
- function testRenderer_classifyStrandCapSteps_capFlags() {
4337
+ // TEST0207: Renderer classify strand cap steps cap flags
4338
+ function test0207_Renderer_classifyStrandCapSteps_capFlags() {
4244
4339
  // Strand: ForEach → cap1 → cap2 → cap3 → Collect. cap1 should have
4245
4340
  // prevForEach=true; cap3 should have nextCollect=true; cap2 should
4246
4341
  // have neither.
@@ -4261,7 +4356,8 @@ function testRenderer_classifyStrandCapSteps_capFlags() {
4261
4356
  assert(capFlags.get(3).nextCollect, 'cap3 has nextCollect');
4262
4357
  }
4263
4358
 
4264
- function testRenderer_classifyStrandCapSteps_nestedForks() {
4359
+ // TEST0208: Renderer classify strand cap steps nested forks
4360
+ function test0208_Renderer_classifyStrandCapSteps_nestedForks() {
4265
4361
  // Nested strand: ForEach → cap1 → ForEach → cap2 → Collect → cap3 → Collect.
4266
4362
  // cap1 has prevForEach (outer), cap2 has prevForEach (inner) and
4267
4363
  // nextCollect (inner), cap3 has nextCollect (outer).
@@ -4291,7 +4387,8 @@ function withMediaDisplayNames(payload, mediaDisplayNames) {
4291
4387
  });
4292
4388
  }
4293
4389
 
4294
- function testRenderer_buildStrandGraphData_singleCapPlain() {
4390
+ // TEST0209: Renderer build strand graph data single cap plain
4391
+ function test0209_Renderer_buildStrandGraphData_singleCapPlain() {
4295
4392
  // Minimal strand with one plain 1→1 cap. Plan builder produces:
4296
4393
  // input_slot → step_0 (cap) → output
4297
4394
  // (two edges, three nodes). No cardinality marker in the cap label
@@ -4318,7 +4415,8 @@ function testRenderer_buildStrandGraphData_singleCapPlain() {
4318
4415
  assert(outEdge !== undefined, 'output edge from step_0 to output exists');
4319
4416
  }
4320
4417
 
4321
- function testRenderer_buildStrandGraphData_sequenceShowsCardinality() {
4418
+ // TEST0210: Renderer build strand graph data sequence shows cardinality
4419
+ function test0210_Renderer_buildStrandGraphData_sequenceShowsCardinality() {
4322
4420
  // A cap with input_is_sequence=true MUST emit "(n→1)" on its edge
4323
4421
  // label.
4324
4422
  const payload = withMediaDisplayNames({
@@ -4338,7 +4436,8 @@ function testRenderer_buildStrandGraphData_sequenceShowsCardinality() {
4338
4436
  `cap edge label must include (n\u21921) marker; got: ${capEdge.label}`);
4339
4437
  }
4340
4438
 
4341
- function testRenderer_buildStrandGraphData_foreachCollectSpan() {
4439
+ // TEST0211: Renderer build strand graph data foreach collect span
4440
+ function test0211_Renderer_buildStrandGraphData_foreachCollectSpan() {
4342
4441
  // Strand: [ForEach, Cap, Collect]. Plan builder produces:
4343
4442
  // input_slot (source) →direct→ step_1 (cap) — cap emits its own
4344
4443
  // direct edge from prev
@@ -4389,7 +4488,8 @@ function testRenderer_buildStrandGraphData_foreachCollectSpan() {
4389
4488
  assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
4390
4489
  }
4391
4490
 
4392
- function testRenderer_buildStrandGraphData_standaloneCollect() {
4491
+ // TEST0212: Renderer build strand graph data standalone collect
4492
+ function test0212_Renderer_buildStrandGraphData_standaloneCollect() {
4393
4493
  // Strand with a standalone Collect (no enclosing ForEach). Plan
4394
4494
  // builder creates a Collect node consuming prev directly — plain
4395
4495
  // direct edge, no iteration/collection semantics.
@@ -4416,7 +4516,8 @@ function testRenderer_buildStrandGraphData_standaloneCollect() {
4416
4516
  assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
4417
4517
  }
4418
4518
 
4419
- function testRenderer_buildStrandGraphData_unclosedForEachBody() {
4519
+ // TEST0213: Renderer build strand graph data unclosed for each body
4520
+ function test0213_Renderer_buildStrandGraphData_unclosedForEachBody() {
4420
4521
  // Strand: [Cap_a, ForEach, Cap_b] with no closing Collect. The plan
4421
4522
  // builder's "unclosed ForEach" branch creates a ForEach node
4422
4523
  // connecting Cap_a to Cap_b via iteration, with prev becoming the
@@ -4453,7 +4554,8 @@ function testRenderer_buildStrandGraphData_unclosedForEachBody() {
4453
4554
  'output edge step_2 → output');
4454
4555
  }
4455
4556
 
4456
- function testRenderer_buildStrandGraphData_nestedForEachThrows() {
4557
+ // TEST0214: Renderer build strand graph data nested for each throws
4558
+ function test0214_Renderer_buildStrandGraphData_nestedForEachThrows() {
4457
4559
  // Nested ForEach without an intervening body cap in the outer
4458
4560
  // ForEach is an illegal nesting per plan_builder. The renderer
4459
4561
  // must throw the same error to surface the issue rather than
@@ -4481,7 +4583,8 @@ function testRenderer_buildStrandGraphData_nestedForEachThrows() {
4481
4583
  assert(threw, 'nested ForEach without outer body cap must throw');
4482
4584
  }
4483
4585
 
4484
- function testRenderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
4586
+ // TEST0215: Renderer collapse strand single cap body keeps cap own label
4587
+ function test0215_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
4485
4588
  // User spec: ForEach/Collect are NOT rendered as nodes, and
4486
4589
  // the collapse does NOT relabel cap edges. Each cap edge
4487
4590
  // carries whatever label the strand builder emitted — the
@@ -4533,7 +4636,8 @@ function testRenderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
4533
4636
  'collect bridge is unlabeled');
4534
4637
  }
4535
4638
 
4536
- function testRenderer_collapseStrand_unclosedForEachBodyCollapses() {
4639
+ // TEST0216: Renderer collapse strand unclosed for each body collapses
4640
+ function test0216_Renderer_collapseStrand_unclosedForEachBodyCollapses() {
4537
4641
  // [Cap_a(1→1), ForEach, Cap_b(1→1)] with no Collect,
4538
4642
  // source=media:a, target=media:c. Cap_b's to_spec is media:c
4539
4643
  // which is equivalent to target_media_urn, so the output node is
@@ -4589,7 +4693,8 @@ function testRenderer_collapseStrand_unclosedForEachBodyCollapses() {
4589
4693
  'merged step_2 takes on the strand-target role');
4590
4694
  }
4591
4695
 
4592
- function testRenderer_collapseStrand_standaloneCollectCollapses() {
4696
+ // TEST0217: Renderer collapse strand standalone collect collapses
4697
+ function test0217_Renderer_collapseStrand_standaloneCollectCollapses() {
4593
4698
  // [Cap, Collect] with no enclosing ForEach, source=media:a,
4594
4699
  // target=media:b;list (NOT equivalent to cap's to_spec media:b,
4595
4700
  // so the output node is retained after collapse).
@@ -4632,7 +4737,8 @@ function testRenderer_collapseStrand_standaloneCollectCollapses() {
4632
4737
  'the synthesized bridging edge for a standalone Collect is an unlabeled connector (cap labels carry all cardinality info)');
4633
4738
  }
4634
4739
 
4635
- function testRenderer_collapseStrand_sequenceProducingCapBeforeForeach() {
4740
+ // TEST0218: Renderer collapse strand sequence producing cap before foreach
4741
+ function test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach() {
4636
4742
  // Regression test mirroring the user's real strand:
4637
4743
  // [Cap_disbind (output_is_sequence=true), ForEach, Cap_make_decision],
4638
4744
  // source = media:pdf, target = media:decision (equivalent to
@@ -4696,7 +4802,8 @@ function testRenderer_collapseStrand_sequenceProducingCapBeforeForeach() {
4696
4802
  'output node merged into step_2 because they represent the same URN');
4697
4803
  }
4698
4804
 
4699
- function testRenderer_collapseStrand_plainCapMergesTrailingOutput() {
4805
+ // TEST0219: Renderer collapse strand plain cap merges trailing output
4806
+ function test0219_Renderer_collapseStrand_plainCapMergesTrailingOutput() {
4700
4807
  // A strand with a single plain 1→1 cap whose to_spec equals
4701
4808
  // target_media_urn. The plan-builder topology produces:
4702
4809
  // input_slot → step_0 (cap) → output
@@ -4732,7 +4839,8 @@ function testRenderer_collapseStrand_plainCapMergesTrailingOutput() {
4732
4839
  assertEqual(collapsed.edges[0].label, 'x', 'cap title preserved as edge label');
4733
4840
  }
4734
4841
 
4735
- function testRenderer_collapseStrand_plainCapDistinctTargetNoMerge() {
4842
+ // TEST0220: Renderer collapse strand plain cap distinct target no merge
4843
+ function test0220_Renderer_collapseStrand_plainCapDistinctTargetNoMerge() {
4736
4844
  // A strand with a single plain cap whose to_spec is NOT
4737
4845
  // equivalent to target_media_urn. The output node must be retained
4738
4846
  // and the trailing connector edge preserved.
@@ -4758,7 +4866,8 @@ function testRenderer_collapseStrand_plainCapDistinctTargetNoMerge() {
4758
4866
  'step_0 retained');
4759
4867
  }
4760
4868
 
4761
- function testRenderer_validateStrandPayload_missingSourceMediaUrn() {
4869
+ // TEST0221: Renderer validate strand payload missing source media urn
4870
+ function test0221_Renderer_validateStrandPayload_missingSourceMediaUrn() {
4762
4871
  let threw = false;
4763
4872
  try {
4764
4873
  rendererValidateStrandPayload({ target_media_urn: 'media:b', steps: [] });
@@ -4771,7 +4880,7 @@ function testRenderer_validateStrandPayload_missingSourceMediaUrn() {
4771
4880
 
4772
4881
  // ---------------- run builder ----------------
4773
4882
 
4774
- function testRenderer_validateBodyOutcome_rejectsNegativeIndex() {
4883
+ function test0222_Renderer_validateBodyOutcome_rejectsNegativeIndex() {
4775
4884
  let threw = false;
4776
4885
  try {
4777
4886
  rendererValidateBodyOutcome({ body_index: -1, success: true, cap_urns: [] }, 'test');
@@ -4781,7 +4890,8 @@ function testRenderer_validateBodyOutcome_rejectsNegativeIndex() {
4781
4890
  assert(threw, 'negative body_index must throw');
4782
4891
  }
4783
4892
 
4784
- function testRenderer_buildRunGraphData_pagesSuccessesAndFailures() {
4893
+ // TEST0223: Renderer build run graph data pages successes and failures
4894
+ function test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
4785
4895
  // 6 successes, 4 failures. visible=3+2, total=10. Body has 2
4786
4896
  // caps (a, b). Each body replica is a chain of:
4787
4897
  // entry node + body_step_0 (cap a) + body_step_1 (cap b)
@@ -4851,7 +4961,8 @@ function testRenderer_buildRunGraphData_pagesSuccessesAndFailures() {
4851
4961
  assertEqual(failureShowMore.data.hiddenCount, 2, 'failure hidden count = 2');
4852
4962
  }
4853
4963
 
4854
- function testRenderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace() {
4964
+ // TEST0224: Renderer build run graph data failure without failed cap renders full trace
4965
+ function test0224_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace() {
4855
4966
  // A failure without failed_cap (infrastructure failure) must
4856
4967
  // still render the full body trace — the builder must not
4857
4968
  // crash or produce zero replicas.
@@ -4889,7 +5000,8 @@ function testRenderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace(
4889
5000
  assertEqual(failureNodes, 2, 'entry + body cap = 2 failure replica nodes');
4890
5001
  }
4891
5002
 
4892
- function testRenderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
5003
+ // TEST0225: Renderer build run graph data uses cap urn is equivalent for failed cap
5004
+ function test0225_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
4893
5005
  // The renderer matches failed_cap against step cap URNs via
4894
5006
  // CapUrn.isEquivalent, NOT string equality. Feed a payload where
4895
5007
  // failed_cap and the step's cap_urn differ only in tag order — they
@@ -4948,7 +5060,8 @@ function testRenderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
4948
5060
  assertEqual(failureNodes, 3, 'trace truncates at cap y via isEquivalent, yielding entry + 2 cap nodes');
4949
5061
  }
4950
5062
 
4951
- function testRenderer_buildRunGraphData_backboneHasNoForeachNode() {
5063
+ // TEST0226: Renderer build run graph data backbone has no foreach node
5064
+ function test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode() {
4952
5065
  // Regression test for the run-mode rendering fix: the backbone
4953
5066
  // delivered to cytoscape must NOT contain any strand-foreach or
4954
5067
  // strand-collect nodes. Run mode inherits the same cosmetic
@@ -5002,7 +5115,8 @@ function testRenderer_buildRunGraphData_backboneHasNoForeachNode() {
5002
5115
  assertEqual(built.showMoreNodes.length, 0, 'no show-more nodes when no hidden outcomes');
5003
5116
  }
5004
5117
 
5005
- function testRenderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
5118
+ // TEST0227: Renderer build run graph data all failed drops target placeholder
5119
+ function test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
5006
5120
  // When every body fails, the strand target node was never
5007
5121
  // reached by any execution. The render drops BOTH the backbone
5008
5122
  // foreach-entry edge AND the orphaned target node so the user
@@ -5067,7 +5181,8 @@ function testRenderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
5067
5181
  assertEqual(hasInputSlot, true, 'input_slot survives as the shared source');
5068
5182
  }
5069
5183
 
5070
- function testRenderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
5184
+ // TEST0228: Renderer build run graph data unclosed foreach success no merge
5185
+ function test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
5071
5186
  // Strand without a Collect: [Disbind, ForEach, make_decision].
5072
5187
  // Under the machfab realize_strand semantics there's no Collect,
5073
5188
  // so each body produces its OWN terminal output. Successful
@@ -5128,7 +5243,8 @@ function testRenderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
5128
5243
  assertEqual(forkEdges.length, 1, 'fork edge input_slot → body-0-entry exists');
5129
5244
  }
5130
5245
 
5131
- function testRenderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget() {
5246
+ // TEST0229: Renderer build run graph data closed foreach success merges at collect target
5247
+ function test0229_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget() {
5132
5248
  // With a Collect closing the body, successful replicas DO merge
5133
5249
  // into the post-collect target so the flow converges.
5134
5250
  // Strand: [Disbind, ForEach, Cap_a, Cap_b, Collect] with a
@@ -5183,7 +5299,7 @@ function testRenderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarge
5183
5299
 
5184
5300
  // ---------------- editor-graph builder ----------------
5185
5301
 
5186
- function testRenderer_validateEditorGraphPayload_rejectsUnknownKind() {
5302
+ function test0230_Renderer_validateEditorGraphPayload_rejectsUnknownKind() {
5187
5303
  let threw = false;
5188
5304
  try {
5189
5305
  rendererValidateEditorGraphPayload({
@@ -5197,7 +5313,8 @@ function testRenderer_validateEditorGraphPayload_rejectsUnknownKind() {
5197
5313
  assert(threw, 'unknown element kind must throw');
5198
5314
  }
5199
5315
 
5200
- function testRenderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges() {
5316
+ // TEST0231: Renderer build editor graph data collapses caps into labeled edges
5317
+ function test0231_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges() {
5201
5318
  // The notation analyzer emits a bipartite chain per cap
5202
5319
  // application: data_node → arg_edge → cap_node → arg_edge →
5203
5320
  // data_node. The machine builder collapses each cap into a
@@ -5235,7 +5352,8 @@ function testRenderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges() {
5235
5352
  'edge carries the cap node tokenId so editor cross-highlight points to the cap in source text');
5236
5353
  }
5237
5354
 
5238
- function testRenderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
5355
+ // TEST0232: Renderer build editor graph data loop marked edge gets loop class
5356
+ function test0232_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
5239
5357
  // A cap marked `is_loop: true` must produce a `machine-loop`
5240
5358
  // edge so the stylesheet's dashed amber rule applies.
5241
5359
  const data = {
@@ -5253,7 +5371,8 @@ function testRenderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
5253
5371
  'loop-marked cap emits machine-loop class on the collapsed edge');
5254
5372
  }
5255
5373
 
5256
- function testRenderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags() {
5374
+ // TEST0233: Renderer build editor graph data cardinality from data slot sequence flags
5375
+ function test0233_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags() {
5257
5376
  // Cardinality markers come from the source and target data
5258
5377
  // slots' `is_sequence` flags. A cap whose output data slot has
5259
5378
  // `is_sequence=true` shows "(1→n)" on its collapsed edge.
@@ -5272,7 +5391,8 @@ function testRenderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags(
5272
5391
  'cardinality marker "(1→n)" derived from output data slot is_sequence=true');
5273
5392
  }
5274
5393
 
5275
- function testRenderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped() {
5394
+ // TEST0234: Renderer build editor graph data cap without complete args is dropped
5395
+ function test0234_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped() {
5276
5396
  // A cap with no incoming or no outgoing argument edges (e.g.
5277
5397
  // the user is mid-typing) contributes nothing to the render.
5278
5398
  // The data slots are still emitted.
@@ -5289,7 +5409,8 @@ function testRenderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped() {
5289
5409
  'incomplete cap (no outgoing argument) drops out of the render');
5290
5410
  }
5291
5411
 
5292
- function testRenderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
5412
+ // TEST0235: Renderer build editor graph data rejects edge with missing source
5413
+ function test0235_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
5293
5414
  let threw = false;
5294
5415
  try {
5295
5416
  rendererBuildEditorGraphData({
@@ -5305,7 +5426,7 @@ function testRenderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
5305
5426
 
5306
5427
  // ---------------- resolved-machine builder ----------------
5307
5428
 
5308
- function testRenderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
5429
+ function test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
5309
5430
  // A single-strand machine: media:pdf → extract → media:txt
5310
5431
  // → embed → media:embedding. Two edges, three nodes, no
5311
5432
  // loops, no fan-in. Tests the basic shape — nodes and
@@ -5363,7 +5484,8 @@ function testRenderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
5363
5484
  'output anchor node carries strand-target class');
5364
5485
  }
5365
5486
 
5366
- function testRenderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
5487
+ // TEST0237: Renderer build resolved machine graph data loop edge gets loop class
5488
+ function test0237_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
5367
5489
  // An is_loop edge corresponds to a strand step inside a
5368
5490
  // ForEach body. The renderer must mark it with the
5369
5491
  // `machine-loop` class so the dashed amber rule applies.
@@ -5397,7 +5519,8 @@ function testRenderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
5397
5519
  'is_loop=true must produce a machine-loop class on the cap edge');
5398
5520
  }
5399
5521
 
5400
- function testRenderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment() {
5522
+ // TEST0238: Renderer build resolved machine graph data fan in produces edge per assignment
5523
+ function test0238_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment() {
5401
5524
  // A cap with two input args (a fan-in) gets one rendered
5402
5525
  // edge per (source_node, target_node) pair so cytoscape can
5403
5526
  // draw both incoming wires. Both edges share the cap title
@@ -5437,7 +5560,8 @@ function testRenderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignme
5437
5560
  assertEqual(built.edges[1].data.target, 'n2', 'second edge targets n2');
5438
5561
  }
5439
5562
 
5440
- function testRenderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint() {
5563
+ // TEST0239: Renderer build resolved machine graph data multi strand keeps strands disjoint
5564
+ function test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint() {
5441
5565
  // Two strands inside one machine. Each strand has its own
5442
5566
  // nodes and edges. Node ids are globally unique across
5443
5567
  // strands (Rust assigns them via a single counter), so no
@@ -5499,7 +5623,8 @@ function testRenderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjo
5499
5623
  assertEqual(idToStrand['n3'], 1, 'n3 belongs to strand 1');
5500
5624
  }
5501
5625
 
5502
- function testRenderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard() {
5626
+ // TEST0240: Renderer build resolved machine graph data duplicate node id across strands fails hard
5627
+ function test0240_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard() {
5503
5628
  // Node ids must be globally unique across strands. The
5504
5629
  // Rust serializer guarantees this via a single global
5505
5630
  // counter. If the host ever feeds a payload that violates
@@ -5534,7 +5659,8 @@ function testRenderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrands
5534
5659
  'error must name the colliding node id');
5535
5660
  }
5536
5661
 
5537
- function testRenderer_validateResolvedMachinePayload_rejectsMissingFields() {
5662
+ // TEST0241: Renderer validate resolved machine payload rejects missing fields
5663
+ function test0241_Renderer_validateResolvedMachinePayload_rejectsMissingFields() {
5538
5664
  // The validator must reject any payload missing a required
5539
5665
  // field on a strand, edge, node, or assignment binding.
5540
5666
  // We exercise the most-likely-to-be-missed field on each
@@ -6065,9 +6191,9 @@ async function runTests() {
6065
6191
  // /api/capabilities). These tests guard the minimal API the renderer relies
6066
6192
  // on: new CapFab(), addCap(cap, registryName), getEdges(), getOutgoing().
6067
6193
  console.log('\n--- cap_fab (browse-mode API used by cap-fab-renderer) ---');
6068
- runTest('cap_fab: add_cap_populates_edges_and_nodes', testCapFabAddCapPopulatesEdgesAndNodes);
6069
- runTest('cap_fab: get_outgoing_conforms_to_matching', testCapFabGetOutgoingConformsToMatching);
6070
- runTest('cap_fab: distinct_registry_names_recorded_per_edge', testCapFabDistinctRegistryNames);
6194
+ runTest('cap_fab: add_cap_populates_edges_and_nodes', test0052_CapFabAddCapPopulatesEdgesAndNodes);
6195
+ runTest('cap_fab: get_outgoing_conforms_to_matching', test0053_CapFabGetOutgoingConformsToMatching);
6196
+ runTest('cap_fab: distinct_registry_names_recorded_per_edge', test0057_CapFabDistinctRegistryNames);
6071
6197
 
6072
6198
  // caller.rs: TEST156-TEST159
6073
6199
  console.log('\n--- caller.rs (StdinSource) ---');
@@ -6096,22 +6222,22 @@ async function runTests() {
6096
6222
  runTest('TEST308: model_path_urn', test308_modelPathUrn);
6097
6223
  runTest('TEST309: model_availability_and_path_are_distinct', test309_modelAvailabilityAndPathAreDistinct);
6098
6224
  runTest('TEST310: llm_generate_text_urn', test310_llmGenerateTextUrn);
6099
- runTest('llm_generate_text_urn_specs', testLlmGenerateTextUrnSpecs);
6225
+ runTest('llm_generate_text_urn_specs', test0058_LlmGenerateTextUrnSpecs);
6100
6226
  runTest('TEST312: all_urn_builders_produce_valid_urns', test312_allUrnBuildersProduceValidUrns);
6101
6227
 
6102
6228
  // JS-specific tests (no Rust number)
6103
6229
  console.log('\n--- JS-specific ---');
6104
- runTest('JS: build_extension_index', testJS_buildExtensionIndex);
6105
- runTest('JS: media_urns_for_extension', testJS_mediaUrnsForExtension);
6106
- runTest('JS: get_extension_mappings', testJS_getExtensionMappings);
6107
- runTest('JS: resolve_media_urn_from_specs', testJS_resolveMediaUrnFromSpecs);
6108
- runTest('JS: cap_json_serialization', testJS_capJSONSerialization);
6109
- runTest('JS: cap_documentation_round_trip', testJS_capDocumentationRoundTrip);
6110
- runTest('JS: cap_documentation_omitted_when_null', testJS_capDocumentationOmittedWhenNull);
6111
- runTest('JS: media_def_documentation_propagates_through_resolve', testJS_mediaDefDocumentationPropagatesThroughResolve);
6112
- runTest('JS: stdin_source_kind_constants', testJS_stdinSourceKindConstants);
6113
- runTest('JS: stdin_source_null_data', testJS_stdinSourceNullData);
6114
- runTest('JS: media_def_construction', testJS_mediaDefConstruction);
6230
+ runTest('JS: build_extension_index', test0059_JS_buildExtensionIndex);
6231
+ runTest('JS: media_urns_for_extension', test0069_JS_mediaUrnsForExtension);
6232
+ runTest('JS: get_extension_mappings', test0070_JS_getExtensionMappings);
6233
+ runTest('JS: resolve_media_urn_from_specs', test0073_JS_resolveMediaUrnFromSpecs);
6234
+ runTest('JS: cap_json_serialization', test0079_JS_capJSONSerialization);
6235
+ runTest('JS: cap_documentation_round_trip', test0080_JS_capDocumentationRoundTrip);
6236
+ runTest('JS: cap_documentation_omitted_when_null', test0081_JS_capDocumentationOmittedWhenNull);
6237
+ runTest('JS: media_def_documentation_propagates_through_resolve', test0082_JS_mediaDefDocumentationPropagatesThroughResolve);
6238
+ runTest('JS: stdin_source_kind_constants', test0083_JS_stdinSourceKindConstants);
6239
+ runTest('JS: stdin_source_null_data', test0084_JS_stdinSourceNullData);
6240
+ runTest('JS: media_def_construction', test0085_JS_mediaDefConstruction);
6115
6241
 
6116
6242
  // cartridge_repo: CartridgeRepoServer and CartridgeRepoClient tests
6117
6243
  console.log('\n--- cartridge_repo ---');
@@ -6179,174 +6305,174 @@ async function runTests() {
6179
6305
 
6180
6306
  // machine module: parser tests (mirrors parser.rs)
6181
6307
  console.log('\n--- machine/parser.rs ---');
6182
- runTest('MACHINE:empty_input', testMachine_emptyInput);
6183
- runTest('MACHINE:whitespace_only', testMachine_whitespaceOnly);
6184
- runTest('MACHINE:header_only_no_wirings', testMachine_headerOnlyNoWirings);
6185
- runTest('MACHINE:duplicate_alias', testMachine_duplicateAlias);
6186
- runTest('MACHINE:simple_linear_chain', testMachine_simpleLinearChain);
6187
- runTest('MACHINE:two_step_chain', testMachine_twoStepChain);
6188
- runTest('MACHINE:fan_out', testMachine_fanOut);
6189
- runTest('MACHINE:fan_in_secondary_assigned_by_prior_wiring', testMachine_fanInSecondaryAssignedByPriorWiring);
6190
- runTest('MACHINE:fan_in_secondary_unassigned_gets_wildcard', testMachine_fanInSecondaryUnassignedGetsWildcard);
6191
- runTest('MACHINE:loop_edge', testMachine_loopEdge);
6192
- runTest('MACHINE:undefined_alias_fails', testMachine_undefinedAliasFails);
6193
- runTest('MACHINE:node_alias_collision', testMachine_nodeAliasCollision);
6194
- runTest('MACHINE:conflicting_media_types_fail', testMachine_conflictingMediaTypesFail);
6195
- runTest('MACHINE:multiline_format', testMachine_multilineFormat);
6196
- runTest('MACHINE:different_aliases_same_graph', testMachine_differentAliasesSameGraph);
6197
- runTest('MACHINE:malformed_input_fails', testMachine_malformedInputFails);
6198
- runTest('MACHINE:unterminated_bracket_fails', testMachine_unterminatedBracketFails);
6308
+ runTest('MACHINE:empty_input', test0087_Machine_emptyInput);
6309
+ runTest('MACHINE:whitespace_only', test0088_Machine_whitespaceOnly);
6310
+ runTest('MACHINE:header_only_no_wirings', test0089_Machine_headerOnlyNoWirings);
6311
+ runTest('MACHINE:duplicate_alias', test0090_Machine_duplicateAlias);
6312
+ runTest('MACHINE:simple_linear_chain', test0094_Machine_simpleLinearChain);
6313
+ runTest('MACHINE:two_step_chain', test0095_Machine_twoStepChain);
6314
+ runTest('MACHINE:fan_out', test0096_Machine_fanOut);
6315
+ runTest('MACHINE:fan_in_secondary_assigned_by_prior_wiring', test0097_Machine_fanInSecondaryAssignedByPriorWiring);
6316
+ runTest('MACHINE:fan_in_secondary_unassigned_gets_wildcard', test0098_Machine_fanInSecondaryUnassignedGetsWildcard);
6317
+ runTest('MACHINE:loop_edge', test0111_Machine_loopEdge);
6318
+ runTest('MACHINE:undefined_alias_fails', test0112_Machine_undefinedAliasFails);
6319
+ runTest('MACHINE:node_alias_collision', test0113_Machine_nodeAliasCollision);
6320
+ runTest('MACHINE:conflicting_media_types_fail', test0114_Machine_conflictingMediaTypesFail);
6321
+ runTest('MACHINE:multiline_format', test0117_Machine_multilineFormat);
6322
+ runTest('MACHINE:different_aliases_same_graph', test0118_Machine_differentAliasesSameGraph);
6323
+ runTest('MACHINE:malformed_input_fails', test0119_Machine_malformedInputFails);
6324
+ runTest('MACHINE:unterminated_bracket_fails', test0120_Machine_unterminatedBracketFails);
6199
6325
 
6200
6326
  // machine module: line-based mode tests
6201
6327
  console.log('\n--- machine/parser.rs (line-based) ---');
6202
- runTest('MACHINE:line_based_simple_chain', testMachine_lineBasedSimpleChain);
6203
- runTest('MACHINE:line_based_two_step_chain', testMachine_lineBasedTwoStepChain);
6204
- runTest('MACHINE:line_based_loop', testMachine_lineBasedLoop);
6205
- runTest('MACHINE:line_based_fan_in', testMachine_lineBasedFanIn);
6206
- runTest('MACHINE:mixed_bracketed_and_line_based', testMachine_mixedBracketedAndLineBased);
6207
- runTest('MACHINE:line_based_equivalent_to_bracketed', testMachine_lineBasedEquivalentToBracketed);
6208
- runTest('MACHINE:line_based_format_serialization', testMachine_lineBasedFormatSerialization);
6209
- runTest('MACHINE:line_based_and_bracketed_parse_same_graph', testMachine_lineBasedAndBracketedParseSameGraph);
6328
+ runTest('MACHINE:line_based_simple_chain', test0121_Machine_lineBasedSimpleChain);
6329
+ runTest('MACHINE:line_based_two_step_chain', test0122_Machine_lineBasedTwoStepChain);
6330
+ runTest('MACHINE:line_based_loop', test0123_Machine_lineBasedLoop);
6331
+ runTest('MACHINE:line_based_fan_in', test0124_Machine_lineBasedFanIn);
6332
+ runTest('MACHINE:mixed_bracketed_and_line_based', test0125_Machine_mixedBracketedAndLineBased);
6333
+ runTest('MACHINE:line_based_equivalent_to_bracketed', test0126_Machine_lineBasedEquivalentToBracketed);
6334
+ runTest('MACHINE:line_based_format_serialization', test0127_Machine_lineBasedFormatSerialization);
6335
+ runTest('MACHINE:line_based_and_bracketed_parse_same_graph', test0128_Machine_lineBasedAndBracketedParseSameGraph);
6210
6336
 
6211
6337
  // machine module: graph tests (mirrors graph.rs)
6212
6338
  console.log('\n--- machine/graph.rs ---');
6213
- runTest('MACHINE:edge_equivalence_same_urns', testMachine_edgeEquivalenceSameUrns);
6214
- runTest('MACHINE:edge_equivalence_different_cap_urns', testMachine_edgeEquivalenceDifferentCapUrns);
6215
- runTest('MACHINE:edge_equivalence_different_targets', testMachine_edgeEquivalenceDifferentTargets);
6216
- runTest('MACHINE:edge_equivalence_different_loop_flag', testMachine_edgeEquivalenceDifferentLoopFlag);
6217
- runTest('MACHINE:edge_equivalence_source_order_independent', testMachine_edgeEquivalenceSourceOrderIndependent);
6218
- runTest('MACHINE:edge_equivalence_different_source_count', testMachine_edgeEquivalenceDifferentSourceCount);
6219
- runTest('MACHINE:graph_equivalence_same_edges', testMachine_graphEquivalenceSameEdges);
6220
- runTest('MACHINE:graph_equivalence_reordered_edges', testMachine_graphEquivalenceReorderedEdges);
6221
- runTest('MACHINE:graph_not_equivalent_different_edge_count', testMachine_graphNotEquivalentDifferentEdgeCount);
6222
- runTest('MACHINE:graph_not_equivalent_different_cap', testMachine_graphNotEquivalentDifferentCap);
6223
- runTest('MACHINE:graph_empty', testMachine_graphEmpty);
6224
- runTest('MACHINE:graph_empty_equivalence', testMachine_graphEmptyEquivalence);
6225
- runTest('MACHINE:root_sources_linear_chain', testMachine_rootSourcesLinearChain);
6226
- runTest('MACHINE:leaf_targets_linear_chain', testMachine_leafTargetsLinearChain);
6227
- runTest('MACHINE:root_sources_fan_in', testMachine_rootSourcesFanIn);
6228
- runTest('MACHINE:display_edge', testMachine_displayEdge);
6229
- runTest('MACHINE:display_graph', testMachine_displayGraph);
6339
+ runTest('MACHINE:edge_equivalence_same_urns', test0129_Machine_edgeEquivalenceSameUrns);
6340
+ runTest('MACHINE:edge_equivalence_different_cap_urns', test0130_Machine_edgeEquivalenceDifferentCapUrns);
6341
+ runTest('MACHINE:edge_equivalence_different_targets', test0131_Machine_edgeEquivalenceDifferentTargets);
6342
+ runTest('MACHINE:edge_equivalence_different_loop_flag', test0132_Machine_edgeEquivalenceDifferentLoopFlag);
6343
+ runTest('MACHINE:edge_equivalence_source_order_independent', test0133_Machine_edgeEquivalenceSourceOrderIndependent);
6344
+ runTest('MACHINE:edge_equivalence_different_source_count', test0134_Machine_edgeEquivalenceDifferentSourceCount);
6345
+ runTest('MACHINE:graph_equivalence_same_edges', test0135_Machine_graphEquivalenceSameEdges);
6346
+ runTest('MACHINE:graph_equivalence_reordered_edges', test0136_Machine_graphEquivalenceReorderedEdges);
6347
+ runTest('MACHINE:graph_not_equivalent_different_edge_count', test0137_Machine_graphNotEquivalentDifferentEdgeCount);
6348
+ runTest('MACHINE:graph_not_equivalent_different_cap', test0138_Machine_graphNotEquivalentDifferentCap);
6349
+ runTest('MACHINE:graph_empty', test0139_Machine_graphEmpty);
6350
+ runTest('MACHINE:graph_empty_equivalence', test0140_Machine_graphEmptyEquivalence);
6351
+ runTest('MACHINE:root_sources_linear_chain', test0141_Machine_rootSourcesLinearChain);
6352
+ runTest('MACHINE:leaf_targets_linear_chain', test0142_Machine_leafTargetsLinearChain);
6353
+ runTest('MACHINE:root_sources_fan_in', test0143_Machine_rootSourcesFanIn);
6354
+ runTest('MACHINE:display_edge', test0144_Machine_displayEdge);
6355
+ runTest('MACHINE:display_graph', test0145_Machine_displayGraph);
6230
6356
 
6231
6357
  // machine module: serializer tests (mirrors serializer.rs)
6232
6358
  console.log('\n--- machine/serializer.rs ---');
6233
- runTest('MACHINE:serialize_single_edge', testMachine_serializeSingleEdge);
6234
- runTest('MACHINE:serialize_two_edge_chain', testMachine_serializeTwoEdgeChain);
6235
- runTest('MACHINE:serialize_empty_graph', testMachine_serializeEmptyGraph);
6236
- runTest('MACHINE:roundtrip_single_edge', testMachine_roundtripSingleEdge);
6237
- runTest('MACHINE:roundtrip_two_edge_chain', testMachine_roundtripTwoEdgeChain);
6238
- runTest('MACHINE:roundtrip_fan_out', testMachine_roundtripFanOut);
6239
- runTest('MACHINE:roundtrip_loop_edge', testMachine_roundtripLoopEdge);
6240
- runTest('MACHINE:serialization_is_deterministic', testMachine_serializationIsDeterministic);
6241
- runTest('MACHINE:reordered_edges_produce_same_notation', testMachine_reorderedEdgesProduceSameNotation);
6242
- runTest('MACHINE:multiline_serialize_format', testMachine_multilineSerializeFormat);
6243
- runTest('MACHINE:alias_from_op_tag', testMachine_aliasFromOpTag);
6244
- runTest('MACHINE:alias_fallback_without_op_tag', testMachine_aliasFallbackWithoutOpTag);
6245
- runTest('MACHINE:duplicate_op_tags_disambiguated', testMachine_duplicateOpTagsDisambiguated);
6359
+ runTest('MACHINE:serialize_single_edge', test0146_Machine_serializeSingleEdge);
6360
+ runTest('MACHINE:serialize_two_edge_chain', test0147_Machine_serializeTwoEdgeChain);
6361
+ runTest('MACHINE:serialize_empty_graph', test0148_Machine_serializeEmptyGraph);
6362
+ runTest('MACHINE:roundtrip_single_edge', test0149_Machine_roundtripSingleEdge);
6363
+ runTest('MACHINE:roundtrip_two_edge_chain', test0151_Machine_roundtripTwoEdgeChain);
6364
+ runTest('MACHINE:roundtrip_fan_out', test0152_Machine_roundtripFanOut);
6365
+ runTest('MACHINE:roundtrip_loop_edge', test0153_Machine_roundtripLoopEdge);
6366
+ runTest('MACHINE:serialization_is_deterministic', test0154_Machine_serializationIsDeterministic);
6367
+ runTest('MACHINE:reordered_edges_produce_same_notation', test0155_Machine_reorderedEdgesProduceSameNotation);
6368
+ runTest('MACHINE:multiline_serialize_format', test0160_Machine_multilineSerializeFormat);
6369
+ runTest('MACHINE:alias_from_op_tag', test0161_Machine_aliasFromOpTag);
6370
+ runTest('MACHINE:alias_fallback_without_op_tag', test0162_Machine_aliasFallbackWithoutOpTag);
6371
+ runTest('MACHINE:duplicate_op_tags_disambiguated', test0163_Machine_duplicateOpTagsDisambiguated);
6246
6372
 
6247
6373
  // machine module: builder tests
6248
6374
  console.log('\n--- machine/builder ---');
6249
- runTest('MACHINE:builder_single_edge', testMachine_builderSingleEdge);
6250
- runTest('MACHINE:builder_with_loop', testMachine_builderWithLoop);
6251
- runTest('MACHINE:builder_chaining', testMachine_builderChaining);
6252
- runTest('MACHINE:builder_equivalent_to_parsed', testMachine_builderEquivalentToParsed);
6253
- runTest('MACHINE:builder_round_trip', testMachine_builderRoundTrip);
6375
+ runTest('MACHINE:builder_single_edge', test0164_Machine_builderSingleEdge);
6376
+ runTest('MACHINE:builder_with_loop', test0165_Machine_builderWithLoop);
6377
+ runTest('MACHINE:builder_chaining', test0166_Machine_builderChaining);
6378
+ runTest('MACHINE:builder_equivalent_to_parsed', test0167_Machine_builderEquivalentToParsed);
6379
+ runTest('MACHINE:builder_round_trip', test0168_Machine_builderRoundTrip);
6254
6380
 
6255
6381
  // machine module: CapUrn.isEquivalent/isComparable
6256
6382
  console.log('\n--- machine/urn_predicates ---');
6257
- runTest('MACHINE:cap_urn_is_equivalent', testMachine_capUrnIsEquivalent);
6258
- runTest('MACHINE:cap_urn_is_comparable', testMachine_capUrnIsComparable);
6259
- runTest('MACHINE:cap_urn_in_media_urn', testMachine_capUrnInMediaUrn);
6260
- runTest('MACHINE:cap_urn_out_media_urn', testMachine_capUrnOutMediaUrn);
6261
- runTest('MACHINE:media_urn_is_equivalent', testMachine_mediaUrnIsEquivalent);
6262
- runTest('MACHINE:media_urn_is_comparable', testMachine_mediaUrnIsComparable);
6383
+ runTest('MACHINE:cap_urn_is_equivalent', test0169_Machine_capUrnIsEquivalent);
6384
+ runTest('MACHINE:cap_urn_is_comparable', test0170_Machine_capUrnIsComparable);
6385
+ runTest('MACHINE:cap_urn_in_media_urn', test0171_Machine_capUrnInMediaUrn);
6386
+ runTest('MACHINE:cap_urn_out_media_urn', test0172_Machine_capUrnOutMediaUrn);
6387
+ runTest('MACHINE:media_urn_is_equivalent', test0173_Machine_mediaUrnIsEquivalent);
6388
+ runTest('MACHINE:media_urn_is_comparable', test0174_Machine_mediaUrnIsComparable);
6263
6389
 
6264
6390
  // Phase 0A: Position tracking
6265
6391
  console.log('\n--- machine/position_tracking ---');
6266
- runTest('MACHINE:parseMachineWithAST_headerLocation', testMachine_parseMachineWithAST_headerLocation);
6267
- runTest('MACHINE:parseMachineWithAST_wiringLocation', testMachine_parseMachineWithAST_wiringLocation);
6268
- runTest('MACHINE:parseMachineWithAST_multilinePositions', testMachine_parseMachineWithAST_multilinePositions);
6269
- runTest('MACHINE:parseMachineWithAST_fanInSourceLocations', testMachine_parseMachineWithAST_fanInSourceLocations);
6270
- runTest('MACHINE:parseMachineWithAST_aliasMap', testMachine_parseMachineWithAST_aliasMap);
6271
- runTest('MACHINE:parseMachineWithAST_nodeMedia', testMachine_parseMachineWithAST_nodeMedia);
6272
- runTest('MACHINE:errorLocation_parseError', testMachine_errorLocation_parseError);
6273
- runTest('MACHINE:errorLocation_duplicateAlias', testMachine_errorLocation_duplicateAlias);
6274
- runTest('MACHINE:errorLocation_undefinedAlias', testMachine_errorLocation_undefinedAlias);
6392
+ runTest('MACHINE:parseMachineWithAST_headerLocation', test0175_Machine_parseMachineWithAST_headerLocation);
6393
+ runTest('MACHINE:parseMachineWithAST_wiringLocation', test0176_Machine_parseMachineWithAST_wiringLocation);
6394
+ runTest('MACHINE:parseMachineWithAST_multilinePositions', test0177_Machine_parseMachineWithAST_multilinePositions);
6395
+ runTest('MACHINE:parseMachineWithAST_fanInSourceLocations', test0178_Machine_parseMachineWithAST_fanInSourceLocations);
6396
+ runTest('MACHINE:parseMachineWithAST_aliasMap', test0179_Machine_parseMachineWithAST_aliasMap);
6397
+ runTest('MACHINE:parseMachineWithAST_nodeMedia', test0180_Machine_parseMachineWithAST_nodeMedia);
6398
+ runTest('MACHINE:errorLocation_parseError', test0181_Machine_errorLocation_parseError);
6399
+ runTest('MACHINE:errorLocation_duplicateAlias', test0182_Machine_errorLocation_duplicateAlias);
6400
+ runTest('MACHINE:errorLocation_undefinedAlias', test0183_Machine_errorLocation_undefinedAlias);
6275
6401
 
6276
6402
  // Phase 0C: Machine.toMermaid()
6277
6403
  console.log('\n--- machine/mermaid ---');
6278
- runTest('MACHINE:toMermaid_linearChain', testMachine_toMermaid_linearChain);
6279
- runTest('MACHINE:toMermaid_loopEdge', testMachine_toMermaid_loopEdge);
6280
- runTest('MACHINE:toMermaid_emptyGraph', testMachine_toMermaid_emptyGraph);
6281
- runTest('MACHINE:toMermaid_fanIn', testMachine_toMermaid_fanIn);
6282
- runTest('MACHINE:toMermaid_fanOut', testMachine_toMermaid_fanOut);
6404
+ runTest('MACHINE:toMermaid_linearChain', test0184_Machine_toMermaid_linearChain);
6405
+ runTest('MACHINE:toMermaid_loopEdge', test0185_Machine_toMermaid_loopEdge);
6406
+ runTest('MACHINE:toMermaid_emptyGraph', test0186_Machine_toMermaid_emptyGraph);
6407
+ runTest('MACHINE:toMermaid_fanIn', test0187_Machine_toMermaid_fanIn);
6408
+ runTest('MACHINE:toMermaid_fanOut', test0188_Machine_toMermaid_fanOut);
6283
6409
 
6284
6410
  // Phase 0B: FabricRegistryClient
6285
6411
  console.log('\n--- registry/client ---');
6286
- runTest('REGISTRY: capRegistryEntry_construction', testMachine_capRegistryEntry_construction);
6287
- runTest('REGISTRY: mediaRegistryEntry_construction', testMachine_mediaRegistryEntry_construction);
6288
- runTest('REGISTRY: capRegistryClient_construction', testMachine_capRegistryClient_construction);
6289
- runTest('REGISTRY: capRegistryEntry_defaults', testMachine_capRegistryEntry_defaults);
6412
+ runTest('REGISTRY: capRegistryEntry_construction', test0189_Machine_capRegistryEntry_construction);
6413
+ runTest('REGISTRY: mediaRegistryEntry_construction', test0190_Machine_mediaRegistryEntry_construction);
6414
+ runTest('REGISTRY: capRegistryClient_construction', test0191_Machine_capRegistryClient_construction);
6415
+ runTest('REGISTRY: capRegistryEntry_defaults', test0192_Machine_capRegistryEntry_defaults);
6290
6416
 
6291
6417
  // cap-fab-renderer pure helpers (no DOM dependency)
6292
6418
  console.log('\n--- cap-fab-renderer helpers ---');
6293
- runTest('RENDERER: cardinalityLabel_allFourCases', testRenderer_cardinalityLabel_allFourCases);
6294
- runTest('RENDERER: cardinalityLabel_usesUnicodeArrow', testRenderer_cardinalityLabel_usesUnicodeArrow);
6295
- runTest('RENDERER: cardinalityFromCap_findsStdinArg', testRenderer_cardinalityFromCap_findsStdinArgNotFirstArg);
6296
- runTest('RENDERER: cardinalityFromCap_scalarDefaults', testRenderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing);
6297
- runTest('RENDERER: cardinalityFromCap_outputOnlySequence', testRenderer_cardinalityFromCap_outputOnlySequence);
6298
- runTest('RENDERER: cardinalityFromCap_rejectsStringBool', testRenderer_cardinalityFromCap_rejectsStringIsSequence);
6299
- runTest('RENDERER: cardinalityFromCap_throwsOnNonObject', testRenderer_cardinalityFromCap_throwsOnNonObject);
6300
- runTest('RENDERER: canonicalMediaUrn_normalizesTagOrder', testRenderer_canonicalMediaUrn_normalizesTagOrder);
6301
- runTest('RENDERER: canonicalMediaUrn_preservesValueTags', testRenderer_canonicalMediaUrn_preservesValueTags);
6302
- runTest('RENDERER: canonicalMediaUrn_rejectsCapUrn', testRenderer_canonicalMediaUrn_rejectsCapUrn);
6303
- runTest('RENDERER: mediaNodeLabel_rejectsUrnDerived', testRenderer_mediaNodeLabel_rejectsUrnDerivedLabels);
6304
- runTest('RENDERER: buildBrowse_rejectsMissingMediaTitles', testRenderer_buildBrowseGraphData_rejectsMissingMediaTitles);
6419
+ runTest('RENDERER: cardinalityLabel_allFourCases', test0193_Renderer_cardinalityLabel_allFourCases);
6420
+ runTest('RENDERER: cardinalityLabel_usesUnicodeArrow', test0194_Renderer_cardinalityLabel_usesUnicodeArrow);
6421
+ runTest('RENDERER: cardinalityFromCap_findsStdinArg', test0195_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg);
6422
+ runTest('RENDERER: cardinalityFromCap_scalarDefaults', test0196_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing);
6423
+ runTest('RENDERER: cardinalityFromCap_outputOnlySequence', test0197_Renderer_cardinalityFromCap_outputOnlySequence);
6424
+ runTest('RENDERER: cardinalityFromCap_rejectsStringBool', test0198_Renderer_cardinalityFromCap_rejectsStringIsSequence);
6425
+ runTest('RENDERER: cardinalityFromCap_throwsOnNonObject', test0199_Renderer_cardinalityFromCap_throwsOnNonObject);
6426
+ runTest('RENDERER: canonicalMediaUrn_normalizesTagOrder', test0200_Renderer_canonicalMediaUrn_normalizesTagOrder);
6427
+ runTest('RENDERER: canonicalMediaUrn_preservesValueTags', test0201_Renderer_canonicalMediaUrn_preservesValueTags);
6428
+ runTest('RENDERER: canonicalMediaUrn_rejectsCapUrn', test0202_Renderer_canonicalMediaUrn_rejectsCapUrn);
6429
+ runTest('RENDERER: mediaNodeLabel_rejectsUrnDerived', test0203_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels);
6430
+ runTest('RENDERER: buildBrowse_rejectsMissingMediaTitles', test0204_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles);
6305
6431
 
6306
6432
  console.log('\n--- cap-fab-renderer strand builder ---');
6307
- runTest('RENDERER: validateStrandStep_unknownVariant', testRenderer_validateStrandStep_rejectsUnknownVariant);
6308
- runTest('RENDERER: validateStrandStep_booleanIsSequence', testRenderer_validateStrandStep_requiresBooleanIsSequence);
6309
- runTest('RENDERER: classifyStrandCapSteps_simple', testRenderer_classifyStrandCapSteps_capFlags);
6310
- runTest('RENDERER: classifyStrandCapSteps_nested', testRenderer_classifyStrandCapSteps_nestedForks);
6311
- runTest('RENDERER: buildStrand_singleCapPlain', testRenderer_buildStrandGraphData_singleCapPlain);
6312
- runTest('RENDERER: buildStrand_sequenceShowsCardinality', testRenderer_buildStrandGraphData_sequenceShowsCardinality);
6313
- runTest('RENDERER: buildStrand_foreachCollectSpan', testRenderer_buildStrandGraphData_foreachCollectSpan);
6314
- runTest('RENDERER: buildStrand_standaloneCollect', testRenderer_buildStrandGraphData_standaloneCollect);
6315
- runTest('RENDERER: buildStrand_unclosedForEachBody', testRenderer_buildStrandGraphData_unclosedForEachBody);
6316
- runTest('RENDERER: buildStrand_nestedForEachThrows', testRenderer_buildStrandGraphData_nestedForEachThrows);
6317
- runTest('RENDERER: collapseStrand_singleCapBody', testRenderer_collapseStrand_singleCapBodyKeepsCapOwnLabel);
6318
- runTest('RENDERER: collapseStrand_unclosedForEachBody', testRenderer_collapseStrand_unclosedForEachBodyCollapses);
6319
- runTest('RENDERER: collapseStrand_standaloneCollect', testRenderer_collapseStrand_standaloneCollectCollapses);
6320
- runTest('RENDERER: collapseStrand_seqCapBeforeForeach', testRenderer_collapseStrand_sequenceProducingCapBeforeForeach);
6321
- runTest('RENDERER: collapseStrand_plainCapMergesOutput', testRenderer_collapseStrand_plainCapMergesTrailingOutput);
6322
- runTest('RENDERER: collapseStrand_plainCapDistinctTarget', testRenderer_collapseStrand_plainCapDistinctTargetNoMerge);
6323
- runTest('RENDERER: validateStrand_missingSourceMediaUrn', testRenderer_validateStrandPayload_missingSourceMediaUrn);
6433
+ runTest('RENDERER: validateStrandStep_unknownVariant', test0205_Renderer_validateStrandStep_rejectsUnknownVariant);
6434
+ runTest('RENDERER: validateStrandStep_booleanIsSequence', test0206_Renderer_validateStrandStep_requiresBooleanIsSequence);
6435
+ runTest('RENDERER: classifyStrandCapSteps_simple', test0207_Renderer_classifyStrandCapSteps_capFlags);
6436
+ runTest('RENDERER: classifyStrandCapSteps_nested', test0208_Renderer_classifyStrandCapSteps_nestedForks);
6437
+ runTest('RENDERER: buildStrand_singleCapPlain', test0209_Renderer_buildStrandGraphData_singleCapPlain);
6438
+ runTest('RENDERER: buildStrand_sequenceShowsCardinality', test0210_Renderer_buildStrandGraphData_sequenceShowsCardinality);
6439
+ runTest('RENDERER: buildStrand_foreachCollectSpan', test0211_Renderer_buildStrandGraphData_foreachCollectSpan);
6440
+ runTest('RENDERER: buildStrand_standaloneCollect', test0212_Renderer_buildStrandGraphData_standaloneCollect);
6441
+ runTest('RENDERER: buildStrand_unclosedForEachBody', test0213_Renderer_buildStrandGraphData_unclosedForEachBody);
6442
+ runTest('RENDERER: buildStrand_nestedForEachThrows', test0214_Renderer_buildStrandGraphData_nestedForEachThrows);
6443
+ runTest('RENDERER: collapseStrand_singleCapBody', test0215_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel);
6444
+ runTest('RENDERER: collapseStrand_unclosedForEachBody', test0216_Renderer_collapseStrand_unclosedForEachBodyCollapses);
6445
+ runTest('RENDERER: collapseStrand_standaloneCollect', test0217_Renderer_collapseStrand_standaloneCollectCollapses);
6446
+ runTest('RENDERER: collapseStrand_seqCapBeforeForeach', test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach);
6447
+ runTest('RENDERER: collapseStrand_plainCapMergesOutput', test0219_Renderer_collapseStrand_plainCapMergesTrailingOutput);
6448
+ runTest('RENDERER: collapseStrand_plainCapDistinctTarget', test0220_Renderer_collapseStrand_plainCapDistinctTargetNoMerge);
6449
+ runTest('RENDERER: validateStrand_missingSourceMediaUrn', test0221_Renderer_validateStrandPayload_missingSourceMediaUrn);
6324
6450
 
6325
6451
  console.log('\n--- cap-fab-renderer run builder ---');
6326
- runTest('RENDERER: validateBodyOutcome_negativeIndex', testRenderer_validateBodyOutcome_rejectsNegativeIndex);
6327
- runTest('RENDERER: buildRun_pagesSuccessesAndFailures', testRenderer_buildRunGraphData_pagesSuccessesAndFailures);
6328
- runTest('RENDERER: buildRun_failureWithoutFailedCap', testRenderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace);
6329
- runTest('RENDERER: buildRun_usesIsEquivalentForFailedCap', testRenderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap);
6330
- runTest('RENDERER: buildRun_backboneHasNoForeachNode', testRenderer_buildRunGraphData_backboneHasNoForeachNode);
6331
- runTest('RENDERER: buildRun_allFailedDropsPlaceholder', testRenderer_buildRunGraphData_allFailedDropsTargetPlaceholder);
6332
- runTest('RENDERER: buildRun_unclosedForeachNoMerge', testRenderer_buildRunGraphData_unclosedForeachSuccessNoMerge);
6333
- runTest('RENDERER: buildRun_closedForeachMerges', testRenderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget);
6452
+ runTest('RENDERER: validateBodyOutcome_negativeIndex', test0222_Renderer_validateBodyOutcome_rejectsNegativeIndex);
6453
+ runTest('RENDERER: buildRun_pagesSuccessesAndFailures', test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures);
6454
+ runTest('RENDERER: buildRun_failureWithoutFailedCap', test0224_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace);
6455
+ runTest('RENDERER: buildRun_usesIsEquivalentForFailedCap', test0225_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap);
6456
+ runTest('RENDERER: buildRun_backboneHasNoForeachNode', test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode);
6457
+ runTest('RENDERER: buildRun_allFailedDropsPlaceholder', test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder);
6458
+ runTest('RENDERER: buildRun_unclosedForeachNoMerge', test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge);
6459
+ runTest('RENDERER: buildRun_closedForeachMerges', test0229_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget);
6334
6460
 
6335
6461
  console.log('\n--- cap-fab-renderer editor-graph builder ---');
6336
- runTest('RENDERER: validateEditorGraph_unknownKind', testRenderer_validateEditorGraphPayload_rejectsUnknownKind);
6337
- runTest('RENDERER: buildEditorGraph_collapsesCapsIntoEdges', testRenderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges);
6338
- runTest('RENDERER: buildEditorGraph_loopEdgeGetsClass', testRenderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass);
6339
- runTest('RENDERER: buildEditorGraph_cardinalityFromIsSeq', testRenderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags);
6340
- runTest('RENDERER: buildEditorGraph_incompleteCapDropped', testRenderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped);
6341
- runTest('RENDERER: buildEditorGraph_rejectsEdgeMissingSrc', testRenderer_buildEditorGraphData_rejectsEdgeWithMissingSource);
6462
+ runTest('RENDERER: validateEditorGraph_unknownKind', test0230_Renderer_validateEditorGraphPayload_rejectsUnknownKind);
6463
+ runTest('RENDERER: buildEditorGraph_collapsesCapsIntoEdges', test0231_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges);
6464
+ runTest('RENDERER: buildEditorGraph_loopEdgeGetsClass', test0232_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass);
6465
+ runTest('RENDERER: buildEditorGraph_cardinalityFromIsSeq', test0233_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags);
6466
+ runTest('RENDERER: buildEditorGraph_incompleteCapDropped', test0234_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped);
6467
+ runTest('RENDERER: buildEditorGraph_rejectsEdgeMissingSrc', test0235_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource);
6342
6468
 
6343
6469
  console.log('\n--- cap-fab-renderer resolved-machine builder ---');
6344
- runTest('RENDERER: buildResolvedMachine_singleStrandLinear', testRenderer_buildResolvedMachineGraphData_singleStrandLinearChain);
6345
- runTest('RENDERER: buildResolvedMachine_loopGetsLoopClass', testRenderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass);
6346
- runTest('RENDERER: buildResolvedMachine_fanInOneEdgePerSrc', testRenderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment);
6347
- runTest('RENDERER: buildResolvedMachine_multiStrandDisjoint', testRenderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint);
6348
- runTest('RENDERER: buildResolvedMachine_dupNodeIdFails', testRenderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard);
6349
- runTest('RENDERER: validateResolvedMachine_rejectsMissingFields', testRenderer_validateResolvedMachinePayload_rejectsMissingFields);
6470
+ runTest('RENDERER: buildResolvedMachine_singleStrandLinear', test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain);
6471
+ runTest('RENDERER: buildResolvedMachine_loopGetsLoopClass', test0237_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass);
6472
+ runTest('RENDERER: buildResolvedMachine_fanInOneEdgePerSrc', test0238_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment);
6473
+ runTest('RENDERER: buildResolvedMachine_multiStrandDisjoint', test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint);
6474
+ runTest('RENDERER: buildResolvedMachine_dupNodeIdFails', test0240_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard);
6475
+ runTest('RENDERER: validateResolvedMachine_rejectsMissingFields', test0241_Renderer_validateResolvedMachinePayload_rejectsMissingFields);
6350
6476
 
6351
6477
  console.log('\n--- CapKind classifier (test1800–test1805) ---');
6352
6478
  runTest('TEST1800: kind_identity_requires_effect_none', test1800_kindIdentityOnlyForBareCap);