capdag 1.211.563 → 1.218.588

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/capdag.js +131 -0
  2. package/capdag.test.js +734 -723
  3. package/package.json +1 -1
package/capdag.test.js CHANGED
@@ -33,7 +33,10 @@ const {
33
33
  MEDIA_COLLECTION, MEDIA_COLLECTION_LIST,
34
34
  MEDIA_DECISION,
35
35
  MEDIA_AUDIO_SPEECH,
36
- CAP_IDENTITY
36
+ CAP_IDENTITY,
37
+ ALIAS_TARGET_CAP, ALIAS_TARGET_MEDIA,
38
+ tokenIsUrn, isAliasToken, normalizeAliasName, classifyAliasTarget,
39
+ StoredAlias, Manifest
37
40
  } = require('./capdag.js');
38
41
 
39
42
  // ============================================================================
@@ -111,8 +114,8 @@ function runTest(name, fn) {
111
114
  * Uses MEDIA_VOID for in and MEDIA_OBJECT for out, matching the
112
115
  * Rust reference test_urn helper: test_urn(tags) => cap:in="media:void";{tags};out="media:enc=utf-8;record"
113
116
  */
114
- // TEST0051: Urn
115
- function test0051_Urn(tags) {
117
+ // TEST6204: Urn
118
+ function test6204_Urn(tags) {
116
119
  if (!tags || tags === '') {
117
120
  return `cap:in="${MEDIA_VOID}";out="${MEDIA_OBJECT}"`;
118
121
  }
@@ -136,9 +139,9 @@ function makeGraphCap(inUrn, outUrn, title) {
136
139
  // cap_urn.rs: TEST001-TEST050, TEST890-TEST891
137
140
  // ============================================================================
138
141
 
139
- // TEST001: Test that cap URN is created with tags parsed correctly and direction specs accessible
142
+ // TEST1: Test that cap URN is created with tags parsed correctly and direction specs accessible
140
143
  function test001_capUrnCreation() {
141
- const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf;target=thumbnail'));
144
+ const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf;target=thumbnail'));
142
145
  assert(cap.hasMarkerTag('generate'), 'Should get op tag');
143
146
  assertEqual(cap.getTag('target'), 'thumbnail', 'Should get target tag');
144
147
  assertEqual(cap.getTag('ext'), 'pdf', 'Should get ext tag');
@@ -146,7 +149,7 @@ function test001_capUrnCreation() {
146
149
  assertEqual(cap.getOutSpec(), MEDIA_OBJECT, 'Should get outSpec');
147
150
  }
148
151
 
149
- // TEST002: Test that missing 'in' or 'out' defaults to media: wildcard
152
+ // TEST2: Test that missing 'in' or 'out' defaults to media: wildcard
150
153
  function test002_directionSpecsRequired() {
151
154
  const missingIn = CapUrn.fromString('cap:in=media:;out=media:void;test');
152
155
  assertEqual(missingIn.getInSpec(), MEDIA_IDENTITY, 'Missing in should default to media:');
@@ -157,10 +160,10 @@ function test002_directionSpecsRequired() {
157
160
  assertEqual(missingOut.getOutSpec(), MEDIA_IDENTITY, 'Missing out should default to media:');
158
161
  }
159
162
 
160
- // TEST003: Test that direction specs must match exactly, different in/out types don't match, wildcard matches any
163
+ // TEST3: Test that direction specs must match exactly, different in/out types don't match, wildcard matches any
161
164
  function test003_directionMatching() {
162
- const cap = CapUrn.fromString(test0051_Urn('generate'));
163
- const request = CapUrn.fromString(test0051_Urn('generate'));
165
+ const cap = CapUrn.fromString(test6204_Urn('generate'));
166
+ const request = CapUrn.fromString(test6204_Urn('generate'));
164
167
  assert(cap.accepts(request), 'Same direction specs should match');
165
168
 
166
169
  // Different direction should not match
@@ -172,9 +175,7 @@ function test003_directionMatching() {
172
175
  assert(wildcardCap.accepts(request), 'Wildcard direction should match any');
173
176
  }
174
177
 
175
- // TEST004: Test that unquoted keys and values are normalized to lowercase.
176
- // Key lookup is case-insensitive: uppercase variants of `ext` resolve
177
- // to the same keyed tag.
178
+ // TEST4: Test that unquoted keys and values are normalized to lowercase
178
179
  function test004_unquotedValuesLowercased() {
179
180
  const cap = CapUrn.fromString('cap:ext=pdf;generate;in=media:void;out="media:enc=utf-8;record"');
180
181
  assert(cap.hasMarkerTag('generate'), 'Unquoted value should be lowercased');
@@ -182,33 +183,33 @@ function test004_unquotedValuesLowercased() {
182
183
  assertEqual(cap.getTag('EXT'), 'pdf', 'Key lookup should be case-insensitive');
183
184
  }
184
185
 
185
- // TEST005: Test that quoted values preserve case while unquoted are lowercased
186
+ // TEST5: Test that quoted values preserve case while unquoted are lowercased
186
187
  function test005_quotedValuesPreserveCase() {
187
188
  const cap = CapUrn.fromString('cap:in="media:void";key="HelloWorld";out="media:void"');
188
189
  assertEqual(cap.getTag('key'), 'HelloWorld', 'Quoted value should preserve case');
189
190
  }
190
191
 
191
- // TEST006: Test that quoted values can contain special characters (semicolons, equals, spaces)
192
+ // TEST6: Test that quoted values can contain special characters (semicolons, equals, spaces)
192
193
  function test006_quotedValueSpecialChars() {
193
194
  const cap = CapUrn.fromString('cap:in="media:void";key="val;ue=with spaces";out="media:void"');
194
195
  assertEqual(cap.getTag('key'), 'val;ue=with spaces', 'Quoted value should preserve special chars');
195
196
  }
196
197
 
197
- // TEST007: Test that escape sequences in quoted values (\" and \\) are parsed correctly
198
+ // TEST7: Test that escape sequences in quoted values (\" and \\) are parsed correctly
198
199
  function test007_quotedValueEscapeSequences() {
199
200
  const s = String.raw`cap:in="media:void";key="val\"ue\\test";out="media:void"`;
200
201
  const cap = CapUrn.fromString(s);
201
202
  assertEqual(cap.getTag('key'), 'val"ue\\test', 'Escaped quote and backslash should be unescaped');
202
203
  }
203
204
 
204
- // TEST008: Test that mixed quoted and unquoted values in same URN parse correctly
205
+ // TEST8: Test that mixed quoted and unquoted values in same URN parse correctly
205
206
  function test008_mixedQuotedUnquoted() {
206
207
  const cap = CapUrn.fromString('cap:a=simple;b="Quoted";in="media:void";out="media:void"');
207
208
  assertEqual(cap.getTag('a'), 'simple', 'Unquoted value should be lowercase');
208
209
  assertEqual(cap.getTag('b'), 'Quoted', 'Quoted value should preserve case');
209
210
  }
210
211
 
211
- // TEST009: Test that unterminated quote produces UnterminatedQuote error
212
+ // TEST9: Test that unterminated quote produces UnterminatedQuote error
212
213
  function test009_unterminatedQuoteError() {
213
214
  let threw = false;
214
215
  try {
@@ -221,7 +222,7 @@ function test009_unterminatedQuoteError() {
221
222
  assert(threw, 'Unterminated quote should produce CapUrnError');
222
223
  }
223
224
 
224
- // TEST010: Test that invalid escape sequences (like \n, \x) produce InvalidEscapeSequence error
225
+ // TEST10: Test that invalid escape sequences (like \n, \x) produce InvalidEscapeSequence error
225
226
  function test010_invalidEscapeSequenceError() {
226
227
  let threw = false;
227
228
  try {
@@ -235,7 +236,7 @@ function test010_invalidEscapeSequenceError() {
235
236
  assert(threw, 'Invalid escape sequence should produce CapUrnError');
236
237
  }
237
238
 
238
- // TEST011: Test that serialization uses smart quoting (no quotes for simple lowercase, quotes for special chars/uppercase)
239
+ // TEST11: Test that serialization uses smart quoting (no quotes for simple lowercase, quotes for special chars/uppercase)
239
240
  function test011_serializationSmartQuoting() {
240
241
  const cap = CapUrn.fromString('cap:a=simple;b="Has Space";in="media:void";out="media:void"');
241
242
  const s = cap.toString();
@@ -244,15 +245,15 @@ function test011_serializationSmartQuoting() {
244
245
  assert(s.includes('b="Has Space"'), 'Value with space should be quoted');
245
246
  }
246
247
 
247
- // TEST012: Test that simple cap URN round-trips (parse -> serialize -> parse equals original)
248
+ // TEST12: Test that simple cap URN round-trips (parse -> serialize -> parse equals original)
248
249
  function test012_roundTripSimple() {
249
- const original = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
250
+ const original = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
250
251
  const serialized = original.toString();
251
252
  const reparsed = CapUrn.fromString(serialized);
252
253
  assert(original.equals(reparsed), 'Simple round-trip should produce equal URN');
253
254
  }
254
255
 
255
- // TEST013: Test that quoted values round-trip preserving case and spaces
256
+ // TEST13: Test that quoted values round-trip preserving case and spaces
256
257
  function test013_roundTripQuoted() {
257
258
  const original = CapUrn.fromString('cap:in="media:void";key="HelloWorld";out="media:void"');
258
259
  const serialized = original.toString();
@@ -261,7 +262,7 @@ function test013_roundTripQuoted() {
261
262
  assertEqual(reparsed.getTag('key'), 'HelloWorld', 'Quoted value should survive round-trip');
262
263
  }
263
264
 
264
- // TEST014: Test that escape sequences round-trip correctly
265
+ // TEST14: Test that escape sequences round-trip correctly
265
266
  function test014_roundTripEscapes() {
266
267
  const s = String.raw`cap:in="media:void";key="val\"ue\\test";out="media:void"`;
267
268
  const original = CapUrn.fromString(s);
@@ -271,7 +272,7 @@ function test014_roundTripEscapes() {
271
272
  assertEqual(reparsed.getTag('key'), 'val"ue\\test', 'Escaped value should survive round-trip');
272
273
  }
273
274
 
274
- // TEST015: Test that cap: prefix is required and case-insensitive
275
+ // TEST15: Test that cap: prefix is required and case-insensitive
275
276
  function test015_capPrefixRequired() {
276
277
  assertThrows(
277
278
  () => CapUrn.fromString('in="media:void";out="media:void";generate'),
@@ -279,28 +280,19 @@ function test015_capPrefixRequired() {
279
280
  'Should require cap: prefix'
280
281
  );
281
282
  // Valid cap: prefix should work
282
- const cap = CapUrn.fromString(test0051_Urn('generate'));
283
+ const cap = CapUrn.fromString(test6204_Urn('generate'));
283
284
  assert(cap.hasMarkerTag('generate'), 'Should parse with valid cap: prefix');
284
285
  }
285
286
 
286
- // TEST016: Test that trailing semicolon is equivalent (same hash, same string, matches)
287
+ // TEST16: Test that trailing semicolon is equivalent (same hash, same string, matches)
287
288
  function test016_trailingSemicolonEquivalence() {
288
- const cap1 = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
289
- const cap2 = CapUrn.fromString(test0051_Urn('generate;ext=pdf') + ';');
289
+ const cap1 = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
290
+ const cap2 = CapUrn.fromString(test6204_Urn('generate;ext=pdf') + ';');
290
291
  assert(cap1.equals(cap2), 'With/without trailing semicolon should be equal');
291
292
  assertEqual(cap1.toString(), cap2.toString(), 'Canonical forms should match');
292
293
  }
293
294
 
294
- // TEST939: The canonical form drops `in=media:` and `out=media:`
295
- // segments. Every spelling of "the same cap with wildcard in/out"
296
- // collapses to one byte-identical canonical string. This is the
297
- // contract that makes registry lookups work: the cap-publisher hashes
298
- // `<canonical-urn>` to compute the cache key, and every language port
299
- // (Rust, Go, Python, JS, ObjC) must agree on the canonical form for
300
- // cross-language lookups to land on the same key. A regression that
301
- // emitted the wildcard segments would silently move the published cap
302
- // to a different SHA-256 bucket, 404'ing every reader that hashes the
303
- // canonical form.
295
+ // TEST939: The canonical form drops `in=media:` and `out=media:` segments. Every spelling of "the same cap with wildcard in/out" collapses to one byte-identical canonical string. This is the contract that makes registry lookups work: the cap-publisher hashes `<canonical-urn>` to compute the cache key, and every language port (Rust, Go, Python, JS, ObjC) must agree on the canonical form for cross-language lookups to land on the same key. A regression that emitted the wildcard segments would silently move the published cap to a different SHA-256 bucket, 404'ing every reader that hashes the canonical form.
304
296
  function test939_capUrnCanonicalFormDropsWildcardInOut() {
305
297
  const canonical = 'cap:decimate-sequence';
306
298
  const variants = [
@@ -328,46 +320,46 @@ function test939_capUrnCanonicalFormDropsWildcardInOut() {
328
320
  assert(identity.toString() !== 'cap:', 'cap:effect=none must not collapse to the illegal bare top form');
329
321
  }
330
322
 
331
- // TEST017: Test tag matching: exact match, subset match, wildcard match, value mismatch
323
+ // TEST17: Test tag matching: exact match, subset match, wildcard match, value mismatch
332
324
  function test017_tagMatching() {
333
- const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf;target=thumbnail'));
325
+ const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf;target=thumbnail'));
334
326
 
335
327
  // Exact match — both directions accept
336
- const exact = CapUrn.fromString(test0051_Urn('generate;ext=pdf;target=thumbnail'));
328
+ const exact = CapUrn.fromString(test6204_Urn('generate;ext=pdf;target=thumbnail'));
337
329
  assert(cap.accepts(exact), 'Should accept exact match');
338
330
  assert(exact.accepts(cap), 'Exact match should accept in reverse too');
339
331
 
340
332
  // Routing direction: request(generate) accepts cap(op,ext,target)
341
- const subset = CapUrn.fromString(test0051_Urn('generate'));
333
+ const subset = CapUrn.fromString(test6204_Urn('generate'));
342
334
  assert(subset.accepts(cap), 'General request should accept more specific instance');
343
335
  assert(!cap.accepts(subset), 'Specific pattern should reject subset instance');
344
336
 
345
337
  // Routing direction: request(ext=*) accepts cap(ext=pdf)
346
- const wildcard = CapUrn.fromString(test0051_Urn('ext=*'));
338
+ const wildcard = CapUrn.fromString(test6204_Urn('ext=*'));
347
339
  assert(wildcard.accepts(cap), 'Wildcard request should accept specific instance');
348
340
 
349
341
  // Conflicting value — neither direction accepts
350
- const mismatch = CapUrn.fromString(test0051_Urn('extract'));
342
+ const mismatch = CapUrn.fromString(test6204_Urn('extract'));
351
343
  assert(!cap.accepts(mismatch), 'Should not accept value mismatch');
352
344
  assert(!mismatch.accepts(cap), 'Reverse mismatch should also reject');
353
345
  }
354
346
 
355
- // TEST018: Test that quoted values with different case do NOT match (case-sensitive)
347
+ // TEST18: Test that quoted values with different case do NOT match (case-sensitive)
356
348
  function test018_matchingCaseSensitiveValues() {
357
349
  const cap = CapUrn.fromString('cap:in="media:void";key="HelloWorld";out="media:void"');
358
350
  const request = CapUrn.fromString('cap:in="media:void";key=helloworld;out="media:void"');
359
351
  assert(!cap.accepts(request), 'Quoted HelloWorld should not match unquoted helloworld');
360
352
  }
361
353
 
362
- // TEST019: Missing tag in instance causes rejection — pattern's tags are constraints
354
+ // TEST19: Missing tag in instance causes rejection — pattern's tags are constraints
363
355
  function test019_missingTagHandling() {
364
- const cap = CapUrn.fromString(test0051_Urn('generate'));
365
- const request = CapUrn.fromString(test0051_Urn('ext=pdf'));
356
+ const cap = CapUrn.fromString(test6204_Urn('generate'));
357
+ const request = CapUrn.fromString(test6204_Urn('ext=pdf'));
366
358
  assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
367
359
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
368
360
 
369
- const cap2 = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
370
- const request2 = CapUrn.fromString(test0051_Urn('generate'));
361
+ const cap2 = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
362
+ const request2 = CapUrn.fromString(test6204_Urn('generate'));
371
363
  assert(!cap2.accepts(request2), 'Specific pattern should reject instance missing ext');
372
364
  assert(request2.accepts(cap2), 'General request should accept more specific instance');
373
365
  }
@@ -377,27 +369,27 @@ function test019_missingTagHandling() {
377
369
  // (must-have-any), exact `key=value` tags score 3, missing/`?` score
378
370
  // 0, `!` scores 1.
379
371
  //
380
- // test0051_Urn() builds "cap:in=media:void;out=media:record;<tags>" so
372
+ // test6204_Urn() builds "cap:in=media:void;out=media:record;<tags>" so
381
373
  // the directional baseline is:
382
374
  // in: media:void -> {void=*} -> 2
383
375
  // out: media:record -> {record=*} -> 2
384
376
  // Total directional baseline: 4.
385
377
  function test020_specificity() {
386
- // test0051_Urn() prepends in="media:void" (1 marker, score 2) and
378
+ // test6204_Urn() prepends in="media:void" (1 marker, score 2) and
387
379
  // out="media:record" (1 marker, score 2). Cap-URN spec is
388
380
  // 10000*spec_U(out) + 100*spec_U(in) + spec_U(y).
389
381
 
390
- const cap1 = CapUrn.fromString(test0051_Urn('type=general'));
382
+ const cap1 = CapUrn.fromString(test6204_Urn('type=general'));
391
383
  // out=2, in=2, y=4 (type=general exact)
392
384
  assertEqual(cap1.specificity(), 10000*2 + 100*2 + 4,
393
385
  'out=2, in=2, y=type=general exact=4 -> 20204');
394
386
 
395
- const cap2 = CapUrn.fromString(test0051_Urn('generate'));
387
+ const cap2 = CapUrn.fromString(test6204_Urn('generate'));
396
388
  // out=2, in=2, y=2 (generate marker = must-have-any)
397
389
  assertEqual(cap2.specificity(), 10000*2 + 100*2 + 2,
398
390
  'out=2, in=2, y=generate marker=2 -> 20202');
399
391
 
400
- const cap3 = CapUrn.fromString(test0051_Urn('op;ext=pdf'));
392
+ const cap3 = CapUrn.fromString(test6204_Urn('op;ext=pdf'));
401
393
  // out=2, in=2, y=2+4 (op marker, ext=pdf exact)
402
394
  assertEqual(cap3.specificity(), 10000*2 + 100*2 + 6,
403
395
  'out=2, in=2, y=op marker(2)+ext=pdf exact(4) -> 20206');
@@ -409,8 +401,7 @@ function test020_specificity() {
409
401
  'out=record=2, in=*->0, y=test marker=2 -> 20002');
410
402
  }
411
403
 
412
- // TEST021: Test builder creates cap URN with marker + keyed tags and direction specs.
413
- // `op` is no longer a special key — operation names are markers (value-less tags).
404
+ // TEST21: Test builder creates cap URN with correct tags and direction specs
414
405
  function test021_builder() {
415
406
  const cap = new CapUrnBuilder()
416
407
  .inSpec('media:void')
@@ -424,7 +415,7 @@ function test021_builder() {
424
415
  assertEqual(cap.getOutSpec(), 'media:object', 'Builder should set outSpec');
425
416
  }
426
417
 
427
- // TEST022: Test builder requires both in_spec and out_spec
418
+ // TEST22: Test builder requires both in_spec and out_spec
428
419
  function test022_builderRequiresDirection() {
429
420
  assertThrows(
430
421
  () => new CapUrnBuilder().tag('op', 'test').build(),
@@ -438,7 +429,7 @@ function test022_builderRequiresDirection() {
438
429
  );
439
430
  }
440
431
 
441
- // TEST023: Test builder lowercases keys but preserves value case
432
+ // TEST23: Test builder lowercases keys but preserves value case
442
433
  function test023_builderPreservesCase() {
443
434
  const cap = new CapUrnBuilder()
444
435
  .inSpec('media:void')
@@ -449,18 +440,18 @@ function test023_builderPreservesCase() {
449
440
  assertEqual(cap.getTag('MyKey'), 'MyValue', 'getTag should be case-insensitive for keys');
450
441
  }
451
442
 
452
- // TEST024: Directional accepts — pattern's tags are constraints, instance must satisfy
443
+ // TEST24: Directional accepts — pattern's tags are constraints, instance must satisfy
453
444
  function test024_compatibility() {
454
- const cap1 = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
455
- const cap2 = CapUrn.fromString(test0051_Urn('generate;format=*'));
456
- const cap3 = CapUrn.fromString(test0051_Urn('type=image;extract'));
445
+ const cap1 = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
446
+ const cap2 = CapUrn.fromString(test6204_Urn('generate;format=*'));
447
+ const cap3 = CapUrn.fromString(test6204_Urn('type=image;extract'));
457
448
 
458
449
  assert(!cap1.accepts(cap2), 'Pattern requiring ext should reject instance missing ext');
459
450
  assert(!cap2.accepts(cap1), 'Pattern requiring format should reject instance missing format');
460
451
  assert(!cap1.accepts(cap3), 'Different op should not accept');
461
452
  assert(!cap3.accepts(cap1), 'Different op should not accept in reverse');
462
453
 
463
- const general = CapUrn.fromString(test0051_Urn('generate'));
454
+ const general = CapUrn.fromString(test6204_Urn('generate'));
464
455
  assert(general.accepts(cap1), 'General request should accept more specific instance');
465
456
  assert(!cap1.accepts(general), 'Specific pattern should reject general instance');
466
457
 
@@ -469,22 +460,22 @@ function test024_compatibility() {
469
460
  assert(broadIn.accepts(cap1), 'Wildcard input should accept specific input');
470
461
  }
471
462
 
472
- // TEST025: Test find_best_match returns most specific matching cap
463
+ // TEST25: Test find_best_match returns most specific matching cap
473
464
  function test025_bestMatch() {
474
465
  const caps = [
475
466
  CapUrn.fromString('cap:in=*;out=*;op'),
476
- CapUrn.fromString(test0051_Urn('generate')),
477
- CapUrn.fromString(test0051_Urn('generate;ext=pdf'))
467
+ CapUrn.fromString(test6204_Urn('generate')),
468
+ CapUrn.fromString(test6204_Urn('generate;ext=pdf'))
478
469
  ];
479
- const request = CapUrn.fromString(test0051_Urn('generate'));
470
+ const request = CapUrn.fromString(test6204_Urn('generate'));
480
471
  const best = CapMatcher.findBestMatch(caps, request);
481
472
  assert(best !== null, 'Should find a best match');
482
473
  assertEqual(best.getTag('ext'), 'pdf', 'Best match should be the most specific (ext=pdf)');
483
474
  }
484
475
 
485
- // TEST026: Test merge combines tags from both caps, subset keeps only specified tags
476
+ // TEST26: Test merge combines tags from both caps, subset keeps only specified tags
486
477
  function test026_mergeAndSubset() {
487
- const cap1 = CapUrn.fromString(test0051_Urn('generate'));
478
+ const cap1 = CapUrn.fromString(test6204_Urn('generate'));
488
479
  const cap2 = CapUrn.fromString('cap:in="media:enc=utf-8";ext=pdf;format=binary;out="media:"');
489
480
 
490
481
  // Merge (other takes precedence)
@@ -501,9 +492,9 @@ function test026_mergeAndSubset() {
501
492
  assertEqual(sub.getInSpec(), 'media:enc=utf-8', 'Subset should preserve inSpec');
502
493
  }
503
494
 
504
- // TEST027: Test with_wildcard_tag sets tag to wildcard, including in/out
495
+ // TEST27: Test with_wildcard_tag sets tag to wildcard, including in/out
505
496
  function test027_wildcardTag() {
506
- const cap = CapUrn.fromString(test0051_Urn('ext=pdf'));
497
+ const cap = CapUrn.fromString(test6204_Urn('ext=pdf'));
507
498
  const wildcardExt = cap.withWildcardTag('ext');
508
499
  assertEqual(wildcardExt.getTag('ext'), '*', 'Should set ext to wildcard');
509
500
 
@@ -514,7 +505,7 @@ function test027_wildcardTag() {
514
505
  assertEqual(wildcardOut.getOutSpec(), 'media:', 'Should set out to canonical top media:');
515
506
  }
516
507
 
517
- // TEST028: Test empty cap URN is illegal
508
+ // TEST28: Test empty cap URN is illegal after effect transition
518
509
  function test028_emptyCapUrnNotAllowed() {
519
510
  assertThrows(
520
511
  () => CapUrn.fromString('cap:'),
@@ -523,7 +514,7 @@ function test028_emptyCapUrnNotAllowed() {
523
514
  );
524
515
  }
525
516
 
526
- // TEST029: Test minimal valid cap URN has just in and out, empty tags
517
+ // TEST29: Test minimal valid cap URN has just in and out, empty tags
527
518
  function test029_minimalCapUrn() {
528
519
  const minimal = CapUrn.fromString('cap:in="media:void";out="media:void"');
529
520
  assertEqual(Object.keys(minimal.tags).length, 0, 'Should have no other tags');
@@ -531,23 +522,23 @@ function test029_minimalCapUrn() {
531
522
  assertEqual(minimal.getOutSpec(), 'media:void', 'Should have outSpec');
532
523
  }
533
524
 
534
- // TEST030: Test extended characters (forward slashes, colons) in tag values
525
+ // TEST30: Test extended characters (forward slashes, colons) in tag values
535
526
  function test030_extendedCharacterSupport() {
536
- const cap = CapUrn.fromString(test0051_Urn('url=https://example_org/api;path=/some/file'));
527
+ const cap = CapUrn.fromString(test6204_Urn('url=https://example_org/api;path=/some/file'));
537
528
  assertEqual(cap.getTag('url'), 'https://example_org/api', 'Should support colons and slashes');
538
529
  assertEqual(cap.getTag('path'), '/some/file', 'Should support forward slashes');
539
530
  }
540
531
 
541
- // TEST031: Test wildcard rejected in keys but accepted in values
532
+ // TEST31: Test wildcard rejected in keys but accepted in values
542
533
  function test031_wildcardRestrictions() {
543
534
  assertThrows(
544
- () => CapUrn.fromString(test0051_Urn('*=value')),
535
+ () => CapUrn.fromString(test6204_Urn('*=value')),
545
536
  ErrorCodes.INVALID_CHARACTER,
546
537
  'Should reject wildcard in key'
547
538
  );
548
539
 
549
540
  // Wildcard accepted in values
550
- const cap = CapUrn.fromString(test0051_Urn('key=*'));
541
+ const cap = CapUrn.fromString(test6204_Urn('key=*'));
551
542
  assertEqual(cap.getTag('key'), '*', 'Should accept wildcard in value');
552
543
 
553
544
  // Wildcard in in/out normalizes to media:
@@ -556,30 +547,30 @@ function test031_wildcardRestrictions() {
556
547
  assertEqual(capWild.getOutSpec(), MEDIA_IDENTITY, 'Wildcard outSpec should normalize to media:');
557
548
  }
558
549
 
559
- // TEST032: Test duplicate keys are rejected with DuplicateKey error
550
+ // TEST32: Test duplicate keys are rejected with DuplicateKey error
560
551
  function test032_duplicateKeyRejection() {
561
552
  assertThrows(
562
- () => CapUrn.fromString(test0051_Urn('key=value1;key=value2')),
553
+ () => CapUrn.fromString(test6204_Urn('key=value1;key=value2')),
563
554
  ErrorCodes.DUPLICATE_KEY,
564
555
  'Should reject duplicate keys'
565
556
  );
566
557
  }
567
558
 
568
- // TEST033: Test pure numeric keys rejected, mixed alphanumeric allowed, numeric values allowed
559
+ // TEST33: Test pure numeric keys rejected, mixed alphanumeric allowed, numeric values allowed
569
560
  function test033_numericKeyRestriction() {
570
561
  assertThrows(
571
- () => CapUrn.fromString(test0051_Urn('123=value')),
562
+ () => CapUrn.fromString(test6204_Urn('123=value')),
572
563
  ErrorCodes.NUMERIC_KEY,
573
564
  'Should reject pure numeric keys'
574
565
  );
575
566
  // Mixed alphanumeric allowed
576
- const cap1 = CapUrn.fromString(test0051_Urn('key123=value'));
567
+ const cap1 = CapUrn.fromString(test6204_Urn('key123=value'));
577
568
  assertEqual(cap1.getTag('key123'), 'value', 'Mixed alphanumeric key should be allowed');
578
- const cap2 = CapUrn.fromString(test0051_Urn('x123key=value'));
569
+ const cap2 = CapUrn.fromString(test6204_Urn('x123key=value'));
579
570
  assertEqual(cap2.getTag('x123key'), 'value', 'Mixed alphanumeric key should be allowed');
580
571
  }
581
572
 
582
- // TEST034: Test empty values are rejected
573
+ // TEST34: Test empty values are rejected
583
574
  function test034_emptyValueError() {
584
575
  let threw = false;
585
576
  try {
@@ -592,7 +583,7 @@ function test034_emptyValueError() {
592
583
  assert(threw, 'Empty value (key=) should be rejected');
593
584
  }
594
585
 
595
- // TEST035: Test has_tag is case-sensitive for values, case-insensitive for keys, works for in/out
586
+ // TEST35: Test has_tag is case-sensitive for values, case-insensitive for keys, works for in/out
596
587
  function test035_hasTagCaseSensitive() {
597
588
  const cap = CapUrn.fromString('cap:in="media:void";key="Value";out="media:void"');
598
589
  assert(cap.hasTag('key', 'Value'), 'hasTag should match exact value');
@@ -604,14 +595,14 @@ function test035_hasTagCaseSensitive() {
604
595
  assert(cap.hasTag('out', 'media:void'), 'hasTag should work for out');
605
596
  }
606
597
 
607
- // TEST036: Test with_tag preserves value case
598
+ // TEST36: Test with_tag preserves value case
608
599
  function test036_withTagPreservesValue() {
609
600
  const cap = CapUrn.fromString('cap:in="media:void";out="media:void"');
610
601
  const modified = cap.withTag('key', 'MyValue');
611
602
  assertEqual(modified.getTag('key'), 'MyValue', 'withTag should preserve value case');
612
603
  }
613
604
 
614
- // TEST037: Test with_tag rejects empty value
605
+ // TEST37: Test with_tag rejects empty value
615
606
  function test037_withTagRejectsEmptyValue() {
616
607
  const cap = CapUrn.fromString('cap:in="media:void";out="media:void"');
617
608
  assertThrows(
@@ -621,7 +612,7 @@ function test037_withTagRejectsEmptyValue() {
621
612
  );
622
613
  }
623
614
 
624
- // TEST038: Test semantic equivalence of unquoted and quoted simple lowercase values
615
+ // TEST38: Test semantic equivalence of unquoted and quoted simple lowercase values
625
616
  function test038_semanticEquivalence() {
626
617
  const c1 = CapUrn.fromString('cap:in="media:void";key=simple;out="media:void"');
627
618
  const c2 = CapUrn.fromString('cap:in="media:void";key="simple";out="media:void"');
@@ -629,7 +620,7 @@ function test038_semanticEquivalence() {
629
620
  assertEqual(c1.getTag('key'), c2.getTag('key'), 'Values should be identical');
630
621
  }
631
622
 
632
- // TEST039: Test get_tag returns direction specs (in/out) with case-insensitive lookup
623
+ // TEST39: Test get_tag returns direction specs (in/out) with case-insensitive lookup
633
624
  function test039_getTagReturnsDirectionSpecs() {
634
625
  const cap = CapUrn.fromString(`cap:in="${MEDIA_VOID}";out="${MEDIA_OBJECT}"`);
635
626
  assertEqual(cap.getTag('in'), MEDIA_VOID, 'getTag(in) should return inSpec');
@@ -638,51 +629,51 @@ function test039_getTagReturnsDirectionSpecs() {
638
629
  assertEqual(cap.getTag('OUT'), MEDIA_OBJECT, 'getTag(OUT) should return outSpec (case-insensitive)');
639
630
  }
640
631
 
641
- // TEST040: Matching semantics - exact match succeeds
632
+ // TEST40: Matching semantics - exact match succeeds
642
633
  function test040_matchingSemanticsExactMatch() {
643
- const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
644
- const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
634
+ const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
635
+ const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
645
636
  assert(cap.accepts(request), 'Exact match should accept');
646
637
  }
647
638
 
648
- // TEST041: Matching semantics - cap missing tag matches (implicit wildcard)
639
+ // TEST41: Matching semantics - cap missing tag matches (implicit wildcard)
649
640
  function test041_matchingSemanticsCapMissingTag() {
650
- const cap = CapUrn.fromString(test0051_Urn('generate'));
651
- const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
641
+ const cap = CapUrn.fromString(test6204_Urn('generate'));
642
+ const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
652
643
  assert(cap.accepts(request), 'General pattern with only op should accept specific instance');
653
644
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
654
645
  }
655
646
 
656
- // TEST042: Pattern rejects instance missing required tags
647
+ // TEST42: Pattern rejects instance missing required tags
657
648
  function test042_matchingSemanticsCapHasExtraTag() {
658
- const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf;version=2'));
659
- const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
649
+ const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf;version=2'));
650
+ const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
660
651
  assert(!cap.accepts(request), 'Pattern requiring version should reject instance missing version');
661
652
  assert(request.accepts(cap), 'General request should accept refined instance');
662
653
  }
663
654
 
664
- // TEST043: Matching semantics - request wildcard matches specific cap value
655
+ // TEST43: Matching semantics - request wildcard matches specific cap value
665
656
  function test043_matchingSemanticsRequestHasWildcard() {
666
- const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
667
- const request = CapUrn.fromString(test0051_Urn('generate;ext=*'));
657
+ const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
658
+ const request = CapUrn.fromString(test6204_Urn('generate;ext=*'));
668
659
  assert(cap.accepts(request), 'Request wildcard should match specific cap value');
669
660
  }
670
661
 
671
- // TEST044: Matching semantics - cap wildcard matches specific request value
662
+ // TEST44: Matching semantics - cap wildcard matches specific request value
672
663
  function test044_matchingSemanticsCapHasWildcard() {
673
- const cap = CapUrn.fromString(test0051_Urn('generate;ext=*'));
674
- const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
664
+ const cap = CapUrn.fromString(test6204_Urn('generate;ext=*'));
665
+ const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
675
666
  assert(cap.accepts(request), 'Cap wildcard should match specific request value');
676
667
  }
677
668
 
678
- // TEST045: Matching semantics - value mismatch does not match
669
+ // TEST45: Matching semantics - value mismatch does not match
679
670
  function test045_matchingSemanticsValueMismatch() {
680
- const cap = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
681
- const request = CapUrn.fromString(test0051_Urn('generate;ext=docx'));
671
+ const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
672
+ const request = CapUrn.fromString(test6204_Urn('generate;ext=docx'));
682
673
  assert(!cap.accepts(request), 'Value mismatch should not accept');
683
674
  }
684
675
 
685
- // TEST046: Matching semantics - fallback pattern (cap missing tag = implicit wildcard)
676
+ // TEST46: Matching semantics - fallback pattern (cap missing tag = implicit wildcard)
686
677
  function test046_matchingSemanticsFallbackPattern() {
687
678
  const cap = CapUrn.fromString('cap:in="media:binary";generate-thumbnail;out="media:binary"');
688
679
  const request = CapUrn.fromString('cap:ext=wav;in="media:binary";generate-thumbnail;out="media:binary"');
@@ -690,29 +681,29 @@ function test046_matchingSemanticsFallbackPattern() {
690
681
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
691
682
  }
692
683
 
693
- // TEST047: Matching semantics - thumbnail fallback with void input
684
+ // TEST47: Matching semantics - thumbnail fallback with void input
694
685
  function test047_matchingSemanticsThumbnailVoidInput() {
695
686
  const cap = CapUrn.fromString('cap:in="media:void";generate-thumbnail;out="media:ext=png;image;thumbnail"');
696
687
  const request = CapUrn.fromString('cap:ext=pdf;in="media:void";generate-thumbnail;out="media:image"');
697
688
  assert(cap.accepts(request), 'Void input cap should accept request; cap output conforms to less-specific request output');
698
689
  }
699
690
 
700
- // TEST048: Matching semantics - wildcard direction matches anything
691
+ // TEST48: Matching semantics - wildcard direction matches anything
701
692
  function test048_matchingSemanticsWildcardDirection() {
702
693
  const cap = CapUrn.fromString('cap:generate');
703
- const request = CapUrn.fromString(test0051_Urn('generate;ext=pdf'));
694
+ const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
704
695
  assert(cap.accepts(request), 'Generic declared directions should accept a more specific matching request');
705
696
  }
706
697
 
707
- // TEST049: Non-overlapping tags — neither direction accepts
698
+ // TEST49: Non-overlapping tags — neither direction accepts
708
699
  function test049_matchingSemanticsCrossDimension() {
709
- const cap = CapUrn.fromString(test0051_Urn('generate'));
710
- const request = CapUrn.fromString(test0051_Urn('ext=pdf'));
700
+ const cap = CapUrn.fromString(test6204_Urn('generate'));
701
+ const request = CapUrn.fromString(test6204_Urn('ext=pdf'));
711
702
  assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
712
703
  assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
713
704
  }
714
705
 
715
- // TEST050: Matching semantics - direction mismatch prevents matching
706
+ // TEST50: Matching semantics - direction mismatch prevents matching
716
707
  function test050_matchingSemanticsDirectionMismatch() {
717
708
  const cap = CapUrn.fromString(
718
709
  `cap:in="${MEDIA_STRING}";generate;out="${MEDIA_OBJECT}"`
@@ -773,10 +764,7 @@ function test890_directionSemanticMatching() {
773
764
  'Generic output cap must NOT satisfy specific output request');
774
765
  }
775
766
 
776
- // TEST891: Semantic direction specificity — more constraints in
777
- // either axis means a higher score under the truth-table-driven sum.
778
- // media: (top, no tags) scores 0; each marker tag scores 2; each
779
- // exact-value tag (e.g. ext=png) scores 4.
767
+ // TEST891: Semantic direction specificity — more constraints in either axis means a higher score under the truth-table-driven sum. media: (top, no tags) scores 0; each marker tag scores 2; each exact tag scores 3.
780
768
  function test891_directionSemanticSpecificity() {
781
769
  const genericCap = CapUrn.fromString(
782
770
  'cap:in="media:";generate-thumbnail;out="media:ext=png;image;thumbnail"'
@@ -814,10 +802,10 @@ function test891_directionSemanticSpecificity() {
814
802
  // validation.rs: TEST053-TEST056
815
803
  // ============================================================================
816
804
 
817
- // TEST053: N/A for JS (Rust-only validation infrastructure)
805
+ // TEST6208: N/A for JS (Rust-only validation infrastructure)
818
806
 
819
- // TEST054: XV5 - Test inline media def redefinition of existing registry spec is detected and rejected
820
- function test054_xv5InlineSpecRedefinitionDetected() {
807
+ // TEST6212: XV5 - Test inline media def redefinition of existing registry spec is detected and rejected
808
+ function test6212_xv5InlineSpecRedefinitionDetected() {
821
809
  const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
822
810
  const mediaDefs = [
823
811
  {
@@ -833,8 +821,8 @@ function test054_xv5InlineSpecRedefinitionDetected() {
833
821
  assert(result.redefines && result.redefines.includes(MEDIA_STRING), 'Should identify MEDIA_STRING as redefined');
834
822
  }
835
823
 
836
- // TEST055: XV5 - Test new inline media def (not in registry) is allowed
837
- function test055_xv5NewInlineSpecAllowed() {
824
+ // TEST6216: XV5 - Test new inline media def (not in registry) is allowed
825
+ function test6216_xv5NewInlineSpecAllowed() {
838
826
  const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
839
827
  const mediaDefs = [
840
828
  {
@@ -848,8 +836,8 @@ function test055_xv5NewInlineSpecAllowed() {
848
836
  assert(result.valid, 'New spec not in registry should pass validation');
849
837
  }
850
838
 
851
- // TEST056: XV5 - Test empty media_defs (no inline specs) passes XV5 validation
852
- function test056_xv5EmptyMediaDefsAllowed() {
839
+ // TEST6220: XV5 - Test empty media_defs (no inline specs) passes XV5 validation
840
+ function test6220_xv5EmptyMediaDefsAllowed() {
853
841
  const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
854
842
  assert(validateNoMediaDefRedefinitionSync({}, registryLookup).valid, 'Empty object should pass');
855
843
  assert(validateNoMediaDefRedefinitionSync(null, registryLookup).valid, 'Null should pass');
@@ -860,7 +848,7 @@ function test056_xv5EmptyMediaDefsAllowed() {
860
848
  // media_urn.rs: TEST060-TEST078
861
849
  // ============================================================================
862
850
 
863
- // TEST060: Test wrong prefix fails with InvalidPrefix error showing expected and actual prefix
851
+ // TEST60: Test wrong prefix fails with InvalidPrefix error showing expected and actual prefix
864
852
  function test060_wrongPrefixFails() {
865
853
  assertThrowsMediaUrn(
866
854
  () => MediaUrn.fromString('cap:string'),
@@ -873,7 +861,7 @@ function test060_wrongPrefixFails() {
873
861
  // vocabulary (isBinary() was deleted from MediaUrn; everything is bytes).
874
862
  // Encoding is now expressed by the orthogonal `enc=` tag, exercised by TEST067.
875
863
 
876
- // TEST062: Test is_record returns true when record marker tag is present indicating key-value structure
864
+ // TEST62: Test is_record returns true when record marker tag is present indicating key-value structure
877
865
  function test062_isRecord() {
878
866
  assert(MediaUrn.fromString(MEDIA_OBJECT).isRecord(), 'MEDIA_OBJECT should be record');
879
867
  assert(MediaUrn.fromString('media:custom;record').isRecord(), 'custom;record should be record');
@@ -884,7 +872,7 @@ function test062_isRecord() {
884
872
  assert(!MediaUrn.fromString(MEDIA_STRING_LIST).isRecord(), 'MEDIA_STRING_LIST should not be record');
885
873
  }
886
874
 
887
- // TEST063: Test is_scalar returns true when list marker tag is absent (scalar is default)
875
+ // TEST63: Test is_scalar returns true when list marker tag is absent (scalar is default)
888
876
  function test063_isScalar() {
889
877
  assert(MediaUrn.fromString(MEDIA_STRING).isScalar(), 'MEDIA_STRING should be scalar');
890
878
  assert(MediaUrn.fromString(MEDIA_INTEGER).isScalar(), 'MEDIA_INTEGER should be scalar');
@@ -897,7 +885,7 @@ function test063_isScalar() {
897
885
  assert(!MediaUrn.fromString(MEDIA_OBJECT_LIST).isScalar(), 'MEDIA_OBJECT_LIST should not be scalar');
898
886
  }
899
887
 
900
- // TEST064: Test is_list returns true when list marker tag is present indicating ordered collection
888
+ // TEST64: Test is_list returns true when list marker tag is present indicating ordered collection
901
889
  function test064_isList() {
902
890
  assert(MediaUrn.fromString(MEDIA_STRING_LIST).isList(), 'MEDIA_STRING_LIST should be list');
903
891
  assert(MediaUrn.fromString(MEDIA_INTEGER_LIST).isList(), 'MEDIA_INTEGER_LIST should be list');
@@ -906,7 +894,7 @@ function test064_isList() {
906
894
  assert(!MediaUrn.fromString(MEDIA_OBJECT).isList(), 'MEDIA_OBJECT should not be list');
907
895
  }
908
896
 
909
- // TEST065: Test is_opaque returns true when record marker is absent (opaque is default)
897
+ // TEST65: Test is_opaque returns true when record marker is absent (opaque is default)
910
898
  function test065_isOpaque() {
911
899
  assert(MediaUrn.fromString(MEDIA_STRING).isOpaque(), 'MEDIA_STRING should be opaque');
912
900
  assert(MediaUrn.fromString(MEDIA_STRING_LIST).isOpaque(), 'MEDIA_STRING_LIST (list but no record) should be opaque');
@@ -917,7 +905,7 @@ function test065_isOpaque() {
917
905
  assert(!MediaUrn.fromString(MEDIA_JSON).isOpaque(), 'MEDIA_JSON should not be opaque');
918
906
  }
919
907
 
920
- // TEST066: Test is_json returns true only when fmt=json content-format tag is present
908
+ // TEST66: Test is_json returns true only when json marker tag is present for JSON representation
921
909
  function test066_isJson() {
922
910
  assert(MediaUrn.fromString(MEDIA_JSON).isJson(), 'MEDIA_JSON should be json');
923
911
  assert(MediaUrn.fromString('media:custom;fmt=json').isJson(), 'fmt=json should be json');
@@ -926,9 +914,7 @@ function test066_isJson() {
926
914
  assert(!MediaUrn.fromString('media:enc=utf-8').isJson(), 'plain text should not be json');
927
915
  }
928
916
 
929
- // TEST067: Text-representability is now carried by the orthogonal `enc=` tag
930
- // (the old text marker and isText() are gone). A media is "text" iff it
931
- // declares an encoding.
917
+ // TEST67: Text-representability is now carried by the orthogonal `enc=` tag (the old `textable` marker and is_text() are gone). A media is "text" iff it declares an encoding. enc is orthogonal to format/numeric, so only media that actually carry enc= are text.
932
918
  function test067_isText() {
933
919
  // Has enc= → text-representable
934
920
  assert(MediaUrn.fromString(MEDIA_STRING).getTag('enc') !== undefined, 'MEDIA_STRING should have enc');
@@ -941,15 +927,15 @@ function test067_isText() {
941
927
  assert(MediaUrn.fromString(MEDIA_OBJECT).getTag('enc') === undefined, 'MEDIA_OBJECT should not have enc');
942
928
  }
943
929
 
944
- // TEST068: Test is_void returns true when void flag or type=void tag is present
930
+ // TEST68: Test is_void returns true when void flag or type=void tag is present
945
931
  function test068_isVoid() {
946
932
  assert(MediaUrn.fromString('media:void').isVoid(), 'media:void should be void');
947
933
  assert(!MediaUrn.fromString(MEDIA_STRING).isVoid(), 'MEDIA_STRING should not be void');
948
934
  }
949
935
 
950
- // TEST069-TEST070: N/A for JS (Rust-only binary_media_urn_for_ext/text_media_urn_for_ext)
936
+ // TEST069-TEST6240: N/A for JS (Rust-only binary_media_urn_for_ext/text_media_urn_for_ext)
951
937
 
952
- // TEST071: Test to_string roundtrip ensures serialization and deserialization preserve URN structure
938
+ // TEST71: Test to_string roundtrip ensures serialization and deserialization preserve URN structure
953
939
  function test071_toStringRoundtrip() {
954
940
  const constants = [MEDIA_STRING, MEDIA_INTEGER, MEDIA_OBJECT, MEDIA_IDENTITY, MEDIA_PDF, MEDIA_JSON];
955
941
  for (const constant of constants) {
@@ -959,7 +945,7 @@ function test071_toStringRoundtrip() {
959
945
  }
960
946
  }
961
947
 
962
- // TEST072: Test all media URN constants parse successfully as valid media URNs
948
+ // TEST72: Test all media URN constants parse successfully as valid media URNs
963
949
  function test072_constantsParse() {
964
950
  const constants = [
965
951
  MEDIA_STRING, MEDIA_INTEGER, MEDIA_NUMBER, MEDIA_BOOLEAN,
@@ -977,9 +963,9 @@ function test072_constantsParse() {
977
963
  }
978
964
  }
979
965
 
980
- // TEST073: N/A for JS (Rust has binary_media_urn_for_ext/text_media_urn_for_ext)
966
+ // TEST6242: N/A for JS (Rust has binary_media_urn_for_ext/text_media_urn_for_ext)
981
967
 
982
- // TEST074: Test media URN conforms_to using tagged URN semantics with specific and generic requirements
968
+ // TEST74: Test media URN conforms_to using tagged URN semantics with specific and generic requirements
983
969
  function test074_mediaUrnMatching() {
984
970
  const pdfUrn = MediaUrn.fromString(MEDIA_PDF);
985
971
  const pdfPattern = MediaUrn.fromString('media:ext=pdf');
@@ -993,7 +979,7 @@ function test074_mediaUrnMatching() {
993
979
  assert(pdfUrn.conformsTo(pdfUrn), 'Same URN should conform to itself');
994
980
  }
995
981
 
996
- // TEST075: Test accepts with implicit wildcards where handlers with fewer tags can handle more requests
982
+ // TEST75: Test accepts with implicit wildcards where handlers with fewer tags can handle more requests
997
983
  function test075_accepts() {
998
984
  const handler = MediaUrn.fromString(MEDIA_PDF);
999
985
  const sameReq = MediaUrn.fromString(MEDIA_PDF);
@@ -1004,7 +990,7 @@ function test075_accepts() {
1004
990
  assert(generalHandler.accepts(specificReq), 'General handler should accept specific request');
1005
991
  }
1006
992
 
1007
- // TEST076: Test specificity increases with more tags for ranking conformance
993
+ // TEST76: Test specificity increases with more tags for ranking conformance
1008
994
  function test076_specificity() {
1009
995
  const s1 = MediaUrn.fromString('media:');
1010
996
  const s2 = MediaUrn.fromString('media:ext=pdf');
@@ -1013,7 +999,7 @@ function test076_specificity() {
1013
999
  assert(s3.specificity() > s2.specificity(), 'image;png;thumbnail should be more specific than pdf');
1014
1000
  }
1015
1001
 
1016
- // TEST077: Test serde roundtrip serializes to JSON string and deserializes back correctly
1002
+ // TEST77: Test serde roundtrip serializes to JSON string and deserializes back correctly
1017
1003
  function test077_serdeRoundtrip() {
1018
1004
  const original = MediaUrn.fromString(MEDIA_PDF);
1019
1005
  const json = JSON.stringify({ urn: original.toString() });
@@ -1022,7 +1008,7 @@ function test077_serdeRoundtrip() {
1022
1008
  assert(original.equals(restored), 'JSON round-trip should preserve MediaUrn');
1023
1009
  }
1024
1010
 
1025
- // TEST078: conforms_to behavior between MEDIA_OBJECT and MEDIA_STRING
1011
+ // TEST78: conforms_to behavior between MEDIA_OBJECT and MEDIA_STRING
1026
1012
  function test078_debugMatchingBehavior() {
1027
1013
  const objUrn = MediaUrn.fromString(MEDIA_OBJECT);
1028
1014
  const strUrn = MediaUrn.fromString(MEDIA_STRING);
@@ -1033,12 +1019,12 @@ function test078_debugMatchingBehavior() {
1033
1019
  // media_def.rs: TEST088-TEST110
1034
1020
  // ============================================================================
1035
1021
 
1036
- // TEST088: N/A for JS (async registry, Rust-only)
1037
- // TEST089: N/A for JS
1038
- // TEST090: N/A for JS
1022
+ // TEST6277: N/A for JS (async registry, Rust-only)
1023
+ // TEST6279: N/A for JS
1024
+ // TEST6280: N/A for JS
1039
1025
 
1040
- // TEST091: Test resolving custom media URN from local media_defs takes precedence over registry
1041
- function test091_resolveCustomMediaDef() {
1026
+ // TEST6282: Test resolving a custom media URN from a registry-seeded media def
1027
+ function test6282_resolveCustomMediaDef() {
1042
1028
  const mediaDefs = [
1043
1029
  { urn: 'media:custom-json', media_type: 'application/json', title: 'Custom JSON', profile_uri: 'https://example.com/schema/custom' }
1044
1030
  ];
@@ -1047,8 +1033,8 @@ function test091_resolveCustomMediaDef() {
1047
1033
  assertEqual(spec.profile, 'https://example.com/schema/custom', 'Should have custom profile');
1048
1034
  }
1049
1035
 
1050
- // TEST092: Test resolving custom record media def with schema from local media_defs
1051
- function test092_resolveCustomWithSchema() {
1036
+ // TEST6283: Test resolving a custom record media def carrying a schema from a registry-seeded media def
1037
+ function test6283_resolveCustomWithSchema() {
1052
1038
  const mediaDefs = [
1053
1039
  {
1054
1040
  urn: 'media:rich-xml',
@@ -1064,8 +1050,8 @@ function test092_resolveCustomWithSchema() {
1064
1050
  assertEqual(spec.schema.type, 'object', 'Schema should have correct type');
1065
1051
  }
1066
1052
 
1067
- // TEST093: Test resolving unknown media URN fails with UnresolvableMediaUrn error
1068
- function test093_resolveUnresolvableFailsHard() {
1053
+ // TEST93: Test resolving unknown media URN fails with UnresolvableMediaUrn error
1054
+ function test93_resolveUnresolvableFailsHard() {
1069
1055
  let caught = false;
1070
1056
  try {
1071
1057
  resolveMediaUrn('media:nonexistent', []);
@@ -1077,14 +1063,14 @@ function test093_resolveUnresolvableFailsHard() {
1077
1063
  assert(caught, 'Should fail hard on unresolvable media URN');
1078
1064
  }
1079
1065
 
1080
- // TEST094: N/A for JS (no registry concept)
1081
- // TEST095: N/A for JS (Rust serde)
1082
- // TEST096: N/A for JS (Rust serde)
1083
- // TEST097: N/A for JS (Rust validation function)
1084
- // TEST098: N/A for JS
1066
+ // TEST6286: N/A for JS (no registry concept)
1067
+ // TEST6288: N/A for JS (Rust serde)
1068
+ // TEST6290: N/A for JS (Rust serde)
1069
+ // TEST6292: N/A for JS (Rust validation function)
1070
+ // TEST6294: N/A for JS
1085
1071
 
1086
- // TEST099: Test ResolvedMediaDef is_binary returns true when enc tag is absent
1087
- function test099_resolvedIsBinary() {
1072
+ // TEST99: Test ResolvedMediaDef is_binary returns true when enc tag is absent
1073
+ function test99_resolvedIsBinary() {
1088
1074
  const spec = new MediaDef('application/octet-stream', null, null, 'Binary', null, MEDIA_IDENTITY);
1089
1075
  assert(spec.isBinary(), 'Resolved binary spec should be binary');
1090
1076
  }
@@ -1283,7 +1269,7 @@ function test116_capArgConstructors() {
1283
1269
 
1284
1270
  // TEST150: JSON roundtrip
1285
1271
  function test150_capManifestJsonSerialization() {
1286
- const capUrn = CapUrn.fromString(test0051_Urn('extract;target=metadata'));
1272
+ const capUrn = CapUrn.fromString(test6204_Urn('extract;target=metadata'));
1287
1273
  const cap = new Cap(capUrn, 'Extract Metadata', 'extract-metadata');
1288
1274
  cap.addArg(new CapArg('media:ext=pdf', true, [new ArgSource({ stdin: 'media:ext=pdf' })]));
1289
1275
  cap.addArg(new CapArg(
@@ -1381,7 +1367,7 @@ function test597_capArgWithFullDefinition() {
1381
1367
 
1382
1368
  // Add a cap and check it becomes an edge with from/to nodes and carries the
1383
1369
  // registry name we passed. This is exactly the shape the renderer depends on.
1384
- function test0052_CapFabAddCapPopulatesEdgesAndNodes() {
1370
+ function test6206_CapFabAddCapPopulatesEdgesAndNodes() {
1385
1371
  const graph = new CapFab();
1386
1372
  const cap = makeGraphCap('media:ext=pdf', 'media:enc=utf-8', 'PDF to Text');
1387
1373
  graph.addCap(cap, 'registry');
@@ -1399,7 +1385,7 @@ function test0052_CapFabAddCapPopulatesEdgesAndNodes() {
1399
1385
 
1400
1386
  // getOutgoing takes a concrete source URN and returns edges whose from_spec
1401
1387
  // the source conforms to. It must NOT be a plain string lookup.
1402
- function test0053_CapFabGetOutgoingConformsToMatching() {
1388
+ function test6208_CapFabGetOutgoingConformsToMatching() {
1403
1389
  const graph = new CapFab();
1404
1390
  graph.addCap(makeGraphCap('media:enc=utf-8', 'media:embedding-vector', 'Embed text'), 'registry');
1405
1391
 
@@ -1420,7 +1406,7 @@ function test0053_CapFabGetOutgoingConformsToMatching() {
1420
1406
 
1421
1407
  // Each edge must carry the registry name it was added with. This is how
1422
1408
  // the renderer colours/groups edges by provenance in browse mode.
1423
- function test0057_CapFabDistinctRegistryNames() {
1409
+ function test6224_CapFabDistinctRegistryNames() {
1424
1410
  const graph = new CapFab();
1425
1411
  graph.addCap(makeGraphCap('media:ext=pdf', 'media:enc=utf-8', 'PDF to Text'), 'providers');
1426
1412
  graph.addCap(makeGraphCap('media:enc=utf-8', 'media:embedding-vector', 'Embed'), 'cartridges');
@@ -1616,7 +1602,7 @@ function test309_modelAvailabilityAndPathAreDistinct() {
1616
1602
  assert(avail.toString() !== path.toString(), 'availability and path must be distinct');
1617
1603
  }
1618
1604
 
1619
- // TEST310: llm_generate_text_urn() produces a valid cap URN with enc=utf-8 in/out specs
1605
+ // TEST310: llm_generate_text_urn() produces a valid cap URN with a UTF-8 text input and plain-text terminal output.
1620
1606
  function test310_llmGenerateTextUrn() {
1621
1607
  const urn = llmGenerateTextUrn();
1622
1608
  assert(urn.hasMarkerTag('generate_text'), 'Must have generate_text marker');
@@ -1629,7 +1615,7 @@ function test310_llmGenerateTextUrn() {
1629
1615
  }
1630
1616
 
1631
1617
  // Mirror-specific coverage: llm_generate_text_urn input/output specs conform to MEDIA_STRING
1632
- function test0058_LlmGenerateTextUrnSpecs() {
1618
+ function test6228_LlmGenerateTextUrnSpecs() {
1633
1619
  const urn = llmGenerateTextUrn();
1634
1620
  const inSpec = TaggedUrn.fromString(urn.getInSpec());
1635
1621
  const expectedIn = TaggedUrn.fromString(MEDIA_STRING);
@@ -1660,7 +1646,7 @@ function test312_allUrnBuildersProduceValidUrns() {
1660
1646
  // These tests cover JS-specific functionality not in the Rust numbering scheme
1661
1647
  // but are important for capdag-js correctness.
1662
1648
 
1663
- function test0059_JS_buildExtensionIndex() {
1649
+ function test6232_JS_buildExtensionIndex() {
1664
1650
  const mediaDefs = [
1665
1651
  { urn: 'media:ext=pdf', media_type: 'application/pdf', extensions: ['pdf'] },
1666
1652
  { urn: 'media:ext=jpeg;image', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] },
@@ -1676,8 +1662,8 @@ function test0059_JS_buildExtensionIndex() {
1676
1662
  assertEqual(index.get('pdf')[0], 'media:ext=pdf', 'pdf should map correctly');
1677
1663
  }
1678
1664
 
1679
- // TEST0069: J s media urns for extension
1680
- function test0069_JS_mediaUrnsForExtension() {
1665
+ // TEST6236: J s media urns for extension
1666
+ function test6236_JS_mediaUrnsForExtension() {
1681
1667
  const mediaDefs = [
1682
1668
  { urn: 'media:ext=pdf', media_type: 'application/pdf', extensions: ['pdf'] },
1683
1669
  { urn: 'media:fmt=json;record', media_type: 'application/json', extensions: ['json'] },
@@ -1706,7 +1692,7 @@ function test0069_JS_mediaUrnsForExtension() {
1706
1692
  }
1707
1693
 
1708
1694
  // TEST0070: J s get extension mappings
1709
- function test0070_JS_getExtensionMappings() {
1695
+ function test6240_JS_getExtensionMappings() {
1710
1696
  const mediaDefs = [
1711
1697
  { urn: 'media:ext=pdf', media_type: 'application/pdf', extensions: ['pdf'] },
1712
1698
  { urn: 'media:ext=jpeg;image', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] }
@@ -1717,7 +1703,7 @@ function test0070_JS_getExtensionMappings() {
1717
1703
  }
1718
1704
 
1719
1705
  // TEST0073: J s resolve media urn from specs
1720
- function test0073_JS_resolveMediaUrnFromSpecs() {
1706
+ function test6242_JS_resolveMediaUrnFromSpecs() {
1721
1707
  const mediaDefs = [
1722
1708
  { urn: MEDIA_STRING, media_type: 'text/plain', title: 'String', profile_uri: 'https://capdag.com/schema/str' },
1723
1709
  { urn: 'media:custom', media_type: 'application/json', title: 'Custom Output', schema: { type: 'object' } }
@@ -1729,9 +1715,9 @@ function test0073_JS_resolveMediaUrnFromSpecs() {
1729
1715
  assert(outputSpec.schema !== null, 'Should have schema');
1730
1716
  }
1731
1717
 
1732
- // TEST0079: J s cap j s o n serialization
1733
- function test0079_JS_capJSONSerialization() {
1734
- const urn = CapUrn.fromString(test0051_Urn('test'));
1718
+ // TEST6246: J s cap j s o n serialization
1719
+ function test6246_JS_capJSONSerialization() {
1720
+ const urn = CapUrn.fromString(test6204_Urn('test'));
1735
1721
  const cap = new Cap(urn, 'Test Cap', 'test_command');
1736
1722
  cap.arguments = {
1737
1723
  required: [{ name: 'input', media_urn: MEDIA_STRING }],
@@ -1753,8 +1739,8 @@ function test0079_JS_capJSONSerialization() {
1753
1739
  // backticks, embedded quotes, Unicode) so escaping mismatches between
1754
1740
  // JSON.stringify on this side and the Rust serializer on the other side
1755
1741
  // surface as failures here.
1756
- function test0080_JS_capDocumentationRoundTrip() {
1757
- const urn = CapUrn.fromString(test0051_Urn('documented'));
1742
+ function test6249_JS_capDocumentationRoundTrip() {
1743
+ const urn = CapUrn.fromString(test6204_Urn('documented'));
1758
1744
  const cap = new Cap(urn, 'Documented Cap', 'documented');
1759
1745
  const body = '# Documented Cap\r\n\nDoes the thing.\n\n```bash\necho "hi"\n```\n\nSee also: \u2605\n';
1760
1746
  cap.setDocumentation(body);
@@ -1775,8 +1761,8 @@ function test0080_JS_capDocumentationRoundTrip() {
1775
1761
  // toDictionary behaviour. A regression where null is emitted as
1776
1762
  // `documentation: null` would break the symmetric round-trip with Rust
1777
1763
  // (which has no null sentinel) and pollute generated JSON.
1778
- function test0081_JS_capDocumentationOmittedWhenNull() {
1779
- const urn = CapUrn.fromString(test0051_Urn('undocumented'));
1764
+ function test6253_JS_capDocumentationOmittedWhenNull() {
1765
+ const urn = CapUrn.fromString(test6204_Urn('undocumented'));
1780
1766
  const cap = new Cap(urn, 'Undocumented Cap', 'undocumented');
1781
1767
  assertEqual(cap.getDocumentation(), null, 'Default documentation must be null');
1782
1768
 
@@ -1798,7 +1784,7 @@ function test0081_JS_capDocumentationOmittedWhenNull() {
1798
1784
  // resolveMediaUrn into the resolved MediaDef. Mirrors TEST924 on the Rust
1799
1785
  // side. This is the path every UI consumer uses, so a break here makes the
1800
1786
  // new field invisible everywhere downstream.
1801
- function test0082_JS_mediaDefDocumentationPropagatesThroughResolve() {
1787
+ function test6257_JS_mediaDefDocumentationPropagatesThroughResolve() {
1802
1788
  const body = '## Markdown body\n\nWith `code` and a [link](https://example.com).';
1803
1789
  const mediaDefs = [
1804
1790
  {
@@ -1828,23 +1814,23 @@ function test0082_JS_mediaDefDocumentationPropagatesThroughResolve() {
1828
1814
  assertEqual(emptyDoc.documentation, null, 'Empty documentation string must collapse to null');
1829
1815
  }
1830
1816
 
1831
- // TEST0083: J s stdin source kind constants
1832
- function test0083_JS_stdinSourceKindConstants() {
1817
+ // TEST6261: J s stdin source kind constants
1818
+ function test6261_JS_stdinSourceKindConstants() {
1833
1819
  assert(StdinSourceKind.DATA !== undefined, 'DATA kind should be defined');
1834
1820
  assert(StdinSourceKind.FILE_REFERENCE !== undefined, 'FILE_REFERENCE kind should be defined');
1835
1821
  assert(StdinSourceKind.DATA !== StdinSourceKind.FILE_REFERENCE, 'Kind values should be distinct');
1836
1822
  }
1837
1823
 
1838
- // TEST0084: J s stdin source null data
1839
- function test0084_JS_stdinSourceNullData() {
1824
+ // TEST6265: J s stdin source null data
1825
+ function test6265_JS_stdinSourceNullData() {
1840
1826
  const source = StdinSource.fromData(null);
1841
1827
  assert(source !== null, 'Should create source');
1842
1828
  assert(source.isData(), 'Should be data source');
1843
1829
  assertEqual(source.data, null, 'Data should be null');
1844
1830
  }
1845
1831
 
1846
- // TEST0085: J s media def construction
1847
- function test0085_JS_mediaDefConstruction() {
1832
+ // TEST6269: J s media def construction
1833
+ function test6269_JS_mediaDefConstruction() {
1848
1834
  const spec1 = new MediaDef('text/plain', 'https://capdag.com/schema/str', null, 'String', null, 'media:string');
1849
1835
  assertEqual(spec1.contentType, 'text/plain', 'Should have content type');
1850
1836
  assertEqual(spec1.profile, 'https://capdag.com/schema/str', 'Should have profile');
@@ -2037,7 +2023,7 @@ function test320_cartridgeInfoConstruction() {
2037
2023
  assert(cartridge.allCaps().length === 1, 'allCaps() should return 1 cap');
2038
2024
  }
2039
2025
 
2040
- // TEST321: CartridgeInfo.is_signed() returns true when signature is present
2026
+ // TEST321: CartridgeInfo.is_signed() returns true when signature (team_id + signed_at) is present, false when either is empty.
2041
2027
  function test321_cartridgeInfoIsSigned() {
2042
2028
  const signed = new CartridgeInfo({id: 'test', registryUrl: 'https://test.example/manifest', channel: 'release', teamId: 'TEAM', signedAt: '2026-01-01', cap_groups: []});
2043
2029
  assert(signed.isSigned() === true, 'Cartridge with teamId and signedAt should be signed');
@@ -2049,7 +2035,7 @@ function test321_cartridgeInfoIsSigned() {
2049
2035
  assert(unsigned2.isSigned() === false, 'Cartridge without signedAt should not be signed');
2050
2036
  }
2051
2037
 
2052
- // TEST322: CartridgeInfo.build_for_platform() returns the build matching the current platform
2038
+ // TEST322: CartridgeInfo.build_for_platform() returns the build that matches the requested platform string and None otherwise.
2053
2039
  function test322_cartridgeInfoBuildForPlatform() {
2054
2040
  const withBuilds = new CartridgeInfo({
2055
2041
  id: 'test', registryUrl: 'https://test.example/manifest', channel: 'release', version: '1.0.0', cap_groups: [],
@@ -2120,9 +2106,7 @@ function test323_cartridgeRepoServerValidateRegistry() {
2120
2106
  assert(threw, 'Should throw when nightly channel is missing');
2121
2107
  }
2122
2108
 
2123
- // TEST324: CartridgeRepoServer walks both channels and emits a flat
2124
- // CartridgeInfo array preserving channel provenance. Release entries
2125
- // appear first.
2109
+ // TEST324: CartridgeRepoServer transforms a v4.0 entry into a flat CartridgeInfo, preserving cap_groups verbatim.
2126
2110
  function test324_cartridgeRepoServerTransformToArray() {
2127
2111
  const server = new CartridgeRepoServer(sampleRegistry);
2128
2112
  const cartridges = server.transformToCartridgeArray();
@@ -2159,8 +2143,7 @@ function test324_cartridgeRepoServerTransformToArray() {
2159
2143
  assert(firstNightlyIdx > lastReleaseIdx, 'Release entries must precede nightly entries');
2160
2144
  }
2161
2145
 
2162
- // TEST325: CartridgeRepoServer.getCartridges() wraps the transformed
2163
- // flat array (across both channels) in the response envelope.
2146
+ // TEST325: get_cartridges() wraps the transformed array in the response envelope.
2164
2147
  function test325_cartridgeRepoServerGetCartridges() {
2165
2148
  const server = new CartridgeRepoServer(sampleRegistry);
2166
2149
  const response = server.getCartridges();
@@ -2172,9 +2155,7 @@ function test325_cartridgeRepoServerGetCartridges() {
2172
2155
  'Every cartridge must carry a channel');
2173
2156
  }
2174
2157
 
2175
- // TEST326: CartridgeRepoServer.getCartridgeById() requires (channel,
2176
- // id). Same id looked up in the wrong channel must miss — channels are
2177
- // independent namespaces.
2158
+ // TEST326: get_cartridge_by_id requires a channel and returns Some for a known (channel, id), None otherwise. The same id looked up in the wrong channel must miss — channels are independent namespaces.
2178
2159
  function test326_cartridgeRepoServerGetCartridgeById() {
2179
2160
  const server = new CartridgeRepoServer(sampleRegistry);
2180
2161
 
@@ -2204,9 +2185,7 @@ function test326_cartridgeRepoServerGetCartridgeById() {
2204
2185
  assert(threw, 'Should throw for invalid channel');
2205
2186
  }
2206
2187
 
2207
- // TEST327: CartridgeRepoServer.searchCartridges() filters across both
2208
- // channels by name/description/tags/cap titles. Cap URN strings are
2209
- // not substring-matched.
2188
+ // TEST327: search_cartridges matches against name/description/tags and cap titles, but never against cap URN strings.
2210
2189
  function test327_cartridgeRepoServerSearchCartridges() {
2211
2190
  const server = new CartridgeRepoServer(sampleRegistry);
2212
2191
 
@@ -2321,8 +2300,7 @@ function test331_cartridgeRepoClientGetSuggestions() {
2321
2300
  assert(nightlySuggestions[0].channel === 'nightly', 'Should report nightly channel');
2322
2301
  }
2323
2302
 
2324
- // TEST332: CartridgeRepoClient.getCartridge() requires (channel, id).
2325
- // Same id in the wrong channel must miss.
2303
+ // TEST332: get_cartridge requires a (channel, id) pair and returns the cached entry for known pairs, None otherwise. The same id in the wrong channel must miss.
2326
2304
  function test332_cartridgeRepoClientGetCartridge() {
2327
2305
  const client = new CartridgeRepoClient(3600);
2328
2306
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2366,8 +2344,7 @@ function test332_cartridgeRepoClientGetCartridge() {
2366
2344
  assert(threw, 'Should throw for invalid channel');
2367
2345
  }
2368
2346
 
2369
- // TEST333: CartridgeRepoClient.getAllAvailableCaps() returns the set
2370
- // of normalized URNs across both channels.
2347
+ // TEST333: get_all_available_caps returns the deduplicated set of normalized URNs across cartridges.
2371
2348
  function test333_cartridgeRepoClientGetAllCaps() {
2372
2349
  const client = new CartridgeRepoClient(3600);
2373
2350
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2383,8 +2360,7 @@ function test333_cartridgeRepoClientGetAllCaps() {
2383
2360
  assert(caps.every(c => typeof c === 'string'), 'All caps should be strings');
2384
2361
  }
2385
2362
 
2386
- // TEST334: CartridgeRepoClient.needsSync() returns true when cache is
2387
- // empty / stale, false right after a fresh update.
2363
+ // TEST334: needs_sync returns true on an empty cache, false right after a successful update.
2388
2364
  function test334_cartridgeRepoClientNeedsSync() {
2389
2365
  const client = new CartridgeRepoClient(1); // 1 second TTL
2390
2366
  const server = new CartridgeRepoServer(sampleRegistry);
@@ -2486,8 +2462,7 @@ function cartridgeWithVersions(id, versions) {
2486
2462
  });
2487
2463
  }
2488
2464
 
2489
- // TEST1849: latest version has a host build → Compatible, resolving to the
2490
- // latest version and that platform's native-format package.
2465
+ // TEST1849: latest version has a host build → Compatible, resolving to the latest version and that platform's native-format package.
2491
2466
  function test1849_resolveForHostCompatibleLatest() {
2492
2467
  const cartridge = cartridgeWithVersions('c', [
2493
2468
  ['1.2.0', [['darwin-arm64', 'pkg', 'c-1.2.0.pkg'], ['linux-x86_64', 'deb', 'c-1.2.0.deb']]],
@@ -2503,9 +2478,7 @@ function test1849_resolveForHostCompatibleLatest() {
2503
2478
  assertEqual(r.hostPlatform, 'linux-x86_64', 'host platform echoed back');
2504
2479
  }
2505
2480
 
2506
- // TEST1850: the latest version lacks a host build but an older version has one
2507
- // → CompatibleOutdated, resolving to the NEWEST older version with a host build
2508
- // (not the oldest), with a reason naming both the latest and the resolved.
2481
+ // TEST1850: the latest version lacks a host build but an older version has one → CompatibleOutdated, resolving to the older version with a reason naming both the latest and the resolved version.
2509
2482
  function test1850_resolveForHostCompatibleOutdated() {
2510
2483
  const cartridge = cartridgeWithVersions('c', [
2511
2484
  ['1.3.0', [['darwin-arm64', 'pkg', 'c-1.3.0.pkg']]],
@@ -2522,8 +2495,7 @@ function test1850_resolveForHostCompatibleOutdated() {
2522
2495
  assert(r.reason.includes('1.2.0'), `reason names the resolved: ${r.reason}`);
2523
2496
  }
2524
2497
 
2525
- // TEST1851: no version ships a host build → Incompatible, no resolved
2526
- // version/package, reason states the host platform.
2498
+ // TEST1851: no version ships a host build → Incompatible, no resolved version/package, reason states the host platform.
2527
2499
  function test1851_resolveForHostIncompatible() {
2528
2500
  const cartridge = cartridgeWithVersions('c', [
2529
2501
  ['1.2.0', [['darwin-arm64', 'pkg', 'c-1.2.0.pkg']]],
@@ -2537,9 +2509,7 @@ function test1851_resolveForHostIncompatible() {
2537
2509
  assert(r.reason.includes('windows-x86_64'), `reason names the host platform: ${r.reason}`);
2538
2510
  }
2539
2511
 
2540
- // TEST1852: a host build whose packages[] is empty AND has no legacy `package`
2541
- // ships no installer; resolution must SKIP it (not resolve to an un-downloadable
2542
- // version) and fall through to an older usable version.
2512
+ // TEST1852: a host build whose packages[] is empty AND has no legacy `package` ships no installer; resolution must SKIP it (not resolve to an un-downloadable version) and fall through to an older usable version.
2543
2513
  function test1852_resolveForHostSkipsBuildWithNoInstaller() {
2544
2514
  const cartridge = cartridgeWithVersions('c', [
2545
2515
  ['2.0.0', [['linux-x86_64', 'deb', 'c-2.0.0.deb']]],
@@ -2557,8 +2527,7 @@ function test1852_resolveForHostSkipsBuildWithNoInstaller() {
2557
2527
  assertEqual(r.resolvedPackage.name, 'c-1.0.0.deb', 'resolves to 1.0.0 deb package');
2558
2528
  }
2559
2529
 
2560
- // TEST1853: hostPlatform() returns a normalized {os}-{arch} string with arch
2561
- // aarch64/arm64 mapped to arm64 — the exact form the registry uses.
2530
+ // TEST1853: host_platform() returns a normalized {os}-{arch} string with arch aarch64 mapped to arm64 — the exact form the registry uses.
2562
2531
  function test1853_hostPlatformNormalizedForm() {
2563
2532
  const p = hostPlatform();
2564
2533
  const dash = p.indexOf('-');
@@ -2588,9 +2557,7 @@ function test1873_registryUrlFromBuildEnvNoneForDev() {
2588
2557
  assert(registryUrlFromBuildEnv(undefined) === null, 'absent env → null (dev build)');
2589
2558
  }
2590
2559
 
2591
- // TEST1874: an exported-but-empty env ('') is neither a dev build nor a valid
2592
- // identity and MUST fail hard, so the build can never silently hash the empty
2593
- // string into a fake registry slug.
2560
+ // TEST1874: an exported-but-empty env (`Some("")`) is neither a dev build nor a valid identity and MUST fail hard at compile time, so the build can never silently hash the empty string into a fake registry slug. We assert the panic rather than letting a bogus empty primary registry ship.
2594
2561
  function test1874_registryUrlFromBuildEnvRejectsEmptyString() {
2595
2562
  let threw = false;
2596
2563
  try {
@@ -2748,8 +2715,8 @@ async function test1878_bundledProviderWithoutBakedHashIsRejected() {
2748
2715
  // media_urn.rs: TEST1294-TEST1302 (MediaUrn predicates)
2749
2716
  // ============================================================================
2750
2717
 
2751
- // TEST1312: is_image returns true only when image marker tag is present
2752
- function test1312_isImage() {
2718
+ // TEST546: is_image returns true only when image marker tag is present
2719
+ function test546_isImage() {
2753
2720
  assert(MediaUrn.fromString(MEDIA_PNG).isImage(), 'MEDIA_PNG should be image');
2754
2721
  assert(MediaUrn.fromString('media:ext=png;image;thumbnail').isImage(), 'media:ext=png;image;thumbnail should be image');
2755
2722
  assert(MediaUrn.fromString('media:ext=jpg;image').isImage(), 'media:ext=jpg;image should be image');
@@ -2760,8 +2727,8 @@ function test1312_isImage() {
2760
2727
  assert(!MediaUrn.fromString(MEDIA_VIDEO).isImage(), 'MEDIA_VIDEO should not be image');
2761
2728
  }
2762
2729
 
2763
- // TEST1313: is_audio returns true only when audio marker tag is present
2764
- function test1313_isAudio() {
2730
+ // TEST547: is_audio returns true only when audio marker tag is present
2731
+ function test547_isAudio() {
2765
2732
  assert(MediaUrn.fromString(MEDIA_AUDIO).isAudio(), 'MEDIA_AUDIO should be audio');
2766
2733
  assert(MediaUrn.fromString(MEDIA_AUDIO_SPEECH).isAudio(), 'MEDIA_AUDIO_SPEECH should be audio');
2767
2734
  assert(MediaUrn.fromString('media:audio;ext=mp3').isAudio(), 'media:audio;ext=mp3 should be audio');
@@ -2771,8 +2738,8 @@ function test1313_isAudio() {
2771
2738
  assert(!MediaUrn.fromString(MEDIA_STRING).isAudio(), 'MEDIA_STRING should not be audio');
2772
2739
  }
2773
2740
 
2774
- // TEST1314: is_video returns true only when video marker tag is present
2775
- function test1314_isVideo() {
2741
+ // TEST548: is_video returns true only when video marker tag is present
2742
+ function test548_isVideo() {
2776
2743
  assert(MediaUrn.fromString(MEDIA_VIDEO).isVideo(), 'MEDIA_VIDEO should be video');
2777
2744
  assert(MediaUrn.fromString('media:ext=mp4;video').isVideo(), 'media:ext=mp4;video should be video');
2778
2745
  // Non-video types
@@ -2781,8 +2748,8 @@ function test1314_isVideo() {
2781
2748
  assert(!MediaUrn.fromString(MEDIA_STRING).isVideo(), 'MEDIA_STRING should not be video');
2782
2749
  }
2783
2750
 
2784
- // TEST1315: is_numeric returns true only when numeric marker tag is present
2785
- function test1315_isNumeric() {
2751
+ // TEST549: is_numeric returns true only when numeric marker tag is present
2752
+ function test549_isNumeric() {
2786
2753
  assert(MediaUrn.fromString(MEDIA_INTEGER).isNumeric(), 'MEDIA_INTEGER should be numeric');
2787
2754
  assert(MediaUrn.fromString(MEDIA_NUMBER).isNumeric(), 'MEDIA_NUMBER should be numeric');
2788
2755
  assert(MediaUrn.fromString(MEDIA_INTEGER_LIST).isNumeric(), 'MEDIA_INTEGER_LIST should be numeric');
@@ -2793,8 +2760,8 @@ function test1315_isNumeric() {
2793
2760
  assert(!MediaUrn.fromString(MEDIA_IDENTITY).isNumeric(), 'MEDIA_IDENTITY should not be numeric');
2794
2761
  }
2795
2762
 
2796
- // TEST1298: is_bool returns true only when bool marker tag is present
2797
- function test1298_isBool() {
2763
+ // TEST550: is_bool returns true only when bool marker tag is present
2764
+ function test550_isBool() {
2798
2765
  assert(MediaUrn.fromString(MEDIA_BOOLEAN).isBool(), 'MEDIA_BOOLEAN should be bool');
2799
2766
  assert(MediaUrn.fromString(MEDIA_BOOLEAN_LIST).isBool(), 'MEDIA_BOOLEAN_LIST should be bool');
2800
2767
  // MEDIA_DECISION is now a JSON record type (not bool)
@@ -2805,10 +2772,8 @@ function test1298_isBool() {
2805
2772
  assert(!MediaUrn.fromString(MEDIA_IDENTITY).isBool(), 'MEDIA_IDENTITY should not be bool');
2806
2773
  }
2807
2774
 
2808
- // TEST1299: isFilePath returns true for the single file-path media URN,
2809
- // false for everything else. There is no "array" variant — cardinality is
2810
- // carried by is_sequence on the wire, not by URN tags.
2811
- function test1299_isFilePath() {
2775
+ // TEST551: is_file_path returns true for the single file-path media URN, false for everything else. There is no "array" variant — cardinality is carried by is_sequence on the wire, not by URN tags.
2776
+ function test551_isFilePath() {
2812
2777
  assert(MediaUrn.fromString(MEDIA_FILE_PATH).isFilePath(), 'MEDIA_FILE_PATH should be file-path');
2813
2778
  assert(!MediaUrn.fromString(MEDIA_STRING).isFilePath(), 'MEDIA_STRING should not be file-path');
2814
2779
  assert(!MediaUrn.fromString(MEDIA_IDENTITY).isFilePath(), 'MEDIA_IDENTITY should not be file-path');
@@ -2816,7 +2781,7 @@ function test1299_isFilePath() {
2816
2781
 
2817
2782
  // Mirror-specific coverage: isCollection returns true when collection marker tag is present
2818
2783
  // Mirror-specific coverage: N/A for JS (MEDIA_COLLECTION constants removed - no longer exists)
2819
- function test0086_isCollection() {
2784
+ function test6272_isCollection() {
2820
2785
  // Skip - collection types removed from capdag
2821
2786
  }
2822
2787
 
@@ -2826,8 +2791,8 @@ function test0086_isCollection() {
2826
2791
 
2827
2792
  // TEST557: N/A for JS (audio_media_urn_for_ext helper not in JS)
2828
2793
 
2829
- // TEST1302: predicates are consistent with constants — every constant triggers exactly the expected predicates
2830
- function test1302_predicateConstantConsistency() {
2794
+ // TEST558: predicates are consistent with constants — every constant triggers exactly the expected predicates
2795
+ function test558_predicateConstantConsistency() {
2831
2796
  // MEDIA_INTEGER must be numeric, scalar, NOT enc-bearing/bool/image/list.
2832
2797
  // Integers carry no enc= (a number is not a character-encoded string).
2833
2798
  const intUrn = MediaUrn.fromString(MEDIA_INTEGER);
@@ -2865,8 +2830,8 @@ function test1302_predicateConstantConsistency() {
2865
2830
  // cap_urn.rs: TEST1303-TEST1307 (CapUrn tier tests)
2866
2831
  // ============================================================================
2867
2832
 
2868
- // TEST1303: without_tag removes tag, rejects structural keys, case-insensitive for keys
2869
- function test1303_withoutTag() {
2833
+ // TEST559: without_tag removes tag, rejects structural keys, case-insensitive for keys
2834
+ function test559_withoutTag() {
2870
2835
  const cap = CapUrn.fromString('cap:in="media:void";test;ext=pdf;out="media:void"');
2871
2836
  const removed = cap.withoutTag('ext');
2872
2837
  assertEqual(removed.getTag('ext'), undefined, 'withoutTag should remove ext');
@@ -2885,8 +2850,8 @@ function test1303_withoutTag() {
2885
2850
  assert(same3.equals(cap), 'Removing non-existent tag is no-op');
2886
2851
  }
2887
2852
 
2888
- // TEST1304: with_in_spec and with_out_spec change direction specs
2889
- function test1304_withInOutSpec() {
2853
+ // TEST560: with_in_spec and with_out_spec change direction specs
2854
+ function test560_withInOutSpec() {
2890
2855
  const cap = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
2891
2856
 
2892
2857
  const changedIn = cap.withInSpec('media:');
@@ -2915,8 +2880,8 @@ function test1304_withInOutSpec() {
2915
2880
 
2916
2881
  // TEST562: N/A for JS (canonical_option not in JS CapUrn)
2917
2882
 
2918
- // TEST1305: CapMatcher::find_all_matches returns all matching caps sorted by specificity
2919
- function test1305_findAllMatches() {
2883
+ // TEST563: CapMatcher::find_all_matches returns all matching caps sorted by specificity
2884
+ function test563_findAllMatches() {
2920
2885
  const caps = [
2921
2886
  CapUrn.fromString('cap:in="media:void";test;out="media:void"'),
2922
2887
  CapUrn.fromString('cap:in="media:void";test;ext=pdf;out="media:void"'),
@@ -2933,8 +2898,8 @@ function test1305_findAllMatches() {
2933
2898
  assertEqual(matches[0].getTag('ext'), 'pdf', 'Most specific match should have ext=pdf');
2934
2899
  }
2935
2900
 
2936
- // TEST1306: CapMatcher::are_compatible detects bidirectional overlap
2937
- function test1306_areCompatible() {
2901
+ // TEST564: CapMatcher::are_compatible detects bidirectional overlap
2902
+ function test564_areCompatible() {
2938
2903
  const caps1 = [
2939
2904
  CapUrn.fromString('cap:in="media:void";test;out="media:void"'),
2940
2905
  ];
@@ -2958,16 +2923,16 @@ function test1306_areCompatible() {
2958
2923
 
2959
2924
  // TEST565: N/A for JS (tags_to_string not in JS CapUrn)
2960
2925
 
2961
- // TEST1307: with_tag rejects structural keys
2962
- function test1307_withTagRejectsStructuralKeys() {
2926
+ // TEST566: with_tag rejects structural keys
2927
+ function test566_withTagRejectsStructuralKeys() {
2963
2928
  const cap = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
2964
2929
  assertThrows(() => cap.withTag('in', 'media:'), ErrorCodes.INVALID_TAG_FORMAT, 'withTag must reject in');
2965
2930
  assertThrows(() => cap.withTag('out', 'media:'), ErrorCodes.INVALID_TAG_FORMAT, 'withTag must reject out');
2966
2931
  assertThrows(() => cap.withTag('effect', 'none'), ErrorCodes.INVALID_TAG_FORMAT, 'withTag must reject effect');
2967
2932
  }
2968
2933
 
2969
- // TEST1308: builder rejects structural keys on tag/marker
2970
- function test1308_builderRejectsStructuralKeys() {
2934
+ // TEST6544: builder rejects structural keys on tag/marker
2935
+ function test6544_builderRejectsStructuralKeys() {
2971
2936
  assertThrows(
2972
2937
  () => new CapUrnBuilder().tag('in', 'media:void'),
2973
2938
  ErrorCodes.INVALID_TAG_FORMAT,
@@ -3041,8 +3006,8 @@ function test1297_rule11NonVoidInputWithStdin() {
3041
3006
  // cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
3042
3007
  // ============================================================================
3043
3008
 
3044
- // TEST639: cap: (empty) is the illegal bare top form
3045
- function test639_emptyCapIsIllegal() {
3009
+ // TEST6201: cap: (empty) is the illegal bare top form
3010
+ function test6201_emptyCapIsIllegal() {
3046
3011
  assertThrows(
3047
3012
  () => CapUrn.fromString('cap:'),
3048
3013
  ErrorCodes.ILLEGAL_DECLARATION,
@@ -3050,7 +3015,7 @@ function test639_emptyCapIsIllegal() {
3050
3015
  );
3051
3016
  }
3052
3017
 
3053
- // TEST640: cap:in collapses to the same illegal bare top form
3018
+ // TEST640: cap:in defaults to the same illegal bare top form
3054
3019
  function test640_inOnlyIsIllegal() {
3055
3020
  assertThrows(
3056
3021
  () => CapUrn.fromString('cap:in'),
@@ -3059,7 +3024,7 @@ function test640_inOnlyIsIllegal() {
3059
3024
  );
3060
3025
  }
3061
3026
 
3062
- // TEST641: cap:out collapses to the same illegal bare top form
3027
+ // TEST641: cap:out defaults to the same illegal bare top form
3063
3028
  function test641_outOnlyIsIllegal() {
3064
3029
  assertThrows(
3065
3030
  () => CapUrn.fromString('cap:out'),
@@ -3068,7 +3033,7 @@ function test641_outOnlyIsIllegal() {
3068
3033
  );
3069
3034
  }
3070
3035
 
3071
- // TEST642: cap:in;out collapses to the same illegal bare top form
3036
+ // TEST642: cap:in;out becomes the same illegal bare top form
3072
3037
  function test642_inOutWithoutValuesAreIllegal() {
3073
3038
  assertThrows(
3074
3039
  () => CapUrn.fromString('cap:in;out'),
@@ -3147,8 +3112,8 @@ function test650_wildcardPreserveOtherTags() {
3147
3112
  assert(cap.hasMarkerTag('test'), 'marker tag should be preserved');
3148
3113
  }
3149
3114
 
3150
- // TEST651: Generic top-to-top spellings are all rejected.
3151
- function test651_wildcardGenericFormsRejected() {
3115
+ // TEST6620: Generic top-to-top spellings are all rejected.
3116
+ function test6620_wildcardGenericFormsRejected() {
3152
3117
  const forms = [
3153
3118
  'cap:',
3154
3119
  'cap:in;out',
@@ -3168,8 +3133,8 @@ function test651_wildcardGenericFormsRejected() {
3168
3133
  }
3169
3134
  }
3170
3135
 
3171
- // TEST652: CAP_IDENTITY constant names the true identity cap, not bare cap:
3172
- function test652_capIdentityConstantWorks() {
3136
+ // TEST6621: CAP_IDENTITY constant names the true identity cap, not bare cap:
3137
+ function test6621_capIdentityConstantWorks() {
3173
3138
  const identity = CapUrn.fromString(CAP_IDENTITY);
3174
3139
  assertEqual(identity.toString(), 'cap:effect=none', 'CAP_IDENTITY must be explicit effect=none');
3175
3140
  assertEqual(identity.kind(), CapKind.IDENTITY, 'CAP_IDENTITY must classify as identity');
@@ -3185,7 +3150,7 @@ function test652_capIdentityConstantWorks() {
3185
3150
  );
3186
3151
  }
3187
3152
 
3188
- // TEST653: invalid effect=none declarations fail at construction.
3153
+ // TEST653: invalid effect=none declarations fail at construction
3189
3154
  function test653_invalidEffectNoneDeclarationRejected() {
3190
3155
  assertThrows(
3191
3156
  () => CapUrn.fromString('cap:in="media:ext=pdf";effect=none;out="media:enc=utf-8"'),
@@ -3194,8 +3159,8 @@ function test653_invalidEffectNoneDeclarationRejected() {
3194
3159
  );
3195
3160
  }
3196
3161
 
3197
- // TEST654: effect=none preserves runtime media identity.
3198
- function test654_effectNonePreservesRuntimeMedia() {
3162
+ // TEST125: effect=none preserves runtime media identity
3163
+ function test125_effectNonePreservesRuntimeMedia() {
3199
3164
  const decimate = CapUrn.fromString('cap:decimate-sequence;effect=none');
3200
3165
  const png = MediaUrn.fromString('media:ext=png;image');
3201
3166
  const pdf = MediaUrn.fromString('media:ext=pdf');
@@ -3203,8 +3168,8 @@ function test654_effectNonePreservesRuntimeMedia() {
3203
3168
  assertEqual(decimate.inferRuntimeOutputMedia(pdf).toString(), pdf.toString(), 'effect=none should preserve pdf');
3204
3169
  }
3205
3170
 
3206
- // TEST655: default effect=declared does not preserve runtime refinements.
3207
- function test655_effectDeclaredUsesDeclaredOutput() {
3171
+ // TEST126: default effect=declared uses the declared output
3172
+ function test126_effectDeclaredUsesDeclaredOutput() {
3208
3173
  const resize = CapUrn.fromString('cap:in=media:image;out=media:image;resize');
3209
3174
  const png = MediaUrn.fromString('media:ext=png;image;width=4000');
3210
3175
  assertEqual(
@@ -3214,8 +3179,8 @@ function test655_effectDeclaredUsesDeclaredOutput() {
3214
3179
  );
3215
3180
  }
3216
3181
 
3217
- // TEST656: invalid effect=none declarations fail hard at construction.
3218
- function test656_invalidEffectNoneFailsHard() {
3182
+ // TEST127: invalid effect=none declarations fail hard
3183
+ function test127_invalidEffectNoneFailsHard() {
3219
3184
  assertThrows(
3220
3185
  () => CapUrn.fromString('cap:in="media:ext=pdf";effect=none;out="media:enc=utf-8"'),
3221
3186
  ErrorCodes.ILLEGAL_DECLARATION,
@@ -3223,8 +3188,8 @@ function test656_invalidEffectNoneFailsHard() {
3223
3188
  );
3224
3189
  }
3225
3190
 
3226
- // TEST657: omitted effect means declared; unconstrained effect must be explicit.
3227
- function test657_effectDispatchRequiresExplicitWildcard() {
3191
+ // TEST128: omitted effect means declared; unconstrained effect must be explicit
3192
+ function test128_effectDispatchRequiresExplicitWildcard() {
3228
3193
  const noneProvider = CapUrn.fromString('cap:effect=none');
3229
3194
  const declaredRequest = CapUrn.fromString('cap:raw');
3230
3195
  const anyRequest = CapUrn.fromString('cap:?effect');
@@ -3238,17 +3203,17 @@ function test657_effectDispatchRequiresExplicitWildcard() {
3238
3203
 
3239
3204
  // --- Machine parser tests (mirrors parser.rs tests) ---
3240
3205
 
3241
- function test0087_Machine_emptyInput() {
3206
+ function test6275_Machine_emptyInput() {
3242
3207
  assertThrowsWithCode(() => parseMachine(''), MachineSyntaxErrorCodes.EMPTY);
3243
3208
  }
3244
3209
 
3245
3210
  // TEST0088: Machine whitespace only
3246
- function test0088_Machine_whitespaceOnly() {
3211
+ function test6277_Machine_whitespaceOnly() {
3247
3212
  assertThrowsWithCode(() => parseMachine(' \n \t '), MachineSyntaxErrorCodes.EMPTY);
3248
3213
  }
3249
3214
 
3250
3215
  // TEST0089: Machine header only no wirings
3251
- function test0089_Machine_headerOnlyNoWirings() {
3216
+ function test6279_Machine_headerOnlyNoWirings() {
3252
3217
  assertThrowsWithCode(
3253
3218
  () => Machine.fromString('[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]'),
3254
3219
  MachineSyntaxErrorCodes.NO_EDGES
@@ -3256,7 +3221,7 @@ function test0089_Machine_headerOnlyNoWirings() {
3256
3221
  }
3257
3222
 
3258
3223
  // TEST0090: Machine duplicate alias
3259
- function test0090_Machine_duplicateAlias() {
3224
+ function test6280_Machine_duplicateAlias() {
3260
3225
  assertThrowsWithCode(
3261
3226
  () => Machine.fromString(
3262
3227
  '[ex cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
@@ -3268,7 +3233,7 @@ function test0090_Machine_duplicateAlias() {
3268
3233
  }
3269
3234
 
3270
3235
  // TEST0094: Machine simple linear chain
3271
- function test0094_Machine_simpleLinearChain() {
3236
+ function test6286_Machine_simpleLinearChain() {
3272
3237
  const g = Machine.fromString(
3273
3238
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
3274
3239
  '[doc -> extract -> text]'
@@ -3284,7 +3249,7 @@ function test0094_Machine_simpleLinearChain() {
3284
3249
  }
3285
3250
 
3286
3251
  // TEST0095: Machine two step chain
3287
- function test0095_Machine_twoStepChain() {
3252
+ function test6288_Machine_twoStepChain() {
3288
3253
  const g = Machine.fromString(
3289
3254
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
3290
3255
  '[embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"]' +
@@ -3299,7 +3264,7 @@ function test0095_Machine_twoStepChain() {
3299
3264
  }
3300
3265
 
3301
3266
  // TEST0096: Machine fan out
3302
- function test0096_Machine_fanOut() {
3267
+ function test6290_Machine_fanOut() {
3303
3268
  const g = Machine.fromString(
3304
3269
  '[meta cap:in="media:ext=pdf";extract-metadata;out="media:enc=utf-8;file-metadata;record"]' +
3305
3270
  '[outline cap:in="media:ext=pdf";extract-outline;out="media:document-outline;enc=utf-8;record"]' +
@@ -3317,7 +3282,7 @@ function test0096_Machine_fanOut() {
3317
3282
  }
3318
3283
 
3319
3284
  // TEST0097: Machine fan in secondary assigned by prior wiring
3320
- function test0097_Machine_fanInSecondaryAssignedByPriorWiring() {
3285
+ function test6292_Machine_fanInSecondaryAssignedByPriorWiring() {
3321
3286
  const g = Machine.fromString(
3322
3287
  '[thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:ext=png;image;thumbnail"]' +
3323
3288
  '[model_dl cap:in="media:enc=utf-8;model-spec";download;out="media:enc=utf-8;model-spec"]' +
@@ -3331,7 +3296,7 @@ function test0097_Machine_fanInSecondaryAssignedByPriorWiring() {
3331
3296
  }
3332
3297
 
3333
3298
  // TEST0098: Machine fan in secondary unassigned gets wildcard
3334
- function test0098_Machine_fanInSecondaryUnassignedGetsWildcard() {
3299
+ function test6294_Machine_fanInSecondaryUnassignedGetsWildcard() {
3335
3300
  const g = Machine.fromString(
3336
3301
  '[describe cap:in="media:ext=png;image";describe-image;out="media:enc=utf-8;image-description"]\n' +
3337
3302
  '[(thumbnail, model_spec) -> describe -> description]'
@@ -3342,8 +3307,8 @@ function test0098_Machine_fanInSecondaryUnassignedGetsWildcard() {
3342
3307
  assertEqual(g.edges()[0].sources[1].toString(), 'media:');
3343
3308
  }
3344
3309
 
3345
- // TEST0111: Machine loop edge
3346
- function test0111_Machine_loopEdge() {
3310
+ // TEST6306: Machine loop edge
3311
+ function test6306_Machine_loopEdge() {
3347
3312
  const g = Machine.fromString(
3348
3313
  '[p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"]' +
3349
3314
  '[pages -> LOOP p2t -> texts]'
@@ -3352,16 +3317,16 @@ function test0111_Machine_loopEdge() {
3352
3317
  assertEqual(g.edges()[0].isLoop, true);
3353
3318
  }
3354
3319
 
3355
- // TEST0112: Machine undefined alias fails
3356
- function test0112_Machine_undefinedAliasFails() {
3320
+ // TEST6308: Machine undefined alias fails
3321
+ function test6308_Machine_undefinedAliasFails() {
3357
3322
  assertThrowsWithCode(
3358
3323
  () => Machine.fromString('[doc -> nonexistent -> text]'),
3359
3324
  MachineSyntaxErrorCodes.UNDEFINED_ALIAS
3360
3325
  );
3361
3326
  }
3362
3327
 
3363
- // TEST0113: Machine node alias collision
3364
- function test0113_Machine_nodeAliasCollision() {
3328
+ // TEST6310: Machine node alias collision
3329
+ function test6310_Machine_nodeAliasCollision() {
3365
3330
  assertThrowsWithCode(
3366
3331
  () => Machine.fromString(
3367
3332
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
@@ -3371,8 +3336,8 @@ function test0113_Machine_nodeAliasCollision() {
3371
3336
  );
3372
3337
  }
3373
3338
 
3374
- // TEST0114: Machine conflicting media types fail
3375
- function test0114_Machine_conflictingMediaTypesFail() {
3339
+ // TEST6312: Machine conflicting media types fail
3340
+ function test6312_Machine_conflictingMediaTypesFail() {
3376
3341
  assertThrowsWithCode(
3377
3342
  () => Machine.fromString(
3378
3343
  '[cap1 cap:in="media:enc=utf-8;ext=txt";a;out="media:ext=pdf"]' +
@@ -3384,8 +3349,8 @@ function test0114_Machine_conflictingMediaTypesFail() {
3384
3349
  );
3385
3350
  }
3386
3351
 
3387
- // TEST0117: Machine multiline format
3388
- function test0117_Machine_multilineFormat() {
3352
+ // TEST6315: Machine multiline format
3353
+ function test6315_Machine_multilineFormat() {
3389
3354
  const g = Machine.fromString(
3390
3355
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n' +
3391
3356
  '[embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"]\n' +
@@ -3395,8 +3360,8 @@ function test0117_Machine_multilineFormat() {
3395
3360
  assertEqual(g.edgeCount(), 2);
3396
3361
  }
3397
3362
 
3398
- // TEST0118: Machine different aliases same graph
3399
- function test0118_Machine_differentAliasesSameGraph() {
3363
+ // TEST6318: Machine different aliases same graph
3364
+ function test6318_Machine_differentAliasesSameGraph() {
3400
3365
  const g1 = Machine.fromString(
3401
3366
  '[ex cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
3402
3367
  '[a -> ex -> b]'
@@ -3408,16 +3373,16 @@ function test0118_Machine_differentAliasesSameGraph() {
3408
3373
  assert(g1.isEquivalent(g2), 'Different aliases should produce equivalent graphs');
3409
3374
  }
3410
3375
 
3411
- // TEST0119: Machine malformed input fails
3412
- function test0119_Machine_malformedInputFails() {
3376
+ // TEST6321: Machine malformed input fails
3377
+ function test6321_Machine_malformedInputFails() {
3413
3378
  assertThrowsWithCode(
3414
3379
  () => parseMachine('not valid machine notation'),
3415
3380
  MachineSyntaxErrorCodes.PARSE_ERROR
3416
3381
  );
3417
3382
  }
3418
3383
 
3419
- // TEST0120: Machine unterminated bracket fails
3420
- function test0120_Machine_unterminatedBracketFails() {
3384
+ // TEST6323: Machine unterminated bracket fails
3385
+ function test6323_Machine_unterminatedBracketFails() {
3421
3386
  assertThrowsWithCode(
3422
3387
  () => parseMachine('[extract cap:in=media:ext=pdf'),
3423
3388
  MachineSyntaxErrorCodes.PARSE_ERROR
@@ -3426,7 +3391,7 @@ function test0120_Machine_unterminatedBracketFails() {
3426
3391
 
3427
3392
  // --- Machine parser line-based mode tests ---
3428
3393
 
3429
- function test0121_Machine_lineBasedSimpleChain() {
3394
+ function test6327_Machine_lineBasedSimpleChain() {
3430
3395
  const g = Machine.fromString(
3431
3396
  'extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"\n' +
3432
3397
  'doc -> extract -> text'
@@ -3439,8 +3404,8 @@ function test0121_Machine_lineBasedSimpleChain() {
3439
3404
  'Target should be media:enc=utf-8;ext=txt');
3440
3405
  }
3441
3406
 
3442
- // TEST0122: Machine line based two step chain
3443
- function test0122_Machine_lineBasedTwoStepChain() {
3407
+ // TEST6331: Machine line based two step chain
3408
+ function test6331_Machine_lineBasedTwoStepChain() {
3444
3409
  const g = Machine.fromString(
3445
3410
  'extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"\n' +
3446
3411
  'embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"\n' +
@@ -3450,8 +3415,8 @@ function test0122_Machine_lineBasedTwoStepChain() {
3450
3415
  assertEqual(g.edgeCount(), 2);
3451
3416
  }
3452
3417
 
3453
- // TEST0123: Machine line based loop
3454
- function test0123_Machine_lineBasedLoop() {
3418
+ // TEST6334: Machine line based loop
3419
+ function test6334_Machine_lineBasedLoop() {
3455
3420
  const g = Machine.fromString(
3456
3421
  'p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"\n' +
3457
3422
  'pages -> LOOP p2t -> texts'
@@ -3460,8 +3425,8 @@ function test0123_Machine_lineBasedLoop() {
3460
3425
  assertEqual(g.edges()[0].isLoop, true);
3461
3426
  }
3462
3427
 
3463
- // TEST0124: Machine line based fan in
3464
- function test0124_Machine_lineBasedFanIn() {
3428
+ // TEST6337: Machine line based fan in
3429
+ function test6337_Machine_lineBasedFanIn() {
3465
3430
  const g = Machine.fromString(
3466
3431
  'thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:ext=png;image;thumbnail"\n' +
3467
3432
  'model_dl cap:in="media:enc=utf-8;model-spec";download;out="media:enc=utf-8;model-spec"\n' +
@@ -3474,8 +3439,8 @@ function test0124_Machine_lineBasedFanIn() {
3474
3439
  assertEqual(g.edges()[2].sources.length, 2);
3475
3440
  }
3476
3441
 
3477
- // TEST0125: Machine mixed bracketed and line based
3478
- function test0125_Machine_mixedBracketedAndLineBased() {
3442
+ // TEST6341: Machine mixed bracketed and line based
3443
+ function test6341_Machine_mixedBracketedAndLineBased() {
3479
3444
  const g = Machine.fromString(
3480
3445
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n' +
3481
3446
  'doc -> extract -> text'
@@ -3483,8 +3448,8 @@ function test0125_Machine_mixedBracketedAndLineBased() {
3483
3448
  assertEqual(g.edgeCount(), 1);
3484
3449
  }
3485
3450
 
3486
- // TEST0126: Machine line based equivalent to bracketed
3487
- function test0126_Machine_lineBasedEquivalentToBracketed() {
3451
+ // TEST6345: Machine line based equivalent to bracketed
3452
+ function test6345_Machine_lineBasedEquivalentToBracketed() {
3488
3453
  const g1 = Machine.fromString(
3489
3454
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
3490
3455
  '[doc -> extract -> text]'
@@ -3496,8 +3461,8 @@ function test0126_Machine_lineBasedEquivalentToBracketed() {
3496
3461
  assert(g1.isEquivalent(g2), 'Line-based and bracketed must produce equivalent graphs');
3497
3462
  }
3498
3463
 
3499
- // TEST0127: Machine line based format serialization
3500
- function test0127_Machine_lineBasedFormatSerialization() {
3464
+ // TEST6349: Machine line based format serialization
3465
+ function test6349_Machine_lineBasedFormatSerialization() {
3501
3466
  const g = new Machine([
3502
3467
  new MachineEdge(
3503
3468
  [MediaUrn.fromString('media:ext=pdf')],
@@ -3518,8 +3483,8 @@ function test0127_Machine_lineBasedFormatSerialization() {
3518
3483
  assert(g.isEquivalent(reparsed), 'Line-based round-trip must produce equivalent graph');
3519
3484
  }
3520
3485
 
3521
- // TEST0128: Machine line based and bracketed parse same graph
3522
- function test0128_Machine_lineBasedAndBracketedParseSameGraph() {
3486
+ // TEST6353: Machine line based and bracketed parse same graph
3487
+ function test6353_Machine_lineBasedAndBracketedParseSameGraph() {
3523
3488
  const g = new Machine([
3524
3489
  new MachineEdge(
3525
3490
  [MediaUrn.fromString('media:ext=pdf')],
@@ -3545,7 +3510,7 @@ function test0128_Machine_lineBasedAndBracketedParseSameGraph() {
3545
3510
 
3546
3511
  // --- Machine graph tests (mirrors graph.rs tests) ---
3547
3512
 
3548
- function test0129_Machine_edgeEquivalenceSameUrns() {
3513
+ function test6357_Machine_edgeEquivalenceSameUrns() {
3549
3514
  const e1 = new MachineEdge(
3550
3515
  [MediaUrn.fromString('media:ext=pdf')],
3551
3516
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3561,8 +3526,8 @@ function test0129_Machine_edgeEquivalenceSameUrns() {
3561
3526
  assert(e1.isEquivalent(e2), 'Same URNs should be equivalent');
3562
3527
  }
3563
3528
 
3564
- // TEST0130: Machine edge equivalence different cap urns
3565
- function test0130_Machine_edgeEquivalenceDifferentCapUrns() {
3529
+ // TEST6361: Machine edge equivalence different cap urns
3530
+ function test6361_Machine_edgeEquivalenceDifferentCapUrns() {
3566
3531
  const e1 = new MachineEdge(
3567
3532
  [MediaUrn.fromString('media:ext=pdf')],
3568
3533
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3578,8 +3543,8 @@ function test0130_Machine_edgeEquivalenceDifferentCapUrns() {
3578
3543
  assert(!e1.isEquivalent(e2), 'Different cap URNs should not be equivalent');
3579
3544
  }
3580
3545
 
3581
- // TEST0131: Machine edge equivalence different targets
3582
- function test0131_Machine_edgeEquivalenceDifferentTargets() {
3546
+ // TEST6365: Machine edge equivalence different targets
3547
+ function test6365_Machine_edgeEquivalenceDifferentTargets() {
3583
3548
  const e1 = new MachineEdge(
3584
3549
  [MediaUrn.fromString('media:ext=pdf')],
3585
3550
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3595,8 +3560,8 @@ function test0131_Machine_edgeEquivalenceDifferentTargets() {
3595
3560
  assert(!e1.isEquivalent(e2), 'Different targets should not be equivalent');
3596
3561
  }
3597
3562
 
3598
- // TEST0132: Machine edge equivalence different loop flag
3599
- function test0132_Machine_edgeEquivalenceDifferentLoopFlag() {
3563
+ // TEST6369: Machine edge equivalence different loop flag
3564
+ function test6369_Machine_edgeEquivalenceDifferentLoopFlag() {
3600
3565
  const e1 = new MachineEdge(
3601
3566
  [MediaUrn.fromString('media:ext=pdf')],
3602
3567
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3612,8 +3577,8 @@ function test0132_Machine_edgeEquivalenceDifferentLoopFlag() {
3612
3577
  assert(!e1.isEquivalent(e2), 'Different loop flags should not be equivalent');
3613
3578
  }
3614
3579
 
3615
- // TEST0133: Machine edge equivalence source order independent
3616
- function test0133_Machine_edgeEquivalenceSourceOrderIndependent() {
3580
+ // TEST6372: Machine edge equivalence source order independent
3581
+ function test6372_Machine_edgeEquivalenceSourceOrderIndependent() {
3617
3582
  const e1 = new MachineEdge(
3618
3583
  [MediaUrn.fromString('media:enc=utf-8;ext=txt'), MediaUrn.fromString('media:enc=utf-8;model-spec')],
3619
3584
  CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
@@ -3629,8 +3594,8 @@ function test0133_Machine_edgeEquivalenceSourceOrderIndependent() {
3629
3594
  assert(e1.isEquivalent(e2), 'Source order should not matter for equivalence');
3630
3595
  }
3631
3596
 
3632
- // TEST0134: Machine edge equivalence different source count
3633
- function test0134_Machine_edgeEquivalenceDifferentSourceCount() {
3597
+ // TEST6375: Machine edge equivalence different source count
3598
+ function test6375_Machine_edgeEquivalenceDifferentSourceCount() {
3634
3599
  const e1 = new MachineEdge(
3635
3600
  [MediaUrn.fromString('media:enc=utf-8;ext=txt')],
3636
3601
  CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
@@ -3646,8 +3611,8 @@ function test0134_Machine_edgeEquivalenceDifferentSourceCount() {
3646
3611
  assert(!e1.isEquivalent(e2), 'Different source counts should not be equivalent');
3647
3612
  }
3648
3613
 
3649
- // TEST0135: Machine graph equivalence same edges
3650
- function test0135_Machine_graphEquivalenceSameEdges() {
3614
+ // TEST6377: Machine graph equivalence same edges
3615
+ function test6377_Machine_graphEquivalenceSameEdges() {
3651
3616
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3652
3617
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3653
3618
  );
@@ -3662,8 +3627,8 @@ function test0135_Machine_graphEquivalenceSameEdges() {
3662
3627
  assert(g1.isEquivalent(g2), 'Same edges should be equivalent');
3663
3628
  }
3664
3629
 
3665
- // TEST0136: Machine graph equivalence reordered edges
3666
- function test0136_Machine_graphEquivalenceReorderedEdges() {
3630
+ // TEST6380: Machine graph equivalence reordered edges
3631
+ function test6380_Machine_graphEquivalenceReorderedEdges() {
3667
3632
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3668
3633
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3669
3634
  );
@@ -3678,8 +3643,8 @@ function test0136_Machine_graphEquivalenceReorderedEdges() {
3678
3643
  assert(g1.isEquivalent(g2), 'Reordered edges should still be equivalent');
3679
3644
  }
3680
3645
 
3681
- // TEST0137: Machine graph not equivalent different edge count
3682
- function test0137_Machine_graphNotEquivalentDifferentEdgeCount() {
3646
+ // TEST6383: Machine graph not equivalent different edge count
3647
+ function test6383_Machine_graphNotEquivalentDifferentEdgeCount() {
3683
3648
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3684
3649
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3685
3650
  );
@@ -3693,8 +3658,8 @@ function test0137_Machine_graphNotEquivalentDifferentEdgeCount() {
3693
3658
  assert(!g1.isEquivalent(g2), 'Different edge counts should not be equivalent');
3694
3659
  }
3695
3660
 
3696
- // TEST0138: Machine graph not equivalent different cap
3697
- function test0138_Machine_graphNotEquivalentDifferentCap() {
3661
+ // TEST6386: Machine graph not equivalent different cap
3662
+ function test6386_Machine_graphNotEquivalentDifferentCap() {
3698
3663
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3699
3664
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3700
3665
  );
@@ -3707,22 +3672,22 @@ function test0138_Machine_graphNotEquivalentDifferentCap() {
3707
3672
  assert(!g1.isEquivalent(g2), 'Different caps should not be equivalent');
3708
3673
  }
3709
3674
 
3710
- // TEST0139: Machine graph empty
3711
- function test0139_Machine_graphEmpty() {
3675
+ // TEST6389: Machine graph empty
3676
+ function test6389_Machine_graphEmpty() {
3712
3677
  const g = Machine.empty();
3713
3678
  assert(g.isEmpty(), 'Empty graph should be empty');
3714
3679
  assertEqual(g.edgeCount(), 0);
3715
3680
  }
3716
3681
 
3717
- // TEST0140: Machine graph empty equivalence
3718
- function test0140_Machine_graphEmptyEquivalence() {
3682
+ // TEST6392: Machine graph empty equivalence
3683
+ function test6392_Machine_graphEmptyEquivalence() {
3719
3684
  const g1 = Machine.empty();
3720
3685
  const g2 = Machine.empty();
3721
3686
  assert(g1.isEquivalent(g2), 'Two empty graphs should be equivalent');
3722
3687
  }
3723
3688
 
3724
- // TEST0141: Machine root sources linear chain
3725
- function test0141_Machine_rootSourcesLinearChain() {
3689
+ // TEST6395: Machine root sources linear chain
3690
+ function test6395_Machine_rootSourcesLinearChain() {
3726
3691
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3727
3692
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3728
3693
  );
@@ -3736,8 +3701,8 @@ function test0141_Machine_rootSourcesLinearChain() {
3736
3701
  'Root source should be media:ext=pdf');
3737
3702
  }
3738
3703
 
3739
- // TEST0142: Machine leaf targets linear chain
3740
- function test0142_Machine_leafTargetsLinearChain() {
3704
+ // TEST6397: Machine leaf targets linear chain
3705
+ function test6397_Machine_leafTargetsLinearChain() {
3741
3706
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3742
3707
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3743
3708
  );
@@ -3751,8 +3716,8 @@ function test0142_Machine_leafTargetsLinearChain() {
3751
3716
  'Leaf target should be media:embedding-vector;enc=utf-8;record');
3752
3717
  }
3753
3718
 
3754
- // TEST0143: Machine root sources fan in
3755
- function test0143_Machine_rootSourcesFanIn() {
3719
+ // TEST6398: Machine root sources fan in
3720
+ function test6398_Machine_rootSourcesFanIn() {
3756
3721
  const e = new MachineEdge(
3757
3722
  [MediaUrn.fromString('media:enc=utf-8;ext=txt'), MediaUrn.fromString('media:enc=utf-8;model-spec')],
3758
3723
  CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
@@ -3764,8 +3729,8 @@ function test0143_Machine_rootSourcesFanIn() {
3764
3729
  assertEqual(roots.length, 2);
3765
3730
  }
3766
3731
 
3767
- // TEST0144: Machine display edge
3768
- function test0144_Machine_displayEdge() {
3732
+ // TEST6400: Machine display edge
3733
+ function test6400_Machine_displayEdge() {
3769
3734
  const e = new MachineEdge(
3770
3735
  [MediaUrn.fromString('media:ext=pdf')],
3771
3736
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3776,8 +3741,8 @@ function test0144_Machine_displayEdge() {
3776
3741
  assert(display.includes('media:ext=pdf'), 'Display should contain media:ext=pdf');
3777
3742
  }
3778
3743
 
3779
- // TEST0145: Machine display graph
3780
- function test0145_Machine_displayGraph() {
3744
+ // TEST6402: Machine display graph
3745
+ function test6402_Machine_displayGraph() {
3781
3746
  const e = new MachineEdge(
3782
3747
  [MediaUrn.fromString('media:ext=pdf')],
3783
3748
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3790,7 +3755,7 @@ function test0145_Machine_displayGraph() {
3790
3755
 
3791
3756
  // --- Machine serializer tests (mirrors serializer.rs tests) ---
3792
3757
 
3793
- function test0146_Machine_serializeSingleEdge() {
3758
+ function test6404_Machine_serializeSingleEdge() {
3794
3759
  const g = new Machine([new MachineEdge(
3795
3760
  [MediaUrn.fromString('media:ext=pdf')],
3796
3761
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3805,8 +3770,8 @@ function test0146_Machine_serializeSingleEdge() {
3805
3770
  assert(notation.includes('-> n1]'), 'Should use n1 for target: ' + notation);
3806
3771
  }
3807
3772
 
3808
- // TEST0147: Machine serialize two edge chain
3809
- function test0147_Machine_serializeTwoEdgeChain() {
3773
+ // TEST6406: Machine serialize two edge chain
3774
+ function test6406_Machine_serializeTwoEdgeChain() {
3810
3775
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3811
3776
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3812
3777
  );
@@ -3819,13 +3784,13 @@ function test0147_Machine_serializeTwoEdgeChain() {
3819
3784
  assertEqual(bracketCount, 4, 'Should have 4 brackets (2 headers + 2 wirings)');
3820
3785
  }
3821
3786
 
3822
- // TEST0148: Machine serialize empty graph
3823
- function test0148_Machine_serializeEmptyGraph() {
3787
+ // TEST6408: Machine serialize empty graph
3788
+ function test6408_Machine_serializeEmptyGraph() {
3824
3789
  assertEqual(Machine.empty().toMachineNotation(), '');
3825
3790
  }
3826
3791
 
3827
- // TEST0149: Machine roundtrip single edge
3828
- function test0149_Machine_roundtripSingleEdge() {
3792
+ // TEST6410: Machine roundtrip single edge
3793
+ function test6410_Machine_roundtripSingleEdge() {
3829
3794
  const original = new Machine([new MachineEdge(
3830
3795
  [MediaUrn.fromString('media:ext=pdf')],
3831
3796
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3838,8 +3803,8 @@ function test0149_Machine_roundtripSingleEdge() {
3838
3803
  'Single edge round-trip failed: ' + notation);
3839
3804
  }
3840
3805
 
3841
- // TEST0151: Machine roundtrip two edge chain
3842
- function test0151_Machine_roundtripTwoEdgeChain() {
3806
+ // TEST6413: Machine roundtrip two edge chain
3807
+ function test6413_Machine_roundtripTwoEdgeChain() {
3843
3808
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3844
3809
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3845
3810
  );
@@ -3853,8 +3818,8 @@ function test0151_Machine_roundtripTwoEdgeChain() {
3853
3818
  'Two-edge chain round-trip failed: ' + notation);
3854
3819
  }
3855
3820
 
3856
- // TEST0152: Machine roundtrip fan out
3857
- function test0152_Machine_roundtripFanOut() {
3821
+ // TEST6415: Machine roundtrip fan out
3822
+ function test6415_Machine_roundtripFanOut() {
3858
3823
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3859
3824
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3860
3825
  );
@@ -3869,8 +3834,8 @@ function test0152_Machine_roundtripFanOut() {
3869
3834
  'Fan-out round-trip failed: ' + notation);
3870
3835
  }
3871
3836
 
3872
- // TEST0153: Machine roundtrip loop edge
3873
- function test0153_Machine_roundtripLoopEdge() {
3837
+ // TEST6417: Machine roundtrip loop edge
3838
+ function test6417_Machine_roundtripLoopEdge() {
3874
3839
  const original = new Machine([new MachineEdge(
3875
3840
  [MediaUrn.fromString('media:disbound-page;enc=utf-8')],
3876
3841
  CapUrn.fromString('cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"'),
@@ -3883,8 +3848,8 @@ function test0153_Machine_roundtripLoopEdge() {
3883
3848
  assertEqual(reparsed.edges()[0].isLoop, true, 'isLoop must be preserved');
3884
3849
  }
3885
3850
 
3886
- // TEST0154: Machine serialization is deterministic
3887
- function test0154_Machine_serializationIsDeterministic() {
3851
+ // TEST6419: Machine serialization is deterministic
3852
+ function test6419_Machine_serializationIsDeterministic() {
3888
3853
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3889
3854
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3890
3855
  );
@@ -3897,8 +3862,8 @@ function test0154_Machine_serializationIsDeterministic() {
3897
3862
  assertEqual(n1, n2, 'Serialization must be deterministic');
3898
3863
  }
3899
3864
 
3900
- // TEST0155: Machine reordered edges produce same notation
3901
- function test0155_Machine_reorderedEdgesProduceSameNotation() {
3865
+ // TEST6421: Machine reordered edges produce same notation
3866
+ function test6421_Machine_reorderedEdgesProduceSameNotation() {
3902
3867
  const mkEdge = (src, cap, tgt) => new MachineEdge(
3903
3868
  [MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
3904
3869
  );
@@ -3914,8 +3879,8 @@ function test0155_Machine_reorderedEdgesProduceSameNotation() {
3914
3879
  'Same graph with reordered edges must produce identical notation');
3915
3880
  }
3916
3881
 
3917
- // TEST0160: Machine multiline serialize format
3918
- function test0160_Machine_multilineSerializeFormat() {
3882
+ // TEST6429: Machine multiline serialize format
3883
+ function test6429_Machine_multilineSerializeFormat() {
3919
3884
  const g = new Machine([new MachineEdge(
3920
3885
  [MediaUrn.fromString('media:ext=pdf')],
3921
3886
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3931,7 +3896,7 @@ function test0160_Machine_multilineSerializeFormat() {
3931
3896
 
3932
3897
  // Aliases are pure-index `edge_<N>` regardless of the cap's tags; there is
3933
3898
  // no privileged `op` tag to derive a friendlier name from.
3934
- function test0161_Machine_aliasFromOpTag() {
3899
+ function test6432_Machine_aliasFromOpTag() {
3935
3900
  const g = new Machine([new MachineEdge(
3936
3901
  [MediaUrn.fromString('media:ext=pdf')],
3937
3902
  CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
@@ -3942,8 +3907,8 @@ function test0161_Machine_aliasFromOpTag() {
3942
3907
  assert(notation.includes('[edge_0 '), 'Expected edge_0 alias, got: ' + notation);
3943
3908
  }
3944
3909
 
3945
- // TEST0162: Machine alias fallback without op tag
3946
- function test0162_Machine_aliasFallbackWithoutOpTag() {
3910
+ // TEST6434: Machine alias fallback without op tag
3911
+ function test6434_Machine_aliasFallbackWithoutOpTag() {
3947
3912
  const g = new Machine([new MachineEdge(
3948
3913
  [MediaUrn.fromString('media:ext=pdf')],
3949
3914
  CapUrn.fromString('cap:in="media:ext=pdf";out="media:enc=utf-8;ext=txt"'),
@@ -3955,7 +3920,7 @@ function test0162_Machine_aliasFallbackWithoutOpTag() {
3955
3920
  }
3956
3921
 
3957
3922
  // Pure-index aliases inherently disambiguate edges that share a marker tag.
3958
- function test0163_Machine_duplicateOpTagsDisambiguated() {
3923
+ function test6436_Machine_duplicateOpTagsDisambiguated() {
3959
3924
  const g = new Machine([
3960
3925
  new MachineEdge(
3961
3926
  [MediaUrn.fromString('media:ext=pdf')],
@@ -3977,7 +3942,7 @@ function test0163_Machine_duplicateOpTagsDisambiguated() {
3977
3942
 
3978
3943
  // --- Machine builder tests ---
3979
3944
 
3980
- function test0164_Machine_builderSingleEdge() {
3945
+ function test6437_Machine_builderSingleEdge() {
3981
3946
  const builder = new MachineBuilder();
3982
3947
  builder.addEdge(
3983
3948
  ['media:ext=pdf'],
@@ -3989,8 +3954,8 @@ function test0164_Machine_builderSingleEdge() {
3989
3954
  assertEqual(g.edges()[0].isLoop, false);
3990
3955
  }
3991
3956
 
3992
- // TEST0165: Machine builder with loop
3993
- function test0165_Machine_builderWithLoop() {
3957
+ // TEST6438: Machine builder with loop
3958
+ function test6438_Machine_builderWithLoop() {
3994
3959
  const builder = new MachineBuilder();
3995
3960
  builder.addEdge(
3996
3961
  ['media:disbound-page;enc=utf-8'],
@@ -4002,8 +3967,8 @@ function test0165_Machine_builderWithLoop() {
4002
3967
  assertEqual(g.edges()[0].isLoop, true);
4003
3968
  }
4004
3969
 
4005
- // TEST0166: Machine builder chaining
4006
- function test0166_Machine_builderChaining() {
3970
+ // TEST6439: Machine builder chaining
3971
+ function test6439_Machine_builderChaining() {
4007
3972
  const g = new MachineBuilder()
4008
3973
  .addEdge(['media:ext=pdf'], 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt')
4009
3974
  .addEdge(['media:enc=utf-8;ext=txt'], 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record')
@@ -4011,8 +3976,8 @@ function test0166_Machine_builderChaining() {
4011
3976
  assertEqual(g.edgeCount(), 2);
4012
3977
  }
4013
3978
 
4014
- // TEST0167: Machine builder equivalent to parsed
4015
- function test0167_Machine_builderEquivalentToParsed() {
3979
+ // TEST6440: Machine builder equivalent to parsed
3980
+ function test6440_Machine_builderEquivalentToParsed() {
4016
3981
  const parsed = Machine.fromString(
4017
3982
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
4018
3983
  '[doc -> extract -> text]'
@@ -4024,8 +3989,8 @@ function test0167_Machine_builderEquivalentToParsed() {
4024
3989
  'Builder-constructed graph should be equivalent to parsed graph');
4025
3990
  }
4026
3991
 
4027
- // TEST0168: Machine builder round trip
4028
- function test0168_Machine_builderRoundTrip() {
3992
+ // TEST6442: Machine builder round trip
3993
+ function test6442_Machine_builderRoundTrip() {
4029
3994
  const built = new MachineBuilder()
4030
3995
  .addEdge(['media:ext=pdf'], 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt')
4031
3996
  .addEdge(['media:enc=utf-8;ext=txt'], 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record')
@@ -4037,7 +4002,7 @@ function test0168_Machine_builderRoundTrip() {
4037
4002
 
4038
4003
  // --- CapUrn.isEquivalent/isComparable tests ---
4039
4004
 
4040
- function test0169_Machine_capUrnIsEquivalent() {
4005
+ function test6444_Machine_capUrnIsEquivalent() {
4041
4006
  const a = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
4042
4007
  const b = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
4043
4008
  assert(a.isEquivalent(b), 'Same cap URNs should be equivalent');
@@ -4045,24 +4010,24 @@ function test0169_Machine_capUrnIsEquivalent() {
4045
4010
  assert(!a.isEquivalent(c), 'Different cap URNs should not be equivalent');
4046
4011
  }
4047
4012
 
4048
- // TEST0170: Machine cap urn is comparable
4049
- function test0170_Machine_capUrnIsComparable() {
4013
+ // TEST6446: Machine cap urn is comparable
4014
+ function test6446_Machine_capUrnIsComparable() {
4050
4015
  const general = CapUrn.fromString('cap:in="media:ext=pdf";out="media:enc=utf-8;ext=txt"');
4051
4016
  const specific = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
4052
4017
  assert(general.isComparable(specific), 'General should be comparable to specific');
4053
4018
  assert(specific.isComparable(general), 'isComparable should be symmetric');
4054
4019
  }
4055
4020
 
4056
- // TEST0171: Machine cap urn in media urn
4057
- function test0171_Machine_capUrnInMediaUrn() {
4021
+ // TEST6448: Machine cap urn in media urn
4022
+ function test6448_Machine_capUrnInMediaUrn() {
4058
4023
  const cap = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
4059
4024
  const inUrn = cap.inMediaUrn();
4060
4025
  assert(inUrn instanceof MediaUrn, 'inMediaUrn should return MediaUrn');
4061
4026
  assert(inUrn.isEquivalent(MediaUrn.fromString('media:ext=pdf')), 'inMediaUrn should be media:ext=pdf');
4062
4027
  }
4063
4028
 
4064
- // TEST0172: Machine cap urn out media urn
4065
- function test0172_Machine_capUrnOutMediaUrn() {
4029
+ // TEST6449: Machine cap urn out media urn
4030
+ function test6449_Machine_capUrnOutMediaUrn() {
4066
4031
  const cap = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
4067
4032
  const outUrn = cap.outMediaUrn();
4068
4033
  assert(outUrn instanceof MediaUrn, 'outMediaUrn should return MediaUrn');
@@ -4071,7 +4036,7 @@ function test0172_Machine_capUrnOutMediaUrn() {
4071
4036
 
4072
4037
  // --- MediaUrn.isEquivalent/isComparable tests ---
4073
4038
 
4074
- function test0173_Machine_mediaUrnIsEquivalent() {
4039
+ function test6450_Machine_mediaUrnIsEquivalent() {
4075
4040
  const a = MediaUrn.fromString('media:ext=pdf');
4076
4041
  const b = MediaUrn.fromString('media:ext=pdf');
4077
4042
  assert(a.isEquivalent(b), 'Same media URNs should be equivalent');
@@ -4079,8 +4044,8 @@ function test0173_Machine_mediaUrnIsEquivalent() {
4079
4044
  assert(!a.isEquivalent(c), 'Different media URNs should not be equivalent');
4080
4045
  }
4081
4046
 
4082
- // TEST0174: Machine media urn is comparable
4083
- function test0174_Machine_mediaUrnIsComparable() {
4047
+ // TEST6451: Machine media urn is comparable
4048
+ function test6451_Machine_mediaUrnIsComparable() {
4084
4049
  const general = MediaUrn.fromString('media:enc=utf-8');
4085
4050
  const specific = MediaUrn.fromString('media:enc=utf-8;ext=txt');
4086
4051
  assert(general.isComparable(specific), 'General should be comparable to specific');
@@ -4093,7 +4058,7 @@ function test0174_Machine_mediaUrnIsComparable() {
4093
4058
  // Phase 0A: Position tracking tests
4094
4059
  // ============================================================================
4095
4060
 
4096
- function test0175_Machine_parseMachineWithAST_headerLocation() {
4061
+ function test6452_Machine_parseMachineWithAST_headerLocation() {
4097
4062
  const input = '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"][doc -> extract -> text]';
4098
4063
  const result = parseMachineWithAST(input);
4099
4064
  assert(result.statements.length === 2, 'Should have 2 statements');
@@ -4109,8 +4074,8 @@ function test0175_Machine_parseMachineWithAST_headerLocation() {
4109
4074
  assertEqual(stmt.alias, 'extract', 'Alias should be extract');
4110
4075
  }
4111
4076
 
4112
- // TEST0176: Machine parse machine with a s t wiring location
4113
- function test0176_Machine_parseMachineWithAST_wiringLocation() {
4077
+ // TEST6453: Machine parse machine with a s t wiring location
4078
+ function test6453_Machine_parseMachineWithAST_wiringLocation() {
4114
4079
  const input = '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n[doc -> extract -> text]';
4115
4080
  const result = parseMachineWithAST(input);
4116
4081
  assert(result.statements.length === 2, 'Should have 2 statements');
@@ -4124,8 +4089,8 @@ function test0176_Machine_parseMachineWithAST_wiringLocation() {
4124
4089
  assertEqual(wiring.target, 'text', 'Target should be text');
4125
4090
  }
4126
4091
 
4127
- // TEST0177: Machine parse machine with a s t multiline positions
4128
- function test0177_Machine_parseMachineWithAST_multilinePositions() {
4092
+ // TEST6454: Machine parse machine with a s t multiline positions
4093
+ function test6454_Machine_parseMachineWithAST_multilinePositions() {
4129
4094
  const input = '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n[doc -> extract -> text]';
4130
4095
  const result = parseMachineWithAST(input);
4131
4096
  const headerLoc = result.statements[0].location;
@@ -4134,8 +4099,8 @@ function test0177_Machine_parseMachineWithAST_multilinePositions() {
4134
4099
  assertEqual(wiringLoc.start.line, 2, 'Wiring should be on line 2');
4135
4100
  }
4136
4101
 
4137
- // TEST0178: Machine parse machine with a s t fan in source locations
4138
- function test0178_Machine_parseMachineWithAST_fanInSourceLocations() {
4102
+ // TEST6455: Machine parse machine with a s t fan in source locations
4103
+ function test6455_Machine_parseMachineWithAST_fanInSourceLocations() {
4139
4104
  const input = [
4140
4105
  '[describe cap:in="media:ext=png;image";describe-image;out="media:enc=utf-8;image-description"]',
4141
4106
  '[(thumbnail, model_spec) -> describe -> description]'
@@ -4146,8 +4111,8 @@ function test0178_Machine_parseMachineWithAST_fanInSourceLocations() {
4146
4111
  assert(wiring.sourceLocations.length === 2, 'Should have 2 source locations');
4147
4112
  }
4148
4113
 
4149
- // TEST0179: Machine parse machine with a s t alias map
4150
- function test0179_Machine_parseMachineWithAST_aliasMap() {
4114
+ // TEST6456: Machine parse machine with a s t alias map
4115
+ function test6456_Machine_parseMachineWithAST_aliasMap() {
4151
4116
  const input = [
4152
4117
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]',
4153
4118
  '[embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"]',
@@ -4165,8 +4130,8 @@ function test0179_Machine_parseMachineWithAST_aliasMap() {
4165
4130
  assert(extractEntry.capUrnLocation !== undefined, 'Alias entry should have capUrnLocation');
4166
4131
  }
4167
4132
 
4168
- // TEST0180: Machine parse machine with a s t node media
4169
- function test0180_Machine_parseMachineWithAST_nodeMedia() {
4133
+ // TEST6457: Machine parse machine with a s t node media
4134
+ function test6457_Machine_parseMachineWithAST_nodeMedia() {
4170
4135
  const input = [
4171
4136
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]',
4172
4137
  '[doc -> extract -> text]',
@@ -4178,8 +4143,8 @@ function test0180_Machine_parseMachineWithAST_nodeMedia() {
4178
4143
  assertEqual(result.nodeMedia.get('text').toString(), 'media:enc=utf-8;ext=txt', 'text should be media:enc=utf-8;ext=txt');
4179
4144
  }
4180
4145
 
4181
- // TEST0181: Machine error location parse error
4182
- function test0181_Machine_errorLocation_parseError() {
4146
+ // TEST6458: Machine error location parse error
4147
+ function test6458_Machine_errorLocation_parseError() {
4183
4148
  try {
4184
4149
  parseMachine('[this is not valid');
4185
4150
  throw new Error('Expected MachineSyntaxError');
@@ -4189,8 +4154,8 @@ function test0181_Machine_errorLocation_parseError() {
4189
4154
  }
4190
4155
  }
4191
4156
 
4192
- // TEST0182: Machine error location duplicate alias
4193
- function test0182_Machine_errorLocation_duplicateAlias() {
4157
+ // TEST6459: Machine error location duplicate alias
4158
+ function test6459_Machine_errorLocation_duplicateAlias() {
4194
4159
  try {
4195
4160
  parseMachine(
4196
4161
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
@@ -4204,8 +4169,8 @@ function test0182_Machine_errorLocation_duplicateAlias() {
4204
4169
  }
4205
4170
  }
4206
4171
 
4207
- // TEST0183: Machine error location undefined alias
4208
- function test0183_Machine_errorLocation_undefinedAlias() {
4172
+ // TEST6460: Machine error location undefined alias
4173
+ function test6460_Machine_errorLocation_undefinedAlias() {
4209
4174
  try {
4210
4175
  parseMachine('[doc -> nonexistent -> text]');
4211
4176
  throw new Error('Expected MachineSyntaxError');
@@ -4219,7 +4184,7 @@ function test0183_Machine_errorLocation_undefinedAlias() {
4219
4184
  // Phase 0C: Machine.toMermaid() tests
4220
4185
  // ============================================================================
4221
4186
 
4222
- function test0184_Machine_toMermaid_linearChain() {
4187
+ function test6462_Machine_toMermaid_linearChain() {
4223
4188
  const machine = Machine.fromString(
4224
4189
  '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
4225
4190
  '[doc -> extract -> text]'
@@ -4237,8 +4202,8 @@ function test0184_Machine_toMermaid_linearChain() {
4237
4202
  assert(mermaid.includes('(['), 'Should have stadium shape nodes');
4238
4203
  }
4239
4204
 
4240
- // TEST0185: Machine to mermaid loop edge
4241
- function test0185_Machine_toMermaid_loopEdge() {
4205
+ // TEST6463: Machine to mermaid loop edge
4206
+ function test6463_Machine_toMermaid_loopEdge() {
4242
4207
  const machine = Machine.fromString(
4243
4208
  '[p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"]' +
4244
4209
  '[pages -> LOOP p2t -> texts]'
@@ -4249,15 +4214,15 @@ function test0185_Machine_toMermaid_loopEdge() {
4249
4214
  assert(mermaid.includes('.->'), 'Should use dotted arrow for LOOP');
4250
4215
  }
4251
4216
 
4252
- // TEST0186: Machine to mermaid empty graph
4253
- function test0186_Machine_toMermaid_emptyGraph() {
4217
+ // TEST6464: Machine to mermaid empty graph
4218
+ function test6464_Machine_toMermaid_emptyGraph() {
4254
4219
  const machine = Machine.empty();
4255
4220
  const mermaid = machine.toMermaid();
4256
4221
  assert(mermaid.includes('empty graph'), 'Should indicate empty graph');
4257
4222
  }
4258
4223
 
4259
- // TEST0187: Machine to mermaid fan in
4260
- function test0187_Machine_toMermaid_fanIn() {
4224
+ // TEST6465: Machine to mermaid fan in
4225
+ function test6465_Machine_toMermaid_fanIn() {
4261
4226
  const machine = Machine.fromString(
4262
4227
  '[describe cap:in="media:ext=png;image";describe-image;out="media:enc=utf-8;image-description"]' +
4263
4228
  '[(thumbnail, model_spec) -> describe -> description]'
@@ -4268,8 +4233,8 @@ function test0187_Machine_toMermaid_fanIn() {
4268
4233
  assertEqual(arrowCount, 2, 'Fan-in should produce 2 arrows');
4269
4234
  }
4270
4235
 
4271
- // TEST0188: Machine to mermaid fan out
4272
- function test0188_Machine_toMermaid_fanOut() {
4236
+ // TEST6466: Machine to mermaid fan out
4237
+ function test6466_Machine_toMermaid_fanOut() {
4273
4238
  const input = [
4274
4239
  '[meta cap:in="media:ext=pdf";extract-metadata;out="media:enc=utf-8;file-metadata;record"]',
4275
4240
  '[thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:ext=png;image;thumbnail"]',
@@ -4289,7 +4254,7 @@ function test0188_Machine_toMermaid_fanOut() {
4289
4254
  // Phase 0B: FabricRegistryClient tests
4290
4255
  // ============================================================================
4291
4256
 
4292
- function test0189_Machine_capRegistryEntry_construction() {
4257
+ function test6467_Machine_capRegistryEntry_construction() {
4293
4258
  const entry = new FabricRegistryEntry({
4294
4259
  urn: 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"',
4295
4260
  title: 'PDF Extractor',
@@ -4312,8 +4277,8 @@ function test0189_Machine_capRegistryEntry_construction() {
4312
4277
  assertEqual(entry.urnTags.op, 'extract', 'op tag should match');
4313
4278
  }
4314
4279
 
4315
- // TEST0190: Machine media registry entry construction
4316
- function test0190_Machine_mediaRegistryEntry_construction() {
4280
+ // TEST6468: Machine media registry entry construction
4281
+ function test6468_Machine_mediaRegistryEntry_construction() {
4317
4282
  const entry = new MediaRegistryEntry({
4318
4283
  urn: 'media:ext=pdf',
4319
4284
  title: 'PDF Document',
@@ -4326,16 +4291,16 @@ function test0190_Machine_mediaRegistryEntry_construction() {
4326
4291
  assertEqual(entry.description, 'Portable Document Format', 'Description should match');
4327
4292
  }
4328
4293
 
4329
- // TEST0191: Machine cap registry client construction
4330
- function test0191_Machine_capRegistryClient_construction() {
4294
+ // TEST6469: Machine cap registry client construction
4295
+ function test6469_Machine_capRegistryClient_construction() {
4331
4296
  const client = new FabricRegistryClient('https://example.com', 600);
4332
4297
  assert(client !== null, 'Client should be constructed');
4333
4298
  // Invalidate should not throw
4334
4299
  client.invalidate();
4335
4300
  }
4336
4301
 
4337
- // TEST0192: Machine cap registry entry defaults
4338
- function test0192_Machine_capRegistryEntry_defaults() {
4302
+ // TEST6470: Machine cap registry entry defaults
4303
+ function test6470_Machine_capRegistryEntry_defaults() {
4339
4304
  // Verify that missing fields default gracefully
4340
4305
  const entry = new FabricRegistryEntry({ urn: 'cap:in=media:;test;out=media:' });
4341
4306
  assertEqual(entry.urn, 'cap:in=media:;test;out=media:', 'URN should match');
@@ -4400,16 +4365,16 @@ if (typeof global.createCap === 'undefined') {
4400
4365
  global.createCap = require('./capdag.js').createCap;
4401
4366
  }
4402
4367
 
4403
- // TEST0193: Renderer cardinality label all four cases
4404
- function test0193_Renderer_cardinalityLabel_allFourCases() {
4368
+ // TEST6471: Renderer cardinality label all four cases
4369
+ function test6471_Renderer_cardinalityLabel_allFourCases() {
4405
4370
  assertEqual(rendererCardinalityLabel(false, false), '1\u21921', 'scalar-to-scalar');
4406
4371
  assertEqual(rendererCardinalityLabel(true, false), 'n\u21921', 'sequence-to-scalar');
4407
4372
  assertEqual(rendererCardinalityLabel(false, true), '1\u2192n', 'scalar-to-sequence');
4408
4373
  assertEqual(rendererCardinalityLabel(true, true), 'n\u2192n', 'sequence-to-sequence');
4409
4374
  }
4410
4375
 
4411
- // TEST0194: Renderer cardinality label uses unicode arrow
4412
- function test0194_Renderer_cardinalityLabel_usesUnicodeArrow() {
4376
+ // TEST6472: Renderer cardinality label uses unicode arrow
4377
+ function test6472_Renderer_cardinalityLabel_usesUnicodeArrow() {
4413
4378
  // The label must use the real rightwards arrow (U+2192), not ASCII "->".
4414
4379
  // Downstream styling and tests depend on this glyph.
4415
4380
  const label = rendererCardinalityLabel(false, true);
@@ -4417,8 +4382,8 @@ function test0194_Renderer_cardinalityLabel_usesUnicodeArrow() {
4417
4382
  assert(!label.includes('->'), 'label must not contain the ASCII replacement "->"');
4418
4383
  }
4419
4384
 
4420
- // TEST0195: Renderer cardinality from cap finds stdin arg not first arg
4421
- function test0195_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
4385
+ // TEST6473: Renderer cardinality from cap finds stdin arg not first arg
4386
+ function test6473_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
4422
4387
  // The main input arg is the one whose sources include a stdin source.
4423
4388
  // A naive implementation that reads args[0] would see `cli-only` (not a
4424
4389
  // sequence) and report 1→1 even though the stdin arg is a sequence.
@@ -4442,8 +4407,8 @@ function test0195_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
4442
4407
  'must pick the arg that has a stdin source, not args[0]');
4443
4408
  }
4444
4409
 
4445
- // TEST0196: Renderer cardinality from cap scalar defaults when fields missing
4446
- function test0196_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
4410
+ // TEST6474: Renderer cardinality from cap scalar defaults when fields missing
4411
+ function test6474_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
4447
4412
  // No args and no output: both sides collapse to 1 (scalar default).
4448
4413
  // If a bug makes the function return "n" for missing data, this fails.
4449
4414
  const cap = { urn: 'cap:in="media:";noop;out="media:"' };
@@ -4451,8 +4416,8 @@ function test0196_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing()
4451
4416
  'missing args/output must default to scalar on both sides');
4452
4417
  }
4453
4418
 
4454
- // TEST0197: Renderer cardinality from cap output only sequence
4455
- function test0197_Renderer_cardinalityFromCap_outputOnlySequence() {
4419
+ // TEST6475: Renderer cardinality from cap output only sequence
4420
+ function test6475_Renderer_cardinalityFromCap_outputOnlySequence() {
4456
4421
  // One scalar stdin arg, output is a sequence: expects 1→n.
4457
4422
  const cap = {
4458
4423
  urn: 'cap:in="media:enc=utf-8";generate;out="media:enc=utf-8;list"',
@@ -4463,8 +4428,8 @@ function test0197_Renderer_cardinalityFromCap_outputOnlySequence() {
4463
4428
  'scalar stdin with sequence output must yield 1→n');
4464
4429
  }
4465
4430
 
4466
- // TEST0198: Renderer cardinality from cap rejects string is sequence
4467
- function test0198_Renderer_cardinalityFromCap_rejectsStringIsSequence() {
4431
+ // TEST6476: Renderer cardinality from cap rejects string is sequence
4432
+ function test6476_Renderer_cardinalityFromCap_rejectsStringIsSequence() {
4468
4433
  // The function must use strict `=== true` to avoid treating truthy strings
4469
4434
  // as booleans. "true" is a string, not a boolean — it must NOT be treated
4470
4435
  // as a sequence, because downstream renderers expect boolean semantics.
@@ -4477,8 +4442,8 @@ function test0198_Renderer_cardinalityFromCap_rejectsStringIsSequence() {
4477
4442
  'string "true" must not be treated as boolean true');
4478
4443
  }
4479
4444
 
4480
- // TEST0199: Renderer cardinality from cap throws on non object
4481
- function test0199_Renderer_cardinalityFromCap_throwsOnNonObject() {
4445
+ // TEST6478: Renderer cardinality from cap throws on non object
4446
+ function test6478_Renderer_cardinalityFromCap_throwsOnNonObject() {
4482
4447
  // Fail-hard on invalid input; no fallback to a default cardinality.
4483
4448
  let threw = false;
4484
4449
  try {
@@ -4497,8 +4462,8 @@ function test0199_Renderer_cardinalityFromCap_throwsOnNonObject() {
4497
4462
  assert(threw, 'cardinalityFromCap(string) must throw');
4498
4463
  }
4499
4464
 
4500
- // TEST0200: Renderer canonical media urn normalizes tag order
4501
- function test0200_Renderer_canonicalMediaUrn_normalizesTagOrder() {
4465
+ // TEST6479: Renderer canonical media urn normalizes tag order
4466
+ function test6479_Renderer_canonicalMediaUrn_normalizesTagOrder() {
4502
4467
  // Two media URNs with identical tags in different input orders must
4503
4468
  // produce the same canonical string. If canonicalization is bypassed,
4504
4469
  // the two strings remain different and this test exposes it.
@@ -4507,14 +4472,14 @@ function test0200_Renderer_canonicalMediaUrn_normalizesTagOrder() {
4507
4472
  assertEqual(a, b, 'tag-order differences must not survive canonicalization');
4508
4473
  }
4509
4474
 
4510
- // TEST0201: Renderer canonical media urn preserves value tags
4511
- function test0201_Renderer_canonicalMediaUrn_preservesValueTags() {
4475
+ // TEST6480: Renderer canonical media urn preserves value tags
4476
+ function test6480_Renderer_canonicalMediaUrn_preservesValueTags() {
4512
4477
  const c = rendererCanonicalMediaUrn('media:model;quant=q4');
4513
4478
  assert(c.includes('quant=q4'), 'value tag must be preserved');
4514
4479
  }
4515
4480
 
4516
- // TEST0202: Renderer canonical media urn rejects cap urn
4517
- function test0202_Renderer_canonicalMediaUrn_rejectsCapUrn() {
4481
+ // TEST6481: Renderer canonical media urn rejects cap urn
4482
+ function test6481_Renderer_canonicalMediaUrn_rejectsCapUrn() {
4518
4483
  // MediaUrn.fromString enforces the media: prefix. Feeding a cap URN to
4519
4484
  // canonicalMediaUrn must fail hard.
4520
4485
  let threw = false;
@@ -4526,8 +4491,8 @@ function test0202_Renderer_canonicalMediaUrn_rejectsCapUrn() {
4526
4491
  assert(threw, 'canonicalMediaUrn must reject non-media URNs');
4527
4492
  }
4528
4493
 
4529
- // TEST0203: Renderer media node label rejects urn derived labels
4530
- function test0203_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
4494
+ // TEST6482: Renderer media node label rejects urn derived labels
4495
+ function test6482_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
4531
4496
  let threw = false;
4532
4497
  let message = '';
4533
4498
  try {
@@ -4541,8 +4506,8 @@ function test0203_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
4541
4506
  'error must explain that explicit titles are required');
4542
4507
  }
4543
4508
 
4544
- // TEST0204: Renderer build browse graph data rejects missing media titles
4545
- function test0204_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles() {
4509
+ // TEST6483: Renderer build browse graph data rejects missing media titles
4510
+ function test6483_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles() {
4546
4511
  let threw = false;
4547
4512
  let message = '';
4548
4513
  try {
@@ -4599,8 +4564,8 @@ function makeCollectStep(mediaDef) {
4599
4564
  };
4600
4565
  }
4601
4566
 
4602
- // TEST0205: Renderer validate strand step rejects unknown variant
4603
- function test0205_Renderer_validateStrandStep_rejectsUnknownVariant() {
4567
+ // TEST6484: Renderer validate strand step rejects unknown variant
4568
+ function test6484_Renderer_validateStrandStep_rejectsUnknownVariant() {
4604
4569
  // A step with an unknown variant must fail hard at validation; no
4605
4570
  // silent coercion.
4606
4571
  let threw = false;
@@ -4617,8 +4582,8 @@ function test0205_Renderer_validateStrandStep_rejectsUnknownVariant() {
4617
4582
  assert(threw, 'unknown variant must throw');
4618
4583
  }
4619
4584
 
4620
- // TEST0206: Renderer validate strand step requires boolean is sequence
4621
- function test0206_Renderer_validateStrandStep_requiresBooleanIsSequence() {
4585
+ // TEST6486: Renderer validate strand step requires boolean is sequence
4586
+ function test6486_Renderer_validateStrandStep_requiresBooleanIsSequence() {
4622
4587
  // A Cap variant must have boolean is_sequence fields; number or string
4623
4588
  // must reject.
4624
4589
  let threw = false;
@@ -4640,8 +4605,8 @@ function test0206_Renderer_validateStrandStep_requiresBooleanIsSequence() {
4640
4605
  assert(threw, 'non-boolean is_sequence must throw');
4641
4606
  }
4642
4607
 
4643
- // TEST0207: Renderer classify strand cap steps cap flags
4644
- function test0207_Renderer_classifyStrandCapSteps_capFlags() {
4608
+ // TEST6487: Renderer classify strand cap steps cap flags
4609
+ function test6487_Renderer_classifyStrandCapSteps_capFlags() {
4645
4610
  // Strand: ForEach → cap1 → cap2 → cap3 → Collect. cap1 should have
4646
4611
  // prevForEach=true; cap3 should have nextCollect=true; cap2 should
4647
4612
  // have neither.
@@ -4662,8 +4627,8 @@ function test0207_Renderer_classifyStrandCapSteps_capFlags() {
4662
4627
  assert(capFlags.get(3).nextCollect, 'cap3 has nextCollect');
4663
4628
  }
4664
4629
 
4665
- // TEST0208: Renderer classify strand cap steps nested forks
4666
- function test0208_Renderer_classifyStrandCapSteps_nestedForks() {
4630
+ // TEST6488: Renderer classify strand cap steps nested forks
4631
+ function test6488_Renderer_classifyStrandCapSteps_nestedForks() {
4667
4632
  // Nested strand: ForEach → cap1 → ForEach → cap2 → Collect → cap3 → Collect.
4668
4633
  // cap1 has prevForEach (outer), cap2 has prevForEach (inner) and
4669
4634
  // nextCollect (inner), cap3 has nextCollect (outer).
@@ -4693,8 +4658,8 @@ function withMediaDisplayNames(payload, mediaDisplayNames) {
4693
4658
  });
4694
4659
  }
4695
4660
 
4696
- // TEST0209: Renderer build strand graph data single cap plain
4697
- function test0209_Renderer_buildStrandGraphData_singleCapPlain() {
4661
+ // TEST6489: Renderer build strand graph data single cap plain
4662
+ function test6489_Renderer_buildStrandGraphData_singleCapPlain() {
4698
4663
  // Minimal strand with one plain 1→1 cap. Plan builder produces:
4699
4664
  // input_slot → step_0 (cap) → output
4700
4665
  // (two edges, three nodes). No cardinality marker in the cap label
@@ -4721,8 +4686,8 @@ function test0209_Renderer_buildStrandGraphData_singleCapPlain() {
4721
4686
  assert(outEdge !== undefined, 'output edge from step_0 to output exists');
4722
4687
  }
4723
4688
 
4724
- // TEST0210: Renderer build strand graph data sequence shows cardinality
4725
- function test0210_Renderer_buildStrandGraphData_sequenceShowsCardinality() {
4689
+ // TEST6491: Renderer build strand graph data sequence shows cardinality
4690
+ function test6491_Renderer_buildStrandGraphData_sequenceShowsCardinality() {
4726
4691
  // A cap with input_is_sequence=true MUST emit "(n→1)" on its edge
4727
4692
  // label.
4728
4693
  const payload = withMediaDisplayNames({
@@ -4742,8 +4707,8 @@ function test0210_Renderer_buildStrandGraphData_sequenceShowsCardinality() {
4742
4707
  `cap edge label must include (n\u21921) marker; got: ${capEdge.label}`);
4743
4708
  }
4744
4709
 
4745
- // TEST0211: Renderer build strand graph data foreach collect span
4746
- function test0211_Renderer_buildStrandGraphData_foreachCollectSpan() {
4710
+ // TEST6492: Renderer build strand graph data foreach collect span
4711
+ function test6492_Renderer_buildStrandGraphData_foreachCollectSpan() {
4747
4712
  // Strand: [ForEach, Cap, Collect]. Plan builder produces:
4748
4713
  // input_slot (source) →direct→ step_1 (cap) — cap emits its own
4749
4714
  // direct edge from prev
@@ -4794,8 +4759,8 @@ function test0211_Renderer_buildStrandGraphData_foreachCollectSpan() {
4794
4759
  assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
4795
4760
  }
4796
4761
 
4797
- // TEST0212: Renderer build strand graph data standalone collect
4798
- function test0212_Renderer_buildStrandGraphData_standaloneCollect() {
4762
+ // TEST6493: Renderer build strand graph data standalone collect
4763
+ function test6493_Renderer_buildStrandGraphData_standaloneCollect() {
4799
4764
  // Strand with a standalone Collect (no enclosing ForEach). Plan
4800
4765
  // builder creates a Collect node consuming prev directly — plain
4801
4766
  // direct edge, no iteration/collection semantics.
@@ -4822,8 +4787,8 @@ function test0212_Renderer_buildStrandGraphData_standaloneCollect() {
4822
4787
  assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
4823
4788
  }
4824
4789
 
4825
- // TEST0213: Renderer build strand graph data unclosed for each body
4826
- function test0213_Renderer_buildStrandGraphData_unclosedForEachBody() {
4790
+ // TEST6494: Renderer build strand graph data unclosed for each body
4791
+ function test6494_Renderer_buildStrandGraphData_unclosedForEachBody() {
4827
4792
  // Strand: [Cap_a, ForEach, Cap_b] with no closing Collect. The plan
4828
4793
  // builder's "unclosed ForEach" branch creates a ForEach node
4829
4794
  // connecting Cap_a to Cap_b via iteration, with prev becoming the
@@ -4860,8 +4825,8 @@ function test0213_Renderer_buildStrandGraphData_unclosedForEachBody() {
4860
4825
  'output edge step_2 → output');
4861
4826
  }
4862
4827
 
4863
- // TEST0214: Renderer build strand graph data nested for each throws
4864
- function test0214_Renderer_buildStrandGraphData_nestedForEachThrows() {
4828
+ // TEST6495: Renderer build strand graph data nested for each throws
4829
+ function test6495_Renderer_buildStrandGraphData_nestedForEachThrows() {
4865
4830
  // Nested ForEach without an intervening body cap in the outer
4866
4831
  // ForEach is an illegal nesting per plan_builder. The renderer
4867
4832
  // must throw the same error to surface the issue rather than
@@ -4889,8 +4854,8 @@ function test0214_Renderer_buildStrandGraphData_nestedForEachThrows() {
4889
4854
  assert(threw, 'nested ForEach without outer body cap must throw');
4890
4855
  }
4891
4856
 
4892
- // TEST0215: Renderer collapse strand single cap body keeps cap own label
4893
- function test0215_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
4857
+ // TEST6496: Renderer collapse strand single cap body keeps cap own label
4858
+ function test6496_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
4894
4859
  // User spec: ForEach/Collect are NOT rendered as nodes, and
4895
4860
  // the collapse does NOT relabel cap edges. Each cap edge
4896
4861
  // carries whatever label the strand builder emitted — the
@@ -4942,8 +4907,8 @@ function test0215_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
4942
4907
  'collect bridge is unlabeled');
4943
4908
  }
4944
4909
 
4945
- // TEST0216: Renderer collapse strand unclosed for each body collapses
4946
- function test0216_Renderer_collapseStrand_unclosedForEachBodyCollapses() {
4910
+ // TEST6497: Renderer collapse strand unclosed for each body collapses
4911
+ function test6497_Renderer_collapseStrand_unclosedForEachBodyCollapses() {
4947
4912
  // [Cap_a(1→1), ForEach, Cap_b(1→1)] with no Collect,
4948
4913
  // source=media:a, target=media:c. Cap_b's to_spec is media:c
4949
4914
  // which is equivalent to target_media_urn, so the output node is
@@ -4999,8 +4964,8 @@ function test0216_Renderer_collapseStrand_unclosedForEachBodyCollapses() {
4999
4964
  'merged step_2 takes on the strand-target role');
5000
4965
  }
5001
4966
 
5002
- // TEST0217: Renderer collapse strand standalone collect collapses
5003
- function test0217_Renderer_collapseStrand_standaloneCollectCollapses() {
4967
+ // TEST6498: Renderer collapse strand standalone collect collapses
4968
+ function test6498_Renderer_collapseStrand_standaloneCollectCollapses() {
5004
4969
  // [Cap, Collect] with no enclosing ForEach, source=media:a,
5005
4970
  // target=media:b;list (NOT equivalent to cap's to_spec media:b,
5006
4971
  // so the output node is retained after collapse).
@@ -5043,8 +5008,8 @@ function test0217_Renderer_collapseStrand_standaloneCollectCollapses() {
5043
5008
  'the synthesized bridging edge for a standalone Collect is an unlabeled connector (cap labels carry all cardinality info)');
5044
5009
  }
5045
5010
 
5046
- // TEST0218: Renderer collapse strand sequence producing cap before foreach
5047
- function test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach() {
5011
+ // TEST6499: Renderer collapse strand sequence producing cap before foreach
5012
+ function test6499_Renderer_collapseStrand_sequenceProducingCapBeforeForeach() {
5048
5013
  // Regression test mirroring the user's real strand:
5049
5014
  // [Cap_disbind (output_is_sequence=true), ForEach, Cap_make_decision],
5050
5015
  // source = media:ext=pdf, target = media:decision (equivalent to
@@ -5108,8 +5073,8 @@ function test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach() {
5108
5073
  'output node merged into step_2 because they represent the same URN');
5109
5074
  }
5110
5075
 
5111
- // TEST0219: Renderer collapse strand plain cap merges trailing output
5112
- function test0219_Renderer_collapseStrand_plainCapMergesTrailingOutput() {
5076
+ // TEST6500: Renderer collapse strand plain cap merges trailing output
5077
+ function test6500_Renderer_collapseStrand_plainCapMergesTrailingOutput() {
5113
5078
  // A strand with a single plain 1→1 cap whose to_spec equals
5114
5079
  // target_media_urn. The plan-builder topology produces:
5115
5080
  // input_slot → step_0 (cap) → output
@@ -5145,8 +5110,8 @@ function test0219_Renderer_collapseStrand_plainCapMergesTrailingOutput() {
5145
5110
  assertEqual(collapsed.edges[0].label, 'x', 'cap title preserved as edge label');
5146
5111
  }
5147
5112
 
5148
- // TEST0220: Renderer collapse strand plain cap distinct target no merge
5149
- function test0220_Renderer_collapseStrand_plainCapDistinctTargetNoMerge() {
5113
+ // TEST6501: Renderer collapse strand plain cap distinct target no merge
5114
+ function test6501_Renderer_collapseStrand_plainCapDistinctTargetNoMerge() {
5150
5115
  // A strand with a single plain cap whose to_spec is NOT
5151
5116
  // equivalent to target_media_urn. The output node must be retained
5152
5117
  // and the trailing connector edge preserved.
@@ -5172,8 +5137,8 @@ function test0220_Renderer_collapseStrand_plainCapDistinctTargetNoMerge() {
5172
5137
  'step_0 retained');
5173
5138
  }
5174
5139
 
5175
- // TEST0221: Renderer validate strand payload missing source media urn
5176
- function test0221_Renderer_validateStrandPayload_missingSourceMediaUrn() {
5140
+ // TEST6502: Renderer validate strand payload missing source media urn
5141
+ function test6502_Renderer_validateStrandPayload_missingSourceMediaUrn() {
5177
5142
  let threw = false;
5178
5143
  try {
5179
5144
  rendererValidateStrandPayload({ target_media_urn: 'media:b', steps: [] });
@@ -5186,7 +5151,7 @@ function test0221_Renderer_validateStrandPayload_missingSourceMediaUrn() {
5186
5151
 
5187
5152
  // ---------------- run builder ----------------
5188
5153
 
5189
- function test0222_Renderer_validateBodyOutcome_rejectsNegativeIndex() {
5154
+ function test6503_Renderer_validateBodyOutcome_rejectsNegativeIndex() {
5190
5155
  let threw = false;
5191
5156
  try {
5192
5157
  rendererValidateBodyOutcome({ body_index: -1, success: true, cap_urns: [] }, 'test');
@@ -5196,8 +5161,8 @@ function test0222_Renderer_validateBodyOutcome_rejectsNegativeIndex() {
5196
5161
  assert(threw, 'negative body_index must throw');
5197
5162
  }
5198
5163
 
5199
- // TEST0223: Renderer build run graph data pages successes and failures
5200
- function test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
5164
+ // TEST6504: Renderer build run graph data pages successes and failures
5165
+ function test6504_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
5201
5166
  // 6 successes, 4 failures. visible=3+2, total=10. Body has 2
5202
5167
  // caps (a, b). Each body replica is a chain of:
5203
5168
  // entry node + body_step_0 (cap a) + body_step_1 (cap b)
@@ -5267,8 +5232,8 @@ function test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
5267
5232
  assertEqual(failureShowMore.data.hiddenCount, 2, 'failure hidden count = 2');
5268
5233
  }
5269
5234
 
5270
- // TEST0224: Renderer build run graph data failure without failed cap renders full trace
5271
- function test0224_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace() {
5235
+ // TEST6505: Renderer build run graph data failure without failed cap renders full trace
5236
+ function test6505_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace() {
5272
5237
  // A failure without failed_cap (infrastructure failure) must
5273
5238
  // still render the full body trace — the builder must not
5274
5239
  // crash or produce zero replicas.
@@ -5306,8 +5271,8 @@ function test0224_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullT
5306
5271
  assertEqual(failureNodes, 2, 'entry + body cap = 2 failure replica nodes');
5307
5272
  }
5308
5273
 
5309
- // TEST0225: Renderer build run graph data uses cap urn is equivalent for failed cap
5310
- function test0225_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
5274
+ // TEST6506: Renderer build run graph data uses cap urn is equivalent for failed cap
5275
+ function test6506_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
5311
5276
  // The renderer matches failed_cap against step cap URNs via
5312
5277
  // CapUrn.isEquivalent, NOT string equality. Feed a payload where
5313
5278
  // failed_cap and the step's cap_urn differ only in tag order — they
@@ -5366,8 +5331,8 @@ function test0225_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap(
5366
5331
  assertEqual(failureNodes, 3, 'trace truncates at cap y via isEquivalent, yielding entry + 2 cap nodes');
5367
5332
  }
5368
5333
 
5369
- // TEST0226: Renderer build run graph data backbone has no foreach node
5370
- function test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode() {
5334
+ // TEST6507: Renderer build run graph data backbone has no foreach node
5335
+ function test6507_Renderer_buildRunGraphData_backboneHasNoForeachNode() {
5371
5336
  // Regression test for the run-mode rendering fix: the backbone
5372
5337
  // delivered to cytoscape must NOT contain any strand-foreach or
5373
5338
  // strand-collect nodes. Run mode inherits the same cosmetic
@@ -5421,8 +5386,8 @@ function test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode() {
5421
5386
  assertEqual(built.showMoreNodes.length, 0, 'no show-more nodes when no hidden outcomes');
5422
5387
  }
5423
5388
 
5424
- // TEST0227: Renderer build run graph data all failed drops target placeholder
5425
- function test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
5389
+ // TEST6508: Renderer build run graph data all failed drops target placeholder
5390
+ function test6508_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
5426
5391
  // When every body fails, the strand target node was never
5427
5392
  // reached by any execution. The render drops BOTH the backbone
5428
5393
  // foreach-entry edge AND the orphaned target node so the user
@@ -5487,8 +5452,8 @@ function test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
5487
5452
  assertEqual(hasInputSlot, true, 'input_slot survives as the shared source');
5488
5453
  }
5489
5454
 
5490
- // TEST0228: Renderer build run graph data unclosed foreach success no merge
5491
- function test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
5455
+ // TEST6509: Renderer build run graph data unclosed foreach success no merge
5456
+ function test6509_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
5492
5457
  // Strand without a Collect: [Disbind, ForEach, make_decision].
5493
5458
  // Under the machfab realize_strand semantics there's no Collect,
5494
5459
  // so each body produces its OWN terminal output. Successful
@@ -5549,8 +5514,8 @@ function test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
5549
5514
  assertEqual(forkEdges.length, 1, 'fork edge input_slot → body-0-entry exists');
5550
5515
  }
5551
5516
 
5552
- // TEST0229: Renderer build run graph data closed foreach success merges at collect target
5553
- function test0229_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget() {
5517
+ // TEST6510: Renderer build run graph data closed foreach success merges at collect target
5518
+ function test6510_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget() {
5554
5519
  // With a Collect closing the body, successful replicas DO merge
5555
5520
  // into the post-collect target so the flow converges.
5556
5521
  // Strand: [Disbind, ForEach, Cap_a, Cap_b, Collect] with a
@@ -5605,7 +5570,7 @@ function test0229_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollect
5605
5570
 
5606
5571
  // ---------------- editor-graph builder ----------------
5607
5572
 
5608
- function test0230_Renderer_validateEditorGraphPayload_rejectsUnknownKind() {
5573
+ function test6511_Renderer_validateEditorGraphPayload_rejectsUnknownKind() {
5609
5574
  let threw = false;
5610
5575
  try {
5611
5576
  rendererValidateEditorGraphPayload({
@@ -5619,8 +5584,8 @@ function test0230_Renderer_validateEditorGraphPayload_rejectsUnknownKind() {
5619
5584
  assert(threw, 'unknown element kind must throw');
5620
5585
  }
5621
5586
 
5622
- // TEST0231: Renderer build editor graph data collapses caps into labeled edges
5623
- function test0231_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges() {
5587
+ // TEST6512: Renderer build editor graph data collapses caps into labeled edges
5588
+ function test6512_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges() {
5624
5589
  // The notation analyzer emits a bipartite chain per cap
5625
5590
  // application: data_node → arg_edge → cap_node → arg_edge →
5626
5591
  // data_node. The machine builder collapses each cap into a
@@ -5658,8 +5623,8 @@ function test0231_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges()
5658
5623
  'edge carries the cap node tokenId so editor cross-highlight points to the cap in source text');
5659
5624
  }
5660
5625
 
5661
- // TEST0232: Renderer build editor graph data loop marked edge gets loop class
5662
- function test0232_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
5626
+ // TEST6513: Renderer build editor graph data loop marked edge gets loop class
5627
+ function test6513_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
5663
5628
  // A cap marked `is_loop: true` must produce a `machine-loop`
5664
5629
  // edge so the stylesheet's dashed amber rule applies.
5665
5630
  const data = {
@@ -5677,8 +5642,8 @@ function test0232_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
5677
5642
  'loop-marked cap emits machine-loop class on the collapsed edge');
5678
5643
  }
5679
5644
 
5680
- // TEST0233: Renderer build editor graph data cardinality from data slot sequence flags
5681
- function test0233_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags() {
5645
+ // TEST6514: Renderer build editor graph data cardinality from data slot sequence flags
5646
+ function test6514_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags() {
5682
5647
  // Cardinality markers come from the source and target data
5683
5648
  // slots' `is_sequence` flags. A cap whose output data slot has
5684
5649
  // `is_sequence=true` shows "(1→n)" on its collapsed edge.
@@ -5697,8 +5662,8 @@ function test0233_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceF
5697
5662
  'cardinality marker "(1→n)" derived from output data slot is_sequence=true');
5698
5663
  }
5699
5664
 
5700
- // TEST0234: Renderer build editor graph data cap without complete args is dropped
5701
- function test0234_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped() {
5665
+ // TEST6515: Renderer build editor graph data cap without complete args is dropped
5666
+ function test6515_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped() {
5702
5667
  // A cap with no incoming or no outgoing argument edges (e.g.
5703
5668
  // the user is mid-typing) contributes nothing to the render.
5704
5669
  // The data slots are still emitted.
@@ -5715,8 +5680,8 @@ function test0234_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped(
5715
5680
  'incomplete cap (no outgoing argument) drops out of the render');
5716
5681
  }
5717
5682
 
5718
- // TEST0235: Renderer build editor graph data rejects edge with missing source
5719
- function test0235_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
5683
+ // TEST6516: Renderer build editor graph data rejects edge with missing source
5684
+ function test6516_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
5720
5685
  let threw = false;
5721
5686
  try {
5722
5687
  rendererBuildEditorGraphData({
@@ -5732,7 +5697,7 @@ function test0235_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
5732
5697
 
5733
5698
  // ---------------- resolved-machine builder ----------------
5734
5699
 
5735
- function test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
5700
+ function test6517_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
5736
5701
  // A single-strand machine: media:ext=pdf → extract → media:ext=txt
5737
5702
  // → embed → media:embedding. Two edges, three nodes, no
5738
5703
  // loops, no fan-in. Tests the basic shape — nodes and
@@ -5790,8 +5755,8 @@ function test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain
5790
5755
  'output anchor node carries strand-target class');
5791
5756
  }
5792
5757
 
5793
- // TEST0237: Renderer build resolved machine graph data loop edge gets loop class
5794
- function test0237_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
5758
+ // TEST6518: Renderer build resolved machine graph data loop edge gets loop class
5759
+ function test6518_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
5795
5760
  // An is_loop edge corresponds to a strand step inside a
5796
5761
  // ForEach body. The renderer must mark it with the
5797
5762
  // `machine-loop` class so the dashed amber rule applies.
@@ -5825,8 +5790,8 @@ function test0237_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass()
5825
5790
  'is_loop=true must produce a machine-loop class on the cap edge');
5826
5791
  }
5827
5792
 
5828
- // TEST0238: Renderer build resolved machine graph data fan in produces edge per assignment
5829
- function test0238_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment() {
5793
+ // TEST6519: Renderer build resolved machine graph data fan in produces edge per assignment
5794
+ function test6519_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment() {
5830
5795
  // A cap with two input args (a fan-in) gets one rendered
5831
5796
  // edge per (source_node, target_node) pair so cytoscape can
5832
5797
  // draw both incoming wires. Both edges share the cap title
@@ -5866,8 +5831,8 @@ function test0238_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAss
5866
5831
  assertEqual(built.edges[1].data.target, 'n2', 'second edge targets n2');
5867
5832
  }
5868
5833
 
5869
- // TEST0239: Renderer build resolved machine graph data multi strand keeps strands disjoint
5870
- function test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint() {
5834
+ // TEST6520: Renderer build resolved machine graph data multi strand keeps strands disjoint
5835
+ function test6520_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint() {
5871
5836
  // Two strands inside one machine. Each strand has its own
5872
5837
  // nodes and edges. Node ids are globally unique across
5873
5838
  // strands (Rust assigns them via a single counter), so no
@@ -5929,8 +5894,8 @@ function test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrands
5929
5894
  assertEqual(idToStrand['n3'], 1, 'n3 belongs to strand 1');
5930
5895
  }
5931
5896
 
5932
- // TEST0240: Renderer build resolved machine graph data duplicate node id across strands fails hard
5933
- function test0240_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard() {
5897
+ // TEST6521: Renderer build resolved machine graph data duplicate node id across strands fails hard
5898
+ function test6521_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard() {
5934
5899
  // Node ids must be globally unique across strands. The
5935
5900
  // Rust serializer guarantees this via a single global
5936
5901
  // counter. If the host ever feeds a payload that violates
@@ -5965,8 +5930,8 @@ function test0240_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossSt
5965
5930
  'error must name the colliding node id');
5966
5931
  }
5967
5932
 
5968
- // TEST0241: Renderer validate resolved machine payload rejects missing fields
5969
- function test0241_Renderer_validateResolvedMachinePayload_rejectsMissingFields() {
5933
+ // TEST6522: Renderer validate resolved machine payload rejects missing fields
5934
+ function test6522_Renderer_validateResolvedMachinePayload_rejectsMissingFields() {
5970
5935
  // The validator must reject any payload missing a required
5971
5936
  // field on a strand, edge, node, or assignment binding.
5972
5937
  // We exercise the most-likely-to-be-missed field on each
@@ -6007,7 +5972,7 @@ function test0241_Renderer_validateResolvedMachinePayload_rejectsMissingFields()
6007
5972
  // surface, not a per-port detail.
6008
5973
  // ============================================================================
6009
5974
 
6010
- // TEST1800: Identity classifier — only explicit effect=none qualifies.
5975
+ // TEST1800: Identity classifier — and only explicit effect=none qualifies.
6011
5976
  function test1800_kindIdentityOnlyForBareCap() {
6012
5977
  const identity = CapUrn.fromString('cap:effect=none');
6013
5978
  assertEqual(identity.kind(), CapKind.IDENTITY, 'cap:effect=none should be Identity');
@@ -6062,8 +6027,7 @@ function test1803_kindEffectWhenBothSidesVoid() {
6062
6027
  'in=void;out=void with empty y is still an Effect');
6063
6028
  }
6064
6029
 
6065
- // TEST1804: Transform classifier — at least one side non-void, and
6066
- // the cap is not the bare identity.
6030
+ // TEST1804: Transform classifier — at least one side non-void, and the cap is not the bare identity. The default kind for ordinary data-processing caps.
6067
6031
  function test1804_kindTransformForNormalDataProcessors() {
6068
6032
  const extract = CapUrn.fromString('cap:extract;in="media:ext=pdf";out="media:enc=utf-8;record"');
6069
6033
  assertEqual(extract.kind(), CapKind.TRANSFORM, 'extract is a Transform');
@@ -6073,12 +6037,7 @@ function test1804_kindTransformForNormalDataProcessors() {
6073
6037
  'fully generic in/out with a tag is a Transform, not Identity');
6074
6038
  }
6075
6039
 
6076
- // TEST1810: media:void is atomic — refinements are parse errors.
6077
- //
6078
- // Mirrored across every language port (Rust, Go, Python, Swift/ObjC,
6079
- // JS) under the SAME number. Any divergence is a wire-level
6080
- // inconsistency — the unit type's atomicity is part of the protocol's
6081
- // deepest layer, not a per-port detail.
6040
+ // TEST1810: media:void is atomic — refinements are parse errors. Mirrored across every language port (Rust, Go, Python, Swift/ObjC, JS) under the SAME number. Any divergence is a wire-level inconsistency — the unit type's atomicity is part of the protocol's deepest layer, not a per-port detail. The bare `media:void` parses successfully; any combination with another tag (marker or key=value) MUST fail with VoidNotAtomic. This forecloses a fake taxonomy of unit values; reasons or labels for *why* void is used belong on the cap URN's non-directional tags or in cap args.
6082
6041
  function test1810_mediaVoidIsAtomic() {
6083
6042
  // Bare void: must parse successfully.
6084
6043
  const bare = MediaUrn.fromString('media:void');
@@ -6111,9 +6070,7 @@ function test1810_mediaVoidIsAtomic() {
6111
6070
  }
6112
6071
  }
6113
6072
 
6114
- // TEST1805: Kind is invariant under canonicalization. The same
6115
- // morphism written in many surface forms must classify the same way
6116
- // once parsed.
6073
+ // TEST1805: Kind is invariant under canonicalization. The same morphism written in many surface forms must classify the same way once parsed. This pins the rule that kind is a property of the cap as a structured object, not of any particular spelling.
6117
6074
  function test1805_kindInvariantUnderCanonicalSpellings() {
6118
6075
  const cases = [
6119
6076
  { a: 'cap:effect=none', b: 'cap:in=media:;out=media:;effect=none', expected: CapKind.IDENTITY },
@@ -6192,8 +6149,7 @@ function test1823_specificityExactValueIsFour() {
6192
6149
  'target=metadata (exact value) must score 4');
6193
6150
  }
6194
6151
 
6195
- // TEST1824: All six forms compose additively on a single cap.
6196
- // y combining 0+1+2+3+4+5 must sum to 15.
6152
+ // TEST1824: All six forms compose additively on a single cap. This pins the truth-table sum across the y axis as a whole.
6197
6153
  function test1824_specificityCombinedYAxis() {
6198
6154
  const cap = CapUrn.fromString('cap:!constrained;?target;extract;stage!=alpha;target2=metadata;ver?=draft');
6199
6155
  assertEqual(cap.specificity(), 15,
@@ -6214,10 +6170,7 @@ function test1830_canonicalizeNoConstraint() {
6214
6170
  }
6215
6171
  }
6216
6172
 
6217
- // TEST1831: ?x=v and x?=v both canonicalize to x?=v. The third
6218
- // hypothetical form `x=?v` is NOT recognized as a qualifier — a
6219
- // value starting with `?` is just an exact value beginning with
6220
- // a `?` character.
6173
+ // TEST1831: ?x=v and x?=v both canonicalize to x?=v. The third hypothetical form `x=?v` is NOT recognized as a qualifier — a value starting with `?` is just an exact value beginning with a `?` character.
6221
6174
  function test1831_canonicalizeAbsentOrNotValue() {
6222
6175
  const canonical = 'cap:x?=foo';
6223
6176
  for (const input of ['cap:?x=foo', 'cap:x?=foo']) {
@@ -6243,10 +6196,7 @@ function test1832_canonicalizeMustHaveAny() {
6243
6196
  }
6244
6197
  }
6245
6198
 
6246
- // TEST1833: !x=v and x!=v both canonicalize to x!=v. The third
6247
- // hypothetical form `x=!v` is NOT recognized as a qualifier — a
6248
- // value starting with `!` is just an exact value beginning with
6249
- // a `!` character.
6199
+ // TEST1833: !x=v and x!=v both canonicalize to x!=v. The third hypothetical form `x=!v` is NOT recognized as a qualifier — a value starting with `!` is just an exact value beginning with a `!` character.
6250
6200
  function test1833_canonicalizePresentNotValue() {
6251
6201
  const canonical = 'cap:x!=foo';
6252
6202
  for (const input of ['cap:!x=foo', 'cap:x!=foo']) {
@@ -6262,7 +6212,7 @@ function test1833_canonicalizePresentNotValue() {
6262
6212
  assertEqual(exact.getTag('x'), '!foo');
6263
6213
  }
6264
6214
 
6265
- // TEST1834: x=v stays as x=v.
6215
+ // TEST1834: x=v stays as x=v (the lone exact-value form).
6266
6216
  function test1834_canonicalizeExactValue() {
6267
6217
  const cap = CapUrn.fromString('cap:x=foo');
6268
6218
  assertEqual(cap.toString(), 'cap:x=foo');
@@ -6306,8 +6256,8 @@ function test1842_truthTableFullCrossProduct() {
6306
6256
  }
6307
6257
  }
6308
6258
 
6309
- // TEST1843: Invalid qualifier combinations must be rejected.
6310
- function test1843_rejectInvalidCombinations() {
6259
+ // TEST6734: Invalid qualifier combinations must be rejected.
6260
+ function test6734_rejectInvalidCombinations() {
6311
6261
  const invalid = [
6312
6262
  'cap:?x?=v', 'cap:!x!=v', 'cap:?!x', 'cap:!?x',
6313
6263
  'cap:?x=*', 'cap:!x=*',
@@ -6321,8 +6271,8 @@ function test1843_rejectInvalidCombinations() {
6321
6271
  }
6322
6272
  }
6323
6273
 
6324
- // TEST1844: out-axis difference dominates combined in+y differences.
6325
- function test1844_axisWeightingOutDominates() {
6274
+ // TEST6735: out-axis difference dominates combined in+y differences.
6275
+ function test6735_axisWeightingOutDominates() {
6326
6276
  const bigOut = CapUrn.fromString('cap:in=media:;out="media:enc=utf-8;record"');
6327
6277
  const bigInAndY = CapUrn.fromString(
6328
6278
  'cap:in="media:ext=pdf";out=media:record;!constrained;?target;extract;stage!=alpha;target2=metadata;ver?=draft'
@@ -6331,7 +6281,7 @@ function test1844_axisWeightingOutDominates() {
6331
6281
  'out-axis difference must dominate combined in+y differences');
6332
6282
  }
6333
6283
 
6334
- // TEST1845: With equal out, in-axis dominates over y-axis.
6284
+ // TEST1845: With equal out-axis, in-axis dominates over y-axis.
6335
6285
  function test1845_axisWeightingInDominatesY() {
6336
6286
  const bigIn = CapUrn.fromString('cap:in="media:ext=pdf";out=media:record');
6337
6287
  const bigY = CapUrn.fromString(
@@ -6341,8 +6291,8 @@ function test1845_axisWeightingInDominatesY() {
6341
6291
  'in-axis difference must dominate y-axis');
6342
6292
  }
6343
6293
 
6344
- // TEST1846: Decoded layout — 10000*out + 100*in + y.
6345
- function test1846_axisWeightingDecodedLayout() {
6294
+ // TEST6736: Decoded layout — 10000*out + 100*in + y.
6295
+ function test6736_axisWeightingDecodedLayout() {
6346
6296
  const cap = CapUrn.fromString('cap:in="media:a;b";out="media:a;b;c;d";extract');
6347
6297
  // out=4 markers (8), in=2 markers (4), y=1 marker (2)
6348
6298
  // 10000*8 + 100*4 + 2 = 80402
@@ -6353,8 +6303,8 @@ function test1846_axisWeightingDecodedLayout() {
6353
6303
  // Cap.version round-trip tests: TEST1847-TEST1848
6354
6304
  // ============================================================================
6355
6305
 
6356
- // TEST1847: Cap with version=0 round-trips with no `version` key on wire
6357
- function test1847_capVersionZeroOmittedOnWire() {
6306
+ // TEST6737: Cap with version=0 round-trips with no `version` key on wire
6307
+ function test6737_capVersionZeroOmittedOnWire() {
6358
6308
  const urn = CapUrn.fromString('cap:in="media:void";test-op;out="media:enc=utf-8;record"');
6359
6309
  const cap = new Cap(urn, 'Test Cap', 'test-op');
6360
6310
  // version defaults to 0
@@ -6377,6 +6327,62 @@ function test1848_capVersionNonZeroOnWire() {
6377
6327
  assertEqual(restored.version, 42, 'Restored version must equal 42');
6378
6328
  }
6379
6329
 
6330
+ // ===========================================================================
6331
+ // Fabric alias tests (shared numbers 1880-1882, 1887)
6332
+ //
6333
+ // Shared test numbers test the same behavior, with the same method, across
6334
+ // every capdag implementation. This lightweight JS mirror provides the alias
6335
+ // primitives + Manifest (de)serialization; the registry-resolution tests
6336
+ // (1888-1892) and notation-parser tests (1883-1886) belong to the mirrors
6337
+ // that implement the full registry/resolver pipeline.
6338
+ // ===========================================================================
6339
+
6340
+ // TEST1880: alias name normalization lowercases and accepts the allowed character class; rejects colon, whitespace, and out-of-class chars with the right error. A broken validator would let a URN-shaped or whitespace name through, or mangle a valid name.
6341
+ function test1880_aliasNameNormalizationRules() {
6342
+ assertEqual(normalizeAliasName('JSONDoc'), 'jsondoc', 'lowercases');
6343
+ assertEqual(normalizeAliasName('pdf2text'), 'pdf2text', 'plain name');
6344
+ assertEqual(normalizeAliasName('my.alias-1_x'), 'my.alias-1_x', 'allowed punctuation');
6345
+ for (const bad of ['', 'pdf:text', 'my alias', 'a/b']) {
6346
+ let threw = false;
6347
+ try { normalizeAliasName(bad); } catch (_) { threw = true; }
6348
+ assert(threw, `normalizeAliasName must reject ${JSON.stringify(bad)}`);
6349
+ }
6350
+ }
6351
+
6352
+ // TEST1881: URN-vs-alias detection keys purely on the presence of ':'. The whole design rests on this discriminator being exact.
6353
+ function test1881_tokenUrnVsAliasDetection() {
6354
+ assert(tokenIsUrn('cap:in="media:ext=pdf";extract;out="media:enc=utf-8"'), 'cap URN is a URN');
6355
+ assert(tokenIsUrn('media:fmt=json;record'), 'media URN is a URN');
6356
+ assert(!tokenIsUrn('pdf2text'), 'bare name is not a URN');
6357
+ assert(isAliasToken('pdf2text'), 'bare name is an alias token');
6358
+ assert(!isAliasToken('media:enc=utf-8'), 'media URN is not an alias token');
6359
+ }
6360
+
6361
+ // TEST1882: alias target classification distinguishes cap from media by prefix and rejects a non-URN target. The typed-boundary enforcement in the registry depends on this.
6362
+ function test1882_classifyAliasTargetByPrefix() {
6363
+ assertEqual(classifyAliasTarget('media:fmt=json;record'), ALIAS_TARGET_MEDIA, 'media target');
6364
+ assertEqual(
6365
+ classifyAliasTarget('cap:effect=patch;in="media:image";name;out="media:ext=png;image"'),
6366
+ ALIAS_TARGET_CAP, 'cap target');
6367
+ assertEqual(classifyAliasTarget('not-a-urn'), null, 'non-URN target is null');
6368
+ }
6369
+
6370
+ // TEST1887: the Manifest type round-trips an `aliases` map.
6371
+ function test1887_manifestSerdeRoundTripsAliases() {
6372
+ const body = '{"version":1,"previous":0,"caps":{},"media":{},"aliases":{"pdf2text":3,"jsondoc":1}}';
6373
+ const m = Manifest.fromJSON(JSON.parse(body));
6374
+ assertEqual(m.aliases['pdf2text'], 3, 'pdf2text defver');
6375
+ assertEqual(m.aliases['jsondoc'], 1, 'jsondoc defver');
6376
+ const back = m.toJSON();
6377
+ assertEqual(back.aliases['pdf2text'], 3, 'round-trip pdf2text');
6378
+ assertEqual(back.aliases['jsondoc'], 1, 'round-trip jsondoc');
6379
+
6380
+ // A StoredAlias round-trips its wire shape.
6381
+ const a = StoredAlias.fromJSON({ name: 'pdf2text', target: 'cap:effect=none', version: 3 });
6382
+ assertEqual(a.target, 'cap:effect=none', 'alias target');
6383
+ assertEqual(JSON.stringify(a.toJSON()), '{"name":"pdf2text","target":"cap:effect=none","version":3}', 'alias wire shape');
6384
+ }
6385
+
6380
6386
  // ============================================================================
6381
6387
  // Test runner
6382
6388
  // ============================================================================
@@ -6443,9 +6449,9 @@ async function runTests() {
6443
6449
  // validation.rs: TEST053-TEST056
6444
6450
  console.log('\n--- validation.rs ---');
6445
6451
  console.log(' SKIP TEST053: N/A for JS (Rust-only validation infrastructure)');
6446
- runTest('TEST054: xv5_inline_spec_redefinition_detected', test054_xv5InlineSpecRedefinitionDetected);
6447
- runTest('TEST055: xv5_new_inline_spec_allowed', test055_xv5NewInlineSpecAllowed);
6448
- runTest('TEST056: xv5_empty_media_defs_allowed', test056_xv5EmptyMediaDefsAllowed);
6452
+ runTest('TEST054: xv5_inline_spec_redefinition_detected', test6212_xv5InlineSpecRedefinitionDetected);
6453
+ runTest('TEST055: xv5_new_inline_spec_allowed', test6216_xv5NewInlineSpecAllowed);
6454
+ runTest('TEST056: xv5_empty_media_defs_allowed', test6220_xv5EmptyMediaDefsAllowed);
6449
6455
 
6450
6456
  // media_urn.rs: TEST060-TEST078
6451
6457
  console.log('\n--- media_urn.rs ---');
@@ -6471,12 +6477,12 @@ async function runTests() {
6471
6477
  // media_def.rs: TEST088-TEST110
6472
6478
  console.log('\n--- media_def.rs ---');
6473
6479
  console.log(' SKIP TEST088-090: N/A for JS (async registry, Rust-only)');
6474
- runTest('TEST091: resolve_custom_media_def', test091_resolveCustomMediaDef);
6475
- runTest('TEST092: resolve_custom_with_schema', test092_resolveCustomWithSchema);
6476
- runTest('TEST093: resolve_unresolvable_fails_hard', test093_resolveUnresolvableFailsHard);
6480
+ runTest('TEST6282: resolve_custom_media_def', test6282_resolveCustomMediaDef);
6481
+ runTest('TEST6283: resolve_custom_with_schema', test6283_resolveCustomWithSchema);
6482
+ runTest('TEST093: resolve_unresolvable_fails_hard', test93_resolveUnresolvableFailsHard);
6477
6483
  console.log(' SKIP TEST094: N/A for JS (no registry concept)');
6478
6484
  console.log(' SKIP TEST095-098: N/A for JS (Rust serde/validation)');
6479
- runTest('TEST099: resolved_is_binary', test099_resolvedIsBinary);
6485
+ runTest('TEST099: resolved_is_binary', test99_resolvedIsBinary);
6480
6486
  runTest('TEST100: resolved_is_record', test100_resolvedIsRecord);
6481
6487
  runTest('TEST101: resolved_is_scalar', test101_resolvedIsScalar);
6482
6488
  runTest('TEST102: resolved_is_list', test102_resolvedIsList);
@@ -6497,9 +6503,9 @@ async function runTests() {
6497
6503
  // /api/capabilities). These tests guard the minimal API the renderer relies
6498
6504
  // on: new CapFab(), addCap(cap, registryName), getEdges(), getOutgoing().
6499
6505
  console.log('\n--- cap_fab (browse-mode API used by cap-fab-renderer) ---');
6500
- runTest('cap_fab: add_cap_populates_edges_and_nodes', test0052_CapFabAddCapPopulatesEdgesAndNodes);
6501
- runTest('cap_fab: get_outgoing_conforms_to_matching', test0053_CapFabGetOutgoingConformsToMatching);
6502
- runTest('cap_fab: distinct_registry_names_recorded_per_edge', test0057_CapFabDistinctRegistryNames);
6506
+ runTest('cap_fab: add_cap_populates_edges_and_nodes', test6206_CapFabAddCapPopulatesEdgesAndNodes);
6507
+ runTest('cap_fab: get_outgoing_conforms_to_matching', test6208_CapFabGetOutgoingConformsToMatching);
6508
+ runTest('cap_fab: distinct_registry_names_recorded_per_edge', test6224_CapFabDistinctRegistryNames);
6503
6509
 
6504
6510
  // caller.rs: TEST156-TEST159
6505
6511
  console.log('\n--- caller.rs (StdinSource) ---');
@@ -6528,22 +6534,22 @@ async function runTests() {
6528
6534
  runTest('TEST308: model_path_urn', test308_modelPathUrn);
6529
6535
  runTest('TEST309: model_availability_and_path_are_distinct', test309_modelAvailabilityAndPathAreDistinct);
6530
6536
  runTest('TEST310: llm_generate_text_urn', test310_llmGenerateTextUrn);
6531
- runTest('llm_generate_text_urn_specs', test0058_LlmGenerateTextUrnSpecs);
6537
+ runTest('llm_generate_text_urn_specs', test6228_LlmGenerateTextUrnSpecs);
6532
6538
  runTest('TEST312: all_urn_builders_produce_valid_urns', test312_allUrnBuildersProduceValidUrns);
6533
6539
 
6534
6540
  // JS-specific tests (no Rust number)
6535
6541
  console.log('\n--- JS-specific ---');
6536
- runTest('JS: build_extension_index', test0059_JS_buildExtensionIndex);
6537
- runTest('JS: media_urns_for_extension', test0069_JS_mediaUrnsForExtension);
6538
- runTest('JS: get_extension_mappings', test0070_JS_getExtensionMappings);
6539
- runTest('JS: resolve_media_urn_from_specs', test0073_JS_resolveMediaUrnFromSpecs);
6540
- runTest('JS: cap_json_serialization', test0079_JS_capJSONSerialization);
6541
- runTest('JS: cap_documentation_round_trip', test0080_JS_capDocumentationRoundTrip);
6542
- runTest('JS: cap_documentation_omitted_when_null', test0081_JS_capDocumentationOmittedWhenNull);
6543
- runTest('JS: media_def_documentation_propagates_through_resolve', test0082_JS_mediaDefDocumentationPropagatesThroughResolve);
6544
- runTest('JS: stdin_source_kind_constants', test0083_JS_stdinSourceKindConstants);
6545
- runTest('JS: stdin_source_null_data', test0084_JS_stdinSourceNullData);
6546
- runTest('JS: media_def_construction', test0085_JS_mediaDefConstruction);
6542
+ runTest('JS: build_extension_index', test6232_JS_buildExtensionIndex);
6543
+ runTest('JS: media_urns_for_extension', test6236_JS_mediaUrnsForExtension);
6544
+ runTest('JS: get_extension_mappings', test6240_JS_getExtensionMappings);
6545
+ runTest('JS: resolve_media_urn_from_specs', test6242_JS_resolveMediaUrnFromSpecs);
6546
+ runTest('JS: cap_json_serialization', test6246_JS_capJSONSerialization);
6547
+ runTest('JS: cap_documentation_round_trip', test6249_JS_capDocumentationRoundTrip);
6548
+ runTest('JS: cap_documentation_omitted_when_null', test6253_JS_capDocumentationOmittedWhenNull);
6549
+ runTest('JS: media_def_documentation_propagates_through_resolve', test6257_JS_mediaDefDocumentationPropagatesThroughResolve);
6550
+ runTest('JS: stdin_source_kind_constants', test6261_JS_stdinSourceKindConstants);
6551
+ runTest('JS: stdin_source_null_data', test6265_JS_stdinSourceNullData);
6552
+ runTest('JS: media_def_construction', test6269_JS_mediaDefConstruction);
6547
6553
 
6548
6554
  // cartridge_repo: CartridgeRepoServer and CartridgeRepoClient tests
6549
6555
  console.log('\n--- cartridge_repo ---');
@@ -6587,22 +6593,22 @@ async function runTests() {
6587
6593
 
6588
6594
  // media_urn.rs: TEST1312-TEST1315, TEST1298-TEST1302 (MediaUrn predicates)
6589
6595
  console.log('\n--- media_urn.rs (predicates) ---');
6590
- runTest('TEST1312: is_image', test1312_isImage);
6591
- runTest('TEST1313: is_audio', test1313_isAudio);
6592
- runTest('TEST1314: is_video', test1314_isVideo);
6593
- runTest('TEST1315: is_numeric', test1315_isNumeric);
6594
- runTest('TEST1298: is_bool', test1298_isBool);
6595
- runTest('TEST1299: is_file_path', test1299_isFilePath);
6596
- runTest('TEST1302: predicate_constant_consistency', test1302_predicateConstantConsistency);
6596
+ runTest('TEST1312: is_image', test546_isImage);
6597
+ runTest('TEST1313: is_audio', test547_isAudio);
6598
+ runTest('TEST1314: is_video', test548_isVideo);
6599
+ runTest('TEST1315: is_numeric', test549_isNumeric);
6600
+ runTest('TEST1298: is_bool', test550_isBool);
6601
+ runTest('TEST1299: is_file_path', test551_isFilePath);
6602
+ runTest('TEST1302: predicate_constant_consistency', test558_predicateConstantConsistency);
6597
6603
 
6598
6604
  // cap_urn.rs: TEST1303-TEST1307 (CapUrn tier tests)
6599
6605
  console.log('\n--- cap_urn.rs (tier tests) ---');
6600
- runTest('TEST1303: without_tag', test1303_withoutTag);
6601
- runTest('TEST1304: with_in_out_spec', test1304_withInOutSpec);
6602
- runTest('TEST1305: find_all_matches', test1305_findAllMatches);
6603
- runTest('TEST1306: are_compatible', test1306_areCompatible);
6604
- runTest('TEST1307: with_tag_rejects_structural_keys', test1307_withTagRejectsStructuralKeys);
6605
- runTest('TEST1308: builder_rejects_structural_keys', test1308_builderRejectsStructuralKeys);
6606
+ runTest('TEST1303: without_tag', test559_withoutTag);
6607
+ runTest('TEST1304: with_in_out_spec', test560_withInOutSpec);
6608
+ runTest('TEST1305: find_all_matches', test563_findAllMatches);
6609
+ runTest('TEST1306: are_compatible', test564_areCompatible);
6610
+ runTest('TEST1307: with_tag_rejects_structural_keys', test566_withTagRejectsStructuralKeys);
6611
+ runTest('TEST1308: builder_rejects_structural_keys', test6544_builderRejectsStructuralKeys);
6606
6612
  runTest('TEST1294: rule11_void_input_with_stdin_rejected', test1294_rule11VoidInputWithStdinRejected);
6607
6613
  runTest('TEST1295: rule11_non_void_input_without_stdin_rejected', test1295_rule11NonVoidInputWithoutStdinRejected);
6608
6614
  runTest('TEST1296: rule11_void_input_cli_flag_only', test1296_rule11VoidInputCliFlagOnly);
@@ -6610,7 +6616,7 @@ async function runTests() {
6610
6616
 
6611
6617
  // cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
6612
6618
  console.log('\n--- cap_urn.rs (wildcard tests) ---');
6613
- runTest('TEST639: empty_cap_is_illegal', test639_emptyCapIsIllegal);
6619
+ runTest('TEST639: empty_cap_is_illegal', test6201_emptyCapIsIllegal);
6614
6620
  runTest('TEST640: in_only_is_illegal', test640_inOnlyIsIllegal);
6615
6621
  runTest('TEST641: out_only_is_illegal', test641_outOnlyIsIllegal);
6616
6622
  runTest('TEST642: in_out_without_values_are_illegal', test642_inOutWithoutValuesAreIllegal);
@@ -6622,184 +6628,184 @@ async function runTests() {
6622
6628
  runTest('TEST648: wildcard_accepts_specific', test648_wildcardAcceptsSpecific);
6623
6629
  runTest('TEST649: specificity_scoring', test649_specificityScoring);
6624
6630
  runTest('TEST650: wildcard_preserve_other_tags', test650_wildcardPreserveOtherTags);
6625
- runTest('TEST651: wildcard_generic_forms_rejected', test651_wildcardGenericFormsRejected);
6626
- runTest('TEST652: cap_identity_constant_works', test652_capIdentityConstantWorks);
6631
+ runTest('TEST651: wildcard_generic_forms_rejected', test6620_wildcardGenericFormsRejected);
6632
+ runTest('TEST652: cap_identity_constant_works', test6621_capIdentityConstantWorks);
6627
6633
  runTest('TEST653: invalid_effect_none_declaration_rejected', test653_invalidEffectNoneDeclarationRejected);
6628
- runTest('TEST654: effect_none_preserves_runtime_media', test654_effectNonePreservesRuntimeMedia);
6629
- runTest('TEST655: effect_declared_uses_declared_output', test655_effectDeclaredUsesDeclaredOutput);
6630
- runTest('TEST656: invalid_effect_none_fails_hard', test656_invalidEffectNoneFailsHard);
6631
- runTest('TEST657: effect_dispatch_requires_explicit_wildcard', test657_effectDispatchRequiresExplicitWildcard);
6634
+ runTest('TEST654: effect_none_preserves_runtime_media', test125_effectNonePreservesRuntimeMedia);
6635
+ runTest('TEST655: effect_declared_uses_declared_output', test126_effectDeclaredUsesDeclaredOutput);
6636
+ runTest('TEST656: invalid_effect_none_fails_hard', test127_invalidEffectNoneFailsHard);
6637
+ runTest('TEST657: effect_dispatch_requires_explicit_wildcard', test128_effectDispatchRequiresExplicitWildcard);
6632
6638
 
6633
6639
  // machine module: parser tests (mirrors parser.rs)
6634
6640
  console.log('\n--- machine/parser.rs ---');
6635
- runTest('MACHINE:empty_input', test0087_Machine_emptyInput);
6636
- runTest('MACHINE:whitespace_only', test0088_Machine_whitespaceOnly);
6637
- runTest('MACHINE:header_only_no_wirings', test0089_Machine_headerOnlyNoWirings);
6638
- runTest('MACHINE:duplicate_alias', test0090_Machine_duplicateAlias);
6639
- runTest('MACHINE:simple_linear_chain', test0094_Machine_simpleLinearChain);
6640
- runTest('MACHINE:two_step_chain', test0095_Machine_twoStepChain);
6641
- runTest('MACHINE:fan_out', test0096_Machine_fanOut);
6642
- runTest('MACHINE:fan_in_secondary_assigned_by_prior_wiring', test0097_Machine_fanInSecondaryAssignedByPriorWiring);
6643
- runTest('MACHINE:fan_in_secondary_unassigned_gets_wildcard', test0098_Machine_fanInSecondaryUnassignedGetsWildcard);
6644
- runTest('MACHINE:loop_edge', test0111_Machine_loopEdge);
6645
- runTest('MACHINE:undefined_alias_fails', test0112_Machine_undefinedAliasFails);
6646
- runTest('MACHINE:node_alias_collision', test0113_Machine_nodeAliasCollision);
6647
- runTest('MACHINE:conflicting_media_types_fail', test0114_Machine_conflictingMediaTypesFail);
6648
- runTest('MACHINE:multiline_format', test0117_Machine_multilineFormat);
6649
- runTest('MACHINE:different_aliases_same_graph', test0118_Machine_differentAliasesSameGraph);
6650
- runTest('MACHINE:malformed_input_fails', test0119_Machine_malformedInputFails);
6651
- runTest('MACHINE:unterminated_bracket_fails', test0120_Machine_unterminatedBracketFails);
6641
+ runTest('MACHINE:empty_input', test6275_Machine_emptyInput);
6642
+ runTest('MACHINE:whitespace_only', test6277_Machine_whitespaceOnly);
6643
+ runTest('MACHINE:header_only_no_wirings', test6279_Machine_headerOnlyNoWirings);
6644
+ runTest('MACHINE:duplicate_alias', test6280_Machine_duplicateAlias);
6645
+ runTest('MACHINE:simple_linear_chain', test6286_Machine_simpleLinearChain);
6646
+ runTest('MACHINE:two_step_chain', test6288_Machine_twoStepChain);
6647
+ runTest('MACHINE:fan_out', test6290_Machine_fanOut);
6648
+ runTest('MACHINE:fan_in_secondary_assigned_by_prior_wiring', test6292_Machine_fanInSecondaryAssignedByPriorWiring);
6649
+ runTest('MACHINE:fan_in_secondary_unassigned_gets_wildcard', test6294_Machine_fanInSecondaryUnassignedGetsWildcard);
6650
+ runTest('MACHINE:loop_edge', test6306_Machine_loopEdge);
6651
+ runTest('MACHINE:undefined_alias_fails', test6308_Machine_undefinedAliasFails);
6652
+ runTest('MACHINE:node_alias_collision', test6310_Machine_nodeAliasCollision);
6653
+ runTest('MACHINE:conflicting_media_types_fail', test6312_Machine_conflictingMediaTypesFail);
6654
+ runTest('MACHINE:multiline_format', test6315_Machine_multilineFormat);
6655
+ runTest('MACHINE:different_aliases_same_graph', test6318_Machine_differentAliasesSameGraph);
6656
+ runTest('MACHINE:malformed_input_fails', test6321_Machine_malformedInputFails);
6657
+ runTest('MACHINE:unterminated_bracket_fails', test6323_Machine_unterminatedBracketFails);
6652
6658
 
6653
6659
  // machine module: line-based mode tests
6654
6660
  console.log('\n--- machine/parser.rs (line-based) ---');
6655
- runTest('MACHINE:line_based_simple_chain', test0121_Machine_lineBasedSimpleChain);
6656
- runTest('MACHINE:line_based_two_step_chain', test0122_Machine_lineBasedTwoStepChain);
6657
- runTest('MACHINE:line_based_loop', test0123_Machine_lineBasedLoop);
6658
- runTest('MACHINE:line_based_fan_in', test0124_Machine_lineBasedFanIn);
6659
- runTest('MACHINE:mixed_bracketed_and_line_based', test0125_Machine_mixedBracketedAndLineBased);
6660
- runTest('MACHINE:line_based_equivalent_to_bracketed', test0126_Machine_lineBasedEquivalentToBracketed);
6661
- runTest('MACHINE:line_based_format_serialization', test0127_Machine_lineBasedFormatSerialization);
6662
- runTest('MACHINE:line_based_and_bracketed_parse_same_graph', test0128_Machine_lineBasedAndBracketedParseSameGraph);
6661
+ runTest('MACHINE:line_based_simple_chain', test6327_Machine_lineBasedSimpleChain);
6662
+ runTest('MACHINE:line_based_two_step_chain', test6331_Machine_lineBasedTwoStepChain);
6663
+ runTest('MACHINE:line_based_loop', test6334_Machine_lineBasedLoop);
6664
+ runTest('MACHINE:line_based_fan_in', test6337_Machine_lineBasedFanIn);
6665
+ runTest('MACHINE:mixed_bracketed_and_line_based', test6341_Machine_mixedBracketedAndLineBased);
6666
+ runTest('MACHINE:line_based_equivalent_to_bracketed', test6345_Machine_lineBasedEquivalentToBracketed);
6667
+ runTest('MACHINE:line_based_format_serialization', test6349_Machine_lineBasedFormatSerialization);
6668
+ runTest('MACHINE:line_based_and_bracketed_parse_same_graph', test6353_Machine_lineBasedAndBracketedParseSameGraph);
6663
6669
 
6664
6670
  // machine module: graph tests (mirrors graph.rs)
6665
6671
  console.log('\n--- machine/graph.rs ---');
6666
- runTest('MACHINE:edge_equivalence_same_urns', test0129_Machine_edgeEquivalenceSameUrns);
6667
- runTest('MACHINE:edge_equivalence_different_cap_urns', test0130_Machine_edgeEquivalenceDifferentCapUrns);
6668
- runTest('MACHINE:edge_equivalence_different_targets', test0131_Machine_edgeEquivalenceDifferentTargets);
6669
- runTest('MACHINE:edge_equivalence_different_loop_flag', test0132_Machine_edgeEquivalenceDifferentLoopFlag);
6670
- runTest('MACHINE:edge_equivalence_source_order_independent', test0133_Machine_edgeEquivalenceSourceOrderIndependent);
6671
- runTest('MACHINE:edge_equivalence_different_source_count', test0134_Machine_edgeEquivalenceDifferentSourceCount);
6672
- runTest('MACHINE:graph_equivalence_same_edges', test0135_Machine_graphEquivalenceSameEdges);
6673
- runTest('MACHINE:graph_equivalence_reordered_edges', test0136_Machine_graphEquivalenceReorderedEdges);
6674
- runTest('MACHINE:graph_not_equivalent_different_edge_count', test0137_Machine_graphNotEquivalentDifferentEdgeCount);
6675
- runTest('MACHINE:graph_not_equivalent_different_cap', test0138_Machine_graphNotEquivalentDifferentCap);
6676
- runTest('MACHINE:graph_empty', test0139_Machine_graphEmpty);
6677
- runTest('MACHINE:graph_empty_equivalence', test0140_Machine_graphEmptyEquivalence);
6678
- runTest('MACHINE:root_sources_linear_chain', test0141_Machine_rootSourcesLinearChain);
6679
- runTest('MACHINE:leaf_targets_linear_chain', test0142_Machine_leafTargetsLinearChain);
6680
- runTest('MACHINE:root_sources_fan_in', test0143_Machine_rootSourcesFanIn);
6681
- runTest('MACHINE:display_edge', test0144_Machine_displayEdge);
6682
- runTest('MACHINE:display_graph', test0145_Machine_displayGraph);
6672
+ runTest('MACHINE:edge_equivalence_same_urns', test6357_Machine_edgeEquivalenceSameUrns);
6673
+ runTest('MACHINE:edge_equivalence_different_cap_urns', test6361_Machine_edgeEquivalenceDifferentCapUrns);
6674
+ runTest('MACHINE:edge_equivalence_different_targets', test6365_Machine_edgeEquivalenceDifferentTargets);
6675
+ runTest('MACHINE:edge_equivalence_different_loop_flag', test6369_Machine_edgeEquivalenceDifferentLoopFlag);
6676
+ runTest('MACHINE:edge_equivalence_source_order_independent', test6372_Machine_edgeEquivalenceSourceOrderIndependent);
6677
+ runTest('MACHINE:edge_equivalence_different_source_count', test6375_Machine_edgeEquivalenceDifferentSourceCount);
6678
+ runTest('MACHINE:graph_equivalence_same_edges', test6377_Machine_graphEquivalenceSameEdges);
6679
+ runTest('MACHINE:graph_equivalence_reordered_edges', test6380_Machine_graphEquivalenceReorderedEdges);
6680
+ runTest('MACHINE:graph_not_equivalent_different_edge_count', test6383_Machine_graphNotEquivalentDifferentEdgeCount);
6681
+ runTest('MACHINE:graph_not_equivalent_different_cap', test6386_Machine_graphNotEquivalentDifferentCap);
6682
+ runTest('MACHINE:graph_empty', test6389_Machine_graphEmpty);
6683
+ runTest('MACHINE:graph_empty_equivalence', test6392_Machine_graphEmptyEquivalence);
6684
+ runTest('MACHINE:root_sources_linear_chain', test6395_Machine_rootSourcesLinearChain);
6685
+ runTest('MACHINE:leaf_targets_linear_chain', test6397_Machine_leafTargetsLinearChain);
6686
+ runTest('MACHINE:root_sources_fan_in', test6398_Machine_rootSourcesFanIn);
6687
+ runTest('MACHINE:display_edge', test6400_Machine_displayEdge);
6688
+ runTest('MACHINE:display_graph', test6402_Machine_displayGraph);
6683
6689
 
6684
6690
  // machine module: serializer tests (mirrors serializer.rs)
6685
6691
  console.log('\n--- machine/serializer.rs ---');
6686
- runTest('MACHINE:serialize_single_edge', test0146_Machine_serializeSingleEdge);
6687
- runTest('MACHINE:serialize_two_edge_chain', test0147_Machine_serializeTwoEdgeChain);
6688
- runTest('MACHINE:serialize_empty_graph', test0148_Machine_serializeEmptyGraph);
6689
- runTest('MACHINE:roundtrip_single_edge', test0149_Machine_roundtripSingleEdge);
6690
- runTest('MACHINE:roundtrip_two_edge_chain', test0151_Machine_roundtripTwoEdgeChain);
6691
- runTest('MACHINE:roundtrip_fan_out', test0152_Machine_roundtripFanOut);
6692
- runTest('MACHINE:roundtrip_loop_edge', test0153_Machine_roundtripLoopEdge);
6693
- runTest('MACHINE:serialization_is_deterministic', test0154_Machine_serializationIsDeterministic);
6694
- runTest('MACHINE:reordered_edges_produce_same_notation', test0155_Machine_reorderedEdgesProduceSameNotation);
6695
- runTest('MACHINE:multiline_serialize_format', test0160_Machine_multilineSerializeFormat);
6696
- runTest('MACHINE:alias_from_op_tag', test0161_Machine_aliasFromOpTag);
6697
- runTest('MACHINE:alias_fallback_without_op_tag', test0162_Machine_aliasFallbackWithoutOpTag);
6698
- runTest('MACHINE:duplicate_op_tags_disambiguated', test0163_Machine_duplicateOpTagsDisambiguated);
6692
+ runTest('MACHINE:serialize_single_edge', test6404_Machine_serializeSingleEdge);
6693
+ runTest('MACHINE:serialize_two_edge_chain', test6406_Machine_serializeTwoEdgeChain);
6694
+ runTest('MACHINE:serialize_empty_graph', test6408_Machine_serializeEmptyGraph);
6695
+ runTest('MACHINE:roundtrip_single_edge', test6410_Machine_roundtripSingleEdge);
6696
+ runTest('MACHINE:roundtrip_two_edge_chain', test6413_Machine_roundtripTwoEdgeChain);
6697
+ runTest('MACHINE:roundtrip_fan_out', test6415_Machine_roundtripFanOut);
6698
+ runTest('MACHINE:roundtrip_loop_edge', test6417_Machine_roundtripLoopEdge);
6699
+ runTest('MACHINE:serialization_is_deterministic', test6419_Machine_serializationIsDeterministic);
6700
+ runTest('MACHINE:reordered_edges_produce_same_notation', test6421_Machine_reorderedEdgesProduceSameNotation);
6701
+ runTest('MACHINE:multiline_serialize_format', test6429_Machine_multilineSerializeFormat);
6702
+ runTest('MACHINE:alias_from_op_tag', test6432_Machine_aliasFromOpTag);
6703
+ runTest('MACHINE:alias_fallback_without_op_tag', test6434_Machine_aliasFallbackWithoutOpTag);
6704
+ runTest('MACHINE:duplicate_op_tags_disambiguated', test6436_Machine_duplicateOpTagsDisambiguated);
6699
6705
 
6700
6706
  // machine module: builder tests
6701
6707
  console.log('\n--- machine/builder ---');
6702
- runTest('MACHINE:builder_single_edge', test0164_Machine_builderSingleEdge);
6703
- runTest('MACHINE:builder_with_loop', test0165_Machine_builderWithLoop);
6704
- runTest('MACHINE:builder_chaining', test0166_Machine_builderChaining);
6705
- runTest('MACHINE:builder_equivalent_to_parsed', test0167_Machine_builderEquivalentToParsed);
6706
- runTest('MACHINE:builder_round_trip', test0168_Machine_builderRoundTrip);
6708
+ runTest('MACHINE:builder_single_edge', test6437_Machine_builderSingleEdge);
6709
+ runTest('MACHINE:builder_with_loop', test6438_Machine_builderWithLoop);
6710
+ runTest('MACHINE:builder_chaining', test6439_Machine_builderChaining);
6711
+ runTest('MACHINE:builder_equivalent_to_parsed', test6440_Machine_builderEquivalentToParsed);
6712
+ runTest('MACHINE:builder_round_trip', test6442_Machine_builderRoundTrip);
6707
6713
 
6708
6714
  // machine module: CapUrn.isEquivalent/isComparable
6709
6715
  console.log('\n--- machine/urn_predicates ---');
6710
- runTest('MACHINE:cap_urn_is_equivalent', test0169_Machine_capUrnIsEquivalent);
6711
- runTest('MACHINE:cap_urn_is_comparable', test0170_Machine_capUrnIsComparable);
6712
- runTest('MACHINE:cap_urn_in_media_urn', test0171_Machine_capUrnInMediaUrn);
6713
- runTest('MACHINE:cap_urn_out_media_urn', test0172_Machine_capUrnOutMediaUrn);
6714
- runTest('MACHINE:media_urn_is_equivalent', test0173_Machine_mediaUrnIsEquivalent);
6715
- runTest('MACHINE:media_urn_is_comparable', test0174_Machine_mediaUrnIsComparable);
6716
+ runTest('MACHINE:cap_urn_is_equivalent', test6444_Machine_capUrnIsEquivalent);
6717
+ runTest('MACHINE:cap_urn_is_comparable', test6446_Machine_capUrnIsComparable);
6718
+ runTest('MACHINE:cap_urn_in_media_urn', test6448_Machine_capUrnInMediaUrn);
6719
+ runTest('MACHINE:cap_urn_out_media_urn', test6449_Machine_capUrnOutMediaUrn);
6720
+ runTest('MACHINE:media_urn_is_equivalent', test6450_Machine_mediaUrnIsEquivalent);
6721
+ runTest('MACHINE:media_urn_is_comparable', test6451_Machine_mediaUrnIsComparable);
6716
6722
 
6717
6723
  // Phase 0A: Position tracking
6718
6724
  console.log('\n--- machine/position_tracking ---');
6719
- runTest('MACHINE:parseMachineWithAST_headerLocation', test0175_Machine_parseMachineWithAST_headerLocation);
6720
- runTest('MACHINE:parseMachineWithAST_wiringLocation', test0176_Machine_parseMachineWithAST_wiringLocation);
6721
- runTest('MACHINE:parseMachineWithAST_multilinePositions', test0177_Machine_parseMachineWithAST_multilinePositions);
6722
- runTest('MACHINE:parseMachineWithAST_fanInSourceLocations', test0178_Machine_parseMachineWithAST_fanInSourceLocations);
6723
- runTest('MACHINE:parseMachineWithAST_aliasMap', test0179_Machine_parseMachineWithAST_aliasMap);
6724
- runTest('MACHINE:parseMachineWithAST_nodeMedia', test0180_Machine_parseMachineWithAST_nodeMedia);
6725
- runTest('MACHINE:errorLocation_parseError', test0181_Machine_errorLocation_parseError);
6726
- runTest('MACHINE:errorLocation_duplicateAlias', test0182_Machine_errorLocation_duplicateAlias);
6727
- runTest('MACHINE:errorLocation_undefinedAlias', test0183_Machine_errorLocation_undefinedAlias);
6725
+ runTest('MACHINE:parseMachineWithAST_headerLocation', test6452_Machine_parseMachineWithAST_headerLocation);
6726
+ runTest('MACHINE:parseMachineWithAST_wiringLocation', test6453_Machine_parseMachineWithAST_wiringLocation);
6727
+ runTest('MACHINE:parseMachineWithAST_multilinePositions', test6454_Machine_parseMachineWithAST_multilinePositions);
6728
+ runTest('MACHINE:parseMachineWithAST_fanInSourceLocations', test6455_Machine_parseMachineWithAST_fanInSourceLocations);
6729
+ runTest('MACHINE:parseMachineWithAST_aliasMap', test6456_Machine_parseMachineWithAST_aliasMap);
6730
+ runTest('MACHINE:parseMachineWithAST_nodeMedia', test6457_Machine_parseMachineWithAST_nodeMedia);
6731
+ runTest('MACHINE:errorLocation_parseError', test6458_Machine_errorLocation_parseError);
6732
+ runTest('MACHINE:errorLocation_duplicateAlias', test6459_Machine_errorLocation_duplicateAlias);
6733
+ runTest('MACHINE:errorLocation_undefinedAlias', test6460_Machine_errorLocation_undefinedAlias);
6728
6734
 
6729
6735
  // Phase 0C: Machine.toMermaid()
6730
6736
  console.log('\n--- machine/mermaid ---');
6731
- runTest('MACHINE:toMermaid_linearChain', test0184_Machine_toMermaid_linearChain);
6732
- runTest('MACHINE:toMermaid_loopEdge', test0185_Machine_toMermaid_loopEdge);
6733
- runTest('MACHINE:toMermaid_emptyGraph', test0186_Machine_toMermaid_emptyGraph);
6734
- runTest('MACHINE:toMermaid_fanIn', test0187_Machine_toMermaid_fanIn);
6735
- runTest('MACHINE:toMermaid_fanOut', test0188_Machine_toMermaid_fanOut);
6737
+ runTest('MACHINE:toMermaid_linearChain', test6462_Machine_toMermaid_linearChain);
6738
+ runTest('MACHINE:toMermaid_loopEdge', test6463_Machine_toMermaid_loopEdge);
6739
+ runTest('MACHINE:toMermaid_emptyGraph', test6464_Machine_toMermaid_emptyGraph);
6740
+ runTest('MACHINE:toMermaid_fanIn', test6465_Machine_toMermaid_fanIn);
6741
+ runTest('MACHINE:toMermaid_fanOut', test6466_Machine_toMermaid_fanOut);
6736
6742
 
6737
6743
  // Phase 0B: FabricRegistryClient
6738
6744
  console.log('\n--- registry/client ---');
6739
- runTest('REGISTRY: capRegistryEntry_construction', test0189_Machine_capRegistryEntry_construction);
6740
- runTest('REGISTRY: mediaRegistryEntry_construction', test0190_Machine_mediaRegistryEntry_construction);
6741
- runTest('REGISTRY: capRegistryClient_construction', test0191_Machine_capRegistryClient_construction);
6742
- runTest('REGISTRY: capRegistryEntry_defaults', test0192_Machine_capRegistryEntry_defaults);
6745
+ runTest('REGISTRY: capRegistryEntry_construction', test6467_Machine_capRegistryEntry_construction);
6746
+ runTest('REGISTRY: mediaRegistryEntry_construction', test6468_Machine_mediaRegistryEntry_construction);
6747
+ runTest('REGISTRY: capRegistryClient_construction', test6469_Machine_capRegistryClient_construction);
6748
+ runTest('REGISTRY: capRegistryEntry_defaults', test6470_Machine_capRegistryEntry_defaults);
6743
6749
 
6744
6750
  // cap-fab-renderer pure helpers (no DOM dependency)
6745
6751
  console.log('\n--- cap-fab-renderer helpers ---');
6746
- runTest('RENDERER: cardinalityLabel_allFourCases', test0193_Renderer_cardinalityLabel_allFourCases);
6747
- runTest('RENDERER: cardinalityLabel_usesUnicodeArrow', test0194_Renderer_cardinalityLabel_usesUnicodeArrow);
6748
- runTest('RENDERER: cardinalityFromCap_findsStdinArg', test0195_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg);
6749
- runTest('RENDERER: cardinalityFromCap_scalarDefaults', test0196_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing);
6750
- runTest('RENDERER: cardinalityFromCap_outputOnlySequence', test0197_Renderer_cardinalityFromCap_outputOnlySequence);
6751
- runTest('RENDERER: cardinalityFromCap_rejectsStringBool', test0198_Renderer_cardinalityFromCap_rejectsStringIsSequence);
6752
- runTest('RENDERER: cardinalityFromCap_throwsOnNonObject', test0199_Renderer_cardinalityFromCap_throwsOnNonObject);
6753
- runTest('RENDERER: canonicalMediaUrn_normalizesTagOrder', test0200_Renderer_canonicalMediaUrn_normalizesTagOrder);
6754
- runTest('RENDERER: canonicalMediaUrn_preservesValueTags', test0201_Renderer_canonicalMediaUrn_preservesValueTags);
6755
- runTest('RENDERER: canonicalMediaUrn_rejectsCapUrn', test0202_Renderer_canonicalMediaUrn_rejectsCapUrn);
6756
- runTest('RENDERER: mediaNodeLabel_rejectsUrnDerived', test0203_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels);
6757
- runTest('RENDERER: buildBrowse_rejectsMissingMediaTitles', test0204_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles);
6752
+ runTest('RENDERER: cardinalityLabel_allFourCases', test6471_Renderer_cardinalityLabel_allFourCases);
6753
+ runTest('RENDERER: cardinalityLabel_usesUnicodeArrow', test6472_Renderer_cardinalityLabel_usesUnicodeArrow);
6754
+ runTest('RENDERER: cardinalityFromCap_findsStdinArg', test6473_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg);
6755
+ runTest('RENDERER: cardinalityFromCap_scalarDefaults', test6474_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing);
6756
+ runTest('RENDERER: cardinalityFromCap_outputOnlySequence', test6475_Renderer_cardinalityFromCap_outputOnlySequence);
6757
+ runTest('RENDERER: cardinalityFromCap_rejectsStringBool', test6476_Renderer_cardinalityFromCap_rejectsStringIsSequence);
6758
+ runTest('RENDERER: cardinalityFromCap_throwsOnNonObject', test6478_Renderer_cardinalityFromCap_throwsOnNonObject);
6759
+ runTest('RENDERER: canonicalMediaUrn_normalizesTagOrder', test6479_Renderer_canonicalMediaUrn_normalizesTagOrder);
6760
+ runTest('RENDERER: canonicalMediaUrn_preservesValueTags', test6480_Renderer_canonicalMediaUrn_preservesValueTags);
6761
+ runTest('RENDERER: canonicalMediaUrn_rejectsCapUrn', test6481_Renderer_canonicalMediaUrn_rejectsCapUrn);
6762
+ runTest('RENDERER: mediaNodeLabel_rejectsUrnDerived', test6482_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels);
6763
+ runTest('RENDERER: buildBrowse_rejectsMissingMediaTitles', test6483_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles);
6758
6764
 
6759
6765
  console.log('\n--- cap-fab-renderer strand builder ---');
6760
- runTest('RENDERER: validateStrandStep_unknownVariant', test0205_Renderer_validateStrandStep_rejectsUnknownVariant);
6761
- runTest('RENDERER: validateStrandStep_booleanIsSequence', test0206_Renderer_validateStrandStep_requiresBooleanIsSequence);
6762
- runTest('RENDERER: classifyStrandCapSteps_simple', test0207_Renderer_classifyStrandCapSteps_capFlags);
6763
- runTest('RENDERER: classifyStrandCapSteps_nested', test0208_Renderer_classifyStrandCapSteps_nestedForks);
6764
- runTest('RENDERER: buildStrand_singleCapPlain', test0209_Renderer_buildStrandGraphData_singleCapPlain);
6765
- runTest('RENDERER: buildStrand_sequenceShowsCardinality', test0210_Renderer_buildStrandGraphData_sequenceShowsCardinality);
6766
- runTest('RENDERER: buildStrand_foreachCollectSpan', test0211_Renderer_buildStrandGraphData_foreachCollectSpan);
6767
- runTest('RENDERER: buildStrand_standaloneCollect', test0212_Renderer_buildStrandGraphData_standaloneCollect);
6768
- runTest('RENDERER: buildStrand_unclosedForEachBody', test0213_Renderer_buildStrandGraphData_unclosedForEachBody);
6769
- runTest('RENDERER: buildStrand_nestedForEachThrows', test0214_Renderer_buildStrandGraphData_nestedForEachThrows);
6770
- runTest('RENDERER: collapseStrand_singleCapBody', test0215_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel);
6771
- runTest('RENDERER: collapseStrand_unclosedForEachBody', test0216_Renderer_collapseStrand_unclosedForEachBodyCollapses);
6772
- runTest('RENDERER: collapseStrand_standaloneCollect', test0217_Renderer_collapseStrand_standaloneCollectCollapses);
6773
- runTest('RENDERER: collapseStrand_seqCapBeforeForeach', test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach);
6774
- runTest('RENDERER: collapseStrand_plainCapMergesOutput', test0219_Renderer_collapseStrand_plainCapMergesTrailingOutput);
6775
- runTest('RENDERER: collapseStrand_plainCapDistinctTarget', test0220_Renderer_collapseStrand_plainCapDistinctTargetNoMerge);
6776
- runTest('RENDERER: validateStrand_missingSourceMediaUrn', test0221_Renderer_validateStrandPayload_missingSourceMediaUrn);
6766
+ runTest('RENDERER: validateStrandStep_unknownVariant', test6484_Renderer_validateStrandStep_rejectsUnknownVariant);
6767
+ runTest('RENDERER: validateStrandStep_booleanIsSequence', test6486_Renderer_validateStrandStep_requiresBooleanIsSequence);
6768
+ runTest('RENDERER: classifyStrandCapSteps_simple', test6487_Renderer_classifyStrandCapSteps_capFlags);
6769
+ runTest('RENDERER: classifyStrandCapSteps_nested', test6488_Renderer_classifyStrandCapSteps_nestedForks);
6770
+ runTest('RENDERER: buildStrand_singleCapPlain', test6489_Renderer_buildStrandGraphData_singleCapPlain);
6771
+ runTest('RENDERER: buildStrand_sequenceShowsCardinality', test6491_Renderer_buildStrandGraphData_sequenceShowsCardinality);
6772
+ runTest('RENDERER: buildStrand_foreachCollectSpan', test6492_Renderer_buildStrandGraphData_foreachCollectSpan);
6773
+ runTest('RENDERER: buildStrand_standaloneCollect', test6493_Renderer_buildStrandGraphData_standaloneCollect);
6774
+ runTest('RENDERER: buildStrand_unclosedForEachBody', test6494_Renderer_buildStrandGraphData_unclosedForEachBody);
6775
+ runTest('RENDERER: buildStrand_nestedForEachThrows', test6495_Renderer_buildStrandGraphData_nestedForEachThrows);
6776
+ runTest('RENDERER: collapseStrand_singleCapBody', test6496_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel);
6777
+ runTest('RENDERER: collapseStrand_unclosedForEachBody', test6497_Renderer_collapseStrand_unclosedForEachBodyCollapses);
6778
+ runTest('RENDERER: collapseStrand_standaloneCollect', test6498_Renderer_collapseStrand_standaloneCollectCollapses);
6779
+ runTest('RENDERER: collapseStrand_seqCapBeforeForeach', test6499_Renderer_collapseStrand_sequenceProducingCapBeforeForeach);
6780
+ runTest('RENDERER: collapseStrand_plainCapMergesOutput', test6500_Renderer_collapseStrand_plainCapMergesTrailingOutput);
6781
+ runTest('RENDERER: collapseStrand_plainCapDistinctTarget', test6501_Renderer_collapseStrand_plainCapDistinctTargetNoMerge);
6782
+ runTest('RENDERER: validateStrand_missingSourceMediaUrn', test6502_Renderer_validateStrandPayload_missingSourceMediaUrn);
6777
6783
 
6778
6784
  console.log('\n--- cap-fab-renderer run builder ---');
6779
- runTest('RENDERER: validateBodyOutcome_negativeIndex', test0222_Renderer_validateBodyOutcome_rejectsNegativeIndex);
6780
- runTest('RENDERER: buildRun_pagesSuccessesAndFailures', test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures);
6781
- runTest('RENDERER: buildRun_failureWithoutFailedCap', test0224_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace);
6782
- runTest('RENDERER: buildRun_usesIsEquivalentForFailedCap', test0225_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap);
6783
- runTest('RENDERER: buildRun_backboneHasNoForeachNode', test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode);
6784
- runTest('RENDERER: buildRun_allFailedDropsPlaceholder', test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder);
6785
- runTest('RENDERER: buildRun_unclosedForeachNoMerge', test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge);
6786
- runTest('RENDERER: buildRun_closedForeachMerges', test0229_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget);
6785
+ runTest('RENDERER: validateBodyOutcome_negativeIndex', test6503_Renderer_validateBodyOutcome_rejectsNegativeIndex);
6786
+ runTest('RENDERER: buildRun_pagesSuccessesAndFailures', test6504_Renderer_buildRunGraphData_pagesSuccessesAndFailures);
6787
+ runTest('RENDERER: buildRun_failureWithoutFailedCap', test6505_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace);
6788
+ runTest('RENDERER: buildRun_usesIsEquivalentForFailedCap', test6506_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap);
6789
+ runTest('RENDERER: buildRun_backboneHasNoForeachNode', test6507_Renderer_buildRunGraphData_backboneHasNoForeachNode);
6790
+ runTest('RENDERER: buildRun_allFailedDropsPlaceholder', test6508_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder);
6791
+ runTest('RENDERER: buildRun_unclosedForeachNoMerge', test6509_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge);
6792
+ runTest('RENDERER: buildRun_closedForeachMerges', test6510_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget);
6787
6793
 
6788
6794
  console.log('\n--- cap-fab-renderer editor-graph builder ---');
6789
- runTest('RENDERER: validateEditorGraph_unknownKind', test0230_Renderer_validateEditorGraphPayload_rejectsUnknownKind);
6790
- runTest('RENDERER: buildEditorGraph_collapsesCapsIntoEdges', test0231_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges);
6791
- runTest('RENDERER: buildEditorGraph_loopEdgeGetsClass', test0232_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass);
6792
- runTest('RENDERER: buildEditorGraph_cardinalityFromIsSeq', test0233_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags);
6793
- runTest('RENDERER: buildEditorGraph_incompleteCapDropped', test0234_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped);
6794
- runTest('RENDERER: buildEditorGraph_rejectsEdgeMissingSrc', test0235_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource);
6795
+ runTest('RENDERER: validateEditorGraph_unknownKind', test6511_Renderer_validateEditorGraphPayload_rejectsUnknownKind);
6796
+ runTest('RENDERER: buildEditorGraph_collapsesCapsIntoEdges', test6512_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges);
6797
+ runTest('RENDERER: buildEditorGraph_loopEdgeGetsClass', test6513_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass);
6798
+ runTest('RENDERER: buildEditorGraph_cardinalityFromIsSeq', test6514_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags);
6799
+ runTest('RENDERER: buildEditorGraph_incompleteCapDropped', test6515_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped);
6800
+ runTest('RENDERER: buildEditorGraph_rejectsEdgeMissingSrc', test6516_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource);
6795
6801
 
6796
6802
  console.log('\n--- cap-fab-renderer resolved-machine builder ---');
6797
- runTest('RENDERER: buildResolvedMachine_singleStrandLinear', test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain);
6798
- runTest('RENDERER: buildResolvedMachine_loopGetsLoopClass', test0237_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass);
6799
- runTest('RENDERER: buildResolvedMachine_fanInOneEdgePerSrc', test0238_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment);
6800
- runTest('RENDERER: buildResolvedMachine_multiStrandDisjoint', test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint);
6801
- runTest('RENDERER: buildResolvedMachine_dupNodeIdFails', test0240_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard);
6802
- runTest('RENDERER: validateResolvedMachine_rejectsMissingFields', test0241_Renderer_validateResolvedMachinePayload_rejectsMissingFields);
6803
+ runTest('RENDERER: buildResolvedMachine_singleStrandLinear', test6517_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain);
6804
+ runTest('RENDERER: buildResolvedMachine_loopGetsLoopClass', test6518_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass);
6805
+ runTest('RENDERER: buildResolvedMachine_fanInOneEdgePerSrc', test6519_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment);
6806
+ runTest('RENDERER: buildResolvedMachine_multiStrandDisjoint', test6520_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint);
6807
+ runTest('RENDERER: buildResolvedMachine_dupNodeIdFails', test6521_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard);
6808
+ runTest('RENDERER: validateResolvedMachine_rejectsMissingFields', test6522_Renderer_validateResolvedMachinePayload_rejectsMissingFields);
6803
6809
 
6804
6810
  console.log('\n--- CapKind classifier (test1800–test1805) ---');
6805
6811
  runTest('TEST1800: kind_identity_requires_effect_none', test1800_kindIdentityOnlyForBareCap);
@@ -6829,15 +6835,20 @@ async function runTests() {
6829
6835
 
6830
6836
  console.log('\n--- Truth-table cross-product + axis weighting (test1842–test1846) ---');
6831
6837
  runTest('TEST1842: truth_table_full_cross_product', test1842_truthTableFullCrossProduct);
6832
- runTest('TEST1843: reject_invalid_combinations', test1843_rejectInvalidCombinations);
6833
- runTest('TEST1844: axis_weighting_out_dominates', test1844_axisWeightingOutDominates);
6838
+ runTest('TEST1843: reject_invalid_combinations', test6734_rejectInvalidCombinations);
6839
+ runTest('TEST1844: axis_weighting_out_dominates', test6735_axisWeightingOutDominates);
6834
6840
  runTest('TEST1845: axis_weighting_in_dominates_y', test1845_axisWeightingInDominatesY);
6835
- runTest('TEST1846: axis_weighting_decoded_layout', test1846_axisWeightingDecodedLayout);
6841
+ runTest('TEST1846: axis_weighting_decoded_layout', test6736_axisWeightingDecodedLayout);
6836
6842
 
6837
6843
  // Cap.version round-trip tests
6838
- runTest('TEST1847: cap_version_zero_omitted_on_wire', test1847_capVersionZeroOmittedOnWire);
6844
+ runTest('TEST1847: cap_version_zero_omitted_on_wire', test6737_capVersionZeroOmittedOnWire);
6839
6845
  runTest('TEST1848: cap_version_nonzero_on_wire', test1848_capVersionNonZeroOnWire);
6840
6846
 
6847
+ runTest('TEST1880: alias_name_normalization_rules', test1880_aliasNameNormalizationRules);
6848
+ runTest('TEST1881: token_urn_vs_alias_detection', test1881_tokenUrnVsAliasDetection);
6849
+ runTest('TEST1882: classify_alias_target_by_prefix', test1882_classifyAliasTargetByPrefix);
6850
+ runTest('TEST1887: manifest_serde_round_trips_aliases', test1887_manifestSerdeRoundTripsAliases);
6851
+
6841
6852
  // Summary
6842
6853
  console.log(`\n${passCount + failCount} tests: ${passCount} passed, ${failCount} failed`);
6843
6854
  if (failCount > 0) {