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
//! Names, at the two altitudes §3.1/§3.3 put them at.
//!
//! **A workspace name is the operator's** (§3.1, bl-df65): typed at the
//! New-workspace affordance, or the fixed [`DEFAULT_NAME`] the empty-world
//! bootstrap uses without asking. Nothing mints one. [`validate`] is what the
//! wordlist used to guarantee by construction — the shape, the length, the
//! reserved literal, and the leaf collision — and it governs **creation only**:
//! enumeration classifies by path and never validates, so pre-reversal minted
//! leaves and foreign leaves stay lawful names.
//!
//! **A conversation name is minted** (§3.3, one word since bl-d12f): a single
//! word from an embedded wordlist — `gecko`. The mint is a **pure function over
//! an injected RNG and an occupied set** ([`mint`]): one RNG draw picks a start
//! index into the wordlist, then the scan walks forward with wraparound,
//! discarding each occupied word for the next, to the first unoccupied name.
//! Collision retry is that scan; its bound is the wordlist itself — exhaustion
//! is the scan running the whole pool out ([`MintError::Exhausted`]). No retry
//! budget, no probabilistic termination, no unbounded loop. The occupied set is
//! the caller's (§3.3: the stamped names of the target workspace's live roots),
//! assembled never stored.
use HashSet;
use PathBuf;
use ;
/// The bootstrap default (§3.1): the empty-world start (§3.4) creates its
/// workspace under this fixed name. **A constant, not a config** — there is
/// nothing to delete for severability — and not a mint. Zero workspaces is the
/// only state that takes it, so it cannot collide locally, and the first Enter
/// meets no name picker.
pub const DEFAULT_NAME: &str = "home";
/// bl's terminal `--as` fallback (§3.1). A workspace so named would false-join
/// every unstamped claim, so it is the one literal creation refuses outright.
const RESERVED: &str = "unknown";
/// The §3.1 length bound — path-safe on every §10 target.
const MAX_BYTES: usize = 32;
/// Why a typed workspace name is refused (§3.1). Each variant's message is the
/// **operator-facing** reason the §11 form renders inline: nothing has spawned,
/// so a refusal is a sentence beside the field, never an ops wound.
/// How a typed name is **read** (§3.1, §3.6): surrounding whitespace forgiven,
/// nothing else. One reading, shared by creation's [`validate`] and deletion's
/// typed-name arming ([`crate::delete::Confirmation::armed`]) — so what the
/// operator may type to raise a sphere wall is exactly what they must type to
/// take it down.
/// The §3.1 shape, spelled as a split rather than a regex dependency: every
/// hyphen-separated segment non-empty and lowercase-ASCII-alphanumeric. An
/// empty name splits to one empty segment and fails here — the general path
/// with no input, not a bootstrap branch.
/// Validate an operator-typed workspace name (§3.1), returning the normalized
/// name to create under. `roots` are the three workspace roots
/// ([`crate::binding::roots`]): a name equal to an existing leaf under **any**
/// of them is refused outright — no suffixing, no prompt-loop, the operator
/// retypes. Equality with `$USER` is deliberately *not* refused (§3.1).
///
/// Collision asks only "does the leaf exist", which is wider than workspace
/// enumeration: a half-created dir still owns its name.
/// The embedded pool (§3.1). Provenance and licence are recorded in the file's
/// own header; it is data, so it ships in the binary via `include_str!`.
const WORDS_TXT: &str = include_str!;
/// The one way a mint fails: every word in the pool is already taken.
/// The injected randomness the mint is pure over. A trait rather than a
/// concrete generator so a test drives the mint with a scripted draw and the
/// production seeding stays out of the pure path.
/// SplitMix64 — the production [`Rng`]. Chosen because it is ~6 lines of
/// wrapping arithmetic: the mint needs one draw per name, and a `rand`
/// dependency for that is not worth the supply-chain surface (AGENTS.md rule 6:
/// zero new dependencies).
/// The embedded wordlist as words: non-blank, non-comment lines, trimmed.
/// The mint over an explicit wordlist — the whole algorithm, kept
/// list-injectable so tests exercise collision retry and exhaustion on a
/// tiny pool instead of the embedded one. The retry is bounded by the pool:
/// each occupied word is discarded for the next with wraparound, and one full
/// lap proves exhaustion exactly — no free name is ever missed, no loop runs
/// unbounded. Fallible reads (rule 4): an out-of-range index cannot occur, and
/// a missing word reads as empty rather than panicking.
/// Mint a name from the embedded wordlist (§3.3): the first single word not in
/// `occupied`, scanning from an RNG-chosen start. Pure — same RNG and same
/// occupied set, same name.