Ballyhoo binary formats in F* — status as of 2026-04-19#

Originally titled "HDT in F* — status"; scope widened 2026-04-19 to cover the full ballyhoo binary-format stack (HDT, HDTQ, COTTAS, and Parquet) after a fuller audit found that Parquet.Footer.fst is substantial real F* code that the first pass missed.

Audit of the ballyhoo track to answer: how much of ballyhoo binary-format parsing is actually done in F* today?

Short answer, by format:

Build-wiring caveat (important — new in this revision)#

The six source files (Parquet.Footer.fst + five Parser.Ballyhoo*.fst) were cherry-picked onto claude/main in commit 8d4fa67. However, the cherry-pick did not bring across the build-ocaml.sh changes that include them in the extraction list. On claude/main today:

On origin/codex/ballyhoo-baseline, all of these are in the extraction and compile lists and do build.

Implication: anyone building from claude/main today with ./build-ocaml.sh gets no ballyhoo / Parquet code — it's dormant source. To actually use the F* Parquet parser, the build wiring from the ballyhoo branch needs to be re-applied. That's a one-commit fix, but currently open.

The five Ballyhoo F* modules#

File Lines What it is assume val?
Parser.Ballyhoo.fst 201 Real F* code. Streaming/chunked N-Quads parser: feed_ballyhoo_nquads_chunk, carry-buffer line splitting, event emission (BE_DefaultTriple / BE_NamedTriple), dataset assembly from events. none
Parser.BallyhooBloom.fst 114 Real F* code. Pure-F* Bloom filter: bloom_bits = list bool, bloom_empty/insert/might_contain/union, double-hashing with two modular string hashes. Self-contained, verifies. none
Parser.BallyhooHDT.fst 173 Types + interface only. Defines hdt_graph_store, hdt_term_ref, hdt_bound_tp, hdt_tp_row, corpus_graph_binding, plus logic-level helpers hdt_build_bound_tp / hdt_rows_to_triples / hdt_search_triples. 10 assume val (open/close/summary, encode×3, decode×3, search, estimate, predicate_present, named_candidate_graphs) + assume type hdt_handle
Parser.BallyhooHDTQ.fst 174 Quad/dataset sibling of HDT: hdtq_dataset_store, hdtq_named_graph_store, hdtq_bound_qp, annotation-mode enum (HQ_AnnotatedGraphs / HQ_AnnotatedTriples). 14 assume val + assume type hdtq_handle
Parser.BallyhooCOTTAS.fst 165 Columnar-quad backend model (CE_Plain/Dictionary/RLE/Delta, row groups, per-column summaries). all ops assume val + assume type cottas_handle

All five are listed in build-ocaml.sh's extraction set on origin/codex/ballyhoo-baseline only. On claude/main they are orphaned source — see the build-wiring caveat above. ocaml-patches.sh applies experimental_ocaml_glue/ballyhoo_hdt_runtime.sh (and sibling cottas_runtime.sh, parquet_footer_runtime.sh) after extraction, but those scripts only do anything if the corresponding .ml files were extracted in the first place.

The outlier: Parquet.Footer.fst (1453 lines)#

This is the most substantial F* binary-format work in the repo and was missed by the first pass of this audit. Three assume val at the top, everything else verified F*:

assume val Purpose Stub
parquet_read_tail_hex (:27) Read last N bytes of file, return as hex string OCaml open_in_bin + really_input_string in parquet_footer_runtime.sh
parquet_read_range_hex (:30) Read byte range, return as hex same
parquet_zstd_decompress_hex (:33) Zstd-decompress a hex blob parquet_zstd_stubs.c (79 lines) — hex→bytes, ZSTD_decompress, bytes→hex

What the F* code actually does:

The "hex string" design choice#

The whole parser operates on hex-encoded strings, not raw bytes. byte_at_hex (:43) reads two hex nibbles per byte; parquet_read_*_hex returns bytes already hex-encoded; parquet_zstd_decompress_hex takes and returns hex. This sidesteps F*'s weak bytes support — strings are well-supported in F*, bytes aren't. Cost is ~2× memory and per-byte ops. Benefit is that the parser stays inside the verified surface end-to-end. For a 50-100 KB Parquet footer this is acceptable; for decompressing a multi-MB data page, the C stub's hex round-trip becomes a real cost worth revisiting.

Load-bearing in the COTTAS path#

experimental_ocaml_glue/cottas_runtime.sh:253 calls Parquet_Footer.probe_parquet_column_delta_length_byte_array_value_count and :272 calls probe_parquet_column_delta_length_byte_array_value_string_at to extract the four quad columns (subject, predicate, object, graph) from a Parquet-encoded COTTAS artifact. So for COTTAS datasets the F* code is doing the real structural decode, not decoration — the OCaml glue just iterates indices and interns the returned strings into RDF terms.

Gap to "full Parquet"#

What's NOT in F*:

How the HDT stubs are actually implemented#

formal/fstar/experimental_ocaml_glue/ballyhoo_hdt_runtime.sh (555 lines) is not F* — it's a post-extraction patch that rewrites the failwith "Not yet implemented" bodies in the generated Parser_BallyhooHDT.ml. It installs an OCaml module Ballyhoo_hdt_runtime with:

Implications#

Stated direction#

From ballyhoo-backlog.md and hdtq-native-backend.md, both unchanged since the ballyhoo cherry-pick:

What's actually done in F*, end-to-end#

  1. Define the interface a verified SPARQL evaluator can rely on (bound triple-pattern search, predicate-presence check, named-graph candidate pruning) so SPARQL11.Algebra can target a real backend instead of list triple. ✅
  2. Define the intended shape of HDT / HDTQ / COTTAS artifacts (dictionary summary, triples summary, SPO order, annotation mode, column encodings, row groups). These are descriptive records, not readers. ✅
  3. Provide a verified value-level Bloom filter so the F* side has a semantics for the sidecar. ✅ (but the runtime uses a different byte-level bloom, so this is documentation, not load-bearing code)
  4. Parse the HDT container header. ❌
  5. Parse the HDT dictionary section (plain-front-coding, bitmap, etc.). ❌
  6. Parse the HDT triples section (bitmap triples, compact indexes). ❌
  7. Parse the HDTQ quad annotations. ❌
  8. Parse Parquet metadata (Thrift Compact Protocol, footer, row group descriptors, column chunk descriptors). ✅ via Parquet.Footer.fst.
  9. Parse Parquet DeltaLengthByteArray string columns. ✅ via Parquet.Footer.fst.
  10. Parse Parquet PLAIN / DICTIONARY / DELTA_BINARY_PACKED / RLE etc. encodings. ❌
  11. Zstd decompression. ❌ (C stub via libzstd; no verified Zstd exists.)

Not to be confused with#