Read retained history
The MCP read_history tool takes a selection and page limits. With no record, it returns retained branch snapshots at or before the captured version. With a record, it also reads that record at the captured selection. It does not return a separate document body for every history entry.
{"selection":{"branch":"main"},"page":{"rows":100,"bytes":65536}}
The answer has selection, history.entries, history.next_after and record. The first selection captures the branch head if no version was given. To continue, repeat its captured version, page limits and the opaque next_after as after. A continuation without that version is mcp_version_required; an altered or mismatched cursor is mcp_cursor_invalid.
To read one old record, take its version from retained history and supply it explicitly.
{"selection":{"branch":"main","version":{"generation":"1792076694393873000","sequence":"2"}},"record":{"collection":"patients","document_id":"p0"},"page":{"rows":100,"bytes":65536}}
The example generation and sequence came from the scratch run. Replace both with a version your store returned. At sequence 2 in that run, p0 read back as Ada, even after later writes and branch operations.
The service method behind the history list is also available from the command line.
haematite call --config service.json history.list '{"branch":"main","limit":100}'
history.list takes branch, a row limit and an optional version after. Its entries include each retained version’s shard roots. Do not substitute an MCP cursor for this protocol-level version cursor.
What remains readable
kept_versions, default 64, bounds previous heads embedded in each branch record. Older retained moments have durable history rows. A fork retains the version it came from. Active readers and other durable references can also keep roots needed by an in-progress operation.
The release driver removes older history under retention.keep.versions and retention.keep.days. Both settings must be supplied to release anything, and the head is always kept. A missing setting means keep every version, not use a guessed default. Count and age both protect a version; it is released only when neither protects it and no other hold blocks it.
A released selection answers version_released, including the released boundary and its time. A version not found in retained state is version_missing. These are not empty result sets. Keep an offline backup if a record must remain available beyond the configured retention policy.
Time in queries
window filters on the revision’s append version. It does not inspect an admitted, created or other date field inside the JSON document.
collection("patients") | window("1970-01-01T00:00:00Z"; $now)
collection("patients") | window(120; 240)
The lower bound is included and the upper bound excluded. Both bounds use the same form: unsigned versions or quoted RFC 3339 instants. $now can be the upper bound. The query resolves that instant once. Missing or released history required to resolve a time is a named query-window refusal.
Rename a branch offline
Stop the service and every other writer first. Check the planned rename, then apply it.
haematite branch-rename --config service.json --from intake --to reviewed-intake --check
haematite branch-rename --config service.json --from intake --to reviewed-intake --apply
Use an ordinary branch such as intake, not main. The command forks the destination from the source head, confirms the same shard roots and discards the source. The old generation’s history remains governed by discarded-generation retention. The destination is a new generation, not an in-place name edit.
The destination must be placeable in Lys. branch_name_unplaceable names an invalid destination; branch_absent names a missing source; branch_exists means the destination is not the expected fork. rename_roots_differ keeps the source and refuses the discard.
The two mutations have stable request ids so an interrupted step can be reconciled while the source remains available. After a completed rename, the old name is gone and another invocation can answer branch_absent. Inspect both branch names before repeating an uncertain command.
Date an old discarded generation
release date records when a generation discarded by older code was discarded, if it has rows but no discard marker. It lets age-based release judge those rows. It does not date a software release or release history immediately.
haematite release date --config service.json --branch discarded-intake \
--generation 1792076694393873000 --at 1792077088195365000
The generation is the creation timestamp under which those history rows are keyed. --at is the operator’s known discard time in Unix nanoseconds. Do not invent either number from the current clock. branch_live refuses a live branch; generation_marked refuses one already marked; generation_empty refuses one with no history to date. Success prints the last version covered by the marker.
The live-branch refusal was run. A successful dating operation was not reproduced because the scratch stores did not contain an undated legacy discard.
Check legacy history files
haematite history-check /var/lib/haematite/ward --show 20
history-check compares each legacy history file’s heads with its service history row under the importer’s rule. It does not check all records, prove a backup complete or repair history. Use a stopped copy. A live writer is refused as history_check_store_in_use, exit 2.
Read the full summary: files, equal, row_missing, roots_differ, shard_set_differs, row_is_a_kept_head, name_corrupt and row_corrupt. Exit 0 means every file compared equal; exit 1 means a mismatch; exit 2 means refused. A zero-file comparison can exit 0 and proves no legacy-file comparison was needed, not that all record history was audited.
Observe a raw key
haematite observe-get /var/lib/haematite/ward --key nonexistent
haematite observe-get /var/lib/haematite/ward --key-hex 6e6f6e6578697374656e74 --hex
observe-get reads one committed raw database key without taking the writer lock. It can run beside a live writer. --key supplies UTF-8 bytes; --key-hex supplies exact bytes. --hex encodes the returned value as hexadecimal. A document id alone is not the store’s encoded document key; prefer documents.get for a record.
Exit 0 means found, exit 1 means no committed value and exit 2 means refused. It writes no data and does not wait for an uncommitted write to appear. The absent-key case was verified both beside the scratch writer and offline.