Files
go/rpc/envelope.go
zeekay a1bf2bfa1e feat: RPC/interfaces codegen + cap spec-conformance (SPEC §2.3/§3) + rebrand finish
zapgen — interface (RPC) support:
- Parse `interface` decls (brace + whitespace forms, one parser) with
  1-based auto-assigned method ordinals; methods take ≤1 struct request and
  (after `returns`) ≤1 struct response, matching the spec README grammar.
- desugar: `interface` opens a whitespace block; method lines pass through
  verbatim (no @N). Block headers now honored only at file scope, so a
  struct field NAMED `interface`/`struct` stays a field (H4 preserved).
- emit: per interface a 1-based ordinal table, a typed Client over a
  Channel seam, and an abstract Handler + Dispatch<Iface> ordinal router,
  all over the new runtime call envelope. Method names exported.
- New `rpc` package: the ZAP call envelope (BuildRequest/ParseRequest,
  BuildResponse/ParseResponse, Call, Response, status codes), byte-compatible
  with the other runtimes' transport envelopes (Version2 header + router
  msgType flags). Adds zap.NewBuilderV2.
- examples/echo: generated service + end-to-end client↔dispatch↔handler test
  covering request/response, void, no-request, bare, handler-error, and
  unknown-ordinal paths. Golden fixtures for the interface codegen.

cap — reconcile to SPEC v1.1:
- B3 signature scope: sign SPEC §3 canonical bytes (Capability[0..164) ||
  canonical(Caveats), Kind:u32||len:u32||Value), via Cap.CanonicalBytes —
  one definition shared by signer and verifier; excludes Sig and heap
  indirection so caps verify cross-language. CapID = Hash32(Canonical||Sig).
- B2 delegation gate: define cross-cutting Permission bits (PermAttenuate
  1<<32, PermAudit, PermRoot); enforce SPEC §2.3 step 3d in VerifyChain and
  at mint time in Attenuate (refuse a cap the verifier would reject).
- B4/B6: VerifyRevocation is scheme-aware (dispatches on the tag byte);
  verifySig fails closed on SchemeReserved/unknown instead of falling back
  to ed25519. Verifier.VerifyRevocation added for PQ revocations.
- spec_test.go covers all four.

Rebrand finish + docs:
- Zero capnp/Cap'n Proto and zero hanzo/avalabs/capnproto.org tokens in
  zap-native source (zap.go doc neutralized; cap doc-comment brand refs
  generalized). LLM.md refreshed: zapgen shipped (not "forthcoming"), rpc
  package, interface DSL, cap signature scope. Ignore the agents example
  binary.

go build ./... and go test ./... -count=1 pass; race-clean on changed pkgs.
2026-06-20 00:02:22 -07:00

147 lines
4.5 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package rpc is the ZAP call envelope: the msgType + method + capability
// framing that carries an interface method call and its response over a
// ZAP transport.
//
// This is the canonical wire contract; the TypeScript runtime's
// envelope.ts mirrors these exact offsets and sizes byte-for-byte, so a
// request built here is decoded by the TS ParseRequest and a response
// built by the TS BuildResponse is decoded by ParseResponse here (and
// vice versa). The generated typed Client and abstract Server (zapgen
// `interface` emission) are thin wrappers over BuildRequest /
// ParseRequest / BuildResponse / ParseResponse.
//
// Request object (fixed size 28):
//
// Method u32 @0 which interface method (the .zap ordinal, 1-based)
// PromiseID u32 @4 caller-assigned id this call's answer resolves to
// Target u32 @8 promise this call pipelines off (0 = root)
// Cap bytes @12 opaque capability buffer (cap.Cap bytes; may be empty)
// Payload bytes @20 ZAP-encoded method params
//
// Response object (fixed size 20):
//
// Status u32 @0 200 ok, else an error code
// PromiseID u32 @4 echoes the request's PromiseID
// Body bytes @12 ZAP-encoded results (empty for a void method)
//
// Both envelopes are finished with header flags = MsgTypeRouterBase << 8
// so a ZAP transport routes them to this service's handler
// (msgType = flags >> 8).
package rpc
import zap "github.com/zap-proto/go"
// MsgTypeRouterBase is this service's ZAP message-type slot, carried in
// the high byte of the header flags word.
const MsgTypeRouterBase uint16 = 200
// NoTarget is the Target value for a call that does not pipeline off an
// earlier promise (the call targets the bootstrap object).
const NoTarget uint32 = 0
// Status codes carried in a Response.
const (
StatusOK uint32 = 200
StatusBadRequest uint32 = 400
StatusUnauthorized uint32 = 401
StatusForbidden uint32 = 403
StatusNotFound uint32 = 404
StatusInternal uint32 = 500
)
// Request field offsets.
const (
reqMethodOff = 0
reqPromiseIDOff = 4
reqTargetOff = 8
reqCapOff = 12
reqPayloadOff = 20
reqFixedSize = 28
)
// Response field offsets.
const (
respStatusOff = 0
respPromiseIDOff = 4
respBodyOff = 12
respFixedSize = 20
)
// Call is one outbound request's fields.
type Call struct {
Method uint32
PromiseID uint32
Target uint32
Cap []byte
Payload []byte
}
// BuildRequest encodes a Call into a router-tagged ZAP message. The
// header is v2 to match the transport default; the flags carry the
// router msgType so the transport dispatches it to this service.
func BuildRequest(c Call) []byte {
b := zap.NewBuilderV2(len(c.Cap) + len(c.Payload) + reqFixedSize + 64)
ob := b.StartObject(reqFixedSize)
ob.SetUint32(reqMethodOff, c.Method)
ob.SetUint32(reqPromiseIDOff, c.PromiseID)
ob.SetUint32(reqTargetOff, c.Target)
ob.SetBytes(reqCapOff, c.Cap)
ob.SetBytes(reqPayloadOff, c.Payload)
ob.FinishAsRoot()
return b.FinishWithFlags(MsgTypeRouterBase << 8)
}
// ParseRequest decodes a router-tagged request message into a Call. The
// returned Cap and Payload slices alias the input buffer; copy them if
// you retain them past the buffer's lifetime.
func ParseRequest(msg []byte) (Call, error) {
m, err := zap.Parse(msg)
if err != nil {
return Call{}, err
}
r := m.Root()
return Call{
Method: r.Uint32(reqMethodOff),
PromiseID: r.Uint32(reqPromiseIDOff),
Target: r.Uint32(reqTargetOff),
Cap: r.Bytes(reqCapOff),
Payload: r.Bytes(reqPayloadOff),
}, nil
}
// Response is a decoded response envelope.
type Response struct {
Status uint32
PromiseID uint32
Body []byte
}
// BuildResponse encodes a status + body into a router-tagged response.
func BuildResponse(status, promiseID uint32, body []byte) []byte {
b := zap.NewBuilderV2(len(body) + respFixedSize + 64)
ob := b.StartObject(respFixedSize)
ob.SetUint32(respStatusOff, status)
ob.SetUint32(respPromiseIDOff, promiseID)
ob.SetBytes(respBodyOff, body)
ob.FinishAsRoot()
return b.FinishWithFlags(MsgTypeRouterBase << 8)
}
// ParseResponse decodes a router-tagged response message. The returned
// Body slice aliases the input buffer.
func ParseResponse(msg []byte) (Response, error) {
m, err := zap.Parse(msg)
if err != nil {
return Response{}, err
}
r := m.Root()
return Response{
Status: r.Uint32(respStatusOff),
PromiseID: r.Uint32(respPromiseIDOff),
Body: r.Bytes(respBodyOff),
}, nil
}