diff --git a/app/api/showcases/[id]/enrichment/retry/route.ts b/app/api/showcases/[id]/enrichment/retry/route.ts new file mode 100644 index 0000000..3192ba5 --- /dev/null +++ b/app/api/showcases/[id]/enrichment/retry/route.ts @@ -0,0 +1,38 @@ +import { requireAuthorizedUser } from "@/lib/auth/authorization"; +import { appendAuditEvent } from "@/lib/data/audit"; +import { retryShowcaseEnrichment } from "@/lib/data/showcase-enrichment"; +import { apiErrorResponse } from "@/lib/http/api"; +import { secureJson } from "@/lib/security/http"; +import { enforceRateLimit } from "@/lib/security/rate-limit"; + +export async function POST( + request: Request, + context: { params: Promise<{ id: string }> }, +) { + try { + const { identity, user } = await requireAuthorizedUser(request); + await enforceRateLimit(identity.subject, { + action: "showcase-enrichment-retry", + limit: 10, + windowMs: 24 * 60 * 60 * 1000, + }); + const { id } = await context.params; + const retry = await retryShowcaseEnrichment(id, user.id); + if (!retry) { + return secureJson( + { error: "No failed automated preview is available to retry." }, + { status: 409 }, + ); + } + await appendAuditEvent({ + actorUserId: user.id, + entityType: "showcase", + entityId: id, + action: "showcase.enrichment_retried", + metadata: { dispatchDeferred: retry.dispatchDeferred }, + }); + return secureJson({ retry }); + } catch (error) { + return apiErrorResponse(error); + } +} diff --git a/app/api/showcases/[id]/processing/retry/route.ts b/app/api/showcases/[id]/processing/retry/route.ts new file mode 100644 index 0000000..4cb8e6b --- /dev/null +++ b/app/api/showcases/[id]/processing/retry/route.ts @@ -0,0 +1,40 @@ +import { requireAuthorizedUser } from "@/lib/auth/authorization"; +import { appendAuditEvent } from "@/lib/data/audit"; +import { retryShowcaseProcessing } from "@/lib/data/showcase-processing"; +import { apiErrorResponse } from "@/lib/http/api"; +import { secureJson } from "@/lib/security/http"; +import { enforceRateLimit } from "@/lib/security/rate-limit"; + +export async function POST( + request: Request, + context: { params: Promise<{ id: string }> }, +) { + try { + const { identity, user } = await requireAuthorizedUser(request); + await enforceRateLimit(identity.subject, { + action: "showcase-processing-retry", + limit: 20, + windowMs: 24 * 60 * 60 * 1000, + }); + const { id } = await context.params; + const processing = await retryShowcaseProcessing(id, user.id); + await appendAuditEvent({ + actorUserId: user.id, + entityType: "showcase", + entityId: id, + action: + processing.outcome === "ready" + ? "showcase.processing_retry_completed" + : processing.outcome === "blocked" + ? "showcase.processing_retry_blocked" + : "showcase.processing_retry_pending", + metadata: { + outcome: processing.outcome, + scannedArtifactCount: processing.scannedArtifactIds.length, + }, + }); + return secureJson({ processing }); + } catch (error) { + return apiErrorResponse(error); + } +} diff --git a/app/api/showcases/[id]/publish/route.ts b/app/api/showcases/[id]/publish/route.ts index fed1e42..89b91c3 100644 --- a/app/api/showcases/[id]/publish/route.ts +++ b/app/api/showcases/[id]/publish/route.ts @@ -8,7 +8,7 @@ import { apiErrorResponse } from "@/lib/http/api"; import { secureJson } from "@/lib/security/http"; import { enforceRateLimit } from "@/lib/security/rate-limit"; import { verifyApprovedShowcaseArtifacts } from "@/lib/security/artifact-scanner"; -import { queuePublishedResult } from "@/lib/data/results"; +import { scheduleShowcaseEnrichment } from "@/lib/data/showcase-enrichment"; export async function POST( request: Request, @@ -31,27 +31,36 @@ export async function POST( } await verifyApprovedShowcaseArtifacts(id); const showcase = await publishShowcase(id, user.id); - let run: Awaited>["run"] | null = - null; - let judgeQueueDeferred = false; + let enrichment = { + dispatchDeferred: false, + eligible: false, + enrichmentId: null as string | null, + }; try { - const queued = await queuePublishedResult(showcase.id); - run = queued.run; - judgeQueueDeferred = queued.judgeQueueDeferred; + enrichment = await scheduleShowcaseEnrichment(showcase.id); } catch { - judgeQueueDeferred = true; + enrichment = { + dispatchDeferred: true, + eligible: true, + enrichmentId: null, + }; } await appendAuditEvent({ actorUserId: user.id, entityType: "showcase", entityId: showcase.id, action: "showcase.published", - metadata: { judgeQueueDeferred }, + metadata: { + enrichmentDeferred: enrichment.dispatchDeferred, + enrichmentEligible: enrichment.eligible, + reviewStatus: "awaiting_review", + }, + }); + return secureJson({ + showcase, + enrichment, + reviewStatus: "awaiting_review", }); - return secureJson( - { showcase, run, judgeQueueDeferred }, - { status: judgeQueueDeferred ? 202 : 200 }, - ); } catch (error) { return apiErrorResponse(error); } diff --git a/app/api/uploads/sessions/[sessionId]/complete/route.ts b/app/api/uploads/sessions/[sessionId]/complete/route.ts index d836f08..96eb824 100644 --- a/app/api/uploads/sessions/[sessionId]/complete/route.ts +++ b/app/api/uploads/sessions/[sessionId]/complete/route.ts @@ -7,6 +7,7 @@ import { promoteUploadSessionObjectKey, } from "@/lib/data/uploads"; import { apiErrorResponse } from "@/lib/http/api"; +import { recordShowcaseProcessingFailure } from "@/lib/data/showcase-processing"; import { secureJson } from "@/lib/security/http"; import { enforceRateLimit } from "@/lib/security/rate-limit"; import { scanQuarantinedArtifact } from "@/lib/security/artifact-scanner"; @@ -99,7 +100,32 @@ export async function POST( } const artifact = await finalizeUploadedArtifact({ sessionId: session.id }); - const scan = await scanQuarantinedArtifact(artifact); + let scan; + try { + scan = await scanQuarantinedArtifact(artifact); + } catch (error) { + const failure = await recordShowcaseProcessingFailure( + artifact.showcaseId, + error, + ); + if (failure) { + await appendAuditEvent({ + actorUserId: user.id, + entityType: "showcase", + entityId: artifact.showcaseId, + action: "showcase.processing_failed", + metadata: { code: failure.code, stage: "artifact_scan" }, + }).catch(() => undefined); + } + return secureJson( + { + code: "processing_failed", + error: + "Evidence processing could not finish. Retry processing from your dashboard.", + }, + { status: 503 }, + ); + } await appendAuditEvent({ actorUserId: user.id, entityType: "artifact", diff --git a/app/components/ShowcaseCard.tsx b/app/components/ShowcaseCard.tsx index fa3008b..86593b2 100644 --- a/app/components/ShowcaseCard.tsx +++ b/app/components/ShowcaseCard.tsx @@ -31,19 +31,21 @@ export function ShowcaseCard({ showcase }: { showcase: Showcase }) {
{showcase.model} - {showcase.reasoning} reasoning + {showcase.harness}

- {showcase.title} + {showcase.title}

{showcase.description}

+ Declared by contributor — not independently verified
{showcase.evidence.map((item) => ( {item} ))}
- {showcase.status} + {simpleStatus(showcase.status, showcase.scoreBps)} + {showcase.reasoning} reasoning {showcase.scoreBps !== null && ( {(showcase.scoreBps / 100).toFixed(2)} )} @@ -58,3 +60,18 @@ export function ShowcaseCard({ showcase }: { showcase: Showcase }) { ); } + +function simpleStatus(status: string, scoreBps: number | null) { + const normalized = status.toLowerCase(); + if (normalized === "ranked" || normalized.includes("ranked #")) { + return "Ranked"; + } + if ( + normalized === "reviewed" || + scoreBps !== null || + normalized.includes("scored") + ) { + return "Reviewed"; + } + return "Awaiting review"; +} diff --git a/app/components/SiteFooter.tsx b/app/components/SiteFooter.tsx index d467923..8e59f76 100644 --- a/app/components/SiteFooter.tsx +++ b/app/components/SiteFooter.tsx @@ -9,15 +9,15 @@ export function SiteFooter() { B/ BENCHMAX -

Real tests. Inspectable evidence. Rankings that earn trust.

+

Public AI Tests with inspectable prompts, setup, and evidence.

PRODUCT - Explore - Tests + All Tests + Models Leaderboards - Submit result + Submit Test
TRUST @@ -31,7 +31,7 @@ export function SiteFooter() {
© 2026 Benchmax - Public methodology · community results v1 + Community Tests · declared setup · inspectable evidence
); diff --git a/app/components/SiteHeader.tsx b/app/components/SiteHeader.tsx index f441873..ee15a50 100644 --- a/app/components/SiteHeader.tsx +++ b/app/components/SiteHeader.tsx @@ -14,33 +14,27 @@ export function SiteHeader() { BENCHMAX
- - Add a test - - Submit result + Submit Test
Menu diff --git a/app/contributors/[handle]/page.tsx b/app/contributors/[handle]/page.tsx index 0a2539b..9b3f2b4 100644 --- a/app/contributors/[handle]/page.tsx +++ b/app/contributors/[handle]/page.tsx @@ -43,13 +43,13 @@ export default async function ContributorPage({ CONTRIBUTOR

@{contributor.handle}

- {contributor.displayName} shares inspectable model test results - and their evidence. + {contributor.displayName} shares public AI Tests with inspectable + prompts, declared setup, and evidence.

-
Public results
+
Public Tests
{results.length}
@@ -57,7 +57,7 @@ export default async function ContributorPage({
PUBLIC RECORD -

Submitted results

+

Submitted Tests

{results.length > 0 ? ( @@ -68,7 +68,7 @@ export default async function ContributorPage({
) : publicResultsPage === null ? (
- Public results are temporarily unavailable. + Public Tests are temporarily unavailable.

Benchmax does not show substitute data when this contributor’s public records cannot be read. @@ -76,10 +76,8 @@ export default async function ContributorPage({

) : (
- No public results yet. -

- This active contributor has not published a model test result. -

+ No public Tests yet. +

This contributor has not published a Test.

)} diff --git a/app/dashboard/Dashboard.tsx b/app/dashboard/Dashboard.tsx index a2674d9..d64830e 100644 --- a/app/dashboard/Dashboard.tsx +++ b/app/dashboard/Dashboard.tsx @@ -19,6 +19,7 @@ type DashboardData = { rank: number | null; judgeDueAt: string | null; updatedAt: string; + canPublish: boolean; state: { code: string; label: string; @@ -35,6 +36,16 @@ type DashboardData = { status: "completed" | "failed" | "info" | "pending"; occurredAt: string; }>; + enrichment: { + status: string; + failureCode: string | null; + canRetry: boolean; + } | null; + processing: { + failureCode: string; + failedAt: string | null; + canRetry: boolean; + } | null; }>; }; @@ -55,6 +66,7 @@ function ConfiguredDashboard() { const { getToken, isLoaded, isSignedIn } = useAuth(); const [data, setData] = useState(null); const [error, setError] = useState(null); + const [retryingId, setRetryingId] = useState(null); useEffect(() => { if (!isSignedIn) return; void (async () => { @@ -102,16 +114,16 @@ function ConfiguredDashboard() { @{data.profile.handle}

{data.profile.displayName}

{data.profile.role}

- {data.submissions.length} submitted results + {data.submissions.length} submitted Tests - {publicCount} public · {rankedCount} ranked. Public results stay visible - while AI review is pending. + {publicCount} public · {rankedCount} ranked. Safe Tests stay visible + while awaiting review.
-

Submitted results

- Submit result → +

Your Tests

+ Submit Test →
{data.submissions.map((submission) => ( @@ -119,66 +131,102 @@ function ConfiguredDashboard() {
{submission.title}

- {submission.benchmark ?? "Test pending"} · {submission.model} - {submission.modelVersion - ? ` ${submission.modelVersion}` - : ""} · {submission.reasoning} + {submission.model} + {submission.modelVersion ? ` ${submission.modelVersion}` : ""} + {` · ${submission.harness} · ${submission.reasoning}`}

-

{submission.state.detail}

- {submission.judgeDueAt && - submission.state.code === "public_pending_review" && ( -

- Review target: {formatTimestamp(submission.judgeDueAt)} -

- )} - {submission.state.blockedReason && ( +

{simpleDashboardDetail(submission.state)}

+ {submission.enrichment?.status === "failed" && ( +

Automated preview unavailable.

+ )} + {submission.state.blockedReason && + ["Blocked", "Processing failed"].includes( + simpleDashboardStatus(submission.state), + ) && (

- - {submission.state.publicVisible - ? "Why not ranked:" - : "Blocked reason:"} - {" "} + Details:{" "} {submission.state.blockedReason}

- )} -
- - Stage history ({submission.timeline.length}) - - {submission.timeline.length > 0 ? ( -
    - {submission.timeline.map((event) => ( -
  1. - - {event.status} - -
    - {event.label} - {event.detail &&

    {event.detail}

    } - -
    -
  2. - ))} -
- ) : ( -

No recorded stage events yet.

)} -
- {submission.state.label} + {simpleDashboardStatus(submission.state)} {submission.state.publicVisible && ( - Open → + Open → + )} + {submission.enrichment?.canRetry && ( + + )} + {submission.processing?.canRetry && ( + + )} + {submission.canPublish && ( + )} ))} {data.submissions.length === 0 && ( -

No submitted results yet.

+

No submitted Tests yet.

)}
@@ -186,16 +234,82 @@ function ConfiguredDashboard() { ); } -function timelineTone(status: "completed" | "failed" | "info" | "pending") { - if (status === "completed") return "approved"; - if (status === "failed") return "blocked"; - if (status === "pending") return "pending"; - return "neutral"; +function simpleDashboardStatus(state: DashboardData["submissions"][number]["state"]) { + if (state.code === "processing_failed") return "Processing failed"; + if (state.code === "ranked") return "Ranked"; + if (state.code === "reviewed") return "Reviewed"; + if (state.publicVisible) return "Awaiting review"; + if (["blocked", "rejected", "removed"].includes(state.code)) return "Blocked"; + return "Processing"; } -function formatTimestamp(value: string) { - return new Intl.DateTimeFormat(undefined, { - dateStyle: "medium", - timeStyle: "short", - }).format(new Date(value)); +async function retryEnrichment( + showcaseId: string, + getToken: () => Promise, +) { + const token = await getToken(); + if (!token) throw new Error("Your session expired."); + const response = await fetch( + `/api/showcases/${encodeURIComponent(showcaseId)}/enrichment/retry`, + { + method: "POST", + headers: { Authorization: `Bearer ${token}` }, + }, + ); + const payload = (await response.json()) as { error?: string }; + if (!response.ok) { + throw new Error(payload.error ?? "Could not retry the automated preview."); + } +} + +async function retryProcessing( + showcaseId: string, + getToken: () => Promise, +) { + const token = await getToken(); + if (!token) throw new Error("Your session expired."); + const response = await fetch( + `/api/showcases/${encodeURIComponent(showcaseId)}/processing/retry`, + { + method: "POST", + headers: { Authorization: `Bearer ${token}` }, + }, + ); + const payload = (await response.json()) as { error?: string }; + if (!response.ok) { + throw new Error(payload.error ?? "Could not retry evidence processing."); + } +} + +async function publishTest( + showcaseId: string, + getToken: () => Promise, +) { + const token = await getToken(); + if (!token) throw new Error("Your session expired."); + const response = await fetch( + `/api/showcases/${encodeURIComponent(showcaseId)}/publish`, + { + method: "POST", + headers: { Authorization: `Bearer ${token}` }, + }, + ); + const payload = (await response.json()) as { error?: string }; + if (!response.ok) { + throw new Error(payload.error ?? "Could not publish this Test."); + } +} + +function simpleDashboardDetail(state: DashboardData["submissions"][number]["state"]) { + const status = simpleDashboardStatus(state); + if (status === "Awaiting review") { + return "This safe Test is public and waiting for review."; + } + if (status === "Reviewed") { + return "This Test has been reviewed and remains public."; + } + if (status === "Ranked") { + return "This Test is included in the current leaderboard."; + } + return state.detail; } diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 40e751a..01e1956 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -16,10 +16,10 @@ export default function DashboardPage() {
PRIVATE WORKSPACE -

Your tests and submitted results.

+

Your Tests.

- Drafts, safety checks, AI review status, rankings, and result - history. + Track private processing, public Awaiting review Tests, reviews, + and rankings.

diff --git a/app/explore/page.tsx b/app/explore/page.tsx index 3aee39e..01d8833 100644 --- a/app/explore/page.tsx +++ b/app/explore/page.tsx @@ -1,236 +1,21 @@ -import type { Metadata } from "next"; -import Link from "next/link"; -import { categoryLabels } from "@/lib/domain/catalog"; -import { ShowcaseCard } from "@/app/components/ShowcaseCard"; -import { SiteFooter } from "@/app/components/SiteFooter"; -import { SiteHeader } from "@/app/components/SiteHeader"; -import { listPublicShowcaseCardsPage } from "@/lib/data/showcases"; +import { permanentRedirect } from "next/navigation"; -const PAGE_SIZE = 24; - -type ExploreFilters = { - category?: string; - contributor?: string; - model?: string; - page?: string; - q?: string; - reasoning?: string; - status?: string; -}; - -export const metadata: Metadata = { - title: "Explore model results", - description: - "Browse public community model test results, including those still pending AI review.", -}; +type ExploreSearchParams = Record; export default async function ExplorePage({ searchParams, }: { - searchParams: Promise; + searchParams: Promise; }) { - const filters = await searchParams; - const page = parsePage(filters.page); - const feedResult = await listPublicShowcaseCardsPage({ - category: filters.category, - contributor: filters.contributor, - limit: PAGE_SIZE, - model: filters.model, - offset: (page - 1) * PAGE_SIZE, - q: filters.q, - reasoning: filters.reasoning, - status: parseStatus(filters.status), - }).catch(() => null); - const results = feedResult?.items ?? []; - const hasFilters = [ - filters.category, - filters.contributor, - filters.model, - filters.q, - filters.reasoning, - filters.status, - ].some((value) => value?.trim()); - const hasPrevious = page > 1; - const hasNext = feedResult?.hasNext ?? false; - return ( -
- -
-
- PUBLIC RESULTS -

Every submitted result stays visible.

-

- Browse code, image, video, and log evidence whether the AI judge has - ranked it yet or not. -

-
-
- - - - - - - -
-
- - {results.length} public result{results.length === 1 ? "" : "s"} on - page {page} - - - {feedResult === null ? "Catalog unavailable" : "Newest first"} - -
- {results.length > 0 ? ( -
- {results.map((result) => ( - - ))} -
- ) : ( -
- - {feedResult === null - ? "Public results are temporarily unavailable." - : hasFilters - ? "No matching results on this page." - : "No results have been submitted yet."} - -

- New results publish after safety scanning and remain visible - while AI judging runs. -

-
- {hasFilters && ( - - Clear filters - - )} - - Submit a result - -
-
- )} - {(hasPrevious || hasNext) && ( - - )} -
- -
- ); -} - -function parsePage(value: string | undefined): number { - if (!value || !/^\d+$/u.test(value)) return 1; - const page = Number(value); - const maxPage = Math.floor(Number.MAX_SAFE_INTEGER / PAGE_SIZE); - return Number.isSafeInteger(page) && page > 0 && page <= maxPage ? page : 1; -} - -function parseStatus( - value: string | undefined, -): "delayed" | "not-ranked" | "pending" | "ranked" | undefined { - return value === "delayed" || - value === "not-ranked" || - value === "pending" || - value === "ranked" - ? value - : undefined; -} - -function explorePageHref(filters: ExploreFilters, page: number): string { + const values = await searchParams; const params = new URLSearchParams(); - for (const key of [ - "category", - "contributor", - "model", - "q", - "reasoning", - "status", - ] as const) { - const value = filters[key]?.trim(); - if (value) params.set(key, value); + for (const [key, value] of Object.entries(values)) { + if (Array.isArray(value)) { + value.forEach((item) => params.append(key, item)); + } else if (value) { + params.set(key, value); + } } - if (page > 1) params.set("page", String(page)); const query = params.toString(); - return query ? `/explore?${query}` : "/explore"; + permanentRedirect(query ? `/tests?${query}` : "/tests"); } diff --git a/app/layout.tsx b/app/layout.tsx index 6c8b485..f0673ea 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -34,22 +34,23 @@ export const metadata: Metadata = { template: "%s · Benchmax", }, description: - "Public, evidence-backed AI model tests with declared provenance, transparent AI judging, and per-test rankings.", + "Browse public AI Tests with their prompts, contributor-declared setup, and inspectable output evidence.", applicationName: "Benchmax", category: "technology", keywords: [ - "AI benchmarks", + "AI tests", + "AI outputs", "coding models", - "model tests", + "community model tests", "AI leaderboard", - "verified benchmarks", + "coding agents", ], openGraph: { type: "website", siteName: "Benchmax", title: "Benchmax — See what models actually built", description: - "Public, evidence-backed AI model tests with declared provenance, transparent AI judging, and per-test rankings.", + "Public AI Tests with their prompts, declared setup, contributor, and output evidence.", images: [ { url: "/og.png", @@ -63,7 +64,7 @@ export const metadata: Metadata = { card: "summary_large_image", title: "Benchmax — See what models actually built", description: - "Public, evidence-backed AI model tests with declared provenance and transparent AI judging.", + "Browse public AI Tests and inspect exactly what contributors submitted.", images: ["/og.png"], }, }; diff --git a/app/leaderboards/page.tsx b/app/leaderboards/page.tsx index df115f5..ef801f4 100644 --- a/app/leaderboards/page.tsx +++ b/app/leaderboards/page.tsx @@ -2,125 +2,43 @@ import type { Metadata } from "next"; import Link from "next/link"; import { SiteFooter } from "@/app/components/SiteFooter"; import { SiteHeader } from "@/app/components/SiteHeader"; -import { listPublicResultLeaderboard } from "@/lib/data/results"; export const metadata: Metadata = { - title: "Result leaderboards", + title: "Leaderboards", description: - "AI-judged community model results ranked separately for each frozen test version.", + "Top-rated public AI Test submissions, added after trustworthy reviews.", }; -export default async function LeaderboardsPage() { - const rows = await listPublicResultLeaderboard().catch(() => null); - const grouped = rows - ? Map.groupBy( - rows, - (row) => - `${row.testSlug}:${row.testVersion}:evaluation-${row.evaluationVersion}`, - ) - : null; +export default function LeaderboardsPage() { return (
- AI-JUDGED RESULTS -

One leaderboard per test.

+ LEADERBOARDS +

Rankings come after trustworthy reviews.

- Results are never merged across different prompts or rubrics. - Unknown model or harness labels stay public but remain unranked - until the catalog mapping is reviewed. Model, harness, reasoning, - and settings are declared, unverified metadata. + This will be a showcase of top-rated submissions across different + prompts, not a scientific like-for-like model benchmark.

- {rows === null ? ( -
- Leaderboards are temporarily unavailable. -

- Benchmax does not show an empty ranking when the public catalog - cannot be read. -

-
- ) : rows.length === 0 ? ( -
- No ranked results yet. -

- Submitted results still appear publicly while AI review is - pending. A leaderboard appears after the first eligible score. -

+
+ No ranked Tests yet. +

+ All safe Tests remain public while Benchmax adds AI and trusted + human review. A score is never required to appear on All Tests. +

+
+ + Browse All Tests + - Submit a result + Submit a Test
- ) : ( - [...grouped!.values()].map((testRows) => { - const first = testRows[0]; - return ( -
-
-
- - TEST VERSION {first.testVersion} · SNAPSHOT{" "} - {first.snapshotVersion} · EVALUATION V - {first.evaluationVersion} - -

- - {first.testTitle} - -

-
- - {first.judgeSnapshot} ·{" "} - {first.snapshotPublishedAt?.toISOString() ?? - "Snapshot pending"} - -
-
-
- Rank / result - Score - Judge samples - Configuration -
- {testRows.map((row) => ( -
-
- {row.rank} -
- - - {row.resultTitle} - - -
- {row.model} · {row.modelVersion} -
- Declared, unverified -
-
- - {(row.scoreBps / 100).toFixed(2)} - - {row.sampleCount} - - {row.harness} · {row.reasoning} · @{row.contributor} - Declared, unverified - -
- ))} -
-
- ); - }) - )} +
diff --git a/app/methodology/page.tsx b/app/methodology/page.tsx index 0d6250d..4e2adc8 100644 --- a/app/methodology/page.tsx +++ b/app/methodology/page.tsx @@ -6,7 +6,7 @@ import { SiteHeader } from "@/app/components/SiteHeader"; export const metadata: Metadata = { title: "Methodology", description: - "How Benchmax publishes community model results, judges evidence, and creates per-test rankings.", + "How Benchmax publishes contributor-submitted AI Tests and adds reviews later.", }; export default function MethodologyPage() { @@ -15,89 +15,75 @@ export default function MethodologyPage() {
- PUBLIC RESULT PROTOCOL -

Evidence first. Ranking second.

+ PUBLIC TEST PROTOCOL +

Publish the evidence. Review it later.

- Benchmax hosts community-run model tests. It does not call the model - being tested and never asks contributors for a tested-model API key. + Benchmax hosts Tests run and submitted by contributors. It does not + call the tested model or ask for that model's API key.

+
- 01 / TEST CONTRACT -

Every result points to one frozen test version.

-

- The test records its goal, exact prompt, success criteria, and - rubric. Editing any scoring-relevant part creates a new version so - incompatible results are not ranked together. -

-
-
- 02 / SUBMISSION -

The contributor declares the exact configuration.

-

- A result records model family and version, reasoning level, harness - and version, prompt context, settings, and evidence. Evidence may be - code, images, video, or logs. Missing catalog entries are accepted - publicly but held out of ranking until mapped. -

+ 01 / ONE TEST +

One submission creates one public Test.

- These configuration fields are always labeled declared, - unverified. Benchmax cannot prove that the named model, - reasoning level, harness, or settings produced the submitted files. - Catalog mapping standardizes names; it does not verify the run. + The contributor records the prompt, model and version, harness, + reasoning, optional settings and notes, and the output evidence. + There is no separate reusable-test or rubric-approval flow.

+
- 03 / PUBLICATION -

Safe results appear before AI judging finishes.

+ 02 / DECLARED SETUP +

Attribution is visible, and the honesty label is explicit.

- Files enter quarantine first. After type, path, archive, malware, - and secret checks pass, the result is published with a visible - pending-review state. The result remains public whether it later - ranks or not. + Model, harness, reasoning, and settings are labeled{" "} + + Declared by contributor — not independently verified + + . Benchmax preserves free-text names even when they are not in a + catalog.

+
- 04 / AI JUDGE -

Review can take up to 24 hours.

+ 03 / SAFETY +

Mandatory evidence checks happen before publication.

- The pinned judge receives the frozen rubric and bounded, - identity-blinded evidence. Submitted content is treated as - untrusted data. Objective evaluator output is included when a safe, - executable source bundle and compatible environment are available. + Files enter quarantine for type, archive, executable, path, secret, + and abuse checks. A safe Test becomes public as Awaiting review. A + blocked Test remains private to its contributor and admins.

+
- 05 / RANKING -

One immutable leaderboard snapshot per test version.

+ 04 / AUTOMATED PREVIEW +

Compatible source ZIPs receive non-blocking enrichment.

- Eligible results rank by score within the same test and judge - version. Equal scores share a rank. Initial review uses one judge - sample; top-ten results are rechecked to three samples before the - leaderboard settles. Every published snapshot is retained. + A safe Test publishes first. Sandbox-generated screenshots, video, + console, and accessibility evidence attach afterward when + available. Enrichment failure never removes the Test.

+
- 06 / INTERPRETATION RISK -

A high score is evidence quality, not independent reproduction.

+ 05 / REVIEW AND RANKING +

Reviews add context without changing the submission.

- A contributor can run a model many times and submit only the best - output. Benchmax does not observe the unsubmitted attempts, so - best-of-N cherry-picking cannot be detected or corrected. Treat a - ranking as a comparison of the submitted evidence under one frozen - test and judge version, not as a verified estimate of pass@1 model - performance. + AI and trusted human reviews are a later layer. Only reviewed, + eligible Tests can become Ranked; unreviewed Tests stay visible in + All Tests.

+
- 07 / STATES -

The public label says what is actually known.

+ 06 / PUBLIC STATES +

The main label stays simple.

{[ - ["Pending AI review", "Public, safe, and waiting for judgment."], - ["Delayed", "The 24-hour target passed; the result stays public."], - ["Ranked", "Scored, catalog-mapped, eligible, and in a snapshot."], - ["Not ranked", "Scored or failed review but excluded with a reason."], + ["Awaiting review", "Public, safe, and not scored yet."], + ["Reviewed", "Has one or more AI or human reviews."], + ["Ranked", "Eligible and included in a leaderboard."], ].map(([title, description]) => (
{title} @@ -105,12 +91,17 @@ export default function MethodologyPage() {
))}
+

+ Processing, Processing failed, and Blocked are private contributor + or admin states and do not appear as public feed items. +

+
-

Have a result ready?

-

Put the evidence on the record.

+

Have a Test ready?

+

Put the prompt, setup, and evidence on the record.

- Submit a result + Submit a Test
diff --git a/app/models/[slug]/page.tsx b/app/models/[slug]/page.tsx index 4e625db..ef1fa12 100644 --- a/app/models/[slug]/page.tsx +++ b/app/models/[slug]/page.tsx @@ -1,14 +1,24 @@ import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; +import { ShowcaseCard } from "@/app/components/ShowcaseCard"; import { SiteFooter } from "@/app/components/SiteFooter"; import { SiteHeader } from "@/app/components/SiteHeader"; -import { getPublicModelPage } from "@/lib/data/public-catalog"; -import { listPublicConfigurationSummaries } from "@/lib/data/results"; +import { + listPublicDeclaredModels, + listPublicShowcaseCardsPage, +} from "@/lib/data/showcases"; +import { declaredModelLabelFromPathKey } from "@/lib/domain/declared-model-path"; -export const metadata: Metadata = { - title: "Community model configuration summary", -}; +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }>; +}): Promise { + const { slug } = await params; + const label = declaredModelLabelFromPathKey(slug); + return { title: label ? `${label} Tests` : "Model Tests" }; +} export default async function ModelPage({ params, @@ -16,114 +26,63 @@ export default async function ModelPage({ params: Promise<{ slug: string }>; }) { const { slug } = await params; - const result = await listPublicConfigurationSummaries(slug).catch( - () => null, - ); - const summaries = result?.summaries ?? []; - let modelExists: boolean | null = null; - if (result && summaries.length === 0) { - try { - modelExists = (await getPublicModelPage(slug)) !== null; - } catch { - modelExists = null; - } + const modelLabel = declaredModelLabelFromPathKey(slug); + if (!modelLabel) notFound(); + const models = await listPublicDeclaredModels().catch(() => null); + if (!models) { + return ( +
+ +
+
Model Tests are temporarily unavailable.
+
+ +
+ ); } - if (modelExists === false) notFound(); - const modelLabel = summaries[0]?.modelLabel ?? slug; - const snapshotDate = summaries - .map((summary) => summary.snapshotDate?.getTime()) - .filter((value): value is number => value !== undefined) - .sort((a, b) => b - a)[0]; + const model = models.find((candidate) => candidate.label === modelLabel); + if (!model) notFound(); + const page = await listPublicShowcaseCardsPage({ + limit: 50, + modelExact: model.label, + }).catch(() => null); return (
- COMMUNITY MODEL SUMMARY -

{modelLabel}

+ DECLARED MODEL +

{model.label}

-

- Contributor-declared, unverified model, harness, reasoning, and - settings metadata. This summary is not a verified model ranking. -

+

Declared by contributors — not independently verified.

-
- Each test version contributes one median to the equal-weight score. - IQR is calculated across those test medians, so popular tests cannot - dominate the summary. -
- {result === null ? ( + {page === null ? (
- Model summary is temporarily unavailable. + Model Tests are temporarily unavailable.

- Benchmax could not read this public catalog record and will not - invent a model summary. + Benchmax will not show an empty feed when these public records + cannot be read.

- ) : summaries.length === 0 ? ( -
- No eligible configurations for this model yet. -

- Pending, delayed, and unranked submissions remain visible in - Explore even when they cannot enter this summary. -

- - Browse public results - + ) : page.items.length > 0 ? ( +
+ {page.items.map((test) => ( + + ))}
) : ( -
-
- Declared configuration - Equal-test score - Coverage / N - IQR -
- {summaries.map((summary) => ( -
-
- {summary.modelVersionLabel} -
- {summary.harnessLabel} · {summary.reasoning} reasoning -
-
- Settings {JSON.stringify(summary.declaredSettings)} · config{" "} - {summary.metadataHash.slice(0, 12)} -
- Declared, unverified -
- - {(summary.scoreBps / 100).toFixed(2)} - - - {summary.testCoverage} tests · {summary.contributorCount}{" "} - contributors - {summary.provisional ? " · provisional" : ""} - - - {(summary.q1ScoreBps / 100).toFixed(2)}– - {(summary.q3ScoreBps / 100).toFixed(2)} - -
- ))} +
+ No public Tests for this model.
)} -
- - Snapshot date{" "} - {snapshotDate - ? new Date(snapshotDate).toISOString() - : "not available"} - - - {result - ? `Evaluation v${result.evaluationVersion ?? "unavailable"} · aggregate snapshot v${result.snapshotVersion ?? "unavailable"} · reproducibility hash ${result.snapshotHash}` - : "Summary unavailable"} - -
- - Compare results on the primary per-test leaderboards + {page?.hasNext && ( +

+ Showing the newest 50 Tests. Use the All Tests filters for a narrower view. +

+ )} + + Open this model in All Tests →
diff --git a/app/models/page.tsx b/app/models/page.tsx index 0377398..8eba708 100644 --- a/app/models/page.tsx +++ b/app/models/page.tsx @@ -2,120 +2,60 @@ import type { Metadata } from "next"; import Link from "next/link"; import { SiteFooter } from "@/app/components/SiteFooter"; import { SiteHeader } from "@/app/components/SiteHeader"; -import { listPublicConfigurationSummaries } from "@/lib/data/results"; +import { listPublicDeclaredModels } from "@/lib/data/showcases"; +import { declaredModelPathKey } from "@/lib/domain/declared-model-path"; export const metadata: Metadata = { - title: "Community model summaries", - description: - "Community-declared model configuration summaries derived from immutable per-test leaderboards.", + title: "Models", + description: "Browse public Tests by the model declared by each contributor.", }; export default async function ModelsPage() { - const result = await listPublicConfigurationSummaries().catch(() => null); - const summaries = result?.summaries ?? []; - const byModel = Map.groupBy(summaries, (summary) => summary.modelSlug); + const result = await listPublicDeclaredModels().catch(() => null); + const models = result ?? []; return (
- COMMUNITY SUMMARIES -

A model name is not a configuration.

+ MODELS +

Tests grouped by declared model.

- These use declared, unverified model, harness, reasoning, and - settings metadata. Per-test leaderboards remain the primary - comparison. + These names come from contributors. They are useful for browsing, + but Benchmax has not independently verified the model identity.

{result === null ? (
- Model summaries are temporarily unavailable. -

- Benchmax does not replace an unavailable catalog with fabricated - model or score data. -

+ Models are temporarily unavailable. +

Benchmax will not replace unavailable data with invented entries.

- ) : summaries.length === 0 ? ( + ) : models.length === 0 ? (
- No eligible configuration summaries yet. -

- Public results appear here after they are scored, catalog-mapped, - and eligible on at least one test leaderboard. -

- - View test leaderboards + No public Tests yet. +

Models appear here as soon as a safe Test is published.

+ + Submit a Test
) : ( - [...byModel.entries()] - .sort(([, a], [, b]) => - a[0].modelLabel.localeCompare(b[0].modelLabel), - ) - .map(([modelSlug, modelSummaries]) => ( -
-
-
- - {modelSummaries.length} DECLARED CONFIGURATION - {modelSummaries.length === 1 ? "" : "S"} - -

- - {modelSummaries[0].modelLabel} - -

-
- Declared, unverified metadata -
-
-
- Configuration - Equal-test score - Tests / contributors - IQR -
- {modelSummaries.map((summary) => ( -
-
- {summary.modelVersionLabel} -
- {summary.harnessLabel} · {summary.reasoning} reasoning -
-
- Settings {JSON.stringify(summary.declaredSettings)} · config{" "} - {summary.metadataHash.slice(0, 12)} -
- Declared, unverified -
- - {(summary.scoreBps / 100).toFixed(2)} - - - {summary.testCoverage} / {summary.contributorCount} - {summary.provisional ? " · provisional" : ""} - - - {(summary.q1ScoreBps / 100).toFixed(2)}– - {(summary.q3ScoreBps / 100).toFixed(2)} - -
- ))} -
-
- )) - )} - {result && ( -

- Evaluation v{result.evaluationVersion ?? "unavailable"} · aggregate - snapshot v{result.snapshotVersion ?? "unavailable"} · reproducible - summary {result.snapshotHash.slice(0, 16)} derived from immutable - per-test snapshots. -

+
+ {models.map((model) => ( +
+ + {model.testCount} Test{model.testCount === 1 ? "" : "s"} + +

+ + {model.label} + +

+

Declared by contributors — not independently verified.

+
+ ))} +
)}
diff --git a/app/page.tsx b/app/page.tsx index 1eaf446..51feb98 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,186 +1 @@ -import Link from "next/link"; -import { ShowcaseCard } from "./components/ShowcaseCard"; -import { SiteHeader } from "./components/SiteHeader"; -import { SiteFooter } from "./components/SiteFooter"; -import { listPublicShowcaseCards } from "@/lib/data/showcases"; -import { listPublicResultLeaderboard } from "@/lib/data/results"; -import { listCommunityTests } from "@/lib/data/community-tests"; - -export default async function Home() { - const [resultsResult, leaderboardRows, tests] = await Promise.all([ - listPublicShowcaseCards(6).catch(() => null), - listPublicResultLeaderboard().catch(() => null), - listCommunityTests().catch(() => null), - ]); - const results = resultsResult ?? []; - const latest = results[0]; - return ( -
- -
-
-
-
-
-

- Share what a model -
- actually produced. -

-

- Submit code, images, videos, or logs from a real model test. - Record the model version, reasoning level, and harness. Your - result appears after safety checks; AI judging can take up to 24 - hours. -

-
- - Submit a result - - - Add a test - -
-
- Visible before ranking - Exact test context - AI-judged evidence -
-
- -
- -
-
-
- 01 / HOW IT WORKS -

Publish first. Judge carefully.

-
-

- Ranking is a later state, not the admission ticket to the site. -

-
-
- {[ - ["1", "Choose or add a test", "Freeze the prompt and success criteria."], - ["2", "Submit the result", "Attach code, images, video, or logs plus exact settings."], - ["3", "Appear publicly", "The safe result is visible with pending AI review status."], - ["4", "AI judge and rank", "Eligible scores enter that test version’s leaderboard."], - ].map(([number, title, description]) => ( -
- {number} - {title} - {description} - - {number === "4" ? "Up to 24h" : "Recorded"} - -
- ))} -
-
- -
-
-
- 02 / PUBLIC RESULTS -

Ranked or not, the evidence stays visible.

-
- - Explore all results → - -
- {results.length > 0 ? ( -
- {results.map((result) => ( - - ))} -
- ) : ( -
- - {resultsResult === null - ? "Public results are temporarily unavailable." - : "No public results yet."} - -

No sample records are shown in place of real submissions.

- {resultsResult !== null && ( - - Submit the first result - - )} -
- )} -
- -
-
-
- 03 / LIVE CATALOG -

Tests and rankings grow with the community.

- {tests === null || leaderboardRows === null ? ( -

- The live catalog is temporarily unavailable. Benchmax does - not substitute sample counts for the public record. -

- ) : ( -

- {tests.length} published test{tests.length === 1 ? "" : "s"} and{" "} - {leaderboardRows.length} ranked result - {leaderboardRows.length === 1 ? "" : "s"} are currently on the - record. -

- )} -
-
- - Browse tests - - - Open leaderboards - -
-
-
-
- -
- ); -} +export { default } from "./tests/page"; diff --git a/app/results/[slug]/page.tsx b/app/results/[slug]/page.tsx index 9420c48..13ffb50 100644 --- a/app/results/[slug]/page.tsx +++ b/app/results/[slug]/page.tsx @@ -1,452 +1,27 @@ -import type { Metadata } from "next"; -import Image from "next/image"; -import Link from "next/link"; -import { headers } from "next/headers"; -import { notFound } from "next/navigation"; -import { SiteFooter } from "@/app/components/SiteFooter"; -import { SiteHeader } from "@/app/components/SiteHeader"; -import { getRequestIdentity } from "@/lib/auth/server"; -import { - getBlockedShowcaseForOwnerBySlug, - getPublicShowcaseBySlug, -} from "@/lib/data/showcases"; +import { permanentRedirect } from "next/navigation"; -export async function generateMetadata({ - params, -}: { - params: Promise<{ slug: string }>; -}): Promise { - const { slug } = await params; - const result = await getPublicShowcaseBySlug(slug).catch(() => null); - return { - title: result?.title ?? "Model test result", - description: result?.summary, - }; -} +type LegacySearchParams = Record; export default async function ResultPage({ params, + searchParams, }: { params: Promise<{ slug: string }>; + searchParams: Promise; }) { - const { slug } = await params; - const result = await getPublicShowcaseBySlug(slug).catch(() => null); - if (!result) { - const identity = await getRequestIdentity( - new Request("https://benchmax.invalid/results", { - headers: await headers(), - }), - ).catch(() => null); - const blocked = identity - ? await getBlockedShowcaseForOwnerBySlug(slug, identity.subject).catch( - () => null, - ) - : null; - if (!blocked) notFound(); - return ; - } - const pending = ["queued", "evaluating", "judging"].includes( - result.judgeStatus, - ); - const delayed = result.judgeStatus === "overdue"; - const notRanked = - !pending && !delayed && result.rank === null; - const publishedLabel = result.publishedAt - ? new Intl.DateTimeFormat("en", { - dateStyle: "medium", - timeStyle: "short", - timeZone: "UTC", - }).format(result.publishedAt) - : "Publication time unavailable"; - return ( -
- -
-
- Results - / - - {result.testTitle} - - / - v{result.testVersion} -
-
-
- - {result.statusLabel} - -

{result.title}

-

{result.summary}

-
- - @{result.contributor} - - -
-
-
-
- TEST - - - {result.testTitle} - - - Version {result.testVersion} -
-
- MODEL - {result.model} - - {result.modelVersion} · {result.provenance.label} - -
-
- HARNESS - {result.harness} - {result.provenance.label} -
-
- REASONING - {result.reasoning} - {result.provenance.label} -
-
- AI SCORE - - {result.scoreBps === null - ? "Pending" - : (result.scoreBps / 100).toFixed(2)} - - {result.rank && Rank #{result.rank}} - {result.evaluation && !result.evaluation.current && ( - - Historical evaluation v{result.evaluation.version}; no current rank - - )} -
-
-
- -
- {result.provenance.label} configuration metadata -

{result.provenance.note}

-

- Settings {JSON.stringify(result.declaredSettings)} · configuration{" "} - {result.configurationHash} -

- Contributor declaration -
- - {pending && ( -
- AI review is in progress. -

- This result is already public. Benchmax aims to finish judging - within 24 hours - {result.judgeDueAt - ? ` (by ${new Intl.DateTimeFormat("en", { - dateStyle: "medium", - timeStyle: "short", - timeZone: "UTC", - }).format(result.judgeDueAt)} UTC)` - : ""} - . -

- Visible now -
- )} - {delayed && ( -
- AI review is taking longer than 24 hours. -

- The result and its evidence remain public. It will not receive a - rank until review finishes and ranking eligibility is confirmed. -

- Delayed -
- )} - {notRanked && ( -
- This result is public but not ranked. -

- The public status above records the current reason. Its submitted - context and approved evidence remain available for inspection. -

- Not ranked -
- )} - - {result.scoreBps !== null && ( -
-
-
- AI JUDGMENT -

How this result scored.

-
- - {result.judgeSampleCount} judge{" "} - {result.judgeSampleCount === 1 ? "sample" : "samples"} - -
- -
-
- FINAL SCORE - {(result.scoreBps / 100).toFixed(2)} - out of 100 -
-
- REVIEW TIMING -
-
-
Evaluated
-
{formatTimestamp(result.evaluatedAt)}
-
-
-
Scored
-
{formatTimestamp(result.scoredAt)}
-
-
-
-
- - {result.dimensions.length > 0 && ( -
- {result.dimensions.map((dimension) => { - const score = Number(dimension.finalScoreBps); - return ( -
-
-
- - {Number(dimension.weightBps) / 100}% of final score - -

{dimension.title}

-
- {(score / 100).toFixed(2)} -
-
- -
-

{dimension.reasoning}

- {dimension.description} -
- ); - })} -
- )} - - {result.evaluation && ( -
-
- IMMUTABLE EVALUATION SNAPSHOT -

Version {result.evaluation.version}

-

- This score remains tied to the judge and protocol snapshot - shown here, even after Benchmax adopts a newer evaluator. -

- - {result.evaluation.current - ? "Current published evaluation" - : "Historical evaluation"} - -
-
-
-
Judge
-
- {result.evaluation.provider} · {result.evaluation.model} ·{" "} - {result.evaluation.modelVersion} -
-
-
-
Rubric protocol
-
{result.evaluation.rubricProtocolVersion}
-
-
-
Prompt template hash
-
{result.evaluation.promptTemplateHash}
-
-
-
Calibration set hash
-
{result.evaluation.calibrationSetHash}
-
-
-
- )} -
- )} - -
-
- SUBMITTED EVIDENCE -

The result stays inspectable before and after judging.

-
-
-
- TEST -

- - {result.testTitle} · version {result.testVersion} - -

-
-
- PROMPT USED -

{result.prompt}

-
- {result.systemPrompt && ( -
- SYSTEM PROMPT -

{result.systemPrompt}

-
- )} -
- EVIDENCE - {result.artifacts.length > 0 ? ( -
    - {result.artifacts.map((artifact) => ( -
  • - {artifact.contentType.startsWith("image/") && ( - - {`Submitted - - )} - {artifact.contentType.startsWith("video/") && ( - - )} -
    - {artifact.fileName} - - {artifact.kind} · {artifact.contentType} ·{" "} - {formatBytes(artifact.byteSize)} - -
    - Download -
  • - ))} -
- ) : ( -

No public artifact metadata is available.

- )} -
-
- RANKING STATUS -

{result.statusLabel}

-
-
-
-
- - Report this result → - -
-
- -
- ); + const [{ slug }, values] = await Promise.all([params, searchParams]); + const query = serializeSearchParams(values); + permanentRedirect(query ? `/tests/${slug}?${query}` : `/tests/${slug}`); } -function BlockedResultPage({ - result, -}: { - result: NonNullable< - Awaited> - >; -}) { - return ( -
- -
-
- Results - / - Owner view -
-
- Blocked -

{result.title}

- This result is private to you while it is blocked. -

- The uploaded evidence did not pass the safety scan. It is not - visible in the public feed or to other visitors. -

-

Updated {result.updatedAt.toISOString()} UTC

- - Open dashboard - -
-
- -
- ); -} - -function formatBytes(value: number): string { - if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(2)} GB`; - if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB`; - if (value >= 1024) return `${(value / 1024).toFixed(1)} KB`; - return `${value} B`; -} - -function formatTimestamp(value: Date | null): string { - if (!value) return "Not recorded"; - return `${new Intl.DateTimeFormat("en", { - dateStyle: "medium", - timeStyle: "short", - timeZone: "UTC", - }).format(value)} UTC`; +function serializeSearchParams(values: LegacySearchParams) { + const params = new URLSearchParams(); + for (const [key, value] of Object.entries(values)) { + if (Array.isArray(value)) { + value.forEach((item) => params.append(key, item)); + } else if (value) { + params.set(key, value); + } + } + return params.toString(); } diff --git a/app/showcases/[slug]/page.tsx b/app/showcases/[slug]/page.tsx index 8a9436e..441cc5d 100644 --- a/app/showcases/[slug]/page.tsx +++ b/app/showcases/[slug]/page.tsx @@ -1,10 +1,27 @@ import { permanentRedirect } from "next/navigation"; +type LegacySearchParams = Record; + export default async function ShowcasePage({ params, + searchParams, }: { params: Promise<{ slug: string }>; + searchParams: Promise; }) { - const { slug } = await params; - permanentRedirect(`/results/${slug}`); + const [{ slug }, values] = await Promise.all([params, searchParams]); + const query = serializeSearchParams(values); + permanentRedirect(query ? `/tests/${slug}?${query}` : `/tests/${slug}`); +} + +function serializeSearchParams(values: LegacySearchParams) { + const params = new URLSearchParams(); + for (const [key, value] of Object.entries(values)) { + if (Array.isArray(value)) { + value.forEach((item) => params.append(key, item)); + } else if (value) { + params.set(key, value); + } + } + return params.toString(); } diff --git a/app/submit/page.tsx b/app/submit/page.tsx index f699116..3482833 100644 --- a/app/submit/page.tsx +++ b/app/submit/page.tsx @@ -5,34 +5,26 @@ import { UploadWizard } from "@/app/upload/UploadWizard"; import { isClerkConfigured } from "@/lib/auth/server"; export const metadata: Metadata = { - title: "Submit a model result", + title: "Submit a Test", description: - "Submit code, images, video, or logs from a model test for public evidence and asynchronous AI judging.", + "Submit an AI Test with its prompt, declared setup, and output evidence.", }; -export default async function SubmitPage({ - searchParams, -}: { - searchParams: Promise<{ test?: string }>; -}) { - const { test } = await searchParams; +export default function SubmitPage() { return (
- SUBMIT A RESULT -

Put your model test result on the record.

+ SUBMIT A TEST +

Share the Test you ran and what the model produced.

- Choose the test, model version, reasoning level, and harness. Attach - code, images, video, or logs. The result publishes after safety - checks; AI judging can take up to 24 hours. + Record the prompt, model, harness, and reasoning, then attach the + output as code, images, video, or logs. Safe Tests publish as + Awaiting review.

- +
diff --git a/app/tests/[slug]/edit/page.tsx b/app/tests/[slug]/edit/page.tsx index 11d6039..8bc6532 100644 --- a/app/tests/[slug]/edit/page.tsx +++ b/app/tests/[slug]/edit/page.tsx @@ -1,75 +1,5 @@ -import type { Metadata } from "next"; -import Link from "next/link"; -import { notFound } from "next/navigation"; -import { SiteFooter } from "@/app/components/SiteFooter"; -import { SiteHeader } from "@/app/components/SiteHeader"; -import { TestCreator } from "@/app/tests/TestCreator"; -import { isClerkConfigured } from "@/lib/auth/server"; -import { getPublicBenchmarkPage } from "@/lib/data/public-catalog"; -import { communityTestDraftSchema } from "@/lib/security/policy"; +import { permanentRedirect } from "next/navigation"; -export const metadata: Metadata = { - title: "Create a new test version", - description: - "Publish an updated immutable version without changing earlier results.", -}; - -export default async function EditTestPage({ - params, -}: { - params: Promise<{ slug: string }>; -}) { - const { slug } = await params; - const data = await getPublicBenchmarkPage(slug).catch(() => null); - const current = data?.versions[0]; - if (!data || !current) notFound(); - - let successCriteria: unknown; - try { - successCriteria = JSON.parse(String(current.success_criteria_json)); - } catch { - notFound(); - } - const source = communityTestDraftSchema.safeParse({ - category: current.category, - goal: current.goal, - prompt: current.canonical_prompt, - successCriteria, - title: current.title, - }); - if (!source.success) notFound(); - - return ( -
- -
-
- Tests - / - {source.data.title} - / - New version -
-
- IMMUTABLE VERSIONING -

Create the next version.

-

- Version {Number(current.version)} and every result attached to it - stay unchanged. Only the approved new version becomes available for - future submissions. -

-
- -
- -
- ); +export default function LegacyTestEditorPage() { + permanentRedirect("/submit"); } - diff --git a/app/tests/[slug]/page.tsx b/app/tests/[slug]/page.tsx index 818624c..371b12b 100644 --- a/app/tests/[slug]/page.tsx +++ b/app/tests/[slug]/page.tsx @@ -1,10 +1,17 @@ import type { Metadata } from "next"; +import Image from "next/image"; import Link from "next/link"; +import { headers } from "next/headers"; import { notFound } from "next/navigation"; import { SiteFooter } from "@/app/components/SiteFooter"; import { SiteHeader } from "@/app/components/SiteHeader"; -import { getPublicBenchmarkPage } from "@/lib/data/public-catalog"; -import { listPublicResultLeaderboard } from "@/lib/data/results"; +import { getRequestIdentity } from "@/lib/auth/server"; +import { getPublicShowcaseEnrichment } from "@/lib/data/showcase-enrichment"; +import { + getBlockedShowcaseForOwnerBySlug, + getPublicShowcaseBySlug, +} from "@/lib/data/showcases"; +import { buildResultArtifactUrl } from "@/lib/security/usercontent"; export async function generateMetadata({ params, @@ -12,242 +19,294 @@ export async function generateMetadata({ params: Promise<{ slug: string }>; }): Promise { const { slug } = await params; - const data = await getPublicBenchmarkPage(slug).catch(() => null); + const test = await getPublicShowcaseBySlug(slug).catch(() => null); return { - title: data?.benchmark.title ?? "Community test", - description: data - ? `Prompt, scoring contract, and ranked model results for ${data.benchmark.title}.` - : undefined, + title: test?.title ?? "Public Test", + description: test?.summary, }; } export default async function TestPage({ params, - searchParams, }: { params: Promise<{ slug: string }>; - searchParams: Promise<{ version?: string }>; }) { const { slug } = await params; - const { version: requestedVersionValue } = await searchParams; - const [data, leaderboard] = await Promise.all([ - getPublicBenchmarkPage(slug).catch(() => null), - listPublicResultLeaderboard().catch(() => []), - ]); - if (!data) notFound(); - - const requestedVersion = Number(requestedVersionValue); - const current = requestedVersionValue - ? data.versions.find( - (version) => Number(version.version) === requestedVersion, - ) - : data.versions[0]; - if (!current) notFound(); - const currentVersion = Number(current?.version ?? 0); - const dimensions = data.dimensions.filter( - (dimension) => - String(dimension.benchmark_version_id) === String(current?.id), - ); - const rankedResults = leaderboard.filter( - (row) => row.testSlug === slug && row.testVersion === currentVersion, - ); - let successCriteria: string[] = []; - try { - const parsed = JSON.parse(String(current.success_criteria_json)) as unknown; - if (Array.isArray(parsed)) { - successCriteria = parsed.filter( - (criterion): criterion is string => typeof criterion === "string", - ); - } - } catch { - successCriteria = []; + const test = await getPublicShowcaseBySlug(slug).catch(() => null); + if (!test) { + const identity = await getRequestIdentity( + new Request("https://benchmax.invalid/tests", { + headers: await headers(), + }), + ).catch(() => null); + const blocked = identity + ? await getBlockedShowcaseForOwnerBySlug(slug, identity.subject).catch( + () => null, + ) + : null; + if (!blocked) notFound(); + return ; } + const enrichment = await getPublicShowcaseEnrichment(test.id).catch( + () => null, + ); + const derivedArtifacts = (enrichment?.artifacts ?? []).map((artifact) => ({ + ...artifact, + fileName: `Automated ${artifact.kind}`, + url: buildResultArtifactUrl(test.slug, artifact.id), + })); + const publishedLabel = test.publishedAt + ? new Intl.DateTimeFormat("en", { + dateStyle: "medium", + timeZone: "UTC", + }).format(test.publishedAt) + : "Publication date unavailable"; + return (
-
+
- Tests + All Tests / - Version {currentVersion || "—"} + {test.title}
- {data.versions.length > 1 && ( - - )} -
+
- {String(current.category)} -

{String(current.title)}

-
-
-

- Every submitted result uses this frozen prompt and rubric. A - changed prompt or scoring rule becomes a new version. -

- {current && ( -
- - Submit a result - - - Create a new version - -
- )} + + {test.statusLabel} + +

{test.title}

+

{test.summary}

+
+ + @{test.contributor} + + +
-
- - {current && ( -
+
- EXACT TEST PROMPT -
{String(current.canonical_prompt)}
+ MODEL + {test.model} + {test.modelVersion}
- GOAL -

{String(current.goal)}

- {successCriteria.length > 0 && ( -
    - {successCriteria.map((criterion) => ( -
  • {criterion}
  • - ))} -
- )} + HARNESS + {test.harness}
- FROZEN VERSION -
-
-
Version
-
{currentVersion}
-
-
-
Published
-
- {current.published_at - ? new Date(Number(current.published_at)).toLocaleDateString( - "en", - { dateStyle: "medium" }, - ) - : "Not published"} -
-
-
-
Scoring dimensions
-
{dimensions.length}
-
-
+ REASONING + {test.reasoning}
-
- )} + {test.scoreBps !== null && ( +
+ SCORE + {(test.scoreBps / 100).toFixed(2)} +
+ )} + {test.rank && ( +
+ RANK + #{test.rank} +
+ )} +
+ -
-
-
- SCORING CONTRACT -

What the AI judge checks.

-
+
+ Declared by contributor — not independently verified +

+ The named model, version, harness, reasoning, and settings describe + what the contributor says produced this evidence. +

+
+ +
+
+ TEST DETAILS +

Inspect the exact prompt and declared setup.

-
- {dimensions.map((dimension) => ( -
- - {Number(dimension.weight_bps) / 100}% - -

{String(dimension.title)}

-

{String(dimension.description)}

+
+
+ PROMPT +
{test.prompt}
+
+ {test.systemPrompt && ( +
+ SYSTEM PROMPT +
{test.systemPrompt}
- ))} + )} +
+ SETTINGS +
{formatSettings(test.declaredSettings)}
+
-
-
-
- RANKED RESULTS -

Compared only within version {currentVersion}.

-
- - See all public results → - -
- {rankedResults.length > 0 ? ( -
-
- Rank / result - Score - Samples - Configuration -
- {rankedResults.map((row) => ( -
-
- {row.rank} -
- - - {row.resultTitle} - - -
- {row.model} · {row.modelVersion} -
- Declared, unverified -
-
- - {(row.scoreBps / 100).toFixed(2)} - - {row.sampleCount} - - {row.harness} · {row.reasoning} - Declared, unverified - -
- ))} -
- ) : ( -
- No ranked results for this version yet. -

- Submitted results can still be public in pending, delayed, or - not-ranked states. -

- {current && ( - ({ + ...artifact, + fileName: artifact.fileName, + }))} + heading="Uploaded output and evidence" + label="SUBMITTED EVIDENCE" + /> + + {derivedArtifacts.length > 0 && ( + + )} + + {enrichment?.availability === "unavailable" && ( +
+ Automated preview unavailable +
+ )} + +
+ + Report this Test → + +
+
+ + + ); +} + +type EvidenceArtifact = { + byteSize: number; + contentType: string; + fileName: string; + id: string; + kind: string; + url: string; +}; + +function EvidenceSection({ + artifacts, + heading, + label, +}: { + artifacts: EvidenceArtifact[]; + heading: string; + label: string; +}) { + return ( +
+
+ {label} +

{heading}

+
+ {artifacts.length > 0 ? ( +
    + {artifacts.map((artifact) => ( +
  • + {artifact.contentType.startsWith("image/") && ( + - Submit the first result - + {artifact.fileName} + )} - - )} + {artifact.contentType.startsWith("video/") && ( + + )} +
    + {artifact.fileName} + + {artifact.kind} · {artifact.contentType} ·{" "} + {formatBytes(artifact.byteSize)} + +
    + Download +
  • + ))} +
+ ) : ( +

No public evidence is available.

+ )} +
+ ); +} + +function BlockedTestPage({ + test, +}: { + test: NonNullable< + Awaited> + >; +}) { + return ( +
+ +
+
+ All Tests + / + Owner view +
+
+ Blocked +

{test.title}

+ This Test is private to you. +

+ Its evidence did not pass the mandatory safety scan, so it is not + visible in All Tests or to other visitors. +

+ + Open dashboard +
); } + +function formatSettings(value: unknown) { + if (!value || (typeof value === "object" && Object.keys(value).length === 0)) { + return "No additional settings declared."; + } + return JSON.stringify(value, null, 2); +} + +function statusTone(status: string) { + if (status === "Ranked") return "approved"; + if (status === "Reviewed") return "neutral"; + return "pending"; +} + +function formatBytes(value: number): string { + if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(2)} GB`; + if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB`; + if (value >= 1024) return `${(value / 1024).toFixed(1)} KB`; + return `${value} B`; +} diff --git a/app/tests/page.tsx b/app/tests/page.tsx index 3285fe8..eed369f 100644 --- a/app/tests/page.tsx +++ b/app/tests/page.tsx @@ -1,82 +1,245 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { ShowcaseCard } from "@/app/components/ShowcaseCard"; import { SiteFooter } from "@/app/components/SiteFooter"; import { SiteHeader } from "@/app/components/SiteHeader"; -import { isClerkConfigured } from "@/lib/auth/server"; -import { listCommunityTests } from "@/lib/data/community-tests"; -import { TestCreator } from "./TestCreator"; +import { listPublicShowcaseCardsPage } from "@/lib/data/showcases"; +import { categoryLabels } from "@/lib/domain/catalog"; + +const PAGE_SIZE = 24; + +type TestFilters = { + category?: string; + contributor?: string; + harness?: string; + model?: string; + page?: string; + q?: string; + reasoning?: string; + status?: string; +}; export const metadata: Metadata = { - title: "Community tests", + title: "All Tests", description: - "Create a public test contract or choose an existing test for a model result.", + "Browse public AI Tests with their prompts, declared setup, contributor, and output evidence.", }; -export default async function TestsPage() { - const tests = await listCommunityTests().catch(() => null); +export default async function TestsPage({ + searchParams, +}: { + searchParams?: Promise; +}) { + const filters = (await searchParams) ?? {}; + const page = parsePage(filters.page); + const feedResult = await listPublicShowcaseCardsPage({ + category: filters.category, + contributor: filters.contributor, + harness: filters.harness, + limit: PAGE_SIZE, + model: filters.model, + offset: (page - 1) * PAGE_SIZE, + q: filters.q, + reasoning: filters.reasoning, + status: dataStatus(filters.status), + }).catch(() => null); + const tests = feedResult?.items ?? []; + const hasFilters = [ + filters.category, + filters.contributor, + filters.harness, + filters.model, + filters.q, + filters.reasoning, + filters.status, + ].some((value) => value?.trim()); + const hasPrevious = page > 1; + const hasNext = feedResult?.hasNext ?? false; + return (
- COMMUNITY TESTS -

Add the tests that matter.

+ ALL TESTS +

See what people tested and what the model produced.

- Define the prompt, goal, and success criteria once. Every result is - judged against that frozen test version. + Every safe Test is public with its prompt, declared setup, output + evidence, and contributor. Reviews and rankings are added later.

- {tests === null ? ( -
- Community tests are temporarily unavailable. -

- Benchmax does not show an empty catalog when the public test - records cannot be read. -

-
- ) : tests.length > 0 ? ( -
+
+ + + + + + + + +
+
+ + {tests.length} public Test{tests.length === 1 ? "" : "s"} on page{" "} + {page} + + {feedResult === null ? "Feed unavailable" : "Newest first"} +
+ {tests.length > 0 ? ( +
{tests.map((test) => ( -
-
- - - {test.title} - - - v{test.version} -
-

{test.goal}

- - Added by {test.creator ? `@${test.creator}` : "Benchmax"} ·{" "} - {test.category} - - - Submit a result → - -
+ ))} -
+
) : (
- No published community tests yet. -

Be the first contributor to freeze a test contract.

-
- )} -
-
+ + {feedResult === null + ? "All Tests is temporarily unavailable." + : hasFilters + ? "No Tests match these filters." + : "No public Tests yet."} + +

+ Safe Tests appear here as Awaiting review. They do not need a + score to be useful or public. +

- CREATE A TEST -

Turn a real prompt into a shared benchmark.

+ {hasFilters && ( + + Clear filters + + )} + + Submit a Test +
- -
+ )} + {(hasPrevious || hasNext) && ( + + )} ); } + +function parsePage(value: string | undefined): number { + if (!value || !/^\d+$/u.test(value)) return 1; + const page = Number(value); + const maxPage = Math.floor(Number.MAX_SAFE_INTEGER / PAGE_SIZE); + return Number.isSafeInteger(page) && page > 0 && page <= maxPage ? page : 1; +} + +function dataStatus( + value: string | undefined, +): "not-ranked" | "pending" | "ranked" | undefined { + if (value === "awaiting-review") return "pending"; + if (value === "reviewed") return "not-ranked"; + if (value === "ranked") return "ranked"; + return undefined; +} + +function testPageHref(filters: TestFilters, page: number): string { + const params = new URLSearchParams(); + for (const key of [ + "category", + "contributor", + "harness", + "model", + "q", + "reasoning", + "status", + ] as const) { + const value = filters[key]?.trim(); + if (value) params.set(key, value); + } + if (page > 1) params.set("page", String(page)); + const query = params.toString(); + return query ? `/tests?${query}` : "/tests"; +} diff --git a/app/upload/UploadWizard.tsx b/app/upload/UploadWizard.tsx index 551bb4d..27c7494 100644 --- a/app/upload/UploadWizard.tsx +++ b/app/upload/UploadWizard.tsx @@ -1,8 +1,7 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { SignInButton, useAuth, useUser } from "@clerk/clerk-react"; -import Link from "next/link"; type Profile = { displayName: string; @@ -11,7 +10,6 @@ type Profile = { }; type DraftFields = { - benchmarkVersionId: string; category: "frontend" | "browser-game" | "browser-3d" | "other"; declaredSettings: Record; harness: string; @@ -34,10 +32,9 @@ type UploadState = { }; const initialDraft: DraftFields = { - benchmarkVersionId: "", title: "", summary: "", - category: "frontend", + category: "other", modelLabel: "", modelVersionLabel: "", harness: "", @@ -49,32 +46,9 @@ const initialDraft: DraftFields = { declaredSettings: {}, }; -type ResultCatalog = { - tests: Array<{ - versionId: string; - title: string; - category: DraftFields["category"]; - prompt: string; - version: number; - }>; - models: Array<{ - id: string; - family: string; - provider: string; - version: string; - }>; - harnesses: Array<{ id: string; name: string; version: number }>; -}; - -export function UploadWizard({ - authConfigured, - initialTestId, -}: { - authConfigured: boolean; - initialTestId?: string; -}) { +export function UploadWizard({ authConfigured }: { authConfigured: boolean }) { if (!authConfigured) return ; - return ; + return ; } function AuthSetupNotice() { @@ -95,16 +69,12 @@ function AuthSetupNotice() { ); } -function ConfiguredUploadWizard({ initialTestId }: { initialTestId?: string }) { +function ConfiguredUploadWizard() { const { isLoaded, isSignedIn, getToken } = useAuth(); const { user: clerkUser } = useUser(); const [profile, setProfile] = useState(null); const [profileChecked, setProfileChecked] = useState(false); - const [draft, setDraft] = useState({ - ...initialDraft, - benchmarkVersionId: initialTestId ?? "", - }); - const [catalog, setCatalog] = useState(null); + const [draft, setDraft] = useState(initialDraft); const [files, setFiles] = useState([]); const [uploads, setUploads] = useState([]); const [draftId, setDraftId] = useState(null); @@ -117,38 +87,6 @@ function ConfiguredUploadWizard({ initialTestId }: { initialTestId?: string }) { [files], ); - useEffect(() => { - void (async () => { - try { - const response = await fetch("/api/results/catalog"); - const payload = (await response.json()) as { - catalog?: ResultCatalog; - error?: string; - }; - if (!response.ok || !payload.catalog) { - throw new Error(payload.error ?? "Could not load the result catalog."); - } - setCatalog(payload.catalog); - setDraft((current) => { - const tests = payload.catalog?.tests ?? []; - const selected = - tests.find( - (test) => test.versionId === current.benchmarkVersionId, - ) ?? tests[0]; - if (!selected) return current; - return { - ...current, - benchmarkVersionId: selected.versionId, - category: selected.category, - prompt: selected.prompt, - }; - }); - } catch (caught) { - setError(toMessage(caught)); - } - })(); - }, []); - async function authorizedFetch(url: string, init: RequestInit = {}) { const token = await getToken(); if (!token) throw new Error("Your session expired. Sign in again."); @@ -221,7 +159,7 @@ function ConfiguredUploadWizard({ initialTestId }: { initialTestId?: string }) { showcase?: { id: string }; }; if (!response.ok || !payload.showcase) { - throw new Error(payload.error ?? "Could not create draft."); + throw new Error(payload.error ?? "Could not create this Test."); } setDraftId(payload.showcase.id); setStep(2); @@ -318,7 +256,7 @@ function ConfiguredUploadWizard({ initialTestId }: { initialTestId?: string }) { showcase?: { slug: string }; }; if (!response.ok || !payload.showcase) { - throw new Error(payload.error ?? "Could not publish this report."); + throw new Error(payload.error ?? "Could not publish this Test."); } setPublishedSlug(payload.showcase.slug); setStep(4); @@ -347,7 +285,7 @@ function ConfiguredUploadWizard({ initialTestId }: { initialTestId?: string }) {

Sign in before sending any data.

Browsing stays public. Google, GitHub, or email-code sign-in is - required to create and own a report. + required to create and own a Test.