// Resolution-refusal vocabulary: the 503 payloads, retry-after math, and the // outcome labels behind resolution metrics. import { json } from "./http.js"; import type { Resolution } from "./placement.js"; export type ServeResolution = | Resolution | { kind: "not-serving" } | { kind: "no-hostname-workers" } | { kind: "binding-quarantine"; retryAfterMs: number } | { kind: "env-conflict" }; export type Refusal = Exclude< ServeResolution, { kind: "forward" } | { kind: "unknown-ns" } >; const REFUSAL_ERRORS: Record = { "no-backends": "no live backends for namespace", quarantine: "registry cold start, retry", "not-serving": "not the serving fleet", "no-hostname-workers": "no live workers for hostname", "binding-quarantine": "hostname binding displaced, retry", "env-conflict": "hostname bindings inconsistent, retry", "takeover-pending": "takeover in progress, retry", "takeover-denied": "placement held by another hostname", }; // takeover-denied rides the takeover-pending outcome; takeovers_total // distinguishes the strategies. "nack" counts in-band activation refusals. export const RESOLUTION_OUTCOMES = [ "forward", "unknown-ns", "no-backends", "quarantine", "not-serving", "no-hostname-workers", "binding-quarantine", "takeover-pending", "env-conflict", "nack", ] as const; export type ResolutionCounts = Record< (typeof RESOLUTION_OUTCOMES)[number], number >; export const createResolutionCounts = (): ResolutionCounts => Object.fromEntries( RESOLUTION_OUTCOMES.map((outcome) => [outcome, 0]), ) as ResolutionCounts; export const resolutionOutcome = ( kind: ServeResolution["kind"], ): (typeof RESOLUTION_OUTCOMES)[number] => kind === "takeover-denied" ? "takeover-pending" : kind; export const refusalResponse = (refusal: Refusal, now: number): Response => { const retryAfterSeconds = refusal.kind === "quarantine" || refusal.kind === "binding-quarantine" ? Math.max(1, Math.ceil(refusal.retryAfterMs / 1000)) : refusal.kind === "takeover-pending" ? Math.max(1, Math.ceil((refusal.fenceExpiry - now) / 1000)) : 1; return json( 503, { error: REFUSAL_ERRORS[refusal.kind] }, { "retry-after": String(retryAfterSeconds) }, ); };