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
//! The model picker (DESIGN §9.4) — the pure half.
//!
//! yog's answer to *"I'm talking to gpt-5.4. How do I change that?"*. The
//! picker is not a fourth config editor: it is §9.2 and §9.3 **composed** by
//! one gesture, because lernie's cross-check makes a role assignment and a
//! model declaration two halves of one fact. Refuse either half and neither is
//! written.
//!
//! This module holds everything that can be decided without touching disk,
//! network or egui: the [`grammar`] the two files are read and written through,
//! the [`query`] view-model over the live `bz --list-models` run, the [`plan`]
//! one pick produces, the [`header`] row the conversation wears (the pair its
//! two dropdowns show, plus the §9.4 drift clause), the [`remedy`] an
//! auth-shaped roster failure routes to, and the
//! sentences the surface paints — kept here rather than in
//! the excluded shell so the scope claim the UI makes is testable, and so it
//! has exactly one home.
pub
pub use ;
pub use ;
pub use ;
/// The role that talks to you (lernie's `WORKER_ROLE`) — the one the
/// conversation's model row shows and writes, because that is the question
/// being asked; the picker's role strip re-scopes the same two dropdowns.
pub const WORKER_ROLE: &str = "worker";
/// The config branch a pick advances — `lernie config <ws>`'s own default
/// (§9.3). A differently-named lineage is edited through the §9.3 surface; the
/// picker deliberately offers no branch chooser. Named here rather than in a
/// frontend because both seats that fire a pick — the §11 pane and the §8.5
/// [`PickModel`](crate::boundary::Action::PickModel) variant — must name the
/// same lineage or they are two pickers.
pub const BRANCH: &str = "default";
/// The file the role assignment lives in, relative to the config checkout.
pub const PROVIDERS: &str = "providers.yaml";
/// Why a pick was declined (§9.4). Two kinds, because two things can be wrong:
/// a file is not the block shape yog edits ([`GrammarError`]), or the pick
/// names a provider row brazen does not have.
/// Whether a model id can be written as a two-space block key in both files.
/// Blank, or carrying whitespace / `:` / `#`, and the anchored grammar would
/// emit a line it could not read back — so the pick is refused instead.
/// Where the picker's provider dropdown lands, **and what it had to leave
/// behind to get there** (bl-bd89, amended by bl-dd7f).
/// The provider row the picker queries and writes for a role (bl-bd89): the
/// role's own row when brazen has it, otherwise brazen's first row.
///
/// A role stranded on a row brazen no longer has — renamed, or dropped from
/// `config.toml` — is precisely the state the picker exists to leave, and
/// asking a dead row for its models can only re-report the strand. An **empty**
/// table is no answer rather than an empty one (brazen could not be asked), so
/// it steers nothing and the role's own row stands.
///
/// **The substitution is a fact the caller is told, not one it has to notice**
/// (bl-dd7f, ruled at bl-9b52). It used to return the bare row, so a
/// conversation whose first turn died on `unknown provider \`openai-chatgpt\``
/// showed a picker reading `anthropic` — brazen's first row — and nothing said
/// the swap had happened: the operator read the picker as a report of what ran.
/// Steering is still right; steering silently was not. So the answer carries
/// both halves ([`Scoped`]) and the seat says the second one.
/// The two file texts one pick produces (§9.4). `models_yaml` is `None` when
/// the id is already declared on the picked provider row and nothing needs
/// writing there.
///
/// **Order is normative:** the caller writes `models_yaml` first. A model
/// declared with no role naming it is inert; a role naming an undeclared model
/// bricks every step in the workspace, because the config load fails before the
/// model is ever called.
/// One operator choice: give `role` this `model` on this provider row.
/// Compose the two writes for one pick, or decline (§9.4). The provider row is
/// checked **first** (bl-bd89) and the `models.yaml` half second, so neither a
/// dead row nor a file the grammar cannot read leaves half-written state to
/// recover from.
///
/// `rows` is brazen's effective provider table; an empty one is no answer and
/// gates nothing, on the same terms as [`grammar::unknown_rows`].
///
/// `served` is the context window brazen published for this model on this row
/// (`None` where the provider publishes none) — the seed for the declaration,
/// so a window the roster already carried is never overwritten with a guess
/// (bl-848f). It reaches only the `models.yaml` half, and only a new entry.
/// The scope sentence the picker paints **at the point of change** (§9.4). It
/// says the thing the operator would otherwise get wrong: this advances a
/// workspace-wide config branch, and the conversation in front of them keeps
/// the policy it forked off.
/// The scope sentence the same picker paints when it is opened from the §11
/// **birth-config block** — the surface for a conversation not started yet.
///
/// [`scope_sentence`] is about a conversation already frozen ("this one stays
/// frozen at …"), which is not a fact the birth block has. What the birth block
/// must say instead is the one thing the operator would otherwise get wrong:
/// **there is no per-conversation pick to make.** lernie 0.0.3's `lernie prompt`
/// takes no config argument and resolves the head of `config/<branch>` itself,
/// so a start-time pick *is* the workspace default moving — the same write the
/// §9.4 picker always did, made one gesture before the start instead of after it.
/// The note beside the write, naming both files and their order (§9.4), so the
/// operator can see what a click is about to do before it does it. It says
/// which of the two generated fields is a guess and which is not (bl-848f): the
/// context window is the provider's own wherever the roster served one, and a
/// declared default only where none was.
pub const WRITE_NOTE: &str = "writes models.yaml first (capabilities are a declared default; the \
context window is the number this provider served, or a declared default \
where it serves none), then providers.yaml through `lernie config`";