@verdict-ai/api 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +21 -2
  3. package/dist/app.d.ts +94 -0
  4. package/dist/app.js +120 -0
  5. package/dist/app.js.map +1 -0
  6. package/dist/github.d.ts +15 -0
  7. package/dist/github.js +40 -0
  8. package/dist/github.js.map +1 -0
  9. package/dist/hub-pages.d.ts +24 -0
  10. package/dist/hub-pages.js +119 -0
  11. package/dist/hub-pages.js.map +1 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.js +7 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/openapi.d.ts +463 -0
  16. package/dist/openapi.js +58 -0
  17. package/dist/openapi.js.map +1 -0
  18. package/dist/present.d.ts +110 -0
  19. package/dist/present.js +10 -0
  20. package/dist/present.js.map +1 -0
  21. package/dist/queue-cloudflare.d.ts +17 -0
  22. package/dist/queue-cloudflare.js +11 -0
  23. package/dist/queue-cloudflare.js.map +1 -0
  24. package/dist/rate-limit.d.ts +44 -0
  25. package/dist/rate-limit.js +40 -0
  26. package/dist/rate-limit.js.map +1 -0
  27. package/dist/routes/admin.d.ts +4 -0
  28. package/dist/routes/admin.js +81 -0
  29. package/dist/routes/admin.js.map +1 -0
  30. package/dist/routes/batches.d.ts +4 -0
  31. package/dist/routes/batches.js +27 -0
  32. package/dist/routes/batches.js.map +1 -0
  33. package/dist/routes/evaluations.d.ts +4 -0
  34. package/dist/routes/evaluations.js +104 -0
  35. package/dist/routes/evaluations.js.map +1 -0
  36. package/dist/routes/hub.d.ts +12 -0
  37. package/dist/routes/hub.js +66 -0
  38. package/dist/routes/hub.js.map +1 -0
  39. package/dist/routes/project-versions.d.ts +4 -0
  40. package/dist/routes/project-versions.js +56 -0
  41. package/dist/routes/project-versions.js.map +1 -0
  42. package/dist/routes/projects.d.ts +4 -0
  43. package/dist/routes/projects.js +91 -0
  44. package/dist/routes/projects.js.map +1 -0
  45. package/dist/routes/promotions.d.ts +4 -0
  46. package/dist/routes/promotions.js +72 -0
  47. package/dist/routes/promotions.js.map +1 -0
  48. package/dist/routes/public-hub.d.ts +41 -0
  49. package/dist/routes/public-hub.js +119 -0
  50. package/dist/routes/public-hub.js.map +1 -0
  51. package/dist/routes/publications.d.ts +4 -0
  52. package/dist/routes/publications.js +79 -0
  53. package/dist/routes/publications.js.map +1 -0
  54. package/dist/routes/reports.d.ts +4 -0
  55. package/dist/routes/reports.js +84 -0
  56. package/dist/routes/reports.js.map +1 -0
  57. package/dist/routes/rubrics.d.ts +4 -0
  58. package/dist/routes/rubrics.js +50 -0
  59. package/dist/routes/rubrics.js.map +1 -0
  60. package/dist/routes/shared.d.ts +42 -0
  61. package/dist/routes/shared.js +94 -0
  62. package/dist/routes/shared.js.map +1 -0
  63. package/dist/server.d.ts +19 -0
  64. package/dist/server.js +69 -0
  65. package/dist/server.js.map +1 -0
  66. package/dist/worker.d.ts +58 -0
  67. package/dist/worker.js +81 -0
  68. package/dist/worker.js.map +1 -0
  69. package/package.json +69 -3
  70. package/wrangler.example.toml +53 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Defines shared request handling at the REST boundary.
3
+ * Tenant context flows to every engine/store operation; validation failures use the shared HTTP error mapper.
4
+ */
5
+ import { allowAll, hasControl, loadRubricText } from "@verdict-ai/core";
6
+ import { HTTPException } from "hono/http-exception";
7
+ import { z } from "zod";
8
+ /** A refusal clients can tell apart by `type`; `extra` is merged into the error object of the response. */
9
+ export class ApiError extends Error {
10
+ status;
11
+ type;
12
+ extra;
13
+ constructor(status, type, message, extra = {}) {
14
+ super(message);
15
+ this.status = status;
16
+ this.type = type;
17
+ this.extra = extra;
18
+ }
19
+ }
20
+ /** Reads a JSON body of at most `maxBytes`; 413 when larger, 400 when it is not JSON. An empty body reads as `{}`. */
21
+ export async function readJson(req, maxBytes = 100_000) {
22
+ const text = await req.text();
23
+ if (new TextEncoder().encode(text).length > maxBytes)
24
+ throw new HTTPException(413, { message: `the request may be at most ${maxBytes} bytes` });
25
+ if (!text.trim())
26
+ return {};
27
+ try {
28
+ return JSON.parse(text);
29
+ }
30
+ catch {
31
+ throw new HTTPException(400, { message: "the request body must be JSON" });
32
+ }
33
+ }
34
+ /** Decodes JSON or YAML rubric bodies; rejects read, syntax and rubric validation failures. */
35
+ export async function readRubricBody(req) {
36
+ const ct = req.headers.get("content-type") ?? "";
37
+ const text = await req.text();
38
+ if (ct.includes("yaml") || ct.includes("text/plain"))
39
+ return loadRubricText(text);
40
+ return JSON.parse(text);
41
+ }
42
+ /** Returns an optional idempotency key; throws ZodError when its length is outside 1–255 characters. */
43
+ export function readIdempotencyKey(req) {
44
+ const key = req.headers.get("Idempotency-Key");
45
+ return key === null ? undefined : z.string().min(1).max(255).parse(key);
46
+ }
47
+ /** Returns the authenticated tenant, or undefined for open local deployments. */
48
+ export const tenant = (c) => c.get("tenantId") || undefined;
49
+ /** Documents leave without the tenant they are stored under. */
50
+ export function bare(doc) {
51
+ const { tenantId: _tenantId, ...rest } = doc;
52
+ return rest;
53
+ }
54
+ const REFUSAL_MESSAGES = {
55
+ forbidden: "this key is not allowed to do that",
56
+ limit_exceeded: "this account's plan does not allow that",
57
+ approval_required: "this change needs an approval first",
58
+ };
59
+ /** What a refusal adds to the error object. It never replaces what says which refusal this is. */
60
+ function refusalDetails(details = {}) {
61
+ const { type: _type, message: _message, ...rest } = details;
62
+ return rest;
63
+ }
64
+ /**
65
+ * What every control-plane route works with: the store, the project a request is about, and `allow`, the one place a
66
+ * request is allowed or refused (docs/design/control-plane.md §9.1). Routes call `allow` before they read or write.
67
+ */
68
+ export function controlContext({ engine, authorize = allowAll, now = () => Date.now() }) {
69
+ const store = () => {
70
+ if (!hasControl(engine.store))
71
+ throw new HTTPException(501, { message: "this server's store cannot keep projects" });
72
+ return engine.store.control;
73
+ };
74
+ const scope = (c) => {
75
+ const tenantId = tenant(c), project = c.req.param("project") ?? "";
76
+ return tenantId === undefined ? { project } : { tenantId, project };
77
+ };
78
+ const allow = async (c, action, target = {}) => {
79
+ const claims = c.get("claims");
80
+ const decision = await authorize({ principal: { tenantId: tenant(c), actor: c.get("actor"), ...(claims ? { claims } : {}) }, action, target });
81
+ if (!decision.allow)
82
+ throw new ApiError(403, decision.reason, decision.message ?? REFUSAL_MESSAGES[decision.reason], refusalDetails(decision.details));
83
+ };
84
+ /** 404 unless the project of the request exists. */
85
+ const project = async (c) => {
86
+ const s = scope(c);
87
+ if (!(await store().getProject(s.tenantId, s.project)))
88
+ throw projectNotFound(s.project);
89
+ return s;
90
+ };
91
+ return { store, scope, allow, project, at: () => new Date(now()).toISOString(), now };
92
+ }
93
+ export const projectNotFound = (name) => new ApiError(404, "not_found", `project ${name} not found`);
94
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/routes/shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { Engine, MemoryQueue, type Store } from "@verdict-ai/core";
2
+ export interface ServerOptions {
3
+ port?: number;
4
+ dataDir?: string;
5
+ database?: string;
6
+ allowLocalFiles?: boolean;
7
+ store?: Store;
8
+ queue?: MemoryQueue;
9
+ loadDatabase?: (kind: "postgres" | "sqlite") => Promise<{
10
+ open: typeof import("@verdict-ai/db/postgres").fromPostgres | typeof import("@verdict-ai/db/sqlite").fromSqliteFile;
11
+ }>;
12
+ }
13
+ /** Starts the Node server using supplied or environment-selected adapters; startup failures reject. */
14
+ export declare function startServer(opts?: ServerOptions): Promise<{
15
+ app: import("hono").Hono<import("./app.js").Env, import("hono/types").BlankSchema, "/">;
16
+ engine: Engine;
17
+ server: import("@hono/node-server").ServerType;
18
+ port: number;
19
+ }>;
package/dist/server.js ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Starts the Node HTTP surface with environment-selected persistence.
3
+ * Database keys retain bootstrap authentication; shutdown drains queued work before exiting.
4
+ */
5
+ import { serve } from "@hono/node-server";
6
+ import { lazyPlaywrightScreenshot, Engine, MemoryQueue, FileStore, MemoryStore } from "@verdict-ai/core";
7
+ import { createApp, envAuth, routeGroups } from "./app.js";
8
+ // Keep environment interpretation at startup so storage and queue dependencies can be supplied by tests.
9
+ function serverConfig(opts, env = process.env) {
10
+ return {
11
+ port: opts.port ?? Number(env.PORT ?? 8787),
12
+ dataDir: opts.dataDir ?? env.VERDICT_DATA_DIR,
13
+ database: opts.database ?? env.VERDICT_DATABASE,
14
+ allowLocalFiles: opts.allowLocalFiles ?? env.VERDICT_ALLOW_LOCAL_FILES === "1",
15
+ // why: a headless browser per request is costly and fetches arbitrary URLs; servers opt in explicitly.
16
+ screenshots: env.VERDICT_SCREENSHOTS === "1",
17
+ log: env.VERDICT_LOG !== "0", apiKeys: env.VERDICT_API_KEYS, adminKey: env.VERDICT_ADMIN_KEY,
18
+ github: env.GITHUB_CLIENT_ID ? { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET } : undefined,
19
+ routes: routeGroups(env.VERDICT_ROUTES),
20
+ };
21
+ }
22
+ /** Starts the Node server using supplied or environment-selected adapters; startup failures reject. */
23
+ export async function startServer(opts = {}) {
24
+ const config = serverConfig(opts);
25
+ const { port, dataDir, database } = config;
26
+ let store;
27
+ let auth = envAuth();
28
+ let keys;
29
+ if (opts.store)
30
+ store = opts.store;
31
+ else if (database) {
32
+ const kind = /^postgres(ql)?:\/\//.test(database) ? "postgres" : "sqlite";
33
+ const { open } = await (opts.loadDatabase ?? loadDatabase)(kind);
34
+ // Env keys stay valid as a bootstrap, but open mode (no VERDICT_API_KEYS) must not bypass the DB keys.
35
+ const h = await open(database, config.apiKeys ? envAuth() : undefined);
36
+ store = h.store;
37
+ auth = h.auth;
38
+ keys = h.auth;
39
+ }
40
+ else
41
+ store = dataDir ? new FileStore(dataDir) : new MemoryStore();
42
+ const queue = opts.queue ?? new MemoryQueue();
43
+ const engine = new Engine({ store, queue, ingest: { allowLocalFiles: config.allowLocalFiles, screenshot: config.screenshots ? lazyPlaywrightScreenshot() : undefined } });
44
+ queue.engine = engine;
45
+ const app = createApp({ engine, auth, keys, log: config.log, github: config.github, clientKey: socketAddress, routes: config.routes });
46
+ const server = serve({ fetch: app.fetch, port }, (info) => {
47
+ const storeName = database ? (/^postgres(ql)?:\/\//.test(database) ? "postgres" : `sqlite:${database}`) : dataDir ? `file:${dataDir}` : "memory";
48
+ console.log(`verdict api listening on http://localhost:${info.port} (store: ${storeName}, auth: ${database ? "db + env keys" : config.apiKeys ? "env keys" : "open"}${config.adminKey && keys ? ", admin on" : ""})`);
49
+ });
50
+ process.once("SIGTERM", () => {
51
+ // why: retry timers are unreferenced; keep Node alive until delayed work drains during graceful shutdown.
52
+ const keepAlive = setInterval(() => { }, 1000);
53
+ server.close(() => { void queue.drain().then(() => { clearInterval(keepAlive); process.exit(0); }); });
54
+ });
55
+ return { app, engine, server, port };
56
+ }
57
+ // Driver imports belong at startup so the Node factory can be exercised without native modules.
58
+ async function loadDatabase(kind) {
59
+ if (kind === "postgres")
60
+ return { open: (await import("@verdict-ai/db/postgres")).fromPostgres };
61
+ return { open: (await import("@verdict-ai/db/sqlite")).fromSqliteFile };
62
+ }
63
+ if (import.meta.url === `file://${process.argv[1]}`)
64
+ void startServer();
65
+ /** The Node server limits per socket address: behind a proxy that is the proxy's, so put limits at the proxy too. */
66
+ function socketAddress(c) {
67
+ return c.env?.incoming?.socket?.remoteAddress ?? "anonymous";
68
+ }
69
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Builds Workers-compatible engines and consumes Cloudflare queue messages.
3
+ * D1 reservations commit before judging; failed queue attempts must be acknowledged or retried explicitly.
4
+ */
5
+ import { Engine, type DecisionProviderAdapter, type Job } from "@verdict-ai/core";
6
+ import { type CloudflareRateLimit, type HubLimits } from "./rate-limit.js";
7
+ export interface Env {
8
+ [name: string]: unknown;
9
+ DB?: D1Database;
10
+ QUEUE?: Pick<Queue<Job>, "send">;
11
+ /** Workers AI binding: Clef / Clef-flash run in-network without a REST token. */
12
+ AI?: Pick<Ai, "run">;
13
+ /** Workers Rate Limiting bindings for the hub; without them, counts are kept per isolate. */
14
+ HUB_LOGIN_LIMITER?: CloudflareRateLimit;
15
+ HUB_PUBLISH_LIMITER?: CloudflareRateLimit;
16
+ HUB_READ_LIMITER?: CloudflareRateLimit;
17
+ }
18
+ /** Hub limits: a Cloudflare binding where configured, else this isolate's counters. */
19
+ export declare function hubLimits(env: Env): HubLimits;
20
+ /**
21
+ * Decision provider "cloudflare" backed by the Workers AI binding instead of the REST API.
22
+ * Same System One body (`model` selector, `state`, `questions`); the binding returns the inner result directly.
23
+ */
24
+ export declare function aiBindingProvider(ai: NonNullable<Env["AI"]>): DecisionProviderAdapter;
25
+ /** Shared by HTTP producers and queue consumers. */
26
+ export declare function buildEngine(env: Env): {
27
+ engine: Engine;
28
+ auth: import("./app.js").Auth | import("@verdict-ai/db").DrizzleAuth;
29
+ keys: import("@verdict-ai/db").DrizzleAuth | undefined;
30
+ adminKey: string;
31
+ github: {
32
+ clientId: string;
33
+ clientSecret: string;
34
+ } | undefined;
35
+ limits: HubLimits;
36
+ routes: readonly ("evaluations" | "rubrics" | "hub" | "control")[];
37
+ };
38
+ export interface JobMessage {
39
+ body: Job;
40
+ ack(): void;
41
+ retry(options: {
42
+ delaySeconds: number;
43
+ }): void;
44
+ }
45
+ /** Returns a queue consumer; retryable jobs retry and terminal jobs are acknowledged. */
46
+ export declare function createQueueHandler(build?: typeof buildEngine): (batch: Pick<MessageBatch<Job>, "messages"> | {
47
+ messages: readonly JobMessage[];
48
+ }, env: Env, _ctx?: ExecutionContext) => Promise<void>;
49
+ export declare const queue: (batch: Pick<MessageBatch<Job>, "messages"> | {
50
+ messages: readonly JobMessage[];
51
+ }, env: Env, _ctx?: ExecutionContext) => Promise<void>;
52
+ declare const _default: {
53
+ fetch(request: Request, env: Env, ctx: Pick<ExecutionContext, "waitUntil">): Response | Promise<Response>;
54
+ queue: (batch: Pick<MessageBatch<Job>, "messages"> | {
55
+ messages: readonly JobMessage[];
56
+ }, env: Env, _ctx?: ExecutionContext) => Promise<void>;
57
+ };
58
+ export default _default;
package/dist/worker.js ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Builds Workers-compatible engines and consumes Cloudflare queue messages.
3
+ * D1 reservations commit before judging; failed queue attempts must be acknowledged or retried explicitly.
4
+ */
5
+ import { CLEF_LIMITS, Engine, MemoryStore, RetryableError, checkClefImages, registerDecisionProvider } from "@verdict-ai/core";
6
+ import { fromD1 } from "@verdict-ai/db";
7
+ import { createApp, envAuth, routeGroups } from "./app.js";
8
+ import { CloudflareQueue } from "./queue-cloudflare.js";
9
+ import { cloudflareLimiter, defaultHubLimits } from "./rate-limit.js";
10
+ const memory = new MemoryStore();
11
+ // An app is built per request; the fallback counters must outlive it to count anything.
12
+ const isolateLimits = defaultHubLimits();
13
+ /** Hub limits: a Cloudflare binding where configured, else this isolate's counters. */
14
+ export function hubLimits(env) {
15
+ return {
16
+ login: env.HUB_LOGIN_LIMITER ? cloudflareLimiter(env.HUB_LOGIN_LIMITER) : isolateLimits.login,
17
+ publish: env.HUB_PUBLISH_LIMITER ? cloudflareLimiter(env.HUB_PUBLISH_LIMITER) : isolateLimits.publish,
18
+ read: env.HUB_READ_LIMITER ? cloudflareLimiter(env.HUB_READ_LIMITER) : isolateLimits.read,
19
+ };
20
+ }
21
+ /**
22
+ * Decision provider "cloudflare" backed by the Workers AI binding instead of the REST API.
23
+ * Same System One body (`model` selector, `state`, `questions`); the binding returns the inner result directly.
24
+ */
25
+ export function aiBindingProvider(ai) {
26
+ return {
27
+ name: "cloudflare",
28
+ acceptsImages: true,
29
+ maxImages: CLEF_LIMITS.maxImages,
30
+ async decide(req) {
31
+ const model = req.model ?? "@cf/cloudflare/clef-flash";
32
+ const extra = req.options?.request;
33
+ const extras = extra && typeof extra === "object" && !Array.isArray(extra) ? { ...extra } : {};
34
+ // Same image rules as the REST adapter: rubric-supplied images first, then the evidence's own.
35
+ const images = [...(Array.isArray(extras.images) ? extras.images.filter((x) => typeof x === "string") : []), ...(req.images ?? [])];
36
+ checkClefImages(images);
37
+ const body = { model: req.options?.selector ?? model.split("/").pop(), state: req.state, questions: req.questions, ...extras, ...(images.length ? { images } : {}) };
38
+ const data = (await ai.run(model, body));
39
+ return { answers: data.answers ?? {}, model, usage: { inputTokens: data.usage?.input_tokens ?? data.usage?.prompt_tokens ?? 0, outputTokens: data.usage?.output_tokens ?? 0 }, raw: data };
40
+ },
41
+ };
42
+ }
43
+ /** Shared by HTTP producers and queue consumers. */
44
+ export function buildEngine(env) {
45
+ const vars = Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
46
+ const envKeys = envAuth(vars);
47
+ const d1 = env.DB ? fromD1(env.DB, vars.VERDICT_API_KEYS ? envKeys : undefined) : undefined;
48
+ if (env.AI)
49
+ registerDecisionProvider(aiBindingProvider(env.AI));
50
+ const engine = new Engine({ store: d1?.store ?? memory, queue: env.QUEUE ? new CloudflareQueue(env.QUEUE) : undefined, env: n => vars[n], webhookSecret: vars.VERDICT_WEBHOOK_SECRET });
51
+ const github = vars.GITHUB_CLIENT_ID ? { clientId: vars.GITHUB_CLIENT_ID, clientSecret: vars.GITHUB_CLIENT_SECRET } : undefined;
52
+ return { engine, auth: d1?.auth ?? envKeys, keys: d1?.auth, adminKey: vars.VERDICT_ADMIN_KEY, github, limits: hubLimits(env), routes: routeGroups(vars.VERDICT_ROUTES) };
53
+ }
54
+ /** Returns a queue consumer; retryable jobs retry and terminal jobs are acknowledged. */
55
+ export function createQueueHandler(build = buildEngine) {
56
+ return async (batch, env, _ctx) => {
57
+ const { engine } = build(env);
58
+ await Promise.all(batch.messages.map(async (message) => {
59
+ try {
60
+ await engine.processJob(message.body);
61
+ message.ack();
62
+ }
63
+ catch (error) {
64
+ if (error instanceof RetryableError)
65
+ message.retry({ delaySeconds: Math.ceil((error.retryAfterMs ?? 0) / 1000) });
66
+ // why: permanent failures must not poison the queue through endless redelivery.
67
+ else
68
+ message.ack();
69
+ }
70
+ }));
71
+ };
72
+ }
73
+ export const queue = createQueueHandler();
74
+ export default {
75
+ fetch(request, env, ctx) {
76
+ const app = createApp({ ...buildEngine(env), background: promise => ctx.waitUntil(promise) });
77
+ return app.fetch(request);
78
+ },
79
+ queue,
80
+ };
81
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,72 @@
1
1
  {
2
2
  "name": "@verdict-ai/api",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Verdict REST API (Hono). Runs on Node and Cloudflare Workers.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./server": {
14
+ "types": "./dist/server.d.ts",
15
+ "import": "./dist/server.js"
16
+ },
17
+ "./worker": {
18
+ "types": "./dist/worker.d.ts",
19
+ "import": "./dist/worker.js"
20
+ }
21
+ },
22
+ "dependencies": {
23
+ "@hono/node-server": "^2.1.6",
24
+ "hono": "^4.13.16",
25
+ "zod": "^4.6.7",
26
+ "@verdict-ai/core": "0.2.0",
27
+ "@verdict-ai/db": "0.2.0"
28
+ },
29
+ "devDependencies": {
30
+ "@cloudflare/workers-types": "^5.20261104.1"
31
+ },
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/Ustay-coder/verdict.git",
36
+ "directory": "packages/api"
37
+ },
38
+ "homepage": "https://github.com/Ustay-coder/verdict#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/Ustay-coder/verdict/issues"
41
+ },
42
+ "keywords": [
43
+ "evaluation",
44
+ "rubric",
45
+ "decision-model",
46
+ "jev",
47
+ "clef",
48
+ "scoring",
49
+ "llm-judge",
50
+ "mcp"
51
+ ],
52
+ "engines": {
53
+ "node": ">=20"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "files": [
59
+ "dist",
60
+ "README.md",
61
+ "LICENSE",
62
+ "wrangler.example.toml"
63
+ ],
64
+ "sideEffects": false,
65
+ "scripts": {
66
+ "build": "tsc -p tsconfig.json",
67
+ "typecheck": "tsc -p tsconfig.json --noEmit",
68
+ "test": "vitest run",
69
+ "dev": "node --watch dist/server.js",
70
+ "start": "node dist/server.js"
71
+ }
6
72
  }
@@ -0,0 +1,53 @@
1
+ # Cloudflare Workers deployment (copy to wrangler.toml). Build first: pnpm build
2
+ name = "verdict-api"
3
+ main = "dist/worker.js"
4
+ compatibility_date = "2026-10-01"
5
+ compatibility_flags = ["nodejs_compat"]
6
+
7
+ # Persistence: Cloudflare D1 through Drizzle. Create with `wrangler d1 create verdict`, paste the id below, then
8
+ # wrangler d1 migrations apply verdict --local # dev
9
+ # wrangler d1 migrations apply verdict --remote # prod
10
+ [[d1_databases]]
11
+ binding = "DB"
12
+ database_name = "verdict"
13
+ database_id = "<paste-from-wrangler-d1-create>"
14
+ migrations_dir = "../db/drizzle"
15
+
16
+ # Secrets (wrangler secret put <NAME>):
17
+ # TYPESAFE_API_KEY Jev (default judge)
18
+ # CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN Clef / Clef-flash via Workers AI REST
19
+ # VERDICT_API_KEYS bootstrap tenant keys, "key:tenant,..." (DB-managed keys take precedence)
20
+ # VERDICT_ADMIN_KEY enables /v1/admin/* (key management, hub takedowns)
21
+ # VERDICT_WEBHOOK_SECRET HMAC for webhook deliveries
22
+ # GITHUB_CLIENT_ID hub sign-in: OAuth App client id (enable "Device Flow" in the app's settings)
23
+ # GITHUB_CLIENT_SECRET hub sign-in: lets the hub check a token was minted for this app (recommended)
24
+
25
+ # Hub limits shared across isolates (Workers Rate Limiting API; period is 10 or 60 seconds; `wrangler dev` simulates them). Without these bindings the
26
+ # same limits are counted per isolate, which is a floor, not a guarantee. namespace_id: any number unique in your account.
27
+ # [[ratelimits]]
28
+ # name = "HUB_LOGIN_LIMITER"
29
+ # namespace_id = "1001"
30
+ # simple = { limit = 10, period = 60 }
31
+ # [[ratelimits]]
32
+ # name = "HUB_PUBLISH_LIMITER"
33
+ # namespace_id = "1002"
34
+ # simple = { limit = 30, period = 60 }
35
+ # [[ratelimits]]
36
+ # name = "HUB_READ_LIMITER"
37
+ # namespace_id = "1003"
38
+ # simple = { limit = 300, period = 60 }
39
+
40
+ # Clef inference through the Worker binding (no REST token needed):
41
+ [ai]
42
+ binding = "AI"
43
+
44
+ # Create verdict-jobs and verdict-dlq before enabling (Queues plan required):
45
+ # [[queues.producers]]
46
+ # binding = "QUEUE"
47
+ # queue = "verdict-jobs"
48
+ # [[queues.consumers]]
49
+ # queue = "verdict-jobs"
50
+ # max_retries = 5
51
+ # max_batch_size = 10
52
+ # max_concurrency = 4
53
+ # dead_letter_queue = "verdict-dlq"