Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Operator limits: enforce limits and suspension at claim

Closed
#277 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
20/100
Issue type
Feature
Clarity
Clearly specified
Activity status
Active
Tech stack
postgresql, sqlite, typescript
Domain
backend, databases

Research direction

Start in src/runner-api/claim.ts, after the guard and before credential creation, then read src/runs/error-copy.ts and the two READMEs named in the issue. The issue depends on #271 and #272, so those entitlement and usage-record pieces must already exist before this can be built. Done means the test table T01 to T13 passes on both SQLite and Postgres, including the race test T06 and the no-record-on-refusal check T07.

Written by the indexing model from the issue text.

Description

area: app type: feature

Part of #268. Depends on #271 and #272.

Problem

With entitlements (#271) and usage records (#272) in place nothing yet acts on them. A workspace with limits must be refused when it has used them up, a suspended workspace must not run reviews, and the check must be exact under concurrency (two runners claiming at once must not both pass the last unit) and must never leave a trace of consumption for a refused run.

Behavior

  • The claim path (src/runner-api/claim.ts, after the guard and before any credential is created) resolves the workspace's effective entitlements and decides, in this order:
    1. suspended: refuse with the stable code workspace_suspended.
    2. A count limit for the job's kind (diffRunsPerPeriod / dynamicRunsPerPeriod): the number of records of that kind in the workspace with state reserved or settled since period_start; at or above the limit refuses with quota_exhausted (detail diff_runs or dynamic_runs). A limit of 0 means "none allowed". null means unlimited.
    3. The cost limit (managedCostLimitMicroUsd) applies to jobs that run on the operator's key: the sum of settled managed cost_micro_usd since period_start; at or above the limit refuses with quota_exhausted (detail managed_cost). Records with unknown cost add nothing to the sum (they still count in 2).
    4. Otherwise the check and the reservation of #272 happen in one transaction that holds the workspace lock (BEGIN IMMEDIATE on SQLite; a row lock on the entitlements row, or on the workspace row when there is none, on Postgres), so concurrent claims never exceed a count limit.
  • A refused job ends failed with that code, is not handed to a runner, creates no credential, and no usage record: nothing is consumed, nothing is owed. A requeued job after a lost lease keeps its existing record and is not counted again.
  • The cost limit is a soft cap: a run that starts below it may end above it by what the runs in flight at that moment cost. The overshoot is bounded by the number of concurrent runs times the per-run bounds, and is recorded as usage like any other. This is documented, not hidden.
  • A key mode managed with the operator key missing is not a quota matter (#276) and keeps its own code.
  • period_start moving forward (a reset by the operator) takes effect on the next claim: records before it no longer count.
  • The Runs list and detail explain both codes in plain words (src/runs/error-copy.ts requires a sentence per code) without numbers or figures; no pull request message here (#281).
  • The check costs one indexed query per claim; unlimited workspaces skip it.

Scope

Claim path, a small enforce-entitlements.ts module, error codes and copy, indexes, docs (src/runner-api/README.md, src/jobs/README.md), tests.

Out of scope

Pull request notice (#281), usage page (#282), hard cost ceilings per run, anything about how limits are decided.

Test cases

ID kind level case expected
T01 regression integration an instance with no entitlements claim path identical to before; the existing suites pass unchanged
T02 behavior integration diffRunsPerPeriod = 2; third Diff job claimed first two run, third fails quota_exhausted / diff_runs, no runner gets it
T03 behavior integration dynamicRunsPerPeriod = 0 every Dynamic job fails quota_exhausted / dynamic_runs; Diff jobs unaffected
T04 behavior integration managed cost limit reached by settled records next managed job fails quota_exhausted / managed_cost; a job on the workspace's own key is not blocked by it
T05 behavior integration suspended = true every job fails workspace_suspended; clearing it lets the next claim pass
T06 security integration two claims race for the last unit (SQLite and Postgres) exactly one runs
T07 behavior integration refused job no usage_records row, no key handed out, no model call
T08 behavior integration lost lease, job claimed again at the limit it runs; one record
T09 behavior integration period_start moved forward earlier records stop counting immediately
T10 behavior integration a void record does not count
T11 behavior integration unknown-cost settled record counts in the run count, adds nothing to the cost sum
T12 behavior unit error copy one sentence per new code, no number or figure
T13 security integration workspace A at its limit workspace B unaffected
Dominant language
TypeScript
Stars
0
Forks
1
Avg merge
1h 16m
Merged PRs (30d)
226

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from ExpidetGames/code-reviewer

All issues in ExpidetGames/code-reviewer

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.