vole-document 0.1.0-alpha.35

Persistent procedural document runtime: byte-exact reconstruction plus a content-addressed procedural seed DAG, queryable observations with provenance, and selective late materialization.
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
//! Byte-based document-format detection for the universal observation API
//! (Phase 12.7, ADR-0031, plan §DEC-5).
//!
//! The format of a field is decided from its **source bytes**, never from a file
//! name or extension. Detection is deliberately conservative: an ambiguous or
//! malformed input falls back to [`DocumentFormat::Opaque`] rather than guessing,
//! and a format whose adapter is not compiled in cannot be detected (the input is
//! then `Opaque`), so the reported capability set always matches what the build
//! can actually serve.
//!
//! * **PDF** — the byte-authoritative physical scanner admits a validated PDF
//!   ([`crate::adapter::pdf::detect`]).
//! * **DOCX** — a valid ZIP that also carries the OPC content-types part
//!   (`[Content_Types].xml`) and a package `officeDocument` relationship.
//! * **EPUB** — a valid ZIP that is an OCF container: the mandatory stored
//!   `mimetype` member equals `application/epub+zip`, or `META-INF/container.xml`
//!   names the OCF namespace and an OPF (`application/oebps-package+xml`) rootfile.
//! * **ODT** — a valid ZIP that is an OpenDocument (ODF) package: the mandatory
//!   stored `mimetype` member is an OpenDocument *text* media type, or
//!   `META-INF/manifest.xml` declares one.
//! * **ODS** — a valid ZIP that is an OpenDocument (ODF) package whose mandatory
//!   stored `mimetype` member is an OpenDocument *spreadsheet* media type, or whose
//!   `META-INF/manifest.xml` declares one (Phase 21.3.1). Mutually exclusive with
//!   ODT (a text document declares the text media type, a spreadsheet the
//!   spreadsheet one).
//! * **ODP** — a valid ZIP that is an OpenDocument (ODF) package whose mandatory
//!   stored `mimetype` member is an OpenDocument *presentation* media type, or whose
//!   `META-INF/manifest.xml` declares one (Phase 21.4.1). Mutually exclusive with
//!   ODT/ODS (a presentation declares the presentation media type).
//! * **Opaque** — everything else, including a ZIP that matches none of the above
//!   (or more than one — an ambiguous ZIP fails safe).
//! * **JSON** — the whole source parses as exactly one JSON value within the
//!   JSON caps (Phase 21.5.1). JSON is a Wave-2 structured-tree format with no
//!   package layer, so it is detected directly (never via `detect_zip_family`);
//!   a malformed or oversized input stays `Opaque`.
//!
//! The detected format is recorded in the field manifest's provenance (a
//! machine-readable `format=<name>;` prefix, see [`DocumentFormat::from_provenance`]),
//! so `observe`/`find`/`explain` can dispatch common selectors without reading the
//! whole source again.

use crate::limits::Limits;

/// The mandatory OCF `mimetype` payload.
pub const EPUB_MIMETYPE: &[u8] = b"application/epub+zip";
/// The mandatory OCF container descriptor member.
pub const CONTAINER_MEMBER: &[u8] = b"META-INF/container.xml";
/// The OCF container-descriptor namespace.
pub const CONTAINER_NS: &[u8] = b"urn:oasis:names:tc:opendocument:xmlns:container";
/// The default (and only normative) package-document media type.
pub const OPF_MEDIA_TYPE: &[u8] = b"application/oebps-package+xml";
/// The OPC content-types part.
#[cfg(feature = "package")]
const CONTENT_TYPES_MEMBER: &[u8] = b"[Content_Types].xml";
/// The OPC package-relationships part.
#[cfg(feature = "package")]
const PACKAGE_RELS_MEMBER: &[u8] = b"_rels/.rels";
/// The `officeDocument` relationship type fragment (transitional and strict).
#[cfg(feature = "package")]
const OFFICE_DOCUMENT_FRAGMENT: &[u8] = b"officeDocument";
/// The WordprocessingML document main content-type fragment (Phase 21.1.2).
#[cfg(feature = "package")]
const DOCX_MAIN_FRAGMENT: &[u8] = b"wordprocessingml.document.main+xml";
/// The canonical WordprocessingML main-part target fragment (Phase 21.1.2).
#[cfg(feature = "package")]
const DOCX_MAIN_TARGET: &[u8] = b"word/document.xml";
/// The ODF package manifest member (Phase 13.3).
#[cfg(feature = "odt")]
const ODT_MANIFEST_MEMBER: &[u8] = b"META-INF/manifest.xml";
/// The OpenDocument *text* media-type fragment (Phase 13.3).
#[cfg(feature = "odt")]
const ODT_TEXT_FRAGMENT: &[u8] = b"application/vnd.oasis.opendocument.text";
/// The ODF package manifest member for the ODS detection rule (Phase 21.3.1).
#[cfg(feature = "ods")]
const ODS_MANIFEST_MEMBER: &[u8] = b"META-INF/manifest.xml";
/// The OpenDocument *spreadsheet* media-type fragment (Phase 21.3.1).
#[cfg(feature = "ods")]
const ODS_SPREADSHEET_FRAGMENT: &[u8] = b"application/vnd.oasis.opendocument.spreadsheet";
/// The ODF package manifest member for the ODP detection rule (Phase 21.4.1).
#[cfg(feature = "odp")]
const ODP_MANIFEST_MEMBER: &[u8] = b"META-INF/manifest.xml";
/// The OpenDocument *presentation* media-type fragment (Phase 21.4.1).
#[cfg(feature = "odp")]
const ODP_PRESENTATION_FRAGMENT: &[u8] = b"application/vnd.oasis.opendocument.presentation";
/// The ODF media-type prefix shared by every OpenDocument package.
#[cfg(any(feature = "odt", feature = "ods", feature = "odp"))]
const ODF_MEDIA_PREFIX: &[u8] = b"application/vnd.oasis.opendocument.";
/// The SpreadsheetML workbook main content-type fragment (Phase 21.1.1).
#[cfg(feature = "xlsx")]
const XLSX_MAIN_FRAGMENT: &[u8] = b"spreadsheetml.sheet.main+xml";
/// The SpreadsheetML content-type namespace fragment (Phase 21.1.1).
#[cfg(feature = "xlsx")]
const XLSX_NS_FRAGMENT: &[u8] = b"spreadsheetml";
/// A SpreadsheetML workbook part-target fragment (Phase 21.1.1).
#[cfg(feature = "xlsx")]
const XLSX_WORKBOOK_TARGET: &[u8] = b"xl/workbook.xml";
/// The PresentationML presentation main content-type fragment (Phase 21.2.1).
#[cfg(feature = "pptx")]
const PPTX_MAIN_FRAGMENT: &[u8] = b"presentationml.presentation.main+xml";
/// The PresentationML content-type namespace fragment (Phase 21.2.1).
#[cfg(feature = "pptx")]
const PPTX_NS_FRAGMENT: &[u8] = b"presentationml";
/// The canonical PresentationML main-part target fragment (Phase 21.2.1).
#[cfg(feature = "pptx")]
const PPTX_MAIN_TARGET: &[u8] = b"ppt/presentation.xml";

/// A detected document format (the class of the field's source bytes).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DocumentFormat {
    /// A validated PDF (physical indicators).
    Pdf,
    /// An OPC package with a WordprocessingML `officeDocument` part.
    Docx,
    /// An OCF container with an EPUB package document.
    Epub,
    /// An ODF package with an OpenDocument text content part.
    Odt,
    /// An ODF package with an OpenDocument spreadsheet content part.
    Ods,
    /// An ODF package with an OpenDocument presentation content part.
    Odp,
    /// An OPC package with a SpreadsheetML workbook part.
    Xlsx,
    /// An OPC package with a PresentationML presentation part.
    Pptx,
    /// A structured JSON document (the whole source parses as exactly one JSON
    /// value). Not a package: the exact leaf is the whole source (Phase 21.5.1).
    Json,
    /// A structured JSON5 / JSONC document (the whole source parses as exactly one
    /// JSON5 value and uses at least one JSON5/JSONC-only construct — a comment, a
    /// trailing comma, an unquoted key, a single-quoted string, a hex/leading-dot/
    /// `Infinity`/`NaN` number, a string continuation, or the extended whitespace
    /// set). A source that is **strict JSON** stays [`DocumentFormat::Json`]; the
    /// recorded dialect distinguishes `jsonc` (comments/trailing commas only) from
    /// `json5`. Not a package: the exact leaf is the whole source (Phase 21.17.1).
    Json5,
    /// A structured YAML document (the whole source parses as a bounded YAML
    /// stream whose every document root is a mapping or a sequence). Not a package:
    /// the exact leaf is the whole source (Phase 21.6.1).
    Yaml,
    /// A CSV/TSV table (the whole source parses under a comma or tab delimiter as a
    /// table with a consistent field count across a sampled majority of at least
    /// two records). Not a package: the exact leaf is the whole source, and the
    /// recorded dialect distinguishes comma from tab (Phase 21.7.1).
    Csv,
    /// A Markdown prose document (the whole source carries at least one structural
    /// mark — an ATX heading, a fenced code block, front matter, a table, a
    /// reference definition, or a footnote definition). Not a package: the exact
    /// leaf is the whole source (Phase 21.8.1).
    Markdown,
    /// A standalone XML document (the whole source begins with `<` and parses as
    /// well-formed XML with exactly one root element under the XML caps). Not a
    /// package: the exact leaf is the whole source, and every node/attribute span
    /// is a `Q_gen` projection (Phase 21.9).
    Xml,
    /// A standalone HTML document (the source is not well-formed XML and carries
    /// clear HTML structure — a `<!doctype html>`, an `<html`/`<head`/`<body` tag,
    /// or a preponderance of known HTML tags). Parsed by a bounded,
    /// **error-recovering** scanner. Not a package: the exact leaf is the whole
    /// source, and every element/attribute/text/comment/raw-text span is a `Q_gen`
    /// projection (Phase 21.10).
    Html,
    /// A TOML document (the source is not any earlier format, parses as TOML 1.0
    /// under the TOML caps with no errors, and carries at least one key/value
    /// assignment). Not a package: the exact leaf is the whole source, and every
    /// table/array/key/value/comment span is a `Q_gen` projection. TOML's
    /// duplicate-key/redefinition rules are enforced (a violation is a typed
    /// decline) (Phase 21.11).
    Toml,
    /// A JSONL / NDJSON line/event stream (the source carries at least two non-blank
    /// lines and **every** non-blank line parses as exactly one JSON value under the
    /// caps, each line parsed by the shared JSON parser). Not a package: the exact
    /// leaf is the whole source, and every record's exact line span, terminator, and
    /// per-token spans are `Q_gen` projections. A single JSON value stays
    /// [`DocumentFormat::Json`] (Phase 21.12).
    Jsonl,
    /// An EML / MIME internet message (an RFC 5322 header block terminated by a
    /// blank line, carrying `From`/`Date`/`Message-ID`, or an explicit
    /// `MIME-Version`). Not a package: the exact leaf is the whole source, and every
    /// header/part span and every `Content-Transfer-Encoding`-decoded constituent is
    /// a `Q_gen` projection (Phase 21.13).
    Eml,
    /// An Apache Parquet file (the source begins with `PAR1` and ends with `PAR1`,
    /// and the 4-byte little-endian footer length before the trailing magic is
    /// consistent with the file length). Not a package: the exact leaf is the whole
    /// source, and the parsed footer inventory (schema, row groups, column chunks,
    /// statistics) and any decoded values are `Q_gen` projections (Phase 21.14).
    Parquet,
    /// An Apache Arrow IPC file/stream (the source begins with the `ARROW1` magic
    /// and is either the **file** format — a trailing `ARROW1` magic preceded by a
    /// consistent little-endian `int32` footer length — or the **stream** format —
    /// a valid encapsulated `Schema` message at the 8-byte magic+padding prefix).
    /// Not a package: the exact leaf is the whole source, and the parsed schema and
    /// record-batch inventory (with each batch's exact source span) and any decoded
    /// columnar values are `Q_gen` projections (Phase 21.16).
    ArrowIpc,
    /// A CBOR (RFC 8949) structured-tree document. CBOR has **no magic bytes**, so
    /// detection is deliberately conservative: either the self-described-CBOR tag
    /// `55799` (`0xd9 0xd9 0xf7`) at the start of a source that then parses, in full,
    /// as exactly one well-formed CBOR item; or a full-input well-formed parse whose
    /// root is a container (array/map) or a tag and which reaches at least three
    /// nodes (so a single scalar, an empty container, and any structurally trivial
    /// input stay `Opaque`). A container head byte is always `>= 0x80`, so a
    /// pure-ASCII text document is never claimed; and the whole-number/short-
    /// container prefix overlaps MessagePack's fixint/fixarray encodings, so an
    /// ambiguous or trivial input stays `Opaque` rather than being guessed
    /// (recorded honestly; a Phase-21.19 MessagePack adapter must share the seam).
    /// Not a package: the exact leaf is the whole source, and every item's kind,
    /// exact span, encoding width, tag number, float width, and definite/indefinite
    /// form are `Q_gen` projections (Phase 21.18).
    Cbor,
    /// A MessagePack structured-tree document. MessagePack has **no magic bytes**, so
    /// detection is deliberately conservative: a full-input well-formed parse of
    /// exactly one item whose root is a container (array/map) reaching a node/byte
    /// threshold, or the same with an unambiguous MessagePack-only head byte
    /// (`0xdc..=0xdf`, which CBOR's grammar rejects). A lone scalar, an empty
    /// container, and any structurally trivial or ambiguous input stay `Opaque`. A
    /// container head byte is always `>= 0x80`, so a pure-ASCII text document is never
    /// claimed; and the whole-number/short-container prefix overlaps CBOR's
    /// `fixint`/short containers, so CBOR (tried first) owns any input well-formed
    /// under both. Not a package: the exact leaf is the whole source, and every item's
    /// kind, exact span, exact format byte (encoding width and signedness), `str` vs
    /// `bin`, float width, and extension type/length are `Q_gen` projections
    /// (Phase 21.19).
    Msgpack,
    /// A config-family document (INI, `.env`, or Java `.properties`). The whole
    /// source is a bounded, line-based key/value document; detection is
    /// deliberately conservative and requires a **dialect-distinguishing signal**,
    /// so the pure `KEY=VALUE` overlap between `env` and `properties` (and plain
    /// prose, and a `#!` script) stays `Opaque` rather than being guessed. Not a
    /// package: the exact leaf is the whole source, the recorded dialect lives in
    /// the model, and every line's content span, key/separator/value span, quoting,
    /// inline-comment, `export`, continuation, and `\uXXXX`-spelling fact is a
    /// `Q_gen` projection (Phase 21.20).
    Config,
    /// An RSS 2.0 / Atom 1.0 syndication feed. A feed is XML, so its physical
    /// bytes are shared with [`DocumentFormat::Xml`]; the recorded dialect
    /// (`rss`/`atom`) lives in the model, exactly as CSV/config record their
    /// dialects. Detection is a **bounded semantic test** run before the generic
    /// XML detector: an RSS root `<rss>` with a `<channel>` child, or an Atom root
    /// `<feed>` in the Atom namespace (`http://www.w3.org/2005/Atom`) with at least
    /// one `<entry>` child. Not a package: the exact leaf is the whole source, and
    /// every element/attribute span, element order, attribute spelling, and the
    /// namespace declaration is a `Q_gen` projection (Phase 21.21).
    Feed,
    /// A GeoJSON (RFC 7946) document. GeoJSON is JSON, so its physical bytes are
    /// shared with [`DocumentFormat::Json`]; the recorded root class (one of the nine
    /// RFC 7946 type names) lives in the model, exactly as a feed records its
    /// dialect. Detection is a **bounded semantic test** run before the generic JSON
    /// detector: the source parses as exactly one JSON value whose root is an object
    /// with a `"type"` string equal to one of the nine type names and whose shape is
    /// consistent (a geometry has an array `coordinates`/`geometries`, a `Feature` has
    /// `geometry`/`properties`, a `FeatureCollection` has an array `features`). A plain
    /// JSON document, and a JSON document whose `"type"` is an unrelated string, stay
    /// [`DocumentFormat::Json`]. Not a package: the exact leaf is the whole source, and
    /// every JSON token span, `coordinates` nesting, `properties` order/duplicates, and
    /// foreign member is a `Q_gen` projection (Phase 21.22).
    Geojson,
    /// A KML 2.2 / GPX 1.1 geospatial document. KML and GPX are XML, so their
    /// physical bytes are shared with [`DocumentFormat::Xml`]; the recorded dialect
    /// (`kml`/`gpx`) lives in the model, exactly as a feed records its dialect.
    /// Detection is a **bounded semantic test** run before the generic XML detector:
    /// a `<kml>` root in the KML namespace (`http://www.opengis.net/kml/2.2`) with a
    /// `Document`/`Folder`/`Placemark` child, or a `<gpx>` root in the GPX namespace
    /// (`http://www.topografix.com/GPX/1/1`) with a `metadata`/`wpt`/`rte`/`trk`
    /// child. A plain XML document, and a shaped-but-invalid KML/GPX, stay
    /// [`DocumentFormat::Xml`]. Not a package: the exact leaf is the whole source,
    /// and every element/attribute span, element order, attribute spelling (KML
    /// geometry `coordinates`, GPX `lat`/`lon`), and the namespace declaration is a
    /// `Q_gen` projection (Phase 21.23).
    Gis,
    /// A Jupyter notebook (`.ipynb`, nbformat) document. A notebook's physical bytes
    /// are JSON, so its bytes are shared with [`DocumentFormat::Json`]; the recorded
    /// `nbformat`/`nbformat_minor` and the cell anchors live in the model, exactly as a
    /// feed records its dialect. Detection is a **bounded semantic test** run before the
    /// generic JSON detector: the source parses as exactly one JSON value whose root is
    /// an object with a plain non-negative integer-literal `nbformat` (≥ 1) and an array
    /// `cells`, every cell an object with a string `cell_type`, and every present
    /// recognized field nbformat-shaped. A plain JSON document, and a JSON document that
    /// merely has a `cells` key but is not nbformat-shaped, stay
    /// [`DocumentFormat::Json`]. Not a package: the exact leaf is the whole source, and
    /// every JSON token span, the exact `cell_type`/`output_type` strings, the exact
    /// `source` representation, and the cell/output order are `Q_gen` projections
    /// (Phase 21.24).
    Notebook,
    /// A fixed-width (column-position) text table. Unlike CSV/TSV/PSV, its columns
    /// are defined by **character positions**, not a delimiter, so it is a distinct
    /// format rather than a CSV dialect. Detection is **maximally conservative**
    /// (fixed-width is inherently ambiguous): at least three sampled records of
    /// identical byte width, an inferred column layout whose interior whitespace gaps
    /// are at least two columns wide, at least two non-empty columns, and an outright
    /// decline of anything that also parses as a delimited (CSV/TSV/PSV) or Markdown
    /// table. Not a package: the exact leaf is the whole source, and the recorded
    /// inferred layout, per-record spans, per-column spans, padding, terminator, BOM,
    /// and header row are `Q_gen` projections (Phase 21.25).
    FixedWidth,
    /// A reStructuredText (Docutils) prose document (the whole source carries a
    /// reST-specific structural mark — an explicit markup start (`.. ` comment,
    /// directive, target, footnote/citation, or substitution definition), a grid or
    /// simple table, a field list, or a section adornment whose char is not a
    /// Markdown construct — so plain prose and a Markdown document are never stolen).
    /// Not a package: the exact leaf is the whole source (Phase 21.26.1).
    Rst,
    /// An AsciiDoc prose document (the whole source carries an AsciiDoc-specific
    /// structural mark — a level-0 document title followed by a further block, a
    /// `==`+ section, a `|===` table, a complete delimited block, or a block attribute
    /// line followed by a block — so plain prose and a Markdown or reStructuredText
    /// document are never stolen). Not a package: the exact leaf is the whole source
    /// (Phase 21.26.2).
    Asciidoc,
    /// An MDX document (Markdown + JSX/ESM). The whole source carries an MDX-specific
    /// signal — a top-level ESM `import`/`export` statement, a JSX element with a
    /// capitalized/namespaced component name (or a JSX-specific attribute), a JSX
    /// fragment, or a whole-line MDX block expression `{…}` — so plain Markdown stays
    /// `Markdown`, plain HTML stays `Html`, and plain prose stays `Opaque`. Not a
    /// package: the exact leaf is the whole source (Phase 21.26.3).
    Mdx,
    /// An MHTML (MIME HTML, RFC 2557) web archive. MHTML is a MIME
    /// `multipart/related` document, so its physical bytes are shared with
    /// [`DocumentFormat::Eml`]; the adapter **reuses the EML adapter's bounded MIME
    /// layer** for the envelope, parts, headers, and spans, and the **HTML adapter's
    /// bounded, error-recovering scanner** for the root part, recording the ordered
    /// sub-resources (keyed by `Content-Location`/`Content-ID`) and the root HTML part
    /// (the `start=` `Content-ID`, else the first `text/html` part, else the first
    /// part). Detection is a **bounded semantic sub-detection** run before the generic
    /// EML detector: a `multipart/related` root plus an MHTML-specific signal — a
    /// top-level `Snapshot-Content-Location`/`Content-Base`, or a `From`+`Subject`
    /// envelope with a `text/html` part. A plain `multipart/related` email, a plain
    /// HTML document, and a plain MIME message stay `Eml`/`Html`/`Eml`. Not a package:
    /// the exact leaf is the whole source, and every envelope/part/resource/HTML span
    /// is a `Q_gen` projection (Phase 21.27).
    Mhtml,
    /// A syslog / log-stream document (the source carries at least two non-blank
    /// lines and **every** non-blank line matches one of three dialects — RFC 5424
    /// syslog, RFC 3164 (BSD) syslog, or a generic application log line with a
    /// leading timestamp and/or level token). The recorded dialect lives per record.
    /// Detection is byte-based and conservative and must not steal a higher-priority
    /// format (JSON/JSON5/JSONL/CBOR/Msgpack/YAML/TOML/config/CSV or the prose
    /// family), all of which the whole-source dispatcher tries first; plain prose and
    /// code stay `Opaque`. Not a package: the exact leaf is the whole source, and
    /// every record's exact line span/terminator plus every field's exact span (and
    /// the preserved message, NILVALUE, and timestamp/level spelling) are `Q_gen`
    /// projections (Phase 21.28).
    Logstream,
    /// A package-metadata manifest (an npm `package.json` / `package-lock.json`, a
    /// Cargo `Cargo.toml` / `Cargo.lock`, or a Python `pyproject.toml`). A manifest's
    /// physical bytes are JSON or TOML, so its bytes are shared with
    /// [`DocumentFormat::Json`]/[`DocumentFormat::Toml`]; the **recorded dialect**
    /// (`npm_package` / `cargo_manifest` / `pyproject_manifest` / `cargo_lock` /
    /// `npm_lock`) lives in the model, exactly as a feed or CSV records its dialect.
    /// Detection is a **bounded semantic test** run before the generic JSON and TOML
    /// detectors: a strong manifest shape (a JSON object with string `name`+`version`
    /// and a third package-specific key, a JSON `lockfileVersion`+`packages`, a TOML
    /// `[package]` table with a string `name`, a TOML `[build-system]`/`[project]`/
    /// `[tool.poetry]`, or a TOML `[[package]]` lockfile shape). A generic JSON
    /// `name`+`version`, a generic TOML `name`/`version`, and plain prose stay
    /// `Json`/`Toml`/`Opaque`. Not a package: the exact leaf is the whole source, and
    /// every reused-arena token span plus every recorded section/entry name/value span
    /// is a `Q_gen` projection (Phase 21.29).
    Pkgmeta,
    /// An API/specification document (a JSON Schema, an OpenAPI 3.x, a Swagger 2.0, or
    /// an AsyncAPI document). An API spec's physical bytes are JSON, so its bytes are
    /// shared with [`DocumentFormat::Json`]; the **recorded dialect** (`json_schema` /
    /// `openapi` / `swagger` / `asyncapi`) lives in the model, exactly as CSV/config/
    /// feed/pkgmeta record their dialects. Detection is a **bounded semantic test** run
    /// before the generic JSON detector on a **strong, root-level string marker**: a
    /// JSON-Schema `$schema` URI, an `openapi` `3.`-prefixed string, a `swagger` equal
    /// to `"2.0"`, or a non-empty `asyncapi` string. A plain JSON object that merely
    /// contains a `properties`/`paths`/`components` key but lacks the marker stays
    /// [`DocumentFormat::Json`], and a JSON-Schema-shaped object with no `$schema` is
    /// an honest recorded ambiguity that also stays `Json`. Not a package: the exact
    /// leaf is the whole source, and every JSON token span plus every recorded
    /// object/member/`$ref` span is a `Q_gen` projection — `$ref` targets are preserved
    /// verbatim and never resolved (Phase 21.30).
    Apispec,
    /// A bibliography document — **BibTeX** (`.bib`), **RIS**, or **CSL-JSON**. One
    /// adapter covers the three dialects and records the dialect in the model, exactly
    /// as CSV/config/feed/pkgmeta/apispec record theirs. BibTeX and RIS are text (no
    /// magic bytes); CSL-JSON's physical bytes are JSON, so its **bounded semantic
    /// sub-detection runs before the generic JSON detector**: the root must be an
    /// **array** of objects (or a single object) each carrying a string `id` **and** a
    /// string `type`. A plain JSON array, a JSON array of non-CSL objects, a generic
    /// JSON object, and prose stay [`DocumentFormat::Json`]/[`DocumentFormat::Opaque`].
    /// Not a package: the exact leaf is the whole source, and every entry/field/item
    /// token span (and each value's exact spelling and delimiters) is a `Q_gen`
    /// projection (Phase 21.31).
    Bib,
    /// An RTF (Rich Text Format) document. RTF is a text, group-brace grammar with
    /// control words — closer to a bounded tree (like XML) than to a flat line format —
    /// but it is *not* a package: there is no OPC/ZIP layer, so the exact leaf is the
    /// whole source (a `DocumentExact`, RAW-like authority) and every derived
    /// observation is a bounded, span-preserving (`Q_gen`) projection of it. Detection
    /// is a **strong magic + brace-balance** test: the source must begin (after an
    /// optional UTF-8 BOM) with `{\rtf` followed by a digit, and its group braces must
    /// balance under a bounded scan (respecting `\\`/`\{`/`\}`, `\'hh`, control words,
    /// and `\binN`), with the outermost group closing exactly at end of input. A plain
    /// text source, a leading-`{` blob, and every other format are therefore never
    /// stolen; they stay `Opaque`. The adapter preserves the header, every group with
    /// its exact brace span and nesting, every control word/symbol, every `\'hh` hex
    /// escape and `\uN` Unicode escape (with its `\ucN` fallback), every destination
    /// (`\fonttbl`/`\colortbl`/`\stylesheet`/`\info`/`\pict`/`\object`/`\*`) verbatim,
    /// and every plain text run byte-exactly (never whitespace-normalized) — Phase
    /// 21.32.
    Rtf,
    /// A generic archive container (Phase 21.33): a standalone **ZIP** archive (a
    /// valid EOCD and a coherent central directory) or a standalone **TAR** archive
    /// (a valid `ustar`/old-GNU or v7 header whose checksum matches). The recorded
    /// dialect (`zip`/`tar`) lives in the model, exactly as CSV/config/feed record
    /// theirs. This is deliberately a **plain archive**, never an office package: the
    /// ZIP-family office formats (DOCX/EPUB/ODT/ODS/ODP/XLSX/PPTX) are detected
    /// **before** this adapter, so a `.docx`/`.xlsx`/`.odt` source still detects as
    /// its office format and is never reclassified. Not a package: the exact leaf is
    /// the whole source (a `DocumentExact`, RAW-like authority) and every derived
    /// observation is a bounded, span-preserving (`Q_gen`) projection of it.
    Container,
    /// A scientific container (Phase 21.34): a NetCDF classic (CDF-1/CDF-2) file or an
    /// HDF5 file (a documented bounded subset). The recorded dialect
    /// (`netcdf`/`hdf5`) lives in the model, exactly as the archive container records
    /// `zip`/`tar`. This is deliberately a **scan-only scientific format**: it is not
    /// an office package. Detection is a **strong magic-byte** test — NetCDF requires
    /// `CDF\x01`/`CDF\x02` at offset 0; HDF5 requires the 8-byte signature
    /// `\x89HDF\r\n\x1a\n` at offset 0 or 512 with a superblock version in `0..=3`, an
    /// offset/length size in `{2,4,8}`, and a superblock that fits inside the source.
    /// Not a package: the exact leaf is the whole source (a `DocumentExact`, RAW-like
    /// authority) and every derived observation is a bounded, span-preserving (`Q_gen`)
    /// projection of it. NetCDF classic is supported in full; HDF5 is a documented
    /// bounded subset (symbol-table groups + contiguous datasets + v1 object headers),
    /// and everything outside it declines **typed**.
    Hdf,
    /// A CFBF / OLE2 compound file (Phase 21.35): the physical-layer container behind
    /// legacy Word (`.doc`), Excel (`.xls`), PowerPoint (`.ppt`), and Outlook (`.msg`)
    /// files. Detection is a **strong magic + header validation** test — the 8-byte
    /// signature `D0 CF 11 E0 A1 B1 1A E1` at offset 0 **and** an internally
    /// consistent header (byte order `FFFE`, a major version of 3 or 4, a sector shift
    /// of 9 or 12 consistent with the version, a mini-sector shift of 6, at least one
    /// FAT sector, and a first directory sector inside the file). A random
    /// signature-only blob and prose stay `Opaque`. The recorded dialect
    /// (`cfb3`/`cfb4`) lives in the model. Not a package: the exact leaf is the whole
    /// source (a `DocumentExact`, RAW-like authority) and every derived observation —
    /// the header, the DIFAT/FAT, the directory entries with their verbatim UTF-16LE
    /// names and reconstructed sibling/child tree, and each stream's exact byte span —
    /// is a bounded, span-preserving (`Q_gen`) projection of it. This is the shared
    /// physical layer that unlocks the legacy DOC/XLS/PPT/MSG semantic models (later
    /// subphases).
    Cfbf,
    /// Anything else; preserved exactly by the opaque floor.
    Opaque,
}

impl DocumentFormat {
    /// Stable lower-case name (used in JSON and in the manifest provenance token).
    pub const fn name(self) -> &'static str {
        match self {
            DocumentFormat::Pdf => "pdf",
            DocumentFormat::Docx => "docx",
            DocumentFormat::Epub => "epub",
            DocumentFormat::Odt => "odt",
            DocumentFormat::Ods => "ods",
            DocumentFormat::Odp => "odp",
            DocumentFormat::Xlsx => "xlsx",
            DocumentFormat::Pptx => "pptx",
            DocumentFormat::Json => "json",
            DocumentFormat::Json5 => "json5",
            DocumentFormat::Yaml => "yaml",
            DocumentFormat::Csv => "csv",
            DocumentFormat::Markdown => "markdown",
            DocumentFormat::Xml => "xml",
            DocumentFormat::Html => "html",
            DocumentFormat::Toml => "toml",
            DocumentFormat::Jsonl => "jsonl",
            DocumentFormat::Eml => "eml",
            DocumentFormat::Parquet => "parquet",
            DocumentFormat::ArrowIpc => "arrow",
            DocumentFormat::Cbor => "cbor",
            DocumentFormat::Msgpack => "msgpack",
            DocumentFormat::Config => "config",
            DocumentFormat::Feed => "feed",
            DocumentFormat::Geojson => "geojson",
            DocumentFormat::Gis => "gis",
            DocumentFormat::Notebook => "notebook",
            DocumentFormat::FixedWidth => "fixedwidth",
            DocumentFormat::Rst => "rst",
            DocumentFormat::Asciidoc => "asciidoc",
            DocumentFormat::Mdx => "mdx",
            DocumentFormat::Mhtml => "mhtml",
            DocumentFormat::Logstream => "logstream",
            DocumentFormat::Pkgmeta => "pkgmeta",
            DocumentFormat::Apispec => "apispec",
            DocumentFormat::Bib => "bib",
            DocumentFormat::Rtf => "rtf",
            DocumentFormat::Container => "container",
            DocumentFormat::Hdf => "hdf",
            DocumentFormat::Cfbf => "cfbf",
            DocumentFormat::Opaque => "opaque",
        }
    }

    /// The adapter that serves this format (the observation layer's name for it).
    pub const fn adapter(self) -> &'static str {
        match self {
            DocumentFormat::Pdf => "pdf",
            DocumentFormat::Docx => "docx",
            DocumentFormat::Epub => "epub",
            DocumentFormat::Odt => "odt",
            DocumentFormat::Ods => "ods",
            DocumentFormat::Odp => "odp",
            DocumentFormat::Xlsx => "xlsx",
            DocumentFormat::Pptx => "pptx",
            DocumentFormat::Json => "json",
            DocumentFormat::Json5 => "json5",
            DocumentFormat::Yaml => "yaml",
            DocumentFormat::Csv => "csv",
            DocumentFormat::Markdown => "markdown",
            DocumentFormat::Xml => "xml",
            DocumentFormat::Html => "html",
            DocumentFormat::Toml => "toml",
            DocumentFormat::Jsonl => "jsonl",
            DocumentFormat::Eml => "eml",
            DocumentFormat::Parquet => "parquet",
            DocumentFormat::ArrowIpc => "arrow",
            DocumentFormat::Cbor => "cbor",
            DocumentFormat::Msgpack => "msgpack",
            DocumentFormat::Config => "config",
            DocumentFormat::Feed => "feed",
            DocumentFormat::Geojson => "geojson",
            DocumentFormat::Gis => "gis",
            DocumentFormat::Notebook => "notebook",
            DocumentFormat::FixedWidth => "fixedwidth",
            DocumentFormat::Rst => "rst",
            DocumentFormat::Asciidoc => "asciidoc",
            DocumentFormat::Mdx => "mdx",
            DocumentFormat::Mhtml => "mhtml",
            DocumentFormat::Logstream => "logstream",
            DocumentFormat::Pkgmeta => "pkgmeta",
            DocumentFormat::Apispec => "apispec",
            DocumentFormat::Bib => "bib",
            DocumentFormat::Rtf => "rtf",
            DocumentFormat::Container => "container",
            DocumentFormat::Hdf => "hdf",
            DocumentFormat::Cfbf => "cfbf",
            DocumentFormat::Opaque => "opaque",
        }
    }

    /// Whether the adapter for this format is compiled into this build.
    pub const fn compiled(self) -> bool {
        match self {
            DocumentFormat::Pdf | DocumentFormat::Opaque => true,
            DocumentFormat::Docx => cfg!(feature = "docx"),
            DocumentFormat::Epub => cfg!(feature = "epub"),
            DocumentFormat::Odt => cfg!(feature = "odt"),
            DocumentFormat::Ods => cfg!(feature = "ods"),
            DocumentFormat::Odp => cfg!(feature = "odp"),
            DocumentFormat::Xlsx => cfg!(feature = "xlsx"),
            DocumentFormat::Pptx => cfg!(feature = "pptx"),
            DocumentFormat::Json => cfg!(feature = "json"),
            DocumentFormat::Json5 => cfg!(feature = "json5"),
            DocumentFormat::Yaml => cfg!(feature = "yaml"),
            DocumentFormat::Csv => cfg!(feature = "csv"),
            DocumentFormat::Markdown => cfg!(feature = "markdown"),
            DocumentFormat::Xml => cfg!(feature = "xml"),
            DocumentFormat::Html => cfg!(feature = "html"),
            DocumentFormat::Toml => cfg!(feature = "toml"),
            DocumentFormat::Jsonl => cfg!(feature = "jsonl"),
            DocumentFormat::Eml => cfg!(feature = "eml"),
            DocumentFormat::Parquet => cfg!(feature = "parquet"),
            DocumentFormat::ArrowIpc => cfg!(feature = "arrow"),
            DocumentFormat::Cbor => cfg!(feature = "cbor"),
            DocumentFormat::Msgpack => cfg!(feature = "msgpack"),
            DocumentFormat::Config => cfg!(feature = "config"),
            DocumentFormat::Feed => cfg!(feature = "feed"),
            DocumentFormat::Geojson => cfg!(feature = "geojson"),
            DocumentFormat::Gis => cfg!(feature = "gis"),
            DocumentFormat::Notebook => cfg!(feature = "notebook"),
            DocumentFormat::FixedWidth => cfg!(feature = "fixedwidth"),
            DocumentFormat::Rst => cfg!(feature = "rst"),
            DocumentFormat::Asciidoc => cfg!(feature = "asciidoc"),
            DocumentFormat::Mdx => cfg!(feature = "mdx"),
            DocumentFormat::Mhtml => cfg!(feature = "mhtml"),
            DocumentFormat::Logstream => cfg!(feature = "logstream"),
            DocumentFormat::Pkgmeta => cfg!(feature = "pkgmeta"),
            DocumentFormat::Apispec => cfg!(feature = "apispec"),
            DocumentFormat::Bib => cfg!(feature = "bib"),
            DocumentFormat::Rtf => cfg!(feature = "rtf"),
            DocumentFormat::Container => cfg!(feature = "container"),
            DocumentFormat::Hdf => cfg!(feature = "hdf"),
            DocumentFormat::Cfbf => cfg!(feature = "cfbf"),
        }
    }

    /// The machine-readable `format=<name>;` provenance prefix recorded at ingest.
    pub fn provenance_prefix(self) -> String {
        format!("format={};", self.name())
    }

    /// Recover the recorded format from a field manifest's provenance string.
    ///
    /// Returns `None` for a manifest that does not carry the token (e.g. a field
    /// written before this subphase); such a field still serves every native
    /// selector, but common observations decline typed rather than guessing.
    pub fn from_provenance(provenance: &str) -> Option<DocumentFormat> {
        let rest = provenance.strip_prefix("format=")?;
        let name = rest.split(';').next()?;
        match name {
            "pdf" => Some(DocumentFormat::Pdf),
            "docx" => Some(DocumentFormat::Docx),
            "epub" => Some(DocumentFormat::Epub),
            "odt" => Some(DocumentFormat::Odt),
            "ods" => Some(DocumentFormat::Ods),
            "odp" => Some(DocumentFormat::Odp),
            "xlsx" => Some(DocumentFormat::Xlsx),
            "pptx" => Some(DocumentFormat::Pptx),
            "json" => Some(DocumentFormat::Json),
            "json5" => Some(DocumentFormat::Json5),
            "yaml" => Some(DocumentFormat::Yaml),
            "csv" => Some(DocumentFormat::Csv),
            "markdown" => Some(DocumentFormat::Markdown),
            "xml" => Some(DocumentFormat::Xml),
            "html" => Some(DocumentFormat::Html),
            "toml" => Some(DocumentFormat::Toml),
            "jsonl" => Some(DocumentFormat::Jsonl),
            "eml" => Some(DocumentFormat::Eml),
            "parquet" => Some(DocumentFormat::Parquet),
            "arrow" => Some(DocumentFormat::ArrowIpc),
            "cbor" => Some(DocumentFormat::Cbor),
            "msgpack" => Some(DocumentFormat::Msgpack),
            "config" => Some(DocumentFormat::Config),
            "feed" => Some(DocumentFormat::Feed),
            "geojson" => Some(DocumentFormat::Geojson),
            "gis" => Some(DocumentFormat::Gis),
            "notebook" => Some(DocumentFormat::Notebook),
            "fixedwidth" => Some(DocumentFormat::FixedWidth),
            "rst" => Some(DocumentFormat::Rst),
            "asciidoc" => Some(DocumentFormat::Asciidoc),
            "mdx" => Some(DocumentFormat::Mdx),
            "mhtml" => Some(DocumentFormat::Mhtml),
            "logstream" => Some(DocumentFormat::Logstream),
            "pkgmeta" => Some(DocumentFormat::Pkgmeta),
            "apispec" => Some(DocumentFormat::Apispec),
            "bib" => Some(DocumentFormat::Bib),
            "rtf" => Some(DocumentFormat::Rtf),
            "container" => Some(DocumentFormat::Container),
            "hdf" => Some(DocumentFormat::Hdf),
            "cfbf" => Some(DocumentFormat::Cfbf),
            "opaque" => Some(DocumentFormat::Opaque),
            _ => None,
        }
    }
}

/// Detect the document format of `source` from its bytes alone.
///
/// Never consults a file name or extension. A malformed or ambiguous input (or a
/// format whose adapter is not compiled) falls back to [`DocumentFormat::Opaque`].
pub fn detect_document_format(source: &[u8], limits: Limits) -> DocumentFormat {
    if crate::adapter::pdf::detect(source, limits) {
        return DocumentFormat::Pdf;
    }
    #[cfg(feature = "package")]
    {
        if let Some(format) = detect_zip_family(source, limits) {
            return format;
        }
    }
    // The generic archive container (Phase 21.33) is a **standalone** format, never
    // an office package: a plain ZIP (a valid EOCD + coherent central directory) or a
    // plain TAR (a strong 512-byte header whose checksum matches). It is inserted
    // **after** the entire office ZIP family above — so an office package is never
    // reclassified as a plain archive — and **before** every weak, no-magic-byte
    // heuristic (JSON/YAML/CSV/Markdown/XML/HTML), which a binary archive must never
    // be guessed to be. It is deliberately conservative: a `PK`-prefixed blob without
    // a valid EOCD, a 512-byte-junk blob whose checksum fails, and prose all stay
    // `Opaque`.
    #[cfg(feature = "container")]
    if crate::adapter::container::detect(source, limits) {
        return DocumentFormat::Container;
    }
    // The scientific container (Phase 21.34) is a **standalone** format with strong
    // magics: NetCDF classic (`CDF\x01`/`CDF\x02` at offset 0) and HDF5 (the 8-byte
    // signature at offset 0 or 512 plus a validated superblock). It is inserted with
    // the other strong-magic binaries (after the office ZIP family and the archive
    // container, before Parquet) and **before** every weak, no-magic-byte heuristic
    // (JSON/YAML/CSV/Markdown/XML/HTML), which a binary scientific file must never be
    // guessed to be. It is deliberately conservative on HDF5: a random
    // `\x89HDF`-prefixed blob with a bad superblock, and prose, stay `Opaque`.
    // NetCDF's magic is the 4-byte `CDF\x01`/`CDF\x02`; a `CDF`-prefixed blob is
    // admitted here and its derived model then declines typed if the header is
    // malformed (the whole-source exact leaf is always unaffected).
    #[cfg(feature = "hdf")]
    if crate::adapter::hdf::detect(source, limits) {
        return DocumentFormat::Hdf;
    }
    // The CFBF / OLE2 compound file (Phase 21.35) is a **standalone** physical-layer
    // format with a strong 8-byte signature (`D0 CF 11 E0 A1 B1 1A E1`) plus a
    // validated header. It is inserted with the other strong-magic binaries (after the
    // office ZIP family, the archive container, and the scientific container, and
    // **before** Parquet) and **before** every weak, no-magic-byte heuristic
    // (JSON/YAML/CSV/Markdown/XML/HTML), which a binary compound file must never be
    // guessed to be. It is deliberately conservative: a signature-only blob (its
    // header fields are zero) and prose stay `Opaque`; a blob with a plausible header
    // but a malformed body is admitted and its derived model then declines typed (the
    // whole-source exact leaf is always unaffected).
    #[cfg(feature = "cfbf")]
    if crate::adapter::cfbf::detect(source, limits) {
        return DocumentFormat::Cfbf;
    }
    // Parquet is the **analytical** Wave-2 format (Phase 21.14), with no package
    // layer. Its detection is a strong magic-byte contract — `PAR1` at both ends
    // plus a consistent little-endian footer length — so it runs immediately after
    // the package families and **before** the weak, no-magic-byte heuristics
    // (JSON/YAML/CSV/Markdown/XML/HTML), which a binary columnar file must never be
    // guessed to be. It is deliberately conservative: a file that merely begins or
    // ends with `PAR1` but whose footer length overruns the leading magic stays
    // `Opaque`.
    #[cfg(feature = "parquet")]
    if crate::adapter::parquet::detect(source, limits) {
        return DocumentFormat::Parquet;
    }
    // Arrow IPC is the **analytical** Wave-2 format (Phase 21.16), with no package
    // layer. Its detection is a strong magic-byte contract — the `ARROW1` magic at
    // the start plus either a consistent trailing footer (file format) or a valid
    // encapsulated `Schema` message (stream format) — so it runs immediately after
    // Parquet and **before** the weak, no-magic-byte heuristics, which a binary
    // columnar file must never be guessed to be. It is deliberately conservative:
    // `ARROW1`-prefixed junk with neither a consistent footer nor a valid schema
    // message stays `Opaque`.
    #[cfg(feature = "arrow")]
    if crate::adapter::arrow::detect(source, limits) {
        return DocumentFormat::ArrowIpc;
    }
    // JSON is a Wave-2 structured-tree format with **no** package layer, so it is
    // detected directly from the whole source (never through `detect_zip_family`).
    // Conservative: the entire source must parse as exactly one JSON value within
    // the JSON caps, else the input stays Opaque (Phase 21.5.1).
    //
    // GeoJSON is the **spatial** Wave-2 format (Phase 21.22), whose physical bytes
    // are JSON, so it is a **bounded semantic sub-detection** run **before** the
    // generic JSON detector below: the source must parse as exactly one JSON value
    // whose root is an object with a `"type"` string equal to one of the nine RFC 7946
    // type names **and** whose shape is consistent (a geometry has an array
    // `coordinates`/`geometries`, a `Feature` has `geometry`/`properties`, a
    // `FeatureCollection` has an array `features`). A more specific claim than a bare
    // JSON value, so it is tried first; a plain JSON document, and a JSON document
    // whose `"type"` is an unrelated string, decline here and are then claimed by the
    // JSON detector (staying `Json`). Prose and malformed input stay `Opaque`.
    #[cfg(feature = "geojson")]
    if crate::adapter::geojson::detect(source, limits) {
        return DocumentFormat::Geojson;
    }
    // A Jupyter notebook (`.ipynb`, nbformat) is the **document-shaped** Wave-2 format
    // (Phase 21.24), whose physical bytes are JSON, so it is a **bounded semantic
    // sub-detection** run **before** the generic JSON detector below: the source must
    // parse as exactly one JSON value that is an nbformat-shaped object — a plain
    // non-negative integer-literal `nbformat` (≥ 1), an array `cells`, every cell an
    // object with a string `cell_type`, and every present recognized field
    // nbformat-shaped (a string-or-array-of-strings `source`, a number-or-null
    // `execution_count`, object `metadata`/`attachments`, an array `outputs` of objects
    // with a string `output_type`). A more specific claim than a bare JSON value, so it
    // is tried first; a plain JSON document, and a JSON document that merely has a
    // `cells` key but is not nbformat-shaped, decline here and are then claimed by the
    // JSON detector (staying `Json`). Prose and malformed input stay `Opaque`.
    #[cfg(feature = "notebook")]
    if crate::adapter::notebook::detect(source, limits) {
        return DocumentFormat::Notebook;
    }
    // A package-metadata manifest (Phase 21.29) — an npm `package.json` /
    // `package-lock.json`, a Cargo `Cargo.toml` / `Cargo.lock`, or a Python
    // `pyproject.toml` — is the **manifest** Wave-2 format, whose physical bytes are
    // JSON or TOML. It is a **bounded semantic sub-detection** run **before** the
    // generic JSON and TOML detectors below: the source must carry a strong manifest
    // shape (a JSON object with string `name`+`version` and a third package-specific
    // key, a JSON `lockfileVersion`+`packages`/`dependencies`, a TOML `[package]` table
    // with a string `name`, a TOML `[build-system]`/`[project]`/`[tool.poetry]`, or a
    // TOML `[[package]]` lockfile shape). A more specific claim than a bare JSON/TOML
    // value, so it is tried first; a generic JSON `{"name":…,"version":…}`, a generic
    // TOML `name = …` / `version = …`, and plain prose decline here and are then
    // claimed by the JSON detector (staying `Json`), the TOML detector (staying
    // `Toml`), or the opaque floor. Detection is content-only and never consults a
    // file name.
    #[cfg(feature = "pkgmeta")]
    if crate::adapter::pkgmeta::detect(source, limits) {
        return DocumentFormat::Pkgmeta;
    }
    // An API-specification document (Phase 21.30) — a JSON Schema, an OpenAPI 3.x, a
    // Swagger 2.0, or an AsyncAPI document — is the **API/spec** Wave-2 format, whose
    // physical bytes are JSON. It is a **bounded semantic sub-detection** run **before**
    // the generic JSON detector below: the root must be a JSON object carrying a
    // **strong, root-level string marker** — a JSON-Schema `$schema` URI, an `openapi`
    // `3.`-prefixed string, a `swagger` equal to `"2.0"`, or a non-empty `asyncapi`
    // string. A more specific claim than a bare JSON value, so it is tried first; a
    // plain JSON object that merely has a `properties`/`paths` key, a JSON-Schema-shaped
    // object with **no** `$schema`, an unrelated `openapi`/`swagger` value, and prose
    // decline here and are then claimed by the JSON detector (staying `Json`) or the
    // opaque floor. Detection is content-only and never consults a file name.
    #[cfg(feature = "apispec")]
    if crate::adapter::apispec::detect(source, limits) {
        return DocumentFormat::Apispec;
    }
    // A bibliography (Phase 21.31) — BibTeX, RIS, or CSL-JSON — is the
    // **bibliography** Wave-2 format. BibTeX/RIS are line-anchored text and CSL-JSON's
    // physical bytes are JSON, so this is a **bounded content-only test** run **before**
    // the generic JSON detector below (a CSL-JSON array is otherwise claimed by the JSON
    // detector, which is the weaker claim) and **before** the weak text heuristics
    // (YAML/TOML/config/CSV/Markdown/RST/AsciiDoc/logstream/fixedwidth), which a BibTeX
    // `@type{…}` file or an RIS `TY  - ` stream must never be guessed to be. It claims
    // only on a strong signal: a line-anchored `@type{` entry sequence, an RIS `TY  - `
    // entry sequence whose every entry is `ER  - `-terminated, or a JSON array (or
    // single object) whose every element carries a string `id` **and** a string `type`.
    // A plain JSON array, a JSON array of non-CSL objects, a generic JSON object, and
    // prose decline here and are then claimed by the JSON detector (staying `Json`) or
    // the opaque floor. Detection is content-only and never consults a file name.
    #[cfg(feature = "bib")]
    if crate::adapter::bib::detect(source, limits) {
        return DocumentFormat::Bib;
    }
    // RTF (Rich Text Format, Phase 21.32) is a text group-brace grammar with a strong
    // header magic (`{\rtf<digit>`), so it is tried here with the **strong-magic**
    // detectors (right after the binary magics PDF/ZIP/Parquet/Arrow and before the
    // JSON family). Its detection is magic + a bounded brace-balance scan, so no other
    // format's bytes can steal it and it can steal no other format: an RTF source
    // begins with `{`, which is not a valid JSON document (JSON would need a valid
    // value), not a config `KEY=VALUE` line, and not a Markdown/RST structural mark;
    // and the binary detectors above reject it. A plain text or leading-`{` blob stays
    // `Opaque`.
    #[cfg(feature = "rtf")]
    if crate::adapter::rtf::detect(source, limits) {
        return DocumentFormat::Rtf;
    }
    #[cfg(feature = "json")]
    if crate::adapter::json::detect(source, limits) {
        return DocumentFormat::Json;
    }
    // JSON5/JSONC is the **structured-extra** Wave-2 format (Phase 21.17.1), also
    // with no package layer. It is tried **immediately after JSON** and before
    // JSONL/YAML/TOML/CSV/Markdown/XML/HTML: JSON5 is a superset of JSON, so a
    // strict JSON source is already claimed by the JSON detector above, and this
    // detector requires at least one JSON5/JSONC-only construct (a comment, a
    // trailing comma, an unquoted key, a single quote, a hex/leading-dot/
    // `Infinity`/`NaN` number, a string continuation, or the extended whitespace
    // set). A strict JSON document is therefore never reclassified, and a plain
    // non-JSON blob stays Opaque.
    #[cfg(feature = "json5")]
    if crate::adapter::json5::detect(source, limits) {
        return DocumentFormat::Json5;
    }
    // JSONL/NDJSON is the **line/event-stream** Wave-2 format (Phase 21.12), also
    // with no package layer. It is tried **immediately after JSON** because it is the
    // most specific signal for a newline-separated JSON stream: each non-blank line
    // must parse as exactly one JSON value, which is stricter than — and must run
    // before — the YAML stream reader (which would otherwise be free to reinterpret a
    // line-delimited JSON stream as a stream of documents). Detection is
    // conservative: at least two non-blank lines are required and every non-blank
    // line must parse as exactly one JSON value under the caps, so a single JSON
    // value, a malformed line, and a bag of values with a non-newline separator all
    // stay `Opaque`.
    #[cfg(feature = "jsonl")]
    if crate::adapter::jsonl::detect(source, limits) {
        return DocumentFormat::Jsonl;
    }
    // CBOR is the **binary** structured-tree Wave-2 format (Phase 21.18), also with
    // no package layer. It is tried **immediately after the JSON family** (JSON,
    // JSON5, JSONL) and **before** the remaining textual heuristics
    // (EML/YAML/TOML/CSV/Markdown/XML/HTML): the strong magic-byte binaries
    // (PDF/ZIP/Parquet/Arrow) run above and are never reconsidered. CBOR has **no
    // magic bytes**, so its detector is deliberately conservative — the
    // self-described-CBOR tag `55799` (`0xd9 0xd9 0xf7`), or a full-input,
    // well-formed parse whose root is a container/tag and which reaches at least
    // three nodes. A container head byte is always `>= 0x80`, so no pure-ASCII
    // document (JSON/YAML/TOML/XML/HTML/prose) can be claimed; a lone scalar, an
    // empty container, a truncated item, and a structurally trivial input all stay
    // `Opaque` rather than being guessed. The small-integer/short-array encodings
    // overlap MessagePack's fixint/fixarray (recorded honestly; a Phase-21.19
    // MessagePack adapter must share this seam).
    #[cfg(feature = "cbor")]
    if crate::adapter::cbor::detect(source, limits) {
        return DocumentFormat::Cbor;
    }
    // MessagePack is the **binary** structured-tree Wave-2 sibling of CBOR (Phase
    // 21.19), also with no package layer. It is tried **immediately after CBOR** and
    // **before** the remaining textual heuristics (EML/YAML/TOML/CSV/Markdown/XML/
    // HTML): the strong magic-byte binaries (PDF/ZIP/Parquet/Arrow) and the JSON
    // family run above and are never reconsidered. **Placement is load-bearing: the
    // two binary detectors share the whole-number/short-container prefix, so putting
    // CBOR first guarantees its stronger self-described-tag signal (and every input
    // well-formed under both grammars) is never stolen from it.** MessagePack has
    // **no magic bytes**, so its detector is deliberately conservative — a
    // full-input, well-formed parse whose root is a container (array/map) reaching at
    // least three items and eight bytes, or the same with an unambiguous
    // MessagePack-only head byte (`0xdc..=0xdf`, which CBOR's grammar rejects). A
    // container head byte is always `>= 0x80`, so no pure-ASCII document can be
    // claimed; a lone scalar, an empty container, the ambiguous
    // `fixarray(3)`/`fixmap(2)` overlap fixtures, and a structurally trivial input
    // all stay `Opaque` rather than being guessed.
    #[cfg(feature = "msgpack")]
    if crate::adapter::msgpack::detect(source, limits) {
        return DocumentFormat::Msgpack;
    }
    // MHTML (MIME HTML, RFC 2557) is the **web-archive** Wave-2 format (Phase
    // 21.27), whose physical bytes are a MIME `multipart/related` message. It is a
    // **bounded semantic sub-detection** run **before** the generic EML detector
    // below (a `multipart/related` message is otherwise claimed by EML, which is a
    // weaker claim): the root must be `multipart/related` **and** carry an
    // MHTML-specific signal — a top-level `Snapshot-Content-Location`/`Content-Base`,
    // or a `From`+`Subject` envelope with a `text/html` part. A plain
    // `multipart/related` email without those markers, a plain HTML document (no MIME
    // envelope), and a plain MIME message (not `multipart/related`) all decline here
    // and are then claimed by their own detectors (`Eml`/`Html`/`Eml`).
    #[cfg(feature = "mhtml")]
    if crate::adapter::mhtml::detect(source, limits) {
        return DocumentFormat::Mhtml;
    }
    // EML/MIME is the **messaging** Wave-2 format (Phase 21.13), also with no package
    // layer. It is tried **immediately after JSONL** and **before YAML/TOML/CSV/
    // Markdown/XML/HTML**: a raw message begins with an RFC 5322 header block, which
    // the YAML stream reader would otherwise reinterpret as a mapping (e.g. `From: a`
    // / `Date: b`). Detection is conservative: a genuine header block terminated by a
    // blank line carrying `From`/`Date`/`Message-ID`, or an explicit `MIME-Version`,
    // is required, so prose without a header block and a colon-bearing note stay
    // `Opaque`. Only the header block is scanned (never the MIME tree).
    #[cfg(feature = "eml")]
    if crate::adapter::eml::detect(source, limits) {
        return DocumentFormat::Eml;
    }
    // YAML is the second Wave-2 structured-tree format (Phase 21.6.1), also with
    // **no** package layer. It is detected directly and conservatively: the whole
    // source must parse as a bounded YAML stream under the YAML caps, and every
    // document root must be a mapping or a sequence, else the input stays Opaque.
    #[cfg(feature = "yaml")]
    if crate::adapter::yaml::detect(source, limits) {
        return DocumentFormat::Yaml;
    }
    // TOML is the next Wave-2 structured-tree format (Phase 21.11). Precedence
    // (explicit): TOML is tried **after** the strong, fully-parsed tree formats
    // (JSON, YAML) but **before** the weak, no-magic-byte heuristics (CSV,
    // Markdown, XML, HTML). TOML's positive signal is a complete, error-free parse,
    // which is far stronger than the CSV/Markdown heuristics; crucially, a TOML
    // comment (`# …` at column 0) is otherwise misread as a Markdown ATX heading and
    // would steal the document. A source that is valid JSON/YAML still wins (it is
    // tried first); a source that is valid XML/HTML never parses as TOML (it begins
    // with `<`), so nothing is lost there. TOML has no magic bytes and a plain prose
    // paragraph is not TOML, so detection is conservative: the whole source must
    // parse as TOML 1.0 under the TOML caps with no errors **and** carry at least one
    // key/value assignment. TOML's duplicate-key/redefinition rules are enforced, so
    // a doc that violates them is not detected. Plain prose and non-TOML text stay
    // Opaque.
    #[cfg(feature = "toml")]
    if crate::adapter::toml::detect(source, limits) {
        return DocumentFormat::Toml;
    }
    // The config family (INI / `.env` / Java `.properties`) is the key/value line
    // Wave-2 format (Phase 21.20). Precedence (explicit): it is tried **after** the
    // strong, fully-parsed tree formats (JSON/JSON5/JSONL/YAML/TOML) — a TOML
    // document is very often a syntactically valid INI file, so a source that is
    // valid TOML must stay `Toml` (never reclassified) — and **before** CSV/Markdown/
    // XML/HTML. It precedes CSV deliberately: a config file whose values contain
    // commas (e.g. `A=x,y`) has a consistent field count and would otherwise be
    // stolen by the CSV detector, whereas the config `KEY=VALUE` shape is the more
    // specific signal. Config has **no magic bytes**, so detection is deliberately
    // conservative and requires a **dialect-distinguishing signal**: an INI
    // `[section]` header; a properties `:`/whitespace separator, `!` comment,
    // `\uXXXX` escape, continuation, or non-identifier key (plus a strong `=`/`:`
    // separator somewhere); or an `env` `export ` prefix. The pure `KEY=VALUE`
    // overlap between `env` and `properties`, plain prose, a two-column `a b`
    // blob, and a `#!` script all stay `Opaque` rather than being guessed; a plain
    // `.txt`/Markdown/code source is never claimed.
    #[cfg(feature = "config")]
    if crate::adapter::config::detect(source, limits) {
        return DocumentFormat::Config;
    }
    // CSV/TSV is the first **tabular** Wave-2 format (Phase 21.7.1). It has no
    // magic bytes, so it is detected last and conservatively: only after the
    // PDF/ZIP/JSON/YAML families are declined does a source qualify, and then only
    // if it parses under a specific delimiter (`,`, tab, or `|`) as a table with a
    // consistent field count across a sampled majority of records and at least two
    // columns. Phase 21.25 adds the pipe delimiter to this dialect set; the pipe
    // dialect is tried after comma/tab and is declined when the source carries a
    // GFM/Markdown delimiter row (so a Markdown table is never stolen). When in
    // doubt the input stays Opaque.
    #[cfg(feature = "csv")]
    if crate::adapter::csv::detect(source, limits) {
        return DocumentFormat::Csv;
    }
    // MDX (Phase 21.26.3) is Markdown + JSX/ESM, so it is tried **immediately before
    // Markdown** (and therefore before HTML/XML and the other prose/tabular
    // heuristics): an MDX document's bytes are Markdown-plus-JSX, so generic Markdown
    // must never claim it first. Detection is conservative and **MDX-specific** — the
    // source must parse under the Markdown model **and** carry a top-level ESM
    // `import`/`export` statement, a JSX component (a capitalized/namespaced element
    // name) or fragment, a JSX-specific attribute (a brace value or spread), or a
    // whole-line MDX block expression `{…}` — and it declines anything already
    // claimed by JSON/YAML/CSV/HTML/XML/RST/AsciiDoc, so a plain Markdown document
    // stays `Markdown`, a plain HTML document stays `Html`, and plain prose stays
    // `Opaque`.
    #[cfg(feature = "mdx")]
    if crate::adapter::mdx::detect(source, limits) {
        return DocumentFormat::Mdx;
    }
    // Markdown is the first **prose** Wave-2 format (Phase 21.8.1). It has no
    // magic bytes either, and a plain prose paragraph is itself valid Markdown, so
    // detection is deliberately conservative: only after the PDF/ZIP/JSON/YAML/CSV
    // families are declined, and only when the source carries a clear structural
    // mark (an ATX heading, a fenced code block, front matter, a table, a reference
    // definition, or a footnote definition), is it admitted. Plain prose stays
    // Opaque rather than being guessed to be Markdown.
    #[cfg(feature = "markdown")]
    if crate::adapter::markdown::detect(source, limits) {
        return DocumentFormat::Markdown;
    }
    // reStructuredText (Phase 21.26.1) is the **next prose** Wave-2 format, also with
    // no package layer. It is tried **immediately after Markdown** and **before
    // fixed-width**: Markdown must be tried first so a Markdown document is never
    // stolen (reST detection requires a reST-**specific** signal — an explicit markup
    // start, a grid/simple table, a field list, or an adornment whose char is not a
    // Markdown construct), and a reST document must be claimed before the maximally
    // ambiguous fixed-width heuristic (which could otherwise read an aligned reST body
    // as a column-position table). Plain prose stays Opaque.
    #[cfg(feature = "rst")]
    if crate::adapter::rst::detect(source, limits) {
        return DocumentFormat::Rst;
    }
    // AsciiDoc (Phase 21.26.2) is the **third prose** Wave-2 format, also with no
    // package layer. It is tried **immediately after reStructuredText** and **before
    // fixed-width**: Markdown and reST must be tried first so neither is stolen (a
    // valid Markdown or reST document always keeps its own format), and AsciiDoc must
    // be claimed before the maximally ambiguous fixed-width heuristic (which could
    // otherwise read an aligned AsciiDoc body as a column-position table). The
    // dispatcher already tries PDF/ZIP/JSON/YAML/config/CSV ahead of it, so a
    // `:name: value`-shaped config source and a `#`/fence-bearing Markdown source are
    // claimed earlier. Detection is conservative and AsciiDoc-specific (a document
    // title followed by a block, a `==`+ section, a `|===` table, a complete
    // delimited block, or a block attribute line followed by a block); a lone `= text`
    // line and plain prose stay Opaque.
    #[cfg(feature = "asciidoc")]
    if crate::adapter::asciidoc::detect(source, limits) {
        return DocumentFormat::Asciidoc;
    }
    // A syslog / log stream is the **line/event-stream** Wave-2 format (Phase 21.28),
    // also with no package layer. Precedence (explicit): it is tried **after** every
    // structured and prose format above — the JSON family (JSON/JSON5/JSONL/CBOR/
    // Msgpack), MHTML/EML, YAML/TOML, the config family, CSV, and the prose family
    // (MDX/Markdown/RST/AsciiDoc) — so a JSON/YAML/CSV/prose document is never stolen;
    // and **before** the maximally ambiguous fixed-width heuristic, which could
    // otherwise read a column-aligned log stream as a column-position table. It is
    // tried before the XML/HTML family too, but a log line never begins with a valid
    // XML/HTML root (a syslog `<PRI>` is not well-formed XML). Detection is byte-based
    // and conservative: at least two non-blank lines, and **every** non-blank line must
    // match a dialect — an RFC 5424 syslog header, an RFC 3164 (BSD) syslog line, or a
    // generic application log line with a leading timestamp and/or level token. Plain
    // prose and code stay `Opaque` (a prose line that does not begin with a timestamp/
    // level declines the whole file; a file whose every line begins with a level word
    // IS claimed — recorded honestly in the adapter docs).
    #[cfg(feature = "logstream")]
    if crate::adapter::logstream::detect(source, limits) {
        return DocumentFormat::Logstream;
    }
    // Fixed-width (column-position) text is the second **tabular** Wave-2 format
    // (Phase 21.25), but its columns are defined by character positions, not a
    // delimiter, so it is a distinct format (and adapter), never a CSV dialect. It is
    // tried **last among the tabular/prose heuristics** — after CSV/TSV/PSV, after
    // Markdown, and after reStructuredText — so a delimited table, a Markdown table, or
    // a reST document always keeps its own format (the fixed-width detector also
    // declines a delimited or Markdown table explicitly, defence in depth).
    // Fixed-width is **genuinely ambiguous** (nearly any aligned text can look
    // tabular), so detection is maximally conservative: at least three sampled records
    // of identical byte width, an inferred column layout whose interior whitespace gaps
    // are at least two columns wide, and at least two non-empty columns; a
    // variable-length or single-space-separated blob stays Opaque.
    #[cfg(feature = "fixedwidth")]
    if crate::adapter::fixedwidth::detect(source, limits) {
        return DocumentFormat::FixedWidth;
    }
    // RSS/Atom is the **syndication** Wave-2 format (Phase 21.21). A feed's physical
    // bytes are XML, so this is a **bounded semantic sub-detection** run **before**
    // the generic standalone-XML detector below: an RSS root `<rss>` with a
    // `<channel>` child, or an Atom root `<feed>` in the Atom namespace
    // (`http://www.w3.org/2005/Atom`) with at least one `<entry>` child. A feed is a
    // more specific claim than a bare XML tree, so it is tried first; a plain XML
    // document (a root that is neither `<rss>` nor an Atom `<feed>`, or an
    // `<rss>`-shaped-but-invalid / non-Atom / record-less `<feed>`) declines here and
    // falls through to the XML detector (staying `Xml`) or to `Opaque`. An HTML
    // document (`<!doctype html>` / an `<html>` root) simply declines here and is
    // then claimed by the HTML document-level marker immediately below.
    #[cfg(feature = "feed")]
    if crate::adapter::feed::detect(source, limits) {
        return DocumentFormat::Feed;
    }
    // KML/GPX is the **geospatial** Wave-2 format (Phase 21.23). Its physical bytes
    // are XML, so this is a **bounded semantic sub-detection** run **before** the
    // generic standalone-XML detector below: a `<kml>` root in the KML namespace
    // (`http://www.opengis.net/kml/2.2`) with a `Document`/`Folder`/`Placemark` child,
    // or a `<gpx>` root in the GPX namespace (`http://www.topografix.com/GPX/1/1`)
    // with a `metadata`/`wpt`/`rte`/`trk` child. A geospatial document is a more
    // specific claim than a bare XML tree, so it is tried first; a plain XML document
    // (a root that is neither `<kml>` nor `<gpx>`, or a shaped-but-invalid / non-GIS
    // namespace / child-less root) declines here and falls through to the XML detector
    // (staying `Xml`) or to `Opaque`. An older KML (2.0/2.1) or GPX 1.0 namespace is
    // not the claimed URI, so it also stays `Xml`. An HTML document simply declines
    // here and is then claimed by the HTML document-level marker immediately below.
    #[cfg(feature = "gis")]
    if crate::adapter::gis::detect(source, limits) {
        return DocumentFormat::Gis;
    }
    // HTML document-level marker (Phase 21.10 / 21.15 fix): a `<!doctype html>` or
    // an `<html>` root element is a more specific signal than the generic XML
    // fallback, so a **well-formed XHTML page** is classified `Html`, not `Xml`
    // (XML's well-formedness parser would otherwise claim it). The general XML
    // branch below is still tried before the general HTML branch, so a bare XML
    // tree stays XML and HTML only claims the sources XML declines.
    #[cfg(feature = "html")]
    if crate::adapter::html::has_document_marker(source)
        && crate::adapter::html::detect(source, limits)
    {
        return DocumentFormat::Html;
    }
    // XML is the structured-tree Wave-2 format for a bare XML source (Phase 21.9).
    // It has no package layer and no magic bytes beyond `<`, so it is detected last
    // and conservatively: only after the PDF/ZIP/JSON/YAML/CSV/Markdown families are
    // declined, only when the source begins with `<` (an XML prolog or a root
    // element start), and only when the whole source parses as well-formed XML with
    // exactly one root element under the XML caps. `<-prefixed` junk and prose stay
    // Opaque rather than being guessed to be XML.
    #[cfg(feature = "xml")]
    if crate::adapter::xml::detect(source, limits) {
        return DocumentFormat::Xml;
    }
    // HTML is the error-recovering markup Wave-2 format (Phase 21.10). Precedence
    // note (explicit): a *document-level* HTML marker is handled above (before XML);
    // for every other `<`-bearing source XML is tried before HTML, so any document
    // XML accepts (a bare XML tree) is genuinely XML. HTML then claims the
    // `<`-bearing sources XML declines — real HTML5 is almost never well-formed XML
    // (void elements, optional end tags, unquoted attributes, undeclared named
    // entities). Detection is conservative: the source must carry clear HTML
    // structure (a `<!doctype html>`, an `<html`/`<head`/`<body` tag, or a
    // preponderance of known HTML tags). Plain prose and non-HTML `<`-junk stay
    // Opaque rather than being guessed to be HTML.
    #[cfg(feature = "html")]
    if crate::adapter::html::detect(source, limits) {
        return DocumentFormat::Html;
    }
    DocumentFormat::Opaque
}

/// Whether `source` is a structurally valid ZIP archive.
///
/// Used by the universal ingest dispatcher so a generic ZIP (which detection
/// reports as `Opaque`) is still inverted through the byte-authoritative package
/// layer rather than the opaque floor.
#[cfg(feature = "package")]
pub fn is_zip(source: &[u8], limits: Limits) -> bool {
    crate::adapter::package::scan(source, limits).is_ok()
}

#[cfg(feature = "package")]
fn detect_zip_family(source: &[u8], limits: Limits) -> Option<DocumentFormat> {
    let physical = crate::adapter::package::scan(source, limits).ok()?;

    // EPUB (OCF): the mandatory `mimetype` member, or an OCF container that
    // resolves to an OPF rootfile.
    let mimetype = member_decoded(&physical, source, EPUB_MIMETYPE_MEMBER, limits);
    let mimetype_ok = mimetype.as_deref() == Some(EPUB_MIMETYPE);
    let container = member_decoded(&physical, source, CONTAINER_MEMBER, limits);
    let container_ok = container
        .as_deref()
        .is_some_and(|c| contains(c, CONTAINER_NS) && contains(c, OPF_MEDIA_TYPE));
    let is_epub = mimetype_ok || container_ok;

    // DOCX: an OPC package whose content types declare a WordprocessingML main
    // part, or whose package relationships declare an `officeDocument` part that
    // targets `word/document.xml`. The positive WordprocessingML signal (rather
    // than the mere absence of a SpreadsheetML one) keeps DOCX and XLSX mutually
    // exclusive without misclassifying a Word document that *embeds* an Excel
    // workbook (whose package declares SpreadsheetML content types for the
    // embedded part, but no SpreadsheetML workbook main part). Without `xlsx`
    // and `pptx` the legacy relationship-only rule stands.
    let content_types = member_decoded(&physical, source, CONTENT_TYPES_MEMBER, limits);
    let rels = member_decoded(&physical, source, PACKAGE_RELS_MEMBER, limits);
    #[cfg(any(feature = "xlsx", feature = "pptx"))]
    let is_docx = content_types
        .as_deref()
        .is_some_and(|ct| contains(ct, DOCX_MAIN_FRAGMENT))
        || rels.as_deref().is_some_and(|r| {
            contains(r, OFFICE_DOCUMENT_FRAGMENT) && contains(r, DOCX_MAIN_TARGET)
        });
    #[cfg(not(any(feature = "xlsx", feature = "pptx")))]
    let is_docx = content_types.is_some()
        && rels
            .as_deref()
            .is_some_and(|r| contains(r, OFFICE_DOCUMENT_FRAGMENT));

    // XLSX: an OPC package whose content types declare a SpreadsheetML workbook
    // (or whose `officeDocument` relationship targets a workbook part).
    #[cfg(feature = "xlsx")]
    let is_xlsx = content_types.as_deref().is_some_and(|ct| {
        contains(ct, XLSX_MAIN_FRAGMENT)
            || (contains(ct, XLSX_NS_FRAGMENT)
                && rels
                    .as_deref()
                    .is_some_and(|r| contains(r, XLSX_WORKBOOK_TARGET)))
    });
    #[cfg(not(feature = "xlsx"))]
    let is_xlsx = false;

    // PPTX: an OPC package whose content types declare a PresentationML main part
    // (or whose content types name PresentationML and whose `officeDocument`
    // relationship targets `ppt/presentation.xml`). The positive PresentationML
    // signal keeps it mutually exclusive with DOCX and XLSX: a Word/Excel document
    // that *embeds* a PowerPoint part declares only the PresentationML embed type
    // (`…presentationml.presentation`, not the `.main+xml` main part) and its
    // `officeDocument` relationship targets `word/document.xml`/`xl/workbook.xml`,
    // so it is never misclassified as PPTX.
    #[cfg(feature = "pptx")]
    let is_pptx = content_types.as_deref().is_some_and(|ct| {
        contains(ct, PPTX_MAIN_FRAGMENT)
            || (contains(ct, PPTX_NS_FRAGMENT)
                && rels
                    .as_deref()
                    .is_some_and(|r| contains(r, PPTX_MAIN_TARGET)))
    });
    #[cfg(not(feature = "pptx"))]
    let is_pptx = false;

    // The mandatory ODF `mimetype` member is **authoritative** when it names an
    // OpenDocument media type (ODF 1.2 §3.2: the package's own media type). An ODP
    // that *embeds a spreadsheet* lists both media types in `META-INF/manifest.xml`
    // (one per file-entry), so scanning the whole manifest would make it look like an
    // ODP *and* an ODS and force the ambiguous case below to Opaque. Only when the
    // `mimetype` member is absent or is not an ODF media type do we fall back to the
    // manifest scan (for a non-conformant package).
    #[cfg(any(feature = "odt", feature = "ods", feature = "odp"))]
    let odf_mimetype = mimetype
        .as_deref()
        .filter(|m| contains(m, ODF_MEDIA_PREFIX));

    // ODT: an ODF package whose mandatory `mimetype` (or, absent that,
    // `META-INF/manifest.xml`) declares an OpenDocument text media type.
    #[cfg(feature = "odt")]
    let is_odt = match odf_mimetype {
        Some(m) => contains(m, ODT_TEXT_FRAGMENT),
        None => member_decoded(&physical, source, ODT_MANIFEST_MEMBER, limits)
            .as_deref()
            .is_some_and(|m| contains(m, ODT_TEXT_FRAGMENT)),
    };
    #[cfg(not(feature = "odt"))]
    let is_odt = false;

    // ODS: an ODF package whose mandatory `mimetype` (or, absent that, the manifest)
    // declares an OpenDocument *spreadsheet* media type. The positive spreadsheet
    // fragment keeps it mutually exclusive with ODT (a text document declares the
    // text media type, never the spreadsheet one).
    #[cfg(feature = "ods")]
    let is_ods = match odf_mimetype {
        Some(m) => contains(m, ODS_SPREADSHEET_FRAGMENT),
        None => member_decoded(&physical, source, ODS_MANIFEST_MEMBER, limits)
            .as_deref()
            .is_some_and(|m| contains(m, ODS_SPREADSHEET_FRAGMENT)),
    };
    #[cfg(not(feature = "ods"))]
    let is_ods = false;

    // ODP: an ODF package whose mandatory `mimetype` (or, absent that, the manifest)
    // declares an OpenDocument *presentation* media type. The positive presentation
    // fragment keeps it mutually exclusive with ODT/ODS.
    #[cfg(feature = "odp")]
    let is_odp = match odf_mimetype {
        Some(m) => contains(m, ODP_PRESENTATION_FRAGMENT),
        None => member_decoded(&physical, source, ODP_MANIFEST_MEMBER, limits)
            .as_deref()
            .is_some_and(|m| contains(m, ODP_PRESENTATION_FRAGMENT)),
    };
    #[cfg(not(feature = "odp"))]
    let is_odp = false;

    // A ZIP matching more than one native signature is ambiguous: fail safe.
    let matches = [is_docx, is_epub, is_odt, is_ods, is_odp, is_xlsx, is_pptx]
        .iter()
        .filter(|b| **b)
        .count();
    match matches {
        1 if is_docx => Some(DocumentFormat::Docx),
        1 if is_epub => Some(DocumentFormat::Epub),
        1 if is_odt => Some(DocumentFormat::Odt),
        1 if is_ods => Some(DocumentFormat::Ods),
        1 if is_odp => Some(DocumentFormat::Odp),
        1 if is_xlsx => Some(DocumentFormat::Xlsx),
        1 if is_pptx => Some(DocumentFormat::Pptx),
        _ => None,
    }
}

#[cfg(feature = "package")]
const EPUB_MIMETYPE_MEMBER: &[u8] = b"mimetype";

/// Decode one member's bytes by exact name, bounded and decline-safe: encrypted,
/// oversized, unsupported-method, or out-of-range members yield `None`. The name
/// match is ASCII case-insensitive, mirroring the OPC layer
/// ([`crate::field::opc`]), which resolves the well-known control parts
/// (`[Content_Types].xml`, `_rels/.rels`) case-insensitively; a package that
/// lowercases them is still identified (Phase 21.15 fix).
#[cfg(feature = "package")]
fn member_decoded(
    physical: &crate::adapter::package::ZipPhysical,
    source: &[u8],
    name: &[u8],
    limits: Limits,
) -> Option<Vec<u8>> {
    const FLAG_ENCRYPTED: u16 = 0x0001;
    let member = physical
        .members
        .iter()
        .find(|m| m.name.eq_ignore_ascii_case(name))?;
    if member.flags & FLAG_ENCRYPTED != 0 || member.uncompressed_size > limits.max_xml_part_bytes {
        return None;
    }
    let off = usize::try_from(member.data.0).ok()?;
    let len = usize::try_from(member.data.1).ok()?;
    let raw = source.get(off..off.checked_add(len)?)?;
    match member.method {
        0 => Some(raw.to_vec()),
        8 => crate::field::derive::inflate_raw_deflate(raw, member.uncompressed_size, limits).ok(),
        _ => None,
    }
}

/// Byte-substring search (no allocation, case-sensitive).
#[cfg(feature = "package")]
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
    !needle.is_empty()
        && needle.len() <= haystack.len()
        && haystack.windows(needle.len()).any(|w| w == needle)
}

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

    #[test]
    fn names_and_provenance_roundtrip() {
        for f in [
            DocumentFormat::Pdf,
            DocumentFormat::Docx,
            DocumentFormat::Epub,
            DocumentFormat::Odt,
            DocumentFormat::Ods,
            DocumentFormat::Odp,
            DocumentFormat::Xlsx,
            DocumentFormat::Pptx,
            DocumentFormat::Json,
            DocumentFormat::Json5,
            DocumentFormat::Yaml,
            DocumentFormat::Csv,
            DocumentFormat::Markdown,
            DocumentFormat::Xml,
            DocumentFormat::Html,
            DocumentFormat::Toml,
            DocumentFormat::Jsonl,
            DocumentFormat::Eml,
            DocumentFormat::Parquet,
            DocumentFormat::ArrowIpc,
            DocumentFormat::Cbor,
            DocumentFormat::Msgpack,
            DocumentFormat::Config,
            DocumentFormat::Feed,
            DocumentFormat::Geojson,
            DocumentFormat::Gis,
            DocumentFormat::Notebook,
            DocumentFormat::FixedWidth,
            DocumentFormat::Rst,
            DocumentFormat::Asciidoc,
            DocumentFormat::Mdx,
            DocumentFormat::Mhtml,
            DocumentFormat::Logstream,
            DocumentFormat::Pkgmeta,
            DocumentFormat::Apispec,
            DocumentFormat::Bib,
            DocumentFormat::Rtf,
            DocumentFormat::Opaque,
        ] {
            let token = format!("{}field:package;members=1", f.provenance_prefix());
            assert_eq!(DocumentFormat::from_provenance(&token), Some(f));
        }
        assert_eq!(DocumentFormat::from_provenance("field:ingest-b"), None);
        assert_eq!(DocumentFormat::from_provenance("format=exotic;x"), None);
    }

    #[test]
    fn plain_bytes_are_opaque() {
        assert_eq!(
            detect_document_format(b"not a document", Limits::DEFAULT),
            DocumentFormat::Opaque
        );
    }

    #[test]
    fn a_corpus_pdf_is_detected() {
        let (_, pdf) = crate::adapter::pdf::sample_pdfs()
            .into_iter()
            .next()
            .expect("the PDF corpus is non-empty");
        assert_eq!(
            detect_document_format(&pdf, Limits::DEFAULT),
            DocumentFormat::Pdf
        );
    }
}