@@ -325,14 +325,13 @@ async function loadPerRunForId(
325325}
326326
327327// First `YYYY-MM-DD` (optionally `_HH-MM-SS`) anywhere in a run id. Ad-hoc ids
328- // are not date-SHAPED — `parseRunIdDate` anchors, so it rejects them — but
329- // almost all of them still CARRY their date: `adhoc-2026-09-02_21-59-13`,
330- // `2026-05-28_skills-full-codex-gpt54`, `haiku45-skills-suite-2026-05-28`.
328+ // aren't date-SHAPED (parseRunIdDate anchors, so it rejects them) but almost all
329+ // still carry their date: `adhoc-2026-09-02_21-59-13`, `skills-2026-05-28`.
331330const ADHOC_DATE_RE = / ( \d { 4 } ) - ( \d { 2 } ) - ( \d { 2 } ) (?: _ ( \d { 2 } ) - ( \d { 2 } ) - ( \d { 2 } ) ) ? / ;
332331
333- // Cheap, id -only start date for an ad-hoc run; null when the id carries no date.
334- // A date-only id resolves to the start of its day, which is right for "which
335- // runs are newest" and can only lose a same-day tie-break. Exported for testing .
332+ // Id -only start date for an ad-hoc run; null when the id carries no date. A
333+ // date-only id resolves to the start of its day, which can only lose a same-day
334+ // tie-break.
336335export function adhocRunDate ( id : string ) : Date | null {
337336 const m = ADHOC_DATE_RE . exec ( id ) ;
338337 if ( ! m ) return null ;
@@ -362,43 +361,33 @@ export function adhocRunDate(id: string): Date | null {
362361// `YYYY-MM-DD_HH-MM-SS`, so a source-blind key would let a Scribe run and a
363362// skills run with the same id serve each other's projection.
364363//
365- // Per-RUN rather than one loader per source, because `unstable_cache` fixes its
366- // revalidate and tags at construction: a shared loader can only hold one TTL
367- // for every run, and cannot carry a per-run tag for the refresh button to
368- // invalidate. Both of those matter — see `perRunRevalidate` below.
364+ // Per-run rather than per-source because `unstable_cache` fixes revalidate and
365+ // tags at construction: one shared loader can hold neither a per-run TTL nor a
366+ // per-run tag for the refresh button to evict.
369367type PerRunLoader = ( id : string ) => Promise < PerRun > ;
370368
371- // Keyed `<source>:<run>`. Bounded by the run count of every container the
372- // process has served, so it grows with history rather than with traffic.
369+ // Keyed `<source>:<run>`; grows with history, not with traffic.
373370const perRunLoaders = new Map < string , ( ) => Promise < PerRun > > ( ) ;
374371
375- // A run's run.json is written once, at the end of the run (the upload is the
376- // last thing the runner does), so a run that finished yesterday is immutable.
377- // Its SIDECARS are not — meta.json (title/description), reviews and analysis can
378- // be edited in blob afterwards — which is what `runCacheTag` is for.
372+ // A run.json is written once, at the end of the run, so a finished run is
373+ // immutable. Its sidecars (meta.json, reviews, analysis) are not, which is what
374+ // `runCacheTag` is for.
379375const SETTLED_AFTER_MS = 24 * 60 * 60 * 1000 ;
380- // A run whose id says it is still recent. Re-read often, because it may be
381- // today's nightly landing while the page is open.
376+ // Recent enough that today's nightly may still be landing.
382377const FRESH_REVALIDATE_SECONDS = 300 ;
383- // Everything older. The read this avoids is a multi-MB run.json off an Azure
384- // Files mount, and its content cannot change on its own.
378+ // Older than that: the read this avoids is a multi-MB run.json off Azure Files.
385379const SETTLED_REVALIDATE_SECONDS = 24 * 60 * 60 ;
386380
387- // Cache tag for one run's projection, so POST /api/refresh can evict it. Without
388- // this, a settled run edited in blob would keep serving a stale projection to
389- // the front page for a day.
381+ // Cache tag for one run's projection, so POST /api/refresh can evict it;
382+ // without it the button would no-op for anything past SETTLED_AFTER_MS.
390383export function runCacheTag ( sourceId : string , runId : string ) : string {
391384 return `evalboard-run:${ sourceId } :${ runId } ` ;
392385}
393386
394- // Settled runs are cached for a day, everything else for 5 minutes. An id that
395- // carries no date at all is treated as fresh: those are hand-uploaded ad-hoc
396- // runs, re-uploaded far more often than pipeline runs, and there are a handful.
397- //
398- // Evaluated once per run per process, when the loader is first built, so a run
399- // that settles while the process is up keeps the 5-minute TTL until the next
400- // restart. That is the safe direction (it is today's behavior) and not worth a
401- // timer to correct.
387+ // Settled runs cache for a day, everything else for 5 minutes. An id carrying no
388+ // date at all is treated as fresh: those are hand-uploaded and re-uploaded far
389+ // more often than pipeline runs. Evaluated once per run per process, so a run
390+ // that settles while the process is up keeps the short TTL until restart.
402391function perRunRevalidate ( id : string ) : number {
403392 const started = parseRunIdDate ( id ) ?? adhocRunDate ( id ) ;
404393 if ( started == null ) return FRESH_REVALIDATE_SECONDS ;
@@ -1234,28 +1223,19 @@ export function buildAdhocRows(
12341223 } ;
12351224}
12361225
1237- // Extra candidates loaded beyond `limit`, to cover ids that turn out to have no
1238- // readable overview (aborted uploads, and the `deploys/` prefix, which is not a
1239- // run at all) and so get dropped from the rows.
1226+ // Extra candidates loaded beyond `limit`, covering ids with no readable overview
1227+ // (aborted uploads, the `deploys/` prefix) that then drop out of the rows.
12401228const ADHOC_LOAD_SLACK = 10 ;
12411229
12421230// The Ad-hoc runs section (front page, below the daily listing). "Ad-hoc" here
12431231// means "not a daily-pipeline run" — i.e. the id isn't date-shaped, which is
12441232// exactly the set listRunIdsInWindow excludes from the chart and main table.
12451233//
12461234// Only the newest `limit + ADHOC_LOAD_SLACK` candidates are loaded, ordered by
1247- // the date in the id. This section used to load EVERY ad-hoc candidate before
1248- // sorting, on the premise that the set was "small by construction (manual
1249- // uploads only)". That premise expired: nothing expires the ad-hoc prefixes in
1250- // the `runs` container (unlike `runs-gha`'s 14-day rule), so the set had grown
1251- // to 165 runs / ~294 MB of run.json read on every cold front-page render, to
1252- // show ten rows.
1253- //
1254- // The authoritative sort key stays run.json's `start_time` — the id key only
1255- // decides what to LOAD, and the two can only disagree for a run whose id date
1256- // contradicts its own start_time. Ids carrying no date at all are always loaded
1257- // (there are a handful, e.g. `sdk-live-r2-final`), so they can never be ordered
1258- // out of the section by a key they don't have.
1235+ // the date in the id; loading all 165 to show ten rows cost ~294 MB per cold
1236+ // render. run.json's `start_time` stays the authoritative sort, so the id only
1237+ // decides what to READ. Ids with no date are always loaded, so they can never be
1238+ // ordered out by a key they don't have.
12591239export async function getAdhocRunListing (
12601240 limit : number | null ,
12611241 source : Source = DEFAULT_SOURCE ,
@@ -1271,8 +1251,7 @@ export async function getAdhocRunListing(
12711251 else dated . push ( { id, at : at . getTime ( ) } ) ;
12721252 }
12731253 dated . sort ( ( a , b ) => b . at - a . at ) ;
1274- // null limit = "load everything", which the page never asks for but the
1275- // signature allows; a finite limit takes the newest slice plus slack.
1254+ // null limit = "load everything": allowed by the signature, never used.
12761255 const budget = limit == null ? dated . length : limit + ADHOC_LOAD_SLACK ;
12771256 const loadedAll = budget >= dated . length ;
12781257 const toLoad = [ ...undated , ...dated . slice ( 0 , budget ) . map ( ( d ) => d . id ) ] ;
@@ -1282,9 +1261,7 @@ export async function getAdhocRunListing(
12821261 cachedLoadPerRunFor ( source ) ,
12831262 ) ;
12841263 const listing = buildAdhocRows ( perRun , limit ) ;
1285- // `total` drives the "Show more" affordance, so while the load is truncated
1286- // it has to report the candidate count rather than what we happened to read
1287- // — otherwise the section caps itself at the first page. Once everything is
1288- // loaded it reverts to the exact readable-row count.
1264+ // `total` drives "Show more", so a truncated load must report the candidate
1265+ // count or the section caps itself at the first page.
12891266 return loadedAll ? listing : { ...listing , total : ids . length } ;
12901267}
0 commit comments