# Gateway routing ledger v2

This format is independent of repository, object, and workspace formats. Adding
a gateway changes none of their bytes. The gateway root contains:

- `SCRIBE_GATEWAY`: exact UTF-8 bytes `scribe-gateway\n2\n`.
- `LOCK`: exclusive OS ownership lock.
- `routes.redb`: redb 4 database containing `routes` and `instances` tables.
- `status.json`: disposable observation snapshot, never authoritative routing.

The root holds no credentials or trust material. The gateway's TLS key and its
service-account credential file (`[tls] key` and `[tls] credential` in the
[gateway configuration](/docs/gateway-usage.md#tls-configuration)) are not part of
this format; keep them outside the root (`scribe-ctl` refuses them inside it).

`routes` maps a repository address string to a `scribe_format::gateway::RouteRecord`.
On a TLS gateway every instance serves one auth namespace and the address is
`<namespace>/<name>`, so the same storage name in two namespaces is two routes
and the namespace of a route is the prefix of its key. A plaintext gateway has
no namespaces and keys routes by the bare name. Ledger v1 keyed every route by
bare name; a v1 root is refused, not migrated. The record itself is unchanged:

| Field | Encoding |
|---|---|
| Record version | u8 = 1 |
| Instance name | u16 LE byte length, then UTF-8; 1–64 ASCII letters/digits/hyphens/underscores |
| Storage compression | u8: 0 none, 1 LZ4 |
| Has repository ID | u8: 0 or 1 |
| Repository ID | 16 exact bytes, present only when the preceding flag is 1 |
| Conflict flag | u8: 0 or 1 |

Unknown versions, invalid flags, invalid compression, invalid names, truncated
records, and trailing bytes are rejected. A record without an ID is a durable
pending name reservation. A conflict flag prevents routing that name. On open,
every route's instance must still be configured and must still serve the
namespace in the route's key; otherwise the gateway refuses to start.
Byte fixtures and strict decoding tests are in
[`gateway.rs`](https://github.com/Flying-Rat/scribe/blob/main/crates/scribe-format/src/gateway.rs).

`instances` maps a configured instance-name string to its exact 16-byte data-root
incarnation. An identity is pinned after successful inventory. A different
identity at the same name or the same identity under another name is rejected.

Both placement reservations and completed assignments commit with
`Durability::Immediate`. Payload encoding, authentication, inventory collection,
and upstream requests happen outside database write transactions. Routing state
is never inferred from the disposable JSON snapshot. Unsupported ledger versions
are refused; there is no ledger migration command.
