The create-time replay is the only thing that rated a public sim — if it died (CPU limit, crash) the sim was orphaned forever: no metrics, no rating, and public sims reject client backfill. On prod 44 of 57 public sims were orphaned this way, which also explains leaderboards showing W-L records with zero battles (only quick, often battle-free duels survived to the board). Now a metrics GET on an unrated public sim re-triggers the server compute in the background, and the nightly cron retries a bounded batch. Both paths are idempotent (metrics first-write, rating_events PK). Also: the sign-in card now carries the agent pointer block (auth.md, llms.txt, MCP, REST) so an agent landing on a login wall knows exactly where to go instead of panicking.
- commit
- f30c0b28fe0f2e0e48e4906207e9c3805cd963ef
- parent
- b605f6b60db88e287e1073dcb058cee7e0fa8777
- author
- Dami <dami.etoile@gmail.com>
| M | web/ | 11 | +++++++++++ |
| M | worker/ | 8 | +++++++- |
| M | worker/ | 15 | ++++++++++++++- |
| M | worker/ | 26 | ++++++++++++++++++++++++++ |
@@ -43,6 +43,17 @@ export function signInCard(ctx: AppCtx): HTMLElement {
4343 { class: 'card' },4444 h('h2', null, 'Sign in'),4545 h('p', { class: 'muted' }, 'Sign in to manage your strategies, run sims, and share to the leaderboard. Sim share links are public — no account needed to watch.'),46+ h(47+ 'pre',48+ { class: 'snippet' },49+ [50+ '# Agents: no signup - mint an ed25519 keypair and sign in with it.',51+ `# how to ${location.origin}/auth.md (identity file + mint snippet)`,52+ `# docs ${location.origin}/llms.txt (full scripting reference)`,53+ `# MCP ${location.origin}/mcp (streamable HTTP, bearer token)`,54+ `# REST ${location.origin}/api/*`,55+ ].join('\n'),56+ ),4657 h(4758 'p',4859 null,@@ -4,7 +4,7 @@
44 * daily TTL sweep cron. Routes in run_worker_first always reach this code.55 */66 import { corsPreflight, handleApi } from './router';7-import { backfillR2, runSweep } from './sweep';7+import { backfillR2, retryDeadPublicSims, runSweep } from './sweep';88 import { runLadder } from './ladder';99 import { syncModels } from './models-sync';1010 import type { Env } from './env';@@ -70,6 +70,12 @@ export default {
7070 } catch (err) {7171 console.warn('models sync failed:', err);7272 }73+ try {74+ const retried = await retryDeadPublicSims(env);75+ if (retried > 0) console.log(JSON.stringify({ event: 'sim_retry', retried }));76+ } catch (err) {77+ console.warn('dead-sim retry failed:', err);78+ }7379 try {7480 await runLadder(env);7581 } catch (err) {@@ -28,6 +28,7 @@ import type { Env } from './env';
2828 import { EXAMPLES, exampleHashes } from './examples';2929 import {3030 METRICS_MAX_GZ_BYTES,31+ computeSimMetrics,3132 getStoredMetricsGz,3233 gunzipJson,3334 looksLikeStoredMetrics,@@ -43,6 +44,7 @@ import {
4344 verifyShooIdToken,4445 } from './auth';4546 import {47+ getSimById,4648 getUserById,4749 listIdentities,4850 removeIdentity,@@ -232,7 +234,18 @@ export async function handleApi(
232234 const simId = metricsMatch[1];233235 if (method === 'GET') {234236 const gz = await getStoredMetricsGz(env, simId);235- if (gz === null) return err('not_found', 'metrics not computed yet', 404);237+ if (gz === null) {238+ // Self-healing: the create-time replay can die (CPU limit, crash)239+ // and public sims reject client backfill — so a missing result240+ // re-triggers the server compute in the background. Idempotent241+ // (metrics first-write + rating_events PK), so repeat views are242+ // harmless while it finishes.243+ const sim = await getSimById(env.DB, simId, null);244+ if (sim !== null && sim.visibility === 'public' && deps.background) {245+ deps.background(computeSimMetrics(env, simId));246+ }247+ return err('not_found', 'metrics not computed yet', 404);248+ }236249 // Stored pre-gzipped; the browser transparently decompresses.237250 // encodeBody 'manual' tells workerd the body is ALREADY encoded per238251 // the content-encoding header (default 'automatic' would mangle it).@@ -19,6 +19,7 @@ import type { Env } from './env';
1919 import { nowSec, TRASH_SECONDS } from './util';2020 import { listedWhere } from './db';2121 import { deleteSources, putMetricsGzIfAbsent, putSource, replayKeyFor } from './r2blobs';22+import { computeSimMetrics } from './simrun';2223 2324 /** Exported for tests: snapshot/assert these without a D1 instance. */2425 export const SWEEP_SQL = {@@ -130,3 +131,28 @@ export async function backfillR2(env: Env): Promise<{ sources: number; metrics:
130131 }131132 return { sources, metrics };132133 }134+135+/**136+ * Rated sims whose create-time replay died (CPU limit, crash) never got137+ * metrics or rating events, and public sims reject client backfill — so the138+ * cron retries a bounded batch each night. Metrics-GET views retry too;139+ * this catches the ones nobody looked at.140+ */141+export async function retryDeadPublicSims(env: Env, limit = 8): Promise<number> {142+ const rows = await env.DB143+ .prepare(144+ `SELECT id FROM sims145+ WHERE visibility = 'public'146+ AND created_at < ?1147+ AND NOT EXISTS (SELECT 1 FROM rating_events WHERE rating_events.sim_id = sims.id)148+ ORDER BY created_at DESC LIMIT ?2`,149+ )150+ .bind(nowSec() - 900, limit)151+ .all<{ id: string }>();152+ let retried = 0;153+ for (const row of rows.results) {154+ await computeSimMetrics(env, row.id); // internally idempotent + fault-tolerant155+ retried++;156+ }157+ return retried;158+}