# 核心

Source: https://gortexa.sheng.page/components/core/

`kernel`、`config`、`interceptor`、`auth` 與 `apperr` 是組裝一個 Gortexa 服務的五個核心 package：kernel 擁有那個單一 h2c 埠與生命週期，config 提供分層設定，interceptor 是固定八段治理鏈，auth 是可插拔的驗證，apperr 是三協定共用的錯誤模型。

先取得模組：`go get github.com/yshengliao/gortexa@v0.28.3`。以下片段皆對 v0.28.3 編譯驗證過。

## kernel

composition root：`App` 擁有單一 h2c listener（多工 gRPC、HTTP/JSON、MCP）、gRPC server、health registry、in-process loopback 與 graceful shutdown。用 `kernel.New(opts...)` 建立，options 有 `WithConfig`、`WithLogger`、`WithInterceptors`（吃 `interceptor.Set` 值，非指標）、`WithoutInterceptors`（明示不要治理鏈——`WithInterceptors` 與它必須恰好給一個，都不給會啟動失敗，兩個都給則互斥並回傳錯誤）、`WithStatsHandler`、`WithHTTPWrap`、`WithGateway`、`WithMCPHandler`、`WithShutdownHook`、`WithServerOptions`（append gRPC ServerOption，讓你加自己的 interceptor／stats，接在框架鏈之後）、`WithAdminListener`（opt-in 獨立 ops 埠只服務 /healthz+/readyz）、`WithExtraListener`（在 caller 自有 listener 上服務任意 handler）。主 h2c 埠始終單埠多工不變；`app.AdminAddr()` 可查 admin 埠實綁位址。

```go
import (
	"context"
	"github.com/yshengliao/gortexa/config"
	"github.com/yshengliao/gortexa/health"
	"github.com/yshengliao/gortexa/kernel"
)

cfg := config.MustBuild()
// New 要求 WithInterceptors / WithoutInterceptors 恰好給一個；set 見下面的 interceptor 段。
app, err := kernel.New(kernel.WithConfig(cfg), kernel.WithInterceptors(set))
if err != nil {
	return err
}
// 在此把 gRPC service 註冊到 app.GRPCServer()
app.Health().Register("self", func(context.Context) health.State { return health.Healthy })
return app.Run(ctx) // /healthz、/readyz 內建
```

`App` 的方法：`GRPCServer()`、`Health()`、`Loopback()`、`Logger()`、`SetGateway()`、`SetMCPHandler()`、`Run(ctx)`、`Shutdown(ctx)`。`SetGateway` 與 `SetMCPHandler` 是組裝期 setter：伺服器開始服務後再呼叫會 panic。`WithHTTPWrap` 可以疊加而不是覆蓋（先傳的包在內層），所以第二次 wrap 不會默默丟掉前一層的安全處理；`WithAdminListener` 最多只能用一次（兩個 admin listener 會互相競爭 `AdminAddr` 的回報值）。

## config

固定優先序：內建預設 < YAML 檔 < .env 檔 < 環境變數（`GORTEXA_` 前綴、`__` 表巢狀）。入口是 `MustBuild`（fail-loud，jwt_secret 為空／預設 placeholder／< 32 bytes 或 issuer 空會 panic）與 `BuildUnvalidated`。沒有 `Load`。`WithConfigFile` 指定的檔案是必要的：檔案不存在會建置失敗（`MustBuild` panic）。

```go
import "github.com/yshengliao/gortexa/config"

cfg := config.MustBuild(config.WithConfigFile("etc/config.yaml"))
dsn := cfg.DB.DSN.Reveal() // Secret 只在使用點還原
```

secret 型別 `config.Secret` 的 `String()`／log／JSON 一律遮罩成 `****`，要真值用 `Reveal()`。`Config` 的子結構：`Server`、`Auth`、`DB`、`Cache`、`MQ`、`Log`、`Observ`（全欄位見[設定](https://gortexa.sheng.page/configuration/)）。

## interceptor

`NewSet(Config)` 產出八段固定順序的鏈（recover → request-id → logger → load-shed → rate-limit → circuit-breaker → auth → validation），交給 `kernel.WithInterceptors`。

```go
import (
	"time"
	"github.com/yshengliao/gortexa/auth"
	"github.com/yshengliao/gortexa/interceptor"
)

set, err := interceptor.NewSet(interceptor.Config{
	Verifier:  auth.MustNewVerifier([]byte(secret), "gortexa"),
	RateLimit: interceptor.RateLimitConfig{RPS: 200, Burst: 100, TTL: 10 * time.Minute},
})
// set 傳給 kernel.WithInterceptors(set)
```

`Config` 另有 `Logger`、`AuthSkip`、`CircuitBreaker`、`LoadShedding`、`Metrics` 欄位。`Set` 提供 `UnaryChain()`／`StreamChain()`。`CBConfig.Logger` 每次狀態轉換會記一行；`NewSet` 會用 `Config.Logger` 自動補上，但直接自建 breaker 時不設它就只有 metric 與 span（沒有 OTLP endpoint 時等於靜默）。`CBConfig` 的失敗定義是三值的：只有伺服端失敗（`Unavailable`、`Internal`、request context 仍存活時的 `DeadlineExceeded`）計數；呼叫端造成的結果（ctx 取消／逾時、`Canceled`、`InvalidArgument`、`Unauthenticated`、`PermissionDenied`）中性——不觸發也不重置；half-open 探測超過 `OpenInterval` 未回報即放棄該回合，長串流不會把方法卡在 half-open。

auth 段可插拔：`Verifier` 走內建 HS256 JWT；非 JWT 方案改設 `Authenticator`（一個 `auth.Authenticator`，`Authenticate(ctx) (ctx, error)`），例如 static bearer／mTLS／API-key。兩者都設時 `Authenticator` 優先。JWT 也可用 `auth.NewJWTAuthenticator(v)` 顯式取得。

## auth

HS256 JWT 驗證／簽發與 context helper。secret 須 ≥ 32 bytes。

```go
import "github.com/yshengliao/gortexa/auth"

v := auth.MustNewVerifier([]byte(secret), "gortexa")
tok, err := v.Sign("user-123", []string{"admin"}, time.Hour)
// 在 handler 內取回驗證過的 claims：
if claims, ok := auth.ClaimsFrom(ctx); ok {
	_ = claims.Subject // "user-123"
}
```

`Verify(token)` 回 `*Claims`（含 `Roles` 與內嵌的 `jwt.RegisteredClaims`）；`BearerToken(header)` 從 Authorization header 取 token。

## apperr

單一對應表驅動 gRPC status、HTTP status 與 MCP error envelope。`*apperr.Error` 實作了 `GRPCStatus()`，handler 直接回傳即可。

```go
import "github.com/yshengliao/gortexa/apperr"

func (s *svc) GetThing(ctx context.Context, req *pb.GetThingRequest) (*pb.Thing, error) {
	t, err := s.repo.Find(ctx, req.GetId())
	if err != nil {
		return nil, apperr.Wrap(apperr.CatNotFound, "thing not found", err)
	}
	return t, nil
}
```

只有 `CatInvalidArgument` 與 `CatUnauthenticated` 會把開發者訊息轉給呼叫端；其餘 category 只露出 registry 的 SafeMessage，內部原因不外洩。validation 失敗轉發的訊息有上限——最多 10 條違規、512 bytes，超出以 `(+N more)` 收尾——所以一個帶大型 repeated 欄位的請求無法把單一 `InvalidArgument` 放大成數 MB 的回應與 log 紀錄。以 `%w` 包裝 status 時只轉發最內層 status 自己的訊息，包裝層的上下文一律換成 SafeMessage。category 常數涵蓋 gRPC 的完整清單（`CatNotFound`、`CatPermissionDenied`、`CatFailedPrecondition`、`CatInternal` 等）。
