Documentation
¶
Overview ¶
Package gateway is the entry point that wraps a sov rpc.Engine and exposes it over an HTTP-shaped server. The package owns the HTTP boundary; the rpc package itself is transport-free.
The HTTP server is pluggable via the Server interface. The default implementation uses net/http and is suitable for production. Consumers who want fiber / fasthttp / echo / a custom server implement Server themselves and pass it via WithServer(...). Users do NOT depend on a fiber adapter shipped by sov.
A Gateway wraps:
- rpc.Engine — services hosted IN-PROCESS (the modular-monolith case)
- Resolver chain — services resolved to remote endpoints (the microservice case). LocalResolver consults the in-process engine; RegisterResolver consults the TTL-backed map populated by POST /rpc/_register.
Both can coexist per request: the gateway tries each resolver in order. In-process services dispatch via direct Engine.Dispatch; remote services HTTP-proxy with strip+inject of X-Sov-* claim headers. The wire shape is identical in either path — same encoder, same auth, same observability, same error model. That symmetry is the PEMM thesis.
Framework endpoints — always present, gateway-owned:
- GET /rpc/_health aggregated health rollup
- GET /rpc/_introspect aggregated rpc.Engine.Describe() across services
Builtin-plugin endpoints — present when the plugin is registered via gw.Use(...), not part of the bare gateway:
- POST /rpc/_register service self-registration + heartbeat TTL (builtin/registry)
- POST /rpc/_batch object-keyed fan-in across multiple methods (builtin/batch)
Service-level `_X` paths (e.g. /rpc/WidgetService/_debug) are refused at the gateway with 404; those are reachable only via direct intra-cluster addressing.
Index ¶
- Constants
- func AuthorizationFromContext(ctx *rpc.Context) string
- func BuildTypeCatalog(report *IntrospectReport)
- func ComputeOverallHealthStatus(svc map[string]HealthService) string
- func GenerateIntrospectTraceID() string
- func GetCapability[T any](g *Gateway, typeName string) (T, bool)
- func HaltErr(err error) error
- func IsHalt(err error) bool
- func LocalRouters(gw *Gateway) []string
- func NormalizeUpstreamURL(raw string) (string, error)
- func PipeStream(fn func(w io.Writer) error) io.ReadCloser
- func RespondErr(resp *Response, err error) error
- type AuthBinding
- type AuthService
- type AuthTranslator
- type AuthzBinding
- type AuthzDecision
- type AuthzService
- type BatchCall
- type BatchRequest
- type BatchResponse
- type BootValidator
- type Capability
- type CapabilityProvider
- type CheckParams
- type Claims
- type ClaimsCache
- type Client
- type ClientOption
- type ConfigApplier
- type Conflict
- type ContextContributor
- type DispatchEvent
- type DispatchHook
- type Endpoint
- type EntryOptions
- type Gateway
- func (g *Gateway) AllCapabilities() map[string][]Capability
- func (g *Gateway) AuthBinding() *AuthBinding
- func (g *Gateway) AuthzBinding() *AuthzBinding
- func (g *Gateway) BuildProxyRequest(ctx context.Context, method, addr, path string, body []byte, parent *Request) (*http.Request, error)
- func (g *Gateway) ConsumeFederationPreemption(svc string)
- func (g *Gateway) Engine() *rpc.Engine
- func (g *Gateway) ExposeIntrospect()
- func (g *Gateway) Handle(ctx context.Context, req *Request) *Response
- func (g *Gateway) IntrospectBody(ctx context.Context, req *Request) *Response
- func (g *Gateway) IntrospectExposed() bool
- func (g *Gateway) InvalidateCatalog()
- func (g *Gateway) JoinMesh(ctx context.Context, opts MeshOptions) error
- func (g *Gateway) LinkPeer(peer *Gateway, services ...string)
- func (g *Gateway) ListenAndServe(ctx context.Context, addr string) error
- func (g *Gateway) LocalClient() Client
- func (g *Gateway) Log() Logger
- func (g *Gateway) LookupCapability(typeName string) (any, bool)
- func (g *Gateway) MustUse(p any)
- func (g *Gateway) MustUseAll(plugins ...any)
- func (g *Gateway) OverrideAuthBinding(service, method string)
- func (g *Gateway) OverrideAuthzBinding(service, method string)
- func (g *Gateway) PluginByName(name string) any
- func (g *Gateway) PluginInfos() []PluginInfo
- func (g *Gateway) PluginNames() []string
- func (g *Gateway) PolicyAllowsRoleTakeover(current, candidate string, role RoleFlag) bool
- func (g *Gateway) PreemptFederation(svc, oldAddr, newAddr string) bool
- func (g *Gateway) ProxyClient() *http.Client
- func (g *Gateway) Register(router any)
- func (g *Gateway) RegisterAuth(svc AuthService)
- func (g *Gateway) RegisterAuthz(svc AuthzService)
- func (g *Gateway) RegisterRemote(name, address string, ttl time.Duration, opts ...RemoteOptions)
- func (g *Gateway) RegisterResolver() *RegisterResolver
- func (g *Gateway) Resolver() Resolver
- func (g *Gateway) Run(ctx context.Context, addr string) error
- func (g *Gateway) RunMesh(ctx context.Context, opts MeshOptions) error
- func (g *Gateway) Use(p any) error
- func (g *Gateway) UseAll(plugins ...any) error
- func (g *Gateway) UseMiddleware(mw Middleware)
- type Handler
- type Header
- type HeaderClaimer
- type HeaderInjector
- type HeaderParser
- type HealthAggregator
- type HealthGateway
- type HealthReport
- type HealthService
- type HookFailure
- type IntrospectContributor
- type IntrospectReport
- type LifecycleHook
- type LocalResolver
- type LogLevel
- type Logger
- type MeshConflictPolicy
- type MeshOptions
- type Middleware
- type Middlewarer
- type NetHTTPOptions
- type NetHTTPServer
- type Option
- func WithAdvertiseURL(url string) Option
- func WithClaimsCache(c ClaimsCache) Option
- func WithMiddleware(mw ...Middleware) Option
- func WithProxyClient(c *http.Client) Option
- func WithRegisterResolver(r *RegisterResolver) Option
- func WithResolver(r Resolver) Option
- func WithServer(s Server) Option
- func WithTrustUpstreamClaims(b bool) Option
- type Options
- type Plugin
- type PluginDependency
- type PluginDoc
- type PluginInfo
- type RecoveryHandler
- type RegisterEntry
- type RegisterRequest
- type RegisterResolver
- func (r *RegisterResolver) AddressGroup() map[string][]string
- func (r *RegisterResolver) Close()
- func (r *RegisterResolver) Delete(service string)
- func (r *RegisterResolver) Introspectables() []string
- func (r *RegisterResolver) Put(service, address string, ttl time.Duration)
- func (r *RegisterResolver) PutEntry(service, address string, ttl time.Duration, opts EntryOptions)
- func (r *RegisterResolver) Resolve(_ context.Context, service string) (*Endpoint, bool)
- func (r *RegisterResolver) Services() []string
- type RegisterResolverOption
- type RegisterResponse
- type RegisterStore
- type RemoteOptions
- type Request
- type RequestHandler
- type Resolver
- type Response
- type ResponseInterceptor
- type RoleFlag
- type RouteHandler
- type SealVerifier
- type Server
- type TypeDescriptor
- type TypeUse
- type TypeVariant
- type TypeVariants
- type UpstreamTrustPolicy
- type VerifyParams
Constants ¶
const ( ContextKeyRemoteIP = "http.remoteIP" ContextKeyPath = "gateway.path" )
ContextKeyRemoteIP and ContextKeyPath are where dispatchLocal stashes the caller IP and the inbound request path for handlers that need them via ctx.Get(...). Constants so callers don't repeat the literals.
const ( HeaderSubject = "X-Sov-Subject" HeaderIssuer = "X-Sov-Issuer" HeaderScopes = "X-Sov-Scopes" // comma-joined OAuth scopes HeaderExpires = "X-Sov-Expires" // unix seconds )
Header name constants for the injected claim bundle. Identity-only — no role/permission header. Role lookup happens at the AuthzService at decision time; per-request claim bundles never carry authorization state. These four are framework-owned because every sov gateway speaks them on the wire; the seal (X-Sov-Seal) is the hmacseal plugin's contract — see gateway/builtin/hmacseal/proto.
const ( IntrospectTraceHeader = "X-Sov-Introspect-Trace" IntrospectVisitedHeader = "X-Sov-Introspect-Visited" // IntrospectInternalHeader, when set to "1" on a root /rpc/_introspect // request, returns the FULL catalog including SOFT-hidden methods // (flagged internal:true). Hard-hidden methods are never returned. // The explorer's "show internal" toggle sets it. IntrospectInternalHeader = "X-Sov-Introspect-Internal" )
Introspect cascade headers — propagate a request id + the set of visited gateway addresses so federated cycles short-circuit and duplicate probes are deduped.
const ( ModeLocal = "local" // in-process router method handled it ModeRemote = "remote" // proxied to a single remote address ModeFederated = "federated" // proxied via an intermediate gateway ModePeer = "peer" // dispatched to a linked in-process peer ModeFramework = "framework" // framework endpoint (_health, _introspect, …) ModePlugin = "plugin" // handled by a RouteHandler plugin )
Dispatch mode labels for DispatchEvent.Mode / Response.Mode. Use these constants instead of the bare string literals at the dispatch call sites.
const ContextKeyAuthorization = "sov.authorization"
ContextKeyAuthorization is where dispatchLocal stashes the verbatim inbound Authorization header. Cross-service callers (e.g. a mesh pod's HTTP-backed Client) forward this back to the gateway for the downstream call so the impersonation chain is intact.
const ContextKeyClaims = rpc.ContextKeyClaims
ContextKeyClaims is re-exported from rpc so existing gateway callers keep working. Prefer ctx.Claims() in handler code.
Variables ¶
This section is empty.
Functions ¶
func AuthorizationFromContext ¶
AuthorizationFromContext returns the verbatim inbound bearer header the gateway stashed during dispatch. Cross-service callers forward this on subsequent RPCs so identity propagates through call chains. Typed accessor — consumers never reach for ctx.Get(ContextKeyAuthorization) directly.
func BuildTypeCatalog ¶
func BuildTypeCatalog(report *IntrospectReport)
BuildTypeCatalog walks every service's descriptors, extracts each param + response type, builds the flat Types map, and detects drift across same-named types into CrossRefs. Exported so plugin aggregators can rebuild the catalog after merging remote descriptors into report.Services.
func ComputeOverallHealthStatus ¶
func ComputeOverallHealthStatus(svc map[string]HealthService) string
ComputeOverallHealthStatus rolls up per-service health into a single status. Exported so plugin health aggregators can reuse the same rule the framework applies to its local view.
func GenerateIntrospectTraceID ¶
func GenerateIntrospectTraceID() string
generateTraceID returns a short id used to dedupe diamond fan-outs across the introspect cascade. Time-based (cheap, no crypto/rand dep here) — collisions are harmless because the visited list is the primary loop guard. GenerateIntrospectTraceID returns a short id used to dedupe diamond fan-outs across the introspect cascade. Exported for plugin aggregators that originate a cascade from a non-root call.
func GetCapability ¶
GetCapability is the generic helper consumers reach for. Returns the typed value when the capability exists AND the stored Impl matches the requested type; (zero, false) otherwise.
gen, ok := gateway.GetCapability[func() string](gw, "requestid.IDGenerator")
if ok {
id := gen()
...
}
Defined as a package-level generic (not a method) because Go methods don't take type parameters. The pattern matches the stdlib's slog.Any/AttrFunc style.
func HaltErr ¶
HaltErr wraps err so the gateway refuses startup. Boot-time hooks (BootValidator, ConfigApplier, LifecycleHook.OnStart) recognize this sentinel and bubble it up from ListenAndServe.
if !cfg.Valid() {
return gateway.HaltErr(fmt.Errorf("config invalid: %s", reason))
}
func LocalRouters ¶
LocalRouters returns every wire-named router registered on gw — a convenience for team gateways that want to federate "everything I host":
gw.JoinMesh(ctx, sov.MeshOptions{
UpstreamURL: prime,
Federate: sov.LocalRouters(gw),
})
func NormalizeUpstreamURL ¶
NormalizeUpstreamURL returns the canonical scheme://host:port form every federation layer agrees on. Used by:
- WithFederationPreemption map keys and values
- WithUpstreamGateways allowlist
- X-Sov-Introspect-Visited cycle detection
Rules:
- Scheme is lowercased; only http and https are accepted.
- Host is lowercased; IPv6 keeps brackets.
- Port is always explicit (defaults are NOT stripped — http://x:80 stays http://x:80 so "is x the same as x:80?" never arises).
- Path, query, fragment, user-info, and trailing slash are stripped. Federation identity is host:port only.
Two DNS aliases that resolve to the same IP normalize to distinct canonical strings — sov does not resolve DNS here. Operators using multiple aliases for the same pod must list every alias explicitly in their preemption/allowlist sets.
func PipeStream ¶
func PipeStream(fn func(w io.Writer) error) io.ReadCloser
PipeStream adapts a writer-callback into an io.ReadCloser for Response.Stream — the ergonomic form when you have code that writes to an io.Writer (a zip.Writer, csv.Writer, a template, an SSE loop) rather than a ready-made reader.
fn runs in its own goroutine writing to the pipe; whatever it writes is streamed to the client as it is produced, in constant memory. fn's return value closes the read end: nil → clean EOF; an error → the adapter's io.Copy stops and the transfer truncates (the client sees a short/aborted body — there is no way to send a 500 after the status line is already on the wire, so validate before you start streaming).
The producer never blocks forever: if the client disconnects, the adapter closes the reader, the next pw.Write fails with io.ErrClosedPipe, and fn returns.
A panic inside fn is recovered and surfaced to the reader as an error (the transfer truncates) instead of crashing the process — fn runs in its own goroutine, so the gateway's request-scoped recovery middleware cannot see it. The panic value is not swallowed silently: it becomes the pipe's close error.
return &gateway.Response{
Status: 200,
Header: gateway.Header{
"Content-Type": "application/zip",
"Content-Disposition": `attachment; filename="export.zip"`,
},
Stream: gateway.PipeStream(func(w io.Writer) error {
zw := zip.NewWriter(w)
for _, f := range files {
fw, err := zw.Create(f.Name)
if err != nil {
return err
}
if _, err := fw.Write(f.Data); err != nil {
return err
}
}
return zw.Close()
}),
}
func RespondErr ¶
RespondErr wraps err with a *Response the gateway returns to the caller instead of a default 500 envelope. Request-path hooks (HeaderParser, Middlewarer, RouteHandler, ResponseInterceptor) recognize this and short-circuit.
if origin == "" {
return gateway.RespondErr(&gateway.Response{Status: 400, Body: ...},
fmt.Errorf("missing Origin header"))
}
Types ¶
type AuthBinding ¶
AuthBinding records which registered service is the auth verifier. A gateway has at most one.
type AuthService ¶
type AuthService interface {
Verify(ctx *rpc.Context, p *VerifyParams) (*Claims, error)
}
AuthService is the contract gw.RegisterAuth requires. The wire name is derived from the implementing struct's type name (strip "Router" suffix); the method is always "verify". Verify resolves a bearer token to identity-only Claims — never to a profile row.
type AuthTranslator ¶
AuthTranslator fires AFTER the auth middleware has resolved Claims (when it has). Translate the verified identity into any format your brownfield downstreams expect. Mutate req.Header to add legacy headers; the gateway forwards them on the proxy hop.
Claims may be nil for anonymous requests — translators that need an identity should early-return in that case.
type AuthzBinding ¶
AuthzBinding records the policy-as-service binding, if any. The gateway calls Service/Method with the resolved Claims (or nil for anonymous) plus the downstream {service, method} the caller wants to invoke; the response is `{allow, authenticate, reason}`.
type AuthzDecision ¶
type AuthzDecision struct {
Allow bool `json:"allow"`
Reason string `json:"reason,omitempty"`
Authenticate bool `json:"authenticate,omitempty"`
}
AuthzDecision is the shape the registered authz service must return.
- Allow=true → request proceeds.
- Allow=false → request denied. Gateway returns 403 FORBIDDEN with Reason as the message — unless Authenticate=true.
- Allow=false, Authenticate=true → gateway returns 401 UNAUTHORIZED with Reason as the message. Use this when the method requires a logged-in caller and none is present (anonymous claims).
type AuthzService ¶
type AuthzService interface {
Check(ctx *rpc.Context, p *CheckParams) (*AuthzDecision, error)
}
AuthzService is the contract gw.RegisterAuthz requires. The method is always "check"; the wire name comes from the struct type.
The gateway calls Check on EVERY request when an AuthzService is bound — including anonymous requests (claims == nil). This makes the authz service the single source of truth for "this method requires auth" vs "this method requires admin scope" vs "this method is public". Returning {Allow:false, Authenticate:true} tells the gateway to surface 401 not 403, so the same primitive expresses both authentication-required and authorization-denied without a second hop.
type BatchCall ¶
type BatchCall struct {
Service string `json:"service"`
Method string `json:"method"`
Args json.RawMessage `json:"args,omitempty"` // forwarded verbatim as {"args": <args>}
}
BatchCall is one entry in a batch — caller-supplied alias keys the result map symmetrically.
type BatchRequest ¶
BatchRequest is the JSON body of POST /rpc/_batch.
type BatchResponse ¶
type BatchResponse struct {
Results map[string]json.RawMessage `json:"results"`
}
BatchResponse is the success body.
type BootValidator ¶
BootValidator fires once at gw.ListenAndServe entry. Return an error to refuse startup with a clear message — replaces the boot-panic pattern (e.g. TrustUpstreamClaims-without-seal panic becomes a validator on the trust-guard plugin).
type Capability ¶
Capability is a plugin-published, type-safe contract another plugin (or business handler) consumes. Plugin advertises capabilities via CapabilityProvider; consumers look them up with GetCapability[T] (see capability.go). Convention: Type uses "<plugin>.<contract>" namespace (e.g. "requestid.IDGenerator", "audit.Recent") so the origin is visible at a glance and collisions are rare.
type IDGenerator func() string
func (p *Plugin) Capabilities() []gateway.Capability {
return []gateway.Capability{
{Type: "requestid.IDGenerator", Impl: IDGenerator(p.generate)},
}
}
type CapabilityProvider ¶
type CapabilityProvider interface {
Capabilities() []Capability
}
CapabilityProvider opts a plugin into capability publication. gw.Use() harvests Capabilities() once at registration. Multiple plugins MAY publish the same Type — first wins on lookup, but the framework records all and surfaces them in introspect for drift detection.
type CheckParams ¶
type CheckParams struct {
Claims *Claims `json:"claims,omitempty"`
Service string `json:"service"`
Method string `json:"method"`
}
CheckParams is the request payload the gateway sends to AuthzService.Check. Claims is nil for anonymous requests; the authz service is expected to handle that case (typically by returning {Allow:false, Authenticate:true} for non-public methods).
type Claims ¶
Claims is the verified caller identity. Aliased to rpc.Claims so handlers can read identity via ctx.Claims() without importing the gateway package. The wire shape (X-Sov-* headers, seal) is owned by the gateway; the Go type lives in rpc/.
func ClaimsFromContext ¶
ClaimsFromContext returns the gateway-stamped Claims, or nil. Prefer ctx.Claims() in handlers; this helper exists for callers that already have a *rpc.Context and don't want the method syntax.
func ClaimsFromHeaders ¶
ClaimsFromHeaders parses X-Sov-* headers into a *Claims. Returns nil if no X-Sov-Subject is present. Typed accessor — downstream services reach for this instead of `h.Get(gateway.HeaderSubject)` + string gymnastics. Pair with the hmacseal/proto.Verify call when an HMAC secret is configured so forged claim headers can be rejected.
type ClaimsCache ¶
type ClaimsCache interface {
// Get returns cached Claims for token, or ok=false on miss.
Get(token string) (claims *Claims, ok bool)
// Put stores Claims for token. Honor claims.ExpiresAt for eviction.
Put(token string, claims *Claims)
}
ClaimsCache caches verified Claims keyed by the raw bearer token so the gateway skips AuthService.verify on repeat requests. The default impl is in-memory (per-replica). Implement this and pass WithClaimsCache to back it with Redis/memcached so a fleet of gateway replicas shares verified results instead of each re-verifying.
SECURITY: the token is a secret. Never log it. In a shared/remote store, hash the key (e.g. SHA-256) rather than storing raw tokens, and set the entry TTL from claims.ExpiresAt so a cached identity can't outlive the token. The gateway independently re-checks ExpiresAt after Get, so a stale entry is never honored even if an impl forgets to expire it.
type Client ¶
Client is the cross-service caller a router uses to invoke another service's method. Two implementations satisfy it:
gw.LocalClient() — calls back into the same gateway's dispatch in-process. No HTTP loopback, no JSON round-trip; the engine handles the call directly. Right answer for monolith mode.
gateway.NewClient(baseURL) — HTTP POST to a remote gateway. Right answer for mesh pods making cross-service calls.
Both auto-forward the verbatim inbound Authorization header so identity propagates across hops without manual header plumbing. Consumer code calls the same Call signature in either topology — that is the PEMM "wire IS the in-process API" contract made literal.
func NewClient ¶
func NewClient(baseURL string, opts ...ClientOption) Client
NewClient returns a Client that POSTs /rpc/{router}/{method} against baseURL. The base URL has no trailing slash and no /rpc/ suffix.
type ClientOption ¶
type ClientOption func(*httpClient)
ClientOption mutates a *httpClient.
func WithHTTPClient ¶
func WithHTTPClient(c *http.Client) ClientOption
WithHTTPClient overrides the underlying *http.Client.
type ConfigApplier ¶
ConfigApplier lets a plugin mutate the gateway's framework-owned configuration at registration time. Fires synchronously inside gw.Use BEFORE any other hook is registered, so a config-applying plugin (mesh secret, allowlist, etc.) takes effect for every subsequent request — including ones served while later plugins are still being registered.
type Conflict ¶
type Conflict struct {
Role RoleFlag // RoleAuth | RoleAuthz, zero if federation
FederatedAddrs [2]string // [old, new] for federation case; zero strings if role case
}
Conflict carries the case discriminator for MeshConflictPolicy. Exactly one of Role / FederatedAddrs is populated at any call site:
- Role != 0 means role-takeover (FederatedAddrs is zero strings).
- FederatedAddrs[0] != "" means federation preemption (Role is zero); FederatedAddrs is [oldAddr, newAddr].
type ContextContributor ¶
ContextContributor lets a plugin stash per-request metadata onto the *rpc.Context that the in-process engine hands to local handlers. Fires in dispatchLocal AFTER the framework's own stashes (Authorization, Claims, path) but BEFORE engine.Dispatch.
The symmetric counterpart of HeaderInjector for the local path — HeaderInjector adds bytes to outbound HTTP requests; this adds values to in-process ctx. A plugin that wants its metadata available in BOTH paths (request-id, trace-id, tenant) implements both. Plugin owns the ctx-key namespace; conventionally "sov.<plugin>.<field>".
func (p *Plugin) ContributeContext(ctx *rpc.Context, req *gateway.Request) {
ctx.Set("sov.requestid", req.Header.Get("X-Sov-Request-Id"))
}
type DispatchEvent ¶
type DispatchEvent struct {
Router string `json:"router"`
Method string `json:"method"`
Path string `json:"path"`
Status int `json:"status"`
Duration time.Duration `json:"duration_ns"`
Subject string `json:"subject,omitempty"`
ErrorCode string `json:"error_code,omitempty"`
BatchID string `json:"batch_id,omitempty"`
At time.Time `json:"at"`
// Mode records where the call actually ran. PEMM observability —
// audit + metrics + tracing can split rates by where the work
// landed without instrumenting the dispatcher itself.
//
// Values:
// "local" — in-process router method handled it
// "remote" — proxied to a single remote address
// "federated" — proxied via an intermediate gateway
// "framework" — framework endpoint (/rpc/_health, _introspect, …)
// "plugin" — handled by a RouteHandler plugin
// "" — pre-dispatch reject (404, path validation, etc.)
Mode string `json:"mode,omitempty"`
}
DispatchEvent is the post-handler observation the gateway emits to every DispatchHook. JSON-tagged so plugins can marshal events directly without further translation.
type DispatchHook ¶
type DispatchHook interface {
OnDispatch(ev DispatchEvent) error
}
DispatchHook fires AFTER a handler returns (success or error). Sees the resolved router/method, status, duration, and identity. Runs on the dispatch goroutine; long-running work belongs on a buffered chan the plugin owns (otherwise you slow down every request).
type Endpoint ¶
type Endpoint struct {
Local bool
RemoteAddr string // e.g. "http://widgets-pod-7:8080" — no trailing slash
Peer Handler // when set, dispatch via this in-process Handler (nested PEMM)
}
Endpoint is what a resolver returns. Three shapes:
- Local=true: dispatch against the in-process engine
- Peer non-nil: dispatch against another *Gateway in the same binary (nested PEMM, no HTTP hop)
- RemoteAddr set: HTTP-proxy to that base URL
Peer takes precedence over Local; RemoteAddr is the fallback.
type EntryOptions ¶
type EntryOptions struct {
// Introspectable, when true, lets the gateway probe this remote
// pod's /rpc/_introspect on aggregation. When false (default), the
// remote stays out of the catalog — useful for services that
// haven't enabled introspection or want to opt out of the
// org-wide type browser.
Introspectable bool
}
EntryOptions configure a RegisterResolver entry.
type Gateway ¶
type Gateway struct {
// contains filtered or unexported fields
}
Gateway is the entry point. It wraps a rpc.Engine (services in-process) and a Resolver chain (services elsewhere) behind a pluggable Server. The same Gateway can do both — that is the PEMM thesis the framework promises.
func New ¶
New constructs a Gateway. All Options are optional — the bare gateway.New() returns a usable standalone gateway with sensible defaults. The gateway always owns its rpc.Engine internally; reach it via g.Engine() only for power-user escape hatches.
func (*Gateway) AllCapabilities ¶
func (g *Gateway) AllCapabilities() map[string][]Capability
AllCapabilities returns every registered capability grouped by Type — useful for diagnostics that surface collisions (more than one plugin publishing the same Type). Order of entries within a slice matches registration order.
func (*Gateway) AuthBinding ¶
func (g *Gateway) AuthBinding() *AuthBinding
AuthBinding returns the current auth binding (nil if unbound). Registry plugin reads this to detect cross-name role conflicts on /rpc/_register.
func (*Gateway) AuthzBinding ¶
func (g *Gateway) AuthzBinding() *AuthzBinding
AuthzBinding returns the current authz binding (nil if unbound).
func (*Gateway) BuildProxyRequest ¶
func (g *Gateway) BuildProxyRequest(ctx context.Context, method, addr, path string, body []byte, parent *Request) (*http.Request, error)
BuildProxyRequest constructs an outbound HTTP request to addr+path pre-populated with the parent request's forwarded headers, the injected X-Sov-* claim bundle, the forwarded-for IP, and every registered HeaderInjector. Plugin authors (batch, custom proxy) use this so their outbound calls participate in the same header-injection chain as the framework's own dispatchRemote.
func (*Gateway) ConsumeFederationPreemption ¶
ConsumeFederationPreemption notifies registered MeshConflictPolicy plugins that a preempted federation rule has been consumed (the takeover succeeded). Plugins drop the rule they own.
func (*Gateway) Engine ¶
Engine returns the underlying rpc.Engine — an escape hatch for power users. Normal consumers register via g.Register / g.RegisterRemote and never need this.
func (*Gateway) ExposeIntrospect ¶
func (g *Gateway) ExposeIntrospect()
ExposeIntrospect opens the PUBLIC /rpc/_introspect endpoint. Called by the builtin introspect plugin's Apply; the endpoint is off by default because the catalog discloses the full API surface. Idempotent.
func (*Gateway) Handle ¶
Handle dispatches one Request through the full middleware chain and returns the resulting Response. Useful for tests and for embedding the gateway inside a custom Server implementation that wants to call dispatch directly rather than going through Server.Handle.
func (*Gateway) IntrospectBody ¶
IntrospectBody builds (and caches) the introspect catalog body for req, independent of whether the PUBLIC endpoint is exposed. The explorer and federation aggregators call this directly so they get the catalog in-process without requiring /rpc/_introspect to be open. req may carry the X-Sov-Introspect-Internal / -Trace / -Visited headers.
func (*Gateway) IntrospectExposed ¶
IntrospectExposed reports whether the public /rpc/_introspect endpoint is open (i.e. the introspect plugin was used).
func (*Gateway) InvalidateCatalog ¶
func (g *Gateway) InvalidateCatalog()
InvalidateCatalog drops the cached aggregated _introspect body so the next probe rebuilds. Registry plugin calls this after a /rpc/_register write.
func (*Gateway) JoinMesh ¶
func (g *Gateway) JoinMesh(ctx context.Context, opts MeshOptions) error
JoinMesh starts the pod: registers with the upstream gateway, heartbeats on the configured interval, and serves until ctx is cancelled. Combines what the previous demo's hand-coded heartbeat() + ListenAndServe() did into one call.
JoinMesh blocks until ctx is cancelled or the server returns an error. The heartbeat goroutine stops when ctx is cancelled.
func (*Gateway) LinkPeer ¶
LinkPeer wires another in-process *Gateway as the dispatch target for the listed service names. Calls bypass HTTP entirely — peer's Handle is invoked directly on the dispatch path. Nested PEMM in one binary: same handler code, different gateway. Folds what the retired localpeer plugin did into the core API.
pub := sov.New()
pub.Register(&PublicRouter{})
admin := sov.New()
admin.Register(&AdminRouter{})
pub.LinkPeer(admin, "Admin")
Service names should NOT collide with the host gateway's own routers — the LocalResolver wins the chain.
func (*Gateway) ListenAndServe ¶
ListenAndServe starts the underlying server. Returns when ctx is cancelled or the server returns an error.
Plugin lifecycle: every registered BootValidator runs first; the first error refuses startup. Then every LifecycleHook.OnStart runs. On context cancel or server return, LifecycleHook.OnStop runs in reverse registration order.
func (*Gateway) LocalClient ¶
LocalClient returns a Client that dispatches against this gateway directly — no HTTP, no JSON encoding overhead. The resolver chain still decides whether a given service call is local or proxied to a remote pod, so a "local" client can transparently reach remote services too. This is the PEMM "wire IS the in-process API" point.
func (*Gateway) Log ¶
Log returns the structured logger framework + builtins should use. Returns the first-registered Logger plugin, or a slog.Default() adapter when none registered. Always non-nil — safe to call before any plugin is wired.
gw.Log().Info("plugin loaded", "name", p.PluginName())
func (*Gateway) LookupCapability ¶
LookupCapability returns the first-registered capability with the given Type, or (nil, false) when none. Most callers should use the generic GetCapability[T] for type-safe access; LookupCapability is here for introspection-style code that needs the raw any.
func (*Gateway) MustUseAll ¶
MustUseAll is the panicking variant of UseAll for main() callers that would otherwise unwrap with log.Fatal. Panics with the wrapped error so the program exits with a stack trace.
func (*Gateway) OverrideAuthBinding ¶
OverrideAuthBinding records the auth role binding, SILENTLY overwriting any existing one — the override semantics are the whole point, so the name says so. The registry plugin calls this from its /rpc/_register handler AFTER its RoleConflictPolicy check has already authorized the takeover; RegisterRemote uses it because remote registration is inherently last-writer-wins. The in-process RegisterAuth path instead uses the strict, unexported bindAuth, which panics on a conflicting binding (fail-fast for two AuthService implementers compiled into one binary).
func (*Gateway) OverrideAuthzBinding ¶
OverrideAuthzBinding is the authz equivalent of OverrideAuthBinding.
func (*Gateway) PluginByName ¶
PluginByName returns the registered plugin whose PluginName equals name, or nil if no plugin matches. Type-assert at the call site for typed access — sov stores plugins as `any` because the sub-interface they satisfy is what actually matters for dispatch.
if rid, ok := gw.PluginByName("request-id").(*requestid.Plugin); ok {
fmt.Println(rid.LastIssued())
}
PluginByName is the foundation of cross-plugin coordination — paired with PluginDependency (Requires + After) it lets one plugin opt in to behavior another plugin owns, without hard-coding import paths.
func (*Gateway) PluginInfos ¶
func (g *Gateway) PluginInfos() []PluginInfo
PluginInfos returns the PluginInfo list for this gateway's registered plugins. Order matches registration order so operators can read the wiring sequence directly. Exported so plugins (e.g. manifest) can read it without a /rpc/_introspect HTTP round trip.
func (*Gateway) PluginNames ¶
PluginNames returns the registered plugin names in registration order. Useful for diagnostics tools that don't want to walk IntrospectReport.Plugins.
func (*Gateway) PolicyAllowsRoleTakeover ¶
PolicyAllowsRoleTakeover exposes the mesh-conflict iterator for the role-takeover path. Registry plugin calls this when a different service name tries to claim an auth/authz role another service already holds. Defers to registered MeshConflictPolicy plugins.
func (*Gateway) PreemptFederation ¶
PreemptFederation exposes the mesh-conflict iterator for the federation-preemption path. Registry plugin calls this when a federated _register tries to claim a wire name already federated by a different address. Defers to registered MeshConflictPolicy plugins.
func (*Gateway) ProxyClient ¶
ProxyClient returns the http client used for outbound proxy hops. Registry plugin uses this for the introspect/health fan-out so the timeouts and TLS config stay consistent with business calls.
func (*Gateway) Register ¶
Register adds an in-process router to the gateway's engine. If the router happens to satisfy AuthService or AuthzService, the gateway auto-binds the corresponding role — same effect as RegisterAuth / RegisterAuthz but without the consumer having to know which routers hold which roles. Mesh pods self-declare via Roles on JoinMesh; monolith mode discovers the role by interface implementation. Both paths land in bindAuth / bindAuthz.
Boot-panics if two distinct routers implement the same role interface — see bindAuth / bindAuthz for the message.
func (*Gateway) RegisterAuth ¶
func (g *Gateway) RegisterAuth(svc AuthService)
RegisterAuth registers a typed AuthService as the gateway's auth verifier AND adds it to the engine. Compile-time check via the AuthService interface replaces the prior boot-time panic on magic-method-name + signature mismatches.
Equivalent to gw.Register(svc) — the plain Register path auto-detects AuthService implementers and binds them. Keep this form when you want the intent explicit in main(); use Register when the wiring is generic.
func (*Gateway) RegisterAuthz ¶
func (g *Gateway) RegisterAuthz(svc AuthzService)
RegisterAuthz is the authz equivalent of RegisterAuth.
func (*Gateway) RegisterRemote ¶
func (g *Gateway) RegisterRemote(name, address string, ttl time.Duration, opts ...RemoteOptions)
RegisterRemote hand-registers a remote service in the resolver chain. In-process equivalent of a remote pod POSTing /rpc/_register. ttl is how long the entry stays alive without a refresh.
Pass RemoteOptions to bind auth/authz roles AND/OR opt into /rpc/_introspect aggregation in the same call — registering an auth service remotely now matches the in-process RegisterAuth shape so the call site can switch deployment modes without touching the registration code:
// In-process:
gw.RegisterAuth(&auth.AuthRouter{...})
// Remote (mesh):
gw.RegisterRemote("Auth", "http://auth-pod:9001", time.Minute,
RemoteOptions{Auth: true, Verify: "verify"})
func (*Gateway) RegisterResolver ¶
func (g *Gateway) RegisterResolver() *RegisterResolver
RegisterResolver returns the gateway's register-based remote resolver so callers can pre-populate entries or Close on shutdown.
func (*Gateway) Resolver ¶
Resolver returns the resolver chain so the registry plugin can check whether a wire name is already federated by another address.
func (*Gateway) Run ¶
Run is the ergonomic main() helper: installs SIGINT/SIGTERM handling that cancels the supplied ctx, then calls ListenAndServe. On signal, the gateway's LifecycleHook.OnStop fires and the server gracefully drains in-flight requests. Returns nil on clean shutdown, error on boot/serve failure.
func main() {
log.Fatal(sov.NewMonolith(cfg).Run(context.Background(), ":8080"))
}
func (*Gateway) RunMesh ¶
func (g *Gateway) RunMesh(ctx context.Context, opts MeshOptions) error
RunMesh is the pod equivalent of Run — JoinMesh + SIGINT/SIGTERM graceful shutdown. Returns nil on signal-driven shutdown so callers can `log.Fatal(gw.RunMesh(ctx, opts))` without printing a spurious "<nil>" on Ctrl-C.
func main() {
log.Fatal(sov.NewPod(cfg).RunMesh(ctx, sov.MeshOptions{...}))
}
func (*Gateway) UseAll ¶
Use registers a plugin on the gateway. The argument is `any` because plugins are duck-typed: the gateway checks each sub-interface (HeaderInjector, AuthTranslator, …) via Go interface assertion and stashes pointers in the appropriate slot list.
If the plugin ALSO has RPC-shaped methods (matching the same signature contract gw.Register requires), they are registered on the engine — one Use call yields both extension hooks and wire-callable methods.
Use is safe to call before or after ListenAndServe. Plugins added post-start participate in subsequent requests; existing in-flight requests are not retroactively wrapped.
Returns an error when the plugin satisfies NO sub-interface AND has no RPC methods — that's almost certainly a bug (the caller probably forgot to make the methods exported, or passed the wrong type). UseAll calls Use on each plugin in order. First error stops the chain so config-applying plugins that need to succeed before later plugins fail fast. Preset packages return []any slices that pair with this helper:
gw := sov.New()
if err := gw.UseAll(preset.Monolith()...); err != nil {
log.Fatal(err)
}
func (*Gateway) UseMiddleware ¶
func (g *Gateway) UseMiddleware(mw Middleware)
UseMiddleware appends a raw Middleware closure to the dispatch chain. The plugin-shaped `Use(any)` is preferred for new code; this is the back-compat path for the closure form. May be called before or after ListenAndServe.
type Header ¶
Header is a transport-neutral header map. Multi-value headers are represented as comma-joined strings — the gateway never needs more.
func (Header) Clone ¶
Clone returns a shallow copy of h. Use it when a request's header map is handed to concurrent sub-dispatches (e.g. batch fan-out): plugins like requestid MUTATE req.Header, so parallel handlers must each get their own map or they race on the shared one (fatal concurrent map access). Returns nil for a nil receiver.
func (Header) Get ¶
Get returns the value for key, case-insensitively. It looks up the canonical form (http.CanonicalHeaderKey) first — which is how inbound headers and Set store keys — then falls back to an exact match so a literal-constructed Header still resolves. Callers no longer need to try both "Authorization" and "authorization".
type HeaderClaimer ¶
type HeaderClaimer interface {
ClaimedHeaders() []string
}
HeaderClaimer lets a plugin declare which inbound HTTP header names it OWNS — those headers bypass the framework's X-Sov-* identity- strip and reach req.Header intact. Without this, a plugin that reads e.g. X-Sov-Register-Sig (mesh-secret) would never see it because the edge strip nukes every X-Sov-*.
Names are http.CanonicalHeaderKey-compared. Identity headers (X-Sov-Subject/Issuer/Scopes/Expires/Seal) cannot be claimed — the strip honors anti-smuggling regardless. Other X-Sov-* and arbitrary-named headers are passable.
func (p *Plugin) ClaimedHeaders() []string {
return []string{"X-Sov-Register-Sig", "X-Sov-Register-Ts"}
}
type HeaderInjector ¶
type HeaderInjector interface {
InjectHeaders(ctx context.Context, req *Request, hreq *http.Request) error
}
HeaderInjector fires before every OUTBOUND proxy hop (dispatchRemote, dispatchRemoteBatch, federated introspect/health probes). Add headers to hreq; do NOT mutate ctx or body.
type HeaderParser ¶
HeaderParser fires on every INBOUND request, after the trust guard has decided whether to keep X-Sov-* headers. Read non-standard headers off req.Header and stash interpreted values on req.User / req.Header via mutation. Returning a non-nil *rpc.Error short-circuits dispatch with that error — use sparingly; most parsers should be lossy.
type HealthAggregator ¶
type HealthAggregator interface {
AggregateHealth(ctx context.Context, report *HealthReport) error
}
HealthAggregator lets a plugin merge remote-pod health probes into the framework's local /rpc/_health report. Framework calls every registered aggregator AFTER building the local report (gateway + in-process services) and BEFORE marshalling. Plugin mutates *HealthReport directly — services map may be extended; the top-level Status field should be downgraded ONLY if a probed remote returned degraded/unhealthy.
type HealthGateway ¶
type HealthGateway struct {
Status string `json:"status"`
}
HealthGateway is the gateway's own health stanza.
type HealthReport ¶
type HealthReport struct {
Status string `json:"status"`
CheckedAt time.Time `json:"checked_at"`
Gateway HealthGateway `json:"gateway"`
Services map[string]HealthService `json:"services"`
}
HealthReport is the aggregated /_health JSON body.
type HealthService ¶
type HealthService struct {
Status string `json:"status"`
Local bool `json:"local"`
Source string `json:"source,omitempty"` // RemoteAddr when not local
Detail string `json:"detail,omitempty"` // populated on failure
Children map[string]HealthService `json:"children,omitempty"` // populated for federated tier rollup
}
HealthService is one service's reported health.
Status taxonomy:
- healthy — service responded green.
- degraded — federated team gateway responded 207 (some-but-not-all of its children are healthy); see Children for the per-tier rollup.
- unhealthy — gateway/pod reachable but reported failure (5xx).
- unknown — registered but no liveness signal (4xx, no _health).
- missing — network unreachable / connection refused.
type HookFailure ¶
type HookFailure struct {
HookName string
PluginName string
// Err is the returned error OR a wrapped panic. Use HaltErr /
// RespondErr sentinel checks (errors.As) to read the plugin's
// intent.
Err error
// Panic is the recovered value when this failure came from a
// panic; nil when the hook returned an error normally.
Panic any
// Stack is the goroutine stack trace at the panic site, only
// populated when Panic != nil.
Stack []byte
}
HookFailure carries everything a RecoveryHandler needs to react to a hook failure — panic recovered or error returned. The reaction is derived from the returned error: HaltErr → halt boot, RespondErr → override the response, anything else → log + continue. Panics are treated as continue by default; the RecoveryHandler can re-classify.
type IntrospectContributor ¶
type IntrospectContributor interface {
ContributeIntrospect(ctx context.Context, report *IntrospectReport, trace string, visited []string) error
}
IntrospectContributor lets a plugin contribute to /rpc/_introspect. Fires after the framework builds the local report. Plugins may:
- decorate the report with their own block (cache stats, ring state)
- fan out to remote pods and merge their descriptors
Use ctx and the cascade headers (trace, visited) for loop guarding when probing remotes. visited carries the normalized address list of gateways already visited on this introspect cascade; plugins MUST append themselves before fanning out and MUST skip any address already in visited.
Replaces the prior IntrospectAugmenter (no-arg, local decoration) and IntrospectAggregator (ctx + cascade headers, remote fan-out) interfaces — both folded under one name. Plugins that don't need ctx/trace/visited just ignore those args.
type IntrospectReport ¶
type IntrospectReport struct {
Services map[string][]rpc.RouterDescriptor `json:"services"`
Types map[string]TypeDescriptor `json:"types"`
CrossRefs map[string]TypeVariants `json:"cross_refs"`
// Plugins lists every plugin registered on this gateway via
// gw.Use(). Populated for the ROOT introspect call only;
// federated probes skip it so per-tier visibility stays clean.
Plugins []PluginInfo `json:"plugins,omitempty"`
// BoundaryWarnings flags data-ownership smells found while inferring
// type ownership — currently, any type returned by more than one
// service (ambiguous producer). Empty when boundaries are clean.
BoundaryWarnings []string `json:"boundary_warnings,omitempty"`
}
IntrospectReport is the aggregated /_introspect JSON body.
Services keys each declared router → its descriptors (one or more when the same name appears in multiple pods — drift!).
Types flattens every Go type appearing in any service's request or response into one map. UsedBy on each TypeDescriptor records every (service, method, role) the type appears in.
CrossRefs is the drift radar: any type name that surfaced in multiple services with differing ShapeHashes lands here, grouped by hash. Empty when every consumer agrees on a single shape.
type LifecycleHook ¶
type LifecycleHook interface {
OnStart(ctx context.Context) error
OnStop(ctx context.Context) error
}
LifecycleHook fires on gw.ListenAndServe start/stop. Use for background goroutines, connection pools, shutdown drains. OnStart runs after BootValidator passes; OnStop runs on context cancel.
type LocalResolver ¶
type LocalResolver struct {
// contains filtered or unexported fields
}
LocalResolver wraps an in-process Engine. Hit when the requested router is registered locally. Every locally-registered router is always introspectable — the Describe path is in-process and cheap.
func NewLocalResolverFunc ¶
func NewLocalResolverFunc(hasRouter func(name string) bool, routers func() []string) *LocalResolver
NewLocalResolverFunc lets the gateway pass plain lookup closures so we don't have to import rpc inside this file (resolver.go stays dependency-free; the engine-aware glue lives in gateway.go).
func (*LocalResolver) Introspectables ¶
func (l *LocalResolver) Introspectables() []string
Introspectables implements Resolver. Local routers are always opt-in.
func (*LocalResolver) Services ¶
func (l *LocalResolver) Services() []string
Services implements Resolver.
type LogLevel ¶
type LogLevel string
LogLevel is the level passed to Logger.Log. Standard four — Debug, Info, Warn, Error — covering 95% of consumer needs without forcing a specific log lib's enum. Plugins implementing Logger map these to whatever level convention they use.
type Logger ¶
type Logger interface {
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
}
Logger is the structured logging contract. Plugins that satisfy this become the gateway-wide logger sink — framework + every builtin route Log() calls through it.
Signature is slog-compatible (Debug/Info/Warn/Error all take `(msg, args...)` — args are key-value pairs). A *slog.Logger satisfies Logger directly. Other libs (zap, logrus, custom) need a tiny adapter (4 methods).
First registered Logger wins. When none, gw.Log() returns a slog adapter backed by slog.Default() so log lines still land somewhere.
type MeshConflictPolicy ¶
type MeshConflictPolicy interface {
AllowMeshConflict(current, candidate string, c Conflict) bool
ConsumeConflict(name string, c Conflict)
}
MeshConflictPolicy decides whether an inbound /rpc/_register may take over an existing claim. Two paths share the interface:
- Role takeover: name already bound to an auth/authz role; the framework calls AllowMeshConflict with (current, candidate, Conflict{Role: <RoleAuth|RoleAuthz>}) and uses the result verbatim.
- Federation preemption: name already federated by a different address; the framework calls AllowMeshConflict with (current, candidate, Conflict{FederatedAddrs: [old, new]}) — "current" is the wire name, "candidate" is the same wire name, and the address pair is in FederatedAddrs.
Framework iterates registered policies; first true wins. Default (no policy registered, or all return false) is deny — handleRegister returns 409 ROLE_CONFLICT / SERVICE_CONFLICT.
ConsumeConflict fires AFTER the takeover succeeds so the plugin can drop the rule it owns (one-shot preemption map cleanup, audit log). Plugins that have nothing to clean up should leave it a no-op.
Replaces the prior RoleConflictPolicy + FederationPreemptionPolicy pair — both folded under one name with Conflict carrying the case discriminator.
type MeshOptions ¶
type MeshOptions struct {
// UpstreamURL is the base URL of the central registry, e.g.
// "http://gateway:8080". Required.
UpstreamURL string
// Address is the local listen address (":9001"). Required.
Address string
// Advertise is the URL the upstream gateway will reach this pod on
// (e.g. "http://auth-pod:9001"). Required.
Advertise string
// ServiceName, if set, overrides the pod's advertised wire name.
// Defaults to the wire name of the single router registered on the
// gateway — i.e. for a pod hosting just AuthRouter, defaults to
// "Auth". When the pod hosts multiple routers, ServiceName MUST be
// set explicitly.
ServiceName string
// Heartbeat is the registration refresh interval. Default 5s.
// Upstream TTL is heartbeat × 3.
Heartbeat time.Duration
// Roles is the bit set of gateway-level roles to self-declare.
// Use RoleAuth | RoleAuthz combos.
Roles RoleFlag
// HTTPClient overrides the http.Client used for /_register POSTs.
HTTPClient *http.Client
// Introspectable, when true, opts this pod into the central
// gateway's /rpc/_introspect aggregation. Default false (zero
// value) keeps surface minimal; chirp mesh sets it true so the
// type catalog spans every pod.
Introspectable bool
// MeshSecret, when non-nil, signs every _register POST with
// HMAC-SHA256 + a current timestamp. Required when the upstream
// gateway was constructed WithMeshSecret(...); the bytes must
// match. Without this, _register on a hardened gateway returns
// 401 and the pod never joins the mesh.
MeshSecret []byte
// RegisterToken, when non-empty, is stamped as the X-Sov-Register-Token
// header on every _register POST — the simple shared-bearer join gate
// (see builtin/registertoken). Required when the upstream gateway runs
// the registertoken plugin; the bytes must match. Independent of
// MeshSecret (you may set either, both, or neither).
RegisterToken []byte
// Federate, when non-empty, advertises this gateway as a tiered
// router that fronts every name in the slice. Upstream registers
// all of them at Advertise via one POST + one heartbeat. The pod
// path (single ServiceName) is bypassed when Federate is set.
//
// Use this for an EXPLICIT, fixed federation list. For a team gateway
// whose service set changes at runtime (sub-pods come and go), prefer
// FederateAll so the list can't drift from reality.
Federate []string
// FederateAll, when true, advertises this gateway's LIVE service set
// (everything its resolver currently knows — local routers + sub-pods
// registered to it), recomputed on every heartbeat. A service that
// appears is federated upstream on the next beat; one that disappears
// drops out of the heartbeat and TTL-expires upstream (~3 beats). This
// keeps the upstream's introspect + health in sync with the team
// gateway with zero hand-maintained lists. Takes precedence over
// Federate when both are set.
FederateAll bool
}
MeshOptions configures gw.JoinMesh.
type Middleware ¶
Middleware wraps the gateway's request handler. Return a non-nil Response to short-circuit dispatch; return nil to fall through to the next middleware (or the gateway's own handler).
type Middlewarer ¶
Middlewarer is the new home for plugins that wrap the dispatch chain. Equivalent to today's Middleware closure but reachable through gw.Use so it shows up in the plugin list. Existing Middleware closures continue to work via the legacy WithMiddleware option.
type NetHTTPOptions ¶
type NetHTTPOptions struct {
// MaxBodyBytes caps the request body size. 0 → default 4 MiB.
MaxBodyBytes int64
// HTTPServer, if set, is used verbatim and Addr/timeouts are
// honored. Otherwise the constructor builds a server with sensible
// defaults using ListenAndServe's addr.
HTTPServer *http.Server
// TrustUpstreamClaims controls inbound X-Sov-* handling.
//
// false (default): strip every inbound X-Sov-* header. The right
// answer for any gateway facing the public internet — clients
// could otherwise smuggle X-Sov-Subject: admin.
//
// true: pass X-Sov-* through. The right answer for downstream
// service pods that sit behind a trusted upstream gateway which
// itself injects the verified claims (and ideally HMAC-seals
// them — pair with hmacseal/proto.Verify middleware to cryptographically
// verify rather than trust the network alone).
TrustUpstreamClaims bool
}
NetHTTPOptions configures NetHTTPServer.
type NetHTTPServer ¶
type NetHTTPServer struct {
// contains filtered or unexported fields
}
NetHTTPServer is the default Server implementation. Production-grade defaults: 10s header timeout, 60s write timeout, configurable max body (default 4 MiB). Consumers who need different limits or TLS pass a pre-configured *http.Server via NetHTTPOptions.HTTPServer.
func NewNetHTTPServer ¶
func NewNetHTTPServer(opts NetHTTPOptions) *NetHTTPServer
NewNetHTTPServer returns a Server backed by net/http.
func (*NetHTTPServer) Handle ¶
func (s *NetHTTPServer) Handle(h RequestHandler)
Handle implements Server.
func (*NetHTTPServer) ListenAndServe ¶
func (s *NetHTTPServer) ListenAndServe(ctx context.Context, addr string) error
ListenAndServe implements Server.
func (*NetHTTPServer) SetHeaderClaim ¶
func (s *NetHTTPServer) SetHeaderClaim(fn func(canonicalName string) bool)
SetHeaderClaim wires the predicate consulted before stripping an identity-shaped header. Returning true preserves the header so a plugin that claimed it can read it from req.Header.
func (*NetHTTPServer) SetTrustGuard ¶
func (s *NetHTTPServer) SetTrustGuard(fn func(req *http.Request, body []byte) bool)
SetTrustGuard wires a predicate that decides whether to KEEP an inbound X-Sov-* bundle (only meaningful when TrustUpstreamClaims is true). False means strip the claims; the request still flows through — auth gating is the authz service's job, not the trust layer.
type Option ¶
type Option func(*Options)
Option mutates Options.
func WithAdvertiseURL ¶
WithAdvertiseURL sets the public address this gateway stamps as X-Sov-Upstream on every outbound proxy hop. Replaces the builtin/advertise plugin (folded into core).
func WithClaimsCache ¶
func WithClaimsCache(c ClaimsCache) Option
WithClaimsCache overrides the verified-claims cache (default: in-memory, per-replica). Pass a shared (e.g. Redis) implementation so gateway replicas reuse each other's AuthService.verify results. See ClaimsCache.
func WithMiddleware ¶
func WithMiddleware(mw ...Middleware) Option
WithMiddleware appends middleware that wrap the dispatch. Consumer middleware runs AFTER the gateway's own auth + authz middleware (when configured) so it can rely on Claims being resolved.
func WithProxyClient ¶
WithProxyClient overrides the http.Client used to proxy remote calls.
func WithRegisterResolver ¶
func WithRegisterResolver(r *RegisterResolver) Option
WithRegisterResolver sets the resolver backing /_register. Defaults to NewRegisterResolver(5s) — pass one explicitly if you need Close() or custom eviction.
func WithResolver ¶
WithResolver sets the resolver chain. Default: a chain of LocalResolver + RegisterResolver.
func WithServer ¶
WithServer overrides the HTTP server (default: NewNetHTTPServer).
func WithTrustUpstreamClaims ¶
WithTrustUpstreamClaims is a top-level lift of NetHTTPOptions. TrustUpstreamClaims so consumers don't have to construct a server by hand. Pods set this true (the upstream gateway is trusted to inject X-Sov-*); edge gateways leave it false (strip inbound X-Sov-*).
type Options ¶
type Options struct {
Server Server // default NewNetHTTPServer
Resolver Resolver // default chain: Local + RegisterResolver
RegisterResolver *RegisterResolver // default NewRegisterResolver(5s)
ProxyClient *http.Client // default http.Client{Timeout: 30s}
Middleware []Middleware // appended after the gateway's own auth/authz middleware
TrustUpstreamClaims bool // when true, the default NetHTTPServer trusts inbound X-Sov-*
// AdvertiseURL, when set, makes the framework stamp X-Sov-Upstream
// on every outbound proxy hop so downstream pods running the
// upstreams plugin can verify the claim bundle came from a trusted
// peer. Empty disables the stamp. URL is normalized at New().
AdvertiseURL string
// ClaimsCache overrides the in-memory verified-claims cache. Supply a
// Redis/memcached-backed impl to share auth-verify results across a
// fleet of gateway replicas. Default: per-replica in-memory.
ClaimsCache ClaimsCache
}
Options configures a Gateway. All fields optional — defaults are sensible for the standalone-single-binary case.
type Plugin ¶
type Plugin interface {
PluginName() string
}
Plugin is the optional diagnostic marker. Types that implement at least one sub-interface (HeaderInjector, AuthTranslator, etc.) are treated as plugins even if they don't satisfy Plugin — but implementing it adds a human-readable name to the introspect "plugins" array and the explorer's Plugins tab.
type PluginDependency ¶
PluginDependency declares cross-plugin ordering constraints. gw.Use errors out when any Requires() name isn't yet registered — fail-fast on misconfiguration. After() is a soft, diagnostic hint that the plugin documents its preferred ordering; the framework records it for introspect surfacing + tooling, but does NOT reorder calls automatically (operator-controlled Use order is load-bearing for predictability).
func (p *CorsPlugin) Requires() []string { return []string{"request-id"} }
func (p *CorsPlugin) After() []string { return []string{"audit"} }
Wire cors after request-id or gw.Use returns:
gw.Use: cors requires plugin "request-id" which is not registered
type PluginDoc ¶
type PluginDoc interface {
Doc() string
}
PluginDoc is an optional companion to Plugin — implement when you want a one-paragraph description to surface in /rpc/_introspect's plugin entry (Extra["doc"]) and in `sov inspect` output. Keep it short; multi-line text is fine but the explorer renders inline.
func (p *Plugin) Doc() string {
return "Stamps X-Sov-Request-Id on every request and propagates it across hops."
}
type PluginInfo ¶
type PluginInfo struct {
Name string `json:"name"`
Hooks []string `json:"hooks"` // ["HeaderInjector", "DispatchHook"]
HasRouter bool `json:"has_router"` // true when the plugin also registered RPC methods
Requires []string `json:"requires,omitempty"` // hard deps from PluginDependency
After []string `json:"after,omitempty"` // soft ordering hint from PluginDependency
Capabilities []string `json:"capabilities,omitempty"` // Type names published via CapabilityProvider
Extra map[string]any `json:"extra,omitempty"` // per-plugin metadata (version, source URL, ...)
}
PluginInfo is the introspect-time descriptor of a registered plugin. Surfaces in IntrospectReport.Plugins; the explorer's Plugins tab renders it; drift radar uses it to flag same-name-different-version across team gateways.
type RecoveryHandler ¶
type RecoveryHandler interface {
HandleHookFailure(failure HookFailure) (override *Response)
}
RecoveryHandler observes every hook failure (returned error OR recovered panic). Plugins implement it to log, sample, alert, or shape the 500-response. Framework installs a default stderr-logging handler when no plugin registers one.
The handler may return a non-nil response override; the framework uses it instead of the default 500 envelope. Return nil to accept the default. A plugin-supplied RespondErr (see plugin_errors.go) always wins over a handler override.
type RegisterEntry ¶
RegisterEntry is one service registration: where the service lives, when the registration expires, and whether it opts into introspect aggregation. The unit a RegisterStore persists.
type RegisterRequest ¶
type RegisterRequest struct {
Name string `json:"name"`
Address string `json:"address"`
HeartbeatInterval int `json:"heartbeat_interval_seconds"`
Auth bool `json:"auth,omitempty"`
Verify string `json:"verify,omitempty"`
Authz bool `json:"authz,omitempty"`
Check string `json:"check,omitempty"`
Introspect bool `json:"introspect,omitempty"`
// Federate, when true, advertises this gateway as a tiered router
// that fronts every name in Services. Master registers each name
// at Address. Name in this mode is a label, not a wire name.
Federate bool `json:"federate,omitempty"`
Services []string `json:"services,omitempty"`
}
RegisterRequest is the JSON body of POST /rpc/_register.
Auth+Verify, when both set, tell the gateway "this service is the auth verifier — route bearer tokens here." Authz+Check tell the gateway "this service is the policy-as-service hook." A service may declare zero, one, or both roles; non-role services just expose their own business methods.
Introspect, when true, opts the pod into the gateway's /_introspect aggregation: the gateway will probe this pod's /rpc/_introspect on catalog rebuild and merge its descriptors into the org-wide type catalog. Defaults to false on the wire so that services opt in explicitly; the chirp mesh demo sets it true.
type RegisterResolver ¶
type RegisterResolver struct {
// contains filtered or unexported fields
}
RegisterResolver holds a TTL-backed map of service → remote address, populated by services that POST /rpc/_register on startup and refresh via heartbeat. Reads are served from a lock-free local cache; the backing store (RegisterStore) is pluggable for multi-replica meshes.
func NewRegisterResolver ¶
func NewRegisterResolver(evictInterval time.Duration, opts ...RegisterResolverOption) *RegisterResolver
NewRegisterResolver returns a resolver with a background reaper that runs at evictInterval (defaults to 5s). Pass options to swap the store or enable cross-replica refresh.
func (*RegisterResolver) AddressGroup ¶
func (r *RegisterResolver) AddressGroup() map[string][]string
AddressGroup returns unique-address → service-names-served-at-that- address. Live entries only (TTL respected). Used by the gateway's introspect + health cascades to probe each federated team gateway exactly once rather than once per service it fronts.
func (*RegisterResolver) Close ¶
func (r *RegisterResolver) Close()
Close stops the background reaper. Safe to call multiple times.
func (*RegisterResolver) Delete ¶
func (r *RegisterResolver) Delete(service string)
Delete removes a service entry. No-op if absent.
func (*RegisterResolver) Introspectables ¶
func (r *RegisterResolver) Introspectables() []string
Introspectables implements Resolver — only entries that registered with Introspectable=true are returned.
func (*RegisterResolver) Put ¶
func (r *RegisterResolver) Put(service, address string, ttl time.Duration)
Put inserts or refreshes a service entry. Defaults to Introspectable=true for the common case (every pod opts in unless it declared otherwise on _register). Use PutEntry to set flags explicitly.
func (*RegisterResolver) PutEntry ¶
func (r *RegisterResolver) PutEntry(service, address string, ttl time.Duration, opts EntryOptions)
PutEntry inserts or refreshes a service entry with explicit options.
func (*RegisterResolver) Services ¶
func (r *RegisterResolver) Services() []string
Services implements Resolver.
type RegisterResolverOption ¶
type RegisterResolverOption func(*RegisterResolver)
RegisterResolverOption configures a RegisterResolver.
func WithRegisterRefresh ¶
func WithRegisterRefresh(d time.Duration) RegisterResolverOption
WithRegisterRefresh makes the resolver pull store.Snapshot() every d to pick up registrations made on OTHER replicas against a shared store. Only one replica receives a given pod's heartbeat, so the rest must poll to converge. 0 (default) = local-only (single replica / in-memory).
func WithRegisterStore ¶
func WithRegisterStore(s RegisterStore) RegisterResolverOption
WithRegisterStore overrides the backing store (default in-memory). Pass a shared (e.g. Redis) store so gateway replicas share one mesh view.
type RegisterResponse ¶
type RegisterResponse struct {
OK bool `json:"ok"`
TTL int `json:"ttl_seconds"`
ForceIntrospect bool `json:"force_introspect,omitempty"`
}
RegisterResponse is the success body. ForceIntrospect, when true, signals the pod that the gateway has the explorer plugin (or other catalog consumer) installed and the pod's entry was force-flipped to Introspectable=true server-side regardless of what the pod requested. The pod SHOULD update its in-memory mesh options so future heartbeats / re-registers stay consistent.
type RegisterStore ¶
type RegisterStore interface {
Put(service string, e RegisterEntry)
Delete(service string)
Snapshot() map[string]RegisterEntry
ReapExpired(now time.Time) (changed bool)
}
RegisterStore is the pluggable backing store for the registry's service→address map. Default is in-memory, per replica. Supply a shared impl (e.g. Redis) via WithRegisterStore so a FLEET of gateway replicas shares one mesh view — otherwise each replica only knows the pods whose heartbeat happened to land on it (partial-view routing/health drift).
Implementations must be safe for concurrent use. Put/Delete are writes at heartbeat rate (from /rpc/_register). Snapshot is read by the resolver to refill its local read cache — NOT per request — so a remote store is hit at most once per refresh tick, never on the dispatch hot path. ReapExpired drops entries past ExpiresAt and reports whether anything changed; a store with native TTL (Redis) may no-op + return false.
type RemoteOptions ¶
type RemoteOptions struct {
// Auth, when true, binds (name, Verify) as the gateway's auth
// verifier. Verify defaults to "verify" if empty.
Auth bool
Verify string
// Authz, when true, binds (name, Check) as the gateway's authz
// policy hook. Check defaults to "check" if empty.
Authz bool
Check string
// Introspect, when true, opts this entry into the gateway's
// /rpc/_introspect aggregator fan-out. Default false (no probe).
Introspect bool
}
RemoteOptions configures RegisterRemote — role flags + introspect opt-in, parallel to what an inbound /rpc/_register POST carries. Empty zero value preserves the legacy two-arg behavior.
type Request ¶
type Request struct {
// Method is the HTTP method, e.g. "POST" or "GET". Most business
// calls are POST; framework endpoints like /rpc/_health are GET.
Method string
// Path is the full URL path, e.g. "/rpc/WidgetService/create".
Path string
// Header is the inbound HTTP headers, with sov-namespaced inbound
// headers already stripped by Server.
Header Header
// Body is the request body, fully buffered. Empty body is allowed.
Body []byte
// RemoteIP is the caller's source IP. Server picks it from
// X-Forwarded-For or the transport-level remote address.
RemoteIP string
// User is the authenticated subject, set by Server-side middleware
// before dispatch. nil for anonymous calls. Gateway copies this onto
// the rpc.Context handed to handlers.
User any
}
Request is the transport-neutral inbound RPC. Server adapters populate this from whatever HTTP types they use.
type RequestHandler ¶
RequestHandler is what the gateway hands to Server.Handle. Server constructs a Request from the inbound HTTP request and passes it; the returned Response is written back to the client.
type Resolver ¶
type Resolver interface {
Resolve(ctx context.Context, service string) (*Endpoint, bool)
// Services returns a snapshot of every service this resolver knows
// about, used by the gateway's /_health aggregator.
Services() []string
// Introspectables returns the subset of Services() that opt in to
// /_introspect probing. Returning a nil/empty slice means "no
// introspection allowed" — the gateway will skip the HTTP probe and
// the service stays out of the aggregated type catalog.
Introspectables() []string
}
Resolver locates a service by wire name and returns either a local dispatch (handlers in-process) or a remote endpoint (HTTP-proxy). A gateway holds a chain of resolvers and tries them in order — the first hit wins. Implementations must be safe for concurrent use.
type Response ¶
type Response struct {
// Status is the HTTP status code.
Status int
// Header carries response headers (Content-Type etc.). Server merges
// these onto its response.
Header Header
// Body is the response payload. Ignored when Stream is set.
Body []byte
// Stream, when non-nil, is written to the client incrementally
// (constant memory) INSTEAD of Body — for large or open-ended payloads
// a RouteHandler plugin produces without buffering: a zip export, a
// file download, a server-sent-event feed. The adapter io.Copy's it to
// the wire (chunked transfer; no Content-Length) and, if it also
// implements io.Closer, closes it when done.
//
// Streaming is a RouteHandler-plugin concern. RPC methods return
// (T, error) and are enveloped as buffered JSON — they cannot stream.
// Set Content-Type in Header (e.g. "application/zip"); the adapter does
// not guess it for a stream. See PipeStream for the writer-callback form.
Stream io.Reader
// Mode is the dispatch-mode hint set by the gateway's internal
// dispatch path (local/remote/federated/framework/plugin). Surfaces
// in DispatchEvent.Mode for observability. Plugins that build a
// Response from scratch should leave this empty; the framework
// fills it in at known dispatch sites.
Mode string
}
Response is what the RequestHandler returns. Server writes it back.
func ErrorResponse ¶
ErrorResponse builds a *Response from an *rpc.Error: the error's HTTP status plus its JSON-marshaled envelope body. Plugins returning an error from a route handler should use this so the wire shape matches the framework's own error responses.
func ErrorResponseFromAny ¶
func ResponseFrom ¶
ResponseFrom returns the embedded *Response when err was wrapped with RespondErr, or nil otherwise.
type ResponseInterceptor ¶
ResponseInterceptor fires AFTER dispatch with the resolved *Response. Plugin may mutate Status, Header, Body — or replace them entirely. Runs in registration order so each interceptor sees the output of the previous one.
Use cases: CORS header injection on every response, response compression, body redaction, status remapping, structured logging of the final envelope.
Fires for ALL responses: framework endpoints, plugin routes, business dispatch. The runtime tags the Response.Mode field before this hook fires so interceptors can branch by dispatch source.
type RoleFlag ¶
type RoleFlag uint32
RoleFlag is a bit-flag a pod uses to declare gateway-level roles to the upstream registry on /_register. The upstream binds those roles (auth verifier, authz checker) to the pod's wire name.
type RouteHandler ¶
type RouteHandler interface {
RoutePatterns() []string
ServeRoute(ctx context.Context, req *Request) *Response
}
RouteHandler lets a plugin own a path on the gateway, instead of wrapping the dispatch chain in a Middlewarer just to peek for a prefix. Patterns follow net/http ServeMux convention: a trailing "/" matches the subtree (prefix match); no trailing "/" matches the exact path only. Routes are checked AFTER the framework's own endpoints (_health/_introspect/_register/_batch) so plugins extend the framework surface but cannot shadow built-ins. Match order is registration order — register the most specific patterns first.
type SealVerifier ¶
SealVerifier decides whether inbound X-Sov-* claim headers carry a valid HMAC seal under any registered key. Trust guard iterates registered verifiers; first true wins. If NO SealVerifier is registered AND TrustUpstreamClaims is true, the seal check is skipped — pair with an UpstreamTrustPolicy in that case.
type Server ¶
type Server interface {
// Handle registers the single catch-all handler for /rpc/* requests.
// Server must invoke it for every POST under /rpc/.
Handle(h RequestHandler)
// ListenAndServe binds and serves until ctx is cancelled.
ListenAndServe(ctx context.Context, addr string) error
}
Server abstracts the HTTP runtime so the gateway is not coupled to any one router/framework. The default implementation (NewNetHTTPServer) is built into this package and uses net/http; consumers who want fiber, fasthttp, echo, or anything else implement Server themselves and pass it via WithServer.
The contract: Server must route every incoming POST request whose path starts with /rpc/ to the gateway. The gateway tells Server how to do that via Handle. Server then calls the registered RequestHandler with the parsed Request and writes whatever ResponseWriter the gateway returns back to the client. Server is responsible for the network (listening, TLS, keep-alives, etc.); the gateway is responsible for the protocol.
type TypeDescriptor ¶
type TypeDescriptor struct {
Name string `json:"name"`
Fields []rpc.ParamField `json:"fields"`
UsedBy []TypeUse `json:"used_by"`
ShapeHash string `json:"shape_hash"`
// Owner is the service that PRODUCES this type — the one that returns
// it (a "response"-role usage). Empty for request/nested-only types
// (no producer) or when ownership is ambiguous, i.e. >1 producer (see
// Owners + IntrospectReport.BoundaryWarnings). Data-boundary metadata,
// inferred by convention; see inferOwnership.
Owner string `json:"owner,omitempty"`
// Owners is the full set of producing services (every service that
// returns this type). One entry is the normal case (Owner mirrors it);
// two or more is a data-boundary smell — overlapping producers — which
// also raises a BoundaryWarning. Sorted, deduped. Surfaced so the
// ambiguous case shows WHO the producers are, not just that it's
// ambiguous.
Owners []string `json:"owners,omitempty"`
// Consumers are services that use this type without owning it (as a
// request param or a nested reference). Sorted, deduped.
Consumers []string `json:"consumers,omitempty"`
}
TypeDescriptor is the catalog entry for one Go type. Fields is the full ParamField list (including the new Position/Omitempty/Deprecated markers). UsedBy lists every appearance in the org's RPC surface.
type TypeUse ¶
type TypeUse struct {
Service string `json:"service"`
Method string `json:"method"`
Role string `json:"role"` // "request" | "response"
}
TypeUse records one appearance of a TypeDescriptor on a method.
type TypeVariant ¶
type TypeVariant struct {
ShapeHash string `json:"shape_hash"`
Fields []rpc.ParamField `json:"fields"`
Services []string `json:"services"`
}
TypeVariant is one shape of a same-named type.
type TypeVariants ¶
type TypeVariants struct {
Name string `json:"name"`
Variants []TypeVariant `json:"variants"`
}
TypeVariants groups same-named types that diverged in shape across services — drift detection. variants[0] is the canonical (sorted by ShapeHash), the rest are the divergences.
type UpstreamTrustPolicy ¶
UpstreamTrustPolicy decides whether inbound X-Sov-* claim headers from the given request should be trusted. Trust guard iterates registered policies; ALL must return true (logical AND) for the bundle to pass. If NO UpstreamTrustPolicy is registered, the allowlist check is skipped (a request passes the upstream gate trivially) — pair with a SealVerifier to keep the trust guard honest.
type VerifyParams ¶
type VerifyParams struct {
Token string `json:"token"`
}
VerifyParams is the request payload the gateway sends to AuthService.Verify.
Source Files
¶
- auth.go
- auth_binding.go
- auth_seal.go
- capability.go
- client.go
- dispatch.go
- doc.go
- endpoints.go
- gateway.go
- health.go
- introspect.go
- mesh.go
- mesh_peer.go
- mesh_resolver.go
- mesh_url.go
- nethttp.go
- plugin.go
- plugin_entry.go
- plugin_errors.go
- plugin_hooks.go
- plugin_interfaces.go
- plugin_lookup.go
- plugin_order.go
- plugin_use.go
- recovery.go
- server.go
- streaming.go
- typecatalog.go
- wire_types.go
Directories
¶
| Path | Synopsis |
|---|---|
|
builtin
|
|
|
audit
Package audit ships a sov plugin that records every dispatch event into a sliding-window in-memory ring AND emits structured JSON to a writer (typically os.Stdout or a log file).
|
Package audit ships a sov plugin that records every dispatch event into a sliding-window in-memory ring AND emits structured JSON to a writer (typically os.Stdout or a log file). |
|
auth
Package auth ships a first-party sov plugin that translates verified Claims into legacy-shape headers ("X-Forwarded-User" / "X-Forwarded-Scopes" / "X-Tenant-Id") on every outbound proxy hop.
|
Package auth ships a first-party sov plugin that translates verified Claims into legacy-shape headers ("X-Forwarded-User" / "X-Forwarded-Scopes" / "X-Tenant-Id") on every outbound proxy hop. |
|
batch
Package batch ships the cascading-batch endpoint at /rpc/_batch.
|
Package batch ships the cascading-batch endpoint at /rpc/_batch. |
|
cors
Package cors adds CORS headers to every response and handles the browser's OPTIONS preflight short-circuit.
|
Package cors adds CORS headers to every response and handles the browser's OPTIONS preflight short-circuit. |
|
explorer
Package explorer mounts the embedded HTML browser at /rpc/_explorer/.
|
Package explorer mounts the embedded HTML browser at /rpc/_explorer/. |
|
hmacseal
Package hmacseal seals injected X-Sov-* claim headers with HMAC-SHA256 so downstream services can detect forged claim bundles.
|
Package hmacseal seals injected X-Sov-* claim headers with HMAC-SHA256 so downstream services can detect forged claim bundles. |
|
hmacseal/proto
Package proto is the wire format for X-Sov-Seal.
|
Package proto is the wire format for X-Sov-Seal. |
|
introspect
Package introspect is the opt-in plugin that exposes the public /rpc/_introspect endpoint — the aggregated API catalog (every service, method, and type the gateway can route).
|
Package introspect is the opt-in plugin that exposes the public /rpc/_introspect endpoint — the aggregated API catalog (every service, method, and type the gateway can route). |
|
manifest
Package manifest emits the PEMM manifest of a running gateway — a single JSON document describing services, plugins, role bindings, federation map, and registered remotes.
|
Package manifest emits the PEMM manifest of a running gateway — a single JSON document describing services, plugins, role bindings, federation map, and registered remotes. |
|
meshsecret
Package meshsecret ships the HMAC gate for /rpc/_register.
|
Package meshsecret ships the HMAC gate for /rpc/_register. |
|
meshsecret/proto
Package proto is the wire format for the mesh-secret-gated /rpc/_register sig.
|
Package proto is the wire format for the mesh-secret-gated /rpc/_register sig. |
|
metrics
Package metrics ships a Prometheus-text-format metrics plugin for sov.
|
Package metrics ships a Prometheus-text-format metrics plugin for sov. |
|
preempt
Package preempt permits a federated _register to take over a service name already claimed by a different address.
|
Package preempt permits a federated _register to take over a service name already claimed by a different address. |
|
registertoken
Package registertoken ships the simple shared-token gate for /rpc/_register.
|
Package registertoken ships the simple shared-token gate for /rpc/_register. |
|
registertoken/proto
Package proto is the wire format for the static register-token join gate.
|
Package proto is the wire format for the static register-token join gate. |
|
registry
Package registry promotes the gateway to central-registry shape — owns POST /rpc/_register and the public top-level /health URL.
|
Package registry promotes the gateway to central-registry shape — owns POST /rpc/_register and the public top-level /health URL. |
|
requestid
Package requestid stamps a stable X-Sov-Request-Id on every request as it enters the gateway and propagates it through every hop the request takes: outbound proxy calls, in-process local dispatch, and downstream gateways (via the same header).
|
Package requestid stamps a stable X-Sov-Request-Id on every request as it enters the gateway and propagates it through every hop the request takes: outbound proxy calls, in-process local dispatch, and downstream gateways (via the same header). |
|
roletakeover
Package roletakeover relaxes the default cross-name role-binding guard.
|
Package roletakeover relaxes the default cross-name role-binding guard. |
|
static
Package static mounts a static-file tree (a built SPA, a docs site, a bundle of assets) on the gateway via a RouteHandler.
|
Package static mounts a static-file tree (a built SPA, a docs site, a bundle of assets) on the gateway via a RouteHandler. |
|
upstreams
Package upstreams limits which upstream gateway URLs a pod trusts X-Sov-* claim headers from.
|
Package upstreams limits which upstream gateway URLs a pod trusts X-Sov-* claim headers from. |
|
Package preset curates default plugin bundles for the common sov deployment modes.
|
Package preset curates default plugin bundles for the common sov deployment modes. |