Skip to content

Core concepts

Protobuf is the single source of truth. You write two things: proto contracts and logic implementations. gRPC stubs, the HTTP/JSON gateway, the OpenAPI spec and MCP tool schemas are all produced by gortexa regen, which always runs buf lint → buf breaking → buf generate.

  • Everything under gen/ is an artifact: not committed, never hand-edited.
  • The breaking-change gate is built into the flow: proto edits that break compatibility are rejected; additive edits pass.

One cleartext HTTP/2 port, dispatched by Content-Type and path:

Request Destination
Content-Type: application/grpc gRPC (grpc-go’s ServeHTTP mode)
Path /mcp MCP bridge (Streamable HTTP)
Everything else grpc-gateway (HTTP/JSON) and built-in routes (/healthz, /openapi.json)

h2c uses Go’s native http.Server.Protocols, not the deprecated x/net/h2c. Cleartext means TLS must be terminated in front of the service in production — see Deployment.

Eight stages, fixed order:

recover → request-id → logger → load-shed → rate-limit → circuit-breaker → auth → validation

An incomplete chain fails startup (fail-loud) rather than silently dropping a stage. OTel attaches as a gRPC StatsHandler, covering unary and streaming.

The order is fixed, but the auth stage is pluggable: the default is HS256 JWT (interceptor.Config.Verifier), yet the stage actually runs an auth.Authenticator — a non-JWT scheme (static bearer, mTLS, API key) just sets Config.Authenticator, leaving the eight-stage order intact. To add your own interceptor, use kernel.WithServerOptions(...) (gRPC chains it after the framework chain, before the handler, still within recover) rather than editing the eight stages.

The defaults for the three governance stages are defined in cmd/server/main.go (not in config.yaml — to change them, edit the interceptor.NewSet parameters and recompile):

Stage Defaults On limit
rate-limit 200 RPS per peer, burst 100, tracking TTL 10 minutes RESOURCE_EXHAUSTED (HTTP 429)
circuit-breaker opens after 5 consecutive failures for 10s, half-open admits 2 probes UNAVAILABLE (HTTP 503)
load-shed in-flight cap 1,024 (gRPC health checks exempt) RESOURCE_EXHAUSTED (HTTP 429)

Measured numbers (benchmarks in the framework README, Apple M1 / go1.26.5): a full unary pass through the chain is about 1,552 ns and 27 allocs; the rate-limiter Allow hot path is 0 allocs (allocs are machine-independent).

A single mapping table decides how the same error appears on all three protocols: gRPC status, HTTP status, MCP error envelope. The rule is that internal causes never leak — only invalid-argument and unauthenticated forward their messages to the caller; every other category returns a safe message from the registry.

The error-resolution hot path is about 105 ns and 2 allocs (using Go 1.26’s errors.AsType).

Component Notes
Config Layered loading, fail-loud, masked secrets. See Configuration
Health Three-state health mapped to gRPC Health; app.Health().Register(...) wires real dependencies
Logging/Observability Structured logs via slog + OTLP export
Database PgBouncer-safe pgx pool + sqlc
Cache Process-local in-memory by default; cache.driver: redis switches to distributed (zero-dependency RESP client)
MQ NATS (core or JetStream); empty mq.group_id = fanout, non-empty = load-balance