@fungi.computer/forage 0.0.0-stage → 0.0.1

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,519 @@
1
+ import { jsonSchemaSubset } from "./json-schema-subset.js";
2
+ import { parameterName, thirdPartyText, isUnsafeSchemaPropertyName, jsonSchemaValueInspection, schemaDepthLimit, schemaNodeLimit, } from "./json-schema-boundary.js";
3
+ import * as z from "zod";
4
+ import { isCollection, isPair, isScalar, parseDocument, visit } from "yaml";
5
+ import $RefParser from "@apidevtools/json-schema-ref-parser";
6
+ import { jsonSchemaToZod } from "./app-api.js";
7
+ const reservedNames = new Set([
8
+ "describe",
9
+ "search",
10
+ "then",
11
+ "prototype",
12
+ ...Object.getOwnPropertyNames(Object.prototype),
13
+ ]);
14
+ const pathTemplate = z
15
+ .string()
16
+ .max(2048)
17
+ .regex(/^\/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2}|\{[A-Za-z0-9._~-]{1,128}\}|\/)*$/u)
18
+ .refine((path) => !path.includes("//") &&
19
+ !path.split("/").some((part) => part === "." || part === "..") &&
20
+ !/%(?:2e|2f|5c|25)/iu.test(path));
21
+ const forbiddenHeaders = new Set([
22
+ "authorization",
23
+ "cookie",
24
+ "host",
25
+ "content-length",
26
+ "connection",
27
+ "transfer-encoding",
28
+ "accept-encoding",
29
+ "set-cookie",
30
+ "upgrade",
31
+ "te",
32
+ "trailer",
33
+ "keep-alive",
34
+ "expect",
35
+ "via",
36
+ "forwarded",
37
+ "x-real-ip",
38
+ "max-forwards",
39
+ "x-http-method-override",
40
+ "x-method-override",
41
+ "x-http-method",
42
+ "content-type",
43
+ ]);
44
+ const jsonSchema = z.union([z.record(z.string(), z.json()), z.boolean()]);
45
+ const supportedSchema = jsonSchemaSubset(false);
46
+ const supportedSchema30 = jsonSchemaSubset(true);
47
+ const parameter = z.object({
48
+ name: parameterName,
49
+ in: z.enum(["path", "query", "header"]),
50
+ required: z.boolean().default(false),
51
+ schema: jsonSchema,
52
+ style: z.string().optional(),
53
+ explode: z.boolean().optional(),
54
+ allowReserved: z.boolean().optional(),
55
+ });
56
+ const media = z.object({ schema: jsonSchema.optional() });
57
+ const content = z.record(z.string(), media);
58
+ const operation = z.object({
59
+ operationId: z.string().max(128).optional(),
60
+ summary: thirdPartyText(2048).prefault(""),
61
+ description: thirdPartyText(2048).prefault(""),
62
+ tags: z.array(thirdPartyText(64)).max(16).default([]),
63
+ parameters: z.array(parameter).default([]),
64
+ requestBody: z
65
+ .object({ required: z.boolean().default(false), content })
66
+ .optional(),
67
+ responses: z.record(z.string(), z.object({
68
+ description: thirdPartyText(2048),
69
+ content: content.optional(),
70
+ })),
71
+ });
72
+ const authScheme = z.discriminatedUnion("type", [
73
+ z.object({
74
+ type: z.literal("apiKey"),
75
+ in: z.enum(["header", "query"]),
76
+ name: parameterName,
77
+ }),
78
+ z.object({
79
+ type: z.literal("http"),
80
+ scheme: z
81
+ .string()
82
+ .transform((scheme) => scheme.toLowerCase())
83
+ .pipe(z.literal("bearer")),
84
+ }),
85
+ z.object({ type: z.literal("oauth2") }),
86
+ ]);
87
+ const authHintsSchema = z.array(z.discriminatedUnion("kind", [
88
+ z.object({
89
+ scheme: z.string().max(128),
90
+ kind: z.enum(["api_key_header", "api_key_query"]),
91
+ name: parameterName,
92
+ }),
93
+ z.object({
94
+ scheme: z.string().max(128),
95
+ kind: z.enum(["bearer", "oauth", "unsupported"]),
96
+ }),
97
+ ]));
98
+ const intakeOptions = z.object({
99
+ authParameter: z
100
+ .object({ in: z.enum(["header", "query"]), name: z.string() })
101
+ .optional(),
102
+ });
103
+ // Server entries are discovery hints, never credential destinations or egress grants.
104
+ // Relative entries remain relative; only the caller knows the spec retrieval URL.
105
+ const serverHint = z
106
+ .string()
107
+ .min(1)
108
+ .max(2048)
109
+ .transform((value, ctx) => {
110
+ try {
111
+ const url = new URL(value, "https://relative-openapi.invalid/");
112
+ if (url.protocol !== "https:" ||
113
+ url.username ||
114
+ url.password ||
115
+ value.startsWith("//") ||
116
+ /[\\\s]/u.test(value)) {
117
+ ctx.addIssue({
118
+ code: "custom",
119
+ message: "Server hints require HTTPS without credentials",
120
+ });
121
+ return z.NEVER;
122
+ }
123
+ return /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(value) ? url.href : value;
124
+ }
125
+ catch (cause) {
126
+ ctx.addIssue({
127
+ code: "custom",
128
+ message: cause instanceof Error ? cause.message : "Invalid server URL",
129
+ });
130
+ return z.NEVER;
131
+ }
132
+ });
133
+ const document = z.object({
134
+ openapi: z.string().regex(/^3\.[01]\.\d+$/u),
135
+ info: z.object({
136
+ title: thirdPartyText(200).min(1),
137
+ version: z.string().max(128),
138
+ }),
139
+ servers: z.array(z.object({ url: serverHint })).default([]),
140
+ paths: z.record(z.string().startsWith("/").max(2048), z.record(z.string(), z.json())),
141
+ components: z.record(z.string(), z.json()).default({}),
142
+ });
143
+ /** A bounded intake refusal, distinct from a listed unsupported operation. */
144
+ export class OpenApiError extends Error {
145
+ code;
146
+ name = "OpenApiError";
147
+ constructor(code, message, cause) {
148
+ super(message, { cause });
149
+ this.code = code;
150
+ }
151
+ }
152
+ /** One stored operation, independent of spec-host URLs or credentials. */
153
+ export const openApiOperationSchema = z
154
+ .strictObject({
155
+ status: z.literal("supported"),
156
+ name: z
157
+ .string()
158
+ .max(80)
159
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/u)
160
+ .refine((name) => !reservedNames.has(name)),
161
+ method: z.enum([
162
+ "GET",
163
+ "HEAD",
164
+ "OPTIONS",
165
+ "POST",
166
+ "PUT",
167
+ "PATCH",
168
+ "DELETE",
169
+ ]),
170
+ path: pathTemplate,
171
+ effect: z.enum(["read", "write"]),
172
+ description: thirdPartyText(2048),
173
+ textSource: z.literal("third_party"),
174
+ tags: z.array(thirdPartyText(64)).max(16),
175
+ parameters: z.array(parameter),
176
+ input: supportedSchema,
177
+ output: supportedSchema,
178
+ })
179
+ .refine((op) => op.effect === operationEffect(op.method), "Effect must match the HTTP method")
180
+ .superRefine((op, ctx) => {
181
+ try {
182
+ validateParameters(op.path, op.parameters);
183
+ }
184
+ catch (cause) {
185
+ if (cause instanceof OpenApiError || cause instanceof z.ZodError)
186
+ ctx.addIssue({ code: "custom", message: "Unsafe OpenAPI parameters" });
187
+ else
188
+ throw cause;
189
+ }
190
+ });
191
+ function operationEffect(method) {
192
+ return ["GET", "HEAD", "OPTIONS"].includes(method) ? "read" : "write";
193
+ }
194
+ function validateParameters(path, parameters, authHints = [], authParameter, schemaDecoder = supportedSchema30) {
195
+ if (!pathTemplate.safeParse(path).success)
196
+ throw new OpenApiError("unsafe_path", "Unsafe OpenAPI path template");
197
+ const pathNames = [...path.matchAll(/\{([^{}]+)\}/gu)].map((match) => match[1]);
198
+ if (pathNames.some((name) => !parameters.some((p) => p.in === "path" && p.name === name && p.required)) ||
199
+ parameters.some((p) => p.in === "path" && (!p.required || !pathNames.includes(p.name))))
200
+ throw new OpenApiError("invalid_operation", "Path parameters must match required placeholders");
201
+ const parameterNames = new Set();
202
+ for (const p of parameters) {
203
+ const scalar = z
204
+ .object({
205
+ type: z.union([
206
+ z.enum(["string", "number", "integer", "boolean"]),
207
+ z
208
+ .array(z.enum(["string", "number", "integer", "boolean", "null"]))
209
+ .min(1),
210
+ ]),
211
+ })
212
+ .safeParse(p.schema);
213
+ const style = p.in === "query" ? "form" : "simple";
214
+ if (!scalar.success ||
215
+ (p.style !== undefined && p.style !== style) ||
216
+ (p.explode !== undefined && p.explode !== (p.in === "query")) ||
217
+ p.allowReserved === true)
218
+ throw new OpenApiError("unsupported_parameter", "Only scalar parameters with default serialization are supported");
219
+ if (parameterNames.has(p.name))
220
+ throw new OpenApiError("parameter_collision", "Flattened parameter names collide");
221
+ parameterNames.add(p.name);
222
+ if (authHints.some((auth) => "name" in auth &&
223
+ auth.name.toLowerCase() === p.name.toLowerCase() &&
224
+ auth.kind === `api_key_${p.in}`) ||
225
+ (authParameter?.in === p.in &&
226
+ authParameter.name.toLowerCase() === p.name.toLowerCase()))
227
+ throw new OpenApiError("unsafe_auth_parameter", "The credential parameter cannot be supplied by a guest");
228
+ const header = p.name.toLowerCase();
229
+ if (p.in === "header" &&
230
+ (!/^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,128}$/u.test(p.name) ||
231
+ forbiddenHeaders.has(header) ||
232
+ /^(?:proxy-|x-forwarded-|nango-|cf-)/iu.test(header)))
233
+ throw new OpenApiError("unsafe_header", "A reserved or malformed header cannot be a parameter");
234
+ schemaDecoder.parse(p.schema);
235
+ }
236
+ }
237
+ async function resolveOperation(spec, raw, parameters) {
238
+ const parser = new $RefParser();
239
+ return parser.dereference({ ...spec, operation: raw, parameters }, {
240
+ mutateInputSchema: false,
241
+ resolve: { external: false, file: false, http: false },
242
+ dereference: {
243
+ maxDepth: schemaDepthLimit,
244
+ circular: false,
245
+ excludedPathMatcher: (path) => path.startsWith("#/components") || path.startsWith("#/paths"),
246
+ onCircular: () => {
247
+ throw new OpenApiError("recursive_schema", "Recursive operation schemas are unsupported");
248
+ },
249
+ },
250
+ });
251
+ }
252
+ function inspectDocument(doc, budget) {
253
+ const countNode = (path) => {
254
+ if (++budget.nodes > schemaNodeLimit)
255
+ throw new OpenApiError("nodes_limit", "The spec exceeds 50,000 nodes");
256
+ if (path.filter(isCollection).length > schemaDepthLimit)
257
+ throw new OpenApiError("depth_limit", "The spec exceeds depth 32");
258
+ };
259
+ visit(doc, {
260
+ Node: (_key, _node, path) => {
261
+ countNode(path);
262
+ },
263
+ Alias: () => {
264
+ throw new OpenApiError("alias_limit", "YAML aliases are not supported");
265
+ },
266
+ Scalar: (_key, scalar, path) => {
267
+ countNode(path);
268
+ const owner = path.findLast(isPair);
269
+ if (owner &&
270
+ isScalar(owner.key) &&
271
+ owner.key.value === "required" &&
272
+ isUnsafeSchemaPropertyName(z.string().safeParse(scalar.value).data ?? ""))
273
+ throw new OpenApiError("unsafe_name", "Unsafe required property name");
274
+ },
275
+ Pair: (_key, pair, path) => {
276
+ countNode(path);
277
+ if (isScalar(pair.key) &&
278
+ isUnsafeSchemaPropertyName(z.string().safeParse(pair.key.value).data ?? ""))
279
+ throw new OpenApiError("unsafe_name", "Unsafe object property name");
280
+ if (isScalar(pair.key) &&
281
+ pair.key.value === "name" &&
282
+ isScalar(pair.value) &&
283
+ isUnsafeSchemaPropertyName(z.string().safeParse(pair.value.value).data ?? ""))
284
+ throw new OpenApiError("unsafe_name", "Unsafe parameter name");
285
+ if (isScalar(pair.key) && pair.key.value === "$ref") {
286
+ const reference = z
287
+ .string()
288
+ .startsWith("#/")
289
+ .safeParse(isScalar(pair.value) ? pair.value.value : null);
290
+ if (!reference.success)
291
+ throw new OpenApiError("external_reference", "Only local #/ references are supported", reference.error);
292
+ }
293
+ },
294
+ });
295
+ }
296
+ function inspectExpansion(source, budget) {
297
+ const refusal = jsonSchemaValueInspection(budget).parse(source);
298
+ if (refusal) {
299
+ const messages = {
300
+ nodes_limit: "Expanded values exceed 50,000 nodes",
301
+ depth_limit: "Expanded values exceed depth 32",
302
+ unsafe_name: "Unsafe object property, parameter or required name",
303
+ };
304
+ throw new OpenApiError(refusal, messages[refusal]);
305
+ }
306
+ }
307
+ /** Pure intake: the host supplies bounded spec text, never a fetch locator. */
308
+ export async function normalizeOpenApi(source, options = {}) {
309
+ const authParameter = intakeOptions.parse(options).authParameter;
310
+ if (source.length > 2 * 1024 * 1024 ||
311
+ new TextEncoder().encode(source).byteLength > 2 * 1024 * 1024)
312
+ throw new OpenApiError("too_large", "The spec exceeds 2 MiB");
313
+ const syntax = parseDocument(source);
314
+ if (syntax.errors.length)
315
+ throw new OpenApiError("invalid_spec", "Invalid JSON or YAML", syntax.errors[0]);
316
+ inspectDocument(syntax, { nodes: 0 });
317
+ const rawDocument = syntax.toJS({ maxAliasCount: 0 });
318
+ const version = z
319
+ .object({ openapi: z.string().regex(/^3\.[01]\.\d+$/u) })
320
+ .safeParse(rawDocument);
321
+ if (!version.success)
322
+ throw new OpenApiError("not_openapi3", "Not OpenAPI 3. Convert Swagger 2 to OpenAPI 3.");
323
+ const decoded = document.safeParse(rawDocument);
324
+ if (!decoded.success)
325
+ throw new OpenApiError("invalid_spec", "Invalid OpenAPI 3 document", decoded.error);
326
+ const parsed = decoded.data;
327
+ const intakeSchema = parsed.openapi.startsWith("3.0.")
328
+ ? supportedSchema30
329
+ : supportedSchema;
330
+ const securityResult = z
331
+ .object({ securitySchemes: z.record(parameterName, z.json()).default({}) })
332
+ .safeParse(parsed.components);
333
+ if (!securityResult.success)
334
+ throw new OpenApiError("invalid_spec", "Invalid security schemes", securityResult.error);
335
+ const security = securityResult.data;
336
+ const authHints = authHintsSchema.parse(Object.entries(security.securitySchemes).map(([scheme, raw]) => {
337
+ const parsed = authScheme.safeParse(raw);
338
+ if (!parsed.success)
339
+ return { scheme, kind: "unsupported" };
340
+ const auth = parsed.data;
341
+ if (auth.type === "apiKey")
342
+ return {
343
+ scheme,
344
+ kind: auth.in === "header" ? "api_key_header" : "api_key_query",
345
+ name: auth.name,
346
+ };
347
+ return { scheme, kind: auth.type === "http" ? "bearer" : "oauth" };
348
+ }));
349
+ const budget = { nodes: 0 };
350
+ const names = new Set(reservedNames);
351
+ const operations = [];
352
+ for (const [path, item] of Object.entries(parsed.paths).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
353
+ if (operations.length >= 500)
354
+ throw new OpenApiError("operations_limit", "The spec exceeds 500 operations");
355
+ if (item.$ref !== undefined) {
356
+ operations.push({
357
+ status: "unsupported",
358
+ name: `path_item_${String(operations.length)}`,
359
+ method: "PATH_ITEM",
360
+ path,
361
+ reason: "unsupported_path_item",
362
+ });
363
+ continue;
364
+ }
365
+ for (const method of [
366
+ "get",
367
+ "head",
368
+ "options",
369
+ "post",
370
+ "put",
371
+ "patch",
372
+ "delete",
373
+ "trace",
374
+ ]) {
375
+ const raw = item[method];
376
+ if (raw === undefined)
377
+ continue;
378
+ if (operations.length >= 500)
379
+ throw new OpenApiError("operations_limit", "The spec exceeds 500 operations");
380
+ const label = z
381
+ .object({ operationId: z.string().max(128).optional() })
382
+ .safeParse(raw);
383
+ const preferred = label.success && label.data.operationId
384
+ ? label.data.operationId
385
+ : `${method}_${path}`;
386
+ const cleaned = preferred.replace(/[^A-Za-z0-9_]/gu, "_").slice(0, 72);
387
+ const base = /^[A-Za-z_]/u.test(cleaned) ? cleaned : `op_${cleaned}`;
388
+ let name = base;
389
+ for (let suffix = 2; names.has(name); suffix++)
390
+ name = `${base}_${String(suffix)}`;
391
+ names.add(name);
392
+ try {
393
+ if (method === "trace")
394
+ throw new OpenApiError("unsupported_method", "TRACE is unsupported");
395
+ const rawResolved = z
396
+ .object({ operation: z.unknown(), parameters: z.unknown() })
397
+ .parse(await resolveOperation(parsed, raw, item.parameters ?? []));
398
+ inspectExpansion({
399
+ operation: rawResolved.operation,
400
+ parameters: rawResolved.parameters,
401
+ }, budget);
402
+ const resolved = z
403
+ .object({ operation, parameters: z.array(parameter) })
404
+ .parse(rawResolved);
405
+ const declared = resolved.operation;
406
+ const merged = new Map();
407
+ for (const list of [resolved.parameters, declared.parameters]) {
408
+ const seen = new Set();
409
+ for (const p of list) {
410
+ const key = `${p.in}:${p.name}`;
411
+ if (seen.has(key))
412
+ throw new OpenApiError("invalid_operation", "Duplicate parameters in one declaration");
413
+ seen.add(key);
414
+ merged.set(key, p);
415
+ }
416
+ }
417
+ const parameters = [...merged.values()];
418
+ validateParameters(path, parameters, authHints, authParameter, intakeSchema);
419
+ const parameterNames = new Set(parameters.map((p) => p.name));
420
+ const properties = Object.fromEntries(parameters.map((parameter) => [parameter.name, parameter.schema]));
421
+ const required = [];
422
+ for (const parameter of parameters) {
423
+ if (parameter.required)
424
+ required.push(parameter.name);
425
+ }
426
+ if (declared.requestBody &&
427
+ declared.requestBody.content["application/json"]?.schema === undefined)
428
+ throw new OpenApiError("unsupported_media", "Request bodies require an application/json schema");
429
+ const body = declared.requestBody?.content["application/json"]?.schema;
430
+ if (body !== undefined) {
431
+ if (parameterNames.has("body"))
432
+ throw new OpenApiError("parameter_collision", "Body collides with a parameter");
433
+ properties.body = body;
434
+ if (declared.requestBody?.required)
435
+ required.push("body");
436
+ }
437
+ const input = {
438
+ type: "object",
439
+ properties,
440
+ required,
441
+ additionalProperties: false,
442
+ };
443
+ const successSchemas = Object.entries(declared.responses)
444
+ .filter(([status]) => /^2\d\d$/u.test(status))
445
+ .map(([, response]) => response.content?.["application/json"]?.schema ?? {});
446
+ const output = successSchemas.length > 1
447
+ ? { anyOf: successSchemas }
448
+ : (successSchemas[0] ?? {});
449
+ const target = parsed.openapi.startsWith("3.0.")
450
+ ? "openapi-3.0"
451
+ : "draft-2020-12";
452
+ const normalizedInput = z.toJSONSchema(jsonSchemaToZod(intakeSchema.parse(input), target), { io: "input" });
453
+ const normalizedOutput = z.toJSONSchema(jsonSchemaToZod(intakeSchema.parse(output), target), { io: "input" });
454
+ operations.push(openApiOperationSchema.parse({
455
+ status: "supported",
456
+ name,
457
+ method: method.toUpperCase(),
458
+ path,
459
+ effect: operationEffect(method.toUpperCase()),
460
+ description: declared.description || declared.summary,
461
+ textSource: "third_party",
462
+ tags: declared.tags,
463
+ parameters,
464
+ input: normalizedInput,
465
+ output: normalizedOutput,
466
+ }));
467
+ }
468
+ catch (cause) {
469
+ if (cause instanceof OpenApiError && cause.code === "nodes_limit")
470
+ throw cause;
471
+ operations.push({
472
+ status: "unsupported",
473
+ name,
474
+ method: method.toUpperCase(),
475
+ path,
476
+ reason: cause instanceof OpenApiError
477
+ ? cause.code
478
+ : cause instanceof RangeError
479
+ ? "depth_limit"
480
+ : "unsupported_schema",
481
+ });
482
+ }
483
+ }
484
+ }
485
+ const tableDigest = await digestOperations(operations.filter((op) => op.status === "supported"));
486
+ return {
487
+ title: parsed.info.title,
488
+ version: parsed.openapi,
489
+ servers: parsed.servers.map((server) => server.url),
490
+ authHints,
491
+ operations,
492
+ tableDigest,
493
+ };
494
+ }
495
+ async function digestOperations(operations) {
496
+ const bytes = new TextEncoder().encode(JSON.stringify(operations));
497
+ if (bytes.byteLength > 512 * 1024)
498
+ throw new OpenApiError("table_too_large", "The normalized table exceeds 512 KiB");
499
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
500
+ return Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
501
+ }
502
+ /** Decode selected operations before persistence or execution; no authority is granted. */
503
+ export async function parseOpenApiTable(source) {
504
+ const raw = z.array(z.unknown()).min(1).max(64).safeParse(source);
505
+ if (!raw.success)
506
+ throw new OpenApiError("invalid_table", "Select 1–64 supported operations", raw.error);
507
+ inspectExpansion(raw.data, { nodes: 0 });
508
+ const decoded = z
509
+ .array(openApiOperationSchema)
510
+ .min(1)
511
+ .max(64)
512
+ .safeParse(raw.data);
513
+ if (!decoded.success)
514
+ throw new OpenApiError("invalid_table", "Select 1–64 supported operations", decoded.error);
515
+ const operations = decoded.data.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
516
+ if (new Set(operations.map((op) => op.name)).size !== operations.length)
517
+ throw new OpenApiError("invalid_table", "Selected method names must be unique");
518
+ return { operations, digest: await digestOperations(operations) };
519
+ }
@@ -0,0 +1,26 @@
1
+ import type { JsonValue } from "./normalization.js";
2
+ import { type JsonSchemaSource } from "./schemas.js";
3
+ /** Installation-time TypeScript projections for one catalog operation. */
4
+ export type RenderedOperation = Readonly<{
5
+ types: string;
6
+ environment: string;
7
+ indexEnvironment: string;
8
+ }>;
9
+ /** Render a namespace index as a callable nested environment. */
10
+ export declare function renderNamespaceIndex(operations: readonly RenderedOperation[]): string;
11
+ /** Render whole-operation schemas and their callable nested environment. */
12
+ export declare function renderOperationTypes(operations: readonly RenderedOperation[]): string;
13
+ /** Prepare all TypeScript descriptions for one operation at installation. */
14
+ export declare function renderOperation(operation: {
15
+ path: string;
16
+ input: JsonSchemaSource;
17
+ output?: JsonSchemaSource | undefined;
18
+ }): RenderedOperation;
19
+ /** Derive sample arguments solely from input-schema examples and defaults. */
20
+ export declare function deriveSample(schema: JsonSchemaSource): Record<string, JsonValue> | undefined;
21
+ /** Preserve validator constraints in the operation's Standard JSON Schema. */
22
+ export declare function inputSchemaProjection(schema: JsonSchemaSource): JsonValue;
23
+ /** Produce a canonical schema representation used in peer identity and stamps. */
24
+ export declare function schemaFingerprint(schema: JsonSchemaSource, io?: "input" | "output"): string;
25
+ /** Validate the bounded JSON projection before a catalog is installed. */
26
+ export declare function schemaProjectionSize(schema: JsonSchemaSource, io: "input" | "output"): number | undefined;