Category: bug Severity: major
Location: lib/arcp/runtime/session_actor.rb:190-231
Spec: ARCP v1.1 §12, §5
What
dispatch rescues only Arcp::Error. Every per-message decoder uses Hash#fetch and raises a plain KeyError on a missing required field (e.g. Job::Submit.from_h → h.fetch('agent'), Job::Cancel, Job::Subscribe, Session::Ack, etc.). KeyError is not an Arcp::Error, so it escapes dispatch, propagates out of loop_inbound (which has no rescue), unwinds the actor's run block, and close_session tears down the whole connection. Likewise, malformed envelopes (Envelope.from_h raising InvalidRequest for a bad arcp version / trace_id) are raised inside loop_inbound at @transport.receive, outside dispatch's rescue, and also kill the session.
Spec §12 defines INVALID_REQUEST as "Malformed envelope or schema violation" — a per-message error, not grounds for terminating an authenticated session. A single malformed frame from any peer drops all in-flight observation for that session.
Evidence
# lib/arcp/runtime/session_actor.rb:190-197 — receive/decode errors are unguarded here
def loop_inbound(_parent)
loop do
env = @transport.receive # Envelope.from_json may raise InvalidRequest
break if env.nil?
dispatch(env)
end
end
# lib/arcp/runtime/session_actor.rb:229-231 — only Arcp::Error is caught; KeyError escapes
rescue Arcp::Error => e
reply_error(env, e)
end
Proposed fix
(1) In message decoders, convert missing/invalid fields into Arcp::Errors::InvalidRequest (or rescue KeyError/TypeError and re-raise as InvalidRequest) so dispatch's rescue produces a proper error reply. (2) Broaden dispatch's rescue to also map non-Arcp::Error StandardError to an INTERNAL_ERROR/INVALID_REQUEST reply rather than letting it escape. (3) Wrap the @transport.receive decode in loop_inbound so a malformed envelope yields a session.error and continues the loop instead of closing. Add integration tests submitting a payload missing agent and an envelope with a bad arcp version, asserting the session survives and returns INVALID_REQUEST.
Acceptance criteria
Category: bug Severity: major
Location:
lib/arcp/runtime/session_actor.rb:190-231Spec: ARCP v1.1 §12, §5
What
dispatchrescues onlyArcp::Error. Every per-message decoder usesHash#fetchand raises a plainKeyErroron a missing required field (e.g.Job::Submit.from_h→h.fetch('agent'),Job::Cancel,Job::Subscribe,Session::Ack, etc.).KeyErroris not anArcp::Error, so it escapesdispatch, propagates out ofloop_inbound(which has no rescue), unwinds the actor'srunblock, andclose_sessiontears down the whole connection. Likewise, malformed envelopes (Envelope.from_hraisingInvalidRequestfor a badarcpversion /trace_id) are raised insideloop_inboundat@transport.receive, outsidedispatch's rescue, and also kill the session.Spec §12 defines
INVALID_REQUESTas "Malformed envelope or schema violation" — a per-message error, not grounds for terminating an authenticated session. A single malformed frame from any peer drops all in-flight observation for that session.Evidence
Proposed fix
(1) In message decoders, convert missing/invalid fields into
Arcp::Errors::InvalidRequest(or rescueKeyError/TypeErrorand re-raise asInvalidRequest) sodispatch's rescue produces a proper error reply. (2) Broadendispatch's rescue to also map non-Arcp::ErrorStandardErrorto anINTERNAL_ERROR/INVALID_REQUESTreply rather than letting it escape. (3) Wrap the@transport.receivedecode inloop_inboundso a malformed envelope yields asession.errorand continues the loop instead of closing. Add integration tests submitting a payload missingagentand an envelope with a badarcpversion, asserting the session survives and returnsINVALID_REQUEST.Acceptance criteria
job.submitmissingagentyields aJOB_ERROR/SESSION_ERRORwith codeINVALID_REQUESTand the session stays open.arcp/trace_id) yieldsINVALID_REQUESTwithout closing the connection.