Skip to content

CostUsageService

Target

This page describes intended behaviour or structure. No implementation is claimed unless a section is explicitly marked Current implementation or Evidence.

Bounded context BC-05 Usage Accounting
Platform / plane Control Plane / Control
Deployment granularity module of host · host ControlPlaneApi
Status target · ConnectSoft.Factory.Usage
Built from connectsoft-microservice · libraries: connectsoft-extensions-saas-abstractions, connectsoft-extensions-saas-asp-net-core, connectsoft-extensions-saas-metering, connectsoft-extensions-messaging-model, connectsoft-extensions-persistence-model-nhibernate
Decisions ADR-0020, ADR-0014
Traceability FR-09 FUX-08 NFR-08 NFR-09 NFR-12 XL-12

Purpose and responsibilities

Every run spends money before anyone knows exactly how much. CostUsageService makes that spend governable: it holds budgets per project and tenant, reserves an estimate before Run Control dispatches the first step, records every metered quantity from model and tool receipts with the pricing revision in force, settles the reservation when the run ends, and reconciles estimated or uncertain records against provider statements later. Its usage states (estimated, reserved, observed, reconciled, uncertain) are shown to users as such (FUX-08); nothing is ever silently zeroed.

It is the source for FinOps views in Observability (PF-13) and for the Factory's own meters in FactoryCommercialService (BC-34). It absorbs the June-2026 rows QuotaService (quota denial is a reservation outcome) and CostTelemetryService (telemetry is a projection of these records, not a second source). Entitlement definitions (what an edition allows) come from BC-33; this service enforces amounts.

Context and boundaries

flowchart LR
    S["CostUsageService"]
    RunControlService["RunControlService"] -->|grpc| S
    ModelRouterService["ModelRouterService"] -->|grpc| S
    ToolAdapterService["ToolAdapterService"] -->|grpc| S
    StudioBff["StudioBff"] -->|sync-http| S
    S -->|library| ext_saas_templates["ext-saas-templates"]
    S -->|event| FactoryCommercialService["FactoryCommercialService"]
    S -->|grpc| RunControlService["RunControlService"]
    S -. "BudgetReserved" .-> E_BudgetReserved(("event"))
    S -. "BudgetReservationDenied" .-> E_BudgetReservationDenied(("event"))
    S -. "ReservationSettled" .-> E_ReservationSettled(("event"))
    S -. "UsageRecorded" .-> E_UsageRecorded(("event"))
    S -. "UsageReconciled" .-> E_UsageReconciled(("event"))
    S -. "BudgetUpdated" .-> E_BudgetUpdated(("event"))
Hold "Alt" / "Option" to enable pan & zoom

The spend approval rule (D21): a run whose estimate exceeds the budget's approval threshold is reserved only after a Reviewer or Workspace admin approves the exact estimate through Run Control. The platform threshold is set by Platform admin policy; a project may only lower it.

Domain model

Budget — A spending limit for a project or tenant with the spend-approval threshold and the current committed amount. (catalog entry)

Field Type Required Description
budgetId guid yes
tenantId string yes
projectId guid no Null for a tenant-wide budget.
period string yes monthly
limitUsd decimal yes
committedUsd decimal yes Reserved plus settled in the period.
approvalThresholdUsd decimal yes Predicted run cost above which approval is required (D21); platform ceiling, project may lower.
version long yes

Invariants:

  • committedUsd never exceeds limitUsd; a reservation that would exceed it is Denied.
  • The approval threshold of a project budget is never higher than the platform threshold.

Events: BudgetUpdated · Tables: Budgets · Repository: BudgetRepository

Reservation — An amount held for a run before dispatch and settled from observed usage afterwards. (catalog entry)

Field Type Required Description
reservationId guid yes
tenantId string yes
projectId guid yes
runId guid yes
budgetId guid yes
estimatedUsd decimal yes
settledUsd decimal no
state enum:ReservationState yes
approvalId guid no
expiresAt datetime yes

Invariants:

  • One active reservation per run; settlement is idempotent on the set of receipts settled.
  • A reservation above the threshold is created in Reserved only after the spend approval is granted.
stateDiagram-v2
    Reserved --> Settled : RunTerminal
    Reserved --> Released : RunCancelledBeforeDispatch
    Reserved --> Expired : ExpiryWithoutRun
Hold "Alt" / "Option" to enable pan & zoom

Events: BudgetReserved, BudgetReservationDenied, ReservationSettled · Tables: Reservations · Repository: ReservationRepository

UsageRecord — One metered quantity attributed to tenant, project, run and attempt, priced with a pricing revision and carrying a reconciliation status. (catalog entry)

Field Type Required Description
usageId guid yes
tenantId string yes
projectId guid yes
runId guid no
attemptId guid no
meter enum:Meter yes
dimensions json yes profile, provider, model, tool, pool, store, product, sloTier
quantity decimal yes
status enum:UsageStatus yes
pricingRevision string yes
estimatedCostUsd decimal no
confirmedCostUsd decimal no
sourceReceipt string no
occurredAt datetime yes

Invariants:

  • A record is created from a receipt exactly once (sourceReceipt unique per meter).
  • Uncertain usage is never zeroed; it is reconciled by a later receipt or by a provider statement.

Events: UsageRecorded, UsageReconciled · Tables: UsageRecords, PricingRevisions · Repository: UsageRecordRepository

erDiagram
    Budget {
        guid budgetId PK
        string tenantId
        guid projectId
        string period
        decimal limitUsd
        decimal committedUsd
        decimal approvalThresholdUsd
        long version
    }
    Reservation {
        guid reservationId PK
        string tenantId
        guid projectId
        guid runId
        guid budgetId
        decimal estimatedUsd
        decimal settledUsd
        enum_ReservationState state
        guid approvalId
        datetime expiresAt
    }
    UsageRecord {
        guid usageId PK
        string tenantId
        guid projectId
        guid runId
        guid attemptId
        enum_Meter meter
        json dimensions
        decimal quantity
        enum_UsageStatus status
        string pricingRevision
        decimal estimatedCostUsd
        decimal confirmedCostUsd
        string sourceReceipt
        datetime occurredAt
    }
    PricingRevision {
        string revision

        enum_Meter meter

        string dimensionKey

        decimal unitPriceUsd

        datetime validFrom

        datetime validTo

    }
    UsageRecord ||--o{ PricingRevision : contains
Hold "Alt" / "Option" to enable pan & zoom
stateDiagram-v2
    [*] --> Reserved: Reserve within budget
    [*] --> Denied: Reserve exceeds budget
    Reserved --> Settled: run terminal
    Reserved --> Released: cancelled before dispatch
    Reserved --> Expired: no dispatch before expiry
    Settled --> [*]
    Released --> [*]
    Expired --> [*]
    Denied --> [*]
Hold "Alt" / "Option" to enable pan & zoom

Usage records are facts, never recomputed: a receipt creates exactly one record per meter (unique on receipt id), reconciliation adds the confirmed cost and status, and pricing revisions are versioned so that a past record always prices with the revision valid when it occurred.

Entities and value objects

PricingRevision (entity, in UsageRecord) — Versioned price list per meter and dimension.

Field Type Required Description
revision string yes
meter enum:Meter yes
dimensionKey string yes
unitPriceUsd decimal yes
validFrom datetime yes
validTo datetime no

Data model and storage

Store Role Engine / profile Tables
Control plane relational store primary azure-sql / nhibernate-sqlserver Budgets, Reservations, UsageRecords, PricingRevisions
Table Owner Columns Indexes
dbo.Budgets Budget 8 1
dbo.Reservations Reservation 10 1
dbo.UsageRecords UsageRecord 14 2
dbo.PricingRevisions PricingRevision 6 0

Full DDL: appendix.

UsageRecords is retained seven years as commercial evidence (D31) and is the largest table of the Control plane; it is partitioned by tenant and month. PricingRevisions is platform-wide (not tenant-scoped).

APIs

Method Path Operation Scope Side effects Idempotency Gateway Summary
GET /factory/projects/{projectId}/usage GetProjectUsage factory.usage.read read natural GW-03 Usage by meter and status (estimated, reserved, observed, reconciled, uncertain) for a period.
GET /factory/runs/{runId}/usage GetRunUsage factory.usage.read read natural GW-03 Usage records and the reservation of one run.
PUT /factory/projects/{projectId}/budget SetProjectBudget factory.usage.admin shared-write natural GW-03 Set the project budget and a threshold not above the platform's.
gRPC service Method Streaming Visibility Side effects Summary
Usage Reserve none internal spend Reserve an estimated amount for a run; returns Reserved, Denied or approval-required.
Usage RecordUsage none internal shared-write Record metered usage from a model or tool receipt (idempotent on receipt id).

Messages

Event Topic Scope / v Aggregate Consumers Summary
BudgetReserved usage-accounting.reservations.v1.budget-reserved integration / 1 Reservation RunControlService, StudioBff A run has budget and may be dispatched.
BudgetReservationDenied usage-accounting.reservations.v1.budget-reservation-denied integration / 1 Reservation RunControlService, StudioBff No budget could be reserved; the run fails as policy-denied.
ReservationSettled usage-accounting.reservations.v1.reservation-settled integration / 1 Reservation FactoryCommercialService, StudioBff, DashboardService Observed usage settled against the reservation.
UsageRecorded usage-accounting.usage.v1.usage-recorded integration / 1 UsageRecord FactoryCommercialService, DashboardService A metered quantity was recorded.
UsageReconciled usage-accounting.usage.v1.usage-reconciled integration / 1 UsageRecord FactoryCommercialService, DashboardService An estimated or uncertain record was confirmed.
BudgetUpdated usage-accounting.budgets.v1.budget-updated integration / 1 Budget StudioBff, AuditService A budget limit or threshold changed.

BudgetReserved payload:

Field Type Required Description
reservationId guid yes
runId guid yes
tenantId string yes
estimatedUsd decimal yes

BudgetReservationDenied payload:

Field Type Required Description
runId guid yes
tenantId string yes
reason string yes

ReservationSettled payload:

Field Type Required Description
reservationId guid yes
runId guid yes
tenantId string yes
settledUsd decimal yes
uncertain bool yes

UsageRecorded payload:

Field Type Required Description
usageId guid yes
tenantId string yes
projectId guid yes
meter enum:Meter yes
quantity decimal yes
status enum:UsageStatus yes

UsageReconciled payload:

Field Type Required Description
usageId guid yes
tenantId string yes
confirmedCostUsd decimal yes

BudgetUpdated payload:

Field Type Required Description
budgetId guid yes
tenantId string yes
projectId guid no
limitUsd decimal yes
Command Handled by Result events Summary
Reserve Reservation BudgetReserved, BudgetReservationDenied
RecordUsage UsageRecord UsageRecorded
Event Producer Handler Purpose
RunAccepted RunControlService ReserveForRun Estimate and reserve budget for an accepted run.
RunCompleted RunControlService SettleReservation Settle observed usage.
RunFailed RunControlService SettleReservation Settle observed usage of a failed run.
RunCancelled RunControlService SettleOrRelease Release or settle after cancellation.
AttemptOutcomeRecorded RunControlService RecordAttemptUsage Record usage from the attempt receipt.

Workers and jobs

Worker Trigger Output events Retry Idempotency key Summary
ReconciliationWorker schedule 0 * * * * UsageReconciled exponential usageId + statementRef Confirms estimated and uncertain usage from provider statements and late receipts; emits UsageReconciled.
ReservationExpiryWorker schedule */15 * * * * ReservationSettled fixed reservationId + expiresAt Expires reservations whose run never dispatched and releases committed amounts.

Actors and grains

None modelled

No grains are modelled for this item yet.

Sagas and long-running flows

None modelled

No sagas are modelled for this item yet.

sequenceDiagram
    participant RC as RunControlService
    participant U as CostUsageService
    participant MR as ModelRouterService
    participant TA as ToolAdapterService
    RC-)U: RunAccepted with limits
    U->>U: estimate from profile and workflow, compare with budget and threshold
    alt within budget and under threshold
        U-)RC: BudgetReserved
    else over threshold
        U->>RC: RequestApproval class spend bound to the estimate
        RC-)U: ApprovalDecided
        U-)RC: BudgetReserved or BudgetReservationDenied
    else over budget
        U-)RC: BudgetReservationDenied
    end
    MR->>U: RecordUsage from model receipt
    TA->>U: RecordUsage from tool receipt
    RC-)U: RunCompleted
    U->>U: settle reservation, mark unknown receipts Uncertain
    U-)RC: ReservationSettled
Hold "Alt" / "Option" to enable pan & zoom

Options and configuration

Section Key Type Default Reloadable Secret Summary
Usage:Thresholds PlatformApprovalThresholdUsd decimal 10 yes no Platform spend threshold above which a run needs approval (D21; original §52 seed).
Usage:Reservations ExpiryMinutes int 60 yes no Reservation lifetime without dispatch.

Metrics and observability

Metric Instrument Unit Dimensions Summary
connectsoft.factory.usage.cost_usd counter USD tenant, project, meter, status Cost by meter and status.
connectsoft.factory.usage.uncertain_records updown {record} tenant Records awaiting reconciliation.
connectsoft.factory.usage.reservations_denied counter {reservation} tenant, reason Denied reservations.

Security

Authentication oauth2-jwt
Scopes factory.usage.read (Read usage and budgets.); factory.usage.admin (Set budgets and thresholds.)
Roles Requester, Operator, WorkspaceAdmin, PlatformAdmin, Observer
Tenant isolation row-filter
Data classification internal
Max side-effect class spend
Secrets —

Dependencies

Outbound

Target Kind Purpose Failure mode Required
ext-saas-templates library Metering template as the meter store. fail-fast yes
FactoryCommercialService event Feed invoices and edition quotas. queue no
RunControlService grpc Request a spend approval when the estimate exceeds the threshold. fail-fast yes

Inbound

Target Kind Purpose Failure mode Required
RunControlService grpc Reserve and RecordUsage. — yes
ModelRouterService grpc RecordUsage from model receipts. — yes
ToolAdapterService grpc RecordUsage from tool receipts. — yes
StudioBff sync-http Usage views and budget administration. — yes

Failure and recovery

Scenario Detection Recovery Data impact RTO (min)
Provider returns no usage in a receipt usage status unknown on the receipt record created as Uncertain; reconciliation worker confirms from statements cost shown as uncertain until reconciled 1440
Budget store conflict under concurrent reservations version mismatch retry the reservation; limit never exceeded none 0

NFR targets

Target Class value (pilot-interactive) Override
latency_p95_ms 2000 —
availability_pct 99.5 —
throughput_rps 50 —
rpo_minutes 15 —
rto_minutes 240 —
cost_class low —

Deployment

Granularity module of host · host ControlPlaneApi
Topologies saas-default, hybrid, on-prem
Runtime —; replicas —–—
Ports —

Current implementation

Nothing exists. The SaaS Metering template exists and is the meter store this service extends (ecosystem composition); MicrosoftExtensionsAIOptions carries no usage receipt.

Later capabilities

Predictive budget alerts and anomaly detection (from the former roadmap); per-step cost attribution in the Studio run view; chargeback exports; provider statement import automation per provider.

Decisions and traceability

Family Ids
FR FR-09
FUX FUX-08
NFR NFR-08, NFR-09, NFR-12
XL XL-12