@tangle-network/agent-interface 0.35.0 → 0.37.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.
@@ -1,6 +1,7 @@
1
1
  import type { AgentProfile, AgentProfileFileMount, AgentProfileHookCommand, AgentProfileMode, AgentProfileModelHints, AgentProfileResources, AgentSubagentProfile, ReasoningEffort } from "./agent-profile.js";
2
2
  import type { HarnessType } from "./harness.js";
3
- import type { AgentProfileImprovementMeasuredComparison } from "./agent-profile-improvement.js";
3
+ import type { AgentProfileImprovementExecutionRef, AgentProfileImprovementMeasuredComparison } from "./agent-profile-improvement.js";
4
+ import type { AgentImprovementSource } from "./agent-improvement-source.js";
4
5
  /** Full SHA-256 digest with an explicit algorithm prefix. */
5
6
  export type Sha256Digest = `sha256:${string}`;
6
7
  /** RFC 8785 JSON Canonicalization Scheme followed by SHA-256. */
@@ -55,6 +56,8 @@ export interface AgentCandidateInlineResource {
55
56
  content: string;
56
57
  sha256: Sha256Digest;
57
58
  byteLength: number;
59
+ /** Optional immutable origin, license obligations, and import history. */
60
+ source?: AgentImprovementSource;
58
61
  }
59
62
  /** GitHub content pinned to a full Git object id and expected content digest. */
60
63
  export interface AgentCandidateGitHubResource {
@@ -65,6 +68,8 @@ export interface AgentCandidateGitHubResource {
65
68
  name?: string;
66
69
  sha256: Sha256Digest;
67
70
  byteLength: number;
71
+ /** Optional immutable origin, license obligations, and import history. */
72
+ source?: AgentImprovementSource;
68
73
  }
69
74
  export type AgentCandidateResourceRef = AgentCandidateInlineResource | AgentCandidateGitHubResource;
70
75
  export interface AgentCandidateFileMount extends Omit<AgentProfileFileMount, "resource"> {
@@ -235,6 +240,33 @@ export type AgentCandidateMemoryPolicy = {
235
240
  scope: "task";
236
241
  seed?: AgentCandidateArtifactRef;
237
242
  };
243
+ /** Whether a settled cost came from provider billing or a reproducible estimate. */
244
+ export type AgentCandidateCostProvenance = "observed" | "estimated";
245
+ /** One known dollar amount carried with its source. */
246
+ export interface AgentImprovementCost {
247
+ usd: number;
248
+ provenance: AgentCandidateCostProvenance;
249
+ }
250
+ /** Complete accounting for a measured improvement proposal. */
251
+ export interface AgentImprovementEvaluationAccounting {
252
+ generationsExplored: number;
253
+ /** Trace analysis and candidate search before held-out execution. */
254
+ preparation: {
255
+ wallDurationMs: number;
256
+ cost: AgentImprovementCost;
257
+ };
258
+ /** Held-out baseline/candidate execution. Work time sums parallel calls. */
259
+ measurement: {
260
+ wallDurationMs: number;
261
+ workDurationMs: number;
262
+ cost: AgentImprovementCost;
263
+ };
264
+ /** Wall time is preparation plus held-out measurement. */
265
+ total: {
266
+ wallDurationMs: number;
267
+ cost: AgentImprovementCost;
268
+ };
269
+ }
238
270
  /** Lossless evaluator-owned usage totals for one candidate execution. */
239
271
  export interface AgentCandidateFixedSpend {
240
272
  inputTokens: number;
@@ -244,6 +276,7 @@ export interface AgentCandidateFixedSpend {
244
276
  modelCalls: number;
245
277
  /** Integer billionths of one US dollar. */
246
278
  costUsdNanos: number;
279
+ costProvenance: AgentCandidateCostProvenance;
247
280
  }
248
281
  /** Evidence and ancestry that produced the immutable candidate. */
249
282
  export interface AgentCandidateLineage {
@@ -350,13 +383,14 @@ export type AgentCandidateEffectiveMemory = {
350
383
  beforeState: AgentCandidateWorkspaceSnapshotEvidence;
351
384
  seedDigest?: Sha256Digest;
352
385
  };
353
- /** The exact evaluator-owned limits applied to every candidate arm. */
386
+ /** The exact evaluator-owned limits applied to every complete candidate arm. */
354
387
  export interface AgentCandidateExecutionLimits {
355
388
  timeoutMs: number;
356
389
  maxSteps: number;
357
390
  maxModelCalls: number;
358
391
  maxInputTokens: number;
359
392
  maxOutputTokens: number;
393
+ /** Total agent plus grading spend for one complete arm. */
360
394
  maxCostUsd: number;
361
395
  }
362
396
  /** Counted attempt identity and the only retry class allowed by the evaluator. */
@@ -765,15 +799,7 @@ export interface AgentImprovementMeasuredComparisonBase<TExperiment, TMeasuremen
765
799
  candidateContentHash: string;
766
800
  };
767
801
  diff: string;
768
- evaluation: {
769
- generationsExplored: number;
770
- searchDurationMs: number;
771
- executionDurationMs: number;
772
- durationMs: number;
773
- searchCostUsd: number;
774
- executionCostUsd: number;
775
- totalCostUsd: number;
776
- };
802
+ evaluation: AgentImprovementEvaluationAccounting;
777
803
  metadata?: {
778
804
  [key: string]: AgentCandidateJsonValue;
779
805
  };
@@ -822,6 +848,8 @@ export interface AgentImprovementActivation {
822
848
  experimentDigest: Sha256Digest;
823
849
  /** Exact proposed state, whether it is a sealed bundle or a normal profile. */
824
850
  candidateDigest: Sha256Digest;
851
+ /** Required when a target uses `agent-profile`; the schema enforces this condition. */
852
+ executionRef?: AgentProfileImprovementExecutionRef;
825
853
  intent: AgentImprovementActivationIntent;
826
854
  targets: [AgentImprovementActivationTarget, ...AgentImprovementActivationTarget[]];
827
855
  fundingOwner: string;
@@ -895,6 +923,7 @@ export interface AgentCandidateModelSettlementCall {
895
923
  cachedInputTokens: number;
896
924
  reasoningTokens: number;
897
925
  costUsdNanos: number;
926
+ costProvenance: AgentCandidateCostProvenance;
898
927
  }
899
928
  /** Canonical model-access ledger after the evaluator has revoked access. */
900
929
  export interface AgentCandidateModelSettlementMaterial {
@@ -16,6 +16,10 @@ export declare const agentExecutionLimitObservationSchema: z.ZodObject<{
16
16
  reasoningTokens: z.ZodNumber;
17
17
  modelCalls: z.ZodNumber;
18
18
  costUsdNanos: z.ZodNumber;
19
+ costProvenance: z.ZodEnum<{
20
+ observed: "observed";
21
+ estimated: "estimated";
22
+ }>;
19
23
  }, z.core.$strict>;
20
24
  }, z.core.$strict>;
21
25
  export interface RefineAgentExecutionWithinLimitsOptions {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import type { AgentCandidateEvaluationPolicy, AgentCandidateJsonValue, AgentImprovementMeasuredComparisonBase } from "./agent-candidate.js";
2
+ import type { AgentCandidateCostProvenance, AgentCandidateEvaluationPolicy, AgentCandidateJsonValue, AgentImprovementMeasuredComparisonBase } from "./agent-candidate.js";
3
3
  export declare const canonicalJsonSchema: z.ZodCustom<AgentCandidateJsonValue, AgentCandidateJsonValue>;
4
4
  export declare const canonicalJsonObjectSchema: z.ZodRecord<z.ZodString, z.ZodCustom<AgentCandidateJsonValue, AgentCandidateJsonValue>>;
5
5
  export declare const agentCandidateEvaluationPolicySchema: z.ZodObject<{
@@ -12,6 +12,13 @@ export declare const agentCandidateEvaluationPolicySchema: z.ZodObject<{
12
12
  criticalDimensions: z.ZodArray<z.ZodString>;
13
13
  regressionTolerance: z.ZodNumber;
14
14
  }, z.core.$strict>;
15
+ export declare const agentImprovementCostSchema: z.ZodObject<{
16
+ usd: z.ZodNumber;
17
+ provenance: z.ZodEnum<{
18
+ observed: "observed";
19
+ estimated: "estimated";
20
+ }>;
21
+ }, z.core.$strict>;
15
22
  export declare const measuredComparisonCommonShape: {
16
23
  overall: z.ZodObject<{
17
24
  direction: z.ZodLiteral<"higher-is-better">;
@@ -157,12 +164,37 @@ export declare const measuredComparisonCommonShape: {
157
164
  diff: z.ZodString;
158
165
  evaluation: z.ZodObject<{
159
166
  generationsExplored: z.ZodNumber;
160
- searchDurationMs: z.ZodNumber;
161
- executionDurationMs: z.ZodNumber;
162
- durationMs: z.ZodNumber;
163
- searchCostUsd: z.ZodNumber;
164
- executionCostUsd: z.ZodNumber;
165
- totalCostUsd: z.ZodNumber;
167
+ preparation: z.ZodObject<{
168
+ wallDurationMs: z.ZodNumber;
169
+ cost: z.ZodObject<{
170
+ usd: z.ZodNumber;
171
+ provenance: z.ZodEnum<{
172
+ observed: "observed";
173
+ estimated: "estimated";
174
+ }>;
175
+ }, z.core.$strict>;
176
+ }, z.core.$strict>;
177
+ measurement: z.ZodObject<{
178
+ wallDurationMs: z.ZodNumber;
179
+ workDurationMs: z.ZodNumber;
180
+ cost: z.ZodObject<{
181
+ usd: z.ZodNumber;
182
+ provenance: z.ZodEnum<{
183
+ observed: "observed";
184
+ estimated: "estimated";
185
+ }>;
186
+ }, z.core.$strict>;
187
+ }, z.core.$strict>;
188
+ total: z.ZodObject<{
189
+ wallDurationMs: z.ZodNumber;
190
+ cost: z.ZodObject<{
191
+ usd: z.ZodNumber;
192
+ provenance: z.ZodEnum<{
193
+ observed: "observed";
194
+ estimated: "estimated";
195
+ }>;
196
+ }, z.core.$strict>;
197
+ }, z.core.$strict>;
166
198
  }, z.core.$strict>;
167
199
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodCustom<AgentCandidateJsonValue, AgentCandidateJsonValue>>>;
168
200
  };
@@ -184,6 +216,7 @@ export declare function refineMeasuredComparisonSummary<TReceipt>(comparison: Me
184
216
  score(receipt: TReceipt): number;
185
217
  dimension(receipt: TReceipt, name: string): number | undefined;
186
218
  cost(receipt: TReceipt): number;
219
+ costProvenance(receipt: TReceipt): AgentCandidateCostProvenance;
187
220
  latency(receipt: TReceipt): number;
188
221
  }, ctx: z.RefinementCtx): void;
189
222
  export declare function refineEstimate(estimate: {
@@ -95,6 +95,36 @@ export const agentCandidateEvaluationPolicySchema = z
95
95
  });
96
96
  }
97
97
  });
98
+ export const agentImprovementCostSchema = z
99
+ .object({
100
+ usd: z.number().finite().nonnegative(),
101
+ provenance: z.enum(["observed", "estimated"]),
102
+ })
103
+ .strict();
104
+ const agentImprovementEvaluationSchema = z
105
+ .object({
106
+ generationsExplored: z.number().int().nonnegative(),
107
+ preparation: z
108
+ .object({
109
+ wallDurationMs: z.number().finite().nonnegative(),
110
+ cost: agentImprovementCostSchema,
111
+ })
112
+ .strict(),
113
+ measurement: z
114
+ .object({
115
+ wallDurationMs: z.number().finite().nonnegative(),
116
+ workDurationMs: z.number().finite().nonnegative(),
117
+ cost: agentImprovementCostSchema,
118
+ })
119
+ .strict(),
120
+ total: z
121
+ .object({
122
+ wallDurationMs: z.number().finite().nonnegative(),
123
+ cost: agentImprovementCostSchema,
124
+ })
125
+ .strict(),
126
+ })
127
+ .strict();
98
128
  export const measuredComparisonCommonShape = {
99
129
  overall: z
100
130
  .object({
@@ -148,17 +178,7 @@ export const measuredComparisonCommonShape = {
148
178
  })
149
179
  .strict(),
150
180
  diff: z.string(),
151
- evaluation: z
152
- .object({
153
- generationsExplored: z.number().int().nonnegative(),
154
- searchDurationMs: z.number().finite().nonnegative(),
155
- executionDurationMs: z.number().finite().nonnegative(),
156
- durationMs: z.number().finite().nonnegative(),
157
- searchCostUsd: z.number().finite().nonnegative(),
158
- executionCostUsd: z.number().finite().nonnegative(),
159
- totalCostUsd: z.number().finite().nonnegative(),
160
- })
161
- .strict(),
181
+ evaluation: agentImprovementEvaluationSchema,
162
182
  metadata: canonicalJsonObjectSchema.optional(),
163
183
  };
164
184
  /** Keep receipt identity reuse rules identical across measured source formats. */
@@ -181,12 +201,18 @@ export function createMeasuredComparisonIdentityRegistry(options) {
181
201
  }
182
202
  export function refineMeasuredComparisonSummary(comparison, policy, expectedN, measurements, values, ctx) {
183
203
  refineEstimate(comparison.overall, ["overall"], ctx);
184
- if (!numbersApproximatelyEqual(comparison.evaluation.durationMs, comparison.evaluation.searchDurationMs + comparison.evaluation.executionDurationMs) ||
185
- !numbersApproximatelyEqual(comparison.evaluation.totalCostUsd, comparison.evaluation.searchCostUsd + comparison.evaluation.executionCostUsd)) {
204
+ const totalCostProvenance = comparison.evaluation.preparation.cost.provenance === "observed" &&
205
+ comparison.evaluation.measurement.cost.provenance === "observed"
206
+ ? "observed"
207
+ : "estimated";
208
+ if (!numbersApproximatelyEqual(comparison.evaluation.total.wallDurationMs, comparison.evaluation.preparation.wallDurationMs +
209
+ comparison.evaluation.measurement.wallDurationMs) ||
210
+ !numbersApproximatelyEqual(comparison.evaluation.total.cost.usd, comparison.evaluation.preparation.cost.usd + comparison.evaluation.measurement.cost.usd) ||
211
+ comparison.evaluation.total.cost.provenance !== totalCostProvenance) {
186
212
  ctx.addIssue({
187
213
  code: "custom",
188
214
  path: ["evaluation"],
189
- message: "evaluation totals must equal their search and execution components",
215
+ message: "evaluation totals must equal preparation and measurement accounting",
190
216
  });
191
217
  }
192
218
  if (comparison.overall.n !== expectedN) {
@@ -265,6 +291,21 @@ export function refineMeasuredComparisonSummary(comparison, policy, expectedN, m
265
291
  latencyCount += 1;
266
292
  }
267
293
  }
294
+ const measurementCostUsd = measurements.reduce((sum, measurement) => sum + values.cost(measurement.baseline) + values.cost(measurement.candidate), 0);
295
+ const measurementWorkDurationMs = measurements.reduce((sum, measurement) => sum + values.latency(measurement.baseline) + values.latency(measurement.candidate), 0);
296
+ const measurementCostProvenance = measurements.every((measurement) => values.costProvenance(measurement.baseline) === "observed" &&
297
+ values.costProvenance(measurement.candidate) === "observed")
298
+ ? "observed"
299
+ : "estimated";
300
+ if (!numbersApproximatelyEqual(comparison.evaluation.measurement.cost.usd, measurementCostUsd) ||
301
+ comparison.evaluation.measurement.cost.provenance !== measurementCostProvenance ||
302
+ !numbersApproximatelyEqual(comparison.evaluation.measurement.workDurationMs, measurementWorkDurationMs)) {
303
+ ctx.addIssue({
304
+ code: "custom",
305
+ path: ["evaluation", "measurement"],
306
+ message: "measurement accounting must equal the complete signed receipts",
307
+ });
308
+ }
268
309
  if (costCount !== 1 || latencyCount !== 1) {
269
310
  ctx.addIssue({
270
311
  code: "custom",
@@ -2,19 +2,77 @@ import { z } from "zod";
2
2
  import type { AgentCandidateJsonValue } from "./agent-candidate.js";
3
3
  /** Metadata key that binds a measured improvement to its exact external source. */
4
4
  export declare const AGENT_IMPROVEMENT_SOURCE_METADATA_KEY = "agentImprovementSource";
5
+ /** License terms retained with an imported source. */
6
+ export declare const agentSourceLicenseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
7
+ kind: z.ZodLiteral<"spdx">;
8
+ expression: z.ZodString;
9
+ }, z.core.$strict>, z.ZodObject<{
10
+ kind: z.ZodLiteral<"custom">;
11
+ name: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
12
+ reference: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
13
+ termsDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
14
+ }, z.core.$strict>], "kind">;
15
+ export type AgentSourceLicense = z.infer<typeof agentSourceLicenseSchema>;
16
+ /**
17
+ * One content transition applied while importing a source.
18
+ *
19
+ * `identity` and `revision` pin the transformer. Input/output digests make the
20
+ * ordered chain auditable without embedding executable transformation code.
21
+ * `procedureDigest` pins the implementation and non-secret configuration used
22
+ * for that step.
23
+ */
24
+ export declare const agentSourceTransformationSchema: z.ZodObject<{
25
+ kind: z.ZodEnum<{
26
+ normalization: "normalization";
27
+ transformation: "transformation";
28
+ }>;
29
+ identity: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
30
+ revision: z.ZodUnion<readonly [z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>, z.ZodNumber]>;
31
+ procedureDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
32
+ inputDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
33
+ outputDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
34
+ }, z.core.$strict>;
35
+ export type AgentSourceTransformation = z.infer<typeof agentSourceTransformationSchema>;
5
36
  /**
6
37
  * Stable reference for the state from which an improvement candidate was made.
7
38
  * `sourceIdentity` identifies the provider object; `sourceDigest` is the exact
8
- * measured source state; `sourceRevision` is an
39
+ * measured source state consumed by the candidate; `sourceRevision` is an
9
40
  * opaque provider version retained to make stale-source errors intelligible to
10
41
  * callers. A revision may be numeric or textual because providers use both
11
42
  * counters and immutable revision identifiers.
43
+ *
44
+ * Optional license, attribution, and notice fields preserve obligations when a
45
+ * public source becomes an improvement input. When transformations are
46
+ * present, their first input pins the fetched source bytes, every adjacent
47
+ * digest must join, and the final output must equal `sourceDigest`.
12
48
  */
13
49
  export declare const agentImprovementSourceSchema: z.ZodObject<{
14
50
  kind: z.ZodString;
15
- sourceIdentity: z.ZodString;
51
+ sourceIdentity: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
16
52
  sourceDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
17
- sourceRevision: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
53
+ sourceRevision: z.ZodUnion<readonly [z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>, z.ZodNumber]>;
54
+ license: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
55
+ kind: z.ZodLiteral<"spdx">;
56
+ expression: z.ZodString;
57
+ }, z.core.$strict>, z.ZodObject<{
58
+ kind: z.ZodLiteral<"custom">;
59
+ name: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
60
+ reference: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
61
+ termsDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
62
+ }, z.core.$strict>], "kind">>;
63
+ attribution: z.ZodOptional<z.ZodArray<z.ZodString>>;
64
+ notices: z.ZodOptional<z.ZodArray<z.ZodString>>;
65
+ transformations: z.ZodOptional<z.ZodArray<z.ZodObject<{
66
+ kind: z.ZodEnum<{
67
+ normalization: "normalization";
68
+ transformation: "transformation";
69
+ }>;
70
+ identity: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>;
71
+ revision: z.ZodUnion<readonly [z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>, z.ZodString>, z.ZodNumber]>;
72
+ procedureDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
73
+ inputDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
74
+ outputDigest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
75
+ }, z.core.$strict>>>;
18
76
  }, z.core.$strict>;
19
77
  export type AgentImprovementSource = z.infer<typeof agentImprovementSourceSchema>;
20
78
  /** Attach one validated source reference to signed improvement metadata. */
@@ -1,26 +1,140 @@
1
+ import parseSpdxExpression from "spdx-expression-parse";
1
2
  import { z } from "zod";
2
- import { sha256DigestSchema } from "./agent-candidate-schema-common.js";
3
+ import { isWellFormedUnicode, looksLikeCredential, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
3
4
  /** Metadata key that binds a measured improvement to its exact external source. */
4
5
  export const AGENT_IMPROVEMENT_SOURCE_METADATA_KEY = "agentImprovementSource";
6
+ function publicSourceTextSchema(maxLength, label) {
7
+ return z
8
+ .string()
9
+ .max(maxLength)
10
+ .refine(isWellFormedUnicode, `${label} must be well-formed Unicode`)
11
+ .refine((value) => !looksLikeCredential(value), `${label} cannot carry credentials`);
12
+ }
13
+ const sourceIdentitySchema = publicSourceTextSchema(256, "source identity")
14
+ .transform((value) => value.trim())
15
+ .pipe(z.string().min(1, "source identity cannot be blank"));
16
+ const sourceRevisionSchema = z.union([
17
+ publicSourceTextSchema(256, "source revision")
18
+ .transform((value) => value.trim())
19
+ .pipe(z.string().min(1, "source revision cannot be blank")),
20
+ z.number().int().nonnegative(),
21
+ ]);
22
+ const sourceStatementSchema = publicSourceTextSchema(16_384, "source statement").refine((value) => value.trim().length > 0, "statement cannot be blank");
23
+ const sourceStatementListSchema = z
24
+ .array(sourceStatementSchema)
25
+ .min(1)
26
+ .max(128)
27
+ .refine((values) => new Set(values).size === values.length, "statements must be unique");
28
+ function containsCustomLicenseReference(expression) {
29
+ if ("license" in expression) {
30
+ return (expression.license.startsWith("LicenseRef-") ||
31
+ expression.license.includes(":LicenseRef-"));
32
+ }
33
+ return (containsCustomLicenseReference(expression.left) ||
34
+ containsCustomLicenseReference(expression.right));
35
+ }
36
+ /** License terms retained with an imported source. */
37
+ export const agentSourceLicenseSchema = z.discriminatedUnion("kind", [
38
+ z
39
+ .object({
40
+ kind: z.literal("spdx"),
41
+ expression: z
42
+ .string()
43
+ .trim()
44
+ .min(1)
45
+ .max(1_024)
46
+ .refine((expression) => {
47
+ try {
48
+ return !containsCustomLicenseReference(parseSpdxExpression(expression));
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }, "license expression must use standard SPDX identifiers; content-pin custom terms with kind=custom"),
54
+ })
55
+ .strict(),
56
+ z
57
+ .object({
58
+ kind: z.literal("custom"),
59
+ name: sourceIdentitySchema,
60
+ /** URL or repository-relative path from which the exact terms can be recovered. */
61
+ reference: publicSourceTextSchema(2_048, "license reference")
62
+ .transform((value) => value.trim())
63
+ .pipe(z.string().min(1, "license reference cannot be blank")),
64
+ /** Digest of the custom license text, not of its URL or file name. */
65
+ termsDigest: sha256DigestSchema,
66
+ })
67
+ .strict(),
68
+ ]);
69
+ /**
70
+ * One content transition applied while importing a source.
71
+ *
72
+ * `identity` and `revision` pin the transformer. Input/output digests make the
73
+ * ordered chain auditable without embedding executable transformation code.
74
+ * `procedureDigest` pins the implementation and non-secret configuration used
75
+ * for that step.
76
+ */
77
+ export const agentSourceTransformationSchema = z
78
+ .object({
79
+ kind: z.enum(["normalization", "transformation"]),
80
+ identity: sourceIdentitySchema,
81
+ revision: sourceRevisionSchema,
82
+ procedureDigest: sha256DigestSchema,
83
+ inputDigest: sha256DigestSchema,
84
+ outputDigest: sha256DigestSchema,
85
+ })
86
+ .strict();
5
87
  /**
6
88
  * Stable reference for the state from which an improvement candidate was made.
7
89
  * `sourceIdentity` identifies the provider object; `sourceDigest` is the exact
8
- * measured source state; `sourceRevision` is an
90
+ * measured source state consumed by the candidate; `sourceRevision` is an
9
91
  * opaque provider version retained to make stale-source errors intelligible to
10
92
  * callers. A revision may be numeric or textual because providers use both
11
93
  * counters and immutable revision identifiers.
94
+ *
95
+ * Optional license, attribution, and notice fields preserve obligations when a
96
+ * public source becomes an improvement input. When transformations are
97
+ * present, their first input pins the fetched source bytes, every adjacent
98
+ * digest must join, and the final output must equal `sourceDigest`.
12
99
  */
13
100
  export const agentImprovementSourceSchema = z
14
101
  .object({
15
102
  kind: z.string().trim().min(1).max(100).regex(/^[a-z][a-z0-9-]*$/),
16
- sourceIdentity: z.string().trim().min(1).max(256),
103
+ sourceIdentity: sourceIdentitySchema,
17
104
  sourceDigest: sha256DigestSchema,
18
- sourceRevision: z.union([
19
- z.string().trim().min(1).max(256),
20
- z.number().int().nonnegative(),
21
- ]),
105
+ sourceRevision: sourceRevisionSchema,
106
+ license: agentSourceLicenseSchema.optional(),
107
+ attribution: sourceStatementListSchema.optional(),
108
+ notices: sourceStatementListSchema.optional(),
109
+ transformations: z
110
+ .array(agentSourceTransformationSchema)
111
+ .min(1)
112
+ .max(128)
113
+ .optional(),
22
114
  })
23
- .strict();
115
+ .strict()
116
+ .superRefine((source, ctx) => {
117
+ const transformations = source.transformations;
118
+ if (transformations === undefined)
119
+ return;
120
+ for (let index = 1; index < transformations.length; index++) {
121
+ if (transformations[index]?.inputDigest !==
122
+ transformations[index - 1]?.outputDigest) {
123
+ ctx.addIssue({
124
+ code: "custom",
125
+ path: ["transformations", index, "inputDigest"],
126
+ message: "transformation input must equal the previous output",
127
+ });
128
+ }
129
+ }
130
+ if (transformations.at(-1)?.outputDigest !== source.sourceDigest) {
131
+ ctx.addIssue({
132
+ code: "custom",
133
+ path: ["sourceDigest"],
134
+ message: "source digest must equal the final transformation output",
135
+ });
136
+ }
137
+ });
24
138
  /** Attach one validated source reference to signed improvement metadata. */
25
139
  export function agentImprovementSourceMetadata(source) {
26
140
  return {