chore: cleanup pass — remove empty types stub, implement superblock v4, reconcile plan docs

- Remove clawhdf5-types (empty 1-line stub crate; type defs already live in
  clawhdf5-format). Update workspace Cargo.toml and CLAUDE.md accordingly.
- Implement HDF5 superblock v4 (page-buffer mode) read and write support in
  clawhdf5-format: Superblock::parse_v4, page_size field, v4 serialize
  branch, and FileWriter::with_page_size. This was the one task left
  unimplemented from docs/superpowers/plans/2026-06-29-format-write-extensions.md.
- Reconcile the three docs/superpowers/plans/*.md docs (filter codecs,
  format write extensions, MPI-IO VOL) against actual shipped code: they
  were pre-work plans for d6c4d4f (2026-06-30) committed to git late on
  2026-08-03 with all checkboxes still unchecked. Mark completed tasks done
  and add a status note so they read as historical records, not open work.
- Refresh ROADMAP.md's "What's Next" section against current repo state.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-03 08:11:31 -07:00
co-authored by Claude Sonnet 5
parent b1fc23e975
commit 6b1ea450f5
11 changed files with 300 additions and 120 deletions
+2 -3
View File
@@ -5,12 +5,11 @@ Pure-Rust HDF5 format implementation with HNSW vector search, WAL-backed persist
## Architecture ## Architecture
Cargo workspace with 17 crates under `crates/`: Cargo workspace with 16 crates under `crates/` (plus `libaec-sys`, an internal FFI bindings crate for the optional `szip` feature):
| Crate | Role | | Crate | Role |
|-------|------| |-------|------|
| `clawhdf5-types` | Shared type definitions and physical constants | | `clawhdf5-format` | HDF5 binary spec parser (superblock, B-tree, heap) — also holds shared type definitions and physical constants |
| `clawhdf5-format` | HDF5 binary spec parser (superblock, B-tree, heap) |
| `clawhdf5-io` | Read/write implementation | | `clawhdf5-io` | Read/write implementation |
| `clawhdf5-filters` | Compression filters (gzip, LZ4, Zstd, Blosc) | | `clawhdf5-filters` | Compression filters (gzip, LZ4, Zstd, Blosc) |
| `clawhdf5-derive` | Proc-macro derive for HDF5-serializable structs | | `clawhdf5-derive` | Proc-macro derive for HDF5-serializable structs |
-1
View File
@@ -1,7 +1,6 @@
[workspace] [workspace]
members = [ members = [
"crates/clawhdf5-format", "crates/clawhdf5-format",
"crates/clawhdf5-types",
"crates/clawhdf5-io", "crates/clawhdf5-io",
"crates/clawhdf5-filters", "crates/clawhdf5-filters",
"crates/clawhdf5-derive", "crates/clawhdf5-derive",
+13 -5
View File
@@ -151,12 +151,20 @@ All 8 tracks delivered. 1,546 tests passing, zero clippy warnings.
## What's Next ## What's Next
- [ ] CI/CD pipeline — GitHub Actions or Gitea Actions for automated testing Verified against current repo state on 2026-08-03 (see also `docs/superpowers/plans/` for the filter-codec/format-write/MPI-IO work, now shipped):
- [ ] CI/CD pipeline — still no GitHub/Gitea Actions workflow in the repo; automated testing is manual only
- [ ] Academic benchmark cross-validation — reproduce MemX/LongMemEval under identical conditions - [ ] Academic benchmark cross-validation — reproduce MemX/LongMemEval under identical conditions
- [ ] TypeScript bridge — full npm package via `clawhdf5-napi` (scaffolding exists) - [ ] TypeScript bridge — `clawhdf5-napi` has no `package.json`; it's still Rust-only scaffolding, not a publishable npm package
- [ ] Publish crates to crates.io - [ ] Publish crates to crates.io — no `publish` config anywhere in the workspace yet
- [ ] Python wheel distribution via maturin for `clawhdf5-py` - [ ] Python wheel distribution via maturin `crates/clawhdf5-py/pyproject.toml` exists (maturin-buildable locally) but wheels aren't published anywhere
### Recently closed out (2026-08-03 cleanup pass)
- [x] Removed `clawhdf5-types` — it was an empty 1-line stub crate; shared type definitions already live in `clawhdf5-format`, so CLAUDE.md and the workspace manifest were corrected instead of filling it in
- [x] Superblock v4 (page-buffer mode) read/write — the only unimplemented task from `docs/superpowers/plans/2026-06-29-format-write-extensions.md`; now done (`Superblock::parse_v4`/`serialize`, `FileWriter::with_page_size`)
- [x] Reconciled the three `docs/superpowers/plans/*.md` docs against actual shipped code — they were pre-work plans for `d6c4d4f` (2026-06-30), committed to git late; checkboxes now reflect reality
--- ---
_Last updated: 2026-04-12_ _Last updated: 2026-08-03_
+47 -3
View File
@@ -932,6 +932,8 @@ pub struct FileWriter {
alignment_threshold: usize, alignment_threshold: usize,
/// Global alignment boundary in bytes (0 = disabled). /// Global alignment boundary in bytes (0 = disabled).
alignment_bytes: usize, alignment_bytes: usize,
/// Page size for page-buffer mode. When set, a v4 superblock is written.
page_size: Option<u32>,
} }
impl Default for FileWriter { impl Default for FileWriter {
@@ -948,6 +950,7 @@ impl FileWriter {
groups: Vec::new(), groups: Vec::new(),
alignment_threshold: 0, alignment_threshold: 0,
alignment_bytes: 0, alignment_bytes: 0,
page_size: None,
} }
} }
@@ -961,6 +964,14 @@ impl FileWriter {
self self
} }
/// Enable page-buffer mode with the given page size. Writing this causes
/// the file to be written with a v4 superblock (page_size field) instead
/// of the default v3.
pub fn with_page_size(&mut self, page_size: u32) -> &mut Self {
self.page_size = Some(page_size);
self
}
pub fn create_group(&mut self, name: &str) -> GroupBuilder { pub fn create_group(&mut self, name: &str) -> GroupBuilder {
GroupBuilder::new(name) GroupBuilder::new(name)
} }
@@ -979,6 +990,7 @@ impl FileWriter {
} }
pub fn finish(self) -> Result<Vec<u8>, FormatError> { pub fn finish(self) -> Result<Vec<u8>, FormatError> {
let page_size = self.page_size;
struct DsFlat { struct DsFlat {
name: String, name: String,
dt: Datatype, dt: Datatype,
@@ -1271,8 +1283,14 @@ impl FileWriter {
let actual_ds_oh_sizes: Vec<usize> = dummy_blobs.iter().map(|b| b.oh_bytes.len()).collect(); let actual_ds_oh_sizes: Vec<usize> = dummy_blobs.iter().map(|b| b.oh_bytes.len()).collect();
// Pass 2: compute real addresses // Pass 2: compute real addresses
let root_group_addr = SUPERBLOCK_SIZE as u64; // v4 superblocks add a 4-byte page_size field before the checksum.
let mut cursor2 = SUPERBLOCK_SIZE + root_oh_size; let superblock_size = if page_size.is_some() {
SUPERBLOCK_SIZE + 4
} else {
SUPERBLOCK_SIZE
};
let root_group_addr = superblock_size as u64;
let mut cursor2 = superblock_size + root_oh_size;
// Each group is laid out as: object header, then (if dense) its link // Each group is laid out as: object header, then (if dense) its link
// blob, then (if dense) its attribute blob. Link blobs are sized with // blob, then (if dense) its attribute blob. Link blobs are sized with
@@ -1445,7 +1463,7 @@ impl FileWriter {
let mut buf = Vec::with_capacity(cursor2); let mut buf = Vec::with_capacity(cursor2);
let sb = Superblock { let sb = Superblock {
version: 3, version: if page_size.is_some() { 4 } else { 3 },
offset_size: OFFSET_SIZE, offset_size: OFFSET_SIZE,
length_size: LENGTH_SIZE, length_size: LENGTH_SIZE,
base_address: 0, base_address: 0,
@@ -1459,6 +1477,7 @@ impl FileWriter {
consistency_flags: 0, consistency_flags: 0,
superblock_extension_address: Some(u64::MAX), superblock_extension_address: Some(u64::MAX),
checksum: None, checksum: None,
page_size,
}; };
buf.extend_from_slice(&sb.serialize()); buf.extend_from_slice(&sb.serialize());
@@ -2100,4 +2119,29 @@ mod tests {
other => panic!("expected External link, got {other:?}"), other => panic!("expected External link, got {other:?}"),
} }
} }
#[test]
fn file_writer_v4_superblock() {
let mut fw = FileWriter::new();
fw.with_page_size(4096);
fw.create_dataset("data").with_f64_data(&[1.0, 2.0]);
let bytes = fw.finish().unwrap();
let sig = signature::find_signature(&bytes).unwrap();
let sb = Superblock::parse(&bytes, sig).unwrap();
assert_eq!(sb.version, 4, "expected superblock v4");
assert_eq!(sb.page_size, Some(4096));
}
#[test]
fn file_writer_default_superblock_is_v3() {
let mut fw = FileWriter::new();
fw.create_dataset("data").with_f64_data(&[1.0, 2.0]);
let bytes = fw.finish().unwrap();
let sig = signature::find_signature(&bytes).unwrap();
let sb = Superblock::parse(&bytes, sig).unwrap();
assert_eq!(sb.version, 3);
assert_eq!(sb.page_size, None);
}
} }
+157 -1
View File
@@ -39,6 +39,8 @@ pub struct Superblock {
pub superblock_extension_address: Option<u64>, pub superblock_extension_address: Option<u64>,
/// CRC32C checksum (v2/v3 only). /// CRC32C checksum (v2/v3 only).
pub checksum: Option<u32>, pub checksum: Option<u32>,
/// Page size for page-buffer mode (v4 only). `None` for v0v3.
pub page_size: Option<u32>,
} }
/// Read an unsigned integer of `size` bytes (LE) from `data` at `pos`. /// Read an unsigned integer of `size` bytes (LE) from `data` at `pos`.
@@ -125,7 +127,8 @@ impl Superblock {
/// Serialize this superblock to bytes. /// Serialize this superblock to bytes.
/// ///
/// Always writes v2/v3 format. Computes and appends Jenkins lookup3 checksum. /// Writes v2/v3 format, or v4 (with `page_size`) when `self.version == 4`.
/// Computes and appends Jenkins lookup3 checksum.
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(48); let mut buf = Vec::with_capacity(48);
buf.extend_from_slice(&HDF5_SIGNATURE); buf.extend_from_slice(&HDF5_SIGNATURE);
@@ -142,6 +145,11 @@ impl Superblock {
Self::write_offset(&mut buf, self.eof_address, self.offset_size); Self::write_offset(&mut buf, self.eof_address, self.offset_size);
// root_group_address // root_group_address
Self::write_offset(&mut buf, self.root_group_address, self.offset_size); Self::write_offset(&mut buf, self.root_group_address, self.offset_size);
// page_size (v4 only)
if self.version >= 4 {
let ps = self.page_size.unwrap_or(0);
buf.extend_from_slice(&ps.to_le_bytes());
}
// checksum // checksum
let checksum = crate::checksum::jenkins_lookup3(&buf); let checksum = crate::checksum::jenkins_lookup3(&buf);
buf.extend_from_slice(&checksum.to_le_bytes()); buf.extend_from_slice(&checksum.to_le_bytes());
@@ -179,6 +187,7 @@ impl Superblock {
0 => Self::parse_v0(d), 0 => Self::parse_v0(d),
1 => Self::parse_v1(d), 1 => Self::parse_v1(d),
2 | 3 => Self::parse_v2v3(d, version), 2 | 3 => Self::parse_v2v3(d, version),
4 => Self::parse_v4(d),
v => Err(FormatError::UnsupportedVersion(v)), v => Err(FormatError::UnsupportedVersion(v)),
} }
} }
@@ -235,6 +244,7 @@ impl Superblock {
consistency_flags, consistency_flags,
superblock_extension_address: None, superblock_extension_address: None,
checksum: None, checksum: None,
page_size: None,
}) })
} }
@@ -292,6 +302,7 @@ impl Superblock {
consistency_flags, consistency_flags,
superblock_extension_address: None, superblock_extension_address: None,
checksum: None, checksum: None,
page_size: None,
}) })
} }
@@ -348,6 +359,71 @@ impl Superblock {
consistency_flags, consistency_flags,
superblock_extension_address: Some(superblock_extension_address), superblock_extension_address: Some(superblock_extension_address),
checksum: Some(stored_checksum), checksum: Some(stored_checksum),
page_size: None,
})
}
fn parse_v4(d: &[u8]) -> Result<Superblock, FormatError> {
// Same layout as v2/v3, plus page_size(4) inserted before the checksum.
ensure_len(d, 12)?;
let offset_size = d[9];
let length_size = d[10];
validate_sizes(offset_size, length_size)?;
let consistency_flags = d[11] as u32;
let os = offset_size as usize;
// 4 addresses + page_size(4) + checksum(4)
let total = 12 + 4 * os + 4 + 4;
ensure_len(d, total)?;
let mut pos = 12;
let base_address = read_offset(d, pos, offset_size)?;
pos += os;
let superblock_extension_address = read_offset(d, pos, offset_size)?;
pos += os;
let eof_address = read_offset(d, pos, offset_size)?;
pos += os;
let root_group_address = read_offset(d, pos, offset_size)?;
pos += os;
let page_size = LittleEndian::read_u32(&d[pos..pos + 4]);
pos += 4;
let stored_checksum = LittleEndian::read_u32(&d[pos..pos + 4]);
pos += 4;
#[cfg(feature = "checksum")]
{
let computed = crate::checksum::jenkins_lookup3(&d[..pos - 4]);
if computed != stored_checksum {
return Err(FormatError::ChecksumMismatch {
expected: stored_checksum,
computed,
});
}
}
#[cfg(not(feature = "checksum"))]
{
let _ = pos;
}
Ok(Superblock {
version: 4,
offset_size,
length_size,
base_address,
eof_address,
root_group_address,
group_leaf_node_k: None,
group_internal_node_k: None,
indexed_storage_internal_node_k: None,
free_space_address: None,
driver_info_address: None,
consistency_flags,
superblock_extension_address: Some(superblock_extension_address),
checksum: Some(stored_checksum),
page_size: Some(page_size),
}) })
} }
} }
@@ -652,4 +728,84 @@ mod tests {
let new_eof = sb.refresh_eof(&data, 0).unwrap(); let new_eof = sb.refresh_eof(&data, 0).unwrap();
assert_eq!(new_eof, old_eof); assert_eq!(new_eof, old_eof);
} }
#[test]
fn parse_v4_with_page_size() {
// Superblock v4 = v2/v3 layout + page_size(4) before checksum.
let mut buf = Vec::new();
buf.extend_from_slice(&HDF5_SIGNATURE);
buf.push(4); // version = 4
buf.push(8); // offset_size
buf.push(8); // length_size
buf.push(0); // consistency_flags
write_offset(&mut buf, 0, 8); // base_address
write_offset(&mut buf, u64::MAX, 8); // superblock_extension_address = UNDEF
write_offset(&mut buf, 512, 8); // eof_address
write_offset(&mut buf, 96, 8); // root_group_address
buf.extend_from_slice(&4096u32.to_le_bytes()); // page_size (v4 addition)
let checksum = crate::checksum::jenkins_lookup3(&buf);
buf.extend_from_slice(&checksum.to_le_bytes());
let sb = Superblock::parse(&buf, 0).unwrap();
assert_eq!(sb.version, 4);
assert_eq!(sb.offset_size, 8);
assert_eq!(sb.eof_address, 512);
assert_eq!(sb.root_group_address, 96);
assert_eq!(sb.page_size, Some(4096));
}
#[test]
fn serialize_v4_roundtrip() {
let sb = Superblock {
version: 4,
offset_size: 8,
length_size: 8,
base_address: 0,
eof_address: 1024,
root_group_address: 96,
group_leaf_node_k: None,
group_internal_node_k: None,
indexed_storage_internal_node_k: None,
free_space_address: None,
driver_info_address: None,
consistency_flags: 0,
superblock_extension_address: Some(u64::MAX),
checksum: None,
page_size: Some(4096),
};
let bytes = sb.serialize();
let parsed = Superblock::parse(&bytes, 0).unwrap();
assert_eq!(parsed.version, 4);
assert_eq!(parsed.page_size, Some(4096));
assert_eq!(parsed.eof_address, 1024);
assert_eq!(parsed.root_group_address, 96);
}
#[test]
fn serialize_v3_unchanged_by_page_size_field() {
// v3 (page_size: None) must serialize identically to before this feature existed.
let sb = Superblock {
version: 3,
offset_size: 8,
length_size: 8,
base_address: 0,
eof_address: 2048,
root_group_address: 96,
group_leaf_node_k: None,
group_internal_node_k: None,
indexed_storage_internal_node_k: None,
free_space_address: None,
driver_info_address: None,
consistency_flags: 0,
superblock_extension_address: Some(u64::MAX),
checksum: None,
page_size: None,
};
let bytes = sb.serialize();
// sig(8) + version/offset/length/flags(4) + 4 addresses(8 each) + checksum(4)
assert_eq!(bytes.len(), 8 + 4 + 4 * 8 + 4);
let parsed = Superblock::parse(&bytes, 0).unwrap();
assert_eq!(parsed.version, 3);
assert_eq!(parsed.page_size, None);
}
} }
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "clawhdf5-types"
version = "2.1.0"
edition = "2024"
description = "HDF5 type system definitions for rustyhdf5"
license = "MIT"
repository = "https://github.com/redclawsystems/clawhdf5"
readme = "README.md"
keywords = ["hdf5", "types", "science", "data"]
categories = ["data-structures", "science"]
-21
View File
@@ -1,21 +0,0 @@
# clawhdf5-types
[![crates.io](https://img.shields.io/crates/v/clawhdf5-types.svg)](https://crates.io/crates/clawhdf5-types)
[![docs.rs](https://docs.rs/clawhdf5-types/badge.svg)](https://docs.rs/clawhdf5-types)
HDF5 type system definitions for the clawhdf5 ecosystem.
## Features
- Complete HDF5 datatype representations (integer, float, string, compound, array, enum, etc.)
- Type conversion and validation utilities
## Usage
```rust
use clawhdf5_types::HDF5Type;
```
## License
MIT
-1
View File
@@ -1 +0,0 @@
//! HDF5 type system representation.
@@ -1,5 +1,7 @@
# Filter Codecs Implementation Plan # Filter Codecs Implementation Plan
> **Status (2026-08-03):** Implemented — shipped in commit `d6c4d4f` (2026-06-30), with FFI/constant fixes in `cb0b0e9`/`e91f7fc`. This doc was authored 2026-06-29 as the pre-work plan and committed to the repo retroactively on 2026-08-03; checkboxes below have been marked complete to match. Treat this as a historical record, not an open task list.
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add SZIP decompression (filter ID 4) and N-Bit E-scale decompression (scale type 1 of filter ID 6) to the clawhdf5-format crate. **Goal:** Add SZIP decompression (filter ID 4) and N-Bit E-scale decompression (scale type 1 of filter ID 6) to the clawhdf5-format crate.
@@ -33,7 +35,7 @@
- E-scale formula: `value = minval + code * 2^E` where `E = cd[1] as i32` (may be negative for sub-unit precision). Compare to D-scale: `value = minval + code / 10^D`. - E-scale formula: `value = minval + code * 2^E` where `E = cd[1] as i32` (may be negative for sub-unit precision). Compare to D-scale: `value = minval + code / 10^D`.
- The binary layout (minbits, minval, 8 reserved bytes, packed MSB-first codes) is IDENTICAL to D-scale. Only the reconstruction formula differs. - The binary layout (minbits, minval, 8 reserved bytes, packed MSB-first codes) is IDENTICAL to D-scale. Only the reconstruction formula differs.
- [ ] **Step 1: Write the failing test** - [x] **Step 1: Write the failing test**
In `crates/clawhdf5-format/src/filters.rs`, inside the existing `#[cfg(test)] mod tests` block, add: In `crates/clawhdf5-format/src/filters.rs`, inside the existing `#[cfg(test)] mod tests` block, add:
@@ -86,7 +88,7 @@ fn scaleoffset_float_escale_negative_exponent() {
} }
``` ```
- [ ] **Step 2: Run tests to verify they fail** - [x] **Step 2: Run tests to verify they fail**
```bash ```bash
cargo test -p clawhdf5-format scaleoffset_float_escale 2>&1 | head -30 cargo test -p clawhdf5-format scaleoffset_float_escale 2>&1 | head -30
@@ -94,7 +96,7 @@ cargo test -p clawhdf5-format scaleoffset_float_escale 2>&1 | head -30
Expected: FAIL — `"UnsupportedFilter(6)"` or similar. Expected: FAIL — `"UnsupportedFilter(6)"` or similar.
- [ ] **Step 3: Implement E-scale in scaleoffset_decompress** - [x] **Step 3: Implement E-scale in scaleoffset_decompress**
In `crates/clawhdf5-format/src/filters.rs`, change the dispatch block (around line 96): In `crates/clawhdf5-format/src/filters.rs`, change the dispatch block (around line 96):
@@ -155,7 +157,7 @@ Then in the float reconstruction block (currently the `if is_float { ... }` bran
} else { } else {
``` ```
- [ ] **Step 4: Run tests to verify they pass** - [x] **Step 4: Run tests to verify they pass**
```bash ```bash
cargo test -p clawhdf5-format scaleoffset_float_escale 2>&1 cargo test -p clawhdf5-format scaleoffset_float_escale 2>&1
@@ -163,7 +165,7 @@ cargo test -p clawhdf5-format scaleoffset_float_escale 2>&1
Expected: both tests PASS. Expected: both tests PASS.
- [ ] **Step 5: Run full test suite** - [x] **Step 5: Run full test suite**
```bash ```bash
cargo test -p clawhdf5-format 2>&1 | tail -10 cargo test -p clawhdf5-format 2>&1 | tail -10
@@ -171,7 +173,7 @@ cargo test -p clawhdf5-format 2>&1 | tail -10
Expected: all tests pass, zero failures. Expected: all tests pass, zero failures.
- [ ] **Step 6: Commit** - [x] **Step 6: Commit**
```bash ```bash
git add crates/clawhdf5-format/src/filters.rs git add crates/clawhdf5-format/src/filters.rs
@@ -198,7 +200,7 @@ git commit -m "feat: add scale-offset E-scale (float binary-exponent) decompress
- `cd[2]` (pixels per scan line): not used for decompression. - `cd[2]` (pixels per scan line): not used for decompression.
- The `libaec` library exposes `aec_decode_init`, `aec_decode`, `aec_decode_end` (struct `aec_stream`). - The `libaec` library exposes `aec_decode_init`, `aec_decode`, `aec_decode_end` (struct `aec_stream`).
- [ ] **Step 1: Write the failing test** - [x] **Step 1: Write the failing test**
In `crates/clawhdf5-format/src/filters_szip.rs` (create the file): In `crates/clawhdf5-format/src/filters_szip.rs` (create the file):
@@ -308,7 +310,7 @@ mod tests {
} }
``` ```
- [ ] **Step 2: Run the new test** - [x] **Step 2: Run the new test**
```bash ```bash
cargo test -p clawhdf5-format szip_disabled_returns_unsupported 2>&1 cargo test -p clawhdf5-format szip_disabled_returns_unsupported 2>&1
@@ -316,7 +318,7 @@ cargo test -p clawhdf5-format szip_disabled_returns_unsupported 2>&1
Expected: the file doesn't compile yet (module not declared). That's the expected failure mode. Expected: the file doesn't compile yet (module not declared). That's the expected failure mode.
- [ ] **Step 3: Add Cargo.toml feature and build.rs** - [x] **Step 3: Add Cargo.toml feature and build.rs**
In `crates/clawhdf5-format/Cargo.toml`, add to `[dependencies]`: In `crates/clawhdf5-format/Cargo.toml`, add to `[dependencies]`:
@@ -363,7 +365,7 @@ fn main() {
Note: `libaec-sys` is a crate that provides raw bindings. If that crate doesn't exist on crates.io with that exact name, use `libaec-sys = { git = "..." }` or add `aec-sys` as a local crate (see Task 3 below for the fallback path). Note: `libaec-sys` is a crate that provides raw bindings. If that crate doesn't exist on crates.io with that exact name, use `libaec-sys = { git = "..." }` or add `aec-sys` as a local crate (see Task 3 below for the fallback path).
- [ ] **Step 4: Declare the module in lib.rs** - [x] **Step 4: Declare the module in lib.rs**
In `crates/clawhdf5-format/src/lib.rs`, add: In `crates/clawhdf5-format/src/lib.rs`, add:
@@ -373,7 +375,7 @@ mod filters_szip;
(Place it alongside the other `mod filters;` declaration.) (Place it alongside the other `mod filters;` declaration.)
- [ ] **Step 5: Hook szip_decompress into decompress_chunk** - [x] **Step 5: Hook szip_decompress into decompress_chunk**
In `crates/clawhdf5-format/src/filters.rs`, change the dispatch inside `decompress_chunk`: In `crates/clawhdf5-format/src/filters.rs`, change the dispatch inside `decompress_chunk`:
@@ -396,7 +398,7 @@ use crate::filter_pipeline::{
(Add `FILTER_SZIP` to the existing import.) (Add `FILTER_SZIP` to the existing import.)
- [ ] **Step 6: Run tests without szip feature** - [x] **Step 6: Run tests without szip feature**
```bash ```bash
cargo test -p clawhdf5-format 2>&1 | tail -15 cargo test -p clawhdf5-format 2>&1 | tail -15
@@ -404,7 +406,7 @@ cargo test -p clawhdf5-format 2>&1 | tail -15
Expected: all existing tests pass; `szip_disabled_returns_unsupported` passes. Expected: all existing tests pass; `szip_disabled_returns_unsupported` passes.
- [ ] **Step 7: Commit** - [x] **Step 7: Commit**
```bash ```bash
git add crates/clawhdf5-format/Cargo.toml \ git add crates/clawhdf5-format/Cargo.toml \
@@ -430,7 +432,7 @@ git commit -m "feat: add SZIP filter hook with libaec FFI (feature-gated, disabl
**Interfaces:** **Interfaces:**
- Produces: `pub unsafe fn aec_buffer_decode(src: *const u8, src_len: usize, dst: *mut u8, dst_len: *mut usize, bits_per_sample: u32, block_size: u32, flags: u32) -> i32` - Produces: `pub unsafe fn aec_buffer_decode(src: *const u8, src_len: usize, dst: *mut u8, dst_len: *mut usize, bits_per_sample: u32, block_size: u32, flags: u32) -> i32`
- [ ] **Step 1: Create the sys crate** - [x] **Step 1: Create the sys crate**
Create `crates/libaec-sys/Cargo.toml`: Create `crates/libaec-sys/Cargo.toml`:
@@ -504,11 +506,11 @@ mod tests {
} }
``` ```
- [ ] **Step 2: Add to workspace** - [x] **Step 2: Add to workspace**
In the root `Cargo.toml`, add `"crates/libaec-sys"` to `[workspace] members`. In the root `Cargo.toml`, add `"crates/libaec-sys"` to `[workspace] members`.
- [ ] **Step 3: Update clawhdf5-format dependency** - [x] **Step 3: Update clawhdf5-format dependency**
In `crates/clawhdf5-format/Cargo.toml`, change: In `crates/clawhdf5-format/Cargo.toml`, change:
@@ -522,7 +524,7 @@ to:
libaec-sys = { path = "../libaec-sys", version = "0.1", optional = true } libaec-sys = { path = "../libaec-sys", version = "0.1", optional = true }
``` ```
- [ ] **Step 4: Run tests** - [x] **Step 4: Run tests**
```bash ```bash
cargo test -p libaec-sys 2>&1 | tail -10 cargo test -p libaec-sys 2>&1 | tail -10
@@ -531,7 +533,7 @@ cargo test -p clawhdf5-format 2>&1 | tail -10
Expected: both pass. Expected: both pass.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add crates/libaec-sys/ Cargo.toml crates/clawhdf5-format/Cargo.toml git add crates/libaec-sys/ Cargo.toml crates/clawhdf5-format/Cargo.toml
@@ -547,7 +549,7 @@ git commit -m "feat: add libaec-sys workspace crate for SZIP FFI bindings"
**Background:** This test only runs when the `szip` feature is enabled AND libaec is installed. It validates that we can round-trip a known dataset (u8 values 0-63, 8 pixels per block, EC mode). **Background:** This test only runs when the `szip` feature is enabled AND libaec is installed. It validates that we can round-trip a known dataset (u8 values 0-63, 8 pixels per block, EC mode).
- [ ] **Step 1: Add integration test** - [x] **Step 1: Add integration test**
In `crates/clawhdf5-format/src/filters_szip.rs`, inside `#[cfg(test)] mod tests`, add: In `crates/clawhdf5-format/src/filters_szip.rs`, inside `#[cfg(test)] mod tests`, add:
@@ -611,7 +613,7 @@ extern "C" {
} }
``` ```
- [ ] **Step 2: Run the integration test (requires libaec installed)** - [x] **Step 2: Run the integration test (requires libaec installed)**
```bash ```bash
# Install libaec if not present: sudo apt install libaec-dev # Install libaec if not present: sudo apt install libaec-dev
@@ -620,7 +622,7 @@ cargo test -p clawhdf5-format --features szip szip_ec_roundtrip_u8 2>&1
Expected: PASS when libaec is installed. Expected: PASS when libaec is installed.
- [ ] **Step 3: Run full suite without szip feature to verify no regressions** - [x] **Step 3: Run full suite without szip feature to verify no regressions**
```bash ```bash
cargo test -p clawhdf5-format 2>&1 | tail -10 cargo test -p clawhdf5-format 2>&1 | tail -10
@@ -628,7 +630,7 @@ cargo test -p clawhdf5-format 2>&1 | tail -10
Expected: all tests pass. Expected: all tests pass.
- [ ] **Step 4: Commit** - [x] **Step 4: Commit**
```bash ```bash
git add crates/clawhdf5-format/src/filters_szip.rs crates/libaec-sys/src/lib.rs git add crates/clawhdf5-format/src/filters_szip.rs crates/libaec-sys/src/lib.rs
@@ -1,5 +1,7 @@
# Format Write Extensions Implementation Plan # Format Write Extensions Implementation Plan
> **Status (2026-08-03):** Implemented. Tasks 13 (external links, VDS mapping serialization, VDS `FileWriter` API) shipped in commit `d6c4d4f` (2026-06-30). Tasks 45 (superblock v4 read/write) were not part of that commit and were completed separately as part of this cleanup pass (2026-08-03) — see `Superblock::parse_v4`/`serialize` and `FileWriter::with_page_size` in `crates/clawhdf5-format`. This doc was authored 2026-06-29 as the pre-work plan and committed to the repo retroactively; checkboxes below have been marked complete to match current state. Treat this as a historical record, not an open task list.
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add three write-side features to clawhdf5-format: (1) external link creation via `GroupBuilder`, (2) external VDS (Virtual Dataset Source) layout writes, and (3) superblock v4 read/write for page-buffering-aware files. **Goal:** Add three write-side features to clawhdf5-format: (1) external link creation via `GroupBuilder`, (2) external VDS (Virtual Dataset Source) layout writes, and (3) superblock v4 read/write for page-buffering-aware files.
@@ -31,7 +33,7 @@
**Interfaces:** **Interfaces:**
- Produces: `GroupBuilder::add_external_link(&mut self, name: &str, target_file: &str, target_path: &str) -> &mut Self` - Produces: `GroupBuilder::add_external_link(&mut self, name: &str, target_file: &str, target_path: &str) -> &mut Self`
- [ ] **Step 1: Write the failing test** - [x] **Step 1: Write the failing test**
At the bottom of the `#[cfg(test)]` block in `crates/clawhdf5-format/src/file_writer.rs`, add: At the bottom of the `#[cfg(test)]` block in `crates/clawhdf5-format/src/file_writer.rs`, add:
@@ -79,7 +81,7 @@ fn external_link_write_roundtrip() {
} }
``` ```
- [ ] **Step 2: Run test to verify it fails** - [x] **Step 2: Run test to verify it fails**
```bash ```bash
cargo test -p clawhdf5-format external_link_write_roundtrip 2>&1 | head -20 cargo test -p clawhdf5-format external_link_write_roundtrip 2>&1 | head -20
@@ -87,7 +89,7 @@ cargo test -p clawhdf5-format external_link_write_roundtrip 2>&1 | head -20
Expected: compile error — `add_external_link` not found. Expected: compile error — `add_external_link` not found.
- [ ] **Step 3: Find GroupBuilder in file_writer.rs and add the method** - [x] **Step 3: Find GroupBuilder in file_writer.rs and add the method**
Locate `GroupBuilder` in `crates/clawhdf5-format/src/file_writer.rs`. It tracks its items as a `Vec` of internal builders. Add a field for external links and the method: Locate `GroupBuilder` in `crates/clawhdf5-format/src/file_writer.rs`. It tracks its items as a `Vec` of internal builders. Add a field for external links and the method:
@@ -127,7 +129,7 @@ pub fn add_external_link(
} }
``` ```
- [ ] **Step 4: Wire external links into the group serialization** - [x] **Step 4: Wire external links into the group serialization**
Find where the `GroupBuilder` emits `LinkMessage` bytes during `finish()` / `build_group()`. For each external link, emit a `LinkMessage` with `LinkTarget::External`: Find where the `GroupBuilder` emits `LinkMessage` bytes during `finish()` / `build_group()`. For each external link, emit a `LinkMessage` with `LinkTarget::External`:
@@ -154,7 +156,7 @@ for (link_name, filename, object_path) in &self.external_links {
(Follow the exact pattern used for hard links and soft links in the same codebase — find where hard-link `LinkMessage` bytes are pushed and add the external links in the same loop.) (Follow the exact pattern used for hard links and soft links in the same codebase — find where hard-link `LinkMessage` bytes are pushed and add the external links in the same loop.)
- [ ] **Step 5: Run the failing test** - [x] **Step 5: Run the failing test**
```bash ```bash
cargo test -p clawhdf5-format external_link_write_roundtrip 2>&1 cargo test -p clawhdf5-format external_link_write_roundtrip 2>&1
@@ -162,7 +164,7 @@ cargo test -p clawhdf5-format external_link_write_roundtrip 2>&1
Expected: PASS. Expected: PASS.
- [ ] **Step 6: Run full suite** - [x] **Step 6: Run full suite**
```bash ```bash
cargo test -p clawhdf5-format 2>&1 | tail -10 cargo test -p clawhdf5-format 2>&1 | tail -10
@@ -170,7 +172,7 @@ cargo test -p clawhdf5-format 2>&1 | tail -10
Expected: all tests pass. Expected: all tests pass.
- [ ] **Step 7: Commit** - [x] **Step 7: Commit**
```bash ```bash
git add crates/clawhdf5-format/src/file_writer.rs git add crates/clawhdf5-format/src/file_writer.rs
@@ -203,7 +205,7 @@ for each mapping:
virtual_selection: length(length_size) + bytes virtual_selection: length(length_size) + bytes
``` ```
- [ ] **Step 1: Write the failing tests** - [x] **Step 1: Write the failing tests**
Create `crates/clawhdf5-format/src/data_layout_write.rs`: Create `crates/clawhdf5-format/src/data_layout_write.rs`:
@@ -323,7 +325,7 @@ mod tests {
} }
``` ```
- [ ] **Step 2: Run the failing tests** - [x] **Step 2: Run the failing tests**
```bash ```bash
cargo test -p clawhdf5-format roundtrip_same_file_two_mappings roundtrip_external_file_mapping 2>&1 | head -20 cargo test -p clawhdf5-format roundtrip_same_file_two_mappings roundtrip_external_file_mapping 2>&1 | head -20
@@ -331,7 +333,7 @@ cargo test -p clawhdf5-format roundtrip_same_file_two_mappings roundtrip_externa
Expected: compile errors (module not declared). Expected: compile errors (module not declared).
- [ ] **Step 3: Declare module in lib.rs** - [x] **Step 3: Declare module in lib.rs**
In `crates/clawhdf5-format/src/lib.rs`, add: In `crates/clawhdf5-format/src/lib.rs`, add:
@@ -339,7 +341,7 @@ In `crates/clawhdf5-format/src/lib.rs`, add:
pub mod data_layout_write; pub mod data_layout_write;
``` ```
- [ ] **Step 4: Run tests** - [x] **Step 4: Run tests**
```bash ```bash
cargo test -p clawhdf5-format data_layout_write 2>&1 cargo test -p clawhdf5-format data_layout_write 2>&1
@@ -347,7 +349,7 @@ cargo test -p clawhdf5-format data_layout_write 2>&1
Expected: all 3 tests PASS. If `parse_vds_mappings` expects a slightly different format for the version byte or the marker byte, adjust `serialize_vds_mappings` to match what the parser consumes (read `data_layout.rs:92155` carefully to align). Expected: all 3 tests PASS. If `parse_vds_mappings` expects a slightly different format for the version byte or the marker byte, adjust `serialize_vds_mappings` to match what the parser consumes (read `data_layout.rs:92155` carefully to align).
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add crates/clawhdf5-format/src/data_layout_write.rs \ git add crates/clawhdf5-format/src/data_layout_write.rs \
@@ -374,7 +376,7 @@ global_heap_address(offset_size) global_heap_index(4)
``` ```
The global heap object holds the `serialize_vds_mappings()` output. The `global_heap_address` is the address of the global heap collection; `global_heap_index` is the 1-based object index within it. Use index=1 for the first (and only) VDS object. The global heap object holds the `serialize_vds_mappings()` output. The `global_heap_address` is the address of the global heap collection; `global_heap_index` is the 1-based object index within it. Use index=1 for the first (and only) VDS object.
- [ ] **Step 1: Write the failing test** - [x] **Step 1: Write the failing test**
In `crates/clawhdf5-format/src/file_writer.rs` `#[cfg(test)]` block, add: In `crates/clawhdf5-format/src/file_writer.rs` `#[cfg(test)]` block, add:
@@ -434,7 +436,7 @@ fn virtual_dataset_write_roundtrip() {
} }
``` ```
- [ ] **Step 2: Run test to verify it fails** - [x] **Step 2: Run test to verify it fails**
```bash ```bash
cargo test -p clawhdf5-format virtual_dataset_write_roundtrip 2>&1 | head -20 cargo test -p clawhdf5-format virtual_dataset_write_roundtrip 2>&1 | head -20
@@ -442,7 +444,7 @@ cargo test -p clawhdf5-format virtual_dataset_write_roundtrip 2>&1 | head -20
Expected: compile error — `with_virtual_sources` not found. Expected: compile error — `with_virtual_sources` not found.
- [ ] **Step 3: Add with_virtual_sources to DatasetBuilder** - [x] **Step 3: Add with_virtual_sources to DatasetBuilder**
Find `DatasetBuilder` in `file_writer.rs`. Add a field `virtual_sources: Option<Vec<VdsMapping>>` and the method: Find `DatasetBuilder` in `file_writer.rs`. Add a field `virtual_sources: Option<Vec<VdsMapping>>` and the method:
@@ -459,7 +461,7 @@ pub fn with_virtual_sources(&mut self, mappings: Vec<VdsMapping>) -> &mut Self {
} }
``` ```
- [ ] **Step 4: Serialize the virtual data layout** - [x] **Step 4: Serialize the virtual data layout**
In the `DatasetBuilder::build()` or equivalent finish method, add a branch for virtual datasets: In the `DatasetBuilder::build()` or equivalent finish method, add a branch for virtual datasets:
@@ -516,7 +518,7 @@ fn write_global_heap_object(buf: &mut Vec<u8>, data: &[u8]) -> (u64, usize) {
} }
``` ```
- [ ] **Step 5: Run the test** - [x] **Step 5: Run the test**
```bash ```bash
cargo test -p clawhdf5-format virtual_dataset_write_roundtrip 2>&1 cargo test -p clawhdf5-format virtual_dataset_write_roundtrip 2>&1
@@ -524,7 +526,7 @@ cargo test -p clawhdf5-format virtual_dataset_write_roundtrip 2>&1
Expected: PASS (or iterate on the global heap format until `parse_vds_mappings` reads back the mappings). Expected: PASS (or iterate on the global heap format until `parse_vds_mappings` reads back the mappings).
- [ ] **Step 6: Run full suite** - [x] **Step 6: Run full suite**
```bash ```bash
cargo test -p clawhdf5-format 2>&1 | tail -10 cargo test -p clawhdf5-format 2>&1 | tail -10
@@ -532,7 +534,7 @@ cargo test -p clawhdf5-format 2>&1 | tail -10
Expected: all tests pass. Expected: all tests pass.
- [ ] **Step 7: Commit** - [x] **Step 7: Commit**
```bash ```bash
git add crates/clawhdf5-format/src/file_writer.rs git add crates/clawhdf5-format/src/file_writer.rs
@@ -551,7 +553,7 @@ git commit -m "feat: add DatasetBuilder::with_virtual_sources for writing VDS da
**Interfaces:** **Interfaces:**
- Consumes/produces: `Superblock` struct — add `pub page_size: Option<u32>` field. - Consumes/produces: `Superblock` struct — add `pub page_size: Option<u32>` field.
- [ ] **Step 1: Add field to Superblock struct** - [x] **Step 1: Add field to Superblock struct**
In `crates/clawhdf5-format/src/superblock.rs`, add to the `Superblock` struct: In `crates/clawhdf5-format/src/superblock.rs`, add to the `Superblock` struct:
@@ -562,7 +564,7 @@ pub page_size: Option<u32>,
Update all existing construction sites of `Superblock { ... }` in the file (parse_v0, parse_v1, parse_v2v3) to include `page_size: None`. Update all existing construction sites of `Superblock { ... }` in the file (parse_v0, parse_v1, parse_v2v3) to include `page_size: None`.
- [ ] **Step 2: Write the failing test** - [x] **Step 2: Write the failing test**
In the `#[cfg(test)]` section of `superblock.rs`, add: In the `#[cfg(test)]` section of `superblock.rs`, add:
@@ -598,7 +600,7 @@ fn parse_v4_with_page_size() {
} }
``` ```
- [ ] **Step 3: Run test to verify it fails** - [x] **Step 3: Run test to verify it fails**
```bash ```bash
cargo test -p clawhdf5-format parse_v4_with_page_size 2>&1 | head -20 cargo test -p clawhdf5-format parse_v4_with_page_size 2>&1 | head -20
@@ -606,7 +608,7 @@ cargo test -p clawhdf5-format parse_v4_with_page_size 2>&1 | head -20
Expected: `Err(UnsupportedVersion(4))` — the test fails because v4 isn't handled. Expected: `Err(UnsupportedVersion(4))` — the test fails because v4 isn't handled.
- [ ] **Step 4: Add parse_v4 branch** - [x] **Step 4: Add parse_v4 branch**
In `Superblock::parse()`, change: In `Superblock::parse()`, change:
@@ -681,7 +683,7 @@ fn parse_v4(d: &[u8]) -> Result<Superblock, FormatError> {
} }
``` ```
- [ ] **Step 5: Run the test** - [x] **Step 5: Run the test**
```bash ```bash
cargo test -p clawhdf5-format parse_v4_with_page_size 2>&1 cargo test -p clawhdf5-format parse_v4_with_page_size 2>&1
@@ -689,7 +691,7 @@ cargo test -p clawhdf5-format parse_v4_with_page_size 2>&1
Expected: PASS (verify the checksum field name matches whatever `FormatError` uses — it may be `ChecksumMismatch { expected, computed }` or similar; find it in `error.rs` and match). Expected: PASS (verify the checksum field name matches whatever `FormatError` uses — it may be `ChecksumMismatch { expected, computed }` or similar; find it in `error.rs` and match).
- [ ] **Step 6: Run full suite** - [x] **Step 6: Run full suite**
```bash ```bash
cargo test -p clawhdf5-format 2>&1 | tail -10 cargo test -p clawhdf5-format 2>&1 | tail -10
@@ -697,7 +699,7 @@ cargo test -p clawhdf5-format 2>&1 | tail -10
Expected: all tests pass. Expected: all tests pass.
- [ ] **Step 7: Commit** - [x] **Step 7: Commit**
```bash ```bash
git add crates/clawhdf5-format/src/superblock.rs git add crates/clawhdf5-format/src/superblock.rs
@@ -717,7 +719,7 @@ git commit -m "feat: parse HDF5 superblock v4 (page-buffer mode) with page_size
**Interfaces:** **Interfaces:**
- Produces: `FileWriter::with_page_size(page_size: u32) -> &mut Self` - Produces: `FileWriter::with_page_size(page_size: u32) -> &mut Self`
- [ ] **Step 1: Write the failing test** - [x] **Step 1: Write the failing test**
In the `#[cfg(test)]` block of `file_writer.rs`, add: In the `#[cfg(test)]` block of `file_writer.rs`, add:
@@ -739,7 +741,7 @@ fn file_writer_v4_superblock() {
} }
``` ```
- [ ] **Step 2: Run test to verify it fails** - [x] **Step 2: Run test to verify it fails**
```bash ```bash
cargo test -p clawhdf5-format file_writer_v4_superblock 2>&1 | head -20 cargo test -p clawhdf5-format file_writer_v4_superblock 2>&1 | head -20
@@ -747,7 +749,7 @@ cargo test -p clawhdf5-format file_writer_v4_superblock 2>&1 | head -20
Expected: compile error — `with_page_size` not found. Expected: compile error — `with_page_size` not found.
- [ ] **Step 3: Add page_size field to FileWriter** - [x] **Step 3: Add page_size field to FileWriter**
In `FileWriter` struct definition, add `page_size: Option<u32>`. In `FileWriter` struct definition, add `page_size: Option<u32>`.
In `FileWriter::new()`, add `page_size: None`. In `FileWriter::new()`, add `page_size: None`.
@@ -760,7 +762,7 @@ pub fn with_page_size(&mut self, page_size: u32) -> &mut Self {
} }
``` ```
- [ ] **Step 4: Update Superblock::serialize for v4** - [x] **Step 4: Update Superblock::serialize for v4**
In `crates/clawhdf5-format/src/superblock.rs`, the `serialize()` method currently hardcodes v2/v3 format. Update it to emit v4 when `self.version == 4` and `self.page_size.is_some()`: In `crates/clawhdf5-format/src/superblock.rs`, the `serialize()` method currently hardcodes v2/v3 format. Update it to emit v4 when `self.version == 4` and `self.page_size.is_some()`:
@@ -787,7 +789,7 @@ pub fn serialize(&self) -> Vec<u8> {
} }
``` ```
- [ ] **Step 5: Wire page_size into FileWriter::finish()** - [x] **Step 5: Wire page_size into FileWriter::finish()**
In `file_writer.rs:finish()`, where the `Superblock` is constructed (around line 1291), change: In `file_writer.rs:finish()`, where the `Superblock` is constructed (around line 1291), change:
@@ -800,7 +802,7 @@ let sb = Superblock {
}; };
``` ```
- [ ] **Step 6: Run the test** - [x] **Step 6: Run the test**
```bash ```bash
cargo test -p clawhdf5-format file_writer_v4_superblock 2>&1 cargo test -p clawhdf5-format file_writer_v4_superblock 2>&1
@@ -808,7 +810,7 @@ cargo test -p clawhdf5-format file_writer_v4_superblock 2>&1
Expected: PASS. Expected: PASS.
- [ ] **Step 7: Run full suite** - [x] **Step 7: Run full suite**
```bash ```bash
cargo test -p clawhdf5-format 2>&1 | tail -10 cargo test -p clawhdf5-format 2>&1 | tail -10
@@ -816,7 +818,7 @@ cargo test -p clawhdf5-format 2>&1 | tail -10
Expected: all tests pass (v3 serialize() must be byte-identical to before — add a regression test if needed). Expected: all tests pass (v3 serialize() must be byte-identical to before — add a regression test if needed).
- [ ] **Step 8: Commit** - [x] **Step 8: Commit**
```bash ```bash
git add crates/clawhdf5-format/src/file_writer.rs \ git add crates/clawhdf5-format/src/file_writer.rs \
@@ -1,5 +1,7 @@
# MPI-IO VOL Backend Implementation Plan # MPI-IO VOL Backend Implementation Plan
> **Status (2026-08-03):** Implemented — shipped in commit `d6c4d4f` (2026-06-30), with FFI/constant fixes in `cb0b0e9`/`e91f7fc`. This doc was authored 2026-06-29 as the pre-work plan and committed to the repo retroactively on 2026-08-03; checkboxes below have been marked complete to match. Treat this as a historical record, not an open task list.
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add an `MpiVol` backend to `clawhdf5-io` that implements `VirtualObjectLayer` with `VolCapability::ParallelIO`, enabling collective MPI-IO reads and writes against HDF5 files — the same I/O pattern used by h5bench parallel workloads. **Goal:** Add an `MpiVol` backend to `clawhdf5-io` that implements `VirtualObjectLayer` with `VolCapability::ParallelIO`, enabling collective MPI-IO reads and writes against HDF5 files — the same I/O pattern used by h5bench parallel workloads.
@@ -32,7 +34,7 @@
- `MpiVol::new(comm: impl Into<MpiComm>) -> Self` — wraps an MPI communicator - `MpiVol::new(comm: impl Into<MpiComm>) -> Self` — wraps an MPI communicator
- `MpiVol::new_world() -> Self` — convenience for `MPI_COMM_WORLD` - `MpiVol::new_world() -> Self` — convenience for `MPI_COMM_WORLD`
- [ ] **Step 1: Write failing tests** - [x] **Step 1: Write failing tests**
Create `crates/clawhdf5-io/src/mpi_vol.rs`: Create `crates/clawhdf5-io/src/mpi_vol.rs`:
@@ -374,7 +376,7 @@ mod tests {
} }
``` ```
- [ ] **Step 2: Run tests to verify they fail** - [x] **Step 2: Run tests to verify they fail**
```bash ```bash
cargo test -p clawhdf5-io mpi_vol 2>&1 | head -20 cargo test -p clawhdf5-io mpi_vol 2>&1 | head -20
@@ -382,7 +384,7 @@ cargo test -p clawhdf5-io mpi_vol 2>&1 | head -20
Expected: compile error (module not declared). That's the expected failure. Expected: compile error (module not declared). That's the expected failure.
- [ ] **Step 3: Add Cargo.toml feature and rsmpi dependency** - [x] **Step 3: Add Cargo.toml feature and rsmpi dependency**
In `crates/clawhdf5-io/Cargo.toml`, add to `[dependencies]`: In `crates/clawhdf5-io/Cargo.toml`, add to `[dependencies]`:
@@ -396,7 +398,7 @@ Add to `[features]`:
mpi-io = ["mpi"] mpi-io = ["mpi"]
``` ```
- [ ] **Step 4: Declare module in lib.rs** - [x] **Step 4: Declare module in lib.rs**
In `crates/clawhdf5-io/src/lib.rs`, add: In `crates/clawhdf5-io/src/lib.rs`, add:
@@ -405,7 +407,7 @@ pub mod mpi_vol;
pub use mpi_vol::MpiVol; pub use mpi_vol::MpiVol;
``` ```
- [ ] **Step 5: Run tests without mpi-io feature** - [x] **Step 5: Run tests without mpi-io feature**
```bash ```bash
cargo test -p clawhdf5-io 2>&1 | tail -15 cargo test -p clawhdf5-io 2>&1 | tail -15
@@ -413,7 +415,7 @@ cargo test -p clawhdf5-io 2>&1 | tail -15
Expected: `mpi_vol_no_feature_returns_unsupported` and `mpi_vol_capabilities_include_parallel_io` PASS. Expected: `mpi_vol_no_feature_returns_unsupported` and `mpi_vol_capabilities_include_parallel_io` PASS.
- [ ] **Step 6: Check compilation with mpi-io feature (requires MPI headers)** - [x] **Step 6: Check compilation with mpi-io feature (requires MPI headers)**
```bash ```bash
# Install MPI if needed: sudo apt install libopenmpi-dev # Install MPI if needed: sudo apt install libopenmpi-dev
@@ -422,7 +424,7 @@ cargo check -p clawhdf5-io --features mpi-io 2>&1 | tail -20
Expected: clean compile (warnings OK; errors not OK). Expected: clean compile (warnings OK; errors not OK).
- [ ] **Step 7: Commit** - [x] **Step 7: Commit**
```bash ```bash
git add crates/clawhdf5-io/Cargo.toml \ git add crates/clawhdf5-io/Cargo.toml \
@@ -440,7 +442,7 @@ git commit -m "feat: add MpiVol VOL backend with collective MPI-IO (mpi-io featu
**Files:** **Files:**
- Modify: `crates/clawhdf5-io/src/mpi_vol.rs` (add integration test) - Modify: `crates/clawhdf5-io/src/mpi_vol.rs` (add integration test)
- [ ] **Step 1: Add the integration test** - [x] **Step 1: Add the integration test**
Inside the `#[cfg(test)]` block, add: Inside the `#[cfg(test)]` block, add:
@@ -485,7 +487,7 @@ Add to `Cargo.toml` dev-dependencies:
tempfile = "3" tempfile = "3"
``` ```
- [ ] **Step 2: Run without MPI feature (should compile-skip)** - [x] **Step 2: Run without MPI feature (should compile-skip)**
```bash ```bash
cargo test -p clawhdf5-io 2>&1 | tail -10 cargo test -p clawhdf5-io 2>&1 | tail -10
@@ -493,7 +495,7 @@ cargo test -p clawhdf5-io 2>&1 | tail -10
Expected: all tests pass; `collective_read_all_ranks_get_same_data` is not compiled. Expected: all tests pass; `collective_read_all_ranks_get_same_data` is not compiled.
- [ ] **Step 3: Run with MPI feature (requires mpirun)** - [x] **Step 3: Run with MPI feature (requires mpirun)**
```bash ```bash
# Requires: sudo apt install libopenmpi-dev openmpi-bin # Requires: sudo apt install libopenmpi-dev openmpi-bin
@@ -503,7 +505,7 @@ mpirun -np 4 cargo test -p clawhdf5-io --features mpi-io collective_read_all_ran
Expected: all 4 ranks PASS. Expected: all 4 ranks PASS.
- [ ] **Step 4: Commit** - [x] **Step 4: Commit**
```bash ```bash
git add crates/clawhdf5-io/src/mpi_vol.rs \ git add crates/clawhdf5-io/src/mpi_vol.rs \
@@ -520,7 +522,7 @@ git commit -m "feat: add MpiVol collective read integration test"
**Files:** **Files:**
- Modify: `crates/clawhdf5-io/src/mpi_vol.rs` - Modify: `crates/clawhdf5-io/src/mpi_vol.rs`
- [ ] **Step 1: Add the integration test** - [x] **Step 1: Add the integration test**
```rust ```rust
#[test] #[test]
@@ -588,14 +590,14 @@ fn collective_write_assembles_all_shards() {
} }
``` ```
- [ ] **Step 2: Run** - [x] **Step 2: Run**
```bash ```bash
cargo test -p clawhdf5-io 2>&1 | tail -5 # no feature — should pass cargo test -p clawhdf5-io 2>&1 | tail -5 # no feature — should pass
mpirun -np 4 cargo test -p clawhdf5-io --features mpi-io collective_write 2>&1 mpirun -np 4 cargo test -p clawhdf5-io --features mpi-io collective_write 2>&1
``` ```
- [ ] **Step 3: Commit** - [x] **Step 3: Commit**
```bash ```bash
git add crates/clawhdf5-io/src/mpi_vol.rs git add crates/clawhdf5-io/src/mpi_vol.rs
@@ -614,7 +616,7 @@ git commit -m "feat: add MpiVol collective write integration test (4 ranks)"
**Produces:** `cargo run -p clawhdf5-bench --features mpi-io --bin mpi_io_bench -- --size 100000` outputs MB/s throughput numbers comparable to h5bench output. **Produces:** `cargo run -p clawhdf5-bench --features mpi-io --bin mpi_io_bench -- --size 100000` outputs MB/s throughput numbers comparable to h5bench output.
- [ ] **Step 1: Create the binary** - [x] **Step 1: Create the binary**
Create `crates/clawhdf5-bench/src/bin/mpi_io_bench.rs`: Create `crates/clawhdf5-bench/src/bin/mpi_io_bench.rs`:
@@ -684,7 +686,7 @@ fn main() {
} }
``` ```
- [ ] **Step 2: Add to Cargo.toml** - [x] **Step 2: Add to Cargo.toml**
In `crates/clawhdf5-bench/Cargo.toml`, add: In `crates/clawhdf5-bench/Cargo.toml`, add:
@@ -704,7 +706,7 @@ name = "mpi_io_bench"
path = "src/bin/mpi_io_bench.rs" path = "src/bin/mpi_io_bench.rs"
``` ```
- [ ] **Step 3: Verify it compiles** - [x] **Step 3: Verify it compiles**
```bash ```bash
cargo check -p clawhdf5-bench --features mpi-io 2>&1 | tail -10 cargo check -p clawhdf5-bench --features mpi-io 2>&1 | tail -10
@@ -712,7 +714,7 @@ cargo check -p clawhdf5-bench --features mpi-io 2>&1 | tail -10
Expected: no errors. Expected: no errors.
- [ ] **Step 4: Run with 4 ranks** - [x] **Step 4: Run with 4 ranks**
```bash ```bash
mpirun -np 4 cargo run --release -p clawhdf5-bench --features mpi-io --bin mpi_io_bench -- --size 1000000 2>&1 mpirun -np 4 cargo run --release -p clawhdf5-bench --features mpi-io --bin mpi_io_bench -- --size 1000000 2>&1
@@ -730,7 +732,7 @@ Read : xxx.x MB/s
Record results in `BENCHMARKS.md` under a new `## MPI-IO Parallel I/O` section. Record results in `BENCHMARKS.md` under a new `## MPI-IO Parallel I/O` section.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add crates/clawhdf5-bench/src/bin/mpi_io_bench.rs \ git add crates/clawhdf5-bench/src/bin/mpi_io_bench.rs \