fffiloni commited on
Commit
c98f6cd
·
verified ·
1 Parent(s): 892ed91

Upload 8 files

Browse files
Files changed (3) hide show
  1. web/static/app.css +10 -0
  2. web/static/app.js +105 -15
  3. web/static/runs.js +55 -6
web/static/app.css CHANGED
@@ -3041,3 +3041,13 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
3041
  /* v190.24 — Build-first Runs Explorer polish */
3042
  /* v190.24 — Build-first compatibility cleanup */
3043
  /* v190.24 — Delete cascade clarity */
 
 
 
 
 
 
 
 
 
 
 
3041
  /* v190.24 — Build-first Runs Explorer polish */
3042
  /* v190.24 — Build-first compatibility cleanup */
3043
  /* v190.24 — Delete cascade clarity */
3044
+
3045
+ /* v190.28 — Space Test Validation reliability */
3046
+ .space-test-reset-state .validation-progress-bar,
3047
+ .space-test-reset-state #spaceTestTimeline{display:none!important;}
3048
+ .validation-args-note.launch-readiness.blocked{background:#fff7ed;border-color:#fed7aa;color:#9a3412;}
3049
+ .validation-args-note.launch-readiness.ready{background:#ecfdf5;border-color:#bbf7d0;color:#166534;}
3050
+ /* v190.28 — Build-first Runs Explorer polish */
3051
+ /* v190.28 — Build-first compatibility cleanup */
3052
+ /* v190.28 — Delete cascade clarity */
3053
+ /* v190.28 — Effective eval and linked validation provenance */
web/static/app.js CHANGED
@@ -559,7 +559,70 @@ function resetRunDetails(){
559
  renderActiveRunEmptyState();
560
  } function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.activeRunProgress=null;state.selectedRunDetail=null;state.documentCache={};state.latencyCache={};state.activeLinks={};state.pollErrors=0;clearSavedRun();document.querySelectorAll('.run-row').forEach(r=>r.classList.remove('selected'));setBuildModeNew();resetRunDetails();setText('progressPercent','0%');const cap=$('progressCaptionLabel');if(cap)cap.textContent='Overall progress';const sem=$('progressSemantics');if(sem){sem.hidden=true;sem.innerHTML=''}const fill=$('progressFill');if(fill)fill.style.width='0%';setText('currentStep','Ready');setText('lastEvent','Choose a model card and launch a build.');setText('elapsed','—');if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;setText('lastPolled','—');setText('hardwarePlan',hardwarePlanLabel());renderMetrics({});showMessage('New build mode ready.','info')}
561
  function renderQuietLaunchState(result={}){setText('progressTitle','Starting build Job…');setText('progressStatus','Running');const statusEl=$('progressStatus');if(statusEl)statusEl.className='badge '+statusClass('running');setText('progressPercent','1%');const fill=$('progressFill');if(fill)fill.style.width='1%';setText('lastEvent','Build Job launched; waiting for the first canonical run snapshot.');setText('elapsed','—');setText('lastPolled','—');const timeline=$('timeline');if(timeline){timeline.innerHTML='<div class="timeline-skeleton">Waiting for first run snapshot…</div>';timeline.classList.remove('timeline-canonical');timeline.style.gridTemplateColumns=''}const note=$('timelineTraceNote');if(note){note.hidden=true;note.innerHTML=''}const details=$('timelinePhaseDetails');if(details){details.hidden=true;details.innerHTML='';details.className='timeline-phase-details'}}
562
- function setActiveRun(result){if(state.poll){clearInterval(state.poll);state.poll=null}resetRunDetails();state.documentCache={};state.latencyCache={};state.runId=result.run_id;state.spaceLinksUnlockedFor=null;if(Array.isArray(state.runsCache)&&typeof sortRunsNewestFirst==='function'){state.runsCache=sortRunsNewestFirst([{...result,run_id:result.run_id,status:result.status||'running',created_at:result.created_at||new Date().toISOString()},...state.runsCache.filter(r=>r.run_id!==result.run_id)]);renderRunsFromCache();if(typeof renderRunStats==='function')renderRunStats();}if(result.bucket_source)state.bucketSource=result.bucket_source;setText('detailRunId',result.run_id);setText('detailJobStatus',result.status||'RUNNING');setText('detailGateStatus',result.kind||'running');setText('detailSpace',result.target_space||'—');setText('detailHardware',result.selected_hardware||result.preferred_space_hardware||'—');renderActiveRunMeta({summary:result,state:result,launch:result});renderQuietLaunchState(result);setText('homeRunDetailStatus',result.status||'Running');setBuildModeInspecting(result.run_id);setQuickLinks({...result.links,bucket_source:result.bucket_source,run_id:result.run_id,job_id:result.job_id,job_owner:state.user?.username,target_space_url:result.target_space_url},result);saveActiveRun();markPendingActiveFields();startPolling(result.run_id)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
  function startPolling(runId,options={}){
564
  if(!runId)return;
565
  const sameRun=state.runId===runId;
@@ -754,13 +817,23 @@ function validationPrefillFromDetail(detail={}){
754
  const gate={...(detail.inference_gate||{}),...(progress.inference_gate||{})};
755
  const gateSmoke=gate.generation_smoke&&typeof gate.generation_smoke==='object'?gate.generation_smoke:{};
756
  const smoke={...(detail.state?.generation_smoke||{}),...(detail.tests?.generation_smoke||{}),...(detail.generation_smoke||{}),...(progress.generation_smoke||{}),...(progress.state?.generation_smoke||{}),...gateSmoke};
 
 
 
 
 
 
 
 
 
 
757
  const launch={...(detail.launch||{}),...(progress.launch||{})};
758
  const stateObj={...(detail.state||{}),...(progress.state||{})};
759
  const expected=smoke.expected_output_type||gate.expected_output_type||launch.expected_output_type||summary.expected_output_type||stateObj.expected_output_type||formValue('buildExpectedOutput')||'text';
760
  const endpointParameters=smoke.parameters||smoke.endpoint_parameters||gate.endpoint_parameters||gate.smoke_parameters||[];
761
- let args=firstArrayPath(smoke.effective_args,smoke.test_args,smoke.args,gate.effective_args,gate.test_args,gateSmoke.effective_args,gateSmoke.test_args,launch.test_args_json,summary.test_args_json);
762
  if(!Array.isArray(args))args=testPayloadFromEndpointParameters(endpointParameters,expected);
763
- let kwargs=firstObjectPath(smoke.effective_kwargs,smoke.test_kwargs,smoke.kwargs,gate.effective_kwargs,gate.test_kwargs,gateSmoke.effective_kwargs,gateSmoke.test_kwargs,launch.test_kwargs_json,summary.test_kwargs_json);
764
  if(!Array.isArray(args))args=defaultValidationArgs(expected);
765
  if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')kwargs={};
766
  const policy=detail.space_test_policy||detail.view?.space_test_policy||detail.space_test?.policy||progress.view?.space_test_policy||progress.view?.space_test?.policy||{};
@@ -768,8 +841,9 @@ function validationPrefillFromDetail(detail={}){
768
  const api=policy.requires_endpoint_discovery?'':normalizeApiName(policy.endpoint||smoke.api_name||gate.api_name||launch.api_name||gateSmoke.api_name||progress.api_name||'/generate');
769
  const parentBuildRunId=policy.parent_build_run_id||detail.run_id||detail.summary?.run_id||state.runId||progress.run_id||'';
770
  const automaticSmokePassed=Boolean(smoke.ok||smoke.status==='success'||policy.mode==='replay');
771
- const payloadSource=policy.requires_endpoint_discovery?'endpoint_discovery_first':(smoke.effective_args||smoke.test_args||smoke.args?'automatic_smoke':(endpointParameters.length?'gradio_schema_defaults':'default_payload'));
772
- return {target,api,expected,args,kwargs,parentBuildRunId,sourceRunId:parentBuildRunId,linkedTargetSpace:target,automaticSmokePassed,smokeLatency:smoke.latency_seconds||smoke.observed_latency_seconds,spaceTestPolicy:policy,requiresEndpointDiscovery:Boolean(policy.requires_endpoint_discovery),payloadSource};
 
773
  }
774
  function setValidationProgressVisible(visible){
775
  const show=Boolean(visible);
@@ -867,7 +941,10 @@ function renderValidationStatus(data={}){
867
  setText('validationStartedAt',isIdle?'Not launched':formatTime(data.updated_at||summary.updated_at||data.state?.updated_at||data.created_at||summary.created_at||new Date().toISOString()));
868
  const latency=firstPositiveNumber(smoke.latency_seconds,smoke.observed_latency_seconds,summary.latency_seconds,summary.observed_latency_seconds,data.latency_seconds,data.observed_latency_seconds);
869
  setText('spaceTestLatency',isIdle?'—':(latency?formatLatencySeconds(latency):(canonical==='running'?'—':'Not recorded')));
870
- setText('spaceTestVerdict',isIdle?(data.message||'No validation run selected.'):data._refreshing?`${badgeLabel(displayStatus)} · Refreshing…`:badgeLabel(displayStatus));
 
 
 
871
  const launchBtn=$('launchValidate');
872
  if(launchBtn&&!(launchBtn.classList?.contains&&launchBtn.classList.contains('is-busy'))){
873
  const draft=state.validationPrefillDraft||{};
@@ -1002,6 +1079,8 @@ function renderValidationPrefillDraft(prefill={},sourceLabel='selected Space'){
1002
  const readiness=prefill.requiresEndpointDiscovery?'Discovery first':'Ready to launch';
1003
  endpoints.innerHTML=`<div class="linked-context-card linked-context-card--pretty"><span>Linked Build Run</span><strong><code>${escapeHtml(state.validationPrefillDraft.parentBuildRunId||'—')}</code></strong><span>Target Space</span><strong>${targetLine}</strong><span>Mode</span><strong>${modeLabel}</strong><span>Endpoint</span><strong>${endpointLine}</strong><span>Payload source</span><strong>${payloadSource}</strong><span>Automatic smoke</span><strong>${latencyLine}</strong><span>Launch readiness</span><strong>${escapeHtml(readiness)}</strong></div>`;
1004
  }
 
 
1005
  const launchBtn=$('launchValidate');
1006
  if(launchBtn&&!launchBtn.classList.contains('is-busy'))launchBtn.textContent=spaceTestLaunchLabel(prefill);
1007
  setValidationLaunchEnabled();
@@ -1030,18 +1109,27 @@ function prepareValidationFromActiveRun(){
1030
  function contextualizeSpaceTestFromBuildRun(detail={}){
1031
  if(!detail||typeof detail!=='object')return;
1032
  if(typeof isValidationRunDetail==='function'&&isValidationRunDetail(detail))return;
1033
- if(state.validationMode==='running')return;
1034
  const prefill=validationPrefillFromDetail(detail);
1035
  if(!prefill.parentBuildRunId||!prefill.target||prefill.target==='—')return;
 
 
 
 
 
 
 
 
 
 
1036
  state.validationPrefillDraft={...prefill,sourceLabel:'selected Build Run',parentBuildRunId:prefill.parentBuildRunId,sourceRunId:prefill.parentBuildRunId,linkedTargetSpace:prefill.target};
1037
  state.linkedValidationParent=prefill.parentBuildRunId;
1038
- if(!state.validationArgsDirty){
1039
- setInputValue('validateSpace',prefill.target);
1040
- setInputValue('expectedOutput',prefill.expected);
1041
- setInputValue('validateApi',prefill.api);
1042
- setInputValue('testArgs',prettyJson(prefill.args));
1043
- setInputValue('testKwargs',prettyJson(prefill.kwargs));
1044
- }
1045
  if(state.centerTab==='validate'){
1046
  renderValidationPrefillDraft(prefill,'selected Build Run');
1047
  }else{
@@ -1078,7 +1166,9 @@ async function launchValidate(){
1078
  test_kwargs_json:prettyJson(resolved.kwargs),
1079
  live_timeout_seconds:resolved.timeout,
1080
  validation_mode:resolved.validationMode,
1081
- payload_source:resolved.payloadSource
 
 
1082
  };
1083
  const result=await apiPost('/api/validate',payload);
1084
  state.validationArgsDirty=false;
 
559
  renderActiveRunEmptyState();
560
  } function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.activeRunProgress=null;state.selectedRunDetail=null;state.documentCache={};state.latencyCache={};state.activeLinks={};state.pollErrors=0;clearSavedRun();document.querySelectorAll('.run-row').forEach(r=>r.classList.remove('selected'));setBuildModeNew();resetRunDetails();setText('progressPercent','0%');const cap=$('progressCaptionLabel');if(cap)cap.textContent='Overall progress';const sem=$('progressSemantics');if(sem){sem.hidden=true;sem.innerHTML=''}const fill=$('progressFill');if(fill)fill.style.width='0%';setText('currentStep','Ready');setText('lastEvent','Choose a model card and launch a build.');setText('elapsed','—');if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;setText('lastPolled','—');setText('hardwarePlan',hardwarePlanLabel());renderMetrics({});showMessage('New build mode ready.','info')}
561
  function renderQuietLaunchState(result={}){setText('progressTitle','Starting build Job…');setText('progressStatus','Running');const statusEl=$('progressStatus');if(statusEl)statusEl.className='badge '+statusClass('running');setText('progressPercent','1%');const fill=$('progressFill');if(fill)fill.style.width='1%';setText('lastEvent','Build Job launched; waiting for the first canonical run snapshot.');setText('elapsed','—');setText('lastPolled','—');const timeline=$('timeline');if(timeline){timeline.innerHTML='<div class="timeline-skeleton">Waiting for first run snapshot…</div>';timeline.classList.remove('timeline-canonical');timeline.style.gridTemplateColumns=''}const note=$('timelineTraceNote');if(note){note.hidden=true;note.innerHTML=''}const details=$('timelinePhaseDetails');if(details){details.hidden=true;details.innerHTML='';details.className='timeline-phase-details'}}
562
+
563
+ function resetSpaceTestForNewBuild(runId=''){
564
+ if(typeof stopValidationPolling==='function')stopValidationPolling();
565
+ state.validationRequestSeq=(state.validationRequestSeq||0)+1;
566
+ state.validationPollToken=null;
567
+ state.validationRunId=null;
568
+ state.validationDetail=null;
569
+ state.validationMode='idle';
570
+ state.validationPollErrors=0;
571
+ state.validationPrefillDraft=null;
572
+ state.linkedValidationParent=null;
573
+ state.validationArgsDirty=false;
574
+ const note=$('spaceTestArgsNote');if(note)note.hidden=true;
575
+ const readiness=$('spaceTestLaunchReadiness');if(readiness){readiness.hidden=false;readiness.className='validation-args-note launch-readiness blocked';readiness.textContent='Space Test will be available after this build reaches a final Space state.';}
576
+ setInputValue('validateSpace','');
577
+ setInputValue('validateApi','');
578
+ setInputValue('testArgs',prettyJson(defaultValidationArgs(formValue('buildExpectedOutput')||'image')));
579
+ setInputValue('testKwargs','{}');
580
+ setText('spaceTestEndpoint','—');
581
+ setText('spaceTestOutput','Not launched');
582
+ setText('validationStartedAt','Not launched');
583
+ setText('spaceTestLatency','—');
584
+ setText('spaceTestVerdict',runId?`Waiting for Build Run ${runId} to finish.`:'Waiting for a Build Run to finish.');
585
+ const badge=$('spaceTestStatus');if(badge){badge.textContent='Blocked';badge.className='badge neutral';}
586
+ const tabDot=$('spaceTestTabDot');if(tabDot)tabDot.className='tab-status-dot neutral';
587
+ const timeline=$('spaceTestTimeline');if(timeline){timeline.hidden=true;timeline.innerHTML='';}
588
+ setValidationProgressVisible(false);
589
+ const deleteBtn=$('deleteValidationRun');if(deleteBtn)deleteBtn.disabled=true;
590
+ const launchBtn=$('launchValidate');if(launchBtn){launchBtn.textContent='Run linked validation';launchBtn.disabled=true;launchBtn.title='Available after the build reaches a final Space state.';}
591
+ const endpoints=$('spaceTestEndpoints');if(endpoints){endpoints.innerHTML='<div class="linked-context-card linked-context-card--compact"><span>Space Test</span><strong>Waiting for current Build Run to finish</strong></div>';}
592
+ }
593
+
594
+
595
+ function clearValidationRunStateForBuildContext(parentRunId='',reason='build_selection'){
596
+ if(typeof stopValidationPolling==='function')stopValidationPolling();
597
+ state.validationRequestSeq=(state.validationRequestSeq||0)+1;
598
+ state.validationPollToken=null;
599
+ state.validationRunId=null;
600
+ state.validationDetail=null;
601
+ state.validationMode='idle';
602
+ state.validationPollErrors=0;
603
+ state.validationArgsDirty=false;
604
+ state.validationPrefillDraft=null;
605
+ state.linkedValidationParent=parentRunId||null;
606
+ const note=$('spaceTestArgsNote');if(note)note.hidden=true;
607
+ setText('spaceTestOutput','Not launched');
608
+ setText('validationStartedAt','Not launched');
609
+ setText('spaceTestLatency','—');
610
+ setText('spaceTestVerdict',parentRunId?`Linked to Build Run ${parentRunId}. No validation run launched yet.`:'Select a Build Run to prepare Space Test.');
611
+ setText('spaceTestEndpoint','—');
612
+ const timeline=$('spaceTestTimeline');if(timeline){timeline.hidden=true;timeline.innerHTML='';timeline.dataset.parentRunId=parentRunId||'';}
613
+ setValidationProgressVisible(false);
614
+ const deleteBtn=$('deleteValidationRun');if(deleteBtn){deleteBtn.disabled=true;deleteBtn.onclick=null;}
615
+ const tabDot=$('spaceTestTabDot');if(tabDot){tabDot.className='tab-status-dot neutral';tabDot.title='No active linked validation';}
616
+ const badge=$('spaceTestStatus');if(badge){badge.textContent='Preparing';badge.className='badge neutral';}
617
+ const card=$('spaceTestPreview');if(card){card.classList.remove('validation-running','validation-success','validation-error');card.classList.add('space-test-context-only');}
618
+ const readiness=$('spaceTestLaunchReadiness');if(readiness){readiness.hidden=false;readiness.className='validation-args-note launch-readiness blocked';readiness.textContent='Preparing Space Test context for the selected Build Run.';}
619
+ return true;
620
+ }
621
+ function requestSpaceTestContextResetForBuild(runId){
622
+ state.spaceTestContextResetForRun=runId||true;
623
+ }
624
+
625
+ function setActiveRun(result){if(state.poll){clearInterval(state.poll);state.poll=null}resetSpaceTestForNewBuild(result?.run_id||'');resetRunDetails();state.documentCache={};state.latencyCache={};state.runId=result.run_id;state.spaceLinksUnlockedFor=null;if(Array.isArray(state.runsCache)&&typeof sortRunsNewestFirst==='function'){state.runsCache=sortRunsNewestFirst([{...result,run_id:result.run_id,status:result.status||'running',created_at:result.created_at||new Date().toISOString()},...state.runsCache.filter(r=>r.run_id!==result.run_id)]);renderRunsFromCache();if(typeof renderRunStats==='function')renderRunStats();}if(result.bucket_source)state.bucketSource=result.bucket_source;setText('detailRunId',result.run_id);setText('detailJobStatus',result.status||'RUNNING');setText('detailGateStatus',result.kind||'running');setText('detailSpace',result.target_space||'—');setText('detailHardware',result.selected_hardware||result.preferred_space_hardware||'—');renderActiveRunMeta({summary:result,state:result,launch:result});renderQuietLaunchState(result);setText('homeRunDetailStatus',result.status||'Running');setBuildModeInspecting(result.run_id);setQuickLinks({...result.links,bucket_source:result.bucket_source,run_id:result.run_id,job_id:result.job_id,job_owner:state.user?.username,target_space_url:result.target_space_url},result);saveActiveRun();markPendingActiveFields();startPolling(result.run_id)}
626
  function startPolling(runId,options={}){
627
  if(!runId)return;
628
  const sameRun=state.runId===runId;
 
817
  const gate={...(detail.inference_gate||{}),...(progress.inference_gate||{})};
818
  const gateSmoke=gate.generation_smoke&&typeof gate.generation_smoke==='object'?gate.generation_smoke:{};
819
  const smoke={...(detail.state?.generation_smoke||{}),...(detail.tests?.generation_smoke||{}),...(detail.generation_smoke||{}),...(progress.generation_smoke||{}),...(progress.state?.generation_smoke||{}),...gateSmoke};
820
+ const smokePayload=firstObjectPath(
821
+ detail.generation_smoke_payload_retry,
822
+ detail.tests?.generation_smoke_payload_retry,
823
+ progress.generation_smoke_payload_retry,
824
+ progress.tests?.generation_smoke_payload_retry,
825
+ detail.generation_smoke_payload,
826
+ detail.tests?.generation_smoke_payload,
827
+ progress.generation_smoke_payload,
828
+ progress.tests?.generation_smoke_payload
829
+ )||{};
830
  const launch={...(detail.launch||{}),...(progress.launch||{})};
831
  const stateObj={...(detail.state||{}),...(progress.state||{})};
832
  const expected=smoke.expected_output_type||gate.expected_output_type||launch.expected_output_type||summary.expected_output_type||stateObj.expected_output_type||formValue('buildExpectedOutput')||'text';
833
  const endpointParameters=smoke.parameters||smoke.endpoint_parameters||gate.endpoint_parameters||gate.smoke_parameters||[];
834
+ let args=firstArrayPath(smoke.effective_args,smoke.test_args,smoke.args,smokePayload.effective_args,smokePayload.test_args,smokePayload.args,gate.effective_args,gate.test_args,gateSmoke.effective_args,gateSmoke.test_args,launch.test_args_json,summary.test_args_json);
835
  if(!Array.isArray(args))args=testPayloadFromEndpointParameters(endpointParameters,expected);
836
+ let kwargs=firstObjectPath(smoke.effective_kwargs,smoke.test_kwargs,smoke.kwargs,smokePayload.effective_kwargs,smokePayload.test_kwargs,smokePayload.kwargs,gate.effective_kwargs,gate.test_kwargs,gateSmoke.effective_kwargs,gateSmoke.test_kwargs,launch.test_kwargs_json,summary.test_kwargs_json);
837
  if(!Array.isArray(args))args=defaultValidationArgs(expected);
838
  if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')kwargs={};
839
  const policy=detail.space_test_policy||detail.view?.space_test_policy||detail.space_test?.policy||progress.view?.space_test_policy||progress.view?.space_test?.policy||{};
 
841
  const api=policy.requires_endpoint_discovery?'':normalizeApiName(policy.endpoint||smoke.api_name||gate.api_name||launch.api_name||gateSmoke.api_name||progress.api_name||'/generate');
842
  const parentBuildRunId=policy.parent_build_run_id||detail.run_id||detail.summary?.run_id||state.runId||progress.run_id||'';
843
  const automaticSmokePassed=Boolean(smoke.ok||smoke.status==='success'||policy.mode==='replay');
844
+ const smokeArtifactHasPayload=Boolean(smokePayload.effective_args||smokePayload.test_args||smokePayload.args);
845
+ const payloadSource=policy.requires_endpoint_discovery?'endpoint_discovery_first':((smoke.effective_args||smoke.test_args||smoke.args||smokeArtifactHasPayload)?'automatic_smoke_artifact':(endpointParameters.length?'gradio_schema_defaults':'default_payload'));
846
+ return {target,api,expected,args,kwargs,parentBuildRunId,sourceRunId:parentBuildRunId,linkedTargetSpace:target,automaticSmokePassed,smokeLatency:smoke.latency_seconds||smoke.observed_latency_seconds,spaceTestPolicy:policy,requiresEndpointDiscovery:Boolean(policy.requires_endpoint_discovery),payloadSource,smokeArtifactPayloadAvailable:smokeArtifactHasPayload};
847
  }
848
  function setValidationProgressVisible(visible){
849
  const show=Boolean(visible);
 
941
  setText('validationStartedAt',isIdle?'Not launched':formatTime(data.updated_at||summary.updated_at||data.state?.updated_at||data.created_at||summary.created_at||new Date().toISOString()));
942
  const latency=firstPositiveNumber(smoke.latency_seconds,smoke.observed_latency_seconds,summary.latency_seconds,summary.observed_latency_seconds,data.latency_seconds,data.observed_latency_seconds);
943
  setText('spaceTestLatency',isIdle?'—':(latency?formatLatencySeconds(latency):(canonical==='running'?'—':'Not recorded')));
944
+ const diagnosis=data.validation_failure_diagnosis||data.summary?.validation_failure_diagnosis||data.state?.validation_failure_diagnosis||smoke.validation_failure_diagnosis||{};
945
+ const diagnosisMsg=diagnosis.message||diagnosis.error||diagnosis.failure_reason||diagnosis.failure_type||smoke.error||summary.error||data.error||'';
946
+ const verdictText=isIdle?(data.message||'No validation run selected.'):data._refreshing?`${badgeLabel(displayStatus)} · Refreshing…`:(canonical==='failed'&&diagnosisMsg?`Failed · ${String(diagnosisMsg).slice(0,220)}`:badgeLabel(displayStatus));
947
+ setText('spaceTestVerdict',verdictText);
948
  const launchBtn=$('launchValidate');
949
  if(launchBtn&&!(launchBtn.classList?.contains&&launchBtn.classList.contains('is-busy'))){
950
  const draft=state.validationPrefillDraft||{};
 
1079
  const readiness=prefill.requiresEndpointDiscovery?'Discovery first':'Ready to launch';
1080
  endpoints.innerHTML=`<div class="linked-context-card linked-context-card--pretty"><span>Linked Build Run</span><strong><code>${escapeHtml(state.validationPrefillDraft.parentBuildRunId||'—')}</code></strong><span>Target Space</span><strong>${targetLine}</strong><span>Mode</span><strong>${modeLabel}</strong><span>Endpoint</span><strong>${endpointLine}</strong><span>Payload source</span><strong>${payloadSource}</strong><span>Automatic smoke</span><strong>${latencyLine}</strong><span>Launch readiness</span><strong>${escapeHtml(readiness)}</strong></div>`;
1081
  }
1082
+ const note=$('spaceTestArgsNote');
1083
+ if(note){note.hidden=false;note.textContent=`Payload source: ${String(prefill.payloadSource||'linked_payload').replace(/_/g,' ')}.`;}
1084
  const launchBtn=$('launchValidate');
1085
  if(launchBtn&&!launchBtn.classList.contains('is-busy'))launchBtn.textContent=spaceTestLaunchLabel(prefill);
1086
  setValidationLaunchEnabled();
 
1109
  function contextualizeSpaceTestFromBuildRun(detail={}){
1110
  if(!detail||typeof detail!=='object')return;
1111
  if(typeof isValidationRunDetail==='function'&&isValidationRunDetail(detail))return;
 
1112
  const prefill=validationPrefillFromDetail(detail);
1113
  if(!prefill.parentBuildRunId||!prefill.target||prefill.target==='—')return;
1114
+ const nextParent=String(prefill.parentBuildRunId||'');
1115
+ const currentParent=String(state.linkedValidationParent||state.validationPrefillDraft?.parentBuildRunId||'');
1116
+ const explicitReset=state.spaceTestContextResetForRun===true||state.spaceTestContextResetForRun===nextParent;
1117
+ const parentChanged=Boolean(currentParent&&nextParent&&currentParent!==nextParent);
1118
+ if(explicitReset||parentChanged){
1119
+ clearValidationRunStateForBuildContext(nextParent,'build_selection');
1120
+ state.spaceTestContextResetForRun=null;
1121
+ }else if(state.validationMode==='running'&&currentParent===nextParent){
1122
+ return;
1123
+ }
1124
  state.validationPrefillDraft={...prefill,sourceLabel:'selected Build Run',parentBuildRunId:prefill.parentBuildRunId,sourceRunId:prefill.parentBuildRunId,linkedTargetSpace:prefill.target};
1125
  state.linkedValidationParent=prefill.parentBuildRunId;
1126
+ state.validationArgsDirty=false;
1127
+ setInputValue('validateSpace',prefill.target);
1128
+ setInputValue('expectedOutput',prefill.expected);
1129
+ setInputValue('validateApi',prefill.api);
1130
+ setInputValue('testArgs',prettyJson(prefill.args));
1131
+ setInputValue('testKwargs',prettyJson(prefill.kwargs));
1132
+ const note=$('spaceTestArgsNote');if(note){note.hidden=false;note.textContent=`Payload source: ${String(prefill.payloadSource||'linked_payload').replace(/_/g,' ')}.`;}
1133
  if(state.centerTab==='validate'){
1134
  renderValidationPrefillDraft(prefill,'selected Build Run');
1135
  }else{
 
1166
  test_kwargs_json:prettyJson(resolved.kwargs),
1167
  live_timeout_seconds:resolved.timeout,
1168
  validation_mode:resolved.validationMode,
1169
+ payload_source:resolved.payloadSource,
1170
+ ui_payload_arg_count:Array.isArray(resolved.args)?resolved.args.length:0,
1171
+ ui_payload_kwargs_keys:resolved.kwargs&&typeof resolved.kwargs==='object'?Object.keys(resolved.kwargs):[]
1172
  };
1173
  const result=await apiPost('/api/validate',payload);
1174
  state.validationArgsDirty=false;
web/static/runs.js CHANGED
@@ -255,14 +255,60 @@ function runNeedsLinkedValidation(run={}){
255
  return ['complete','recover'].includes(String(policy.mode||'').toLowerCase())&&policy.enabled!==false&&!buildManualValidationStatus(run).passed;
256
  }
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  function buildManualValidationStatus(run={}){
259
  run=safeRunObject(run);
260
  const explicit=run.manual_validation_status||run.summary?.manual_validation_status||{};
261
- const linked=linkedValidationsForBuild(run.run_id).filter(v=>!runMatchesStatus(v,'running'));
262
- const passed=Boolean(run.manual_validation_passed||run.summary?.manual_validation_passed||explicit.status==='success'||linked.some(v=>runMatchesStatus(v,'full_inference_success')));
263
- const failed=Boolean(explicit.status==='failed'||linked.some(v=>runMatchesStatus(v,'failed')));
264
- const latest=linked[0]||{};
265
- return {passed,failed,linkedCount:linked.length,latest,explicit};
 
 
 
 
 
 
 
 
 
 
 
 
266
  }
267
  function buildEffectiveStatus(run={}){
268
  run=safeRunObject(run);
@@ -566,6 +612,7 @@ function renderRunSelectionPreview(runId){
566
  const detail=optimisticDetailFromSummary(cached);
567
  if(isValidationRunDetail(detail)){const parent=linkedValidationParentId(cached)||detail.summary?.parent_build_run_id||detail.state?.parent_build_run_id;if(parent){selectRun(parent);return;}showValidationRunDetail(detail);return}
568
  switchCenterTab('active');
 
569
  renderRunDetail(detail);
570
  setText('progressTitle',runId);
571
  setText('progressStatus',badgeLabel(cached.status||'loading'));
@@ -651,7 +698,7 @@ async function selectRun(runId){
651
  showMessage(`Opening parent Build Run for linked validation ${runId}.`,'info');
652
  runId=parentId;
653
  }else if(isValidationRunDetail(localDetail))showValidationRunDetail(localDetail);
654
- else{switchCenterTab('active');setBuildModeInspecting(runId);state.runId=runId;renderRunDetail(localDetail);}
655
  }else{
656
  renderRunSelectionPreview(runId);
657
  }
@@ -667,6 +714,7 @@ async function selectRun(runId){
667
  switchCenterTab('active');
668
  setBuildModeInspecting(parentId);
669
  state.runId=parentId;
 
670
  renderRunViewSnapshot(parentSnapshot);
671
  }else showValidationRunDetail(snapshot);
672
  return;
@@ -674,6 +722,7 @@ async function selectRun(runId){
674
  switchCenterTab('active');
675
  setBuildModeInspecting(runId);
676
  state.runId=runId;
 
677
  renderRunViewSnapshot(snapshot);
678
  const status=String(snapshot.view?.header?.status||snapshot.status||snapshot.summary?.status||snapshot.state?.status||'').toLowerCase();
679
  if(runCanBeDeleted(status)||TERMINAL_STATUSES.has(status)){
 
255
  return ['complete','recover'].includes(String(policy.mode||'').toLowerCase())&&policy.enabled!==false&&!buildManualValidationStatus(run).passed;
256
  }
257
 
258
+ function linkedValidationRowsFromMetadata(run={}){
259
+ run=safeRunObject(run);
260
+ const candidates=[
261
+ run.linked_validations,
262
+ run.summary?.linked_validations,
263
+ run.manual_validation_status?.linked_validations,
264
+ run.summary?.manual_validation_status?.linked_validations
265
+ ].filter(Boolean);
266
+ const rows=[];
267
+ for(const item of candidates){
268
+ if(Array.isArray(item))rows.push(...item);
269
+ else if(Array.isArray(item.validations))rows.push(...item.validations);
270
+ else if(item.latest_validation)rows.push(item.latest_validation);
271
+ else if(item.validation_run_id||item.run_id||item.status)rows.push(item);
272
+ }
273
+ return rows.filter(x=>x&&typeof x==='object');
274
+ }
275
+ function linkedValidationCountFromMetadata(run={},rows=[]){
276
+ const counts=[
277
+ run.linked_validation_count,
278
+ run.summary?.linked_validation_count,
279
+ run.linked_validations_count,
280
+ run.summary?.linked_validations_count,
281
+ run.manual_validation_status?.linked_count,
282
+ run.summary?.manual_validation_status?.linked_count,
283
+ run.linked_validations?.count,
284
+ run.summary?.linked_validations?.count
285
+ ].map(Number).filter(n=>Number.isFinite(n)&&n>0);
286
+ return Math.max(rows.length,...counts,0);
287
+ }
288
+ function linkedValidationRowStatus(row={}){
289
+ row=safeRunObject(row);
290
+ return String(row.status||row.validation_status||row.result_status||row.verdict||row.effective_status||row.summary?.status||'').toLowerCase();
291
+ }
292
  function buildManualValidationStatus(run={}){
293
  run=safeRunObject(run);
294
  const explicit=run.manual_validation_status||run.summary?.manual_validation_status||{};
295
+ const metadataRows=linkedValidationRowsFromMetadata(run);
296
+ const cacheRows=linkedValidationsForBuild(run.run_id).filter(v=>!runMatchesStatus(v,'running'));
297
+ const rows=[...metadataRows,...cacheRows];
298
+ const explicitStatus=String(explicit.status||explicit.validation_status||explicit.result_status||explicit.verdict||'').toLowerCase();
299
+ const metadataCount=linkedValidationCountFromMetadata(run,metadataRows);
300
+ const linkedCount=Math.max(metadataCount,cacheRows.length,explicitStatus?1:0);
301
+ const passed=Boolean(
302
+ run.manual_validation_passed||run.summary?.manual_validation_passed||
303
+ explicitStatus==='success'||explicitStatus==='passed'||explicitStatus==='full_inference_success'||
304
+ rows.some(v=>{const st=linkedValidationRowStatus(v);return st==='success'||st==='passed'||st==='full_inference_success'||runMatchesStatus(v,'full_inference_success');})
305
+ );
306
+ const failed=Boolean(
307
+ explicitStatus==='failed'||explicitStatus==='failure'||explicitStatus==='error'||
308
+ rows.some(v=>{const st=linkedValidationRowStatus(v);return st==='failed'||st==='failure'||st==='error'||runMatchesStatus(v,'failed');})
309
+ );
310
+ const latest=rows[0]||{};
311
+ return {passed,failed,linkedCount,latest,explicit,metadataRows,cacheRows};
312
  }
313
  function buildEffectiveStatus(run={}){
314
  run=safeRunObject(run);
 
612
  const detail=optimisticDetailFromSummary(cached);
613
  if(isValidationRunDetail(detail)){const parent=linkedValidationParentId(cached)||detail.summary?.parent_build_run_id||detail.state?.parent_build_run_id;if(parent){selectRun(parent);return;}showValidationRunDetail(detail);return}
614
  switchCenterTab('active');
615
+ if(typeof requestSpaceTestContextResetForBuild==='function')requestSpaceTestContextResetForBuild(runId);
616
  renderRunDetail(detail);
617
  setText('progressTitle',runId);
618
  setText('progressStatus',badgeLabel(cached.status||'loading'));
 
698
  showMessage(`Opening parent Build Run for linked validation ${runId}.`,'info');
699
  runId=parentId;
700
  }else if(isValidationRunDetail(localDetail))showValidationRunDetail(localDetail);
701
+ else{switchCenterTab('active');setBuildModeInspecting(runId);state.runId=runId;if(typeof requestSpaceTestContextResetForBuild==='function')requestSpaceTestContextResetForBuild(runId);renderRunDetail(localDetail);}
702
  }else{
703
  renderRunSelectionPreview(runId);
704
  }
 
714
  switchCenterTab('active');
715
  setBuildModeInspecting(parentId);
716
  state.runId=parentId;
717
+ if(typeof requestSpaceTestContextResetForBuild==='function')requestSpaceTestContextResetForBuild(parentId);
718
  renderRunViewSnapshot(parentSnapshot);
719
  }else showValidationRunDetail(snapshot);
720
  return;
 
722
  switchCenterTab('active');
723
  setBuildModeInspecting(runId);
724
  state.runId=runId;
725
+ if(typeof requestSpaceTestContextResetForBuild==='function')requestSpaceTestContextResetForBuild(runId);
726
  renderRunViewSnapshot(snapshot);
727
  const status=String(snapshot.view?.header?.status||snapshot.status||snapshot.summary?.status||snapshot.state?.status||'').toLowerCase();
728
  if(runCanBeDeleted(status)||TERMINAL_STATUSES.has(status)){