@tangle-network/agent-interface 0.20.0 → 0.21.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.
@@ -0,0 +1,263 @@
1
+ import { z } from "zod";
2
+ import { agentCandidateCapturedArtifactSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, } from "./agent-candidate-artifact-schema.js";
3
+ import { agentCandidateExecutionPlanEvidenceSchema, agentCandidateProfilePlanEvidenceSchema, agentCandidateResolvedModelSchema, } from "./agent-candidate-execution-plan-schema.js";
4
+ import { agentCandidateSpendSchema, } from "./agent-candidate-lineage-schema.js";
5
+ import { gitObjectSchema, isCanonicalJsonValue, isSafeRelativePath, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
6
+ import { harnessTypeSchema } from "./harness.js";
7
+ const entrypointReceiptSchema = z
8
+ .object({
9
+ path: z
10
+ .string()
11
+ .refine((value) => isSafeRelativePath(value, false), "entrypoint path must be a canonical candidate-relative path"),
12
+ sha256: sha256DigestSchema,
13
+ byteLength: z.number().int().nonnegative(),
14
+ })
15
+ .strict();
16
+ const ociPlatformSchema = z
17
+ .object({
18
+ os: z.string().min(1),
19
+ architecture: z.string().min(1),
20
+ variant: z.string().min(1).optional(),
21
+ })
22
+ .strict();
23
+ export const agentCandidateTraceEvidenceSchema = z
24
+ .object({
25
+ schemaVersion: z.literal(1),
26
+ artifact: agentCandidateCapturedArtifactSchema,
27
+ eventCount: z.number().int().positive(),
28
+ modelCallCount: z.number().int().nonnegative(),
29
+ })
30
+ .strict()
31
+ .superRefine((trace, ctx) => {
32
+ if (trace.artifact.byteLength === 0) {
33
+ ctx.addIssue({
34
+ code: "custom",
35
+ path: ["artifact", "byteLength"],
36
+ message: "trace artifact must contain captured events",
37
+ });
38
+ }
39
+ if (trace.eventCount < trace.modelCallCount) {
40
+ ctx.addIssue({
41
+ code: "custom",
42
+ path: ["eventCount"],
43
+ message: "trace must contain at least one event for every model call",
44
+ });
45
+ }
46
+ });
47
+ export const agentCandidateModelUsageSchema = z
48
+ .object({
49
+ resolved: agentCandidateResolvedModelSchema,
50
+ usage: agentCandidateSpendSchema,
51
+ })
52
+ .strict();
53
+ export const agentCandidateMemoryReceiptSchema = z.discriminatedUnion("mode", [
54
+ z.object({ mode: z.literal("disabled") }).strict(),
55
+ z
56
+ .object({
57
+ mode: z.literal("isolated"),
58
+ scope: z.literal("task"),
59
+ effectiveNamespace: z.string().min(1),
60
+ resetEvidenceDigest: sha256DigestSchema,
61
+ beforeStateDigest: sha256DigestSchema,
62
+ afterState: agentCandidateWorkspaceSnapshotEvidenceSchema,
63
+ })
64
+ .strict(),
65
+ ]);
66
+ export const agentCandidateTerminationSchema = z.discriminatedUnion("kind", [
67
+ z.object({ kind: z.literal("exit"), exitCode: z.number().int() }).strict(),
68
+ z
69
+ .object({
70
+ kind: z.literal("timeout"),
71
+ timeoutMs: z.number().int().positive(),
72
+ })
73
+ .strict(),
74
+ z
75
+ .object({
76
+ kind: z.literal("signal"),
77
+ signal: z.string().regex(/^SIG[A-Z0-9]+$/),
78
+ })
79
+ .strict(),
80
+ z.object({ kind: z.literal("cancelled") }).strict(),
81
+ ]);
82
+ export const agentCandidateMaterializationReceiptSchema = z
83
+ .object({
84
+ schemaVersion: z.literal(1),
85
+ kind: z.literal("agent-candidate-materialization"),
86
+ digestAlgorithm: z.literal("rfc8785-sha256"),
87
+ bundleDigest: sha256DigestSchema,
88
+ profilePlan: agentCandidateProfilePlanEvidenceSchema,
89
+ executionPlan: agentCandidateExecutionPlanEvidenceSchema,
90
+ candidateWorkspace: agentCandidateWorkspaceSnapshotEvidenceSchema.optional(),
91
+ codeKind: z.enum(["disabled", "no-op", "git-patch"]),
92
+ materializedTree: gitObjectSchema.optional(),
93
+ harness: harnessTypeSchema,
94
+ harnessVersion: z.string().min(1),
95
+ container: z
96
+ .object({
97
+ source: z.enum(["pinned-container", "evaluator-task-container"]),
98
+ image: z.string().min(1),
99
+ indexDigest: sha256DigestSchema,
100
+ manifestDigest: sha256DigestSchema,
101
+ platform: ociPlatformSchema,
102
+ })
103
+ .strict(),
104
+ resolvedModel: agentCandidateResolvedModelSchema,
105
+ knowledgeManifestDigest: sha256DigestSchema.optional(),
106
+ entrypoint: entrypointReceiptSchema.optional(),
107
+ digest: sha256DigestSchema,
108
+ })
109
+ .strict()
110
+ .superRefine((receipt, ctx) => {
111
+ const plan = receipt.executionPlan.material;
112
+ if (receipt.codeKind === "disabled") {
113
+ if (receipt.materializedTree !== undefined ||
114
+ receipt.entrypoint !== undefined ||
115
+ receipt.candidateWorkspace !== undefined) {
116
+ ctx.addIssue({
117
+ code: "custom",
118
+ message: "disabled code must not claim a materialized tree, entrypoint, or workspace",
119
+ });
120
+ }
121
+ }
122
+ else if (receipt.materializedTree === undefined ||
123
+ receipt.entrypoint === undefined ||
124
+ receipt.candidateWorkspace === undefined) {
125
+ ctx.addIssue({
126
+ code: "custom",
127
+ message: "active code requires a materialized tree, entrypoint, and workspace receipt",
128
+ });
129
+ }
130
+ else {
131
+ const entrypoint = receipt.candidateWorkspace.material.files.find((file) => file.path === receipt.entrypoint?.path);
132
+ if (entrypoint === undefined ||
133
+ entrypoint.sha256 !== receipt.entrypoint.sha256 ||
134
+ entrypoint.byteLength !== receipt.entrypoint.byteLength) {
135
+ ctx.addIssue({
136
+ code: "custom",
137
+ path: ["entrypoint"],
138
+ message: "entrypoint receipt must identify exact candidate-workspace bytes",
139
+ });
140
+ }
141
+ }
142
+ const checks = [
143
+ [
144
+ receipt.bundleDigest === plan.bundleDigest,
145
+ ["executionPlan", "material", "bundleDigest"],
146
+ "execution plan must bind the receipt bundle",
147
+ ],
148
+ [
149
+ receipt.profilePlan.digest === plan.profile.planDigest,
150
+ ["executionPlan", "material", "profile", "planDigest"],
151
+ "execution plan must bind the exact profile plan",
152
+ ],
153
+ [
154
+ JSON.stringify(plan.profile.mountPaths) ===
155
+ JSON.stringify(receipt.profilePlan.material.files.map((file) => file.relPath)),
156
+ ["executionPlan", "material", "profile", "mountPaths"],
157
+ "execution plan must bind every profile mount path",
158
+ ],
159
+ [
160
+ receipt.codeKind === plan.codeKind,
161
+ ["executionPlan", "material", "codeKind"],
162
+ "execution plan code kind must match materialization",
163
+ ],
164
+ [
165
+ receipt.harness === plan.harness &&
166
+ receipt.harnessVersion === plan.harnessVersion,
167
+ ["executionPlan", "material", "harness"],
168
+ "execution plan must bind the exact harness and version",
169
+ ],
170
+ [
171
+ receipt.container.source === plan.container.source &&
172
+ receipt.container.image === plan.container.image &&
173
+ receipt.container.indexDigest === plan.container.indexDigest &&
174
+ receipt.container.manifestDigest === plan.container.manifestDigest &&
175
+ receipt.container.platform.os === plan.container.platform.os &&
176
+ receipt.container.platform.architecture ===
177
+ plan.container.platform.architecture &&
178
+ receipt.container.platform.variant === plan.container.platform.variant,
179
+ ["executionPlan", "material", "container"],
180
+ "execution plan must bind the selected container bytes",
181
+ ],
182
+ [
183
+ JSON.stringify(receipt.resolvedModel) ===
184
+ JSON.stringify(plan.model.resolved),
185
+ ["executionPlan", "material", "model", "resolved"],
186
+ "execution plan must bind the exact resolved model",
187
+ ],
188
+ [
189
+ receipt.knowledgeManifestDigest === plan.knowledgeManifestDigest,
190
+ ["executionPlan", "material", "knowledgeManifestDigest"],
191
+ "execution plan knowledge must match materialization",
192
+ ],
193
+ [
194
+ JSON.stringify(receipt.candidateWorkspace) ===
195
+ JSON.stringify(plan.candidateWorkspace),
196
+ ["executionPlan", "material", "candidateWorkspace"],
197
+ "execution plan must bind the exact uploaded candidate workspace",
198
+ ],
199
+ [
200
+ receipt.profilePlan.material.harness === receipt.harness,
201
+ ["profilePlan", "material", "harness"],
202
+ "profile plan harness must match materialization",
203
+ ],
204
+ ];
205
+ for (const [valid, path, message] of checks) {
206
+ if (!valid)
207
+ ctx.addIssue({ code: "custom", path, message });
208
+ }
209
+ if (!isCanonicalJsonValue(receipt)) {
210
+ ctx.addIssue({
211
+ code: "custom",
212
+ message: "materialization receipt must contain only RFC 8785 JSON values",
213
+ });
214
+ }
215
+ });
216
+ export const agentCandidateRunReceiptSchema = z
217
+ .object({
218
+ schemaVersion: z.literal(1),
219
+ kind: z.literal("agent-candidate-run"),
220
+ digestAlgorithm: z.literal("rfc8785-sha256"),
221
+ bundleDigest: sha256DigestSchema,
222
+ materializationReceiptDigest: sha256DigestSchema,
223
+ executionPlanDigest: sha256DigestSchema,
224
+ memory: agentCandidateMemoryReceiptSchema,
225
+ usage: agentCandidateSpendSchema,
226
+ modelUsage: agentCandidateModelUsageSchema,
227
+ trace: agentCandidateTraceEvidenceSchema,
228
+ termination: agentCandidateTerminationSchema,
229
+ digest: sha256DigestSchema,
230
+ })
231
+ .strict()
232
+ .superRefine((receipt, ctx) => {
233
+ const usageMatchesModel = receipt.usage.costUsd === receipt.modelUsage.usage.costUsd &&
234
+ receipt.usage.inputTokens === receipt.modelUsage.usage.inputTokens &&
235
+ receipt.usage.outputTokens === receipt.modelUsage.usage.outputTokens &&
236
+ receipt.usage.cachedInputTokens ===
237
+ receipt.modelUsage.usage.cachedInputTokens &&
238
+ receipt.usage.modelCalls === receipt.modelUsage.usage.modelCalls;
239
+ if (!usageMatchesModel) {
240
+ ctx.addIssue({
241
+ code: "custom",
242
+ path: ["modelUsage", "usage"],
243
+ message: "single-model usage must equal aggregate protected usage",
244
+ });
245
+ }
246
+ if (receipt.trace.modelCallCount !== receipt.modelUsage.usage.modelCalls) {
247
+ ctx.addIssue({
248
+ code: "custom",
249
+ path: ["trace", "modelCallCount"],
250
+ message: "trace model-call count must match protected single-model usage",
251
+ });
252
+ }
253
+ if (!isCanonicalJsonValue(receipt)) {
254
+ ctx.addIssue({
255
+ code: "custom",
256
+ message: "run receipt must contain only RFC 8785 JSON values",
257
+ });
258
+ }
259
+ });
260
+ const _materializationReceiptSchemaMatchesType = true;
261
+ const _runReceiptSchemaMatchesType = true;
262
+ void _materializationReceiptSchemaMatchesType;
263
+ void _runReceiptSchemaMatchesType;
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ import type { Sha256Digest } from "./agent-candidate.js";
3
+ export declare const sha256DigestSchema: z.ZodType<Sha256Digest>;
4
+ export declare const gitObjectSchema: z.ZodString;
5
+ export declare const environmentNameSchema: z.ZodString;
6
+ export declare const headerNameSchema: z.ZodString;
7
+ export declare function isWellFormedUnicode(value: string): boolean;
8
+ export declare function isSafeRelativePath(value: string, allowDot: boolean): boolean;
9
+ export declare function isSafeExecutable(value: string): boolean;
10
+ export declare function isObviouslyPrivateHostname(rawHostname: string): boolean;
11
+ export declare function isCanonicalJsonValue(value: unknown, ancestors?: Set<object>): boolean;
12
+ export declare function looksLikeCredential(value: string): boolean;
13
+ export declare const agentCandidateConfigValueSchema: z.ZodObject<{
14
+ kind: z.ZodLiteral<"public">;
15
+ value: z.ZodString;
16
+ }, z.core.$strict>;
17
+ export declare const environmentConfigSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
18
+ kind: z.ZodLiteral<"public">;
19
+ value: z.ZodString;
20
+ }, z.core.$strict>>;
21
+ export declare const headerConfigSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
22
+ kind: z.ZodLiteral<"public">;
23
+ value: z.ZodString;
24
+ }, z.core.$strict>>;
25
+ export declare const candidateMetadataSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
26
+ export declare const agentCandidateGitHubRepositorySchema: z.ZodObject<{
27
+ kind: z.ZodLiteral<"github">;
28
+ owner: z.ZodString;
29
+ repo: z.ZodString;
30
+ }, z.core.$strict>;
31
+ export declare function addDuplicateIssues(values: readonly string[] | undefined, path: (string | number)[], ctx: z.RefinementCtx): void;
32
+ export declare function sameGitObjectFormat(...values: string[]): boolean;
@@ -0,0 +1,251 @@
1
+ import { z } from "zod";
2
+ const sha256Pattern = /^sha256:[a-f0-9]{64}$/;
3
+ const gitObjectPattern = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
4
+ const environmentNamePattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
5
+ const headerNamePattern = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
6
+ const githubComponentPattern = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$/;
7
+ const secretNamePattern = /(?:^|[_-])(?:api[_-]?key|access[_-]?key|private[_-]?key|token|secret|password|credentials?|authorization|cookie|database[_-]?url|dsn|pat)(?:[_-]|$)/i;
8
+ const obviousSecretValuePattern = /(?:\b(?:sk|gh[pousr]|github_pat|AKIA)[-_A-Za-z0-9]{12,}\b|-----BEGIN [A-Z ]*PRIVATE KEY-----|\bBearer\s+\S+)/;
9
+ const controlCharacterPattern = /[\u0000-\u001f\u007f]/;
10
+ const reservedWorkspaceRoots = new Set([".git", ".sidecar"]);
11
+ const shellNames = new Set([
12
+ "sh",
13
+ "bash",
14
+ "zsh",
15
+ "fish",
16
+ "cmd",
17
+ "cmd.exe",
18
+ "powershell",
19
+ "pwsh",
20
+ ]);
21
+ const blockedHostnames = new Set([
22
+ "localhost",
23
+ "metadata.google.internal",
24
+ "metadata.google",
25
+ ]);
26
+ export const sha256DigestSchema = z
27
+ .string()
28
+ .regex(sha256Pattern);
29
+ export const gitObjectSchema = z.string().regex(gitObjectPattern);
30
+ export const environmentNameSchema = z.string().regex(environmentNamePattern);
31
+ export const headerNameSchema = z.string().regex(headerNamePattern);
32
+ export function isWellFormedUnicode(value) {
33
+ for (let index = 0; index < value.length; index++) {
34
+ const code = value.charCodeAt(index);
35
+ if (code >= 0xd800 && code <= 0xdbff) {
36
+ const next = value.charCodeAt(index + 1);
37
+ if (!(next >= 0xdc00 && next <= 0xdfff))
38
+ return false;
39
+ index++;
40
+ }
41
+ else if (code >= 0xdc00 && code <= 0xdfff) {
42
+ return false;
43
+ }
44
+ }
45
+ return true;
46
+ }
47
+ export function isSafeRelativePath(value, allowDot) {
48
+ if (value.length === 0 ||
49
+ controlCharacterPattern.test(value) ||
50
+ !isWellFormedUnicode(value) ||
51
+ value.startsWith("/") ||
52
+ value.startsWith("\\") ||
53
+ value.includes("\\") ||
54
+ /^[A-Za-z]:/.test(value)) {
55
+ return false;
56
+ }
57
+ if (value === ".")
58
+ return allowDot;
59
+ const parts = value.split("/");
60
+ return (parts.every((part) => part.length > 0 && part !== "." && part !== "..") &&
61
+ !parts.some((part) => reservedWorkspaceRoots.has(part)));
62
+ }
63
+ export function isSafeExecutable(value) {
64
+ if (value.length === 0 ||
65
+ controlCharacterPattern.test(value) ||
66
+ !isWellFormedUnicode(value) ||
67
+ /\s/.test(value) ||
68
+ value.includes("\\") ||
69
+ !/^[A-Za-z0-9._+/-]+$/.test(value)) {
70
+ return false;
71
+ }
72
+ const parts = value.split("/");
73
+ if (value.startsWith("/"))
74
+ parts.shift();
75
+ if (parts.length === 0 ||
76
+ parts.some((part) => part.length === 0 || part === "." || part === "..")) {
77
+ return false;
78
+ }
79
+ return !shellNames.has(parts.at(-1)?.toLowerCase() ?? "");
80
+ }
81
+ export function isObviouslyPrivateHostname(rawHostname) {
82
+ const literal = rawHostname.toLowerCase().replace(/^\[|\]$/g, "");
83
+ let hostname = literal;
84
+ try {
85
+ const parsed = new URL(`http://${literal.includes(":") ? `[${literal}]` : literal}/`);
86
+ hostname = parsed.hostname.replace(/^\[|\]$/g, "");
87
+ }
88
+ catch {
89
+ // Non-URL hostnames remain ordinary DNS names and are handled below.
90
+ }
91
+ if (blockedHostnames.has(hostname) || hostname.endsWith(".localhost")) {
92
+ return true;
93
+ }
94
+ if (hostname.includes(":")) {
95
+ const firstHextet = hostname.split(":", 1)[0] ?? "";
96
+ if (hostname === "::" ||
97
+ hostname === "::1" ||
98
+ hostname.startsWith("fe8") ||
99
+ hostname.startsWith("fe9") ||
100
+ hostname.startsWith("fea") ||
101
+ hostname.startsWith("feb") ||
102
+ firstHextet.startsWith("fc") ||
103
+ firstHextet.startsWith("fd") ||
104
+ hostname.startsWith("::ffff:")) {
105
+ return true;
106
+ }
107
+ }
108
+ const parts = hostname.split(".").map(Number);
109
+ if (parts.length !== 4 ||
110
+ parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
111
+ return false;
112
+ }
113
+ const [a, b] = parts;
114
+ if (a === undefined || b === undefined) {
115
+ return false;
116
+ }
117
+ if (a === 0 ||
118
+ a === 10 ||
119
+ a === 127 ||
120
+ (a === 100 && b >= 64 && b <= 127) ||
121
+ (a === 169 && b === 254) ||
122
+ (a === 172 && b >= 16 && b <= 31) ||
123
+ (a === 192 && b === 168) ||
124
+ (a === 198 && (b === 18 || b === 19)) ||
125
+ a >= 224) {
126
+ return true;
127
+ }
128
+ return false;
129
+ }
130
+ export function isCanonicalJsonValue(value, ancestors = new Set()) {
131
+ if (value === null || typeof value === "boolean")
132
+ return true;
133
+ if (typeof value === "string")
134
+ return isWellFormedUnicode(value);
135
+ if (typeof value === "number")
136
+ return Number.isFinite(value);
137
+ if (typeof value !== "object" || ancestors.has(value))
138
+ return false;
139
+ const prototype = Object.getPrototypeOf(value);
140
+ if (!Array.isArray(value) &&
141
+ prototype !== Object.prototype &&
142
+ prototype !== null) {
143
+ return false;
144
+ }
145
+ const nextAncestors = new Set(ancestors).add(value);
146
+ if (Array.isArray(value)) {
147
+ return value.every((entry) => isCanonicalJsonValue(entry, nextAncestors));
148
+ }
149
+ return Object.entries(value).every(([key, entry]) => isWellFormedUnicode(key) && isCanonicalJsonValue(entry, nextAncestors));
150
+ }
151
+ function hasCredentialBearingUrl(value) {
152
+ try {
153
+ const url = new URL(value);
154
+ if (url.username !== "" || url.password !== "")
155
+ return true;
156
+ return [...url.searchParams.keys()].some((key) => secretNamePattern.test(key));
157
+ }
158
+ catch {
159
+ return false;
160
+ }
161
+ }
162
+ export function looksLikeCredential(value) {
163
+ return obviousSecretValuePattern.test(value) || hasCredentialBearingUrl(value);
164
+ }
165
+ function isPublicConfigValue(value) {
166
+ return (!controlCharacterPattern.test(value) &&
167
+ isWellFormedUnicode(value) &&
168
+ !looksLikeCredential(value));
169
+ }
170
+ export const agentCandidateConfigValueSchema = z
171
+ .object({
172
+ kind: z.literal("public"),
173
+ value: z
174
+ .string()
175
+ .refine(isPublicConfigValue, "candidate config cannot carry credentials; the evaluator owns model authorization"),
176
+ })
177
+ .strict();
178
+ function configRecordSchema(keySchema) {
179
+ return z
180
+ .record(keySchema, agentCandidateConfigValueSchema)
181
+ .superRefine((config, ctx) => {
182
+ for (const [name, value] of Object.entries(config)) {
183
+ if (secretNamePattern.test(name)) {
184
+ ctx.addIssue({
185
+ code: "custom",
186
+ path: [name],
187
+ message: "candidate config cannot declare credential-bearing names",
188
+ });
189
+ }
190
+ }
191
+ });
192
+ }
193
+ export const environmentConfigSchema = configRecordSchema(environmentNameSchema);
194
+ export const headerConfigSchema = configRecordSchema(headerNameSchema);
195
+ export const candidateMetadataSchema = z
196
+ .record(z.string(), z.unknown())
197
+ .superRefine((metadata, ctx) => {
198
+ if (hasSensitiveMetadataKey(metadata)) {
199
+ ctx.addIssue({
200
+ code: "custom",
201
+ message: "metadata must not contain credential-bearing keys",
202
+ });
203
+ }
204
+ });
205
+ function hasSensitiveMetadataKey(value, ancestors = new Set()) {
206
+ if (value === null || typeof value !== "object" || ancestors.has(value)) {
207
+ return false;
208
+ }
209
+ const nextAncestors = new Set(ancestors).add(value);
210
+ if (Array.isArray(value)) {
211
+ return value.some((entry) => hasSensitiveMetadataKey(entry, nextAncestors));
212
+ }
213
+ for (const [key, entry] of Object.entries(value)) {
214
+ if (secretNamePattern.test(key))
215
+ return true;
216
+ if (hasSensitiveMetadataKey(entry, nextAncestors))
217
+ return true;
218
+ }
219
+ return false;
220
+ }
221
+ export const agentCandidateGitHubRepositorySchema = z
222
+ .object({
223
+ kind: z.literal("github"),
224
+ owner: z
225
+ .string()
226
+ .regex(githubComponentPattern)
227
+ .refine((value) => value !== "." && value !== ".."),
228
+ repo: z
229
+ .string()
230
+ .regex(githubComponentPattern)
231
+ .refine((value) => value !== "." && value !== ".."),
232
+ })
233
+ .strict();
234
+ export function addDuplicateIssues(values, path, ctx) {
235
+ if (!values)
236
+ return;
237
+ const seen = new Set();
238
+ for (const [index, value] of values.entries()) {
239
+ if (seen.has(value)) {
240
+ ctx.addIssue({
241
+ code: "custom",
242
+ path: [...path, index],
243
+ message: `duplicate value '${value}'`,
244
+ });
245
+ }
246
+ seen.add(value);
247
+ }
248
+ }
249
+ export function sameGitObjectFormat(...values) {
250
+ return new Set(values.map((value) => value.length)).size === 1;
251
+ }