Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""add sandbox compute + storage_bytes meters to meters_type
"""add sandbox compute + bytes meters to meters_type

Revision ID: ee0000000004
Revises: ee0000000003
Expand Down Expand Up @@ -29,7 +29,7 @@ def upgrade() -> None:
op.execute(
"ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'SANDBOX_GPU_CORE_SECONDS'"
)
op.execute("ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'STORAGE_BYTES'")
op.execute("ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'BYTES'")


def downgrade() -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""add sandbox + wallet debit meters to meters_type

Revision ID: ee0000000005
Revises: ee0000000004
Create Date: 2026-07-02 00:00:00.000000
"""

from typing import Sequence, Union

from alembic import op


revision: str = "ee0000000005"
down_revision: Union[str, None] = "ee0000000004"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.execute(
"ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'SANDBOX_CPU_CORE_DEBITS'"
)
op.execute(
"ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'SANDBOX_RAM_GIBI_DEBITS'"
)
op.execute(
"ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'SANDBOX_SSD_GIBI_DEBITS'"
)
op.execute(
"ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'SANDBOX_GPU_CORE_DEBITS'"
)
op.execute("ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'SANDBOX_DEBITS'")
op.execute("ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'LLM_DEBITS'")
op.execute("ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'GATEWAY_DEBITS'")
op.execute("ALTER TYPE meters_type ADD VALUE IF NOT EXISTS 'WALLET_DEBITS'")


def downgrade() -> None:
# Postgres cannot drop an enum label; leave the values in place.
pass
151 changes: 118 additions & 33 deletions api/ee/src/core/access/entitlements/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,22 @@ class Counter(str, Enum):
CREDITS_CONSUMED = "credits_consumed"
EVENTS_INGESTED = "events_ingested"
RECORDS_INGESTED = "records_ingested"
SANDBOX_CPU_CORE_SECONDS = "sandbox_cpu_core_seconds"
SANDBOX_RAM_GIBI_SECONDS = "sandbox_ram_gibi_seconds"
SANDBOX_SSD_GIBI_SECONDS = "sandbox_ssd_gibi_seconds"
SANDBOX_GPU_CORE_SECONDS = "sandbox_gpu_core_seconds"
# Billing layer (Track C): per-dimension + family + wallet debit roll-ups.
# Raw *_seconds dimension meters are cost-explainer data (traces/analytics),
# not billing meters, and are intentionally not modeled here.
SANDBOX_CPU_CORE_DEBITS = "sandbox_cpu_core_debits"
SANDBOX_RAM_GIBI_DEBITS = "sandbox_ram_gibi_debits"
SANDBOX_SSD_GIBI_DEBITS = "sandbox_ssd_gibi_debits"
SANDBOX_GPU_CORE_DEBITS = "sandbox_gpu_core_debits"
SANDBOX_DEBITS = "sandbox_debits"
LLM_DEBITS = "llm_debits"
GATEWAY_DEBITS = "gateway_debits"
WALLET_DEBITS = "wallet_debits"


class Gauge(str, Enum):
USERS = "users"
STORAGE_BYTES = "storage_bytes"
BYTES = "bytes"


class Constraint(str, Enum):
Expand Down Expand Up @@ -242,6 +249,9 @@ class Throttle(BaseModel):
},
],
},
# TODO(pricing): sandbox_debits tiers — measurement-only; wallet
# debits are prepaid (billed at top-up time), never reported to
# Stripe in arrears, so no REPORTS wiring applies here.
},
"features": [
"Unlimited prompts",
Expand Down Expand Up @@ -278,6 +288,9 @@ class Throttle(BaseModel):
},
],
},
# TODO(pricing): sandbox_debits tiers — measurement-only; wallet
# debits are prepaid (billed at top-up time), never reported to
# Stripe in arrears, so no REPORTS wiring applies here.
},
"features": [
"Everything in Pro",
Expand Down Expand Up @@ -362,16 +375,29 @@ class Throttle(BaseModel):
retention=Retention.WEEKLY,
period=Period.MONTHLY,
),
Counter.SANDBOX_CPU_CORE_SECONDS: Quota(
# TODO(pricing): real free/limit once sandbox billing goes live.
Counter.SANDBOX_CPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_RAM_GIBI_SECONDS: Quota(
Counter.SANDBOX_RAM_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_SSD_GIBI_SECONDS: Quota(
Counter.SANDBOX_SSD_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_GPU_CORE_SECONDS: Quota(
Counter.SANDBOX_GPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.LLM_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.GATEWAY_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.WALLET_DEBITS: Quota(
period=Period.MONTHLY,
),
},
Expand All @@ -381,7 +407,7 @@ class Throttle(BaseModel):
limit=2,
strict=True,
),
Gauge.STORAGE_BYTES: Quota(
Gauge.BYTES: Quota(
free=1_073_741_824,
limit=1_073_741_824,
strict=True,
Expand Down Expand Up @@ -471,24 +497,37 @@ class Throttle(BaseModel):
retention=Retention.MONTHLY,
period=Period.MONTHLY,
),
Counter.SANDBOX_CPU_CORE_SECONDS: Quota(
# TODO(pricing): real free/limit once sandbox billing goes live.
Counter.SANDBOX_CPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_RAM_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_RAM_GIBI_SECONDS: Quota(
Counter.SANDBOX_SSD_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_SSD_GIBI_SECONDS: Quota(
Counter.SANDBOX_GPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_GPU_CORE_SECONDS: Quota(
Counter.SANDBOX_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.LLM_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.GATEWAY_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.WALLET_DEBITS: Quota(
period=Period.MONTHLY,
),
},
Tracker.GAUGES: {
Gauge.USERS: Quota(
strict=True,
),
Gauge.STORAGE_BYTES: Quota(
Gauge.BYTES: Quota(
free=5_368_709_120,
limit=10_737_418_240,
strict=True,
Expand Down Expand Up @@ -578,24 +617,37 @@ class Throttle(BaseModel):
retention=Retention.QUARTERLY,
period=Period.MONTHLY,
),
Counter.SANDBOX_CPU_CORE_SECONDS: Quota(
# TODO(pricing): real free/limit once sandbox billing goes live.
Counter.SANDBOX_CPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_RAM_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_SSD_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_RAM_GIBI_SECONDS: Quota(
Counter.SANDBOX_GPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_SSD_GIBI_SECONDS: Quota(
Counter.SANDBOX_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_GPU_CORE_SECONDS: Quota(
Counter.LLM_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.GATEWAY_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.WALLET_DEBITS: Quota(
period=Period.MONTHLY,
),
},
Tracker.GAUGES: {
Gauge.USERS: Quota(
strict=True,
),
Gauge.STORAGE_BYTES: Quota(
Gauge.BYTES: Quota(
free=53_687_091_200,
strict=True,
),
Expand Down Expand Up @@ -680,16 +732,29 @@ class Throttle(BaseModel):
Counter.RECORDS_INGESTED: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_CPU_CORE_SECONDS: Quota(
# TODO(pricing): real free/limit once sandbox billing goes live.
Counter.SANDBOX_CPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_RAM_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_SSD_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_GPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_RAM_GIBI_SECONDS: Quota(
Counter.SANDBOX_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_SSD_GIBI_SECONDS: Quota(
Counter.LLM_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_GPU_CORE_SECONDS: Quota(
Counter.GATEWAY_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.WALLET_DEBITS: Quota(
period=Period.MONTHLY,
),
},
Expand Down Expand Up @@ -730,16 +795,29 @@ class Throttle(BaseModel):
Counter.RECORDS_INGESTED: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_CPU_CORE_SECONDS: Quota(
# TODO(pricing): real free/limit once sandbox billing goes live.
Counter.SANDBOX_CPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_RAM_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_SSD_GIBI_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_GPU_CORE_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_RAM_GIBI_SECONDS: Quota(
Counter.LLM_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_SSD_GIBI_SECONDS: Quota(
Counter.GATEWAY_DEBITS: Quota(
period=Period.MONTHLY,
),
Counter.SANDBOX_GPU_CORE_SECONDS: Quota(
Counter.WALLET_DEBITS: Quota(
period=Period.MONTHLY,
),
},
Expand All @@ -758,6 +836,9 @@ class Throttle(BaseModel):
# name to report under (`REPORTS[key]`).
REPORTS: dict[str, str] = {
Counter.TRACES_INGESTED.value: "traces",
# Wallet/sandbox debit meters are intentionally absent: the wallet total
# is prepaid (money moves at top-up time), so it must never be reported
# to Stripe in arrears.
}

CONSTRAINTS = {
Expand All @@ -771,7 +852,7 @@ class Throttle(BaseModel):
],
Tracker.GAUGES: [
Gauge.USERS,
Gauge.STORAGE_BYTES,
Gauge.BYTES,
],
},
Constraint.READ_ONLY: {
Expand All @@ -782,10 +863,14 @@ class Throttle(BaseModel):
Counter.CREDITS_CONSUMED,
Counter.EVENTS_INGESTED,
Counter.RECORDS_INGESTED,
Counter.SANDBOX_CPU_CORE_SECONDS,
Counter.SANDBOX_RAM_GIBI_SECONDS,
Counter.SANDBOX_SSD_GIBI_SECONDS,
Counter.SANDBOX_GPU_CORE_SECONDS,
Counter.SANDBOX_CPU_CORE_DEBITS,
Counter.SANDBOX_RAM_GIBI_DEBITS,
Counter.SANDBOX_SSD_GIBI_DEBITS,
Counter.SANDBOX_GPU_CORE_DEBITS,
Counter.SANDBOX_DEBITS,
Counter.LLM_DEBITS,
Counter.GATEWAY_DEBITS,
Counter.WALLET_DEBITS,
],
},
}
14 changes: 9 additions & 5 deletions api/ee/src/core/meters/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ class Meters(str, Enum):
CREDITS_CONSUMED = Counter.CREDITS_CONSUMED.value
EVENTS_INGESTED = Counter.EVENTS_INGESTED.value
RECORDS_INGESTED = Counter.RECORDS_INGESTED.value
SANDBOX_CPU_CORE_SECONDS = Counter.SANDBOX_CPU_CORE_SECONDS.value
SANDBOX_RAM_GIBI_SECONDS = Counter.SANDBOX_RAM_GIBI_SECONDS.value
SANDBOX_SSD_GIBI_SECONDS = Counter.SANDBOX_SSD_GIBI_SECONDS.value
SANDBOX_GPU_CORE_SECONDS = Counter.SANDBOX_GPU_CORE_SECONDS.value
SANDBOX_CPU_CORE_DEBITS = Counter.SANDBOX_CPU_CORE_DEBITS.value
SANDBOX_RAM_GIBI_DEBITS = Counter.SANDBOX_RAM_GIBI_DEBITS.value
SANDBOX_SSD_GIBI_DEBITS = Counter.SANDBOX_SSD_GIBI_DEBITS.value
SANDBOX_GPU_CORE_DEBITS = Counter.SANDBOX_GPU_CORE_DEBITS.value
SANDBOX_DEBITS = Counter.SANDBOX_DEBITS.value
LLM_DEBITS = Counter.LLM_DEBITS.value
GATEWAY_DEBITS = Counter.GATEWAY_DEBITS.value
WALLET_DEBITS = Counter.WALLET_DEBITS.value
# GAUGES
USERS = Gauge.USERS.value
STORAGE_BYTES = Gauge.STORAGE_BYTES.value
BYTES = Gauge.BYTES.value


class MeterScope(BaseModel):
Expand Down
Loading
Loading