commits
- Vds.pp prints [<vds alg=N size=N root=hex>] using the existing accessors
(E305 on type t).
- Wrap the scitt.mli example in a [let run] taking keys / clock / payload
as parameters, fix the use sites that didn't compile against the real
signatures, and add the [(mdx ...)] stanza to lib/dune so the example is
typechecked on every build.
scitt + scitt_atp tests pass; merlint clean on ocaml-scitt.
The codec record drops its Value.t-based [encode] / [decode] fields, leaving
[{ kind; encode_rw; decode_rw }] — codecs now write to a Binary.encoder and
read from a Binary.decoder directly, building 'a from bytes without an
intermediate Value.t. This satisfies the ocaml-encodings skill's "codec.ml
does not depend on value.ml" rule; only cbor.ml bridges the two via
[Binary.read_cbor] / [write_cbor].
Surface follows RFC 8949 spec order: cbor.mli sections walk major types 0-7,
then simple values (3.3) and tags (3.4), each linking to the relevant clause.
Combinators move under [Cbor.Codec] (was top-level on [Cbor]); naming tracks
CDDL: [Cbor.Obj] -> [Cbor.Codec.Map], [Cbor.Obj_int] -> [Cbor.Codec.Map_int],
[Cbor.string] -> [Cbor.Codec.text], [Cbor.string_map] -> [Cbor.Codec.text_map].
Top-level IO verbs follow the parallel naming convention used elsewhere in the
monorepo: [encode_string] / [decode_string] -> [to_string] / [of_string];
[Cbor.Private.{decode,encode}_cbor] collapse into [Cbor.{decode,encode}].
[update_mem] and [delete_mem] move out of [Codec] into [Cbor] as Value.t
patch helpers (they cannot be pure-stream codecs).
Includes a fix to [Binary.skip] for definite-length text/bytes (it was
unconditionally calling [skip_break], which fired on the next item's header).
297 cbor tests + mdx pass; 16 downstream callers (cose, jwt/cwt, mst, scitt,
irmin/cbor) migrated and pass tests.
Sweep ocaml-scc, ocaml-scitt, ocaml-sdls, ocaml-sdnv, ocaml-sgp4,
ocaml-sle, ocaml-spake2, ocaml-sqlite, ocaml-srp to declare every
test-scope dependency that is actually used at test time.
Several issues compounded into a syntax error: a missing [in] joining
the [Transparent_statement.v] line into [match], unbound free
variables ([clock], [ts_private_key], [issuer_key], etc.), [signed] /
[receipt] used as bare values when both [Signed_statement.sign] and
[Transparency_service.register] return [(_, error) result], and
[verify] called with the issuer's private key instead of the public
one.
Wrap the whole thing in a [run ~clock ~ts_private_key ~ts_public_key
~issuer_key ~issuer_public_key ~sbom_json] function, [match] each
result, switch [Format.printf] / [Format.eprintf] to [Fmt.pr] /
[Fmt.epr], and add [cose] / [fmt] to the mdx libraries.
Remove libraries declared in '(libraries ...)' clauses but unreferenced
by any module in the same source tree, as flagged by 'monopam lint'
after the new Dead_lib detection landed. Touches 131 dune files across
~80 packages.
A few stanzas needed a positive correction instead of a pure removal:
- ocaml-git/bin/diag: depended on eio_main + bytesrw-eio for an
Eio_posix.run call site; the umbrella was overkill, switch to the
precise eio_posix package.
- ocaml-scaleway/lib, ocaml-s3/lib: scaleway.mli / s3.mli reference
Eio_unix.Stdenv.base; eio.unix is required and was missing.
- merlint/lib: pulled bytesrw + nox-opam.bytesrw to surface
Opam_bytesrw, used by rule e915 and lint helpers.
Stanzas where Dead_lib was a false positive (transitive dep needed
for module visibility, virtual-library impls) are left untouched —
e.g. helix.jx.jsoo for ocaml-globe/demo retains its (libraries ...)
entry because it provides the impl of the helix.jx virtual lib.
Pure formatting: dune fmt outdents the example by two spaces to match
the project's odoc-block style.
Run mdx on lib/atp/scitt_atp.mli so the {[ ... ]} odoc block now
type-checks. The example called Heap_b.v -- a fictional shorthand the
test file constructs by hand via Irmin.Heap.Make over a custom
BACKEND. That backend is ~15 lines of test scaffolding that does not
belong inline in the .mli.
Replaced with a one-line `Irmin.Heap.of_list ~equal:Atp.Cid.equal []`
that gives a heap of the right type without the backend boilerplate;
this is enough to typecheck the Scitt_atp.v construction the doc
demonstrates.
The READMEs all share the standard install/overlay snippet, but the
sh blocks lacked the "<!-- $MDX skip -->" directive. `dune test`
would shell out to `opam install` against the live switch, which
either prompts interactively or fails with a package conflict —
either way diffing as a test failure.
Bulk-add skip directives in front of every install/overlay block.
Also collapse the doubled "non-deterministic + skip" stack on three
READMEs (memtrace, ocaml-dpop, ocaml-pid1, ocaml-yaml, merlint) where
`skip` already implies the runtime is bypassed.
Doc review surfaced five issues; this commit addresses all of them.
nox-toml/doc/index.mld:
- Drop the dead {!Toml_jsont} library reference. The toml-test JSON
bridge lives only in test_json/ as a private test helper, not as a
public library.
- Correct the design note: codecs are 'a Toml.codec (alias for
'a Toml.Codec.t), not 'a Toml.t (which is the value type).
nox-toml/doc/cookbook.mld:
- The recursive-codec example called Toml.Codec.rec'; the actual API
is Toml.Codec.fix. Fix the page so copy-pasted examples compile.
nox-yaml/doc/: didn't exist. Add a Quick Start / Library Structure /
Design landing page mirroring nox-toml's, with a (documentation ...)
stanza so odoc actually picks it up.
READMEs: update both nox-toml and nox-yaml to use the post-rename
package names ('opam install nox-toml', 'nox-toml.eio', etc.) so users
following the README don't get a missing-package error.
hermest: the lexicon code generator was hard-coded to emit
'(libraries atp json)' in every generated dune.inc, which dune
re-promotes on each build — undoing any manual rename. Update the
emitter to write 'nox-json' and re-promote the four atp/scitt
lexicon dune.inc files. Without this fix, dune build @install hits
'Library "json" not found' inside lexicon subtrees.
Extends the nox- prefix to the remaining encoding/codec packages —
none clash with opam-repository today, but the rule "blacksun forks
get nox-" applies the same way regardless of conflict status.
Renamed: json, xml, meta, opam, protobuf -> nox-*
Renaming packages to nox-* pushed several (libraries ...) lines past
dune fmt's wrap threshold, so dune fmt now spreads them one-per-line.
No semantic changes.
Renames 35 packages to make blacksun forks distinguishable from their
opam-repository upstreams. Module names (Git.x, Tls.x, ...) stay bare;
opam package names and dune (public_name) findlib references move to
nox-X. After this commit, zero local package names overlap with
opam-repository.
Renamed:
- nox-git, nox-irmin
- nox-crypto, nox-crypto-pk, nox-crypto-rng, nox-crypto-ec
- nox-tls, nox-tls-eio, nox-tar, nox-tar-eio, nox-tty, nox-tty-eio
- nox-arp, nox-ca-certs, nox-cbor, nox-cookie, nox-crc, nox-csv
- nox-gpt, nox-hkdf, nox-http, nox-jwt, nox-kdf, nox-loc
- nox-memtrace, nox-pds, nox-sexp, nox-slack, nox-toml
- nox-websocket, nox-x509, nox-xdge, nox-yaml
Also drops orphan tar-mirage and tar-unix opam templates that had no
matching package stanza.
- scitt-atp: irmin, ptime (lib/atp uses both)
- atp-lexicon-scitt: atp (lib uses Atp for IPLD primitives)
Pure formatting changes from `dune fmt`: doc comment placement moves
from above the binding to below it for `type`s, multi-line `match`
expressions collapse onto one line where they fit, and infix operator
applications pick up spaces (`Soup.($?)` -> `Soup.( $? )`). No
semantic changes.
Per the ocaml-encodings convention, encoding never has a meaningful
runtime failure mode: the encoder walks the GADT and invokes
user-supplied [enc] callbacks. The only failure cases are (a) a codec
built around [Codec.ignore] (programmer error), or (b) a user-
supplied [enc] that raises (user's own bug). Neither is something a
caller should recover from, so the [result] variant was dead API
surface and every caller I've seen either [get_ok]-ed it or fell
back to [Json.Null] on error.
Drop [Codec.encode] / [Json.encode] result-returning, rename the
exception-raising [encode_exn] to just [encode]. [decode] keeps both
forms since malformed JSON is a legitimate runtime condition.
Downstream sweep: 28 files across claude / oci / atp / qemu / scitt /
yaml. Most were pattern A ([Error _ -> Json.Null ((), Meta.none)]) or
pattern B (print the error); both collapse to a direct call. Tests
using pattern F ([match Json.encode ... with Ok j -> ... | Error e
-> Alcotest.fail]) collapse to [let j = Json.encode ... in]. A few
helpers in claude/client.ml and the atp shims kept their names as
thin aliases to preserve grep targets.
Object combinators: [Object.mem] -> [Object.member], [Object.opt_mem]
-> [Object.opt_member], [Object.case_mem] -> [Object.case_member]. The
sibling submodules [Object.Mem] / [Object.Mems] become
[Object.Member] / [Object.Members]. RFC 8259 §4 calls these
"name/value pairs, referred to as the members", so mirror the spec
name rather than the shortened [mem].
[Object.finish] -> [Object.seal]. "Seal" reads as "close the map, no
more members added", which is what the operation does.
Value constructors/queries: [Value.mem] (function) -> [Value.member];
[Value.mem_find] -> [Value.member_key]; [Value.mem_names] ->
[Value.member_names]; [Value.mem_keys] -> [Value.member_keys].
[type mem = ...] -> [type member = ...]; [type object'] still points
at [member list].
Downstream (~80 files across slack, sbom, stripe, sigstore, requests,
claude, irmin, freebox) updated via perl-pie. dune build clean,
dune test ocaml-json clean.
Replace the first-class-module construction path with a functor.
Each backend (In_memory, Sqlite, Scitt_atp) now applies Make(Impl)
at module level and builds its state through an Impl.v with named
parameters, so the public constructor is a one-liner with no inline
record literal.
Promote Scitt_atp's Backend and Impl from nested modules (inside a
function body) to top-level modules, which is what the functor
pattern requires.
The top-level abstract Vds.t is preserved; dispatch goes through an
existential packing the backend module alongside its state.
Fixes for:
- Xml.Value.element -> Xml.Value.t (xtce).
- Xml.Error.t to_string wrap (xtce.of_string).
- Json.to_string now plain; drop result patterns across sbom, runc, cdm,
freebox, gauth, gdocs, hap, sigstore, space, meross, stix.
- Loc.Error.t is a record now; read e.meta directly (rego, sbom).
- 'Err' submodule renamed to 'Error' in claude; update refs + test.ml suite list.
- Yaml_jsont -> Yaml_json (space-dtn, space).
- Move sw from Monitor.S.create to Monitor.S.start.
- Drop unused sw, bundle fields from Runc.Command records.
- Drop orphan Compact.rebuild in scitt.Vds; drop 'mutable' from levels (field
content is mutated via array write, not field reassignment).
- Claude Code test: Alcotest.fail e where e is Json.Error.t -> wrap with
Json.Error.to_string.
- toml.jsont dune refs had broken '... loc))' merger in many test/bin dunes.
Toml.ml/Codec.ml and ocaml-http/lib/Xtce.ml xtce have pending mli
mismatches from concurrent linter reshape.
Follow up to the module rename: update the remaining callers that
still referenced [Err] (library [claude.ml{,i}], [client.ml], the test
driver [test.ml]), and fix one stray [^ e] string concatenation in
hermest's CLI that needed [Json.Error.to_string e] now that
[Json.of_string] yields a structured error.
- Drop jsont / jsont.bytesrw library deps in dune.inc; use json.
- Json.Codec.Value.null() / bool / number / string / array / mem / name ->
top-level Json.null / bool / etc. (these are value constructors, not codecs).
- Json.int / Json.int64 (as codec) -> Json.Codec.int / int64.
- Json.(list x) -> Json.Codec.(list x) where x is a codec.
- Json.object_' back to Json.object' (stray regex).
- xrpc_client: wrap Json.Error.t with Json.Error.to_string at the
Parse_error constructor boundary.
Warning 69 (unused-field, mutable-never-assigned). Four independent
record fields were flagged as mutable but the code only mutates their
referents in place, never rebinds the record slot itself:
- ocaml-wal/lib/wal.ml: [t.file] (the Eio file resource; methods call
Eio.File.pwrite_all etc., the slot is set once at open time).
- ocaml-block/lib/block.ml: [Memory.state.data] (the backing bytes,
written via Bytes.blit_string; [Bytes.t] is already mutable).
- ocaml-sse/lib/sse.ml: [Parser.t.data_buf] (a Buffer.t, written via
Buffer.add_*; the slot never changes).
- ocaml-zephyr/lib/zephyr.ml: drop [mode : Read | Write] entirely —
set at open-time, read nowhere. The open_read / open_write
constructors already distinguish the two call shapes, so mode
tracking was redundant.
Previously the eight git-x subcommands sat flat at top level (split,
check, fix, verify, filter-paths, split-commit, drop-commit, reword),
with 'split' ambiguous between the subtree split and the per-directory
commit split.
Regrouped into two namespaces that mirror the object they act on:
git-x tree
split (was: git-x split)
add (new — inject a standalone history under a prefix)
drop (was: git-x filter-paths)
check (was: git-x check)
fix (was: git-x fix)
verify (was: git-x verify)
git-x commit
split (was: git-x split-commit)
drop (was: git-x drop-commit)
reword (was: git-x reword)
Each subcommand lives in cmd_<group>_<verb>.{ml,mli}; cmd_tree.ml and
cmd_commit.ml are the Cmd.group wrappers. git_x.ml registers just the
two groups.
'tree add' is a thin wrapper over Git.Subtree.add, which already
existed in the library but had no CLI exposure. It accepts a ref (e.g.
FETCH_HEAD after 'git fetch URL REF') and a --prefix, then builds a
subtree-merge commit with the current user's git config identity.
Log source names are updated to match (git-x.tree.split,
git-x.tree.fix). The cram test under test/cram/tree_split.t is
updated to use the new 'git-x tree split' invocation throughout.
Drops the "t" suffix. Internal raw CBOR module moves to Value (was
Cbor in lib/cbor.ml), matching the value/codec/<pkg> layout from the
other codec packages. Low-level byte R/W moved to lib/binary.ml (was
lib/cbor_rw.ml). Library name cbor; main module Cbor via lib/cbor.ml
(was cbort.ml).
Downstream packages (ocaml-bundle, ocaml-cose, ocaml-bpsec, ocaml-scitt,
ocaml-crow, irmin) partially migrated: Cbort.Cbor -> Cbor.Value, the
internal Cbor alias shadowing in each file renamed to V to free the
top-level Cbor for the library facade. Some downstream build errors
remain because many callsites conflated raw value constructors
(Cbor.int, Cbor.int64) with schema codecs and need manual triage.
The lib/binary.ml R/W primitives are NOT re-exported through Cbor.Binary
due to OCaml's lazy module alias elision when the aliased module isn't
referenced by any type/value in the parent signature. A separate
cbor.bytesrw library (ocaml-cbor/lib/bytesrw/) is the right home for
that, matching json.bytesrw / toml.bytesrw; left as a follow-up.
Covers every package whose .opam was missing tags, misspelled a
singleton tag, or only carried a foreign org marker. The canonical
vocabulary now lives in .merlint as E915's topics: list; this pass
makes the monorepo pass E915 on the packages whose dune-project is
the source of truth.
Cleanups: drop singletons cop1/sle/sdls/uslp/s3/sdk; rename log to
logging (json-logs, vlog); add org:blacksun to formerly-Mirage-only
packages (ca-certs, tar, tls, x509, crypto-ec).
Additions to packages missing tags: cfdp-eio, cop1-eio, ltp-eio,
scc-eio, sdls-eio, sle-eio, yamlrw-eio, yamlrw-unix, ocaml-merlin,
scitt-atp, atp-lexicon-scitt, and 10 ocaml-atp subpackages.
- Refactor proof.verify_mst (93 lines) into decode_blocks, run_mst_proof,
check_cose_leaf, verify_mst_inner helpers.
- Replace catch-all 'with _' in mst_parse and copy_block with
Invalid_argument | Failure to avoid hiding unexpected errors.
- Extract err_consistency_first helper for vds.ml E340 patterns.
Generate .opam.template files with x-quality-* fields based on
detected package features:
- x-quality-build: has lib/ with .ml files
- x-quality-test: has test/ with .ml files
- x-quality-fuzz: has fuzz/ with .ml files
- x-quality-interop: has test/interop/ directory
- x-quality-cram: has test/*.t/ directories
These fields are picked up by dune's opam generation and will be
checked by merlint E910 for consistency.
Also: add fmt dep to ocaml-sse/lib/dune (Fmt.pf used without dep).
Standard vocabulary based on crates.io categories, erratique/opam
conventions, and monorepo domain coverage:
Org: org:blacksun
Domain: aerospace, codec, crypto, network, storage, git, merkle
Purpose: cli, test, bench, format, log, system
Protocol: ccsds, uslp, cop1, sdls, sle, atproto, tls, http, json, binary
Cross-cutting: eio, simulation, math, compression
Tags placed in dune-project (package ...) stanzas via (tags ...).
Propagated to .opam files by dune's opam generation.
112 files across the monorepo. Printf.sprintf → Fmt.str,
Printf.printf → Fmt.pr for consistent formatting library usage.
Heap.t: ('h, 'v, 'b) t → ('h, 'v) t. The phantom 'b parameter
served no purpose — layer, recording, and of_list produce heaps
with no meaningful backend tag. The record-of-closures is the
correct representation for this case (composable heaps can't be
expressed as a single BACKEND module).
Tests: move schema and tar tests from irmin/test/{schema,tar}/
into irmin/test/test_{schema,tar}.ml. All 19 tests now run from
a single `dune exec irmin/test/test.exe`.
ref is not an OCaml keyword (it's a Stdlib function), so it's a
valid name in a different module. Drops the redundant find_ prefix
per merlint E331.
Replace the GADT-shaped schema with a record-based codec carrying a
MIME-style name and a Type.Id witness. This unifies "schema" and
"content type" into one concept, modelled after IPLD codecs and HTTP
content negotiation.
API changes:
- 'a t now means "codec for blocks of value type 'a"
- node ~name ~dec ~enc ?merge ?rules () : children t
- opaque : H.block t (leaf, no navigation)
- map ~dec ~enc inner : transforms value type, keeps navigation
- fix : ('a t -> 'a t) -> 'a t (recursion)
- name, id (Type.Id witness) for runtime introspection
- ('a, 'b) field: typed accessor (path + expected codec)
- step (typed): cursor -> field -> 'b cursor option
- step_any (untyped): returns Step (codec, cursor) existential
- cast: recover typed cursor via Type.Id equality
- Step constructor now carries (codec, cursor) for type recovery
- step_result type renamed (was step) to avoid clash with val step
All backends declare a MIME name like "application/json",
"application/x-git-tree", "application/dag-cbor".
11 tests pass (6 schema + 5 tar), mst_proof verified.
- Schema is now a GADT: 'a t with Opaque : H.block t,
Node : ... -> children t, Map, Rec
- Cursors carry the value type: 'a cursor
- get : 'a cursor -> 'a option (decoded value)
- put : 'a cursor -> 'a -> 'a cursor
- set : 'a cursor -> string -> H.block -> 'a cursor (raw block)
- get_block : _ cursor -> H.block option (raw escape hatch)
- step / up return existential: type step = Step : 'a cursor -> step
- map : dec:('b -> 'a) -> enc:('a -> 'b) -> 'b t -> 'a t
- produce / verify callbacks return step * 'b
- All callsites updated: backends, tests, bin commands, ocaml-scitt
- 11 tests pass
- Move backends into irmin/lib/{git,json,cbor,tar,atproto,oci}/
- Rename parse→dec, serialize→enc across Schema API
- Add Irmin.SHA1, Irmin.SHA256 pre-built schema instances
- Deduplicate Schema.Make boilerplate in json, cbor, tar, oci
- Implement structural diff and 4-point ddiff (was stubs)
- Implement JSON serialize via Jsont, CBOR serialize via Cbort
- Rewrite ATProto backend: Schema.Make + Heap.BACKEND + MST bridge
- Rewrite OCI backend: SHA256 JSON schema
- Rewrite all 13 bin commands for Schema/Heap API
- Fix test infrastructure: restrict old tests, rewrite mst_proof
- Fix ocaml-scitt for dec/enc rename
- Two-phase merge API: cursor * conflict list (not Ok/Error)
- Irmin.Merge module with typed combinators + v/v_result lifters
- 11 tests pass (6 schema + 5 tar), mst_proof verified
- CCSDS packages (aos, clcw, fsr, sdls, space-packet, tc, tm, uslp):
Wire.bool renamed to Wire.bit upstream; mechanical migration.
- ocaml-cfdp: adopt wire library for PDU header codec.
- ocaml-ltp: adopt wire library for segment header codec.
- ocaml-cop1: simplify interop test error formatting.
- dune fmt: reformat irmin, pus, scitt, crypto, tc/uslp 3D specs.
- monopam: add TODO.md tracking cram coverage gaps.
- irmin/lib/tree.ml: list() now returns in-memory children even when
force_node fails; to_concrete() recursively loads backend entries
instead of dropping them silently (the placeholder None was eating
all backend-stored subtree content, so git-backed checkout → add
→ commit was losing records).
- irmin/lib/irmin: add Irmin.tree_hash and Irmin.prove — both backends
expose inclusion proofs via a new prove method on the S signature.
Git uses Private.Proof.Git.produce, Mst uses Private.Proof.Mst.produce;
prove returns the CBOR-encoded proof bytes + the value at the key.
- ocaml-scitt/lib/atp/scitt_atp.ml: the MST VDS was returning path = []
as a placeholder, so every receipt failed verification. Now produces
a real Irmin.prove-generated inclusion proof, wraps it in
[repo_key; proof_bytes] CBOR, and uses the tree-root CID (not the
commit hash) as the receipt root.
- ocaml-mbr/lib/mbr.ml: to_string used Bytes.create which is not
guaranteed to be zeroed; unused partition slots carried uninitialised
bytes and the roundtrip fuzz test broke. Use Bytes.make '\x00'.
- ocaml-tls/eio/tests/tls_eio.md: MDX could not resolve the virtual
crypto library from #require "crypto-rng.unix" alone; explicitly
require crypto.c first so the default implementation is loaded.
- irmin/test/dune: git.t was not wired into the cram stanza, so the
test was using whichever irmin binary happened to be on PATH instead
of the freshly built one.
- Update .ocamlformat to 0.29.0 across all 591 files
- csvt: reuse single Buffer.t for field reads (no alloc per field)
- sexpt: Obj members decoded from stream into Dict, typed Variant GADT
- Reformat all source files for 0.29.0
- Fix BCJR extrinsic LLR double-subtraction in turbo decoder;
all 7 turbo roundtrip tests now pass (39 total tm-sync tests)
- ocaml-sdls/fuzz: SA lifecycle, key state, EP PDU roundtrip (13 tests)
- ocaml-reed-solomon/fuzz: encode/decode with random errors (6 tests)
- ocaml-flexacm/fuzz: mode lookup, SNR monotonicity (6 tests)
Backend.t is now abstract — convert irmin_backend_of_atp from a
record literal to a proper Atp_backend module matching Backend.S,
packed via Backend.Make.
New READMEs for: ocaml-auth, ocaml-cose, ocaml-http, ocaml-osv,
ocaml-rego, ocaml-scitt, ocaml-sigstore, ocaml-vec3.
Expanded: ca-certs (7→40 lines), osrelease (8→45 lines).
Each includes: synopsis, installation, usage example, API overview,
and license. Skipped ocaml-cel and ocaml-chor (no code yet).
160 new tests exercising security-critical code paths identified by
mapping known CVEs from C/reference implementations to our OCaml code:
- ocaml-sqlite (9): cyclic pages, oversized varints, record overflow,
wrong page kind, truncated WAL, out-of-bounds root, garbage files
- ocaml-cbort (12): deep nesting (CVE-2025-24302), indefinite-length
DoS, integer overflow in lengths, truncated input, invalid types
- ocaml-tar (10): path traversal (CVE-2021-32803), symlink escape
(CVE-2025-45582), oversized octal, truncated headers, checksum
- ocaml-http (14): CRLF header injection (CWE-113), null bytes,
Content-Length overflow, empty/duplicate headers
Also hardens validate_header_name_str to reject null bytes/empty names
- ocaml-jsonwt (21): "none" algorithm bypass (CVE-2015-9235) case
variations, algorithm confusion (CVE-2016-10555), malformed headers,
empty segments, extra dots, large payloads
- ocaml-cose (8): algorithm substitution, missing algorithm header,
malformed CBOR, wrong types, label overlap (RFC 9052)
- ocaml-git (18): tree path traversal, null bytes, symlink mode,
malformed tree data, pack delta attacks, pack format validation
- ocaml-tomlt (25): duplicate keys, integer overflow, malformed dates
(invalid month/day/hour/minute), deep nesting, long strings
- ocaml-squashfs (20): symlink traversal edge cases, fragment table
bounds, inode self-reference, compression bomb limits, bad superblock
- ocaml-cpio (23): symlink target validation, null bytes in filenames,
oversized filesize, truncated archives, invalid magic numbers
monotonic_now: increment changed from 1μs to 1ms, drift capped at 300s.
Rejects registration if clock drift exceeds cap instead of silently
issuing synthetic timestamps indefinitely.
verify/verify_receipt_only now return receipt_summary with total,
verified, failed, skipped counts so callers can detect partial
verification (e.g. 2 of 3 receipts tampered).
Convert remaining bare Mutex.lock/unlock patterns to
Eio.Mutex.use_rw/use_ro in cookeio, requests, oci, and space.
Fix unattached doc comment in vds.ml Node_cache.
Read .git/HEAD and follow the ref to get the commit hash. No
subprocess, no dependency on git being in PATH. Uses Bos.OS.File.read
which is already a dependency.
Multiple releases per day now get unique versions. The git short
hash makes each release traceable to the exact commit. Comparing
versions: date gives chronological order, hash gives exact source.
set-url --push only ran in the clone branch. On subsequent runs
the tap directory already existed, so the pull branch ran instead,
leaving the origin push URL as HTTPS. Now sets the push URL every
time before pull, so pushes always use the configured SSH URL.
git pull --ff-only fails when the remote tap repo has no commits
(empty repo, no main branch). Check rev-parse origin/main first;
if it fails, skip the pull.
The failwith at module init was an uncatchable crash on 32-bit targets.
Use (enabled_if %{arch_sixtyfour}) in dune so the library is rejected
at compile time. Document why 64-bit is required: native int overflow
at ~2B entries is reachable for transparency logs.
Both backends use Stdlib.Mutex (domain-safe, no Eio runtime needed).
Eio.Mutex would be better for fiber scheduling but requires an Eio
event loop — VDS is a data structure that should work without one.
Two new multicore stress tests:
- 4 domains appending 250 entries each concurrently (1000 total)
- 2 writer domains + 2 reader domains running simultaneously
Both pass — Mutex prevents data corruption across domains.
Both In_memory and Sqlite backends now have an internal Mutex.t that
serializes all operations. The Vds.t wrapper is pure dispatch — no
locks, no overhead.
Backends own their concurrency story:
- In_memory: Mutex protects Growable, Compact, Node_cache, Hashtbl
- Sqlite: Mutex protects in-memory cache; SQLite transactions for
disk atomicity
Performance: ~250k appends/s single-domain (uncontended Mutex adds
~5ns per operation — within noise). Multicore: domains block on the
Mutex for writes, which is correct for an append-only log.
- Vds.pp prints [<vds alg=N size=N root=hex>] using the existing accessors
(E305 on type t).
- Wrap the scitt.mli example in a [let run] taking keys / clock / payload
as parameters, fix the use sites that didn't compile against the real
signatures, and add the [(mdx ...)] stanza to lib/dune so the example is
typechecked on every build.
scitt + scitt_atp tests pass; merlint clean on ocaml-scitt.
The codec record drops its Value.t-based [encode] / [decode] fields, leaving
[{ kind; encode_rw; decode_rw }] — codecs now write to a Binary.encoder and
read from a Binary.decoder directly, building 'a from bytes without an
intermediate Value.t. This satisfies the ocaml-encodings skill's "codec.ml
does not depend on value.ml" rule; only cbor.ml bridges the two via
[Binary.read_cbor] / [write_cbor].
Surface follows RFC 8949 spec order: cbor.mli sections walk major types 0-7,
then simple values (3.3) and tags (3.4), each linking to the relevant clause.
Combinators move under [Cbor.Codec] (was top-level on [Cbor]); naming tracks
CDDL: [Cbor.Obj] -> [Cbor.Codec.Map], [Cbor.Obj_int] -> [Cbor.Codec.Map_int],
[Cbor.string] -> [Cbor.Codec.text], [Cbor.string_map] -> [Cbor.Codec.text_map].
Top-level IO verbs follow the parallel naming convention used elsewhere in the
monorepo: [encode_string] / [decode_string] -> [to_string] / [of_string];
[Cbor.Private.{decode,encode}_cbor] collapse into [Cbor.{decode,encode}].
[update_mem] and [delete_mem] move out of [Codec] into [Cbor] as Value.t
patch helpers (they cannot be pure-stream codecs).
Includes a fix to [Binary.skip] for definite-length text/bytes (it was
unconditionally calling [skip_break], which fired on the next item's header).
297 cbor tests + mdx pass; 16 downstream callers (cose, jwt/cwt, mst, scitt,
irmin/cbor) migrated and pass tests.
Several issues compounded into a syntax error: a missing [in] joining
the [Transparent_statement.v] line into [match], unbound free
variables ([clock], [ts_private_key], [issuer_key], etc.), [signed] /
[receipt] used as bare values when both [Signed_statement.sign] and
[Transparency_service.register] return [(_, error) result], and
[verify] called with the issuer's private key instead of the public
one.
Wrap the whole thing in a [run ~clock ~ts_private_key ~ts_public_key
~issuer_key ~issuer_public_key ~sbom_json] function, [match] each
result, switch [Format.printf] / [Format.eprintf] to [Fmt.pr] /
[Fmt.epr], and add [cose] / [fmt] to the mdx libraries.
Remove libraries declared in '(libraries ...)' clauses but unreferenced
by any module in the same source tree, as flagged by 'monopam lint'
after the new Dead_lib detection landed. Touches 131 dune files across
~80 packages.
A few stanzas needed a positive correction instead of a pure removal:
- ocaml-git/bin/diag: depended on eio_main + bytesrw-eio for an
Eio_posix.run call site; the umbrella was overkill, switch to the
precise eio_posix package.
- ocaml-scaleway/lib, ocaml-s3/lib: scaleway.mli / s3.mli reference
Eio_unix.Stdenv.base; eio.unix is required and was missing.
- merlint/lib: pulled bytesrw + nox-opam.bytesrw to surface
Opam_bytesrw, used by rule e915 and lint helpers.
Stanzas where Dead_lib was a false positive (transitive dep needed
for module visibility, virtual-library impls) are left untouched —
e.g. helix.jx.jsoo for ocaml-globe/demo retains its (libraries ...)
entry because it provides the impl of the helix.jx virtual lib.
Run mdx on lib/atp/scitt_atp.mli so the {[ ... ]} odoc block now
type-checks. The example called Heap_b.v -- a fictional shorthand the
test file constructs by hand via Irmin.Heap.Make over a custom
BACKEND. That backend is ~15 lines of test scaffolding that does not
belong inline in the .mli.
Replaced with a one-line `Irmin.Heap.of_list ~equal:Atp.Cid.equal []`
that gives a heap of the right type without the backend boilerplate;
this is enough to typecheck the Scitt_atp.v construction the doc
demonstrates.
The READMEs all share the standard install/overlay snippet, but the
sh blocks lacked the "<!-- $MDX skip -->" directive. `dune test`
would shell out to `opam install` against the live switch, which
either prompts interactively or fails with a package conflict —
either way diffing as a test failure.
Bulk-add skip directives in front of every install/overlay block.
Also collapse the doubled "non-deterministic + skip" stack on three
READMEs (memtrace, ocaml-dpop, ocaml-pid1, ocaml-yaml, merlint) where
`skip` already implies the runtime is bypassed.
Doc review surfaced five issues; this commit addresses all of them.
nox-toml/doc/index.mld:
- Drop the dead {!Toml_jsont} library reference. The toml-test JSON
bridge lives only in test_json/ as a private test helper, not as a
public library.
- Correct the design note: codecs are 'a Toml.codec (alias for
'a Toml.Codec.t), not 'a Toml.t (which is the value type).
nox-toml/doc/cookbook.mld:
- The recursive-codec example called Toml.Codec.rec'; the actual API
is Toml.Codec.fix. Fix the page so copy-pasted examples compile.
nox-yaml/doc/: didn't exist. Add a Quick Start / Library Structure /
Design landing page mirroring nox-toml's, with a (documentation ...)
stanza so odoc actually picks it up.
READMEs: update both nox-toml and nox-yaml to use the post-rename
package names ('opam install nox-toml', 'nox-toml.eio', etc.) so users
following the README don't get a missing-package error.
hermest: the lexicon code generator was hard-coded to emit
'(libraries atp json)' in every generated dune.inc, which dune
re-promotes on each build — undoing any manual rename. Update the
emitter to write 'nox-json' and re-promote the four atp/scitt
lexicon dune.inc files. Without this fix, dune build @install hits
'Library "json" not found' inside lexicon subtrees.
Renames 35 packages to make blacksun forks distinguishable from their
opam-repository upstreams. Module names (Git.x, Tls.x, ...) stay bare;
opam package names and dune (public_name) findlib references move to
nox-X. After this commit, zero local package names overlap with
opam-repository.
Renamed:
- nox-git, nox-irmin
- nox-crypto, nox-crypto-pk, nox-crypto-rng, nox-crypto-ec
- nox-tls, nox-tls-eio, nox-tar, nox-tar-eio, nox-tty, nox-tty-eio
- nox-arp, nox-ca-certs, nox-cbor, nox-cookie, nox-crc, nox-csv
- nox-gpt, nox-hkdf, nox-http, nox-jwt, nox-kdf, nox-loc
- nox-memtrace, nox-pds, nox-sexp, nox-slack, nox-toml
- nox-websocket, nox-x509, nox-xdge, nox-yaml
Also drops orphan tar-mirage and tar-unix opam templates that had no
matching package stanza.
Per the ocaml-encodings convention, encoding never has a meaningful
runtime failure mode: the encoder walks the GADT and invokes
user-supplied [enc] callbacks. The only failure cases are (a) a codec
built around [Codec.ignore] (programmer error), or (b) a user-
supplied [enc] that raises (user's own bug). Neither is something a
caller should recover from, so the [result] variant was dead API
surface and every caller I've seen either [get_ok]-ed it or fell
back to [Json.Null] on error.
Drop [Codec.encode] / [Json.encode] result-returning, rename the
exception-raising [encode_exn] to just [encode]. [decode] keeps both
forms since malformed JSON is a legitimate runtime condition.
Downstream sweep: 28 files across claude / oci / atp / qemu / scitt /
yaml. Most were pattern A ([Error _ -> Json.Null ((), Meta.none)]) or
pattern B (print the error); both collapse to a direct call. Tests
using pattern F ([match Json.encode ... with Ok j -> ... | Error e
-> Alcotest.fail]) collapse to [let j = Json.encode ... in]. A few
helpers in claude/client.ml and the atp shims kept their names as
thin aliases to preserve grep targets.
Object combinators: [Object.mem] -> [Object.member], [Object.opt_mem]
-> [Object.opt_member], [Object.case_mem] -> [Object.case_member]. The
sibling submodules [Object.Mem] / [Object.Mems] become
[Object.Member] / [Object.Members]. RFC 8259 §4 calls these
"name/value pairs, referred to as the members", so mirror the spec
name rather than the shortened [mem].
[Object.finish] -> [Object.seal]. "Seal" reads as "close the map, no
more members added", which is what the operation does.
Value constructors/queries: [Value.mem] (function) -> [Value.member];
[Value.mem_find] -> [Value.member_key]; [Value.mem_names] ->
[Value.member_names]; [Value.mem_keys] -> [Value.member_keys].
[type mem = ...] -> [type member = ...]; [type object'] still points
at [member list].
Downstream (~80 files across slack, sbom, stripe, sigstore, requests,
claude, irmin, freebox) updated via perl-pie. dune build clean,
dune test ocaml-json clean.
Replace the first-class-module construction path with a functor.
Each backend (In_memory, Sqlite, Scitt_atp) now applies Make(Impl)
at module level and builds its state through an Impl.v with named
parameters, so the public constructor is a one-liner with no inline
record literal.
Promote Scitt_atp's Backend and Impl from nested modules (inside a
function body) to top-level modules, which is what the functor
pattern requires.
The top-level abstract Vds.t is preserved; dispatch goes through an
existential packing the backend module alongside its state.
Fixes for:
- Xml.Value.element -> Xml.Value.t (xtce).
- Xml.Error.t to_string wrap (xtce.of_string).
- Json.to_string now plain; drop result patterns across sbom, runc, cdm,
freebox, gauth, gdocs, hap, sigstore, space, meross, stix.
- Loc.Error.t is a record now; read e.meta directly (rego, sbom).
- 'Err' submodule renamed to 'Error' in claude; update refs + test.ml suite list.
- Yaml_jsont -> Yaml_json (space-dtn, space).
- Move sw from Monitor.S.create to Monitor.S.start.
- Drop unused sw, bundle fields from Runc.Command records.
- Drop orphan Compact.rebuild in scitt.Vds; drop 'mutable' from levels (field
content is mutated via array write, not field reassignment).
- Claude Code test: Alcotest.fail e where e is Json.Error.t -> wrap with
Json.Error.to_string.
- toml.jsont dune refs had broken '... loc))' merger in many test/bin dunes.
Toml.ml/Codec.ml and ocaml-http/lib/Xtce.ml xtce have pending mli
mismatches from concurrent linter reshape.
Follow up to the module rename: update the remaining callers that
still referenced [Err] (library [claude.ml{,i}], [client.ml], the test
driver [test.ml]), and fix one stray [^ e] string concatenation in
hermest's CLI that needed [Json.Error.to_string e] now that
[Json.of_string] yields a structured error.
- Drop jsont / jsont.bytesrw library deps in dune.inc; use json.
- Json.Codec.Value.null() / bool / number / string / array / mem / name ->
top-level Json.null / bool / etc. (these are value constructors, not codecs).
- Json.int / Json.int64 (as codec) -> Json.Codec.int / int64.
- Json.(list x) -> Json.Codec.(list x) where x is a codec.
- Json.object_' back to Json.object' (stray regex).
- xrpc_client: wrap Json.Error.t with Json.Error.to_string at the
Parse_error constructor boundary.
Warning 69 (unused-field, mutable-never-assigned). Four independent
record fields were flagged as mutable but the code only mutates their
referents in place, never rebinds the record slot itself:
- ocaml-wal/lib/wal.ml: [t.file] (the Eio file resource; methods call
Eio.File.pwrite_all etc., the slot is set once at open time).
- ocaml-block/lib/block.ml: [Memory.state.data] (the backing bytes,
written via Bytes.blit_string; [Bytes.t] is already mutable).
- ocaml-sse/lib/sse.ml: [Parser.t.data_buf] (a Buffer.t, written via
Buffer.add_*; the slot never changes).
- ocaml-zephyr/lib/zephyr.ml: drop [mode : Read | Write] entirely —
set at open-time, read nowhere. The open_read / open_write
constructors already distinguish the two call shapes, so mode
tracking was redundant.
Previously the eight git-x subcommands sat flat at top level (split,
check, fix, verify, filter-paths, split-commit, drop-commit, reword),
with 'split' ambiguous between the subtree split and the per-directory
commit split.
Regrouped into two namespaces that mirror the object they act on:
git-x tree
split (was: git-x split)
add (new — inject a standalone history under a prefix)
drop (was: git-x filter-paths)
check (was: git-x check)
fix (was: git-x fix)
verify (was: git-x verify)
git-x commit
split (was: git-x split-commit)
drop (was: git-x drop-commit)
reword (was: git-x reword)
Each subcommand lives in cmd_<group>_<verb>.{ml,mli}; cmd_tree.ml and
cmd_commit.ml are the Cmd.group wrappers. git_x.ml registers just the
two groups.
'tree add' is a thin wrapper over Git.Subtree.add, which already
existed in the library but had no CLI exposure. It accepts a ref (e.g.
FETCH_HEAD after 'git fetch URL REF') and a --prefix, then builds a
subtree-merge commit with the current user's git config identity.
Log source names are updated to match (git-x.tree.split,
git-x.tree.fix). The cram test under test/cram/tree_split.t is
updated to use the new 'git-x tree split' invocation throughout.
Drops the "t" suffix. Internal raw CBOR module moves to Value (was
Cbor in lib/cbor.ml), matching the value/codec/<pkg> layout from the
other codec packages. Low-level byte R/W moved to lib/binary.ml (was
lib/cbor_rw.ml). Library name cbor; main module Cbor via lib/cbor.ml
(was cbort.ml).
Downstream packages (ocaml-bundle, ocaml-cose, ocaml-bpsec, ocaml-scitt,
ocaml-crow, irmin) partially migrated: Cbort.Cbor -> Cbor.Value, the
internal Cbor alias shadowing in each file renamed to V to free the
top-level Cbor for the library facade. Some downstream build errors
remain because many callsites conflated raw value constructors
(Cbor.int, Cbor.int64) with schema codecs and need manual triage.
The lib/binary.ml R/W primitives are NOT re-exported through Cbor.Binary
due to OCaml's lazy module alias elision when the aliased module isn't
referenced by any type/value in the parent signature. A separate
cbor.bytesrw library (ocaml-cbor/lib/bytesrw/) is the right home for
that, matching json.bytesrw / toml.bytesrw; left as a follow-up.
Covers every package whose .opam was missing tags, misspelled a
singleton tag, or only carried a foreign org marker. The canonical
vocabulary now lives in .merlint as E915's topics: list; this pass
makes the monorepo pass E915 on the packages whose dune-project is
the source of truth.
Cleanups: drop singletons cop1/sle/sdls/uslp/s3/sdk; rename log to
logging (json-logs, vlog); add org:blacksun to formerly-Mirage-only
packages (ca-certs, tar, tls, x509, crypto-ec).
Additions to packages missing tags: cfdp-eio, cop1-eio, ltp-eio,
scc-eio, sdls-eio, sle-eio, yamlrw-eio, yamlrw-unix, ocaml-merlin,
scitt-atp, atp-lexicon-scitt, and 10 ocaml-atp subpackages.
- Refactor proof.verify_mst (93 lines) into decode_blocks, run_mst_proof,
check_cose_leaf, verify_mst_inner helpers.
- Replace catch-all 'with _' in mst_parse and copy_block with
Invalid_argument | Failure to avoid hiding unexpected errors.
- Extract err_consistency_first helper for vds.ml E340 patterns.
Generate .opam.template files with x-quality-* fields based on
detected package features:
- x-quality-build: has lib/ with .ml files
- x-quality-test: has test/ with .ml files
- x-quality-fuzz: has fuzz/ with .ml files
- x-quality-interop: has test/interop/ directory
- x-quality-cram: has test/*.t/ directories
These fields are picked up by dune's opam generation and will be
checked by merlint E910 for consistency.
Also: add fmt dep to ocaml-sse/lib/dune (Fmt.pf used without dep).
Standard vocabulary based on crates.io categories, erratique/opam
conventions, and monorepo domain coverage:
Org: org:blacksun
Domain: aerospace, codec, crypto, network, storage, git, merkle
Purpose: cli, test, bench, format, log, system
Protocol: ccsds, uslp, cop1, sdls, sle, atproto, tls, http, json, binary
Cross-cutting: eio, simulation, math, compression
Tags placed in dune-project (package ...) stanzas via (tags ...).
Propagated to .opam files by dune's opam generation.
Heap.t: ('h, 'v, 'b) t → ('h, 'v) t. The phantom 'b parameter
served no purpose — layer, recording, and of_list produce heaps
with no meaningful backend tag. The record-of-closures is the
correct representation for this case (composable heaps can't be
expressed as a single BACKEND module).
Tests: move schema and tar tests from irmin/test/{schema,tar}/
into irmin/test/test_{schema,tar}.ml. All 19 tests now run from
a single `dune exec irmin/test/test.exe`.
Replace the GADT-shaped schema with a record-based codec carrying a
MIME-style name and a Type.Id witness. This unifies "schema" and
"content type" into one concept, modelled after IPLD codecs and HTTP
content negotiation.
API changes:
- 'a t now means "codec for blocks of value type 'a"
- node ~name ~dec ~enc ?merge ?rules () : children t
- opaque : H.block t (leaf, no navigation)
- map ~dec ~enc inner : transforms value type, keeps navigation
- fix : ('a t -> 'a t) -> 'a t (recursion)
- name, id (Type.Id witness) for runtime introspection
- ('a, 'b) field: typed accessor (path + expected codec)
- step (typed): cursor -> field -> 'b cursor option
- step_any (untyped): returns Step (codec, cursor) existential
- cast: recover typed cursor via Type.Id equality
- Step constructor now carries (codec, cursor) for type recovery
- step_result type renamed (was step) to avoid clash with val step
All backends declare a MIME name like "application/json",
"application/x-git-tree", "application/dag-cbor".
11 tests pass (6 schema + 5 tar), mst_proof verified.
- Schema is now a GADT: 'a t with Opaque : H.block t,
Node : ... -> children t, Map, Rec
- Cursors carry the value type: 'a cursor
- get : 'a cursor -> 'a option (decoded value)
- put : 'a cursor -> 'a -> 'a cursor
- set : 'a cursor -> string -> H.block -> 'a cursor (raw block)
- get_block : _ cursor -> H.block option (raw escape hatch)
- step / up return existential: type step = Step : 'a cursor -> step
- map : dec:('b -> 'a) -> enc:('a -> 'b) -> 'b t -> 'a t
- produce / verify callbacks return step * 'b
- All callsites updated: backends, tests, bin commands, ocaml-scitt
- 11 tests pass
- Move backends into irmin/lib/{git,json,cbor,tar,atproto,oci}/
- Rename parse→dec, serialize→enc across Schema API
- Add Irmin.SHA1, Irmin.SHA256 pre-built schema instances
- Deduplicate Schema.Make boilerplate in json, cbor, tar, oci
- Implement structural diff and 4-point ddiff (was stubs)
- Implement JSON serialize via Jsont, CBOR serialize via Cbort
- Rewrite ATProto backend: Schema.Make + Heap.BACKEND + MST bridge
- Rewrite OCI backend: SHA256 JSON schema
- Rewrite all 13 bin commands for Schema/Heap API
- Fix test infrastructure: restrict old tests, rewrite mst_proof
- Fix ocaml-scitt for dec/enc rename
- Two-phase merge API: cursor * conflict list (not Ok/Error)
- Irmin.Merge module with typed combinators + v/v_result lifters
- 11 tests pass (6 schema + 5 tar), mst_proof verified
- CCSDS packages (aos, clcw, fsr, sdls, space-packet, tc, tm, uslp):
Wire.bool renamed to Wire.bit upstream; mechanical migration.
- ocaml-cfdp: adopt wire library for PDU header codec.
- ocaml-ltp: adopt wire library for segment header codec.
- ocaml-cop1: simplify interop test error formatting.
- dune fmt: reformat irmin, pus, scitt, crypto, tc/uslp 3D specs.
- monopam: add TODO.md tracking cram coverage gaps.
- irmin/lib/tree.ml: list() now returns in-memory children even when
force_node fails; to_concrete() recursively loads backend entries
instead of dropping them silently (the placeholder None was eating
all backend-stored subtree content, so git-backed checkout → add
→ commit was losing records).
- irmin/lib/irmin: add Irmin.tree_hash and Irmin.prove — both backends
expose inclusion proofs via a new prove method on the S signature.
Git uses Private.Proof.Git.produce, Mst uses Private.Proof.Mst.produce;
prove returns the CBOR-encoded proof bytes + the value at the key.
- ocaml-scitt/lib/atp/scitt_atp.ml: the MST VDS was returning path = []
as a placeholder, so every receipt failed verification. Now produces
a real Irmin.prove-generated inclusion proof, wraps it in
[repo_key; proof_bytes] CBOR, and uses the tree-root CID (not the
commit hash) as the receipt root.
- ocaml-mbr/lib/mbr.ml: to_string used Bytes.create which is not
guaranteed to be zeroed; unused partition slots carried uninitialised
bytes and the roundtrip fuzz test broke. Use Bytes.make '\x00'.
- ocaml-tls/eio/tests/tls_eio.md: MDX could not resolve the virtual
crypto library from #require "crypto-rng.unix" alone; explicitly
require crypto.c first so the default implementation is loaded.
- irmin/test/dune: git.t was not wired into the cram stanza, so the
test was using whichever irmin binary happened to be on PATH instead
of the freshly built one.
- Fix BCJR extrinsic LLR double-subtraction in turbo decoder;
all 7 turbo roundtrip tests now pass (39 total tm-sync tests)
- ocaml-sdls/fuzz: SA lifecycle, key state, EP PDU roundtrip (13 tests)
- ocaml-reed-solomon/fuzz: encode/decode with random errors (6 tests)
- ocaml-flexacm/fuzz: mode lookup, SNR monotonicity (6 tests)
160 new tests exercising security-critical code paths identified by
mapping known CVEs from C/reference implementations to our OCaml code:
- ocaml-sqlite (9): cyclic pages, oversized varints, record overflow,
wrong page kind, truncated WAL, out-of-bounds root, garbage files
- ocaml-cbort (12): deep nesting (CVE-2025-24302), indefinite-length
DoS, integer overflow in lengths, truncated input, invalid types
- ocaml-tar (10): path traversal (CVE-2021-32803), symlink escape
(CVE-2025-45582), oversized octal, truncated headers, checksum
- ocaml-http (14): CRLF header injection (CWE-113), null bytes,
Content-Length overflow, empty/duplicate headers
Also hardens validate_header_name_str to reject null bytes/empty names
- ocaml-jsonwt (21): "none" algorithm bypass (CVE-2015-9235) case
variations, algorithm confusion (CVE-2016-10555), malformed headers,
empty segments, extra dots, large payloads
- ocaml-cose (8): algorithm substitution, missing algorithm header,
malformed CBOR, wrong types, label overlap (RFC 9052)
- ocaml-git (18): tree path traversal, null bytes, symlink mode,
malformed tree data, pack delta attacks, pack format validation
- ocaml-tomlt (25): duplicate keys, integer overflow, malformed dates
(invalid month/day/hour/minute), deep nesting, long strings
- ocaml-squashfs (20): symlink traversal edge cases, fragment table
bounds, inode self-reference, compression bomb limits, bad superblock
- ocaml-cpio (23): symlink target validation, null bytes in filenames,
oversized filesize, truncated archives, invalid magic numbers
monotonic_now: increment changed from 1μs to 1ms, drift capped at 300s.
Rejects registration if clock drift exceeds cap instead of silently
issuing synthetic timestamps indefinitely.
verify/verify_receipt_only now return receipt_summary with total,
verified, failed, skipped counts so callers can detect partial
verification (e.g. 2 of 3 receipts tampered).
Both backends use Stdlib.Mutex (domain-safe, no Eio runtime needed).
Eio.Mutex would be better for fiber scheduling but requires an Eio
event loop — VDS is a data structure that should work without one.
Two new multicore stress tests:
- 4 domains appending 250 entries each concurrently (1000 total)
- 2 writer domains + 2 reader domains running simultaneously
Both pass — Mutex prevents data corruption across domains.
Both In_memory and Sqlite backends now have an internal Mutex.t that
serializes all operations. The Vds.t wrapper is pure dispatch — no
locks, no overhead.
Backends own their concurrency story:
- In_memory: Mutex protects Growable, Compact, Node_cache, Hashtbl
- Sqlite: Mutex protects in-memory cache; SQLite transactions for
disk atomicity
Performance: ~250k appends/s single-domain (uncontended Mutex adds
~5ns per operation — within noise). Multicore: domains block on the
Mutex for writes, which is correct for an append-only log.