// The registry HTTP surface: authentication, account-policy scope checks, // and the /registry/* route handlers. import type { Registry } from "./registry.js"; import type { createPinboard } from "./server.js"; import type { Passport } from "@assistant-ui/passport"; import { hostAllowed, namespaceAllowed, registryPolicy, type RegistryPolicy, } from "./auth.js"; import { validateNamespace } from "./router.js"; import { validateWorldSegment } from "./registry-bodies.js"; import { collectWorkerStats } from "./stats.js"; import { json, MAX_REGISTER_BODY_BYTES, readBodyOrReject } from "./http.js"; import { refusalResponse } from "./refusals.js"; import { logger } from "./logger.js"; import type { Metrics } from "./metrics.js"; import { passportUnauthorizedReason } from "./passport.js"; import type { Transport } from "./proxy.js"; type RegistryRole = "pinboard:operator" | "pinboard:worker"; export const authenticateRegistry = async ( passport: Passport.Instance, hostnameAware: boolean, req: Request, role: RegistryRole, ): Promise => { let account: Passport.Principal | null; try { account = await passport.validate(req); } catch (error) { const reason = passportUnauthorizedReason(error); if (reason === null) throw error; logger.warn( { operation: "auth", endpoint: req.url, reason }, "registry credential rejected", ); return json(401, { error: "unauthorized" }); } if (account === null) return json(401, { error: "unauthorized" }); if (!account.scopes.includes(role)) { logger.warn( { operation: "auth", account: account.sub, endpoint: req.url, role }, "registry role rejected", ); return json(403, { error: "forbidden" }); } try { return registryPolicy(account, role, hostnameAware); } catch (error) { logger.warn( { operation: "auth", account: account.sub, endpoint: req.url, error: String(error), }, "registry policy rejected", ); return json(403, { error: "forbidden" }); } }; // Best-effort worker identity from a register body that may be malformed. const registerIdentity = ( body: unknown, ): { hostname?: string; advertiseUrl?: string } => { if (typeof body !== "object" || body === null) return {}; const { hostname, advertiseUrl } = body as Record; return { ...(typeof hostname === "string" && { hostname }), ...(typeof advertiseUrl === "string" && { advertiseUrl }), }; }; export namespace createRegistryApi { export type Deps = { registry: Registry; passport: Passport.Instance; hostnameAware: boolean; metrics: Metrics.Instance; pool?: ((hostname: string) => string) | undefined; serving(): Promise; now(): number; health(): Promise; fetch: Transport.FetchLike; }; } export const createRegistryApi = ( deps: createRegistryApi.Deps, ): ((req: Request, pathname: string) => Promise) => { const { registry, passport, hostnameAware, metrics, pool, serving, now, health, fetch, } = deps; // Pre-checks the account policy against the register body; malformed // fields fall through to registry.register's own 400. const registerScopeError = ( entry: RegistryPolicy, body: unknown, ): string | null => { if (typeof body !== "object" || body === null) return null; const { namespaces, advertiseUrl } = body as Record; if ( Array.isArray(namespaces) && namespaces.every((value) => typeof value === "string") ) { try { const denied = namespaces .map(validateNamespace) .find((ns) => !namespaceAllowed(entry, ns)); if (denied !== undefined) return `namespace not allowed: ${denied}`; } catch { return null; } } if (typeof advertiseUrl === "string") { try { const { hostname } = new URL(advertiseUrl); if (!hostAllowed(entry.backendHosts, hostname)) { return `backend host not allowed: ${hostname}`; } } catch { return null; } } if (hostnameAware) { const { hostname, env, envs } = body as Record; if (typeof hostname === "string" && hostname !== "") { if (!hostAllowed(entry.hostnames, hostname)) { return `hostname not allowed: ${hostname}`; } const effectiveEnv = typeof env === "string" ? env : hostname; if (!hostAllowed(entry.envs, effectiveEnv)) { return `env not allowed: ${effectiveEnv}`; } if (typeof envs === "object" && envs !== null) { for (const value of Object.values(envs as Record)) { if (typeof value === "string" && !hostAllowed(entry.envs, value)) { return `env not allowed: ${value}`; } } } } } return null; }; const namespaceScopeError = ( entry: RegistryPolicy, body: unknown, ): string | null => { if (typeof body !== "object" || body === null) return null; const { namespace, env } = body as Record; if (typeof namespace !== "string") return null; try { const ns = validateNamespace(namespace); if (!namespaceAllowed(entry, ns)) return `namespace not allowed: ${ns}`; } catch { return null; } if (hostnameAware && typeof env === "string") { try { const value = validateWorldSegment("env", env); if (authHostAllowed(entry.envs, value)) { return null; } return `env not allowed: ${value}`; } catch { return null; } } return null; }; const authHostAllowed = (rules: RegistryPolicy["hostnames"], value: string) => rules.length === 0 || hostAllowed(rules, value); const authorization = (entry: RegistryPolicy, owner: boolean) => ({ ...(owner && { account: entry.name }), namespaceAllowed: (namespace: string) => namespaceAllowed(entry, namespace), worldAllowed: (hostname: string, env: string) => !hostnameAware || (authHostAllowed(entry.hostnames, hostname) && authHostAllowed(entry.envs, env)), }); const nackResponse = (result: { error: string; nack?: "reregister" | "superseded"; forbidden?: true; }): Response => result.forbidden === true ? json(403, { error: "forbidden" }) : result.nack === "reregister" ? json(409, { error: result.error, reregister: true }) : result.nack === "superseded" ? json(409, { error: result.error, superseded: true }) : json(400, { error: result.error }); const OPERATOR_ROUTES = new Set([ "/registry/workers", "/registry/overview", "/registry/stats", "/registry/health", "/registry/wind_down", "/registry/drain", "/registry/undrain", ]); const handleRegistry = async ( req: Request, pathname: string, ): Promise => { const auth = await authenticateRegistry( passport, hostnameAware, req, OPERATOR_ROUTES.has(pathname) ? "pinboard:operator" : "pinboard:worker", ); if (auth instanceof Response) return auth; const entry = auth; if (pathname === "/registry/health" && req.method === "GET") { return json(200, await health()); } if (!(await serving())) { return refusalResponse({ kind: "not-serving" }, now()); } if (pathname === "/registry/workers" && req.method === "GET") { return json(200, { workers: await registry.listWorkers(authorization(entry, false)), }); } if (pathname === "/registry/overview" && req.method === "GET") { return json(200, await registry.overview(authorization(entry, false))); } if (pathname === "/registry/stats" && req.method === "GET") { return json(200, { workers: await collectWorkerStats( await registry.liveWorkers(authorization(entry, false)), now(), fetch, ), }); } if (pathname === "/registry/register" && req.method === "POST") { const parsed = await readBodyOrReject(req, MAX_REGISTER_BODY_BYTES); if ("response" in parsed) return parsed.response; const scopeError = registerScopeError(entry, parsed.body); const identity = registerIdentity(parsed.body); const rejection = (reason: string) => ({ account: entry.name, pool: pool !== undefined && identity.hostname !== undefined ? pool(identity.hostname) : "", reason, }); if (scopeError !== null) { metrics.registerRejectionsTotal.inc(rejection("policy")); logger.warn( { operation: "register", entry: entry.name, ...identity, error: scopeError, }, "worker registration out of account policy", ); return json(403, { error: scopeError }); } const result = await registry.register(parsed.body, entry.name); if (!result.ok) { if (result.unavailable === true) { metrics.registerRejectionsTotal.inc(rejection("unavailable")); logger.warn( { operation: "register", entry: entry.name, ...identity, error: result.error, }, "worker registration deferred, advertise URL unavailable", ); return json(503, { error: result.error }, { "retry-after": "1" }); } metrics.registerRejectionsTotal.inc( rejection(result.conflict === true ? "conflict" : "invalid"), ); logger.warn( { operation: "register", entry: entry.name, ...identity, error: result.error, }, "worker registration rejected", ); return json(result.conflict === true ? 409 : 400, { error: result.error, }); } logger.info( { operation: "register", sessionId: result.sessionId, hostname: result.hostname, envs: result.envs, reclaimed: result.reclaimed, duplicates: result.duplicates.length, }, "worker registered", ); return json(200, { sessionId: result.sessionId, expiresInMs: result.expiresInMs, draining: result.draining, ...(result.drainDeadline !== undefined && { drainDeadline: result.drainDeadline, }), ...(result.duplicates.length > 0 && { duplicates: result.duplicates }), }); } if (pathname === "/registry/heartbeat" && req.method === "POST") { const parsed = await readBodyOrReject(req); if ("response" in parsed) return parsed.response; const result = await registry.heartbeat( parsed.body, authorization(entry, true), ); if (!result.ok) { if (result.voided !== undefined) { metrics.degradedHeartbeatsTotal.inc({ reason: "mismatch" }); logger.error( { operation: "heartbeat", advertiseUrl: result.voided.advertiseUrl, error: result.error, }, "registration voided: the advertise URL answered another worker's challenge — " + "likely a load balancer or shared URL; it must route to exactly this worker", ); } else if (result.nack !== undefined) { logger.warn( { operation: "heartbeat", nack: result.nack, error: result.error }, "heartbeat nacked", ); } return nackResponse(result); } if (result.degraded !== undefined) { metrics.degradedHeartbeatsTotal.inc({ reason: result.degraded.reason }); logger.warn( { operation: "heartbeat", reason: result.degraded.reason, error: result.degraded.error, }, "worker degraded: challenge probe failed", ); } return json(200, { expiresInMs: result.expiresInMs, draining: result.draining, ...(result.drainDeadline !== undefined && { drainDeadline: result.drainDeadline, }), ...(result.degraded !== undefined && { degraded: true }), ...(result.messages.length > 0 && { messages: result.messages }), }); } if (pathname === "/registry/release" && req.method === "POST") { const parsed = await readBodyOrReject(req); if ("response" in parsed) return parsed.response; const result = await registry.release( parsed.body, authorization(entry, true), ); if (!result.ok) return nackResponse(result); logger.info( { operation: "release", released: result.released }, "placement released", ); return json(200, { released: result.released }); } if (pathname === "/registry/deregister" && req.method === "POST") { const parsed = await readBodyOrReject(req); if ("response" in parsed) return parsed.response; const result = await registry.deregister( parsed.body, authorization(entry, true), ); if (!result.ok) return nackResponse(result); logger.info({ operation: "deregister" }, "session deregistered"); return json(200, {}); } if (pathname === "/registry/wind_down" && req.method === "POST") { const parsed = await readBodyOrReject(req); if ("response" in parsed) return parsed.response; const scopeError = namespaceScopeError(entry, parsed.body); if (scopeError !== null) { logger.warn( { operation: "wind_down", entry: entry.name, error: scopeError }, "operator namespace rejected", ); return json(403, { error: scopeError }); } const result = await registry.windDown( parsed.body, authorization(entry, false), ); if (!result.ok) return nackResponse(result); const target = parsed.body as { namespace: string; id?: string; deadline: number | null; }; logger.info( { operation: "wind_down", namespace: target.namespace, id: target.id, deadline: target.deadline, queued: result.queued, }, "wind-down queued", ); return json(200, { queued: result.queued }); } if (pathname === "/registry/drain" && req.method === "POST") { const parsed = await readBodyOrReject(req); if ("response" in parsed) return parsed.response; const result = await registry.drain( parsed.body, authorization(entry, false), ); if (!result.ok) return nackResponse(result); const target = parsed.body as { clientId: string; deadline: number | null; }; logger.info( { operation: "drain", clientId: target.clientId, deadline: target.deadline, }, "client drain set", ); return json(200, { drained: true }); } if (pathname === "/registry/undrain" && req.method === "POST") { const parsed = await readBodyOrReject(req); if ("response" in parsed) return parsed.response; const result = await registry.undrain( parsed.body, authorization(entry, false), ); if (!result.ok) return nackResponse(result); const target = parsed.body as { clientId: string }; logger.info( { operation: "undrain", clientId: target.clientId, revoked: result.revoked, }, "client drain revoked", ); return json(200, { revoked: result.revoked }); } return json(404, { error: "not found" }); }; return handleRegistry; };