@tangle-network/agent-interface 0.33.0 → 0.35.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 (36) hide show
  1. package/dist/agent-candidate-code-schema.d.ts +0 -3
  2. package/dist/agent-candidate-execution-plan-schema.d.ts +11 -17
  3. package/dist/agent-candidate-lineage-schema.d.ts +2 -2
  4. package/dist/agent-candidate-outcome-schema.d.ts +4 -4
  5. package/dist/agent-candidate-profile-schema.d.ts +0 -3
  6. package/dist/agent-candidate-promotion-schema.d.ts +1418 -885
  7. package/dist/agent-candidate-promotion-schema.js +55 -346
  8. package/dist/agent-candidate-receipt-schema.d.ts +9 -11
  9. package/dist/agent-candidate-receipt-schema.js +1 -0
  10. package/dist/agent-candidate-schema.d.ts +0 -6
  11. package/dist/agent-candidate-schema.js +1 -3
  12. package/dist/agent-candidate.d.ts +15 -7
  13. package/dist/agent-execution-limits.d.ts +28 -0
  14. package/dist/agent-execution-limits.js +77 -0
  15. package/dist/agent-improvement-measurement-schema.d.ts +198 -0
  16. package/dist/agent-improvement-measurement-schema.js +349 -0
  17. package/dist/agent-improvement-source.d.ts +23 -0
  18. package/dist/agent-improvement-source.js +38 -0
  19. package/dist/agent-profile-improvement-schema.d.ts +1088 -0
  20. package/dist/agent-profile-improvement-schema.js +560 -0
  21. package/dist/agent-profile-improvement.d.ts +140 -0
  22. package/dist/agent-profile-improvement.js +1 -0
  23. package/dist/agent-profile.d.ts +2 -2
  24. package/dist/agent-profile.js +2 -2
  25. package/dist/harness-capabilities.d.ts +1 -1
  26. package/dist/harness-capabilities.js +9 -12
  27. package/dist/harness.d.ts +1 -10
  28. package/dist/harness.js +0 -13
  29. package/dist/index.d.ts +8 -1
  30. package/dist/index.js +6 -0
  31. package/dist/interaction.d.ts +0 -17
  32. package/dist/interaction.js +0 -23
  33. package/dist/number-validation.d.ts +1 -0
  34. package/dist/number-validation.js +4 -0
  35. package/dist/profile-schema.d.ts +0 -3
  36. package/package.json +1 -1
@@ -0,0 +1,560 @@
1
+ import { z } from "zod";
2
+ import { agentCandidateLineageSchema } from "./agent-candidate-lineage-schema.js";
3
+ import { canonicalCandidateDigest, isCanonicalJsonValue, omitTopLevelDigest, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
4
+ import { refineAgentExecutionWithinLimits } from "./agent-execution-limits.js";
5
+ import { agentCandidateBenchmarkGraderIdentitySchema, agentCandidateExecutionLimitsSchema, agentCandidateResolvedModelSchema, } from "./agent-candidate-execution-plan-schema.js";
6
+ import { agentCandidateBenchmarkDimensionSchema, agentCandidateFixedSpendSchema, } from "./agent-candidate-outcome-schema.js";
7
+ import { agentCandidateEvaluationPolicySchema, createMeasuredComparisonIdentityRegistry, measuredComparisonCommonShape, refineMeasuredComparisonSummary, } from "./agent-improvement-measurement-schema.js";
8
+ import { agentImprovementSourceSchema } from "./agent-improvement-source.js";
9
+ import { numbersApproximatelyEqual } from "./number-validation.js";
10
+ import { agentProfileDiffSchema } from "./profile-schema.js";
11
+ const profileImprovementScenarioSchema = z
12
+ .object({
13
+ id: z.string().min(1).max(500),
14
+ kind: z.string().min(1).max(200),
15
+ digest: sha256DigestSchema,
16
+ })
17
+ .strict();
18
+ export const agentProfileImprovementTaskSchema = z
19
+ .object({
20
+ kind: z.literal("agent-profile-improvement-task"),
21
+ digestAlgorithm: z.literal("rfc8785-sha256"),
22
+ scenario: profileImprovementScenarioSchema,
23
+ grader: agentCandidateBenchmarkGraderIdentitySchema,
24
+ model: agentCandidateResolvedModelSchema,
25
+ limits: agentCandidateExecutionLimitsSchema,
26
+ digest: sha256DigestSchema,
27
+ })
28
+ .strict()
29
+ .superRefine((task, ctx) => {
30
+ if (canonicalCandidateDigest(omitTopLevelDigest(task)) !== task.digest) {
31
+ ctx.addIssue({
32
+ code: "custom",
33
+ path: ["digest"],
34
+ message: "profile improvement task digest is invalid",
35
+ });
36
+ }
37
+ });
38
+ export const agentProfileImprovementSuiteSchema = z
39
+ .object({
40
+ kind: z.literal("agent-profile-improvement-suite"),
41
+ digestAlgorithm: z.literal("rfc8785-sha256"),
42
+ splitDigest: sha256DigestSchema,
43
+ taskDigests: z.tuple([sha256DigestSchema]).rest(sha256DigestSchema),
44
+ reps: z.number().int().positive(),
45
+ seeds: z.tuple([z.number().int().safe()]).rest(z.number().int().safe()),
46
+ digest: sha256DigestSchema,
47
+ })
48
+ .strict()
49
+ .superRefine((suite, ctx) => {
50
+ if (canonicalCandidateDigest(omitTopLevelDigest(suite)) !== suite.digest) {
51
+ ctx.addIssue({
52
+ code: "custom",
53
+ path: ["digest"],
54
+ message: "profile improvement suite digest is invalid",
55
+ });
56
+ }
57
+ });
58
+ export const agentProfileImprovementSuiteInputsSchema = z
59
+ .object({
60
+ suite: agentProfileImprovementSuiteSchema,
61
+ tasks: z.tuple([agentProfileImprovementTaskSchema]).rest(agentProfileImprovementTaskSchema),
62
+ })
63
+ .strict()
64
+ .superRefine((input, ctx) => {
65
+ if (input.suite.taskDigests.length !== input.tasks.length) {
66
+ ctx.addIssue({
67
+ code: "custom",
68
+ path: ["tasks"],
69
+ message: "profile improvement suite task count does not match its signed digests",
70
+ });
71
+ }
72
+ if (input.suite.seeds.length !== input.tasks.length * input.suite.reps) {
73
+ ctx.addIssue({
74
+ code: "custom",
75
+ path: ["suite", "seeds"],
76
+ message: "profile improvement suite must have one seed per task and repetition",
77
+ });
78
+ }
79
+ for (const [index, task] of input.tasks.entries()) {
80
+ if (input.suite.taskDigests[index] !== task.digest) {
81
+ ctx.addIssue({
82
+ code: "custom",
83
+ path: ["tasks", index, "digest"],
84
+ message: "profile improvement suite task digest does not match its task",
85
+ });
86
+ }
87
+ }
88
+ });
89
+ const profileImprovementEvidenceSchema = z
90
+ .object({
91
+ kind: z.string().trim().min(1).max(100).regex(/^[a-z][a-z0-9-]*$/),
92
+ identity: z.string().trim().min(1).max(500),
93
+ digest: sha256DigestSchema,
94
+ })
95
+ .strict();
96
+ export const agentProfileImprovementArmSchema = z
97
+ .object({
98
+ stateDigest: sha256DigestSchema,
99
+ })
100
+ .strict();
101
+ /**
102
+ * The first product path changes only prompt and skills, but it uses the
103
+ * shared profile-diff language so execution and activation apply identical
104
+ * ordered patches.
105
+ */
106
+ export const agentProfileImprovementChangeStepSchema = agentProfileDiffSchema.superRefine((change, ctx) => {
107
+ const changed = changedProfileImprovementSurfaces([change]);
108
+ if (changed.length === 0) {
109
+ ctx.addIssue({
110
+ code: "custom",
111
+ message: "profile improvement patch must change prompt or skills",
112
+ });
113
+ }
114
+ const setKeys = Object.keys(change.set ?? {});
115
+ if (setKeys.some((key) => key !== "prompt" && key !== "resources")) {
116
+ ctx.addIssue({
117
+ code: "custom",
118
+ path: ["set"],
119
+ message: "profile improvement patches may set only prompt or skill resources",
120
+ });
121
+ }
122
+ const setResourceKeys = Object.keys(change.set?.resources ?? {});
123
+ if (setResourceKeys.some((key) => key !== "skills")) {
124
+ ctx.addIssue({
125
+ code: "custom",
126
+ path: ["set", "resources"],
127
+ message: "profile improvement patches may set only skill resources",
128
+ });
129
+ }
130
+ if (change.set?.resources?.skills?.some((skill) => skill.kind !== "inline")) {
131
+ ctx.addIssue({
132
+ code: "custom",
133
+ path: ["set", "resources", "skills"],
134
+ message: "measured profile skill patches require inline content with exact bytes",
135
+ });
136
+ }
137
+ const removeKeys = Object.keys(change.remove ?? {});
138
+ if (removeKeys.some((key) => key !== "prompt" && key !== "resources")) {
139
+ ctx.addIssue({
140
+ code: "custom",
141
+ path: ["remove"],
142
+ message: "profile improvement patches may remove only prompt or skill resources",
143
+ });
144
+ }
145
+ if (change.remove?.resources === true) {
146
+ ctx.addIssue({
147
+ code: "custom",
148
+ path: ["remove", "resources"],
149
+ message: "profile improvement patches may not remove unrelated resources",
150
+ });
151
+ }
152
+ const removeResources = change.remove?.resources;
153
+ const removeResourceKeys = Object.keys(typeof removeResources === "object" ? removeResources : {});
154
+ if (removeResourceKeys.some((key) => key !== "skills")) {
155
+ ctx.addIssue({
156
+ code: "custom",
157
+ path: ["remove", "resources"],
158
+ message: "profile improvement patches may remove only skill resources",
159
+ });
160
+ }
161
+ });
162
+ export const agentProfileImprovementChangeSchema = z
163
+ .tuple([agentProfileImprovementChangeStepSchema])
164
+ .rest(agentProfileImprovementChangeStepSchema)
165
+ .superRefine((change, ctx) => {
166
+ const surfaces = changedProfileImprovementSurfaces(change);
167
+ if (surfaces.length === 0) {
168
+ ctx.addIssue({
169
+ code: "custom",
170
+ message: "profile improvement change must alter at least one supported surface",
171
+ });
172
+ }
173
+ });
174
+ export const agentProfileImprovementExperimentSchema = z
175
+ .object({
176
+ kind: z.literal("agent-profile-improvement-experiment"),
177
+ digestAlgorithm: z.literal("rfc8785-sha256"),
178
+ source: agentImprovementSourceSchema,
179
+ baseline: agentProfileImprovementArmSchema,
180
+ candidate: agentProfileImprovementArmSchema,
181
+ change: agentProfileImprovementChangeSchema,
182
+ candidateLineage: agentCandidateLineageSchema,
183
+ benchmark: agentProfileImprovementSuiteInputsSchema,
184
+ policy: agentCandidateEvaluationPolicySchema,
185
+ digest: sha256DigestSchema,
186
+ })
187
+ .strict()
188
+ .superRefine((experiment, ctx) => {
189
+ if (experiment.baseline.stateDigest !== experiment.source.sourceDigest) {
190
+ ctx.addIssue({
191
+ code: "custom",
192
+ path: ["baseline", "stateDigest"],
193
+ message: "profile improvement baseline must bind the exact source state",
194
+ });
195
+ }
196
+ if (experiment.baseline.stateDigest === experiment.candidate.stateDigest) {
197
+ ctx.addIssue({
198
+ code: "custom",
199
+ path: ["candidate", "stateDigest"],
200
+ message: "profile improvement experiment requires a changed candidate state",
201
+ });
202
+ }
203
+ const source = experiment.candidateLineage.source;
204
+ if ((source === "optimizer" || source === "compound") &&
205
+ !experiment.candidateLineage.parentDigests?.includes(experiment.baseline.stateDigest)) {
206
+ ctx.addIssue({
207
+ code: "custom",
208
+ path: ["candidateLineage", "parentDigests"],
209
+ message: "generated profile lineage must include the experiment baseline",
210
+ });
211
+ }
212
+ if (experiment.candidateLineage.parentDigests?.includes(experiment.candidate.stateDigest)) {
213
+ ctx.addIssue({
214
+ code: "custom",
215
+ path: ["candidateLineage", "parentDigests"],
216
+ message: "profile lineage cannot name the candidate itself as a parent",
217
+ });
218
+ }
219
+ if (experiment.candidateLineage.developmentSplitDigest !== undefined &&
220
+ experiment.candidateLineage.developmentSplitDigest === experiment.benchmark.suite.splitDigest) {
221
+ ctx.addIssue({
222
+ code: "custom",
223
+ path: ["candidateLineage", "developmentSplitDigest"],
224
+ message: "profile improvement development and held-out splits must be disjoint",
225
+ });
226
+ }
227
+ if (canonicalCandidateDigest(omitTopLevelDigest(experiment)) !== experiment.digest) {
228
+ ctx.addIssue({
229
+ code: "custom",
230
+ path: ["digest"],
231
+ message: "profile improvement experiment digest is invalid",
232
+ });
233
+ }
234
+ });
235
+ export const agentProfileImprovementRunCellSchema = z
236
+ .object({
237
+ kind: z.literal("agent-profile-improvement-run-cell"),
238
+ experimentDigest: sha256DigestSchema,
239
+ arm: z.enum(["baseline", "candidate"]),
240
+ stateDigest: sha256DigestSchema,
241
+ suiteDigest: sha256DigestSchema,
242
+ taskDigest: sha256DigestSchema,
243
+ taskIndex: z.number().int().nonnegative(),
244
+ repetition: z.number().int().nonnegative(),
245
+ seed: z.number().int().safe(),
246
+ attempt: z.number().int().positive(),
247
+ digest: sha256DigestSchema,
248
+ })
249
+ .strict()
250
+ .superRefine((cell, ctx) => {
251
+ if (canonicalCandidateDigest(omitTopLevelDigest(cell)) !== cell.digest) {
252
+ ctx.addIssue({
253
+ code: "custom",
254
+ path: ["digest"],
255
+ message: "profile improvement run cell digest is invalid",
256
+ });
257
+ }
258
+ });
259
+ const timingSchema = z
260
+ .object({
261
+ startedAtMs: z.number().finite().nonnegative(),
262
+ endedAtMs: z.number().finite().nonnegative(),
263
+ durationMs: z.number().finite().nonnegative(),
264
+ })
265
+ .strict()
266
+ .superRefine((timing, ctx) => {
267
+ if (!numbersApproximatelyEqual(timing.durationMs, timing.endedAtMs - timing.startedAtMs)) {
268
+ ctx.addIssue({
269
+ code: "custom",
270
+ path: ["durationMs"],
271
+ message: "timing duration must equal its start and end timestamps",
272
+ });
273
+ }
274
+ });
275
+ const agentProfileImprovementGradingSchema = z
276
+ .object({
277
+ grader: agentCandidateBenchmarkGraderIdentitySchema,
278
+ evidence: profileImprovementEvidenceSchema,
279
+ timing: timingSchema,
280
+ usage: agentCandidateFixedSpendSchema,
281
+ score: z.number().finite(),
282
+ passed: z.boolean(),
283
+ dimensions: z.array(agentCandidateBenchmarkDimensionSchema),
284
+ })
285
+ .strict()
286
+ .superRefine((grading, ctx) => {
287
+ const names = grading.dimensions.map((dimension) => dimension.name);
288
+ if (new Set(names).size !== names.length) {
289
+ ctx.addIssue({
290
+ code: "custom",
291
+ path: ["dimensions"],
292
+ message: "profile improvement grading dimension names must be unique",
293
+ });
294
+ }
295
+ if (grading.evidence.digest === grading.grader.artifact.sha256) {
296
+ ctx.addIssue({
297
+ code: "custom",
298
+ path: ["evidence"],
299
+ message: "profile improvement grading evidence must be distinct from its grader",
300
+ });
301
+ }
302
+ });
303
+ export const agentProfileImprovementRunReceiptSchema = z
304
+ .object({
305
+ kind: z.literal("agent-profile-improvement-run"),
306
+ digestAlgorithm: z.literal("rfc8785-sha256"),
307
+ executionId: z.string().min(1).max(500),
308
+ runCell: agentProfileImprovementRunCellSchema,
309
+ runRecord: profileImprovementEvidenceSchema,
310
+ billing: z
311
+ .tuple([profileImprovementEvidenceSchema])
312
+ .rest(profileImprovementEvidenceSchema),
313
+ timing: timingSchema,
314
+ steps: z.number().int().nonnegative().safe(),
315
+ resolvedModel: agentCandidateResolvedModelSchema,
316
+ limits: agentCandidateExecutionLimitsSchema,
317
+ usage: agentCandidateFixedSpendSchema,
318
+ trace: z
319
+ .object({
320
+ evidence: profileImprovementEvidenceSchema,
321
+ eventCount: z.number().int().nonnegative(),
322
+ modelCallCount: z.number().int().nonnegative(),
323
+ })
324
+ .strict(),
325
+ output: profileImprovementEvidenceSchema,
326
+ outcome: z.discriminatedUnion("status", [
327
+ z.object({ status: z.literal("succeeded") }).strict(),
328
+ z
329
+ .object({
330
+ status: z.literal("failed"),
331
+ code: z.string().min(1).max(200),
332
+ message: z.string().min(1).max(4_000),
333
+ })
334
+ .strict(),
335
+ ]),
336
+ grading: agentProfileImprovementGradingSchema,
337
+ digest: sha256DigestSchema,
338
+ })
339
+ .strict()
340
+ .superRefine((receipt, ctx) => {
341
+ if (evidenceKey(receipt.output) === evidenceKey(receipt.grading.evidence)) {
342
+ ctx.addIssue({
343
+ code: "custom",
344
+ path: ["grading", "evidence"],
345
+ message: "profile improvement grading evidence must be distinct from the agent output",
346
+ });
347
+ }
348
+ if (receipt.trace.modelCallCount !== receipt.usage.modelCalls) {
349
+ ctx.addIssue({
350
+ code: "custom",
351
+ path: ["trace", "modelCallCount"],
352
+ message: "profile trace model calls must equal settled execution model calls",
353
+ });
354
+ }
355
+ if (receipt.trace.eventCount < receipt.trace.modelCallCount) {
356
+ ctx.addIssue({
357
+ code: "custom",
358
+ path: ["trace", "eventCount"],
359
+ message: "profile trace must retain at least one event for every model call",
360
+ });
361
+ }
362
+ refineAgentExecutionWithinLimits(receipt.limits, {
363
+ durationMs: receipt.timing.durationMs,
364
+ steps: receipt.steps,
365
+ usage: receipt.usage,
366
+ }, ctx);
367
+ const billing = receipt.billing.map(evidenceKey);
368
+ if (new Set(billing).size !== billing.length) {
369
+ ctx.addIssue({
370
+ code: "custom",
371
+ path: ["billing"],
372
+ message: "profile improvement billing evidence must be unique",
373
+ });
374
+ }
375
+ if (canonicalCandidateDigest(omitTopLevelDigest(receipt)) !== receipt.digest) {
376
+ ctx.addIssue({
377
+ code: "custom",
378
+ path: ["digest"],
379
+ message: "profile improvement run receipt digest is invalid",
380
+ });
381
+ }
382
+ });
383
+ const agentProfileImprovementMeasurementSchema = z
384
+ .object({
385
+ baseline: agentProfileImprovementRunReceiptSchema,
386
+ candidate: agentProfileImprovementRunReceiptSchema,
387
+ })
388
+ .strict();
389
+ export const agentProfileImprovementMeasuredComparisonSchema = z
390
+ .object({
391
+ kind: z.literal("agent-profile-improvement-measured-comparison"),
392
+ experiment: agentProfileImprovementExperimentSchema,
393
+ measurements: z.array(agentProfileImprovementMeasurementSchema),
394
+ ...measuredComparisonCommonShape,
395
+ })
396
+ .strict()
397
+ .superRefine((comparison, ctx) => {
398
+ refineProfileImprovementComparison(comparison, ctx);
399
+ });
400
+ export function changedProfileImprovementSurfaces(change) {
401
+ const surfaces = new Set();
402
+ for (const step of change) {
403
+ if (step.set?.prompt !== undefined || step.remove?.prompt !== undefined) {
404
+ surfaces.add("prompt");
405
+ }
406
+ if (step.set?.resources?.skills !== undefined ||
407
+ (typeof step.remove?.resources === "object" &&
408
+ step.remove.resources.skills !== undefined)) {
409
+ surfaces.add("skills");
410
+ }
411
+ }
412
+ return [...surfaces].sort();
413
+ }
414
+ function refineProfileImprovementComparison(comparison, ctx) {
415
+ const { suite, tasks } = comparison.experiment.benchmark;
416
+ const expectedN = tasks.length * suite.reps;
417
+ if (comparison.provenance.baselineContentHash !== comparison.experiment.baseline.stateDigest ||
418
+ comparison.provenance.candidateContentHash !== comparison.experiment.candidate.stateDigest) {
419
+ ctx.addIssue({
420
+ code: "custom",
421
+ path: ["provenance"],
422
+ message: "profile comparison provenance must bind both complete measured states",
423
+ });
424
+ }
425
+ if (comparison.measurements.length !== expectedN) {
426
+ ctx.addIssue({
427
+ code: "custom",
428
+ path: ["measurements"],
429
+ message: "profile comparison must contain every signed benchmark cell",
430
+ });
431
+ }
432
+ const recordProfileIdentities = createMeasuredComparisonIdentityRegistry({
433
+ ctx,
434
+ identityLabel: "profile measurements",
435
+ });
436
+ const expectedDimensions = measurementDimensionNames(comparison.measurements[0]);
437
+ for (let taskIndex = 0; taskIndex < tasks.length; taskIndex += 1) {
438
+ const task = tasks[taskIndex];
439
+ if (!task)
440
+ continue;
441
+ for (let repetition = 0; repetition < suite.reps; repetition += 1) {
442
+ const index = taskIndex * suite.reps + repetition;
443
+ const measurement = comparison.measurements[index];
444
+ if (!measurement)
445
+ continue;
446
+ const seed = suite.seeds[index];
447
+ for (const arm of ["baseline", "candidate"]) {
448
+ const receipt = measurement[arm];
449
+ const cell = receipt.runCell;
450
+ const profile = comparison.experiment[arm];
451
+ const armPath = ["measurements", index, arm];
452
+ const checks = [
453
+ [
454
+ cell.experimentDigest === comparison.experiment.digest,
455
+ [...armPath, "runCell", "experimentDigest"],
456
+ "profile run receipt must bind the measured experiment",
457
+ ],
458
+ [
459
+ cell.arm === arm,
460
+ [...armPath, "runCell", "arm"],
461
+ "profile run receipt must bind its measured arm",
462
+ ],
463
+ [
464
+ cell.stateDigest === profile.stateDigest,
465
+ [...armPath, "runCell", "stateDigest"],
466
+ "profile run receipt must bind the experiment arm state",
467
+ ],
468
+ [
469
+ cell.suiteDigest === suite.digest &&
470
+ cell.taskDigest === task.digest &&
471
+ cell.taskIndex === taskIndex &&
472
+ cell.repetition === repetition &&
473
+ cell.seed === seed &&
474
+ cell.attempt === 1,
475
+ [...armPath, "runCell"],
476
+ "publishable profile evidence must use the first signed task attempt",
477
+ ],
478
+ [
479
+ JSON.stringify(receipt.grading.grader) === JSON.stringify(task.grader),
480
+ [...armPath, "grading", "grader"],
481
+ "profile run receipt must bind the signed evaluator",
482
+ ],
483
+ [
484
+ JSON.stringify(receipt.resolvedModel) === JSON.stringify(task.model),
485
+ [...armPath, "resolvedModel"],
486
+ "profile run receipt must bind the signed model snapshot",
487
+ ],
488
+ [
489
+ JSON.stringify(receipt.limits) === JSON.stringify(task.limits),
490
+ [...armPath, "limits"],
491
+ "profile run receipt must bind the signed execution limits",
492
+ ],
493
+ [
494
+ JSON.stringify(receipt.grading.dimensions.map((dimension) => dimension.name)) ===
495
+ JSON.stringify(expectedDimensions),
496
+ [...armPath, "grading", "dimensions"],
497
+ "profile run receipt dimensions must match the complete measured comparison",
498
+ ],
499
+ ];
500
+ for (const [valid, path, message] of checks) {
501
+ if (!valid)
502
+ ctx.addIssue({ code: "custom", path, message });
503
+ }
504
+ recordProfileIdentities([
505
+ { kind: "execution", value: receipt.executionId },
506
+ { kind: "runCell", value: cell.digest },
507
+ { kind: "receipt", value: receipt.digest },
508
+ { kind: "runRecord", value: evidenceKey(receipt.runRecord) },
509
+ ...receipt.billing.map((billing) => ({
510
+ kind: "billing",
511
+ value: evidenceKey(billing),
512
+ path: [...armPath, "billing"],
513
+ })),
514
+ ], armPath);
515
+ }
516
+ if (measurement.baseline.executionId === measurement.candidate.executionId ||
517
+ measurement.baseline.runCell.digest === measurement.candidate.runCell.digest ||
518
+ measurement.baseline.digest === measurement.candidate.digest) {
519
+ ctx.addIssue({
520
+ code: "custom",
521
+ path: ["measurements", index],
522
+ message: "profile measurement must use independent baseline and candidate executions",
523
+ });
524
+ }
525
+ }
526
+ }
527
+ if (comparison.decision.outcome === "ship" &&
528
+ comparison.measurements.some((measurement) => measurement.baseline.outcome.status !== "succeeded" ||
529
+ measurement.candidate.outcome.status !== "succeeded")) {
530
+ ctx.addIssue({
531
+ code: "custom",
532
+ path: ["measurements"],
533
+ message: "a shippable profile comparison cannot include failed executions",
534
+ });
535
+ }
536
+ refineMeasuredComparisonSummary(comparison, comparison.experiment.policy, expectedN, comparison.measurements, {
537
+ score: (receipt) => receipt.grading.score,
538
+ dimension: (receipt, name) => receipt.grading.dimensions.find((dimension) => dimension.name === name)?.score,
539
+ cost: profileImprovementExecutionCostUsd,
540
+ latency: profileImprovementExecutionLatencyMs,
541
+ }, ctx);
542
+ if (!isCanonicalJsonValue(comparison)) {
543
+ ctx.addIssue({
544
+ code: "custom",
545
+ message: "profile measured comparison must contain only RFC 8785 JSON values",
546
+ });
547
+ }
548
+ }
549
+ function measurementDimensionNames(measurement) {
550
+ return measurement?.baseline.grading.dimensions.map((dimension) => dimension.name) ?? [];
551
+ }
552
+ function evidenceKey(evidence) {
553
+ return `${evidence.kind}\u0000${evidence.identity}\u0000${evidence.digest}`;
554
+ }
555
+ function profileImprovementExecutionCostUsd(receipt) {
556
+ return (receipt.usage.costUsdNanos + receipt.grading.usage.costUsdNanos) / 1_000_000_000;
557
+ }
558
+ function profileImprovementExecutionLatencyMs(receipt) {
559
+ return receipt.timing.durationMs + receipt.grading.timing.durationMs;
560
+ }