← Back to part 1
Case study · Part 2 · Observability & contracts
The Gateway That Learned to Say No
Degradation as a first-class contract
A fallback ladder keeps the AI answering, but it can’t tell
you whether the answer is right. This part makes
degradation an explicit contract: every response carries its tier
and confidence in a type callers can’t ignore, prompts and
routing live in Redis so a rollback is a pointer flip instead of a
redeploy, cost comes from our own price table, and a fourth tier
validates domain invariants after every “successful”
call.
05
Implementation
LlmResponse.ts · TypeScript / NestJS
export type LlmTier = 'retry' | 'rag' | 'floor';
export type Confidence = 'high' | 'medium' | 'low';
export type LlmResponse<T> =
| { tier: 'retry'; degraded: false; confidence: 'high'; content: T;
providerMeta: ProviderMeta; invariants: InvariantReport }
| {
tier: 'rag' | 'floor';
degraded: true;
confidence: 'medium' | 'low';
readonly _content: T;
providerMeta: ProviderMeta;
invariants: InvariantReport;
acceptDegraded(): T;
};
/**
* Degraded responses do not expose `.content` at all. The payload is only
* returned after `acceptDegraded()` runs the invariant check and audit log.
*/
export function acceptDegraded<T>(res: LlmResponse<T>): T {
if (res.degraded) {
if (!res.invariants.passed) {
throw new DegradedInvariantViolation(res.invariants.violated);
}
logger.warn('degraded_accept', {
tier: res.tier,
confidence: res.confidence,
violations: res.invariants.violated,
});
return res._content;
}
return res.content;
}
// Gateway side — invariant validation runs AFTER a Tier-1 success.
const raw = await this.dispatchPrimaryLLM(req, tenantContext);
const invariants = this.invariantEngine.check(raw, req.domainRules);
if (!invariants.passed) {
// Tier 1 "succeeded" but violated a domain rule. Downgrade.
return this.ruleEngine.extractHeuristicData(req.rawDocument, 'floor', invariants);
}
return { tier: 'retry', degraded: false, confidence: 'high', content: raw, providerMeta, invariants };