Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Upload 4 files
Browse files
README.md
CHANGED
|
@@ -133,15 +133,16 @@ See `CHANGELOG_V194.md` for the Model Pre-scan Decision Card UI pass.
|
|
| 133 |
See `CHANGELOG_V195.md` for the Runtime CSS Cleanup with Legacy Safety Net pass.
|
| 134 |
|
| 135 |
|
| 136 |
-
Current release: Agentic Space Factory v198.26.
|
| 137 |
|
| 138 |
-
## v198.26.
|
| 139 |
|
| 140 |
-
-
|
| 141 |
-
-
|
| 142 |
-
- Keeps linked
|
|
|
|
| 143 |
|
| 144 |
-
See `
|
| 145 |
|
| 146 |
## v198.26.118 — Runtime Risk Hints Advisory Layer
|
| 147 |
|
|
|
|
| 133 |
See `CHANGELOG_V195.md` for the Runtime CSS Cleanup with Legacy Safety Net pass.
|
| 134 |
|
| 135 |
|
| 136 |
+
Current release: Agentic Space Factory v198.26.121.
|
| 137 |
|
| 138 |
+
## v198.26.121 — Target Space Identity Override
|
| 139 |
|
| 140 |
+
- Adds an auditable Target Space override for Build Runs whose generated Space was renamed manually in Hugging Face settings.
|
| 141 |
+
- Preserves the original generated target Space while using the effective override for future Space Tests and quick links.
|
| 142 |
+
- Keeps validation linked to the selected Build Run: arbitrary target edits are still rejected unless the override was explicitly saved first.
|
| 143 |
+
- Excludes `TARGET_SPACE_OVERRIDE.json` from target Space release payloads while preserving it in the run bucket.
|
| 144 |
|
| 145 |
+
See `CHANGELOG_V198_26_121.md`.
|
| 146 |
|
| 147 |
## v198.26.118 — Runtime Risk Hints Advisory Layer
|
| 148 |
|
app.py
CHANGED
|
@@ -11,7 +11,13 @@ from fastapi import FastAPI, HTTPException, Request
|
|
| 11 |
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, RedirectResponse
|
| 12 |
from huggingface_hub import HfApi, attach_huggingface_oauth
|
| 13 |
|
| 14 |
-
from src.bucket import RunPaths, check_user_bucket, create_user_bucket, delete_run_folder, read_run_bundle, list_recent_runs, read_json, upsert_run_index_entry, write_json, write_launch_metadata
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
from src.config import settings, user_bucket_source
|
| 16 |
from src.eval_config import activate_eval_archive_config, disable_eval_archive_config, flush_eval_archive_records, public_eval_config
|
| 17 |
from src.eval_archive import maybe_publish_eval_record
|
|
@@ -328,10 +334,10 @@ def _target_space_from_events(events: list[dict[str, Any]] | None, *, require_su
|
|
| 328 |
def _resolve_target_space_identity(run_id: str | None, bundle: dict[str, Any], *, bucket_source: str | None, job_url: str | None = None) -> dict[str, Any]:
|
| 329 |
"""Return canonical Space links from all stable run sources.
|
| 330 |
|
| 331 |
-
v198.26.
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
"""
|
| 336 |
summary = bundle.get("summary") or bundle.get("summary_file") or {}
|
| 337 |
state = bundle.get("state") or {}
|
|
@@ -340,7 +346,7 @@ def _resolve_target_space_identity(run_id: str | None, bundle: dict[str, Any], *
|
|
| 340 |
explicit_url = _first_non_empty(
|
| 341 |
summary.get("target_space_url"), state.get("target_space_url"), launch.get("target_space_url")
|
| 342 |
)
|
| 343 |
-
|
| 344 |
_first_non_empty(
|
| 345 |
summary.get("target_space"), summary.get("target_space_id"),
|
| 346 |
state.get("target_space"), state.get("target_space_id"),
|
|
@@ -349,21 +355,20 @@ def _resolve_target_space_identity(run_id: str | None, bundle: dict[str, Any], *
|
|
| 349 |
_target_space_from_events(bundle.get("events") or []),
|
| 350 |
)
|
| 351 |
)
|
| 352 |
-
if explicit_url and not
|
| 353 |
-
|
| 354 |
-
|
|
|
|
|
|
|
| 355 |
target_space_settings_url = f"{target_space_url}/settings" if target_space_url else ""
|
| 356 |
events = bundle.get("events") or []
|
| 357 |
space_runtime = bundle.get("space_runtime") or {}
|
| 358 |
runtime_history = runtime_upload_epoch.get("history") if isinstance(runtime_upload_epoch.get("history"), list) else []
|
| 359 |
-
create_event_ok = bool(
|
| 360 |
upload_event_ok = bool(any(str((e or {}).get("step") or "") in {"upload_files", "runtime_upload_epoch"} and str((e or {}).get("status") or "").lower() == "success" for e in events if isinstance(e, dict)))
|
| 361 |
runtime_uploaded = bool(runtime_upload_epoch.get("last_upload_completed_at") or runtime_upload_epoch.get("upload_sequence") or runtime_history or upload_event_ok)
|
| 362 |
space_runtime_known = bool(isinstance(space_runtime, dict) and (space_runtime.get("stage") or space_runtime.get("status") or space_runtime.get("runtime_status") or space_runtime.get("updated_at") or space_runtime.get("url")))
|
| 363 |
-
|
| 364 |
-
# latest 100 events. A runtime upload epoch or a persisted runtime probe
|
| 365 |
-
# proves that the Space existed even if the final outcome later failed.
|
| 366 |
-
space_created = bool(target and (create_event_ok or runtime_uploaded or space_runtime_known))
|
| 367 |
sources = []
|
| 368 |
if create_event_ok:
|
| 369 |
sources.append("create_space_event")
|
|
@@ -375,14 +380,21 @@ def _resolve_target_space_identity(run_id: str | None, bundle: dict[str, Any], *
|
|
| 375 |
sources.append("space_runtime")
|
| 376 |
if explicit_url:
|
| 377 |
sources.append("explicit_target_space_url")
|
| 378 |
-
|
|
|
|
|
|
|
| 379 |
return {
|
| 380 |
-
"schema_version": "space_identity.
|
| 381 |
"target_space": target,
|
| 382 |
"target_space_id": target,
|
| 383 |
"target_space_known": bool(target),
|
| 384 |
"target_space_url": target_space_url,
|
| 385 |
"target_space_settings_url": target_space_settings_url,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
"space_created": bool(space_created),
|
| 387 |
"runtime_uploaded": bool(runtime_uploaded),
|
| 388 |
"space_uploaded": bool(runtime_uploaded),
|
|
@@ -406,6 +418,131 @@ def _api_links(*, run_id: str | None, bucket_source: str | None, target_space: s
|
|
| 406 |
}
|
| 407 |
|
| 408 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
def _job_id_from_run_bundle(summary: dict[str, Any], launch: dict[str, Any], state: dict[str, Any]) -> str:
|
| 410 |
return str(summary.get("job_id") or launch.get("job_id") or state.get("job_id") or "").strip()
|
| 411 |
|
|
@@ -600,13 +737,17 @@ def _build_live_run_snapshot(
|
|
| 600 |
view_bundle["eval_publish_status"] = eval_publish
|
| 601 |
space_identity = _resolve_target_space_identity(run_id, view_bundle, bucket_source=bucket_source, job_url=effective_state.get("job_url") or effective_summary.get("job_url"))
|
| 602 |
if space_identity.get("target_space"):
|
| 603 |
-
effective_state
|
| 604 |
-
effective_state
|
| 605 |
-
|
| 606 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
if space_identity.get("target_space_url"):
|
| 608 |
-
effective_state
|
| 609 |
-
effective_summary
|
| 610 |
view_bundle["state"] = effective_state
|
| 611 |
view_bundle["summary"] = effective_summary
|
| 612 |
view_bundle["space_identity"] = space_identity
|
|
@@ -1036,17 +1177,26 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
|
|
| 1036 |
parent_bundle = read_run_bundle(parent_build_run_id, bucket_source=bucket_status.get("bucket_source") or user_bucket_source(username=ctx["username"], bucket_name=bucket_name), token=ctx["token"], include_heavy=True)
|
| 1037 |
if _run_is_validation_like({"run_id": parent_build_run_id, **parent_bundle}):
|
| 1038 |
raise ValueError("Space Test must be linked to a Build Run, not to another validation run. Select the parent Build Run and retry validation.")
|
| 1039 |
-
|
| 1040 |
-
|
| 1041 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1042 |
if not parent_target:
|
| 1043 |
raise ValueError("Linked Space Test requires a parent Build Run with a generated target Space.")
|
| 1044 |
if requested_target != parent_target:
|
| 1045 |
-
raise ValueError("Space Test must remain linked to the selected Build Run target Space.
|
| 1046 |
parent_view = build_run_view_model(parent_build_run_id, parent_bundle, bucket_source=bucket_status.get("bucket_source") or user_bucket_source(username=ctx["username"], bucket_name=bucket_name))
|
| 1047 |
space_test_policy = parent_view.get("space_test_policy") or parent_view.get("space_test", {}).get("policy") or {}
|
| 1048 |
if not space_test_policy.get("enabled"):
|
| 1049 |
raise ValueError(str(space_test_policy.get("message") or "This Build Run is not eligible for linked Space Test yet."))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1050 |
payload_source_raw = str(payload.get("payload_source") or "").strip()
|
| 1051 |
manual_endpoint_override = bool(payload.get("endpoint_override")) or payload_source_raw == "endpoint_registry_manual_override"
|
| 1052 |
api_name_for_validation = str(payload.get("api_name") or "").strip()
|
|
@@ -1070,6 +1220,9 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
|
|
| 1070 |
api_name_for_validation = ""
|
| 1071 |
requested_validation_mode = space_test_policy.get("mode") or payload.get("validation_mode") or "complete"
|
| 1072 |
validation_mode_for_launch = "complete" if manual_endpoint_override and str(requested_validation_mode or "").strip().lower() == "replay" else requested_validation_mode
|
|
|
|
|
|
|
|
|
|
| 1073 |
# Compatibility anchor: "ui_payload_source": payload.get("payload_source") or ""
|
| 1074 |
validation_launch_payload = {
|
| 1075 |
"schema_version": "1.0",
|
|
@@ -1081,6 +1234,10 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
|
|
| 1081 |
"test_args": test_args,
|
| 1082 |
"test_kwargs": test_kwargs,
|
| 1083 |
"validation_mode": validation_mode_for_launch,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1084 |
"payload_source": (payload.get("payload_source") if manual_endpoint_override else "parent_automatic_smoke") if replay_source else (payload.get("payload_source") or "ui_payload"),
|
| 1085 |
"endpoint_override": bool(manual_endpoint_override),
|
| 1086 |
"endpoint_registry_source_run_id": payload.get("endpoint_registry_source_run_id") or "",
|
|
@@ -1089,6 +1246,7 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
|
|
| 1089 |
"space_test_policy": {
|
| 1090 |
"mode": space_test_policy.get("mode"),
|
| 1091 |
"launch_mode": validation_mode_for_launch,
|
|
|
|
| 1092 |
"requires_endpoint_discovery": bool(space_test_policy.get("requires_endpoint_discovery")),
|
| 1093 |
"effective_status_on_success": space_test_policy.get("effective_status_on_success"),
|
| 1094 |
},
|
|
@@ -1104,7 +1262,9 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
|
|
| 1104 |
expected_output_type=validation_launch_payload["expected_output_type"],
|
| 1105 |
live_timeout_seconds=payload.get("live_timeout_seconds") or 1800,
|
| 1106 |
validation_mode=validation_launch_payload["validation_mode"], # effectively: validation_mode=space_test_policy.get("mode")
|
| 1107 |
-
effective_status_on_success=space_test_policy.get("effective_status_on_success") or "validated_after_manual_space_test",
|
|
|
|
|
|
|
| 1108 |
parent_replay_source_json=json.dumps(replay_source, ensure_ascii=False) if replay_source and not manual_endpoint_override else None,
|
| 1109 |
validation_launch_payload_json=json.dumps(validation_launch_payload, ensure_ascii=False),
|
| 1110 |
run_id=payload.get("run_id"),
|
|
@@ -1133,6 +1293,51 @@ def register_custom_routes(fastapi_app: FastAPI) -> None:
|
|
| 1133 |
pass
|
| 1134 |
return JSONResponse(result)
|
| 1135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1136 |
@fastapi_app.post("/api/progress/from-events")
|
| 1137 |
async def api_progress_from_events(payload: dict[str, Any]): # type: ignore[no-untyped-def]
|
| 1138 |
events = payload.get("events") or []
|
|
|
|
| 11 |
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, RedirectResponse
|
| 12 |
from huggingface_hub import HfApi, attach_huggingface_oauth
|
| 13 |
|
| 14 |
+
from src.bucket import RunPaths, append_run_event, check_user_bucket, create_user_bucket, delete_run_folder, read_run_bundle, list_recent_runs, read_json, upsert_run_index_entry, write_json, write_launch_metadata
|
| 15 |
+
from src.target_space_identity import (
|
| 16 |
+
TARGET_SPACE_OVERRIDE_FILENAME,
|
| 17 |
+
normalize_target_space_id as normalize_effective_target_space_id,
|
| 18 |
+
target_space_identity_overlay,
|
| 19 |
+
target_space_url as effective_target_space_url,
|
| 20 |
+
)
|
| 21 |
from src.config import settings, user_bucket_source
|
| 22 |
from src.eval_config import activate_eval_archive_config, disable_eval_archive_config, flush_eval_archive_records, public_eval_config
|
| 23 |
from src.eval_archive import maybe_publish_eval_record
|
|
|
|
| 334 |
def _resolve_target_space_identity(run_id: str | None, bundle: dict[str, Any], *, bucket_source: str | None, job_url: str | None = None) -> dict[str, Any]:
|
| 335 |
"""Return canonical Space links from all stable run sources.
|
| 336 |
|
| 337 |
+
v198.26.121: apply an auditable Target Space identity override before
|
| 338 |
+
building UI links or validation prefill. The raw/generated target remains
|
| 339 |
+
available as target_space_original for audit; target_space is the effective
|
| 340 |
+
repo id to use for future validations and buttons.
|
| 341 |
"""
|
| 342 |
summary = bundle.get("summary") or bundle.get("summary_file") or {}
|
| 343 |
state = bundle.get("state") or {}
|
|
|
|
| 346 |
explicit_url = _first_non_empty(
|
| 347 |
summary.get("target_space_url"), state.get("target_space_url"), launch.get("target_space_url")
|
| 348 |
)
|
| 349 |
+
raw_target = _normalize_target_space_id(
|
| 350 |
_first_non_empty(
|
| 351 |
summary.get("target_space"), summary.get("target_space_id"),
|
| 352 |
state.get("target_space"), state.get("target_space_id"),
|
|
|
|
| 355 |
_target_space_from_events(bundle.get("events") or []),
|
| 356 |
)
|
| 357 |
)
|
| 358 |
+
if explicit_url and not raw_target:
|
| 359 |
+
raw_target = _normalize_target_space_id(explicit_url)
|
| 360 |
+
overlay = target_space_identity_overlay({**bundle, "summary": summary, "state": state, "launch": launch})
|
| 361 |
+
target = overlay.get("target_space") or raw_target
|
| 362 |
+
target_space_url = overlay.get("target_space_url") or (f"https://huggingface.co/spaces/{target}" if target else "")
|
| 363 |
target_space_settings_url = f"{target_space_url}/settings" if target_space_url else ""
|
| 364 |
events = bundle.get("events") or []
|
| 365 |
space_runtime = bundle.get("space_runtime") or {}
|
| 366 |
runtime_history = runtime_upload_epoch.get("history") if isinstance(runtime_upload_epoch.get("history"), list) else []
|
| 367 |
+
create_event_ok = bool(raw_target and any(str((e or {}).get("step") or "") in {"create_space", "create_space_hardware"} and str((e or {}).get("status") or "").lower() == "success" for e in events if isinstance(e, dict)))
|
| 368 |
upload_event_ok = bool(any(str((e or {}).get("step") or "") in {"upload_files", "runtime_upload_epoch"} and str((e or {}).get("status") or "").lower() == "success" for e in events if isinstance(e, dict)))
|
| 369 |
runtime_uploaded = bool(runtime_upload_epoch.get("last_upload_completed_at") or runtime_upload_epoch.get("upload_sequence") or runtime_history or upload_event_ok)
|
| 370 |
space_runtime_known = bool(isinstance(space_runtime, dict) and (space_runtime.get("stage") or space_runtime.get("status") or space_runtime.get("runtime_status") or space_runtime.get("updated_at") or space_runtime.get("url")))
|
| 371 |
+
space_created = bool(target and (create_event_ok or runtime_uploaded or space_runtime_known or overlay.get("target_space_override_active")))
|
|
|
|
|
|
|
|
|
|
| 372 |
sources = []
|
| 373 |
if create_event_ok:
|
| 374 |
sources.append("create_space_event")
|
|
|
|
| 380 |
sources.append("space_runtime")
|
| 381 |
if explicit_url:
|
| 382 |
sources.append("explicit_target_space_url")
|
| 383 |
+
if overlay.get("target_space_override_active"):
|
| 384 |
+
sources.insert(0, "target_space_override")
|
| 385 |
+
links_ready = bool(target_space_url and (space_created or runtime_uploaded or space_runtime_known or overlay.get("target_space_override_active")))
|
| 386 |
return {
|
| 387 |
+
"schema_version": "space_identity.v198_26_121",
|
| 388 |
"target_space": target,
|
| 389 |
"target_space_id": target,
|
| 390 |
"target_space_known": bool(target),
|
| 391 |
"target_space_url": target_space_url,
|
| 392 |
"target_space_settings_url": target_space_settings_url,
|
| 393 |
+
"target_space_original": overlay.get("target_space_original") or raw_target,
|
| 394 |
+
"target_space_original_url": overlay.get("target_space_original_url") or (f"https://huggingface.co/spaces/{raw_target}" if raw_target else ""),
|
| 395 |
+
"target_space_override_active": bool(overlay.get("target_space_override_active")),
|
| 396 |
+
"target_space_override": overlay.get("target_space_override") or {},
|
| 397 |
+
"target_space_source": overlay.get("target_space_source") or "generated_run",
|
| 398 |
"space_created": bool(space_created),
|
| 399 |
"runtime_uploaded": bool(runtime_uploaded),
|
| 400 |
"space_uploaded": bool(runtime_uploaded),
|
|
|
|
| 418 |
}
|
| 419 |
|
| 420 |
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
def _run_target_override_path(run_id: str, bucket_source: str) -> str:
|
| 424 |
+
return f"{RunPaths(run_id, bucket_source=bucket_source).root}/{TARGET_SPACE_OVERRIDE_FILENAME}"
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
def _target_space_override_response(run_id: str, bucket_source: str, bundle: dict[str, Any]) -> dict[str, Any]:
|
| 428 |
+
identity = _resolve_target_space_identity(run_id, bundle, bucket_source=bucket_source, job_url=(bundle.get("state") or {}).get("job_url") or (bundle.get("summary") or {}).get("job_url") or "")
|
| 429 |
+
return {
|
| 430 |
+
"run_id": run_id,
|
| 431 |
+
"bucket_source": bucket_source,
|
| 432 |
+
"target_space": identity.get("target_space") or "",
|
| 433 |
+
"target_space_id": identity.get("target_space") or "",
|
| 434 |
+
"target_space_url": identity.get("target_space_url") or "",
|
| 435 |
+
"target_space_settings_url": identity.get("target_space_settings_url") or "",
|
| 436 |
+
"target_space_original": identity.get("target_space_original") or "",
|
| 437 |
+
"target_space_original_url": identity.get("target_space_original_url") or "",
|
| 438 |
+
"target_space_override_active": bool(identity.get("target_space_override_active")),
|
| 439 |
+
"target_space_override": identity.get("target_space_override") or {},
|
| 440 |
+
"target_space_source": identity.get("target_space_source") or "generated_run",
|
| 441 |
+
"space_identity": identity,
|
| 442 |
+
"links": {**identity, "run_id": run_id, "bucket_source": bucket_source},
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
def _assert_override_repo_allowed(*, target_space: str, username: str, token: str | None) -> None:
|
| 447 |
+
owner = target_space.split("/", 1)[0] if "/" in target_space else ""
|
| 448 |
+
if username and owner and owner != username:
|
| 449 |
+
raise ValueError(f"Target Space override must stay in your namespace for now ({username}/...).")
|
| 450 |
+
try:
|
| 451 |
+
HfApi(token=token).repo_info(repo_id=target_space, repo_type="space", token=token)
|
| 452 |
+
except Exception as exc: # noqa: BLE001
|
| 453 |
+
raise ValueError(f"Could not verify target Space `{target_space}`. Make sure it exists and your OAuth token can access it. {redact(str(exc))}") from exc
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
def _write_target_space_override(
|
| 457 |
+
*,
|
| 458 |
+
run_id: str,
|
| 459 |
+
bucket_source: str,
|
| 460 |
+
username: str,
|
| 461 |
+
token: str | None,
|
| 462 |
+
target_space: str,
|
| 463 |
+
reason: str,
|
| 464 |
+
) -> dict[str, Any]:
|
| 465 |
+
bundle = read_run_bundle(run_id, bucket_source=bucket_source, token=token, include_heavy=False)
|
| 466 |
+
if _run_is_validation_like({"run_id": run_id, **bundle}):
|
| 467 |
+
raise ValueError("Target Space override can only be set on a Build Run, not on a validation run.")
|
| 468 |
+
original_identity = target_space_identity_overlay(bundle)
|
| 469 |
+
original = original_identity.get("target_space_original") or original_identity.get("target_space") or ""
|
| 470 |
+
payload = {
|
| 471 |
+
"schema_version": "target_space_override.v1",
|
| 472 |
+
"status": "active",
|
| 473 |
+
"parent_build_run_id": run_id,
|
| 474 |
+
"original_target_space_id": original,
|
| 475 |
+
"target_space_id": target_space,
|
| 476 |
+
"target_space": target_space,
|
| 477 |
+
"target_space_url": effective_target_space_url(target_space),
|
| 478 |
+
"target_space_settings_url": f"{effective_target_space_url(target_space)}/settings",
|
| 479 |
+
"reason": reason,
|
| 480 |
+
"set_by": username,
|
| 481 |
+
"verified_repo_exists": True,
|
| 482 |
+
"updated_at": utc_now_iso(),
|
| 483 |
+
}
|
| 484 |
+
write_json(_run_target_override_path(run_id, bucket_source), payload, token=token)
|
| 485 |
+
try:
|
| 486 |
+
append_run_event(
|
| 487 |
+
run_id,
|
| 488 |
+
bucket_source=bucket_source,
|
| 489 |
+
step="target_space_override",
|
| 490 |
+
status="success",
|
| 491 |
+
message="Target Space identity override saved",
|
| 492 |
+
details={"target_space": target_space, "original_target_space": original, "reason": reason},
|
| 493 |
+
token=token,
|
| 494 |
+
)
|
| 495 |
+
except Exception:
|
| 496 |
+
pass
|
| 497 |
+
refreshed = read_run_bundle(run_id, bucket_source=bucket_source, token=token, include_heavy=False)
|
| 498 |
+
response = _target_space_override_response(run_id, bucket_source, refreshed)
|
| 499 |
+
try:
|
| 500 |
+
summary = refreshed.get("summary") or {}
|
| 501 |
+
upsert_run_index_entry(run_id, bucket_source=bucket_source, summary={**summary, **response, "updated_at": utc_now_iso()}, token=token)
|
| 502 |
+
except Exception:
|
| 503 |
+
pass
|
| 504 |
+
return response
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
def _clear_target_space_override(*, run_id: str, bucket_source: str, username: str, token: str | None, reason: str = "") -> dict[str, Any]:
|
| 508 |
+
bundle = read_run_bundle(run_id, bucket_source=bucket_source, token=token, include_heavy=False)
|
| 509 |
+
if _run_is_validation_like({"run_id": run_id, **bundle}):
|
| 510 |
+
raise ValueError("Target Space override can only be cleared on a Build Run, not on a validation run.")
|
| 511 |
+
current = bundle.get("target_space_override") or {}
|
| 512 |
+
original_identity = target_space_identity_overlay(bundle)
|
| 513 |
+
payload = {
|
| 514 |
+
"schema_version": "target_space_override.v1",
|
| 515 |
+
"status": "cleared",
|
| 516 |
+
"parent_build_run_id": run_id,
|
| 517 |
+
"previous_target_space_id": current.get("target_space_id") or current.get("target_space") or "",
|
| 518 |
+
"original_target_space_id": original_identity.get("target_space_original") or "",
|
| 519 |
+
"reason": reason or "Target Space override cleared by user.",
|
| 520 |
+
"cleared_by": username,
|
| 521 |
+
"updated_at": utc_now_iso(),
|
| 522 |
+
}
|
| 523 |
+
write_json(_run_target_override_path(run_id, bucket_source), payload, token=token)
|
| 524 |
+
try:
|
| 525 |
+
append_run_event(
|
| 526 |
+
run_id,
|
| 527 |
+
bucket_source=bucket_source,
|
| 528 |
+
step="target_space_override",
|
| 529 |
+
status="cleared",
|
| 530 |
+
message="Target Space identity override cleared",
|
| 531 |
+
details={"previous_target_space": payload["previous_target_space_id"], "reason": payload["reason"]},
|
| 532 |
+
token=token,
|
| 533 |
+
)
|
| 534 |
+
except Exception:
|
| 535 |
+
pass
|
| 536 |
+
refreshed = read_run_bundle(run_id, bucket_source=bucket_source, token=token, include_heavy=False)
|
| 537 |
+
response = _target_space_override_response(run_id, bucket_source, refreshed)
|
| 538 |
+
try:
|
| 539 |
+
summary = refreshed.get("summary") or {}
|
| 540 |
+
upsert_run_index_entry(run_id, bucket_source=bucket_source, summary={**summary, **response, "updated_at": utc_now_iso()}, token=token)
|
| 541 |
+
except Exception:
|
| 542 |
+
pass
|
| 543 |
+
return response
|
| 544 |
+
|
| 545 |
+
|
| 546 |
def _job_id_from_run_bundle(summary: dict[str, Any], launch: dict[str, Any], state: dict[str, Any]) -> str:
|
| 547 |
return str(summary.get("job_id") or launch.get("job_id") or state.get("job_id") or "").strip()
|
| 548 |
|
|
|
|
| 737 |
view_bundle["eval_publish_status"] = eval_publish
|
| 738 |
space_identity = _resolve_target_space_identity(run_id, view_bundle, bucket_source=bucket_source, job_url=effective_state.get("job_url") or effective_summary.get("job_url"))
|
| 739 |
if space_identity.get("target_space"):
|
| 740 |
+
effective_state["target_space"] = space_identity["target_space"]
|
| 741 |
+
effective_state["target_space_id"] = space_identity["target_space"]
|
| 742 |
+
effective_state["target_space_original"] = space_identity.get("target_space_original") or effective_state.get("target_space_original") or ""
|
| 743 |
+
effective_state["target_space_override_active"] = bool(space_identity.get("target_space_override_active"))
|
| 744 |
+
effective_summary["target_space"] = space_identity["target_space"]
|
| 745 |
+
effective_summary["target_space_id"] = space_identity["target_space"]
|
| 746 |
+
effective_summary["target_space_original"] = space_identity.get("target_space_original") or effective_summary.get("target_space_original") or ""
|
| 747 |
+
effective_summary["target_space_override_active"] = bool(space_identity.get("target_space_override_active"))
|
| 748 |
if space_identity.get("target_space_url"):
|
| 749 |
+
effective_state["target_space_url"] = space_identity["target_space_url"]
|
| 750 |
+
effective_summary["target_space_url"] = space_identity["target_space_url"]
|
| 751 |
view_bundle["state"] = effective_state
|
| 752 |
view_bundle["summary"] = effective_summary
|
| 753 |
view_bundle["space_identity"] = space_identity
|
|
|
|
| 1177 |
parent_bundle = read_run_bundle(parent_build_run_id, bucket_source=bucket_status.get("bucket_source") or user_bucket_source(username=ctx["username"], bucket_name=bucket_name), token=ctx["token"], include_heavy=True)
|
| 1178 |
if _run_is_validation_like({"run_id": parent_build_run_id, **parent_bundle}):
|
| 1179 |
raise ValueError("Space Test must be linked to a Build Run, not to another validation run. Select the parent Build Run and retry validation.")
|
| 1180 |
+
parent_identity = _resolve_target_space_identity(
|
| 1181 |
+
parent_build_run_id,
|
| 1182 |
+
parent_bundle,
|
| 1183 |
+
bucket_source=bucket_status.get("bucket_source") or user_bucket_source(username=ctx["username"], bucket_name=bucket_name),
|
| 1184 |
+
job_url=(parent_bundle.get("state") or {}).get("job_url") or (parent_bundle.get("summary") or {}).get("job_url"),
|
| 1185 |
+
)
|
| 1186 |
+
parent_target = parent_identity.get("target_space") or ""
|
| 1187 |
+
requested_target = normalize_effective_target_space_id(payload.get("target_space_id") or "")
|
| 1188 |
if not parent_target:
|
| 1189 |
raise ValueError("Linked Space Test requires a parent Build Run with a generated target Space.")
|
| 1190 |
if requested_target != parent_target:
|
| 1191 |
+
raise ValueError("Space Test must remain linked to the selected Build Run effective target Space. Save a Target Space override first if the Space was renamed.")
|
| 1192 |
parent_view = build_run_view_model(parent_build_run_id, parent_bundle, bucket_source=bucket_status.get("bucket_source") or user_bucket_source(username=ctx["username"], bucket_name=bucket_name))
|
| 1193 |
space_test_policy = parent_view.get("space_test_policy") or parent_view.get("space_test", {}).get("policy") or {}
|
| 1194 |
if not space_test_policy.get("enabled"):
|
| 1195 |
raise ValueError(str(space_test_policy.get("message") or "This Build Run is not eligible for linked Space Test yet."))
|
| 1196 |
+
expert_intervention_required = bool(payload.get("expert_intervention_required"))
|
| 1197 |
+
expert_intervention_note = str(payload.get("expert_intervention_note") or "").strip()
|
| 1198 |
+
if expert_intervention_required and not expert_intervention_note:
|
| 1199 |
+
raise ValueError("Expert intervention acceptance requires a short note describing the manual repair before validation.")
|
| 1200 |
payload_source_raw = str(payload.get("payload_source") or "").strip()
|
| 1201 |
manual_endpoint_override = bool(payload.get("endpoint_override")) or payload_source_raw == "endpoint_registry_manual_override"
|
| 1202 |
api_name_for_validation = str(payload.get("api_name") or "").strip()
|
|
|
|
| 1220 |
api_name_for_validation = ""
|
| 1221 |
requested_validation_mode = space_test_policy.get("mode") or payload.get("validation_mode") or "complete"
|
| 1222 |
validation_mode_for_launch = "complete" if manual_endpoint_override and str(requested_validation_mode or "").strip().lower() == "replay" else requested_validation_mode
|
| 1223 |
+
base_validation_mode_for_launch = validation_mode_for_launch
|
| 1224 |
+
if expert_intervention_required:
|
| 1225 |
+
validation_mode_for_launch = "expert_acceptance"
|
| 1226 |
# Compatibility anchor: "ui_payload_source": payload.get("payload_source") or ""
|
| 1227 |
validation_launch_payload = {
|
| 1228 |
"schema_version": "1.0",
|
|
|
|
| 1234 |
"test_args": test_args,
|
| 1235 |
"test_kwargs": test_kwargs,
|
| 1236 |
"validation_mode": validation_mode_for_launch,
|
| 1237 |
+
"base_validation_mode": base_validation_mode_for_launch,
|
| 1238 |
+
"expert_intervention_required": expert_intervention_required,
|
| 1239 |
+
"expert_intervention_note": expert_intervention_note,
|
| 1240 |
+
"expert_acceptance_requires_validation": bool(expert_intervention_required),
|
| 1241 |
"payload_source": (payload.get("payload_source") if manual_endpoint_override else "parent_automatic_smoke") if replay_source else (payload.get("payload_source") or "ui_payload"),
|
| 1242 |
"endpoint_override": bool(manual_endpoint_override),
|
| 1243 |
"endpoint_registry_source_run_id": payload.get("endpoint_registry_source_run_id") or "",
|
|
|
|
| 1246 |
"space_test_policy": {
|
| 1247 |
"mode": space_test_policy.get("mode"),
|
| 1248 |
"launch_mode": validation_mode_for_launch,
|
| 1249 |
+
"base_launch_mode": base_validation_mode_for_launch,
|
| 1250 |
"requires_endpoint_discovery": bool(space_test_policy.get("requires_endpoint_discovery")),
|
| 1251 |
"effective_status_on_success": space_test_policy.get("effective_status_on_success"),
|
| 1252 |
},
|
|
|
|
| 1262 |
expected_output_type=validation_launch_payload["expected_output_type"],
|
| 1263 |
live_timeout_seconds=payload.get("live_timeout_seconds") or 1800,
|
| 1264 |
validation_mode=validation_launch_payload["validation_mode"], # effectively: validation_mode=space_test_policy.get("mode")
|
| 1265 |
+
effective_status_on_success="success_with_expert_intervention" if expert_intervention_required else (space_test_policy.get("effective_status_on_success") or "validated_after_manual_space_test"),
|
| 1266 |
+
expert_intervention_required=expert_intervention_required,
|
| 1267 |
+
expert_intervention_note=expert_intervention_note,
|
| 1268 |
parent_replay_source_json=json.dumps(replay_source, ensure_ascii=False) if replay_source and not manual_endpoint_override else None,
|
| 1269 |
validation_launch_payload_json=json.dumps(validation_launch_payload, ensure_ascii=False),
|
| 1270 |
run_id=payload.get("run_id"),
|
|
|
|
| 1293 |
pass
|
| 1294 |
return JSONResponse(result)
|
| 1295 |
|
| 1296 |
+
@fastapi_app.post("/api/runs/{run_id}/target-space-override")
|
| 1297 |
+
async def api_set_target_space_override(request: Request, run_id: str, payload: dict[str, Any]): # type: ignore[no-untyped-def]
|
| 1298 |
+
ctx = _oauth_context_from_request(request)
|
| 1299 |
+
bucket_name = payload.get("bucket_name") or settings.bucket_name
|
| 1300 |
+
bucket_source = user_bucket_source(username=ctx["username"], bucket_name=bucket_name)
|
| 1301 |
+
run_id = validate_run_id(run_id)
|
| 1302 |
+
target = normalize_effective_target_space_id(payload.get("target_space_id") or payload.get("target_space") or "")
|
| 1303 |
+
if not target:
|
| 1304 |
+
raise HTTPException(status_code=400, detail="Enter a valid Hugging Face Space repo ID like owner/space-name.")
|
| 1305 |
+
reason = str(payload.get("reason") or "").strip()
|
| 1306 |
+
if not reason:
|
| 1307 |
+
raise HTTPException(status_code=400, detail="Target Space override requires a short reason, for example: renamed manually in HF Space settings.")
|
| 1308 |
+
try:
|
| 1309 |
+
_assert_override_repo_allowed(target_space=target, username=ctx["username"], token=ctx["token"])
|
| 1310 |
+
result = _write_target_space_override(
|
| 1311 |
+
run_id=run_id,
|
| 1312 |
+
bucket_source=bucket_source,
|
| 1313 |
+
username=ctx["username"],
|
| 1314 |
+
token=ctx["token"],
|
| 1315 |
+
target_space=target,
|
| 1316 |
+
reason=reason,
|
| 1317 |
+
)
|
| 1318 |
+
return JSONResponse(result)
|
| 1319 |
+
except Exception as exc: # noqa: BLE001
|
| 1320 |
+
raise _json_error(exc) from exc
|
| 1321 |
+
|
| 1322 |
+
@fastapi_app.post("/api/runs/{run_id}/target-space-override/clear")
|
| 1323 |
+
async def api_clear_target_space_override(request: Request, run_id: str, payload: dict[str, Any] | None = None): # type: ignore[no-untyped-def]
|
| 1324 |
+
ctx = _oauth_context_from_request(request)
|
| 1325 |
+
payload = payload or {}
|
| 1326 |
+
bucket_name = payload.get("bucket_name") or settings.bucket_name
|
| 1327 |
+
bucket_source = user_bucket_source(username=ctx["username"], bucket_name=bucket_name)
|
| 1328 |
+
run_id = validate_run_id(run_id)
|
| 1329 |
+
try:
|
| 1330 |
+
result = _clear_target_space_override(
|
| 1331 |
+
run_id=run_id,
|
| 1332 |
+
bucket_source=bucket_source,
|
| 1333 |
+
username=ctx["username"],
|
| 1334 |
+
token=ctx["token"],
|
| 1335 |
+
reason=str(payload.get("reason") or "").strip(),
|
| 1336 |
+
)
|
| 1337 |
+
return JSONResponse(result)
|
| 1338 |
+
except Exception as exc: # noqa: BLE001
|
| 1339 |
+
raise _json_error(exc) from exc
|
| 1340 |
+
|
| 1341 |
@fastapi_app.post("/api/progress/from-events")
|
| 1342 |
async def api_progress_from_events(payload: dict[str, Any]): # type: ignore[no-untyped-def]
|
| 1343 |
events = payload.get("events") or []
|