* test(#3673): add failing tests for dispatch.maxConcurrency axis and dispatch-capacity CLI route Extends tests/host-integration.test.cjs with negotiateHostCapabilities maxConcurrency negotiation coverage (test matrix rows 1-15, including a fast-check property test) and a new #3673 dispatch-capacity CLI route describe block spawning the real gsd-tools.cjs (rows 16-25). Extends tests/host-integration-validator-parity.test.cjs with an all-19-descriptor maxConcurrency presence/validity sweep (row 26) and adds a hostile-input validator test to host-integration.test.cjs (row 27). The dispatch.maxConcurrency field does not exist yet, so these tests fail. * feat(#3673): add dispatch.maxConcurrency axis, negotiation, validator parity, and the dispatch-capacity query Adds a numeric dispatch.maxConcurrency sub-field to the Host-Integration Interface (ADR-1239 Phase 1), following the existing dispatch.isolation sub-field pattern: DispatchCapability interface, SAFE_DEFAULTS/PROFILE_BASELINES floors, and a negotiateHostCapabilities branch that passes through a positive safe integer and fails closed to 1 otherwise (no engine-side reduction, per the design doc's explicit rejection of a min(host,engine) rule). capability-validator.cjs gains parity validation for the new field (optional, positive safe integer or the "undocumented" sentinel — mirroring isolation's "added after existing descriptors" treatment). gsd-tools.cjs gains a new `query dispatch-capacity` route, a pure-read sibling of `dispatch-isolation` with no side effects: live env (GSD_DISPATCH_MAX_CONCURRENCY) > descriptor > fallback-to-1 precedence. All 19 capabilities/*/capability.json descriptors now declare dispatch.maxConcurrency: claude carries the one cited value (20, per code.claude.com/docs/en/sub-agents); the other 18 carry "undocumented" (not yet researched for this axis). * docs(#3673): document dispatch.maxConcurrency and add its citation row to the capability matrix Updates docs/reference/host-integration-interface.md's dispatch struct entry (also backfilling the previously-undocumented isolation/backgroundDispatch sub-fields found stale in the same table) and adds fail-closed/live-transport precedence prose for the new maxConcurrency field. Adds a dispatch.maxConcurrency row (with citation) to all 19 host sections in docs/reference/host-integration-capability-matrix.md — required for tests/host-integration-descriptors.test.cjs's kimi-code matrix-parity check, which asserts every declared dispatch sub-axis is documented there. Updates docs/how-to/add-or-update-a-host-integration.md's dispatch checklist and example descriptor block to mention maxConcurrency (and, likewise backfilling a stale gap, isolation/backgroundDispatch). * fix(#3673): extract shared maxConcurrency validator, drop dead reserved-name check Exports isPositiveSafeInteger from src/host-integration.cts as the single source of truth for the dispatch.maxConcurrency positive-safe-integer contract; negotiateHostCapabilities and gsd-tools.cjs's routeDispatchCapacity now both call it instead of independently reimplementing the same predicate. Also removes the __proto__/constructor/prototype reserved-name branch from capability-validator.cjs's maxConcurrency check — copy-pasted from the string-enum fields above it, but unreachable for a numeric field (the generic positive-safe-integer branch already rejects any string) and absent from maxDepth, the field the code's own comment claims to mirror. --------- Co-authored-by: sim <sim@local>
480 lines
19 KiB
JavaScript
480 lines
19 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* ADR-1239 Phase A: Parity guard — validator VALID_* sets MUST exactly match
|
|
* HOST_INTEGRATION_AXES arrays from host-integration.cjs.
|
|
*
|
|
* If either side drifts, this test fails immediately (not silently).
|
|
*/
|
|
|
|
const { describe, test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
|
|
const {
|
|
HOST_INTEGRATION_AXES,
|
|
} = require(path.join(__dirname, '../gsd-core/bin/lib/host-integration.cjs'));
|
|
|
|
const {
|
|
_HOST_INTEGRATION_VOCAB,
|
|
validateCapability,
|
|
} = require(path.join(__dirname, '../gsd-core/bin/lib/capability-validator.cjs'));
|
|
|
|
// Sort for deterministic comparison
|
|
function sorted(arr) {
|
|
return [...arr].sort();
|
|
}
|
|
|
|
// Minimal valid runtime capability descriptor (all documented values)
|
|
function makeMinimalRuntimeCap(overrides = {}) {
|
|
return {
|
|
id: 'test-runtime',
|
|
role: 'runtime',
|
|
title: 'Test Runtime',
|
|
description: 'Test runtime capability for parity tests',
|
|
tier: 'core',
|
|
requires: [],
|
|
version: '1.0.0',
|
|
runtime: {
|
|
configHome: {
|
|
kind: 'dot-home',
|
|
name: '.test-runtime',
|
|
env: [],
|
|
},
|
|
configFormat: 'markdown',
|
|
artifactLayout: {
|
|
global: [],
|
|
local: [],
|
|
},
|
|
commandStyle: 'slash-hyphen',
|
|
hooksSurface: 'none',
|
|
sandboxTier: 'none',
|
|
supportTier: 1,
|
|
installSurface: 'profile-marker-only',
|
|
localConfigDir: '.test-runtime',
|
|
writesSharedSettings: false,
|
|
permissionWriter: null,
|
|
extendedHookEvents: [],
|
|
hostIntegration: {
|
|
embeddingMode: 'imperative',
|
|
commandSurface: 'slash-file',
|
|
modelMode: 'passive',
|
|
hookBus: 'host',
|
|
stateIO: 'filesystem',
|
|
transport: 'mcp',
|
|
runtime: 'node',
|
|
dispatch: {
|
|
namedDispatch: true,
|
|
nested: false,
|
|
maxDepth: 1,
|
|
background: false,
|
|
subagentToolkit: 'full',
|
|
backgroundDispatch: false,
|
|
},
|
|
},
|
|
...overrides,
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('ADR-1239 Phase A: host-integration validator parity', () => {
|
|
test('_HOST_INTEGRATION_VOCAB is exported from capability-validator.cjs', () => {
|
|
assert.ok(
|
|
_HOST_INTEGRATION_VOCAB !== undefined && _HOST_INTEGRATION_VOCAB !== null,
|
|
'_HOST_INTEGRATION_VOCAB must be exported from capability-validator.cjs',
|
|
);
|
|
assert.strictEqual(typeof _HOST_INTEGRATION_VOCAB, 'object');
|
|
});
|
|
|
|
test('embeddingMode: validator set === HOST_INTEGRATION_AXES.embeddingMode', () => {
|
|
assert.deepEqual(
|
|
sorted(_HOST_INTEGRATION_VOCAB.embeddingMode),
|
|
sorted(HOST_INTEGRATION_AXES.embeddingMode),
|
|
'validator VALID_EMBEDDING_MODES must exactly match HOST_INTEGRATION_AXES.embeddingMode',
|
|
);
|
|
});
|
|
|
|
test('commandSurface: validator set === HOST_INTEGRATION_AXES.commandSurface', () => {
|
|
assert.deepEqual(
|
|
sorted(_HOST_INTEGRATION_VOCAB.commandSurface),
|
|
sorted(HOST_INTEGRATION_AXES.commandSurface),
|
|
'validator VALID_COMMAND_SURFACES must exactly match HOST_INTEGRATION_AXES.commandSurface',
|
|
);
|
|
});
|
|
|
|
test('modelMode: validator set === HOST_INTEGRATION_AXES.modelMode', () => {
|
|
assert.deepEqual(
|
|
sorted(_HOST_INTEGRATION_VOCAB.modelMode),
|
|
sorted(HOST_INTEGRATION_AXES.modelMode),
|
|
'validator VALID_MODEL_MODES must exactly match HOST_INTEGRATION_AXES.modelMode',
|
|
);
|
|
});
|
|
|
|
test('hookBus: validator set === HOST_INTEGRATION_AXES.hookBus', () => {
|
|
assert.deepEqual(
|
|
sorted(_HOST_INTEGRATION_VOCAB.hookBus),
|
|
sorted(HOST_INTEGRATION_AXES.hookBus),
|
|
'validator VALID_HOOK_BUSES must exactly match HOST_INTEGRATION_AXES.hookBus',
|
|
);
|
|
});
|
|
|
|
test('stateIO: validator set === HOST_INTEGRATION_AXES.stateIO', () => {
|
|
assert.deepEqual(
|
|
sorted(_HOST_INTEGRATION_VOCAB.stateIO),
|
|
sorted(HOST_INTEGRATION_AXES.stateIO),
|
|
'validator VALID_STATE_IO must exactly match HOST_INTEGRATION_AXES.stateIO',
|
|
);
|
|
});
|
|
|
|
test('transport: validator set === HOST_INTEGRATION_AXES.transport', () => {
|
|
assert.deepEqual(
|
|
sorted(_HOST_INTEGRATION_VOCAB.transport),
|
|
sorted(HOST_INTEGRATION_AXES.transport),
|
|
'validator VALID_TRANSPORTS must exactly match HOST_INTEGRATION_AXES.transport',
|
|
);
|
|
});
|
|
|
|
test('runtime (axis): validator set === HOST_INTEGRATION_AXES.runtime (8 documented values)', () => {
|
|
assert.deepEqual(
|
|
sorted(_HOST_INTEGRATION_VOCAB.runtime),
|
|
sorted(HOST_INTEGRATION_AXES.runtime),
|
|
'validator VALID_HOST_RUNTIMES must exactly match HOST_INTEGRATION_AXES.runtime',
|
|
);
|
|
});
|
|
|
|
test('subagentToolkit: validator set === HOST_INTEGRATION_AXES.subagentToolkit', () => {
|
|
assert.deepEqual(
|
|
sorted(_HOST_INTEGRATION_VOCAB.subagentToolkit),
|
|
sorted(HOST_INTEGRATION_AXES.subagentToolkit),
|
|
'validator VALID_SUBAGENT_TOOLKITS must exactly match HOST_INTEGRATION_AXES.subagentToolkit',
|
|
);
|
|
});
|
|
|
|
test('all axis keys in HOST_INTEGRATION_AXES are covered by _HOST_INTEGRATION_VOCAB', () => {
|
|
const axisKeys = Object.keys(HOST_INTEGRATION_AXES).sort();
|
|
const vocabKeys = Object.keys(_HOST_INTEGRATION_VOCAB).sort();
|
|
assert.deepEqual(
|
|
vocabKeys,
|
|
axisKeys,
|
|
'_HOST_INTEGRATION_VOCAB must cover exactly the same axis keys as HOST_INTEGRATION_AXES',
|
|
);
|
|
});
|
|
|
|
test('_HOST_INTEGRATION_VOCAB does NOT include "undocumented" (documented vocab only)', () => {
|
|
for (const [axis, values] of Object.entries(_HOST_INTEGRATION_VOCAB)) {
|
|
assert.ok(
|
|
!values.includes('undocumented'),
|
|
`_HOST_INTEGRATION_VOCAB.${axis} must not include "undocumented" (sentinel is NOT documented vocab)`,
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Behavioral: "undocumented" passes validator; bogus values still fail
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('ADR-1239 validator behavioral: undocumented sentinel passes, bogus fails', () => {
|
|
const SCALAR_AXES = ['embeddingMode', 'commandSurface', 'modelMode', 'hookBus', 'stateIO', 'transport', 'runtime'];
|
|
|
|
for (const axis of SCALAR_AXES) {
|
|
test(`hostIntegration.${axis}:"undocumented" → ZERO validator errors`, () => {
|
|
const cap = makeMinimalRuntimeCap({
|
|
hostIntegration: {
|
|
...makeMinimalRuntimeCap().runtime.hostIntegration,
|
|
[axis]: 'undocumented',
|
|
},
|
|
});
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const hiErrors = errors.filter((e) => e.includes('hostIntegration.' + axis));
|
|
assert.strictEqual(hiErrors.length, 0,
|
|
`"undocumented" for axis "${axis}" must produce no validator errors; got: ${hiErrors.join(', ')}`);
|
|
});
|
|
|
|
test(`hostIntegration.${axis}:"zzz" → produces a validator error`, () => {
|
|
const cap = makeMinimalRuntimeCap({
|
|
hostIntegration: {
|
|
...makeMinimalRuntimeCap().runtime.hostIntegration,
|
|
[axis]: 'zzz',
|
|
},
|
|
});
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const hiErrors = errors.filter((e) => e.includes('hostIntegration.' + axis));
|
|
assert.ok(hiErrors.length > 0,
|
|
`bogus value "zzz" for axis "${axis}" must produce a validator error`);
|
|
});
|
|
}
|
|
|
|
test('dispatch.namedDispatch:"undocumented" → ZERO validator errors for that field', () => {
|
|
const cap = makeMinimalRuntimeCap({
|
|
hostIntegration: {
|
|
...makeMinimalRuntimeCap().runtime.hostIntegration,
|
|
dispatch: {
|
|
namedDispatch: 'undocumented',
|
|
nested: 'undocumented',
|
|
maxDepth: 'undocumented',
|
|
background: 'undocumented',
|
|
subagentToolkit: 'undocumented',
|
|
backgroundDispatch: 'undocumented',
|
|
},
|
|
},
|
|
});
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const dispatchErrors = errors.filter((e) => e.includes('hostIntegration.dispatch'));
|
|
assert.strictEqual(dispatchErrors.length, 0,
|
|
`"undocumented" for all dispatch fields must produce no validator errors; got: ${dispatchErrors.join(', ')}`);
|
|
});
|
|
|
|
test('dispatch boolean fields: true/false still accepted', () => {
|
|
const cap = makeMinimalRuntimeCap();
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const dispatchErrors = errors.filter((e) => e.includes('hostIntegration.dispatch'));
|
|
assert.strictEqual(dispatchErrors.length, 0,
|
|
`Valid boolean dispatch fields must produce no errors; got: ${dispatchErrors.join(', ')}`);
|
|
});
|
|
|
|
// Phase B: backgroundDispatch field validation
|
|
test('dispatch.backgroundDispatch:true → ZERO validator errors', () => {
|
|
const cap = makeMinimalRuntimeCap({
|
|
hostIntegration: {
|
|
...makeMinimalRuntimeCap().runtime.hostIntegration,
|
|
dispatch: { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch, backgroundDispatch: true },
|
|
},
|
|
});
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
|
|
assert.strictEqual(bdErrors.length, 0,
|
|
`backgroundDispatch:true must produce no errors; got: ${bdErrors.join(', ')}`);
|
|
});
|
|
|
|
test('dispatch.backgroundDispatch:false → ZERO validator errors', () => {
|
|
const cap = makeMinimalRuntimeCap({
|
|
hostIntegration: {
|
|
...makeMinimalRuntimeCap().runtime.hostIntegration,
|
|
dispatch: { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch, backgroundDispatch: false },
|
|
},
|
|
});
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
|
|
assert.strictEqual(bdErrors.length, 0,
|
|
`backgroundDispatch:false must produce no errors; got: ${bdErrors.join(', ')}`);
|
|
});
|
|
|
|
test('dispatch.backgroundDispatch:"undocumented" → ZERO validator errors', () => {
|
|
const cap = makeMinimalRuntimeCap({
|
|
hostIntegration: {
|
|
...makeMinimalRuntimeCap().runtime.hostIntegration,
|
|
dispatch: { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch, backgroundDispatch: 'undocumented' },
|
|
},
|
|
});
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
|
|
assert.strictEqual(bdErrors.length, 0,
|
|
`backgroundDispatch:"undocumented" must produce no errors; got: ${bdErrors.join(', ')}`);
|
|
});
|
|
|
|
test('dispatch.backgroundDispatch:"zzz" → produces a validator error', () => {
|
|
const cap = makeMinimalRuntimeCap({
|
|
hostIntegration: {
|
|
...makeMinimalRuntimeCap().runtime.hostIntegration,
|
|
dispatch: { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch, backgroundDispatch: 'zzz' },
|
|
},
|
|
});
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
|
|
assert.ok(bdErrors.length > 0,
|
|
`bogus value "zzz" for backgroundDispatch must produce a validator error`);
|
|
});
|
|
|
|
test('dispatch without backgroundDispatch key → validator error (required field — all 15 descriptors carry it)', () => {
|
|
// backgroundDispatch is now REQUIRED (matches sibling fields namedDispatch/nested/background/subagentToolkit/maxDepth).
|
|
const cap = makeMinimalRuntimeCap({
|
|
hostIntegration: {
|
|
...makeMinimalRuntimeCap().runtime.hostIntegration,
|
|
dispatch: (() => {
|
|
const d = { ...makeMinimalRuntimeCap().runtime.hostIntegration.dispatch };
|
|
delete d.backgroundDispatch;
|
|
return d;
|
|
})(),
|
|
},
|
|
});
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const bdErrors = errors.filter((e) => e.includes('backgroundDispatch'));
|
|
assert.ok(bdErrors.length > 0,
|
|
`Missing backgroundDispatch (required field) must produce a validator error`);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fix 3: validator must reject reserved keys on hostIntegration and dispatch
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('Fix 3: reserved-key guard on hostIntegration and hostIntegration.dispatch', () => {
|
|
// Base valid runtime body (all documented values, claude layout)
|
|
// We build it via JSON.parse to produce an own "__proto__" key that would
|
|
// normally be swallowed by a spread (JSON.parse always produces own props).
|
|
const BASE_RUNTIME_JSON = JSON.stringify({
|
|
id: 'test-runtime',
|
|
role: 'runtime',
|
|
title: 'Test',
|
|
description: 'Test runtime',
|
|
tier: 'core',
|
|
requires: [],
|
|
version: '1.6.0',
|
|
engines: { gsd: '>=1.6.0' },
|
|
runtime: {
|
|
configHome: { kind: 'dot-home', name: '.test', env: [] },
|
|
configFormat: 'settings-json',
|
|
artifactLayout: { global: [], local: [] },
|
|
commandStyle: 'slash-hyphen',
|
|
hooksSurface: 'settings-json',
|
|
hookEvents: 'claude',
|
|
sandboxTier: 'none',
|
|
supportTier: 1,
|
|
installSurface: 'settings-json',
|
|
localConfigDir: '.test-runtime',
|
|
writesSharedSettings: true,
|
|
permissionWriter: null,
|
|
extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact', 'FileChanged'],
|
|
hostIntegration: {
|
|
embeddingMode: 'imperative',
|
|
commandSurface: 'slash-file',
|
|
dispatch: {
|
|
namedDispatch: true,
|
|
nested: true,
|
|
maxDepth: 5,
|
|
background: true,
|
|
subagentToolkit: 'full',
|
|
backgroundDispatch: true,
|
|
},
|
|
modelMode: 'passive',
|
|
hookBus: 'host',
|
|
stateIO: 'filesystem',
|
|
transport: 'mcp',
|
|
runtime: 'node',
|
|
},
|
|
},
|
|
});
|
|
|
|
test('baseline (no reserved keys) → ZERO errors', () => {
|
|
const cap = JSON.parse(BASE_RUNTIME_JSON);
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
assert.strictEqual(errors.length, 0,
|
|
'Baseline with no reserved keys must produce zero errors; got: ' + errors.join(', '));
|
|
});
|
|
|
|
test('hostIntegration with own __proto__ key → error mentioning "reserved key" and "__proto__"', () => {
|
|
// Inject a raw __proto__ key via JSON string manipulation — JSON.parse gives it
|
|
// as an OWN property (unlike { ..., __proto__: ... } which sets prototype chain).
|
|
const json = BASE_RUNTIME_JSON.replace(
|
|
'"hostIntegration":{',
|
|
'"hostIntegration":{"__proto__":{"polluted":true},',
|
|
);
|
|
const cap = JSON.parse(json);
|
|
// Verify the own-key is actually present (our assumption about JSON.parse)
|
|
assert.ok(
|
|
Object.prototype.hasOwnProperty.call(cap.runtime.hostIntegration, '__proto__'),
|
|
'JSON.parse must produce an own __proto__ key on hostIntegration',
|
|
);
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const reservedErrors = errors.filter((e) => e.includes('reserved key') && e.includes('__proto__'));
|
|
assert.ok(reservedErrors.length > 0,
|
|
'Must produce an error mentioning "reserved key" and "__proto__" for hostIntegration; got: ' + errors.join(', '));
|
|
});
|
|
|
|
test('hostIntegration with own "constructor" key → error mentioning "reserved key" and "constructor"', () => {
|
|
const json = BASE_RUNTIME_JSON.replace(
|
|
'"hostIntegration":{',
|
|
'"hostIntegration":{"constructor":"polluted",',
|
|
);
|
|
const cap = JSON.parse(json);
|
|
assert.ok(
|
|
Object.prototype.hasOwnProperty.call(cap.runtime.hostIntegration, 'constructor'),
|
|
'JSON.parse must produce an own constructor key on hostIntegration',
|
|
);
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const reservedErrors = errors.filter((e) => e.includes('reserved key') && e.includes('constructor'));
|
|
assert.ok(reservedErrors.length > 0,
|
|
'Must produce an error for "constructor" reserved key on hostIntegration; got: ' + errors.join(', '));
|
|
});
|
|
|
|
test('hostIntegration.dispatch with own __proto__ key → error mentioning "reserved key" and "__proto__"', () => {
|
|
const json = BASE_RUNTIME_JSON.replace(
|
|
'"dispatch":{',
|
|
'"dispatch":{"__proto__":{"polluted":true},',
|
|
);
|
|
const cap = JSON.parse(json);
|
|
assert.ok(
|
|
Object.prototype.hasOwnProperty.call(cap.runtime.hostIntegration.dispatch, '__proto__'),
|
|
'JSON.parse must produce an own __proto__ key on dispatch',
|
|
);
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const reservedErrors = errors.filter((e) => e.includes('reserved key') && e.includes('__proto__'));
|
|
assert.ok(reservedErrors.length > 0,
|
|
'Must produce an error for "__proto__" reserved key on dispatch; got: ' + errors.join(', '));
|
|
});
|
|
|
|
test('hostIntegration.dispatch with own "prototype" key → error mentioning "reserved key" and "prototype"', () => {
|
|
const json = BASE_RUNTIME_JSON.replace(
|
|
'"dispatch":{',
|
|
'"dispatch":{"prototype":{"polluted":true},',
|
|
);
|
|
const cap = JSON.parse(json);
|
|
assert.ok(
|
|
Object.prototype.hasOwnProperty.call(cap.runtime.hostIntegration.dispatch, 'prototype'),
|
|
'JSON.parse must produce an own prototype key on dispatch',
|
|
);
|
|
const errors = validateCapability(cap, 'test-runtime');
|
|
const reservedErrors = errors.filter((e) => e.includes('reserved key') && e.includes('prototype'));
|
|
assert.ok(reservedErrors.length > 0,
|
|
'Must produce an error for "prototype" reserved key on dispatch; got: ' + errors.join(', '));
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// #3673 — ADR-1239 Phase 1: all 19 shipped descriptors carry
|
|
// dispatch.maxConcurrency (a real sourced integer or the "undocumented"
|
|
// sentinel — never silently absent), and every one validates clean.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('#3673 dispatch.maxConcurrency — all 19 shipped descriptors', () => {
|
|
const registry = require(path.join(__dirname, '../gsd-core/bin/lib/capability-registry.cjs'));
|
|
|
|
test('registry carries exactly 19 runtime descriptors', () => {
|
|
assert.strictEqual(Object.keys(registry.runtimes).length, 19,
|
|
`expected exactly 19 shipped runtimes; got: ${Object.keys(registry.runtimes).sort().join(', ')}`);
|
|
});
|
|
|
|
test('every shipped descriptor declares dispatch.maxConcurrency as a number or "undocumented" — never absent', () => {
|
|
for (const [id, cap] of Object.entries(registry.runtimes)) {
|
|
const mc = cap && cap.runtime && cap.runtime.hostIntegration && cap.runtime.hostIntegration.dispatch
|
|
&& cap.runtime.hostIntegration.dispatch.maxConcurrency;
|
|
assert.ok(mc !== undefined,
|
|
`${id}: runtime.hostIntegration.dispatch.maxConcurrency must be present (number or "undocumented")`);
|
|
assert.ok(typeof mc === 'number' || mc === 'undocumented',
|
|
`${id}: runtime.hostIntegration.dispatch.maxConcurrency must be a number or "undocumented"; got: ${JSON.stringify(mc)}`);
|
|
}
|
|
});
|
|
|
|
test('claude declares the one real sourced value (20)', () => {
|
|
assert.strictEqual(registry.runtimes.claude.runtime.hostIntegration.dispatch.maxConcurrency, 20);
|
|
});
|
|
|
|
test('every other shipped descriptor declares "undocumented" (not researched for this axis)', () => {
|
|
for (const [id, cap] of Object.entries(registry.runtimes)) {
|
|
if (id === 'claude') continue;
|
|
assert.strictEqual(cap.runtime.hostIntegration.dispatch.maxConcurrency, 'undocumented',
|
|
`${id}: expected "undocumented" (only claude has a cited value)`);
|
|
}
|
|
});
|
|
|
|
test('validateCapability accepts all 19 shipped descriptors\' dispatch.maxConcurrency with zero errors', () => {
|
|
for (const [id, cap] of Object.entries(registry.runtimes)) {
|
|
const errors = validateCapability(cap, id);
|
|
const mcErrors = errors.filter((e) => e.includes('maxConcurrency'));
|
|
assert.strictEqual(mcErrors.length, 0,
|
|
`${id}: shipped dispatch.maxConcurrency must validate clean; got: ${JSON.stringify(mcErrors)}`);
|
|
}
|
|
});
|
|
});
|