Extract the transport server stack into go/transport¶
- Authors
- Matt Cockayne, Claude Opus 4.8 (AI drafting assistant)
- Date
- 2026-07-17
- Status
- IMPLEMENTED (2026-07-18) — shipped to main (cut-over MR !247); originally APPROVED (2026-07-17) — O1–O7 resolved. Phase 5 (final) of the transport-stack extraction plan. This is a deliberate clean break, not a facade cut-over.
Summary¶
The last resident of GTB's transport layer is the server stack: the HTTP server,
the gRPC server, and the grpc-gateway. Phases 1–4 extracted everything a client needs
(go/tls, go/authn, go/observability, go/transit, go/httpclient,
go/grpcclient); the server bootstraps, health endpoints, request/RPC authentication and
security headers stayed behind because they legitimately depend on the heavier
go/controls lifecycle and on each other.
This spec promotes all three, together, into a single standalone module
gitlab.com/phpboyscout/go/transport with http, grpc and gateway
sub-packages (per the plan's O1/O3). It consumes go/controls, go/authn,
go/transit, go/tls, go/grpcclient and the gRPC/grpc-gateway SDKs.
Unlike Phases 2–4, this is a clean break, not a facade cut-over (O1). The pure
server API moves to go/transport and GTB does not re-export it via value aliases.
GTB retains only the config.Containable adapters — the framework-config glue that
cannot live in a config-free module — and those adapters call go/transport internally.
GTB's own internal callers of the pure API are repointed to go/transport; downstream
tools that imported the pure server symbols from pkg/http/pkg/grpc/pkg/gateway
migrate their imports to go/transport, guided by a migration note. This is a breaking
change, permitted under the pre-1.0 policy and shipped as a minor bump.
Completing this makes the transport stack fully modular and ends the extraction
programme: a tool can build a hardened HTTP/gRPC service from go/transport without
the GTB framework, and GTB itself becomes a consumer of its own extracted transport
layer through the thin config adapters it keeps.
Problem statement / goals¶
- Ship
go/transportv0.1.0:http,grpc,gatewaysub-packages holding the pure server API — server constructors from typedServerSettings, health/liveness/readiness handlers,AuthMiddleware/AuthInterceptor, security headers,Start/Stop/Statuslifecycle glue, the gatewayNew/Register, and (O5)DialLocal+TLSClientCredentials. - Clean break in GTB (O1): delete the re-exported pure API from
pkg/http/pkg/grpc/pkg/gateway; keep only theconfig.Containableadapters, repointed atgo/transport. Repoint GTB's internal callers to the module. - Leave GTB green (
just ci) — including the E2E controls/HTTP/gRPC scenarios — and keep the retained-adapter packages ≥90% under coverage-policy. go/transport's dependency graph may containgo/controls,go/authn,go/transit,go/tls,go/grpcclientand the gRPC/gateway SDKs, but notgo-tool-base, Viper/Cobra/Charm, or cloud SDKs. Adepfootprintguard enforces this.- Document the break: a migration note mapping every moved symbol
pkg/{http,grpc,gateway}.X→transport/{http,grpc,gateway}.X.
Non-goals
- No behaviour change to the hardened server defaults (TLS, timeouts, health wiring, auth precedence, security headers).
- The GTB config-key schema and the
config.Containableadapters stay in GTB — the module never imports the framework config container. - No new middleware — that lives in
go/transit(Phase 3) and is consumed. - No change to the already-extracted client modules.
What moves vs. what stays¶
Moves into go/transport (and is removed from GTB)¶
transport/http (from pkg/http, server-side files):
- Health: HealthHandler, LivenessHandler, ReadinessHandler (consume
controls.HealthReporter).
- Server: ServerSettings{Port, MaxHeaderBytes} (kept distinct — O2), ServerOption +
With* (WithConfigPrefix, WithPort, WithMaxHeaderBytes, WithReadTimeout,
WithWriteTimeout, WithIdleTimeout, WithServerTLSConfig), NewServer,
StartWithTLSPair, Stop, Status, RegisterOption, WithMiddleware,
WithMaxRequestBodyBytes, MaxBytesMiddleware, Register.
- Auth (O3): AuthMiddleware + its AuthOptions, IdentityFromContext (consume
go/authn).
- Security headers: SecurityHeadersMiddleware + its options.
transport/grpc (from pkg/grpc, server-side files):
- Server: ServerSettings{Port, Reflection} (kept distinct — O2), ServerOption +
With*, NewServer, RegisterHealthService, Start, TLSServerCredentials, Stop,
Status, RegisterOption, WithInterceptors, Register.
- Auth (O3): AuthInterceptor + its options, IdentityFromContext.
- Client-dial helpers (O5): DialLocal (already a thin adapter over go/grpcclient) and
TLSClientCredentials move here, keeping the grpc package self-contained.
transport/gateway (from pkg/gateway):
- Settings, RegisterFunc, Option + With* (WithMuxOptions, WithDialOptions,
WithMiddleware), New, Register. Consumes transport/http (for Chain) + the
grpc-gateway runtime + gRPC SDK, not go/grpcclient (O4).
Stays in GTB (config adapters only — no facades)¶
- The
config.Containableadapters (config_adapter.goin each package), repointed atgo/transport:ServerSettingsFromConfig,ObserveServerSettingsFromConfig,NewServerFromContainable,StartFromContainable,RegisterFromContainable,RateLimitConfigFromConfig,CircuitBreakerConfigFromConfig,DialLocalFromContainable, and the gateway'sSettingsFromConfig/NewFromContainable/NewFromConfig/RegisterFromContainable/RegisterFromConfig. These needconfig.Containable(a GTB type) and therefore cannot move; they remain the recommended GTB-facing API and callgo/transportinternally. They reference the module'sServerSettings/config types directly (no re-export alias). - The GTB config-key schema and default assets.
- No value-alias re-exports of the pure API.
pkg/http/pkg/grpc/pkg/gatewayshrink to the adapter set above (plus whatever GTB-only glue genuinely remains). A GTB or downstream caller that wants the pure constructors importsgo/transportdirectly.
Public API after the break¶
The config.Containable adapters keep their names/signatures (bar referencing the
module's ServerSettings type). The pure constructors are no longer available from
pkg/* — they live at go/transport:
// go/transport/http (pure)
func NewServer(ctx context.Context, s ServerSettings, h http.Handler, opts ...ServerOption) (*http.Server, error)
// pkg/http (GTB — adapter retained, pure NewServer NOT re-exported)
func NewServerFromContainable(ctx context.Context, cfg config.Containable, h http.Handler, opts ...transporthttp.ServerOption) (*http.Server, error) {
s := ServerSettingsFromConfig(cfg, prefix) // config-key derivation stays in GTB
return transporthttp.NewServer(ctx, s, h, opts...)
}
// Migration for a downstream that called the pure API directly:
// - gtbhttp.NewServer(ctx, settings, h)
// + transporthttp.NewServer(ctx, settings, h)
Module layout & dependencies¶
- One module,
gitlab.com/phpboyscout/go/transport, sub-packageshttp,grpc,gateway(mirrorsgo/transit).gatewayconsumeshttpand the gRPC SDK. - Dependency graph (allowed):
go/controls,go/authn,go/transit,go/tls,go/grpcclient, gRPC SDK, grpc-gateway runtime,cockroachdb/errors. Forbidden (depfootprint):go-tool-base, Viper/Cobra/Charm, cloud SDKs. - This is the one extracted module that legitimately carries
go/controls— it is the server-lifecycle stack, sequenced last for exactly this reason.
Cut-over shape (single combined MR — O6)¶
Ship the whole GTB-side repoint as one MR (O6):
- Move the pure symbols into
go/transport; publish v0.1.0. - In GTB, delete the moved server code from
pkg/http/pkg/grpc/pkg/gateway, leaving theconfig.Containableadapters, and repoint those adapters + every internal GTB caller (pkg/cmd/*,internal/generatortemplates, generated scaffolds, E2E test binary) atgo/transport. - Update
go.mod, runjust cigreen (unit + race + lint + coverage-policy + E2E). - Migration notes mapping every moved symbol; mark the extraction programme complete.
Because there are no re-export wrappers, the coverage-policy risk of Phase 3 does not arise here; the retained adapter packages are covered by the existing GTB adapter tests.
Resolved (review 2026-07-17)¶
- O1 — Clean break, not a facade. Eliminate the value-alias re-exports; GTB keeps
only the
config.Containableadapters (which cannot leave the framework). Downstreams migrate togo/transport; document it thoroughly. (Matt.) - O2 —
ServerSettingskept distinct per sub-package (http{Port, MaxHeaderBytes},grpc{Port, Reflection}) — no unified type. (Matt.) - O3 — Auth moves with its transport into
transport/httpandtransport/grpc. (Recommended; implied by the clean break.) - O4 —
gatewayneeds only the grpc-gateway runtime + gRPC SDK, notgo/grpcclient; the dial happens in GTB's retainedNewFromContainableadapter. (Recommended.) - O5 —
DialLocal+TLSClientCredentialsmove totransport/grpc, keeping the grpc server package self-contained; GTB'sDialLocalFromContainableadapter stays. (Matt leaned "move"; confirmed.) - O6 — Single combined cut-over MR. (Matt.)
- O7 — Naming & docs.
go/transport, docs attransport.go.phpboyscout.uk, same publish playbook (public repo, DNS-only CNAME, Diátaxis microsite, landing card). (Plan O3; confirmed.)
Open questions¶
None outstanding. Implementation-time detail: confirm whether any residual GTB-only glue
beyond the config adapters must stay in pkg/* (e.g. a config-derived ServerSettings
type reference) — resolve while cutting over, keeping the clean-break intent (no pure-API
re-exports).
Testing strategy¶
- Pure server logic (constructors, health handlers, auth, security headers, gateway,
DialLocal,TLSClientCredentials) is unit-tested insidego/transport— lift the existingpkg/*server tests, ≥90% (target 100% on new files). - GTB keeps adapter tests: the
*FromContainableadapters (which own the config-key derivation) are tested in GTB against a realconfig.Containable. - BDD (Godog): the server stack does warrant E2E scenarios (service lifecycle,
health/readiness, graceful shutdown) — the existing
features/controls/HTTP/gRPC scenarios exercise GTB's retained adapters end-to-end and must stay green through the break. depfootprint_test.goguard in the module (forbid go-tool-base/Viper/Cobra/Charm/ cloud; allow controls/authn/transit/tls/grpcclient/grpc/gateway).
Documentation¶
- New microsite
transport.go.phpboyscout.uk(zensical, Diátaxis): index, getting-started (stand up a health-checked HTTP+gRPC service), how-to (add auth / security headers / a gateway), explanation (the server/client split & the lifecycle seam). Reference → pkg.go.dev. - Migration note
docs/reference/migration/v0.x-transport-extracted.md— this one is a real migration (breaking): a symbol-by-symbol mappkg/{http,grpc,gateway}.X → transport/{http,grpc,gateway}.X, with the note that the*FromContainable/*FromConfigadapters remain in GTB. Updatedocs/explanation/components/{http,grpc,gateway}.mdand tick the extraction report (transport stack complete → extraction programme done).
Implementation phases¶
- Assemble
go/transport(http → grpc → gateway sub-packages), lift server tests, depfootprint + docs; build green locally (checkpoint before repo/DNS). - Publish: public repo, DNS-only domain, release v0.1.0 (human-gated), landing card.
- GTB clean-break cut-over (single MR): delete moved code, keep + repoint the config
adapters, repoint internal callers,
just cigreen; migration notes + docs; mark the extraction programme complete.