@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.
Files changed (116) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +362 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +249 -0
  6. package/dist/compile.d.ts +14 -0
  7. package/dist/compile.js +132 -0
  8. package/dist/compiler.d.ts +13 -0
  9. package/dist/compiler.js +7 -0
  10. package/dist/conformance/fixtures.d.ts +24 -0
  11. package/dist/conformance/fixtures.js +119 -0
  12. package/dist/conformance/index.d.ts +2 -0
  13. package/dist/conformance/index.js +2 -0
  14. package/dist/conformance/runtime-suite.d.ts +31 -0
  15. package/dist/conformance/runtime-suite.js +343 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/load.d.ts +31 -0
  19. package/dist/load.js +20 -0
  20. package/dist/operations.d.ts +27 -0
  21. package/dist/operations.js +231 -0
  22. package/dist/permissions.d.ts +31 -0
  23. package/dist/permissions.js +89 -0
  24. package/dist/release/compile-release.d.ts +33 -0
  25. package/dist/release/compile-release.js +1620 -0
  26. package/dist/release/load-v4.d.ts +29 -0
  27. package/dist/release/load-v4.js +297 -0
  28. package/dist/release/manifest-builder.d.ts +19 -0
  29. package/dist/release/manifest-builder.js +28 -0
  30. package/dist/release/publish.d.ts +45 -0
  31. package/dist/release/publish.js +346 -0
  32. package/dist/release/reference-map.d.ts +53 -0
  33. package/dist/release/reference-map.js +347 -0
  34. package/dist/release/schema-v4.d.ts +5 -0
  35. package/dist/release/schema-v4.js +91 -0
  36. package/dist/runtime/action-permit.d.ts +11 -0
  37. package/dist/runtime/action-permit.js +1 -0
  38. package/dist/runtime/classify.d.ts +16 -0
  39. package/dist/runtime/classify.js +279 -0
  40. package/dist/runtime/credential-binding.d.ts +3 -0
  41. package/dist/runtime/credential-binding.js +101 -0
  42. package/dist/runtime/digest.d.ts +8 -0
  43. package/dist/runtime/digest.js +80 -0
  44. package/dist/runtime/errors.d.ts +14 -0
  45. package/dist/runtime/errors.js +15 -0
  46. package/dist/runtime/index.d.ts +20 -0
  47. package/dist/runtime/index.js +14 -0
  48. package/dist/runtime/manifest.d.ts +33 -0
  49. package/dist/runtime/manifest.js +489 -0
  50. package/dist/runtime/prepared-call.d.ts +39 -0
  51. package/dist/runtime/prepared-call.js +566 -0
  52. package/dist/runtime/references.d.ts +7 -0
  53. package/dist/runtime/references.js +197 -0
  54. package/dist/runtime/runtime.d.ts +25 -0
  55. package/dist/runtime/runtime.js +1272 -0
  56. package/dist/runtime/schema-keywords.d.ts +2 -0
  57. package/dist/runtime/schema-keywords.js +30 -0
  58. package/dist/runtime/schema-resolver.d.ts +10 -0
  59. package/dist/runtime/schema-resolver.js +237 -0
  60. package/dist/runtime/serialize.d.ts +12 -0
  61. package/dist/runtime/serialize.js +877 -0
  62. package/dist/runtime/store.d.ts +52 -0
  63. package/dist/runtime/store.js +582 -0
  64. package/dist/runtime/strict-json.d.ts +19 -0
  65. package/dist/runtime/strict-json.js +539 -0
  66. package/dist/runtime/types.d.ts +457 -0
  67. package/dist/runtime/types.js +1 -0
  68. package/dist/runtime/verify-record.d.ts +6 -0
  69. package/dist/runtime/verify-record.js +492 -0
  70. package/dist/runtime/versions.d.ts +63 -0
  71. package/dist/runtime/versions.js +86 -0
  72. package/dist/safety.d.ts +28 -0
  73. package/dist/safety.js +60 -0
  74. package/dist/schema.d.ts +5 -0
  75. package/dist/schema.js +52 -0
  76. package/dist/schemas.d.ts +9 -0
  77. package/dist/schemas.js +16 -0
  78. package/dist/sign.d.ts +20 -0
  79. package/dist/sign.js +55 -0
  80. package/dist/sqlite/auth.d.ts +33 -0
  81. package/dist/sqlite/auth.js +973 -0
  82. package/dist/sqlite/catalog-store.d.ts +20 -0
  83. package/dist/sqlite/catalog-store.js +370 -0
  84. package/dist/sqlite/destination-guard.d.ts +24 -0
  85. package/dist/sqlite/destination-guard.js +140 -0
  86. package/dist/sqlite/generation-store.d.ts +12 -0
  87. package/dist/sqlite/generation-store.js +721 -0
  88. package/dist/sqlite/guarded-fetch.d.ts +24 -0
  89. package/dist/sqlite/guarded-fetch.js +626 -0
  90. package/dist/sqlite/index.d.ts +5 -0
  91. package/dist/sqlite/index.js +5 -0
  92. package/dist/sqlite/oauth-resource.d.ts +2 -0
  93. package/dist/sqlite/oauth-resource.js +15 -0
  94. package/dist/sqlite/release-completion.d.ts +5 -0
  95. package/dist/sqlite/release-completion.js +80 -0
  96. package/dist/stdio/action-broker.d.ts +15 -0
  97. package/dist/stdio/action-broker.js +136 -0
  98. package/dist/stdio/authorizer.d.ts +30 -0
  99. package/dist/stdio/authorizer.js +484 -0
  100. package/dist/stdio/config.d.ts +77 -0
  101. package/dist/stdio/config.js +144 -0
  102. package/dist/stdio/exact-policy.d.ts +41 -0
  103. package/dist/stdio/exact-policy.js +361 -0
  104. package/dist/stdio/index.d.ts +6 -0
  105. package/dist/stdio/index.js +148 -0
  106. package/dist/stdio/render.d.ts +8 -0
  107. package/dist/stdio/render.js +253 -0
  108. package/dist/stdio/request-state.d.ts +18 -0
  109. package/dist/stdio/request-state.js +79 -0
  110. package/dist/stdio/search.d.ts +10 -0
  111. package/dist/stdio/search.js +76 -0
  112. package/dist/stdio/server.d.ts +29 -0
  113. package/dist/stdio/server.js +309 -0
  114. package/dist/types.d.ts +32 -0
  115. package/dist/types.js +1 -0
  116. package/package.json +131 -0
@@ -0,0 +1,489 @@
1
+ import { sha256, verifyEd25519 } from "./digest.js";
2
+ import { OpenApiMcpError } from "./errors.js";
3
+ import { parseTypedRecordId } from "./references.js";
4
+ import { canonicalJson, parseJsonStrict } from "./strict-json.js";
5
+ import { resolveRuntimeLimits } from "./versions.js";
6
+ function manifestSignatureDomain(format) {
7
+ return `knitli.openapi-mcp.release-manifest.v${format}\0`;
8
+ }
9
+ function manifestDigestDomain(format) {
10
+ return `knitli.openapi-mcp.release-manifest.v${format}`;
11
+ }
12
+ const rollbackSignatureDomain = "knitli.openapi-mcp.rollback-authorization.v1\0";
13
+ const digestPattern = /^[0-9a-f]{64}$/;
14
+ const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
15
+ const boundedAsciiPattern = /^[\x21-\x7e]+$/;
16
+ const manifestKeys = [
17
+ "allowedOrigins",
18
+ "catalogId",
19
+ "compiledAt",
20
+ "compilerVersion",
21
+ "contract",
22
+ "format",
23
+ "generation",
24
+ "issuer",
25
+ "keyId",
26
+ "policyId",
27
+ "records",
28
+ "releaseId",
29
+ "source",
30
+ ];
31
+ const sourceKeys = [
32
+ "contentSha256",
33
+ "referenceGraphDigest",
34
+ "revision",
35
+ "uri",
36
+ ];
37
+ const signatureKeys = ["algorithm", "keyId", "signature"];
38
+ const rollbackKeys = [
39
+ "algorithm",
40
+ "catalogId",
41
+ "currentHighestGeneration",
42
+ "expiresAt",
43
+ "id",
44
+ "issuer",
45
+ "keyId",
46
+ "reason",
47
+ "signature",
48
+ "targetGeneration",
49
+ "targetManifestDigest",
50
+ ];
51
+ const envelopeRequiredKeys = ["manifestJson", "signature"];
52
+ function manifestInvalid(message) {
53
+ return new OpenApiMcpError("MANIFEST_INVALID", message);
54
+ }
55
+ function rollbackRejected(message) {
56
+ return new OpenApiMcpError("MANIFEST_ROLLBACK_REJECTED", message);
57
+ }
58
+ function isObject(value) {
59
+ if (typeof value !== "object" || value === null || Array.isArray(value))
60
+ return false;
61
+ const prototype = Object.getPrototypeOf(value);
62
+ return prototype === null || prototype === Object.prototype;
63
+ }
64
+ function ownData(value, key) {
65
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
66
+ if (descriptor === undefined ||
67
+ !("value" in descriptor) ||
68
+ !descriptor.enumerable) {
69
+ throw manifestInvalid(`${key} must be an own data property`);
70
+ }
71
+ return descriptor.value;
72
+ }
73
+ function requireExactShape(value, expected, label) {
74
+ if (!isObject(value))
75
+ throw manifestInvalid(`${label} must be an object`);
76
+ if (Reflect.ownKeys(value).some((key) => typeof key !== "string")) {
77
+ throw manifestInvalid(`${label} has a symbol property`);
78
+ }
79
+ const keys = Reflect.ownKeys(value).sort();
80
+ const sortedExpected = [...expected].sort();
81
+ if (keys.length !== sortedExpected.length ||
82
+ keys.some((key, index) => typeof key === "string" ? key !== sortedExpected[index] : true)) {
83
+ throw manifestInvalid(`${label} shape is invalid`);
84
+ }
85
+ for (const key of sortedExpected)
86
+ ownData(value, key);
87
+ return value;
88
+ }
89
+ function requireString(value, label, maximum, pattern) {
90
+ if (typeof value !== "string" ||
91
+ value.length === 0 ||
92
+ value.length > maximum ||
93
+ (pattern && !pattern.test(value))) {
94
+ throw manifestInvalid(`${label} is invalid`);
95
+ }
96
+ return value;
97
+ }
98
+ function requireIdentifier(value, label) {
99
+ const result = requireString(value, label, 128, identifierPattern);
100
+ if (result === "." || result === "..")
101
+ throw manifestInvalid(`${label} is invalid`);
102
+ return result;
103
+ }
104
+ function requireInteger(value, label) {
105
+ if (!Number.isSafeInteger(value) || value < 0)
106
+ throw manifestInvalid(`${label} is invalid`);
107
+ return value;
108
+ }
109
+ function requireDigest(value, label) {
110
+ if (typeof value !== "string" || !digestPattern.test(value))
111
+ throw manifestInvalid(`${label} is invalid`);
112
+ return value;
113
+ }
114
+ function requireTimestamp(value, label) {
115
+ const timestamp = requireString(value, label, 64, boundedAsciiPattern);
116
+ const parsed = new Date(timestamp);
117
+ if (!Number.isFinite(parsed.getTime()) ||
118
+ parsed.toISOString() !== timestamp) {
119
+ throw manifestInvalid(`${label} must be a canonical UTC timestamp`);
120
+ }
121
+ return timestamp;
122
+ }
123
+ function requireOrigin(value) {
124
+ const candidate = requireString(value, "allowed origin", 2048, boundedAsciiPattern);
125
+ let url;
126
+ try {
127
+ url = new URL(candidate);
128
+ }
129
+ catch {
130
+ throw manifestInvalid("allowed origin is invalid");
131
+ }
132
+ if (url.protocol !== "https:" ||
133
+ url.username !== "" ||
134
+ url.password !== "" ||
135
+ url.pathname !== "/" ||
136
+ url.search !== "" ||
137
+ url.hash !== "" ||
138
+ url.origin !== candidate) {
139
+ throw manifestInvalid("allowed origin must be an exact canonical HTTPS origin");
140
+ }
141
+ return candidate;
142
+ }
143
+ function parseManifest(text, limits) {
144
+ const parsed = parseJsonStrict(text, {
145
+ maxBytes: limits.maxManifestBytes,
146
+ maxDepth: limits.maxJsonDepth,
147
+ maxKeys: limits.maxManifestRecords + 32,
148
+ });
149
+ const value = requireExactShape(parsed, manifestKeys, "manifest");
150
+ const format = ownData(value, "format");
151
+ if ((format !== 4 && format !== 5) || ownData(value, "contract") !== 1) {
152
+ throw manifestInvalid("manifest versions are unsupported");
153
+ }
154
+ const catalogId = requireIdentifier(ownData(value, "catalogId"), "catalog ID");
155
+ const releaseId = requireIdentifier(ownData(value, "releaseId"), "release ID");
156
+ const generation = requireInteger(ownData(value, "generation"), "generation");
157
+ const issuer = requireString(ownData(value, "issuer"), "issuer", 256, boundedAsciiPattern);
158
+ const keyId = requireIdentifier(ownData(value, "keyId"), "key ID");
159
+ const policyId = requireIdentifier(ownData(value, "policyId"), "policy ID");
160
+ const compilerVersion = requireString(ownData(value, "compilerVersion"), "compiler version", 128, boundedAsciiPattern);
161
+ const compiledAt = requireTimestamp(ownData(value, "compiledAt"), "compiledAt");
162
+ const rawOrigins = ownData(value, "allowedOrigins");
163
+ if (!Array.isArray(rawOrigins) || rawOrigins.length === 0)
164
+ throw manifestInvalid("allowedOrigins is invalid");
165
+ const allowedOrigins = rawOrigins.map(requireOrigin);
166
+ if (new Set(allowedOrigins).size !== allowedOrigins.length)
167
+ throw manifestInvalid("allowedOrigins contains a duplicate");
168
+ const rawSource = requireExactShape(ownData(value, "source"), sourceKeys, "manifest source");
169
+ const source = {
170
+ uri: requireString(ownData(rawSource, "uri"), "source URI", 4096),
171
+ revision: requireString(ownData(rawSource, "revision"), "source revision", 512, boundedAsciiPattern),
172
+ contentSha256: requireDigest(ownData(rawSource, "contentSha256"), "source content digest"),
173
+ referenceGraphDigest: requireDigest(ownData(rawSource, "referenceGraphDigest"), "reference graph digest"),
174
+ };
175
+ const rawRecords = ownData(value, "records");
176
+ if (!isObject(rawRecords))
177
+ throw manifestInvalid("records must be an object");
178
+ const recordKeys = Reflect.ownKeys(rawRecords);
179
+ if (recordKeys.length > limits.maxManifestRecords)
180
+ throw manifestInvalid("manifest exceeds its record limit");
181
+ const records = Object.create(null);
182
+ for (const rawId of recordKeys) {
183
+ if (typeof rawId !== "string")
184
+ throw manifestInvalid("records has a symbol key");
185
+ let id;
186
+ try {
187
+ id = parseTypedRecordId(rawId);
188
+ }
189
+ catch {
190
+ throw manifestInvalid("manifest record ID is invalid");
191
+ }
192
+ records[id] = requireDigest(ownData(rawRecords, rawId), "record digest");
193
+ }
194
+ return {
195
+ format,
196
+ contract: 1,
197
+ catalogId,
198
+ releaseId,
199
+ generation,
200
+ issuer,
201
+ keyId,
202
+ policyId,
203
+ allowedOrigins,
204
+ compiledAt,
205
+ compilerVersion,
206
+ source,
207
+ records,
208
+ };
209
+ }
210
+ function parseSignature(value) {
211
+ const object = requireExactShape(value, signatureKeys, "manifest signature");
212
+ if (ownData(object, "algorithm") !== "Ed25519")
213
+ throw manifestInvalid("manifest signature algorithm is invalid");
214
+ return {
215
+ algorithm: "Ed25519",
216
+ keyId: requireIdentifier(ownData(object, "keyId"), "signature key ID"),
217
+ signature: requireString(ownData(object, "signature"), "manifest signature", 256, /^[A-Za-z0-9_-]+$/),
218
+ };
219
+ }
220
+ function findKey(keys, issuer, keyId) {
221
+ const matches = keys.filter((key) => key.issuer === issuer && key.keyId === keyId);
222
+ return matches.length === 1 ? matches[0] : null;
223
+ }
224
+ async function requireReleaseSignature(manifest, canonical, signatureValue, trust) {
225
+ let signature;
226
+ try {
227
+ signature = parseSignature(signatureValue);
228
+ }
229
+ catch {
230
+ throw new OpenApiMcpError("MANIFEST_SIGNATURE_INVALID", "Manifest signature envelope is invalid");
231
+ }
232
+ if (signature.keyId !== manifest.keyId) {
233
+ throw new OpenApiMcpError("MANIFEST_SIGNATURE_INVALID", "Manifest signature key does not match the manifest");
234
+ }
235
+ const key = findKey(trust.releaseKeys, manifest.issuer, signature.keyId);
236
+ if (key === null ||
237
+ !(await verifyEd25519(`${manifestSignatureDomain(manifest.format)}${canonical}`, signature.signature, key.publicKey))) {
238
+ throw new OpenApiMcpError("MANIFEST_SIGNATURE_INVALID", "Manifest signature is not trusted");
239
+ }
240
+ }
241
+ function rollbackUnsigned(value) {
242
+ return {
243
+ id: value.id,
244
+ catalogId: value.catalogId,
245
+ issuer: value.issuer,
246
+ currentHighestGeneration: value.currentHighestGeneration,
247
+ targetGeneration: value.targetGeneration,
248
+ targetManifestDigest: value.targetManifestDigest,
249
+ reason: value.reason,
250
+ expiresAt: value.expiresAt,
251
+ keyId: value.keyId,
252
+ algorithm: value.algorithm,
253
+ };
254
+ }
255
+ function parseRollback(value) {
256
+ const object = requireExactShape(value, rollbackKeys, "rollback authorization");
257
+ const rollback = {
258
+ id: requireIdentifier(ownData(object, "id"), "rollback authorization ID"),
259
+ catalogId: requireIdentifier(ownData(object, "catalogId"), "rollback catalog ID"),
260
+ issuer: requireString(ownData(object, "issuer"), "rollback issuer", 256, boundedAsciiPattern),
261
+ currentHighestGeneration: requireInteger(ownData(object, "currentHighestGeneration"), "rollback current generation"),
262
+ targetGeneration: requireInteger(ownData(object, "targetGeneration"), "rollback target generation"),
263
+ targetManifestDigest: requireDigest(ownData(object, "targetManifestDigest"), "rollback target digest"),
264
+ reason: requireString(ownData(object, "reason"), "rollback reason", 1024),
265
+ expiresAt: requireTimestamp(ownData(object, "expiresAt"), "rollback expiry"),
266
+ keyId: requireIdentifier(ownData(object, "keyId"), "rollback key ID"),
267
+ algorithm: ownData(object, "algorithm"),
268
+ signature: requireString(ownData(object, "signature"), "rollback signature", 256, /^[A-Za-z0-9_-]+$/),
269
+ };
270
+ if (rollback.algorithm !== "Ed25519")
271
+ throw manifestInvalid("rollback algorithm is invalid");
272
+ return rollback;
273
+ }
274
+ async function requireRollback(manifest, manifestDigest, state, candidate, trust) {
275
+ if (candidate === undefined)
276
+ throw rollbackRejected("A signed rollback authorization is required");
277
+ let value;
278
+ try {
279
+ value = parseRollback(candidate);
280
+ }
281
+ catch {
282
+ throw rollbackRejected("Rollback authorization is malformed");
283
+ }
284
+ if (value.catalogId !== manifest.catalogId ||
285
+ value.issuer !== manifest.issuer ||
286
+ value.currentHighestGeneration !== state.highestGeneration ||
287
+ value.targetGeneration !== manifest.generation ||
288
+ value.targetManifestDigest !== manifestDigest ||
289
+ state.consumedRollbackAuthorizationIds.includes(value.id)) {
290
+ throw rollbackRejected("Rollback authorization does not match current state and target");
291
+ }
292
+ const now = trust.now?.() ?? new Date();
293
+ if (new Date(value.expiresAt).getTime() <= now.getTime())
294
+ throw rollbackRejected("Rollback authorization has expired");
295
+ const key = findKey(trust.rollbackKeys, manifest.issuer, value.keyId);
296
+ if (key === null ||
297
+ trust.releaseKeys.some((releaseKey) => releaseKey.publicKey === key.publicKey)) {
298
+ throw rollbackRejected("Rollback key is not separately trusted");
299
+ }
300
+ const valid = await verifyEd25519(`${rollbackSignatureDomain}${canonicalJson(rollbackUnsigned(value))}`, value.signature, key.publicKey);
301
+ if (!valid)
302
+ throw rollbackRejected("Rollback signature is invalid");
303
+ return value.id;
304
+ }
305
+ function nextNormalState(state, manifest, digest) {
306
+ if (state === null) {
307
+ return {
308
+ revision: 0,
309
+ highestGeneration: manifest.generation,
310
+ highestManifestDigest: digest,
311
+ activeGeneration: manifest.generation,
312
+ activeManifestDigest: digest,
313
+ consumedRollbackAuthorizationIds: [],
314
+ };
315
+ }
316
+ if (manifest.generation > state.highestGeneration) {
317
+ return {
318
+ ...state,
319
+ revision: state.revision + 1,
320
+ highestGeneration: manifest.generation,
321
+ highestManifestDigest: digest,
322
+ activeGeneration: manifest.generation,
323
+ activeManifestDigest: digest,
324
+ };
325
+ }
326
+ if (manifest.generation === state.highestGeneration) {
327
+ if (digest !== state.highestManifestDigest) {
328
+ throw new OpenApiMcpError("MANIFEST_GENERATION_CONFLICT", "Equal manifest generation has a different digest");
329
+ }
330
+ if (state.activeGeneration === manifest.generation &&
331
+ state.activeManifestDigest === digest)
332
+ return null;
333
+ return {
334
+ ...state,
335
+ revision: state.revision + 1,
336
+ activeGeneration: manifest.generation,
337
+ activeManifestDigest: digest,
338
+ };
339
+ }
340
+ return null;
341
+ }
342
+ function deepFreeze(value) {
343
+ if (typeof value !== "object" || value === null || Object.isFrozen(value))
344
+ return value;
345
+ for (const key of Reflect.ownKeys(value))
346
+ deepFreeze(value[key]);
347
+ return Object.freeze(value);
348
+ }
349
+ function detachedManifest(manifest, limits) {
350
+ return parseManifest(canonicalJson(manifest), limits);
351
+ }
352
+ /** Authenticate a v4/v5 manifest without reading or mutating generation state. */
353
+ export async function authenticateManifest(envelope, trust, limitOverrides) {
354
+ const limits = resolveRuntimeLimits(limitOverrides);
355
+ if (!isObject(envelope))
356
+ throw manifestInvalid("manifest envelope must be an object");
357
+ const envelopeKeys = Object.hasOwn(envelope, "rollback")
358
+ ? [...envelopeRequiredKeys, "rollback"]
359
+ : envelopeRequiredKeys;
360
+ const checkedEnvelope = requireExactShape(envelope, envelopeKeys, "manifest envelope");
361
+ const manifestJson = ownData(checkedEnvelope, "manifestJson");
362
+ if (typeof manifestJson !== "string")
363
+ throw manifestInvalid("manifestJson must be a string");
364
+ const signature = ownData(checkedEnvelope, "signature");
365
+ const rollbackCandidate = envelopeKeys.length === 3
366
+ ? ownData(checkedEnvelope, "rollback")
367
+ : undefined;
368
+ let manifest;
369
+ try {
370
+ manifest = parseManifest(manifestJson, limits);
371
+ }
372
+ catch (error) {
373
+ if (error instanceof OpenApiMcpError)
374
+ throw error;
375
+ throw manifestInvalid("Manifest parsing failed");
376
+ }
377
+ const canonical = canonicalJson(manifest);
378
+ await requireReleaseSignature(manifest, canonical, signature, trust);
379
+ const manifestDigest = await sha256(manifestDigestDomain(manifest.format), manifest);
380
+ let rollback;
381
+ let rollbackMalformed = false;
382
+ if (rollbackCandidate !== undefined) {
383
+ try {
384
+ rollback = parseRollback(rollbackCandidate);
385
+ }
386
+ catch {
387
+ rollback = undefined;
388
+ rollbackMalformed = true;
389
+ }
390
+ }
391
+ return deepFreeze({
392
+ manifest: detachedManifest(manifest, limits),
393
+ manifestDigest,
394
+ rollback,
395
+ rollbackMalformed,
396
+ });
397
+ }
398
+ /** Atomically commit the generation transition for an authenticated manifest. */
399
+ /**
400
+ * Commit only the generation decision made against `state`.
401
+ * A null result is a CAS miss; callers must reread and re-plan rather than
402
+ * reinterpreting this manifest under a newer generation policy.
403
+ */
404
+ export async function commitAuthenticatedManifestAtState(authenticated, trust, generations, state) {
405
+ const { manifest, manifestDigest, rollback, rollbackMalformed } = authenticated;
406
+ const admitted = { manifest, manifestDigest };
407
+ if (state !== null &&
408
+ manifest.generation === state.activeGeneration &&
409
+ manifestDigest === state.activeManifestDigest) {
410
+ return admitted;
411
+ }
412
+ let next = nextNormalState(state, manifest, manifestDigest);
413
+ if (state !== null &&
414
+ manifest.generation === state.highestGeneration &&
415
+ manifestDigest === state.highestManifestDigest &&
416
+ state.activeGeneration !== state.highestGeneration) {
417
+ throw rollbackRejected("Inactive high-water release cannot reactivate");
418
+ }
419
+ if (state !== null && manifest.generation < state.highestGeneration) {
420
+ if (rollbackMalformed)
421
+ throw rollbackRejected("Rollback authorization is malformed");
422
+ const rollbackId = await requireRollback(manifest, manifestDigest, state, rollback, trust);
423
+ next = {
424
+ ...state,
425
+ revision: state.revision + 1,
426
+ activeGeneration: manifest.generation,
427
+ activeManifestDigest: manifestDigest,
428
+ consumedRollbackAuthorizationIds: [
429
+ ...state.consumedRollbackAuthorizationIds,
430
+ rollbackId,
431
+ ],
432
+ };
433
+ }
434
+ if (next === null)
435
+ return admitted;
436
+ const accepted = await generations.accept(manifest.catalogId, manifest.issuer, {
437
+ expectedRevision: state?.revision ?? null,
438
+ next,
439
+ });
440
+ return accepted === null ? null : admitted;
441
+ }
442
+ export async function admitAuthenticatedManifest(authenticated, trust, generations) {
443
+ const { manifest, manifestDigest, rollback, rollbackMalformed } = authenticated;
444
+ const admitted = { manifest, manifestDigest };
445
+ for (let attempt = 0; attempt < 32; attempt += 1) {
446
+ const state = await generations.get(manifest.catalogId, manifest.issuer);
447
+ if (state !== null &&
448
+ manifest.generation === state.activeGeneration &&
449
+ manifestDigest === state.activeManifestDigest) {
450
+ return admitted;
451
+ }
452
+ let next = nextNormalState(state, manifest, manifestDigest);
453
+ if (state !== null &&
454
+ manifest.generation === state.highestGeneration &&
455
+ manifestDigest === state.highestManifestDigest &&
456
+ state.activeGeneration !== state.highestGeneration) {
457
+ throw rollbackRejected("Inactive high-water release cannot reactivate");
458
+ }
459
+ if (state !== null && manifest.generation < state.highestGeneration) {
460
+ if (rollbackMalformed)
461
+ throw rollbackRejected("Rollback authorization is malformed");
462
+ const rollbackId = await requireRollback(manifest, manifestDigest, state, rollback, trust);
463
+ next = {
464
+ ...state,
465
+ revision: state.revision + 1,
466
+ activeGeneration: manifest.generation,
467
+ activeManifestDigest: manifestDigest,
468
+ consumedRollbackAuthorizationIds: [
469
+ ...state.consumedRollbackAuthorizationIds,
470
+ rollbackId,
471
+ ],
472
+ };
473
+ }
474
+ if (next === null)
475
+ return admitted;
476
+ const accepted = await generations.accept(manifest.catalogId, manifest.issuer, {
477
+ expectedRevision: state?.revision ?? null,
478
+ next,
479
+ });
480
+ if (accepted !== null)
481
+ return admitted;
482
+ }
483
+ throw new OpenApiMcpError("MANIFEST_GENERATION_CONFLICT", "Generation state changed too many times; retry admission");
484
+ }
485
+ /** Verify a v4/v5 manifest and atomically admit its generation transition. */
486
+ export async function admitManifest(envelope, trust, generations, limitOverrides) {
487
+ const authenticated = await authenticateManifest(envelope, trust, limitOverrides);
488
+ return admitAuthenticatedManifest(authenticated, trust, generations);
489
+ }
@@ -0,0 +1,39 @@
1
+ import type { ActionCardinality, ActionKind, CatalogId, HttpMethod, JsonObject, PaginationTokenState, PreparedCall, ReleaseId, Sha256, TypedOperationId } from "./types.ts";
2
+ import { type RuntimeLimits } from "./versions.ts";
3
+ export type PreparedCallInput = Omit<PreparedCall, "inputDigest" | "preparedCallDigest">;
4
+ interface ParsedPreparedCall {
5
+ readonly version: 2;
6
+ readonly catalogId: CatalogId;
7
+ readonly releaseId: ReleaseId;
8
+ readonly operationId: TypedOperationId;
9
+ readonly operationDigest: Sha256;
10
+ readonly manifestDigest: Sha256;
11
+ readonly credentialProfileId: string;
12
+ readonly credentialProfileDigest: Sha256;
13
+ readonly reservedSlotsDigest: Sha256;
14
+ readonly method: HttpMethod;
15
+ readonly origin: string;
16
+ readonly relativeUrl: string;
17
+ readonly pageToken: string | null;
18
+ readonly headers: Readonly<Record<string, string>>;
19
+ readonly body: Uint8Array | null;
20
+ readonly normalizedArguments: JsonObject;
21
+ readonly safety: "read" | "action";
22
+ readonly actionKind: ActionKind | null;
23
+ readonly cardinality: ActionCardinality | null;
24
+ readonly inputDigest: Sha256;
25
+ readonly preparedCallDigest: Sha256;
26
+ }
27
+ /** Package-private defensive snapshot shared by the runtime and local codec. */
28
+ export declare function snapshotPaginationTokenState(value: unknown, limits: RuntimeLimits, now?: number): Readonly<PaginationTokenState>;
29
+ /** SHA-256 of raw request bytes, independent of JSON canonicalization. */
30
+ export declare function digestBytes(value: Uint8Array): Promise<Sha256>;
31
+ /** Compute the domain-separated call digest, deliberately omitting its own key. */
32
+ export declare function digestPreparedCall(call: PreparedCall): Promise<Sha256>;
33
+ /** Verify a prepared call locally; storage admission is rechecked separately. */
34
+ export declare function verifyAndSnapshotPreparedCall(call: PreparedCall): Promise<Readonly<ParsedPreparedCall>>;
35
+ /** Verify a prepared call locally; storage admission is rechecked separately. */
36
+ export declare function verifyPreparedCall(call: PreparedCall): Promise<void>;
37
+ /** Build an owned, detached prepared call and bind all fields to its digests. */
38
+ export declare function createPreparedCall(input: PreparedCallInput): Promise<PreparedCall>;
39
+ export {};