fix(format): files we write now open in h5py and libhdf5

Two write-side bugs, both present in every release (the first at least
since v2.1.0), made libhdf5 refuse files written by clawhdf5. Our own
reader ignores both fields, and the interop suites only ever wrote f64
from our side, so nothing here caught them.

- Every f32 dataset: "sign bit position out of bounds". The float
  datatype encoder hard-coded the sign bit's position (bits 8-15 of the
  class bit field) to 63, which is right only for f64. It is now derived
  from the type: bit_offset + bit_precision - 1. This covered every
  agent store's embeddings, norms and activation weights.
- Every empty dataset: "invalid dataset size, likely file corruption".
  It was written with a real address and size 0, which trips libhdf5's
  `addr + size <= addr` overflow check. An empty contiguous dataset now
  gets the undefined address, as libhdf5 writes it. This covered every
  agent store without sessions or a knowledge graph.

Agent stores are rewritten in full at each checkpoint, so they become
readable at their next checkpoint on a fixed build; other files with f32
or empty datasets need rewriting. Both are recorded in
docs/known-issues.md.

Tests: the sign position byte for f32/f64, and h5py reading our f32
datasets (plain and chunked + deflate) bit for bit.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-24 12:00:26 -05:00
co-authored by Claude Opus 5.5
parent 1cceb930b2
commit 5e4aa1c6bf
4 changed files with 121 additions and 4 deletions
+52
View File
@@ -227,3 +227,55 @@ wrong structure. All four are fixed and covered by interop tests against
HDF5 2.0 at sizes that cross each boundary, including paged data blocks.
Files written by this crate are unaffected — this was purely a read-path bug.
## Every `f32` dataset we wrote was unreadable by h5py / libhdf5
**Status:** fixed 2026-09-23, after v2.7.0. **Every
release up to and including v2.7.0 is affected** — the encoder was already
wrong in v2.1.0.
The floating-point datatype message carries the position of the sign bit
(bits 8–15 of its class bit field). `clawhdf5-format` wrote 63 for every
float, which is correct only for `f64`. libhdf5 validates the field, so opening
any `f32` dataset written by this crate failed:
```
KeyError: 'Unable to synchronously open object (sign bit position out of bounds)'
```
That covers every agent store (`/memory/embeddings`, `norms` and
`activation_weights` are `f32`). `clawhdf5` itself ignores the field on read,
and the interop suites only ever wrote `f64` from our side, so nothing here
noticed.
**Fix:** the sign position is computed from the type (`bit_offset +
bit_precision - 1`: 15, 31, 63 for half, single, double). Regression tests:
`float_sign_location_is_the_top_bit_of_the_value` (byte level),
`clawhdf5_writes_f32_h5py_reads` and the agent's
`h5py_reads_every_dataset_of_an_agent_store`.
**Existing files:** an agent store is rewritten in full at every checkpoint, so
it becomes readable by h5py at its next checkpoint with a fixed build. Other
files with `f32` datasets need to be rewritten.
## Empty datasets we wrote were unreadable by h5py / libhdf5
**Status:** fixed 2026-09-23, after v2.7.0. Every
release up to and including v2.7.0 is affected.
A dataset with no elements was written with a real file address and a storage
size of 0. libhdf5 guards contiguous storage with an overflow check
(`addr + size <= addr`) that is always true when the size is 0, so it rejected
the dataset:
```
KeyError: 'Unable to synchronously open object (invalid dataset size, likely file corruption)'
```
In practice: every agent store without sessions or a knowledge graph — the
`/sessions` and `/knowledge_graph` datasets are empty until something is added
— could not be read by h5py even once the `f32` bug above was fixed. Found by
the same agent-store interop test.
**Fix:** an empty contiguous dataset gets the undefined address (all `0xff`),
which is what libhdf5 itself writes.