@tangle-network/agent-interface 5.8.1 → 5.10.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.
package/README.md CHANGED
@@ -424,3 +424,5 @@ must validate source ownership and active generation bindings for its own runtim
424
424
  before admitting a knowledge tool. A profile copied to another owner does not
425
425
  confer access. Hosts unable to resolve the saved pins must fail closed rather than
426
426
  inline JSON into instructions, substitute a newer generation or silently omit them.
427
+
428
+ Profile collection admission uses the shared [contract limits](src/contract-limits.ts) before parsing elements or encoding record keys. Training receipts and knowledge references retain their own typed limits in [the profile schema](src/profile-schema.ts). Opaque metadata is admitted and detached by value as bounded portable data; accessors are refused and cross-key object identity is not preserved. Snapshot validation precedes the final detached copy, preserving explicit undefined fields, canonical hashes and nested isolation. The generic serialized-JSON byte bound is not a whole-profile transport limit.
@@ -1,4 +1,10 @@
1
1
  import type { AgentCandidateJsonValue } from "./agent-candidate.js";
2
+ type ProfilePath = readonly (string | number)[];
3
+ /** Why a value cannot be AgentProfile JSON, with the path of the offending entry. */
4
+ export declare class AgentProfileValueError extends Error {
5
+ readonly path: ProfilePath;
6
+ constructor(path: ProfilePath, reason: string);
7
+ }
2
8
  /**
3
9
  * Normalize profile-shaped data into the canonical JSON domain used for public
4
10
  * profile identity. Undefined object entries are omitted; unsupported values,
@@ -8,5 +14,13 @@ import type { AgentCandidateJsonValue } from "./agent-candidate.js";
8
14
  * profile-level operations that own validation, identity, or diff semantics.
9
15
  */
10
16
  export declare function canonicalAgentProfileValue(value: unknown): AgentCandidateJsonValue | undefined;
17
+ /**
18
+ * Throw the path-specific reason a value refused by the bounded admission walker is
19
+ * not profile JSON. Reads no accessor and throws at the contract node and depth
20
+ * bounds, so hostile input stays cheap; returns only when the refusal was a bound
21
+ * this walker does not model, such as string or entry length.
22
+ */
23
+ export declare function assertAgentProfileValue(value: unknown, path: ProfilePath): void;
11
24
  /** Canonical serialization after applying AgentProfile normalization rules. */
12
25
  export declare function canonicalAgentProfileJson(value: unknown): string | undefined;
26
+ export {};
@@ -1,4 +1,14 @@
1
1
  import { canonicalCandidateJson, isWellFormedUnicode, } from "./agent-candidate-schema-common.js";
2
+ import { CONTRACT_MAX_JSON_DEPTH, CONTRACT_MAX_JSON_NODES } from "./contract-limits.js";
3
+ /** Why a value cannot be AgentProfile JSON, with the path of the offending entry. */
4
+ export class AgentProfileValueError extends Error {
5
+ path;
6
+ constructor(path, reason) {
7
+ super(`AgentProfile ${renderPath(path)} ${reason}`);
8
+ this.path = path;
9
+ this.name = "AgentProfileValueError";
10
+ }
11
+ }
2
12
  /**
3
13
  * Normalize profile-shaped data into the canonical JSON domain used for public
4
14
  * profile identity. Undefined object entries are omitted; unsupported values,
@@ -10,66 +20,57 @@ import { canonicalCandidateJson, isWellFormedUnicode, } from "./agent-candidate-
10
20
  export function canonicalAgentProfileValue(value) {
11
21
  return normalizeAgentProfileValue(value, [], new Set());
12
22
  }
23
+ /**
24
+ * Throw the path-specific reason a value refused by the bounded admission walker is
25
+ * not profile JSON. Reads no accessor and throws at the contract node and depth
26
+ * bounds, so hostile input stays cheap; returns only when the refusal was a bound
27
+ * this walker does not model, such as string or entry length.
28
+ */
29
+ export function assertAgentProfileValue(value, path) {
30
+ normalizeAgentProfileValue(value, path, new Set(), { nodes: CONTRACT_MAX_JSON_NODES, base: path.length });
31
+ }
13
32
  /** Canonical serialization after applying AgentProfile normalization rules. */
14
33
  export function canonicalAgentProfileJson(value) {
15
34
  const normalized = canonicalAgentProfileValue(value);
16
- return normalized === undefined
17
- ? undefined
18
- : canonicalCandidateJson(normalized);
35
+ return normalized === undefined ? undefined : canonicalCandidateJson(normalized);
19
36
  }
20
- function normalizeAgentProfileValue(value, path, ancestors) {
21
- if (value === undefined)
22
- return undefined;
23
- if (value === null ||
24
- typeof value === "boolean" ||
25
- typeof value === "string") {
26
- return value;
27
- }
28
- if (typeof value === "number") {
29
- if (!Number.isFinite(value)) {
30
- throw new Error(`AgentProfile ${renderPath(path)} must be finite`);
31
- }
37
+ function normalizeAgentProfileValue(value, path, ancestors, bounds) {
38
+ const refuse = (reason, at = path) => { throw new AgentProfileValueError(at, reason); };
39
+ if (bounds && --bounds.nodes < 0)
40
+ refuse("exceeds the contract node bound");
41
+ if (value === undefined || value === null || typeof value === "boolean" || typeof value === "string")
32
42
  return value;
33
- }
34
- if (typeof value !== "object") {
35
- throw new Error(`AgentProfile ${renderPath(path)} is not JSON serializable`);
36
- }
37
- if (ancestors.has(value)) {
38
- throw new Error(`AgentProfile ${renderPath(path)} must be acyclic`);
39
- }
43
+ if (typeof value === "number")
44
+ return Number.isFinite(value) ? value : refuse("must be finite");
45
+ if (typeof value !== "object")
46
+ return refuse("is not JSON serializable");
47
+ if (ancestors.has(value))
48
+ refuse("must be acyclic");
40
49
  const prototype = Object.getPrototypeOf(value);
41
- if (!Array.isArray(value) &&
42
- prototype !== Object.prototype &&
43
- prototype !== null) {
44
- throw new Error(`AgentProfile ${renderPath(path)} must be a plain JSON object`);
45
- }
50
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null)
51
+ refuse("must be a plain JSON object");
52
+ if (bounds && path.length - bounds.base >= CONTRACT_MAX_JSON_DEPTH)
53
+ refuse("exceeds the contract depth");
46
54
  const nextAncestors = new Set(ancestors).add(value);
47
- if (Array.isArray(value)) {
48
- return Array.from({ length: value.length }, (_, index) => {
49
- if (!Object.prototype.hasOwnProperty.call(value, index)) {
50
- throw new Error(`AgentProfile ${renderPath([...path, index])} cannot be a sparse array hole`);
51
- }
52
- const entry = value[index];
53
- const normalized = normalizeAgentProfileValue(entry, [...path, index], nextAncestors);
54
- if (normalized === undefined) {
55
- throw new Error(`AgentProfile ${renderPath([...path, index])} cannot be undefined`);
56
- }
57
- return normalized;
58
- });
59
- }
55
+ // Entries are read through own data descriptors so refusing a value never runs an accessor.
56
+ const entry = (key) => {
57
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
58
+ if (!descriptor)
59
+ return refuse("cannot be a sparse array hole", [...path, key]);
60
+ if (!("value" in descriptor))
61
+ return refuse("must be a data property", [...path, key]);
62
+ const normalized = normalizeAgentProfileValue(descriptor.value, [...path, key], nextAncestors, bounds);
63
+ return normalized === undefined && typeof key === "number" ? refuse("cannot be undefined", [...path, key]) : normalized;
64
+ };
65
+ if (Array.isArray(value))
66
+ return Array.from({ length: value.length }, (_, index) => entry(index));
60
67
  const material = Object.create(null);
61
- for (const [key, entry] of Object.entries(value)) {
62
- if (!isWellFormedUnicode(key)) {
63
- throw new Error(`AgentProfile ${renderPath(path)} has a record key that is not valid Unicode`);
64
- }
65
- const normalized = normalizeAgentProfileValue(entry, [...path, key], nextAncestors);
68
+ for (const key of Object.keys(value)) {
69
+ if (!isWellFormedUnicode(key))
70
+ refuse("has a record key that is not valid Unicode");
71
+ const normalized = entry(key);
66
72
  if (normalized !== undefined) {
67
- Object.defineProperty(material, key, {
68
- value: normalized,
69
- enumerable: true,
70
- configurable: true,
71
- writable: true,
72
- });
73
+ Object.defineProperty(material, key, { value: normalized, enumerable: true, configurable: true, writable: true });
73
74
  }
74
75
  }
75
76
  return material;
@@ -9,7 +9,7 @@ import { agentProfileSchema } from "./profile-schema.js";
9
9
  * existing canonical profile JSON domain fail instead of becoming mutable state.
10
10
  */
11
11
  export function snapshotAgentProfile(value) {
12
- const parsed = agentProfileSchema.parse(structuredClone(value));
12
+ const parsed = structuredClone(agentProfileSchema.parse(value));
13
13
  canonicalAgentProfileValue(parsed);
14
14
  return deepFreeze(parsed);
15
15
  }
@@ -12,23 +12,16 @@ export declare const CONTRACT_MAX_JSON_NODES = 8192;
12
12
  export declare const boundedStringSchema: z.ZodString;
13
13
  export declare const confidentialAttestationQuoteSchema: z.ZodString;
14
14
  export declare const boundedIdentifierSchema: z.ZodString;
15
- /**
16
- * Validate JSON without asking Zod to recursively copy an attacker-sized
17
- * value. This is intentionally iterative so depth and collection limits are
18
- * checked before canonical serialization.
19
- */
15
+ /** Admit ordinary JSON before recursive parsing, copying or hashing. */
20
16
  export declare function isBoundedJsonValue(value: unknown): boolean;
21
- /**
22
- * Validate provider event and terminal content. Unlike ordinary metadata,
23
- * content may contain a single large transcript or tool result. It remains
24
- * finite, plain JSON with the normal structural limits, and its complete JSON
25
- * representation is capped exactly by UTF-8 bytes.
26
- */
17
+ /** Event content permits large strings within the exact serialized UTF-8 budget. */
27
18
  export declare function isBoundedEventContentJson(value: unknown, { omitUndefinedObjectFields }?: {
28
19
  omitUndefinedObjectFields?: boolean;
29
20
  }): boolean;
30
- /** Validate digest input while matching JSON's omission of undefined object fields. */
31
- export declare function isBoundedJsonMaterial(value: unknown): boolean;
21
+ /** Digest material omits undefined object fields; portable profile values require data properties. */
22
+ export declare function isBoundedJsonMaterial(value: unknown, { dataPropertiesOnly }?: {
23
+ dataPropertiesOnly?: boolean;
24
+ }): boolean;
32
25
  export declare const boundedJsonSchema: z.ZodCustom<unknown, unknown>;
33
26
  export declare const boundedJsonRecordSchema: z.ZodCustom<Record<string, unknown>, Record<string, unknown>>;
34
27
  /** One provider event payload or terminal response, bounded as serialized UTF-8 JSON. */
@@ -17,72 +17,9 @@ export const boundedIdentifierSchema = boundedStringSchema
17
17
  .min(1)
18
18
  .max(CONTRACT_MAX_IDENTIFIER_LENGTH)
19
19
  .refine((value) => value.trim() === value, "identifier cannot have outer whitespace");
20
- /**
21
- * Validate JSON without asking Zod to recursively copy an attacker-sized
22
- * value. This is intentionally iterative so depth and collection limits are
23
- * checked before canonical serialization.
24
- */
20
+ /** Admit ordinary JSON before recursive parsing, copying or hashing. */
25
21
  export function isBoundedJsonValue(value) {
26
- const pending = [{ value, depth: 0 }];
27
- const ancestors = new Set();
28
- let nodes = 0;
29
- while (pending.length > 0) {
30
- const item = pending.pop();
31
- if (!item)
32
- continue;
33
- nodes += 1;
34
- if (nodes > CONTRACT_MAX_JSON_NODES)
35
- return false;
36
- const current = item.value;
37
- if (item.leave) {
38
- ancestors.delete(current);
39
- continue;
40
- }
41
- if (current === undefined)
42
- return false;
43
- if (current === null || typeof current === "boolean")
44
- continue;
45
- if (typeof current === "string") {
46
- if (current.length > CONTRACT_MAX_STRING_LENGTH)
47
- return false;
48
- continue;
49
- }
50
- if (typeof current === "number") {
51
- if (!Number.isFinite(current))
52
- return false;
53
- continue;
54
- }
55
- if (typeof current !== "object" || item.depth >= CONTRACT_MAX_JSON_DEPTH) {
56
- return false;
57
- }
58
- if (ancestors.has(current))
59
- return false;
60
- ancestors.add(current);
61
- pending.push({ value: current, depth: item.depth, leave: true });
62
- if (Array.isArray(current)) {
63
- if (current.length > CONTRACT_MAX_ARRAY_LENGTH)
64
- return false;
65
- for (const entry of current) {
66
- pending.push({ value: entry, depth: item.depth + 1 });
67
- }
68
- continue;
69
- }
70
- const prototype = Object.getPrototypeOf(current);
71
- if (prototype !== Object.prototype && prototype !== null)
72
- return false;
73
- const keys = Object.keys(current);
74
- if (keys.length > CONTRACT_MAX_MAP_ENTRIES)
75
- return false;
76
- for (const key of keys) {
77
- if (key.length > CONTRACT_MAX_IDENTIFIER_LENGTH)
78
- return false;
79
- pending.push({
80
- value: current[key],
81
- depth: item.depth + 1,
82
- });
83
- }
84
- }
85
- return true;
22
+ return boundedJson(value);
86
23
  }
87
24
  /** Exact serialized JSON UTF-8 byte count for one string scalar. */
88
25
  function serializedJsonStringBytes(value) {
@@ -122,187 +59,104 @@ function serializedJsonStringBytes(value) {
122
59
  }
123
60
  return bytes;
124
61
  }
125
- /**
126
- * Validate provider event and terminal content. Unlike ordinary metadata,
127
- * content may contain a single large transcript or tool result. It remains
128
- * finite, plain JSON with the normal structural limits, and its complete JSON
129
- * representation is capped exactly by UTF-8 bytes.
130
- */
62
+ /** Event content permits large strings within the exact serialized UTF-8 budget. */
131
63
  export function isBoundedEventContentJson(value, { omitUndefinedObjectFields = false } = {}) {
64
+ return boundedJson(value, { content: true, omitUndefinedObjectFields });
65
+ }
66
+ /** Digest material omits undefined object fields; portable profile values require data properties. */
67
+ export function isBoundedJsonMaterial(value, { dataPropertiesOnly = false } = {}) {
68
+ return boundedJson(value, { material: true, omitUndefinedObjectFields: true, dataPropertiesOnly });
69
+ }
70
+ /** One iterative walker owns structural admission; typed domains keep their own schema limits. */
71
+ function boundedJson(value, options = {}) {
132
72
  const pending = [
133
73
  { value, depth: 0 },
134
74
  ];
135
75
  const ancestors = new Set();
136
76
  let nodes = 0;
137
77
  let bytes = 0;
138
- const addBytes = (additional) => {
139
- bytes += additional;
140
- return bytes <= CONTRACT_MAX_JSON_BYTES;
141
- };
142
- while (pending.length > 0) {
78
+ const addBytes = (count) => !options.content || (bytes += count) <= CONTRACT_MAX_JSON_BYTES;
79
+ while (pending.length) {
143
80
  const item = pending.pop();
144
- if (!item)
145
- continue;
146
81
  const current = item.value;
147
- if (current === undefined && item.omitUndefined === true)
148
- continue;
149
- nodes += 1;
150
- if (nodes > CONTRACT_MAX_JSON_NODES)
82
+ if ((!item.leave || !options.material) && ++nodes > CONTRACT_MAX_JSON_NODES)
151
83
  return false;
84
+ if (current === undefined && item.omitUndefined)
85
+ continue;
152
86
  if (item.leave) {
153
87
  ancestors.delete(current);
154
88
  continue;
155
89
  }
156
- if (current === null) {
157
- if (!addBytes(4))
158
- return false;
159
- continue;
160
- }
161
- if (typeof current === "boolean") {
162
- if (!addBytes(current ? 4 : 5))
90
+ if (current === null || typeof current === "boolean") {
91
+ if (!addBytes(current === null ? 4 : current ? 4 : 5))
163
92
  return false;
164
93
  continue;
165
94
  }
166
95
  if (typeof current === "string") {
167
- if (!addBytes(serializedJsonStringBytes(current)))
96
+ if (options.content ? !addBytes(serializedJsonStringBytes(current)) : current.length > CONTRACT_MAX_STRING_LENGTH)
168
97
  return false;
169
98
  continue;
170
99
  }
171
100
  if (typeof current === "number") {
172
- if (!Number.isFinite(current))
173
- return false;
174
- if (!addBytes(JSON.stringify(current).length))
101
+ if (!Number.isFinite(current) || !addBytes(JSON.stringify(current).length))
175
102
  return false;
176
103
  continue;
177
104
  }
178
- if (typeof current !== "object" || item.depth >= CONTRACT_MAX_JSON_DEPTH) {
179
- return false;
180
- }
181
- if (ancestors.has(current))
105
+ if (typeof current !== "object" || item.depth >= CONTRACT_MAX_JSON_DEPTH || ancestors.has(current))
182
106
  return false;
183
107
  ancestors.add(current);
184
108
  pending.push({ value: current, depth: item.depth, leave: true });
185
- if (Array.isArray(current)) {
186
- if (current.length > CONTRACT_MAX_ARRAY_LENGTH)
187
- return false;
188
- if (!addBytes(2 + Math.max(current.length - 1, 0)))
189
- return false;
190
- const prototype = Object.getPrototypeOf(current);
191
- if (prototype !== Array.prototype && prototype !== null)
192
- return false;
193
- const keys = Reflect.ownKeys(current);
194
- const entryKeys = keys.filter((key) => key !== "length");
195
- if (entryKeys.length !== current.length)
196
- return false;
197
- if (entryKeys.some((key) => {
198
- if (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key))
199
- return true;
200
- const index = Number(key);
201
- return !Number.isSafeInteger(index) || index < 0 || index >= current.length || index >= 4_294_967_295;
202
- })) {
109
+ const array = Array.isArray(current);
110
+ if (array && current.length > CONTRACT_MAX_ARRAY_LENGTH)
111
+ return false;
112
+ const prototype = Object.getPrototypeOf(current);
113
+ if ((!array && prototype !== Object.prototype && prototype !== null) ||
114
+ (array && options.content && prototype !== Array.prototype && prototype !== null))
115
+ return false;
116
+ if (options.content || options.dataPropertiesOnly) {
117
+ const keys = Reflect.ownKeys(current).filter((key) => !array || key !== "length");
118
+ if (array ? keys.length !== current.length : keys.length > CONTRACT_MAX_MAP_ENTRIES)
203
119
  return false;
204
- }
205
- for (const key of entryKeys) {
120
+ let entries = 0;
121
+ for (const key of keys) {
122
+ if (typeof key !== "string" || (!array && key.length > CONTRACT_MAX_IDENTIFIER_LENGTH))
123
+ return false;
124
+ if (array && (!/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= current.length || Number(key) >= 4_294_967_295))
125
+ return false;
206
126
  const descriptor = Object.getOwnPropertyDescriptor(current, key);
207
- if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor))
127
+ if (!descriptor?.enumerable || !("value" in descriptor))
128
+ return false;
129
+ if (!array && descriptor.value === undefined && options.omitUndefinedObjectFields) {
130
+ if (++nodes > CONTRACT_MAX_JSON_NODES)
131
+ return false;
132
+ continue;
133
+ }
134
+ if (++entries > (array ? CONTRACT_MAX_ARRAY_LENGTH : CONTRACT_MAX_MAP_ENTRIES))
135
+ return false;
136
+ if (!array && !addBytes(serializedJsonStringBytes(key) + 1))
208
137
  return false;
209
138
  pending.push({ value: descriptor.value, depth: item.depth + 1 });
210
139
  }
211
- continue;
212
- }
213
- const prototype = Object.getPrototypeOf(current);
214
- if (prototype !== Object.prototype && prototype !== null)
215
- return false;
216
- const entries = [];
217
- for (const key of Reflect.ownKeys(current)) {
218
- if (typeof key !== "string" || key.length > CONTRACT_MAX_IDENTIFIER_LENGTH)
219
- return false;
220
- const descriptor = Object.getOwnPropertyDescriptor(current, key);
221
- if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor))
222
- return false;
223
- if (descriptor.value === undefined && omitUndefinedObjectFields)
224
- continue;
225
- entries.push([key, descriptor]);
226
- if (entries.length > CONTRACT_MAX_MAP_ENTRIES)
140
+ if (!addBytes(2 + Math.max(entries - 1, 0)))
227
141
  return false;
228
142
  }
229
- if (!addBytes(2 + Math.max(entries.length - 1, 0)))
230
- return false;
231
- for (const [key, descriptor] of entries) {
232
- if (!addBytes(serializedJsonStringBytes(key) + 1))
233
- return false;
234
- pending.push({
235
- value: descriptor.value,
236
- depth: item.depth + 1,
237
- omitUndefined: omitUndefinedObjectFields,
238
- });
239
- }
240
- }
241
- return true;
242
- }
243
- /** Validate digest input while matching JSON's omission of undefined object fields. */
244
- export function isBoundedJsonMaterial(value) {
245
- const pending = [{ value, depth: 0 }];
246
- const ancestors = new Set();
247
- let nodes = 0;
248
- while (pending.length > 0) {
249
- const item = pending.pop();
250
- if (!item)
251
- continue;
252
- if (item.leave) {
253
- ancestors.delete(item.value);
254
- continue;
255
- }
256
- if (item.value === undefined) {
257
- if (item.omitUndefined === true)
258
- continue;
259
- return false;
260
- }
261
- nodes += 1;
262
- if (nodes > CONTRACT_MAX_JSON_NODES)
263
- return false;
264
- const current = item.value;
265
- if (current === null || typeof current === "boolean")
266
- continue;
267
- if (typeof current === "string") {
268
- if (current.length > CONTRACT_MAX_STRING_LENGTH)
269
- return false;
270
- continue;
271
- }
272
- if (typeof current === "number") {
273
- if (!Number.isFinite(current))
274
- return false;
275
- continue;
276
- }
277
- if (typeof current !== "object" || item.depth >= CONTRACT_MAX_JSON_DEPTH) {
278
- return false;
279
- }
280
- if (ancestors.has(current))
281
- return false;
282
- ancestors.add(current);
283
- pending.push({ value: current, depth: item.depth, leave: true });
284
- if (Array.isArray(current)) {
285
- if (current.length > CONTRACT_MAX_ARRAY_LENGTH)
286
- return false;
287
- for (const entry of current) {
143
+ else if (array) {
144
+ for (const entry of current)
288
145
  pending.push({ value: entry, depth: item.depth + 1 });
289
- }
290
- continue;
291
146
  }
292
- const prototype = Object.getPrototypeOf(current);
293
- if (prototype !== Object.prototype && prototype !== null)
294
- return false;
295
- const keys = Object.keys(current);
296
- if (keys.length > CONTRACT_MAX_MAP_ENTRIES)
297
- return false;
298
- for (const key of keys) {
299
- if (key.length > CONTRACT_MAX_IDENTIFIER_LENGTH)
147
+ else {
148
+ const keys = Object.keys(current);
149
+ if (keys.length > CONTRACT_MAX_MAP_ENTRIES)
300
150
  return false;
301
- pending.push({
302
- value: current[key],
303
- depth: item.depth + 1,
304
- omitUndefined: true,
305
- });
151
+ for (const key of keys) {
152
+ if (key.length > CONTRACT_MAX_IDENTIFIER_LENGTH)
153
+ return false;
154
+ pending.push({
155
+ value: current[key],
156
+ depth: item.depth + 1,
157
+ omitUndefined: options.omitUndefinedObjectFields,
158
+ });
159
+ }
306
160
  }
307
161
  }
308
162
  return true;
@@ -130,7 +130,7 @@ export declare function reasoningEffortsFor(harness: HarnessType, model?: ModelR
130
130
  * that verifies the receipt read this function, so a CLI that renames a rung moves both at once
131
131
  * instead of turning into a refused run.
132
132
  */
133
- export declare function nativeReasoningControl(harness: HarnessType, effort: ReasoningEffort | null): string | null;
133
+ export declare function nativeReasoningControl(harness: HarnessType, effort: ReasoningEffort | null, model?: string): string | null;
134
134
  /** Whether the harness's runner honors a per-turn MODEL override (vs. picking the model itself). */
135
135
  export declare function harnessHonorsModel(harness: HarnessType): boolean;
136
136
  /** Whether the harness's runner honors a reasoning-EFFORT override (vs. dropping it). */
@@ -253,8 +253,8 @@ export function snapHarnessToModel(harness, modelId) {
253
253
  * - claude-code: `--effort` accepts `low|medium|high|xhigh|max`; canonical `ultracode` maps to
254
254
  * native `max`. It cannot express `none` or `minimal`, and an unsupported value is warned about
255
255
  * and silently replaced with the default rather than rejected — so the set must not overstate.
256
- * - pi: `--thinking` accepts `off|minimal|low|medium|high|xhigh`; canonical `none` maps to
257
- * `off` and `ultracode` clamps to `xhigh`, its top rung.
256
+ * - pi: `--thinking` accepts `off|minimal|low|medium|high|xhigh|max` (pi 1.4.0
257
+ * `cli/args.js`); canonical `none` maps to `off` and `ultracode` to `max`.
258
258
  * - prime: the prime fork of the pi line accepts the same `--thinking` set
259
259
  * (`off|minimal|low|medium|high|xhigh|max`); canonical `none` maps to `off` and `ultracode` to
260
260
  * `max`.
@@ -316,19 +316,26 @@ export function reasoningEffortsFor(harness, model) {
316
316
  * `low`, and `ultracode` becomes its ceiling `max`.
317
317
  * - codex — `-c model_reasoning_effort="<value>"`; it takes the canonical rungs directly and
318
318
  * names its ceiling `ultra`.
319
- * - pi — `--thinking <value>`; `none` becomes `off` and `ultracode` clamps to `xhigh`, the
320
- * highest rung the pi line accepts.
321
- * - prime — `--thinking <value>`; the fork carries `max` above `xhigh`, so `ultracode` reaches
322
- * `max`. This is the one rung where prime and pi differ.
319
+ * - pi, prime and openclaw — `--thinking <value>`; `none` becomes `off` and `ultracode` reaches
320
+ * `max` (pi 1.4.0 `cli/args.js`, the prime fork's `cli/args.ts`, openclaw `agent --help`).
323
321
  * - kimi-code — the control is the FLAG itself, `--thinking` or `--no-thinking`, because kimi's
324
322
  * thinking switch is binary. `medium` is its default and passes no flag at all.
325
- * - opencode — the router-backed variant name is the canonical rung unchanged.
323
+ * - opencode — `--variant <value>`, forwarded verbatim as the provider's reasoning effort; `none`
324
+ * omits the flag, as the sandbox adapter does. No
325
+ * provider spells `ultracode`, so it becomes the model's ceiling: `max` for GLM (Z.ai answers
326
+ * 400 code 1210 for anything outside `none…max`) and `xhigh` for every other model (GPT-6
327
+ * answers `max` with 400). Pass `model` so this rung resolves; without it `xhigh` is sent.
326
328
  *
327
329
  * A harness with no entry applies NO native control: either it derives thinking from the model
328
330
  * (gemini's `--thinking-budget`) or it plumbs no thinking flag at all (see
329
331
  * {@link harnessHonorsEffort}). Both answer `null`, which is what their receipts carry — so an
330
332
  * unknown harness is never asserted to have applied a control it cannot apply.
331
333
  */
334
+ const thinkingRung = (effort) => {
335
+ if (effort === "none")
336
+ return "off";
337
+ return effort === "ultracode" ? "max" : effort;
338
+ };
332
339
  const harnessNativeReasoningControl = {
333
340
  "claude-code": (effort) => {
334
341
  if (effort === "none" || effort === "minimal")
@@ -336,16 +343,9 @@ const harnessNativeReasoningControl = {
336
343
  return effort === "ultracode" ? "max" : effort;
337
344
  },
338
345
  codex: (effort) => (effort === "ultracode" ? "ultra" : effort),
339
- pi: (effort) => {
340
- if (effort === "none")
341
- return "off";
342
- return effort === "ultracode" ? "xhigh" : effort;
343
- },
344
- prime: (effort) => {
345
- if (effort === "none")
346
- return "off";
347
- return effort === "ultracode" ? "max" : effort;
348
- },
346
+ pi: thinkingRung,
347
+ prime: thinkingRung,
348
+ openclaw: thinkingRung,
349
349
  "kimi-code": (effort) => {
350
350
  if (effort === "medium")
351
351
  return null;
@@ -353,7 +353,13 @@ const harnessNativeReasoningControl = {
353
353
  ? "--no-thinking"
354
354
  : "--thinking";
355
355
  },
356
- opencode: (effort) => effort,
356
+ opencode: (effort, model) => {
357
+ if (effort === "none")
358
+ return null;
359
+ if (effort !== "ultracode")
360
+ return effort;
361
+ return /(?:^|\/)glm-/iu.test(model ?? "") ? "max" : "xhigh";
362
+ },
357
363
  };
358
364
  /**
359
365
  * The native control token a harness applies for one canonical effort, or `null` when it applies
@@ -363,10 +369,10 @@ const harnessNativeReasoningControl = {
363
369
  * that verifies the receipt read this function, so a CLI that renames a rung moves both at once
364
370
  * instead of turning into a refused run.
365
371
  */
366
- export function nativeReasoningControl(harness, effort) {
372
+ export function nativeReasoningControl(harness, effort, model) {
367
373
  if (effort === null)
368
374
  return null;
369
- return harnessNativeReasoningControl[harness]?.(effort) ?? null;
375
+ return harnessNativeReasoningControl[harness]?.(effort, model) ?? null;
370
376
  }
371
377
  // ── Per-turn selector support (does the harness honor the chat pickers?) ──────
372
378
  /**
@@ -6,4 +6,7 @@ import { z } from "zod";
6
6
  * sees it, then restore the exact UTF-16 key with defineProperty so all string
7
7
  * keys allowed by `Record<string, ...>` remain data rather than object syntax.
8
8
  */
9
- export declare function ownPropertyRecordSchema<ValueSchema extends z.ZodType>(valueSchema: ValueSchema): z.ZodType<Record<string, z.output<ValueSchema>>>;
9
+ export declare function ownPropertyRecordSchema<ValueSchema extends z.ZodType>(valueSchema: ValueSchema, { maxEntries, prepareValue }?: {
10
+ maxEntries?: number;
11
+ prepareValue?: (key: string, value: unknown) => unknown;
12
+ }): z.ZodType<Record<string, z.output<ValueSchema>>>;
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
+ import { CONTRACT_MAX_IDENTIFIER_LENGTH, CONTRACT_MAX_MAP_ENTRIES } from "./contract-limits.js";
2
3
  import { isWellFormedUnicode } from "./agent-candidate-schema-common.js";
4
+ import { AgentProfileValueError } from "./agent-profile-canonical.js";
3
5
  /**
4
6
  * Zod records currently assign parsed values onto an ordinary object. The
5
7
  * special own key `__proto__` therefore invokes the legacy prototype setter
@@ -7,8 +9,48 @@ import { isWellFormedUnicode } from "./agent-candidate-schema-common.js";
7
9
  * sees it, then restore the exact UTF-16 key with defineProperty so all string
8
10
  * keys allowed by `Record<string, ...>` remain data rather than object syntax.
9
11
  */
10
- export function ownPropertyRecordSchema(valueSchema) {
11
- return z.preprocess(encodeOwnRecordKeys, z.record(z
12
+ export function ownPropertyRecordSchema(valueSchema, { maxEntries = CONTRACT_MAX_MAP_ENTRIES, prepareValue } = {}) {
13
+ return z.preprocess((value, context) => {
14
+ if (value === null || typeof value !== "object" || Array.isArray(value))
15
+ return value;
16
+ const prototype = Object.getPrototypeOf(value);
17
+ if (prototype !== Object.prototype && prototype !== null)
18
+ return [];
19
+ let entries = 0;
20
+ for (const key in value) {
21
+ if (!Object.hasOwn(value, key))
22
+ continue;
23
+ if (++entries > maxEntries || key.length > CONTRACT_MAX_IDENTIFIER_LENGTH) {
24
+ context.addIssue({ code: "custom", message: "record exceeds the contract bounds" });
25
+ return z.NEVER;
26
+ }
27
+ }
28
+ const encoded = Object.create(null);
29
+ for (const key of Object.keys(value)) {
30
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
31
+ if (!descriptor || !("value" in descriptor)) {
32
+ context.addIssue({ code: "custom", message: "record entries must be data properties" });
33
+ return z.NEVER;
34
+ }
35
+ let entry;
36
+ try {
37
+ entry = prepareValue ? prepareValue(key, descriptor.value) : descriptor.value;
38
+ }
39
+ catch (error) {
40
+ // Only a profile-value refusal is a validation issue; any other throw is a defect.
41
+ if (!(error instanceof AgentProfileValueError))
42
+ throw error;
43
+ context.addIssue({ code: "custom", path: [...error.path], message: error.message });
44
+ return z.NEVER;
45
+ }
46
+ if (entry === z.NEVER) {
47
+ context.addIssue({ code: "custom", message: "record value exceeds the contract bounds or is not portable JSON" });
48
+ return z.NEVER;
49
+ }
50
+ encoded[encodeRecordKey(key)] = entry;
51
+ }
52
+ return encoded;
53
+ }, z.record(z
12
54
  .string()
13
55
  .regex(/^u(?:[0-9a-f]{4})*$/, "record keys must contain valid Unicode"), valueSchema).transform((record) => {
14
56
  const restored = {};
@@ -23,19 +65,6 @@ export function ownPropertyRecordSchema(valueSchema) {
23
65
  return restored;
24
66
  }));
25
67
  }
26
- function encodeOwnRecordKeys(value) {
27
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
28
- return value;
29
- }
30
- const prototype = Object.getPrototypeOf(value);
31
- if (prototype !== Object.prototype && prototype !== null)
32
- return [];
33
- const encoded = Object.create(null);
34
- for (const [key, entry] of Object.entries(value)) {
35
- encoded[encodeRecordKey(key)] = entry;
36
- }
37
- return encoded;
38
- }
39
68
  function encodeRecordKey(value) {
40
69
  let encoded = isWellFormedUnicode(value) ? "u" : "i";
41
70
  for (let index = 0; index < value.length; index += 1) {
@@ -1,5 +1,6 @@
1
1
  import { agentCandidateSecretRefSchema } from "./agent-candidate-schema-common.js";
2
2
  import { z } from "zod";
3
+ import { CONTRACT_MAX_ARRAY_LENGTH, isBoundedJsonMaterial } from "./contract-limits.js";
3
4
  import { ownPropertyRecordSchema } from "./own-property-record.js";
4
5
  import { ProxySecretsSchema } from "./proxy-secrets.js";
5
6
  import { agentProfileKnowledgeRefsSchema } from "./profile-knowledge.js";
@@ -7,9 +8,24 @@ export { agentProfileKnowledgeRefSchema, agentProfileKnowledgeRefsSchema, } from
7
8
  import { REASONING_EFFORTS, TRAINED_MODEL_PREFIX, trainedModelIdForArtifact, } from "./agent-profile.js";
8
9
  import { canonicalCandidateDigest, sha256DigestSchema, environmentNameSchema, headerNameSchema, isSafeExecutable, isSafeRelativePath, isWellFormedUnicode, looksLikeCredential, } from "./agent-candidate-schema-common.js";
9
10
  import { harnessTypeSchema } from "./harness.js";
10
- import { canonicalAgentProfileJson } from "./agent-profile-canonical.js";
11
+ import { AgentProfileValueError, assertAgentProfileValue, canonicalAgentProfileJson } from "./agent-profile-canonical.js";
11
12
  import { harnessHonorsEffort, harnessHonorsModel, harnessModelSupport, harnessReasoningEfforts, harnessSystemPromptIntents, resolveModelProvider, } from "./harness-capabilities.js";
12
13
  import { SANDBOX_SIZE_PRESET_NAMES, } from "./sandbox-size.js";
14
+ const profileOpaqueValueSchema = z.unknown().transform((value, context) => {
15
+ if (value === undefined || isBoundedJsonMaterial(value, { dataPropertiesOnly: true }))
16
+ return structuredClone(value);
17
+ try {
18
+ assertAgentProfileValue(value, []);
19
+ }
20
+ catch (error) {
21
+ if (!(error instanceof AgentProfileValueError))
22
+ throw error;
23
+ context.addIssue({ code: "custom", path: [...error.path], message: error.message });
24
+ return z.NEVER;
25
+ }
26
+ context.addIssue({ code: "custom", message: "profile metadata exceeds contract bounds or is not portable JSON" });
27
+ return z.NEVER;
28
+ });
13
29
  export const agentProfilePermissionValueSchema = z.enum([
14
30
  "allow",
15
31
  "deny",
@@ -41,11 +57,11 @@ export const agentProfileFileMountSchema = z.strictObject({
41
57
  });
42
58
  export const agentProfileResourcesSchema = z.strictObject({
43
59
  knowledge: agentProfileKnowledgeRefsSchema.optional(),
44
- files: z.array(agentProfileFileMountSchema).optional(),
45
- tools: z.array(agentProfileResourceRefSchema).optional(),
46
- skills: z.array(agentProfileResourceRefSchema).optional(),
47
- agents: z.array(agentProfileResourceRefSchema).optional(),
48
- commands: z.array(agentProfileResourceRefSchema).optional(),
60
+ files: z.array(agentProfileFileMountSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
61
+ tools: z.array(agentProfileResourceRefSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
62
+ skills: z.array(agentProfileResourceRefSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
63
+ agents: z.array(agentProfileResourceRefSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
64
+ commands: z.array(agentProfileResourceRefSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
49
65
  instructions: z
50
66
  .union([z.string(), agentProfileResourceRefSchema])
51
67
  .optional(),
@@ -88,7 +104,7 @@ export const agentProfileModelHintsBaseSchema = z.strictObject({
88
104
  maxVisibleOutputTokens: z.number().int().positive().optional(),
89
105
  maxReasoningTokens: z.number().int().positive().optional(),
90
106
  maxTotalOutputTokens: z.number().int().positive().optional(),
91
- metadata: ownPropertyRecordSchema(z.unknown()).optional(),
107
+ metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
92
108
  });
93
109
  export const agentProfileModelHintsSchema = agentProfileModelHintsBaseSchema.superRefine(enforceModelTokenBounds);
94
110
  /** Ordered account selection policy, without credentials or ambient model defaults. */
@@ -113,7 +129,7 @@ export const agentProfileSeatSchema = z.strictObject({
113
129
  export const agentProfilePromptSchema = z.strictObject({
114
130
  systemPrompt: z.string().optional(),
115
131
  appendSystemPrompt: z.string().optional(),
116
- instructions: z.array(z.string()).optional(),
132
+ instructions: z.array(z.string()).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
117
133
  });
118
134
  const controlCharacterPattern = /[\u0000-\u001f\u007f]/;
119
135
  const secretCapableNamePattern = /(?:^|[_-])(?:api[_-]?key|access[_-]?key|private[_-]?key|token|secret|password|credentials?|authorization|cookie|database[_-]?url|dsn|pat)(?:[_-]|$)/i;
@@ -166,7 +182,7 @@ export const agentSubagentProfileSchema = z.strictObject({
166
182
  tools: ownPropertyRecordSchema(z.boolean()).optional(),
167
183
  permissions: ownPropertyRecordSchema(agentProfilePermissionSchema).optional(),
168
184
  maxSteps: z.number().optional(),
169
- metadata: ownPropertyRecordSchema(z.unknown()).optional(),
185
+ metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
170
186
  });
171
187
  export const agentProfileHookCommandSchema = z.strictObject({
172
188
  command: z.string(),
@@ -181,7 +197,7 @@ export const agentProfileModeSchema = z.strictObject({
181
197
  prompt: z.string().optional(),
182
198
  tools: ownPropertyRecordSchema(z.boolean()).optional(),
183
199
  permissions: ownPropertyRecordSchema(agentProfilePermissionSchema).optional(),
184
- metadata: ownPropertyRecordSchema(z.unknown()).optional(),
200
+ metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
185
201
  });
186
202
  export const agentProfileConfidentialSchema = z.strictObject({
187
203
  tee: z.string().optional(),
@@ -204,7 +220,7 @@ const remoteMcpUrlSchema = nonBlankMcpValueSchema.refine((value) => {
204
220
  const agentProfileLocalMcpServerSchema = z.strictObject({
205
221
  transport: z.literal("stdio").optional(),
206
222
  command: nonBlankMcpValueSchema.refine(isSafeExecutable, "local MCP command must be a canonical public executable"),
207
- args: z.array(agentProfileConfigValueSchema).optional(),
223
+ args: z.array(agentProfileConfigValueSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
208
224
  env: agentProfileEnvironmentSchema.optional(),
209
225
  cwd: z
210
226
  .string()
@@ -213,7 +229,7 @@ const agentProfileLocalMcpServerSchema = z.strictObject({
213
229
  url: z.undefined().optional(),
214
230
  headers: z.undefined().optional(),
215
231
  enabled: z.literal(true).optional(),
216
- metadata: ownPropertyRecordSchema(z.unknown()).optional(),
232
+ metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
217
233
  });
218
234
  const agentProfileRemoteMcpServerSchema = z.strictObject({
219
235
  transport: z.enum(["sse", "http"]).optional(),
@@ -224,7 +240,7 @@ const agentProfileRemoteMcpServerSchema = z.strictObject({
224
240
  url: remoteMcpUrlSchema,
225
241
  headers: agentProfileHeaderSchema.optional(),
226
242
  enabled: z.literal(true).optional(),
227
- metadata: ownPropertyRecordSchema(z.unknown()).optional(),
243
+ metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
228
244
  });
229
245
  const agentProfileDisabledMcpServerSchema = z.strictObject({
230
246
  enabled: z.literal(false),
@@ -235,7 +251,7 @@ const agentProfileDisabledMcpServerSchema = z.strictObject({
235
251
  cwd: z.undefined().optional(),
236
252
  url: z.undefined().optional(),
237
253
  headers: z.undefined().optional(),
238
- metadata: ownPropertyRecordSchema(z.unknown()).optional(),
254
+ metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
239
255
  });
240
256
  export const agentProfileMcpServerSchema = z.union([
241
257
  agentProfileLocalMcpServerSchema,
@@ -244,10 +260,10 @@ export const agentProfileMcpServerSchema = z.union([
244
260
  ]);
245
261
  export const agentProfileConnectionSchema = z.strictObject({
246
262
  connectionId: z.string().min(1),
247
- capabilities: z.array(z.string().min(1)).min(1),
263
+ capabilities: z.array(z.string().min(1)).max(CONTRACT_MAX_ARRAY_LENGTH).min(1),
248
264
  alias: z.string().min(1).optional(),
249
265
  });
250
- const removeListSchema = z.union([z.literal(true), z.array(z.string().min(1))]);
266
+ const removeListSchema = z.union([z.literal(true), z.array(z.string().min(1)).max(CONTRACT_MAX_ARRAY_LENGTH)]);
251
267
  export const agentProfilePromptRemovalSchema = z.strictObject({
252
268
  systemPrompt: z.literal(true).optional(),
253
269
  appendSystemPrompt: z.literal(true).optional(),
@@ -311,10 +327,7 @@ export const agentTrainingDatasetIdentitySchema = z.strictObject({
311
327
  context.addIssue({ code: "custom", path: ["taskSetDigest"], message: "training task inventory digest mismatch" });
312
328
  }
313
329
  });
314
- export const agentTrainingParametersSchema = ownPropertyRecordSchema(z.union([z.string().max(2_048).pipe(publicProfileConfigStringSchema), z.number().finite(), z.boolean(), z.null()])).superRefine((parameters, context) => {
315
- if (Object.keys(parameters).length > 100) {
316
- context.addIssue({ code: "custom", message: "at most 100 public training parameters are allowed" });
317
- }
330
+ export const agentTrainingParametersSchema = ownPropertyRecordSchema(z.union([z.string().max(2_048).pipe(publicProfileConfigStringSchema), z.number().finite(), z.boolean(), z.null()]), { maxEntries: 100 }).superRefine((parameters, context) => {
318
331
  for (const name of Object.keys(parameters)) {
319
332
  if (!trainingIdentitySchema.safeParse(name).success || isCredentialBearingProfileConfigName(name)) {
320
333
  context.addIssue({ code: "custom", path: [name], message: "training parameter name must be public and non-credential" });
@@ -359,13 +372,25 @@ export const agentProfileTrainingSchema = z.strictObject({
359
372
  }
360
373
  }
361
374
  });
362
- export const agentProfileMetadataSchema = ownPropertyRecordSchema(z.unknown()).superRefine((metadata, context) => {
375
+ export const agentProfileMetadataSchema = ownPropertyRecordSchema(z.unknown(), {
376
+ prepareValue: (key, value) => {
377
+ if (key === "training")
378
+ return value;
379
+ if (value === undefined || isBoundedJsonMaterial(value, { dataPropertiesOnly: true }))
380
+ return structuredClone(value);
381
+ assertAgentProfileValue(value, [key]);
382
+ return z.NEVER;
383
+ },
384
+ }).superRefine((metadata, context) => {
363
385
  if (!Object.hasOwn(metadata, "training"))
364
386
  return;
365
387
  const result = agentProfileTrainingSchema.safeParse(metadata.training);
366
- if (!result.success) {
388
+ if (result.success)
389
+ metadata.training = result.data;
390
+ else {
391
+ delete metadata.training;
367
392
  for (const issue of result.error.issues) {
368
- context.addIssue({ code: "custom", path: ["training", ...issue.path], message: issue.message });
393
+ context.addIssue({ code: "custom", path: ["training", ...issue.path], message: issue.message, continue: false });
369
394
  }
370
395
  }
371
396
  });
@@ -379,23 +404,23 @@ export const agentProfileSchema = z
379
404
  name: z.string().optional(),
380
405
  description: z.string().optional(),
381
406
  version: z.string().optional(),
382
- tags: z.array(z.string()).optional(),
407
+ tags: z.array(z.string()).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
383
408
  prompt: agentProfilePromptSchema.optional(),
384
409
  model: agentProfileModelHintsSchema.optional(),
385
410
  harness: harnessTypeSchema.optional(),
386
- seats: z.array(agentProfileSeatSchema).nonempty().optional(),
411
+ seats: z.array(agentProfileSeatSchema).max(CONTRACT_MAX_ARRAY_LENGTH).nonempty().optional(),
387
412
  proxySecrets: ProxySecretsSchema.optional(),
388
413
  permissions: ownPropertyRecordSchema(agentProfilePermissionSchema).optional(),
389
414
  tools: ownPropertyRecordSchema(z.boolean()).optional(),
390
415
  mcp: ownPropertyRecordSchema(agentProfileMcpServerSchema).optional(),
391
- connections: z.array(agentProfileConnectionSchema).optional(),
416
+ connections: z.array(agentProfileConnectionSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
392
417
  subagents: ownPropertyRecordSchema(agentSubagentProfileSchema).optional(),
393
418
  resources: agentProfileResourcesSchema.optional(),
394
- hooks: ownPropertyRecordSchema(z.array(agentProfileHookCommandSchema)).optional(),
419
+ hooks: ownPropertyRecordSchema(z.array(agentProfileHookCommandSchema).max(CONTRACT_MAX_ARRAY_LENGTH)).optional(),
395
420
  modes: ownPropertyRecordSchema(agentProfileModeSchema).optional(),
396
421
  confidential: agentProfileConfidentialSchema.optional(),
397
422
  metadata: agentProfileMetadataSchema.optional(),
398
- extensions: ownPropertyRecordSchema(z.union([ownPropertyRecordSchema(z.unknown()), z.undefined()])).optional(),
423
+ extensions: ownPropertyRecordSchema(z.union([ownPropertyRecordSchema(profileOpaqueValueSchema), z.undefined()])).optional(),
399
424
  })
400
425
  .superRefine((profile, context) => {
401
426
  validateNestedRecordKeys(profile, context, [], new Set());
@@ -533,13 +558,13 @@ export const agentProfileDiffSchema = z.strictObject({
533
558
  "optimizer",
534
559
  "compound",
535
560
  ]),
536
- artifacts: z.array(z.string().min(1)).optional(),
537
- notes: z.array(z.string()).optional(),
561
+ artifacts: z.array(z.string().min(1)).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
562
+ notes: z.array(z.string()).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
538
563
  })
539
564
  .optional(),
540
565
  set: agentProfileSchema.optional(),
541
566
  remove: agentProfileDiffRemovalSchema.optional(),
542
- metadata: ownPropertyRecordSchema(z.unknown()).optional(),
567
+ metadata: ownPropertyRecordSchema(profileOpaqueValueSchema).optional(),
543
568
  });
544
569
  const _agentProfileSchemaMatchesInterface = true;
545
570
  void _agentProfileSchemaMatchesInterface;
@@ -57,9 +57,8 @@ export const ProxySecretsSchema = z.array(ProxySecretBindingSchema).max(CONTRACT
57
57
  * Strings select an API-verified parent sandbox; null explicitly selects independent issuance.
58
58
  * When present, every destination in the final effective selection must have exactly one key.
59
59
  */
60
- export const ProxySecretParentsSchema = ownPropertyRecordSchema(boundedIdentifierSchema.nullable())
61
- .refine((parents) => Object.keys(parents).length <= CONTRACT_MAX_ARRAY_LENGTH &&
62
- Object.keys(parents).every((name) => envNameSchema.safeParse(name).success), "invalid parent selection keys");
60
+ export const ProxySecretParentsSchema = ownPropertyRecordSchema(boundedIdentifierSchema.nullable(), { maxEntries: CONTRACT_MAX_ARRAY_LENGTH })
61
+ .refine((parents) => Object.keys(parents).every((name) => envNameSchema.safeParse(name).success), "invalid parent selection keys");
63
62
  /** Shape and totality only; the API verifies issuer authority and durable immutable pins. */
64
63
  export function isProxySecretParentsTotal(bindings, parents) {
65
64
  const selection = ProxySecretsSchema.safeParse(bindings);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-interface",
3
- "version": "5.8.1",
3
+ "version": "5.10.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",