@tangle-network/agent-interface 0.28.0 → 0.30.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 (30) hide show
  1. package/dist/agent-candidate-code-schema.d.ts +0 -8
  2. package/dist/agent-candidate-code-schema.js +0 -1
  3. package/dist/agent-candidate-execution-plan-schema.d.ts +193 -350
  4. package/dist/agent-candidate-execution-plan-schema.js +98 -131
  5. package/dist/agent-candidate-lineage-schema.d.ts +1 -28
  6. package/dist/agent-candidate-lineage-schema.js +3 -33
  7. package/dist/agent-candidate-outcome-schema.d.ts +48 -18
  8. package/dist/agent-candidate-outcome-schema.js +19 -24
  9. package/dist/agent-candidate-profile-schema.d.ts +3 -28
  10. package/dist/agent-candidate-profile-schema.js +15 -27
  11. package/dist/agent-candidate-promotion-schema.d.ts +10981 -2356
  12. package/dist/agent-candidate-promotion-schema.js +495 -105
  13. package/dist/agent-candidate-receipt-schema.d.ts +268 -303
  14. package/dist/agent-candidate-receipt-schema.js +82 -8
  15. package/dist/agent-candidate-schema-common.d.ts +8 -0
  16. package/dist/agent-candidate-schema-common.js +35 -1
  17. package/dist/agent-candidate-schema.d.ts +3 -52
  18. package/dist/agent-candidate-schema.js +3 -28
  19. package/dist/agent-candidate-task-schema.d.ts +643 -0
  20. package/dist/agent-candidate-task-schema.js +191 -0
  21. package/dist/agent-candidate.d.ts +191 -75
  22. package/dist/agent-candidate.test-fixture.d.ts +0 -26
  23. package/dist/agent-candidate.test-fixture.js +0 -26
  24. package/dist/agent-profile.d.ts +33 -9
  25. package/dist/harness-capabilities.d.ts +2 -2
  26. package/dist/harness-capabilities.js +23 -14
  27. package/dist/profile-schema.d.ts +65 -84
  28. package/dist/profile-schema.js +82 -32
  29. package/dist/profile-security.js +2 -0
  30. package/package.json +2 -2
@@ -1,9 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { agentCandidateBundleSchema } from "./agent-candidate-schema.js";
3
- import { agentCandidateProfileActivationSchema } from "./agent-candidate-execution-plan-schema.js";
4
- import { isCanonicalJsonValue, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
3
+ import { agentCandidateLineageSchema } from "./agent-candidate-lineage-schema.js";
4
+ import { agentCandidateBenchmarkSuiteInputsSchema } from "./agent-candidate-task-schema.js";
5
+ import { canonicalCandidateDigest, isCanonicalJsonValue, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
5
6
  import { agentCandidateMaterializationReceiptSchema, agentCandidateRunReceiptSchema, } from "./agent-candidate-receipt-schema.js";
6
- import { agentProfileSchema } from "./profile-schema.js";
7
7
  const canonicalJsonSchema = z.custom(isCanonicalJsonValue, "value must be finite, acyclic RFC 8785 JSON");
8
8
  const canonicalJsonObjectSchema = z
9
9
  .record(z.string(), canonicalJsonSchema)
@@ -74,9 +74,7 @@ const measuredObjectiveSchema = z.union([
74
74
  measuredObjectiveVariant(qualityDimensionFields),
75
75
  unavailableObjectiveVariant(qualityDimensionFields),
76
76
  measuredObjectiveVariant(costObjectiveFields),
77
- unavailableObjectiveVariant(costObjectiveFields),
78
77
  measuredObjectiveVariant(latencyObjectiveFields),
79
- unavailableObjectiveVariant(latencyObjectiveFields),
80
78
  ]);
81
79
  const improvementSurfaceSchema = z.enum([
82
80
  "prompt",
@@ -90,18 +88,139 @@ const improvementSurfaceSchema = z.enum([
90
88
  "code",
91
89
  "knowledge",
92
90
  ]);
91
+ export const agentCandidateEvaluationPolicySchema = z
92
+ .object({
93
+ confidenceLevel: z.number().finite().gt(0).lt(1),
94
+ resamples: z.number().int().min(100),
95
+ bootstrapSeed: z.number().int().safe(),
96
+ deltaThreshold: z.number().finite().nonnegative(),
97
+ minProductiveRuns: z.number().int().min(3),
98
+ budgetUsd: z.number().finite().nonnegative().optional(),
99
+ criticalDimensions: z.array(z.string().min(1)),
100
+ regressionTolerance: z.number().finite().nonnegative(),
101
+ })
102
+ .strict()
103
+ .superRefine((policy, ctx) => {
104
+ if (new Set(policy.criticalDimensions).size !== policy.criticalDimensions.length ||
105
+ policy.criticalDimensions.some((name, index) => index > 0 && policy.criticalDimensions[index - 1] >= name)) {
106
+ ctx.addIssue({
107
+ code: "custom",
108
+ path: ["criticalDimensions"],
109
+ message: "critical dimensions must be sorted and unique",
110
+ });
111
+ }
112
+ });
113
+ export const agentCandidateExperimentSchema = z
114
+ .object({
115
+ kind: z.literal("agent-candidate-experiment"),
116
+ digestAlgorithm: z.literal("rfc8785-sha256"),
117
+ baseline: agentCandidateBundleSchema,
118
+ candidate: agentCandidateBundleSchema,
119
+ candidateLineage: agentCandidateLineageSchema,
120
+ benchmark: agentCandidateBenchmarkSuiteInputsSchema,
121
+ policy: agentCandidateEvaluationPolicySchema,
122
+ digest: sha256DigestSchema,
123
+ })
124
+ .strict()
125
+ .superRefine((experiment, ctx) => {
126
+ const source = experiment.candidateLineage.source;
127
+ if ((source === "optimizer" || source === "compound") &&
128
+ !experiment.candidateLineage.parentDigests?.includes(experiment.baseline.digest)) {
129
+ ctx.addIssue({
130
+ code: "custom",
131
+ path: ["candidateLineage", "parentDigests"],
132
+ message: "generated candidate lineage must include the experiment baseline",
133
+ });
134
+ }
135
+ if (experiment.candidateLineage.parentDigests?.includes(experiment.candidate.digest)) {
136
+ ctx.addIssue({
137
+ code: "custom",
138
+ path: ["candidateLineage", "parentDigests"],
139
+ message: "candidate lineage cannot name the candidate itself as a parent",
140
+ });
141
+ }
142
+ if (experiment.candidateLineage.developmentSplitDigest !== undefined &&
143
+ experiment.benchmark.tasks.some((task) => task.benchmark.splitDigest ===
144
+ experiment.candidateLineage.developmentSplitDigest)) {
145
+ ctx.addIssue({
146
+ code: "custom",
147
+ path: ["candidateLineage", "developmentSplitDigest"],
148
+ message: "candidate development and held-out splits must be disjoint",
149
+ });
150
+ }
151
+ if (!isCanonicalJsonValue(experiment)) {
152
+ ctx.addIssue({
153
+ code: "custom",
154
+ message: "candidate experiment must contain only RFC 8785 JSON values",
155
+ });
156
+ }
157
+ });
158
+ export const candidateExecutionEvidenceSchema = z
159
+ .object({
160
+ kind: z.literal("agent-candidate-execution-evidence"),
161
+ materializationReceipt: agentCandidateMaterializationReceiptSchema,
162
+ receipt: agentCandidateRunReceiptSchema,
163
+ digest: sha256DigestSchema,
164
+ })
165
+ .strict()
166
+ .superRefine((evidence, ctx) => {
167
+ const materialization = evidence.materializationReceipt;
168
+ const plan = materialization.executionPlan;
169
+ const checks = [
170
+ [
171
+ evidence.receipt.materializationReceiptDigest === materialization.digest,
172
+ ["receipt", "materializationReceiptDigest"],
173
+ "run receipt must bind the included materialization receipt",
174
+ ],
175
+ [
176
+ evidence.receipt.executionPlanDigest === plan.digest,
177
+ ["receipt", "executionPlanDigest"],
178
+ "run receipt must bind the included execution plan",
179
+ ],
180
+ [
181
+ evidence.receipt.bundleDigest === materialization.bundleDigest,
182
+ ["receipt", "bundleDigest"],
183
+ "run receipt and materialization must bind one bundle",
184
+ ],
185
+ [
186
+ evidence.receipt.runCellDigest === plan.material.runCell.digest,
187
+ ["receipt", "runCellDigest"],
188
+ "run receipt must bind the materialized run cell",
189
+ ],
190
+ [
191
+ materialization.profileActivation.profilePlan.digest ===
192
+ plan.material.profile.planDigest,
193
+ ["materializationReceipt", "profileActivation", "profilePlan", "digest"],
194
+ "profile activation must bind the materialized execution plan",
195
+ ],
196
+ [
197
+ evidence.receipt.modelSettlement.material.grantDigest ===
198
+ plan.material.model.access.grantDigest,
199
+ ["receipt", "modelSettlement", "material", "grantDigest"],
200
+ "model settlement must bind the execution plan grant",
201
+ ],
202
+ ];
203
+ for (const [valid, path, message] of checks) {
204
+ if (!valid)
205
+ ctx.addIssue({ code: "custom", path, message });
206
+ }
207
+ if (!isCanonicalJsonValue(evidence)) {
208
+ ctx.addIssue({
209
+ code: "custom",
210
+ message: "candidate execution evidence must contain only RFC 8785 JSON values",
211
+ });
212
+ }
213
+ });
93
214
  export const agentImprovementMeasuredComparisonSchema = z
94
215
  .object({
95
216
  kind: z.literal("agent-improvement-measured-comparison"),
96
- benchmark: z
217
+ experiment: agentCandidateExperimentSchema,
218
+ measurements: z.array(z
97
219
  .object({
98
- name: z.string().min(1),
99
- version: z.string().min(1),
100
- splitDigest: sha256DigestSchema,
220
+ baseline: candidateExecutionEvidenceSchema,
221
+ candidate: candidateExecutionEvidenceSchema,
101
222
  })
102
- .strict(),
103
- baselineProfileDigest: sha256DigestSchema,
104
- candidateBundleDigest: sha256DigestSchema,
223
+ .strict()),
105
224
  overall: z
106
225
  .object({
107
226
  name: z.literal("composite"),
@@ -157,7 +276,11 @@ export const agentImprovementMeasuredComparisonSchema = z
157
276
  evaluation: z
158
277
  .object({
159
278
  generationsExplored: z.number().int().nonnegative(),
279
+ searchDurationMs: z.number().finite().nonnegative(),
280
+ executionDurationMs: z.number().finite().nonnegative(),
160
281
  durationMs: z.number().finite().nonnegative(),
282
+ searchCostUsd: z.number().finite().nonnegative(),
283
+ executionCostUsd: z.number().finite().nonnegative(),
161
284
  totalCostUsd: z.number().finite().nonnegative(),
162
285
  })
163
286
  .strict(),
@@ -166,6 +289,257 @@ export const agentImprovementMeasuredComparisonSchema = z
166
289
  .strict()
167
290
  .superRefine((comparison, ctx) => {
168
291
  refineEstimate(comparison.overall, ["overall"], ctx);
292
+ if (!approximatelyEqual(comparison.evaluation.durationMs, comparison.evaluation.searchDurationMs + comparison.evaluation.executionDurationMs) ||
293
+ !approximatelyEqual(comparison.evaluation.totalCostUsd, comparison.evaluation.searchCostUsd + comparison.evaluation.executionCostUsd)) {
294
+ ctx.addIssue({
295
+ code: "custom",
296
+ path: ["evaluation"],
297
+ message: "evaluation totals must equal their search and execution components",
298
+ });
299
+ }
300
+ const { suite, tasks } = comparison.experiment.benchmark;
301
+ const expectedN = suite.taskDigests.length * suite.reps;
302
+ if (comparison.measurements.length !== expectedN) {
303
+ ctx.addIssue({
304
+ code: "custom",
305
+ path: ["measurements"],
306
+ message: "measured comparison must contain every signed benchmark cell",
307
+ });
308
+ }
309
+ const executionIdentities = {
310
+ execution: new Set(),
311
+ runCell: new Set(),
312
+ materialization: new Set(),
313
+ receipt: new Set(),
314
+ evidence: new Set(),
315
+ };
316
+ for (let taskIndex = 0; taskIndex < suite.taskDigests.length; taskIndex += 1) {
317
+ const task = tasks[taskIndex];
318
+ if (!task)
319
+ continue;
320
+ for (let repetition = 0; repetition < suite.reps; repetition += 1) {
321
+ const index = taskIndex * suite.reps + repetition;
322
+ const measurement = comparison.measurements[index];
323
+ if (!measurement)
324
+ continue;
325
+ const seed = suite.seeds[index];
326
+ for (const arm of ["baseline", "candidate"]) {
327
+ const evidence = measurement[arm];
328
+ const bundle = comparison.experiment[arm];
329
+ const materialization = evidence.materializationReceipt;
330
+ const plan = materialization.executionPlan;
331
+ const runCell = plan.material.runCell;
332
+ const result = evidence.receipt.benchmarkResult.material;
333
+ const outcome = evidence.receipt.taskOutcome.material.outcome;
334
+ const armPath = ["measurements", index, arm];
335
+ const expectedTree = bundle.code.kind === "disabled"
336
+ ? undefined
337
+ : bundle.code.kind === "no-op"
338
+ ? bundle.code.baseTree
339
+ : bundle.code.candidateTree;
340
+ const containerMatches = bundle.execution.environment.kind === "evaluator-task-container"
341
+ ? task.evaluatorTaskContainer !== undefined &&
342
+ plan.material.container.source === "evaluator-task-container" &&
343
+ JSON.stringify(plan.material.container) ===
344
+ JSON.stringify(task.evaluatorTaskContainer)
345
+ : plan.material.container.source === "pinned-container" &&
346
+ plan.material.container.image ===
347
+ bundle.execution.environment.container.image &&
348
+ plan.material.container.indexDigest ===
349
+ bundle.execution.environment.container.indexDigest;
350
+ const checks = [
351
+ [
352
+ runCell.experimentDigest === comparison.experiment.digest,
353
+ [...armPath, "materializationReceipt", "executionPlan", "material", "runCell", "experimentDigest"],
354
+ "execution evidence must bind the measured experiment",
355
+ ],
356
+ [
357
+ runCell.arm === arm,
358
+ [...armPath, "materializationReceipt", "executionPlan", "material", "runCell", "arm"],
359
+ "execution evidence must bind its measured arm",
360
+ ],
361
+ [
362
+ runCell.bundleDigest === bundle.digest && materialization.bundleDigest === bundle.digest,
363
+ [...armPath, "materializationReceipt", "bundleDigest"],
364
+ "execution evidence must bind the experiment arm bundle",
365
+ ],
366
+ [
367
+ runCell.suiteDigest === suite.digest &&
368
+ runCell.taskDigest === task.digest &&
369
+ runCell.taskIndex === taskIndex &&
370
+ runCell.repetition === repetition &&
371
+ runCell.seed === seed &&
372
+ runCell.attempt === 1,
373
+ [...armPath, "materializationReceipt", "executionPlan", "material", "runCell"],
374
+ "publishable execution evidence must use the first signed task attempt",
375
+ ],
376
+ [
377
+ materialization.codeKind === bundle.code.kind,
378
+ [...armPath, "materializationReceipt", "codeKind"],
379
+ "materialized code must match the experiment arm bundle",
380
+ ],
381
+ [
382
+ materialization.benchmark.suite.digest === suite.digest &&
383
+ materialization.benchmark.task.digest === task.digest,
384
+ [...armPath, "materializationReceipt", "benchmark"],
385
+ "execution evidence must capture the signed suite and selected task",
386
+ ],
387
+ [
388
+ materialization.harness === bundle.execution.harness &&
389
+ materialization.harnessVersion === bundle.execution.harnessVersion &&
390
+ plan.material.harness === bundle.execution.harness &&
391
+ plan.material.harnessVersion === bundle.execution.harnessVersion,
392
+ [...armPath, "materializationReceipt", "harness"],
393
+ "execution evidence must bind the candidate harness and version",
394
+ ],
395
+ [
396
+ JSON.stringify(plan.material.instructionDelivery) ===
397
+ JSON.stringify(bundle.execution.instructionDelivery),
398
+ [...armPath, "materializationReceipt", "executionPlan", "material", "instructionDelivery"],
399
+ "execution plan must bind the candidate instruction delivery",
400
+ ],
401
+ [
402
+ JSON.stringify(plan.material.limits) === JSON.stringify(task.limits),
403
+ [...armPath, "materializationReceipt", "executionPlan", "material", "limits"],
404
+ "execution plan must bind every signed task limit",
405
+ ],
406
+ [
407
+ containerMatches,
408
+ [...armPath, "materializationReceipt", "executionPlan", "material", "container"],
409
+ "execution plan must bind the candidate or evaluator task container",
410
+ ],
411
+ [
412
+ JSON.stringify(plan.material.candidateWorkspace) ===
413
+ JSON.stringify(bundle.execution.workspace) &&
414
+ JSON.stringify(materialization.candidateWorkspace) ===
415
+ JSON.stringify(bundle.execution.workspace),
416
+ [...armPath, "materializationReceipt", "candidateWorkspace"],
417
+ "execution evidence must bind the candidate workspace",
418
+ ],
419
+ [
420
+ materialization.materializedTree === expectedTree,
421
+ [...armPath, "materializationReceipt", "materializedTree"],
422
+ "materialized tree must match the candidate code",
423
+ ],
424
+ [
425
+ plan.material.launch.cwd.workspace === bundle.execution.cwd.workspace &&
426
+ plan.material.launch.cwd.path === bundle.execution.cwd.path,
427
+ [...armPath, "materializationReceipt", "executionPlan", "material", "launch", "cwd"],
428
+ "execution plan must bind the candidate working directory",
429
+ ],
430
+ [
431
+ plan.material.knowledgeManifestDigest === bundle.knowledge?.snapshot.digest &&
432
+ materialization.knowledgeManifestDigest === bundle.knowledge?.snapshot.digest,
433
+ [...armPath, "materializationReceipt", "knowledgeManifestDigest"],
434
+ "execution evidence must bind the candidate knowledge snapshot",
435
+ ],
436
+ [
437
+ (bundle.memory.mode === "disabled" && plan.material.memory.mode === "disabled") ||
438
+ (bundle.memory.mode === "isolated" &&
439
+ plan.material.memory.mode === "isolated" &&
440
+ plan.material.memory.seedDigest === bundle.memory.seed?.sha256),
441
+ [...armPath, "materializationReceipt", "executionPlan", "material", "memory"],
442
+ "execution plan must bind the candidate memory policy",
443
+ ],
444
+ [
445
+ result.evidence.sha256 !== task.grader.artifact.sha256,
446
+ [...armPath, "receipt", "benchmarkResult", "material", "evidence", "sha256"],
447
+ "grading evidence must be distinct from the signed grader implementation",
448
+ ],
449
+ [
450
+ JSON.stringify(result.grader) === JSON.stringify(task.grader),
451
+ [...armPath, "receipt", "benchmarkResult", "material", "grader"],
452
+ "benchmark result must bind the signed grader",
453
+ ],
454
+ [
455
+ materialization.profileActivation.profilePlan.material.sourceProfileDigest ===
456
+ canonicalCandidateDigest(bundle.profile),
457
+ [...armPath, "materializationReceipt", "profileActivation", "profilePlan", "material", "sourceProfileDigest"],
458
+ "materialized profile files must bind the experiment arm profile",
459
+ ],
460
+ [
461
+ JSON.stringify(materialization.resolvedModel) === JSON.stringify(task.model),
462
+ [...armPath, "materializationReceipt", "resolvedModel"],
463
+ "execution must use the selected task model",
464
+ ],
465
+ [
466
+ (task.limits.maxModelCalls === 0 &&
467
+ materialization.executionPlan.material.model.access.network.mode ===
468
+ "disabled") ||
469
+ (task.limits.maxModelCalls > 0 &&
470
+ materialization.executionPlan.material.model.access.network.mode ===
471
+ "gateway-only"),
472
+ [
473
+ ...armPath,
474
+ "materializationReceipt",
475
+ "executionPlan",
476
+ "material",
477
+ "model",
478
+ "access",
479
+ "network",
480
+ ],
481
+ "model gateway access must match the signed model-call limit",
482
+ ],
483
+ [
484
+ outcome.kind === task.outcome.kind,
485
+ [...armPath, "receipt", "taskOutcome", "material", "outcome", "kind"],
486
+ "captured outcome must match the selected task contract",
487
+ ],
488
+ [
489
+ task.outcome.kind !== "output" ||
490
+ (outcome.kind === "output" &&
491
+ outcome.spec.mediaType === task.outcome.mediaType &&
492
+ outcome.spec.maxBytes === task.outcome.maxBytes),
493
+ [...armPath, "receipt", "taskOutcome", "material", "outcome", "spec"],
494
+ "captured output must match the selected task specification",
495
+ ],
496
+ [
497
+ task.outcome.kind !== "workspace" ||
498
+ (outcome.kind === "workspace" &&
499
+ task.repository !== undefined &&
500
+ outcome.baseRepository.identity === task.repository.identity &&
501
+ outcome.baseRepository.rootIdentity === task.repository.rootIdentity &&
502
+ outcome.baseRepository.commit === task.repository.baseCommit &&
503
+ outcome.baseRepository.tree === task.repository.baseTree),
504
+ [...armPath, "receipt", "taskOutcome", "material", "outcome", "baseRepository"],
505
+ "captured workspace must start from the selected task repository",
506
+ ],
507
+ ];
508
+ for (const [valid, path, message] of checks) {
509
+ if (!valid)
510
+ ctx.addIssue({ code: "custom", path, message });
511
+ }
512
+ const identitiesForRun = {
513
+ execution: plan.material.executionId,
514
+ runCell: runCell.digest,
515
+ materialization: materialization.digest,
516
+ receipt: evidence.receipt.digest,
517
+ evidence: evidence.digest,
518
+ };
519
+ for (const [kind, identity] of Object.entries(identitiesForRun)) {
520
+ if (executionIdentities[kind].has(identity)) {
521
+ ctx.addIssue({
522
+ code: "custom",
523
+ path: armPath,
524
+ message: `measured executions must not reuse ${kind} identity`,
525
+ });
526
+ }
527
+ executionIdentities[kind].add(identity);
528
+ }
529
+ }
530
+ }
531
+ }
532
+ if (comparison.overall.n !== expectedN) {
533
+ ctx.addIssue({
534
+ code: "custom",
535
+ path: ["overall", "n"],
536
+ message: "measured sample count must equal the complete benchmark suite",
537
+ });
538
+ }
539
+ if (comparison.measurements.length > 0) {
540
+ refineMeasuredMean(comparison.overall.baseline, comparison.measurements.map((row) => row.baseline.receipt.benchmarkResult.material.score), ["overall", "baseline"], ctx);
541
+ refineMeasuredMean(comparison.overall.candidate, comparison.measurements.map((row) => row.candidate.receipt.benchmarkResult.material.score), ["overall", "candidate"], ctx);
542
+ }
169
543
  const identities = new Set();
170
544
  const qualityObjectives = new Set();
171
545
  const dimensionParents = [];
@@ -174,6 +548,21 @@ export const agentImprovementMeasuredComparisonSchema = z
174
548
  for (const [index, objective] of comparison.objectives.entries()) {
175
549
  if (objective.availability === "measured") {
176
550
  refineEstimate(objective, ["objectives", index], ctx);
551
+ if (objective.n !== expectedN) {
552
+ ctx.addIssue({
553
+ code: "custom",
554
+ path: ["objectives", index, "n"],
555
+ message: "measured objective count must equal the complete benchmark suite",
556
+ });
557
+ }
558
+ if (comparison.measurements.length > 0 && objective.kind === "cost") {
559
+ refineMeasuredMean(objective.baseline, comparison.measurements.map((row) => executionCostUsd(row.baseline)), ["objectives", index, "baseline"], ctx);
560
+ refineMeasuredMean(objective.candidate, comparison.measurements.map((row) => executionCostUsd(row.candidate)), ["objectives", index, "candidate"], ctx);
561
+ }
562
+ if (comparison.measurements.length > 0 && objective.kind === "latency") {
563
+ refineMeasuredMean(objective.baseline, comparison.measurements.map((row) => executionLatencyMs(row.baseline)), ["objectives", index, "baseline"], ctx);
564
+ refineMeasuredMean(objective.candidate, comparison.measurements.map((row) => executionLatencyMs(row.candidate)), ["objectives", index, "candidate"], ctx);
565
+ }
177
566
  }
178
567
  const identity = objective.kind === "dimension"
179
568
  ? `${objective.kind}:${objective.objective}:${objective.name}`
@@ -229,6 +618,29 @@ export const agentImprovementMeasuredComparisonSchema = z
229
618
  message: "power analysis must use the paired held-out sample",
230
619
  });
231
620
  }
621
+ if (comparison.overall.confidenceInterval.level !==
622
+ comparison.experiment.policy.confidenceLevel ||
623
+ comparison.overall.confidenceInterval.resamples !==
624
+ comparison.experiment.policy.resamples ||
625
+ comparison.power.confidenceLevel !== comparison.experiment.policy.confidenceLevel) {
626
+ ctx.addIssue({
627
+ code: "custom",
628
+ path: ["experiment", "policy"],
629
+ message: "reported uncertainty must use the frozen evaluation policy",
630
+ });
631
+ }
632
+ for (const [index, objective] of comparison.objectives.entries()) {
633
+ if (objective.availability === "measured" &&
634
+ (objective.confidenceInterval.level !==
635
+ comparison.experiment.policy.confidenceLevel ||
636
+ objective.confidenceInterval.resamples !== comparison.experiment.policy.resamples)) {
637
+ ctx.addIssue({
638
+ code: "custom",
639
+ path: ["objectives", index, "confidenceInterval"],
640
+ message: "objective uncertainty must use the frozen evaluation policy",
641
+ });
642
+ }
643
+ }
232
644
  if (!isCanonicalJsonValue(comparison)) {
233
645
  ctx.addIssue({
234
646
  code: "custom",
@@ -245,31 +657,34 @@ export const agentImprovementProposalSchema = z
245
657
  .rest(improvementSurfaceSchema)
246
658
  .refine((surfaces) => new Set(surfaces).size === surfaces.length, "changed surfaces must be unique"),
247
659
  proposedAt: z.iso.datetime(),
248
- baselineProfile: agentProfileSchema,
249
660
  findings: z.array(canonicalJsonObjectSchema),
250
661
  evaluation: agentImprovementMeasuredComparisonSchema,
251
- candidateBundle: agentCandidateBundleSchema,
252
662
  digest: sha256DigestSchema,
253
663
  })
254
664
  .strict()
255
665
  .superRefine((proposal, ctx) => {
256
- const measuredBenchmark = proposal.evaluation.benchmark;
257
- const bundleBenchmark = proposal.candidateBundle.lineage.benchmark;
258
- if (!bundleBenchmark ||
259
- measuredBenchmark.name !== bundleBenchmark.name ||
260
- measuredBenchmark.version !== bundleBenchmark.version ||
261
- measuredBenchmark.splitDigest !== bundleBenchmark.splitDigest) {
666
+ if (proposal.evaluation.decision.outcome !== "ship") {
667
+ ctx.addIssue({
668
+ code: "custom",
669
+ path: ["evaluation", "decision", "outcome"],
670
+ message: "an improvement proposal requires a passing measured comparison",
671
+ });
672
+ }
673
+ if (!proposal.evaluation.power.sufficient ||
674
+ proposal.evaluation.overall.n <
675
+ proposal.evaluation.experiment.policy.minProductiveRuns) {
262
676
  ctx.addIssue({
263
677
  code: "custom",
264
- path: ["evaluation", "benchmark"],
265
- message: "measured comparison must bind the candidate development split",
678
+ path: ["evaluation", "power"],
679
+ message: "an improvement proposal requires sufficient pre-registered power",
266
680
  });
267
681
  }
268
- if (proposal.evaluation.candidateBundleDigest !== proposal.candidateBundle.digest) {
682
+ if (proposal.evaluation.experiment.baseline.digest ===
683
+ proposal.evaluation.experiment.candidate.digest) {
269
684
  ctx.addIssue({
270
685
  code: "custom",
271
- path: ["evaluation", "candidateBundleDigest"],
272
- message: "measured comparison must bind the exact candidate bundle",
686
+ path: ["evaluation", "experiment", "candidate", "digest"],
687
+ message: "an improvement proposal requires a changed candidate bundle",
273
688
  });
274
689
  }
275
690
  if (!isCanonicalJsonValue(proposal)) {
@@ -299,11 +714,33 @@ function refineEstimate(estimate, path, ctx) {
299
714
  });
300
715
  }
301
716
  }
717
+ function refineMeasuredMean(reported, values, path, ctx) {
718
+ const measured = values.reduce((sum, value) => sum + value, 0) / values.length;
719
+ const tolerance = Number.EPSILON * Math.max(1, Math.abs(measured)) * values.length * 8;
720
+ if (Math.abs(reported - measured) > tolerance) {
721
+ ctx.addIssue({
722
+ code: "custom",
723
+ path,
724
+ message: "reported mean must equal the signed per-cell results",
725
+ });
726
+ }
727
+ }
728
+ function approximatelyEqual(left, right) {
729
+ const tolerance = Number.EPSILON * Math.max(1, Math.abs(left), Math.abs(right)) * 16;
730
+ return Math.abs(left - right) <= tolerance;
731
+ }
732
+ function executionCostUsd(evidence) {
733
+ return (evidence.receipt.modelSettlement.material.usage.costUsdNanos +
734
+ evidence.receipt.benchmarkResult.material.grading.usage.costUsdNanos) / 1_000_000_000;
735
+ }
736
+ function executionLatencyMs(evidence) {
737
+ return (evidence.receipt.timing.durationMs +
738
+ evidence.receipt.benchmarkResult.material.grading.timing.durationMs);
739
+ }
302
740
  export const agentImprovementReviewSchema = z
303
741
  .object({
304
742
  kind: z.literal("agent-improvement-review"),
305
743
  proposalDigest: sha256DigestSchema,
306
- candidateBundleDigest: sha256DigestSchema,
307
744
  decision: z.enum(["approve", "reject", "request-changes"]),
308
745
  reviewedBy: z.string().min(1),
309
746
  reviewedAt: z.iso.datetime(),
@@ -313,93 +750,46 @@ export const agentImprovementReviewSchema = z
313
750
  })
314
751
  .strict()
315
752
  .refine(isCanonicalJsonValue, "review must contain only RFC 8785 JSON values");
316
- export const candidateExecutionEvidenceSchema = z
753
+ export const agentImprovementActivationSchema = z
317
754
  .object({
318
- kind: z.literal("agent-candidate-execution-evidence"),
755
+ kind: z.literal("agent-improvement-activation"),
319
756
  proposalDigest: sha256DigestSchema,
320
757
  reviewDigest: sha256DigestSchema,
321
- executionId: z.string().regex(/^[A-Za-z0-9._:-]{1,200}$/),
322
- succeeded: z.literal(true),
323
- materializationReceipt: agentCandidateMaterializationReceiptSchema,
324
- profileActivation: agentCandidateProfileActivationSchema,
325
- receipt: agentCandidateRunReceiptSchema,
758
+ experimentDigest: sha256DigestSchema,
759
+ candidateBundleDigest: sha256DigestSchema,
760
+ targets: z
761
+ .tuple([
762
+ z
763
+ .object({
764
+ surface: improvementSurfaceSchema,
765
+ identity: z.string().min(1).max(500),
766
+ expectedBaseDigest: sha256DigestSchema,
767
+ })
768
+ .strict(),
769
+ ])
770
+ .rest(z
771
+ .object({
772
+ surface: improvementSurfaceSchema,
773
+ identity: z.string().min(1).max(500),
774
+ expectedBaseDigest: sha256DigestSchema,
775
+ })
776
+ .strict()),
777
+ fundingOwner: z.string().min(1).max(500),
778
+ authorizedBy: z.string().min(1).max(500),
779
+ authorizedAt: z.iso.datetime(),
326
780
  digest: sha256DigestSchema,
327
781
  })
328
782
  .strict()
329
- .superRefine((evidence, ctx) => {
330
- const materialization = evidence.materializationReceipt;
331
- const plan = materialization.executionPlan;
332
- const plannedOutcome = plan.material.task.outcome;
333
- const capturedOutcome = evidence.receipt.taskOutcome.material.outcome;
334
- const checks = [
335
- [
336
- evidence.receipt.materializationReceiptDigest === materialization.digest,
337
- ["receipt", "materializationReceiptDigest"],
338
- "run receipt must bind the included materialization receipt",
339
- ],
340
- [
341
- evidence.receipt.executionPlanDigest === plan.digest,
342
- ["receipt", "executionPlanDigest"],
343
- "run receipt must bind the included execution plan",
344
- ],
345
- [
346
- evidence.receipt.bundleDigest === materialization.bundleDigest,
347
- ["receipt", "bundleDigest"],
348
- "run receipt and materialization must bind one bundle",
349
- ],
350
- [
351
- evidence.executionId === plan.material.executionId,
352
- ["executionId"],
353
- "execution evidence must bind the materialized execution id",
354
- ],
355
- [
356
- evidence.profileActivation.profilePlan.digest ===
357
- materialization.profilePlan.digest,
358
- ["profileActivation", "profilePlan", "digest"],
359
- "profile activation must bind the materialized profile plan",
360
- ],
361
- [
362
- capturedOutcome.kind === plannedOutcome.kind,
363
- ["receipt", "taskOutcome", "material", "outcome", "kind"],
364
- "task outcome kind must match the signed execution plan",
365
- ],
366
- [
367
- evidence.receipt.termination.kind === "exit" &&
368
- evidence.receipt.termination.exitCode === 0,
369
- ["receipt", "termination"],
370
- "successful execution evidence requires a zero exit status",
371
- ],
372
- ];
373
- if (plannedOutcome.kind === "output" && capturedOutcome.kind === "output") {
374
- checks.push([
375
- capturedOutcome.spec.mediaType === plannedOutcome.mediaType &&
376
- capturedOutcome.spec.maxBytes === plannedOutcome.maxBytes,
377
- ["receipt", "taskOutcome", "material", "outcome", "spec"],
378
- "task output constraints must match the signed execution plan",
379
- ]);
380
- }
381
- if (plannedOutcome.kind === "workspace" &&
382
- capturedOutcome.kind === "workspace" &&
383
- plan.material.task.repository) {
384
- const repository = plan.material.task.repository;
385
- const base = capturedOutcome.baseRepository;
386
- checks.push([
387
- base.identity === repository.identity &&
388
- base.rootIdentity === repository.rootIdentity &&
389
- base.commit === repository.baseCommit &&
390
- base.tree === repository.baseTree,
391
- ["receipt", "taskOutcome", "material", "outcome", "baseRepository"],
392
- "workspace outcome must bind the signed repository base",
393
- ]);
394
- }
395
- for (const [valid, path, message] of checks) {
396
- if (!valid)
397
- ctx.addIssue({ code: "custom", path, message });
398
- }
399
- if (!isCanonicalJsonValue(evidence)) {
783
+ .superRefine((activation, ctx) => {
784
+ const identities = activation.targets.map((target) => `${target.surface}\u0000${target.identity}`);
785
+ if (new Set(identities).size !== identities.length) {
400
786
  ctx.addIssue({
401
787
  code: "custom",
402
- message: "candidate execution evidence must contain only RFC 8785 JSON values",
788
+ path: ["targets"],
789
+ message: "activation targets must be unique by surface and identity",
403
790
  });
404
791
  }
792
+ if (!isCanonicalJsonValue(activation)) {
793
+ ctx.addIssue({ code: "custom", message: "activation must contain only RFC 8785 JSON values" });
794
+ }
405
795
  });