License: Apache 2.0
rho-kit is the standard Go service toolkit. It centralizes the
infrastructure patterns every service needs so teams can focus on domain logic
while staying consistent, secure, and observable.
- docs/RELEASE_NOTES_v2.md — historical v2.0.0 breaking-changes enumeration.
- CHANGELOG.md — per-release summary.
The kit is a Go multi-module workspace; releases use go.work as the
sole cross-module-resolution mechanism (no replace directives in
go.mod files). The one-time replace-drop was done at v2.0.0;
subsequent releases just bump version numbers and tag.
-
Validate locally. Run
make release-candidate. It includes the policy and tidy checks, lint, race tests, builds, vulnerability analysis, integration tests, coverage, and kit-doctor. GitHub does not duplicate this exhaustive release-owner gate. -
Rehearse locally (safe, no real origin touched).
RELEASE_VERSION=v2.x.y bash tools/rehearse-v2-release.sh
Runs the entire dance against a temp bare repo. Must reach "Rehearsal passed." before touching origin. The manual
Release RehearsalGitHub workflow exists only for validating release machinery changes; it is not required for every release. -
Run the real release. Branch protection is not toggled any more — see "Why the release can push to main" below.
RELEASE_VERSION=v2.x.y bash tools/release-version.sh
Preflights first:
mainclean,origin/mainnot ahead, and nov2.x.ytag already on origin (a collision used to abort mid-run, after earlier levels were already pushed).Per-level: bumps internal kit requires to the target version, tidies, commits, tags every module in the level, pushes tags atomically. Mechanical release commits contain
[skip ci]so the dependency levels do not launch duplicate GitHub jobs — except the final level, which is the commitmainis left at and therefore does run CI. After all levels: pushes coordination tagrelease/v2.x.y, then runs the downstream smoke test automatically (resolves every released module atv2.x.yfrom a throwaway module outside the workspace and fails the release on any version skew). -
Publish the GitHub Release. Use the version entry in
CHANGELOG.md, call out breaking changes, and link the comparison from the previous coordination tag. Do not reuse the historical v2.0.0 notes.
Releases must land in dependency waves: level N's tag has to be on origin
before level N+1 can go mod tidy, because that is what its go.sum hashes
are computed from. That is inherent to Go module versioning in a monorepo and
cannot be collapsed into a single PR.
Classic branch protection on main therefore runs with
enforce_admins: false. Everyone still needs a PR with CODEOWNERS review and
linear history; the repository admin running the release can push the wave
commits and tags directly. Previously the release deleted
required_pull_request_reviews and restored it afterwards, which left main
unprotected for the whole run — and silently unprotected forever if the run
died in the middle.
Two things block a move to repository rulesets today, both worth fixing before attempting it:
- Every workflow is path-filtered (
ci.ymlhaspaths-ignorefordocs/**and*.md;dashboards.ymlandsupply-chain.ymlonly trigger on their own paths). Required status checks would leave any PR that misses those paths permanently pending. An always-runs aggregate gate job has to come first. tools/check-release-team.shreads the classic/branches/main/protectionendpoint and requiresrequire_code_owner_reviews: true. It must learn about rulesets before classic protection can be removed, ormake check-release-team— a release gate — starts failing.
New downstream services should start with
docs/ai/adoption.md: minimum go.mod (the v2.x tags
are published, so require the versioned modules directly — no replace
block needed), the smallest compilable main.go, where each capability lives
in the decision tree, and the common first-mistake checklist. The package
decision tree in AGENTS.md is the
canonical "I need to X, what do I import?" reference.
Snippet status: Go blocks in this README are illustrative fragments. The shell
commands are executable from a downstream module. Golden-path evidence lives in
examples/agentic-service and the cmd/kit-new scaffold tests.
Why it exists
- Provide a single, opinionated "golden path" for service startup.
- Eliminate repeated boilerplate around logging, tracing, health, and config.
- Ship hardened primitives for data stores, messaging, and security.
Design principles
- Secure by default and fail fast on misconfiguration.
- Small, composable packages with explicit boundaries.
- Observability is never optional (logs, metrics, traces are first‑class).
- Safe defaults that scale in production without surprises.
app is the standard service entry point. It wires logging, health
endpoints, lifecycle management, and optional infrastructure like databases,
RabbitMQ, and JWT verification.
app.Main("backend", handler.Version, func(logger *slog.Logger) error {
base, err := app.LoadBaseConfig(8080)
if err != nil {
return err
}
return app.New("backend", handler.Version, base).
With(postgres.Module(pgxbackend.Config{DSN: os.Getenv("DATABASE_URL")})).
With(redis.Module(&goredis.Options{Addr: "cache.internal:6379", Password: "***", TLSConfig: &tls.Config{ServerName: "cache.internal"}})).
With(amqp.Module(os.Getenv("RABBITMQ_URL"))).
With(jwt.Module(os.Getenv("JWKS_URL"),
jwt.WithIssuer("https://issuer.example.com"),
jwt.WithAudience("backend"),
)).
With(ratelimit.IP(100, time.Minute)).
Router(func(infra app.Infrastructure) http.Handler {
return router.New(infra, logger)
}).
Run()
})Use app.LoadBaseConfig, sqldb.LoadFields, and package-specific loaders for
env-backed settings. Pass a hardened pgxbackend.Config to postgres.Module.
v2.0.0 lazy-adapter architecture. Heavy adapter wiring (Postgres, Redis,
RabbitMQ, NATS, OTel tracing, public gRPC) lives in per-adapter sub-modules
under app/: app/postgres, app/redis, app/amqp, app/nats,
app/tracing, app/grpc. Importing app/v2 alone no longer pulls pgx,
go-redis, amqp091, nats.go, otelgrpc, or grpc-go.
For credential rotation, prefer provider hooks over static secrets: pgx
PasswordProvider, go-redis credential providers, AMQP/NATS auth providers,
cloud SDK default credentials, CSRF WithSecrets, and signed-request
WrapKeyStore. See docs/ai/credential-rotation.md.
See examples/agentic-service for a full example.
csrfMW := csrf.New(
csrf.WithSecrets(cfg.CSRFSecret, cfg.PreviousCSRFSecrets...),
csrf.WithAllowedOrigins(cfg.PublicOrigin),
)
handler := stack.Default(router, logger,
stack.WithOuter(csrfMW, csrf.RequireJSONContentType),
)conn, err := kitredis.Connect(&goredis.Options{Addr: "localhost:6379"}, kitredis.WithInstance("cache"))
if err != nil {
log.Fatal(err)
}
c, err := rediscache.NewCache(conn.Client(), "api-cache")
if err != nil {
log.Fatal(err)
}
tenantCache := tenantcache.Wrap(c)
_ = tenantCache.Set(ctx, "profile:123", []byte("..."), time.Minute)# Each module ships independently and uses Go's /v2 path suffix.
go get github.com/bds421/rho-kit/app/v2
go get github.com/bds421/rho-kit/httpx/v2app– service bootstrap, infrastructure wiring, lifecycle, and graceful shutdown.core/config,core/apperror,core/validate,core/secret,core/tenant– configuration, typed errors, validation, and focused primitives.httpx,httpx/middleware/*,httpx/healthhttp,httpx/pagination,httpx/mcp– hardened HTTP servers, JSON helpers, middleware, health endpoints, pagination, and MCP handlers.cmd/kit-contract– portable OpenAPI/event-schema artifacts and conservative compatibility checks for CI.authz,authz/openfga,httpx/authz– authorization interfaces, OpenFGA adapter, and HTTP bridge.security/jwtutil,security/identity,security/netutil,security/csrf,security/asvs– JWT verification and canonical principals, mTLS/SSRF-safe networking, CSRF helpers, and ASVS scanning metadata.auth/oauth2,auth/oauth2/redis– provider-neutral browser OIDC and durable Redis session/state stores.app/oidc– Builder composition for OIDC login/callback/logout routes and session-to-principal projection.crypto/encrypt,crypto/envelope,crypto/paseto,crypto/passhash,crypto/signing– encryption, token, password, and request-signing primitives.infra/sqldb,infra/sqldb/pgx,infra/sqldb/dbtest– SQL contracts, pgx backend, migrations, and Docker-backed DB test helper module.infra/redis,infra/redis/redistest– resilient Redis connection management plus the split Docker-backed Redis test helper module.data/cache,data/cache/rediscache,data/idempotency,data/lock,data/queue,data/stream,data/ratelimit– data interfaces, memory implementations, and optional Redis/Postgres adapters.infra/messaging,infra/messaging/amqpbackend,infra/messaging/redisbackend,infra/messaging/natsbackend– message contracts, buffered delivery, RabbitMQ, Redis Streams, and NATS JetStream adapters.infra/inbox/postgres,infra/outbox,infra/outbox/postgres– durable inbound deduplication plus transactional outbound delivery for at-least-once messaging.infra/storagepluss3backend,azurebackend,gcsbackend,sftpbackend,storagehttp/uploadsec,storagehttp/uploadsec/clamav,storagetest– storage interfaces, cloud/SFTP/local backends, upload validation/scanning, and backend compliance tests.observability/health,observability/logging,observability/logattr,observability/redmetrics,observability/runtimemetrics,observability/slo,observability/pprof,observability/tracing– health, logs, metrics, profiling, SLOs, and tracing.runtime/lifecycle,runtime/concurrency,runtime/eventbus,runtime/cron,runtime/batchworker– lifecycle orchestration, worker patterns, eventing, and scheduling.resilience/retry,resilience/circuitbreaker,io/atomicfile,io/progress,flags– retries, circuit breakers, safe file writes, progress tracking, and feature flags.
auth.JWTpanics if the provider is nil to prevent accidental auth bypass.- Some packages intentionally panic on programmer errors to fail fast.
httpxintentionally avoids the stdlibnet/http/httputilname collision.- Resource names are used as Prometheus labels; keep them small and static.
ENVIRONMENT– set todevelopmentto enable dev-only behavior.LOG_LEVEL–debug,info,warn,error.SERVER_HOST,SERVER_PORT,INTERNAL_PORT– HTTP bind settings.TLS_CA_CERT,TLS_CERT,TLS_KEY– enable mTLS when all are set.DB_HOST,DB_PORT,<PREFIX>_DB_USER/_DB_PASSWORD/_DB_NAME– DB config.DB_SSL_MODE– PostgreSQL SSL mode (disable, allow, prefer, require, verify-ca, verify-full).RABBITMQ_URL– AMQP URL (Docker secrets supported via_FILE).