Repository navigation
Conversation
"Kjør test" had a runner of its own: it graded each case pass or fail, recorded no judge or provenance, and stored a ServiceRun that no publishing gate could read. It now starts an eval run (ADR 0003 as amended by ADR 0040; closes #94 with the two commits before it). - The draft or version is registered once as a capability, and platform.eval.run invokes it for each case. The run is judged by contract, by retrieval when cases name sources and by judge:criteria when there are criteria cases. - The EvalRun is stored when it starts and keeps its state: running, waiting-for-scores, completed, failed or interrupted, with progress, under the same background lease as other work. A run left by an instance that stopped reads as interrupted. - It is stored under the spec it tested, computed as the publish check computes it, so a publishing gate reads a draft's run. Gates, comparisons and the console overview read only completed runs. - The run records the tokens and cost of the answers and of the judge calls, priced from the price list. - The run history shows the run's state, its metrics with confidence intervals, its judges and any incomplete judging. A case passes on its type's own metric; its answer is read from its task. - The optimiser and the model matrix read the latest completed eval run. Test runs from before are no longer listed.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved authorization leaks expose restricted usage and lease data, while race conditions can overwrite completed runs and valid model-free refusals can fail evaluation entirely.
8 open issues
Generic eval endpoints expose unredacted usage and lease data · New Stale lease recovery can overwrite completed results · New Model-free rejections are rejected before contract scoring · New Waiting notices can overwrite completed run state · New Not-applicable cases are omitted from result denominators · New Random capability IDs prevent comparing identical portal runs · New Eval orchestration tasks are misclassified as service work · New Failed runs discard accumulated usage · New
What changed in this PR
This PR moves the portal’s test runs onto the shared evaluation mechanism, connecting draft and version testing to publishing gates, judge provenance, and usage reporting.
Changes:
- Replaces service-run records with eval runs that track lifecycle state, progress, leases, and answer/judge usage.
- Updates gates, comparisons, the optimiser, and model matrix to use completed eval runs.
- Adds metrics, confidence intervals, judge identities, and new run states to the portal and API documentation.
| File | Description |
|---|---|
packages/eval/src/strategy.ts |
Passes run IDs and answer metering into evaluation. |
packages/eval/src/service.ts |
Rejects comparisons involving unfinished runs. |
packages/eval/src/schema.ts |
Adds lifecycle, usage, lease, and tested-version fields. |
packages/eval/src/runner.ts |
Records completion state and answer/judge usage. |
packages/eval/src/platform.test.ts |
Tests gate selection of completed runs. |
packages/eval/src/gates.ts |
Selects the latest completed run per test set. |
packages/eval/src/evaluators.ts |
Adds evaluator usage reporting. |
packages/eval/src/cases.ts |
Exposes criteria-judge usage. |
docs/architecture/system-atlas.html |
Updates the generated eval configuration schema. |
docs/api/work.md |
Documents unified service evaluation behavior. |
apps/portal/src/screens/services/setup-facts.test.ts |
Updates fixtures to the completed state. |
apps/portal/src/screens/services/live-builder.test.tsx |
Updates builder run fixtures. |
apps/portal/src/screens/quality/RunHistory.tsx |
Displays run states, metrics, intervals, and judges. |
apps/portal/src/screens/quality/live-quality.test.tsx |
Tests metric and judge presentation. |
apps/portal/src/mock/quality.ts |
Adds eval states and metrics to mock runs. |
apps/portal/src/i18n/nb/operate-quality.ts |
Adds Norwegian run-state and metric labels. |
apps/portal/src/i18n/en/operate-quality.ts |
Adds English run-state and metric labels. |
apps/portal/src/api/quality.ts |
Updates run types and completed-run selection. |
apps/api/src/service-eval.ts |
Starts leased eval runs and builds service-facing results. |
apps/api/src/routes/console-routes.ts |
Limits console evaluation summaries to completed runs. |
apps/api/src/restart.test.ts |
Tests interrupted-run recovery and live leases. |
apps/api/src/quality.ts |
Reads shared eval records and task-backed answers. |
apps/api/src/quality.test.ts |
Tests provenance, metrics, and publishing-gate lookup. |
apps/api/src/platform-modules.ts |
Exposes eval capability and application version dependencies. |
apps/api/src/openapi/work.ts |
Updates the detailed service eval response schema. |
apps/api/src/openapi/services.ts |
Updates the service eval summary schema. |
apps/api/src/model-matrix.ts |
Estimates answer usage from completed eval runs. |
apps/api/src/main.ts |
Supplies the application version for provenance. |
apps/api/src/improvements.ts |
Reads optimiser evidence from completed eval runs. |
apps/api/src/eval-judges.ts |
Meters criteria-judge calls per run. |
apps/api/src/eval-judges.test.ts |
Checks judge usage reporting. |
apps/api/src/draft-runner.ts |
Adds reusable draft preparation and usage summaries. |
apps/api/src/definition-subject.ts |
Extracts shared spec hashing for gate lookup. |
apps/api/src/composition.ts |
Wires evaluation metering, model families, and dependencies. |
apps/api/src/background-lease.ts |
Aligns optional lease-field types with eval records. |
Review effort: Balanced
…one-eval-runner # Conflicts: # apps/api/src/composition.ts # packages/eval/src/evaluators.ts # packages/eval/src/runner.ts # packages/eval/src/strategy.ts
…y right
- No eval run endpoint shows a run's lease, and usage shows only with
view-usage, also in GET /tenants/{tenant}/eval-runs and
GET /eval-runs/{id}.
- A run left by a stopped instance is marked interrupted only while it
is still as it was read (EvalRunStore.replace), so a late recovery
never overwrites a finished run.
- Progress and waiting notices never move a finished run back.
- A case that reached no model, such as a refusal before it, is scored;
only a run that reached no model at all is refused.
- Progress carries the usage so far, so a failed run keeps what it
spent.
- The summary counts every case, as the case list does.
- A run names the service it tested instead of its one-off draft
capability, so two runs of one configuration can be compared.
- The eval task of a test run counts as testing, so it stays out of
Kjøringer and the service's usage.
…dge off the service's models A portal test run passes the service's own models, fallback included, as avoidAsJudge, so its criteria judge never shares a family with a model the service may answer with.
One kind of run, evaluators that answer later, expected.criteria (upgraded on read) and the criteria judge's identity are built; the rest of the amendment is listed as not built yet.
…ywhere - A run is looked up by its spec without the risk confirmation, which publish stamps with its time, so a risk-confirmed service's gate finds its portal run. - Building-block scores read only completed runs. - A draft that cannot run answers 422 with the reasons. - A criteria case that must also refuse is decided by its criteria. - A run left by a stopped instance reads as interrupted on every eval run endpoint, ended at its last heartbeat, also while waiting for scores. - The run history labels metrics in bokmål, says which judge left cases unscored from the counts, and shows why a run stopped. - The judge is picked again once a case has writers, OpenAPI hides the lease, and the docs say what changed.
|
Ready for review. Copilot and a claim-checking review are done (no blind review) and CI passes except one test this PR does not touch, |
|
Found while testing the stack in the portal: a run of a service with structured output shows no answer.
Fix: show the JSON part when there is no text part, for example its |
A run of a service that answers with a JSON part stored an empty output for every case, so the portal showed only the reference. The output is now the answer's text, or its fields as JSON when it has none, as the judge reads it.
… the judge pick test An empty answer now scores 0 without picking a judge, so the test's first case needs an answer to reach the pick.


Stacked on #193.
What and why
"Kjør test" had a runner of its own: it graded each case pass or fail, recorded no judge or provenance, and stored a
ServiceRunthat no publishing gate could read. It now starts an eval run (ADR 0003 as amended by ADR 0040). Closes #94 with #192 and #193.drafts.prepare), andplatform.eval.runinvokes it for each case. The run is judged bycontract, byretrievalwhen cases name sources and byjudge:criteriawhen there are criteria cases; the judge is picked outside every model the service may answer with (avoidAsJudge).EvalRunis stored when it starts and keeps its state (running,waiting-for-scores,completed,failed,interrupted), with progress and the usage so far, under the same background lease as other portal work. A run left by a stopped instance reads as interrupted on every eval run endpoint, ended at its last heartbeat; a recovery only replaces a run that is still as it was read (EvalRunStore.replace), and late notices never reopen a finished run.answerOfParts), so a service that answers with a JSON part shows what it answered rather than nothing.usageshows only with view-usage. A run names the service it tested inprovenance.services, so two runs of the same version compare, and the eval task counts as testing, not service work.definitionSubjectmoves toapps/api/src/definition-subject.ts. Docs:docs/api/work.md,docs/api/README.md(API changes), ADR 0003 (amendment partly built), ADR 0041, OpenAPI (ServiceEvalRun,ServiceEvalRunSummary, lease omitted). i18n:apps/api/src/i18n/{en,nb}/errors.ts, portaloperate-quality.Not in this PR: score reasons from evaluators and judges are English, as the judges write them.
Screenshots
Before: a test run graded each case pass or fail, with the reason for a failure

After: the run shows its metrics with 95 % confidence intervals; a failed case has no reason yet

How it was tested
CI on this head passes (static, size and the four test shards). The top of the stack (Delete what genki sent to an agency's Langfuse when it purges the case #197) passes
npm run check: 470 files, 5103 tests.New tests: "runs as an eval run whose metrics have confidence intervals and whose judge is named with its model", "feeds a publishing gate: the run is found under the spec it tested, as the gate looks it up", "finds a draft's run under the spec the publish check computes for that draft", "finds a risk-confirmed draft's run under the spec publish checks, though publish stamps the confirmation", "never judges with a model the service may answer with, its fallback too", "reads only completed runs: a newer run still running, failed or interrupted never hides the last completed one", "marks test runs and proposals left running or waiting before the restart as interrupted or failed, ended at their last heartbeat, but not a live run on another instance", "replaces a run only while it is still as it was read, so a late recovery never overwrites a finished run", "never shows the lease of a run, owner and heartbeat, in the list or the run", "shows the cost and tokens of an eval run only with view-usage", "compares two runs of the same version, since a run names the service it tested, not its one-off draft", "compares only completed runs, and finds a run only under its own tenant", "shows no cost for a run whose model has no price, rather than a cost of nothing", "keeps a test run's own eval task out of Kjøringer, as its answers are", "answers 422 with the reasons instead of starting", "counts every case in the summary, so one without a verdict shows as not passed, as in the case list", "decides a criteria case by its criteria, also when it must refuse as well", "never moves a finished run back to waiting or to running progress", "scores a case no model answered, such as a refusal before the model, and reports usage so far with its progress", "picks the judge again once a case has writers, if the first case reached no model", "scores a block by the services' latest completed runs, never a newer run still running or failed", "says in bokmål why a run stopped and which judge left cases unscored", "shows a fields-only answer as the case's output".
The review broke 17 rules on purpose; the four checks no test caught now have the tests above.
In the portal after
npm start: open a service's quality page as Kari Testansatt with the mock login, run its test set, open the result and see the metrics table with confidence intervals.npm run checkpassesChecklist
packages/providers-*docs/architecture/modulesare updated if a contract, boundary or module changed