diff --git a/src/kernelbot/api/main.py b/src/kernelbot/api/main.py index 6526ec9f..7a7d4e6e 100644 --- a/src/kernelbot/api/main.py +++ b/src/kernelbot/api/main.py @@ -1133,6 +1133,18 @@ async def get_user_submission( response_run = {key: run[key] for key in run_fields} if not run["secret"] and run["mode"] in ("test", "benchmark"): response_run["result"] = run["result"] + # Runtime tracebacks, compiler errors, and process failures are + # stored separately from the structured evaluator result. Expose + # them only for the authenticated owner's public runs; secret-run + # diagnostics can contain private validation details. + meta = run.get("meta") or {} + response_run["diagnostics"] = { + key: meta[key] + for key in ("stdout", "stderr", "success", "exit_code", "duration") + if key in meta + } + if run.get("compilation") is not None: + response_run["diagnostics"]["compilation"] = run["compilation"] runs.append(response_run) runner_queue = await get_submission_runner_queue_status(submission) return { diff --git a/tests/test_admin_api.py b/tests/test_admin_api.py index 2b0a6352..2e103b32 100644 --- a/tests/test_admin_api.py +++ b/tests/test_admin_api.py @@ -289,6 +289,14 @@ def test_submission_details_include_stored_run_results(self, test_client, mock_b "score": None, "passed": False, "result": test_result, + "meta": { + "stdout": "", + "stderr": "Traceback: public test failure", + "success": False, + "exit_code": 1, + "duration": 1.25, + }, + "compilation": None, }, { "start_time": "2026-07-06T12:00:00Z", @@ -299,6 +307,18 @@ def test_submission_details_include_stored_run_results(self, test_client, mock_b "score": None, "passed": True, "result": benchmark_result, + "meta": { + "stdout": "benchmark output", + "stderr": "", + "success": True, + "exit_code": 0, + "duration": 2.5, + }, + "compilation": { + "success": True, + "stdout": "", + "stderr": "", + }, }, { "start_time": "2026-07-06T12:00:00Z", @@ -309,6 +329,13 @@ def test_submission_details_include_stored_run_results(self, test_client, mock_b "score": None, "passed": True, "result": {"secret-result": "must not be exposed"}, + "meta": { + "stderr": "SECRET_TRACEBACK_SENTINEL", + "exit_code": 1, + }, + "compilation": { + "stderr": "SECRET_COMPILER_SENTINEL", + }, }, { "start_time": "2026-07-06T12:00:00Z", @@ -353,14 +380,26 @@ def test_submission_details_include_stored_run_results(self, test_client, mock_b assert response.status_code == 200 runs = response.json()["runs"] assert runs[0]["result"] == test_result + assert runs[0]["diagnostics"] == { + "stdout": "", + "stderr": "Traceback: public test failure", + "success": False, + "exit_code": 1, + "duration": 1.25, + } assert runs[1]["result"] == benchmark_result + assert runs[1]["diagnostics"]["stdout"] == "benchmark output" + assert runs[1]["diagnostics"]["compilation"]["success"] is True assert "result" not in runs[2] + assert "diagnostics" not in runs[2] assert runs[2]["secret"] is True assert runs[2]["passed"] is True assert "result" not in runs[3] assert "result" not in runs[4] assert "must not be exposed" not in response.text assert "PROFILE_SENTINEL" not in response.text + assert "SECRET_TRACEBACK_SENTINEL" not in response.text + assert "SECRET_COMPILER_SENTINEL" not in response.text class TestAdminStats: