# v0.6.0 — Codex Layer Complete (Layer 5)

**Release date:** 2026-05-20
**Phase:** 0.6.0 — `StaticCodex` + `DynamicCodex` + vault integration
**Status:** development (pre-1.0; public API subject to change without notice)

---

## What is `key-vault`?

`key-vault` is an enterprise-grade in-memory key management vault for Rust.
See [README](../../README.md) for the 1.0 design and
[`docs/SECURITY.md`](../SECURITY.md) for the per-layer architecture.

After this release, **6 of the 9 layers are complete**:

| Layer | Status after 0.6.0 |
|-------|--------------------|
| Layer 2 — page locking | ✅ |
| Layer 3 — fragmentation (4/4 strategies) | ✅ |
| Layer 4 — decoy bytes (3 strategies) | ✅ |
| **Layer 5 — codex (4/4 implementations)** | **✅ complete** |
| Layer 6 — constant-time handle equality | ✅ |
| Layer 7 — zero-on-drop | ✅ |
| Layer 1 — built-in fetchers | planned for 0.7 |
| Layer 8 — monitor impls | planned for 0.8 |
| Layer 9 — audit logging | planned for 0.8 |

---

## What's in 0.6.0

### `StaticCodex` — declarative + random involution lookup tables

Two constructors:

```rust
use key_vault::{Codex, StaticCodex};

// Declarative: list of swap pairs, each (a, b) means a ↔ b.
let codex = StaticCodex::from_swaps(&[
    (b'0', b'#'),
    (b'A', b'@'),
])?;
assert_eq!(codex.encode(b'0'), b'#');
assert_eq!(codex.encode(b'B'), b'B'); // not swapped, fixed point

// Random: pair up every byte randomly, no fixed points.
let codex = StaticCodex::random_involution()?;
for byte in 0u8..=255 {
    assert_eq!(codex.decode(codex.encode(byte)), byte);
    assert_ne!(codex.encode(byte), byte); // no fixed points
}
# Ok::<(), key_vault::Error>(())
```

**Storage.** The 256-byte lookup table lives in a `LockedBytes` buffer:
mlock'd against swap, volatile-zeroed before drop. Knowing the table is
equivalent to knowing the transformation.

**Hot path.** Encoding or decoding one byte is `table[byte as usize]`
— one memory load, branch-free, ~1 CPU cycle. Inlined.

### `DynamicCodex` — per-vault random involution

Thin wrapper around `StaticCodex::random_involution()` for the common
"each vault gets its own codex" use case:

```rust
use key_vault::{DynamicCodex, KeyVaultBuilder};

let vault = KeyVaultBuilder::new()
    .with_codex(DynamicCodex::new()?)
    .build();
# Ok::<(), key_vault::Error>(())
```

### `KeyVaultBuilder::with_codex` — vault integration

The codex transformation is applied transparently at the vault boundary:

```text
fragment:    raw → blake3_normalize → codex.encode → fragmenter.fragment
defragment:  fragments → fragmenter.defragment → codex.decode → raw
```

For involution-based codices (`StaticCodex`, `DynamicCodex`, involution
closures wrapped in `FnCodex`) `decode == encode`, but the vault calls
them by name so non-involution codices would work too in principle.

### Layer 5 status summary

| Implementation | Default? | Construction                       | Use case                              |
|----------------|----------|------------------------------------|---------------------------------------|
| `IdentityCodex`| ✅       | `IdentityCodex`                    | no transformation, max performance    |
| `FnCodex<F>`   |          | `FnCodex::new(closure)`            | user-provided involution closure      |
| `StaticCodex`  |          | `from_swaps(&[(u8, u8)])` or `random_involution()` | declarative or one-off random table |
| `DynamicCodex` |          | `DynamicCodex::new()`              | fresh random involution per vault     |

### New / changed public surface

| Item                              | Status                            |
|-----------------------------------|-----------------------------------|
| `StaticCodex`                     | **new**                           |
| `DynamicCodex`                    | **new**                           |
| `KeyVaultBuilder::with_codex`     | **new**                           |
| `KeyVault::fragment/defragment`   | now applies codex transformation if configured |
| `KeyVaultBuilder` `Debug` impl    | manual (was derived) — needed because `Arc<dyn Codex>` isn't `Debug` |
| `crate::fragment::util::*`        | promoted from `pub(super)` to `pub(crate)` |

### Test surface (relative to 0.5.0)

|                   | 0.5.0 | 0.6.0 |
|-------------------|-------|-------|
| Unit tests        |   82  |   99  |
| Integration tests |   15  |   15  |
| Doc tests         |    9  |   13  |
| **Total**         | **106**| **127** |

---

## Breaking changes

Purely additive on top of 0.5.0:

- All 0.5.0 types and methods are preserved.
- `KeyVaultBuilder` losing the derived `Debug` for a manual `Debug` is
  not observable.

---

## Verification

Verified locally on both stable and pinned MSRV 1.85:

```text
cargo {fmt, clippy, test, doc} --all-features      clean
127 tests pass; -D warnings on clippy + RUSTDOCFLAGS
```

No new `unsafe` blocks in this release.

---

## Installation

```toml
[dependencies]
key-vault = "0.6"
```

Default features unchanged from 0.5.0. Minimum supported Rust version:
**1.85** (edition 2024).

---

## Documentation

- [`README.md`](../../README.md) — overview + Current status table
- [`docs/SECURITY.md`](../SECURITY.md) — 9-layer architecture
- [`docs/TRANSFORMATION.md`](../TRANSFORMATION.md) — visual walkthrough
- [`CHANGELOG.md`](../../CHANGELOG.md) — full change log

---

## Diff

Full diff: [`v0.5.0...v0.6.0`](https://github.com/jamesgober/key-vault/compare/v0.5.0...v0.6.0)

---

<sub>key-vault v0.6.0 — Copyright (c) 2026 James Gober. Apache-2.0 OR MIT.</sub>
