bursar.CreditStore
class bursar.CreditStore
Bases: ABC
Interface for credit storage backends.
PostgresStore is the production adapter. Custom stores implement this
interface to back Bursar with their own storage.
close()
Release resources owned by the store.
Stateless/custom stores need no cleanup. Connection-backed stores should override this method so applications can close them through the Bursar facade instead of retaining an adapter-specific reference.
- Return type: None
abstractmethod get_balance(user_id)
Return current balance and lifetime purchased amount.
- Parameters: user_id (str)
- Return type: BalanceResult
abstractmethod add_credits(user_id, amount, type='adjustment', metadata=None, expires_at=None, bucket=None, *, idempotency_key)
Atomically add credits and log a transaction.
- Parameters:
- amount (Decimal) – Fractional credit amount (
Decimal). - expires_at (datetime | None) – Optional datetime after which the credits expire.
- bucket (str | None) – Optional bucket key to grant into. When no buckets are
configured, must be
Noneor"default". When buckets are configured and omitted, resolves to the bucket withis_default=True(raises if none is marked default). - idempotency_key (str) – Required user-scoped replay key. A retried grant
with the same key (e.g. a webhook redelivered by the sender)
returns the original entry’s result rather than
granting a second time — no double-mutation, no second
ledger row. Follows the same replay idiom already used by
deduct_with_allowance()/settle_lease()/deduct_team(). - user_id (str)
- type (str)
- metadata (CreditMetadata | None)
- amount (Decimal) – Fractional credit amount (
- Return type: AddCreditsResult
abstractmethod deduct_with_allowance(user_id, amount, *, idempotency_key, operation='usage', feature=None, model=None, region=None, measures=None, dimensions=None, metadata=None)
Atomically charge a gross cost in a single server-side transaction.
This is the canonical “calculate cost then charge now” path. Within one transaction the store:
- Locks the user’s credit row.
- Honors
idempotency_key(user-scoped) — a replay returns the : original result withidempotent=True. The replayedbalance_afteris the balance at the time of the original call, not the current balance. - Consumes free allowance first (
allowance_consumedon the result), : charging only the net remainder to the balance. - Enforces the canonical plan credit policy and quotas server-side.
- Debits the balance and inserts one
usagetransaction.
All-or-nothing: any failure rolls back allowance consumption and the
balance change. Business failures are returned via
DeductionResult.error (the manager maps codes to exceptions); the
store does not import manager-level exceptions.
- Parameters:
- user_id (str) – The user to charge.
- amount (Decimal) – Gross cost (
Decimal,>= 0, fractional 6dp). - idempotency_key (str) – Optional user-scoped replay key.
- operation (str) – Catalog operation key used for plan-aware policy.
- feature (str | None) – Optional feature key used for entitlement checks.
- model (str | None) – Optional model name recorded on the transaction.
- region (str | None) – Optional deployment region recorded on the transaction.
- measures (dict *[*str , Decimal | int | float ] | None) – Usage measures evaluated by quota rules.
- dimensions (dict *[*str , Any ] | None) – Usage dimensions evaluated by pricing/policy rules.
- metadata (CreditMetadata | None) – Extra metadata merged onto the transaction.
- Returns:
DeductionResultwith netamount,allowance_consumed,balance_after,idempotent, anderror. - Return type: DeductionResult
abstractmethod create_lease(user_id, amount, operation_type, options)
Atomically acquire a lease (hold) — the only authoritative admission control.
Under one lock the store: (1) ensures the balance row exists; (2) enforces
max_concurrent by counting active leases for (user_id, operation_type); (3) enforces canonical entitlements and quotas;
(4) computes available = balance − Σ active holds and rejects with
error="insufficient_credits" if available − amount < floor; (5)
inserts an active lease expiring after ttl_seconds.
floor is the resolved admission floor (>= 0 for strict; the negative
overdraft_floor for overdraft). billing_mode/overdraft_floor are
persisted on the lease for settle-time/observability. Business failures are
returned via LeaseResult.error; the store never raises domain exceptions.
- Parameters:
- user_id (str)
- amount (Decimal)
- operation_type (str)
- options (CreateLeaseOptions)
- Return type: LeaseResult
abstractmethod settle_lease(user_id, lease_id, amount, options=None)
Charge the actual cost against a lease, then mark it settled.
De-clamped: charges amount even if it exceeds the lease hold (overdraft),
never clamps to the lease amount.
Pipeline: idempotency replay → allowance consumption → quota accounting →
debit (the balance may go negative in overdraft) → ledger row → mark the
lease settled. amount == 0 releases the lease without charging.
Lease-state failures are returned via DeductionResult.error:
lease_not_found (missing / other user / released) or lease_expired
(the lease TTL elapsed). A replayed settle
(same idempotency key, or a re-settle of an already-settled lease) returns
the original result with idempotent=True.
- Parameters:
- user_id (str)
- lease_id (str)
- amount (Decimal)
- options (SettleLeaseOptions | None)
- Return type: DeductionResult
abstractmethod get_lease_pricing_context(user_id, lease_id)
Return the catalog revision and rate card captured by a lease.
Usage-metric settlement must price against this immutable context rather
than the subject’s current plan, which may have changed after admission.
None means the lease is missing or does not belong to user_id.
- Parameters:
- user_id (str)
- lease_id (str)
- Return type: LeasePricingContext | None
abstractmethod release_lease(user_id, lease_id)
Release a lease without charging (work failed/aborted).
Idempotent and safe on missing or already-finalized leases:
transitions an active/expired lease to released and reports
released=True; otherwise reports released=False with a reason.
- Parameters:
- user_id (str)
- lease_id (str)
- Return type: ReleaseResult
abstractmethod renew_lease(user_id, lease_id, ttl_seconds)
Extend an active lease without changing its captured policy.
- Parameters:
- user_id (str)
- lease_id (str)
- ttl_seconds (int)
- Return type: LeaseResult
expire_leases(limit=100)
Expire a bounded batch of abandoned leases and release reservations.
- Parameters: limit (int)
- Return type: int
abstractmethod get_available(user_id)
Advisory, non-locking read of available = balance − Σ active holds.
For UI only — never an admission gate; the value may be stale the instant it is read.
- Parameters: user_id (str)
- Return type: AvailableResult
abstractmethod get_bucket_balances(user_id)
Return per-bucket balance breakdown for a user, ordered by priority ascending.
When no buckets are configured, returns a single synthetic "default"
bucket entry so the shape is uniform regardless of whether buckets are
configured.
- Parameters: user_id (str)
- Return type: BucketBalancesResult
execute_grant_program(request)
Execute one configured grant-program event.
- Parameters: request (ExecuteGrantProgramRequest)
- Return type: list[GrantProgramAwardResult]
abstractmethod get_active_catalog()
Fetch the active catalog revision from the store.
- Return type: CatalogRevision | None
abstractmethod publish_and_activate_catalog(config, label=None, rollout=None)
Publish and activate a catalog revision.
Deactivates the previous active config and inserts a new one. Returns the new config id.
- Parameters:
- config (dict *[*str , Any ])
- label (str | None)
- rollout (CatalogRollout | dict *[*str , Any ] | None)
- Return type: str
abstractmethod get_catalog_history()
List catalog revisions, newest first.
- Return type: list[CatalogRevisionSummary]
abstractmethod get_catalog_revision(version)
Fetch a catalog revision by version number.
- Parameters: version (int)
- Return type: CatalogRevision | None
abstractmethod activate_catalog_revision(version, rollout=None)
Activate a catalog revision (deactivates all others).
- Parameters:
- version (int) – The version number to activate.
- rollout (CatalogRollout | dict *[*str , Any ] | None)
- Returns: The activated config id.
- Return type: str
abstractmethod publish_catalog_draft(config, label=None)
Publish an inactive catalog draft without changing the live catalog.
- Parameters:
- config (dict *[*str , Any ])
- label (str | None)
- Return type: str
abstractmethod get_user_plan(user_id)
Fetch user’s current plan (including feature entitlements).
- Parameters: user_id (str)
- Return type: GetUserPlanResult
check_feature(user_id, feature)
Check whether a user’s plan has a specific feature entitlement.
Convenience method. Default implementation calls get_user_plan()
and inspects the features dict. Override in custom stores for
optimized queries.
Feature presence is distinguished from truthiness:
the feature is considered present when the key exists and its value is
not None/False. Numeric 0 and empty string "" are
therefore present (has_feature=True).
- absent /
None/False→has_feature=False True/ numeric (incl.0) / string (incl."") →has_feature=True
Note: identity checks (is None/is False) are used rather than the
contract’s literal not in (None, False), because 0 == False /
0.0 == False in Python would otherwise mis-classify numeric 0 as
absent even though numeric 0 and "" are present values.
- Parameters:
- user_id (str)
- feature (str)
- Return type: CheckFeatureResult
abstractmethod set_user_plan(user_id, plan_key, plan_assigned_at=None)
Assign a plan to a user.
plan_assigned_at anchors plan-assignment policy windows. When
omitted, the store uses the current time.
- Parameters:
- user_id (str)
- plan_key (str)
- plan_assigned_at (datetime | None)
- Return type: SetUserPlanResult
abstractmethod unset_user_plan(user_id)
Clear the user’s plan assignment.
- Parameters: user_id (str)
- Return type: UnsetUserPlanResult
abstractmethod set_plan_revision_pin(user_id, pinned)
Pin or unpin the current assignment’s catalog revision.
- Parameters:
- user_id (str)
- pinned (bool)
- Return type: bool
abstractmethod apply_due_plan_changes(limit=100)
Apply a bounded batch of scheduled plan changes that are now due.
- Parameters: limit (int)
- Return type: int
abstractmethod start_plan_migration(from_plan_id, to_plan_id)
Create a resumable migration from one catalog plan to another.
- Parameters:
- from_plan_id (str | None)
- to_plan_id (str)
- Return type: PlanMigrationStartResult
abstractmethod migrate_plan_batch(migration_id, batch_size=100)
Advance a plan migration by one bounded batch.
- Parameters:
- migration_id (str)
- batch_size (int)
- Return type: PlanMigrationBatchResult
abstractmethod get_quota_state(user_id, quota_key=None)
Return current quota windows for a user.
- Parameters:
- user_id (str)
- quota_key (str | None)
- Return type: list[QuotaState]
abstractmethod check_allowance(user_id)
Get the database-owned current allowance window.
- Parameters: user_id (str)
- Return type: AllowanceResult | None
abstractmethod list_quota_events(user_id, options=None)
List persisted quota threshold and blocking events.
- Parameters:
- user_id (str)
- options (ListQuotaEventsOptions | None)
- Return type: list[QuotaEvent]
abstractmethod refund_credits(entry_id, *, idempotency_key, amount=None, reason=None, metadata=None)
Refund a previous credit deduction.
- Parameters:
- entry_id (str) – The transaction to refund.
- amount (Decimal | None) – Optional partial refund amount. Full refund if omitted.
- reason (str | None) – Optional reason for the refund.
- metadata (CreditMetadata | None) – Extra metadata to attach to the refund entry.
- idempotency_key (str) – Required stable replay key.
- Returns:
RefundResultwith the refund ledger entry details, orerrorset if the transaction doesn’t exist or is already refunded. - Return type: RefundResult
abstractmethod sweep_expired_credits(dry_run=False, user_id=None, limit=100)
Expire at most limit eligible credit lots.
- Parameters:
- dry_run (bool)
- user_id (str | None)
- limit (int)
- Return type: SweepResult
abstractmethod revoke_credits_by_entry_type(user_id, entry_type)
Revoke all credits of a given transaction type for a user (LIFO across tiers).
Used by the subscription lifecycle to replace cycle-grant credits on renewal. Returns the revoked amount and resulting committed balance.
- Parameters:
- user_id (str)
- entry_type (str)
- Return type: RevokeCreditsResult
spend_by_user(start, end)
Aggregate spend by user in a time window.
- Parameters:
- start (datetime) – Start of time window (inclusive).
- end (datetime) – End of time window (inclusive).
- Returns:
List of
SpendByUserRowwith totals per user. - Return type: list[SpendByUserRow]
spend_by_model(start, end)
Aggregate spend by model in a time window.
- Parameters:
- start (datetime) – Start of time window (inclusive).
- end (datetime) – End of time window (inclusive).
- Returns:
List of
SpendByModelRowwith totals per model. - Return type: list[SpendByModelRow]
top_users(limit, start, end)
Top users by spend in a time window.
- Parameters:
- limit (int) – Maximum number of users to return.
- start (datetime) – Start of time window (inclusive).
- end (datetime) – End of time window (inclusive).
- Returns:
List of
TopUserRowsorted by total_spend descending. - Return type: list[TopUserRow]
daily_spend(start, end)
Daily spend aggregation in a time window.
- Parameters:
- start (datetime) – Start of time window (inclusive).
- end (datetime) – End of time window (inclusive).
- Returns:
List of
DailySpendRowwith per-day totals. - Return type: list[DailySpendRow]
aggregate_stats(start, end)
Aggregate statistics across all users in a time window.
- Parameters:
- start (datetime) – Start of time window (inclusive).
- end (datetime) – End of time window (inclusive).
- Returns:
AggregateStatswith total credits consumed, active users, average daily spend, top model, and top user. - Return type: AggregateStats
list_ledger_entries(user_id, entry_types=None, from_date=None, to_date=None, limit=50, cursor=None)
List account ledger history with a stable timestamp-plus-entry cursor.
- Parameters:
- user_id (str)
- entry_types (list *[*str ] | None)
- from_date (datetime | None)
- to_date (datetime | None)
- limit (int)
- cursor (LedgerCursor | None)
- Return type: LedgerPage
list_usage_entries(user_id, from_date=None, to_date=None, limit=50, cursor=None)
List usage ledger entries with the same cursor contract.
- Parameters:
- user_id (str)
- from_date (datetime | None)
- to_date (datetime | None)
- limit (int)
- cursor (LedgerCursor | None)
- Return type: LedgerPage
list_usage_charges(user_id, from_date=None, to_date=None, limit=50, cursor=None, include_record_only=True)
List metered usage charges, including allowance-covered events.
- Parameters:
- user_id (str)
- from_date (datetime | None)
- to_date (datetime | None)
- limit (int)
- cursor (UsageChargeCursor | None)
- include_record_only (bool)
- Return type: UsageChargePage
record_usage(user_id, operation, requested, *, idempotency_key, feature=None, model=None, region=None, metadata=None, measures=None, dimensions=None)
Append priced usage telemetry without debiting the account again.
- Parameters:
- user_id (str)
- operation (str)
- requested (Decimal)
- idempotency_key (str)
- feature (str | None)
- model (str | None)
- region (str | None)
- metadata (CreditMetadata | None)
- measures (dict *[*str , Any ] | None)
- dimensions (dict *[*str , Any ] | None)
- Return type: UsageRecordResult
get_ledger_entry(user_id, entry_id)
Return one ledger entry when it belongs to the user account.
- Parameters:
- user_id (str)
- entry_id (str)
- Return type: LedgerEntry | None
create_team(owner_subject_id, name, initial_balance=Decimal('0'), *, idempotency_key)
Create a team with a shared credit balance pool.
- Parameters:
- owner_subject_id (str) – Subject that owns the team.
- name (str) – Human-readable team name.
- initial_balance (Decimal) – Starting credit balance.
- idempotency_key (str) – Caller-owned replay key for this creation request.
- Returns:
CreateTeamResultwith the new team id. - Return type: CreateTeamResult
get_team_balance(team_id)
Fetch team balance and member count.
- Parameters: team_id (str) – The team’s UUID.
- Returns:
TeamBalanceResultwith balance and member count, orNonewhen the team does not exist. - Return type: TeamBalanceResult | None
add_team_member(team_id, user_id, role='member', spend_cap=None)
Add a user to a team.
- Parameters:
- team_id (str) – The team’s UUID.
- user_id (str) – The user’s UUID.
- role (Literal [ 'owner' , 'admin' , 'member' ]) – Member role (e.g. “member”, “admin”).
- spend_cap (Decimal | None) – Optional per-user spend cap.
- Returns:
AddTeamMemberResultconfirming membership. - Return type: AddTeamMemberResult
get_team_members(team_id)
List all members of a team.
- Parameters: team_id (str) – The team’s UUID.
- Returns:
List of
TeamMember. - Return type: list[TeamMember]
remove_team_member(team_id, user_id)
Remove a team member unless they are the final owner.
- Parameters:
- team_id (str)
- user_id (str)
- Return type: bool
deduct_team(team_id, user_id, amount, metadata=None, *, idempotency_key)
Deduct credits from a team pool, attributed to a user.
- Parameters:
- team_id (str) – The team’s UUID.
- user_id (str) – The user to attribute the deduction to.
- amount (Decimal) – Credits to deduct (
Decimal). - metadata (CreditMetadata | None) – Extra metadata.
- idempotency_key (str) – Required replay key. A retried team deduction with the same key returns the original result rather than charging the shared pool again.
- Returns:
TeamDeductionResultwith ledger entry details. - Return type: TeamDeductionResult