Handover: config migration (config-section-adapters)¶
This note hands off the refactor/config-section-adapters branch (MR !200) to a
follow-up session. It records what is done, the patterns to reuse, and
the remaining config-migration work.
Status¶
- slog-first logging (
docs/development/specs/2026-07-07-slog-first-extraction-seams.md, APPROVED): COMPLETE.logger.Loggeris now a*slog.Loggermirror, and every extraction-candidate package (chat,controls,config,http,grpc,gateway,telemetry+datadog/posthog,errorhandling) has been migrated offpkg/loggerto*slog.Logger. Only GTB adapter files (*config_adapter.go,*FromProps) and the composition layer (pkg/cmd,pkg/setup,pkg/props,pkg/docs,pkg/utils) still importpkg/logger. - config-section-adapters (
docs/development/specs/2026-07-07-config-section-adapters-for-extraction.md, IN PROGRESS): first wave DONE (chat,tls,http,grpc,gateway,telemetry/otelcore,vcs+ providers +repo). Later waves remain (below).
Patterns to reuse¶
Logger boundary¶
- Packages accept
*slog.Logger; a nil logger resolves toslog.New(slog.DiscardHandler)(quiet by default — the app wires logging). - GTB adapters convert
props.Logger(thelogger.Loggerinterface) to*slog.Loggerwithlogger.ToSlog(props.Logger)at the boundary.ToSlogis nil-safe, returns a*slog.Loggerunchanged, else builds one over the logger'sHandler()(so buffer capture is preserved in tests). - Runtime level/format live only in the GTB root via
logger.SetLevel/logger.SetFormatterhelpers (the optionalLeveller/Reformatterinterfaces). A plain*slog.Loggerno-ops for them. - Tests: wrap logger args in
logger.ToSlog(...); replace custom capture loggers withlogger.NewCaptureHandler()+slog.New(capture)and assert oncapture.Records()/Messages()/Contains().
Config typed-struct adapters¶
- Extracted packages own typed config structs (
mapstructuretags). GTB'sconfig_adapter.godoes the config reads:config.UnmarshalSection[T](one shot) orconfig.ObserveSection[T](long-lived, reload-aware), merges package defaults, validates, resolves credentials. - Choose the mechanism by shape (see the spec's §12 note):
UnmarshalSectionfor self-contained sections (chat,http/grpcserver settings).- Narrow lookup interface (
release.Config/vcs.TokenConfig) for provider registries with dynamic, provider-specific keys (vcs) — do NOT force typed decode there; it already imports zero GTB packages. - Manual
IsSet-override for shared↔transport/signal fallback (tls,telemetry/otelcore). - Composition across multiple prefixes (
gateway). config_adapter.gois the GTB adapter file that will NOT travel with the extracted module. Keep allpkg/config+pkg/propscoupling there; package cores stay clean.
Remaining config-migration work¶
Per config-section-adapters §8 (later waves) and §9 Phase 6:
- Packages that currently need no config — add typed structs only if/when
GTB introduces config-driven behaviour (deferred, not speculative):
authn(API-key/JWT/mTLS config in the transports),output.Config(deferred — output resolves format from the--outputflag, not a config section; adding it now would be dead surface),browser,workspace,forms,changelog,redact,regexutil,openapi,errorhandlingHelpConfig. - Root
pkg/telemetry— thepkg/propstype coupling is now DONE (docs/development/specs/2026-07-10-telemetry-props-decoupling.md, IMPLEMENTED):EventType/DeliveryMode+ constants moved to the dependency-freepkg/telemetrytypesleaf;pkg/propskeeps them as type aliases (zero downstream break);pkg/telemetrycore no longer importspkg/props. Still remaining before extraction: the larger product-analytics/consent ↔ observability split (see the extraction report's telemetry section) — a separate effort, not a config/props-type concern. - Phase 6 — automated import-boundary check: DONE.
test/architecture/boundary_test.gois now a table-driven guard overpkg/propsandpkg/logger, covering all first-wave cores pluspkg/telemetry. No separate CI job is needed —go test ./...(the cicdgo-testMR component) already runstest/architecture; the earlierjust ciworry was moot. - Open questions still deferred: Q1
SectionInConfig(not shipped —SectionExists+IsSetcover the telemetry-consent case); Q5 the extractedpkg/configmodule name (defer to the config-extraction spec). vcs/releasereceived a registry-signature narrowing rather than a full typed adapter (judged adequate since its providers own the typed settings) — revisit if/when extracting the releases module.
Gotchas & conventions¶
- Mockery churn:
mockery/just mocksregenerates the wholemocks/tree with import-order churn. After an interface change, regenerate then keep ONLY the affected package's mocks (git checkout/git cleanthe rest). - slog
int→int64: slog normalises integer attributes toint64; tests asserting on captured integer key/values must useassert.EqualValues. contextcheck: request/RPC logging usesl.Log(ctx, level, msg)— thread the request/RPC context through, nevercontext.Background().- Adding imports: prefer
goimports -w; ad-hocsedinsertion can misorder the group or leave a stray blank line (rungofmt -wafter). internal/agenttest:TestGoBuildTool_SuccessOnTrivialModulefails witherror obtaining VCS status: exit status 128— an environmental Go build-stamp failure in this sandbox, pre-existing onmainand unrelated to this work. Exclude it:go test $(go list ./... | grep -v internal/agent).
Verification¶
go build ./...
go test $(go list ./... | grep -v internal/agent)
golangci-lint run ./...
go test ./test/architecture/... # props import-boundary guard