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
//! **The pairing invariant** (ARCH §2.3, §2.5, §3.3): a tool call and
//! the result answering it are **one unit**, and no cut may split them.
//!
//! Every provider validates the pair positionally. Anthropic refuses a
//! `tool_result` whose `tool_use` is absent (*"tool_result without
//! tool_use"*); the OpenAI Responses API refuses the same shape as
//! *"No tool call found for function call output with call_id …"*.
//! Neither refusal is recoverable in band: the history is a pure
//! function of the branch's tree (§5.1), so every later prompt carries
//! the same orphan and dies the same way — the branch is wedged, and
//! only an edit to the record or to what assembly composes gets it back
//! (bl-2d93; three live conversations died this way).
//!
//! A **tool window** is one model-output entry's `tool_use` blocks plus
//! the `messages/NNN-tool.json` entries answering them (§3.3). The
//! transcript cuts the harness makes are therefore held to one rule —
//! *a cut falls between windows, never inside one* — and this module is
//! that rule's one home, read from both sides:
//!
//! - **The write side.** [`unsettled_from`] answers where a transcript's
//! trailing unsettled window begins, over an ordered entry list and a
//! reader for one entry's blocks — the worktree's files at a fork
//! ([`super::step_commit::unsettled`], which deletes that tail), a
//! commit's blobs at the compaction landing
//! ([`crate::prompt::compactor::land`], which declines to sweep it).
//! Only the tail can be unsettled: the executor commits every result
//! before the next model call (§2.5 pairing), so one look at the end
//! is total. The other two cuts are already whole-window by
//! construction and need nothing here — a response cut at the output
//! cap is never sealed, so no entry is committed
//! ([`super::model_call`], `Error::OutputTruncated`), and a window a
//! stop or a crash felled is *settled* with one in-band `is_error`
//! result per unanswered call ([`super::tool_step::settle`]).
//! - **The read side.** [`drop_orphans`] refuses to compose an orphan
//! `tool_result` onto the wire at all, and names what it dropped so
//! the step record can carry it ([`super::assembler`]). The write side
//! stops new orphans; this is what revives a branch already carrying
//! one, at its next prompt rather than never.
use crateError;
use ;
use HashSet;
use Path;
/// What a transcript entry is, derived from its path alone (§2.3
/// *Origins and wire framing*): the extension and the reserved-token
/// test, never frontmatter.
pub
/// The one reserved `.json` origin token (§2.3): a tool call's result.
/// Every other `.json` token is the model id that authored the entry.
const TOOL_ORIGIN: &str = "tool";
/// [`Kind`] of the transcript-relative or worktree-relative `rel`.
pub
/// `paths` in transcript order — by the filename's `NNN` counter, which
/// is where order lives and nowhere else (§2.3). A name carrying no
/// counter contributes no entry. Sorted by the *parsed* counter rather
/// than lexically, so a branch past `999` — where the zero pad stops
/// making the two orders agree — still reads in the order it was
/// written.
pub
/// The `NNN` counter of a `messages/NNN-<origin>.<ext>` path.
/// The index in `entries` ([`ordered`]) at which the trailing
/// **unsettled** window begins, or `None` when the tail is settled.
///
/// The window is the branch's *last* model-output entry plus everything
/// after it; it is unsettled when some `tool_use` id that entry emitted
/// has no `tool_result` naming it among the following tool entries.
/// `blocks` reads one entry's canonical blocks from whatever the caller
/// is cutting — a worktree file, a commit's blob — and is called only
/// for the entries the answer depends on: the last model entry and the
/// tool entries after it.
pub
/// Drop every **orphan** `tool_result` from an assembled wire history —
/// a result whose `tool_use` no message before it carries — and answer
/// the ids dropped, in the order they were met (module docs).
///
/// A message left with no content at all goes with its last block: an
/// empty message is not a lawful wire message anywhere, and the entry it
/// came from is exactly the one that must not be sent. Its neighbours
/// then **re-group**, because removing a message from the middle can
/// leave two same-side messages adjacent and §2.3's framing is that
/// consecutive same-side entries are one wire message — the drop must
/// not hand a provider a second illegal shape in place of the first.
pub