Commercial subscription state is authoritative for entitlement grants, access projection is derived and revocable on separate surfaces, and cache plus background jobs must converge within bounded latency when payment or plan changes revoke feature access.
Package status: AI-assisted reference package. Static package validation has passed; implementation scenarios remain not run and human domain review is required before stable adoption.
Decision
The billing/subscription ledger remains the sole authority for whether a tenant or user holds a commercial entitlement. Application feature gates read from an access projection derived from entitlement events carrying source_event_id and idempotency_key, never directly from payment-provider webhooks. Grants may be cached up to cache_grant_ttl_seconds, but a revocation updates projection truth and its entitlement version in one transaction. Gates accept a cached grant only when its version matches the current readable version; an unavailable version check fails closed.
Scope
- Entitlement grant and revocation events, access projection store, API middleware checks, cache keys, and background job capability guards.
- Plan upgrades, downgrades, trial expiry, chargeback-driven suspension, and manual operator adjustments that affect feature access.
Outside this block
- Payment capture, invoicing, and tax calculation.
- Marketing feature flags unrelated to paid entitlements.
- Detailed subscription accounting journal entries and revenue recognition rules.
Contract
- Every entitlement event includes source_event_id, idempotency_key, subject_id, and occurred_at per entitlement-events.yaml.
- No feature gate reads payment-provider objects directly; it reads projection rows keyed by subject_id and entitlement_code with source_event_id.
- After revocation event persistence, API denial occurs within max_projection_lag_seconds under normal load.
- cache_revoke_invalidation immediate-version-bump updates the projection and version atomically on revoke, invalidating cached grants even when cache_ttl_seconds is greater than max_projection_lag_seconds.
- Deny checks use projection when deny_checks_projection_not_cache is true; cache hits require a version read and an exact version match. Missing, stale, or unavailable version evidence denies access.
- Duplicate events with the same idempotency_key and source_event_id do not double-extend expiry or duplicate projection rows.
- Background jobs re-check entitlements at run time when background_job_recheck is true.
Implementation guidance
- Implement projection updates as idempotent consumers of entitlement-events.yaml vocabulary.
- Bump entitlement version in the same transaction as every projection revoke and include version in cache keys.
- Expose internal diagnostics showing last applied source_event_id per subject for support without exposing billing ledger credentials to application tiers.
- Document which HTTP routes, workers, and admin tools consult projection versus billing read APIs for access audits.
Failure handling and safeguards
- Projection lag beyond threshold fails closed for grant checks: deny premium access rather than allow stale grants.
- Failure to read the current entitlement version fails closed; cached grants are not treated as proof on their own.
- Chargeback suspension revokes all paid entitlements in one atomic projection update per profile rules.
- Background job mid-flight checks entitlement at checkpoint boundaries for long-running work.
Verification and operations
- Tests simulate downgrade and measure denial latency across API and cache paths after revoke with cache_grant_ttl_seconds still active.
- Metrics track projection lag, stale-cache hits prevented, cache version bumps on revoke, and job skips due to revocation.
- Alert when convergence SLA breaches after subscription state changes.
Adoption assumptions
- Names and numeric values in example.yaml and entitlement-events.yaml are an adoption profile, not universal defaults.
- The adopting team maps SKUs to entitlement codes and owns billing authority integration.