Skip to content

Platform Operations

Release publication gates, database connection pools, upgrade acceptance and API process diagnostics. Read this chapter when changing how Preloop is built, released, pooled or started. Deployment guides live under docs/operations/.

Release publication gate

Version-tag publication is gated by image SBOM and release audit validation. Backend, console, scanner and universal images carry inventories for both architectures, bound to immutable image digests and the source commit. The gate waits for the same tag's image and security workflows before publishing release assets.

Database connection pools

Credential checks use a dedicated synchronous auth pool alongside the sync and async request pools and the health engine. The auth pool adds its configured base size to each API/gateway process's retained connection budget, and base plus overflow to its ceiling. Helm values include this contribution; the pool monitor reports it as auth. Bounded non-machine classification caching never replaces route authentication; restricted CI operations documents its freshness and database-error fallback windows. Async session teardown has a single shielded cleanup owner so caller cancellation cannot interrupt rollback/close or start a concurrent close on the same session.

Upgrade acceptance harness

The upgrade acceptance harness runs published release initializers and Helm hooks against a synthetic corpus in disposable local stacks. It stops old writers, preserves encryption settings, and verifies data, identity and credential continuity. Candidate rehearsals remain separate from acceptance of an actually published release candidate.

API diagnostics

The API entry point enables Python fatal-error tracebacks on stderr (including spawned Uvicorn reload/worker interpreters via PYTHONFAULTHANDLER) before application startup so native faults retain thread-stack evidence in container logs. These traces do not include frame locals. Setting SENTRY_DSN keeps error reporting and transaction tracing enabled, but background stack profiling is off by default. Operators may explicitly set SENTRY_PROFILES_SAMPLE_RATE to a finite value from 0 to 1 after validating profiling with their runtime and native extensions. Invalid values disable profiling and log a configuration warning. Profiling is independent of the request-level PROFILING_ENABLED middleware option.

API liveness-triggered restarts use a separate 60-second termination grace (api.livenessTerminationGracePeriodSeconds, including the preStop sleep). Planned rollouts retain the 900-second stream drain. This prevents an unhealthy replica from spending the full rollout window draining before replacement. The chart renders probe-level grace on Kubernetes 1.28 or newer; older clusters retain the existing pod-level grace behavior.

Request rate limiting

services/rate_limit.py is the one request limiter. Every rule is a fixed window per identity (client address, account and address, approval request, API key or user), stored in the rate_limit_bucket table so all API replicas share one budget and a restart does not reset it. When the table cannot be reached, the same rule is enforced per process and an error is logged, so a limiter outage neither refuses nor unthrottles requests. Refusals are 429 with Retry-After and count in preloop_rate_limit_hits_total{rule}. JSON API refusals also carry the rate_limited code; OTLP ingest keeps the OTLP status envelope its exporters parse.

Rules on the shared limiter: login, refresh, reset-password, register, OAuth token/register/authorize, public approval, passkey challenges, verification resend and forgot-password, OTLP ingest and admin test pushes. No request rate limit keeps a per-process counter; in-flight concurrency caps (heavy routes, duplicate checks) are a separate mechanism. Operator notes and desktop views are capped by counting their own database rows, which is already shared. Rules, defaults and settings: operations/rate-limiting.md.