# History and time

Read retained versions, understand release policy and use the offline history and generation tools.

Every change has a version. A retained version can be read again; a released version cannot. The branch head, recent heads, history rows and fork references keep versions alive for different reasons. Retention releases history before reclaim removes unreachable storage.

## 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.

**read_history arguments**

```json
{"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.

**read_history arguments**

```json
{"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.

**Terminal**

```bash
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.

```text
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.

**Terminal**

```bash
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.

> Do not rename main
> In the verified binary, a check can print a plan for main and apply can create the destination before the discard is refused as main_protected. A check is not proof that every later mutation will be permitted. Leave main named main.


## 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.

**Terminal**

```bash
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

**Terminal**

```bash
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

**Terminal**

```bash
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.
