CatalogGuard
Exports `CatalogGuard`, `CatalogGuardLive`.
CatalogGuard
Kind: Primitive
The guard that keeps the log honest about money. A Subscribe,
ChangeTier, StartTrial or ChangePlans carries the allowances and the
price it will record, because decide is pure and cannot look them up. A
caller that omits the price, or sends allowances its tier does not grant, is
refused with CatalogMismatch instead of writing a standing that can never
be reconciled against an invoice. A plan set is judged move by move, and a
cancellation’s fallback is judged like the plan it moves to.
A plan the catalog declares with custom: true is the one exception, and it
is the catalogue’s choice rather than the caller’s (ADR 0041): its grants
are a default, so a bespoke set is accepted when every item is one the
catalog could have declared for that plan. Its price may likewise be the
negotiated price the command states, because a custom plan is where a
deployment opted into deals the catalog cannot enumerate (ADR 0042 §3). The
credit terms are still compared exactly for a custom plan, so a bespoke deal
changes what a customer may use and pay, never the pool or the rates the
deployment’s catalog states.
A catalog-owning sender — Paykit.Subscribe, the webhook translator — builds
the command from CatalogService and never needs this guard. It exists for
the surfaces where the caller is not the catalog’s owner: HTTP, an MCP tool,
a CLI.
Guarding a catalog-derived command
export class Subscribe extends Domain.Command("Subscribe", {
target: AccountId,
input: { tier: Schema.String, allowances: Allowances, price: Schema.optionalKey(Price) },
events: [SubscriptionActivated],
rejects: [PlanNotInCatalog, CurrencyMismatch, CatalogMismatch],
middleware: [Session, CatalogGuard],
}) {}
CatalogGuardLive
Kind: Layer
The guard’s Layer: it reads the catalog the deployment validated and refuses what disagrees with it, whether the command carries one plan or a plan set.