1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
//! Phase 119 (DOCS-06): **proof that the shipped v2 (2026-07-28) examples work
//! TOGETHER over a real socket** — `s47_v2_stateless_mrtr` is spawned as a live
//! server and both documented client examples, `s48_v2_mrtr_client` and
//! `s53_v2_agent_client`, are driven against it as real peer processes.
//!
//! # Why this file exists at all
//!
//! DOCS-06's claim is "runnable v2 examples ship and pass". `make test-examples`
//! only BUILDS examples — its own banner says so — and a compiled server that
//! never answers a request is indistinguishable from a working one at build time.
//! `s47_v2_stateless_mrtr` in particular demonstrates NOTHING on its own: it is a
//! server, and every property the docs attribute to it (no `initialize`
//! handshake, no `Mcp-Session-Id`, a continuation resumed across an independent
//! HTTP request) is a property of an exchange, not of a binary. That is why the
//! phase pairs it with its two client examples here rather than asserting on it
//! alone.
//!
//! The sibling file `tests/docs04_examples_run.rs` covers the examples that bind
//! nothing and prove themselves by their stdout. This one covers the shape that
//! needs a port, and it is deliberately a SEPARATE file so that all the port
//! reasoning lives in one place.
//!
//! # Two harness idioms in one file, on purpose
//!
//! The server leg uses `spawn_example` + `wait_until_listening` +
//! `wait_until_released`: it discards the child's streams, hands back a reaping
//! guard, and the evidence that it started is the accepted TCP connection.
//!
//! The two client legs use `run_example_to_completion` instead, because they are
//! themselves example BINARIES rather than in-test HTTP calls — the documented
//! reader experience is `cargo run --example s48_v2_mrtr_client`, so that is what
//! gets run. `spawn_example` would be the wrong tool for them twice over: it
//! discards both streams (and their stdout IS the evidence) and it returns a
//! guard rather than an exit status (and their exit status is the first thing
//! asserted).
//!
//! Do not "unify" the two idioms. They answer different questions.
//!
//! # Port 8161, deliberately
//!
//! Ports 8147 (`s47_v2_stateless_mrtr`'s own default, and the default both client
//! examples fall back to), 8149, 8150, 8151, 8153, 8155, 8157 and 8159 are
//! already claimed across `tests/`, `scripts/` and `examples/`. 8161 is the next
//! free slot in that family and nothing else in the repo mentions it.
//!
//! Because s47's own default 8147 is claimed, this leg passes the address
//! EXPLICITLY as `argv[1]` — to the server and to both clients — rather than
//! relying on any of the three defaults agreeing.
//!
//! # Why both client legs run before either is asserted
//!
//! Asserting the `s48` outcome before `s53` has executed would leave half the
//! DOCS-06 claim with zero executed evidence exactly when something is wrong.
//! Both clients therefore run to completion and both outcomes are written to a
//! JSON artifact under `target/` BEFORE the first assertion fires, so a red on
//! either client is diagnosed against a recording of both. This is the same house
//! rule `tests/embedded_resource_example_run.rs` follows.
//!
//! ONE case is outside that rule, stated rather than implied: a client that
//! never EXITS. `run_example_to_completion` panics on its own deadline, so an
//! `s48` that hangs aborts this test before `s53` runs and before the artifact
//! is written. Nothing is lost by it — that panic already carries the hung
//! client's partial stdout and stderr, which is the same evidence the artifact
//! would have held for that leg — but do not read the artifact's absence as
//! "the legs did not run". A timeout red is diagnosed from the panic body.
//!
//! # Why the client legs need a budget at all
//!
//! These two are the highest-risk callers of `run_example_to_completion` in the
//! phase: each drives a live socket peer. `wait_until_listening` bounds only the
//! BIND — a server that accepts the connection and then never answers would leave
//! an unbounded client waiting forever, hanging the integration suite rather than
//! failing it. Nothing bounds the exchange that follows except `S48_TIMEOUT` and
//! `S53_TIMEOUT`. On expiry the helper kills AND reaps the child and panics with
//! both partial streams.
//!
//! # The `PMCP_REQUEST_STATE_KEY` startup warning
//!
//! On startup s47 emits a real `WARN` before its banner: with no key set it
//! generates a fresh PER-PROCESS key, so a multi-round-trip follow-up landing on
//! a different instance behind a load balancer cannot be resumed and is
//! re-elicited from scratch. This test deliberately does NOT set that variable, so
//! the leg exercises the default per-process-key path — which is also the path a
//! reader following the docs gets. No key value appears anywhere in this file.
//!
//! The warning itself is NOT asserted on here: `spawn_example` discards the
//! child's streams, so the test cannot see it. It is recorded in this header
//! because it is the source text plan 119-05 promotes into the migration
//! chapter's server track, and a later reader looking for where that text is
//! verified should find this note rather than assume a missing assertion.
use ;
use json;
use Output;
use Duration;
/// The stateless-MRTR server example's compiled path, relative to the target dir.
///
/// A root `examples/` binary requiring the two transport features, so
/// `cargo build --features full --example s47_v2_stateless_mrtr` produces it.
const S47_REL_PATH: &str = "debug/examples/s47_v2_stateless_mrtr";
/// The MRTR client example's compiled path, relative to the target dir.
///
/// This is the client the s47 header itself tells the reader to run in a second
/// terminal, which is precisely why it is the peer this leg drives.
const S48_REL_PATH: &str = "debug/examples/s48_v2_mrtr_client";
/// The `pmcp-agent` connector client example's compiled path.
///
/// The second, independent peer: it reaches the same server through the agent
/// connector rather than the MRTR client API, so a green here means the server's
/// v2 surface answers more than one client shape.
const S53_REL_PATH: &str = "debug/examples/s53_v2_agent_client";
/// See the module header for why this port and not another.
const BIND_ADDR: &str = "127.0.0.1:8161";
/// Where the recorded client outcomes land, for the SUMMARY to quote verbatim.
const ARTIFACT_REL_PATH: &str = "119-04-v2-example-run.json";
/// How long the server gets to bind its socket before the leg gives up.
const READY_TIMEOUT: Duration = from_secs;
/// How long the port gets to become free again after the server is killed.
///
/// A short budget on purpose: this one is not guarding against slowness, it is
/// asserting that the teardown TORE DOWN. A listener still answering ten seconds
/// after its process was killed and reaped is a leak, and the honest place to
/// discover it is here rather than in the next run's bind failure.
const RELEASE_TIMEOUT: Duration = from_secs;
/// The budget for the `s48_v2_mrtr_client` leg.
///
/// The exchange is three demonstrations over loopback against an in-process
/// server, measured well under a second. Sixty seconds is orders of magnitude
/// above that, deliberately: the budget exists to convert a HANG — a server that
/// accepted the connection and then stopped answering — into a red, not to police
/// performance.
///
/// UNIT NOTE: `from_mins`, not the equivalent `Duration::from_secs(60)`, because
/// `make lint` runs clippy's nursery group and
/// `clippy::duration_suboptimal_units` rejects a whole-minute duration written in
/// seconds. The same note is recorded on `S50_TIMEOUT` in
/// `tests/docs04_examples_run.rs`.
const S48_TIMEOUT: Duration = from_mins;
/// The budget for the `s53_v2_agent_client` leg.
///
/// A SEPARATE constant from [`S48_TIMEOUT`] even though the two currently share a
/// value. They are separate legs against separate binaries with different client
/// stacks, and a future retune of one must not silently retune the other. Per the
/// `example_process` module header's rule, both live in THIS file and are never
/// imported from a shared location.
///
/// Written in minutes for the lint reason recorded on [`S48_TIMEOUT`].
const S53_TIMEOUT: Duration = from_mins;
/// The line both client examples print once all three of their demonstrations
/// have behaved as their documentation says.
///
/// It is the last thing either binary prints before returning `Ok(())`, so it is
/// the clients' own statement that no demonstration was skipped — a stronger
/// claim than the exit status alone, which a client that returned early would
/// also satisfy.
const CLIENT_BANNER: &str = "All three demonstrations behaved as documented.";
/// The argv both client legs are invoked with.
///
/// Shared by the run and by [`record`] so the artifact cannot describe an
/// invocation that did not happen — the artifact's whole value is being a
/// trustworthy record of what actually ran.
const CLIENT_ARGS: & = &;
/// Turn one client's captured `Output` into the artifact's record of that leg.
///
/// Both streams go in as TEXT, not just the one asserted on: a red whose stdout
/// says nothing usually has the reason on stderr.
/// Assert one client leg exited 0 and printed its completion banner.
///
/// Status FIRST, then the banner: a client that died mid-exchange produces a
/// banner mismatch whose message says nothing about the death, so the crash has
/// to be reported AS a crash. Every message names the leg, states what was
/// expected, and carries the captured body — a red that says only
/// `assertion failed` is a red that says nothing.
/// The stateless v2 server example serves BOTH documented v2 client examples,
/// each run as a real peer process over a real socket.
async