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
//! §4 config VALUES — the `EffectiveConfig`, read from the LANDING.
//!
//! Config's durable home is the landing (`balls/config`); it is NEVER read from
//! the store and NEVER layered down a trail (there is no trail — §12). The
//! EFFECTIVE config is the landing's `config/balls.toml` overlaid by the
//! per-machine XDG user file, with built-in serde defaults beneath. A center's
//! config reaches you only by `install` copying it INTO the landing (§6), where
//! it becomes local — so this read is the sole authority for what runs.
//!
//! [`EffectiveConfig::resolve`] is PURE over LOCAL checkouts: the caller hands in
//! the landing checkout and the XDG user-config path; this reads each
//! `config/balls.toml` and folds them per §4. It never fetches.
//!
//! §4 layers, INNERMOST wins (highest priority first):
//! 1. CLI flags — a documented seam (below)
//! 2. `$XDG_CONFIG_HOME/balls/config.toml` — `user_config`
//! 3. `config/balls.toml` on the landing
//! 4. built-in defaults — serde fills any absent field
//!
//! Merge semantics (§4): scalar/object fields — innermost layer fully replaces
//! outer (objects are NOT deep-merged). List fields — bare `<field>` = full
//! replacement; compose with `<field>_prepend` / `<field>_append` / `<field>_ban`.
//!
//! The §4 layer-1 CLI override is an unbuilt seam: no flag consumes `tasks_branch`
//! today, so wiring an argv layer here would be a consumer-less mechanism. When
//! a flag needs it, it composes as one more (highest) table.
use crateDEFAULT_TASKS_BRANCH;
use Deserialize;
use io;
use Path;
use ;
// The §4 TOML layer-merge primitives (a general utility shared with
// [`crate::hooks`]) live in a sibling; re-exported so consumers keep reaching
// `crate::config::{read_layer, layer_over}`.
pub use ;
/// The resolved §4 config — the built-in fields balls core reads. Other keys in
/// `config/balls.toml` are layered through the merge but ignored on projection
/// (serde drops unknown keys), so a team/plugin key round-trips through the fold
/// without core having to know it.
/// Refuse a `tasks_branch` that names the LANDING branch (§2/§4, bl-ac89). The
/// coincident name is structurally impossible — `config/` and `tasks/` are two
/// worktrees of ONE local repo, and git refuses a branch checked out twice — and
/// §4 independently forbids what it would mean: the landing is single-owner,
/// never pushed, never sync-merged, so it cannot double as the store. ONE
/// invariant, two doors: the read authority ([`EffectiveConfig::resolve`] — a
/// seeded, adopted, or hand-edited poison fails NAMED on every op instead of
/// wedging prime on a raw git fatal) and the `conf set task-branch` write
/// ([`crate::conf`], the log-level ladder-validation precedent).
pub
/// The §12 stealth sentinel — the one value the landing `task_remote` rung may
/// hold: "the store's remote is nothing, on purpose". Stealth is not a mode or
/// a lock file; it is federation's zero case, a value on the ONE remote ladder
/// (bl-9df0).
pub const STEALTH_REMOTE: &str = "none";
/// Read one string `field` from a LOCAL-TRUST TOML layer — the XDG user config
/// or a clone's `binding.toml`, read identically. The shared reader behind the
/// §12 store `remote` and the §4/§8 `clock_provider`, both NON-TRAVELING
/// per-machine/per-clone values (§4 — never carried by `install`). An absent
/// file/key ⇒ `None`; a malformed file ⇒ `None` too. `pub(crate)` so `conf`'s
/// provenance read ([`crate::conf`]) can read the SAME per-tier value this folds,
/// naming which tier answered (the `binding_remote`/`xdg_remote` precedent).
pub
/// The §8 op-clock provider ([`crate::clock`]) named in this checkout's
/// LOCAL-TRUST layer — the per-clone `binding.toml` `clock_provider` key, else
/// the per-machine XDG `config.toml`. A DIRECTLY-SET value (an absolute path, or
/// a PATH-resolved name), NOT a landing-config name bound via `install` (bl-cfe3
/// dissolved that — the clock is box-local, cosmetic, fail-open (§1/§4), so none
/// of the shared-schedule/RCE-consent rationale for the `bin/<name>` indirection
/// applies). It lives in the NON-TRAVELING layer, so a fetched config can never
/// smuggle a provider in — it is your own machine's setting (§4). Absent/malformed
/// ⇒ `None` ⇒ the system clock. `bl conf set clock-provider <value>` writes the
/// per-clone binding key.
/// The per-clone store remote named in this checkout's `binding.toml` `remote`
/// key — the §12 DURABLE tier between the landing stealth sentinel and the legacy
/// XDG remote (bl-d081). The store remote is a PER-CHECKOUT fact (which center
/// THIS clone tracks), so its authoritative home is the per-clone binding — local
/// state that never travels on `install` and can never shadow another repo's
/// store, the machine-wide-XDG footgun this layer replaces. Absent/malformed ⇒
/// `None`; `bl conf set task-remote <url>` writes it ([`crate::conf`]).
/// The per-machine store remote named in the XDG user config's `remote` key — the
/// §12 LEGACY tier beneath the per-clone binding (bl-d081). Kept READ-ONLY for
/// back-compat: a machine that wrote a global `remote` before the per-clone home
/// still resolves it, but new writes land per-clone, so one repo's setup can no
/// longer redirect every other repo's store. Absent file/key ⇒ `None`; a
/// malformed file ⇒ `None` too — the same file is read by
/// [`EffectiveConfig::resolve`], which surfaces the parse error, so this stays
/// quiet rather than double-reporting.
/// The per-checkout store-remote POLICY — the landing `balls.toml` `task_remote`
/// key, the §12 rung between the per-op flag and the per-machine XDG remote.
/// Today it legally holds only [`STEALTH_REMOTE`] ("declared stealth"): remote
/// URLs stay per-machine (§4), but the stealth policy is the CHECKOUT's, lives
/// in its config, and travels on `install` like any other team policy. A raw
/// read — [`remote_ladder`] enforces the legal value.
/// Resolve the EXPLICIT tiers of the ONE §12 remote ladder — per-op
/// `--remote` > landing `task_remote` > per-clone `binding.toml`
/// remote > legacy XDG `remote` — returning the explicit remote and whether
/// stealth is DECLARED. Consent given supersedes consent withheld: a per-op
/// remote outranks the sentinel for that one op. A declared sentinel STOPS
/// resolution — no binding/XDG fallback, and the stealth bit rides the binding so
/// the tracker skips even its implicit `origin` discovery beneath (the §12 "locks
/// the store local" promise, now derived per op from config instead of written
/// once to a lock file — bl-9df0). The per-clone binding outranks the legacy
/// per-machine XDG remote (bl-d081): a remote is a per-checkout fact, so the
/// per-clone home is more specific; XDG remains only a read-only back-compat
/// fallback. A landing value other than the sentinel is refused: a URL's durable
/// home is the per-clone binding, not the shared landing config.