zap-proto/zap-spec
Canonical, language-agnostic schemas for the ZAP wire format.
This repository defines what a ZAP message is. The language runtimes
(github.com/zap-proto/go, github.com/zap-proto/zap-es, and future
Rust / Swift / Kotlin runtimes) implement the format described here.
What is ZAP?
ZAP is a fixed-layout, zero-copy binary wire format. Each struct has a
fixed-size header where every field sits at a compile-time offset; variable-
length payloads (bytes, text, lists, nested structs) are referenced from the
header by a (offset, length) pointer pair into the same message. Readers
parse in O(1) — no scanning, no allocation.
Primitives
| Type | Size in fixed area | Notes |
|---|---|---|
bool |
1 | 0 = false, 1 = true |
u8 / i8 |
1 | |
u16 / i16 |
2 | little-endian |
u32 / i32 |
4 | little-endian |
u64 / i64 |
8 | little-endian |
f32 / f64 |
4 / 8 | IEEE 754 |
bytes |
8 | (offset:u32, length:u32) — payload in heap area |
text |
8 | UTF-8, same layout as bytes |
bytes_fixed[N] |
N | inline, no indirection |
list<T> |
8 | (offset:u32, length:u32) — payload in heap area |
| nested struct | 4 | offset:u32 — pointed-to header in heap area |
Discriminator
Every ZAP message on the wire begins with a 2-byte discriminator:
+--------+--------+----------------- ... -------------------+
| TypeK | ShapeK | fixed header | heap area (var-length) |
+--------+--------+----------------- ... -------------------+
TypeKind (1 byte) names a registered struct family (e.g. 0xC0 = capability).
ShapeKind (1 byte) names the specific struct within that family (e.g. 0x01
= Capability, 0x02 = Revocation). The combination uniquely identifies
the schema the bytes that follow conform to. The envelope writes / reads this
discriminator; the body's first byte is at offset 2 of the message.
.zap DSL
Schemas are authored in a tiny DSL that the codegen tools consume directly.
package <ident>
type <name> = <primitive-or-bytes_fixed[N]> # type alias
struct <Name> {
<FieldName> <type> @<offset> # offset in bytes from start
...
}
interface <Name> {
<method>(<param>: <Struct>) returns (<param>: <Struct>) # request + response
<method>() returns () # no payloads
<method>(<param>: <Struct>) # request, no response
...
}
Supported types in field position: every primitive above, plus list<T>,
bytes_fixed[N], type aliases, and references to other structs by name.
Comments are # to end-of-line. See capabilities.zap in this repo for a
production example.
Interfaces (services)
An interface declares an RPC service: a named set of methods. Each method
takes at most one struct payload as its request and, after returns, at most
one struct payload as its response — either may be empty (()). Method
ordinals are auto-assigned 1, 2, 3, … in declaration order; they are the
integers carried on the wire envelope's method field. Appending a method never
renumbers the existing ones, so the wire ordinal of an existing method is
stable for the life of the interface (reordering or removing a method is a
breaking change — declare a new interface instead).
Codegen emits, per interface, a typed client, an abstract server with
ordinal-routed dispatch, and a method-ordinal table — over the host runtime's
ZAP call envelope. Struct emission is unchanged; interface is purely additive.
Codegen tools
Each language runtime ships a codegen that consumes .zap files from this
repo:
- Go:
github.com/zap-proto/go/cmd/zapgen(zapgen schema.zap > schema.go) - TypeScript: the
zapgenbin of@zap-proto/zap(npx zapgen schema.zap) - Rust:
github.com/zap-proto/zap-rs/cmd/zapgen-rs(forthcoming)
The DSL grammar is identical across all runtimes; the codegen emits idiomatic accessors for the host language while preserving the exact byte layout.
What lives here
| File | Purpose |
|---|---|
capabilities.zap |
Capability schema v1.0 (object-cap auth) |
capabilities_kinds.md |
CapKind + CaveatKind + Permission enums |
SPEC.md |
Capability semantics, threat model, wire policy |
Add new message families as <family>.zap + <family>_kinds.md.
Versioning
The schemas here are frozen at v1.0 once tagged. Any wire change requires
a new struct name (e.g. Capability2) and a new ShapeKind allocation.
Codegen consumers pin to a tag in their build.