Files
msd-core/tests/host-integration-validator-parity.test.cjs
Tom Boucher c0fd2e3f4c feat(#3673): add dispatch.maxConcurrency axis and dispatch-capacity query (#4162)
* 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>
2026-09-01 21:39:12 -04:00

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)}`);
}
});
});