Skip to content

feat: show what a project is spending, and cap it per project - #288

Merged
AKogut merged 1 commit into
mainfrom
feat/usage-visibility-2
Aug 18, 2026
Merged

feat: show what a project is spending, and cap it per project#288
AKogut merged 1 commit into
mainfrom
feat/usage-visibility-2

Conversation

@AKogut

@AKogut AKogut commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Toward #73"an operator can see and cap AI + storage spend per tenant". Capping half-existed; seeing did not exist at all.

The cap was not per tenant

The budget is enforced — the worker sums the day's tokenCost and stops calling the model, with a metric when it trips. But it was read only from FLAKEMETRY_AI_DAILY_TOKEN_BUDGET, so ai.dailyTokenBudget in a project's flakemetry.yml was documented in the config reference, validated by the schema, and ignored. One instance could have exactly one budget.

It is now a policy field with the same default → UI → env tiers as everything else.

Nothing showed the spend

From the dashboard, a project with root-cause analysis switched on and a project that quietly stopped analysing at noon looked identical. The only trace was a metric counter nobody self-hosting is scraping.

The Data page now shows it, verified against a real database:

AI tokens today          2,148 of 200,000
Root-cause reports today 1
Executions stored        201 across 24 runs and 8 tests
Artifacts                object storage is not configured

The paused state is stated outright rather than left to be inferred from a number. Listing a bucket is both the only way to know what storage holds and the slowest thing on the page, so an unreachable store degrades that one row rather than breaking the page.

Three guards fired, all correctly

Every one of them on code written in this same change:

  • policy-env.test.ts — a new policy field with no environment tier
  • the tenant isolation coverage guard — getProjectUsage not exercised
  • the export credential classifier — ai_daily_token_budget matches token and needed classifying as not a credential

Third time the isolation guard has caught something added after it was written, which is the whole argument for coverage guards over a fixed list of cases.

Still open on #73

Alerts when a budget is approached, and storage tiering. Both need somewhere to send an alert and a decision about what tiering means here — not things to guess at.

62/62 turbo tasks.

#73 asks that an operator can see and cap AI and storage spend per tenant.
Capping half-existed and seeing did not exist at all.

The daily token budget was enforced — the worker sums the day's tokenCost and
stops calling the model — but read only from the environment, so
`ai.dailyTokenBudget` in a project's flakemetry.yml was documented, validated
and ignored. One instance could have exactly one budget, which is not what
"per tenant" means. It is now a policy field with the same default/UI/env tiers
as everything else.

Nothing showed the spend. From the dashboard, a project with root-cause
analysis switched on and a project that quietly stopped analysing at noon
looked identical; the only trace was a metric counter. The Data page now shows
tokens against budget, reports today, rows stored, artifact objects and bytes,
and the oldest execution — with the paused state stated outright.

Bucket listing is the slow part and the only way to know what storage holds, so
an unreachable store degrades that one row rather than the page.

Three guards written earlier fired on this change and all three were right: a
new policy field with no environment tier, a new project-scoped query missing
from the tenant isolation suite, and a new column matching the credential
pattern needing classification. That is the third time the isolation guard has
caught something added in the same session it was written for.
@AKogut
AKogut merged commit cd50173 into main Aug 18, 2026
8 checks passed
@AKogut
AKogut deleted the feat/usage-visibility-2 branch August 18, 2026 13:20
AKogut added a commit that referenced this pull request Aug 18, 2026
…289)

The alerting half of #73, on email as asked — and on every other
channel, because it goes through the notification path that already
exists rather than a new one.

## What it fixes

The budget stops root-cause analysis and recorded that in a **metric
counter**. On a self-hosted instance nobody is scraping it, so
explanations simply stopped appearing partway through the afternoon and
nothing anywhere said why. #288 made the state visible on a page you
have to go and look at; this one comes to you.

```
Subject: [Flakemetry] AI budget spent for today

Root-cause analysis is paused until tomorrow — 200,000 of 200,000 tokens used today.

Spent: 200,000
Budget: 200,000
```

The message says *analysis is paused* rather than only reporting
numbers. Someone reading this in a mail client has no dashboard open,
and "budget exceeded" alone does not tell them anything stopped.

## Deduplicated per project per day

The budget is re-checked on **every run**, so without a key that
collapses them a busy afternoon sends one of these per failing suite —
which is how a useful alert becomes a filter rule.

## My first test proved nothing

It emitted the event on the bus and watched a channel receive it. That
tests the routing — bus name, notification type, subscription list — and
all of it passed with the emit inside RCA **deleted**:

```
$ # emit removed from rca.ts
Tests  1 passed        ← the routing test, still green
```

Found by deleting the emit, which is the only way that gap shows up. The
real test drives `processFailures` until the budget is exhausted and
fails when the emit is gone:

```
× announces that the budget is spent rather than only counting it
AssertionError: expected [] to have a length of 1
```

Both tests are kept: one covers the wiring between three pieces that
must agree, the other covers the thing that triggers it.

Getting the second one to fire took two attempts — the first used a
second failure whose message normalised close enough to the first that
it was not a new signature, so the loop skipped it before reaching the
budget check. Worth knowing that the budget only applies to signatures
that would actually call the model.

62/62 turbo tasks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant