@volter/world-core 3.0.5 → 3.0.6
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/dist/src/derived-real.js +32 -3
- package/dist/src/executor.d.ts +12 -3
- package/dist/src/executor.js +9 -1
- package/dist/src/remote-execute.d.ts +2 -0
- package/package.json +1 -1
- package/src/derived-real.ts +28 -3
- package/src/executor.ts +14 -3
- package/src/remote-execute.ts +2 -0
package/dist/src/derived-real.js
CHANGED
|
@@ -124,6 +124,33 @@ function unitOf(units, input) {
|
|
|
124
124
|
?? units.find((u) => u.surface.operations.some((o) => o.id === id))
|
|
125
125
|
?? (input.graphql ? units[0] : undefined);
|
|
126
126
|
}
|
|
127
|
+
/** The account a root is, before a perform sends a path naming the World's ("The account a root is"): the World's
|
|
128
|
+
* account its own doors made, unadopted, adopts the one account the root's credential lists. Read once per deploy (an
|
|
129
|
+
* adopted account is not asked again); a list the vendor refuses, or one naming several accounts, adopts none. */
|
|
130
|
+
async function adoptAccount(units, execute, ctx) {
|
|
131
|
+
if (ctx.service === undefined)
|
|
132
|
+
return;
|
|
133
|
+
for (const u of units) {
|
|
134
|
+
const m = u.manifest;
|
|
135
|
+
const decl = m.account ? m.resources[m.account] : undefined;
|
|
136
|
+
if (!m.account || !decl)
|
|
137
|
+
continue;
|
|
138
|
+
const type = storedType(m, m.account);
|
|
139
|
+
const local = readTree(ctx.service, ctx.root).filter((r) => r.type === type && r.deleted !== true && ctx.resolve(type, String(r.id)) === String(r.id));
|
|
140
|
+
const scope = decl.refresh;
|
|
141
|
+
const op = scope && 'list' in scope ? u.surface.operations.find((o) => o.id === scope.list) : undefined;
|
|
142
|
+
if (!local.length || !op)
|
|
143
|
+
continue;
|
|
144
|
+
const answer = await execute({ method: op.method.toUpperCase(), path: `${op.basePath ?? u.surface.basePath ?? ''}${op.path}`, ...(u.lane ? { lane: u.lane } : {}) });
|
|
145
|
+
if (answer.status >= 400)
|
|
146
|
+
continue;
|
|
147
|
+
const items = itemsOf(op, parse(answer));
|
|
148
|
+
const id = items.length === 1 ? storedIdOf(decl, items[0]) : undefined;
|
|
149
|
+
if (id === undefined || local.some((l) => String(l.id) === id))
|
|
150
|
+
continue;
|
|
151
|
+
observeResources(ctx.service, [{ type, id, fields: items[0], adopts: String(local[0].id) }], ctx.root !== undefined ? { root: ctx.root } : {});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
127
154
|
function performOf(units) {
|
|
128
155
|
// the request a group's first entry recorded (or one a protocol hook changed), sent to the vendor
|
|
129
156
|
const derived = async (execute, group, ctx, input) => {
|
|
@@ -190,7 +217,7 @@ function performOf(units) {
|
|
|
190
217
|
// a retried deploy makes nothing twice, and two Worlds deploying into one account never collide
|
|
191
218
|
if (m.idempotency)
|
|
192
219
|
headers[m.idempotency.header] = hashFieldValue({ credential: ctx.credential ?? '', entry: first.id });
|
|
193
|
-
const answer = await execute({ method, path, headers, ...(sent.body !== undefined ? { body: sent.body } : {}) });
|
|
220
|
+
const answer = await execute({ method, path, headers, ...(sent.body !== undefined ? { body: sent.body } : {}), ...(unit.lane ? { lane: unit.lane } : {}) });
|
|
194
221
|
const parsed = parse(answer);
|
|
195
222
|
const shape = refusalShape(m);
|
|
196
223
|
const refused = answer.status >= 400 || (shape.length > 0 && shape.every(([p, v]) => at(parsed, p) === v));
|
|
@@ -231,6 +258,7 @@ function performOf(units) {
|
|
|
231
258
|
return { externalId, actionId: primary.id, data: obj, ...(also.length ? { also } : {}) };
|
|
232
259
|
};
|
|
233
260
|
return async (execute, group, ctx) => {
|
|
261
|
+
await adoptAccount(units, execute, ctx);
|
|
234
262
|
const input = (group[0].input ?? {});
|
|
235
263
|
const unit = unitOf(units, input);
|
|
236
264
|
// a protocol one request cannot express is the pack's hook, which composes the derived perform ("Protocols a
|
|
@@ -297,7 +325,8 @@ function refreshOf(units, service) {
|
|
|
297
325
|
return async (execute, opts) => {
|
|
298
326
|
const observed = [];
|
|
299
327
|
const complete = [];
|
|
300
|
-
for (const
|
|
328
|
+
for (const unit of units) {
|
|
329
|
+
const { manifest: m, surface } = unit;
|
|
301
330
|
// parents before children, however deep
|
|
302
331
|
const scoped = Object.entries(m.resources).filter(([, d]) => d.refresh && !('none' in d.refresh));
|
|
303
332
|
const depth = (r, seen = new Set()) => { const p = m.resources[r]?.parent?.resource; return p && !seen.has(p) ? 1 + depth(p, seen.add(r)) : 0; };
|
|
@@ -319,7 +348,7 @@ function refreshOf(units, service) {
|
|
|
319
348
|
// a parent the operation names in its query, not its path (Slack's conversations.history `channel`)
|
|
320
349
|
const byQuery = decl.parent?.param && parent !== undefined && !op.path.includes(`{${decl.parent.param}}`) ? { [decl.parent.param]: String(parent) } : {};
|
|
321
350
|
const q = new URLSearchParams({ ...page, ...byQuery });
|
|
322
|
-
const answer = await execute({ method: op.method.toUpperCase(), path: [...q].length ? `${path}?${q}` : path });
|
|
351
|
+
const answer = await execute({ method: op.method.toUpperCase(), path: [...q].length ? `${path}?${q}` : path, ...(unit.lane ? { lane: unit.lane } : {}) });
|
|
323
352
|
if (answer.status === 401 || answer.status === 403)
|
|
324
353
|
throw new Error(`${answer.status}: the vendor refused the root's credential; nothing folded`);
|
|
325
354
|
if (answer.status === 429)
|
package/dist/src/executor.d.ts
CHANGED
|
@@ -12,8 +12,14 @@ import type { HostRule } from './packRegistry.js';
|
|
|
12
12
|
* and no sealed credential migrates.
|
|
13
13
|
*/
|
|
14
14
|
export type TwinAuthStrategy =
|
|
15
|
-
/**
|
|
15
|
+
/** A vendor of lanes whose lanes authenticate apart (Cloudflare's API by bearer, its R2 by SigV4): a lane named takes
|
|
16
|
+
* its own strategy, every other call the sealed headers; the request names its lane (`RemoteExecuteRequest.lane`). */
|
|
16
17
|
{
|
|
18
|
+
in: 'lanes';
|
|
19
|
+
lanes: Record<string, TwinAuthStrategy>;
|
|
20
|
+
}
|
|
21
|
+
/** the key rides in the query string (`?key=…`, `?appid=…`) — no header will do */
|
|
22
|
+
| {
|
|
17
23
|
in: 'query';
|
|
18
24
|
name: string;
|
|
19
25
|
}
|
|
@@ -23,7 +29,10 @@ export type TwinAuthStrategy =
|
|
|
23
29
|
| {
|
|
24
30
|
in: 'signature';
|
|
25
31
|
algorithm: 'aws-sigv4';
|
|
26
|
-
scope:
|
|
32
|
+
scope: {
|
|
33
|
+
region: string;
|
|
34
|
+
service: string;
|
|
35
|
+
} | ((req: {
|
|
27
36
|
method: string;
|
|
28
37
|
path: string;
|
|
29
38
|
host: string;
|
|
@@ -32,7 +41,7 @@ export type TwinAuthStrategy =
|
|
|
32
41
|
}) => {
|
|
33
42
|
region: string;
|
|
34
43
|
service: string;
|
|
35
|
-
};
|
|
44
|
+
});
|
|
36
45
|
}
|
|
37
46
|
/** a signature computed PER REQUEST over bytes only the pack can canonicalize */
|
|
38
47
|
| {
|
package/dist/src/executor.js
CHANGED
|
@@ -130,6 +130,14 @@ async function awsSigV4Headers(input) {
|
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
export function buildRemoteExecute(origin, credential, auth, vendorHosts, options = {}) {
|
|
133
|
+
if (auth?.in === 'lanes') {
|
|
134
|
+
// each lane's strategy over the same origin and credential, the budget charged once, around them all
|
|
135
|
+
const own = { ...options, vendor: undefined };
|
|
136
|
+
const byLane = new Map(Object.entries(auth.lanes).map(([lane, strategy]) => [lane, buildRemoteExecute(origin, credential, strategy, vendorHosts, own)]));
|
|
137
|
+
const headers = buildRemoteExecute(origin, credential, undefined, vendorHosts, own);
|
|
138
|
+
const execute = (request) => (byLane.get(request.lane ?? '') ?? headers)(request);
|
|
139
|
+
return options.vendor === undefined ? execute : budgeted(options.vendor, execute, options.custody?.key ?? JSON.stringify(credential.headers));
|
|
140
|
+
}
|
|
133
141
|
const execute = auth?.in === 'exchange' ? exchangeExecute(origin, credential, auth, vendorHosts, options.custody) : direct(origin, credential, auth, vendorHosts);
|
|
134
142
|
return options.vendor === undefined ? execute : budgeted(options.vendor, execute, options.custody?.key ?? JSON.stringify(credential.headers));
|
|
135
143
|
}
|
|
@@ -234,7 +242,7 @@ function direct(origin, credential, auth, vendorHosts) {
|
|
|
234
242
|
throw new Error('remote execute: the aws-sigv4 credential strategy needs `keyId` on the sealed credential, and it is absent');
|
|
235
243
|
}
|
|
236
244
|
// the HOST goes with it: an AWS endpoint names its own region, and only the pack knows how
|
|
237
|
-
const { region, service } = auth.scope({ method, path, host: target.host, headers: Object.fromEntries(outbound.entries()), ...(body === undefined ? {} : { body }) });
|
|
245
|
+
const { region, service } = typeof auth.scope === 'function' ? auth.scope({ method, path, host: target.host, headers: Object.fromEntries(outbound.entries()), ...(body === undefined ? {} : { body }) }) : auth.scope;
|
|
238
246
|
const signed = await awsSigV4Headers({ method, url: target, ...(body === undefined ? {} : { body }), keyId, secret, region, service, headers: outbound, at: new Date() });
|
|
239
247
|
for (const [name, value] of Object.entries(signed))
|
|
240
248
|
outbound.set(name, value);
|
|
@@ -16,6 +16,8 @@ export type RemoteExecuteRequest = {
|
|
|
16
16
|
* that its upload calls carry instead of the API token), on the root's own origin: it is sent as the request's
|
|
17
17
|
* Authorization and the sealed credential does not go with it. */
|
|
18
18
|
issued?: string;
|
|
19
|
+
/** the lane of a vendor of lanes the request is (its strategy, when its lanes authenticate apart) */
|
|
20
|
+
lane?: string;
|
|
19
21
|
};
|
|
20
22
|
export type RemoteExecuteResponse = {
|
|
21
23
|
status: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
package/src/derived-real.ts
CHANGED
|
@@ -111,6 +111,29 @@ function unitOf(units: Unit[], input: Row): Unit | undefined {
|
|
|
111
111
|
?? (input.graphql ? units[0] : undefined);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/** The account a root is, before a perform sends a path naming the World's ("The account a root is"): the World's
|
|
115
|
+
* account its own doors made, unadopted, adopts the one account the root's credential lists. Read once per deploy (an
|
|
116
|
+
* adopted account is not asked again); a list the vendor refuses, or one naming several accounts, adopts none. */
|
|
117
|
+
async function adoptAccount(units: Unit[], execute: RemoteExecute, ctx: PerformContext): Promise<void> {
|
|
118
|
+
if (ctx.service === undefined) return;
|
|
119
|
+
for (const u of units) {
|
|
120
|
+
const m = u.manifest;
|
|
121
|
+
const decl = m.account ? m.resources[m.account] : undefined;
|
|
122
|
+
if (!m.account || !decl) continue;
|
|
123
|
+
const type = storedType(m, m.account);
|
|
124
|
+
const local = (readTree(ctx.service, ctx.root) as Row[]).filter((r) => r.type === type && r.deleted !== true && ctx.resolve(type, String(r.id)) === String(r.id));
|
|
125
|
+
const scope = decl.refresh;
|
|
126
|
+
const op = scope && 'list' in scope ? u.surface.operations.find((o) => o.id === scope.list) : undefined;
|
|
127
|
+
if (!local.length || !op) continue;
|
|
128
|
+
const answer = await execute({ method: op.method.toUpperCase(), path: `${op.basePath ?? u.surface.basePath ?? ''}${op.path}`, ...(u.lane ? { lane: u.lane } : {}) });
|
|
129
|
+
if (answer.status >= 400) continue;
|
|
130
|
+
const items = itemsOf(op, parse(answer));
|
|
131
|
+
const id = items.length === 1 ? storedIdOf(decl, items[0]!) : undefined;
|
|
132
|
+
if (id === undefined || local.some((l) => String(l.id) === id)) continue;
|
|
133
|
+
observeResources(ctx.service, [{ type, id, fields: items[0] as ObservedResource['fields'], adopts: String(local[0]!.id) }], ctx.root !== undefined ? { root: ctx.root } : {});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
114
137
|
function performOf(units: Unit[]): PerformAction {
|
|
115
138
|
// the request a group's first entry recorded (or one a protocol hook changed), sent to the vendor
|
|
116
139
|
const derived = async (execute: RemoteExecute, group: TwinAction[], ctx: PerformContext, input: Row): Promise<PushOutcome> => {
|
|
@@ -159,7 +182,7 @@ function performOf(units: Unit[]): PerformAction {
|
|
|
159
182
|
const headers: Record<string, string> = { ...((object(input.headers) ?? {}) as Record<string, string>), ...(sent.type ? { 'content-type': sent.type } : {}) };
|
|
160
183
|
// a retried deploy makes nothing twice, and two Worlds deploying into one account never collide
|
|
161
184
|
if (m.idempotency) headers[m.idempotency.header] = hashFieldValue({ credential: ctx.credential ?? '', entry: first.id });
|
|
162
|
-
const answer = await execute({ method, path, headers, ...(sent.body !== undefined ? { body: sent.body } : {}) });
|
|
185
|
+
const answer = await execute({ method, path, headers, ...(sent.body !== undefined ? { body: sent.body } : {}), ...(unit.lane ? { lane: unit.lane } : {}) });
|
|
163
186
|
const parsed = parse(answer);
|
|
164
187
|
const shape = refusalShape(m);
|
|
165
188
|
const refused = answer.status >= 400 || (shape.length > 0 && shape.every(([p, v]) => at(parsed, p) === v));
|
|
@@ -196,6 +219,7 @@ function performOf(units: Unit[]): PerformAction {
|
|
|
196
219
|
return { externalId, actionId: primary.id, data: obj, ...(also.length ? { also } : {}) };
|
|
197
220
|
};
|
|
198
221
|
return async (execute: RemoteExecute, group: TwinAction[], ctx: PerformContext): Promise<PushOutcome> => {
|
|
222
|
+
await adoptAccount(units, execute, ctx);
|
|
199
223
|
const input = (group[0]!.input ?? {}) as Row;
|
|
200
224
|
const unit = unitOf(units, input);
|
|
201
225
|
// a protocol one request cannot express is the pack's hook, which composes the derived perform ("Protocols a
|
|
@@ -256,7 +280,8 @@ function refreshOf(units: Unit[], service: string): NonNullable<StateSystemAdapt
|
|
|
256
280
|
return async (execute, opts) => {
|
|
257
281
|
const observed: ObservedResource[] = [];
|
|
258
282
|
const complete: Completed[] = [];
|
|
259
|
-
for (const
|
|
283
|
+
for (const unit of units) {
|
|
284
|
+
const { manifest: m, surface } = unit;
|
|
260
285
|
// parents before children, however deep
|
|
261
286
|
const scoped = Object.entries(m.resources).filter(([, d]) => d.refresh && !('none' in d.refresh));
|
|
262
287
|
const depth = (r: string, seen = new Set<string>()): number => { const p = m.resources[r]?.parent?.resource; return p && !seen.has(p) ? 1 + depth(p, seen.add(r)) : 0; };
|
|
@@ -277,7 +302,7 @@ function refreshOf(units: Unit[], service: string): NonNullable<StateSystemAdapt
|
|
|
277
302
|
// a parent the operation names in its query, not its path (Slack's conversations.history `channel`)
|
|
278
303
|
const byQuery = decl.parent?.param && parent !== undefined && !op.path.includes(`{${decl.parent.param}}`) ? { [decl.parent.param]: String(parent) } : {};
|
|
279
304
|
const q = new URLSearchParams({ ...page, ...byQuery });
|
|
280
|
-
const answer = await execute({ method: op.method.toUpperCase(), path: [...q].length ? `${path}?${q}` : path });
|
|
305
|
+
const answer = await execute({ method: op.method.toUpperCase(), path: [...q].length ? `${path}?${q}` : path, ...(unit.lane ? { lane: unit.lane } : {}) });
|
|
281
306
|
if (answer.status === 401 || answer.status === 403) throw new Error(`${answer.status}: the vendor refused the root's credential; nothing folded`);
|
|
282
307
|
if (answer.status === 429) throw new Error(`429: rate limited${answer.headers['retry-after'] ? `, retry after ${answer.headers['retry-after']}s` : ''}`);
|
|
283
308
|
if (answer.status >= 400) throw new Error(`${answer.status}: ${refusalWords(m, parse(answer))}`);
|
package/src/executor.ts
CHANGED
|
@@ -21,12 +21,15 @@ import { RateBudget } from './rateBudget.ts';
|
|
|
21
21
|
* and no sealed credential migrates.
|
|
22
22
|
*/
|
|
23
23
|
export type TwinAuthStrategy =
|
|
24
|
+
/** A vendor of lanes whose lanes authenticate apart (Cloudflare's API by bearer, its R2 by SigV4): a lane named takes
|
|
25
|
+
* its own strategy, every other call the sealed headers; the request names its lane (`RemoteExecuteRequest.lane`). */
|
|
26
|
+
| { in: 'lanes'; lanes: Record<string, TwinAuthStrategy> }
|
|
24
27
|
/** the key rides in the query string (`?key=…`, `?appid=…`) — no header will do */
|
|
25
28
|
| { in: 'query'; name: string }
|
|
26
29
|
/** AWS Signature Version 4 — a signature computed per request over the whole canonical request.
|
|
27
30
|
* `scope` answers which region and service THIS request is for, because a pack may route several
|
|
28
31
|
* services over one executor (aws routes six); it is pure and is handed no secret. */
|
|
29
|
-
| { in: 'signature'; algorithm: 'aws-sigv4'; scope: (req: { method: string; path: string; host: string; headers: Record<string, string>; body?: string | Uint8Array }) => { region: string; service: string } }
|
|
32
|
+
| { in: 'signature'; algorithm: 'aws-sigv4'; scope: { region: string; service: string } | ((req: { method: string; path: string; host: string; headers: Record<string, string>; body?: string | Uint8Array }) => { region: string; service: string }) }
|
|
30
33
|
/** a signature computed PER REQUEST over bytes only the pack can canonicalize */
|
|
31
34
|
| {
|
|
32
35
|
in: 'signature';
|
|
@@ -223,6 +226,14 @@ export type CredentialCustody = { key: string; open: () => Promise<CredentialPay
|
|
|
223
226
|
* made, refused when the budget is spent, and settled with the vendor's answer (its back-off honoured). */
|
|
224
227
|
export type RemoteExecuteOptions = { custody?: CredentialCustody; vendor?: string };
|
|
225
228
|
export function buildRemoteExecute(origin: URL, credential: CredentialPayload, auth?: TwinAuthStrategy, vendorHosts?: readonly HostRule[], options: RemoteExecuteOptions = {}): RemoteExecute {
|
|
229
|
+
if (auth?.in === 'lanes') {
|
|
230
|
+
// each lane's strategy over the same origin and credential, the budget charged once, around them all
|
|
231
|
+
const own = { ...options, vendor: undefined };
|
|
232
|
+
const byLane = new Map(Object.entries(auth.lanes).map(([lane, strategy]) => [lane, buildRemoteExecute(origin, credential, strategy, vendorHosts, own)]));
|
|
233
|
+
const headers = buildRemoteExecute(origin, credential, undefined, vendorHosts, own);
|
|
234
|
+
const execute: RemoteExecute = (request) => (byLane.get(request.lane ?? '') ?? headers)(request);
|
|
235
|
+
return options.vendor === undefined ? execute : budgeted(options.vendor, execute, options.custody?.key ?? JSON.stringify(credential.headers));
|
|
236
|
+
}
|
|
226
237
|
const execute = auth?.in === 'exchange' ? exchangeExecute(origin, credential, auth, vendorHosts, options.custody) : direct(origin, credential, auth, vendorHosts);
|
|
227
238
|
return options.vendor === undefined ? execute : budgeted(options.vendor, execute, options.custody?.key ?? JSON.stringify(credential.headers));
|
|
228
239
|
}
|
|
@@ -242,7 +253,7 @@ function budgeted(vendor: string, execute: RemoteExecute, token: string): Remote
|
|
|
242
253
|
}
|
|
243
254
|
|
|
244
255
|
/** The call made as the root's credential is applied by the strategy (header replacement when none). */
|
|
245
|
-
function direct(origin: URL, credential: CredentialPayload, auth?: Exclude<TwinAuthStrategy, { in: 'exchange' }>, vendorHosts?: readonly HostRule[]): RemoteExecute {
|
|
256
|
+
function direct(origin: URL, credential: CredentialPayload, auth?: Exclude<TwinAuthStrategy, { in: 'exchange' | 'lanes' }>, vendorHosts?: readonly HostRule[]): RemoteExecute {
|
|
246
257
|
return async ({ method, path, headers, body, responseType, presigned, issued }) => {
|
|
247
258
|
// A PRESIGNED UPLOAD URL the vendor returned: its own signature is its authorization, so the
|
|
248
259
|
// sealed credential never goes with it, whatever host it names. A host other than the root's
|
|
@@ -314,7 +325,7 @@ function direct(origin: URL, credential: CredentialPayload, auth?: Exclude<TwinA
|
|
|
314
325
|
throw new Error('remote execute: the aws-sigv4 credential strategy needs `keyId` on the sealed credential, and it is absent');
|
|
315
326
|
}
|
|
316
327
|
// the HOST goes with it: an AWS endpoint names its own region, and only the pack knows how
|
|
317
|
-
const { region, service } = auth.scope({ method, path, host: target.host, headers: Object.fromEntries(outbound.entries()), ...(body === undefined ? {} : { body }) });
|
|
328
|
+
const { region, service } = typeof auth.scope === 'function' ? auth.scope({ method, path, host: target.host, headers: Object.fromEntries(outbound.entries()), ...(body === undefined ? {} : { body }) }) : auth.scope;
|
|
318
329
|
const signed = await awsSigV4Headers({ method, url: target, ...(body === undefined ? {} : { body }), keyId, secret, region, service, headers: outbound, at: new Date() });
|
|
319
330
|
for (const [name, value] of Object.entries(signed)) outbound.set(name, value);
|
|
320
331
|
} else {
|
package/src/remote-execute.ts
CHANGED
|
@@ -20,6 +20,8 @@ export type RemoteExecuteRequest = {
|
|
|
20
20
|
* that its upload calls carry instead of the API token), on the root's own origin: it is sent as the request's
|
|
21
21
|
* Authorization and the sealed credential does not go with it. */
|
|
22
22
|
issued?: string;
|
|
23
|
+
/** the lane of a vendor of lanes the request is (its strategy, when its lanes authenticate apart) */
|
|
24
|
+
lane?: string;
|
|
23
25
|
};
|
|
24
26
|
export type RemoteExecuteResponse = {
|
|
25
27
|
status: number;
|