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
//! §4.4 terminal reading rules for a settled `response.json`.
//!
//! Once the fd is closed (the classifier reaches here only with no lock
//! held, §3.5), the latest step's `response.json` is classified by its tail
//! (§4.4). **Two facts come off that one walk, and they answer different
//! questions** (bl-fb87):
//!
//! - [`Framing`] — the **transport** question: did the segment close cleanly?
//! *complete* (last line `end`, last segment a `finish` with no `error`),
//! *failed* (an `error` in the last segment — retry budget exhausted or
//! non-retryable, §2.10), *killed* (no trailing `end` — the writer died
//! mid-stream, §2.9).
//! - [`Ending`] — the **semantic** question: what ended the turn? Read from
//! the canonical `finish.reason` brazen writes, verbatim.
//!
//! **Transport completion is not task completion.** A segment can keep every
//! transport promise — `finish`, no `error`, a trailing `end` — while the turn
//! it framed was cut off mid-utterance because the request's `max_tokens` ran
//! out. That is `Framing::Complete` and [`Ending::OutputLimit`], and the pair
//! is the whole of it: the framing stays honest (a sealed transcript entry
//! really was committed, which is what `rail::place` pairs against), and the
//! ending says the thing framing has no vocabulary for — that nothing more is
//! coming.
//!
//! Only a *complete and whole* tail is quiescent ([`Settled::whole`]); failed,
//! killed and output-limited are stopped (§3.5).
//! This is a self-delimiting reader over appended attempt segments (§4.4):
//! only the **last** segment decides, because it is the settled outcome.
/// The §4.4 settled outcome a closed `response.json` tail carries. Derived
/// from the **last** segment alone (the settled outcome), by the same
/// self-delimiting reader the live view uses. Widened to `pub(crate)` (the
/// enum + [`settled`] + [`segment_count`]) so the Y13 steps inspector reuses
/// this classifier instead of re-parsing the JSONL (§15 Y13: "reuse
/// git_tree::terminal's segment classification — do NOT duplicate the
/// parser").
/// What **ended the turn** — the semantic result the settled segment's
/// canonical `finish` names, beside [`Framing`]'s transport reading (bl-fb87).
///
/// Read from brazen's `FinishReason` verbatim and never inferred from token
/// counts: `usage == max_tokens` would duplicate a fact the reason already
/// states, and duplicated facts drift.
pub
/// The whole §4.4 reading of a settled tail: transport [`Framing`] and
/// semantic [`Ending`], off one walk. Neither is recoverable from the other,
/// so a caller that needs both reads the file once (§5.1 #10's discipline).
pub
/// Classify a `response.json` payload by its tail (§4.4). Only the last
/// segment decides. A trailing partial line (no `\n` yet) is dropped.
pub
/// The `reason` brazen writes beside a `finish` event
/// (`{"type":"finish","reason":"length"}`) that yog reads differently from
/// every other. One word, because it is the one whose consequence differs: the
/// turn did not end, it ran out of room.
const LENGTH_REASON: &str = "length";
/// Read one already-parsed `finish` event as its [`Ending`]. Any reason but
/// [`LENGTH_REASON`] — and a `finish` carrying no readable reason at all —
/// is [`Ending::OwnTerms`], which leaves every pre-existing classification
/// exactly where it was.
/// Number of completed attempt segments — the count of `end` events (§4.4:
/// every attempt segment terminates with an `end`). A still-open final
/// segment (no trailing `end`) is not yet counted; this is the "attempts"
/// figure the steps inspector shows.
pub
/// The raw JSONL text of the last settled segment's `error` event, when it
/// carries one (§4.4 *failed* framing; §5.1 #13 "response/error text"). Returns
/// `Some(line)` **iff** [`settled`] would answer [`Framing::Failed`] — the same
/// last-segment traversal, stopping at the first `error` before the previous
/// segment boundary; `None` for complete / killed / empty. The verbatim event
/// line (its `kind`/`message`/`status` fields and all) is what the auth heuristic
/// ([`crate::login::auth`]) scans, so the classifier stays schema-agnostic (bz's
/// and litany's error shapes may differ, §5.1 #10/#13).
pub
/// The classifier-relevant `type` field of one JSONL event line **and the
/// event itself**, or `None` if it does not parse as a JSON object with a
/// recognized string `type`. The value rides along so the `finish` arm can
/// read its `reason` without a second `from_slice` of a line that already
/// parsed — a second parse would carry a failure arm nothing can reach.
/// [`event`]'s thin projection, for the two walks that need only the kind.