Commit Graph
21 Commits
Author SHA1 Message Date
Omar Sobh 429c29b76b docs: sync README/ROADMAP/CLAUDE/CHANGELOG with Tier 1-4 hardening work
CI / test (push) Failing after 3s
README.md:
- Fix badly stale LongMemEval numbers (badge said Hit@5 46%, table showed
  fabricated ~46%/~0.34/~72% figures that never matched BENCHMARKS.md's
  actual results of Hit@5 100% session / 84.4% turn-level, MRR 1.0/0.6597)
- Remove clawhdf5-types from the Crate Map — that crate was removed in an
  earlier cleanup pass but the README diagram was never updated; fix the
  crate count (16, not 17) and stale line-of-code figures (72,087/84K -> ~92K)
- Fix a dead #benchmarks badge anchor (no such heading exists) -> #performance
- Document the new clawhdf5-ann `parallel` feature (had no Feature Flags entry)
- Note WAL's CRC32 per-entry check, link the new tank LongMemEval/SIMD/
  vector-search reproduction section, update stale test-count comment
  (417+ -> 1,650+) and Phase 2 roadmap blurb (LongMemEval is now done)

ROADMAP.md:
- Check off "Academic benchmark cross-validation" (done via the tank
  LongMemEval re-run) and add a new "Recently closed out" section
  summarizing the Tier 3-4 hardening pass (Android JNI validation, pyo3
  bump, WAL CRC32, bounds-check audit + fuzz harness that found 3 real
  bugs, HNSW optional parallel feature, workspace.dependencies)
- Update stale test count (1,546 -> 1,650+) and last-updated date

CLAUDE.md: mention WAL's per-entry CRC32 check

CHANGELOG.md: add Security/Performance/Architecture/Documentation entries
under Unreleased summarizing all of Tiers 1-4 (this had not been touched
since 2026-06-04, predating the entire hardening pass)
2026-08-05 15:26:47 -07:00
osobhandClaude Opus 4.8 8534c7d204 feat: migrate-engine improvements (content validation, schema, streaming, incremental)
clawhdf5-migrate:
- Real content validation: the post-migration check reads the written HDF5
  back (new hdf5_reader) and compares actual content — chunk text, embeddings,
  and every session/entity/relation field — to the source, not just row counts.
  A representative sample of chunk rows is verified by default; --validate-full
  checks every row. A count-preserving corruption no longer passes.
- Configurable schema: SQL is built from a SchemaConfig (table + ordered column
  names, defaulting to the ZeroClaw layout) instead of hardcoded queries, with
  --chunks-table / --sessions-table / --entities-table / --relations-table.
- Streaming count pass: --dry-run does a COUNT(*)-only pass per table instead
  of loading every row.
- Incremental migration: --incremental reads the existing output, reads only
  source chunks with id greater than the last migrated id, and appends them
  (metadata groups refreshed from source) rather than re-migrating everything.

clawhdf5-format:
- read_as_f32 / read_as_f64 now decode IEEE-754 half-precision (2-byte) floats
  via a no_std-safe bit conversion — needed to read float16-stored embeddings
  back (e.g. for migrate's content validation), previously a TypeMismatch.

Tests: f16 read unit test; migrate tests for content-corruption detection,
custom table names, and incremental append; CLI smoke-tested end-to-end and the
dense/incremental output verified with h5py.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-04 02:19:31 +00:00
osobhandClaude Opus 4.8 0754afb7f2 feat: write multi-block fractal heaps (root indirect block)
Dense attribute and dense link storage capped at a single fractal-heap direct
block (~64 KiB of heap data — a few thousand objects); beyond that the writer
produced an invalid oversized block. Lift the cap with a root indirect block.

When the serialized objects don't fit in one direct block, build a root
indirect block (FHIB) over multiple direct blocks sized by the doubling table
(start 512, width 4, doubling per row up to 64 KiB). Objects are packed
row-major across blocks, each block carries its logical block offset, and heap
IDs encode each object's heap offset (block offset + position). The FRHP points
root -> FHIB with the row count; unused slots in the current rows are undefined.

The fractal-heap builder is unified: FractalHeapBlock now carries the full heap
blob, and a shared write_frhp helper serializes the header for both the
single-block and multi-block paths. The single-block path is unchanged
(byte-identical), so existing dense attrs/links stay valid.

Validated end-to-end: a 2,500-attribute object and a 2,500-link group round-trip
through our reader and are read correctly by h5py. Objects still may not span a
block (no huge-object path).

Tests: facade round-trips for multi-block dense attrs and dense links, plus an
h5py-gated interop test (verified against the real h5py environment).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-04 01:37:42 +00:00
osobhandClaude Opus 4.8 20ad16ab69 feat: write dense group link storage (fractal heap + v2 B-tree)
A group with more than 8 links (libhdf5's compact max_compact default) is now
written densely instead of as inline Link messages: the links live in a
single-direct-block fractal heap indexed by a v2 B-tree of type 5 (link-name
index), referenced from the group's LinkInfo message. This matches libhdf5's
compact->dense switchover and keeps large groups out of the object header.

- Extract the byte-identical fractal-heap builder shared by dense attributes
  and dense links, parameterized by heap_id_length / max_heap_size. Attributes
  keep 8 / 40; links use 7 / 32 to match libhdf5 (reverse-engineered: an
  h5py-written dense group uses heap_id_length 7, max_heap_size 32, type-5
  record = hash(4) + heap_id(7) = 11 bytes). This was the cause of an initial
  "object overruns end of direct block" error from h5py.
- build_group_oh gained an optional dense LinkInfo (omitting inline Link
  messages); the two-pass file assembly allocates each group's link blob after
  its object header and rebuilds it with real target addresses in the final
  pass (link-message size is address-independent, so layout is stable).

Validated end-to-end: our reader round-trips dense groups, h5py reads the
groups we write, and dense attributes remain byte-identical (still h5py-valid).
The agent's 9-dataset memory group now writes densely and round-trips. Single
direct block only (~a couple thousand links); indirect blocks remain a TODO.

Tests: facade round-trip (20-link dense group + compact sibling) and an
h5py-gated interop test confirming libhdf5 reads our dense groups.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-04 01:12:23 +00:00
osobhandClaude Opus 4.8 e0189cd5c4 harden: make the new readers panic-free on malformed input
The readers added this cycle parse untrusted bytes, so malformed/hostile
input must produce errors — never a panic, OOM, or unbounded recursion.
Audited each new surface and fixed the concrete vectors, each covered by an
adversarial regression test:

- Paged Fixed Array: `1 << max_nelmts_bits` shift overflow (u8 up to 255);
  element-count bounded by file size; element/page offset multiplies checked.
- H5S selection decoder: ALL/NONE validate they have the 16 bytes they claim
  to consume; hyperslab rank capped at 32 (H5S_MAX_RANK); iter_linear
  coordinate/stride/product arithmetic uses checked ops.
- VDS mapping parser: drop pre-allocation from the untrusted `nused`;
  bounds-check all selection slicing.
- scale-offset / N-Bit filters: `1 << minbits` overflow at minbits==64; N-Bit
  `bit_offset + precision` overflow; N-Bit type-tree recursion depth capped to
  stop a crafted nested tree from overflowing the stack; element counts bounded
  by the chunk's expected decompressed size (threaded the previously-unused
  chunk_size into both decoders) so a bogus count can't over-allocate.
- VDS assembly: a virtual dataset whose source is itself virtual (a cycle) now
  errors instead of recursing into a stack overflow.

16 new adversarial tests; full format suite (482 lib) + agent + facade green;
clippy clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-04 00:28:33 +00:00
osobhandClaude Opus 4.8 4fa7e89a46 feat: compress fixed-length string datasets (+ fix shared chunk-cache bug)
clawhdf5-agent: fixed-length string datasets (memory text chunks, session
summaries, ids, tags, entity/relation names) were stored uncompressed behind
a stale "chunked compound not yet supported" comment. Chunked writes work for
fixed-size string/compound datatypes like any other, so write_string_dataset
now chunks + deflates once a dataset's payload reaches 4 KiB — large,
redundant NullPad content compresses well while tiny metadata stays
contiguous (no chunk-overhead bloat). The dead `compress` parameter is
removed in favor of this size heuristic.

clawhdf5-format: enabling string compression exposed a latent bug — the
per-file ChunkCache built its chunk index once and reused it for every
chunked dataset in the file, keyed only by chunk coordinate with no dataset
discrimination. With one chunked dataset per file this never surfaced; with
two of different rank (a 1-D compressed string array and the 2-D embeddings
matrix) the first dataset's rank-1 index was reused for the second, panicking
with an out-of-bounds chunk coordinate. The cache now binds to a dataset by
its chunk-index address and rebinds — dropping the index, chunk-index map,
layout, and decompressed slots — whenever the dataset being read changes,
while still caching repeated/sequential access to the same dataset.

Tests: facade regression reading a 1-D compressed string dataset and a 2-D
compressed f32 dataset through one shared File cache (verified to panic
without the fix); existing agent e2e tests (large text chunks, migration
round-trip) now pass with compression on.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-03 22:48:39 +00:00
osobhandClaude Opus 4.8 57adc88320 test: regression for scale-offset float E-scale (raw + masked filter)
The HDF5 library does not implement the scale-offset filter's floating-point
E-scale mode. When asked for it (cd_values[0] = 1) it stores the chunk raw
(no minbits/minval header) and sets the chunk filter mask to skip the filter,
so such datasets read back verbatim purely by honoring the per-chunk filter
mask — no E-scale decoder is required.

Add a fixture written via the HDF5 low-level API (exact-representable values)
and a test asserting it reads back verbatim, locking in the filter-mask path.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-03 22:14:01 +00:00
osobhandClaude Opus 4.8 e6f0d8f161 feat: read external-file Virtual Datasets via a source resolver
VDS sources living in other files were previously unsupported because the
pure-byte read API has no filesystem. Add a resolver seam and wire a default.

clawhdf5-format:
- Add VdsSourceResolver (Fn(&str) -> Option<Vec<u8>>) and
  read_raw_data_full_with_resolver. read_virtual_data uses the resolver to
  fetch an external source file's bytes by its stored name, then reads the
  named source dataset from those bytes and scatters as usual. A resolver
  returning None leaves the region at fill (HDF5's missing-source behavior);
  an external source with no resolver at all is a clean error. read_raw_data_full
  is unchanged (delegates with no resolver).

clawhdf5:
- File now records the directory it was opened from and, for virtual layouts,
  reads through a default resolver that loads sibling source files relative to
  that directory. So File::open(virt).dataset(d).read_*() transparently
  assembles cross-file VDS. In-memory files (from_bytes) have no directory, so
  only same-file VDS resolves there.

Tests: format-layer external read with an injected resolver (and the
no-resolver error path), plus facade tests that drop both files in a temp dir
and read through File::open — covering successful resolution and the
missing-source-is-fill case.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-03 21:19:36 +00:00
osobhandClaude Opus 4.8 98ccc69411 feat: extend same-file VDS assembly to N dimensions
Generalize Selection iteration from 1-D to arbitrary rank: iter_linear(dims)
enumerates a selection's row-major linear indices over a dataspace of the
given shape (ALL, NONE, regular hyperslabs, points), which is the order HDF5
uses to pair virtual and source selections.

read_virtual_data now passes the full virtual/source dimensions instead of a
single extent, so multi-dimensional block mappings scatter to the correct
non-contiguous linear positions. read_named_dataset_raw returns the source
dataset's dimensions. The rank-1 restriction is removed; only external-file
sources remain unsupported.

Tests: 2-D integration fixture (vds_2d_same_file.h5: two 2x2 sources placed
as non-contiguous blocks in a 4x4 virtual) plus N-D iter_linear unit tests
(block, strided, ALL, rank-mismatch). The 1-D path is unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-03 20:57:12 +00:00
osobhandClaude Opus 4.8 908af40282 feat: assemble 1-D same-file Virtual Datasets (VDS)
A virtual layout previously returned UnsupportedVersion. Implement reading
for the common 1-D, same-file case, reverse-engineered and validated against
HDF5 2.0.

- Rewrite parse_vds_mappings to the real global-heap block format
  (version(1) · nused(length_size) · entries · checksum(4)), where each
  entry is source-file(null) · source-dataset(null) · source-selection ·
  virtual-selection. Block version 1 encodes a same-file source as a single
  0x04 marker in place of the file name; version 0 stores an explicit file
  name. The selections are H5S-serialized and self-describing in length, so
  they are decoded to find entry boundaries. The previous parser used a
  guessed layout that did not match real files.

- Extend Selection with decode_serialized() (H5S_select_serialize: ALL,
  NONE, and version-3 regular hyperslabs) and iter_linear_1d().

- Add read_virtual_data: resolve the mapping block from the global heap,
  read each same-file source dataset, and scatter its selected elements into
  the virtual buffer; unmapped regions stay at the zero fill value.
  External-file sources and N-D selections return a clean unsupported error.

Tests: real-file integration test (vds_same_file.h5: partial source slice +
fill gap), selection decoder unit tests built from the fixture bytes, and
same-file/external mapping-parser unit tests.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-03 20:01:51 +00:00
osobhandClaude Opus 4.8 a24fcb8be4 feat: read paged Fixed Array chunk indexes
A filtered, fixed-dimension dataset with more than one Fixed Array
data-block page (>1024 chunks by default) previously failed with
"paged Fixed Array data blocks not yet supported".

Implement the paged data-block layout, reverse-engineered and validated
against an HDF5 2.0 file:
- after the FADB prefix: a page-init bitmap (one bit per page, MSB-first
  within each byte), a 4-byte checksum, then the pages;
- each page is a fixed full-size slot of page_nelmts elements plus a
  4-byte checksum, with only the final page shorter;
- uninitialized pages still occupy their slot (zero-filled), so the
  bitmap — not a 0xFF sentinel — marks a whole page unallocated.

Element parsing is factored into parse_fa_element, shared by the
non-paged and paged paths.

Tests: real-file integration test against a minimal 2-page gzip fixture
(v4_fixed_array_paged.h5) plus a synthetic unit test covering a
multi-byte/MSB-first bitmap, a skipped uninitialized page, and a short
final page.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-03 17:41:31 +00:00
osobhandClaude Opus 4.8 4b1f4e369a docs: changelog for array-typed datatype reads
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 20:12:33 +00:00
osobhandClaude Opus 4.8 dbd683dcaf docs: changelog for compound/array N-Bit support
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 13:04:22 +00:00
osobhandClaude Opus 4.8 8c68b5de33 docs: changelog for reduced-precision integer sign-extension
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 12:48:23 +00:00
osobhandClaude Opus 4.8 bff039fa29 docs: changelog for float D-scale and N-Bit filter support
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 12:36:50 +00:00
osobhandClaude Opus 4.8 ec7357de45 docs: changelog entry for scale-offset filter support
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 12:18:55 +00:00
osobhandClaude Opus 4.8 19ca662975 docs: changelog entry for HDF5 2.0 (version-5) read fixes
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 12:01:58 +00:00
osobhandClaude Opus 4.8 3ff501c8ef docs: add Unreleased changelog section for post-2.1.0 changes
Records the parallel chunk-compression perf change and the doc sweep that
landed after the v2.1.0 tag.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 11:42:43 +00:00
osobhandClaude Opus 4.8 41a8fb7734 docs: finalize CHANGELOG for v2.1.0 release
Fold the Unreleased HNSW / Python 3.14 entries into a single v2.1.0
(2026-06-03) section being cut and tagged now.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 10:39:34 +00:00
osobhandClaude Opus 4.8 4e2f4c3d2a docs: changelog entry for HNSW integration and Python 3.14 build fix
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-03 10:10:31 +00:00
redclawsystems 3f222f6956 Merge pull request 'docs(clawhdf5): document DType variants, fix unresolved doc links' (#17) from sdlc-docs/clawhdf5-types-20260514-165210 into main 2026-05-14 23:54:48 +00:00