fix(daemon): report the /rpc backstop through the runtime's injected sinks
Merged
TH
8d ago+410+410+012345678901234567890123456789-59-59-01234567890123456789
Summary
#3505 gave the detached /rpc flow a last-resort .catch. Two things were wrong with it:
- it reported through
process.stderr.writeand a scope-lessemitDiagnostic, so a daemon with an injected error stream got the reason on the wrong stream and dropped the diagnostic; - it normalized the reason while building its answer, and both of the flow's own catches normalize the same way — while evaluating their answer. A reason that cannot be read (a
detailsgetter that throws) fails all three, and that second throw escaped the.catchas the fatalunhandledRejection#3505 existed to prevent.
createDaemonHttpServer now takes onUnownedRequestFailure, and openDaemonServers wires it to the pair every other out-of-request daemon event uses: the runtime's injected stderr plus emitDaemonDiagnostic. The server only answers, normalizes defensively, and an unreadable reason reports as the unknown-error failure.
Possible follow-up, not taken here: parsing the envelope with the kernel's jsonRpcRequestSchema would replace the hand-rolled isRecord check and the as JsonRpcRequest cast. It also changes the answer for a non-scalar id, so it owes a CHANGELOG line.
Validation
http-server-rpc-unowned-failure.test.ts: an unreadable thrown reason answers 500 withid: null, the sink hears it, the next call answers 200. The sink throws on its first event, so a broken sink cannot cost the answer.daemon-runtime-rpc-flow-failure.test.ts: boots the real runtime;daemon_rpc_request_flow_failedreaches the injectedstderranddaemon.log.- Mutation-checked: no
.catch, directprocess.stderr, or no defensive normalize turns both red. - Changed-line coverage 12/12; CRAP, eager-closure, DI-seam and layering guards pass.