Fail-closed
Fail-closed means a security check that errors, times out, or hits an unexpected state denies access by default, rather than falling through to allow it. Caisson's tenancy-rls module practices this literally: an unbound tenant, an unverified role, or an empty account id each refuse the query outright instead of running it unscoped.
In code
async function assertRoleNotPrivileged(
tx: TenantExecutor,
role: string,
): Promise<void> {
const { rows } = await tx.query<{
rolsuper: boolean;
rolbypassrls: boolean;
}>(`SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname = $1`, [role]);
const row = rows[0];
if (!row || row.rolsuper || row.rolbypassrls) {
throw new TenancyError(
`Refusing to use role "${role}" for tenant isolation: it must exist and be neither SUPERUSER nor BYPASSRLS`,
);
}
}How it holds
Refuse when uncertain, never fall through
assertRoleNotPrivileged runs a positive check: the role must exist and must not be SUPERUSER or BYPASSRLS, and it throws on any other outcome (a missing row, an unexpected privilege, a failed query). There is no default branch that proceeds; unresolved is refused.
The same posture gates money and licenses, not just rows
InsufficientCreditsError closes a request with 402 the instant a wallet cannot cover a metered call, and license-verify treats any malformed, unsigned, or unparseable token as fail-safe-to-community instead of guessing at intent. Three different surfaces share one refusal-by-default shape.
withTenant refuses an empty account id outright
The tenant-scoping wrapper throws before opening a transaction if accountId.length === 0, so a caller that forgot to resolve a tenant never runs unscoped. It errors immediately instead of quietly defaulting to some global view.
Scoped honestly: not every check in Caisson fails closed
The registry Worker's license-revocation deny-set is deliberately fail-open: if the cache backing it is unavailable, getDenied returns an empty set rather than blocking every install. Fail-closed is the posture for tenant data, credits, and license validity; availability wins on the revocation cache.