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
//! **Landing convergence** (DESIGN §16.3, bl-7e54): the repair for a landing
//! yog's world founded *before* balls' config home was nested.
//!
//! bl-e47b nested balls' config home at the `bl` seam ([`super::bl::edge`]), so
//! `Xdg::default_config()` now resolves inside the world and falls through to
//! balls' EMBEDDED default. That fixed **founding**, and founding only: a
//! landing's `config/plugins.toml` is seeded ONCE (`checkout::prime` seeds only
//! when `substrate::is_landing` is false, and `seed::rebind` "never prunes or
//! rewrites the committed schedule"), and balls' own rename convergence cannot
//! reach this damage either — it rewrites a RETIRED name to its current
//! spelling, and here there is no old name left to rewrite, just an absent one.
//! So every landing founded against the operator's stale seed template stayed
//! silently local: no `bl-tracker` at any phase, so the store never fetched and
//! never pushed, and no `show` hook, so `bl show <id>` printed no worktree line.
//!
//! **The reframe that makes this yog's business at all.** balls draws its
//! convergence boundary at the landing on purpose — *"an old name in the XDG
//! layer is the user's file"*. Inside yog's world there IS no user file: yog
//! supplies balls' state home, its config home and its `exe_dir`, so a landing
//! under the world is yog's own generated artifact, exactly like the
//! `world/tools/` shims (§5.2). The shims converge on the way into every verb
//! ([`crate::world::tools::ensure_tools`]); this is the same rule one layer
//! down, for the same reason, and it lands at the same seam.
//!
//! **yog never restates balls' schedule.** The repair re-runs
//! [`balls::seed::seed_landing`] — balls' own embedded default, bound and
//! pruned against the world's tools dir by balls' own rule — so no phase name,
//! plugin name or hook order is ever written here. That is what separates this
//! from the rejected candidate 3 (a second implementation of the seed default
//! living in yog); the single source of truth stays balls'. The correct
//! long-term home is still balls itself — `bl prime` converging a schedule
//! missing first-party entries would repair every box, not just yog's — and
//! that ask is unaffected by this landing.
//!
//! **What the repair preserves.** Only `plugins.toml` is balls' to re-derive.
//! `balls.toml` holds the scalar knobs an operator may have set through `bl
//! conf` (`task-remote`, `log_level`, a landing-scoped `tasks_branch`), so it is
//! read before the re-seed and written back after — the repair restores the
//! capability schedule without spending anybody's configuration.
use fs;
use io;
use Path;
use Edge;
use Hooks;
use Message;
use Verb;
use ;
use crategit_env;
use cratetools;
/// The balls plugin binaries **yog's world provides** as `bl` siblings — the
/// world's own roster fact ([`tools::ROSTER`]), not a reading of balls'
/// schedule. `ensure_tools` seeds both before any verb reaches here, so balls'
/// seed can never prune either one, and a landing that names one of them
/// nowhere was therefore seeded against a template that is not the one this
/// binary carries.
const PROVIDED: = ;
/// The landing commit's subject when the repair rewrites a schedule.
const SUBJECT: &str = "balls: converge landing schedule";
/// Converge the landing this invocation addresses, returning whether it was
/// repaired. Idempotent, and cheap on the overwhelmingly common converged
/// landing: one `starts_with`, one `rev-parse` and one parse of `plugins.toml`,
/// then out — no re-seed, no git write, no commit.
///
/// Four ways out, in cost order: the landing is not yog's to converge; the clone
/// was never founded (a `prime` is about to seed it correctly, so there is
/// nothing to repair); its schedule already names every plugin the world
/// provides; or it does not, and balls' own seed re-derives it.
///
/// **The containment gate is the reframe's precondition, not a safety belt.**
/// This module may rewrite a committed schedule *because* a landing inside yog's
/// world is yog's own generated artifact — and that is true only of landings
/// inside yog's world. The world env is handed DOWN to a spawn rather than
/// re-composed here (see [`super::bl::edge`]), so a `yog bl` invoked from a
/// shell that never entered the world addresses the operator's **ambient** balls
/// state, where balls' own boundary rules and *"an old name in the XDG layer is
/// the user's file"* is exactly right. Converging there would be yog reaching
/// outside itself to rewrite a file it does not own — the §16.2 severability
/// promise inverted — so a landing that is not under `<yog-data-root>/world` is
/// left alone, however tracker-less it looks.
/// Name the site an [`io::Error`] came out of, keeping its `kind` so a caller
/// can still match on it and its own words so nothing is lost.
///
/// Every fallible step of this convergence is a read or a fork against a path,
/// and each of them can answer the *same* bare `NotFound` — which is exactly
/// what a rare macOS failure did answer (bl-1ce0), naming neither the step nor
/// the path, so a one-line warning out of [`report`] was undiagnosable. This
/// takes an already-evaluated `Result` rather than a closure on purpose: one
/// arm, one test, and a site label at each call instead of a per-site error
/// type nobody would match on.
///
/// **The label locates the fork; it does not promise the path is the fault.**
/// A `NotFound` off one of these forks has been the *program* rather than the
/// cwd — a peer thread's returning `exec` freeing the environment this fork's
/// `PATH` was read out of, so `git` itself could not be found while the
/// checkout named here existed the whole time (bl-2f8b; the mechanism and its
/// placement rule live at [`crate::git_env::exec`]). Rule that out before
/// reading the path as the complaint.
/// Re-derive the landing's capability schedule from balls' embedded default,
/// preserving the scalar config beside it. `seed_landing` writes BOTH files, so
/// `balls.toml` is carried across the call rather than protected from it — one
/// read and one write, no branch on which keys an operator might have set.
/// Seal the rewrite as one ordinary landing commit, in balls' own message
/// shape. Gated on a dirty tree, so a re-seed that reproduced the bytes already
/// there costs a `status` and stops — which is what makes the whole convergence
/// idempotent independently of the [`converge`] gate above.
/// Report a convergence outcome and carry on. A repair is **announced** — it
/// rewrote a committed file, so it must not be silent — and a failure is a
/// warning, never the verb's exit: the landing was usable enough to reach here,
/// and a repair that cannot run must not take the op it rode in on with it.
pub
/// Run one `git` in `cwd`, answering its stdout under a site naming the
/// subcommand — so a failure to *spawn* (an ENOENT off the `PATH` lookup or an
/// absent `cwd`, indistinguishable from a failed read in a bare error) reads
/// the same way as a failure to *succeed*, and both say which of the three
/// forks it was.
/// The fork itself, through the crate's scrubbing constructor. A non-zero exit
/// becomes the error carrying git's own stderr — the caller reports it and the
/// verb proceeds, since a repair that cannot run must never fail the op it rode
/// in on.