# `limits/timeout-ceiling` ## Purpose Enforces the hard 30 000 ms ceiling on caller-supplied `timeoutMs` values. The check fires at the public entry point of `run2pc` so above-ceiling calls never contact a participant; the operational warning in design doc §9.2 is that long prepares hold row-level locks, so surfacing the misuse loudly is more useful than silently clamping. This module is intentionally tiny — one function — because the behaviour is small but the gate is one of the most-asserted contracts in the spec. ## Features - `assertTimeoutBelowCeiling(timeoutMs)` — throws `TWOPC_TIMEOUT_CEILING_EXCEEDED` for above-ceiling, non-positive, non-finite, or `NaN` inputs. - Re-uses the `TIMEOUT_CEILING_MS` constant from `options/` so the ceiling stays a single source of truth. ## Usage: Basic ```ts import { assertTimeoutBelowCeiling } from "@suaveplan/api-2pc/limits"; assertTimeoutBelowCeiling(5000); // ok assertTimeoutBelowCeiling(30001); // throws TWOPC_TIMEOUT_CEILING_EXCEEDED ``` ## Usage: Advanced The function is intentionally side-effect-free so callers can wrap it in a per-tenant override path: ```ts import { TIMEOUT_CEILING_MS } from "@suaveplan/api-2pc/options"; import { assertTimeoutBelowCeiling } from "@suaveplan/api-2pc/limits"; function resolveTimeout( requested: number | undefined, tenantMax: number ): number { const effective = requested ?? DEFAULT_TIMEOUT_MS; assertTimeoutBelowCeiling(effective); if (effective > tenantMax) { return tenantMax; } if (effective > TIMEOUT_CEILING_MS) { // unreachable given the assert above — included only to // illustrate that the ceiling acts as the strict upper bound. return TIMEOUT_CEILING_MS; } return effective; } ``` ## API Reference - `function assertTimeoutBelowCeiling(timeoutMs: number): void` - Throws `TwoPcError(TWOPC_CODES.TIMEOUT_CEILING_EXCEEDED)` for any of: `NaN`, `Infinity`, `-Infinity`, values `<= 0`, values `> TIMEOUT_CEILING_MS`. ## Implementation Notes The check rejects non-positive values for the same reason it rejects above-ceiling ones: an immediate or backwards-in-time deadline would collapse phase 1 to a single tick and the rollback path would fire before any participant could finish a real prepare. Treating that as the same misuse class as "30 minutes" is a deliberate ergonomic choice — operators get one error code to grep for. `Number.isFinite` is used in preference to `Number.isNaN` because the former covers `Infinity` and `-Infinity` in addition to `NaN`. The explicit `<= 0` check then excludes zero and negative-finite values.