Files
6f0dfd20c5 docs: complete tooling story (pb2zap + zap2pb), zip server, ZAP-only production case (#1)
* docs: tools section (pb2zap, zap2pb), zip server page, ZAP-only production story

- content/docs/tools/: new Tooling nav group — the complete pb<->ZAP
  interop boundary. pb2zap (go/ast codemod off protobuf), zap2pb
  (the one sanctioned protobuf import: Marshal/Unmarshal/Equal/
  AppendMessage, v0.2.0), and the migration path over the existing
  schema/codegen pages.
- content/docs/zip.mdx: the ZAP-native application server — one
  Listen verb (ZAP primary, HTTP secondary), typed handlers with
  REST/OpenAPI/MCP projections, Mount composition, and the
  hanzoai/cloud case: sub-1,000-line compose root hosting ~75K LOC
  of subsystems (76:1).
- why-zap: scrub the unmeasured benchmark tables (per the
  benchmarks-page policy), replace with mechanism arguments and the
  ZAP-only-in-production account: zero gRPC, protobuf confined to
  the four-function zap2pb boundary at the OTLP edge.
- ecosystem: add zip/pb2zap/zap2pb rows; quick-start + index: link
  the new pages, drop the unmeasured 5-20x claim.

* fix(build): alias MDX collections to regenerated docs/, drop frozen .docs snapshot

next.config.mjs and tsconfig.json pointed @hanzo/docs-mdx:collections/*
at .docs/ — a snapshot frozen on Jun 1 — while the MDX plugin
regenerates docs/ on every build. Result: no page added since the
snapshot (schema, codegen, rpc, capabilities, and this PR%27s tools/zip)
was ever emitted; the site silently served 37 routes instead of 45.
Alias both resolvers at docs/ and remove .docs.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-07 17:58:42 -07:00

59 lines
3.3 KiB
Plaintext

---
title: zap2pb
description: The sanctioned ZAP↔protobuf wire boundary — one file, four functions, the only place protobuf is imported.
---
# zap2pb
[`zap2pb`](https://github.com/zap-proto/zap2pb) is the other edge of [pb2zap](/docs/tools/pb2zap): where pb2zap moves your *code* off protobuf, zap2pb is the one place a ZAP-native stack still *touches* protobuf — because some external system on the other end of a socket demands it.
It is deliberately tiny: one file, four functions, wrapping `google.golang.org/protobuf`. That is the point. The package exists as **policy, not convenience** — it is the single sanctioned import of protobuf in the stack, so "who still links protobuf?" has a one-word answer.
```bash
go get github.com/zap-proto/[email protected]
```
## API
```go
// Marshal encodes m to protobuf wire bytes.
func Marshal(m proto.Message) ([]byte, error)
// Unmarshal decodes protobuf wire bytes into m.
func Unmarshal(b []byte, m proto.Message) error
// Equal reports whether a and b are semantically equal messages.
func Equal(a, b proto.Message) bool
// AppendMessage marshals m and appends it to dst as length-delimited
// field num; repeated calls with one num build a `repeated` field,
// byte-identical to marshaling the enclosing message.
func AppendMessage(dst []byte, num int, m proto.Message) ([]byte, error)
```
That is the entire surface. If your interop edge needs more than this, the edge is too wide.
## Why `AppendMessage` exists
The subtle function is `AppendMessage`, and it exists for a dependency-hygiene reason. Generated protobuf packages for RPC services ship their gRPC service stubs in the same package, with no build tag — importing the generated *request* type drags `google.golang.org/grpc` into your module graph. `AppendMessage` lets you hand-build the enclosing request envelope from its grpc-free field messages instead, byte-identical to marshaling the generated type, without ever importing it.
## The OTLP edge, concretely
Observability is the edge where this matters in practice. OpenTelemetry's OTLP (and OpAMP) wire is protobuf; that is the industry contract and not worth fighting. The ZAP-native answer:
- Spans are marshaled as standard OTLP protobuf **messages** — through this package.
- The bytes ship over the **ZAP wire** (zero-copy frames, X-Wing PQ-KEM handshake) to the collector's ZAP receiver — never OTLP-over-HTTP (`:4318`) or OTLP-over-gRPC (`:4317`).
- The `ExportTraceServiceRequest` envelope is built with `AppendMessage` from the grpc-free trace messages, so no gRPC enters the module graph.
The result: OTLP-compatible telemetry, zero gRPC, and protobuf confined to one four-function file. This is the boundary that lets the [ZAP-only production stack](/docs/why-zap#zap-only-in-production) claim *zero protobuf in application code* and mean it.
## The rule
Exactly one import of `google.golang.org/protobuf` per stack — this one. A second import site is a regression, not a style choice: it un-answers the question the package exists to answer.
## Related
- [Tools overview](/docs/tools) — the full pb↔ZAP migration path
- [pb2zap](/docs/tools/pb2zap) — the codemod that gets your code off protobuf in the first place
- [Why ZAP](/docs/why-zap) — what a stack looks like when this boundary is all that is left