newt-tui 0.8.0

Newt-Agent TUI surfaces (ratatui): code mode + pilot mode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
//! `/settings` — **one typed form for the knob space** (#1981 slice 1).
//!
//! The operator's directive: aggressively reduce the slash surface. The line
//! that decides each command is **a verb that merely SETS A VALUE is absorbed;
//! a verb that PERFORMS something stays** — and `/vi`, `/emacs`, `/nano`,
//! `/edit-mode` are four verbs for one enum-valued setting.
//!
//! # Why a form and not a fifth verb
//!
//! A form is an `InteractionDefinition`, so it renders through C0's plain
//! projection, through the RichTUI span projection, and on the web — none of
//! which a slash verb does. `crew_form` (D1a, #1885) is the precedent this
//! copies rather than reinvents: the flow is a sequence of definitions asked
//! through C1's seam, so it never carries a private console path and the unit
//! tier stays fully mocked by injecting [`Ask`].
//!
//! **No panel this slice.** `/settings` ships through the plain/typed path
//! only. RegionLease (#1986) has landed and a leased inline panel is a later
//! upgrade; a chooser you drive blind is not a consolidation.
//!
//! # One mutation path, and now one receipt (#1965)
//!
//! [`apply`] is the only place a setting changes, and it is PRIVATE.
//! [`apply_and_record`] is the only way to reach it: the form, a deep link
//! (`/settings edit-mode vi`), the deprecated verbs (`/vi`), the dial setters
//! (`/psyche tenacity`) and `/psyche obsessive` all go through it, and each
//! one writes a content-addressed `newt_core::settings_receipt` line.
//!
//! That privacy is the design. Slash commands reached no receipt path at all —
//! thirty-four commands mutating session state with nothing durable behind
//! them, which is how a round-cap escalation to effectively unlimited left no
//! record in config, receipts, turns or artifacts. Collapsing the knob verbs
//! into one chokepoint is what turned "instrument thirty-four call sites" into
//! "instrument one", and making the mutation unreachable without the recorder
//! is what stops the thirty-fifth from reintroducing the gap.
//!
//! # The deprecated verbs still work
//!
//! Operators have muscle memory. `/vi` does not become "unknown command" and
//! does not become a lecture — it applies the setting AND names where the
//! setting now lives. Routing it through [`apply`] rather than leaving the old
//! arm in place is what keeps "one mutation path" true rather than aspirational.

use newt_core::interaction_surface::SurfaceInteraction;
use newt_core::settings_receipt::SettingChange;
use newt_core::HumanQuestionOutcome;
use newt_interaction::InteractionDefinition;

/// How the form reaches the operator: C1's seam, and nothing wider — the same
/// type `crew_form` uses, for the same reason.
pub(crate) type Ask<'a> = &'a dyn Fn(&SurfaceInteraction) -> HumanQuestionOutcome;

/// A setting `/settings` carries.
///
/// Two families so far: the editor mode (slice 1) and the effort dials (this
/// slice). Every one of them is enum-valued with a small closed vocabulary,
/// which is exactly the shape the operator's line singles out — *a verb that
/// merely SETS A VALUE is absorbed; a verb that PERFORMS something stays*.
///
/// `/psyche` is therefore NOT absorbed: its panel and its status view perform,
/// and they keep performing. What moved here is its two text setters, which
/// only ever set. See `docs/decisions/slash_command_target_set.md`.
/// What a field will accept.
///
/// Four of the five settings are a closed vocabulary, and a menu is the right
/// surface for those. The round cap is a NUMBER, so it gets a text field —
/// widening the form rather than bolting a second dispatch beside it, which is
/// what forced `/rounds` to keep its own unrecorded write until now.
pub(crate) enum ValueSpace {
    /// A closed vocabulary, rendered as a menu of `(value, what it means)`.
    Choice(Vec<(&'static str, String)>),
    /// A number in `min..=max`, plus one token that RELEASES the setting back
    /// to its derived default (`auto`).
    Number {
        release: &'static str,
        min: usize,
        max: usize,
    },
    /// **Free text, taken verbatim.**
    ///
    /// Not a vocabulary and not a number: a prompt template is whatever the
    /// operator typed, trailing space and all. `release` releases the setting
    /// back to config the way `auto` does for a number, so "go back to my
    /// `[tui] prompt`" stays a value rather than becoming a second verb.
    ///
    /// The important half is in `run`: a `Text` field reads the RAW remainder
    /// of the line, because trimming it would silently eat the trailing space
    /// in `/settings prompt "❯ "` — which is the single most likely template
    /// anyone writes.
    Text {
        release: &'static str,
        placeholder: &'static str,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Field {
    EditMode,
    Tenacity,
    Initiative,
    Cognition,
    Thinking,
    Nudge,
    Markdown,
    Mode,
    Prompt,
    Compaction,
    Detail,
    Posture,
    Rounds,
    Prefix,
    GitAuthor,
    GitConfig,
    GitSigning,
}

impl Field {
    /// Every field the form offers, menu order.
    pub(crate) const ALL: &'static [Self] = &[
        Self::EditMode,
        Self::Tenacity,
        Self::Initiative,
        Self::Cognition,
        Self::Thinking,
        Self::Nudge,
        Self::Markdown,
        Self::Mode,
        Self::Prompt,
        Self::Compaction,
        Self::Detail,
        Self::Posture,
        Self::Rounds,
        Self::Prefix,
        Self::GitAuthor,
        Self::GitConfig,
        Self::GitSigning,
    ];

    /// The deep-link token: `/settings <name> [value]`. This is ALSO the
    /// registry's command name, which is what lets `apply` ask the registry
    /// where this setting's receipt lands rather than carrying a second list.
    pub(crate) fn name(self) -> &'static str {
        match self {
            Self::EditMode => "edit-mode",
            Self::Tenacity => "tenacity",
            Self::Initiative => "initiative",
            Self::Cognition => "cognition",
            Self::Thinking => "thinking",
            Self::Nudge => "nudge",
            Self::Markdown => "markdown",
            Self::Mode => "mode",
            Self::Prompt => "prompt",
            Self::Compaction => "compaction",
            Self::Detail => "detail",
            Self::Posture => "posture",
            Self::Rounds => "rounds",
            Self::Prefix => "prefix",
            Self::GitAuthor => "git-author",
            Self::GitConfig => "git-config",
            Self::GitSigning => "git-signing",
        }
    }

    pub(crate) fn label(self) -> &'static str {
        match self {
            Self::EditMode => "line-editor key bindings",
            Self::Tenacity => "tenacity",
            Self::Initiative => "initiative",
            Self::Cognition => "cognition",
            Self::Thinking => "reasoning display",
            Self::Nudge => "action-pressure nudges",
            Self::Markdown => "markdown rendering",
            Self::Mode => "operating mode",
            Self::Prompt => "input prompt template",
            Self::Compaction => "automatic-compaction trigger",
            Self::Detail => "tool-output detail rows",
            Self::Posture => "permission posture",
            Self::Rounds => "tool-call round limit",
            Self::Prefix => "meta prefix key",
            Self::GitAuthor => "sandbox git commit author",
            Self::GitConfig => "sandbox git settings",
            Self::GitSigning => "harness commit signing",
        }
    }

    /// The allowed values, as `(value, what it means)`.
    ///
    /// The dial levels and their descriptions come from `Tenacity::all()` /
    /// `Initiative::all()` / `Cognition::all()` — the same lists `/psyche …
    /// list` renders. A second
    /// hand-written copy here is how the form and the dial would drift apart
    /// the first time a level is added.
    pub(crate) fn value_space(self) -> ValueSpace {
        use newt_core::role_profile::Cognition;
        use newt_core::{Initiative, Tenacity};
        let owned = |pairs: &[(&'static str, &str)]| -> ValueSpace {
            ValueSpace::Choice(pairs.iter().map(|(v, d)| (*v, (*d).to_string())).collect())
        };
        match self {
            Self::EditMode => owned(&[
                ("vi", "modal editing (default)"),
                ("emacs", "emacs-style editing"),
                ("nano", "nano-style editing"),
            ]),
            Self::Tenacity => ValueSpace::Choice(
                std::iter::once(("auto", "normal, unless set explicitly".to_string()))
                    .chain(
                        Tenacity::all()
                            .into_iter()
                            .map(|t| (t.label(), t.describe())),
                    )
                    .collect(),
            ),
            Self::Initiative => ValueSpace::Choice(
                std::iter::once((
                    "auto",
                    "inherit from the persona / config / model family".to_string(),
                ))
                .chain(
                    Initiative::all()
                        .into_iter()
                        .map(|i| (i.label(), i.describe())),
                )
                .collect(),
            ),
            Self::Cognition => ValueSpace::Choice(
                std::iter::once(("auto", "follow the active persona (default)".to_string()))
                    .chain(std::iter::once((
                        "off",
                        "send no reasoning controls, overriding any persona".to_string(),
                    )))
                    .chain(
                        Cognition::all()
                            .into_iter()
                            .map(|c| (c.label(), c.describe().to_string())),
                    )
                    .collect(),
            ),
            Self::Thinking => owned(&[
                (
                    "fold",
                    "bounded reasoning, then how long it thought (default)",
                ),
                ("stream", "every reasoning line, unbounded"),
                ("off", "just the answer"),
            ]),
            Self::Nudge => owned(&[
                ("on", "action-pressure steering enabled (default)"),
                ("off", "no narration rescue / workflow repair / plan pushes"),
            ]),
            // `auto` is a real choosable value, not the absence of one: it
            // means "follow colour", which is a different state from `on`
            // on a pipe. The absorbed verb offered all three and so does this.
            // Each choice says what it would shadow, because that is the
            // whole trade: tmux/herdr semantics on a chord of your choosing.
            Self::GitAuthor => owned(&[
                (
                    "agent",
                    "the agent identity, so sandbox commits never pass for yours (default)",
                ),
                ("operator", "your own git name and email"),
            ]),
            Self::GitConfig => owned(&[
                ("baseline", "git's own defaults; nothing copied (default)"),
                (
                    "environment",
                    "copy your ~/.gitconfig's plain settings now (never aliases, credentials or includes)",
                ),
            ]),
            Self::GitSigning => owned(&[
                ("off", "newt's commits are unsigned (default)"),
                (
                    "harness",
                    "a harness key newt holds (created if `signing_key` is unset)",
                ),
                (
                    "operator",
                    "your own git signing key (user.signingkey, gpg.format)",
                ),
            ]),
            Self::Prefix => owned(&[
                ("ctrl+space", "collides with nothing in newt (default)"),
                (
                    "ctrl+a",
                    "GNU Screen's; shadows line-start in emacs/nano editing",
                ),
                (
                    "ctrl+b",
                    "tmux's and herdr's own; inside them it reaches them first",
                ),
                ("ctrl+n", "shadows next-line in emacs editing"),
            ]),
            Self::Markdown => owned(&[
                ("auto", "render when colour is active (default)"),
                ("on", "always render (still needs colour)"),
                ("off", "stream raw text"),
            ]),
            // **Built from the enum, not typed out beside it.** The styles,
            // their order and their one-line descriptions all live on
            // `OperatingMode`; a style added there appears here without an
            // edit, which is the whole reason the type moved to core.
            Self::Mode => ValueSpace::Choice(
                newt_core::operating_mode::OperatingMode::all()
                    .iter()
                    .map(|m| (m.as_str(), m.description().to_string()))
                    .collect(),
            ),
            // Built from the enum's own keywords, so a policy added to
            // `CompactionTriggerPolicy` shows up here without an edit.
            Self::Compaction => ValueSpace::Choice(vec![
                (
                    newt_core::CompactionTriggerPolicy::HeadroomAware.keyword(),
                    "defer a count-only compaction while input headroom is known (default)"
                        .to_string(),
                ),
                (
                    newt_core::CompactionTriggerPolicy::MessageCount.keyword(),
                    "compact whenever the message count exceeds its threshold".to_string(),
                ),
            ]),
            Self::Prompt => ValueSpace::Text {
                release: "reset",
                placeholder: "\"[$TIME] ❯ \"",
            },
            // A count, like the round cap — and for the same reason: the
            // absorbed verb takes `/spill <rows>` with any number, so a
            // vocabulary could not represent the state the operator can
            // already reach.
            //
            // `0` is this knob's UNBOUNDED, which is what `/detail` toggles to
            // and what `--trace` installs. `auto` releases back to
            // `[tui] spill_lines`. The maximum is a formality — a `Number`
            // field must declare one, and no terminal has a hundred thousand
            // rows of useful scrollback.
            // **The vocabulary is the operator's config, read at ask time.**
            // A posture is whatever `[modes.<name>]` they wrote, so a static
            // list could only ever be wrong. `off` releases the clamp — the
            // same word `/posture off` uses.
            Self::Posture => {
                let mut options = vec![("off", "no posture — release the clamp".to_string())];
                if let Ok(cfg) =
                    crate::migration_notices::read(|report| newt_core::Config::resolve(report))
                {
                    for (name, m) in &cfg.modes {
                        let what = m.preset.as_ref().map_or_else(
                            || "guidance only (no permission clamp)".to_string(),
                            |p| format!("clamped by preset '{p}'"),
                        );
                        // Leaked deliberately: `ValueSpace::Choice` holds
                        // `&'static str`, and these names come from a config
                        // read at ask time. One small leak per opened form is
                        // the cost of a runtime-seeded vocabulary; the
                        // alternative is a static list that cannot describe
                        // the operator's own config.
                        options.push((Box::leak(name.clone().into_boxed_str()) as &str, what));
                    }
                }
                ValueSpace::Choice(options)
            }
            Self::Detail => ValueSpace::Number {
                release: "auto",
                min: 0,
                max: 100_000,
            },
            // The one field that is not a vocabulary. `/rounds double` and
            // `/rounds unlimited` stay affordances of the VERB — they are
            // relative operations, not values — and the verb resolves them to a
            // number before it lands here.
            Self::Rounds => ValueSpace::Number {
                release: "auto",
                min: 1,
                max: newt_core::tenacity::RELENTLESS_TOOL_ROUND_TARGET,
            },
        }
    }

    /// What this setting is right now, through the one resolver that already
    /// owns the precedence rather than a second reading of it.
    ///
    /// For the dials that means the OVERRIDE, not the effective level: the
    /// setting is what this form can change, and `auto` is a real, choosable
    /// value that resolution then fills in.
    pub(crate) fn current(self) -> String {
        use newt_core::cognition::cli_cognition;
        match self {
            Self::Prefix => prefix_setting(),
            Self::GitAuthor => git_profile().author.as_str().to_string(),
            Self::GitConfig => git_profile().config_source.as_str().to_string(),
            Self::GitSigning => git_profile().signing.as_str().to_string(),
            Self::EditMode => match crate::prompt::resolve_edit_mode() {
                newt_core::EditMode::Vi => "vi",
                newt_core::EditMode::Emacs => "emacs",
                newt_core::EditMode::Nano => "nano",
            }
            .to_string(),
            // Through the one dial→token mapping, which the typed doors also
            // use to report what they changed.
            Self::Tenacity => tenacity_token(newt_core::tenacity::cli_tenacity()),
            Self::Initiative => initiative_token(newt_core::initiative::cli_initiative()),
            Self::Cognition => cognition_token(cli_cognition()),
            // Three states now, so this reports the mode rather than
            // collapsing `fold` and `stream` into one "on".
            Self::Thinking => {
                match crate::migration_notices::read(newt_core::agentic::thinking_mode) {
                    newt_core::ThinkingMode::Fold => "fold",
                    newt_core::ThinkingMode::Stream => "stream",
                    newt_core::ThinkingMode::Off => "off",
                }
                .to_string()
            }
            Self::Nudge => if nudges_off() { "off" } else { "on" }.to_string(),
            // The OVERRIDE, like the dials — `auto` is what this form can set
            // and what resolution then fills in from colour.
            Self::Markdown => {
                crate::migration_notices::read(newt_core::config::session_markdown_mode)
                    .keyword()
                    .to_string()
            }
            Self::Mode => newt_core::operating_mode::session_operating_mode()
                .as_str()
                .to_string(),
            // The TEMPLATE, not the rendered preview: the setting is what this
            // form can change, and the preview is what resolution makes of it.
            Self::Prompt => crate::prompt::active_prompt_template(),
            Self::Compaction => {
                crate::migration_notices::read(newt_core::config::session_compaction_trigger_policy)
                    .keyword()
                    .to_string()
            }
            // The OVERRIDE, like the dials: `auto` is a real choosable value
            // that resolution then fills in from `[tui] spill_lines`.
            Self::Detail => newt_core::config::session_spill_lines()
                .map_or_else(|| "auto".to_string(), |n| n.to_string()),
            Self::Posture => {
                newt_core::posture::active_posture().map_or_else(|| "off".to_string(), |p| p.name)
            }
            Self::Rounds => newt_core::tenacity::session_tool_rounds()
                .map_or_else(|| "auto".to_string(), |n| n.to_string()),
        }
    }

    /// This setting's value **with whatever provenance it has** — the payload a
    /// receipt records (#1998).
    ///
    /// For every token-valued field that is just the token. For the round cap
    /// it is the whole `ToolRoundLimit`, because a cap without the number it
    /// was measured against is precisely the record #1965 found missing. It
    /// falls back to the token when no config baseline has been installed:
    /// "unknown" is an honest answer, an invented `configured` is not.
    fn value_now(self) -> newt_core::settings_receipt::SettingValue {
        use newt_core::settings_receipt::SettingValue;
        match self {
            Self::Rounds => newt_core::tenacity::session_tool_round_limit()
                .map_or_else(|| SettingValue::Token(self.current()), SettingValue::from),
            _ => SettingValue::Token(self.current()),
        }
    }

    /// Whether this field's value is FREE TEXT rather than a vocabulary.
    ///
    /// Asked by `help_request`: a template may legitimately contain `help` or
    /// `-h`, so `/settings prompt "help me"` must not be swallowed by the help
    /// interceptor. The carve-out asks the field instead of carrying a third
    /// hardcoded name beside `start` and `rename` — a list that had already
    /// gone stale by missing `/name` (#2009 PR5).
    pub(crate) fn takes_free_text(self) -> bool {
        matches!(self.value_space(), ValueSpace::Text { .. })
    }

    /// Resolve a deep-link token to a field.
    pub(crate) fn from_token(token: &str) -> Option<Self> {
        Self::ALL
            .iter()
            .copied()
            .find(|f| f.name().eq_ignore_ascii_case(token))
    }

    /// Whether `value` is one this field accepts, normalized.
    ///
    /// The alias arms are not politeness — each one was accepted by the verb
    /// this field absorbed, and absorbing a command must not silently drop an
    /// affordance operators already type.
    pub(crate) fn accepts(self, value: &str) -> Option<String> {
        let want = value.trim().to_lowercase();
        // **The mode aliases live on the enum**, which already owns
        // `developer`/`sysadmin`/`diagnostic`/`full_auto`. Listing them again
        // in the arms below would be a second copy of a vocabulary this slice
        // moved to core precisely so there could be one.
        if matches!(self, Self::Mode) {
            return newt_core::operating_mode::OperatingMode::from_keyword(&want)
                .map(|m| m.as_str().to_string());
        }
        let want = match (self, want.as_str()) {
            // `/edit-mode vim`
            (Self::EditMode, "vim") => "vi",
            // `/psyche tenacity inherit|reset`
            (Self::Tenacity | Self::Initiative, "inherit" | "reset") => "auto",
            // `/thinking on` predates the three-way vocabulary and meant
            // "show me the reasoning". It still does; that is now `fold`.
            (Self::Thinking, "on") => "fold",
            // `/psyche cognition none` / `reset` / `persona`
            (Self::Cognition, "none") => "off",
            (Self::Cognition, "reset" | "persona") => "auto",
            // `/rounds reset|default` release the override, the same word the
            // dials use for the same act.
            (Self::Rounds, "reset" | "default") => "auto",
            // `/spill reset|default|config` release this one — every word the
            // absorbed verb already accepted for the same act.
            (Self::Detail, "reset" | "default" | "config") => "auto",
            // `/detail`'s unbounded, said in words rather than as a bare `0`.
            (Self::Detail, "full" | "unbounded" | "all") => "0",
            // `/markdown always|never` — the keywords `MarkdownMode` has
            // always aliased. Absorbing a verb must not drop an affordance
            // operators already type.
            (Self::Markdown, "always") => "on",
            (Self::Markdown, "never") => "off",
            _ => want.as_str(),
        };
        match self.value_space() {
            ValueSpace::Choice(values) => values
                .into_iter()
                .find(|(v, _)| *v == want)
                .map(|(v, _)| v.to_string()),
            // Verbatim, from the ORIGINAL — `want` is lowercased, and a
            // template is not case-insensitive. One layer of surrounding
            // quotes comes off, which is what lets a trailing space be typed
            // at all.
            ValueSpace::Text { release, .. } => {
                let raw = crate::prompt::strip_one_quote_pair(value);
                if raw.trim().eq_ignore_ascii_case(release) || raw.trim().is_empty() {
                    Some(release.to_string())
                } else {
                    Some(raw.to_string())
                }
            }
            ValueSpace::Number { release, min, max } => {
                if want == release {
                    return Some(release.to_string());
                }
                // Canonical decimal, so `/settings rounds 0050` and
                // `/settings rounds 50` are one value with one address.
                want.parse::<usize>()
                    .ok()
                    .filter(|n| (min..=max).contains(n))
                    .map(|n| n.to_string())
            }
        }
    }

    /// The tokens this field offers, for tests that check the vocabulary is the
    /// dial's own rather than a second copy. Empty for a numeric field.
    #[cfg(test)]
    fn offered(self) -> Vec<&'static str> {
        match self.value_space() {
            ValueSpace::Choice(values) => values.into_iter().map(|(v, _)| v).collect(),
            ValueSpace::Number { release, .. } | ValueSpace::Text { release, .. } => {
                vec![release]
            }
        }
    }

    /// What this field accepts, said in one line — the refusal message, and the
    /// text field's hint.
    pub(crate) fn accepts_hint(self) -> String {
        match self.value_space() {
            ValueSpace::Choice(values) => values
                .into_iter()
                .map(|(v, _)| v)
                .collect::<Vec<_>>()
                .join(", "),
            ValueSpace::Number { release, min, max } => {
                format!("{release}, or a number from {min} to {max}")
            }
            ValueSpace::Text {
                release,
                placeholder,
            } => format!("{release}, or a template like {placeholder}"),
        }
    }
}

/// The one reading of the nudge dial — `NEWT_NUDGE=off` disables it, anything
/// else (including unset) leaves it on.
pub(crate) fn nudges_off() -> bool {
    std::env::var("NEWT_NUDGE").is_ok_and(|v| v.eq_ignore_ascii_case("off"))
}

/// The top-level form: which setting to change, each showing its current value.
pub(crate) fn field_menu() -> InteractionDefinition {
    let entries: Vec<(String, String)> = Field::ALL
        .iter()
        .enumerate()
        .map(|(i, f)| {
            (
                (i + 1).to_string(),
                format!("{} — currently {}", f.label(), f.current()),
            )
        })
        .collect();
    let refs: Vec<(&str, &str)> = entries
        .iter()
        .map(|(k, v)| (k.as_str(), v.as_str()))
        .collect();
    newt_core::interaction_form::menu("settings", "Esc cancels", &refs)
}

/// The second step: which value for `field`.
pub(crate) fn value_menu(field: Field) -> InteractionDefinition {
    let current = field.current();
    let ValueSpace::Choice(values) = field.value_space() else {
        // A number has no menu to pick from, so the form asks for it — still a
        // typed `InteractionDefinition`, so it still renders on the plain
        // scroller, the RichTUI and the web.
        return newt_core::interaction_form::text_field(
            format!("{} — currently {current}", field.label()),
            format!("{} · Esc cancels", field.accepts_hint()),
        );
    };
    let entries: Vec<(String, String)> = values
        .into_iter()
        .enumerate()
        .map(|(i, (value, what))| {
            let mark = if value == current { "  (current)" } else { "" };
            ((i + 1).to_string(), format!("{value} — {what}{mark}"))
        })
        .collect();
    let refs: Vec<(&str, &str)> = entries
        .iter()
        .map(|(k, v)| (k.as_str(), v.as_str()))
        .collect();
    newt_core::interaction_form::menu(field.label(), "Esc cancels", &refs)
}

/// The meta prefix as the operator names it: `NEWT_PREFIX_KEY` (this
/// session), else `[tui] prefix_key`, else `ctrl+space`. The ONE owner of that
/// precedence — the panel driver parses this same string (`prefix::configured`).
pub(crate) fn prefix_setting() -> String {
    std::env::var("NEWT_PREFIX_KEY")
        .ok()
        .filter(|value| !value.trim().is_empty())
        .or_else(|| {
            crate::migration_notices::read(|report| newt_core::Config::resolve_unpublished(report))
                .ok()
                .and_then(|c| c.tui)
                .and_then(|t| t.prefix_key)
        })
        .unwrap_or_else(|| "ctrl+space".to_string())
}

/// Write one `[tui]` scalar to the operator's config, preserving the rest.
fn persist_tui_key(key: &str, value: &str) -> Result<(), String> {
    let path = newt_core::Config::user_config_path()
        .ok_or_else(|| "no config directory to save the setting in".to_string())?;
    let text = config_fs::read(&path);
    let updated = newt_core::Config::with_tui_key(&text, key, value).map_err(|e| e.to_string())?;
    config_fs::write(&path, &updated).map_err(|e| format!("saving {key}: {e}"))
}

/// The config file, as a seam: the unit tier never touches a real file.
#[cfg(not(test))]
mod config_fs {
    pub(super) fn read(path: &std::path::Path) -> String {
        std::fs::read_to_string(path).unwrap_or_default()
    }
    pub(super) fn write(path: &std::path::Path, text: &str) -> std::io::Result<()> {
        std::fs::write(path, text)
    }
}

#[cfg(test)]
pub(crate) mod config_fs {
    use std::cell::RefCell;
    use std::path::{Path, PathBuf};

    thread_local! {
        static FILES: RefCell<Vec<(PathBuf, String)>> = const { RefCell::new(Vec::new()) };
    }

    pub(crate) fn read(path: &Path) -> String {
        FILES.with(|files| {
            files
                .borrow()
                .iter()
                .rev()
                .find(|(p, _)| p == path)
                .map(|(_, text)| text.clone())
                .unwrap_or_default()
        })
    }

    pub(crate) fn write(path: &Path, text: &str) -> std::io::Result<()> {
        FILES.with(|files| {
            files
                .borrow_mut()
                .push((path.to_path_buf(), text.to_string()));
        });
        Ok(())
    }
}

/// **THE ONE MUTATION PATH.** Every route — the form, a deep link, a
/// deprecated verb, `/psyche tenacity`, `/psyche obsessive` — lands here.
///
/// **Private on purpose.** The receipt attaches in [`apply_and_record`], and a
/// caller that could reach the write without it would be the #1965 gap with
/// extra steps. The module's own tests call this directly, because what they
/// are checking is the mutation, not the record.
///
/// The `#1668` posture marks are made HERE, beside the write, for the same
/// reason: a caller that sets the dial and forgets to mark the axis leaves the
/// conversation's operator pin stale, and there is now exactly one caller.
fn apply(field: Field, value: &str) -> Result<String, String> {
    use newt_core::cognition::CognitionOverride;
    use newt_core::role_profile::Cognition;
    use newt_core::{Initiative, Tenacity};

    let Some(value) = field.accepts(value) else {
        return Err(format!(
            "/settings {} takes {}",
            field.name(),
            field.accepts_hint()
        ));
    };
    let value = value.as_str();
    match field {
        // Under the process-env lock (#1850); the editor is rebuilt right
        // after a slash command returns, before further input is read.
        Field::EditMode => newt_core::process_env::set_var("NEWT_EDIT_MODE", value),
        // `auto` is the only value that does not parse as a level, because
        // `accepts` already refused everything else. Both dials parse their
        // token and hand it to the SAME writer the typed door uses, so the two
        // routes cannot drift in what they set or what they pin.
        Field::Tenacity => write_tenacity(value.parse::<Tenacity>().ok()),
        Field::Initiative => write_initiative(value.parse::<Initiative>().ok()),
        Field::Cognition => write_cognition(match value {
            "auto" => CognitionOverride::Unset,
            "off" => CognitionOverride::Off,
            level => level
                .parse::<Cognition>()
                .map_or(CognitionOverride::Unset, CognitionOverride::Set),
        }),
        Field::Thinking => newt_core::process_env::set_var("NEWT_THINKING", value),
        // Written the same way `/markdown` writes it, under the #1850 lock,
        // and read back through the one resolver that owns the precedence.
        Field::Markdown => newt_core::process_env::set_var("NEWT_MARKDOWN", value),
        // `accepts` has already resolved the token through `from_keyword`, so
        // this cannot fail — and it goes through the SAME setter `/mode` uses.
        // The session loop clears the stale model-selected style when it sees
        // this value change; see `chat.rs`'s reconcile beside
        // `dispatch_slash_with_ask`.
        Field::Mode => {
            if let Some(mode) = newt_core::operating_mode::OperatingMode::from_keyword(value) {
                newt_core::operating_mode::set_session_operating_mode(mode);
            }
        }
        // `on` REMOVES the variable rather than setting it, which is what the
        // absorbed `/nudge on` did: the readers test for `=off`, so an unset
        // variable and `on` are the same state and only one of them is a
        // leftover for the next `/nudge status` to explain.
        Field::Nudge => {
            if value == "off" {
                newt_core::process_env::set_var("NEWT_NUDGE", "off");
            } else {
                newt_core::process_env::remove_var("NEWT_NUDGE");
            }
        }
        // `auto` releases the override so the cap derives from tenacity +
        // config again; anything else is a number `accepts` has already
        // bounded, so the parse cannot fail.
        Field::Rounds => newt_core::tenacity::set_session_tool_rounds(value.parse::<usize>().ok()),
        // `reset` removes the variable rather than storing the word, so the
        // session falls back to `[tui] prompt` and no leftover survives for
        // the next `/prompt` to explain — the same shape `/nudge on` uses.
        // Resolved by the SAME `build_posture` the verb calls, over the same
        // config-rooted skill dirs, and installed through the one writer — so
        // `/posture` and `/settings posture` cannot install different
        // bindings. A posture that fails to resolve installs NOTHING: it is
        // atomic-or-nothing, and `accepts` has already bounded the name to one
        // the config declares.
        Field::Posture => {
            if value == "off" {
                newt_core::posture::set_active_posture(None);
            } else {
                let cfg =
                    crate::migration_notices::read(|report| newt_core::Config::resolve(report))
                        .map_err(|e| e.to_string())?;
                let dirs = cfg.skill_search_dirs();
                let posture = newt_core::posture::build_posture(value, &cfg, |skill| {
                    newt_skills::load_body_from(&dirs, skill)
                })
                .map_err(|e| e.to_string())?;
                newt_core::posture::set_active_posture(Some(posture));
            }
        }
        // Through the same writer `/spill` and `/detail` use, so the three
        // doors onto this one knob cannot disagree.
        Field::Detail => {
            newt_core::config::set_session_spill_lines(value.parse::<usize>().ok());
        }
        // Through the same writer `/context compaction` uses.
        Field::Compaction => {
            if let Some(policy) = newt_core::CompactionTriggerPolicy::from_keyword(value) {
                newt_core::process_env::set_var("NEWT_COMPACTION_TRIGGER", policy.keyword());
            }
        }
        Field::Prompt => {
            if value == "reset" {
                newt_core::process_env::remove_var("NEWT_PROMPT");
            } else {
                newt_core::process_env::set_var("NEWT_PROMPT", value);
            }
        }
        // The one field here that PERSISTS: a prefix you had to set again every
        // session would be a trap. `[tui] prefix_key`, through the
        // comment-preserving writer; and the session, so the next panel uses it
        // at once.
        // Persisted to the operator's `agent-identity.toml`: the shell reads
        // it on every dispatch, so the next command already uses it.
        Field::GitAuthor => persist_git_profile(|git| {
            git.author = if value == "operator" {
                newt_core::agent_identity::SandboxAuthor::Operator
            } else {
                newt_core::agent_identity::SandboxAuthor::Agent
            };
            Ok(())
        })?,
        // Choosing `environment` takes the snapshot NOW, so choosing it again
        // is how the operator refreshes it.
        Field::GitConfig => persist_git_profile(|git| {
            if value == "environment" {
                let listing = newt_core::git_hardening::ambient_git_config_listing()
                    .map_err(|e| format!("could not read your git config: {e}"))?;
                git.config = newt_core::git_hardening::copyable_git_config(&listing);
                git.config_source = newt_core::agent_identity::GitConfigSource::Environment;
            } else {
                git.config.clear();
                git.config_source = newt_core::agent_identity::GitConfigSource::Baseline;
            }
            Ok(())
        })?,
        // `harness` with no `signing_key` creates one, and says where its
        // public half goes, because a signature nobody can verify is noise.
        Field::GitSigning => {
            let mut created = None;
            persist_identity(|identity| {
                use newt_core::agent_identity::SigningMode;
                identity.git.signing = match value {
                    "harness" => SigningMode::Harness,
                    "operator" => SigningMode::Operator,
                    _ => SigningMode::Off,
                };
                if value == "harness" && identity.signing_key.is_none() {
                    let dir = newt_core::Config::user_config_dir()
                        .ok_or("no config directory for the harness key")?;
                    let path = dir.join("harness-signing.pem");
                    created = Some(newt_core::commit_signing::generate_harness_key(&path)?);
                    identity.signing_key = Some(path.to_string_lossy().into_owned());
                }
                Ok(())
            })?;
            if let Some(public) = created {
                return Ok(format!(
                    "{}: {value}\nnew harness key; add this to the agent's GitHub account \
                     as a signing key so its commits verify:\n{public}",
                    field.label()
                ));
            }
        }
        Field::Prefix => {
            persist_tui_key("prefix_key", value)?;
            newt_core::process_env::set_var("NEWT_PREFIX_KEY", value);
            #[cfg(feature = "rich-tui")]
            crate::prefix::invalidate();
        }
    }
    Ok(format!("{}: {value}", field.label()))
}

/// The sandbox git profile in force (`[agent-identity.git]`).
fn git_profile() -> newt_core::agent_identity::GitProfile {
    newt_core::AgentIdentity::resolve().unwrap_or_default().git
}

/// Edit the sandbox git profile in the operator's own `agent-identity.toml`,
/// keeping every other field of that file as it was.
fn persist_git_profile(
    edit: impl FnOnce(&mut newt_core::agent_identity::GitProfile) -> Result<(), String>,
) -> Result<(), String> {
    persist_identity(|identity| edit(&mut identity.git))
}

/// Edit the operator's own `agent-identity.toml`, keeping every other field.
fn persist_identity(
    edit: impl FnOnce(&mut newt_core::AgentIdentity) -> Result<(), String>,
) -> Result<(), String> {
    let path = newt_core::AgentIdentity::user_identity_path()
        .ok_or("no home directory to hold agent-identity.toml")?;
    let mut identity = if path.is_file() {
        newt_core::AgentIdentity::load(&path).map_err(|e| e.to_string())?
    } else {
        newt_core::AgentIdentity::default()
    };
    edit(&mut identity)?;
    identity.save(&path).map_err(|e| e.to_string())
}

/// The change a transition is recorded as, or `None` when the registry
/// declares no destination for this setting.
///
/// **This is the production reader of the registry's receipt column.** The
/// decision is data, not a call site's memory: a field is receipted because
/// `slash_registry` says where its receipt lands. A `Receipt::Missing` field
/// writes nothing and stays counted in the #1965 debt, which is the honest
/// answer — a receipt sent to a destination nobody declared looks like
/// coverage without being any.
///
/// Pure, so the whole decision is exercised with no filesystem: the write
/// itself belongs to `settings_receipt::record`.
fn change_for(
    setting: &str,
    from: newt_core::settings_receipt::SettingValue,
    to: newt_core::settings_receipt::SettingValue,
    via: &str,
) -> Option<SettingChange> {
    match crate::slash_registry::receipt_for(setting) {
        crate::slash_registry::Receipt::Journal => Some(SettingChange::new(setting, from, to, via)),
        _ => None,
    }
}

/// **THE ONE ROUTE OUT.** Apply a setting and durably record that it happened.
///
/// [`apply`] is private precisely so this cannot be bypassed: a caller that
/// could reach the mutation without the receipt is the #1965 defect with extra
/// steps, and there were thirty-four of those. `via` is the verb the operator
/// actually typed — `/vi` and `/settings edit-mode` produce the same setting
/// and two different events, and the route is the half a reader cannot
/// reconstruct afterwards.
///
/// A no-op (`/vi` when already `vi`) is recorded too. The journal records what
/// the operator DID, and "asked for vi, was already vi" is a fact about the
/// session, not noise to be filtered by a rule nobody can see.
///
/// Recording is best effort by construction (`settings_receipt::record`
/// swallows its own failures): failing to observe a change must never undo the
/// change.
pub(crate) fn apply_and_record(field: Field, value: &str, via: &str) -> Result<String, String> {
    recorded(field, via, || apply(field, value)).map(|(message, _)| message)
}

/// Snapshot the setting, run the write, record what moved.
///
/// The recording half of [`apply_and_record`], factored out because there is
/// now a second door — a TYPED one, for callers that hold a dial rather than a
/// token (see [`apply_cognition`]). Two doors, ONE recorder: a second copy of
/// this five-line dance is exactly how one of them would quietly stop
/// journalling.
fn recorded<T>(
    field: Field,
    via: &str,
    write: impl FnOnce() -> Result<T, String>,
) -> Result<(T, Option<SettingChange>), String> {
    recorded_values(field.name(), field.value_now(), via, || {
        let applied = write()?;
        Ok((applied, field.value_now()))
    })
}

/// Shared recorder for both process-owned fields and caller-owned tab state.
/// A failed write produces no receipt; every successful write consults the
/// same registry destination and stores the actual typed before/after values.
fn recorded_values<T>(
    setting: &str,
    from: newt_core::settings_receipt::SettingValue,
    via: &str,
    write: impl FnOnce() -> Result<(T, newt_core::settings_receipt::SettingValue), String>,
) -> Result<(T, Option<SettingChange>), String> {
    let (applied, to) = write()?;
    let change = change_for(setting, from, to, via);
    if let Some(change) = change.clone() {
        let _ = newt_core::settings_receipt::record(change);
    }
    Ok((applied, change))
}

/// **The typed door, for a caller holding a dial rather than a token.**
///
/// The `/psyche` panel has a `CognitionOverride` in hand and used to write it
/// straight through `set_cli_cognition`, which is the #1965 gap in the one
/// place best positioned to know better: a panel that changes a dial and
/// leaves no receipt. The obvious fix — render the dial to a token and call
/// [`apply_and_record`] — buys a `Result` the panel cannot report (its apply
/// runs inside `close_outcome`, which has no error arm), so an unaccepted
/// token would silently drop the operator's change. A typed door has no such
/// failure mode: the value is already the thing the runtime stores.
///
/// Same writer, same recorder, same posture mark as the token path — it is
/// literally the arm [`apply`] runs, called directly.
///
/// **`rich-tui`, to match its only caller.** The panel is the thing that holds
/// a typed dial, and the panel does not exist in a lean / headless / wyvern
/// build. The token door stays ungated, because slash commands do.
#[cfg(feature = "rich-tui")]
pub(crate) fn apply_cognition(
    choice: newt_core::cognition::CognitionOverride,
    via: &str,
) -> String {
    journalled_cognition(choice, via).0
}

/// [`apply_cognition`], also handing back the change it journalled.
///
/// **The change is returned so a test can SEE which setting was recorded.**
/// Without it the `Field` handed to [`recorded`] is unguarded: swapping
/// `Field::Cognition` for `Field::Tenacity` here leaves the runtime state
/// right, the returned message right (it is built from an explicit label),
/// and every source-scanning guard passing — while the journal gains a false
/// `tenacity` row whose from and to are equal, and the operator's real
/// cognition change is recorded nowhere. An adversarial review ran exactly
/// that mutation against the whole suite and got 1289 green.
///
/// It cannot be observed through the journal in the unit tier, deliberately:
/// `GlobalSettingsGuard` blanks `RECEIPT_PATH_ENV` so tests write no files.
/// Returning the value is what makes the claim checkable without one.
#[cfg(feature = "rich-tui")]
fn journalled_cognition(
    choice: newt_core::cognition::CognitionOverride,
    via: &str,
) -> (String, Option<SettingChange>) {
    // Infallible by construction, so the `Result` is unwrapped rather than
    // returned: `write_cognition` cannot fail, and a caller forced to handle
    // an impossible error would invent a behaviour for it.
    recorded(Field::Cognition, via, || {
        write_cognition(choice);
        // The message reads the resolver, which is the ONE place that renders
        // a dial as its token — a second rendering here would be a second
        // opinion about what `auto` is called.
        Ok(format!(
            "{}: {}",
            Field::Cognition.label(),
            Field::Cognition.current()
        ))
    })
    .unwrap_or_else(|never| (never, None))
}

/// The typed door for tenacity. `None` releases the override — `auto`.
#[cfg(feature = "rich-tui")]
pub(crate) fn apply_tenacity(level: Option<newt_core::Tenacity>, via: &str) -> String {
    journalled_tenacity(level, via).0
}

/// [`apply_tenacity`], also handing back the change it journalled — see
/// [`journalled_cognition`] for why that is returned rather than inferred.
#[cfg(feature = "rich-tui")]
fn journalled_tenacity(
    level: Option<newt_core::Tenacity>,
    via: &str,
) -> (String, Option<SettingChange>) {
    recorded(Field::Tenacity, via, || {
        write_tenacity(level);
        Ok(format!(
            "{}: {}",
            Field::Tenacity.label(),
            Field::Tenacity.current()
        ))
    })
    .unwrap_or_else(|never| (never, None))
}

/// The typed door for initiative. `None` releases the override — `auto`.
#[cfg(feature = "rich-tui")]
pub(crate) fn apply_initiative(level: Option<newt_core::Initiative>, via: &str) -> String {
    journalled_initiative(level, via).0
}

/// [`apply_initiative`], also handing back the change it journalled — see
/// [`journalled_cognition`] for why that is returned rather than inferred.
#[cfg(feature = "rich-tui")]
fn journalled_initiative(
    level: Option<newt_core::Initiative>,
    via: &str,
) -> (String, Option<SettingChange>) {
    recorded(Field::Initiative, via, || {
        write_initiative(level);
        Ok(format!(
            "{}: {}",
            Field::Initiative.label(),
            Field::Initiative.current()
        ))
    })
    .unwrap_or_else(|never| (never, None))
}

/// Apply only to the caller's active tab. Style cannot mark cognition,
/// tenacity, initiative, model preferences or change capability-bearing persona metadata.
#[cfg(feature = "rich-tui")]
pub(crate) fn apply_personality(
    current: &mut newt_core::role_profile::PersonalityTraits,
    next: newt_core::role_profile::PersonalityTraits,
    via: &str,
) -> String {
    journalled_personality(current, next, via).0
}

#[cfg(feature = "rich-tui")]
fn journalled_personality(
    current: &mut newt_core::role_profile::PersonalityTraits,
    next: newt_core::role_profile::PersonalityTraits,
    via: &str,
) -> (String, Option<SettingChange>) {
    recorded_values("psyche", (*current).into(), via, || {
        *current = next;
        let to = newt_core::settings_receipt::SettingValue::from(*current);
        Ok((format!("personality overrides: {to}"), to))
    })
    .unwrap_or_else(|never| (never, None))
}

/// The runtime write for cognition, and the posture mark that must accompany
/// it. Private: every route in reaches it through [`recorded`].
fn write_cognition(choice: newt_core::cognition::CognitionOverride) {
    newt_core::cognition::set_cli_cognition(choice);
    // #1668: setting a dial pins its axis as an operator preference.
    newt_core::runtime::mark_cognition_choice(choice);
}

fn write_tenacity(level: Option<newt_core::Tenacity>) {
    match level {
        Some(level) => newt_core::tenacity::set_cli_tenacity(level),
        None => newt_core::tenacity::clear_cli_tenacity(),
    }
    newt_core::runtime::mark_tenacity_choice(level);
}

fn write_initiative(level: Option<newt_core::Initiative>) {
    match level {
        Some(level) => newt_core::initiative::set_cli_initiative(level),
        None => newt_core::initiative::clear_cli_initiative(),
    }
    newt_core::runtime::mark_initiative_choice(level);
}

/// The vocabulary token for a dial position — the same string the menu offers
/// and `accepts` takes.
///
/// **The one mapping**, called by [`Field::current`] rather than duplicated
/// there. It was written as a second copy of that arm; a review named it, and
/// two renderings of "what is this dial called" is precisely how one of them
/// starts saying `inherit` where the other says `auto`.
fn cognition_token(choice: newt_core::cognition::CognitionOverride) -> String {
    use newt_core::cognition::CognitionOverride;
    match choice {
        CognitionOverride::Unset => "auto".to_string(),
        CognitionOverride::Off => "off".to_string(),
        CognitionOverride::Set(level) => level.label().to_string(),
    }
}

fn tenacity_token(level: Option<newt_core::Tenacity>) -> String {
    level.map_or_else(|| "auto".to_string(), |l| l.label().to_string())
}

fn initiative_token(level: Option<newt_core::Initiative>) -> String {
    level.map_or_else(|| "auto".to_string(), |l| l.label().to_string())
}

/// What a deprecated verb prints in addition to doing its job.
pub(crate) fn moved_notice(verb: &str, field: Field) -> String {
    format!(
        "(/{verb} still works; this setting now lives in /settings {})",
        field.name()
    )
}

/// Drive `/settings [field] [value]`.
///
/// `rest` is everything after the command word. Empty opens the form; one
/// token deep-links to a field's value menu; two apply directly. Returns the
/// lines to print — the caller owns the terminal, this owns no I/O.
pub(crate) fn run(ask: Ask<'_>, rest: &str) -> Vec<String> {
    // `trim_end` only for the split: a trailing space belongs to a `Text`
    // value, so trimming the whole line here would eat it before anything
    // could decide whether it mattered.
    let mut parts = rest.trim_start().splitn(2, char::is_whitespace);
    let first = parts.next().unwrap_or("").trim();
    let raw_second = parts.next().unwrap_or("");
    let second = raw_second.trim();

    // `/settings <field> <value>` — no question needed.
    if !first.is_empty() {
        let Some(field) = Field::from_token(first) else {
            let known: Vec<&str> = Field::ALL.iter().map(|f| f.name()).collect();
            return vec![format!(
                "/settings has no `{first}` setting (have: {})",
                known.join(", ")
            )];
        };
        // **The deep link carries the quoted template verbatim** (#2009 PR5).
        //
        // This is the parsing the `/prompt set` verb owned, ported rather than
        // reinvented: everything after the field name is the literal value,
        // taken from the RAW remainder so internal and trailing spaces
        // survive, with one layer of surrounding quotes stripped by `accepts`.
        //
        // `/settings prompt "❯ "` is the case that decides it. Trimming turns
        // it into `❯`, which renders flush against whatever the operator
        // types, and nothing in the pipeline would report the difference.
        let is_text = matches!(field.value_space(), ValueSpace::Text { .. });
        let value = if is_text { raw_second } else { second };
        if !value.trim().is_empty() {
            return vec![match apply_and_record(field, value, "/settings") {
                Ok(msg) | Err(msg) => msg,
            }];
        }
        return ask_value(ask, field);
    }

    // Bare `/settings` — pick a field, then a value.
    let menu = field_menu();
    let Some(choice) = ask_choice(ask, &menu) else {
        return vec!["settings: cancelled".to_string()];
    };
    let Some(index) = choice.parse::<usize>().ok().filter(|i| *i >= 1) else {
        return vec!["settings: cancelled".to_string()];
    };
    let Some(field) = Field::ALL.get(index - 1).copied() else {
        return vec!["settings: cancelled".to_string()];
    };
    ask_value(ask, field)
}

fn ask_value(ask: Ask<'_>, field: Field) -> Vec<String> {
    ask_and_apply(ask, field, "/settings")
}

/// Ask for one field's value and apply it through [`apply_and_record`],
/// recorded as reached `via` (`/settings`, `/setup`). Setup asks the git
/// profile through this, so its questions and its write are `/settings`' own.
pub(crate) fn ask_and_apply(ask: Ask<'_>, field: Field, via: &str) -> Vec<String> {
    let cancelled = || vec!["settings: cancelled".to_string()];
    let definition = value_menu(field);
    let value = match field.value_space() {
        // A number is taken as typed; `apply` is the one validator, so the
        // form does not grow a second opinion about what is in range.
        ValueSpace::Number { .. } => {
            let interaction = SurfaceInteraction::blocking(definition.clone());
            let HumanQuestionOutcome::Answer(answer) = ask(&interaction) else {
                return cancelled();
            };
            let answer = answer.trim().to_string();
            // An empty line is backing out, not an invalid number.
            if answer.is_empty() {
                return cancelled();
            }
            answer
        }
        // Free text, taken as typed — `accepts` is the one validator, and for
        // a template it accepts anything, so the form grows no second opinion
        // about what a prompt may contain.
        ValueSpace::Text { .. } => {
            let interaction = SurfaceInteraction::blocking(definition.clone());
            let HumanQuestionOutcome::Answer(answer) = ask(&interaction) else {
                return cancelled();
            };
            // NOT trimmed: `"❯ "` is the likeliest template anyone types and
            // its trailing space is the point. An empty line is backing out.
            if answer.trim().is_empty() {
                return cancelled();
            }
            answer
        }
        ValueSpace::Choice(values) => {
            let Some(choice) = ask_choice(ask, &definition) else {
                return cancelled();
            };
            let Some(index) = choice.parse::<usize>().ok().filter(|i| *i >= 1) else {
                return cancelled();
            };
            let Some((value, _)) = values.get(index - 1) else {
                return cancelled();
            };
            (*value).to_string()
        }
    };
    vec![match apply_and_record(field, &value, via) {
        Ok(msg) | Err(msg) => msg,
    }]
}

/// Ask one menu and return the chosen option id, or `None` when the operator
/// backed out. The resolution is `interaction_form::resolve`'s, not a second
/// parser.
fn ask_choice(ask: Ask<'_>, definition: &InteractionDefinition) -> Option<String> {
    let interaction = SurfaceInteraction::blocking(definition.clone());
    match ask(&interaction) {
        HumanQuestionOutcome::Answer(answer) => {
            newt_core::interaction_form::resolve(definition, answer.trim())
                .map(|id| id.as_str().to_string())
        }
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Every test that reaches [`apply_and_record`] holds this. It serializes
    /// the process-global dials AND turns the receipt journal off, so a unit
    /// test cannot append to the developer's real `~/.newt/receipts.jsonl` —
    /// which it did until the guard learned about the journal.
    fn settings_guard() -> newt_core::test_guard::GlobalSettingsGuard {
        newt_core::test_guard::GlobalSettingsGuard::acquire()
    }

    /// A scripted operator. The `Ask` seam is injected, so the whole form is
    /// exercised with no terminal, no I/O and no real prompt — the unit tier
    /// stays fully mocked, which is the reason `crew_form`'s shape was copied
    /// rather than a private console path invented.
    fn answering(
        answers: &'static [&'static str],
    ) -> impl Fn(&SurfaceInteraction) -> HumanQuestionOutcome {
        let next = std::cell::Cell::new(0usize);
        move |_| {
            let i = next.get();
            next.set(i + 1);
            answers.get(i).map_or(HumanQuestionOutcome::Cancelled, |a| {
                HumanQuestionOutcome::Answer((*a).to_string())
            })
        }
    }

    /// Like [`answering`] but for answers built at runtime (a field index, a
    /// typed number).
    fn answering_owned(
        answers: Vec<String>,
    ) -> impl Fn(&SurfaceInteraction) -> HumanQuestionOutcome {
        let next = std::cell::Cell::new(0usize);
        move |_| {
            let i = next.get();
            next.set(i + 1);
            answers.get(i).map_or(HumanQuestionOutcome::Cancelled, |a| {
                HumanQuestionOutcome::Answer(a.clone())
            })
        }
    }

    fn declining() -> impl Fn(&SurfaceInteraction) -> HumanQuestionOutcome {
        |_| HumanQuestionOutcome::Cancelled
    }

    /// **Deep link: `/settings edit-mode vi` asks nothing and applies.**
    #[test]
    fn a_full_deep_link_applies_without_a_question() {
        let _g = settings_guard();
        let asked = std::cell::Cell::new(false);
        let ask = |_: &SurfaceInteraction| {
            asked.set(true);
            HumanQuestionOutcome::Cancelled
        };
        let out = run(&ask, "edit-mode emacs");
        assert!(!asked.get(), "a fully-specified deep link must not prompt");
        assert_eq!(out, vec!["line-editor key bindings: emacs".to_string()]);
        assert_eq!(Field::EditMode.current(), "emacs");
    }

    /// The two-step form: pick the field, then the value.
    #[test]
    fn the_form_walks_field_then_value() {
        let _g = settings_guard();
        // "1" selects the first field, "1" selects its first value (vi).
        let out = run(&answering(&["1", "1"]), "");
        assert_eq!(out, vec!["line-editor key bindings: vi".to_string()]);
        assert_eq!(Field::EditMode.current(), "vi");
    }

    /// **Anti-vacuous twin.** Every assertion above would hold over a form
    /// that applied unconditionally. Backing out changes nothing.
    #[test]
    fn cancelling_the_form_changes_no_setting() {
        let _g = settings_guard();
        let _ = apply(Field::EditMode, "nano");
        let before = Field::EditMode.current();
        let out = run(&declining(), "");
        assert_eq!(out, vec!["settings: cancelled".to_string()]);
        assert_eq!(
            Field::EditMode.current(),
            before,
            "a cancelled form must not write"
        );
    }

    /// A value the field does not accept is refused with the allowed set, and
    /// writes nothing.
    #[test]
    fn an_unaccepted_value_is_refused_and_writes_nothing() {
        let _g = settings_guard();
        let _ = apply(Field::EditMode, "vi");
        let err = apply(Field::EditMode, "ed").expect_err("`ed` is not an edit mode");
        assert!(err.contains("vi, emacs, nano"), "{err}");
        assert_eq!(Field::EditMode.current(), "vi", "a refusal must not write");
    }

    /// `vim` was accepted by the old `/edit-mode` arm. Absorbing a command
    /// must not silently drop an alias operators type.
    #[test]
    fn the_vim_alias_the_old_verb_took_still_resolves() {
        assert_eq!(Field::EditMode.accepts("vim").as_deref(), Some("vi"));
        assert_eq!(Field::EditMode.accepts("VI").as_deref(), Some("vi"));
        assert_eq!(Field::EditMode.accepts("ed"), None);
    }

    /// An unknown setting name lists what exists rather than failing silently.
    #[test]
    fn an_unknown_field_names_the_ones_that_exist() {
        let out = run(&declining(), "nosuchsetting");
        assert!(out[0].contains("no `nosuchsetting` setting"), "{out:?}");
        assert!(out[0].contains("edit-mode"), "{out:?}");
    }

    /// The menus are typed definitions, not printed strings — which is what
    /// makes them render on the plain scroller, the RichTUI and the web.
    #[test]
    fn the_menus_are_definitions_carrying_every_choice() {
        let _g = settings_guard();
        let menu = value_menu(Field::EditMode);
        let rendered = newt_core::markup::plain::render(&menu);
        for value in ["vi", "emacs", "nano"] {
            assert!(rendered.contains(value), "{value} missing: {rendered}");
        }
        assert!(
            rendered.contains("(current)"),
            "the current value is marked: {rendered}"
        );
        assert_eq!(field_menu().controls.len(), 1, "one choice control");
    }

    /// **Every dial the form claims reports the value its own resolver owns.**
    ///
    /// The knobs are process globals set through four different modules; a
    /// form that read them by hand would show a stale value the moment one
    /// module changed. Each `current()` goes through that module's reader.
    #[test]
    fn each_field_reports_what_its_own_resolver_says() {
        let _g = settings_guard();
        for (field, value) in [
            (Field::EditMode, "nano"),
            (Field::Tenacity, "relentless"),
            (Field::Initiative, "decisive"),
            (Field::Cognition, "thoughtful"),
            (Field::Thinking, "off"),
            (Field::Nudge, "off"),
        ] {
            apply(field, value).expect("an offered value applies");
            assert_eq!(field.current(), value, "{} did not read back", field.name());
        }
        // ...and every one of them goes back, which a write-only global would
        // not manage.
        for (field, value) in [
            (Field::Tenacity, "auto"),
            (Field::Initiative, "auto"),
            (Field::Cognition, "auto"),
            (Field::Thinking, "fold"),
            (Field::Nudge, "on"),
        ] {
            apply(field, value).expect("an offered value applies");
            assert_eq!(field.current(), value, "{} did not release", field.name());
        }
    }

    /// The dial levels come from the dial, not a second list in this file.
    ///
    /// Adding a `Tenacity` level must make it choosable here without anyone
    /// remembering to; a hand-copied list is how `/psyche … list` and the form
    /// would silently disagree.
    #[test]
    fn the_dial_levels_are_the_dials_own() {
        let offered = Field::Tenacity.offered();
        for level in newt_core::Tenacity::all() {
            assert!(offered.contains(&level.label()), "{level:?} not offered");
        }
        assert!(offered.contains(&"auto"), "the release value is missing");
        assert_eq!(offered.len(), newt_core::Tenacity::all().len() + 1);

        let offered = Field::Initiative.offered();
        for level in newt_core::Initiative::all() {
            assert!(offered.contains(&level.label()), "{level:?} not offered");
        }
        assert_eq!(offered.len(), newt_core::Initiative::all().len() + 1);

        let offered = Field::Cognition.offered();
        for level in newt_core::role_profile::Cognition::all() {
            assert!(offered.contains(&level.label()), "{level:?} not offered");
        }
        assert!(offered.contains(&"off") && offered.contains(&"auto"));
    }

    /// **Every alias the absorbed verbs took still resolves.** Each of these
    /// was reachable before the family moved; absorbing a command must not
    /// quietly delete an affordance operators already type.
    /// **Three doors onto one knob** (#2009 PR7b).
    ///
    /// `/spill <rows>`, `/detail` and `/settings detail` all set the session
    /// spill override. The code already claimed "one variable, so the launch
    /// flag and the runtime control cannot disagree"; absorbing it added a
    /// third door, and this is what keeps that claim true across all of them.
    #[test]
    fn the_detail_field_shares_one_knob_with_spill_and_the_toggle() {
        let _guard = settings_guard();
        newt_core::config::set_session_spill_lines(None);
        assert_eq!(Field::Detail.current(), "auto", "unpinned follows config");

        // The field sets it...
        assert_eq!(
            apply(Field::Detail, "12"),
            Ok("tool-output detail rows: 12".to_string())
        );
        assert_eq!(newt_core::config::session_spill_lines(), Some(12));

        // ...`/detail`'s toggle reads the SAME value and flips it to unbounded.
        let toggled = crate::toggle_spill_detail(newt_core::config::session_spill_lines(), 3);
        newt_core::config::set_session_spill_lines(toggled);
        assert_eq!(Field::Detail.current(), "0", "0 is this knob's unbounded");

        // ...and the field reports what the toggle did, with no second state.
        assert_eq!(newt_core::config::session_spill_lines(), Some(0));
    }

    /// `auto` releases; the launch flag's unbounded is a real value, not a
    /// release. Those are different states and the field keeps them apart.
    #[test]
    fn releasing_detail_is_not_the_same_as_setting_it_unbounded() {
        let _guard = settings_guard();

        apply(Field::Detail, "0").expect("0 is in range");
        assert_eq!(newt_core::config::session_spill_lines(), Some(0));
        assert_eq!(Field::Detail.current(), "0");

        apply(Field::Detail, "auto").expect("auto releases");
        assert_eq!(
            newt_core::config::session_spill_lines(),
            None,
            "release must REMOVE the override, not store a word"
        );
        assert_eq!(Field::Detail.current(), "auto");
    }

    /// Every word the absorbed verbs accepted for these two acts still works.
    #[test]
    fn the_detail_verbs_vocabulary_survives_absorption() {
        for word in ["reset", "default", "config"] {
            assert_eq!(
                Field::Detail.accepts(word),
                Some("auto".to_string()),
                "`/spill {word}` released the override"
            );
        }
        for word in ["full", "unbounded", "all"] {
            assert_eq!(Field::Detail.accepts(word), Some("0".to_string()), "{word}");
        }
        assert_eq!(Field::Detail.accepts("40"), Some("40".to_string()));
        assert_eq!(Field::Detail.accepts("-1"), None);
        assert_eq!(Field::Detail.accepts("banana"), None);
    }

    /// **`off` releases the clamp, and the field reports what is installed.**
    ///
    /// The posture field is the last of §5.1's relocations: the value lives in
    /// core, so a pure `apply` can install it. `off` is the same word
    /// `/posture off` uses — a released clamp is a value, not a second verb.
    #[test]
    fn the_posture_field_reports_and_releases_what_is_installed() {
        let _guard = settings_guard();
        newt_core::posture::set_active_posture(None);
        assert_eq!(Field::Posture.current(), "off");

        newt_core::posture::set_active_posture(Some(newt_core::posture::ActivePosture {
            name: "locked".to_string(),
            preset_name: "strict".to_string(),
            clamp: newt_core::Caveats::default(),
            clamp_summary: "deny writes".to_string(),
            skill_body: None,
            framing: None,
        }));
        assert_eq!(Field::Posture.current(), "locked");

        assert_eq!(
            apply(Field::Posture, "off"),
            Ok("permission posture: off".to_string())
        );
        assert!(
            newt_core::posture::active_posture().is_none(),
            "`off` releases the clamp rather than storing the word"
        );
    }

    /// Grounds the atomic resolver tests in the real config-loading and
    /// receipt path used by the settings form. A declared but invalid posture
    /// must neither change authority nor journal a successful setting change.
    #[tokio::test]
    async fn posture_settings_resolution_failure_is_not_a_success_receipt() {
        // Config::resolve also has unguarded readers and subprocess consumers
        // in this test binary. Keep this new NEWT_CONFIG fixture in a child,
        // so it cannot leak into another test's environment snapshot.
        const CHILD: &str = "NEWT_POSTURE_CONFIG_FIXTURE";
        if std::env::var_os(CHILD).is_none() {
            let fixture = tempfile::tempdir().unwrap();
            let config_dir = fixture.path().join(".newt");
            std::fs::create_dir_all(&config_dir).unwrap();
            let config = config_dir.join("config.toml");
            std::fs::write(&config, "[modes.broken]\npreset = 'missing'\n").unwrap();
            let mut command = tokio::process::Command::new(std::env::current_exe().unwrap());
            // Same three-axis policy as CLI common::isolate: an explicit
            // config alone does not stop the project walk or user drop-ins.
            for (key, _) in std::env::vars_os() {
                if key.to_string_lossy().starts_with("NEWT_") {
                    command.env_remove(key);
                }
            }
            for key in [
                "OPENAI_BASE_URL",
                "OPENAI_API_KEY",
                "OPENAI_MODEL",
                "OLLAMA_HOST",
            ] {
                command.env_remove(key);
            }
            command.args([
                    "--exact",
                    "settings_form::tests::posture_settings_resolution_failure_is_not_a_success_receipt",
                    "--nocapture",
                ])
                .current_dir(fixture.path())
                .env("HOME", fixture.path())
                .env("USERPROFILE", fixture.path())
                .env("NEWT_CONFIG_DIR", &config_dir)
                .env("NEWT_CONFIG", &config)
                .env(CHILD, "1")
                .stdin(std::process::Stdio::null())
                .kill_on_drop(true);
            let output = tokio::time::timeout(newt_core::test_guard::HANG_GUARD, command.output())
                .await
                .expect("settings fixture watchdog expired; child is killed on drop")
                .unwrap();
            assert!(
                output.status.success(),
                "isolated settings fixture failed: {}\n{}",
                String::from_utf8_lossy(&output.stdout),
                String::from_utf8_lossy(&output.stderr)
            );
            assert!(String::from_utf8_lossy(&output.stdout).contains("1 passed"));
            return;
        }
        let _guard = settings_guard();
        let receipts = std::env::current_dir().unwrap().join("receipts.jsonl");
        let saved_posture = newt_core::posture::active_posture();
        std::env::set_var(newt_core::settings_receipt::RECEIPT_PATH_ENV, &receipts);
        let previous = newt_core::posture::ActivePosture {
            name: "existing".into(),
            preset_name: "strict".into(),
            clamp: newt_core::Caveats::default(),
            clamp_summary: "deny writes".into(),
            skill_body: None,
            framing: Some("Keep the existing guidance.".into()),
        };
        newt_core::posture::set_active_posture(Some(previous.clone()));
        let result = apply_and_record(Field::Posture, "broken", "/settings posture");
        let installed = newt_core::posture::active_posture();
        newt_core::posture::set_active_posture(saved_posture);
        let error = result.expect_err("an unresolved preset must fail");
        assert!(
            error.contains("names preset 'missing'")
                && error.contains("[permission_presets.missing]"),
            "must reach the missing-preset resolver, not an unrelated config error: {error}"
        );
        let installed = installed.expect("failed resolution must preserve the old posture");
        assert_eq!(installed.name, previous.name);
        assert_eq!(installed.clamp, previous.clamp);
        assert_eq!(installed.framing, previous.framing);
        assert!(
            !receipts.exists(),
            "failed settings must not emit success receipts"
        );
    }

    /// The vocabulary is the operator's own config, read at ask time — a
    /// static list could only ever be wrong about `[modes.<name>]`.
    #[test]
    fn the_posture_vocabulary_always_offers_the_release() {
        let offered = Field::Posture.offered();
        assert!(
            offered.contains(&"off"),
            "releasing the clamp is always available: {offered:?}"
        );
        assert_eq!(offered.first().copied(), Some("off"), "and it leads");
    }

    /// **The verb and the field write one policy** (#2009 PR7).
    ///
    /// `/context compaction` set a `run_chat` local that
    /// `settings_form::apply` could not see. Both doors go through
    /// `NEWT_COMPACTION_TRIGGER` now, read back by the one resolver that owns
    /// the precedence.
    #[test]
    fn the_compaction_field_and_its_verb_resolve_to_one_state() {
        let _guard = settings_guard();
        newt_core::process_env::remove_var("NEWT_COMPACTION_TRIGGER");
        assert_eq!(
            Field::Compaction.current(),
            newt_core::CompactionTriggerPolicy::default().keyword(),
            "unpinned, the field reports what the resolver resolves"
        );

        assert_eq!(
            apply(Field::Compaction, "message_count"),
            Ok("automatic-compaction trigger: message_count".to_string())
        );
        assert_eq!(Field::Compaction.current(), "message_count");
        assert!(newt_core::config::compaction_trigger_is_session_pinned());
    }

    /// The field's vocabulary is the enum's, and only the enum's.
    #[test]
    fn the_compaction_fields_choices_are_the_policies_that_exist() {
        use newt_core::CompactionTriggerPolicy as P;
        let offered = Field::Compaction.offered();
        assert_eq!(
            offered,
            vec![P::HeadroomAware.keyword(), P::MessageCount.keyword()]
        );
        // A policy the enum does not have is refused, not stored.
        assert_eq!(Field::Compaction.accepts("aggressive"), None);
        // ...and the canonical parser's case-insensitivity is inherited.
        assert_eq!(
            Field::Compaction.accepts("HEADROOM_AWARE"),
            Some("headroom_aware".to_string())
        );
    }

    /// **The trailing space survives the deep link.**
    ///
    /// `/settings prompt "❯ "` is the case that decides whether the port was
    /// real. `run` split on whitespace and trimmed, which would turn that
    /// template into `❯` — rendering flush against whatever the operator
    /// types — and nothing downstream would report the difference. The quoted
    /// parsing the `/prompt set` verb owned is carried, not reinvented.
    #[test]
    fn a_quoted_template_reaches_the_field_verbatim() {
        let _guard = settings_guard();
        newt_core::process_env::remove_var("NEWT_PROMPT");

        let out = run(&declining(), "prompt \"[$TIME] ❯ \"");
        assert_eq!(out.len(), 1, "{out:?}");
        assert_eq!(
            Field::Prompt.current(),
            "[$TIME] ❯ ",
            "the trailing space is the whole point of quoting it"
        );

        // Internal spaces too, and one layer of quotes only.
        run(&declining(), "prompt \"a  b\"");
        assert_eq!(Field::Prompt.current(), "a  b");
    }

    /// `reset` is a VALUE of the field, not a second verb — and it removes the
    /// variable rather than storing the word, so nothing is left for the next
    /// `/prompt` to explain.
    #[test]
    fn resetting_the_prompt_releases_it_to_config() {
        let _guard = settings_guard();
        newt_core::process_env::set_var("NEWT_PROMPT", "custom ");
        assert_eq!(Field::Prompt.current(), "custom ");

        run(&declining(), "prompt reset");
        assert!(
            std::env::var("NEWT_PROMPT").is_err(),
            "reset must remove the override, not store the word"
        );
        assert_ne!(Field::Prompt.current(), "reset");
    }

    /// A `Text` field is not a menu and not a number, and the hint says so.
    #[test]
    fn the_text_field_offers_a_release_and_a_shape_not_a_vocabulary() {
        assert_eq!(Field::Prompt.offered(), vec!["reset"]);
        let hint = Field::Prompt.accepts_hint();
        assert!(hint.contains("reset"), "{hint}");
        assert!(hint.contains("template"), "{hint}");
    }

    /// **The mode field's vocabulary IS the enum's** (#2009 PR4b).
    ///
    /// The styles, their order and their descriptions are read off
    /// `OperatingMode` rather than typed out beside it. A style added to the
    /// enum appears in the form without an edit here — which is the reuse the
    /// type's move down to core buys, beyond making the field possible at all.
    #[test]
    fn the_mode_fields_choices_are_the_enums_own() {
        use newt_core::operating_mode::OperatingMode;
        let offered = Field::Mode.offered();
        let want: Vec<&str> = OperatingMode::all().iter().map(|m| m.as_str()).collect();
        assert_eq!(offered, want, "the form drifted from the mode vocabulary");
        assert!(!want.is_empty(), "an empty list would make this vacuous");
        // Every style is choosable, `full-auto` included: this is the human's
        // door, and the human-only exclusion belongs to the MODEL's schema.
        assert!(offered.contains(&"full-auto"));
    }

    /// The verb and the field select one style, through one setter.
    #[test]
    fn the_mode_field_and_its_verb_resolve_to_one_state() {
        use newt_core::operating_mode::{session_operating_mode, OperatingMode};
        let _guard = settings_guard();
        newt_core::process_env::remove_var("NEWT_MODE");
        assert_eq!(session_operating_mode(), OperatingMode::Chat);
        assert_eq!(Field::Mode.current(), "chat");

        assert_eq!(
            apply(Field::Mode, "plan"),
            Ok("operating mode: plan".to_string())
        );
        assert_eq!(session_operating_mode(), OperatingMode::Plan);
        assert_eq!(Field::Mode.current(), "plan");
    }

    /// The keywords the verb accepted, kept by the absorption.
    #[test]
    fn the_mode_verbs_aliases_survive_absorption() {
        assert_eq!(Field::Mode.accepts("developer"), Some("dev".to_string()));
        assert_eq!(Field::Mode.accepts("sysadmin"), Some("admin".to_string()));
        assert_eq!(
            Field::Mode.accepts("FULL-AUTO"),
            Some("full-auto".to_string())
        );
        assert_eq!(Field::Mode.accepts("nonsense"), None);
    }

    /// **The verb and the field write the same state** (#2009 PR4).
    ///
    /// `/markdown off` set a `run_chat` local; `/settings markdown off` could
    /// not see it. Two writers for one setting is how they come to disagree,
    /// and it is why the receipt could not record a from→to. Both doors now
    /// go through `NEWT_MARKDOWN`, read back by the one resolver.
    /// `/settings prefix ctrl+a` persists to `[tui] prefix_key` (through the
    /// config seam — no real file) and takes effect this session; the
    /// resolver the panel driver parses reads it back; an unoffered chord is
    /// refused and writes nothing.
    #[test]
    fn the_prefix_field_persists_and_takes_effect_now() {
        let _guard = settings_guard();
        newt_core::process_env::remove_var("NEWT_PREFIX_KEY");
        let path = newt_core::Config::user_config_path().expect("a config path");
        assert_eq!(
            apply(Field::Prefix, "ctrl+a"),
            Ok("meta prefix key: ctrl+a".to_string())
        );
        let saved = config_fs::read(&path);
        assert!(
            saved.contains("[tui]") && saved.contains("prefix_key = \"ctrl+a\""),
            "{saved}"
        );
        assert_eq!(Field::Prefix.current(), "ctrl+a");
        assert_eq!(prefix_setting(), "ctrl+a");
        assert!(
            apply(Field::Prefix, "ctrl+q").is_err(),
            "not an offered chord"
        );
        assert_eq!(
            config_fs::read(&path),
            saved,
            "a refused value writes nothing"
        );
        newt_core::process_env::remove_var("NEWT_PREFIX_KEY");
    }

    #[test]
    fn the_markdown_field_and_its_verb_resolve_to_one_state() {
        let _guard = settings_guard();
        newt_core::process_env::remove_var("NEWT_MARKDOWN");
        assert_eq!(
            Field::Markdown.current(),
            crate::migration_notices::read(newt_core::config::session_markdown_mode).keyword(),
            "unpinned, the field reports what the resolver resolves"
        );

        assert_eq!(
            apply(Field::Markdown, "off"),
            Ok("markdown rendering: off".to_string())
        );
        assert_eq!(Field::Markdown.current(), "off");
        assert!(newt_core::config::markdown_is_session_pinned());

        // `auto` is a value, not a release: it means "follow colour", which
        // is a different state from `on` on a pipe.
        assert_eq!(
            apply(Field::Markdown, "auto"),
            Ok("markdown rendering: auto".to_string())
        );
        assert_eq!(Field::Markdown.current(), "auto");
        assert!(
            newt_core::config::markdown_is_session_pinned(),
            "`auto` is still an operator pin — it beats a config that says off"
        );
    }

    /// The keywords the verb accepted, kept. Absorbing a command must not
    /// silently drop an affordance operators already type.
    #[test]
    fn the_markdown_verbs_aliases_survive_absorption() {
        assert_eq!(Field::Markdown.accepts("always"), Some("on".to_string()));
        assert_eq!(Field::Markdown.accepts("never"), Some("off".to_string()));
        assert_eq!(Field::Markdown.accepts("AUTO"), Some("auto".to_string()));
        assert_eq!(Field::Markdown.accepts("sometimes"), None);
    }

    #[test]
    fn the_aliases_the_absorbed_verbs_took_still_resolve() {
        for (field, typed, want) in [
            (Field::EditMode, "vim", "vi"),
            (Field::Tenacity, "inherit", "auto"),
            (Field::Tenacity, "reset", "auto"),
            (Field::Initiative, "inherit", "auto"),
            (Field::Initiative, "reset", "auto"),
            (Field::Cognition, "none", "off"),
            (Field::Cognition, "reset", "auto"),
            (Field::Cognition, "persona", "auto"),
        ] {
            assert_eq!(
                field.accepts(typed).as_deref(),
                Some(want),
                "/{} {typed} stopped resolving",
                field.name()
            );
        }
        // Anti-vacuous: `accepts` is not "yes to everything".
        assert_eq!(Field::Tenacity.accepts("obsessive"), None);
        assert_eq!(Field::Initiative.accepts("relentless"), None);
        assert_eq!(Field::Cognition.accepts("hard"), None);
        assert_eq!(Field::Thinking.accepts("maybe"), None);
    }

    /// A refused value on a dial writes nothing — the same law slice 1 pinned
    /// for the editor, now that four more fields can be refused.
    #[test]
    fn a_refused_dial_value_changes_nothing() {
        let _g = settings_guard();
        apply(Field::Tenacity, "relentless").expect("a level applies");
        let err = apply(Field::Tenacity, "obsessive").expect_err("not a level");
        assert!(err.contains("relentless"), "{err}");
        assert_eq!(Field::Tenacity.current(), "relentless", "a refusal wrote");
        // A pre-split level is not a tenacity level any more.
        assert!(apply(Field::Tenacity, "insistent").is_err());
        assert_eq!(Field::Tenacity.current(), "relentless", "a refusal wrote");
    }

    /// **The receipt destination is read from the registry, not assumed.**
    ///
    /// This is the law that keeps the two files honest with each other: a
    /// field of `/settings` whose verb the registry has not given a
    /// destination writes nothing, and the ratchet keeps counting it. You
    /// cannot add a knob to this form and get provenance by accident.
    #[test]
    fn every_field_declares_where_its_receipt_lands() {
        use crate::slash_registry::{receipt_for, Receipt};
        for field in Field::ALL {
            assert_eq!(
                receipt_for(field.name()),
                Receipt::Journal,
                "/settings {} has no declared receipt destination",
                field.name()
            );
            assert!(change_for(field.name(), "a".into(), "b".into(), "/settings").is_some());
        }
        // **Anti-vacuous.** If the column read `Journal` for everything,
        // reading it would prove nothing. `/setup` mutates and records no
        // decision; `/help` mutates nothing.
        //
        // This used to name `/rounds`, which is the whole point of #1998: the
        // example had to be replaced because the debt it stood for was paid.
        // It then named `/remember`, and #2085 PR-E2 paid that one too — to the
        // OTHER destination. That makes it the sharpest row here: the loop
        // above demands `Journal` of every field, and `/remember` is `Event`,
        // so the two writers provably read disjoint variants rather than one
        // shared "somebody records this".
        assert_eq!(receipt_for("remember"), Receipt::Event);
        assert_eq!(receipt_for("setup"), Receipt::Missing);
        assert_eq!(receipt_for("help"), Receipt::None_);
        assert_eq!(receipt_for("zzznotacommand"), Receipt::Missing);
    }

    /// The recorded change carries the transition AND the route taken — the
    /// half of the event a reader cannot reconstruct from the resulting state.
    #[test]
    fn the_recorded_change_names_the_route_and_the_transition() {
        let change = change_for(Field::EditMode.name(), "vi".into(), "emacs".into(), "/vi")
            .expect("declared");
        assert_eq!(change.setting, "edit-mode");
        assert_eq!(change.from.to_string(), "vi");
        assert_eq!(change.to.to_string(), "emacs");
        assert_eq!(change.via, "/vi");
        assert_eq!(
            change.schema,
            newt_core::settings_receipt::SETTING_CHANGE_SCHEMA_V1
        );
    }

    /// **The round cap takes a number, which no other field does** (#1998).
    ///
    /// The form only knew closed vocabularies, and that — not the derived
    /// operations — is what actually kept `/rounds` outside it.
    #[test]
    fn the_round_cap_accepts_a_bounded_number_and_a_release_token() {
        let max = newt_core::tenacity::RELENTLESS_TOOL_ROUND_TARGET;
        assert_eq!(Field::Rounds.accepts("50").as_deref(), Some("50"));
        // Canonicalized, so `0050` and `50` are one value with one address.
        assert_eq!(Field::Rounds.accepts(" 0050 ").as_deref(), Some("50"));
        assert_eq!(
            Field::Rounds.accepts(&max.to_string()).as_deref(),
            Some(max.to_string().as_str())
        );
        // Every word the verb uses to RELEASE the override lands on one value.
        for release in ["auto", "reset", "default"] {
            assert_eq!(Field::Rounds.accepts(release).as_deref(), Some("auto"));
        }
        // Anti-vacuous: the bound is real at both ends, and it is a number
        // field, not a "take anything" field.
        assert_eq!(Field::Rounds.accepts("0"), None, "0 rounds is not a limit");
        assert_eq!(Field::Rounds.accepts(&(max + 1).to_string()), None);
        assert_eq!(
            Field::Rounds.accepts("unlimited"),
            None,
            "a verb affordance, not a value"
        );
        assert_eq!(Field::Rounds.accepts("banana"), None);
    }

    /// A numeric field is asked for, not chosen from — and it is still a typed
    /// definition, so it still renders everywhere the menus do.
    #[test]
    fn the_number_field_is_a_text_definition_not_a_menu() {
        let _g = settings_guard();
        let asked = value_menu(Field::Rounds);
        assert!(
            asked
                .controls
                .iter()
                .all(|c| !matches!(c.kind, newt_interaction::ControlKind::Choice { .. })),
            "a number has no menu to pick from"
        );
        let rendered = newt_core::markup::plain::render(&asked);
        assert!(rendered.contains("10000"), "the bound is shown: {rendered}");

        // …and the menus still are menus.
        assert!(value_menu(Field::Tenacity)
            .controls
            .iter()
            .any(|c| matches!(c.kind, newt_interaction::ControlKind::Choice { .. })));
    }

    /// **The form drives the numeric field end to end**: pick `rounds`, type a
    /// number, and the cap is set.
    #[test]
    fn the_form_walks_to_the_round_cap_and_takes_a_typed_number() {
        let _g = settings_guard();
        newt_core::tenacity::set_session_tool_rounds(None);
        let index = Field::ALL
            .iter()
            .position(|f| *f == Field::Rounds)
            .expect("rounds is offered")
            + 1;
        let out = run(
            &answering_owned(vec![index.to_string(), "320".to_string()]),
            "",
        );
        assert_eq!(out, vec!["tool-call round limit: 320".to_string()]);
        assert_eq!(newt_core::tenacity::session_tool_rounds(), Some(320));

        // An empty line backs out rather than being an invalid number.
        let out = run(
            &answering_owned(vec![index.to_string(), "  ".to_string()]),
            "",
        );
        assert_eq!(out, vec!["settings: cancelled".to_string()]);
        assert_eq!(
            newt_core::tenacity::session_tool_rounds(),
            Some(320),
            "backing out of the number must not clear the cap"
        );
    }

    /// **The receipt for a cap carries the whole derivation** (#1998) — the
    /// difference between "320 rounds" and the record #1965 asked for.
    #[test]
    fn the_round_cap_records_its_derivation_not_just_the_number() {
        use newt_core::settings_receipt::SettingValue;
        let _g = settings_guard();
        newt_core::tenacity::set_configured_tool_rounds(Some(40));
        newt_core::tenacity::set_cli_tenacity(newt_core::Tenacity::Relentless);
        newt_core::tenacity::set_session_tool_rounds(None);

        let from = Field::Rounds.value_now();
        apply(Field::Rounds, "320").expect("in range");
        let to = Field::Rounds.value_now();
        let change = change_for(Field::Rounds.name(), from, to, "/max-rounds").expect("declared");

        let SettingValue::ToolRounds(after) = &change.to else {
            panic!("the cap was recorded as a bare token: {:?}", change.to);
        };
        assert_eq!(after.rounds, 320);
        assert_eq!(after.configured, 40, "the baseline it escalated over");
        assert!(after.is_escalated());
        assert_eq!(change.via, "/max-rounds", "the alias typed is bound in");

        // Anti-vacuous: a token-valued field is still a token, so the branch
        // above is about `Rounds` and not about every field.
        let token = change_for(
            Field::EditMode.name(),
            Field::EditMode.value_now(),
            Field::EditMode.value_now(),
            "/vi",
        )
        .expect("declared");
        assert!(matches!(token.to, SettingValue::Token(_)));
    }

    /// **No baseline means no invented one.** With nothing installed the cap
    /// records as a token — honest — rather than claiming a `configured` it
    /// never saw.
    #[test]
    fn a_cap_with_no_installed_baseline_records_a_token_not_a_guess() {
        use newt_core::settings_receipt::SettingValue;
        let _g = settings_guard();
        newt_core::tenacity::set_configured_tool_rounds(None);
        newt_core::tenacity::set_session_tool_rounds(Some(320));
        assert!(
            matches!(Field::Rounds.value_now(), SettingValue::Token(ref t) if t == "320"),
            "with no baseline the cap must record as a token, not a guessed derivation"
        );

        // Anti-vacuous: install one and the SAME override records as a
        // derivation, so the assertion above is about the baseline and not
        // about `value_now` always returning a token.
        newt_core::tenacity::set_configured_tool_rounds(Some(40));
        assert!(matches!(
            Field::Rounds.value_now(),
            SettingValue::ToolRounds(_)
        ));
    }

    /// The recording route applies exactly what the bare mutation would.
    ///
    /// The guard holds the journal off, so this creates no filesystem state:
    /// what it proves is that `apply_and_record` still performs the change,
    /// not what the written line looks like — which
    /// `newt_core::settings_receipt`'s own tests own.
    #[test]
    fn recording_does_not_change_what_gets_applied() {
        let _g = settings_guard();
        let recorded = apply_and_record(Field::Tenacity, "relentless", "/settings")
            .expect("an offered value applies");
        assert_eq!(Field::Tenacity.current(), "relentless");
        apply(Field::Tenacity, "auto").expect("released");
        let bare = apply(Field::Tenacity, "relentless").expect("an offered value applies");
        assert_eq!(recorded, bare, "the recorder changed the outcome");
    }

    /// **Every dial position is a token the form would accept.**
    ///
    /// The load-bearing guard for the typed door. `apply_cognition` /
    /// `apply_tenacity` report the change using a token, and `change_for`
    /// records that token as the new value — so a dial position whose token
    /// the form does not offer would journal a value `/settings cognition`
    /// could never be given. The two vocabularies derive from the same
    /// `Cognition::all()` / `Tenacity::all()` labels; this is what keeps that
    /// true after someone adds a ladder position to one of them.
    // Guards the typed door, which the lean build has no panel to call.
    #[cfg(feature = "rich-tui")]
    #[test]
    fn every_dial_position_is_a_token_the_form_offers() {
        use newt_core::cognition::CognitionOverride;
        use newt_core::role_profile::Cognition;
        use newt_core::Tenacity;

        let mut cognition = vec![CognitionOverride::Unset, CognitionOverride::Off];
        cognition.extend(Cognition::all().iter().copied().map(CognitionOverride::Set));
        for choice in cognition {
            let token = cognition_token(choice);
            assert!(
                Field::Cognition.accepts(&token).is_some(),
                "cognition dial position {choice:?} renders {token:?}, which the form refuses"
            );
        }

        let mut tenacity: Vec<Option<Tenacity>> = vec![None];
        tenacity.extend(Tenacity::all().iter().copied().map(Some));
        for level in tenacity {
            let token = tenacity_token(level);
            assert!(
                Field::Tenacity.accepts(&token).is_some(),
                "tenacity dial position {level:?} renders {token:?}, which the form refuses"
            );
        }

        let mut initiative: Vec<Option<newt_core::Initiative>> = vec![None];
        initiative.extend(newt_core::Initiative::all().iter().copied().map(Some));
        for level in initiative {
            let token = initiative_token(level);
            assert!(
                Field::Initiative.accepts(&token).is_some(),
                "initiative dial position {level:?} renders {token:?}, which the form refuses"
            );
        }
    }

    /// **The receipt names the setting the door was asked to change, and the
    /// route the operator took.**
    ///
    /// The guard an adversarial review earned: with the `Field` handed to
    /// `recorded` unobserved, swapping `Field::Cognition` for
    /// `Field::Tenacity` inside `apply_cognition` left the runtime state
    /// right, the returned message right, and all 1289 tests green — while
    /// the journal gained a false `tenacity` row (from == to) and the
    /// operator's real cognition change was recorded nowhere. Exactly the
    /// #1965 gap this change closes, with a fabricated entry on top.
    ///
    /// Asserted through the DOOR, on the change the door produced. A test
    /// that called `change_for(Field::Cognition, ..)` beside it would supply
    /// the very field the door was supposed to choose, and could not catch
    /// the swap. Filesystem-free: `settings_guard` blanks the receipt path, so
    /// what is checked is the change that WOULD be written.
    ///
    /// **Grounded by `settings_form_pty_test`**, which drives the token door
    /// against a real `receipts.jsonl` and proves `record` actually appends.
    /// This test owns the half that one cannot vary — which setting and which
    /// route each door names — and that one owns the half this cannot see.
    #[cfg(feature = "rich-tui")]
    #[test]
    fn each_typed_door_journals_its_own_setting_and_route() {
        use newt_core::cognition::CognitionOverride;
        use newt_core::role_profile::Cognition;
        let _g = settings_guard();

        apply(Field::Cognition, "off").expect("arrange");
        let (_, change) =
            journalled_cognition(CognitionOverride::Set(Cognition::all()[0]), "/psyche edit");
        let change = change.expect("cognition declares a journal destination");
        assert_eq!(change.setting, "cognition", "the door journals ITS setting");
        assert_eq!(change.via, "/psyche edit", "the route is the one taken");
        assert_eq!(change.from.to_string(), "off");
        assert_eq!(change.to.to_string(), Cognition::all()[0].label());

        apply(Field::Tenacity, "normal").expect("arrange");
        let (_, change) = journalled_tenacity(None, "/psyche");
        let change = change.expect("tenacity declares a journal destination");
        assert_eq!(change.setting, "tenacity");
        assert_eq!(change.via, "/psyche");
        assert_eq!(change.from.to_string(), "normal");
        assert_eq!(change.to.to_string(), "auto", "None releases the override");

        apply(Field::Initiative, "patient").expect("arrange");
        let (_, change) = journalled_initiative(Some(newt_core::Initiative::Eager), "/psyche");
        let change = change.expect("initiative declares a journal destination");
        assert_eq!(
            change.setting, "initiative",
            "the door journals ITS setting"
        );
        assert_eq!(change.via, "/psyche");
        assert_eq!(change.from.to_string(), "patient");
        assert_eq!(change.to.to_string(), "eager");
    }

    /// **The two doors apply the same thing.**
    ///
    /// A typed caller and a token caller must be indistinguishable in what
    /// they leave behind, or the panel and the slash verb would drift into
    /// two settings wearing one name. Asserted on the runtime state AND on
    /// the reported message, since the message is what a receipt records.
    // Guards the typed door, which the lean build has no panel to call.
    #[cfg(feature = "rich-tui")]
    #[test]
    fn the_typed_door_and_the_token_door_agree() {
        use newt_core::cognition::CognitionOverride;
        use newt_core::role_profile::Cognition;
        use newt_core::Tenacity;
        let _g = settings_guard();

        // EVERY ladder position, not a sample: the panel offers all of them,
        // so a position the two doors disagree about is one an operator can
        // actually reach.
        let mut cognition = vec![
            (CognitionOverride::Unset, "auto".to_string()),
            (CognitionOverride::Off, "off".to_string()),
        ];
        cognition.extend(
            Cognition::all()
                .iter()
                .map(|c| (CognitionOverride::Set(*c), c.label().to_string())),
        );
        for (choice, token) in cognition {
            let token = token.as_str();
            // Start from somewhere else, so each door has something to change.
            apply(Field::Cognition, "off").expect("arrange");
            let typed = apply_cognition(choice, "/psyche");
            let after_typed = Field::Cognition.current();

            apply(Field::Cognition, "off").expect("arrange");
            let by_token =
                apply_and_record(Field::Cognition, token, "/settings").expect("offered token");
            assert_eq!(
                Field::Cognition.current(),
                after_typed,
                "same runtime state"
            );
            assert_eq!(typed, by_token, "same reported change");
        }

        let mut tenacity = vec![(None, "auto".to_string())];
        tenacity.extend(
            Tenacity::all()
                .iter()
                .map(|t| (Some(*t), t.label().to_string())),
        );
        for (level, token) in tenacity {
            let token = token.as_str();
            apply(Field::Tenacity, "normal").expect("arrange");
            let typed = apply_tenacity(level, "/psyche");
            let after_typed = Field::Tenacity.current();

            apply(Field::Tenacity, "normal").expect("arrange");
            let by_token =
                apply_and_record(Field::Tenacity, token, "/settings").expect("offered token");
            assert_eq!(Field::Tenacity.current(), after_typed, "same runtime state");
            assert_eq!(typed, by_token, "same reported change");
        }

        let mut initiative = vec![(None, "auto".to_string())];
        initiative.extend(
            newt_core::Initiative::all()
                .iter()
                .map(|i| (Some(*i), i.label().to_string())),
        );
        for (level, token) in initiative {
            let token = token.as_str();
            apply(Field::Initiative, "measured").expect("arrange");
            let typed = apply_initiative(level, "/psyche");
            let after_typed = Field::Initiative.current();

            apply(Field::Initiative, "measured").expect("arrange");
            let by_token =
                apply_and_record(Field::Initiative, token, "/settings").expect("offered token");
            assert_eq!(
                Field::Initiative.current(),
                after_typed,
                "same runtime state"
            );
            assert_eq!(typed, by_token, "same reported change");
        }
    }

    /// The deprecated verbs point at their new home and say they still work —
    /// never "unknown command" (#1981).
    #[test]
    fn a_deprecated_verb_names_its_replacement_without_retiring_itself() {
        let notice = moved_notice("vi", Field::EditMode);
        assert!(notice.contains("/vi still works"), "{notice}");
        assert!(notice.contains("/settings edit-mode"), "{notice}");
    }

    #[cfg(feature = "rich-tui")]
    #[test]
    fn personality_apply_records_typed_override_and_inherit_without_pinning_other_dials() {
        use newt_core::role_profile::{PersonalityLevel, PersonalityTraits};
        use newt_core::settings_receipt::SettingValue;
        let _g = newt_core::test_guard::GlobalSettingsGuard::acquire();
        let _ = newt_core::runtime::drain_preference_actions();
        let before_cognition = newt_core::cognition::cli_cognition();
        let before_tenacity = newt_core::tenacity::cli_tenacity();
        let before_initiative = newt_core::initiative::cli_initiative();
        let mut current = PersonalityTraits::default();
        let next = PersonalityTraits {
            warmth: Some(PersonalityLevel::try_from(0).unwrap()),
            ..Default::default()
        };
        let (_, receipt) = journalled_personality(&mut current, next, "/psyche edit");
        assert_eq!(current, next);
        let receipt = receipt.expect("psyche declares its journal destination");
        assert_eq!(receipt.setting, "psyche");
        assert_eq!(receipt.via, "/psyche edit");
        assert_eq!(
            receipt.from,
            SettingValue::from(PersonalityTraits::default())
        );
        assert_eq!(receipt.to, SettingValue::from(next));
        let (_, reset) =
            journalled_personality(&mut current, PersonalityTraits::default(), "/psyche");
        let reset = reset.unwrap();
        assert_eq!(reset.from, SettingValue::from(next));
        assert_eq!(reset.to, SettingValue::from(PersonalityTraits::default()));
        assert_eq!(current, PersonalityTraits::default());
        assert_eq!(newt_core::cognition::cli_cognition(), before_cognition);
        assert_eq!(newt_core::tenacity::cli_tenacity(), before_tenacity);
        assert_eq!(newt_core::initiative::cli_initiative(), before_initiative);
        assert!(newt_core::runtime::drain_preference_actions().is_empty());
    }
}