Download app/space_checks.py from githubtailscaleduplicity342/RL-Explorer: direct link, hf CLI and curl.
- Browser
- Download file 17.8 kB
-
https://huggingface.co/spaces/githubtailscaleduplicity342/RL-Explorer/resolve/main/app/space_checks.py
- Command line
-
hf download hf://spaces/githubtailscaleduplicity342/RL-Explorer/app/space_checks.py
-
curl -L -o space_checks.py https://huggingface.co/spaces/githubtailscaleduplicity342/RL-Explorer/resolve/main/app/space_checks.py
17.8 kB
| """Recent, non-executing OpenEnv API evidence, separate from the immutable catalog. | |
| No wake, reset, step or tool invocation. Only public running Spaces are probed. | |
| Per-Space atomic files work on both local storage and the mounted bucket. Search | |
| reads a bounded cached inventory; it never waits for the network. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import logging | |
| import os | |
| import re | |
| import threading | |
| import time | |
| import tomllib | |
| from concurrent.futures import ThreadPoolExecutor | |
| import httpx | |
| from packaging.requirements import InvalidRequirement, Requirement | |
| from packaging.version import InvalidVersion, Version | |
| from . import catalog, config | |
| FRESH = 3600 | |
| REFRESH = 1800 | |
| PASS = "API checked" | |
| UNKNOWN = "Not checked" | |
| _cache = (None, 0.0, {}) | |
| _lock = threading.Lock() | |
| _write_lock = threading.Lock() | |
| _refreshing = {} | |
| _stop = threading.Event() | |
| _worker = None | |
| log = logging.getLogger("rlx") | |
| def _path(spec): | |
| return config.STORAGE_DIR / "openenv-checks" / f"{catalog._slug(catalog.check_spec(spec))}.json" | |
| def save(rec): | |
| global _cache | |
| path = _path(rec["id"]) | |
| # Bucket I/O must not hold the catalog's reader lock. | |
| with _write_lock: | |
| try: | |
| old = json.loads(path.read_bytes()) | |
| if old.get("checked_at", 0) > rec["checked_at"]: | |
| return | |
| except (OSError, ValueError): | |
| pass | |
| catalog.atomic_write(path, json.dumps(rec, separators=(",", ":")).encode()) | |
| with _lock: | |
| directory = path.parent | |
| records = dict(_cache[2]) if _cache[0] == directory else {} | |
| records[rec["id"]] = rec | |
| _cache = (directory, _cache[1] if _cache[0] == directory else 0.0, records) | |
| def _read_inventory(directory): | |
| """Public evidence only; invalid or oversized files are ignored, never trusted.""" | |
| records = {} | |
| for path in sorted(directory.glob("*.json"))[:20000]: | |
| try: | |
| if path.stat().st_size > 32 * 1024: | |
| continue | |
| r = json.loads(path.read_bytes()) | |
| spec = catalog.check_spec(r["id"]) | |
| if r.get("schema") == 1 and isinstance(r.get("checked_at"), (float, int)): | |
| records[spec] = r | |
| except (OSError, ValueError, KeyError, TypeError): | |
| continue | |
| return records | |
| def _refresh_inventory(directory, done): | |
| global _cache | |
| try: | |
| records = _read_inventory(directory) | |
| with _lock: | |
| if _cache[0] == directory: | |
| # A check may finish during the read. Its newer evidence, | |
| # including a failure superseding a pass, must win. | |
| for spec, rec in _cache[2].items(): | |
| if rec.get("checked_at", 0) > records.get(spec, {}).get("checked_at", 0): | |
| records[spec] = rec | |
| _cache = (directory, time.time(), records) | |
| except Exception: | |
| log.exception("Could not read stored OpenEnv checks") | |
| finally: | |
| with _lock: | |
| _refreshing.pop(directory, None) | |
| done.set() | |
| def inventory(*, wait=False): | |
| """Serve cached evidence immediately; refresh bucket files off the request path. | |
| Cold readers see no verified Spaces until the first read completes. Expiry | |
| still uses checked_at. Only the background checker waits for storage so it | |
| does not re-probe everything on boot. | |
| """ | |
| global _cache | |
| directory = config.STORAGE_DIR / "openenv-checks" | |
| with _lock: | |
| if _cache[0] != directory: | |
| _cache = (directory, 0.0, {}) | |
| if time.time() - _cache[1] < 60: | |
| return _cache[2] | |
| done = _refreshing.get(directory) | |
| if done is None: | |
| done = _refreshing[directory] = threading.Event() | |
| threading.Thread(target=_refresh_inventory, args=(directory, done), daemon=True, | |
| name="read-openenv-checks").start() | |
| records = _cache[2] | |
| if not wait: | |
| return records | |
| done.wait() | |
| with _lock: | |
| return _cache[2] | |
| def status(rec, now=None): | |
| if not rec: | |
| return UNKNOWN | |
| age = (time.time() if now is None else now) - rec.get("checked_at", 0) | |
| if age < 0 or age >= FRESH: | |
| return "Check expired" | |
| return rec.get("status") if rec.get("status") in (PASS, "Checks failed", "Not running", "Check unavailable") else UNKNOWN | |
| def verified(rec): | |
| """Only a fresh successful check of a running server establishes API support.""" | |
| return bool(rec and rec.get("stage") == "RUNNING" and status(rec) == PASS) | |
| def browseable(rec): | |
| """A live, recognized environment API; this does not certify reward quality.""" | |
| return verified(rec) or bool(rec and rec.get("stage") == "RUNNING" | |
| and status(rec) == "Checks failed" | |
| and rec.get("interface") in ("nemo-gym", "ors")) | |
| def environment_interface(api): | |
| """Other supported server contracts, kept distinct from OpenEnv evidence.""" | |
| if not isinstance(api, dict) or not isinstance(api.get("info"), dict) or not api["info"].get("version"): | |
| return None | |
| paths = api.get("paths") | |
| if not isinstance(paths, dict): | |
| return None | |
| for name, routes in (("nemo-gym", ("/seed_session", "/verify")), ("ors", ("/create_session", "/{env_name}/call"))): | |
| if all(isinstance(paths.get(p), dict) and isinstance(paths[p].get("post"), dict) for p in routes): | |
| return name | |
| return None | |
| def tag_version(tags): | |
| for tag in tags or []: | |
| match = re.fullmatch(r"openenv-(\d[^\s]{0,70})", str(tag), re.I) | |
| if match: | |
| try: | |
| return {"value": str(Version(match[1])), "source": "Hub tag"} | |
| except InvalidVersion: | |
| pass | |
| return {"value": "Unknown", "source": "Unknown"} | |
| def requirement_version(text): | |
| try: | |
| req = Requirement(text) | |
| except InvalidRequirement: | |
| return None | |
| if req.name.lower().replace("_", "-") not in ("openenv", "openenv-core"): | |
| return None # current OpenEnv and the earlier openenv-core distribution | |
| if req.marker: | |
| return {"value": "Conditional", "source": "Dependency constraint", "requirement": str(req)[:240]} | |
| if req.url: | |
| ref = re.search(r"@([^/#?\s]+)(?:#.*)?$", req.url) | |
| return {"value": "git:" + ref[1][:64] if ref else "Unpinned source", "source": "Source reference"} | |
| specs = list(req.specifier) | |
| if len(specs) == 1 and specs[0].operator in ("==", "===") and "*" not in specs[0].version: | |
| try: | |
| return {"value": str(Version(specs[0].version)), "source": "Dependency pin"} | |
| except InvalidVersion: | |
| pass | |
| return {"value": str(req.specifier) or "Unpinned", "source": "Dependency constraint"} | |
| def parse_version(text, path): | |
| """Repository evidence, never an assertion about the installed runtime.""" | |
| if path.endswith(".toml") or path.endswith("uv.lock"): | |
| try: | |
| doc = tomllib.loads(text) | |
| except (ValueError, RecursionError): | |
| return None | |
| if path.endswith("uv.lock"): | |
| pins = [] | |
| for pkg in doc.get("package", []): | |
| if not isinstance(pkg, dict) or pkg.get("name", "").replace("_", "-") not in ("openenv", "openenv-core"): | |
| continue | |
| source = pkg.get("source") or {} | |
| if source.get("git"): | |
| ref = source["git"].rsplit("#", 1) | |
| pins.append({"value": "git:" + ref[-1][:64] if len(ref) == 2 else "Unpinned source", "source": "Lockfile"}) | |
| elif pkg.get("version"): | |
| pins.append(requirement_version(f"openenv-core=={pkg['version']}")) | |
| pins = [p for p in pins if p] | |
| if len({p["value"] for p in pins}) == 1: | |
| return {**pins[0], "source": "Lockfile"} | |
| return {"value": "Multiple locked versions", "source": "Lockfile"} if pins else None | |
| sources = doc.get("tool", {}).get("uv", {}).get("sources", {}) | |
| source = sources.get("openenv") or sources.get("openenv-core") or sources.get("openenv_core") | |
| if isinstance(source, dict) and source.get("git"): | |
| ref = source.get("rev") or source.get("tag") or source.get("branch") | |
| return {"value": "git:" + str(ref)[:64] if ref else "Unpinned source", "source": "Source reference"} | |
| if source: | |
| return {"value": "Custom source", "source": "Source reference"} | |
| deps = doc.get("project", {}).get("dependencies", []) | |
| else: | |
| deps = [line.split(" #", 1)[0].strip() for line in text.splitlines()] | |
| for dep in deps: | |
| if isinstance(dep, str) and (v := requirement_version(dep)): | |
| return v | |
| return None | |
| def declared_version(spec, tags): | |
| from . import space_files as files | |
| fallback = tag_version(tags) | |
| try: | |
| info = files._info(spec) | |
| listing, _, _ = files.listing(info) | |
| paths = [f["path"] for f in listing] | |
| manifest = files.manifest_path(paths, "Dockerfile" if "Dockerfile" in paths else None, spec) | |
| root = manifest.rsplit("/", 1)[0] if manifest and "/" in manifest else "" | |
| keys = files.key_files(paths, root, manifest) | |
| sizes = {f["path"]: f.get("size", 0) for f in listing} | |
| reqs = [f"{root}/requirements.txt" if root else "requirements.txt", "requirements.txt"] | |
| for path in dict.fromkeys([keys.get("lock"), keys.get("pyproject"), *reqs]): | |
| if not path or path not in sizes or sizes[path] > 400 * 1024: | |
| continue | |
| text = files._fetch_text(spec, info["sha"], path, sizes[path]) | |
| if text and (v := parse_version(text, path)): | |
| return {**v, "path": path, "revision": info["sha"]} | |
| except Exception: # incomplete version evidence must not change API health | |
| pass | |
| return fallback | |
| def assess(api, health, metadata, schema, tools): | |
| api = api if isinstance(api, dict) else {} | |
| paths = api.get("paths") if isinstance(api.get("paths"), dict) else {} | |
| def method(path, verb): | |
| return isinstance(paths.get(path), dict) and isinstance(paths[path].get(verb), dict) | |
| checks = { | |
| "OpenAPI": isinstance(api.get("info"), dict) and isinstance(api["info"].get("version"), str), | |
| "Health": isinstance(health, dict) and health.get("status") == "healthy", | |
| "Metadata": isinstance(metadata, dict) and all(isinstance(metadata.get(k), str) for k in ("name", "description")), | |
| "Schemas": isinstance(schema, dict) and all(isinstance(schema.get(k), dict) for k in ("action", "observation", "state")), | |
| } | |
| simulation = any(p in paths for p in ("/reset", "/step", "/state")) | |
| mode = "Simulation" if simulation else "MCP service" | |
| checks["Control routes" if simulation else "MCP tools"] = ( | |
| all(method(p, v) for p, v in (("/reset", "post"), ("/step", "post"), ("/state", "get"))) | |
| if simulation else method("/mcp", "post") and bool(tools)) | |
| return {"status": PASS if all(checks.values()) else "Checks failed", "mode": mode if all(checks.values()) else None, | |
| "failed": [k for k, ok in checks.items() if not ok], "tools": len(tools or [])} | |
| def _task_splits(task_api): | |
| from .seo_tasks import ranges | |
| out = [] | |
| for row in ranges(task_api): | |
| if len(json.dumps(out + [row]).encode()) > 12000: | |
| break | |
| out.append(row) | |
| return out | |
| def check(spec): | |
| from . import spaces_live as live | |
| rec = {"schema": 1, "id": catalog.check_spec(spec), "checked_at": time.time(), "status": "Check unavailable"} | |
| previous = inventory().get(spec, {}) | |
| rec["version"] = previous.get("version") or {"value": "Unknown", "source": "Unknown"} | |
| try: | |
| hub = live.record(spec, fresh=True) | |
| rec["stage"] = hub["stage"] | |
| if hub["stage"] != "RUNNING": | |
| rec["status"] = "Not running" | |
| elif hub.get("host"): | |
| urls = ("/openapi.json", "/health", "/metadata", "/schema") | |
| def get(path): | |
| try: | |
| with live.SpaceClient(timeout=httpx.Timeout(6, connect=4), follow_redirects=False) as client: | |
| response = client.get(live._url(hub, path)) | |
| return response.json() if response.status_code == 200 and "json" in response.headers.get("content-type", "") else None | |
| except (httpx.HTTPError, ValueError, live.SpaceError): | |
| return None | |
| with ThreadPoolExecutor(4) as pool: | |
| api, health, meta, schema = list(pool.map(get, urls)) | |
| # MCP discovery is separate from typed-action OpenEnv compatibility. | |
| paths = api.get("paths", {}) if isinstance(api, dict) else {} | |
| typed = isinstance(schema, dict) and all(isinstance(schema.get(k), dict) for k in ("action", "observation", "state")) | |
| tools, _, _ = live._tools({**hub, "mcp_standard": not typed}) if isinstance(paths, dict) and "/mcp" in paths else (None, None, None) | |
| rec.update(assess(api, health, meta, schema, tools)) | |
| rec["interface"] = environment_interface(api) | |
| from . import space_tasks | |
| task_api = space_tasks.discover(hub, paths) | |
| rec["task_catalog"] = space_tasks.summary(task_api) | |
| rec["task_splits"] = _task_splits(task_api) | |
| if rec["status"] == PASS: | |
| rec["version"] = declared_version(spec, hub.get("tags")) | |
| elif rec["version"]["value"] == "Unknown": | |
| rec["version"] = tag_version(hub.get("tags")) | |
| except Exception as exc: | |
| rec["error"] = type(exc).__name__ # no upstream response/secret in public inventory | |
| save(rec) | |
| return rec | |
| def observe(spec, info): | |
| """Opening/rechecking a Space also updates discovery, including negative results.""" | |
| try: | |
| old = inventory().get(spec, {}) | |
| rec = {"schema": 1, "id": spec, "checked_at": info.get("checked") or time.time(), "stage": info.get("stage"), | |
| "version": old.get("version") or {"value": "Unknown", "source": "Unknown"}} | |
| if not info.get("running"): | |
| rec["status"] = "Not running" | |
| else: | |
| rec.update(assess({"info": {"version": info.get("openapi_version")}, "paths": info.get("endpoint_methods")}, | |
| info.get("health"), info.get("metadata"), info.get("schema"), info.get("mcp"))) | |
| rec["interface"] = environment_interface({"info": {"version": info.get("openapi_version")}, "paths": info.get("endpoint_methods")}) | |
| from . import space_tasks | |
| rec["task_catalog"] = space_tasks.summary(info.get("task_api")) | |
| rec["task_splits"] = _task_splits(info.get("task_api")) | |
| info["openenv_verified"] = verified(rec) | |
| save(rec) | |
| except Exception: | |
| log.warning("Could not save OpenEnv check for %s", spec) | |
| def scan(): | |
| # App workers and an operator's CLI refresh share one bounded probe queue. | |
| # Use local cache storage for this lock, never a mounted object-store bucket. | |
| try: | |
| import fcntl | |
| except ImportError: # Windows development: the app still has one worker thread | |
| return _scan() | |
| config.CACHE_DIR.mkdir(parents=True, exist_ok=True) | |
| with (config.CACHE_DIR / "openenv-checks.lock").open("a") as lease: | |
| try: | |
| fcntl.flock(lease, fcntl.LOCK_EX | fcntl.LOCK_NB) | |
| except BlockingIOError: | |
| return [] | |
| return _scan() | |
| def _scan(): | |
| from . import snapshot | |
| old = inventory(wait=True) | |
| with snapshot.use() as (_, conn): | |
| candidates = list(conn.execute("SELECT id, stage FROM envs WHERE kind = 'space' ORDER BY trending DESC, likes DESC")) | |
| due = pending(candidates, old) | |
| def visit(spec): | |
| if not _stop.is_set(): | |
| return check(spec) | |
| with ThreadPoolExecutor(4) as pool: | |
| return [r for r in pool.map(visit, due) if r] | |
| def pending(candidates, old, now=None): | |
| """Bound the queue and reserve capacity for inactive candidates too. | |
| Never probing sleeping hosts, but refreshing their Hub metadata lets a | |
| newly started Space enter the catalog without someone opening its page. | |
| """ | |
| now = time.time() if now is None else now | |
| active, inactive = [], [] | |
| for spec, stage in candidates: | |
| rec = old.get(spec, {}) | |
| running = rec.get("stage", stage) == "RUNNING" | |
| at = rec.get("checked_at", 0) | |
| interval = REFRESH if running else 6 * 3600 | |
| if not rec or now - at >= interval or (running and status(rec, now) == PASS and "task_catalog" not in rec): | |
| (active if running else inactive).append((spec, at)) | |
| # Once checked, older entries take precedence so a failing popular Space | |
| # cannot starve the long tail. FineEnvs goes first within each generation. | |
| key = lambda item: (int(item[1] // REFRESH), item[0].split("/", 1)[0].lower() != "fineenvs", item[1]) | |
| active.sort(key=key) | |
| inactive.sort(key=key) | |
| return [s for s, _ in active[:96] + inactive[:32]] | |
| def start(): | |
| global _worker | |
| if os.environ.get("RLX_TEST_TMP") or os.environ.get("RLX_SPACE_CHECKS", "1") != "1" or (_worker and _worker.is_alive()): | |
| return | |
| _stop.clear() | |
| def watch(): | |
| while not _stop.is_set(): | |
| try: | |
| scan() | |
| except Exception: | |
| log.exception("OpenEnv API inventory refresh failed") | |
| _stop.wait(60) | |
| _worker = threading.Thread(target=watch, daemon=True, name="openenv-checks") | |
| _worker.start() | |
| def stop(): | |
| _stop.set() | |
| if __name__ == "__main__": | |
| from collections import Counter | |
| from . import runtime | |
| runtime.hub_timeouts() | |
| print(dict(Counter(r["status"] for r in scan())), flush=True) | |