fix(daemon): refuse a non-object /rpc body before the flow reads it
Summary
A /rpc POST whose body parses as JSON null shut the daemon down before the token check: validateRpcRequestEnvelope read jsonrpc off the null root and threw, the flow runs as a detached void handleRpcRequest(...) so the rejection escaped to unhandledRejection, and that handler exits. Any process reaching the port could stop the daemon with one body.
A root that is not one plain object is now refused as the JSON-RPC Invalid Request (-32600, HTTP 400, id: null) before any property read — the convention src/mcp/server.ts already uses. The detached call gains a .catch that logs to the daemon's error stream and answers through the existing failure path without writing after end. That .catch is a defensive branch with no reachable trigger today: the flow's own catches answer everything else. The lifetime owner and its finally are unchanged; -32602 reuses INVALID_PARAMS. Releasing the request lifetime once was rejected — the disconnect tests need the early release.
Closes #3453
Validation
Commit 635201301 (rebased; #3502 retired the dead approval rows).
http-server-rpc-invalid-body.test.tsis the regression proof: reverting the guard fails both tests —-32600times out unanswered, keeps-serving reportsthe daemon never answered the request, and the escapingTypeErrorreaches the test's rejection listener.http-server-rpc-handler-failure.test.tscovers existing behavior (green onmain), pinning the invariant the defensive branch protects.check:coverage-changedPASS 9/12 (70% threshold);check:coverage-crapnone ≥ 30;eager-closure-budgets838 passed, no new modules.- format/lint/typecheck clean;
check:affected --runpassed; wire-compat unchanged.