@frockbot/plugin-credentials 0.0.0 → 0.1.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/frockbot.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "schemaVersion": 3,
3
+ "id": "credentials",
4
+ "displayName": "Credential Store",
5
+ "version": "0.0.1",
6
+ "compatibility": { "frockbot": ">=0.0.1" },
7
+ "contributions": {
8
+ "backend": [{ "entry": "./user", "host": "user" }],
9
+ "runtime": { "entry": "./runtime" }
10
+ },
11
+ "permissions": ["credentials:manage"]
12
+ }
package/package.json CHANGED
@@ -1,14 +1,36 @@
1
1
  {
2
2
  "name": "@frockbot/plugin-credentials",
3
- "version": "0.0.0",
4
- "description": "Placeholder reserving this name for trusted publishing. Superseded by the first release.",
5
- "license": "UNLICENSED",
3
+ "version": "0.1.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "exports": {
7
+ "./user": "./src/user.ts",
8
+ "./runtime": "./src/runtime.ts",
9
+ "./manifest": "./src/manifest.ts",
10
+ "./frockbot.json": "./frockbot.json",
11
+ "./package.json": "./package.json"
12
+ },
13
+ "frockbot": {
14
+ "manifest": "./frockbot.json"
15
+ },
16
+ "scripts": {
17
+ "test": "bun test src",
18
+ "typecheck": "tsc --noEmit -p tsconfig.json"
19
+ },
20
+ "dependencies": {
21
+ "@frockbot/connection-core": "0.1.1",
22
+ "cordis": "4.0.0-rc.8"
23
+ },
24
+ "devDependencies": {
25
+ "@types/bun": "1.3.6",
26
+ "typescript": "^7.0.2"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
6
31
  "repository": {
7
32
  "type": "git",
8
33
  "url": "git+https://github.com/timoconnellaus/frockbot.git",
9
34
  "directory": "packages/plugin-credentials"
10
- },
11
- "publishConfig": {
12
- "access": "public"
13
35
  }
14
36
  }
@@ -0,0 +1,4 @@
1
+ // This typed export is the Package manifest consumed by the application compiler.
2
+ import manifest from "../frockbot.json" with { type: "json" };
3
+
4
+ export default manifest;
@@ -0,0 +1,64 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ parseCredentialKeyringV1,
4
+ sealCredentialV1,
5
+ } from "@frockbot/connection-core";
6
+ import { Context } from "cordis";
7
+ import {
8
+ createCredentialRuntimePlugin,
9
+ type CredentialLeaseRuntime,
10
+ } from "./runtime.js";
11
+
12
+ const serializedKeyring =
13
+ '{"schemaVersion":1,"currentKeyId":"primary","keys":{"primary":"MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY"}}';
14
+
15
+ describe("Credential runtime Contribution", () => {
16
+ test("opens an opaque lease only for its exact authority", async () => {
17
+ const envelope = await sealCredentialV1({
18
+ keyring: parseCredentialKeyringV1(serializedKeyring),
19
+ context: {
20
+ accountId: "account-1",
21
+ connectionId: "connection-1",
22
+ packageId: "provider-ollama-cloud",
23
+ credentialGeneration: "generation-1",
24
+ },
25
+ plaintext: "secret",
26
+ });
27
+ const lease = {
28
+ schemaVersion: 1 as const,
29
+ leaseId: "lease-1",
30
+ effectId: "effect-1",
31
+ connectionId: "connection-1",
32
+ credentialGeneration: "generation-1",
33
+ expiresAt: "2099-01-01T00:00:00.000Z",
34
+ envelope,
35
+ };
36
+ const root = new Context();
37
+ await root.plugin(
38
+ createCredentialRuntimePlugin({
39
+ readSecret: () => serializedKeyring,
40
+ }),
41
+ );
42
+
43
+ const credentialLease = (
44
+ root as Context & { credentialLease: CredentialLeaseRuntime }
45
+ ).credentialLease;
46
+ await expect(
47
+ credentialLease.open({
48
+ accountId: "account-1",
49
+ connectionId: "connection-1",
50
+ packageId: "provider-ollama-cloud",
51
+ lease,
52
+ }),
53
+ ).resolves.toBe("secret");
54
+ await expect(
55
+ credentialLease.open({
56
+ accountId: "another-account",
57
+ connectionId: "connection-1",
58
+ packageId: "provider-ollama-cloud",
59
+ lease,
60
+ }),
61
+ ).rejects.toThrow();
62
+ await root.fiber.dispose();
63
+ });
64
+ });
package/src/runtime.ts ADDED
@@ -0,0 +1,61 @@
1
+ import {
2
+ decodeCredentialLeaseV1,
3
+ type CredentialLeaseV1,
4
+ openCredentialV1,
5
+ parseCredentialKeyringV1,
6
+ } from "@frockbot/connection-core";
7
+ import { type Context, type Plugin, Service } from "cordis";
8
+
9
+ export interface CredentialLeaseOpenRequest {
10
+ accountId: string;
11
+ connectionId: string;
12
+ packageId: string;
13
+ lease: CredentialLeaseV1;
14
+ }
15
+
16
+ export interface CredentialRuntimeConfig {
17
+ readSecret(name: "CREDENTIAL_KEYRING"): string | undefined;
18
+ }
19
+
20
+ export class CredentialLeaseRuntime extends Service {
21
+ private readonly keyring;
22
+
23
+ constructor(ctx: Context, config: CredentialRuntimeConfig) {
24
+ super(ctx, "credentialLease");
25
+ const serialized = config.readSecret("CREDENTIAL_KEYRING");
26
+ if (!serialized) {
27
+ throw new Error("Credential Store Contribution is not configured");
28
+ }
29
+ this.keyring = parseCredentialKeyringV1(serialized);
30
+ }
31
+
32
+ open(input: CredentialLeaseOpenRequest): Promise<string> {
33
+ const lease = decodeCredentialLeaseV1(input.lease);
34
+ if (
35
+ lease.connectionId !== input.connectionId ||
36
+ lease.envelope.credentialGeneration !== lease.credentialGeneration
37
+ ) {
38
+ return Promise.reject(new Error("Credential lease authority is invalid"));
39
+ }
40
+ return openCredentialV1({
41
+ keyring: this.keyring,
42
+ context: {
43
+ accountId: input.accountId,
44
+ connectionId: input.connectionId,
45
+ packageId: input.packageId,
46
+ credentialGeneration: lease.credentialGeneration,
47
+ },
48
+ envelope: lease.envelope,
49
+ });
50
+ }
51
+ }
52
+
53
+ export function createCredentialRuntimePlugin(
54
+ config: CredentialRuntimeConfig,
55
+ ): Plugin.Function {
56
+ return (ctx) => {
57
+ new CredentialLeaseRuntime(ctx, config);
58
+ };
59
+ }
60
+
61
+ export default createCredentialRuntimePlugin;
@@ -0,0 +1,512 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ openCredentialV1,
4
+ parseCredentialKeyringV1,
5
+ } from "@frockbot/connection-core";
6
+ import {
7
+ createCredentialUserBackendContribution,
8
+ type CredentialStorage,
9
+ type CredentialTransaction,
10
+ } from "./user.js";
11
+
12
+ class MemoryStorage implements CredentialStorage {
13
+ readonly values = new Map<string, unknown>();
14
+ alarm?: number;
15
+
16
+ get<T>(key: string): Promise<T | undefined> {
17
+ return Promise.resolve(this.values.get(key) as T | undefined);
18
+ }
19
+
20
+ put<T>(key: string, value: T): Promise<void>;
21
+ put(entries: Record<string, unknown>): Promise<void>;
22
+ put<T>(
23
+ keyOrEntries: string | Record<string, unknown>,
24
+ value?: T,
25
+ ): Promise<void> {
26
+ if (typeof keyOrEntries === "string") this.values.set(keyOrEntries, value);
27
+ else
28
+ for (const [key, entry] of Object.entries(keyOrEntries))
29
+ this.values.set(key, entry);
30
+ return Promise.resolve();
31
+ }
32
+
33
+ delete(key: string): Promise<boolean> {
34
+ return Promise.resolve(this.values.delete(key));
35
+ }
36
+
37
+ transaction<T>(
38
+ callback: (storage: CredentialTransaction) => Promise<T>,
39
+ ): Promise<T> {
40
+ return callback(this);
41
+ }
42
+
43
+ getAlarm(): Promise<number | null> {
44
+ return Promise.resolve(this.alarm ?? null);
45
+ }
46
+
47
+ setAlarm(scheduledTime: number | Date): Promise<void> {
48
+ this.alarm = Number(scheduledTime);
49
+ return Promise.resolve();
50
+ }
51
+ }
52
+
53
+ const bytes = Uint8Array.from({ length: 32 }, (_, index) => index + 7);
54
+ let binary = "";
55
+ for (const byte of bytes) binary += String.fromCharCode(byte);
56
+ const encodedKey = btoa(binary)
57
+ .replaceAll("+", "-")
58
+ .replaceAll("/", "_")
59
+ .replace(/=+$/, "");
60
+ const serializedKeyring = JSON.stringify({
61
+ schemaVersion: 1,
62
+ currentKeyId: "primary",
63
+ keys: { primary: encodedKey },
64
+ });
65
+
66
+ function contribution(
67
+ storage = new MemoryStorage(),
68
+ now: () => number = () => Date.parse("2026-08-30T00:00:00.000Z"),
69
+ ) {
70
+ return {
71
+ storage,
72
+ credentials: createCredentialUserBackendContribution({
73
+ storage,
74
+ keyring: serializedKeyring,
75
+ now,
76
+ }),
77
+ };
78
+ }
79
+
80
+ const authority = {
81
+ accountId: "account-1",
82
+ connectionId: "connection-1",
83
+ packageId: "provider-ollama-cloud",
84
+ expectedGeneration: "generation-1",
85
+ };
86
+
87
+ describe("Credential User Contribution", () => {
88
+ test("rejects malformed durable credential generations and leases", async () => {
89
+ const { storage, credentials } = contribution();
90
+ await credentials.stageApiKey({
91
+ ...authority,
92
+ generation: "generation-1",
93
+ apiKey: "secret",
94
+ });
95
+ const generationKey = "credential:connection-1:generation-1";
96
+ storage.values.set(generationKey, {
97
+ ...(storage.values.get(generationKey) as Record<string, unknown>),
98
+ unexpected: true,
99
+ });
100
+
101
+ await expect(
102
+ credentials.readStagedApiKey({
103
+ ...authority,
104
+ generation: "generation-1",
105
+ }),
106
+ ).rejects.toThrow("Stored credential generation is invalid");
107
+
108
+ storage.values.delete(generationKey);
109
+ await credentials.stageApiKey({
110
+ ...authority,
111
+ generation: "generation-1",
112
+ apiKey: "secret",
113
+ });
114
+ await credentials.activate({ ...authority, generation: "generation-1" });
115
+ const lease = await credentials.lease({
116
+ ...authority,
117
+ effectId: "effect-1",
118
+ expiresAt: "2026-08-30T01:00:00.000Z",
119
+ });
120
+ const leaseKey = "credential-lease:effect-1";
121
+ storage.values.set(leaseKey, {
122
+ ...(storage.values.get(leaseKey) as Record<string, unknown>),
123
+ settled: "no",
124
+ });
125
+
126
+ await expect(
127
+ credentials.openLease({
128
+ accountId: authority.accountId,
129
+ packageId: authority.packageId,
130
+ lease,
131
+ }),
132
+ ).rejects.toThrow("Stored credential lease is invalid");
133
+
134
+ storage.values.set("credential-lease-queue", {
135
+ schemaVersion: 1,
136
+ headPage: 0,
137
+ tailPage: 0,
138
+ scanPage: null,
139
+ scanMinimum: null,
140
+ nextAlarm: null,
141
+ unexpected: true,
142
+ });
143
+ await expect(credentials.nextLeaseExpiry()).rejects.toThrow(
144
+ "Stored credential lease queue is invalid",
145
+ );
146
+ });
147
+
148
+ test("deletes an unleased credential generation during rotation", async () => {
149
+ const { storage, credentials } = contribution();
150
+ await credentials.stageApiKey({
151
+ ...authority,
152
+ generation: "generation-1",
153
+ apiKey: "old-key",
154
+ });
155
+ await credentials.activate({ ...authority, generation: "generation-1" });
156
+ await credentials.stageApiKey({
157
+ ...authority,
158
+ generation: "generation-2",
159
+ apiKey: "new-key",
160
+ });
161
+ await credentials.activate({ ...authority, generation: "generation-2" });
162
+
163
+ expect(storage.values.has("credential:connection-1:generation-1")).toBe(
164
+ false,
165
+ );
166
+ expect(storage.values.has("credential:connection-1:generation-2")).toBe(
167
+ true,
168
+ );
169
+ });
170
+
171
+ test("rotates atomically while admitted effects retain the old generation", async () => {
172
+ const { storage, credentials } = contribution();
173
+ await credentials.stageApiKey({
174
+ ...authority,
175
+ generation: "generation-1",
176
+ apiKey: "old-key",
177
+ });
178
+ await credentials.activate({ ...authority, generation: "generation-1" });
179
+ const oldLease = await credentials.lease({
180
+ ...authority,
181
+ effectId: "effect-1",
182
+ expiresAt: "2026-08-30T01:00:00.000Z",
183
+ });
184
+
185
+ await credentials.stageApiKey({
186
+ ...authority,
187
+ generation: "generation-2",
188
+ apiKey: "new-key",
189
+ });
190
+ await credentials.activate({ ...authority, generation: "generation-2" });
191
+ const newLease = await credentials.lease({
192
+ ...authority,
193
+ expectedGeneration: "generation-2",
194
+ effectId: "effect-2",
195
+ expiresAt: "2026-08-30T01:00:00.000Z",
196
+ });
197
+
198
+ expect(oldLease.credentialGeneration).toBe("generation-1");
199
+ expect(newLease.credentialGeneration).toBe("generation-2");
200
+ expect(storage.values.has("credential:connection-1:generation-1")).toBe(
201
+ true,
202
+ );
203
+ const keyring = parseCredentialKeyringV1(serializedKeyring);
204
+ expect(
205
+ await openCredentialV1({
206
+ keyring,
207
+ context: {
208
+ ...authority,
209
+ credentialGeneration: oldLease.credentialGeneration,
210
+ },
211
+ envelope: oldLease.envelope,
212
+ }),
213
+ ).toBe("old-key");
214
+ });
215
+
216
+ test("replays the same effect lease and rejects cross-Connection reuse", async () => {
217
+ const { credentials } = contribution();
218
+ await credentials.stageApiKey({
219
+ ...authority,
220
+ generation: "generation-1",
221
+ apiKey: "secret",
222
+ });
223
+ await credentials.activate({ ...authority, generation: "generation-1" });
224
+ const first = await credentials.lease({
225
+ ...authority,
226
+ effectId: "effect-1",
227
+ expiresAt: "2026-08-30T01:00:00.000Z",
228
+ });
229
+ const replay = await credentials.lease({
230
+ ...authority,
231
+ effectId: "effect-1",
232
+ expiresAt: "2026-08-30T02:00:00.000Z",
233
+ });
234
+ expect(replay).toEqual(first);
235
+ await expect(
236
+ credentials.lease({
237
+ ...authority,
238
+ connectionId: "connection-2",
239
+ effectId: "effect-1",
240
+ expiresAt: "2026-08-30T02:00:00.000Z",
241
+ }),
242
+ ).rejects.toThrow("Credential lease effect id was reused");
243
+ });
244
+
245
+ test("expires leases until a delayed durable outcome settles", async () => {
246
+ let now = Date.parse("2026-08-30T00:00:00.000Z");
247
+ const { credentials } = contribution(undefined, () => now);
248
+ await credentials.stageApiKey({
249
+ ...authority,
250
+ generation: "generation-1",
251
+ apiKey: "secret",
252
+ });
253
+ await credentials.activate({ ...authority, generation: "generation-1" });
254
+ const lease = await credentials.lease({
255
+ ...authority,
256
+ effectId: "effect-expired",
257
+ expiresAt: "2026-08-30T01:00:00.000Z",
258
+ });
259
+
260
+ now = Date.parse("2026-08-30T01:00:00.000Z");
261
+ await expect(
262
+ credentials.openLease({
263
+ accountId: authority.accountId,
264
+ packageId: authority.packageId,
265
+ lease,
266
+ }),
267
+ ).rejects.toThrow("Credential lease expired");
268
+ await expect(
269
+ credentials.lease({
270
+ ...authority,
271
+ effectId: "effect-expired",
272
+ expiresAt: "2026-08-30T02:00:00.000Z",
273
+ }),
274
+ ).rejects.toThrow("Credential lease expired");
275
+
276
+ await credentials.settle({ ...authority, effectId: "effect-expired" });
277
+
278
+ await expect(
279
+ credentials.lease({
280
+ ...authority,
281
+ effectId: "effect-expired",
282
+ expiresAt: "2026-08-30T02:00:00.000Z",
283
+ }),
284
+ ).resolves.toMatchObject({ effectId: "effect-expired" });
285
+ });
286
+
287
+ test("tombstones an expired pending-generation lease", async () => {
288
+ let now = Date.parse("2026-08-30T00:00:00.000Z");
289
+ const { storage, credentials } = contribution(undefined, () => now);
290
+ await credentials.stageApiKey({
291
+ ...authority,
292
+ generation: "pending-generation",
293
+ apiKey: "secret",
294
+ });
295
+ await credentials.lease({
296
+ ...authority,
297
+ expectedGeneration: "pending-generation",
298
+ credentialState: "pending",
299
+ effectId: "validation:connect-1",
300
+ expiresAt: "2026-08-30T01:00:00.000Z",
301
+ });
302
+
303
+ now = Date.parse("2026-08-30T01:00:00.000Z");
304
+ await credentials.expireLeases();
305
+
306
+ expect(
307
+ storage.values.has("credential-lease-expired:validation:connect-1"),
308
+ ).toBe(true);
309
+ await expect(
310
+ credentials.lease({
311
+ ...authority,
312
+ expectedGeneration: "pending-generation",
313
+ credentialState: "pending",
314
+ effectId: "validation:connect-1",
315
+ expiresAt: "2026-08-30T02:00:00.000Z",
316
+ }),
317
+ ).rejects.toThrow("Credential lease expired");
318
+
319
+ await credentials.discardPending("connection-1", "pending-generation");
320
+
321
+ expect(
322
+ storage.values.has("credential-lease-expired:validation:connect-1"),
323
+ ).toBe(false);
324
+ expect(
325
+ storage.values.has(
326
+ "credential-lease-expired-index:connection-1:pending-generation",
327
+ ),
328
+ ).toBe(false);
329
+ });
330
+
331
+ test("bounds expired lease tombstones", async () => {
332
+ let now = Date.parse("2026-08-30T00:00:00.000Z");
333
+ const { storage, credentials } = contribution(
334
+ new MemoryStorage(),
335
+ () => now,
336
+ );
337
+ await credentials.stageApiKey({
338
+ ...authority,
339
+ generation: "generation-1",
340
+ apiKey: "secret",
341
+ });
342
+ await credentials.activate({ ...authority, generation: "generation-1" });
343
+ for (let index = 0; index < 64; index += 1) {
344
+ await credentials.lease({
345
+ ...authority,
346
+ effectId: `effect-${index}`,
347
+ expiresAt: "2026-08-30T01:00:00.000Z",
348
+ });
349
+ }
350
+ await expect(
351
+ credentials.lease({
352
+ ...authority,
353
+ effectId: "effect-over-capacity",
354
+ expiresAt: "2026-08-30T01:00:00.000Z",
355
+ }),
356
+ ).rejects.toThrow("Credential lease capacity requires rotation");
357
+
358
+ const otherAuthority = {
359
+ ...authority,
360
+ connectionId: "connection-2",
361
+ expectedGeneration: "other-generation",
362
+ };
363
+ await credentials.stageApiKey({
364
+ ...otherAuthority,
365
+ generation: "other-generation",
366
+ apiKey: "other-secret",
367
+ });
368
+ await credentials.activate({
369
+ ...otherAuthority,
370
+ generation: "other-generation",
371
+ });
372
+ await expect(
373
+ credentials.lease({
374
+ ...otherAuthority,
375
+ effectId: "other-connection-effect",
376
+ expiresAt: "2026-08-30T01:00:00.000Z",
377
+ }),
378
+ ).resolves.toMatchObject({ connectionId: "connection-2" });
379
+
380
+ now = Date.parse("2026-08-30T01:00:00.000Z");
381
+ await credentials.expireLeases();
382
+
383
+ expect(
384
+ [...storage.values.keys()].filter((key) =>
385
+ key.startsWith("credential-lease-expired:effect-"),
386
+ ),
387
+ ).toHaveLength(64);
388
+ expect(storage.values.has("credential-lease:other-connection-effect")).toBe(
389
+ true,
390
+ );
391
+ expect(storage.alarm).toBe(now);
392
+ await credentials.expireLeases();
393
+ expect(storage.values.has("credential-lease:other-connection-effect")).toBe(
394
+ false,
395
+ );
396
+ await expect(
397
+ credentials.lease({
398
+ ...authority,
399
+ effectId: "effect-0",
400
+ expiresAt: "2026-08-30T02:00:00.000Z",
401
+ }),
402
+ ).rejects.toThrow("Credential lease expired");
403
+
404
+ await credentials.stageApiKey({
405
+ ...authority,
406
+ generation: "generation-2",
407
+ apiKey: "replacement",
408
+ });
409
+ await credentials.activate({ ...authority, generation: "generation-2" });
410
+ expect(
411
+ [...storage.values.keys()].filter((key) =>
412
+ key.startsWith("credential-lease-expired:effect-"),
413
+ ),
414
+ ).toHaveLength(0);
415
+ expect(
416
+ storage.values.has("credential-lease-expired:other-connection-effect"),
417
+ ).toBe(true);
418
+ await expect(
419
+ credentials.lease({
420
+ ...authority,
421
+ effectId: "effect-0",
422
+ expiresAt: "2026-08-30T02:00:00.000Z",
423
+ }),
424
+ ).rejects.toThrow("Connection credential is unavailable");
425
+ });
426
+
427
+ test("disconnect purges the active generation expiry ledger", async () => {
428
+ let now = Date.parse("2026-08-30T00:00:00.000Z");
429
+ const { storage, credentials } = contribution(
430
+ new MemoryStorage(),
431
+ () => now,
432
+ );
433
+ await credentials.stageApiKey({
434
+ ...authority,
435
+ generation: "generation-1",
436
+ apiKey: "secret",
437
+ });
438
+ await credentials.activate({ ...authority, generation: "generation-1" });
439
+ await credentials.lease({
440
+ ...authority,
441
+ effectId: "expired-before-disconnect",
442
+ expiresAt: "2026-08-30T01:00:00.000Z",
443
+ });
444
+ now = Date.parse("2026-08-30T01:00:00.000Z");
445
+ await credentials.expireLeases();
446
+ expect(
447
+ storage.values.has("credential-lease-expired:expired-before-disconnect"),
448
+ ).toBe(true);
449
+
450
+ await credentials.disconnect(authority.connectionId);
451
+
452
+ expect(
453
+ storage.values.has("credential-lease-expired:expired-before-disconnect"),
454
+ ).toBe(false);
455
+ expect(
456
+ storage.values.has(
457
+ "credential-lease-expired-index:connection-1:generation-1",
458
+ ),
459
+ ).toBe(false);
460
+ expect(
461
+ storage.values.has(
462
+ "credential-lease-generation-index:connection-1:generation-1",
463
+ ),
464
+ ).toBe(false);
465
+ });
466
+
467
+ test("disconnect blocks new leases while preserving an admitted lease", async () => {
468
+ const { credentials } = contribution();
469
+ await credentials.stageApiKey({
470
+ ...authority,
471
+ generation: "generation-1",
472
+ apiKey: "secret",
473
+ });
474
+ await credentials.activate({ ...authority, generation: "generation-1" });
475
+ const admitted = await credentials.lease({
476
+ ...authority,
477
+ effectId: "effect-1",
478
+ expiresAt: "2026-08-30T01:00:00.000Z",
479
+ });
480
+
481
+ await credentials.disconnect(authority.connectionId);
482
+
483
+ expect(
484
+ await credentials.lease({
485
+ ...authority,
486
+ effectId: "effect-1",
487
+ expiresAt: "2026-08-30T01:00:00.000Z",
488
+ }),
489
+ ).toEqual(admitted);
490
+ await expect(
491
+ credentials.lease({
492
+ ...authority,
493
+ effectId: "effect-2",
494
+ expiresAt: "2026-08-30T01:00:00.000Z",
495
+ }),
496
+ ).rejects.toThrow("Connection credential is unavailable");
497
+ await expect(
498
+ credentials.settle({
499
+ ...authority,
500
+ connectionId: "connection-2",
501
+ effectId: "effect-1",
502
+ }),
503
+ ).rejects.toThrow("Credential lease authority does not match");
504
+ await expect(
505
+ credentials.replayLease({
506
+ ...authority,
507
+ effectId: "effect-1",
508
+ }),
509
+ ).resolves.toEqual(admitted);
510
+ await credentials.settle({ ...authority, effectId: "effect-1" });
511
+ });
512
+ });