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:
hdtSearch CLI shelled out via Unix.open_process_full.Parquet.Footer.fst, 1453 lines): substantial real F*
code. Thrift Compact Protocol, metadata navigation, and the
DeltaLengthByteArray value decoder are all implemented and verified.
Only file I/O and Zstd decompression are assume val.Parser.Ballyhoo.fst
(201 lines) and the value-level Parser.BallyhooBloom.fst (114 lines)
are fully F*, but neither is a binary format reader.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:
formal/fstar/build-ocaml.sh's for fst in … list (~line 71) does not
mention Parquet.Footer.fst or any Parser.Ballyhoo*.fst.COMMON_MODULES compile list (~line 101) also omits them..ml files in ocaml-output/ are older than the
.fst sources (verified via stat -f "%m"), so even the stale
extraction is out of sync.SPARQL11.Store.fst / SPARQL11_Store.ml (which wires HDT + COTTAS
into the SPARQL evaluator) is likewise not in the build list; the
stale extracted .ml references Parser_BallyhooHDT.* and
Parser_BallyhooCOTTAS.* but nothing compiles it.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.
| 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.
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:
:36-62, :158-164):120-411)num_rows, row_group_count, column
chunks, compression codec, data-page offsets, page-header encodings
(:413-1061):885-1165)probe_parquet_column_ delta_length_byte_array_value_string_at that returns the Nth string
value of a column (:1194-1449)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.
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.
What's NOT in F*:
_first_row_group_* or
hard-code Z.zero as the column index for row-count lookups.UTF8 STRING, ENUM, DECIMAL, etc.BallyhooBloom.fst).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:
Hashtbls map subject/predicate/object
strings ↔ Z.t ids. So hdt_encode_* / hdt_decode_* are a process-local
dictionary — nothing to do with the HDT file's own dictionary sections.hdt_search shells out: Unix.open_process_full "hdtSearch <path>",
writes the S/P/O pattern with ? for unbound slots, reads stdout line by
line, parses each line with a hand-written tokenizer (_:bnode, IRIs,
"lit"@lang, "lit"^^<dt>, backslash escapes), re-interns the results.
The real HDT binary reader is the external hdtSearch binary from
rdfhdt; the F* side never touches the bytes.hdt_predicate_present — if sidecar files graph.bloom.pred.json +
graph.bloom.pred.bin exist, it loads bit_count / hash_count from
JSON and runs a bloom test. Hashes go through sha256sum via
Unix.open_process_in (sic), split into two 16-byte halves for double
hashing. Falls back to hdt_estimate when no sidecar is present.
Note: this is a different bloom from Parser.BallyhooBloom.fst —
it operates on raw bytes from disk, not on the F* bloom_bits = list bool.
The two do not share code.hdt_estimate is List.length (hdt_search …) — not the HDT library's
index-backed estimate.hdt_named_candidate_graphs is a stub: returns all bindings regardless
of the predicate hint.hdtSearch on $PATH. This is an
undeclared runtime dependency not captured anywhere in the F* boundary.fork+exec+parse-stdout. Anything that is slow because
of this will not be fixed by F* work — it's an OCaml-glue shape problem.predicate_present call spawns a subprocess. Anyone running this under
load should expect it to dominate the profile.From ballyhoo-backlog.md and hdtq-native-backend.md, both unchanged
since the ballyhoo cherry-pick:
assume val seam.SPARQL11.Algebra can target a real backend instead of
list triple. ✅Parquet.Footer.fst.Parquet.Footer.fst.Parser.Ballyhoo.fst itself is a real F* streaming N-Quads parser
and is not part of the HDT gap — it's a separate experimental text-parser
ingestion track.hdt-backed-sparql-subset.md — what SPARQL
features work over the (stubbed) HDT backendhdtq-native-backend.md — HDTQ design intentcottas-native-backend.md — COTTAS design intentballyhoo-backlog.md — overall ballyhoo trackballyhoo-bloom-fstar.md — the verified
F* bloom filter (not wired to the HDT runtime glue; see above)sparql-store-backend.md — the storage/query
boundary the HDT interface is meant to plug intoformal/fstar/Parquet.Footer.fst — the real F* Parquet metadata +
DeltaLengthByteArray value decoder (no standalone design doc yet)2026-04-19-cottas-parquet-wiring-plan.md
— plan to restore the build wiring on claude/main and extend to
js_of_ocaml + wasm_of_ocaml