Files
msd-core/tests/handshake-serialized.test.cjs
Tom Boucher 47ecf997d6 feat(#1683): serialized capability-exchange handshake — Phase 6 Slice 1 (#1937)
* feat(#1683): serialized capability-exchange handshake — Phase 6 Slice 1

The out-of-process wire form of Phase 1's negotiateHostCapabilities: SDK hosts
(pi, VS Code) that cannot share object refs exchange a JSON capability set over
a wire boundary (MCP-style initialize). src/handshake-serialized.cts provides
buildHandshakeRequest (host side) + handleHandshakeRequest (engine side,
delegates to negotiateHostCapabilities), both JSON-round-trip-safe.

CONSISTENCY is the contract: a serialized request yields the same
NegotiationResult as the in-process call for the same axes (asserted in the
test). Pure + additive; the companion MCP server or an SDK host binds it to a
real transport.

* fix(#1683): ignore tsc-emitted handshake-serialized.cjs (ADR-457) + refresh inventory manifest

* fix(#1683): correct inventory manifest — drop junk smart-entry.cjs, add handshake-serialized.cjs

The local gen-inventory-manifest scan captured a stale untracked smart-entry.cjs
(not on next, no src/*.cts) and missed the freshly-built handshake-serialized.cjs.
Aligned to the canonical clean-build report: + handshake-serialized.cjs, - smart-entry.cjs.

* fix(#1683): type the JSON wire round-trips (no-unsafe-return)
2026-07-02 18:08:42 -04:00

78 lines
3.2 KiB
JavaScript

'use strict';
/**
* handshake-serialized.test.cjs — ADR-1239 Phase E / #1683 Slice 1.
*
* The wire-form capability-exchange handshake MUST be consistent with the
* in-process negotiateHostCapabilities: a request built + serialized here yields
* the same NegotiationResult the in-process call produces for the same axes.
* That consistency is what makes the out-of-process SDK handshake safe.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const {
negotiateHostCapabilities,
PROTOCOL_VERSION,
} = require('../gsd-core/bin/lib/host-integration.cjs');
const {
buildHandshakeRequest,
handleHandshakeRequest,
HANDSHAKE_METHOD,
} = require('../gsd-core/bin/lib/handshake-serialized.cjs');
test('HANDSHAKE_METHOD is a non-empty initialize-style wire method name', () => {
assert.equal(typeof HANDSHAKE_METHOD, 'string');
assert.ok(HANDSHAKE_METHOD.length > 0);
});
test('buildHandshakeRequest: bare-axes form → {protocolVersion, axes}, JSON-safe', () => {
const req = buildHandshakeRequest({ embeddingMode: 'declarative', commandSurface: 'slash-file' });
assert.equal(req.protocolVersion, PROTOCOL_VERSION);
assert.equal(req.axes.embeddingMode, 'declarative');
assert.deepEqual(req, JSON.parse(JSON.stringify(req)));
});
test('buildHandshakeRequest: {protocolVersion, axes} form honored', () => {
const req = buildHandshakeRequest({ protocolVersion: 999, axes: { embeddingMode: 'imperative' } });
assert.equal(req.protocolVersion, 999);
assert.equal(req.axes.embeddingMode, 'imperative');
});
test('buildHandshakeRequest throws on non-object descriptor', () => {
assert.throws(() => buildHandshakeRequest(null), /host descriptor/);
assert.throws(() => buildHandshakeRequest('x'), /host descriptor/);
});
test('handleHandshakeRequest is CONSISTENT with in-process negotiateHostCapabilities', () => {
// A full declarative-cli descriptor.
const axes = {
embeddingMode: 'declarative', commandSurface: 'slash-file', modelMode: 'passive',
hookBus: 'host', stateIO: 'filesystem', transport: 'mcp', runtime: 'node',
};
const inProcess = negotiateHostCapabilities(axes);
const wire = handleHandshakeRequest(buildHandshakeRequest(axes));
// The wire result MUST equal the in-process result for the same axes.
assert.deepEqual(wire.effective, inProcess.effective);
assert.deepEqual(wire.protocolVersion, inProcess.protocolVersion);
assert.deepEqual(wire.points, inProcess.points);
});
test('handleHandshakeRequest returns a strictly JSON-serializable NegotiationResult', () => {
const wire = handleHandshakeRequest({ protocolVersion: 1, axes: { embeddingMode: 'declarative' } });
assert.deepEqual(wire, JSON.parse(JSON.stringify(wire)));
});
test('full round-trip: host builds → engine handles → negotiated IDE profile', () => {
const req = buildHandshakeRequest({ embeddingMode: 'imperative', runtime: 'sandboxed-web' });
const result = handleHandshakeRequest(req);
assert.ok(result.effective && typeof result.effective === 'object');
assert.ok(result.points && typeof result.points === 'object');
assert.ok(Array.isArray(result.warnings));
});
test('handleHandshakeRequest throws on non-object request', () => {
assert.throws(() => handleHandshakeRequest(null), /request/);
});