yog 0.0.3

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
# yog — The Remote Seat (client/server split)

Status: normative for the split; direction adopted by operator ruling
2026-08-13 (bl-b9a2). §9 is the build sequence and each step lands under its
own ball; steps 1–8 are in the tree, and §9.7 records what step 8 opened and
what it left. `docs/DESIGN.md` remains the
architecture authority for the engine; this document governs the wire, the
client, and the trust model. Where the two collide, DESIGN wins until DESIGN is
amended. The same amendment doctrine applies here: prose is replaced, the ball
id is cited, the path to the ruling is not narrated.

---

## 1. The ruling

1. **The file religion is authoritative for the server side.** The engine keeps
   every DESIGN invariant (I0–I9): disk is the bus, the world converges over
   files, all durable state lives in the world. The server is the only process
   that touches the world.
2. **The UI operates entirely via RPC.** The window is a pure client of the
   control boundary (DESIGN §8.5). **One method, one channel: even the local,
   single-machine version does the hard split** — the window talks to the
   engine over the same wire a remote seat uses. There is no in-process face
   and no file-transport fallback for a seat; a second execution path would be
   a second implementation, which VISION §8 refuses.

   **Executed rather than aspirational, by operator ruling 2026-08-14
   (bl-ae05): the local window is a wire client of localhost.** The real
   socket, the real handshake, the real certificate — everything through the
   front door. The cheaper option §9.7 offered, an in-process *transport* to
   the one `Answerer`, was weighed and declined, so §11's rejection of a second
   in-process face stands unlifted and nothing in-process was added. What the
   ruling costs is §1.4's *absence is the off switch*, and §8 records how that
   is paid: the engine's boot performs the same out-of-channel mint the
   operator's own act performs, on the operator's own box, before anything has
   been dialled. Nothing crosses a wire unauthenticated, and yog still links no
   certificate library.

   **Closed to the letter for the window's acts (bl-1747).** The reads crossed
   in bl-ae05/bl-adcb/bl-f297 and the acts in bl-4841/bl-1747; with the last
   four gestures posted, `AppModel::dispatch` — the frame's own way into the
   chokepoint — is deleted, and the only callers of `boundary::dispatch` left
   are the engine's own intakes. The residual is on the read side and is scope
   rather than architecture (§9.7): three standing classes, each blocked on
   named work, none on a decision.
3. **The channel is mTLS**, and the wrapper must be strong enough to leave on
   the public internet. Both ends authenticate with certificates.
4. **Bootstrapping is explicitly out-of-channel.** The starting assumption is
   an operator who administers both machines. Key and certificate provisioning
   is an act the operator performs on the boxes; yog carries no enrollment,
   pairing, or account protocol in the channel, ever.
5. **Client registration is per-workspace; the workspace is the trust
   domain.** A client registered in one workspace is invisible in another —
   the corporate machine participates in the corporate workspace without
   seeing the personal ones, and vice versa.
6. **Conversations are aware of registered clients and the tools they
   advertise.** Client presence and tool availability are rendered facts of
   the workspace and enter agent context.
7. **Agents run on the server, in the background**, independent of any client
   connection. Seats attach and detach; the work does not.

The canonical scene: a home server runs the engine and keeps every log; a
phone seat talks to a conversation; the conversation teleoperates a work
laptop through the tools that laptop's client advertises into its workspace.

## 2. Nouns

lernie bans "session" (DESIGN §1), and the transport/connection collision is
exactly the reason — the word appears nowhere here. The split adds three nouns
and reuses one:

| Noun | Definition |
|---|---|
| **client** | A machine holding an operator-issued certificate. One certificate = one client identity (its leaf name). A client is a fact about a machine, not a person — v1 has one human, and every certificate is operator-grade within its registrations. |
| **seat** | A client connection acting as an operator face: it asks queries, paints replies, dispatches gestures. (The word already names GUI/headless/line faces in DESIGN §8.5 — a remote seat is the fourth face of the same surface.) |
| **tool host** | A client advertising tools into the workspaces it is registered in. One client may be seat and tool host at once (the work laptop usually is). |
| **registration** | The durable fact that client C participates in workspace W. Server-side, in the world, a file — the file religion applies; the wire only ever transports the gesture that writes it. |

## 3. The wire is the boundary — and adds nothing to it

The protocol **is** the existing gesture surface: `Act(Action) | Ask(Query)`
in, `Reply` out, in the JSON serialization the codec already defines
exhaustively (`src/boundary/codec.rs`, strict decode, compile-gated). The wire
is a transport for that surface, not a vocabulary:

- **No wire-only verbs.** A capability a client needs and the boundary lacks
  is added to the boundary — every face gains it — never to the wire alone.
  One dispatch surface, N serializations, never two implementations (VISION
  §8).
- **Framing** *(decided and landed, bl-b6fa — `src/wire/frame.rs`)*: **a
  big-endian `u32` byte length, then that many bytes of JSON.** A request is
  one frame; an answer is **N ≥ 1 reply frames followed by a zero-length
  frame**, which is the terminator. Length-delimited rather than newline: a
  reader never scans, so no property of the *encoder* is load-bearing in the
  *framing*; the allocation is bounded before it is made, so a peer on the open
  internet cannot make a reader grow to meet it; and a zero-length frame is not
  a JSON value, so nothing a payload can say collides with the terminator.
  **The streaming form is not a second form.** Every answer is a stream, so a
  follow-class read is the general path with more than one frame in it — no
  flag, no version, no second reader, and nothing to add when the first
  follow-class `Query` lands. Today N is always 1, because none is
  follow-class: the seat polls (below).
- **`Reply` gains a decode side.** Today the reply codec is encode-only. The
  client is in-crate (§8), so the hand-codec discipline extends to decode;
  serde derive stays a non-dependency (existing stance, `src/ui_state`).
- **Routing does NOT invert the ask, and that is the ruling** *(decided,
  bl-c907; landed, bl-024b — §5.3)*. §5 has the engine route an invocation *down* a
  tool host's live connection, which reads like the one place a server must
  speak first. It is not, and making it one would cost the property this whole
  section exists to keep. **The tool host stays the asker**: it rides a
  follow-class read for its next invocation — the general path with more frames,
  which the framing already carries — and posts each capture back as an ordinary
  act on the same surface. Nothing about the framing, the listener or a client's
  socket posture changes; the engine's *work* flows down a stream the client
  asked for.

  The alternative was weighed and refused. A true inversion — the server writing
  a request frame down an established connection — makes the channel
  bidirectional-symmetric: a second reader at both ends, a correlation id
  because two request streams now share one socket, and an `Answerer` **on the
  client**, which is a second dispatch implementation. VISION §8 refuses that,
  and §3's own "one dispatch surface, N serializations" refuses it here. The
  long poll buys the same capability with the readers that already exist.

  What it costs is a held connection and a thread parked on it per tool host,
  which is exactly the case the bullet below leaves open ("when a seat's ask rate
  exceeds human cadence") arriving from the other side: a tool host is not
  polling faster than a human, it is waiting. And it is the **first**
  follow-class read to have a consumer, which §10 named as the gate on minting
  one. The verbs it needs are boundary verbs like any other — a read that drains
  this client's pending invocations, an act that posts a capture — so every face
  gains them and the wire still adds nothing.

  **As built (bl-024b) it is four verbs, not two, and the second pair is the
  reason.** A tool host's two are what this bullet describes: `invocations`, the
  follow-class read, and `complete`, the act. But the *asking* side needed two
  as well — `invoke` and `capture` — because the engine's intake is **one
  thread for the whole world** (§8.5's deposit consumer), so a gesture that
  waited for a tool to finish would stop every other deposit converging for as
  long as the tool ran. `invoke` therefore queues and answers a handle at once,
  and the waiting is a poll the *driver* does, in a child process that has
  nothing else to do and is the only party that knows how long its call is
  worth waiting for. The follow-class read blocks a **connection** thread, which
  is a thread per tool host and nothing else's.
- **Cadence: the seat polls.** The window today adopts a snapshot per frame;
  a remote seat asks at human cadence and rides the follow stream for the hot
  path. A push/subscription channel is deliberately deferred (§10) — it is an
  optimization of the same surface, not a different one.
- **The disk inbox survives for in-world callers.** Agents drive yog through
  the `yog` PATH shim and the `gestures/` deposit inbox — same machine, same
  world, disk is the bus. The wire is for cross-trust-domain callers; the
  inbox is for the world's own residents. One boundary, two intakes, each the
  religion of its domain. (Certificates for every spawned agent would be an
  enrollment surface §1.4 forbids.) *(Landed, bl-b6fa: the two intakes are
  `yog gesture` and `yog seat`, and they share one argv reader and one
  answering function — `ConsumerCtx::answer` — so the second door opens onto
  the same room and can add no verb.)*

## 4. Identity, authentication, authorization

- **mTLS both ways.** The operator runs a private CA out-of-channel and
  issues the server certificate and every client certificate. Presentation of
  a valid client certificate is the entire authentication story: no
  passwords, tokens, or accounts in-channel, and therefore nothing in-channel
  to phish, rotate, or leak. An unauthenticated connection gets a TLS refusal,
  not a yog reply — the surface exposed to the open internet is the TLS
  handshake and nothing behind it.
- **Authorization is registration.** A connection may see and gesture into
  exactly the workspaces its client identity is registered in. Everything
  else is not "forbidden" — it is **absent**: enumeration replies simply do
  not contain unregistered workspaces, the same shape as a workspace that
  does not exist. Scope errors that confirm existence are a disclosure.
- **Unscoped gestures.** Reads enumerate the registered set. A workspace
  created over the wire auto-registers its creating client. The first
  registration on a fresh server is out-of-channel like the certificates: the
  operator writes the registration file on the box. (A special "first client"
  flow would be the bootstrap-in-channel §1.4 forbids; the general path with
  an operator-seeded file dissolves it.)
- **Revocation** is deletion: remove the registration (per-workspace) or
  distrust the certificate at the CA (per-client). A lost phone costs a
  certificate, never data — the client holds nothing durable (§6).

### 4.1 The registry, as landed (bl-8bbc)

**A registration is a file, and its existence is the fact.**

```text
<yog-state-root>/clients/<client>/pane.json          the §7 pane-of-glass facts
<yog-state-root>/clients/<client>/workspaces/<name>   one empty file per registration
```

Registering writes the file, **revocation deletes it**, and the registered set
is the directory listing — nothing stored that the path already says. It sits
at yog's own state root beside `ui.json`, not under `wire/`: `wire/` holds what
yog can never mint (§8) and this holds what only yog ever writes. The operator's
bootstrap is therefore `mkdir -p` and `touch`, which is the same out-of-channel
act §1.4 already requires for the certificates.

**No gesture manages registrations, and none was added.** §3's ban is absolute
— a capability that exists on the wire and nowhere else is forbidden — and the
whole management surface here is the operator's own file acts, which are the
same out-of-channel acts §1.4 already requires. Seating a *second* client in a
workspace is one `touch`; de-scoping one is one `rm`. The read side is §5's
client-management tool, and that belongs to step 7 with the advertisements it
lists. Minting a boundary verb before that step would be a verb with one caller
and no face.

**The identity is the certificate's subject common name** (§2), read off the
presented leaf by a structural DER walk — yog links no certificate library.
A fingerprint would have been cheaper and wrong twice over: unreadable in a
`clients/` listing, and changed by a renewal, silently de-scoping every
registration the operator wrote. `local` is **reserved** for the
**certificate-less in-world callers** (§3) — the `gestures/` deposit inbox and
`yog gesture` — which hold no certificate but do own a pane document; a
certificate claiming it is refused on the same rule that refuses `.` and `..`,
so the reservation is one rule and not three special cases.

**The window is no longer among them** (bl-ae05). §1.2's ruling gives it a leaf
of its own, `yog-window` (`registry::WINDOW` — one const, spent by the mint that
puts the name on the certificate and by the seating that writes its
registrations), so it is identified exactly as a phone seat is: by the common
name on the certificate it presented. It is scoped like any client, it appears
in its own workspaces' rosters, and its §7 pane document keys on that leaf. The
reservation narrowed rather than dissolving, because the deposit inbox and
`yog gesture` still hold no certificate and still must not be scoped — each
intake the religion of its domain.

**Seating the window is the engine's own act, and it is the general path.** §4
already has the first registration on a fresh server performed out-of-channel by
the operator; this is that act, performed by the engine for the one client it
*is*. The asker seats `yog-window` in every workspace the published derivation
enumerates, on every pass — idempotent, one directory read when nothing is new —
so a workspace founded while the window is up is registered within one cadence
period. No create to detect, no bootstrap flow, and the same `mkdir`/`touch` an
operator performs for a remote client, performed by the process that already
holds the enumeration.

**Scoping is one filter, not twenty checks.** The engine narrows the published
derivation (`Snapshot::scoped`) to the client's registered workspaces before
anything runs, at the same chokepoint §8's name resolution already stands at.
That is what makes absence *structural*: the roster maps the narrowed set, and
`ws_path` resolves over it, so an unregistered name earns the resolver's own
`unknown workspace "x"` — the identical bytes a name nobody ever founded earns.
There is no scope branch to write, so there is no scope error to leak. The
narrowing is exactly the workspace-keyed facts, because §1.5 makes the workspace
the whole trust domain and §11 keeps finer policy rejected.

**Auto-registration needs no create-detection.** Under scope a gesture can name
only a workspace the client is registered in — or one it just founded, which is
the single case the resolver could not resolve and the raise founded anyway. So
a *successful* answer naming a workspace outside the scope is, by construction,
a creation, and seating the client in it is the general path rather than a
branch.

**The raise, and what it costs.** `Action::Prepare` names a workspace that need
not exist: the resolution falls back to `<names-root>/<name>` (§3.1) and
`Step::EnsureWorkspace` founds it — which is what the window has always done by
handing `prepare` a path directly, and what no other seat could do while the
resolution refused every name the enumeration lacked. It can only **found**,
never join: a directory already at that path refuses with the resolver's own
sentence, so a create is never a way into a workspace the scope hides.

That refusal is the one place existence is observable to a scoped client, and
the ruling is that this is acceptable and bounded: **a namespace with creation
*by name* cannot also make a name's availability unknowable**, and §4 chose
creation. What a collision reveals is that a name is taken. It reveals no
workspace's contents, conversations, clients or registrations — everything §4
makes absent stays absent.

## 5. Tools follow the client

- **Advertisement is durable; presence is live.** (Amended by bl-bc7c; the
  original ruled advertisements connection-scoped RAM, which put a
  connectivity-rate fact inside the model's cached context prefix.) A tool
  host presents its tool set — name, description, input schema — when it
  connects; the engine writes it into the client's registration (world, file)
  when it differs from what is stored. **Presence** — connected right now —
  is connection-scoped RAM. Two facts because two rates of change: a tool set
  changes when the operator reconfigures a machine; presence changes with
  every network blip.
- **Context is structurally append-only, so client facts are point-in-time.**
  The model's stable prefix carries exactly one client-facing surface: a
  **client-management tool**. Its operations: `list` — the workspace's
  registered clients and which are live, now; `get` — one client's detail and
  the tools it advertises. Every reply is a dated observation appended to
  context, free to go stale, never a prefix mutation — a presence flap
  cannot touch what the model already read, and the prompt cache (keyed on
  the prefix) survives every blip.
- **Loading is the agent's own point-in-time act.** From a `get`, the agent
  loads a client's tools; loaded definitions are callable from that turn on.
  Whether the driver re-declares them in the tool block (one deliberate,
  paid prefix rebuild at a moment the agent chose) or carries them
  append-only was left to the lernie seam; the invariant
  is that nothing but an explicit load ever changes the tool surface.
  **Settled by bl-c907 (§5.2): re-declared.** The set is a durable document the
  injection reads at every assembly, so the rebuild happens once, at the step
  after the load, and the agent chose it.
- **Use is attempt.** A loaded tool, when called, is attempted: routed to
  the client, and refused in band when it cannot be — an error tool result is
  an appended message the model reacts to, never a prefix change. The same
  path corrects staleness: a client refuses a tool it no longer carries.

  **Presence is NOT the routing predicate, and that is bl-024b's amendment.**
  This bullet used to say *"routed to the client if it is live"*. It cannot be:
  a tool host dials per ask (§10) and holds a connection only while it is
  *waiting*, so it is absent for the whole time it is executing something — and
  a presence test would therefore refuse the second call of a busy host, which
  is the one host that is certainly there. The queue is the predicate instead:
  an invocation waits in the client's mailbox, and what makes a vanished client
  visible is the **caller's own deadline**, in band, never a hang. What *is*
  checked at the invoke is the staleness correction above — that the named
  machine advertises the named tool right now.
- The workspace surface renders its registered clients — present or absent —
  and each one's advertised tools, live: the seat sees the flap; the model's
  prefix does not.
- **Invocation path:** the agent's tool call hits lernie's tool seam →
  server-side adjudication (`yog tool-control`, unchanged, fails closed) →
  the driver queues the invocation in that client's engine-side mailbox → the
  tool host, waiting on its follow-class read, is handed it → the client
  executes locally → the client posts the capture back as an ordinary act →
  the driver's poll collects it. A routed invocation carries a deadline; a
  vanished client is a visible refusal, not a hang. **Nothing in that path is
  the engine speaking first** (§3).
- **Honesty about containment:** execution happens on a machine the
  adjudicator cannot inspect. Adjudication judges the invocation exactly as
  today; any containment beyond that is whatever the client enforces locally,
  and the design must not claim otherwise.
- **The driver-side seam was a lernie ask, and it landed.** lernie's driver
  executes tools; routing a designated tool to a remote executor needed an
  upstream seam. It is lernie 0.0.9's `Fx::tool_injection` (its
  `docs/DESIGN_TOOL_INJECTION.md`): **one** object carrying both halves — the
  definitions prompt assembly and the grant gate read, and the router the
  executor consults ahead of binary resolution — so a tool declared and not
  permitted, or permitted and not declared, is unrepresentable. An injected
  name outranks an elected one, the per-invocation disk record stays the
  executor's, and adjudication is untouched. yog owns the registry, the
  advertisement, the routing and the adjudication chokepoint; §5.2 is what it
  filled the seam with.

### 5.1 The advertisement, as landed (bl-4e08)

**The gesture is a boundary verb, and the wire adds nothing** (§3). `advertise`
carries one field, `tools`, an array whose element is exactly three facts:

```json
{"op": "advertise", "tools": [
  {"name": "Bash", "description": "run a command",
   "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}}}
]}
```

- **`name` is a single path component.** It is the handle the later load act
  addresses one tool by, and a name carrying a separator is a name that
  addresses a filesystem.
- **`description` is one string**, the tool host's own words.
- **`input_schema` is the JSON Schema, verbatim.** yog neither validates nor
  rewrites it: it is the host's statement to a model, and any narrowing here
  would be yog inventing a contract it does not own.

Nothing else. There is no version, no enable flag and no per-workspace list —
each would be a fact yog stores and cannot check.

**It names no client, and that is the gesture.** The identity a set lands under
is the **intake's** — the connection's certificate common name, read exactly
where §4's scoping reads it. A `client` field on the wire would let any
connection overwrite any other's set, which is the authorization the certificate
has already decided. An intake carrying no client identity — the `gestures/`
deposit inbox and `yog gesture` (both `local`, §4.1; the window carries a leaf
of its own since bl-ae05 and is not among them) — **refuses in
band**, with a sentence: a caller who typed this at a terminal made a category
error worth naming, not an authentication failure worth hiding. The threading is
the act-side twin of `ConsumerCtx::answer_as`: one `caller` on the dispatch
`Deps`, carrying who is asking and who else is connected.

**A name collision inside one client's set declines loudly**, naming the token;
a collision *across* clients is legal and ordinary — two laptops both offering
`Bash` — and disambiguating them belongs to the act that loads one.

**The document.** One file per client, beside its §7 pane document:

```text
<yog-state-root>/clients/<client>/tools.json   the advertised set, one JSON array
```

One document per **client**, not one per registration: a tool set is a fact
about a machine, and §2's registration listing already says which workspaces see
it, so writing the array under every registration would be one fact stored N
times. Its element spelling **is** the wire's — one encoder, spent by the codec
and the file alike, because a stored set and a presented one spelled twice drift
within a week. The engine writes it only when it differs from what is stored, so
a re-presentation on every reconnect touches no mtime. An unreadable or
undecodable document reads as the empty set, which is also what a client that
has never advertised reads as — no reader carries two cases.

**Presence lives in RAM and has no leave verb.** The listener takes a
connection-scoped guard the first time a connection names its client and drops
it when that connection ends, however it ends; the map is a **refcount** per
identity, because one client may hold two seats and the second closing must not
unsay the first. The `Mutex` sits in `src/state.rs` (the crate's lock
chokepoint) and the operations beside it in `registry::presence`.

**The roster is the read.** `Query::Clients {workspace}` →
`Reply::Clients([{client, present, tools}])` joins the three at the moment it is
asked — the §4.1 registration listing, the presence map, each client's stored
set — so nothing is cached and a flap needs no invalidation. It is scoped like
every other read: an unregistered workspace earns the resolver's own
`unknown workspace "x"`. The window paints those same rows from the same
function (`registry::roster`), memoized per derivation with the live set in the
key. The §5 **client-management tool** the model reads is a separate surface and
is not this — it is §5.2, and it is built on this read.

### 5.2 The tool, the load, and the client's own config (bl-c907)

**One tool, named `clients`, three ops.** Its subject is the roster, which is
why it is one tool rather than one per op — and why loaded remote tools still
surface as individually named definitions of their own. lernie's
`docs/DESIGN_MCP_BRIDGE.md` §6 ruling binds a host too: a generic
`call {client, tool, arguments}` would collapse the role grant, the grant gate,
the tool control and every future policy into one bit. The declared schema:

```json
{"name": "clients",
 "input_schema": {
   "type": "object",
   "properties": {
     "op": {"type": "string", "enum": ["list", "get", "load"]},
     "client": {"type": "string"},
     "tools": {"type": "array", "items": {"type": "string"}}},
   "required": ["op"]}}
```

- **`list`** — every client registered in this workspace, which hold a live
  connection *right now*, and how much each advertises.
- **`get {client}`** — one client's detail: its presence, each advertised tool
  with its description, and the name that tool would become callable under.
- **`load {client, tools}`** — those definitions become callable from the next
  step on.

Every answer opens with the instant it was observed at, because presence is true
only then and a line that did not say when it was read would be a claim about
now. Every refusal is in band and non-zero: an unknown op, an identity this
workspace has not registered (**absent**, §4 — the same sentence a name nobody
seated earns), a tool the client does not advertise, an engine that did not
answer. Nothing here ever mutates the prefix.

**A load resolves whole or not at all.** Every named tool must be advertised
right now; one miss refuses the whole act, because a partial load leaves the
model believing it holds a tool it does not.

**The presented name is `<client>_<tool>`, always.** §5.1 leaves the
cross-client collision — two laptops both advertising `Bash` — to the act that
loads one. Prefixing *conditionally* would make a tool's own name depend on what
some other machine advertises, so a name the model already learned could change
under it; one rule and no case. A composed name a provider's tool block would
refuse declines at the load, naming it.

**The loaded set is durable, and its document is**

```text
<yog-state-root>/loaded/<workspace>/<agent>.json
```

Durable because a driver is not: each step is a fresh process, so a set held in
RAM would be unloaded by the next hop and "callable from that turn on" would be
false. It sits under yog's own state root rather than in the workspace, which is
the conversation's git repository.

**The definition is frozen at the load act**, name, description and schema
together, and re-read from that document at every assembly — never from the
client's live `tools.json`. That is §5's own rule (bl-bc7c) rather than a
shortcut: a prefix that changed when a client reconnected would put a
connectivity-rate fact inside the model's cached context, which is the defect §5
was amended to remove. The staleness freezing admits is corrected where §5
already corrects it — *"a client refuses a tool it no longer carries"*, in band,
at the call.

**There is no unload in v1, and no inheritance.** The set belongs to the agent
that loaded it; a fresh conversation, and a freshly dispatched subagent, starts
clean and loads what it needs through the `clients` tool every agent always has.
Subtraction is a second act on the one surface §5 says nothing but an explicit
load may change, and nothing has yet needed it.

**Where the injection runs, and what it may touch.** It is installed by the
`yog lernie` arm (DESIGN §16.7 W11) at `Fx::tool_injection`, which puts it
inside the **driver** — a child process, not the engine. Two consequences, both
load-bearing. Declaring touches nothing but disk, so a slow or absent engine can
never change the prefix. Answering a `clients` op needs presence, which is
engine RAM by ruling, so it asks through the door §3 already reserves for the
world's own residents: `Query::Clients` deposited into the `gestures/` inbox and
its reply read back with the one reply codec. **No verb and no transport were
added** — the roster read is the one bl-4e08 landed. The child folds the same
state root the engine writes, because the world hands `XDG_STATE_HOME` down to
every process it spawns (DESIGN §16.2). Every wait carries a bound and ends
early on lernie's stop flag, which is the router obligation lernie states and
cannot enforce.

**The tool host's own config, and why the advertisement is derived from it.**
The far end of the wire holds one operator-authored document, out-of-world
because it describes *that machine* — `<yog-data-root>/tools.json`, the sibling
of `wire/` and of the world subtree, for the same reason the key material is
(§8): a reseed must not take an operator's file with it.

```json
[{"name": "Bash",
  "description": "run a command in a shell",
  "input_schema": {"type": "object",
                   "properties": {"command": {"type": "string"}},
                   "required": ["command"]},
  "command": ["/usr/local/libexec/yog-tools/bash-tool"],
  "cwd": "/srv/work"}]
```

The first three keys **are** §5.1's advertised element, verbatim; `command` and
the optional `cwd` are the local half. The client presents the advertisement by
reading this file and dropping the local half — one document, two readings — so
what a host offers and what it can actually run cannot drift, which is the whole
of why the config is not a second list beside the advertisement.

`command` is an **argv, spawned directly**. There is no shell and no
interpolation of the invocation's input into it: a shell would make the declared
schema advisory and turn an operator's config into a command-injection surface
for anything the model can type. The invocation reaches the command exactly as
lernie's own tool contract already delivers one (its ARCH §3.3): the
`tool_use.input` JSON on stdin, bytes on stdout, the exit code the verdict. So a
tool host's executable is the same kind of program a local pool tool is, and the
capture that comes back is the same three facts.

JSON rather than TOML for one reason: `input_schema` is JSON Schema carried
verbatim (§5.1), and any other syntax would make the operator transcribe it.

### 5.3 The routing leg and the client-side executor, as landed (bl-024b)

**Four boundary verbs, and the wire gained none of them** (§3). Two are the
tool host's — `invocations`, the follow-class read that waits for this
machine's next work, and `complete`, the act that answers one — and two are the
asking side's: `invoke`, which queues a call for the machine that advertised
the tool, and `capture`, which polls for what came back. All four are ordinary
gestures in all three serializations, typable at any seat, and the compile
gates (`codec`, `line::spell`, the dispatch and answer matches) are what
enforced it.

**The identity is the intake's at three of the four**, exactly as the
advertisement's is (§5.1): a connection drains its own queue, answers its own
invocations, and collects its own captures. A `client` field on the read would
let one connection take another's work. An intake carrying no client identity —
the `gestures/` deposit inbox and `yog gesture` (§4.1; the window is a
certificate-bearing client since bl-ae05) — **refuses in band**
on the two that are a tool host's, with a sentence. `invoke` is the exception
and names its client, because there the identity is the **addressee** rather
than the author.

**A handle that is not yours is absent, not forbidden** (§4). A completion
quoting an invocation addressed to another machine, and a poll on a handle this
caller never posted, both earn the sentence a handle nobody minted earns. A
refusal that confirmed existence would be the disclosure §4 excludes.

**One reply for one subject.** `invoke`, `complete` and `capture` all answer
`Reply::Routed { invocation, capture? }` — the slot **as it stands after the
call**, which is the `Marks` discipline (a receipt is a re-read, never an echo).
`capture` is absent rather than empty while the far machine still runs it, so a
reader never has to tell "not finished" from "finished saying nothing".

**The mailbox is RAM, beside the presence refcount, and swept.** An invocation
in flight is a fact about *this process for the seconds a tool takes*, not a
fact about the world, so it is not a file (`src/registry/mailbox/slots.rs`, the
fourth lock carve-out). A slot lives from the post until the capture is read,
and a post older than an hour is swept — a driver that died mid-invocation
costs one entry rather than a leak.

**A capture is text, and the transcode happens once.** A capture ends as a
model's tool result and a model's message is text, so the *executor* transcodes
its child's bytes at the one place bytes stop being bytes, and nothing
downstream carries an encoding case. A tool whose output is not UTF-8 loses
exactly the bytes no string can name, which is the trade every other §11 file
read already makes.

**The executor is `yog tool-host`, a client mode beside `yog seat`.** It reads
the §5.2 config, advertises the projection of it, and then loops: `invocations`
→ run → `complete`. It runs **serially** (one invocation at a time, which is
what makes a busy host absent) and it **does not reconnect** — a channel that
fails is an exit naming the failure, because restart policy belongs to the
supervision the operator's machine already has, and inventing one here would be
yog deciding how a box it does not administer runs a program.

**A tool runs under two deadlines, and they measure different things.** The
host's own bound terminates the child (SIGTERM then SIGKILL, the cascade the
crate already owns) and answers the shell's `timeout` verdict with a sentence;
the driver's longer patience stands behind it for the case where the whole host
process went away. Neither is a knob: an engine that has not answered is down,
and a tool that has not answered is working.

## 6. What lives where

Everything durable is the server's: the world, the logs (`ops.jsonl`), the
UI-state documents, the agent processes, the LLM calls (brazen in the engine
process), the sentry, the pilot, the clock. A client holds exactly two things:
its key material (operator-provisioned, out-of-channel) and RAM. Losing a
client loses nothing; the server's disk remains the single history.

## 7. Per-seat UI state

`ui.json` was one last-writer-wins document holding two kinds of fact. The
split cuts along that line:

- **Facts about the world** — seen watermarks, pins, acks — are operator
  facts, shared across every seat: one document, as today. (Attention answered
  on the phone must clear on the desktop; that is I0's whole point.)
- **Facts about the pane of glass** — panel sizes, collapsed sets, view knobs
  — become per-client documents keyed by client identity, held server-side so
  the client stays stateless and any two seats of the same client converge.

**As landed (bl-8bbc).** The world document stays exactly where it is,
`<yog-state-root>/ui.json`, so the §7.2 worker's watch, the whole-file `adopt`
and every external editor are untouched. The pane document is
`<yog-state-root>/clients/<client>/pane.json` — §4.1's layout, because a pane
document and a registration are both *this client's*, and one home for a client
beats two. The pane path is **derived, never stored**: `ui.json` sits at the
state root and `clients/` is its sibling.

Which document owns which key:

| Document | Keys | Why |
|---|---|---|
| world (`ui.json`) | `seen`, `pinned`, `identity_last_used`, `ceiling`, `prices` | assertions about the world, or operator policy over it. `identity_last_used` is the §3.2 name the operator last claimed a ball under — a thing they did to the world, and two seats claiming under different names is worse than converging. §10 keeps *whether a pin is a world or a pane fact* open; §7's default to world is kept. |
| pane (`pane.json`) | `panels`, `collapsed`, `zoom`, the two transcript-density knobs, `notify_unfocused` | how one piece of glass is arranged. A phone that puts the roster away must not put it away on the desktop; a desktop notifier is a fact about a desktop. |

**The local window is a client called `yog-window`** (§4.1 as narrowed by
bl-ae05), and that is the whole of its spelling: it presents a certificate
carrying that common name, it is scoped by its registrations, and it owns a pane
document like any other seat — so the window's panel sizes and the phone's are
two documents rather than one that grows a second reader later. The path is
derived from the identity at both ends, so the document the frame writes through
in process and the one a gesture the window sends over the wire lands in are the
same file. It was `local` until the ruling; the one visible cost of the move is
that a window's stored panel sizes reset once, on the boot that mints its leaf.

Both documents are read through **one handle**, so which file owns a key is
stated once, at that key's own accessor, and no caller knows there are two. The
§3.6 unmake subtracts from both; another client's pane keeps its now-inert
collapse override, because a collapse override for a section that no longer
renders costs nothing and readdir'ing every client to delete one would be a
sweep buying nothing.

## 8. Shape of the code

One crate, one multi-call binary, as today: `yog serve` runs the engine (the
former `headless` boot plus the wire listener); bare `yog` runs the window;
`yog seat` and `yog tool-host` are the two **client** modes, the second landed
with §5.3; `yog wire-certs` is the operator's mint (below). A TUI or web seat
later is another consumer of the same wire and needs nothing new from the
engine.

**Two roles in one process, one boundary between them** *(bl-ae05)*. Bare `yog`
boots the engine in its own process exactly as it always has — the listener
rides `Engine::boot`, one world one engine, and that ruling is untouched — and
then talks to it over **nothing but the wire**: a seat presenting the window
leaf, dialling `127.0.0.1` at the port the listener actually bound. The address
is handed over in RAM rather than read back out of a file, because the two roles
share a process and only the listener knows what a `:0` became. The two
alternatives §8 already rejected stay rejected and neither is needed: nothing
spawns a second engine, and nothing refuses a desktop launch with a terminal
instruction.

**`headless` is now `serve`, and the rename is the point** *(bl-b6fa)*. The
face did not change — it is still the one `Engine::boot` with no window — but
the engine now carries the listener, so what it is to anything off the box is a
server. Two spellings of one face is the drift the const exists to prevent, so
there is one word and §8's is it.

**The listener rides the ENGINE, not one face** *(decided, bl-b6fa)*. §8.5
already runs the deposit consumer on both faces, for I0's reason: *a deposit
converges whichever face is up*. A seat wants the identical guarantee, so the
listener boots beside the consumer in `Engine::boot`, and a windowed yog serves
the wire exactly as `yog serve` does. That decides the local boot question §9.5
was asked to settle, and it decides it by **dissolving it**: nothing spawns
anything, nothing refuses with a remedy, and there is no ladder — because there
is never a second engine to arrange. One world, one engine, whichever face
started it; every other seat is a client of that one. The alternatives were both
worse and both were considered: bare `yog` *spawning* `yog serve` gives one
world two engines (two pilots, two sentries, two derivation workers — the
instance-coordination shape DESIGN §14 rejects), and *refusing with a remedy*
puts a terminal instruction in front of a desktop launch that has no terminal.

**Absence WAS the off switch, and bl-ae05 is why it no longer is.** It could
not survive §1.2's ruling: the window reads over this listener now, so a box
with no material would be a window that paints nothing — the outcome §8 had
already rejected in both its other spellings. The engine's boot therefore
**founds its own material** when it finds none (`provision::ensure`), and what
it writes is aimed at loopback. Severability is unchanged in substance: delete
the directory and the next boot writes a fresh loopback root; nothing about the
channel, the codec or the boundary is conditional on a file.

**What still distinguishes loopback from wider listening is the address, and
nothing else.** Self-provisioning writes `127.0.0.1:<port>`, so material yog
minted for itself serves exactly the window that minted it. A host that is not
loopback is written by an operator — by `yog wire-certs WIRE_HOST=…`, or by
editing `address` — and *that* is the statement of intent. One fact with one
home (below), no flag, and no ladder deciding how far a listener reaches.

A *half*-provisioned wire the mint **cannot heal** is still warned about and
still does not listen, because silently degrading to no encryption is the one
failure this design exists to exclude — and because replacing an operator's
trust root is the other. The mint issues only what is missing and only what it
can issue: a box holding an operator's `ca.pem` with no CA key beside it (a
client machine) is left exactly as it was.

**Certificates are minted out of channel, by `yog wire-certs` and by the
engine's own boot** *(bl-b6fa; amended bl-ae05)*. It shells to `openssl` — yog
links no certificate library and mints nothing *in channel*, ever (§1.4) —
writing a private CA and the server, client and **window** leaves into
`<yog-data-root>/wire`, plus one `address` file.

There is **one recipe** (`src/wire/provision.rs`) and two callers: the boot,
which mints what a box lacks aimed at loopback, and the verb, which is the
operator's own act for a server another machine dials by name and for a
rotation. `scripts/wire-certs.sh` was the recipe until bl-ae05 and is retired —
an installed binary has no repository to find a script in, and two spellings of
one act drift within a week; `make wire-certs` runs the verb, and its
`WIRE_DIR`/`WIRE_HOST`/`WIRE_PORT`/`FORCE` interface is unchanged. The server
leaf always carries `IP:127.0.0.1` beside whatever host it is minted for,
because the window is a client of loopback unconditionally and a certificate
that named only an operator's public host would refuse the one seat certain to
be there.
`WIRE_HOST`/`WIRE_PORT` name what the server binds and a seat dials; the SAN
and the address file are derived from the same `WIRE_HOST`, because two
spellings of one host is the drift §8's name resolution removed from the
boundary. The verb refuses to overwrite: a rotation distrusts every
certificate already issued, so it is `FORCE=1` and never a silent re-mint. (The
boot's call cannot rotate at all — it only ever adds what is absent.) Test material is
minted the same way at test runtime (`src/test_support/wire.rs`) — **a
certificate fixture is never committed**, which `make leak-scan` would refuse
anyway and which would be a private key in a public repository whether or not
it guarded anything.

**The material sits BESIDE the world, not inside it** — `<yog-data-root>/wire`,
the sibling of `<yog-data-root>/world`. The world subtree is a generated
artifact yog seeds, wipes and reseeds; key material is operator-provisioned and
irreplaceable by anything yog can do. Nesting it under a directory yog rebuilds
would make a reseed a revocation.

**The address is one fact with one home.** A server binds `address` and a local
seat dials it; a seat on another machine has its own `wire/address` naming the
server it belongs to. There is no second file and no flag — and no server-name
knob either, because the name a client verifies is read off the address it
dialled (an IP literal is an IP identity, anything else a DNS one).

**Paths never cross the wire.** *(Landed, bl-f5f6.)* Boundary types addressed
workspaces and projects by absolute `PathBuf`; across machines those are
meaningless and a disclosure besides. The wire spelling is now the **name**, on
the types themselves — `Action`, `Query`, the nested payloads they carry
(`monitor::Verb`, `fleet::Verb`, `fan::Obligation`, `config::ConfigFile`,
`start::Payload`, `start::Prepared`), the line's `Context`, and the two `Reply`
fields that identify rather than locate. The engine resolves a name to a path at
the dispatch chokepoint. Four rulings came out of it and belong here rather than
in a ball body.

- **The name of a workspace is its directory leaf; the name of a project is
  derived.** The rule differs because the nouns do (`src/naming`). §3.1 already
  says a workspace's leaf *is* its name and §3.2 already makes that same leaf
  the `--as` identity every ball claim is stamped with — so there is nothing to
  invent, and **a foreign workspace needs no special case**: lernie's
  `workspaces/`/`replays/` leaves are the auto-ids the tab strip already paints
  as their identity. Two roots holding one leaf is a world whose §3.2 join is
  already ambiguous — both would claim `--as home` — so the resolver refuses
  naming the token instead of inventing a disambiguator for a world that is
  broken one level down. A **project**, by contrast, has no name at all: its
  identity is the decoded balls invocation path (§5.1 #1) and two checkouts of
  one repo legitimately share a basename, so its name is the shortest trailing
  run of components no other enumerated project shares — the basename wherever
  that is already unique. The §11 roster label is that same name, elided, so
  what the operator reads off the left panel is the word they may type at
  `--project`.
- **The resolution is one function, at the chokepoint, ahead of the table.**
  `Action::workspace()` / `Action::project()` / `Query::workspace()`
  (`src/boundary/address.rs`) are tables *on* the enums; `dispatch` and `answer`
  each resolve once, before their match, so no arm re-derives an address and an
  unresolvable name refuses — naming the token, the codec's own strict-decode
  discipline — before anything runs. A gesture that names no workspace resolves
  to nothing and no arm reads it: the general path with no input, not a case of
  its own. The mapping is read backwards at exactly one place too — the frame
  spells `Snapshot::ws_name` / `project_name` where a seat's *selection* becomes
  a gesture (`AppModel::line_context`, the click-glue), over the same enumerated
  sets the resolvers read. Two directions, one mapping.
- **A reply speaks the name where it IDENTIFIES and the path where the path
  IS the answer.** `WsRow` identified a workspace by carrying the whole
  `Workspace` — path, kind and, for a named one, a `name` beside the path. It
  now carries the name and the kind: §3.1 makes the leaf the name, so the field
  that was already the identity became the whole of it and the path went. A
  field whose *subject* is a filesystem location, by contrast, keeps path
  semantics, because answering it by name would answer a different question.
  bl-f5f6 listed four such fields as a residual to be narrowed once a transport
  made the distance real. **§8.1 is what became of that list.**
- **`Prepared::name` is gone, not migrated.** It carried the §3.2
  `--as`/`YOG_NAME` stamp beside a `workspace` path. Once `workspace` became the
  name, §3.1 and §3.2 made the two fields one string twice — so the second went
  rather than being kept in step.

### 8.1 The path-typed reply residuals, closed (bl-ccf7, bl-b4b5)

The list was four items and it was wrong about one of them; three narrowed, and
the fourth is now a ruling rather than a residual. **The question each answers
is "what does this field's subject actually make computable?"** — not "can a
path be shortened", which is why three of the four ended up carrying *less*
rather than carrying a relative spelling.

- **`Reply::Applied { file }` carries nothing at all.** It said the absolute
  path that now held the staged text. But a `ConfigFile` **determines its own
  location** — the named workspace's wall `config.toml`, `models.yaml`,
  `workflows/<name>.yaml`, `cadence.yaml` — so the field was the destination
  the gesture had just named, respelled as the engine's home root. One
  computable fact said twice, and the second saying was the disclosure. The
  receipt is now `Reply::Applied`, the `Nudged`/`Acked`/`TrailCleared` shape:
  *it landed* is the whole of what a write adds to the address it was given.
- **`Reply::Marks { space }` is gone for the same reason, one level deeper.**
  It was answered on the argument that "which branch" and "whose branch" are
  one question. That argument expired at the §16.3 per-agent ruling: a
  workspace's marks space is now *always* its own (`<wall>/marks`), so the read
  is a pure function of the workspace the gesture named and the field could
  never have answered anything else. `Reply::Marks { branch }` — the branch,
  re-read after the write, exactly as before.
- **`WorkDiff`'s `Attempt::project` is the project's §5.1 #1 wire name.** This
  one identifies rather than locates, so it took the *name* this section's
  first bullet already rules for a project: the shortest unique trailing run,
  the word the §11 roster labels it with and the word `--project` takes. The
  patch read that needs a real repository (`workdiff::patch`) now takes the
  snapshot and resolves the name at that one seam — the same round trip
  `Snapshot::project_name` / `project_path` already owns, read in both
  directions rather than a path carried so one caller need not ask. Its one
  visible cost is the Work tab's copy-paste hint, which was `git -C <abs> diff
  …` and is now the range beside the project's name: a `-C` a remote seat could
  not have run anyway.
- **`Files` was never a residual, and the list was wrong to name it.**
  `FilesView` is `FileEntry { rel_path, … }`, `/`-joined from the worktree root
  since it was written, and `Preview` carries no path at all. The absolute
  worktree root exists only engine-side. Nothing was narrowed here because
  nothing crossed.
- **The join row's own two paths were the residual this list missed, and they
  are closed** (bl-b4b5). `JoinRow::project` and `JoinRow::workspace` rode
  `Reply::Balls` — and, copied off it, `BoardRow`'s two — as absolute paths
  under the engine's home, which is neither usable nor unseeable on a thin
  client. They are the §5.1 #1 project name and the §3.1 workspace leaf now, the
  same two words the `bl` family's actions already take, so a seat holding a
  focus can select its own workspace's rows without joining the answer back
  against the engine's table (bl-7407's refused shape). The engine resolves
  either back at the one seam that owns the round trip
  (`Snapshot::project_path` / `ws_path`).
- **`Prepared::binding` stays a path, and that is now the ruling.** It is
  lernie's `--cwd`: minted by the engine at `Prepare`, relayed back verbatim
  inside `Action::Prompt` by a seat that never reads it, and read again by the
  engine to seed the working-directory mark and to discover the §3.7
  instruction pins. Both narrowings §8 imagined are worse than the disclosure.
  An **opaque handle** needs a mint→resolve table, which is durable state for a
  fact that is already computed — the one thing the file religion and the
  single-source rule both refuse — and it would be state whose lifetime is a
  composer draft's. **Re-deriving it at the fire** means a second derivation of
  the work target beside the executor's cross-checked worktree, and §3.3's
  whole point is that there is one. So the binding crosses as a path, and what
  it discloses is bounded and stated: an engine-side directory name, to a seat
  that was already told the workspace it belongs to.

  It is worth being exact about what remains. Since bl-b4b5 this is the only
  path-typed field left on the reply surface, and it is one a **remote seat
  cannot use and does not read** — which makes it a disclosure and not an interop defect. Closing
  it wants the thing §9.5's residual wants: a ruling about what the local
  window *is*, because the shape that dissolves it (`Prepared` becoming opaque
  to the seat entirely) is only affordable once one seat's read path is the
  only read path.
## 9. Build sequence

Each step is its own ball; boundary-surface work serializes (shared-surface
merges cost more than the work). Steps 1–4 are pure boundary-completion,
valuable with no network at all — they finish VISION V5 teleop parity.

1. **Boundary-complete the reads.** *(Landed, bl-6233.)* The
   transcript/inspector surface read disk on the frame thread and had no
   boundary spelling — the chats themselves were unreachable by any face but
   the window. Six Query/Reply pairs now cover it: `Transcript`, `Steps`,
   `Step`, `Files`, `Rail`, `Inbox`, each addressed by workspace + agent
   (name-based addressing is step 3, not this one), in all three
   serializations. Two rulings came out of it and belong here rather than in
   a ball body:
   - **The in-flight tail is folded, not dropped.** A live model call's tail
     is the snapshot's own `Stream`, which the `Deps` already carry, so
     folding it costs no read. Answering the committed half alone would have
     been cheaper and wrong — the window folds it, so a headless seat that
     did not would describe a different moment than the GUI does of the same
     instant, which is the divergence §3's "one dispatch surface, N
     serializations" exists to prevent.
   - **The pin is not answered.** A pin is a viewport fold, and DESIGN §8.5
     files folds under views, so `Rail` answers the notches and every
     inspector read answers unpinned — the same ruling `Conversations`
     already makes by answering the all-collapsed list.
2. **`Reply` decode**, with encode↔decode round-trip tests over the whole
   reply surface. *(Landed, bl-7067.)* Strict, on the gesture codec's own
   terms: an unknown `kind`, a missing field and an unknown token each refuse
   naming the offender, and the refusal `{"ok": false, "error": …}` reads back
   as the `Err` side. The discriminant is `kind`, **not** `ok` — a captured run
   spells its own exit verdict there, so a `bl close` that failed its gate is
   `ok: false` and is an answer, not a refusal. Three rulings came out of it:
   - **Round-trip failures were fixed in the ENCODING, never in the test.**
     Four facts left the window and never reached a headless seat: the search
     answer's own needle (bl-648a's whole point), the conversation row's depth,
     tone and standing alignment verdict, the display-only rung, and the §3.5
     figure's four token counters and its attribution class. Each is now on the
     wire. The reply file has no consumer that a widened object breaks — its
     readers parse JSON forgivingly — and the alternative was a codec that
     narrowed the answer silently.
   - **Derived text rides beside the fact, never instead of it.** `usd` beside
     `micro_usd` was already the tree's shape; the attribution clause and the
     fleet label now follow it, and the decode reads the fact and re-derives
     the text.
   - **Help resolves against the seat's own roster.** A `HelpRow` is four
     `&'static str`s out of a `const` table and no decoder can mint one — but
     help's subject is *the interface, not the world*, so the roster is an
     answer every seat already holds. An unknown verb refuses, naming it.
3. **Name-based addressing:** `PathBuf` leaves the boundary types.
   *(Landed, bl-f5f6 — the rulings are in §8.)*
4. **The shell paints only boundary payloads.** *(Landed, bl-1eb0 — see §9.4.)*
5. **The wire.** *(Landed, bl-b6fa — see §9.5.)* The mTLS listener, the seat
   transport, the framing, the certificate bootstrap, and `yog seat` — the
   wire's first shipped consumer. The deposit inbox remains for in-world
   callers (§3).
6. **Registration and scoping.** *(Landed, bl-8bbc — see §4.1, §7 and §9.6.)*
   The per-workspace registry, the one scope filter that makes an unregistered
   workspace absent, auto-registration on create, and the per-seat `ui.json`
   split.
7. **Tool hosts:** advertisement, rendering, routing, the lernie seam —
   after its own design pass (§5). **Landed, in three parts.**
   - bl-4e08 (§5.1): the `advertise` gesture in all three serializations, the
     per-client `tools.json`, connection-scoped presence, and the
     `Query::Clients` roster the workspace surface renders.
   - bl-c907 (§5.2): the lernie seam filled (`Fx::tool_injection`, lernie
     0.0.9), the `clients` tool with its three ops, the durable per-agent
     loaded set, and the frozen definitions the prefix declares. The
     model-facing half is complete: an agent can see this workspace's machines,
     read what they advertise, and make named tools callable.

   - bl-024b (§5.3): the routing leg and the client-side executor — the four
     boundary verbs (`invocations`/`complete` for the tool host,
     `invoke`/`capture` for the asking side), the engine-side mailbox beside
     the presence refcount, and `yog tool-host`, the client mode that reads the
     §5.2 config, advertises the projection of it, and runs what it is handed.
     An agent's call on a loaded remote name now crosses to the machine that
     advertised it and the capture comes back, verbatim.

   The day the leg landed the same call succeeded with nothing above it
   changing, which is what the interim refusal was shaped to make true: an
   in-band non-zero result is what a vanished endpoint had to produce anyway
   (lernie's §3.3), so the seam was complete and honest before the transport
   existed.
8. **The window becomes a client.** *(Landed. Opened bl-ae05 — see §9.7; the act
   path §9.8, designed and half-migrated in bl-4841 and **completed in
   bl-1747**, which deleted `AppModel::dispatch` and with it the window's last
   in-process gesture. Its read residual is §9.7's, and is scope rather than
   architecture.)* The operator ruling that settled §1.2 against §4.1, the
   boot's own mint, the window's leaf and its seating, the off-frame asker, and
   the first surface painted from a decoded reply. Its residual was the
   migration of the remaining reads and of the acts: **the acts are done**
   (§9.8 — every one of them crosses, and the second execution path §1.2 exists
   to refuse no longer exists), and the reads are bl-f297's list (§9.7).

   **Two rulings came out of it and belong here rather than in a ball body.**
   *Presence is not the routing predicate* (§5, amended): a tool host holds a
   connection only while it is waiting, so a busy one is absent and a presence
   test would refuse exactly the calls that would have succeeded — the queue is
   the predicate, and the caller's deadline is what makes a vanished machine
   visible. And *the asking side needs two verbs, not one* (§3): the engine's
   intake is one thread for the world, so no gesture may wait on a tool.

### 9.4 Where orchestration stops and paint begins

The line, stated once because §9's step 4 is where it had to be drawn:
**`AppModel` may orchestrate; what crosses into paint is a payload the wire
could carry.** Orchestration is holding the focus, resolving it against the
published snapshot, memoizing a heavy build per derivation, handing a search
off and painting whatever landed — none of it crosses the boundary, and all of
it stays. What went is the frame deriving *out of* `GitTree`: names, marks,
liveness, descent and verb gates folded on the render thread from the engine's
own agent set, which is a thing no client will ever hold.

Mechanized as `rules/no-engine-tree-in-paint.yml` over `src/shell/**` plus the
three modules that render rather than derive (`inspector`, `composer`,
`inboxview`). The test is **not which module a type lives in** — it is whether
a `Reply` can say it: `AgentState`, `AgentMark`, `Flight` and `Tone` ride the
wire today and are lawful in paint; `GitTree`, `Agent` and `CommitNode` ride
nothing. Every other module under `src/` is engine and folds the agent set
freely, because folding it is what the server does.

Four rulings came out of it.

- **The fat `Agent` gets a projection, not a diet.** `Query::Agent {workspace,
  agent}` → `Reply::Agent(AgentView)` is the family's seventh member, sharing
  the six inspector reads' address and envelope: the selection's id, its §2.3
  conversation root and the ancestor chain above it, the §3.3 name with its
  addressability rung, the tip the §5.1 #17 governing derivation takes, its own
  §3.5 liveness, the §6 marks it wears with the parked invocation's own
  sentence beside them, the §5.1 #28 class in flight anywhere in the
  conversation, and the four §8.2 gates (present, nudgeable, stoppable,
  children). Every field is a fold the boundary already owned; nothing was
  derived here for the first time. That is the shape the rest of §9 wants:
  **what was missing was never the derivation, it was the spelling.**
- **A gate that is not derivable from a row goes ON the row.** `ConvRow` gained
  `stoppable` and `stop_children`. Neither follows from what it already
  carried: `state` is the badge aggregated over the whole subtree, so a quiet
  root with a working child paints Live and has no driver to kill, and the
  cascade's membership is the Stop menu's looser prefix test rather than the
  strict §5.1 #8 descent `direct` counts. Deriving them per row against a
  roster the seat had to be holding is what the ban forbids; deriving them once
  where the row is built costs one pass instead of one per row.
- **A name is resolved against a table, not a tree.** Painting a *third
  party's* name — a deposit's sender, in the two seats mail appears in — was
  `display_name_of` over `&[Agent]`. The ladder did not move (§3.3 still has
  one home); its input narrowed to `nav::convs::Titles`, id→title, which is
  buildable from the engine's agent set **or** from a conversations reply's own
  `root_id`/`display` pairs. The second constructor is the claim made
  executable: a face holding replies and no world resolves the same names.
- **A residual is named rather than papered over.** The model picker's
  config-lineage tip crosses as `model_pick::ConfigTip` — two strings, plainly
  wire-carryable — but it is handed over by an `AppModel` accessor and has no
  `Query` of its own. The §11 picker family (lineage browse, `providers.yaml`
  reads at an oid, the write claims) is not wire-complete, and saying so is
  worth more than a query minted to close a checklist. It belongs with §9.5's
  transport, beside the path-typed reply fields §8 already lists as residual.

### 9.5 The wire, and what it does not yet carry

Landed (bl-b6fa): `src/wire/*` — the framing (§3), the two rustls
configurations, the engine's synchronous accept loop, a seat's transport, the
material reader, and `yog seat`. `rustls` is a direct dependency by operator
ruling; **no tokio** (the listener is `std::net` and blocking threads, so
AGENTS.md rule 8 stays vacuous) and **no rcgen** (§8's make target). Four
rulings are in §3 and §8 rather than in a ball body: the framing, the
listener riding the engine, the material's home, and the address as one fact.

**What the wire adds to the boundary: nothing** — and that is structural, not
a promise. A connection's request goes to `ConsumerCtx::answer`, the same
function the gestures inbox calls, which decodes with the one codec and runs
the one `dispatch`/`answer`. The listener never sees an `Action` or a `Query`,
so there is no place a wire-only verb could be added.

**The residual, as it stood: the window was not a client.** §1.2 rules that
the UI operates entirely via RPC and that even the local case does the hard
split. What bl-b6fa landed was the channel, the server and a *terminal* seat;
the window still held the engine it serves and read it in process. **bl-ae05
opened the read path** (§9.7) — the window now dials its own listener with its
own leaf — and what remains of this bullet is scope rather than architecture:

- A frame still paints the published snapshot (§7.2) for every surface but one.
  §9.4 finished the *taxonomy* — paint may name only what a `Reply` can say —
  and bl-ae05 built the delivery, but only the clients section (§5) is fed by
  it so far. The rest is a migration, surface by surface, with the transport
  already under it.
- ~~Per-seat UI state (§7) is undivided~~ — split by bl-8bbc (§7, §9.6).
- ~~The path-typed reply fields §8 lists as residual are still absolute
  paths~~ — closed by bl-ccf7 (§8.1): three carried a fact the address already
  made computable and now carry nothing, and the fourth is a ruling.
- ~~One connection per gesture~~ — kept, and ruled rather than deferred
  (§10, bl-ccf7). No follow-class `Query` yet; the framing carries a chunked
  stream already (§3), so the live tail needs a query, not a wire change.

Registration, scoping and reply filtering were deliberately **not** here: a
connection was trusted at certificate grade this step. §9.6 (bl-8bbc) closed
that, and the per-seat `ui.json` bullet above with it. **One residual in this
list is left, it is the read path, and §9.7 is why it is still here.**

### 9.6 Registration and scoping, and what the trust domain still is not

Landed (bl-8bbc): `src/registry/*` — the file-per-registration store, the
reserved `local` identity, and the certificate → common-name walk;
`Snapshot::scoped`, the one filter; `ConsumerCtx::answer_as`, the scoped
chokepoint; the raise in `dispatch`; and the two-document `UiState`. The
rulings live in §4.1 and §7 rather than in a ball body: the registry layout,
the identity spelling, the one-filter scoping, auto-registration without
create-detection, the raise-cannot-join rule and the existence-is-observable-at-
creation ruling, plus §7's key-by-key split and the `local` window's spelling.

**What the workspace-grade trust domain still is not.** §1.5 divides
workspaces and nothing else, and the landing divides exactly the
workspace-keyed facts. The **project** set, the balls projection, the §3.5 join
and the `ops.jsonl` trail are world-wide and every registered client sees all of
them. That is §11's standing rejection of per-tool and per-verb ACLs in force,
not an oversight — a finer policy layer is speculative until a second human
exists — but it is worth stating plainly, because "the workspace is the trust
domain" reads like more isolation than it buys: a client registered in one
workspace still reads the trail of every workspace.

**Two residuals, named rather than papered over.**

- ~~The window is not scoped, because the window is not a client yet~~ —
  **closed by bl-ae05** (§9.7). It carries `yog-window`'s leaf, it is scoped by
  its registrations like any client, and the engine seats it in every workspace
  it enumerates (§4.1). Its pane document moved with the identity.
- **A workspace name is a global namespace.** Creation refuses on a collision,
  including with a workspace the creator cannot see, so two clients cannot both
  hold a workspace called `home`. Per-client name spaces would dissolve it and
  would also break §3.2's `--as` identity, which is the same leaf; the collision
  refusal is the cheaper answer and §4.1 records what it discloses.

### 9.7 The read path, and the ruling that opened it (bl-ccf7, bl-ae05, bl-adcb, bl-f297, bl-44e9, bl-13f9, bl-7407, bl-48ae, bl-296f, bl-b4b5)

Landed (bl-ccf7): §8.1's narrowing of the path-typed reply fields, and §10's two
transport questions settled. It could not move the read path, because §1.2 and
§4.1 could not both be executed and the tree had already executed §4.1 — a
window that dialled the wire would present a leaf, be identified by *that* name,
be scoped like any remote client and read a different pane document, and on an
unprovisioned box would have no read path at all.

**The ruling, 2026-08-14: neither of §9.7's cheap options — the window is a wire
client of localhost.** One boundary, everything through the front door: the real
socket, the real handshake, the real certificate. The in-process transport
bl-ccf7 recommended was declined, so §11's rejection stands unlifted; §4.1 was
narrowed rather than kept, so the collision is dissolved rather than deferred;
and the unprovisioned box is answered by the engine's boot performing the same
out-of-channel act the operator's own verb performs, on the operator's own box,
before anything is dialled (§8). Every consequence is recorded where it belongs
— §1.2, §4.1, §7, §8 — rather than here.

**What bl-ae05 landed.**

- `src/wire/provision.rs` — the one `openssl` recipe, and `scripts/wire-certs.sh`
  retired into it. The engine's boot mints what a box lacks; `yog wire-certs` is
  the operator's act over the same function.
- `Role::Window` and `registry::WINDOW` — the window's leaf and the identity on
  it, one const spent by the mint and by the seating.
- `src/wire/asker.rs` + `src/wire/link.rs` — the off-frame asker and the frame's
  half of it. The frame declares standing questions and paints what has landed;
  the asker seats the window, dials loopback once per ask at human cadence,
  decodes with `reply::decode` and publishes. Two channels, no lock, and no
  frame-side wait: a dead engine costs a surface its content and the window
  nothing.
- `Engine::asker` — a window takes it, `yog serve` never does. One asker per
  engine, because the link end is taken rather than shared.
- The **clients section** (§5) is the first surface painted from a wire reply:
  the roster it renders crossed loopback mTLS, was scoped against the window's
  own registrations, and was decoded like any seat's. Its per-derivation memo
  and the model's in-process `clients()` derivation both went — with the model's
  copy of the presence map, which nothing reads any more.

**What bl-adcb landed, and what it found.**

- Two more surfaces read over the wire. The **ops trail** (§4.2, the §11
  activity accessory's expanded pane) is a `Reply::Ops` bounded by
  `opslog::OPS_TAIL`, which is now the log's one number rather than the
  derivation's private one; the **V4 board** (the §11 balls fold) is a
  `Reply::Board`. `AppModel::ops_rows` and `AppModel::board` went with them —
  neither had a memo, an answer already being the cached fold.
- `src/shell/wire.rs` is the shell's **one spelling** of a wire read. bl-ae05's
  four arms — the answer, the refusal, the honest not-yet, and the wrong-kind
  reply that is a codec defect rather than a state — were written by hand for
  the clients section; restating them per pane would restate the third one
  wrong. A surface now declares its query and picks its payload out of one
  reply variant.
- `src/shell/acceptance/wire.rs` is the **acceptance world's own answerer**: the
  frame's standing questions taken off the `LinkEnd` exactly as the asker takes
  them, decoded by the one codec and answered by `AppModel::answer` — the
  transport stood in for, never a second dispatch, because a fixture mints no
  certificate and binds no port. Without it a migrated surface paints nothing in
  every test, which is a surface with no witness. It pays the **settle-then-render
  shape** out in full: paint (ask), settle, answer, paint (re-declare), settle
  (land), paint. Three passes, and it cannot be fewer — a settle whose frame
  declared nothing drops the answers as no longer standing, which is the same
  rule that makes a collapsed pane free.
- **A migrated read paints the derivation, never the frame's fold** — and that
  is §7.2's own partition (*paint reads the fold, gestures read the derivation*)
  landing on the other side of the line, because a wire read is answered from
  the engine's published snapshot like any seat's. The cost is stated rather
  than discovered: the §3.4 pending **echo** and the §7.2 live tail are folds
  the window makes for itself, so a just-fired conversation no longer shows as a
  drone row on the board until the derivation carries it. Optimism is a seat's,
  and a seat that reads over a wire has none — which is a fact bl-4841 inherits
  whole, since an act's receipt is what the echo exists to stand in for.
- **What crosses is the read, not the affordance.** The board's rows crossed;
  `startable`/`resumable`, which build the composer's fire-time inputs the
  click-glue consumes synchronously, did not. That line is the acts ball's, and
  a surface that mixes the two migrates its read half only.

**The residual, as bl-adcb left it: three surfaces read over the wire, the rest
do not, and the acts do not.** This was still scope rather than architecture,
but the remaining reads were **not** more of the same three moves — each was
blocked on one of three named things, and bl-adcb's audit is what found that.
**bl-f297** took the list; what follows is what it landed and what it ruled, and
the three classes are stated in its terms rather than in the audit's, because
two of them dissolved.

**What bl-f297 landed.** Three more surfaces: one of bl-adcb's two unblocked
candidates, one the acts ruling had just unblocked, and one out of a class the
audit had called blocked and which turned out to need a sentence rather than a
mechanism.

- The **§11 Work tab** (§5.1 #32) is a standing `Query::WorkDiff`. Its *two*
  per-snapshot memos went with it — every arm of that read forks `git` against
  a project repo, which is why the in-process version was memoized, and an
  answer is that cached fold refreshed at the asker's cadence instead. The
  listing and the picked file's patch were two reads with two memo keys and are
  now **one question**: the query carries the file, so re-picking asks a
  different question and scrolling asks nothing. An inactive tab declares
  nothing at all, which is the collapsed-pane rule.
- The **marks pane's `Read current`** is the click-time read §9.8 handed back:
  **a standing question with a latch**. The click throws the latch, the pane
  declares `Query::Marks` while it paints, and the branch lands one ask-period
  later. The latch names the *workspace* it was thrown for, never a bare
  `bool` — the focus can move under it, and a latch that could not say which
  wall it meant would paint one wall's branch under another's name. It does
  **not** fall on the first answer: a read that switched itself off would be a
  one-shot with a socket behind it, showing the same bytes whether the branch
  moved a second later or not. The `Cli` pair evaporated from the pane and from
  `config_edit::center` above it, which is §9.8's observation reaching the read
  side.
- The **§6 decision queue** behind the desktop escalation is `Query::Attention`,
  and `AppModel::decision_queue` went with it — one accessor fewer, and the
  strip's count, the desktop's ask and a headless `/attention` are now one
  answer rather than one derivation run twice.

**Class 3 dissolved, and the rule it needed is one sentence.** The audit's third
class — *a read with nowhere to paint a refusal, or a consumer that reads it
synchronously* — is empty now, and neither half needed a mechanism.

- The **alert baseline** was the hard half: `alert::announce` folded the queue
  on every frame, and a frame the wire has not answered holds no queue, so
  folding one would read as everything having departed and then, on the next
  answer, as everything arriving at once — the first-boot flood re-armed twice a
  second. The rule: **an unanswered frame is not a reading of the queue, and
  neither is a refusal.** The baseline moves only on a frame that was told
  something, which is the *same* rule that already makes a freshly-opened window
  silent — no observation, no arrival — so the special case dissolved into the
  general path rather than earning a branch. The refusal half answers itself
  with it: this seat has no surface to paint a refusal on, because a
  notification is output and not a pane, so the window stays quiet rather than
  announcing on a guess. Recorded in DESIGN §6, where the baseline's ruling
  lives. What it costs is stated: the fold runs at the asker's cadence rather
  than the frame's, and the focus gate is read on the frame the answer lands.
  A window buried and re-focused inside one ask period folds once instead of
  thirty times, which is a difference no difference detector can feel.
  Its witness is **both directions** (`shell/acceptance/alerts.rs`), because the
  positive one alone would have passed against a seat that never asked: each
  test seeds the baseline with an empty fold first, since with no prior
  observation `announce` returns nothing whatever the frames did.
- The **§9 config family** was smaller than the audit read it as, and that is
  worth recording rather than quietly acting on. Of the five queries the audit
  named (`ReadConfig`, `Lineages`, `Models`, `Providers`, `Marks`) exactly
  **one** was ever a frame-side read: the marks pane's, migrated above. The
  §9.1/§9.2/§9.3 editors do not reach those queries at all — they load their
  bytes directly and their *writes* are the boundary's — so "the config family
  is answered at click time" was true of one surface and a projection onto four.
  Pointing the editors at `ReadConfig`/`Lineages`/`Models`/`Providers` is a real
  and separate migration: it would replace `Editor::load` with an answer, which
  is a change to what a config editor *is* (§9.5's "controls over facts"), not a
  read moved onto a wire.

**Class 1 stands, and the ruling is that it stands.** The viewport folds —
`Query::Conversations`' expanded set and the §11 inspector family's rail **pin**
— stay in process, and §8.5 is **not** amended. Three shapes were weighed:

- **The fold as a query parameter.** It preserves the letter of the ruling — the
  boundary would still *store* no viewport state, the seat spelling its own fold
  in its ask — and it is the shape `visible_conversations(now, expanded)`
  already has. It is refused on §8.5's own sentence: *"Views gain no boundary
  representation, by design."* The parameter **is** the representation; the
  question of whether the engine keeps it is a different question, and answering
  the second does not answer the first. The precedent that looks like a
  counter-example is not one: `Query::Files { path }`, `Query::Step { seq }` and
  `Query::WorkDiff { file }` carry a *selection*, and a selection names **which
  thing you are asking about** — it is the question. A fold names **which
  answers you have open**, which is the rendering of an answer already given.
- **The fold becomes a durable pane fact.** Closed already, by DESIGN §5.3's own
  entry on the expanded set: it is keyed one-per-conversation and would accrete
  a stale key for every conversation that ever existed, and mirroring it would
  drag a second instance's list open under the operator's hands. Not re-argued
  here.
- **The answer moves altitude.** The one shape that could work: the boundary
  answers the whole descent **forest** with its per-row rollups, all-collapsed
  being the root subset of it, and each seat renders the rows its own fold makes
  visible. The fold then never crosses, the seat derives nothing (it *selects*),
  and one derivation still serves both seats. This is not smuggled in with a
  migration, because it is a change to `Reply::Conversations`' payload that
  every seat and the codec read — a headless `/conversations` included — and the
  same move on `Reply::Rail` for the pin, plus the pinned `Files` listing that
  no query spells at all. It is a payload ball, not a read-path one, and it is
  where this class goes next.

**Class 1's payload landed, and the class is closed (bl-44e9).** The third shape
was built, and §8.5 is still not amended — the fold stayed seat-side, so *views
gain no boundary representation* is true of the letter and of the spirit.

- **`Reply::Conversations` is the whole descent forest.** `answer::conversations`
  answers every member with its own per-subtree rollups, in paint order, and
  `nav::convs::visible(rows, expanded)` is the seat's pure selection out of it —
  a one-pass depth cut over rows, no snapshot, no recursion. `visible_rows` split
  into `forest_rows` (the derivation) and `visible` (the fold), which is the
  altitude ruling made structural. A seat holding no fold selects the **root
  subset**, which is byte for byte the list this query used to answer, so a
  machine reader that wants the old shape filters `depth == 0` and every existing
  consumer keeps working.
- **The §11 list reads it over the wire**, and `AppModel::conversations`,
  `visible_conversations` and `conversation_groups` are gone with it. One seat
  read (`shell::convs`) serves the paint, the ↑/↓ walk and the ← that pages to a
  parent, so the rows on the glass and the rows the keyboard steps cannot be two
  answers. The grouped-by-ball view is `nav::convs::group::group_by_ball` over
  the same folded rows — a partition of a selection, not a second derivation.
- **`Reply::Rail`'s notches carry the budget as a rollup**, not their own spend
  (`Notch::budget`, wire key `budget`), so `rail::pin` reads one field off the
  notch the operator picked instead of summing the prefix. Every field a pin
  shows is now on the answer: the commit and the cut were already, the transcript
  is a prefix of the chat the seat was answered, and the budget is this. The pin
  is a *view* that derives nothing.
- **The pinned `Files` listing is spelled**: `Query::Files` gained
  `at: Option<String>`, and it is a **selection, not a fold** — it names which
  *tree* you are asking about, exactly as `Step { seq }` names which step. Of
  VISION V1.2's four pinnable tabs this is the one whose subject is a different
  tree; the other three read something the seat already holds. Headless it is
  `/files [<path>] [--at <commit>]`, and the window's own pinned tab is the memo
  around that one answer rather than a second spelling of the same two arms.
  **Config-frozen-at is the same shape and has no query at all yet** — when it
  gets one it takes `at` for this reason, and that is a residual below rather
  than a decision left open.

**And the §3.4 echo needed an altitude of its own, which is the finding.**
bl-adcb's *"optimism is a seat's, and a seat that reads over a wire has none"* is
true of the board and would have been a silent regression here: the §11 list is
the surface §3.4 exists for, and migrating it read as deleting `bl-915e` — the
operator's typed goal with no representation anywhere in yog until the driver
wrote a branch. The sentence is narrower than it looked, and the ruling is:
**a seat's optimism reaches whatever that seat actually reads.** So the echo has
a second projection beside `echo::compose`'s — `echo::rows::with_echo`, over an
answered list — and both live in the one module, which is `compose`'s own
single-source argument kept. A start with no row yet leads the list, faded, in
the operator's own words; a target already in the answer is *freshened* to the
echo's own age and nothing else moves (the reorder `compose` earns by bumping
`last_action_unix` is deliberately not made — the answer arrives sorted, and a
seat that re-sorted it would be deriving). A `Target::Agent` the answer no longer
carries adds nothing.

**A driven frame settles its own reads, too** (the harness half). §9.8 ruled that
`Screen::run` pays an act's round trip once so the transport does not leak into
every call site; bl-44e9 extended it to the read half for the same reason — a
migrated surface paints an answer that landed a round trip later, so a drive that
only settled acts saw every such surface blank and a beat asserting a row is
*there* would have been red for the transport's reason rather than the window's.
The loop settles to a fixed point on `AppModel::awaiting` — *is any standing
question still unanswered* — because a `Link` may never be settled twice without
a frame between (the second declares nothing and drops every answer), so a landed
read costs the two extra passes `wire::wired` spells out and cannot cost fewer.
A frame that asked nothing new pays none of it.

**Class 2 stands unchanged, and bl-44e9 found what it is really blocked on.**
`Reply::Workspaces` carries no path (§8.1's ruling), and the tab bar's every
entry is a **focus target** — `nav::tabs::Tab` holds the `PathBuf` a click, a pin
toggle and the §3.6 delete seat all spend. It could be made to *paint* off a
reply tomorrow by resolving each name through `Snapshot::ws_name`/`ws_path` at
the seat's door, and that is exactly the shape to refuse: the seat would be
joining a wire answer back against the engine's own in-process table, which is
two sources for one fact and worse than not having migrated. The honest change is
the wide one — focus held by **name**, resolved at the doors that need a path.

It was attacked and put down, and the reason is worth more than the refactor
would have been. **A name resolves against the enumeration, and the §3.4 raise
focuses a workspace the enumeration does not carry yet.** `Snapshot::ws_path`
answers over the published `workspaces` set; a `lernie new` that has just
returned is a wall the derivation has not read. Today the frame holds the path
and the composer's bare rung fires into the wall that was just raised
(bl-9acf's whole fix); name-keyed, that focus would be unresolvable for as long
as one derivation takes, and Enter in that window would fire into the *previous*
workspace. Re-deriving the enumeration synchronously at the raise, or holding the
raised wall as a claim until the world catches up (the §3.4 start claim's own
shape, one noun up), are the two answers — and picking between them is design,
not a refactor, so it is its own ball. Note that it is **not** dissolved by
migrating the tab bar: the engine resolves the same name against the same
snapshot and refuses for the same reason.

**Class 2's prerequisite landed, and the raise was answered by the claim
(bl-7407).** `Focus::ws` is the §3.1 name — the wire spelling — `nav::tabs::Tab`
carries no path at all, and `selected` compares names. The synchronous re-derive
was refused: it puts a disk walk on the receipt path, and it is a special case
where a general shape already exists.

- **The claim is the §3.4 start claim's shape one noun up, and it is one
  mechanism rather than two** (`src/app/raise.rs`). Same holder — per-instance
  RAM on the model (§13.1) — same retirement predicate, *the derivation shows
  it*, and the **same seam**: `echo::compose` is *"the one place snapshot and the
  non-derived facts meet… a third such fact is a third argument here rather than
  a third mechanism"*, and the raised wall is that third argument. The two stay
  two *values* because a raise carries no message and a send raises no wall.
  Every landed `Prepare` claims — the raise, ▶ Continue's own claimant wall, the
  bootstrap — and a start into an enumerated workspace is simply the claim
  retired the instant it is made, which is one rule instead of a raise/re-focus
  branch. bl-9acf's invariant is held by
  `app::balls::tests::prepare::a_raise_focuses_the_raised_workspace_and_retargets_the_bare_rung`
  (the bare rung resolves into the wall just raised) and, end to end through the
  real window, by `shell::acceptance::raise`.
- **Folding beats resolving per door, and that is what keeps it one source.**
  The claim is folded into the snapshot the frame paints, so `ws_path`, the tab
  bar, the centre pane and the composer read the *same* enumerated set and none
  of them knows a claim exists. It is retired ahead of the fold, so the painted
  set can never carry one workspace twice — which matters, `by_leaf` refusing an
  ambiguous name.
- **The doors that still need a path are named and few**:
  `AppModel::workspace_path` (the one resolver, `by_leaf` over the painted
  enumeration — the engine's own rule, so the two directions cannot disagree),
  `focused_workspace` above it, the pin toggle (`ui.json` keys pins by path,
  durable state whose re-keying is its own migration, so `toggle_pin` takes a
  name and resolves at the click), and the §3.6 delete dialog. `focus_agent`
  keeps taking a path because the acknowledgement it writes is keyed by one and
  the caller holds it already.
- **Two things dissolved rather than moved.** `focused_ws_name` was a leaf
  derivation off a held path and is now the focus verbatim; `startup_focus`
  answers a name while still *ranking* by path, so §4.1's derived-path order is
  unchanged.
- **What this does not do**: the tab bar still paints from the in-process
  enumeration. The refused shape — painting off a `Reply::Workspaces` while
  joining each name back through the engine's own table — is now the only thing
  standing between it and the wire, and the join is gone from the seat, so the
  migration is scope.

`Prepared::binding` stays named for the condition it always had (*once one seat's
read path is the only read path*), which bl-44e9 does not reach — the composer's
own reads are not migrated.

**Search was re-assessed and is no longer an unblocked candidate.** bl-adcb
named it one, and that reading predates §9.8. The asker's pass is **serial over
the standing set**, and §9.8 put acts on a thread of their own precisely because
*"an act runs as long as the verb behind it, so posting it on the asker's thread
would stall every standing read for the duration"*. `Query::Search` walks every
transcript in the world; a standing question is re-asked every `ASK_PERIOD`, so
riding the asker as written turns a once-per-ask walk into a 2 Hz one **and**
puts it in front of every other surface's answer. That is the poster's own
ruling pointed at a read. Two shapes remain and neither is "retire a mechanism":
give the asker a second lane for long reads (which is the poster, renamed), or
point the existing `Searcher` thread at the wire — it stays a thread, justified
by exactly the argument above, and what changes is that its read crosses the
boundary instead of running in process. The second is the smaller and is the one
to take; the win is `§1.2` compliance, not one fewer thread.

**Landed (bl-44e9), as ruled.** `Searcher` holds a seat rather than a snapshot
cell and asks `Query::Search` over the wire; it is minted by `Engine::searcher`
beside the asker and the poster, on the same window leaf and the same failure
condition, and rides `WindowWire` as its third half. Two consequences are stated
rather than discovered. **A refusal is an unreadable source** — `Found` already
carries *"each unreadable source, named with why"*, which is exactly what the
engine's sentence is, so a refused search paints the reason and never reads as
*no matches*; no new state and no second pane. And **the abandon predicate went
with the walk**: in process a superseded search stopped mid-walk, and over the
wire the engine finishes what it was asked while the answer for a question nobody
is asking any more is dropped on publish. The cost is one wasted walk on the
engine; the alternative is a cancel the boundary would have to carry, which is a
mechanism for an optimisation.

**The residual, as bl-44e9 leaves it.** The payload work is done and two more
surfaces crossed; what is left is scope with one exception, and the exception is
named rather than open.

- ~~The §11 inspector family is still read in process~~ — **landed, bl-13f9**,
  below.
- ~~Config-frozen-at has no query~~ — **spelled, bl-13f9**, below.
- ~~The Files preview reads its own two arms~~ — **dissolved, bl-13f9**, below.
- **The workspace tab bar** (class 2) — ~~blocked on the raise's unenumerated
  focus~~ **unblocked, bl-7407**: focus is a name and the raise holds a claim, so
  what is left is the migration itself.
- **The §9 config editors' own loads**, which were never one of the three classes
  (bl-f297's ruling: pointing them at `ReadConfig`/`Lineages`/`Models`/
  `Providers` changes what a config editor *is*, and is a separate design).
- ~~The seat's own `Query::Agent`~~ — **closed, bl-48ae**, below.

Nothing in that list is blocked on a decision that has not now been taken; each
is blocked on work whose shape is named.

**And the list is a list of NAMED SURFACES, which is worth saying out loud**
(bl-48ae). Every one bl-adcb's audit enumerated has now crossed or been ruled
on, and it would be easy to read that as *the window reads over the wire*. It is
not what any of these balls established. What is still derived in process is the
§11 **accessory** tail nobody put on the list: the header's live mark
(`mark_seats`) and the bottom in-flight strip (`flight_strip`), the §3.2/§3.5
ball and spend family (`conversation_ball`, `ws_balls`, `ball_spend`,
`conversation_spend`, `conversation_context`, `focused_join`, `bound_ball`,
`roster_ball_rows`), the composer's pending listing (`focused_pending`) and its
§3.3 title table (`agent_titles`), the §2.2 lineage tip (`config_tip`), the §3.6
and §8.4 fire-time gates (`delete_confirmation`, `agent_delete_confirmation`,
`move_targets`, `conversation_names`), and the altitude-0 chrome
(`tab_bar`, `strip_total`, `activity`, `staleness`, `has_alarms`). None of them
is blocked and none of them is a ruling — each is a `Reply` that does not exist
yet — but a residual that says *three surfaces plus scope* while thirty
accessors fold the snapshot on the paint thread is a doc that has stopped being
true. (**Seven of that list crossed in bl-296f and needed no new query at all;
that block, and what is left of this list, are below.**) The **affordance**
half is deliberately not on that list: `startable` /
`resumable` and the composer's fire-time inputs are the acts side of bl-adcb's
own line (*what crosses is the read, not the affordance*).

**The §11 inspector family crossed, and config-frozen-at got its query
(bl-13f9).** Six tab reads — transcript, steps, one step's records, the worktree
listing, the spine, the mail — plus the seventh the pin needed and nobody had
spelled. The window's Altitude-2 pane derives nothing at all now; what is left in
`shell/inspector` is which tab is open and the pin.

- **The four memos went with the accessors** (`tx_memo`, `steps_memo`,
  `rail_memo`, `files_memo`), which is bl-adcb's rule paid in full: an answer
  *is* the cached fold, refreshed at the asker's cadence rather than the
  derivation's, so a memo in front of one is a second cache over one fact. Two
  seats outside the inspector read the same two questions — the centre's
  auth/wound banners want the steps view, the composer's ↑ recall wants the
  transcript — and pay nothing for them: a standing question is keyed by its own
  encoded envelope, so two callers are one ask. What is left in
  `shell/ram/inspector.rs` is the V2 fork composer's choices, whose subject is a
  draft's input rather than a §11 read.
- **The Files preview's seat-side branch dissolved rather than moving.**
  `Query::Files` carries the path *and* the tree, so one ask answers the listing
  and the picked file's bytes at whichever commit the pin names. Its cost was a
  **selection change**: `Ephemera::files_sel` is the entry's path now, not its
  row index, because the number would index a listing that landed a round trip
  ago. That is `work_sel`'s own shape one tab over, and it is the general rule
  for a selection that is a query's parameter.
- **Config-frozen-at is `Query::Governing { workspace, agent, at }`**, all three
  serializations plus a help page, answered by `answer::inspector::governing`.
  `at` is the `Files` shape and for the same reason — it names *which commit*,
  which is the question and not the view — and absent it is the agent's own tip,
  resolved engine-side off the published snapshot so no seat has to know one
  before it may ask. It is the family's one read that **refuses** where its
  siblings answer absent: the derivation is a walk of the workspace's git and
  fails as `Lineages` fails, and "this conversation has no policy" is never a
  reading. Headless it is `/governing [--at <commit>]`.
- **The pin stayed seat-side and §8.5 is still unamended.** Every field it shows
  is on an answer (bl-44e9's altitude work), so `shell/inspector/rail.rs` is two
  selections and nothing else: pick a notch out of the landed spine, cut the
  landed transcript in front of it. Of VISION V1.2's four pinnable tabs, the two
  whose subject really is a *different tree* now name that tree as a query
  parameter; the other two fold over answers the seat already holds.
- **A pane declares several questions, so refusals are collected distinct**
  (`shell::wire::Said`). The family shares one address, so an unresolvable
  workspace refuses every one of them in the same sentence — five copies of which
  is a report about the transport, where one is content.

**The live tail is now half a second old at the seat, and the follow-stream
graduation was declined.** bl-ccf7 named the §7.2 live tail the one follow-class
candidate, and this is the migration where it would have paid. It does not, and
the reasoning is the ruling rather than the scope. The fold itself is unmoved —
`inspector::live_tail` is still the boundary's (bl-6233), so the two seats cannot
describe one moment differently — but the frame used to re-fold it off the
rendered snapshot every paint and now reads it at `ASK_PERIOD`, so streamed text
arrives as one row per half second rather than as characters. That is legible:
500 ms is the cadence the whole window already reads at, and a model's output is
prose a person reads, not a machine polling a machine — which is REMOTE §10's own
criterion for minting a held connection, and it is not met. Taking the
graduation would cost a **second lane on the asker**, because the asker's pass is
serial over the standing set and a held read would stall every other surface for
its duration — which is the poster, renamed (§9.7 already refused exactly that
for `Query::Search`). A mechanism for a legibility optimisation is the wrong
trade; if the tail ever needs to be smoother, the lane is the ball, not the
migration.

**A drive settles to a fixed point in one place now** (`World::drain`). bl-44e9
put that loop in `Screen::run`; the whole-window paint driver (`acceptance::mod`'s
`painted`) still counted three passes, and three stopped being enough the moment a
read *chained* — the step drill-in's sequence name is picked out of the step list
that landed, so its own question is not even declared until the pass that answers
the list. Two facts came out of writing it and both are stated rather than
rediscovered: a `Link` may never be settled twice without a frame between (the
second declares nothing and drops every answer), and a **fresh** `egui::Context`
measures before it paints — it sizes a content-sized panel and culls a scroll area
against the previous frame's rect, so the §11 chat read off the frame its answer
arrived on is read off an unmeasured layout. `painted` therefore pre-rolls two
frames, drains, and reads the frame after; `Screen` needs no pre-roll, its context
being persistent across the drive.

**What bl-13f9 did NOT take, and why it is a ruling and not scope** — *closed
by bl-48ae, below.* The seat's own `Query::Agent` —
`AppModel::focused_conversation` — stayed in process. It is
not a §11 tab read: it is the *selection's* own view, and its consumers are
frame-synchronous in a way the tab reads are not. The composer's target line names
the conversation off it (a name that blinked between "start a conversation" and
the real one for a round trip would be a regression, not a cost), the §11 focus
walk unfolds a selected member's ancestors off it (an unfold that lags the
selection by an ask period is the visible-selection invariant broken), and two act
gates read it at click time (`x` stops the selection iff `seat.stoppable`). Three
of its seven callers hold `&AppModel` rather than `&mut`, so migrating it is a
signature cascade *and* a rendering ruling about what a seat shows between the
click and the answer. That is bl-f297's dissolved class 3 — *a consumer that reads
it synchronously* — reappearing at a different noun, and it wanted the ruling
first. It was the residual that ball left, and it was filed as **bl-48ae**.

**The seat's own read is gone, and it did not become a question (bl-48ae).**
The ruling is a **split by who needs it when**, and the two shapes bl-48ae
weighed against it are recorded as declined: a *latch* (bl-f297's marks-pane
shape) answers the gates and answers neither the name nor the unfold, both of
which are wanted on the frame the selection changes; and a seat *holding its
last answer* under the new selection's name — a surface that lies for half a
second — is worse than one that is blank. What made a third shape available is
that the payload work had already been done: since bl-44e9 `Query::Conversations`
lands the **whole descent forest** with per-row rollups, and a `ConvRow` has
carried the §8.2 gates since bl-1eb0. So the frame-synchronous half was never a
wire read at all — it is a *selection out of an answer the §11 list is already
holding*.

- **`nav::convs::selection` is `visible`'s sibling** and the same kind of thing:
  a pure fold over an answered forest, `visible` keyed by which rows are open and
  this one by which row is picked. The composer's `→ message <name>` line, §11's
  ancestor unfold (`shell::focus::ancestors`), `x`'s `stoppable` and the §3.6
  danger row's root all come off it, in the frame the selection changed, with no
  new ask and nothing latched. Depth *is* the parentage — the answer is
  pre-order, so a row's ancestors are exactly the shallower rows above it, which
  is `parent_of`'s own rule iterated to the root — and `answer::agent`'s
  *absence is a value, not a refusal* ruling is kept verbatim at this altitude.
- **Nothing was added to `ConvRow`, and nothing was taken off `AgentView`.** The
  ball's fallback was a payload move; it was not needed for the four consumers
  that forced the ruling, and it would have been wrong for the residue. What the
  forest does not answer is `tip`, `marks`, `held` and `nudgeable` — facts about
  **one agent** rather than about the list, one of them a git oid and one a
  parked invocation blob, and putting either on every row of a workspace's forest
  to serve the one row that is selected is the altitude mistake `ConvRow`'s own
  definition (*the projection of one subtree to what the list paints*) exists to
  prevent. They ride a standing `Query::Agent` at the seat — the first window
  consumer that query has ever had.
- **The parity is pinned, because two projections of one derivation are two
  facts waiting to disagree.**
  `boundary::answer::agent::tests::the_forest_answers_every_fact_the_seat_reads_off_the_selection`
  asserts all nine shared fields equal between `answer::agent` and
  `nav::convs::selection` over a root, a member and an id nothing carries. That
  is what licenses the split: the two answers are one derivation, projected
  twice, and a change to either that moved them apart reddens.
- **The rendering ruling, stated once so no later reader re-derives it: a fact
  that gates a gesture is read off the forest; a fact that only paints may land
  an ask period later.** Everything on the `Query::Agent` side paints an
  affordance rather than judging one — an unpainted button cannot refuse a
  click — so a freshly-selected conversation shows its §6 marks, its §9.4 model
  row and its `Nudge` button half a second after its name. That is the same beat
  the transcript beside them has kept since bl-13f9, and it is the general rule
  at one more surface rather than a case of its own.
- **`AppModel::focused_conversation` is deleted, and the subtraction is the
  proof** (bl-1747's precedent). Its seven callers took the split; three of them
  (`shell::{focus,delete_agent,settings}`) took `&mut AppModel` with it, which is
  what a read that crosses the boundary costs a signature and is the cascade the
  ball named. `src/shell/seat.rs` is the one place either half is spelled.
- **Four bespoke acceptance drivers learned the harness ruling** (§9.8's, as
  bl-44e9 extended it to reads): `settings`, `drift`, `inbox_composer` and
  `one_rendering` each ran their own counted frames and settled no wire, so a
  seat whose subject is now an answer painted nothing in them. They pre-roll and
  then `World::drain` to a fixed point like `acceptance::painted` does. A driver
  that counts frames is fine; one that counts frames *and* reads a migrated
  surface is not.
- **One ast-grep rule gained a carve-out, keyed to the enum path rather than to
  a file.** `no-engine-tree-in-paint` forbids paint code naming `GitTree`/`Agent`/
  `CommitNode`; `Query::Agent` and `Reply::Agent` are the words a seat writes to
  declare *a payload the codec spells in both directions*, which is exactly what
  the rule exists to permit. The exception matches only an identifier whose
  scoped parent's path is `Query` or `Reply`, so `crate::git_tree::Agent` is
  still flagged everywhere it appears; the fixtures still bite and `src` is
  clean, which is the two-direction check.

**The accessory tail, part one: the folds that needed no question (bl-296f).**
Seven of the tail's accessors are gone and the boundary gained **no new
query**, which is the finding rather than the scope. The tail was filed as
*"each a `Reply` that does not exist yet"*, and for these seven that reading was
wrong: the facts were already on answers this window had landed, and what was
missing was a seat willing to read them there. Where a payload grew it grew by
**one field on an existing answer**, never by a question of its own.

- **The §11 tab bar and the attention strip are one `Query::Workspaces`, and
  class 2 is closed.** `WsRow` gained the §4.1 **pin rank** — `pinned:
  Option<usize>`, its place in the durable pin list, absent-not-null so rank 0
  is not read as unpinned — and `nav::tabs::build` now folds the answered rows
  rather than a snapshot: `Item` is deleted, the bar has no path in it anywhere,
  and `strip_total` beside it is the same rows summed. The **rank** rather than
  a flag is the whole of why no join is left at the seat: hoisting in pin order
  off a boolean would mean re-reading `ui.json` to sort, which is the seat
  joining an answer back against the engine's own document — bl-7407's refused
  shape reappearing at the field it was refused for. A pin is lawful on the wire
  where the §11 expanded set is not, and the test is DESIGN §5.3's own: a pin is
  **durable operator state**, living in the document whose §6 acknowledgements
  this row's `attention` already folds, where a fold is a viewport's.
  A **stale pin key dissolves** rather than being skipped — a key naming no
  enumerated workspace ranks no row, so the seat has nothing to drop.
- **The §3.4 raise claim needed a second projection, exactly as the echo did.**
  Migrating the bar re-opened bl-9acf on the first run: a wall `lernie new` has
  just founded is in no answer for as long as one derivation takes, so it wore
  no tab and its name resolved to nothing, and the composer's bare Enter would
  fire into the previous wall. bl-44e9's ruling covers it verbatim — *a seat's
  optimism reaches whatever that seat actually reads* — so `AppModel::raised_rows`
  is `echo::rows::with_echo`'s shape one noun up, folding the claim into an
  answered listing. It cannot double-count: the claim is retired against the
  same derivation the answer is made from.
- **The activity chip and its Dismiss fold the trail they summarize.** The chip's
  counts were an in-process fold over the window's own snapshot while the rows
  under them came off the wire — two readings of one tail, and the one number an
  operator could catch lying. `activity`/`has_alarms` are gone; the seat folds
  `Query::Ops` with `opslog::activity`, and *alarming* is now a method on the
  summary so the ichor and the button cannot come apart. The question therefore
  stands whenever the chip paints, which is every frame; expanding the trail
  asks nothing new, a standing question being keyed by its own envelope.
- **The live mark and the in-flight strip are fields on `Query::Agent`.**
  `AgentView` gained `seats` (§5.1 #28b, one entry per agent in the conversation
  with what it is doing) and `strip` (§5.1 #28's characteristics), so the §11
  header's marks, its mark and the bottom strip are **one ask**. They ride the
  per-agent answer rather than `ConvRow` for bl-48ae's own reason: a per-agent
  activity list on every row of a workspace's forest, to serve the one row that
  is selected, is the altitude mistake `ConvRow`'s definition exists to prevent.
  The **cost is stated**: the strip's elapsed segment is stamped when the answer
  is derived rather than re-rendered per frame, so it advances at `ASK_PERIOD`
  — bl-13f9's live-tail ruling at a coarser unit, a figure that ticks in seconds
  read at half-second cadence.
- **The header's conversation ball was already on the row.** `ConvRow::ball` has
  carried the §3.3 stamp resolved through the §3.5 join since the list was
  written, so `conversation_ball` and the `resolve_conv_ball` behind it were a
  second read of an answered fact. It is `Selection::ball` now — the one field
  of that fold with no `AgentView` twin, deliberately, since a second copy on the
  per-agent answer is precisely the disagreement the parity test exists to catch.
  It lands in the frame the selection changes, which is where the ball line
  belongs: it sits under the name, and arriving late would read as belonging to
  the previous conversation.
- **One more bespoke driver learned the harness ruling** (`acceptance::bands`),
  for bl-48ae's reason at the fifth site: it counted frames and censused which
  bands each window size seats, and the strip's subject is an answer now, so the
  band it exists to place was missing for the transport's reason. It drains to a
  fixed point like the rest.

**The residual, as bl-296f leaves it, and it is the accessory tail minus the
seven above** — filed whole as **bl-b4b5**, and closed by it below.

- **The §3.2/§3.5 ball-and-spend family** — `ws_balls`, `roster_ball_rows`,
  `bound_ball`, `focused_join`, `ball_spend`, `conversation_spend`,
  `conversation_context`. The altitude is the open question and it is worth
  taking as one: `Query::Balls` already answers the join rows, but it answers
  them **path-typed** (`JoinRow` carries `project` and `workspace` as
  `PathBuf`s — §8.1's list did not reach them), so a seat holding a §3.1 *name*
  cannot select its workspace's balls out of that answer without the join
  bl-7407 refused. So this group is one workspace-addressed question answering
  the bound balls with their figures, not eight small ones, and the §8.1
  narrowing of `JoinRow` rides with it. The two conversation-scoped figures
  (`conversation_spend`, `conversation_context`) are `AgentView`'s shape — facts
  about one conversation's subtree, like `flight` and now `strip`.
- **The §3.6/§8.4 fire-time gates** — `delete_confirmation`,
  `agent_delete_confirmation`, `move_targets`, `conversation_names`. The
  rendering split is already exact and must stay so: the chokepoint re-derives
  every confirmation **fail-closed** at fire (§9.8, bl-1747), so the seat's copy
  is a painted affordance and may land an ask period late — which is what makes
  these ordinary reads rather than a class of their own.
- **The misc singles** — `focused_pending` (the composer's §5.1 #11 queue),
  `agent_titles` (the §3.3 table a seat resolves a *third party* against), and
  `config_tip` (the §2.2 lineage tip the §9.4 row's drift clause reads).
- **`staleness`, and the one thing in the tail that is not scope.** The §7.2
  staleness line is the age of `Snapshot::derived_at`, an `Instant`; the
  chokepoint takes `now_unix`, an `i64` minted at the process boundary
  precisely so every derivation is deterministic under test. So this read cannot
  be answered without the snapshot carrying its completion as a **wall-clock
  stamp**, which is a payload-and-clock change rather than a read migration, and
  `growth_note` beside it is painted in the same two-line loop and moves with it.

**The accessory tail, part two: the tail is empty (bl-b4b5).** Every accessor
above is gone, the boundary gained **one** query, and the finding is the ratio:
of four groups, three were folds over answers this window was already holding
and only one was a question nobody had asked. Where a payload grew it grew by a
field, which is bl-296f's own rule applied a second time.

- **The ball-and-spend family is one workspace-addressed question**
  (`Query::WorkspaceBalls { workspace }`), and the eight accessors were never
  eight questions. The answer is the §11 balls section's whole content — every
  ball the workspace holds, with its badge, its project, its claimant and **its
  §3.5 figure** — and `roster_ball_rows` / `bound_ball` are pure selections out
  of it (`nav::balls`), exactly as `visible` and `selection` are selections out
  of the forest. The figure rides the row rather than earning a read of its own
  because it is a filter over the very `Snapshot::bills` walk the listing is made
  from: asking separately would be the two-readings-of-one-derivation defect
  bl-296f closed at the activity chip. The two conversation-scoped figures are
  fields on `AgentView`, for `seats`/`strip`'s reason, and they land an ask
  period after the name like everything else on that answer.
- **`JoinRow` says names now, and the last path-typed payload field but one is
  gone** (§8.1). `project` is the §5.1 #1 wire name and `workspace` the §3.1
  leaf — the *same two words* `Action::Close`/`Assign`/`Move` already take — so
  the seat's ball row hands its verbs a name it was answered rather than a path
  it resolved, and `Reply::Balls` is finally readable by a client that holds no
  world. `BoardRow` narrowed with it (it copies both fields off the join), and
  `delete::Claim` did too, which is what let the §3.6 confirmation become
  buildable by a seat at all. The engine resolves either back through
  `Snapshot::project_path` / `ws_path`, the one seam that owns the round trip.
- **The fire-time gates needed no query, and that is the finding.** Every one
  was already answered: `move_targets` is the enumeration minus the holder
  (`nav::tabs::move_targets`), the §3.6 scope is `WsRow`'s own `kind`, the
  workspace confirmation is the enumeration + the descent forest + the balls
  listing folded into the *same* `Confirmation` type the chokepoint gates on
  (`delete::confirmation_of_rows`), the conversation one is the forest's own
  subtree run (`delete::agent::confirmation_of_rows`), and the §3.3 occupied set
  is every answered row's stored name. Two constructors, one type, one
  `armed`/`refused` — a second type would have been two representations of one
  gate. **The chokepoint's re-derivation is untouched and stays authoritative**;
  what moved is the painted affordance, and it may be an ask period behind
  because an unpainted button cannot refuse a click.
- **The misc singles split the same way.** `agent_titles` is `Titles::of_rows`
  over the landed forest — a row's `display` *is* the ladder's answer for its own
  agent, so the answered list already carried the table. `focused_pending` is
  `Query::Inbox`' answer, which is the §11 Inbox tab's own standing question, so
  the composer and the tab are one ask; it re-reads the deposit directory where
  the accessor folded the tree's gathered copy, which is the same rows one
  derivation fresher. And `config_tip` — bl-1eb0's named residual — became a
  **field on `WsRow`**: the §2.2 lineage tip is a fact about a *workspace*
  exactly as `running` is, and `Query::Workspaces` is the question that answers
  those. Its altitude objection does not apply, since this is not a per-agent
  fact pushed onto every row of a list to serve one.
- **The §3.4 echo needed a third projection, for bl-44e9's reason a third
  time.** The composer's queue reads an answer now, and `echo::compose`'s fold
  lands on the *snapshot* — so a typed message would have vanished for an ask
  period between Enter and the deposit file, which is the §11 faded-send ruling
  deleted at the one surface it exists for. `AppModel::echoed_pending` folds the
  echo's deposit onto the answered listing; all three projections stay in the one
  module, which is `compose`'s own single-source argument kept.
- **The staleness stamp is the payload-and-clock change the ball named, and it
  is a subtraction.** `Snapshot::derived_at` was an `Instant`; it is
  `derived_at_unix` now — one stamp, in the unit both ends of the wire speak
  (DESIGN §7.2). The two §7.2 lines then ride `Query::Workspaces`' answer beside
  the rows, because that is the one read every window makes every frame and the
  currency of an answer costs it nothing to say; a question of its own would have
  bought a round trip per ask period for two lines that are usually absent. They
  cross as the **rendered** lines for `FlightStrip::facts`' reason: the wording
  is one derivation's, over a bound the operator tunes in `cadence.yaml`, and a
  wire spelling of the parts would be a second place that decides when a
  derivation is late.
- **A test double had to grow a wall clock, and the reason is the ruling.**
  `FakeClock::stamp` is the opaque `"TS"` sentinel every ops assertion spells, so
  the default `Clock::unix` would have pinned every derivation's completion at
  epoch zero and no §7.2 staleness line could ever be observed under a clock the
  test moves. The fake answers `unix` from its own advanceable instant instead —
  epoch-relative, because every reader of it computes a *difference*.
- **One more driver pair learned the harness ruling, and a second kind of
  settle appeared beside it.** `acceptance::painted` and `inbox_composer::Frames`
  both drain the wire already; what they did not settle is the composer's fold
  line, which is last frame's *painted* content height eased over `i.time`
  (bl-929d) and therefore changes on the frame the inbox answer lands — after
  the drain's fixed point. Both now run trailing **clock** frames. `Frames`
  settles the wire between them because its beats deposit between settles;
  `painted` deliberately does not, because a settle on a frame the steps answer
  has not reached reads as *not wounded* and closes the §7.3 grace window
  (`app::grace`).

**What is left of §9.7, in full.** Two residuals, and neither is scope:

- **The §9 config editors' own loads**, which were never one of the three
  classes (bl-f297's ruling: pointing them at `ReadConfig`/`Lineages`/`Models`/
  `Providers` changes what a config editor *is*, and is a separate design).
- **`Prepared::binding` stays a path** (§8.1's ruling, unchanged), affordable to
  dissolve only once one seat's read path is the *only* read path — and the
  composer's own fire-time inputs (`startable`/`resumable`, the §8.5 line
  context) are the acts side of bl-adcb's line, which no read ball reaches.

Everything else the read path ever named has crossed, been folded, or been
ruled on.

- ~~Gestures are still dispatched in process~~ — **the design and the first
  group landed, §9.8** (bl-4841). An act is a declaration whose receipt lands
  later, under a ticket the frame mints; thirteen gestures cross the wire and
  four are left, each named there with the frame-side fact its receipt gates.
  `AppModel::dispatch` stayed until those four moved; **bl-1747 moved them and
  deleted it** (§9.8), so the window's dialogue is wire-only in full.
- **`Prepared::binding` stays a path** (§8.1's ruling, unchanged, re-checked
  bl-44e9). The shape that dissolves it — `Prepared` becoming opaque to the seat
  — is affordable only once one seat's read path is the *only* read path, and the
  composer is not migrated yet. It is a disclosure of an engine-side directory
  name to a seat already told the workspace it belongs to, and it stays bounded
  and stated.
- ~~Search rides a snapshot of its own~~ — **landed, bl-44e9**: the `Searcher`
  asks the engine.

### 9.8 The acts, and the receipt that lands later (bl-4841, closed bl-1747)

§9.7 moved the window's *reads* onto the wire and left its *acts* where they
were: every gesture still went through `AppModel::dispatch`, the in-process
`boundary::dispatch` over this instance's own `ui.json` — the second execution
path §1.2 exists to refuse, and the larger half of the residual by volume. It
could not simply be re-pointed, because the click-glue read each act's `Reply`
**synchronously** and branched on it in the same frame, and a frame may never
wait on a socket. Four questions were answered before anything was built.

**1. The receipt's identity is a minted ticket, and could not be anything else.**
The read path keys a standing question by its own encoded envelope, because
asking twice is asking once. An act cannot borrow that rule: a gesture is not
idempotent — two clicks of Nudge are two nudges — and a resend is never free, so
nothing about an act's own bytes can be its handle. Something has to *mint* one.
It is a `Ticket`: a number from a counter the frame owns, minted at the send,
spent at the read, and mintable by nothing else. What makes it survive the
repaints between the two is that the surface holds it in **its own RAM**, beside
the status line it already held across frames — the frame it was clicked in is
long gone by then.

It is deliberately **not** bl-024b's invocation id. That handle names a slot in
the engine's mailbox: a fact about the world for the seconds a tool takes,
readable by the client that posted it, swept after an hour. A receipt is a fact
about *this window between two frames* — it never crosses the wire, and nothing
but the frame that minted it can name it.

**Every ticket earns exactly one receipt, so "never answered" does not exist.**
The poster is the only thing that can answer and it answers on every path it
has: the engine's reply, the engine's refusal, an answer the codec could not
read, a socket that would not open. A send that cannot even reach the poster —
a window with nothing behind its end of the channel — is answered *in the send*.
So there is no timeout, no clock and no expiry sweep in the act path: the
special case dissolved rather than being answered, and the one bound that
remains is `Seat`'s own read timeout, which turns an engine that has stopped
answering into a sentence.

**2. In flight is the sentence the click already wrote, with a mark on it.**
The surfaces that read a receipt at all are the four that paint one — the marks
pane, the picker's two writes, the lineage config editor — and each already held
a status line across frames. So the fire writes **what a clean landing means**,
which is known at the click, and the line carries an ellipsis until the receipt
lands: a clean receipt drops the mark and moves nothing else, and anything else
appends the reason. No second phrasing to learn, and no state the reader has to
be taught. Every other act paints nothing new, because none of them ever did:
their durable record is the `ops.jsonl` line the §7.3 banner reads back (INV-2).

**The §3.4 pending echo is neither replaced nor generalized, and that is the
ruling.** bl-adcb found that a migrated read paints the derivation, so the
window's optimism — the echo, the §7.2 live tail — is a fold a seat makes for
itself, and asked whether an act's receipt is what the echo was standing in for.
It is not: they are two facts at two rates. An **echo** stands in for a
*derivation* that has not caught up, and is retired by the world showing the
conversation. A **receipt** stands in for an *engine* that has not answered, and
is retired by the answer. They compose — a fire may hold both — and collapsing
them would make a conversation appear on the strength of a gesture having been
*sent*. What does change is the echo's trigger: one held today on a synchronous
`Ok` must be held on the receipt instead, which is the move the fire-and-hold
flows make when they migrate.

**3. `ui.json`: the engine writes and the window adopts, which is the ordering
two faces already have.** In process, dispatch took `&mut self.ui` — the
window's own document. Over the wire the engine opens the file fresh per
gesture, writes, and the §7.2 worker carries the bytes back as an external
change; `adopt_ui` takes them wholesale unless they hash to what this window
last wrote, which an engine's write never does. So an act's own write reads back
as an external change and the window **adopts** it rather than fighting it —
whole-file last-writer-wins is I5, and the window's own writes are write-through
at each set, so the file the engine opens already holds them.

What remains is the window's own direct writes (the per-frame §6 ack, panel
sizes) landing between the engine's write and the window's adopt, where a
re-save carries the older copy back. That race is **not new** — it is exactly
the one the `gestures/` inbox consumer already has with the frame — and it is
bounded by the derivation cadence. It was untouched by bl-4841's landing — no
act that crossed there wrote `ui.json` at all — so the ordering was stated
rather than discovered, and **bl-1747 paid the check**. The writers turned out
to be three, not four: `MarkSeen` and the two §3.6 deletes. `Prompt` reads the
§3.5 ceiling out of the document and writes nothing, so the file it opens is
one it never dirties. All three cross now, and the residual's own entry records
what the check found: the prune lands whole on the next pass and nothing
fights.

**4. Fire-and-hold is the precedent in structure, and not the mechanism.** The
start claim and the pending echo already hold state across frames awaiting a
derivation, and that is the shape everything converges on — a click that cannot
finish is a fact held until something retires it. But they are not the
mechanism, because what retires them is the world catching up, not an answer
arriving. They survive unchanged; the general handle is the ticket.

**What landed.**

- `src/wire/post.rs` and `src/wire/poster.rs` — the act path's two halves, the
  read path's shape pointed the other way.
- `src/app/acts.rs` — the model's act half: post a gesture, hold what its
  landing re-derives, hand the receipt to whoever kept the ticket.
- `src/shell/act.rs` — the shell's one spelling of an act, `src/shell/wire.rs`'s
  twin: `fire` for the act whose receipt is nothing, `Held` for the act whose
  receipt is a sentence.
- `src/engine/window.rs` — the engine's hand-overs to a window, both ends off
  the one loopback seat, split from `engine.rs` at DESIGN §12's budget.

**The poster is a thread of its own, and that is a ruling.** An act runs as long
as the verb behind it — a `bl close` runs a project's gate — so posting it on
the asker's thread would stall every standing read for the duration, which is
the frame going blind because the operator clicked something. Acts are
serialized among *themselves*, one connection at a time in the order they were
clicked, which is strictly less blocking than the in-process dispatch it
replaces: there, the **frame** waited. Connection-per-act, like the asker, so
§10's ruling is untouched.

**The `Cli` pair evaporates as a surface migrates**, and it is worth recording
because it is the split becoming visible in the code. A dispatched act needed
`boundary_deps` — the verb binaries *this box* resolved; a posted one carries
the gesture and nothing else, because the engine owns the binaries and a seat
never did. Ten click-glue files lost the parameter in this landing and none
gained one. A remote seat could fire every act listed below.

**What crossed in bl-4841**, by the line that decided it then — *an act whose
receipt is nothing, or a sentence, crossed; an act whose receipt gates a
frame-side state change did not*. That line is retired: bl-1747 crossed the
other side of it too, so **everything crosses** and the list below is history
rather than a boundary.

- the §8.2 short verbs — `Stop`, `Scan`, `Nudge`, `AnswerHold`;
- the ball verbs — `Close`, `Release`, `Move`, `Assign`;
- the V2 fork cohort — one `Fork` per candidate, in the order composed;
- the four that paint a sentence — `SetMarks`, `PickModel`, `Retarget`,
  `ApplyConfig`.

**The residual, closed (bl-1747): none.** Four were left, and none of them was
more of the same move — each is recorded here with what its receipt turned out
to owe, because the answers are the ruling and the code is only where they live.

- **`Action::Message`.** Its reply gated two frame-side facts in one breath: the
  draft clearing (a refused send leaves the words on screen to be fixed) and the
  §3.4 pending echo. The rendering question — *does a draft stay up for one
  round trip?* — is answered **yes**: it stays until the receipt, and a refusal
  keeps it for good. That is §5.3's own sentence read literally (*a draft is RAM
  until sent*) with "sent" meaning what the engine says rather than what the
  click hoped, and it is the only answer that cannot lose typed text. The echo
  moves with it, by ruling 3: same fact, same rate, new trigger.
- **`Prepare` / `Prompt`.** The frame-only aftermath — the §3.4 workspace
  adoption, the held start claim, the §3.3 mint seed a landed fire spends —
  rides the receipt now, and the typed doors the window used to enter through
  are gone from the seat side (the chokepoint still has them; the `Prepare` and
  `Prompt` arms are their one caller). **The seed forced a reframe and got the
  right one.** It was a `Deps` field, which is the engine's to fill — but the
  window's greyed §3.3 preview is drawn off it, and a seat that predicts a name
  must be able to fire *that* name. It rides `Action::Prompt` as
  `seed: Option<u64>`: a parameter of the gesture, not of the environment, with
  `None` for a caller that predicted nothing and one stamp-derived default at
  the one door that mints. `Deps::mint_seed` is deleted, and with it the three
  intakes that each spelled the same default. **The composer's Enter is two
  acts**, the `Prompt` posted when the `Prepared` lands; that chain is held in
  one place so the draft it composed empties when the *second* one lands, and
  the gesture is judged once exactly as the synchronous pair was.
- **`DeleteWorkspace` / `DeleteAgent`.** The modal holds a ticket beside the
  sentence it already held, disarms while one is outstanding, closes on a clean
  receipt and keeps the reason otherwise. The fire-time re-derivation stays
  fail-closed and unmoved — it is inside the chokepoint, so it now runs where
  every other seat's does. **Answer 3's ordering was checked here and holds.**
  These are the only acts left that write `ui.json` (`Prompt` reads the ceiling
  and writes nothing), so this is where the check was owed: the engine opens the
  file fresh and prunes, the window keeps its own copy inside the frame, and the
  §7.2 worker carries the pruned bytes back as an ordinary external change that
  `adopt_ui` takes wholesale — the window's own last write being a different
  document, so the echo test never suppresses it. Nothing fights, and the
  regression naming that is
  `app::tests::deletes::the_engines_prune_reaches_the_window_as_an_ordinary_external_change`.
- **The slash line's act arm.** Same shape as `Message` and answered the same
  way: the typed line clears on a clean receipt and is kept, with the reason
  under it, otherwise. Its **query** arm is untouched — a read is answered in
  place or latched (§9.7), and only the act arm had a receipt to wait for.

**`AppModel::dispatch` is deleted, and the subtraction is the proof.** It was
exactly the size of that list. With it went `AppModel::fire_prompt`,
`prepare_start`, `delete_workspace` and `delete_agent` — the frame's four
private entrances into the chokepoint — and `Deps::mint_seed`. The window's
dialogue is **wire-only in full**, and §1.2 is closed to the letter: the engine's
own intakes (the deposit consumer, the listener) are the only callers of
`boundary::dispatch::dispatch` that remain, which is what having one execution
path means. The `Cli` pair went on evaporating as it was always going to
(§9.8's own observation): eleven more click-glue files lost it, and what still
carries it does so for the §8.5 line's *query* arm and nothing else.

**What it unblocks for bl-f297.** That ball's third blocked class — *a read with
nowhere to paint a refusal, or a consumer that reads it synchronously* — put the
§9 config family here, on the grounds that it is answered at click time. It
turns out to need no receipt at all: a click-time read is **a standing question
with a latch**, the click turning it on and the surface declaring it while it
paints, which is the same rule that makes a collapsed pane free. And where such
a read is really a *write's* own re-read — the marks pane's branch — the receipt
already carries it: `Reply::Marks` is the branch re-read after the write, which
is §5.3's receipt discipline (*a receipt is a re-read, never an echo*). One
fewer blocked class.

**The acceptance world answers acts too**, and since bl-1747 it does so for
*every* world rather than the wired ones: the two channel ends are taken at the
fixture's own boot (`shell::acceptance::fixture::world`), because a fixture with
nothing behind its end of the channel is a window whose every gesture is refused.
Two chokepoints — the standing questions through `AppModel::answer`, the posted
acts through `boundary::dispatch::dispatch` over a `ui.json` opened fresh per
gesture — with the transport stood in for and no second dispatch added. A read
that reaches the act queue, or an act that reaches the standing set, is refused
with the sentence saying so rather than quietly answered by the other half.

**A driven frame settles its own acts**, and that is a harness ruling worth
stating. A gesture's aftermath lands on a *later* frame reading the receipt, and
the start family is two acts deep. A window pays that in ask periods; a drive
would have to pay it in counted frames at every call site, which is a fact about
the transport leaking into every test that has nothing to do with it. So
`Screen::run` pays it once — answer what the frame posted, repaint, repeat while
anything was answered — and the loop terminates because only a receipt can post
an act and nothing in the window posts one unprompted. The engine's substrate
binaries live on the **world**, not on the driver, for the same reason a posted
act carries no `Cli`: they are the engine's, and a seat never had them.

## 10. Open questions (living)

- ~~The follow/streaming frame shape~~ — settled by bl-b6fa (§3): every answer
  is a frame stream terminated by a zero-length frame, so a follow-class read
  is the general path with more frames.
- ~~Whether a seat holds one connection across gestures~~ — **settled by
  bl-ccf7: it dials per ask, and the server already loops.** Two facts decide
  it. The listener's connection thread is `while let Ok(Some(request)) =
  read_value(…)` — request, reply stream, terminator, again — so a held
  connection is not a wire change, a framing change or a server change: it is
  `Seat::ask` keeping the `StreamOwned` it currently drops, and it can be taken
  the day it pays. And it does not pay yet: nothing polls. `yog seat` is
  one-shot by construction, and the seat that *would* poll is the window, which
  §9.7 says is not a seat until a ruling lands. Holding a connection ahead of a
  poller would buy a reconnect ladder, a liveness question and a
  connection-scoped identity — REMOTE §4 derives the client per request
  precisely so none of that exists — in exchange for a handshake nobody is
  paying yet. The criterion for revisiting is stated rather than felt: **when a
  seat's ask rate exceeds human cadence.**

  **Re-asked and kept by bl-ae05, now that a poller exists.** The window is that
  seat, and its rate is one pass per 500 ms over its standing set — which is
  human cadence by construction rather than by luck, and the criterion is
  therefore still unmet. Nothing was added to hold a connection: a handshake per
  ask on loopback is microseconds, and the reconnect ladder, the liveness
  question and the connection-scoped identity a held connection buys are all
  still costs with no payer. Revisit when a surface needs a rate an operator
  could not read at — which is what a follow-class read is for, and it holds its
  own connection by asking (§3).
- ~~When polling graduates to a follow-class query~~ — **settled and built by
  bl-024b**: `Query::Invocations` is the first follow-class read with a
  consumer, and it needed no wire change. What stays open is only whether the
  *transcript* tail follows it. The reasoning, kept: bl-ccf7 said there was one candidate —
  the live model-call tail (§9 step 1's folded `Stream`), the one read whose
  subject changes faster than an operator looks, every other read being a
  projection of a snapshot the derivation worker republishes on its own
  schedule. **bl-c907 found the second, and it is the one with a consumer: a
  tool host waiting for its next invocation** (§3's routing-frame ruling). It
  needs no wire change either, and unlike the tail it has a caller that cannot
  be written any other way — a poll would be a machine asking a machine, which
  is the ask rate the bullet above set as the criterion.
- **Whether a tool host is a seat**, and whether it should run more than one
  invocation at a time. New with bl-c907, sharpened by bl-024b. A client may be
  both (§2 says the work laptop usually is), and the two are one identity
  holding connections at different moments, which the presence refcount already
  handles. What bl-024b found is that the question is not really about a
  *dedicated* connection: `yog tool-host` executes **serially**, so it holds no
  connection at all while a tool runs, and a second invocation simply waits in
  the mailbox. Concurrency there would want a worker pool on the client and
  nothing on the engine — the mailbox already hands out every queued
  invocation at once. Deferred until a host has two tools worth overlapping.
- ~~The tool-advertisement schema (the exact shape of name/description/input
  schema)~~ — settled by bl-4e08 (§5.1): three fields, `name` a single path
  component, `description` one string, `input_schema` the JSON Schema verbatim,
  in a `tools.json` document per client. How availability is spelled was already
  settled (§5, bl-bc7c): definitions frozen in the prefix, presence answered at
  invocation.
- Whether pins are world facts or pane facts (§7 defaults them to world, and
  bl-8bbc landed that default — moving a pin is one accessor, not a migration).
- Certificate hygiene: lifetime, rotation cadence, whether the CA distrusts
  or registrations carry the whole revocation load.

## 11. Rejections

Recorded so they are not relitigated:

- **A second in-process face for the local window** — the split is the point;
  one method, one channel (§1.2). Challenged by bl-ccf7 (§9.7) and **upheld by
  operator ruling 2026-08-14 (bl-ae05)**: the challenge was that an in-process
  *transport* to the one `Answerer` is not a face, and the answer was to take
  the front door instead. The window is a wire client of localhost, nothing
  in-process was added, and this entry stands as written.
- **Any in-channel authentication besides the client certificate** —
  passwords, bearer tokens, OAuth flows: each is an enrollment or secret
  surface §1.3–§1.4 exist to exclude.
- **An in-channel pairing/enrollment protocol** — bootstrap is out-of-channel
  by ruling; convenience flows reopen the exact surface mTLS closed.
- **A separate client crate or binary for v1** — the multi-call single binary
  stays (DESIGN §0); a foreign-language client is a future consumer of the
  wire, not a reason to split the crate now.
- **Syncing or mounting the world at clients** (network filesystem, rsync,
  shared checkout) — the world never leaves the server; clients receive
  replies, not files. Two full engines over one networked disk is the
  instance-coordination shape DESIGN §14 rejects, kept rejected.
- **Per-tool or per-verb ACLs in v1** — registration is workspace-grade
  trust; a finer policy layer is speculative until a second human exists.