a peer that is this process is a function call, not a socket

zip.Ask names an app and reaches it over that app's canonical socket. When the
app IS this process — a fused binary, or a service asking one of its own ops —
that socket is a loop: the value the caller holds is encoded, handed to the
kernel, read back, and parsed into a copy for a function pointer that was in
memory the whole time. Only the ADDRESSING ever needed a wire, and a by-name
call already has that.

So a bound listener records which App answers for it. Serving(name) reads it
back and Here runs the op through the op's OWN invoke seam — the same
opByName the socket plane uses, the same validate and authorize a REST request
and an MCP tools/call get. registeredOp.direct and registeredOp.invoke now
share one contract closure rather than each carrying a copy, so there is no way
into a handler that skips a check another way makes; direct differs only in
that it is handed the In instead of bytes to decode it from.

Serving goes false before the listeners close, so the in-process door and the
socket cannot disagree about whether the app is up.

This is what a co-resident caller uses instead of building a request for its own
router to take apart again. That pattern cost cloud a re-entrancy counter keyed
on a goroutine id parsed out of runtime.Stack, because dispatching into the
whole app re-ran every edge middleware and a middleware that read the subsystem
it was serving recursed without bound. Entering one op cannot express that.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-05 19:14:00 -07:00
co-authored by Hanzo Dev
parent 6c2674394d
commit f048747237
6 changed files with 396 additions and 21 deletions
+40
View File
@@ -453,6 +453,46 @@ canonical path works on a fresh host. An existing directory keeps its own mode:
a deployment needing a socket shared across users creates the directory itself
and points `ZIP_RUNTIME_DIR` at it.
## The peer that is not a peer — `here.go` (v1.26.1)
Sometimes the app a caller names is *this* process. A fused binary mounts many
apps; a service asks one of its own ops. Dialing the canonical socket for that
is a loop — encode a value we already hold, hand it to the kernel, read it back,
parse it into a copy — and only the ADDRESSING ever needed a wire.
So a bound listener records which `*App` answers for it, and two calls read that
back:
```go
if a := zip.Serving("commerce"); a != nil { // is it here?
return zip.Here[BalanceIn, Balance](ctx, a, "finance_balance", &in)
}
out, err := zip.Ask[BalanceIn, Balance](ctx, "commerce", "finance_balance", &in)
```
`Here` is `Call` with the `Conn` removed. It finds the op with the same
`opByName` the socket plane uses and runs it through the op's own seam, so it
validates and authorizes identically — `registeredOp.direct` and
`registeredOp.invoke` share one contract closure, and there is no path into a
handler that skips a check another path makes. What `direct` does not do is
decode, or bind a URL and headers: there is no request, and the caller supplies
the whole input at once. The reply is the handler's own value, not a copy —
read it, do not write it.
`Serving(name)` is a fact about the PROCESS, keyed on `SocketPath(name)` so
there is no second opinion about where an app lives. It goes true when the
listener binds and false when it closes, before the listener does, so the
in-process door and the socket never disagree about whether the app is up. Nil
means "not here" and never "not deployed" — absence in the DEPLOYMENT is the
router's word.
Why it exists: cloud reached its own co-resident subsystems by building an
`http.Request` and dispatching it into its own router, which re-ran every edge
middleware — including middlewares that themselves read the subsystem being
served, so a read re-entered the app without bound. The cure there was a depth
cap keyed on a goroutine id parsed out of `runtime.Stack`. A direct op call
cannot express that shape: it enters one op, not one router.
## Who is calling — `caller.go`
Two questions, two authorities, neither answered by the caller:
+99
View File
@@ -0,0 +1,99 @@
package zip
import (
"context"
"fmt"
"sync"
)
// here.go — the peer that is not a peer.
//
// [Ask] names an app and reaches it over that app's canonical socket. When the
// app is THIS process, that socket is a loop: the value the caller holds is
// serialized, handed to the kernel, read back, parsed into a fresh copy, and
// dispatched to a function pointer that was in memory the whole time. Nothing
// about the call needed a wire; only the ADDRESSING did, and addressing is
// exactly what a by-name call already has.
//
// So a bound listener records which App answers for it, [Serving] reads that
// back, and [Here] runs the op through the op's OWN invoke seam — the same
// validate → authorize → run core a REST request, an MCP tools/call, a CLI
// command and a socket call all land on. It is one fewer transport, not one more:
// the contract is the op, and only the encoding was ever the transport's.
//
// This is what a co-resident caller uses INSTEAD of building a request for its
// own router to take apart again. That pattern cost cloud a re-entrancy counter
// keyed on a goroutine id parsed out of runtime.Stack, because dispatching a
// request into the whole app re-ran every edge middleware, and a middleware that
// read the app it was serving recursed without bound. A direct op call cannot
// express that shape: it enters one op, not one router.
// serving maps a bound address to the App answering on it. The ADDRESS is the
// key rather than a name, because an address is what a listener has and
// [SocketPath] is already the one mapping from a name to it — so this holds no
// second opinion about where an app lives.
var serving sync.Map // addr -> *App
// bound records that a answers at addr, and returns the undo. Called by
// listenOn for every address it binds.
func bound(addr string, a *App) func() {
serving.Store(addr, a)
return func() { serving.Delete(addr) }
}
// Serving returns the App THIS PROCESS serves at name's canonical socket, or
// nil when the app is somewhere else.
//
// It answers a question a caller has to be able to ask before it spends a wake
// or a dial on a peer that is already in memory. It is a fact about the process,
// not a configuration: an app is here because something in this program bound
// its socket.
//
// Nil is the ordinary answer — a fleet of one-app processes is every app but
// one — and it means "not here", never "not deployed". Absence in the DEPLOYMENT
// is the router's word.
func Serving(name string) *App {
if a, ok := serving.Load(SocketPath(name)); ok {
return a.(*App)
}
return nil
}
// Here invokes op on a — in this process, with no wire under it.
//
// It is [Call] with the Conn removed. The op is found by the same [App.opByName]
// the socket plane uses and run through the same invoke seam, so it validates
// and authorizes identically; what it does NOT do is encode the input, because
// there is nothing between the two ends to encode it for.
//
// The reply is the handler's OWN value. Over a wire a caller gets a fresh copy
// because a wire cannot hand back anything else; here it does not, and a reply
// is a reply — read it, do not write it.
//
// In and Out must be the pair the op declared. A socket call learns that from a
// decode failure at the far end; here it is a type assertion, and the error says
// so plainly.
func Here[In, Out any](ctx context.Context, a *App, op string, in *In) (*Out, error) {
if a == nil {
return nil, fmt.Errorf("zip: %s: no app here", op)
}
// opByName is the ONE by-name lookup — the same one the call plane and
// tools/call run. An op this app does not answer is the same error whichever
// way it was reached.
o := a.opByName(op)
if o == nil || o.direct == nil {
return nil, ErrNotFound("unknown op: " + op)
}
out, err := o.direct(ctx, in)
if err != nil {
return nil, err
}
if out == nil {
return nil, nil
}
reply, ok := out.(*Out)
if !ok {
return nil, fmt.Errorf("zip: %s answers %T, not %T", op, out, reply)
}
return reply, nil
}
+186
View File
@@ -0,0 +1,186 @@
package zip_test
import (
"context"
"net"
"path/filepath"
"sync/atomic"
"testing"
"time"
"github.com/zap-proto/zip"
)
type addIn struct {
A int `json:"a"`
B int `json:"b"`
}
type addOut struct {
Sum int `json:"sum"`
}
// A co-resident op is reached through the OP, not through the wire.
//
// This is the property the seam exists for, and the test proves it the only way
// that cannot be argued with: it takes the wire away. The listener is shut down,
// a dial to the socket then fails — and the same op, on the same app, still
// answers. A call that needed the socket could not survive that.
//
// It matters because the alternative shape has a cost that is not obvious.
// cloud reached its own co-resident subsystems by building an http.Request and
// dispatching it into its own router, which re-ran every edge middleware —
// including middlewares that themselves read the subsystem being served, so a
// read re-entered the whole app without bound. The cure there was a depth
// counter keyed on a goroutine id parsed out of runtime.Stack. The cure here is
// not calling the router at all: [Here] enters one op.
func TestHereRunsTheOpWithoutTheSocket(t *testing.T) {
dir := t.TempDir()
t.Setenv(zip.RuntimeDirEnv, dir)
var ran atomic.Int64
app := zip.New(zip.Config{AppName: "calc"})
zip.Post[addIn, addOut](app, "/add", func(_ context.Context, in *addIn) (*addOut, error) {
ran.Add(1)
return &addOut{Sum: in.A + in.B}, nil
}, zip.WithOperationID("add"))
sock := zip.SocketPath("calc")
if want := filepath.Join(dir, "calc.sock"); sock != want {
t.Fatalf("socket path = %s, want %s", sock, want)
}
go func() { _ = app.Listen(sock) }()
waitFor(t, sock)
// The app answers for its own name, in this process.
a := zip.Serving("calc")
if a == nil {
t.Fatal("Serving(calc) = nil after Listen on its canonical socket")
}
if a != app {
t.Fatal("Serving(calc) returned a different app than the one that bound the socket")
}
// Over the socket: a real connection, a real encode, for comparison.
conn, err := zip.DialApp("calc")
if err != nil {
t.Fatalf("dial: %v", err)
}
remote, err := zip.Call[addIn, addOut](context.Background(), conn, "add", &addIn{A: 2, B: 3})
if err != nil {
t.Fatalf("call over the socket: %v", err)
}
if remote.Sum != 5 {
t.Fatalf("socket call = %d, want 5", remote.Sum)
}
_ = conn.Close()
// In-process, with the listener still up: same op, same answer.
local, err := zip.Here[addIn, addOut](context.Background(), a, "add", &addIn{A: 2, B: 3})
if err != nil {
t.Fatalf("Here: %v", err)
}
if local.Sum != 5 {
t.Fatalf("Here = %d, want 5", local.Sum)
}
// Now take the wire away.
if err := app.Shutdown(); err != nil {
t.Fatalf("shutdown: %v", err)
}
if c, err := net.DialTimeout("unix", sock, time.Second); err == nil {
_ = c.Close()
t.Fatal("the socket still accepts after Shutdown; the wire was not actually removed")
}
after, err := zip.Here[addIn, addOut](context.Background(), app, "add", &addIn{A: 2, B: 3})
if err != nil {
t.Fatalf("Here with no listener: %v — the in-process call is still going through the wire", err)
}
if after.Sum != 5 {
t.Fatalf("Here with no listener = %d, want 5", after.Sum)
}
if got := ran.Load(); got != 3 {
t.Fatalf("handler ran %d times, want 3 (socket, in-process, in-process with no listener)", got)
}
}
// An op this app does not answer is the same refusal whichever way it is
// reached — a name is a name, not a transport.
func TestHereRefusesAnUnknownOp(t *testing.T) {
app := zip.New(zip.Config{AppName: "calc"})
zip.Post[addIn, addOut](app, "/add", func(_ context.Context, in *addIn) (*addOut, error) {
return &addOut{Sum: in.A + in.B}, nil
}, zip.WithOperationID("add"))
if _, err := zip.Here[addIn, addOut](context.Background(), app, "subtract", &addIn{}); err == nil {
t.Fatal("Here on an unregistered op returned no error")
}
if _, err := zip.Here[addIn, addOut](context.Background(), nil, "add", &addIn{}); err == nil {
t.Fatal("Here with no app returned no error")
}
}
// Serving answers about THIS process and nothing else. An app that never bound
// its socket is not here, and one that shut down stops being here — otherwise a
// caller would be handed an app on its way out while the socket beside it was
// already refusing connections.
func TestServingFollowsTheListener(t *testing.T) {
dir := t.TempDir()
t.Setenv(zip.RuntimeDirEnv, dir)
app := zip.New(zip.Config{AppName: "meter"})
zip.Post[addIn, addOut](app, "/add", func(_ context.Context, in *addIn) (*addOut, error) {
return &addOut{Sum: in.A + in.B}, nil
}, zip.WithOperationID("add"))
if zip.Serving("meter") != nil {
t.Fatal("Serving(meter) is non-nil before anything bound the socket")
}
sock := zip.SocketPath("meter")
go func() { _ = app.Listen(sock) }()
waitFor(t, sock)
if zip.Serving("meter") == nil {
t.Fatal("Serving(meter) = nil while its socket is bound")
}
if err := app.Shutdown(); err != nil {
t.Fatalf("shutdown: %v", err)
}
if zip.Serving("meter") != nil {
t.Fatal("Serving(meter) is still answering after Shutdown")
}
}
// The direct seam runs the SAME contract the decoding one does: an input the op
// declared invalid is refused identically whether it arrived as bytes or as a
// value. A second path into a handler that skipped a check would be a hole in
// exactly the shape nobody looks at.
func TestHereValidatesLikeTheWire(t *testing.T) {
type payIn struct {
Subject string `json:"subject" validate:"required"`
}
app := zip.New(zip.Config{AppName: "till"})
zip.Post[payIn, addOut](app, "/pay", func(_ context.Context, _ *payIn) (*addOut, error) {
return &addOut{Sum: 1}, nil
}, zip.WithOperationID("pay"))
if _, err := zip.Here[payIn, addOut](context.Background(), app, "pay", &payIn{}); err == nil {
t.Fatal("Here accepted an input the op's own validation rejects")
}
if _, err := zip.Here[payIn, addOut](context.Background(), app, "pay", &payIn{Subject: "acme"}); err != nil {
t.Fatalf("Here refused a valid input: %v", err)
}
}
func waitFor(t *testing.T, path string) {
t.Helper()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
c, err := net.DialTimeout("unix", path, time.Second)
if err == nil {
_ = c.Close()
return
}
time.Sleep(5 * time.Millisecond)
}
t.Fatalf("socket %s never accepted", path)
}
+15
View File
@@ -196,6 +196,7 @@ func (a *App) listenOn(addrs []string) error {
h := a.pinned()
servers := make([]Server, 0, len(addrs))
unbind := make([]func(), 0, len(addrs))
for _, raw := range addrs {
scheme, addr, t, err := transportFor(raw)
if err != nil {
@@ -220,11 +221,16 @@ func (a *App) listenOn(addrs []string) error {
t.applyConfig(a.cfg)
}
servers = append(servers, s)
// This app now answers at this address, and a caller in THIS process can
// reach its ops without the address (see [Serving] and [Here]). Recorded
// here rather than at Serve because binding is what makes it true.
unbind = append(unbind, bound(addr, a))
a.logger.Info("zip listening", "transport", scheme, "addr", addr)
}
a.srvMu.Lock()
a.servers = servers
a.unbind = unbind
a.srvMu.Unlock()
a.listening.Add(int32(len(servers)))
@@ -342,7 +348,16 @@ func makeSocketDir(addr string) error {
func (a *App) closeServers() {
a.srvMu.Lock()
servers := a.servers
unbind := a.unbind
a.unbind = nil
a.srvMu.Unlock()
// Stop answering by name BEFORE the listeners go, so an in-process caller
// cannot be handed an App that is on its way down while a caller over the
// socket is already getting a refused connection. The two ends of the same
// app must not disagree about whether it is up.
for _, u := range unbind {
u()
}
for _, s := range servers {
_ = s.Close()
}
+50 -21
View File
@@ -60,6 +60,17 @@ type registeredOp struct {
// choice: it says WHO declared the type, which is a property of the code.
Origin string
invoke func(ctx context.Context, dec decoder, rawIn []byte, query, path map[string]string, header func(string) string) (any, error)
// direct is invoke with the decoding removed: the In arrives as the *In the
// caller already holds. It exists for the one transport that is not a
// transport — a call whose two ends are the same process (see [Here]) —
// where encoding the value would only be a way of copying it.
//
// It shares the contract half (validate → authorize → run) with invoke
// rather than reimplementing it, so there is no path into a handler that
// skips a check another way makes. What it does not share is the BINDING of
// a URL and headers onto the input: there is no URL and no request, and an
// op reached this way is given its whole input at once.
direct func(ctx context.Context, in any) (any, error)
}
// decoder reads a request body into an op's In. It is a PARAMETER rather than a
@@ -433,9 +444,44 @@ func registerTyped[In, Out any](on OpTarget, method, path string, fn TypedHandle
// authorizer on every invoke — REST and MCP alike.
meta := Op{Method: op.Method, Path: op.Path, OperationID: opName(op)}
// The transport-agnostic core: decode raw JSON args → In, validate, authorize,
// run fn, return Out (or a literal nil for a void result). REST and MCP both
// call THIS — one handler, many projections. A nil *Out becomes a nil `any`.
// The CONTRACT, on an In however it was obtained: validate, authorize, run
// fn, return Out (or a literal nil for a void result). Both seams below end
// here, so no way of reaching this op can skip a check another way makes. A
// nil *Out becomes a nil `any`.
run := func(ctx context.Context, in *In) (any, error) {
if err := validate(in); err != nil {
return nil, ErrBadRequest(err.Error())
}
// Authorize the DECODED input — the exact value the handler will bind — so
// the decision cannot diverge from execution. Runs for REST and MCP alike.
if auth := app.authorizer; auth != nil {
if err := auth(ctx, meta, in); err != nil {
return nil, err
}
}
out, err := fn(ctx, in)
if err != nil {
return nil, err
}
if out == nil {
return nil, nil
}
return out, nil
}
// The in-process seam: the caller already holds the *In, so there is nothing
// to decode and no URL to bind from. See registeredOp.direct and [Here].
op.direct = func(ctx context.Context, in any) (any, error) {
v, ok := in.(*In)
if !ok {
return nil, ErrBadRequest(fmt.Sprintf("%s takes %T, not %T", opName(op), (*In)(nil), in))
}
return run(ctx, v)
}
// The transport-agnostic core: decode raw JSON args → In, bind the URL, then
// the contract above. REST and MCP both call THIS — one handler, many
// projections.
op.invoke = func(ctx context.Context, dec decoder, rawIn []byte, query, path map[string]string, header func(string) string) (any, error) {
var in In
if len(rawIn) > 0 {
@@ -460,24 +506,7 @@ func registerTyped[In, Out any](on OpTarget, method, path string, fn TypedHandle
}
bindURL(&in, query)
bindURL(&in, path)
if err := validate(&in); err != nil {
return nil, ErrBadRequest(err.Error())
}
// Authorize the DECODED input — the exact value the handler will bind — so
// the decision cannot diverge from execution. Runs for REST and MCP alike.
if auth := app.authorizer; auth != nil {
if err := auth(ctx, meta, &in); err != nil {
return nil, err
}
}
out, err := fn(ctx, &in)
if err != nil {
return nil, err
}
if out == nil {
return nil, nil
}
return out, nil
return run(ctx, &in)
}
handler := func(c fiber.Ctx) error {
// hasBody is THE rule about what a method carries, read here as well as
+6
View File
@@ -220,6 +220,12 @@ type App struct {
servers []Server // the running transport listeners, set by Listen
// unbind withdraws this app from the by-address table [Serving] reads, one
// entry per bound listener. Held beside servers because they have the same
// lifetime: an app answers in-process for exactly as long as it answers on
// the wire.
unbind []func()
// authorizer, when set via Authorize, runs at every typed op's invoke seam
// on the decoded In — the one place REST and MCP both funnel the value the
// handler will act on. nil leaves every decoded request unauthorized.