* feat(orchestrator-api): P0 skeleton — Fastify+tRPC service (health/metrics/authed ping)
Phase 0 of the orchestrator generation-API spin-out (strangler-fig). Stands up a
standalone Fastify + tRPC-over-HTTP Node service in the monorepo — the future home of
the generation slice of the orchestrator subdomain — with NO generation code moved yet.
What this ships:
- apps/orchestrator-api/ — @civitai/orchestrator-api, Fastify on :3000, tsup build,
Dockerfile mirroring apps/auth/Dockerfile (multi-stage node:22-alpine, corepack pnpm,
filtered frozen install, pnpm deploy --prod runtime).
- GET /health — no-auth liveness+readiness (zero external deps → can't flap the pod).
- GET /metrics — prom-client scrape, private-by-x-forwarded-for guard (mirrors the hub).
- tRPC at /api/trpc: orchestrator.health (public) + orchestrator.ping (PROTECTED —
verifies __Secure-civ-token / Bearer LOCALLY via @civitai/auth ES256/JWKS + injected
sysRedis revocation, returns { userId }).
- Wired-but-unused clients (import + connect-config, no features): @civitai/db (Prisma
via shared pooler, lazy dynamic import), @civitai/redis (cache + sysRedis),
@civitai/client (external orchestrator SDK).
- vitest: /health 200, /metrics XFF-guarded, ping 401 unauthenticated + userId for a
valid token (mocked verifier).
Deliberately deferred (later phases): moving workflows.ts / orchestration-new.service.ts
/ ecosystems (P1/P2); the @civitai/prompt-audit + @civitai/generation-graph carve-outs
(next P0 increment, separate PR); live traffic routing (monolith still serves
/api/trpc/orchestrator — the same-origin PathPrefix cutover is P2).
Plan: datapacket-talos:claudedocs/plan-orchestrator-api-spinout-2026-06-30.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(orchestrator-gateway): rename service orchestrator-api → orchestrator-gateway
Mechanical rename of the P0 scaffolding service. No behavior change.
- apps/orchestrator-api → apps/orchestrator-gateway
- package @civitai/orchestrator-api → @civitai/orchestrator-gateway (+ imports)
- image ghcr.io/civitai/civitai-orchestrator-api → civitai-orchestrator-gateway
- tag prefix orchestrator-api-v → orchestrator-gateway-v
- prom metrics orchestrator_api_* → orchestrator_gateway_*
- service string literals + Dockerfile/tsup/vitest/README refs
tRPC router key stays `orchestrator.*` (client-facing, unchanged).
Verified: pnpm --filter @civitai/orchestrator-gateway build + test both pass
(6/6). typecheck shows only the 5 pre-existing @civitai/db-schema/@civitai/db
Prisma-not-generated-on-NixOS errors — ZERO in apps/orchestrator-gateway/src.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(orchestrator-gateway): point pnpm-lock importer at renamed app dir
The service rename (orchestrator-api → orchestrator-gateway) updated the app
dir + package name but left the pnpm-lock.yaml importer key as
apps/orchestrator-api, so CI's frozen-lockfile install failed with
ERR_PNPM_OUTDATED_LOCKFILE (15 deps 'added'). Importer key now matches the
app dir; all resolved deps were already correct.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@civitai/orchestrator-gateway
Status: P0 skeleton. A standalone Fastify + tRPC-over-HTTP Node service — the target for the generation slice of the orchestrator subdomain, spun out of the Next.js monolith (strangler-fig).
See the full plan: datapacket-talos:claudedocs/plan-orchestrator-gateway-spinout-2026-06-30.md.
Why this exists
The civitai-dp-prod-api pool is pinned at HPA min 80 as a reliability buffer because bursty orchestrator
parks (img2img whatIf/generate source-download hangs) head-of-line-block the shared api pool at the
connection layer. Moving orchestrator.* generation onto its own pod pool contains a park burst to this
service, so the api pool's tail becomes predictable, its floor can be cut, and a civitai-web node can be
drained (5→4). Success is isolation + node-drain, not raw CPU reduction.
What P0 ships (this skeleton)
- Fastify HTTP server on
:3000. GET /health— no-auth liveness+readiness (zero external deps, so a DB/redis blip can't flap the pod).GET /metrics— Prometheus scrape (prom-client), private-by-x-forwarded-forguard (mirrors the auth hub).- tRPC mounted at
/api/trpc(fetch adapter) with:orchestrator.health— public liveness echo.orchestrator.ping— protected; verifies the incoming__Secure-civ-token/Bearertoken LOCALLY via@civitai/auth(ES256/JWKS + injected sysRedis revocation, fail-open) and returns{ userId }.
- Wired-but-unused clients (construct + connect-config, no features on them yet):
@civitai/db(Prisma via the shared pooler),@civitai/redis(cache + sysRedis),@civitai/client(external orchestrator SDK).
What P0 deliberately does NOT do
- No generation code moved (
workflows.ts,orchestration-new.service.ts, ecosystems) — that's P1/P2. - No
@civitai/prompt-audit/@civitai/generation-graphcarve-outs — next P0 increment, separate PR. - No live traffic routing — the monolith still serves
/api/trpc/orchestrator. The same-originPathPrefix(/api/trpc/orchestrator)Traefik cutover is P2. In prod this is ClusterIP + ServiceMonitor only, reachable via port-forward.
Layout
src/
server.ts entrypoint (listen)
app.ts buildServer() factory — /health, /metrics, tRPC mount (testable, no listen)
trpc/
context.ts per-request ctx: local token verify → { claims, userId }
trpc.ts initTRPC, publicProcedure, protectedProcedure (auth gate)
router.ts appRouter — orchestrator.health (public) + orchestrator.ping (protected)
lib/server/
metrics.ts prom-client registry + counters/histograms
auth/
verifier.ts createAuthVerifier (spoke: JWKS + injected revocation)
registry.ts createSessionRegistry (sysRedis revocation read, fail-open no-op fallback)
clients/
redis.ts getRedis / getSysRedis (lazy, memoized)
db.ts getDb (lazy dynamic import behind DATABASE_URL guard)
orchestrator.ts createOrchestratorClient / getInternalOrchestratorClient
__tests__/
health.test.ts /health 200, /metrics served + XFF-guarded 404
ping.test.ts ping rejects unauthenticated (401), returns userId for a valid token (mocked verifier)
Local dev
cp .env.example .env # fill secrets for a real run
pnpm --filter @civitai/orchestrator-gateway dev # tsx watch
pnpm --filter @civitai/orchestrator-gateway typecheck # tsc --noEmit
pnpm --filter @civitai/orchestrator-gateway test # vitest
pnpm --filter @civitai/orchestrator-gateway build # tsup → dist/server.js
Build / release
Built by the shared Tekton tag-webhook → build-and-push pipeline (auth is its first user). Push a
git tag orchestrator-gateway-vX.Y.Z on civitai/civitai → the webhook matches the prefix in APP_CONFIG
(datapacket-talos:clusters/production/apps/tekton-builds/tag-webhook.py) → buildkit builds this
Dockerfile → pushes ghcr.io/civitai/civitai-orchestrator-gateway:X.Y.Z → Flux ImagePolicy picks it up.