capdag 0.178.444 → 0.181.455
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/capdag.js +363 -94
- package/capdag.test.js +364 -116
- package/package.json +2 -2
package/capdag.test.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// All implementations (Rust, Go, JS, ObjC, Python) must pass these identically.
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
|
-
CapUrn, CapKind, CapUrnBuilder, CapMatcher, CapUrnError, ErrorCodes,
|
|
6
|
+
CapUrn, CapKind, CapEffect, CapUrnBuilder, CapMatcher, CapUrnError, ErrorCodes,
|
|
7
7
|
MediaUrn, MediaUrnError, MediaUrnErrorCodes,
|
|
8
|
-
Cap, MediaSpec, MediaSpecError, MediaSpecErrorCodes,
|
|
8
|
+
Cap, CapGroup, CapManifest, MediaSpec, MediaSpecError, MediaSpecErrorCodes,
|
|
9
9
|
resolveMediaUrn, buildExtensionIndex, mediaUrnsForExtension, getExtensionMappings,
|
|
10
10
|
CartridgeInfo, CartridgeCapSummary, CartridgeSuggestion, CartridgeRepoClient, CartridgeRepoServer,
|
|
11
11
|
CapFabEdge, CapFabStats, CapFab,
|
|
@@ -26,7 +26,8 @@ const {
|
|
|
26
26
|
MEDIA_FILE_PATH,
|
|
27
27
|
MEDIA_COLLECTION, MEDIA_COLLECTION_LIST,
|
|
28
28
|
MEDIA_DECISION,
|
|
29
|
-
MEDIA_AUDIO_SPEECH
|
|
29
|
+
MEDIA_AUDIO_SPEECH,
|
|
30
|
+
CAP_IDENTITY
|
|
30
31
|
} = require('./capdag.js');
|
|
31
32
|
|
|
32
33
|
// ============================================================================
|
|
@@ -309,9 +310,15 @@ function test939_capUrnCanonicalFormDropsWildcardInOut() {
|
|
|
309
310
|
`input ${JSON.stringify(v)} canonicalized to ${JSON.stringify(parsed.toString())}, expected ${JSON.stringify(canonical)} — wildcard in/out segments must be elided so the registry SHA-256 key is stable across input spellings`
|
|
310
311
|
);
|
|
311
312
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
assertThrows(
|
|
314
|
+
() => CapUrn.fromString('cap:in=media:;out=media:'),
|
|
315
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
316
|
+
'declared top-to-top cap must be rejected as inadmissible'
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const identity = CapUrn.fromString('cap:effect=none');
|
|
320
|
+
assertEqual(identity.toString(), 'cap:effect=none', 'true identity must preserve explicit effect=none');
|
|
321
|
+
assert(identity.toString() !== generic.toString(), 'cap: and cap:effect=none must not collapse');
|
|
315
322
|
}
|
|
316
323
|
|
|
317
324
|
// TEST017: Test tag matching: exact match, subset match, wildcard match, value mismatch
|
|
@@ -494,17 +501,19 @@ function test027_wildcardTag() {
|
|
|
494
501
|
assertEqual(wildcardExt.getTag('ext'), '*', 'Should set ext to wildcard');
|
|
495
502
|
|
|
496
503
|
const wildcardIn = cap.withWildcardTag('in');
|
|
497
|
-
assertEqual(wildcardIn.getInSpec(), '
|
|
504
|
+
assertEqual(wildcardIn.getInSpec(), 'media:', 'Should set in to canonical top media:');
|
|
498
505
|
|
|
499
506
|
const wildcardOut = cap.withWildcardTag('out');
|
|
500
|
-
assertEqual(wildcardOut.getOutSpec(), '
|
|
507
|
+
assertEqual(wildcardOut.getOutSpec(), 'media:', 'Should set out to canonical top media:');
|
|
501
508
|
}
|
|
502
509
|
|
|
503
|
-
// TEST028: Test empty cap URN
|
|
510
|
+
// TEST028: Test empty cap URN is illegal
|
|
504
511
|
function test028_emptyCapUrnNotAllowed() {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
512
|
+
assertThrows(
|
|
513
|
+
() => CapUrn.fromString('cap:'),
|
|
514
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
515
|
+
'Empty cap must be rejected as inadmissible'
|
|
516
|
+
);
|
|
508
517
|
}
|
|
509
518
|
|
|
510
519
|
// TEST029: Test minimal valid cap URN has just in and out, empty tags
|
|
@@ -683,7 +692,7 @@ function test047_matchingSemanticsThumbnailVoidInput() {
|
|
|
683
692
|
|
|
684
693
|
// TEST048: Matching semantics - wildcard direction matches anything
|
|
685
694
|
function test048_matchingSemanticsWildcardDirection() {
|
|
686
|
-
const cap = CapUrn.fromString('cap:in=*;out
|
|
695
|
+
const cap = CapUrn.fromString('cap:in=*;out=*;op');
|
|
687
696
|
const request = CapUrn.fromString(testUrn('generate;ext=pdf'));
|
|
688
697
|
assert(cap.accepts(request), 'Wildcard cap should accept any request');
|
|
689
698
|
}
|
|
@@ -1209,6 +1218,152 @@ function test110_multipleExtensions() {
|
|
|
1209
1218
|
assertEqual(resolved.extensions[1], 'jpeg', 'Second extension should be jpeg');
|
|
1210
1219
|
}
|
|
1211
1220
|
|
|
1221
|
+
// TEST115: Test CapArg serialization and deserialization with multiple sources
|
|
1222
|
+
function test115_capArgSerialization() {
|
|
1223
|
+
const arg = new CapArg(
|
|
1224
|
+
MEDIA_STRING,
|
|
1225
|
+
true,
|
|
1226
|
+
[new ArgSource({ cli_flag: '--name' }), new ArgSource({ position: 0 })],
|
|
1227
|
+
{
|
|
1228
|
+
arg_description: 'The name argument',
|
|
1229
|
+
default_value: 400,
|
|
1230
|
+
metadata: { kind: 'example', flags: [true, false] }
|
|
1231
|
+
}
|
|
1232
|
+
);
|
|
1233
|
+
|
|
1234
|
+
const json = arg.toJSON();
|
|
1235
|
+
assertEqual(json.media_urn, MEDIA_STRING, 'media_urn must serialize');
|
|
1236
|
+
assertEqual(json.required, true, 'required must serialize');
|
|
1237
|
+
assertEqual(json.arg_description, 'The name argument', 'arg_description must serialize');
|
|
1238
|
+
assertEqual(json.default_value, 400, 'numeric default_value must serialize as number');
|
|
1239
|
+
assertEqual(JSON.stringify(json.metadata), JSON.stringify({ kind: 'example', flags: [true, false] }),
|
|
1240
|
+
'metadata must serialize as arbitrary JSON');
|
|
1241
|
+
|
|
1242
|
+
const roundTripped = CapArg.fromJSON(JSON.parse(JSON.stringify(json)));
|
|
1243
|
+
assertEqual(roundTripped.media_urn, arg.media_urn, 'media_urn must round-trip');
|
|
1244
|
+
assertEqual(roundTripped.required, arg.required, 'required must round-trip');
|
|
1245
|
+
assertEqual(roundTripped.arg_description, arg.arg_description, 'arg_description must round-trip');
|
|
1246
|
+
assertEqual(roundTripped.default_value, 400, 'numeric default_value must round-trip');
|
|
1247
|
+
assertEqual(JSON.stringify(roundTripped.metadata), JSON.stringify({ kind: 'example', flags: [true, false] }),
|
|
1248
|
+
'metadata must round-trip');
|
|
1249
|
+
assertEqual(roundTripped.sources.length, 2, 'sources length must round-trip');
|
|
1250
|
+
assertEqual(roundTripped.sources[0].cli_flag, '--name', 'cli_flag source must round-trip');
|
|
1251
|
+
assertEqual(roundTripped.sources[1].position, 0, 'position source must round-trip');
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// TEST116: Test CapArg constructor methods basic and with_description create args correctly
|
|
1255
|
+
function test116_capArgConstructors() {
|
|
1256
|
+
const basicArg = new CapArg(
|
|
1257
|
+
MEDIA_STRING,
|
|
1258
|
+
true,
|
|
1259
|
+
[new ArgSource({ cli_flag: '--name' })]
|
|
1260
|
+
);
|
|
1261
|
+
assertEqual(basicArg.media_urn, MEDIA_STRING, 'basic arg media_urn must match');
|
|
1262
|
+
assertEqual(basicArg.required, true, 'basic arg required must match');
|
|
1263
|
+
assertEqual(basicArg.sources.length, 1, 'basic arg must keep one source');
|
|
1264
|
+
assertEqual(basicArg.arg_description, null, 'basic arg arg_description must be absent');
|
|
1265
|
+
assertEqual(basicArg.default_value, null, 'basic arg default_value must be absent');
|
|
1266
|
+
|
|
1267
|
+
const describedArg = new CapArg(
|
|
1268
|
+
MEDIA_INTEGER,
|
|
1269
|
+
false,
|
|
1270
|
+
[new ArgSource({ position: 0 })],
|
|
1271
|
+
{
|
|
1272
|
+
arg_description: 'The count argument',
|
|
1273
|
+
default_value: 10
|
|
1274
|
+
}
|
|
1275
|
+
);
|
|
1276
|
+
assertEqual(describedArg.media_urn, MEDIA_INTEGER, 'described arg media_urn must match');
|
|
1277
|
+
assertEqual(describedArg.required, false, 'described arg required must match');
|
|
1278
|
+
assertEqual(describedArg.arg_description, 'The count argument', 'described arg description must match');
|
|
1279
|
+
assertEqual(describedArg.default_value, 10, 'described arg default_value must match');
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// TEST150: JSON roundtrip
|
|
1283
|
+
function test150_capManifestJsonSerialization() {
|
|
1284
|
+
const capUrn = CapUrn.fromString(testUrn('extract;target=metadata'));
|
|
1285
|
+
const cap = new Cap(capUrn, 'Extract Metadata', 'extract-metadata');
|
|
1286
|
+
cap.addArg(new CapArg('media:pdf', true, [new ArgSource({ stdin: 'media:pdf' })]));
|
|
1287
|
+
cap.addArg(new CapArg(
|
|
1288
|
+
'media:chunk-size;textable;numeric',
|
|
1289
|
+
false,
|
|
1290
|
+
[new ArgSource({ cli_flag: '--chunk-size' })],
|
|
1291
|
+
{
|
|
1292
|
+
arg_description: 'Chunk size',
|
|
1293
|
+
default_value: 400,
|
|
1294
|
+
metadata: { unit: 'words' }
|
|
1295
|
+
}
|
|
1296
|
+
));
|
|
1297
|
+
cap.addArg(new CapArg(
|
|
1298
|
+
'media:timestamps;textable;bool',
|
|
1299
|
+
false,
|
|
1300
|
+
[new ArgSource({ cli_flag: '--timestamps' })],
|
|
1301
|
+
{
|
|
1302
|
+
arg_description: 'Include timestamps',
|
|
1303
|
+
default_value: false
|
|
1304
|
+
}
|
|
1305
|
+
));
|
|
1306
|
+
|
|
1307
|
+
const manifest = new CapManifest(
|
|
1308
|
+
'TestComponent',
|
|
1309
|
+
'0.1.0',
|
|
1310
|
+
'release',
|
|
1311
|
+
null,
|
|
1312
|
+
'A test component',
|
|
1313
|
+
[new CapGroup('default', [cap], [])]
|
|
1314
|
+
);
|
|
1315
|
+
|
|
1316
|
+
manifest.author = 'Test Author';
|
|
1317
|
+
|
|
1318
|
+
const json = manifest.toJSON();
|
|
1319
|
+
assertEqual(json.name, 'TestComponent', 'manifest name must serialize');
|
|
1320
|
+
assertEqual(json.author, 'Test Author', 'author must serialize');
|
|
1321
|
+
assert(Array.isArray(json.cap_groups), 'cap_groups must serialize');
|
|
1322
|
+
assertEqual(json.cap_groups.length, 1, 'cap_groups length must serialize');
|
|
1323
|
+
assertEqual(json.cap_groups[0].caps[0].args[1].default_value, 400, 'numeric default must serialize as number');
|
|
1324
|
+
assertEqual(json.cap_groups[0].caps[0].args[1].metadata.unit, 'words', 'metadata must serialize');
|
|
1325
|
+
assertEqual(json.cap_groups[0].caps[0].args[2].default_value, false, 'boolean default must serialize as boolean');
|
|
1326
|
+
|
|
1327
|
+
const roundTripped = CapManifest.fromJSON(JSON.parse(JSON.stringify(json)));
|
|
1328
|
+
const decodedCap = roundTripped.allCaps()[0];
|
|
1329
|
+
assertEqual(roundTripped.name, manifest.name, 'manifest name must round-trip');
|
|
1330
|
+
assertEqual(roundTripped.author, 'Test Author', 'author must round-trip');
|
|
1331
|
+
assertEqual(roundTripped.cap_groups.length, 1, 'cap_groups length must round-trip');
|
|
1332
|
+
assertEqual(decodedCap.args[1].default_value, 400, 'numeric default must round-trip');
|
|
1333
|
+
assertEqual(JSON.stringify(decodedCap.args[1].metadata), JSON.stringify({ unit: 'words' }),
|
|
1334
|
+
'metadata must round-trip');
|
|
1335
|
+
assertEqual(decodedCap.args[2].default_value, false, 'boolean default must round-trip');
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// TEST597: CapArg::with_full_definition stores all fields including optional ones
|
|
1339
|
+
function test597_capArgWithFullDefinition() {
|
|
1340
|
+
const arg = new CapArg(
|
|
1341
|
+
MEDIA_STRING,
|
|
1342
|
+
true,
|
|
1343
|
+
[new ArgSource({ cli_flag: '--name' })],
|
|
1344
|
+
{
|
|
1345
|
+
arg_description: 'User name',
|
|
1346
|
+
default_value: { chunk_size: 400, timestamps: false },
|
|
1347
|
+
metadata: { hint: 'enter name' }
|
|
1348
|
+
}
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1351
|
+
assertEqual(arg.media_urn, MEDIA_STRING, 'media_urn must match');
|
|
1352
|
+
assertEqual(arg.required, true, 'required must match');
|
|
1353
|
+
assertEqual(arg.arg_description, 'User name', 'arg_description must match');
|
|
1354
|
+
assertEqual(JSON.stringify(arg.default_value), JSON.stringify({ chunk_size: 400, timestamps: false }),
|
|
1355
|
+
'object default_value must be preserved');
|
|
1356
|
+
assertEqual(JSON.stringify(arg.metadata), JSON.stringify({ hint: 'enter name' }),
|
|
1357
|
+
'metadata must be preserved');
|
|
1358
|
+
|
|
1359
|
+
const roundTripped = CapArg.fromJSON(JSON.parse(JSON.stringify(arg.toJSON())));
|
|
1360
|
+
assertEqual(roundTripped.arg_description, 'User name', 'arg_description must round-trip');
|
|
1361
|
+
assertEqual(JSON.stringify(roundTripped.default_value), JSON.stringify({ chunk_size: 400, timestamps: false }),
|
|
1362
|
+
'object default_value must round-trip');
|
|
1363
|
+
assertEqual(JSON.stringify(roundTripped.metadata), JSON.stringify({ hint: 'enter name' }),
|
|
1364
|
+
'metadata must round-trip');
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1212
1367
|
// ============================================================================
|
|
1213
1368
|
// cap_fab: browse-mode API used by cap-fab-renderer.js
|
|
1214
1369
|
//
|
|
@@ -2396,7 +2551,7 @@ function test1302_predicateConstantConsistency() {
|
|
|
2396
2551
|
// cap_urn.rs: TEST1303-TEST1307 (CapUrn tier tests)
|
|
2397
2552
|
// ============================================================================
|
|
2398
2553
|
|
|
2399
|
-
// TEST1303: without_tag removes tag,
|
|
2554
|
+
// TEST1303: without_tag removes tag, rejects structural keys, case-insensitive for keys
|
|
2400
2555
|
function test1303_withoutTag() {
|
|
2401
2556
|
const cap = CapUrn.fromString('cap:in="media:void";test;ext=pdf;out="media:void"');
|
|
2402
2557
|
const removed = cap.withoutTag('ext');
|
|
@@ -2407,11 +2562,9 @@ function test1303_withoutTag() {
|
|
|
2407
2562
|
const removed2 = cap.withoutTag('EXT');
|
|
2408
2563
|
assertEqual(removed2.getTag('ext'), undefined, 'withoutTag should be case-insensitive');
|
|
2409
2564
|
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
const same2 = cap.withoutTag('out');
|
|
2414
|
-
assertEqual(same2.getOutSpec(), 'media:void', 'withoutTag must not remove out');
|
|
2565
|
+
assertThrows(() => cap.withoutTag('in'), 'withoutTag must reject in');
|
|
2566
|
+
assertThrows(() => cap.withoutTag('out'), 'withoutTag must reject out');
|
|
2567
|
+
assertThrows(() => cap.withoutTag('effect'), 'withoutTag must reject effect');
|
|
2415
2568
|
|
|
2416
2569
|
// Removing non-existent tag is no-op
|
|
2417
2570
|
const same3 = cap.withoutTag('nonexistent');
|
|
@@ -2435,6 +2588,12 @@ function test1304_withInOutSpec() {
|
|
|
2435
2588
|
const changedBoth = cap.withInSpec('media:pdf').withOutSpec(MEDIA_TXT);
|
|
2436
2589
|
assertEqual(changedBoth.getInSpec(), 'media:pdf', 'Chain should set inSpec');
|
|
2437
2590
|
assertEqual(changedBoth.getOutSpec(), MEDIA_TXT, 'Chain should set outSpec');
|
|
2591
|
+
|
|
2592
|
+
const identity = CapUrn.fromString('cap:effect=none');
|
|
2593
|
+
assertThrows(
|
|
2594
|
+
() => identity.withOutSpec('media:pdf'),
|
|
2595
|
+
'withOutSpec must revalidate admissibility'
|
|
2596
|
+
);
|
|
2438
2597
|
}
|
|
2439
2598
|
|
|
2440
2599
|
// TEST561: N/A for JS (in_media_urn/out_media_urn not in JS CapUrn)
|
|
@@ -2484,15 +2643,28 @@ function test1306_areCompatible() {
|
|
|
2484
2643
|
|
|
2485
2644
|
// TEST565: N/A for JS (tags_to_string not in JS CapUrn)
|
|
2486
2645
|
|
|
2487
|
-
// TEST1307: with_tag
|
|
2488
|
-
function
|
|
2646
|
+
// TEST1307: with_tag rejects structural keys
|
|
2647
|
+
function test1307_withTagRejectsStructuralKeys() {
|
|
2489
2648
|
const cap = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2649
|
+
assertThrows(() => cap.withTag('in', 'media:'), 'withTag must reject in');
|
|
2650
|
+
assertThrows(() => cap.withTag('out', 'media:'), 'withTag must reject out');
|
|
2651
|
+
assertThrows(() => cap.withTag('effect', 'none'), 'withTag must reject effect');
|
|
2652
|
+
}
|
|
2493
2653
|
|
|
2494
|
-
|
|
2495
|
-
|
|
2654
|
+
// TEST1308: builder rejects structural keys on tag/marker
|
|
2655
|
+
function test1308_builderRejectsStructuralKeys() {
|
|
2656
|
+
assertThrows(
|
|
2657
|
+
() => new CapUrnBuilder().tag('in', 'media:void'),
|
|
2658
|
+
'builder.tag must reject structural in'
|
|
2659
|
+
);
|
|
2660
|
+
assertThrows(
|
|
2661
|
+
() => new CapUrnBuilder().marker('effect'),
|
|
2662
|
+
'builder.marker must reject structural effect'
|
|
2663
|
+
);
|
|
2664
|
+
assertThrows(
|
|
2665
|
+
() => new CapUrnBuilder().inSpec('media:void').outSpec('media:record').tag('123', 'value').build(),
|
|
2666
|
+
'builder.build must reject invalid non-structural tags'
|
|
2667
|
+
);
|
|
2496
2668
|
}
|
|
2497
2669
|
|
|
2498
2670
|
// TEST1294: RULE11 - void-input cap with stdin source rejected
|
|
@@ -2551,47 +2723,58 @@ function test1297_rule11NonVoidInputWithStdin() {
|
|
|
2551
2723
|
// cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
|
|
2552
2724
|
// ============================================================================
|
|
2553
2725
|
|
|
2554
|
-
// TEST639: cap: (empty)
|
|
2555
|
-
function
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2726
|
+
// TEST639: cap: (empty) is the illegal bare top form
|
|
2727
|
+
function test639_emptyCapIsIllegal() {
|
|
2728
|
+
assertThrows(
|
|
2729
|
+
() => CapUrn.fromString('cap:'),
|
|
2730
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2731
|
+
'Empty cap must be rejected as inadmissible'
|
|
2732
|
+
);
|
|
2560
2733
|
}
|
|
2561
2734
|
|
|
2562
|
-
// TEST640: cap:in
|
|
2563
|
-
function
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2735
|
+
// TEST640: cap:in collapses to the same illegal bare top form
|
|
2736
|
+
function test640_inOnlyIsIllegal() {
|
|
2737
|
+
assertThrows(
|
|
2738
|
+
() => CapUrn.fromString('cap:in'),
|
|
2739
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2740
|
+
'Bare in must be rejected as inadmissible'
|
|
2741
|
+
);
|
|
2567
2742
|
}
|
|
2568
2743
|
|
|
2569
|
-
// TEST641: cap:out
|
|
2570
|
-
function
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2744
|
+
// TEST641: cap:out collapses to the same illegal bare top form
|
|
2745
|
+
function test641_outOnlyIsIllegal() {
|
|
2746
|
+
assertThrows(
|
|
2747
|
+
() => CapUrn.fromString('cap:out'),
|
|
2748
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2749
|
+
'Bare out must be rejected as inadmissible'
|
|
2750
|
+
);
|
|
2574
2751
|
}
|
|
2575
2752
|
|
|
2576
|
-
// TEST642: cap:in;out
|
|
2577
|
-
function
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2753
|
+
// TEST642: cap:in;out collapses to the same illegal bare top form
|
|
2754
|
+
function test642_inOutWithoutValuesAreIllegal() {
|
|
2755
|
+
assertThrows(
|
|
2756
|
+
() => CapUrn.fromString('cap:in;out'),
|
|
2757
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2758
|
+
'Bare in/out must be rejected as inadmissible'
|
|
2759
|
+
);
|
|
2581
2760
|
}
|
|
2582
2761
|
|
|
2583
|
-
// TEST643: cap:in=*;out=*
|
|
2584
|
-
function
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2762
|
+
// TEST643: cap:in=*;out=* is the same illegal bare top form
|
|
2763
|
+
function test643_explicitAsteriskIsIllegal() {
|
|
2764
|
+
assertThrows(
|
|
2765
|
+
() => CapUrn.fromString('cap:in=*;out=*'),
|
|
2766
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2767
|
+
'Explicit wildcard top-to-top must be rejected as inadmissible'
|
|
2768
|
+
);
|
|
2588
2769
|
}
|
|
2589
2770
|
|
|
2590
|
-
// TEST644: cap:in=media:;out=*
|
|
2591
|
-
function
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2771
|
+
// TEST644: cap:in=media:;out=* is the same illegal bare top form
|
|
2772
|
+
function test644_specificInWildcardOutIsIllegal() {
|
|
2773
|
+
assertThrows(
|
|
2774
|
+
() => CapUrn.fromString('cap:in=media:;out=*'),
|
|
2775
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2776
|
+
'Top-to-top declared form must be rejected as inadmissible'
|
|
2777
|
+
);
|
|
2595
2778
|
}
|
|
2596
2779
|
|
|
2597
2780
|
// TEST645: cap:in=*;out=media:text has wildcard in, specific out
|
|
@@ -2621,8 +2804,8 @@ function test647_invalidOutSpecFails() {
|
|
|
2621
2804
|
|
|
2622
2805
|
// TEST648: Wildcard in/out match specific caps
|
|
2623
2806
|
function test648_wildcardAcceptsSpecific() {
|
|
2624
|
-
const wildcard = CapUrn.fromString('cap:in=*;out
|
|
2625
|
-
const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
|
|
2807
|
+
const wildcard = CapUrn.fromString('cap:in=*;out=*;raw');
|
|
2808
|
+
const specific = CapUrn.fromString('cap:in="media:";out="media:text";raw');
|
|
2626
2809
|
|
|
2627
2810
|
assert(wildcard.accepts(specific), 'Wildcard should accept specific');
|
|
2628
2811
|
assert(specific.conformsTo(wildcard), 'Specific should conform to wildcard');
|
|
@@ -2630,52 +2813,105 @@ function test648_wildcardAcceptsSpecific() {
|
|
|
2630
2813
|
|
|
2631
2814
|
// TEST649: Specificity - wildcard has 0, specific has tag count
|
|
2632
2815
|
function test649_specificityScoring() {
|
|
2633
|
-
const wildcard = CapUrn.fromString('cap:in=*;out
|
|
2634
|
-
const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
|
|
2816
|
+
const wildcard = CapUrn.fromString('cap:in=*;out=*;raw');
|
|
2817
|
+
const specific = CapUrn.fromString('cap:in="media:";out="media:text";raw');
|
|
2635
2818
|
|
|
2636
|
-
assertEqual(wildcard.specificity(),
|
|
2819
|
+
assertEqual(wildcard.specificity(), 2, 'Marker-only wildcard cap should have y-axis specificity only');
|
|
2637
2820
|
assert(specific.specificity() > 0, 'Specific cap should have non-zero specificity');
|
|
2638
2821
|
}
|
|
2639
2822
|
|
|
2640
|
-
// TEST650:
|
|
2823
|
+
// TEST650: cap:in=media:;out=media:;test preserves other tags
|
|
2824
|
+
function test650_wildcardPreserveOtherTags() {
|
|
2825
|
+
const cap = CapUrn.fromString('cap:in=media:;out=media:;test');
|
|
2826
|
+
assertEqual(cap.getInSpec(), 'media:', 'in spec should remain media:');
|
|
2827
|
+
assertEqual(cap.getOutSpec(), 'media:', 'out spec should remain media:');
|
|
2828
|
+
assertEqual(cap.getEffect(), CapEffect.DECLARED, 'missing effect should default to declared');
|
|
2829
|
+
assert(cap.hasMarkerTag('test'), 'marker tag should be preserved');
|
|
2830
|
+
}
|
|
2641
2831
|
|
|
2642
|
-
// TEST651:
|
|
2643
|
-
function
|
|
2832
|
+
// TEST651: Generic top-to-top spellings are all rejected.
|
|
2833
|
+
function test651_wildcardGenericFormsRejected() {
|
|
2644
2834
|
const forms = [
|
|
2835
|
+
'cap:',
|
|
2836
|
+
'cap:in;out',
|
|
2645
2837
|
'cap:in=*;out=*',
|
|
2646
|
-
'cap:in=
|
|
2838
|
+
'cap:in=media:;out=media:',
|
|
2839
|
+
'cap:in;out=media:',
|
|
2840
|
+
'cap:in=*;out=media:',
|
|
2841
|
+
'cap:in=media:;out',
|
|
2842
|
+
'cap:in=media:;out=*',
|
|
2647
2843
|
];
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
|
|
2655
|
-
assert(first.accepts(specific), `Form 0 should accept specific`);
|
|
2656
|
-
assert(cap.accepts(specific), `Form ${i} should accept specific`);
|
|
2844
|
+
for (const form of forms) {
|
|
2845
|
+
assertThrows(
|
|
2846
|
+
() => CapUrn.fromString(form),
|
|
2847
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2848
|
+
`${form} must be rejected as inadmissible`
|
|
2849
|
+
);
|
|
2657
2850
|
}
|
|
2658
2851
|
}
|
|
2659
2852
|
|
|
2660
|
-
// TEST652:
|
|
2853
|
+
// TEST652: CAP_IDENTITY constant names the true identity cap, not bare cap:
|
|
2854
|
+
function test652_capIdentityConstantWorks() {
|
|
2855
|
+
const identity = CapUrn.fromString(CAP_IDENTITY);
|
|
2856
|
+
assertEqual(identity.toString(), 'cap:effect=none', 'CAP_IDENTITY must be explicit effect=none');
|
|
2857
|
+
assertEqual(identity.kind(), CapKind.IDENTITY, 'CAP_IDENTITY must classify as identity');
|
|
2858
|
+
|
|
2859
|
+
const longForm = CapUrn.fromString('cap:in=media:;out=media:;effect=none');
|
|
2860
|
+
assert(identity.accepts(longForm), 'identity should accept its long form');
|
|
2861
|
+
assert(longForm.accepts(identity), 'long form should accept canonical identity');
|
|
2862
|
+
|
|
2863
|
+
assertThrows(
|
|
2864
|
+
() => CapUrn.fromString('cap:'),
|
|
2865
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2866
|
+
'bare cap must be rejected as inadmissible'
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
// TEST653: invalid effect=none declarations fail at construction.
|
|
2871
|
+
function test653_invalidEffectNoneDeclarationRejected() {
|
|
2872
|
+
assertThrows(
|
|
2873
|
+
() => CapUrn.fromString('cap:in=media:pdf;out=media:textable;effect=none'),
|
|
2874
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2875
|
+
'invalid effect=none declaration must fail at construction'
|
|
2876
|
+
);
|
|
2877
|
+
}
|
|
2661
2878
|
|
|
2662
|
-
//
|
|
2663
|
-
function
|
|
2664
|
-
const
|
|
2665
|
-
const
|
|
2879
|
+
// TEST654: effect=none preserves runtime media identity.
|
|
2880
|
+
function test654_effectNonePreservesRuntimeMedia() {
|
|
2881
|
+
const decimate = CapUrn.fromString('cap:decimate-sequence;effect=none');
|
|
2882
|
+
const png = MediaUrn.fromString('media:image;png');
|
|
2883
|
+
const pdf = MediaUrn.fromString('media:pdf');
|
|
2884
|
+
assertEqual(decimate.inferRuntimeOutputMedia(png).toString(), png.toString(), 'effect=none should preserve png');
|
|
2885
|
+
assertEqual(decimate.inferRuntimeOutputMedia(pdf).toString(), pdf.toString(), 'effect=none should preserve pdf');
|
|
2886
|
+
}
|
|
2666
2887
|
|
|
2667
|
-
|
|
2668
|
-
|
|
2888
|
+
// TEST655: default effect=declared does not preserve runtime refinements.
|
|
2889
|
+
function test655_effectDeclaredUsesDeclaredOutput() {
|
|
2890
|
+
const resize = CapUrn.fromString('cap:in=media:image;out=media:image;resize');
|
|
2891
|
+
const png = MediaUrn.fromString('media:image;png;width=4000');
|
|
2892
|
+
assertEqual(
|
|
2893
|
+
resize.inferRuntimeOutputMedia(png).toString(),
|
|
2894
|
+
'media:image',
|
|
2895
|
+
'default declared effect should collapse to declared output'
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2669
2898
|
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2899
|
+
// TEST656: invalid effect=none declarations fail hard at construction.
|
|
2900
|
+
function test656_invalidEffectNoneFailsHard() {
|
|
2901
|
+
assertThrows(
|
|
2902
|
+
() => CapUrn.fromString('cap:in=media:pdf;out=media:textable;effect=none'),
|
|
2903
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
2904
|
+
'invalid effect=none declaration must fail at construction'
|
|
2905
|
+
);
|
|
2906
|
+
}
|
|
2673
2907
|
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
const
|
|
2677
|
-
|
|
2678
|
-
|
|
2908
|
+
// TEST657: omitted effect means declared; unconstrained effect must be explicit.
|
|
2909
|
+
function test657_effectDispatchRequiresExplicitWildcard() {
|
|
2910
|
+
const noneProvider = CapUrn.fromString('cap:effect=none');
|
|
2911
|
+
const declaredRequest = CapUrn.fromString('cap:raw');
|
|
2912
|
+
const anyRequest = CapUrn.fromString('cap:?effect');
|
|
2913
|
+
assert(!noneProvider.isDispatchable(declaredRequest), 'effect=none should not silently satisfy declared request');
|
|
2914
|
+
assert(noneProvider.isDispatchable(anyRequest), 'explicit ?effect should accept any provider effect');
|
|
2679
2915
|
}
|
|
2680
2916
|
|
|
2681
2917
|
// ============================================================================
|
|
@@ -5335,25 +5571,28 @@ function testRenderer_validateResolvedMachinePayload_rejectsMissingFields() {
|
|
|
5335
5571
|
// surface, not a per-port detail.
|
|
5336
5572
|
// ============================================================================
|
|
5337
5573
|
|
|
5338
|
-
// TEST1800: Identity classifier — only
|
|
5339
|
-
// Adding any tag (even one that doesn't constrain in/out) demotes
|
|
5340
|
-
// the cap to Transform because the operation/metadata axis is no
|
|
5341
|
-
// longer fully generic.
|
|
5574
|
+
// TEST1800: Identity classifier — only explicit effect=none qualifies.
|
|
5342
5575
|
function test1800_kindIdentityOnlyForBareCap() {
|
|
5343
|
-
const identity = CapUrn.fromString('cap:');
|
|
5344
|
-
assertEqual(identity.kind(), CapKind.IDENTITY, 'cap: should be Identity');
|
|
5576
|
+
const identity = CapUrn.fromString('cap:effect=none');
|
|
5577
|
+
assertEqual(identity.kind(), CapKind.IDENTITY, 'cap:effect=none should be Identity');
|
|
5345
5578
|
|
|
5346
5579
|
for (const spelling of [
|
|
5347
|
-
'cap:in=media:;out=media
|
|
5348
|
-
'cap:in=*;out=*',
|
|
5349
|
-
'cap:in=media:',
|
|
5350
|
-
'cap:out=media:',
|
|
5580
|
+
'cap:in=media:;out=media:;effect=none',
|
|
5581
|
+
'cap:effect=none;in=*;out=*',
|
|
5582
|
+
'cap:effect=none;in=media:',
|
|
5583
|
+
'cap:effect=none;out=media:',
|
|
5351
5584
|
]) {
|
|
5352
5585
|
const cap = CapUrn.fromString(spelling);
|
|
5353
5586
|
assertEqual(cap.kind(), CapKind.IDENTITY,
|
|
5354
|
-
`${spelling} should classify as Identity
|
|
5587
|
+
`${spelling} should classify as Identity`);
|
|
5355
5588
|
}
|
|
5356
5589
|
|
|
5590
|
+
assertThrows(
|
|
5591
|
+
() => CapUrn.fromString('cap:'),
|
|
5592
|
+
ErrorCodes.ILLEGAL_DECLARATION,
|
|
5593
|
+
'bare cap must be rejected as inadmissible'
|
|
5594
|
+
);
|
|
5595
|
+
|
|
5357
5596
|
const withOp = CapUrn.fromString('cap:passthrough');
|
|
5358
5597
|
assertEqual(withOp.kind(), CapKind.TRANSFORM,
|
|
5359
5598
|
'cap:passthrough specifies the operation axis — not Identity');
|
|
@@ -5441,7 +5680,7 @@ function test1810_mediaVoidIsAtomic() {
|
|
|
5441
5680
|
// once parsed.
|
|
5442
5681
|
function test1805_kindInvariantUnderCanonicalSpellings() {
|
|
5443
5682
|
const cases = [
|
|
5444
|
-
{ a: 'cap:', b: 'cap:in=media:;out=media
|
|
5683
|
+
{ a: 'cap:effect=none', b: 'cap:in=media:;out=media:;effect=none', expected: CapKind.IDENTITY },
|
|
5445
5684
|
{
|
|
5446
5685
|
a: 'cap:extract;in=media:pdf;out=media:textable',
|
|
5447
5686
|
b: 'cap:extract;in="media:pdf";out="media:textable"',
|
|
@@ -5481,8 +5720,8 @@ function test1805_kindInvariantUnderCanonicalSpellings() {
|
|
|
5481
5720
|
|
|
5482
5721
|
// TEST1820: A `?`-valued cap-tag scores 0. Same as missing.
|
|
5483
5722
|
function test1820_specificityQuestionIsZero() {
|
|
5484
|
-
const bare = CapUrn.fromString('cap
|
|
5485
|
-
assertEqual(bare.specificity(), 0, 'cap
|
|
5723
|
+
const bare = CapUrn.fromString('cap:?effect');
|
|
5724
|
+
assertEqual(bare.specificity(), 0, 'cap:?effect must score 0 (fully unconstrained request)');
|
|
5486
5725
|
|
|
5487
5726
|
const withQ = CapUrn.fromString('cap:?target');
|
|
5488
5727
|
assertEqual(withQ.specificity(), 0,
|
|
@@ -5785,6 +6024,10 @@ async function runTests() {
|
|
|
5785
6024
|
runTest('TEST108: extensions_serialization', test108_extensionsSerialization);
|
|
5786
6025
|
runTest('TEST109: extensions_with_metadata_and_validation', test109_extensionsWithMetadataAndValidation);
|
|
5787
6026
|
runTest('TEST110: multiple_extensions', test110_multipleExtensions);
|
|
6027
|
+
runTest('TEST115: cap_arg_serialization', test115_capArgSerialization);
|
|
6028
|
+
runTest('TEST116: cap_arg_constructors', test116_capArgConstructors);
|
|
6029
|
+
runTest('TEST150: cap_manifest_json_serialization', test150_capManifestJsonSerialization);
|
|
6030
|
+
runTest('TEST597: cap_arg_with_full_definition', test597_capArgWithFullDefinition);
|
|
5788
6031
|
|
|
5789
6032
|
// cap-fab-renderer.js uses CapFab in browse mode (static registry from
|
|
5790
6033
|
// /api/capabilities). These tests guard the minimal API the renderer relies
|
|
@@ -5873,7 +6116,8 @@ async function runTests() {
|
|
|
5873
6116
|
runTest('TEST1304: with_in_out_spec', test1304_withInOutSpec);
|
|
5874
6117
|
runTest('TEST1305: find_all_matches', test1305_findAllMatches);
|
|
5875
6118
|
runTest('TEST1306: are_compatible', test1306_areCompatible);
|
|
5876
|
-
runTest('TEST1307:
|
|
6119
|
+
runTest('TEST1307: with_tag_rejects_structural_keys', test1307_withTagRejectsStructuralKeys);
|
|
6120
|
+
runTest('TEST1308: builder_rejects_structural_keys', test1308_builderRejectsStructuralKeys);
|
|
5877
6121
|
runTest('TEST1294: rule11_void_input_with_stdin_rejected', test1294_rule11VoidInputWithStdinRejected);
|
|
5878
6122
|
runTest('TEST1295: rule11_non_void_input_without_stdin_rejected', test1295_rule11NonVoidInputWithoutStdinRejected);
|
|
5879
6123
|
runTest('TEST1296: rule11_void_input_cli_flag_only', test1296_rule11VoidInputCliFlagOnly);
|
|
@@ -5881,21 +6125,25 @@ async function runTests() {
|
|
|
5881
6125
|
|
|
5882
6126
|
// cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
|
|
5883
6127
|
console.log('\n--- cap_urn.rs (wildcard tests) ---');
|
|
5884
|
-
runTest('TEST639:
|
|
5885
|
-
runTest('TEST640:
|
|
5886
|
-
runTest('TEST641:
|
|
5887
|
-
runTest('TEST642:
|
|
5888
|
-
runTest('TEST643:
|
|
5889
|
-
runTest('TEST644:
|
|
6128
|
+
runTest('TEST639: empty_cap_is_illegal', test639_emptyCapIsIllegal);
|
|
6129
|
+
runTest('TEST640: in_only_is_illegal', test640_inOnlyIsIllegal);
|
|
6130
|
+
runTest('TEST641: out_only_is_illegal', test641_outOnlyIsIllegal);
|
|
6131
|
+
runTest('TEST642: in_out_without_values_are_illegal', test642_inOutWithoutValuesAreIllegal);
|
|
6132
|
+
runTest('TEST643: explicit_asterisk_is_illegal', test643_explicitAsteriskIsIllegal);
|
|
6133
|
+
runTest('TEST644: specific_in_wildcard_out_is_illegal', test644_specificInWildcardOutIsIllegal);
|
|
5890
6134
|
runTest('TEST645: wildcard_in_specific_out', test645_wildcardInSpecificOut);
|
|
5891
6135
|
runTest('TEST646: invalid_in_spec_fails', test646_invalidInSpecFails);
|
|
5892
6136
|
runTest('TEST647: invalid_out_spec_fails', test647_invalidOutSpecFails);
|
|
5893
6137
|
runTest('TEST648: wildcard_accepts_specific', test648_wildcardAcceptsSpecific);
|
|
5894
6138
|
runTest('TEST649: specificity_scoring', test649_specificityScoring);
|
|
5895
|
-
|
|
5896
|
-
runTest('TEST651:
|
|
5897
|
-
|
|
5898
|
-
runTest('TEST653:
|
|
6139
|
+
runTest('TEST650: wildcard_preserve_other_tags', test650_wildcardPreserveOtherTags);
|
|
6140
|
+
runTest('TEST651: wildcard_generic_forms_rejected', test651_wildcardGenericFormsRejected);
|
|
6141
|
+
runTest('TEST652: cap_identity_constant_works', test652_capIdentityConstantWorks);
|
|
6142
|
+
runTest('TEST653: invalid_effect_none_declaration_rejected', test653_invalidEffectNoneDeclarationRejected);
|
|
6143
|
+
runTest('TEST654: effect_none_preserves_runtime_media', test654_effectNonePreservesRuntimeMedia);
|
|
6144
|
+
runTest('TEST655: effect_declared_uses_declared_output', test655_effectDeclaredUsesDeclaredOutput);
|
|
6145
|
+
runTest('TEST656: invalid_effect_none_fails_hard', test656_invalidEffectNoneFailsHard);
|
|
6146
|
+
runTest('TEST657: effect_dispatch_requires_explicit_wildcard', test657_effectDispatchRequiresExplicitWildcard);
|
|
5899
6147
|
|
|
5900
6148
|
// machine module: parser tests (mirrors parser.rs)
|
|
5901
6149
|
console.log('\n--- machine/parser.rs ---');
|
|
@@ -6069,7 +6317,7 @@ async function runTests() {
|
|
|
6069
6317
|
runTest('RENDERER: validateResolvedMachine_rejectsMissingFields', testRenderer_validateResolvedMachinePayload_rejectsMissingFields);
|
|
6070
6318
|
|
|
6071
6319
|
console.log('\n--- CapKind classifier (test1800–test1805) ---');
|
|
6072
|
-
runTest('TEST1800:
|
|
6320
|
+
runTest('TEST1800: kind_identity_requires_effect_none', test1800_kindIdentityOnlyForBareCap);
|
|
6073
6321
|
runTest('TEST1801: kind_source_when_input_is_void', test1801_kindSourceWhenInputIsVoid);
|
|
6074
6322
|
runTest('TEST1802: kind_sink_when_output_is_void', test1802_kindSinkWhenOutputIsVoid);
|
|
6075
6323
|
runTest('TEST1803: kind_effect_when_both_sides_void', test1803_kindEffectWhenBothSidesVoid);
|