@capaxle/runtime 0.1.0-alpha.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.
- package/LICENSE +202 -0
- package/README.md +5 -0
- package/dist/authorization.d.ts +12 -0
- package/dist/authorization.d.ts.map +1 -0
- package/dist/authorization.js +23 -0
- package/dist/authorization.js.map +1 -0
- package/dist/base64url.d.ts +4 -0
- package/dist/base64url.d.ts.map +1 -0
- package/dist/base64url.js +11 -0
- package/dist/base64url.js.map +1 -0
- package/dist/confirmation-types.d.ts +255 -0
- package/dist/confirmation-types.d.ts.map +1 -0
- package/dist/confirmation-types.js +2 -0
- package/dist/confirmation-types.js.map +1 -0
- package/dist/confirmation.d.ts +6 -0
- package/dist/confirmation.d.ts.map +1 -0
- package/dist/confirmation.js +1205 -0
- package/dist/confirmation.js.map +1 -0
- package/dist/idempotency-types.d.ts +130 -0
- package/dist/idempotency-types.d.ts.map +1 -0
- package/dist/idempotency-types.js +2 -0
- package/dist/idempotency-types.js.map +1 -0
- package/dist/idempotency.d.ts +4 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +798 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/ingress.d.ts +33 -0
- package/dist/ingress.d.ts.map +1 -0
- package/dist/ingress.js +325 -0
- package/dist/ingress.js.map +1 -0
- package/dist/kernel.d.ts +5 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +2872 -0
- package/dist/kernel.js.map +1 -0
- package/dist/principal.d.ts +7 -0
- package/dist/principal.d.ts.map +1 -0
- package/dist/principal.js +130 -0
- package/dist/principal.js.map +1 -0
- package/dist/rate-limit.d.ts +12 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +82 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/redaction.d.ts +19 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +338 -0
- package/dist/redaction.js.map +1 -0
- package/dist/registry.d.ts +23 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +188 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +401 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +33 -0
package/dist/kernel.js
ADDED
|
@@ -0,0 +1,2872 @@
|
|
|
1
|
+
import { addSensitiveString, captureRedactionPaths, compileRedactionPaths, createRedactionState, equalJson, redactJson, } from "./redaction.js";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
4
|
+
import { types } from "node:util";
|
|
5
|
+
import { canonicalizeInput, jcs, validateSchemaValue } from "@capaxle/ir";
|
|
6
|
+
import { compileBearerDescriptors } from "./ingress.js";
|
|
7
|
+
import { authorizePrincipals } from "./authorization.js";
|
|
8
|
+
import { decodeCanonicalBase64Url256 } from "./base64url.js";
|
|
9
|
+
import { anonymousPrincipal, canonicalIdentityId, normalizePrincipal, rootIdentity, } from "./principal.js";
|
|
10
|
+
import { copyJson, ownData, registryState, RuntimeConfigurationError, } from "./registry.js";
|
|
11
|
+
const adapterControlKeys = [
|
|
12
|
+
"confirmationToken",
|
|
13
|
+
"idempotencyKey",
|
|
14
|
+
"correlationId",
|
|
15
|
+
"timeoutMs",
|
|
16
|
+
];
|
|
17
|
+
const adapterCandidateKeys = [
|
|
18
|
+
"ok",
|
|
19
|
+
"input",
|
|
20
|
+
"controls",
|
|
21
|
+
"code",
|
|
22
|
+
"status",
|
|
23
|
+
"safeDetails",
|
|
24
|
+
];
|
|
25
|
+
const ADAPTER_DETAIL_MAX_DEPTH = 8;
|
|
26
|
+
const ADAPTER_DETAIL_MAX_NODES = 128;
|
|
27
|
+
const ADAPTER_DETAIL_MAX_STRING = 1024;
|
|
28
|
+
const ADAPTER_DETAIL_MAX_KEY = 128;
|
|
29
|
+
const ADAPTER_DETAIL_MAX_JSON = 4096;
|
|
30
|
+
function boundedAdapterDetails(value) {
|
|
31
|
+
const state = { nodes: 0 };
|
|
32
|
+
const visit = (item, depth) => {
|
|
33
|
+
state.nodes++;
|
|
34
|
+
if (state.nodes > ADAPTER_DETAIL_MAX_NODES ||
|
|
35
|
+
depth > ADAPTER_DETAIL_MAX_DEPTH)
|
|
36
|
+
throw new Error("adapter_details");
|
|
37
|
+
if (item === null || typeof item === "boolean")
|
|
38
|
+
return item;
|
|
39
|
+
if (typeof item === "string") {
|
|
40
|
+
if (item.length > ADAPTER_DETAIL_MAX_STRING)
|
|
41
|
+
throw new Error("adapter_details");
|
|
42
|
+
return item;
|
|
43
|
+
}
|
|
44
|
+
if (typeof item === "number" && Number.isFinite(item))
|
|
45
|
+
return item;
|
|
46
|
+
if (Array.isArray(item)) {
|
|
47
|
+
if (item.length > ADAPTER_DETAIL_MAX_NODES ||
|
|
48
|
+
types.isProxy(item) ||
|
|
49
|
+
Object.getPrototypeOf(item) !== Array.prototype ||
|
|
50
|
+
Reflect.ownKeys(item).length !== item.length + 1)
|
|
51
|
+
throw new Error("adapter_details");
|
|
52
|
+
return Object.freeze(Array.from({ length: item.length }, (_, index) => {
|
|
53
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, String(index));
|
|
54
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable)
|
|
55
|
+
throw new Error("adapter_details");
|
|
56
|
+
return visit(descriptor.value, depth + 1);
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
const data = ownData(item);
|
|
60
|
+
if (Object.keys(data).length > ADAPTER_DETAIL_MAX_NODES)
|
|
61
|
+
throw new Error("adapter_details");
|
|
62
|
+
const result = {};
|
|
63
|
+
for (const [key, member] of Object.entries(data)) {
|
|
64
|
+
if (key.length > ADAPTER_DETAIL_MAX_KEY)
|
|
65
|
+
throw new Error("adapter_details");
|
|
66
|
+
Object.defineProperty(result, key, {
|
|
67
|
+
value: visit(member, depth + 1),
|
|
68
|
+
enumerable: true,
|
|
69
|
+
writable: false,
|
|
70
|
+
configurable: false,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return Object.freeze(result);
|
|
74
|
+
};
|
|
75
|
+
const result = visit(value, 0);
|
|
76
|
+
if (JSON.stringify(result).length > ADAPTER_DETAIL_MAX_JSON)
|
|
77
|
+
throw new Error("adapter_details");
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
/** Normative stages; adapter serialization follows the returned canonical result. */
|
|
81
|
+
export const RUNTIME_STAGES = Object.freeze([
|
|
82
|
+
"resolve",
|
|
83
|
+
"context",
|
|
84
|
+
"authenticate",
|
|
85
|
+
"authorize",
|
|
86
|
+
"admission",
|
|
87
|
+
"parse",
|
|
88
|
+
"input_validation",
|
|
89
|
+
"quota_preconditions",
|
|
90
|
+
"idempotency_inspect",
|
|
91
|
+
"confirmation",
|
|
92
|
+
"approval_consume",
|
|
93
|
+
"idempotency_claim",
|
|
94
|
+
"resources",
|
|
95
|
+
"audit_start",
|
|
96
|
+
"handler",
|
|
97
|
+
"output_validation",
|
|
98
|
+
"finalize",
|
|
99
|
+
"telemetry_audit",
|
|
100
|
+
]);
|
|
101
|
+
const requestKeys = [
|
|
102
|
+
"capability",
|
|
103
|
+
"version",
|
|
104
|
+
"input",
|
|
105
|
+
"source",
|
|
106
|
+
"correlationId",
|
|
107
|
+
"signal",
|
|
108
|
+
"deadline",
|
|
109
|
+
"principal",
|
|
110
|
+
"idempotencyKey",
|
|
111
|
+
"confirmationToken",
|
|
112
|
+
"metadata",
|
|
113
|
+
"adapterCandidate",
|
|
114
|
+
];
|
|
115
|
+
const abortedGetter = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted")?.get;
|
|
116
|
+
const addListener = AbortSignal.prototype.addEventListener;
|
|
117
|
+
const removeListener = AbortSignal.prototype.removeEventListener;
|
|
118
|
+
const getDateTime = Date.prototype.getTime;
|
|
119
|
+
const nativeSignalSlots = new Map();
|
|
120
|
+
const nativeEventMaps = new Set();
|
|
121
|
+
let nativeListenerPrototype;
|
|
122
|
+
let nativeWeakGetter;
|
|
123
|
+
const nativeWeakRefPrototypes = new Set([WeakRef.prototype]);
|
|
124
|
+
const exemplarController = new AbortController();
|
|
125
|
+
const exemplarSignal = exemplarController.signal;
|
|
126
|
+
addListener.call(exemplarSignal, "abort", () => undefined);
|
|
127
|
+
const handlerExemplar = new AbortController().signal;
|
|
128
|
+
handlerExemplar.onabort = () => undefined;
|
|
129
|
+
const freshExemplar = new AbortController().signal;
|
|
130
|
+
const abortedExemplar = AbortSignal.abort(new Error("Native signal profile."));
|
|
131
|
+
const compositeExemplar = AbortSignal.any([exemplarSignal]);
|
|
132
|
+
const registeredCompositeSourceExemplar = new AbortController().signal;
|
|
133
|
+
const registeredCompositeExemplar = AbortSignal.any([
|
|
134
|
+
registeredCompositeSourceExemplar,
|
|
135
|
+
]);
|
|
136
|
+
addListener.call(registeredCompositeExemplar, "abort", () => undefined);
|
|
137
|
+
function nativeWeakRefValue(value) {
|
|
138
|
+
if (typeof value !== "object" || value === null || types.isProxy(value))
|
|
139
|
+
return false;
|
|
140
|
+
try {
|
|
141
|
+
WeakRef.prototype.deref.call(value);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const timeoutExemplar = AbortSignal.timeout(1);
|
|
149
|
+
for (const exemplar of [
|
|
150
|
+
freshExemplar,
|
|
151
|
+
exemplarSignal,
|
|
152
|
+
handlerExemplar,
|
|
153
|
+
abortedExemplar,
|
|
154
|
+
compositeExemplar,
|
|
155
|
+
registeredCompositeExemplar,
|
|
156
|
+
registeredCompositeSourceExemplar,
|
|
157
|
+
timeoutExemplar,
|
|
158
|
+
]) {
|
|
159
|
+
for (const key of Reflect.ownKeys(exemplar)) {
|
|
160
|
+
const value = Object.getOwnPropertyDescriptor(exemplar, key)?.value;
|
|
161
|
+
const slot = nativeSignalSlots.get(key) ?? {
|
|
162
|
+
kinds: new Set(),
|
|
163
|
+
prototypes: new Set(),
|
|
164
|
+
allowsUndefined: false,
|
|
165
|
+
};
|
|
166
|
+
if (value === undefined)
|
|
167
|
+
slot.allowsUndefined = true;
|
|
168
|
+
else {
|
|
169
|
+
const kind = types.isMap(value)
|
|
170
|
+
? "map"
|
|
171
|
+
: types.isSet(value)
|
|
172
|
+
? "set"
|
|
173
|
+
: nativeWeakRefValue(value)
|
|
174
|
+
? "weakref"
|
|
175
|
+
: typeof value === "boolean"
|
|
176
|
+
? "boolean"
|
|
177
|
+
: typeof value === "number"
|
|
178
|
+
? "number"
|
|
179
|
+
: "opaque";
|
|
180
|
+
slot.kinds.add(kind);
|
|
181
|
+
if (kind === "weakref")
|
|
182
|
+
nativeWeakRefPrototypes.add(Object.getPrototypeOf(value));
|
|
183
|
+
if (kind === "map" || kind === "set" || kind === "weakref")
|
|
184
|
+
slot.prototypes.add(Object.getPrototypeOf(value));
|
|
185
|
+
}
|
|
186
|
+
nativeSignalSlots.set(key, slot);
|
|
187
|
+
if (types.isMap(value))
|
|
188
|
+
Map.prototype.forEach.call(value, (header) => {
|
|
189
|
+
if (typeof header !== "object" || header === null)
|
|
190
|
+
return;
|
|
191
|
+
const next = Object.getOwnPropertyDescriptor(header, "next")?.value;
|
|
192
|
+
if (typeof next !== "object" || next === null)
|
|
193
|
+
return;
|
|
194
|
+
nativeEventMaps.add(key);
|
|
195
|
+
nativeListenerPrototype = Object.getPrototypeOf(next);
|
|
196
|
+
nativeWeakGetter = Object.getOwnPropertyDescriptor(nativeListenerPrototype, "weak")?.get;
|
|
197
|
+
});
|
|
198
|
+
if (types.isSet(value))
|
|
199
|
+
Set.prototype.forEach.call(value, (reference) => nativeWeakRefPrototypes.add(Object.getPrototypeOf(reference)));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const weakRefDeref = WeakRef.prototype.deref;
|
|
203
|
+
function validWeakRef(value) {
|
|
204
|
+
if (typeof value !== "object" ||
|
|
205
|
+
value === null ||
|
|
206
|
+
types.isProxy(value) ||
|
|
207
|
+
!nativeWeakRefPrototypes.has(Object.getPrototypeOf(value)) ||
|
|
208
|
+
Reflect.ownKeys(value).length)
|
|
209
|
+
return false;
|
|
210
|
+
try {
|
|
211
|
+
weakRefDeref.call(value);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** Validate only native event-list structure; registered callbacks stay opaque. */
|
|
219
|
+
function validEventHeader(value, inactiveHandler) {
|
|
220
|
+
const header = ownData(value, ["size", "next", "resistStopPropagation"]);
|
|
221
|
+
if (!Number.isInteger(header.size) ||
|
|
222
|
+
header.size < 0 ||
|
|
223
|
+
header.size > 1024 ||
|
|
224
|
+
typeof header.resistStopPropagation !== "boolean")
|
|
225
|
+
return false;
|
|
226
|
+
const seen = new Set();
|
|
227
|
+
let active = 0;
|
|
228
|
+
let previous = value;
|
|
229
|
+
let next = header.next;
|
|
230
|
+
while (next !== undefined) {
|
|
231
|
+
if (typeof next !== "object" ||
|
|
232
|
+
next === null ||
|
|
233
|
+
types.isProxy(next) ||
|
|
234
|
+
seen.has(next) ||
|
|
235
|
+
seen.size >= 1024 ||
|
|
236
|
+
Object.getPrototypeOf(next) !== nativeListenerPrototype)
|
|
237
|
+
return false;
|
|
238
|
+
seen.add(next);
|
|
239
|
+
const node = Object.create(null);
|
|
240
|
+
for (const key of Reflect.ownKeys(next)) {
|
|
241
|
+
if (typeof key !== "string" ||
|
|
242
|
+
!["next", "previous", "listener", "flags", "callback"].includes(key))
|
|
243
|
+
return false;
|
|
244
|
+
const descriptor = Object.getOwnPropertyDescriptor(next, key);
|
|
245
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable)
|
|
246
|
+
return false;
|
|
247
|
+
node[key] = descriptor.value;
|
|
248
|
+
}
|
|
249
|
+
if (node.previous !== previous ||
|
|
250
|
+
!Number.isInteger(node.flags) ||
|
|
251
|
+
node.flags < 0 ||
|
|
252
|
+
node.flags > 127)
|
|
253
|
+
return false;
|
|
254
|
+
if (nativeWeakGetter.call(next)) {
|
|
255
|
+
if (!validWeakRef(node.listener) || !validWeakRef(node.callback))
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
else if (typeof node.callback !== "function" ||
|
|
259
|
+
types.isProxy(node.callback) ||
|
|
260
|
+
(typeof node.listener !== "function" &&
|
|
261
|
+
(typeof node.listener !== "object" || node.listener === null)) ||
|
|
262
|
+
types.isProxy(node.listener))
|
|
263
|
+
return false;
|
|
264
|
+
if (node.listener !== inactiveHandler)
|
|
265
|
+
active++;
|
|
266
|
+
previous = next;
|
|
267
|
+
next = node.next;
|
|
268
|
+
}
|
|
269
|
+
// Native onabort wrappers may retain their count when initially assigned null.
|
|
270
|
+
return seen.size === header.size || active === header.size;
|
|
271
|
+
}
|
|
272
|
+
function validSignal(signal, establishedEvents) {
|
|
273
|
+
if (signal === null || typeof signal !== "object" || types.isProxy(signal))
|
|
274
|
+
return false;
|
|
275
|
+
try {
|
|
276
|
+
if (Object.getPrototypeOf(signal) !== AbortSignal.prototype)
|
|
277
|
+
return false;
|
|
278
|
+
if (establishedEvents)
|
|
279
|
+
for (const [key, eventMap] of establishedEvents) {
|
|
280
|
+
const descriptor = Object.getOwnPropertyDescriptor(signal, key);
|
|
281
|
+
if (!descriptor ||
|
|
282
|
+
!("value" in descriptor) ||
|
|
283
|
+
descriptor.value !== eventMap)
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
const signalKeys = Reflect.ownKeys(signal);
|
|
287
|
+
if (signalKeys.length > 128)
|
|
288
|
+
return false;
|
|
289
|
+
const inactiveHandlers = new Map();
|
|
290
|
+
for (const [key, slot] of nativeSignalSlots) {
|
|
291
|
+
if (!slot.kinds.has("map") || nativeEventMaps.has(key))
|
|
292
|
+
continue;
|
|
293
|
+
const descriptor = Object.getOwnPropertyDescriptor(signal, key);
|
|
294
|
+
const value = descriptor?.value;
|
|
295
|
+
if (descriptor &&
|
|
296
|
+
"value" in descriptor &&
|
|
297
|
+
value === undefined &&
|
|
298
|
+
slot.allowsUndefined)
|
|
299
|
+
continue;
|
|
300
|
+
if (!descriptor ||
|
|
301
|
+
!("value" in descriptor) ||
|
|
302
|
+
types.isProxy(value) ||
|
|
303
|
+
!types.isMap(value) ||
|
|
304
|
+
!slot.prototypes.has(Object.getPrototypeOf(value)) ||
|
|
305
|
+
Reflect.ownKeys(value).length)
|
|
306
|
+
return false;
|
|
307
|
+
let entries = 0;
|
|
308
|
+
Map.prototype.forEach.call(value, (handler, event) => {
|
|
309
|
+
if (++entries > 1024 ||
|
|
310
|
+
typeof handler !== "function" ||
|
|
311
|
+
types.isProxy(handler) ||
|
|
312
|
+
typeof event !== "string")
|
|
313
|
+
throw new Error("event_handlers");
|
|
314
|
+
const data = Object.getOwnPropertyDescriptor(handler, "handler");
|
|
315
|
+
if (data && !("value" in data))
|
|
316
|
+
throw new Error("event_handler");
|
|
317
|
+
if (data && typeof data.value !== "function")
|
|
318
|
+
inactiveHandlers.set(event, handler);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
for (const key of signalKeys) {
|
|
322
|
+
const descriptor = Object.getOwnPropertyDescriptor(signal, key);
|
|
323
|
+
if (!descriptor ||
|
|
324
|
+
!("value" in descriptor) ||
|
|
325
|
+
types.isProxy(descriptor.value) ||
|
|
326
|
+
typeof key === "string")
|
|
327
|
+
return false;
|
|
328
|
+
const value = descriptor.value;
|
|
329
|
+
const slot = nativeSignalSlots.get(key);
|
|
330
|
+
if (!slot) {
|
|
331
|
+
if (typeof value === "object" || typeof value === "function")
|
|
332
|
+
return false;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (value === undefined) {
|
|
336
|
+
if (!slot.allowsUndefined)
|
|
337
|
+
return false;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const kind = types.isMap(value)
|
|
341
|
+
? "map"
|
|
342
|
+
: types.isSet(value)
|
|
343
|
+
? "set"
|
|
344
|
+
: slot.kinds.has("weakref") && nativeWeakRefValue(value)
|
|
345
|
+
? "weakref"
|
|
346
|
+
: typeof value === "boolean"
|
|
347
|
+
? "boolean"
|
|
348
|
+
: typeof value === "number"
|
|
349
|
+
? "number"
|
|
350
|
+
: "opaque";
|
|
351
|
+
if (!slot.kinds.has("opaque") && !slot.kinds.has(kind))
|
|
352
|
+
return false;
|
|
353
|
+
if (kind === "number" &&
|
|
354
|
+
!slot.kinds.has("opaque") &&
|
|
355
|
+
(typeof value !== "number" || !Number.isFinite(value) || value < 0))
|
|
356
|
+
return false;
|
|
357
|
+
if (kind === "weakref" && !validWeakRef(value))
|
|
358
|
+
return false;
|
|
359
|
+
if (kind === "map" && !slot.kinds.has("opaque")) {
|
|
360
|
+
if (!types.isMap(value) ||
|
|
361
|
+
!slot.prototypes.has(Object.getPrototypeOf(value)) ||
|
|
362
|
+
Reflect.ownKeys(value).length)
|
|
363
|
+
return false;
|
|
364
|
+
let entries = 0;
|
|
365
|
+
let valid = true;
|
|
366
|
+
Map.prototype.forEach.call(value, (entry, event) => {
|
|
367
|
+
if (++entries > 1024)
|
|
368
|
+
throw new Error("event_bounds");
|
|
369
|
+
if (typeof event !== "string" ||
|
|
370
|
+
(nativeEventMaps.has(key)
|
|
371
|
+
? !validEventHeader(entry, inactiveHandlers.get(event))
|
|
372
|
+
: typeof entry !== "function" || types.isProxy(entry)))
|
|
373
|
+
valid = false;
|
|
374
|
+
});
|
|
375
|
+
if (!valid)
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
if (kind === "set" && !slot.kinds.has("opaque")) {
|
|
379
|
+
if (!types.isSet(value) ||
|
|
380
|
+
!slot.prototypes.has(Object.getPrototypeOf(value)) ||
|
|
381
|
+
Reflect.ownKeys(value).length)
|
|
382
|
+
return false;
|
|
383
|
+
let entries = 0;
|
|
384
|
+
let valid = true;
|
|
385
|
+
Set.prototype.forEach.call(value, (entry) => {
|
|
386
|
+
if (++entries > 1024)
|
|
387
|
+
throw new Error("signal_bounds");
|
|
388
|
+
if (!validWeakRef(entry))
|
|
389
|
+
valid = false;
|
|
390
|
+
});
|
|
391
|
+
if (!valid)
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
for (const [key, slot] of nativeSignalSlots)
|
|
396
|
+
if (slot.kinds.has("map") && !Object.hasOwn(signal, key))
|
|
397
|
+
return false;
|
|
398
|
+
return typeof abortedGetter?.call(signal) === "boolean";
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/** Registration materializes lazy event storage; retain that exact caller listener map. */
|
|
405
|
+
function establishedSignalEvents(signal) {
|
|
406
|
+
const established = new Map();
|
|
407
|
+
for (const key of nativeEventMaps) {
|
|
408
|
+
const descriptor = Object.getOwnPropertyDescriptor(signal, key);
|
|
409
|
+
const value = descriptor?.value;
|
|
410
|
+
if (!descriptor ||
|
|
411
|
+
!("value" in descriptor) ||
|
|
412
|
+
types.isProxy(value) ||
|
|
413
|
+
!types.isMap(value))
|
|
414
|
+
throw new Error("signal_event_state");
|
|
415
|
+
established.set(key, value);
|
|
416
|
+
}
|
|
417
|
+
return established;
|
|
418
|
+
}
|
|
419
|
+
function safelyNotify(callback, value) {
|
|
420
|
+
try {
|
|
421
|
+
const returned = callback?.(Object.freeze(value));
|
|
422
|
+
if (types.isPromise(returned) && !types.isProxy(returned))
|
|
423
|
+
nativePromiseThen.call(returned, () => undefined, () => undefined);
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
/* Telemetry loss cannot authorize or alter an invocation. */
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const canonical256 = (value) => {
|
|
430
|
+
try {
|
|
431
|
+
decodeCanonicalBase64Url256(value);
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
function confirmationResponse(value, operation, expected) {
|
|
439
|
+
try {
|
|
440
|
+
const result = ownData(value, [
|
|
441
|
+
"ok",
|
|
442
|
+
"error",
|
|
443
|
+
"challenge",
|
|
444
|
+
"receipt",
|
|
445
|
+
"attempt",
|
|
446
|
+
"kernelInvocationId",
|
|
447
|
+
"outcome",
|
|
448
|
+
"confirmationToken",
|
|
449
|
+
"expiresAt",
|
|
450
|
+
"completed",
|
|
451
|
+
]);
|
|
452
|
+
if (result.ok !== true && result.ok !== false)
|
|
453
|
+
throw new Error("confirmation_response");
|
|
454
|
+
const exact = (data, keys) => {
|
|
455
|
+
if (Object.keys(data).length !== keys.length ||
|
|
456
|
+
keys.some((key) => !Object.hasOwn(data, key)))
|
|
457
|
+
throw new Error("confirmation_response");
|
|
458
|
+
};
|
|
459
|
+
const handle = (artifact, prefix) => {
|
|
460
|
+
if (typeof artifact !== "string")
|
|
461
|
+
return false;
|
|
462
|
+
const segments = artifact.split(".");
|
|
463
|
+
return (segments.length === 4 &&
|
|
464
|
+
segments[0] === prefix &&
|
|
465
|
+
/^[A-Za-z0-9_-]{1,32}$/.test(segments[1]) &&
|
|
466
|
+
canonical256(segments[2]) &&
|
|
467
|
+
canonical256(segments[3]));
|
|
468
|
+
};
|
|
469
|
+
const timestamp = (value) => typeof value === "string" &&
|
|
470
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) &&
|
|
471
|
+
Number.isFinite(Date.parse(value)) &&
|
|
472
|
+
new Date(value).toISOString() === value;
|
|
473
|
+
const receipt = (value, attempt = false) => {
|
|
474
|
+
const data = ownData(value, attempt
|
|
475
|
+
? ["recordId", "confirmationRef", "executionAttemptId"]
|
|
476
|
+
: ["recordId", "confirmationRef"]);
|
|
477
|
+
exact(data, attempt
|
|
478
|
+
? ["recordId", "confirmationRef", "executionAttemptId"]
|
|
479
|
+
: ["recordId", "confirmationRef"]);
|
|
480
|
+
if (!Object.isFrozen(value) ||
|
|
481
|
+
!canonical256(data.recordId) ||
|
|
482
|
+
typeof data.confirmationRef !== "string" ||
|
|
483
|
+
!data.confirmationRef.startsWith("capr1.") ||
|
|
484
|
+
!canonical256(data.confirmationRef.slice(6)) ||
|
|
485
|
+
(attempt && !canonical256(data.executionAttemptId)))
|
|
486
|
+
throw new Error("confirmation_response");
|
|
487
|
+
return data;
|
|
488
|
+
};
|
|
489
|
+
if (result.ok === false) {
|
|
490
|
+
exact(result, ["ok", "error"]);
|
|
491
|
+
const error = ownData(result.error, [
|
|
492
|
+
"code",
|
|
493
|
+
"status",
|
|
494
|
+
"message",
|
|
495
|
+
"retryable",
|
|
496
|
+
]);
|
|
497
|
+
exact(error, ["code", "status", "message", "retryable"]);
|
|
498
|
+
if (typeof error.code !== "string" ||
|
|
499
|
+
typeof error.message !== "string" ||
|
|
500
|
+
typeof error.retryable !== "boolean" ||
|
|
501
|
+
![
|
|
502
|
+
"failed_precondition",
|
|
503
|
+
"unavailable",
|
|
504
|
+
"unauthenticated",
|
|
505
|
+
"permission_denied",
|
|
506
|
+
"invalid_argument",
|
|
507
|
+
].includes(error.status))
|
|
508
|
+
throw new Error("confirmation_response");
|
|
509
|
+
const safeErrors = {
|
|
510
|
+
CAP_CONFIRMATION_INVALID: {
|
|
511
|
+
status: "failed_precondition",
|
|
512
|
+
message: "Approval evidence is invalid.",
|
|
513
|
+
retryable: false,
|
|
514
|
+
},
|
|
515
|
+
CAP_DEPENDENCY_UNAVAILABLE: {
|
|
516
|
+
status: "unavailable",
|
|
517
|
+
message: "Required runtime provider is unavailable.",
|
|
518
|
+
retryable: undefined,
|
|
519
|
+
},
|
|
520
|
+
CAP_UNAUTHENTICATED: {
|
|
521
|
+
status: "unauthenticated",
|
|
522
|
+
message: "Invocation failed.",
|
|
523
|
+
retryable: false,
|
|
524
|
+
},
|
|
525
|
+
CAP_PERMISSION_DENIED: {
|
|
526
|
+
status: "permission_denied",
|
|
527
|
+
message: "Invocation failed.",
|
|
528
|
+
retryable: false,
|
|
529
|
+
},
|
|
530
|
+
CAP_INPUT_INVALID: {
|
|
531
|
+
status: "invalid_argument",
|
|
532
|
+
message: "Confirmation operation failed.",
|
|
533
|
+
retryable: false,
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
const safe = Object.hasOwn(safeErrors, error.code)
|
|
537
|
+
? safeErrors[error.code]
|
|
538
|
+
: undefined;
|
|
539
|
+
if (!safe ||
|
|
540
|
+
safe.status !== error.status ||
|
|
541
|
+
(safe.retryable !== undefined && safe.retryable !== error.retryable))
|
|
542
|
+
throw new Error("confirmation_response");
|
|
543
|
+
result.error = { ...error, message: safe.message };
|
|
544
|
+
}
|
|
545
|
+
if (result.ok === true) {
|
|
546
|
+
if (operation === "allocation") {
|
|
547
|
+
exact(result, ["ok", "kernelInvocationId"]);
|
|
548
|
+
if (!canonical256(result.kernelInvocationId))
|
|
549
|
+
throw new Error("confirmation_response");
|
|
550
|
+
}
|
|
551
|
+
else if (operation === "consume") {
|
|
552
|
+
exact(result, ["ok", "receipt"]);
|
|
553
|
+
receipt(result.receipt);
|
|
554
|
+
}
|
|
555
|
+
else if (operation === "begin") {
|
|
556
|
+
exact(result, ["ok", "attempt"]);
|
|
557
|
+
const data = receipt(result.attempt, true);
|
|
558
|
+
const consumed = receipt(expected);
|
|
559
|
+
if (data.recordId !== consumed.recordId ||
|
|
560
|
+
data.confirmationRef !== consumed.confirmationRef)
|
|
561
|
+
throw new Error("confirmation_response");
|
|
562
|
+
}
|
|
563
|
+
else if (operation === "issue") {
|
|
564
|
+
exact(result, ["ok", "challenge"]);
|
|
565
|
+
const challenge = ownData(result.challenge, [
|
|
566
|
+
"challenge",
|
|
567
|
+
"capability",
|
|
568
|
+
"summary",
|
|
569
|
+
"impact",
|
|
570
|
+
"expiresAt",
|
|
571
|
+
]);
|
|
572
|
+
exact(challenge, [
|
|
573
|
+
"challenge",
|
|
574
|
+
"capability",
|
|
575
|
+
"summary",
|
|
576
|
+
"impact",
|
|
577
|
+
"expiresAt",
|
|
578
|
+
]);
|
|
579
|
+
const capability = ownData(challenge.capability, ["id", "version"]);
|
|
580
|
+
exact(capability, ["id", "version"]);
|
|
581
|
+
const action = expected;
|
|
582
|
+
if (!handle(challenge.challenge, "capc1") ||
|
|
583
|
+
capability.id !== action.capabilityId ||
|
|
584
|
+
capability.version !== action.version ||
|
|
585
|
+
challenge.impact !== action.impact ||
|
|
586
|
+
!timestamp(challenge.expiresAt) ||
|
|
587
|
+
typeof challenge.summary !== "string" ||
|
|
588
|
+
challenge.summary.length === 0 ||
|
|
589
|
+
Buffer.byteLength(challenge.summary, "utf8") > 512 ||
|
|
590
|
+
/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/u.test(challenge.summary) ||
|
|
591
|
+
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(challenge.summary))
|
|
592
|
+
throw new Error("confirmation_response");
|
|
593
|
+
result.challenge = copyJson(result.challenge);
|
|
594
|
+
}
|
|
595
|
+
else if (operation === "decision") {
|
|
596
|
+
if (result.outcome === "denied")
|
|
597
|
+
exact(result, ["ok", "outcome"]);
|
|
598
|
+
else if (result.outcome === "approved") {
|
|
599
|
+
exact(result, ["ok", "outcome", "confirmationToken", "expiresAt"]);
|
|
600
|
+
if (!handle(result.confirmationToken, "capa1") ||
|
|
601
|
+
!timestamp(result.expiresAt))
|
|
602
|
+
throw new Error("confirmation_response");
|
|
603
|
+
}
|
|
604
|
+
else
|
|
605
|
+
throw new Error("confirmation_response");
|
|
606
|
+
}
|
|
607
|
+
else if (operation === "query") {
|
|
608
|
+
exact(result, ["ok", "completed"]);
|
|
609
|
+
if (typeof result.completed !== "boolean")
|
|
610
|
+
throw new Error("confirmation_response");
|
|
611
|
+
}
|
|
612
|
+
else
|
|
613
|
+
exact(result, ["ok"]);
|
|
614
|
+
}
|
|
615
|
+
return result;
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
return {
|
|
619
|
+
ok: false,
|
|
620
|
+
error: {
|
|
621
|
+
code: "CAP_DEPENDENCY_UNAVAILABLE",
|
|
622
|
+
status: "unavailable",
|
|
623
|
+
message: "Required runtime provider is unavailable.",
|
|
624
|
+
retryable: true,
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const nativePromiseThen = Promise.prototype.then;
|
|
630
|
+
/** Semantic results stay opaque; trusted provider native Promise machinery is contained. */
|
|
631
|
+
function providerValue(callback) {
|
|
632
|
+
return new Promise((resolve) => {
|
|
633
|
+
queueMicrotask(() => {
|
|
634
|
+
try {
|
|
635
|
+
const returned = callback();
|
|
636
|
+
if (types.isPromise(returned) && !types.isProxy(returned)) {
|
|
637
|
+
nativePromiseThen.call(returned, (value) => resolve({ value }), () => resolve({ failed: true }));
|
|
638
|
+
}
|
|
639
|
+
else
|
|
640
|
+
resolve({ value: returned });
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
resolve({ failed: true });
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
function idempotencyResponse(value, operation) {
|
|
649
|
+
const unavailable = () => ({
|
|
650
|
+
ok: false,
|
|
651
|
+
error: {
|
|
652
|
+
code: "CAP_DEPENDENCY_UNAVAILABLE",
|
|
653
|
+
status: "unavailable",
|
|
654
|
+
message: "Required runtime provider is unavailable.",
|
|
655
|
+
retryable: operation !== "complete",
|
|
656
|
+
},
|
|
657
|
+
});
|
|
658
|
+
try {
|
|
659
|
+
const data = ownData(value, [
|
|
660
|
+
"ok",
|
|
661
|
+
"error",
|
|
662
|
+
"outcome",
|
|
663
|
+
"claim",
|
|
664
|
+
"terminal",
|
|
665
|
+
"confirmation",
|
|
666
|
+
]);
|
|
667
|
+
const exact = (value, fields) => {
|
|
668
|
+
if (Object.keys(value).length !== fields.length ||
|
|
669
|
+
fields.some((field) => !Object.hasOwn(value, field)))
|
|
670
|
+
throw new Error("response");
|
|
671
|
+
};
|
|
672
|
+
if (data.ok === false) {
|
|
673
|
+
exact(data, ["ok", "error"]);
|
|
674
|
+
const error = ownData(data.error, [
|
|
675
|
+
"code",
|
|
676
|
+
"status",
|
|
677
|
+
"message",
|
|
678
|
+
"retryable",
|
|
679
|
+
]);
|
|
680
|
+
exact(error, ["code", "status", "message", "retryable"]);
|
|
681
|
+
const codes = {
|
|
682
|
+
CAP_DEPENDENCY_UNAVAILABLE: "unavailable",
|
|
683
|
+
CAP_IDEMPOTENCY_KEY_REQUIRED: "invalid_argument",
|
|
684
|
+
CAP_IDEMPOTENCY_CONFLICT: "failed_precondition",
|
|
685
|
+
CAP_IDEMPOTENCY_IN_PROGRESS: "failed_precondition",
|
|
686
|
+
CAP_IDEMPOTENCY_RESULT_UNAVAILABLE: "failed_precondition",
|
|
687
|
+
CAP_IDEMPOTENCY_AMBIGUOUS: "failed_precondition",
|
|
688
|
+
};
|
|
689
|
+
if (typeof error.code !== "string" ||
|
|
690
|
+
!Object.hasOwn(codes, error.code) ||
|
|
691
|
+
codes[error.code] !== error.status ||
|
|
692
|
+
typeof error.message !== "string" ||
|
|
693
|
+
typeof error.retryable !== "boolean" ||
|
|
694
|
+
(error.code !== "CAP_DEPENDENCY_UNAVAILABLE" &&
|
|
695
|
+
error.retryable !== false))
|
|
696
|
+
throw new Error("error");
|
|
697
|
+
return {
|
|
698
|
+
ok: false,
|
|
699
|
+
error: {
|
|
700
|
+
code: error.code,
|
|
701
|
+
status: error.status,
|
|
702
|
+
message: "Idempotency admission or persistence failed.",
|
|
703
|
+
retryable: operation === "complete" ? false : error.retryable,
|
|
704
|
+
},
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
if (data.ok !== true)
|
|
708
|
+
throw new Error("response");
|
|
709
|
+
if (operation === "complete") {
|
|
710
|
+
exact(data, ["ok"]);
|
|
711
|
+
return value;
|
|
712
|
+
}
|
|
713
|
+
if (data.outcome === "absent" && operation === "inspect")
|
|
714
|
+
exact(data, ["ok", "outcome"]);
|
|
715
|
+
else if (data.outcome === "claimed" && operation === "claim") {
|
|
716
|
+
exact(data, ["ok", "outcome", "claim"]);
|
|
717
|
+
const claim = ownData(data.claim, ["index", "ownerId"]);
|
|
718
|
+
exact(claim, ["index", "ownerId"]);
|
|
719
|
+
if (!Object.isFrozen(data.claim) ||
|
|
720
|
+
!canonical256(claim.index) ||
|
|
721
|
+
!canonical256(claim.ownerId))
|
|
722
|
+
throw new Error("claim");
|
|
723
|
+
}
|
|
724
|
+
else if (data.outcome === "replay") {
|
|
725
|
+
exact(data, data.confirmation === undefined
|
|
726
|
+
? ["ok", "outcome", "terminal"]
|
|
727
|
+
: ["ok", "outcome", "terminal", "confirmation"]);
|
|
728
|
+
const snapshot = redactJson(data.terminal, createRedactionState());
|
|
729
|
+
const terminal = ownData(snapshot, ["kind", "value", "error"]);
|
|
730
|
+
if (terminal.kind === "success")
|
|
731
|
+
exact(terminal, ["kind", "value"]);
|
|
732
|
+
else {
|
|
733
|
+
if (!["declared_error", "unexpected_error"].includes(terminal.kind))
|
|
734
|
+
throw new Error("terminal");
|
|
735
|
+
exact(terminal, ["kind", "error"]);
|
|
736
|
+
const error = ownData(terminal.error, [
|
|
737
|
+
"code",
|
|
738
|
+
"status",
|
|
739
|
+
"message",
|
|
740
|
+
"retryable",
|
|
741
|
+
"details",
|
|
742
|
+
]);
|
|
743
|
+
exact(error, Object.hasOwn(error, "details")
|
|
744
|
+
? ["code", "status", "message", "retryable", "details"]
|
|
745
|
+
: ["code", "status", "message", "retryable"]);
|
|
746
|
+
if (typeof error.code !== "string" ||
|
|
747
|
+
typeof error.status !== "string" ||
|
|
748
|
+
typeof error.message !== "string" ||
|
|
749
|
+
typeof error.retryable !== "boolean")
|
|
750
|
+
throw new Error("terminal");
|
|
751
|
+
}
|
|
752
|
+
if (data.confirmation !== undefined) {
|
|
753
|
+
const link = ownData(data.confirmation, [
|
|
754
|
+
"recordId",
|
|
755
|
+
"confirmationRef",
|
|
756
|
+
"executionAttemptId",
|
|
757
|
+
]);
|
|
758
|
+
exact(link, ["recordId", "confirmationRef", "executionAttemptId"]);
|
|
759
|
+
if (!canonical256(link.recordId) ||
|
|
760
|
+
!canonical256(link.executionAttemptId) ||
|
|
761
|
+
typeof link.confirmationRef !== "string" ||
|
|
762
|
+
!link.confirmationRef.startsWith("capr1.") ||
|
|
763
|
+
!canonical256(link.confirmationRef.slice(6)))
|
|
764
|
+
throw new Error("linkage");
|
|
765
|
+
}
|
|
766
|
+
return {
|
|
767
|
+
ok: true,
|
|
768
|
+
outcome: "replay",
|
|
769
|
+
terminal: snapshot,
|
|
770
|
+
...(data.confirmation
|
|
771
|
+
? { confirmation: copyJson(data.confirmation) }
|
|
772
|
+
: {}),
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
else
|
|
776
|
+
throw new Error("response");
|
|
777
|
+
return value;
|
|
778
|
+
}
|
|
779
|
+
catch {
|
|
780
|
+
return unavailable();
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
export function createRuntimeKernel(options) {
|
|
784
|
+
let carrier;
|
|
785
|
+
try {
|
|
786
|
+
carrier = ownData(options, [
|
|
787
|
+
"registry",
|
|
788
|
+
"onStage",
|
|
789
|
+
"telemetry",
|
|
790
|
+
"authenticationProviders",
|
|
791
|
+
"adapters",
|
|
792
|
+
"authorizationProvider",
|
|
793
|
+
"identityFingerprintProvider",
|
|
794
|
+
"internalInvocationSecurity",
|
|
795
|
+
"disclosure",
|
|
796
|
+
"maxInternalDepth",
|
|
797
|
+
"confirmationProvider",
|
|
798
|
+
"idempotencyProvider",
|
|
799
|
+
"bearerDescriptors",
|
|
800
|
+
"rateLimitProvider",
|
|
801
|
+
"secretProvider",
|
|
802
|
+
"failOpenPrivateReads",
|
|
803
|
+
"redactionPaths",
|
|
804
|
+
"onTrace",
|
|
805
|
+
"onMetric",
|
|
806
|
+
"onLog",
|
|
807
|
+
]);
|
|
808
|
+
}
|
|
809
|
+
catch {
|
|
810
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_CONFIGURATION_INVALID");
|
|
811
|
+
}
|
|
812
|
+
const registry = registryState(carrier.registry);
|
|
813
|
+
if (!registry ||
|
|
814
|
+
(carrier.onStage !== undefined && typeof carrier.onStage !== "function") ||
|
|
815
|
+
(carrier.telemetry !== undefined && typeof carrier.telemetry !== "function"))
|
|
816
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_CONFIGURATION_INVALID");
|
|
817
|
+
const onStage = carrier.onStage;
|
|
818
|
+
const telemetry = carrier.telemetry;
|
|
819
|
+
const onTrace = carrier.onTrace;
|
|
820
|
+
const onMetric = carrier.onMetric;
|
|
821
|
+
const onLog = carrier.onLog;
|
|
822
|
+
let rateCheck;
|
|
823
|
+
let secretResolve;
|
|
824
|
+
const failOpen = new Set();
|
|
825
|
+
try {
|
|
826
|
+
for (const hook of [onTrace, onMetric, onLog])
|
|
827
|
+
if (hook !== undefined &&
|
|
828
|
+
(typeof hook !== "function" || types.isProxy(hook)))
|
|
829
|
+
throw new Error("hook");
|
|
830
|
+
let policyNames = [];
|
|
831
|
+
const array = (value) => {
|
|
832
|
+
if (types.isProxy(value) ||
|
|
833
|
+
!Array.isArray(value) ||
|
|
834
|
+
Object.getPrototypeOf(value) !== Array.prototype)
|
|
835
|
+
throw new Error("array");
|
|
836
|
+
if (value.length > 1024 ||
|
|
837
|
+
Reflect.ownKeys(value).length !== value.length + 1)
|
|
838
|
+
throw new Error("array");
|
|
839
|
+
return Array.from({ length: value.length }, (_, index) => {
|
|
840
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
841
|
+
if (!descriptor || !("value" in descriptor))
|
|
842
|
+
throw new Error("array");
|
|
843
|
+
return descriptor.value;
|
|
844
|
+
});
|
|
845
|
+
};
|
|
846
|
+
if (carrier.rateLimitProvider !== undefined) {
|
|
847
|
+
const data = ownData(carrier.rateLimitProvider, ["policies", "check"]);
|
|
848
|
+
policyNames = array(data.policies);
|
|
849
|
+
if (typeof data.check !== "function" ||
|
|
850
|
+
types.isProxy(data.check) ||
|
|
851
|
+
policyNames.some((name) => typeof name !== "string" || !name || name.length > 128) ||
|
|
852
|
+
new Set(policyNames).size !== policyNames.length)
|
|
853
|
+
throw new Error("rate");
|
|
854
|
+
const check = data.check;
|
|
855
|
+
rateCheck = (view) => check.call(carrier.rateLimitProvider, view);
|
|
856
|
+
}
|
|
857
|
+
if (carrier.secretProvider !== undefined) {
|
|
858
|
+
const data = ownData(carrier.secretProvider, ["resolve"]);
|
|
859
|
+
if (typeof data.resolve !== "function" || types.isProxy(data.resolve))
|
|
860
|
+
throw new Error("secret");
|
|
861
|
+
const resolve = data.resolve;
|
|
862
|
+
secretResolve = (name, view) => resolve.call(carrier.secretProvider, name, view);
|
|
863
|
+
}
|
|
864
|
+
for (const { capability } of registry.entries.values()) {
|
|
865
|
+
const rate = capability.limits.rateLimit;
|
|
866
|
+
if (rate && (!rateCheck || !policyNames.includes(rate.policy)))
|
|
867
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_PROVIDER_UNAVAILABLE");
|
|
868
|
+
if (capability.requirements.secrets.some((declaration) => !declaration.optional) &&
|
|
869
|
+
!secretResolve)
|
|
870
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_PROVIDER_UNAVAILABLE");
|
|
871
|
+
}
|
|
872
|
+
if (carrier.failOpenPrivateReads !== undefined)
|
|
873
|
+
for (const id of array(carrier.failOpenPrivateReads)) {
|
|
874
|
+
if (typeof id !== "string" || failOpen.has(id))
|
|
875
|
+
throw new Error("failopen");
|
|
876
|
+
const capability = registry.entries.get(id)?.capability;
|
|
877
|
+
if (!capability ||
|
|
878
|
+
!capability.limits.rateLimit ||
|
|
879
|
+
capability.effects.impact !== "read" ||
|
|
880
|
+
Object.values(capability.access.exposure).some((exposure) => exposure !== "disabled" && exposure !== "private"))
|
|
881
|
+
throw new Error("failopen");
|
|
882
|
+
failOpen.add(id);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
catch (error) {
|
|
886
|
+
if (error instanceof RuntimeConfigurationError)
|
|
887
|
+
throw error;
|
|
888
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_CONFIGURATION_INVALID");
|
|
889
|
+
}
|
|
890
|
+
const redactionPaths = compileRedactionPaths(carrier.redactionPaths, new Set(registry.entries.keys()));
|
|
891
|
+
const bearerGuard = compileBearerDescriptors(carrier.bearerDescriptors);
|
|
892
|
+
let reportFingerprintFailure;
|
|
893
|
+
const confirmationProvider = carrier.confirmationProvider;
|
|
894
|
+
if (confirmationProvider !== undefined) {
|
|
895
|
+
try {
|
|
896
|
+
const provider = ownData(confirmationProvider);
|
|
897
|
+
for (const name of [
|
|
898
|
+
"allocateKernelInvocationId",
|
|
899
|
+
"issue",
|
|
900
|
+
"consume",
|
|
901
|
+
"decideConfirmation",
|
|
902
|
+
"beginExecution",
|
|
903
|
+
"completeExecution",
|
|
904
|
+
])
|
|
905
|
+
if (typeof provider[name] !== "function")
|
|
906
|
+
throw new Error("provider");
|
|
907
|
+
if (provider.reportProviderFailure !== undefined) {
|
|
908
|
+
if (typeof provider.reportProviderFailure !== "function" ||
|
|
909
|
+
types.isProxy(provider.reportProviderFailure))
|
|
910
|
+
throw new Error("provider");
|
|
911
|
+
const report = provider.reportProviderFailure;
|
|
912
|
+
reportFingerprintFailure = (incident, controls) => report.call(confirmationProvider, incident, controls);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
catch {
|
|
916
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_CONFIGURATION_INVALID");
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
const unhealthyConfirmationPolicies = new Set();
|
|
920
|
+
const idempotencyProvider = carrier.idempotencyProvider;
|
|
921
|
+
if (idempotencyProvider !== undefined) {
|
|
922
|
+
try {
|
|
923
|
+
const provider = ownData(idempotencyProvider);
|
|
924
|
+
for (const name of [
|
|
925
|
+
"inspect",
|
|
926
|
+
"claim",
|
|
927
|
+
"enter",
|
|
928
|
+
"release",
|
|
929
|
+
"complete",
|
|
930
|
+
"reconcile",
|
|
931
|
+
])
|
|
932
|
+
if (typeof provider[name] !== "function")
|
|
933
|
+
throw new Error("provider");
|
|
934
|
+
}
|
|
935
|
+
catch {
|
|
936
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_CONFIGURATION_INVALID");
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
let identityUnavailable = false;
|
|
940
|
+
const allocateCorrelation = () => {
|
|
941
|
+
if (identityUnavailable)
|
|
942
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_IDENTITY_UNAVAILABLE");
|
|
943
|
+
try {
|
|
944
|
+
return randomUUID();
|
|
945
|
+
}
|
|
946
|
+
catch {
|
|
947
|
+
identityUnavailable = true;
|
|
948
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_IDENTITY_UNAVAILABLE");
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
const providers = new Map();
|
|
952
|
+
const principalIssuerIds = new Set();
|
|
953
|
+
const adapters = new Map();
|
|
954
|
+
const tokens = new WeakMap();
|
|
955
|
+
const authorizationProvider = carrier.authorizationProvider;
|
|
956
|
+
const derivePolicies = new Map();
|
|
957
|
+
const servicePolicies = new Map();
|
|
958
|
+
let deployment;
|
|
959
|
+
let serviceIdentity;
|
|
960
|
+
let fingerprintProvider;
|
|
961
|
+
let journal;
|
|
962
|
+
const conceal = carrier.disclosure === "conceal";
|
|
963
|
+
const maxDepth = carrier.maxInternalDepth ?? 32;
|
|
964
|
+
try {
|
|
965
|
+
if ((carrier.disclosure !== undefined &&
|
|
966
|
+
!["explicit", "conceal"].includes(carrier.disclosure)) ||
|
|
967
|
+
(authorizationProvider !== undefined &&
|
|
968
|
+
typeof authorizationProvider !== "function") ||
|
|
969
|
+
!Number.isInteger(maxDepth) ||
|
|
970
|
+
maxDepth < 1 ||
|
|
971
|
+
maxDepth > 32)
|
|
972
|
+
throw new Error("configuration");
|
|
973
|
+
const list = (value) => {
|
|
974
|
+
if (value === undefined)
|
|
975
|
+
return [];
|
|
976
|
+
if (typeof value !== "object" ||
|
|
977
|
+
value === null ||
|
|
978
|
+
types.isProxy(value) ||
|
|
979
|
+
!Array.isArray(value) ||
|
|
980
|
+
Object.getPrototypeOf(value) !== Array.prototype ||
|
|
981
|
+
Reflect.ownKeys(value).length !== value.length + 1 ||
|
|
982
|
+
value.length > 256)
|
|
983
|
+
throw new Error("list");
|
|
984
|
+
const output = [];
|
|
985
|
+
for (let i = 0; i < value.length; i++) {
|
|
986
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(i));
|
|
987
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable)
|
|
988
|
+
throw new Error("list");
|
|
989
|
+
output.push(descriptor.value);
|
|
990
|
+
}
|
|
991
|
+
return output;
|
|
992
|
+
};
|
|
993
|
+
for (const provider of list(carrier.authenticationProviders)) {
|
|
994
|
+
const data = ownData(provider, ["id", "authenticate"]);
|
|
995
|
+
if (!canonicalIdentityId(data.id) ||
|
|
996
|
+
typeof data.authenticate !== "function" ||
|
|
997
|
+
principalIssuerIds.has(data.id))
|
|
998
|
+
throw new Error("provider");
|
|
999
|
+
providers.set(data.id, data.authenticate);
|
|
1000
|
+
principalIssuerIds.add(data.id);
|
|
1001
|
+
}
|
|
1002
|
+
if (carrier.identityFingerprintProvider !== undefined) {
|
|
1003
|
+
const provider = ownData(carrier.identityFingerprintProvider, [
|
|
1004
|
+
"id",
|
|
1005
|
+
"fingerprint",
|
|
1006
|
+
]);
|
|
1007
|
+
if (Object.keys(provider).length !== 2 ||
|
|
1008
|
+
!canonicalIdentityId(provider.id) ||
|
|
1009
|
+
typeof provider.fingerprint !== "function" ||
|
|
1010
|
+
types.isProxy(provider.fingerprint))
|
|
1011
|
+
throw new Error("fingerprint");
|
|
1012
|
+
const fingerprint = provider.fingerprint;
|
|
1013
|
+
fingerprintProvider = {
|
|
1014
|
+
id: provider.id,
|
|
1015
|
+
fingerprint: (identity, controls) => fingerprint.call(carrier.identityFingerprintProvider, identity, controls),
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
if (carrier.internalInvocationSecurity !== undefined) {
|
|
1019
|
+
const security = ownData(carrier.internalInvocationSecurity, [
|
|
1020
|
+
"deployment",
|
|
1021
|
+
"derivePolicies",
|
|
1022
|
+
"servicePolicies",
|
|
1023
|
+
"serviceIdentityProvider",
|
|
1024
|
+
"journal",
|
|
1025
|
+
]);
|
|
1026
|
+
if (!canonicalIdentityId(security.deployment))
|
|
1027
|
+
throw new Error("deployment");
|
|
1028
|
+
deployment = security.deployment;
|
|
1029
|
+
for (const registration of list(security.derivePolicies)) {
|
|
1030
|
+
const data = ownData(registration, ["id", "providerId", "derive"]);
|
|
1031
|
+
if (Object.keys(data).length !== 3 ||
|
|
1032
|
+
!canonicalIdentityId(data.id) ||
|
|
1033
|
+
derivePolicies.has(data.id) ||
|
|
1034
|
+
!canonicalIdentityId(data.providerId) ||
|
|
1035
|
+
principalIssuerIds.has(data.providerId) ||
|
|
1036
|
+
typeof data.derive !== "function" ||
|
|
1037
|
+
types.isProxy(data.derive))
|
|
1038
|
+
throw new Error("derive");
|
|
1039
|
+
const derive = data.derive;
|
|
1040
|
+
derivePolicies.set(data.id, {
|
|
1041
|
+
providerId: data.providerId,
|
|
1042
|
+
derive: (view) => derive.call(registration, view),
|
|
1043
|
+
});
|
|
1044
|
+
principalIssuerIds.add(data.providerId);
|
|
1045
|
+
}
|
|
1046
|
+
for (const registration of list(security.servicePolicies)) {
|
|
1047
|
+
const data = ownData(registration, ["id", "evaluate"]);
|
|
1048
|
+
if (Object.keys(data).length !== 2 ||
|
|
1049
|
+
!canonicalIdentityId(data.id) ||
|
|
1050
|
+
servicePolicies.has(data.id) ||
|
|
1051
|
+
typeof data.evaluate !== "function" ||
|
|
1052
|
+
types.isProxy(data.evaluate))
|
|
1053
|
+
throw new Error("service_policy");
|
|
1054
|
+
const evaluate = data.evaluate;
|
|
1055
|
+
servicePolicies.set(data.id, {
|
|
1056
|
+
evaluate: (view) => evaluate.call(registration, view),
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
if (security.serviceIdentityProvider !== undefined) {
|
|
1060
|
+
const provider = ownData(security.serviceIdentityProvider, [
|
|
1061
|
+
"id",
|
|
1062
|
+
"resolve",
|
|
1063
|
+
]);
|
|
1064
|
+
if (Object.keys(provider).length !== 2 ||
|
|
1065
|
+
!canonicalIdentityId(provider.id) ||
|
|
1066
|
+
principalIssuerIds.has(provider.id) ||
|
|
1067
|
+
typeof provider.resolve !== "function" ||
|
|
1068
|
+
types.isProxy(provider.resolve))
|
|
1069
|
+
throw new Error("service_identity");
|
|
1070
|
+
const resolve = provider.resolve;
|
|
1071
|
+
serviceIdentity = {
|
|
1072
|
+
id: provider.id,
|
|
1073
|
+
resolve: (view) => resolve.call(security.serviceIdentityProvider, view),
|
|
1074
|
+
};
|
|
1075
|
+
principalIssuerIds.add(provider.id);
|
|
1076
|
+
}
|
|
1077
|
+
if (security.journal !== undefined) {
|
|
1078
|
+
const provider = ownData(security.journal, ["id", "commit"]);
|
|
1079
|
+
if (Object.keys(provider).length !== 2 ||
|
|
1080
|
+
!canonicalIdentityId(provider.id) ||
|
|
1081
|
+
typeof provider.commit !== "function" ||
|
|
1082
|
+
types.isProxy(provider.commit))
|
|
1083
|
+
throw new Error("journal");
|
|
1084
|
+
const commit = provider.commit;
|
|
1085
|
+
journal = {
|
|
1086
|
+
id: provider.id,
|
|
1087
|
+
commit: (event, controls) => commit.call(security.journal, event, controls),
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
if (fingerprintProvider &&
|
|
1091
|
+
journal &&
|
|
1092
|
+
fingerprintProvider.id === journal.id)
|
|
1093
|
+
throw new Error("provider_id");
|
|
1094
|
+
}
|
|
1095
|
+
for (const registration of list(carrier.adapters)) {
|
|
1096
|
+
const data = ownData(registration, [
|
|
1097
|
+
"id",
|
|
1098
|
+
"source",
|
|
1099
|
+
"providerId",
|
|
1100
|
+
"capabilities",
|
|
1101
|
+
"privateBoundary",
|
|
1102
|
+
]);
|
|
1103
|
+
const ids = copyJson(data.capabilities);
|
|
1104
|
+
if (!canonicalIdentityId(data.id) ||
|
|
1105
|
+
adapters.has(data.id) ||
|
|
1106
|
+
!canonicalIdentityId(data.providerId) ||
|
|
1107
|
+
!providers.has(data.providerId) ||
|
|
1108
|
+
!["http", "cli", "mcp", "internal", "sdk"].includes(data.source) ||
|
|
1109
|
+
!Array.isArray(ids) ||
|
|
1110
|
+
!ids.length ||
|
|
1111
|
+
ids.some((id) => typeof id !== "string" || !registry.entries.has(id)) ||
|
|
1112
|
+
(data.privateBoundary !== undefined &&
|
|
1113
|
+
typeof data.privateBoundary !== "boolean"))
|
|
1114
|
+
throw new Error("adapter");
|
|
1115
|
+
adapters.set(data.id, Object.freeze({
|
|
1116
|
+
...data,
|
|
1117
|
+
capabilities: ids,
|
|
1118
|
+
}));
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
catch {
|
|
1122
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_CONFIGURATION_INVALID");
|
|
1123
|
+
}
|
|
1124
|
+
const invoke = async (request, ingress, parent, invalid = false, transition) => {
|
|
1125
|
+
const correlationId = allocateCorrelation();
|
|
1126
|
+
const startedAt = Date.now();
|
|
1127
|
+
const redactionState = parent?.redactionState ?? createRedactionState();
|
|
1128
|
+
const redactionValues = redactionState.strings;
|
|
1129
|
+
const redactSecrets = (value) => redactJson(value, redactionState);
|
|
1130
|
+
const hasSensitiveString = (value) => [...redactionValues].some((pattern) => pattern === "" ? value === "" : value.includes(pattern));
|
|
1131
|
+
const notify = (callback, value) => {
|
|
1132
|
+
if (Object.values(value).some((field) => typeof field === "string" && hasSensitiveString(field)))
|
|
1133
|
+
return;
|
|
1134
|
+
safelyNotify(callback, value);
|
|
1135
|
+
};
|
|
1136
|
+
const failure = (code, status, details, message = "Invocation failed.", retryable = false) => ({
|
|
1137
|
+
ok: false,
|
|
1138
|
+
error: {
|
|
1139
|
+
code,
|
|
1140
|
+
status,
|
|
1141
|
+
message,
|
|
1142
|
+
retryable,
|
|
1143
|
+
correlationId,
|
|
1144
|
+
...(details === undefined ? {} : { details }),
|
|
1145
|
+
},
|
|
1146
|
+
});
|
|
1147
|
+
const invalidInput = () => failure("CAP_INPUT_INVALID", "invalid_argument", {
|
|
1148
|
+
path: "/input",
|
|
1149
|
+
code: "schema_constraint",
|
|
1150
|
+
});
|
|
1151
|
+
const diagnostic = (code) => notify(telemetry, {
|
|
1152
|
+
kind: "diagnostic",
|
|
1153
|
+
code,
|
|
1154
|
+
correlationId,
|
|
1155
|
+
severity: "error",
|
|
1156
|
+
});
|
|
1157
|
+
if (invalid)
|
|
1158
|
+
return failure("CAP_INTERNAL_INVOCATION_INVALID", "internal");
|
|
1159
|
+
let root;
|
|
1160
|
+
try {
|
|
1161
|
+
root = ownData(request, requestKeys);
|
|
1162
|
+
}
|
|
1163
|
+
catch {
|
|
1164
|
+
return failure("CAP_INTERNAL_INVOCATION_INVALID", "internal");
|
|
1165
|
+
}
|
|
1166
|
+
let adapterCandidate;
|
|
1167
|
+
let adapterTimeoutMs;
|
|
1168
|
+
const rootControlPresence = {
|
|
1169
|
+
confirmationToken: Object.hasOwn(root, "confirmationToken"),
|
|
1170
|
+
idempotencyKey: Object.hasOwn(root, "idempotencyKey"),
|
|
1171
|
+
correlationId: Object.hasOwn(root, "correlationId"),
|
|
1172
|
+
};
|
|
1173
|
+
if (!bearerGuard.validateCorrelationHint(root.correlationId, Object.hasOwn(root, "correlationId")).valid)
|
|
1174
|
+
return failure("CAP_INPUT_INVALID", "invalid_argument", {
|
|
1175
|
+
path: "/correlationId",
|
|
1176
|
+
});
|
|
1177
|
+
// The ingress hint is intentionally excluded from every later operation value.
|
|
1178
|
+
delete root.correlationId;
|
|
1179
|
+
if (typeof root.capability !== "string" ||
|
|
1180
|
+
!["http", "cli", "mcp", "internal", "sdk"].includes(root.source) ||
|
|
1181
|
+
(root.version !== undefined && typeof root.version !== "string"))
|
|
1182
|
+
return failure("CAP_INTERNAL_INVOCATION_INVALID", "internal");
|
|
1183
|
+
const entry = registry.entries.get(root.capability);
|
|
1184
|
+
if (!entry)
|
|
1185
|
+
return failure("CAP_NOT_FOUND", "not_found");
|
|
1186
|
+
const { capability, validators, handler } = entry;
|
|
1187
|
+
const source = root.source;
|
|
1188
|
+
const stage = (name, skipped = false) => notify(onStage, {
|
|
1189
|
+
stage: name,
|
|
1190
|
+
skipped,
|
|
1191
|
+
capability: capability.id,
|
|
1192
|
+
version: capability.version,
|
|
1193
|
+
source,
|
|
1194
|
+
correlationId,
|
|
1195
|
+
});
|
|
1196
|
+
stage("resolve");
|
|
1197
|
+
const protectedTarget = capability.access.authentication !== "public" ||
|
|
1198
|
+
capability.access.permissions.public !== true ||
|
|
1199
|
+
capability.access.exposure[source === "sdk" ? "http" : source] !==
|
|
1200
|
+
"public";
|
|
1201
|
+
const denial = (code, status, unavailable = false) => conceal && protectedTarget
|
|
1202
|
+
? failure("CAP_NOT_FOUND", "not_found")
|
|
1203
|
+
: failure(code, status, undefined, unavailable
|
|
1204
|
+
? "Required runtime provider is unavailable."
|
|
1205
|
+
: "Invocation failed.", unavailable);
|
|
1206
|
+
if (root.version !== undefined && root.version !== capability.version)
|
|
1207
|
+
return conceal && protectedTarget
|
|
1208
|
+
? failure("CAP_NOT_FOUND", "not_found")
|
|
1209
|
+
: failure("CAP_VERSION_UNSUPPORTED", "failed_precondition");
|
|
1210
|
+
if (ingress && !ingress.adapter.capabilities.includes(capability.id))
|
|
1211
|
+
return denial("CAP_UNAUTHENTICATED", "unauthenticated");
|
|
1212
|
+
const exposure = capability.access.exposure[source === "sdk" ? "http" : source];
|
|
1213
|
+
if (exposure === "disabled")
|
|
1214
|
+
return failure("CAP_NOT_FOUND", "not_found");
|
|
1215
|
+
if (source !== "internal" &&
|
|
1216
|
+
(!capability.interfaces[source].enabled ||
|
|
1217
|
+
(source === "sdk" && !capability.interfaces.http.enabled)))
|
|
1218
|
+
return failure("CAP_NOT_FOUND", "not_found");
|
|
1219
|
+
if (root.adapterCandidate !== undefined) {
|
|
1220
|
+
try {
|
|
1221
|
+
const candidate = ownData(root.adapterCandidate, adapterCandidateKeys);
|
|
1222
|
+
if (candidate.ok === true) {
|
|
1223
|
+
if (!Object.hasOwn(candidate, "input") ||
|
|
1224
|
+
Object.keys(candidate).some((key) => !["ok", "input", "controls"].includes(key)))
|
|
1225
|
+
throw new Error("adapter_candidate");
|
|
1226
|
+
if (Object.hasOwn(candidate, "controls")) {
|
|
1227
|
+
const controls = ownData(candidate.controls, adapterControlKeys);
|
|
1228
|
+
for (const key of [
|
|
1229
|
+
"confirmationToken",
|
|
1230
|
+
"idempotencyKey",
|
|
1231
|
+
"correlationId",
|
|
1232
|
+
]) {
|
|
1233
|
+
if (!Object.hasOwn(controls, key))
|
|
1234
|
+
continue;
|
|
1235
|
+
if (typeof controls[key] !== "string" || rootControlPresence[key])
|
|
1236
|
+
throw new Error("adapter_controls");
|
|
1237
|
+
root[key] = controls[key];
|
|
1238
|
+
}
|
|
1239
|
+
if (Object.hasOwn(controls, "timeoutMs")) {
|
|
1240
|
+
if (!Number.isSafeInteger(controls.timeoutMs) ||
|
|
1241
|
+
controls.timeoutMs <= 0)
|
|
1242
|
+
throw new Error("adapter_controls");
|
|
1243
|
+
adapterTimeoutMs = controls.timeoutMs;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
adapterCandidate = Object.freeze({
|
|
1247
|
+
ok: true,
|
|
1248
|
+
input: candidate.input,
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
else if (candidate.ok === false) {
|
|
1252
|
+
if (!Object.hasOwn(candidate, "code") ||
|
|
1253
|
+
!Object.hasOwn(candidate, "status") ||
|
|
1254
|
+
Object.keys(candidate).some((key) => !["ok", "code", "status", "safeDetails"].includes(key)))
|
|
1255
|
+
throw new Error("adapter_candidate");
|
|
1256
|
+
const inputInvalid = candidate.code === "CAP_INPUT_INVALID" &&
|
|
1257
|
+
(candidate.status === "invalid_argument" ||
|
|
1258
|
+
candidate.status === 400);
|
|
1259
|
+
const metadataRequired = candidate.code === "CAP_MCP_CLIENT_METADATA_REQUIRED" &&
|
|
1260
|
+
candidate.status === "failed_precondition";
|
|
1261
|
+
if (!inputInvalid && !metadataRequired)
|
|
1262
|
+
throw new Error("adapter_rejection");
|
|
1263
|
+
adapterCandidate = Object.freeze({
|
|
1264
|
+
ok: false,
|
|
1265
|
+
code: candidate.code,
|
|
1266
|
+
status: inputInvalid ? "invalid_argument" : "failed_precondition",
|
|
1267
|
+
...(Object.hasOwn(candidate, "safeDetails")
|
|
1268
|
+
? { safeDetails: boundedAdapterDetails(candidate.safeDetails) }
|
|
1269
|
+
: {}),
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
else
|
|
1273
|
+
throw new Error("adapter_candidate");
|
|
1274
|
+
}
|
|
1275
|
+
catch {
|
|
1276
|
+
return failure("CAP_INTERNAL_INVOCATION_INVALID", "internal");
|
|
1277
|
+
}
|
|
1278
|
+
delete root.adapterCandidate;
|
|
1279
|
+
}
|
|
1280
|
+
if (!bearerGuard.validateCorrelationHint(root.correlationId, Object.hasOwn(root, "correlationId")).valid)
|
|
1281
|
+
return failure("CAP_INPUT_INVALID", "invalid_argument", {
|
|
1282
|
+
path: "/correlationId",
|
|
1283
|
+
});
|
|
1284
|
+
delete root.correlationId;
|
|
1285
|
+
stage("context");
|
|
1286
|
+
if (root.signal !== undefined && !validSignal(root.signal))
|
|
1287
|
+
return failure("CAP_INTERNAL_INVOCATION_INVALID", "internal");
|
|
1288
|
+
let requestedDeadline = Infinity;
|
|
1289
|
+
if (root.deadline !== undefined) {
|
|
1290
|
+
try {
|
|
1291
|
+
if (typeof root.deadline !== "object" ||
|
|
1292
|
+
root.deadline === null ||
|
|
1293
|
+
types.isProxy(root.deadline))
|
|
1294
|
+
throw new Error("deadline");
|
|
1295
|
+
requestedDeadline = getDateTime.call(root.deadline);
|
|
1296
|
+
if (!Number.isFinite(requestedDeadline))
|
|
1297
|
+
throw new Error("deadline");
|
|
1298
|
+
}
|
|
1299
|
+
catch {
|
|
1300
|
+
return failure("CAP_INTERNAL_INVOCATION_INVALID", "internal");
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
if (adapterTimeoutMs !== undefined)
|
|
1304
|
+
requestedDeadline = Math.min(requestedDeadline, startedAt + adapterTimeoutMs);
|
|
1305
|
+
const deadlineMs = Math.min(requestedDeadline, parent?.deadlineMs ?? Infinity, Date.now() + Math.min(capability.execution.timeoutMs ?? 30_000, 30_000));
|
|
1306
|
+
const controller = new AbortController();
|
|
1307
|
+
let interrupted;
|
|
1308
|
+
let started = false;
|
|
1309
|
+
let confirmationAttempt;
|
|
1310
|
+
let idempotencyClaim;
|
|
1311
|
+
let idempotencyEntered = false;
|
|
1312
|
+
let terminalCompletion;
|
|
1313
|
+
let settleInterrupt;
|
|
1314
|
+
const interruption = new Promise((resolve) => {
|
|
1315
|
+
settleInterrupt = resolve;
|
|
1316
|
+
});
|
|
1317
|
+
const interrupt = (code) => {
|
|
1318
|
+
if (interrupted)
|
|
1319
|
+
return;
|
|
1320
|
+
interrupted = code;
|
|
1321
|
+
controller.abort();
|
|
1322
|
+
settleInterrupt?.(failure(code, code === "CAP_CANCELLED" ? "cancelled" : "deadline_exceeded", { executionState: started ? "started" : "not_started" }));
|
|
1323
|
+
};
|
|
1324
|
+
const parentSignal = root.signal;
|
|
1325
|
+
const ancestorSignal = parent?.signal;
|
|
1326
|
+
const cancel = () => interrupt("CAP_CANCELLED");
|
|
1327
|
+
let establishedParentEvents;
|
|
1328
|
+
const observeCancellation = () => {
|
|
1329
|
+
parent?.observeCancellation();
|
|
1330
|
+
if (parentSignal &&
|
|
1331
|
+
(!validSignal(parentSignal, establishedParentEvents) ||
|
|
1332
|
+
abortedGetter?.call(parentSignal)))
|
|
1333
|
+
interrupt("CAP_CANCELLED");
|
|
1334
|
+
};
|
|
1335
|
+
try {
|
|
1336
|
+
if (parentSignal) {
|
|
1337
|
+
addListener.call(parentSignal, "abort", cancel, { once: true });
|
|
1338
|
+
establishedParentEvents = establishedSignalEvents(parentSignal);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
catch {
|
|
1342
|
+
try {
|
|
1343
|
+
if (parentSignal && validSignal(parentSignal, establishedParentEvents))
|
|
1344
|
+
removeListener.call(parentSignal, "abort", cancel);
|
|
1345
|
+
}
|
|
1346
|
+
catch {
|
|
1347
|
+
/* Malformed native event state is contained. */
|
|
1348
|
+
}
|
|
1349
|
+
return failure("CAP_INTERNAL_INVOCATION_INVALID", "internal");
|
|
1350
|
+
}
|
|
1351
|
+
if (ancestorSignal)
|
|
1352
|
+
addListener.call(ancestorSignal, "abort", cancel, { once: true });
|
|
1353
|
+
const timer = setTimeout(() => interrupt("CAP_DEADLINE_EXCEEDED"), Math.max(0, deadlineMs - Date.now()));
|
|
1354
|
+
// A pending invocation is real work: retain the timer until it settles.
|
|
1355
|
+
observeCancellation();
|
|
1356
|
+
if ((parentSignal &&
|
|
1357
|
+
(!validSignal(parentSignal, establishedParentEvents) ||
|
|
1358
|
+
abortedGetter?.call(parentSignal))) ||
|
|
1359
|
+
(ancestorSignal && abortedGetter?.call(ancestorSignal)))
|
|
1360
|
+
cancel();
|
|
1361
|
+
if (deadlineMs <= Date.now())
|
|
1362
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
1363
|
+
let result;
|
|
1364
|
+
try {
|
|
1365
|
+
result = await (async () => {
|
|
1366
|
+
if (interrupted)
|
|
1367
|
+
return await interruption;
|
|
1368
|
+
const serviceName = registry.document.service.name;
|
|
1369
|
+
const currentNode = Object.freeze({
|
|
1370
|
+
service: serviceName,
|
|
1371
|
+
capability: capability.id,
|
|
1372
|
+
exactVersion: capability.version,
|
|
1373
|
+
});
|
|
1374
|
+
const node = `${serviceName}@${capability.id}@${capability.version}`;
|
|
1375
|
+
let identity = parent
|
|
1376
|
+
? Object.freeze({
|
|
1377
|
+
...parent.identity,
|
|
1378
|
+
provenance: Object.freeze([
|
|
1379
|
+
...parent.identity.provenance,
|
|
1380
|
+
Object.freeze({
|
|
1381
|
+
mode: transition?.mode ?? "delegate",
|
|
1382
|
+
caller: parent.caller.capability,
|
|
1383
|
+
target: capability.id,
|
|
1384
|
+
}),
|
|
1385
|
+
]),
|
|
1386
|
+
})
|
|
1387
|
+
: rootIdentity(anonymousPrincipal);
|
|
1388
|
+
const sourceChain = Object.freeze([
|
|
1389
|
+
...(parent?.sourceChain ?? []),
|
|
1390
|
+
Object.freeze({
|
|
1391
|
+
source,
|
|
1392
|
+
capability: capability.id,
|
|
1393
|
+
exactVersion: capability.version,
|
|
1394
|
+
}),
|
|
1395
|
+
]);
|
|
1396
|
+
const traceId = parent?.traceId ?? correlationId;
|
|
1397
|
+
const spanId = correlationId;
|
|
1398
|
+
const operationView = () => {
|
|
1399
|
+
const follower = new AbortController();
|
|
1400
|
+
const follow = () => {
|
|
1401
|
+
try {
|
|
1402
|
+
follower.abort();
|
|
1403
|
+
}
|
|
1404
|
+
catch {
|
|
1405
|
+
/* An isolated provider signal cannot compromise control. */
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
addListener.call(controller.signal, "abort", follow, { once: true });
|
|
1409
|
+
if (abortedGetter?.call(controller.signal))
|
|
1410
|
+
follow();
|
|
1411
|
+
return Object.freeze({
|
|
1412
|
+
capability: Object.freeze({
|
|
1413
|
+
id: capability.id,
|
|
1414
|
+
version: capability.version,
|
|
1415
|
+
access: capability.access,
|
|
1416
|
+
}),
|
|
1417
|
+
identity,
|
|
1418
|
+
sourceChain,
|
|
1419
|
+
correlationId,
|
|
1420
|
+
traceId,
|
|
1421
|
+
spanId,
|
|
1422
|
+
...(parent ? { parentSpanId: parent.spanId } : {}),
|
|
1423
|
+
deadlineMs,
|
|
1424
|
+
signal: follower.signal,
|
|
1425
|
+
});
|
|
1426
|
+
};
|
|
1427
|
+
const awaitProvider = async (callback) => {
|
|
1428
|
+
observeCancellation();
|
|
1429
|
+
if (interrupted)
|
|
1430
|
+
return await interruption;
|
|
1431
|
+
const pending = providerValue(callback);
|
|
1432
|
+
const outcome = await Promise.race([pending, interruption]);
|
|
1433
|
+
observeCancellation();
|
|
1434
|
+
if (interrupted || deadlineMs <= Date.now()) {
|
|
1435
|
+
if (!interrupted)
|
|
1436
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
1437
|
+
return await interruption;
|
|
1438
|
+
}
|
|
1439
|
+
return outcome;
|
|
1440
|
+
};
|
|
1441
|
+
let auditOwnerId;
|
|
1442
|
+
let cachedFingerprint;
|
|
1443
|
+
let serviceAssumed = false;
|
|
1444
|
+
const operationControls = () => Object.freeze({
|
|
1445
|
+
deadlineMs,
|
|
1446
|
+
signal: operationView().signal,
|
|
1447
|
+
});
|
|
1448
|
+
const acquireFingerprint = async () => {
|
|
1449
|
+
if (cachedFingerprint?.identity === identity)
|
|
1450
|
+
return { value: cachedFingerprint.set };
|
|
1451
|
+
if (!fingerprintProvider)
|
|
1452
|
+
return { failed: true, reason: "provider_missing" };
|
|
1453
|
+
const acquired = await awaitProvider(() => fingerprintProvider.fingerprint(identity, operationControls()));
|
|
1454
|
+
if ("ok" in acquired)
|
|
1455
|
+
return acquired;
|
|
1456
|
+
if ("failed" in acquired)
|
|
1457
|
+
return { failed: true, reason: "provider_failed" };
|
|
1458
|
+
try {
|
|
1459
|
+
const value = ownData(acquired.value, [
|
|
1460
|
+
"generationId",
|
|
1461
|
+
"originatingFingerprint",
|
|
1462
|
+
"effectiveFingerprint",
|
|
1463
|
+
"requesterFingerprint",
|
|
1464
|
+
"tenantFingerprint",
|
|
1465
|
+
]);
|
|
1466
|
+
if (Object.keys(value).length !== 5 ||
|
|
1467
|
+
!canonicalIdentityId(value.generationId) ||
|
|
1468
|
+
[
|
|
1469
|
+
value.originatingFingerprint,
|
|
1470
|
+
value.effectiveFingerprint,
|
|
1471
|
+
value.requesterFingerprint,
|
|
1472
|
+
value.tenantFingerprint,
|
|
1473
|
+
].some((item) => {
|
|
1474
|
+
try {
|
|
1475
|
+
decodeCanonicalBase64Url256(item);
|
|
1476
|
+
return false;
|
|
1477
|
+
}
|
|
1478
|
+
catch {
|
|
1479
|
+
return true;
|
|
1480
|
+
}
|
|
1481
|
+
}))
|
|
1482
|
+
throw new Error("fingerprint");
|
|
1483
|
+
const set = Object.freeze({
|
|
1484
|
+
generationId: value.generationId,
|
|
1485
|
+
originatingFingerprint: value.originatingFingerprint,
|
|
1486
|
+
effectiveFingerprint: value.effectiveFingerprint,
|
|
1487
|
+
requesterFingerprint: value.requesterFingerprint,
|
|
1488
|
+
tenantFingerprint: value.tenantFingerprint,
|
|
1489
|
+
});
|
|
1490
|
+
cachedFingerprint = { identity, set };
|
|
1491
|
+
return { value: set };
|
|
1492
|
+
}
|
|
1493
|
+
catch {
|
|
1494
|
+
return { failed: true, reason: "provider_malformed" };
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
const recordFingerprintIncident = async (reason) => {
|
|
1498
|
+
observeCancellation();
|
|
1499
|
+
if (interrupted)
|
|
1500
|
+
return await interruption;
|
|
1501
|
+
if (deadlineMs <= Date.now()) {
|
|
1502
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
1503
|
+
return await interruption;
|
|
1504
|
+
}
|
|
1505
|
+
if (!reportFingerprintFailure)
|
|
1506
|
+
return undefined;
|
|
1507
|
+
const reported = await awaitProvider(() => reportFingerprintFailure(Object.freeze({
|
|
1508
|
+
providerKind: "identity_fingerprint",
|
|
1509
|
+
operation: "fingerprint",
|
|
1510
|
+
reason,
|
|
1511
|
+
}), operationControls()));
|
|
1512
|
+
if ("ok" in reported)
|
|
1513
|
+
return reported;
|
|
1514
|
+
return undefined;
|
|
1515
|
+
};
|
|
1516
|
+
const owner = (mandatory) => {
|
|
1517
|
+
if (auditOwnerId)
|
|
1518
|
+
return auditOwnerId;
|
|
1519
|
+
try {
|
|
1520
|
+
auditOwnerId = randomBytes(32).toString("base64url");
|
|
1521
|
+
return auditOwnerId;
|
|
1522
|
+
}
|
|
1523
|
+
catch {
|
|
1524
|
+
if (mandatory)
|
|
1525
|
+
identityUnavailable = true;
|
|
1526
|
+
return undefined;
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
const eventId = (ownerId, eventType, transitionOrdinal) => `cape1.${createHash("sha256")
|
|
1530
|
+
.update(jcs({
|
|
1531
|
+
service: serviceName,
|
|
1532
|
+
deployment: deployment,
|
|
1533
|
+
ownerType: "internal_invocation",
|
|
1534
|
+
ownerId,
|
|
1535
|
+
eventType,
|
|
1536
|
+
transitionOrdinal,
|
|
1537
|
+
}))
|
|
1538
|
+
.digest("base64url")}`;
|
|
1539
|
+
const buildAuditEvent = (ownerId, fingerprints, event) => Object.freeze({
|
|
1540
|
+
eventId: eventId(ownerId, event.eventType, event.transitionOrdinal),
|
|
1541
|
+
eventType: event.eventType,
|
|
1542
|
+
outcome: event.outcome,
|
|
1543
|
+
...(event.reason === undefined ? {} : { reason: event.reason }),
|
|
1544
|
+
service: serviceName,
|
|
1545
|
+
ownerType: "internal_invocation",
|
|
1546
|
+
ownerId,
|
|
1547
|
+
transitionOrdinal: event.transitionOrdinal,
|
|
1548
|
+
stage: event.stage,
|
|
1549
|
+
...(transition?.policyId === undefined
|
|
1550
|
+
? {}
|
|
1551
|
+
: { policyId: transition.policyId }),
|
|
1552
|
+
caller: parent.caller,
|
|
1553
|
+
target: currentNode,
|
|
1554
|
+
deployment: deployment,
|
|
1555
|
+
trace: Object.freeze({
|
|
1556
|
+
traceId,
|
|
1557
|
+
spanId,
|
|
1558
|
+
parentSpanId: parent.spanId,
|
|
1559
|
+
}),
|
|
1560
|
+
originatingFingerprint: fingerprints.originatingFingerprint,
|
|
1561
|
+
effectiveFingerprint: fingerprints.effectiveFingerprint,
|
|
1562
|
+
requesterFingerprint: fingerprints.requesterFingerprint,
|
|
1563
|
+
tenantFingerprint: fingerprints.tenantFingerprint,
|
|
1564
|
+
fingerprintGenerationId: fingerprints.generationId,
|
|
1565
|
+
sourceChain,
|
|
1566
|
+
correlationId,
|
|
1567
|
+
});
|
|
1568
|
+
const rejection = async (selected, reason, auditStage, outcome) => {
|
|
1569
|
+
if (!parent)
|
|
1570
|
+
return selected;
|
|
1571
|
+
if (!journal || !fingerprintProvider || !deployment) {
|
|
1572
|
+
diagnostic("rejection_audit_unavailable");
|
|
1573
|
+
return selected;
|
|
1574
|
+
}
|
|
1575
|
+
const ownerId = owner(false);
|
|
1576
|
+
if (!ownerId) {
|
|
1577
|
+
diagnostic("rejection_audit_unavailable");
|
|
1578
|
+
return selected;
|
|
1579
|
+
}
|
|
1580
|
+
const fingerprint = await acquireFingerprint();
|
|
1581
|
+
if ("ok" in fingerprint)
|
|
1582
|
+
return fingerprint;
|
|
1583
|
+
if ("failed" in fingerprint) {
|
|
1584
|
+
diagnostic("rejection_audit_unavailable");
|
|
1585
|
+
return selected;
|
|
1586
|
+
}
|
|
1587
|
+
const event = buildAuditEvent(ownerId, fingerprint.value, {
|
|
1588
|
+
eventType: "internal_invocation_rejected",
|
|
1589
|
+
outcome,
|
|
1590
|
+
reason,
|
|
1591
|
+
stage: auditStage,
|
|
1592
|
+
transitionOrdinal: serviceAssumed ? 2 : 1,
|
|
1593
|
+
});
|
|
1594
|
+
const committed = await awaitProvider(() => journal.commit(event, operationControls()));
|
|
1595
|
+
if ("ok" in committed)
|
|
1596
|
+
return committed;
|
|
1597
|
+
if ("failed" in committed || committed.value !== undefined)
|
|
1598
|
+
diagnostic("rejection_audit_unavailable");
|
|
1599
|
+
return selected;
|
|
1600
|
+
};
|
|
1601
|
+
if (parent?.ancestry.includes(node))
|
|
1602
|
+
return await rejection(failure("CAP_INTERNAL_INVOCATION_CYCLE", "failed_precondition"), "cycle_detected", "cycle_check", "rejected");
|
|
1603
|
+
if (parent && parent.depth + 1 > maxDepth)
|
|
1604
|
+
return await rejection(failure("CAP_INTERNAL_INVOCATION_DEPTH_EXCEEDED", "resource_exhausted"), "depth_exceeded", "depth_check", "rejected");
|
|
1605
|
+
if (parent && transition && transition.mode !== "delegate") {
|
|
1606
|
+
const transitionRequest = transition;
|
|
1607
|
+
const transitionView = () => {
|
|
1608
|
+
const view = operationView();
|
|
1609
|
+
return Object.freeze({
|
|
1610
|
+
caller: parent.caller,
|
|
1611
|
+
target: currentNode,
|
|
1612
|
+
mode: transitionRequest.mode,
|
|
1613
|
+
policyId: transitionRequest.policyId,
|
|
1614
|
+
identity,
|
|
1615
|
+
tenant: identity.effective.tenant === undefined
|
|
1616
|
+
? Object.freeze({ present: false })
|
|
1617
|
+
: Object.freeze({
|
|
1618
|
+
present: true,
|
|
1619
|
+
value: identity.effective.tenant,
|
|
1620
|
+
}),
|
|
1621
|
+
service: serviceName,
|
|
1622
|
+
deployment: deployment,
|
|
1623
|
+
sourceChain: Object.freeze([...sourceChain]),
|
|
1624
|
+
correlationId,
|
|
1625
|
+
trace: Object.freeze({
|
|
1626
|
+
traceId,
|
|
1627
|
+
spanId,
|
|
1628
|
+
parentSpanId: parent.spanId,
|
|
1629
|
+
}),
|
|
1630
|
+
deadlineMs,
|
|
1631
|
+
signal: view.signal,
|
|
1632
|
+
});
|
|
1633
|
+
};
|
|
1634
|
+
if (transitionRequest.mode === "derive") {
|
|
1635
|
+
const policy = derivePolicies.get(transitionRequest.policyId);
|
|
1636
|
+
if (!policy || !deployment)
|
|
1637
|
+
return await rejection(denial("CAP_PERMISSION_DENIED", "permission_denied"), "derive_policy_absent", "derive_policy", "denied");
|
|
1638
|
+
const decided = await awaitProvider(() => policy.derive(transitionView()));
|
|
1639
|
+
if ("ok" in decided)
|
|
1640
|
+
return decided;
|
|
1641
|
+
if ("failed" in decided)
|
|
1642
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "derive_provider_failed", "derive_policy", "unavailable");
|
|
1643
|
+
let decision;
|
|
1644
|
+
try {
|
|
1645
|
+
decision = ownData(decided.value, ["allowed", "principal"]);
|
|
1646
|
+
}
|
|
1647
|
+
catch {
|
|
1648
|
+
decision = Object.create(null);
|
|
1649
|
+
}
|
|
1650
|
+
if (decision.allowed === false &&
|
|
1651
|
+
Object.keys(decision).length === 1)
|
|
1652
|
+
return await rejection(denial("CAP_PERMISSION_DENIED", "permission_denied"), "derive_policy_denied", "derive_policy", "denied");
|
|
1653
|
+
if (decision.allowed !== true ||
|
|
1654
|
+
Object.keys(decision).length !== 2 ||
|
|
1655
|
+
!Object.hasOwn(decision, "principal"))
|
|
1656
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "derive_provider_failed", "derive_policy", "unavailable");
|
|
1657
|
+
let derived;
|
|
1658
|
+
try {
|
|
1659
|
+
derived = normalizePrincipal(decision.principal, policy.providerId);
|
|
1660
|
+
}
|
|
1661
|
+
catch {
|
|
1662
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "derive_provider_failed", "derive_identity", "unavailable");
|
|
1663
|
+
}
|
|
1664
|
+
if (derived.tenant !== identity.effective.tenant)
|
|
1665
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "tenant_transition_forbidden", "derive_identity", "unavailable");
|
|
1666
|
+
identity = Object.freeze({
|
|
1667
|
+
...identity,
|
|
1668
|
+
effective: derived,
|
|
1669
|
+
authorityChain: Object.freeze([
|
|
1670
|
+
...identity.authorityChain,
|
|
1671
|
+
derived,
|
|
1672
|
+
]),
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
else {
|
|
1676
|
+
const policy = servicePolicies.get(transitionRequest.policyId);
|
|
1677
|
+
if (!policy || !deployment)
|
|
1678
|
+
return await rejection(denial("CAP_PERMISSION_DENIED", "permission_denied"), "service_policy_absent", "service_policy", "denied");
|
|
1679
|
+
const decided = await awaitProvider(() => policy.evaluate(transitionView()));
|
|
1680
|
+
if ("ok" in decided)
|
|
1681
|
+
return decided;
|
|
1682
|
+
if ("failed" in decided || typeof decided.value !== "boolean")
|
|
1683
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "service_policy_failed", "service_policy", "unavailable");
|
|
1684
|
+
if (!decided.value)
|
|
1685
|
+
return await rejection(denial("CAP_PERMISSION_DENIED", "permission_denied"), "service_policy_denied", "service_policy", "denied");
|
|
1686
|
+
if (!serviceIdentity)
|
|
1687
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "service_identity_unavailable", "service_identity", "unavailable");
|
|
1688
|
+
const resolved = await awaitProvider(() => serviceIdentity.resolve(transitionView()));
|
|
1689
|
+
if ("ok" in resolved)
|
|
1690
|
+
return resolved;
|
|
1691
|
+
let servicePrincipal;
|
|
1692
|
+
try {
|
|
1693
|
+
if ("failed" in resolved)
|
|
1694
|
+
throw new Error("provider");
|
|
1695
|
+
const supplied = normalizePrincipal(resolved.value, serviceIdentity.id);
|
|
1696
|
+
if (supplied.tenant !== undefined)
|
|
1697
|
+
throw new Error("tenant");
|
|
1698
|
+
servicePrincipal = Object.freeze({
|
|
1699
|
+
...supplied,
|
|
1700
|
+
...(identity.effective.tenant === undefined
|
|
1701
|
+
? {}
|
|
1702
|
+
: { tenant: identity.effective.tenant }),
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
catch {
|
|
1706
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "service_identity_unavailable", "service_identity", "unavailable");
|
|
1707
|
+
}
|
|
1708
|
+
identity = Object.freeze({
|
|
1709
|
+
...identity,
|
|
1710
|
+
effective: servicePrincipal,
|
|
1711
|
+
authorityChain: Object.freeze([servicePrincipal]),
|
|
1712
|
+
});
|
|
1713
|
+
const ownerId = owner(true);
|
|
1714
|
+
if (!ownerId)
|
|
1715
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "service_audit_unavailable", "service_audit_identity", "unavailable");
|
|
1716
|
+
const fingerprint = await acquireFingerprint();
|
|
1717
|
+
if ("ok" in fingerprint)
|
|
1718
|
+
return fingerprint;
|
|
1719
|
+
if ("failed" in fingerprint)
|
|
1720
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "service_audit_unavailable", "service_audit_fingerprint", "unavailable");
|
|
1721
|
+
if (!journal)
|
|
1722
|
+
return denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true);
|
|
1723
|
+
const event = buildAuditEvent(ownerId, fingerprint.value, {
|
|
1724
|
+
eventType: "service_identity_assumed",
|
|
1725
|
+
outcome: "allowed",
|
|
1726
|
+
stage: "service_identity_transition",
|
|
1727
|
+
transitionOrdinal: 1,
|
|
1728
|
+
});
|
|
1729
|
+
const committed = await awaitProvider(() => journal.commit(event, operationControls()));
|
|
1730
|
+
if ("ok" in committed)
|
|
1731
|
+
return committed;
|
|
1732
|
+
if ("failed" in committed || committed.value !== undefined)
|
|
1733
|
+
return await rejection(denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true), "service_audit_unavailable", "service_audit_commit", "unavailable");
|
|
1734
|
+
serviceAssumed = true;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
stage("authenticate", !parent &&
|
|
1738
|
+
!ingress &&
|
|
1739
|
+
root.principal === undefined &&
|
|
1740
|
+
capability.access.authentication === "public");
|
|
1741
|
+
if (!parent && root.principal !== undefined) {
|
|
1742
|
+
const held = typeof root.principal === "object" &&
|
|
1743
|
+
root.principal !== null &&
|
|
1744
|
+
!types.isProxy(root.principal)
|
|
1745
|
+
? tokens.get(root.principal)
|
|
1746
|
+
: undefined;
|
|
1747
|
+
if (!held ||
|
|
1748
|
+
!ingress ||
|
|
1749
|
+
held.adapter !== ingress.adapter ||
|
|
1750
|
+
held.capability !== capability.id ||
|
|
1751
|
+
ingress.hasCredentials)
|
|
1752
|
+
return denial("CAP_UNAUTHENTICATED", "unauthenticated");
|
|
1753
|
+
identity = held.identity;
|
|
1754
|
+
}
|
|
1755
|
+
else if (ingress?.hasCredentials) {
|
|
1756
|
+
const authenticate = providers.get(ingress.adapter.providerId);
|
|
1757
|
+
const outcome = await awaitProvider(() => authenticate(ingress.credentials, operationView()));
|
|
1758
|
+
if ("ok" in outcome)
|
|
1759
|
+
return outcome;
|
|
1760
|
+
if ("failed" in outcome) {
|
|
1761
|
+
diagnostic("authentication_provider_failed");
|
|
1762
|
+
return denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true);
|
|
1763
|
+
}
|
|
1764
|
+
if (outcome.value === null)
|
|
1765
|
+
return denial("CAP_UNAUTHENTICATED", "unauthenticated");
|
|
1766
|
+
try {
|
|
1767
|
+
identity = rootIdentity(normalizePrincipal(outcome.value, ingress.adapter.providerId));
|
|
1768
|
+
}
|
|
1769
|
+
catch {
|
|
1770
|
+
diagnostic("authentication_provider_failed");
|
|
1771
|
+
return denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
if ((capability.access.authentication === "required" ||
|
|
1775
|
+
exposure === "authenticated") &&
|
|
1776
|
+
identity.effective.type === "anonymous")
|
|
1777
|
+
return denial("CAP_UNAUTHENTICATED", "unauthenticated");
|
|
1778
|
+
stage("authorize", capability.access.permissions.public === true &&
|
|
1779
|
+
exposure === "public" &&
|
|
1780
|
+
!authorizationProvider);
|
|
1781
|
+
if (exposure === "private" &&
|
|
1782
|
+
!(parent?.privateBoundary ?? ingress?.adapter.privateBoundary))
|
|
1783
|
+
return parent
|
|
1784
|
+
? await rejection(denial("CAP_PERMISSION_DENIED", "permission_denied"), "authority_chain_denied", "authorization", "denied")
|
|
1785
|
+
: denial("CAP_PERMISSION_DENIED", "permission_denied");
|
|
1786
|
+
const authorization = await authorizePrincipals(identity.authorityChain, capability.access.permissions, authorizationProvider
|
|
1787
|
+
? (principal) => awaitProvider(() => authorizationProvider(operationView(), principal))
|
|
1788
|
+
: undefined);
|
|
1789
|
+
if ("ok" in authorization)
|
|
1790
|
+
return authorization;
|
|
1791
|
+
if (authorization.kind === "failed") {
|
|
1792
|
+
diagnostic("authorization_provider_failed");
|
|
1793
|
+
const selected = denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true);
|
|
1794
|
+
return parent
|
|
1795
|
+
? await rejection(selected, "authorization_provider_failed", "authorization", "unavailable")
|
|
1796
|
+
: selected;
|
|
1797
|
+
}
|
|
1798
|
+
if (authorization.kind === "deny")
|
|
1799
|
+
return parent
|
|
1800
|
+
? await rejection(denial("CAP_PERMISSION_DENIED", "permission_denied"), "authority_chain_denied", "authorization", "denied")
|
|
1801
|
+
: denial("CAP_PERMISSION_DENIED", "permission_denied");
|
|
1802
|
+
if (interrupted)
|
|
1803
|
+
return await interruption;
|
|
1804
|
+
let kernelInvocationId;
|
|
1805
|
+
if (confirmationProvider) {
|
|
1806
|
+
const allocated = await awaitProvider(() => confirmationProvider.allocateKernelInvocationId({
|
|
1807
|
+
signal: operationView().signal,
|
|
1808
|
+
refreshCancellation: observeCancellation,
|
|
1809
|
+
deadlineMs,
|
|
1810
|
+
correlationId,
|
|
1811
|
+
}));
|
|
1812
|
+
if ("ok" in allocated)
|
|
1813
|
+
return allocated;
|
|
1814
|
+
if ("failed" in allocated)
|
|
1815
|
+
return denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true);
|
|
1816
|
+
const result = confirmationResponse(allocated.value, "allocation");
|
|
1817
|
+
if (!result.ok)
|
|
1818
|
+
return denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true);
|
|
1819
|
+
kernelInvocationId = result.kernelInvocationId;
|
|
1820
|
+
}
|
|
1821
|
+
const rate = capability.limits.rateLimit;
|
|
1822
|
+
stage("admission", !rate);
|
|
1823
|
+
if (rate) {
|
|
1824
|
+
const checked = await awaitProvider(() => rateCheck(Object.freeze({
|
|
1825
|
+
...operationView(),
|
|
1826
|
+
policy: rate.policy,
|
|
1827
|
+
cost: rate.cost ?? 1,
|
|
1828
|
+
})));
|
|
1829
|
+
if ("ok" in checked)
|
|
1830
|
+
return checked;
|
|
1831
|
+
if ("failed" in checked) {
|
|
1832
|
+
if (!(failOpen.has(capability.id) &&
|
|
1833
|
+
(parent?.privateBoundary ?? ingress?.adapter.privateBoundary) ===
|
|
1834
|
+
true))
|
|
1835
|
+
return denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true);
|
|
1836
|
+
diagnostic("rate_limit_provider_failed_open");
|
|
1837
|
+
}
|
|
1838
|
+
else {
|
|
1839
|
+
try {
|
|
1840
|
+
const decision = ownData(checked.value, [
|
|
1841
|
+
"allowed",
|
|
1842
|
+
"retryAfterMs",
|
|
1843
|
+
"limit",
|
|
1844
|
+
"remaining",
|
|
1845
|
+
"resetAt",
|
|
1846
|
+
]);
|
|
1847
|
+
if (decision.allowed === true &&
|
|
1848
|
+
Object.keys(decision).length === 1) {
|
|
1849
|
+
/* admitted */
|
|
1850
|
+
}
|
|
1851
|
+
else if (decision.allowed === false) {
|
|
1852
|
+
if (!Number.isSafeInteger(decision.retryAfterMs) ||
|
|
1853
|
+
decision.retryAfterMs < 0)
|
|
1854
|
+
throw new Error("retry");
|
|
1855
|
+
for (const key of ["limit", "remaining"])
|
|
1856
|
+
if (decision[key] !== undefined &&
|
|
1857
|
+
(!Number.isSafeInteger(decision[key]) ||
|
|
1858
|
+
decision[key] < 0))
|
|
1859
|
+
throw new Error("metadata");
|
|
1860
|
+
if (decision.remaining !== undefined &&
|
|
1861
|
+
decision.limit !== undefined &&
|
|
1862
|
+
decision.remaining > decision.limit)
|
|
1863
|
+
throw new Error("metadata");
|
|
1864
|
+
if (decision.resetAt !== undefined &&
|
|
1865
|
+
(typeof decision.resetAt !== "string" ||
|
|
1866
|
+
decision.resetAt.length > 32 ||
|
|
1867
|
+
!Number.isFinite(Date.parse(decision.resetAt)) ||
|
|
1868
|
+
new Date(decision.resetAt).toISOString() !==
|
|
1869
|
+
decision.resetAt))
|
|
1870
|
+
throw new Error("reset");
|
|
1871
|
+
return failure("CAP_RATE_LIMITED", "resource_exhausted", copyJson({
|
|
1872
|
+
retryAfterMs: decision.retryAfterMs,
|
|
1873
|
+
...(decision.limit === undefined
|
|
1874
|
+
? {}
|
|
1875
|
+
: { limit: decision.limit }),
|
|
1876
|
+
...(decision.remaining === undefined
|
|
1877
|
+
? {}
|
|
1878
|
+
: { remaining: decision.remaining }),
|
|
1879
|
+
...(decision.resetAt === undefined
|
|
1880
|
+
? {}
|
|
1881
|
+
: { resetAt: decision.resetAt }),
|
|
1882
|
+
}), "Rate limit exceeded.", true);
|
|
1883
|
+
}
|
|
1884
|
+
else
|
|
1885
|
+
throw new Error("decision");
|
|
1886
|
+
}
|
|
1887
|
+
catch {
|
|
1888
|
+
return denial("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", true);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
stage("parse");
|
|
1893
|
+
let input = root.input;
|
|
1894
|
+
if (adapterCandidate !== undefined) {
|
|
1895
|
+
if (!adapterCandidate.ok)
|
|
1896
|
+
return failure(adapterCandidate.code, adapterCandidate.status, adapterCandidate.safeDetails, adapterCandidate.code === "CAP_INPUT_INVALID"
|
|
1897
|
+
? "Invocation input is invalid."
|
|
1898
|
+
: "Required client metadata is unavailable.");
|
|
1899
|
+
if (Object.hasOwn(root, "input"))
|
|
1900
|
+
return failure("CAP_INTERNAL_INVOCATION_INVALID", "internal");
|
|
1901
|
+
input = adapterCandidate.input;
|
|
1902
|
+
}
|
|
1903
|
+
stage("input_validation");
|
|
1904
|
+
let canonical;
|
|
1905
|
+
try {
|
|
1906
|
+
canonical = canonicalizeInput(registry.document, capability, copyJson(input));
|
|
1907
|
+
}
|
|
1908
|
+
catch {
|
|
1909
|
+
return invalidInput();
|
|
1910
|
+
}
|
|
1911
|
+
if (!canonical.valid ||
|
|
1912
|
+
!validators.input.validate(canonical.value).accepted)
|
|
1913
|
+
return invalidInput();
|
|
1914
|
+
try {
|
|
1915
|
+
captureRedactionPaths(canonical.value, redactionPaths.get(capability.id) ?? [], redactionState);
|
|
1916
|
+
}
|
|
1917
|
+
catch {
|
|
1918
|
+
redactionState.failed = true;
|
|
1919
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", false);
|
|
1920
|
+
}
|
|
1921
|
+
stage("quota_preconditions", true);
|
|
1922
|
+
const keyed = capability.effects.idempotency === "key";
|
|
1923
|
+
stage("idempotency_inspect", !keyed && !Object.hasOwn(root, "idempotencyKey"));
|
|
1924
|
+
if (!keyed && Object.hasOwn(root, "idempotencyKey"))
|
|
1925
|
+
return failure("CAP_INPUT_INVALID", "invalid_argument", {
|
|
1926
|
+
path: "/idempotencyKey",
|
|
1927
|
+
code: "unsupported_control",
|
|
1928
|
+
});
|
|
1929
|
+
if (!keyed &&
|
|
1930
|
+
!["none", "intrinsic"].includes(capability.effects.idempotency))
|
|
1931
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable");
|
|
1932
|
+
let idempotencyAction;
|
|
1933
|
+
const providerFailure = (result) => {
|
|
1934
|
+
if (result.error.code === "CAP_DEPENDENCY_UNAVAILABLE")
|
|
1935
|
+
diagnostic("CAP_DEPENDENCY_UNAVAILABLE");
|
|
1936
|
+
return failure(result.error.code, result.error.status, undefined, result.error.message, result.error.retryable);
|
|
1937
|
+
};
|
|
1938
|
+
const replay = async (result) => {
|
|
1939
|
+
const terminal = result.terminal;
|
|
1940
|
+
if (capability.effects.confirmation === "required") {
|
|
1941
|
+
if (!result.confirmation || !confirmationProvider)
|
|
1942
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", false);
|
|
1943
|
+
const queried = await awaitProvider(() => confirmationProvider.queryExecutionCompletion(result.confirmation, terminal.kind === "success" ? "succeeded" : "failed"));
|
|
1944
|
+
if ("ok" in queried)
|
|
1945
|
+
return queried;
|
|
1946
|
+
const proof = confirmationResponse("failed" in queried ? undefined : queried.value, "query");
|
|
1947
|
+
if (!proof.ok)
|
|
1948
|
+
return providerFailure(proof);
|
|
1949
|
+
if (!proof.completed)
|
|
1950
|
+
return failure("CAP_IDEMPOTENCY_IN_PROGRESS", "failed_precondition");
|
|
1951
|
+
}
|
|
1952
|
+
stage("output_validation");
|
|
1953
|
+
try {
|
|
1954
|
+
if (terminal.kind === "success") {
|
|
1955
|
+
const storedValue = terminal.value;
|
|
1956
|
+
const value = redactSecrets(storedValue);
|
|
1957
|
+
if (!equalJson(storedValue, value) ||
|
|
1958
|
+
!validateSchemaValue(registry.document, capability.output, value).valid ||
|
|
1959
|
+
!validators.output.validate(value).accepted) {
|
|
1960
|
+
diagnostic("CAP_INVALID_HANDLER_OUTPUT");
|
|
1961
|
+
return failure("CAP_INVALID_HANDLER_OUTPUT", "internal");
|
|
1962
|
+
}
|
|
1963
|
+
stage("finalize", true);
|
|
1964
|
+
return { ok: true, value, correlationId };
|
|
1965
|
+
}
|
|
1966
|
+
const error = terminal.error;
|
|
1967
|
+
if (terminal.kind === "unexpected_error") {
|
|
1968
|
+
if (!["CAP_INTERNAL", "CAP_INVALID_HANDLER_OUTPUT"].includes(error.code) ||
|
|
1969
|
+
error.status !== "internal" ||
|
|
1970
|
+
error.message !== "Invocation failed." ||
|
|
1971
|
+
error.retryable !== false ||
|
|
1972
|
+
error.details !== undefined)
|
|
1973
|
+
throw new Error("terminal");
|
|
1974
|
+
stage("finalize", true);
|
|
1975
|
+
return failure(error.code, "internal");
|
|
1976
|
+
}
|
|
1977
|
+
const declaration = Object.hasOwn(capability.errors, error.code)
|
|
1978
|
+
? capability.errors[error.code]
|
|
1979
|
+
: undefined;
|
|
1980
|
+
if (!declaration ||
|
|
1981
|
+
error.status !== declaration.status ||
|
|
1982
|
+
error.message !== declaration.message ||
|
|
1983
|
+
error.retryable !== declaration.retryable)
|
|
1984
|
+
throw new Error("declaration");
|
|
1985
|
+
if (hasSensitiveString(error.code) ||
|
|
1986
|
+
hasSensitiveString(error.message))
|
|
1987
|
+
return failure("CAP_INTERNAL", "internal");
|
|
1988
|
+
let details;
|
|
1989
|
+
if (declaration.details) {
|
|
1990
|
+
details = redactJson(error.details, createRedactionState());
|
|
1991
|
+
const storedDetails = details;
|
|
1992
|
+
if (!validateSchemaValue(registry.document, declaration.details, details).valid ||
|
|
1993
|
+
!validators.errors.get(error.code)?.validate(details).accepted)
|
|
1994
|
+
throw new Error("details");
|
|
1995
|
+
details = redactSecrets(bearerGuard.redactValue(details));
|
|
1996
|
+
if (!equalJson(storedDetails, details) ||
|
|
1997
|
+
!validateSchemaValue(registry.document, declaration.details, details).valid ||
|
|
1998
|
+
!validators.errors.get(error.code)?.validate(details).accepted)
|
|
1999
|
+
throw new Error("details");
|
|
2000
|
+
}
|
|
2001
|
+
else if (error.details !== undefined)
|
|
2002
|
+
throw new Error("details");
|
|
2003
|
+
const safeMessage = bearerGuard.redactString(declaration.message);
|
|
2004
|
+
if (safeMessage !== error.message)
|
|
2005
|
+
throw new Error("replay_privacy");
|
|
2006
|
+
stage("finalize", true);
|
|
2007
|
+
return failure(error.code, declaration.status, details, safeMessage, declaration.retryable);
|
|
2008
|
+
}
|
|
2009
|
+
catch {
|
|
2010
|
+
const code = terminal.kind === "success"
|
|
2011
|
+
? "CAP_INVALID_HANDLER_OUTPUT"
|
|
2012
|
+
: "CAP_INTERNAL";
|
|
2013
|
+
diagnostic(code);
|
|
2014
|
+
return failure(code, "internal");
|
|
2015
|
+
}
|
|
2016
|
+
};
|
|
2017
|
+
if (keyed) {
|
|
2018
|
+
if (typeof root.idempotencyKey !== "string" ||
|
|
2019
|
+
!root.idempotencyKey ||
|
|
2020
|
+
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(root.idempotencyKey))
|
|
2021
|
+
return failure("CAP_IDEMPOTENCY_KEY_REQUIRED", "invalid_argument");
|
|
2022
|
+
if (!idempotencyProvider)
|
|
2023
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", true);
|
|
2024
|
+
idempotencyAction = Object.freeze({
|
|
2025
|
+
capabilityId: capability.id,
|
|
2026
|
+
version: capability.version,
|
|
2027
|
+
irHash: carrier.registry
|
|
2028
|
+
.irHash,
|
|
2029
|
+
impact: capability.effects.impact,
|
|
2030
|
+
confirmation: capability.effects.confirmation,
|
|
2031
|
+
identity: Object.freeze({
|
|
2032
|
+
originating: identity.originating,
|
|
2033
|
+
effective: identity.effective,
|
|
2034
|
+
authorityChain: identity.authorityChain,
|
|
2035
|
+
}),
|
|
2036
|
+
input: canonical.value,
|
|
2037
|
+
key: root.idempotencyKey,
|
|
2038
|
+
});
|
|
2039
|
+
const inspected = await awaitProvider(() => idempotencyProvider.inspect(idempotencyAction, {
|
|
2040
|
+
signal: operationView().signal,
|
|
2041
|
+
deadlineMs,
|
|
2042
|
+
refreshCancellation: observeCancellation,
|
|
2043
|
+
}));
|
|
2044
|
+
if ("ok" in inspected)
|
|
2045
|
+
return inspected;
|
|
2046
|
+
const result = idempotencyResponse("failed" in inspected ? undefined : inspected.value, "inspect");
|
|
2047
|
+
if (!result.ok)
|
|
2048
|
+
return providerFailure(result);
|
|
2049
|
+
if (result.outcome === "replay")
|
|
2050
|
+
return await replay(result);
|
|
2051
|
+
}
|
|
2052
|
+
const confirmationPolicy = capability.effects.confirmation;
|
|
2053
|
+
stage("confirmation", confirmationPolicy === "none" &&
|
|
2054
|
+
!Object.hasOwn(root, "confirmationToken"));
|
|
2055
|
+
if (unhealthyConfirmationPolicies.has(capability.id) ||
|
|
2056
|
+
(confirmationPolicy !== "none" && confirmationPolicy !== "required")) {
|
|
2057
|
+
unhealthyConfirmationPolicies.add(capability.id);
|
|
2058
|
+
diagnostic("CAP_CONFIRMATION_POLICY_INVALID");
|
|
2059
|
+
return failure("CAP_CONFIRMATION_POLICY_INVALID", "internal", undefined, "Confirmation policy is invalid or unsupported.");
|
|
2060
|
+
}
|
|
2061
|
+
if (confirmationPolicy === "none" &&
|
|
2062
|
+
Object.hasOwn(root, "confirmationToken"))
|
|
2063
|
+
return failure("CAP_CONFIRMATION_INVALID", "failed_precondition");
|
|
2064
|
+
let confirmationReceipt;
|
|
2065
|
+
if (confirmationPolicy === "required") {
|
|
2066
|
+
if (identity.effective.type === "anonymous" ||
|
|
2067
|
+
identity.originating.type === "anonymous")
|
|
2068
|
+
return denial("CAP_UNAUTHENTICATED", "unauthenticated");
|
|
2069
|
+
if (!confirmationProvider)
|
|
2070
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", true);
|
|
2071
|
+
const confirmationFingerprint = await acquireFingerprint();
|
|
2072
|
+
if ("ok" in confirmationFingerprint)
|
|
2073
|
+
return confirmationFingerprint;
|
|
2074
|
+
if ("failed" in confirmationFingerprint) {
|
|
2075
|
+
diagnostic("confirmation_fingerprint");
|
|
2076
|
+
const incident = await recordFingerprintIncident(confirmationFingerprint.reason);
|
|
2077
|
+
if (incident)
|
|
2078
|
+
return incident;
|
|
2079
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", true);
|
|
2080
|
+
}
|
|
2081
|
+
const action = {
|
|
2082
|
+
irHash: carrier.registry
|
|
2083
|
+
.irHash,
|
|
2084
|
+
capabilityId: capability.id,
|
|
2085
|
+
version: capability.version,
|
|
2086
|
+
impact: capability.effects.impact,
|
|
2087
|
+
summary: capability.summary,
|
|
2088
|
+
requesterFingerprint: confirmationFingerprint.value.requesterFingerprint,
|
|
2089
|
+
tenantFingerprint: confirmationFingerprint.value.tenantFingerprint,
|
|
2090
|
+
fingerprintGenerationId: confirmationFingerprint.value.generationId,
|
|
2091
|
+
kernelInvocationId: kernelInvocationId,
|
|
2092
|
+
input: canonical.value,
|
|
2093
|
+
...(keyed ? { idempotencyKey: root.idempotencyKey } : {}),
|
|
2094
|
+
correlationId,
|
|
2095
|
+
sourceChain: copyJson(sourceChain),
|
|
2096
|
+
};
|
|
2097
|
+
if (!Object.hasOwn(root, "confirmationToken")) {
|
|
2098
|
+
const issued = await awaitProvider(() => confirmationProvider.issue(action, {
|
|
2099
|
+
signal: operationView().signal,
|
|
2100
|
+
refreshCancellation: observeCancellation,
|
|
2101
|
+
deadlineMs,
|
|
2102
|
+
kernelInvocationId: kernelInvocationId,
|
|
2103
|
+
correlationId,
|
|
2104
|
+
}));
|
|
2105
|
+
if ("ok" in issued)
|
|
2106
|
+
return issued;
|
|
2107
|
+
if ("failed" in issued)
|
|
2108
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", true);
|
|
2109
|
+
const result = confirmationResponse(issued.value, "issue", action);
|
|
2110
|
+
if (!result.ok)
|
|
2111
|
+
return failure(result.error.code, result.error.status, undefined, result.error.message, result.error.retryable);
|
|
2112
|
+
return failure("CAP_CONFIRMATION_REQUIRED", "failed_precondition", copyJson(result.challenge), "Approval is required before this action can run.");
|
|
2113
|
+
}
|
|
2114
|
+
stage("approval_consume");
|
|
2115
|
+
const consumed = await awaitProvider(() => confirmationProvider.consume(action, root.confirmationToken, {
|
|
2116
|
+
signal: operationView().signal,
|
|
2117
|
+
refreshCancellation: observeCancellation,
|
|
2118
|
+
deadlineMs,
|
|
2119
|
+
kernelInvocationId: kernelInvocationId,
|
|
2120
|
+
correlationId,
|
|
2121
|
+
}));
|
|
2122
|
+
if ("ok" in consumed)
|
|
2123
|
+
return consumed;
|
|
2124
|
+
if ("failed" in consumed)
|
|
2125
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", true);
|
|
2126
|
+
const result = confirmationResponse(consumed.value, "consume");
|
|
2127
|
+
if (!result.ok)
|
|
2128
|
+
return failure(result.error.code, result.error.status, undefined, result.error.message, result.error.retryable);
|
|
2129
|
+
confirmationReceipt = result.receipt;
|
|
2130
|
+
}
|
|
2131
|
+
else
|
|
2132
|
+
stage("approval_consume", true);
|
|
2133
|
+
stage("idempotency_claim", !keyed);
|
|
2134
|
+
if (keyed) {
|
|
2135
|
+
// Observe a late accepted claim even when the interruption race wins.
|
|
2136
|
+
// This kernel proves no handler can enter using that unreturned claim.
|
|
2137
|
+
const pendingClaim = providerValue(() => idempotencyProvider.claim(idempotencyAction, {
|
|
2138
|
+
signal: operationView().signal,
|
|
2139
|
+
deadlineMs,
|
|
2140
|
+
refreshCancellation: observeCancellation,
|
|
2141
|
+
})).then((outcome) => {
|
|
2142
|
+
const result = idempotencyResponse("failed" in outcome ? undefined : outcome.value, "claim");
|
|
2143
|
+
if (result.ok && result.outcome === "claimed") {
|
|
2144
|
+
idempotencyClaim = result.claim;
|
|
2145
|
+
if (interrupted)
|
|
2146
|
+
void providerValue(() => idempotencyProvider.release(result.claim));
|
|
2147
|
+
}
|
|
2148
|
+
return result;
|
|
2149
|
+
});
|
|
2150
|
+
const claimed = await Promise.race([pendingClaim, interruption]);
|
|
2151
|
+
observeCancellation();
|
|
2152
|
+
if (interrupted || deadlineMs <= Date.now()) {
|
|
2153
|
+
if (!interrupted)
|
|
2154
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
2155
|
+
return await interruption;
|
|
2156
|
+
}
|
|
2157
|
+
if (!claimed.ok)
|
|
2158
|
+
return providerFailure(claimed);
|
|
2159
|
+
if (!("outcome" in claimed))
|
|
2160
|
+
return claimed;
|
|
2161
|
+
if (claimed.outcome === "replay")
|
|
2162
|
+
return await replay(claimed);
|
|
2163
|
+
}
|
|
2164
|
+
stage("resources", capability.requirements.secrets.length === 0);
|
|
2165
|
+
const secrets = new Map();
|
|
2166
|
+
for (const declaration of capability.requirements.secrets) {
|
|
2167
|
+
const resolved = await awaitProvider(() => secretResolve
|
|
2168
|
+
? secretResolve(declaration.name, operationView())
|
|
2169
|
+
: undefined);
|
|
2170
|
+
if ("ok" in resolved)
|
|
2171
|
+
return resolved;
|
|
2172
|
+
if ("failed" in resolved ||
|
|
2173
|
+
(resolved.value === undefined
|
|
2174
|
+
? !declaration.optional
|
|
2175
|
+
: typeof resolved.value !== "string" ||
|
|
2176
|
+
resolved.value.length === 0 ||
|
|
2177
|
+
resolved.value.length > 16384))
|
|
2178
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", true);
|
|
2179
|
+
if (typeof resolved.value === "string") {
|
|
2180
|
+
secrets.set(declaration.name, resolved.value);
|
|
2181
|
+
try {
|
|
2182
|
+
addSensitiveString(redactionState, resolved.value);
|
|
2183
|
+
}
|
|
2184
|
+
catch {
|
|
2185
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable");
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
stage("audit_start", confirmationPolicy !== "required" ||
|
|
2190
|
+
(capability.effects.impact !== "destructive" && !keyed));
|
|
2191
|
+
if (confirmationPolicy === "required" &&
|
|
2192
|
+
(capability.effects.impact === "destructive" || keyed)) {
|
|
2193
|
+
const begun = await awaitProvider(() => confirmationProvider.beginExecution(confirmationReceipt, {
|
|
2194
|
+
signal: operationView().signal,
|
|
2195
|
+
refreshCancellation: observeCancellation,
|
|
2196
|
+
deadlineMs,
|
|
2197
|
+
kernelInvocationId: kernelInvocationId,
|
|
2198
|
+
correlationId,
|
|
2199
|
+
}));
|
|
2200
|
+
if ("ok" in begun)
|
|
2201
|
+
return begun;
|
|
2202
|
+
if ("failed" in begun)
|
|
2203
|
+
return failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", true);
|
|
2204
|
+
const result = confirmationResponse(begun.value, "begin", confirmationReceipt);
|
|
2205
|
+
if (!result.ok)
|
|
2206
|
+
return failure(result.error.code, result.error.status, undefined, result.error.message, result.error.retryable);
|
|
2207
|
+
confirmationAttempt = result.attempt;
|
|
2208
|
+
}
|
|
2209
|
+
if (idempotencyClaim) {
|
|
2210
|
+
const entry = await awaitProvider(() => idempotencyProvider.enter(idempotencyClaim, confirmationAttempt
|
|
2211
|
+
? {
|
|
2212
|
+
recordId: confirmationAttempt.recordId,
|
|
2213
|
+
confirmationRef: confirmationAttempt.confirmationRef,
|
|
2214
|
+
executionAttemptId: confirmationAttempt.executionAttemptId,
|
|
2215
|
+
}
|
|
2216
|
+
: undefined, {
|
|
2217
|
+
signal: operationView().signal,
|
|
2218
|
+
deadlineMs,
|
|
2219
|
+
refreshCancellation: observeCancellation,
|
|
2220
|
+
}));
|
|
2221
|
+
if ("ok" in entry)
|
|
2222
|
+
return entry;
|
|
2223
|
+
const result = idempotencyResponse("failed" in entry ? undefined : entry.value, "complete");
|
|
2224
|
+
if (!result.ok)
|
|
2225
|
+
return providerFailure(result);
|
|
2226
|
+
idempotencyEntered = true;
|
|
2227
|
+
}
|
|
2228
|
+
observeCancellation();
|
|
2229
|
+
if (interrupted || deadlineMs <= Date.now()) {
|
|
2230
|
+
if (!interrupted)
|
|
2231
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
2232
|
+
return await interruption;
|
|
2233
|
+
}
|
|
2234
|
+
const declaredErrors = new WeakMap();
|
|
2235
|
+
const invokeChild = (target, input, childOptions, boundVersion) => {
|
|
2236
|
+
let child;
|
|
2237
|
+
try {
|
|
2238
|
+
child =
|
|
2239
|
+
childOptions === undefined
|
|
2240
|
+
? Object.create(null)
|
|
2241
|
+
: ownData(childOptions, [
|
|
2242
|
+
"version",
|
|
2243
|
+
"mode",
|
|
2244
|
+
"deadline",
|
|
2245
|
+
"signal",
|
|
2246
|
+
"idempotencyKey",
|
|
2247
|
+
"confirmationToken",
|
|
2248
|
+
"policyId",
|
|
2249
|
+
]);
|
|
2250
|
+
if (typeof target !== "string" ||
|
|
2251
|
+
(child.mode !== undefined &&
|
|
2252
|
+
!["delegate", "derive", "service"].includes(child.mode)) ||
|
|
2253
|
+
((child.mode === "derive" || child.mode === "service") &&
|
|
2254
|
+
!canonicalIdentityId(child.policyId)) ||
|
|
2255
|
+
(child.mode !== "derive" &&
|
|
2256
|
+
child.mode !== "service" &&
|
|
2257
|
+
Object.hasOwn(child, "policyId")))
|
|
2258
|
+
throw new Error("options");
|
|
2259
|
+
}
|
|
2260
|
+
catch {
|
|
2261
|
+
return invoke({ capability: "", source: "internal" }, undefined, undefined, true);
|
|
2262
|
+
}
|
|
2263
|
+
if (boundVersion !== undefined && child.version === undefined)
|
|
2264
|
+
child.version = boundVersion;
|
|
2265
|
+
const childTransition = Object.freeze({
|
|
2266
|
+
mode: (child.mode ?? "delegate"),
|
|
2267
|
+
...(typeof child.policyId === "string"
|
|
2268
|
+
? { policyId: child.policyId }
|
|
2269
|
+
: {}),
|
|
2270
|
+
});
|
|
2271
|
+
delete child.mode;
|
|
2272
|
+
delete child.policyId;
|
|
2273
|
+
return invoke({
|
|
2274
|
+
capability: target,
|
|
2275
|
+
input,
|
|
2276
|
+
source: "internal",
|
|
2277
|
+
...child,
|
|
2278
|
+
}, undefined, {
|
|
2279
|
+
observeCancellation,
|
|
2280
|
+
redactionState,
|
|
2281
|
+
privateBoundary: parent?.privateBoundary ??
|
|
2282
|
+
ingress?.adapter.privateBoundary ??
|
|
2283
|
+
false,
|
|
2284
|
+
identity,
|
|
2285
|
+
sourceChain,
|
|
2286
|
+
ancestry: Object.freeze([...(parent?.ancestry ?? []), node]),
|
|
2287
|
+
deadlineMs,
|
|
2288
|
+
signal: controller.signal,
|
|
2289
|
+
traceId,
|
|
2290
|
+
spanId,
|
|
2291
|
+
depth: (parent?.depth ?? -1) + 1,
|
|
2292
|
+
caller: currentNode,
|
|
2293
|
+
}, false, childTransition);
|
|
2294
|
+
};
|
|
2295
|
+
const facade = Object.create(null);
|
|
2296
|
+
for (const selected of [...registry.entries.values()]
|
|
2297
|
+
.filter(({ capability: item }) => item.access.exposure.internal !== "disabled")
|
|
2298
|
+
.sort((left, right) => left.capability.id < right.capability.id
|
|
2299
|
+
? -1
|
|
2300
|
+
: left.capability.id > right.capability.id
|
|
2301
|
+
? 1
|
|
2302
|
+
: left.capability.version < right.capability.version
|
|
2303
|
+
? -1
|
|
2304
|
+
: left.capability.version > right.capability.version
|
|
2305
|
+
? 1
|
|
2306
|
+
: 0)) {
|
|
2307
|
+
const segments = selected.capability.id.split(".");
|
|
2308
|
+
let namespace = facade;
|
|
2309
|
+
for (const [index, segment] of segments.entries()) {
|
|
2310
|
+
if (index === segments.length - 1) {
|
|
2311
|
+
const target = selected.capability.id;
|
|
2312
|
+
const exactVersion = selected.capability.version;
|
|
2313
|
+
Object.defineProperty(namespace, segment, {
|
|
2314
|
+
value: Object.freeze((input, options) => invokeChild(target, input, options, exactVersion)),
|
|
2315
|
+
enumerable: true,
|
|
2316
|
+
configurable: false,
|
|
2317
|
+
writable: false,
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
else {
|
|
2321
|
+
if (!Object.hasOwn(namespace, segment))
|
|
2322
|
+
Object.defineProperty(namespace, segment, {
|
|
2323
|
+
value: Object.create(null),
|
|
2324
|
+
enumerable: true,
|
|
2325
|
+
configurable: false,
|
|
2326
|
+
writable: false,
|
|
2327
|
+
});
|
|
2328
|
+
namespace = namespace[segment];
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
const freezeFacade = (value) => {
|
|
2333
|
+
for (const child of Object.values(value))
|
|
2334
|
+
if (typeof child === "object" && child !== null)
|
|
2335
|
+
freezeFacade(child);
|
|
2336
|
+
Object.freeze(value);
|
|
2337
|
+
};
|
|
2338
|
+
freezeFacade(facade);
|
|
2339
|
+
const context = Object.freeze({
|
|
2340
|
+
services: Object.freeze({}),
|
|
2341
|
+
secrets: Object.freeze({
|
|
2342
|
+
get: (name) => typeof name === "string" ? secrets.get(name) : undefined,
|
|
2343
|
+
}),
|
|
2344
|
+
trace: Object.freeze({
|
|
2345
|
+
traceId,
|
|
2346
|
+
spanId,
|
|
2347
|
+
...(parent ? { parentSpanId: parent.spanId } : {}),
|
|
2348
|
+
}),
|
|
2349
|
+
logger: Object.freeze(Object.fromEntries(["info", "warn", "error"].map((level) => [
|
|
2350
|
+
level,
|
|
2351
|
+
(code) => {
|
|
2352
|
+
const safeCode = typeof code === "string" &&
|
|
2353
|
+
[
|
|
2354
|
+
"CAP_HANDLER_DIAGNOSTIC",
|
|
2355
|
+
"CAP_HANDLER_WARNING",
|
|
2356
|
+
"CAP_HANDLER_ERROR",
|
|
2357
|
+
].includes(code) &&
|
|
2358
|
+
!hasSensitiveString(code)
|
|
2359
|
+
? code
|
|
2360
|
+
: "CAP_HANDLER_DIAGNOSTIC";
|
|
2361
|
+
notify(onLog, {
|
|
2362
|
+
level,
|
|
2363
|
+
code: safeCode,
|
|
2364
|
+
capability: capability.id,
|
|
2365
|
+
version: capability.version,
|
|
2366
|
+
source,
|
|
2367
|
+
correlationId,
|
|
2368
|
+
traceId,
|
|
2369
|
+
spanId,
|
|
2370
|
+
});
|
|
2371
|
+
},
|
|
2372
|
+
]))),
|
|
2373
|
+
identity,
|
|
2374
|
+
invoke: invokeChild,
|
|
2375
|
+
capabilities: facade,
|
|
2376
|
+
signal: operationView().signal,
|
|
2377
|
+
deadline: Object.freeze(new Date(deadlineMs)),
|
|
2378
|
+
correlationId,
|
|
2379
|
+
error: (code, details) => {
|
|
2380
|
+
const error = new Error("Capability error.");
|
|
2381
|
+
// Undeclared names are remembered, then safely mapped to CAP_INTERNAL.
|
|
2382
|
+
declaredErrors.set(error, {
|
|
2383
|
+
code,
|
|
2384
|
+
...(details === undefined ? {} : { details }),
|
|
2385
|
+
});
|
|
2386
|
+
Object.defineProperties(error, {
|
|
2387
|
+
code: { value: code, enumerable: true },
|
|
2388
|
+
details: { value: details },
|
|
2389
|
+
});
|
|
2390
|
+
return Object.freeze(error);
|
|
2391
|
+
},
|
|
2392
|
+
});
|
|
2393
|
+
const declared = (value, observe = true) => {
|
|
2394
|
+
const raised = typeof value === "object" && value !== null
|
|
2395
|
+
? declaredErrors.get(value)
|
|
2396
|
+
: undefined;
|
|
2397
|
+
if (!raised)
|
|
2398
|
+
return undefined;
|
|
2399
|
+
const declaration = Object.hasOwn(capability.errors, raised.code)
|
|
2400
|
+
? capability.errors[raised.code]
|
|
2401
|
+
: undefined;
|
|
2402
|
+
if (!declaration)
|
|
2403
|
+
return failure("CAP_INTERNAL", "internal");
|
|
2404
|
+
let details;
|
|
2405
|
+
if (declaration.details) {
|
|
2406
|
+
try {
|
|
2407
|
+
details = redactJson(raised.details, createRedactionState());
|
|
2408
|
+
const checked = validateSchemaValue(registry.document, declaration.details, details);
|
|
2409
|
+
if (!checked.valid ||
|
|
2410
|
+
!validators.errors.get(raised.code)?.validate(details).accepted)
|
|
2411
|
+
return failure("CAP_INTERNAL", "internal");
|
|
2412
|
+
details = redactSecrets(bearerGuard.redactValue(details));
|
|
2413
|
+
const sanitized = validateSchemaValue(registry.document, declaration.details, details);
|
|
2414
|
+
if (!sanitized.valid ||
|
|
2415
|
+
!validators.errors.get(raised.code)?.validate(details).accepted)
|
|
2416
|
+
return failure("CAP_INTERNAL", "internal");
|
|
2417
|
+
}
|
|
2418
|
+
catch {
|
|
2419
|
+
if (observe && !interrupted)
|
|
2420
|
+
diagnostic("CAP_INTERNAL");
|
|
2421
|
+
return failure("CAP_INTERNAL", "internal");
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
else if (raised.details !== undefined)
|
|
2425
|
+
return failure("CAP_INTERNAL", "internal");
|
|
2426
|
+
if (hasSensitiveString(raised.code) ||
|
|
2427
|
+
hasSensitiveString(declaration.message))
|
|
2428
|
+
return failure("CAP_INTERNAL", "internal");
|
|
2429
|
+
return failure(raised.code, declaration.status, details, declaration.message, declaration.retryable);
|
|
2430
|
+
};
|
|
2431
|
+
const validateOutcome = (outcome, observe) => {
|
|
2432
|
+
const domainResult = declared(outcome.kind === "value" ? outcome.value : outcome.exception, observe);
|
|
2433
|
+
if (domainResult)
|
|
2434
|
+
return domainResult;
|
|
2435
|
+
if (outcome.kind === "exception") {
|
|
2436
|
+
if (observe && !interrupted)
|
|
2437
|
+
diagnostic("CAP_INTERNAL");
|
|
2438
|
+
return failure("CAP_INTERNAL", "internal");
|
|
2439
|
+
}
|
|
2440
|
+
try {
|
|
2441
|
+
const value = redactSecrets(outcome.value);
|
|
2442
|
+
const checked = validateSchemaValue(registry.document, capability.output, value);
|
|
2443
|
+
if (!checked.valid || !validators.output.validate(value).accepted) {
|
|
2444
|
+
if (observe && !interrupted)
|
|
2445
|
+
diagnostic("CAP_INVALID_HANDLER_OUTPUT");
|
|
2446
|
+
return failure("CAP_INVALID_HANDLER_OUTPUT", "internal");
|
|
2447
|
+
}
|
|
2448
|
+
return { ok: true, value, correlationId };
|
|
2449
|
+
}
|
|
2450
|
+
catch {
|
|
2451
|
+
if (observe && !interrupted)
|
|
2452
|
+
diagnostic("CAP_INVALID_HANDLER_OUTPUT");
|
|
2453
|
+
return failure("CAP_INVALID_HANDLER_OUTPUT", "internal");
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
const persistTerminal = async (validated) => {
|
|
2457
|
+
try {
|
|
2458
|
+
let persisted = true;
|
|
2459
|
+
if (idempotencyClaim) {
|
|
2460
|
+
const terminal = validated.ok
|
|
2461
|
+
? { kind: "success", value: validated.value }
|
|
2462
|
+
: {
|
|
2463
|
+
kind: Object.hasOwn(capability.errors, validated.error.code)
|
|
2464
|
+
? "declared_error"
|
|
2465
|
+
: "unexpected_error",
|
|
2466
|
+
error: {
|
|
2467
|
+
code: validated.error.code,
|
|
2468
|
+
status: validated.error.status,
|
|
2469
|
+
message: validated.error.message,
|
|
2470
|
+
retryable: validated.error.retryable,
|
|
2471
|
+
...(validated.error.details === undefined
|
|
2472
|
+
? {}
|
|
2473
|
+
: { details: validated.error.details }),
|
|
2474
|
+
},
|
|
2475
|
+
};
|
|
2476
|
+
const completion = await providerValue(() => idempotencyProvider.complete(idempotencyClaim, terminal));
|
|
2477
|
+
if (!idempotencyResponse("failed" in completion ? undefined : completion.value, "complete").ok)
|
|
2478
|
+
persisted = false;
|
|
2479
|
+
}
|
|
2480
|
+
if (confirmationAttempt) {
|
|
2481
|
+
const completion = await providerValue(() => confirmationProvider.completeExecution(confirmationAttempt, validated.ok ? "succeeded" : "failed"));
|
|
2482
|
+
if ("failed" in completion ||
|
|
2483
|
+
!confirmationResponse(completion.value, "complete").ok)
|
|
2484
|
+
persisted = false;
|
|
2485
|
+
}
|
|
2486
|
+
if (idempotencyClaim && persisted) {
|
|
2487
|
+
const reconciled = await providerValue(() => idempotencyProvider.reconcile());
|
|
2488
|
+
if (!idempotencyResponse("failed" in reconciled ? undefined : reconciled.value, "complete").ok)
|
|
2489
|
+
return false;
|
|
2490
|
+
}
|
|
2491
|
+
return persisted;
|
|
2492
|
+
}
|
|
2493
|
+
catch {
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2497
|
+
stage("handler");
|
|
2498
|
+
if (interrupted)
|
|
2499
|
+
return await interruption;
|
|
2500
|
+
const execution = Promise.resolve()
|
|
2501
|
+
.then(() => {
|
|
2502
|
+
observeCancellation();
|
|
2503
|
+
if (!interrupted && deadlineMs <= Date.now())
|
|
2504
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
2505
|
+
if (interrupted)
|
|
2506
|
+
return interruption;
|
|
2507
|
+
started = true;
|
|
2508
|
+
return handler(canonical.value, context);
|
|
2509
|
+
})
|
|
2510
|
+
.then((value) => ({ kind: "value", value }), (exception) => ({ kind: "exception", exception }));
|
|
2511
|
+
// Confirmed effects retain a settlement observer after the caller returns.
|
|
2512
|
+
// Only durable terminal classification continues; the caller result and
|
|
2513
|
+
// ordinary completion telemetry remain owned by the interruption race.
|
|
2514
|
+
const settledExecution = execution.then((outcome) => {
|
|
2515
|
+
if ((!confirmationAttempt && !idempotencyClaim) || !started)
|
|
2516
|
+
return { outcome };
|
|
2517
|
+
observeCancellation();
|
|
2518
|
+
if (!interrupted && deadlineMs <= Date.now())
|
|
2519
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
2520
|
+
if (!interrupted)
|
|
2521
|
+
stage("output_validation");
|
|
2522
|
+
let validated;
|
|
2523
|
+
try {
|
|
2524
|
+
validated = validateOutcome(outcome, !interrupted);
|
|
2525
|
+
}
|
|
2526
|
+
catch {
|
|
2527
|
+
validated = failure("CAP_INTERNAL", "internal");
|
|
2528
|
+
}
|
|
2529
|
+
terminalCompletion = persistTerminal(validated);
|
|
2530
|
+
observeCancellation();
|
|
2531
|
+
if (!interrupted && deadlineMs <= Date.now())
|
|
2532
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
2533
|
+
if (!interrupted)
|
|
2534
|
+
stage("finalize", true);
|
|
2535
|
+
return { outcome, validated };
|
|
2536
|
+
});
|
|
2537
|
+
const settled = await Promise.race([settledExecution, interruption]);
|
|
2538
|
+
if ("ok" in settled)
|
|
2539
|
+
return settled;
|
|
2540
|
+
if (interrupted || deadlineMs <= Date.now()) {
|
|
2541
|
+
if (!interrupted)
|
|
2542
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
2543
|
+
return await interruption;
|
|
2544
|
+
}
|
|
2545
|
+
if (settled.validated)
|
|
2546
|
+
return settled.validated;
|
|
2547
|
+
stage("output_validation");
|
|
2548
|
+
if (interrupted)
|
|
2549
|
+
return await interruption;
|
|
2550
|
+
const validated = validateOutcome(settled.outcome, true);
|
|
2551
|
+
stage("finalize", true);
|
|
2552
|
+
if (interrupted)
|
|
2553
|
+
return await interruption;
|
|
2554
|
+
return validated;
|
|
2555
|
+
})();
|
|
2556
|
+
}
|
|
2557
|
+
catch {
|
|
2558
|
+
diagnostic("CAP_INTERNAL");
|
|
2559
|
+
result = failure("CAP_INTERNAL", "internal");
|
|
2560
|
+
}
|
|
2561
|
+
finally {
|
|
2562
|
+
// Terminal persistence has no invocation abort controls. Await it only
|
|
2563
|
+
// while the caller remains live; late settlement still commits once.
|
|
2564
|
+
if (terminalCompletion && !interrupted) {
|
|
2565
|
+
const completion = await Promise.race([
|
|
2566
|
+
terminalCompletion,
|
|
2567
|
+
interruption,
|
|
2568
|
+
]);
|
|
2569
|
+
if (completion === false)
|
|
2570
|
+
result = failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", false);
|
|
2571
|
+
}
|
|
2572
|
+
else if (confirmationAttempt && !started && !interrupted) {
|
|
2573
|
+
const completion = await Promise.race([
|
|
2574
|
+
providerValue(() => confirmationProvider.completeExecution(confirmationAttempt, "failed")),
|
|
2575
|
+
interruption,
|
|
2576
|
+
]);
|
|
2577
|
+
if (!("ok" in completion) &&
|
|
2578
|
+
("failed" in completion ||
|
|
2579
|
+
!confirmationResponse(completion.value, "complete").ok))
|
|
2580
|
+
result = failure("CAP_DEPENDENCY_UNAVAILABLE", "unavailable", undefined, "Required runtime provider is unavailable.", true);
|
|
2581
|
+
}
|
|
2582
|
+
if (idempotencyClaim && !idempotencyEntered && !started) {
|
|
2583
|
+
// A rejected/late entry can still have committed its durable fence.
|
|
2584
|
+
// The provider releases only CLAIMED; an accepted entry remains blocked.
|
|
2585
|
+
const release = providerValue(() => idempotencyProvider.release(idempotencyClaim));
|
|
2586
|
+
if (!interrupted)
|
|
2587
|
+
await Promise.race([release, interruption]);
|
|
2588
|
+
}
|
|
2589
|
+
clearTimeout(timer);
|
|
2590
|
+
try {
|
|
2591
|
+
if (parentSignal && validSignal(parentSignal, establishedParentEvents))
|
|
2592
|
+
removeListener.call(parentSignal, "abort", cancel);
|
|
2593
|
+
}
|
|
2594
|
+
catch {
|
|
2595
|
+
/* Caller native state mutation is contained. */
|
|
2596
|
+
}
|
|
2597
|
+
if (ancestorSignal)
|
|
2598
|
+
removeListener.call(ancestorSignal, "abort", cancel);
|
|
2599
|
+
}
|
|
2600
|
+
stage("telemetry_audit", true);
|
|
2601
|
+
// Synchronous validators and terminal observers can exhaust the budget
|
|
2602
|
+
// before the event loop delivers an abort/deadline notification.
|
|
2603
|
+
if (!interrupted)
|
|
2604
|
+
observeCancellation();
|
|
2605
|
+
if (!interrupted && deadlineMs <= Date.now())
|
|
2606
|
+
interrupt("CAP_DEADLINE_EXCEEDED");
|
|
2607
|
+
if (interrupted)
|
|
2608
|
+
result = await interruption;
|
|
2609
|
+
notify(telemetry, {
|
|
2610
|
+
kind: "completion",
|
|
2611
|
+
code: result.ok ? "OK" : result.error.code,
|
|
2612
|
+
correlationId,
|
|
2613
|
+
});
|
|
2614
|
+
const metric = Object.freeze({
|
|
2615
|
+
capability: capability.id,
|
|
2616
|
+
version: capability.version,
|
|
2617
|
+
source,
|
|
2618
|
+
code: result.ok ? "OK" : result.error.code,
|
|
2619
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
2620
|
+
});
|
|
2621
|
+
notify(onMetric, metric);
|
|
2622
|
+
notify(onTrace, {
|
|
2623
|
+
...metric,
|
|
2624
|
+
correlationId,
|
|
2625
|
+
traceId: parent?.traceId ?? correlationId,
|
|
2626
|
+
spanId: correlationId,
|
|
2627
|
+
...(parent ? { parentSpanId: parent.spanId } : {}),
|
|
2628
|
+
});
|
|
2629
|
+
return result;
|
|
2630
|
+
};
|
|
2631
|
+
const createAdapterIngress = (adapterId) => {
|
|
2632
|
+
if (identityUnavailable)
|
|
2633
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_IDENTITY_UNAVAILABLE");
|
|
2634
|
+
const adapter = adapters.get(adapterId);
|
|
2635
|
+
if (!adapter)
|
|
2636
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_ADAPTER_UNREGISTERED");
|
|
2637
|
+
const invokeIngress = (request) => {
|
|
2638
|
+
let data;
|
|
2639
|
+
try {
|
|
2640
|
+
data = ownData(request, [
|
|
2641
|
+
...requestKeys.filter((key) => key !== "source"),
|
|
2642
|
+
"credentials",
|
|
2643
|
+
]);
|
|
2644
|
+
}
|
|
2645
|
+
catch {
|
|
2646
|
+
return invoke({ capability: "", source: adapter.source }, undefined, undefined, true);
|
|
2647
|
+
}
|
|
2648
|
+
const credentials = data.credentials;
|
|
2649
|
+
const hasCredentials = Object.hasOwn(data, "credentials");
|
|
2650
|
+
delete data.credentials;
|
|
2651
|
+
return invoke({ ...data, source: adapter.source }, { adapter, credentials, hasCredentials });
|
|
2652
|
+
};
|
|
2653
|
+
return Object.freeze({
|
|
2654
|
+
invoke: invokeIngress,
|
|
2655
|
+
authenticate: async (capabilityId, credentials, authenticationOptions) => {
|
|
2656
|
+
if (identityUnavailable)
|
|
2657
|
+
throw new RuntimeConfigurationError("CAP_RUNTIME_IDENTITY_UNAVAILABLE");
|
|
2658
|
+
const entry = registry.entries.get(capabilityId);
|
|
2659
|
+
if (!entry || !adapter.capabilities.includes(capabilityId))
|
|
2660
|
+
throw new RuntimeConfigurationError("CAP_UNAUTHENTICATED");
|
|
2661
|
+
let controls;
|
|
2662
|
+
let deadlineMs = Date.now() + 30000;
|
|
2663
|
+
try {
|
|
2664
|
+
controls =
|
|
2665
|
+
authenticationOptions === undefined
|
|
2666
|
+
? Object.create(null)
|
|
2667
|
+
: ownData(authenticationOptions, ["deadline", "signal"]);
|
|
2668
|
+
if (controls.signal !== undefined && !validSignal(controls.signal))
|
|
2669
|
+
throw new Error("signal");
|
|
2670
|
+
if (controls.deadline !== undefined) {
|
|
2671
|
+
if (typeof controls.deadline !== "object" ||
|
|
2672
|
+
controls.deadline === null ||
|
|
2673
|
+
types.isProxy(controls.deadline))
|
|
2674
|
+
throw new Error("deadline");
|
|
2675
|
+
const requested = getDateTime.call(controls.deadline);
|
|
2676
|
+
if (!Number.isFinite(requested))
|
|
2677
|
+
throw new Error("deadline");
|
|
2678
|
+
deadlineMs = Math.min(deadlineMs, requested);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
catch {
|
|
2682
|
+
throw new RuntimeConfigurationError("CAP_INTERNAL_INVOCATION_INVALID");
|
|
2683
|
+
}
|
|
2684
|
+
const signal = controls.signal;
|
|
2685
|
+
const follower = new AbortController();
|
|
2686
|
+
const correlationId = allocateCorrelation();
|
|
2687
|
+
const view = Object.freeze({
|
|
2688
|
+
capability: Object.freeze({
|
|
2689
|
+
id: entry.capability.id,
|
|
2690
|
+
version: entry.capability.version,
|
|
2691
|
+
access: entry.capability.access,
|
|
2692
|
+
}),
|
|
2693
|
+
identity: rootIdentity(anonymousPrincipal),
|
|
2694
|
+
sourceChain: Object.freeze([
|
|
2695
|
+
Object.freeze({
|
|
2696
|
+
source: adapter.source,
|
|
2697
|
+
capability: capabilityId,
|
|
2698
|
+
exactVersion: entry.capability.version,
|
|
2699
|
+
}),
|
|
2700
|
+
]),
|
|
2701
|
+
correlationId,
|
|
2702
|
+
traceId: correlationId,
|
|
2703
|
+
spanId: correlationId,
|
|
2704
|
+
deadlineMs,
|
|
2705
|
+
signal: follower.signal,
|
|
2706
|
+
});
|
|
2707
|
+
let timer;
|
|
2708
|
+
let rejectInterrupt;
|
|
2709
|
+
const cancel = () => {
|
|
2710
|
+
try {
|
|
2711
|
+
follower.abort();
|
|
2712
|
+
}
|
|
2713
|
+
catch {
|
|
2714
|
+
/* Isolated native signal mutation is contained. */
|
|
2715
|
+
}
|
|
2716
|
+
rejectInterrupt?.("cancel");
|
|
2717
|
+
};
|
|
2718
|
+
let establishedEvents;
|
|
2719
|
+
let outcome;
|
|
2720
|
+
try {
|
|
2721
|
+
const interruption = new Promise((resolve) => {
|
|
2722
|
+
rejectInterrupt = resolve;
|
|
2723
|
+
timer = setTimeout(() => {
|
|
2724
|
+
try {
|
|
2725
|
+
follower.abort();
|
|
2726
|
+
}
|
|
2727
|
+
catch {
|
|
2728
|
+
/* isolated */
|
|
2729
|
+
}
|
|
2730
|
+
resolve("deadline");
|
|
2731
|
+
}, Math.max(0, deadlineMs - Date.now()));
|
|
2732
|
+
});
|
|
2733
|
+
try {
|
|
2734
|
+
if (signal) {
|
|
2735
|
+
addListener.call(signal, "abort", cancel, { once: true });
|
|
2736
|
+
establishedEvents = establishedSignalEvents(signal);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
catch {
|
|
2740
|
+
throw new RuntimeConfigurationError("CAP_INTERNAL_INVOCATION_INVALID");
|
|
2741
|
+
}
|
|
2742
|
+
if (signal &&
|
|
2743
|
+
(!validSignal(signal, establishedEvents) ||
|
|
2744
|
+
abortedGetter?.call(signal)))
|
|
2745
|
+
cancel();
|
|
2746
|
+
if (deadlineMs <= Date.now())
|
|
2747
|
+
rejectInterrupt?.("deadline");
|
|
2748
|
+
const pending = providerValue(() => {
|
|
2749
|
+
if ((signal &&
|
|
2750
|
+
(!validSignal(signal, establishedEvents) ||
|
|
2751
|
+
abortedGetter?.call(signal))) ||
|
|
2752
|
+
deadlineMs <= Date.now())
|
|
2753
|
+
throw new Error("interrupted");
|
|
2754
|
+
return providers.get(adapter.providerId)(credentials, view);
|
|
2755
|
+
});
|
|
2756
|
+
outcome = await Promise.race([pending, interruption]);
|
|
2757
|
+
}
|
|
2758
|
+
finally {
|
|
2759
|
+
clearTimeout(timer);
|
|
2760
|
+
try {
|
|
2761
|
+
if (signal && validSignal(signal, establishedEvents))
|
|
2762
|
+
removeListener.call(signal, "abort", cancel);
|
|
2763
|
+
}
|
|
2764
|
+
catch {
|
|
2765
|
+
/* Malformed caller event state is contained. */
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
if (signal &&
|
|
2769
|
+
(!validSignal(signal, establishedEvents) ||
|
|
2770
|
+
abortedGetter?.call(signal)))
|
|
2771
|
+
throw new RuntimeConfigurationError("CAP_CANCELLED");
|
|
2772
|
+
if (outcome === "cancel")
|
|
2773
|
+
throw new RuntimeConfigurationError("CAP_CANCELLED");
|
|
2774
|
+
if (outcome === "deadline" || deadlineMs <= Date.now())
|
|
2775
|
+
throw new RuntimeConfigurationError("CAP_DEADLINE_EXCEEDED");
|
|
2776
|
+
if ("failed" in outcome)
|
|
2777
|
+
throw new RuntimeConfigurationError("CAP_DEPENDENCY_UNAVAILABLE");
|
|
2778
|
+
if (outcome.value === null)
|
|
2779
|
+
throw new RuntimeConfigurationError("CAP_UNAUTHENTICATED");
|
|
2780
|
+
let identity;
|
|
2781
|
+
try {
|
|
2782
|
+
identity = rootIdentity(normalizePrincipal(outcome.value, adapter.providerId));
|
|
2783
|
+
}
|
|
2784
|
+
catch {
|
|
2785
|
+
throw new RuntimeConfigurationError("CAP_DEPENDENCY_UNAVAILABLE");
|
|
2786
|
+
}
|
|
2787
|
+
const token = Object.freeze({});
|
|
2788
|
+
tokens.set(token, { adapter, capability: capabilityId, identity });
|
|
2789
|
+
return token;
|
|
2790
|
+
},
|
|
2791
|
+
});
|
|
2792
|
+
};
|
|
2793
|
+
return Object.freeze({
|
|
2794
|
+
invoke: (request) => invoke(request),
|
|
2795
|
+
createAdapterIngress,
|
|
2796
|
+
decideConfirmation: async (command, approvalRequest) => {
|
|
2797
|
+
const correlationId = allocateCorrelation();
|
|
2798
|
+
const invalid = (code, status, details) => ({
|
|
2799
|
+
ok: false,
|
|
2800
|
+
error: {
|
|
2801
|
+
code,
|
|
2802
|
+
status,
|
|
2803
|
+
message: "Confirmation operation failed.",
|
|
2804
|
+
retryable: status === "unavailable",
|
|
2805
|
+
correlationId,
|
|
2806
|
+
...(details === undefined ? {} : { details }),
|
|
2807
|
+
},
|
|
2808
|
+
});
|
|
2809
|
+
let data;
|
|
2810
|
+
try {
|
|
2811
|
+
data = ownData(command, ["challenge", "decision", "correlationId"]);
|
|
2812
|
+
}
|
|
2813
|
+
catch {
|
|
2814
|
+
return invalid("CAP_INPUT_INVALID", "invalid_argument");
|
|
2815
|
+
}
|
|
2816
|
+
if (!bearerGuard.validateCorrelationHint(data.correlationId, Object.hasOwn(data, "correlationId")).valid)
|
|
2817
|
+
return invalid("CAP_INPUT_INVALID", "invalid_argument", {
|
|
2818
|
+
path: "/correlationId",
|
|
2819
|
+
});
|
|
2820
|
+
delete data.correlationId;
|
|
2821
|
+
if (!confirmationProvider)
|
|
2822
|
+
return invalid("CAP_DEPENDENCY_UNAVAILABLE", "unavailable");
|
|
2823
|
+
const controller = new AbortController();
|
|
2824
|
+
const deadlineMs = Date.now() + 30_000;
|
|
2825
|
+
let timedOut = false;
|
|
2826
|
+
let expire;
|
|
2827
|
+
const timeout = new Promise((resolve) => {
|
|
2828
|
+
expire = () => {
|
|
2829
|
+
timedOut = true;
|
|
2830
|
+
controller.abort();
|
|
2831
|
+
resolve({ failed: true });
|
|
2832
|
+
};
|
|
2833
|
+
});
|
|
2834
|
+
const timer = setTimeout(() => expire?.(), 30_000);
|
|
2835
|
+
const observe = (callback) => Promise.race([providerValue(callback), timeout]);
|
|
2836
|
+
try {
|
|
2837
|
+
const allocated = await observe(() => confirmationProvider.allocateKernelInvocationId({
|
|
2838
|
+
signal: controller.signal,
|
|
2839
|
+
deadlineMs,
|
|
2840
|
+
correlationId,
|
|
2841
|
+
}));
|
|
2842
|
+
if ("failed" in allocated)
|
|
2843
|
+
return invalid("CAP_DEPENDENCY_UNAVAILABLE", "unavailable");
|
|
2844
|
+
const allocation = confirmationResponse(allocated.value, "allocation");
|
|
2845
|
+
if (!allocation.ok)
|
|
2846
|
+
return {
|
|
2847
|
+
...allocation,
|
|
2848
|
+
error: { ...allocation.error, correlationId },
|
|
2849
|
+
};
|
|
2850
|
+
const outcome = await observe(() => confirmationProvider.decideConfirmation({ challenge: data.challenge, decision: data.decision }, approvalRequest, {
|
|
2851
|
+
signal: controller.signal,
|
|
2852
|
+
deadlineMs,
|
|
2853
|
+
kernelInvocationId: allocation.kernelInvocationId,
|
|
2854
|
+
correlationId,
|
|
2855
|
+
}));
|
|
2856
|
+
if ("failed" in outcome || timedOut)
|
|
2857
|
+
return invalid("CAP_DEPENDENCY_UNAVAILABLE", "unavailable");
|
|
2858
|
+
const result = confirmationResponse(outcome.value, "decision");
|
|
2859
|
+
return result.ok
|
|
2860
|
+
? { ...result, correlationId }
|
|
2861
|
+
: { ...result, error: { ...result.error, correlationId } };
|
|
2862
|
+
}
|
|
2863
|
+
catch {
|
|
2864
|
+
return invalid("CAP_DEPENDENCY_UNAVAILABLE", "unavailable");
|
|
2865
|
+
}
|
|
2866
|
+
finally {
|
|
2867
|
+
clearTimeout(timer);
|
|
2868
|
+
}
|
|
2869
|
+
},
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
//# sourceMappingURL=kernel.js.map
|