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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
//! §12/§15 prime's rename convergence (bl-18bf piece 1) — the one MUTATION
//! prime performs to close version skew.
//!
//! `bl doctor` was built and burned (bl-77a7): every fix is an existing
//! idempotent verb, drift fails loud at point of use. That held for STEADY-STATE
//! drift; it never covered VERSION SKEW — a checkout healthy under an old binary
//! and stale under a new one. The catalog's lone semantics-preserving case is a
//! retired first-party plugin name (`renames::renamed_to`, the closed static map
//! — today `tracker` → `bl-tracker`, bl-27bf) still committed in a landing's
//! `config/plugins.toml`. The dispatch notice (bl-27bf, [`crate::plugin`]) was an
//! explicit stopgap "until its owner updates it"; prime acting as the owner's
//! hand IS that update — spelling correction on a closed, first-party-RESERVED
//! (§5/§6) map, never policy change (a retired name has exactly one meaning and
//! can never bind again).
//!
//! Convergence is [`converge`], called from [`crate::checkout::prime`] at ONE
//! site — after landing founding/rebind (and any adopt), before the prime chain's
//! [`crate::hooks::Hooks::effective`] read — so the op that rewrites also
//! dispatches the rewritten schedule and binds the fresh name in one breath (no
//! "run prime twice"). It:
//!
//! 1. rewrites every retired name across the `[hooks]` arrays AND `[source]`
//! keys through [`crate::conf::edit_landing_toml`] (a raw-`toml::Table` seal
//! that round-trips a team's foreign tables — NOT `Hooks::to_toml`, which
//! drops them), one ordinary landing commit, no-change = no commit;
//! 2. binds the CURRENT name to its sibling beside `bl` (the seed's own rule,
//! [`crate::seed::sibling`]) and drops the now-dangling old-name symlink (a
//! dangling link is not work — the one deletion converge is allowed).
//!
//! **Live-binding guard.** `bl-` is reserved but `tracker` is NOT, so a third
//! party may legitimately ship a live-bound `tracker`. Converge acts only on an
//! UNBOUND old name (`Registry::resolve_bin(old).is_none()`): a live-bound old
//! name is not our retired plugin, so its schedule entry and symlink are left
//! whole and dispatch invokes it as ever.
//!
//! **Cost (§the high-throughput constraint).** On the overwhelmingly common
//! converged checkout: ONE extra read+parse of the landing `plugins.toml` and a
//! scan against a static map — zero new subprocess spawns, zero new commits. Git
//! runs only when a retired name is actually present-and-unbound (once per
//! checkout, ever). Boundaries: LANDING only — an old name in the XDG layer is
//! the user's file (the dispatch notice stays its cover); adopt paths converge
//! their COPY-IN ([`rewrite_config`]) so a stale center cannot re-inject the old
//! name each cycle.
//!
//! §12.2 [`debris`] (bl-18bf piece 2, bl-3e5e) — the SAME module boundary, a
//! DIFFERENT contract: REPORT ONLY, prime deletes nothing here (what it names
//! may hold uncommitted work, and may belong to a LIVE op — bl-7f82, which is
//! why every line hedges rather than instructing). Three checks, one `readdir` +
//! one `exists()` on the clean path, returned as rendered lines the SAME way
//! [`crate::seed::seed_landing`]'s prune notes are (this layer stays log-free;
//! `prime` emits each line through the op log at `info` + stderr echo once it
//! exists, the bl-b1be idiom).
use ;
use fs;
use io;
use Path;
use ;
use crateCloneDir;
use crateRegistry;
use crate::;
/// Converge a version-skewed landing (§12.1): rewrite every retired-and-unbound
/// first-party name in the committed `config/plugins.toml` schedule to its
/// current spelling, seal ONE commit, then bind the current name to its sibling
/// beside `bl` and drop the dangling old symlink. `exe_dir` is the directory
/// holding `bl` (where the renamed sibling ships). A converged checkout no-ops:
/// no retired name present-and-unbound ⇒ no read past the parse, no git, no bind.
/// Apply the rename map to config a center is COPYING INTO a landing (adopt's
/// `--install`/`--center` copy-in, [`crate::adopt`]) BEFORE the seal — so a stale
/// center's `tracker` lands already spelled `bl-tracker` and re-adopting is the
/// no-op seal, not a rewrite commit each cycle. `change` is the staged change
/// worktree; `reg` is the landing's registry (the same live-binding guard). No
/// retired name present ⇒ the copied bytes are LEFT UNTOUCHED — no reserialize,
/// so identical adopted config still seals to nothing (§13).
pub
/// The retired-and-unbound first-party names present in this raw `plugins.toml`
/// root, mapped old → current. A name qualifies iff it is in the closed rename
/// map ([`renames::renamed_to`]) AND unbound here (`resolve_bin` is `None`, the
/// live-binding guard — a bound old name is a third party's, left whole).
/// Every plugin name this raw root references — string entries of the `[hooks]`
/// arrays and the `[source]` table keys (the two surfaces a retired name can
/// hide in). Deduped; any other shape contributes nothing.
/// Rewrite the retired names in-place: each `[hooks]` array entry is replaced by
/// its current spelling, and each `[source]` key is re-keyed (its hint carried
/// verbatim). Foreign tables and every other entry are untouched.
/// The landing-commit subject naming each rename applied (deterministic, from the
/// [`BTreeMap`] order): `balls: converge tracker->bl-tracker`.
/// The core-side crash-debris report (§12.2, bl-18bf piece 2): [`change_worktrees`],
/// [`stealth_lock`], then [`index_lock`], concatenated in that order. REPORT ONLY
/// — nothing here deletes; each line names the fixing command and `prime` carries the returned
/// lines into the op log at `info` + stderr echo, exactly like [`seed::seed_landing`]'s
/// prune notes. `landing` is the founded landing checkout (as passed to
/// [`converge`]); `clone` is this invocation's bundle (§1 [`CloneDir`]), the
/// scope `changes/` and `stealth.lock` both live under. A converged, debris-free
/// checkout costs one `readdir` + two `exists()` and returns empty.
/// Every `changes/<uuid>/` entry present at prime time ([`CloneDir::change`],
/// §1/§8). One `readdir`; a `changes/` directory that has never been created
/// (no op has ever run here) is not an error, just nothing to report.
///
/// **The line never claims the worktree is orphaned (bl-7f82).** It once did —
/// "crash debris — its op's teardown never ran", plus an unconditional `git
/// worktree remove` — and that claim is unprovable: a worktree is open from
/// [`crate::git::Anvil::open`] until teardown, which for `close` spans the whole
/// `close.pre` gate, so under fleet concurrency the common occupant of
/// `changes/` is a LIVE op. Observed 2026-07-26: an agent followed this advice,
/// removed a running `close`'s worktree, and that op's seal died on a vanished
/// cwd — a failure manufactured by our own report.
///
/// **Nor can anything here name the owning op.** The worktree is detached at the
/// store tip and stays there until the seal commits, so its HEAD carries the
/// *previous* op's §5 trailers, never its own; a stale base only means siblings
/// sealed while it worked, which is the normal state of a long op. There is no
/// op identity to ask the store about, and every candidate discriminator (base
/// staleness, HEAD reachability, tree dirtiness) misreads a live op in exactly
/// the window that matters.
///
/// So the line degrades to the honest report [`index_lock`] already gives for
/// the same hazard, in the same words: name the path and the removal, and
/// CONDITION the advice on no op running. Silence was rejected (a real crash's
/// debris must still be reported), as were an age bound and pid tracking (a knob
/// and new state, for a question the operator can answer and prime cannot).
/// Deletion stays a human act either way: an occupant may hold uncommitted work.
/// The retired `stealth.lock` (bl-9df0) at the clone root: a file written by
/// nothing and read by nothing today. Present while the landing sentinel does
/// NOT declare stealth ([`config::landing_remote`] reading anything other than
/// [`config::STEALTH_REMOTE`]) is the catalog's one silent-*publish* hazard — an
/// operator who declared stealth the old way is silently un-stealthed by the
/// modern remote ladder. SUPPRESSED (no line at all) once the sentinel already
/// reads stealth: the operator has already re-declared, and the file is inert
/// cruft rather than a live hazard. The wording claims only that the mechanism
/// is retired — core cannot see whether a remote actually resolves, so it never
/// claims a publish happened either way.
/// The landing repo's `.git/index.lock` (bl-3e89): git's own index lock, left
/// behind by any op killed mid-`git add`/mid-commit. It is the ONE piece of crash
/// debris the bl-ffbf re-runnable founding cannot overwrite its way past — `git
/// init` re-inits and the seed rewrites, but [`crate::substrate::found_landing`]'s
/// `git add -A` fails on the lock with git's raw error and no act converges it.
/// Deleting it here is forbidden by the same rule that makes this a report: a lock
/// may be LIVE (another process mid-op) and dropping it corrupts that op's index,
/// so prime names it and the removal and lets a human judge. Returned as an
/// [`Option`] rather than an `io::Result` because one `exists()` cannot fail;
/// [`crate::substrate::found_landing`] calls this too, so the ONE run that is
/// about to trip over the lock refuses in these words instead of git's.
pub