|
1 | 1 | # Changelog |
2 | 2 |
|
3 | | -All notable changes to this project are documented here. The format is based on |
4 | | -[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project |
5 | | -adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 3 | +All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
6 | 4 |
|
7 | | -## [Unreleased] |
| 5 | +## [0.1.0] - 2026-07-28 |
8 | 6 |
|
9 | | -The first stable release. Pre-releases are published as `0.1.0-beta.N`; the |
10 | | -entries below describe `0.1.0` as a whole rather than deltas against a shipped |
11 | | -version, since none exists yet. |
| 7 | +The first release. Pre-releases were published as `0.1.0-beta.N`; the entries below describe `0.1.0` as a whole rather than deltas against a shipped version, since none exists yet. |
12 | 8 |
|
13 | 9 | ### Added |
14 | 10 |
|
15 | | -- **B+ tree surface** — sorted `bytes → bytes` store with copy-on-write shadow |
16 | | - paging, A/B headers, ACID transactions, range scans, monotonic append, and |
17 | | - bulk load. Reads return `Bytes` borrowed from the page cache; scans come |
18 | | - bounded (`scan_from`, `scan_prefix_from`) or materialising. |
19 | | -- **Segment File API** — engine-owned, append-mostly, atomically sealed |
20 | | - encrypted files for formats that own their own layout (vectors, columnar |
21 | | - blocks, FTS postings, R-trees). |
22 | | -- **Encrypted pager** — every persistent page is authenticated; AES-256-GCM and |
23 | | - ChaCha20-Poly1305 cipher dispatch with per-page `cipher_id` for cipher |
24 | | - agility. SIEVE page cache with bounded memory budgets. |
25 | | -- **Key hierarchy** — KEK → MK → DEK derivation, stateful nonce generation with |
26 | | - a durable anchor (nonce reuse impossible under one key), `zeroize` on all key |
27 | | - material. |
28 | | -- **Realm isolation** — `RealmId` bound into AEAD AAD on every persistent page; |
29 | | - misrouted reads fail tag verification. Per-realm quotas. |
30 | | -- **Cross-platform VFS** — Linux (`io_uring`), Windows (IOCP), macOS/iOS |
31 | | - (Grand Central Dispatch), Android, WASM/OPFS, and WASI backends, plus a |
32 | | - tokio thread-pool fallback and an in-memory backend, with format-bit identity |
33 | | - across targets. Backends may complete a positional read or write in several |
34 | | - calls; PageDB finishes the caller's whole buffer before treating authenticated |
35 | | - metadata as present or durable. On Linux the backend choice is made at run |
36 | | - time: where the kernel refuses an `io_uring` ring — `RLIMIT_MEMLOCK` |
37 | | - exhausted, seccomp blocking `io_uring_setup`, a kernel older than 5.1 — the |
38 | | - open logs a warning and continues on the thread pool rather than failing. |
39 | | - Every native backend shares one advisory-lock implementation, so processes on |
40 | | - different backends still exclude each other on the same store. |
41 | | -- **Snapshots** — `snapshot_to`, `restore_from`, and incremental apply, each |
42 | | - authenticated against the exact state its manifest describes. Destinations |
43 | | - must be empty; malformed or incomplete artifacts fail closed. |
44 | | -- **Recovery** — open-flow GC, apply-journal replay, deep-walk `fsck`, and the |
45 | | - `pagedb-fsck` binary. |
46 | | -- **Online rekey** — rekey the database under a new key with mixed-cipher and |
47 | | - mixed-epoch page coexistence (no full-file migration). |
| 11 | +- **B+ tree surface** — sorted `bytes → bytes` with copy-on-write shadow paging, A/B headers, ACID transactions, range scans, monotonic append, and bulk load. Reads return `Bytes` borrowed from the page cache; scans are bounded (`scan_from`, `scan_prefix_from`) or materialising. |
| 12 | +- **Segment File API** — engine-owned, append-mostly, atomically sealed encrypted files for formats that own their own layout (vectors, columnar blocks, FTS postings, R-trees). |
| 13 | +- **Encrypted pager** — every persistent page is authenticated; AES-256-GCM and ChaCha20-Poly1305 with per-page `cipher_id` for cipher agility. SIEVE page cache with bounded memory budgets. |
| 14 | +- **Key hierarchy** — KEK → MK → per-`(realm, file)` DEK/IK, stateful nonce generation with a durable anchor, `zeroize` on all key material. |
| 15 | +- **Realm isolation** — `RealmId` bound into AEAD AAD on every persistent page and recorded in the `main.db` header. Per-realm quotas. |
| 16 | +- **Cross-platform VFS** — Linux (`io_uring`), Windows (IOCP), macOS/iOS (Grand Central Dispatch), Android, WASM/OPFS and WASI backends, plus a tokio thread-pool fallback and an in-memory backend, with format-bit identity across targets. On Linux the backend is chosen at run time: a kernel that refuses an `io_uring` ring falls back to the thread pool with a warning instead of failing the open. All native backends share one advisory-lock implementation, so processes on different backends still exclude each other on one store. |
| 17 | +- **Snapshots** — `snapshot_to`, `restore_from`, and incremental apply, each authenticated against the state its manifest describes. Destinations must be empty; malformed or incomplete artifacts fail closed. |
| 18 | +- **Recovery** — open-flow GC, apply-journal replay, deep-walk `fsck`, and the `pagedb-fsck` binary. |
| 19 | +- **Online rekey** — rekey under a new key with mixed-cipher and mixed-epoch page coexistence; no full-file migration. |
48 | 20 | - **Handle modes** — `Standalone`, `Follower`, `ReadOnly`, and `Observer`. |
49 | | -- **Failures report themselves** — an unreadable free-list chain, main file, or |
50 | | - segment catalog fails `stats()` instead of reporting zero; compaction never |
51 | | - skips a catalog entry whose file it cannot open; segment open distinguishes a |
52 | | - missing file from a permission or backend error; and only genuine contention |
53 | | - is reported as contention. Persisted named-counter rows are validated at open, |
54 | | - and commit-history keys are rejected unless exactly eight bytes. |
| 21 | +- **Open refusals name the parameter, not the store** — `KeyMismatch`, `PageSizeMismatch`, and `RealmMismatch`, each decided before anything is read or written, and none reported as corruption. |
| 22 | +- **Failures report themselves** — an unreadable free-list chain, main file, or segment catalog fails `stats()` instead of reporting zero; compaction never skips a catalog entry whose file it cannot open; segment open distinguishes a missing file from a permission or backend error; and only genuine contention is reported as contention. Persisted named-counter rows are validated at open, and commit-history keys are rejected unless exactly eight bytes. |
| 23 | + |
| 24 | +### Security |
| 25 | + |
| 26 | +- Threat model documented in the README; disclosure policy in `SECURITY.md`. |
| 27 | +- Data keys are scoped per file, so no two files share a nonce space. |
| 28 | +- `io_uring`: an error mid-submission no longer frees a transfer buffer the kernel may still be writing into. |
| 29 | +- `pagedb-fsck` requires an explicit KEK — no all-zero default. |
| 30 | +- Diagnostic reports redact embedder-chosen segment names. |
55 | 31 |
|
56 | 32 | ### Known limitations |
57 | 33 |
|
58 | | -- Pre-1.0: the on-disk format may change between minor versions until 1.0. |
| 34 | +- Pre-1.0: the API may break in a minor bump. The on-disk format is frozen for the `0.1.x` line and any later format change ships a migrator — see `VERSIONING.md`. Stores written by a `0.1.0-beta` pre-release are not readable by `0.1.0`. |
| 35 | +- pagedb detects tampering, not reversion: an attacker who can write to the store files can substitute an older genuine ciphertext and it will authenticate. Freshness belongs above pagedb. |
| 36 | +- `main.db` cannot be reconstructed from `seg/` — back the directory up as a unit. |
59 | 37 | - Single-writer per database; multi-writer cross-process is not supported. |
60 | | -- Writes carry per-page AEAD and copy-on-write overhead; for throughput-bound |
61 | | - plaintext KV workloads a generic store may be faster. |
62 | | -- Applying an incremental snapshot stages a full copy of `main.db` alongside the |
63 | | - original and commits it with a rename, so a follower needs room for two copies |
64 | | - of the database during an apply. |
| 38 | +- Writes carry per-page AEAD and copy-on-write overhead; for throughput-bound plaintext KV workloads a generic store may be faster. |
| 39 | +- Applying an incremental snapshot stages a full copy of `main.db` alongside the original, so a follower needs room for two copies during an apply. |
0 commit comments