The system caller

Version: 1

The system endpoints (/api/v1/system/*, the automations drain tick first) are called by an installation's own infrastructure: a scheduler, a push subscription, an operator's script. Who may call them is decided by system caller verifiers, asked in turn for each request. The first that accepts decides the caller; when none does, the request is refused 403, its body naming each verifier's reason, and the refusal is logged at most once a minute per caller address and verifier.

Two verifiers are the ones a host is expected to ship, and a host chooses among them by its own configuration (lf-platform does it with SYSTEM_AUTH_MODES, described in its self-hosting runbook):

An embedder adds its own before the server serves (lf-platform: registerSystemCallerVerifier, in apps/api/src/services/system_auth/). Registering any verifier other than api_key, an embedder's included, turns on the deferral described under the shape below. This document is the interface it implements: an in-process one, so there is nothing on the wire to carry a version; the version is what an embedder's verifier states it was written against.

Versions #

The shape #

The shapes are TypeScript-ish: x?: T may be absent.

interface SystemCallerVerifier {
  name: string;                      // names it in refusals and logs; unique among those registered
  verify(request: SystemCallerRequest): Promise<SystemCallerVerdict>;
}

interface SystemCallerRequest {
  method: string;                    // `POST`
  path: string;                      // the full path, `/api/v1/system/automations/drain`
  headers: Record<string, string | string[] | undefined>;  // the raw headers, names lower case: every credential the request carries is here
  hostCredential?: HostCredential;   // host-specific, other verifiers ignore it: the API key the host's own authentication verified
}

interface HostCredential {
  id?: string;                       // the key's id
  scopes: string[];                  // the key's scopes
}

type SystemCallerVerdict =
  | { ok: true; principal: SystemCallerPrincipal; actions: SystemAction[] }
  | { ok: false; reason: string };   // shown to the caller and logged: fixed words, never a token, a secret or anything the request said

interface SystemCallerPrincipal {
  id: string;                        // who called: the token's subject, the key's id
  display?: string;                  // a readable name, such as a service account's email
}

type SystemAction = 'drain';         // run a drain pass: POST /api/v1/system/automations/drain

Changes #