Lands the minimal tenant-registry service so portal can resolve a tenant from a slug.
Endpoints (under :8080 in dev):
GET /healthz → {"status":"ok"}
GET /v1/tenants/by-slug/{slug} → seeded acme tenant or 404
GET /v1/tenants/{id} → same by UUID or 404
In-memory store pre-seeded with one tenant (acme) matching the seed user in the dev-stack Keycloak realm.
Stdlib-only — Go 1.22 enhanced ServeMux, log/slog, net/http. No third-party deps in the skeleton.
migrations/0001_init.up.sql lands the full M4.1 schema for review but is unapplied by the skeleton.
Makefile + multi-stage distroless Dockerfile.
Why
The portal middleware needs to call this service to resolve <slug>.localhost:3000 → tenant during the OIDC redirect dance. Without a running tenant-registry, portal can't even render /[slug]/dashboard in dev. Skeleton-mode unblocks portal work today; full pg-backed CRUD + JWT validation + audit endpoints land in the M4.1 follow-up.
Linked milestone: M4.1 (skeleton — full implementation in follow-up)
How
cmd/server/main.go — graceful-shutdown server, slog JSON logger.
Blast radius: dev only. Service has no production deployment yet (the manifest stub in orca-platform/manifests/vm-control/tenant-registry.toml still says placeholder image).
What could break:
The skeleton has no auth — /v1/tenants/* is open. Fine for dev/loopback; must not ship to stage without the JWT middleware. The M4.1 follow-up adds it.
The in-memory store loses state on restart. Intentional; pgx-backed store lands with the schema in the follow-up.
Rollback plan: revert the PR. Nothing depends on this service yet.
Checklist
Unit tests added (3, exercising every endpoint + 404 path)
Docs updated (README, CHANGELOG)
Secrets via Infisical — none yet; dev .env documented; .env.local gitignored
Migration is forward-only — schema committed but unapplied; M4.1 follow-up wires golang-migrate
Tenant scoping — n/a (handler is a public lookup; tenant scoping enforced once auth lands)
OpenAPI spec — lands with the M4.1 follow-up that adds the full endpoint set
CHANGELOG entry under "Added"
## What
- Lands the minimal tenant-registry service so portal can resolve a tenant from a slug.
- Endpoints (under `:8080` in dev):
- `GET /healthz` → `{"status":"ok"}`
- `GET /v1/tenants/by-slug/{slug}` → seeded `acme` tenant or 404
- `GET /v1/tenants/{id}` → same by UUID or 404
- In-memory store pre-seeded with one tenant (`acme`) matching the seed user in the dev-stack Keycloak realm.
- Stdlib-only — Go 1.22 enhanced ServeMux, log/slog, net/http. No third-party deps in the skeleton.
- `migrations/0001_init.up.sql` lands the full M4.1 schema for review but is **unapplied** by the skeleton.
- Makefile + multi-stage distroless Dockerfile.
## Why
The `portal` middleware needs to call this service to resolve `<slug>.localhost:3000` → tenant during the OIDC redirect dance. Without a running tenant-registry, portal can't even render `/[slug]/dashboard` in dev. Skeleton-mode unblocks portal work today; full pg-backed CRUD + JWT validation + audit endpoints land in the M4.1 follow-up.
Linked milestone: **M4.1** (skeleton — full implementation in follow-up)
## How
- `cmd/server/main.go` — graceful-shutdown server, slog JSON logger.
- `internal/config/` — env-driven config (`APP_ENV`, `ADDR`, `KEYCLOAK_ISSUER`, `DATABASE_URL`).
- `internal/server/` — handlers + a tiny request-logging middleware. Stdlib mux with Go 1.22 method routing.
- `internal/store/memory.go` — interface-shaped so the M4.1 follow-up swaps it for `pgx` without touching handlers. Pre-seeded with one tenant.
- Three integration tests against `httptest.Server`: healthz, by-slug acme, by-slug 404.
- Dockerfile uses `gcr.io/distroless/static-debian12:nonroot` — final image is ~10 MB; CGO disabled.
## Test plan
- [x] `go test -race ./...` ✅ (3 tests in internal/server)
- [x] `go vet ./...` clean
- [x] `gofmt -l .` empty
- [x] `go build` ✅ (8.4 MB static binary)
- [ ] End-to-end smoke once portal lands: visit `acme.localhost:3000`, observe portal calling `tenant-registry /v1/tenants/by-slug/acme`
## Risk
**Blast radius:** dev only. Service has no production deployment yet (the manifest stub in `orca-platform/manifests/vm-control/tenant-registry.toml` still says `placeholder` image).
**What could break:**
- The skeleton has **no auth** — `/v1/tenants/*` is open. Fine for dev/loopback; **must not** ship to stage without the JWT middleware. The M4.1 follow-up adds it.
- The in-memory store loses state on restart. Intentional; pgx-backed store lands with the schema in the follow-up.
**Rollback plan:** revert the PR. Nothing depends on this service yet.
## Checklist
- [x] Unit tests added (3, exercising every endpoint + 404 path)
- [x] Docs updated (README, CHANGELOG)
- [x] Secrets via Infisical — none yet; dev .env documented; `.env.local` gitignored
- [x] Migration is forward-only — schema committed but unapplied; M4.1 follow-up wires `golang-migrate`
- [ ] Tenant scoping — n/a (handler is a public lookup; tenant scoping enforced once auth lands)
- [ ] OpenAPI spec — lands with the M4.1 follow-up that adds the full endpoint set
- [x] CHANGELOG entry under "Added"
Minimal Go service so platform/portal has something to resolve in local
dev. Stdlib net/http with Go 1.22 enhanced ServeMux (method+path
patterns); no third-party deps yet.
Layout:
cmd/server/main.go entry point with graceful shutdown
internal/config/ env-driven config (APP_ENV, ADDR, KC issuer)
internal/server/ http handlers + request-logging middleware
internal/store/memory.go in-memory tenant store, seeded with acme
migrations/0001_init.up.sql schema for the M4.1 follow-up (unapplied)
Makefile dev/test/build/lint/docker targets
Dockerfile multi-stage distroless build
Endpoints (under :8080 in dev):
GET /healthz
GET /v1/tenants/by-slug/{slug} 200 acme | 404
GET /v1/tenants/{id} 200 by uuid | 404
JWT validation and the real Postgres-backed store land in the M4.1
follow-up PR — keeping this PR strictly to 'boots, replies, tests pass'.
Refs: M4.1 (skeleton)
CODEOWNERS rules
requested review from Benjamin_Boenisch 2026-05-18 20:41:19 +00:00
act_runner doesn't reliably evaluate hashFiles() at job-level if:
conditions, so the gate skipped the test job even with the Go
sources committed. tenant-registry has Go source from day one — let
test always run.
Refs: M4.1
The runner's golangci-lint binary is built with Go 1.24 and refuses
to lint modules targeting a higher Go version ('the Go language
version (go1.24) used to build golangci-lint is lower than the
targeted Go version (1.25)'). 1.24 is current stable and covers
everything the skeleton uses (slog, ServeMux method routing).
Dockerfile pinned to golang:1.24-alpine to match.
Refs: M4.1
CI's coverage gate is 70% (line-level, package average per the
matrix). The skeleton only had handler tests so most files at 0% =
65% average and CI red.
Adds:
internal/store/memory_test.go seeded fixture, ErrNotFound paths,
pointer-copy isolation check
internal/config/config_test.go defaults, overrides, invalid APP_ENV
Refs: M4.1
Two-step fix:
- setup-go was pinned to 1.22; bump to 1.24 to match go.mod and
unblock the golangci-lint version mismatch path consistently.
- Coverage gate runs over ./internal/... so cmd/server's signal
handling + Listen path doesn't drag the line-pct under 70%. The
internal/* packages all sit ≥ 70% individually; total is 78.6%.
Real e2e tests land with M4.1.
Refs: M4.1
sharang
merged commit af9f331781 into main2026-05-19 09:35:04 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
:8080in dev):GET /healthz→{"status":"ok"}GET /v1/tenants/by-slug/{slug}→ seededacmetenant or 404GET /v1/tenants/{id}→ same by UUID or 404acme) matching the seed user in the dev-stack Keycloak realm.migrations/0001_init.up.sqllands the full M4.1 schema for review but is unapplied by the skeleton.Why
The
portalmiddleware needs to call this service to resolve<slug>.localhost:3000→ tenant during the OIDC redirect dance. Without a running tenant-registry, portal can't even render/[slug]/dashboardin dev. Skeleton-mode unblocks portal work today; full pg-backed CRUD + JWT validation + audit endpoints land in the M4.1 follow-up.Linked milestone: M4.1 (skeleton — full implementation in follow-up)
How
cmd/server/main.go— graceful-shutdown server, slog JSON logger.internal/config/— env-driven config (APP_ENV,ADDR,KEYCLOAK_ISSUER,DATABASE_URL).internal/server/— handlers + a tiny request-logging middleware. Stdlib mux with Go 1.22 method routing.internal/store/memory.go— interface-shaped so the M4.1 follow-up swaps it forpgxwithout touching handlers. Pre-seeded with one tenant.httptest.Server: healthz, by-slug acme, by-slug 404.gcr.io/distroless/static-debian12:nonroot— final image is ~10 MB; CGO disabled.Test plan
go test -race ./...✅ (3 tests in internal/server)go vet ./...cleangofmt -l .emptygo build✅ (8.4 MB static binary)acme.localhost:3000, observe portal callingtenant-registry /v1/tenants/by-slug/acmeRisk
Blast radius: dev only. Service has no production deployment yet (the manifest stub in
orca-platform/manifests/vm-control/tenant-registry.tomlstill saysplaceholderimage).What could break:
/v1/tenants/*is open. Fine for dev/loopback; must not ship to stage without the JWT middleware. The M4.1 follow-up adds it.Rollback plan: revert the PR. Nothing depends on this service yet.
Checklist
.env.localgitignoredgolang-migrateMinimal Go service so platform/portal has something to resolve in local dev. Stdlib net/http with Go 1.22 enhanced ServeMux (method+path patterns); no third-party deps yet. Layout: cmd/server/main.go entry point with graceful shutdown internal/config/ env-driven config (APP_ENV, ADDR, KC issuer) internal/server/ http handlers + request-logging middleware internal/store/memory.go in-memory tenant store, seeded with acme migrations/0001_init.up.sql schema for the M4.1 follow-up (unapplied) Makefile dev/test/build/lint/docker targets Dockerfile multi-stage distroless build Endpoints (under :8080 in dev): GET /healthz GET /v1/tenants/by-slug/{slug} 200 acme | 404 GET /v1/tenants/{id} 200 by uuid | 404 JWT validation and the real Postgres-backed store land in the M4.1 follow-up PR — keeping this PR strictly to 'boots, replies, tests pass'. Refs: M4.1 (skeleton)The runner's golangci-lint binary is built with Go 1.24 and refuses to lint modules targeting a higher Go version ('the Go language version (go1.24) used to build golangci-lint is lower than the targeted Go version (1.25)'). 1.24 is current stable and covers everything the skeleton uses (slog, ServeMux method routing). Dockerfile pinned to golang:1.24-alpine to match. Refs: M4.1CI's coverage gate is 70% (line-level, package average per the matrix). The skeleton only had handler tests so most files at 0% = 65% average and CI red. Adds: internal/store/memory_test.go seeded fixture, ErrNotFound paths, pointer-copy isolation check internal/config/config_test.go defaults, overrides, invalid APP_ENV Refs: M4.1