@byollm/relay 0.1.0-alpha.8 → 0.1.0-alpha.80

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.
@@ -0,0 +1,67 @@
1
+ import { R as RoutingStore } from './store-DPCLO12l.js';
2
+ import '@byollm/protocol';
3
+
4
+ /**
5
+ * The routing store's behaviour, written once and run against every
6
+ * implementation — cloud_008 finding 54.
7
+ *
8
+ * There were two copies. This package tested `RelayState`; `byollm-cloud`
9
+ * tested `ValkeyRoutingStore` with a file that opened by explaining it was
10
+ * "written once and parameterised… a second copy of the scenario is a second
11
+ * place for two implementations to quietly diverge" — and was itself the
12
+ * second copy. It had drifted to sixteen cases against the eighteen in the
13
+ * ledger, which is exactly the divergence the sentence predicted, happening
14
+ * to the sentence.
15
+ *
16
+ * So the contract lives here, in the package that declares `RoutingStore`,
17
+ * and both repositories import it. A store that cannot pass this is not a
18
+ * routing store, whatever it implements.
19
+ *
20
+ * ## Why this ships in the published package
21
+ *
22
+ * Because the implementation that matters most is in another repository. A
23
+ * contract only the author can run is a description; one a third party runs
24
+ * against their own implementation is a contract. `@byollm/relay/store-contract`
25
+ * is a subpath export so nothing that imports the relay itself pulls in a
26
+ * test framework.
27
+ *
28
+ * ## What this cannot prove
29
+ *
30
+ * `CLAIM_ATOMIC` is a MUST and every case here would pass against a store
31
+ * that read, decided and wrote in three steps — in one process there is
32
+ * nothing to interleave. Concurrency belongs with the implementation that
33
+ * has a network in it, and `byollm-cloud`'s suite runs it against Valkey with
34
+ * many connections. Named here so the omission is a decision rather than an
35
+ * oversight.
36
+ */
37
+ /** The control-plane site id these cases route under. */
38
+ declare const CONTRACT_SITE = "site_store";
39
+ interface StoreContractOptions {
40
+ /**
41
+ * A fresh, empty store, and how to dispose of it.
42
+ *
43
+ * Arrow-typed rather than a method, because the caller passes these
44
+ * around: a method signature lets `this` travel with the call, and a
45
+ * factory read off an options object is exactly where that goes wrong.
46
+ */
47
+ readonly make: () => Promise<{
48
+ store: RoutingStore;
49
+ done: () => Promise<void>;
50
+ }>;
51
+ /**
52
+ * Whether this store writes stubs as bytes and reads them back.
53
+ *
54
+ * An in-process store holds typed objects and cannot hold a stub it cannot
55
+ * parse; a serialising one can, because what it wrote may have been written
56
+ * by a previous version. The case that covers it is skipped rather than
57
+ * hidden — a reader should know which half of this contract each store is
58
+ * proving (cloud_008 §2.1a).
59
+ */
60
+ readonly serialising?: boolean;
61
+ /** Write a stub's raw bytes, bypassing serialisation. */
62
+ readonly writeRawStub?: (store: RoutingStore, id: string, raw: string) => Promise<void>;
63
+ }
64
+ /** Run the contract. Call inside a suite; it declares its own `describe`. */
65
+ declare function describeStoreContract(name: string, options: StoreContractOptions): void;
66
+
67
+ export { CONTRACT_SITE, type StoreContractOptions, describeStoreContract };
@@ -0,0 +1,625 @@
1
+ import {
2
+ routeKey
3
+ } from "./chunk-OB6LPEEE.js";
4
+
5
+ // src/store-contract.ts
6
+ import { generateKeys, publicIdentityOf } from "@byollm/protocol";
7
+ import { describe, expect, it } from "vitest";
8
+ var CONTRACT_SITE = "site_store";
9
+ var SITE_KEY_ID = "BYOLLM-TEST-SITE-KEY-ID";
10
+ var SITE = CONTRACT_SITE;
11
+ var stub = (id, owner = "alice") => ({
12
+ id,
13
+ kind: "llm.generate",
14
+ owner,
15
+ site: SITE_KEY_ID,
16
+ audience: "private",
17
+ sizeClass: "small",
18
+ streaming: false,
19
+ deadlineAt: 41024448e5
20
+ });
21
+ var capability = (model) => ({
22
+ kind: "llm.generate",
23
+ service: model,
24
+ backendId: "ollama",
25
+ backendClass: "process",
26
+ model,
27
+ offerScope: "private"
28
+ });
29
+ var ENVELOPE = {
30
+ ciphertext: "AAAA",
31
+ recipientKeyId: "r",
32
+ senderKeyId: "s",
33
+ direction: "payload",
34
+ deadlineAt: 41024448e5
35
+ };
36
+ var claimArgs = (over = {}) => {
37
+ const { owners = ["alice"], sites = [SITE], ...rest } = over;
38
+ return {
39
+ runnerId: "runner_1",
40
+ owner: "alice",
41
+ device: publicIdentityOf(generateKeys(Date.now())),
42
+ kinds: /* @__PURE__ */ new Set(["llm.generate"]),
43
+ routes: new Set(
44
+ sites.flatMap((site) => owners.map((owner) => routeKey(site, owner)))
45
+ ),
46
+ max: 10,
47
+ leaseMs: 6e4,
48
+ ...rest
49
+ };
50
+ };
51
+ function describeStoreContract(name, options) {
52
+ const { make, serialising, writeRawStub } = options;
53
+ describe(`the routing store \u2014 ${name}`, () => {
54
+ it("is idempotent by job id", async () => {
55
+ const { store, done } = await make();
56
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
57
+ await store.claim(claimArgs());
58
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
59
+ expect((await store.job(SITE, "a"))?.state).toBe("awaiting-payload");
60
+ await done();
61
+ });
62
+ it("gives two sites the same id without either seeing the other", async () => {
63
+ const { store, done } = await make();
64
+ await store.enqueue({ id: "shared", siteId: SITE, stub: stub("shared") });
65
+ await store.enqueue({
66
+ id: "shared",
67
+ siteId: "site_other",
68
+ stub: stub("shared", "bob")
69
+ });
70
+ expect((await store.job(SITE, "shared"))?.stub.owner).toBe("alice");
71
+ expect((await store.job("site_other", "shared"))?.stub.owner).toBe("bob");
72
+ const granted = await store.claim(claimArgs());
73
+ expect(granted.map((job) => job.id)).toEqual(["shared"]);
74
+ expect(granted[0]?.owner).toBe("alice");
75
+ const replay = await store.enqueue({
76
+ id: "shared",
77
+ siteId: SITE,
78
+ stub: stub("shared")
79
+ });
80
+ expect(replay).toEqual(await store.job(SITE, "shared"));
81
+ await done();
82
+ });
83
+ it("grants only what the owners set allows", async () => {
84
+ const { store, done } = await make();
85
+ await store.enqueue({ id: "mine", siteId: SITE, stub: stub("mine") });
86
+ await store.enqueue({
87
+ id: "theirs",
88
+ siteId: SITE,
89
+ stub: stub("theirs", "mallory")
90
+ });
91
+ const granted = await store.claim(claimArgs());
92
+ expect(granted.map((job) => job.id)).toEqual(["mine"]);
93
+ await done();
94
+ });
95
+ it("refuses a stale lease and honours the current one", async () => {
96
+ const { store, done } = await make();
97
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
98
+ const [granted] = await store.claim(claimArgs());
99
+ await store.seal({ jobId: "a", siteId: SITE, envelope: ENVELOPE });
100
+ expect(
101
+ await store.takePayload({
102
+ jobId: "a",
103
+ runnerId: "runner_1",
104
+ leaseId: "an-older-grant"
105
+ })
106
+ ).toEqual({ refused: "stale-lease" });
107
+ expect(
108
+ await store.takePayload({
109
+ jobId: "a",
110
+ runnerId: "runner_1",
111
+ leaseId: granted.lease.id
112
+ })
113
+ ).toEqual({ envelope: ENVELOPE });
114
+ await done();
115
+ });
116
+ it("will not answer for one job with another job's lease", async () => {
117
+ const { store, done } = await make();
118
+ await store.enqueue({ id: "mine", siteId: SITE, stub: stub("mine") });
119
+ await store.enqueue({ id: "other", siteId: SITE, stub: stub("other") });
120
+ const granted = await store.claim(claimArgs());
121
+ const mine = granted.find((job) => job.id === "mine");
122
+ const other = granted.find((job) => job.id === "other");
123
+ await store.seal({ jobId: "other", siteId: SITE, envelope: ENVELOPE });
124
+ expect(
125
+ await store.takePayload({
126
+ jobId: "other",
127
+ runnerId: "runner_1",
128
+ leaseId: mine.lease.id
129
+ })
130
+ ).toEqual({ refused: "stale-lease" });
131
+ expect(
132
+ await store.takePayload({
133
+ jobId: "other",
134
+ runnerId: "runner_1",
135
+ leaseId: other.lease.id
136
+ })
137
+ ).toEqual({ envelope: ENVELOPE });
138
+ await done();
139
+ });
140
+ it("renews a grant it still holds, and extends the sweep with it", async () => {
141
+ const { store, done } = await make();
142
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
143
+ const [granted] = await store.claim(claimArgs({ leaseMs: 1e3 }));
144
+ const renewed = await store.renewLeases({
145
+ runnerId: "runner_1",
146
+ leases: [{ jobId: "a", leaseId: granted.lease.id }],
147
+ leaseMs: 6e4
148
+ });
149
+ expect(renewed.lost).toEqual([]);
150
+ expect(renewed.renewed.map((r) => r.jobId)).toEqual(["a"]);
151
+ expect(renewed.renewed[0].expiresAt).toBeGreaterThan(
152
+ granted.lease.expiresAt
153
+ );
154
+ expect((await store.job(SITE, "a"))?.claimedBy?.leaseExpiresAt).toBe(
155
+ renewed.renewed[0].expiresAt
156
+ );
157
+ await done();
158
+ });
159
+ it("reports a lease the runner no longer holds as lost, and renews nothing", async () => {
160
+ const { store, done } = await make();
161
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
162
+ const [granted] = await store.claim(claimArgs({ leaseMs: 1e3 }));
163
+ const before = (await store.job(SITE, "a"))?.claimedBy?.leaseExpiresAt;
164
+ const renewed = await store.renewLeases({
165
+ runnerId: "runner_1",
166
+ leases: [{ jobId: "a", leaseId: "a-grant-that-ended" }],
167
+ leaseMs: 6e4
168
+ });
169
+ expect(renewed.renewed).toEqual([]);
170
+ expect(renewed.lost).toEqual([
171
+ { jobId: "a", leaseId: "a-grant-that-ended" }
172
+ ]);
173
+ expect((await store.job(SITE, "a"))?.claimedBy?.leaseExpiresAt).toBe(
174
+ before
175
+ );
176
+ expect((await store.job(SITE, "a"))?.claimedBy?.leaseId).toBe(
177
+ granted.lease.id
178
+ );
179
+ await done();
180
+ });
181
+ it("does not hand out a stub it cannot parse", async () => {
182
+ const { store, done } = await make();
183
+ if (!serialising || writeRawStub === void 0) {
184
+ await done();
185
+ return;
186
+ }
187
+ await store.enqueue({ id: "good", siteId: SITE, stub: stub("good") });
188
+ await store.enqueue({ id: "old", siteId: SITE, stub: stub("old") });
189
+ const legacy = { ...stub("old") };
190
+ delete legacy["site"];
191
+ await writeRawStub(store, "old", JSON.stringify(legacy));
192
+ const granted = await store.claim(claimArgs({ max: 10 }));
193
+ expect(granted.map((job) => job.id)).toEqual(["good"]);
194
+ expect((await store.job(SITE, "old"))?.state).toBe("queued");
195
+ await done();
196
+ });
197
+ it("honours a self job's audience", async () => {
198
+ const { store, done } = await make();
199
+ await store.enqueue({
200
+ id: "private",
201
+ siteId: SITE,
202
+ stub: { ...stub("private", "alice"), audience: "private" }
203
+ });
204
+ const granted = await store.claim(
205
+ claimArgs({ owner: "owner", owners: ["owner", "alice"] })
206
+ );
207
+ expect(granted).toEqual([]);
208
+ expect((await store.job(SITE, "private"))?.state).toBe("queued");
209
+ await done();
210
+ });
211
+ it("still offers that job to its owner's own machine", async () => {
212
+ const { store, done } = await make();
213
+ await store.enqueue({
214
+ id: "private",
215
+ siteId: SITE,
216
+ stub: { ...stub("private", "alice"), audience: "private" }
217
+ });
218
+ const granted = await store.claim(
219
+ claimArgs({ owner: "alice", owners: ["alice"] })
220
+ );
221
+ expect(granted.map((job) => job.id)).toEqual(["private"]);
222
+ await done();
223
+ });
224
+ it("does not offer a job back to the runner that refused it", async () => {
225
+ const { store, done } = await make();
226
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
227
+ const [granted] = await store.claim(claimArgs());
228
+ await store.releaseLeases({
229
+ runnerId: "runner_1",
230
+ leases: [{ jobId: "a", leaseId: granted.lease.id }],
231
+ reason: "refused"
232
+ });
233
+ expect(await store.claim(claimArgs())).toEqual([]);
234
+ expect((await store.job(SITE, "a"))?.state).toBe("queued");
235
+ expect(
236
+ (await store.claim(claimArgs({ runnerId: "runner_2" }))).map(
237
+ (j) => j.id
238
+ )
239
+ ).toEqual(["a"]);
240
+ await done();
241
+ });
242
+ it("does not offer a job back to a runner before its not-before", async () => {
243
+ const { store, done } = await make();
244
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
245
+ const [granted] = await store.claim(claimArgs());
246
+ await store.releaseLeases({
247
+ runnerId: "runner_1",
248
+ leases: [{ jobId: "a", leaseId: granted.lease.id }],
249
+ retryAfter: Date.now() + 6e5
250
+ });
251
+ expect(await store.claim(claimArgs())).toEqual([]);
252
+ expect((await store.job(SITE, "a"))?.state).toBe("queued");
253
+ expect(
254
+ (await store.claim(claimArgs({ runnerId: "runner_2" }))).map(
255
+ (j) => j.id
256
+ )
257
+ ).toEqual(["a"]);
258
+ await done();
259
+ });
260
+ it("offers it again once the not-before has passed", async () => {
261
+ const { store, done } = await make();
262
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
263
+ const [granted] = await store.claim(claimArgs());
264
+ await store.releaseLeases({
265
+ runnerId: "runner_1",
266
+ leases: [{ jobId: "a", leaseId: granted.lease.id }],
267
+ retryAfter: Date.now() - 1e3
268
+ });
269
+ expect((await store.claim(claimArgs())).map((j) => j.id)).toEqual(["a"]);
270
+ await done();
271
+ });
272
+ it("will not un-finish a job that is already done", async () => {
273
+ const { store, done } = await make();
274
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
275
+ const [granted] = await store.claim(claimArgs());
276
+ const lease = granted.lease.id;
277
+ await store.takePayload({
278
+ jobId: "a",
279
+ runnerId: "runner_1",
280
+ leaseId: lease
281
+ });
282
+ await store.complete({
283
+ jobId: "a",
284
+ runnerId: "runner_1",
285
+ leaseId: lease,
286
+ envelope: ENVELOPE,
287
+ disposition: "ok"
288
+ });
289
+ const released = await store.releaseLeases({
290
+ runnerId: "runner_1",
291
+ leases: [{ jobId: "a", leaseId: lease }],
292
+ reason: "shutdown"
293
+ });
294
+ expect(released).toEqual([]);
295
+ expect((await store.job(SITE, "a"))?.state).toBe("done");
296
+ expect((await store.finished(SITE)).map((j) => j.id)).toEqual(["a"]);
297
+ expect(await store.claim(claimArgs({ runnerId: "runner_2" }))).toEqual(
298
+ []
299
+ );
300
+ await done();
301
+ });
302
+ it("keeps a job claimable by a runner that only went away", async () => {
303
+ const { store, done } = await make();
304
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
305
+ const [granted] = await store.claim(claimArgs());
306
+ await store.releaseLeases({
307
+ runnerId: "runner_1",
308
+ leases: [{ jobId: "a", leaseId: granted.lease.id }],
309
+ reason: "shutdown"
310
+ });
311
+ expect((await store.claim(claimArgs())).map((j) => j.id)).toEqual(["a"]);
312
+ await done();
313
+ });
314
+ it("stops offering a job past its deadline, and drops the ciphertext", async () => {
315
+ const { store, done } = await make();
316
+ await store.enqueue({
317
+ id: "late",
318
+ siteId: SITE,
319
+ stub: { ...stub("late"), deadlineAt: Date.now() - 1e3 }
320
+ });
321
+ expect(await store.claim(claimArgs())).toEqual([]);
322
+ expect(await store.job(SITE, "late")).toBeUndefined();
323
+ await done();
324
+ });
325
+ it("names a cancelled job to the device holding it, and offers it to nobody", async () => {
326
+ const { store, done } = await make();
327
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
328
+ await store.claim(claimArgs());
329
+ expect(await store.cancel({ jobId: "a", siteId: SITE })).toBe(true);
330
+ const held = (await store.job(SITE, "a"))?.claimedBy?.leaseId;
331
+ expect(await store.cancelRequests("runner_1")).toEqual([
332
+ { jobId: "a", leaseId: held }
333
+ ]);
334
+ expect(await store.cancel({ jobId: "a", siteId: "someone_else" })).toBe(
335
+ false
336
+ );
337
+ await done();
338
+ });
339
+ it("records a result once", async () => {
340
+ const { store, done } = await make();
341
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
342
+ const [granted] = await store.claim(claimArgs());
343
+ const first = await store.complete({
344
+ jobId: "a",
345
+ runnerId: "runner_1",
346
+ leaseId: granted.lease.id,
347
+ envelope: ENVELOPE,
348
+ disposition: "ok"
349
+ });
350
+ const replay = await store.complete({
351
+ jobId: "a",
352
+ runnerId: "runner_1",
353
+ leaseId: granted.lease.id,
354
+ envelope: { ...ENVELOPE, ciphertext: "different" },
355
+ disposition: "error"
356
+ });
357
+ expect(first).toEqual({ accepted: true, state: "done" });
358
+ expect(replay).toEqual({
359
+ accepted: false,
360
+ duplicate: true,
361
+ state: "done"
362
+ });
363
+ expect((await store.job(SITE, "a"))?.disposition).toBe("ok");
364
+ await done();
365
+ });
366
+ it("answers the device that finished it with `duplicate`", async () => {
367
+ const { store, done } = await make();
368
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
369
+ const [granted] = await store.claim(claimArgs());
370
+ const first = await store.complete({
371
+ jobId: "a",
372
+ runnerId: "runner_1",
373
+ leaseId: granted.lease.id,
374
+ envelope: ENVELOPE,
375
+ disposition: "ok"
376
+ });
377
+ expect(first).toEqual({ accepted: true, state: "done" });
378
+ const replay = await store.complete({
379
+ jobId: "a",
380
+ runnerId: "runner_1",
381
+ leaseId: granted.lease.id,
382
+ envelope: { ...ENVELOPE, ciphertext: "a-different-result" },
383
+ disposition: "error"
384
+ });
385
+ expect(replay).toEqual({
386
+ accepted: false,
387
+ duplicate: true,
388
+ state: "done"
389
+ });
390
+ const stranger = await store.complete({
391
+ jobId: "a",
392
+ runnerId: "runner_2",
393
+ leaseId: granted.lease.id,
394
+ envelope: ENVELOPE,
395
+ disposition: "ok"
396
+ });
397
+ expect(stranger).toEqual({ refused: "not-holder" });
398
+ expect((await store.job(SITE, "a"))?.disposition).toBe("ok");
399
+ await done();
400
+ });
401
+ it("refuses a result produced under a grant that ended", async () => {
402
+ const { store, done } = await make();
403
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
404
+ await store.claim(claimArgs());
405
+ const late = await store.complete({
406
+ jobId: "a",
407
+ runnerId: "runner_1",
408
+ leaseId: "a-grant-that-ended",
409
+ envelope: ENVELOPE,
410
+ disposition: "ok"
411
+ });
412
+ expect(late).toEqual({ refused: "stale-lease" });
413
+ expect((await store.job(SITE, "a"))?.state).toBe("awaiting-payload");
414
+ expect((await store.job(SITE, "a"))?.result).toBeUndefined();
415
+ await done();
416
+ });
417
+ it("refuses a payload for a job that already finished [V1-6]", async () => {
418
+ const { store, done } = await make();
419
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
420
+ const [granted] = await store.claim(claimArgs());
421
+ await store.seal({ jobId: "a", siteId: SITE, envelope: ENVELOPE });
422
+ await store.takePayload({
423
+ jobId: "a",
424
+ runnerId: "runner_1",
425
+ leaseId: granted.lease.id
426
+ });
427
+ await store.complete({
428
+ jobId: "a",
429
+ runnerId: "runner_1",
430
+ leaseId: granted.lease.id,
431
+ envelope: ENVELOPE,
432
+ disposition: "ok"
433
+ });
434
+ const replay = await store.takePayload({
435
+ jobId: "a",
436
+ runnerId: "runner_1",
437
+ leaseId: granted.lease.id
438
+ });
439
+ expect(replay).toEqual({ refused: "terminal" });
440
+ expect((await store.job(SITE, "a"))?.state).toBe("done");
441
+ await done();
442
+ });
443
+ it("tells a runner nothing about another site's job id [V1-8]", async () => {
444
+ const { store, done } = await make();
445
+ await store.enqueue({
446
+ id: "secret",
447
+ siteId: "site_somebody_else",
448
+ stub: stub("secret")
449
+ });
450
+ await store.enqueue({ id: "mine", siteId: SITE, stub: stub("mine") });
451
+ const granted = await store.claim(claimArgs());
452
+ const mine = granted.find((job) => job.id === "mine");
453
+ const other = await store.takePayload({
454
+ jobId: "secret",
455
+ runnerId: "runner_1",
456
+ leaseId: mine.lease.id
457
+ });
458
+ const absent = await store.takePayload({
459
+ jobId: "no-such-job-anywhere",
460
+ runnerId: "runner_1",
461
+ leaseId: mine.lease.id
462
+ });
463
+ expect(other).toEqual(absent);
464
+ expect(other).toEqual({ refused: "not-found" });
465
+ await done();
466
+ });
467
+ it("offers a job to any device that serves its kind", async () => {
468
+ const { store, done } = await make();
469
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
470
+ const claimed = await store.claim(
471
+ claimArgs({ runnerId: "runner_any", kinds: /* @__PURE__ */ new Set(["llm.generate"]) })
472
+ );
473
+ expect(claimed.map((j) => j.id)).toEqual(["a"]);
474
+ await done();
475
+ });
476
+ it("does not offer a job to a device that serves a different kind", async () => {
477
+ const { store, done } = await make();
478
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
479
+ const claimed = await store.claim(
480
+ claimArgs({ runnerId: "runner_chat", kinds: /* @__PURE__ */ new Set(["llm.chat"]) })
481
+ );
482
+ expect(claimed).toEqual([]);
483
+ await done();
484
+ });
485
+ it("remembers a device, and what it says it can run", async () => {
486
+ const { store, done } = await make();
487
+ const device = publicIdentityOf(generateKeys(3e12));
488
+ await store.seen({
489
+ runnerId: "runner_1",
490
+ owner: "alice",
491
+ device,
492
+ capabilities: [capability("llama3")],
493
+ withheld: []
494
+ });
495
+ const known = await store.presence("runner_1");
496
+ expect(known?.owner).toBe("alice");
497
+ expect(known?.device.identity).toBe(device.identity);
498
+ expect(known?.capabilities.map((row) => row.model)).toEqual(["llama3"]);
499
+ await done();
500
+ });
501
+ it("replaces the matrix rather than merging it", async () => {
502
+ const { store, done } = await make();
503
+ const device = publicIdentityOf(generateKeys(3000000000001));
504
+ const seen = { runnerId: "runner_2", owner: "alice", device };
505
+ await store.seen({
506
+ ...seen,
507
+ capabilities: [capability("llama3")],
508
+ withheld: []
509
+ });
510
+ await store.seen({
511
+ ...seen,
512
+ capabilities: [capability("mistral")],
513
+ withheld: []
514
+ });
515
+ const known = await store.presence("runner_2");
516
+ expect(known?.capabilities.map((row) => row.model)).toEqual(["mistral"]);
517
+ await done();
518
+ });
519
+ it("keeps an empty matrix, because empty is an answer", async () => {
520
+ const { store, done } = await make();
521
+ const device = publicIdentityOf(generateKeys(3000000000002));
522
+ const seen = { runnerId: "runner_3", owner: "alice", device };
523
+ await store.seen({
524
+ ...seen,
525
+ capabilities: [capability("llama3")],
526
+ withheld: []
527
+ });
528
+ await store.seen({ ...seen, capabilities: [], withheld: [] });
529
+ expect((await store.presence("runner_3"))?.capabilities).toEqual([]);
530
+ await done();
531
+ });
532
+ it("remembers which kinds are withheld, and drops them when resolved", async () => {
533
+ const { store, done } = await make();
534
+ const device = publicIdentityOf(generateKeys(3000000000004));
535
+ const seen = { runnerId: "runner_withheld", owner: "alice", device };
536
+ await store.seen({
537
+ ...seen,
538
+ capabilities: [],
539
+ withheld: [
540
+ {
541
+ kind: "llm.generate",
542
+ claimants: [
543
+ { service: "ollama", offer: "team" },
544
+ { service: "mlx", offer: "private" }
545
+ ]
546
+ }
547
+ ]
548
+ });
549
+ const held = await store.presence("runner_withheld");
550
+ expect(held?.withheld.map((row) => row.kind)).toEqual(["llm.generate"]);
551
+ expect(held?.withheld[0]?.claimants.map((c) => c.service)).toEqual([
552
+ "ollama",
553
+ "mlx"
554
+ ]);
555
+ await store.seen({
556
+ ...seen,
557
+ capabilities: [capability("llama3")],
558
+ withheld: []
559
+ });
560
+ expect((await store.presence("runner_withheld"))?.withheld).toEqual([]);
561
+ await done();
562
+ });
563
+ it("stamps presence from its own clock, and moves it on the next beat", async () => {
564
+ const { store, done } = await make();
565
+ const device = publicIdentityOf(generateKeys(3000000000003));
566
+ const seen = { runnerId: "runner_4", owner: "alice", device };
567
+ const before = await store.now();
568
+ const first = await store.seen({
569
+ ...seen,
570
+ capabilities: [],
571
+ withheld: []
572
+ });
573
+ expect(first.lastSeenAt).toBeGreaterThanOrEqual(before);
574
+ const later = await store.seen({
575
+ ...seen,
576
+ capabilities: [],
577
+ withheld: []
578
+ });
579
+ expect(later.lastSeenAt).toBeGreaterThanOrEqual(first.lastSeenAt);
580
+ await done();
581
+ });
582
+ it("lists everyone it has seen", async () => {
583
+ const { store, done } = await make();
584
+ await store.seen({
585
+ runnerId: "runner_5",
586
+ owner: "alice",
587
+ device: publicIdentityOf(generateKeys(3000000000004)),
588
+ capabilities: [],
589
+ withheld: []
590
+ });
591
+ await store.seen({
592
+ runnerId: "runner_6",
593
+ owner: "bob",
594
+ device: publicIdentityOf(generateKeys(3000000000005)),
595
+ capabilities: [],
596
+ withheld: []
597
+ });
598
+ const everyone = await store.everyone();
599
+ const ids = everyone.map((row) => row.runnerId);
600
+ expect(ids).toContain("runner_5");
601
+ expect(ids).toContain("runner_6");
602
+ await done();
603
+ });
604
+ it("does not know a runner it has never seen", async () => {
605
+ const { store, done } = await make();
606
+ expect(await store.presence("runner_never")).toBeUndefined();
607
+ await done();
608
+ });
609
+ it("stamps deadlines from its own clock", async () => {
610
+ const { store, done } = await make();
611
+ await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
612
+ const before = await store.now();
613
+ const [granted] = await store.claim(claimArgs({ leaseMs: 45e3 }));
614
+ const after = await store.now();
615
+ expect(granted.lease.expiresAt).toBeGreaterThanOrEqual(before + 45e3);
616
+ expect(granted.lease.expiresAt).toBeLessThanOrEqual(after + 45e3);
617
+ await done();
618
+ });
619
+ });
620
+ }
621
+ export {
622
+ CONTRACT_SITE,
623
+ describeStoreContract
624
+ };
625
+ //# sourceMappingURL=store-contract.js.map