razeri-plugin-api 0.15.0

Extension traits and registries for Razeri plugins
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
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
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
// Copyright (C) 2026 Gabriel Shea
// Licensed under either of the Apache License, Version 2.0 or the MIT
// license, at your option.

//! **Every obligation a dialect owes, reported at once.**
//!
//! A dialect owes Razeri more than the methods its traits require: decisions
//! whose default is another engine's answer, facts it declares twice that must
//! agree, the wording of its refusals, the names it answers to. Met one at a
//! time, each is a separate failing test, found only once it fires. This
//! module is the report that answers all of them in one pass.
//!
//! # Running it
//!
//! From your own crate's tests, where your dialect is linked and Razeri's
//! registry can see it:
//!
//! ```rust
//! # use razeri_plugin_api::conformance;
//! # fn example(my_dialect: &dyn razeri_plugin_api::PlatformDialect) {
//! let report = conformance::check(my_dialect);
//! assert!(report.is_clean(), "{report}");
//! # }
//! ```
//!
//! # What it can and cannot see
//!
//! Everything here is read off the dialect object, so it works wherever your
//! plugin compiles: it grades what your dialect declares and what its
//! renderers return. It runs no SQL, so anything only an engine can settle —
//! whether the SQL your dialect emits is the SQL that engine accepts — stays
//! yours to test against a real instance.
//!
//! # Your dialect's name
//!
//! Your platform's names are yours. `DialectInfo::name` is the one Razeri
//! reports, `aliases` is every other spelling a design may use for it, and the
//! resolver reads both off the dialects that are loaded — so the report asks
//! only that your own spellings reach you, and there is no table inside Razeri
//! for your platform to be missing from. The one thing it will tell you is that
//! a spelling you declared is one another loaded platform also answers to,
//! because then the spelling names neither of you.

use crate::PlatformDialect;
use razeri_plugin_contract::{
    BitwiseOperator, CatalogueReach, ColumnEvolution, Concat, ConstraintEnforcement, DataType,
    DdlRenderer, DialectCapabilities, DialectFamily, EntityRef, ExpressionRenderer,
    FormulaRenderer, HeldAs, MergeRestrictions, MergeShape, ProcedureForm, RoutineKind,
    RoutineSpec, ScalarFunctionForm, SliceRef, SliceReplacement, SqlRenderer, StorageRenderer,
    TableFunctionForm, UNKNOWN_PLATFORM, WrittenObject,
    data_type::{ArrayDataType, MapDataType, StructDataType, StructField},
    storage_strategy::{
        CompressionStrategy, IndexStrategy, PartitionGranularity, PartitionStrategy, StoragePlan,
    },
};
use std::fmt;

/// How much a finding matters.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
    /// **This dialect will produce SQL its engine rejects.** Fix it before
    /// anything else.
    Blocking,
    /// **A question this dialect has not answered.** Razeri has a default and
    /// will use it; the default was written for a different engine.
    Unanswered,
    /// Worth a look. Nothing is broken.
    Note,
}

impl fmt::Display for Severity {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Self::Blocking => "must fix",
            Self::Unanswered => "unanswered",
            Self::Note => "worth a look",
        })
    }
}

/// One thing to fix, in the order a reader wants it: what is wrong, then what
/// to do about it.
#[derive(Debug, Clone)]
pub struct Finding {
    /// How much it matters.
    pub severity: Severity,
    /// One line naming what is wrong.
    pub what: String,
    /// One line naming what to do.
    pub fix: String,
}

/// The whole report for one dialect.
#[derive(Debug, Clone)]
pub struct Report {
    /// The dialect's registry name.
    pub dialect: String,
    /// Everything found, worst first.
    pub findings: Vec<Finding>,
}

impl Report {
    /// Whether nothing blocking or unanswered was found.
    ///
    /// Notes do not fail a report: they are observations, not obligations.
    #[must_use]
    pub fn is_clean(&self) -> bool {
        !self.findings.iter().any(|f| f.severity != Severity::Note)
    }

    /// How many findings of one severity.
    #[must_use]
    pub fn count(&self, severity: Severity) -> usize {
        self.findings.iter().filter(|f| f.severity == severity).count()
    }
}

impl fmt::Display for Report {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = &self.dialect;
        if self.findings.is_empty() {
            return writeln!(
                f,
                "{name}: nothing to fix. Every question Razeri asks a dialect, this one answers."
            );
        }
        let blocking = self.count(Severity::Blocking);
        let unanswered = self.count(Severity::Unanswered);
        let notes = self.count(Severity::Note);
        writeln!(f, "{name}: {blocking} to fix, {unanswered} unanswered, {notes} worth a look.")?;
        for finding in &self.findings {
            writeln!(f)?;
            writeln!(f, "  [{}] {}", finding.severity, finding.what)?;
            writeln!(f, "      → {}", finding.fix)?;
        }
        Ok(())
    }
}

/// **The report.** Everything about this dialect that Razeri can check by
/// asking it.
///
/// Registration is checked against the dialects linked into this program. A
/// dialect the host loaded at run time is not among them, so report it with
/// [`check_with_registry`] and the registry it was loaded into.
#[must_use]
pub fn check(dialect: &dyn PlatformDialect) -> Report {
    check_with_registry(dialect, &crate::dialect::PlatformRegistry::with_auto_discovered())
}

/// The same report, against a registry the host built — the path for a dialect
/// loaded at run time rather than linked in.
#[must_use]
pub fn check_with_registry(
    dialect: &dyn PlatformDialect,
    registry: &crate::dialect::PlatformRegistry,
) -> Report {
    let info = dialect.info();
    let mut findings = Vec::new();

    check_registration(&info, registry, &mut findings);
    check_family(info.dialect_family, &mut findings);
    check_required_rendering(dialect, &mut findings);
    check_decisions(dialect, &mut findings);
    check_formula_renderer(dialect, &mut findings);
    check_evolution(dialect, &mut findings);
    check_run_again(dialect, &mut findings);
    check_transactions(dialect, &mut findings);
    check_paired_facts(dialect, &mut findings);
    check_schema_fold(dialect, &mut findings);
    check_deploy_client(&info, &mut findings);

    findings.sort_by_key(|f| f.severity);
    Report { dialect: info.name.to_owned(), findings }
}

/// Every spelling this dialect answers to has to reach this dialect: its own
/// name, and each alias it declares.
///
/// That is the whole obligation, and it is one the author can meet from their
/// own crate — the spellings are the dialect's own, so there is no list inside
/// Razeri to be missing from and nothing here can ask for a change only Razeri
/// could make. What is worth saying is a spelling that reaches somewhere else:
/// one another loaded platform also claims (so it names neither, and Razeri
/// refuses it), or one that reaches a different platform because the registry
/// holds this dialect under another key.
fn check_registration(
    info: &crate::dialect::DialectInfo,
    registry: &crate::dialect::PlatformRegistry,
    findings: &mut Vec<Finding>,
) {
    let name = info.name;
    if matches!(
        crate::resolve::resolve_dialect_by_name_with_registry(name, registry),
        Err(crate::ResolveError::UnknownDialect { .. })
    ) {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!(
                "the name `{name}` does not resolve — nothing in this program registered it"
            ),
            fix: "add `razeri_plugin_api::export_plugin!(register);` at the top of your \
                  crate's `lib.rs`, where `register` calls \
                  `builder.register_dialect(Arc::new(MyDialect))`. The one line makes the \
                  dialect one the registry finds, whether razeri loads your library from a \
                  plugin directory or links your crate in."
                .to_owned(),
        });
        return;
    }
    for spelling in info.spellings() {
        match crate::resolve::resolve_dialect_by_name_with_registry(spelling, registry) {
            Ok(found) if found.info().name == name => {}
            Ok(found) => findings.push(Finding {
                severity: Severity::Blocking,
                what: format!("`{spelling}` reaches `{}` rather than `{name}`", found.info().name),
                fix: format!(
                    "the registry holds `{name}` under another key. Register it under its own \
                     name, or drop `{spelling}` from its aliases."
                ),
            }),
            Err(crate::ResolveError::AmbiguousName { claimed_by, .. }) => findings.push(Finding {
                severity: Severity::Blocking,
                what: format!(
                    "`{spelling}` is claimed by more than one loaded platform ({}), so it names \
                     none of them — Razeri refuses that spelling rather than guessing which one \
                     a design meant",
                    claimed_by.join(" and ")
                ),
                fix: format!(
                    "give this dialect a name no other platform answers to. A platform name is \
                     read off the loaded dialects' own declared spellings, so a spelling another \
                     platform already claims is never `{name}`'s."
                ),
            }),
            Err(_) => findings.push(Finding {
                severity: Severity::Blocking,
                what: format!(
                    "`{spelling}` is declared as a spelling of `{name}` but reaches no platform"
                ),
                fix: format!(
                    "the `{name}` in the registry is a different build of this dialect and does \
                     not declare `{spelling}`. Register the one you are grading."
                ),
            }),
        }
    }
}

/// A family is a claim about spelling, so an unmeasured engine claiming a
/// measured family's name licenses SQL it may reject.
fn check_family(family: DialectFamily, findings: &mut Vec<Finding>) {
    if matches!(family, DialectFamily::Custom(name) if name.trim().is_empty()) {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect's family is a custom one with no name".to_owned(),
            fix: "give `DialectFamily::Custom` your engine's own name — it is what a report \
                  prints and what a reader looks up."
                .to_owned(),
        });
    }
}

/// The four required methods must answer for every type a design can declare.
fn check_required_rendering(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    let declarable = declarable_types();
    let empty_column: Vec<String> = declarable
        .iter()
        .filter(|dt| SqlRenderer::render_data_type(dialect, dt).trim().is_empty())
        .map(|dt| format!("{dt:?}"))
        .collect();
    if !empty_column.is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!(
                "`render_data_type` answers nothing for {} of the types a design can declare",
                empty_column.len()
            ),
            fix: "every declarable type needs a column spelling on this engine, even if the \
                  spelling is a wider type that stores it. A blank answer becomes a column \
                  declaration with no type in it."
                .to_owned(),
        });
    }
    let empty_cast: Vec<String> = declarable
        .iter()
        .filter(|dt| SqlRenderer::render_cast_type(dialect, dt).trim().is_empty())
        .map(|dt| format!("{dt:?}"))
        .collect();
    if !empty_cast.is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!(
                "`render_cast_type` answers nothing for {} declarable types",
                empty_cast.len()
            ),
            fix: "the generated load opens by casting every source column to its declared type, \
                  so a blank answer fails the load on its first statement."
                .to_owned(),
        });
    }
    let quoted = SqlRenderer::quote_identifier(dialect, "order");
    if quoted == "order" {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "`quote_identifier` returns the identifier unchanged, so a reserved word \
                   reaches this engine bare"
                .to_owned(),
            fix: "wrap the identifier in whatever this engine quotes with — double quotes, \
                  backticks, or brackets."
                .to_owned(),
        });
    }
    if SqlRenderer::render_current_timestamp(dialect).trim().is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "`render_current_timestamp` answers nothing, and every generated load stamps \
                   a row with it"
                .to_owned(),
            fix: "return this engine's own expression for the current moment.".to_owned(),
        });
    }
}

/// **Reading a file in a named encoding**, where the dialect states a way to.
///
/// The same path question [`check_decisions`] asks of the plain relation, for
/// the same reason, plus one only this method can get wrong: an engine that
/// reads no file at all cannot read one in a named encoding, and a dialect that
/// answers here and not there states a relation nothing will ever ask it for.
///
/// Parameters:
/// - `dialect`: the plugin under test.
/// - `probe`: the same quoted path literal the plain relation was asked about.
/// - `findings`: where anything wrong is recorded.
fn check_encoded_file_relation(
    dialect: &dyn PlatformDialect,
    probe: &str,
    findings: &mut Vec<Finding>,
) {
    let Some(relation) =
        SqlRenderer::delimited_file_relation_in_encoding(dialect, probe, "windows-1252")
    else {
        return;
    };
    if !relation.contains(probe) {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "`delimited_file_relation_in_encoding` returns a relation that does not carry \
                   the path it was given"
                .to_owned(),
            fix: "put the literal you were handed where your engine's file relation takes a \
                  path. It arrives already quoted and escaped."
                .to_owned(),
        });
    }
    if SqlRenderer::delimited_file_relation(dialect, probe).is_none() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect states how to read a file in a named encoding and states no way \
                   to read a file at all"
                .to_owned(),
            fix: "answer `delimited_file_relation` as well, or answer neither. Razeri asks the \
                  first question before the second — a source whose bytes are UTF-8 never \
                  reaches the encoding arm — so an engine that answers only the second has \
                  written a relation nothing will ask it for."
                .to_owned(),
        });
    }
}

/// The decisions a dialect states rather than inherits.
fn check_decisions(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    let spelling = SqlRenderer::sid_spelling(dialect);
    if spelling.platform == UNKNOWN_PLATFORM {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect states no surrogate-key spelling, so every key it renders calls \
                   an `xxhash64` its engine very likely does not have"
                .to_owned(),
            fix: "override `SqlRenderer::sid_spelling`. Say what this engine calls LOWER and \
                  TRIM, how it joins strings, how it casts to text, and how it spells a 64-bit \
                  hash. If it has no portable hash, name an artifact your prerequisites install \
                  — never the nearest-looking built-in, which keys a warehouse to a number \
                  nothing else can reproduce."
                .to_owned(),
        });
    }

    // A join with no name in it writes `(a, '|', b)` or `a  '|'  b`: a
    // composite key the engine cannot parse, met at the first load that
    // joins two parts.
    let join = match spelling.concat {
        Concat::Function(name) | Concat::FunctionBinary(name) | Concat::Operator(name) => name,
        Concat::Plus => "+",
    };
    if join.trim().is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect's surrogate-key spelling joins the parts of a composite key \
                   with a function or operator that has no name"
                .to_owned(),
            fix: "name what your engine joins strings with in `SidSpelling::concat`: \
                  `Concat::Function(\"CONCAT\")`, `Concat::FunctionBinary(\"CONCAT\")` where it \
                  takes exactly two arguments, or `Concat::Operator(\"||\")` where it has an \
                  operator and no function."
                .to_owned(),
        });
    }

    // **A stated body that says nothing is worse than no body at all.** Razeri
    // writes the `CREATE FUNCTION` envelope around whatever comes back, so an
    // empty one deploys cleanly and returns nothing — every key in the
    // warehouse is then the sentinel or garbage, under a green deploy.
    if let Some(supplied) = SqlRenderer::sid_helper_body(dialect)
        && supplied.body.trim().is_empty()
    {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect says its surrogate-key helper has a body Razeri cannot compose, \
                   and then supplies an empty one"
                .to_owned(),
            fix: "return the body that computes XXH64 with seed 0 over the UTF-8 bytes of the \
                  one argument, or answer `None` and let Razeri compose the derivation from \
                  your `sid_spelling`. An empty body deploys without error and keys every row \
                  to nothing."
                .to_owned(),
        });
    }

    // A file relation that drops its path reads the same file for every entity
    // — one table filled, the rest filled wrong, and nothing about it fails.
    let probe = "'/razeri/probe.csv'";
    if let Some(relation) = SqlRenderer::delimited_file_relation(dialect, probe)
        && !relation.contains(probe)
    {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "`delimited_file_relation` returns a relation that does not carry the path it \
                   was given"
                .to_owned(),
            fix: "put the literal you were handed where your engine's file relation takes a \
                  path. It arrives already quoted and escaped. A relation that drops it reads \
                  one file for every entity the design binds, and the generated fill loads the \
                  wrong rows without failing."
                .to_owned(),
        });
    }

    check_encoded_file_relation(dialect, probe, findings);
    check_routine_forms(dialect, findings);
    check_slice_replacement(dialect, findings);

    // The note is what a reader of the DDL learns from, and on an engine with
    // no keyword of its own it is the ONLY place they can learn it.
    if let ConstraintEnforcement::DeclaredOnly(declared) =
        DdlRenderer::constraint_enforcement(dialect)
        && declared.note.split_whitespace().count() < 5
    {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect says its engine accepts constraints it does not enforce, and \
                   the note explaining that says almost nothing"
                .to_owned(),
            fix: "a reader of your rendered DDL sees a PRIMARY KEY that looks like a \
                  guarantee. Say what this engine really does with the declaration, and where \
                  the guarantee has to come from instead."
                .to_owned(),
        });
    }

    // **The two refusals are different findings, and that is the point of the
    // second variant.** `None` is silence, and silence is unanswered. A stated
    // `Unsupported` is a measured claim; what is left to grade there is whether
    // the sentence is worth reading, because it is the only thing an operator
    // gets where the load would have been.
    match DialectCapabilities::merge_shape(dialect) {
        MergeShape::None => findings.push(Finding {
            severity: Severity::Unanswered,
            what: "this dialect has not stated its upsert shape, so Razeri writes no generated \
                   load for it"
                .to_owned(),
            fix: "if this engine has a MERGE or an upsert statement, say which in \
                  `DialectCapabilities::merge_shape`. If it genuinely has neither, answer \
                  `MergeShape::Unsupported` with your engine's own sentence — a refusal an \
                  operator can act on, rather than a silence."
                .to_owned(),
        }),
        MergeShape::Unsupported(orientation) => {
            check_refusal_copy("the upsert refusal", orientation, findings);
        }
        MergeShape::Merge(restrictions) => check_merge_restrictions(restrictions, findings),
        _ => {}
    }
}

/// **A dialect that names a verb for replacing one slice has to put the slice
/// in it.**
///
/// Most dialects answer nothing here, and nothing is a complete answer: Razeri
/// then writes a delete filtered to the slice, which is correct on every
/// engine. What is worth grading is the dialect that *does* answer, because its
/// statement runs in front of a load's write and clears rows. A statement that
/// forgot the value it was handed does not clear the day this call was given —
/// it clears some other day, or every day, and the load then reports success
/// over a table it emptied.
///
/// The probe is a daily partition of a date column, which is the case every
/// partition-level verb is allowed to answer for. A dialect that answers
/// nothing to it has said the portable form is what it wants.
fn check_slice_replacement(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    const VALUE: &str = "$razeri_slice_probe";
    let plan = StoragePlan {
        partition: PartitionStrategy::RangeByTimeDimension {
            field: "razeri_probe_day".to_owned(),
            granularity: PartitionGranularity::Day,
            retention: None,
            look_ahead: None,
            window: razeri_plugin_contract::storage_strategy::PartitionWindow::default(),
        },
        compression: CompressionStrategy::None,
        index: IndexStrategy::None,
    };
    let day = DataType::date();
    let columns = ["razeri_probe_day"];
    let entity = EntityRef {
        schema: Some("razeri_probe"),
        name: "razeri_probe_table",
        is_identity_table: false,
        columns: &columns,
        partition_column_type: Some(&day),
    };
    let slice = SliceRef { column: "razeri_probe_day", value: VALUE };
    let Some(answer) = StorageRenderer::render_slice_replacement(dialect, entity, &plan, slice)
    else {
        return;
    };

    let carries = match &answer {
        SliceReplacement::Emptied(statements) => {
            !statements.is_empty() && statements.iter().all(|sql| sql.contains(VALUE))
        }
        SliceReplacement::WrittenByInsert(clause) => clause.contains(VALUE),
    };
    // **A verb that carries the slice is graded nowhere**, deliberately. The
    // kit cannot tell a measured answer from a lucky one, and a note on every
    // dialect that got it right would fire on exactly the dialects that need no
    // help. What is left to say is said where it is useful — in the trait's own
    // documentation, which an author reads before writing the override.
    if carries {
        return;
    }
    findings.push(Finding {
        severity: Severity::Blocking,
        what: "`render_slice_replacement` returns a statement that does not carry the slice it \
               was handed"
            .to_owned(),
        fix: "put the value you were given where your statement names the slice, and build any \
              partition NAME from it the same way your extend procedure built it. A replacement \
              that drops the value clears a slice this call was not given — or every slice — \
              and the load reports success over what it emptied."
            .to_owned(),
    });
}

/// **The two halves of an evolution, held together**: what this engine says it
/// keeps when a loaded table changes shape, and the statement it writes for the
/// change.
///
/// An answer left at its default is graded nowhere, for the reason
/// [`check_merge_restrictions`] states: from outside, a measured *"my engine
/// does not do that"* and a question nobody asked read the same, and a finding
/// made out of that silence would fire on every dialect.
///
/// What is worth saying is a **pair that disagrees**. A dialect that says its
/// engine widens a string in place and then writes no statement for a widening
/// has told Razeri to call the change safe and left the operator to work out
/// the `ALTER` alone — a gap that reads to them as Razeri not having finished.
/// And a dialect that answers `None` to a new column has declined the one
/// evolution every warehouse has, which is nearly always an override that meant
/// to narrow something and narrowed everything.
fn check_evolution(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    let answers = DialectCapabilities::evolution_answers(dialect);
    let boolean = DataType::boolean();
    let long = DataType::varchar(40);

    if DdlRenderer::render_column_evolution(
        dialect,
        &ColumnEvolution::AddColumn {
            schema: Some("s"),
            table: "t",
            column: "c",
            data_type: &boolean,
        },
    )
    .is_none()
    {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect writes no statement for a column that is new, which is the \
                  commonest change a deployed warehouse ever sees"
                .to_owned(),
            fix: "answer `ColumnEvolution::AddColumn` in `render_column_evolution`, or drop the \
                 override and take the contract's `ALTER TABLE … ADD COLUMN …`, which most \
                 engines take; some write `ADD` without `COLUMN`, so run it on yours first. An \
                 engine that genuinely has no way to add a column is worth saying out loud in \
                 the crate's own documentation."
                .to_owned(),
        });
    }

    if answers.parameterized_string_widening_is_lossless
        && DdlRenderer::render_column_evolution(
            dialect,
            &ColumnEvolution::WidenColumn {
                schema: Some("s"),
                table: "t",
                column: "c",
                data_type: &long,
            },
        )
        .is_none()
    {
        findings.push(Finding {
            severity: Severity::Note,
            what: "this dialect says its engine widens a string's declared length in place, and \
                  writes no statement for the change"
                .to_owned(),
            fix: "answer `ColumnEvolution::WidenColumn` in `render_column_evolution` with this \
                 engine's spelling of it. Without it Razeri tells an operator the widening \
                 costs nothing and then hands them nothing to run, which reads as Razeri not \
                 having finished rather than as a statement they own."
                .to_owned(),
        });
    }

    if answers.nested_field_addition_is_lossless
        && DdlRenderer::render_column_evolution(
            dialect,
            &ColumnEvolution::AddNestedField {
                schema: Some("s"),
                table: "t",
                column: "c",
                field: "f",
                data_type: &long,
            },
        )
        .is_none()
    {
        findings.push(Finding {
            severity: Severity::Note,
            what: "this dialect says its engine keeps every value when a record gains a field, \
                  and writes no statement for the change"
                .to_owned(),
            fix: "answer `ColumnEvolution::AddNestedField` in `render_column_evolution`. Some \
                 engines name the new field; some restate the whole column's type, which is \
                 why the change carries both the field's path and the column's type with the \
                 field in it."
                .to_owned(),
        });
    }
}

/// **The statements a redeploy and a file of drops run again**, each carrying
/// the object it was handed.
///
/// A drop that forgot the column it was asked about drops another one, or
/// none, under a green run; a guard around a `CREATE TABLE` that left the
/// statement out makes nothing, and the next file meets a table that is not
/// there. Each answer is asked about a table of the dialect's own spelling and
/// read for the names it was given.
fn check_run_again(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    let schema = SqlRenderer::quote_identifier(dialect, "razeri_probe_schema");
    let name = SqlRenderer::quote_identifier(dialect, "razeri_probe_table");
    let relation = format!("{schema}.{name}");
    let table = WrittenObject::new(Some(&schema), &name, &relation);
    let column = SqlRenderer::quote_identifier(dialect, "razeri_probe_column");
    let mut lost: Vec<&str> = Vec::new();

    match DdlRenderer::render_drop_column(dialect, table, &column, false) {
        Some(drop) if drop.text().contains(&column) => {}
        _ => lost.push("render_drop_column"),
    }
    if let Some(guarded) = DdlRenderer::render_drop_column(dialect, table, &column, true)
        && !guarded.text().contains(&column)
    {
        lost.push("render_drop_column (only where it stands)");
    }
    for only_where_it_stands in [false, true] {
        if let Some(drop) = DdlRenderer::render_drop_view(dialect, table, only_where_it_stands)
            && !drop.text().contains(&name)
        {
            lost.push("render_drop_view");
        }
    }
    let create = format!("CREATE TABLE {relation} (a INTEGER)");
    if let Some(guarded) = DdlRenderer::render_create_table_if_absent(dialect, table, &create)
        && !guarded.text().contains(&create)
    {
        lost.push("render_create_table_if_absent");
    }
    lost.dedup();
    if !lost.is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!(
                "{} returns a statement that does not carry the object it was handed",
                lost.join(", ")
            ),
            fix: "put the names you were given where your statement names the object, and keep \
                  a create you guard whole inside the guard. A drop that loses its column drops \
                  another one or none, and a guard that loses its statement makes nothing — \
                  both under a run that reports success."
                .to_owned(),
        });
    }

    // A comparison that loses the value it stands for compares something
    // else, and the load's search for a row that arrives twice finds none.
    let value = "razeri_probe_value";
    let loses_its_value = declarable_types().iter().any(|data_type| {
        [HeldAs::Declared, HeldAs::Converted].into_iter().any(|held| {
            ExpressionRenderer::render_comparable(dialect, value, data_type, held)
                .is_some_and(|compared| !compared.contains(value))
        })
    });
    if loses_its_value {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "`render_comparable` answers an expression that does not carry the value it \
                   stands for"
                .to_owned(),
            fix: "answer the value itself where your engine compares the type directly, an \
                  expression over it where it compares one only that way, and `None` where it \
                  cannot compare one at all — then Razeri refuses such a column by name."
                .to_owned(),
        });
    }

    // A text ceiling of nothing refuses every varchar, and a blank word for
    // what a text cannot be says nothing beside the type.
    if let Some(limits) = DialectCapabilities::text_limits(dialect)
        && (limits.longest_varchar == Some(0)
            || limits.text_is_not.is_some_and(|what| what.trim().is_empty()))
    {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "`text_limits` states a longest varchar of 0, or a blank word for what an \
                   unbounded text cannot be"
                .to_owned(),
            fix: "state the longest varchar(n) your engine creates — one more is refused at \
                  CREATE TABLE — and name what it refuses a text column as, or leave it None."
                .to_owned(),
        });
    }

    match DialectCapabilities::catalogue_reach(dialect) {
        CatalogueReach::HeldColumnsOnly(reason) | CatalogueReach::Nothing(reason) => {
            check_refusal_copy("the reason the schema check is not asked", reason, findings);
        }
        _ => {}
    }
}

/// **A begin statement that leaves the commit to the client**, said by an
/// engine that has no transaction spanning statements, is two answers that
/// cannot both be true.
fn check_transactions(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    // A log line answered with no text writes a blank statement into every
    // routine that logs; `None` is how an engine with no log statement says so.
    if SqlRenderer::render_procedure_log(
        dialect,
        razeri_plugin_contract::ProcedureLogLevel::Info,
        "'razeri'",
    )
    .is_some_and(|line| line.trim().is_empty())
    {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "`render_procedure_log` answers a log line with no text in it".to_owned(),
            fix: "answer `None` where your engine has no statement that writes a message: the \
                  line is then left out of the routine."
                .to_owned(),
        });
    }
    if DialectCapabilities::begin_leaves_the_commit_to_the_client(dialect)
        && !DialectCapabilities::supports_multi_statement_transactions(dialect)
    {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect says its begin statement leaves the commit to the client, and \
                   that its engine has no transaction spanning statements"
                .to_owned(),
            fix: "answer `begin_leaves_the_commit_to_the_client` only for an engine whose loads \
                  run in one transaction. Where there is none, the bundle already tells the \
                  operator the boundary is theirs."
                .to_owned(),
        });
    }
}

/// **What a stated `MERGE` restriction changes about the SQL Razeri writes for
/// this engine**, said back to the author while the claim is still cheap to
/// withdraw.
///
/// A restriction left at its default costs nothing and is graded nowhere: the
/// kit cannot tell a measured *"my engine is fine with this"* from a question
/// nobody asked, and inventing a finding out of that silence would fire on
/// every dialect. What it can do is report the restriction a dialect does
/// state, because each one makes Razeri emit something other than the
/// statement it emits everywhere else.
fn check_merge_restrictions(restrictions: MergeRestrictions, findings: &mut Vec<Finding>) {
    if restrictions.slice_pruning_needs_literal {
        findings.push(Finding {
            severity: Severity::Note,
            what: "this dialect says its planner reads one slice of a partitioned table only \
                   from a literal, so a load scoped to one day cannot simply bind the day"
                .to_owned(),
            fix: "Razeri will put the day into the statement text through this engine's \
                  dynamic-execution form rather than binding it to the load procedure's \
                  parameter. Confirm that is what your engine wants: the statement is composed \
                  once per call and its text differs per day, which some planners cache and \
                  some do not. If a bound parameter does prune here, drop the restriction and \
                  Razeri binds it like everywhere else."
                .to_owned(),
        });
    }
}

/// The one override nothing tells an author about, and every formula in every
/// design goes through it — and the operators a formula is written with.
fn check_formula_renderer(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    // An operator answered with no text writes `a  b`, which reads as two
    // values side by side. `None` is the answer for an operator the engine
    // lacks, and it is refused by name where a formula needs it.
    let bitwise = BitwiseOperator::ALL.map(|operator| {
        (
            format!("render_bitwise_operator({operator:?})"),
            FormulaRenderer::render_bitwise_operator(dialect, operator),
        )
    });
    let blank: Vec<String> = [
        ("render_power_operator".to_owned(), FormulaRenderer::render_power_operator(dialect)),
        ("render_ilike_operator".to_owned(), FormulaRenderer::render_ilike_operator(dialect)),
        (
            "render_not_ilike_operator".to_owned(),
            FormulaRenderer::render_not_ilike_operator(dialect),
        ),
        ("render_concat_operator".to_owned(), FormulaRenderer::render_concat_operator(dialect)),
    ]
    .into_iter()
    .chain(bitwise)
    .filter(|(_, answer)| answer.as_deref().is_some_and(|op| op.trim().is_empty()))
    .map(|(method, _)| method)
    .collect();
    if !blank.is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!("{} answers an operator with no text in it", blank.join(", ")),
            fix: "answer `None` where your engine has no such operator: a formula that needs it \
                  is then refused by name, and the reader is told to write the function your \
                  engine has. An empty answer writes the two operands side by side."
                .to_owned(),
        });
    }

    let formula = SqlRenderer::to_formula_renderer(dialect);
    let dialect_name = SqlRenderer::dialect_name(dialect);
    if formula.dialect_name() != dialect_name && dialect_name != "ansi" {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "`to_formula_renderer` was not overridden, so every formula in every design \
                   renders through Razeri's ANSI defaults instead of this dialect"
                .to_owned(),
            fix: "return `Box::new(Self)`. It is one line, nothing fails without it, and every \
                  formula this engine sees is silently somebody else's."
                .to_owned(),
        });
    }
}

/// **The three routine grammars a dialect states**, and what each answer owes.
///
/// A grammar Razeri carries needs nothing further. A refusal owes words a
/// reader can act on. `Custom` owes the render method it promised — see
/// [`check_custom_grammar`].
fn check_routine_forms(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    match SqlRenderer::procedure_form(dialect) {
        // Razeri's own agnostic renderer answers `Agnostic` on purpose: the
        // annotated form IS its output. Every other dialect that answers it has
        // simply not said.
        ProcedureForm::Agnostic if SqlRenderer::is_agnostic_dialect(dialect) => {}
        ProcedureForm::Agnostic => findings.push(Finding {
            severity: Severity::Unanswered,
            what: "this dialect states no stored-procedure grammar, so Razeri emits its own \
                   annotated form — which no engine parses"
                .to_owned(),
            fix: "override `SqlRenderer::procedure_form` with the grammar this engine speaks, \
                  or with `ProcedureForm::Unsupported(\"…\")` and a sentence saying what it \
                  offers instead."
                .to_owned(),
        }),
        ProcedureForm::Unsupported(orientation) => {
            check_refusal_copy("the stored-procedure refusal", orientation, findings);
            if DialectCapabilities::supports_stored_procedures(dialect) {
                findings.push(Finding {
                    severity: Severity::Blocking,
                    what: "this dialect says its engine has stored procedures, and its \
                           procedure form refuses to write one"
                        .to_owned(),
                    fix: "decide which is true. If the engine has none, leave \
                          `DialectCapabilities::supports_stored_procedures` at `false`; if it \
                          has them, answer `procedure_form` with the grammar it speaks."
                        .to_owned(),
                });
            }
        }
        ProcedureForm::Custom(grammar) => check_custom_grammar(
            CustomGrammar {
                kind: RoutineKind::Procedure,
                grammar,
                form_method: "procedure_form",
                render_method: "render_procedure",
                noun: "procedures",
            },
            |spec| SqlRenderer::render_procedure(dialect, spec),
            findings,
        ),
        _ => {}
    }
    check_procedures_are_used(dialect, findings);

    match SqlRenderer::scalar_function_form(dialect) {
        ScalarFunctionForm::Unsupported(orientation) => {
            check_refusal_copy("the scalar-function refusal", orientation, findings);
        }
        ScalarFunctionForm::Custom(grammar) => check_custom_grammar(
            CustomGrammar {
                kind: RoutineKind::ScalarFunction,
                grammar,
                form_method: "scalar_function_form",
                render_method: "render_scalar_function",
                noun: "scalar functions",
            },
            |spec| SqlRenderer::render_scalar_function(dialect, spec),
            findings,
        ),
        _ => {}
    }

    match SqlRenderer::table_function_form(dialect) {
        TableFunctionForm::Unsupported(orientation) => {
            check_refusal_copy("the table-function refusal", orientation, findings);
        }
        TableFunctionForm::Custom(grammar) => check_custom_grammar(
            CustomGrammar {
                kind: RoutineKind::TableFunction,
                grammar,
                form_method: "table_function_form",
                render_method: "render_table_function",
                noun: "table functions",
            },
            |spec| SqlRenderer::render_table_function(dialect, spec),
            findings,
        ),
        _ => {}
    }

    check_parameter_type_spelling(dialect, findings);
}

/// **A dialect that spells its own procedures has to say its engine has
/// them**, or Razeri never writes one.
///
/// Two answers carry the fact. `procedure_form` says how a procedure is
/// written; `DialectCapabilities::supports_stored_procedures` says whether a
/// load is one, and its default is `false`. A dialect that answers `Custom`
/// and leaves the capability alone renders a procedure nothing asks for:
/// every load is written as plain statements, and the bundle says the engine
/// has no stored procedures. A grammar Razeri carries is left out on purpose,
/// because Razeri keeps that pair for its own engines: `ClickHouse`'s form
/// writes executable functions, which no load runs as a procedure.
fn check_procedures_are_used(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    let Some(grammar) = SqlRenderer::procedure_form(dialect).custom_grammar() else {
        return;
    };
    if DialectCapabilities::supports_stored_procedures(dialect) {
        return;
    }
    findings.push(Finding {
        severity: Severity::Blocking,
        what: format!(
            "this dialect writes procedures in {grammar:?} and says its engine has none, so no \
             load is ever written as one"
        ),
        fix: "answer `DialectCapabilities::supports_stored_procedures` with `true`. Razeri asks \
              it before it writes a load as a procedure, and its default is `false`."
            .to_owned(),
    });
}

/// **A parameter's type is not always spelled the way a column's is**, and this
/// asks whether the dialect has looked.
///
/// Oracle is the measured case: a column there takes `NUMBER(19, 0)` and
/// `VARCHAR2(100)`, and the same types in a parameter list are a compile error
/// — the bare `NUMBER` and `VARCHAR2` are what a parameter clause takes, with
/// the precision belonging on the local declaration instead. A dialect that has
/// not said otherwise gets the column spelling in its parameter lists, which is
/// right for most engines and a routine that will not compile on one like that.
///
/// It is a note and not a failure, because for most engines the two spellings
/// genuinely are the same and there is nothing to say. And it is asked only of a
/// dialect spelling a grammar of its own, because that is the population where
/// nobody has measured the answer yet.
fn check_parameter_type_spelling(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    let spells_its_own = SqlRenderer::procedure_form(dialect).custom_grammar().is_some()
        || SqlRenderer::scalar_function_form(dialect).custom_grammar().is_some()
        || SqlRenderer::table_function_form(dialect).custom_grammar().is_some();
    if !spells_its_own {
        return;
    }
    // A type that carries a length is the only kind the two spellings can
    // disagree about, so it is the only kind worth asking with.
    let qualified = DataType::varchar(100);
    let column = SqlRenderer::render_data_type(dialect, &qualified);
    let parameter = SqlRenderer::render_parameter_data_type(dialect, &qualified);
    if column == parameter && column.contains('(') {
        findings.push(Finding {
            severity: Severity::Note,
            what: format!(
                "a parameter of this routine's would be declared `{parameter}`, the same as a \
                column of that type"
            ),
            fix: "that is right for most engines, so this is only worth a look. If yours refuses \
                 a length or a precision in a parameter list — Oracle does, and answers \
                 PLS-00103 at the opening parenthesis — override \
                 `SqlRenderer::render_parameter_data_type` with the bare spelling. Local \
                 declarations keep the length either way."
                .to_owned(),
        });
    }
}

/// One routine kind whose grammar a dialect said it spells itself.
#[derive(Clone, Copy)]
struct CustomGrammar {
    /// Which routine kind the claim is about.
    kind: RoutineKind,
    /// The grammar's own name, as the dialect wrote it.
    grammar: &'static str,
    /// The method that carried the claim.
    form_method: &'static str,
    /// The method that owes the rendering.
    render_method: &'static str,
    /// What to call the routines in a sentence.
    noun: &'static str,
}

/// **A dialect that says it spells its own grammar has to spell it.**
///
/// `Custom` moves the whole rendering to the dialect: Razeri substitutes no
/// near-neighbour grammar and refuses nothing, because both of those say
/// something false — one about the engine's syntax, the other about whether the
/// engine has the routine at all. So the claim is only worth anything with the
/// method behind it, and a dialect that made the claim and left the method
/// alone ships a build that stops on the routine.
///
/// The check is the question itself: hand the render method the shape it will
/// receive and see whether anything comes back.
///
/// Parameters:
/// - `claim`: which kind, which grammar, and the two method names to name;
/// - `render`: the dialect's render method for that kind;
/// - `findings`: where the report is collected.
fn check_custom_grammar(
    claim: CustomGrammar,
    render: impl Fn(&RoutineSpec) -> Option<String>,
    findings: &mut Vec<Finding>,
) {
    let CustomGrammar { kind, grammar, form_method, render_method, noun } = claim;
    // The grammar's name is a name, not a refusal: it is read in a report and
    // in a comment, so it has to say something and does not have to say much.
    if grammar.trim().is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!("`{form_method}` says Custom, and names no grammar"),
            fix: "carry your grammar's own name — `Custom(\"PL/SQL\")`. It is what a reader \
                  of a report, and of the comment Razeri writes where a routine could not be \
                  rendered, is told this engine speaks."
                .to_owned(),
        });
    }

    let mut probe = RoutineSpec::new(kind, "razeri_conformance_probe");
    probe.or_replace = true;
    if render(&probe).is_none_or(|rendered| rendered.trim().is_empty()) {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!(
                "`{form_method}` says Custom({grammar:?}), so this dialect must render {noun} \
                 itself — and `{render_method}` returns nothing"
            ),
            fix: format!(
                "override `SqlRenderer::{render_method}`. It receives the routine Razeri \
                 composed — name, parameters with their types already in your spelling, body, \
                 comment — and returns the whole CREATE statement your engine parses. Razeri \
                 emits what you return and writes nothing of its own: a near-neighbour grammar \
                 fails on deploy, and a refusal would say this engine has no {noun} when it \
                 has them in a grammar of its own."
            ),
        });
    }
}

/// A refusal that does not say what to do instead is a wall.
fn check_refusal_copy(subject: &str, copy: &str, findings: &mut Vec<Finding>) {
    if copy.trim().is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!("{subject} has no words in it"),
            fix: "say what this engine offers instead. A reader who hits a refusal needs the \
                  next thing to try, and it has to be about their engine."
                .to_owned(),
        });
        return;
    }
    if copy.split_whitespace().count() < 5 {
        findings.push(Finding {
            severity: Severity::Note,
            what: format!("{subject} is very short"),
            fix: "a refusal earns its place by naming the alternative — what this engine has \
                  instead of the thing it cannot do."
                .to_owned(),
        });
    }
}

/// Fifteen facts are declared twice and must agree.
fn check_paired_facts(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    let caps: &dyn DialectCapabilities = dialect;
    let ren: &dyn SqlRenderer = dialect;
    let ddl: &dyn DdlRenderer = dialect;
    let mut disagreements: Vec<&'static str> = Vec::new();

    macro_rules! pair {
        ($m:ident, $renderer_value:expr) => {
            if caps.$m() != $renderer_value {
                disagreements.push(stringify!($m));
            }
        };
    }
    pair!(supports_on_conflict, ren.supports_on_conflict());
    pair!(supports_insert_overwrite, ren.supports_insert_overwrite());
    pair!(supports_update_from, ren.supports_update_from());
    pair!(supports_named_windows, ren.supports_named_windows());
    pair!(supports_compound_assignment, ren.supports_compound_assignment());
    pair!(supports_window_frame_exclude, ren.supports_window_frame_exclude());
    pair!(supports_merge_not_matched_by_source, ren.supports_merge_not_matched_by_source());
    pair!(merge_not_matched_keyword, ren.merge_not_matched_keyword());
    pair!(uses_alter_table_update, ren.uses_alter_table_update());
    pair!(supports_multi_table_insert, ren.supports_multi_table_insert());
    pair!(supports_cascade_drop, ddl.supports_cascade_drop());
    pair!(supports_sync_drop, ddl.supports_sync_drop());
    pair!(supports_force_drop, ddl.supports_force_drop());

    // **The fourteenth, whose two sides are different types.** The capability
    // is a boolean; the renderer answer is a form that either renders or
    // carries a refusal. Both are read — the build door reads the form, and
    // registry-walking rosters read the capability — so a dialect claiming a
    // table function on one side and refusing on the other has an engine that
    // both does and does not have one. `SQLite` was exactly that on the day
    // this check was written: `false` since the capability existed, and a
    // renderer still inheriting PostgreSQL's `RETURNS TABLE`.
    if caps.supports_table_functions() != ren.table_function_form().refusal().is_none() {
        disagreements.push("supports_table_functions");
    }
    // **The fifteenth, the same shape for a scalar function.** The build door
    // reads the capability; the renderer reads the form.
    if caps.supports_stored_functions() != ren.scalar_function_form().refusal().is_none() {
        disagreements.push("supports_stored_functions");
    }

    if !disagreements.is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!(
                "this dialect declares {} {} two ways and the two disagree: {}",
                disagreements.len(),
                if disagreements.len() == 1 { "fact" } else { "facts" },
                disagreements.join(", ")
            ),
            fix: "each of these is declared once on the capability trait and once in the \
                  renderer hierarchy, and Razeri reads both. Decide which is true of your engine \
                  and move the other one to match."
                .to_owned(),
        });
    }
}

/// **A dialect that folds each schema into an object's name has to fold it
/// everywhere.**
///
/// Razeri writes most names itself, asking `fold_schema_into_name`, and some
/// through the dialect's own `qualify_name`. The two have to name the same
/// object, or a table is created under one name and read under another. And a
/// fold is asked for only where the engine has no schemas: on one that has
/// them, the schema is written apart from the name, and a fold says otherwise.
fn check_schema_fold(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    let (schema, name) = ("razeri_probe_raw", "razeri_probe_table");
    let Some(folded) = SqlRenderer::fold_schema_into_name(dialect, schema, name) else {
        return;
    };
    if DialectCapabilities::supports_schemas(dialect) {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect folds a schema into an object's name on an engine it says has \
                   schemas"
                .to_owned(),
            fix: "answer `fold_schema_into_name` with `None`. A fold is for an engine with no \
                  schemas, and only there is it asked; or answer \
                  `DialectCapabilities::supports_schemas` with `false` if your engine has none."
                .to_owned(),
        });
        return;
    }
    let expected = SqlRenderer::quote_identifier(dialect, &folded);
    let qualified = SqlRenderer::qualify_name(dialect, Some(schema), name);
    if qualified != expected {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!(
                "`fold_schema_into_name` names an object `{expected}` and `qualify_name` names \
                 the same object `{qualified}`"
            ),
            fix: "make `qualify_name` answer the folded name, quoted, when it is given a \
                  schema. Razeri names some objects through it and the rest through the fold, \
                  so a difference creates a table under one name and reads it under another."
                .to_owned(),
        });
    }
}

/// **A described deploy client has to be told which file to run.**
///
/// The generated script substitutes the file for `{file}` in the arguments
/// the dialect names, and reads nothing else, so an argument list without it
/// runs the client with no SQL at all — and a client given no file usually
/// waits for one on its input.
fn check_deploy_client(info: &crate::dialect::DialectInfo, findings: &mut Vec<Finding>) {
    let Some(crate::dialect::DeployScript::Client(client)) = info.deploy_script else {
        return;
    };
    if client.command.trim().is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "this dialect describes a deploy client with no command".to_owned(),
            fix: "name the client's command as an operator has it on `PATH`.".to_owned(),
        });
    }
    let unnamed: Vec<&str> = [("run_file", Some(client.run_file)), ("read_rows", client.read_rows)]
        .into_iter()
        .filter_map(|(field, args)| {
            args.filter(|args| !args.iter().any(|arg| arg.contains("{file}"))).map(|_| field)
        })
        .collect();
    if !unnamed.is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!(
                "the deploy client's {} never names the file to run",
                unnamed.join(" and ")
            ),
            fix: "put `{file}` where your client takes the file of SQL to run — on its own, or \
                  inside an argument such as `--file={file}`. The script writes the file there \
                  and nowhere else."
                .to_owned(),
        });
    }
    if let Some(terminator) = client.block_terminator
        && [terminator.open, terminator.end, terminator.close]
            .iter()
            .any(|part| part.trim().is_empty())
    {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: "the deploy client's block terminator leaves a part blank".to_owned(),
            fix: "name the line before a routine, what ends it, and the line after it — \
                  `SET TERM ^ ;`, `^` and `SET TERM ; ^` for Firebird's isql. A blank end leaves \
                  the routine open, and the next statement is read into it."
                .to_owned(),
        });
    }
}

/// Every type a design can declare, which is the set the required methods owe
/// an answer for.
///
/// The last four are the ones whose spelling actually varies between engines:
/// an array, a map, a struct, and an array of structs. A dialect that answers
/// only for scalars is a dialect that has not been graded on the types a
/// nested source table is made of.
fn declarable_types() -> Vec<DataType> {
    vec![
        DataType::tinyint(),
        DataType::smallint(),
        DataType::int(),
        DataType::bigint(),
        DataType::double(),
        DataType::boolean(),
        DataType::text(),
        DataType::varchar(200),
        DataType::uuid(),
        DataType::json(),
        DataType::date(),
        DataType::timestamp(),
        DataType::timestamp_tz(),
        DataType::decimal(18, 2),
        DataType::Array(ArrayDataType::new(DataType::varchar(100))),
        DataType::Map(MapDataType::new(DataType::varchar(50), DataType::bigint())),
        DataType::Struct(StructDataType::new(vec![
            StructField::new("street", DataType::varchar(200)),
            StructField::new("zip", DataType::varchar(10)),
        ])),
        DataType::Array(ArrayDataType::new(DataType::Struct(StructDataType::new(vec![
            StructField::new("key", DataType::varchar(100)),
            StructField::new("value", DataType::varchar(1000)),
        ])))),
    ]
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::dialect::DialectInfo;
    use razeri_plugin_contract::{
        Concat, DmlRenderer, ExpressionRenderer, FormulaRenderer, MergeRestrictions,
        ProcedureRenderer, ReplicationRenderer, ScalarFunctionForm, SidSpelling, StorageRenderer,
        TaskRenderer, TextCast,
    };
    use std::marker::PhantomData;

    // ── The broken fixture ────────────────────────────────────────────
    //
    // A plugin that compiles, registers nothing it should, and answers every
    // question with somebody else's default. This is what the kit exists to
    // catch, and it is deliberately the plugin an author gets by implementing
    // only the four required methods and stopping.

    struct BrokenPlugin;

    impl SqlRenderer for BrokenPlugin {
        fn render_data_type(&self, _dt: &DataType) -> String {
            String::new()
        }
        fn quote_identifier(&self, name: &str) -> String {
            name.to_owned()
        }
        fn render_cast_type(&self, _dt: &DataType) -> String {
            String::new()
        }
        fn qualify_name(&self, _schema: Option<&str>, name: &str) -> String {
            name.to_owned()
        }
        fn render_current_timestamp(&self) -> String {
            String::new()
        }
        fn dialect_name(&self) -> &'static str {
            "brokendb"
        }
        // No `sid_spelling`, no `procedure_form`, no `to_formula_renderer` —
        // and one fact declared twice, two ways.
        fn supports_on_conflict(&self) -> bool {
            true
        }
    }
    impl ExpressionRenderer for BrokenPlugin {}
    impl DdlRenderer for BrokenPlugin {}
    impl DmlRenderer for BrokenPlugin {}
    impl ProcedureRenderer for BrokenPlugin {}
    impl FormulaRenderer for BrokenPlugin {}
    impl TaskRenderer for BrokenPlugin {}
    impl ReplicationRenderer for BrokenPlugin {}
    impl StorageRenderer for BrokenPlugin {}
    impl DialectCapabilities for BrokenPlugin {
        fn supports_on_conflict(&self) -> bool {
            false
        }
    }
    impl PlatformDialect for BrokenPlugin {
        fn info(&self) -> DialectInfo {
            DialectInfo {
                name: "brokendb",
                display_name: "Broken DB",
                aliases: &[],
                dialect_family: DialectFamily::Custom(""),
                family: "Broken",
                is_cloud_native: false,
                is_olap: false,
                is_postgres_compatible: false,
                versions: &[],
                deploy_script: None,
            }
        }
    }

    // ── The answered fixture, under two names ─────────────────────────
    //
    // The same plugin with every question answered, carrying the name it
    // registers under as a type parameter. Neither name is one Razeri knows a
    // spelling for; the difference is that one of them is in the registry and
    // the other is in nothing at all. Those are two different facts about a
    // dialect, and they read as two different reports.

    trait FixtureName: Send + Sync + 'static {
        /// The registry name this fixture answers to.
        const NAME: &'static str;
        /// How this fixture's engine joins the parts of a composite key. Most
        /// of them take the inherited variadic `CONCAT`; the one that does not
        /// is the point of it being a knob.
        const CONCAT: Concat = Concat::Function("CONCAT");
        /// This fixture's engine's exponentiation operator; `None` where it
        /// has none.
        const POWER: Option<&'static str> = Some("^");
        /// This fixture's engine's left shift; `None` where it has none.
        const SHIFT_LEFT: Option<&'static str> = Some("<<");
        /// Whether this fixture's column drop forgets the column it was handed.
        const DROP_LOSES_ITS_COLUMN: bool = false;
        /// Whether this fixture compares something other than the value.
        const COMPARES_SOMETHING_ELSE: bool = false;
        /// Whether this fixture says its begin leaves the commit to the client
        /// while it has no transaction spanning statements.
        const BEGIN_WITHOUT_TRANSACTIONS: bool = false;
        /// The client this fixture's deploy script drives, if it names one.
        const DEPLOY: Option<crate::dialect::DeployScript> = None;
    }

    /// Answered, and nothing linked it — what an author sees running the report
    /// from a crate whose registration nobody calls.
    struct Unlinked;
    impl FixtureName for Unlinked {
        const NAME: &'static str = "healthydb";
    }

    /// Answered and registered, under a name Razeri ships no spelling for —
    /// the shape of every dialect written outside Razeri's own tree.
    struct Outside;
    impl FixtureName for Outside {
        const NAME: &'static str = "outsidedb";
    }

    /// Answered, and joining composite keys with a `CONCAT` that takes exactly
    /// two arguments — the shape of Db2 and of older Oracle releases. Nothing
    /// about the join is an obligation, so the report on it must read exactly
    /// like the report on a variadic engine.
    struct BinaryConcat;
    impl FixtureName for BinaryConcat {
        const NAME: &'static str = "binaryconcatdb";
        const CONCAT: Concat = Concat::FunctionBinary("CONCAT");
    }

    /// Answered, and joining composite keys with the `||` operator — the
    /// shape of an engine with no `CONCAT` function at all. Like the
    /// two-argument join, it is a decision and owes nothing.
    struct OperatorConcat;
    impl FixtureName for OperatorConcat {
        const NAME: &'static str = "operatorconcatdb";
        const CONCAT: Concat = Concat::Operator("||");
    }

    /// Answered, with a join that names nothing.
    struct NamelessConcat;
    impl FixtureName for NamelessConcat {
        const NAME: &'static str = "namelessconcatdb";
        const CONCAT: Concat = Concat::Operator(" ");
    }

    /// Answered, with an engine that raises to a power only through a
    /// function: no operator, said out loud.
    struct NoPowerOperator;
    impl FixtureName for NoPowerOperator {
        const NAME: &'static str = "nopowerdb";
        const POWER: Option<&'static str> = None;
    }

    /// Answered, with an operator spelled as nothing at all.
    struct BlankPowerOperator;
    impl FixtureName for BlankPowerOperator {
        const NAME: &'static str = "blankpowerdb";
        const POWER: Option<&'static str> = Some(" ");
    }

    /// Answered, with a comparison that forgets the value it stands for.
    struct ComparesSomethingElse;
    impl FixtureName for ComparesSomethingElse {
        const NAME: &'static str = "comparesotherdb";
        const COMPARES_SOMETHING_ELSE: bool = true;
    }

    /// Answered, with a column drop that forgets its column.
    struct DropLosesItsColumn;
    impl FixtureName for DropLosesItsColumn {
        const NAME: &'static str = "droplosesdb";
        const DROP_LOSES_ITS_COLUMN: bool = true;
    }

    /// Answered, with a begin that leaves the commit to a client on an engine
    /// with no transaction spanning statements.
    struct BeginWithoutTransactions;
    impl FixtureName for BeginWithoutTransactions {
        const NAME: &'static str = "beginwithouttxdb";
        const BEGIN_WITHOUT_TRANSACTIONS: bool = true;
    }

    /// Answered, with an engine that has no left shift — an answer.
    struct NoShiftOperator;
    impl FixtureName for NoShiftOperator {
        const NAME: &'static str = "noshiftdb";
        const SHIFT_LEFT: Option<&'static str> = None;
    }

    /// Answered, with a left shift that has no text in it.
    struct BlankShiftOperator;
    impl FixtureName for BlankShiftOperator {
        const NAME: &'static str = "blankshiftdb";
        const SHIFT_LEFT: Option<&'static str> = Some("");
    }

    /// Answered, with a deploy client described well.
    struct DescribedClient;
    impl FixtureName for DescribedClient {
        const NAME: &'static str = "describedclientdb";
        const DEPLOY: Option<crate::dialect::DeployScript> =
            Some(crate::dialect::DeployScript::Client(crate::dialect::DeployClient {
                command: "sqlite3",
                run_file: &["-bail", "{target}", ".read {file}"],
                read_rows: Some(&["-csv", "-noheader", "{target}", ".read {file}"]),
                block_terminator: None,
            }));
    }

    /// Answered, with a deploy client that is never told which file to run.
    struct FilelessClient;
    impl FixtureName for FilelessClient {
        const NAME: &'static str = "filelessclientdb";
        const DEPLOY: Option<crate::dialect::DeployScript> =
            Some(crate::dialect::DeployScript::Client(crate::dialect::DeployClient {
                command: "sqlite3",
                run_file: &["-bail", "{target}"],
                read_rows: None,
                block_terminator: None,
            }));
    }

    /// Answered, with a deploy client whose routine end is blank.
    struct BlankBlockEnd;
    impl FixtureName for BlankBlockEnd {
        const NAME: &'static str = "blankblockenddb";
        const DEPLOY: Option<crate::dialect::DeployScript> =
            Some(crate::dialect::DeployScript::Client(crate::dialect::DeployClient {
                command: "isql",
                run_file: &["-bail", "-input", "{file}", "{target}"],
                read_rows: None,
                block_terminator: Some(crate::dialect::BlockTerminator {
                    open: "SET TERM ^ ;",
                    end: " ",
                    close: "SET TERM ; ^",
                }),
            }));
    }

    struct Answered<N: FixtureName>(PhantomData<N>);

    impl<N: FixtureName> Answered<N> {
        const fn new() -> Self {
            Self(PhantomData)
        }
    }

    type HealthyPlugin = Answered<Unlinked>;
    type OutsidePlugin = Answered<Outside>;

    inventory::submit! {
        crate::builder::DialectRegistration::new(
            Outside::NAME,
            || std::sync::Arc::new(OutsidePlugin::new()),
        )
    }

    impl<N: FixtureName> SqlRenderer for Answered<N> {
        fn render_data_type(&self, dt: &DataType) -> String {
            razeri_plugin_contract::agnostic::render_data_type_ansi(dt)
        }
        fn quote_identifier(&self, name: &str) -> String {
            format!("\"{name}\"")
        }
        fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
            schema.map_or_else(
                || self.quote_identifier(name),
                |s| format!("{}.{}", self.quote_identifier(s), self.quote_identifier(name)),
            )
        }
        fn render_current_timestamp(&self) -> String {
            "NOW()".to_owned()
        }
        fn dialect_name(&self) -> &'static str {
            N::NAME
        }
        fn sid_spelling(&self) -> SidSpelling {
            SidSpelling {
                platform: N::NAME,
                hash: ("answered_xxhash64(", ")"),
                cast_type: TextCast::Direct("STRING"),
                concat: N::CONCAT,
                through_helper: false,
                ..razeri_plugin_contract::ANSI_SID_SPELLING
            }
        }
        fn procedure_form(&self) -> ProcedureForm {
            ProcedureForm::Unsupported(
                "This engine has no stored procedures; write the load as a scheduled query \
                 instead.",
            )
        }
        fn scalar_function_form(&self) -> ScalarFunctionForm {
            ScalarFunctionForm::PostgreSql
        }
        fn to_formula_renderer(&self) -> Box<dyn FormulaRenderer> {
            Box::new(Self::new())
        }
    }
    impl<N: FixtureName> ExpressionRenderer for Answered<N> {
        fn render_comparable(
            &self,
            value: &str,
            _data_type: &DataType,
            _held: HeldAs,
        ) -> Option<String> {
            Some(if N::COMPARES_SOMETHING_ELSE { "1".to_owned() } else { value.to_owned() })
        }
    }
    impl<N: FixtureName> DdlRenderer for Answered<N> {
        fn render_drop_column(
            &self,
            table: WrittenObject<'_>,
            column: &str,
            only_where_it_stands: bool,
        ) -> Option<razeri_plugin_contract::Written> {
            if N::DROP_LOSES_ITS_COLUMN {
                return Some(razeri_plugin_contract::Written::Statement(format!(
                    "ALTER TABLE {} DROP COLUMN c",
                    table.relation
                )));
            }
            let guard = if only_where_it_stands { " IF EXISTS" } else { "" };
            Some(razeri_plugin_contract::Written::Statement(format!(
                "ALTER TABLE {} DROP COLUMN{guard} {column}",
                table.relation
            )))
        }
    }
    impl<N: FixtureName> DmlRenderer for Answered<N> {}
    impl<N: FixtureName> ProcedureRenderer for Answered<N> {}
    impl<N: FixtureName> FormulaRenderer for Answered<N> {
        fn render_power_operator(&self) -> Option<String> {
            N::POWER.map(str::to_owned)
        }
        fn render_bitwise_operator(&self, operator: BitwiseOperator) -> Option<String> {
            match operator {
                BitwiseOperator::ShiftLeft => N::SHIFT_LEFT.map(str::to_owned),
                other => Some(other.standard_spelling().to_owned()),
            }
        }
    }
    impl<N: FixtureName> TaskRenderer for Answered<N> {}
    impl<N: FixtureName> ReplicationRenderer for Answered<N> {}
    impl<N: FixtureName> StorageRenderer for Answered<N> {}
    impl<N: FixtureName> DialectCapabilities for Answered<N> {
        fn begin_leaves_the_commit_to_the_client(&self) -> bool {
            N::BEGIN_WITHOUT_TRANSACTIONS
        }
        fn supports_multi_statement_transactions(&self) -> bool {
            !N::BEGIN_WITHOUT_TRANSACTIONS
        }
        fn merge_shape(&self) -> MergeShape {
            MergeShape::Merge(MergeRestrictions::none())
        }
    }
    impl<N: FixtureName> PlatformDialect for Answered<N> {
        fn info(&self) -> DialectInfo {
            DialectInfo {
                name: N::NAME,
                display_name: "An Answered Engine",
                // It answers to its own name and nothing else, which is the
                // shape of a plugin that declares no extra spellings.
                aliases: &[],
                dialect_family: DialectFamily::Custom(N::NAME),
                family: "Answered",
                is_cloud_native: false,
                is_olap: false,
                is_postgres_compatible: false,
                versions: &[],
                deploy_script: N::DEPLOY,
            }
        }
    }

    // ── The twin: a dialect that claims a name another platform answers to ──
    //
    // Answered in every other respect, so the only thing its report can be
    // about is the spelling it took.

    struct Twin;

    impl SqlRenderer for Twin {
        fn render_data_type(&self, dt: &DataType) -> String {
            razeri_plugin_contract::agnostic::render_data_type_ansi(dt)
        }
        fn quote_identifier(&self, name: &str) -> String {
            format!("\"{name}\"")
        }
        fn qualify_name(&self, _schema: Option<&str>, name: &str) -> String {
            self.quote_identifier(name)
        }
        fn render_current_timestamp(&self) -> String {
            "NOW()".to_owned()
        }
    }
    impl ExpressionRenderer for Twin {}
    impl DdlRenderer for Twin {}
    impl DmlRenderer for Twin {}
    impl ProcedureRenderer for Twin {}
    impl FormulaRenderer for Twin {}
    impl TaskRenderer for Twin {}
    impl ReplicationRenderer for Twin {}
    impl StorageRenderer for Twin {}
    impl DialectCapabilities for Twin {}
    impl PlatformDialect for Twin {
        fn info(&self) -> DialectInfo {
            DialectInfo {
                name: "twindb",
                display_name: "Twin DB",
                // The name the answered fixture already answers to.
                aliases: &["healthydb"],
                dialect_family: DialectFamily::Custom("twindb"),
                family: "Twin",
                is_cloud_native: false,
                is_olap: false,
                is_postgres_compatible: false,
                versions: &[],
                deploy_script: None,
            }
        }
    }

    /// Every finding whose `what` line mentions one of these subjects.
    fn mentions(report: &Report, needle: &str) -> bool {
        report.findings.iter().any(|f| f.what.contains(needle) || f.fix.contains(needle))
    }

    /// **The kit's own red.** A plugin that implements the four required
    /// methods and stops compiles and registers, and every one of these
    /// findings is a defect a shipped dialect actually had.
    #[test]
    fn the_kit_catches_a_deliberately_broken_plugin() {
        let report = check(&BrokenPlugin);
        assert!(!report.is_clean(), "the broken plugin reported clean:\n{report}");

        assert!(mentions(&report, "does not resolve"), "unregistered:\n{report}");
        assert!(mentions(&report, "custom one with no name"), "unnamed family:\n{report}");
        assert!(mentions(&report, "render_data_type"), "empty column types:\n{report}");
        assert!(mentions(&report, "render_cast_type"), "empty cast types:\n{report}");
        assert!(mentions(&report, "identifier unchanged"), "no quoting:\n{report}");
        assert!(mentions(&report, "render_current_timestamp"), "no now():\n{report}");
        assert!(mentions(&report, "surrogate-key spelling"), "unspelled key:\n{report}");
        assert!(mentions(&report, "stored-procedure grammar"), "no procedure form:\n{report}");
        assert!(mentions(&report, "upsert shape"), "no merge shape:\n{report}");
        assert!(mentions(&report, "to_formula_renderer"), "formula renderer:\n{report}");
        assert!(mentions(&report, "supports_on_conflict"), "paired fact:\n{report}");

        assert!(
            report.count(Severity::Blocking) >= 8,
            "the broken plugin owes more than {} blocking fixes:\n{report}",
            report.count(Severity::Blocking)
        );
    }

    /// **The kit does not cry wolf.** The same plugin with its questions
    /// answered reports only what is actually true of it: it is not
    /// registered in this test binary, and nothing else.
    #[test]
    fn the_kit_reports_only_registration_on_a_plugin_that_answered_everything() {
        let report = check(&HealthyPlugin::new());
        let unregistered: Vec<&Finding> =
            report.findings.iter().filter(|f| f.what.contains("does not resolve")).collect();
        assert_eq!(unregistered.len(), 1, "the registration finding is the honest one here");
        assert_eq!(
            report.findings.len(),
            1,
            "a plugin that answered every question owes nothing else:\n{report}"
        );
    }

    /// **A join written for a two-argument `CONCAT` is an answer, not a
    /// shortfall.** The report on a dialect that states it reads exactly like
    /// the report on a variadic one, and the key it renders nests pairwise
    /// instead of widening a call the engine does not have.
    #[test]
    fn the_kit_takes_a_two_argument_concat_as_an_answer_like_any_other() {
        let binary = Answered::<BinaryConcat>::new();
        let report = check(&binary);
        assert_eq!(
            report.findings.len(),
            check(&HealthyPlugin::new()).findings.len(),
            "the join form is a decision, so stating this one owes nothing extra:\n{report}"
        );
        assert!(
            !report.to_string().contains("joins"),
            "nothing about the join is outstanding:\n{report}"
        );

        let key = razeri_plugin_contract::sid::sid_expression(
            SqlRenderer::sid_spelling(&binary),
            razeri_plugin_contract::sid::Backslash::of(&binary),
            None,
            &["a".to_owned(), "b".to_owned(), "c".to_owned()],
        );
        assert!(
            key.contains("CONCAT(CONCAT("),
            "a two-argument engine gets one call per pair:\n{key}"
        );
    }

    /// **A join written with an operator is an answer too**, and the key it
    /// renders is one expression with the operator between each part and the
    /// separator.
    #[test]
    fn the_kit_takes_an_operator_join_as_an_answer_like_any_other() {
        let operator = Answered::<OperatorConcat>::new();
        let report = check(&operator);
        assert_eq!(
            report.findings.len(),
            check(&HealthyPlugin::new()).findings.len(),
            "the join form is a decision, so stating this one owes nothing extra:\n{report}"
        );
        let key = razeri_plugin_contract::sid::sid_expression(
            SqlRenderer::sid_spelling(&operator),
            razeri_plugin_contract::sid::Backslash::of(&operator),
            None,
            &["a".to_owned(), "b".to_owned()],
        );
        assert!(key.contains(" || '|' || "), "the operator joins each part:\n{key}");
        assert!(!key.contains("CONCAT"), "an operator engine calls no function:\n{key}");
    }

    /// **A join that names nothing is refused**, because the key it writes is
    /// one the engine cannot parse.
    #[test]
    fn a_join_that_names_nothing_must_be_fixed() {
        let report = check(&Answered::<NamelessConcat>::new());
        assert!(
            report.findings.iter().any(|finding| finding.severity == Severity::Blocking
                && finding.what.contains("joins the parts of a composite key")),
            "{report}"
        );
        assert!(
            !check(&Answered::<OperatorConcat>::new())
                .findings
                .iter()
                .any(|finding| finding.what.contains("joins the parts of a composite key")),
            "a named operator is not the finding"
        );
    }

    /// **An operator the engine lacks is answered `None`, and that is an
    /// answer**; an operator answered with no text is the finding, because it
    /// writes two operands side by side.
    #[test]
    fn an_operator_is_answered_none_where_the_engine_lacks_it_and_never_blank() {
        let none = check(&Answered::<NoPowerOperator>::new());
        assert_eq!(
            none.findings.len(),
            check(&HealthyPlugin::new()).findings.len(),
            "saying the engine has no such operator owes nothing:\n{none}"
        );
        let blank = check(&Answered::<BlankPowerOperator>::new());
        assert!(
            blank.findings.iter().any(|finding| finding.severity == Severity::Blocking
                && finding.what.contains("render_power_operator")),
            "{blank}"
        );
    }

    /// **A bitwise operator the engine lacks is answered `None`, and that is
    /// an answer** (`fr-1018-121`); one answered with no text is the finding.
    #[test]
    fn a_bitwise_operator_is_answered_none_where_the_engine_lacks_it_and_never_blank() {
        let none = check(&Answered::<NoShiftOperator>::new());
        assert_eq!(
            none.findings.len(),
            check(&HealthyPlugin::new()).findings.len(),
            "saying the engine has no left shift owes nothing:\n{none}"
        );
        let blank = check(&Answered::<BlankShiftOperator>::new());
        assert!(
            blank.findings.iter().any(|finding| finding.severity == Severity::Blocking
                && finding.what.contains("render_bitwise_operator(ShiftLeft)")),
            "{blank}"
        );
    }

    /// **A column drop that loses the column it was handed is the finding**
    /// (`fr-1018-84`); the healthy fixture's drop carries it.
    #[test]
    fn a_drop_that_loses_its_column_must_be_fixed() {
        let healthy = check(&HealthyPlugin::new());
        assert!(!healthy.to_string().contains("render_drop_column"), "{healthy}");
        let lost = check(&Answered::<DropLosesItsColumn>::new());
        assert!(
            lost.findings.iter().any(|finding| finding.severity == Severity::Blocking
                && finding.what.contains("render_drop_column")),
            "{lost}"
        );
    }

    /// **A comparison that loses the value it stands for is the finding**
    /// (`fr-1019-71`).
    #[test]
    fn a_comparison_that_loses_its_value_must_be_fixed() {
        assert!(!check(&HealthyPlugin::new()).to_string().contains("render_comparable"));
        let report = check(&Answered::<ComparesSomethingElse>::new());
        assert!(
            report.findings.iter().any(|finding| finding.severity == Severity::Blocking
                && finding.what.contains("render_comparable")),
            "{report}"
        );
    }

    /// **A begin that leaves the commit to a client, on an engine with no
    /// transaction spanning statements, is two answers that disagree**
    /// (`fr-1019-62`).
    #[test]
    fn a_begin_that_leaves_the_commit_needs_a_transaction() {
        let report = check(&Answered::<BeginWithoutTransactions>::new());
        assert!(
            report.findings.iter().any(|finding| finding.severity == Severity::Blocking
                && finding.what.contains("leaves the commit to the client")),
            "{report}"
        );
    }

    /// **A block terminator with a blank part is the finding** (`fr-1016-28`).
    #[test]
    fn a_block_terminator_with_a_blank_part_must_be_fixed() {
        let report = check(&Answered::<BlankBlockEnd>::new());
        assert!(
            report.findings.iter().any(|finding| finding.severity == Severity::Blocking
                && finding.what.contains("block terminator")),
            "{report}"
        );
    }

    /// **A described deploy client is an answer; one that is never told which
    /// file to run is the finding.**
    #[test]
    fn a_deploy_client_has_to_be_told_which_file_to_run() {
        let described = check(&Answered::<DescribedClient>::new());
        assert_eq!(
            described.findings.len(),
            check(&HealthyPlugin::new()).findings.len(),
            "a client described in full owes nothing:\n{described}"
        );
        let fileless = check(&Answered::<FilelessClient>::new());
        assert!(
            fileless.findings.iter().any(|finding| finding.severity == Severity::Blocking
                && finding.what.contains("run_file never names the file")),
            "{fileless}"
        );
    }

    /// **A fold has to agree with `qualify_name`, and is for an engine with no
    /// schemas.** `SCHEMAS`: what the engine says about schemas. `AGREES`:
    /// whether `qualify_name` answers the folded name.
    #[test]
    fn a_schema_fold_agrees_with_qualify_name_on_an_engine_with_no_schemas() {
        struct Folder<const SCHEMAS: bool, const AGREES: bool>;
        impl<const SCHEMAS: bool, const AGREES: bool> SqlRenderer for Folder<SCHEMAS, AGREES> {
            fn render_data_type(&self, dt: &DataType) -> String {
                razeri_plugin_contract::agnostic::render_data_type_ansi(dt)
            }
            fn quote_identifier(&self, name: &str) -> String {
                format!("\"{name}\"")
            }
            fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
                match schema {
                    Some(schema) if AGREES => self.quote_identifier(&format!("{schema}_{name}")),
                    Some(schema) => format!("{schema}.{}", self.quote_identifier(name)),
                    None => self.quote_identifier(name),
                }
            }
            fn render_current_timestamp(&self) -> String {
                "CURRENT_TIMESTAMP".to_owned()
            }
            fn fold_schema_into_name(&self, schema: &str, name: &str) -> Option<String> {
                Some(format!("{schema}_{name}"))
            }
        }
        impl<const S: bool, const A: bool> ExpressionRenderer for Folder<S, A> {}
        impl<const S: bool, const A: bool> DdlRenderer for Folder<S, A> {}
        impl<const S: bool, const A: bool> DmlRenderer for Folder<S, A> {}
        impl<const S: bool, const A: bool> ProcedureRenderer for Folder<S, A> {}
        impl<const S: bool, const A: bool> FormulaRenderer for Folder<S, A> {}
        impl<const S: bool, const A: bool> TaskRenderer for Folder<S, A> {}
        impl<const S: bool, const A: bool> ReplicationRenderer for Folder<S, A> {}
        impl<const S: bool, const A: bool> StorageRenderer for Folder<S, A> {}
        impl<const S: bool, const A: bool> DialectCapabilities for Folder<S, A> {
            fn supports_schemas(&self) -> bool {
                S
            }
        }
        impl<const S: bool, const A: bool> PlatformDialect for Folder<S, A> {
            fn info(&self) -> DialectInfo {
                DialectInfo {
                    name: "folderdb",
                    display_name: "Folder DB",
                    aliases: &[],
                    dialect_family: DialectFamily::Custom("folderdb"),
                    family: "Folder",
                    is_cloud_native: false,
                    is_olap: false,
                    is_postgres_compatible: false,
                    versions: &[],
                    deploy_script: None,
                }
            }
        }

        let fold_findings = |dialect: &dyn PlatformDialect| {
            let mut findings = Vec::new();
            check_schema_fold(dialect, &mut findings);
            findings
        };
        assert!(fold_findings(&Folder::<false, true>).is_empty(), "a fold kept everywhere");
        let disagrees = fold_findings(&Folder::<false, false>);
        assert_eq!(disagrees.len(), 1, "{disagrees:?}");
        assert!(disagrees[0].what.contains("`qualify_name` names"), "{disagrees:?}");
        let has_schemas = fold_findings(&Folder::<true, true>);
        assert_eq!(has_schemas.len(), 1, "{has_schemas:?}");
        assert!(has_schemas[0].what.contains("says has schemas"), "{has_schemas:?}");
    }

    /// **A stated `MERGE` restriction is reported; a default one is not.**
    ///
    /// The asymmetry is the point. A dialect that says its planner needs a
    /// literal slice gets Razeri's plan for that answer back, while the engine
    /// it is describing is still in front of the author. A dialect that has
    /// said nothing gets nothing, because the kit cannot tell a measured
    /// *"a parameter is fine here"* from a question never asked, and a finding
    /// made out of that silence would fire on every dialect alike.
    #[test]
    fn a_stated_merge_restriction_is_reported_and_a_default_one_is_not() {
        let mut quiet = Vec::new();
        check_merge_restrictions(MergeRestrictions::none(), &mut quiet);
        assert!(quiet.is_empty(), "a dialect with no stated restriction is asked nothing");

        let mut stated = Vec::new();
        check_merge_restrictions(MergeRestrictions::requiring_a_literal_slice(), &mut stated);
        assert_eq!(stated.len(), 1, "the stated restriction is reported once");
        assert_eq!(stated[0].severity, Severity::Note, "nothing is broken; it is worth a look");
        assert!(
            stated[0].what.contains("literal"),
            "the finding names the restriction:\n{}",
            stated[0].what
        );
        assert!(
            stated[0].fix.contains("dynamic-execution"),
            "and says what Razeri does with the answer:\n{}",
            stated[0].fix
        );

        // The other restriction travels on the same struct and must not drag
        // the new finding along with it.
        let mut two_clauses = Vec::new();
        check_merge_restrictions(MergeRestrictions::requiring_two_when_clauses(), &mut two_clauses);
        assert!(two_clauses.is_empty(), "one restriction is not the other");
    }

    /// **A dialect written outside Razeri reaches a clean report.** It is
    /// reached the way every out-of-tree dialect is reached — it registered, and
    /// the resolver reads the loaded dialects' own declared spellings — so there
    /// is nothing for Razeri to be missing it from and nothing for the report to
    /// ask of its author.
    #[test]
    fn a_dialect_registered_under_its_own_name_reaches_a_clean_report() {
        assert!(
            crate::resolve_dialect_by_name(Outside::NAME).is_ok(),
            "the fixture registers through the registry, which is the whole path being graded"
        );
        // The precondition that used to read "no row in Razeri's table": no
        // other loaded platform declares this name, so the fixture is reached
        // only because it registered and said so itself.
        let registry = crate::dialect::PlatformRegistry::with_auto_discovered();
        assert!(
            registry
                .list()
                .iter()
                .filter(|info| info.name != Outside::NAME)
                .all(|info| !info.answers_to(Outside::NAME)),
            "the fixture's name must be one no other platform claims"
        );

        let report = check(&OutsidePlugin::new());
        assert!(report.findings.is_empty(), "nothing is owed here:\n{report}");
        assert!(report.is_clean(), "and the report says so:\n{report}");
        assert_eq!(report.count(Severity::Blocking), 0, "{report}");
        assert!(
            report.to_string().contains("nothing to fix"),
            "a clean dialect is told it is clean:\n{report}"
        );
    }

    /// **A platform cannot be reachable under an alias but not under its own
    /// name.** A dialect declares its own spellings and its own name leads
    /// them, so a platform reachable as `mynew_db` and never as `mynewdb`
    /// cannot be written down — and this is the assertion that keeps it that
    /// way.
    #[test]
    fn a_platform_is_never_reachable_by_an_alias_but_not_by_its_own_name() {
        let registry = crate::dialect::PlatformRegistry::with_auto_discovered();
        for info in registry.list() {
            assert_eq!(
                info.spellings().first().copied(),
                Some(info.name),
                "a platform's own name leads its spellings"
            );
            assert!(info.answers_to(info.name), "{} answers to its own name", info.name);
        }
    }

    /// **A name another platform already answers to is a collision, not a
    /// registration.** A dialect claiming a spelling Healthy DB answers to is
    /// named as the collision it is — with both platforms named, and a fix its
    /// author can actually perform — rather than being reported as an
    /// unregistered platform, which is a different problem with a different fix.
    #[test]
    fn a_name_razeri_reads_as_another_platform_is_blocking_and_says_which() {
        let mut registry = crate::dialect::PlatformRegistry::new();
        registry.register("healthydb".to_owned(), std::sync::Arc::new(HealthyPlugin::new()));
        registry.register("twindb".to_owned(), std::sync::Arc::new(Twin));

        let report = check_with_registry(&Twin, &registry);
        let collision = report
            .findings
            .iter()
            .find(|f| f.what.contains("claimed by more than one loaded platform"))
            .unwrap_or_else(|| panic!("the collision is reported:\n{report}"));
        assert_eq!(collision.severity, Severity::Blocking, "{collision:?}");
        assert!(collision.what.contains("healthydb"), "the other platform is named: {collision:?}");
        assert!(collision.what.contains("twindb"), "{collision:?}");
        assert!(collision.fix.contains("name no other platform answers to"), "{collision:?}");
        assert!(
            !mentions(&report, "does not resolve"),
            "a collision is not an unregistered platform:\n{report}"
        );
    }

    /// A platform reached under its own name owes nothing, whatever the case or
    /// the surrounding whitespace, and a name nothing has ever heard of is
    /// simply unknown rather than a finding against this dialect.
    #[test]
    fn registration_is_silent_on_a_platform_reached_under_its_own_name() {
        let mut registry = crate::dialect::PlatformRegistry::new();
        registry.register("healthydb".to_owned(), std::sync::Arc::new(HealthyPlugin::new()));

        let mut findings = Vec::new();
        check_registration(&HealthyPlugin::new().info(), &registry, &mut findings);
        assert!(findings.is_empty(), "{findings:?}");

        for spelling in ["healthydb", "HealthyDB", "  healthydb  "] {
            assert!(
                crate::resolve::resolve_dialect_by_name_with_registry(spelling, &registry).is_ok(),
                "'{spelling}' is the same platform"
            );
        }
        assert!(
            crate::resolve::resolve_dialect_by_name_with_registry("nosuchdb", &registry).is_err()
        );
    }

    /// **A dialect the host loaded at run time can be graded at all.** [`check`]
    /// asks the dialects linked into this program, which one loaded from a
    /// shared library is not among; [`check_with_registry`] asks the registry it
    /// was loaded into, and the registration judgement is answered from there.
    #[test]
    fn a_dialect_the_host_loaded_reports_clean_against_its_own_registry() {
        let mut registry = crate::dialect::PlatformRegistry::new();
        registry.register("healthydb".to_owned(), std::sync::Arc::new(HealthyPlugin::new()));

        let report = check_with_registry(&HealthyPlugin::new(), &registry);
        assert!(
            report.is_clean(),
            "a loaded dialect that answered everything owes nothing:\n{report}"
        );
    }

    /// **Every fix an outside author can read is a fix in their own crate.** A
    /// report that names a file inside Razeri is telling an author to edit a
    /// tree they do not have, and the fix cannot be performed at all.
    #[test]
    fn no_fix_line_sends_an_author_into_razeris_own_tree() {
        let mut collided = crate::dialect::PlatformRegistry::new();
        collided.register("healthydb".to_owned(), std::sync::Arc::new(HealthyPlugin::new()));
        collided.register("twindb".to_owned(), std::sync::Arc::new(Twin));

        let mut fixes: Vec<String> = Vec::new();
        for report in [
            check(&BrokenPlugin),
            check(&HealthyPlugin::new()),
            check(&OutsidePlugin::new()),
            // The collision and the unregistered-name judgements, which are the
            // two registration findings an author can actually reach.
            check_with_registry(&Twin, &collided),
            check_with_registry(&Twin, &crate::dialect::PlatformRegistry::new()),
        ] {
            fixes.extend(report.findings.iter().map(|f| f.fix.clone()));
        }
        assert!(fixes.len() >= 10, "the sweep reached the findings: {}", fixes.len());
        for fix in &fixes {
            for path in ["schemas/", "crates/", ".json", "scripts/", "razeri-design"] {
                assert!(
                    !fix.contains(path),
                    "a fix line names `{path}`, which is not in the author's crate:\n  {fix}"
                );
            }
        }
    }

    /// **No finding prints a run of spaces in the middle of a sentence.** A
    /// string literal broken across source lines without a `\` keeps the next
    /// line's indentation, and the report then reads "the same as a", a run of
    /// eighteen spaces, then "column". This reaches every finding the fixtures
    /// can reach, plus the four that only a dialect declining every column
    /// change and spelling its own routine grammar can.
    #[test]
    fn no_finding_holds_a_run_of_spaces() {
        struct Declining;
        impl SqlRenderer for Declining {
            fn render_data_type(&self, dt: &DataType) -> String {
                razeri_plugin_contract::agnostic::render_data_type_ansi(dt)
            }
            fn quote_identifier(&self, name: &str) -> String {
                format!("\"{name}\"")
            }
            fn qualify_name(&self, _schema: Option<&str>, name: &str) -> String {
                name.to_owned()
            }
            fn render_current_timestamp(&self) -> String {
                "CURRENT_TIMESTAMP".to_owned()
            }
            fn procedure_form(&self) -> ProcedureForm {
                ProcedureForm::Custom("Declining PSQL")
            }
        }
        impl ExpressionRenderer for Declining {}
        impl DdlRenderer for Declining {
            fn render_column_evolution(&self, _change: &ColumnEvolution<'_>) -> Option<String> {
                None
            }
        }
        impl DialectCapabilities for Declining {
            fn evolution_answers(&self) -> razeri_plugin_contract::EvolutionAnswers {
                razeri_plugin_contract::EvolutionAnswers::widening_a_string_in_place()
                    .and_a_nested_field()
            }
        }
        impl DmlRenderer for Declining {}
        impl ProcedureRenderer for Declining {}
        impl FormulaRenderer for Declining {}
        impl TaskRenderer for Declining {}
        impl ReplicationRenderer for Declining {}
        impl StorageRenderer for Declining {}
        impl PlatformDialect for Declining {
            fn info(&self) -> DialectInfo {
                DialectInfo {
                    name: "decliningdb",
                    display_name: "Declining DB",
                    aliases: &[],
                    dialect_family: DialectFamily::Custom("decliningdb"),
                    family: "Declining",
                    is_cloud_native: false,
                    is_olap: false,
                    is_postgres_compatible: false,
                    versions: &[],
                    deploy_script: None,
                }
            }
        }

        let mut findings = Vec::new();
        check_evolution(&Declining, &mut findings);
        check_parameter_type_spelling(&Declining, &mut findings);
        assert_eq!(findings.len(), 4, "every column change and the parameter note: {findings:?}");

        for report in [
            check(&BrokenPlugin),
            check(&HealthyPlugin::new()),
            check(&OutsidePlugin::new()),
            check(&Declining),
            check_with_registry(&Twin, &crate::dialect::PlatformRegistry::new()),
        ] {
            findings.extend(report.findings);
        }
        for finding in &findings {
            for text in [&finding.what, &finding.fix] {
                assert!(!text.contains("  "), "a finding holds a run of spaces:\n  {text:?}");
            }
        }
    }

    /// **A dialect that claims its own grammar and does not spell it is
    /// blocking**, and the finding names the method it owes.
    ///
    /// `Custom` is a promise that the dialect renders the routine itself, and
    /// Razeri keeps its side by writing nothing: no near-neighbour grammar,
    /// which fails on deploy, and no refusal, which would tell an author their
    /// engine has no routines when it has a whole language. A promise with
    /// nothing behind it is therefore a build that stops, and it has to be
    /// findable before the deploy rather than during it.
    #[test]
    fn a_custom_grammar_without_a_render_method_is_blocking_on_all_three_kinds() {
        for (kind, form_method, render_method) in [
            (RoutineKind::Procedure, "procedure_form", "render_procedure"),
            (RoutineKind::ScalarFunction, "scalar_function_form", "render_scalar_function"),
            (RoutineKind::TableFunction, "table_function_form", "render_table_function"),
        ] {
            let mut promised = Vec::new();
            check_custom_grammar(
                CustomGrammar {
                    kind,
                    grammar: "PL/SQL",
                    form_method,
                    render_method,
                    noun: "routines",
                },
                |_| None,
                &mut promised,
            );
            assert_eq!(promised.len(), 1, "{form_method}: {promised:?}");
            assert_eq!(promised[0].severity, Severity::Blocking, "{promised:?}");
            assert!(promised[0].what.contains(form_method), "{promised:?}");
            assert!(promised[0].what.contains("PL/SQL"), "{promised:?}");
            assert!(promised[0].fix.contains(render_method), "{promised:?}");

            // The same claim, kept: nothing to report.
            let mut kept = Vec::new();
            check_custom_grammar(
                CustomGrammar {
                    kind,
                    grammar: "PL/SQL",
                    form_method,
                    render_method,
                    noun: "routines",
                },
                |spec| Some(format!("CREATE {} …", spec.name)),
                &mut kept,
            );
            assert!(kept.is_empty(), "{form_method}: a kept promise is not a finding: {kept:?}");
        }
    }

    /// **The two answers about stored procedures have to agree.** A dialect
    /// that writes its own procedures and leaves the capability at its default
    /// `false` gets no load written as one; a dialect that says its engine has
    /// them and refuses to write one says both things at once.
    #[test]
    fn a_procedure_form_and_the_procedure_capability_must_agree() {
        fn check_forms(dialect: &dyn PlatformDialect) -> Report {
            let mut findings = Vec::new();
            check_routine_forms(dialect, &mut findings);
            Report { dialect: "claimsdb".to_owned(), findings }
        }

        /// `CUSTOM`: the form spells its own grammar, or refuses. `HAS`: the
        /// capability's answer.
        struct Claims<const CUSTOM: bool, const HAS: bool>;
        impl<const CUSTOM: bool, const HAS: bool> SqlRenderer for Claims<CUSTOM, HAS> {
            fn render_data_type(&self, dt: &DataType) -> String {
                razeri_plugin_contract::agnostic::render_data_type_ansi(dt)
            }
            fn quote_identifier(&self, name: &str) -> String {
                format!("\"{name}\"")
            }
            fn qualify_name(&self, _schema: Option<&str>, name: &str) -> String {
                self.quote_identifier(name)
            }
            fn render_current_timestamp(&self) -> String {
                "CURRENT_TIMESTAMP".to_owned()
            }
            fn procedure_form(&self) -> ProcedureForm {
                if CUSTOM {
                    ProcedureForm::Custom("Firebird PSQL")
                } else {
                    ProcedureForm::Unsupported(
                        "This engine has no stored procedures; schedule the load as a query.",
                    )
                }
            }
            fn render_procedure(&self, spec: &RoutineSpec) -> Option<String> {
                Some(format!("CREATE PROCEDURE {} AS BEGIN END", spec.name))
            }
        }
        impl<const CUSTOM: bool, const HAS: bool> ExpressionRenderer for Claims<CUSTOM, HAS> {}
        impl<const CUSTOM: bool, const HAS: bool> DdlRenderer for Claims<CUSTOM, HAS> {}
        impl<const CUSTOM: bool, const HAS: bool> DmlRenderer for Claims<CUSTOM, HAS> {}
        impl<const CUSTOM: bool, const HAS: bool> ProcedureRenderer for Claims<CUSTOM, HAS> {}
        impl<const CUSTOM: bool, const HAS: bool> FormulaRenderer for Claims<CUSTOM, HAS> {}
        impl<const CUSTOM: bool, const HAS: bool> TaskRenderer for Claims<CUSTOM, HAS> {}
        impl<const CUSTOM: bool, const HAS: bool> ReplicationRenderer for Claims<CUSTOM, HAS> {}
        impl<const CUSTOM: bool, const HAS: bool> StorageRenderer for Claims<CUSTOM, HAS> {}
        impl<const CUSTOM: bool, const HAS: bool> DialectCapabilities for Claims<CUSTOM, HAS> {
            fn supports_stored_procedures(&self) -> bool {
                HAS
            }
        }
        impl<const CUSTOM: bool, const HAS: bool> PlatformDialect for Claims<CUSTOM, HAS> {
            fn info(&self) -> DialectInfo {
                DialectInfo {
                    name: "claimsdb",
                    display_name: "Claims DB",
                    aliases: &[],
                    dialect_family: DialectFamily::Custom("claimsdb"),
                    family: "Claims",
                    is_cloud_native: false,
                    is_olap: false,
                    is_postgres_compatible: false,
                    versions: &[],
                    deploy_script: None,
                }
            }
        }

        let disagreement = |report: &Report| {
            report
                .findings
                .iter()
                .filter(|finding| {
                    finding.severity == Severity::Blocking
                        && (finding.what.contains("says its engine has none")
                            || finding.what.contains("refuses to write one"))
                })
                .count()
        };
        let mut findings = Vec::new();
        check_routine_forms(&Claims::<true, false>, &mut findings);
        let report = Report { dialect: "claimsdb".to_owned(), findings };
        assert_eq!(disagreement(&report), 1, "{report}");
        assert!(report.to_string().contains("supports_stored_procedures"), "{report}");

        for (agrees, label) in [
            (check_forms(&Claims::<true, true>), "spelled and used"),
            (check_forms(&Claims::<false, false>), "refused and absent"),
        ] {
            assert_eq!(disagreement(&agrees), 0, "{label}: {agrees}");
        }
        assert_eq!(disagreement(&check_forms(&Claims::<false, true>)), 1, "refused and claimed");
    }

    /// **A render method that answers with nothing at all is the same defect.**
    ///
    /// An override that returns an empty string is a routine razeri would emit
    /// as blank space in the middle of a schema, which is worse than an error:
    /// nothing in the deploy says anything is missing.
    #[test]
    fn a_custom_render_method_that_returns_nothing_is_blocking() {
        let mut findings = Vec::new();
        check_custom_grammar(
            CustomGrammar {
                kind: RoutineKind::Procedure,
                grammar: "PL/SQL",
                form_method: "procedure_form",
                render_method: "render_procedure",
                noun: "procedures",
            },
            |_| Some("   \n".to_owned()),
            &mut findings,
        );
        assert_eq!(findings.len(), 1, "{findings:?}");
        assert_eq!(findings[0].severity, Severity::Blocking, "{findings:?}");
    }

    /// **A `Custom` grammar with no name gives a reader nothing to read.**
    #[test]
    fn a_custom_grammar_with_no_name_is_blocking() {
        let mut findings = Vec::new();
        check_custom_grammar(
            CustomGrammar {
                kind: RoutineKind::ScalarFunction,
                grammar: "  ",
                form_method: "scalar_function_form",
                render_method: "render_scalar_function",
                noun: "scalar functions",
            },
            |spec| Some(format!("CREATE FUNCTION {}", spec.name)),
            &mut findings,
        );
        assert_eq!(findings.len(), 1, "{findings:?}");
        assert!(findings[0].what.contains("names no grammar"), "{findings:?}");
    }

    /// **A stated fact that is empty, and a stated fact that ignores its
    /// argument.**
    ///
    /// Both answers deploy without an error and are wrong in the data: an empty
    /// helper body keys every row to nothing, and a file relation that drops
    /// its path reads one file for every entity a design binds. Neither is
    /// visible anywhere else, which is why the kit grades them.
    #[test]
    fn a_stated_key_body_that_is_blank_and_a_file_relation_that_drops_its_path_are_blocking() {
        struct Overstated;
        impl SqlRenderer for Overstated {
            fn render_data_type(&self, dt: &DataType) -> String {
                razeri_plugin_contract::agnostic::render_data_type_ansi(dt)
            }
            fn quote_identifier(&self, name: &str) -> String {
                format!("\"{name}\"")
            }
            fn qualify_name(&self, _schema: Option<&str>, name: &str) -> String {
                name.to_owned()
            }
            fn render_current_timestamp(&self) -> String {
                "CURRENT_TIMESTAMP".to_owned()
            }
            fn dialect_name(&self) -> &'static str {
                "overstateddb"
            }
            fn sid_helper_body(&self) -> Option<razeri_plugin_contract::SidHelperBody> {
                Some(razeri_plugin_contract::SidHelperBody {
                    language: razeri_plugin_contract::SidHelperLanguage::JavaScript,
                    body: "   \n  ",
                })
            }
            fn delimited_file_relation(&self, _path_literal: &str) -> Option<String> {
                Some("read_csv_auto('/tmp/whatever.csv')".to_owned())
            }
        }
        impl ExpressionRenderer for Overstated {}
        impl DdlRenderer for Overstated {}
        impl DmlRenderer for Overstated {}
        impl ProcedureRenderer for Overstated {}
        impl FormulaRenderer for Overstated {}
        impl TaskRenderer for Overstated {}
        impl ReplicationRenderer for Overstated {}
        impl StorageRenderer for Overstated {}
        impl DialectCapabilities for Overstated {}
        impl PlatformDialect for Overstated {
            fn info(&self) -> DialectInfo {
                DialectInfo {
                    name: "overstateddb",
                    display_name: "Overstated DB",
                    aliases: &[],
                    dialect_family: DialectFamily::Custom(""),
                    family: "Overstated",
                    is_cloud_native: false,
                    is_olap: false,
                    is_postgres_compatible: false,
                    versions: &[],
                    deploy_script: None,
                }
            }
        }

        let report = check(&Overstated);
        for (fragment, why) in [
            ("supplies an empty one", "a blank helper body"),
            ("does not carry the path", "a file relation that drops its path"),
        ] {
            let found = report
                .findings
                .iter()
                .find(|f| f.what.contains(fragment))
                .unwrap_or_else(|| panic!("{why} is not graded:\n{report}"));
            assert_eq!(found.severity, Severity::Blocking, "{why}: {found:?}");
        }

        // And silence is not a finding. The fully-answered fixture states
        // neither fact — the majority answer — and owes nothing for it.
        let quiet = check(&HealthyPlugin::new());
        for fragment in ["supplies an empty one", "does not carry the path"] {
            assert!(
                !quiet.findings.iter().any(|f| f.what.contains(fragment)),
                "silence is the default and owes nothing:\n{quiet}"
            );
        }
    }

    /// **A stated refusal is a claim, and a claim owes the reader words** — the
    /// same grading every other refusal gets, because these words are the only
    /// thing an operator finds where the load would have been.
    #[test]
    fn a_stated_upsert_refusal_is_graded_as_copy_and_a_silent_one_as_unanswered() {
        let mut mute = Vec::new();
        check_refusal_copy("the upsert refusal", "no merge", &mut mute);
        assert_eq!(mute.len(), 1, "a three-word refusal is a wall: {mute:?}");
        assert!(mute[0].what.contains("the upsert refusal"), "{mute:?}");

        let silence = check(&BrokenPlugin);
        assert!(
            silence
                .findings
                .iter()
                .any(|f| f.severity == Severity::Unanswered && f.what.contains("upsert shape")),
            "a dialect that never answered is unanswered, not wrong:\n{silence}"
        );
        assert!(
            silence.findings.iter().any(|f| f.fix.contains("MergeShape::Unsupported")),
            "the fix line names the way to state a measured refusal:\n{silence}"
        );
    }

    /// The report leads with what to fix and says it in words a reader can
    /// act on — no identifiers from Razeri's own record-keeping, and a
    /// one-line count before the detail.
    #[test]
    fn the_report_reads_as_a_summary_then_a_fix_list() {
        let text = check(&BrokenPlugin).to_string();
        let first = text.lines().next().expect("a summary line");
        assert!(first.starts_with("brokendb:"), "{first}");
        assert!(first.contains("to fix"), "{first}");
        assert!(text.contains("      → "), "every finding names the fix:\n{text}");
    }
}