@runuai/host 0.9.150 → 0.9.152
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/db/migrations/0027_cloud_credential_foundation.sql +51 -0
- package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
- package/db/migrations/meta/_journal.json +14 -0
- package/db/schema.ts +188 -0
- package/lib/agent-cli.ts +11 -13
- package/lib/agent.ts +177 -7
- package/lib/agents/durable-proc.ts +56 -44
- package/lib/agents/registry.ts +19 -3
- package/lib/agents/session-state.ts +23 -10
- package/lib/agents/transport.ts +101 -69
- package/lib/apple-one-off.ts +277 -0
- package/lib/apple-runtime-recycle.ts +4 -3
- package/lib/attachments.ts +19 -17
- package/lib/capture-flow.ts +319 -0
- package/lib/cloud-credential-cleanup-adapter.ts +123 -0
- package/lib/cloud-credential-host.ts +512 -0
- package/lib/cloud-credential-leases.ts +2544 -0
- package/lib/cloud-credential-runtime.ts +1035 -0
- package/lib/codex-auth.ts +21 -1
- package/lib/container-runtime.ts +76 -0
- package/lib/credential-cleanup-inventory.ts +405 -0
- package/lib/credential-consumers/engine-anchor.ts +1880 -0
- package/lib/credential-consumers/engines.ts +773 -0
- package/lib/credential-consumers/github.ts +594 -0
- package/lib/credential-consumers/mcp.ts +384 -0
- package/lib/credential-consumers/task-files.ts +127 -0
- package/lib/credential-store-pickup.ts +15 -2
- package/lib/credential-store.ts +361 -33
- package/lib/credentials-activation-authority.ts +546 -0
- package/lib/credentials-purge.ts +1006 -0
- package/lib/db.ts +11 -0
- package/lib/docker-exec.ts +3 -1
- package/lib/engine-accounts.ts +42 -32
- package/lib/engine-connect.ts +47 -2
- package/lib/engine-credential-env.ts +50 -0
- package/lib/engine-login.ts +83 -1
- package/lib/env-file.ts +62 -8
- package/lib/env.ts +5 -0
- package/lib/fs-aliases.ts +49 -0
- package/lib/git-clone-credentials.ts +334 -0
- package/lib/git-diff.ts +219 -177
- package/lib/git-identity.ts +59 -14
- package/lib/github-origin.ts +28 -0
- package/lib/github-tokens.ts +56 -21
- package/lib/host-env.ts +17 -4
- package/lib/launch-mode.ts +25 -0
- package/lib/legacy-runtime-retirement.ts +388 -0
- package/lib/machine-exec.ts +14 -3
- package/lib/machine-provider-aws.ts +50 -8
- package/lib/machine-provider-local.ts +4 -0
- package/lib/machine-provider.ts +4 -0
- package/lib/managed-operation-drain.ts +8 -0
- package/lib/managed-runtime.ts +371 -7
- package/lib/mcp-capture.ts +860 -0
- package/lib/mcp-connections.ts +8 -1
- package/lib/mcp-gateway.ts +168 -10
- package/lib/obs.ts +2 -6
- package/lib/orchestrator.ts +1837 -224
- package/lib/person-capture.ts +327 -0
- package/lib/project-env-legacy-tags.ts +130 -0
- package/lib/project-env-task-replica.ts +1752 -0
- package/lib/runner-boot.ts +10 -0
- package/lib/runtime-activation-tail.ts +50 -3
- package/lib/runtime-archive.ts +68 -1
- package/lib/runtime-reconnect.ts +86 -0
- package/lib/runtime-state.ts +429 -13
- package/lib/safe-http.ts +364 -0
- package/lib/secrets-proxy/task-trust.ts +34 -3
- package/lib/sidecar-resume.ts +18 -5
- package/lib/skills.ts +47 -8
- package/lib/standard-image.ts +157 -103
- package/lib/stopped-task-status-queue.ts +68 -10
- package/lib/task-credential-cleanup.ts +525 -0
- package/lib/task-diff-sandbox.ts +464 -0
- package/lib/task-diff.ts +66 -163
- package/lib/task-env-source.ts +68 -14
- package/lib/task-environment/anchor.ts +31 -0
- package/lib/task-environment/apple-container.ts +69 -28
- package/lib/task-environment/docker.ts +101 -20
- package/lib/task-environment/machine-environment.ts +61 -0
- package/lib/task-environment/machine-task-up.ts +459 -96
- package/lib/task-environment/machine.ts +101 -11
- package/lib/task-environment/self.ts +108 -50
- package/lib/task-environment/types.ts +89 -5
- package/lib/task-environment/workspace-files.ts +650 -25
- package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
- package/lib/task-gateway-recovery.ts +4 -1
- package/lib/task-gateway-state.ts +15 -0
- package/lib/task-machine-swap-safety.ts +729 -0
- package/lib/token-broker-adopt.ts +344 -0
- package/lib/token-broker-lambda.ts +56 -2
- package/lib/token-broker-local.ts +28 -4
- package/lib/token-broker-revision.ts +310 -0
- package/lib/token-broker-ssm.ts +368 -28
- package/lib/token-lanes/openai-oauth.ts +7 -3
- package/lib/token-lanes/xai-oauth.ts +7 -3
- package/lib/transcript.ts +9 -5
- package/package.json +6 -2
- package/scripts/agent/task-up.sh +242 -100
- package/src/cloud-credentials.ts +2399 -0
- package/src/event-outbox.ts +8 -6
- package/src/index.ts +1386 -875
- package/src/main.ts +1061 -139
- package/src/outbound-frame.ts +31 -0
- package/src/project-env-policy.ts +125 -10
- package/src/protocol.ts +699 -23
- package/src/ui/server.ts +48 -10
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-154 §7: the broker's exact-name entry point.
|
|
3
|
+
*
|
|
4
|
+
* The cloud is the only scheduler: it knows which revisions are active, so it
|
|
5
|
+
* names one revision — its broker-only part under `/uai-broker` and its
|
|
6
|
+
* lease-readable part under `/uai/lease` — and the broker refreshes exactly
|
|
7
|
+
* that one. It never lists `/uai-broker`, so a retired revision is never
|
|
8
|
+
* refreshed.
|
|
9
|
+
*
|
|
10
|
+
* Every invoke, holding the credential's lock:
|
|
11
|
+
* 1. makes the lease-readable copy agree with the broker-only material,
|
|
12
|
+
* rewriting it if a crash left it behind;
|
|
13
|
+
* 2. refreshes when due (or when the cloud forces it, for a runner's
|
|
14
|
+
* credential-invalid report), writing the broker-only part first and the
|
|
15
|
+
* lease-readable copy second, so a crash between them keeps the new
|
|
16
|
+
* refresh token and leaves the previous access token valid;
|
|
17
|
+
* 3. answers for that one credential, never with material: `refreshed` or
|
|
18
|
+
* `fresh` with the lease-readable parameter's version and the expiry,
|
|
19
|
+
* `needs_sign_in`, `busy`, or `unavailable`.
|
|
20
|
+
*
|
|
21
|
+
* An adopted revision (`legacyParameter`, owner answer 3) also keeps today's
|
|
22
|
+
* parameter it came from agreeing with it, the brokered copy only, until the
|
|
23
|
+
* fleet audit (token-broker-adopt.ts).
|
|
24
|
+
*
|
|
25
|
+
* Storage is an interface; the Lambda binds it to SSM (token-broker-ssm.ts).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
brokerErrorKind,
|
|
30
|
+
CREDENTIAL_LANE_SCHEDULES,
|
|
31
|
+
parseBrokerParameterName,
|
|
32
|
+
parseLeasedValue,
|
|
33
|
+
serializeLeasedValue,
|
|
34
|
+
type BrokerRevisionAnswer,
|
|
35
|
+
type BrokerRevisionRequest,
|
|
36
|
+
type CredentialLocation,
|
|
37
|
+
} from "../src/cloud-credentials";
|
|
38
|
+
import { parseBrokerEngineValue, type BrokerEngineValue } from "./capture-flow";
|
|
39
|
+
import { syncLegacyCopy, type LegacySync } from "./token-broker-adopt";
|
|
40
|
+
import { MCP_OAUTH_LANE, parseBrokerMcpValue, postTokenEndpoint, type BrokerMcpValue, oauthFailure } from "./mcp-capture";
|
|
41
|
+
import type { SafeHttpDeps } from "./safe-http";
|
|
42
|
+
import { backoffMs, createLaneHttp, isRefreshDue } from "./token-broker";
|
|
43
|
+
import { laneById } from "./token-lanes/registry";
|
|
44
|
+
import { isTerminalOAuthError, type CredentialFiles, type LaneHttp, type TokenLane } from "./token-lanes/types";
|
|
45
|
+
|
|
46
|
+
export interface StoredParameter {
|
|
47
|
+
value: string;
|
|
48
|
+
version: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The storage one invoke needs: read and overwrite two named parameters,
|
|
52
|
+
* and a lock per credential. */
|
|
53
|
+
export interface RevisionStore {
|
|
54
|
+
get(name: string): Promise<StoredParameter | null>;
|
|
55
|
+
/** Overwrite a parameter this invoke has just read; the new version. */
|
|
56
|
+
overwrite(name: string, value: string): Promise<number>;
|
|
57
|
+
/** The credential's lock, or null while another invoke holds a fresh one. */
|
|
58
|
+
lock(credentialId: string): Promise<(() => Promise<void>) | null>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const MAX_ERROR_CHARS = 512;
|
|
62
|
+
|
|
63
|
+
function leaseCopy(location: CredentialLocation, lane: TokenLane, files: CredentialFiles): string {
|
|
64
|
+
return serializeLeasedValue({
|
|
65
|
+
v: 1,
|
|
66
|
+
part: "lease",
|
|
67
|
+
location,
|
|
68
|
+
class: "engine",
|
|
69
|
+
authKind: "config-dir",
|
|
70
|
+
files: lane.brokeredCopy(files),
|
|
71
|
+
expiresAt: lane.expiresAt(files),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function sameFiles(a: CredentialFiles, b: CredentialFiles): boolean {
|
|
76
|
+
const keys = Object.keys(a).sort();
|
|
77
|
+
const other = Object.keys(b).sort();
|
|
78
|
+
return keys.length === other.length && keys.every((k, i) => k === other[i] && a[k] === b[k]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Refresh one named revision. Never throws for a provider or storage
|
|
82
|
+
* failure: the answer says what happened. */
|
|
83
|
+
export async function refreshRevision(
|
|
84
|
+
request: BrokerRevisionRequest,
|
|
85
|
+
store: RevisionStore,
|
|
86
|
+
options: {
|
|
87
|
+
now?: () => number;
|
|
88
|
+
http?: (lane: TokenLane) => LaneHttp;
|
|
89
|
+
/** The SSRF rule's seams, for an MCP server's token endpoint. */
|
|
90
|
+
safeHttp?: SafeHttpDeps;
|
|
91
|
+
/** ADR-134 root of an adopted compatibility copy. */
|
|
92
|
+
legacyPrefix?: string;
|
|
93
|
+
} = {},
|
|
94
|
+
): Promise<BrokerRevisionAnswer> {
|
|
95
|
+
const now = options.now ?? Date.now;
|
|
96
|
+
const location = parseBrokerParameterName(request.brokerParameter)!;
|
|
97
|
+
if (location.class === "mcp") return refreshMcpRevision(request, store, location, options);
|
|
98
|
+
let release: (() => Promise<void>) | null = null;
|
|
99
|
+
try {
|
|
100
|
+
release = await store.lock(request.credentialId);
|
|
101
|
+
if (!release) return { outcome: "busy" };
|
|
102
|
+
|
|
103
|
+
const brokerPart = await store.get(request.brokerParameter);
|
|
104
|
+
const broker = brokerPart ? parseBrokerEngineValue(brokerPart.value, location) : null;
|
|
105
|
+
if (!broker) return { outcome: "unavailable", error: "the revision's broker part is missing or unreadable" };
|
|
106
|
+
const lane = laneById(broker.lane);
|
|
107
|
+
if (!lane) return { outcome: "unavailable", error: `no lane ${broker.lane}` };
|
|
108
|
+
|
|
109
|
+
// 1. The lease-readable copy agrees with the broker-only material. It is
|
|
110
|
+
// rewritten, never created: a missing one was deleted with its retired
|
|
111
|
+
// revision, which must not come back.
|
|
112
|
+
const leasePart = await store.get(request.leaseParameter);
|
|
113
|
+
if (!leasePart) return { outcome: "unavailable", error: "the revision's lease-readable part is missing" };
|
|
114
|
+
const current = parseLeasedValue(leasePart.value, location);
|
|
115
|
+
const expected = lane.brokeredCopy(broker.files);
|
|
116
|
+
let version = leasePart.version;
|
|
117
|
+
if (
|
|
118
|
+
!current ||
|
|
119
|
+
current.class !== "engine" ||
|
|
120
|
+
current.authKind !== "config-dir" ||
|
|
121
|
+
!sameFiles(current.files, expected)
|
|
122
|
+
) {
|
|
123
|
+
version = await store.overwrite(request.leaseParameter, leaseCopy(location, lane, broker.files));
|
|
124
|
+
}
|
|
125
|
+
// An adopted revision: today's parameter agrees with it too.
|
|
126
|
+
let legacy = request.legacyParameter
|
|
127
|
+
? await syncLegacyCopy(store, request.legacyParameter, lane, broker.files, options.legacyPrefix)
|
|
128
|
+
: null;
|
|
129
|
+
const withLegacy = <T extends object>(answer: T): T & { legacy?: LegacySync } =>
|
|
130
|
+
legacy === null ? answer : { ...answer, legacy };
|
|
131
|
+
|
|
132
|
+
if (broker.refresh.needsSignIn) return { outcome: "needs_sign_in" };
|
|
133
|
+
const at = now();
|
|
134
|
+
if (!isRefreshDue(lane, broker.files, broker.refresh, at, request.force)) {
|
|
135
|
+
return withLegacy({
|
|
136
|
+
outcome: "fresh" as const,
|
|
137
|
+
version,
|
|
138
|
+
expiresAt: lane.expiresAt(broker.files) ?? broker.refresh.expiresAt,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. Refresh: the broker-only part first.
|
|
143
|
+
const http = options.http?.(lane) ?? createLaneHttp(lane.endpoints);
|
|
144
|
+
const result = await lane.refresh(broker.files, http, at);
|
|
145
|
+
if (!result.ok) {
|
|
146
|
+
const next: BrokerEngineValue = {
|
|
147
|
+
...broker,
|
|
148
|
+
refresh: {
|
|
149
|
+
...broker.refresh,
|
|
150
|
+
attempts: broker.refresh.attempts + 1,
|
|
151
|
+
lastAttemptAt: at,
|
|
152
|
+
lastError: result.reason.slice(0, MAX_ERROR_CHARS),
|
|
153
|
+
needsSignIn: result.terminal,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
await store.overwrite(request.brokerParameter, JSON.stringify(next));
|
|
157
|
+
return result.terminal
|
|
158
|
+
? { outcome: "needs_sign_in" }
|
|
159
|
+
: { outcome: "unavailable", error: result.reason.slice(0, MAX_ERROR_CHARS) };
|
|
160
|
+
}
|
|
161
|
+
const refreshed: BrokerEngineValue = {
|
|
162
|
+
...broker,
|
|
163
|
+
files: { ...result.files },
|
|
164
|
+
refresh: {
|
|
165
|
+
...broker.refresh,
|
|
166
|
+
expiresAt: result.expiresAt,
|
|
167
|
+
refreshedAt: at,
|
|
168
|
+
attempts: 0,
|
|
169
|
+
lastAttemptAt: at,
|
|
170
|
+
lastError: null,
|
|
171
|
+
needsSignIn: false,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
await store.overwrite(request.brokerParameter, JSON.stringify(refreshed));
|
|
175
|
+
version = await store.overwrite(request.leaseParameter, leaseCopy(location, lane, result.files));
|
|
176
|
+
if (request.legacyParameter) {
|
|
177
|
+
legacy = await syncLegacyCopy(store, request.legacyParameter, lane, result.files, options.legacyPrefix);
|
|
178
|
+
}
|
|
179
|
+
return withLegacy({ outcome: "refreshed" as const, version, expiresAt: result.expiresAt });
|
|
180
|
+
} catch (error) {
|
|
181
|
+
// The error's kind only: a message can quote what a request carried.
|
|
182
|
+
return { outcome: "unavailable", error: brokerErrorKind(error) };
|
|
183
|
+
} finally {
|
|
184
|
+
if (release) await release();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function mcpLeaseCopy(location: CredentialLocation, broker: BrokerMcpValue): string {
|
|
189
|
+
return serializeLeasedValue({
|
|
190
|
+
v: 1,
|
|
191
|
+
part: "lease",
|
|
192
|
+
location,
|
|
193
|
+
class: "mcp",
|
|
194
|
+
authKind: "oauth",
|
|
195
|
+
accessToken: broker.accessToken,
|
|
196
|
+
tokenType: "Bearer",
|
|
197
|
+
expiresAt: broker.refresh.expiresAt,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function mcpRefreshDue(broker: BrokerMcpValue, now: number, forced: boolean): boolean {
|
|
202
|
+
if (broker.refresh.needsSignIn) return false;
|
|
203
|
+
if (forced) return true;
|
|
204
|
+
const { attempts, lastAttemptAt } = broker.refresh;
|
|
205
|
+
if (attempts > 0 && lastAttemptAt !== null && now - lastAttemptAt < backoffMs(attempts)) return false;
|
|
206
|
+
const schedule = CREDENTIAL_LANE_SCHEDULES[MCP_OAUTH_LANE]!;
|
|
207
|
+
if (broker.refresh.expiresAt !== null) return broker.refresh.expiresAt - now <= schedule.leadMs;
|
|
208
|
+
return now - (broker.refresh.refreshedAt ?? 0) >= (schedule.everyMs ?? schedule.leadMs);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* ADR-154 §11: an MCP connection's sign-in, refreshed at its token endpoint
|
|
213
|
+
* through the SSRF rule. The same order as an engine's: the lease-readable
|
|
214
|
+
* copy agrees with the broker-only material first, then a due refresh writes
|
|
215
|
+
* the broker-only part before the copy. A sign-in that gave no refresh token,
|
|
216
|
+
* or that the provider calls dead, needs a new sign-in.
|
|
217
|
+
*/
|
|
218
|
+
async function refreshMcpRevision(
|
|
219
|
+
request: BrokerRevisionRequest,
|
|
220
|
+
store: RevisionStore,
|
|
221
|
+
location: CredentialLocation,
|
|
222
|
+
options: { now?: () => number; safeHttp?: SafeHttpDeps },
|
|
223
|
+
): Promise<BrokerRevisionAnswer> {
|
|
224
|
+
const now = options.now ?? Date.now;
|
|
225
|
+
let release: (() => Promise<void>) | null = null;
|
|
226
|
+
try {
|
|
227
|
+
release = await store.lock(request.credentialId);
|
|
228
|
+
if (!release) return { outcome: "busy" };
|
|
229
|
+
const brokerPart = await store.get(request.brokerParameter);
|
|
230
|
+
const broker = brokerPart ? parseBrokerMcpValue(brokerPart.value, location) : null;
|
|
231
|
+
if (!broker) return { outcome: "unavailable", error: "the revision's broker part is missing or unreadable" };
|
|
232
|
+
const leasePart = await store.get(request.leaseParameter);
|
|
233
|
+
if (!leasePart) return { outcome: "unavailable", error: "the revision's lease-readable part is missing" };
|
|
234
|
+
const current = parseLeasedValue(leasePart.value, location);
|
|
235
|
+
let version = leasePart.version;
|
|
236
|
+
if (
|
|
237
|
+
!current ||
|
|
238
|
+
current.class !== "mcp" ||
|
|
239
|
+
current.authKind !== "oauth" ||
|
|
240
|
+
current.accessToken !== broker.accessToken ||
|
|
241
|
+
current.expiresAt !== broker.refresh.expiresAt
|
|
242
|
+
) {
|
|
243
|
+
version = await store.overwrite(request.leaseParameter, mcpLeaseCopy(location, broker));
|
|
244
|
+
}
|
|
245
|
+
if (broker.refresh.needsSignIn) return { outcome: "needs_sign_in" };
|
|
246
|
+
const at = now();
|
|
247
|
+
if (!mcpRefreshDue(broker, at, request.force)) {
|
|
248
|
+
return { outcome: "fresh", version, expiresAt: broker.refresh.expiresAt };
|
|
249
|
+
}
|
|
250
|
+
const markFailed = async (reason: string, terminal: boolean): Promise<void> => {
|
|
251
|
+
const next: BrokerMcpValue = {
|
|
252
|
+
...broker,
|
|
253
|
+
refresh: {
|
|
254
|
+
...broker.refresh,
|
|
255
|
+
attempts: broker.refresh.attempts + 1,
|
|
256
|
+
lastAttemptAt: at,
|
|
257
|
+
lastError: reason.slice(0, MAX_ERROR_CHARS),
|
|
258
|
+
needsSignIn: terminal,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
await store.overwrite(request.brokerParameter, JSON.stringify(next));
|
|
262
|
+
};
|
|
263
|
+
if (broker.refreshToken === null) {
|
|
264
|
+
await markFailed("the server gave no refresh token", true);
|
|
265
|
+
return { outcome: "needs_sign_in" };
|
|
266
|
+
}
|
|
267
|
+
const { status, json } = await postTokenEndpoint(
|
|
268
|
+
broker.tokenEndpoint,
|
|
269
|
+
{
|
|
270
|
+
grant_type: "refresh_token",
|
|
271
|
+
refresh_token: broker.refreshToken,
|
|
272
|
+
client_id: broker.clientId,
|
|
273
|
+
resource: broker.resource,
|
|
274
|
+
},
|
|
275
|
+
{ clientId: broker.clientId, clientSecret: broker.clientSecret },
|
|
276
|
+
{ http: options.safeHttp },
|
|
277
|
+
);
|
|
278
|
+
if (status < 200 || status >= 300 || typeof json.access_token !== "string") {
|
|
279
|
+
const terminal = status >= 400 && isTerminalOAuthError(status, json);
|
|
280
|
+
// A standard OAuth error code or the status: never the endpoint's own text.
|
|
281
|
+
const reason = oauthFailure(json, status);
|
|
282
|
+
await markFailed(reason, terminal);
|
|
283
|
+
return terminal ? { outcome: "needs_sign_in" } : { outcome: "unavailable", error: reason.slice(0, MAX_ERROR_CHARS) };
|
|
284
|
+
}
|
|
285
|
+
const expiresAt = typeof json.expires_in === "number" ? at + json.expires_in * 1000 : null;
|
|
286
|
+
const refreshed: BrokerMcpValue = {
|
|
287
|
+
...broker,
|
|
288
|
+
accessToken: json.access_token,
|
|
289
|
+
// Rotating servers send a new one; the rest keep the old.
|
|
290
|
+
refreshToken: typeof json.refresh_token === "string" ? json.refresh_token : broker.refreshToken,
|
|
291
|
+
refresh: {
|
|
292
|
+
...broker.refresh,
|
|
293
|
+
expiresAt,
|
|
294
|
+
refreshedAt: at,
|
|
295
|
+
attempts: 0,
|
|
296
|
+
lastAttemptAt: at,
|
|
297
|
+
lastError: null,
|
|
298
|
+
needsSignIn: false,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
await store.overwrite(request.brokerParameter, JSON.stringify(refreshed));
|
|
302
|
+
version = await store.overwrite(request.leaseParameter, mcpLeaseCopy(location, refreshed));
|
|
303
|
+
return { outcome: "refreshed", version, expiresAt };
|
|
304
|
+
} catch (error) {
|
|
305
|
+
// The error's kind only: a message can quote what a request carried.
|
|
306
|
+
return { outcome: "unavailable", error: brokerErrorKind(error) };
|
|
307
|
+
} finally {
|
|
308
|
+
if (release) await release();
|
|
309
|
+
}
|
|
310
|
+
}
|