@verdict-ai/api 0.1.0 → 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 (53) hide show
  1. package/dist/app.d.ts +50 -4
  2. package/dist/app.js +63 -11
  3. package/dist/app.js.map +1 -1
  4. package/dist/github.d.ts +15 -0
  5. package/dist/github.js +40 -0
  6. package/dist/github.js.map +1 -0
  7. package/dist/hub-pages.d.ts +24 -0
  8. package/dist/hub-pages.js +119 -0
  9. package/dist/hub-pages.js.map +1 -0
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.js +2 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/openapi.d.ts +355 -0
  14. package/dist/openapi.js +35 -4
  15. package/dist/openapi.js.map +1 -1
  16. package/dist/present.d.ts +3 -0
  17. package/dist/rate-limit.d.ts +44 -0
  18. package/dist/rate-limit.js +40 -0
  19. package/dist/rate-limit.js.map +1 -0
  20. package/dist/routes/admin.d.ts +1 -1
  21. package/dist/routes/admin.js +54 -8
  22. package/dist/routes/admin.js.map +1 -1
  23. package/dist/routes/hub.d.ts +12 -0
  24. package/dist/routes/hub.js +66 -0
  25. package/dist/routes/hub.js.map +1 -0
  26. package/dist/routes/project-versions.d.ts +4 -0
  27. package/dist/routes/project-versions.js +56 -0
  28. package/dist/routes/project-versions.js.map +1 -0
  29. package/dist/routes/projects.d.ts +4 -0
  30. package/dist/routes/projects.js +91 -0
  31. package/dist/routes/projects.js.map +1 -0
  32. package/dist/routes/promotions.d.ts +4 -0
  33. package/dist/routes/promotions.js +72 -0
  34. package/dist/routes/promotions.js.map +1 -0
  35. package/dist/routes/public-hub.d.ts +41 -0
  36. package/dist/routes/public-hub.js +119 -0
  37. package/dist/routes/public-hub.js.map +1 -0
  38. package/dist/routes/publications.d.ts +4 -0
  39. package/dist/routes/publications.js +79 -0
  40. package/dist/routes/publications.js.map +1 -0
  41. package/dist/routes/reports.d.ts +4 -0
  42. package/dist/routes/reports.js +84 -0
  43. package/dist/routes/reports.js.map +1 -0
  44. package/dist/routes/shared.d.ts +34 -0
  45. package/dist/routes/shared.js +73 -1
  46. package/dist/routes/shared.js.map +1 -1
  47. package/dist/server.js +12 -4
  48. package/dist/server.js.map +1 -1
  49. package/dist/worker.d.ts +20 -2
  50. package/dist/worker.js +23 -5
  51. package/dist/worker.js.map +1 -1
  52. package/package.json +3 -3
  53. package/wrangler.example.toml +18 -1
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Publishing to the public hub (docs/design/hub.md §16). Authenticated: a tenant previews, publishes and withdraws its
3
+ * own publications. Preview and publish build the document with the same function, so what the author previews is
4
+ * what is stored. Public reads come later (P3-3) and read only the publications collection.
5
+ */
6
+ import { buildPublication, hasPublications, NotFoundError, PUBLICATION_LIMITS, withdrawalNote } from "@verdict-ai/core";
7
+ import { HTTPException } from "hono/http-exception";
8
+ import { enforce } from "../rate-limit.js";
9
+ import { tenant } from "./shared.js";
10
+ /** Registers publication routes; requests are validated by `buildPublication`, never trusted as documents. */
11
+ export function registerPublications(app, { engine, publisherOf = defaultPublisher, now = () => Date.now(), limits }) {
12
+ const store = () => {
13
+ if (!hasPublications(engine.store))
14
+ throw new HTTPException(501, { message: "this server's store cannot keep publications" });
15
+ return engine.store;
16
+ };
17
+ const build = async (raw, tenantId) => {
18
+ // Previews do the same work as publishing, so they count against the same per-account limit.
19
+ await enforce(limits?.publish, tenantId ?? "local", "publishing");
20
+ const text = await raw.text();
21
+ if (new TextEncoder().encode(text).length > PUBLICATION_LIMITS.bytes)
22
+ throw new HTTPException(413, { message: `a publication request may be at most ${PUBLICATION_LIMITS.bytes} bytes` });
23
+ const owner = await publisherOf(tenantId);
24
+ if (!owner)
25
+ throw new HTTPException(403, { message: "publishing needs a GitHub sign-in: run `verdict hub login`" });
26
+ const s = store();
27
+ let body;
28
+ try {
29
+ body = JSON.parse(text);
30
+ }
31
+ catch {
32
+ throw new HTTPException(400, { message: "the publication request must be JSON" });
33
+ }
34
+ return buildPublication(body, {
35
+ owner, tenantId,
36
+ findParent: async (versionId) => {
37
+ const [parent] = await s.listPublications({ versionId, limit: 1 });
38
+ return parent ? { id: parent.id, ref: parent.ref } : undefined;
39
+ },
40
+ });
41
+ };
42
+ // A diagnostic, like /v1/rubrics/validate: a rejected request is a successful answer listing its issues.
43
+ app.post("/v1/publications/preview", async (c) => c.json(await build(c.req.raw, tenant(c))));
44
+ app.post("/v1/publications", async (c) => {
45
+ const tenantId = tenant(c);
46
+ const result = await build(c.req.raw, tenantId);
47
+ if (!result.ok || !result.publication)
48
+ return c.json({ error: { type: "validation", message: "the publication was rejected", issues: result.issues } }, 400);
49
+ const s = store(), draft = result.publication;
50
+ // A renamed GitHub login can be taken by someone else: a ref stays with the account that first published under it.
51
+ const [first] = await s.listPublications({ owner: draft.owner, slug: draft.slug, includeWithdrawn: true, limit: 1 });
52
+ if (first && first.tenantId !== tenantId)
53
+ throw new HTTPException(409, { message: `${draft.ref} belongs to another account; choose another slug` });
54
+ const [existing] = await s.listPublications({ owner: draft.owner, slug: draft.slug, versionId: draft.version.id, includeWithdrawn: true, limit: 1 });
55
+ if (existing?.withdrawnAt)
56
+ throw new HTTPException(409, { message: `${withdrawalNote(existing)}; publish a new version instead` });
57
+ // Publishing the same version again is a no-op that returns the stored document.
58
+ if (existing)
59
+ return c.json({ publication: existing, warnings: result.warnings, created: false }, 200);
60
+ const publication = { id: `pub_${crypto.randomUUID().replace(/-/g, "").slice(0, 20)}`, ...draft, createdAt: new Date(now()).toISOString() };
61
+ await s.putPublication(publication);
62
+ return c.json({ publication, warnings: result.warnings, created: true }, 201);
63
+ });
64
+ app.get("/v1/publications", async (c) => {
65
+ // An open single-tenant server has no tenant: every publication on it is the caller's.
66
+ return c.json({ data: await store().listPublications({ tenantId: tenant(c), includeWithdrawn: true, limit: Number(c.req.query("limit") ?? 50) }) });
67
+ });
68
+ app.delete("/v1/publications/:id", async (c) => {
69
+ const id = c.req.param("id");
70
+ if (!(await store().withdrawPublication(id, tenant(c), new Date(now()).toISOString())))
71
+ throw new NotFoundError(`publication ${id} not found`);
72
+ return c.body(null, 204);
73
+ });
74
+ }
75
+ /** Until authors sign in with GitHub (P3-2), a tenant publishes under its own id; open single-tenant mode under "local". */
76
+ function defaultPublisher(tenantId) {
77
+ return tenantId ?? "local";
78
+ }
79
+ //# sourceMappingURL=publications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publications.js","sourceRoot":"","sources":["../../src/routes/publications.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 report routes. */
4
+ export declare function registerReports(app: Hono<Env>, options: AppOptions): void;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Reports of runs and comparisons (docs/design/control-plane.md §7): numbers a team's CI sends about runs it made
3
+ * itself. The server cannot repeat a run, so every report is kept as reported by its author; it checks only that the
4
+ * run names a version it holds (or receives with the report) and that version's hashes.
5
+ */
6
+ import { CONTROL_LIST_LIMIT, CONTROL_VERSION_BYTES, ComparisonReportRequest, comparisonReport, NotFoundError, projectVersion, runReport, RunReportRequest, ValidationError, } from "@verdict-ai/core";
7
+ import { z } from "zod";
8
+ import { ApiError, bare, controlContext, projectNotFound, readJson } from "./shared.js";
9
+ const limit = z.coerce.number().int().min(1).max(CONTROL_LIST_LIMIT).default(CONTROL_LIST_LIMIT);
10
+ const RunsQuery = z.object({ name: z.string().optional(), versionId: z.string().optional(), dataset: z.string().optional(), limit });
11
+ const ComparisonsQuery = z.object({ name: z.string().optional(), head: z.string().optional(), limit });
12
+ /** 404 for a project that is not there; otherwise the refusal the caller names. */
13
+ async function missing(store, scope, otherwise) {
14
+ return (await store.getProject(scope.tenantId, scope.project)) ? otherwise : projectNotFound(scope.project);
15
+ }
16
+ /** Registers report routes. */
17
+ export function registerReports(app, options) {
18
+ const { store, scope, allow, project, at, now } = controlContext(options);
19
+ const base = "/v1/projects/:project/reports";
20
+ /** The version a run report is about: the one sent with it, else the one kept under that name. */
21
+ const reportedVersion = async (s, request) => {
22
+ const id = request.run.rubricVersionId;
23
+ if (request.version) {
24
+ if (request.version.id !== id)
25
+ throw new ValidationError(`the report sends version ${request.version.id} with a run of ${id}`);
26
+ return projectVersion(request.version, s, request.name, now);
27
+ }
28
+ const kept = await store().getVersion(s, request.name, id);
29
+ if (!kept)
30
+ throw await missing(store(), s, new ApiError(400, "unknown_version", `version ${id} is not kept under ${request.name} in project ${s.project}; send it with the report`));
31
+ return kept;
32
+ };
33
+ app.post(`${base}/runs`, async (c) => {
34
+ const request = RunReportRequest.parse(await readJson(c.req.raw, CONTROL_VERSION_BYTES));
35
+ const s = scope(c);
36
+ // Covers the version sent along: a key that may report may bring the version its report is about.
37
+ await allow(c, "report.put", { project: s.project, name: request.name, versionId: request.run.rubricVersionId });
38
+ const version = await reportedVersion(s, request);
39
+ const report = runReport(request, version, { scope: s, actor: c.get("actor"), at: at() });
40
+ if (request.version && (await store().putVersion(version)) === "no_project")
41
+ throw projectNotFound(s.project);
42
+ const result = await store().putRunReport(report);
43
+ if (result === "no_project")
44
+ throw projectNotFound(s.project);
45
+ return c.json({ report: bare(result.report), created: result.created }, result.created ? 201 : 200);
46
+ });
47
+ app.get(`${base}/runs`, async (c) => {
48
+ const q = RunsQuery.parse(c.req.query());
49
+ await allow(c, "report.read", { project: c.req.param("project"), name: q.name, versionId: q.versionId });
50
+ const reports = await store().listRunReports(await project(c), { name: q.name, versionId: q.versionId, datasetName: q.dataset, limit: q.limit });
51
+ return c.json({ data: reports.map(bare) });
52
+ });
53
+ app.get(`${base}/runs/:runId`, async (c) => {
54
+ const s = scope(c), id = c.req.param("runId");
55
+ await allow(c, "report.read", { project: s.project });
56
+ const report = await store().getRunReport(s, id);
57
+ if (!report)
58
+ throw new NotFoundError(`no report of run ${id} in project ${s.project}`);
59
+ return c.json(bare(report));
60
+ });
61
+ app.post(`${base}/comparisons`, async (c) => {
62
+ const request = ComparisonReportRequest.parse(await readJson(c.req.raw));
63
+ const s = scope(c), { base: from, head } = request.comparison;
64
+ await allow(c, "report.put", { project: s.project, name: request.name, versionId: head.rubricVersionId });
65
+ for (const side of [from, head]) {
66
+ const run = await store().getRunReport(s, side.runId);
67
+ if (!run)
68
+ throw await missing(store(), s, new ApiError(400, "unknown_run", `run ${side.runId} has not been reported to project ${s.project}; report both runs first`));
69
+ if (run.summary.rubricVersionId !== side.rubricVersionId)
70
+ throw new ValidationError(`run ${side.runId} was reported as a run of ${run.summary.rubricVersionId}, not ${side.rubricVersionId}`);
71
+ }
72
+ const result = await store().putComparisonReport(await comparisonReport(request, { scope: s, actor: c.get("actor"), at: at() }));
73
+ if (result === "no_project")
74
+ throw projectNotFound(s.project);
75
+ return c.json({ report: bare(result.report), created: result.created }, result.created ? 201 : 200);
76
+ });
77
+ app.get(`${base}/comparisons`, async (c) => {
78
+ const q = ComparisonsQuery.parse(c.req.query());
79
+ await allow(c, "report.read", { project: c.req.param("project"), name: q.name, versionId: q.head });
80
+ const reports = await store().listComparisonReports(await project(c), { name: q.name, headVersionId: q.head, limit: q.limit });
81
+ return c.json({ data: reports.map(bare) });
82
+ });
83
+ }
84
+ //# sourceMappingURL=reports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reports.js","sourceRoot":"","sources":["../../src/routes/reports.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,20 @@
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 { type AccessTarget, type ControlAction, type ControlStore, type ProjectScope } from "@verdict-ai/core";
6
+ import type { Context } from "hono";
7
+ import type { ContentfulStatusCode } from "hono/utils/http-status";
8
+ import type { AppOptions, Env } from "../app.js";
9
+ /** A refusal clients can tell apart by `type`; `extra` is merged into the error object of the response. */
10
+ export declare class ApiError extends Error {
11
+ readonly status: ContentfulStatusCode;
12
+ readonly type: string;
13
+ readonly extra: Record<string, unknown>;
14
+ constructor(status: ContentfulStatusCode, type: string, message: string, extra?: Record<string, unknown>);
15
+ }
16
+ /** Reads a JSON body of at most `maxBytes`; 413 when larger, 400 when it is not JSON. An empty body reads as `{}`. */
17
+ export declare function readJson(req: Request, maxBytes?: number): Promise<unknown>;
1
18
  /** Decodes JSON or YAML rubric bodies; rejects read, syntax and rubric validation failures. */
2
19
  export declare function readRubricBody(req: Request): Promise<unknown>;
3
20
  /** Returns an optional idempotency key; throws ZodError when its length is outside 1–255 characters. */
@@ -6,3 +23,20 @@ export declare function readIdempotencyKey(req: Request): string | undefined;
6
23
  export declare const tenant: (c: {
7
24
  get(k: "tenantId"): string;
8
25
  }) => string | undefined;
26
+ /** Documents leave without the tenant they are stored under. */
27
+ export declare function bare<T extends {
28
+ tenantId?: string;
29
+ }>(doc: T): Omit<T, "tenantId">;
30
+ /**
31
+ * What every control-plane route works with: the store, the project a request is about, and `allow`, the one place a
32
+ * request is allowed or refused (docs/design/control-plane.md §9.1). Routes call `allow` before they read or write.
33
+ */
34
+ export declare function controlContext({ engine, authorize, now }: AppOptions): {
35
+ store: () => ControlStore;
36
+ scope: (c: Context<Env>) => ProjectScope;
37
+ allow: (c: Context<Env>, action: ControlAction, target?: AccessTarget) => Promise<void>;
38
+ project: (c: Context<Env>) => Promise<ProjectScope>;
39
+ at: () => string;
40
+ now: () => number;
41
+ };
42
+ export declare const projectNotFound: (name: string) => ApiError;
@@ -2,8 +2,35 @@
2
2
  * Defines shared request handling at the REST boundary.
3
3
  * Tenant context flows to every engine/store operation; validation failures use the shared HTTP error mapper.
4
4
  */
5
- import { loadRubricText } from "@verdict-ai/core";
5
+ import { allowAll, hasControl, loadRubricText } from "@verdict-ai/core";
6
+ import { HTTPException } from "hono/http-exception";
6
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
+ }
7
34
  /** Decodes JSON or YAML rubric bodies; rejects read, syntax and rubric validation failures. */
8
35
  export async function readRubricBody(req) {
9
36
  const ct = req.headers.get("content-type") ?? "";
@@ -19,4 +46,49 @@ export function readIdempotencyKey(req) {
19
46
  }
20
47
  /** Returns the authenticated tenant, or undefined for open local deployments. */
21
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`);
22
94
  //# sourceMappingURL=shared.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/routes/shared.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -3,8 +3,8 @@
3
3
  * Database keys retain bootstrap authentication; shutdown drains queued work before exiting.
4
4
  */
5
5
  import { serve } from "@hono/node-server";
6
- import { Engine, MemoryQueue, FileStore, MemoryStore } from "@verdict-ai/core";
7
- import { createApp, envAuth } from "./app.js";
6
+ import { lazyPlaywrightScreenshot, Engine, MemoryQueue, FileStore, MemoryStore } from "@verdict-ai/core";
7
+ import { createApp, envAuth, routeGroups } from "./app.js";
8
8
  // Keep environment interpretation at startup so storage and queue dependencies can be supplied by tests.
9
9
  function serverConfig(opts, env = process.env) {
10
10
  return {
@@ -12,7 +12,11 @@ function serverConfig(opts, env = process.env) {
12
12
  dataDir: opts.dataDir ?? env.VERDICT_DATA_DIR,
13
13
  database: opts.database ?? env.VERDICT_DATABASE,
14
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",
15
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),
16
20
  };
17
21
  }
18
22
  /** Starts the Node server using supplied or environment-selected adapters; startup failures reject. */
@@ -36,9 +40,9 @@ export async function startServer(opts = {}) {
36
40
  else
37
41
  store = dataDir ? new FileStore(dataDir) : new MemoryStore();
38
42
  const queue = opts.queue ?? new MemoryQueue();
39
- const engine = new Engine({ store, queue, ingest: { allowLocalFiles: config.allowLocalFiles } });
43
+ const engine = new Engine({ store, queue, ingest: { allowLocalFiles: config.allowLocalFiles, screenshot: config.screenshots ? lazyPlaywrightScreenshot() : undefined } });
40
44
  queue.engine = engine;
41
- const app = createApp({ engine, auth, keys, log: config.log });
45
+ const app = createApp({ engine, auth, keys, log: config.log, github: config.github, clientKey: socketAddress, routes: config.routes });
42
46
  const server = serve({ fetch: app.fetch, port }, (info) => {
43
47
  const storeName = database ? (/^postgres(ql)?:\/\//.test(database) ? "postgres" : `sqlite:${database}`) : dataDir ? `file:${dataDir}` : "memory";
44
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" : ""})`);
@@ -58,4 +62,8 @@ async function loadDatabase(kind) {
58
62
  }
59
63
  if (import.meta.url === `file://${process.argv[1]}`)
60
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
+ }
61
69
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/worker.d.ts CHANGED
@@ -2,20 +2,38 @@
2
2
  * Builds Workers-compatible engines and consumes Cloudflare queue messages.
3
3
  * D1 reservations commit before judging; failed queue attempts must be acknowledged or retried explicitly.
4
4
  */
5
- import { Engine, type Job } from "@verdict-ai/core";
5
+ import { Engine, type DecisionProviderAdapter, type Job } from "@verdict-ai/core";
6
+ import { type CloudflareRateLimit, type HubLimits } from "./rate-limit.js";
6
7
  export interface Env {
7
8
  [name: string]: unknown;
8
9
  DB?: D1Database;
9
10
  QUEUE?: Pick<Queue<Job>, "send">;
10
11
  /** Workers AI binding: Clef / Clef-flash run in-network without a REST token. */
11
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;
12
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;
13
25
  /** Shared by HTTP producers and queue consumers. */
14
26
  export declare function buildEngine(env: Env): {
15
27
  engine: Engine;
16
- auth: import("./app.js").Auth;
28
+ auth: import("./app.js").Auth | import("@verdict-ai/db").DrizzleAuth;
17
29
  keys: import("@verdict-ai/db").DrizzleAuth | undefined;
18
30
  adminKey: string;
31
+ github: {
32
+ clientId: string;
33
+ clientSecret: string;
34
+ } | undefined;
35
+ limits: HubLimits;
36
+ routes: readonly ("evaluations" | "rubrics" | "hub" | "control")[];
19
37
  };
20
38
  export interface JobMessage {
21
39
  body: Job;
package/dist/worker.js CHANGED
@@ -2,22 +2,39 @@
2
2
  * Builds Workers-compatible engines and consumes Cloudflare queue messages.
3
3
  * D1 reservations commit before judging; failed queue attempts must be acknowledged or retried explicitly.
4
4
  */
5
- import { Engine, MemoryStore, RetryableError, registerDecisionProvider } from "@verdict-ai/core";
5
+ import { CLEF_LIMITS, Engine, MemoryStore, RetryableError, checkClefImages, registerDecisionProvider } from "@verdict-ai/core";
6
6
  import { fromD1 } from "@verdict-ai/db";
7
- import { createApp, envAuth } from "./app.js";
7
+ import { createApp, envAuth, routeGroups } from "./app.js";
8
8
  import { CloudflareQueue } from "./queue-cloudflare.js";
9
+ import { cloudflareLimiter, defaultHubLimits } from "./rate-limit.js";
9
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
+ }
10
21
  /**
11
22
  * Decision provider "cloudflare" backed by the Workers AI binding instead of the REST API.
12
23
  * Same System One body (`model` selector, `state`, `questions`); the binding returns the inner result directly.
13
24
  */
14
- function aiBindingProvider(ai) {
25
+ export function aiBindingProvider(ai) {
15
26
  return {
16
27
  name: "cloudflare",
28
+ acceptsImages: true,
29
+ maxImages: CLEF_LIMITS.maxImages,
17
30
  async decide(req) {
18
31
  const model = req.model ?? "@cf/cloudflare/clef-flash";
19
32
  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 : {}) };
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 } : {}) };
21
38
  const data = (await ai.run(model, body));
22
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 };
23
40
  },
@@ -31,7 +48,8 @@ export function buildEngine(env) {
31
48
  if (env.AI)
32
49
  registerDecisionProvider(aiBindingProvider(env.AI));
33
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 });
34
- return { engine, auth: d1?.auth ?? envKeys, keys: d1?.auth, adminKey: vars.VERDICT_ADMIN_KEY };
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) };
35
53
  }
36
54
  /** Returns a queue consumer; retryable jobs retry and terminal jobs are acknowledged. */
37
55
  export function createQueueHandler(build = buildEngine) {
@@ -1 +1 @@
1
- {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdict-ai/api",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Verdict REST API (Hono). Runs on Node and Cloudflare Workers.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,8 +23,8 @@
23
23
  "@hono/node-server": "^2.1.6",
24
24
  "hono": "^4.13.16",
25
25
  "zod": "^4.6.7",
26
- "@verdict-ai/core": "0.1.0",
27
- "@verdict-ai/db": "0.1.0"
26
+ "@verdict-ai/core": "0.2.0",
27
+ "@verdict-ai/db": "0.2.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@cloudflare/workers-types": "^5.20261104.1"
@@ -17,8 +17,25 @@ migrations_dir = "../db/drizzle"
17
17
  # TYPESAFE_API_KEY Jev (default judge)
18
18
  # CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN Clef / Clef-flash via Workers AI REST
19
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
20
+ # VERDICT_ADMIN_KEY enables /v1/admin/* (key management, hub takedowns)
21
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 }
22
39
 
23
40
  # Clef inference through the Worker binding (no REST token needed):
24
41
  [ai]