@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,347 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { open, realpath } from "node:fs/promises";
|
|
4
|
+
import { extname, isAbsolute, posix, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { sha256 } from "../runtime/digest.js";
|
|
6
|
+
import { parseJsonStrict } from "../runtime/strict-json.js";
|
|
7
|
+
import { DEFAULT_COMPILER_LIMITS, parseDocumentBytesV4, readFileBoundedV4, readOpenedFileBoundedV4, requireOpenedFileV4, } from "./load-v4.js";
|
|
8
|
+
import { capturePathIdentity, samePathIdentity, } from "./publish.js";
|
|
9
|
+
const digestPattern = /^[0-9a-f]{64}$/;
|
|
10
|
+
const unreserved = /^[A-Za-z0-9._~-]$/;
|
|
11
|
+
function normalizeEscapes(value) {
|
|
12
|
+
let output = "";
|
|
13
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
14
|
+
if (value[index] !== "%") {
|
|
15
|
+
output += value[index];
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const pair = value.slice(index + 1, index + 3);
|
|
19
|
+
if (!/^[0-9A-Fa-f]{2}$/.test(pair))
|
|
20
|
+
throw new Error("URI contains an invalid percent escape");
|
|
21
|
+
const character = String.fromCharCode(Number.parseInt(pair, 16));
|
|
22
|
+
output += unreserved.test(character) ? character : `%${pair.toUpperCase()}`;
|
|
23
|
+
index += 2;
|
|
24
|
+
}
|
|
25
|
+
return output;
|
|
26
|
+
}
|
|
27
|
+
export function normalizeHttpsUri(value) {
|
|
28
|
+
if (value.length > 2048 || !/^[\x21-\x7e]+$/.test(value))
|
|
29
|
+
throw new Error("source URI is invalid");
|
|
30
|
+
normalizeEscapes(value);
|
|
31
|
+
let url;
|
|
32
|
+
try {
|
|
33
|
+
url = new URL(value);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error("source URI is invalid");
|
|
37
|
+
}
|
|
38
|
+
if (url.protocol !== "https:" ||
|
|
39
|
+
url.username !== "" ||
|
|
40
|
+
url.password !== "" ||
|
|
41
|
+
url.search !== "" ||
|
|
42
|
+
url.hash !== "")
|
|
43
|
+
throw new Error("source URI must be credential-free HTTPS without query or fragment");
|
|
44
|
+
url.pathname = normalizeEscapes(url.pathname || "/");
|
|
45
|
+
return url.href;
|
|
46
|
+
}
|
|
47
|
+
export function sourceUriFromLabel(label) {
|
|
48
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(label) ||
|
|
49
|
+
label === "." ||
|
|
50
|
+
label === "..") {
|
|
51
|
+
throw new Error("source label is invalid");
|
|
52
|
+
}
|
|
53
|
+
return `urn:openapi-source:${label}`;
|
|
54
|
+
}
|
|
55
|
+
function normalizeRelativeGraphUri(value, base) {
|
|
56
|
+
if (value.startsWith("/") ||
|
|
57
|
+
value.startsWith("//") ||
|
|
58
|
+
value.includes("?") ||
|
|
59
|
+
value.includes("#")) {
|
|
60
|
+
throw new Error("relative reference-map URI is invalid");
|
|
61
|
+
}
|
|
62
|
+
const joined = base === undefined ? value : posix.join(posix.dirname(base), value);
|
|
63
|
+
const normalized = normalizeEscapes(joined);
|
|
64
|
+
if (!normalized ||
|
|
65
|
+
normalized.startsWith("/") ||
|
|
66
|
+
normalized
|
|
67
|
+
.split("/")
|
|
68
|
+
.some((part) => part === "." || part === ".." || part === "")) {
|
|
69
|
+
throw new Error("relative reference-map URI traverses or is noncanonical");
|
|
70
|
+
}
|
|
71
|
+
return normalized;
|
|
72
|
+
}
|
|
73
|
+
export function normalizeGraphUri(value, baseUri) {
|
|
74
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(value))
|
|
75
|
+
return normalizeHttpsUri(value);
|
|
76
|
+
if (value.startsWith("//"))
|
|
77
|
+
throw new Error("network-path references are unsupported");
|
|
78
|
+
if (baseUri.startsWith("https:"))
|
|
79
|
+
return normalizeHttpsUri(new URL(value, baseUri).href);
|
|
80
|
+
return normalizeRelativeGraphUri(value, baseUri.startsWith("urn:") ? undefined : baseUri);
|
|
81
|
+
}
|
|
82
|
+
function exactObject(value, keys, label) {
|
|
83
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
84
|
+
throw new Error(`${label} must be an object`);
|
|
85
|
+
const actual = Object.keys(value).sort();
|
|
86
|
+
const expected = [...keys].sort();
|
|
87
|
+
if (actual.length !== expected.length ||
|
|
88
|
+
actual.some((key, index) => key !== expected[index]))
|
|
89
|
+
throw new Error(`${label} shape is invalid`);
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
export async function loadReferenceMap(mapPath, sourceUri, limits = DEFAULT_COMPILER_LIMITS) {
|
|
93
|
+
let text;
|
|
94
|
+
try {
|
|
95
|
+
const bytes = await readFileBoundedV4(mapPath, Math.min(limits.maxSourceBytes, 8 * 1024 * 1024), "reference map");
|
|
96
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new Error("reference map could not be read");
|
|
100
|
+
}
|
|
101
|
+
const root = exactObject(parseJsonStrict(text, {
|
|
102
|
+
maxBytes: Math.min(limits.maxSourceBytes, 8 * 1024 * 1024),
|
|
103
|
+
maxDepth: limits.maxJsonDepth,
|
|
104
|
+
maxKeys: limits.maxDocumentKeys,
|
|
105
|
+
}), ["entries", "version"], "reference map");
|
|
106
|
+
if (root.version !== 1 || !Array.isArray(root.entries))
|
|
107
|
+
throw new Error("reference map version or entries are invalid");
|
|
108
|
+
if (root.entries.length > limits.maxReferencedDocuments)
|
|
109
|
+
throw new Error("referenced document count exceeds limit");
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
const entries = root.entries.map((raw, index) => {
|
|
112
|
+
const entry = exactObject(raw, ["file", "sha256", "uri"], `reference map entry ${index}`);
|
|
113
|
+
if (typeof entry.uri !== "string" ||
|
|
114
|
+
typeof entry.file !== "string" ||
|
|
115
|
+
typeof entry.sha256 !== "string" ||
|
|
116
|
+
!digestPattern.test(entry.sha256)) {
|
|
117
|
+
throw new Error(`reference map entry ${index} is invalid`);
|
|
118
|
+
}
|
|
119
|
+
const uri = normalizeGraphUri(entry.uri, sourceUri);
|
|
120
|
+
if (seen.has(uri))
|
|
121
|
+
throw new Error("duplicate normalized reference-map URI");
|
|
122
|
+
seen.add(uri);
|
|
123
|
+
if (isAbsolute(entry.file) ||
|
|
124
|
+
entry.file.includes("\\") ||
|
|
125
|
+
entry.file
|
|
126
|
+
.split("/")
|
|
127
|
+
.some((part) => part === "" || part === "." || part === "..")) {
|
|
128
|
+
throw new Error("reference-map file must be a contained POSIX-relative path");
|
|
129
|
+
}
|
|
130
|
+
return { uri, file: entry.file, sha256: entry.sha256 };
|
|
131
|
+
});
|
|
132
|
+
entries.sort((left, right) => left.uri < right.uri ? -1 : left.uri > right.uri ? 1 : 0);
|
|
133
|
+
return { version: 1, entries };
|
|
134
|
+
}
|
|
135
|
+
export async function referenceGraphDigest(map) {
|
|
136
|
+
return sha256("knitli.openapi-mcp.reference-graph.v1", map.entries.map(({ uri, sha256: digest }) => ({
|
|
137
|
+
uri,
|
|
138
|
+
sha256: digest,
|
|
139
|
+
})));
|
|
140
|
+
}
|
|
141
|
+
async function requireReferenceRoot(root, identity) {
|
|
142
|
+
const current = await capturePathIdentity(root, "directory").catch(() => null);
|
|
143
|
+
if (!current || !samePathIdentity(current, identity))
|
|
144
|
+
throw new Error("reference root identity was lost");
|
|
145
|
+
}
|
|
146
|
+
function requireContainedReference(root, actual) {
|
|
147
|
+
const relation = relative(root, actual);
|
|
148
|
+
if (relation === "" ||
|
|
149
|
+
relation === ".." ||
|
|
150
|
+
relation.startsWith(`..${sep}`) ||
|
|
151
|
+
isAbsolute(relation))
|
|
152
|
+
throw new Error("reference-map file escapes reference root");
|
|
153
|
+
}
|
|
154
|
+
async function captureReferenceDirectories(root, path) {
|
|
155
|
+
const relation = relative(root, path);
|
|
156
|
+
const directories = [];
|
|
157
|
+
let current = root;
|
|
158
|
+
for (const component of relation.split(sep).slice(0, -1)) {
|
|
159
|
+
current = resolve(current, component);
|
|
160
|
+
const identity = await capturePathIdentity(current, "directory").catch(() => {
|
|
161
|
+
throw new Error("referenced document directory identity was lost");
|
|
162
|
+
});
|
|
163
|
+
directories.push({ path: current, identity });
|
|
164
|
+
}
|
|
165
|
+
return directories;
|
|
166
|
+
}
|
|
167
|
+
async function requireReferenceDirectories(directories) {
|
|
168
|
+
for (const directory of directories) {
|
|
169
|
+
const current = await capturePathIdentity(directory.path, "directory").catch(() => null);
|
|
170
|
+
if (!current || !samePathIdentity(current, directory.identity))
|
|
171
|
+
throw new Error("referenced document directory identity was lost");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function captureReferencePath(root, path) {
|
|
175
|
+
const directories = await captureReferenceDirectories(root, path);
|
|
176
|
+
const leaf = await capturePathIdentity(path, "file").catch(() => {
|
|
177
|
+
throw new Error("referenced document could not be read");
|
|
178
|
+
});
|
|
179
|
+
return { directories, leaf };
|
|
180
|
+
}
|
|
181
|
+
async function requireReferencePath(path, expected) {
|
|
182
|
+
await requireReferenceDirectories(expected.directories);
|
|
183
|
+
const current = await capturePathIdentity(path, "file").catch(() => null);
|
|
184
|
+
if (!current || !samePathIdentity(current, expected.leaf))
|
|
185
|
+
throw new Error("referenced document identity was lost");
|
|
186
|
+
}
|
|
187
|
+
let referenceReadTestCheckpoint;
|
|
188
|
+
async function readContainedReference(root, rootIdentity, candidate, maxBytes) {
|
|
189
|
+
await requireReferenceRoot(root, rootIdentity);
|
|
190
|
+
const actual = await realpath(candidate).catch(() => {
|
|
191
|
+
throw new Error("reference-map file is unavailable");
|
|
192
|
+
});
|
|
193
|
+
requireContainedReference(root, actual);
|
|
194
|
+
const candidatePath = await captureReferencePath(root, candidate);
|
|
195
|
+
const actualPath = await captureReferencePath(root, actual);
|
|
196
|
+
if (!samePathIdentity(candidatePath.leaf, actualPath.leaf))
|
|
197
|
+
throw new Error("referenced document path identity was lost");
|
|
198
|
+
await requireReferenceRoot(root, rootIdentity);
|
|
199
|
+
await requireReferencePath(candidate, candidatePath);
|
|
200
|
+
await requireReferencePath(actual, actualPath);
|
|
201
|
+
await referenceReadTestCheckpoint?.beforeOpen();
|
|
202
|
+
if (typeof constants.O_NOFOLLOW !== "number" || constants.O_NOFOLLOW === 0)
|
|
203
|
+
throw new Error("referenced document requires O_NOFOLLOW support");
|
|
204
|
+
const handle = await open(actual, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
205
|
+
try {
|
|
206
|
+
await requireOpenedFileV4(handle, actualPath.leaf, "referenced document").catch(() => {
|
|
207
|
+
throw new Error("referenced document descriptor identity was lost");
|
|
208
|
+
});
|
|
209
|
+
await requireReferenceRoot(root, rootIdentity);
|
|
210
|
+
await requireReferencePath(candidate, candidatePath);
|
|
211
|
+
await requireReferencePath(actual, actualPath);
|
|
212
|
+
referenceReadTestCheckpoint?.onRead();
|
|
213
|
+
const bytes = await readOpenedFileBoundedV4(handle, actualPath.leaf, maxBytes, "referenced document");
|
|
214
|
+
await requireReferenceRoot(root, rootIdentity);
|
|
215
|
+
await requireReferencePath(candidate, candidatePath);
|
|
216
|
+
const currentActual = await realpath(candidate).catch(() => {
|
|
217
|
+
throw new Error("referenced document identity was lost");
|
|
218
|
+
});
|
|
219
|
+
if (currentActual !== actual)
|
|
220
|
+
throw new Error("referenced document path identity was lost");
|
|
221
|
+
requireContainedReference(root, currentActual);
|
|
222
|
+
await requireReferencePath(actual, actualPath);
|
|
223
|
+
await requireReferenceRoot(root, rootIdentity);
|
|
224
|
+
await requireReferencePath(candidate, candidatePath);
|
|
225
|
+
await requireReferencePath(actual, actualPath);
|
|
226
|
+
return bytes;
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
await handle.close();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
export class ReferenceGraphV4 {
|
|
233
|
+
sourceUri;
|
|
234
|
+
map;
|
|
235
|
+
#entries;
|
|
236
|
+
#cache = new Map();
|
|
237
|
+
#root;
|
|
238
|
+
#rootIdentity;
|
|
239
|
+
#resolver;
|
|
240
|
+
#limits;
|
|
241
|
+
#bytesUsed;
|
|
242
|
+
/** @internal Test-only hook; this module is not exported by the package. */
|
|
243
|
+
static setReferenceReadCheckpointForTesting(checkpoint) {
|
|
244
|
+
referenceReadTestCheckpoint = checkpoint;
|
|
245
|
+
}
|
|
246
|
+
constructor(sourceUri, map, root, rootIdentity, resolver, limits, rootBytes) {
|
|
247
|
+
this.sourceUri = sourceUri;
|
|
248
|
+
this.map = map;
|
|
249
|
+
this.#entries = new Map(map.entries.map((entry) => [entry.uri, entry]));
|
|
250
|
+
this.#root = root;
|
|
251
|
+
this.#rootIdentity = rootIdentity;
|
|
252
|
+
this.#resolver = resolver;
|
|
253
|
+
this.#limits = limits;
|
|
254
|
+
this.#bytesUsed = rootBytes;
|
|
255
|
+
}
|
|
256
|
+
static async create(options) {
|
|
257
|
+
const map = options.mapPath
|
|
258
|
+
? await loadReferenceMap(options.mapPath, options.sourceUri, options.limits)
|
|
259
|
+
: { version: 1, entries: [] };
|
|
260
|
+
if ((map.entries.length > 0 || options.resolver) &&
|
|
261
|
+
!options.referenceRoot &&
|
|
262
|
+
!options.resolver) {
|
|
263
|
+
throw new Error("reference root is required for a reference map");
|
|
264
|
+
}
|
|
265
|
+
const root = options.referenceRoot
|
|
266
|
+
? await realpath(options.referenceRoot).catch(() => {
|
|
267
|
+
throw new Error("reference root is invalid");
|
|
268
|
+
})
|
|
269
|
+
: null;
|
|
270
|
+
const rootIdentity = root
|
|
271
|
+
? await capturePathIdentity(root, "directory").catch(() => {
|
|
272
|
+
throw new Error("reference root is invalid");
|
|
273
|
+
})
|
|
274
|
+
: null;
|
|
275
|
+
return new ReferenceGraphV4(options.sourceUri, map, root, rootIdentity, options.resolver, options.limits, options.rootBytes);
|
|
276
|
+
}
|
|
277
|
+
async resolve(rawRef, fromUri, depth) {
|
|
278
|
+
if (Buffer.byteLength(rawRef) > this.#limits.maxRefLength)
|
|
279
|
+
throw new Error("reference exceeds ref length limit");
|
|
280
|
+
if (depth > this.#limits.maxJsonDepth)
|
|
281
|
+
throw new Error("reference depth exceeds limit");
|
|
282
|
+
const hashIndex = rawRef.indexOf("#");
|
|
283
|
+
const rawDocument = hashIndex < 0 ? rawRef : rawRef.slice(0, hashIndex);
|
|
284
|
+
const pointer = hashIndex < 0 ? "#" : `#${rawRef.slice(hashIndex + 1)}`;
|
|
285
|
+
if (rawDocument === "")
|
|
286
|
+
throw new Error("local reference must be resolved against its current document");
|
|
287
|
+
const uri = normalizeGraphUri(rawDocument, fromUri);
|
|
288
|
+
const entry = this.#entries.get(uri);
|
|
289
|
+
if (!entry)
|
|
290
|
+
throw new Error("external reference is absent from the reference map");
|
|
291
|
+
let resolved = this.#cache.get(uri);
|
|
292
|
+
if (!resolved) {
|
|
293
|
+
let bytes;
|
|
294
|
+
let mediaType;
|
|
295
|
+
if (this.#resolver) {
|
|
296
|
+
const result = await this.#resolver.resolve(uri);
|
|
297
|
+
if (result.bytes.byteLength >
|
|
298
|
+
this.#limits.maxSourceBytes - this.#bytesUsed)
|
|
299
|
+
throw new Error("aggregate source bytes exceed limit");
|
|
300
|
+
bytes = new Uint8Array(result.bytes);
|
|
301
|
+
mediaType = result.mediaType;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
if (!this.#root || !this.#rootIdentity)
|
|
305
|
+
throw new Error("reference root is required");
|
|
306
|
+
const candidate = resolve(this.#root, ...entry.file.split("/"));
|
|
307
|
+
bytes = await readContainedReference(this.#root, this.#rootIdentity, candidate, this.#limits.maxSourceBytes - this.#bytesUsed).catch((error) => {
|
|
308
|
+
if (error instanceof Error &&
|
|
309
|
+
error.message.includes("exceeds byte limit"))
|
|
310
|
+
throw new Error("aggregate source bytes exceed limit");
|
|
311
|
+
if (error instanceof Error &&
|
|
312
|
+
(/^reference root identity was lost$/.test(error.message) ||
|
|
313
|
+
/^reference-map file escapes reference root$/.test(error.message) ||
|
|
314
|
+
/^referenced document (path |directory |descriptor )?identity was lost$/.test(error.message)))
|
|
315
|
+
throw error;
|
|
316
|
+
throw new Error("referenced document could not be read");
|
|
317
|
+
});
|
|
318
|
+
const extension = extname(entry.file).toLowerCase();
|
|
319
|
+
mediaType =
|
|
320
|
+
extension === ".json"
|
|
321
|
+
? "json"
|
|
322
|
+
: extension === ".yaml" || extension === ".yml"
|
|
323
|
+
? "yaml"
|
|
324
|
+
: (() => {
|
|
325
|
+
throw new Error("unsupported referenced media type");
|
|
326
|
+
})();
|
|
327
|
+
}
|
|
328
|
+
const actualDigest = createHash("sha256").update(bytes).digest("hex");
|
|
329
|
+
if (actualDigest !== entry.sha256)
|
|
330
|
+
throw new Error("reference-map content digest mismatch");
|
|
331
|
+
if (this.#bytesUsed + bytes.byteLength > this.#limits.maxSourceBytes)
|
|
332
|
+
throw new Error("aggregate source bytes exceed limit");
|
|
333
|
+
this.#bytesUsed += bytes.byteLength;
|
|
334
|
+
const document = parseDocumentBytesV4(bytes, mediaType, {
|
|
335
|
+
...this.#limits,
|
|
336
|
+
maxSourceBytes: bytes.byteLength,
|
|
337
|
+
});
|
|
338
|
+
if (typeof document !== "object" ||
|
|
339
|
+
document === null ||
|
|
340
|
+
Array.isArray(document))
|
|
341
|
+
throw new Error("referenced document must be an object");
|
|
342
|
+
resolved = { uri, document: document };
|
|
343
|
+
this.#cache.set(uri, resolved);
|
|
344
|
+
}
|
|
345
|
+
return { uri, pointer, document: resolved.document };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
/** Compatibility-era schema name for immutable v4/v5 SQLite and D1 transport. */
|
|
3
|
+
export declare const RELEASE_SCHEMA_V4 = "\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE release_metadata (\n catalog_id TEXT NOT NULL COLLATE BINARY,\n release_id TEXT NOT NULL COLLATE BINARY,\n format INTEGER NOT NULL CHECK (format IN (4, 5)),\n contract INTEGER NOT NULL CHECK (contract = 1),\n generation INTEGER NOT NULL CHECK (generation >= 0),\n issuer TEXT NOT NULL COLLATE BINARY,\n key_id TEXT NOT NULL COLLATE BINARY,\n policy_id TEXT NOT NULL COLLATE BINARY,\n allowed_origins_json TEXT NOT NULL,\n compiled_at TEXT NOT NULL,\n compiler_version TEXT NOT NULL,\n source_uri TEXT NOT NULL,\n source_revision TEXT NOT NULL,\n source_content_sha256 TEXT NOT NULL CHECK (\n length(source_content_sha256) = 64\n AND source_content_sha256 NOT GLOB '*[^0-9a-f]*'\n ),\n reference_graph_digest TEXT NOT NULL CHECK (\n length(reference_graph_digest) = 64\n AND reference_graph_digest NOT GLOB '*[^0-9a-f]*'\n ),\n manifest_json TEXT NOT NULL,\n signature_algorithm TEXT NOT NULL CHECK (signature_algorithm = 'Ed25519'),\n signature_key_id TEXT NOT NULL COLLATE BINARY,\n signature TEXT NOT NULL,\n PRIMARY KEY (catalog_id, release_id)\n) WITHOUT ROWID;\n\nCREATE TABLE operations (\n catalog_id TEXT NOT NULL COLLATE BINARY,\n release_id TEXT NOT NULL COLLATE BINARY,\n record_id TEXT NOT NULL COLLATE BINARY CHECK (\n record_id GLOB 'operation:*' AND length(record_id) <= 651\n ),\n record_json TEXT NOT NULL,\n logical_digest TEXT NOT NULL CHECK (\n length(logical_digest) = 64\n AND logical_digest NOT GLOB '*[^0-9a-f]*'\n ),\n api TEXT NOT NULL COLLATE BINARY,\n operation_id TEXT NOT NULL COLLATE BINARY,\n summary TEXT,\n path TEXT NOT NULL,\n search_text TEXT NOT NULL,\n PRIMARY KEY (catalog_id, release_id, record_id),\n FOREIGN KEY (catalog_id, release_id)\n REFERENCES release_metadata(catalog_id, release_id)\n ON UPDATE RESTRICT ON DELETE RESTRICT\n);\n\nCREATE INDEX operations_release_api\n ON operations (catalog_id, release_id, api, record_id);\n\nCREATE VIRTUAL TABLE operations_fts USING fts5(\n record_id, operation_id, summary, path, api, search_text,\n content='operations', content_rowid='rowid'\n);\n\nCREATE TABLE schemas (\n catalog_id TEXT NOT NULL COLLATE BINARY,\n release_id TEXT NOT NULL COLLATE BINARY,\n record_id TEXT NOT NULL COLLATE BINARY CHECK (\n record_id GLOB 'schema:*' AND length(record_id) <= 669\n ),\n record_json TEXT NOT NULL,\n logical_digest TEXT NOT NULL CHECK (\n length(logical_digest) = 64\n AND logical_digest NOT GLOB '*[^0-9a-f]*'\n ),\n PRIMARY KEY (catalog_id, release_id, record_id),\n FOREIGN KEY (catalog_id, release_id)\n REFERENCES release_metadata(catalog_id, release_id)\n ON UPDATE RESTRICT ON DELETE RESTRICT\n) WITHOUT ROWID;\n";
|
|
4
|
+
export declare function createReleaseSchemaV4(database: DatabaseSync): void;
|
|
5
|
+
export declare function populateOperationsFtsV4(database: DatabaseSync): void;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Compatibility-era schema name for immutable v4/v5 SQLite and D1 transport. */
|
|
2
|
+
export const RELEASE_SCHEMA_V4 = `
|
|
3
|
+
PRAGMA foreign_keys = ON;
|
|
4
|
+
|
|
5
|
+
CREATE TABLE release_metadata (
|
|
6
|
+
catalog_id TEXT NOT NULL COLLATE BINARY,
|
|
7
|
+
release_id TEXT NOT NULL COLLATE BINARY,
|
|
8
|
+
format INTEGER NOT NULL CHECK (format IN (4, 5)),
|
|
9
|
+
contract INTEGER NOT NULL CHECK (contract = 1),
|
|
10
|
+
generation INTEGER NOT NULL CHECK (generation >= 0),
|
|
11
|
+
issuer TEXT NOT NULL COLLATE BINARY,
|
|
12
|
+
key_id TEXT NOT NULL COLLATE BINARY,
|
|
13
|
+
policy_id TEXT NOT NULL COLLATE BINARY,
|
|
14
|
+
allowed_origins_json TEXT NOT NULL,
|
|
15
|
+
compiled_at TEXT NOT NULL,
|
|
16
|
+
compiler_version TEXT NOT NULL,
|
|
17
|
+
source_uri TEXT NOT NULL,
|
|
18
|
+
source_revision TEXT NOT NULL,
|
|
19
|
+
source_content_sha256 TEXT NOT NULL CHECK (
|
|
20
|
+
length(source_content_sha256) = 64
|
|
21
|
+
AND source_content_sha256 NOT GLOB '*[^0-9a-f]*'
|
|
22
|
+
),
|
|
23
|
+
reference_graph_digest TEXT NOT NULL CHECK (
|
|
24
|
+
length(reference_graph_digest) = 64
|
|
25
|
+
AND reference_graph_digest NOT GLOB '*[^0-9a-f]*'
|
|
26
|
+
),
|
|
27
|
+
manifest_json TEXT NOT NULL,
|
|
28
|
+
signature_algorithm TEXT NOT NULL CHECK (signature_algorithm = 'Ed25519'),
|
|
29
|
+
signature_key_id TEXT NOT NULL COLLATE BINARY,
|
|
30
|
+
signature TEXT NOT NULL,
|
|
31
|
+
PRIMARY KEY (catalog_id, release_id)
|
|
32
|
+
) WITHOUT ROWID;
|
|
33
|
+
|
|
34
|
+
CREATE TABLE operations (
|
|
35
|
+
catalog_id TEXT NOT NULL COLLATE BINARY,
|
|
36
|
+
release_id TEXT NOT NULL COLLATE BINARY,
|
|
37
|
+
record_id TEXT NOT NULL COLLATE BINARY CHECK (
|
|
38
|
+
record_id GLOB 'operation:*' AND length(record_id) <= 651
|
|
39
|
+
),
|
|
40
|
+
record_json TEXT NOT NULL,
|
|
41
|
+
logical_digest TEXT NOT NULL CHECK (
|
|
42
|
+
length(logical_digest) = 64
|
|
43
|
+
AND logical_digest NOT GLOB '*[^0-9a-f]*'
|
|
44
|
+
),
|
|
45
|
+
api TEXT NOT NULL COLLATE BINARY,
|
|
46
|
+
operation_id TEXT NOT NULL COLLATE BINARY,
|
|
47
|
+
summary TEXT,
|
|
48
|
+
path TEXT NOT NULL,
|
|
49
|
+
search_text TEXT NOT NULL,
|
|
50
|
+
PRIMARY KEY (catalog_id, release_id, record_id),
|
|
51
|
+
FOREIGN KEY (catalog_id, release_id)
|
|
52
|
+
REFERENCES release_metadata(catalog_id, release_id)
|
|
53
|
+
ON UPDATE RESTRICT ON DELETE RESTRICT
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE INDEX operations_release_api
|
|
57
|
+
ON operations (catalog_id, release_id, api, record_id);
|
|
58
|
+
|
|
59
|
+
CREATE VIRTUAL TABLE operations_fts USING fts5(
|
|
60
|
+
record_id, operation_id, summary, path, api, search_text,
|
|
61
|
+
content='operations', content_rowid='rowid'
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE schemas (
|
|
65
|
+
catalog_id TEXT NOT NULL COLLATE BINARY,
|
|
66
|
+
release_id TEXT NOT NULL COLLATE BINARY,
|
|
67
|
+
record_id TEXT NOT NULL COLLATE BINARY CHECK (
|
|
68
|
+
record_id GLOB 'schema:*' AND length(record_id) <= 669
|
|
69
|
+
),
|
|
70
|
+
record_json TEXT NOT NULL,
|
|
71
|
+
logical_digest TEXT NOT NULL CHECK (
|
|
72
|
+
length(logical_digest) = 64
|
|
73
|
+
AND logical_digest NOT GLOB '*[^0-9a-f]*'
|
|
74
|
+
),
|
|
75
|
+
PRIMARY KEY (catalog_id, release_id, record_id),
|
|
76
|
+
FOREIGN KEY (catalog_id, release_id)
|
|
77
|
+
REFERENCES release_metadata(catalog_id, release_id)
|
|
78
|
+
ON UPDATE RESTRICT ON DELETE RESTRICT
|
|
79
|
+
) WITHOUT ROWID;
|
|
80
|
+
`;
|
|
81
|
+
export function createReleaseSchemaV4(database) {
|
|
82
|
+
database.exec(RELEASE_SCHEMA_V4);
|
|
83
|
+
}
|
|
84
|
+
export function populateOperationsFtsV4(database) {
|
|
85
|
+
database.exec(`
|
|
86
|
+
INSERT INTO operations_fts
|
|
87
|
+
(rowid, record_id, operation_id, summary, path, api, search_text)
|
|
88
|
+
SELECT rowid, record_id, operation_id, summary, path, api, search_text
|
|
89
|
+
FROM operations
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const actionDispatchPermitBrand: unique symbol;
|
|
2
|
+
/**
|
|
3
|
+
* Engine-private proof that an exact authorized action was consumed.
|
|
4
|
+
*
|
|
5
|
+
* This module deliberately exposes no constructor or issuer. Runtime identity
|
|
6
|
+
* is held by the authorization boundary that created the permit.
|
|
7
|
+
*/
|
|
8
|
+
export type ActionDispatchPermit = Readonly<{
|
|
9
|
+
readonly [actionDispatchPermitBrand]: never;
|
|
10
|
+
}>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ActionCardinality, ActionKind, JsonObject, OperationRecordV4, SchemaRecordV4 } from "./types.ts";
|
|
2
|
+
/** A runtime-derived classification; no value is read from record advisory data. */
|
|
3
|
+
export interface OperationClassification {
|
|
4
|
+
readonly safety: "read" | "action";
|
|
5
|
+
readonly actionKind: ActionKind | null;
|
|
6
|
+
readonly cardinality: ActionCardinality | null;
|
|
7
|
+
/** Whether the kind always requires a fresh, per-call confirmation. */
|
|
8
|
+
readonly dangerous: boolean;
|
|
9
|
+
/** Whether the action must be treated as high-risk by the authorizer. */
|
|
10
|
+
readonly highRisk: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Recompute safety, action kind, cardinality, and risk from verified records.
|
|
14
|
+
* Compiler-supplied advisory fields intentionally do not participate.
|
|
15
|
+
*/
|
|
16
|
+
export declare function classifyOperation(operation: OperationRecordV4, schemas?: readonly SchemaRecordV4[], normalizedArguments?: JsonObject): OperationClassification;
|