Review #002 R4 — a NegotiateRequest parse failure of the open op's
schema-validated input died silently (log + return, channel teardown,
consumer observed NoExitChunk — indistinguishable from a crashed
producer), while the other post-open failure classes (unknown backend,
allocate_failed, ownership denial) wrote the 0x00-prefixed error frame.
- make_tty_open_handler now accepts the channel's BiStream and writes
a malformed_negotiation frame via the shared
crate::adapter::send_negotiation_error (now pub(crate)) before
returning; the consumer's M1 peek surfaces NegotiationRejected
unchanged
- the frame type and layout are unchanged (ADR-001 wire-stable
contract); no new frame type, no wire change
- tests: make_tty_open_handler seam test with a hand-built
schema-bypassing input (cwd: 42) + a real-registry end-to-end test
via ChannelClient::open_channel (bypasses open_via_channels's local
fail-fast parse — R5's path — so it exercises the producer handler)
- docs: ADR-009 amended (Parse-failure error frame section);
tty-adapter.md malformed_negotiation row covers both paths;
session.rs post-open failure lists updated; review #002 R4 resolved
Note: the review's "unreachable end-to-end" premise was refined —
open_via_channels parses params locally (fail-fast) so a TtySession
consumer never hits the producer-side parse failure, but direct
ChannelClient callers do; the schema is deliberately partial so a
schema-valid value (cwd typed as a number) reaches the handler.
Verification: cargo test 95 lib (default) / 138 (--all-features);
clippy -D warnings native + wasm clean; fmt clean; doc 0 warnings.
The channels path no longer carries a second negotiation frame on the
channel's data stream (ADR-009). The open op's registry-validated
input IS the negotiation:
- producer: make_tty_open_handler parses the open op's input into a
NegotiateRequest and drives the new drive_session_pre_negotiated
(same three-pump driver as drive_session, minus the wire-frame
negotiation phase; validate/allocate factored into
validate_and_allocate, shared by both paths). Post-open failures
(unknown backend, allocate_failed, ownership denial) still go to the
client as a 0x00-prefixed negotiation error frame, so the consumer's
M1 disambiguation read applies unchanged. The tty:open scope gate is
enforced by the registry's AccessControl (not re-checked in the
handler).
- consumer: open_via_channels parses params locally (fail-fast before
a channel is allocated), opens the channel, and starts raw-chunk
mode directly (from_halves_raw — no negotiation write; the
0x00-error-frame peek retained).
- tty_open_spec's input schema is now the partial NegotiateRequest
shape (carriage/backend/cmd required; backend params stay free-form
— raw JSON Schema is permissive on unknown keys).
Prerequisites landed upstream: alkcall 0.4.0 enforces
OperationSpec.input_schema at dispatch (the registry check this design
leans on never existed before); alkcall 0.4.1 parks early-arrival
chunks for un-adopted channels instead of dropping them — the open
response / producer's-first-write race was silently losing the first
chunks (found by L3's test; the session never resolved).
L3: open_via_channels + from_bidi_stream_via now covered end-to-end
(5 session tests + pre-negotiated adapter test + shared-harness tests
in the new crate::testing module; the channels harness moved there so
session tests share it).
Verification: cargo test 93 lib (default), 116 lib + 19 integration
(--all-features); clippy -D warnings native + wasm clean; fmt clean;
doc 0 warnings; wasm check clean.