@knitli/openapi-mcp 0.0.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/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +362 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +249 -0
- package/dist/compile.d.ts +14 -0
- package/dist/compile.js +132 -0
- package/dist/compiler.d.ts +13 -0
- package/dist/compiler.js +7 -0
- package/dist/conformance/fixtures.d.ts +24 -0
- package/dist/conformance/fixtures.js +119 -0
- package/dist/conformance/index.d.ts +2 -0
- package/dist/conformance/index.js +2 -0
- package/dist/conformance/runtime-suite.d.ts +31 -0
- package/dist/conformance/runtime-suite.js +343 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/load.d.ts +31 -0
- package/dist/load.js +20 -0
- package/dist/operations.d.ts +27 -0
- package/dist/operations.js +231 -0
- package/dist/permissions.d.ts +31 -0
- package/dist/permissions.js +89 -0
- package/dist/release/compile-release.d.ts +33 -0
- package/dist/release/compile-release.js +1620 -0
- package/dist/release/load-v4.d.ts +29 -0
- package/dist/release/load-v4.js +297 -0
- package/dist/release/manifest-builder.d.ts +19 -0
- package/dist/release/manifest-builder.js +28 -0
- package/dist/release/publish.d.ts +45 -0
- package/dist/release/publish.js +346 -0
- package/dist/release/reference-map.d.ts +53 -0
- package/dist/release/reference-map.js +347 -0
- package/dist/release/schema-v4.d.ts +5 -0
- package/dist/release/schema-v4.js +91 -0
- package/dist/runtime/action-permit.d.ts +11 -0
- package/dist/runtime/action-permit.js +1 -0
- package/dist/runtime/classify.d.ts +16 -0
- package/dist/runtime/classify.js +279 -0
- package/dist/runtime/credential-binding.d.ts +3 -0
- package/dist/runtime/credential-binding.js +101 -0
- package/dist/runtime/digest.d.ts +8 -0
- package/dist/runtime/digest.js +80 -0
- package/dist/runtime/errors.d.ts +14 -0
- package/dist/runtime/errors.js +15 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.js +14 -0
- package/dist/runtime/manifest.d.ts +33 -0
- package/dist/runtime/manifest.js +489 -0
- package/dist/runtime/prepared-call.d.ts +39 -0
- package/dist/runtime/prepared-call.js +566 -0
- package/dist/runtime/references.d.ts +7 -0
- package/dist/runtime/references.js +197 -0
- package/dist/runtime/runtime.d.ts +25 -0
- package/dist/runtime/runtime.js +1272 -0
- package/dist/runtime/schema-keywords.d.ts +2 -0
- package/dist/runtime/schema-keywords.js +30 -0
- package/dist/runtime/schema-resolver.d.ts +10 -0
- package/dist/runtime/schema-resolver.js +237 -0
- package/dist/runtime/serialize.d.ts +12 -0
- package/dist/runtime/serialize.js +877 -0
- package/dist/runtime/store.d.ts +52 -0
- package/dist/runtime/store.js +582 -0
- package/dist/runtime/strict-json.d.ts +19 -0
- package/dist/runtime/strict-json.js +539 -0
- package/dist/runtime/types.d.ts +457 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/verify-record.d.ts +6 -0
- package/dist/runtime/verify-record.js +492 -0
- package/dist/runtime/versions.d.ts +63 -0
- package/dist/runtime/versions.js +86 -0
- package/dist/safety.d.ts +28 -0
- package/dist/safety.js +60 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.js +52 -0
- package/dist/schemas.d.ts +9 -0
- package/dist/schemas.js +16 -0
- package/dist/sign.d.ts +20 -0
- package/dist/sign.js +55 -0
- package/dist/sqlite/auth.d.ts +33 -0
- package/dist/sqlite/auth.js +973 -0
- package/dist/sqlite/catalog-store.d.ts +20 -0
- package/dist/sqlite/catalog-store.js +370 -0
- package/dist/sqlite/destination-guard.d.ts +24 -0
- package/dist/sqlite/destination-guard.js +140 -0
- package/dist/sqlite/generation-store.d.ts +12 -0
- package/dist/sqlite/generation-store.js +721 -0
- package/dist/sqlite/guarded-fetch.d.ts +24 -0
- package/dist/sqlite/guarded-fetch.js +626 -0
- package/dist/sqlite/index.d.ts +5 -0
- package/dist/sqlite/index.js +5 -0
- package/dist/sqlite/oauth-resource.d.ts +2 -0
- package/dist/sqlite/oauth-resource.js +15 -0
- package/dist/sqlite/release-completion.d.ts +5 -0
- package/dist/sqlite/release-completion.js +80 -0
- package/dist/stdio/action-broker.d.ts +15 -0
- package/dist/stdio/action-broker.js +136 -0
- package/dist/stdio/authorizer.d.ts +30 -0
- package/dist/stdio/authorizer.js +484 -0
- package/dist/stdio/config.d.ts +77 -0
- package/dist/stdio/config.js +144 -0
- package/dist/stdio/exact-policy.d.ts +41 -0
- package/dist/stdio/exact-policy.js +361 -0
- package/dist/stdio/index.d.ts +6 -0
- package/dist/stdio/index.js +148 -0
- package/dist/stdio/render.d.ts +8 -0
- package/dist/stdio/render.js +253 -0
- package/dist/stdio/request-state.d.ts +18 -0
- package/dist/stdio/request-state.js +79 -0
- package/dist/stdio/search.d.ts +10 -0
- package/dist/stdio/search.js +76 -0
- package/dist/stdio/server.d.ts +29 -0
- package/dist/stdio/server.js +309 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +1 -0
- package/package.json +131 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type FileHandle } from "node:fs/promises";
|
|
2
|
+
import type { OpenApiDoc } from "../load.ts";
|
|
3
|
+
import { type PathIdentity } from "./publish.ts";
|
|
4
|
+
export interface CompilerLimits {
|
|
5
|
+
readonly maxSourceBytes: number;
|
|
6
|
+
readonly maxReferencedDocuments: number;
|
|
7
|
+
readonly maxPaths: number;
|
|
8
|
+
readonly maxOperations: number;
|
|
9
|
+
readonly maxSchemas: number;
|
|
10
|
+
readonly maxParametersPerOperation: number;
|
|
11
|
+
readonly maxPropertiesPerSchema: number;
|
|
12
|
+
readonly maxStringBytes: number;
|
|
13
|
+
readonly maxRefLength: number;
|
|
14
|
+
readonly maxJsonDepth: number;
|
|
15
|
+
readonly maxRecordBytes: number;
|
|
16
|
+
readonly maxYamlAliasExpansions: number;
|
|
17
|
+
readonly maxDocumentNodes: number;
|
|
18
|
+
readonly maxDocumentKeys: number;
|
|
19
|
+
}
|
|
20
|
+
export declare const DEFAULT_COMPILER_LIMITS: CompilerLimits;
|
|
21
|
+
/** Package-internal descriptor reader for callers that already pinned a path identity. */
|
|
22
|
+
export declare function requireOpenedFileV4(handle: FileHandle, expected: PathIdentity, label: string): Promise<bigint>;
|
|
23
|
+
/** Package-internal bounded read that consumes only an already-verified descriptor. */
|
|
24
|
+
export declare function readOpenedFileBoundedV4(handle: FileHandle, expected: PathIdentity, maxBytes: number, label: string): Promise<Uint8Array>;
|
|
25
|
+
export declare function readFileBoundedV4(path: string, maxBytes: number, label: string): Promise<Uint8Array>;
|
|
26
|
+
export declare function parseDocumentBytesV4(bytes: Uint8Array, mediaType: "json" | "yaml", limitOverrides?: Partial<CompilerLimits>): unknown;
|
|
27
|
+
export declare function parseSpecBytesV4(bytes: Uint8Array, mediaType: "json" | "yaml", limitOverrides?: Partial<CompilerLimits>): OpenApiDoc;
|
|
28
|
+
export declare function loadSpecV4(path: string, limitOverrides?: Partial<CompilerLimits>): Promise<OpenApiDoc>;
|
|
29
|
+
export declare function redactSourcePath(_path: string, sourceLabel?: string): string;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat, open } from "node:fs/promises";
|
|
3
|
+
import { extname } from "node:path";
|
|
4
|
+
import { isAlias, isMap, isScalar, isSeq, parseDocument } from "yaml";
|
|
5
|
+
import { parseJsonStrict } from "../runtime/strict-json.js";
|
|
6
|
+
import { pathIdentityFromStats, samePathIdentity, } from "./publish.js";
|
|
7
|
+
export const DEFAULT_COMPILER_LIMITS = Object.freeze({
|
|
8
|
+
maxSourceBytes: 128 * 1024 * 1024,
|
|
9
|
+
maxReferencedDocuments: 1_024,
|
|
10
|
+
maxPaths: 100_000,
|
|
11
|
+
maxOperations: 100_000,
|
|
12
|
+
maxSchemas: 50_000,
|
|
13
|
+
maxParametersPerOperation: 256,
|
|
14
|
+
maxPropertiesPerSchema: 10_000,
|
|
15
|
+
maxStringBytes: 1 * 1024 * 1024,
|
|
16
|
+
maxRefLength: 2_048,
|
|
17
|
+
maxJsonDepth: 64,
|
|
18
|
+
maxRecordBytes: 1 * 1024 * 1024,
|
|
19
|
+
maxYamlAliasExpansions: 100,
|
|
20
|
+
maxDocumentNodes: 1_000_000,
|
|
21
|
+
maxDocumentKeys: 250_000,
|
|
22
|
+
});
|
|
23
|
+
const forbiddenKeys = new Set(["__proto__", "prototype", "constructor"]);
|
|
24
|
+
/** Package-internal descriptor reader for callers that already pinned a path identity. */
|
|
25
|
+
export async function requireOpenedFileV4(handle, expected, label) {
|
|
26
|
+
const metadata = await handle.stat({ bigint: true });
|
|
27
|
+
if (!metadata.isFile() ||
|
|
28
|
+
metadata.isSymbolicLink() ||
|
|
29
|
+
metadata.nlink !== 1n ||
|
|
30
|
+
!samePathIdentity(pathIdentityFromStats(metadata), expected))
|
|
31
|
+
throw new Error(`${label} descriptor identity is invalid`);
|
|
32
|
+
return metadata.size;
|
|
33
|
+
}
|
|
34
|
+
/** Package-internal bounded read that consumes only an already-verified descriptor. */
|
|
35
|
+
export async function readOpenedFileBoundedV4(handle, expected, maxBytes, label) {
|
|
36
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
|
|
37
|
+
throw new Error(`${label} byte limit invalid`);
|
|
38
|
+
const beforeSize = await requireOpenedFileV4(handle, expected, label);
|
|
39
|
+
if (beforeSize > BigInt(maxBytes))
|
|
40
|
+
throw new Error(`${label} exceeds byte limit`);
|
|
41
|
+
const expectedBytes = Number(beforeSize);
|
|
42
|
+
const bytes = new Uint8Array(expectedBytes);
|
|
43
|
+
let offset = 0;
|
|
44
|
+
while (offset < expectedBytes) {
|
|
45
|
+
const { bytesRead } = await handle.read(bytes, offset, Math.min(64 * 1024, expectedBytes - offset), null);
|
|
46
|
+
if (bytesRead === 0)
|
|
47
|
+
throw new Error(`${label} size changed while reading`);
|
|
48
|
+
offset += bytesRead;
|
|
49
|
+
}
|
|
50
|
+
const growthProbe = new Uint8Array(1);
|
|
51
|
+
if ((await handle.read(growthProbe, 0, 1, null)).bytesRead !== 0)
|
|
52
|
+
throw new Error(`${label} size changed while reading`);
|
|
53
|
+
const afterSize = await requireOpenedFileV4(handle, expected, label);
|
|
54
|
+
if (afterSize !== beforeSize)
|
|
55
|
+
throw new Error(`${label} size changed while reading`);
|
|
56
|
+
return bytes;
|
|
57
|
+
}
|
|
58
|
+
export async function readFileBoundedV4(path, maxBytes, label) {
|
|
59
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
|
|
60
|
+
throw new Error(`${label} byte limit is invalid`);
|
|
61
|
+
if (typeof constants.O_NOFOLLOW !== "number" || constants.O_NOFOLLOW === 0)
|
|
62
|
+
throw new Error(`${label} requires O_NOFOLLOW support`);
|
|
63
|
+
const expected = await lstat(path, { bigint: true });
|
|
64
|
+
if (!expected.isFile() || expected.isSymbolicLink() || expected.nlink !== 1n)
|
|
65
|
+
throw new Error(`${label} is unsafe or not a single-link regular file`);
|
|
66
|
+
if (expected.size > BigInt(maxBytes))
|
|
67
|
+
throw new Error(`${label} exceeds byte limit`);
|
|
68
|
+
const expectedIdentity = pathIdentityFromStats(expected);
|
|
69
|
+
let handle;
|
|
70
|
+
try {
|
|
71
|
+
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code === "ELOOP")
|
|
75
|
+
throw new Error(`${label} is unsafe or symbolic`);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const before = await handle.stat({ bigint: true });
|
|
80
|
+
if (!before.isFile() ||
|
|
81
|
+
before.nlink !== 1n ||
|
|
82
|
+
!samePathIdentity(pathIdentityFromStats(before), expectedIdentity))
|
|
83
|
+
throw new Error(`${label} is unsafe or not a single-link file`);
|
|
84
|
+
if (before.size > BigInt(maxBytes))
|
|
85
|
+
throw new Error(`${label} exceeds byte limit`);
|
|
86
|
+
const chunks = [];
|
|
87
|
+
let total = 0;
|
|
88
|
+
const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1) || 1);
|
|
89
|
+
while (true) {
|
|
90
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, null);
|
|
91
|
+
if (bytesRead === 0)
|
|
92
|
+
break;
|
|
93
|
+
total += bytesRead;
|
|
94
|
+
if (total > maxBytes)
|
|
95
|
+
throw new Error(`${label} exceeds byte limit`);
|
|
96
|
+
chunks.push(Uint8Array.from(buffer.subarray(0, bytesRead)));
|
|
97
|
+
}
|
|
98
|
+
const after = await handle.stat({ bigint: true });
|
|
99
|
+
if (!samePathIdentity(pathIdentityFromStats(after), pathIdentityFromStats(before)))
|
|
100
|
+
throw new Error(`${label} identity changed while reading`);
|
|
101
|
+
const current = await lstat(path, { bigint: true }).catch(() => null);
|
|
102
|
+
if (!current ||
|
|
103
|
+
current.isSymbolicLink() ||
|
|
104
|
+
!samePathIdentity(pathIdentityFromStats(current), pathIdentityFromStats(before)))
|
|
105
|
+
throw new Error(`${label} pathname identity changed while reading`);
|
|
106
|
+
const bytes = new Uint8Array(total);
|
|
107
|
+
let offset = 0;
|
|
108
|
+
for (const chunk of chunks) {
|
|
109
|
+
bytes.set(chunk, offset);
|
|
110
|
+
offset += chunk.byteLength;
|
|
111
|
+
}
|
|
112
|
+
return bytes;
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await handle.close();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function requirePositiveLimits(limits) {
|
|
119
|
+
for (const [name, value] of Object.entries(limits)) {
|
|
120
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
121
|
+
throw new Error(`${name} is invalid`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function detachAndValidate(value, limits) {
|
|
125
|
+
const active = new WeakSet();
|
|
126
|
+
let nodes = 0;
|
|
127
|
+
let keys = 0;
|
|
128
|
+
const copy = (input, depth) => {
|
|
129
|
+
nodes += 1;
|
|
130
|
+
if (nodes > limits.maxDocumentNodes)
|
|
131
|
+
throw new Error("document exceeds node limit");
|
|
132
|
+
if (depth > limits.maxJsonDepth)
|
|
133
|
+
throw new Error("document exceeds JSON depth limit");
|
|
134
|
+
if (typeof input === "string") {
|
|
135
|
+
if (Buffer.byteLength(input) > limits.maxStringBytes)
|
|
136
|
+
throw new Error("document string exceeds byte limit");
|
|
137
|
+
return input;
|
|
138
|
+
}
|
|
139
|
+
if (input === null ||
|
|
140
|
+
typeof input === "boolean" ||
|
|
141
|
+
typeof input === "number")
|
|
142
|
+
return input;
|
|
143
|
+
if (Array.isArray(input)) {
|
|
144
|
+
if (active.has(input))
|
|
145
|
+
throw new Error("document contains an alias cycle");
|
|
146
|
+
active.add(input);
|
|
147
|
+
const result = input.map((entry) => copy(entry, depth + 1));
|
|
148
|
+
active.delete(input);
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
if (typeof input !== "object")
|
|
152
|
+
throw new Error("document contains a non-JSON value");
|
|
153
|
+
if (active.has(input))
|
|
154
|
+
throw new Error("document contains an alias cycle");
|
|
155
|
+
active.add(input);
|
|
156
|
+
const result = Object.create(null);
|
|
157
|
+
for (const key of Object.keys(input)) {
|
|
158
|
+
keys += 1;
|
|
159
|
+
if (keys > limits.maxDocumentKeys)
|
|
160
|
+
throw new Error("document exceeds key limit");
|
|
161
|
+
if (forbiddenKeys.has(key))
|
|
162
|
+
throw new Error(`document contains forbidden key ${key}`);
|
|
163
|
+
if (Buffer.byteLength(key) > limits.maxStringBytes)
|
|
164
|
+
throw new Error("document key exceeds string byte limit");
|
|
165
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
166
|
+
if (descriptor === undefined || !("value" in descriptor))
|
|
167
|
+
throw new Error("document contains an accessor");
|
|
168
|
+
result[key] = copy(descriptor.value, depth + 1);
|
|
169
|
+
}
|
|
170
|
+
active.delete(input);
|
|
171
|
+
return result;
|
|
172
|
+
};
|
|
173
|
+
return copy(value, 0);
|
|
174
|
+
}
|
|
175
|
+
function validateYamlAst(root, limits) {
|
|
176
|
+
const stack = [
|
|
177
|
+
{ node: root, depth: 0 },
|
|
178
|
+
];
|
|
179
|
+
let nodes = 0;
|
|
180
|
+
let keys = 0;
|
|
181
|
+
let aliases = 0;
|
|
182
|
+
while (stack.length > 0) {
|
|
183
|
+
const { node, depth } = stack.pop();
|
|
184
|
+
nodes += 1;
|
|
185
|
+
if (nodes > limits.maxDocumentNodes)
|
|
186
|
+
throw new Error("YAML document exceeds node limit");
|
|
187
|
+
if (depth > limits.maxJsonDepth)
|
|
188
|
+
throw new Error("YAML document exceeds depth limit");
|
|
189
|
+
if (isAlias(node)) {
|
|
190
|
+
aliases += 1;
|
|
191
|
+
if (aliases > limits.maxYamlAliasExpansions)
|
|
192
|
+
throw new Error("YAML alias expansion limit exceeded");
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (isMap(node)) {
|
|
196
|
+
for (const pair of node.items) {
|
|
197
|
+
keys += 1;
|
|
198
|
+
if (keys > limits.maxDocumentKeys)
|
|
199
|
+
throw new Error("YAML document exceeds key limit");
|
|
200
|
+
if (!isScalar(pair.key) || typeof pair.key.value !== "string")
|
|
201
|
+
throw new Error("YAML mapping keys must be strings");
|
|
202
|
+
if (forbiddenKeys.has(pair.key.value))
|
|
203
|
+
throw new Error(`document contains forbidden key ${pair.key.value}`);
|
|
204
|
+
if (Buffer.byteLength(pair.key.value) > limits.maxStringBytes)
|
|
205
|
+
throw new Error("YAML key exceeds string byte limit");
|
|
206
|
+
if (pair.value !== null)
|
|
207
|
+
stack.push({ node: pair.value, depth: depth + 1 });
|
|
208
|
+
}
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (isSeq(node)) {
|
|
212
|
+
for (const child of node.items)
|
|
213
|
+
if (child !== null)
|
|
214
|
+
stack.push({ node: child, depth: depth + 1 });
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (isScalar(node) &&
|
|
218
|
+
typeof node.value === "string" &&
|
|
219
|
+
Buffer.byteLength(node.value) > limits.maxStringBytes) {
|
|
220
|
+
throw new Error("YAML string exceeds byte limit");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
export function parseDocumentBytesV4(bytes, mediaType, limitOverrides = {}) {
|
|
225
|
+
const limits = { ...DEFAULT_COMPILER_LIMITS, ...limitOverrides };
|
|
226
|
+
requirePositiveLimits(limits);
|
|
227
|
+
if (bytes.byteLength > limits.maxSourceBytes)
|
|
228
|
+
throw new Error("aggregate source bytes exceed limit");
|
|
229
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
230
|
+
let parsed;
|
|
231
|
+
if (mediaType === "json") {
|
|
232
|
+
parsed = parseJsonStrict(text, {
|
|
233
|
+
maxBytes: limits.maxSourceBytes,
|
|
234
|
+
maxDepth: limits.maxJsonDepth,
|
|
235
|
+
maxKeys: limits.maxDocumentKeys,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const document = parseDocument(text, {
|
|
240
|
+
version: "1.2",
|
|
241
|
+
schema: "core",
|
|
242
|
+
strict: true,
|
|
243
|
+
uniqueKeys: true,
|
|
244
|
+
stringKeys: true,
|
|
245
|
+
merge: false,
|
|
246
|
+
});
|
|
247
|
+
if (document.errors.length > 0 ||
|
|
248
|
+
document.warnings.length > 0 ||
|
|
249
|
+
document.contents === null) {
|
|
250
|
+
const issue = document.errors[0] ?? document.warnings[0];
|
|
251
|
+
throw new Error(`invalid YAML document: ${issue?.message ?? "empty document"}`);
|
|
252
|
+
}
|
|
253
|
+
validateYamlAst(document.contents, limits);
|
|
254
|
+
parsed = document.toJS({
|
|
255
|
+
maxAliasCount: limits.maxYamlAliasExpansions,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return detachAndValidate(parsed, limits);
|
|
259
|
+
}
|
|
260
|
+
export function parseSpecBytesV4(bytes, mediaType, limitOverrides = {}) {
|
|
261
|
+
const detached = parseDocumentBytesV4(bytes, mediaType, limitOverrides);
|
|
262
|
+
if (!detached ||
|
|
263
|
+
typeof detached !== "object" ||
|
|
264
|
+
typeof detached.openapi !== "string" ||
|
|
265
|
+
!/^3\.(?:0|1)\.[0-9]+$/.test(detached.openapi) ||
|
|
266
|
+
typeof detached.paths !== "object" ||
|
|
267
|
+
detached.paths === null ||
|
|
268
|
+
Array.isArray(detached.paths)) {
|
|
269
|
+
throw new Error("unsupported OpenAPI version or missing paths object");
|
|
270
|
+
}
|
|
271
|
+
return detached;
|
|
272
|
+
}
|
|
273
|
+
export async function loadSpecV4(path, limitOverrides = {}) {
|
|
274
|
+
const limits = { ...DEFAULT_COMPILER_LIMITS, ...limitOverrides };
|
|
275
|
+
requirePositiveLimits(limits);
|
|
276
|
+
const extension = extname(path).toLowerCase();
|
|
277
|
+
const mediaType = extension === ".json"
|
|
278
|
+
? "json"
|
|
279
|
+
: extension === ".yaml" || extension === ".yml"
|
|
280
|
+
? "yaml"
|
|
281
|
+
: null;
|
|
282
|
+
if (mediaType === null)
|
|
283
|
+
throw new Error("unsupported OpenAPI media type or file extension");
|
|
284
|
+
let bytes;
|
|
285
|
+
try {
|
|
286
|
+
bytes = await readFileBoundedV4(path, limits.maxSourceBytes, "source document");
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (error instanceof Error && error.message.includes("exceeds byte limit"))
|
|
290
|
+
throw new Error("source bytes exceed limit");
|
|
291
|
+
throw new Error("source document could not be read");
|
|
292
|
+
}
|
|
293
|
+
return parseSpecBytesV4(bytes, mediaType, limits);
|
|
294
|
+
}
|
|
295
|
+
export function redactSourcePath(_path, sourceLabel = "source document") {
|
|
296
|
+
return sourceLabel;
|
|
297
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CatalogId, ManifestEnvelope, ReleaseId, ReleaseManifestV4, Sha256, TypedRecordId } from "../runtime/types.ts";
|
|
2
|
+
export interface ManifestBuilderInput {
|
|
3
|
+
catalogId: CatalogId;
|
|
4
|
+
releaseId: ReleaseId;
|
|
5
|
+
generation: number;
|
|
6
|
+
issuer: string;
|
|
7
|
+
keyId: string;
|
|
8
|
+
policyId: string;
|
|
9
|
+
allowedOrigins: readonly string[];
|
|
10
|
+
compiledAt: string;
|
|
11
|
+
compilerVersion: string;
|
|
12
|
+
sourceUri: string;
|
|
13
|
+
sourceRevision: string;
|
|
14
|
+
sourceContentSha256: Sha256;
|
|
15
|
+
referenceGraphDigest: Sha256;
|
|
16
|
+
records: Readonly<Record<TypedRecordId, Sha256>>;
|
|
17
|
+
}
|
|
18
|
+
export declare function buildReleaseManifestV4(input: ManifestBuilderInput): ReleaseManifestV4;
|
|
19
|
+
export declare function buildManifestEnvelopeV4(manifest: ReleaseManifestV4, privateKeyPem: string): ManifestEnvelope;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ARTIFACT_FORMAT_VERSION, RUNTIME_CONTRACT_VERSION, } from "../runtime/versions.js";
|
|
2
|
+
import { signReleaseManifestV4 } from "../sign.js";
|
|
3
|
+
export function buildReleaseManifestV4(input) {
|
|
4
|
+
return {
|
|
5
|
+
format: ARTIFACT_FORMAT_VERSION,
|
|
6
|
+
contract: RUNTIME_CONTRACT_VERSION,
|
|
7
|
+
catalogId: input.catalogId,
|
|
8
|
+
releaseId: input.releaseId,
|
|
9
|
+
generation: input.generation,
|
|
10
|
+
issuer: input.issuer,
|
|
11
|
+
keyId: input.keyId,
|
|
12
|
+
policyId: input.policyId,
|
|
13
|
+
allowedOrigins: [...input.allowedOrigins].sort(),
|
|
14
|
+
compiledAt: input.compiledAt,
|
|
15
|
+
compilerVersion: input.compilerVersion,
|
|
16
|
+
source: {
|
|
17
|
+
uri: input.sourceUri,
|
|
18
|
+
revision: input.sourceRevision,
|
|
19
|
+
contentSha256: input.sourceContentSha256,
|
|
20
|
+
referenceGraphDigest: input.referenceGraphDigest,
|
|
21
|
+
},
|
|
22
|
+
records: Object.fromEntries(Object.entries(input.records).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function buildManifestEnvelopeV4(manifest, privateKeyPem) {
|
|
26
|
+
const signed = signReleaseManifestV4(manifest, manifest.keyId, privateKeyPem);
|
|
27
|
+
return { manifestJson: signed.manifestJson, signature: signed.signature };
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ManifestEnvelope, ReleaseManifestV4 } from "../runtime/types.ts";
|
|
2
|
+
export interface CompiledReleasePaths {
|
|
3
|
+
readonly directory: string;
|
|
4
|
+
readonly sqlite: string;
|
|
5
|
+
readonly signature: string;
|
|
6
|
+
readonly manifest: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CompiledRelease {
|
|
9
|
+
readonly manifest: ReleaseManifestV4;
|
|
10
|
+
readonly envelope: ManifestEnvelope;
|
|
11
|
+
readonly paths: CompiledReleasePaths;
|
|
12
|
+
}
|
|
13
|
+
type StageFileKind = "sqlite" | "signature" | "manifest";
|
|
14
|
+
type StageFileRecord<T> = Readonly<Record<StageFileKind, T>>;
|
|
15
|
+
export interface PathIdentity {
|
|
16
|
+
readonly dev: bigint;
|
|
17
|
+
readonly ino: bigint;
|
|
18
|
+
readonly birthtimeNs: bigint;
|
|
19
|
+
}
|
|
20
|
+
export interface CompiledReleaseOwnership {
|
|
21
|
+
readonly directory: PathIdentity;
|
|
22
|
+
readonly sqlite: PathIdentity;
|
|
23
|
+
readonly signature: PathIdentity;
|
|
24
|
+
readonly manifest: PathIdentity;
|
|
25
|
+
}
|
|
26
|
+
/** Package-private construction gate; intentionally absent from the compiler barrel. */
|
|
27
|
+
export declare function registerCompiledRelease(compiled: CompiledRelease, outDir: string, digests: StageFileRecord<string>, sizes: StageFileRecord<number>, ownership: CompiledReleaseOwnership): CompiledRelease;
|
|
28
|
+
export declare function pathIdentityFromStats(stat: {
|
|
29
|
+
readonly dev: bigint;
|
|
30
|
+
readonly ino: bigint;
|
|
31
|
+
readonly birthtimeNs: bigint;
|
|
32
|
+
}): PathIdentity;
|
|
33
|
+
export declare function samePathIdentity(left: PathIdentity, right: PathIdentity): boolean;
|
|
34
|
+
export declare function capturePathIdentity(path: string, kind: "directory" | "file"): Promise<PathIdentity>;
|
|
35
|
+
export declare function cleanupOwnedStage(paths: CompiledReleasePaths, ownership: Partial<CompiledReleaseOwnership>): Promise<void>;
|
|
36
|
+
export type PublishCheckpoint = "before-sqlite-validation-open" | "before-signature-validation-open" | "before-manifest-validation-open" | "before-sqlite-validation-read" | "before-signature-validation-read" | "before-manifest-validation-read" | "payload-published" | "signature-published" | "manifest-published";
|
|
37
|
+
type Checkpoint = (checkpoint: PublishCheckpoint) => void | Promise<void>;
|
|
38
|
+
export declare function publishReleaseWithCheckpoint(compiled: CompiledRelease, target: {
|
|
39
|
+
readonly directory: string;
|
|
40
|
+
}, checkpoint?: Checkpoint): Promise<void>;
|
|
41
|
+
export declare function publishRelease(compiled: CompiledRelease, target: {
|
|
42
|
+
readonly directory: string;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
export declare function discardCompiledRelease(compiled: CompiledRelease): Promise<void>;
|
|
45
|
+
export {};
|