Your AI feature has more vendors behind it than you think.
Not just the model. The thing that stores what it generated, the thing that deploys it, the thing that emails the user about it, the thing that bills them. Any one of them can be having a bad afternoon, and the difference between a mature product and a fragile one is entirely in what happens when one is.
The fragile version discovers the outage the way a user does: half-way through, with a 502 in hand and no idea which of six vendors produced it.
Name Your Dependencies
Start by making the vendors first-class:
/**
* External services Webplo depends on. Tracked individually so a single vendor
* incident can be detected and gated without guessing from a raw 502.
*/
enum Dependency: string
{
case GitHub = 'github';
case Forge = 'forge';
case Stripe = 'stripe';
case OpenAI = 'openai';
case Fastly = 'fastly';
case Email = 'email';
case Registrar = 'registrar';
}
An enum, not strings scattered through error handlers. Now "is the model provider up?" is a question the application can hold an answer to, rather than something inferred from the shape of the last exception.
Without this you are guessing from a 502. With it, health is a fact you record when a call fails and consult before making the next one.
Name What the User Loses
The important half is the second enum, because a merchant does not care which vendor is down:
/**
* Merchant-facing capabilities, each backed by one or more Dependencys. Flows
* gate on these and the UI reports on these — always in Webplo's own terms,
* never the underlying vendor.
*/
enum Capability: string
{
case CreateSite = 'create_site';
case Publish = 'publish';
case ServeSite = 'serve_site';
case SignUp = 'sign_up';
case Billing = 'billing';
public function dependencies(): array
{
return match ($this) {
self::CreateSite => [Dependency::GitHub, Dependency::Forge, Dependency::OpenAI],
self::Publish => [Dependency::GitHub, Dependency::Forge],
self::ServeSite => [Dependency::Fastly],
self::SignUp => [Dependency::Email],
self::Billing => [Dependency::Stripe],
};
}
}
Two layers, deliberately. Dependencies are engineering names; capabilities are product names, and the map between them is a single match.
That map is worth more than it looks. It answers, instantly and without a meeting: the model provider is down — what can our customers still do? Publishing an edit still works, because publishing does not need the model. Serving existing sites is untouched. Only creating a new site is blocked. That is a precise, defensible answer that would otherwise be produced by three people guessing in a channel.
A capability is available only when every dependency it needs is healthy. Creating a site needs three; any one of them takes it down.