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
//! Harness-side of the provider-adapter contract (ARCH §4.4).
//!
//! The provider adapter is brazen's `bz` — one stateless binary for
//! every provider. The harness invokes it **once per attempt** as
//! `bz --json --provider <row>`, pipes a canonical request (JSON) to its
//! stdin, and reads its stdout as brazen's `v=1` canonical event stream
//! (NDJSON — one event per line). [`AdapterRunner`] is the exec seam the
//! retry driver ([`super::dispatch`]) depends on; [`SpawnAdapter`] is
//! the production implementation, tests inject a stub.
//!
//! Per-line dispatch is structural: the §4.4 stream emits one event per
//! line, and the harness is the live writer of
//! `<conv-repo>/steps/<conv-id>/<NNN>/response.json` (§3.5). Routing
//! lines through a callback lets the harness append each event to disk
//! as it arrives and stream *content* into the transcript writer's
//! staging sink (§2.3) in the same pass — one stream, two sinks, no
//! read-back.
//!
//! **Exit code is diagnostic (§4.4).** brazen surfaces every failure
//! in-band as an `Error` event on stdout and *also* sets a sysexits
//! exit code computed from the same fact — the event is authoritative,
//! the exit code diagnostic. So a non-zero `bz` exit is NOT a spawn
//! error here: only a failure to *launch* the binary is. brazen dies at
//! once on SIGTERM with no flush (§2.9); the missing trailing `end` on
//! the closed fd is the stop signature, handled by classification, not
//! this runner.
//!
//! **Stderr is the adapter's diagnostic channel**, and the run's
//! product beside the stdout stream. An adapter that dies *before* it
//! can speak the in-band contract — a malformed brazen config, an
//! unreadable credstore — says so only there, so discarding it turns a
//! startup failure into an empty stdout stream indistinguishable from a
//! mid-stream kill (§2.9). The runner captures it whole and hands it
//! back; the caller lands it in the step record and quotes its tail
//! when the stream ends without a terminal `end` (§2.3, §4.4). It is
//! read concurrently with stdout on its own thread, so a chatty adapter
//! filling the stderr pipe buffer can never deadlock against the
//! harness tailing stdout.
//!
//! **No env forwarding.** Auth and endpoints are entirely brazen's
//! (§4.4): its config resolves via `--config` / `BRAZEN_CONFIG` / XDG,
//! and the harness sets `BRAZEN_CONFIG` only under test isolation — as
//! an inherited process env, never a per-call value the harness
//! threads. The child inherits the harness environment unchanged.
use Error;
use OsString;
use ;
use Path;
use ;
use thread;
/// The provider-adapter binary: brazen's `bz`, resolved on `PATH`
/// unless the global `models.yaml` names an `adapter:` override (ARCH
/// §4.2 / §4.4).
pub const BZ_BIN: &str = "bz";
/// Resolve the adapter binary. One resolution order, most-specific
/// first: the `models.yaml` `adapter:` override (§4.2), else the
/// binding-injected `host` target (`cmd::Fx::adapter_target` — an
/// embedding host naming itself as the adapter, the same injection
/// philosophy as `driver_target`, §3.4), else `bz` on `PATH`. Both named
/// targets are used verbatim, and both skip the load-time version guard
/// in favor of the in-band `MessageStart.v` handshake (§4.4): a named
/// target — config override or host assertion — is identity the caller
/// vouches for, one trust class. The version guard runs only for the
/// default `PATH`-resolved `bz`, when both are `None`.
/// The slice of the adapter contract the harness calls into.
///
/// One subprocess per [`Self::run`]. As the child writes stdout, every
/// completed line (terminator stripped, blanks skipped) is handed to
/// `on_line`. The callback may surface an [`io::Error`] to abort early;
/// otherwise the call returns when the child exits and stdout reaches
/// EOF. A non-zero exit is NOT surfaced — the in-band `Error` event is
/// authoritative and the exit code is diagnostic (§4.4). Only a failure
/// to spawn the binary surfaces as an error.
/// Default [`AdapterRunner`]. Uses [`Command`] with PATH lookup and
/// inherits the harness environment (test isolation sets `BRAZEN_CONFIG`
/// there, §4.4).
;
/// Run `binary` with `args`, discarding stdin, and return its stdout as
/// one UTF-8 string (lines rejoined by `\n`). Used by the load-time
/// version guard (`bz --version`, §4.4) — the single stdout line `bz`
/// prints is captured through the same exec seam so the guard is
/// stub-testable.
/// Classify a failure to *launch* the adapter (§4.4) — the one
/// classification the harness makes over a spawn `io::Error`, and the
/// reason both spawn seams (the version guard's `--version` probe and
/// every model call) route through here rather than mapping the errno
/// straight onto [`Error::AdapterSpawn`]. `NotFound` means the binary
/// is simply not there, which is actionable, so it earns the version
/// guard's voice ([`Error::AdapterMissing`]); everything else is a real
/// spawn failure with nothing to advise.
pub
/// Strip a single trailing `\n` (and the `\r` of a `\r\n` pair) from
/// `buf` so callbacks see clean payload bytes.