mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
The typed-op registry gets its fourth projection. An aggregator that reaches
another app today either imports its package — dragging in that app's whole
dependency graph, which is what turns a fleet of small services into one
mega-binary — or hand-writes a client against its URLs, which is the schema
copied into a second place to drift. Neither is necessary: the op already has
a stable name, and now that name is callable.
c, err := zip.DialApp("flags") // $ZIP_RUNTIME_DIR/flags.sock
out, err := zip.Call[BoolIn, BoolOut](ctx, c, "flags_bool", &in)
The plane is an ordinary route (/.well-known/zip/op/:op), so it rides every
transport Listen was given and inherits the app's middleware, error handler,
validator and Authorizer — exposed exactly as much as the REST routes and no
more. It resolves through opByName + op.invoke, the same pair tools/call runs,
without MCP's agent-facing envelope. Errors cross whole: the wire form is what
errorHandler already writes, so errors.As sees the *HTTPError the callee
returned, status intact. A void op yields (nil, nil).
Generic, not generated, deliberately: a generated per-op Go client would put a
second copy of the schema in the caller's repo on its own regeneration
schedule — the drift this plane removes, one directory over — and would be a
fourth client generator racing openapi-generator, which already builds the
published SDKs from the OpenAPI projection.
ONE socket-path scheme, used by both halves so they cannot drift:
SocketPath(name) = RuntimeDir() + "/" + name + ".sock"
RuntimeDir() = $ZIP_RUNTIME_DIR | $XDG_RUNTIME_DIR/zip | /run/zip
A service serves at zip.Addr(zip.SocketPath("flags")), a caller reaches it with
zip.DialApp("flags"). socketIn states the shape once; the plugin loader names a
child's private socket with the same function. Listen creates the directory
0700 when missing, so the canonical path works on a fresh host.
Identity is never forged, because the caller never states it. WHO the request
is for is the gateway's assertion, forwarded by Ctx.Forward and read by
CallerOf; WHAT is calling is SO_PEERCRED, read by PeerOf — the peer does not
send it, the kernel answers on its behalf. nil means "cannot attest", so a
check fails closed rather than reading a zero Peer as root.
MCPTools() exposes the tool surface the same way OpenAPISpec() exposes the
document, so a host can compose plugins' tools in-process without a round trip.
openapi.go now reads operationId from opName, the one place that rule lives,
instead of re-deriving it inline — all four projections address an op by the
same token.
Cost: Benchmark_TypedRoute/typed 28 -> 29 allocs/op (the one context value that
makes CallerOf and PeerOf possible). The untyped path is unchanged at 27 and
still pinned at one heap value per request.
Proven end to end: a real plugin on a unix socket, a client dialing it, a typed
round trip with no HTTP in the path — the reply frame is decoded with zaphttp's
own codec, HTTP/1.1 text gets no HTTP answer, and the peer credential the
handler observes is proof the transport was unix. Full suite green with
CGO_ENABLED=0 and under -race.
Co-authored-by: Hanzo Dev <[email protected]>
35 lines
965 B
Go
35 lines
965 B
Go
//go:build linux
|
|
|
|
package zip
|
|
|
|
import (
|
|
"net"
|
|
"syscall"
|
|
)
|
|
|
|
// peerOf reads SO_PEERCRED off a unix connection — the pid, uid and gid the
|
|
// KERNEL recorded for the process at the far end when it connected. It is not
|
|
// a value the peer sends, so there is nothing for it to forge.
|
|
//
|
|
// A non-unix conn has no answer: a tcp peer is an address, and an address is
|
|
// not an identity. That case returns nil rather than a zero Peer, so "not
|
|
// attested" is never mistaken for "attested as root".
|
|
func peerOf(c net.Conn) *Peer {
|
|
uc, ok := c.(*net.UnixConn)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
raw, err := uc.SyscallConn()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var cred *syscall.Ucred
|
|
var sockErr error
|
|
if err := raw.Control(func(fd uintptr) {
|
|
cred, sockErr = syscall.GetsockoptUcred(int(fd), syscall.SOL_SOCKET, syscall.SO_PEERCRED)
|
|
}); err != nil || sockErr != nil || cred == nil {
|
|
return nil
|
|
}
|
|
return &Peer{PID: int(cred.Pid), UID: int(cred.Uid), GID: int(cred.Gid)}
|
|
}
|