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
//! **`clients` is granted to the role that works, and never to machinery**
//! (DESIGN §8.6, bl-0460) — the third file of the one policy convergence
//! [`execute_ensure_workspace`](super::execute_ensure_workspace) already runs.
//!
//! bl-52b7 made the roster tool part of the **grant**: `Injection::tools`
//! declares nothing at all for a role whose `providers.yaml` `tools:` does not
//! name `clients`, because litany's permit gate is *in the grant OR injected by
//! the host*, and injecting beside a grant is granting. It is the right law —
//! a compactor whose grant is empty had `bash` refused and then ran thirty
//! shell calls on an enrolled machine through `clients {"op":"load"}` — and it
//! left every workspace on the wrong side of it: `clients` is **yog's** tool,
//! litany's shipped template cannot name it, so after bl-52b7 no role in any
//! workspace, new or existing, could reach a foot.
//!
//! # Why this and not the install-wide template override
//!
//! The override (`<world>/litany/template/providers.yaml`, litany's own
//! `TEMPLATE_OVERRIDE_DIR` seam) is the obvious home and it is unavailable in
//! both halves. It replaces a template file **wholesale**, and litany's
//! embedded template is not reachable from here — the crate exports `cmd` and
//! `mint` and nothing else — so yog would have to carry a hand-copy of a file
//! whose own header warns that a copy is drift: *"a name added to the pool and
//! forgotten here is grantless for everyone"*. And it reaches only workspaces
//! **not yet born**, while the defect is in every existing one.
//!
//! So the grant rides the birth-time fold yog already owns (the ruling's own
//! second option): one name asserted in one role's list, in the same single
//! `litany config` pass that authors §8.6's control block and §3.7's
//! instruction glob, **outside the create skip** — so a workspace made a moment
//! ago and one made last week both converge at their next start. Every other
//! byte of the file is the operator's, litany's tool names included.
//!
//! **It is not bl-7fc8's deleted grant path returning.** That one re-asserted
//! `message`/`dispatch` — names litany's template already carried — so it read
//! the role, found them present and authored nothing, a no-op kept alive by its
//! own stale comments. This asserts the one name litany's template *cannot*
//! carry, because the tool is not litany's. And it is not tool-name narrowing
//! either (VISION §4.11's rejection): it only ever adds, and what an invocation
//! may **do** is still adjudicated per call by the §8.6 capability control.
//!
//! **Machinery is never granted.** Only [`WORKER_ROLE`] — litany's *roots are
//! workers*, and the roles this door does not touch are the checkpoint pair:
//! the compactor, whose empty grant is the confinement bl-52b7 restored, and
//! the reviewer, whose grant is stated as its confinement in litany's own
//! template. A role an operator adds is the operator's own grant to write.
//!
//! # A grant and its description are one fact (bl-7d33)
//!
//! bl-0460 wrote the grant alone and that broke every conversation on main:
//! litany's *descriptions-always* rule (ARCH §3.3) refuses a **fork** whose
//! role grants a tool the governing config commit does not describe — before
//! any injection is consulted, so an injected tool is no exception. Its own
//! words for why are this module's argument back at it: *"`providers.yaml` and
//! `descriptions/**` disagree, and both live in that one commit"*. Every
//! `/prompt` answered `started` and died at the fork with
//! `no descriptions/tools/clients.json`. The two files move together now, in
//! the same pass, or neither moves — and the check is per file, so a workspace
//! already carrying bl-0460's half-written state converges at its next start.
//!
//! The schema written is [`clients::schema`] itself, which is the very value
//! [`ToolInjection::tools`](crate::tool_host) declares, so the committed
//! description and the wire declaration cannot disagree. It is the honest one
//! for the second reader too: since litany's bl-55b1 cut,
//! `descriptions/tools/` **is** the callable set an agent reads with `bash`.
//!
//! **The pin does not decide this.** litany 0.0.10 and 0.0.11 run the same
//! check with the same input — `clients` is yog's tool in both, and 0.0.11's
//! `BUILTIN_TOOLS` gains `remember`, not this — so the fix is the same
//! whichever version the manifest names.
use Path;
use crateDraftFile;
use crateWORKER_ROLE;
use crate;
use crateclients;
/// Worktree-relative home of the committed tool schemas (litany ARCH §3.3),
/// spelled here because litany's own constant is crate-private — the same
/// reason [`tool_host::grant`](crate::tool_host) spells the field names it
/// reads.
const SCHEMA_PATH: &str = "descriptions/tools/clients.json";
/// `workspace`'s drift on `config/<config>` — the grant and the description it
/// is worthless without, empty when the tip already carries both. That is the
/// steady state, which stages nothing and spawns nothing. The lineage is a
/// parameter for §8.7's reason: what a drone may call must be authored where
/// the drone forks.
/// The committed description of [`clients::NAME`]: the schema the injection
/// declares, verbatim, so the config commit and the wire cannot disagree.
/// Trailing newline because it is a committed text file.
/// Whether `text` grants the worker [`clients::NAME`] — asked of the *authored*
/// file, so a grant this pass is about to write counts as granted.
/// `base` with [`clients::NAME`] in `roles.worker.tools`. A **fixed point**: a
/// file that already grants it comes back byte for byte, and so does one this
/// has nothing to say about.
///
/// **A worker that declares no `tools:` at all is left alone**, and that is the
/// grant's own semantics rather than a gap: an absent list is a role granted
/// nothing, which is a thing an operator may mean, and inventing a list to put
/// one name in would be yog answering a question the file already answered. The
/// same holds for a `tools:` that is not the flow sequence litany writes, and
/// for a rewrite the grammar declines — every one of them is the operator's own
/// file, unchanged.