This repository is public. raw/README.md already documented a sanitisation rule for the Retool exports, but that pass matched on the SHAPE of a value, so content that looks like ordinary prose went straight through. Staff identity. moderator-id-mapping.md paired moderation team members' real names with their Civitai user ids and usernames, and user-lookup-v2.json gated features on current_user.fullName === a real name, so the authorization model itself was written in names. The table now lives in the private infra repo; the public doc keeps the coverage figures and the backfill method and points there. Export literals became __MODERATOR_A__..__MODERATOR_C__, underscore-delimited because bare MODERATOR_A is a strict prefix of the live MODERATOR_APP_URL env var. One staff member had all three legs of a pseudonym-to-identity linkage present in this repo -- pseudonym, userId in the retained moderator id set, and real name in a planning doc. That one was reconstructible from repo content alone; the display name is dropped. Other staff mentions complete no linkage and stand. End-user identifiers. bulk-ban carried four real banned users' IP addresses and five real account ids. Now RFC5737 addresses and <accountId>. Internal service names. Comments, .env.examples and test fixtures named real in-cluster services and two node hostnames. Every namespace token is now clear. Deliberately unchanged: five executed fallbacks, verify-runner as an application identifier, stub-oidc as a local e2e test double, and cnpg-database in immutable Prisma migrations. Four adversarial audit rounds. The redactions verified clean every round; the prose describing them needed four public corrections, including one that misattributed which moderator was exposed and one that misdiagnosed why a survivor survived -- it was a term-list defect, not the line wrap I claimed, so the guard I added protected against the wrong mechanism. Corrections are on the PR. These files were already committed and pushed, so this narrows further exposure rather than undoing it. Nothing here is a credential. The history decision -- leave it, treat the content as disclosed -- is recorded on the tracking ticket. Verified: 168 tests across four suites (run with --project; without it vitest silently runs three files and still reports success), 11/11 raw exports parse, byte deltas match the replacements arithmetically.
@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
(a tag webhook in the private infra repo) → buildkit builds this
Dockerfile → pushes ghcr.io/civitai/civitai-orchestrator-gateway:X.Y.Z → Flux ImagePolicy picks it up.