From 45d577db5bc8f3fa7f51cf614783a87a88538207 Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Tue, 20 Oct 2026 22:53:23 +0000 Subject: [PATCH] =?UTF-8?q?inference:=20quota=20counters=20cost=20one=20KV?= =?UTF-8?q?=20write=20per=20call=20and=20fail=20open=20=E2=80=94=20free-ti?= =?UTF-8?q?er=20KV=20(1,000=20writes/day)=20ran=20out=20and=20answered=205?= =?UTF-8?q?00=20for=20every=20call?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6 --- .github/time/cloudflare/inference.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/time/cloudflare/inference.js b/.github/time/cloudflare/inference.js index 17f8650..160ed8a 100644 --- a/.github/time/cloudflare/inference.js +++ b/.github/time/cloudflare/inference.js @@ -45,18 +45,19 @@ async function who(req, app) { const raw = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${app}:${ip}`)); return [...new Uint8Array(raw).slice(0, 8)].map((b) => b.toString(16).padStart(2, "0")).join(""); } -// One counter per (day, app, kind, person) and one per (day, app, kind) in KV: over either limit means no. KV is +// One counter per (day, app, person) in KV holding both kinds, so a call costs one read and one write: free-tier KV +// allows 1,000 writes a day and two per call ran out. The app-wide total is the sum the counter can't see, so it is +// approximated as perPerson x people seen today only when KV is on a paid plan; here it is not enforced. KV is // eventually consistent, so two calls in the same instant can both count; that only ever over-counts, never under. +// A KV failure (limit exceeded, outage) never blocks a call: the counter is bookkeeping, the music is the point. async function spend(env, app, kind, person, perPerson, total) { - const day = new Date().toISOString().slice(0, 10); - const bump = async (k) => { - const n = Number((await env.QUOTA.get(k)) || 0) + 1; - await env.QUOTA.put(k, String(n), { expirationTtl: 2 * 86400 }); - return n; - }; - const [mine, all] = await Promise.all([bump(`${day}:${app}:${kind}:${person}`), bump(`${day}:${app}:${kind}:*`)]); - if (mine > perPerson) return "you're over today's limit for this app; add your own Venice key or come back tomorrow"; - if (all > total) return "this app is over its daily budget; add your own Venice key or come back tomorrow"; + try { + const k = `${new Date().toISOString().slice(0, 10)}:${app}:${person}`; + const c = JSON.parse((await env.QUOTA.get(k)) || "{}"); + c[kind] = (c[kind] || 0) + 1; + await env.QUOTA.put(k, JSON.stringify(c), { expirationTtl: 2 * 86400 }); + if (c[kind] > perPerson) return "you're over today's limit for this app; come back tomorrow"; + } catch (e) { console.log("quota skipped: " + String(e).slice(0, 120)); } return ""; }