Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions crates/challenge-agentic/src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,15 @@ mod tests {
assert!(resolve_rel(&wd, "/etc/passwd").is_err());
}

/// `AGENTIC_ENABLE_RUN_COMMAND` is process-global; serialize the two tests
/// that mutate it so parallel libtest cannot race enable vs disable.
static RUN_COMMAND_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());

#[test]
fn run_command_executes_in_sandboxed_cwd() {
let _guard = RUN_COMMAND_ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
Comment on lines +707 to +715

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore AGENTIC_ENABLE_RUN_COMMAND after each test.

The mutex serializes the two tests, but it does not restore the previous process-global value. run_command_executes_in_sandboxed_cwd removes the variable at Line 729, and run_command_disabled_without_container_env removes it at Line 746. The review container sets this variable in crates/review-docker/src/lib.rs:79-95. A later test can therefore observe the wrong ToolContext::from_request mode. Capture the previous value and restore it with a scoped guard before releasing RUN_COMMAND_ENV_LOCK, including during panic unwinding.

Also applies to: 743-746

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/challenge-agentic/src/tools.rs` around lines 707 - 715, Update both
tests protected by RUN_COMMAND_ENV_LOCK, run_command_executes_in_sandboxed_cwd
and run_command_disabled_without_container_env, to capture the existing
AGENTIC_ENABLE_RUN_COMMAND value and restore it via a scoped guard before the
mutex guard is released, including during panic unwinding. Preserve each test’s
current environment mutations while ensuring the original present or absent
value is reinstated afterward.

let dir = tempdir().unwrap();
fs::write(dir.path().join("agent.py"), "x = 1\n").unwrap();
std::env::set_var("AGENTIC_ENABLE_RUN_COMMAND", "1");
Expand Down Expand Up @@ -733,6 +740,10 @@ mod tests {

#[test]
fn run_command_disabled_without_container_env() {
let _guard = RUN_COMMAND_ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
std::env::remove_var("AGENTIC_ENABLE_RUN_COMMAND");
let dir = tempdir().unwrap();
fs::write(dir.path().join("agent.py"), "x = 1\n").unwrap();
let req = ReviewRequest {
Expand Down
120 changes: 101 additions & 19 deletions crates/design-http/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,64 @@ pub async fn get_stats(State(st): State<Arc<AppState>>) -> Response {
.into_response()
}

/// Cap for dashboard `recent_runs` (site gallery + activity feed).
const RECENT_RUNS_LIMIT: usize = 40;
/// Wider scan so a queue flood of brand-new rows cannot hide screenshot runs.
const RECENT_RUNS_SCAN: u32 = 500;

/// Stages that normally carry a captured `index.png` after sanitize.
fn likely_has_screenshot(status: RunStage) -> bool {
matches!(
status,
RunStage::AgenticReview
| RunStage::AwaitingAdmin
| RunStage::AwaitingAnnotation
| RunStage::Scored
)
}

/// Prefer post-sanitize / scored runs (gallery candidates), then fill with the
/// newest remaining rows so the feed still reflects live queue activity.
fn select_recent_runs(runs: &[design_store::RunState], limit: usize) -> Vec<Value> {
let mut viewable = Vec::new();
let mut other = Vec::new();
for r in runs {
if likely_has_screenshot(r.status) {
viewable.push(r);
} else {
other.push(r);
}
}
viewable
.into_iter()
.chain(other)
.take(limit)
.map(|r| {
let (prompt_title, _) = prompt_fields(&r.prompt_id);
json!({
"id": r.id,
"status": r.status.as_str(),
"round_id": r.round_id,
"harness_id": r.harness_id,
"prompt_id": r.prompt_id,
"prompt_title": prompt_title,
"error_detail": r.error_detail,
"created_at_ms": r.created_at_ms,
"updated_at_ms": r.updated_at_ms,
})
})
.collect()
}

/// `GET /v1/dashboard` — one-shot UI payload (status + leaderboard + recent jobs).
pub async fn get_dashboard(State(st): State<Arc<AppState>>) -> Response {
let secs = now_secs();
let rid = round_id_at(secs);
let runs = st.store.list_runs(None, 100).await.unwrap_or_default();
let runs = st
.store
.list_runs(None, RECENT_RUNS_SCAN)
.await
.unwrap_or_default();
let by_status = count_by_status(&runs);
let round = st.store.get_round(rid).await.ok().flatten();
let ratings = st.store.ratings_for_round(rid).await.unwrap_or_default();
Expand All @@ -120,24 +173,7 @@ pub async fn get_dashboard(State(st): State<Arc<AppState>>) -> Response {
vec![]
};
let opens = rid * round_secs();
let jobs: Vec<Value> = runs
.iter()
.take(40)
.map(|r| {
let (prompt_title, _) = prompt_fields(&r.prompt_id);
json!({
"id": r.id,
"status": r.status.as_str(),
"round_id": r.round_id,
"harness_id": r.harness_id,
"prompt_id": r.prompt_id,
"prompt_title": prompt_title,
"error_detail": r.error_detail,
"created_at_ms": r.created_at_ms,
"updated_at_ms": r.updated_at_ms,
})
})
.collect();
let jobs = select_recent_runs(&runs, RECENT_RUNS_LIMIT);
let lb = |rows: &[design_store::RatingRow]| -> Vec<Value> {
rows.iter()
.map(|r| {
Expand Down Expand Up @@ -315,3 +351,49 @@ pub async fn run_status_json(store: &dyn DesignStore, id: &str) -> Result<Value,
"annotation_ready": r.status == RunStage::AwaitingAnnotation,
}))
}

#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]

use design_store::{RunStage, RunState};

use super::select_recent_runs;

fn run(id: &str, status: RunStage, created_at_ms: u64) -> RunState {
RunState {
id: id.into(),
round_id: 1,
harness_id: "h".into(),
prompt_id: "p01".into(),
status,
artifact_digest: None,
sanitize_report: None,
agentic_verdict: None,
error_detail: None,
final_score: None,
retry_count: 0,
created_at_ms,
updated_at_ms: created_at_ms,
}
}

#[test]
fn select_recent_runs_prefers_screenshot_stages() {
// Newest-first scan: a flood of queued rows would otherwise fill the
// site gallery window and hide the awaiting_admin candidate.
let runs = vec![
run("q1", RunStage::Queued, 100),
run("q2", RunStage::Queued, 99),
run("q3", RunStage::Queued, 98),
run("a1", RunStage::AwaitingAdmin, 97),
run("s1", RunStage::Scored, 96),
];
let jobs = select_recent_runs(&runs, 3);
let ids: Vec<&str> = jobs
.iter()
.filter_map(|j| j.get("id").and_then(|v| v.as_str()))
.collect();
assert_eq!(ids, vec!["a1", "s1", "q1"]);
}
}
50 changes: 48 additions & 2 deletions crates/site-api/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,25 @@ async fn design_leaderboard_json(
.and_then(Value::as_array)
.cloned()
.unwrap_or_default();
let previous_round = dash
.as_ref()
.and_then(|d| d.pointer("/leaderboard/previous_round"))
.and_then(Value::as_u64)
.or_else(|| round_id.map(|r| r.saturating_sub(1)));
let epoch = dash
.as_ref()
.and_then(|d| d.get("epoch"))
.and_then(Value::as_u64)
.unwrap_or(0);
let mut rows = design_leaderboard(&ratings, &previous, epoch);
// Mid-round before admin winners: current ratings are empty. Surface the
// previous round's standings so the marketing board is not blank.
let (board_ratings, board_previous, board_round_id) =
if ratings.is_empty() && !previous.is_empty() {
(previous.as_slice(), &[][..], previous_round)
} else {
(ratings.as_slice(), previous.as_slice(), round_id)
};
let mut rows = design_leaderboard(board_ratings, board_previous, epoch);
if let Some(needle) = q.filter(|s| !s.trim().is_empty()) {
rows.retain(|r| leaderboard_matches_query(r, needle));
}
Expand All @@ -334,7 +347,7 @@ async fn design_leaderboard_json(
"total": page_out.total,
"pageCount": page_out.page_count,
"epoch": epoch,
"roundId": round_id,
"roundId": board_round_id,
"roundEndsAt": round_ends_at,
"secondsRemaining": seconds_remaining,
"updatedAt": now_iso(),
Expand Down Expand Up @@ -1028,6 +1041,39 @@ mod tests {
);
}

#[tokio::test]
async fn design_leaderboard_falls_back_to_previous_round() {
let (design, _prism, st) = setup().await;
Mock::given(method("GET"))
.and(path("/v1/dashboard"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"epoch": 3,
"leaderboard": {
"current_round": 9,
"previous_round": 8,
"ratings": [],
"previous_ratings": [
{"miner_hotkey": "aa".repeat(32), "rating": 250, "wins": 1, "losses": 0}
]
},
"round": {
"round_id": 9,
"closes_at_secs": 1_700_000_100_u64,
"seconds_remaining": 120
},
"recent_runs": []
})))
.mount(&design)
.await;
let app = site_router(st);

let (s, v) = call(app, "/v1/site/arenas/design/leaderboard").await;
assert_eq!(s, StatusCode::OK, "{v}");
assert_eq!(v["total"], 1, "{v}");
assert_eq!(v["roundId"], 8, "{v}");
assert_eq!(v["items"][0]["elo"], 250.0);
}

#[tokio::test]
async fn arenas_carry_trust_root_shares_and_weights_endpoint() {
use std::sync::Arc;
Expand Down
14 changes: 10 additions & 4 deletions docs/SITE_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ public preview is `screenshotUrl` → `/challenge/design/v1/view/{runId}/index.p
`https://chain.joinbase.ai/challenge/design/v1/view/{runId}/index.png`) so PNG
bytes are not proxied through the site's Vercel `/gbase-api` rewrite. JSON
`/v1/site/*` calls may keep using the same-origin proxy. Runs without a captured
screenshot are excluded from the submissions list.
Leaderboard `elo` is the design
`rating` field. Prism window series use real terminal `bpb` with a single
`[final]` point when no step curve is stored.
screenshot are excluded from the submissions list. Design `GET /v1/dashboard`
`recent_runs` therefore prioritizes post-sanitize stages (`awaiting_admin`,
`scored`, …) over a flood of brand-new `queued` rows so the site gallery is not
starved.

Leaderboard `elo` is the design `rating` field. When the current round has no
winners yet (`ratings: []`), `/v1/site/arenas/design/leaderboard` surfaces the
previous round's standings (`roundId` = previous) rather than an empty board.
Prism window series use real terminal `bpb` with a single `[final]` point when
no step curve is stored.

`GET /v1/site/arenas/{slug}/submissions` and `/leaderboard` accept optional
`?q=` — case-insensitive substring over miner hotkey (SS58 or hex), handle,
Expand Down
Loading