negotiate adds every extension the client advertised but the runtime did not to the unsupported list at lib/src/main/kotlin/dev/arcp/runtime/CapabilityNegotiation.kt:30 (unsupported += proposed.extensions - supported.extensions.toSet()). ARCPRuntime.handleHandshake then routes any non-empty unsupported set into rejectUnsupported at lib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:544, which sends SessionRejected with UNIMPLEMENTED and tears the transport down at lib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:149. The effect is that a client that advertises any arcpx.* extension the runtime does not recognize cannot connect at all — including the optional vendor extensions whose entire point per RFC §21 is graceful degradation. The boolean-feature path at lib/src/main/kotlin/dev/arcp/runtime/CapabilityNegotiation.kt:97 correctly only marks features that the client requested but the runtime cannot satisfy; the extensions path mirrors that asymmetry incorrectly because extensions are optional by spec unless marked otherwise.
Fix prompt: In negotiate, stop appending unsupported extensions to the rejection list. Compute the negotiated extension set as the intersection (already done in negotiateExtensions) and silently drop extensions either side does not support. If a client wants to require an extension, it must request a corresponding boolean capability or send the extension's required messages, which will hit the existing classifyUnknown/Nack path. Add a negotiate unit test in lib/src/test/kotlin/dev/arcp/runtime/ (no such file exists yet) that asserts a client advertising arcpx.unknown.example.v1 against a runtime with no extensions yields a Capabilities.extensions == [] negotiated result and an unsupported.isEmpty(), and a separate test that confirms a streaming = true client against a runtime with streaming = false still appears in unsupported.
negotiateadds every extension the client advertised but the runtime did not to theunsupportedlist atlib/src/main/kotlin/dev/arcp/runtime/CapabilityNegotiation.kt:30(unsupported += proposed.extensions - supported.extensions.toSet()).ARCPRuntime.handleHandshakethen routes any non-emptyunsupportedset intorejectUnsupportedatlib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:544, which sendsSessionRejectedwithUNIMPLEMENTEDand tears the transport down atlib/src/main/kotlin/dev/arcp/runtime/ARCPRuntime.kt:149. The effect is that a client that advertises anyarcpx.*extension the runtime does not recognize cannot connect at all — including the optional vendor extensions whose entire point per RFC §21 is graceful degradation. The boolean-feature path atlib/src/main/kotlin/dev/arcp/runtime/CapabilityNegotiation.kt:97correctly only marks features that the client requested but the runtime cannot satisfy; the extensions path mirrors that asymmetry incorrectly because extensions are optional by spec unless marked otherwise.Fix prompt: In
negotiate, stop appending unsupported extensions to the rejection list. Compute the negotiated extension set as the intersection (already done innegotiateExtensions) and silently drop extensions either side does not support. If a client wants to require an extension, it must request a corresponding boolean capability or send the extension's required messages, which will hit the existingclassifyUnknown/Nackpath. Add anegotiateunit test inlib/src/test/kotlin/dev/arcp/runtime/(no such file exists yet) that asserts a client advertisingarcpx.unknown.example.v1against a runtime with no extensions yields aCapabilities.extensions == []negotiated result and anunsupported.isEmpty(), and a separate test that confirms astreaming = trueclient against a runtime withstreaming = falsestill appears inunsupported.