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
//! §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 (an orphan
//! worktree may hold uncommitted work). Two crash-debris 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): [`orphan_changes`]
/// then [`stealth_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` + one `exists()` and returns empty.
/// Every `changes/<uuid>/` entry present at prime time ([`CloneDir::change`],
/// §1/§8): crash debris from an op whose teardown never removed its own change
/// worktree (an op that finishes normally always does). One `readdir`; a
/// `changes/` directory that has never been created (no op has ever run here)
/// is not an error, just nothing to report. Each line names `git worktree
/// remove <path>` — never deleted here, because an orphan may hold uncommitted
/// work (the reason this is a report, not a prune).
/// 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.