fffiloni commited on
Commit
3331cc9
·
verified ·
1 Parent(s): 034f807

Upload 8 files

Browse files
Files changed (3) hide show
  1. web/static/app.js +31 -3
  2. web/static/progress.js +2 -0
  3. web/static/runs.js +95 -32
web/static/app.js CHANGED
@@ -1291,6 +1291,33 @@ function showValidationRunDetail(detail={}){
1291
  switchCenterTab('validate');
1292
  if(state.validationRunId&&validationCanonicalStatus(detail)==='running')startValidationPolling(state.validationRunId);
1293
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1294
  function stopValidationPolling(){
1295
  if(state.validationPoll){clearInterval(state.validationPoll);state.validationPoll=null}
1296
  state.validationPollToken=null;
@@ -1301,11 +1328,12 @@ async function refreshValidationProgress(pollToken=state.validationPollToken){
1301
  try{
1302
  const p=await apiGet(`/api/runs/${encodeURIComponent(requestedRunId)}/progress?bucket_name=${encodeURIComponent(state.bucketName)}&include_job_logs=1`);
1303
  if(pollToken!==state.validationPollToken||requestedRunId!==state.validationRunId)return;
1304
- if(shouldIgnoreValidationUpdate(p,requestedRunId)){const terminal=terminalValidationForRun(requestedRunId);if(terminal){renderValidationStatus({...terminal.detail,_refreshing:true});stopValidationPolling();loadRuns(false);}return;}
1305
  state.validationPollErrors=0;
1306
  renderValidationStatus(p);
 
1307
  const canonical=validationCanonicalStatus(p);
1308
- if(validationIsTerminalStatus(canonical)){state.validationMode='view';rememberTerminalValidation(p);stopValidationPolling();showMessage(`Validation finished: ${badgeLabel(canonical)}`,statusClass(canonical)==='success'?'success':statusClass(canonical)==='error'?'error':'warning');loadRuns(false)}
1309
  }catch(e){
1310
  if(pollToken!==state.validationPollToken||requestedRunId!==state.validationRunId)return;
1311
  state.validationPollErrors+=1;
@@ -1315,7 +1343,7 @@ async function refreshValidationProgress(pollToken=state.validationPollToken){
1315
  }
1316
  function startValidationPolling(runId){
1317
  const terminal=terminalValidationForRun(runId);
1318
- if(terminal){stopValidationPolling();state.validationMode='view';state.validationRunId=runId;renderValidationStatus({...terminal.detail,_refreshing:true});return;}
1319
  stopValidationPolling();
1320
  state.validationMode='running';
1321
  state.validationRunId=runId;
 
1291
  switchCenterTab('validate');
1292
  if(state.validationRunId&&validationCanonicalStatus(detail)==='running')startValidationPolling(state.validationRunId);
1293
  }
1294
+
1295
+ function linkedValidationParentFromDetail(detail={}){
1296
+ return firstNonEmpty(
1297
+ detail.parent_build_run_id,
1298
+ detail.summary?.parent_build_run_id,
1299
+ detail.state?.parent_build_run_id,
1300
+ detail.launch?.parent_build_run_id,
1301
+ state.linkedValidationParent,
1302
+ state.validationPrefillDraft?.parentBuildRunId,
1303
+ state.validationPrefillDraft?.sourceRunId
1304
+ );
1305
+ }
1306
+ async function refreshLinkedValidationParentAfterTerminal(detail={}){
1307
+ const parentId=linkedValidationParentFromDetail(detail);
1308
+ if(!parentId)return;
1309
+ try{
1310
+ const snapshot=await fetchRunViewSnapshotOnce(parentId,{includeJobLogs:false});
1311
+ if(snapshot){
1312
+ if(typeof rememberRunDetail==='function')rememberRunDetail(parentId,snapshot,true);
1313
+ if(typeof upsertRunExplorerFromProgress==='function')upsertRunExplorerFromProgress(snapshot);
1314
+ if(state.runId===parentId&&typeof renderRunViewSnapshot==='function')renderRunViewSnapshot(snapshot);
1315
+ if(typeof renderRunStats==='function')renderRunStats(state.runsCache||[]);
1316
+ }
1317
+ }catch(err){console.warn('linked validation parent refresh failed',err)}
1318
+ try{await loadRuns(true)}catch(err){console.warn('linked validation runs refresh failed',err)}
1319
+ }
1320
+
1321
  function stopValidationPolling(){
1322
  if(state.validationPoll){clearInterval(state.validationPoll);state.validationPoll=null}
1323
  state.validationPollToken=null;
 
1328
  try{
1329
  const p=await apiGet(`/api/runs/${encodeURIComponent(requestedRunId)}/progress?bucket_name=${encodeURIComponent(state.bucketName)}&include_job_logs=1`);
1330
  if(pollToken!==state.validationPollToken||requestedRunId!==state.validationRunId)return;
1331
+ if(shouldIgnoreValidationUpdate(p,requestedRunId)){const terminal=terminalValidationForRun(requestedRunId);if(terminal){renderValidationStatus({...terminal.detail,_refreshing:true});stopValidationPolling();await refreshLinkedValidationParentAfterTerminal(terminal.detail);}return;}
1332
  state.validationPollErrors=0;
1333
  renderValidationStatus(p);
1334
+ if(typeof upsertRunExplorerFromProgress==='function')upsertRunExplorerFromProgress(p);
1335
  const canonical=validationCanonicalStatus(p);
1336
+ if(validationIsTerminalStatus(canonical)){state.validationMode='view';rememberTerminalValidation(p);stopValidationPolling();showMessage(`Validation finished: ${badgeLabel(canonical)}`,statusClass(canonical)==='success'?'success':statusClass(canonical)==='error'?'error':'warning');await refreshLinkedValidationParentAfterTerminal(p)}
1337
  }catch(e){
1338
  if(pollToken!==state.validationPollToken||requestedRunId!==state.validationRunId)return;
1339
  state.validationPollErrors+=1;
 
1343
  }
1344
  function startValidationPolling(runId){
1345
  const terminal=terminalValidationForRun(runId);
1346
+ if(terminal){stopValidationPolling();state.validationMode='view';state.validationRunId=runId;renderValidationStatus({...terminal.detail,_refreshing:true});refreshLinkedValidationParentAfterTerminal(terminal.detail);return;}
1347
  stopValidationPolling();
1348
  state.validationMode='running';
1349
  state.validationRunId=runId;
web/static/progress.js CHANGED
@@ -575,6 +575,8 @@ function timelineDetailStatusLabel(status=''){
575
  if(s==='verified')return'Verified';
576
  if(s==='fallback')return'Fallback';
577
  if(s==='building')return'Building';
 
 
578
  if(s==='warning'||s==='warn')return'Needs attention';
579
  if(s==='running'||s==='waiting')return'In progress';
580
  if(s==='not_needed'||s==='skipped')return'Not needed';
 
575
  if(s==='verified')return'Verified';
576
  if(s==='fallback')return'Fallback';
577
  if(s==='building')return'Building';
578
+ if(s==='queued')return'Queued';
579
+ if(s==='blocked')return'Blocked';
580
  if(s==='warning'||s==='warn')return'Needs attention';
581
  if(s==='running'||s==='waiting')return'In progress';
582
  if(s==='not_needed'||s==='skipped')return'Not needed';
web/static/runs.js CHANGED
@@ -60,7 +60,7 @@ function runHasPersistedGenerationSuccess(run={}){
60
  }
61
  function runHasHardTerminalProblem(run={}){
62
  const canon=runCanonicalStatus(run);
63
- return ['failed','manual_hardware_required','technical_blocker','technical_blocker_boot_only','auth_refresh_required','stale','stopped'].includes(canon);
64
  }
65
  function normalizeExplorerStatusValue(value){return String(value||'').trim().toLowerCase().replace(/\s+/g,'_')}
66
  function explorerTextHas(text,needles=[]){text=String(text||'').toLowerCase();return needles.some(x=>text.includes(x))}
@@ -91,7 +91,7 @@ function explicitExplorerStatus(run={}){
91
  run.status,run.final_status,run.verdict,run.result_status
92
  ].map(normalizeExplorerStatusValue).filter(Boolean);
93
  for(const s of values){
94
- if(['full_inference_success','partial_validation','manual_hardware_required','technical_blocker','technical_blocker_boot_only','failed','stale','stopped','cancelled','canceled','auth_refresh_required','validated_after_space_test','recovered_by_space_test','validated_after_manual_space_test','recovered_by_manual_validation','demo_usable_full_promise_not_verified','interactive_app_available_smoke_failed','manual_test_required_smoke_failed','placeholder_scaffold_deployed'].includes(s))return s==='cancelled'||s==='canceled'?'stopped':s;
95
  if(s==='full_inference_candidate_health_passed'||s==='health_only'||s==='partial'||s==='completed_with_warnings')return 'partial_validation';
96
  }
97
  return '';
@@ -435,7 +435,45 @@ function linkedValidationRowsFromMetadata(run={}){
435
  }
436
  return rows.filter(x=>x&&typeof x==='object');
437
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  function linkedValidationCountFromMetadata(run={},rows=[]){
 
439
  const counts=[
440
  run.linked_validation_count,
441
  run.summary?.linked_validation_count,
@@ -446,32 +484,39 @@ function linkedValidationCountFromMetadata(run={},rows=[]){
446
  run.linked_validations?.count,
447
  run.summary?.linked_validations?.count
448
  ].map(Number).filter(n=>Number.isFinite(n)&&n>0);
449
- return Math.max(rows.length,...counts,0);
450
  }
451
- function linkedValidationRowStatus(row={}){
452
- row=safeRunObject(row);
453
- return String(row.status||row.validation_status||row.result_status||row.verdict||row.effective_status||row.summary?.status||'').toLowerCase();
 
 
 
 
 
 
 
 
 
 
 
454
  }
455
  function buildManualValidationStatus(run={}){
456
  run=safeRunObject(run);
457
  const explicit=run.post_build_validation||run.effective_run_status?.post_build_validation||run.summary?.post_build_validation||run.summary?.effective_run_status?.post_build_validation||run.manual_validation_status||run.summary?.manual_validation_status||{};
458
  const metadataRows=linkedValidationRowsFromMetadata(run);
459
- const cacheRows=linkedValidationsForBuild(run.run_id).filter(v=>!runMatchesStatus(v,'running'));
460
- const rows=[...metadataRows,...cacheRows];
461
- const explicitStatus=String(explicit.status||explicit.validation_status||explicit.result_status||explicit.verdict||'').toLowerCase();
462
- const metadataCount=linkedValidationCountFromMetadata(run,metadataRows);
463
- const linkedCount=Math.max(metadataCount,cacheRows.length,explicitStatus?1:0);
464
  const passed=Boolean(
465
  run.manual_validation_passed||run.summary?.manual_validation_passed||
466
- explicitStatus==='success'||explicitStatus==='passed'||explicitStatus==='full_inference_success'||explicitStatus==='validated_after_space_test'||explicitStatus==='validated_after_manual_space_test'||
467
- rows.some(v=>{const st=linkedValidationRowStatus(v);return st==='success'||st==='passed'||st==='full_inference_success'||st==='validated_after_space_test'||st==='validated_after_manual_space_test'||runMatchesStatus(v,'full_inference_success');})
468
- );
469
- const failed=Boolean(
470
- explicitStatus==='failed'||explicitStatus==='failure'||explicitStatus==='error'||
471
- rows.some(v=>{const st=linkedValidationRowStatus(v);return st==='failed'||st==='failure'||st==='error'||runMatchesStatus(v,'failed');})
472
  );
 
473
  const latest=rows[0]||{};
474
- return {passed,failed,linkedCount,latest,explicit,metadataRows,cacheRows};
475
  }
476
  function buildEffectiveStatus(run={}){
477
  // Kept for legacy callers, but now delegates to the deterministic Runs Explorer status model.
@@ -501,17 +546,18 @@ function percentile(values=[],p=0.5){
501
  return nums[lo]+(nums[hi]-nums[lo])*(idx-lo);
502
  }
503
  function pct(part,total){return total?`${Math.round((part/total)*100)}%`:'—'}
504
- function runStatsIssueCounts(runs=[]){
505
  runs=validRunsList(runs);
506
  const counts={failed_builds:0,failed_linked_tests:0,manual_hardware:0,pi_model_mismatch:0,stale:0};
 
507
  for(const r of runs){
508
  const validation=isValidationRunSummary(r);
509
  const status=String(r.status||'').toLowerCase();
510
  const pi=r.pi_model_resolution||{};
511
- if(runMatchesStatus(r,'failed')){if(validation&&isLinkedValidationRun(r))counts.failed_linked_tests++;else if(!validation)counts.failed_builds++;}
512
  if(!validation&&runMatchesStatus(r,'manual_hardware_required')) counts.manual_hardware++;
513
  if(!validation&&(pi.mismatch||status.includes('model_mismatch'))) counts.pi_model_mismatch++;
514
- if(status.includes('stale')) counts.stale++;
515
  }
516
  return counts;
517
  }
@@ -524,7 +570,9 @@ function renderRunAggregateStats(){
524
  if(!root)return;
525
  const runs=validRunsList(state.runsCache||[]);
526
  const builds=runs.filter(r=>!isValidationRunSummary(r));
527
- const validations=runs.filter(r=>isLinkedValidationRun(r));
 
 
528
  const buildStatuses=builds.map(r=>({run:r,status:buildExplorerStatus(r)}));
529
  const runningBuilds=buildStatuses.filter(x=>x.status.filter_buckets?.has('running')).length;
530
  const failedBuilds=buildStatuses.filter(x=>x.status.filter_buckets?.has('failed')).length;
@@ -536,17 +584,17 @@ function renderRunAggregateStats(){
536
  const needsValidation=terminalBuilds.filter(x=>runNeedsLinkedValidation(x.run)).length;
537
  // Legacy aggregate labels kept for regression tests: Total runs / Test response time / Typical / slowest recent / Build success / Validation pass.
538
  // Legacy formula kept for audit readability: pct(successBuilds,terminalBuilds.length) and pct(passedValidations,terminalValidations.length).
539
- const passedLinked=validations.filter(r=>!runMatchesStatus(r,'running')&&runMatchesStatus(r,'full_inference_success')).length;
540
- const terminalValidations=validations.filter(r=>!runMatchesStatus(r,'running'));
541
  const passedValidations=passedLinked;
542
  const latencies=validations.map(runStatLatencyValue).filter(v=>v!==null);
543
  const median=percentile(latencies,.5);
544
  const p95=percentile(latencies,.95);
545
- const issues=runStatsIssueCounts(runs);
546
  const issueTotal=Object.values(issues).reduce((a,b)=>a+b,0);
547
  const badge=$('runStatsBadge');
548
  if(badge){
549
- badge.textContent=builds.length?`${builds.length} builds · ${validations.length} linked tests`:'No builds';
550
  badge.className='badge '+(failedBuilds||issueTotal?'warn':runningBuilds?'running':builds.length?'success':'neutral');
551
  }
552
  if(!builds.length){
@@ -562,7 +610,7 @@ function renderRunAggregateStats(){
562
  ].filter(([,count])=>count>0);
563
  root.innerHTML=`
564
  <div class="run-stats-grid build-first-stats">
565
- ${runStatsCell('Build Runs',String(builds.length),`${validations.length} linked Space Test${validations.length===1?'':'s'}`,'neutral')}
566
  ${runStatsCell('Build status',`${runningBuilds} running`,`${failedBuilds} failed` ,failedBuilds?'warn':runningBuilds?'running':'success')}
567
  ${runStatsCell('Automatic success',pct(automaticSuccess,terminalBuilds.length),`${automaticSuccess}/${terminalBuilds.length} finished builds`,'success')}
568
  ${runStatsCell('Effective success',pct(effectiveSuccess,terminalBuilds.length),`${effectiveSuccess}/${terminalBuilds.length} after linked tests`,'success')}
@@ -618,7 +666,7 @@ function runExplorerNeedsBackgroundHydration(run={}){
618
  const runId=run.run_id||run.summary?.run_id;
619
  if(!runId||isRunLocallyDeleted(runId))return false;
620
  const canonical=buildEffectiveStatus(run);
621
- if(['full_inference_success','failed','manual_hardware_required','technical_blocker','technical_blocker_boot_only','auth_refresh_required','stale','stopped','running'].includes(canonical))return false;
622
  return ['partial_validation','health_only','partial','completed_with_warnings','unknown',''].includes(String(canonical||'').toLowerCase());
623
  }
624
  function scheduleRunExplorerBackgroundHydration(runs=[]){
@@ -803,8 +851,16 @@ function upsertRunExplorerFromProgress(p={}){
803
  recommended_zerogpu_duration_seconds:p.summary?.recommended_zerogpu_duration_seconds||p.generation_smoke?.recommended_zerogpu_duration_seconds||p.generation_smoke?.recommended_zero_gpu_duration_seconds,
804
  kind:p.summary?.kind||p.state?.kind||'universal_model_card_builder',
805
  parent_build_run_id:p.summary?.parent_build_run_id||p.state?.parent_build_run_id,
806
- manual_validation_passed:p.summary?.manual_validation_passed||p.manual_validation_status?.status==='success',
807
- effective_status:p.summary?.effective_status||p.manual_validation_status?.effective_status,
 
 
 
 
 
 
 
 
808
  bucket_source:p.bucket_source||p.summary?.bucket_source||state.bucketSource,
809
  };
810
  state.runsCache=validRunsList(state.runsCache||[]);
@@ -815,8 +871,15 @@ function upsertRunExplorerFromProgress(p={}){
815
  if(parentId&&isValidationRunSummary(summary)){
816
  const parentIdx=state.runsCache.findIndex(r=>(r.run_id||r.summary?.run_id)===parentId);
817
  if(parentIdx>=0){
818
- const passed=validationExplorerStatus(summary).display_status==='full_inference_success';
819
- state.runsCache[parentIdx]={...state.runsCache[parentIdx],manual_validation_passed:state.runsCache[parentIdx].manual_validation_passed||passed,post_build_status:passed?'validated_after_space_test':state.runsCache[parentIdx].post_build_status,effective_status:passed?'validated_after_space_test':state.runsCache[parentIdx].effective_status,updated_at:summary.updated_at||state.runsCache[parentIdx].updated_at};
 
 
 
 
 
 
 
820
  }
821
  }
822
  state.runsLastLoaded=Date.now();
 
60
  }
61
  function runHasHardTerminalProblem(run={}){
62
  const canon=runCanonicalStatus(run);
63
+ return ['failed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','technical_blocker_boot_only','blocked','completed_with_warnings','auth_refresh_required','stale','stopped'].includes(canon);
64
  }
65
  function normalizeExplorerStatusValue(value){return String(value||'').trim().toLowerCase().replace(/\s+/g,'_')}
66
  function explorerTextHas(text,needles=[]){text=String(text||'').toLowerCase();return needles.some(x=>text.includes(x))}
 
91
  run.status,run.final_status,run.verdict,run.result_status
92
  ].map(normalizeExplorerStatusValue).filter(Boolean);
93
  for(const s of values){
94
+ if(['full_inference_success','partial_validation','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','technical_blocker_boot_only','blocked','completed_with_warnings','failed','stale','stopped','cancelled','canceled','auth_refresh_required','validated_after_space_test','recovered_by_space_test','validated_after_manual_space_test','recovered_by_manual_validation','demo_usable_full_promise_not_verified','interactive_app_available_smoke_failed','manual_test_required_smoke_failed','placeholder_scaffold_deployed'].includes(s))return s==='cancelled'||s==='canceled'?'stopped':s;
95
  if(s==='full_inference_candidate_health_passed'||s==='health_only'||s==='partial'||s==='completed_with_warnings')return 'partial_validation';
96
  }
97
  return '';
 
435
  }
436
  return rows.filter(x=>x&&typeof x==='object');
437
  }
438
+ const LINKED_VALIDATION_RUNNING_STATUSES=new Set(['','none','unknown','unchanged','idle','pending','queued','running','started','building','waiting','scheduled']);
439
+ const LINKED_VALIDATION_TERMINAL_STATUSES=new Set(['success','passed','succeeded','full_inference_success','validated_after_space_test','validated_after_manual_space_test','recovered_by_space_test','recovered_by_manual_validation','manual_validation_passed','manual_validated','partial_validation','failed','failure','error','auth_refresh_required','stale','stopped','cancelled','canceled','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','technical_blocker_boot_only','blocked','completed_with_warnings']);
440
+ const LINKED_VALIDATION_PASS_STATUSES=new Set(['success','passed','succeeded','full_inference_success','validated_after_space_test','validated_after_manual_space_test','recovered_by_space_test','recovered_by_manual_validation','manual_validation_passed','manual_validated']);
441
+ const LINKED_VALIDATION_FAIL_STATUSES=new Set(['failed','failure','error','auth_refresh_required','stale','stopped','cancelled','canceled','technical_blocker','technical_blocker_boot_only']);
442
+ function linkedValidationRowStatus(row={}){
443
+ row=safeRunObject(row);
444
+ return normalizeExplorerStatusValue(row.status||row.validation_status||row.result_status||row.verdict||row.effective_status||row.summary?.status||row.state?.status||'');
445
+ }
446
+ function linkedValidationIsTerminalForAccounting(row={}){
447
+ const st=linkedValidationRowStatus(row);
448
+ if(LINKED_VALIDATION_RUNNING_STATUSES.has(st))return false;
449
+ if(LINKED_VALIDATION_TERMINAL_STATUSES.has(st))return true;
450
+ return runIsFinished(st)&&!LINKED_VALIDATION_RUNNING_STATUSES.has(st);
451
+ }
452
+ function linkedValidationPassedForAccounting(row={}){
453
+ const st=linkedValidationRowStatus(row);
454
+ return LINKED_VALIDATION_PASS_STATUSES.has(st);
455
+ }
456
+ function linkedValidationFailedForAccounting(row={}){
457
+ const st=linkedValidationRowStatus(row);
458
+ return LINKED_VALIDATION_FAIL_STATUSES.has(st);
459
+ }
460
+ function linkedValidationAccountingKey(row={},idx=0){
461
+ return String(row.validation_run_id||row.run_id||row.summary?.run_id||row.state?.run_id||row.id||`${row.parent_build_run_id||''}:${row.updated_at||row.validated_at||row.created_at||idx}`);
462
+ }
463
+ function dedupeLinkedValidationRows(rows=[]){
464
+ const seen=new Set();
465
+ const out=[];
466
+ (Array.isArray(rows)?rows:[]).forEach((row,idx)=>{
467
+ if(!row||typeof row!=='object')return;
468
+ const key=linkedValidationAccountingKey(row,idx);
469
+ if(key&&seen.has(key))return;
470
+ if(key)seen.add(key);
471
+ out.push(row);
472
+ });
473
+ return out;
474
+ }
475
  function linkedValidationCountFromMetadata(run={},rows=[]){
476
+ const explicitRows=Array.isArray(rows)?rows.filter(linkedValidationIsTerminalForAccounting):[];
477
  const counts=[
478
  run.linked_validation_count,
479
  run.summary?.linked_validation_count,
 
484
  run.linked_validations?.count,
485
  run.summary?.linked_validations?.count
486
  ].map(Number).filter(n=>Number.isFinite(n)&&n>0);
487
+ return Math.max(explicitRows.length,...counts,0);
488
  }
489
+ function linkedValidationExplicitTerminalRow(explicit={}){
490
+ if(!explicit||typeof explicit!=='object')return null;
491
+ return linkedValidationIsTerminalForAccounting(explicit)?explicit:null;
492
+ }
493
+ function linkedValidationTerminalRowsForBuild(run={}){
494
+ run=safeRunObject(run);
495
+ const metadataRows=linkedValidationRowsFromMetadata(run);
496
+ const cacheRows=linkedValidationsForBuild(run.run_id).filter(linkedValidationIsTerminalForAccounting);
497
+ return dedupeLinkedValidationRows([...metadataRows,...cacheRows]).filter(linkedValidationIsTerminalForAccounting);
498
+ }
499
+ function linkedValidationAccountingRowsForBuilds(builds=[]){
500
+ const rows=[];
501
+ validRunsList(builds).forEach(build=>rows.push(...linkedValidationTerminalRowsForBuild(build)));
502
+ return dedupeLinkedValidationRows(rows).filter(linkedValidationIsTerminalForAccounting);
503
  }
504
  function buildManualValidationStatus(run={}){
505
  run=safeRunObject(run);
506
  const explicit=run.post_build_validation||run.effective_run_status?.post_build_validation||run.summary?.post_build_validation||run.summary?.effective_run_status?.post_build_validation||run.manual_validation_status||run.summary?.manual_validation_status||{};
507
  const metadataRows=linkedValidationRowsFromMetadata(run);
508
+ const cacheRows=linkedValidationsForBuild(run.run_id).filter(linkedValidationIsTerminalForAccounting);
509
+ const explicitRow=linkedValidationExplicitTerminalRow(explicit);
510
+ const rows=dedupeLinkedValidationRows([...metadataRows,...cacheRows,...(explicitRow?[explicitRow]:[])]).filter(linkedValidationIsTerminalForAccounting);
511
+ const metadataCount=linkedValidationCountFromMetadata(run,rows);
512
+ const linkedCount=Math.max(metadataCount,rows.length);
513
  const passed=Boolean(
514
  run.manual_validation_passed||run.summary?.manual_validation_passed||
515
+ rows.some(linkedValidationPassedForAccounting)
 
 
 
 
 
516
  );
517
+ const failed=Boolean(rows.some(linkedValidationFailedForAccounting));
518
  const latest=rows[0]||{};
519
+ return {passed,failed,linkedCount,latest,explicit,metadataRows,cacheRows,accountedRows:rows};
520
  }
521
  function buildEffectiveStatus(run={}){
522
  // Kept for legacy callers, but now delegates to the deterministic Runs Explorer status model.
 
546
  return nums[lo]+(nums[hi]-nums[lo])*(idx-lo);
547
  }
548
  function pct(part,total){return total?`${Math.round((part/total)*100)}%`:'—'}
549
+ function runStatsIssueCounts(runs=[],linkedRows=[]){
550
  runs=validRunsList(runs);
551
  const counts={failed_builds:0,failed_linked_tests:0,manual_hardware:0,pi_model_mismatch:0,stale:0};
552
+ counts.failed_linked_tests=dedupeLinkedValidationRows(linkedRows).filter(linkedValidationFailedForAccounting).length;
553
  for(const r of runs){
554
  const validation=isValidationRunSummary(r);
555
  const status=String(r.status||'').toLowerCase();
556
  const pi=r.pi_model_resolution||{};
557
+ if(!validation&&runMatchesStatus(r,'failed'))counts.failed_builds++;
558
  if(!validation&&runMatchesStatus(r,'manual_hardware_required')) counts.manual_hardware++;
559
  if(!validation&&(pi.mismatch||status.includes('model_mismatch'))) counts.pi_model_mismatch++;
560
+ if(!validation&&status.includes('stale')) counts.stale++;
561
  }
562
  return counts;
563
  }
 
570
  if(!root)return;
571
  const runs=validRunsList(state.runsCache||[]);
572
  const builds=runs.filter(r=>!isValidationRunSummary(r));
573
+ const linkedRows=linkedValidationAccountingRowsForBuilds(builds);
574
+ const validations=linkedRows;
575
+ const runningLinked=runs.filter(r=>isLinkedValidationRun(r)&&!linkedValidationIsTerminalForAccounting(r)).length;
576
  const buildStatuses=builds.map(r=>({run:r,status:buildExplorerStatus(r)}));
577
  const runningBuilds=buildStatuses.filter(x=>x.status.filter_buckets?.has('running')).length;
578
  const failedBuilds=buildStatuses.filter(x=>x.status.filter_buckets?.has('failed')).length;
 
584
  const needsValidation=terminalBuilds.filter(x=>runNeedsLinkedValidation(x.run)).length;
585
  // Legacy aggregate labels kept for regression tests: Total runs / Test response time / Typical / slowest recent / Build success / Validation pass.
586
  // Legacy formula kept for audit readability: pct(successBuilds,terminalBuilds.length) and pct(passedValidations,terminalValidations.length).
587
+ const passedLinked=validations.filter(linkedValidationPassedForAccounting).length;
588
+ const terminalValidations=validations;
589
  const passedValidations=passedLinked;
590
  const latencies=validations.map(runStatLatencyValue).filter(v=>v!==null);
591
  const median=percentile(latencies,.5);
592
  const p95=percentile(latencies,.95);
593
+ const issues=runStatsIssueCounts(runs,validations);
594
  const issueTotal=Object.values(issues).reduce((a,b)=>a+b,0);
595
  const badge=$('runStatsBadge');
596
  if(badge){
597
+ badge.textContent=builds.length?`${builds.length} builds · ${validations.length} linked tests${runningLinked?` · ${runningLinked} running`:''}`:'No builds';
598
  badge.className='badge '+(failedBuilds||issueTotal?'warn':runningBuilds?'running':builds.length?'success':'neutral');
599
  }
600
  if(!builds.length){
 
610
  ].filter(([,count])=>count>0);
611
  root.innerHTML=`
612
  <div class="run-stats-grid build-first-stats">
613
+ ${runStatsCell('Build Runs',String(builds.length),`${validations.length} completed linked Space Test${validations.length===1?'':'s'}${runningLinked?` · ${runningLinked} running`:''}`,'neutral')}
614
  ${runStatsCell('Build status',`${runningBuilds} running`,`${failedBuilds} failed` ,failedBuilds?'warn':runningBuilds?'running':'success')}
615
  ${runStatsCell('Automatic success',pct(automaticSuccess,terminalBuilds.length),`${automaticSuccess}/${terminalBuilds.length} finished builds`,'success')}
616
  ${runStatsCell('Effective success',pct(effectiveSuccess,terminalBuilds.length),`${effectiveSuccess}/${terminalBuilds.length} after linked tests`,'success')}
 
666
  const runId=run.run_id||run.summary?.run_id;
667
  if(!runId||isRunLocallyDeleted(runId))return false;
668
  const canonical=buildEffectiveStatus(run);
669
+ if(['full_inference_success','failed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','technical_blocker_boot_only','blocked','completed_with_warnings','auth_refresh_required','stale','stopped','running'].includes(canonical))return false;
670
  return ['partial_validation','health_only','partial','completed_with_warnings','unknown',''].includes(String(canonical||'').toLowerCase());
671
  }
672
  function scheduleRunExplorerBackgroundHydration(runs=[]){
 
851
  recommended_zerogpu_duration_seconds:p.summary?.recommended_zerogpu_duration_seconds||p.generation_smoke?.recommended_zerogpu_duration_seconds||p.generation_smoke?.recommended_zero_gpu_duration_seconds,
852
  kind:p.summary?.kind||p.state?.kind||'universal_model_card_builder',
853
  parent_build_run_id:p.summary?.parent_build_run_id||p.state?.parent_build_run_id,
854
+ post_build_validation:p.post_build_validation||p.view?.post_build_validation||p.summary?.post_build_validation||p.effective_run_status?.post_build_validation||p.summary?.effective_run_status?.post_build_validation||{},
855
+ post_build_validation_status:p.post_build_validation_status||p.summary?.post_build_validation_status||{},
856
+ manual_validation_status:p.manual_validation_status||p.summary?.manual_validation_status||p.view?.manual_validation||{},
857
+ linked_validations:p.linked_validations||p.summary?.linked_validations||{},
858
+ linked_validation_count:p.summary?.linked_validation_count||p.linked_validation_count,
859
+ linked_validations_count:p.summary?.linked_validations_count||p.linked_validations_count,
860
+ effective_run_status:p.effective_run_status||p.summary?.effective_run_status||p.view?.effective_run_status||{},
861
+ display_status:p.summary?.display_status||p.display_status||p.view?.display_status,
862
+ manual_validation_passed:p.summary?.manual_validation_passed||p.manual_validation_status?.status==='success'||p.view?.manual_validation?.status==='success',
863
+ effective_status:p.summary?.effective_status||p.effective_status||p.manual_validation_status?.effective_status||p.view?.effective_status,
864
  bucket_source:p.bucket_source||p.summary?.bucket_source||state.bucketSource,
865
  };
866
  state.runsCache=validRunsList(state.runsCache||[]);
 
871
  if(parentId&&isValidationRunSummary(summary)){
872
  const parentIdx=state.runsCache.findIndex(r=>(r.run_id||r.summary?.run_id)===parentId);
873
  if(parentIdx>=0){
874
+ const terminal=linkedValidationIsTerminalForAccounting(summary);
875
+ const passed=linkedValidationPassedForAccounting(summary);
876
+ const existingParent=state.runsCache[parentIdx]||{};
877
+ const existingLinked=existingParent.linked_validations&&typeof existingParent.linked_validations==='object'?existingParent.linked_validations:{parent_build_run_id:parentId,validations:[]};
878
+ const existingRows=Array.isArray(existingLinked.validations)?existingLinked.validations:[];
879
+ const nextRows=terminal?dedupeLinkedValidationRows([...existingRows,summary]):existingRows;
880
+ const nextLinked={...existingLinked,parent_build_run_id:parentId,validations:nextRows,updated_at:summary.updated_at||existingLinked.updated_at||''};
881
+ if(passed)nextLinked.effective_status='validated_after_space_test';
882
+ state.runsCache[parentIdx]={...existingParent,linked_validations:nextLinked,linked_validation_count:nextRows.filter(linkedValidationIsTerminalForAccounting).length,linked_validations_count:nextRows.filter(linkedValidationIsTerminalForAccounting).length,manual_validation_passed:existingParent.manual_validation_passed||passed,post_build_status:passed?'validated_after_space_test':existingParent.post_build_status,effective_status:passed?'validated_after_space_test':existingParent.effective_status,updated_at:summary.updated_at||existingParent.updated_at};
883
  }
884
  }
885
  state.runsLastLoaded=Date.now();