erikjm commited on
Commit
8514b99
·
verified ·
1 Parent(s): 3b8ff64

Deploy the results API

Browse files
Files changed (4) hide show
  1. README.md +7 -4
  2. app.py +97 -17
  3. tests/conftest.py +34 -1
  4. tests/test_app.py +79 -7
README.md CHANGED
@@ -16,18 +16,21 @@ The results API behind the leaderboard pop-up of the Steerability Challenge site
16
 
17
  The index of runs is `leaderboard/runs.json` in the archive, which `challenge-leaderboard` rebuilds on every render: `{"schema", "latest", "runs": [{"name", "label", "layer", "date", "deadline", "n_ranked", "n_baselines"}, ...]}`. A run's site board is `leaderboard/<run>/site.json`, `{"schema", "run", "rows"}` with the same `run` block and one row per ranked submission and baseline.
18
 
 
 
19
  | Request | Response |
20
  |---|---|
21
  | `GET /healthz` | `200 {"run", "date"}` of the index's latest run, both null before one is published. The pop-up also pings it to wake the Space. |
22
- | `GET /runs` | `200` with the index verbatim, or `503 {"error": "no_board"}` without one. No authentication. |
23
  | `GET /board/<run>` | `200` with the run's site board verbatim, or `404 {"error": "not_found"}` for a run that is not a run name, is not in the index, or has no site board. No authentication. |
24
  | `GET /me[?run=<run>]` with `Authorization: Bearer <token>` | `200` with `{"role": "participant", "run", "teams", "missing"}` or `{"role": "organizer", "run", "board", "teams", "pull", "progress", "failures"}` for the run, by default the index's latest; `run` is the site board's `run` block. |
 
25
  | | `401 {"error": "invalid_token"}` when Hugging Face does not accept the token (checked before the run). |
26
- | | `404 {"error": "not_found"}` for a `run` that is not a run name or is not in the index. |
27
  | | `503 {"error": "no_board"}` before a board is published. |
28
  | any of the above | `503 {"error": "archive_unavailable"}` when the archive cannot be read. |
29
 
30
- HTTPS only, no cookies, `GET` and the `Authorization` header only. CORS allows `ALLOWED_ORIGINS` and, when set, `ALLOWED_ORIGIN_REGEX`. The hf.space proxy in front of the app answers CORS requests from any origin by design, and browsers do not enforce this list there. Access rests on the bearer token, which another site cannot obtain, and the app refuses no request by its `Origin`. Logs give the viewer's handle, role, run, and status code, never a token or a file's contents.
31
 
32
  ## Settings at deploy time
33
 
@@ -47,4 +50,4 @@ Optional: `USERINFO_URL` (default `https://huggingface.co/oauth/userinfo`) and `
47
 
48
  ## Tests
49
 
50
- From the repository that contains this Space: `python -m pytest spaces/leaderboard-api/tests`. They run against a stub userinfo server on a loopback port and a fixture archive directory with two runs.
 
16
 
17
  The index of runs is `leaderboard/runs.json` in the archive, which `challenge-leaderboard` rebuilds on every render: `{"schema", "latest", "runs": [{"name", "label", "layer", "date", "deadline", "n_ranked", "n_baselines"}, ...]}`. A run's site board is `leaderboard/<run>/site.json`, `{"schema", "run", "rows"}` with the same `run` block and one row per ranked submission and baseline.
18
 
19
+ The pending run is the run being evaluated between its intake and the publication of its board: the `run` block `{"name", "label", "layer", "pull_finished", "n_pulled", "models"}` of `leaderboard/pending.json` (written by `challenge-leaderboard --pending`, cleared to null by the render that publishes the board), unless the index lists that run. It has no site board. Each organization of its pull manifest has a pending file `leaderboard/<run>/pending/<org>.json`, `{"schema", "run", "org", "team", "entry"}` with the entry block of a team file, and the API adds `evaluation`, the status of the organization's submission per base model (`not_started`, `in_progress`, `completed`, or `error`) read from the run's `status.json` on every request. Nothing else of the status file reaches a viewer.
20
+
21
  | Request | Response |
22
  |---|---|
23
  | `GET /healthz` | `200 {"run", "date"}` of the index's latest run, both null before one is published. The pop-up also pings it to wake the Space. |
24
+ | `GET /runs` | `200` with the index verbatim plus `"pending": <the pending run's block or null>`, or `503 {"error": "no_board"}` without an index. No authentication. |
25
  | `GET /board/<run>` | `200` with the run's site board verbatim, or `404 {"error": "not_found"}` for a run that is not a run name, is not in the index, or has no site board. No authentication. |
26
  | `GET /me[?run=<run>]` with `Authorization: Bearer <token>` | `200` with `{"role": "participant", "run", "teams", "missing"}` or `{"role": "organizer", "run", "board", "teams", "pull", "progress", "failures"}` for the run, by default the index's latest; `run` is the site board's `run` block. |
27
+ | `GET /me?run=<pending run>` | `200` with `{"role": "participant", "pending": true, "run", "teams", "missing"}`, `teams` being the viewer's organizations' pending files each with `evaluation`, or `{"role": "organizer", "pending": true, "run", "board": null, "teams", "pull", "progress", "failures"}` with every pending file. The published view has no `pending` key. |
28
  | | `401 {"error": "invalid_token"}` when Hugging Face does not accept the token (checked before the run). |
29
+ | | `404 {"error": "not_found"}` for a `run` that is not a run name, or is neither in the index nor pending. |
30
  | | `503 {"error": "no_board"}` before a board is published. |
31
  | any of the above | `503 {"error": "archive_unavailable"}` when the archive cannot be read. |
32
 
33
+ HTTPS only, no cookies, `GET` and the `Authorization` header only. CORS allows `ALLOWED_ORIGINS` and, when set, `ALLOWED_ORIGIN_REGEX`. The hf.space proxy in front of the app answers CORS requests from any origin by design, and browsers do not enforce this list there. Access rests on the bearer token, which another site cannot obtain, and the app refuses no request by its `Origin`. Logs give the viewer's handle, role, run, whether it is pending, and status code, never a token or a file's contents.
34
 
35
  ## Settings at deploy time
36
 
 
50
 
51
  ## Tests
52
 
53
+ From the repository that contains this Space: `python -m pytest spaces/leaderboard-api/tests`. They run against a stub userinfo server on a loopback port and a fixture archive directory with two published runs and a pending one.
app.py CHANGED
@@ -1,12 +1,15 @@
1
  """The results API behind the competition site's leaderboard pop-up, served from a public Hugging Face Space.
2
 
3
  The index of runs is `leaderboard/runs.json` in the archive (`RUNS`), which `challenge-leaderboard` rebuilds on every
4
- render. `GET /healthz` gives the latest run and its date (both null without an index), `GET /runs` gives the index,
 
 
5
  and `GET /board/<run>` gives a run's site board (`leaderboard/<run>/site.json`), each without authentication.
6
  `GET /me` gives a signed-in viewer the files they may see for one run (`?run=<run>`, by default the index's
7
  latest): a participant gets the team file of each of their organizations, and an organizer gets every file of the
8
- run. The server decides the viewer's handle, organizations, and role from their OAuth token alone; a request names
9
- no organization.
 
10
 
11
  Settings (environment):
12
  - `HF_TOKEN` (a Space secret): the token that reads the archive dataset. It is read from the environment only, a
@@ -19,7 +22,8 @@ Settings (environment):
19
  - `USERINFO_URL`: the OAuth userinfo endpoint, `https://huggingface.co/oauth/userinfo` by default.
20
  - `ARCHIVE_DIR`: a local directory read in place of the archive dataset (tests and local previews).
21
 
22
- Logs give the viewer's handle, role, run, and status code only, never a token or a file's contents.
 
23
  """
24
  from __future__ import annotations
25
 
@@ -42,6 +46,9 @@ logger = logging.getLogger("leaderboard-api")
42
 
43
  USERINFO_URL = "https://huggingface.co/oauth/userinfo"
44
  RUNS = "leaderboard/runs.json" # the index of the runs with a site board, written by `challenge-leaderboard`
 
 
 
45
  CACHE_SECONDS = 60 # how long a userinfo answer and a directory listing are reused
46
  USERINFO_TIMEOUT_SECONDS = 15
47
  HANDLE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$") # a Hugging Face user or organization handle
@@ -155,6 +162,34 @@ def site_board(archive, run: str) -> dict | None:
155
  return board if isinstance(board, dict) else None
156
 
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  def viewer_role(info: dict, settings: dict) -> tuple[str, list[str]]:
159
  """`organizer` for a handle in `ORGANIZER_USERS` or a member of `ORGANIZER_ORG`, else `participant`, with the
160
  viewer's organization handles in the order the userinfo lists them."""
@@ -194,6 +229,41 @@ def organizer_payload(archive, entry: dict) -> dict:
194
  "failures": _text(archive, f"{run}/failures.md")}
195
 
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  def create_app(settings: dict | None = None) -> FastAPI:
198
  """The API with CORS for the configured origins, `GET` and the `Authorization` header only, and no cookies."""
199
  settings = settings_from_env() if settings is None else settings
@@ -251,8 +321,12 @@ def create_app(settings: dict | None = None) -> FastAPI:
251
  if index is None:
252
  logger.info("runs: 503")
253
  return JSONResponse({"error": "no_board"}, status_code=503)
254
- logger.info("runs: 200")
255
- return index
 
 
 
 
256
 
257
  @app.get("/board/{run}")
258
  def board(run: str):
@@ -278,21 +352,27 @@ def create_app(settings: dict | None = None) -> FastAPI:
278
  role, orgs = viewer_role(info, settings)
279
  try:
280
  index = runs_index(archive)
281
- if index is None:
282
- logger.info("me: %s %s %s 503", handle, role, run or "-")
283
- return JSONResponse({"error": "no_board"}, status_code=503)
284
- name = index["latest"] if run is None else run
285
- entry = index_entry(index, name) if RUN_NAME.fullmatch(name) else None
286
- if entry is None:
287
- logger.info("me: %s %s %s 404", handle, role, name)
288
- return JSONResponse({"error": "not_found"}, status_code=404)
289
- payload = (organizer_payload(archive, entry) if role == "organizer"
290
- else participant_payload(archive, entry, orgs))
 
 
 
 
 
 
291
  except HfHubHTTPError as exc: # e.g., no read token yet
292
  logger.error("me: %s %s %s 503; the archive could not be read (%s)", handle, role, run or "-",
293
  type(exc).__name__)
294
  return JSONResponse({"error": "archive_unavailable"}, status_code=503)
295
- logger.info("me: %s %s %s 200", handle, role, name)
296
  return payload
297
 
298
  return app
 
1
  """The results API behind the competition site's leaderboard pop-up, served from a public Hugging Face Space.
2
 
3
  The index of runs is `leaderboard/runs.json` in the archive (`RUNS`), which `challenge-leaderboard` rebuilds on every
4
+ render, and the pending run (the run being evaluated between its intake and the publication of its board) is the
5
+ `run` of `leaderboard/pending.json` (`PENDING`) when the index does not list it. `GET /healthz` gives the latest
6
+ run and its date (both null without an index), `GET /runs` gives the index with the pending run under `pending`,
7
  and `GET /board/<run>` gives a run's site board (`leaderboard/<run>/site.json`), each without authentication.
8
  `GET /me` gives a signed-in viewer the files they may see for one run (`?run=<run>`, by default the index's
9
  latest): a participant gets the team file of each of their organizations, and an organizer gets every file of the
10
+ run. For the pending run it gives the pending files instead (`leaderboard/<run>/pending/<org>.json`), each with
11
+ the evaluation's progress per base model read from the run's `status.json` (`evaluation`). The server decides the
12
+ viewer's handle, organizations, and role from their OAuth token alone; a request names no organization.
13
 
14
  Settings (environment):
15
  - `HF_TOKEN` (a Space secret): the token that reads the archive dataset. It is read from the environment only, a
 
22
  - `USERINFO_URL`: the OAuth userinfo endpoint, `https://huggingface.co/oauth/userinfo` by default.
23
  - `ARCHIVE_DIR`: a local directory read in place of the archive dataset (tests and local previews).
24
 
25
+ Logs give the viewer's handle, role, run, whether it is pending, and the status code only, never a token or a
26
+ file's contents.
27
  """
28
  from __future__ import annotations
29
 
 
46
 
47
  USERINFO_URL = "https://huggingface.co/oauth/userinfo"
48
  RUNS = "leaderboard/runs.json" # the index of the runs with a site board, written by `challenge-leaderboard`
49
+ PENDING = "leaderboard/pending.json" # the run being evaluated, written by `challenge-leaderboard --pending`
50
+ STATUS_FILE = "status.json" # `<run>/status.json`, the evaluation's record of the run's work, one entry per key
51
+ EVALUATION_STATUSES = ("not_started", "in_progress", "completed", "error") # the status codes of its entries
52
  CACHE_SECONDS = 60 # how long a userinfo answer and a directory listing are reused
53
  USERINFO_TIMEOUT_SECONDS = 15
54
  HANDLE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$") # a Hugging Face user or organization handle
 
162
  return board if isinstance(board, dict) else None
163
 
164
 
165
+ def pending_run(archive, index: dict | None) -> dict | None:
166
+ """The pending run's block, i.e., the `run` of `PENDING`, or None without one, for a malformed one (`run` not a
167
+ mapping, `name` not matching `RUN_NAME`, `models` not a list of strings), or when the index lists the run, since
168
+ a run in the index has been published whatever the pending index says."""
169
+ pending = _json(archive, PENDING)
170
+ run = pending.get("run") if isinstance(pending, dict) else None
171
+ if not isinstance(run, dict) or not RUN_NAME.fullmatch(str(run.get("name"))):
172
+ return None
173
+ if not isinstance(run.get("models"), list) or not all(isinstance(model, str) for model in run["models"]):
174
+ return None
175
+ if index is not None and index_entry(index, run["name"]) is not None:
176
+ return None
177
+ return run
178
+
179
+
180
+ def evaluation(status: dict | None, org: str, models: list[str]) -> dict[str, str]:
181
+ """The evaluation's progress of one organization's submission per base model, in the order of `models`: the
182
+ status code of the `status.json` entry `submission-<org>/<model slug>` (the ref with `/` replaced by `__`), and
183
+ `not_started` without the file, the entry, or a code of `EVALUATION_STATUSES`. The code is the only field read
184
+ from an entry, and no other entry is read."""
185
+ codes = {}
186
+ for model in models:
187
+ entry = status.get(f"submission-{org}/{model.replace('/', '__')}") if isinstance(status, dict) else None
188
+ code = entry.get("status") if isinstance(entry, dict) else None
189
+ codes[model] = code if code in EVALUATION_STATUSES else "not_started"
190
+ return codes
191
+
192
+
193
  def viewer_role(info: dict, settings: dict) -> tuple[str, list[str]]:
194
  """`organizer` for a handle in `ORGANIZER_USERS` or a member of `ORGANIZER_ORG`, else `participant`, with the
195
  viewer's organization handles in the order the userinfo lists them."""
 
229
  "failures": _text(archive, f"{run}/failures.md")}
230
 
231
 
232
+ def _with_evaluation(archive, run: dict, org: str, team: dict | None, status: dict | None) -> dict | None:
233
+ """A pending file with the organization's `evaluation` block added, or None without the file."""
234
+ if not isinstance(team, dict):
235
+ return None
236
+ return {**team, "evaluation": evaluation(status, org, run["models"])}
237
+
238
+
239
+ def pending_participant_payload(archive, run: dict, orgs: list[str]) -> dict:
240
+ """The pending run's `run` block, the pending file of each of the viewer's organizations that has one, in the
241
+ userinfo's order, each with its `evaluation` block from the run's `status.json` (read on every request), and
242
+ the organizations without one under `missing`."""
243
+ status = _json(archive, f"{run['name']}/{STATUS_FILE}")
244
+ teams, missing = [], []
245
+ for org in orgs:
246
+ team = _with_evaluation(archive, run, org, _json(archive, f"leaderboard/{run['name']}/pending/{org}.json"),
247
+ status)
248
+ (missing.append(org) if team is None else teams.append(team))
249
+ return {"role": "participant", "pending": True, "run": run, "teams": teams, "missing": missing}
250
+
251
+
252
+ def pending_organizer_payload(archive, run: dict) -> dict:
253
+ """The pending run's `run` block, a null board, every pending file of the run with its `evaluation` block, the
254
+ pull manifest, and the run's progress and failures files."""
255
+ name = run["name"]
256
+ status = _json(archive, f"{name}/{STATUS_FILE}")
257
+ teams = {}
258
+ for path in archive.listing(f"leaderboard/{name}/pending"):
259
+ if path.endswith(".json"):
260
+ org = Path(path).stem
261
+ teams[org] = _with_evaluation(archive, run, org, _json(archive, path), status)
262
+ return {"role": "organizer", "pending": True, "run": run, "board": None, "teams": teams,
263
+ "pull": _json(archive, f"{name}/manifest.json"), "progress": _text(archive, f"{name}/progress.txt"),
264
+ "failures": _text(archive, f"{name}/failures.md")}
265
+
266
+
267
  def create_app(settings: dict | None = None) -> FastAPI:
268
  """The API with CORS for the configured origins, `GET` and the `Authorization` header only, and no cookies."""
269
  settings = settings_from_env() if settings is None else settings
 
321
  if index is None:
322
  logger.info("runs: 503")
323
  return JSONResponse({"error": "no_board"}, status_code=503)
324
+ try:
325
+ pending = pending_run(archive, index)
326
+ except HfHubHTTPError as exc:
327
+ return unavailable("runs", exc)
328
+ logger.info("runs: 200 (pending %s)", pending["name"] if pending else "-")
329
+ return {**index, "pending": pending}
330
 
331
  @app.get("/board/{run}")
332
  def board(run: str):
 
352
  role, orgs = viewer_role(info, settings)
353
  try:
354
  index = runs_index(archive)
355
+ pending = pending_run(archive, index) if run is not None else None
356
+ if pending is not None and run == pending["name"]:
357
+ name, kind = run, "pending"
358
+ payload = (pending_organizer_payload(archive, pending) if role == "organizer"
359
+ else pending_participant_payload(archive, pending, orgs))
360
+ else:
361
+ if index is None:
362
+ logger.info("me: %s %s %s 503", handle, role, run or "-")
363
+ return JSONResponse({"error": "no_board"}, status_code=503)
364
+ name, kind = (index["latest"] if run is None else run), "published"
365
+ entry = index_entry(index, name) if RUN_NAME.fullmatch(name) else None
366
+ if entry is None:
367
+ logger.info("me: %s %s %s 404", handle, role, name)
368
+ return JSONResponse({"error": "not_found"}, status_code=404)
369
+ payload = (organizer_payload(archive, entry) if role == "organizer"
370
+ else participant_payload(archive, entry, orgs))
371
  except HfHubHTTPError as exc: # e.g., no read token yet
372
  logger.error("me: %s %s %s 503; the archive could not be read (%s)", handle, role, run or "-",
373
  type(exc).__name__)
374
  return JSONResponse({"error": "archive_unavailable"}, status_code=503)
375
+ logger.info("me: %s %s %s %s 200", handle, role, name, kind)
376
  return payload
377
 
378
  return app
tests/conftest.py CHANGED
@@ -23,7 +23,10 @@ TOKENS = {
23
  "orgs": [{"preferred_username": "team-a"}, {"preferred_username": "team-z"}]},
24
  "organizer-token": {"preferred_username": ORGANIZER, "orgs": []},
25
  "member-token": {"preferred_username": "bob", "orgs": [{"preferred_username": ORGANIZER_ORG}]},
 
26
  }
 
 
27
  RUN_BLOCKS = {
28
  "week_01": {"name": "week_01", "label": "Week 1", "layer": "public", "date": "2026-10-02",
29
  "deadline": "2026-09-29T12:00:00+00:00", "n_ranked": 2, "n_baselines": 1},
@@ -74,6 +77,22 @@ def site_board(run: str) -> dict:
74
  "delta": 0.2, "r_side": 0.01, "r_guard": 0.02}]}
75
 
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  def team_file(run: str, org: str) -> dict:
78
  return {"schema": 2, "run": {**RUN_BLOCKS[run], "pull_finished": None, "models": ["m/one"]}, "org": org,
79
  "team": org.title(), "entry": {"status": "pulled", "record": [["repo", f"{org}/submission"]]},
@@ -83,7 +102,8 @@ def team_file(run: str, org: str) -> dict:
83
  @pytest.fixture()
84
  def archive(tmp_path: Path) -> Path:
85
  """An archive with the runs `week_01` and `week_02` indexed, `week_00` indexed without a site board, `week_03`
86
- with an internal board and no site board (not indexed), and the pull files of `week_02`."""
 
87
  root = tmp_path / "archive"
88
  write(root, "leaderboard/runs.json", {"schema": 1, "latest": "week_02",
89
  "runs": [{"name": "week_00", "label": "Week 0", "layer": "public",
@@ -101,6 +121,19 @@ def archive(tmp_path: Path) -> Path:
101
  write(root, "week_02/manifest.json", {"run": "week_02", "orgs": [{"org": "team-a", "status": "pulled"}]})
102
  write(root, "week_02/progress.txt", "## submissions\n- [✓] team-a\n")
103
  write(root, "week_02/failures.md", "# Failures\n\nnone\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  return root
105
 
106
 
 
23
  "orgs": [{"preferred_username": "team-a"}, {"preferred_username": "team-z"}]},
24
  "organizer-token": {"preferred_username": ORGANIZER, "orgs": []},
25
  "member-token": {"preferred_username": "bob", "orgs": [{"preferred_username": ORGANIZER_ORG}]},
26
+ "other-token": {"preferred_username": "carol", "orgs": [{"preferred_username": "team-q"}]},
27
  }
28
+ PENDING_RUN = {"name": "week_03", "label": "Week 3", "layer": "public", "pull_finished": "2026-10-13T12:41:10+00:00",
29
+ "n_pulled": 2, "models": ["m/one", "m/two", "m/three"]}
30
  RUN_BLOCKS = {
31
  "week_01": {"name": "week_01", "label": "Week 1", "layer": "public", "date": "2026-10-02",
32
  "deadline": "2026-09-29T12:00:00+00:00", "n_ranked": 2, "n_baselines": 1},
 
77
  "delta": 0.2, "r_side": 0.01, "r_guard": 0.02}]}
78
 
79
 
80
+ def pending_file(org: str, team: str) -> dict:
81
+ return {"schema": 1, "run": PENDING_RUN, "org": org, "team": team,
82
+ "entry": {"status": "pulled", "repo_private": True, "evaluator_role": "read", "commit": "c" * 40,
83
+ "commit_time": "2026-10-12T10:00:00+00:00", "pulled_at": "2026-10-13T12:03:51+00:00",
84
+ "files": [{"path": "method_card.md", "bytes": 3120}], "card": None, "label": "ccccccc",
85
+ "intake": None, "record": [["repo", f"{org}/submission"], ["commit", f"{'c' * 12} 2026-10-12 10:00 UTC"],
86
+ ["pulled", "2026-10-13 12:03 UTC"], ["files", "method_card.md 3 KB"],
87
+ ["intake", "no intake report for this run"]],
88
+ "method_card": {"file": "method_card.md", "text": f"# {team}\n", "truncated": False}}}
89
+
90
+
91
+ def status_entry(status: str, **fields) -> dict:
92
+ return {"after": ["elicit/m__one"], "error": None, "heartbeat": "2026-10-13T14:00:00+00:00", "kind": "submission",
93
+ "status": status, "updated_at": "2026-10-13T14:00:00+00:00", "worker": "worker-7:12345", **fields}
94
+
95
+
96
  def team_file(run: str, org: str) -> dict:
97
  return {"schema": 2, "run": {**RUN_BLOCKS[run], "pull_finished": None, "models": ["m/one"]}, "org": org,
98
  "team": org.title(), "entry": {"status": "pulled", "record": [["repo", f"{org}/submission"]]},
 
102
  @pytest.fixture()
103
  def archive(tmp_path: Path) -> Path:
104
  """An archive with the runs `week_01` and `week_02` indexed, `week_00` indexed without a site board, `week_03`
105
+ pending (an internal board and no site board, not indexed, with a pending index, two pending files, and a status
106
+ file), and the pull files of `week_02` and `week_03`."""
107
  root = tmp_path / "archive"
108
  write(root, "leaderboard/runs.json", {"schema": 1, "latest": "week_02",
109
  "runs": [{"name": "week_00", "label": "Week 0", "layer": "public",
 
121
  write(root, "week_02/manifest.json", {"run": "week_02", "orgs": [{"org": "team-a", "status": "pulled"}]})
122
  write(root, "week_02/progress.txt", "## submissions\n- [✓] team-a\n")
123
  write(root, "week_02/failures.md", "# Failures\n\nnone\n")
124
+ # the pending run: an index, two pending files, and a status file with entries for one of them on two models
125
+ write(root, "leaderboard/pending.json", {"schema": 1, "run": PENDING_RUN})
126
+ write(root, "leaderboard/week_03/pending/team-a.json", pending_file("team-a", "Team A"))
127
+ write(root, "leaderboard/week_03/pending/team-b.json", pending_file("team-b", "Team B Secret"))
128
+ write(root, "week_03/status.json", {
129
+ "elicit/m__one": status_entry("completed", kind="elicit", after=[]),
130
+ "submission-team-a/m__one": status_entry("completed"),
131
+ "submission-team-a/m__two": status_entry("in_progress", error="a secret traceback"),
132
+ "submission-team-b/m__one": status_entry("error", error="another secret traceback")})
133
+ write(root, "week_03/manifest.json", {"run": "week_03", "orgs": [{"org": "team-a", "status": "pulled"},
134
+ {"org": "team-b", "status": "pulled"}]})
135
+ write(root, "week_03/progress.txt", "## submissions\n- [ ] team-a\n- [✗] team-b\n")
136
+ write(root, "week_03/failures.md", "# Failures\n\nteam-b\n")
137
  return root
138
 
139
 
tests/test_app.py CHANGED
@@ -10,7 +10,7 @@ from fastapi.testclient import TestClient
10
  from huggingface_hub.errors import HfHubHTTPError
11
 
12
  import app as leaderboard_api
13
- from conftest import RUN_BLOCKS, settings
14
 
15
 
16
  def read(archive: Path, path: str):
@@ -29,10 +29,12 @@ def test_the_fixture_archive_holds_two_runs_with_a_site_board(archive: Path) ->
29
 
30
 
31
  def test_runs_gives_the_index_verbatim(client: TestClient, archive: Path) -> None:
 
32
  response = client.get("/runs")
33
  assert response.status_code == 200
34
- assert response.json() == read(archive, "leaderboard/runs.json")
35
- assert response.json()["latest"] == "week_02"
 
36
 
37
 
38
  def test_runs_without_an_index_is_no_board(empty_client: TestClient) -> None:
@@ -62,7 +64,7 @@ def test_board_gives_a_runs_site_board_verbatim(client: TestClient, archive: Pat
62
  def test_board_is_not_found_for_an_unknown_malformed_or_unindexed_run(client: TestClient) -> None:
63
  """An unknown run, a name that is not a run name, a run with an internal board but no site board that the index
64
  does not list, and an indexed run without a site board each answer 404."""
65
- for run in ("nope", "Week-01", "week_03", "week_00"):
66
  response = client.get(f"/board/{run}")
67
  assert response.status_code == 404 and response.json() == {"error": "not_found"}, run
68
 
@@ -106,9 +108,9 @@ def test_me_gives_an_organizer_every_file_of_the_run(client: TestClient, archive
106
 
107
 
108
  def test_me_answers_404_for_an_unknown_run_and_401_before_it(client: TestClient) -> None:
109
- """An unknown run, a name that is not a run name, and a run the index does not list each answer 404; a missing
110
- or rejected token answers 401 before the run is looked at."""
111
- for run in ("nope", "Week-01", "week_03"):
112
  response = client.get("/me", params={"run": run}, headers=bearer("participant-token"))
113
  assert response.status_code == 404 and response.json() == {"error": "not_found"}, run
114
  assert client.get("/me", params={"run": "nope"}).status_code == 401
@@ -151,3 +153,73 @@ def test_an_unreadable_archive_is_503_everywhere(archive: Path, userinfo_url: st
151
  client.get("/me", headers=bearer("participant-token"))):
152
  assert request.status_code == 503 and request.json() == {"error": "archive_unavailable"}
153
  assert client.get("/me").status_code == 401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  from huggingface_hub.errors import HfHubHTTPError
11
 
12
  import app as leaderboard_api
13
+ from conftest import PENDING_RUN, RUN_BLOCKS, settings
14
 
15
 
16
  def read(archive: Path, path: str):
 
29
 
30
 
31
  def test_runs_gives_the_index_verbatim(client: TestClient, archive: Path) -> None:
32
+ """The index's fields are served as written, with the pending run added under `pending`."""
33
  response = client.get("/runs")
34
  assert response.status_code == 200
35
+ assert {key: value for key, value in response.json().items() if key != "pending"} == read(
36
+ archive, "leaderboard/runs.json")
37
+ assert response.json()["latest"] == "week_02" and "pending" in response.json()
38
 
39
 
40
  def test_runs_without_an_index_is_no_board(empty_client: TestClient) -> None:
 
64
  def test_board_is_not_found_for_an_unknown_malformed_or_unindexed_run(client: TestClient) -> None:
65
  """An unknown run, a name that is not a run name, a run with an internal board but no site board that the index
66
  does not list, and an indexed run without a site board each answer 404."""
67
+ for run in ("nope", "Week-01", "week_03", "week_04", "week_00"):
68
  response = client.get(f"/board/{run}")
69
  assert response.status_code == 404 and response.json() == {"error": "not_found"}, run
70
 
 
108
 
109
 
110
  def test_me_answers_404_for_an_unknown_run_and_401_before_it(client: TestClient) -> None:
111
+ """An unknown run, a name that is not a run name, and a run that is neither in the index nor pending each
112
+ answer 404; a missing or rejected token answers 401 before the run is looked at."""
113
+ for run in ("nope", "Week-01", "week_04"):
114
  response = client.get("/me", params={"run": run}, headers=bearer("participant-token"))
115
  assert response.status_code == 404 and response.json() == {"error": "not_found"}, run
116
  assert client.get("/me", params={"run": "nope"}).status_code == 401
 
153
  client.get("/me", headers=bearer("participant-token"))):
154
  assert request.status_code == 503 and request.json() == {"error": "archive_unavailable"}
155
  assert client.get("/me").status_code == 401
156
+
157
+
158
+ # ---- the pending run ----------------------------------------------------------------------------------------
159
+
160
+
161
+ def test_runs_names_the_pending_run(client: TestClient, archive: Path, userinfo_url: str) -> None:
162
+ """`/runs` carries the pending run's block; it is null with the index cleared, with a pending index naming a
163
+ run the index lists, and with a malformed pending index."""
164
+ response = client.get("/runs")
165
+ assert response.status_code == 200
166
+ assert response.json()["pending"] == PENDING_RUN
167
+ assert {key: value for key, value in response.json().items() if key != "pending"} == read(archive, "leaderboard/runs.json")
168
+ pending = archive / "leaderboard" / "pending.json"
169
+ for cleared in ({"schema": 1, "run": None}, {"schema": 1, "run": {**PENDING_RUN, "name": "week_02"}},
170
+ {"schema": 1, "run": {**PENDING_RUN, "name": "Week 3"}}, {"schema": 1, "run": {**PENDING_RUN, "models": "m/one"}},
171
+ {"schema": 1}):
172
+ pending.write_text(json.dumps(cleared), encoding="utf-8")
173
+ assert client.get("/runs").json()["pending"] is None, cleared
174
+ assert client.get("/me", params={"run": "week_03"}, headers=bearer("participant-token")).status_code == 404
175
+ pending.unlink()
176
+ assert client.get("/runs").json()["pending"] is None
177
+
178
+
179
+ def test_me_gives_a_participant_their_pending_files_with_the_evaluation_per_model(client: TestClient,
180
+ archive: Path) -> None:
181
+ response = client.get("/me", params={"run": "week_03"}, headers=bearer("participant-token"))
182
+ assert response.status_code == 200
183
+ payload = response.json()
184
+ assert list(payload) == ["role", "pending", "run", "teams", "missing"]
185
+ assert (payload["role"], payload["pending"], payload["run"]) == ("participant", True, PENDING_RUN)
186
+ assert payload["missing"] == ["team-z"] and len(payload["teams"]) == 1
187
+ team = payload["teams"][0]
188
+ assert team["evaluation"] == {"m/one": "completed", "m/two": "in_progress", "m/three": "not_started"}
189
+ assert {key: value for key, value in team.items() if key != "evaluation"} == read(
190
+ archive, "leaderboard/week_03/pending/team-a.json")
191
+ text = response.text
192
+ for forbidden in ("team-b", "Team B Secret", "error", "worker", "heartbeat", "traceback", "after", "updated_at"):
193
+ assert forbidden not in text, forbidden
194
+ other = client.get("/me", params={"run": "week_03"}, headers=bearer("other-token"))
195
+ assert other.status_code == 200 and other.json()["teams"] == [] and other.json()["missing"] == ["team-q"]
196
+ (archive / "week_03" / "status.json").unlink()
197
+ without = client.get("/me", params={"run": "week_03"}, headers=bearer("participant-token")).json()
198
+ assert without["teams"][0]["evaluation"] == {model: "not_started" for model in PENDING_RUN["models"]}
199
+
200
+
201
+ def test_me_gives_an_organizer_every_pending_file(client: TestClient, archive: Path) -> None:
202
+ response = client.get("/me", params={"run": "week_03"}, headers=bearer("organizer-token"))
203
+ assert response.status_code == 200
204
+ payload = response.json()
205
+ assert list(payload) == ["role", "pending", "run", "board", "teams", "pull", "progress", "failures"]
206
+ assert (payload["role"], payload["pending"], payload["run"], payload["board"]) == ("organizer", True,
207
+ PENDING_RUN, None)
208
+ assert set(payload["teams"]) == {"team-a", "team-b"}
209
+ assert payload["teams"]["team-a"]["evaluation"] == {"m/one": "completed", "m/two": "in_progress",
210
+ "m/three": "not_started"}
211
+ assert payload["teams"]["team-b"]["evaluation"] == {"m/one": "error", "m/two": "not_started",
212
+ "m/three": "not_started"}
213
+ assert payload["teams"]["team-b"]["team"] == "Team B Secret"
214
+ assert payload["pull"] == read(archive, "week_03/manifest.json")
215
+ assert payload["progress"].startswith("## submissions") and payload["failures"].startswith("# Failures")
216
+ assert "traceback" not in response.text
217
+
218
+
219
+ def test_the_pending_run_has_no_board_and_is_not_the_default(client: TestClient) -> None:
220
+ assert client.get("/board/week_03").status_code == 404
221
+ latest = client.get("/me", headers=bearer("participant-token")).json()
222
+ assert latest["run"]["name"] == "week_02" and "pending" not in latest
223
+ assert client.get("/healthz").json() == {"run": "week_02", "date": "2026-10-09"}
224
+ assert client.get("/me", params={"run": "week_03"}).status_code == 401
225
+ assert client.get("/me", params={"run": "week_03"}, headers=bearer("bad-token")).status_code == 401