yog 0.0.2

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# yog — Agent Operating Guide

You are working in **yog**, a single published binary crate: an egui desktop
window that drives `lernie` loops over `balls` tasks. Two authorities govern
your work and they do not overlap:

- **`docs/DESIGN.md` is the architecture authority** — what yog *is*, its
  invariants, module map (§12), and the world substrate it composes (§16). When
  code and DESIGN disagree, one of them is a bug; never invent a third answer.
  Do not implement a deviation silently — fix the doc (see the global AGENTS.md
  guidance: "Nothing is set in stone").
- **This file is the code-style authority** — the machine-enforced rules below
  and the repo discipline that surrounds them.

yog composes a **nested world** (DESIGN §16): it overrides `LERNIE_HOME` and
`XDG_STATE_HOME` under `$XDG_DATA_HOME/yog/world` and hands that env to every
child it spawns, so yog's `bl`/`lernie` substrate never collides with the
user's ambient tools (brazen's config/credentials/cache resolve **per
workspace** since the blast-radius ruling — nothing brazen-shaped
stays ambient, §16.2). If you touch spawn paths, env folds (`src/xdg`), or
`src/world/*`, read §16 first — the nesting is the point.

---

## Code-style rules (Rust Bootstrap v3, adapted to yog)

The house standard is **contained Rust**: complexity lives in function bodies
(local, compiler-caught), not in type signatures (viral). Prefer
clones / `Arc` / `Box<dyn Trait>` / enums over borrow-based APIs — the perf given
up was never why we chose Rust. The rules are flat-numbered so they are
mechanical to follow; most are machine-enforced by a pinned ast-grep rule
(`rules/*.yml`), the clippy manifest (`Cargo.toml [lints]`), or `cargo-deny`.
**Where yog deviates from the standard, the deviation and its reason are
recorded verbatim in the rule — read it before assuming a rule is absolute.**

1. **No named lifetimes.** `'a`/`'ctx`/… on a signature, struct, or impl leaks
   internal storage into the interface. Borrow on the way IN (elided), hand back
   OWNED on the way OUT. `'static` and `'_` are fine (they name nothing).
   Enforced: `rules/no-named-lifetimes.yml`.

2. **A `pub fn` returns an owned, concrete type** — never `&T`/`&mut T`/`&[T]`
   nor an opaque `impl Trait` (edition-2024 implicit capture makes `impl Trait`
   smuggle borrows invisibly). Return `String`/`Vec<T>`/a named struct, or an
   index the caller resolves. If the accessor is internal, demote to
   `pub(crate)` rather than clone-to-own. Enforced:
   `rules/no-pub-borrow-return.yml`.

3. **`unsafe` is confined, not forbidden. yog ADAPTATION:** the standard's
   `unsafe_code = "forbid"` is replaced by an ast-grep *location* rule pinning
   every `unsafe` to `src/cli_outbound/sys.rs` — one irreducible SIGTERM syscall
   in `Stream`'s drop. `forbid` is unoverridable and reaches test code, and a
   `nix`/`rustix` dependency or a crate split for ~10 lines of FFI is worse than
   a confinement rule. Enforced: `rules/unsafe-outside-sys.yml`.

4. **No panic paths outside tests.** `unwrap`/`expect`/`panic!`/`todo!`/
   `unimplemented!`/`dbg!` and unchecked `indexing_slicing`/`string_slice` are
   `deny` in the manifest; `assert!`/`assert_eq!`/`assert_ne!` are banned in
   prod (a `panic!` in disguise); `debug_assert!` is fine. Locks use
   `unwrap_or_else(PoisonError::into_inner)`; fallible reads use `.get()`/`?`.
   Tests get carve-outs via `clippy.toml` (`allow-*-in-tests`) and
   `rules/no-assert-outside-tests.yml`.

5. **No `#[allow]` in prod.** An inline `#[allow]`, a `#![allow]`, or a
   `#[cfg_attr(…, allow(…))]` hides a warning where it fires. Policy lives in
   the manifest (`Cargo.toml [lints]`) where it is reviewable and justified in
   one place — **that manifest is the only home for a suppression.** Test code
   may relax a lint. Enforced: `rules/no-lint-suppression.yml`.

6. **Dependencies are pre-approved + `cargo-deny`. yog ADAPTATION (amended
   §16.7 W10):** the standard's "rustls-only, no openssl" is now yog's rule
   **verbatim**. The former adaptation — "no TLS surface at all", justified by
   yog being a native GL desktop app with no network — died when the
   batteries-included wave embedded `brazen`, the LLM network adapter: yog's
   own process now makes the HTTPS calls, so `ureq`/`rustls`/`ring`/
   `webpki-roots` are load-bearing, not incidental. `deny.toml` still bans
   `openssl-sys` AND `native-tls`, which was always the standard's point — a C
   toolchain dep and a non-portable system bridge, either of which breaks the
   single-binary musl/macOS/Windows story rustls keeps. The license allow-list
   is exhaustive over the committed `Cargo.lock`; three transitive advisories
   are ignored with recorded reasons (each unreachable, fixable only by bumping
   the eframe 0.29 stack). **Zero new dependencies without explicit user
   approval.** Sources are **registry-only, with no exception in force**: since
   bl-89a4 all three embedded substrate crates (`balls`, `brazen`, `lernie`)
   are plain crates.io pins — **`Cargo.toml` is the pin authority and no
   version is restated here**, the restatement having gone stale once already
   — `deny.toml`
   has no `allow-git` list, and `make publish` works. The phase-2 ruling (DESIGN
   §16.7) still permits ONE interim exception — an embedded substrate crate
   pinned `version = "=x.y.z"` **plus** an exact `git`/`rev` while an upstream
   publish is in flight — but taking it re-blocks `make publish` (crates.io
   refuses git deps) and re-exposes yog to a rewritten upstream history
   orphaning the rev, so it is a last resort with a named exit. The pin must
   always be exact and lockfile-fixed; a `path` dependency is never lawful.

7. **`Mutex`/`RwLock` only in `src/state.rs`; no `Rc`/`RefCell` anywhere. yog
   ADAPTATION:** the lock chokepoint (`state.rs`) has three sanctioned
   carve-outs — the test scaffolding locks (`SPAWN_LOCK`/`ENV_LOCK` in
   `test_support`), `src/git_tree/probe_cache.rs` (a macOS 2 s TTL cache whose
   `Mutex` is uncontended single-thread interior mutability), and
   `src/fs_watcher/hub.rs` (the process's one `notify` instance and its fan-out
   registry, `OnceLock` singletons that are never dropped or handed out —
   bl-908c). Both code carve-outs share one reason: folding them into
   `state.rs` breaks llvm-cov's per-line coverage there, shifting the file's
   byte offsets and mis-attributing phantom uncovered regions onto its `impl`
   headers and type aliases. `Rc`/`RefCell` are banned everywhere, tests
   included (bare `Cell` counters are fine). Enforced:
   `rules/locks-outside-state.yml`, `rules/no-rc-refcell.yml`.

8. **Async is tokio-only, `#[async_trait]` mandatory. yog ADAPTATION:** yog runs
   **no async and no tokio today** — it is a synchronous egui frame loop over
   subprocess spawns. This rule is installed but **vacuous**; honor it if async
   is ever introduced, do not add tokio to satisfy a rule that currently matches
   nothing.

9. **No trait bounds on a `pub` item** — no `pub struct S<C: Clock>`,
   `pub fn f<T: Into<String>>`, nor a bounded `where`. A bound on the public
   surface forces monomorphization onto every consumer. Dissolve with a trait
   object or a concrete param; demote an internal bounded helper to
   `pub(crate)`. An *unbounded* `pub fn f<T>(x: T)` is fine. **yog ADAPTATION:**
   the shared time source is `Arc<dyn Clock>`, not `Box<dyn Clock>` — the caller
   that injects it (a test advancing a `FakeClock`, `main.rs` handing over a
   `SystemClock`) keeps a handle while the §7.2 sweep schedule holds its own, so
   it must be shared, not owned. Enforced: `rules/no-pub-generic-bounds.yml`.

10. **`thiserror` in libs, `anyhow` in the app. yog ADAPTATION:** `thiserror` is
    in place for the error enums; **`anyhow` is NOT a dependency** — `main.rs` is
    a thin entry with no error-plumbing layer that would justify it. Do not add
    anyhow.

11. **One crate per module boundary. yog ADAPTATION:** yog is a **single
    published binary crate**, not a workspace. The module tree plus the 300-line
    cap (below) already contain complexity; a crates split buys nothing here and
    would fight the 100% coverage floor. No `[workspace]`.

**What "pub" means for rules 2 and 9:** the `pub` surface is the *real* library
surface consumed by the `tests/` integration crate and `src/main.rs`. Anything
internal is `pub(crate)` — the ast-grep rules scan only bare `pub`, so an honest
demotion removes an internal API from the boundary's obligations. Reach for
`pub(crate)` before cloning-to-own or de-generifying a purely internal type.

---

## Repo discipline

- **Task tracking is `bl` (balls).** Run `bl skill` before using it. Session
  start is `bl prime --as YOUR_IDENTITY`, then `bl list`.
- **Claim → work → close, in the worktree.** `bl claim <id> --as ID` prints a
  `work/<id>` worktree; **every edit goes there**, never on `main`. `bl close
  <id> --as ID` folds `main` in, runs the pre-commit gate, squash-delivers, and
  tears the worktree down. Always pass `--as ID` — never let the model invent a
  name. A stray edit on `main` is invisible to the squash and is left behind.
- **Every child process is spawned through `git_env::command`.** `git` exports
  `GIT_DIR`/`GIT_INDEX_FILE` into every process it starts, and those OUTRANK
  `-C <repo>` and `current_dir` — so a child that inherits them forks its *own*
  `git` against the hook's repo. bl-0dff closed that for yog's direct `git`
  forks; bl-916a moved the scrub to the **spawn boundary**, where one
  `env_remove` clears the whole descendant process tree (`bl`, `lernie`, `bz`,
  an `$EDITOR` shim, the suite's fake substrate scripts). A bare `Command::new`
  outside `src/git_env.rs` is an error — `rules/no-bare-command.yml`.
  **`git commit` inside a `work/<id>` worktree is safe again:** the hook runs
  the suite, and the suite no longer writes to the outer repo (regression:
  `tests/git_env_scrub.rs`; verified by running the whole suite with
  `GIT_DIR`/`GIT_INDEX_FILE` pointed at a decoy repo, which stays untouched).
  Two residuals, both narrow: `make rules-audit` scans `src` only, so a bare
  `Command::new` added under `tests/` is on you; and a test binary that drives
  the embedded substrate **in-process** (`multiplex::dispatch`) must scrub its
  own process env from `git_env::INHERITED`, as `tests/multiplex_bl.rs` and
  `tests/multiplex_lernie.rs` do — no spawn boundary exists to do it for them.
- **300-line hard cap on every source file, inline tests included.** Docs and
  config (`.md`/`.toml`/`.yml`/`.json`/lock, `Makefile`, `LICENSE`) are exempt.
  Anything projected ≥200 is pre-split at design time (DESIGN §12), not at the
  cap — 200 is the aspiration the tree was swept to (bl-52f8), 300 the wall. **`make line-cap` is the one definition of the cap and of the exempt
  set**; the pre-commit hook and `make lint` both call it, neither restates it.
  It scans the **whole tree**, not the staged diff — the hook once checked only
  the files you happened to touch, which made the cap a sampling rather than an
  invariant (`src/app/balls.rs` rode at 308 lines undetected until an unrelated
  task edited it, bl-12dc). Over the cap? Split along a real seam and add the
  row to DESIGN §12; never shave lines to duck the limit.
- **100% test coverage, `cargo-tarpaulin` pinned 0.35.2.** `tarpaulin.toml`
  holds the config (excludes `src/main.rs` and `src/shell/*`); the hook and CI
  both run `--fail-under 100`. If it can't be tested, it mustn't be built.
- **The clippy pedantic allow-list lives ONLY in the manifest.** `Cargo.toml
  [lints.clippy]` runs `pedantic = deny` with a justified allow-list (currently
  13 entries in three tiers: the bootstrap five, six empirically-warranted for
  this egui GUI, and two site-specific false positives). Each entry carries a
  one-line justification. Never inline a suppression to dodge it (rule 5) — add
  a justified manifest entry via review instead.
- **`docs/DESIGN.md` is the architecture authority.** Amend the doc when reality
  diverges; do not code around a stale doc.
- **Never credit AI or tooling** in commit messages, code, or docs.

---

## The local gate

`make check` is the complete local gate and mirrors CI exactly:

    fmt-check → lint (line-cap + leak-scan + clippy + ast-grep scan + cargo-deny) → coverage

- `make lint` — `make line-cap` (sub-second, so it fails first), then
  `make leak-scan` (~5s), then `cargo clippy --all-targets -- -D warnings`
  (picks up the manifest `[lints]`), then `make rules-audit`, then
  `cargo deny check`.
- `make line-cap` — the 300-line cap over every tracked non-exempt file. Prints
  every offender at once, and fails if it enumerates *nothing* (a broken
  pattern must not pass silently — the same two-direction discipline as
  `rules-audit`'s fixtures). The cap is a parameter, so **`make line-cap
  LINE_CAP=199` lists the ≥200 pre-split band** — run that before you extend a
  module, not after. It is deliberately *not* a gate: 94 of 395 source files sit
  in that band today. A warning that fires on a fifth of the tree is noise, and a
  gate there is just the cap moved to 200. ≥200 is a **design-time projection
  rule** — it fires on the author about to add to a file, not on the file's
  existing state. The band is the aspiration, not the limit: bl-52f8 swept the
  tree so nothing rides the 300 wall, and nothing has since — **ask `make
  line-cap LINE_CAP=n` for today's census rather than trusting a count written
  here** (this line has been wrong before: it named `transcript/rows.rs` at 266
  as the tree's one ≥250 file long after bl-2335 split that file in two).
- `make leak-scan` — the disclosure gate (bl-fd5a, reworked bl-167d).
  `scripts/leak-rules.sh` is the one definition of what may not be committed:
  private keys, vendor API tokens, credential assignments, routable
  IPv4/IPv6/MAC addresses, **absolute paths under any home root on any
  platform** (`/home/…`, `/Users/…`, `C:\Users\…` — the house synthetic roots
  `/home/u`, `/home/op`, `/home/x` are the only account names that pass),
  email addresses outside the reserved documentation space, dialogue behind a
  speaker label, agent-session artifacts (vendor resource ids, Claude Code
  transcript keys), credential-shaped file paths, and **content no rule can
  read**. `scripts/leak-scan.sh` is the mechanism; findings are truncated to 12
  characters, because a finding must LOCATE a leak, never reprint it into a
  terminal or a CI log.

  **It reads index BLOBS, not the worktree.** `git checkout-index`
  materializes the index into a scratch tree and the scan reads that, so the
  bytes scanned are the bytes committed. The index rather than the diff, for
  the same reason `line-cap` reads it. Until bl-167d the scan enumerated `git
  ls-files` — path NAMES — and grepped the WORKTREE files they pointed at, so a
  leak that was `git add`ed and then overwritten with a clean copy on disk was
  committed unread.

  **Unreadable is rejected, not skipped.** `grep -I` silently passes binary
  files, which is the class most likely to carry a dump (archives, databases,
  PDFs, HAR captures, screenshots, executables). A tracked binary must be a
  regenerable derivation with a byte-for-byte test — `BINARY_ALLOWED` names
  exactly `assets/yog-*.png`, which `make icon` emits and
  `src/theme/icon/tests/artifacts.rs` pins.

  **The scan is never cached.** `scripts/pre-commit` runs it BEFORE consulting
  bl-speculate's verdict cache, so no stored verdict — including one imported
  from the remote builder — can let a leak through unread. (The cache's gate
  fingerprint is a fixed file list compiled into `bl-speculate` that cannot
  name the scanner; not being cacheable dissolves that rather than waiting on
  upstream.)

  Its regression half is `--self-test`, which the target runs first and which
  is stricter than `rules-audit`'s: every rule owns a fixture in
  `scripts/leak-fixtures/` where **every non-comment line** must be flagged
  **by that rule** — so one dead alternative inside a nine-way pattern cannot
  hide behind the eight still working — and must carry `FIXTURE_MARKER`
  (`notreal`), because no regex can tell a real secret from a fabricated one
  and only the value can say so. Plus `clean.txt` / `clean-paths.txt`,
  near-misses that must NOT be flagged. Both directions, because a leak gate
  dies by matching nothing, and a noisy one dies by being bypassed.
  `tests/leak_gate.rs` holds the other half: seven tests that drive the real
  scanner over throwaway repositories.

  **There is no allowlist and no per-rule path exemption, and nothing is
  exempt from the tree scan.** There was one — `docs/drive-logs/` was exempt
  from the home-path rule, on the argument that a drive log is evidence of a
  run on a real box and the path *is* the evidence. bl-244f burned those logs
  instead. bl-167d then removed the last two skips: the scanner and its rule
  table are scanned (they stay clean because **no pattern may match its own
  text** — see `leak-rules.sh` on the one that did), and each fixture is
  scanned by every rule EXCEPT the one it is the fixture of, a structural
  exemption keyed to the file's own name rather than an allowlist. **Fix the
  rule, not the coverage.**

  Writing a drive log is still QUALITY.md §3 step 6, and it stays lawful
  because `scripts/drive/logskel.sh` folds `$HOME` to `~` in every path it
  emits, at the one place the text is written. Hand-finish a log the same way.

  `.githooks/commit-msg` runs the same scanner over the commit MESSAGE, which
  `pre-commit` never sees. Run `make install-hooks` once to seat it.

  **The scanner scans the tree it is RUN IN, which need not be this repo**
  (bl-1043). It resolves its rule table from its own directory and the tree
  from `git rev-parse` in the working directory, so `cd <any git checkout> &&
  <repo>/scripts/leak-scan.sh` judges that checkout's index by this table. That
  is how the task store is gated below without a second copy of the rules.
- `make rules-audit` — `ast-grep scan src` (must be clean) AND a negative check
  that `ast-grep scan rules/fixtures` *fails* (proving every rule still bites).
- `make coverage` — pinned tarpaulin, `--fail-under 100`.

**Tool pins (must match, or the gate/CI is not reproducible):** rustc `1.95.0`
(`rust-toolchain.toml`), ast-grep `0.44.1` (`sgconfig.yml`), cargo-deny `0.20.2`
(`deny.toml`), cargo-tarpaulin `0.35.2` (`tarpaulin.toml`). Bump a pin only as a
deliberate, isolated change.

---

## What may never enter a ball body

`bl` keeps this project's tasks in a **separate git repository** — `tasks/*.md`
on the `balls/tasks` branch, pushed to the *same remote as the source*. A ball
body is therefore published text on a ref that goes public with the crate
(operator ruling 2026-08-13, bl-dd1d: the store is **scrubbed and published**,
not moved to a private remote). Nothing you write in one is private, and the
source gate has never seen a byte of it — `make leak-scan` reads the index of
*this* tree, and the store is not in it.

Write the reasoning; leave out the identity, the chronology and the machine
state — the same editorial rule bl-2368 applied to the source tree. None of the
following may enter a task title, body, comment or `-m` note:

- **Other people's names, handles and addresses.** Third parties, other
  operators, anyone who did not publish themselves. **The maintainer's own
  `mudbungie` identity and `mudbungie@gmail.com` are explicitly permitted** —
  that is the companion ruling on bl-dd1d, not an oversight: the handle is
  already public in `LICENSE`, `Cargo.toml`, the README and the release-plz
  owner guard, and `leak-rules.sh`'s `personal-email` rule excepts that one
  address on purpose. Every *other* address is a leak.
- **Verbatim transcript prose.** Operator dialogue, model output, an agent's
  own reply pasted back in. Cite the conclusion and the ball it came from — a
  conversation is content somebody said, and quoting it publishes them.
- **Live machine state.** Process ids, load figures, absolute paths under a
  real home (`/home/<account>`, `/Users/<account>`, `C:\Users\<account>`),
  workspace and wall names off a live world, host and device names. Cite the
  *shape*, not the instance: `/home/u` is a house synthetic root and passes.
- **Provider auth state.** Who is signed in to what, which credential exists or
  does not, billing and account-status text quoted from a provider. "The
  account cannot run jobs" is the fact; the provider's sentence about it is
  disclosure.
- **Conversation and session ids.** Vendor resource ids, Claude Code transcript
  keys, and the identifiers of a specific run on a specific box.

The gate below enforces the mechanical half of this list. The half no regex can
reach — an unlabelled paragraph of somebody's conversation, a third party named
in ordinary prose — is yours, and it is the half that actually leaked: bl-dd1d's
audit recovered no credential of any kind. What it recovered was private
context.

### The task store gate

`scripts/yog-leak-gate` is a **balls plugin** that runs `scripts/leak-scan.sh`
— the same script and the same `leak-rules.sh` table as `make leak-scan`,
because two copies of the rules drift within a week — over the store checkout,
and exits non-zero. A non-zero exit is the balls protocol's abort: the op is
refused and the plugins that already ran roll back in reverse, so the store
commit is un-sealed before `bl-tracker` can push it.

It hangs at `<op>.post`, not `pre`. A `pre` plugin runs **before** `bl` writes
the task file, so it would scan the previous state and wave through the very
body being added; `post` is the one window in which the ball exists and has not
yet been published.

Wiring is one act per checkout, and this repo cannot perform it — the plugin
schedule lives in the balls landing (`balls/config`), not in yog's tree:

    bl install --bin yog-leak-gate=<repo>/scripts/yog-leak-gate
    for op in create update claim unclaim close drop; do
      bl conf prepend $op.post yog-leak-gate
    done

`prepend`, never `append`: plugins run in list order and only the irreversible
belongs last, so the gate must sit ahead of `bl-tracker` (which pushes) and
`bl-delivery` (which squashes). Those six ops are exactly the ones this
landing runs `bl-tracker` on — *the gate goes immediately before the publisher,
everywhere the publisher runs*. It is severable: `bl conf remove <op>.post
yog-leak-gate` deletes config, not code.

**Wire it only once the store scrub (bl-dd1d) has landed.** When this gate was
written the store tip tripped `home-path` on nine live task files — bl-dd1d's
own body among them, because it cites the paths as evidence — so wiring it
first would refuse every `bl` op on the box, the scrub's own edits included.

**What it cannot do.** It stops the accident, not the author. The same agent can
`bl conf remove` it, or commit and push inside the store clone by hand, exactly
as `git commit --no-verify` defeats the source hook. There is no unbypassable
*preventive* placement to move it to: a git hook inside the store clone is
strictly worse (untracked, per-clone, re-founded by `bl prime`, absent on every
other box and silently so), and GitHub cannot interpose a check on a direct
push to `balls/tasks` — there is no pull request to require a status check on,
and a server-side hook is not a repo artifact, the same boundary bl-167d drew
for protected refs. The check the author *cannot* switch off is
`.github/workflows/store-scan.yml`, which scans the published ref daily, on
dispatch, and whenever the rule table itself changes. It runs after the push,
so it **detects**; the remedy for a hit is a history rewrite. Prevention is
local and bypassable, enforcement is remote and late, and stating that is worth
more than a gate that implies otherwise.

---

## Before making the repo or a crate version public

**A commit hook scans one tree.** `make leak-scan` reads the index it is about
to commit, and that is the whole of what it can promise. Everything below is
outside any hook's reach, cannot be made an invariant by one, and is therefore
a checklist run by a person once per publication — not a gate, and not
something the gate should imply it has covered (bl-167d).

Nothing here is automated on purpose: each item is a one-time judgement whose
remedy is destructive (a history rewrite, a yank, a rotation), so a green
checkbox would be a worse answer than a person looking.

1. **History.** The gate has only ever seen the tip. Sweep every reachable
   commit for the same material before the first public push —
   `git log -p --all` through `scripts/leak-scan.sh FILE...` on a checkout of
   each commit, or a purpose-built history scanner. A hit means a rewrite
   (`git filter-repo`) *and* rotation of whatever it named; the commit is
   public the moment the repo is.
2. **Other refs.** Tags, `speculation/**` branches left by a crashed
   `scripts/speculate-gate` driver, and any `work/bl-*` branch that was pushed.
   Delete what should never have been pushed rather than scanning it twice.
   **`balls/tasks` and `balls/config` are on this same remote and publish with
   it** — the task store is a ref, not a private sidecar (bl-dd1d). Run
   `store-scan` by `workflow_dispatch` rather than trusting the schedule was
   alive, and note that both the workflow and the store gate only ever see the
   TIP: the store's history is item 1's problem too, and rewriting it
   invalidates every existing clone.
3. **Commit messages.** `.githooks/commit-msg` covers messages written with the
   hook seated (`make install-hooks`). Messages older than the hook, and any
   written elsewhere, are not covered.
4. **Repository text nobody committed.** Pull-request titles and bodies, issue
   text, review comments, release notes, and the crates.io description and
   metadata. None of it is in the tree.
5. **Actions logs and artifacts.** A failed gate prints paths, hostnames and
   sometimes the offending line into a public log; `.github/workflows/
   speculate.yml` also uploads a `verdicts` artifact. Both survive the run.
   yog is a private repo today — the audit is what makes that reversible.
6. **Already-published versions.** `cargo publish` is irreversible: a yanked
   version stays downloadable. Audit the packaged file list
   (`cargo package --list`) before `make publish CONFIRM=yes`, not after.

## The merge queue — speculative closes, builds on GitHub Actions

The gate above costs minutes and closes serialize on it. yog rides balls'
speculative merge queue (balls `docs/design/bl-24e7-speculative-merge-queue.md`,
adopted in bl-1a5b): the gate consults a tree-keyed **verdict cache** first —
`scripts/pre-commit` exits in seconds when this exact worktree tree already
passed this exact gate — and speculative builds warm that cache ahead of the
queue, on GitHub Actions (`.github/workflows/speculate.yml`), so the local
machine never pays the build. After your last commit in the claim worktree:

    bl-speculate enqueue bl-XXXX                      # seal work/bl-XXXX into the queue
    bl-speculate run --gate scripts/speculate-gate    # builds run on GH Actions
    bl close bl-XXXX --as YOU                         # cache hit → seconds

Facts the queue derives from (do not fight them):

- **Sealing is the tag** `merging/bl-XXXX` on your tip. Any new commit unseals
  you; re-running `enqueue` re-seals at the *bottom* of the queue. That is the
  whole eviction mechanism.
- **A conflict or FAIL verdict ahead of you stops the chain** — the fix belongs
  to that branch's owner. Your close still works; it just pays the stock gate.
- **Everything degrades to the stock local gate.** No binary, no verdict, no
  network, no runner: the cache misses honestly and `bl close` builds locally.
  Never wait on the remote to close.
- **The gate fingerprint is content**: `scripts/pre-commit`,
  `scripts/check-line-lengths.sh`, `scripts/check-coverage.sh`, `Makefile`,
  plus local `rustc -V`. Editing any of them invalidates every stored verdict
  (deliberately). `rust-toolchain.toml` pins the toolchain on both sides —
  bump it in lockstep or remote verdicts silently stop matching.
- A crashed `scripts/speculate-gate` can strand a `speculation/<sha>` branch
  on origin; sweep with `git push origin --delete speculation/<sha>`.