Upload 15 files
Browse files- src/bucket.py +2 -0
- src/effective_status.py +5 -2
- src/version.py +2 -2
- src/view_models.py +65 -8
- src/worker_payload.py +340 -32
src/bucket.py
CHANGED
|
@@ -1384,6 +1384,8 @@ def read_run_bundle(run_id: str, *, bucket_source: str, token: str | None = None
|
|
| 1384 |
"model_analysis": _safe_read_json(f"{paths.root}/model_analysis.json", token=token),
|
| 1385 |
"pi_model_resolution": _safe_read_json(f"{paths.root}/pi_model_resolution.json", token=token) or (read_json(paths.state, token=token) or {}).get("pi_model_resolution") or {},
|
| 1386 |
"space_runtime": _safe_read_json(f"{paths.root}/space_runtime.json", token=token),
|
|
|
|
|
|
|
| 1387 |
"api_schema": _safe_read_json(f"{paths.root}/tests/api_schema.json", token=token) or _safe_read_json(f"{paths.root}/tests/generation_api_schema.json", token=token),
|
| 1388 |
"validation_payload": _safe_read_json(f"{paths.root}/tests/validation_payload.json", token=token),
|
| 1389 |
"post_build_validation_status": _safe_read_json(f"{paths.root}/post_build_validation_status.json", token=token),
|
|
|
|
| 1384 |
"model_analysis": _safe_read_json(f"{paths.root}/model_analysis.json", token=token),
|
| 1385 |
"pi_model_resolution": _safe_read_json(f"{paths.root}/pi_model_resolution.json", token=token) or (read_json(paths.state, token=token) or {}).get("pi_model_resolution") or {},
|
| 1386 |
"space_runtime": _safe_read_json(f"{paths.root}/space_runtime.json", token=token),
|
| 1387 |
+
"runtime_upload_epoch": _safe_read_json(f"{paths.root}/runtime_upload_epoch.json", token=token),
|
| 1388 |
+
"placeholder_scaffold_detection": _safe_read_json(f"{paths.root}/placeholder_scaffold_detection.json", token=token),
|
| 1389 |
"api_schema": _safe_read_json(f"{paths.root}/tests/api_schema.json", token=token) or _safe_read_json(f"{paths.root}/tests/generation_api_schema.json", token=token),
|
| 1390 |
"validation_payload": _safe_read_json(f"{paths.root}/tests/validation_payload.json", token=token),
|
| 1391 |
"post_build_validation_status": _safe_read_json(f"{paths.root}/post_build_validation_status.json", token=token),
|
src/effective_status.py
CHANGED
|
@@ -23,6 +23,7 @@ PARTIAL_BUILD_TOKENS = {
|
|
| 23 |
"health_only",
|
| 24 |
"full_inference_candidate_health_passed",
|
| 25 |
"demo_usable_full_promise_not_verified",
|
|
|
|
| 26 |
"interactive_app_available_smoke_failed",
|
| 27 |
"manual_test_required_smoke_failed",
|
| 28 |
"completed_with_warnings",
|
|
@@ -150,7 +151,7 @@ def _normalize_build_status(value: Any) -> str:
|
|
| 150 |
return "stopped"
|
| 151 |
if status in {"full_inference_candidate_health_passed", "health_only", "partial", "completed_with_warnings"}:
|
| 152 |
return "partial_validation"
|
| 153 |
-
if status in {"demo_usable_full_promise_not_verified", "interactive_app_available_smoke_failed", "manual_test_required_smoke_failed"}:
|
| 154 |
return status
|
| 155 |
return status or "unknown"
|
| 156 |
|
|
@@ -211,6 +212,8 @@ def compute_effective_run_status(
|
|
| 211 |
display_label = "Full inference success"
|
| 212 |
elif display_status == "demo_usable_full_promise_not_verified":
|
| 213 |
display_label = "Demo usable — full promise not verified"
|
|
|
|
|
|
|
| 214 |
elif display_status == "interactive_app_available_smoke_failed":
|
| 215 |
display_label = "Smoke input issue — Space reachable"
|
| 216 |
elif display_status == "manual_test_required_smoke_failed":
|
|
@@ -229,7 +232,7 @@ def compute_effective_run_status(
|
|
| 229 |
display_label = display_status.replace("_", " ").title() if display_status else "Unknown"
|
| 230 |
|
| 231 |
return {
|
| 232 |
-
"schema_version": "effective_run_status.
|
| 233 |
"build_status": normalized_build,
|
| 234 |
"build_verdict": normalized_verdict,
|
| 235 |
"build_verdict_preserved": True,
|
|
|
|
| 23 |
"health_only",
|
| 24 |
"full_inference_candidate_health_passed",
|
| 25 |
"demo_usable_full_promise_not_verified",
|
| 26 |
+
"placeholder_scaffold_deployed",
|
| 27 |
"interactive_app_available_smoke_failed",
|
| 28 |
"manual_test_required_smoke_failed",
|
| 29 |
"completed_with_warnings",
|
|
|
|
| 151 |
return "stopped"
|
| 152 |
if status in {"full_inference_candidate_health_passed", "health_only", "partial", "completed_with_warnings"}:
|
| 153 |
return "partial_validation"
|
| 154 |
+
if status in {"demo_usable_full_promise_not_verified", "placeholder_scaffold_deployed", "interactive_app_available_smoke_failed", "manual_test_required_smoke_failed"}:
|
| 155 |
return status
|
| 156 |
return status or "unknown"
|
| 157 |
|
|
|
|
| 212 |
display_label = "Full inference success"
|
| 213 |
elif display_status == "demo_usable_full_promise_not_verified":
|
| 214 |
display_label = "Demo usable — full promise not verified"
|
| 215 |
+
elif display_status == "placeholder_scaffold_deployed":
|
| 216 |
+
display_label = "Placeholder demo — runtime not implemented"
|
| 217 |
elif display_status == "interactive_app_available_smoke_failed":
|
| 218 |
display_label = "Smoke input issue — Space reachable"
|
| 219 |
elif display_status == "manual_test_required_smoke_failed":
|
|
|
|
| 232 |
display_label = display_status.replace("_", " ").title() if display_status else "Unknown"
|
| 233 |
|
| 234 |
return {
|
| 235 |
+
"schema_version": "effective_run_status.v198_26_5",
|
| 236 |
"build_status": normalized_build,
|
| 237 |
"build_verdict": normalized_verdict,
|
| 238 |
"build_verdict_preserved": True,
|
src/version.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
-
ASF_APP_VERSION = "v198.26.
|
| 4 |
-
ASF_RELEASE_NAME = "Agentic Space Factory v198.26.
|
| 5 |
|
| 6 |
|
| 7 |
def resolve_app_version(value: str | None = None) -> str:
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
ASF_APP_VERSION = "v198.26.5"
|
| 4 |
+
ASF_RELEASE_NAME = "Agentic Space Factory v198.26.5"
|
| 5 |
|
| 6 |
|
| 7 |
def resolve_app_version(value: str | None = None) -> str:
|
src/view_models.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from datetime import datetime, timedelta, timezone
|
|
|
|
| 4 |
from typing import Any
|
| 5 |
|
| 6 |
from .progress import STEP_ALIASES, STEP_LABELS, STEP_ORDER
|
|
@@ -391,28 +392,82 @@ def build_diagnostics(bundle: dict[str, Any], status_model: dict[str, Any], phas
|
|
| 391 |
}
|
| 392 |
|
| 393 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
def _space_target_from_bundle(bundle: dict[str, Any]) -> str:
|
| 395 |
summary = bundle.get("summary") or {}
|
| 396 |
state = bundle.get("state") or {}
|
| 397 |
launch = bundle.get("launch") or {}
|
| 398 |
-
|
|
|
|
|
|
|
|
|
|
| 399 |
summary.get("target_space"),
|
| 400 |
state.get("target_space"),
|
| 401 |
launch.get("target_space"),
|
| 402 |
summary.get("target_space_id"),
|
| 403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
|
| 405 |
|
| 406 |
def _space_target_url_from_bundle(bundle: dict[str, Any], target: str = "") -> str:
|
| 407 |
summary = bundle.get("summary") or {}
|
| 408 |
state = bundle.get("state") or {}
|
| 409 |
launch = bundle.get("launch") or {}
|
| 410 |
-
|
|
|
|
| 411 |
if explicit:
|
| 412 |
return explicit
|
|
|
|
| 413 |
return f"https://huggingface.co/spaces/{target}" if target else ""
|
| 414 |
|
| 415 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
def _known_gradio_endpoint_info(bundle: dict[str, Any]) -> dict[str, Any]:
|
| 417 |
smoke = bundle.get("generation_smoke") or {}
|
| 418 |
gate = bundle.get("inference_gate") or {}
|
|
@@ -602,10 +657,12 @@ def build_run_view_model(
|
|
| 602 |
if created:
|
| 603 |
end = latest if status_model.get("is_terminal") and latest else current_now
|
| 604 |
elapsed_seconds = int((end - created).total_seconds())
|
|
|
|
|
|
|
| 605 |
links = {
|
| 606 |
"job_url": _job_url_from_view_sources(bucket_source=bucket_source, summary=summary, state=state, launch=bundle.get("launch") or {}),
|
| 607 |
-
"target_space_url":
|
| 608 |
-
"target_space_settings_url": f"{
|
| 609 |
"artifacts_url": summary.get("artifacts_url") or f"https://huggingface.co/buckets/{bucket_source}/tree/{_runs_prefix()}/{run_id}",
|
| 610 |
}
|
| 611 |
return {
|
|
@@ -619,8 +676,8 @@ def build_run_view_model(
|
|
| 619 |
"display_status": effective_run_status.get("display_status") or status_model.get("global_status"),
|
| 620 |
"display_label": effective_run_status.get("display_label") or status_model["global_status"].replace("_", " ").title(),
|
| 621 |
"raw_status": status_model["raw_status"],
|
| 622 |
-
"space": summary.get("target_space") or "",
|
| 623 |
-
"space_url":
|
| 624 |
"current_phase": phase,
|
| 625 |
"current_phase_label": next((s["label"] for s in PRODUCT_STEPS if s["id"] == phase), phase),
|
| 626 |
"elapsed_seconds": max(0, elapsed_seconds or 0),
|
|
@@ -643,7 +700,7 @@ def build_run_view_model(
|
|
| 643 |
"actions": {
|
| 644 |
"can_resume": status_model["global_status"] in {"running", "stale", "unknown"},
|
| 645 |
"can_stop": status_model["global_status"] in {"running", "queued", "unknown"},
|
| 646 |
-
"can_open_space": bool(
|
| 647 |
"can_validate": bool(space_test_policy.get("enabled")),
|
| 648 |
"requires_manual_action": status_model["requires_manual_action"],
|
| 649 |
},
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
from datetime import datetime, timedelta, timezone
|
| 4 |
+
import re
|
| 5 |
from typing import Any
|
| 6 |
|
| 7 |
from .progress import STEP_ALIASES, STEP_LABELS, STEP_ORDER
|
|
|
|
| 392 |
}
|
| 393 |
|
| 394 |
|
| 395 |
+
def _normalize_target_space_id(value: Any) -> str:
|
| 396 |
+
text = str(value or "").strip()
|
| 397 |
+
if not text:
|
| 398 |
+
return ""
|
| 399 |
+
text = text.replace("https://huggingface.co/spaces/", "").strip("/")
|
| 400 |
+
text = text.split("/settings", 1)[0].strip("/")
|
| 401 |
+
if ".hf.space" in text and "/" not in text:
|
| 402 |
+
return ""
|
| 403 |
+
return text if re.match(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$", text) else ""
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
def _space_target_from_events(events: list[dict[str, Any]] | None, *, require_success: bool = False) -> str:
|
| 407 |
+
for event in reversed(events or []):
|
| 408 |
+
if not isinstance(event, dict):
|
| 409 |
+
continue
|
| 410 |
+
step = str(event.get("step") or "")
|
| 411 |
+
status = str(event.get("status") or "").lower()
|
| 412 |
+
if step not in {"create_space", "create_space_hardware", "upload_files", "runtime_upload_epoch"}:
|
| 413 |
+
continue
|
| 414 |
+
if require_success and status != "success":
|
| 415 |
+
continue
|
| 416 |
+
data = event.get("data") if isinstance(event.get("data"), dict) else {}
|
| 417 |
+
target = _normalize_target_space_id(data.get("target_space") or data.get("target_space_id"))
|
| 418 |
+
if target:
|
| 419 |
+
return target
|
| 420 |
+
return ""
|
| 421 |
+
|
| 422 |
+
|
| 423 |
def _space_target_from_bundle(bundle: dict[str, Any]) -> str:
|
| 424 |
summary = bundle.get("summary") or {}
|
| 425 |
state = bundle.get("state") or {}
|
| 426 |
launch = bundle.get("launch") or {}
|
| 427 |
+
runtime_upload_epoch = bundle.get("runtime_upload_epoch") or {}
|
| 428 |
+
identity = bundle.get("space_identity") or {}
|
| 429 |
+
return _normalize_target_space_id(_first_nonempty(
|
| 430 |
+
identity.get("target_space"),
|
| 431 |
summary.get("target_space"),
|
| 432 |
state.get("target_space"),
|
| 433 |
launch.get("target_space"),
|
| 434 |
summary.get("target_space_id"),
|
| 435 |
+
state.get("target_space_id"),
|
| 436 |
+
launch.get("target_space_id"),
|
| 437 |
+
runtime_upload_epoch.get("target_space_id"),
|
| 438 |
+
_space_target_from_events(bundle.get("events") or []),
|
| 439 |
+
))
|
| 440 |
|
| 441 |
|
| 442 |
def _space_target_url_from_bundle(bundle: dict[str, Any], target: str = "") -> str:
|
| 443 |
summary = bundle.get("summary") or {}
|
| 444 |
state = bundle.get("state") or {}
|
| 445 |
launch = bundle.get("launch") or {}
|
| 446 |
+
identity = bundle.get("space_identity") or {}
|
| 447 |
+
explicit = _first_nonempty(identity.get("target_space_url"), summary.get("target_space_url"), state.get("target_space_url"), launch.get("target_space_url"))
|
| 448 |
if explicit:
|
| 449 |
return explicit
|
| 450 |
+
target = _normalize_target_space_id(target)
|
| 451 |
return f"https://huggingface.co/spaces/{target}" if target else ""
|
| 452 |
|
| 453 |
|
| 454 |
+
def _space_links_ready_from_bundle(bundle: dict[str, Any]) -> bool:
|
| 455 |
+
identity = bundle.get("space_identity") or {}
|
| 456 |
+
if identity.get("links_ready") or identity.get("space_created") or identity.get("space_uploaded"):
|
| 457 |
+
return True
|
| 458 |
+
runtime_upload_epoch = bundle.get("runtime_upload_epoch") or {}
|
| 459 |
+
if runtime_upload_epoch.get("last_upload_completed_at"):
|
| 460 |
+
return True
|
| 461 |
+
for event in bundle.get("events") or []:
|
| 462 |
+
if not isinstance(event, dict):
|
| 463 |
+
continue
|
| 464 |
+
step = str(event.get("step") or "")
|
| 465 |
+
status = str(event.get("status") or "").lower()
|
| 466 |
+
if status == "success" and step in {"create_space", "create_space_hardware", "upload_files", "runtime_upload_epoch"}:
|
| 467 |
+
return True
|
| 468 |
+
return False
|
| 469 |
+
|
| 470 |
+
|
| 471 |
def _known_gradio_endpoint_info(bundle: dict[str, Any]) -> dict[str, Any]:
|
| 472 |
smoke = bundle.get("generation_smoke") or {}
|
| 473 |
gate = bundle.get("inference_gate") or {}
|
|
|
|
| 657 |
if created:
|
| 658 |
end = latest if status_model.get("is_terminal") and latest else current_now
|
| 659 |
elapsed_seconds = int((end - created).total_seconds())
|
| 660 |
+
target = _space_target_from_bundle(bundle)
|
| 661 |
+
target_url = _space_target_url_from_bundle(bundle, target)
|
| 662 |
links = {
|
| 663 |
"job_url": _job_url_from_view_sources(bucket_source=bucket_source, summary=summary, state=state, launch=bundle.get("launch") or {}),
|
| 664 |
+
"target_space_url": target_url,
|
| 665 |
+
"target_space_settings_url": f"{target_url}/settings" if target_url else "",
|
| 666 |
"artifacts_url": summary.get("artifacts_url") or f"https://huggingface.co/buckets/{bucket_source}/tree/{_runs_prefix()}/{run_id}",
|
| 667 |
}
|
| 668 |
return {
|
|
|
|
| 676 |
"display_status": effective_run_status.get("display_status") or status_model.get("global_status"),
|
| 677 |
"display_label": effective_run_status.get("display_label") or status_model["global_status"].replace("_", " ").title(),
|
| 678 |
"raw_status": status_model["raw_status"],
|
| 679 |
+
"space": target or summary.get("target_space") or "",
|
| 680 |
+
"space_url": target_url,
|
| 681 |
"current_phase": phase,
|
| 682 |
"current_phase_label": next((s["label"] for s in PRODUCT_STEPS if s["id"] == phase), phase),
|
| 683 |
"elapsed_seconds": max(0, elapsed_seconds or 0),
|
|
|
|
| 700 |
"actions": {
|
| 701 |
"can_resume": status_model["global_status"] in {"running", "stale", "unknown"},
|
| 702 |
"can_stop": status_model["global_status"] in {"running", "queued", "unknown"},
|
| 703 |
+
"can_open_space": bool(target_url and _space_links_ready_from_bundle(bundle)),
|
| 704 |
"can_validate": bool(space_test_policy.get("enabled")),
|
| 705 |
"requires_manual_action": status_model["requires_manual_action"],
|
| 706 |
},
|
src/worker_payload.py
CHANGED
|
@@ -35,8 +35,8 @@ DEFAULT_PREFERRED_SPACE_HARDWARE = "zero-a10g"
|
|
| 35 |
DEFAULT_FALLBACK_SPACE_HARDWARE = "a10g-large"
|
| 36 |
DEFAULT_MODEL_ID = "sshleifer/tiny-gpt2"
|
| 37 |
MAX_PI_REPAIR_ATTEMPTS = 3
|
| 38 |
-
APP_VERSION = "v198.26.
|
| 39 |
-
app_version = "v198.26.
|
| 40 |
|
| 41 |
# Internal agent/recovery files may be needed inside the transient Pi
|
| 42 |
# workspace, but they should not be published to the generated Space or shown
|
|
@@ -1888,7 +1888,7 @@ def _gpu_partial_autopause_mode() -> str:
|
|
| 1888 |
def should_pause_generated_space(final_status: str | None, target_space: str | None, selected_hardware: str | None = None, *, keep_failed_spaces_running: bool | None = None, health_semantic_passed: bool | None = None) -> dict:
|
| 1889 |
"""Decide whether a generated Space should be paused after a terminal outcome.
|
| 1890 |
|
| 1891 |
-
v198.26.
|
| 1892 |
partial can still be useful and should be validated via Space Test/minimal
|
| 1893 |
smoke before pausing. `ASF_GPU_PARTIAL_AUTOPAUSE_MODE=strict` restores
|
| 1894 |
automatic pause for GPU partials; `recommended` emits a cost warning only.
|
|
@@ -2365,6 +2365,75 @@ def sanitize_model_id(model_id: str) -> str:
|
|
| 2365 |
return model_id
|
| 2366 |
|
| 2367 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2368 |
def make_gradio_client(target_space_id: str, token: str, timeout_s: int | float | None = None):
|
| 2369 |
import inspect
|
| 2370 |
from gradio_client import Client
|
|
@@ -2920,7 +2989,7 @@ def _smoke_param_name(param: dict) -> str:
|
|
| 2920 |
def _minimal_numeric_value(name: str, value, *, expected_output_type: str = ""):
|
| 2921 |
"""Return a safer minimal smoke value for expensive generation parameters.
|
| 2922 |
|
| 2923 |
-
v198.26.
|
| 2924 |
prove the Space is usable after a canonical/promise smoke OOM, but it must
|
| 2925 |
not be promoted to full inference success.
|
| 2926 |
"""
|
|
@@ -2986,7 +3055,7 @@ def build_minimal_demo_smoke_args(args: list, params: list[dict], expected_outpu
|
|
| 2986 |
def minimal_smoke_status_payload(payload: dict) -> dict:
|
| 2987 |
"""Mark a successful minimal smoke without claiming full promise success."""
|
| 2988 |
out = dict(payload or {})
|
| 2989 |
-
out["schema_version"] = "generation_smoke_result.
|
| 2990 |
out["status"] = "demo_usable_smoke_passed"
|
| 2991 |
out["demo_usable_smoke_passed"] = True
|
| 2992 |
out["canonical_promise_smoke_passed"] = False
|
|
@@ -3222,6 +3291,106 @@ def write_contract_skipped_generation_smoke(run_dir: Path, events_path: Path, ex
|
|
| 3222 |
return payload
|
| 3223 |
|
| 3224 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3225 |
def _canonical_smoke_dict_from_contracts(inference_contract: dict | None, demo_quality_contract: dict | None) -> tuple[dict | None, str]:
|
| 3226 |
"""Return the preferred canonical smoke example and its source.
|
| 3227 |
|
|
@@ -3282,24 +3451,16 @@ def canonical_smoke_example_payload(inference_contract: dict, demo_quality_contr
|
|
| 3282 |
|
| 3283 |
if isinstance(raw_args, dict):
|
| 3284 |
args = []
|
|
|
|
| 3285 |
for i, param in enumerate(params):
|
| 3286 |
-
|
| 3287 |
-
|
| 3288 |
-
|
| 3289 |
-
|
| 3290 |
-
name.
|
| 3291 |
-
|
| 3292 |
-
|
| 3293 |
-
|
| 3294 |
-
]
|
| 3295 |
-
found = False
|
| 3296 |
-
for candidate in candidates:
|
| 3297 |
-
if candidate in raw_args:
|
| 3298 |
-
args.append(coerce_smoke_value(raw_args[candidate], param))
|
| 3299 |
-
found = True
|
| 3300 |
-
break
|
| 3301 |
-
if not found:
|
| 3302 |
-
args.append(generated[i] if i < len(generated) else smoke_value_for_parameter(param, expected_output_type))
|
| 3303 |
if not params:
|
| 3304 |
args = list(raw_args.values())
|
| 3305 |
return {
|
|
@@ -3310,6 +3471,7 @@ def canonical_smoke_example_payload(inference_contract: dict, demo_quality_contr
|
|
| 3310 |
"source": source,
|
| 3311 |
"canonical_smoke_example_present": True,
|
| 3312 |
"canonical_smoke_reason": smoke.get("reason") or smoke.get("why_representative") or smoke.get("description") or "",
|
|
|
|
| 3313 |
}
|
| 3314 |
|
| 3315 |
# Empty/unsupported inputs: canonical exists but cannot be transformed safely.
|
|
@@ -3369,6 +3531,7 @@ def validation_health_semantics(validation: dict | None) -> dict:
|
|
| 3369 |
|
| 3370 |
negative_statuses = {"unhealthy", "failed", "failure", "error", "not_ready", "not ready", "runtime_error"}
|
| 3371 |
positive_statuses = {"healthy", "ok", "ready", "success", "passed"}
|
|
|
|
| 3372 |
for payload in _semantic_health_payloads(validation):
|
| 3373 |
for key in ("status", "health", "state"):
|
| 3374 |
if key in payload:
|
|
@@ -3379,6 +3542,12 @@ def validation_health_semantics(validation: dict | None) -> dict:
|
|
| 3379 |
elif value in positive_statuses and key != "state":
|
| 3380 |
details["semantic_checked"] = True
|
| 3381 |
details["positive_markers"].append(f"{key}={value}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3382 |
for key in ("pipeline_ready", "pipeline_loaded", "model_ready", "model_loaded"):
|
| 3383 |
if key in payload:
|
| 3384 |
details["semantic_checked"] = True
|
|
@@ -3428,6 +3597,45 @@ def is_cuda_oom_text(text: str) -> bool:
|
|
| 3428 |
return ("cuda" in lowered or "gpu" in lowered or "outofmemory" in lowered) and any(marker in lowered for marker in markers)
|
| 3429 |
|
| 3430 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3431 |
def _extract_missing_executable(text: str) -> str:
|
| 3432 |
text = str(text or "")
|
| 3433 |
patterns = [
|
|
@@ -3481,7 +3689,25 @@ def diagnose_generation_smoke_failure(error_or_result, *, inference_strategy: st
|
|
| 3481 |
marker = "no value provided for required argument:"
|
| 3482 |
missing_executable = _extract_missing_executable(text)
|
| 3483 |
|
| 3484 |
-
if "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3485 |
base.update({
|
| 3486 |
"failure_owner": "factory_validation_client",
|
| 3487 |
"failure_class": "smoke_input_materialization_failed",
|
|
@@ -3905,6 +4131,14 @@ def run_generation_smoke(target_space_id: str, token: str, run_dir: Path, events
|
|
| 3905 |
timeout_s = smoke_timeout_seconds(expected_output_type)
|
| 3906 |
append_event(events_path, "generation_smoke", "started", "Calling live generation endpoint for ZeroGPU timing", {"api_name": api_name, "expected_output_type": expected_output_type, "smoke_timeout_seconds": timeout_s})
|
| 3907 |
client = make_gradio_client(target_space_id, token, timeout_s=timeout_s)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3908 |
schema = client.view_api(return_format="dict")
|
| 3909 |
discovered = api_names_from_schema(schema)
|
| 3910 |
if api_name not in discovered and discovered:
|
|
@@ -3939,14 +4173,67 @@ def run_generation_smoke(target_space_id: str, token: str, run_dir: Path, events
|
|
| 3939 |
if contract_payload and contract_payload.get("canonical_smoke_example_present"):
|
| 3940 |
canonical_example, canonical_source = _canonical_smoke_dict_from_contracts(contract, demo_quality_contract)
|
| 3941 |
smoke_payload = {"api_name": api_name, "test_args": test_args, "raw_test_args": raw_test_args, "test_kwargs": test_kwargs, "parameters": smoke_parameters, "source": smoke_source, "contract_present": bool(contract), "demo_quality_contract_present": bool(demo_quality_contract), "canonical_smoke_example_present": bool(canonical_example), "canonical_smoke_example_source": canonical_source, "choice_corrections": initial_choice_changes, "file_input_conversions": file_input_conversions, "smoke_timeout_seconds": timeout_s}
|
|
|
|
|
|
|
| 3942 |
if canonical_example:
|
| 3943 |
-
write_json(run_dir / "tests" / "canonical_smoke_example.json", {"source": canonical_source, "example": canonical_example, "resolved_api_name": api_name, "raw_resolved_args": raw_test_args, "resolved_args": test_args, "resolved_kwargs": test_kwargs, "expected_output_type": expected_output_type, "file_input_conversions": file_input_conversions})
|
| 3944 |
write_json(run_dir / "tests" / "generation_smoke_payload.json", smoke_payload)
|
| 3945 |
write_json(run_dir / "tests" / "payload_source.json", {"schema_version": "1.0", "validation_engine": "unified_gradio_validation_harness", "validation_mode": "automatic_smoke", "payload_source": smoke_source, "selected_api_name": api_name, "parent_smoke_payload_used": False, "canonical_smoke_example_used": bool(canonical_example), "canonical_smoke_example_source": canonical_source})
|
| 3946 |
write_json(run_dir / "tests" / "validation_engine.json", {"schema_version": "1.0", "validation_engine": "unified_gradio_validation_harness", "validation_mode": "automatic_smoke", "resolved_request_required_before_predict": True})
|
| 3947 |
-
write_json(run_dir / "tests" / "resolved_validation_request.json", {"api_name": api_name, "test_args": test_args, "raw_test_args": raw_test_args, "test_kwargs": test_kwargs, "expected_output_type": expected_output_type, "validation_mode": "automatic_smoke", "payload_source": smoke_source, "canonical_smoke_example_used": bool(canonical_example), "canonical_smoke_example_source": canonical_source, "schema_choice_corrections": initial_choice_changes, "file_input_conversions": file_input_conversions, "smoke_timeout_seconds": timeout_s})
|
| 3948 |
-
write_json(run_dir / "tests" / "schema_coercion.json", {"api_name": api_name, "changes": initial_choice_changes, "original_args": raw_test_args, "resolved_args": test_args, "file_input_conversions": file_input_conversions})
|
| 3949 |
write_json(run_dir / "tests" / "generation_api_schema.json", {"schema": schema, "api_names": discovered, "selected_api_name": api_name, "endpoint_parameters": endpoint_parameters, "smoke_parameters": smoke_parameters, "smoke_payload_source": smoke_source, "canonical_smoke_example_used": bool(canonical_example)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3950 |
write_live_status(run_dir, stage="generation_smoke", status="running", message="Calling live generation endpoint", data={"api_name": api_name, "source": smoke_source, "canonical_smoke_example_used": bool(canonical_example)})
|
| 3951 |
started = time.time()
|
| 3952 |
retry_info = {"attempts": 1, "choice_corrections": initial_choice_changes, "retried": False}
|
|
@@ -3994,7 +4281,7 @@ def run_generation_smoke(target_space_id: str, token: str, run_dir: Path, events
|
|
| 3994 |
retry_info = {"attempts": 2, "choice_corrections": initial_choice_changes, "file_input_conversions": file_input_conversions, "retried": True, "first_error": str(first_error)[:2000], "retry_reason": "canonical_cuda_oom_minimal_demo_smoke", "minimal_changes": minimal_changes}
|
| 3995 |
minimal_artifact = {"api_name": api_name, "test_args": minimal_args, "raw_test_args": minimal_raw_args, "test_kwargs": test_kwargs, "parameters": minimal_params, "validation_level": "minimal_demo_smoke", **retry_info}
|
| 3996 |
write_json(run_dir / "tests" / "generation_smoke_payload_minimal.json", minimal_artifact)
|
| 3997 |
-
write_json(run_dir / "tests" / "minimal_demo_smoke_retry.json", {"schema_version": "minimal_demo_smoke_retry.
|
| 3998 |
try:
|
| 3999 |
result = client.predict(*minimal_args, api_name=api_name, **test_kwargs)
|
| 4000 |
raw_test_args = minimal_raw_args
|
|
@@ -4657,6 +4944,9 @@ def validate_http_health(target_space_id: str, token: str, run_dir: Path, events
|
|
| 4657 |
|
| 4658 |
def validate_gradio_api(target_space_id: str, token: str, run_dir: Path, events_path: Path, attempt: int):
|
| 4659 |
client = make_gradio_client(target_space_id, token)
|
|
|
|
|
|
|
|
|
|
| 4660 |
schema = client.view_api(return_format="dict")
|
| 4661 |
write_json(run_dir / "tests" / "api_schema.json", schema if isinstance(schema, dict) else {"schema": str(schema)})
|
| 4662 |
discovered = api_names_from_schema(schema)
|
|
@@ -9278,6 +9568,10 @@ def upload_workspace(api, workspace: Path, target_space_id: str, token: str, run
|
|
| 9278 |
if gen_dir.exists():
|
| 9279 |
shutil.rmtree(gen_dir)
|
| 9280 |
shutil.copytree(workspace, gen_dir, ignore=internal_workspace_copy_ignore)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9281 |
payload_dir, manifest = build_runtime_upload_payload(workspace, run_dir, events_path)
|
| 9282 |
try:
|
| 9283 |
api.upload_folder(
|
|
@@ -9329,6 +9623,7 @@ def infer_generation_gate(workspace: Path, implementation_mode: str, validation:
|
|
| 9329 |
req_text = (workspace / "requirements.txt").read_text(encoding="utf-8", errors="ignore") if (workspace / "requirements.txt").exists() else ""
|
| 9330 |
blockers_path = workspace / "TECHNICAL_BLOCKERS.json"
|
| 9331 |
blockers = load_json_if_exists(blockers_path)
|
|
|
|
| 9332 |
|
| 9333 |
combined = (app_text + "\n" + summary_text).lower()
|
| 9334 |
blocked_markers = [
|
|
@@ -9359,11 +9654,12 @@ def infer_generation_gate(workspace: Path, implementation_mode: str, validation:
|
|
| 9359 |
full_inference_requested = implementation_mode in {"full-inference-gated", "full-inference-attempt"}
|
| 9360 |
promise_fulfilled = bool(promise_validation.get("promise_fulfilled"))
|
| 9361 |
heuristic_marker_detected = any(m in combined for m in blocked_markers)
|
|
|
|
| 9362 |
blocker_source = str(blockers.get("source") or "") if isinstance(blockers, dict) else ""
|
| 9363 |
heuristic_blocker_json = bool(blockers) and blocker_source == "worker_heuristic_from_PI_SUMMARY_or_app.py"
|
| 9364 |
heuristic_blocker_detected = bool(heuristic_marker_detected or heuristic_blocker_json)
|
| 9365 |
contract_blocker_detected = bool(contract_no_full or diagnostic_contract)
|
| 9366 |
-
hard_blocker_detected = bool((bool(blockers) and not heuristic_blocker_json) or contract_blocker_detected)
|
| 9367 |
blocker_detected = bool(hard_blocker_detected or heuristic_blocker_detected)
|
| 9368 |
strong_full_inference_success = bool(full_inference_requested and smoke_ok and promise_fulfilled)
|
| 9369 |
recommendation = generation_smoke if isinstance(generation_smoke, dict) else measured_zero_gpu_recommendation(None)
|
|
@@ -9379,6 +9675,7 @@ def infer_generation_gate(workspace: Path, implementation_mode: str, validation:
|
|
| 9379 |
"health_semantic_passed": health_semantics.get("semantic_passed"),
|
| 9380 |
"health_semantic_negative_markers": health_semantics.get("negative_markers") or [],
|
| 9381 |
"health_load_error": health_semantics.get("load_error") or "",
|
|
|
|
| 9382 |
"generation_smoke_passed": smoke_ok,
|
| 9383 |
"demo_usable_smoke_passed": minimal_smoke_ok,
|
| 9384 |
"canonical_promise_smoke_passed": smoke_ok,
|
|
@@ -9393,10 +9690,17 @@ def infer_generation_gate(workspace: Path, implementation_mode: str, validation:
|
|
| 9393 |
"source": "TECHNICAL_BLOCKERS.json" if heuristic_blocker_json else "PI_SUMMARY.md_or_app.py",
|
| 9394 |
"non_blocking": True,
|
| 9395 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9396 |
if hard_blocker_detected:
|
| 9397 |
blocking_evidence.append({
|
| 9398 |
"type": "contract_or_explicit_blocker",
|
| 9399 |
-
"source": "INFERENCE_CONTRACT.json_or_TECHNICAL_BLOCKERS.
|
| 9400 |
"overridable_by_live_smoke": True,
|
| 9401 |
})
|
| 9402 |
|
|
@@ -9421,6 +9725,9 @@ def infer_generation_gate(workspace: Path, implementation_mode: str, validation:
|
|
| 9421 |
message = "Generation smoke was skipped by contract because full inference was declared unavailable; this is not a full inference success."
|
| 9422 |
else:
|
| 9423 |
message = "Space boots/responds as a diagnostic app, but real/full model inference is not implemented; this is not a full inference success."
|
|
|
|
|
|
|
|
|
|
| 9424 |
elif hard_blocker_detected:
|
| 9425 |
status = "technical_blocker"
|
| 9426 |
message = "Space boots, but full model inference was not implemented. See TECHNICAL_BLOCKERS.json / PI_SUMMARY.md."
|
|
@@ -9518,6 +9825,7 @@ def infer_generation_gate(workspace: Path, implementation_mode: str, validation:
|
|
| 9518 |
"recommendation_confidence": recommendation.get("recommendation_confidence"),
|
| 9519 |
"measurement_note": recommendation.get("measurement_note"),
|
| 9520 |
},
|
|
|
|
| 9521 |
"blockers": blockers,
|
| 9522 |
}
|
| 9523 |
write_json(run_dir / "inference_gate.json", gate)
|
|
@@ -12096,7 +12404,7 @@ def smoke_generate(target_space_id: str, token: str, run_dir: Path, events_path:
|
|
| 12096 |
# write_json(run_dir / "tests" / "payload_source.json", payload_source_record)
|
| 12097 |
# write_json(run_dir / "tests" / "replay_source.json", replay_source)
|
| 12098 |
# write_json(run_dir / "tests" / "validation_preflight.json", validation_preflight)
|
| 12099 |
-
app_version = "v198.26.
|
| 12100 |
engine_version = "unified_gradio_validation_harness_v198_25_3"
|
| 12101 |
parent_build_run_id = os.environ.get("PARENT_BUILD_RUN_ID", "").strip()
|
| 12102 |
validation_mode = os.environ.get("VALIDATION_MODE") or os.environ.get("SPACE_TEST_POLICY_MODE") or "linked"
|
|
@@ -12481,7 +12789,7 @@ def main():
|
|
| 12481 |
effective_status_on_success = os.environ.get("EFFECTIVE_STATUS_ON_SUCCESS") or "validated_after_space_test"
|
| 12482 |
space_test_policy_mode = os.environ.get("SPACE_TEST_POLICY_MODE") or "complete"
|
| 12483 |
manual_status = {
|
| 12484 |
-
"schema_version": "post_build_validation.
|
| 12485 |
"status": "success",
|
| 12486 |
"effective_status": effective_status_on_success,
|
| 12487 |
"legacy_effective_status": "validated_after_manual_space_test",
|
|
@@ -12559,7 +12867,7 @@ Target Space: [`{target_space_id}`](https://huggingface.co/spaces/{target_space_
|
|
| 12559 |
if parent_build_run_id:
|
| 12560 |
parent_dir = output_root / "runs" / parent_build_run_id
|
| 12561 |
parent_dir.mkdir(parents=True, exist_ok=True)
|
| 12562 |
-
failed_status = {"schema_version": "post_build_validation.
|
| 12563 |
linked_path = parent_dir / "linked_validations.json"
|
| 12564 |
linked = read_json(linked_path, {"parent_build_run_id": parent_build_run_id, "validations": []}) or {"parent_build_run_id": parent_build_run_id, "validations": []}
|
| 12565 |
validations = linked.get("validations") if isinstance(linked, dict) else []
|
|
|
|
| 35 |
DEFAULT_FALLBACK_SPACE_HARDWARE = "a10g-large"
|
| 36 |
DEFAULT_MODEL_ID = "sshleifer/tiny-gpt2"
|
| 37 |
MAX_PI_REPAIR_ATTEMPTS = 3
|
| 38 |
+
APP_VERSION = "v198.26.5"
|
| 39 |
+
app_version = "v198.26.5"
|
| 40 |
|
| 41 |
# Internal agent/recovery files may be needed inside the transient Pi
|
| 42 |
# workspace, but they should not be published to the generated Space or shown
|
|
|
|
| 1888 |
def should_pause_generated_space(final_status: str | None, target_space: str | None, selected_hardware: str | None = None, *, keep_failed_spaces_running: bool | None = None, health_semantic_passed: bool | None = None) -> dict:
|
| 1889 |
"""Decide whether a generated Space should be paused after a terminal outcome.
|
| 1890 |
|
| 1891 |
+
v198.26.5 keeps GPU partial cleanup non-aggressive by default: a reachable
|
| 1892 |
partial can still be useful and should be validated via Space Test/minimal
|
| 1893 |
smoke before pausing. `ASF_GPU_PARTIAL_AUTOPAUSE_MODE=strict` restores
|
| 1894 |
automatic pause for GPU partials; `recommended` emits a cost warning only.
|
|
|
|
| 2365 |
return model_id
|
| 2366 |
|
| 2367 |
|
| 2368 |
+
|
| 2369 |
+
|
| 2370 |
+
def expected_space_subdomain(target_space_id: str) -> str:
|
| 2371 |
+
owner, _, slug = str(target_space_id or "").partition("/")
|
| 2372 |
+
if not owner or not slug:
|
| 2373 |
+
return ""
|
| 2374 |
+
return re.sub(r"[^a-z0-9-]+", "-", f"{owner}-{slug}".lower()).strip("-")
|
| 2375 |
+
|
| 2376 |
+
|
| 2377 |
+
def gradio_client_identity_payload(client, target_space_id: str) -> dict:
|
| 2378 |
+
"""Best-effort identity check for gradio_client target resolution.
|
| 2379 |
+
|
| 2380 |
+
v198.26.5: inspect every observed `.hf.space` URL independently. A
|
| 2381 |
+
correct repo id in `client.src` must not hide a mismatched app URL such as
|
| 2382 |
+
`...-3a319105` expected but `...-2b5b161.hf.space` loaded by the client.
|
| 2383 |
+
"""
|
| 2384 |
+
from urllib.parse import urlparse
|
| 2385 |
+
observed_values = []
|
| 2386 |
+
for attr in ("src", "space_id", "space_name", "app_url", "root_url", "src_url", "api_url"):
|
| 2387 |
+
try:
|
| 2388 |
+
value = getattr(client, attr, None)
|
| 2389 |
+
except Exception:
|
| 2390 |
+
value = None
|
| 2391 |
+
if value:
|
| 2392 |
+
observed_values.append({"attr": attr, "value": str(value)})
|
| 2393 |
+
expected_repo = str(target_space_id or "")
|
| 2394 |
+
expected_subdomain = expected_space_subdomain(expected_repo)
|
| 2395 |
+
observed_text = " ".join(item["value"] for item in observed_values).lower()
|
| 2396 |
+
observed_hf_space_urls = []
|
| 2397 |
+
mismatched_hf_space_urls = []
|
| 2398 |
+
for item in observed_values:
|
| 2399 |
+
value = item["value"]
|
| 2400 |
+
if ".hf.space" not in value:
|
| 2401 |
+
continue
|
| 2402 |
+
parsed = urlparse(value if value.startswith(("http://", "https://")) else "https://" + value.lstrip("/"))
|
| 2403 |
+
host = (parsed.netloc or parsed.path.split("/", 1)[0]).lower()
|
| 2404 |
+
if not host.endswith(".hf.space"):
|
| 2405 |
+
continue
|
| 2406 |
+
subdomain = host.rsplit(".hf.space", 1)[0]
|
| 2407 |
+
record = {"attr": item["attr"], "value": value, "host": host, "subdomain": subdomain}
|
| 2408 |
+
observed_hf_space_urls.append(record)
|
| 2409 |
+
if expected_subdomain and subdomain != expected_subdomain:
|
| 2410 |
+
mismatched_hf_space_urls.append(record)
|
| 2411 |
+
mismatch = False
|
| 2412 |
+
if mismatched_hf_space_urls:
|
| 2413 |
+
mismatch = True
|
| 2414 |
+
elif observed_text and expected_repo and expected_repo.lower() not in observed_text and not observed_hf_space_urls:
|
| 2415 |
+
mismatch = True
|
| 2416 |
+
return {
|
| 2417 |
+
"schema_version": "gradio_client_identity.v198_26_5",
|
| 2418 |
+
"target_space_id": expected_repo,
|
| 2419 |
+
"expected_subdomain": expected_subdomain,
|
| 2420 |
+
"observed": observed_values,
|
| 2421 |
+
"observed_hf_space_urls": observed_hf_space_urls,
|
| 2422 |
+
"mismatched_hf_space_urls": mismatched_hf_space_urls,
|
| 2423 |
+
"mismatch": bool(mismatch),
|
| 2424 |
+
"failure_owner": "factory_validation_client" if mismatch else "",
|
| 2425 |
+
"failure_class": "space_identity_mismatch" if mismatch else "",
|
| 2426 |
+
}
|
| 2427 |
+
|
| 2428 |
+
|
| 2429 |
+
def write_gradio_client_identity(client, target_space_id: str, run_dir: Path, events_path: Path, *, phase: str) -> dict:
|
| 2430 |
+
payload = gradio_client_identity_payload(client, target_space_id)
|
| 2431 |
+
payload["phase"] = phase
|
| 2432 |
+
write_json(run_dir / "tests" / f"gradio_client_identity_{phase}.json", payload)
|
| 2433 |
+
if payload.get("mismatch"):
|
| 2434 |
+
append_event(events_path, "space_identity", "failed", "Gradio client resolved a different Space than the run target", payload)
|
| 2435 |
+
return payload
|
| 2436 |
+
|
| 2437 |
def make_gradio_client(target_space_id: str, token: str, timeout_s: int | float | None = None):
|
| 2438 |
import inspect
|
| 2439 |
from gradio_client import Client
|
|
|
|
| 2989 |
def _minimal_numeric_value(name: str, value, *, expected_output_type: str = ""):
|
| 2990 |
"""Return a safer minimal smoke value for expensive generation parameters.
|
| 2991 |
|
| 2992 |
+
v198.26.5 introduces a second validation level: a minimal demo smoke can
|
| 2993 |
prove the Space is usable after a canonical/promise smoke OOM, but it must
|
| 2994 |
not be promoted to full inference success.
|
| 2995 |
"""
|
|
|
|
| 3055 |
def minimal_smoke_status_payload(payload: dict) -> dict:
|
| 3056 |
"""Mark a successful minimal smoke without claiming full promise success."""
|
| 3057 |
out = dict(payload or {})
|
| 3058 |
+
out["schema_version"] = "generation_smoke_result.v198_26_5"
|
| 3059 |
out["status"] = "demo_usable_smoke_passed"
|
| 3060 |
out["demo_usable_smoke_passed"] = True
|
| 3061 |
out["canonical_promise_smoke_passed"] = False
|
|
|
|
| 3291 |
return payload
|
| 3292 |
|
| 3293 |
|
| 3294 |
+
|
| 3295 |
+
|
| 3296 |
+
def _param_semantic_name(param: dict, index: int, expected_output_type: str = "") -> str:
|
| 3297 |
+
"""Best-effort semantic name for anonymous Gradio `param_N` schemas.
|
| 3298 |
+
|
| 3299 |
+
v198.26.5: gradio_client can expose Textbox/Number inputs as param_0,
|
| 3300 |
+
param_1... while Pi's canonical smoke example uses semantic keys like
|
| 3301 |
+
prompt/width/height/steps/seed. A positional anonymous fallback prevents a
|
| 3302 |
+
valid prompt from being replaced by the schema-generated empty string.
|
| 3303 |
+
"""
|
| 3304 |
+
raw = str((param or {}).get("name") or "").strip().lower().replace("-", "_").replace(" ", "_")
|
| 3305 |
+
component = str((param or {}).get("component") or "").strip().lower()
|
| 3306 |
+
if raw and not re.fullmatch(r"param_\d+|arg\d+", raw):
|
| 3307 |
+
return raw
|
| 3308 |
+
expected = str(expected_output_type or "").strip().lower()
|
| 3309 |
+
if index == 0 and any(token in component for token in ("textbox", "text", "str")):
|
| 3310 |
+
return "prompt"
|
| 3311 |
+
if index == 0 and expected in {"text", "audio", "video", "image"}:
|
| 3312 |
+
return "prompt"
|
| 3313 |
+
common = ["prompt", "width", "height", "num_inference_steps", "seed", "guidance_scale"]
|
| 3314 |
+
if expected == "audio":
|
| 3315 |
+
common = ["prompt", "duration", "steps", "seed", "guidance_scale"]
|
| 3316 |
+
if expected == "video":
|
| 3317 |
+
common = ["prompt", "width", "height", "num_frames", "num_inference_steps", "seed"]
|
| 3318 |
+
if index < len(common):
|
| 3319 |
+
return common[index]
|
| 3320 |
+
return raw or f"arg{index}"
|
| 3321 |
+
|
| 3322 |
+
|
| 3323 |
+
def _canonical_key_aliases(name: str) -> list[str]:
|
| 3324 |
+
name = str(name or "").strip().lower().replace("-", "_").replace(" ", "_")
|
| 3325 |
+
aliases = {
|
| 3326 |
+
"prompt": ["prompt", "text", "query", "input", "instruction", "caption"],
|
| 3327 |
+
"negative_prompt": ["negative_prompt", "negative", "negative_text"],
|
| 3328 |
+
"width": ["width", "image_width", "w"],
|
| 3329 |
+
"height": ["height", "image_height", "h"],
|
| 3330 |
+
"num_inference_steps": ["num_inference_steps", "inference_steps", "steps", "num_steps", "sampling_steps"],
|
| 3331 |
+
"steps": ["steps", "num_inference_steps", "inference_steps", "num_steps", "sampling_steps"],
|
| 3332 |
+
"seed": ["seed", "random_seed"],
|
| 3333 |
+
"guidance_scale": ["guidance_scale", "cfg_scale", "scale", "guidance"],
|
| 3334 |
+
"duration": ["duration", "seconds", "length", "audio_length"],
|
| 3335 |
+
"num_frames": ["num_frames", "frames", "frame_count"],
|
| 3336 |
+
}
|
| 3337 |
+
return aliases.get(name, [name, name.replace("_", " "), name.replace("_", "-")])
|
| 3338 |
+
|
| 3339 |
+
|
| 3340 |
+
def _canonical_dict_value_for_param(raw_args: dict, param: dict, index: int, generated: list, expected_output_type: str) -> tuple[object, str]:
|
| 3341 |
+
semantic = _param_semantic_name(param, index, expected_output_type)
|
| 3342 |
+
raw_lower = {str(k).strip().lower().replace("-", "_").replace(" ", "_"): k for k in raw_args.keys()}
|
| 3343 |
+
for candidate in _canonical_key_aliases(semantic):
|
| 3344 |
+
key = raw_lower.get(candidate.replace("-", "_").replace(" ", "_"))
|
| 3345 |
+
if key is not None:
|
| 3346 |
+
return raw_args[key], f"semantic:{candidate}"
|
| 3347 |
+
name = str((param or {}).get("name") or f"arg{index}")
|
| 3348 |
+
for candidate in (name, name.replace("_", " "), name.replace("_", "-"), name.lower(), name.lower().replace("_", " "), name.lower().replace("_", "-")):
|
| 3349 |
+
if candidate in raw_args:
|
| 3350 |
+
return raw_args[candidate], f"schema_name:{candidate}"
|
| 3351 |
+
values = list(raw_args.values())
|
| 3352 |
+
if index < len(values) and re.fullmatch(r"param_\d+|arg\d+", str(name).strip().lower()):
|
| 3353 |
+
return values[index], "anonymous_positional_dict_order"
|
| 3354 |
+
return (generated[index] if index < len(generated) else smoke_value_for_parameter(param, expected_output_type)), "schema_generated_fallback"
|
| 3355 |
+
|
| 3356 |
+
|
| 3357 |
+
def detect_required_text_payload_resolution_issue(args: list, params: list[dict], canonical_example: dict | None = None) -> dict:
|
| 3358 |
+
"""Detect when ASF resolved a required text prompt to empty before predict()."""
|
| 3359 |
+
non_empty_canonical = []
|
| 3360 |
+
if isinstance(canonical_example, dict):
|
| 3361 |
+
raw_inputs, _ = _raw_inputs_from_canonical_smoke(canonical_example)
|
| 3362 |
+
if isinstance(raw_inputs, dict):
|
| 3363 |
+
for key, value in raw_inputs.items():
|
| 3364 |
+
if isinstance(value, str) and value.strip():
|
| 3365 |
+
non_empty_canonical.append({"key": str(key), "value_preview": value[:120]})
|
| 3366 |
+
elif isinstance(raw_inputs, list):
|
| 3367 |
+
for i, value in enumerate(raw_inputs):
|
| 3368 |
+
if isinstance(value, str) and value.strip():
|
| 3369 |
+
non_empty_canonical.append({"index": i, "value_preview": value[:120]})
|
| 3370 |
+
for index, param in enumerate(params or []):
|
| 3371 |
+
if index >= len(args or []):
|
| 3372 |
+
continue
|
| 3373 |
+
name = str((param or {}).get("name") or f"arg{index}").strip()
|
| 3374 |
+
semantic = _param_semantic_name(param, index)
|
| 3375 |
+
component = str((param or {}).get("component") or "").lower()
|
| 3376 |
+
required = bool((param or {}).get("required", False))
|
| 3377 |
+
value = args[index]
|
| 3378 |
+
is_text = any(token in component for token in ("textbox", "text", "str")) or semantic in {"prompt", "text", "query", "instruction"}
|
| 3379 |
+
if required and is_text and isinstance(value, str) and not value.strip() and non_empty_canonical:
|
| 3380 |
+
return {
|
| 3381 |
+
"schema_version": "smoke_payload_resolution_guard.v198_26_5",
|
| 3382 |
+
"detected": True,
|
| 3383 |
+
"failure_owner": "factory_validation_client",
|
| 3384 |
+
"failure_class": "smoke_payload_resolution_failed",
|
| 3385 |
+
"failure_type": "required_text_resolved_empty",
|
| 3386 |
+
"parameter_index": index,
|
| 3387 |
+
"parameter_name": name,
|
| 3388 |
+
"semantic_name": semantic,
|
| 3389 |
+
"canonical_non_empty_inputs": non_empty_canonical[:5],
|
| 3390 |
+
"recommended_action": "Fix canonical smoke dict-to-args resolution before calling gradio_client.predict; do not call the Space with an empty required prompt.",
|
| 3391 |
+
}
|
| 3392 |
+
return {"schema_version": "smoke_payload_resolution_guard.v198_26_5", "detected": False}
|
| 3393 |
+
|
| 3394 |
def _canonical_smoke_dict_from_contracts(inference_contract: dict | None, demo_quality_contract: dict | None) -> tuple[dict | None, str]:
|
| 3395 |
"""Return the preferred canonical smoke example and its source.
|
| 3396 |
|
|
|
|
| 3451 |
|
| 3452 |
if isinstance(raw_args, dict):
|
| 3453 |
args = []
|
| 3454 |
+
resolution = []
|
| 3455 |
for i, param in enumerate(params):
|
| 3456 |
+
value, source_key = _canonical_dict_value_for_param(raw_args, param, i, generated, expected_output_type)
|
| 3457 |
+
args.append(coerce_smoke_value(value, param))
|
| 3458 |
+
resolution.append({
|
| 3459 |
+
"index": i,
|
| 3460 |
+
"name": str(param.get("name") or f"arg{i}"),
|
| 3461 |
+
"semantic_name": _param_semantic_name(param, i, expected_output_type),
|
| 3462 |
+
"source": source_key,
|
| 3463 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3464 |
if not params:
|
| 3465 |
args = list(raw_args.values())
|
| 3466 |
return {
|
|
|
|
| 3471 |
"source": source,
|
| 3472 |
"canonical_smoke_example_present": True,
|
| 3473 |
"canonical_smoke_reason": smoke.get("reason") or smoke.get("why_representative") or smoke.get("description") or "",
|
| 3474 |
+
"dict_resolution": resolution,
|
| 3475 |
}
|
| 3476 |
|
| 3477 |
# Empty/unsupported inputs: canonical exists but cannot be transformed safely.
|
|
|
|
| 3531 |
|
| 3532 |
negative_statuses = {"unhealthy", "failed", "failure", "error", "not_ready", "not ready", "runtime_error"}
|
| 3533 |
positive_statuses = {"healthy", "ok", "ready", "success", "passed"}
|
| 3534 |
+
scaffold_markers = {"initial-scaffold", "initial_scaffold", "placeholder", "scaffold", "template"}
|
| 3535 |
for payload in _semantic_health_payloads(validation):
|
| 3536 |
for key in ("status", "health", "state"):
|
| 3537 |
if key in payload:
|
|
|
|
| 3542 |
elif value in positive_statuses and key != "state":
|
| 3543 |
details["semantic_checked"] = True
|
| 3544 |
details["positive_markers"].append(f"{key}={value}")
|
| 3545 |
+
for key in ("stage", "runtime_stage", "phase"):
|
| 3546 |
+
if key in payload:
|
| 3547 |
+
value = str(payload.get(key) or "").strip().lower()
|
| 3548 |
+
if value in scaffold_markers or any(marker in value for marker in ("initial-scaffold", "initial_scaffold", "placeholder")):
|
| 3549 |
+
details["semantic_checked"] = True
|
| 3550 |
+
details["negative_markers"].append(f"{key}={value}")
|
| 3551 |
for key in ("pipeline_ready", "pipeline_loaded", "model_ready", "model_loaded"):
|
| 3552 |
if key in payload:
|
| 3553 |
details["semantic_checked"] = True
|
|
|
|
| 3597 |
return ("cuda" in lowered or "gpu" in lowered or "outofmemory" in lowered) and any(marker in lowered for marker in markers)
|
| 3598 |
|
| 3599 |
|
| 3600 |
+
|
| 3601 |
+
|
| 3602 |
+
def is_placeholder_scaffold_text(text: str) -> bool:
|
| 3603 |
+
lowered = str(text or "").lower()
|
| 3604 |
+
markers = [
|
| 3605 |
+
"initial scaffold",
|
| 3606 |
+
"initial-scaffold",
|
| 3607 |
+
"initial_scaffold",
|
| 3608 |
+
"pi should replace this",
|
| 3609 |
+
"model-specific inference path",
|
| 3610 |
+
"placeholder demo",
|
| 3611 |
+
"placeholder scaffold",
|
| 3612 |
+
"stage: initial-scaffold",
|
| 3613 |
+
]
|
| 3614 |
+
return any(marker in lowered for marker in markers)
|
| 3615 |
+
|
| 3616 |
+
|
| 3617 |
+
def detect_placeholder_scaffold(workspace: Path) -> dict:
|
| 3618 |
+
"""Detect a final runtime that still contains ASF/Pi placeholder scaffold text."""
|
| 3619 |
+
app_path = workspace / "app.py"
|
| 3620 |
+
text = app_path.read_text(encoding="utf-8", errors="ignore") if app_path.exists() else ""
|
| 3621 |
+
detected = is_placeholder_scaffold_text(text)
|
| 3622 |
+
markers = []
|
| 3623 |
+
lowered = text.lower()
|
| 3624 |
+
for marker in ["initial scaffold", "initial-scaffold", "pi should replace this", "model-specific inference path", "placeholder scaffold"]:
|
| 3625 |
+
if marker in lowered:
|
| 3626 |
+
markers.append(marker)
|
| 3627 |
+
return {
|
| 3628 |
+
"schema_version": "placeholder_scaffold_detection.v198_26_5",
|
| 3629 |
+
"detected": bool(detected),
|
| 3630 |
+
"failure_owner": "pi_generation" if detected else "",
|
| 3631 |
+
"failure_class": "placeholder_scaffold_deployed" if detected else "",
|
| 3632 |
+
"failure_type": "model_specific_runtime_missing" if detected else "",
|
| 3633 |
+
"repair_candidate": bool(detected),
|
| 3634 |
+
"markers": markers,
|
| 3635 |
+
"checked_file": "app.py",
|
| 3636 |
+
"recommended_action": "Replace the initial scaffold with a model-specific inference app before treating the Space as healthy." if detected else "",
|
| 3637 |
+
}
|
| 3638 |
+
|
| 3639 |
def _extract_missing_executable(text: str) -> str:
|
| 3640 |
text = str(text or "")
|
| 3641 |
patterns = [
|
|
|
|
| 3689 |
marker = "no value provided for required argument:"
|
| 3690 |
missing_executable = _extract_missing_executable(text)
|
| 3691 |
|
| 3692 |
+
if "smoke_payload_resolution_failed" in lowered or "required_text_resolved_empty" in lowered or "prompt cannot be empty" in lowered:
|
| 3693 |
+
base.update({
|
| 3694 |
+
"failure_owner": "factory_validation_client",
|
| 3695 |
+
"failure_class": "smoke_payload_resolution_failed",
|
| 3696 |
+
"failure_type": "required_text_resolved_empty",
|
| 3697 |
+
"actionability": "factory_fix_required",
|
| 3698 |
+
"repair_candidate": False,
|
| 3699 |
+
"recommended_action": "Fix ASF canonical smoke payload resolution so required prompt/text inputs are non-empty before calling gradio_client; do not repair the generated Space.",
|
| 3700 |
+
})
|
| 3701 |
+
elif is_placeholder_scaffold_text(text):
|
| 3702 |
+
base.update({
|
| 3703 |
+
"failure_owner": "pi_generation",
|
| 3704 |
+
"failure_class": "placeholder_scaffold_deployed",
|
| 3705 |
+
"failure_type": "model_specific_runtime_missing",
|
| 3706 |
+
"actionability": "targeted_pi_repair",
|
| 3707 |
+
"repair_candidate": True,
|
| 3708 |
+
"recommended_action": "Run a targeted Pi repair to replace the initial scaffold with a model-specific inference app that returns the promised artifact.",
|
| 3709 |
+
})
|
| 3710 |
+
elif "asf smoke input materialization failed" in lowered or "does not exist on local filesystem" in lowered:
|
| 3711 |
base.update({
|
| 3712 |
"failure_owner": "factory_validation_client",
|
| 3713 |
"failure_class": "smoke_input_materialization_failed",
|
|
|
|
| 4131 |
timeout_s = smoke_timeout_seconds(expected_output_type)
|
| 4132 |
append_event(events_path, "generation_smoke", "started", "Calling live generation endpoint for ZeroGPU timing", {"api_name": api_name, "expected_output_type": expected_output_type, "smoke_timeout_seconds": timeout_s})
|
| 4133 |
client = make_gradio_client(target_space_id, token, timeout_s=timeout_s)
|
| 4134 |
+
identity = write_gradio_client_identity(client, target_space_id, run_dir, events_path, phase="generation_smoke")
|
| 4135 |
+
if identity.get("mismatch"):
|
| 4136 |
+
diagnosis = {"schema_version": "generation_smoke_diagnosis.v198_26_5", "smoke_passed": False, "phase": "space_identity", "expected_output_type": expected_output_type or "", "failure_owner": "factory_validation_client", "failure_class": "space_identity_mismatch", "failure_type": "space_identity_mismatch", "actionability": "factory_fix_required", "repair_candidate": False, "recommended_action": "Do not validate a Space whose gradio_client URL does not match the target_space_id; fix Space identity propagation before retrying.", "evidence": [json.dumps(identity, ensure_ascii=False)[:1200]]}
|
| 4137 |
+
payload = {"status": "failed", "target_space": target_space_id, "api_name": api_name, "expected_output_type": expected_output_type, "error": "space_identity_mismatch", "failure_type": "space_identity_mismatch", "failure_class": "space_identity_mismatch", "failure_owner": "factory_validation_client", "actionability": "factory_fix_required", "repair_candidate": False, "space_identity": identity, **measured_zero_gpu_recommendation(None)}
|
| 4138 |
+
write_json(run_dir / "tests" / "generation_smoke_diagnosis.json", diagnosis)
|
| 4139 |
+
record_generation_smoke_result(run_dir, payload, phase="generation_smoke")
|
| 4140 |
+
append_event(events_path, "generation_smoke", "failed", "Space identity mismatch before generation smoke", payload)
|
| 4141 |
+
return payload
|
| 4142 |
schema = client.view_api(return_format="dict")
|
| 4143 |
discovered = api_names_from_schema(schema)
|
| 4144 |
if api_name not in discovered and discovered:
|
|
|
|
| 4173 |
if contract_payload and contract_payload.get("canonical_smoke_example_present"):
|
| 4174 |
canonical_example, canonical_source = _canonical_smoke_dict_from_contracts(contract, demo_quality_contract)
|
| 4175 |
smoke_payload = {"api_name": api_name, "test_args": test_args, "raw_test_args": raw_test_args, "test_kwargs": test_kwargs, "parameters": smoke_parameters, "source": smoke_source, "contract_present": bool(contract), "demo_quality_contract_present": bool(demo_quality_contract), "canonical_smoke_example_present": bool(canonical_example), "canonical_smoke_example_source": canonical_source, "choice_corrections": initial_choice_changes, "file_input_conversions": file_input_conversions, "smoke_timeout_seconds": timeout_s}
|
| 4176 |
+
payload_resolution_guard = detect_required_text_payload_resolution_issue(test_args, smoke_parameters, canonical_example)
|
| 4177 |
+
smoke_payload["payload_resolution_guard"] = payload_resolution_guard
|
| 4178 |
if canonical_example:
|
| 4179 |
+
write_json(run_dir / "tests" / "canonical_smoke_example.json", {"source": canonical_source, "example": canonical_example, "resolved_api_name": api_name, "raw_resolved_args": raw_test_args, "resolved_args": test_args, "resolved_kwargs": test_kwargs, "expected_output_type": expected_output_type, "file_input_conversions": file_input_conversions, "dict_resolution": contract_payload.get("dict_resolution") if isinstance(contract_payload, dict) else [], "payload_resolution_guard": payload_resolution_guard})
|
| 4180 |
write_json(run_dir / "tests" / "generation_smoke_payload.json", smoke_payload)
|
| 4181 |
write_json(run_dir / "tests" / "payload_source.json", {"schema_version": "1.0", "validation_engine": "unified_gradio_validation_harness", "validation_mode": "automatic_smoke", "payload_source": smoke_source, "selected_api_name": api_name, "parent_smoke_payload_used": False, "canonical_smoke_example_used": bool(canonical_example), "canonical_smoke_example_source": canonical_source})
|
| 4182 |
write_json(run_dir / "tests" / "validation_engine.json", {"schema_version": "1.0", "validation_engine": "unified_gradio_validation_harness", "validation_mode": "automatic_smoke", "resolved_request_required_before_predict": True})
|
| 4183 |
+
write_json(run_dir / "tests" / "resolved_validation_request.json", {"api_name": api_name, "test_args": test_args, "raw_test_args": raw_test_args, "test_kwargs": test_kwargs, "expected_output_type": expected_output_type, "validation_mode": "automatic_smoke", "payload_source": smoke_source, "canonical_smoke_example_used": bool(canonical_example), "canonical_smoke_example_source": canonical_source, "schema_choice_corrections": initial_choice_changes, "file_input_conversions": file_input_conversions, "smoke_timeout_seconds": timeout_s, "payload_resolution_guard": payload_resolution_guard})
|
| 4184 |
+
write_json(run_dir / "tests" / "schema_coercion.json", {"api_name": api_name, "changes": initial_choice_changes, "original_args": raw_test_args, "resolved_args": test_args, "file_input_conversions": file_input_conversions, "payload_resolution_guard": payload_resolution_guard})
|
| 4185 |
write_json(run_dir / "tests" / "generation_api_schema.json", {"schema": schema, "api_names": discovered, "selected_api_name": api_name, "endpoint_parameters": endpoint_parameters, "smoke_parameters": smoke_parameters, "smoke_payload_source": smoke_source, "canonical_smoke_example_used": bool(canonical_example)})
|
| 4186 |
+
if payload_resolution_guard.get("detected"):
|
| 4187 |
+
diagnosis = {
|
| 4188 |
+
"schema_version": "generation_smoke_diagnosis.v198_26_5",
|
| 4189 |
+
"smoke_passed": False,
|
| 4190 |
+
"phase": "payload_resolution",
|
| 4191 |
+
"inference_strategy": str(contract.get("inference_strategy") or ""),
|
| 4192 |
+
"expected_output_type": expected_output_type or "",
|
| 4193 |
+
"failure_owner": "factory_validation_client",
|
| 4194 |
+
"failure_class": "smoke_payload_resolution_failed",
|
| 4195 |
+
"failure_type": "required_text_resolved_empty",
|
| 4196 |
+
"actionability": "factory_fix_required",
|
| 4197 |
+
"repair_candidate": False,
|
| 4198 |
+
"recommended_action": payload_resolution_guard.get("recommended_action"),
|
| 4199 |
+
"evidence": [json.dumps(payload_resolution_guard, ensure_ascii=False)[:1200]],
|
| 4200 |
+
}
|
| 4201 |
+
payload = {
|
| 4202 |
+
"status": "failed",
|
| 4203 |
+
"target_space": target_space_id,
|
| 4204 |
+
"api_name": api_name,
|
| 4205 |
+
"discovered_api_names": discovered,
|
| 4206 |
+
"endpoint_parameters": endpoint_parameters,
|
| 4207 |
+
"test_args": test_args,
|
| 4208 |
+
"test_kwargs": test_kwargs,
|
| 4209 |
+
"smoke_payload_source": smoke_source,
|
| 4210 |
+
"validation_level": "canonical_promise_smoke",
|
| 4211 |
+
"demo_usable_smoke_passed": False,
|
| 4212 |
+
"canonical_promise_smoke_passed": False,
|
| 4213 |
+
"full_promise_smoke_passed": False,
|
| 4214 |
+
"canonical_smoke_example_used": bool(canonical_example),
|
| 4215 |
+
"canonical_smoke_example_source": canonical_source,
|
| 4216 |
+
"payload_resolution_guard": payload_resolution_guard,
|
| 4217 |
+
"file_input_conversions": file_input_conversions,
|
| 4218 |
+
"smoke_timeout_seconds": timeout_s,
|
| 4219 |
+
"next_action": diagnosis["recommended_action"],
|
| 4220 |
+
"expected_output_type": expected_output_type,
|
| 4221 |
+
"latency_seconds": None,
|
| 4222 |
+
"result_info": {},
|
| 4223 |
+
"copied_artifacts": [],
|
| 4224 |
+
"validated_at": now(),
|
| 4225 |
+
"failure_type": diagnosis["failure_type"],
|
| 4226 |
+
"failure_class": diagnosis["failure_class"],
|
| 4227 |
+
"failure_owner": diagnosis["failure_owner"],
|
| 4228 |
+
"actionability": diagnosis["actionability"],
|
| 4229 |
+
"repair_candidate": False,
|
| 4230 |
+
**measured_zero_gpu_recommendation(None),
|
| 4231 |
+
}
|
| 4232 |
+
write_json(run_dir / "tests" / "generation_smoke_diagnosis.json", diagnosis)
|
| 4233 |
+
record_generation_smoke_result(run_dir, payload, phase="generation_smoke")
|
| 4234 |
+
write_live_status(run_dir, stage="generation_smoke", status="failed", message="Automatic smoke payload resolution failed before calling Gradio", data=payload)
|
| 4235 |
+
append_event(events_path, "generation_smoke", "failed", "Automatic smoke payload resolution failed before calling Gradio", payload)
|
| 4236 |
+
return payload
|
| 4237 |
write_live_status(run_dir, stage="generation_smoke", status="running", message="Calling live generation endpoint", data={"api_name": api_name, "source": smoke_source, "canonical_smoke_example_used": bool(canonical_example)})
|
| 4238 |
started = time.time()
|
| 4239 |
retry_info = {"attempts": 1, "choice_corrections": initial_choice_changes, "retried": False}
|
|
|
|
| 4281 |
retry_info = {"attempts": 2, "choice_corrections": initial_choice_changes, "file_input_conversions": file_input_conversions, "retried": True, "first_error": str(first_error)[:2000], "retry_reason": "canonical_cuda_oom_minimal_demo_smoke", "minimal_changes": minimal_changes}
|
| 4282 |
minimal_artifact = {"api_name": api_name, "test_args": minimal_args, "raw_test_args": minimal_raw_args, "test_kwargs": test_kwargs, "parameters": minimal_params, "validation_level": "minimal_demo_smoke", **retry_info}
|
| 4283 |
write_json(run_dir / "tests" / "generation_smoke_payload_minimal.json", minimal_artifact)
|
| 4284 |
+
write_json(run_dir / "tests" / "minimal_demo_smoke_retry.json", {"schema_version": "minimal_demo_smoke_retry.v198_26_5", "triggered": True, "failure_owner": "hardware", "failure_type": "canonical_cuda_oom", "canonical_error": str(first_error)[:2000], "minimal_changes": minimal_changes, "retry_payload_path": "tests/generation_smoke_payload_minimal.json"})
|
| 4285 |
try:
|
| 4286 |
result = client.predict(*minimal_args, api_name=api_name, **test_kwargs)
|
| 4287 |
raw_test_args = minimal_raw_args
|
|
|
|
| 4944 |
|
| 4945 |
def validate_gradio_api(target_space_id: str, token: str, run_dir: Path, events_path: Path, attempt: int):
|
| 4946 |
client = make_gradio_client(target_space_id, token)
|
| 4947 |
+
identity = write_gradio_client_identity(client, target_space_id, run_dir, events_path, phase="api_validation")
|
| 4948 |
+
if identity.get("mismatch"):
|
| 4949 |
+
raise RuntimeError(f"space_identity_mismatch: expected {target_space_id}, observed {identity.get('observed')}")
|
| 4950 |
schema = client.view_api(return_format="dict")
|
| 4951 |
write_json(run_dir / "tests" / "api_schema.json", schema if isinstance(schema, dict) else {"schema": str(schema)})
|
| 4952 |
discovered = api_names_from_schema(schema)
|
|
|
|
| 9568 |
if gen_dir.exists():
|
| 9569 |
shutil.rmtree(gen_dir)
|
| 9570 |
shutil.copytree(workspace, gen_dir, ignore=internal_workspace_copy_ignore)
|
| 9571 |
+
scaffold_detection = detect_placeholder_scaffold(workspace)
|
| 9572 |
+
write_json(run_dir / "placeholder_scaffold_detection.json", scaffold_detection)
|
| 9573 |
+
if scaffold_detection.get("detected"):
|
| 9574 |
+
append_event(events_path, "placeholder_scaffold", "warning", "Generated runtime still contains initial scaffold markers", scaffold_detection)
|
| 9575 |
payload_dir, manifest = build_runtime_upload_payload(workspace, run_dir, events_path)
|
| 9576 |
try:
|
| 9577 |
api.upload_folder(
|
|
|
|
| 9623 |
req_text = (workspace / "requirements.txt").read_text(encoding="utf-8", errors="ignore") if (workspace / "requirements.txt").exists() else ""
|
| 9624 |
blockers_path = workspace / "TECHNICAL_BLOCKERS.json"
|
| 9625 |
blockers = load_json_if_exists(blockers_path)
|
| 9626 |
+
scaffold_detection = detect_placeholder_scaffold(workspace)
|
| 9627 |
|
| 9628 |
combined = (app_text + "\n" + summary_text).lower()
|
| 9629 |
blocked_markers = [
|
|
|
|
| 9654 |
full_inference_requested = implementation_mode in {"full-inference-gated", "full-inference-attempt"}
|
| 9655 |
promise_fulfilled = bool(promise_validation.get("promise_fulfilled"))
|
| 9656 |
heuristic_marker_detected = any(m in combined for m in blocked_markers)
|
| 9657 |
+
scaffold_detected = bool(scaffold_detection.get("detected"))
|
| 9658 |
blocker_source = str(blockers.get("source") or "") if isinstance(blockers, dict) else ""
|
| 9659 |
heuristic_blocker_json = bool(blockers) and blocker_source == "worker_heuristic_from_PI_SUMMARY_or_app.py"
|
| 9660 |
heuristic_blocker_detected = bool(heuristic_marker_detected or heuristic_blocker_json)
|
| 9661 |
contract_blocker_detected = bool(contract_no_full or diagnostic_contract)
|
| 9662 |
+
hard_blocker_detected = bool((bool(blockers) and not heuristic_blocker_json) or contract_blocker_detected or scaffold_detected)
|
| 9663 |
blocker_detected = bool(hard_blocker_detected or heuristic_blocker_detected)
|
| 9664 |
strong_full_inference_success = bool(full_inference_requested and smoke_ok and promise_fulfilled)
|
| 9665 |
recommendation = generation_smoke if isinstance(generation_smoke, dict) else measured_zero_gpu_recommendation(None)
|
|
|
|
| 9675 |
"health_semantic_passed": health_semantics.get("semantic_passed"),
|
| 9676 |
"health_semantic_negative_markers": health_semantics.get("negative_markers") or [],
|
| 9677 |
"health_load_error": health_semantics.get("load_error") or "",
|
| 9678 |
+
"placeholder_scaffold_detected": scaffold_detected,
|
| 9679 |
"generation_smoke_passed": smoke_ok,
|
| 9680 |
"demo_usable_smoke_passed": minimal_smoke_ok,
|
| 9681 |
"canonical_promise_smoke_passed": smoke_ok,
|
|
|
|
| 9690 |
"source": "TECHNICAL_BLOCKERS.json" if heuristic_blocker_json else "PI_SUMMARY.md_or_app.py",
|
| 9691 |
"non_blocking": True,
|
| 9692 |
})
|
| 9693 |
+
if scaffold_detected:
|
| 9694 |
+
blocking_evidence.append({
|
| 9695 |
+
"type": "placeholder_scaffold_deployed",
|
| 9696 |
+
"source": "app.py",
|
| 9697 |
+
"overridable_by_live_smoke": True,
|
| 9698 |
+
"details": scaffold_detection,
|
| 9699 |
+
})
|
| 9700 |
if hard_blocker_detected:
|
| 9701 |
blocking_evidence.append({
|
| 9702 |
"type": "contract_or_explicit_blocker",
|
| 9703 |
+
"source": "INFERENCE_CONTRACT.json_or_TECHNICAL_BLOCKERS.json_or_app.py",
|
| 9704 |
"overridable_by_live_smoke": True,
|
| 9705 |
})
|
| 9706 |
|
|
|
|
| 9725 |
message = "Generation smoke was skipped by contract because full inference was declared unavailable; this is not a full inference success."
|
| 9726 |
else:
|
| 9727 |
message = "Space boots/responds as a diagnostic app, but real/full model inference is not implemented; this is not a full inference success."
|
| 9728 |
+
elif scaffold_detected:
|
| 9729 |
+
status = "placeholder_scaffold_deployed"
|
| 9730 |
+
message = "Space boots, but the published app still contains the initial scaffold instead of model-specific inference."
|
| 9731 |
elif hard_blocker_detected:
|
| 9732 |
status = "technical_blocker"
|
| 9733 |
message = "Space boots, but full model inference was not implemented. See TECHNICAL_BLOCKERS.json / PI_SUMMARY.md."
|
|
|
|
| 9825 |
"recommendation_confidence": recommendation.get("recommendation_confidence"),
|
| 9826 |
"measurement_note": recommendation.get("measurement_note"),
|
| 9827 |
},
|
| 9828 |
+
"placeholder_scaffold_detection": scaffold_detection,
|
| 9829 |
"blockers": blockers,
|
| 9830 |
}
|
| 9831 |
write_json(run_dir / "inference_gate.json", gate)
|
|
|
|
| 12404 |
# write_json(run_dir / "tests" / "payload_source.json", payload_source_record)
|
| 12405 |
# write_json(run_dir / "tests" / "replay_source.json", replay_source)
|
| 12406 |
# write_json(run_dir / "tests" / "validation_preflight.json", validation_preflight)
|
| 12407 |
+
app_version = "v198.26.5"
|
| 12408 |
engine_version = "unified_gradio_validation_harness_v198_25_3"
|
| 12409 |
parent_build_run_id = os.environ.get("PARENT_BUILD_RUN_ID", "").strip()
|
| 12410 |
validation_mode = os.environ.get("VALIDATION_MODE") or os.environ.get("SPACE_TEST_POLICY_MODE") or "linked"
|
|
|
|
| 12789 |
effective_status_on_success = os.environ.get("EFFECTIVE_STATUS_ON_SUCCESS") or "validated_after_space_test"
|
| 12790 |
space_test_policy_mode = os.environ.get("SPACE_TEST_POLICY_MODE") or "complete"
|
| 12791 |
manual_status = {
|
| 12792 |
+
"schema_version": "post_build_validation.v198_26_5",
|
| 12793 |
"status": "success",
|
| 12794 |
"effective_status": effective_status_on_success,
|
| 12795 |
"legacy_effective_status": "validated_after_manual_space_test",
|
|
|
|
| 12867 |
if parent_build_run_id:
|
| 12868 |
parent_dir = output_root / "runs" / parent_build_run_id
|
| 12869 |
parent_dir.mkdir(parents=True, exist_ok=True)
|
| 12870 |
+
failed_status = {"schema_version": "post_build_validation.v198_26_5", "status": terminal_status, "validation_run_id": run_id, "parent_build_run_id": parent_build_run_id, "target_space": target_space_id, "details": details, "updated_at": now(), "effective_status": "unchanged"}
|
| 12871 |
linked_path = parent_dir / "linked_validations.json"
|
| 12872 |
linked = read_json(linked_path, {"parent_build_run_id": parent_build_run_id, "validations": []}) or {"parent_build_run_id": parent_build_run_id, "validations": []}
|
| 12873 |
validations = linked.get("validations") if isinstance(linked, dict) else []
|