AdithyaSK HF Staff commited on
Commit
1c2e6db
·
verified ·
1 Parent(s): 3ceafe2

Deploy HF RL Explorer

Browse files
.gitignore CHANGED
@@ -3,3 +3,12 @@ __pycache__/
3
  .venv/
4
  .gradio/
5
  .ruff_cache/
 
 
 
 
 
 
 
 
 
 
3
  .venv/
4
  .gradio/
5
  .ruff_cache/
6
+
7
+ # Admin implementation is maintained outside this public repository.
8
+ web-admin/
9
+ app/admin.py
10
+ app/admin_app.py
11
+ app/admin_access.py
12
+ tests/test_admin*.py
13
+ ADMIN_SPACE_README.md
14
+ EXPLORER_AUDIT.md
DESIGN.md CHANGED
@@ -235,3 +235,10 @@ HF scheduled Job, hourly (scripts/schedule_indexer.py) Explorer / admin
235
  5. **Smoke tests on the Space**: `/readyz`; `scripts/check_judge_relay.py`'s path through the Space's `/capture`
236
  (a model-graded rollout, e.g. a MiMo General twin with Inkling as judge: reward recorded, `judge_calls > 0`);
237
  `node tests/ui-audit.mjs https://<space>`.
 
 
 
 
 
 
 
 
235
  5. **Smoke tests on the Space**: `/readyz`; `scripts/check_judge_relay.py`'s path through the Space's `/capture`
236
  (a model-graded rollout, e.g. a MiMo General twin with Inkling as judge: reward recorded, `judge_calls > 0`);
237
  `node tests/ui-audit.mjs https://<space>`.
238
+
239
+ ## Admin source boundary
240
+
241
+ This public tooling directory contains only the Explorer. The dashboard application,
242
+ its assets, membership checks, tests and deployment tooling are maintained separately
243
+ in the private `FineEnvs/RL-Explorer-admin` Space. The Explorer reads shared settings
244
+ and moderation requests from the data bucket without importing the admin app.
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- # HF RL Explorer as a Docker Space; the same image runs its admin Space with RLX_APP=app.admin_app:app.
2
  # /data the private bucket FineEnvs/rl-explorer-data: indexes, packs, rollouts, admin settings; mounted by both
3
  # /tmp/... dataset files read while indexing and the tasks rollouts run on: many small files, local disk
4
  # abcmidi the MiMo release's music scorer renders a piece with abc2midi (app/mimo/vendor/music_scorer)
 
1
+ # Public HF RL Explorer Docker Space. The admin has a separate private deployment.
2
  # /data the private bucket FineEnvs/rl-explorer-data: indexes, packs, rollouts, admin settings; mounted by both
3
  # /tmp/... dataset files read while indexing and the tasks rollouts run on: many small files, local disk
4
  # abcmidi the MiMo release's music scorer renders a piece with abc2midi (app/mimo/vendor/music_scorer)
app/admin.py DELETED
@@ -1,379 +0,0 @@
1
- """The admin dashboard's API, served by its own app (app/admin_app.py) on its own private Space, for members of the
2
- admin organization only (see app/settings.py). Every route checks the signed-in user against the organization on
3
- every request. Every change is written to the audit log with who made it.
4
-
5
- The admin app shares the explorer's bucket but not its process. It reads the run records the explorer writes, and
6
- what it changes goes into the shared settings file, which the explorer re-reads: collections, pins, hidden
7
- environments, the rollout switches, rollouts taken off Community, and requests to stop a running rollout.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import gzip
13
- import json
14
- import re
15
- import time
16
- from collections import Counter
17
- from typing import Any, Literal
18
-
19
- from fastapi import APIRouter, HTTPException, Request
20
- from pydantic import BaseModel, Field
21
-
22
- from . import auth, auto_index, catalog, community, config, runner, search_api, settings, snapshot, space_checks, space_tasks, store
23
-
24
- STALE = 600 # a rollout whose record hasn't changed for this long is no longer live, whatever it says
25
-
26
- router = APIRouter(prefix="/api/admin")
27
- KEY = re.compile(r"^(space:)?[A-Za-z0-9][\w.-]*/[\w.-]+$")
28
-
29
-
30
- def _admin(request: Request) -> dict[str, Any]:
31
- u = auth.current_user(request)
32
- if not u:
33
- raise HTTPException(401, "sign in")
34
- if not settings.is_admin(u):
35
- raise HTTPException(403, f"only members of {settings.ADMIN_ORG} can open the admin dashboard")
36
- return u
37
-
38
-
39
- # ── overview ─────────────────────────────────────────────────────────────────
40
- @router.get("/overview")
41
- def overview(request: Request):
42
- _admin(request)
43
- envs = search_api.environments(include_hidden=True)
44
- runs = _runs()
45
- now = time.time()
46
- day = 86400
47
- by_day = Counter(time.strftime("%Y-%m-%d", time.gmtime(r.get("created_at", 0))) for r in runs if now - r.get("created_at", 0) < 14 * day)
48
- finished = [r for r in runs if r.get("status") in ("done", "failed", "cancelled", "interrupted")]
49
- new = sorted((e for e in envs if e.get("created") and now - _ts(e["created"]) < 14 * day), key=lambda e: e["created"], reverse=True)
50
- hidden_runs = set(settings.get("hidden_runs", []))
51
- ctx = search_api.context()
52
- with snapshot.use() as (_, conn):
53
- public_counts = search_api.sql_extra(conn, ctx, 0, search_api.Query(scope="ready"))["stats"]
54
- idx_files = list(config.INDEX_DIR.glob("*.json.gz"))
55
- pack_files = list(config.PACK_DIR.glob("*.json.gz")) if config.PACK_DIR.is_dir() else []
56
- return {
57
- "environments": {"datasets": sum(1 for e in envs if e["kind"] == "dataset"), "spaces": sum(1 for e in envs if e["kind"] == "space"),
58
- "openenv": sum(space_checks.verified(e.get("api_check")) for e in envs), "indexed": sum(1 for e in envs if e.get("indexed")),
59
- "checked_spaces": sum(space_checks.browseable(e.get("api_check")) for e in envs),
60
- "public": public_counts, "hidden": len(catalog.hidden()), "pinned": len(catalog.pinned()), "collections": len(catalog.collections())},
61
- "rollouts": {"total": len(runs), "live": sum(1 for r in runs if _live(r)), "today": sum(1 for r in runs if now - r.get("created_at", 0) < day),
62
- "week": sum(1 for r in runs if now - r.get("created_at", 0) < 7 * day),
63
- "public": sum(community.is_public(r, hidden_runs) for r in runs),
64
- "private": sum(r.get("visibility") != "public" for r in runs),
65
- "by_runner": Counter(community.runner(r) for r in runs).most_common(),
66
- "failed": sum(1 for r in finished if r.get("status") != "done"), "finished": len(finished),
67
- "users": len({r.get("user") for r in runs}),
68
- "by_day": [[time.strftime("%Y-%m-%d", time.gmtime(now - i * day)), by_day.get(time.strftime("%Y-%m-%d", time.gmtime(now - i * day)), 0)]
69
- for i in range(13, -1, -1)],
70
- "by_model": Counter(r.get("model") or "custom endpoint" for r in runs).most_common(8),
71
- "by_status": Counter(r.get("status") for r in runs).most_common()},
72
- "storage": {"indexes": len(idx_files), "index_bytes": sum(f.stat().st_size for f in idx_files),
73
- "packs": len(pack_files), "pack_bytes": sum(f.stat().st_size for f in pack_files)},
74
- "recent": [_admin_view(r) for r in runs[:8]],
75
- "live": [_admin_view(r) for r in runs if _live(r)][:24],
76
- "operations": _operations(envs),
77
- "new": new[:12], "new_count": len(new),
78
- "settings": _settings(),
79
- }
80
-
81
-
82
- def _ts(iso: str) -> float:
83
- from datetime import datetime
84
-
85
- try:
86
- return datetime.fromisoformat(iso.replace("Z", "+00:00")).timestamp()
87
- except ValueError:
88
- return 0.0
89
-
90
-
91
- _avatars: dict[str, tuple[float, str | None]] = {}
92
-
93
-
94
- def avatar(name: str | None) -> str | None:
95
- """A Hub user's profile picture, looked up once a day (rollouts started before avatars were recorded have none)."""
96
- if not name:
97
- return None
98
- hit = _avatars.get(name)
99
- if hit and time.time() - hit[0] < 86400:
100
- return hit[1]
101
- import httpx
102
-
103
- url = None
104
- try:
105
- r = httpx.get(f"https://huggingface.co/api/users/{name}/overview", timeout=6)
106
- if r.status_code == 200:
107
- url = r.json().get("avatarUrl")
108
- url = url if isinstance(url, str) and url.startswith("https://") else None
109
- except Exception: # noqa: BLE001
110
- pass
111
- _avatars[name] = (time.time(), url)
112
- return url
113
-
114
-
115
- def _live(r: dict[str, Any]) -> bool:
116
- return r.get("status") in store.ACTIVE and time.time() - (r.get("updated_at") or 0) < STALE
117
-
118
-
119
- def _runs() -> list[dict[str, Any]]:
120
- store.refresh(10) # the explorer writes these; re-read them every few seconds
121
- return store.list_runs(limit=100000)
122
-
123
-
124
- def _admin_view(r: dict[str, Any]) -> dict[str, Any]:
125
- """A rollout for admins: everything but where a visitor's own endpoint lives."""
126
- v = dict(r)
127
- if v.get("endpoint"):
128
- v["endpoint"] = {"host": v["endpoint"].get("host"), "model": v["endpoint"].get("model")}
129
- v["runner"] = community.runner(r)
130
- v["community"] = community.is_public(r, set(settings.get("hidden_runs", [])))
131
- v["live"] = _live(r)
132
- v["avatar"] = r.get("avatar") or avatar(r.get("user"))
133
- v["moderated"] = r["id"] in set(settings.get("hidden_runs", []))
134
- return v
135
-
136
-
137
- # ── rollouts ─────────────────────────────────────────────────────────────────
138
- @router.get("/rollouts")
139
- def rollouts(request: Request, status: str = "", q: str = "", user: str = "", runner: str = "",
140
- visibility: str = "", limit: int = 100, offset: int = 0):
141
- _admin(request)
142
- ql = q.strip().lower()
143
- hidden = set(settings.get("hidden_runs", []))
144
- all_runs = _runs()
145
- runs = [r for r in all_runs
146
- if (not status or (status == "live" and _live(r)) or r.get("status") == status)
147
- and (not user or r.get("user") == user)
148
- and (not runner or community.runner(r) == runner)
149
- and (not visibility or (visibility == "community" and community.is_public(r, hidden))
150
- or (visibility == "moderated" and r["id"] in hidden) or r.get("visibility") == visibility)
151
- and (not ql or ql in f"{r.get('title', '')} {r.get('dataset', '')} {r.get('model', '')} {r.get('user', '')} {r['id']} {community.runner(r)}".lower())]
152
- offset = max(0, offset)
153
- return {"total": len(runs), "runners": sorted({community.runner(r) for r in all_runs}),
154
- "runs": [_admin_view(r) for r in runs[offset:offset + max(1, min(limit, 500))]]}
155
-
156
-
157
- @router.post("/rollouts/{run_id}/cancel")
158
- def cancel(run_id: str, request: Request):
159
- """Ask the explorer to stop a running rollout: it watches the settings for these, and stops it within seconds."""
160
- u = _admin(request)
161
- r = store.get(run_id)
162
- if not r:
163
- raise HTTPException(404, "no such rollout")
164
- asks = {k: v for k, v in settings.get("cancel_requests", {}).items() if time.time() - v < 3600}
165
- asks[run_id] = time.time()
166
- settings.save(u["name"], {"cancel_requests": asks}, note=f"asked to stop rollout {run_id}")
167
- return {"cancelled": _live(r)}
168
-
169
-
170
- class Moderate(BaseModel):
171
- hidden: bool
172
-
173
-
174
- @router.post("/rollouts/{run_id}/moderate")
175
- def moderate(run_id: str, body: Moderate, request: Request):
176
- """Take a public rollout off Community (or put it back). Its owner still sees it."""
177
- u = _admin(request)
178
- r = store.get(run_id)
179
- if not r:
180
- raise HTTPException(404, "no such rollout")
181
- gone = set(settings.get("hidden_runs", []))
182
- gone = (gone | {run_id}) if body.hidden else (gone - {run_id})
183
- settings.save(u["name"], {"hidden_runs": sorted(gone)}, note=f"{'hid' if body.hidden else 'restored'} rollout {run_id} on Community")
184
- return {"run": _admin_view(r)}
185
-
186
-
187
- # ── people ───────────────────────────────────────────────────────────────────
188
- @router.get("/people")
189
- def people(request: Request):
190
- """Everyone who has run a rollout: how much, how well, on what, and when last."""
191
- _admin(request)
192
- by: dict[str, dict[str, Any]] = {}
193
- for r in _runs():
194
- name = r.get("user") or "unknown"
195
- p = by.setdefault(name, {"name": name, "avatar": None, "runs": 0, "done": 0, "scored": 0, "live": 0, "public": 0,
196
- "first": r.get("created_at"), "last": r.get("created_at"), "models": Counter(), "datasets": Counter()})
197
- p["avatar"] = p["avatar"] or r.get("avatar")
198
- p["runs"] += 1
199
- p["done"] += r.get("status") == "done"
200
- p["scored"] += r.get("status") == "done" and community.scalar_reward(r)
201
- p["live"] += _live(r)
202
- p["public"] += community.is_public(r, set(settings.get("hidden_runs", [])))
203
- p["first"] = min(p["first"] or 0, r.get("created_at") or 0)
204
- p["last"] = max(p["last"] or 0, r.get("created_at") or 0)
205
- p["models"][r.get("model") or "custom endpoint"] += 1
206
- p["datasets"][r.get("dataset")] += 1
207
- out = []
208
- for p in sorted(by.values(), key=lambda x: -(x["last"] or 0)):
209
- p["avatar"] = p["avatar"] or avatar(p["name"])
210
- p["models"], p["datasets"] = p["models"].most_common(3), p["datasets"].most_common(3)
211
- out.append(p)
212
- return {"people": out, "me": auth.public(auth.current_user(request))}
213
-
214
-
215
- # ── environments: pin, hide, feature, index ──────────────────────────────────
216
- @router.get("/environments")
217
- def environments(request: Request):
218
- _admin(request)
219
- envs = search_api.environments(include_hidden=True)
220
- gone = catalog.hidden()
221
- runs = Counter(r.get("dataset") for r in _runs())
222
- return {"environments": [{**e, "hidden": e["key"] in gone, "ready": e["kind"] == "dataset" or space_checks.browseable(e.get("api_check")), "rollouts": runs.get(e["id"], 0) if e["kind"] == "dataset" else 0} for e in envs],
223
- "collections": catalog.collections(), "jobs": _jobs()}
224
-
225
-
226
- def _jobs() -> dict[str, Any]:
227
- with catalog._jobs_lock:
228
- return {k: {kk: vv for kk, vv in v.items() if kk not in ("sha",)} for k, v in catalog._jobs.items()
229
- if v.get("state") not in ("done",)}
230
-
231
-
232
- class EnvAction(BaseModel):
233
- key: str = Field(max_length=220)
234
- action: Literal["pin", "unpin", "hide", "unhide", "feature", "unfeature", "index"]
235
- collection: str | None = Field(None, max_length=40)
236
-
237
-
238
- @router.post("/environments")
239
- def env_action(body: EnvAction, request: Request):
240
- u = _admin(request)
241
- if not KEY.match(body.key):
242
- raise HTTPException(400, "not an environment key")
243
- key = body.key
244
- if body.action in ("pin", "unpin"):
245
- pins = [k for k in catalog.pinned() if k != key] + ([key] if body.action == "pin" else [])
246
- settings.save(u["name"], {"pinned": pins}, note=f"{body.action} {key}")
247
- elif body.action in ("hide", "unhide"):
248
- gone = sorted((catalog.hidden() - {key}) | ({key} if body.action == "hide" else set()))
249
- settings.save(u["name"], {"hidden": gone}, note=f"{body.action} {key}")
250
- elif body.action in ("feature", "unfeature"):
251
- cols = [dict(c, ids=[k for k in c["ids"] if k != key]) for c in catalog.collections()]
252
- if body.action == "feature":
253
- target = next((c for c in cols if c["id"] == body.collection), None)
254
- if not target:
255
- raise HTTPException(400, "no such collection")
256
- target["ids"].append(key)
257
- settings.save(u["name"], {"collections": cols}, note=f"{body.action} {key}" + (f" in {body.collection}" if body.collection else ""))
258
- elif body.action == "index":
259
- if key.startswith("space:"):
260
- raise HTTPException(400, "Space task counts are discovered automatically from their Task API; dataset indexes are separate")
261
- st = catalog.index_status(key) # anonymous: public datasets only
262
- settings.save(u["name"], {}, note=f"indexed {key}")
263
- return {"ok": True, "job": {k: v for k, v in st.items() if k != "index"}}
264
- return {"ok": True}
265
-
266
-
267
- # ── collections ──────────────────────────────────────────────────────────────
268
- class Collection(BaseModel):
269
- id: str = Field(pattern=r"^[a-z][a-z0-9-]{1,30}$")
270
- group: str = Field(min_length=1, max_length=40)
271
- about: str = Field("", max_length=80)
272
- icon: str
273
- color: str
274
- ids: list[str] = Field(default_factory=list, max_length=80)
275
-
276
-
277
- class Collections(BaseModel):
278
- collections: list[Collection] = Field(max_length=20)
279
-
280
-
281
- @router.get("/collections")
282
- def get_collections(request: Request):
283
- _admin(request)
284
- return {"collections": catalog.collections(), "icons": catalog.ICONS, "colors": catalog.COLORS}
285
-
286
-
287
- @router.put("/collections")
288
- def put_collections(body: Collections, request: Request):
289
- u = _admin(request)
290
- ids = [c.id for c in body.collections]
291
- if len(set(ids)) != len(ids):
292
- raise HTTPException(400, "two collections share an id")
293
- for c in body.collections:
294
- if c.icon not in catalog.ICONS or c.color not in catalog.COLORS:
295
- raise HTTPException(400, f"{c.group}: pick an icon and a colour from the lists")
296
- bad = [k for k in c.ids if not KEY.match(k)]
297
- if bad:
298
- raise HTTPException(400, f"{c.group}: not environment ids: {', '.join(bad[:3])}")
299
- settings.save(u["name"], {"collections": [c.model_dump() for c in body.collections]}, note="edited collections")
300
- return {"collections": catalog.collections()}
301
-
302
-
303
- # ── settings ─────────────────────────────────────────────────────────────────
304
- def _settings() -> dict[str, Any]:
305
- return {"rollouts_enabled": settings.get("rollouts_enabled", True),
306
- "max_active": settings.get("max_active", config.MAX_ACTIVE_ROLLOUTS),
307
- "max_per_user": settings.get("max_per_user", config.MAX_ACTIVE_PER_USER),
308
- "agents": settings.get("agents", [a["id"] for a in runner.AGENTS]),
309
- "announcement": settings.get("announcement", ""),
310
- "all_agents": runner.AGENTS, "admin_org": settings.ADMIN_ORG}
311
-
312
-
313
- class SettingsIn(BaseModel):
314
- rollouts_enabled: bool
315
- max_active: int = Field(ge=1, le=200)
316
- max_per_user: int = Field(ge=1, le=20)
317
- agents: list[str] = Field(max_length=20)
318
- announcement: str = Field("", max_length=280)
319
-
320
-
321
- @router.get("/settings")
322
- def get_settings(request: Request):
323
- _admin(request)
324
- return _settings()
325
-
326
-
327
- @router.put("/settings")
328
- def put_settings(body: SettingsIn, request: Request):
329
- u = _admin(request)
330
- if any(a not in runner.AGENT_IDS for a in body.agents):
331
- raise HTTPException(400, "unknown agent")
332
- settings.save(u["name"], body.model_dump(), note="changed settings")
333
- return _settings()
334
-
335
-
336
- # ── audit and storage ────────────────────────────────────────────────────────
337
- @router.get("/audit")
338
- def audit(request: Request, limit: int = 200):
339
- _admin(request)
340
- return {"entries": settings.audit(max(1, min(limit, 1000)))}
341
-
342
-
343
- @router.get("/indexes")
344
- def indexes(request: Request):
345
- _admin(request)
346
- out = []
347
- for f in sorted(config.INDEX_DIR.glob("*.json.gz")):
348
- head = {}
349
- try:
350
- head = json.loads(f.with_name(f.name.replace(".json.gz", ".head.json")).read_text())
351
- except (OSError, ValueError):
352
- try:
353
- idx = json.loads(gzip.decompress(f.read_bytes()))
354
- head = {"tasks": len(idx.get("tasks") or []), "version": idx.get("version"), "built": idx.get("built")}
355
- except (OSError, ValueError):
356
- pass
357
- pack = config.PACK_DIR / f.name
358
- out.append({"spec": f.name[:-8].replace("__", "/", 1), "bytes": f.stat().st_size, "pack_bytes": pack.stat().st_size if pack.exists() else None,
359
- "tasks": head.get("tasks"), "version": head.get("version"), "current": head.get("version") == catalog.INDEX_VERSION,
360
- "built": head.get("built")})
361
- return {"indexes": out, "version": catalog.INDEX_VERSION, "jobs": _jobs(), "operations": _operations()}
362
-
363
-
364
- def _operations(envs=None):
365
- """Evidence from the shared store, not the admin process's worker settings."""
366
- envs = envs if envs is not None else search_api.environments(include_hidden=True)
367
- spaces = [e for e in envs if e["kind"] == "space"]
368
- records = space_checks.inventory()
369
- states = Counter(space_checks.status(records.get(e["id"])) for e in spaces)
370
- versions = Counter(e.get("declared_version") or "Unknown" for e in spaces if space_checks.verified(records.get(e["id"])))
371
- job = auto_index.status()
372
- now = time.time()
373
- stale = bool(job and now > (job.get("next_at") or job.get("started_at") or 0) + auto_index.INTERVAL)
374
- return {"snapshot": snapshot.status(), "indexer": {**job, "stale": stale},
375
- "checks": {"candidates": len(spaces), "ready": sum(space_checks.browseable(records.get(e["id"])) for e in spaces),
376
- "openenv": sum(space_checks.verified(records.get(e["id"])) for e in spaces),
377
- "statuses": dict(states), "versions": versions.most_common(), "fresh_seconds": space_checks.FRESH,
378
- "latest_at": max((records.get(e["id"], {}).get("checked_at", 0) for e in spaces), default=0)},
379
- "task_catalog": space_tasks.census(records, [e["id"] for e in spaces if e["key"] not in catalog.hidden() and space_checks.browseable(records.get(e["id"]))])}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/admin_app.py DELETED
@@ -1,79 +0,0 @@
1
- """The HF RL Explorer admin: its own app, deployed as its own private Space, for members of the admin organization.
2
-
3
- uv run uvicorn app.admin_app:app --port 8061 # locally, next to the explorer on 8060
4
-
5
- It mounts the same bucket as the explorer (/data on the Space) and shares nothing else with it: no process, no
6
- cookies (another origin), no rollout threads. What it changes goes into the bucket's settings file, which the
7
- explorer re-reads (see app/admin.py). The Space being private already limits it to the organization; every API
8
- route checks membership again.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import os
14
-
15
- from fastapi import FastAPI, Request
16
- from fastapi.middleware.gzip import GZipMiddleware
17
- from fastapi.responses import FileResponse
18
- from fastapi.staticfiles import StaticFiles
19
-
20
- from . import admin, auth, config, http, settings, version
21
-
22
- # where the public explorer is, for links to its datasets, Spaces and rollouts
23
- EXPLORER = os.environ.get("RLX_EXPLORER_URL", "http://localhost:8060" if config.LOCAL_MODE else "https://fineenvs-rl-explorer.hf.space").rstrip("/")
24
-
25
- app = FastAPI(title="HF RL Explorer admin", docs_url=None, redoc_url=None, openapi_url=None)
26
- app.include_router(auth.router)
27
- app.include_router(admin.router)
28
- app.add_middleware(GZipMiddleware, minimum_size=2048, compresslevel=5)
29
- http.install(app)
30
- ADMIN_WEB = config.ROOT / "web-admin"
31
-
32
-
33
- @app.on_event("startup")
34
- def _startup() -> None:
35
- import threading
36
-
37
- from . import search_api, snapshot
38
-
39
- config.STORAGE_DIR.mkdir(parents=True, exist_ok=True)
40
- config.INDEX_DIR.mkdir(parents=True, exist_ok=True)
41
- # the Hub listing the dashboard opens with: fetched now, so the first visit doesn't wait on it
42
- snapshot.start()
43
- threading.Thread(target=lambda: search_api.environments(include_hidden=True), daemon=True, name="warm").start()
44
-
45
-
46
- @app.get("/healthz", include_in_schema=False)
47
- def healthz():
48
- """Alive: the process answers."""
49
- return {"ok": True}
50
-
51
-
52
- @app.get("/readyz", include_in_schema=False)
53
- def readyz():
54
- """Ready: the bucket it keeps the settings in is mounted and writable (this app runs no rollouts or indexes)."""
55
- from fastapi.responses import JSONResponse
56
-
57
- try:
58
- settings.DIR.mkdir(parents=True, exist_ok=True)
59
- (settings.DIR / ".ready").write_text("ok")
60
- store = "ok"
61
- except OSError as e:
62
- store = f"unwritable: {type(e).__name__}"
63
- return JSONResponse({"ok": store == "ok", "store": store}, status_code=200 if store == "ok" else 503)
64
-
65
-
66
- @app.get("/api/me")
67
- def me(request: Request):
68
- u = auth.current_user(request)
69
- return {"user": auth.public(u), "local": config.LOCAL_MODE, "oauth": not config.LOCAL_MODE, "is_admin": settings.is_admin(u),
70
- "admin_org": settings.ADMIN_ORG, "explorer": EXPLORER, "version": version.app_version(), "source": version.source_hash()}
71
-
72
-
73
- @app.get("/")
74
- def index():
75
- return FileResponse(ADMIN_WEB / "index.html")
76
-
77
-
78
- app.mount("/admin", StaticFiles(directory=ADMIN_WEB), name="admin-web")
79
- app.mount("/", StaticFiles(directory=config.WEB_DIR), name="web") # the explorer's stylesheets, icons and helpers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/settings.py CHANGED
@@ -4,8 +4,8 @@ One JSON file in the bucket (STORAGE_DIR/admin/settings.json) holds the collecti
4
  environments, the rollout switches and an announcement; every change is appended to admin/audit.jsonl with who
5
  made it. Values not set here fall back to the defaults in code.
6
 
7
- Admins are the members of one Hub organization (RLX_ADMIN_ORG, FineEnvs by default), checked against the Hub,
8
- plus any usernames in RLX_ADMINS.
9
  """
10
 
11
  from __future__ import annotations
@@ -19,8 +19,6 @@ from typing import Any
19
 
20
  from . import config
21
 
22
- ADMIN_ORG = os.environ.get("RLX_ADMIN_ORG", "FineEnvs")
23
- EXTRA_ADMINS = {u.strip() for u in os.environ.get("RLX_ADMINS", "").split(",") if u.strip()}
24
  DIR = config.STORAGE_DIR / "admin"
25
  PATH = DIR / "settings.json"
26
  AUDIT = DIR / "audit.jsonl"
@@ -90,37 +88,5 @@ def audit(limit: int = 200) -> list[dict[str, Any]]:
90
  return out
91
 
92
 
93
- # ── who is an admin ──────────────────────────────────────────────────────────
94
- _members: tuple[float, set[str]] | None = None
95
- _members_lock = threading.Lock()
96
-
97
-
98
- def _org_members() -> set[str]:
99
- """The admin organization's members, from the Hub, refreshed every 10 minutes."""
100
- global _members
101
- with _members_lock:
102
- if _members and time.time() - _members[0] < 600:
103
- return _members[1]
104
- from huggingface_hub import HfApi
105
-
106
- try:
107
- names = {m.username for m in HfApi(token=False).list_organization_members(ADMIN_ORG)}
108
- except Exception: # noqa: BLE001 - the Hub is down: keep the last list, else nobody
109
- with _members_lock:
110
- return _members[1] if _members else set()
111
- with _members_lock:
112
- _members = (time.time(), names)
113
- return names
114
-
115
-
116
- def is_admin(user: dict[str, Any] | None) -> bool:
117
- """Whether this signed-in user may open the admin dashboard. The name comes from the encrypted session, which
118
- the Hub set at sign-in, so it can't be claimed by a request."""
119
- if not user or not user.get("name"):
120
- return False
121
- # the org's public member list, or the memberships the Hub reported when this user signed in (private ones too)
122
- return user["name"] in EXTRA_ADMINS or user["name"] in _org_members() or ADMIN_ORG in (user.get("orgs") or [])
123
-
124
-
125
  def path_for(name: str) -> Path:
126
  return DIR / name
 
4
  environments, the rollout switches and an announcement; every change is appended to admin/audit.jsonl with who
5
  made it. Values not set here fall back to the defaults in code.
6
 
7
+ The separate private admin application writes these settings; this public module
8
+ only supplies the shared storage contract.
9
  """
10
 
11
  from __future__ import annotations
 
19
 
20
  from . import config
21
 
 
 
22
  DIR = config.STORAGE_DIR / "admin"
23
  PATH = DIR / "settings.json"
24
  AUDIT = DIR / "audit.jsonl"
 
88
  return out
89
 
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  def path_for(name: str) -> Path:
92
  return DIR / name
app/version.py CHANGED
@@ -26,7 +26,7 @@ def app_version() -> str:
26
  @lru_cache(maxsize=1)
27
  def source_hash() -> str:
28
  h = hashlib.sha256()
29
- for base in (ROOT / "app", ROOT / "web", ROOT / "web-admin"):
30
  for p in sorted(base.rglob("*")):
31
  if p.is_file() and p.suffix in (".py", ".js", ".txt", ".json", ".css", ".html") and "__pycache__" not in p.parts:
32
  h.update(p.relative_to(ROOT).as_posix().encode())
 
26
  @lru_cache(maxsize=1)
27
  def source_hash() -> str:
28
  h = hashlib.sha256()
29
+ for base in (ROOT / "app", ROOT / "web"):
30
  for p in sorted(base.rglob("*")):
31
  if p.is_file() and p.suffix in (".py", ".js", ".txt", ".json", ".css", ".html") and "__pycache__" not in p.parts:
32
  h.update(p.relative_to(ROOT).as_posix().encode())
scripts/check_judge_relay.py DELETED
@@ -1,57 +0,0 @@
1
- """The judge relay end to end, minus the sandbox's network hop: a judge session made exactly as
2
- app/runner.Rollout._judge_session makes it, then the call a task's grader makes (its variables bound by app/judge.py: the
3
- per-rollout key, the relay URL, the judge model) to the capture proxy. Then a forged key, and the key after the session ends,
4
- both refused. Uses your HF token (`hf auth login`) for one judge call; prints no token.
5
-
6
- uv run python scripts/check_judge_relay.py path/to/task.toml
7
-
8
- Last run 2026-10-03 on a MiMo General task: 200 with a correct grade in 49 s (Inkling is slow; the local gradio.live
9
- tunnel cuts requests at 60 s, so long judge prompts need the Space's own /capture), forged 401, 1 call, deleted 401."""
10
- import asyncio, os, secrets, sys, time
11
- from dataclasses import replace
12
-
13
- sys.path.insert(0, os.getcwd())
14
- os.environ.setdefault("RLX_WARM", "0")
15
- import httpx
16
- from huggingface_hub import get_token
17
-
18
- from app import config, judge, runner
19
-
20
-
21
- async def main():
22
- runner._proxy_url() # starts the capture server (and, locally, its tunnel)
23
- svc = runner.service()
24
- port = svc.capture.port
25
- pool = svc.capture.app.state.upstreams
26
- from openenv.core.harness.capture.sessions import Upstream
27
-
28
- jup = Upstream(llm_url=config.ROUTER, model="thinkingmachines/Inkling", api_key=get_token(), provider="hf")
29
- jclient, jlevel = await pool.resolve(jup)
30
- sid = "j" + secrets.token_hex(16)
31
- svc.capture.registry.create(sid, upstream=replace(jup, model=jclient.served_model or jup.model), capture_level=jlevel,
32
- purpose="eval", max_model_calls=runner.JUDGE_MAX_CALLS, role="judge", run="local-test")
33
- toml = open(sys.argv[1]).read()
34
- b = judge.bindings(judge.plan(toml), relay=f"http://127.0.0.1:{port}", capability=sid, judge="thinkingmachines/Inkling")["verifier"]
35
- url_key = next(k for k in b if k.endswith("_URL")); key_key = next(k for k in b if k.endswith("_KEY") and b[k] == sid)
36
- model = next(v for k, v in b.items() if "MODEL" in k)
37
- print("grader gets", {k: ("<capability>" if v == sid else v) for k, v in b.items()})
38
- t = time.time()
39
- async with httpx.AsyncClient(timeout=180) as c:
40
- r = await c.post(b[url_key] + "/chat/completions", headers={"Authorization": f"Bearer {b[key_key]}"},
41
- json={"model": model, "temperature": 0, "max_tokens": 200,
42
- "messages": [{"role": "system", "content": "You grade answers. Reply with JSON {\"score\": 0 or 1, \"why\": str}."},
43
- {"role": "user", "content": "Rubric: the answer states that 7*8=56. Answer: 7 times 8 is 56."}]})
44
- # a forged key and a model the session doesn't serve
45
- bad = await c.post(b[url_key] + "/chat/completions", headers={"Authorization": "Bearer jforged"}, json={"model": model, "messages": [{"role": "user", "content": "hi"}]})
46
- print("judge reply", r.status_code, round(time.time() - t, 1), "s:", (r.json().get("choices") or [{}])[0].get("message", {}).get("content", r.text)[:200])
47
- print("forged key ->", bad.status_code)
48
- sess = svc.capture.registry.get(sid)
49
- print("session calls:", sess.graph.stats()["n_turns"])
50
- svc.capture.registry.delete(sid)
51
- print("after delete, same key ->", httpx.post(b[url_key] + "/chat/completions", headers={"Authorization": f"Bearer {sid}"},
52
- json={"model": model, "messages": [{"role": "user", "content": "hi"}]}, timeout=30).status_code)
53
-
54
-
55
- asyncio.run(main())
56
- for t in __import__("gradio.tunneling", fromlist=["x"]).CURRENT_TUNNELS:
57
- t.kill()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scripts/deploy_spaces.py DELETED
@@ -1,115 +0,0 @@
1
- """Deploy HF RL Explorer from this folder: the public explorer Space and its private admin Space.
2
-
3
- uv run python scripts/deploy_spaces.py # the plan, nothing changed
4
- uv run python scripts/deploy_spaces.py --apply # create or update both Spaces
5
- uv run python scripts/deploy_spaces.py --apply --only admin # one of them
6
- uv run python scripts/deploy_spaces.py --apply --listing-from-store # once the indexer Job runs (DESIGN.md)
7
-
8
- Both run this image (Dockerfile); the admin with RLX_APP=app.admin_app:app. They share only the bucket
9
- FineEnvs/rl-explorer-data, mounted read-write at /data; the explorer also mounts the MiMo explorer's repository
10
- snapshots read-only (one copy, the one MiMo's own runs use). Each Space has its own SESSION_SECRET: created once,
11
- never printed, never replaced (replacing it signs everyone out). Each Space gets one commit with its own README,
12
- so one build, and files no longer here are removed from it.
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- import argparse
18
- import fnmatch
19
- import os
20
- import secrets
21
- import sys
22
- from pathlib import Path
23
-
24
- from huggingface_hub import CommitOperationAdd, CommitOperationDelete, HfApi, Volume
25
-
26
- ROOT = Path(__file__).resolve().parents[1]
27
- BUCKET = "FineEnvs/rl-explorer-data"
28
- EXPLORER_URL = "https://fineenvs-rl-explorer.hf.space"
29
- DATA = Volume(type="bucket", source=BUCKET, mount_path="/data")
30
- SPACES = {
31
- "explorer": {
32
- "repo": "FineEnvs/RL-Explorer", "private": False, "readme": "SPACE_README.md", "hardware": "cpu-basic",
33
- "variables": {"MIMO_SNAPSHOT_DIR": "/mimo-snapshots"},
34
- "volumes": [DATA, Volume(type="bucket", source="FineEnvs/mimo-explorer-runs", mount_path="/mimo-snapshots",
35
- path="repo-snapshots", read_only=True)],
36
- },
37
- "admin": {
38
- "repo": "FineEnvs/RL-Explorer-admin", "private": True, "readme": "ADMIN_SPACE_README.md", "hardware": "cpu-basic",
39
- "variables": {"RLX_APP": "app.admin_app:app", "RLX_OAUTH_SCOPES": "openid profile read-memberships", "RLX_EXPLORER_URL": EXPLORER_URL},
40
- "volumes": [DATA],
41
- },
42
- }
43
- # never uploaded: local data and caches, the READMEs (each Space gets its own as README.md), editor and OS files
44
- IGNORE = [".venv/*", ".local-data/*", "*/__pycache__/*", "__pycache__/*", "*.pyc", ".pytest_cache/*", ".ruff_cache/*",
45
- ".gradio/*", "node_modules/*", ".DS_Store", "*/.DS_Store", "README.md", "SPACE_README.md", "ADMIN_SPACE_README.md", "EXPLORER_AUDIT.md",
46
- ".env", ".env.*", "*/.env", "*/.env.*", "*.env", "*.pem", "*.key", ".git/*"]
47
-
48
-
49
- SKIP_DIRS = {".venv", ".local-data", "__pycache__", ".pytest_cache", ".ruff_cache", ".gradio", "node_modules", ".git"}
50
-
51
-
52
- def files() -> list[str]:
53
- """What goes into the Space: walked without descending into local data, caches or the virtualenv."""
54
- out = []
55
- for root, dirs, names in os.walk(ROOT):
56
- dirs[:] = sorted(d for d in dirs if d not in SKIP_DIRS and not (Path(root) / d).is_symlink())
57
- for n in sorted(names):
58
- rel = (Path(root) / n).relative_to(ROOT).as_posix()
59
- if not (Path(root) / n).is_symlink() and not any(fnmatch.fnmatch(rel, pat) for pat in IGNORE):
60
- out.append(rel)
61
- return out
62
-
63
-
64
- def deploy(api: HfApi, name: str, spec: dict, listing_from_store: bool, apply: bool) -> None:
65
- repo = spec["repo"]
66
- variables = {**spec["variables"], **({"RLX_LISTING_FROM_STORE": "1"} if listing_from_store else {})}
67
- paths = files()
68
- print(f"\n{name}: {repo} ({'private' if spec['private'] else 'public'}, {spec['hardware']})")
69
- print(f" volumes: " + "; ".join(f"{v.source}{'/' + v.path if v.path else ''} -> {v.mount_path}{' (read-only)' if v.read_only else ''}" for v in spec["volumes"]))
70
- print(f" variables: {variables}")
71
- print(f" files: {len(paths)} + README.md from {spec['readme']}")
72
- if not apply:
73
- return
74
- api.create_repo(repo, repo_type="space", space_sdk="docker", private=spec["private"], exist_ok=True,
75
- space_hardware=spec["hardware"])
76
- info = api.space_info(repo)
77
- if bool(info.private) != spec["private"]:
78
- api.update_repo_settings(repo, repo_type="space", private=spec["private"])
79
- api.set_space_volumes(repo, spec["volumes"])
80
- for k, v in variables.items():
81
- api.add_space_variable(repo, k, v)
82
- if "SESSION_SECRET" not in {getattr(x, "key", x) for x in api.get_space_secrets(repo)}: # names, or objects with .key
83
- api.add_space_secret(repo, "SESSION_SECRET", secrets.token_urlsafe(48), description="signs this Space's sessions")
84
- print(" SESSION_SECRET: created")
85
- runtime = api.get_space_runtime(repo)
86
- if (runtime.requested_hardware or runtime.hardware) != spec["hardware"]:
87
- api.request_space_hardware(repo, spec["hardware"])
88
- ops = [CommitOperationAdd(path_in_repo=rel, path_or_fileobj=str(ROOT / rel)) for rel in paths]
89
- ops.append(CommitOperationAdd(path_in_repo="README.md", path_or_fileobj=str(ROOT / spec["readme"])))
90
- keep = set(paths) | {"README.md", ".gitattributes"}
91
- ops += [CommitOperationDelete(path_in_repo=f) for f in api.list_repo_files(repo, repo_type="space") if f not in keep]
92
- commit = api.create_commit(repo, repo_type="space", operations=ops, commit_message="Deploy HF RL Explorer")
93
- print(f" pushed {commit.oid[:10]}: https://huggingface.co/spaces/{repo}")
94
-
95
-
96
- def main() -> None:
97
- ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
98
- ap.add_argument("--apply", action="store_true", help="create or update the Spaces (without it, print the plan)")
99
- ap.add_argument("--only", choices=sorted(SPACES), help="deploy one Space")
100
- ap.add_argument("--listing-from-store", action="store_true", help="read the indexer Job's listing (set once the Job runs)")
101
- args = ap.parse_args()
102
- api = HfApi()
103
- me = api.whoami()
104
- orgs = {o["name"]: o.get("roleInOrg") for o in me.get("orgs", [])}
105
- if orgs.get("FineEnvs") not in ("admin", "write", "contributor"):
106
- sys.exit(f"{me['name']} can't write to FineEnvs (role: {orgs.get('FineEnvs')})")
107
- for name, spec in SPACES.items():
108
- if not args.only or args.only == name:
109
- deploy(api, name, spec, args.listing_from_store, args.apply)
110
- if not args.apply:
111
- print("\nNothing changed: run with --apply.")
112
-
113
-
114
- if __name__ == "__main__":
115
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scripts/schedule_indexer.py DELETED
@@ -1,146 +0,0 @@
1
- """Schedule the catalog indexer (app/indexer.py) as an HF scheduled Job. Prints what it would create; --apply creates it.
2
-
3
- uv run python scripts/schedule_indexer.py # print the Job and the equivalent `hf` command
4
- uv run python scripts/schedule_indexer.py --apply # create it (asks nothing; needs write access to
5
- # FineEnvs' Jobs, and the explorer Space built)
6
- uv run python scripts/schedule_indexer.py --apply --replace # replace one created before (same name label)
7
-
8
- What the Job is:
9
-
10
- image the explorer Space's own image, `hf.co/spaces/FineEnvs/RL-Explorer` (same code, same lockfile, already built
11
- by the Space; a Job can run any Space's image). The Space must exist and have built once.
12
- command `python -m app.indexer --store /data --budget-min 40 --use-token`, from the image's app folder through
13
- uv, like the Space's own CMD.
14
- schedule @hourly, with concurrency off: a run still going when the next is due makes the next one wait, never two
15
- writers. The run's own budget (40 min of index rebuilds) and the Job's timeout (55 min) keep it inside the
16
- hour.
17
- flavor cpu-upgrade (8 vCPU, 32 GB): the full listing (every Space with its files) and a big dataset's index
18
- (13,825 tasks) fit with room to spare; SQLite builds on the container's local disk.
19
- volume the private bucket FineEnvs/rl-explorer-data mounted read-write at /data (`Volume(type="bucket")`, the
20
- platform's hf-mount, the same volume the explorer and admin Spaces mount). STORAGE_DIR=/data, so catalog's
21
- own writers put indexes, packs and listing.json.gz exactly where the Spaces read them.
22
- secret HF_TOKEN, a token of the Job's owner: raises the Hub's rate limits for downloading public dataset files
23
- (--use-token; listings and access checks stay anonymous, so nothing private is ever indexed). It's sent
24
- as a Job secret (encrypted, not shown in the Job's settings) and never printed here.
25
-
26
- How the Job writes to the bucket. Two ways exist: the mounted volume (files written under /data are uploaded when
27
- closed; a rename is one bucket batch, adding the new path and deleting the old, so temp-then-rename is atomic for
28
- readers; other mounts see a change within their metadata TTL, ~10 s, and a full listing poll, ~30 s), or the bucket API
29
- (`HfApi.batch_bucket_files`, one commit per call). The indexer writes through the mount: catalog's index writers
30
- already work that way on the Spaces, and its snapshot store (`--store /data`) writes the database to a temp name,
31
- renames it into `snapshots/`, checks it landed whole, then writes the pointer `snapshots/LATEST.v<SCHEMA>.json` last
32
- the same way. `--store hf://buckets/FineEnvs/rl-explorer-data` publishes the snapshot through the bucket API instead
33
- (the same two-step order: database, then pointer) if the mount ever proves unreliable for a large file.
34
-
35
- The apps pick a new snapshot up on their own: each looks at the pointer every 60 s (app/snapshot.py). Nothing else
36
- changes on the Spaces.
37
- """
38
-
39
- from __future__ import annotations
40
-
41
- import argparse
42
- import json
43
- import shlex
44
- import sys
45
-
46
- NAMESPACE = "FineEnvs"
47
- NAME = "rl-explorer-indexer"
48
- IMAGE = "hf.co/spaces/FineEnvs/RL-Explorer"
49
- BUCKET = "FineEnvs/rl-explorer-data"
50
- APP_DIR = "/home/user/app" # the Dockerfile's WORKDIR
51
-
52
-
53
- def spec(args: argparse.Namespace) -> dict:
54
- run = (f"cd {APP_DIR} && exec uv run --frozen --no-dev python -m app.indexer --store /data "
55
- f"--budget-min {args.budget_min:g} --max-builds {args.max_builds} --keep {args.keep}"
56
- + (" --use-token" if args.with_token else ""))
57
- return {
58
- "namespace": args.namespace,
59
- "name": args.name,
60
- "image": args.image,
61
- "command": ["sh", "-c", run],
62
- "schedule": args.schedule,
63
- "concurrency": False,
64
- "flavor": args.flavor,
65
- "timeout": args.timeout,
66
- "env": {"STORAGE_DIR": "/data", "RLX_CACHE_DIR": "/tmp/rlx-cache", "HF_HOME": "/tmp/hf-home", "PYTHONUNBUFFERED": "1"},
67
- # anonymous by default: public datasets need no token. --with-token sends RLX_INDEXER_TOKEN (a read-only token
68
- # made for this) as HF_TOKEN, for the Hub's higher rate limits; never your login token, which can write
69
- "secrets": ["HF_TOKEN"] if args.with_token else [],
70
- "volumes": [{"type": "bucket", "source": args.bucket, "mount_path": "/data"}],
71
- "labels": {"app": "rl-explorer", "role": "indexer"},
72
- }
73
-
74
-
75
- def cli(s: dict) -> str:
76
- """The same Job with the `hf` CLI."""
77
- parts = ["hf", "jobs", "scheduled", "run", s["schedule"], "--namespace", s["namespace"], "--name", s["name"],
78
- "--flavor", s["flavor"], "--timeout", s["timeout"], "--no-concurrency",
79
- "-v", f"hf://buckets/{s['volumes'][0]['source']}:{s['volumes'][0]['mount_path']}"]
80
- for k, v in s["env"].items():
81
- parts += ["-e", f"{k}={v}"]
82
- for k in s["secrets"]:
83
- parts += ["--secrets", k]
84
- for k, v in s["labels"].items():
85
- parts += ["-l", f"{k}={v}"]
86
- parts += [s["image"], *s["command"]]
87
- return " ".join(shlex.quote(p) for p in parts)
88
-
89
-
90
- def apply(s: dict, replace: bool) -> int:
91
- from huggingface_hub import HfApi, Volume, get_token
92
-
93
- import os
94
-
95
- token = get_token()
96
- if not token:
97
- print("No HF token: run `hf auth login` (a token that can create Jobs in the FineEnvs organization).", file=sys.stderr)
98
- return 2
99
- job_token = os.environ.get("RLX_INDEXER_TOKEN")
100
- if s["secrets"] and not job_token:
101
- print("--with-token needs RLX_INDEXER_TOKEN: a read-only token made for the indexer (not your login token).", file=sys.stderr)
102
- return 2
103
- api = HfApi(token=token)
104
- existing = [j for j in api.list_scheduled_jobs(namespace=s["namespace"], labels={"name": s["name"]})]
105
- if existing and not replace:
106
- print(f"A scheduled Job named {s['name']} exists already ({', '.join(j.id for j in existing)}): --replace to replace it.",
107
- file=sys.stderr)
108
- return 1
109
- for j in existing:
110
- api.delete_scheduled_job(scheduled_job_id=j.id, namespace=s["namespace"])
111
- print(f"deleted scheduled Job {j.id}")
112
- job = api.create_scheduled_job(
113
- image=s["image"], command=s["command"], schedule=s["schedule"], concurrency=s["concurrency"], env=s["env"],
114
- secrets={k: job_token for k in s["secrets"]} or None, flavor=s["flavor"], timeout=s["timeout"], name=s["name"],
115
- labels=s["labels"], volumes=[Volume(**v) for v in s["volumes"]], namespace=s["namespace"])
116
- print(f"created scheduled Job {job.id} ({s['schedule']}); runs: hf jobs scheduled inspect {job.id} --namespace {s['namespace']}")
117
- return 0
118
-
119
-
120
- def main(argv: list[str] | None = None) -> int:
121
- ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
122
- ap.add_argument("--apply", action="store_true", help="create the scheduled Job (otherwise only print it)")
123
- ap.add_argument("--replace", action="store_true", help="with --apply: delete a scheduled Job of the same name first")
124
- ap.add_argument("--with-token", action="store_true", help="send RLX_INDEXER_TOKEN (a read-only token) as the Job's HF_TOKEN")
125
- ap.add_argument("--namespace", default=NAMESPACE)
126
- ap.add_argument("--name", default=NAME)
127
- ap.add_argument("--image", default=IMAGE, help="a Docker image with this app: the explorer Space's, by default")
128
- ap.add_argument("--bucket", default=BUCKET)
129
- ap.add_argument("--schedule", default="@hourly", help='a preset (@hourly) or a cron expression ("17 * * * *")')
130
- ap.add_argument("--flavor", default="cpu-upgrade")
131
- ap.add_argument("--timeout", default="55m")
132
- ap.add_argument("--budget-min", type=float, default=40, help="minutes of index rebuilds per run")
133
- ap.add_argument("--max-builds", type=int, default=100)
134
- ap.add_argument("--keep", type=int, default=5, help="snapshots kept in the bucket")
135
- args = ap.parse_args(argv)
136
- s = spec(args)
137
- print(json.dumps(s, indent=1))
138
- print("\n# the same with the hf CLI:\n" + cli(s))
139
- if not args.apply:
140
- print("\n# nothing created: run again with --apply to create it")
141
- return 0
142
- return apply(s, args.replace)
143
-
144
-
145
- if __name__ == "__main__":
146
- sys.exit(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/conftest.py DELETED
@@ -1,9 +0,0 @@
1
- """One sandbox for every test module: storage and caches in a temp folder, a fixed session secret, no warm-up."""
2
-
3
- import os
4
- import tempfile
5
-
6
- _TMP = tempfile.mkdtemp(prefix="rlx-test-")
7
- os.environ.setdefault("RLX_TEST_TMP", _TMP)
8
- os.environ.update(STORAGE_DIR=os.environ["RLX_TEST_TMP"], RLX_CACHE_DIR=f"{os.environ['RLX_TEST_TMP']}/cache", OAUTH_CLIENT_ID="test-client",
9
- SESSION_SECRET="test-secret", RLX_WARM="0", RLX_ADMIN_ORG="FineEnvs")
 
 
 
 
 
 
 
 
 
 
tests/reward-stats.test.mjs DELETED
@@ -1,20 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { rewardDistribution } from '../web/js/reward-stats.js';
4
-
5
- test('negative and unbounded rewards remain in the histogram', () => {
6
- const d = rewardDistribution([-100, -5, 0, 0.5, 1, 500]);
7
- assert.equal(d.min, -100);
8
- assert.equal(d.max, 500);
9
- assert.equal(d.hist.reduce((a, b) => a+b), 6);
10
- assert.equal(d.hist[0], 1);
11
- assert.equal(d.hist[9], 1);
12
- });
13
- test('empty and normalized rewards keep a useful finite range', () => {
14
- for (const xs of [[], [0, 0.5, 1], [NaN, Infinity]]) {
15
- const d = rewardDistribution(xs);
16
- assert.equal(d.min, 0);
17
- assert.equal(d.max, 1);
18
- assert.ok(d.hist.every(Number.isFinite));
19
- }
20
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/schemaform.test.mjs DELETED
@@ -1,89 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { schemaForm, readForm, resolveSchema, validateValue } from '../web/js/schemaform.js';
4
-
5
- const form = (fields = [], root = null) => ({
6
- querySelector: () => root,
7
- querySelectorAll: () => fields.map(([k, t, value]) => ({ dataset: { k, t }, value })),
8
- });
9
- const plain = (v) => JSON.parse(JSON.stringify(v));
10
-
11
- test('optional booleans can be omitted, and explicit false remains false', () => {
12
- const schema = { type: 'object', properties: { enabled: { type: 'boolean', default: true } } };
13
- assert.deepEqual(plain(readForm(form([['enabled', 'bool', '']]), schema)), {});
14
- assert.deepEqual(plain(readForm(form([['enabled', 'bool', 'false']]), schema)), { enabled: false });
15
- assert.match(schemaForm(schema), /Use server default/);
16
- });
17
-
18
- test('integer inputs reject fractions rather than truncating actions', () => {
19
- const s = { type: 'object', properties: { action: { type: 'integer', minimum: 0, maximum: 3 } }, required: ['action'] };
20
- for (const value of ['1.5', '4', '-1', 'Infinity', '9007199254740992', '']) {
21
- assert.throws(() => readForm(form([['action', 'int', value]]), s));
22
- }
23
- assert.equal(readForm(form([['action', 'int', '0']]), s).action, 0);
24
- });
25
-
26
- test('enum values retain their JSON type and never silently pick the first option', () => {
27
- const s = { type: 'object', properties: { action: { enum: [0, 1, 'stop', null] } }, required: ['action'] };
28
- assert.match(schemaForm(s), /Choose a value/);
29
- assert.equal(readForm(form([['action', 'enum', '0']]), s).action, 0);
30
- assert.equal(readForm(form([['action', 'enum', 'null']]), s).action, null);
31
- assert.throws(() => readForm(form([['action', 'enum', '"bad"']]), s));
32
- });
33
-
34
- test('local references retain defaults and referenced discriminators', () => {
35
- const s = { type: 'object', $defs: { Mode: { const: 'move' }, Count: { type: 'integer', default: 7 } },
36
- properties: { type: { $ref: '#/$defs/Mode' }, count: { $ref: '#/$defs/Count' } } };
37
- assert.match(schemaForm(s), /value="7"/);
38
- assert.equal(readForm(form(), s).type, 'move');
39
- });
40
-
41
- test('recursive and missing references are bounded', () => {
42
- const s = { $ref: '#/$defs/Loop', $defs: { Loop: { $ref: '#/$defs/Loop' } } };
43
- assert.doesNotThrow(() => schemaForm(s));
44
- assert.deepEqual(resolveSchema({ $ref: '#/$defs/missing' }, s), {});
45
- });
46
-
47
- test('root action unions use JSON without losing nested arguments', () => {
48
- const s = { oneOf: [
49
- { type: 'object', properties: { type: { const: 'move' }, position: { type: 'array', items: { type: 'number' }, minItems: 2 } }, required: ['type', 'position'] },
50
- { type: 'object', properties: { type: { const: 'stop' } }, required: ['type'] },
51
- ] };
52
- assert.match(schemaForm(s), /data-root-json/);
53
- assert.deepEqual(readForm(form([], { value: '{"type":"move","position":[1,2]}' }), s), { type: 'move', position: [1, 2] });
54
- assert.throws(() => readForm(form([], { value: '{"type":"move"}' }), s));
55
- });
56
-
57
- test('open objects have a JSON editor rather than claiming no inputs', () => {
58
- assert.match(schemaForm({ type: 'object' }), /data-root-json/);
59
- assert.match(schemaForm({ type: 'object', additionalProperties: false }), /No inputs/);
60
- });
61
-
62
- test('all interpolated upstream schema values are escaped', () => {
63
- const payload = '\"><img src=https://evil.test/>';
64
- const s = { type: 'object', properties: { [payload]: { type: 'number', default: payload, minimum: payload } } };
65
- const html = schemaForm(s, { prefix: payload });
66
- assert.ok(!html.includes('<img'));
67
- assert.ok(!html.includes('min="' + payload));
68
- });
69
-
70
- test('sanitized property names cannot create duplicate input ids', () => {
71
- const html = schemaForm({ properties: { 'a.b': { type: 'string' }, 'a/b': { type: 'string' } } });
72
- const ids = [...html.matchAll(/\bid="([^"]+)"/g)].map((x) => x[1]);
73
- assert.equal(ids.length, new Set(ids).size);
74
- });
75
-
76
- test('prototype-like field names remain ordinary JSON properties', () => {
77
- const schema = JSON.parse('{"type":"object","properties":{"__proto__":{"type":"object"}}}');
78
- const result = readForm(form([['__proto__', 'json', '{"polluted":true}']]), schema);
79
- assert.equal(Object.getPrototypeOf(result), null);
80
- assert.equal({}.polluted, undefined);
81
- assert.equal(JSON.parse(JSON.stringify(result)).__proto__.polluted, true);
82
- });
83
-
84
- test('nested required, size and additional-properties constraints report actionable errors', () => {
85
- const s = { type: 'object', properties: { args: { type: 'object', properties: { text: { type: 'string', minLength: 2 } }, required: ['text'], additionalProperties: false } } };
86
- assert.throws(() => validateValue({ args: {} }, s), /args.text is required/);
87
- assert.throws(() => validateValue({ args: { text: '' } }, s), /at least 2/);
88
- assert.throws(() => validateValue({ args: { text: 'ok', answer: 'secret' } }, s), /not an allowed field/);
89
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/space-media.test.mjs DELETED
@@ -1,24 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { spaceMedia } from '../web/js/space-media.js';
4
- import { taskContent } from '../web/js/space-task-view.js';
5
-
6
- test('lazy Space media resolves against the serving Space without mutating observations', () => {
7
- const raw = { observation: { asset_path: '/assets/hash?task_id=a', mime: 'audio/wav' } };
8
- const out = spaceMedia(raw, 'https://owner-env.hf.space');
9
- assert.equal(out.observation.media_url, 'https://owner-env.hf.space/assets/hash?task_id=a');
10
- assert.equal(raw.observation.media_url, undefined);
11
- });
12
-
13
- test('media paths cannot change origin or use active URL schemes', () => {
14
- for (const path of ['//evil.example/assets/a', 'javascript:alert(1)', 'https://evil.example/a', '/other/a'])
15
- assert.equal(spaceMedia({ asset_path: path, mime: 'audio/wav' }, 'https://owner-env.hf.space').media_url, undefined);
16
- assert.equal(spaceMedia({ asset_path: '/assets/a', mime: 'audio/wav' }, 'http://localhost').media_url, undefined);
17
- });
18
-
19
- test('task inputs are escaped and lazy media remains an available episode', () => {
20
- const html = taskContent({ prompt: '<img src=x onerror=alert(1)>', task_id: 'a'.repeat(200), media_ready: false });
21
- assert.ok(!html.includes('<img src=x'));
22
- assert.ok(html.includes('Media loads when the episode starts'));
23
- assert.ok(html.includes('Task record'));
24
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_admin_dashboard.py DELETED
@@ -1,94 +0,0 @@
1
- import time
2
- from contextlib import contextmanager
3
-
4
- import pytest
5
- from fastapi.testclient import TestClient
6
-
7
- from app import admin, admin_app, auth, auto_index, catalog, community, search_api, settings, snapshot, space_checks, store
8
-
9
- client = TestClient(admin_app.app)
10
-
11
-
12
- @pytest.fixture
13
- def data(monkeypatch):
14
- now = time.time()
15
- checks = {name: {'id': name, 'stage': 'RUNNING', 'status': status, 'checked_at': at, 'interface': interface,
16
- 'task_catalog': {'tasks': 50, 'counted_splits': 1, 'complete': True, 'checked_at': now}}
17
- for name, status, at, interface in [('org/openenv', space_checks.PASS, now, None),
18
- ('org/stale', space_checks.PASS, now-7200, None), ('org/nemo', 'Checks failed', now, 'nemo-gym')]}
19
- envs = [{'id':'org/harbor','key':'org/harbor','kind':'dataset','framework':'harbor','indexed':{'tasks':8}}]
20
- envs += [{'id':name, 'key':'space:'+name, 'kind':'space', 'api_check':rec, 'declared_version':'0.3.0'} for name,rec in checks.items()]
21
- base = {'user':'alice','dataset':'org/harbor','path':'task','title':'Example','status':'done','reward':1,
22
- 'visibility':'public','created_at':now,'updated_at':now,'avatar':'https://example.com/avatar.png'}
23
- runs = [{**base,'id':'harbor','runner':'harbor','reward':-2}, {**base,'id':'mimo','runner':'mimo','domain':'code','visibility':'private'},
24
- {**base,'id':'nemo','runner':'nemo_gym'}, {**base,'id':'hidden','runner':'harbor'},
25
- {**base,'id':'restricted','runner':'harbor','restricted':True}, {**base,'id':'ungraded','reward':None},
26
- {**base,'id':'live','runner':'mimo','status':'running','reward':None}]
27
- monkeypatch.setattr(auth,'current_user',lambda request:{'name':'alice'})
28
- monkeypatch.setattr(settings,'is_admin',lambda u:True)
29
- monkeypatch.setattr(settings,'get',lambda k,d=None: ['hidden'] if k=='hidden_runs' else d)
30
- monkeypatch.setattr(admin,'_runs',lambda:runs)
31
- monkeypatch.setattr(admin,'avatar',lambda u:None)
32
- monkeypatch.setattr(search_api,'environments',lambda **kw:envs)
33
- monkeypatch.setattr(space_checks,'inventory',lambda:checks)
34
- monkeypatch.setattr(catalog,'hidden',lambda:set())
35
- monkeypatch.setattr(auto_index,'status',lambda:{'state':'idle','last_success':now-20,'next_at':now+600})
36
- monkeypatch.setattr(snapshot,'status',lambda:{'built_at':'2026-10-04T00:00:00+00:00'})
37
- @contextmanager
38
- def use():
39
- yield None,None
40
- monkeypatch.setattr(snapshot,'use',use)
41
- monkeypatch.setattr(search_api,'sql_extra',lambda conn,ctx,n,q:{'stats':{'datasets':1,'spaces':2,'tasks':8}})
42
- return envs,runs,checks
43
-
44
-
45
- def test_overview_matches_community_eligibility_and_checked_spaces(data):
46
- d=client.get('/api/admin/overview').json()
47
- assert d['environments']['spaces']==3
48
- assert d['environments']['checked_spaces']==2
49
- assert d['environments']['openenv']==1
50
- assert d['environments']['public']=={'datasets':1,'spaces':2,'tasks':8}
51
- assert d['rollouts']['public']==2
52
- assert d['rollouts']['private']==1
53
- assert len(d['live'])==1
54
- assert dict(d['rollouts']['by_runner'])=={'harbor':4,'mimo':2,'nemo-gym':1}
55
-
56
-
57
- def test_operations_show_expired_checks_without_counting_them_as_ready(data,monkeypatch):
58
- monkeypatch.setattr(auto_index,'status',lambda:{'state':'indexing','next_at':time.time()-5000})
59
- monkeypatch.setattr(catalog,'hidden',lambda:{'space:org/nemo'})
60
- d=admin._operations(data[0])
61
- assert d['indexer']['stale']
62
- assert d['checks']['ready']==2
63
- assert d['checks']['statuses']['Check expired']==1
64
- assert d['task_catalog']['tasks']==50 # hidden and expired Spaces excluded
65
- assert d['checks']['versions']==[('0.3.0',1)]
66
-
67
-
68
- def test_admin_runner_visibility_filters_and_safe_pagination(data):
69
- d=client.get('/api/admin/rollouts?runner=nemo-gym&visibility=community').json()
70
- assert d['total']==1 and d['runs'][0]['runner']=='nemo-gym'
71
- assert d['runs'][0]['community']
72
- assert d['runners']==['harbor','mimo','nemo-gym']
73
- assert client.get('/api/admin/rollouts?visibility=community').json()['total']==2
74
- assert client.get('/api/admin/rollouts?visibility=moderated').json()['total']==1
75
- assert client.get('/api/admin/rollouts?visibility=private').json()['total']==1
76
- assert client.get('/api/admin/rollouts?offset=-8&limit=1').json()['runs'][0]['id']=='harbor'
77
-
78
-
79
- def test_people_do_not_treat_reward_above_one_as_universal_success(data):
80
- d=client.get('/api/admin/people').json()['people'][0]
81
- assert 'solved' not in d
82
- assert d['scored']==5 and d['public']==2
83
-
84
-
85
- def test_environment_admin_keeps_unverified_candidates_for_review(data):
86
- rows=client.get('/api/admin/environments').json()['environments']
87
- assert len(rows)==4
88
- assert {r['id'] for r in rows if r['ready']}=={'org/harbor','org/openenv','org/nemo'}
89
-
90
-
91
- def test_public_eligibility_reused_for_admin_and_community(data):
92
- hidden={'hidden'}
93
- for r in data[1]:
94
- assert admin._admin_view(r)['community']==community.is_public(r,hidden)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_agents_install.py DELETED
@@ -1,60 +0,0 @@
1
- """How agents get into a task's sandbox: OpenCode from its release binary, Harbor's npm install only as the fallback."""
2
-
3
- from __future__ import annotations
4
-
5
- import asyncio
6
- from types import SimpleNamespace
7
-
8
- from app import runner
9
-
10
-
11
- class FakeEnv:
12
- def __init__(self, ok: bool):
13
- self.ok, self.calls = ok, []
14
-
15
- async def exec(self, command, user=None, env=None, cwd=None, timeout_sec=None, **_):
16
- self.calls.append((command, user))
17
- install = "releases/download" in command
18
- return SimpleNamespace(return_code=0 if (self.ok or not install) else 1, stdout="", stderr="no route")
19
-
20
-
21
- def _agent(tmp_path):
22
- from harbor.agents.installed.opencode import OpenCode
23
-
24
- return OpenCode(logs_dir=tmp_path, model_name="p/m")
25
-
26
-
27
- def test_opencode_installs_from_the_release_binary(tmp_path):
28
- runner._patch_opencode_install()
29
- from harbor.agents.installed.opencode import OpenCode
30
-
31
- env = FakeEnv(ok=True)
32
- asyncio.run(OpenCode.install(_agent(tmp_path), env))
33
- assert "releases/download" in env.calls[0][0] and env.calls[0][1] == "root"
34
- assert not any("npm i -g" in c for c, _ in env.calls) # no apt, nvm or npm
35
-
36
-
37
- def test_opencode_falls_back_to_harbors_install(monkeypatch, tmp_path):
38
- runner._patch_opencode_install()
39
- from harbor.agents.installed.opencode import OpenCode
40
-
41
- fell_back = []
42
- monkeypatch.setattr(runner, "log", SimpleNamespace(warning=lambda *a, **k: fell_back.append(a)))
43
- env = FakeEnv(ok=False)
44
- try:
45
- asyncio.run(OpenCode.install(_agent(tmp_path), env))
46
- except Exception: # noqa: BLE001 - Harbor's own install against a fake sandbox: only that it was tried matters
47
- pass
48
- assert fell_back
49
- assert len(env.calls) > 1 # Harbor's install ran after the binary failed
50
-
51
-
52
- def test_a_failed_agent_reads_as_what_went_wrong_not_its_command():
53
- out = ('RuntimeError: Command failed (exit 1): opencode run -- \'Prepare the 401(k) note for Sarah ...\' | tee x\n'
54
- 'stdout: {"type":"error","error":{"name":"APIError","data":{"message":"Gateway Time-out","statusCode":504,'
55
- '"metadata":{"url":"https://abc.gradio.live/v1/chat/completions"}}}}\n\nstderr: None')
56
- msg = runner._friendly_text(out)
57
- assert msg.startswith("The agent's model calls failed with Gateway Time-out (504) at the gradio.live tunnel")
58
- assert "401(k)" not in msg and "Sign in again" not in msg # the prompt's "401" is not an auth failure
59
- other = runner._friendly_text("RuntimeError: Command failed (exit 2): python run.py --prompt 'the whole task'\nstdout: boom")
60
- assert "the whole task" not in other and "exit 2" in other and "boom" in other
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_audit_regressions.py DELETED
@@ -1,163 +0,0 @@
1
- """Security and lifecycle regressions found during PR #33's explorer audit."""
2
- import gzip
3
-
4
- import httpx
5
- import pytest
6
- from fastapi.testclient import TestClient
7
-
8
- from app import config, main, runner, spaces_live as live
9
-
10
-
11
- def test_forwarded_addresses_require_a_trusted_proxy(monkeypatch):
12
- from starlette.requests import Request
13
- from app.http import client_ip
14
- request = Request({"type": "http", "headers": [(b"x-forwarded-for", b"forged, proxy-appended")], "client": ("peer", 123)})
15
- monkeypatch.setattr(config, "TRUST_PROXY", False)
16
- assert client_ip(request) == "peer"
17
- monkeypatch.setattr(config, "TRUST_PROXY", True)
18
- assert client_ip(request) == "proxy-appended"
19
-
20
-
21
- def test_streamed_gzip_has_a_decompression_output_cap(monkeypatch):
22
- monkeypatch.setattr(live, "MAX_REPLY", 1024)
23
- class Stream(httpx.SyncByteStream):
24
- def __iter__(self):
25
- yield gzip.compress(b"x" * 10_000_000)
26
- with live.SpaceClient(transport=httpx.MockTransport(lambda r: httpx.Response(200, headers={"content-encoding": "gzip"}, stream=Stream()))) as c:
27
- with pytest.raises(live.SpaceError, match="too large"):
28
- c.get("https://fixture.hf.space/")
29
-
30
-
31
- @pytest.mark.parametrize("headers", [
32
- {"Origin": "https://evil.example", "X-Forwarded-Host": "evil.example"},
33
- {"Origin": "http://testserver"},
34
- {"Origin": "null"},
35
- {"Origin": "https://testserver/path"},
36
- {"Origin": "https://testserver@evil.example"},
37
- ])
38
- def test_origin_must_match_scheme_and_authority(monkeypatch, headers):
39
- monkeypatch.setattr(config, "PUBLIC_URL", "")
40
- with TestClient(main.app, base_url="https://testserver") as c:
41
- assert c.post("/api/logout", headers=headers).status_code == 403
42
-
43
-
44
- def test_valid_origin_and_explicit_public_origin(monkeypatch):
45
- with TestClient(main.app, base_url="https://testserver") as c:
46
- monkeypatch.setattr(config, "PUBLIC_URL", "")
47
- assert c.post("/api/logout", headers={"Origin": "https://testserver:443"}).status_code == 200
48
- monkeypatch.setattr(config, "PUBLIC_URL", "https://public.hf.space")
49
- assert c.post("/api/logout", headers={"Origin": "https://public.hf.space"}).status_code == 200
50
-
51
-
52
- def test_personal_api_responses_are_not_cached():
53
- with TestClient(main.app, base_url="https://testserver") as c:
54
- assert c.get("/api/runs").headers["cache-control"] == "private, no-store"
55
- assert c.get("/api/me").headers["cache-control"] == "private, no-store"
56
-
57
-
58
- @pytest.mark.parametrize("bad", [".", "..", "x\n", "x/y", "%2e%2e", "//elsewhere"])
59
- def test_path_parameters_cannot_change_the_published_route(bad):
60
- info = {"api": [{"method": "GET", "path": "/tasks/{id}", "params": ["id"]}]}
61
- with pytest.raises(live.SpaceError):
62
- live.route_url(info, "GET", "/tasks/{id}", {"id": bad})
63
-
64
-
65
- def test_upstream_body_is_stopped_before_it_is_fully_buffered(monkeypatch):
66
- monkeypatch.setattr(live, "MAX_REPLY", 64 * 1024)
67
- reads = []
68
-
69
- class Stream(httpx.SyncByteStream):
70
- def __iter__(self):
71
- for i in range(100):
72
- reads.append(i)
73
- yield b"x" * 64 * 1024
74
-
75
- with live.SpaceClient(transport=httpx.MockTransport(lambda r: httpx.Response(200, stream=Stream()))) as c:
76
- with pytest.raises(live.SpaceError, match="too large"):
77
- c.get("https://fixture.hf.space/")
78
- assert len(reads) <= 3
79
-
80
-
81
- def test_gzip_reply_is_decoded_once_and_limited(monkeypatch):
82
- monkeypatch.setattr(live, "MAX_REPLY", 1024)
83
- def response(r):
84
- return httpx.Response(200, content=gzip.compress(b'{"ok":true}'), headers={"Content-Encoding": "gzip"})
85
- with live.SpaceClient(transport=httpx.MockTransport(response)) as c:
86
- assert c.get("https://fixture.hf.space/").json() == {"ok": True}
87
- with live.SpaceClient(transport=httpx.MockTransport(lambda r: httpx.Response(200, content=gzip.compress(b"x" * 2048), headers={"Content-Encoding": "gzip"}))) as c:
88
- with pytest.raises(live.SpaceError, match="too large"):
89
- c.get("https://fixture.hf.space/")
90
-
91
-
92
- def test_disconnected_episode_is_not_silently_replaced(monkeypatch):
93
- s = live.Session("fixture/env", "owner")
94
- class Socket:
95
- def send(self, message): pass
96
- def recv(self, timeout): raise TimeoutError
97
- def close(self): pass
98
- s.ws = Socket()
99
- with pytest.raises(live.SpaceError) as error:
100
- live._ws_send(s, {"type": "step", "data": {}}, 1)
101
- assert error.value.status == 504
102
- monkeypatch.setattr(live, "_open", lambda s: pytest.fail("must not create a replacement episode"))
103
- with pytest.raises(live.SpaceError) as error:
104
- live._ws_send(s, {"type": "step", "data": {}}, 1)
105
- assert error.value.status == 410
106
-
107
-
108
- @pytest.mark.parametrize("value", [float("nan"), float("inf"), -float("inf"), 1.5])
109
- def test_rollout_limits_reject_non_finite_and_fractional_values(value):
110
- with pytest.raises(ValueError):
111
- runner._check_fields([{"key": "steps", "type": "number", "step": 1, "min": 1, "max": 100}], {"steps": value})
112
-
113
-
114
- def test_raw_file_names_are_unicode_safe_and_cannot_inject_headers(monkeypatch):
115
- monkeypatch.setattr(main.registry, "raw", lambda *a: (b"hello", "text/plain"))
116
- c = TestClient(main.app, base_url="https://testserver")
117
- for name in ['文件.txt', 'x\r\nInjected: true.txt']:
118
- r = c.get('/api/env/org/ds/raw', params={"ref": "train/0", "f": name, "download": "true"})
119
- assert r.status_code == 200
120
- assert r.headers['content-disposition'].startswith("attachment; filename*=UTF-8''")
121
- assert '\r' not in r.headers['content-disposition'] and '\n' not in r.headers['content-disposition']
122
- assert 'Injected' not in r.headers
123
- assert r.headers['content-security-policy'] == 'sandbox'
124
-
125
-
126
- def test_query_parameters_survive_route_discovery_and_are_encoded():
127
- api = {"paths": {"/tasks/{id}": {"parameters": [{"name": "split", "in": "query", "required": True, "schema": {"type": "string"}}],
128
- "get": {"parameters": [{"name": "limit", "in": "query", "schema": {"type": "integer", "default": 10}}]}}}}
129
- info = {"api": live._api_routes(api, False)}
130
- schema = info["api"][0]["query_schema"]
131
- assert schema["required"] == ["split"] and schema["properties"]["limit"]["default"] == 10
132
- assert live.route_url(info, "GET", "/tasks/{id}", {"id": "a"}, {"split": "x&admin=true"}) == '/tasks/a?split=x%26admin%3Dtrue'
133
- with pytest.raises(live.SpaceError, match='required'):
134
- live.route_url(info, "GET", "/tasks/{id}", {"id": "a"})
135
- with pytest.raises(live.SpaceError, match='published'):
136
- live.route_url(info, "GET", "/tasks/{id}", {"id": "a"}, {"url": "https://evil.example"})
137
-
138
-
139
- def test_mcp_routes_separate_body_path_and_query_and_keep_grading_routes(monkeypatch):
140
- from app import mcp_bridge
141
- api = {"paths": {"/verify/{id}": {"post": {
142
- "parameters": [{"name": "id", "in": "query", "schema": {"type": "string"}}],
143
- "requestBody": {"required": True, "content": {"application/json": {"schema": {"type": "object", "properties": {"id": {"type": "integer"}}, "required": ["id"]}}}},
144
- }}}}
145
- info = {"api": live._api_routes(api, False), "mcp": [{"name": "look", "inputSchema": {"type": "object"}}]}
146
- tools = {t['name']: t for t in mcp_bridge._tools(info)}
147
- assert {'look', 'verify_id'} == set(tools)
148
- assert set(tools['verify_id']['inputSchema']['properties']) == {'path_params', 'query', 'body'}
149
- captured = []
150
- monkeypatch.setattr(mcp_bridge, '_play', lambda *a: 'session')
151
- monkeypatch.setattr(live, 'act', lambda *args: captured.append(args[-1]) or {'result': {'json': {'reward': 1}, 'status': 200}})
152
- args = {"path_params": {"id": "path-id"}, "query": {"id": "query-id"}, "body": {"id": 42}}
153
- mcp_bridge._call('o/s', info, {"owner": "owner"}, 'verify_id', args)
154
- assert captured[0]['params'] == args['path_params'] and captured[0]['body'] == args['body'] and captured[0]['query'] == args['query']
155
-
156
-
157
- def test_mcp_rate_limiter_does_not_accumulate_rejected_requests():
158
- from app import mcp_bridge
159
- owner = 'rate-regression'
160
- for _ in range(mcp_bridge.RATE[0] * 3):
161
- mcp_bridge._limited(owner)
162
- assert len(mcp_bridge._hits[owner]) == mcp_bridge.RATE[0]
163
- mcp_bridge._hits.pop(owner)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_auto_index.py DELETED
@@ -1,86 +0,0 @@
1
- import gzip
2
- import json
3
- import time
4
-
5
- import pytest
6
-
7
- from app import auto_index, config, indexer, snapshot
8
-
9
-
10
- @pytest.fixture(autouse=True)
11
- def storage(tmp_path, monkeypatch):
12
- monkeypatch.setattr(config, "STORAGE_DIR", tmp_path)
13
- monkeypatch.setattr(config, "CACHE_DIR", tmp_path / "cache")
14
- monkeypatch.setenv("RLX_SNAPSHOT_STORE", str(tmp_path))
15
-
16
-
17
- def test_recent_full_listing_is_reused_without_extending_discovery_age(monkeypatch):
18
- at = time.time() - 300
19
- rows = [{"id": "owner/env", "kind": "dataset"}]
20
- (config.STORAGE_DIR / "listing.json.gz").write_bytes(gzip.compress(json.dumps({"at": at, "full": True, "rows": rows}).encode()))
21
- called = []
22
- def run(store, **kwargs):
23
- called.append(kwargs)
24
- return {"counts": {"envs": 1, "tasks": 2}}
25
- monkeypatch.setattr(indexer, "run", run)
26
- auto_index.cycle()
27
- assert called == [{"rows": rows, "listing_at": at, "budget_s": 90, "max_builds": 4}]
28
- assert auto_index.status()["state"] == "idle"
29
- assert auto_index.status()["last_success"] >= at
30
- auto_index.cycle()
31
- assert len(called) == 1 # app restarts and duplicate workers honor cadence
32
- assert auto_index.cached_listing(at + 3601) == (None, None)
33
- assert auto_index.cached_listing(at - 1) == (None, None)
34
-
35
-
36
- def test_locked_worker_cannot_publish_duplicate_snapshot(monkeypatch):
37
- fcntl = pytest.importorskip("fcntl")
38
- config.CACHE_DIR.mkdir()
39
- monkeypatch.setattr(indexer, "run", lambda *a, **k: pytest.fail("duplicate indexer"))
40
- with (config.CACHE_DIR / "auto-index.lock").open("a") as lease:
41
- fcntl.flock(lease, fcntl.LOCK_EX | fcntl.LOCK_NB)
42
- auto_index.cycle()
43
- assert auto_index.status() == {}
44
-
45
-
46
- def test_failure_preserves_published_pointer_and_backs_off(monkeypatch):
47
- store = snapshot.open_store()
48
- store.put_bytes(snapshot.POINTER, b"last-good-snapshot")
49
- def fail(*a, **k):
50
- raise ConnectionError("upstream response must not enter public status")
51
- monkeypatch.setattr(indexer, "run", fail)
52
- auto_index.cycle()
53
- first = auto_index.status()
54
- assert first["state"] == "retrying" and first["error"] == "ConnectionError"
55
- assert first["next_at"] > time.time() + 590
56
- assert store.read_bytes(snapshot.POINTER) == b"last-good-snapshot"
57
- auto_index._save({**first, "next_at": 0})
58
- auto_index.cycle()
59
- assert auto_index.status()["failures"] == 2
60
- assert auto_index.status()["next_at"] > time.time() + 1190
61
-
62
-
63
- def test_missing_or_partial_listing_requires_fresh_discovery():
64
- assert auto_index.cached_listing(time.time()) == (None, None)
65
- doc = {"at": time.time(), "full": False, "rows": [{"id": "owner/partial"}]}
66
- (config.STORAGE_DIR / "listing.json.gz").write_bytes(gzip.compress(json.dumps(doc).encode()))
67
- assert auto_index.cached_listing(time.time()) == (None, None)
68
-
69
-
70
- def test_shutdown_terminates_only_owned_indexer_child(monkeypatch):
71
- calls = []
72
- class Child:
73
- def poll(self): return None
74
- def terminate(self): calls.append("terminate")
75
- def wait(self, **kwargs): calls.append("wait")
76
- def popen(command, **kwargs):
77
- assert command[-2:] == ["-m", "app.auto_index"]
78
- assert kwargs["env"]["HF_HUB_DISABLE_IMPLICIT_TOKEN"] == "1"
79
- return Child()
80
- monkeypatch.setattr(auto_index.subprocess, "Popen", popen)
81
- auto_index._stop.set()
82
- try:
83
- auto_index._child()
84
- finally:
85
- auto_index._stop.clear()
86
- assert calls == ["terminate", "wait"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_capabilities.py DELETED
@@ -1,68 +0,0 @@
1
- """Support claims reflect runtime evidence, not a source's framework tag."""
2
- import pytest
3
-
4
- from app import spaces_live as live
5
- from app.envs.capabilities import dataset_support, space_support
6
-
7
-
8
- def state(support, feature):
9
- return next(c["state"] for c in support["capabilities"] if c["id"] == feature)
10
-
11
-
12
- @pytest.mark.parametrize("framework,runner", [("Verifiers", "verifiers"), ("NeMo Gym", "nemo-gym"), ("verl", "verl"), ("SkyRL", "skyrl")])
13
- def test_native_framework_dataset_does_not_claim_local_execution(framework, runner):
14
- support = dataset_support(framework, options=[{"runner": runner, "label": framework, "ok": False, "why": "Use its native harness"}])
15
- assert state(support, "browse") == "available"
16
- assert state(support, "run") == "external"
17
- assert support["framework"]["docs"].startswith("https://")
18
-
19
-
20
- def test_task_support_distinguishes_blocked_from_runnable():
21
- options = [{"runner": "harbor", "label": "Harbor", "ok": False, "why": "GPU required"}]
22
- assert state(dataset_support("Harbor", options=options), "run") == "unavailable"
23
- options[0]["ok"] = True
24
- assert state(dataset_support("Harbor", options=options), "run") == "available"
25
- assert state(dataset_support("Harbor"), "run") == "unknown"
26
-
27
-
28
- def test_sleeping_server_never_claims_live_capabilities():
29
- s = space_support({"running": False, "stage": "SLEEPING", "last_seen": {"step_api": True}})
30
- assert state(s, "play") == "unknown"
31
- assert s["transport"] is None
32
-
33
-
34
- @pytest.mark.parametrize("protocol", ["openenv", "gymnasium", "nemo-gym", "mcp", "api"])
35
- def test_probe_uses_published_protocol_not_reset_route_or_tag(monkeypatch, protocol):
36
- from app import catalog
37
- catalog._memo.clear()
38
- rec = {"id": "fixture/env", "stage": "RUNNING", "host": "https://fixture-env.hf.space", "base_path": None, "tags": ["openenv"]}
39
- monkeypatch.setattr(live, "record", lambda *a, **k: rec)
40
- paths = {"/reset": {"post": {}}, "/step": {"post": {}}} if protocol in {"openenv", "gymnasium"} else (
41
- {"/seed_session": {"post": {}}, "/verify": {"post": {}}} if protocol == "nemo-gym" else {"/mcp": {"post": {}}} if protocol == "mcp" else {"/guess": {"post": {}}})
42
- docs = {"/openapi.json": {"info": {"version": "1"}, "paths": paths}}
43
- if protocol == "openenv":
44
- docs["/schema"] = {k: {"type": "object", "properties": {}} for k in ("action", "observation", "state")}
45
- monkeypatch.setattr(live, "_get_json", lambda rec, path: docs.get(path))
46
- monkeypatch.setattr(live, "_ui_path", lambda rec: None)
47
- monkeypatch.setattr(live, "_tools", lambda rec: ([{"name": "guess"}] if protocol == "mcp" else [], None, {}))
48
- monkeypatch.setattr(live, "remember", lambda *a: None)
49
- info = live.probe("fixture/env")
50
- assert info["framework"] == protocol
51
- assert info["openenv"] is (protocol == "openenv")
52
- assert info["step_api"] is (protocol == "openenv")
53
- if protocol == "gymnasium":
54
- assert {r["path"] for r in info["api"]} == {"/reset", "/step"}
55
- assert info["support"]["transport"] == "http"
56
- assert state(info["support"], "play") == "available"
57
-
58
-
59
- def test_private_dataset_does_not_offer_public_mcp():
60
- assert state(dataset_support("Harbor", restricted=True), "mcp") == "unavailable"
61
-
62
-
63
- def test_packed_harbor_rows_keep_the_harbor_lifecycle():
64
- assert dataset_support("Harbor (packed)")["framework"]["id"] == "harbor"
65
-
66
-
67
- def test_unknown_runner_is_not_claimed_as_hosted():
68
- assert state(dataset_support("Custom", options=[{"runner": "custom", "label": "Custom", "ok": True}]), "run") == "unavailable"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_community.py DELETED
@@ -1,96 +0,0 @@
1
- import json
2
-
3
- import pytest
4
- from fastapi.testclient import TestClient
5
-
6
- from app import auth, community, main, settings, store
7
-
8
- client = TestClient(main.app)
9
-
10
-
11
- def record(id, runner="harbor", **fields):
12
- return {"id": id, "dataset": f"org/{runner}", "path": "task/1", "title": "A task", "user": "hidden-user",
13
- "runner": runner, "status": "done", "reward": 1, "visibility": "public", "model": "org/model",
14
- "created_at": 10, **fields}
15
-
16
-
17
- @pytest.fixture
18
- def records(monkeypatch):
19
- rows = [record("harbor", reward=-3, dataset="a/same"), record("mimo", "mimo", domain="code", reward=0.25),
20
- record("nemo", "nemo-gym", reward=1, dataset="b/same"), record("future", "custom-runtime", reward=20),
21
- record("private", visibility="private"), record("restricted", restricted=True),
22
- record("failed", status="failed", reward=None), record("hidden")]
23
- monkeypatch.setattr(store, "list_runs", lambda **kw: rows)
24
- monkeypatch.setattr(store, "has_artifact", lambda *a: False)
25
- monkeypatch.setattr(settings, "get", lambda key, default=None: ["hidden"] if key == "hidden_runs" else default)
26
- return rows
27
-
28
-
29
- def test_all_saved_runners_share_the_feed_with_scoped_counts(records):
30
- data = client.get("/api/community").json()
31
- assert {r["id"] for r in data["runs"]} == {"harbor", "mimo", "nemo", "future"}
32
- assert data["total"] == data["stats"]["runs"] == 4
33
- assert data["stats"]["datasets"] == data["stats"]["runners"] == 4
34
- assert not data["stats"]["comparable"]
35
- assert "mean" not in data["stats"] # no meaningless global reward average
36
- assert {r["group"] for r in data["runs"]} >= {"a/same", "b/same"}
37
- assert "hidden-user" not in json.dumps(data)
38
- for runner in ("harbor", "mimo", "nemo-gym", "custom-runtime"):
39
- subset = client.get("/api/community", params={"runner": runner}).json()
40
- assert subset["total"] == subset["stats"]["runs"] == subset["stats"]["datasets"] == 1
41
- assert {r["runner"] for r in subset["runs"]} == {runner}
42
- assert len(subset["facets"]["runner"]) == 4 # own facet retains alternatives
43
- assert len(subset["facets"]["dataset"]) == 1
44
-
45
-
46
- def test_search_includes_environment_runner_and_model_and_empty_counts(records):
47
- for query, expected in (("b/same", "nemo"), ("nemo-gym", "nemo"), ("org/mimo model", "mimo")):
48
- data = client.get("/api/community", params={"q": query}).json()
49
- assert [r["id"] for r in data["runs"]] == [expected]
50
- empty = client.get("/api/community?dataset=nobody/missing").json()
51
- assert empty["total"] == empty["stats"]["datasets"] == empty["stats"]["models"] == empty["stats"]["runners"] == 0
52
- assert empty["stats"]["results"] == []
53
-
54
-
55
- def test_reward_summaries_separate_metrics_domains_judges_and_revisions():
56
- rows = [record("a", reward=-4), record("b", reward=-2)]
57
- for field, value in (("reward_key", "latency"), ("domain", "code"), ("judge", "judge/new"), ("sha", "new-revision")):
58
- rows.append(record(field, reward=100, **{field: value}))
59
- data = community.stats(rows)
60
- assert len(data["results"]) == 5
61
- original = next(r for r in data["results"] if r["runs"] == 2)
62
- assert original["mean"] == -3 and original["max"] == -2
63
- assert community.stats([record("a", reward=1e308), record("b", reward=1e308)])["results"][0]["mean"] == 1e308
64
-
65
-
66
- @pytest.mark.parametrize("value", [None, "1", True, {}, float("nan"), float("inf"), 10**1000])
67
- def test_invalid_rewards_cannot_break_or_enter_public_feed(value, monkeypatch):
68
- monkeypatch.setattr(settings, "get", lambda *a: [])
69
- assert not main._shareable(record("bad", reward=value))
70
-
71
-
72
- def test_pagination_sort_and_negative_reward_band(records):
73
- records.extend(record(f"r{i:02}", created_at=i, reward=-i, cost={"total": None}) for i in range(20))
74
- all_rows = client.get("/api/community?sort=reward_asc").json()["runs"]
75
- pages = [client.get("/api/community", params={"sort": "reward_asc", "offset": i, "limit": 5}).json()["runs"] for i in range(0, 24, 5)]
76
- assert [r["id"] for page in pages for r in page] == [r["id"] for r in all_rows]
77
- negative = client.get("/api/community?reward=zero").json()
78
- assert negative["total"] == 21 and all(r["reward"] <= 0 for r in negative["runs"])
79
- assert client.get("/api/community?sort=cost_asc").status_code == 200
80
-
81
-
82
- @pytest.mark.parametrize("runner,domain,events", [("harbor", None, False), ("mimo", "code", True), ("nemo-gym", "nemo-gym", True), (None, "code", True)])
83
- def test_trace_routes_match_runner_and_keep_public_scrubbing(monkeypatch, runner, domain, events):
84
- from app.mimo.runner import core
85
- r = record("trace", runner, domain=domain)
86
- monkeypatch.setattr(store, "get", lambda id: r)
87
- monkeypatch.setattr(settings, "get", lambda *a: [])
88
- monkeypatch.setattr(auth, "current_user", lambda request: None)
89
- monkeypatch.setattr(store, "read_artifact", lambda *a: b'[{"content":"hidden-user"}]')
90
- monkeypatch.setattr(main.runner, "is_live", lambda *a: False)
91
- monkeypatch.setattr(core, "live", lambda *a: None)
92
- monkeypatch.setattr(core, "events", lambda *a: [{"content": "hidden-user"}])
93
- response = client.get("/api/runs/trace")
94
- assert response.status_code == 200 and "hidden-user" not in response.text
95
- assert ("events" in response.json()) == events
96
- assert response.json()["run"]["runner"] == (runner or "mimo")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_contract.py DELETED
@@ -1,175 +0,0 @@
1
- """The environment contract (app/envs/contract.py): a new format plugs in by registering an adapter, and then the
2
- pages' API, the runner, rollouts across a task's forms and the MCP server all work for it, with nothing else changed
3
- (no network: the Hub is stubbed).
4
-
5
- What is checked: the surest adapter that confirms reads a source, and one that hands a source over isn't kept;
6
- views come back without an adapter's private keys and with run options and aliases; files and folders go through the
7
- adapter; a run is refused unless the task's chosen run option says it can run, with its own inputs checked and its
8
- agents limited; rollouts of the same task elsewhere are listed with it; linkers add links; the dataset MCP server
9
- lists, reads and withholds through the same adapter; row refs round-trip.
10
- """
11
-
12
- from __future__ import annotations
13
-
14
- import json
15
- import time
16
- from pathlib import Path
17
- from types import SimpleNamespace
18
-
19
- import pytest
20
- from fastapi.testclient import TestClient
21
-
22
- from app import auth, main, models, runner, settings, store
23
- from app.envs import contract as c, registry, rows
24
-
25
- client = TestClient(main.app, base_url="https://testserver")
26
- TOKEN = "hf_contract_secret_token_0003"
27
-
28
-
29
- def cookie() -> dict[str, str]:
30
- session = {"token": TOKEN, "name": "carol", "avatar": None, "via": "token", "exp": time.time() + 3600}
31
- return {"Cookie": f"{auth.COOKIE}={auth._box.encrypt(json.dumps(session).encode()).decode()}"}
32
-
33
-
34
- class Toy(c.Adapter):
35
- """A made-up format: tasks are numbered puzzles; puzzle 2 holds its answer in a file; "3" can't run."""
36
- id, name, framework = "toy", "Toy puzzles", "Toy"
37
- about = "Each task is a numbered puzzle."
38
-
39
- def detect(self, kind, meta):
40
- return 0.95 if meta["id"].startswith("toy/") else 0.0
41
-
42
- def summary(self, env, subset=None):
43
- return {"state": "ready", "total": 3, "inline": True, "search": True, "subsets": [], "how": {"framework": "Toy", "name": self.name, "about": self.about},
44
- "facets": [c.facet("level", "Level", "level")], "overview": [c.section("graded", "How it's graded", [c.kv([("Reward", "1 if solved")])])]}
45
-
46
- def tasks(self, env, *, subset=None, q="", filters=None, offset=0, everything=False):
47
- cards = [c.card(f"p/{i}", f"Puzzle {i}", facets={"level": ["easy" if i < 2 else "hard"]}) for i in (1, 2, 3)]
48
- return {"cards": cards, "total": 3, "offset": 0, "page": 3}
49
-
50
- def task(self, env, ref):
51
- if ref not in ("p/1", "p/2", "p/3"):
52
- raise LookupError("no such puzzle")
53
- return {"ref": ref, "title": f"Puzzle {ref[2:]}", "sections": [c.section("task", "The task", [c.markdown("Solve it."),
54
- c.files([{"path": "README.md", "size": 3}, {"path": "answer.txt", "size": 2, "withheld": True}])])],
55
- "glance": [["Level", "easy"]], "withheld": ["answer"], "links": [], "_secret": "never shown"}
56
-
57
- def file(self, env, ref, path):
58
- return {"path": path, "withheld": True} if path == "answer.txt" else {"path": path, "text": "hi\n", "size": 3}
59
-
60
- def random(self, env, subset=None):
61
- return "p/1"
62
-
63
- def run_options(self, env, ref, view):
64
- return [c.run_option("harbor", "Harbor", ok=ref != "p/3", why="no image", default=True,
65
- fields=[c.field("judge", "Judge", "select", default="a", options=["a", "b"]), c.field("hint", "Hint", "bool", default=False)],
66
- harnesses=["opencode"])]
67
-
68
- def run_task(self, env, ref):
69
- return {"title": f"Puzzle {ref[2:]}", "restricted": False, "sha": "abc", "bytes": 10, "runnable": {"ok": True, "how": "image"}, "image": "python:3.12"}
70
-
71
- def aliases(self, env, ref):
72
- return [("toy/converted", f"tasks/{ref[2:]}")]
73
-
74
-
75
- class Unsure(c.Adapter):
76
- """Sure of everything at first, then hands every source over (a Harbor tag on a dataset of rows)."""
77
- id = "unsure"
78
-
79
- def detect(self, kind, meta):
80
- return 0.99 if meta["id"].startswith("toy/") else 0.0
81
-
82
- def confirm(self, env):
83
- return False
84
-
85
-
86
- @pytest.fixture(autouse=True)
87
- def toy(monkeypatch):
88
- monkeypatch.setattr(registry, "_meta", lambda kind, spec, token: {"id": spec, "sha": "abc", "restricted": False})
89
- monkeypatch.setattr(registry, "ADAPTERS", [Unsure(), Toy(), *registry.ADAPTERS])
90
- monkeypatch.setattr(registry, "_choice", {})
91
- monkeypatch.setattr(models, "get", lambda mid: {"id": mid, "tools": True, "provider": "x"})
92
- monkeypatch.setattr(runner.Rollout, "execute", lambda self: None)
93
- monkeypatch.setattr(settings, "_org_members", lambda: set())
94
-
95
-
96
- def test_the_surest_adapter_that_confirms_reads_it():
97
- env = registry.resolve("dataset", "toy/puzzles")
98
- assert env.adapter.id == "toy"
99
- d = client.get("/api/env/toy/puzzles").json()
100
- assert d["env"]["adapter"] == "toy" and d["env"]["framework"] == "Toy" and d["total"] == 3 and d["inline"]
101
- assert d["overview"][0]["blocks"][0] == {"type": "kv", "rows": [["Reward", "1 if solved"]]}
102
-
103
-
104
- def test_task_views_are_public_and_complete():
105
- v = client.get("/api/env/toy/puzzles/task?ref=p/1").json()
106
- assert "_secret" not in json.dumps(v)
107
- assert v["env"]["adapter"] == "toy" and v["run"]["options"][0]["ok"] and v["run"]["note"] == ""
108
- assert v["aliases"] == [{"env": "toy/converted", "ref": "tasks/1"}]
109
- assert client.get("/api/env/toy/puzzles/task?ref=nope").status_code == 404
110
- assert client.get("/api/env/toy/puzzles/file?ref=p/2&f=answer.txt").json()["withheld"] is True
111
- assert client.get("/api/env/toy/puzzles/random").json() == {"ref": "p/1"}
112
- assert client.get("/api/env/toy/puzzles/folder?ref=p/1&f=x/").status_code == 404 # lists its folders up front
113
-
114
-
115
- def test_linkers_add_links_without_either_adapter_knowing(monkeypatch):
116
- monkeypatch.setattr(registry, "LINKERS", [lambda env, ref, view: [c.link("Raw row", "/t/raw/ds/train/1", rel="same")]])
117
- v = client.get("/api/env/toy/puzzles/task?ref=p/1").json()
118
- assert v["links"] == [{"label": "Raw row", "href": "/t/raw/ds/train/1", "note": "", "rel": "same"}]
119
-
120
-
121
- def test_runs_follow_the_chosen_run_option():
122
- post = lambda **kw: client.post("/api/runs", json={"dataset": "toy/puzzles", "path": "p/1", "model": "m/x", **kw}, headers=cookie())
123
- r = post(fields={"judge": "b"})
124
- assert r.status_code == 200, r.text
125
- run = r.json()["run"]
126
- assert run["adapter"] == "toy" and run["runner"] == "harbor" and run["fields"] == {"judge": "b", "hint": False} and run["env"] == "toy/puzzles"
127
- assert TOKEN not in r.text
128
- assert post(fields={"judge": "zzz"}).status_code == 400 # not one of its choices
129
- assert post(fields={"other": 1}).status_code == 400 # an input it doesn't take
130
- assert post(fields={"hint": "yes"}).status_code == 400 # not a bool
131
- assert post(runner="mimo").status_code == 400 # no such runner for this task
132
- assert post(runner="Bad Runner!").status_code == 422
133
- assert post(harness="terminus-2").status_code == 400 # this runner drives only opencode
134
- r = client.post("/api/runs", json={"dataset": "toy/puzzles", "path": "p/3", "model": "m/x"}, headers=cookie())
135
- assert r.status_code == 400 and "no image" in r.json()["detail"]
136
-
137
-
138
- def test_rollouts_of_the_same_task_elsewhere_are_listed_with_it():
139
- base = {"user": "carol", "title": "T", "model": "m/x", "harness": "opencode", "status": "done", "reward": 1.0, "visibility": "public"}
140
- here = store.create({**base, "id": "20260102-000000-aaaaaa", "dataset": "toy/puzzles", "path": "p/2", "task_id": "toy/puzzles:p/2"})
141
- there = store.create({**base, "id": "20260102-000000-bbbbbb", "dataset": "toy/converted", "path": "tasks/2", "task_id": "toy/converted:tasks/2", "user": "dave"})
142
- other = store.create({**base, "id": "20260102-000000-cccccc", "dataset": "toy/converted", "path": "tasks/3", "task_id": "toy/converted:tasks/3"})
143
- d = client.get("/api/env/toy/puzzles/runs?ref=p/2", headers=cookie()).json()
144
- assert [r["id"] for r in d["mine"]] == [here["id"]]
145
- assert [r["id"] for r in d["public"]] == [there["id"]] and "user" not in d["public"][0]
146
- assert other["id"] not in json.dumps(d)
147
- assert d["elsewhere"] == [{"env": "toy/converted", "ref": "tasks/2"}]
148
-
149
-
150
- def test_the_dataset_mcp_server_uses_the_same_adapter():
151
- def rpc(method, params=None, **headers):
152
- return client.post("/mcp/d/toy/puzzles", json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params or {}}, headers=headers)
153
-
154
- init = rpc("initialize", {"protocolVersion": "2025-06-18"})
155
- assert init.status_code == 200 and init.headers.get("mcp-session-id") and "Toy" in init.json()["result"]["serverInfo"]["name"]
156
- names = [t["name"] for t in rpc("tools/list").json()["result"]["tools"]]
157
- assert names == ["describe", "list_tasks", "get_task", "read_file", "random_task"]
158
- call = lambda name, args: rpc("tools/call", {"name": name, "arguments": args}).json()["result"]
159
- assert json.loads(call("list_tasks", {"filters": {"level": ["hard"]}})["content"][0]["text"])["total"] == 2
160
- assert json.loads(call("list_tasks", {"query": "puzzle 3"})["content"][0]["text"])["tasks"][0]["ref"] == "p/3"
161
- text = call("get_task", {"ref": "p/2"})["content"][0]["text"]
162
- assert "Solve it." in text and "answer.txt (withheld)" in text and "never shown" not in text
163
- assert "withheld" in call("read_file", {"ref": "p/2", "path": "answer.txt"})["content"][0]["text"]
164
- assert call("get_task", {"ref": "nope"})["isError"] is True
165
- assert rpc("tools/list", Origin="https://evil.example").status_code == 403
166
-
167
-
168
- def test_row_refs_round_trip():
169
- for config, split, i in (("default", "train", 0), ("code", "train", 6), ("a/b", "test", 12)):
170
- ref = rows.ref_of(config, split, i)
171
- assert rows.parse_ref(ref) == (config, split, i)
172
- assert rows.ref_of("default", "train", 3) == "train/3" and rows.ref_of("code", "train", 6) == "code/train/6"
173
- assert rows.subset_id("default", "train") == "train" and rows.parse_subset("code/train") == ("code", "train")
174
- with pytest.raises(LookupError): # a 404, not a 500
175
- rows.parse_ref("train/x")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_deploy_spaces.py DELETED
@@ -1,12 +0,0 @@
1
- from scripts import deploy_spaces
2
-
3
-
4
- def test_upload_manifest_excludes_local_secrets_and_symlinks(tmp_path, monkeypatch):
5
- for name in ('app/main.py','web/app.css','.env','.env.production','sub/.env.local','sub/private.key','.local-data/run.json','.venv/env.py'):
6
- path=tmp_path/name
7
- path.parent.mkdir(parents=True,exist_ok=True)
8
- path.write_text('sample')
9
- (tmp_path/'linked.py').symlink_to(tmp_path/'.env')
10
- (tmp_path/'linked-dir').symlink_to(tmp_path/'.local-data',target_is_directory=True)
11
- monkeypatch.setattr(deploy_spaces,'ROOT',tmp_path)
12
- assert deploy_spaces.files()==['app/main.py','web/app.css']
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_discovery.py DELETED
@@ -1,69 +0,0 @@
1
- """Discovery evidence is metadata, not a runtime attestation or an editorial decision."""
2
- from types import SimpleNamespace
3
-
4
- import pytest
5
- from huggingface_hub import SpaceInfo
6
-
7
- from app import catalog
8
-
9
-
10
- def space(sid="org/env", *, tags=(), files=(), **kw):
11
- return SpaceInfo(id=sid, sdk="docker", private=False, tags=list(tags), likes=0,
12
- siblings=[{"rfilename": f} for f in files], **kw)
13
-
14
-
15
- @pytest.mark.parametrize("files", [("openenv.yaml",), ("my_env/openenv.yml",)])
16
- def test_manifest_discovery(files):
17
- d = catalog._space_summary(space(files=files))
18
- assert d["openenv"] and d["manifest"] == files[0]
19
-
20
-
21
- @pytest.mark.parametrize("file", ["src/openenv.yaml", "templates/openenv.yaml", "examples/openenv.yaml",
22
- "build/openenv.yaml", "vendor/openenv.yaml", "envs/echo/openenv.yaml",
23
- "src/openenv/cli/templates/openenv.yaml", ".venv/openenv.yaml"])
24
- def test_vendored_manifests_are_not_declarations(file):
25
- d = catalog._space_summary(space(files=[file]))
26
- assert not d["openenv"] and d["manifest"] is None
27
-
28
-
29
- @pytest.mark.parametrize("tag", ["openenv", "library:openenv", "openenv-0.7.0"])
30
- def test_tag_only_remains_a_candidate_without_a_manifest(tag):
31
- d = catalog._space_summary(space(tags=[tag]))
32
- assert d["openenv"] and d["manifest"] is None
33
-
34
-
35
- def test_names_and_generic_rl_mcp_tags_do_not_establish_openenv():
36
- d = catalog._space_summary(space("openenv/not-an-env", tags=["rl-environment", "mcp-server"]))
37
- assert not d["openenv"] and d["mcp"]
38
-
39
-
40
- def test_space_census_deduplicates_and_applies_visibility_to_featured(monkeypatch):
41
- good = space(tags=["openenv"])
42
- article = space("org/article", tags=["openenv", "research-article-template"])
43
- private = space("org/private"); private.private = True
44
- static = space("org/static"); static.sdk = "static"
45
- by = {s.id: s for s in [good, article, private, static]}
46
- monkeypatch.setattr(catalog, "collections", lambda: [{"ids": [f"space:{s}" for s in by]}])
47
- monkeypatch.setattr(catalog, "_api", lambda: SimpleNamespace(list_spaces=lambda **kw: iter(by.values()),
48
- space_info=lambda sid, **kw: by[sid]))
49
- assert [r["id"] for r in catalog.spaces()] == [good.id]
50
-
51
-
52
- def test_failed_full_census_is_not_silently_published(monkeypatch):
53
- def listing(**kw):
54
- yield space(tags=["openenv"])
55
- raise ConnectionError("page two unavailable")
56
-
57
- monkeypatch.setattr(catalog, "collections", lambda: [])
58
- monkeypatch.setattr(catalog, "_api", lambda: SimpleNamespace(list_spaces=listing))
59
- with pytest.raises(ConnectionError):
60
- catalog.spaces(full=True)
61
- assert len(catalog.spaces(full=False)) == 1 # a quick bootstrap may serve candidates while refreshing
62
-
63
-
64
- def test_collection_never_reclassifies_framework(monkeypatch):
65
- d = catalog._space_summary(space(tags=["ors"]))
66
- monkeypatch.setattr(catalog, "_listing_rows", lambda: [d])
67
- monkeypatch.setattr(catalog, "collections", lambda: [{"id": "fineenvs", "ids": [d["key"]]}])
68
- monkeypatch.setattr(catalog, "hidden", lambda: set())
69
- assert catalog.environments()[0]["framework"] == "ors"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_envs.py DELETED
@@ -1,307 +0,0 @@
1
- """Rows datasets (app/envs) and big Harbor datasets walked folder by folder (no network: rows and the Hub are stubbed).
2
-
3
- What is checked: each processor claims the datasets it should; answers never come back (named columns, nested
4
- keys, a packed task's solution and answer files, MiMo's patches); archives can't escape their folder or blow up;
5
- filters can't inject; the direct reader pages JSON Lines right; the walker finds tasks without listing whole repos,
6
- and a task's unlisted folders can't be used to reach files outside it.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import base64
12
- import io
13
- import json
14
- import tarfile
15
- from types import SimpleNamespace
16
-
17
- import pytest
18
- from fastapi.testclient import TestClient
19
-
20
- from app import catalog, envs, main
21
- from app.envs import base, direct, processors
22
-
23
- client = TestClient(main.app, base_url="https://testserver")
24
-
25
-
26
- def ds(columns, rows, tags=(), spec="org/ds"):
27
- return base.Dataset(spec=spec, tags=list(tags), card={}, splits=[{"config": "default", "split": "train"}],
28
- features=[{"name": c, "type": {}} for c in columns], sample=rows)
29
-
30
-
31
- def tarball(files: dict[str, bytes], prefix="task_0/") -> str:
32
- buf = io.BytesIO()
33
- with tarfile.open(fileobj=buf, mode="w:gz") as t:
34
- for name, data in files.items():
35
- info = tarfile.TarInfo(prefix + name if not name.startswith(("..", "/")) else name)
36
- info.size = len(data)
37
- t.addfile(info, io.BytesIO(data))
38
- return base64.b64encode(buf.getvalue()).decode()
39
-
40
-
41
- TASK = {
42
- "task.toml": b'[metadata]\ntitle = "Count the words"\ndifficulty = "easy"\ngold_answer = "42"\n[environment]\ndocker_image = "python:3.12"\n',
43
- "instruction.md": b"# Count the words\n\nCount the words in /app/a.txt.\n",
44
- "tests/test.sh": b"#!/bin/bash\nexpected=42\necho 1 > /logs/verifier/reward.txt\n",
45
- "tests/expected_output.json": b'{"answer": 42}',
46
- "solution/solve.sh": b"echo 42\n",
47
- "task_manifest.json": b'{"original_bash": "wc -w /app/a.txt"}',
48
- "environment/Dockerfile": b"FROM python:3.12\n",
49
- "../escape.txt": b"nope",
50
- }
51
-
52
-
53
- # ── which processor reads what ───────────────────────────────────────────────
54
- def test_processors_claim_their_formats():
55
- pick = lambda d: envs.pick(d).id # noqa: E731
56
- assert pick(ds(["path", "task_binary"], [{"path": "t", "task_binary": "x"}], tags=["library:harbor"])) == "harbor-packed"
57
- assert pick(ds(["data_source", "prompt", "ability", "reward_model", "extra_info", "agent_name"], [{}], spec="XiaomiMiMo/MiMo-V2.6-RL-oss")) == "mimo"
58
- assert pick(ds(["id", "responses_create_params", "kwargs"], [{}])) == "nemo-gym"
59
- assert pick(ds(["data_source", "prompt", "reward_model"], [{}])) == "verl"
60
- conv = [{"role": "user", "content": "do it"}, {"role": "assistant", "content": "done"}]
61
- assert pick(ds(["conversations", "agent", "result"], [{"conversations": conv, "agent": "a", "result": "pass"}])) == "traces"
62
- assert pick(ds(["question", "answer"], [{"question": "q", "answer": "a"}], tags=["library:verifiers"])) == "verifiers"
63
- assert pick(ds(["question", "answer"], [{"question": "q", "answer": "a"}])) == "generic"
64
-
65
-
66
- def test_generic_roles_and_answers():
67
- rows = [{"task_id": f"t{i}", "instruction": f"You are an agent.\nWork carefully.\n\nQuestion {i}: what is {i}?", "question": f"what is {i}?",
68
- "answer": str(i), "reward_mode": "exact", "atol": 0.0, "files": ["a.csv"], "difficulty": "easy"} for i in range(5)]
69
- d = ds(list(rows[0]), rows)
70
- p = envs.pick(d)
71
- roles = p.roles(d)
72
- assert roles["task"] == "instruction" and roles["id"] == "task_id" and roles["answer"] == ["answer"]
73
- assert roles["title"] == "question" # the instructions share a preamble: titled by the question
74
- assert set(roles["grading"]) == {"reward_mode", "atol"} and roles["environment"] == ["files"]
75
- view = p.view(rows[3], 3, d, roles)
76
- assert '"3"' not in json.dumps([s["body"] for s in view["sections"] if s["id"] != "task"]) and view["withheld"] == ["answer"]
77
- card = p.card(rows[3], 3, roles)
78
- assert card["title"] == "what is 3?" and card["id"] == "t3"
79
-
80
-
81
- def test_nested_answers_are_withheld_everywhere():
82
- clean, gone = base.withhold({"meta": {"oracle_commands": ["rm"], "level": 1}, "items": [{"gold": 1, "x": 2}], "prompt": "p"})
83
- assert clean == {"meta": {"level": 1}, "items": [{"x": 2}], "prompt": "p"}
84
- assert set(gone) == {"meta.oracle_commands", "items.gold"}
85
- clean, _ = base.withhold({"label": "tw_1", "x": 1}, keep=("label",)) # a label that is the row's id stays
86
- assert clean["label"] == "tw_1"
87
-
88
-
89
- def test_retitle_drops_a_shared_preamble():
90
- cards = [{"title": "Setup", "snippet": "", "_text": f"# Setup\nrun setup.sh\n\n# Goal\nTask number {i} is hard\nmore"} for i in range(4)]
91
- base.retitle(cards)
92
- assert [c["title"] for c in cards] == [f"Task number {i} is hard" for i in range(4)]
93
- assert all("_text" not in c for c in cards)
94
-
95
-
96
- # ── Harbor tasks packed into rows ────────────────────────────────────────────
97
- def test_packed_tasks_unpack_without_escaping_and_withhold_answers():
98
- files = processors.unpack(tarball(TASK))
99
- assert "task.toml" in files and "tests/test.sh" in files # the folder around them stripped
100
- assert not any(".." in n for n in files)
101
- p, d = processors.PackedHarbor(), ds(["path", "task_binary"], [], tags=["library:harbor"])
102
- row = {"path": "task_0", "task_binary": tarball(TASK)}
103
- view = p.view(row, 0, d, p.roles(d))
104
- text = json.dumps(view)
105
- assert "gold_answer" not in json.dumps([s for s in view["sections"] if s["id"] == "metadata"])
106
- assert "solution/solve.sh" in view["withheld"] and "tests/expected_output.json" in view["withheld"] and "task_manifest.json" in view["withheld"]
107
- assert p.file(row, d, "task_manifest.json")["withheld"] is True
108
- assert view["title"] == "Count the words" and "Count the words in /app/a.txt" in text
109
- assert p.file(row, d, "solution/solve.sh") == {"path": "solution/solve.sh", "size": 8, "withheld": True}
110
- assert p.file(row, d, "tests/expected_output.json")["withheld"] is True
111
- assert "42" not in p.file(row, d, "task.toml")["text"]
112
- assert "42" not in p.file(row, d, "tests/test.sh")["text"] # expected=42 masked
113
- assert p.file(row, d, "../escape.txt")["error"]
114
- card = p.card(row, 0, {})
115
- assert card["title"] == "Count the words" and "easy" in card["chips"]
116
-
117
-
118
- def test_data_files_naming_the_answer_are_withheld():
119
- assert catalog._withheld("task_manifest.json", json.dumps({"original_nl": "do x", "mutated_bash": "grep -c x f", "captured_output_path": "tests/o"}))
120
- assert catalog._withheld("meta.yaml", "task: x\nexpected_output: 42\n")
121
- assert catalog._withheld("data.jsonl", '{"q": 1, "answer": 2}\n')
122
- assert not catalog._withheld("config.json", json.dumps({"captured_output_path": "x", "timeout": 3, "name": "t"}))
123
- assert not catalog._withheld("instruction.md", "the expected_output is printed") # only data files by key
124
-
125
-
126
- def test_bad_archives_are_empty_not_errors():
127
- assert processors.unpack("not base64 !!!") == {}
128
- assert processors.unpack(base64.b64encode(b"\x00" * 64).decode()) == {}
129
- assert processors.unpack(12) == {}
130
- big = base64.b64encode(b"x" * (processors.MAX_TAR + 10)).decode()
131
- assert processors.unpack(big) == {}
132
-
133
-
134
- # ── MiMo's raw release ───────────────────────────────────────────────────────
135
- def test_mimo_rows_withhold_patches_and_link_their_harbor_twin(monkeypatch):
136
- inst = {"instance_id": "Repo_Task-001", "docker_image": "img:1", "problem_statement": "fix it", "test_patch": "diff --git a/t b/t",
137
- "patch": "diff --git a/src b/src SECRET", "poc": "crash bytes"}
138
- row = {"data_source": "x", "ability": "swe", "agent_name": "a", "prompt": [{"role": "user", "content": "fix it"}],
139
- "reward_model": {"style": "rule", "ground_truth": "SECRET2"}, "extra_info": {"instance_id": "Repo_Task-001", "instance_json": json.dumps(inst)}}
140
- monkeypatch.setattr(catalog, "_read_index", lambda spec, sha=None: {"tasks": [{"path": "tasks/repo_task-001"}]} if spec.endswith("-code") else None)
141
- p = processors.MiMo()
142
- d = ds(list(row), [row], spec="XiaomiMiMo/MiMo-V2.6-RL-oss")
143
- roles = {**p.roles(d), "_config": "code"}
144
- view = p.view(row, 0, d, roles)
145
- text = json.dumps(view)
146
- assert "SECRET" not in text and "crash bytes" not in text
147
- assert view["run"]["dataset"] == "FineEnvs/MiMo-V2.6-RL-harbor-code" and view["run"]["path"] == "tasks/repo_task-001"
148
- assert any(s["id"] == "tests" for s in view["sections"]) # hidden tests are shown: they're how it's graded
149
-
150
-
151
- # ── filters and the direct reader ────────────────────────────────────────────
152
- def test_filters_are_quoted_and_limited_to_known_columns():
153
- w = envs._where({"difficulty": "x' OR '1'='1", "nope": "1", 'a"b': "v"}, ["difficulty", 'a"b'])
154
- assert w == "\"difficulty\"='x'' OR ''1''=''1' AND \"a\"\"b\"='v'"
155
-
156
-
157
- def test_env_api_rejects_bad_filter_json():
158
- assert client.get("/api/env/org/ds/tasks?f=nope").status_code == 400
159
- assert client.get("/api/env/org/ds/tasks?f=[1]").status_code == 400
160
- assert client.get("/api/env/org/ds/tasks?f=" + json.dumps({f"k{i}": ["v"] for i in range(20)})).status_code == 400
161
-
162
-
163
- def test_jsonl_rows_page_by_line(tmp_path, monkeypatch):
164
- p = tmp_path / "data.jsonl"
165
- p.write_text("\n".join(json.dumps({"id": i, "prompt": f"p{i}"}) for i in range(10)) + "\n\n")
166
- monkeypatch.setattr(direct, "_background", lambda *a, **k: {"state": "done", "local": str(p)})
167
- monkeypatch.setattr(direct.Source, "_size", lambda self, path: 100)
168
- src = direct.Source("org/ds", {"sha": "abc"}, ["data.jsonl"], None)
169
- assert src.total() == 10
170
- assert [i for i, _ in src.read(8, 5)] == [8, 9] and src.read(8, 5)[0][1]["prompt"] == "p8"
171
- hits, cut = direct.scan(src, lambda r: r["id"] % 3 == 0)
172
- assert [i for i, _ in hits] == [0, 3, 6, 9] and not cut
173
-
174
-
175
- def test_a_file_still_coming_serves_its_first_rows(monkeypatch):
176
- monkeypatch.setattr(direct, "_background", lambda *a, **k: {"state": "running"})
177
- monkeypatch.setattr(direct, "_head", lambda *a, **k: ([{"i": n} for n in range(30)], False))
178
- monkeypatch.setattr(direct.Source, "_size", lambda self, path: 10**9)
179
- src = direct.Source("org/ds", {"sha": "abc"}, ["big.jsonl"], None)
180
- assert src.total() is None
181
- assert [r["i"] for _, r in src.read(0, 24)] == list(range(24))
182
- with pytest.raises(direct.DirectError) as e:
183
- src.read(100, 24)
184
- assert e.value.status == 409
185
-
186
-
187
- def test_data_files_follow_the_card_configs(monkeypatch):
188
- files = [("data/train-00000.parquet", 10), ("data/test-00000.parquet", 10), ("extra/notes.json", 5)]
189
- monkeypatch.setattr(catalog, "_cached", lambda key, ttl, fn: files if key[0] == "data-files" else {
190
- "configs": [{"config_name": "default", "data_files": [{"split": "train", "path": "data/train-*"}, {"split": "test", "path": "data/test-*"}]}]})
191
- got = direct.data_files("org/ds", {"sha": "abc"}, None)
192
- assert got == {"default": {"train": ["data/train-00000.parquet"], "test": ["data/test-00000.parquet"]}}
193
-
194
-
195
- # ── big Harbor datasets, walked folder by folder ─────────────────────────────
196
- class FakeHub:
197
- """A repo of 3 splits x 40 task folders, each with a big app folder that must never be listed."""
198
-
199
- def __init__(self):
200
- self.listed, self.info_calls = [], 0
201
- self.dirs = {"": ["tasks"], "tasks": ["tasks/a", "tasks/b", "tasks/c"]}
202
- self.files = {}
203
- for s in ("a", "b", "c"):
204
- self.dirs[f"tasks/{s}"] = [f"tasks/{s}/t{i}" for i in range(40)]
205
- for i in range(40):
206
- d = f"tasks/{s}/t{i}"
207
- self.files.update({f"{d}/task.toml": 10, f"{d}/instruction.md": 20, f"{d}/tests/test.sh": 5})
208
- self.dirs[d] = [f"{d}/app", f"{d}/tests"]
209
-
210
- def list_repo_tree(self, spec, repo_type=None, revision=None, path_in_repo=None, recursive=False):
211
- from huggingface_hub.hf_api import RepoFile, RepoFolder
212
-
213
- p = path_in_repo or ""
214
- self.listed.append(p)
215
- assert not p.endswith("/app"), "a task's app folder was listed"
216
- out = [RepoFolder(path=d, oid="x", tree_id="x") for d in self.dirs.get(p, [])]
217
- out += [RepoFile(path=f, size=n, oid="x") for f, n in self.files.items() if f.rsplit("/", 1)[0] == p]
218
- return iter(out)
219
-
220
- def get_paths_info(self, spec, paths, repo_type=None, revision=None):
221
- from huggingface_hub.hf_api import RepoFile
222
-
223
- self.info_calls += 1
224
- assert len(paths) <= 500
225
- return [RepoFile(path=p, size=self.files[p], oid="x") for p in paths if p in self.files]
226
-
227
-
228
- def test_walker_finds_every_task_with_few_calls(monkeypatch):
229
- hub = FakeHub()
230
- monkeypatch.setattr(catalog, "_api", lambda token=None: hub)
231
- job = {}
232
- out = catalog._walk("org/big", "abc", None, job)
233
- assert len(out["tasks"]) == 120 and out["tasks"][0] == "tasks/a/t0"
234
- assert out["files"]["tasks/a/t0"] == {"task.toml": 10, "instruction.md": 20, "tests/test.sh": 5}
235
- assert len(hub.listed) <= 6 # root, tasks/, three splits (+0): never a task folder
236
- assert hub.info_calls <= 6 and job["tasks"] == 120
237
-
238
-
239
- def test_a_task_folder_listing_stays_inside_the_task(monkeypatch):
240
- monkeypatch.setattr(catalog, "_task_row", lambda spec, path, token=None: ({"path": path}, {"sha": "abc", "info": {}}))
241
- for bad in ("../other", "..", "", "/", "a/../../b"):
242
- with pytest.raises(ValueError):
243
- catalog.task_folder("org/ds", "tasks/t1", bad)
244
-
245
-
246
- def test_listing_falls_back_to_walking_when_too_slow(monkeypatch):
247
- hub = FakeHub()
248
-
249
- def slow(*a, recursive=False, **k):
250
- if recursive:
251
- for n in range(10_000):
252
- yield SimpleNamespace(path=f"f{n}")
253
- else:
254
- yield from hub.list_repo_tree(*a, recursive=False, **k)
255
-
256
- monkeypatch.setattr(catalog, "_api", lambda token=None: SimpleNamespace(list_repo_tree=slow, get_paths_info=hub.get_paths_info))
257
- monkeypatch.setattr(catalog, "LIST_BUDGET", -1)
258
- files, walked = catalog._listing("org/big", "abc", None, {})
259
- assert files == {} and walked and len(walked["tasks"]) == 120
260
-
261
-
262
- # ── when the viewer can't filter or search ───────────────────────────────────
263
- def test_filter_and_search_fall_back_to_reading_a_small_split(monkeypatch):
264
- rows = [(i, {"id": f"t{i}", "q": f"question {i}", "tier": "hard" if i % 3 == 0 else "easy", "answer": "zebra" if i == 4 else "x"}) for i in range(50)]
265
-
266
- def no(*a, **k):
267
- raise envs.ViewerError("the dataset index is loading")
268
-
269
- monkeypatch.setattr(envs.viewer, "filter_rows", no)
270
- monkeypatch.setattr(envs.viewer, "search", no)
271
- monkeypatch.setattr(envs.rows, "_all_rows", lambda spec, config, split, tok: rows)
272
- envs._broken.clear()
273
- v = envs.Viewer("org/ds", None)
274
- page, _ = v.filter("default", "train", {"tier": "hard"}, ["tier"], 0)
275
- assert page["total"] == 17 and all(r["tier"] == "hard" for _, r, _ in page["rows"])
276
- page, _ = v.search("default", "train", "question 4", 0)
277
- assert {r["id"] for _, r, _ in page["rows"]} >= {"t4", "t40", "t41"}
278
- page, _ = v.search("default", "train", "zebra", 0)
279
- assert page["total"] == 0 # searching an answer finds nothing
280
- assert ("org/ds", "filter") in envs._broken # next time straight to the fallback
281
-
282
-
283
- def test_one_slow_viewer_answer_does_not_send_a_dataset_to_its_files(monkeypatch):
284
- calls = {"n": 0}
285
-
286
- def flaky(spec, tok=None):
287
- calls["n"] += 1
288
- if calls["n"] == 1:
289
- raise envs.ViewerError("timeout", 502)
290
- return [{"config": "default", "split": "train"}]
291
-
292
- monkeypatch.setattr(catalog, "info", lambda spec, token=None: {"sha": "abc", "restricted": False})
293
- monkeypatch.setattr(envs.viewer, "splits", flaky)
294
- monkeypatch.setattr(envs.viewer, "rows", lambda *a, **k: {"rows": [], "total": 0, "features": []})
295
- envs._choice.clear()
296
- _, be = envs._backend("org/ds", None)
297
- assert be.kind == "viewer" and calls["n"] == 2
298
-
299
-
300
- def test_harbor_layouts_are_found_whatever_the_tags(monkeypatch):
301
- hub = FakeHub()
302
- monkeypatch.setattr(catalog, "_api", lambda token=None: hub)
303
- catalog._memo.clear()
304
- assert catalog.looks_harbor("org/big", "abc") is True
305
- hub.files = {}
306
- catalog._memo.clear()
307
- assert catalog.looks_harbor("org/big", "abc") is False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_frameworks.py DELETED
@@ -1,420 +0,0 @@
1
- """The framework readers: NeMo Gym (app/envs/nemogym.py), verl / SkyRL (verl_rows.py) and Verifiers (verifiers_rows.py),
2
- with the conversation and tool reader they share (convo.py). No network: rows are built here in each framework's real
3
- field names, and the Prime Environments Hub and file listings are stubbed.
4
-
5
- Checked first and most: no answer ever leaves a reader (not in the task view, its cards, the dataset overview, nor
6
- what search looks at), for every framework's real answer fields. Then: each reader says what the policy is asked
7
- (the conversation, its tools), how it's graded and by what, and how to run it with its own framework, honestly not
8
- here.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import json
14
-
15
- import pytest
16
-
17
- from app.envs import base, convo, nemogym, processors, rows, verifiers_rows, verl_rows
18
- from app.envs.base import withhold
19
-
20
-
21
- def ds(columns, sample, spec="org/ds", tags=(), splits=None):
22
- return base.Dataset(spec=spec, tags=list(tags), card={}, splits=splits or [{"config": "default", "split": "train"}],
23
- features=[{"name": c, "type": {}} for c in columns], sample=sample)
24
-
25
-
26
- def everything(proc, row, spec="org/ds", tags=(), splits=None):
27
- """A row through a reader the way the pages get it: its view, its card, and the dataset's overview."""
28
- d = ds(list(row), [row], spec=spec, tags=tags, splits=splits)
29
- roles = {**proc.roles(d), "_config": "default", "_split": "train"}
30
- view = proc.view(row, 0, d, roles)
31
- card = proc.card(row, 0, roles)
32
- overview = proc.overview(d, roles, [], "default", "train")
33
- return view, card, overview, roles, json.dumps([view, card, overview, {k: v for k, v in roles.items() if not k.startswith("_")}], ensure_ascii=False)
34
-
35
-
36
- @pytest.fixture(autouse=True)
37
- def _offline(monkeypatch):
38
- monkeypatch.setattr(verifiers_rows, "_hub", lambda env_id: {"runtime": "VERIFIERS_V1", "description": "a taskset"})
39
- monkeypatch.setattr(verifiers_rows, "_card_env", lambda spec, meta: None)
40
-
41
-
42
- # ── conversations and tools ──────────────────────────────────────────────────
43
- RESPONSES_INPUT = [
44
- {"role": "system", "content": "You are a finance assistant."},
45
- {"role": "user", "content": "Pull up the balance sheet for Suncor."},
46
- {"id": "rs_1", "type": "reasoning", "summary": [{"type": "summary_text", "text": "I need the symbol."}]},
47
- {"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "Is **SU** right?", "annotations": []}]},
48
- {"role": "user", "content": "Yes, SU."},
49
- {"type": "function_call", "name": "get_balance_sheet", "arguments": "{\"symbol\": \"SU\"}", "call_id": "c1"},
50
- {"type": "function_call_output", "call_id": "c1", "output": "{\"assets\": 1}"},
51
- {"role": "user", "content": "Now the earnings, please."},
52
- ]
53
- TOOLS = [{"type": "function", "name": "get_balance_sheet", "description": "Balance sheets. Annual and quarterly.", "strict": True,
54
- "parameters": {"type": "object", "properties": {"symbol": {"type": "string", "description": "Ticker"},
55
- "period": {"type": "string", "enum": ["annual", "quarter"]}}, "required": ["symbol"]}},
56
- {"type": "function", "function": {"name": "get_earnings", "description": "EPS", "parameters": {"type": "object", "properties": {
57
- "symbol": {"type": "string"}, "years": {"type": "array", "items": {"type": "integer"}}}}}}]
58
-
59
-
60
- def test_responses_items_become_a_transcript_with_outputs_paired_to_calls():
61
- ts = convo.turns(RESPONSES_INPUT)
62
- kinds = [(t["kind"], t["role"]) for t in ts]
63
- assert kinds == [("message", "system"), ("message", "user"), ("reasoning", "assistant"), ("message", "assistant"), ("message", "user"),
64
- ("call", "assistant"), ("message", "user")]
65
- call = ts[5]
66
- assert call["name"] == "get_balance_sheet" and json.loads(call["args"]) == {"symbol": "SU"} and "\n" in call["args"] # pretty-printed
67
- assert json.loads(call["output"]) == {"assets": 1} # JSON outputs are pretty-printed too
68
- assert ts[3]["text"] == "Is **SU** right?" and ts[2]["text"] == "I need the symbol."
69
- assert convo.first_user(ts).startswith("Pull up") and convo.last_user(ts) == "Now the earnings, please."
70
- assert "get_balance_sheet" in convo.transcript_text(ts)
71
-
72
-
73
- def test_chat_tool_calls_pair_with_tool_replies_in_order():
74
- msgs = [{"role": "user", "content": "go"},
75
- {"role": "assistant", "content": None, "reasoning_content": "think", "tool_calls": [
76
- {"id": "a", "type": "function", "function": {"name": "f", "arguments": "{}"}},
77
- {"id": "b", "type": "function", "function": {"name": "g", "arguments": "{\"x\": 1}"}}]},
78
- {"role": "tool", "tool_call_id": "b", "content": "from g"}, {"role": "tool", "content": "from f"}]
79
- ts = convo.turns(msgs)
80
- calls = [t for t in ts if t["kind"] == "call"]
81
- assert [c["output"] for c in calls] == ["from f", "from g"]
82
- assert ts[1]["kind"] == "reasoning" and not any(t["kind"] == "output" for t in ts)
83
-
84
-
85
- def test_tools_read_as_signatures_whatever_their_shape():
86
- ts = convo.tools(TOOLS)
87
- assert [t["name"] for t in ts] == ["get_balance_sheet", "get_earnings"]
88
- sym, period = ts[0]["params"]
89
- assert sym == {"name": "symbol", "type": "string", "required": True, "description": "Ticker"}
90
- assert period["enum"] == ["annual", "quarter"] and not period["required"]
91
- assert ts[1]["params"][1]["type"] == "array of integer"
92
- assert convo.tools([{"name": "mcp_tool", "input_schema": {"properties": {"q": {"type": "string"}}, "required": ["q"]}}])[0]["params"][0]["required"]
93
-
94
-
95
- def test_titles_skip_tags_templates_and_preambles():
96
- assert convo.task_title("<uploaded_files>\n/workspace/x\n</uploaded_files>\n\n<issue>\n**Title:** Mask ignored on assignment\n") == "Mask ignored on assignment"
97
- tpl = "Answer the following question. Reply with 'Answer: X'.\n\n{problem}"
98
- assert convo.task_title("Answer the following question. Reply with 'Answer: A/B'.\n\nWhat is 2+2?\nA: 4\nB: 5", tpl) == "What is 2+2?"
99
- assert convo.headline("Task:\nIn the sheet, sum column B.") == "In the sheet, sum column B."
100
- assert convo.headline("<image> As shown in the figure, find x.") == "As shown in the figure, find x."
101
- long = "We need a shortlist of films " + "with many constraints " * 12
102
- assert convo.headline(long).endswith("…") and len(convo.headline(long)) <= 182
103
-
104
-
105
- def test_withhold_takes_dotted_names_through_lists():
106
- v, gone = withhold({"rubric": [{"question": "Q1?", "pass_criteria": "YES"}], "verifier_metadata": {"unit_tests": {"outputs": ["9"]}, "x": 1}},
107
- hide=frozenset({"rubric.pass_criteria", "verifier_metadata.unit_tests"}))
108
- assert v == {"rubric": [{"question": "Q1?"}], "verifier_metadata": {"x": 1}}
109
- assert gone == ["rubric.pass_criteria", "verifier_metadata.unit_tests"]
110
-
111
-
112
- # ── NeMo Gym ─────────────────────────────────────────────────────────────────
113
- def pivot_row():
114
- return {"trajectory_id": 3344, "info": {"turn": 6, "step": 1, "depth": 12},
115
- "responses_create_params": {"input": RESPONSES_INPUT, "tools": TOOLS, "parallel_tool_calls": True},
116
- "expected_action": {"type": "function_call", "name": "get_earnings", "arguments": "{\"symbol\": \"SECRET-NEXT-STEP\"}"},
117
- "agent_ref": {"type": "responses_api_agents", "name": "toolcall_schema_single_step_tool_use_with_argument_comparison_agent"}}
118
-
119
-
120
- def test_nemo_gym_pivot_row_reads_as_a_conversation_and_hides_the_experts_next_step():
121
- view, card, overview, roles, text = everything(nemogym.NemoGym(), pivot_row(), spec="nvidia/Nemotron-RL-Agentic-Function-Calling-Pivot-v1")
122
- assert "SECRET-NEXT-STEP" not in text
123
- assert view["withheld"] == ["expected_action"] and roles["answer"] == ["expected_action"]
124
- assert view["title"] == "Pull up the balance sheet for Suncor." and card["snippet"].startswith("Latest: Now the earnings")
125
- sec = {s["id"]: s for s in view["sections"]}
126
- tr = sec["task"]["body"][0]
127
- assert tr["type"] == "custom" and tr["renderer"] == "rl" and tr["kind"] == "transcript"
128
- assert any(t["kind"] == "call" and t.get("output") for t in tr["data"]["turns"]) and "expert" in tr["data"]["next"]["text"]
129
- assert sec["tools"]["body"][0]["kind"] == "tools" and len(sec["tools"]["body"][0]["data"]["tools"]) == 2
130
- grading = json.dumps(sec["grading"]["body"])
131
- assert "single_step_tool_use_with_argument_comparison" in grading and "expected_action" in grading
132
- run = sec["run"]["body"][0]["text"]
133
- assert "gym env start" in run and "resources_servers/single_step_tool_use_with_argument_comparison/configs/" \
134
- "toolcall_schema_single_step_tool_use_with_argument_comparison.yaml" in run
135
- assert "--agent toolcall_schema_single_step_tool_use_with_argument_comparison_agent" in run and "gym eval run --no-serve" in run
136
- assert "--repo-id nvidia/Nemotron-RL-Agentic-Function-Calling-Pivot-v1" in run and "sed -n '1p'" in run
137
- opt = view["framework_run"]
138
- # This browsing fixture mixes Chat Completions and Responses tool formats;
139
- # execution requires the dataset's native Responses function definitions.
140
- assert opt["runner"] == "nemo-gym" and opt["ok"] is False and "native NeMo Gym" in opt["why"]
141
- assert ["Step", "turn 6 · step 1"] in view["glance"] and card["chips"][0] == "turn 6 · step 1"
142
-
143
-
144
- NEMO_ANSWERS = [
145
- # (row, the secret strings, the withheld names)
146
- ({"responses_create_params": {"input": [{"role": "user", "content": "Which? A: x B: y"}]}, "expected_answer": "SECRET-LETTER-C",
147
- "options": [{"A": "x"}, {"B": "y"}], "agent_ref": {"type": "responses_api_agents", "name": "mcqa_simple_agent"}},
148
- ["SECRET-LETTER-C"], ["expected_answer"]),
149
- ({"id": 0, "responses_create_params": {"input": [{"role": "user", "content": "Reply to carlos"}], "tools": TOOLS},
150
- "ground_truth": [{"name": "email_reply_email", "arguments": "{\"body\": \"SECRET-REPLY\"}"}], "category": "email",
151
- "environment_name": "workplace_assistant", "agent_ref": {"type": "responses_api_agents", "name": "workplace_assistant_simple_agent"}},
152
- ["SECRET-REPLY"], ["ground_truth"]),
153
- ({"responses_create_params": {"input": [{"role": "user", "content": "add a meeting"}]}, "exp_cal_state": {"0": {"min_time": "SECRET-10:00"}},
154
- "agent_ref": {"type": "responses_api_agents", "name": "calendar_simple_agent"}}, ["SECRET-10:00"], ["exp_cal_state"]),
155
- ({"responses_create_params": {"input": [{"role": "user", "content": "solve"}]}, "verifier_metadata": {"unit_tests": {"inputs": ["1"], "outputs": ["SECRET-OUT"]}},
156
- "hash_id": "h", "agent_ref": {"type": "responses_api_agents", "name": "code_gen_simple_agent"}}, ["SECRET-OUT"], ["verifier_metadata.unit_tests"]),
157
- ({"trajectory_id": 1, "responses_create_params": {"input": [{"role": "user", "content": "<issue>fix</issue>"}]},
158
- "ref_patch": "SECRET-PATCH", "ref_message": {"role": "assistant", "content": "SECRET-MSG"}, "expected_action": {"type": "message", "content": "SECRET-ACT"},
159
- "agent_ref": {"type": "responses_api_agents", "name": "single_step_tool_use_with_argument_comparison_swe"}},
160
- ["SECRET-PATCH", "SECRET-MSG", "SECRET-ACT"], ["expected_action", "ref_message", "ref_patch"]),
161
- ({"uuid": "u", "responses_create_params": {"input": [{"role": "user", "content": "plan a snack"}]},
162
- "rubric": [{"question": "Did it avoid peanuts?", "pass_criteria": "SECRET-YES"}], "agent_ref": {"type": "responses_api_agents", "name": "multichallenge_simple_agent"}},
163
- ["SECRET-YES"], ["rubric.pass_criteria"]),
164
- ({"task_id": "t", "responses_create_params": {"input": [{"role": "system", "content": "bank"}]}, "customer": "C1", "opening_message": "Hi, a statement please",
165
- "user_scenario": {"persona": "p"}, "initial_state": {}, "evaluation_criteria": {"actions": [{"name": "SECRET-ACTION"}], "communicate_info": ["SECRET-FACT"],
166
- "nl_assertions": ["no mutation"]}},
167
- ["SECRET-ACTION", "SECRET-FACT"], ["evaluation_criteria.actions", "evaluation_criteria.communicate_info"]),
168
- ({"id": "j", "responses_create_params": {"input": [{"role": "user", "content": "how to"}]}, "response_policy_mapped": "SECRET-POLICY",
169
- "agent_ref": {"type": "responses_api_agents", "name": "jailbreak_detection_simple_agent"}}, ["SECRET-POLICY"], ["response_policy_mapped"]),
170
- ({"uuid": "r", "responses_create_params": {"input": [{"role": "user", "content": "knights"}]}, "question": "knights", "answer": "SECRET-ANS",
171
- "metadata": {"solution": "SECRET-SOLUTION"}, "agent_ref": {"type": "responses_api_agents", "name": "reasoning_gym_simple_agent"}},
172
- ["SECRET-ANS", "SECRET-SOLUTION"], ["answer", "metadata"]),
173
- ({"responses_create_params": {"input": [{"role": "user", "content": "cite"}]}, "verifier": {"type": "string_match", "patterns": ["SECRET-RE"],
174
- "expected_markers": ["SECRET-MARK"]},
175
- "agent_ref": {"type": "responses_api_agents", "name": "citation_format_simple_agent"}}, ["SECRET-RE", "SECRET-MARK"], ["verifier"]),
176
- ]
177
-
178
-
179
- @pytest.mark.parametrize(("row", "secrets", "names"), NEMO_ANSWERS)
180
- def test_nemo_gym_answer_fields_never_leave(row, secrets, names):
181
- view, card, overview, roles, text = everything(nemogym.NemoGym(), row)
182
- for s in secrets:
183
- assert s not in text, s
184
- assert set(names) <= set(view["withheld"]), view["withheld"]
185
-
186
-
187
- def test_nemo_gym_names_the_config_that_runs_a_renamed_or_unnamed_agent():
188
- a = nemogym.agent_of({"agent_ref": {"type": "responses_api_agents", "name": "turing_vif_simple_agent"}}, "org/x")
189
- assert a["known"] and a["renamed"] and a["config_name"] == "verifif_simple_agent" and a["resources_server"] == "verifif"
190
- a = nemogym.agent_of({"agent_ref": {"name": "single_step_tool_use_with_argument_comparison_swe"}}, "org/x")
191
- assert a["config_name"] == "swe_pivot_single_step_tool_use_with_argument_comparison_agent"
192
- a = nemogym.agent_of({"task_id": "t", "customer": "C", "user_scenario": {}, "evaluation_criteria": {}, "initial_state": {}, "opening_message": "hi"}, "org/x")
193
- assert a["inferred"] and a["resources_server"] == "indian_banking"
194
- a = nemogym.agent_of({"question": "q", "expected_answer": "1"}, "nvidia/Nemotron-RL-math-OpenMathReasoning") # no agent_ref: the config naming the dataset
195
- assert a["resources_server"] == "math_with_judge"
196
- a = nemogym.agent_of({"agent_ref": {"name": "nobody_knows_agent"}}, "org/x")
197
- assert not a["known"] and a["name"] == "nobody_knows_agent"
198
-
199
-
200
- def test_nemo_gym_blends_route_each_row_and_run_without_one_agent():
201
- rows_ = [{"responses_create_params": {"input": [{"role": "user", "content": f"q{i}"}]},
202
- "agent_ref": {"type": "responses_api_agents", "name": n}} for i, n in enumerate(["mcqa_simple_agent", "workplace_assistant_simple_agent"])]
203
- d = ds(["responses_create_params", "agent_ref"], rows_, spec="nvidia/Nemotron-RL-Ultra-Training-Blends")
204
- p = nemogym.NemoGym()
205
- roles = p.roles(d)
206
- assert roles["_mixed"] and p.card(rows_[0], 0, roles)["lead"] == "mcqa_simple_agent"
207
- ov = p.overview(d, roles, [], "rlvr1", "train")
208
- run = next(s for s in ov if s["id"] == "run")["blocks"][0]["text"]
209
- assert run.count("--config ") == 2 and "--agent" not in run and "load_dataset('nvidia/Nemotron-RL-Ultra-Training-Blends', 'rlvr1'" in run
210
-
211
-
212
- def test_nemo_gym_catalog_is_current_and_complete():
213
- cat = nemogym.catalog()
214
- assert len(cat["commit"]) == 40 and len(cat["agents"]) > 100 and len(cat["servers"]) > 100
215
- assert cat["servers"]["instruction_following"]["verification"] == "Rule-based instruction checks"
216
- assert cat["servers"]["math_proof_judgement"]["verification"] == "Deterministic answer parsing"
217
- for name in ("mcqa_simple_agent", "workplace_assistant_simple_agent", "calendar_simple_agent", "code_gen_simple_agent",
218
- "instruction_following_simple_agent", "reasoning_gym_simple_agent"):
219
- a = cat["agents"][name]
220
- assert a["config"].startswith("resources_servers/") and a["resources_server"] in cat["servers"]
221
-
222
-
223
- # ── verl / SkyRL ─────────────────────────────────────────────────────────────
224
- def verl_row(**kw):
225
- return {"data_source": "openai/gsm8k", "prompt": [{"role": "user", "content": "Natalia sold clips to 48 friends. How many? Let's think step by step."}],
226
- "ability": "math", "reward_model": {"style": "rule", "ground_truth": "SECRET-72"},
227
- "extra_info": {"split": "train", "index": 0, "answer": "SECRET-WORKED-SOLUTION", "question": "Natalia sold clips to 48 friends. How many?",
228
- "tools_kwargs": {"calc_gsm8k_reward": {"create_kwargs": {"ground_truth": "SECRET-TOOL-GT"}}}}, **kw}
229
-
230
-
231
- def test_verl_rows_hide_every_ground_truth_and_name_their_scorer():
232
- view, card, overview, roles, text = everything(verl_rows.Verl(), verl_row(agent_name="tool_agent"))
233
- for s in ("SECRET-72", "SECRET-WORKED-SOLUTION", "SECRET-TOOL-GT"):
234
- assert s not in text, s
235
- assert {"reward_model.ground_truth", "extra_info.answer"} <= set(view["withheld"])
236
- assert view["title"] == "Natalia sold clips to 48 friends. How many?" and ["Scorer", "`gsm8k`"] in view["glance"]
237
- sec = {s["id"]: s for s in view["sections"]}
238
- assert "calc_gsm8k_reward" in json.dumps(sec["tools"]["body"])
239
- run = sec["run"]["body"][0]["text"]
240
- assert "python3 -m verl.trainer.main_ppo" in run and "multi_turn.enable=True" in run and "custom_reward_function" not in run
241
- assert view["framework_run"]["runner"] == "verl" and view["framework_run"]["ok"] is False
242
-
243
-
244
- @pytest.mark.parametrize(("source", "scorer"), [("openai/gsm8k", "gsm8k"), ("HuggingFaceH4/MATH-500", "math_reward"), ("aime24", "math_dapo"),
245
- ("numina_olympiads", "prime_math"), ("codeforces", "sandbox_fusion / prime_code"),
246
- ("searchR1_nq", "search_r1_like_qa_em"), ("DeepScaleR-Preview", None)])
247
- def test_verl_routes_data_sources_as_default_compute_score_does(source, scorer):
248
- got = verl_rows.scorer(source)
249
- assert (got[0] if got else None) == scorer
250
-
251
-
252
- def test_verl_rows_with_no_built_in_scorer_get_a_custom_reward_in_the_command():
253
- view, *_ = everything(verl_rows.Verl(), {**verl_row(), "data_source": "sheet_arena"})
254
- run = next(s for s in view["sections"] if s["id"] == "run")["body"][0]["text"]
255
- assert "reward.custom_reward_function.path=" in run
256
-
257
-
258
- def test_compressed_blobs_and_skyrl_sql_answers_are_withheld():
259
- blob = "H4sI" + "A" * 400
260
- row = {"data_source": "synsql", "prompt": [{"role": "user", "content": "Write SQL for the question"}], "env_class": "text2sql",
261
- "reward_spec": {"method": "rule", "ground_truth": "SECRET-GT"}, "sql": "SELECT SECRET_SQL", "output_seq": "SECRET-REASONING",
262
- "db_id": "shop", "extra_info": {"part_1": blob}}
263
- view, card, overview, roles, text = everything(verl_rows.Verl(), row)
264
- for s in ("SECRET-GT", "SECRET_SQL", "SECRET-REASONING", blob):
265
- assert s not in text
266
- assert view["framework"] == "SkyRL" and "extra_info.part_1" in view["withheld"]
267
- run = next(s for s in view["sections"] if s["id"] == "run")["body"][0]["text"]
268
- assert "skyrl.train.entrypoints.main_base" in run and "environment.env_class=text2sql" in run and "org/ds:train" in run
269
-
270
-
271
- def test_mimo_no_longer_claims_verl_rows_that_name_an_agent_loop():
272
- row = {"data_source": "sheet_arena", "agent_name": "tool_agent", "prompt": [{"role": "user", "content": "x"}], "ability": "spreadsheet",
273
- "reward_model": {"style": "rule", "ground_truth": "id"}, "extra_info": {"id": "x"}}
274
- assert rows.pick(ds(list(row), [row])).id == "verl"
275
- mimo = {**row, "extra_info": {"instance_json": "{}"}}
276
- assert rows.pick(ds(list(mimo), [mimo])).id == "mimo"
277
-
278
-
279
- # ── Verifiers ────────────────────────────────────────────────────────────────
280
- def test_verifiers_swe_rows_withhold_gold_patches_tests_and_solutions():
281
- row = {"repo_name": "orange3", "docker_image": "namanjain12/orange3:abc", "commit_hash": "abc",
282
- "problem_statement": "[ISSUE]\n**Title:** Context migration fails\n\n**Description:** It raises.\n[/ISSUE]",
283
- "prompt": "Write an issue for this commit SECRET-COMMIT-PROMPT", "parsed_commit_content": "SECRET-GOLD-DIFF",
284
- "execution_result_content": "SECRET-TESTS", "expected_output_json": "{\"test_a\": \"SECRET-PASSED\"}", "modified_files": ["SECRET/file.py"],
285
- "relevant_files": ["SECRET/rel.py"], "modified_entity_summaries": "SECRET-SUMMARY"}
286
- view, card, overview, roles, text = everything(verifiers_rows.Verifiers(), row, spec="PrimeIntellect/R2E-Gym-Subset-Verified")
287
- for s in ("SECRET-COMMIT-PROMPT", "SECRET-GOLD-DIFF", "SECRET-TESTS", "SECRET-PASSED", "SECRET/file.py", "SECRET/rel.py", "SECRET-SUMMARY"):
288
- assert s not in text, s
289
- assert view["title"] == "Context migration fails"
290
- assert ["Image", "`namanjain12/orange3:abc`"] in view["glance"] and ["API", "v1"] in view["glance"]
291
- run = next(s for s in view["sections"] if s["id"] == "run")["body"][0]["text"]
292
- assert "prime env install primeintellect/r2e-gym" in run and "uv run eval primeintellect/r2e-gym" in run and "eval.toml" not in run
293
-
294
-
295
- def test_multi_swe_bench_shows_the_issues_and_hides_the_fixing_pr():
296
- row = {"org": "facebook", "repo": "zstd", "number": 3942, "title": "Fix #3719: SECRET-PR-TITLE", "body": "SECRET-PR-BODY",
297
- "resolved_issues": {"title": ["zstd won't remove the file"], "body": ["Steps to reproduce"], "number": [3719]},
298
- "fix_patch": "SECRET-FIX", "test_patch": "SECRET-TEST-PATCH", "f2p_tests": {"t": "SECRET-F2P"}, "run_result": {"x": "SECRET-RUN"},
299
- "instance_id": "facebook__zstd-3942", "hints": "SECRET-HINT"}
300
- view, card, overview, roles, text = everything(verifiers_rows.Verifiers(), row, spec="PrimeIntellect/Multi-SWE-bench",
301
- splits=[{"config": "default", "split": "test"}])
302
- for s in ("SECRET-PR-TITLE", "SECRET-PR-BODY", "SECRET-FIX", "SECRET-TEST-PATCH", "SECRET-F2P", "SECRET-RUN", "SECRET-HINT"):
303
- assert s not in text, s
304
- assert view["title"] == "zstd won't remove the file" and roles["task"] == "resolved_issues"
305
- run = next(s for s in view["sections"] if s["id"] == "run")["body"][0]["text"]
306
- assert 'dataset_name = "PrimeIntellect/Multi-SWE-bench"' in run and 'id = "primeintellect/multiswe"' in run # not the taskset's default dataset
307
-
308
-
309
- def test_verifiers_v0_rows_show_the_prompt_and_withhold_the_answer():
310
- row = {"question": "What is 6*7?", "answer": "SECRET-42", "info": {"difficulty": "easy", "solution": "SECRET-STEPS"}, "task": "arith"}
311
- view, card, overview, roles, text = everything(verifiers_rows.Verifiers(), row, tags=["verifiers"])
312
- assert "SECRET-42" not in text and "SECRET-STEPS" not in text
313
- assert view["title"] == "What is 6*7?" and {"answer", "info.solution"} <= set(view["withheld"])
314
- assert "difficulty" in json.dumps(view["sections"])
315
- run = next(s for s in view["sections"] if s["id"] == "run")["body"][0]["text"]
316
- assert "prime env install <owner>/<environment>" in run # no environment named: says how to find it, invents none
317
-
318
-
319
- def test_verifiers_known_v0_environment_uses_the_legacy_runner(monkeypatch):
320
- monkeypatch.setattr(verifiers_rows, "_hub", lambda env_id: {"runtime": "VERIFIERS_V0"})
321
- view, *_ = everything(verifiers_rows.Verifiers(), {"prompt": "The community in Bruck was merged into it"}, spec="PrimeIntellect/Reverse-Text-RL")
322
- task = next(s for s in view["sections"] if s["id"] == "task")["body"][0]["data"]["turns"]
323
- assert task[0]["role"] == "system" and "<reversed_text>" in task[0]["text"]
324
- run = next(s for s in view["sections"] if s["id"] == "run")["body"][0]["text"]
325
- assert "uv run vf-eval reverse-text" in run and "prime env install primeintellect/reverse-text" in run
326
-
327
-
328
- def test_rule_induction_rows_hide_the_rule():
329
- row = {"rule_id": "red_cards", "label": "red cards", "family": "single_axis", "code": "return card.color == 'red'"}
330
- view, card, overview, roles, text = everything(verifiers_rows.Verifiers(), row, spec="nph4rd/eleusis-simple-rules", tags=["verifiers"])
331
- assert "red_cards" not in text and "red cards" not in text and "card.color" not in text
332
-
333
-
334
- # ── the rows adapter ─────────────────────────────────────────────────────────
335
- def test_search_never_looks_at_framework_answers():
336
- row = {"expected_action": {"content": "SECRET"}, "ref_patch": "SECRET", "exp_cal_state": "SECRET", "fix_patch": "SECRET", "prompt": "find me"}
337
- assert "SECRET" not in json.dumps(rows._searchable(row)) and "find me" in json.dumps(rows._searchable(row))
338
-
339
-
340
- def test_framework_rows_offer_their_own_framework_as_the_way_to_run():
341
- opt = nemogym.run_option({"resources_server": "mcqa"})
342
- assert rows.RowsAdapter().run_options(None, "train/0", {"_framework_run": opt}) == [opt]
343
- assert opt["ok"] is False and opt["why"] and "**Run it with NeMo Gym**" in opt["about"]
344
-
345
-
346
- def test_a_dataset_opens_on_its_train_split_not_its_leftovers():
347
- assert rows._first([{"config": "default", "split": "dropped"}, {"config": "default", "split": "resolved"}])["split"] == "resolved"
348
- assert rows._first([{"config": "a", "split": "test"}, {"config": "a", "split": "train"}, {"config": "b", "split": "train"}]) == {"config": "a", "split": "train"}
349
-
350
-
351
- def test_processors_hidden_union_covers_every_framework():
352
- assert {"expected_action", "sql", "fix_patch"} <= processors.HIDDEN
353
-
354
-
355
- def test_a_config_the_viewer_cant_read_is_read_from_its_files(monkeypatch):
356
- class BrokenViewer:
357
- kind, tok = "viewer", None
358
-
359
- def page(self, config, split, offset, length):
360
- raise rows.ViewerError("The dataset generation failed", 502)
361
-
362
- class FakeFiles:
363
- kind = "files"
364
-
365
- def __init__(self, spec, meta, tok):
366
- pass
367
-
368
- def splits(self):
369
- return [{"config": "rlvr1", "split": "train"}]
370
-
371
- def page(self, config, split, offset, length):
372
- return {"rows": [(0, {"responses_create_params": {"input": "hi"}}, [])], "total": 1, "features": []}
373
-
374
- monkeypatch.setattr(rows, "Files", FakeFiles)
375
- rows._split_files.clear()
376
- be, page = rows._first_page("org/blend", {"sha": "abc"}, BrokenViewer(), {"config": "rlvr1", "split": "train"})
377
- assert be.kind == "files" and page["total"] == 1
378
- be, _ = rows._first_page("org/blend", {"sha": "abc"}, BrokenViewer(), {"config": "rlvr1", "split": "train"}) # remembered
379
- assert be.kind == "files"
380
- with pytest.raises(rows.ViewerError): # a split the files don't have either: the viewer's error stands
381
- rows._first_page("org/blend", {"sha": "abc"}, BrokenViewer(), {"config": "other", "split": "train"})
382
-
383
-
384
- def test_blend_rows_are_pivots_only_when_they_carry_an_expert_step_and_placeholders_say_where_from():
385
- plain = {"responses_create_params": {"input": [{"role": "user", "content": "q"}]}, "expected_action": None, "ref_message": None,
386
- "agent_ref": {"type": "responses_api_agents", "name": "mcqa_simple_agent"}}
387
- assert not nemogym.is_pivot(plain) and nemogym.is_pivot(pivot_row())
388
- ph = {"question": "", "responses_create_params": {"input": [{"role": "user", "content": ""}]}, "expected_answer": "",
389
- "agent_ref": {"type": "responses_api_agents", "name": "math_with_judge_simple_agent"},
390
- "_hf_question_placeholder": {"mode": "canonical", "dataset": "Skywork/Skywork-OR1-RL-Data", "split": "math", "row": 246}}
391
- view, card, *_ = everything(nemogym.NemoGym(), ph)
392
- assert card["title"] == "A question from Skywork/Skywork-OR1-RL-Data (math, row 246)"
393
- assert "fills it in from" in json.dumps(view["sections"][0]["body"])
394
-
395
-
396
- def test_search_never_finds_a_row_through_its_withheld_answer(monkeypatch):
397
- """The dataset viewer's search matches every column; a hit found only through an answer is dropped, and the total
398
- then says nothing about how many rows hold it. Rows read from files are searched without their answers too."""
399
- from app.envs import rows, viewer
400
-
401
- found = [(0, {"prompt": "add 2 and 3", "reward_model": {"ground_truth": "5"}}, []),
402
- (1, {"prompt": "the answer is 5 apples", "reward_model": {"ground_truth": "7"}}, [])]
403
- monkeypatch.setattr(viewer, "search", lambda *a, **k: {"rows": found, "total": 900, "features": []})
404
- rows._broken.clear()
405
- page, _ = rows.Viewer("o/ds", None).search("default", "train", "5", 0)
406
- assert [i for i, _, _ in page["rows"]] == [1] and page["total"] == 1
407
- page, _ = rows.Viewer("o/ds", None).search("default", "train", "apple", 0) # a stemmed word still counts as visible
408
- assert [i for i, _, _ in page["rows"]] == [1]
409
-
410
- files = rows.Files.__new__(rows.Files)
411
- monkeypatch.setattr(rows.Files, "_scan", lambda self, c, s, o, match, what: ({"rows": [h for h in found if match(h[1])]}, None))
412
- assert [i for i, _, _ in files.search("d", "train", "7", 0)[0]["rows"]] == []
413
-
414
-
415
- def test_verifiers_v1_taskdata_system_prompt_is_part_of_the_conversation():
416
- row = {"prompt": "Solve the task", "system_prompt": "Use the supplied tools", "answer": "PRIVATE_REFERENCE"}
417
- view, _, _, _, text = everything(verifiers_rows.Verifiers(), row, tags=["verifiers"])
418
- turns = next(s for s in view["sections"] if s["id"] == "task")["body"][0]["data"]["turns"]
419
- assert [(t["role"], t["text"]) for t in turns] == [("system", "Use the supplied tools"), ("user", "Solve the task")]
420
- assert "PRIVATE_REFERENCE" not in text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_harbor_spec.py DELETED
@@ -1,340 +0,0 @@
1
- """Harbor's task.toml as Harbor 0.23 reads it (app/catalog.py), and what a task page shows of it (app/envs/harbor.py).
2
- No network: each task is a folder written to a temp dir, indexed by catalog._row, and served through stubs.
3
-
4
- What is checked: multi-step tasks read their instructions from steps/<name>/instruction.md; a grader in a container of
5
- its own is said to run there, built from tests/Dockerfile; legacy `memory = "4G"` is converted; tasks that ask for a
6
- GPU, a Windows container or no network for one phase can't run here (and say why); nested metadata and other tables are
7
- kept; a model judge is a real model call, not a mention; healthchecks and MCP servers are shown; a step's solution and
8
- the answers in its tests stay out.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import json
14
-
15
- import pytest
16
-
17
- from app import catalog
18
- from app.envs import contract as c
19
- from app.envs.harbor import HarborAdapter
20
-
21
- TB = """schema_version = "1.4"
22
- [task]
23
- name = "org/{name}"
24
- [metadata]
25
- difficulty = "easy"
26
- [verifier]
27
- timeout_sec = 300
28
- [agent]
29
- timeout_sec = 600
30
- [environment]
31
- docker_image = "python:3.12-slim"
32
- """
33
- TEST_SH = "#!/bin/bash\npytest /tests/test_outputs.py && echo 1 > /logs/verifier/reward.txt\n"
34
-
35
-
36
- def dump(x) -> str:
37
- return json.dumps(x, ensure_ascii=False)
38
-
39
-
40
- def build(tmp_path, files: dict[str, str], name: str = "t1"):
41
- """A task folder on disk, indexed the way build_index indexes it: (row, the pack's texts, file sizes, picks)."""
42
- root = tmp_path / name
43
- for rel, text in files.items():
44
- p = root / rel
45
- p.parent.mkdir(parents=True, exist_ok=True)
46
- p.write_text(text)
47
- sizes = {rel: len(text.encode()) for rel, text in files.items()}
48
- picks = catalog._pick(sizes)
49
- row, kept = catalog._row(0, name, tmp_path, sizes, picks, {})
50
- return row, kept, sizes, picks
51
-
52
-
53
- def view(monkeypatch, tmp_path, files: dict[str, str]):
54
- """The task page's view of a task folder, through HarborAdapter.task, with the index and the pack stubbed."""
55
- row, kept, sizes, picks = build(tmp_path, files)
56
- monkeypatch.setattr(catalog, "_task_row", lambda s, p, t=None: (row, {"sha": "abc", "info": {}}))
57
- monkeypatch.setattr(catalog, "_task_texts", lambda s, sha, p, t: (sizes, kept, set(picks)))
58
- monkeypatch.setattr(catalog, "_walked_root", lambda s, idx, p, t: ({}, []))
59
- monkeypatch.setattr(catalog, "collection", lambda s: None)
60
- adapter = HarborAdapter()
61
- return adapter.task(c.Env("dataset", "org/ds", {"sha": "abc"}, adapter), "t1"), row, kept
62
-
63
-
64
- def section(v, sid: str) -> dict:
65
- return next(s for s in v["sections"] if s["id"] == sid)
66
-
67
-
68
- def rows_of(sec: dict) -> dict:
69
- """A section's kv rows as {label: value} (the first of a repeated label)."""
70
- out: dict = {}
71
- for b in sec["blocks"]:
72
- if b["type"] == "kv":
73
- for k, v in b["rows"]:
74
- out.setdefault(k, v)
75
- return out
76
-
77
-
78
- # ── multi-step tasks ─────────────────────────────────────────────────────────
79
- MULTI = """version = "1.0"
80
- multi_step_reward_strategy = "final"
81
- [environment]
82
- docker_image = "python:3.12"
83
- [[steps]]
84
- name = "write"
85
- min_reward = 0.5
86
- [steps.agent]
87
- timeout_sec = 120
88
- [[steps]]
89
- name = "extend"
90
- """
91
-
92
-
93
- def test_multi_step_instructions_come_from_each_steps_folder(tmp_path, monkeypatch):
94
- files = {"task.toml": MULTI, "tests/test.sh": TEST_SH,
95
- "steps/write/instruction.md": "Implement greet(name) in /app/greeting.py.\n\nReturn a greeting.",
96
- "steps/write/tests/test.sh": "echo 1 > /logs/verifier/reward.txt\n",
97
- "steps/write/workdir/setup.sh": "mkdir -p /app\n",
98
- "steps/extend/instruction.md": "Extend greet with an uppercase flag.",
99
- "steps/extend/solution/solve.sh": "echo SECRET-STEP-SOLUTION\n"}
100
- assert catalog._wanted("steps/write/instruction.md", 100) and catalog._wanted("steps/write/tests/test.sh", 100)
101
- v, row, kept = view(monkeypatch, tmp_path, files)
102
- assert row["steps"] == 2 and row["spec"]["step_names"] == ["write", "extend"]
103
- assert row["spec"]["multi"] == {"strategy": "final", "min_reward": {"write": 0.5}}
104
- assert row["title"].startswith("Implement greet(name)") # no instruction.md: the first step's
105
- assert "steps/write/instruction.md" in kept and "steps/write/tests/test.sh" in kept
106
- assert row["run"] == "image" and row["solution"] # a step's solution counts as the task's reference solution
107
- steps = section(v, "steps")
108
- text = dump(steps)
109
- assert "Implement greet(name)" in text and "Extend greet with an uppercase flag." in text
110
- assert "`tests/test.sh`" in text and "`setup.sh`" in text and "reward ≥ 0.5" in text
111
- assert section(v, "task")["title"] == "The task, in 2 steps"
112
- assert "the last step's reward" in dump(section(v, "config"))
113
- assert "SECRET-STEP-SOLUTION" not in dump(v)
114
-
115
-
116
- def test_an_inline_step_instruction_is_flagged(tmp_path, monkeypatch):
117
- toml = 'version = "1.0"\n[environment]\ndocker_image = "x"\n[[steps]]\nname = "only"\ninstruction = "Do the thing."\n'
118
- v, row, _ = view(monkeypatch, tmp_path, {"task.toml": toml, "tests/test.sh": TEST_SH})
119
- text = dump(section(v, "steps"))
120
- assert "Do the thing." in text and "no longer reads it" in text
121
-
122
-
123
- # ── a grader in a container of its own ───────────────────────────────────────
124
- SEPARATE = TB + """[verifier.environment]
125
- cpus = 2
126
- memory = "4G"
127
- """
128
-
129
-
130
- def test_separate_verifier_is_said_to_run_in_its_own_container(tmp_path, monkeypatch):
131
- toml = SEPARATE.format(name="sep").replace("[verifier]\n", '[verifier]\nenvironment_mode = "separate"\n')
132
- files = {"task.toml": toml, "instruction.md": "Do it.", "tests/test.sh": TEST_SH,
133
- "tests/Dockerfile": "FROM python:3.12-slim\nRUN pip install pytest\nCOPY . /tests\n"}
134
- v, row, _ = view(monkeypatch, tmp_path, files)
135
- sep = row["verifier"]["separate"]
136
- assert sep["own"] and sep["dockerfile"] and sep["ok"] and not sep.get("image")
137
- runs = rows_of(section(v, "grading"))["Runs"]
138
- assert "separate container" in runs and "tests/Dockerfile" in runs and "same sandbox" not in runs
139
- assert "4 GB memory" in rows_of(section(v, "config"))["Grader's container"]
140
- assert ["Grader", "in its own container"] in v["glance"]
141
- assert catalog.runnable(row, {})["ok"]
142
-
143
-
144
- def test_separate_verifier_without_an_image_or_dockerfile_cant_run(tmp_path):
145
- toml = TB.format(name="sep2").replace('docker_image = "python:3.12-slim"\n', "") + '[verifier.environment]\ncpus = 1\n'
146
- row, *_ = build(tmp_path, {"task.toml": toml, "instruction.md": "x", "tests/test.sh": TEST_SH,
147
- "environment/Dockerfile": "FROM python:3.12\n"})
148
- r = catalog.runnable(row, {"environment/Dockerfile": "FROM python:3.12\n"})
149
- assert not r["ok"] and "container of its own" in r["why"]
150
-
151
-
152
- # ── what Harbor converts, and what an HF Sandbox can't do ────────────────────
153
- def test_legacy_memory_and_storage_strings_are_converted(tmp_path, monkeypatch):
154
- toml = TB.format(name="m").replace("[environment]\n", '[environment]\ncpus = 1\nmemory = "4G"\nstorage = "512M"\n')
155
- v, row, _ = view(monkeypatch, tmp_path, {"task.toml": toml, "instruction.md": "x", "tests/test.sh": TEST_SH})
156
- assert row["env"]["memory_mb"] == 4096 and row["env"]["storage_mb"] == 512
157
- assert rows_of(section(v, "config"))["Resources"] == "1 CPU · 4 GB memory · 512 MB storage"
158
-
159
-
160
- @pytest.mark.parametrize("patch,why", [
161
- ('[environment]\ngpus = 1\ngpu_types = ["L4"]\n', "GPU"),
162
- ('[environment]\nos = "Windows"\n', "Windows"),
163
- ('[environment]\ntpu = { type = "v6e", topology = "2x4" }\n', "TPU"),
164
- ])
165
- def test_what_hf_sandboxes_cant_give_is_not_runnable(tmp_path, patch, why):
166
- toml = TB.format(name="g").replace("[environment]\n", patch)
167
- row, *_ = build(tmp_path, {"task.toml": toml, "instruction.md": "x", "tests/test.sh": TEST_SH, "tests/test.bat": "@echo off\n"})
168
- r = catalog.runnable(row, {})
169
- assert row["run"] is None and not r["ok"] and why in r["why"]
170
-
171
-
172
- @pytest.mark.parametrize("section_,label", [("agent", "agent"), ("verifier", "grader")])
173
- def test_a_phase_without_internet_is_not_runnable(tmp_path, monkeypatch, section_, label):
174
- toml = TB.format(name="n").replace(f"[{section_}]\n", f'[{section_}]\nnetwork_mode = "no-network"\n')
175
- v, row, _ = view(monkeypatch, tmp_path, {"task.toml": toml, "instruction.md": "x", "tests/test.sh": TEST_SH})
176
- assert row["env"]["network"] is None and row["env"]["phases"] == {section_: "no-network"}
177
- r = catalog.runnable(row, {})
178
- assert not r["ok"] and f"the {label} asks for no internet access" in r["why"]
179
- assert f"{label}: no internet" in rows_of(section(v, "config"))["Network"]
180
- assert not v["_runnable"]["ok"]
181
-
182
-
183
- def test_an_allowlist_lists_its_hosts(tmp_path, monkeypatch):
184
- toml = TB.format(name="a").replace("[environment]\n", '[environment]\nnetwork_mode = "allowlist"\nallowed_hosts = ["pypi.org", "*.github.com"]\n')
185
- v, row, _ = view(monkeypatch, tmp_path, {"task.toml": toml, "instruction.md": "x", "tests/test.sh": TEST_SH})
186
- assert row["env"]["hosts"] == ["pypi.org", "*.github.com"] and not catalog.runnable(row, {})["ok"]
187
- assert "`pypi.org`" in rows_of(section(v, "config"))["Allowed hosts"]
188
-
189
-
190
- def test_a_task_toml_harbor_rejects_is_read_as_written_and_cant_run(tmp_path):
191
- toml = '[task]\nname = "no-org-slash"\n[environment]\ndocker_image = "x"\nmemory = "2G"\n'
192
- row, *_ = build(tmp_path, {"task.toml": toml, "instruction.md": "x", "tests/test.sh": TEST_SH})
193
- assert "task.name" in row["spec"]["harbor_error"] and row["env"]["memory_mb"] == 2048
194
- r = catalog.runnable(row, {})
195
- assert not r["ok"] and "Harbor itself rejects" in r["why"]
196
-
197
-
198
- # ── metadata ────────────────────────────────────────��────────────────────────
199
- NESTED = TB + """[metadata.repo2env]
200
- recipe = "tasksmith"
201
- fail_to_pass_count = 14
202
- gold_patch = "diff --git SECRET-PATCH"
203
- [metadata.repo2env.evaluation]
204
- status = "verified"
205
- [metadata.expected_result]
206
- value = "SECRET-NESTED"
207
- [scoring]
208
- difficulty_label = "hard"
209
- """
210
-
211
-
212
- def test_nested_metadata_and_other_tables_are_kept_answers_left_out(tmp_path, monkeypatch):
213
- v, row, kept = view(monkeypatch, tmp_path, {"task.toml": NESTED.format(name="nm"), "instruction.md": "x", "tests/test.sh": TEST_SH})
214
- assert row["meta"]["repo2env.recipe"] == "tasksmith" and row["meta"]["repo2env.evaluation.status"] == "verified"
215
- assert row["meta"]["scoring.difficulty_label"] == "hard"
216
- assert "repo2env.gold_patch" in row["withheld"] and "expected_result" in row["withheld"]
217
- assert not any("gold" in k or "expected" in k for k in row["meta"])
218
- meta = dump(section(v, "metadata"))
219
- assert "tasksmith" in meta and "verified" in meta and "hard" in meta
220
- assert "SECRET" not in dump(v) and "SECRET" not in kept["task.toml"] # nor in the raw task.toml shown
221
-
222
-
223
- def test_masked_toml_hides_multi_line_values_and_answer_tables():
224
- out = catalog._masked_toml('[metadata]\nexpected = [\n "a1",\n "a2",\n]\ntags = [\n "x",\n]\n[metadata.gold]\nv = 42\n'
225
- '[solution.env]\nFOO = "${FOO}"\n')
226
- assert "a1" not in out and "42" not in out and '"x"' in out and 'FOO = "${FOO}"' in out
227
- import tomllib
228
-
229
- assert tomllib.loads(out)["metadata"]["gold"] == {"v": "‹withheld›"}
230
-
231
-
232
- # ── a model judge is a model call ────────────────────────────────────────────
233
- GRADER_CALLS = """import os
234
- from openai import OpenAI
235
-
236
- client = OpenAI(base_url=os.environ["JUDGE_URL"])
237
- resp = client.chat.completions.create(model="gpt-4o", messages=[{"role": "user", "content": "grade"}])
238
- """
239
- GRADER_MENTIONS = """# compare with an OpenAI-style judge later, maybe
240
- def grade(answer: str, judge: bool = False) -> float:
241
- llm_judge = None
242
- return float(answer.strip() == open('/app/out.txt').read().strip())
243
- """
244
-
245
-
246
- @pytest.mark.parametrize("grader,judge", [(GRADER_CALLS, True), (GRADER_MENTIONS, False)])
247
- def test_judge_is_a_real_model_call_not_a_mention(tmp_path, grader, judge):
248
- row, *_ = build(tmp_path, {"task.toml": TB.format(name="j"), "instruction.md": "x",
249
- "tests/test.sh": "python3 /tests/grade.py > /logs/verifier/reward.txt\n", "tests/grade.py": grader})
250
- assert row["verifier"]["judge"] is judge
251
-
252
-
253
- def test_judge_from_the_graders_model_key():
254
- toml = TB.format(name="k") + '[verifier.env]\nOPENAI_API_KEY = "${OPENAI_API_KEY}"\n'
255
- assert catalog.verifier_kind("", "", toml_text=toml, sources={})["judge"]
256
- assert not catalog.verifier_kind("", "", ["SOME_API_KEY"], toml_text=TB.format(name="k"), sources={})["judge"] # a key's name isn't a judge
257
- js = "import OpenAI from 'openai';\nconst r = await client.responses.create({model: 'x', input: 'y'});\n"
258
- assert catalog._calls_model(js) and not catalog._calls_model("import openai # unused\nprint('judge')\n")
259
-
260
-
261
- # ── what the agent gets besides the instruction ──────────────────────────────
262
- def test_healthcheck_mcp_servers_and_settings_are_shown(tmp_path, monkeypatch):
263
- toml = TB.format(name="h").replace("[agent]\n", '[agent]\nuser = "learner"\n') + """workdir = "/testbed"
264
- skills_dir = "/skills"
265
- [environment.healthcheck]
266
- command = "test -f /ready"
267
- interval_sec = 2
268
- retries = 5
269
- [[environment.mcp_servers]]
270
- name = "docs"
271
- transport = "streamable-http"
272
- url = "http://docs:8000/mcp"
273
- [[verifier.collect]]
274
- command = "pg_dump > /logs/db.sql"
275
- service = "db"
276
- [[artifacts]]
277
- source = "/app/out"
278
- exclude = ["*.pyc"]
279
- """
280
- v, row, _ = view(monkeypatch, tmp_path, {"task.toml": toml, "instruction.md": "x", "tests/test.sh": TEST_SH, "trajectory.json": "{}"})
281
- assert row["env"]["healthcheck"] and row["env"]["mcp"] == ["docs"] and row["env"]["workdir"] == "/testbed"
282
- assert row["verifier"]["collect"] == 1 and row["spec"]["agent_user"] == "learner" and row["spec"]["artifacts"] == ["/app/out"]
283
- cfg = rows_of(section(v, "config"))
284
- assert "`test -f /ready`" in cfg["Healthcheck"] and "every 2 s" in cfg["Healthcheck"] and "5 failed tries" in cfg["Healthcheck"]
285
- assert cfg["MCP server"].startswith("`docs` · streamable-http") and "`http://docs:8000/mcp`" in cfg["MCP server"]
286
- assert cfg["Users"] == "agent as `learner`" and cfg["Working directory"] == "`/testbed`" and "`/skills`" in cfg["Skills"]
287
- assert "`pg_dump > /logs/db.sql` in `db`" in cfg["Collect hooks"] and "`/app/out`" in cfg["Artifacts"]
288
- assert cfg["Name"] == "`org/h`" and "Prior context" in cfg
289
- assert any(b["type"] == "disclose" and b["label"].startswith("task.toml") for b in section(v, "config")["blocks"])
290
- assert "trajectory.json" in dump(section(v, "task"))
291
-
292
-
293
- def test_summary_facets_name_what_sets_tasks_apart(tmp_path, monkeypatch):
294
- plain, *_ = build(tmp_path, {"task.toml": TB.format(name="p"), "instruction.md": "x", "tests/test.sh": TEST_SH}, "p")
295
- multi, *_ = build(tmp_path, {"task.toml": MULTI, "tests/test.sh": TEST_SH, "steps/write/instruction.md": "a",
296
- "steps/extend/instruction.md": "b"}, "m")
297
- rows = [plain, multi]
298
- catalog._group(rows)
299
- for r in rows:
300
- r.pop("_paras", None)
301
- r["brief"] = ""
302
- idx = {"tasks": rows, "summary": catalog.summarize(rows), "info": {}, "sha": "abc"}
303
- monkeypatch.setattr(catalog, "index_status", lambda spec, token=None: {"state": "done", "index": idx})
304
- adapter = HarborAdapter()
305
- env = c.Env("dataset", "org/ds", {"sha": "abc"}, adapter)
306
- assert "setup" in [f["key"] for f in adapter.summary(env)["facets"]]
307
- cards = {x["ref"]: x for x in adapter.tasks(env, everything=True)["cards"]}
308
- assert cards["m"]["facets"]["setup"] == ["Multi-step"] and cards["p"]["facets"]["setup"] == []
309
- assert "2 steps" in cards["m"]["chips"]
310
-
311
-
312
- # ── answers stay out of steps ────────────────────────────────────────────────
313
- @pytest.mark.parametrize("rel,text,withheld", [
314
- ("steps/s1/solution/solve.sh", None, True),
315
- ("steps/s1/solution/notes.md", "anything", True),
316
- ("steps/s1/tests/test.sh", "pytest", False),
317
- ("steps/s1/tests/grade.py", "import json", False),
318
- ("steps/s1/tests/expected.json", None, True),
319
- ("steps/s1/tests/data.csv", None, True),
320
- ("steps/s1/tests/check.py", "GOLD = {'gold_answer': 42}", True),
321
- ("steps/s1/instruction.md", "Write it.", False),
322
- ])
323
- def test_step_answers_are_withheld(rel, text, withheld):
324
- assert catalog._withheld(rel, text) is withheld
325
-
326
-
327
- def test_a_step_solution_is_never_served(tmp_path, monkeypatch):
328
- files = {"task.toml": MULTI, "tests/test.sh": TEST_SH, "steps/write/instruction.md": "a", "steps/extend/instruction.md": "b",
329
- "steps/write/tests/test.sh": 'EXPECTED="SECRET-42"\n[ "$(cat /app/x)" = "$EXPECTED" ] && echo 1 > /logs/verifier/reward.txt\n',
330
- "steps/write/solution/solve.sh": "echo SECRET-SOLVE\n"}
331
- row, kept, sizes, picks = build(tmp_path, files)
332
- assert "steps/write/solution/solve.sh" not in kept and "SECRET-42" not in kept["steps/write/tests/test.sh"]
333
- monkeypatch.setattr(catalog, "_task_row", lambda s, p, t=None: (row, {"sha": "abc", "info": {}}))
334
- monkeypatch.setattr(catalog, "_task_texts", lambda s, sha, p, t: (sizes, kept, set(picks)))
335
- monkeypatch.setattr(catalog, "_walked_root", lambda s, idx, p, t: ({}, []))
336
- monkeypatch.setattr(catalog, "_fetch", lambda *a, **k: pytest.fail("a withheld file was fetched"))
337
- got = catalog.task_file("org/ds", "t1", "steps/write/solution/solve.sh")
338
- assert got.get("withheld") and "text" not in got
339
- tree = {f["path"]: f["withheld"] for f in catalog.task("org/ds", "t1")["tree"]}
340
- assert tree["steps/write/solution/solve.sh"] is True and tree["steps/write/instruction.md"] is False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_judge.py DELETED
@@ -1,99 +0,0 @@
1
- """A Harbor task's variables and model-graded tasks (app/judge.py), on the patterns real datasets use (MiMo's
2
- conversions, skilltrainbench, data-agent, WildClawBench): what each variable gets in a rollout here, in which phase,
3
- and that nothing the agent sees is a key of ours."""
4
-
5
- from __future__ import annotations
6
-
7
- from app import judge
8
-
9
- MIMO_GENERAL = '''
10
- [verifier.env]
11
- GA_JUDGE_KEY = "${HF_TOKEN}"
12
- GA_JUDGE_URL = "${MIMO_JUDGE_URL:-https://router.huggingface.co/v1}"
13
- GA_JUDGE_MODEL = "${MIMO_TEXT_JUDGE:-thinkingmachines/Inkling}"
14
- VERIFY_DETERMINISTIC = "1"
15
- '''
16
- MIMO_WEBDEV = '''
17
- [verifier.env]
18
- WEBDEV_JUDGE_KEY = "${HF_TOKEN}"
19
- WEBDEV_JUDGE_URL = "${MIMO_JUDGE_URL:-https://router.huggingface.co/v1}"
20
- WEBDEV_JUDGE_MODEL = "${MIMO_VISION_JUDGE:-meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8}"
21
- '''
22
- HEALTHBENCH = '''
23
- [verifier.env]
24
- STBENCH_JUDGE_BASE_URL = "${STBENCH_JUDGE_BASE_URL}"
25
- STBENCH_JUDGE_API_KEY = "${STBENCH_JUDGE_API_KEY}"
26
- STBENCH_JUDGE_MODEL = "${STBENCH_JUDGE_MODEL}"
27
- STBENCH_JUDGE_TEMPERATURE = "${STBENCH_JUDGE_TEMPERATURE}"
28
- '''
29
- HLE = '''
30
- [verifier.env]
31
- OPENAI_API_KEY = "${OPENAI_API_KEY}"
32
- OPENAI_BASE_URL = "${OPENAI_BASE_URL:-}"
33
- JUDGE_MODEL = "o3-mini-2025-01-31"
34
- '''
35
- DATA_AGENT = '''
36
- [environment.env]
37
- HF_TOKEN = "${HF_TOKEN}"
38
- '''
39
- NEEDS_SERVICE = '''
40
- [environment.env]
41
- DATABASE_URL = "${DATABASE_URL}"
42
- '''
43
-
44
-
45
- def test_a_model_graded_task_gets_the_relay_in_its_grading_phase_only():
46
- p = judge.plan(MIMO_GENERAL)
47
- assert p["judge"] == {"requested": "thinkingmachines/Inkling", "vision": False, "anthropic": False, "keys": ["GA_JUDGE_KEY", "GA_JUDGE_URL"]}
48
- assert p["missing"] == [] and p["agent_keys"] == []
49
- b = judge.bindings(p, relay="https://x.hf.space/capture", capability="jcap", judge="thinkingmachines/Inkling")
50
- assert b["verifier"]["GA_JUDGE_KEY"] == "jcap" and b["verifier"]["HF_TOKEN"] == "jcap"
51
- assert b["verifier"]["GA_JUDGE_URL"] == "https://x.hf.space/capture/v1" # never the real router: the relay holds the token
52
- assert b["verifier"]["GA_JUDGE_MODEL"] == "thinkingmachines/Inkling"
53
- assert "jcap" not in b["environment"].values()
54
-
55
-
56
- def test_a_vision_grader_asks_for_a_vision_judge():
57
- assert judge.plan(MIMO_WEBDEV)["judge"]["vision"] is True
58
-
59
-
60
- def test_a_judge_without_defaults_gets_the_picked_model_and_deterministic_settings():
61
- p = judge.plan(HEALTHBENCH)
62
- assert p["judge"] and p["missing"] == []
63
- b = judge.bindings(p, relay="https://r", capability="c", judge="openai/gpt-oss-120b")["verifier"]
64
- assert b["STBENCH_JUDGE_MODEL"] == "openai/gpt-oss-120b" and b["STBENCH_JUDGE_TEMPERATURE"] == "0"
65
- assert b["STBENCH_JUDGE_BASE_URL"] == "https://r/v1" and b["STBENCH_JUDGE_API_KEY"] == "c"
66
-
67
-
68
- def test_an_openai_client_with_an_empty_default_url_is_pointed_at_the_relay():
69
- p = judge.plan(HLE)
70
- assert p["judge"]["requested"] == "o3-mini-2025-01-31"
71
- b = judge.bindings(p, relay="https://r", capability="c", judge="deepseek-ai/DeepSeek-V4-Pro")["verifier"]
72
- assert b["OPENAI_BASE_URL"] == "https://r/v1" and b["OPENAI_API_KEY"] == "c" # not api.openai.com with our key
73
-
74
-
75
- def test_a_key_the_agent_would_see_is_left_empty():
76
- p = judge.plan(DATA_AGENT)
77
- assert p["judge"] is None and p["agent_keys"] == ["HF_TOKEN"] and p["missing"] == []
78
- assert judge.bindings(p)["environment"]["HF_TOKEN"] == ""
79
-
80
-
81
- def test_a_variable_nothing_here_provides_stops_the_task():
82
- p = judge.plan(NEEDS_SERVICE)
83
- assert p["missing"] == ["DATABASE_URL"]
84
-
85
-
86
- def test_without_a_judge_nothing_is_bound_to_a_relay():
87
- b = judge.bindings(judge.plan(MIMO_GENERAL))
88
- assert b["verifier"]["GA_JUDGE_KEY"] == "" and "GA_JUDGE_URL" not in b["verifier"] # no capability: an empty key, the task's defaults
89
-
90
-
91
- def test_the_table_says_how_each_variable_is_filled():
92
- rows = dict(judge.table(judge.plan(MIMO_GENERAL)))
93
- assert any("judge relay" in v for v in rows.values())
94
- assert not any("hf_" in v for v in rows.values())
95
-
96
-
97
- def test_a_broken_toml_is_not_a_crash():
98
- p = judge.plan("this is = = not toml")
99
- assert p["invalid"] and p["vars"] == []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_live.py DELETED
@@ -1,409 +0,0 @@
1
- """Live Spaces and the MCP bridge (no network: the Hub and the Spaces are stubbed).
2
-
3
- What is checked: the explorer only ever talks to a Space's own hf.space host (never a redirect elsewhere, never a
4
- private Space); waking and restarting do only what they should; playground sessions belong to whoever started them
5
- and are capped; answers stay out of the Task API browser; the bridge speaks MCP (handshake, notifications, errors,
6
- sessions), refuses cross-origin pages, and turns servers without tools into reset/step/state tools.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import json
12
- import time
13
- from types import SimpleNamespace
14
-
15
- import httpx
16
- import pytest
17
- from fastapi.testclient import TestClient
18
-
19
- from app import catalog, main, mcp_bridge, spaces_live as live
20
-
21
- client = TestClient(main.app, base_url="https://testserver")
22
-
23
-
24
- def space_info(stage="RUNNING", sub="org-env", private=False, base_path=None):
25
- card = SimpleNamespace(to_dict=lambda: ({"base_path": base_path} if base_path else {}))
26
- return SimpleNamespace(private=private, card_data=card, subdomain=sub, sdk="docker", tags=["openenv"],
27
- runtime=SimpleNamespace(stage=stage, hardware="cpu-basic"))
28
-
29
-
30
- @pytest.fixture(autouse=True)
31
- def fresh(monkeypatch):
32
- catalog._memo.clear()
33
- live._sessions.clear()
34
- live._woken.clear()
35
- mcp_bridge._hits.clear()
36
- mcp_bridge._clients.clear()
37
- yield
38
- for s in list(live._sessions.values()):
39
- s.ws = None
40
- live._sessions.clear()
41
-
42
-
43
- def stub_hub(monkeypatch, **kw):
44
- monkeypatch.setattr(catalog, "_api", lambda token=None: SimpleNamespace(space_info=lambda spec, expand=None: space_info(**kw)))
45
-
46
-
47
- # ── the Space's record ───────────────────────────────────────────────────────
48
- def test_only_the_spaces_own_hf_space_host_is_used(monkeypatch):
49
- stub_hub(monkeypatch, sub="evil.example.com/x")
50
- assert live.record("org/env")["host"] == ""
51
- catalog._memo.clear()
52
- stub_hub(monkeypatch, sub="org-env")
53
- assert live.record("org/env")["host"] == "https://org-env.hf.space"
54
-
55
-
56
- def test_private_spaces_are_refused(monkeypatch):
57
- stub_hub(monkeypatch, private=True)
58
- with pytest.raises(PermissionError):
59
- live.record("org/env")
60
- assert client.get("/api/spaces/org/env/live").status_code == 404
61
-
62
-
63
- def test_base_path_must_be_a_plain_path(monkeypatch):
64
- stub_hub(monkeypatch, base_path="//evil.com")
65
- assert live.record("org/env")["base_path"] is None
66
- catalog._memo.clear()
67
- stub_hub(monkeypatch, base_path="/web")
68
- assert live.record("org/env")["base_path"] == "/web"
69
-
70
-
71
- def test_bad_space_ids_are_refused():
72
- for bad in ("../x/y", "a/b/c"):
73
- assert client.get(f"/api/spaces/{bad}/live").status_code in (400, 404)
74
-
75
-
76
- def test_redirects_off_the_space_are_never_followed(monkeypatch):
77
- stub_hub(monkeypatch)
78
- rec = live.record("org/env")
79
- seen = []
80
-
81
- def fake_get(url, **kw):
82
- seen.append(url)
83
- if url.endswith("/web"):
84
- return httpx.Response(302, headers={"location": "http://169.254.169.254/latest/meta-data"}, request=httpx.Request("GET", url))
85
- if url.endswith("/start-page"):
86
- return httpx.Response(302, headers={"location": "/web/"}, request=httpx.Request("GET", url))
87
- return httpx.Response(200, headers={"content-type": "text/html"}, text="<html>", request=httpx.Request("GET", url))
88
-
89
- monkeypatch.setattr(live._client, "get", fake_get)
90
- assert live._get_here(rec, "/web") is None # off-host: dropped, not fetched
91
- assert not any("169.254" in u for u in seen)
92
- r = live._get_here(rec, "/start-page") # on-host: followed
93
- assert r is not None and r.url.path == "/web/"
94
-
95
-
96
- def test_hub_dataset_names_map_back_safely():
97
- assert live.hub_dataset("/data/FineEnvs__MiMo-V2.6-RL-harbor-terminal") == "FineEnvs/MiMo-V2.6-RL-harbor-terminal"
98
- assert live.hub_dataset("/app/prepared/datasets/train") is None
99
- assert live.hub_dataset("/data/a__b__c") is None
100
- assert live.hub_dataset("/data/__x") is None
101
-
102
-
103
- # ── wake and restart ─────────────────────────────────────────────────────────
104
- def test_wake_only_sleeping_spaces_and_at_most_once_a_minute(monkeypatch):
105
- calls = []
106
- monkeypatch.setattr(live.httpx, "post", lambda url, **kw: calls.append(url) or httpx.Response(302))
107
- stub_hub(monkeypatch, stage="SLEEPING")
108
- assert live.wake("org/env")["woken"] is True
109
- assert live.wake("org/env")["woken"] is True # again within a minute: not sent again
110
- assert calls == ["https://huggingface.co/spaces/org/env/start"]
111
- catalog._memo.clear()
112
- stub_hub(monkeypatch, stage="RUNTIME_ERROR")
113
- with pytest.raises(live.SpaceError):
114
- live.wake("org/env")
115
- catalog._memo.clear()
116
- stub_hub(monkeypatch, stage="RUNNING")
117
- assert live.wake("org/env") == {"stage": "RUNNING", "woken": False}
118
- assert len(calls) == 1
119
-
120
-
121
- def test_restart_needs_a_signed_in_visitor():
122
- assert client.post("/api/spaces/org/env/restart", json={}).status_code == 401
123
-
124
-
125
- def test_restart_without_write_access_says_so(monkeypatch):
126
- from huggingface_hub.errors import HfHubHTTPError
127
-
128
- def refuse(_spec):
129
- raise HfHubHTTPError("no", response=httpx.Response(403, request=httpx.Request("POST", "https://huggingface.co")))
130
-
131
- space = space_info(stage="RUNTIME_ERROR")
132
- monkeypatch.setattr(catalog, "_api", lambda token=None: SimpleNamespace(space_info=lambda spec, expand=None: space, restart_space=refuse))
133
- with pytest.raises(live.SpaceError) as e:
134
- live.restart("org/env", "hf_x")
135
- assert e.value.status == 403
136
-
137
-
138
- def test_wake_and_play_refuse_cross_site_posts():
139
- for path, body in (("/api/spaces/org/env/wake", {}), ("/api/spaces/org/env/play", {"op": "start"})):
140
- r = client.post(path, json=body, headers={"Origin": "https://evil.example"})
141
- assert r.status_code == 403
142
-
143
-
144
- # ── the Task API browser ─────────────────────────────────────────────────────
145
- def test_task_api_leaves_answers_out(monkeypatch):
146
- stub_hub(monkeypatch)
147
- monkeypatch.setattr(live, "probe", lambda spec, fresh=False: {"running": True, "task_api": {"env": "e", "splits": []}})
148
-
149
- def fake_post(url, json=None, **kw):
150
- task = {"task_id": "t1", "prompt": "find x", "ground_truth": "42", "meta": {"expected_output": "x", "level": 2},
151
- "target_latex": "a^2", "target_language": "fr", "options": [{"answer": "b", "text": "B"}]}
152
- body = {"tasks": [task]} if url.endswith("/task_range") else {"task": task}
153
- return httpx.Response(200, json=body, request=httpx.Request("POST", url))
154
-
155
- monkeypatch.setattr(live._client, "post", fake_post)
156
- for out in (live.tasks("org/env", "train", 0, 999)["tasks"][0], live.task("org/env", "train", 0)["task"]):
157
- text = json.dumps(out)
158
- assert "42" not in text and "expected_output" not in text and "a^2" not in text and '"answer"' not in text
159
- assert out["prompt"] == "find x" and out["target_language"] == "fr"
160
- assert live.tasks("org/env", "train", 0, 999)["stop"] == 50 # a page is at most 50 tasks
161
-
162
-
163
- # ── playground sessions ──────────────────────────────────────────────────────
164
- def running(monkeypatch):
165
- stub_hub(monkeypatch)
166
- monkeypatch.setattr(live, "probe", lambda spec, fresh=False: {"running": True, "openenv": True, "step_api": True, "mcp": None})
167
-
168
-
169
- def test_sessions_belong_to_who_started_them(monkeypatch):
170
- running(monkeypatch)
171
- sid = live.start("org/env", "web:1.1.1.1")["session"]
172
- with pytest.raises(live.SpaceError) as e:
173
- live.act(sid, "web:2.2.2.2", "state")
174
- assert e.value.status == 410
175
-
176
-
177
- def test_sessions_are_capped_per_owner_and_in_total(monkeypatch):
178
- running(monkeypatch)
179
- ids = [live.start("org/env", "web:a")["session"] for _ in range(live.PER_OWNER + 2)]
180
- mine = [s for s in live._sessions.values() if s.owner == "web:a"]
181
- assert len(mine) == live.PER_OWNER and ids[-1] in live._sessions and ids[0] not in live._sessions
182
- monkeypatch.setattr(live, "TOTAL", len(live._sessions))
183
- with pytest.raises(live.SpaceError) as e:
184
- live.start("org/env", "web:b")
185
- assert e.value.status == 429
186
-
187
-
188
- def test_idle_sessions_end(monkeypatch):
189
- running(monkeypatch)
190
- sid = live.start("org/env", "web:a")["session"]
191
- live._sessions[sid].used = time.time() - live.IDLE - 1
192
- with pytest.raises(live.SpaceError):
193
- live.act(sid, "web:a", "state")
194
- assert sid not in live._sessions
195
-
196
-
197
- def test_play_rejects_bad_operations_and_tool_names(monkeypatch):
198
- running(monkeypatch)
199
- sid = live.start("org/env", "web:a")["session"]
200
- with pytest.raises(live.SpaceError):
201
- live.act(sid, "web:a", "rm -rf")
202
- with pytest.raises(live.SpaceError):
203
- live.act(sid, "web:a", "call", {"name": "../../x y"})
204
- r = client.post("/api/spaces/org/env/play", json={"op": "nope"})
205
- assert r.status_code == 422
206
-
207
-
208
- def test_play_sessions_do_not_cross_spaces(monkeypatch):
209
- running(monkeypatch)
210
- r = client.post("/api/spaces/org/env/play", json={"op": "start"})
211
- sid = r.json()["session"]
212
- r = client.post("/api/spaces/org/other/play", json={"op": "state", "session": sid})
213
- assert r.status_code == 400
214
-
215
-
216
- def test_websocket_replies_become_results_and_errors(monkeypatch):
217
- running(monkeypatch)
218
- sent = []
219
-
220
- class FakeWS:
221
- def __init__(self, replies):
222
- self.replies = list(replies)
223
-
224
- def send(self, m):
225
- sent.append(json.loads(m))
226
-
227
- def recv(self, timeout=None):
228
- return json.dumps(self.replies.pop(0))
229
-
230
- def close(self):
231
- pass
232
-
233
- sid = live.start("org/env", "web:a")["session"]
234
- live._sessions[sid].ws = FakeWS([{"type": "observation", "data": {"observation": {"x": 1}, "reward": 0.5, "done": True}},
235
- {"type": "error", "data": {"message": "bad action"}},
236
- {"type": "mcp", "data": {"jsonrpc": "2.0", "id": 1, "result": {"content": [{"type": "text", "text": "ok"}]}}}])
237
- out = live.act(sid, "web:a", "reset", {"seed": 1})
238
- assert out["result"]["reward"] == 0.5 and sent[0] == {"type": "reset", "data": {"seed": 1}}
239
- with pytest.raises(live.SpaceError) as e:
240
- live.act(sid, "web:a", "step", {"a": 1})
241
- assert "bad action" in str(e.value)
242
- out = live.act(sid, "web:a", "call", {"name": "look", "arguments": {"h": 1}})
243
- assert out["result"]["content"][0]["text"] == "ok" and sent[-1]["type"] == "mcp" and sent[-1]["data"]["method"] == "tools/call"
244
-
245
-
246
- def test_images_in_observations_are_found():
247
- import base64
248
-
249
- png = base64.b64encode(b"\x89PNG" + b"0" * 400).decode()
250
- parts = live.image_parts({"observation": {"image_base64": png, "image_format": "png", "text": "hi"}})
251
- assert parts and parts[0][0] == "image/png"
252
- assert live.image_parts({"observation": {"text": "x" * 500}}) == []
253
-
254
-
255
- # ── the MCP bridge ───────────────────────────────────────────────────────────
256
- INFO = {"running": True, "stage": "RUNNING", "openenv": True, "step_api": True, "mcp": None,
257
- "schema": {"action": {"type": "object", "properties": {"latex": {"type": "string"}, "metadata": {"type": "object"}}}},
258
- "task_api": {"env": "e", "splits": [{"name": "train", "num_tasks": 3}]}}
259
-
260
-
261
- def rpc(method, params=None, rid=1, sid=None, **headers):
262
- h = {"Content-Type": "application/json", **({"Mcp-Session-Id": sid} if sid else {}), **headers}
263
- body = {"jsonrpc": "2.0", "method": method, **({"id": rid} if rid is not None else {}), **({"params": params} if params is not None else {})}
264
- return client.post("/mcp/org/env", content=json.dumps(body), headers=h)
265
-
266
-
267
- @pytest.fixture
268
- def bridge(monkeypatch):
269
- stub_hub(monkeypatch)
270
- monkeypatch.setattr(live, "probe", lambda spec, fresh=False: INFO)
271
- calls = []
272
- monkeypatch.setattr(live, "act", lambda sid, owner, op, data=None: calls.append((op, data)) or {"result": {"observation": {"ok": 1}, "reward": 1.0}})
273
- return calls
274
-
275
-
276
- def test_bridge_handshake_and_session(bridge):
277
- r = rpc("initialize", {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "t", "version": "1"}})
278
- assert r.status_code == 200 and r.headers["mcp-session-id"]
279
- res = r.json()["result"]
280
- assert res["protocolVersion"] == "2025-06-18" and res["capabilities"]["tools"] == {"listChanged": False}
281
- assert rpc("initialize", {"protocolVersion": "1999-01-01"}).json()["result"]["protocolVersion"] == mcp_bridge.PROTOCOLS[0]
282
- assert rpc("notifications/initialized", rid=None).status_code == 202
283
- assert rpc("ping").json()["result"] == {}
284
-
285
-
286
- def test_bridge_offers_episode_and_task_tools_for_servers_without_tools(bridge):
287
- tools = {t["name"]: t for t in rpc("tools/list").json()["result"]["tools"]}
288
- assert set(tools) == {"reset", "step", "state", "list_splits", "list_tasks", "get_task"}
289
- assert set(tools["step"]["inputSchema"]["properties"]) == {"latex"} # the action schema, metadata dropped
290
- r = rpc("tools/call", {"name": "step", "arguments": {"latex": "x^2"}}).json()["result"]
291
- assert bridge[-1] == ("step", {"latex": "x^2"}) and "content" in r and not r.get("isError")
292
-
293
-
294
- def test_bridge_tool_errors_are_results_the_agent_can_read(bridge, monkeypatch):
295
- def boom(*a, **k):
296
- raise live.SpaceError("the Space didn't answer within 180 s", 504)
297
-
298
- monkeypatch.setattr(live, "act", boom)
299
- r = rpc("tools/call", {"name": "step", "arguments": {}}).json()
300
- assert r["result"]["isError"] is True and "180 s" in r["result"]["content"][0]["text"]
301
- assert rpc("tools/call", {"name": "nope", "arguments": {}}).json()["result"]["isError"] is True
302
-
303
-
304
- def test_bridge_rejects_what_it_should(bridge):
305
- assert rpc("resources/read").json()["error"]["code"] == -32601
306
- assert client.post("/mcp/org/env", content="{nope", headers={"Content-Type": "application/json"}).status_code == 400
307
- assert client.post("/mcp/org/env", content=json.dumps([{"jsonrpc": "2.0", "id": 1, "method": "ping"}])).status_code == 400
308
- assert client.post("/mcp/org/env", content=json.dumps({"id": 1, "method": "ping"})).status_code == 400
309
- assert rpc("ping", **{"Origin": "https://evil.example"}).status_code == 403
310
- assert client.post("/mcp/org/env", content="x" * (mcp_bridge.MAX_BODY + 1)).status_code == 413
311
- assert client.get("/mcp/org/env").status_code == 405
312
- assert client.delete("/mcp/org/env").status_code == 204
313
-
314
-
315
- def test_bridge_is_rate_limited(bridge, monkeypatch):
316
- monkeypatch.setattr(mcp_bridge, "RATE", (5, 60))
317
- codes = [rpc("ping").status_code for _ in range(8)]
318
- assert codes[:5] == [200] * 5 and 429 in codes[5:]
319
-
320
-
321
- def test_bridge_wakes_a_sleeping_space_instead_of_failing(monkeypatch):
322
- stub_hub(monkeypatch, stage="SLEEPING")
323
- monkeypatch.setattr(live, "probe", lambda spec, fresh=False: {"running": False, "stage": "SLEEPING"})
324
- woke = []
325
- monkeypatch.setattr(live, "wake", lambda spec: woke.append(spec) or {"stage": "APP_STARTING"})
326
- r = rpc("tools/list").json()
327
- assert r["error"]["code"] == -32002 and woke == ["org/env"]
328
-
329
-
330
- def test_bridge_passes_through_mcp_content_and_drops_output_schemas(monkeypatch):
331
- stub_hub(monkeypatch)
332
- info = {**INFO, "mcp": [{"name": "look", "inputSchema": {"type": "object"}, "outputSchema": {"type": "object"}}]}
333
- monkeypatch.setattr(live, "probe", lambda spec, fresh=False: info)
334
- monkeypatch.setattr(live, "act", lambda *a, **k: {"result": {"content": [{"type": "text", "text": "hi", "annotations": None, "meta": None}],
335
- "structured_content": {"result": "hi"}, "is_error": False}})
336
- tools = rpc("tools/list").json()["result"]["tools"]
337
- assert all("outputSchema" not in t for t in tools)
338
- assert {t["name"] for t in tools} >= {"look", "reset", "state"} and "step" not in {t["name"] for t in tools}
339
- r = rpc("tools/call", {"name": "look", "arguments": {}}).json()["result"]
340
- assert r["content"] == [{"type": "text", "text": "hi"}] and r["structuredContent"] == {"result": "hi"} and "isError" not in r
341
-
342
-
343
- # ── a server's own routes (NeMo Gym, ORS, custom) ────────────────────────────
344
- OPENAPI = {"paths": {
345
- "/guess": {"post": {"summary": "Guess", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/G"}}}}}},
346
- "/tasks/{task_id}": {"get": {"summary": "A task"}},
347
- "//evil.example/x": {"get": {}}, "/a/../b": {"get": {}}, "/q?x=1": {"get": {}}, "/web/x": {"get": {}}, "/reset": {"post": {}},
348
- "/self": {"post": {"requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/S"}}}}}},
349
- }, "components": {"schemas": {"G": {"type": "object", "properties": {"word": {"type": "string", "title": "Word"}}, "required": ["word"]},
350
- "S": {"type": "object", "properties": {"next": {"$ref": "#/components/schemas/S"}}}}}}
351
-
352
-
353
- def test_routes_are_plain_paths_on_non_openenv_servers_only():
354
- routes = live._api_routes(OPENAPI, openenv=False)
355
- assert {(r["method"], r["path"]) for r in routes} == {("POST", "/guess"), ("GET", "/tasks/{task_id}"), ("POST", "/self"), ("POST", "/reset")}
356
- guess = next(r for r in routes if r["path"] == "/guess")
357
- assert guess["schema"]["properties"]["word"] == {"type": "string", "title": "Word"} and guess["schema"]["required"] == ["word"]
358
- assert next(r for r in routes if r["path"] == "/tasks/{task_id}")["params"] == ["task_id"]
359
- assert live._api_routes(OPENAPI, openenv=True) == [] # an OpenEnv server is used through reset/step and tools
360
- json.dumps(routes) # a self-referencing schema is cut, not looped
361
-
362
-
363
- def test_route_parameters_cannot_leave_the_route():
364
- info = {"api": live._api_routes(OPENAPI, openenv=False)}
365
- assert live.route_url(info, "GET", "/tasks/{task_id}", {"task_id": "t-1.2"}) == "/tasks/t-1.2"
366
- for bad in ("../admin", "a/b", "x y", "", "%2e%2e"):
367
- with pytest.raises(live.SpaceError):
368
- live.route_url(info, "GET", "/tasks/{task_id}", {"task_id": bad})
369
- with pytest.raises(live.SpaceError):
370
- live.route_url(info, "POST", "/not/published", {})
371
- with pytest.raises(live.SpaceError):
372
- live.route_url(info, "DELETE", "/guess", {})
373
-
374
-
375
- def test_route_calls_keep_their_cookies_and_withhold_answers_from_gets(monkeypatch):
376
- stub_hub(monkeypatch)
377
- monkeypatch.setattr(live, "probe", lambda spec, fresh=False: {"running": True, "openenv": False, "api": live._api_routes(OPENAPI, openenv=False)})
378
- seen = []
379
-
380
- def handler(request: httpx.Request):
381
- seen.append((request.method, request.url.path, request.headers.get("cookie")))
382
- if request.url.path == "/guess":
383
- return httpx.Response(200, json={"output": "ok"}, headers={"set-cookie": "session=abc; path=/"})
384
- return httpx.Response(200, json={"task_id": "t1", "prompt": "p", "answer": "SECRET", "meta": {"ground_truth": "TRUTHVAL"}})
385
-
386
- real = live.SpaceClient
387
- monkeypatch.setattr(live, "SpaceClient", lambda **kw: real(transport=httpx.MockTransport(handler), **kw))
388
- sid = live.start("org/env", "web:a")["session"]
389
- out = live.act(sid, "web:a", "http", {"method": "POST", "path": "/guess", "body": {"word": "crane"}})["result"]
390
- assert out["status"] == 200 and out["json"] == {"output": "ok"}
391
- out = live.act(sid, "web:a", "http", {"method": "GET", "path": "/tasks/{task_id}", "params": {"task_id": "t1"}})["result"]
392
- assert "SECRET" not in json.dumps(out) and "TRUTHVAL" not in json.dumps(out) and set(out["withheld"]) == {"answer", "ground_truth"}
393
- assert seen[0][0] == "POST" and seen[1][2] == "session=abc" # the session's own cookie, kept server-side
394
- with pytest.raises(live.SpaceError):
395
- live.act(sid, "web:a", "http", {"method": "PUT", "path": "/guess"})
396
-
397
-
398
- def test_a_reset_step_server_still_lists_its_tools(monkeypatch):
399
- """No MCP tools of its own (OpenEnv answers "Environment does not support MCP"): the page gets exactly what the MCP
400
- bridge offers an agent, step taking the server's own action fields. Tool servers use the same descriptor."""
401
- info = {"running": True, "stage": "RUNNING", "step_api": True, "mcp": None, "task_api": None,
402
- "schema": {"action": {"type": "object", "properties": {"latex": {"type": "string", "description": "Predicted LaTeX"}}, "required": ["latex"]}}}
403
- monkeypatch.setattr(live, "probe", lambda spec, fresh=False: info)
404
- tools = {t["name"]: t for t in client.get("/api/spaces/o/latex/live").json()["bridge_tools"]}
405
- assert set(tools) == {"reset", "step", "state"}
406
- assert "latex" in json.dumps(tools["step"]["inputSchema"])
407
- assert tools == {t["name"]: t for t in mcp_bridge._tools(info)} # the page and the bridge never disagree
408
- monkeypatch.setattr(live, "probe", lambda spec, fresh=False: {**info, "mcp": [{"name": "look", "inputSchema": {"type": "object"}}]})
409
- assert {t["name"] for t in client.get("/api/spaces/o/geo/live").json()["bridge_tools"]} == {"look", "reset", "state"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_mimo.py DELETED
@@ -1,187 +0,0 @@
1
- """The MiMo release behind the environment contract (app/envs/mimo.py, app/mimo): its tasks read the way the MiMo RL
2
- Environment Explorer reads them, run as their Harbor conversion or on the release's own harness, and linked to their
3
- Harbor twins both ways (no network: the release's rows and task views are stubbed; its browsing index is the real one).
4
-
5
- What is checked: the MiMo adapter claims the release and nothing else; tiles, scoped facets and the map are coherent;
6
- every task is a card; a raw row's ref opens the same task; the Harbor twin is the default unless its grader can't score
7
- here; a rubric task's judge is required and must come from the offered pool; the MiMo runner gets the task and the
8
- explorer's fields, never a token in its record; a twin links back to the release; a MiMo rollout's page serves its
9
- events, scrubbed for the public; custom data refuses paths outside the workspace, and raw files are sandboxed.
10
- """
11
-
12
- from __future__ import annotations
13
-
14
- import json
15
- import time
16
-
17
- import pytest
18
- from fastapi.testclient import TestClient
19
-
20
- from app import auth, main, models, settings, store
21
- from app.envs import contract as c, mimo, registry
22
- from app.mimo import catalog as mcat
23
-
24
- client = TestClient(main.app, base_url="https://testserver")
25
- TOKEN = "hf_mimo_secret_token_0004"
26
- SPEC = mimo.SPEC
27
- GENERAL = "s3k_0052_accounting_audit_tax_en_t2_rl_008"
28
-
29
-
30
- def cookie() -> dict[str, str]:
31
- session = {"token": TOKEN, "name": "erin", "avatar": None, "via": "token", "exp": time.time() + 3600}
32
- return {"Cookie": f"{auth.COOKIE}={auth._box.encrypt(json.dumps(session).encode()).decode()}"}
33
-
34
-
35
- def view(tid: str) -> dict:
36
- rec = mcat.record(tid)
37
- d = rec["d"]
38
- v = {"id": tid, "domain": d, "title": rec["t"], "short_title": rec["t"], "facets": rec["f"], "brief": "Do the thing.", "meta": [],
39
- "image": "docker.io/x:y", "runnable": True, "environment": {"cwd": "/work", "image": "docker.io/x:y"}, "prompt": {"parts": [["task", "Do the thing."]], "task_is_brief": True}}
40
- if d == "general":
41
- v["verify"] = {"kind": "rubric", "summary": "Checked.", "needs_judge": "text", "formula": "Σ",
42
- "checks": [{"id": "c1", "tier": "critical", "method": "llm", "weight": 2, "question": "Is it right?", "files": []}]}
43
- else:
44
- v["verify"] = {"kind": "tests", "summary": "Tests.", "files": [{"path": "t.py", "added": 1, "removed": 0, "new": True}], "formula": "1 or 0"}
45
- return v
46
-
47
-
48
- @pytest.fixture(autouse=True)
49
- def stubs(monkeypatch):
50
- ids = [e["id"] for e in mcat.index()["envs"]]
51
- by_domain: dict[str, list[str]] = {}
52
- for e in mcat.index()["envs"]:
53
- by_domain.setdefault(e["d"], []).append(e["id"])
54
- monkeypatch.setattr(mcat, "rows", lambda: {tid: {"domain": d} for d, tids in by_domain.items() for tid in tids})
55
- monkeypatch.setattr(mcat, "view", lambda tid: view(tid) if tid in set(ids) else None)
56
- mimo._order.cache_clear(); mimo._position.cache_clear()
57
- monkeypatch.setattr(registry, "_meta", lambda kind, spec, token: {"id": spec, "sha": "abc", "restricted": False})
58
- monkeypatch.setattr(registry, "_choice", {})
59
- monkeypatch.setattr("app.mimo.runner.domains.run_defaults", lambda tid, d: {"steps": 100, "timeout_min": 30, "max_tokens": None})
60
- monkeypatch.setattr(models, "get", lambda mid: {"id": mid, "tools": True, "provider": "x"})
61
- monkeypatch.setattr(models, "catalog", lambda: {"text_judges": [{"id": "judge/text"}], "vision_judges": [{"id": "judge/vision"}], "agents": []})
62
- monkeypatch.setattr(settings, "_org_members", lambda: set())
63
- yield
64
- mimo._order.cache_clear(); mimo._position.cache_clear()
65
-
66
-
67
- def twin_opt(monkeypatch, warnings=()):
68
- """A Harbor twin for every task, whose grader may need a key it won't get here."""
69
- from app.envs import harbor
70
-
71
- monkeypatch.setattr(mimo.MiMoAdapter, "_twin", lambda self, tid, d: {"dataset": f"FineEnvs/MiMo-V2.6-RL-harbor-{d}", "path": f"tasks/{tid}"})
72
- toml = "[verifier.env]\n" + "".join(f'{k} = "${{{k}}}"\n' for k in warnings)
73
- monkeypatch.setattr("app.catalog.task", lambda d, p, t=None: {"runnable": {"ok": True, "how": "image"}, "verifier": {"env": list(warnings)},
74
- "env": {"image": "img"}, "bytes": 1, "toml": toml})
75
- return harbor
76
-
77
-
78
- def test_the_release_is_read_by_the_mimo_adapter_only():
79
- a = mimo.MiMoAdapter()
80
- assert a.detect("dataset", {"id": SPEC}) == 1.0 and a.detect("dataset", {"id": "someone/else"}) == 0
81
- assert registry.resolve("dataset", SPEC).adapter.id == "mimo"
82
-
83
-
84
- def test_tiles_facets_and_map_are_coherent():
85
- s = client.get(f"/api/env/{SPEC}").json()
86
- keys = [f["key"] for f in s["facets"]]
87
- assert len(keys) == len(set(keys)), "one facet per key"
88
- tiles = {t["value"] for t in s["tiles"]["items"]}
89
- assert tiles == {"Code", "Webdev", "Cyber", "Music", "General"} and s["tiles"]["key"] == "domain"
90
- for f in s["facets"]:
91
- assert set(f["scope"]) <= tiles | {"*"}
92
- assert set(s["map"]["by_tile"]) == tiles and all(k in keys for k in s["map"]["by_tile"].values())
93
- assert s["order"] == "shuffle" and s["overview"][0]["collapsed"] is True
94
-
95
-
96
- def test_every_task_is_a_card():
97
- d = client.get(f"/api/env/{SPEC}/tasks?all=1").json()
98
- assert d["total"] == len(mcat.index()["envs"]) == len(d["cards"])
99
- assert all(x["facets"]["domain"] and x["color"] for x in d["cards"])
100
-
101
-
102
- def test_a_raw_rows_ref_opens_the_same_task(monkeypatch):
103
- twin_opt(monkeypatch)
104
- first = mimo._order()["code"][6]
105
- v = client.get(f"/api/env/{SPEC}/task?ref=code/train/6").json()
106
- assert v["ref"] == first and v["nav"]["index"] == 6
107
- assert client.get(f"/api/env/{SPEC}/task?ref=code/train/999999").status_code == 404
108
- assert client.get(f"/api/env/{SPEC}/task?ref=nope").status_code == 404
109
- # by its own ref, its row is an alias (rollouts recorded against the row are listed with it)
110
- assert {"env": SPEC, "ref": "code/train/6"} in client.get(f"/api/env/{SPEC}/task?ref={first}").json()["aliases"]
111
-
112
-
113
- def test_harbor_is_the_default_and_a_model_graded_twin_gets_a_judge(monkeypatch):
114
- twin_opt(monkeypatch)
115
- code = mimo._order()["code"][0]
116
- opts = client.get(f"/api/env/{SPEC}/task?ref={code}").json()["run"]["options"]
117
- assert [(o["runner"], o["default"]) for o in opts] == [("harbor", True), ("mimo", False)]
118
- assert not any(f["key"] == "judge" for f in opts[0]["fields"])
119
- twin_opt(monkeypatch, warnings=["GA_JUDGE_KEY"]) # its grader calls a model: Harbor still runs it, through the relay
120
- opts = client.get(f"/api/env/{SPEC}/task?ref={GENERAL}").json()["run"]["options"]
121
- assert [(o["runner"], o["default"]) for o in opts] == [("harbor", True), ("mimo", False)]
122
- for o in opts:
123
- judge = next(f for f in o["fields"] if f["key"] == "judge")
124
- assert judge["type"] == "model" and judge["pool"] == "text_judges" and judge["required"]
125
-
126
-
127
- def test_mimo_runs_get_the_task_and_checked_fields(monkeypatch):
128
- twin_opt(monkeypatch)
129
- from app.mimo.runner import core
130
-
131
- seen = {}
132
-
133
- def fake_submit(user, token, task, model, provider, judge, **kw):
134
- seen.update(user=user, token=token, task=task, judge=judge, **kw)
135
- return store.create({"id": "20260103-000000-mimo01", "user": user, "task_id": task["id"], "status": "queued", **kw["extra"]})
136
-
137
- monkeypatch.setattr(core, "submit", fake_submit)
138
- post = lambda **kw: client.post("/api/runs", json={"dataset": SPEC, "path": GENERAL, "runner": "mimo", "model": "m/x", **kw}, headers=cookie())
139
- assert post().status_code == 400 # a rubric task needs a judge
140
- assert post(fields={"judge": "not/offered"}).status_code == 400 # from the pool only
141
- assert post(fields={"judge": "judge/text", "thinking": "loud"}).status_code == 400
142
- r = post(fields={"judge": "judge/text", "thinking": "high", "temperature": 0.5})
143
- assert r.status_code == 200, r.text
144
- assert seen["task"]["id"] == GENERAL and seen["judge"] == "judge/text" and seen["params"] == {"thinking": "high", "temperature": 0.5}
145
- assert seen["extra"] == {"dataset": SPEC, "path": GENERAL, "env": SPEC, "adapter": "mimo", "runner": "mimo", "restricted": False, "sha": "abc"}
146
- assert TOKEN not in json.dumps(store.get("20260103-000000-mimo01"))
147
-
148
-
149
- def test_a_harbor_twin_links_back_to_the_release():
150
- tid = mimo._order()["code"][3]
151
- from app.envs.processors import _slug
152
-
153
- env = c.Env("dataset", "FineEnvs/MiMo-V2.6-RL-harbor-code", {}, None)
154
- links = mimo.harbor_to_mimo(env, f"tasks/{_slug(tid)}", {})
155
- assert links and links[0]["rel"] == "same" and links[0]["href"] == f"/t/{SPEC}/{tid}"
156
- assert (SPEC, tid) in mimo.harbor_aliases(env, f"tasks/{_slug(tid)}")
157
- assert mimo.harbor_to_mimo(c.Env("dataset", "someone/else", {}, None), f"tasks/{_slug(tid)}", {}) == []
158
-
159
-
160
- def test_a_mimo_rollouts_page_serves_its_events_scrubbed_for_the_public():
161
- rid = "20260103-000000-mimo02"
162
- store.create({"id": rid, "user": "erin", "task_id": GENERAL, "dataset": SPEC, "path": GENERAL, "runner": "mimo", "status": "done", "reward": 1.0,
163
- "visibility": "public", "title": "T", "model": "m/x", "endpoint": {"base_url": "https://erin.example/v1", "host": "erin.example"}})
164
- store.append_events(rid, [{"i": 0, "t": 0, "kind": "text", "text": "hello from erin at https://erin.example/v1"}])
165
- mine = client.get(f"/api/runs/{rid}", headers=cookie()).json()
166
- assert mine["events"][0]["text"].startswith("hello from erin") and mine["run"]["is_owner"]
167
- pub = client.get(f"/api/runs/{rid}").json()
168
- assert "erin" not in json.dumps(pub["events"]) and "erin" not in json.dumps(pub["run"])
169
- assert client.get(f"/api/runs/{rid}?after=1").json()["events"] == []
170
-
171
-
172
- def test_custom_data_stays_inside_the_workspace(monkeypatch):
173
- def outside(tid, rel):
174
- raise PermissionError(rel)
175
-
176
- monkeypatch.setattr(mcat, "workspace_path", outside)
177
- assert client.get(f"/api/env/{SPEC}/data?ref={GENERAL}&part=preview&path=../../etc/passwd").status_code == 404
178
- assert client.get(f"/api/env/{SPEC}/raw?ref={GENERAL}&f=../../etc/passwd").status_code == 404
179
- assert client.get(f"/api/env/{SPEC}/data?ref={GENERAL}&part=nothing").status_code == 404
180
-
181
-
182
- def test_raw_files_are_sandboxed(monkeypatch, tmp_path):
183
- page = tmp_path / "index.html"
184
- page.write_text("<script>alert(1)</script>")
185
- monkeypatch.setattr(mcat, "workspace_path", lambda tid, rel: page)
186
- r = client.get(f"/api/env/{SPEC}/raw?ref={GENERAL}&f=index.html")
187
- assert r.status_code == 200 and r.headers["content-security-policy"] == "sandbox" and r.headers["x-content-type-options"] == "nosniff"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_model_relay.py DELETED
@@ -1,23 +0,0 @@
1
- from fastapi.testclient import TestClient
2
-
3
- from app import main, runner, runtime
4
- from app.mimo.runner import core
5
-
6
-
7
- def test_local_relay_precedes_capture_catchall_and_keeps_capability_auth(monkeypatch):
8
- monkeypatch.setattr(runner, "_service", None)
9
- client = TestClient(runner.service().capture.app)
10
- monkeypatch.setattr(core, "by_cap", lambda cap: None)
11
- assert client.get("/rlx/llm/invalid/health").status_code == 404
12
- assert client.post("/rlx/llm/invalid/v1/chat/completions", json={}).status_code == 404
13
- assert client.post("/v1/chat/completions", json={"model": "test", "messages": []}).status_code == 401
14
- async def relay(cap, request):
15
- return {"route": "scoped-relay"}
16
- monkeypatch.setattr(main, "llm_proxy", relay)
17
- assert client.post("/rlx/llm/test/v1/chat/completions", json={}).json() == {"route": "scoped-relay"}
18
-
19
-
20
- def test_local_and_deployed_capabilities_are_redacted_from_logs():
21
- for prefix in ("api", "rlx"):
22
- msg = f"POST /{prefix}/llm/SECRET-CAP/v1/chat/completions"
23
- assert "SECRET-CAP" not in runtime.SECRET_PATH.sub(lambda m: (m.group(1) or m.group(2)) + "[hidden]", msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_nemo_runner.py DELETED
@@ -1,73 +0,0 @@
1
- import json
2
-
3
- import pytest
4
-
5
- from app import nemo_runner as runner
6
- from app.envs import nemogym
7
-
8
-
9
- def row():
10
- return {"responses_create_params": {"input": [{"role": "user", "content": "Look up A."},
11
- {"type": "reasoning", "summary": [{"type": "summary_text", "text": "Previous reasoning"}]}],
12
- "tools": [{"type": "function", "name": "lookup", "parameters": {"type": "object"}}]},
13
- "expected_action": {"type": "function_call", "name": "lookup", "arguments": '{"id":"PRIVATE-ANSWER"}'}}
14
-
15
-
16
- def test_responses_input_preserved_and_answer_withheld():
17
- data = row()
18
- out = runner.responses_request(data)
19
- assert out["input"] == data["responses_create_params"]["input"]
20
- assert "PRIVATE-ANSWER" not in json.dumps(out)
21
-
22
-
23
- def test_provider_hosted_tools_cannot_execute():
24
- data = row()
25
- data["responses_create_params"]["tools"] = [{"type": "mcp", "server_url": "https://example.com", "require_approval": "never"}]
26
- with pytest.raises(ValueError, match="Only function"):
27
- runner.responses_request(data)
28
-
29
-
30
- def test_native_comparator_scores_arguments_and_calls_before_text():
31
- data = row()
32
- agent = nemogym.agent_of({}, "nvidia/Nemotron-RL-Agentic-Function-Calling-Pivot-v1")
33
- expected = runner.validate(data, agent)
34
- response = {"output": [{"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "Looking it up"}]},
35
- {"type": "function_call", "name": "lookup", "arguments": '{"id":"PRIVATE-ANSWER"}'}]}
36
- assert runner.score(expected, runner.prediction(response))[0] == 1
37
- response["output"][1]["arguments"] = '{"id":"wrong"}'
38
- assert runner.score(expected, runner.prediction(response))[0] == 0
39
- response["output"][1]["arguments"] = 'not json'
40
- assert runner.score(expected, runner.prediction(response))[0] == 0
41
-
42
-
43
- def test_invalid_reference_does_not_leak_through_errors():
44
- data = row()
45
- data["expected_action"] = {"type": "PRIVATE-ANSWER"}
46
- with pytest.raises(ValueError) as e:
47
- runner.validate(data, nemogym.agent_of({}, "nvidia/Nemotron-RL-Agentic-Function-Calling-Pivot-v1"))
48
- assert "PRIVATE-ANSWER" not in str(e.value)
49
-
50
-
51
- def test_unknown_nemo_server_stays_native():
52
- with pytest.raises(ValueError, match="not hosted"):
53
- runner.validate(row(), {"known": True, "resources_server": "another_server"})
54
-
55
-
56
- def test_provider_failure_inside_http_200_is_not_a_zero_reward():
57
- with pytest.raises(ValueError, match="did not produce a prediction"):
58
- runner.prediction({"status": "failed", "error": {"message": "invalid schema"}, "output": []})
59
- with pytest.raises(ValueError, match="invalid Responses"):
60
- runner.prediction({"message": "not a response"})
61
-
62
-
63
- def test_invalid_strict_declaration_is_relaxed_without_changing_parameters():
64
- data = row()
65
- tool = data["responses_create_params"]["tools"][0]
66
- tool["strict"] = True
67
- original = json.dumps(data)
68
- request = runner.responses_request(data)
69
- assert request["tools"][0]["strict"] is False
70
- assert request["tools"][0]["parameters"] == tool["parameters"]
71
- assert json.dumps(data) == original
72
- tool["parameters"].update(properties={}, required=[], additionalProperties=False)
73
- assert runner.responses_request(data)["tools"][0]["strict"] is True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_remote_mcp.py DELETED
@@ -1,62 +0,0 @@
1
- """Standard MCP sessions preserve cookies, negotiated headers and reply identity."""
2
- import json
3
-
4
- import httpx
5
- import pytest
6
-
7
- from app import spaces_live as live
8
-
9
-
10
- def test_standard_mcp_initializes_isolates_and_closes_sessions():
11
- seen, next_id = [], []
12
- def handle(request):
13
- seen.append(request)
14
- if request.method == "DELETE":
15
- return httpx.Response(204)
16
- message = json.loads(request.content)
17
- if message["method"] == "initialize":
18
- sid = str(len(next_id) + 1)
19
- next_id.append(sid)
20
- return httpx.Response(200, headers={"Mcp-Session-Id": sid, "Set-Cookie": f"episode={sid}; Path=/"},
21
- json={"jsonrpc": "2.0", "id": message["id"], "result": {"protocolVersion": "2025-06-18"}})
22
- sid = request.headers["mcp-session-id"]
23
- assert request.headers["mcp-protocol-version"] == "2025-06-18"
24
- assert request.headers["cookie"] == f"episode={sid}"
25
- if message["method"] == "notifications/initialized":
26
- assert "id" not in message
27
- return httpx.Response(202)
28
- # A progress notification is not the result; the actual event has multi-line data.
29
- event = json.dumps({"jsonrpc": "2.0", "id": message["id"], "result": {"episode": sid}}, indent=2)
30
- text = 'data: {"jsonrpc":"2.0","method":"notifications/progress"}\n\n' + '\n'.join('data: '+line for line in event.splitlines())+'\n\n'
31
- return httpx.Response(200, headers={"Content-Type": "text/event-stream"}, text=text)
32
- with live.SpaceClient(transport=httpx.MockTransport(handle)) as a, live.SpaceClient(transport=httpx.MockTransport(handle)) as b:
33
- first = live.RemoteMCP({"host": "https://fixture.hf.space"}, a)
34
- second = live.RemoteMCP({"host": "https://fixture.hf.space"}, b)
35
- assert first.call("tools/call", {}, 2) == {"episode": "1"}
36
- assert second.call("tools/call", {}, 2) == {"episode": "2"}
37
- assert first.call("tools/call", {}, 2) == {"episode": "1"}
38
- first.close()
39
- second.close()
40
- assert len(next_id) == 2
41
- assert [r.headers["mcp-session-id"] for r in seen if r.method == "DELETE"] == ["1", "2"]
42
-
43
-
44
- def test_expired_mcp_session_does_not_replay_or_reinitialize():
45
- calls = []
46
- def handle(request):
47
- calls.append(request)
48
- return httpx.Response(404)
49
- with live.SpaceClient(transport=httpx.MockTransport(handle)) as c:
50
- remote = live.RemoteMCP({"host": "https://fixture.hf.space"}, c)
51
- remote.initialized, remote.session = True, "existing"
52
- for _ in range(2):
53
- with pytest.raises(live.SpaceError) as error:
54
- remote.call("tools/call", {"name": "step"}, 2)
55
- assert error.value.status == 410
56
- assert len(calls) == 1
57
-
58
-
59
- def test_mcp_rejects_an_unrelated_rpc_response():
60
- with live.SpaceClient(transport=httpx.MockTransport(lambda r: httpx.Response(200, json={"jsonrpc": "2.0", "id": "other", "result": {}}))) as c:
61
- with pytest.raises(live.SpaceError, match="matching"):
62
- live.RemoteMCP({"host": "https://fixture.hf.space"}, c).call("tools/list", {}, 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_search_api.py DELETED
@@ -1,629 +0,0 @@
1
- """/api/search and /api/search/tasks (app/search_api.py) over a catalog snapshot (no network: the listing is fixture rows).
2
-
3
- What is checked: declared framework taxonomy independent of editorial collections; metric-only trending with
4
- separate featured picks; hidden environments left out of everything at query time; every answer (order, total, every
5
- facet's counts) equal to a plain-Python port of the page's own filtering over the same rows, for hundreds of queries
6
- (a parity test), through a published snapshot and through the live fallback alike; search text never read as FTS
7
- syntax; the visitor's own private datasets merged in; cache headers and ETags.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import gzip
13
- import json
14
- import random
15
- import shutil
16
- import time
17
- from pathlib import Path
18
-
19
- import pytest
20
- from fastapi import FastAPI
21
- from fastapi.testclient import TestClient
22
-
23
- from app import catalog, config, indexer, search_api, snapshot
24
-
25
- app = FastAPI()
26
- app.include_router(search_api.router)
27
- client = TestClient(app)
28
-
29
- TAGS = ["code", "agents", "math", "terminal", "swe", "sql", "vision", "a\"quote", "c++", "日本語", "NEAR", "tool-use"]
30
- COLLS = [{"id": "fineenvs", "group": "FineEnvs on OpenEnv", "icon": "globe", "color": "amber", "about": "ours",
31
- "ids": ["space:fe/geo", "space:fe/latex", "space:fe/ors-one", "space:fe/plain"]},
32
- {"id": "terminal", "group": "Terminal-Bench", "icon": "terminal", "color": "cyber", "about": "terminal tasks",
33
- "ids": ["tb/bench-1", "tb/bench-2", "space:fe/plain"]}] # fe/plain is in both: the first one counts
34
- PINS = ["space:fe/latex", "ds/d07"]
35
- HIDDEN = {"ds/d13", "space:sp/s05"}
36
-
37
-
38
- def rows_fixture(n_ds: int = 30, n_sp: int = 40, seed: int = 7) -> list[dict]:
39
- """Listing rows as catalog.environments() returns them (datasets' framework already resolved), varied enough to
40
- exercise every facet, sort and tie."""
41
- rnd = random.Random(seed)
42
- fws = ["harbor", "harbor", "harbor", "verifiers", "nemo-gym", "openenv-data", "verl", "rows", None]
43
- out = []
44
- for i in range(n_ds):
45
- fw = fws[i % len(fws)]
46
- tasks = rnd.choice([None, None, 0, 5, 99, 100, 999, 1000, 4000]) if fw in ("harbor", None) else None
47
- out.append({"id": f"ds/d{i:02d}", "key": f"ds/d{i:02d}", "kind": "dataset", "framework": fw,
48
- "heading": rnd.choice([None, f"Bench {i}", "Terminal tasks", "Data analysis"]),
49
- "brief": rnd.choice(["", "Agents solve SQL tasks in a sandbox.", "Hard math, graded by tests.", "Tools and C++ builds."]),
50
- "downloads": rnd.choice([0, 5, 50, 9999, 20000, 5]), "likes": rnd.choice([0, 1, 1, 3, 40]),
51
- "trending": rnd.choice([0, 0, 1, 2, 5]), "updated": f"2026-0{rnd.randint(1, 9)}-1{rnd.randint(0, 9)}T10:00:00+00:00",
52
- "created": rnd.choice([None, "2025-01-02T00:00:00.123+00:00", f"2026-0{rnd.randint(1, 9)}-01T00:00:00Z"]),
53
- "badges": rnd.choice([[], ["RL Environment"], ["Benchmark"], ["RL Environment", "Benchmark"]]),
54
- "tags": rnd.sample(TAGS, rnd.randint(0, 4)), "private": False, "gated": False,
55
- "indexed": {"tasks": tasks, "graded": {"txt": 3}, "image": 1} if tasks is not None else None})
56
- out += [{"id": "tb/bench-1", "key": "tb/bench-1", "kind": "dataset", "framework": "harbor", "heading": "TB one", "brief": "Terminal",
57
- "downloads": 100, "likes": 2, "trending": 3, "badges": ["Benchmark"], "tags": ["terminal"], "indexed": {"tasks": 89},
58
- "updated": "2026-10-01T00:00:00+00:00"},
59
- {"id": "tb/bench-2", "key": "tb/bench-2", "kind": "dataset", "framework": "verifiers", "heading": None, "brief": "x" * 40,
60
- "downloads": 100, "likes": 2, "trending": 3, "badges": [], "tags": [], "indexed": None}]
61
- for i in range(n_sp):
62
- fw = rnd.choice(["openenv", "openenv", "openenv", "ors", "space"])
63
- out.append({"id": f"sp/s{i:02d}", "key": f"space:sp/s{i:02d}", "kind": "space", "framework": fw, "openenv": fw == "openenv",
64
- "heading": rnd.choice([None, f"Game {i}", "Wordle"]), "brief": rnd.choice(["", "Play a game.", "An OCR env."]),
65
- "downloads": 0, "likes": rnd.choice([0, 1, 2, 2, 10]), "trending": rnd.choice([0, 1, 1, 4]),
66
- "updated": rnd.choice([None, f"2026-0{rnd.randint(1, 9)}-0{rnd.randint(1, 9)}T00:00:00+00:00"]),
67
- "created": f"2025-1{rnd.randint(0, 2)}-01T00:00:00+00:00", "stage": rnd.choice(["RUNNING", "SLEEPING", None, "RUNTIME_ERROR"]),
68
- "mcp": rnd.random() < 0.3, "openenv_version": rnd.choice([None, None, "0.2.1", "0.2.3"]) if fw == "openenv" else None,
69
- "badges": ["OpenEnv"] if fw == "openenv" else ["ORS"] if fw == "ors" else [], "tags": rnd.sample(TAGS, rnd.randint(0, 3)),
70
- "private": False, "gated": False, "hardware": "cpu-basic", "manifest": "openenv.yaml" if fw == "openenv" else None})
71
- for sid, fw in (("fe/geo", "openenv"), ("fe/latex", "space"), ("fe/ors-one", "ors"), ("fe/plain", "space")):
72
- out.append({"id": sid, "key": f"space:{sid}", "kind": "space", "framework": fw, "openenv": fw == "openenv", "heading": sid,
73
- "brief": "FineEnvs server", "downloads": 0, "likes": 1, "trending": 0, "stage": "RUNNING", "mcp": True,
74
- "badges": ["OpenEnv"] if fw == "openenv" else ["ORS"] if fw == "ors" else [], "tags": ["agents"]})
75
- out.append({"id": "priv/secret", "key": "priv/secret", "kind": "dataset", "framework": "harbor", "private": True, "heading": "Secret",
76
- "brief": "", "tags": [], "badges": [], "downloads": 0, "likes": 0, "trending": 99})
77
- return out
78
-
79
-
80
- ROWS = rows_fixture()
81
-
82
-
83
- def write_index(spec: str, tasks: list[dict], restricted: bool = False) -> None:
84
- idx = {"spec": spec, "sha": "abc", "built": 1.0, "version": catalog.INDEX_VERSION, "info": {"restricted": restricted},
85
- "tasks": tasks, "summary": {}}
86
- catalog.atomic_write(catalog._index_path(spec), gzip.compress(json.dumps(idx).encode()))
87
-
88
-
89
- TASKS = {"ds/d00": [{"path": "tasks/sql-join", "title": "Join two tables in SQL", "brief": "Write a query.", "category": "sql",
90
- "difficulty": "easy", "tags": ["sql"], "verifier": {"kind": "txt"}, "env": {}, "run": "image"},
91
- {"path": "tasks/foo-only", "title": "Foo the bar", "brief": "Nothing else.", "category": "misc", "tags": [],
92
- "verifier": {}, "env": {}}],
93
- "ds/d13": [{"path": "tasks/hidden-one", "title": "Join hidden tables", "brief": "", "tags": [], "verifier": {}, "env": {}}],
94
- "priv/secret": [{"path": "tasks/s", "title": "Join secret tables", "brief": "", "tags": [], "verifier": {}, "env": {}}]}
95
-
96
-
97
- @pytest.fixture(autouse=True)
98
- def stubs(monkeypatch, tmp_path):
99
- monkeypatch.setattr(catalog, "collections", lambda: COLLS)
100
- monkeypatch.setattr(catalog, "pinned", lambda: list(PINS))
101
- monkeypatch.setattr(catalog, "hidden", lambda: set(HIDDEN))
102
- monkeypatch.setattr(catalog, "environments", lambda include_hidden=False: [r for r in ROWS if include_hidden or r["key"] not in HIDDEN])
103
- monkeypatch.setattr(search_api, "rollout_counts", lambda: {"ds/d03": 4, "ds/d05": 1, "sp/s01": 2})
104
- monkeypatch.setenv("RLX_SNAPSHOT_LOCAL", str(tmp_path / "local"))
105
- monkeypatch.setenv("RLX_SNAPSHOT_STORE", str(tmp_path / "store"))
106
- for spec, tasks in TASKS.items():
107
- write_index(spec, tasks)
108
- snapshot.reset()
109
- search_api._answers.clear()
110
- yield
111
- snapshot.reset()
112
- for spec in TASKS:
113
- catalog._index_path(spec).unlink(missing_ok=True)
114
-
115
-
116
- def publish_fixture(tmp_path: Path) -> dict:
117
- """ROWS as a published snapshot in the test store (as the indexer would: raw rows, admin settings left out)."""
118
- store = snapshot.LocalStore(tmp_path / "store")
119
- db = tmp_path / "build.db"
120
- public = [r["id"] for r in ROWS if r["kind"] == "dataset"]
121
- counts = snapshot.build_db(db, ROWS, snapshot.iter_index_tasks(public, mimo=False))
122
- return indexer.publish(store, db, counts)
123
-
124
-
125
- def fw_js(d: dict) -> str:
126
- """No runtime evidence in the base fixture: metadata only supplies candidates."""
127
- if d["kind"] == "space":
128
- return "unverified-space" if d.get("framework") == "openenv" or d.get("openenv") else "other-space"
129
- return d.get("framework") or "harbor"
130
-
131
-
132
- def visible() -> list[dict]:
133
- return [r for r in ROWS if r["key"] not in HIDDEN and not r.get("private")]
134
-
135
-
136
- # ── the taxonomy and the orders ──────────────────────────────────────────────
137
- @pytest.mark.parametrize("mode", ["snapshot", "live"])
138
- def test_kind_taxonomy(tmp_path, mode):
139
- if mode == "snapshot":
140
- publish_fixture(tmp_path)
141
- d = client.get("/api/search?size=100").json()
142
- assert d["snapshot"]["source"] == mode
143
- expected: dict[str, int] = {}
144
- for r in visible():
145
- k = fw_js(r)
146
- expected[k] = expected.get(k, 0) + 1
147
- assert {k: v for k, v in d["facets"]["kind"].items() if k != "all"} == expected
148
- assert d["facets"]["kind"]["all"] == d["total"] == len(visible())
149
- assert expected["other-space"] > 0 and expected["unverified-space"] > 0
150
- cards = {c["key"]: c for c in d["rows"]}
151
- # Collections cannot supply framework evidence, including for FineEnvs.
152
- assert all(c["fw"] == "other-space" for c in cards.values() if c["kind"] == "space" and c["collection"] != "fineenvs" and c["framework"] == "ors")
153
- for key in ("space:fe/latex", "space:fe/ors-one", "space:fe/plain"):
154
- assert cards[key]["fw"] == "other-space" and not cards[key]["openenv"]
155
- assert cards["space:fe/plain"]["collection"] == "fineenvs" # the first collection naming it
156
- assert cards["ds/d08"]["fw"] == "harbor" # no framework at all: Harbor, as the page reads it
157
- for k in expected:
158
- sub = client.get(f"/api/search?kind={k}&size=100").json()
159
- assert sub["total"] == expected[k] and all(c["fw"] == k for c in sub["rows"])
160
- assert client.get("/api/search?k=ors").json()["query"]["kind"] == "other-space" # the page's old links
161
-
162
-
163
- def test_trending_uses_metrics_and_featured_is_separate(tmp_path):
164
- publish_fixture(tmp_path)
165
- rows = client.get("/api/search?size=100").json()["rows"]
166
- want = sorted(visible(), key=lambda r: (-r["trending"], -r["likes"], -r["downloads"]))
167
- assert [r["key"] for r in rows] == [r["key"] for r in want]
168
- oe = client.get("/api/search?kind=unverified-space&size=100").json()["rows"]
169
- assert [r["key"] for r in oe] == [r["key"] for r in want if fw_js(r) == "unverified-space"]
170
- assert client.get("/api/search?kind=unverified-space&sort=likes&size=100").json()["rows"][0]["likes"] == max(r["likes"] for r in oe)
171
- trending = client.get("/api/search?trending=5&facets=0").json()["trending"]
172
- assert [r["key"] for r in trending] == [r["key"] for r in rows[:5]]
173
- assert [r["key"] for r in client.get("/api/search?trending=5").json()["featured"]] == PINS
174
- filtered = client.get("/api/search?kind=unverified-space&trending=48").json()
175
- assert filtered["featured"] == []
176
- assert [r["key"] for r in filtered["trending"]] == [r["key"] for r in oe]
177
-
178
-
179
- def test_hidden_environments_are_left_out_everywhere(tmp_path):
180
- publish_fixture(tmp_path)
181
- d = client.get("/api/search?size=100&trending=48").json()
182
- keys = {r["key"] for r in d["rows"]} | {r["key"] for r in d["trending"]}
183
- assert not keys & HIDDEN and "priv/secret" not in keys
184
- assert d["stats"]["datasets"] + d["stats"]["spaces"] == len(visible())
185
- assert client.get("/api/search?q=d13").json()["total"] == 0
186
- assert client.get("/api/search/tasks?q=join").json()["rows"] == [
187
- {"env": "ds/d00", "ref": "tasks/sql-join", "title": "Join two tables in SQL", "brief": "Write a query.", "category": "sql",
188
- "difficulty": "easy", "grading": "txt", "run": "image"}] # not the hidden one's, nor the private one's
189
-
190
-
191
- def test_an_admin_hiding_shows_without_a_new_snapshot(tmp_path, monkeypatch):
192
- publish_fixture(tmp_path)
193
- before = client.get("/api/search?q=d01").json()["total"]
194
- assert client.get("/api/search?q=d01").json()["total"] == before == 1 # answered again (from memory)
195
- monkeypatch.setattr(catalog, "hidden", lambda: set(HIDDEN) | {"ds/d01"})
196
- assert client.get("/api/search?q=d01").json()["total"] == 0 # a settings change is a new question
197
-
198
-
199
- def test_recent_openenv_checks_versions_and_trending_use_same_filters(tmp_path, monkeypatch):
200
- from app import space_checks
201
- import time
202
- publish_fixture(tmp_path)
203
- now = time.time()
204
- evidence = {"fe/geo": {"schema": 1, "id": "fe/geo", "stage": "RUNNING", "checked_at": now,
205
- "status": "API checked", "mode": "Simulation", "tools": 0,
206
- "version": {"value": "0.2.3", "source": "Lockfile"}}}
207
- monkeypatch.setattr(space_checks, "inventory", lambda: evidence)
208
- params = {"kind": "openenv", "f": "health:API%20checked;oe:0.2.3", "trending": "24"}
209
- data = client.get("/api/search", params=params).json()
210
- assert data["total"] == 1
211
- assert [r["id"] for r in data["rows"]] == [r["id"] for r in data["trending"]] == ["fe/geo"]
212
- assert data["rows"][0]["version_source"] == "Lockfile"
213
- assert data["rows"][0]["tools_status"] == "No tools discovered"
214
- assert data["facets"]["oe"] == [["0.2.3", 1]]
215
- assert client.get("/api/search", params={**params, "f": "health:API%20checked;tools:Tools%20discovered"}).json()["total"] == 0
216
- # Negative checks and expiration invalidate cached search responses too.
217
- evidence["fe/geo"]["status"] = "Checks failed"
218
- assert client.get("/api/search", params=params).json()["total"] == 0
219
- evidence["fe/geo"].update(status="API checked", checked_at=now - space_checks.FRESH - 1)
220
- assert client.get("/api/search", params=params).json()["total"] == 0
221
- expired = client.get("/api/search", params={"f": "health:Check%20expired"}).json()
222
- assert expired["total"] == 1 and expired["rows"][0]["api_status"] == "Check expired"
223
-
224
-
225
- def test_sql_python_parity_for_api_evidence(tmp_path, monkeypatch):
226
- from app import space_checks
227
- import time
228
- publish_fixture(tmp_path)
229
- now = time.time()
230
- statuses = ["API checked", "Checks failed", "Not running", "Check unavailable"]
231
- evidence = {r["id"]: {"id": r["id"], "status": statuses[i % 4], "checked_at": now - (i % 5) * 1000,
232
- "mode": "Simulation", "tools": i % 3, "version": {"value": ">=0.2.3", "source": "Dependency constraint"}}
233
- for i, r in enumerate(ROWS) if r.get("kind") == "space"}
234
- monkeypatch.setattr(space_checks, "inventory", lambda: evidence)
235
- ctx = search_api.context()
236
- cards = search_api.py_cards([r for r in ROWS if not r.get("private")], ctx)
237
- with snapshot.use() as (_, conn):
238
- for facet, value in [("health", "API checked"), ("health", "Check expired"), ("health", "Not checked"),
239
- ("mode", "Simulation"), ("tools", "Tools discovered"), ("oe", ">=0.2.3"),
240
- ("oe_source", "Dependency constraint")]:
241
- q = search_api.Query(size=100, sel={facet: [value]})
242
- actual, expected = search_api.sql_search(conn, q, ctx), search_api.py_search(cards, q)
243
- assert actual["total"] == expected["total"]
244
- assert actual["facets"] == expected["facets"]
245
- assert [r["id"] for r in actual["rows"]] == [r["id"] for r in expected["matches"]]
246
-
247
-
248
- # ── parity with the page's own filtering ─────────────────────────────────────
249
- def random_queries(n: int, seed: int = 3) -> list[search_api.Query]:
250
- rnd = random.Random(seed)
251
- words = ["", "", "a", "te", "sql", "bench", "game ocr", "s0", "C++", "日本", "\"", "near", "x" * 41, "d1 tasks"]
252
- out = []
253
- for _ in range(n):
254
- sel = {}
255
- for k, vals in (("type", ["Neither", "Benchmark", "OpenEnv", "ORS", "RL Environment"]),
256
- ("size", ["Under 100", "100 to 1,000", "1,000 or more", "Not indexed yet"]),
257
- ("stage", ["Running", "Sleeping", "Error", "Unknown"]), ("mcp", ["MCP tagged", "No MCP tag"]),
258
- ("evidence", ["Manifest present", "Hub tag only"]),
259
- ("oe", ["0.2.1", "0.2.3"]), ("tags", TAGS)):
260
- if rnd.random() < 0.2:
261
- sel[k] = rnd.sample(vals, rnd.randint(1, 2))
262
- out.append(search_api.Query(q=rnd.choice(words), kind=rnd.choice(search_api.KINDS), sort=rnd.choice(search_api.SORTS),
263
- coll=rnd.choice([None, None, "fineenvs", "terminal", "other"]), sel=sel))
264
- return out
265
-
266
-
267
- @pytest.mark.parametrize("mode", ["snapshot", "live"])
268
- def test_parity_with_the_pages_filtering(tmp_path, mode):
269
- if mode == "snapshot":
270
- publish_fixture(tmp_path)
271
- ctx = search_api.context()
272
- cards = search_api.py_cards([r for r in ROWS if not r.get("private")], ctx)
273
- with snapshot.use() as (snap, conn):
274
- assert snap.source == mode
275
- for q in random_queries(300):
276
- got = search_api.sql_search(conn, q, ctx, limit=1000, offset=0)
277
- want = search_api.py_search(cards, q)
278
- assert [r["key"] for r in got["rows"]] == [d["key"] for d in want["matches"]], q
279
- assert got["total"] == want["total"], q
280
- for k in ("kind", "collection", *search_api.FACETS):
281
- assert got["facets"][k] == want["facets"][k], (q, k)
282
-
283
-
284
- def test_api_facets_match_the_reference(tmp_path):
285
- publish_fixture(tmp_path)
286
- ctx = search_api.context()
287
- cards = search_api.py_cards([r for r in ROWS if not r.get("private")], ctx)
288
- for qs, q in [("f=tags:sql|code;type:Neither", search_api.Query(sel={"tags": ["sql", "code"], "type": ["Neither"]})),
289
- ("k=openenv&f=stage:Running&s=updated", search_api.Query(kind="openenv", sel={"stage": ["Running"]}, sort="updated")),
290
- ("c=other&q=game&sort=new", search_api.Query(coll="other", q="game", sort="new")),
291
- ("f=tags:a%22quote", search_api.Query(sel={"tags": ['a"quote']}))]:
292
- d = client.get(f"/api/search?{qs}&size=100").json()
293
- want = search_api.py_search(cards, q)
294
- assert d["total"] == want["total"] and [r["key"] for r in d["rows"]] == [x["key"] for x in want["matches"]][:100]
295
- for k in search_api.FACETS:
296
- assert d["facets"][k] == search_api._rank(want["facets"][k]), k
297
- assert d["facets"]["collection"] == want["facets"]["collection"]
298
-
299
-
300
- def test_paging_covers_every_match_once(tmp_path):
301
- publish_fixture(tmp_path)
302
- total = client.get("/api/search?facets=0").json()["total"]
303
- seen = []
304
- for page in range(1, 20):
305
- rows = client.get(f"/api/search?sort=likes&page={page}&size=7&facets=0").json()["rows"]
306
- if not rows:
307
- break
308
- seen += [r["key"] for r in rows]
309
- assert len(seen) == len(set(seen)) == total
310
-
311
-
312
- # ── search text is data, never FTS syntax ────────────────────────────────────
313
- NASTY = ['"', '""', '"unbalanced', 'NEAR(a b)', 'NEAR(sql, 2)', 'a*', '*', '^sql', 'title:sql', 'brief:x', '{id heading}:sql',
314
- 'AND', 'OR', 'NOT', 'sql OR math', 'sql NOT math', '(sql', 'sql)', '-sql', '+sql', "'; DROP TABLE envs; --",
315
- '\x00sql', 'c++', '日本語', '%', '_', '\\', 'a" OR "b', 'sql"*', ':', '"" ""', 'NEAR', 'near(']
316
-
317
-
318
- @pytest.mark.parametrize("text", NASTY)
319
- def test_search_text_cant_inject(tmp_path, text):
320
- publish_fixture(tmp_path)
321
- r = client.get("/api/search", params={"q": text, "size": 100})
322
- assert r.status_code == 200
323
- q = search_api.Query(q=text.strip())
324
- want = search_api.py_search(search_api.py_cards([x for x in ROWS if not x.get("private")], search_api.context()), q)
325
- assert [x["key"] for x in r.json()["rows"]] == [d["key"] for d in want["matches"]]
326
- t = client.get("/api/search/tasks", params={"q": text})
327
- assert t.status_code == 200 and isinstance(t.json()["rows"], list)
328
-
329
-
330
- def test_fts_expressions_are_quoted():
331
- assert search_api.envs_fts_query(['a"b', "near(x", "ab"]) == '"a""b" "near(x"'
332
- assert search_api.tasks_fts_query('NEAR(sql OR "x*" title:join ^z)') == '"near"* "sql"* "or"* "x"* "title"* "join"* "z"*'
333
- assert search_api.tasks_fts_query('"*" -- ()') is None
334
- assert search_api.tasks_fts_query("a " * 50).count("*") == 8
335
-
336
-
337
- def test_task_search_operators_are_words(tmp_path):
338
- publish_fixture(tmp_path)
339
- assert [r["ref"] for r in client.get("/api/search/tasks?q=foo").json()["rows"]] == ["tasks/foo-only"]
340
- assert client.get("/api/search/tasks?q=foo OR join").json()["total"] == 0 # OR is a word to find, not an operator
341
- assert client.get("/api/search/tasks?q=NEAR(foo bar)").json()["total"] == 0 # nor NEAR ("near" isn't in it)
342
- assert client.get("/api/search/tasks?q=NOT foo").json()["total"] == 1 # "not" finds "Nothing": a word
343
- assert client.get("/api/search/tasks?q=jo").json()["total"] == 1 # prefixes
344
- assert client.get("/api/search/tasks?q=join&env=ds/d01").json()["total"] == 0
345
- assert client.get("/api/search/tasks?q=join&env=../x").status_code == 400
346
-
347
-
348
- # ── the visitor's own, headers ───────────────────────────────────────────────
349
- def test_mine_merges_private_datasets(tmp_path, monkeypatch):
350
- publish_fixture(tmp_path)
351
- mine = [{"id": "me/private-ds", "key": "me/private-ds", "kind": "dataset", "framework": "harbor", "private": True, "heading": "Mine",
352
- "brief": "my sql tasks", "tags": ["sql"], "badges": [], "downloads": 0, "likes": 0, "trending": 50, "indexed": {"tasks": 3}},
353
- {**next(r for r in ROWS if r["id"] == "ds/d02"), "mine": True}]
354
- monkeypatch.setattr(search_api, "_mine_rows", lambda request: mine)
355
- d = client.get("/api/search?mine=1&c=mine&size=100").json()
356
- assert {r["key"] for r in d["rows"]} == {"me/private-ds", "ds/d02"} and d["total"] == 2
357
- assert d["facets"]["collection"]["mine"] == 2 and d["mine"] == 2
358
- top = client.get("/api/search?mine=1&trending=3").json()
359
- assert top["trending"][0]["key"] == "me/private-ds"
360
- assert [r["key"] for r in top["featured"]] == PINS
361
- own = client.get("/api/search?mine=1&c=mine&trending=24").json()
362
- assert {r["key"] for r in own["trending"]} == {"me/private-ds", "ds/d02"}
363
- assert top["total"] == len(visible()) + 1 and top["stats"]["datasets"] == sum(1 for r in visible() if r["kind"] == "dataset") + 1
364
- assert next(r for r in top["rows"] if r["key"] == "me/private-ds")["private"] is True
365
- r = client.get("/api/search?mine=1")
366
- assert r.headers["cache-control"].startswith("private") and r.headers["vary"] == "Cookie"
367
- monkeypatch.setattr(search_api, "_mine_rows", lambda request: None) # signed out: "mine" is no filter
368
- assert client.get("/api/search?mine=1&c=mine").json()["query"]["collection"] is None
369
-
370
-
371
- def test_cache_headers_and_etag(tmp_path):
372
- publish_fixture(tmp_path)
373
- r = client.get("/api/search?q=sql")
374
- assert r.headers["cache-control"].startswith("public, max-age=") and r.headers["etag"].startswith('W/"')
375
- again = client.get("/api/search?q=sql", headers={"If-None-Match": r.headers["etag"]})
376
- assert again.status_code == 304 and not again.content
377
- assert client.get("/api/search/tasks?q=sql").headers["cache-control"].startswith("public")
378
-
379
-
380
- def test_responses_are_cards_only(tmp_path):
381
- publish_fixture(tmp_path)
382
- d = client.get("/api/search?trending=24").json()
383
- assert len(d["rows"]) == 40 and len(d["trending"]) == 24
384
- assert set(d["rows"][0]) == {"id", "key", "kind", "framework", "fw", "openenv", "collection", "heading", "brief", "downloads", "likes",
385
- "trending", "updated", "created", "stage", "mcp", "openenv_version", "manifest", "hardware", "badges",
386
- "tags", "pinned", "mine", "indexed", "rollouts", "private", "evidence", "stage_label",
387
- "api_check", "api_status", "api_mode", "declared_version", "version_source", "tools_status"}
388
- assert [c["id"] for c in d["collections"]] == ["fineenvs", "terminal"] and "ids" not in d["collections"][0]
389
- assert client.get("/api/search?q=ds/d03").json()["rows"][0]["rollouts"] == 4
390
- assert len(client.get("/api/search?trending=24").content) < 60_000
391
-
392
-
393
- def test_rollouts_sort(tmp_path):
394
- publish_fixture(tmp_path)
395
- rows = client.get("/api/search?sort=rollouts&size=3").json()["rows"]
396
- assert [r["id"] for r in rows] == ["ds/d03", "sp/s01", "ds/d05"]
397
-
398
-
399
- def teardown_module(module):
400
- shutil.rmtree(Path(config.STORAGE_DIR) / "snapshots", ignore_errors=True)
401
-
402
-
403
- def test_environments_helper_for_whole_listing_readers(tmp_path, monkeypatch):
404
- publish_fixture(tmp_path)
405
- rows = search_api.environments()
406
- assert [r["key"] for r in rows] == [r["key"] for r in ROWS if r["key"] not in HIDDEN and not r.get("private")] # listing order
407
- by = {r["key"]: r for r in rows}
408
- assert by["space:fe/plain"]["framework"] == "space" and by["space:fe/plain"]["collection"] == "fineenvs"
409
- assert by["ds/d07"]["pinned"] and not by["ds/d07"]["gated"] and not by["ds/d07"]["private"]
410
- assert {r["key"] for r in search_api.environments(include_hidden=True)} >= HIDDEN
411
-
412
-
413
- def test_the_visitors_datasets_never_make_a_search_wait(monkeypatch):
414
- from app import auth
415
-
416
- calls = []
417
-
418
- def mine(token):
419
- calls.append(token)
420
- return [{"id": f"me/v{len(calls)}"}]
421
-
422
- monkeypatch.setattr(auth, "current_user", lambda request: {"name": "me", "token": "hf_secret_token"})
423
- monkeypatch.setattr(catalog, "mine", mine)
424
- search_api._mine.clear()
425
- assert search_api._mine_rows(None)[0]["id"] == "me/v1" and search_api._mine_rows(None)[0]["id"] == "me/v1" and len(calls) == 1
426
- who = next(iter(search_api._mine))
427
- assert "hf_secret_token" not in who
428
- search_api._mine[who] = (search_api._mine[who][0] - search_api.MINE_FRESH - 1, search_api._mine[who][1])
429
- assert search_api._mine_rows(None)[0]["id"] == "me/v1" # stale: answered at once...
430
- for _ in range(100):
431
- if search_api._mine[who][1][0]["id"] == "me/v2":
432
- break
433
- time.sleep(0.02)
434
- assert search_api._mine[who][1][0]["id"] == "me/v2" # ...and refreshed behind it
435
- monkeypatch.setattr(auth, "current_user", lambda request: None)
436
- assert search_api._mine_rows(None) is None
437
-
438
-
439
- def test_a_spaces_rank_matches_the_trending_order_without_the_listing(tmp_path):
440
- publish_fixture(tmp_path)
441
- key = lambda d: (-d["trending"], -d["likes"], -(d.get("downloads") or 0))
442
- spaces = [r for r in visible() if r["kind"] == "space"]
443
- order = sorted(spaces, key=key)
444
- for i in (0, len(order) // 2, len(order) - 1):
445
- r = client.get(f"/api/search/rank?key={order[i]['key']}")
446
- assert r.status_code == 200 and r.json() == {"n": i + 1, "of": len(order)}, order[i]["key"]
447
- assert client.get(f"/api/search/rank?key={next(iter(HIDDEN))}").status_code == 404 # hidden: not ranked
448
- assert client.get("/api/search/rank?key=space:nobody/nothing").status_code == 404
449
-
450
-
451
- def test_runtime_and_evidence_filters_and_census_agree(tmp_path):
452
- publish_fixture(tmp_path)
453
- d = client.get("/api/search?kind=unverified-space&size=100").json()
454
- assert sum(d["stats"]["openenv"].values()) == d["total"]
455
- for evidence, count in d["stats"]["openenv"].items():
456
- r = client.get("/api/search", params={"kind": "unverified-space", "f": f"evidence:{evidence}", "trending": 48, "size": 100}).json()
457
- assert r["total"] == count
458
- assert all(x["evidence"] == evidence for x in r["rows"] + r["trending"])
459
- running = client.get("/api/search?kind=unverified-space&f=stage:Running").json()
460
- assert running["total"] == d["stats"]["openenv_running"]
461
- errors = client.get("/api/search?f=stage:Error&size=100").json()
462
- assert errors["total"] and all(r["stage"] == "RUNTIME_ERROR" and r["stage_label"] == "Error" for r in errors["rows"])
463
- sleeping = client.get("/api/search?f=stage:Sleeping&size=100").json()
464
- assert sleeping["total"] and all(r["stage"] == "SLEEPING" for r in sleeping["rows"])
465
- old = client.get("/api/search?f=stage:Asleep%20or%20stopped;mcp:Has%20MCP%20tools").json()
466
- assert "Error" in old["query"]["f"]["stage"] and old["query"]["f"]["mcp"] == ["MCP tagged"]
467
-
468
-
469
- def test_trending_priorities_cannot_overflow_into_each_other(tmp_path, monkeypatch):
470
- # Old 1e9/1e4 weights let popularity displace a larger recent-interest score,
471
- # and capped downloads made 10k and 10M downloads indistinguishable.
472
- custom = [dict(ROWS[0], id=f"rank/{i}", key=f"rank/{i}", trending=t, likes=l, downloads=d)
473
- for i, (t, l, d) in enumerate([(0, 10**9, 10**12), (0.1, 0, 0), (1, 3, 10000), (1, 3, 10**7)])]
474
- monkeypatch.setattr(__import__(__name__, fromlist=["ROWS"]), "ROWS", custom)
475
- monkeypatch.setattr(catalog, "pinned", lambda: ["rank/0"])
476
- publish_fixture(tmp_path)
477
- got = client.get("/api/search?trending=4").json()
478
- assert [r["id"] for r in got["rows"]] == ["rank/3", "rank/2", "rank/1", "rank/0"]
479
- assert got["trending"] == got["rows"]
480
- assert [r["id"] for r in got["featured"]] == ["rank/0"]
481
- assert got["ranking"]["order"] == ["trending", "likes", "downloads"]
482
-
483
-
484
- def test_empty_filters_have_no_unrelated_trending_or_featured(tmp_path):
485
- publish_fixture(tmp_path)
486
- d = client.get("/api/search?q=absent-environment-xyz&trending=24").json()
487
- assert d["total"] == 0 and d["trending"] == [] and d["featured"] == []
488
-
489
-
490
- @pytest.mark.parametrize("mode", ["published", "fallback"])
491
- def test_owner_filter_is_exact_and_shared_by_results_facets_and_trending(tmp_path, monkeypatch, mode):
492
- import sys
493
- space = next(r for r in ROWS if r["kind"] == "space")
494
- dataset = ROWS[0]
495
- mixed = [*ROWS, {**space, "id": "ds/owned-space", "key": "space:ds/owned-space"},
496
- {**dataset, "id": "ds-copy/impostor", "key": "ds-copy/impostor", "heading": "ds environments"}]
497
- monkeypatch.setattr(sys.modules[__name__], "ROWS", mixed)
498
- if mode == "published":
499
- publish_fixture(tmp_path)
500
- expected = {r["key"] for r in visible() if r["id"].split("/")[0] == "ds"}
501
- data = client.get("/api/search", params={"owner": "DS", "size": 100, "trending": 48}).json()
502
- assert {r["key"] for r in data["rows"]} == expected
503
- assert {r["kind"] for r in data["rows"]} == {"dataset", "space"}
504
- assert data["total"] == data["facets"]["kind"]["all"] == len(expected)
505
- assert {r["key"] for r in data["trending"]} == expected
506
- assert all(r["id"].split("/")[0] == "ds" for r in data["featured"])
507
- query = search_api.Query(owner="DS")
508
- ref = search_api.py_search(search_api.py_cards([r for r in mixed if not r.get("private")], search_api.context()), query)
509
- assert {r["key"] for r in ref["matches"]} == expected
510
- pages = [client.get("/api/search", params={"owner": "ds", "size": 10, "page": page, "facets": 0}).json()["rows"]
511
- for page in range(1, (len(expected) + 9) // 10 + 1)]
512
- assert {r["key"] for page in pages for r in page} == expected
513
- assert sum(map(len, pages)) == len(expected)
514
- filtered = client.get("/api/search", params={"owner": "ds", "kind": "openenv", "q": "owned-space"}).json()
515
- assert all(r["id"] == "ds/owned-space" for r in filtered["rows"])
516
- for owner in ("d", "ds%", "ds/", "missing", "ds' OR 1=1 --"):
517
- empty = client.get("/api/search", params={"owner": owner, "trending": 24}).json()
518
- assert empty["total"] == 0 and empty["trending"] == [] and empty["featured"] == []
519
-
520
-
521
- def test_owner_filter_applies_to_authorized_private_dataset_merge(tmp_path, monkeypatch):
522
- publish_fixture(tmp_path)
523
- mine = [{**ROWS[0], "id": "me/private", "key": "me/private", "private": True},
524
- {**ROWS[0], "id": "someone-else/private", "key": "someone-else/private", "private": True}]
525
- monkeypatch.setattr(search_api, "_mine_rows", lambda request: mine)
526
- public = client.get("/api/search", params={"owner": "me"}).json()
527
- assert public["total"] == 0
528
- response = client.get("/api/search", params={"owner": "ME", "mine": 1, "trending": 24})
529
- data = response.json()
530
- assert data["total"] == 1
531
- assert data["stats"]["datasets"] == 1
532
- assert data["stats"]["dataset_tasks"] == (mine[0].get("indexed") or {}).get("tasks", 0)
533
- assert [r["id"] for r in data["rows"]] == [r["id"] for r in data["trending"]] == ["me/private"]
534
- assert data["rows"][0]["private"] is True
535
- assert response.headers["cache-control"].startswith("private")
536
-
537
-
538
- @pytest.mark.parametrize("status,stage,age,expected", [
539
- (None, "RUNNING", 0, "unverified-space"),
540
- ("Checks failed", "RUNNING", 0, "unverified-space"),
541
- ("API checked", "RUNNING", 0, "openenv"),
542
- ("API checked", "RUNNING", 3601, "unverified-space"),
543
- ("API checked", "SLEEPING", 0, "unverified-space"),
544
- ])
545
- def test_manifest_filename_never_establishes_openenv_without_fresh_live_evidence(tmp_path, monkeypatch, status, stage, age, expected):
546
- from app import space_checks
547
- publish_fixture(tmp_path)
548
- target = next(r for r in ROWS if r.get("manifest"))
549
- record = {"id": target["id"], "status": status, "stage": stage, "checked_at": time.time() - age}
550
- monkeypatch.setattr(space_checks, "inventory", lambda: {target["id"]: record} if status else {})
551
- data = client.get("/api/search", params={"q": target["id"], "trending": 24}).json()
552
- row = next(r for r in data["rows"] if r["id"] == target["id"])
553
- assert row["fw"] == expected
554
- assert row["openenv"] is (expected == "openenv")
555
- if expected != "openenv":
556
- assert row["framework"] == "space" and "OpenEnv" not in row["badges"]
557
- filtered = client.get("/api/search", params={"q": target["id"], "kind": "openenv", "trending": 24}).json()
558
- assert bool(filtered["rows"]) is (expected == "openenv")
559
- assert bool(filtered["trending"]) is (expected == "openenv")
560
- assert filtered["facets"]["kind"].get(expected, 0) > 0
561
-
562
-
563
- def test_verified_untagged_space_has_protocol_facets(tmp_path, monkeypatch):
564
- from app import space_checks
565
- publish_fixture(tmp_path)
566
- record = {"id": "fe/plain", "status": "API checked", "stage": "RUNNING", "checked_at": time.time(), "mode": "Simulation", "tools": 0}
567
- monkeypatch.setattr(space_checks, "inventory", lambda: {"fe/plain": record})
568
- data = client.get("/api/search", params={"kind": "openenv", "f": "health:API%20checked"}).json()
569
- assert [r["id"] for r in data["rows"]] == ["fe/plain"]
570
- ctx = search_api.context()
571
- cards = search_api.py_cards([r for r in ROWS if not r.get("private")], ctx)
572
- with snapshot.use() as (_, conn):
573
- q = search_api.Query(kind="openenv")
574
- assert search_api.sql_search(conn, q, ctx)["facets"] == search_api.py_search(cards, q)["facets"]
575
-
576
-
577
- def test_ready_scope_counts_and_facets_share_live_evidence(tmp_path, monkeypatch):
578
- from app import space_checks
579
- publish_fixture(tmp_path)
580
- now = time.time()
581
- good = {"id": "fe/geo", "status": "API checked", "stage": "RUNNING", "checked_at": now,
582
- "task_catalog": {"checked_at": now, "tasks": 1234, "counted_splits": 1, "complete": True}}
583
- checks = {"fe/geo": good,
584
- "fe/latex": {**good, "id": "fe/latex", "checked_at": now - 3601, "task_catalog": {}},
585
- "fe/plain": {**good, "id": "fe/plain", "status": "Checks failed", "task_catalog": {}},
586
- "fe/ors-one": {**good, "id": "fe/ors-one", "status": "Checks failed", "interface": "ors", "task_catalog": {}}}
587
- monkeypatch.setattr(space_checks, "inventory", lambda: checks)
588
- data = client.get("/api/search", params={"scope": "ready", "owner": "fe", "size": 100, "trending": 24}).json()
589
- assert {r["id"] for r in data["rows"]} == {"fe/geo", "fe/ors-one"}
590
- assert data["stats"]["datasets"] == data["stats"]["dataset_tasks"] == 0
591
- assert data["stats"]["spaces"] == data["total"] == data["facets"]["kind"]["all"] == 2
592
- assert data["stats"]["space_tasks"] == 1234
593
- rank = client.get("/api/search/rank", params={"scope": "ready", "key": "space:fe/geo"}).json()
594
- assert rank["of"] == 2
595
- assert client.get("/api/search/rank", params={"scope": "ready", "key": "space:fe/plain"}).status_code == 404
596
- ctx = search_api.context()
597
- cards = search_api.py_cards([r for r in ROWS if not r.get("private")], ctx)
598
- with snapshot.use() as (_, conn):
599
- for q in (search_api.Query(scope="ready"), search_api.Query(scope="ready", owner="fe"),
600
- search_api.Query(scope="ready", kind="openenv")):
601
- sql = search_api.sql_search(conn, q, ctx, limit=100)
602
- py = search_api.py_search(cards, q)
603
- assert sql["total"] == py["total"]
604
- assert sql["facets"] == py["facets"]
605
- datasets = client.get("/api/search", params={"scope": "ready", "owner": "ds"}).json()
606
- assert datasets["stats"]["datasets"] == datasets["total"] and datasets["stats"]["spaces"] == 0
607
- assert datasets["stats"]["dataset_tasks"] == len(TASKS["ds/d00"])
608
- empty = client.get("/api/search", params={"scope": "ready", "q": "nothing-matches"}).json()
609
- assert all(empty["stats"][k] == 0 for k in ("datasets", "spaces", "dataset_tasks", "space_tasks"))
610
-
611
-
612
- def test_fineenvs_promoted_without_pins_and_without_changing_trending(tmp_path, monkeypatch):
613
- import sys
614
- from app import space_checks
615
- custom = [{**ROWS[0], "id": "FineEnvs/data", "key": "FineEnvs/data", "trending": 0},
616
- {**ROWS[1], "id": "other/popular", "key": "other/popular", "trending": 100},
617
- {**ROWS[-2], "id": "FineEnvs/server", "key": "space:FineEnvs/server", "kind": "space", "trending": 0}]
618
- monkeypatch.setattr(sys.modules[__name__], "ROWS", custom)
619
- monkeypatch.setattr(catalog, "pinned", list)
620
- monkeypatch.setattr(space_checks, "inventory", lambda: {"FineEnvs/server": {
621
- "id": "FineEnvs/server", "status": "API checked", "stage": "RUNNING", "checked_at": time.time()}})
622
- publish_fixture(tmp_path)
623
- data = client.get("/api/search?scope=ready&trending=24").json()
624
- assert [r["id"] for r in data["promoted"]] == ["FineEnvs/server", "FineEnvs/data"]
625
- assert data["trending"][0]["id"] == "other/popular"
626
- assert data["featured"] == []
627
- assert client.get("/api/search?scope=ready&owner=other&trending=24").json()["promoted"] == []
628
- assert client.get("/api/search?scope=ready&q=popular&trending=24").json()["promoted"] == []
629
- assert len(client.get("/api/search?scope=ready&owner=fineenvs&trending=24").json()["promoted"]) == 2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_security.py DELETED
@@ -1,554 +0,0 @@
1
- """Security properties of the explorer, end to end through the HTTP API (no network: the Hub is stubbed).
2
-
3
- uv run pytest tests -q
4
-
5
- What is checked: who may see and change what (admins, owners, everyone), that sessions can't be forged, that
6
- cross-site requests are refused, that private datasets and private rollouts stay private, that answers and tokens
7
- never reach a page, that a visitor's endpoint can't point the server at a private address, and the headers.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import json
13
- import os
14
- import tempfile
15
- import time
16
- from pathlib import Path
17
-
18
- import pytest
19
-
20
- _TMP = os.environ.get("RLX_TEST_TMP") or tempfile.mkdtemp(prefix="rlx-test-") # conftest.py's, shared by every module
21
- os.environ.update(STORAGE_DIR=_TMP, RLX_CACHE_DIR=f"{_TMP}/cache", OAUTH_CLIENT_ID="test-client", SESSION_SECRET="test-secret",
22
- RLX_WARM="0", RLX_ADMIN_ORG="FineEnvs")
23
-
24
- from fastapi.testclient import TestClient # noqa: E402
25
-
26
- from app import admin_app, auth, catalog, config, endpoints, main, models, runner, settings, store # noqa: E402
27
-
28
- TOKENS = {"alice": "hf_alice_secret_token_0001", "bob": "hf_bob_secret_token_0002"}
29
-
30
-
31
- def cookie(name: str, exp: float | None = None) -> dict[str, str]:
32
- session = {"token": TOKENS[name], "name": name, "avatar": None, "via": "token", "exp": exp or time.time() + 3600}
33
- return {"Cookie": f"{auth.COOKIE}={auth._box.encrypt(json.dumps(session).encode()).decode()}"}
34
-
35
-
36
- @pytest.fixture(autouse=True)
37
- def stubs(monkeypatch):
38
- """No Hub: alice is a FineEnvs member, bob isn't; the catalog is empty."""
39
- monkeypatch.setattr(settings, "_org_members", lambda: {"alice"})
40
- monkeypatch.setattr(catalog, "environments", lambda include_hidden=False: [])
41
- monkeypatch.setattr(models, "get", lambda mid: {"id": mid, "tools": True, "provider": "x"})
42
-
43
-
44
- client = TestClient(main.app, base_url="https://testserver")
45
- admin = TestClient(admin_app.app, base_url="https://testserver") # its own app, on its own private Space
46
-
47
-
48
- def runnable_task(monkeypatch, **row):
49
- """A task any environment's adapter says runs with the Harbor runner (app/envs/registry.run_task, stubbed)."""
50
- from types import SimpleNamespace
51
-
52
- from app.envs import registry
53
-
54
- row = {"title": "T", "restricted": False, "sha": "abc", "runnable": {"ok": True, "how": "image"}, "image": "python:3.12", **row}
55
- opts = [{"runner": "harbor", "label": "Harbor", "ok": True, "why": "", "fields": [], "harnesses": None}]
56
- monkeypatch.setattr(registry, "run_task", lambda d, p, t=None: (SimpleNamespace(key=d, adapter=SimpleNamespace(id="test")), row, opts))
57
- monkeypatch.setattr(runner.Rollout, "execute", lambda self: None)
58
-
59
-
60
- def run(**kw) -> dict:
61
- rid = kw.pop("id", f"20260101-000000-{os.urandom(3).hex()}")
62
- base = {"id": rid, "user": "alice", "dataset": "org/ds", "path": "tasks/t1", "task_id": "org/ds:tasks/t1", "title": "T",
63
- "model": "m/x", "harness": "opencode", "status": "done", "reward": 1.0, "visibility": "public"}
64
- return store.create({**base, **kw})
65
-
66
-
67
- # ── sessions ─────────────────────────────────────────────────────────────────
68
- def test_signed_out_and_forged_sessions_are_nobody():
69
- assert client.get("/api/runs").status_code == 401
70
- assert client.get("/api/runs", headers={"Cookie": f"{auth.COOKIE}=gAAAAABforged"}).status_code == 401
71
- assert client.get("/api/runs", headers=cookie("alice", exp=time.time() - 10)).status_code == 401
72
- assert client.get("/api/runs", headers=cookie("alice")).status_code == 200
73
-
74
-
75
- def test_me_never_returns_the_token():
76
- body = client.get("/api/me", headers=cookie("alice")).text
77
- assert TOKENS["alice"] not in body
78
- assert json.loads(body)["user"]["name"] == "alice"
79
-
80
-
81
- # ── admin ────────────────────────────────────────────────────────────────────
82
- ADMIN_GETS = ["/api/admin/overview", "/api/admin/rollouts", "/api/admin/environments", "/api/admin/collections",
83
- "/api/admin/indexes", "/api/admin/settings", "/api/admin/audit"]
84
-
85
-
86
- @pytest.mark.parametrize("path", ADMIN_GETS)
87
- def test_admin_is_for_org_members_only(path):
88
- assert admin.get(path).status_code == 401
89
- assert admin.get(path, headers=cookie("bob")).status_code == 403
90
- assert admin.get(path, headers=cookie("alice")).status_code == 200
91
-
92
-
93
- @pytest.mark.parametrize("path", ADMIN_GETS)
94
- def test_the_public_explorer_has_no_admin(path):
95
- r = client.get(path, headers=cookie("alice"))
96
- assert r.status_code == 404 or "Admin" not in r.text # the static fallback serves nothing there
97
-
98
-
99
- def test_admin_writes_refused_for_others_and_audited():
100
- body = {"rollouts_enabled": True, "max_active": 5, "max_per_user": 2, "agents": ["opencode"], "announcement": "hi"}
101
- assert admin.put("/api/admin/settings", json=body).status_code == 401
102
- assert admin.put("/api/admin/settings", json=body, headers=cookie("bob")).status_code == 403
103
- assert admin.post("/api/admin/environments", json={"key": "org/ds", "action": "hide"}, headers=cookie("bob")).status_code == 403
104
- r = admin.put("/api/admin/settings", json=body, headers=cookie("alice"))
105
- assert r.status_code == 200 and r.json()["max_active"] == 5
106
- entry = settings.audit(1)[0]
107
- assert entry["user"] == "alice" and entry["after"]["max_active"] == 5
108
-
109
-
110
- def test_admin_input_is_validated():
111
- h = cookie("alice")
112
- assert admin.put("/api/admin/settings", json={"rollouts_enabled": True, "max_active": 0, "max_per_user": 2, "agents": []}, headers=h).status_code == 422
113
- assert admin.put("/api/admin/settings", json={"rollouts_enabled": True, "max_active": 3, "max_per_user": 2, "agents": ["rm -rf"]}, headers=h).status_code == 400
114
- bad_key = {"collections": [{"id": "x1", "group": "X", "icon": "grid", "color": "code", "ids": ["not a key"]}]}
115
- assert admin.put("/api/admin/collections", json=bad_key, headers=h).status_code == 400
116
- bad_id = {"collections": [{"id": "Bad Id", "group": "X", "icon": "grid", "color": "code", "ids": []}]}
117
- assert admin.put("/api/admin/collections", json=bad_id, headers=h).status_code == 422
118
- assert admin.post("/api/admin/environments", json={"key": "../../etc", "action": "pin"}, headers=h).status_code == 400
119
-
120
-
121
- def test_session_name_cannot_be_claimed_by_a_header():
122
- # the admin check reads the name from the encrypted session only
123
- assert admin.get("/api/admin/settings", headers={**cookie("bob"), "X-User": "alice", "X-Forwarded-User": "alice"}).status_code == 403
124
-
125
-
126
- # ── cross-site requests ──────────────────────────────────────────────────────
127
- def test_cross_site_posts_are_refused():
128
- h = cookie("alice")
129
- r = client.post("/api/runs", json={"dataset": "org/ds"}, headers={**h, "Origin": "https://evil.example"})
130
- assert r.status_code == 403
131
- r = client.post("/api/runs", json={"dataset": "org/ds"}, headers={**h, "Sec-Fetch-Site": "cross-site"})
132
- assert r.status_code == 403
133
- r = admin.put("/api/admin/settings", json={}, headers={**h, "Origin": "https://evil.example"})
134
- assert r.status_code == 403
135
-
136
-
137
- # ── rollouts: who sees what ──────────────────────────────────────────────────
138
- def test_private_rollouts_are_owner_only():
139
- r = run(visibility="private")
140
- assert client.get(f"/api/runs/{r['id']}").status_code == 404
141
- assert client.get(f"/api/runs/{r['id']}", headers=cookie("bob")).status_code == 404
142
- assert client.get(f"/api/runs/{r['id']}", headers=cookie("alice")).status_code == 200
143
- assert client.post(f"/api/runs/{r['id']}/visibility", json={"visibility": "public"}, headers=cookie("bob")).status_code == 404
144
- assert client.post(f"/api/runs/{r['id']}/cancel", headers=cookie("bob")).status_code == 404
145
-
146
-
147
- def test_public_rollouts_hide_who_ran_them():
148
- r = run(endpoint={"base_url": "https://llm.alice-corp.example/v1", "host": "llm.alice-corp.example", "model": "m"})
149
- store.write_artifact(r["id"], "trajectory.json", json.dumps([{"role": "user", "content": "hello alice, use https://llm.alice-corp.example/v1"}]))
150
- body = client.get(f"/api/runs/{r['id']}").text
151
- data = json.loads(body)
152
- assert "user" not in data["run"] and data["run"]["endpoint"] == {"custom": True}
153
- assert "alice" not in body and "alice-corp" not in body
154
-
155
-
156
- def test_ungraded_unfinished_and_restricted_rollouts_stay_off_community():
157
- keep = run(dataset="org/community-check")
158
- run(dataset="org/community-check", status="running", reward=None)
159
- run(dataset="org/community-check", status="failed", reward=None)
160
- run(dataset="org/community-check", restricted=True)
161
- run(dataset="org/community-check", visibility="private")
162
- ids = [x["id"] for x in client.get("/api/community?dataset=org/community-check").json()["runs"]]
163
- assert ids == [keep["id"]]
164
-
165
-
166
- def test_rollouts_on_private_datasets_cannot_go_public():
167
- r = run(visibility="private", restricted=True)
168
- assert client.post(f"/api/runs/{r['id']}/visibility", json={"visibility": "public"}, headers=cookie("alice")).status_code == 400
169
-
170
-
171
- def test_tokens_never_reach_the_store(monkeypatch):
172
- runnable_task(monkeypatch)
173
- r = client.post("/api/runs", json={"dataset": "org/ds", "path": "tasks/t1", "model": "m/x"}, headers=cookie("alice"))
174
- assert r.status_code == 200, r.text
175
- assert TOKENS["alice"] not in r.text
176
- for f in Path(_TMP).rglob("*"):
177
- if f.is_file():
178
- assert TOKENS["alice"].encode() not in f.read_bytes(), f
179
-
180
-
181
- def test_paused_rollouts_are_refused(monkeypatch):
182
- runnable_task(monkeypatch)
183
- settings.save("alice", {"rollouts_enabled": False})
184
- try:
185
- r = client.post("/api/runs", json={"dataset": "org/ds", "path": "tasks/t1", "model": "m/x"}, headers=cookie("alice"))
186
- assert r.status_code == 429 and "paused" in r.json()["detail"]
187
- finally:
188
- settings.save("alice", {"rollouts_enabled": True})
189
-
190
-
191
- # ── private datasets ─────────────────────────────────────────────────────────
192
- def test_private_datasets_need_a_token_that_can_read_them(monkeypatch):
193
- calls = []
194
-
195
- def fetch(spec, token): # the Hub: only alice's token can read it
196
- calls.append(token)
197
- if token == TOKENS["alice"]:
198
- return {"id": spec, "sha": "abc", "restricted": True}
199
- raise PermissionError("private")
200
-
201
- monkeypatch.setattr(catalog, "_fetch_info", fetch)
202
- catalog._memo.clear()
203
- with pytest.raises(PermissionError):
204
- catalog.info("org/private-ds")
205
- assert catalog.info("org/private-ds", TOKENS["alice"])["restricted"]
206
- with pytest.raises(PermissionError): # bob's token is checked on its own, never served alice's cached answer
207
- catalog.info("org/private-ds", TOKENS["bob"])
208
- with pytest.raises(PermissionError): # and signed out, still nothing
209
- catalog.info("org/private-ds")
210
- assert TOKENS["bob"] in calls
211
-
212
-
213
- # ── answers stay out ─────────────────────────────────────────────────────────
214
- @pytest.mark.parametrize("rel,text,withheld", [
215
- ("solution/solve.sh", None, True),
216
- ("tests/test.sh", "pytest /tests", False),
217
- ("tests/grade.py", "import json", False),
218
- ("tests/expected_output.json", None, True),
219
- ("tests/verifier/verifier_meta.json", None, True),
220
- ("tests/grade.json", None, True),
221
- ("tests/check.py", "GOLD = {'gold_answer': 42}", True),
222
- ("environment/setup/files/expected_func.json", None, True),
223
- ("instruction.md", "find the gold_answer", False),
224
- ])
225
- def test_answers_are_withheld(rel, text, withheld):
226
- assert catalog._withheld(rel, text) is withheld
227
-
228
-
229
- def test_task_toml_masks_answer_keys():
230
- out = catalog._masked_toml('[metadata]\ngold_answer = "42"\nexpected_output = "x"\ndifficulty = "easy"')
231
- assert '"42"' not in out and '"x"' not in out and 'difficulty = "easy"' in out
232
-
233
-
234
- def test_task_files_stay_inside_the_task(monkeypatch):
235
- monkeypatch.setattr(catalog, "_task_row", lambda s, p, t=None: ({}, {"sha": "abc"}))
236
- monkeypatch.setattr(catalog, "_task_texts", lambda s, sha, p, t: ({"tests/test.sh": 10, "solution/solve.sh": 5}, {"tests/test.sh": "echo"}, set()))
237
- assert catalog.task_file("org/ds", "tasks/t1", "../t2/task.toml")["error"]
238
- assert catalog.task_file("org/ds", "tasks/t1", "/etc/passwd")["error"]
239
- assert catalog.task_file("org/ds", "tasks/t1", "solution/solve.sh").get("withheld")
240
- assert catalog.task_file("org/ds", "tasks/t1", "tests/test.sh")["text"] == "echo"
241
-
242
-
243
- def test_dataset_ids_are_checked():
244
- for bad in ("../../etc", "a/b/c", "org/..", "", "org/ name"):
245
- with pytest.raises(ValueError):
246
- catalog.check_spec(bad)
247
-
248
-
249
- # ── a visitor's endpoint ─────────────────────────────────────────────────────
250
- @pytest.mark.parametrize("url", ["http://example.com/v1", "https://127.0.0.1/v1", "https://localhost/v1", "https://169.254.169.254/latest",
251
- "https://10.0.0.5/v1", "https://[::1]/v1", "https://user:pass@example.com/v1"])
252
- def test_endpoints_must_be_public_https(url):
253
- with pytest.raises(endpoints.EndpointError):
254
- endpoints.check_url(url)
255
-
256
-
257
- def test_space_hosts_are_pinned_to_hf_space():
258
- import re
259
-
260
- pat = re.compile(r"^https://[a-z0-9-]+\.hf\.space$")
261
- assert pat.match("https://openenv-coding-env.hf.space")
262
- for bad in ("https://evil.com", "https://x.hf.space.evil.com", "https://x.hf.space/../", "http://x.hf.space"):
263
- assert not pat.match(bad)
264
-
265
-
266
- # ── headers ──────────────────────────────────────────────────────────────────
267
- def test_security_headers():
268
- r = client.get("/")
269
- csp = r.headers["content-security-policy"]
270
- assert "object-src 'none'" in csp and "frame-ancestors 'self' https://huggingface.co" in csp and "script-src 'self'" in csp
271
- assert r.headers["x-content-type-options"] == "nosniff"
272
-
273
-
274
- def test_local_mode_refuses_other_hosts(monkeypatch):
275
- monkeypatch.setattr(config, "LOCAL_MODE", True)
276
- assert client.get("/api/me", headers={"Host": "evil.example"}).status_code == 403
277
- assert client.get("/api/me", headers={"Host": "localhost:8060"}).status_code == 200
278
-
279
-
280
- # ── what the admin Space changes reaches the explorer through the bucket ─────
281
- def test_admin_hides_a_rollout_from_community():
282
- r = run(dataset="org/moderation-check")
283
- assert [x["id"] for x in client.get("/api/community?dataset=org/moderation-check").json()["runs"]] == [r["id"]]
284
- assert admin.post(f"/api/admin/rollouts/{r['id']}/moderate", json={"hidden": True}, headers=cookie("alice")).status_code == 200
285
- assert client.get("/api/community?dataset=org/moderation-check").json()["runs"] == []
286
- assert client.get(f"/api/runs/{r['id']}").status_code == 404 # gone for everyone
287
- assert client.get(f"/api/runs/{r['id']}", headers=cookie("alice")).status_code == 200 # its owner still has it
288
- admin.post(f"/api/admin/rollouts/{r['id']}/moderate", json={"hidden": False}, headers=cookie("alice"))
289
- assert client.get(f"/api/runs/{r['id']}").status_code == 200
290
-
291
-
292
- def test_admin_stop_requests_reach_running_rollouts(monkeypatch):
293
- stopped = []
294
- monkeypatch.setattr(runner, "is_live", lambda rid: True)
295
- monkeypatch.setattr(runner, "cancel", lambda rid: stopped.append(rid) or True)
296
- r = run(status="running", reward=None, updated_at=time.time())
297
- assert admin.post(f"/api/admin/rollouts/{r['id']}/cancel", headers=cookie("alice")).status_code == 200
298
- assert admin.post(f"/api/admin/rollouts/{r['id']}/cancel", headers=cookie("bob")).status_code == 403
299
- import threading
300
-
301
- threading.Thread(target=runner.watch_cancel_requests, daemon=True).start()
302
- for _ in range(40):
303
- if r["id"] in stopped:
304
- break
305
- time.sleep(0.1)
306
- assert r["id"] in stopped
307
-
308
-
309
- def test_answers_written_into_grader_scripts_are_masked():
310
- sh = "expected_answer=$(cat <<'ANSWER_EOF'\n138236\nANSWER_EOF\n)\nEXPECTED=\"42\"\nagent=$(cat /workdir/answer.txt)\n"
311
- out = catalog._mask_answers("tests/test.sh", sh)
312
- assert "138236" not in out and '"42"' not in out and "cat /workdir/answer.txt" in out
313
- py = 'GOLD_ANSWER = {"x": 1}\nresult = run()\n'
314
- assert "{\"x\": 1}" not in catalog._mask_answers("tests/grade.py", py)
315
- assert catalog._mask_answers("instruction.md", "expected = 5") == "expected = 5" # only grader files
316
-
317
-
318
- def test_no_network_tasks_are_refused_with_a_reason():
319
- r = catalog.runnable({"env": {"network": "no-network", "image": "python:3.12"}}, {})
320
- assert not r["ok"] and "internet" in r["why"]
321
-
322
-
323
- def test_indexing_is_capped(monkeypatch):
324
- monkeypatch.setattr(catalog, "info", lambda spec, token=None: {"sha": "s1", "restricted": False})
325
- monkeypatch.setattr(catalog, "_read_index", lambda spec, sha=None: None)
326
- monkeypatch.setattr(catalog, "_build", lambda *a, **k: None)
327
- with catalog._jobs_lock:
328
- saved = dict(catalog._jobs)
329
- catalog._jobs.clear()
330
- catalog._jobs.update({f"o/busy{i}": {"state": "reading", "sha": "x"} for i in range(config.MAX_INDEX_JOBS)})
331
- try:
332
- with pytest.raises(RuntimeError):
333
- catalog.index_status("o/new-one")
334
- finally:
335
- with catalog._jobs_lock:
336
- catalog._jobs.clear()
337
- catalog._jobs.update(saved)
338
-
339
-
340
- def test_huge_tasks_are_refused(monkeypatch):
341
- runnable_task(monkeypatch, bytes=50 * 1024**3)
342
- r = client.post("/api/runs", json={"dataset": "org/ds", "path": "tasks/t1", "model": "m/x"}, headers=cookie("alice"))
343
- assert r.status_code == 400 and "GB" in r.json()["detail"]
344
-
345
-
346
- # ── the run list agrees with the run files ───────────────────────────────────
347
- def test_list_follows_records_written_elsewhere():
348
- r = run(status="running", reward=None)
349
- store.list_runs() # index it
350
- doc = json.loads((store.RUNS / r["id"] / "run.json").read_text())
351
- time.sleep(0.01)
352
- doc.update(status="done", reward=1.0, updated_at=time.time())
353
- (store.RUNS / r["id"] / "run.json").write_text(json.dumps(doc)) # another process finishes it
354
- store._synced_at = 0
355
- assert next(x for x in store.list_runs() if x["id"] == r["id"])["status"] == "done"
356
-
357
-
358
- def test_only_silent_rollouts_are_marked_interrupted():
359
- busy = run(status="running", reward=None) # touched just now: someone is running it
360
- lost = run(status="running", reward=None)
361
- doc = json.loads((store.RUNS / lost["id"] / "run.json").read_text())
362
- doc["updated_at"] = time.time() - 3600 # silent for an hour: its worker is gone
363
- (store.RUNS / lost["id"] / "run.json").write_text(json.dumps(doc))
364
- store._synced_at = 0
365
- store.mark_interrupted(set())
366
- assert store.get(busy["id"])["status"] == "running"
367
- assert store.get(lost["id"])["status"] == "interrupted"
368
-
369
-
370
- # ── a task's ${VAR}s never reach this server's environment ───────────────────
371
- def test_task_variables_never_come_from_this_servers_environment(monkeypatch):
372
- monkeypatch.setenv("SESSION_SECRET_PROBE", "s3cret-value")
373
- monkeypatch.setenv("HF_TOKEN", "hf_operator_token")
374
- with pytest.raises(runner.MissingTaskEnv):
375
- runner.resolve_task_env({"X": "${SESSION_SECRET_PROBE}"})
376
- with pytest.raises(runner.MissingTaskEnv):
377
- runner.resolve_task_env({"KEY": "${HF_TOKEN}"})
378
- assert runner.resolve_task_env({"A": "${HF_TOKEN:-none}", "B": "literal"}) == {"A": "none", "B": "literal"}
379
- token = runner._TASK_ENV.set({"environment": {"OPENAI_API_KEY": "cap-123"}, "verifier": {"JUDGE": "cap-456"}})
380
- try:
381
- assert runner.resolve_task_env({"K": "${OPENAI_API_KEY}"}) == {"K": "cap-123"}
382
- with pytest.raises(runner.MissingTaskEnv): # the grader's values never reach the agent's phase
383
- runner.resolve_task_env({"J": "${JUDGE}"})
384
- assert runner._resolver("verifier")({"J": "${JUDGE}"}) == {"J": "cap-456"}
385
- finally:
386
- runner._TASK_ENV.reset(token)
387
-
388
-
389
- def test_harbor_and_its_agents_are_cut_off_from_this_servers_secrets(monkeypatch):
390
- monkeypatch.setenv("HF_TOKEN", "hf_operator_token")
391
- monkeypatch.setenv("OAUTH_CLIENT_SECRET", "oauth-secret")
392
- monkeypatch.setenv("PATH_LIKE_SETTING", "fine")
393
- runner._patch_harbor_env()
394
- import harbor.environments.base as env_base
395
- import harbor.trial.trial as trial
396
- import harbor.verifier.verifier as verifier
397
- from harbor.agents import base as agent_base
398
- from harbor.utils import env as harbor_env
399
- for mod, phase in ((env_base, "environment"), (trial, "any"), (verifier, "verifier"), (harbor_env, "environment")):
400
- assert getattr(mod.resolve_env_vars, "_rlx_phase", None) == phase, mod.__name__
401
- host = runner._HostEnv()
402
- assert "HF_TOKEN" not in host and "OAUTH_CLIENT_SECRET" not in host and host.get("PATH_LIKE_SETTING") == "fine"
403
- fake = type("A", (), {"_extra_env": {}})()
404
- sources = agent_base.BaseAgent._env_sources(fake)
405
- assert all("HF_TOKEN" not in s and "OAUTH_CLIENT_SECRET" not in s for s in sources)
406
-
407
-
408
- def test_a_public_rollouts_files_are_images_only_to_others():
409
- r = run(user="alice", status="done", reward=1.0, visibility="public")
410
- store.write_artifact(r["id"], "trajectory.json", '[{"role": "user", "content": "from alice"}]')
411
- store.write_artifact(r["id"], "screenshot.jpg", b"\xff\xd8\xff")
412
- assert client.get(f"/api/runs/{r['id']}/artifacts/trajectory.json").status_code == 404
413
- assert client.get(f"/api/runs/{r['id']}/artifacts/screenshot.jpg").status_code == 200
414
- assert client.get(f"/api/runs/{r['id']}/artifacts/trajectory.json", headers=cookie("alice")).status_code == 200
415
- assert client.get(f"/api/runs/{r['id']}/artifacts/run.json", headers=cookie("alice")).status_code == 404
416
-
417
-
418
- # ── sign-in ──────────────────────────────────────────────────────────────────
419
- def _hub_signin(monkeypatch):
420
- from types import SimpleNamespace
421
-
422
- monkeypatch.setattr(auth.httpx, "post", lambda *a, **k: SimpleNamespace(status_code=200, json=lambda: {"access_token": "hf_oauth_x", "scope": "openid inference-api jobs", "expires_in": 3600}))
423
- monkeypatch.setattr(auth.httpx, "get", lambda *a, **k: SimpleNamespace(json=lambda: {"preferred_username": "carol"}))
424
-
425
-
426
- def test_oauth_state_lives_in_a_signed_cookie_for_one_browser(monkeypatch):
427
- _hub_signin(monkeypatch)
428
- c = TestClient(main.app, base_url="https://testserver")
429
- r = c.get("/login", follow_redirects=False)
430
- assert r.status_code in (302, 307)
431
- state = dict(x.split("=", 1) for x in r.headers["location"].split("?", 1)[1].split("&"))["state"]
432
- sent = r.cookies.get(auth.STATE_COOKIE)
433
- assert sent and state not in sent # encrypted, not the state itself
434
- other = TestClient(main.app, base_url="https://testserver") # another browser, the same callback URL: refused
435
- assert other.get(f"/login/callback?code=x&state={state}", follow_redirects=False).status_code == 400
436
- assert c.get("/login/callback?code=x&state=forged", cookies={auth.STATE_COOKIE: sent}, follow_redirects=False).status_code == 400
437
- ok = c.get(f"/login/callback?code=x&state={state}", cookies={auth.STATE_COOKIE: sent}, follow_redirects=False)
438
- assert ok.status_code in (302, 307) and auth.COOKIE in ok.headers.get("set-cookie", "")
439
-
440
-
441
- def test_an_expired_sign_in_is_refused(monkeypatch):
442
- _hub_signin(monkeypatch)
443
- old = auth._box.encrypt_at_time(b"s1", int(time.time()) - auth.STATE_TTL - 5).decode()
444
- r = client.get("/login/callback?code=x&state=s1", cookies={auth.STATE_COOKIE: old}, follow_redirects=False)
445
- assert r.status_code == 400
446
-
447
-
448
- def test_a_cancelled_sign_in_goes_home():
449
- r = client.get("/login/callback?error=access_denied", follow_redirects=False)
450
- assert r.status_code in (302, 307) and r.headers["location"] == "/"
451
-
452
-
453
- def test_pasting_tokens_is_rate_limited(monkeypatch):
454
- from types import SimpleNamespace
455
-
456
- monkeypatch.setattr(auth, "TOKEN_LIMIT", auth.Limiter(3, 600))
457
- monkeypatch.setattr(auth.httpx, "get", lambda *a, **k: SimpleNamespace(status_code=401))
458
- codes = [client.post("/api/login/token", json={"token": "hf_" + "x" * 30}).status_code for _ in range(5)]
459
- assert codes == [400, 400, 400, 429, 429]
460
-
461
-
462
- def test_two_submits_at_once_cant_both_take_the_last_place(monkeypatch):
463
- import threading as th
464
-
465
- runnable_task(monkeypatch)
466
- runner._live.clear()
467
- real_create = store.create
468
-
469
- def slow_create(doc): # the record takes a moment to write: the window two submits used to race through
470
- time.sleep(0.3)
471
- return real_create(doc)
472
-
473
- monkeypatch.setattr(store, "create", slow_create)
474
- before = {k: settings.get(k) for k in ("max_per_user", "max_active")}
475
- settings.save("alice", {"rollouts_enabled": True, "max_per_user": 1, "max_active": 5})
476
- codes: list[int] = []
477
- try:
478
- ts = [th.Thread(target=lambda: codes.append(client.post("/api/runs", json={"dataset": "org/ds", "path": "tasks/t1", "model": "m/x"},
479
- headers=cookie("bob")).status_code)) for _ in range(3)]
480
- [t.start() for t in ts]
481
- [t.join() for t in ts]
482
- assert sorted(codes) == [200, 429, 429]
483
- assert runner._reserved == {} # every reservation let go, the refused ones too
484
- finally:
485
- runner._live.clear()
486
- settings.save("alice", {"rollouts_enabled": True, **{k: v for k, v in before.items() if v is not None}})
487
-
488
-
489
- def test_a_live_rollouts_events_stay_local_until_flushed(monkeypatch):
490
- r = run(user="alice", status="running")
491
- store.append_events(r["id"], [{"kind": "turn", "n": 1}])
492
- on_store = (store._dir(r["id"]) / "events.jsonl")
493
- assert store.read_events(r["id"]) == [{"kind": "turn", "n": 1}] # the page reads the local copy at once
494
- assert on_store.read_text() == "" # the store (a bucket) wasn't rewritten per event
495
- monkeypatch.setattr(store, "FLUSH_EVERY", 0.0)
496
- store.flush_due()
497
- assert json.loads(on_store.read_text().splitlines()[0]) == {"kind": "turn", "n": 1}
498
- store.append_events(r["id"], [{"kind": "turn", "n": 2}])
499
- store.update(r["id"], status="done") # it ended: everything is on the store, no local copy
500
- assert len(on_store.read_text().splitlines()) == 2 and not (store.LOCAL / r["id"]).exists()
501
- assert store.read_events(r["id"], after=1) == [{"kind": "turn", "n": 2}]
502
-
503
-
504
- def test_events_a_stopped_process_left_on_local_disk_are_kept():
505
- r = run(user="alice", status="running")
506
- store.append_events(r["id"], [{"kind": "turn", "n": 1}])
507
- store._local.pop(r["id"]) # the process stopped: nothing in memory, the local file left behind
508
- store._write_json(store._dir(r["id"]) / "run.json", {**store.get(r["id"]), "updated_at": time.time() - store.STALE - 10})
509
- store._index_put(store.get(r["id"]))
510
- assert store.mark_interrupted(set()) >= 1
511
- assert store.get(r["id"])["status"] == "interrupted"
512
- assert store.read_events(r["id"]) == [{"kind": "turn", "n": 1}] and not (store.LOCAL / r["id"]).exists()
513
-
514
-
515
- def test_a_visitors_endpoint_cant_rebind_to_a_private_address_later(monkeypatch):
516
- import socket
517
-
518
- answers = {"model.example": "93.184.216.34", "internal.example": "10.0.0.5"}
519
-
520
- def fake(host, port, *a, **k):
521
- return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (answers[host], port))]
522
-
523
- monkeypatch.setattr(endpoints, "_real_getaddrinfo", fake)
524
- real = socket.getaddrinfo
525
- try:
526
- assert endpoints.check_url("https://model.example/v1") == "https://model.example/v1"
527
- assert socket.getaddrinfo("model.example", 443)[0][4][0] == "93.184.216.34"
528
- answers["model.example"] = "169.254.169.254" # the name now points at the metadata service
529
- with pytest.raises(socket.gaierror):
530
- socket.getaddrinfo("model.example", 443) # any client in this process (the capture proxy's too)
531
- assert socket.getaddrinfo("internal.example", 443)[0][4][0] == "10.0.0.5" # names nobody gave us: untouched
532
- finally:
533
- socket.getaddrinfo = real
534
- endpoints._untrusted.discard("model.example")
535
-
536
-
537
- def test_a_rollout_never_downloads_reference_solutions(monkeypatch):
538
- import huggingface_hub
539
-
540
- seen = {}
541
- monkeypatch.setattr(catalog, "info", lambda spec, token=None: {"sha": "abc", "restricted": False})
542
- monkeypatch.setattr(huggingface_hub, "snapshot_download", lambda *a, **k: seen.update(k))
543
- catalog.task_dir("org/ds", "tasks/t1")
544
- from fnmatch import fnmatch
545
- for f in ("tasks/t1/solution/solve.sh", "tasks/t1/steps/s1/solution/solve.sh", "tasks/t1/steps/two/solution/deep/x.py"):
546
- assert any(fnmatch(f, p) for p in seen["ignore_patterns"]), f
547
- assert not any(fnmatch("tasks/t1/steps/s1/instruction.md", p) for p in seen["ignore_patterns"])
548
-
549
-
550
- def test_the_admin_space_has_health_checks_and_nothing_else_open():
551
- assert admin.get("/healthz").json() == {"ok": True}
552
- r = admin.get("/readyz")
553
- assert r.status_code == 200 and r.json()["store"] == "ok"
554
- assert admin.get("/api/admin/settings").status_code == 401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_seo.py DELETED
@@ -1,118 +0,0 @@
1
- """Every page at its own address, findable and previewable (app/seo.py), without starting work or leaking anything
2
- (no network: the catalog's listing is stubbed).
3
-
4
- What is checked: each page answers with its own title, description, canonical address and schema.org data; what a
5
- page shows comes escaped (a task title can't inject markup or end the JSON-LD script); pages that are personal or
6
- unknown aren't indexed; old addresses move for good; robots.txt keeps crawlers off the API and rollouts; the
7
- sitemap lists environments and indexed tasks; preview images are images; unknown paths are 404 pages.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import json
13
- import re
14
-
15
- import pytest
16
- from fastapi.testclient import TestClient
17
-
18
- from app import main, seo, snapshot
19
-
20
- client = TestClient(main.app, base_url="https://testserver")
21
- EVIL = '</script><script>alert(1)</script>"<b>'
22
- ROWS = [{"id": "org/ds", "key": "org/ds", "kind": "dataset", "heading": f"Bench {EVIL}", "brief": "A bench.", "tags": ["code"], "framework": "harbor",
23
- "indexed": {"tasks": 2}, "updated": "2026-10-01T00:00:00+00:00", "downloads": 10, "likes": 2, "trending": 1},
24
- {"id": "org/sp", "key": "space:org/sp", "kind": "space", "heading": "Game", "brief": "A game env.", "tags": ["openenv"], "trending": 0,
25
- "framework": "openenv", "likes": 3},
26
- {"id": "org/copy", "key": "space:org/copy", "kind": "space", "heading": "Echo copy", "tags": ["openenv"], "framework": "openenv", "likes": 0}]
27
- TASKS = [{"path": "tasks/a", "title": f"Fix it {EVIL}", "brief": "Do it.", "category": "code"}, {"path": "tasks/b", "title": "B", "brief": "", "category": None}]
28
-
29
-
30
- @pytest.fixture(autouse=True)
31
- def stubs(monkeypatch):
32
- snapshot.reset() # seo reads the listing through the snapshot (a live one here): built from these rows
33
- seo._listing.update(at=0.0, by={}, rows=[])
34
- seo._sitemap.update(at=0.0, pages=[], tasks=[])
35
- monkeypatch.setattr(seo.catalog, "environments", lambda include_hidden=False: ROWS)
36
- monkeypatch.setattr(seo, "index_rows", lambda spec: TASKS if spec == "org/ds" else [])
37
-
38
-
39
- def head(html: str) -> dict:
40
- return {"title": re.search(r"<title>(.*?)</title>", html).group(1),
41
- "description": re.search(r'<meta name="description" content="([^"]*)"', html).group(1),
42
- "canonical": re.search(r'rel="canonical" href="([^"]*)"', html).group(1),
43
- "robots": re.search(r'name="robots" content="([^"]*)"', html).group(1),
44
- "ld": [json.loads(x) for x in re.findall(r'<script type="application/ld\+json">(.*?)</script>', html, re.S)]}
45
-
46
-
47
- def test_every_page_has_its_own_head():
48
- for path, kind in [("/", "WebSite"), ("/d/org/ds", "Dataset"), ("/t/org/ds/tasks/a", "CreativeWork"), ("/s/org/sp", "SoftwareApplication")]:
49
- r = client.get(path)
50
- assert r.status_code == 200 and r.headers["content-type"].startswith("text/html")
51
- h = head(r.text)
52
- assert h["canonical"] == "https://testserver" + path and "index" in h["robots"] and not h["robots"].startswith("noindex")
53
- assert kind in [x["@type"] for x in h["ld"]] and h["description"]
54
- assert "Bench" in head(client.get("/d/org/ds").text)["title"]
55
- assert client.get("/t/org/ds/tasks/a").text.count('href="/t/') == 0 # a task page links up, not sideways
56
- assert client.get("/d/org/ds").text.count('href="/t/org/ds/tasks/') == 2 # crawlable links to its tasks
57
-
58
-
59
- def test_what_a_page_shows_is_escaped():
60
- for path in ("/d/org/ds", "/t/org/ds/tasks/a", "/"):
61
- html = client.get(path).text
62
- lds = re.findall(r'<script type="application/ld\+json">(.*?)</script>', html, re.S)
63
- for x in lds:
64
- assert "</" not in x # nothing in the data can end its script early
65
- json.loads(x)
66
- rest = re.sub(r'<script type="application/ld\+json">.*?</script>', "", html, flags=re.S)
67
- assert "<script>alert(1)" not in rest and '"<b>' not in rest, path
68
- assert "&lt;/script&gt;" in rest or path == "/" # shown, as text
69
-
70
-
71
- def test_personal_and_unknown_pages_arent_indexed():
72
- for path in ("/runs", "/run/20260101-000000-abcdef", "/compare/org/ds/tasks/a?r=x,y", "/d/someone/unknown", "/t/org/ds/tasks/zzz"):
73
- assert head(client.get(path).text)["robots"].startswith("noindex"), path
74
- r = client.get("/nowhere/at/all")
75
- assert r.status_code == 404 and head(r.text)["robots"].startswith("noindex")
76
- assert client.get("/api/nothing").json() == {"detail": "Not Found"}
77
-
78
-
79
- def test_old_addresses_move_for_good():
80
- for old, new in [("/r/org/ds?c=default&s=train&i=3", "/t/org/ds/train/3"), ("/r/org/ds?c=code&s=train&i=6", "/t/org/ds/code/train/6"),
81
- ("/task/music-1", f"/t/{seo.MIMO}/music-1"), ("/rewards", f"/d/{seo.MIMO}?rewards=1")]:
82
- r = client.get(old, follow_redirects=False)
83
- assert r.status_code == 301 and r.headers["location"] == new, old
84
-
85
-
86
- def test_robots_and_sitemap():
87
- t = client.get("/robots.txt").text
88
- for line in ("Disallow: /api/", "Disallow: /mcp/", "Disallow: /run/", "Sitemap: https://testserver/sitemap.xml"):
89
- assert line in t
90
- idx = client.get("/sitemap.xml").text
91
- assert "<sitemapindex" in idx and "/sitemap-pages.xml" in idx and "/sitemap-tasks-1.xml" in idx
92
- pages = client.get("/sitemap-pages.xml").text
93
- assert "https://testserver/d/org/ds" in pages and "https://testserver/s/org/sp" in pages and "<lastmod>2026-10-01</lastmod>" in pages
94
- assert "https://testserver/s/org/copy" not in pages # an unliked, sleeping copy isn't put forward
95
- tasks = client.get("/sitemap-tasks-1.xml").text
96
- assert "https://testserver/t/org/ds/tasks/a" in tasks
97
- assert client.get("/sitemap-tasks-99.xml").status_code == 404
98
-
99
-
100
- def test_preview_images_are_images():
101
- for path in ("/og.png", "/og/d/org/ds.png", "/og/t/org/ds/tasks/a.png", "/og/s/org/sp.png"):
102
- r = client.get(path)
103
- assert r.status_code == 200 and r.headers["content-type"] == "image/png" and r.content[:8] == b"\x89PNG\r\n\x1a\n", path
104
- assert client.get("/og/d/org/ds.jpg").status_code == 404
105
- # nothing listed there: no image drawn (and its page points at the site's card instead)
106
- for path in ("/og/d/nobody/nothing.png", "/og/s/nobody/nothing.png", "/og/whatever.png", "/og/x/y/z.png"):
107
- assert client.get(path).status_code == 404, path
108
- assert 'content="https://testserver/og.png"' in client.get("/d/nobody/nothing").text
109
-
110
-
111
- def test_a_card_section_heading_is_not_a_datasets_name():
112
- from app.catalog import _card_text
113
-
114
- desc = "NVIDIA NeMo Gym knowledge MCQA dataset for training models on multiple choice questions across domains."
115
- assert _card_text(f"Dataset Description:\n\n{desc}") == (None, desc)
116
- assert _card_text(f"Overview\n\n{desc}")[0] is None
117
- assert _card_text(f"Dataset Card for Terminal Bench\n\n{desc}")[0] == "Terminal Bench"
118
- assert _card_text(f"Terminal-Bench 2.1\n\n{desc}")[0] == "Terminal-Bench 2.1"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_snapshot.py DELETED
@@ -1,456 +0,0 @@
1
- """Catalog snapshots (app/snapshot.py) and the indexer that publishes them (app/indexer.py), with no network.
2
-
3
- What is checked: a snapshot built from listing rows holds only public environments and their tasks' public fields
4
- (no private or gated dataset, no index built for a signed-in visitor, no metadata values); publishing writes the
5
- database first and the pointer last, and the app swaps to it while a query on the old one finishes; a pointer whose
6
- database is corrupt, truncated or not the file it names is rejected and the app keeps what it had (or falls back to
7
- the live catalog); a newer schema's pointer is left alone; the indexer rebuilds only indexes whose dataset changed,
8
- within its budget, doesn't retry a failure at the same revision, refuses a listing that shrank, publishes nothing
9
- when any step fails, reruns safely and prunes old snapshots; the same works through the bucket API.
10
- """
11
-
12
- from __future__ import annotations
13
-
14
- import gzip
15
- import json
16
- import shutil
17
- import threading
18
- import time
19
- from pathlib import Path
20
- from types import SimpleNamespace
21
-
22
- import pytest
23
-
24
- from app import catalog, config, indexer, snapshot
25
-
26
-
27
- def ds(i: int, **kw) -> dict:
28
- return {"id": f"snaptest/d{i}", "key": f"snaptest/d{i}", "kind": "dataset", "framework": "harbor", "heading": f"D{i}",
29
- "brief": "tasks", "tags": ["t"], "badges": [], "downloads": i, "likes": 0, "trending": i,
30
- "updated": "2026-09-01T00:00:00+00:00", **kw}
31
-
32
-
33
- def sp(i: int, **kw) -> dict:
34
- return {"id": f"snaptest/s{i}", "key": f"space:snaptest/s{i}", "kind": "space", "framework": "openenv", "openenv": True,
35
- "heading": None, "brief": "", "tags": [], "badges": ["OpenEnv"], "downloads": 0, "likes": i, "trending": 0, **kw}
36
-
37
-
38
- ROWS = [ds(0), ds(1), ds(2, private=True), ds(3, gated=True), ds(4), sp(0), sp(1), sp(2)]
39
- SECRET = "SECRET-ANSWER-4242"
40
-
41
-
42
- def task(path: str, title: str = "A task") -> dict:
43
- return {"path": path, "title": title, "brief": "Do the thing.", "category": "c", "difficulty": "easy", "tags": ["x"],
44
- "meta": {"gold_answer": SECRET, "note": SECRET}, "withheld": ["gold_answer"], "verifier": {"kind": "txt", "env": ["OPENAI_KEY"]},
45
- "env": {"image": "img", "gpus": 0}, "run": "image"}
46
-
47
-
48
- def write_index(spec: str, tasks: list[dict], *, restricted: bool = False, version: int | None = None, sha: str = "sha1",
49
- built: float = 1e9, partial: bool = False) -> None:
50
- idx = {"spec": spec, "sha": sha, "built": built, "version": version or catalog.INDEX_VERSION, "info": {"restricted": restricted},
51
- "tasks": tasks, "summary": {}, **({"partial": True} if partial else {})}
52
- catalog.atomic_write(catalog._index_path(spec), gzip.compress(json.dumps(idx).encode()))
53
- head = {"tasks": len(tasks), "graded": {"txt": len(tasks)}, "image": len(tasks), "built": built, "sha": sha,
54
- "version": version or catalog.INDEX_VERSION}
55
- catalog.atomic_write(catalog._head_path(spec), json.dumps(head).encode())
56
- catalog._heads.pop(spec, None)
57
-
58
-
59
- @pytest.fixture(autouse=True)
60
- def isolated(monkeypatch, tmp_path):
61
- monkeypatch.setenv("RLX_SNAPSHOT_LOCAL", str(tmp_path / "local"))
62
- monkeypatch.setenv("RLX_SNAPSHOT_STORE", str(tmp_path / "store"))
63
- monkeypatch.setattr(catalog, "environments", lambda include_hidden=False: ROWS)
64
- monkeypatch.setattr(catalog, "hidden", lambda: set())
65
- write_index("snaptest/d0", [task("tasks/a", "Alpha"), task("tasks/b", "Beta")])
66
- write_index("snaptest/d2", [task("tasks/p")]) # a private dataset's index (a visitor built it)
67
- write_index("snaptest/d4", [task("tasks/r")], restricted=True) # an index built with a visitor's token
68
- snapshot.reset()
69
- listing = dict(catalog._env_list)
70
- had_listing = (config.STORAGE_DIR / "listing.json.gz").exists()
71
- yield
72
- snapshot.reset()
73
- for p in list(config.INDEX_DIR.glob("snaptest__*")):
74
- p.unlink(missing_ok=True)
75
- (config.STORAGE_DIR / indexer.STATE).unlink(missing_ok=True)
76
- catalog._env_list.clear()
77
- catalog._env_list.update(listing) # the indexer sets catalog's listing: other tests mustn't see these rows
78
- if not had_listing:
79
- (config.STORAGE_DIR / "listing.json.gz").unlink(missing_ok=True)
80
-
81
-
82
- def store_of(tmp_path: Path) -> snapshot.LocalStore:
83
- return snapshot.LocalStore(tmp_path / "store")
84
-
85
-
86
- def build(tmp_path: Path, rows=ROWS, name: str = "build.db") -> tuple[Path, dict]:
87
- db = tmp_path / name
88
- counts = indexer.build_snapshot(rows, db, {"test": True})
89
- return db, counts
90
-
91
-
92
- # ── what a snapshot holds ────────────────────────────────────────────────────
93
- def test_snapshot_holds_public_rows_and_public_fields_only(tmp_path):
94
- db, counts = build(tmp_path)
95
- assert counts["envs"] == 6 and counts["datasets"] == 3 and counts["spaces"] == 3
96
- assert counts["tasks"] == 2 and counts["task_envs"] == 1 # d0's; not the private one's, nor the restricted index's
97
- raw = db.read_bytes()
98
- assert SECRET.encode() not in raw and b"gold_answer" not in raw and b"OPENAI_KEY" not in raw
99
- import sqlite3
100
-
101
- c = sqlite3.connect(db)
102
- assert c.execute("PRAGMA journal_mode").fetchone()[0] != "wal"
103
- assert {r[0] for r in c.execute("SELECT key FROM envs")} == {"snaptest/d0", "snaptest/d1", "snaptest/d4", "space:snaptest/s0",
104
- "space:snaptest/s1", "space:snaptest/s2"}
105
- assert c.execute("SELECT tasks, framework FROM envs WHERE key = 'snaptest/d0'").fetchone() == (2, "harbor")
106
- assert json.loads(c.execute("SELECT v FROM meta WHERE k = 'schema'").fetchone()[0]) == snapshot.SCHEMA
107
- names = {r[0] for r in c.execute("SELECT name FROM sqlite_master WHERE type = 'index'")}
108
- assert {"envs_trending", "envs_downloads", "envs_likes", "envs_tasks", "envs_updated", "envs_created", "tasks_env"} <= names
109
- assert c.execute("SELECT COUNT(*) FROM sqlite_stat1").fetchone()[0] > 0 # ANALYZE ran
110
-
111
-
112
- def test_harbor_whenever_an_index_found_tasks(tmp_path):
113
- rows = [ds(0, framework="verifiers"), ds(1, framework="verifiers")]
114
- db, _ = build(tmp_path, rows)
115
- import sqlite3
116
-
117
- got = dict(sqlite3.connect(db).execute("SELECT id, framework FROM envs"))
118
- assert got == {"snaptest/d0": "harbor", "snaptest/d1": "verifiers"}
119
-
120
-
121
- # ── publishing and swapping ──────────────────────────────────────────────────
122
- class Recording(snapshot.LocalStore):
123
- def __init__(self, root):
124
- super().__init__(root)
125
- self.ops: list[tuple[str, str]] = []
126
-
127
- def put_file(self, src, rel):
128
- self.ops.append(("file", rel))
129
- super().put_file(src, rel)
130
-
131
- def put_bytes(self, rel, data):
132
- self.ops.append(("bytes", rel))
133
- super().put_bytes(rel, data)
134
-
135
-
136
- def test_database_first_pointer_last(tmp_path):
137
- store = Recording(tmp_path / "store")
138
- db, counts = build(tmp_path)
139
- ptr = indexer.publish(store, db, counts)
140
- assert store.ops == [("file", ptr["db"]), ("bytes", snapshot.POINTER)]
141
- assert snapshot.read_pointer(store) == ptr and ptr["sha256"] == snapshot.sha256_file(tmp_path / "store" / ptr["db"])
142
- assert ptr["counts"]["envs"] == 6 and ptr["schema"] == snapshot.SCHEMA
143
-
144
-
145
- def test_app_swaps_to_a_new_snapshot_while_a_query_finishes(tmp_path):
146
- store = store_of(tmp_path)
147
- db, counts = build(tmp_path)
148
- first = indexer.publish(store, db, counts)
149
- assert snapshot.check(store) and snapshot.current().name == first["db"]
150
- with snapshot.use() as (old, conn):
151
- assert conn.execute("SELECT COUNT(*) FROM envs").fetchone()[0] == 6
152
- db2, counts2 = build(tmp_path, ROWS + [ds(9)], "build2.db")
153
- second = indexer.publish(store, db2, counts2, now=snapshot.datetime(2030, 1, 1, tzinfo=snapshot.UTC))
154
- assert snapshot.check(store) # swapped under a running query
155
- assert snapshot.current().name == second["db"] and old.name == first["db"]
156
- assert conn.execute("SELECT COUNT(*) FROM envs").fetchone()[0] == 6 # the old one still answers
157
- assert not old._closed
158
- assert old._closed # closed once its last query was done
159
- with snapshot.use() as (new, conn):
160
- assert new.name == second["db"] and conn.execute("SELECT COUNT(*) FROM envs").fetchone()[0] == 7
161
- assert not snapshot.check(store) # nothing new: no work
162
-
163
-
164
- def test_concurrent_queries_during_swaps(tmp_path):
165
- store = store_of(tmp_path)
166
- db, counts = build(tmp_path)
167
- indexer.publish(store, db, counts)
168
- snapshot.check(store)
169
- errors: list[BaseException] = []
170
- stop = threading.Event()
171
-
172
- def reader():
173
- while not stop.is_set():
174
- try:
175
- with snapshot.use() as (_, conn):
176
- assert conn.execute("SELECT COUNT(*) FROM envs").fetchone()[0] in (6, 7)
177
- except BaseException as e: # noqa: BLE001
178
- errors.append(e)
179
-
180
- threads = [threading.Thread(target=reader) for _ in range(6)]
181
- for t in threads:
182
- t.start()
183
- for i in range(6):
184
- rows = ROWS + ([ds(9)] if i % 2 == 0 else [])
185
- dbi, ci = build(tmp_path, rows, f"b{i}.db")
186
- indexer.publish(store, dbi, ci, now=snapshot.datetime(2030, 1, 1, 0, 0, i, tzinfo=snapshot.UTC))
187
- assert snapshot.check(store)
188
- stop.set()
189
- for t in threads:
190
- t.join()
191
- assert not errors
192
-
193
-
194
- def test_corrupt_or_mismatched_snapshots_are_rejected(tmp_path):
195
- store = store_of(tmp_path)
196
- good_db, counts = build(tmp_path)
197
- good = indexer.publish(store, good_db, counts)
198
- assert snapshot.check(store)
199
- root = tmp_path / "store"
200
-
201
- def point(name: str, data: bytes, sha: str | None = None, **extra) -> None:
202
- (root / name).write_bytes(data)
203
- ptr = {"db": name, "sha256": sha or snapshot.hashlib.sha256(data).hexdigest(), "size": len(data), "built_at": "x",
204
- "schema": snapshot.SCHEMA, "counts": counts, **extra}
205
- store.put_bytes(snapshot.POINTER, json.dumps(ptr).encode())
206
-
207
- raw = good_db.read_bytes()
208
- point("snapshots/catalog-20300101T000001Z.db", raw, sha="0" * 64) # not the file the pointer names
209
- assert not snapshot.check(store) and "sha256" in snapshot.status()["error"]
210
- point("snapshots/catalog-20300101T000002Z.db", b"not a database" * 100) # garbage with a matching sha
211
- assert not snapshot.check(store)
212
- point("snapshots/catalog-20300101T000003Z.db", raw[: len(raw) // 2]) # truncated
213
- assert not snapshot.check(store)
214
- point("snapshots/catalog-20300101T000004Z.db", raw, counts={**counts, "envs": 99}) # not what the pointer says
215
- assert not snapshot.check(store)
216
- store.put_bytes(snapshot.POINTER, b"{not json")
217
- assert not snapshot.check(store)
218
- store.put_bytes(snapshot.POINTER, json.dumps({"db": "../../etc/passwd", "sha256": "0" * 64, "schema": 1}).encode())
219
- assert not snapshot.check(store)
220
- assert snapshot.current().name == good["db"] # the good one stayed in use
221
- assert not list((tmp_path / "local").glob("catalog-20300101T00000[234]Z.db")) # rejected files aren't kept
222
-
223
-
224
- def test_no_usable_snapshot_falls_back_to_the_live_catalog(tmp_path):
225
- store = store_of(tmp_path)
226
- (tmp_path / "store" / "snapshots").mkdir(parents=True)
227
- (tmp_path / "store" / "snapshots" / "catalog-20300101T000000Z.db").write_bytes(b"garbage")
228
- store.put_bytes(snapshot.POINTER, json.dumps({"db": "snapshots/catalog-20300101T000000Z.db", "sha256": "1" * 64,
229
- "schema": snapshot.SCHEMA, "counts": {}}).encode())
230
- with snapshot.use() as (snap, conn):
231
- assert snap.source == "live" and conn.execute("SELECT COUNT(*) FROM envs").fetchone()[0] == 6
232
- assert conn.execute("SELECT COUNT(*) FROM tasks").fetchone()[0] == 2
233
- assert snapshot.status()["source"] == "live" and "rejected" in snapshot.status()["error"]
234
-
235
-
236
- def test_a_newer_schemas_pointer_is_not_read(tmp_path):
237
- store = store_of(tmp_path)
238
- store.put_bytes(f"snapshots/LATEST.v{snapshot.SCHEMA + 1}.json", b'{"db": "snapshots/catalog-1Z.db"}')
239
- assert snapshot.read_pointer(store) is None and not snapshot.check(store)
240
-
241
-
242
- # ── the indexer ──────────────────────────────────────────────────────────────
243
- def test_plan_rebuilds_only_what_changed(tmp_path, monkeypatch):
244
- monkeypatch.setattr(catalog, "featured_datasets", lambda: ["snaptest/f1"])
245
- built = 1_790_000_000.0 # 2026-09-21
246
- write_index("snaptest/same", [task("t")], sha="s1", built=built)
247
- write_index("snaptest/touched", [task("t")], sha="s1", built=built)
248
- write_index("snaptest/moved", [task("t")], sha="old", built=built)
249
- write_index("snaptest/oldfmt", [task("t")], version=catalog.INDEX_VERSION - 1, built=built)
250
- write_index("snaptest/part", [task("t")], built=built)
251
- rows = [{**ds(0), "id": i, "key": i, "updated": u, "trending": t} for i, u, t in [
252
- ("snaptest/same", "2026-09-01T00:00:00+00:00", 9), # changed before its index was built: no Hub call
253
- ("snaptest/touched", "2026-09-30T00:00:00+00:00", 8), # changed after, same revision: checked, skipped
254
- ("snaptest/moved", "2026-09-30T00:00:00+00:00", 1), # a new revision
255
- ("snaptest/oldfmt", "2026-09-01T00:00:00+00:00", 7),
256
- ("snaptest/part", "2026-09-01T00:00:00+00:00", 6),
257
- ("snaptest/new", "2026-09-01T00:00:00+00:00", 5),
258
- ("snaptest/f1", "2026-09-01T00:00:00+00:00", 0)]]
259
- rows.append({**ds(0), "id": "snaptest/rows", "key": "snaptest/rows", "framework": "verl"}) # read row by row: never indexed
260
- asked: list[str] = []
261
-
262
- def info(spec):
263
- asked.append(spec)
264
- return {"sha": "new" if spec == "snaptest/moved" else "s1"}
265
-
266
- items = indexer.plan(rows, {"rebuild": ["snaptest/part"]}, info=info)
267
- assert sorted(asked) == ["snaptest/moved", "snaptest/touched"]
268
- assert [(i["spec"], i["reason"]) for i in items] == [("snaptest/f1", "new"), ("snaptest/moved", "changed"),
269
- ("snaptest/oldfmt", "format"), ("snaptest/part", "partial"),
270
- ("snaptest/new", "new")]
271
-
272
-
273
- def test_refresh_keeps_to_its_budget_and_doesnt_retry_failures(tmp_path):
274
- items = [{"spec": f"snaptest/x{i}", "reason": "new", "tagged": True, "featured": False, "trending": 0, "downloads": 0, "head": None}
275
- for i in range(5)]
276
- calls: list[str] = []
277
-
278
- def build(spec, meta):
279
- calls.append(spec)
280
- if spec == "snaptest/x1":
281
- raise ValueError("more than 20,000 tasks")
282
- return 3
283
-
284
- state = indexer.load_state()
285
- info = lambda spec: {"sha": "r1"}
286
- out = indexer.refresh_indexes(items, state, budget_s=60, max_builds=3, build=build, info=info, looks_harbor=lambda s, h: True)
287
- assert calls == ["snaptest/x0", "snaptest/x1", "snaptest/x2"] and out["deferred"] == 2
288
- assert [b["spec"] for b in out["built"]] == ["snaptest/x0", "snaptest/x2"] and out["failed"][0]["spec"] == "snaptest/x1"
289
- calls.clear()
290
- out = indexer.refresh_indexes(items[1:2], state, budget_s=60, max_builds=3, build=build, info=info, looks_harbor=lambda s, h: True)
291
- assert calls == [] and out["skipped"][0]["why"].startswith("failed at this revision")
292
- out = indexer.refresh_indexes(items[1:2], state, budget_s=60, max_builds=3, build=build, info=lambda s: {"sha": "r2"},
293
- looks_harbor=lambda s, h: True)
294
- assert calls == ["snaptest/x1"] # a new revision is tried again
295
- out = indexer.refresh_indexes(items, state, budget_s=0, max_builds=3, build=build, info=info)
296
- assert out["deferred"] == 5 # out of time: nothing started
297
-
298
-
299
- def test_indexer_run_publishes_and_prunes(tmp_path, monkeypatch):
300
- store = Recording(tmp_path / "store")
301
- monkeypatch.setattr(catalog, "featured_datasets", list)
302
- monkeypatch.setattr(catalog, "info", lambda spec: {"sha": "zz"})
303
- builds: list[str] = []
304
-
305
- def fake_build(spec, meta):
306
- builds.append(spec)
307
- write_index(spec, [task("tasks/new")], sha=meta["sha"], built=time.time())
308
- return 1
309
-
310
- monkeypatch.setattr(indexer, "_build_one", fake_build)
311
- reports = []
312
- for i in range(4):
313
- reports.append(indexer.run(store, rows=ROWS, budget_s=60, max_builds=10, keep=2))
314
- time.sleep(1.05) # one snapshot a second at most: names are UTC seconds
315
- # d4 and d0 changed revision (the Hub says zz), d1 was never indexed; after that, all current: no more builds
316
- assert builds == ["snaptest/d4", "snaptest/d0", "snaptest/d1"]
317
- assert [r["indexes"]["built"] for r in reports[1:]] == [[], [], []]
318
- ptr = snapshot.read_pointer(store)
319
- assert ptr == reports[-1]["pointer"] and ptr["counts"]["tasks"] == 3
320
- assert store.ops[-2:] == [("file", ptr["db"]), ("bytes", snapshot.POINTER)]
321
- dbs = [n for n in store.list("snapshots") if n.endswith(".db")]
322
- assert len(dbs) == 2 and ptr["db"] in dbs # pruned to --keep
323
- assert (config.STORAGE_DIR / "listing.json.gz").exists()
324
- assert snapshot.check(store) and snapshot.current().counts["envs"] == 6
325
-
326
-
327
- def test_cached_listing_publication_preserves_discovery_timestamp(tmp_path):
328
- at = time.time() - 1800
329
- indexer.run(snapshot.LocalStore(tmp_path / "store"), rows=ROWS, index=False, listing_at=at)
330
- data = json.loads(gzip.decompress((config.STORAGE_DIR / "listing.json.gz").read_bytes()))
331
- assert data["at"] == at
332
- assert catalog._env_list["at"] == at
333
-
334
-
335
- def test_timed_out_build_backs_off_so_later_batches_can_progress(monkeypatch):
336
- class Stalled:
337
- def __init__(self, **kwargs): pass
338
- def start(self): pass
339
- def join(self, **kwargs): pass
340
- def is_alive(self): return True
341
- monkeypatch.setattr(indexer.threading, "Thread", Stalled)
342
- items = [{"spec": "snaptest/large", "reason": "new", "tagged": True}]
343
- state = {"failed": {}, "not_harbor": {}, "rebuild": []}
344
- info = lambda spec: {"sha": "same-revision"}
345
- first = indexer.refresh_indexes(items, state, budget_s=90, max_builds=4, info=info)
346
- assert first["abandoned"] == ["snaptest/large"]
347
- second = indexer.refresh_indexes(items, state, budget_s=90, max_builds=4, info=info)
348
- assert second["abandoned"] == [] and second["skipped"][0]["spec"] == "snaptest/large"
349
-
350
-
351
- def test_a_failed_run_publishes_nothing(tmp_path, monkeypatch):
352
- store = store_of(tmp_path)
353
- first = indexer.run(store, rows=ROWS, index=False)["pointer"]
354
- time.sleep(1.05)
355
- monkeypatch.setattr(snapshot, "build_db", lambda *a, **k: (_ for _ in ()).throw(RuntimeError("disk full")))
356
- with pytest.raises(RuntimeError):
357
- indexer.run(store, rows=ROWS, index=False)
358
- assert snapshot.read_pointer(store) == first
359
-
360
-
361
- def test_a_listing_that_shrank_is_refused(tmp_path):
362
- store = store_of(tmp_path)
363
- first = indexer.run(store, rows=ROWS, index=False)["pointer"]
364
- with pytest.raises(indexer.IndexerError, match="refusing to publish"):
365
- indexer.run(store, rows=ROWS[:2], index=False) # the Spaces' listing failed
366
- assert snapshot.read_pointer(store) == first
367
- time.sleep(1.05)
368
- assert indexer.run(store, rows=ROWS[:2], index=False, force=True)["pointer"]["counts"]["envs"] == 2
369
-
370
-
371
- def test_dry_run_writes_nothing(tmp_path):
372
- store = store_of(tmp_path)
373
- out = tmp_path / "dry.db"
374
- report = indexer.run(store, rows=ROWS, dry_run=True, out=out)
375
- assert report["counts"]["envs"] == 6 and out.exists()
376
- assert snapshot.read_pointer(store) is None and not (tmp_path / "store").exists()
377
-
378
-
379
- def test_main_exit_status(tmp_path, monkeypatch):
380
- monkeypatch.setenv("STORAGE_DIR", str(config.STORAGE_DIR)) # main() points it at --store: restored after
381
- monkeypatch.setattr(indexer, "fetch_listing", lambda attempts=2: (_ for _ in ()).throw(indexer.IndexerError("Hub down")))
382
- monkeypatch.setattr("app.runtime.logs", lambda: None)
383
- assert indexer.main(["--store", str(tmp_path / "store"), "--no-index"]) == 1
384
- assert not (tmp_path / "store" / snapshot.POINTER).exists()
385
-
386
-
387
- # ── through the bucket API ───────────────────────────────────────────────────
388
- class FakeBucketApi:
389
- """The four bucket calls the store uses, over a dict."""
390
-
391
- def __init__(self):
392
- self.files: dict[str, bytes] = {}
393
- self.batches: list[dict] = []
394
-
395
- def batch_bucket_files(self, bucket_id, *, add=None, delete=None, copy=None, token=None):
396
- self.batches.append({"add": [p for _, p in add or []], "delete": list(delete or [])})
397
- for src, path in add or []:
398
- self.files[path] = src if isinstance(src, bytes) else Path(src).read_bytes()
399
- for path in delete or []:
400
- self.files.pop(path, None)
401
-
402
- def download_bucket_files(self, bucket_id, files, *, raise_on_missing_files=False, token=None):
403
- for path, dst in files:
404
- if path not in self.files:
405
- if raise_on_missing_files:
406
- raise FileNotFoundError(path)
407
- continue
408
- Path(dst).write_bytes(self.files[path])
409
-
410
- def list_bucket_tree(self, bucket_id, prefix=None, *, recursive=None, token=None):
411
- return [SimpleNamespace(type="file", path=p, size=len(b)) for p, b in sorted(self.files.items()) if p.startswith(prefix or "")]
412
-
413
- def get_bucket_paths_info(self, bucket_id, paths, *, token=None):
414
- return [SimpleNamespace(type="file", path=p, size=len(self.files[p])) for p in paths if p in self.files]
415
-
416
-
417
- def test_the_same_through_the_bucket_api(tmp_path):
418
- api = FakeBucketApi()
419
- store = snapshot.BucketStore("FineEnvs/rl-explorer-data", api=api)
420
- report = indexer.run(store, rows=ROWS, index=False, keep=1)
421
- ptr = report["pointer"]
422
- assert [b["add"] for b in api.batches] == [[ptr["db"]], [snapshot.POINTER]] # the database, then the pointer
423
- assert snapshot.check(store) and snapshot.current().counts["envs"] == 6
424
- time.sleep(1.05)
425
- indexer.run(store, rows=ROWS, index=False, keep=1)
426
- assert api.batches[-1]["delete"] == [ptr["db"]] # pruned through the API too
427
- assert str(snapshot.open_store("hf://buckets/FineEnvs/rl-explorer-data/sub")).endswith("rl-explorer-data/sub")
428
-
429
-
430
- def teardown_module(module):
431
- shutil.rmtree(Path(config.STORAGE_DIR) / "snapshots", ignore_errors=True)
432
-
433
-
434
- def test_a_snapshot_whose_file_vanished_is_dropped_and_fetched_again(tmp_path):
435
- store = store_of(tmp_path)
436
- db, counts = build(tmp_path)
437
- indexer.publish(store, db, counts)
438
- assert snapshot.check(store)
439
- snap = snapshot.current()
440
- snap._close() # its pooled connections gone, as after a while idle
441
- snap._closed = False
442
- snap.path.unlink() # a disk cleaner took the file
443
- with snapshot.use() as (s, conn): # this request is still answered: the snapshot is fetched again at once
444
- assert conn.execute("SELECT COUNT(*) FROM envs").fetchone()[0] == 6
445
- assert s is not snap and s.name == snap.name and s.path.exists() and snapshot.current() is s
446
-
447
-
448
- def test_fallback_files_of_dead_processes_are_cleaned(tmp_path):
449
- local = tmp_path / "local"
450
- local.mkdir(parents=True)
451
- dead, mine = local / "fallback-999999-1.db", local / f"fallback-{snapshot.os.getpid()}-77.db"
452
- dead.write_bytes(b"x")
453
- mine.write_bytes(b"x")
454
- with snapshot.use() as (snap, _):
455
- assert snap.source == "live"
456
- assert not dead.exists() and mine.exists()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_space_checks.py DELETED
@@ -1,135 +0,0 @@
1
- import json
2
- import time
3
-
4
- import pytest
5
-
6
- from app import space_checks as checks, spaces_live as live
7
-
8
-
9
- def facts():
10
- api = {"info": {"version": "1.0.0"}, "paths": {
11
- "/reset": {"post": {}}, "/step": {"post": {}}, "/state": {"get": {}}, "/mcp": {"post": {}}}}
12
- return [api, {"status": "healthy"}, {"name": "test", "description": "test", "version": "9.9.9"},
13
- {"action": {}, "observation": {}, "state": {}}, None]
14
-
15
-
16
- def test_typed_actions_do_not_require_mcp_but_production_does():
17
- f = facts()
18
- assert checks.assess(*f)["status"] == checks.PASS
19
- f[0]["paths"] = {"/mcp": {"post": {}}}
20
- assert checks.assess(*f)["status"] == "Checks failed"
21
- f[-1] = [{"name": "echo"}]
22
- assert checks.assess(*f)["mode"] == "MCP service"
23
-
24
-
25
- @pytest.mark.parametrize("change", ["schema", "health", "route", "method", "html"])
26
- def test_failures_cannot_become_api_checked(change):
27
- f = facts()
28
- if change == "schema":
29
- f[3].pop("state")
30
- elif change == "health":
31
- f[1]["status"] = "ok"
32
- elif change == "route":
33
- f[0]["paths"].pop("/step")
34
- elif change == "method":
35
- f[0]["paths"]["/step"] = {"get": {}}
36
- else:
37
- f[0] = "<html>Space UI</html>"
38
- assert checks.assess(*f)["status"] == "Checks failed"
39
-
40
-
41
- @pytest.mark.parametrize("dependency,value,source", [
42
- ("openenv-core==0.2.3", "0.2.3", "Dependency pin"),
43
- ("openenv==0.7.0", "0.7.0", "Dependency pin"),
44
- ("openenv_core[core]>=0.2.0,<0.3", "<0.3,>=0.2.0", "Dependency constraint"),
45
- ("openenv-core==0.2.*", "==0.2.*", "Dependency constraint"),
46
- ("openenv-core", "Unpinned", "Dependency constraint"),
47
- ("openenv-core @ git+https://github.com/meta-pytorch/OpenEnv.git@abc123", "git:abc123", "Source reference"),
48
- ('openenv-core==0.2.3; python_version < "3.12"', "Conditional", "Dependency constraint"),
49
- ])
50
- def test_declared_versions_keep_constraints_and_source_refs(dependency, value, source):
51
- v = checks.requirement_version(dependency)
52
- assert (v["value"], v["source"]) == (value, source)
53
-
54
-
55
- def test_version_does_not_use_environment_or_api_version():
56
- assert checks.parse_version('[project]\nname="env"\nversion="9.9.9"\ndependencies=["fastapi"]', "pyproject.toml") is None
57
- assert checks.requirement_version("gymnasium==1.0.0") is None
58
- assert checks.tag_version(["openenv", "openenv-nonsense"])["value"] == "Unknown"
59
- assert checks.tag_version(["openenv-0.2.3"])["value"] == "0.2.3"
60
-
61
-
62
- def test_lock_and_uv_source_precedence():
63
- doc = '[[package]]\nname="openenv-core"\nversion="0.7.1.dev0"\nsource={git="https://example.org/env?rev=main#abcdef"}'
64
- assert checks.parse_version(doc, "uv.lock") == {"value": "git:abcdef", "source": "Lockfile"}
65
- doc = '[project]\ndependencies=["openenv-core>=0.2"]\n[tool.uv.sources]\nopenenv-core={git="https://example.org/env",rev="v0.2.3"}'
66
- assert checks.parse_version(doc, "pyproject.toml")["value"] == "git:v0.2.3"
67
- assert checks.parse_version(doc.replace("openenv-core", "openenv"), "pyproject.toml")["value"] == "git:v0.2.3"
68
-
69
-
70
- def test_expiry_future_dates_and_newer_failures(tmp_path, monkeypatch):
71
- monkeypatch.setattr(checks.config, "STORAGE_DIR", tmp_path)
72
- now = time.time()
73
- passing = {"schema": 1, "id": "owner/env", "checked_at": now - 10, "status": checks.PASS}
74
- checks.save(passing)
75
- assert checks.status(checks.inventory()["owner/env"], now) == checks.PASS
76
- assert checks.status(passing, now + checks.FRESH) == "Check expired"
77
- assert checks.status(passing, now - 20) == "Check expired"
78
- checks.save({**passing, "checked_at": now, "status": "Checks failed"})
79
- checks.save(passing) # late completion cannot overwrite a newer failure
80
- assert checks.status(checks.inventory()["owner/env"], now) == "Checks failed"
81
-
82
-
83
- def test_sleeping_space_is_not_contacted_or_woken(tmp_path, monkeypatch):
84
- monkeypatch.setattr(checks.config, "STORAGE_DIR", tmp_path)
85
- monkeypatch.setattr(live, "record", lambda *a, **kw: {"stage": "SLEEPING"})
86
- monkeypatch.setattr(live, "SpaceClient", lambda *a, **kw: pytest.fail("contacted sleeping server"))
87
- r = checks.check("owner/sleeping")
88
- assert r["status"] == "Not running"
89
-
90
-
91
- def test_network_failure_invalidates_previous_pass(tmp_path, monkeypatch):
92
- monkeypatch.setattr(checks.config, "STORAGE_DIR", tmp_path)
93
- checks.save({"schema": 1, "id": "owner/env", "checked_at": time.time() - 10, "status": checks.PASS})
94
- def offline(*a, **kw):
95
- raise ConnectionError("offline")
96
- monkeypatch.setattr(live, "record", offline)
97
- assert checks.check("owner/env")["status"] == "Check unavailable"
98
- assert checks.status(checks.inventory()["owner/env"]) != checks.PASS
99
-
100
-
101
- def test_concurrent_refresh_skips_locked_queue(tmp_path, monkeypatch):
102
- fcntl = pytest.importorskip("fcntl")
103
- monkeypatch.setattr(checks.config, "CACHE_DIR", tmp_path)
104
- monkeypatch.setattr(checks, "_scan", lambda: pytest.fail("started duplicate queue"))
105
- with (tmp_path / "openenv-checks.lock").open("a") as lease:
106
- fcntl.flock(lease, fcntl.LOCK_EX | fcntl.LOCK_NB)
107
- assert checks.scan() == []
108
-
109
-
110
- def test_automatic_queue_includes_inactive_and_untagged_without_starvation():
111
- candidates = [(f"org/active{i}", "RUNNING") for i in range(120)] + [(f"org/sleep{i}", "SLEEPING") for i in range(40)]
112
- candidates += [("FineEnvs/recent", "RUNNING"), ("FineEnvs/new", "RUNNING")]
113
- now = time.time()
114
- old = {"FineEnvs/recent": {"stage": "RUNNING", "checked_at": now, "status": checks.PASS, "task_catalog": {}}}
115
- pending = checks.pending(candidates, old, now)
116
- assert len(pending) == 128 and pending[0] == "FineEnvs/new"
117
- assert sum("sleep" in spec for spec in pending) == 32
118
- assert "FineEnvs/recent" not in pending
119
- for spec in pending:
120
- old[spec] = {"checked_at": now, "stage": "SLEEPING", "status": "Not running"}
121
- next_batch = checks.pending(candidates, old, now)
122
- assert next_batch and not set(next_batch) & set(pending)
123
-
124
-
125
- def test_other_environment_interfaces_need_published_methods_and_fresh_running_checks():
126
- api = {"info": {"version": "1"}, "paths": {"/seed_session": {"post": {}}, "/verify": {"post": {}}}}
127
- assert checks.environment_interface(api) == "nemo-gym"
128
- rec = {"interface": "nemo-gym", "status": "Checks failed", "stage": "RUNNING", "checked_at": time.time()}
129
- assert checks.browseable(rec) and not checks.verified(rec)
130
- assert not checks.browseable({**rec, "checked_at": time.time() - checks.FRESH})
131
- assert not checks.browseable({**rec, "stage": "SLEEPING"})
132
- assert not checks.browseable({**rec, "status": "Check unavailable"})
133
- api["paths"]["/verify"] = {"get": {}}
134
- assert checks.environment_interface(api) is None
135
- assert checks.environment_interface(facts()[0]) is None # generic reset/step alone is insufficient
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_space_files.py DELETED
@@ -1,449 +0,0 @@
1
- """An environment Space read as files, and what a live probe leaves behind (no network: the Hub is stubbed).
2
-
3
- What is checked: openenv.yaml in its variants (canonical, legacy class names, hackathon task lists, the `validation:`
4
- block, a manifest in a subfolder, broken or explosive YAML); models.py, server/app.py and the environment read with
5
- `ast`, never imported or run; vendored OpenEnv and build output left out of the tree; the file endpoint serves only
6
- listed text files (no traversal, no binaries, no answers); private Spaces are refused and no token is ever sent;
7
- the last good probe is kept and shown while the Space sleeps; `openenv validate`'s six criteria are judged right.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import json
13
- import time
14
- from pathlib import Path
15
- from types import SimpleNamespace
16
-
17
- import pytest
18
- from fastapi import FastAPI
19
- from fastapi.testclient import TestClient
20
-
21
- from app import catalog, config
22
- from app import space_files as sf
23
- from app import spaces_live as live
24
-
25
- api = FastAPI()
26
- api.include_router(sf.router)
27
- client = TestClient(api)
28
-
29
- SHA = "a" * 40
30
-
31
-
32
- @pytest.fixture(autouse=True)
33
- def fresh():
34
- catalog._memo.clear()
35
- yield
36
- catalog._memo.clear()
37
-
38
-
39
- def sibling(name, size=100, lfs=False):
40
- return SimpleNamespace(rfilename=name, size=size, lfs={"sha256": "x"} if lfs else None)
41
-
42
-
43
- def stub_space(monkeypatch, tmp_path, files: dict[str, str | bytes], private=False, extra: list[str] = (), seen_tokens=None):
44
- """A public Space whose repository holds `files` (path -> content) and `extra` (listed only)."""
45
- sibs = [sibling(p, len(c.encode() if isinstance(c, str) else c)) for p, c in files.items()] + [sibling(p) for p in extra]
46
- sp = SimpleNamespace(sha=SHA, private=private, gated=False, siblings=sibs, subdomain="org-env", sdk="docker", tags=["openenv"],
47
- card_data=SimpleNamespace(to_dict=lambda: {"title": "Env", "base_path": "/web", "app_port": 8000}),
48
- runtime=SimpleNamespace(stage="SLEEPING", hardware=None, requested_hardware="cpu-basic"),
49
- last_modified=None)
50
-
51
- def space_info(spec, files_metadata=False, expand=None):
52
- return sp
53
-
54
- def _api(token=None):
55
- if seen_tokens is not None:
56
- seen_tokens.append(("api", token))
57
- return SimpleNamespace(space_info=space_info)
58
-
59
- calls = []
60
-
61
- def download(repo_id, filename, repo_type=None, revision=None, token=None, cache_dir=None, **kw):
62
- calls.append({"repo": repo_id, "file": filename, "type": repo_type, "revision": revision, "token": token})
63
- p = tmp_path / "dl" / filename
64
- p.parent.mkdir(parents=True, exist_ok=True)
65
- data = files[filename]
66
- p.write_bytes(data.encode() if isinstance(data, str) else data)
67
- return str(p)
68
-
69
- import huggingface_hub
70
-
71
- monkeypatch.setattr(catalog, "_api", _api)
72
- monkeypatch.setattr(huggingface_hub, "hf_hub_download", download)
73
- return calls
74
-
75
-
76
- # ── openenv.yaml ─────────────────────────────────────────────────────────────
77
- CANONICAL = """spec_version: 1
78
- name: echo_env
79
- type: space
80
- runtime: fastapi
81
- app: server.app:app
82
- port: 8000
83
- """
84
-
85
-
86
- def test_canonical_manifest_keys():
87
- m = sf.parse_manifest(CANONICAL, "openenv.yaml")
88
- assert m["keys"] == {"spec_version": 1, "name": "echo_env", "type": "space", "runtime": "fastapi", "app": "server.app:app", "port": 8000}
89
- assert "validation" not in m and "tasks" not in m and "error" not in m
90
-
91
-
92
- def test_legacy_manifest_names_its_classes():
93
- m = sf.parse_manifest('name: browsergym_env\nversion: "0.1.0"\naction: BrowserGymAction\nobservation: BrowserGymObservation\n', "openenv.yaml")
94
- assert m["legacy"] == {"action": "BrowserGymAction", "observation": "BrowserGymObservation"}
95
- assert m["keys"]["version"] == "0.1.0"
96
-
97
-
98
- def test_hackathon_task_lists_in_every_shape_and_without_answers():
99
- listed = sf.parse_manifest("""name: triage
100
- tasks:
101
- - id: easy
102
- name: Direct injection
103
- difficulty: easy
104
- description: Detect it.
105
- expected_score_range: [0.2, 0.5]
106
- gold_answer: "yes"
107
- - id: hard
108
- level: hard
109
- reward:
110
- min: 0.0
111
- max: 1.0
112
- expected_output: 42
113
- """, "openenv.yaml")
114
- assert [t["id"] for t in listed["tasks"]] == ["easy", "hard"]
115
- assert listed["tasks"][0]["difficulty"] == "easy" and listed["tasks"][1]["difficulty"] == "hard"
116
- flat = json.dumps(listed)
117
- assert "gold_answer" not in flat and "expected_score_range" not in flat and "42" not in flat
118
- assert "expected_output" in listed["withheld"]
119
- strings = sf.parse_manifest("name: x\ntasks: [easy, medium, hard]\n", "openenv.yaml")
120
- assert [t["id"] for t in strings["tasks"]] == ["easy", "medium", "hard"]
121
- keyed = sf.parse_manifest("name: x\ntasks:\n easy: {description: one}\n hard: two\n", "openenv.yaml")
122
- assert keyed["tasks"] == [{"id": "easy", "description": "one"}, {"id": "hard", "description": "two"}]
123
-
124
-
125
- def test_validation_block():
126
- m = sf.parse_manifest(CANONICAL + """validation:
127
- reward: {range: [0.0, 1.0], oracle_tolerance: 0.01, floor_margin: 0.5, variance_tolerance: 0.05}
128
- judge: {model: Qwen/Qwen3-8B, version: "2025-05", params: {temperature: 0}}
129
- resources: {cpu: 2, memory_mb: 4096, disk_mb: 2048, episode_timeout_s: 600}
130
- network: {mode: allowlist, allowed_hosts: [api.example.com]}
131
- capabilities:
132
- verifier: {kind: reward_channel}
133
- llm_judged: true
134
- task_api: true
135
- declared_tools: [look, submit_guess]
136
- declared_task_count: {train: 1000, eval: 200}
137
- types: {tags: [geo]}
138
- """, "openenv.yaml")
139
- v = m["validation"]
140
- assert v["reward"]["range"] == [0.0, 1.0] and v["reward"]["variance_tolerance"] == 0.05
141
- assert v["judge"]["model"] == "Qwen/Qwen3-8B"
142
- assert v["capabilities"]["llm_judged"] is True and v["capabilities"]["verifier"]["kind"] == "reward_channel"
143
- assert v["capabilities"]["declared_tools"] == ["look", "submit_guess"]
144
- assert v["capabilities"]["declared_task_count"] == {"train": 1000, "eval": 200}
145
- assert v["network"] == {"mode": "allowlist", "allowed_hosts": ["api.example.com"]}
146
- assert v["resources"]["memory_mb"] == 4096 and v["types"] == ["geo"]
147
-
148
-
149
- def test_broken_and_explosive_yaml_is_said_so_quickly():
150
- assert sf.parse_manifest("name: [unclosed\n", "openenv.yaml")["error"].startswith("not valid YAML")
151
- assert sf.parse_manifest("- just\n- a list\n", "openenv.yaml")["error"] == "not a YAML mapping"
152
- laughs = "a: &a [x, x, x, x, x, x, x, x, x]\n" + "".join(f"{c}: &{c} [*{p}, *{p}, *{p}, *{p}, *{p}, *{p}, *{p}, *{p}, *{p}]\n"
153
- for p, c in zip("abcdefgh", "bcdefghi")) + "rubric: *i\n"
154
- t = time.time()
155
- m = sf.parse_manifest(laughs, "openenv.yaml")
156
- assert time.time() - t < 2 and len(json.dumps(m)) < 2_000_000
157
-
158
-
159
- def test_the_shallowest_manifest_wins_and_ties_follow_the_dockerfile():
160
- paths = ["envs/deep/x/openenv.yaml", "env/openenv.yaml", "geoguesser_env/openenv.yaml", "src/openenv/cli/templates/openenv_env/openenv.yaml"]
161
- assert sf.manifest_path(paths, "FROM python:3.12\nCOPY geoguesser_env /app/geoguesser_env\n", "FineEnvs/geoguesser-env") == "geoguesser_env/openenv.yaml"
162
- assert sf.manifest_path(paths, None, "FineEnvs/geoguesser-env") == "geoguesser_env/openenv.yaml" # named like the Space
163
- assert sf.manifest_path(paths, None, "someone/other") == "env/openenv.yaml"
164
- assert sf.manifest_path(["a/openenv.yaml", "openenv.yaml"], None, "o/n") == "openenv.yaml"
165
- assert sf.manifest_path(["src/openenv/cli/templates/openenv_env/openenv.yaml"], None, "o/n") is None
166
-
167
-
168
- def test_a_subfolder_manifest_roots_the_environment(monkeypatch, tmp_path):
169
- files = {"Dockerfile": "FROM python:3.12\nCOPY my_env /app/my_env\nENV ENABLE_WEB_INTERFACE=true\n",
170
- "my_env/openenv.yaml": CANONICAL.replace("echo_env", "my_env"),
171
- "my_env/models.py": "from openenv.core.env_server.types import Action, Observation\nclass A(Action):\n x: int = 1\n",
172
- "my_env/server/app.py": "app = create_app(E, A, O, env_name='my_env')\n",
173
- "my_env/server/my_environment.py": "class E(Environment):\n pass\n",
174
- "my_env/pyproject.toml": '[project]\nname = "openenv-my-env"\ndependencies = ["openenv-core>=0.2.1"]\n'}
175
- stub_space(monkeypatch, tmp_path, files)
176
- d = sf.declared("org/env")
177
- assert d["root"] == "my_env" and d["sha"] == SHA and d["short"] == SHA[:7]
178
- assert d["keys"]["manifest"] == "my_env/openenv.yaml" and d["keys"]["models"] == "my_env/models.py"
179
- assert d["declared"]["manifest"]["keys"]["name"] == "my_env"
180
- assert d["declared"]["app"]["env_name"] == "my_env"
181
- assert d["declared"]["dockerfile"]["web_interface"] is True
182
- assert d["declared"]["pyproject"]["openenv_version"] == ">=0.2.1"
183
- assert d["declared"]["card"]["base_path"] == "/web"
184
-
185
-
186
- # ── Python, parsed and never run ─────────────────────────────────────────────
187
- MODELS = '''
188
- import os
189
- os.system("touch {marker}")
190
- raise SystemExit("models.py must never run here")
191
-
192
- from enum import Enum
193
- from pydantic import Field
194
- from openenv.core.env_server.types import Action, Observation, State
195
-
196
-
197
- class Mode(str, Enum):
198
- EASY = "easy"
199
- HARD = "hard"
200
-
201
-
202
- class Base(Action):
203
- """Every action."""
204
-
205
-
206
- class MoveAction(Base):
207
- """Walk along the road.
208
-
209
- More detail."""
210
- direction: str = Field(..., description="forward or backward", pattern="^(forward|backward)$")
211
- meters: float = Field(default=10.0, gt=0, description="how far")
212
- tags: list[str] = Field(default_factory=list)
213
- _private: int = 0
214
-
215
-
216
- class GeoObservation(Observation):
217
- image_b64: str = Field(default="", description="the view, as base64 PNG")
218
- feedback: str = ""
219
- model_config = {{"extra": "forbid"}}
220
-
221
-
222
- class GeoState(State):
223
- step_count: int = 0
224
- '''
225
-
226
-
227
- def test_models_are_parsed_and_never_executed(tmp_path):
228
- marker = tmp_path / "pwned"
229
- m = sf.parse_models(MODELS.format(marker=marker), "models.py") # would raise SystemExit, or touch the marker, if run
230
- assert not marker.exists()
231
- by = {c["name"]: c for c in m["classes"]}
232
- assert by["MoveAction"]["role"] == "action" and by["Base"]["role"] == "action" # through Base, within the file
233
- assert by["GeoObservation"]["role"] == "observation" and by["GeoState"]["role"] == "state"
234
- assert by["Mode"]["kind"] == "enum" and by["Mode"]["values"] == ["'easy'", "'hard'"] and by["Mode"]["role"] is None
235
- assert by["MoveAction"]["doc"] == "Walk along the road."
236
- f = {x["name"]: x for x in by["MoveAction"]["fields"]}
237
- assert set(f) == {"direction", "meters", "tags"} # private fields left out
238
- assert f["direction"]["required"] is True and f["direction"]["description"] == "forward or backward"
239
- assert f["meters"]["default"] == "10.0" and not f["meters"]["required"] and "gt 0" in f["meters"]["limits"]
240
- assert f["tags"]["default"] == "list()" and f["tags"]["type"] == "list[str]"
241
- o = {x["name"]: x for x in by["GeoObservation"]["fields"]}
242
- assert o["image_b64"].get("image") is True and "model_config" not in o
243
- assert sf.parse_models("def broken(:\n", "models.py")["error"]
244
-
245
-
246
- def test_server_app_and_environment_are_read_not_run(tmp_path):
247
- marker = tmp_path / "ran"
248
- app_py = f'''
249
- import os
250
- open("{marker}", "w").write("x")
251
- MAX_CONCURRENT = int(os.getenv("MAX_CONCURRENT_ENVS", "4"))
252
- app = create_app(GeoEnvironment, GeoAction, GeoObservation, env_name="geo", max_concurrent_envs=MAX_CONCURRENT,
253
- gradio_builder=build_ui, custom_tab_name="Play")
254
-
255
- @app.get("/geo/task/{{i}}")
256
- def task(i: int): ...
257
- '''
258
- a = sf.parse_app(app_py, "server/app.py")
259
- assert not marker.exists()
260
- assert (a["env"], a["action"], a["observation"], a["env_name"]) == ("GeoEnvironment", "GeoAction", "GeoObservation", "geo")
261
- assert a["max_concurrent_envs"] == 4 and a["gradio_builder"] == "build_ui" and a["custom_tab_name"] == "Play"
262
- assert a["routes"] == ["GET /geo/task/{i}"] and not a["mcp"]
263
- assert sf.parse_app("app = create_app(E, CallToolAction, CallToolObservation, env_name='echo')", "server/app.py")["mcp"] is True
264
-
265
- env = sf.parse_environment('''
266
- from openai import OpenAI
267
- from .scoring import compute_reward
268
- from openenv.core.env_server.mcp_environment import MCPEnvironment
269
-
270
- class GeoEnvironment(MCPEnvironment):
271
- def _register(self, mcp):
272
- @mcp.tool
273
- def look(heading_deg: float, fov_deg: float = 90):
274
- """Render a view."""
275
- @mcp.tool()
276
- def submit_guess(lat: float, lon: float):
277
- """Commit the answer."""
278
-
279
- def _grade(self, guess):
280
- return compute_reward(guess)
281
- ''', "server/geo_environment.py")
282
- assert [t["name"] for t in env["tools"]] == ["look", "submit_guess"]
283
- assert env["tools"][0]["params"] == ["heading_deg", "fov_deg"] and env["tools"][0]["description"] == "Render a view."
284
- assert env["llm_judge"] is True and env["mcp"] is True and "_grade" in env["functions"]
285
- assert sf.reward_files("server/geo_environment.py", env["reward_imports"], {"server/scoring.py"}, "") == ["server/scoring.py"]
286
- assert sf.parse_environment("from openenv.core.rubrics import Rubric\nclass R(Rubric): pass\n", "e.py")["rubric"] is True
287
-
288
-
289
- def test_dockerfile_and_pyproject_facts():
290
- d = sf.parse_dockerfile("ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest\nFROM ${BASE_IMAGE} AS builder\n"
291
- "FROM ${BASE_IMAGE}\nENV ENABLE_WEB_INTERFACE=true\nEXPOSE 7860\n"
292
- 'CMD ["uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "8000"]\n', "Dockerfile")
293
- assert d["from"] == ["ghcr.io/meta-pytorch/openenv-base:latest"] and d["web_interface"] is True
294
- assert d["expose"] == 7860 and d["port"] == 8000
295
- assert sf.parse_dockerfile("FROM python:3.12\nENV ENABLE_WEB_INTERFACE false\n", "Dockerfile")["web_interface"] is False
296
- git = sf.parse_pyproject('[project]\nname = "openenv-echo-env"\ndependencies = ["fastapi", '
297
- '"openenv-core[core] @ git+https://github.com/meta-pytorch/OpenEnv.git@v0.2.3"]\n'
298
- '[project.scripts]\nserver = "echo_env.server.app:main"\n', "pyproject.toml")
299
- assert git["package"] == "openenv-echo-env" and git["openenv_version"] == "0.2.3" and git["server_script"] == "echo_env.server.app:main"
300
- assert sf.parse_pyproject('[project]\ndependencies = ["openenv==0.3.1"]\n', "p")["openenv_version"] == "0.3.1"
301
- assert "openenv" not in sf.parse_pyproject('[project]\ndependencies = ["openenv-echo-env>=1"]\n', "p") # another package
302
- assert sf.parse_pyproject("not = [toml", "p")["error"]
303
-
304
-
305
- # ── the tree ────────────────────────────────────────────────────────────────��
306
- def test_junk_is_left_out_of_the_tree():
307
- info = {"files": [(p, 10, False) for p in (
308
- ".gitattributes", "README.md", "openenv.yaml", "models.py", "server/app.py", "uv.lock",
309
- "src/openenv/core/env_server/http_server.py", "src/core/env_client.py", "src/openenv_core/__init__.py",
310
- "build/lib/server/app.py", "envs/echo_env/build/lib/echo_env/client.py", "openenv_echo_env.egg-info/PKG-INFO",
311
- "server/__pycache__/app.cpython-312.pyc", "envs/echo_env/server/app.py", "solution/solve.py",
312
- "tasks/eval.jsonl", "data/geo/countries.geojson")] + [("assets/map.png", 10, True)]}
313
- files, dropped, truncated = sf.listing(info)
314
- paths = [f["path"] for f in files]
315
- assert paths == sorted(["README.md", "openenv.yaml", "models.py", "server/app.py", "uv.lock", "envs/echo_env/server/app.py",
316
- "solution/solve.py", "tasks/eval.jsonl", "data/geo/countries.geojson", "assets/map.png"])
317
- assert dropped == 8 and not truncated
318
- by = {f["path"]: f for f in files}
319
- assert by["uv.lock"]["collapsed"] and by["assets/map.png"]["binary"]
320
- assert by["solution/solve.py"]["withheld"] and by["tasks/eval.jsonl"]["withheld"]
321
- assert not by["data/geo/countries.geojson"].get("withheld") and not by["openenv.yaml"].get("withheld")
322
-
323
-
324
- # ── one file ─────────────────────────────────────────────────────────────────
325
- def test_the_file_endpoint_serves_only_listed_text(monkeypatch, tmp_path):
326
- big = "x" * (sf.MAX_TEXT + 10)
327
- calls = stub_space(monkeypatch, tmp_path, {
328
- "openenv.yaml": CANONICAL, "models.py": "class A: pass\n", "big.txt": big,
329
- "blob.dat": b"\x00\x01binary", "picture.png": b"\x89PNG", "config/answers.json": '{"x": 1}',
330
- "data/manifest.json": '{"steps": [{"original_bash": "ls"}]}',
331
- "uv.lock": 'version = 1\n[[package]]\nname = "fastapi"\nversion = "0.115.0"\n[[package]]\nname = "openenv-core"\nversion = "0.2.3"\n',
332
- }, extra=["src/openenv/core/secret.py"])
333
- r = client.get("/api/spaces/org/env/file", params={"f": "models.py"})
334
- assert r.status_code == 200 and r.json()["text"] == "class A: pass\n" and r.json()["sha"] == SHA
335
- for bad in ("../etc/passwd", "/etc/passwd", "server/../models.py", "a\\b", "./models.py", ""):
336
- assert client.get("/api/spaces/org/env/file", params={"f": bad}).status_code in (400, 404), bad
337
- assert client.get("/api/spaces/org/env/file", params={"f": "src/openenv/core/secret.py"}).status_code == 404 # junk: not served
338
- assert client.get("/api/spaces/org/env/file", params={"f": "nothere.py"}).status_code == 404
339
- assert client.get("/api/spaces/org/env/file", params={"f": "picture.png"}).status_code == 415 # binary by name
340
- assert client.get("/api/spaces/org/env/file", params={"f": "blob.dat"}).status_code == 415 # binary by content
341
- long = client.get("/api/spaces/org/env/file", params={"f": "big.txt"}).json()
342
- assert long["truncated"] and len(long["text"]) == sf.MAX_TEXT
343
- for answers in ("config/answers.json", "data/manifest.json"): # by name; by its keys
344
- w = client.get("/api/spaces/org/env/file", params={"f": answers}).json()
345
- assert w["withheld"] and "text" not in w, answers
346
- lock = client.get("/api/spaces/org/env/file", params={"f": "uv.lock"}).json()
347
- assert lock["collapsed"] and "openenv-core 0.2.3" in lock["text"] and "[[package]]" not in lock["text"]
348
- assert {c["file"] for c in calls} <= {"models.py", "big.txt", "blob.dat", "data/manifest.json", "uv.lock"} # never the junk or the png
349
- assert all(c["token"] is False and c["revision"] == SHA and c["type"] == "space" for c in calls)
350
-
351
-
352
- def test_files_endpoint_shape_and_no_token(monkeypatch, tmp_path):
353
- tokens = []
354
- stub_space(monkeypatch, tmp_path, {"openenv.yaml": CANONICAL, "README.md": "# hi\n"}, extra=["src/core/x.py", "build/lib/a.py"], seen_tokens=tokens)
355
- r = client.get("/api/spaces/org/env/files", headers={"Authorization": "Bearer hf_visitor_token"})
356
- assert r.status_code == 200
357
- d = r.json()
358
- assert [f["path"] for f in d["files"]] == ["README.md", "openenv.yaml"] and d["dropped"] == 2
359
- assert d["subdomain"] == "org-env" and d["hardware"] == "cpu-basic" and d["declared"]["manifest"]["keys"]["name"] == "echo_env"
360
- assert tokens and all(t is None for _, t in tokens) # catalog._api(): anonymous
361
-
362
-
363
- def test_private_spaces_are_refused(monkeypatch, tmp_path):
364
- stub_space(monkeypatch, tmp_path, {"openenv.yaml": CANONICAL}, private=True)
365
- assert client.get("/api/spaces/org/env/files").status_code == 404
366
- assert client.get("/api/spaces/org/env/file", params={"f": "openenv.yaml"}).status_code == 404
367
- from huggingface_hub.errors import RepositoryNotFoundError
368
-
369
- def missing(spec, **kw):
370
- raise RepositoryNotFoundError("nope", response=SimpleNamespace(status_code=404, headers={}, request=None))
371
-
372
- catalog._memo.clear()
373
- monkeypatch.setattr(catalog, "_api", lambda token=None: SimpleNamespace(space_info=missing))
374
- assert client.get("/api/spaces/org/env/files").status_code == 404
375
- assert client.get("/api/spaces/../x/files").status_code in (400, 404)
376
-
377
-
378
- # ── last seen, and openenv validate's criteria ───────────────────────────────
379
- SCHEMA = {"action": {"properties": {"message": {"type": "string"}}}, "observation": {"properties": {"reward": {}}}, "state": {"properties": {}}}
380
- OPENAPI = {"info": {"version": "1.0.0"}, "paths": {p: {} for p in ("/reset", "/step", "/state", "/metadata", "/health", "/schema", "/mcp")}}
381
-
382
-
383
- def stub_running(monkeypatch, stage="RUNNING"):
384
- rec = {"id": "org/env", "host": "https://org-env.hf.space", "stage": stage, "base_path": "/web"}
385
- monkeypatch.setattr(live, "record", lambda spec, fresh=False: rec)
386
- answers = {"/openapi.json": OPENAPI, "/metadata": {"name": "env", "description": "an env", "readme_content": "x" * 5000},
387
- "/schema": SCHEMA, "/health": {"status": "healthy"}, "/list_environments": None}
388
- monkeypatch.setattr(live, "_get_json", lambda rec, path: answers.get(path))
389
- monkeypatch.setattr(live, "_ui_path", lambda rec: "/web/")
390
- monkeypatch.setattr(live, "_tools", lambda rec: ([{"name": "echo", "inputSchema": {"type": "object"}}], None, {"ok": True, "detail": "JSON-RPC 2.0"}))
391
- return rec
392
-
393
-
394
- def test_a_good_probe_is_kept_and_shown_while_the_space_sleeps(monkeypatch):
395
- rec = stub_running(monkeypatch)
396
- info = live.probe("org/env")
397
- assert info["running"] and info["mode"] == "simulation" and info["openapi_version"] == "1.0.0"
398
- assert [c["status"] for c in info["conformance"]] == ["pass"] * 6
399
- p = Path(config.STORAGE_DIR) / "space-probes" / "org__env.json"
400
- assert p.exists() and not [x for x in p.parent.iterdir() if x.name.endswith(".tmp")] # written whole
401
- kept = json.loads(p.read_text())
402
- assert kept["mcp"][0]["name"] == "echo" and kept["schema"] == SCHEMA and kept["ui"] == "/web/"
403
- assert kept["metadata"] == {"name": "env", "description": "an env"} # no README copy
404
- rec["stage"] = "SLEEPING"
405
- catalog._memo.clear()
406
- asleep = live.probe("org/env")
407
- assert not asleep["running"] and asleep["last_seen"]["mcp"][0]["name"] == "echo"
408
- assert asleep["last_seen"]["checked_at"] <= time.time() and len(asleep["last_seen"]["conformance"]) == 6
409
-
410
-
411
- def test_a_probe_that_got_nothing_does_not_replace_the_last_good_one(monkeypatch):
412
- stub_running(monkeypatch)
413
- live.probe("org/env")
414
- catalog._memo.clear()
415
- monkeypatch.setattr(live, "_get_json", lambda rec, path: None)
416
- monkeypatch.setattr(live, "_tools", lambda rec: (None, "the Space's /mcp answered HTTP 503", {"ok": False, "detail": "HTTP 503"}))
417
- bad = live.probe("org/env")
418
- assert bad["running"] and bad["conformance"][0]["status"] == "fail"
419
- assert live.last_seen("org/env")["mcp"][0]["name"] == "echo"
420
-
421
-
422
- def test_the_last_seen_record_is_trimmed_to_fit():
423
- huge = [{"name": f"t{i}", "inputSchema": {"description": "x" * 4000}, "outputSchema": {"d": "y" * 4000}} for i in range(120)]
424
- kept = live._seen_record("org/env", {"mcp": huge, "schema": SCHEMA, "checked": 1.0})
425
- assert len(json.dumps(kept)) <= live.SEEN_MAX and [t["name"] for t in kept["mcp"]] == [f"t{i}" for i in range(120)]
426
-
427
-
428
- def test_conformance_criteria():
429
- allgood = {"openapi": OPENAPI, "health": {"status": "healthy"}, "metadata": {"name": "a", "description": "b"}, "schema": SCHEMA,
430
- "mcp": {"ok": True, "detail": "JSON-RPC 2.0"}, "mcp_tried": True, "paths": list(OPENAPI["paths"])}
431
- ids = [c["id"] for c in live.conformance(allgood)]
432
- assert ids == ["openapi_version_available", "health_endpoint", "metadata_endpoint", "schema_endpoint", "mcp_endpoint", "mode_endpoint_consistency"]
433
- assert all(c["status"] == "pass" for c in live.conformance(allgood))
434
-
435
- def status(facts):
436
- return {c["id"]: c["status"] for c in live.conformance({**allgood, **facts})}
437
-
438
- assert status({"openapi": {"paths": {}}})["openapi_version_available"] == "fail"
439
- assert status({"health": {"status": "ok"}})["health_endpoint"] == "fail"
440
- assert status({"metadata": {"name": "a"}})["metadata_endpoint"] == "fail"
441
- assert status({"schema": {"action": {}, "observation": {}}})["schema_endpoint"] == "fail"
442
- assert status({"mcp": {"ok": False, "detail": "HTTP 404"}})["mcp_endpoint"] == "fail"
443
- assert status({"mcp": None})["mcp_endpoint"] == "fail" # not among its routes
444
- assert status({"mcp": None, "paths": None})["mcp_endpoint"] == "unknown"
445
- assert status({"paths": ["/mcp", "/health"]})["mode_endpoint_consistency"] == "pass" # production
446
- assert status({"paths": ["/mcp", "/step"]})["mode_endpoint_consistency"] == "fail" # production with /step
447
- assert status({"paths": ["/reset", "/step"]})["mode_endpoint_consistency"] == "fail" # simulation without /state
448
- assert status({"paths": None})["mode_endpoint_consistency"] == "fail"
449
- assert {c["status"] for c in live.conformance(None)} == {"unknown"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_space_tasks.py DELETED
@@ -1,41 +0,0 @@
1
- import time
2
-
3
- import pytest
4
-
5
- from app import space_tasks as tasks, spaces_live as live
6
-
7
-
8
- def test_multiple_environments_count_once_and_keep_unknowns(monkeypatch):
9
- docs = {"/list_environments": ["a", "a", "b", "../bad"],
10
- "/a/splits": [{"name": "train", "num_tasks": 12}, {"name": "train", "num_tasks": 12}],
11
- "/b/splits": [{"name": "test", "num_tasks": True}, {"name": "valid", "num_tasks": 0}]}
12
- monkeypatch.setattr(live, "_get_json", lambda rec, path: docs[path])
13
- d = tasks.discover({}, {"/{env_name}/splits"})
14
- assert [e["env"] for e in d["environments"]] == ["a", "b"]
15
- s = tasks.summary(d)
16
- assert s["tasks"] == 12 and s["counted_splits"] == 2 and s["unknown_splits"] == 1
17
- assert not s["complete"]
18
-
19
-
20
- @pytest.mark.parametrize("value", [True, -1, "100", 2.5, float("inf"), 10**30])
21
- def test_invalid_counts_never_inflate_totals(value):
22
- assert tasks.count(value) is None
23
-
24
-
25
- def test_census_filters_visibility_expiry_and_inactive_spaces():
26
- now = time.time()
27
- s = {"tasks": 19, "counted_splits": 1, "checked_at": now, "complete": True}
28
- records = {"a": {"stage": "RUNNING", "task_catalog": s},
29
- "private": {"stage": "RUNNING", "task_catalog": s},
30
- "asleep": {"stage": "SLEEPING", "task_catalog": s},
31
- "stale": {"stage": "RUNNING", "task_catalog": {**s, "checked_at": now - tasks.FRESH}}}
32
- assert tasks.census(records, ["a", "asleep", "stale"], now)["tasks"] == 19
33
- assert tasks.census(records, ["a"], now - 1)["tasks"] == 0
34
-
35
-
36
- def test_task_environment_must_be_advertised(monkeypatch):
37
- monkeypatch.setattr(live, "probe", lambda spec: {"running": True, "task_api": {"env": "a", "environments": [{"env": "a"}, {"env": "b"}]}})
38
- monkeypatch.setattr(live, "record", lambda spec: {})
39
- assert live._task_api("o/s", "b")[1] == "b"
40
- with pytest.raises(live.SpaceError):
41
- live._task_api("o/s", "../secrets")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/ui-audit.mjs DELETED
@@ -1,320 +0,0 @@
1
- // UI audit of the HF RL Explorer: every check clicks or types like a person would and asserts the result, then
2
- // every page is checked at four widths in both themes for errors, sideways scroll, broken images and unlabelled
3
- // buttons, with a screenshot of each.
4
- // npm i -D playwright && npx playwright install chromium (or PW_CHROME=/path/to/chrome-headless-shell)
5
- // node tests/ui-audit.mjs http://localhost:8060 /tmp/rlx-shots
6
- // Uses live Hub data and live Spaces (FineEnvs/geoguesser-env, FineEnvs/latex-ocr-env, ...): it needs network.
7
- import { chromium } from "playwright";
8
- import { mkdirSync } from "node:fs";
9
-
10
- const BASE = process.argv[2] || "http://localhost:8060";
11
- const SHOTS = process.argv[3] || "/tmp/rlx-shots";
12
- mkdirSync(SHOTS, { recursive: true });
13
- const HARBOR = "harborframework/terminal-bench-2.1", HTASK = "tasks/feal-differential-cryptanalysis";
14
- const MIMO = "XiaomiMiMo/MiMo-V2.6-RL-oss", MIMO_GENERAL = "s3k_0052_accounting_audit_tax_en_t2_rl_008";
15
- const results = [];
16
- const ok = (name, cond, detail = "") => { results.push({ name, pass: !!cond, detail }); if (!cond) console.log(" FAIL", name, detail); };
17
-
18
- const browser = await chromium.launch(process.env.PW_CHROME ? { executablePath: process.env.PW_CHROME } : {});
19
-
20
- async function page(width, theme = "light", height = 900) {
21
- const ctx = await browser.newContext({ viewport: { width, height }, deviceScaleFactor: 1, hasTouch: width < 768, isMobile: width < 768 });
22
- await ctx.addInitScript((t) => { try { localStorage.setItem("theme", t); localStorage.removeItem("fe-banner-closed"); } catch {} }, theme);
23
- const p = await ctx.newPage();
24
- p.errors = [];
25
- p.on("pageerror", (e) => p.errors.push("pageerror: " + e.message));
26
- p.on("console", (m) => { if (m.type() === "error" && !/favicon|Failed to load resource|net::ERR|cdn-thumbnails|hf\.space/.test(m.text())) p.errors.push("console: " + m.text()); });
27
- p.on("dialog", (d) => { p.errors.push("native dialog: " + d.message()); d.dismiss(); });
28
- p.on("response", (r) => { const u = r.url(); if (u.startsWith(BASE) && (r.status() === 403 || r.status() >= 500)) p.errors.push(`${r.status()} ${r.request().method()} ${u.slice(BASE.length, BASE.length + 120)}`); });
29
- return p;
30
- }
31
- // a live Space's interface fills in once its server answers the probe: wait for that, not for the section's skeleton
32
- const liveIn = async (p, ms = 60000) => { try { await p.waitForFunction(() => /live/.test(document.querySelector("#sec-interface")?.textContent || "") && document.querySelector(".pg-opts, #pg-route, #pg-reset"), null, { timeout: ms }); return true; } catch { return false; } };
33
- const here = (p) => p.evaluate(() => location.pathname + location.search + location.hash); // pages are paths now; old #/ links redirect
34
- const go = async (p, to, wait = 1200) => { await p.goto(BASE + "/" + to.replace(/^\/+/, ""), { waitUntil: "domcontentloaded" }); await p.waitForTimeout(wait); }; // "#/old", "path" or "/path"
35
- const until = async (p, sel, ms = 20000) => { try { await p.waitForSelector(sel, { timeout: ms }); return true; } catch { return false; } };
36
-
37
- async function health(p, label) {
38
- const r = await p.evaluate(() => {
39
- const over = document.documentElement.scrollWidth > document.documentElement.clientWidth + 1;
40
- const wide = over ? [...document.querySelectorAll("body *")].filter((e) => e.getBoundingClientRect().right > document.documentElement.clientWidth + 2
41
- && getComputedStyle(e).position !== "fixed" && e.offsetParent).slice(0, 4).map((e) => `${e.tagName.toLowerCase()}.${[...e.classList].join(".")}`) : [];
42
- const broken = [...document.images].filter((i) => i.complete && i.naturalWidth === 0 && i.src && !i.src.includes("cdn-thumbnails") && i.offsetParent).map((i) => i.src.slice(0, 80));
43
- const unnamed = [...document.querySelectorAll("button, a[href]")].filter((b) => b.offsetParent && !(b.textContent.trim() || b.getAttribute("aria-label") || b.getAttribute("title"))).map((b) => b.outerHTML.slice(0, 90));
44
- const cut = [];
45
- for (const e of document.querySelectorAll("body *")) {
46
- if (!e.offsetParent || ![...e.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim())) continue;
47
- if (getComputedStyle(e).textOverflow === "ellipsis") continue;
48
- const r = e.getBoundingClientRect();
49
- for (let a = e.parentElement; a && a !== document.body; a = a.parentElement) {
50
- const ox = getComputedStyle(a).overflowX;
51
- if (ox === "auto" || ox === "scroll") break;
52
- if (ox !== "visible") {
53
- if (getComputedStyle(a).textOverflow === "ellipsis") break; // cut on purpose, with an ellipsis
54
- const ar = a.getBoundingClientRect();
55
- if (r.right > ar.right + 1 && !a.matches(".sp-facts")) cut.push(`${e.tagName.toLowerCase()}.${[...e.classList].join(".")} "${e.textContent.trim().slice(0, 30)}"`);
56
- break;
57
- }
58
- }
59
- }
60
- // content spilling out of its panel, card or column over its neighbour (nothing clips it, so "cut" misses it)
61
- const spill = [];
62
- const inScroller = (e, box) => { for (let a = e.parentElement; a && a !== box; a = a.parentElement) { const s = getComputedStyle(a); if (s.overflowX !== "visible" || s.textOverflow === "ellipsis") return true; } return false; };
63
- for (const box of document.querySelectorAll(".panel, .card, .pg-ctl, #pg-log, .tp-side, .sp-side")) {
64
- if (!box.offsetParent) continue;
65
- const br = box.getBoundingClientRect();
66
- for (const e of box.querySelectorAll("*")) {
67
- if (!e.offsetParent || getComputedStyle(e).position === "absolute") continue;
68
- if (e.getBoundingClientRect().right > br.right + 2 && !inScroller(e, box)) { spill.push(`${e.tagName.toLowerCase()}.${[...e.classList].join(".")} out of ${box.tagName.toLowerCase()}.${[...box.classList].join(".") || box.id}`); break; }
69
- }
70
- }
71
- return { over, wide, broken, unnamed, cut: [...new Set(cut)], spill: [...new Set(spill)] };
72
- });
73
- ok(`${label}: no sideways scroll`, !r.over, r.wide.join(" "));
74
- ok(`${label}: no broken images`, !r.broken.length, r.broken.join(" "));
75
- ok(`${label}: every button and link is named`, !r.unnamed.length, r.unnamed.slice(0, 3).join(" | "));
76
- ok(`${label}: no text cut off by its card or panel`, !r.cut.length, r.cut.slice(0, 3).join(" | "));
77
- ok(`${label}: nothing spills out of its panel or column`, !r.spill.length, r.spill.slice(0, 3).join(" | "));
78
- ok(`${label}: no errors`, !p.errors.length, p.errors.slice(0, 4).join(" | "));
79
- p.errors = [];
80
- }
81
-
82
- // ── desktop: interactions ────────────────────────────────────────────────────
83
- {
84
- const p = await page(1440, "dark");
85
- await go(p, "#/", 600);
86
- ok("home: the app replaces the boot loader", await until(p, "#list .card"));
87
- ok("home: no collection tiles", !(await p.$("#domains, .domains")));
88
- ok("home: one search box (the header's hides on Explore)", await p.evaluate(() => getComputedStyle(document.querySelector(".top-search")).visibility === "hidden"));
89
- ok("home: the FineEnvs banner is one link", await p.evaluate(() => { const a = document.querySelector("#fe-banner a.fe-banner-link"); return a && !document.querySelector("#fe-banner").hidden && a.href.includes("huggingface.co/FineEnvs"); }));
90
- ok("home: heading says what it explores", /Explore RL environments on Hugging Face/.test(await p.textContent("h1")));
91
- const kinds = await p.$$eval("[data-kind]", (bs) => bs.map((b) => b.textContent));
92
- ok("home: Kind filter covers the frameworks", ["Harbor", "Spaces", "Verifiers", "NeMo Gym"].every((k) => kinds.some((x) => x.includes(k))), kinds.join(","));
93
- ok("home: ORS isn't a Kind of its own (its one Space is with the other Spaces)", !kinds.some((x) => /ORS/.test(x)), kinds.join(","));
94
- await p.click('[data-kind="openenv"]');
95
- await p.waitForTimeout(1500); // the server answers (/api/search), after a short debounce
96
- const firstOE = await p.$$eval("#list .card", (cs) => cs.slice(0, 8).map((c) => c.getAttribute("href")));
97
- ok("home: OpenEnv Spaces open on FineEnvs' curated servers", firstOE.length === 8 && firstOE.every((h) => h.startsWith("/s/FineEnvs/")), firstOE.join(" "));
98
- await p.click('[data-kind="verifiers"]');
99
- await p.waitForTimeout(1500);
100
- ok("home: filtering to Verifiers shows only Verifiers datasets", await p.$$eval("#list .card .kind-tag", (ts) => ts.length > 0 && ts.every((t) => t.textContent.includes("Verifiers"))));
101
- await p.click('[data-kind="all"]');
102
- await p.waitForTimeout(1200);
103
- await p.fill("#q", "terminal-bench");
104
- // the server answers the search (/api/search): wait for its answer, not a fixed time (a Space is a round trip away)
105
- const narrowed = await p.waitForFunction(() => { const cs = [...document.querySelectorAll("#list .card")]; return cs.length > 0 && cs.every((c) => /terminal/i.test(c.textContent)); }, null, { timeout: 15000 }).then(() => true, () => false);
106
- ok("home: search narrows the list", narrowed);
107
- await p.fill("#q", "");
108
- const page1 = await p.textContent("#tr-page");
109
- await p.click("#tr-next");
110
- await p.waitForTimeout(200);
111
- ok("home: trending pages forward", (await p.textContent("#tr-page")) !== page1);
112
- ok("home: Space cards show the whole picture", await p.$$eval("#list .card.sp .sp-thumb", (is) => is.slice(0, 3).every((i) => Math.abs(i.getBoundingClientRect().width / i.getBoundingClientRect().height - 1200 / 630) < 0.05)));
113
- await health(p, "home 1440 dark");
114
-
115
- // a Harbor task's file viewer
116
- await go(p, `#/t/${HARBOR}/${HTASK}`, 600);
117
- ok("task: file viewer renders", await until(p, ".fv .fv-row.fv-file"));
118
- await p.click('.fv-row.fv-file[data-path="tests/test.sh"]');
119
- await p.waitForTimeout(500);
120
- ok("task: a shell file is highlighted with line numbers", await p.$$eval(".fv-body .tk-k", (x) => x.length > 2) && await p.$(".fv-body .ln .n"));
121
- ok("task: the open file is in the URL", (await here(p)).includes("f=tests%2Ftest.sh"));
122
- await p.click(".fv-body .ln:nth-child(3) .n");
123
- ok("task: a line can be linked", (await here(p)).includes("L=3"));
124
- await p.click('[data-act="full"]');
125
- ok("task: full view covers the page, above the header", await p.evaluate(() => {
126
- const fv = document.querySelector(".fv.full"); if (!fv) return false;
127
- const r = fv.getBoundingClientRect(); const hit = document.elementFromPoint(r.left + 40, r.top + 20);
128
- return fv.contains(hit);
129
- }));
130
- await p.keyboard.press("Escape");
131
- ok("task: Esc closes full view", !(await p.$(".fv.full")));
132
- const solution = await p.$('.fv-row.fv-file.withheld');
133
- if (solution) { await solution.click(); await p.waitForTimeout(200); ok("task: withheld files say so", /Not shown/.test(await p.textContent(".fv-body"))); }
134
- await health(p, "task 1440 dark");
135
-
136
- // a big Harbor dataset: walked, opens immediately
137
- await go(p, "#/d/microsoft/ProgramDistill", 2500);
138
- ok("big dataset: its tasks list without waiting for every file", await until(p, "#list .card, .ix-partial, .tk-card, [data-task]", 30000) || (await p.textContent("#body")).length > 200);
139
- await health(p, "ProgramDistill 1440");
140
-
141
- // rows: a generic dataset, paged, its facets filtering on the server
142
- await go(p, "#/d/FineEnvs/SmolDataEnvs", 600);
143
- ok("rows: list renders", await until(p, "#list .card", 30000));
144
- ok("rows: says how it's read", /How it's read/.test(await p.textContent(".rw-how")) && /answer/i.test(await p.textContent(".rw-roles")));
145
- const firstTitles = await p.$$eval("#list .card .t", (ts) => ts.slice(0, 6).map((t) => t.textContent));
146
- ok("rows: titles differ from row to row", new Set(firstTitles).size === firstTitles.length, firstTitles.join(" / "));
147
- const opt = await p.$("#facets .opt");
148
- if (opt) {
149
- await opt.click();
150
- await p.waitForFunction(() => /matching/.test(document.querySelector("#count").textContent) || /available/.test(document.querySelector("#note").textContent), null, { timeout: 60000 }).catch(() => {});
151
- ok("rows: a facet filters (or says it can't yet)", /matching/.test(await p.textContent("#count")) || /isn't available/.test(await p.textContent("#note")), await p.textContent("#count"));
152
- }
153
- await health(p, "rows SmolDataEnvs 1440");
154
- await go(p, "#/r/FineEnvs/SmolDataEnvs?c=default&s=train&i=3", 600);
155
- ok("row: an old row link opens its task page", await until(p, ".tp-sec", 30000) && (await here(p)).startsWith("/t/FineEnvs/SmolDataEnvs/train/3"));
156
- ok("row: the answer is withheld", /Withheld/.test(await p.textContent(".tp-side")) && !(await p.textContent(".tp-body")).includes('"answer"'));
157
- await p.click('.tp-nav a[title^="Next"]');
158
- await p.waitForTimeout(2500);
159
- ok("row: next task navigates", (await here(p)).includes("train/4"));
160
- await p.keyboard.press("ArrowLeft");
161
- await p.waitForTimeout(2500);
162
- ok("row: ← goes back a task", (await here(p)).includes("train/3"));
163
- await health(p, "row 1440");
164
-
165
- // a packed Harbor task (TaskTrove)
166
- await go(p, "#/t/open-thoughts/TaskTrove/train/100", 600);
167
- ok("packed: unpacked into a file tree", await until(p, ".fv .fv-row.fv-file", 40000));
168
- ok("packed: its solution is withheld", await p.$('.fv-row.fv-file.withheld[data-path="solution/solve.sh"]'));
169
- ok("packed: runs on Harbor", /Harbor/.test((await (await p.$("#run-card"))?.textContent()) || ""));
170
- await health(p, "TaskTrove task 1440");
171
-
172
- // MiMo: the release, as the MiMo explorer shows it
173
- await go(p, `#/d/${MIMO}`, 600);
174
- ok("MiMo: every environment listed", await until(p, "#list .card", 40000) && /7,780/.test(await p.textContent("#count")));
175
- ok("MiMo: a tile per domain, with its grader", (await p.$$("#tiles .dom")).length === 6 && /Graded by hidden unit tests/.test(await p.textContent("#tiles")));
176
- await p.click('#tiles .dom[data-tile="Cyber"]');
177
- await p.waitForTimeout(500);
178
- ok("MiMo: a domain's tile scopes the facets and the map", /Sanitizer/i.test(await p.textContent("#facets")) && /Cyber, by crash type/.test(await p.textContent("#map-title")));
179
- await p.fill("#q", "heap overflow png");
180
- await p.waitForTimeout(600);
181
- ok("MiMo: search ranks and highlights", /best match/.test(await p.textContent("#order-note")) && (await p.$("#list .card mark")));
182
- await p.evaluate(() => { document.querySelector("#ov-rewards").open = true; });
183
- ok("MiMo: the reward design unfolds", /General rubrics, across the dataset/.test(await p.textContent("#ov-rewards")));
184
- await health(p, "MiMo env 1440");
185
- await go(p, `#/t/${MIMO}/${MIMO_GENERAL}`, 600);
186
- ok("MiMo task: its systems and workspace", await until(p, ".tbl-btn", 90000) && (await p.$(".files-grid .file")));
187
- await p.click(".tbl-btn");
188
- ok("MiMo task: a table opens with the rows the agent starts with", await until(p, "#modal:not([hidden]) table", 30000));
189
- await p.click("#modal-close");
190
- await p.click('.files-grid .file[data-kind="document"]');
191
- ok("MiMo task: a Word file previews", await until(p, "#modal:not([hidden]) .pv-doc", 40000));
192
- await p.click("#modal-close");
193
- ok("MiMo task: the rubric's checks, answers withheld", /checks, weighted/.test(await p.textContent("#sec-grading")) && /pass_anchor/.test(await p.textContent(".tp-side")));
194
- ok("MiMo task: both runners offered, the MiMo harness first here", /MiMo harness/.test(await p.textContent("#run-runner")));
195
- ok("MiMo task: links its Harbor twin", await p.$('.tp-links a[href^="/t/FineEnvs/MiMo-V2.6-RL-harbor-general/"]'));
196
- await health(p, "MiMo task 1440");
197
- await go(p, `#/r/${MIMO}?c=code&s=train&i=6`, 600);
198
- ok("MiMo: an old row link opens the task by its id", await until(p, ".tp-sec", 60000) && /^\/t\/XiaomiMiMo\/MiMo-V2\.6-RL-oss\/format-code-task-/.test(await here(p)));
199
- const twin = await p.$eval('.tp-links a[href^="/t/FineEnvs/MiMo-V2.6-RL-harbor-code/"]', (a) => a.getAttribute("href")).catch(() => null);
200
- ok("MiMo: links its Harbor twin", !!twin);
201
- if (twin) {
202
- await go(p, twin, 600);
203
- ok("MiMo twin: links back to the release", await until(p, `.tp-links a[href^="/t/${MIMO}/"]`, 40000));
204
- }
205
- await go(p, "#/task/music-gK-0768", 600);
206
- ok("MiMo explorer's addresses still open", await until(p, ".tp-sec", 40000) && (await here(p)) === `/t/${MIMO}/music-gK-0768`);
207
- await go(p, "#/community?view=tasks", 600);
208
- ok("community: legacy Tasks links open the rollout feed", await until(p, "#cm-filters select[data-f=runner]", 60000) && !(await p.$("#cm-view")));
209
- await health(p, "community rollouts 1440");
210
-
211
- // a live Space: playground, tasks, connect
212
- await go(p, "#/s/FineEnvs/geoguesser-env", 600);
213
- ok("space: what it offers fills in", await liveIn(p));
214
- ok("space: status says running", /Running/.test(await p.textContent("#sp-status")));
215
- await p.evaluate(() => { const o = document.querySelector(".pg-opts"); if (o) o.open = true; });
216
- await p.selectOption("#pg-split", "eval");
217
- await p.fill("#pg-index", "5");
218
- await p.click("#pg-reset");
219
- ok("space: a reset starts an episode with an image", await until(p, "#pg-log .rv-img", 40000));
220
- await p.selectOption("#pg-tool", "look");
221
- await p.getByRole("spinbutton", { name: "heading_deg", exact: true }).fill("90");
222
- await p.click("#pg-call");
223
- await p.waitForTimeout(5000);
224
- ok("space: a tool call answers in the episode", /Facing 90/.test(await p.textContent("#pg-log")));
225
- ok("space: tasks list from its Task API", await until(p, "#tk-table [data-task]", 30000));
226
- await p.click("#tk-table [data-task='1']");
227
- ok("space: a task opens in full", await until(p, "#tk-detail:not([hidden]) .tk-detail-b"));
228
- await p.click('[data-agent="cursor"]');
229
- ok("space: connect shows each agent's config", /mcpServers/.test(await p.textContent("#sp-agent")) && (await p.textContent("#sp-agent")).includes("/mcp/FineEnvs/geoguesser-env"));
230
- await health(p, "space geoguesser 1440");
231
-
232
- await go(p, "s/FineEnvs/nayana-ocr-env", 600); // ids and paths hundreds of characters long, no spaces
233
- if (await liveIn(p)) {
234
- await p.click("#pg-reset");
235
- ok("space (long ids): an episode starts", await until(p, "#pg-log .pg-entry", 40000));
236
- await p.waitForTimeout(800);
237
- await health(p, "space nayana episode 1440");
238
- }
239
- await go(p, "#/s/FineEnvs/latex-ocr-env", 600);
240
- ok("space (reset/step): fills in", await liveIn(p));
241
- await p.click("#pg-reset");
242
- ok("space (reset/step): reset shows the image", await until(p, "#pg-log .rv-img", 40000));
243
- await p.getByRole("textbox", { name: /^latex$/i }).fill("x^2");
244
- await p.click("#pg-step");
245
- ok("space (reset/step): a step earns a reward", await until(p, "#pg-log .pg-entry:last-of-type .rv-reward", 40000));
246
- await health(p, "space latex 1440");
247
-
248
- // a NeMo Gym server: its own routes, with the session's cookie carrying the game
249
- await go(p, "#/s/FineEnvs/wordle-nemo-gym", 600);
250
- if (await until(p, "#pg-route", 40000)) {
251
- ok("nemo gym: labelled as such", /NeMo Gym/.test(await p.textContent("main")));
252
- await p.selectOption("#pg-route", { label: "POST /seed_session" });
253
- await p.click("#pg-send");
254
- await p.waitForTimeout(2500);
255
- await p.selectOption("#pg-route", { label: "POST /guess" });
256
- ok("nemo gym: a short string is a one-line input", await p.$('input[data-k="word"]'));
257
- await p.getByRole("textbox", { name: "Word", exact: true }).fill("crane");
258
- await p.click("#pg-send");
259
- await p.waitForTimeout(2500);
260
- await p.selectOption("#pg-route", { label: "POST /get_history" });
261
- await p.click("#pg-send");
262
- await p.waitForTimeout(2500);
263
- ok("nemo gym: the session keeps the game between calls", /Guess 1: crane/.test(await p.textContent("#pg-log")));
264
- } else ok("nemo gym: routes playground renders", false, "no #pg-route (asleep?)");
265
- await health(p, "nemo gym space 1440");
266
-
267
- await go(p, "#/s/AdithyaSK/openenv-harbor-ui-test", 600);
268
- if (await until(p, "#sec-interface .panel-b", 40000) && (await p.$("#sec-harbor"))) {
269
- ok("harbor space: maps its dataset back to the explorer", await p.$('#sec-harbor a[href="/d/FineEnvs/MiMo-V2.6-RL-harbor-terminal"]'));
270
- }
271
- await health(p, "harbor space 1440");
272
- await p.context().close();
273
- }
274
-
275
- // ── the frameworks' rows, and a model-graded task ────────────────────────────
276
- {
277
- const p = await page(1440, "dark");
278
- await go(p, "t/nvidia/Nemotron-RL-Agentic-Function-Calling-Pivot-v1/train/0", 600);
279
- ok("NeMo Gym: the row reads as a conversation, not a table", await until(p, ".tp-sec", 40000)
280
- && await p.evaluate(() => { const t = document.querySelector(".tp-sec"); const turns = t ? [...t.querySelectorAll(".rl-turn")] : [];
281
- return turns.length > 1 && turns.every((x) => !x.closest("table")) && turns.some((x) => /user/i.test(x.querySelector(".rl-who")?.textContent || "")); }));
282
- // (a tool's output may be shown as a table: data, inside its turn; the turns themselves never are)
283
- ok("NeMo Gym: its title is the request, not a row number", !/^(Row )?\d+$/.test((await p.textContent("h1")).trim()));
284
- ok("NeMo Gym: the expert's next action is withheld", /expected_action/.test(await p.textContent(".tp-side")) && !(await p.textContent(".tp-body")).includes('"expected_action"'));
285
- ok("NeMo Gym: says how to run it with NeMo Gym", /Run it with NeMo Gym/.test(await p.textContent("main")));
286
- await health(p, "NeMo Gym row 1440");
287
- await go(p, "t/sungyub/deepscaler-preview-verl/train/0", 600);
288
- ok("verl: the ground truth is withheld", await until(p, ".tp-sec", 40000) && /ground_truth/.test(await p.textContent(".tp-side")));
289
- ok("verl: its math is typeset (KaTeX), not left as TeX", await until(p, "h1 .math .katex", 20000) && !(await p.textContent("h1")).includes("\\frac"));
290
- await health(p, "verl row 1440");
291
- await go(p, "t/FineEnvs/MiMo-V2.6-RL-harbor-general/tasks/s3k_0052_accounting_audit_tax_en_t2_rl_008", 600);
292
- // signed in, the run panel offers the judge; signed out (a Space without a session), the page still says how it's graded
293
- ok("judge: a model-graded task offers a judge, through the relay", await until(p, "#run-card", 30000) && (
294
- (/Judge model/.test(await p.textContent("#run-card")) && /relay/.test(await p.textContent("#run-card")))
295
- || (/Sign in/i.test(await p.textContent("#run-card")) && /Graded by a model/.test(await p.textContent("main")))));
296
- await health(p, "model-graded task 1440");
297
- await p.context().close();
298
- }
299
-
300
- // ── every page, every width, both themes ─────────────────────────────────────
301
- const PAGES = [["home", "#/"], ["dataset", `#/d/${HARBOR}`], ["task", `#/t/${HARBOR}/${HTASK}`], ["rows", "#/d/FineEnvs/SmolDataEnvs"],
302
- ["row", "#/t/FineEnvs/SmolDataEnvs/train/2"], ["packed", "#/t/open-thoughts/TaskTrove/train/100"],
303
- ["space", "#/s/FineEnvs/geoguesser-env"], ["traces", "#/t/open-thoughts/AgentTrove/train/0"], ["mimo", `#/d/${MIMO}`],
304
- ["mimo-task", `#/t/${MIMO}/${MIMO_GENERAL}`], ["mimo-code", `#/t/${MIMO}/format-code-task-000720`], ["community", "#/community"]];
305
- for (const [w, theme] of [[375, "light"], [768, "dark"], [1280, "light"], [1680, "dark"]]) {
306
- const p = await page(w, theme);
307
- for (const [name, hash] of PAGES) {
308
- await go(p, hash, 3500);
309
- await p.waitForTimeout(name === "space" ? 6000 : 1500);
310
- await p.screenshot({ path: `${SHOTS}/${name}-${w}-${theme}.png`, fullPage: false });
311
- await health(p, `${name} ${w} ${theme}`);
312
- }
313
- await p.context().close();
314
- }
315
-
316
- await browser.close();
317
- const failed = results.filter((r) => !r.pass);
318
- console.log(`\n${results.length - failed.length}/${results.length} checks passed`);
319
- for (const f of failed) console.log(`FAIL ${f.name}${f.detail ? ` (${f.detail})` : ""}`);
320
- process.exit(failed.length ? 1 : 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web-admin/admin.css DELETED
@@ -1,175 +0,0 @@
1
- /* The admin dashboard, on top of the explorer's app.css and rlx.css. */
2
- /* admin */
3
- .ad-tabs { flex-wrap: wrap; }
4
- .ad-stats { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px; }
5
- .ad-stat { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 12px 14px; display: grid; gap: 1px; min-width: 0; }
6
- .ad-stat b { font-size: 20px; font-weight: 650; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
7
- .ad-stat span { font-size: 12.5px; color: var(--muted); }
8
- .ad-stat em { font-style: normal; font-size: 12px; color: var(--faint); }
9
- .ad-grid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 16px; margin-top: 16px; }
10
- .ad-bars { display: grid; grid-template-columns: repeat(14, minmax(0, 1fr)); gap: 4px; height: 120px; align-items: end; }
11
- .ad-bars div { display: grid; grid-template-rows: 1fr auto; height: 100%; align-items: end; gap: 3px; }
12
- .ad-bars i { display: block; border-radius: 4px 4px 0 0; background: var(--focus); opacity: .8; min-height: 2px; }
13
- .ad-bars span { font-size: 10.5px; color: var(--faint); text-align: center; font-variant-numeric: tabular-nums; }
14
- .ad-new { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
15
- .ad-new li { display: flex; justify-content: space-between; gap: 10px; font-size: 13px; }
16
- .ad-new a { display: inline-flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; }
17
- .ad-new code { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
18
- .ad-runs { display: grid; gap: 0; }
19
- .ad-run { display: grid; grid-template-columns: 130px minmax(0, 1fr) 140px 200px 60px 90px auto; gap: 12px; align-items: center; padding: 9px 0; border-top: 1px solid var(--border); font-size: 13px; }
20
- .ad-run:first-child, .ad-runs > p + .ad-run { border-top: 0; }
21
- .ad-task { display: grid; min-width: 0; } .ad-task b { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
22
- .ad-task em, .ad-when em { font-style: normal; font-size: 12px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
23
- .ad-user, .ad-model { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-flex; align-items: center; gap: 5px; }
24
- .ad-when { font-size: 12.5px; color: var(--faint); text-align: right; }
25
- .ad-act { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }
26
- .ad-envs { display: grid; }
27
- .ad-env { display: grid; grid-template-columns: minmax(0, 1fr) 170px 80px 90px 70px 70px 150px; gap: 10px; align-items: center; padding: 8px 14px; border-top: 1px solid var(--border); font-size: 13px; }
28
- .ad-env.head { border-top: 0; background: var(--surface-2); font-size: 11.5px; font-weight: 600; color: var(--faint); text-transform: uppercase; letter-spacing: .04em; }
29
- .ad-env.gone { opacity: .55; }
30
- .ad-env .num { text-align: right; font-variant-numeric: tabular-nums; }
31
- .ad-name { display: flex; align-items: center; gap: 6px; min-width: 0; flex-wrap: wrap; }
32
- .ad-name a { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; }
33
- .ad-name code { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
34
- .hf-select-mimo.sm { height: 28px; font-size: 12.5px; width: 100%; }
35
- .ad-cols { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 460px), 1fr)); gap: 12px; }
36
- .ad-form { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }
37
- .ad-form .wide { grid-column: 1 / -1; }
38
- .ad-form textarea { height: auto; padding: 8px 10px; resize: vertical; font-size: 12.5px; line-height: 1.5; }
39
- .ad-checks { display: flex; flex-wrap: wrap; gap: 8px 18px; font-size: 13.5px; }
40
- .ad-checks label, .ad-switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
41
- .ad-switch span { display: grid; } .ad-switch em { font-style: normal; font-size: 12.5px; color: var(--faint); }
42
- .ad-audit { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; font-size: 13px; }
43
- .ad-audit li { display: grid; grid-template-columns: 110px 140px minmax(0, 1fr); gap: 10px; }
44
- .ic.up { transform: rotate(180deg); }
45
- @media (max-width: 1100px) { .ad-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); } .ad-grid { grid-template-columns: minmax(0, 1fr); }
46
- .ad-run { grid-template-columns: 120px minmax(0, 1fr) 60px auto; } .ad-run .ad-user, .ad-run .ad-model, .ad-run .ad-when { display: none; }
47
- .ad-env { grid-template-columns: minmax(0, 1fr) 140px 120px; } .ad-env > .num { display: none; } }
48
- @media (max-width: 760px) { .ad-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } .ad-form { grid-template-columns: minmax(0, 1fr); }
49
- .ad-env { grid-template-columns: minmax(0, 1fr); } .ad-env.head { display: none; } .ad-act { justify-content: flex-start; }
50
- .ad-run { grid-template-columns: minmax(0, 1fr) auto; } .ad-run > span:first-child { grid-column: 1 / -1; } .ad-audit li { grid-template-columns: minmax(0, 1fr); gap: 0; } }
51
-
52
- /* layout: a section list on the left, the section on the right */
53
- .ad-shell { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 28px; padding-top: 22px; padding-bottom: 64px; align-items: start; }
54
- .ad-nav { position: sticky; top: calc(var(--header-h) + 18px); display: grid; gap: 2px; }
55
- .ad-nav a { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px; color: var(--muted); font-weight: 500; font-size: 13.5px; }
56
- .ad-nav a:hover { background: var(--surface-2); color: var(--text); }
57
- .ad-nav a.on { background: var(--surface-2); color: var(--text); box-shadow: inset 2px 0 0 var(--text); }
58
- .ad-nav a .ic { color: var(--faint); }
59
- .ad-nav a.on .ic { color: var(--text); }
60
- .ad-nav em { margin-left: auto; font-style: normal; font-size: 11.5px; font-weight: 600; color: var(--muted); background: var(--surface-3); border-radius: 99px; padding: 1px 7px; }
61
- .ad-nav em.live { color: var(--live); background: color-mix(in srgb, var(--live) 14%, transparent); }
62
- .ad-main { min-width: 0; }
63
- .ad-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px 20px; flex-wrap: wrap; margin-bottom: 16px; }
64
- .ad-head h1 { font-size: 22px; letter-spacing: -.02em; }
65
- .ad-head p { color: var(--muted); font-size: 13.5px; margin-top: 4px; max-width: 720px; }
66
- .ad-actions { display: flex; gap: 8px; flex-wrap: wrap; }
67
- .ad-tools { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
68
- .ad-tools .search { flex: 1 1 260px; }
69
- .ad-tools .seg { flex-wrap: wrap; }
70
-
71
- /* overview */
72
- .ad-hello { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; flex-wrap: wrap; }
73
- .ad-hello h1 { font-size: 22px; letter-spacing: -.02em; }
74
- .ad-hello p { color: var(--muted); font-size: 13.5px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
75
- .ad-hello .ad-actions { margin-left: auto; }
76
- .ad-stat { display: flex; gap: 12px; align-items: flex-start; }
77
- .ad-stat .ic-wrap { width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center; background: var(--surface-2); color: var(--muted); flex: none; }
78
- .ad-stat > div { display: grid; gap: 1px; min-width: 0; }
79
- .ad-stat.live .ic-wrap { color: var(--live); background: color-mix(in srgb, var(--live) 12%, transparent); }
80
- .ad-stat.warn .ic-wrap { color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); }
81
- .ad-live { margin-top: 16px; border-color: color-mix(in srgb, var(--live) 35%, var(--border)); }
82
- .ad-models { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; font-size: 13px; }
83
- .ad-models li { display: grid; grid-template-columns: minmax(0, 1fr) 90px 30px; gap: 10px; align-items: center; }
84
- .ad-models code { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
85
- .ad-models b { text-align: right; font-variant-numeric: tabular-nums; }
86
- .ad-new li { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 10px; align-items: center; }
87
- .ad-new a { display: grid; min-width: 0; }
88
- .ad-new a em { font-style: normal; font-size: 12px; color: var(--faint); }
89
- .ad-th { width: 40px; height: 30px; border-radius: 6px; object-fit: cover; border: 1px solid var(--border); background: var(--surface-2); flex: none; }
90
- .ad-th.ic-only { display: grid; place-items: center; color: var(--faint); }
91
- .ad-env .ad-th { width: 34px; height: 26px; }
92
-
93
- /* people */
94
- .pfp { border-radius: 50%; flex: none; background: var(--surface-3); object-fit: cover; }
95
- .pfp.none { display: inline-grid; place-items: center; font-size: 11px; font-weight: 700; color: var(--muted); }
96
- .person { display: inline-flex; align-items: center; gap: 7px; min-width: 0; font-size: 13px; }
97
- .person a, .person .link, .person b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-weight: 500; }
98
- .person .link:hover, .person a:hover { text-decoration: underline; }
99
- .ad-people { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); gap: 12px; }
100
- .ad-person { padding: 16px; display: grid; gap: 14px; align-content: start; }
101
- .ad-person header { display: flex; gap: 12px; align-items: center; }
102
- .ad-person header > div { display: grid; min-width: 0; }
103
- .ad-person header a { display: inline-flex; align-items: center; gap: 4px; color: var(--text); }
104
- .ad-person header em { font-style: normal; font-size: 12.5px; color: var(--faint); }
105
- .live-txt { color: var(--live); }
106
- .ad-pstats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }
107
- .ad-pstats div { background: var(--surface-2); border-radius: 8px; padding: 7px 8px; display: grid; }
108
- .ad-pstats b { font-size: 15px; font-variant-numeric: tabular-nums; }
109
- .ad-pstats span { font-size: 11.5px; color: var(--faint); }
110
- .ad-person .btn { justify-self: start; }
111
- .ad-run.is-live { background: color-mix(in srgb, var(--live) 5%, transparent); }
112
- .ad-model { display: grid; min-width: 0; }
113
- .ad-model em { font-style: normal; font-size: 12px; color: var(--faint); }
114
- .chip.new { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent); }
115
-
116
- /* collections, settings */
117
- .ad-swatch { width: 24px; height: 24px; border-radius: 7px; display: inline-grid; place-items: center; color: #fff; background: var(--dc); }
118
- .ad-swatch .ic { color: #fff !important; }
119
- .ad-settings { display: grid; gap: 0; padding: 4px 20px; max-width: 820px; }
120
- .ad-set { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 16px 0; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
121
- .ad-set.col { display: grid; gap: 10px; justify-content: stretch; }
122
- .ad-set b { display: block; font-size: 14px; }
123
- .ad-set span { font-size: 12.5px; color: var(--faint); }
124
- .ad-nums { display: flex; align-items: center; gap: 8px; }
125
- .ad-nums .input { width: 80px; }
126
- .ad-save-row { display: flex; align-items: center; justify-content: flex-end; gap: 12px; padding: 16px 0; }
127
- .ad-preview { border-radius: 8px; }
128
- .toggle { position: relative; display: inline-block; width: 42px; height: 24px; flex: none; cursor: pointer; }
129
- .toggle input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
130
- .toggle i { position: absolute; inset: 0; border-radius: 99px; background: var(--surface-3); transition: background .15s; }
131
- .toggle i::after { content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: #fff; box-shadow: var(--shadow-sm); transition: transform .15s; }
132
- .toggle input:checked + i { background: var(--ok); }
133
- .toggle input:checked + i::after { transform: translateX(18px); }
134
- .toggle input:focus-visible + i { box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 30%, transparent); }
135
- .ad-audit li { grid-template-columns: 170px minmax(0, 1fr) 90px; align-items: center; }
136
- @media (max-width: 1000px) {
137
- .ad-shell { grid-template-columns: minmax(0, 1fr); gap: 14px; }
138
- .ad-nav { position: static; display: flex; overflow-x: auto; gap: 4px; scrollbar-width: none; margin: 0 calc(-1 * var(--gutter)); padding: 0 var(--gutter); }
139
- .ad-nav a { flex: none; } .ad-nav a.on { box-shadow: inset 0 -2px 0 var(--text); border-radius: 8px 8px 0 0; }
140
- }
141
- @media (max-width: 760px) { .ad-hello .ad-actions { margin-left: 0; } .ad-pstats { grid-template-columns: repeat(2, minmax(0, 1fr)); } .ad-audit li { grid-template-columns: minmax(0, 1fr); } }
142
-
143
- /* Evidence and maintenance are separate from dataset indexes and saved runs. */
144
- .ad-operations { margin: 16px 0; }
145
- .ad-ops-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 24px; }
146
- .ad-ops-grid > div { min-width: 0; }
147
- .ad-ops-grid p { margin: 6px 0 10px; overflow-wrap: anywhere; }
148
- .ad-ops-grid dd, .ad-ops-grid dt { min-width: 0; overflow-wrap: anywhere; }
149
- .ad-check-states, .ad-runner-summary { display: flex; flex-wrap: wrap; gap: 6px; }
150
- .ad-check-states, .ad-ops-note { grid-column: 1 / -1; }
151
- .ad-check-states .chip { max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
152
- .ad-ops-note summary { cursor: pointer; margin-bottom: 10px; }
153
- .ad-tools .input, .ad-tools select { min-width: 0; max-width: 100%; width: auto; }
154
- .ad-tools .search { min-width: 0; }
155
- .ad-name > .chip { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
156
- .ad-stat b { overflow-wrap: anywhere; }
157
- .ad-task, .ad-model, .ad-when, .ad-act { min-width: 0; }
158
- .ad-main .panel { min-width: 0; }
159
- .ad-main .tbl { overflow-x: auto; max-width: 100%; }
160
- .ad-main #ad-run-error { margin-bottom: 10px; color: var(--muted); font-size: 13px; }
161
- @media (max-width: 760px) {
162
- .ad-ops-grid { grid-template-columns: minmax(0, 1fr); }
163
- .ad-hello h1 { overflow-wrap: anywhere; }
164
- .ad-stat { padding: 10px; gap: 8px; }
165
- .ad-stat .ic-wrap { display: none; }
166
- .ad-tools .search { flex-basis: 100%; }
167
- .ad-tools > select { flex: 1 1 130px; }
168
- .ad-head .ad-actions { width: 100%; }
169
- .ad-run { gap: 8px; }
170
- .ad-run .ad-act { grid-column: 1 / -1; }
171
- }
172
-
173
- @media (min-width: 1101px) and (max-width: 1450px) { .ad-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
174
-
175
- .ad-name > .faint { min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web-admin/admin.js DELETED
@@ -1,406 +0,0 @@
1
- // The admin dashboard, on its own private Space, for FineEnvs members: what's happening (rollouts, people, new
2
- // environments), and the controls (what trends at the top, what's hidden, the collections, indexing, the rollout
3
- // switches). The server checks membership on every call. Links to rollouts, datasets and Spaces open the explorer.
4
- import { $, api, esc, fmt, ago, dur, sk, emptyState, toast, statusPill, rewardBadge, spinner } from "/js/util.js";
5
- import { icon } from "/js/icons.js";
6
- import { getSession } from "/js/session.js";
7
-
8
- const TABS = [["overview", "Overview", "grid"], ["rollouts", "Rollouts", "play"], ["people", "People", "users"], ["environments", "Environments", "database"],
9
- ["collections", "Collections", "layout"], ["indexes", "Indexing & API checks", "archive"], ["settings", "Settings", "gauge"], ["audit", "Audit log", "list"]];
10
- const enc = (s) => s.split("/").map(encodeURIComponent).join("/");
11
- const compact = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 });
12
- const bytes = (n) => (n == null ? "–" : n > 1e6 ? `${(n / 1e6).toFixed(1)} MB` : `${Math.round(n / 1e3)} KB`);
13
- let EXPLORER = ""; // the public explorer: datasets, Spaces and rollouts open there
14
- const envHref = (e) => `${EXPLORER}/${e.kind === "space" ? "s" : "d"}/${enc(e.id)}`;
15
- const runHref = (id) => `${EXPLORER}/run/${encodeURIComponent(id)}`;
16
- const thumb = (id) => `https://cdn-thumbnails.huggingface.co/social-thumbnails/spaces/${enc(id)}.png`;
17
- let alive = true, timer = null, counts = {}, events = null;
18
- const runnerName = v => ({harbor:"Harbor", mimo:"MiMo", "nemo-gym":"NeMo Gym", openenv:"OpenEnv", verifiers:"Verifiers", verl:"verl", skyrl:"SkyRL"}[v] || v || "Unknown");
19
- export function unmount() { alive = false; clearTimeout(timer); events?.abort(); }
20
-
21
- // a person: their Hub picture (or their initial), their name, a link to their profile
22
- const pfp = (name, src, size = 22) => (src ? `<img class="pfp" src="${esc(src)}" alt="" width="${size}" height="${size}" loading="lazy">`
23
- : `<span class="pfp none" style="width:${size}px;height:${size}px">${esc((name || "?").slice(0, 1).toUpperCase())}</span>`);
24
- const person = (name, src, { link = true, filter = false } = {}) => `<span class="person">${pfp(name, src)}${filter
25
- ? `<button class="link" data-user="${esc(name)}" title="Only ${esc(name)}'s rollouts">${esc(name)}</button>`
26
- : link ? `<a href="https://huggingface.co/${encodeURIComponent(name)}" target="_blank" rel="noopener">${esc(name)}</a>` : `<b>${esc(name)}</b>`}</span>`;
27
-
28
- export async function mount(el, { qs, explorer }) {
29
- alive = true;
30
- events?.abort(); events = new AbortController();
31
- EXPLORER = explorer || "";
32
- const s = getSession();
33
- if (!s.user || !s.is_admin) {
34
- el.innerHTML = `<div class="wrap page">${emptyState("shield", "Admins only", s.user ? `The admin dashboard is for members of the ${esc(s.admin_org)} organization on Hugging Face.`
35
- : `Sign in with a ${esc(s.admin_org || "FineEnvs")} account.`, s.user ? "" : `<a class="btn primary" href="/login">${icon("user", 15)}Sign in with Hugging Face</a>`)}</div>`;
36
- return;
37
- }
38
- let tab = TABS.some(([k]) => k === qs.get("tab")) ? qs.get("tab") : "overview";
39
- const preset = { user: qs.get("user") || "" };
40
- el.innerHTML = `<div class="wrap ad-shell fade-in">
41
- <nav class="ad-nav" id="ad-nav" aria-label="Admin sections"></nav>
42
- <section class="ad-main"><div id="ad-body"></div></section></div>`;
43
- const nav = () => {
44
- $("#ad-nav", el).innerHTML = TABS.map(([k, l, ic]) => `<a href="#/${k === "overview" ? "" : `?tab=${k}`}" data-tab="${k}" class="${k === tab ? "on" : ""}">
45
- ${icon(ic, 15)}<span>${l}</span>${counts[k] ? `<em class="${k === "rollouts" ? "live" : ""}">${counts[k]}</em>` : ""}</a>`).join("");
46
- };
47
- const show = () => {
48
- clearTimeout(timer);
49
- nav();
50
- history.replaceState(null, "", `#/${tab === "overview" ? "" : `?tab=${tab}${tab === "rollouts" && preset.user ? `&user=${encodeURIComponent(preset.user)}` : ""}`}`);
51
- $("#ad-body", el).replaceWith(Object.assign(document.createElement("div"), {id:"ad-body"}));
52
- const body = $("#ad-body", el);
53
- body.innerHTML = `<div class="panel"><div class="panel-b">${sk.lines(80, 60, 90, 70)}</div></div>`;
54
- ({ overview, rollouts, people, environments, collections, indexes, settings, audit })[tab](body, { go, preset, s }).catch((e) => {
55
- if (alive && body.isConnected) body.innerHTML = `<div class="note-box err">${icon("alert")}<span>${esc(e.message)}</span></div>`;
56
- });
57
- window.scrollTo(0, 0);
58
- };
59
- const go = (t, opts = {}) => { tab = t; Object.assign(preset, opts); show(); };
60
- el.addEventListener("click", (e) => {
61
- const t = e.target.closest("[data-tab]");
62
- if (t) { e.preventDefault(); preset.user = ""; go(t.dataset.tab); }
63
- const u = e.target.closest("[data-user]");
64
- if (u) { e.preventDefault(); go("rollouts", { user: u.dataset.user }); }
65
- }, {signal:events.signal});
66
- show();
67
- // live counts beside the sections: running rollouts, new environments
68
- api("/api/admin/overview").then((o) => { counts = { rollouts: o.rollouts.live || "", environments: o.new_count || "" }; if (alive) nav(); }).catch(() => {});
69
- }
70
-
71
- const head = (title, sub, actions = "") => `<div class="ad-head"><div><h1>${title}</h1>${sub ? `<p>${sub}</p>` : ""}</div><div class="ad-actions">${actions}</div></div>`;
72
- const stat = (ic, n, label, sub = "", tone = "") => `<div class="ad-stat ${tone}"><span class="ic-wrap">${icon(ic, 16)}</span><div><b>${n}</b><span>${label}</span>${sub ? `<em>${sub}</em>` : ""}</div></div>`;
73
- const runRow = (r, actions = false) => `<div class="ad-run${r.live ? " is-live" : ""}">
74
- <span>${statusPill(r.status)}</span>
75
- <a class="ad-task" href="${runHref(r.id)}" target="_blank" rel="noopener"><b title="${esc(r.title || r.path)}">${esc(r.title || r.path)}</b><em title="${esc(r.dataset)}">${esc(r.dataset)} · ${esc(runnerName(r.runner))}</em><em>${r.community ? "On Community" : r.moderated ? "Moderated" : r.visibility === "public" ? "Public · not eligible for Community" : "Private"}</em></a>
76
- ${person(r.user, r.avatar, { filter: true })}
77
- <span class="ad-model" title="${esc(r.model)}">${esc((r.model || "").split("/")[1] || r.model || "endpoint")}<em>${esc(r.harness || runnerName(r.runner))}</em></span>
78
- <span>${rewardBadge(r.reward, r.status)}</span>
79
- <span class="ad-when">${ago(r.created_at)}<em>${dur(r.wall_s || ((r.finished_at || Date.now() / 1000) - (r.started_at || r.created_at)))}</em></span>
80
- ${actions ? `<span class="ad-act">${r.live ? `<button class="btn sm danger" data-cancel="${esc(r.id)}">${icon("stop", 12)}Stop</button>` : ""}
81
- ${r.moderated ? `<button class="btn sm" data-unhide="${esc(r.id)}">Remove moderation</button>` : r.community ? `<button class="btn sm ghost" data-hide="${esc(r.id)}">Hide from Community</button>` : ""}</span>` : ""}</div>`;
82
-
83
- async function runActions(body, reload) {
84
- body.runEvents?.abort(); body.runEvents = new AbortController();
85
- body.addEventListener("click", async (e) => {
86
- const c = e.target.closest("[data-cancel]"), h = e.target.closest("[data-hide]"), u = e.target.closest("[data-unhide]");
87
- try {
88
- if (c) { if (!confirm("Stop this rollout? The sandbox shuts down and it isn't graded.")) return;
89
- await api(`/api/admin/rollouts/${c.dataset.cancel}/cancel`, { method: "POST" }); toast("Asked the explorer to stop it: a few seconds"); }
90
- else if (h) { if (!confirm("Take this rollout off Community? Its owner still sees it.")) return;
91
- await api(`/api/admin/rollouts/${h.dataset.hide}/moderate`, { method: "POST", body: { hidden: true } }); toast("Hidden from Community"); }
92
- else if (u) { await api(`/api/admin/rollouts/${u.dataset.unhide}/moderate`, { method: "POST", body: { hidden: false } }); toast("Moderation removed; Community eligibility still applies"); }
93
- else return;
94
- reload();
95
- } catch (err) { toast(err.message); }
96
- }, {signal:body.runEvents.signal});
97
- }
98
-
99
- // ── overview ─────────────────────────────────────────────────────────────────
100
- async function overview(body, { go, s }) {
101
- const o = await api("/api/admin/overview");
102
- if (!alive || !body.isConnected) return;
103
- const r = o.rollouts, e = o.environments, on = o.settings.rollouts_enabled;
104
- const peak = Math.max(1, ...r.by_day.map(([, n]) => n));
105
- const live = o.live;
106
- const last = o.recent[0];
107
- body.innerHTML = `
108
- <div class="ad-hello">${pfp(s.user.name, s.user.avatar, 44)}<div><h1>Hi, ${esc(s.user.name)}</h1>
109
- <p><span class="dot" style="--dc:${on ? "var(--ok)" : "var(--warn)"}"></span> Rollouts are ${on ? "on" : "<b>paused</b>"} · ${fmt.format(r.live)} running now${last ? ` · last one ${ago(last.created_at)}` : ""}</p></div>
110
- <div class="ad-actions"><button class="btn ${on ? "" : "primary"}" id="ov-toggle" type="button">${icon(on ? "stop" : "play", 14)}${on ? "Pause rollouts" : "Resume rollouts"}</button>
111
- <button class="btn" type="button" data-go="settings">${icon("message", 14)}Banner</button><button class="btn" type="button" data-go="indexes">${icon("archive", 14)}Indexing & API checks</button></div></div>
112
- <div class="ad-stats">
113
- ${stat("play", fmt.format(r.live), "running now", `${fmt.format(r.today)} in the last 24 hours`, r.live ? "live" : "")}
114
- ${stat("list", fmt.format(r.total), "rollouts in all", `${fmt.format(r.week)} in the last 7 days`)}
115
- ${stat("users", fmt.format(r.public), "on Community", `${fmt.format(r.private)} private · ${fmt.format(r.users)} people`)}
116
- ${stat("alert", r.finished ? `${Math.round((100 * r.failed) / r.finished)}%` : "–", "failed or stopped", `of ${fmt.format(r.finished)} finished`, r.finished && r.failed / r.finished > 0.3 ? "warn" : "")}
117
- ${stat("database", fmt.format(e.public.datasets), "visible datasets", `${fmt.format(e.public.tasks)} indexed task records`)}
118
- ${stat("globe", fmt.format(e.public.spaces), "visible checked Spaces", `${fmt.format(e.spaces)} discovery candidates`)}
119
- </div>
120
- ${operations(o.operations)}
121
- ${live.length ? `<section class="panel ad-live"><div class="panel-h"><h2><i class="pulse" style="color:var(--live)"></i>Running now</h2><a class="aside" href="#" data-go="rollouts">All rollouts</a></div>
122
- <div class="panel-b ad-runs">${live.map((x) => runRow(x, true)).join("")}</div></section>` : ""}
123
- <div class="ad-runner-summary">${r.by_runner.map(([name,n]) => `<span class="chip">${esc(runnerName(name))} · ${fmt.format(n)}</span>`).join("")}</div>
124
- <div class="ad-grid">
125
- <section class="panel"><div class="panel-h"><h2>${icon("play", 15)}Rollouts, last 14 days</h2><span class="aside">${fmt.format(r.week)} in the last 7 days</span></div>
126
- <div class="panel-b"><div class="ad-bars">${r.by_day.map(([d, n]) => `<div title="${esc(d)}: ${n} rollout${n === 1 ? "" : "s"}"><i style="height:${Math.max(2, (100 * n) / peak)}%"></i><span>${d.slice(8)}</span></div>`).join("")}</div>
127
- ${r.by_model.length ? `<div class="sec-label" style="margin-top:16px">Most-used models</div><ul class="ad-models">${r.by_model.map(([m, n]) => `<li><code>${esc(m)}</code>
128
- <span class="share"><i style="width:${(100 * n) / r.by_model[0][1]}%"></i></span><b>${fmt.format(n)}</b></li>`).join("")}</ul>` : ""}</div></section>
129
- <section class="panel"><div class="panel-h"><h2>${icon("flame", 15)}New on the Hub</h2><a class="aside" href="#" data-go="environments">Curate</a></div>
130
- <div class="panel-b">${o.new.length ? `<ul class="ad-new">${o.new.slice(0, 9).map((x) => `<li>
131
- ${x.kind === "space" ? `<img class="ad-th" src="${thumb(x.id)}" alt="" loading="lazy">` : `<span class="ad-th ic-only">${icon("database", 14)}</span>`}
132
- <a href="${envHref(x)}" title="${esc(x.id)}" target="_blank" rel="noopener"><code>${esc(x.id)}</code><em>${x.kind === "space" ? "Space" : "Dataset"} · ${ago(Date.parse(x.created) / 1000)}${x.collection ? ` · in ${esc(x.collection)}` : ""}</em></a></li>`).join("")}</ul>`
133
- : `<p class="muted sm">Nothing new in the last two weeks.</p>`}</div></section>
134
- </div>
135
- <section class="panel" style="margin-top:16px"><div class="panel-h"><h2>${icon("clock", 15)}Latest rollouts</h2><a class="aside" href="#" data-go="rollouts">All rollouts</a></div>
136
- <div class="panel-b ad-runs">${o.recent.length ? o.recent.map((x) => runRow(x)).join("") : emptyState("play", "No rollouts yet", "They show up here as soon as someone runs one.")}</div></section>`;
137
- body.querySelectorAll("[data-go]").forEach((b) => b.addEventListener("click", (ev) => { ev.preventDefault(); go(b.dataset.go); }));
138
- $("#ov-toggle", body).addEventListener("click", async () => {
139
- if (on && !confirm("Pause rollouts for everyone? Running ones finish; new ones are refused until you resume.")) return;
140
- try { await api("/api/admin/settings", { method: "PUT", body: { ...pick(o.settings), rollouts_enabled: !on } }); toast(on ? "Rollouts paused" : "Rollouts resumed"); overview(body, { go, s }); }
141
- catch (err) { toast(err.message); }
142
- });
143
- runActions(body, () => overview(body, { go, s }));
144
- }
145
- const pick = (st) => ({ rollouts_enabled: st.rollouts_enabled, max_active: st.max_active, max_per_user: st.max_per_user, agents: st.agents, announcement: st.announcement });
146
-
147
- // ── rollouts ─────────────────────────────────────────────────────────────────
148
- async function rollouts(body, { preset }) {
149
- const st = { status: "", q: "", user: preset.user || "", runner: "", visibility: "", limit: 100 };
150
- let sequence = 0, pending = null;
151
- body.innerHTML = `${head("Rollouts", "All saved runners, including private runs. Community eligibility requires a completed, graded, unrestricted public rollout.")}
152
- <div class="ad-tools"><div class="search">${icon("search", 16)}<input id="ad-q" type="search" aria-label="Search rollouts" placeholder="Task, environment, model, person or id"></div>
153
- <select class="input" id="ad-runner" aria-label="Runner"><option value="">All runners</option></select>
154
- <select class="input" id="ad-vis" aria-label="Visibility"><option value="">All visibility</option><option value="community">On Community</option><option value="private">Private</option><option value="public">Public records</option><option value="moderated">Moderated</option></select>
155
- <select class="input" id="ad-st" aria-label="Status">${[["", "All statuses"], ["live", "Running"], ["done", "Done"], ["failed", "Failed"], ["cancelled", "Stopped"], ["interrupted", "Interrupted"]].map(([k,l])=>`<option value="${k}">${l}</option>`).join("")}</select>
156
- <button class="btn" id="ad-refresh" type="button">${icon("refresh",14)}Refresh</button><span id="ad-who"></span></div>
157
- <div id="ad-run-error" role="status"></div><div class="panel"><div class="panel-b ad-runs" id="ad-runs">${sk.lines(80, 70, 90)}</div></div>
158
- <div class="more-row"><button class="btn" id="ad-run-more" hidden>Load more</button></div>`;
159
- const load = async () => {
160
- clearTimeout(timer); pending?.abort(); pending = new AbortController(); const request = ++sequence;
161
- try {
162
- const d = await api(`/api/admin/rollouts?${new URLSearchParams(st)}`, {signal:pending.signal});
163
- if (!alive || !body.isConnected || request !== sequence) return;
164
- $("#ad-run-error",body).textContent = "";
165
- $("#ad-runner",body).innerHTML = '<option value="">All runners</option>' + d.runners.map(v=>`<option value="${esc(v)}" ${v===st.runner?'selected':''}>${esc(runnerName(v))}</option>`).join("");
166
- $("#ad-who", body).innerHTML = st.user ? `<button class="pill" id="ad-clear-user" type="button"><span>by</span>${esc(st.user)}${icon("x", 13)}</button>` : "";
167
- $("#ad-runs", body).innerHTML = d.runs.length ? `<p class="muted sm">Showing ${fmt.format(d.runs.length)} of ${fmt.format(d.total)} rollouts</p>${d.runs.map((r) => runRow(r, true)).join("")}` : emptyState("filter", "No rollouts here", "Try another filter.");
168
- $("#ad-run-more",body).hidden = d.runs.length >= d.total || st.limit >= 500;
169
- if (d.total > 500 && st.limit >= 500) $("#ad-run-error",body).textContent = "Showing the latest 500 matches. Refine your filters to find older runs.";
170
- if (d.runs.some(r=>r.live)) timer = setTimeout(()=>body.isConnected && load(), 5000);
171
- } catch(e) { if(e.name!=="AbortError" && body.isConnected) $("#ad-run-error",body).textContent=e.message; }
172
- };
173
- let t;
174
- $("#ad-q", body).addEventListener("input", e=>{clearTimeout(t); pending?.abort(); ++sequence; st.q=e.target.value.trim(); st.limit=100; t=setTimeout(()=>body.isConnected && load(),250);});
175
- for(const [id,key] of [["ad-runner","runner"],["ad-vis","visibility"],["ad-st","status"]]) $("#"+id,body).addEventListener("change",e=>{st[key]=e.target.value;st.limit=100;load();});
176
- $("#ad-refresh",body).addEventListener("click",load);
177
- $("#ad-run-more",body).addEventListener("click",()=>{st.limit+=100;load();});
178
- body.addEventListener("click",e=>{if(e.target.closest("#ad-clear-user")){st.user="";preset.user="";load();}});
179
- runActions(body,load); await load();
180
- }
181
-
182
- // ── people ───────────────────────────────────────────────────────────────────
183
- async function people(body, { go }) {
184
- const d = await api("/api/admin/people");
185
- if (!alive || !body.isConnected) return;
186
- body.innerHTML = `${head("People", "Everyone who has run a rollout, most recent first.")}
187
- ${d.people.length ? `<div class="ad-people">${d.people.map((p) => `<article class="panel ad-person">
188
- <header>${pfp(p.name, p.avatar, 40)}<div><a href="https://huggingface.co/${encodeURIComponent(p.name)}" target="_blank" rel="noopener"><b>${esc(p.name)}</b> ${icon("external", 12)}</a>
189
- <em>active ${ago(p.last)}${p.live ? ` · <span class="live-txt">${p.live} running</span>` : ""}</em></div></header>
190
- <div class="ad-pstats"><div><b>${fmt.format(p.runs)}</b><span>rollouts</span></div><div><b>${fmt.format(p.scored)}</b><span>graded</span></div>
191
- <div><b>${p.runs ? `${Math.round((100 * p.done) / p.runs)}%` : "–"}</b><span>finished</span></div><div><b>${fmt.format(p.public)}</b><span>on Community</span></div></div>
192
- <dl class="kv sm"><dt>Models</dt><dd>${p.models.map(([m, n]) => `<code>${esc(m.split("/")[1] || m)}</code> <span class="faint">${n}</span>`).join(", ")}</dd>
193
- <dt>Datasets</dt><dd>${p.datasets.map(([x, n]) => `${esc(x)} <span class="faint">${n}</span>`).join(", ")}</dd>
194
- <dt>Since</dt><dd>${new Date(p.first * 1000).toLocaleDateString()}</dd></dl>
195
- <button class="btn sm" type="button" data-user="${esc(p.name)}">${icon("play", 12)}Their rollouts</button></article>`).join("")}</div>`
196
- : `<div class="panel">${emptyState("users", "Nobody yet", "People show up here after their first rollout.")}</div>`}`;
197
- }
198
-
199
- // ── environments: what trends, what's hidden, what's featured ────────────────
200
- async function environments(body) {
201
- const d = await api("/api/admin/environments");
202
- if (!alive || !body.isConnected) return;
203
- const st = { q: "", kind: "", flag: "", health: "", shown: 60 };
204
- const cols = d.collections;
205
- const now = Date.now();
206
- const isNew = (x) => x.created && now - Date.parse(x.created) < 14 * 864e5;
207
- const n = (f) => d.environments.filter(f).length;
208
- body.innerHTML = `${head("Environments", "Pin to feature separately above the trending row on Explore; hide to take off every listing (the page still opens by link); put in a collection to feature it.")}
209
- <div class="ad-tools"><div class="search">${icon("search", 16)}<input id="ad-q" type="search" aria-label="Search environments" placeholder="Search environments…"></div>
210
- <div class="seg" id="ad-flag">${[["", "All", d.environments.length], ["new", "New", n(isNew)], ["pinned", "Pinned", n((x) => x.pinned)], ["hidden", "Hidden", n((x) => x.hidden)],
211
- ["loose", "In no collection", n((x) => !x.collection)], ["unindexed", "No file index", n((x) => x.kind === "dataset" && !x.indexed)]]
212
- .map(([k, l, c]) => `<button type="button" data-flag="${k}" aria-pressed="${k === ""}">${l}<span>${fmt.format(c)}</span></button>`).join("")}</div>
213
- <select class="hf-select-mimo" id="ad-kind" aria-label="Environment type"><option value="">Datasets and Spaces</option><option value="dataset">Datasets</option><option value="space">Spaces</option></select><select class="input" id="ad-health" aria-label="API evidence"><option value="">All API evidence</option><option value="ready">Checked Spaces</option><option value="candidate">Unverified / unavailable Spaces</option><option value="Check expired">Expired checks</option><option value="Checks failed">OpenEnv checks failed</option><option value="Not running">Not running</option></select></div><p class="fine">Discovery candidates are kept here for review. Only Spaces with fresh supported API evidence appear in the default explorer. Versions are declared dependencies, not runtime attestations.</p>
214
- <div class="panel"><div class="ad-envs" id="ad-envs"></div></div>
215
- <div class="more-row"><button class="btn" id="ad-more" type="button">Show more</button></div>`;
216
- const draw = () => {
217
- const q = st.q.toLowerCase();
218
- const rows = d.environments.filter((x) => (!st.kind || x.kind === st.kind) && (!q || `${x.id} ${x.heading || ""} ${x.tags.join(" ")}`.toLowerCase().includes(q))
219
- && (!st.health || (x.kind === "space" && (st.health === "ready" ? x.ready : st.health === "candidate" ? !x.ready : x.api_status === st.health)))
220
- && (!st.flag || (st.flag === "new" ? isNew(x) : st.flag === "pinned" ? x.pinned : st.flag === "hidden" ? x.hidden
221
- : st.flag === "loose" ? !x.collection : x.kind === "dataset" && !x.indexed)))
222
- .sort((a, b) => (b.pinned - a.pinned) || (b.trending - a.trending) || (b.likes - a.likes) || (b.downloads - a.downloads));
223
- $("#ad-envs", body).innerHTML = rows.length ? `<div class="ad-env head"><span>Environment</span><span>Collection</span><span class="num">Trending</span><span class="num">Downloads</span>
224
- <span class="num">Tasks</span><span class="num">Rollouts</span><span></span></div>` + rows.slice(0, st.shown).map((x) => `<div class="ad-env${x.hidden ? " gone" : ""}">
225
- <span class="ad-name">${x.kind === "space" ? `<img class="ad-th" src="${thumb(x.id)}" alt="" loading="lazy">` : `<span class="ad-th ic-only">${icon("database", 14)}</span>`}
226
- <a href="${envHref(x)}" title="${esc(x.id)}" target="_blank" rel="noopener"><code>${esc(x.id)}</code></a>
227
- ${x.kind === "space" ? `<span class="chip ${x.ready ? "new" : ""}" title="${esc(x.api_status || "Not checked")}">${esc(x.ready ? (x.openenv ? "OpenEnv API checked" : x.framework + " API checked") : x.api_status || "Not checked")}</span><span class="faint xs" title="${esc((x.declared_version || "Unknown version") + " · " + (x.version_source || "Unknown source"))}">${esc(x.declared_version || "Unknown version")}</span>` : `<span class="chip">${esc(x.id === "XiaomiMiMo/MiMo-V2.6-RL-oss" ? "MiMo" : x.framework === "rows" ? "RL dataset" : runnerName(x.framework))}</span>`}
228
- ${x.pinned ? `<span class="chip">${icon("flag", 11)}Pinned</span>` : ""}${x.hidden ? `<span class="chip">Hidden</span>` : ""}${isNew(x) ? `<span class="chip new">New</span>` : ""}</span>
229
- <span><select class="hf-select-mimo sm" data-feature="${esc(x.key)}" aria-label="Collection"><option value="">No collection</option>${cols.map((c) => `<option value="${c.id}" ${x.collection === c.id ? "selected" : ""}>${esc(c.group)}</option>`).join("")}</select></span>
230
- <span class="num">${fmt.format(x.trending || 0)}</span><span class="num">${x.kind === "dataset" ? compact.format(x.downloads) : `${compact.format(x.likes)} ♥`}</span>
231
- <span class="num">${x.indexed?.tasks != null ? fmt.format(x.indexed.tasks) : x.kind === "dataset" && x.framework === "harbor" ? `<button class="link" data-index="${esc(x.key)}">Index</button>` : "–"}</span>
232
- <span class="num">${fmt.format(x.rollouts || 0)}</span>
233
- <span class="ad-act"><button class="btn sm ${x.pinned ? "" : "ghost"}" data-act="${x.pinned ? "unpin" : "pin"}" data-key="${esc(x.key)}" title="${x.pinned ? "Unpin" : "Feature above trending"}">${icon("flag", 12)}${x.pinned ? "Unpin" : "Pin"}</button>
234
- <button class="btn sm ${x.hidden ? "" : "ghost"}" data-act="${x.hidden ? "unhide" : "hide"}" data-key="${esc(x.key)}">${x.hidden ? "Unhide" : "Hide"}</button></span></div>`).join("")
235
- : emptyState("search", "Nothing here", "Try another filter.");
236
- $("#ad-more", body).hidden = rows.length <= st.shown;
237
- };
238
- const act = async (key, action, extra = {}) => {
239
- if (action === "hide" && !confirm(`Hide ${key.replace(/^space:/, "")} from every listing?`)) return draw();
240
- try {
241
- await api("/api/admin/environments", { method: "POST", body: { key, action, ...extra } });
242
- const x = d.environments.find((y) => y.key === key);
243
- if (x) {
244
- if (action === "pin" || action === "unpin") x.pinned = action === "pin";
245
- if (action === "hide" || action === "unhide") x.hidden = action === "hide";
246
- if (action === "feature" || action === "unfeature") x.collection = extra.collection || null;
247
- }
248
- toast({ pin: "Pinned: shown as an editorial pick", unpin: "Unpinned", hide: "Hidden from listings", unhide: "Back in listings", feature: "Added to the collection",
249
- unfeature: "Taken out of its collection", index: "Indexing started" }[action]);
250
- draw();
251
- } catch (e) { toast(e.message); }
252
- };
253
- let t;
254
- $("#ad-q", body).addEventListener("input", (e) => { clearTimeout(t); t = setTimeout(() => { st.q = e.target.value.trim(); st.shown = 60; draw(); }, 150); });
255
- $("#ad-health",body).addEventListener("change",e=>{st.health=e.target.value;st.shown=60;draw();});
256
- $("#ad-kind", body).addEventListener("change", (e) => { st.kind = e.target.value; st.shown=60; draw(); });
257
- $("#ad-flag", body).addEventListener("click", (e) => { const b = e.target.closest("[data-flag]"); if (!b) return; st.flag = b.dataset.flag;
258
- body.querySelectorAll("#ad-flag [data-flag]").forEach((x) => x.setAttribute("aria-pressed", String(x === b))); draw(); });
259
- $("#ad-more", body).addEventListener("click", () => { st.shown += 100; draw(); });
260
- body.addEventListener("click", (e) => {
261
- const b = e.target.closest("[data-act]");
262
- if (b) act(b.dataset.key, b.dataset.act);
263
- const i = e.target.closest("[data-index]");
264
- if (i) { i.outerHTML = spinner(); act(i.dataset.index, "index"); }
265
- });
266
- body.addEventListener("change", (e) => {
267
- const s = e.target.closest("[data-feature]");
268
- if (s) act(s.dataset.feature, s.value ? "feature" : "unfeature", s.value ? { collection: s.value } : {});
269
- });
270
- draw();
271
- }
272
-
273
- // ── collections ──────────────────────────────────────────────────────────────
274
- async function collections(body) {
275
- const d = await api("/api/admin/collections");
276
- if (!alive || !body.isConnected) return;
277
- let cols = d.collections.map((c) => ({ ...c, ids: [...c.ids] }));
278
- let dirty = false;
279
- const draw = () => {
280
- body.innerHTML = `${head("Collections", "The tiles on Explore, in this order. One environment per line: a dataset id, or <code>space:org/name</code> for a Space.",
281
- `<button class="btn" id="ad-add" type="button">${icon("plus", 14)}Add a collection</button><button class="btn primary" id="ad-save" type="button" ${dirty ? "" : "disabled"}>${icon("check", 14)}Save</button>`)}
282
- <div class="ad-cols">${cols.map((c, i) => `<section class="panel" data-i="${i}"><div class="panel-h"><h2><span class="ad-swatch" style="--dc:var(--c-${esc(c.color)})">${icon(c.icon, 14)}</span>${esc(c.group || "New collection")}
283
- <span class="faint xs">${c.ids.length} environment${c.ids.length === 1 ? "" : "s"}</span></h2>
284
- <span class="aside"><button class="icon-btn" data-up="${i}" ${i ? "" : "disabled"} aria-label="Move up" title="Move up">${icon("chevronDown", 14, "up")}</button>
285
- <button class="icon-btn" data-down="${i}" ${i < cols.length - 1 ? "" : "disabled"} aria-label="Move down" title="Move down">${icon("chevronDown", 14)}</button>
286
- <button class="icon-btn" data-del="${i}" aria-label="Delete" title="Delete">${icon("x", 14)}</button></span></div>
287
- <div class="panel-b ad-form">
288
- <label class="field"><span>Name</span><input class="input" data-f="group" value="${esc(c.group)}" maxlength="40"></label>
289
- <label class="field"><span>Id <em>lowercase, for links</em></span><input class="input mono" data-f="id" value="${esc(c.id)}" maxlength="31"></label>
290
- <label class="field wide"><span>About</span><input class="input" data-f="about" value="${esc(c.about || "")}" maxlength="80"></label>
291
- <label class="field"><span>Icon</span><select class="hf-select-mimo" data-f="icon">${d.icons.map((x) => `<option ${x === c.icon ? "selected" : ""}>${x}</option>`).join("")}</select></label>
292
- <label class="field"><span>Colour</span><select class="hf-select-mimo" data-f="color">${d.colors.map((x) => `<option ${x === c.color ? "selected" : ""}>${x}</option>`).join("")}</select></label>
293
- <label class="field wide"><span>Environments</span><textarea class="input mono" data-f="ids" rows="${Math.min(10, c.ids.length + 2)}">${esc(c.ids.join("\n"))}</textarea></label>
294
- </div></section>`).join("")}</div>`;
295
- };
296
- const read = () => body.querySelectorAll("[data-i]").forEach((sec) => {
297
- const c = cols[+sec.dataset.i];
298
- sec.querySelectorAll("[data-f]").forEach((f) => { c[f.dataset.f] = f.dataset.f === "ids" ? f.value.split("\n").map((x) => x.trim()).filter(Boolean) : f.value.trim(); });
299
- });
300
- const touch = () => { dirty = true; const b = $("#ad-save", body); if (b) b.disabled = false; };
301
- body.addEventListener("input", touch);
302
- body.addEventListener("change", touch);
303
- body.addEventListener("click", async (e) => {
304
- const up = e.target.closest("[data-up]"), down = e.target.closest("[data-down]"), del = e.target.closest("[data-del]");
305
- if (up || down || del) { read(); dirty = true; }
306
- if (up) { const i = +up.dataset.up; [cols[i - 1], cols[i]] = [cols[i], cols[i - 1]]; draw(); }
307
- else if (down) { const i = +down.dataset.down; [cols[i + 1], cols[i]] = [cols[i], cols[i + 1]]; draw(); }
308
- else if (del) { if (confirm(`Delete the collection “${cols[+del.dataset.del].group}”? Its environments stay on the Hub.`)) { cols.splice(+del.dataset.del, 1); draw(); } }
309
- else if (e.target.closest("#ad-add")) { read(); dirty = true; cols.push({ id: `new-${cols.length + 1}`, group: "New collection", about: "", icon: "grid", color: "code", ids: [] }); draw(); }
310
- else if (e.target.closest("#ad-save")) {
311
- read();
312
- try { cols = (await api("/api/admin/collections", { method: "PUT", body: { collections: cols } })).collections.map((c) => ({ ...c, ids: [...c.ids] })); dirty = false; draw(); toast("Saved: Explore shows them within seconds"); }
313
- catch (err) { toast(err.message); }
314
- }
315
- });
316
- draw();
317
- }
318
-
319
- function operations(o) {
320
- const job=o.indexer, snap=o.snapshot, c=o.checks, tasks=o.task_catalog;
321
- const state=job.stale ? "Update overdue" : job.state === "indexing" ? "Indexing" : job.state === "retrying" ? "Retry scheduled" : job.state ? "Waiting for next cycle" : "Waiting for first report";
322
- const when=v=>v ? ago(v) : "Not recorded";
323
- return `<section class="panel ad-operations"><div class="panel-h"><h2>${icon("refresh",15)}Catalog operations</h2><span class="aside">Shared explorer state</span></div><div class="panel-b ad-ops-grid">
324
- <div><b>Automatic indexing</b><p class="${job.stale || job.error ? 'err-text' : 'muted'}">${esc(state)}</p><dl class="kv sm"><dt>Last success</dt><dd>${when(job.last_success)}</dd><dt>Next attempt</dt><dd>${job.next_at ? esc(new Date(job.next_at*1000).toLocaleString()) : "Not recorded"}</dd><dt>Snapshot</dt><dd>${snap.built_at ? esc(new Date(snap.built_at).toLocaleString()) : "No snapshot yet"}</dd></dl>${job.error || snap.error ? `<p class="err-text">${esc(job.error || snap.error)}</p>` : ''}</div>
325
- <div><b>Space API checks</b><p>${fmt.format(c.ready)} checked · ${fmt.format(c.openenv)} OpenEnv · ${fmt.format(c.candidates)} candidates</p><dl class="kv sm"><dt>Latest check</dt><dd>${when(c.latest_at)}</dd><dt>Freshness</dt><dd>${c.fresh_seconds/60} minutes</dd><dt>Reported tasks</dt><dd>${fmt.format(tasks.tasks)} in ${fmt.format(tasks.spaces)} Spaces${tasks.partial_spaces ? ` · ${tasks.partial_spaces} partial catalogs` : ''}</dd></dl></div>
326
- <div class="ad-check-states">${Object.entries(c.statuses).map(([v,n])=>`<span class="chip">${esc(v)} · ${fmt.format(n)}</span>`).join('')}</div>
327
- <p class="fine ad-ops-note">Task entries reported by Space APIs are separate from downloaded, searchable task records. Checks do not wake sleeping Spaces or certify grading quality. The explorer runs maintenance automatically while it is online.</p>
328
- ${c.versions.length ? `<details class="ad-ops-note"><summary>Declared OpenEnv versions (${c.versions.length})</summary><div class="ad-check-states">${c.versions.map(([v,n])=>`<span class="chip">${esc(v)} · ${fmt.format(n)}</span>`).join('')}</div></details>` : ''}
329
- </div></section>`;
330
- }
331
-
332
- // ── indexes ──────────────────────────────────────────────────────────────────
333
- async function indexes(body) {
334
- const d = await api("/api/admin/indexes");
335
- if (!alive || !body.isConnected) return;
336
- body.indexEvents?.abort(); body.indexEvents = new AbortController();
337
- const jobs = Object.entries(d.jobs);
338
- body.innerHTML = `${head("Indexing & API checks", "Automatic maintenance, fresh protocol evidence and dataset indexes across frameworks.", '<button class="btn" id="ad-index-refresh">Refresh status</button>')}
339
- ${operations(d.operations)}
340
- <div class="ad-tools"><div class="search">${icon("database", 16)}<input id="ad-spec" placeholder="A public Harbor dataset: org/name"></div>
341
- <button class="btn primary" id="ad-go" type="button">${icon("play", 14)}Index it</button></div>
342
- ${jobs.length ? `<section class="panel" style="margin-bottom:12px"><div class="panel-h"><h2>${spinner()}Indexing now</h2></div><div class="panel-b"><dl class="kv">${jobs.map(([k, j]) =>
343
- `<dt><code>${esc(k)}</code></dt><dd>${esc(j.state)}${j.total ? ` · ${fmt.format(j.done || 0)} of ${fmt.format(j.total)}` : ""}${j.error ? ` · <span class="err-text">${esc(j.error)}</span>` : ""}</dd>`).join("")}</dl></div></section>` : ""}
344
- <div class="panel"><div class="tbl" style="max-height:none;border:0"><table><thead><tr><th>Dataset</th><th class="num">Tasks</th><th>Built</th><th class="num">Index</th><th class="num">Pack</th><th></th></tr></thead><tbody>
345
- ${d.indexes.map((x) => `<tr><td><a class="u" href="${EXPLORER}/d/${enc(x.spec)}" target="_blank" rel="noopener"><code>${esc(x.spec)}</code></a></td><td class="num">${x.tasks != null ? fmt.format(x.tasks) : "–"}</td>
346
- <td>${x.built ? ago(x.built) : "–"}${x.current ? "" : ` <span class="chip">old format</span>`}</td><td class="num">${bytes(x.bytes)}</td><td class="num">${bytes(x.pack_bytes)}</td>
347
- <td><button class="btn sm ghost" data-reindex="${esc(x.spec)}">${icon("refresh", 12)}Rebuild</button></td></tr>`).join("")}</tbody></table></div></div>`;
348
- const go = async (spec) => {
349
- try { const result = await api("/api/admin/environments", { method: "POST", body: { key: spec, action: "index" } }); toast(result.job?.state === "done" ? "Index is current" : result.job?.state === "error" ? result.job.error || "Indexing failed" : `Indexing ${spec}`); timer = setTimeout(() => alive && body.isConnected && indexes(body), 1500); }
350
- catch (e) { toast(e.message); }
351
- };
352
- $("#ad-index-refresh",body).addEventListener("click",()=>indexes(body));
353
- $("#ad-go", body).addEventListener("click", () => { const v = $("#ad-spec", body).value.trim(); if (v) go(v); });
354
- $("#ad-spec", body).addEventListener("keydown", (e) => { if (e.key === "Enter") $("#ad-go", body).click(); });
355
- body.addEventListener("click", (e) => { const b = e.target.closest("[data-reindex]"); if (b) go(b.dataset.reindex); }, {signal:body.indexEvents.signal});
356
- if (jobs.length) timer = setTimeout(() => alive && body.isConnected && indexes(body), 3000);
357
- }
358
-
359
- // ── settings ─────────────────────────────────────────────────────────────────
360
- async function settings(body) {
361
- const s = await api("/api/admin/settings");
362
- if (!alive || !body.isConnected) return;
363
- body.innerHTML = `${head("Settings", "They apply to the explorer within seconds.")}
364
- <section class="panel"><div class="panel-b ad-settings">
365
- <div class="ad-set"><div><b>Rollouts</b><span>Off pauses new rollouts for everyone; running ones finish.</span></div>
366
- <label class="toggle"><input type="checkbox" id="st-on" aria-label="Enable rollouts" ${s.rollouts_enabled ? "checked" : ""}><i></i></label></div>
367
- <div class="ad-set"><div><b>Rollouts at once</b><span>The whole explorer, then each person.</span></div>
368
- <div class="ad-nums"><input class="input" type="number" id="st-max" min="1" max="200" value="${s.max_active}" aria-label="The whole explorer"><span class="faint">/</span>
369
- <input class="input" type="number" id="st-user" min="1" max="20" value="${s.max_per_user}" aria-label="Each person"></div></div>
370
- <div class="ad-set"><div><b>Harbor agent harnesses</b><span>Controls Harbor runs. MiMo and NeMo Gym use their own supported execution paths.</span></div>
371
- <div class="ad-checks">${s.all_agents.map((a) => `<label><input type="checkbox" data-agent="${a.id}" ${s.agents.includes(a.id) ? "checked" : ""}> ${esc(a.name)}</label>`).join("")}</div></div>
372
- <div class="ad-set col"><div><b>Announcement</b><span>One line above every page of the explorer; empty for none.</span></div>
373
- <input class="input" id="st-ann" maxlength="280" value="${esc(s.announcement)}" placeholder="e.g. Rollouts are slow today: a provider is overloaded.">
374
- <div class="announce ad-preview" id="st-prev" ${s.announcement ? "" : "hidden"}><div class="wrap">${icon("info", 14)}<span>${esc(s.announcement)}</span></div></div></div>
375
- <div class="ad-save-row"><span class="faint sm" id="st-dirty"></span><button class="btn primary" id="st-save" type="button">${icon("check", 14)}Save settings</button></div>
376
- </div></section>`;
377
- const mark = () => { $("#st-dirty", body).textContent = "Unsaved changes"; };
378
- body.addEventListener("input", (e) => {
379
- mark();
380
- if (e.target.id === "st-ann") { $("#st-prev", body).hidden = !e.target.value.trim(); $("#st-prev span", body).textContent = e.target.value; }
381
- });
382
- body.addEventListener("change", mark);
383
- $("#st-save", body).addEventListener("click", async () => {
384
- const on = $("#st-on", body).checked;
385
- if (!on && s.rollouts_enabled && !confirm("Pause rollouts for everyone?")) return;
386
- try {
387
- const r = await api("/api/admin/settings", { method: "PUT", body: { rollouts_enabled: on, max_active: +$("#st-max", body).value,
388
- max_per_user: +$("#st-user", body).value, agents: [...body.querySelectorAll("[data-agent]:checked")].map((x) => x.dataset.agent), announcement: $("#st-ann", body).value.trim() } });
389
- Object.assign(s, r);
390
- $("#st-dirty", body).textContent = "";
391
- toast("Saved");
392
- } catch (e) { toast(e.message); }
393
- });
394
- }
395
-
396
- // ── audit ────────────────────────────────────────────────────────────────────
397
- async function audit(body) {
398
- const [d, ppl] = await Promise.all([api("/api/admin/audit"), api("/api/admin/people").catch(() => ({ people: [] }))]);
399
- if (!alive || !body.isConnected) return;
400
- const pics = Object.fromEntries(ppl.people.map((p) => [p.name, p.avatar]));
401
- const me = getSession().user;
402
- if (me) pics[me.name] = pics[me.name] || me.avatar;
403
- body.innerHTML = `${head("Audit log", "Every change made here, newest first.")}<div class="panel"><div class="panel-b">${d.entries.length ? `<ol class="ad-audit">${d.entries.map((x) => `<li>
404
- ${person(x.user, pics[x.user])}<span>${esc(x.note || Object.keys(x.after || {}).join(", ") || "changed settings")}</span><span class="faint" title="${new Date(x.at * 1000).toLocaleString()}">${ago(x.at)}</span></li>`).join("")}</ol>`
405
- : emptyState("list", "Nothing yet", "Changes made here show up in this log.")}</div></div>`;
406
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web-admin/index.html DELETED
@@ -1,41 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
- <title>HF RL Explorer admin</title>
7
- <meta name="robots" content="noindex">
8
- <link rel="icon" href="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" type="image/svg+xml">
9
- <link rel="preconnect" href="https://fonts.googleapis.com">
10
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
12
- <link rel="stylesheet" href="/app.css">
13
- <link rel="stylesheet" href="/rlx.css">
14
- <link rel="stylesheet" href="/admin/admin.css">
15
- <script src="/js/boot.js"></script>
16
- </head>
17
- <body>
18
- <div class="progress" id="progress" aria-hidden="true"><i></i></div>
19
- <header class="top">
20
- <div class="wrap top-inner">
21
- <a class="brand" href="#/" aria-label="HF RL Explorer admin, home">
22
- <img class="logo" src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="" width="30" height="30">
23
- <span class="name">HF RL Explorer <span>Admin</span></span>
24
- </a>
25
- <nav class="nav" aria-label="Main"><a href="#/" class="on">Dashboard</a><a id="to-explorer" href="/" target="_blank" rel="noopener">Explorer ↗</a></nav>
26
- <div class="top-right">
27
- <button class="icon-btn" id="theme" type="button" aria-label="Toggle dark mode" title="Toggle dark mode"></button>
28
- <div id="account"></div>
29
- </div>
30
- </div>
31
- </header>
32
- <main id="view"><div class="boot" aria-label="Loading"><span class="spinner lg"></span></div></main>
33
- <div class="scrim" id="scrim" hidden></div>
34
- <div class="modal" id="modal" role="dialog" aria-modal="true" hidden><div class="modal-bar"><span id="modal-icon"></span><span id="modal-title"></span><span class="grow"></span>
35
- <a class="btn sm" id="modal-open" target="_blank" rel="noopener" hidden></a><button class="icon-btn" id="modal-close" type="button" aria-label="Close"></button></div><div class="modal-body" id="modal-body"></div></div>
36
- <div id="dialog-root"></div>
37
- <div class="toast" id="toast" role="status" hidden></div>
38
- <footer class="foot"><div class="wrap"><span>HF RL Explorer admin · private to the FineEnvs organization</span><span class="grow"></span><span id="foot-version"></span></div></footer>
39
- <script type="module" src="/admin/main.js"></script>
40
- </body>
41
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web-admin/main.js DELETED
@@ -1,28 +0,0 @@
1
- // The admin Space's shell: who you are, the theme, and the dashboard (web-admin/admin.js).
2
- import { $, api, esc, storage } from "/js/util.js";
3
- import { icon } from "/js/icons.js";
4
- import { getSession, setSession } from "/js/session.js";
5
- import * as dash from "/admin/admin.js";
6
-
7
- async function boot() {
8
- try { setSession(await api("/api/me")); } catch { setSession({ user: null }); }
9
- const s = getSession();
10
- $("#to-explorer").href = s.explorer || "/";
11
- if (s.version) $("#foot-version").innerHTML = `v${esc(s.version)} · <code>${esc(s.source || "")}</code>`;
12
- $("#account").innerHTML = s.user ? `<span class="acct-btn" title="${esc(s.user.name)}">${s.user.avatar ? `<img src="${esc(s.user.avatar)}" alt="">` : ""}<span class="name">${esc(s.user.name)}</span></span>
13
- ${s.local ? "" : `<button class="btn sm ghost" id="signout" type="button">${icon("logout", 13)}Sign out</button>`}` : "";
14
- $("#signout")?.addEventListener("click", async () => { await api("/api/logout", { method: "POST" }).catch(() => {}); location.reload(); });
15
- const route = () => {
16
- dash.unmount();
17
- const qs = new URLSearchParams((location.hash.split("?")[1]) || "");
18
- dash.mount($("#view"), { qs, explorer: s.explorer });
19
- };
20
- addEventListener("hashchange", route);
21
- route();
22
- }
23
-
24
- const isDark = () => (document.documentElement.dataset.theme ? document.documentElement.dataset.theme === "dark" : matchMedia("(prefers-color-scheme: dark)").matches);
25
- const paint = () => { $("#theme").innerHTML = icon(isDark() ? "sun" : "moon", 17); };
26
- paint();
27
- $("#theme").addEventListener("click", () => { document.documentElement.dataset.theme = isDark() ? "light" : "dark"; storage.set("theme", document.documentElement.dataset.theme); paint(); });
28
- boot();