fffiloni commited on
Commit
574e1f0
·
verified ·
1 Parent(s): 94f18b8

Upload 15 files

Browse files
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.v198_26_3",
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.3"
4
- ASF_RELEASE_NAME = "Agentic Space Factory v198.26.3"
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
- return _first_nonempty(
 
 
 
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
- explicit = _first_nonempty(summary.get("target_space_url"), state.get("target_space_url"), launch.get("target_space_url"))
 
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": summary.get("target_space_url") or "",
608
- "target_space_settings_url": f"{summary.get('target_space_url')}/settings" if summary.get("target_space_url") else "",
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": summary.get("target_space_url") or "",
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(summary.get("target_space_url")),
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.3"
39
- app_version = "v198.26.3"
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.3 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,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.3 introduces a second validation level: a minimal demo smoke can
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.v198_26_3"
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
- name = str(param.get("name") or f"arg{i}")
3287
- candidates = [
3288
- name,
3289
- name.replace("_", " "),
3290
- name.replace("_", "-"),
3291
- name.lower(),
3292
- name.lower().replace("_", " "),
3293
- name.lower().replace("_", "-"),
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 "asf smoke input materialization failed" in lowered or "does not exist on local filesystem" in lowered:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.v198_26_3", "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"})
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.json",
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.3"
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.v198_26_3",
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.v198_26_3", "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"}
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 []