@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 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/routes/projects.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 promotion, rollback and history routes. */
4
+ export declare function registerPromotions(app: Hono<Env>, options: AppOptions): void;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Pointer changes and their history (docs/design/control-plane.md §5). A promotion names the version the caller saw;
3
+ * the store applies all of a request's changes or none, and a stale request is answered with where the pointers are.
4
+ */
5
+ import { CONTROL_LIST_LIMIT, NotFoundError, planRollback, PromotionRequest, promotionEvents, RollbackRequest, } from "@verdict-ai/core";
6
+ import { z } from "zod";
7
+ import { ApiError, bare, controlContext, readJson } from "./shared.js";
8
+ const HistoryQuery = z.object({
9
+ environment: z.string().optional(), name: z.string().optional(),
10
+ limit: z.coerce.number().int().min(1).max(CONTROL_LIST_LIMIT).default(CONTROL_LIST_LIMIT),
11
+ });
12
+ const noEnvironment = (ctx) => new NotFoundError(`environment ${ctx.environment} not found in project ${ctx.scope.project}`);
13
+ /** Where the named pointers are now, for the answer to a request that saw them elsewhere. */
14
+ async function current(store, scope, environment, names) {
15
+ const pointers = await store.listPointers(scope, environment);
16
+ return names.map((name) => ({ name, versionId: pointers.find((p) => p.name === name)?.versionId ?? null }));
17
+ }
18
+ /** The error for a batch the store refused. */
19
+ async function refusal(store, reason, events, ctx) {
20
+ if (reason === "no_environment")
21
+ return noEnvironment(ctx);
22
+ if (reason === "unknown_version") {
23
+ for (const e of events) {
24
+ if (e.to !== null && !(await store.getVersion(ctx.scope, e.name, e.to)))
25
+ return new ApiError(400, "unknown_version", `version ${e.to} is not kept under ${e.name} in project ${ctx.scope.project}; upload it first`);
26
+ }
27
+ }
28
+ const now = await current(store, ctx.scope, ctx.environment, events.map((e) => e.name));
29
+ return new ApiError(409, "pointer_conflict", `${ctx.environment} changed since you read it; nothing was applied`, { current: now });
30
+ }
31
+ /** Registers promotion, rollback and history routes. */
32
+ export function registerPromotions(app, options) {
33
+ const { store, scope, allow, project, at } = controlContext(options);
34
+ const base = "/v1/projects/:project/environments/:environment";
35
+ app.post(`${base}/promotions`, async (c) => {
36
+ const request = PromotionRequest.parse(await readJson(c.req.raw));
37
+ const ctx = { scope: scope(c), environment: c.req.param("environment"), actor: c.get("actor"), at: at() };
38
+ for (const change of request.changes) {
39
+ await allow(c, change.versionId === null ? "pointer.unset" : "pointer.promote", { project: ctx.scope.project, environment: ctx.environment, name: change.name, versionId: change.versionId });
40
+ }
41
+ const events = promotionEvents(request, ctx);
42
+ const result = await store().applyPromotions(events);
43
+ if (!result.ok)
44
+ throw await refusal(store(), result.reason, events, ctx);
45
+ return c.json({ events: events.map(bare) });
46
+ });
47
+ app.post(`${base}/rollbacks`, async (c) => {
48
+ const request = RollbackRequest.parse(await readJson(c.req.raw));
49
+ const ctx = { scope: scope(c), environment: c.req.param("environment"), actor: c.get("actor"), at: at() };
50
+ await allow(c, "pointer.rollback", { project: ctx.scope.project, environment: ctx.environment, name: request.name });
51
+ const plan = await planRollback(store(), request, ctx);
52
+ if (!plan.ok) {
53
+ if (plan.reason === "no_environment")
54
+ throw noEnvironment(ctx);
55
+ if (plan.reason === "nothing_to_roll_back")
56
+ throw new ApiError(409, "nothing_to_roll_back", `${request.name} in ${ctx.environment} has no earlier version on record`);
57
+ throw new ApiError(409, "pointer_conflict", `${ctx.environment} changed since you read it; nothing was applied`, { current: [{ name: request.name, versionId: plan.current }] });
58
+ }
59
+ const result = await store().applyPromotions([plan.event]);
60
+ if (!result.ok && result.reason === "unknown_version")
61
+ throw new ApiError(409, "nothing_to_roll_back", `the earlier version ${plan.event.to} of ${request.name} was deleted`);
62
+ if (!result.ok)
63
+ throw await refusal(store(), result.reason, [plan.event], ctx);
64
+ return c.json({ event: bare(plan.event) });
65
+ });
66
+ app.get("/v1/projects/:project/history", async (c) => {
67
+ const q = HistoryQuery.parse(c.req.query());
68
+ await allow(c, "history.read", { project: c.req.param("project"), environment: q.environment, name: q.name });
69
+ return c.json({ data: (await store().listHistory(await project(c), q)).map(bare) });
70
+ });
71
+ }
72
+ //# sourceMappingURL=promotions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promotions.js","sourceRoot":"","sources":["../../src/routes/promotions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Public hub reads (docs/design/hub.md §9, §16.3): search, a card's versions and one publication, as JSON under
3
+ * `/v1/public/*` and as pages under `/hub`. No API key. These are the only store reads that are not tenant-scoped, and
4
+ * they read only the publications collection, through `publicView`, which drops the publishing tenant.
5
+ */
6
+ import { type Publication, type PublicationStore, type PublicPublication, type PublicVersionEntry } from "@verdict-ai/core";
7
+ import { Hono } from "hono";
8
+ import { z } from "zod";
9
+ import type { AppOptions, Env } from "../app.js";
10
+ /** Public paths start with these; the tenant middleware lets them through without a key. */
11
+ export declare const isPublicHubPath: (path: string) => boolean;
12
+ declare const Search: z.ZodObject<{
13
+ q: z.ZodOptional<z.ZodString>;
14
+ domain: z.ZodOptional<z.ZodString>;
15
+ locale: z.ZodOptional<z.ZodString>;
16
+ type: z.ZodOptional<z.ZodEnum<{
17
+ choice: "choice";
18
+ score: "score";
19
+ noul: "noul";
20
+ }>>;
21
+ owner: z.ZodOptional<z.ZodString>;
22
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
23
+ }, z.core.$strip>;
24
+ export type PublicSearch = z.infer<typeof Search>;
25
+ /** A withdrawn version answers 410: it existed and was taken down, which is different from never existing. */
26
+ export declare class WithdrawnError extends Error {
27
+ publication: Publication;
28
+ constructor(publication: Publication);
29
+ }
30
+ export declare function registerPublicHub(app: Hono<Env>, { engine, limits, clientKey }: Pick<AppOptions, "engine" | "limits" | "clientKey">): void;
31
+ /** The reads themselves, kept apart from HTTP so the JSON routes and the pages answer the same way. */
32
+ export declare function publicHub(store: () => PublicationStore): {
33
+ search(q: PublicSearch): Promise<Publication[]>;
34
+ /** A card at `versionId`, or at its newest live version, with every version it has had. */
35
+ card(owner: string, slug: string, versionId?: string): Promise<{
36
+ publication: PublicPublication;
37
+ versions: PublicVersionEntry[];
38
+ }>;
39
+ publication(id: string): Promise<PublicPublication>;
40
+ };
41
+ export {};
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Public hub reads (docs/design/hub.md §9, §16.3): search, a card's versions and one publication, as JSON under
3
+ * `/v1/public/*` and as pages under `/hub`. No API key. These are the only store reads that are not tenant-scoped, and
4
+ * they read only the publications collection, through `publicView`, which drops the publishing tenant.
5
+ */
6
+ import { hasPublications, latestPerRef, listingOf, NotFoundError, publicView, versionEntry, withdrawalNote, } from "@verdict-ai/core";
7
+ import { HTTPException } from "hono/http-exception";
8
+ import { z } from "zod";
9
+ import { cardPage, goneCardPage, missingPage, searchPage } from "../hub-pages.js";
10
+ import { defaultClientKey, enforce } from "../rate-limit.js";
11
+ /** Public paths start with these; the tenant middleware lets them through without a key. */
12
+ export const isPublicHubPath = (path) => path.startsWith("/v1/public/") || path === "/hub" || path.startsWith("/hub/");
13
+ const SEARCH_LIMIT = 50;
14
+ /** Enough rows to find the newest version of each card a page shows. */
15
+ const SCAN_LIMIT = 500;
16
+ const Search = z.object({
17
+ q: z.string().max(200).optional(), domain: z.string().max(60).optional(), locale: z.string().max(35).optional(),
18
+ type: z.enum(["score", "noul", "choice"]).optional(), owner: z.string().max(64).optional(),
19
+ limit: z.coerce.number().int().min(1).max(SEARCH_LIMIT).default(SEARCH_LIMIT),
20
+ });
21
+ /** A withdrawn version answers 410: it existed and was taken down, which is different from never existing. */
22
+ export class WithdrawnError extends Error {
23
+ publication;
24
+ constructor(publication) {
25
+ super(withdrawalNote(publication));
26
+ this.publication = publication;
27
+ }
28
+ }
29
+ export function registerPublicHub(app, { engine, limits, clientKey = defaultClientKey }) {
30
+ const store = () => {
31
+ if (!hasPublications(engine.store))
32
+ throw new HTTPException(501, { message: "this server's store cannot keep publications" });
33
+ return engine.store;
34
+ };
35
+ const hub = publicHub(store);
36
+ // One check per request: in Hono "/hub/*" also matches "/hub", so the paths are tested here rather than as patterns.
37
+ app.use(async (c, next) => {
38
+ if (isPublicHubPath(c.req.path))
39
+ await enforce(limits?.read, clientKey(c), "hub read");
40
+ await next();
41
+ });
42
+ app.get("/v1/public/rubrics", async (c) => c.json({ data: (await hub.search(Search.parse(c.req.query()))).map(listingOf) }));
43
+ app.get("/v1/public/rubrics/:owner/:slug", async (c) => {
44
+ try {
45
+ return c.json(await hub.card(c.req.param("owner"), c.req.param("slug"), c.req.query("version")));
46
+ }
47
+ catch (e) {
48
+ return withdrawnJson(c, e);
49
+ }
50
+ });
51
+ app.get("/v1/public/publications/:id", async (c) => {
52
+ try {
53
+ return c.json(await hub.publication(c.req.param("id")));
54
+ }
55
+ catch (e) {
56
+ return withdrawnJson(c, e);
57
+ }
58
+ });
59
+ app.get("/hub", async (c) => {
60
+ const parsed = Search.safeParse(c.req.query());
61
+ const query = parsed.success ? parsed.data : Search.parse({});
62
+ return html(c, searchPage({ query, results: (await hub.search(query)).map(listingOf) }));
63
+ });
64
+ app.get("/hub/:owner/:slug", async (c) => {
65
+ const origin = new URL(c.req.url).origin;
66
+ try {
67
+ return html(c, cardPage({ origin, ...(await hub.card(c.req.param("owner"), c.req.param("slug"), c.req.query("version"))) }));
68
+ }
69
+ catch (e) {
70
+ if (e instanceof WithdrawnError)
71
+ return html(c, goneCardPage(e.publication, e.message), 410);
72
+ if (e instanceof NotFoundError)
73
+ return html(c, missingPage(e.message), 404);
74
+ throw e;
75
+ }
76
+ });
77
+ }
78
+ /** The reads themselves, kept apart from HTTP so the JSON routes and the pages answer the same way. */
79
+ export function publicHub(store) {
80
+ return {
81
+ async search(q) {
82
+ const query = { q: q.q || undefined, domain: q.domain || undefined, locale: q.locale || undefined, judgmentType: q.type, owner: q.owner || undefined, limit: SCAN_LIMIT };
83
+ return latestPerRef(await store().listPublications(query)).slice(0, q.limit);
84
+ },
85
+ /** A card at `versionId`, or at its newest live version, with every version it has had. */
86
+ async card(owner, slug, versionId) {
87
+ const all = await store().listPublications({ owner, slug, includeWithdrawn: true, limit: SCAN_LIMIT });
88
+ if (!all.length)
89
+ throw new NotFoundError(`no card ${owner}/${slug} on this hub`);
90
+ const chosen = versionId ? all.find((p) => p.version.id === versionId) : all.find((p) => !p.withdrawnAt) ?? all[0];
91
+ if (!chosen)
92
+ throw new NotFoundError(`${owner}/${slug} has no version ${versionId}`);
93
+ if (chosen.withdrawnAt)
94
+ throw new WithdrawnError(chosen);
95
+ return { publication: publicView(chosen), versions: all.map(versionEntry) };
96
+ },
97
+ async publication(id) {
98
+ const p = await store().getPublication(id);
99
+ if (!p)
100
+ throw new NotFoundError(`publication ${id} not found`);
101
+ if (p.withdrawnAt)
102
+ throw new WithdrawnError(p);
103
+ return publicView(p);
104
+ },
105
+ };
106
+ }
107
+ function withdrawnJson(c, e) {
108
+ if (!(e instanceof WithdrawnError))
109
+ throw e;
110
+ return c.json({ error: { type: e.publication.takedown ? "taken_down" : "withdrawn", message: e.message, withdrawnAt: e.publication.withdrawnAt } }, 410);
111
+ }
112
+ /** Pages carry no scripts and load nothing from elsewhere: everything shown is author-written text, escaped. */
113
+ function html(c, body, status = 200) {
114
+ c.header("content-security-policy", "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'");
115
+ c.header("x-content-type-options", "nosniff");
116
+ c.header("referrer-policy", "no-referrer");
117
+ return c.html(body, status);
118
+ }
119
+ //# sourceMappingURL=public-hub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-hub.js","sourceRoot":"","sources":["../../src/routes/public-hub.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 publication routes; requests are validated by `buildPublication`, never trusted as documents. */
4
+ export declare function registerPublications(app: Hono<Env>, { engine, publisherOf, now, limits }: AppOptions): void;
@@ -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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"}
@@ -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,42 @@
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>;
18
+ /** Decodes JSON or YAML rubric bodies; rejects read, syntax and rubric validation failures. */
19
+ export declare function readRubricBody(req: Request): Promise<unknown>;
20
+ /** Returns an optional idempotency key; throws ZodError when its length is outside 1–255 characters. */
21
+ export declare function readIdempotencyKey(req: Request): string | undefined;
22
+ /** Returns the authenticated tenant, or undefined for open local deployments. */
23
+ export declare const tenant: (c: {
24
+ get(k: "tenantId"): string;
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;