Operator limits: record usage per run with its measured cost
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 18/100
- Issue type
- Feature
- Clarity
- Clearly specified
- Activity status
- Stale
- Tech stack
- postgresql, sqlite, typescript
Research direction
Start in the job claim and complete paths in src/jobs, where the reserve and settle inserts go, and read src/jobs/README.md and src/runner-api/README.md. Add the usage_records table with its migration and a scoped repository, then cover it in scoping.test.ts and scoped-isolation.integration.test.ts. Done when T01 to T10 pass on both SQLite and Postgres. A linked PR (#297) is already open and the issue depends on #267, so check with maintainers before starting.
Written by the indexing model from the issue text.
Description
Part of #268. Depends on #267 (the exact model cost of Dynamic Review runs): this issue stores the cost that issue reports and does not measure anything itself.
Problem
A job's usage (tokens, costUsd) is stored inside the JSON result of the job. That is enough to show one run, but not to ask "how many Diff and Dynamic reviews did this workspace run since X" or "how much did the operator's key spend", atomically with the decision to start a run, and in a form an external service can read incrementally. Limits (#277), the usage page (#282) and the operator usage API (#278) all need that.
Behavior
- New table
usage_records, one row per job (uniquejob_id), scoped to the workspace:id(monotonic),workspace_id,job_id,review_kind(diff/dynamic),key_mode(ownwhen the job ran on the workspace's key,managedwhen it ran on the operator's key, i.e. thesourceof the resolved LLM access),model_tier(nullable, filled by #280),state(reserved,settled,void),cost_micro_usd(nullable integer: the job'susage.costUsdtimes one million, rounded half up; null when unknown),prompt_tokens,completion_tokens(nullable),reserved_at,settled_at(null until settled). - Reserve. When the claim hands a job to a runner, the record is inserted in the same transaction as the claim (state
reserved). A job that is claimed again after a lost lease reuses its record; it never counts twice. - Settle. When the job completes, the record is settled from the result: with a reported usage it is
settledwith its cost (possibly null) and tokens; a result with no model call made (no usage and the job failed before the model was used: checkout, configuration, lease loss that ends in a failure, cancellation) turns the recordvoid. A record is immutable oncesettledorvoid. voidandreservedrecords exist for the counting rules of #277:reservedandsettledcount,voiddoes not.- Reading is by repository functions only: count of records by kind since a time, sum of settled managed cost since a time, a page of settled records ordered by
(settled_at, id)after a cursor. - Records are small and kept; no retention job in this issue.
- A run on a workspace without any entitlement is recorded as well (key mode from the access source), so that usage exists the moment limits are switched on. This does not change what the run does.
Scope
Table, migration, repository (scoped, classified in scoping.test.ts, rows in scoped-isolation.integration.test.ts), the insert in the claim transaction, the settle in the complete path, tests, docs in src/jobs/README.md and src/runner-api/README.md.
Out of scope
Limits and refusal (#277), runs that fail after using the model (#273), the Diff cost (#274), any endpoint (#278), UI (#282).
Depends on
#267. Can run in parallel with #271.
Test cases
| ID | kind | level | case | expected |
|---|---|---|---|---|
| T01 | behavior | integration | a job is claimed | one reserved record in the same transaction, with kind and key mode of the job |
| T02 | behavior | integration | the job completes with usage.costUsd = 0.0123456 |
record settled, cost_micro_usd = 12346, tokens stored |
| T03 | behavior | integration | the result has costUsd: null |
settled with null cost, not void |
| T04 | behavior | integration | the job fails before any model call | record void |
| T05 | behavior | integration | the lease is lost and the job is claimed again | the same record, still one |
| T06 | security | integration | repository of workspace A | cannot see or settle B's records (scoping tests) |
| T07 | behavior | integration | complete is repeated (alreadyCompleted) | the record is not changed again |
| T08 | regression | integration | a workspace without entitlements | runs exactly as before and still gets a record |
| T09 | behavior | integration | page of settled records after a cursor | ordered by (settled_at, id), stable under inserts, never contains reserved or void |
| T10 | regression | integration | SQLite and Postgres | same shape (parity test) |
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 1
- Avg merge
- 1h 20m
- Merged PRs (30d)
- 309
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from ExpidetGames/code-reviewer
-
area: app type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
ExpidetGames/code-reviewer#585 ·
Maintainers usually reply within 1 day
-
area: app type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
ExpidetGames/code-reviewer#584 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ExpidetGames/code-reviewer#464 ·
Maintainers usually reply within 1 day
-
area: app type: bug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
ExpidetGames/code-reviewer#308 ·
Maintainers usually reply within 1 day
-
type: docs
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
ExpidetGames/code-reviewer#94 ·
Maintainers usually reply within 1 day
All issues in ExpidetGames/code-reviewer
Similar issues
-
native
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
prime-radiant-inc/evener#4839 ·
Maintainers usually reply within 1 day
-
bug needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
processone/fluux-messenger#1790 ·
Maintainers usually reply within 1 day
-
🛣️ Infrastructure
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
opencrvs/opencrvs-core#14232 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
kunchenguid/gh-axi#220 ·
Maintainers usually reply within 1 day