# IQuotaStore

> Tracon documentation. Tracon is a .NET package family that adds a control
> plane on Microsoft Agent Framework, published as NuGet packages that run
> inside your own ASP.NET Core application.
>
> Page: https://tracon.dev/api/tracon.iquotastore/
> Summary: IQuotaStore: The store for quota rules and consumption counters.

<div class="api-provenance">Namespace Tracon · Assembly <code>Tracon.Abstractions.dll</code></div>

The store for quota rules and consumption counters.

```csharp
public interface IQuotaStore
```

## Remarks

Two different kinds of data live here: *rules* (defined by an
administrator, change rarely) and *counters* (increment at the end
of every run). Both sit in the same contract because a check always reads
them together.

**Important:** [IQuotaStore.AddUsageAsync](/api/tracon.iquotastore/#Tracon_IQuotaStore_AddUsageAsync_Tracon_QuotaConsumption_System_Collections_Generic_IReadOnlyDictionary_Tracon_QuotaPeriod_System_DateOnly__System_Threading_CancellationToken_) must be atomic —
the PostgreSQL implementation uses `INSERT ... ON CONFLICT DO UPDATE`.
Concurrent runs increment the same row and no increment is lost.

## Methods

### <a id="Tracon_IQuotaStore_AddUsageAsync_Tracon_QuotaConsumption_System_Collections_Generic_IReadOnlyDictionary_Tracon_QuotaPeriod_System_DateOnly__System_Threading_CancellationToken_"></a> AddUsageAsync\(QuotaConsumption, IReadOnlyDictionary<QuotaPeriod, DateOnly\>, CancellationToken\)

Adds a consumption to both the agent counter and the tenant-wide counter.

```csharp
ValueTask AddUsageAsync(QuotaConsumption consumption, IReadOnlyDictionary<QuotaPeriod, DateOnly> periodStarts, CancellationToken cancellationToken = default)
```

#### Parameters

`consumption` [QuotaConsumption](/api/tracon.quotaconsumption/)

The consumption.

`periodStarts` [IReadOnlyDictionary](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlydictionary\-2)<[QuotaPeriod](/api/tracon.quotaperiod/), [DateOnly](https://learn.microsoft.com/dotnet/api/system.dateonly)\>

For each period, the first day of the period the consumption falls
into. The caller computes this from the configured time zone; the
store does not know time zones.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask)

The completion task.

#### Remarks

The store **carries no** time zone: the caller computes
the period boundary and passes it ready-made. This lets the same store
behave correctly for consumers running in different time zones.

### <a id="Tracon_IQuotaStore_DeleteAsync_System_String_System_Guid_System_Threading_CancellationToken_"></a> DeleteAsync\(string, Guid, CancellationToken\)

Deletes a rule.

```csharp
ValueTask<bool> DeleteAsync(string tenantId, Guid id, CancellationToken cancellationToken = default)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The tenant identifier.

`id` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The rule identifier.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[bool](https://learn.microsoft.com/dotnet/api/system.boolean)\>

[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if the delete happened.

### <a id="Tracon_IQuotaStore_GetAsync_System_String_System_Guid_System_Threading_CancellationToken_"></a> GetAsync\(string, Guid, CancellationToken\)

Fetches a single rule.

```csharp
ValueTask<QuotaDefinition?> GetAsync(string tenantId, Guid id, CancellationToken cancellationToken = default)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The tenant identifier.

`id` [Guid](https://learn.microsoft.com/dotnet/api/system.guid)

The rule identifier.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[QuotaDefinition](/api/tracon.quotadefinition/)?\>

The rule; [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) if it does not exist or belongs to another tenant.

### <a id="Tracon_IQuotaStore_GetUsageAsync_Tracon_QuotaUsageQuery_System_Threading_CancellationToken_"></a> GetUsageAsync\(QuotaUsageQuery, CancellationToken\)

Fetches the consumption counters that match the filter.

```csharp
ValueTask<IReadOnlyList<QuotaUsageRecord>> GetUsageAsync(QuotaUsageQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [QuotaUsageQuery](/api/tracon.quotausagequery/)

The filter.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[QuotaUsageRecord](/api/tracon.quotausagerecord/)\>\>

The counters. An empty list when nothing matches.

#### Remarks

Without [QuotaUsageQuery.PeriodStarts](/api/tracon.quotausagequery/#Tracon_QuotaUsageQuery_PeriodStarts) every period's
counters are returned — the whole history, which only grows. The
built-in callers always pass the current periods, so a run check reads
a bounded set. A store must apply every filter the query carries.

### <a id="Tracon_IQuotaStore_ListAsync_System_String_System_Threading_CancellationToken_"></a> ListAsync\(string, CancellationToken\)

Lists a tenant's quota rules.

```csharp
ValueTask<IReadOnlyList<QuotaDefinition>> ListAsync(string tenantId, CancellationToken cancellationToken = default)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The tenant identifier.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[QuotaDefinition](/api/tracon.quotadefinition/)\>\>

The rules.

### <a id="Tracon_IQuotaStore_SaveAsync_Tracon_QuotaDefinition_System_Threading_CancellationToken_"></a> SaveAsync\(QuotaDefinition, CancellationToken\)

Saves a rule. Overwrites an existing rule for the same scope (tenant +
agent + period), if one already exists.

```csharp
ValueTask<QuotaDefinition> SaveAsync(QuotaDefinition definition, CancellationToken cancellationToken = default)
```

#### Parameters

`definition` [QuotaDefinition](/api/tracon.quotadefinition/)

The rule.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[QuotaDefinition](/api/tracon.quotadefinition/)\>

The saved rule.

#### Remarks

Scope uniqueness is built over `COALESCE(agent_name, '')`; a plain
`UNIQUE` constraint treats NULLs as distinct from each other and
would let the same rule be added an unlimited number of times.

### <a id="Tracon_IQuotaStore_TryClaimThresholdNotificationAsync_System_String_System_String_Tracon_QuotaPeriod_System_DateOnly_Tracon_QuotaMetric_System_Int32_System_Threading_CancellationToken_"></a> TryClaimThresholdNotificationAsync\(string, string, QuotaPeriod, DateOnly, QuotaMetric, int, CancellationToken\)

Atomically claims a quota threshold notification for one scope/period,
so that only one caller among concurrent workers — and only the first
run to reach it across a process restart — proceeds to publish it.

```csharp
ValueTask<bool> TryClaimThresholdNotificationAsync(string tenantId, string agentName, QuotaPeriod period, DateOnly periodStart, QuotaMetric metric, int thresholdPercent, CancellationToken cancellationToken = default)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The tenant identifier.

`agentName` [string](https://learn.microsoft.com/dotnet/api/system.string)

The scope: an agent name, or an empty string (`""`) for the
tenant-wide counter — the same convention [QuotaUsageRecord.AgentName](/api/tracon.quotausagerecord/#Tracon_QuotaUsageRecord_AgentName) uses.

`period` [QuotaPeriod](/api/tracon.quotaperiod/)

The counter's interval.

`periodStart` [DateOnly](https://learn.microsoft.com/dotnet/api/system.dateonly)

The first day of the period (local time).

`metric` [QuotaMetric](/api/tracon.quotametric/)

The metric whose threshold was crossed.

`thresholdPercent` [int](https://learn.microsoft.com/dotnet/api/system.int32)

The crossed threshold percentage.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[bool](https://learn.microsoft.com/dotnet/api/system.boolean)\>

[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if this call is the one that claims the
threshold — no earlier call claimed it in this period, and the caller
should publish the notice. [false](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if it was already
claimed (by an earlier run, or a concurrent worker), or the usage row
for this scope/period does not exist yet.

#### Remarks

The claim survives a process restart: it lives in the same durable
row [IQuotaStore.AddUsageAsync](/api/tracon.iquotastore/#Tracon_IQuotaStore_AddUsageAsync_Tracon_QuotaConsumption_System_Collections_Generic_IReadOnlyDictionary_Tracon_QuotaPeriod_System_DateOnly__System_Threading_CancellationToken_) writes to, not in memory. When the
period rolls over, a new row forms and the claim starts clean —
there is nothing to reset by hand.
