"""core/releases.py — what each deployed environment is RUNNING (wave 20, owner item 12 / R6). ⛔ **THIS MODULE EXISTS BECAUSE OF `ops/verify_portability.py` B2**, and the reason is worth stating rather than inferring: the API process must stay HOST-AGNOSTIC. The releases panel was first written with `HfApi`/`hf_hub_download` inside `routes_platform_admin.py`, and the gate turned red on the first run — correctly. A hub client in a request handler couples the runtime to HuggingFace, in the one process whose design promise is that moving hosts is a config change. So the route DELEGATES here, exactly as `routes_assets` delegates to `core/assets.py` (the same rule, the same shape, and that module's own note says so). The SDK import is LAZY and every path is gated on a token being configured, so a deployment with no hub credential simply reports nothing rather than failing to import. ⚠ **WHAT THIS READS IS EACH SPACE'S OWN CLAIM ABOUT ITSELF.** `VERSION` is written INTO the Space by `deploy_web.py` at deploy time, so "what is LIVE running" is a lookup, not an inference from timestamps — `last_modified` moves when a SECRET is pushed, which would report a deploy that never happened. An unreachable environment yields a NOTE, never a blank: about a production environment, "no version" reads as "nothing is deployed", which is the worst available way to be wrong. """ import json import os def _token(): """The credential that can read the Spaces. `AIOS_HF_TOKEN` is the personal (fsanyoto) token that owns the staging Space; `HF_TOKEN` is the org-scoped one. Either can read.""" return os.environ.get('AIOS_HF_TOKEN') or os.environ.get('HF_TOKEN') or None #: The two environments, by Space id. ⚠ These are REPO IDENTIFIERS, not host literals — no URL is #: built anywhere in this module (portability C1). Mirrors `deploy_web.STAGING`/`LIVE`, which #: cannot be imported here: it lives outside the container's tree. ENVIRONMENTS = (('staging', 'fsanyoto/loopable'), ('live', 'royal-imports/cfo-os')) def environments(): """[{env, space, version, note, stage}] — one row per deployed environment. Never raises: every failure becomes a `note` on its own row. A panel that 500s because one Space was briefly unreachable tells the operator less than a row saying so. """ tok = _token() out = [] for name, repo in ENVIRONMENTS: row = {'env': name, 'space': repo, 'version': None, 'note': None, 'stage': None} if not tok: row['note'] = ('no hub credential is configured on this deployment, so other ' 'environments cannot be read from here') out.append(row) continue try: from huggingface_hub import hf_hub_download # noqa: PLC0415 — lazy, see header p = hf_hub_download(repo, 'VERSION', repo_type='space', token=tok) with open(p, encoding='utf-8') as f: row['version'] = f.read().strip().splitlines()[0] except Exception as e: # noqa: BLE001 row['note'] = f'could not read this Space\'s VERSION ({type(e).__name__})' try: from huggingface_hub import HfApi # noqa: PLC0415 row['stage'] = str(HfApi(token=tok).space_info(repo).runtime.stage) except Exception: # noqa: BLE001 pass out.append(row) return out def history(): """Every `vN` ever cut: [{version, sha, date, subject}], newest first. Read from `RELEASES.json`, which `deploy_web.py` ships INTO the Space beside `VERSION` — the history has to travel with the build because the container has no git checkout to derive it from. Tries the local filesystem FIRST (this deployment's own copy, no network at all) and only then asks the hub, so the common case costs nothing. """ from pathlib import Path for p in (Path('RELEASES.json'), Path(__file__).resolve().parents[2] / 'RELEASES.json'): try: if p.is_file(): return (json.loads(p.read_text(encoding='utf-8')) or {}).get('releases') or [] except Exception: # noqa: BLE001 pass tok = _token() if not tok: return [] for _name, repo in ENVIRONMENTS: try: from huggingface_hub import hf_hub_download # noqa: PLC0415 q = hf_hub_download(repo, 'RELEASES.json', repo_type='space', token=tok) with open(q, encoding='utf-8') as f: return (json.load(f) or {}).get('releases') or [] except Exception: # noqa: BLE001 continue return []