Test Report — Streaming Return Route, polyglot dry-run (Java ⇄ Rust)¶
Live cross-engine validation of the streaming return route's normative wire contract
(port spec §4)
— experiment R4, the acceptance gate of the sync-over-async port — conducted 2026-09-13
(local time) on a single developer machine: one redis-standalone, four pods across two
engines, Node.js as the SSE witness. Follows the
Rust cross-pod report (R3) and the Java engine's
E-series record.
The claim under test¶
The Redis rendezvous contract is language-neutral: identical key shapes
(request:{cid} / queue:{cid}), one compact segment envelope, one
{sync.return.channel.prefix}:{origin} channel convention, the same configuration
defaults. If that holds, a producer on either engine can stream into a rendezvous held
by a UI pod on either engine — with no engine change and no translation layer anywhere.
R4 makes it literal:
| Process | Engine | Role | Port |
|---|---|---|---|
redis-standalone 4.12.8 (helper jar) |
— | the rendezvous | 6379 |
sync-over-async-demo 4.12.8, profile stream-ui |
Java | holds an SSE connection | 8600 |
sync-over-async-demo 4.12.8, profile stream-producer |
Java | posts events (StreamResponder, Lua append) |
8601 |
sync-over-async-demo, profile stream-ui |
Rust | holds an SSE connection | 8602 |
sync-over-async-demo, profile stream-producer |
Rust | posts events (StreamResponder, MULTI/EXEC append) |
8603 |
Both coordinators' startup lines show the shared channel convention side by side:
Java: Return-route subscriber listening on svc-return:202609147bd6f561cccf42cc92715d66431faede
Rust: Return-route subscriber listening on svc-return:504ae8e5bd8345d68be73758c957805e
P1 — Rust producer → Java UI pod (the chat shape)¶
The Java facade announces the cid; the Rust responder posts five tokens + eof; the
Java coordinator drains and the Java edge renders:
18:03:30.503 event: cid
18:03:30.503 data: 20a9873ed80d47529ec93a30325b8883
18:03:30.698 data: Streaming
18:03:30.698 data: across
18:03:30.698 data: pods
18:03:30.699 data: by
18:03:30.699 data: design
18:03:30.700 event: done
18:03:30.701 data: {"tokens":5}
Rust producer's view: {"live":true,"posted":6}. Exact order, terminal metadata,
across engines. ✅
P2 — Java producer → Rust UI pod (the reverse)¶
The Rust facade announces; the Java responder posts (its atomic append is the Lua script); the Rust coordinator drains (its append vehicle is MULTI/EXEC — the §5 item 1 delta proving interop-neutral, since consumers never see the vehicle):
18:03:33.242 event: cid
18:03:33.242 data: be79fed03ada4a2ea68a8eb8f0ab7d1c
18:03:33.439 data: Streaming
...
18:03:33.445 event: done
18:03:33.445 data: {"tokens":5}
Java producer's view: {"live":true,"posted":6}. ✅
P3 — two engines, ONE rendezvous (both UI pods)¶
The fullest form of the claim: a Java producer and a Rust producer post named
events into the same channel, and either engine may close it. On the Rust UI pod
(Java posts orders, Rust posts billing, Java closes):
18:03:52.051 event: orders
18:03:52.051 data: order 42 shipped (posted by the Java pod)
18:03:53.133 event: billing
18:03:53.133 data: invoice 7 ready (posted by the Rust pod)
18:03:54.224 event: done
And mirrored on the Java UI pod (Rust posts, Java posts, Rust closes):
18:04:12.695 event: orders
18:04:12.695 data: order 43 packed (posted by the Rust pod)
18:04:13.797 event: billing
18:04:13.797 data: invoice 8 sent (posted by the Java pod)
18:04:14.917 event: done
Posting order preserved, any-producer close honored, in both directions. Both edges
rendered the metadata-less terminal identically as data: {} — settling the R3 report's
"presentation nuance" note: it is parity, not a divergence (the R3 observation
compared against an elided line in the Java E3 evidence, not against the Java wire). ✅
P4 — cross-engine chaos recovery¶
The Rust producer's lost mode stores a data segment and the terminal eof with
every wake-up suppressed, against a channel held by the Java UI pod (idle allowance
6s). Nothing wakes the pod; the Java watchdog's single final drain recovers both
Rust-stored segments and completes the render normally:
18:04:38.758 event: cid
(data + eof stored by the RUST producer, all wake-ups suppressed)
18:04:44.763 event: orders
18:04:44.763 data: stored by the Rust pod, wake-up lost
18:04:44.764 event: done
18:04:44.764 data: {"recovered":"cross-engine"}
Recovery at idle + 5 ms. The recovery machinery itself crosses engines — design D4's cornerstone with the store and the drain on different runtimes. ✅
P5 — the real-LLM leg (the Java E4 analog, cross-engine)¶
The blueprint payoff, now polyglot: the Java producer's llm mode pulls Gemini's own
SSE stream through the platform's SSE consumer and bridges each token batch into the
rendezvous — and the Rust edge renders it progressively, terminal done carrying the
provider's usage metadata (one short generation, quota-polite):
18:05:05.572 event: cid
18:05:07.040 data: It enables diverse systems
18:05:07.063 data: to seamlessly communicate and evolve independently without being
locked into a single programming language or technology stack.
18:05:07.071 event: done
18:05:07.071 data: {"provider":"gemini","model":"gemini-3.8-flash","finishReason":"STOP",
"promptTokenCount":15,"candidatesTokenCount":22,"totalTokenCount":37}
Real tokens, produced on a Java pod, rendered by a Rust pod — the model's own answer is a fair summary of the gate. ✅
Findings and round notes¶
- The wire-parity gate is MET. Every scenario ran on the normative contract alone — no shims, no translation, no engine change. The port spec's §4 table is now live-verified in both directions, including the mixed-producer form.
- Mid-round server bounce — both engines healed their subscriptions on camera. An
operator mistake (see note 4) forced a full Redis restart with all four pods live. The
Rust coordinator's explicit reconnect loop logged
Return-route subscriber re-subscribed to svc-return:…and Java's Lettuce loggedReconnected to 127.0.0.1:6379— the §5 item 2 delta (hand-rolled resubscribe where Lettuce auto-resubscribes) proving equivalent live. - One behavioral delta found on the bounce-recovery path (command lane): after a
server restart, the Rust pods' first Redis command fails (
broken pipe) and heals on the next call — redis-rs'sConnectionManagerarms an asynchronous reconnect but returns the failed command's error to the caller, and its retry configuration governs connection attempts, not command replay. Lettuce requeues not-yet-written commands across a reconnect, so the same first call on the Java pod simply works. The failure surfaces cleanly (a 500 on the producer endpoint; a pre-head 500 on a facade open) and the design already treats transport errors as retryable, but the engines differ in who retries: the library (Java) vs the application (Rust). Maintainer decision requested: accept and document, or harden the Rust store with a retry-once on its idempotent operations only (save_route/get_route/cleanup/queue_length—SETEX/GET/DEL/LLEN); the append deliberately stays fail-fast either way, since replaying an ambiguousRPUSHrisks a duplicate segment (at-least-once) that the no-sequence-number design cannot detect. - Operator note for the helper:
kill -9on theredis-standalonewrapper JVM orphans itsredis-serversubprocess, which keeps port 6379 and — once a later helper start wipes/tmp/soa-redis— fails its next background save and disables writes (MISCONF … stop-writes-on-bgsave-error). A plain SIGTERM/Ctrl-C shuts the subprocess down cleanly (verified at round close). The helper README's Ctrl-C guidance is the contract; this note records what the hard-kill failure looks like from the pods.
What remains¶
Nothing in the R-series — R1 → R4 complete; the port is functionally in lock-step with
the Java engine's ratified design. Outside the series, recorded for their own gates: the
mercury-sync-over-async crates.io publication rides the next release (with the
path-only dev-dependency check), the one-shot facade tasks stay deferred until a transport
worth demonstrating lands (ruling Q1), and note 3's hardening question awaits the
maintainer's ruling.