razeri-plugin-api 0.10.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
// 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.**
//!
//! Two people built dialects against Razeri's authoring guide without help.
//! Between them they met twenty-seven obligations one `cargo test` at a time —
//! a roster fails, you add a row, the next roster fails. Not one of those
//! failures was discoverable before it fired. 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:
//!
//! ```no_run
//! # 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::{
    ColumnEvolution, ConstraintEnforcement, DataType, DdlRenderer, DialectCapabilities,
    DialectFamily, EntityRef, MergeRestrictions, MergeShape, ProcedureForm, RoutineKind,
    RoutineSpec, ScalarFunctionForm, SliceRef, SliceReplacement, SqlRenderer, StorageRenderer,
    TableFunctionForm, UNKNOWN_PLATFORM,
    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_paired_facts(dialect, &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 `inventory::submit! { DialectRegistration::new(\"<name>\", || \
                  Arc::new(MyDialect)) }` to your crate, and a `pub fn force_link() {}` beside \
                  it that the program using your dialect calls once. A crate nothing links is a \
                  crate the registry cannot see."
                .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 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 is this                   spelling on every engine Razeri reaches. 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(),
        });
    }
}

/// **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.
fn check_formula_renderer(dialect: &dyn PlatformDialect, findings: &mut Vec<Finding>) {
    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);
        }
        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,
        ),
        _ => {}
    }

    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 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(),
        });
    }
}

/// Fourteen 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");
    }

    if !disagreements.is_empty() {
        findings.push(Finding {
            severity: Severity::Blocking,
            what: format!(
                "this dialect declares {} fact(s) two ways and the two disagree: {}",
                disagreements.len(),
                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(),
        });
    }
}

/// 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 — which the guide, until
    // now, told them was enough.

    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,
            }
        }
    }

    // ── 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");
    }

    /// 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");
    }

    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> {}
    impl<N: FixtureName> DdlRenderer for Answered<N> {}
    impl<N: FixtureName> DmlRenderer for Answered<N> {}
    impl<N: FixtureName> ProcedureRenderer for Answered<N> {}
    impl<N: FixtureName> FormulaRenderer for Answered<N> {}
    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 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,
            }
        }
    }

    // ── 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,
            }
        }
    }

    /// 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 is exactly what the guide used to produce, 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),
            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 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.** That used to be a judgement about a hand-written table, which
    /// could spell a platform `mynew_db` and never `mynewdb`. A dialect now
    /// declares its own spellings and its own name leads them, so the defect is
    /// unrepresentable rather than caught — 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}"
                );
            }
        }
    }

    /// **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:?}");
        }
    }

    /// **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,
                }
            }
        }

        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}");
    }
}