Store layout¶
A store is one directory. The engine creates it on first open.
<store>/
.hms.lock exclusive flock; content "pid=<n>\n"; not part of the data
store.json store.json.crc32
vectors_data.bin/ the arena (a directory despite the name)
format.json format.json.crc32
seg_0.bin seg_1.bin ... journal segments
CURRENT only after a compaction: names the live generation directory
gen-<pid>-<nanos>/ the live generation after a compaction: seg_*.bin, format.json
documents.text cache (CACHES.md)
indices.json cache (CACHES.md); older stores also have indices.json.crc32, ignored
nsg_index.bin ivf_index.bin or nsg_index_<i>.bin ivf_index_<i>.bin caches
shard_meta.json shard count after auto-sharding
encryption.salt 16 bytes, encrypted stores only
audit.bin hms_signing.key hms_provenance.key provenance_log.jsonl* kel.json (ANCILLARY.md)
Server data directory: <data-dir>/tenants/<hex(utf8 tenant name)>/ is one store per tenant; nothing else in <data-dir> is read as store data. hms-format verify <data-dir> checks each.
A snapshot is a copy of the whole directory taken while no engine holds the lock; hms-admin migrate makes a verified copy (lock file left out). There is no other snapshot artefact.
store.json¶
Pretty-printed JSON, written once (atomic: temp file, fsync, rename, directory fsync; the sidecar first). Keys, in this order:
| Key | Type | Meaning |
|---|---|---|
version | integer | 2 |
dimensions | integer | hypervector dimension, 1..=1000000, fixed for the store |
text_encoder | string | see ENCODERS.md |
vector_encoder | string | signed-projection-v2 |
metric | string | jaccard |
encrypted | bool | frames are AES-256-GCM sealed |
meaning_enabled | bool | meaning memory configured |
embedding_space | object or null | {model, revision, dimensions, normalization, metric} |
On open the engine builds the same object from its configuration and requires equality. A different value is refused ("incompatible store schema"); a version above 2 is refused as newer than supported; a store with vectors_data.bin but no store.json is refused as legacy. The sidecar store.json.crc32 is the CRC32 of the file bytes as 8 lowercase hex digits and a newline; absent on stores written before sidecars, and then unverified.
format.json¶
segment_size must be 64 MiB (67108864) when version is 3 and 1 GiB (1073741824) for 2 and 4. Magic other than HMS_ARENA or version above 4 is refused. Same sidecar rule as store.json. With a CURRENT pointer the engine reads vectors_data.bin/format.json, falling back to the generation's copy.
CURRENT¶
ASCII name, no newline, matching gen-[A-Za-z0-9-]+. The directory must exist and not be a symlink. Without CURRENT the live segments are in vectors_data.bin/ itself.
Open sequence and what open writes¶
- create the directory; take the lock; validate or create
store.json - open the arena: create or validate
format.json, find the live generation, recover the active segment (JOURNAL.md), write the end marker at the recovered end, extend the active file to its allocation size - zero and unlink a
documents.text.canonicalleft by an interrupted purge; truncate and recreatedocuments.text; replay the journal; load index caches if fresh
So opening modifies: the lock file, documents.text, the end of the active segment, and the manifest/schema when missing. tests/format_open.rs pins this list; a refused open modifies nothing but the lock file.