@danceiny/gotry 0.0.1-rc.20 → 0.0.1-rc.21

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.
@@ -4,9 +4,9 @@ import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { Context } from '@deepseek-ai/cordis';
6
6
  import { apply } from '../src/index.js';
7
- import { BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, registerBenchmarkEnvironmentBridge } from '../src/benchmark-environment-bridge.js';
7
+ import { BRIDGE_ERROR_CONTRACT, BENCHMARK_TOOL_RESULT_SCHEMA_VERSION, registerBenchmarkEnvironmentBridge } from '../src/benchmark-environment-bridge.js';
8
8
  import { installBenchmarkToolIsolation } from '../src/benchmark-tool-isolation.js';
9
- import { BENCHMARK_BRIDGE_CALL_FAILED, BENCHMARK_BRIDGE_CALL_REQUIRED, BENCHMARK_BRIDGE_RETRY_CALL_NOT_ALLOWED, BENCHMARK_BRIDGE_OUTPUT_TRUNCATED, BENCHMARK_BRIDGE_RUNNER_FAILED, BENCHMARK_BRIDGE_SPAWN_FAILED, BENCHMARK_BRIDGE_TIMED_OUT, BENCHMARK_CONFORMANCE_STATE_UNAVAILABLE, BENCHMARK_TERMINAL_INVALID, MAX_CONFORMANCE_RETRIES, benchmarkChildFailureForConformanceCode, createBenchmarkAgentConformance, installBenchmarkAgentConformance, parseBenchmarkTerminal, validateTerminalOutputConfig } from '../src/benchmark-agent-conformance.js';
9
+ import { BENCHMARK_BRIDGE_CALL_FAILED, BENCHMARK_BRIDGE_CALL_REQUIRED, BENCHMARK_BRIDGE_RETRY_CALL_NOT_ALLOWED, BENCHMARK_BRIDGE_OUTPUT_TRUNCATED, BENCHMARK_BRIDGE_RUNNER_FAILED, BENCHMARK_BRIDGE_SPAWN_FAILED, BENCHMARK_BRIDGE_TIMED_OUT, BENCHMARK_CONFORMANCE_STATE_UNAVAILABLE, BENCHMARK_TERMINAL_INVALID, MAX_CONFORMANCE_RETRIES, benchmarkChildFailureForConformanceCode, createBenchmarkAgentConformance, installBenchmarkAgentConformance, parseBenchmarkTerminal, terminalSchemaOutline, validateTerminalBodySchema, validateTerminalBodyValue, validateTerminalOutputConfig } from '../src/benchmark-agent-conformance.js';
10
10
  import { BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES, BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA, appendBoundedChildDiagnostic, classifyBenchmarkChildFailure, classifyBenchmarkTurnEnd, createBenchmarkDiagnosticArbiter, parseBenchmarkChildDiagnostic } from '../src/benchmark-headless-child-diagnostics.js';
11
11
  {
12
12
  const exactFamilies = [
@@ -262,6 +262,22 @@ import { BENCHMARK_CHILD_DIAGNOSTIC_MAX_BYTES, BENCHMARK_CHILD_DIAGNOSTIC_SCHEMA
262
262
  'child_model_server'
263
263
  ]);
264
264
  }assert.equal(MAX_CONFORMANCE_RETRIES, 1);
265
+ const OK_BODY_SCHEMA = {
266
+ type: 'object',
267
+ properties: {
268
+ ok: {
269
+ type: 'boolean'
270
+ },
271
+ status: {
272
+ type: 'string'
273
+ },
274
+ x: {
275
+ type: 'string'
276
+ }
277
+ },
278
+ required: [],
279
+ additionalProperties: false
280
+ };
265
281
  const projection = {
266
282
  toolName: 'gotry_benchmark_environment',
267
283
  allowedTools: [
@@ -269,7 +285,8 @@ const projection = {
269
285
  ],
270
286
  terminal: {
271
287
  tag: 'done',
272
- max_bytes: 1024
288
+ max_bytes: 1024,
289
+ body_schema: OK_BODY_SCHEMA
273
290
  }
274
291
  };
275
292
  assert.equal(validateTerminalOutputConfig(projection.terminal), true);
@@ -294,6 +311,20 @@ for (const invalid of [
294
311
  tag: 'done',
295
312
  max_bytes: 1024,
296
313
  extra: true
314
+ },
315
+ {
316
+ tag: 'done',
317
+ max_bytes: 1024
318
+ },
319
+ {
320
+ tag: 'done',
321
+ max_bytes: 1024,
322
+ body_schema: {
323
+ type: 'object',
324
+ properties: {},
325
+ required: [],
326
+ additionalProperties: true
327
+ }
297
328
  }
298
329
  ])assert.equal(validateTerminalOutputConfig(invalid), false);
299
330
  assert.deepEqual(parseBenchmarkTerminal(' \n<done>{"ok":true}</done>\n', projection.terminal), {
@@ -338,7 +369,284 @@ for (const [raw, expectOk] of [
338
369
  ]){
339
370
  assert.equal(parseBenchmarkTerminal(raw, projection.terminal).ok, expectOk, raw);
340
371
  }
341
- function turnStart(turn = 1) {
372
+ {
373
+ const travelSchema = {
374
+ type: 'object',
375
+ properties: {
376
+ people_number: {
377
+ type: 'integer'
378
+ },
379
+ start_city: {
380
+ type: 'string'
381
+ },
382
+ target_city: {
383
+ type: 'string'
384
+ },
385
+ itinerary: {
386
+ type: 'array',
387
+ items: {
388
+ type: 'object',
389
+ properties: {
390
+ day: {
391
+ type: 'integer'
392
+ },
393
+ activities: {
394
+ type: 'array',
395
+ items: {
396
+ type: 'object',
397
+ properties: {
398
+ type: {
399
+ type: 'string'
400
+ },
401
+ start_time: {
402
+ type: 'string'
403
+ },
404
+ cost: {
405
+ type: 'number'
406
+ },
407
+ transports: {
408
+ type: 'array',
409
+ items: {
410
+ type: 'object',
411
+ properties: {
412
+ start: {
413
+ type: 'string'
414
+ },
415
+ mode: {
416
+ type: 'string'
417
+ }
418
+ },
419
+ required: [
420
+ 'start',
421
+ 'mode'
422
+ ],
423
+ additionalProperties: false
424
+ }
425
+ }
426
+ },
427
+ required: [
428
+ 'type',
429
+ 'start_time',
430
+ 'cost',
431
+ 'transports'
432
+ ],
433
+ additionalProperties: false
434
+ }
435
+ }
436
+ },
437
+ required: [
438
+ 'day',
439
+ 'activities'
440
+ ],
441
+ additionalProperties: false
442
+ }
443
+ }
444
+ },
445
+ required: [
446
+ 'people_number',
447
+ 'start_city',
448
+ 'target_city',
449
+ 'itinerary'
450
+ ],
451
+ additionalProperties: false
452
+ };
453
+ const travelConfig = {
454
+ tag: 'done',
455
+ max_bytes: 65_536,
456
+ body_schema: travelSchema
457
+ };
458
+ assert.equal(validateTerminalOutputConfig(travelConfig), true, 'ChinaTravel-like closed schema is a valid terminal config');
459
+ const legalBody = {
460
+ people_number: 2,
461
+ start_city: 'Dubai',
462
+ target_city: 'Chengdu',
463
+ itinerary: [
464
+ {
465
+ day: 1,
466
+ activities: [
467
+ {
468
+ type: 'attraction',
469
+ start_time: '09:00',
470
+ cost: 0,
471
+ transports: [
472
+ {
473
+ start: 'hotel',
474
+ mode: 'walk'
475
+ }
476
+ ]
477
+ }
478
+ ]
479
+ }
480
+ ]
481
+ };
482
+ assert.equal(validateTerminalBodySchema(travelSchema), true);
483
+ const mutate = (fn)=>{
484
+ const copy = JSON.parse(JSON.stringify(travelSchema));
485
+ fn(copy);
486
+ return copy;
487
+ };
488
+ for (const [label, schema] of [
489
+ [
490
+ 'extra root key',
491
+ mutate((s)=>{
492
+ s.description = 'the plan';
493
+ })
494
+ ],
495
+ [
496
+ 'enum on scalar',
497
+ mutate((s)=>{
498
+ s.properties.people_number.enum = [
499
+ 2
500
+ ];
501
+ })
502
+ ],
503
+ [
504
+ 'const on scalar',
505
+ mutate((s)=>{
506
+ s.properties.start_city.const = 'Dubai';
507
+ })
508
+ ],
509
+ [
510
+ 'default on scalar',
511
+ mutate((s)=>{
512
+ s.properties.target_city.default = 'Chengdu';
513
+ })
514
+ ],
515
+ [
516
+ 'examples key',
517
+ mutate((s)=>{
518
+ s.properties.itinerary.examples = [];
519
+ })
520
+ ],
521
+ [
522
+ 'pattern on scalar',
523
+ mutate((s)=>{
524
+ s.properties.start_city.pattern = '^[A-Z]';
525
+ })
526
+ ],
527
+ [
528
+ 'open object',
529
+ mutate((s)=>{
530
+ s.additionalProperties = true;
531
+ })
532
+ ],
533
+ [
534
+ 'missing additionalProperties',
535
+ mutate((s)=>{
536
+ delete s.additionalProperties;
537
+ })
538
+ ],
539
+ [
540
+ 'array without items',
541
+ mutate((s)=>{
542
+ s.properties.itinerary = {
543
+ type: 'array'
544
+ };
545
+ })
546
+ ],
547
+ [
548
+ 'object without required',
549
+ mutate((s)=>{
550
+ delete s.required;
551
+ })
552
+ ],
553
+ [
554
+ 'unknown type',
555
+ mutate((s)=>{
556
+ s.properties.people_number.type = 'float';
557
+ })
558
+ ],
559
+ [
560
+ 'allOf composition',
561
+ mutate((s)=>{
562
+ s.allOf = [];
563
+ })
564
+ ]
565
+ ]){
566
+ assert.equal(validateTerminalBodySchema(schema), false, `schema dialect rejects: ${label}`);
567
+ }
568
+ assert.equal(validateTerminalBodyValue(legalBody, travelSchema), true, 'legal ChinaTravel-like hierarchy passes');
569
+ assert.equal(parseBenchmarkTerminal(`<done>${JSON.stringify(legalBody)}</done>`, travelConfig).ok, true, 'legal body parses through the terminal gate');
570
+ for (const [label, body] of [
571
+ [
572
+ 'extra root key (Round 11 budget/total_cost case)',
573
+ {
574
+ ...legalBody,
575
+ budget: {
576
+ total_cost: 1
577
+ }
578
+ }
579
+ ],
580
+ [
581
+ 'itinerary item written as direct activity (missing day)',
582
+ {
583
+ ...legalBody,
584
+ itinerary: [
585
+ {
586
+ type: 'attraction',
587
+ start_time: '09:00',
588
+ cost: 0,
589
+ transports: []
590
+ }
591
+ ]
592
+ }
593
+ ],
594
+ [
595
+ 'missing activities array',
596
+ {
597
+ ...legalBody,
598
+ itinerary: [
599
+ {
600
+ day: 1
601
+ }
602
+ ]
603
+ }
604
+ ],
605
+ [
606
+ 'wrong type (string day)',
607
+ {
608
+ ...legalBody,
609
+ itinerary: [
610
+ {
611
+ day: '1',
612
+ activities: []
613
+ }
614
+ ]
615
+ }
616
+ ],
617
+ [
618
+ 'nested extra field',
619
+ {
620
+ ...legalBody,
621
+ itinerary: [
622
+ {
623
+ day: 1,
624
+ activities: [
625
+ {
626
+ type: 'attraction',
627
+ start_time: '09:00',
628
+ cost: 0,
629
+ transports: [],
630
+ unexpected: 1
631
+ }
632
+ ]
633
+ }
634
+ ]
635
+ }
636
+ ]
637
+ ]){
638
+ assert.equal(validateTerminalBodyValue(body, travelSchema), false, `value validation rejects: ${label}`);
639
+ assert.equal(parseBenchmarkTerminal(`<done>${JSON.stringify(body)}</done>`, travelConfig).ok, false, `terminal gate rejects: ${label}`);
640
+ }
641
+ assert.equal(parseBenchmarkTerminal(`<done>${' '.repeat(2000)}${JSON.stringify({
642
+ ok: true
643
+ })}</done>`, projection.terminal).ok, false, 'schema-valid body over max_bytes still rejects');
644
+ const outline = terminalSchemaOutline(travelSchema);
645
+ assert.equal(outline, terminalSchemaOutline(JSON.parse(JSON.stringify(travelSchema))), 'outline is deterministic');
646
+ assert.equal(outline, 'object{people_number:integer,start_city:string,target_city:string,itinerary:array<object{day:integer,activities:array<object{type:string,start_time:string,cost:number,transports:array<object{start:string,mode:string}>}>}>}', 'outline renders the exact structural contract');
647
+ assert.match(outline, /object\{/, 'outline renders object nodes');
648
+ assert.ok(!outline.includes('optional') && !outline.includes('?'), 'all keys required render without optional markers');
649
+ }function turnStart(turn = 1) {
342
650
  return {
343
651
  type: 'turn/start',
344
652
  data: {
@@ -874,6 +1182,8 @@ function toolResult(callId = 'call-1', options = {}) {
874
1182
  assert.match(assembled.sections[0].text, /\"action\":\"call\"/);
875
1183
  assert.match(assembled.sections[0].text, /<done>/);
876
1184
  assert.equal(assembled.sections[0].text.includes('/tmp/'), false);
1185
+ const round12Outline = terminalSchemaOutline(OK_BODY_SCHEMA);
1186
+ assert.ok(assembled.sections[0].text.includes(`matching exactly ${round12Outline}`), 'system section projects the terminal body schema outline');
877
1187
  rootListeners.get('session/event')[0](session, turnEnd());
878
1188
  assert.deepEqual(runtimeWrites, []);
879
1189
  rootListeners.get('session/event')[0](session, turnStart(2));
@@ -1206,7 +1516,7 @@ try {
1206
1516
  assert.equal(parseBenchmarkChildDiagnostic(boundedControl.toString('utf8')), 'child_bridge_timed_out', 'rolling tail preserves a final structured reason after bounded noise');
1207
1517
  const configPath = join(root, 'bridge.json');
1208
1518
  writeFileSync(configPath, JSON.stringify({
1209
- schema_version: 'gotry_benchmark_environment_bridge_v3',
1519
+ schema_version: 'gotry_benchmark_environment_bridge_v4',
1210
1520
  enabled: true,
1211
1521
  executable: process.execPath,
1212
1522
  cwd: root,
@@ -1268,7 +1578,8 @@ try {
1268
1578
  max_output_bytes: 4_096,
1269
1579
  terminal_output: {
1270
1580
  tag: 'done',
1271
- max_bytes: 4_096
1581
+ max_bytes: 4_096,
1582
+ body_schema: OK_BODY_SCHEMA
1272
1583
  },
1273
1584
  isolation: {
1274
1585
  mode: 'host-enforced',
@@ -1891,29 +2202,28 @@ try {
1891
2202
  const bridge = registered.find((tool)=>tool.name === 'gotry_benchmark_environment');
1892
2203
  assert.ok(bridge.execute, 'registered bridge exposes execute');
1893
2204
  const parameters = bridge.parameters;
1894
- assert.equal(Array.isArray(parameters.oneOf), true, 'v3 bridge exposes flat descriptor-derived model branches');
1895
- const lookupBranch = parameters.oneOf.find((branch)=>branch?.properties?.tool?.const === 'lookup');
1896
- assert.ok(lookupBranch, 'model schema contains the configured lookup tool branch');
1897
- assert.equal(lookupBranch.description, 'Lookup one declared city.', 'descriptor description reaches the model-visible call branch');
1898
- assert.deepEqual(lookupBranch.required, [
1899
- 'action',
1900
- 'tool',
1901
- 'arguments'
1902
- ]);
1903
- assert.equal(lookupBranch.additionalProperties, false);
1904
- assert.deepEqual(lookupBranch.properties.arguments.required, [
1905
- 'city'
1906
- ]);
1907
- assert.equal(lookupBranch.properties.arguments.additionalProperties, false);
1908
- assert.equal(lookupBranch.properties.arguments.properties.city.type, 'string');
1909
- assert.deepEqual(lookupBranch.properties.arguments.properties.city.enum, [
1910
- 'Dubai',
1911
- 'Abu Dhabi'
2205
+ assert.equal(parameters.type, 'object', 'flat bridge uses an object root for model-facing parameters');
2206
+ assert.equal(parameters.oneOf, undefined, 'model-facing wire has no top-level oneOf');
2207
+ assert.deepEqual(parameters.required, [
2208
+ 'action'
2209
+ ], 'action is the only universally required wire field');
2210
+ assert.equal(parameters.additionalProperties, false);
2211
+ assert.deepEqual(parameters.properties?.action?.enum, [
2212
+ 'tools',
2213
+ 'call',
2214
+ 'errors'
1912
2215
  ]);
2216
+ assert.deepEqual(parameters.properties?.tool?.enum, [
2217
+ 'lookup',
2218
+ 'constructor',
2219
+ 'toString'
2220
+ ], 'tool enum exposes frozen descriptor names');
2221
+ assert.equal(parameters.properties?.arguments?.type, 'object', 'arguments remains a generic object on the open wire');
2222
+ assert.equal(parameters.properties?.arguments?.additionalProperties, true);
1913
2223
  assert.equal(Object.isFrozen(bridge.parameters), true, 'registered raw parameters are frozen');
1914
- assert.equal(Object.isFrozen(lookupBranch.properties.arguments.properties.city.enum), true, 'descriptor-derived nested schema is frozen');
2224
+ assert.equal(Object.isFrozen(parameters.properties?.tool?.enum), true, 'descriptor-derived tool enum is frozen');
1915
2225
  assert.throws(()=>{
1916
- lookupBranch.properties.arguments.properties.city.enum.push('escape');
2226
+ parameters.properties?.tool?.enum.push('escape');
1917
2227
  }, TypeError);
1918
2228
  const beforeProtocolRejected = spawnSpecs.length;
1919
2229
  await assert.rejects(()=>bridge.execute({
@@ -1926,15 +2236,54 @@ try {
1926
2236
  action: 'tools',
1927
2237
  tool: 'lookup'
1928
2238
  }, null), /invalid arguments/, 'mixed action/tool fields are rejected by the flat protocol');
2239
+ await assert.rejects(()=>bridge.execute({
2240
+ action: 'tools',
2241
+ arguments: {}
2242
+ }, null), /invalid arguments/, 'tools rejects mixed arguments');
2243
+ await assert.rejects(()=>bridge.execute({
2244
+ action: 'errors',
2245
+ tool: 'lookup'
2246
+ }, null), /invalid arguments/, 'errors rejects mixed tool fields');
2247
+ await assert.rejects(()=>bridge.execute({
2248
+ action: 'errors',
2249
+ arguments: {}
2250
+ }, null), /invalid arguments/, 'errors rejects mixed arguments');
1929
2251
  await assert.rejects(()=>bridge.execute({
1930
2252
  ...bridgeCall('lookup', {
1931
2253
  city: 'Dubai'
1932
2254
  }),
1933
2255
  extra: true
1934
2256
  }, null), /invalid arguments/, 'extra top-level fields are rejected by the flat protocol');
2257
+ await assert.rejects(()=>bridge.execute({
2258
+ action: 'call'
2259
+ }, null), /invalid arguments/, 'call without tool or arguments is rejected');
2260
+ await assert.rejects(()=>bridge.execute({
2261
+ action: 'call',
2262
+ tool: 'lookup'
2263
+ }, null), /invalid arguments/, 'call without arguments is rejected');
2264
+ await assert.rejects(()=>bridge.execute({
2265
+ action: 'call',
2266
+ arguments: {
2267
+ city: 'Dubai'
2268
+ }
2269
+ }, null), /invalid arguments/, 'call without tool is rejected');
1935
2270
  await assert.rejects(()=>bridge.execute(bridgeCall('lookup', {
1936
2271
  city: 'Sharjah'
1937
2272
  }), null), /invalid arguments/, 'invalid descriptor argument values are rejected by the flat protocol');
2273
+ await assert.rejects(()=>bridge.execute({
2274
+ action: 'call',
2275
+ tool: 'lookup',
2276
+ arguments: {}
2277
+ }, null), /invalid arguments/, 'missing required descriptor arguments are rejected by the flat protocol');
2278
+ for (const tool of [
2279
+ 'lookup',
2280
+ 'constructor',
2281
+ 'toString'
2282
+ ]){
2283
+ await assert.rejects(()=>bridge.execute(bridgeCall(tool, {
2284
+ city: 'Sharjah'
2285
+ }), null), /invalid arguments/, `${tool} rejects invalid descriptor arguments`);
2286
+ }
1938
2287
  assert.equal(spawnSpecs.length, beforeProtocolRejected, 'flat protocol rejections happen before spawn');
1939
2288
  const args = {
1940
2289
  city: 'Dubai',
@@ -2297,6 +2646,64 @@ try {
2297
2646
  }
2298
2647
  assert.equal(errorsTable.errors?.invalid_arguments?.recoverable, true, 'invalid caller arguments are recoverable');
2299
2648
  assert.equal(errorsTable.errors?.forbidden_output?.recoverable, false, 'policy-boundary output is not recoverable');
2649
+ assert.deepEqual(errorsTable, {
2650
+ ok: true,
2651
+ errors: BRIDGE_ERROR_CONTRACT
2652
+ }, 'errors action returns the exact closed failure contract');
2653
+ const toolsTable = await bridge.execute({
2654
+ action: 'tools'
2655
+ }, null);
2656
+ assert.deepEqual(toolsTable, {
2657
+ ok: true,
2658
+ tools: [
2659
+ {
2660
+ name: 'lookup',
2661
+ description: 'Lookup one declared city.',
2662
+ input_schema: lookupInputSchema(),
2663
+ output_keys: [
2664
+ 'city',
2665
+ 'nested'
2666
+ ],
2667
+ domain_outcomes: [
2668
+ {
2669
+ status: 'miss',
2670
+ code: 'NOT_FOUND',
2671
+ recovery: 'revise_arguments'
2672
+ },
2673
+ {
2674
+ status: 'error',
2675
+ code: 'AMBIGUOUS',
2676
+ recovery: 'choose_alternative'
2677
+ }
2678
+ ]
2679
+ },
2680
+ {
2681
+ name: 'constructor',
2682
+ description: 'Construct one declared city.',
2683
+ input_schema: lookupInputSchema(),
2684
+ output_keys: [
2685
+ 'legacy'
2686
+ ],
2687
+ domain_outcomes: [
2688
+ {
2689
+ status: 'error',
2690
+ code: 'INVALID',
2691
+ recovery: 'retry_same'
2692
+ }
2693
+ ]
2694
+ },
2695
+ {
2696
+ name: 'toString',
2697
+ description: 'Stringify one declared city.',
2698
+ input_schema: lookupInputSchema(),
2699
+ output_keys: [
2700
+ 'city',
2701
+ 'nested'
2702
+ ],
2703
+ domain_outcomes: []
2704
+ }
2705
+ ]
2706
+ }, 'tools action returns every exact frozen descriptor, including its input schema');
2300
2707
  for (const stdout of [
2301
2708
  'not-json',
2302
2709
  '{"result":1}{"result":2}'
@@ -2669,7 +3076,7 @@ try {
2669
3076
  else process.env.GOTRY_LLM_MODEL = originalModelOverride;
2670
3077
  }
2671
3078
  const validConfig = {
2672
- schema_version: 'gotry_benchmark_environment_bridge_v3',
3079
+ schema_version: 'gotry_benchmark_environment_bridge_v4',
2673
3080
  enabled: true,
2674
3081
  executable: process.execPath,
2675
3082
  cwd: root,
@@ -2716,7 +3123,8 @@ try {
2716
3123
  max_output_bytes: 4_096,
2717
3124
  terminal_output: {
2718
3125
  tag: 'done',
2719
- max_bytes: 4_096
3126
+ max_bytes: 4_096,
3127
+ body_schema: OK_BODY_SCHEMA
2720
3128
  },
2721
3129
  isolation: {
2722
3130
  mode: 'host-enforced',
@@ -2891,6 +3299,20 @@ try {
2891
3299
  ...validConfig,
2892
3300
  schema_version: 'gotry_benchmark_environment_bridge_v1'
2893
3301
  }), /benchmark environment bridge configuration unavailable/, 'v1 config cannot silently omit the Round 3 terminal semantics');
3302
+ assert.throws(()=>bridgeRegistrationFor({
3303
+ ...validConfig,
3304
+ schema_version: 'gotry_benchmark_environment_bridge_v3'
3305
+ }), /benchmark environment bridge configuration unavailable/, 'v3 config cannot silently omit the Round 12 terminal body schema (#215)');
3306
+ assert.throws(()=>bridgeRegistrationFor({
3307
+ ...validConfig,
3308
+ terminal_output: {
3309
+ ...validConfig.terminal_output,
3310
+ body_schema: {
3311
+ ...validConfig.terminal_output.body_schema,
3312
+ additionalProperties: true
3313
+ }
3314
+ }
3315
+ }), /benchmark environment bridge configuration unavailable/, 'non-closed terminal body schema fails the config load closed');
2894
3316
  assert.throws(()=>bridgeRegistrationFor({
2895
3317
  ...validConfig,
2896
3318
  tools: [