Files
zeekay 4738ffea2f docs: hide Z-Wing, kill fabricated perf, banner spec-only protocols
Red-team review (this session) caught half the protocol pages were AI
slop — describing Go APIs that don't exist in repos that contain only
schemas, plus performance tables contradicted by the only real bench
harness in tree. This is the surgery:

post-quantum:
- Drop the Z-Wing brand from public docs (user feedback: confusing).
- Reframe PQ as ZAP's stack: IETF X-Wing KEM (transport) + hybrid
  Ed25519+ML-DSA-65 (identity) + PQ-RNS (name service). One canonical
  story; layers are independent.
- Per-language status matrix shows realistic state (X-Wing KAT shipped
  in 4 SDKs, sigs in all, resolver in progress).

protocols/{a2a,acp,fix,ws}:
- Marked "Status — spec-only, January 2026". Schema exists; runtime
  doesn't. Removed every Go code snippet that called a non-existent
  function. Replaced with a roadmap section.

protocols/rns:
- Rewrote against the actual schema at zap-proto/rns/schema/zap_rns.zap
  (Record / Query / Response / signed-by-registry).
- Added the PQ-RNS end-to-end flow diagram and the DID definition
  (base32(SHA3-256(kemPk || sigPk))).
- Documents the cross-language KAT fixture published at
  zap-proto/rns/testdata/pqrns_kat.json.

protocols/mcp:
- Rewrote against the actual bridge.go API: NewBridge(*zap.Node),
  AddServer, GetTools, CallTool, CallToolByName. Bridges existing
  stdio MCP servers (it's a bridge, not a from-scratch impl).
- Dropped the invented zmcp.NewServer / Connect / ListTools API.

protocols/http:
- Killed the 6× req/s / 19× p99 / 6.2× RSS table — contradicted by
  bench-results.txt which shows ZAP-HTTP currently ~10% slower.
- Replaced with honest current numbers + the value-prop statement.

protocols/native:
- Killed the 18× / 38× / 6.7× / 9.2× gRPC table — the bench harness
  has no gRPC comparator, the numbers were fabricated.
- Replaced with the actual concurrency=32 numbers from bench-results
  + an explanation that the win is semantic, not currently latency.

transports:
- Killed the @zap-proto/wasm and Cloudflare-Worker code snippets that
  referenced a non-existent npm package.
- Replaced with the honest pattern: browser→TLS→Worker→ZAP-over-X-Wing
  to origin. Points readers at the SDK status table.

app/page.tsx:
- Dropped Z-Wing from the home page cards.
2026-05-27 04:49:24 -07:00

240 lines
7.5 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Native ZAP RPC
description: ZAP used directly — capability-based RPC, no higher-level protocol on top.
---
# Native ZAP RPC
When you reach for HTTP, MCP, or FIX, you're carrying *someone else's* protocol on the ZAP wire. **Native ZAP RPC** is when ZAP is the application protocol: you define your own interfaces in `.zap` schema, generate code, and your services talk directly in ZAP messages.
This is the fastest path on the wire and the only path that gives you ZAP's full capability model — explicit, transferable, revocable capabilities instead of bearer tokens.
## When to use native vs. a higher-level protocol
| Use native ZAP RPC | Use a protocol-over-ZAP |
|---|---|
| Greenfield internal service ↔ service | You need to be reachable from existing HTTP / MCP / FIX clients |
| You want capabilities, not API keys | You're integrating with code you don't control |
| You want streaming with no framing tax | You're behind a corporate gateway that only knows HTTP |
| You care about every µs | The classical protocol's semantics are exactly right |
## Define a schema
```
# calculator.zap
interface Calculator
add (a Float64, b Float64) -> (result Float64)
subtract (a Float64, b Float64) -> (result Float64)
divide (a Float64, b Float64) -> (result Float64)
sum (numbers List(Float64)) -> (total Float64)
observe (subject Text) -> (stream stream Float64)
```
No file IDs. No ordinals. Forward-compatible: append-only field/method addition is non-breaking by construction.
## Generate code
```bash
zap compile -o ./gen calculator.zap --go --ts --py --rust --java
```
One pass generates every language at once. The compiler is content-addressed; an unchanged schema produces byte-identical output.
## Server (Go)
```go
package main
import (
"context"
"errors"
"log"
zap "github.com/zap-proto/go"
"yourmod/gen"
)
type calc struct{}
func (calc) Add(ctx context.Context, a, b float64) (float64, error) { return a + b, nil }
func (calc) Subtract(ctx context.Context, a, b float64) (float64, error) { return a - b, nil }
func (calc) Divide(ctx context.Context, a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func (calc) Sum(ctx context.Context, ns []float64) (float64, error) {
var t float64
for _, n := range ns {
t += n
}
return t, nil
}
func (calc) Observe(ctx context.Context, subject string, out zap.Stream[float64]) error {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
if err := out.Send(rand.Float64()); err != nil {
return err
}
}
}
}
func main() {
srv := zap.NewServer(zap.WithAddr("zap://0.0.0.0:9000"))
gen.RegisterCalculatorServer(srv, calc{})
log.Fatal(srv.ListenAndServe())
}
```
Every parameter and return is a zero-copy ZAP buffer. `zap.Stream[T]` is the bidirectional half of an open call — `Observe` can push values until the client disconnects.
## Client (Go)
```go
ctx := context.Background()
client, _ := zap.Dial(ctx, "zap://calc.example:9000")
defer client.Close()
c := gen.NewCalculatorClient(client)
sum, _ := c.Add(ctx, 10, 5)
fmt.Println(sum) // 15
stream, _ := c.Observe(ctx, "ticker")
for v := range stream.Recv(ctx) {
fmt.Println(v)
}
```
## Capabilities
Native ZAP RPC's killer feature: methods can return **capabilities** — references that the holder can call but cannot forge, transfer freely, and the issuer can revoke without changing keys.
```
# storage.zap
interface Storage
open (path Text) -> (file File)
interface File
read (n UInt32) -> (bytes Data)
write (bytes Data) -> ()
close () -> ()
```
```go
// Server: returning a capability
func (s *storage) Open(ctx context.Context, path string) (gen.File, error) {
f, err := os.Open(path)
if err != nil { return nil, err }
return &fileHandle{f: f}, nil
}
```
```go
// Client: using a returned capability
storage, _ := gen.NewStorageClient(client).Open(ctx, "/etc/hosts")
data, _ := storage.Read(ctx, 1024)
storage.Close(ctx)
```
Crucially, you can pass `storage` to a *different* peer over the same connection — they can read from `/etc/hosts` on the original server without ever holding the original client's identity. That's capability delegation. There is no equivalent in HTTP.
## Multi-language clients
The exact same schema generates working clients in every language. From a TypeScript client calling the Go server above:
```ts
import { Calculator } from './gen/calculator'
import { connect } from '@zap-proto/zap'
const client = await connect('zap://calc.example:9000')
const calc = new Calculator(client)
console.log(await calc.add(10, 5)) // 15
for await (const v of calc.observe('ticker')) {
console.log(v)
}
```
Python:
```python
from gen.calculator import Calculator
from zap_proto import connect
async with await connect('zap://calc.example:9000') as client:
calc = Calculator(client)
print(await calc.add(10, 5)) # 15
async for v in calc.observe('ticker'):
print(v)
```
Rust:
```rust
use zap_proto::Client;
use gen::calculator::CalculatorClient;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Client::connect("zap://calc.example:9000").await?;
let calc = CalculatorClient::new(client);
println!("{}", calc.add(10.0, 5.0).await?); // 15
let mut stream = calc.observe("ticker").await?;
while let Some(v) = stream.next().await {
println!("{}", v?);
}
Ok(())
}
```
## Authentication
Connections are mutually KEM-authenticated by default. The peer's identity is bound to its keypair, not to a bearer token:
```go
srv := zap.NewServer(
zap.WithAddr("zap://0.0.0.0:9000"),
zap.WithIdentity(zap.LoadIdentity("./service.key")),
zap.WithAuthorize(func(peer zap.Peer, method string) error {
if peer.DID() != "did:zap:z6Mk..." && method == "Divide" {
return zap.ErrPermissionDenied
}
return nil
}),
)
```
For unauthenticated services (public read-only endpoints), use `zap.WithAnonymous()`.
## Performance
The current [zap-proto/bench](https://github.com/zap-proto/bench) harness measures ZAP-HTTP and a length-prefixed native ZAP echo against `net/http`+JSON. Honest numbers from `bench-results.txt` (concurrency=32, small workload):
- HTTP/1.1+JSON: 34,411 req/s
- ZAP-binary HTTP: 30,868 req/s (0.90×)
A native-ZAP echo (no HTTP shape, no headers, length-prefixed frames) is in `native_zap_test.go` — comparison numbers will be folded into `bench-results.txt` in the next harness pass. Until then there is no published native-vs-gRPC table here, because we don't have a reproducible gRPC comparator in-tree.
What native ZAP RPC actually buys you over gRPC isn't µs of latency — that depends on a careful harness — it's the semantic shift: **capabilities instead of bearer tokens**, **identity-bound transport** instead of TLS-over-PKI, **one wire format** that the rest of the protocol stack rides on. The latency improvement, when measured, will land in the benchmarks page sourced from the bench harness output.
## Related
- [Protocol wire spec](/docs/protocol) — byte-for-byte format
- [Architecture](/docs/architecture) — why capabilities, why content-addressed
- [Transports](/docs/transports) — TCP, UDP+FEC, shared-memory, in-process
- [Gateway](/docs/gateway) — translate native ZAP into HTTP/JSON for browsers
- [SDKs](/docs/sdks) — per-language client + server libraries