@openpond/evals 0.4.2 → 0.6.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 (64) hide show
  1. package/CONTRACT.md +27 -1
  2. package/README.md +31 -2
  3. package/conformance/telemetry/v1/invalid-batch.json +17 -0
  4. package/conformance/telemetry/v1/valid-batch.json +58 -0
  5. package/dist/artifact-verification.js +172 -0
  6. package/dist/builtin-benchmarks/harness-refiner.js +469 -24
  7. package/dist/execution-contracts.js +153 -0
  8. package/dist/execution-receipts.js +185 -0
  9. package/dist/graders.js +8 -2
  10. package/dist/index.js +10 -0
  11. package/dist/learned-preference.js +334 -0
  12. package/dist/preferences.js +803 -0
  13. package/dist/rollouts.js +241 -0
  14. package/dist/tasksets.js +24 -0
  15. package/dist/telemetry/index.js +4 -0
  16. package/dist/telemetry-analysis.js +183 -0
  17. package/dist/telemetry-bundle.js +60 -0
  18. package/dist/telemetry-catalog.js +50 -0
  19. package/dist/telemetry.js +112 -0
  20. package/dist/types/artifact-verification.d.ts +31 -0
  21. package/dist/types/artifact-verification.d.ts.map +1 -0
  22. package/dist/types/builtin-benchmarks/harness-refiner.d.ts +22 -0
  23. package/dist/types/builtin-benchmarks/harness-refiner.d.ts.map +1 -1
  24. package/dist/types/conformance.d.ts +44 -0
  25. package/dist/types/conformance.d.ts.map +1 -1
  26. package/dist/types/evidence/conformance.d.ts +24 -24
  27. package/dist/types/evidence/contracts.d.ts +42 -42
  28. package/dist/types/execution-contracts.d.ts +806 -0
  29. package/dist/types/execution-contracts.d.ts.map +1 -0
  30. package/dist/types/execution-receipts.d.ts +55 -0
  31. package/dist/types/execution-receipts.d.ts.map +1 -0
  32. package/dist/types/graders.d.ts.map +1 -1
  33. package/dist/types/index.d.ts +10 -0
  34. package/dist/types/index.d.ts.map +1 -1
  35. package/dist/types/learned-preference.d.ts +282 -0
  36. package/dist/types/learned-preference.d.ts.map +1 -0
  37. package/dist/types/preferences.d.ts +583 -0
  38. package/dist/types/preferences.d.ts.map +1 -0
  39. package/dist/types/review-conformance.d.ts +20 -15
  40. package/dist/types/review-conformance.d.ts.map +1 -1
  41. package/dist/types/rollouts.d.ts +221 -0
  42. package/dist/types/rollouts.d.ts.map +1 -0
  43. package/dist/types/tasksets.d.ts +89 -0
  44. package/dist/types/tasksets.d.ts.map +1 -1
  45. package/dist/types/telemetry/index.d.ts +5 -0
  46. package/dist/types/telemetry/index.d.ts.map +1 -0
  47. package/dist/types/telemetry-analysis.d.ts +116 -0
  48. package/dist/types/telemetry-analysis.d.ts.map +1 -0
  49. package/dist/types/telemetry-bundle.d.ts +309 -0
  50. package/dist/types/telemetry-bundle.d.ts.map +1 -0
  51. package/dist/types/telemetry-catalog.d.ts +269 -0
  52. package/dist/types/telemetry-catalog.d.ts.map +1 -0
  53. package/dist/types/telemetry.d.ts +266 -0
  54. package/dist/types/telemetry.d.ts.map +1 -0
  55. package/package.json +20 -2
  56. package/schemas/telemetry/v1/evidence-completeness.schema.json +82 -0
  57. package/schemas/telemetry/v1/evidence-reference.schema.json +41 -0
  58. package/schemas/telemetry/v1/metric-definition.schema.json +96 -0
  59. package/schemas/telemetry/v1/metric-observation.schema.json +182 -0
  60. package/schemas/telemetry/v1/run-metric-summary.schema.json +98 -0
  61. package/schemas/telemetry/v1/run-telemetry-batch.schema.json +405 -0
  62. package/schemas/telemetry/v1/run-telemetry-event.schema.json +201 -0
  63. package/schemas/telemetry/v1/telemetry-cohort.schema.json +100 -0
  64. package/schemas/telemetry/v1/telemetry-export-bundle.schema.json +655 -0
@@ -0,0 +1,803 @@
1
+ import { z } from "zod";
2
+ import { ImmutableArtifactRefSchema, ImmutableReleaseRefSchema, MetadataSchema, ReleaseHashSchema, ReleaseIdSchema, ReleaseTimestampSchema, contentHash, } from "@openpond/harness";
3
+ import { ArtifactManifestContentSchema, ArtifactManifestSchema, RewardComponentReceiptSchema, } from "./execution-contracts.js";
4
+ import { AttemptReceiptSchema, RunManifestSchema, assertComparableRunManifests, verifyAttemptReceipt, } from "./runs.js";
5
+ import { TasksetReleaseContentSchema, TasksetReleaseSchema, } from "./tasksets.js";
6
+ export const PreferenceComparisonPurposeSchema = z.enum([
7
+ "calibration",
8
+ "training_reward",
9
+ "validation",
10
+ "frozen_eval",
11
+ ]);
12
+ export const PreferenceReviewerKindSchema = z.enum([
13
+ "human",
14
+ "model",
15
+ "deterministic",
16
+ "fixture",
17
+ ]);
18
+ export const PreferenceRewardScopeSchema = z.enum([
19
+ "production",
20
+ "synthetic_smoke",
21
+ ]);
22
+ export const PreferenceRendererSchema = z.enum([
23
+ "markdown",
24
+ "text",
25
+ "code",
26
+ "json",
27
+ "image",
28
+ ]);
29
+ const PreferenceCriterionSchema = z.object({
30
+ id: ReleaseIdSchema,
31
+ label: z.string().trim().min(1).max(500),
32
+ instruction: z.string().trim().min(1).max(10_000),
33
+ weight: z.number().nonnegative().max(1_000),
34
+ }).strict();
35
+ const CandidatePresentationPartSchema = z.object({
36
+ source: z.enum(["attempt_output", "artifact"]),
37
+ path: z.string().trim().min(1).max(2_000),
38
+ renderer: PreferenceRendererSchema,
39
+ }).strict();
40
+ const CandidatePresentationSpecSchema = z.object({
41
+ showTaskPrompt: z.boolean(),
42
+ randomizeCandidateOrder: z.boolean(),
43
+ hideModelIdentity: z.boolean(),
44
+ parts: z.array(CandidatePresentationPartSchema).min(1).max(20),
45
+ }).strict().superRefine((presentation, context) => {
46
+ if (!presentation.parts.some((part) => part.renderer === "markdown" || part.renderer === "text")) {
47
+ context.addIssue({
48
+ code: "custom",
49
+ path: ["parts"],
50
+ message: "A preference presentation must expose text or Markdown content.",
51
+ });
52
+ }
53
+ });
54
+ const PreferenceAssignmentPolicySchema = z.object({
55
+ strategy: z.literal("randomized_blinded_v1"),
56
+ maxAssignmentsPerCandidate: z.number().int().positive().max(10_000),
57
+ }).strict();
58
+ const PreferenceAggregationPolicySchema = z.object({
59
+ algorithm: z.literal("mean_pairwise_win_fraction_v1"),
60
+ quorum: z.number().int().positive().max(100),
61
+ rejectAllThreshold: z.number().min(0.5).max(1),
62
+ }).strict();
63
+ const PreferenceRewardProjectionSchema = z.object({
64
+ algorithm: z.literal("pairwise_win_fraction_v1"),
65
+ verifierId: ReleaseIdSchema,
66
+ verifierVersion: z.string().trim().min(1).max(100),
67
+ weight: z.number().positive().max(1_000),
68
+ }).strict();
69
+ const PreferenceCalibrationPolicySchema = z.object({
70
+ minimumSamples: z.number().int().positive().max(100_000),
71
+ minimumOrderAgreement: z.number().min(0).max(1),
72
+ minimumTieAgreement: z.number().min(0).max(1),
73
+ minimumOrderSwapAgreement: z.number().min(0).max(1),
74
+ }).strict();
75
+ export const PreferenceComparisonReleaseContentSchema = z.object({
76
+ schemaVersion: z.literal("openpond.preferenceComparisonRelease.v1"),
77
+ id: ReleaseIdSchema,
78
+ revision: z.number().int().positive(),
79
+ tasksetRelease: ImmutableReleaseRefSchema,
80
+ candidateCount: z.number().int().min(2).max(4),
81
+ resultMode: z.literal("ordered_tie_groups"),
82
+ allowTies: z.boolean(),
83
+ allowRejectAll: z.boolean(),
84
+ presentation: CandidatePresentationSpecSchema,
85
+ rubricRef: ImmutableArtifactRefSchema,
86
+ criteria: z.array(PreferenceCriterionSchema).max(20),
87
+ assignment: PreferenceAssignmentPolicySchema,
88
+ aggregation: PreferenceAggregationPolicySchema,
89
+ rewardProjection: PreferenceRewardProjectionSchema,
90
+ calibration: PreferenceCalibrationPolicySchema,
91
+ metadata: MetadataSchema,
92
+ }).strict().superRefine((release, context) => {
93
+ const criteria = release.criteria.map((criterion) => criterion.id);
94
+ if (new Set(criteria).size !== criteria.length) {
95
+ context.addIssue({ code: "custom", path: ["criteria"], message: "Preference criterion IDs must be unique." });
96
+ }
97
+ });
98
+ export const PreferenceComparisonReleaseSchema = PreferenceComparisonReleaseContentSchema
99
+ .extend({ contentHash: ReleaseHashSchema })
100
+ .strict();
101
+ const PreferenceTaskReferenceSchema = z.object({
102
+ id: ReleaseIdSchema,
103
+ contentHash: ReleaseHashSchema,
104
+ }).strict();
105
+ const PreferenceComparisonLineageSchema = z.object({
106
+ tasksetRelease: ImmutableReleaseRefSchema,
107
+ harnessReleases: z.array(ImmutableReleaseRefSchema).min(1).max(4),
108
+ runManifestRefs: z.array(ImmutableReleaseRefSchema).min(1).max(4),
109
+ harnessCompatibilityReceiptRefs: z.array(ImmutableReleaseRefSchema).max(6),
110
+ environmentRelease: ImmutableReleaseRefSchema,
111
+ verifierSetRelease: ImmutableReleaseRefSchema,
112
+ toolContractHash: ReleaseHashSchema,
113
+ policyHash: ReleaseHashSchema,
114
+ }).strict();
115
+ const ComparisonCandidateSchema = z.object({
116
+ attemptRef: ImmutableReleaseRefSchema,
117
+ runManifestRef: ImmutableReleaseRefSchema,
118
+ artifactManifestRef: ImmutableReleaseRefSchema,
119
+ visibleArtifactIds: z.array(ReleaseIdSchema).max(100_000),
120
+ }).strict().superRefine((candidate, context) => {
121
+ if (new Set(candidate.visibleArtifactIds).size !== candidate.visibleArtifactIds.length) {
122
+ context.addIssue({
123
+ code: "custom",
124
+ path: ["visibleArtifactIds"],
125
+ message: "Visible artifact IDs must be unique per candidate.",
126
+ });
127
+ }
128
+ });
129
+ const ComparisonAssignmentBaseSchema = z.object({
130
+ schemaVersion: z.literal("openpond.comparisonAssignment.v1"),
131
+ id: ReleaseIdSchema,
132
+ comparisonRelease: ImmutableReleaseRefSchema,
133
+ taskRef: PreferenceTaskReferenceSchema,
134
+ lineage: PreferenceComparisonLineageSchema,
135
+ candidates: z.array(ComparisonCandidateSchema).min(2).max(4),
136
+ presentedCandidateOrder: z.array(ReleaseIdSchema).min(2).max(4),
137
+ purpose: PreferenceComparisonPurposeSchema,
138
+ createdAt: ReleaseTimestampSchema,
139
+ metadata: MetadataSchema,
140
+ }).strict().superRefine((assignment, context) => {
141
+ const candidateIds = assignment.candidates.map((candidate) => candidate.attemptRef.id);
142
+ if (new Set(candidateIds).size !== candidateIds.length) {
143
+ context.addIssue({ code: "custom", path: ["candidates"], message: "An assignment cannot contain the same attempt twice." });
144
+ }
145
+ if (new Set(assignment.presentedCandidateOrder).size !== assignment.presentedCandidateOrder.length
146
+ || assignment.presentedCandidateOrder.length !== candidateIds.length
147
+ || assignment.presentedCandidateOrder.some((id) => !candidateIds.includes(id))) {
148
+ context.addIssue({
149
+ code: "custom",
150
+ path: ["presentedCandidateOrder"],
151
+ message: "Presented candidate order must contain every assignment candidate exactly once.",
152
+ });
153
+ }
154
+ });
155
+ export const ComparisonAssignmentContentSchema = ComparisonAssignmentBaseSchema;
156
+ export const ComparisonAssignmentSchema = ComparisonAssignmentBaseSchema
157
+ .extend({ contentHash: ReleaseHashSchema })
158
+ .strict()
159
+ .superRefine((assignment, context) => {
160
+ const candidateIds = assignment.candidates.map((candidate) => candidate.attemptRef.id);
161
+ if (new Set(candidateIds).size !== candidateIds.length) {
162
+ context.addIssue({ code: "custom", path: ["candidates"], message: "An assignment cannot contain the same attempt twice." });
163
+ }
164
+ });
165
+ const PreferenceOrderSchema = z.array(z.array(ReleaseIdSchema).min(1).max(4)).max(4);
166
+ const PreferenceReviewerSchema = z.discriminatedUnion("kind", [
167
+ z.object({
168
+ kind: z.enum(["human", "model", "deterministic"]),
169
+ releaseRef: ImmutableReleaseRefSchema,
170
+ }).strict(),
171
+ z.object({
172
+ kind: z.literal("fixture"),
173
+ releaseRef: ImmutableReleaseRefSchema,
174
+ fixtureRelease: ImmutableReleaseRefSchema,
175
+ labelSource: z.literal("synthetic"),
176
+ qualificationEligibility: z.literal("smoke_only"),
177
+ }).strict(),
178
+ ]);
179
+ const PreferenceReceiptBaseSchema = z.object({
180
+ schemaVersion: z.literal("openpond.preferenceReceipt.v1"),
181
+ id: ReleaseIdSchema,
182
+ assignmentRef: ImmutableReleaseRefSchema,
183
+ reviewer: PreferenceReviewerSchema,
184
+ order: PreferenceOrderSchema,
185
+ rejectAll: z.boolean(),
186
+ criterionScores: z.record(ReleaseIdSchema, z.record(ReleaseIdSchema, z.number().finite().min(0).max(1))),
187
+ feedbackArtifactRef: ImmutableArtifactRefSchema.nullable(),
188
+ startedAt: ReleaseTimestampSchema,
189
+ completedAt: ReleaseTimestampSchema,
190
+ metadata: MetadataSchema,
191
+ }).strict().superRefine((receipt, context) => {
192
+ if (receipt.rejectAll && receipt.order.length !== 0) {
193
+ context.addIssue({ code: "custom", path: ["order"], message: "A reject-all preference receipt cannot contain an order." });
194
+ }
195
+ if (!receipt.rejectAll && receipt.order.length === 0) {
196
+ context.addIssue({ code: "custom", path: ["order"], message: "A non-reject preference receipt requires an ordered result." });
197
+ }
198
+ if (Date.parse(receipt.completedAt) < Date.parse(receipt.startedAt)) {
199
+ context.addIssue({ code: "custom", path: ["completedAt"], message: "A preference receipt cannot complete before it starts." });
200
+ }
201
+ });
202
+ export const PreferenceReceiptContentSchema = PreferenceReceiptBaseSchema;
203
+ export const PreferenceReceiptSchema = PreferenceReceiptBaseSchema
204
+ .extend({ contentHash: ReleaseHashSchema })
205
+ .strict();
206
+ const PreferenceAggregateSchema = z.object({
207
+ schemaVersion: z.literal("openpond.preferenceAggregationReceipt.v1"),
208
+ id: ReleaseIdSchema,
209
+ assignmentRef: ImmutableReleaseRefSchema,
210
+ comparisonRelease: ImmutableReleaseRefSchema,
211
+ receiptRefs: z.array(ImmutableReleaseRefSchema).min(1).max(100),
212
+ order: PreferenceOrderSchema,
213
+ rejectAll: z.boolean(),
214
+ pairwiseWinFractions: z.record(ReleaseIdSchema, z.number().min(0).max(1)),
215
+ quorum: z.number().int().positive().max(100),
216
+ createdAt: ReleaseTimestampSchema,
217
+ metadata: MetadataSchema,
218
+ }).strict().superRefine((aggregate, context) => {
219
+ if (aggregate.rejectAll && aggregate.order.length !== 0) {
220
+ context.addIssue({ code: "custom", path: ["order"], message: "A reject-all aggregate cannot contain an order." });
221
+ }
222
+ });
223
+ export const PreferenceAggregationReceiptContentSchema = PreferenceAggregateSchema;
224
+ export const PreferenceAggregationReceiptSchema = PreferenceAggregateSchema
225
+ .extend({ contentHash: ReleaseHashSchema })
226
+ .strict();
227
+ const PreferenceCalibrationReportBaseSchema = z.object({
228
+ schemaVersion: z.literal("openpond.preferenceCalibrationReport.v1"),
229
+ id: ReleaseIdSchema,
230
+ comparisonRelease: ImmutableReleaseRefSchema,
231
+ automatedReviewer: ImmutableReleaseRefSchema,
232
+ humanReceiptRefs: z.array(ImmutableReleaseRefSchema).min(1).max(100_000),
233
+ modelReceiptRefs: z.array(ImmutableReleaseRefSchema).min(1).max(100_000),
234
+ sampleCount: z.number().int().positive().max(100_000),
235
+ orderAgreement: z.number().min(0).max(1),
236
+ tieAgreement: z.number().min(0).max(1),
237
+ orderSwapAgreement: z.number().min(0).max(1),
238
+ passed: z.boolean(),
239
+ createdAt: ReleaseTimestampSchema,
240
+ metadata: MetadataSchema,
241
+ }).strict();
242
+ export const PreferenceCalibrationReportContentSchema = PreferenceCalibrationReportBaseSchema;
243
+ export const PreferenceCalibrationReportSchema = PreferenceCalibrationReportBaseSchema
244
+ .extend({ contentHash: ReleaseHashSchema })
245
+ .strict();
246
+ export function createPreferenceComparisonRelease(input) {
247
+ const content = PreferenceComparisonReleaseContentSchema.parse(input);
248
+ return PreferenceComparisonReleaseSchema.parse({ ...content, contentHash: contentHash(content) });
249
+ }
250
+ export function verifyPreferenceComparisonRelease(value) {
251
+ return verifyHashed(value, PreferenceComparisonReleaseContentSchema, PreferenceComparisonReleaseSchema);
252
+ }
253
+ export function createComparisonAssignment(input) {
254
+ const release = verifyComparisonRelease(input.comparisonRelease);
255
+ const taskset = verifyTasksetRelease(input.taskset);
256
+ if (!sameRef(release.tasksetRelease, ref(taskset))) {
257
+ throw new Error("Preference Comparison Release does not belong to the supplied Taskset Release.");
258
+ }
259
+ if (input.candidates.length !== release.candidateCount) {
260
+ throw new Error("Comparison assignment candidate count does not match the immutable comparison release.");
261
+ }
262
+ const requiresVisibleArtifact = release.presentation.parts.some((part) => part.source === "artifact");
263
+ const normalized = input.candidates.map((candidate) => validateComparisonCandidate(candidate, taskset, requiresVisibleArtifact));
264
+ const first = normalized[0];
265
+ for (const candidate of normalized.slice(1)) {
266
+ if (candidate.attempt.taskId !== first.attempt.taskId) {
267
+ throw new Error("Preference comparison candidates must belong to the same Taskset task.");
268
+ }
269
+ const compatibility = compatibilityForRunManifests(first.runManifest, candidate.runManifest, input.harnessCompatibilityReceipts ?? []);
270
+ if (compatibility && sameRef(compatibility.baseHarnessRelease, candidate.runManifest.harnessRelease)) {
271
+ assertComparableRunManifests(candidate.runManifest, first.runManifest, compatibility);
272
+ }
273
+ else {
274
+ assertComparableRunManifests(first.runManifest, candidate.runManifest, compatibility);
275
+ }
276
+ }
277
+ const task = taskset.tasks.find((record) => record.id === first.attempt.taskId);
278
+ if (!task)
279
+ throw new Error("Comparison candidates reference a task absent from the supplied Taskset Release.");
280
+ if (!taskset.environmentRelease || !taskset.verifierSetRelease) {
281
+ throw new Error("Preference comparison requires a Taskset bound to immutable Environment and Verifier Set Releases.");
282
+ }
283
+ const candidateIds = normalized.map((candidate) => candidate.attempt.id);
284
+ const presentedCandidateOrder = [...(input.presentedCandidateOrder ?? candidateIds)];
285
+ const content = ComparisonAssignmentContentSchema.parse({
286
+ schemaVersion: "openpond.comparisonAssignment.v1",
287
+ id: input.id,
288
+ comparisonRelease: ref(release),
289
+ taskRef: {
290
+ id: task.id,
291
+ contentHash: contentHash({ taskId: task.id, tasksetRelease: ref(taskset) }),
292
+ },
293
+ lineage: {
294
+ tasksetRelease: ref(taskset),
295
+ harnessReleases: uniqueRefs(normalized.map((candidate) => candidate.runManifest.harnessRelease)),
296
+ runManifestRefs: uniqueRefs(normalized.map((candidate) => ref(candidate.runManifest))),
297
+ harnessCompatibilityReceiptRefs: uniqueRefs((input.harnessCompatibilityReceipts ?? []).map(ref)),
298
+ environmentRelease: taskset.environmentRelease,
299
+ verifierSetRelease: taskset.verifierSetRelease,
300
+ toolContractHash: contentHash(taskset.tools),
301
+ policyHash: contentHash(taskset.policy),
302
+ },
303
+ candidates: normalized.map((candidate) => ({
304
+ attemptRef: ref(candidate.attempt),
305
+ runManifestRef: ref(candidate.runManifest),
306
+ artifactManifestRef: ref(candidate.artifactManifest),
307
+ visibleArtifactIds: [...candidate.visibleArtifactIds],
308
+ })),
309
+ presentedCandidateOrder,
310
+ purpose: input.purpose,
311
+ createdAt: input.createdAt,
312
+ metadata: input.metadata ?? {},
313
+ });
314
+ return ComparisonAssignmentSchema.parse({ ...content, contentHash: contentHash(content) });
315
+ }
316
+ export function verifyComparisonAssignment(value) {
317
+ return verifyHashed(value, ComparisonAssignmentContentSchema, ComparisonAssignmentSchema);
318
+ }
319
+ export function validateComparisonAssignment(assignment, release) {
320
+ const parsedAssignment = verifyAssignment(assignment);
321
+ const parsedRelease = verifyComparisonRelease(release);
322
+ if (!sameRef(parsedAssignment.comparisonRelease, ref(parsedRelease))) {
323
+ throw new Error("Comparison assignment does not reference the supplied comparison release.");
324
+ }
325
+ if (parsedAssignment.candidates.length !== parsedRelease.candidateCount) {
326
+ throw new Error("Comparison assignment does not satisfy its release candidate count.");
327
+ }
328
+ }
329
+ export function createPreferenceReceipt(input) {
330
+ const assignment = verifyAssignment(input.assignment);
331
+ const release = verifyComparisonRelease(input.comparisonRelease);
332
+ validateComparisonAssignment(assignment, release);
333
+ const content = PreferenceReceiptContentSchema.parse({
334
+ schemaVersion: "openpond.preferenceReceipt.v1",
335
+ id: input.id,
336
+ assignmentRef: ref(assignment),
337
+ reviewer: input.reviewer,
338
+ order: input.order.map((group) => [...group]),
339
+ rejectAll: input.rejectAll,
340
+ criterionScores: input.criterionScores ?? {},
341
+ feedbackArtifactRef: input.feedbackArtifactRef ?? null,
342
+ startedAt: input.startedAt,
343
+ completedAt: input.completedAt,
344
+ metadata: input.metadata ?? {},
345
+ });
346
+ validatePreferenceResult({ order: content.order, rejectAll: content.rejectAll }, assignment, release);
347
+ validateCriterionScores(content.criterionScores, assignment, release);
348
+ return PreferenceReceiptSchema.parse({ ...content, contentHash: contentHash(content) });
349
+ }
350
+ export function createSyntheticFixturePreferenceReceipt(input) {
351
+ const candidateIds = input.assignment.candidates.map((candidate) => candidate.attemptRef.id);
352
+ const ratingIds = Object.keys(input.ratings);
353
+ if (ratingIds.length !== candidateIds.length
354
+ || candidateIds.some((candidateId) => !input.ratings[candidateId])
355
+ || ratingIds.some((candidateId) => !candidateIds.includes(candidateId))) {
356
+ throw new Error("Synthetic fixture ratings must label every assignment candidate exactly once.");
357
+ }
358
+ const orderedRatings = ["love", "like", "reject"];
359
+ const rejectAll = candidateIds.every((candidateId) => input.ratings[candidateId] === "reject");
360
+ const order = rejectAll ? [] : orderedRatings
361
+ .map((rating) => candidateIds.filter((candidateId) => input.ratings[candidateId] === rating))
362
+ .filter((bucket) => bucket.length > 0);
363
+ const score = { love: 1, like: 0.5, reject: 0 };
364
+ return createPreferenceReceipt({
365
+ id: input.id,
366
+ assignment: input.assignment,
367
+ comparisonRelease: input.comparisonRelease,
368
+ reviewer: {
369
+ kind: "fixture",
370
+ releaseRef: input.labelerRelease,
371
+ fixtureRelease: input.fixtureRelease,
372
+ labelSource: "synthetic",
373
+ qualificationEligibility: "smoke_only",
374
+ },
375
+ order,
376
+ rejectAll,
377
+ criterionScores: Object.fromEntries(candidateIds.map((candidateId) => [
378
+ candidateId,
379
+ Object.fromEntries(input.comparisonRelease.criteria.map((criterion) => [
380
+ criterion.id,
381
+ score[input.ratings[candidateId]],
382
+ ])),
383
+ ])),
384
+ startedAt: input.startedAt,
385
+ completedAt: input.completedAt,
386
+ metadata: input.metadata ?? {},
387
+ });
388
+ }
389
+ export function verifyPreferenceReceipt(value) {
390
+ return verifyHashed(value, PreferenceReceiptContentSchema, PreferenceReceiptSchema);
391
+ }
392
+ export function aggregatePreferenceReceipts(input) {
393
+ const assignment = verifyAssignment(input.assignment);
394
+ const release = verifyComparisonRelease(input.comparisonRelease);
395
+ validateComparisonAssignment(assignment, release);
396
+ const receipts = input.receipts.map((receipt) => verifyReceipt(receipt));
397
+ if (receipts.length < release.aggregation.quorum) {
398
+ throw new Error(`Preference aggregation requires a quorum of ${release.aggregation.quorum} receipts.`);
399
+ }
400
+ for (const receipt of receipts) {
401
+ if (!sameRef(receipt.assignmentRef, ref(assignment))) {
402
+ throw new Error("Preference aggregation receipts must belong to the same assignment.");
403
+ }
404
+ validatePreferenceResult(receipt, assignment, release);
405
+ }
406
+ const rejectCount = receipts.filter((receipt) => receipt.rejectAll).length;
407
+ const rejectAll = rejectCount / receipts.length >= release.aggregation.rejectAllThreshold;
408
+ const scores = pairwiseScoresForReceipts(receipts, assignment);
409
+ const order = rejectAll ? [] : orderForScores(scores, assignment);
410
+ const content = PreferenceAggregationReceiptContentSchema.parse({
411
+ schemaVersion: "openpond.preferenceAggregationReceipt.v1",
412
+ id: input.id,
413
+ assignmentRef: ref(assignment),
414
+ comparisonRelease: ref(release),
415
+ receiptRefs: receipts.map(ref),
416
+ order,
417
+ rejectAll,
418
+ pairwiseWinFractions: scores,
419
+ quorum: release.aggregation.quorum,
420
+ createdAt: input.createdAt,
421
+ metadata: input.metadata ?? {},
422
+ });
423
+ return PreferenceAggregationReceiptSchema.parse({ ...content, contentHash: contentHash(content) });
424
+ }
425
+ export function verifyPreferenceAggregationReceipt(value) {
426
+ return verifyHashed(value, PreferenceAggregationReceiptContentSchema, PreferenceAggregationReceiptSchema);
427
+ }
428
+ export function createPreferenceCalibrationReport(input) {
429
+ const release = verifyComparisonRelease(input.comparisonRelease);
430
+ if (input.pairs.length < release.calibration.minimumSamples) {
431
+ throw new Error(`Preference calibration requires at least ${release.calibration.minimumSamples} human/model pairs.`);
432
+ }
433
+ const normalized = input.pairs.map((pair) => validateCalibrationPair(pair, release));
434
+ const firstReviewer = normalized[0].model.reviewer.releaseRef;
435
+ if (normalized.some((pair) => !sameRef(pair.model.reviewer.releaseRef, firstReviewer))) {
436
+ throw new Error("Preference calibration requires model receipts from one immutable automated reviewer release.");
437
+ }
438
+ const orderAgreement = mean(normalized.map((pair) => pairwiseAgreement(pair.human, pair.model, pair.assignment)));
439
+ const tieAgreement = mean(normalized.map((pair) => tieAgreementScore(pair.human, pair.model, pair.assignment)));
440
+ const orderSwapAgreement = mean(normalized.map((pair) => pair.swappedModel
441
+ ? pairwiseAgreement(pair.model, pair.swappedModel, pair.assignment)
442
+ : 1));
443
+ const passed = orderAgreement >= release.calibration.minimumOrderAgreement
444
+ && tieAgreement >= release.calibration.minimumTieAgreement
445
+ && orderSwapAgreement >= release.calibration.minimumOrderSwapAgreement;
446
+ const content = PreferenceCalibrationReportContentSchema.parse({
447
+ schemaVersion: "openpond.preferenceCalibrationReport.v1",
448
+ id: input.id,
449
+ comparisonRelease: ref(release),
450
+ automatedReviewer: firstReviewer,
451
+ humanReceiptRefs: normalized.map((pair) => ref(pair.human)),
452
+ modelReceiptRefs: normalized.map((pair) => ref(pair.model)),
453
+ sampleCount: normalized.length,
454
+ orderAgreement,
455
+ tieAgreement,
456
+ orderSwapAgreement,
457
+ passed,
458
+ createdAt: input.createdAt,
459
+ metadata: input.metadata ?? {},
460
+ });
461
+ return PreferenceCalibrationReportSchema.parse({ ...content, contentHash: contentHash(content) });
462
+ }
463
+ export function verifyPreferenceCalibrationReport(value) {
464
+ return verifyHashed(value, PreferenceCalibrationReportContentSchema, PreferenceCalibrationReportSchema);
465
+ }
466
+ export function projectPairwiseWinFraction(input) {
467
+ const assignment = verifyAssignment(input.assignment);
468
+ const release = verifyComparisonRelease(input.comparisonRelease);
469
+ validateComparisonAssignment(assignment, release);
470
+ if (input.result.schemaVersion === "openpond.preferenceReceipt.v1") {
471
+ const receipt = verifyReceipt(input.result);
472
+ if (!sameRef(receipt.assignmentRef, ref(assignment))) {
473
+ throw new Error("Preference receipt does not belong to the supplied assignment.");
474
+ }
475
+ validatePreferenceResult(receipt, assignment, release);
476
+ return pairwiseScoresForResult(receipt, assignment);
477
+ }
478
+ const aggregate = verifyAggregate(input.result);
479
+ if (!sameRef(aggregate.assignmentRef, ref(assignment)) || !sameRef(aggregate.comparisonRelease, ref(release))) {
480
+ throw new Error("Preference aggregate does not belong to the supplied assignment and release.");
481
+ }
482
+ validatePreferenceResult(aggregate, assignment, release);
483
+ return aggregate.rejectAll ? zeroScores(assignment) : aggregate.pairwiseWinFractions;
484
+ }
485
+ export function createPreferenceRewardComponents(input) {
486
+ const assignment = verifyAssignment(input.assignment);
487
+ const release = verifyComparisonRelease(input.comparisonRelease);
488
+ const scores = projectPairwiseWinFraction({ assignment, comparisonRelease: release, result: input.result });
489
+ const eligible = new Map((input.candidates ?? []).map((candidate) => [candidate.attemptRef.id, candidate]));
490
+ const modelReceipt = input.result.schemaVersion === "openpond.preferenceReceipt.v1"
491
+ ? input.result
492
+ : null;
493
+ const automatedReceipt = modelReceipt?.reviewer.kind === "model";
494
+ const fixtureReceipt = modelReceipt?.reviewer.kind === "fixture";
495
+ const rewardScope = PreferenceRewardScopeSchema.parse(input.rewardScope ?? "production");
496
+ const admittedReviewer = fixtureReceipt
497
+ ? rewardScope === "synthetic_smoke"
498
+ : !automatedReceipt || isCalibratedAutomatedReviewer(modelReceipt, release, input.calibrationReport ?? null);
499
+ return Object.fromEntries(assignment.candidates.map((candidate) => {
500
+ const candidateEligibility = eligible.get(candidate.attemptRef.id);
501
+ const canScore = candidateEligibility?.eligible ?? true;
502
+ const score = scores[candidate.attemptRef.id];
503
+ const component = canScore && admittedReviewer
504
+ ? RewardComponentReceiptSchema.parse({
505
+ verifierId: release.rewardProjection.verifierId,
506
+ verifierVersion: release.rewardProjection.verifierVersion,
507
+ status: "scored",
508
+ rawScore: score,
509
+ normalizedScore: score,
510
+ weight: release.rewardProjection.weight,
511
+ passed: score > 0,
512
+ hardGate: false,
513
+ rewardEligible: true,
514
+ rewardContribution: score,
515
+ failureOwner: null,
516
+ feedback: [],
517
+ visibleEvidenceRefs: [],
518
+ privilegedEvidenceRefs: [],
519
+ metadata: {
520
+ comparisonAssignment: ref(assignment),
521
+ preferenceResult: ref(input.result),
522
+ ...(input.calibrationReport ? { calibrationReport: ref(input.calibrationReport) } : {}),
523
+ },
524
+ })
525
+ : RewardComponentReceiptSchema.parse({
526
+ verifierId: release.rewardProjection.verifierId,
527
+ verifierVersion: release.rewardProjection.verifierVersion,
528
+ status: "unscorable",
529
+ rawScore: null,
530
+ normalizedScore: null,
531
+ weight: release.rewardProjection.weight,
532
+ passed: false,
533
+ hardGate: false,
534
+ rewardEligible: false,
535
+ rewardContribution: null,
536
+ failureOwner: candidateEligibility?.failureOwner ?? "verifier",
537
+ feedback: [canScore
538
+ ? fixtureReceipt
539
+ ? "Synthetic fixture preference evidence is admitted only to synthetic-smoke reward scope."
540
+ : "Automated preference reviewer is uncalibrated or inconsistent."
541
+ : "Candidate is not eligible for comparative preference scoring."],
542
+ visibleEvidenceRefs: [],
543
+ privilegedEvidenceRefs: [],
544
+ metadata: {
545
+ comparisonAssignment: ref(assignment),
546
+ preferenceResult: ref(input.result),
547
+ ...(input.calibrationReport ? { calibrationReport: ref(input.calibrationReport) } : {}),
548
+ },
549
+ });
550
+ return [candidate.attemptRef.id, component];
551
+ }));
552
+ }
553
+ function validateComparisonCandidate(input, taskset, requiresVisibleArtifact) {
554
+ const attempt = AttemptReceiptSchema.parse(input.attempt);
555
+ if (!verifyAttemptReceipt(attempt))
556
+ throw new Error("Comparison candidate Attempt Receipt has an invalid content hash.");
557
+ const artifactManifest = ArtifactManifestSchema.parse(input.artifactManifest);
558
+ if (!verifyArtifactManifest(artifactManifest))
559
+ throw new Error("Comparison candidate Artifact Manifest has an invalid content hash.");
560
+ const runManifest = RunManifestSchema.parse(input.runManifest);
561
+ if (!verifyRunManifest(runManifest))
562
+ throw new Error("Comparison candidate Run Manifest has an invalid content hash.");
563
+ if (!sameRef(attempt.runManifest, ref(runManifest))) {
564
+ throw new Error("Comparison candidate Attempt Receipt does not belong to its supplied Run Manifest.");
565
+ }
566
+ if (!sameRef(runManifest.tasksetRelease, ref(taskset))) {
567
+ throw new Error("Comparison candidate Run Manifest does not belong to the supplied Taskset Release.");
568
+ }
569
+ if (!sameRef(artifactManifest.attemptRef, ref(attempt))) {
570
+ throw new Error("Comparison candidate Artifact Manifest does not belong to its Attempt Receipt.");
571
+ }
572
+ if (!attempt.terminal || attempt.failureClass !== null) {
573
+ throw new Error("Only completed, non-failed attempts can be assigned for comparative review.");
574
+ }
575
+ const visibleArtifactIds = [...input.visibleArtifactIds];
576
+ const visible = new Set(visibleArtifactIds);
577
+ const available = new Set(artifactManifest.entries
578
+ .filter((entry) => entry.status === "collected" && entry.artifact !== null)
579
+ .map((entry) => entry.artifact.id));
580
+ if (requiresVisibleArtifact && !visibleArtifactIds.length) {
581
+ throw new Error("Comparison presentation requires each candidate to expose a reviewable artifact.");
582
+ }
583
+ if (visibleArtifactIds.some((id) => !available.has(id))) {
584
+ throw new Error("Comparison candidate exposes an artifact that is missing, uncollected, or unreviewable.");
585
+ }
586
+ if (visible.size !== visibleArtifactIds.length)
587
+ throw new Error("Comparison candidate visible artifacts must be unique.");
588
+ return { attempt, artifactManifest, runManifest, visibleArtifactIds };
589
+ }
590
+ function validatePreferenceResult(result, assignment, release) {
591
+ const candidateIds = assignment.candidates.map((candidate) => candidate.attemptRef.id);
592
+ if (result.rejectAll) {
593
+ if (!release.allowRejectAll)
594
+ throw new Error("This comparison release does not allow reject-all results.");
595
+ if (result.order.length)
596
+ throw new Error("Reject-all results cannot include ordered candidates.");
597
+ return;
598
+ }
599
+ const flattened = result.order.flat();
600
+ if (new Set(flattened).size !== flattened.length || flattened.length !== candidateIds.length || flattened.some((id) => !candidateIds.includes(id))) {
601
+ throw new Error("Preference result must rank every assignment candidate exactly once.");
602
+ }
603
+ if (!release.allowTies && result.order.some((group) => group.length > 1)) {
604
+ throw new Error("This comparison release does not allow tied candidates.");
605
+ }
606
+ }
607
+ function validateCriterionScores(scores, assignment, release) {
608
+ const candidateIds = new Set(assignment.candidates.map((candidate) => candidate.attemptRef.id));
609
+ const criteria = new Set(release.criteria.map((criterion) => criterion.id));
610
+ for (const [candidateId, candidateScores] of Object.entries(scores)) {
611
+ if (!candidateIds.has(candidateId))
612
+ throw new Error("Criterion scores include a candidate absent from the assignment.");
613
+ for (const criterionId of Object.keys(candidateScores)) {
614
+ if (!criteria.has(criterionId))
615
+ throw new Error("Criterion scores include a criterion absent from the comparison release.");
616
+ }
617
+ }
618
+ }
619
+ function pairwiseScoresForReceipts(receipts, assignment) {
620
+ const sums = zeroScores(assignment);
621
+ for (const receipt of receipts) {
622
+ const scores = pairwiseScoresForResult(receipt, assignment);
623
+ for (const [candidateId, score] of Object.entries(scores))
624
+ sums[candidateId] += score;
625
+ }
626
+ return Object.fromEntries(Object.entries(sums).map(([candidateId, score]) => [candidateId, score / receipts.length]));
627
+ }
628
+ function pairwiseScoresForResult(result, assignment) {
629
+ if (result.rejectAll)
630
+ return zeroScores(assignment);
631
+ const candidateIds = assignment.candidates.map((candidate) => candidate.attemptRef.id);
632
+ const rank = new Map(result.order.flatMap((group, index) => group.map((candidateId) => [candidateId, index])));
633
+ return Object.fromEntries(candidateIds.map((candidateId) => {
634
+ const position = rank.get(candidateId);
635
+ if (position === undefined)
636
+ throw new Error("Preference result is missing a comparison candidate.");
637
+ let total = 0;
638
+ for (const opponentId of candidateIds) {
639
+ if (opponentId === candidateId)
640
+ continue;
641
+ const opponentPosition = rank.get(opponentId);
642
+ if (opponentPosition === undefined)
643
+ throw new Error("Preference result is missing a comparison candidate.");
644
+ total += position < opponentPosition ? 1 : position === opponentPosition ? 0.5 : 0;
645
+ }
646
+ return [candidateId, total / (candidateIds.length - 1)];
647
+ }));
648
+ }
649
+ function orderForScores(scores, assignment) {
650
+ const ids = assignment.candidates.map((candidate) => candidate.attemptRef.id);
651
+ const groups = new Map();
652
+ for (const id of ids) {
653
+ const key = scores[id].toPrecision(12);
654
+ const value = Number(key);
655
+ groups.set(value, [...(groups.get(value) ?? []), id]);
656
+ }
657
+ return [...groups.entries()]
658
+ .sort(([left], [right]) => right - left)
659
+ .map(([, candidateIds]) => candidateIds.sort((left, right) => ids.indexOf(left) - ids.indexOf(right)));
660
+ }
661
+ function pairwiseAgreement(left, right, assignment) {
662
+ const ids = assignment.candidates.map((candidate) => candidate.attemptRef.id);
663
+ const pairs = pairsFor(ids);
664
+ if (!pairs.length)
665
+ return 1;
666
+ return mean(pairs.map(([first, second]) => pairRelation(left, first, second) === pairRelation(right, first, second) ? 1 : 0));
667
+ }
668
+ function tieAgreementScore(left, right, assignment) {
669
+ const pairs = pairsFor(assignment.candidates.map((candidate) => candidate.attemptRef.id));
670
+ if (!pairs.length)
671
+ return 1;
672
+ return mean(pairs.map(([first, second]) => (pairRelation(left, first, second) === 0) === (pairRelation(right, first, second) === 0) ? 1 : 0));
673
+ }
674
+ function pairRelation(result, first, second) {
675
+ if (result.rejectAll)
676
+ return 0;
677
+ const rank = new Map(result.order.flatMap((group, index) => group.map((candidate) => [candidate, index])));
678
+ const firstRank = rank.get(first);
679
+ const secondRank = rank.get(second);
680
+ if (firstRank === undefined || secondRank === undefined)
681
+ throw new Error("Preference result does not cover all comparison candidates.");
682
+ return firstRank < secondRank ? 1 : firstRank === secondRank ? 0 : -1;
683
+ }
684
+ function pairsFor(ids) {
685
+ const pairs = [];
686
+ for (let index = 0; index < ids.length; index += 1) {
687
+ for (let other = index + 1; other < ids.length; other += 1)
688
+ pairs.push([ids[index], ids[other]]);
689
+ }
690
+ return pairs;
691
+ }
692
+ function validateCalibrationPair(pair, release) {
693
+ const assignment = verifyAssignment(pair.assignment);
694
+ validateComparisonAssignment(assignment, release);
695
+ if (assignment.purpose === "frozen_eval") {
696
+ throw new Error("Frozen-evaluation comparisons cannot calibrate an automated preference reviewer.");
697
+ }
698
+ const human = verifyReceipt(pair.human);
699
+ const model = verifyReceipt(pair.model);
700
+ if (human.reviewer.kind !== "human" || model.reviewer.kind !== "model") {
701
+ throw new Error("Preference calibration requires one human and one automated model receipt per assignment.");
702
+ }
703
+ if (!sameRef(human.assignmentRef, ref(assignment)) || !sameRef(model.assignmentRef, ref(assignment))) {
704
+ throw new Error("Preference calibration receipts must belong to their supplied assignment.");
705
+ }
706
+ validatePreferenceResult(human, assignment, release);
707
+ validatePreferenceResult(model, assignment, release);
708
+ const swappedModel = pair.swappedModel ? verifyReceipt(pair.swappedModel) : null;
709
+ if (swappedModel) {
710
+ if (swappedModel.reviewer.kind !== "model" || !sameRef(swappedModel.assignmentRef, ref(assignment))) {
711
+ throw new Error("Order-swap calibration receipts must be model receipts for the same assignment.");
712
+ }
713
+ if (!sameRef(swappedModel.reviewer.releaseRef, model.reviewer.releaseRef)) {
714
+ throw new Error("Order-swap calibration must use the same immutable automated reviewer release.");
715
+ }
716
+ validatePreferenceResult(swappedModel, assignment, release);
717
+ }
718
+ return { assignment, human, model, swappedModel };
719
+ }
720
+ function isCalibratedAutomatedReviewer(receipt, release, report) {
721
+ if (!report || !verifyPreferenceCalibrationReport(report))
722
+ return false;
723
+ return report.passed
724
+ && sameRef(report.comparisonRelease, ref(release))
725
+ && sameRef(report.automatedReviewer, receipt.reviewer.releaseRef);
726
+ }
727
+ function zeroScores(assignment) {
728
+ return Object.fromEntries(assignment.candidates.map((candidate) => [candidate.attemptRef.id, 0]));
729
+ }
730
+ function mean(values) {
731
+ return values.length ? values.reduce((total, value) => total + value, 0) / values.length : 0;
732
+ }
733
+ function ref(value) {
734
+ return { id: value.id, contentHash: value.contentHash };
735
+ }
736
+ function sameRef(left, right) {
737
+ return left.id === right.id && left.contentHash === right.contentHash;
738
+ }
739
+ function uniqueRefs(refs) {
740
+ return [...new Map(refs.map((value) => [`${value.id}:${value.contentHash}`, value])).values()];
741
+ }
742
+ function compatibilityForRunManifests(base, candidate, receipts) {
743
+ if (sameRef(base.harnessRelease, candidate.harnessRelease))
744
+ return undefined;
745
+ return receipts.find((receipt) => sameRef(receipt.tasksetRelease, base.tasksetRelease)
746
+ && ((sameRef(receipt.baseHarnessRelease, base.harnessRelease)
747
+ && sameRef(receipt.candidateHarnessRelease, candidate.harnessRelease))
748
+ || (sameRef(receipt.baseHarnessRelease, candidate.harnessRelease)
749
+ && sameRef(receipt.candidateHarnessRelease, base.harnessRelease))));
750
+ }
751
+ function verifyHashed(value, contentSchema, fullSchema) {
752
+ const parsed = fullSchema.safeParse(value);
753
+ if (!parsed.success || !parsed.data)
754
+ return false;
755
+ const { contentHash: actual, ...content } = parsed.data;
756
+ try {
757
+ return contentHash(contentSchema.parse(content)) === actual;
758
+ }
759
+ catch {
760
+ return false;
761
+ }
762
+ }
763
+ function verifyComparisonRelease(value) {
764
+ if (!verifyPreferenceComparisonRelease(value))
765
+ throw new Error("Preference Comparison Release has an invalid content hash.");
766
+ return value;
767
+ }
768
+ function verifyAssignment(value) {
769
+ if (!verifyComparisonAssignment(value))
770
+ throw new Error("Comparison Assignment has an invalid content hash.");
771
+ return value;
772
+ }
773
+ function verifyReceipt(value) {
774
+ if (!verifyPreferenceReceipt(value))
775
+ throw new Error("Preference Receipt has an invalid content hash.");
776
+ return value;
777
+ }
778
+ function verifyAggregate(value) {
779
+ if (!verifyPreferenceAggregationReceipt(value))
780
+ throw new Error("Preference Aggregation Receipt has an invalid content hash.");
781
+ return value;
782
+ }
783
+ function verifyTasksetRelease(value) {
784
+ const { contentHash: actual, ...content } = TasksetReleaseSchema.parse(value);
785
+ if (contentHash(TasksetReleaseContentSchema.parse(content)) !== actual) {
786
+ throw new Error("Taskset Release has an invalid content hash.");
787
+ }
788
+ return value;
789
+ }
790
+ function verifyArtifactManifest(value) {
791
+ const parsed = ArtifactManifestSchema.safeParse(value);
792
+ if (!parsed.success)
793
+ return false;
794
+ const { contentHash: actual, ...content } = parsed.data;
795
+ return contentHash(ArtifactManifestContentSchema.parse(content)) === actual;
796
+ }
797
+ function verifyRunManifest(value) {
798
+ const parsed = RunManifestSchema.safeParse(value);
799
+ if (!parsed.success)
800
+ return false;
801
+ const { contentHash: actual, ...content } = parsed.data;
802
+ return contentHash(content) === actual;
803
+ }