Research digest 1119 — Adopting the golusoris fx framework across vmafx Go¶
Question. Can vmafx's six Go binaries and twelve pkg/ libraries be migrated onto github.com/golusoris/golusoris (a go.uber.org/fx module framework) fully, and in what order, given the maintainer's "adopt it fully; file issues for any missing capability" directive?
Method¶
Read-only comparison of the golusoris source tree (at v0.3.1) against every vmafx cmd/*/main.go + pkg/*. For each binary, mapped its hand-rolled composition (logger, OTel, lifecycle, HTTP/gRPC server, config) onto the equivalent golusoris module, and recorded what is replaced vs kept (domain code). Diffed the two go.mod files to assess merge risk.
Findings¶
-
Dependency alignment is exact. Both repos pin byte-identical versions of every shared dependency:
grpc v1.81.1,otel v1.44.0,controller-runtime v0.24.1,client-go v0.36.2,cobra v1.10.2, MCP SDKv1.6.1,go 1.26.4.go get golusoris@v0.3.1+go mod tidywidened the closure (koanf, chi, fx/dig, river transitives) butgo build ./...and all test binaries still compile clean — no version-skew breakage. This was the dominant a-priori risk and it is retired. -
Module coverage is near-complete. golusoris provides every server primitive vmafx needs:
golusoris.Core(config+log+clock+id+validate+crypto),otel.Module,golusoris.HTTP(chi + graceful*http.Server),grpc.Module(OTel/logging/recovery interceptors baked in),k8s/healthprobes,k8s/operator(a full controller-runtime manager fx module — not just health),clikit(cobra+fx),leader. The operator module is the single best fit:vmafx-operatorreduces tooperator.Module + ProvideScheme(AddToScheme) + fx.Invoke(SetupWithManager). -
Three real gaps, filed upstream (per the directive, not worked around):
- golusoris#225 —
grpc.Modulehard-codes itsServerOptions (grpc/grpc.go:128-184); no fx-injectable interceptor hook. Blocks the controller, which must chain a JWKS auth interceptor. Also requests a boundedStop()fallback on shutdown (todayOnStoponlyGracefulStop()s). - golusoris#226 — no
version/buildinfomodule; every vmafx binary stampsbuildVersionvia ldflags. Interim: vmafx shipsversion.Info+version.Get()locally. -
golusoris#227 —
k8s/operatordoes not callctrl.SetLogger(so controller-runtime logs bypass slog) andoperator.Optionshas no webhook-server port/cert field. Both are app-shimmable in the interim. -
Two deliberate non-adoptions (design choices, not gaps): keep the
VMAFX_config env-prefix viafx.Replace(config.Options{EnvPrefix:"VMAFX_"})rather than migrate the whole deployment surface to golusoris's defaultAPP_; and keep the controller's embeddedmodernc.org/sqlitejob queue rather than adoptgolusoris.Jobs(river/Postgres), which would force a Postgres dependency onto a single-binary design. golusoris#99 itself frames river/Postgres as a deliberate framework strength, confirming this is not a gap to backfill.
Recommendation¶
Phase 0 foundation (go get + internal/app/bootstrap + this digest + ADR-1119), then services-first: vmafx-server (proves Core+HTTP+gRPC+health with no blockers) → vmafx-controller (after #225 lands) → vmafx-node → vmafx-operator (parallelisable — shares no scoring code). Then CLI tools (vmafx-mcp, vmafx-tune via clikit) and the pkg/ sweep. Keep pkg/* framework-agnostic: resolve config in each binary's providers and pass plain typed args down, so the libraries stay unit-testable without fx.
The chief residual correctness risk is cgo-scorer lifetime under fx OnStop ordering — the gRPC server must drain in-flight Score calls before the libvmaf.Scorer Close()s its C resources; enforced by the provider dependency edge (gRPC depends on Scorer ⇒ fx stops gRPC first).