Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| from __future__ import annotations | |
| from typing import Any | |
| from .progress import progress_from_events | |
| PHASE_ORDER = [ | |
| "start", | |
| "model", | |
| "agent", | |
| "hardware", | |
| "deploy", | |
| "live_validation", | |
| "recovery", | |
| "archive", | |
| "done", | |
| ] | |
| PHASE_LABELS = { | |
| "start": "Start", | |
| "model": "Model", | |
| "agent": "Agent", | |
| "hardware": "GPU", | |
| "deploy": "Deploy", | |
| "live_validation": "Live test", | |
| "recovery": "Recovery", | |
| "archive": "Archive", | |
| "done": "Done", | |
| } | |
| PHASE_STEPS = { | |
| "start": {"token_context", "bootstrap", "dependencies", "auth", "workspace", "node", "pi_install"}, | |
| "model": {"model_analysis", "model_prescan"}, | |
| "agent": {"pi_config", "pi_run", "traces", "pi_model_resolution", "pi_verification"}, | |
| "hardware": {"hardware_strategy", "create_space_hardware", "create_space"}, | |
| "deploy": {"metadata_sanitize", "requirements_sanitize", "upload_files", "space_logs", "space_runtime", "live_wait"}, | |
| "live_validation": {"endpoint_discovery", "api_validation", "generation_smoke", "inference_gate"}, | |
| "recovery": { | |
| "failure_detected", | |
| "failure_diagnosis", | |
| "pi_diagnosis", | |
| "repair_decision", | |
| "wait_for_logs", | |
| "factory_rebuild", | |
| "repair", | |
| "repair_diagnosis", | |
| "repair_brief", | |
| "repair_plan", | |
| "repair_patch", | |
| "repair_upload", | |
| "repair_validation", | |
| }, | |
| "archive": {"report_write", "artifact_manifest", "anonymous_eval", "eval_publish", "eval_archive"}, | |
| "done": {"done", "failure", "technical_blocker", "technical_blocker_boot_only", "manual_hardware_required"}, | |
| } | |
| STEP_TO_PHASE = {step: phase for phase, steps in PHASE_STEPS.items() for step in steps} | |
| SUCCESS_STATUSES = {"success", "done", "passed", "completed", "full_inference_success"} | |
| PARTIAL_STATUSES = {"full_inference_candidate_health_passed", "health_only", "partial", "partial_validation", "completed_with_warnings"} | |
| FAILED_STATUSES = {"failed", "failure", "error"} | |
| RUNNING_STATUSES = {"started", "running", "waiting", "pending", "scheduled"} | |
| CANCELLED_STATUSES = {"cancelled", "canceled"} | |
| MANUAL_STATUSES = {"manual_hardware_required", "manual_action_required", "generated_needs_manual_hardware", "waiting_manual_hardware"} | |
| BLOCKED_STATUSES = {"technical_blocker", "technical_blocker_boot_only", "blocked"} | |
| AUTH_STATUSES = {"auth_refresh_required", "oauth_expired", "auth_expired", "repair_validation_inconclusive_auth", "inconclusive_auth_expired"} | |
| TERMINAL_SOURCE_KEYS = ("status", "final_status", "effective_status", "effective_verdict", "display_status", "verdict", "result_status", "validation_status") | |
| def _lower(value: Any) -> str: | |
| return str(value or "").strip().lower() | |
| def _terminal_success(bundle: dict[str, Any], verdict: str | None = None) -> bool: | |
| """Return true when durable artifacts prove a terminal full-inference success. | |
| Historical run hydration may be requested with a truncated or missing event | |
| list. A final success artifact must still replay the whole product journey | |
| as complete instead of leaving early UI phases pending. | |
| """ | |
| normalized = _normalize_verdict_token(verdict) if verdict else "" | |
| if normalized == "full_inference_success": | |
| return True | |
| for src in ( | |
| bundle.get("final_status_reconciliation") or {}, | |
| bundle.get("summary") or {}, | |
| bundle.get("summary_file") or {}, | |
| bundle.get("state") or {}, | |
| bundle.get("live_status") or {}, | |
| ): | |
| if not isinstance(src, dict): | |
| continue | |
| if _normalize_verdict_token(src.get("status") or src.get("final_status") or src.get("display_status")) == "full_inference_success": | |
| return True | |
| smoke = _smoke(bundle) | |
| signals = _signals(bundle) | |
| if _lower(smoke.get("status")) == "success" or smoke.get("ok") is True or signals.get("generation_smoke_passed") is True: | |
| # Smoke success is treated as terminal-success proof only when a durable | |
| # final/summary source also says the run finished successfully. | |
| for src in (bundle.get("final_status_reconciliation") or {}, bundle.get("summary") or {}, bundle.get("summary_file") or {}): | |
| if isinstance(src, dict) and _normalize_verdict_token(src.get("status")) in {"full_inference_success", "success"}: | |
| return True | |
| return False | |
| def _events(bundle: dict[str, Any]) -> list[dict[str, Any]]: | |
| return [event for event in (bundle.get("events") or []) if isinstance(event, dict)] | |
| def _event_steps(bundle: dict[str, Any]) -> set[str]: | |
| return {_lower(event.get("step")) for event in _events(bundle) if event.get("step")} | |
| def _events_for_phase(bundle: dict[str, Any], phase: str) -> list[dict[str, Any]]: | |
| steps = PHASE_STEPS.get(phase, set()) | |
| return [event for event in _events(bundle) if _lower(event.get("step")) in steps] | |
| def _latest_event_for_phase(bundle: dict[str, Any], phase: str) -> dict[str, Any] | None: | |
| events = _events_for_phase(bundle, phase) | |
| return events[-1] if events else None | |
| def _latest_events_by_step(bundle: dict[str, Any], phase: str) -> dict[str, dict[str, Any]]: | |
| latest: dict[str, dict[str, Any]] = {} | |
| for event in _events_for_phase(bundle, phase): | |
| step = _lower(event.get("step")) | |
| if step: | |
| latest[step] = event | |
| return latest | |
| def _has_downstream_proof(bundle: dict[str, Any], phase: str) -> bool: | |
| steps = _event_steps(bundle) | |
| phase_index = PHASE_ORDER.index(phase) | |
| return any(steps.intersection(PHASE_STEPS[p]) for p in PHASE_ORDER[phase_index + 1 :]) | |
| def _has_successful_phase_event(bundle: dict[str, Any], phase: str) -> bool: | |
| return any(_lower(e.get("status")) in SUCCESS_STATUSES for e in _events_for_phase(bundle, phase)) | |
| def _has_status(bundle: dict[str, Any], statuses: set[str]) -> bool: | |
| state = bundle.get("state") or {} | |
| summary = bundle.get("summary") or {} | |
| gate = bundle.get("inference_gate") or {} | |
| smoke = bundle.get("generation_smoke") or {} | |
| values = { | |
| _lower(state.get("status")), | |
| _lower(state.get("gate_status")), | |
| _lower(summary.get("status")), | |
| _lower(gate.get("status")), | |
| _lower(smoke.get("status")), | |
| } | |
| values.update(_lower(event.get("status")) for event in _events(bundle)) | |
| return bool(values.intersection(statuses)) | |
| def _gate(bundle: dict[str, Any]) -> dict[str, Any]: | |
| return bundle.get("inference_gate") or {} | |
| def _signals(bundle: dict[str, Any]) -> dict[str, Any]: | |
| gate = _gate(bundle) | |
| return gate.get("implementation_signals") if isinstance(gate.get("implementation_signals"), dict) else {} | |
| def _smoke(bundle: dict[str, Any]) -> dict[str, Any]: | |
| smoke = bundle.get("generation_smoke") or {} | |
| if smoke: | |
| return smoke | |
| gate_smoke = (_gate(bundle).get("generation_smoke") or {}) | |
| return gate_smoke if isinstance(gate_smoke, dict) else {} | |
| def _runtime(bundle: dict[str, Any]) -> dict[str, Any]: | |
| runtime = bundle.get("space_runtime") or {} | |
| return runtime if isinstance(runtime, dict) else {} | |
| def _live_status(bundle: dict[str, Any]) -> dict[str, Any]: | |
| live = bundle.get("live_status") or {} | |
| return live if isinstance(live, dict) else {} | |
| def _latest_event_by_step(bundle: dict[str, Any], step_name: str) -> dict[str, Any]: | |
| target = _lower(step_name) | |
| for event in reversed(_events(bundle)): | |
| if _lower(event.get("step")) == target: | |
| return event | |
| return {} | |
| def _hardware_attempt_rows(bundle: dict[str, Any]) -> list[dict[str, Any]]: | |
| """Return normalized hardware-at-creation attempts. | |
| v198.26.11 uses these only to explain the Live Test story. They must not | |
| influence the run verdict. | |
| """ | |
| sources = (bundle.get("hardware_attempts") or {}, bundle.get("hardware_strategy") or {}, bundle.get("state") or {}) | |
| for source in sources: | |
| attempts = source.get("attempts") if isinstance(source, dict) else None | |
| if isinstance(attempts, list): | |
| return [a for a in attempts if isinstance(a, dict)] | |
| return [] | |
| def _attempt_hardware(attempt: dict[str, Any]) -> str: | |
| return str( | |
| attempt.get("hardware") | |
| or attempt.get("requested_hardware") | |
| or attempt.get("target_hardware") | |
| or attempt.get("sku") | |
| or attempt.get("flavor") | |
| or "" | |
| ).strip() | |
| def _attempt_status(attempt: dict[str, Any]) -> str: | |
| return _lower(attempt.get("status") or attempt.get("result") or attempt.get("outcome")) | |
| def _selected_hardware(bundle: dict[str, Any]) -> str: | |
| for source in (bundle.get("hardware_attempts") or {}, bundle.get("hardware_strategy") or {}, bundle.get("summary") or {}, bundle.get("state") or {}): | |
| if isinstance(source, dict): | |
| value = str(source.get("selected_hardware") or source.get("hardware") or "").strip() | |
| if value: | |
| return value | |
| return "" | |
| def _zero_gpu_fallback_story(bundle: dict[str, Any]) -> tuple[bool, str, str]: | |
| attempts = _hardware_attempt_rows(bundle) | |
| selected = _selected_hardware(bundle) | |
| zero_failed = False | |
| fallback_hardware = selected | |
| fallback_success = False | |
| for attempt in attempts: | |
| hw = _attempt_hardware(attempt) | |
| status = _attempt_status(attempt) | |
| hw_lower = _lower(hw) | |
| is_zero = "zero" in hw_lower or hw_lower.startswith("zero-") | |
| if is_zero and (status in FAILED_STATUSES or status in {"refused", "unavailable", "denied", "quota_exceeded", "quota"} or "fail" in status): | |
| zero_failed = True | |
| if not is_zero and (status in SUCCESS_STATUSES or status in {"selected", "created", "ok"}): | |
| fallback_success = True | |
| fallback_hardware = hw or selected | |
| if zero_failed and (fallback_success or (selected and "zero" not in _lower(selected))): | |
| return True, fallback_hardware or selected or "fallback hardware", "ZeroGPU unavailable → using " + (fallback_hardware or selected or "fallback hardware") | |
| return False, fallback_hardware or selected, "" | |
| def _latest_step_status(bundle: dict[str, Any], steps: set[str]) -> str: | |
| for event in reversed(_events(bundle)): | |
| if _lower(event.get("step")) in steps: | |
| return _lower(event.get("status")) | |
| return "" | |
| def _latest_step_message(bundle: dict[str, Any], steps: set[str]) -> str: | |
| for event in reversed(_events(bundle)): | |
| if _lower(event.get("step")) in steps: | |
| return str(event.get("message") or "").strip() | |
| return "" | |
| BUILD_ERROR_STAGE_TOKENS = {"BUILD_ERROR", "BUILD_FAILED", "CONFIG_ERROR", "NO_APP_FILE"} | |
| RUNTIME_ERROR_STAGE_TOKENS = {"RUNTIME_ERROR", "APP_START_ERROR", "STARTUP_ERROR"} | |
| BUILDING_STAGE_TOKENS = {"BUILDING", "BUILD_QUEUED", "QUEUED", "PENDING_BUILD"} | |
| STARTING_STAGE_TOKENS = {"STARTING", "RUNTIME_STARTING", "APP_STARTING", "LOADING", "RESTARTING"} | |
| RUNNING_STAGE_TOKENS = {"RUNNING", "RUNNING_BUILDING", "RUNNING_APP_STARTING"} | |
| def _runtime_stage_value(runtime: dict[str, Any]) -> str: | |
| return str(runtime.get("stage") or runtime.get("runtime_stage") or runtime.get("status") or runtime.get("phase") or "").strip() | |
| def _runtime_stage_upper(runtime: dict[str, Any]) -> str: | |
| return _runtime_stage_value(runtime).upper().replace("-", "_").replace(" ", "_") | |
| def _stage_has(upper_stage: str, tokens: set[str]) -> bool: | |
| if not upper_stage: | |
| return False | |
| return any(token in upper_stage for token in tokens) | |
| def _runtime_error_message(runtime: dict[str, Any]) -> str: | |
| return str(runtime.get("error") or runtime.get("message") or runtime.get("last_error") or "").strip() | |
| def _runtime_stage_facts(bundle: dict[str, Any], *, live: dict[str, Any], runtime: dict[str, Any], smoke: dict[str, Any], gate: dict[str, Any]) -> dict[str, Any]: | |
| signals = _signals(bundle) | |
| runtime_upload_epoch = bundle.get("runtime_upload_epoch") or {} | |
| upload_status = _latest_step_status(bundle, {"upload_files"}) | |
| runtime_uploaded = bool( | |
| runtime_upload_epoch.get("last_upload_completed_at") | |
| or runtime_upload_epoch.get("upload_sequence") | |
| or upload_status in SUCCESS_STATUSES | |
| ) | |
| stage_value = _runtime_stage_value(runtime) | |
| upper_stage = _runtime_stage_upper(runtime) | |
| smoke_status = _lower(smoke.get("status") or _latest_step_status(bundle, {"generation_smoke"})) | |
| api_status = _latest_step_status(bundle, {"endpoint_discovery", "api_validation"}) | |
| endpoints = gate.get("endpoints") or gate.get("api_endpoints") or gate.get("named_endpoints") or [] | |
| selected_endpoint = str(smoke.get("api_name") or gate.get("api_name") or gate.get("selected_endpoint") or gate.get("selected_api_name") or "").strip() | |
| endpoint_known = bool(selected_endpoint or (isinstance(endpoints, list) and endpoints) or api_status in SUCCESS_STATUSES or smoke_status == "success" or signals.get("generation_smoke_passed") is True) | |
| health_passed = signals.get("health_passed") is True or api_status in SUCCESS_STATUSES | |
| health_failed = signals.get("health_passed") is False or api_status in FAILED_STATUSES | |
| smoke_passed = smoke_status == "success" or smoke.get("ok") is True or signals.get("generation_smoke_passed") is True | |
| smoke_failed = smoke_status in FAILED_STATUSES or smoke_status in {"timeout", "exception"} | |
| build_error = _stage_has(upper_stage, BUILD_ERROR_STAGE_TOKENS) | |
| runtime_error = _stage_has(upper_stage, RUNTIME_ERROR_STAGE_TOKENS) | |
| building = _stage_has(upper_stage, BUILDING_STAGE_TOKENS) or ("BUILD" in upper_stage and not build_error) | |
| starting = _stage_has(upper_stage, STARTING_STAGE_TOKENS) | |
| running = _stage_has(upper_stage, RUNNING_STAGE_TOKENS) or "RUNNING" in upper_stage or endpoint_known or health_passed or smoke_passed | |
| return { | |
| "runtime_uploaded": runtime_uploaded, | |
| "stage_value": stage_value, | |
| "upper_stage": upper_stage, | |
| "build_error": build_error, | |
| "runtime_error": runtime_error, | |
| "building": building, | |
| "starting": starting, | |
| "running": running, | |
| "endpoint_known": endpoint_known, | |
| "selected_endpoint": selected_endpoint, | |
| "api_status": api_status, | |
| "health_passed": health_passed, | |
| "health_failed": health_failed, | |
| "smoke_status": smoke_status, | |
| "smoke_passed": smoke_passed, | |
| "smoke_failed": smoke_failed, | |
| "error_message": _runtime_error_message(runtime), | |
| } | |
| def build_runtime_stage_history(bundle: dict[str, Any], *, live: dict[str, Any] | None = None, runtime: dict[str, Any] | None = None, smoke: dict[str, Any] | None = None, gate: dict[str, Any] | None = None) -> dict[str, Any]: | |
| """Granular Space runtime state for the Live Test panel. | |
| v198.26.13 keeps verdict logic untouched and only translates durable Space | |
| facts into a user-facing state machine: build queued/building/build error, | |
| runtime starting/running/runtime error, API ready, and generation verified. | |
| """ | |
| live = live if isinstance(live, dict) else _live_status(bundle) | |
| runtime = runtime if isinstance(runtime, dict) else _runtime(bundle) | |
| smoke = smoke if isinstance(smoke, dict) else _smoke(bundle) | |
| gate = gate if isinstance(gate, dict) else _gate(bundle) | |
| facts = _runtime_stage_facts(bundle, live=live, runtime=runtime, smoke=smoke, gate=gate) | |
| stages: list[dict[str, Any]] = [] | |
| def add(stage: str, label: str, status: str, message: str, *, diagnostic: bool = False) -> None: | |
| stages.append({ | |
| "stage": stage, | |
| "id": stage, | |
| "label": label, | |
| "status": status, | |
| "message": message, | |
| "detail": message, | |
| "diagnostic": diagnostic, | |
| }) | |
| if facts["build_error"]: | |
| add("space_build", "Space build", "failed", facts["error_message"] or "Build error detected on Hugging Face", diagnostic=True) | |
| add("runtime_startup", "Runtime startup", "blocked", "Runtime startup cannot continue until the build error is diagnosed.", diagnostic=True) | |
| current_stage = "build_error" | |
| current_message = "Build error detected. Switching to diagnostic mode." | |
| status = "failed" | |
| elif facts["runtime_error"]: | |
| add("space_build", "Space build", "success", "Space build completed") | |
| add("runtime_startup", "Runtime startup", "failed", facts["error_message"] or "Runtime error detected while starting the app", diagnostic=True) | |
| current_stage = "runtime_error" | |
| current_message = "Runtime error detected. Switching to diagnostic mode." | |
| status = "failed" | |
| elif facts["smoke_passed"]: | |
| add("space_build", "Space build", "success", "Space build completed") | |
| add("runtime_startup", "Runtime startup", "success", "Space is running") | |
| add("api_readiness", "Gradio API", "ready", f"API ready{': ' + facts['selected_endpoint'] if facts['selected_endpoint'] else ''}") | |
| add("generation_smoke", "Generation smoke", "verified", "Generation verified") | |
| current_stage = "generation_verified" | |
| current_message = "Generation verified." | |
| status = "success" | |
| elif facts["smoke_failed"]: | |
| add("space_build", "Space build", "success", "Space build completed" if facts["running"] else "Build stage passed") | |
| add("runtime_startup", "Runtime startup", "success" if facts["running"] else "running", "Space is running" if facts["running"] else "Runtime is starting") | |
| if facts["endpoint_known"]: | |
| add("api_readiness", "Gradio API", "ready", f"API ready{': ' + facts['selected_endpoint'] if facts['selected_endpoint'] else ''}") | |
| else: | |
| add("api_readiness", "Gradio API", "running", "Waiting for Gradio API") | |
| add("generation_smoke", "Generation smoke", "failed", smoke.get("error") or smoke.get("message") or "Generation smoke failed", diagnostic=True) | |
| current_stage = "generation_smoke_failed" | |
| current_message = "Runtime is reachable, but generation was not verified." | |
| status = "failed" | |
| elif facts["endpoint_known"] or facts["api_status"] in SUCCESS_STATUSES: | |
| add("space_build", "Space build", "success", "Space build completed") | |
| add("runtime_startup", "Runtime startup", "success", "Space is running") | |
| add("api_readiness", "Gradio API", "ready", f"API ready{': ' + facts['selected_endpoint'] if facts['selected_endpoint'] else ''}") | |
| add("generation_smoke", "Generation smoke", "pending", "Waiting for generation smoke") | |
| current_stage = "api_ready" | |
| current_message = "Gradio API is ready. Waiting for generation smoke." | |
| status = "in_progress" | |
| elif facts["health_failed"]: | |
| add("space_build", "Space build", "success" if facts["running"] else "running", "Space build completed" if facts["running"] else "Waiting for build completion") | |
| add("runtime_startup", "Runtime startup", "failed", "Runtime became reachable but health/API did not pass", diagnostic=True) | |
| current_stage = "runtime_error" | |
| current_message = "Runtime health check failed. Switching to diagnostic mode if it does not recover." | |
| status = "failed" | |
| elif facts["running"]: | |
| add("space_build", "Space build", "success", "Space build completed") | |
| add("runtime_startup", "Runtime startup", "running", "Space is running; waiting for Gradio API") | |
| add("api_readiness", "Gradio API", "running", "Waiting for Gradio API and health check") | |
| current_stage = "runtime_starting" | |
| current_message = "Space is running. Waiting for Gradio API." | |
| status = "in_progress" | |
| elif facts["building"]: | |
| add("space_build", "Space build", "building", "Space is building on Hugging Face") | |
| add("runtime_startup", "Runtime startup", "pending", "Runtime startup begins after build completes") | |
| current_stage = "building" | |
| current_message = "Space is still building on Hugging Face." | |
| status = "in_progress" | |
| elif facts["runtime_uploaded"]: | |
| add("space_build", "Space build", "queued", "Runtime uploaded; waiting for Hugging Face build to start") | |
| add("runtime_startup", "Runtime startup", "pending", "Waiting for Space build") | |
| current_stage = "build_queued" | |
| current_message = "Runtime uploaded. Waiting for Hugging Face build." | |
| status = "in_progress" | |
| else: | |
| add("space_build", "Space build", "pending", "Waiting for runtime upload") | |
| add("runtime_startup", "Runtime startup", "pending", "Waiting for Space build") | |
| current_stage = "waiting_for_upload" | |
| current_message = "Waiting for runtime upload before build can start." | |
| status = "pending" | |
| diagnostic_mode = any(bool(item.get("diagnostic")) for item in stages) | |
| return { | |
| "schema_version": "runtime_stage_history.v198_26_13", | |
| "status": status, | |
| "current_stage": current_stage, | |
| "current_message": current_message, | |
| "terminal": status in {"success", "failed"}, | |
| "diagnostic_mode": diagnostic_mode, | |
| "runtime_stage": facts["stage_value"], | |
| "stages": stages, | |
| } | |
| def build_live_test_story(bundle: dict[str, Any], *, live: dict[str, Any] | None = None, runtime: dict[str, Any] | None = None, smoke: dict[str, Any] | None = None, gate: dict[str, Any] | None = None) -> dict[str, Any]: | |
| """Curated Live Test story for Active Run. | |
| This is deliberately not a raw event dump. It compresses retry/fallback | |
| mechanics and now includes a granular runtime stage machine so users know | |
| whether they are waiting for HF build, runtime startup, API readiness, or | |
| a diagnostic-worthy error. | |
| """ | |
| live = live if isinstance(live, dict) else _live_status(bundle) | |
| runtime = runtime if isinstance(runtime, dict) else _runtime(bundle) | |
| smoke = smoke if isinstance(smoke, dict) else _smoke(bundle) | |
| gate = gate if isinstance(gate, dict) else _gate(bundle) | |
| summary = bundle.get("summary") or {} | |
| state = bundle.get("state") or {} | |
| steps: list[dict[str, Any]] = [] | |
| def add(step_id: str, label: str, status: str, detail: Any = "", ts: Any = "") -> None: | |
| steps.append({ | |
| "id": step_id, | |
| "stage": step_id, | |
| "label": label, | |
| "status": status or "pending", | |
| "detail": str(detail or "")[:500], | |
| "ts": ts or "", | |
| }) | |
| fallback_used, fallback_hw, fallback_detail = _zero_gpu_fallback_story(bundle) | |
| selected_hw = fallback_hw or _selected_hardware(bundle) or str(summary.get("selected_hardware") or state.get("selected_hardware") or "").strip() | |
| create_status = _latest_step_status(bundle, {"create_space", "create_space_hardware"}) | |
| upload_status = _latest_step_status(bundle, {"upload_files"}) | |
| create_message = _latest_step_message(bundle, {"create_space", "create_space_hardware"}) | |
| runtime_upload_epoch = bundle.get("runtime_upload_epoch") or {} | |
| runtime_uploaded = bool( | |
| runtime_upload_epoch.get("last_upload_completed_at") | |
| or runtime_upload_epoch.get("upload_sequence") | |
| or upload_status in SUCCESS_STATUSES | |
| ) | |
| zero_attempt_failed_without_fallback = (not fallback_used) and any( | |
| ("zero" in _lower(_attempt_hardware(attempt))) and _attempt_status(attempt) in FAILED_STATUSES | |
| for attempt in _hardware_attempt_rows(bundle) | |
| ) | |
| if fallback_used: | |
| add("hardware", "Hardware provisioning", "fallback", fallback_detail) | |
| elif zero_attempt_failed_without_fallback or create_status in FAILED_STATUSES: | |
| add("hardware", "Hardware provisioning", "failed", create_message or "Hardware provisioning failed") | |
| elif selected_hw: | |
| add("hardware", "Hardware provisioning", "selected", f"{selected_hw} selected") | |
| else: | |
| add("hardware", "Hardware provisioning", "running" if create_status in RUNNING_STATUSES else "pending", "Selecting hardware") | |
| if create_status in FAILED_STATUSES and not fallback_used and not runtime_uploaded: | |
| add("space", "Space setup", "failed", create_message or "Space creation failed") | |
| elif create_status in SUCCESS_STATUSES or runtime_uploaded or summary.get("target_space") or state.get("target_space"): | |
| suffix = f" on {selected_hw}" if selected_hw else "" | |
| add("space", "Space setup", "success", f"Space created{suffix}") | |
| elif create_status in RUNNING_STATUSES: | |
| add("space", "Space setup", "running", "Creating Space") | |
| else: | |
| add("space", "Space setup", "pending", "Waiting for Space creation") | |
| if runtime_uploaded: | |
| add("runtime_upload", "Runtime deployment", "uploaded", "Runtime uploaded") | |
| elif upload_status in RUNNING_STATUSES: | |
| add("runtime_upload", "Runtime deployment", "running", "Uploading runtime") | |
| elif upload_status in FAILED_STATUSES: | |
| add("runtime_upload", "Runtime deployment", "failed", _latest_step_message(bundle, {"upload_files"}) or "Runtime upload failed") | |
| else: | |
| add("runtime_upload", "Runtime deployment", "pending", "Waiting for runtime upload") | |
| runtime_stage_history = build_runtime_stage_history(bundle, live=live, runtime=runtime, smoke=smoke, gate=gate) | |
| for row in runtime_stage_history.get("stages") or []: | |
| if not isinstance(row, dict): | |
| continue | |
| add(str(row.get("id") or row.get("stage") or "runtime_stage"), str(row.get("label") or "Runtime stage"), str(row.get("status") or "pending"), row.get("message") or row.get("detail") or "") | |
| story_status = runtime_stage_history.get("status") or "in_progress" | |
| if story_status == "success": | |
| # v198.26.25: a terminal generation smoke success should hydrate the | |
| # whole Live Test story as complete on historical reload, even when the | |
| # cached run selection shell lacked early create/upload events. | |
| for row in steps: | |
| row_id = str(row.get("id") or "") | |
| row_status = _lower(row.get("status")) | |
| if row_id == "hardware" and row_status in {"", "pending", "running", "selected"}: | |
| row["status"] = "selected" | |
| if not str(row.get("detail") or "").strip() or "selecting" in str(row.get("detail") or "").lower(): | |
| row["detail"] = f"{selected_hw} selected" if selected_hw else "Hardware selected" | |
| elif row_id == "space" and row_status in {"", "pending", "running"}: | |
| row["status"] = "success" | |
| if not str(row.get("detail") or "").strip() or "waiting" in str(row.get("detail") or "").lower(): | |
| row["detail"] = "Space created" | |
| elif row_id == "runtime_upload" and row_status in {"", "pending", "running"}: | |
| row["status"] = "uploaded" | |
| if not str(row.get("detail") or "").strip() or "waiting" in str(row.get("detail") or "").lower(): | |
| row["detail"] = "Runtime uploaded" | |
| headline = ( | |
| "Generation verified" if story_status == "success" | |
| else "Live test needs attention" if story_status == "failed" | |
| else runtime_stage_history.get("current_message") or "Waiting for Space runtime" | |
| ) | |
| return { | |
| "schema_version": "live_test_story.v198_26_13", | |
| "status": story_status, | |
| "headline": headline, | |
| "steps": steps, | |
| "fallback_used": fallback_used, | |
| "selected_hardware": selected_hw, | |
| "runtime_stage_history": runtime_stage_history, | |
| } | |
| def _runtime_history(bundle: dict[str, Any], *, live: dict[str, Any], runtime: dict[str, Any], smoke: dict[str, Any], gate: dict[str, Any]) -> list[dict[str, Any]]: | |
| """Return the curated Live Test story steps for timeline details. | |
| v198.26.11 replaces duplicate raw create/upload events with a process story: | |
| hardware fallback, Space setup, runtime deployment, HF build/runtime, API, smoke. | |
| """ | |
| return build_live_test_story(bundle, live=live, runtime=runtime, smoke=smoke, gate=gate).get("steps") or [] | |
| def build_live_validation_model(bundle: dict[str, Any]) -> dict[str, Any]: | |
| """Compact live validation telemetry for the UI. | |
| The timeline phase remains the coarse product step. This model gives the | |
| Live Validation card concrete runtime facts while the Space is building, | |
| starting, exposing Gradio endpoints, or running generation smoke tests. | |
| """ | |
| runtime = _runtime(bundle) | |
| smoke = _smoke(bundle) | |
| gate = _gate(bundle) | |
| signals = _signals(bundle) | |
| api_event = _latest_event_by_step(bundle, "api_validation") | |
| smoke_event = _latest_event_by_step(bundle, "generation_smoke") | |
| live_status = _live_status(bundle) | |
| live_stage = str(live_status.get("stage") or "").strip() | |
| live_message = str(live_status.get("message") or "").strip() | |
| runtime_stage = str(runtime.get("stage") or runtime.get("status") or "").strip() | |
| runtime_error = str(runtime.get("error") or "").strip() | |
| runtime_stage_history = build_runtime_stage_history(bundle, live=live_status, runtime=runtime, smoke=smoke, gate=gate) | |
| health_passed = signals.get("health_passed") is True or _lower(api_event.get("status")) in SUCCESS_STATUSES | |
| health_failed = signals.get("health_passed") is False or _lower(api_event.get("status")) in FAILED_STATUSES | |
| smoke_status = _lower(smoke.get("status") or smoke_event.get("status")) | |
| endpoints = gate.get("endpoints") or gate.get("api_endpoints") or gate.get("named_endpoints") or [] | |
| endpoint_count = len(endpoints) if isinstance(endpoints, list) else 0 | |
| selected_endpoint = str( | |
| smoke.get("api_name") | |
| or gate.get("api_name") | |
| or gate.get("selected_endpoint") | |
| or gate.get("selected_api_name") | |
| or "" | |
| ).strip() | |
| endpoint_known = bool(endpoint_count or selected_endpoint or smoke_status == "success" or signals.get("generation_smoke_passed") is True) | |
| severity = "info" | |
| stage = "waiting_for_space" | |
| label = "Waiting for Space runtime" | |
| next_action = "Waiting for the generated Space to build and start." | |
| upper_stage = runtime_stage.upper() | |
| granular_stage = str(runtime_stage_history.get("current_stage") or "") | |
| if granular_stage == "build_error": | |
| severity = "error" | |
| stage = "space_build_error" | |
| label = "Space build error" | |
| next_action = "Build error detected. Switching to diagnostic mode." | |
| elif granular_stage == "runtime_error": | |
| severity = "error" | |
| stage = "space_runtime_error" | |
| label = "Space runtime error" | |
| next_action = "Runtime error detected. Switching to diagnostic mode." | |
| elif smoke_status == "success" or signals.get("generation_smoke_passed") is True: | |
| severity = "success" | |
| stage = "generation_verified" | |
| label = "Generation verified" | |
| next_action = "The generated Space passed the automatic generation smoke test." | |
| elif smoke and smoke_status in FAILED_STATUSES.union({"timeout", "exception"}): | |
| severity = "warning" | |
| stage = "generation_smoke_failed" | |
| label = "Generation not verified" | |
| if smoke.get("auto_retry", {}).get("retried"): | |
| next_action = "The factory retried once with schema-corrected values. Use Prefill Space Test to retry manually if needed." | |
| elif smoke.get("retryable_with_schema_payload") or smoke.get("auto_retry_supported"): | |
| next_action = "Use Prefill Space Test after the run finishes to retry with schema-adjusted args." | |
| else: | |
| next_action = "Use Prefill Space Test after the run finishes to retry with adjusted args." | |
| elif live_stage == "generation_smoke_retry": | |
| severity = "info" | |
| stage = "generation_smoke_retry" | |
| label = "Retrying generation smoke" | |
| next_action = "Retrying once with schema-corrected Gradio choice values." | |
| elif live_stage == "generation_smoke": | |
| severity = "info" | |
| stage = "generation_smoke_running" | |
| label = "Generation smoke running" | |
| next_action = "Waiting for the generated endpoint response." | |
| elif live_stage == "endpoint_discovery": | |
| severity = "info" | |
| stage = "endpoint_discovery" | |
| label = "Discovering Gradio endpoints" | |
| next_action = "Inspecting the Space API schema before validation." | |
| elif _latest_event_by_step(bundle, "endpoint_discovery"): | |
| event = _latest_event_by_step(bundle, "endpoint_discovery") | |
| severity = "success" if _lower(event.get("status")) in SUCCESS_STATUSES else "info" | |
| stage = "endpoint_discovery" | |
| label = "Gradio endpoint discovery" | |
| next_action = str(event.get("message") or "Endpoint discovery completed.") | |
| elif health_passed: | |
| severity = "info" | |
| stage = "health_passed" | |
| label = "Health passed; waiting for generation smoke" | |
| next_action = "Waiting for Gradio endpoints and automatic generation validation." | |
| elif health_failed: | |
| severity = "warning" | |
| stage = "health_not_confirmed" | |
| label = "Health not confirmed" | |
| next_action = "Waiting for the Space API to become reachable or fail conclusively." | |
| elif granular_stage == "building": | |
| stage = "space_building" | |
| label = "Space is building" | |
| next_action = "Hugging Face is still building the Space runtime." | |
| elif granular_stage == "build_queued": | |
| stage = "space_build_queued" | |
| label = "Space build queued" | |
| next_action = "Runtime files are uploaded; waiting for Hugging Face build to start." | |
| elif granular_stage == "runtime_starting": | |
| stage = "space_starting" | |
| label = "Runtime is starting" | |
| next_action = "Space is running; waiting for Gradio API and health check." | |
| elif granular_stage == "api_ready": | |
| stage = "api_ready" | |
| label = "Gradio API ready" | |
| next_action = "Waiting for the automatic generation smoke test." | |
| elif endpoint_count: | |
| stage = "gradio_schema_detected" | |
| label = "Gradio endpoints detected" | |
| next_action = "Waiting for automatic generation smoke validation." | |
| return { | |
| "stage": stage, | |
| "label": label, | |
| "severity": severity, | |
| "runtime_stage": runtime_stage, | |
| "runtime_error": runtime_error, | |
| "health": "passed" if health_passed else "failed" if health_failed else "pending", | |
| "gradio_schema": "detected" if endpoint_known else "pending", | |
| "endpoint_count": endpoint_count, | |
| "selected_endpoint": selected_endpoint, | |
| "generation_smoke": "passed" if smoke_status == "success" else "failed" if smoke and smoke_status in FAILED_STATUSES.union({"timeout", "exception"}) else "pending", | |
| "live_status_stage": live_stage, | |
| "live_status_updated_at": live_status.get("updated_at") or "", | |
| "message": smoke.get("error") or smoke.get("message") or live_message or api_event.get("message") or runtime_error or "", | |
| "next_action": next_action, | |
| "runtime_history": _runtime_history(bundle, live=live_status, runtime=runtime, smoke=smoke, gate=gate), | |
| "runtime_stage_history": runtime_stage_history, | |
| "live_test_story": build_live_test_story(bundle, live=live_status, runtime=runtime, smoke=smoke, gate=gate), | |
| } | |
| def _eval_publish(bundle: dict[str, Any]) -> dict[str, Any]: | |
| status = bundle.get("eval_publish_status") or bundle.get("eval_publish") or {} | |
| return status if isinstance(status, dict) else {} | |
| def _build_error_observation(bundle: dict[str, Any]) -> dict[str, Any]: | |
| observation = bundle.get("build_error_observation") or {} | |
| return observation if isinstance(observation, dict) else {} | |
| def _final_blocker_label(bundle: dict[str, Any]) -> str: | |
| observation = _build_error_observation(bundle) | |
| reason = _lower(observation.get("reason")) | |
| tail = str(observation.get("tail") or observation.get("first_error") or "") | |
| if reason or "pyenv install 3.1" in tail or "BUILD FAILED" in tail: | |
| if "pyenv install 3.1" in tail or "python3.1" in tail.replace(" ", ""): | |
| return "Build failed: invalid Python version requested pyenv install 3.1" | |
| return "Build failed after repair" | |
| blockage = bundle.get("blockage") or {} | |
| if isinstance(blockage, dict): | |
| status = str(blockage.get("status") or "").replace("_", " ").strip() | |
| decision = blockage.get("decision") if isinstance(blockage.get("decision"), dict) else {} | |
| reason_text = str(decision.get("reason") or blockage.get("reason") or "").strip() | |
| if reason_text: | |
| return reason_text[:220] | |
| if status: | |
| return status.title() | |
| return "" | |
| def _is_record_not_ready(status: dict[str, Any]) -> bool: | |
| return _lower(status.get("reason")) == "record_not_ready" | |
| def _has_terminal_event(bundle: dict[str, Any]) -> bool: | |
| return any(_lower(event.get("step")) in {"done", "failure", "technical_blocker", "technical_blocker_boot_only", "manual_hardware_required"} for event in _events(bundle)) | |
| def _latest_recovery_event(bundle: dict[str, Any]) -> dict[str, Any] | None: | |
| events = _events_for_phase(bundle, "recovery") | |
| return events[-1] if events else None | |
| def _phase_status_from_events(bundle: dict[str, Any], phase: str) -> str: | |
| latest_by_step = _latest_events_by_step(bundle, phase) | |
| if not latest_by_step: | |
| return "pending" | |
| statuses = {_lower(event.get("status")) for event in latest_by_step.values()} | |
| if statuses.intersection(FAILED_STATUSES): | |
| return "failed" | |
| if statuses.intersection({"warning"}) or statuses.intersection(PARTIAL_STATUSES): | |
| return "warning" | |
| if statuses.intersection(RUNNING_STATUSES): | |
| # While a phase is actively running, the running signal must win over | |
| # earlier successful setup events in the same phase. Example: pi_config | |
| # can be success while pi_run is started/running; the Agent phase should | |
| # remain active and pulsing until a downstream proof or terminal event | |
| # later marks it complete. | |
| return "running" | |
| if statuses.intersection(SUCCESS_STATUSES): | |
| return "complete" | |
| return "complete" | |
| def _normalize_verdict_token(value: Any) -> str: | |
| normalized = _lower(value) | |
| if not normalized: | |
| return "" | |
| if normalized in AUTH_STATUSES: | |
| return "auth_refresh_required" | |
| if normalized in {"succeeded", "success", "done", "completed", "passed", "repair_success"}: | |
| return "full_inference_success" | |
| if normalized in SUCCESS_STATUSES: | |
| return "full_inference_success" if normalized == "full_inference_success" else "success" | |
| if normalized in PARTIAL_STATUSES or normalized in {"partial", "health_only", "full_inference_candidate_health_passed", "completed_with_warnings"}: | |
| return "partial_validation" | |
| if normalized in MANUAL_STATUSES: | |
| return "manual_action_required" | |
| if normalized in BLOCKED_STATUSES: | |
| return "technical_blocker_boot_only" if normalized == "technical_blocker_boot_only" else "technical_blocker" | |
| if normalized in FAILED_STATUSES: | |
| return "failed" | |
| if normalized in CANCELLED_STATUSES or normalized == "stopped": | |
| return "cancelled" | |
| if normalized == "stale": | |
| return "stale" | |
| if normalized in {"running", "pending", "queued", "started", "waiting", "scheduled"}: | |
| return "running" | |
| return normalized | |
| def _first_terminal_verdict_from_sources(bundle: dict[str, Any]) -> str: | |
| """Return authoritative terminal verdict for canonical timeline convergence. | |
| v198.26.7: final reconciliation and summary must win over stale state, | |
| live_status, or repair artifacts. This keeps Active Run and Timeline aligned | |
| with Run Explorer once the backend has written the final success/partial/fail. | |
| """ | |
| sources: list[dict[str, Any]] = [] | |
| for src in ( | |
| bundle.get("final_status_reconciliation") or {}, | |
| bundle.get("summary") or {}, | |
| bundle.get("summary_file") or {}, | |
| bundle.get("effective_run_status") or {}, | |
| (bundle.get("summary") or {}).get("effective_run_status") if isinstance((bundle.get("summary") or {}).get("effective_run_status"), dict) else {}, | |
| bundle.get("eval_record") or {}, | |
| bundle.get("state") or {}, | |
| bundle.get("live_status") or {}, | |
| ): | |
| if isinstance(src, dict): | |
| sources.append(src) | |
| for source in sources: | |
| for key in TERMINAL_SOURCE_KEYS: | |
| verdict = _normalize_verdict_token(source.get(key)) | |
| if verdict and verdict != "running": | |
| return verdict | |
| if source.get("ok") is True or source.get("generation_smoke_passed") is True: | |
| return "full_inference_success" | |
| return "" | |
| def _verdict(bundle: dict[str, Any]) -> str: | |
| terminal = _first_terminal_verdict_from_sources(bundle) | |
| if terminal: | |
| return terminal | |
| gate = _gate(bundle) | |
| state = bundle.get("state") or {} | |
| live = bundle.get("live_status") or {} | |
| repair = bundle.get("repair_outcome") or {} | |
| for value in (state.get("status"), live.get("status") if _lower(live.get("stage")) == "done" else "", gate.get("status"), state.get("gate_status"), repair.get("post_repair_validation"), repair.get("failure_type")): | |
| normalized = _normalize_verdict_token(value) | |
| if normalized: | |
| return normalized | |
| return "running" if _events(bundle) else "unknown" | |
| def _final_visual_status(verdict: str) -> str: | |
| if verdict in {"full_inference_success", "success", "passed"}: | |
| return "success" | |
| if verdict in {"partial_validation", "partial", "health_only", "completed_with_warnings"}: | |
| return "warn" | |
| if verdict in {"failed", "failure", "technical_blocker", "technical_blocker_boot_only"}: | |
| return "error" | |
| if verdict == "auth_refresh_required": | |
| return "warn" | |
| if verdict in {"manual_action_required", "cancelled"}: | |
| return "stopped" | |
| return "running" | |
| def _status_label(verdict: str) -> tuple[str, str]: | |
| if verdict == "full_inference_success": | |
| return "Full inference success", "Space boots, generation passed, and latency was measured." | |
| if verdict == "partial_validation": | |
| return "Completed with partial validation", "The run finished, but full generation was not verified." | |
| if verdict == "technical_blocker_boot_only": | |
| return "Technical blocker", "Health may pass, but no generation endpoint exists for full inference." | |
| if verdict == "technical_blocker": | |
| return "Technical blocker", "The run found a technical blocker." | |
| if verdict == "auth_refresh_required": | |
| return "Auth refresh required", "HF OAuth expired or is too close to expiry; sign in again before retrying validation." | |
| if verdict == "manual_action_required": | |
| return "Manual action required", "The run needs user action before validation can continue." | |
| if verdict == "failed": | |
| return "Failed", "The run did not complete successfully." | |
| if verdict == "cancelled": | |
| return "Cancelled", "The run was cancelled." | |
| if verdict in {"running", "pending", "unknown"}: | |
| return "Running", "" | |
| return verdict.replace("_", " ").title(), "" | |
| def _hardware_warning(bundle: dict[str, Any]) -> dict[str, Any] | None: | |
| events = _events_for_phase(bundle, "hardware") | |
| preferred_failed = any(_lower(e.get("step")) == "create_space_hardware" and _lower(e.get("status")) in FAILED_STATUSES for e in events) | |
| fallback_ok = any(_lower(e.get("step")) in {"create_space", "create_space_hardware"} and _lower(e.get("status")) in SUCCESS_STATUSES and _lower((e.get("data") or {}).get("hardware")) for e in events) | |
| if preferred_failed and fallback_ok: | |
| return {"code": "hardware_fallback_used", "label": "Fallback GPU used", "detail": "Preferred hardware was unavailable; the factory continued on fallback hardware."} | |
| return None | |
| def _pi_model_warning(bundle: dict[str, Any]) -> dict[str, Any] | None: | |
| for event in _events(bundle): | |
| if _lower(event.get("step")) == "pi_model_resolution" and _lower(event.get("status")) == "warning": | |
| data = event.get("data") or {} | |
| requested = data.get("requested_model") or data.get("configured_model") or "requested model" | |
| effective = data.get("effective_model") or "observed model" | |
| return {"code": "pi_model_changed", "label": "Pi assistant model changed", "detail": f"{requested} → {effective}"} | |
| return None | |
| def _logs_warning(bundle: dict[str, Any]) -> dict[str, Any] | None: | |
| logs = bundle.get("space_logs_index") or bundle.get("space_logs") or {} | |
| if not isinstance(logs, dict): | |
| return None | |
| quality = _lower(logs.get("log_quality") or logs.get("quality")) | |
| if quality and quality not in {"full", "complete"}: | |
| return {"code": "logs_partial", "label": "Space logs partial", "detail": f"Log quality: {quality}."} | |
| return None | |
| def _archive_warning(bundle: dict[str, Any], *, terminal: bool) -> dict[str, Any] | None: | |
| status = _eval_publish(bundle) | |
| if not status or status.get("published") is True: | |
| return None | |
| # record_not_ready is expected during the terminal write/reconcile window. | |
| # It is a neutral finalization state, not an action required for users. | |
| if _is_record_not_ready(status): | |
| return None | |
| if status.get("attempted"): | |
| return {"code": "eval_archive_not_published", "label": "Eval archive not published", "detail": str(status.get("reason") or "Backend archive copy did not complete.")} | |
| return None | |
| def _build_phase_details(bundle: dict[str, Any], phase: str) -> list[dict[str, Any]]: | |
| details: list[dict[str, Any]] = [] | |
| if phase == "deploy": | |
| blocker = _final_blocker_label(bundle) | |
| if blocker and _build_error_observation(bundle): | |
| details.append({"label": blocker, "status": "failed"}) | |
| else: | |
| latest = _latest_event_for_phase(bundle, phase) | |
| if latest: | |
| details.append({"label": str(latest.get("message") or latest.get("step") or PHASE_LABELS[phase]), "status": _lower(latest.get("status")) or "info"}) | |
| elif phase == "live_validation": | |
| live = build_live_validation_model(bundle) | |
| runtime_stage = live.get("runtime_stage") or "" | |
| runtime_history = live.get("runtime_history") if isinstance(live.get("runtime_history"), list) else [] | |
| for item in runtime_history: | |
| if isinstance(item, dict) and item.get("label"): | |
| details.append({"label": str(item.get("label")), "status": str(item.get("status") or "info"), "detail": str(item.get("detail") or "")}) | |
| if runtime_stage and not runtime_history: | |
| details.append({"label": f"Space runtime: {runtime_stage}", "status": "info"}) | |
| if live.get("health") == "passed": | |
| details.append({"label": "Health passed", "status": "complete"}) | |
| elif live.get("health") == "failed": | |
| details.append({"label": "Health not confirmed", "status": "warning"}) | |
| smoke = _smoke(bundle) | |
| generation_passed = live.get("generation_smoke") == "passed" | |
| if live.get("gradio_schema") == "detected": | |
| count = live.get("endpoint_count") or 0 | |
| selected = str(live.get("selected_endpoint") or smoke.get("api_name") or "").strip() | |
| if selected: | |
| details.append({"label": f"Endpoint selected: {selected}", "status": "complete"}) | |
| elif count: | |
| details.append({"label": f"Gradio endpoints detected: {count}", "status": "complete" if generation_passed else "info"}) | |
| elif not generation_passed: | |
| details.append({"label": "Waiting for Gradio endpoints", "status": "info"}) | |
| if generation_passed: | |
| pass | |
| elif live.get("generation_smoke") == "failed": | |
| reason = smoke.get("failure_type") or smoke.get("error") or live.get("message") or "Generation was not verified" | |
| details.append({"label": f"Generation not verified: {reason}", "status": "warning"}) | |
| auto_retry = smoke.get("auto_retry") if isinstance(smoke.get("auto_retry"), dict) else {} | |
| if auto_retry.get("retried"): | |
| details.append({"label": "Smoke test retried once with schema-corrected choices", "status": "info"}) | |
| elif smoke.get("retryable_with_schema_payload") or smoke.get("auto_retry_supported"): | |
| details.append({"label": "Schema-adjusted retry available in Space Test", "status": "info"}) | |
| else: | |
| details.append({"label": "Generation smoke pending", "status": "info"}) | |
| blocker = _final_blocker_label(bundle) | |
| if blocker: | |
| details.append({"label": blocker, "status": "failed"}) | |
| recommendation = smoke.get("recommended_zero_gpu_duration_seconds") or (_gate(bundle).get("zero_gpu_duration_recommendation") or {}).get("recommended_zero_gpu_duration_seconds") | |
| if recommendation: | |
| source_hw = str(_smoke(bundle).get("hardware") or _gate(bundle).get("hardware") or (bundle.get("summary") or {}).get("selected_hardware") or "").strip() | |
| suffix = f" · measured on {source_hw}" if source_hw else "" | |
| details.append({"label": f"ZeroGPU estimate: {recommendation}s{suffix}", "status": "complete"}) | |
| elif phase == "archive": | |
| eval_status = _eval_publish(bundle) | |
| if eval_status: | |
| if eval_status.get("published") is True: | |
| details.append({"label": "Eval archive published", "status": "complete"}) | |
| if eval_status.get("archive_relative_path"): | |
| details.append({"label": str(eval_status.get("archive_relative_path")), "status": "info"}) | |
| elif eval_status.get("attempted"): | |
| if _is_record_not_ready(eval_status): | |
| details.append({"label": "Eval archive finalizing", "status": "info"}) | |
| else: | |
| details.append({"label": f"Eval archive not published: {eval_status.get('reason') or 'unknown'}", "status": "warning"}) | |
| else: | |
| details.append({"label": "Eval archive pending", "status": "pending"}) | |
| elif phase == "recovery": | |
| steps = _event_steps(bundle) | |
| if not steps.intersection(PHASE_STEPS["recovery"]): | |
| details.append({"label": "Recovery not needed", "status": "skipped"}) | |
| else: | |
| failed = any(_lower(e.get("step")) == "repair_validation" and _lower(e.get("status")) in FAILED_STATUSES for e in _events(bundle)) | |
| if failed: | |
| details.append({"label": "Patch attempted; final blocker remains", "status": "failed"}) | |
| blocker = _final_blocker_label(bundle) | |
| if blocker: | |
| details.append({"label": blocker, "status": "failed"}) | |
| elif any(_lower(e.get("step")) == "repair_validation" and _lower(e.get("status")) in SUCCESS_STATUSES for e in _events(bundle)): | |
| details.append({"label": "Repair revalidated", "status": "complete"}) | |
| else: | |
| details.append({"label": "Recovery in progress", "status": "running"}) | |
| else: | |
| latest = _latest_event_for_phase(bundle, phase) | |
| if latest: | |
| details.append({"label": str(latest.get("message") or latest.get("step") or PHASE_LABELS[phase]), "status": _lower(latest.get("status")) or "info"}) | |
| return details | |
| def _phase_summary(bundle: dict[str, Any], phase: str, status: str) -> str: | |
| if phase == "agent": | |
| # A Pi model mismatch is an audit note rendered in Run notes, not a | |
| # phase-level issue. Keep the Agent phase focused on Pi execution and | |
| # trace collection so successful terminal runs do not show a misleading | |
| # NEEDS ATTENTION state. | |
| pass | |
| if phase == "hardware": | |
| warning = _hardware_warning(bundle) | |
| if warning: | |
| return warning["label"] | |
| if phase == "deploy": | |
| blocker = _final_blocker_label(bundle) | |
| if blocker and _build_error_observation(bundle): | |
| return blocker | |
| if phase == "live_validation": | |
| blocker = _final_blocker_label(bundle) | |
| if blocker and _final_visual_status(_verdict(bundle)) == "error": | |
| return "Live validation incomplete" | |
| signals = _signals(bundle) | |
| smoke = _smoke(bundle) | |
| if signals.get("generation_smoke_passed") is True or _lower(smoke.get("status")) == "success": | |
| latency = smoke.get("latency_seconds") or smoke.get("observed_latency_seconds") | |
| return f"Generation passed in {latency}s" if latency is not None else "Generation passed" | |
| if signals.get("health_passed") is True: | |
| return "Health passed; generation not verified" | |
| live = build_live_validation_model(bundle) | |
| return str(live.get("label") or ("Live validation pending" if status in {"pending", "running"} else "Live validation incomplete")) | |
| if phase == "recovery": | |
| steps = _event_steps(bundle) | |
| if not steps.intersection(PHASE_STEPS["recovery"]): | |
| return "Not needed" | |
| if any(_lower(e.get("step")) == "repair_validation" and _lower(e.get("status")) in FAILED_STATUSES for e in _events(bundle)): | |
| return "Patch attempted; final blocker remains" | |
| if any(_lower(e.get("step")) == "repair_validation" and _lower(e.get("status")) in SUCCESS_STATUSES for e in _events(bundle)): | |
| return "Repair revalidated" | |
| latest = _latest_recovery_event(bundle) or {} | |
| latest_step = _lower(latest.get("step")) | |
| if latest_step in {"repair_validation"}: | |
| return "Revalidating repair" | |
| if latest_step in {"repair", "repair_plan", "repair_patch", "repair_upload"}: | |
| return "Repairing" | |
| return "Diagnosing repair" | |
| if phase == "archive": | |
| eval_status = _eval_publish(bundle) | |
| if eval_status.get("published") is True: | |
| return "Eval archive published" | |
| if eval_status.get("attempted") and not _is_record_not_ready(eval_status): | |
| return "Eval archive attempted" | |
| if eval_status.get("attempted") and _is_record_not_ready(eval_status): | |
| return "Finalizing archive" if status in {"pending", "running"} else "Final artifacts written" | |
| return "Archive pending" if status in {"pending", "running"} else "Final artifacts written" | |
| if phase == "done": | |
| label, _ = _status_label(_verdict(bundle)) | |
| return label | |
| latest = _latest_event_for_phase(bundle, phase) | |
| if latest: | |
| return str(latest.get("message") or PHASE_LABELS[phase]) | |
| if status == "skipped": | |
| return "Not needed" | |
| return "Pending" if status == "pending" else PHASE_LABELS[phase] | |
| def _phase_status(bundle: dict[str, Any], phase: str, verdict: str) -> str: | |
| steps = _event_steps(bundle) | |
| terminal = _final_visual_status(verdict) in {"success", "warn", "error", "stopped"} | |
| terminal_success = _terminal_success(bundle, verdict) | |
| if terminal_success and phase not in {"done", "recovery"}: | |
| # v198.26.25: historical/full reloads can arrive with only final | |
| # artifacts or recent events. A full inference success proves that all | |
| # pre-live phases completed, so do not leave early dots gray. | |
| if phase == "hardware" and _hardware_warning(bundle): | |
| return "warning" | |
| return "complete" | |
| if phase == "done": | |
| if verdict in {"full_inference_success", "success", "passed"}: | |
| return "complete" | |
| if verdict in {"partial_validation", "partial", "completed_with_warnings", "health_only"}: | |
| return "warning" | |
| if verdict in {"failed", "technical_blocker", "technical_blocker_boot_only"}: | |
| return "failed" | |
| if verdict in {"manual_action_required", "cancelled"}: | |
| return "stopped" | |
| return "pending" | |
| if phase == "recovery": | |
| if terminal_success and not steps.intersection(PHASE_STEPS["recovery"]): | |
| return "skipped" | |
| if not steps.intersection(PHASE_STEPS["recovery"]): | |
| return "skipped" if terminal else "pending" | |
| if _final_visual_status(verdict) == "success": | |
| return "complete" | |
| if any(_lower(e.get("step")) == "repair_validation" and _lower(e.get("status")) in FAILED_STATUSES for e in _events(bundle)): | |
| return "failed" | |
| if any(_lower(e.get("step")) == "repair_validation" and _lower(e.get("status")) in SUCCESS_STATUSES for e in _events(bundle)): | |
| return "complete" | |
| latest_recovery = _latest_recovery_event(bundle) or {} | |
| latest_status = _lower(latest_recovery.get("status")) | |
| if latest_status in FAILED_STATUSES: | |
| return "failed" | |
| return "warning" if terminal else "running" | |
| if phase == "agent": | |
| latest_by_step = _latest_events_by_step(bundle, phase) | |
| pi_run = latest_by_step.get("pi_run") or {} | |
| # v198.26.9: terminal success with downstream proof closes Agent. An | |
| # old pi_run=running signal must not keep the visible dot pulsing after | |
| # deploy/live/archive/final success has already happened. | |
| if terminal and _final_visual_status(verdict) == "success" and _has_downstream_proof(bundle, phase): | |
| return "complete" | |
| if _lower(pi_run.get("status")) in RUNNING_STATUSES and not terminal: | |
| return "running" | |
| if terminal: | |
| material_events = [e for e in _events_for_phase(bundle, phase) if _lower(e.get("step")) != "pi_model_resolution"] | |
| material_statuses = {_lower(e.get("status")) for e in material_events} | |
| if material_statuses.intersection(FAILED_STATUSES) and _final_visual_status(verdict) != "success": | |
| return "failed" | |
| if material_statuses.intersection(RUNNING_STATUSES) and not _has_downstream_proof(bundle, phase): | |
| return "running" | |
| if material_statuses.intersection(SUCCESS_STATUSES) or _has_downstream_proof(bundle, phase): | |
| return "complete" | |
| if phase == "deploy": | |
| blocker = _final_blocker_label(bundle) | |
| if blocker and _build_error_observation(bundle): | |
| return "failed" | |
| if phase == "live_validation": | |
| blocker = _final_blocker_label(bundle) | |
| if blocker and _final_visual_status(_verdict(bundle)) == "error": | |
| return "failed" | |
| signals = _signals(bundle) | |
| smoke = _smoke(bundle) | |
| if _final_visual_status(verdict) == "success": | |
| return "complete" | |
| if signals.get("generation_smoke_passed") is True or _lower(smoke.get("status")) == "success": | |
| return "complete" | |
| if signals.get("health_passed") is True or smoke: | |
| return "warning" | |
| return _phase_status_from_events(bundle, phase) | |
| if phase == "archive": | |
| eval_status = _eval_publish(bundle) | |
| if eval_status.get("published") is True: | |
| return "complete" | |
| if eval_status.get("attempted"): | |
| if _is_record_not_ready(eval_status): | |
| return "pending" if not terminal else "complete" | |
| return "warning" | |
| if terminal: | |
| return "complete" | |
| return "pending" | |
| if phase == "deploy" and _build_error_observation(bundle): | |
| return "failed" | |
| # Pi model changes are surfaced as run notes, not phase warnings. | |
| if phase == "hardware" and _hardware_warning(bundle): | |
| return "warning" | |
| event_status = _phase_status_from_events(bundle, phase) | |
| if event_status != "pending": | |
| if event_status == "running" and terminal and (_has_downstream_proof(bundle, phase) or _has_successful_phase_event(bundle, phase)): | |
| return "complete" | |
| return event_status | |
| # On terminal runs, phases before the first missing/optional later phase can | |
| # be considered complete when downstream proofs exist. | |
| if terminal and _has_downstream_proof(bundle, phase): | |
| return "complete" | |
| return "pending" | |
| def build_run_timeline_model(bundle: dict[str, Any]) -> dict[str, Any]: | |
| """Return the canonical product progress/timeline projection for a run. | |
| This model is deliberately derived from persisted run artifacts and events, | |
| not from transient DOM state. It separates process completion from the final | |
| result verdict so a completed run can be success, partial, failed, blocked, | |
| or manual-action-required without the progress bar lying. | |
| """ | |
| bundle = bundle or {} | |
| state = bundle.get("state") or {} | |
| progress = progress_from_events(_events(bundle), state=state) | |
| verdict = _verdict(bundle) | |
| visual_status = _final_visual_status(verdict) | |
| label, subtitle = _status_label(verdict) | |
| terminal = visual_status in {"success", "warn", "error", "stopped"} | |
| percent = 100 if terminal else int(progress.get("progress") or 0) | |
| warnings = [w for w in (_pi_model_warning(bundle), _hardware_warning(bundle), _logs_warning(bundle), _archive_warning(bundle, terminal=terminal)) if w] | |
| phases: list[dict[str, Any]] = [] | |
| for phase in PHASE_ORDER: | |
| status = _phase_status(bundle, phase, verdict) | |
| phases.append( | |
| { | |
| "id": phase, | |
| "label": PHASE_LABELS[phase], | |
| "status": status, | |
| "summary": _phase_summary(bundle, phase, status), | |
| "details": _build_phase_details(bundle, phase), | |
| } | |
| ) | |
| return { | |
| "schema_version": "run_timeline_model.v1", | |
| "progress": { | |
| "percent": max(0, min(100, percent)), | |
| "visual_status": visual_status, | |
| "label": label, | |
| "subtitle": subtitle, | |
| "process_status": progress.get("status") or "unknown", | |
| "verdict": verdict, | |
| "terminal": terminal, | |
| }, | |
| "phases": phases, | |
| "warnings": warnings, | |
| "live_validation": build_live_validation_model(bundle), | |
| } | |