# OpenLogi Config TOML Validation and Recovery: A Deep Dive into Schema Integrity Mechanisms

> Explore OpenLogi's strict TOML validation and recovery mechanisms. Learn how deny unknown fields, schema checks, and atomic saves protect configuration integrity.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: deep-dive
- Published: 2026-09-02

---

**OpenLogi enforces strict validation through `deny_unknown_fields`, schema version checks, and automatic migrations, with atomic saves and clear error propagation ensuring configuration integrity across upgrades.**

The `AprilNEA/OpenLogi` repository implements a robust configuration system in Rust that balances strict schema enforcement with graceful recovery from older formats. Every TOML configuration file undergoes multiple validation layers before the application accepts it, while built-in migration paths preserve user data across schema evolution.

## How Strict Validation Prevents Configuration Drift

OpenLogi's validation strategy operates at deserialization, version comparison, and structural levels to reject invalid configurations immediately.

### Reject Unknown Fields with `deny_unknown_fields`

The `Config` struct in [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) uses Serde's `deny_unknown_fields` attribute to eliminate silent configuration errors:

```rust
// From crates/openlogi-core/src/config.rs, lines 98-99
#[serde(deny_unknown_fields)]
pub struct Config {
    // ...
}

```

This causes any TOML key not explicitly defined in the schema to trigger a deserialization error. Users receive immediate feedback about typos or deprecated options rather than having settings silently ignored.

### Schema Version Enforcement

Every configuration carries a `schema_version: u32` field validated against the current codebase's `SCHEMA_VERSION` constant (set to **7** as of the source analysis):

```rust
// From crates/openlogi-core/src/config.rs, lines 94-95
pub const SCHEMA_VERSION: u32 = 7;

```

The `ConfigFile::load_from_path` implementation applies a hard rule: **newer versions are rejected**, while older versions trigger automatic migration. A version of zero or any value exceeding `SCHEMA_VERSION` causes an explicit error, preventing data loss from version mismatches.

## Automatic Recovery Through Schema Migrations

When `ConfigFile::load_from_path` encounters schema versions 1 through 6, it executes a pipeline of migration functions that transform legacy structures into the current format.

### Migration Functions in [`config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/config.rs)

| Migration | Version Range | Purpose |
|-----------|-------------|---------|
| `migrate_thumbwheel_native_direction` | Pre-v7 | Updates thumb-wheel defaults introduced in v7 ([lines 24-33](/AprilNEA/OpenLogi/blob/master/crates/openlogi-core/src/config.rs#L24-L33)) |
| `migrate_transport_scoped_keys` | v4 | Rewrites `direct:` prefixed keys to stable identity keys ([lines 38-57](/AprilNEA/OpenLogi/blob/master/crates/openlogi-core/src/config.rs#L38-L57)) |
| `migrate_owner_locked_gestures` | Pre-v4 | Resolves "owner-locked" gesture data structures ([lines 59-86](/AprilNEA/OpenLogi/blob/master/crates/openlogi-core/src/config.rs#L59-L86)) |

Each migration runs **in-place** on the parsed TOML value before final struct deserialization. After successful migration, the file is rewritten atomically to persist the updated schema.

```rust
// Loading with automatic migration handling
let cfg = ConfigFile::load_from_path(
    openlogi_core::paths::config_dir().join("config.toml")
)?;
// If the loaded file was version 4, migrate_transport_scoped_keys
// transforms it to version 7 before returning
println!("Loaded and migrated to version {}", cfg.schema_version);

```

## Atomic Persistence and Ephemeral Mode

### Atomic Save Operations

The configuration system guarantees durability through atomic file writes. The `save_atomic` implementation (in [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs)) ensures that partial writes never corrupt the on-disk state.

### Ephemeral Configurations for Testing

The `Config` struct includes an internal `ephemeral` flag marked with `#[serde(skip)]`, making it invisible to TOML serialization:

```rust
// From crates/openlogi-core/src/config.rs, lines 152-162
#[serde(skip)]
ephemeral: bool,

```

Creating an ephemeral configuration disables all disk writes:

```rust
// Ephemeral mode: all changes stay in memory
let mut test_cfg = Config::ephemeral();
test_cfg.set_device_custom_name("receiver:abc123:slot:2", Some("Test Device".into()));
test_cfg.save_atomic()?; // No-op: disk is never touched

```

This mechanism allows test suites to manipulate full configurations without risking developer data.

## Error Propagation to the User Interface

When the GUI requests a configuration reload from the background agent, the IPC layer transforms low-level parsing failures into actionable diagnostics.

The `ConfigReloadError` type in [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) ([lines 72-79](/AprilNEA/OpenLogi/blob/master/crates/openlogi-ipc/src/ipc.rs#L72-L79)) carries both the file path and, when available, the precise TOML source location:

```rust
// Handling reload feedback in GUI code
match agent.reload_config().await {
    Ok(_) => println!("Config reloaded successfully"),
    Err(e) => eprintln!("Failed to reload config: {}", e),
    // e.g.: "Failed to reload config: unknown field `gestre` at line 12 column 5"
}

```

This prevents the agent from entering undefined states and gives users exact line numbers for fixing syntax or schema violations.

## Key Implementation Files

| File Path | Responsibility |
|-----------|--------------|
| [`crates/openlogi-core/src/config.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config.rs) | `Config` struct, schema version constant, migration functions |
| [`crates/openlogi-core/src/config/file.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-core/src/config/file.rs) | `ConfigFile` trait with `load_from_path` and `save_atomic` |
| [`crates/openlogi-ipc/src/ipc.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-ipc/src/ipc.rs) | `ConfigReloadError` for cross-process error reporting |
| [`docs/config.example.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/docs/config.example.toml) | Reference configuration following the validated schema |

## Summary

- **`deny_unknown_fields`** enforces schema exactness by rejecting unexpected TOML keys at parse time
- **Schema version checking** blocks forward-incompatible files while triggering automatic migration for older versions
- **Migration pipeline** transforms legacy configurations through specialized functions for each version gap
- **Atomic saves and ephemeral mode** guarantee data durability and enable safe testing
- **Rich error propagation** surfaces precise failure locations to users through the IPC boundary

## Frequently Asked Questions

### What happens if I manually add an unknown key to [`config.toml`](https://github.com/AprilNEA/OpenLogi/blob/main/config.toml)?

OpenLogi will refuse to load the configuration. The `deny_unknown_fields` attribute causes Serde to return a deserialization error with the unknown key name, which propagates through the system as a clear error message pointing to the offending line.

### Can I use a configuration from a newer OpenLogi version with an older binary?

No. The version check explicitly rejects configurations where `schema_version` exceeds the binary's `SCHEMA_VERSION` constant. This prevents silent data loss from settings that the older binary cannot interpret.

### How does migration preserve my custom settings?

Migration functions transform only the structural elements that changed between versions, leaving user-defined values intact. For example, `migrate_transport_scoped_keys` rewrites key prefixes but preserves the associated gesture mappings. The migrated file is then atomically rewritten to disk with the updated `schema_version`.