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
//! Derive the dispatched agent's descriptor tree from its **governing
//! config commit**, filtered to its role's grant (ARCH §3.3, §5.1, §2.3
//! step 2).
//!
//! `descriptions/**` is snapshotted **whole** into the config commit —
//! every tool's schema and every skill's frontmatter the install
//! provides (§3.3 *Descriptions-always population*) — because one config
//! commit serves every role. That commit is the authoritative descriptor
//! set; an agent's own tree is a *derived* view of it, cut to the role's
//! `tools:` grant (§4.3), and the dispatch commit is where the cut is
//! made.
//!
//! **Derived from the config commit, never from the parent's tree.**
//! Pruning the forked-in tree instead capped every child's descriptors at
//! its dispatcher's: the tree a child forks off was already cut to the
//! *parent's* grant, so a chain of dispatches intersected grant after
//! grant and a child's own `tools:` could never widen one. Reproduced
//! (bl-a900): a sensor role granted `[slack_read, message]`, dispatched
//! by a worker whose grant lacked `slack_read`, composed a request naming
//! neither — the role's one instrument gone with no diagnostic anywhere,
//! because tools-list assembly (§3.3) intersects declaration against the
//! tree and drops what the tree does not carry. Deriving from the
//! governing config commit makes an agent's tree a function of *its own*
//! grant alone, so dispatch order cannot narrow it.
//!
//! **A grant with no descriptor is declined, not composed smaller.** A
//! role granting a tool the governing config commit does not describe is
//! a config fault — `providers.yaml` and `descriptions/**` disagree, and
//! both live in that one commit. It is refused at the fork, naming the
//! tool and the described pool, before a branch, worktree or inbox exists
//! — the validity-before-fork discipline of role validation (§4.3) and
//! the §6 budget gate.
//!
//! **What the cut leaves is exact.** A non-granted tool's descriptors
//! compose **nowhere**: the body walk skips `descriptions/tools/**` and
//! every tool-claimed skill description (§3.3 *two wire homes*), and the
//! tools array carries only what the role declared. Uncomposed worktree
//! bytes are ordinary — the manifest is the inclusion list (§5.1) — but
//! these ones purported to describe the callable set, and they are
//! reachable by `bash`, which is how the failure was found (yog
//! bl-55b1): an agent read `descriptions/tools/message.json`, concluded
//! the environment supported messaging, and spent many steps discovering
//! that its wire array said otherwise. After the cut,
//! `descriptions/tools/` **is** the callable set, answered from the
//! agent's own branch in one listing.
//!
//! **Standalone skills stay.** Only a skill some tool claims — one with a
//! `descriptions/tools/<name>.json` beside it — leaves with its tool. A
//! skill no tool claims composes as a path-framed head text block (§3.3,
//! §5.2) and is `load_skill`-able; it is granted by being present.
use crateError;
use crateGitRunner;
use Path;
/// Worktree-relative home of the committed tool schemas (§3.3).
const TOOLS_DIR: &str = "descriptions/tools";
/// Worktree-relative home of the committed skill frontmatter (§3.3).
const SKILLS_DIR: &str = "descriptions/skills";
/// A role grants a tool the governing config commit does not describe
/// (§3.3) — `providers.yaml` and `descriptions/**` disagree inside one
/// commit. Its own type, like role validity's
/// ([`crate::prompt::role::validate::Invalid`]): the decline's vocabulary
/// belongs with the check that raises it, and the pool it names is read
/// only here.
pub
/// What one dispatch commit cuts an agent's descriptor tree to (§2.3
/// step 2): a role, its `tools:` grant, and the config commit both were
/// read from. The three travel together because the cut means nothing
/// without all of them — the grant selects, the commit supplies, and the
/// role is what a refusal names.
pub
/// Cut the forked tree's descriptors to `grant`, derived from the
/// governing config commit: decline an undescribed grant, drop what the
/// grant does not cover, check out what it does.
///
/// Order is load-bearing only at the head — a fork that cannot be
/// described correctly changes nothing. Drop and check-out name disjoint
/// tools by construction.
pub
/// Decline the dispatch when the governing config commit carries no
/// schema for some granted tool (§3.3). Runs in any checkout onto the
/// workspace's object store, so a caller pre-flights it *before* the
/// fork and a refusal leaves no branch debris.
pub
/// Check out every granted tool's schema — and its claimed skill
/// frontmatter, where the commit carries one — from the config commit.
/// `git checkout <commit> -- <paths>` writes *and* stages, so the
/// dispatch commit carries them with no second `add`.
///
/// Unconditional over the whole grant rather than only over what the
/// forked-in tree lacks: the config commit is the descriptor set's one
/// home (`docs/PRINCIPLES.md` Single source of truth), so what the fork
/// point happened to carry is never consulted. An empty grant checks out
/// nothing — the compactor's shape.
/// Stage the removal of every descriptor the forked-in tree carries that
/// `granted` does not cover.
///
/// Issues **no** git command when there is nothing to drop — the shipped
/// default, whose `worker` grant is the whole pool (§4.3), and equally a
/// fork off a parent tip already cut to the same grant. Idempotent for
/// that reason, and `--ignore-unmatch` keeps a tool whose skill
/// frontmatter never snapshotted from being a failure.
/// The tool names this tree carries a schema for that `granted` does not
/// list, sorted so the staged removal is deterministic.
///
/// A tree with no `descriptions/tools/` at all yields none: nothing was
/// snapshotted there, so nothing is stranded. That is the ordinary case
/// for a child forked off a parent tip with a narrow grant, and for the
/// stub-git unit fixtures.
/// Does the config commit's tree carry `path`? (`git cat-file -e`.)
/// The tools the governing config commit *does* describe, rendered for a
/// decline ([`crate::name::pool`] — the "name the pool" idiom every
/// absent-name refusal shares). Read only when declining; a listing that
/// cannot be read renders as the empty pool, which is exactly what a
/// caller facing an undescribed grant must be told.
/// `descriptions/tools/<tool>.json` — the schema half of a descriptor.
/// `descriptions/skills/<tool>.md` — the frontmatter half, when a tool
/// claims one.