@verdict-ai/api 0.0.0-stage → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Verdict contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,22 @@
1
- # Temporary Holding Version
1
+ # @verdict-ai/api
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Hono REST API for Node and Cloudflare Workers.
4
+
5
+ ```bash
6
+ npm install @verdict-ai/api @verdict-ai/core
7
+ ```
8
+
9
+ ```ts
10
+ import { Engine, MemoryStore } from "@verdict-ai/core";
11
+ import { createApp } from "@verdict-ai/api";
12
+ const engine = new Engine({ store: new MemoryStore() });
13
+ const app = createApp({ engine });
14
+ const response = await app.request("/v1/health");
15
+ console.log(response.status);
16
+ console.log(await response.json());
17
+ // Export app.fetch for a compatible HTTP runtime.
18
+ // Configure authentication before exposing a public service.
19
+ export default app;
20
+ ```
21
+
22
+ Requires Node 20 or newer. [Documentation](https://verdict-docs.developerolim.workers.dev/reference/rest). MIT licensed.
package/dist/app.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Assembles authenticated REST routes around an injected engine.
3
+ * Request bodies are validated at the boundary; Workers imports must remain free of eager Node I/O.
4
+ */
5
+ import { Engine } from "@verdict-ai/core";
6
+ import { Hono } from "hono";
7
+ export interface Auth {
8
+ /** Map an API key to a tenant id, or undefined to reject. Return "" for single-tenant open mode. */
9
+ resolve(apiKey: string | undefined): Promise<string | undefined> | string | undefined;
10
+ }
11
+ /** `VERDICT_API_KEYS="key1:tenantA,key2:tenantB"`; a bare key maps to tenant "default". Unset = open (single tenant). */
12
+ export declare function envAuth(env?: Record<string, string | undefined>): Auth;
13
+ /** Optional API-key management (backed by @verdict-ai/db's DrizzleAuth). Exposed under /v1/admin with VERDICT_ADMIN_KEY. */
14
+ export interface KeyManager {
15
+ createKey(tenantId: string, name: string): Promise<{
16
+ id: string;
17
+ key: string;
18
+ prefix: string;
19
+ }>;
20
+ revokeKey(id: string, tenantId?: string): Promise<boolean>;
21
+ listKeys(tenantId: string): Promise<Array<{
22
+ id: string;
23
+ name: string;
24
+ prefix: string;
25
+ createdAt: string;
26
+ lastUsedAt: string | null;
27
+ revokedAt: string | null;
28
+ }>>;
29
+ }
30
+ export interface AppOptions {
31
+ engine: Engine;
32
+ auth?: Auth;
33
+ keys?: KeyManager;
34
+ /** Bearer token for /v1/admin/* (default: env VERDICT_ADMIN_KEY). Unset = admin routes disabled. */
35
+ adminKey?: string;
36
+ /** Return 202 immediately and finish in the background when the client asks (`"async": true`). */
37
+ background?: (promise: Promise<unknown>) => void;
38
+ allowAsync?: boolean;
39
+ log?: boolean;
40
+ }
41
+ export type Env = {
42
+ Variables: {
43
+ tenantId: string;
44
+ };
45
+ };
46
+ /** Builds the REST app around an engine; route failures are translated into HTTP error responses. */
47
+ export declare function createApp({ engine, auth, keys, adminKey, allowAsync, background, log }: AppOptions): Hono<Env, import("hono/types").BlankSchema, "/">;
48
+ export { present } from "./present.js";
package/dist/app.js ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Assembles authenticated REST routes around an injected engine.
3
+ * Request bodies are validated at the boundary; Workers imports must remain free of eager Node I/O.
4
+ */
5
+ import { ConflictError, NotFoundError, ValidationError } from "@verdict-ai/core";
6
+ import { Hono } from "hono";
7
+ import { cors } from "hono/cors";
8
+ import { HTTPException } from "hono/http-exception";
9
+ import { logger } from "hono/logger";
10
+ import { z } from "zod";
11
+ import { openapi } from "./openapi.js";
12
+ import { registerAdmin } from "./routes/admin.js";
13
+ import { registerBatches } from "./routes/batches.js";
14
+ import { registerEvaluations } from "./routes/evaluations.js";
15
+ import { registerRubrics } from "./routes/rubrics.js";
16
+ /** `VERDICT_API_KEYS="key1:tenantA,key2:tenantB"`; a bare key maps to tenant "default". Unset = open (single tenant). */
17
+ export function envAuth(env = globalThis.process?.env ?? {}) {
18
+ const raw = env.VERDICT_API_KEYS?.trim();
19
+ if (!raw)
20
+ return { resolve: () => "default" };
21
+ const keys = new Map(raw.split(",").map((pair) => { const [k, t] = pair.trim().split(":"); return [k, t ?? "default"]; }));
22
+ return { resolve: (k) => (k ? keys.get(k) : undefined) };
23
+ }
24
+ /** Builds the REST app around an engine; route failures are translated into HTTP error responses. */
25
+ export function createApp({ engine, auth = envAuth(), keys, adminKey = globalThis.process?.env?.VERDICT_ADMIN_KEY, allowAsync = true, background, log = false }) {
26
+ const app = new Hono();
27
+ if (log)
28
+ app.use(logger());
29
+ app.use("*", cors({ exposeHeaders: ["Idempotency-Replayed"] }));
30
+ app.get("/", (c) => c.json({ name: "verdict", version: "0.1.0", docs: "/v1/openapi.json" }));
31
+ app.get("/v1/health", (c) => c.json({ ok: true }));
32
+ app.get("/v1/openapi.json", (c) => c.json(openapi));
33
+ // why: admin routes use their own credential and must be registered before tenant authentication.
34
+ registerAdmin(app, { engine, keys, adminKey, allowAsync, background });
35
+ app.use("/v1/*", async (c, next) => {
36
+ if (c.req.path === "/v1/health" || c.req.path === "/v1/openapi.json" || c.req.path.startsWith("/v1/admin/"))
37
+ return next();
38
+ const header = c.req.header("authorization");
39
+ const key = header?.replace(/^Bearer\s+/i, "") ?? c.req.header("x-api-key");
40
+ const tenant = await auth.resolve(key);
41
+ if (tenant === undefined)
42
+ throw new HTTPException(401, { message: "invalid or missing API key" });
43
+ c.set("tenantId", tenant);
44
+ await next();
45
+ });
46
+ app.onError((err, c) => {
47
+ if (err instanceof HTTPException)
48
+ return c.json({ error: { type: "http", message: err.message } }, err.status);
49
+ if (err instanceof z.ZodError)
50
+ return c.json({ error: { type: "validation", message: "invalid request", issues: err.issues } }, 400);
51
+ if (err instanceof NotFoundError)
52
+ return c.json({ error: { type: "not_found", message: err.message } }, 404);
53
+ if (err instanceof ConflictError)
54
+ return c.json({ error: { type: "idempotency_conflict", message: err.message } }, 409);
55
+ if (err instanceof ValidationError)
56
+ return c.json({ error: { type: "validation", message: err.message } }, 400);
57
+ const message = err.message ?? "internal error";
58
+ // why: legacy rubric validators still throw plain errors; preserve their established 400 responses.
59
+ const status = /duplicate|unknown|requires|must/i.test(message) ? 400 : 500;
60
+ return c.json({ error: { type: status === 400 ? "validation" : "internal", message } }, status);
61
+ });
62
+ registerRubrics(app, { engine, keys, adminKey, allowAsync, background });
63
+ registerEvaluations(app, { engine, keys, adminKey, allowAsync, background });
64
+ registerBatches(app, { engine, keys, adminKey, allowAsync, background });
65
+ return app;
66
+ }
67
+ export { present } from "./present.js";
68
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Exports the package entry points without starting services.
3
+ * Consumers choose their runtime adapters explicitly; importing types must not open network or database connections.
4
+ */
5
+ export { createApp, envAuth, present, type Auth, type AppOptions } from "./app.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Exports the package entry points without starting services.
3
+ * Consumers choose their runtime adapters explicitly; importing types must not open network or database connections.
4
+ */
5
+ export { createApp, envAuth, present } from "./app.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAA8B,MAAM,UAAU,CAAC"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Describes the public REST operations for API discovery.
3
+ * The published paths mirror route registration; runtime validation remains owned by request schemas.
4
+ */
5
+ export declare const openapi: {
6
+ openapi: string;
7
+ info: {
8
+ title: string;
9
+ version: string;
10
+ description: string;
11
+ };
12
+ servers: {
13
+ url: string;
14
+ }[];
15
+ security: {
16
+ bearer: never[];
17
+ }[];
18
+ components: {
19
+ securitySchemes: {
20
+ bearer: {
21
+ type: string;
22
+ scheme: string;
23
+ };
24
+ };
25
+ };
26
+ paths: {
27
+ "/v1/rubrics": {
28
+ post: {
29
+ summary: string;
30
+ };
31
+ get: {
32
+ summary: string;
33
+ };
34
+ };
35
+ "/v1/rubrics/validate": {
36
+ post: {
37
+ summary: string;
38
+ };
39
+ };
40
+ "/v1/rubrics/{id}": {
41
+ get: {
42
+ summary: string;
43
+ };
44
+ put: {
45
+ summary: string;
46
+ };
47
+ delete: {
48
+ summary: string;
49
+ };
50
+ };
51
+ "/v1/rubrics/{id}/calibration": {
52
+ get: {
53
+ summary: string;
54
+ };
55
+ };
56
+ "/v1/evaluations": {
57
+ post: {
58
+ summary: string;
59
+ };
60
+ get: {
61
+ summary: string;
62
+ };
63
+ };
64
+ "/v1/evaluations/{id}": {
65
+ get: {
66
+ summary: string;
67
+ };
68
+ };
69
+ "/v1/evaluations/{id}/retry": {
70
+ post: {
71
+ summary: string;
72
+ };
73
+ };
74
+ "/v1/evaluations/{id}/scores": {
75
+ post: {
76
+ summary: string;
77
+ };
78
+ };
79
+ "/v1/evaluations/{id}/aggregate": {
80
+ post: {
81
+ summary: string;
82
+ };
83
+ };
84
+ "/v1/tasks": {
85
+ get: {
86
+ summary: string;
87
+ };
88
+ };
89
+ "/v1/batches": {
90
+ post: {
91
+ summary: string;
92
+ };
93
+ };
94
+ "/v1/admin/keys": {
95
+ post: {
96
+ summary: string;
97
+ };
98
+ get: {
99
+ summary: string;
100
+ };
101
+ };
102
+ "/v1/admin/keys/{id}": {
103
+ delete: {
104
+ summary: string;
105
+ };
106
+ };
107
+ };
108
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Describes the public REST operations for API discovery.
3
+ * The published paths mirror route registration; runtime validation remains owned by request schemas.
4
+ */
5
+ export const openapi = {
6
+ openapi: "3.1.0",
7
+ info: { title: "Verdict Evaluation API", version: "0.1.0", description: "Customizable rubrics, pluggable judges (LLM / human / benchmark), customizable aggregation." },
8
+ servers: [{ url: "/" }],
9
+ security: [{ bearer: [] }],
10
+ components: { securitySchemes: { bearer: { type: "http", scheme: "bearer" } } },
11
+ paths: {
12
+ "/v1/rubrics": { post: { summary: "Create rubric (JSON or YAML body)" }, get: { summary: "List rubrics" } },
13
+ "/v1/rubrics/validate": { post: { summary: "Validate a rubric without storing it" } },
14
+ "/v1/rubrics/{id}": { get: { summary: "Get rubric" }, put: { summary: "Replace rubric" }, delete: { summary: "Delete rubric" } },
15
+ "/v1/rubrics/{id}/calibration": { get: { summary: "Calibration report (`?target=0.8&buckets=5&limit=1000`)" } },
16
+ "/v1/evaluations": { post: { summary: "Evaluate a subject (sync; `async:true` for 202). Optional Idempotency-Key header (1–255 chars); replay 200, conflict 409, TTL 24h" }, get: { summary: "List evaluations" } },
17
+ "/v1/evaluations/{id}": { get: { summary: "Get evaluation (`?evidence=true` for full text)" } },
18
+ "/v1/evaluations/{id}/retry": { post: { summary: "Re-enqueue a failed evaluation (202; unknown 404; not failed 409)" } },
19
+ "/v1/evaluations/{id}/scores": { post: { summary: "Submit a human score for a pending criterion" } },
20
+ "/v1/evaluations/{id}/aggregate": { post: { summary: "Re-aggregate with a different aggregation config (no re-judging)" } },
21
+ "/v1/tasks": { get: { summary: "Pending human-rating tasks (`?rater=`)" } },
22
+ "/v1/batches": { post: { summary: "Evaluate many subjects against one rubric and rank them. Optional Idempotency-Key header (1–255 chars); replay 200, conflict 409, TTL 24h" } },
23
+ "/v1/admin/keys": { post: { summary: "Create an API key for a tenant (admin)" }, get: { summary: "List a tenant's keys (admin, `?tenant=`)" } },
24
+ "/v1/admin/keys/{id}": { delete: { summary: "Revoke a key (admin)" } },
25
+ },
26
+ };
27
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,EAAE,KAAK,EAAE,wBAAwB,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,6FAA6F,EAAE;IACvK,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACvB,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC1B,UAAU,EAAE,EAAE,eAAe,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC/E,KAAK,EAAE;QACL,aAAa,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,mCAAmC,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE;QAC3G,sBAAsB,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,sCAAsC,EAAE,EAAE;QACrF,kBAAkB,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE;QAChI,8BAA8B,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,yDAAyD,EAAE,EAAE;QAC/G,iBAAiB,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,mIAAmI,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE;QACnN,sBAAsB,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,iDAAiD,EAAE,EAAE;QAC/F,4BAA4B,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,mEAAmE,EAAE,EAAE;QACxH,6BAA6B,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,8CAA8C,EAAE,EAAE;QACpG,gCAAgC,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,kEAAkE,EAAE,EAAE;QAC3H,WAAW,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,wCAAwC,EAAE,EAAE;QAC3E,aAAa,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,2IAA2I,EAAE,EAAE;QACjL,gBAAgB,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,wCAAwC,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,0CAA0C,EAAE,EAAE;QAC/I,qBAAqB,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE;KACvE;CACF,CAAC"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Projects stored evaluations into public REST representations.
3
+ * Evidence text and rating details are included only when requested; frozen rubric policy remains visible.
4
+ */
5
+ import type { Evaluation } from "@verdict-ai/core";
6
+ /** Public view of an evaluation: strip bulky evidence text unless asked. */
7
+ export declare function present(ev: Evaluation, { evidence, ratings }?: {
8
+ evidence?: boolean | undefined;
9
+ ratings?: boolean | undefined;
10
+ }): {
11
+ rubric: {
12
+ id: string | undefined;
13
+ name: string;
14
+ version: string;
15
+ aggregation: {
16
+ method: "min" | "max" | "formula" | "mean" | "median" | "weighted_mean" | "weighted_sum" | "geometric_mean";
17
+ rater: "min" | "max" | "mean" | "median" | "majority" | "trimmed_mean";
18
+ normalize: false | [number, number];
19
+ missing: "fail" | "exclude" | "zero";
20
+ gates: {
21
+ criterion: string;
22
+ min?: number | undefined;
23
+ max?: number | undefined;
24
+ message?: string | undefined;
25
+ }[];
26
+ precision: number;
27
+ formula?: string | undefined;
28
+ grades?: {
29
+ label: string;
30
+ min: number;
31
+ }[] | undefined;
32
+ passThreshold?: number | undefined;
33
+ };
34
+ };
35
+ evidence: import("@verdict-ai/core").Evidence[] | {
36
+ chars: number;
37
+ inputIndex: number;
38
+ kind: import("@verdict-ai/core").Input["type"];
39
+ label: string;
40
+ tags: string[];
41
+ json?: unknown;
42
+ source?: string;
43
+ truncated?: boolean;
44
+ stats: {
45
+ chars: number;
46
+ words: number;
47
+ lines: number;
48
+ };
49
+ warnings?: string[];
50
+ }[];
51
+ criteria: {
52
+ criterionId: string;
53
+ name: string;
54
+ weight: number;
55
+ group?: string;
56
+ status: "scored" | "pending" | "error" | "excluded";
57
+ choice?: string;
58
+ score?: number;
59
+ normalized?: number;
60
+ choices?: Record<string, number>;
61
+ pending: number;
62
+ gate?: {
63
+ passed: boolean;
64
+ message?: string;
65
+ };
66
+ }[];
67
+ job?: {
68
+ attempts: number;
69
+ maxAttempts: number;
70
+ lastError?: string;
71
+ nextAttemptAt?: string;
72
+ deadLettered?: boolean;
73
+ };
74
+ id: string;
75
+ tenantId?: string;
76
+ idempotencyKey?: string;
77
+ rubricId?: string;
78
+ subject: import("@verdict-ai/core").Subject;
79
+ status: import("@verdict-ai/core").EvaluationStatus;
80
+ aggregate: import("@verdict-ai/core").AggregateResult;
81
+ pendingTasks: Array<{
82
+ criterionId: string;
83
+ raterId?: string;
84
+ allowedRaters?: string[];
85
+ instructions?: string;
86
+ }>;
87
+ checks?: Array<{
88
+ id: string;
89
+ probability: number | null;
90
+ passed: boolean;
91
+ action: "abort" | "fail" | "flag";
92
+ message?: string;
93
+ error?: string;
94
+ }>;
95
+ error?: string;
96
+ usage: {
97
+ decisionCalls: number;
98
+ llmCalls: number;
99
+ inputTokens: number;
100
+ outputTokens: number;
101
+ durationMs: number;
102
+ };
103
+ metadata?: Record<string, unknown>;
104
+ webhook?: string;
105
+ createdAt: string;
106
+ updatedAt: string;
107
+ };
@@ -0,0 +1,10 @@
1
+ /** Public view of an evaluation: strip bulky evidence text unless asked. */
2
+ export function present(ev, { evidence = false, ratings = true } = {}) {
3
+ return {
4
+ ...ev,
5
+ rubric: { id: ev.rubric.id, name: ev.rubric.name, version: ev.rubric.version, aggregation: ev.rubric.aggregation },
6
+ evidence: evidence ? ev.evidence : ev.evidence.map(({ text, ...rest }) => ({ ...rest, chars: text.length })),
7
+ criteria: ratings ? ev.criteria : ev.criteria.map(({ ratings: _r, ...c }) => c),
8
+ };
9
+ }
10
+ //# sourceMappingURL=present.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"present.js","sourceRoot":"","sources":["../src/present.ts"],"names":[],"mappings":"AAKA,4EAA4E;AAC5E,MAAM,UAAU,OAAO,CAAC,EAAc,EAAE,EAAE,QAAQ,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,EAAE;IAC/E,OAAO;QACL,GAAG,EAAE;QACL,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE;QAClH,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5G,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;KAChF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Adapts serializable engine jobs to a Cloudflare queue binding.
3
+ * Messages carry request data only; the consumer owns execution and retry policy.
4
+ */
5
+ import type { Job, Queue } from "@verdict-ai/core";
6
+ /** Sends jobs to the queue binding; enqueue rejects when the binding cannot accept a message. */
7
+ export declare class CloudflareQueue implements Queue {
8
+ private binding;
9
+ constructor(binding: {
10
+ send(body: Job, options?: {
11
+ delaySeconds?: number;
12
+ }): Promise<unknown>;
13
+ });
14
+ enqueue(job: Job, opts?: {
15
+ delayMs?: number;
16
+ }): Promise<void>;
17
+ }
@@ -0,0 +1,11 @@
1
+ /** Sends jobs to the queue binding; enqueue rejects when the binding cannot accept a message. */
2
+ export class CloudflareQueue {
3
+ binding;
4
+ constructor(binding) {
5
+ this.binding = binding;
6
+ }
7
+ async enqueue(job, opts) {
8
+ await this.binding.send(job, { delaySeconds: Math.ceil((opts?.delayMs ?? 0) / 1000) });
9
+ }
10
+ }
11
+ //# sourceMappingURL=queue-cloudflare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-cloudflare.js","sourceRoot":"","sources":["../src/queue-cloudflare.ts"],"names":[],"mappings":"AAMA,iGAAiG;AACjG,MAAM,OAAO,eAAe;IACN;IAApB,YAAoB,OAAmF;QAAnF,YAAO,GAAP,OAAO,CAA4E;IAAG,CAAC;IAC3G,KAAK,CAAC,OAAO,CAAC,GAAQ,EAAE,IAA2B;QACjD,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ import { Hono } from "hono";
2
+ import type { AppOptions, Env } from "../app.js";
3
+ /** Registers admin operations; handler failures propagate to the transport error boundary. */
4
+ export declare function registerAdmin(app: Hono<Env>, { keys, adminKey }: AppOptions): void;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Defines admin 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 { NotFoundError, ValidationError } from "@verdict-ai/core";
6
+ import { HTTPException } from "hono/http-exception";
7
+ import { z } from "zod";
8
+ /** Registers admin operations; handler failures propagate to the transport error boundary. */
9
+ export function registerAdmin(app, { keys, adminKey }) {
10
+ /* ---------- admin: API keys ---------- */
11
+ app.use("/v1/admin/*", async (c, next) => {
12
+ if (!adminKey || !keys)
13
+ throw new HTTPException(404, { message: "admin API disabled (set VERDICT_ADMIN_KEY and a database-backed key store)" });
14
+ const header = c.req.header("authorization")?.replace(/^Bearer\s+/i, "");
15
+ if (header !== adminKey)
16
+ throw new HTTPException(401, { message: "invalid admin key" });
17
+ await next();
18
+ });
19
+ app.post("/v1/admin/keys", async (c) => {
20
+ const body = z.object({ tenantId: z.string().min(1), name: z.string().min(1) }).parse(await c.req.json());
21
+ return c.json(await keys.createKey(body.tenantId, body.name), 201);
22
+ });
23
+ app.get("/v1/admin/keys", async (c) => {
24
+ const tenantId = c.req.query("tenant");
25
+ if (!tenantId)
26
+ throw new ValidationError("query param `tenant` is required");
27
+ return c.json({ data: await keys.listKeys(tenantId) });
28
+ });
29
+ app.delete("/v1/admin/keys/:id", async (c) => {
30
+ if (!(await keys.revokeKey(c.req.param("id"))))
31
+ throw new NotFoundError(`key ${c.req.param("id")} not found`);
32
+ return c.body(null, 204);
33
+ });
34
+ }
35
+ //# sourceMappingURL=admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/routes/admin.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,8FAA8F;AAC9F,MAAM,UAAU,aAAa,CAAC,GAAc,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAc;IAC1E,2CAA2C;IAC3C,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,aAAa,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,4EAA4E,EAAE,CAAC,CAAC;QAChJ,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACzE,IAAI,MAAM,KAAK,QAAQ;YAAE,MAAM,IAAI,aAAa,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;QACxF,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1G,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,IAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACpC,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,eAAe,CAAC,kCAAkC,CAAC,CAAC;QAC7E,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,IAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,MAAM,CAAC,oBAAoB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC3C,IAAI,CAAC,CAAC,MAAM,IAAK,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC/G,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AAEL,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Hono } from "hono";
2
+ import type { AppOptions, Env } from "../app.js";
3
+ /** Registers batches operations; handler failures propagate to the transport error boundary. */
4
+ export declare function registerBatches(app: Hono<Env>, { engine }: AppOptions): void;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Tenant context flows to every engine/store operation; validation failures use the shared HTTP error mapper.
3
+ */
4
+ import { Rubric, Subject } from "@verdict-ai/core";
5
+ import { z } from "zod";
6
+ import { present } from "../present.js";
7
+ import { readIdempotencyKey, tenant } from "./shared.js";
8
+ /** Registers batches operations; handler failures propagate to the transport error boundary. */
9
+ export function registerBatches(app, { engine }) {
10
+ /* ---------- batches (leaderboards) ---------- */
11
+ app.post("/v1/batches", async (c) => {
12
+ const raw = z.record(z.string(), z.unknown()).parse(await c.req.json());
13
+ const idempotencyKey = readIdempotencyKey(c.req.raw);
14
+ const body = z.object({ rubric: z.union([z.string(), Rubric]), subjects: z.array(Subject).min(1), metadata: z.record(z.string(), z.unknown()).optional(), webhook: z.string().url().optional() }).parse(raw);
15
+ const opts = { tenantId: tenant(c), metadata: body.metadata, webhook: body.webhook };
16
+ // why: preserve the submitted body for conflict detection instead of hashing schema defaults.
17
+ const fingerprintBody = Object.fromEntries(["rubric", "subjects", "metadata", "webhook"].filter(k => k in raw).map(k => [k, raw[k]]));
18
+ const result = idempotencyKey === undefined
19
+ ? { ...await engine.evaluateBatch(body.rubric, body.subjects, opts), replayed: false }
20
+ : await engine.evaluateBatchIdempotent(body.rubric, body.subjects, { ...opts, idempotencyKey, fingerprintBody });
21
+ const { evaluations, ranking, replayed } = result;
22
+ if (replayed)
23
+ c.header("Idempotency-Replayed", "true");
24
+ return c.json({ ranking, evaluations: evaluations.map((e) => present(e, { ratings: false })) }, replayed ? 200 : 201);
25
+ });
26
+ }
27
+ //# sourceMappingURL=batches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batches.js","sourceRoot":"","sources":["../../src/routes/batches.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEzD,gGAAgG;AAChG,MAAM,UAAU,eAAe,CAAC,GAAc,EAAE,EAAE,MAAM,EAAc;IACpE,kDAAkD;IAClD,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAClC,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACxE,MAAM,cAAc,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7M,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QACrF,8FAA8F;QAC9F,MAAM,eAAe,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtI,MAAM,MAAM,GAAG,cAAc,KAAK,SAAS;YACzC,CAAC,CAAC,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE;YACtF,CAAC,CAAC,MAAM,MAAM,CAAC,uBAAuB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC,CAAC;QACnH,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;QAClD,IAAI,QAAQ;YAAE,CAAC,CAAC,MAAM,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;QACvD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxH,CAAC,CAAC,CAAC;AAEL,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Hono } from "hono";
2
+ import type { AppOptions, Env } from "../app.js";
3
+ /** Registers evaluations operations; handler failures propagate to the transport error boundary. */
4
+ export declare function registerEvaluations(app: Hono<Env>, { engine, allowAsync, background }: AppOptions): void;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Tenant context flows to every engine/store operation; validation failures use the shared HTTP error mapper.
3
+ */
4
+ import { Aggregation, NotFoundError, Rubric, Subject, ValidationError } from "@verdict-ai/core";
5
+ import { z } from "zod";
6
+ import { present } from "../present.js";
7
+ import { readIdempotencyKey, tenant } from "./shared.js";
8
+ // why: the inbox is bounded independently of ordinary listings so pending tasks remain useful without unbounded reads.
9
+ const TASK_INBOX_LIMIT = 500;
10
+ /** Registers evaluations operations; handler failures propagate to the transport error boundary. */
11
+ export function registerEvaluations(app, { engine, allowAsync, background }) {
12
+ /* ---------- evaluations ---------- */
13
+ app.post("/v1/evaluations", async (c) => {
14
+ const parsed = await parseEvaluateBody(c);
15
+ const { body } = parsed;
16
+ if (body.async && allowAsync)
17
+ return respondAsync(c, engine, parsed, background);
18
+ return respondSync(c, engine, parsed);
19
+ });
20
+ app.get("/v1/evaluations", async (c) => {
21
+ const list = await engine.store.listEvaluations({ tenantId: tenant(c), rubricId: c.req.query("rubric"), status: c.req.query("status"), limit: Number(c.req.query("limit") ?? 50) });
22
+ return c.json({ data: list.map((e) => present(e, { ratings: false })) });
23
+ });
24
+ app.get("/v1/evaluations/:id", async (c) => {
25
+ const ev = await engine.store.getEvaluation(c.req.param("id"), tenant(c));
26
+ if (!ev)
27
+ throw new NotFoundError(`evaluation ${c.req.param("id")} not found`);
28
+ return c.json(present(ev, { evidence: c.req.query("evidence") === "true" }));
29
+ });
30
+ app.post("/v1/evaluations/:id/retry", async (c) => {
31
+ const ev = await engine.retryEvaluation(c.req.param("id"), tenant(c));
32
+ return c.json({ id: ev.id, status: "queued", url: `/v1/evaluations/${ev.id}` }, 202);
33
+ });
34
+ const ScoreBody = z.object({ criterionId: z.string(), raterId: z.string(), score: z.union([z.number(), z.string()]), rationale: z.string().optional(), confidence: z.number().min(0).max(1).optional() });
35
+ app.post("/v1/evaluations/:id/scores", async (c) => {
36
+ const body = ScoreBody.parse(await c.req.json());
37
+ return c.json(present(await engine.submitScore(c.req.param("id"), body, tenant(c))));
38
+ });
39
+ /** Human task inbox: everything waiting on a rater. */
40
+ app.get("/v1/tasks", async (c) => {
41
+ const rater = c.req.query("rater");
42
+ const list = await engine.store.listEvaluations({ tenantId: tenant(c), status: "pending_human", limit: TASK_INBOX_LIMIT });
43
+ const tasks = list.flatMap((e) => e.pendingTasks.filter((t) => !rater || (t.raterId ? t.raterId === rater : !t.allowedRaters || t.allowedRaters.includes(rater))).map((t) => ({ evaluationId: e.id, rubric: e.rubric.name, subject: e.subject.name, ...t })));
44
+ return c.json({ data: tasks });
45
+ });
46
+ app.post("/v1/evaluations/:id/aggregate", async (c) => {
47
+ const body = z.object({ aggregation: Aggregation.partial(), persist: z.boolean().default(false) }).parse(await c.req.json());
48
+ return c.json(present(await engine.reaggregate(c.req.param("id"), body.aggregation, tenant(c), body.persist)));
49
+ });
50
+ }
51
+ const EvaluateBody = z.object({
52
+ rubric: z.union([z.string(), Rubric]),
53
+ subject: Subject.optional(),
54
+ /** Shorthand: inputs at top level instead of subject.inputs. */
55
+ inputs: Subject.shape.inputs.optional(),
56
+ name: z.string().optional(),
57
+ metadata: z.record(z.string(), z.unknown()).optional(),
58
+ webhook: z.string().url().optional(),
59
+ async: z.boolean().optional(),
60
+ /** Include full evidence text in the response. */
61
+ includeEvidence: z.boolean().optional(),
62
+ });
63
+ async function parseEvaluateBody(c) {
64
+ const raw = z.record(z.string(), z.unknown()).parse(await c.req.json());
65
+ const body = EvaluateBody.parse(raw);
66
+ const idempotencyKey = readIdempotencyKey(c.req.raw);
67
+ // why: transport-only flags do not change evaluation identity; hash only scoring inputs and delivery metadata.
68
+ const fingerprintBody = Object.fromEntries(["rubric", "subject", "inputs", "name", "metadata", "webhook"].filter(k => k in raw).map(k => [k, raw[k]]));
69
+ const subject = body.subject ?? { name: body.name, inputs: body.inputs ?? [] };
70
+ if (!subject.inputs?.length)
71
+ throw new ValidationError("provide `subject.inputs` or `inputs`");
72
+ const req = { rubric: body.rubric, subject, tenantId: tenant(c), metadata: body.metadata, webhook: body.webhook };
73
+ return { body, req, idempotencyKey, fingerprintBody };
74
+ }
75
+ async function respondAsync(c, engine, { req, idempotencyKey, fingerprintBody }, background) {
76
+ // why: durable queues own retries; the background fallback is for deployments without a queue binding.
77
+ if (engine.queue) {
78
+ const result = idempotencyKey === undefined
79
+ ? { evaluation: await engine.enqueueEvaluation(req), replayed: false }
80
+ : await engine.enqueueEvaluationIdempotent({ ...req, idempotencyKey, fingerprintBody });
81
+ // why: a replay returns the existing resource with 200 instead of claiming a new job was accepted.
82
+ if (result.replayed)
83
+ c.header("Idempotency-Replayed", "true");
84
+ return c.json({ id: result.evaluation.id, status: result.evaluation.status, url: `/v1/evaluations/${result.evaluation.id}` }, result.replayed ? 200 : 202);
85
+ }
86
+ {
87
+ const result = await engine.evaluateBackground({ ...req, idempotencyKey, fingerprintBody }, background ?? (work => { void work.catch(error => console.error("Background evaluation failed", error)); }));
88
+ if (result.replayed)
89
+ c.header("Idempotency-Replayed", "true");
90
+ return c.json({ id: result.evaluation.id, status: result.replayed ? result.evaluation.status : "queued", url: `/v1/evaluations/${result.evaluation.id}` }, result.replayed ? 200 : 202);
91
+ }
92
+ }
93
+ async function respondSync(c, engine, { req, idempotencyKey, fingerprintBody, body }) {
94
+ const includeEvidence = body.includeEvidence;
95
+ if (idempotencyKey !== undefined) {
96
+ const { evaluation, replayed } = await engine.evaluateIdempotent({ ...req, idempotencyKey, fingerprintBody });
97
+ if (replayed)
98
+ c.header("Idempotency-Replayed", "true");
99
+ return c.json(present(evaluation, { evidence: includeEvidence }), replayed ? 200 : evaluation.status === "failed" ? 422 : 201);
100
+ }
101
+ const ev = await engine.evaluate(req);
102
+ return c.json(present(ev, { evidence: includeEvidence }), ev.status === "failed" ? 422 : 201);
103
+ }
104
+ //# sourceMappingURL=evaluations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluations.js","sourceRoot":"","sources":["../../src/routes/evaluations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { Hono } from "hono";
2
+ import type { AppOptions, Env } from "../app.js";
3
+ /** Registers rubrics operations; handler failures propagate to the transport error boundary. */
4
+ export declare function registerRubrics(app: Hono<Env>, { engine }: AppOptions): void;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Tenant context flows to every engine/store operation; validation failures use the shared HTTP error mapper.
3
+ */
4
+ import { NotFoundError, calibrate, parseRubric } from "@verdict-ai/core";
5
+ import { z } from "zod";
6
+ import { readRubricBody, tenant } from "./shared.js";
7
+ /** Registers rubrics operations; handler failures propagate to the transport error boundary. */
8
+ export function registerRubrics(app, { engine }) {
9
+ /* ---------- rubrics ---------- */
10
+ app.post("/v1/rubrics", async (c) => {
11
+ const body = await readRubricBody(c.req.raw);
12
+ const r = await engine.createRubric(body, tenant(c));
13
+ return c.json(r, 201);
14
+ });
15
+ app.post("/v1/rubrics/validate", async (c) => {
16
+ try {
17
+ const r = parseRubric(await readRubricBody(c.req.raw));
18
+ return c.json({ valid: true, rubric: r });
19
+ }
20
+ // why: validation is a diagnostic endpoint; invalid rubrics are successful diagnostic responses.
21
+ catch (e) {
22
+ return c.json({ valid: false, error: e instanceof z.ZodError ? e.issues : e.message }, 200);
23
+ }
24
+ });
25
+ app.get("/v1/rubrics", async (c) => c.json({ data: await engine.store.listRubrics({ tenantId: tenant(c), limit: Number(c.req.query("limit") ?? 100) }) }));
26
+ app.get("/v1/rubrics/:id", async (c) => {
27
+ const r = await engine.store.getRubric(c.req.param("id"), tenant(c));
28
+ if (!r)
29
+ throw new NotFoundError(`rubric ${c.req.param("id")} not found`);
30
+ return c.json(r);
31
+ });
32
+ app.get("/v1/rubrics/:id/calibration", async (c) => {
33
+ const rubricId = c.req.param("id"), tenantId = tenant(c);
34
+ if (!(await engine.store.getRubric(rubricId, tenantId)))
35
+ throw new NotFoundError(`rubric ${rubricId} not found`);
36
+ const options = z.object({
37
+ target: z.coerce.number().min(0).max(1).default(0.8),
38
+ buckets: z.coerce.number().int().min(1).max(1000).default(5),
39
+ limit: z.coerce.number().int().min(1).default(1000),
40
+ }).parse(c.req.query());
41
+ return c.json(calibrate(await engine.store.listEvaluations({ tenantId, rubricId, limit: options.limit }), options));
42
+ });
43
+ app.put("/v1/rubrics/:id", async (c) => c.json(await engine.updateRubric(c.req.param("id"), await readRubricBody(c.req.raw), tenant(c))));
44
+ app.delete("/v1/rubrics/:id", async (c) => {
45
+ if (!(await engine.store.deleteRubric(c.req.param("id"), tenant(c))))
46
+ throw new NotFoundError(`rubric ${c.req.param("id")} not found`);
47
+ return c.body(null, 204);
48
+ });
49
+ }
50
+ //# sourceMappingURL=rubrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rubrics.js","sourceRoot":"","sources":["../../src/routes/rubrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /** Decodes JSON or YAML rubric bodies; rejects read, syntax and rubric validation failures. */
2
+ export declare function readRubricBody(req: Request): Promise<unknown>;
3
+ /** Returns an optional idempotency key; throws ZodError when its length is outside 1–255 characters. */
4
+ export declare function readIdempotencyKey(req: Request): string | undefined;
5
+ /** Returns the authenticated tenant, or undefined for open local deployments. */
6
+ export declare const tenant: (c: {
7
+ get(k: "tenantId"): string;
8
+ }) => string | undefined;
@@ -0,0 +1,22 @@
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 { loadRubricText } from "@verdict-ai/core";
6
+ import { z } from "zod";
7
+ /** Decodes JSON or YAML rubric bodies; rejects read, syntax and rubric validation failures. */
8
+ export async function readRubricBody(req) {
9
+ const ct = req.headers.get("content-type") ?? "";
10
+ const text = await req.text();
11
+ if (ct.includes("yaml") || ct.includes("text/plain"))
12
+ return loadRubricText(text);
13
+ return JSON.parse(text);
14
+ }
15
+ /** Returns an optional idempotency key; throws ZodError when its length is outside 1–255 characters. */
16
+ export function readIdempotencyKey(req) {
17
+ const key = req.headers.get("Idempotency-Key");
18
+ return key === null ? undefined : z.string().min(1).max(255).parse(key);
19
+ }
20
+ /** Returns the authenticated tenant, or undefined for open local deployments. */
21
+ export const tenant = (c) => c.get("tenantId") || undefined;
22
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/routes/shared.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAY;IAC/C,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IACjD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,kBAAkB,CAAC,GAAY;IAC7C,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC/C,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC1E,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAiC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,SAAS,CAAC"}
@@ -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,61 @@
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 { Engine, MemoryQueue, FileStore, MemoryStore } from "@verdict-ai/core";
7
+ import { createApp, envAuth } 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
+ log: env.VERDICT_LOG !== "0", apiKeys: env.VERDICT_API_KEYS, adminKey: env.VERDICT_ADMIN_KEY,
16
+ };
17
+ }
18
+ /** Starts the Node server using supplied or environment-selected adapters; startup failures reject. */
19
+ export async function startServer(opts = {}) {
20
+ const config = serverConfig(opts);
21
+ const { port, dataDir, database } = config;
22
+ let store;
23
+ let auth = envAuth();
24
+ let keys;
25
+ if (opts.store)
26
+ store = opts.store;
27
+ else if (database) {
28
+ const kind = /^postgres(ql)?:\/\//.test(database) ? "postgres" : "sqlite";
29
+ const { open } = await (opts.loadDatabase ?? loadDatabase)(kind);
30
+ // Env keys stay valid as a bootstrap, but open mode (no VERDICT_API_KEYS) must not bypass the DB keys.
31
+ const h = await open(database, config.apiKeys ? envAuth() : undefined);
32
+ store = h.store;
33
+ auth = h.auth;
34
+ keys = h.auth;
35
+ }
36
+ else
37
+ store = dataDir ? new FileStore(dataDir) : new MemoryStore();
38
+ const queue = opts.queue ?? new MemoryQueue();
39
+ const engine = new Engine({ store, queue, ingest: { allowLocalFiles: config.allowLocalFiles } });
40
+ queue.engine = engine;
41
+ const app = createApp({ engine, auth, keys, log: config.log });
42
+ const server = serve({ fetch: app.fetch, port }, (info) => {
43
+ const storeName = database ? (/^postgres(ql)?:\/\//.test(database) ? "postgres" : `sqlite:${database}`) : dataDir ? `file:${dataDir}` : "memory";
44
+ 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" : ""})`);
45
+ });
46
+ process.once("SIGTERM", () => {
47
+ // why: retry timers are unreferenced; keep Node alive until delayed work drains during graceful shutdown.
48
+ const keepAlive = setInterval(() => { }, 1000);
49
+ server.close(() => { void queue.drain().then(() => { clearInterval(keepAlive); process.exit(0); }); });
50
+ });
51
+ return { app, engine, server, port };
52
+ }
53
+ // Driver imports belong at startup so the Node factory can be exercised without native modules.
54
+ async function loadDatabase(kind) {
55
+ if (kind === "postgres")
56
+ return { open: (await import("@verdict-ai/db/postgres")).fromPostgres };
57
+ return { open: (await import("@verdict-ai/db/sqlite")).fromSqliteFile };
58
+ }
59
+ if (import.meta.url === `file://${process.argv[1]}`)
60
+ void startServer();
61
+ //# 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,40 @@
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 Job } from "@verdict-ai/core";
6
+ export interface Env {
7
+ [name: string]: unknown;
8
+ DB?: D1Database;
9
+ QUEUE?: Pick<Queue<Job>, "send">;
10
+ /** Workers AI binding: Clef / Clef-flash run in-network without a REST token. */
11
+ AI?: Pick<Ai, "run">;
12
+ }
13
+ /** Shared by HTTP producers and queue consumers. */
14
+ export declare function buildEngine(env: Env): {
15
+ engine: Engine;
16
+ auth: import("./app.js").Auth;
17
+ keys: import("@verdict-ai/db").DrizzleAuth | undefined;
18
+ adminKey: string;
19
+ };
20
+ export interface JobMessage {
21
+ body: Job;
22
+ ack(): void;
23
+ retry(options: {
24
+ delaySeconds: number;
25
+ }): void;
26
+ }
27
+ /** Returns a queue consumer; retryable jobs retry and terminal jobs are acknowledged. */
28
+ export declare function createQueueHandler(build?: typeof buildEngine): (batch: Pick<MessageBatch<Job>, "messages"> | {
29
+ messages: readonly JobMessage[];
30
+ }, env: Env, _ctx?: ExecutionContext) => Promise<void>;
31
+ export declare const queue: (batch: Pick<MessageBatch<Job>, "messages"> | {
32
+ messages: readonly JobMessage[];
33
+ }, env: Env, _ctx?: ExecutionContext) => Promise<void>;
34
+ declare const _default: {
35
+ fetch(request: Request, env: Env, ctx: Pick<ExecutionContext, "waitUntil">): Response | Promise<Response>;
36
+ queue: (batch: Pick<MessageBatch<Job>, "messages"> | {
37
+ messages: readonly JobMessage[];
38
+ }, env: Env, _ctx?: ExecutionContext) => Promise<void>;
39
+ };
40
+ export default _default;
package/dist/worker.js ADDED
@@ -0,0 +1,63 @@
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, MemoryStore, RetryableError, registerDecisionProvider } from "@verdict-ai/core";
6
+ import { fromD1 } from "@verdict-ai/db";
7
+ import { createApp, envAuth } from "./app.js";
8
+ import { CloudflareQueue } from "./queue-cloudflare.js";
9
+ const memory = new MemoryStore();
10
+ /**
11
+ * Decision provider "cloudflare" backed by the Workers AI binding instead of the REST API.
12
+ * Same System One body (`model` selector, `state`, `questions`); the binding returns the inner result directly.
13
+ */
14
+ function aiBindingProvider(ai) {
15
+ return {
16
+ name: "cloudflare",
17
+ async decide(req) {
18
+ const model = req.model ?? "@cf/cloudflare/clef-flash";
19
+ const extra = req.options?.request;
20
+ const body = { model: req.options?.selector ?? model.split("/").pop(), state: req.state, questions: req.questions, ...(extra && typeof extra === "object" ? extra : {}) };
21
+ const data = (await ai.run(model, body));
22
+ return { answers: data.answers ?? {}, model, usage: { inputTokens: data.usage?.input_tokens ?? data.usage?.prompt_tokens ?? 0, outputTokens: data.usage?.output_tokens ?? 0 }, raw: data };
23
+ },
24
+ };
25
+ }
26
+ /** Shared by HTTP producers and queue consumers. */
27
+ export function buildEngine(env) {
28
+ const vars = Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
29
+ const envKeys = envAuth(vars);
30
+ const d1 = env.DB ? fromD1(env.DB, vars.VERDICT_API_KEYS ? envKeys : undefined) : undefined;
31
+ if (env.AI)
32
+ registerDecisionProvider(aiBindingProvider(env.AI));
33
+ 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 });
34
+ return { engine, auth: d1?.auth ?? envKeys, keys: d1?.auth, adminKey: vars.VERDICT_ADMIN_KEY };
35
+ }
36
+ /** Returns a queue consumer; retryable jobs retry and terminal jobs are acknowledged. */
37
+ export function createQueueHandler(build = buildEngine) {
38
+ return async (batch, env, _ctx) => {
39
+ const { engine } = build(env);
40
+ await Promise.all(batch.messages.map(async (message) => {
41
+ try {
42
+ await engine.processJob(message.body);
43
+ message.ack();
44
+ }
45
+ catch (error) {
46
+ if (error instanceof RetryableError)
47
+ message.retry({ delaySeconds: Math.ceil((error.retryAfterMs ?? 0) / 1000) });
48
+ // why: permanent failures must not poison the queue through endless redelivery.
49
+ else
50
+ message.ack();
51
+ }
52
+ }));
53
+ };
54
+ }
55
+ export const queue = createQueueHandler();
56
+ export default {
57
+ fetch(request, env, ctx) {
58
+ const app = createApp({ ...buildEngine(env), background: promise => ctx.waitUntil(promise) });
59
+ return app.fetch(request);
60
+ },
61
+ queue,
62
+ };
63
+ //# 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.1.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.1.0",
27
+ "@verdict-ai/db": "0.1.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,36 @@
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 POST/GET/DELETE /v1/admin/keys
21
+ # VERDICT_WEBHOOK_SECRET HMAC for webhook deliveries
22
+
23
+ # Clef inference through the Worker binding (no REST token needed):
24
+ [ai]
25
+ binding = "AI"
26
+
27
+ # Create verdict-jobs and verdict-dlq before enabling (Queues plan required):
28
+ # [[queues.producers]]
29
+ # binding = "QUEUE"
30
+ # queue = "verdict-jobs"
31
+ # [[queues.consumers]]
32
+ # queue = "verdict-jobs"
33
+ # max_retries = 5
34
+ # max_batch_size = 10
35
+ # max_concurrency = 4
36
+ # dead_letter_queue = "verdict-dlq"