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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
//! The §7.3 **step wound**: what went wrong with one step, said in words where
//! the step is rendered. Three classes, which is the whole of the vocabulary:
//!
//! - **No response** — the driver died before the model produced anything, and
//! since bl-55d8, why.
//! - **Output limit** — the model call framed cleanly and the *turn* did not
//! end: the request's `max_tokens` ran out mid-utterance (§4.4
//! [`Ending::OutputLimit`], bl-fb87). Framing alone paints that step `✔
//! complete`, which is true of the transport and a lie about the turn — the
//! same quiet-step misreading the no-response class exists to correct, one
//! layer up.
//! - **Refused** — the provider said no: the step settled `Failed` with an
//! auth-shaped error, which is §8.3's [`AuthFailure`] classification exactly.
//! The arm **wraps that answer** rather than restating it, so the Login
//! affordance and the wound are one fact with one derivation; what the arm
//! adds is that the refusal is now IN the vocabulary, which is what makes
//! [`latest_wound`] total over the ways a conversation dies quietly and lets
//! the §8.5 transcript seat one notice for all of them (bl-015b).
//!
//! The no-response class, in detail:
//!
//! Without this state such a step reads as a quiet one — `Framing::Killed`
//! paints the same ash "stopped" badge a mid-stream kill gets, over a
//! `0 attempts · 0 tok` row that looks like nothing happened, while STORIES S0
//! step 4 promises "any step failure is a rendered fact" and §7.3 that a failed
//! action is never stderr-only.
//!
//! The **state** is two observations — the same pair §3.5 already composes for
//! agent state, nothing stored (§5.1 #13):
//!
//! - **Unanswered on disk** — the step's `response.json` carries no bytes
//! (absent or zero-length) *and* its `meta.json` is absent. litany writes
//! `meta.json` only after the model call returns (ARCH §2.3 — its dispatch
//! loop short-circuits on the call's own error before `write_meta`), so the
//! pair says exactly: the call emitted nothing and the step never settled.
//! - **Nobody driving** — a live driver's newest step is legitimately
//! unanswered for the moments between opening `response.json` and the first
//! streamed event, so the wound is claimed only of an agent whose lock is
//! free (§3.5). Never a false definite (§10).
//!
//! Only the newest step can be the one a driver is filling, so the liveness
//! observation gates that step alone — an earlier unanswered step is
//! unambiguous, and stays rendered as the place the conversation died.
//!
//! The **reason** is a third read of the same step directory, and it is the
//! whole of bl-55d8. litany ARCH §2.3 on `stderr.log`, verbatim: *"the adapter
//! subprocess's stderr, appended once per attempt across the model call.
//! **Empty on an ordinary run**: brazen speaks every failure in-band on stdout
//! (§4.4), so bytes here mean the adapter failed outside that contract — a
//! startup failure (a malformed brazen config, an unreadable credstore) that
//! produced no events at all."* That is this wound's class exactly, so the
//! file is not a hint about the cause — it **is** the cause, in the adapter's
//! own words, sitting in the step yog is already reading.
//!
//! **It is not a new state, and it is not a new stored fact** (bl-55d8): the
//! predicate is unchanged, the read is gated on it (a healthy step pays
//! nothing), and the words are re-read from disk every derivation like every
//! other §5.1 fact.
//!
//! **Where it is NOT.** Until bl-55d8 the banner pointed at the ops surface —
//! *"the driver's own stderr is in the activity trail below"*. For the class
//! the operator actually hit that pointer is empty: a turn continued by
//! `litany message` is driven by a child **litany** launched, not by a yog
//! detached spawn, so no §8.1 per-spawn sink exists to fold into a `-2` ops row
//! at all. The step's own `stderr.log` is the only copy, which is why the
//! sentence now carries the bytes instead of naming a place to look.
//!
//! Deliberately *not* a reproduction hatch (§8.4 `yog exec`): see §14's
//! rejection — the driver's own words are now rendered where the wound is, so
//! there is even less reason to ask the operator to re-create it.
use Path;
use crate;
use crateAuthFailure;
/// The sentence yog renders at the wound — the §7.3 rendered fact for this
/// class. Used verbatim beside the Steps row and composed into the §11
/// Altitude-1 banner, so both surfaces say one thing.
pub const NO_RESPONSE: &str = "driver produced no response";
/// The same, for the §4.4 output-limit class (bl-fb87). It names what happened
/// to the **turn**, not to the transport, because the transport is the half
/// the framing badge already reports and the half that went fine.
pub const OUTPUT_LIMIT: &str = "output limit ended the turn";
/// What the banner adds when the step's own `stderr.log` is empty too — the
/// honest end of the trail, said outright rather than pointing somewhere that
/// has nothing either.
const MUTE: &str = "and its stderr.log is empty too — nothing on disk says why";
/// How the banner introduces the captured bytes. It names the **file**, not a
/// surface, for the §8.3 fallback-grammar reason: an operator who wants more
/// than the tail must be told where the whole of it lives.
const SPOKE: &str = "its stderr.log says:";
/// What the banner adds for the output-limit class: what the operator is
/// looking at, and the one gesture that carries it on.
///
/// It names Nudge in order to **retire** it, because §8.2 offers Nudge on
/// every other resting conversation and a control that silently disappears
/// reads as a bug. Linked litany derives `NothingDue` from a tool-free
/// assistant tail and exits without creating a step, so the honest sentence is
/// that the gesture cannot help and which one can — never a blind retry, and
/// never a new verb (bl-fb87).
const CUT_OFF: &str = "the reply stops where the model's output budget ran out, so nothing \
more is coming. Nudge cannot resume it — send a message to carry it on.";
/// The §11 banner's leading mark. Never the only carrier — the sentence beside
/// it states the fact in words (§11 glyph doctrine).
const ALARM: &str = "⚠";
/// The §7.3 wound, its class **and its reason** — four readings of one
/// derivation, never a stored flag (§5.1 #13).
///
/// The two no-response arms are separate rather than an `Option<String>`
/// because a wound with nothing to say is a real, distinct answer (a SIGKILL
/// mid-call leaves an empty `stderr.log`), and `Some("")` would spell it as a
/// wound whose words are blank — one fact with two encodings, which is how
/// they drift.
/// Read one step's wound from its own bytes. The **refusal is asked first**
/// and its answer is [`crate::login::auth::classify`]'s, unchanged: a provider
/// that said no settled the step with an error event, so it can never be one
/// of the unanswered classes below and the two questions are disjoint by
/// construction rather than by an ordering rule. What `classify` cannot know
/// is the provider **row** — that needs the agent's governing config, one git
/// read for the whole view — so it answers `Unrouted` here and
/// `steps_view`'s own `route_auth` upgrades it.
///
/// `response`, `meta_present` and
/// `ending` are the reads [`summarize`](super::summarize) already made —
/// `meta_present` is the *existence* of `meta.json`, not its parse, since a
/// malformed meta still means the step settled, and `ending` is the §4.4
/// classifier's semantic half off the same response bytes (never a second
/// walk, §15 Y13).
///
/// The two classes are disjoint by construction: a step with no response bytes
/// has no settled tail to read an [`Ending`] out of.
///
/// The `stderr.log` read is **gated on the predicate**: an ordinary step pays
/// one comparison and no syscall, so attaching the reason costs a healthy
/// conversation nothing. Both bounds on how much is read are borrowed, not
/// invented — [`crate::opslog::detached::captured`] for how much of a capture
/// file yog ever reads, [`crate::opslog::rows::stderr_tail`] for how much of a
/// stderr a *surface* says.
pub
/// Is a driver at work on this agent (§3.5)? Then its newest step is allowed
/// to be unanswered — a model call in flight, not a wound.
pub
/// The **second** reason the newest step's unanswered shape may still be a call
/// in flight (§7.2, bl-90bf/bl-18e8; judged here since bl-776a): the liveness
/// half of [`driven`] is younger than its own catch-up latency.
///
/// The no-response wound's two halves do not share a clock. Its disk half is
/// read fresh at every ask; its liveness half rides the last published
/// snapshot, and a driver *taking* its flock emits no fs event — so for the
/// whole of [`Cadence::wound_grace`](crate::app::Cadence::wound_grace) after a
/// call starts, a genuinely-in-flight empty step reads as a wound. A structural
/// TOCTOU, not a bad classifier, and the fix is time: **a wound is stated only
/// once the reading that would contradict it has had time to arrive.** The wait
/// was a render-layer gate with no consumer in this crate and no carrier to a
/// seat, so every seat either hard-coded a window or flashed the alarm the
/// grace exists to prevent; spent here, the wound crosses **already-judged**
/// and a seat holds no period at all.
///
/// The anchor is the step's own `request.json` mtime — litany writes it
/// immediately before invoking the model, so it is that call's start (§5.1 #28,
/// the stamp `call_start_unix` already reads) and a fact about the world rather
/// than about any observer's first sight. No stamp means nothing on disk says
/// the step is young, and the wound stands.
///
/// **Only the unanswered classes** ([`Wound::Mute`]/[`Wound::Spoke`]) wait:
/// they are the two whose truth depends on the stale half. A refusal and an
/// output limit are settled on disk the instant they are written. Whole
/// seconds, rounding **down**: the grace bounds how long a healthy call may
/// look wounded, never how long a real wound is held back. A **zero** grace is
/// therefore no window at all rather than a special case, and a stamp *ahead*
/// of the caller's clock reads as an age of zero rather than a negative one —
/// §7.2's own convention for a derivation stamped after the reader (a clock
/// wound back, two boxes a second apart), and the conservative arm either
/// way.
pub
/// The agent's **latest** step's wound — the §11 Altitude-1 banner's input,
/// read off an already-built [`super::StepsView`] (the one owner of the
/// per-step reading) exactly as the Login banner reads its own. It takes the
/// view, not the disk: the shell declares one standing `Query::Steps` that this
/// banner and the Steps tab share (REMOTE §9.7, bl-13f9), and a predicate that
/// re-read the whole steps tree per frame was the chat pane's frame-time cost.