# v0.8.0 — Layer 8 Security Monitor + Threshold Lockout

**Release date:** 2026-05-20
**Phase:** 0.8.0 — `NoMonitor` + `CompositeMonitor` + `LogMonitor` + threshold-driven lockout
**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, **8 of the 9 layers are complete**:

| Layer | Status after 0.8.0 |
|-------|--------------------|
| Layer 1 — built-in fetchers | ✅ |
| Layer 2 — page locking | ✅ |
| Layer 3 — fragmentation (4/4 strategies) | ✅ |
| Layer 4 — decoy bytes (3 strategies) | ✅ |
| Layer 5 — codex (4/4 implementations) | ✅ |
| Layer 6 — constant-time handle equality | ✅ |
| Layer 7 — zero-on-drop | ✅ |
| **Layer 8 — monitor impls + threshold lockout** | **✅ shipped (Metrics/Webhook deferred)** |
| Layer 9 — dedicated audit-event surface | partial — `LogMonitor` covers failure/anomaly; per-access audit lands with the named-key registry in 0.9 |

---

## What's in 0.8.0

### Three `SecurityMonitor` implementations

#### `NoMonitor` — the inert default

```rust
use key_vault::{KeyVaultBuilder, NoMonitor};
let _vault = KeyVaultBuilder::new().with_monitor(NoMonitor).build();
```

Discards every event. Equivalent to leaving the monitor slot empty
(the vault internally falls back to `NoMonitor`). Use when you want
to be explicit about "no monitoring configured."

#### `CompositeMonitor` — fan-out across multiple monitors

```rust
use std::sync::Arc;
use key_vault::{CompositeMonitor, NoMonitor, SecurityMonitor};

let composite = CompositeMonitor::new(vec![
    Arc::new(NoMonitor) as Arc<dyn SecurityMonitor>,
    Arc::new(NoMonitor) as Arc<dyn SecurityMonitor>,
]);
```

Chains every event to every inner monitor. Useful when you want both
a human-readable log sink and a machine-readable metrics sink from
the same vault.

#### `LogMonitor` — structured `tracing` events (feature `monitor-tracing`)

```rust,no_run
use key_vault::{KeyVaultBuilder, LogMonitor};

let vault = KeyVaultBuilder::new()
    .with_monitor(LogMonitor::new())
    .build();
# let _ = vault;
```

Emits at three levels:

| Callback | Level | Target |
|----------|-------|--------|
| `on_decryption_failure` | `warn!` | `key_vault::monitor` |
| `on_anomalous_access` | `warn!` | `key_vault::monitor` |
| `on_threshold_breach` | `error!` | `key_vault::monitor` |

Fields are structured (`key_name`, `consecutive_failures`,
`window_elapsed_ms`, etc.) for easy filtering with
`tracing-subscriber`.

### Threshold-driven lockout

The vault now tracks per-key failures in a sliding window and locks
itself out when the count exceeds a configurable threshold:

```rust
use std::time::Duration;
use key_vault::{KeyVaultBuilder, LogMonitor};

# fn main() -> Result<(), key_vault::Error> {
let vault = KeyVaultBuilder::new()
    .with_monitor(LogMonitor::new())
    .with_failure_threshold(5, Duration::from_secs(60))
    .build();

// Downstream code calls report_failure when it sees a failure.
vault.report_failure("db-primary", Some("AEAD verify failed"));
vault.report_failure("db-primary", Some("AEAD verify failed"));
vault.report_failure("db-primary", Some("AEAD verify failed"));
vault.report_failure("db-primary", Some("AEAD verify failed"));
vault.report_failure("db-primary", Some("AEAD verify failed"));
// Five failures in the window → lockout.
assert!(vault.is_locked_out());

// fragment / defragment refuse to proceed while locked.
let raw = key_vault::RawKey::new(vec![0u8; 32]);
let err = vault.fragment(&raw).unwrap_err();
assert!(matches!(err, key_vault::Error::LockedOut));

// Operators reset after investigation.
vault.clear_lockout();
assert!(!vault.is_locked_out());
# Ok(())
# }
```

Defaults:

- `max_failures_before_lockout = 0` → **threshold lockout disabled**.
  Failures still forward to the monitor; no automatic lockout.
- `failure_window = 60s` (only meaningful when `max > 0`).

Per-key counters are independent — failures against `alpha` don't
count toward the threshold for `beta`.

### New `KeyVault` API surface

| Method | Description |
|--------|-------------|
| `report_failure(name, note)` | Forward a failure to the monitor; advance the threshold counter; trigger lockout if the threshold is crossed. |
| `report_anomalous_access(name, note)` | Forward an anomaly to the monitor (no state change). |
| `clear_lockout()` | Reset the lockout flag and clear the failure tracker. Operator escape hatch. |

### Lockout enforcement in `fragment` / `defragment`

Both methods now return `Error::LockedOut` when the vault is in
lock-out state. Previously they were oblivious to the flag — it
existed but was inert.

### Blanket `SecurityMonitor` impl for `Arc<dyn SecurityMonitor>`

So tests and callers can pass pre-wrapped monitor instances directly
to APIs that accept `impl SecurityMonitor + 'static`:

```rust
use std::sync::Arc;
use key_vault::{KeyVaultBuilder, NoMonitor, SecurityMonitor};

let monitor: Arc<dyn SecurityMonitor> = Arc::new(NoMonitor);
let _vault = KeyVaultBuilder::new().with_monitor(monitor).build();
```

### New / changed public surface

| Item                                          | Status        |
|-----------------------------------------------|---------------|
| `NoMonitor`                                   | **new**       |
| `CompositeMonitor`                            | **new**       |
| `LogMonitor` (feature `monitor-tracing`)      | **new**       |
| `KeyVaultBuilder::with_monitor`               | **new**       |
| `KeyVaultBuilder::with_failure_threshold`     | **new**       |
| `KeyVault::report_failure`                    | **new**       |
| `KeyVault::report_anomalous_access`           | **new**       |
| `KeyVault::clear_lockout`                     | **new**       |
| `VaultConfig::max_failures_before_lockout`    | **new field** |
| `VaultConfig::failure_window`                 | **new field** |
| `KeyVault::fragment` / `defragment`           | now return `Error::LockedOut` when locked |
| `SecurityMonitor` for `Arc<dyn SecurityMonitor>` | new blanket impl |

### Test surface (relative to 0.7.0)

|                   | 0.7.0 | 0.8.0 |
|-------------------|-------|-------|
| Unit tests        |  111  |  119  |
| Integration tests |   15  |   15  |
| Doc tests         |   17  |   20  |
| **Total**         | **143**| **154** |

New tests cover:

- `report_failure` / `report_anomalous_access` monitor dispatch.
- Threshold lockout fires after the configured count.
- Per-key counters are independent.
- `CompositeMonitor` fans events to all inner monitors.
- `fragment` and `defragment` return `Error::LockedOut` when locked.
- `clear_lockout` resets state correctly.

---

## Breaking changes

The public-API surface gains the new fields on `VaultConfig`. Since
`VaultConfig` is `#[non_exhaustive]`, existing construction via
`KeyVaultBuilder` is unaffected. Direct field access on
`VaultConfig` from outside the crate is already forbidden by
`#[non_exhaustive]`, so there's no observable break.

The behavioral change in `fragment` / `defragment` (return
`LockedOut` when locked) is gated by an opt-in feature: the threshold
is `0` (disabled) by default, so vaults that haven't called
`with_failure_threshold` cannot enter lockout and their fragment/
defragment paths are unchanged.

---

## Verification

Verified locally on both stable and pinned MSRV 1.85:

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

No new `unsafe` blocks in this release. The new `Mutex` and
`HashMap` state on `VaultInner` is plain safe Rust.

---

## Documentation sync (this release)

Per the project's doc-sync rule:

- `README.md` — "Current status" marks Layer 8 **shipped**;
  Quick-start dep `0.7` → `0.8`; security-monitoring section updated
  with new API surface.
- `docs/API.md` — pending update with the new monitor types + vault
  API in the next docs pass.
- `CHANGELOG.md` — `[0.8.0]` section under Added / Changed / Security.
- `docs/release/v0.8.0.md` — this file.

---

## What's next — Phase 0.9.0

**Multi-key vaults + key rotation + master key recovery.**

Exit criteria for 0.9:

- Named-key registry: `KeyVault::register(name, RawKey)` →
  `KeyHandle`, `KeyVault::with_key(handle, |bytes| { ... })` scoped
  access.
- Atomic rotation: `KeyVault::rotate(name, new_key)` swaps without
  dropping in-flight access (lock-free via `ArcSwap`).
- Master-key concept for vault-level operations.
- Master-key recovery flow + emergency unlock path.
- Dedicated `AuditEvent` surface on per-access hooks (Layer 9
  completion).

Estimated effort: 4–5 days.

---

## Installation

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

To enable the tracing-backed `LogMonitor`:

```toml
[dependencies]
key-vault = { version = "0.8", features = ["monitor-tracing"] }
```

Minimum supported Rust version: **1.85** (edition 2024).

---

## Documentation

- [`README.md`](../../README.md) — overview + Current status table
- [`docs/API.md`](../API.md) — full public-API reference
- [`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.7.0...v0.8.0`](https://github.com/jamesgober/key-vault/compare/v0.7.0...v0.8.0)

---

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