[grade=B] fix(DC-162 rev3): presence-based id detection, null-params rejection, boundary matrix

Judge r2 (grade C) — all 3 blockers + both polish items:

1. REQUEST-vs-NOTIFICATION by PRESENCE, never truthiness: validateJsonRpc
   uses hasOwnProperty('id'); id 0 and id '' are VALID request ids echoed
   back BY VALUE (strict-equality tested), id:null treated as a request
   under the documented error-id convention. mcp-core default branch fixed
   the same way (`if (id)` → hasOwnProperty — id 0 / '' unknown-method now
   answer -32601 with the id echoed instead of being swallowed as falsy).
   Routes' malformed-notification silence path also switched to
   presence-based detection (id:null + bad params → 400 envelope, not 204).

2. params:null REJECTED (-32600): contract is object-or-omitted ONLY
   (strict JSON-RPC; the old code's m.params !== null guard contradicted
   the documented contract). Boundary tests pin the policy.

3. Unknown-method boundary tests: id 0 → -32601 id:0, id '' → -32601 id:'',
   id null → -32601 id:null — asserted at BOTH layer levels (route).

Polish:
- 16-case TABLE-DRIVEN boundary matrix covering every allowed/rejected
  id/params combination incl. 202 vs 204 notification split.
- Verifier now probes writable /tmp up front (exit 73 + clear message if
  unavailable; documented requirement) instead of dying at mktemp.
- Verifier target count 57 → 73 (DC-162 suites) with the boundary table
  named in the assertion label.

Full DC-162 suite: 73 passed (route 50 + core 23). No transport/credential
semantics changed from rev2.

urn:ump:or2qqhcytp6lfzlyi7xru7hdqy3uar2hbjllsrrbxrbray3c5kpa
This commit is contained in:
Hermes committed 2026-10-08 04:02:26 -07:00
1 parent c9015b59ad
commit 73fde1e2a8
3 files changed
+74 -19

No files matched your search

@@ -273,6 +273,42 @@ describe('DC-162 rev2 mcp routes (Streamable HTTP)', () => {
expect(r.json.error.code).toBe(-32601);
} finally { server.close(); }
});
// Judge r2 blockers #1/#2/#3 as ONE table-driven boundary matrix
// (also r2 polish): id/params presence, falsy ids, null policy.
const BOUNDARY = [
// [name, request, expect: {status, errCode?, id?, notification?}]
['id 0: valid request id, echoed', { jsonrpc: '2.0', id: 0, method: 'ping' }, { status: 200, id: 0 }],
['id empty string: valid request id, echoed', { jsonrpc: '2.0', id: '', method: 'ping' }, { status: 200, id: '' }],
['id null: request under our policy, error id null echoed', { jsonrpc: '2.0', id: null, method: 'ping' }, { status: 200, id: null }],
['id null with unknown method: -32601 id null', { jsonrpc: '2.0', id: null, method: 'bogus/x' }, { status: 200, errCode: -32601, id: null }],
['id 0 with unknown method: -32601 echoes 0, never swallowed', { jsonrpc: '2.0', id: 0, method: 'bogus/x' }, { status: 200, errCode: -32601, id: 0 }],
['id empty string with unknown method: -32601 echoes ""', { jsonrpc: '2.0', id: '', method: 'bogus/x' }, { status: 200, errCode: -32601, id: '' }],
['params null rejected: -32600 (object-or-omitted only)', { jsonrpc: '2.0', id: 3, method: 'ping', params: null }, { status: 400, errCode: -32600 }],
['params omitted: accepted', { jsonrpc: '2.0', id: 4, method: 'ping' }, { status: 200 }],
['params empty object: accepted', { jsonrpc: '2.0', id: 5, method: 'ping', params: {} }, { status: 200 }],
['params array rejected', { jsonrpc: '2.0', id: 6, method: 'ping', params: [] }, { status: 400, errCode: -32600 }],
['params string rejected', { jsonrpc: '2.0', id: 7, method: 'ping', params: 'x' }, { status: 400, errCode: -32600 }],
['id boolean rejected: -32600 id null', { jsonrpc: '2.0', id: true, method: 'ping' }, { status: 400, errCode: -32600, id: null }],
['id object rejected', { jsonrpc: '2.0', id: { a: 1 }, method: 'ping' }, { status: 400, errCode: -32600 }],
['no id (absent): notification → 202', { jsonrpc: '2.0', method: 'notifications/x' }, { status: 202, notification: true }],
['no id + broken params: malformed notification → 204 silent', { jsonrpc: '2.0', method: 'notifications/x', params: 'oops' }, { status: 204, notification: true }],
['no id + params null: malformed notification → 204 silent', { jsonrpc: '2.0', method: 'notifications/x', params: null }, { status: 204, notification: true }],
];
for (const [name, request, exp] of BOUNDARY) {
test(`boundary: ${name}`, async () => {
const { app } = buildRouter({ auth: SESSION });
const { server, base } = await start(app);
try {
const r = await postJson(base, request);
expect(r.status).toBe(exp.status);
if (exp.errCode !== undefined) expect(r.json.error.code).toBe(exp.errCode);
// strict equality on id: falsy ids (0, '') must echo by VALUE
if ('id' in exp) expect(r.json.id).toBe(exp.id);
if (exp.notification) expect(r.text).toBe('');
} finally { server.close(); }
});
}
});
describe('notifications (no id) — spec 202 Accepted', () => {