Skip to content

Add Zod worker protocol conformance tests#63

Merged
t-kalinowski merged 17 commits into
mainfrom
zod-contract
May 14, 2026
Merged

Add Zod worker protocol conformance tests#63
t-kalinowski merged 17 commits into
mainfrom
zod-contract

Conversation

@t-kalinowski
Copy link
Copy Markdown
Member

Summary

This PR adds an internal Zod conformance worker and custom worker launch path for testing the worker/server protocol independently of R and Python.

It tightens sideband IPC handling around worker readiness, stdin accounting, request completion, session-end ordering, interrupts, timeout follow-ups, and protocol-error reporting.

Public-facing changes

  • No intentional change to the stable MCP tool surface.
  • cargo install continues to expose only the mcp-repl binary; the Zod worker is an internal test fixture.

Internal-only changes

  • Adds a custom worker-spec launch path for protocol conformance tests.
  • Adds tests/fixtures/zod-worker.rs as a deterministic protocol worker.
  • Adds Zod protocol coverage for:
    • input echo and newline normalization
    • prompt-shaped stdout preservation
    • timeout polling and busy follow-ups
    • mixed stdout/stderr ordering
    • image output
    • interrupt recovery
    • protocol-error latching and delayed protocol errors
    • session-end protocol violations
  • Updates IPC and worker-process handling so protocol errors observed during timeout resolution are preserved and reported.

Diff composition

Measured against origin/main, this PR is 1912 insertions and 153 deletions across 14 files.

  • runtime src/: +763/-118 (42.7% of churn)
  • inline tests inside src/: +39/-29 (3.3% of churn)
  • tests in tests/: +1085/-0 (52.5% of churn)
  • docs: +21/-6 (1.3% of churn)
  • other: +4/-0 (0.2% of churn)

Largest files:

  • tests/zod_protocol.rs: +629/-0
  • tests/fixtures/zod-worker.rs: +407/-0
  • src/ipc.rs: +303/-63
  • src/worker_process.rs: +315/-44
  • src/backend.rs: +96/-0

Testing

  • cargo check
  • cargo build
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test
  • cargo +nightly fmt

Introduce a JSON worker launch spec and route custom launches through the same server-managed stdin, stdio, sandbox, and sideband setup as built-in workers. Add the standalone zod-worker development binary with a worker_ready handshake, deterministic echo behavior, and basic protocol events.

Add a public MCP integration test that launches Zod through --worker-spec and verifies server-normalized stdin plus the worker-supplied prompt.

Validation:
- cargo check
- cargo test --test zod_protocol zod_worker_echoes_input_and_returns_worker_prompt
- cargo +nightly fmt
Track the active stdin bytes for protocol workers and drain them only from worker-reported readline_input/readline_discard events. A worker prompt now completes a custom-worker turn only after the active byte queue is empty, and protocol errors propagate instead of looking like IPC disconnects.

Expand the Zod public MCP coverage for prompt-shaped output, empty prompts, timeout polling, and busy follow-up rejection.

Validation:
- cargo check
- cargo test --test zod_protocol
- cargo +nightly fmt
Keep the existing WorkerManager::new(Backend, ...) constructor for built-in tests and add new_with_launch for custom workers. Reject explicit --worker-spec plus --interpreter combinations, preserve server-owned environment over custom worker env overlays, and reject sideband streams whose first structural message is not startup metadata.

Validation:
- cargo check
- cargo test parse_cli_args_rejects_worker_spec_with_interpreter
- cargo test --test zod_protocol
- cargo +nightly fmt
Move R and Python startup onto the worker_ready handshake while keeping the server able to accept legacy backend_info from older workers. The handshake now carries protocol version, descriptive worker identity, image capability, and the worker-provided graceful shutdown stdin text.

Validation:
- cargo check
- cargo test --test python_backend python_smoke
- cargo test --test server_smoke sends_input_to_r_console_smoke
- cargo test --test zod_protocol
- cargo +nightly fmt
Add Zod test commands that intentionally violate output base64 and readline_input accounting, and cover both through public MCP tests. These cases exercise the strict protocol error path added for custom workers.

Validation:
- cargo check
- cargo test --test zod_protocol
- cargo +nightly fmt
Add Zod command coverage for sideband-ordered stdout/stderr output and deterministic image emission through the public MCP surface.

Validation:
- cargo check
- cargo test --test zod_protocol
- cargo +nightly fmt
Add a public MCP test that times out an interruptible Zod turn, sends Ctrl-C with tail input, and verifies the tail runs only after the worker recovers to an unsatisfied prompt.

Validation:
- cargo check
- cargo test --test zod_protocol
- cargo +nightly fmt
Remove needless returns from sideband transport setup so all-target clippy passes with warnings denied.

Validation:
- cargo +nightly fmt
- cargo clippy --all-targets --all-features -- -D warnings
Keep strict startup-message validation on real server IPC streams, but mark test-only connection pairs as already past startup so unit fixtures can send focused synthetic sideband events.
Update malformed-message and image-ID protocol tests to use the current contract.
Treat session_end as terminal on the worker-to-server sideband and surface a late terminality violation after request-completion settling.
Add a Zod public protocol test that intentionally emits output after session_end.
Add a Zod command that writes prompt-shaped raw stdout before delaying completion, then verify raw output remains visible but does not complete the active turn.
Add explicit coverage that input already ending in a newline is not double-normalized.
Fix review findings:

[P2] Honor client_waiting before sealing custom turns — /Users/tomasz/github/posit-dev/mcp-repl/src/ipc.rs:390-394
When a protocol worker sends `readline_start` with `client_waiting: false` after accounting for all bytes of the active payload, this treats an empty `active_stdin` queue as sufficient to mark the prompt unsatisfied. That can finalize the reply while the runtime is about to consume worker-internal buffered input rather than waiting for the next MCP input; require `client_waiting` in addition to the active queue being empty.

Addressed by requiring `client_waiting` before marking a drained active-stdin prompt as unsatisfied, with public Zod coverage for a client-busy prompt that only completes after a later client-waiting prompt.

[P2] Preserve fatal protocol errors across requests — /Users/tomasz/github/posit-dev/mcp-repl/src/ipc.rs:636-636
Clearing `protocol_error` at request start can hide a fatal sideband error that arrived while the worker was idle or just after the previous reply's settle window; the reader thread has already exited in those cases, so the next request can hang or time out instead of reporting the real protocol failure. Protocol errors should remain latched until the worker is reset or the error is surfaced.

Addressed by keeping protocol errors latched across request start and surfacing them before new stdin writes, with public Zod coverage for a delayed idle protocol error reported on the next request.

Validation:
- cargo +nightly fmt
- cargo check
- cargo build
- cargo clippy --all-targets --all-features -- -D warnings
- cargo test
Fix review finding:

[P2] Account stdin consumed by documented custom workers — /Users/tomasz/github/posit-dev/mcp-repl/src/ipc.rs:641-641
When a custom worker follows the currently documented sideband contract and only sends `readline_result` after consuming stdin, this queue is never drained because the reader only calls `account_active_stdin` for `readline_input`/`readline_discard`. The following `readline_start` is therefore treated as already satisfied and `wait_for_request_completion` can time out even though the worker is idle; either document/require the new accounting messages for `--worker-spec` workers or account `readline_result.line` for protocol-compatible workers.

Addressed by restoring the original worker contract shape: `readline_start` carries only prompt text, while active-turn completion is derived from required `readline_input` and `readline_discard` accounting facts. The public sideband docs now document those accounting messages, and the Zod conformance worker no longer emits a prompt wait hint. Added regression coverage that serialized `readline_start` has no extra field and that a prompt with remaining active stdin does not complete until the buffered input is accounted for.

Validation:
- cargo +nightly fmt
- cargo check
- cargo build
- cargo clippy --all-targets --all-features -- -D warnings
- cargo test
Move the Zod protocol worker from a package binary target to an example fixture so default Cargo installs only expose mcp-repl. The zod protocol tests now build the example fixture explicitly before spawning it, and the install tests assert Cargo's default binary target surface.

Validation:
- cargo +nightly fmt
- cargo check
- cargo build
- cargo clippy --all-targets --all-features -- -D warnings
- cargo test

Review finding:
[P2] Keep the test worker out of installed binaries — /Users/tomasz/github/posit-dev/mcp-repl/Cargo.toml:14-16
With this extra `[[bin]]` target, `cargo install --git ... --locked` installs both `mcp-repl` and `zod-worker`. That makes the internal protocol test fixture part of the user-facing install surface and can even block installation when a user already has a `zod-worker` executable in Cargo's bin dir; gate it behind a non-default feature or move it out of package bin targets.

Response:
Moved zod-worker to an explicit Cargo example fixture at tests/fixtures/zod-worker.rs, so default package binary targets only include mcp-repl. Updated zod protocol tests to build the example fixture and added a cargo metadata regression for the installable binary surface.
Latch protocol errors observed while probing timed-out requests so the next pending poll or busy follow-up reports the real worker protocol failure instead of consuming it and reporting only busy state.

Validation:
- cargo +nightly fmt
- cargo check
- cargo build
- cargo clippy --all-targets --all-features -- -D warnings
- cargo test

Review finding:
[P2] Propagate protocol errors while resolving timeouts — /Users/tomasz/github/posit-dev/mcp-repl/src/worker_process.rs:4387-4387
If a sideband protocol error arrives after a request has timed out, this timeout-marker probe consumes it from `wait_for_request_completion` and then ignores it unless the worker has exited. A non-empty follow-up runs this path before the busy poll, so inputs like delayed invalid JSON/base64 can lose the actual protocol error and leave the request reported only as busy; return or re-latch the protocol error instead of folding it into the timeout/disconnect case.

Response:
Added a settled pending-error slot for timed-out requests. Timeout-marker resolution now latches protocol errors instead of folding them into timeout/disconnect handling, and pending polls or busy follow-ups return that error before probing completion again. Added a zod protocol regression that delays invalid output until after the first timeout and verifies the follow-up reports the base64 protocol error.
@t-kalinowski t-kalinowski marked this pull request as ready for review May 14, 2026 12:42
@t-kalinowski t-kalinowski merged commit e4d054c into main May 14, 2026
5 checks passed
@t-kalinowski t-kalinowski deleted the zod-contract branch May 14, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant