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
//! 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 the §9.3 lineage write reached by
//! two dropdowns over brazen's own facts.
//!
//! **It was two writes and is one** (bl-d9cb). The pick used to compose §9.2 and
//! §9.3 in a normative order, because litany's cross-check refused a config
//! whose `roles.<r>.model` was not declared in the global `models.yaml`. That
//! check is retired upstream (litany's bl-35e2): a role's `providers.yaml`
//! assignment is the single home of its (provider row, model id) pointer, so
//! there is one file to write and no order to get wrong.
//!
//! This module holds everything that can be decided without touching disk,
//! network or a face: the [`grammar`] both config files are read and written
//! through,
//! the [`query`] view-model over the live `bz --list-models` run, the [`pick`]
//! one gesture produces and the gates it passes, the [`header`] row the
//! conversation wears (the pair its
//! two dropdowns show, plus the §9.4 clause naming a conversation that resolves
//! something else), //! 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.
/// One operator choice, the three gates it passes, and the `providers.yaml`
/// text it produces (§9.4).
/// The §9.4 tuning pair — a role's effort level and its priority lane, the two
/// optional assignment fields litany 0.0.6 reads (bl-23bd).
pub
pub use ;
pub use ;
pub use ;
pub use ;
/// The role that talks to you (litany'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 — `litany 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";
/// 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 scope sentence the picker paints **at the point of change** (§9.4, as
/// inverted by bl-e654). It says the thing the operator would otherwise get
/// wrong, and what that is has changed sides: the write is still
/// workspace-wide, but the conversation in front of them does **not** keep the
/// policy it forked off — it follows this lineage's head, so the pick lands on
/// it at its next step along with every other conversation here.
///
/// `short_oid` is the head the pick advances **from**, which is what makes the
/// sentence checkable against the row beside it rather than a promise.
/// 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 conversations that already exist and will feel
/// this write at their next step, 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.** litany's `litany 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 the one file a click touches (§9.4), so the
/// operator can see what it is about to do before it does it.
///
/// It named two files and their order until bl-d9cb, and both halves of that
/// sentence were false at the pin: litany reads no global `models:` table, so the
/// declaration was inert and its ordering rule protected nothing.
pub const WRITE_NOTE: &str = "writes the role's provider and model into providers.yaml on this workspace's \
config branch, through `litany config`";