Upload 8 files
Browse files- web/static/app.js +31 -3
- web/static/progress.js +2 -0
- 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();
|
| 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');
|
| 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(
|
| 450 |
}
|
| 451 |
-
function
|
| 452 |
-
|
| 453 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 460 |
-
const
|
| 461 |
-
const
|
| 462 |
-
const metadataCount=linkedValidationCountFromMetadata(run,
|
| 463 |
-
const linkedCount=Math.max(metadataCount,
|
| 464 |
const passed=Boolean(
|
| 465 |
run.manual_validation_passed||run.summary?.manual_validation_passed||
|
| 466 |
-
|
| 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'))
|
| 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
|
|
|
|
|
|
|
| 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(
|
| 540 |
-
const terminalValidations=validations
|
| 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 |
-
|
| 807 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 819 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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();
|