wireframe 0.3.0

Simplify building servers and clients for custom binary protocols.
Documentation
# Message Versioning and Compatibility

> **Status:** Targeted for a post-1.0 release — see the
> [schema evolution roadmap item]roadmap.md#4-extended-features for progress.

## Motivation

Binary protocols often evolve. New features require changes to message
structures, while older clients may still be deployed in the field. `wireframe`
needs a strategy to support multiple message versions without breaking
compatibility.

## Goals

- **Version negotiation**: Determine a mutually supported protocol version when
  a client connects.
- **Per-message versions**: Allow message types to define version numbers.
- **Compatibility checks**: Detect incompatible versions at runtime and surface
  meaningful errors.
- **Ergonomic API**: Provide Actix-like helpers for routing and version guards.

## Actix Web Inspiration

Actix Web uses "scopes" and guards to group endpoints by path or header values.
A similar approach can work for binary protocols: routes can be grouped under a
`VersionScope`, and a custom guard can inspect a version field before dispatch.

## Proposed Design

### 1. VersionedMessage Trait

```rust
pub trait VersionedMessage: Message {
    const VERSION: u16;
}
```

Derive macros will implement this trait for message structs. Handlers can then
declare their expected version by accepting `VersionedMessage` types.

### 2. Envelope Extensions

Extend `Envelope` with an optional `version` field:

```rust
pub struct Envelope {
    id: u32,
    version: Option<u16>,
    msg: Vec<u8>,
}
```

The serializer will encode and decode this field transparently. Older clients
that lack a version field will represent it as `None`.

### 3. Version Guards

Introduce a `version_guard` method on `WireframeApp` to register handlers for a
specific version. Internally this works like Actix Web's `scope` with a guard:

```rust
app.version_guard(2).route(MessageId::Login, handle_login_v2);
```

If a message's version does not match any registered guard, a configurable
fallback handler can respond with an error.

### 4. Compatibility Checks

Handlers for newer versions can implement `From<OldVersion>` to convert legacy
messages. A helper `ensure_compatible` function will attempt the conversion
when a mismatched version is received.

```rust
async fn handle_login_v2(req: Message<LoginV2>) { /* ... */ }

app.version_guard(1).route(MessageId::Login, |m: Message<LoginV1>| {
    let upgraded: LoginV2 = m.into();
    handle_login_v2(Message(upgraded))
});
```

### 5. Handshake Negotiation

During the optional connection preamble, both sides can exchange the highest
protocol version they support. The server selects a version and stores it in
the connection state so extractors and middleware can access it. If no
compatible version exists, the connection is rejected early.

## Future Work

- The features described in this document are considered post-1.0 scope.
- Tooling to generate migration code between versions.
- Optional compile-time features for strict version matching or automatic
  upgrades.
- Documentation examples showing how to deprecate old versions gracefully.