Blog

When a missing provider must deny

2026-07-30

An isometric illustration of a round no-entry road sign on a short post — a solid disc with a single white bar across it

Two seams sit next to each other in core. They look almost identical: a contract, a resolver, and a provider that fills it if you composed one.

One of them, when nothing is composed, quietly does nothing. The other denies everything.

That difference is not an inconsistency. It is the most important line in either file.

The seam pattern

Capabilities in the kit never import each other. When one needs something another provides, the contract lives in core and both sides depend on that.

Audit works this way. Core owns what an audit event is and how to record one. The audit capability is the provider. Payments records an entitlement grant without importing audit at all.

Entitlements work the same way. Core owns what an entitlement is, how a request resolves the caller’s, and the read-time rule that decides whether one grants access. It owns no payments logic — payments is the provider that fills the seam. Any capability gating on a paid feature depends on the seam in core, never on the payments package.

Same shape, twice.

Now the defaults

Compose nothing, and:

Audit’s emit() is a no-op. No provider, no write, no error. Your code calls it and nothing happens.

That is safe, and the source says why in six words: a missing audit write cannot grant anyone access. The worst case is a record you did not keep. Nobody gets in who should not.

Every requireEntitlement() denies. No provider means the resolver produces nothing, and nothing grants access to anything.

Because an entitlement check is a gate. If a missing provider made it a no-op — the same behavior as audit, applied consistently — then removing a package from your config would silently unlock every paid feature in your product.

The consistent choice is the catastrophic one. So the seams differ, deliberately, and the difference is a function of what the seam is for rather than of how it is built.

Failing closed is the backstop, not the plan

There is a second half to this that I think is the more mature idea.

A Worker whose routes gate on entitlements while composing no provider is broken. Denying every request is the correct behavior, but it is not a good outcome — your paying customers are locked out and you find out from support tickets.

So runtime denial is the backstop. The primary defense is that pithy doctor reports it as a composition error, in your terminal, before it ships.

The source is explicit about this. Runtime denial is the backstop; the composition error is what an adopter is meant to see, rather than discovering the problem as production 403s.

Fail closed and tell them early. Either alone is worse: fail closed with no warning and you have shipped an outage safely; warn without failing closed and the warning is advisory while the hole is real.

What an entitlement actually is

Worth stating, since the seam is the subject.

An entitlement is a keypro — not a product and not a purchase. It lives in your own D1, like everything else here. Payments resolves four rails’ SKUs down to keys, and gating code names the key.

That is what lets the seam be this thin. Core does not need to know what Apple’s receipt format looks like, or that Lemon Squeezy is a merchant of record, or that a subscription can renew late. It needs to answer one question: does this caller hold this key right now.

Why this is worth reading if you are not using the kit

Because the pattern generalizes and the choice is easy to get wrong.

Any time you build a plugin seam, you pick a default for the unplugged case. The instinct is consistency — make them all behave the same, document it, move on.

The right question is not “what is consistent” but “what does this seam do when it works?” A seam that records something can safely do nothing. A seam that decides something must refuse.

Get that backwards once, in the direction that matters, and the failure is not a bug report. It is every paid feature in your product, free, until somebody notices.