Repository navigation
Store: the Card's used set and run_results at ingest, the run projection query, hidden Evals for redaction, comparison over used runs - #40
Merged
Conversation
…ion query, hidden Evals for redaction, comparison over used runs Stage 5 of #24. Everything in evalhub-store that joins a Card to runs. At Card ingest, after relation resolution, the store takes FOR SHARE on each referenced Eval's record row (in id order, after the Card's own FOR UPDATE; run writes lock only the Eval, so there is no cycle) and fixes the used set per Eval record: the union of attrs.runs over the Card's uses_eval relations resolved to that record, or, when none carries attrs.runs, every run neither archived nor tombstoned at that moment. The visibility of each Eval is re-read under the lock, so an Eval that turned private between resolution and the lock is treated as unresolved. Existence is checked only among Evals the writer can see: a run of an invisible Eval, a missing Eval and a missing run answer with byte-identical run_unknown entries. run_results[] must lie inside the used set (run_not_in_used_set); archived and tombstoned runs are rows and therefore inside it. The store reads body["run_results"] itself, so NewVersion gains no field. Step 8 inserts run_results and card_eval_runs, each used run with its content hash at posting time. relations::add does the same fixing for a later uses_eval edge, grow-only: a version's run_results were checked against the set it had, and a version never changes. runs::project answers GET /evals/{ns}/{name}/runs (stage 7): runs joined with metrics, fingerprints and, per Card named in the request, the run_results of that Card's latest live version; per Card the used_set_hash over current hashes and changed_since_card from the posting-time hashes; keyset paging on (sort key, run_id), NULL-aware; archived and tombstoned rows only when asked and only for a member, a used run that is archived or tombstoned marked as such, archived looking like tombstoned to a non-member; every read in one REPEATABLE READ read-only transaction. run_sql.rs builds the SQL, query_sql stays bound to records JOIN versions. Timestamp literals must be RFC 3339. Cards the caller cannot see, that do not exist or that do not use this Eval are answered identically as unknown. relations::hidden_evals gives the server's redaction its second input: per Card version, the Evals its run_results name that the caller cannot see, with the count to withhold. The comparison view compares the Card's fingerprint with every run in its used set, false if any differs or is missing; an empty used set compares with the header as before 0.2.0, which is what keeps the server's comparison tests green. Rows gain runs_used, used_set_hash and changed_since_card. .sqlx regenerated (17 added, 1 removed). Until stage 7 maps them, CardRunsRejected and RunCardsUnknown reach the server's catch-all; no server path posts run_results or attrs.runs today. Verified: cargo fmt --check; cargo clippy --workspace --all-targets -D warnings; cargo test -p evalhub-store (91 passed, Docker); cargo test -p evalhub-server (60, Docker); cargo sqlx prepare --check against a migrated throwaway Postgres; cargo doc --no-deps -p evalhub-store (0 warnings). Refs #31 Signed-off-by: Yutaka Nishimura <ytk.nishimura@gmail.com>
This was referenced Sep 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stage 5 of 9 of #24 (Runs as first-class rows), on top of stages 1–4 (#36, #37, #38, #39).
evalhub-storeonly: everything that joins a Card to runs. No server code changes; the branch is mergeable on its own. The version bump is stage 9 (#35).What changed (all under
crates/evalhub-store/)records.rs, new step 5bfix_card_runs, Cards only): after relation resolution,FOR SHAREon each referenced Eval's record row in id order (after the Card's ownFOR UPDATE; run writes lock only the EvalFOR UPDATE, so a concurrent run write waits for the Card and the hashes recorded are the committed ones; no lock cycle). The used set per Eval record: the union ofattrs.runsover the Card'score/uses_evalrelations resolved to that record, else every run neither archived nor tombstoned at that moment. Eval visibility is re-read under the lock, so an Eval that turned private between resolution and the lock is treated as unresolved (no used set, edge stored as text,refs_resolvednot awarded). External / unresolved targets get no used set and theirattrs.runsare not checked. A run of an invisible Eval, a missing Eval and a missing run answer byte-identicalrun_unknownentries (tested on serialised bytes).run_results[]outside the used set isrun_not_in_used_set; archived and tombstoned runs are rows and inside it. The store readsbody["run_results"]itself;NewVersiongains no field.resolve_versionnow returnsResolvedVersion { version_id, record_id, record_type }. Step 8 insertsrun_results(oneUNNEST) andcard_eval_runs(each used run with its currentcontent_hash).relations::add(a lateruses_eval): same fixing, grow-only (ON CONFLICT DO NOTHING): a version'srun_resultswere checked against the set it had and a version never changes, so the set keeps its posting-time hashes and gains runs; documented and tested.runs::project(pool, record_id, &RunQuery, include, caller_ns) -> Option<RunPage>with the SQL builder inrun_sql.rs(query_sql::buildstays bound torecords JOIN versions):runsjoined withrun_metrics,run_fingerprintsand, per Card inRunQuery::cards, therun_resultsof that Card's latest live version; per Cardused_set_hash(core'sruns_hashover current hashes),posted_used_set_hash,changed_since_card; keyset paging on(sort key, run_id)withRunCursor, NULL-aware (NULLS LASTasc /NULLS FIRSTdesc, tie-break follows the last key's direction); archived and tombstoned rows only when asked viaincludeand only for a namespace member, plus every run in a requested Card's used set whatever its state, markedstate(archived looks like deleted to a non-member; masked rows expose no status / timestamps / body / metrics / fingerprints, judgements follow the Card); judgement filters useEXISTS, judgement sorts take the best non-null value asquery_sqldoes forresults; timestamp literals must be RFC 3339 (elseQueryUnsupported),prefix/containsrefused on timestamps; every dynamic value bound or escaped; all reads in oneREPEATABLE READ, READ ONLYtransaction. A Card the caller cannot see, that does not exist, has no live version or has nouses_evalinto this Eval is answered identically asRunCardsUnknown([names]). An invisible / missing / header-less Eval isNone, asruns::get.relations::hidden_evals(card_version_ids, caller_ns)→ per Card version the Evals itsrun_resultsname that the caller cannot see (eval_record_id,ns,name, count); no run ids in the return. Second input for the server's redaction besidehidden_targets.relations::cards_using_eval):same_harness/same_modelcompare the Card's fingerprint with every run in its used set, false if any differs or is missing; an empty used set (no runs, or an explicitattrs.runs: []) compares with the header as before 0.2.0, which is what keepsevalhub-server/tests/relations.rsgreen as Store: the Card's used set and run_results at ingest, the run projection query, hidden Evals for redaction, comparison over used runs #31 requires. Rows gainruns_used,used_set_hash,changed_since_card(a list of run ids).StoreError::CardRunsRejected(Vec<ErrorEntry>),StoreError::RunCardsUnknown(Vec<String>).used_set.rs(new, private):lock_evals,fix,check_run_results,insert,read,summarise.query_sql.rs: nine helperspub(crate)for reuse, no behaviour change (diffed againstmain).push_key_valuetakes awhatargument so a bad filter literal no longer reports "cursor timestamp".records.rsingest steps, "A Card's runs", the lock order;relations.rs;runs.rs"The projection";run_sql.rsmodule doc mirroringquery_sql's; crate doc (Runs, Visibility, Modules)..sqlx: 17 added, 1 removed (the oldresolve_versionquery).tests/cards_runs.rs(10): used set with / withoutattrs.runsand as a union over twouses_evalto different seqs;run_unknownbyte-identical for a missing run and an invisible Eval's run;run_not_in_used_set; archived / tombstoned still count;card_eval_runsholds the posting-time hash, an overwrite lands inchanged_since_cardand movesused_set_hash; projection filter / sort / page onstatus,error.kind, a facet key,metrics[x],results[c][m].value/.label,started_at, with NULL cursors and tie-breaks;include=archivedmember-only; a private Card named by an outsider, a missing Card and a Card not using this Eval are unknown;hidden_evals;same_modelfalse when one used run differs; a lateruses_evaledge only grows the set.tests/relations.rs: theaddcase withattrs.runs ["r9"]seeds a run row and checkscard_eval_runs; a second case assertsrun_unknownat/attrs/runs/1with no edge written.run_sqlunit tests (5) incl. a non-RFC 3339 literal.Verified
cargo fmt --checkcargo clippy --workspace --all-targets -- -D warningscargo test -p evalhub-store(Docker)cargo test -p evalhub-server(Docker)cargo sqlx prepare --check(throwawaypostgres:16-alpine, migrations 0001–0003).sqlxfilescargo doc --no-deps -p evalhub-storeNot run:
cargo deny check(no dependency change),just e2e, schema / core / query tests (untouched crates).Recorded here, decided later
relations::add(above): Store: the Card's used set and run_results at ingest, the run projection query, hidden Evals for redaction, comparison over used runs #31 says "the same used-set fixing"; recomputing from scratch could leave a version's already-checkedrun_resultsoutside its own set. Kept grow-only; owner to confirm.false.CardRunsRejected/RunCardsUnknownreach the server's catch-all until stage 7 maps them to 422. No server test, fixture or e2e postsrun_resultsorattrs.runstoday; a client that does gets 500 where it got 201 before this stage.changed_since_cardin a comparison row is an unbounded list, and each projection page hashes the whole used set of every named Card. Fine at the 100 000run_resultscap; stage 7 decides whether the envelope carries the list or a count.Refs #31