@byollm/relay 0.1.0-alpha.9 → 0.1.0-alpha.91
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/README.md +162 -5
- package/dist/chunk-4ISUXCIO.js +484 -0
- package/dist/chunk-4ISUXCIO.js.map +1 -0
- package/dist/index.d.ts +506 -163
- package/dist/index.js +980 -264
- package/dist/index.js.map +1 -1
- package/dist/store-BNhCedHZ.d.ts +771 -0
- package/dist/store-contract.d.ts +77 -0
- package/dist/store-contract.js +712 -0
- package/dist/store-contract.js.map +1 -0
- package/package.json +19 -4
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AWAITING_PAYLOAD_MS,
|
|
3
|
+
routeKey
|
|
4
|
+
} from "./chunk-4ISUXCIO.js";
|
|
5
|
+
|
|
6
|
+
// src/store-contract.ts
|
|
7
|
+
import { generateKeys, publicIdentityOf } from "@byollm/protocol";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
var CONTRACT_SITE = "site_store";
|
|
10
|
+
var SITE_KEY_ID = "BYOLLM-TEST-SITE-KEY-ID";
|
|
11
|
+
var SITE = CONTRACT_SITE;
|
|
12
|
+
var stub = (id, owner = "alice") => ({
|
|
13
|
+
id,
|
|
14
|
+
kind: "llm.generate",
|
|
15
|
+
owner,
|
|
16
|
+
site: SITE_KEY_ID,
|
|
17
|
+
audience: "private",
|
|
18
|
+
sizeClass: "small",
|
|
19
|
+
streaming: false,
|
|
20
|
+
deadlineAt: 41024448e5
|
|
21
|
+
});
|
|
22
|
+
var capability = (model) => ({
|
|
23
|
+
kind: "llm.generate",
|
|
24
|
+
service: model,
|
|
25
|
+
backendId: "ollama",
|
|
26
|
+
backendClass: "process",
|
|
27
|
+
model,
|
|
28
|
+
offerScope: "private"
|
|
29
|
+
});
|
|
30
|
+
var ENVELOPE = {
|
|
31
|
+
ciphertext: "AAAA",
|
|
32
|
+
recipientKeyId: "r",
|
|
33
|
+
senderKeyId: "s",
|
|
34
|
+
direction: "payload",
|
|
35
|
+
deadlineAt: 41024448e5
|
|
36
|
+
};
|
|
37
|
+
var claimArgs = (over = {}) => {
|
|
38
|
+
const { owners = ["alice"], sites = [SITE], ...rest } = over;
|
|
39
|
+
return {
|
|
40
|
+
runnerId: "runner_1",
|
|
41
|
+
owner: "alice",
|
|
42
|
+
device: publicIdentityOf(generateKeys(Date.now())),
|
|
43
|
+
kinds: /* @__PURE__ */ new Set(["llm.generate"]),
|
|
44
|
+
routes: new Set(
|
|
45
|
+
sites.flatMap((site) => owners.map((owner) => routeKey(site, owner)))
|
|
46
|
+
),
|
|
47
|
+
max: 10,
|
|
48
|
+
leaseMs: 6e4,
|
|
49
|
+
...rest
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
function describeStoreContract(name, options) {
|
|
53
|
+
const { make, serialising, writeRawStub, advance } = options;
|
|
54
|
+
describe(`the routing store \u2014 ${name}`, () => {
|
|
55
|
+
it("is idempotent by job id", async () => {
|
|
56
|
+
const { store, done } = await make();
|
|
57
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
58
|
+
await store.claim(claimArgs());
|
|
59
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
60
|
+
expect((await store.job(SITE, "a"))?.state).toBe("awaiting-payload");
|
|
61
|
+
await done();
|
|
62
|
+
});
|
|
63
|
+
it("hands one pair at most two unsealed grants at a time", async () => {
|
|
64
|
+
const { store, done } = await make();
|
|
65
|
+
for (const id of ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]) {
|
|
66
|
+
await store.enqueue({ id, siteId: SITE, stub: stub(id) });
|
|
67
|
+
}
|
|
68
|
+
const granted = await store.claim(claimArgs());
|
|
69
|
+
expect(granted).toHaveLength(2);
|
|
70
|
+
await done();
|
|
71
|
+
});
|
|
72
|
+
it("counts the cap per owner, not per site", async () => {
|
|
73
|
+
const { store, done } = await make();
|
|
74
|
+
for (const id of ["a1", "a2", "a3"]) {
|
|
75
|
+
await store.enqueue({ id, siteId: SITE, stub: stub(id, "alice") });
|
|
76
|
+
}
|
|
77
|
+
for (const id of ["b1", "b2", "b3"]) {
|
|
78
|
+
await store.enqueue({ id, siteId: SITE, stub: stub(id, "bob") });
|
|
79
|
+
}
|
|
80
|
+
const forAlice = await store.claim(claimArgs());
|
|
81
|
+
const forBob = await store.claim(
|
|
82
|
+
claimArgs({ owner: "bob", owners: ["bob"], runnerId: "runner_2" })
|
|
83
|
+
);
|
|
84
|
+
expect(forAlice, "alice gets her two").toHaveLength(2);
|
|
85
|
+
expect(
|
|
86
|
+
forBob,
|
|
87
|
+
"and bob gets his own two, rather than what is left of a site-wide pair"
|
|
88
|
+
).toHaveLength(2);
|
|
89
|
+
await done();
|
|
90
|
+
});
|
|
91
|
+
it("re-offers an abandoned grant only to the device that abandoned it", async () => {
|
|
92
|
+
const { store, done } = await make();
|
|
93
|
+
if (advance === void 0) {
|
|
94
|
+
await done();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
98
|
+
const first = await store.claim(claimArgs());
|
|
99
|
+
expect(first).toHaveLength(1);
|
|
100
|
+
await advance(store, AWAITING_PAYLOAD_MS + 1e3);
|
|
101
|
+
const stranger = await store.claim(claimArgs({ runnerId: "runner_2" }));
|
|
102
|
+
expect(stranger, "a device that never held it gets nothing").toEqual([]);
|
|
103
|
+
const again = await store.claim(claimArgs());
|
|
104
|
+
expect(again, "its first claimant gets it back").toHaveLength(1);
|
|
105
|
+
await done();
|
|
106
|
+
});
|
|
107
|
+
it("leaves an unabandoned job free for whoever asks", async () => {
|
|
108
|
+
const { store, done } = await make();
|
|
109
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
110
|
+
const other = await store.claim(claimArgs({ runnerId: "runner_9" }));
|
|
111
|
+
expect(other).toHaveLength(1);
|
|
112
|
+
await done();
|
|
113
|
+
});
|
|
114
|
+
it("lets a sealing site keep working, which is every honest one", async () => {
|
|
115
|
+
const { store, done } = await make();
|
|
116
|
+
for (const id of ["a", "b", "c", "d"]) {
|
|
117
|
+
await store.enqueue({ id, siteId: SITE, stub: stub(id) });
|
|
118
|
+
}
|
|
119
|
+
const first = await store.claim(claimArgs());
|
|
120
|
+
expect(first).toHaveLength(2);
|
|
121
|
+
for (const job of first) {
|
|
122
|
+
await store.seal({
|
|
123
|
+
siteId: SITE,
|
|
124
|
+
jobId: job.id,
|
|
125
|
+
envelope: ENVELOPE
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const next = await store.claim(claimArgs());
|
|
129
|
+
expect(next, "the slots came back").toHaveLength(2);
|
|
130
|
+
await done();
|
|
131
|
+
});
|
|
132
|
+
it("gives two sites the same id without either seeing the other", async () => {
|
|
133
|
+
const { store, done } = await make();
|
|
134
|
+
await store.enqueue({ id: "shared", siteId: SITE, stub: stub("shared") });
|
|
135
|
+
await store.enqueue({
|
|
136
|
+
id: "shared",
|
|
137
|
+
siteId: "site_other",
|
|
138
|
+
stub: stub("shared", "bob")
|
|
139
|
+
});
|
|
140
|
+
expect((await store.job(SITE, "shared"))?.stub.owner).toBe("alice");
|
|
141
|
+
expect((await store.job("site_other", "shared"))?.stub.owner).toBe("bob");
|
|
142
|
+
const granted = await store.claim(claimArgs());
|
|
143
|
+
expect(granted.map((job) => job.id)).toEqual(["shared"]);
|
|
144
|
+
expect(granted[0]?.owner).toBe("alice");
|
|
145
|
+
const replay = await store.enqueue({
|
|
146
|
+
id: "shared",
|
|
147
|
+
siteId: SITE,
|
|
148
|
+
stub: stub("shared")
|
|
149
|
+
});
|
|
150
|
+
expect(replay).toEqual(await store.job(SITE, "shared"));
|
|
151
|
+
await done();
|
|
152
|
+
});
|
|
153
|
+
it("grants only what the owners set allows", async () => {
|
|
154
|
+
const { store, done } = await make();
|
|
155
|
+
await store.enqueue({ id: "mine", siteId: SITE, stub: stub("mine") });
|
|
156
|
+
await store.enqueue({
|
|
157
|
+
id: "theirs",
|
|
158
|
+
siteId: SITE,
|
|
159
|
+
stub: stub("theirs", "mallory")
|
|
160
|
+
});
|
|
161
|
+
const granted = await store.claim(claimArgs());
|
|
162
|
+
expect(granted.map((job) => job.id)).toEqual(["mine"]);
|
|
163
|
+
await done();
|
|
164
|
+
});
|
|
165
|
+
it("refuses a stale lease and honours the current one", async () => {
|
|
166
|
+
const { store, done } = await make();
|
|
167
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
168
|
+
const [granted] = await store.claim(claimArgs());
|
|
169
|
+
await store.seal({ jobId: "a", siteId: SITE, envelope: ENVELOPE });
|
|
170
|
+
expect(
|
|
171
|
+
await store.takePayload({
|
|
172
|
+
jobId: "a",
|
|
173
|
+
runnerId: "runner_1",
|
|
174
|
+
leaseId: "an-older-grant"
|
|
175
|
+
})
|
|
176
|
+
).toEqual({ refused: "stale-lease" });
|
|
177
|
+
expect(
|
|
178
|
+
await store.takePayload({
|
|
179
|
+
jobId: "a",
|
|
180
|
+
runnerId: "runner_1",
|
|
181
|
+
leaseId: granted.lease.id
|
|
182
|
+
})
|
|
183
|
+
).toEqual({ envelope: ENVELOPE });
|
|
184
|
+
await done();
|
|
185
|
+
});
|
|
186
|
+
it("will not answer for one job with another job's lease", async () => {
|
|
187
|
+
const { store, done } = await make();
|
|
188
|
+
await store.enqueue({ id: "mine", siteId: SITE, stub: stub("mine") });
|
|
189
|
+
await store.enqueue({ id: "other", siteId: SITE, stub: stub("other") });
|
|
190
|
+
const granted = await store.claim(claimArgs());
|
|
191
|
+
const mine = granted.find((job) => job.id === "mine");
|
|
192
|
+
const other = granted.find((job) => job.id === "other");
|
|
193
|
+
await store.seal({ jobId: "other", siteId: SITE, envelope: ENVELOPE });
|
|
194
|
+
expect(
|
|
195
|
+
await store.takePayload({
|
|
196
|
+
jobId: "other",
|
|
197
|
+
runnerId: "runner_1",
|
|
198
|
+
leaseId: mine.lease.id
|
|
199
|
+
})
|
|
200
|
+
).toEqual({ refused: "stale-lease" });
|
|
201
|
+
expect(
|
|
202
|
+
await store.takePayload({
|
|
203
|
+
jobId: "other",
|
|
204
|
+
runnerId: "runner_1",
|
|
205
|
+
leaseId: other.lease.id
|
|
206
|
+
})
|
|
207
|
+
).toEqual({ envelope: ENVELOPE });
|
|
208
|
+
await done();
|
|
209
|
+
});
|
|
210
|
+
it("renews a grant it still holds, and extends the sweep with it", async () => {
|
|
211
|
+
const { store, done } = await make();
|
|
212
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
213
|
+
const [granted] = await store.claim(claimArgs({ leaseMs: 1e3 }));
|
|
214
|
+
const renewed = await store.renewLeases({
|
|
215
|
+
runnerId: "runner_1",
|
|
216
|
+
leases: [{ jobId: "a", leaseId: granted.lease.id }],
|
|
217
|
+
leaseMs: 6e4
|
|
218
|
+
});
|
|
219
|
+
expect(renewed.lost).toEqual([]);
|
|
220
|
+
expect(renewed.renewed.map((r) => r.jobId)).toEqual(["a"]);
|
|
221
|
+
expect(renewed.renewed[0].expiresAt).toBeGreaterThan(
|
|
222
|
+
granted.lease.expiresAt
|
|
223
|
+
);
|
|
224
|
+
expect((await store.job(SITE, "a"))?.claimedBy?.leaseExpiresAt).toBe(
|
|
225
|
+
renewed.renewed[0].expiresAt
|
|
226
|
+
);
|
|
227
|
+
await done();
|
|
228
|
+
});
|
|
229
|
+
it("reports a lease the runner no longer holds as lost, and renews nothing", async () => {
|
|
230
|
+
const { store, done } = await make();
|
|
231
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
232
|
+
const [granted] = await store.claim(claimArgs({ leaseMs: 1e3 }));
|
|
233
|
+
const before = (await store.job(SITE, "a"))?.claimedBy?.leaseExpiresAt;
|
|
234
|
+
const renewed = await store.renewLeases({
|
|
235
|
+
runnerId: "runner_1",
|
|
236
|
+
leases: [{ jobId: "a", leaseId: "a-grant-that-ended" }],
|
|
237
|
+
leaseMs: 6e4
|
|
238
|
+
});
|
|
239
|
+
expect(renewed.renewed).toEqual([]);
|
|
240
|
+
expect(renewed.lost).toEqual([
|
|
241
|
+
{ jobId: "a", leaseId: "a-grant-that-ended" }
|
|
242
|
+
]);
|
|
243
|
+
expect((await store.job(SITE, "a"))?.claimedBy?.leaseExpiresAt).toBe(
|
|
244
|
+
before
|
|
245
|
+
);
|
|
246
|
+
expect((await store.job(SITE, "a"))?.claimedBy?.leaseId).toBe(
|
|
247
|
+
granted.lease.id
|
|
248
|
+
);
|
|
249
|
+
await done();
|
|
250
|
+
});
|
|
251
|
+
it("does not hand out a stub it cannot parse", async () => {
|
|
252
|
+
const { store, done } = await make();
|
|
253
|
+
if (!serialising || writeRawStub === void 0) {
|
|
254
|
+
await done();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
await store.enqueue({ id: "good", siteId: SITE, stub: stub("good") });
|
|
258
|
+
await store.enqueue({ id: "old", siteId: SITE, stub: stub("old") });
|
|
259
|
+
const legacy = { ...stub("old") };
|
|
260
|
+
delete legacy["site"];
|
|
261
|
+
await writeRawStub(store, "old", JSON.stringify(legacy));
|
|
262
|
+
const granted = await store.claim(claimArgs({ max: 10 }));
|
|
263
|
+
expect(granted.map((job) => job.id)).toEqual(["good"]);
|
|
264
|
+
expect((await store.job(SITE, "old"))?.state).toBe("queued");
|
|
265
|
+
await done();
|
|
266
|
+
});
|
|
267
|
+
it("honours a self job's audience", async () => {
|
|
268
|
+
const { store, done } = await make();
|
|
269
|
+
await store.enqueue({
|
|
270
|
+
id: "private",
|
|
271
|
+
siteId: SITE,
|
|
272
|
+
stub: { ...stub("private", "alice"), audience: "private" }
|
|
273
|
+
});
|
|
274
|
+
const granted = await store.claim(
|
|
275
|
+
claimArgs({ owner: "owner", owners: ["owner", "alice"] })
|
|
276
|
+
);
|
|
277
|
+
expect(granted).toEqual([]);
|
|
278
|
+
expect((await store.job(SITE, "private"))?.state).toBe("queued");
|
|
279
|
+
await done();
|
|
280
|
+
});
|
|
281
|
+
it("still offers that job to its owner's own machine", async () => {
|
|
282
|
+
const { store, done } = await make();
|
|
283
|
+
await store.enqueue({
|
|
284
|
+
id: "private",
|
|
285
|
+
siteId: SITE,
|
|
286
|
+
stub: { ...stub("private", "alice"), audience: "private" }
|
|
287
|
+
});
|
|
288
|
+
const granted = await store.claim(
|
|
289
|
+
claimArgs({ owner: "alice", owners: ["alice"] })
|
|
290
|
+
);
|
|
291
|
+
expect(granted.map((job) => job.id)).toEqual(["private"]);
|
|
292
|
+
await done();
|
|
293
|
+
});
|
|
294
|
+
it("does not offer a job back to the runner that refused it", async () => {
|
|
295
|
+
const { store, done } = await make();
|
|
296
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
297
|
+
const [granted] = await store.claim(claimArgs());
|
|
298
|
+
await store.releaseLeases({
|
|
299
|
+
runnerId: "runner_1",
|
|
300
|
+
leases: [{ jobId: "a", leaseId: granted.lease.id }],
|
|
301
|
+
reason: "refused"
|
|
302
|
+
});
|
|
303
|
+
expect(await store.claim(claimArgs())).toEqual([]);
|
|
304
|
+
expect((await store.job(SITE, "a"))?.state).toBe("queued");
|
|
305
|
+
expect(
|
|
306
|
+
(await store.claim(claimArgs({ runnerId: "runner_2" }))).map(
|
|
307
|
+
(j) => j.id
|
|
308
|
+
)
|
|
309
|
+
).toEqual(["a"]);
|
|
310
|
+
await done();
|
|
311
|
+
});
|
|
312
|
+
it("does not offer a job back to a runner before its not-before", async () => {
|
|
313
|
+
const { store, done } = await make();
|
|
314
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
315
|
+
const [granted] = await store.claim(claimArgs());
|
|
316
|
+
await store.releaseLeases({
|
|
317
|
+
runnerId: "runner_1",
|
|
318
|
+
leases: [{ jobId: "a", leaseId: granted.lease.id }],
|
|
319
|
+
retryAfter: Date.now() + 6e5
|
|
320
|
+
});
|
|
321
|
+
expect(await store.claim(claimArgs())).toEqual([]);
|
|
322
|
+
expect((await store.job(SITE, "a"))?.state).toBe("queued");
|
|
323
|
+
expect(
|
|
324
|
+
(await store.claim(claimArgs({ runnerId: "runner_2" }))).map(
|
|
325
|
+
(j) => j.id
|
|
326
|
+
)
|
|
327
|
+
).toEqual(["a"]);
|
|
328
|
+
await done();
|
|
329
|
+
});
|
|
330
|
+
it("offers it again once the not-before has passed", async () => {
|
|
331
|
+
const { store, done } = await make();
|
|
332
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
333
|
+
const [granted] = await store.claim(claimArgs());
|
|
334
|
+
await store.releaseLeases({
|
|
335
|
+
runnerId: "runner_1",
|
|
336
|
+
leases: [{ jobId: "a", leaseId: granted.lease.id }],
|
|
337
|
+
retryAfter: Date.now() - 1e3
|
|
338
|
+
});
|
|
339
|
+
expect((await store.claim(claimArgs())).map((j) => j.id)).toEqual(["a"]);
|
|
340
|
+
await done();
|
|
341
|
+
});
|
|
342
|
+
it("will not un-finish a job that is already done", async () => {
|
|
343
|
+
const { store, done } = await make();
|
|
344
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
345
|
+
const [granted] = await store.claim(claimArgs());
|
|
346
|
+
const lease = granted.lease.id;
|
|
347
|
+
await store.takePayload({
|
|
348
|
+
jobId: "a",
|
|
349
|
+
runnerId: "runner_1",
|
|
350
|
+
leaseId: lease
|
|
351
|
+
});
|
|
352
|
+
await store.complete({
|
|
353
|
+
jobId: "a",
|
|
354
|
+
runnerId: "runner_1",
|
|
355
|
+
leaseId: lease,
|
|
356
|
+
envelope: ENVELOPE,
|
|
357
|
+
disposition: "ok"
|
|
358
|
+
});
|
|
359
|
+
const released = await store.releaseLeases({
|
|
360
|
+
runnerId: "runner_1",
|
|
361
|
+
leases: [{ jobId: "a", leaseId: lease }],
|
|
362
|
+
reason: "shutdown"
|
|
363
|
+
});
|
|
364
|
+
expect(released).toEqual([]);
|
|
365
|
+
expect((await store.job(SITE, "a"))?.state).toBe("done");
|
|
366
|
+
expect((await store.finished(SITE)).map((j) => j.id)).toEqual(["a"]);
|
|
367
|
+
expect(await store.claim(claimArgs({ runnerId: "runner_2" }))).toEqual(
|
|
368
|
+
[]
|
|
369
|
+
);
|
|
370
|
+
await done();
|
|
371
|
+
});
|
|
372
|
+
it("keeps a job claimable by a runner that only went away", async () => {
|
|
373
|
+
const { store, done } = await make();
|
|
374
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
375
|
+
const [granted] = await store.claim(claimArgs());
|
|
376
|
+
await store.releaseLeases({
|
|
377
|
+
runnerId: "runner_1",
|
|
378
|
+
leases: [{ jobId: "a", leaseId: granted.lease.id }],
|
|
379
|
+
reason: "shutdown"
|
|
380
|
+
});
|
|
381
|
+
expect((await store.claim(claimArgs())).map((j) => j.id)).toEqual(["a"]);
|
|
382
|
+
await done();
|
|
383
|
+
});
|
|
384
|
+
it("stops offering a job past its deadline, and drops the ciphertext", async () => {
|
|
385
|
+
const { store, done } = await make();
|
|
386
|
+
await store.enqueue({
|
|
387
|
+
id: "late",
|
|
388
|
+
siteId: SITE,
|
|
389
|
+
stub: { ...stub("late"), deadlineAt: Date.now() - 1e3 }
|
|
390
|
+
});
|
|
391
|
+
expect(await store.claim(claimArgs())).toEqual([]);
|
|
392
|
+
expect(await store.job(SITE, "late")).toBeUndefined();
|
|
393
|
+
await done();
|
|
394
|
+
});
|
|
395
|
+
it("reports what a sweep removed, not only what it requeued \u2014 B096", async () => {
|
|
396
|
+
const { store, done } = await make();
|
|
397
|
+
await store.enqueue({
|
|
398
|
+
id: "expired",
|
|
399
|
+
siteId: SITE,
|
|
400
|
+
stub: { ...stub("expired"), deadlineAt: Date.now() - 1e3 }
|
|
401
|
+
});
|
|
402
|
+
await store.enqueue({ id: "alive", siteId: SITE, stub: stub("alive") });
|
|
403
|
+
const swept = await store.sweep();
|
|
404
|
+
expect(
|
|
405
|
+
swept.map((job) => job.id),
|
|
406
|
+
"a sweep removed a job and reported nothing about it"
|
|
407
|
+
).toContain("expired");
|
|
408
|
+
expect(swept.map((job) => job.id)).not.toContain("alive");
|
|
409
|
+
expect(await store.job(SITE, "alive")).toBeDefined();
|
|
410
|
+
await done();
|
|
411
|
+
});
|
|
412
|
+
it("names a cancelled job to the device holding it, and offers it to nobody", async () => {
|
|
413
|
+
const { store, done } = await make();
|
|
414
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
415
|
+
await store.claim(claimArgs());
|
|
416
|
+
expect(await store.cancel({ jobId: "a", siteId: SITE })).toBe(true);
|
|
417
|
+
const held = (await store.job(SITE, "a"))?.claimedBy?.leaseId;
|
|
418
|
+
expect(await store.cancelRequests("runner_1")).toEqual([
|
|
419
|
+
{ jobId: "a", leaseId: held }
|
|
420
|
+
]);
|
|
421
|
+
expect(await store.cancel({ jobId: "a", siteId: "someone_else" })).toBe(
|
|
422
|
+
false
|
|
423
|
+
);
|
|
424
|
+
await done();
|
|
425
|
+
});
|
|
426
|
+
it("records a result once", async () => {
|
|
427
|
+
const { store, done } = await make();
|
|
428
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
429
|
+
const [granted] = await store.claim(claimArgs());
|
|
430
|
+
const first = await store.complete({
|
|
431
|
+
jobId: "a",
|
|
432
|
+
runnerId: "runner_1",
|
|
433
|
+
leaseId: granted.lease.id,
|
|
434
|
+
envelope: ENVELOPE,
|
|
435
|
+
disposition: "ok"
|
|
436
|
+
});
|
|
437
|
+
const replay = await store.complete({
|
|
438
|
+
jobId: "a",
|
|
439
|
+
runnerId: "runner_1",
|
|
440
|
+
leaseId: granted.lease.id,
|
|
441
|
+
envelope: { ...ENVELOPE, ciphertext: "different" },
|
|
442
|
+
disposition: "error"
|
|
443
|
+
});
|
|
444
|
+
expect(first).toEqual({ accepted: true, state: "done" });
|
|
445
|
+
expect(replay).toEqual({
|
|
446
|
+
accepted: false,
|
|
447
|
+
duplicate: true,
|
|
448
|
+
state: "done"
|
|
449
|
+
});
|
|
450
|
+
expect((await store.job(SITE, "a"))?.disposition).toBe("ok");
|
|
451
|
+
await done();
|
|
452
|
+
});
|
|
453
|
+
it("answers the device that finished it with `duplicate`", async () => {
|
|
454
|
+
const { store, done } = await make();
|
|
455
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
456
|
+
const [granted] = await store.claim(claimArgs());
|
|
457
|
+
const first = await store.complete({
|
|
458
|
+
jobId: "a",
|
|
459
|
+
runnerId: "runner_1",
|
|
460
|
+
leaseId: granted.lease.id,
|
|
461
|
+
envelope: ENVELOPE,
|
|
462
|
+
disposition: "ok"
|
|
463
|
+
});
|
|
464
|
+
expect(first).toEqual({ accepted: true, state: "done" });
|
|
465
|
+
const replay = await store.complete({
|
|
466
|
+
jobId: "a",
|
|
467
|
+
runnerId: "runner_1",
|
|
468
|
+
leaseId: granted.lease.id,
|
|
469
|
+
envelope: { ...ENVELOPE, ciphertext: "a-different-result" },
|
|
470
|
+
disposition: "error"
|
|
471
|
+
});
|
|
472
|
+
expect(replay).toEqual({
|
|
473
|
+
accepted: false,
|
|
474
|
+
duplicate: true,
|
|
475
|
+
state: "done"
|
|
476
|
+
});
|
|
477
|
+
const stranger = await store.complete({
|
|
478
|
+
jobId: "a",
|
|
479
|
+
runnerId: "runner_2",
|
|
480
|
+
leaseId: granted.lease.id,
|
|
481
|
+
envelope: ENVELOPE,
|
|
482
|
+
disposition: "ok"
|
|
483
|
+
});
|
|
484
|
+
expect(stranger).toEqual({ refused: "not-holder" });
|
|
485
|
+
expect((await store.job(SITE, "a"))?.disposition).toBe("ok");
|
|
486
|
+
await done();
|
|
487
|
+
});
|
|
488
|
+
it("refuses a result produced under a grant that ended", async () => {
|
|
489
|
+
const { store, done } = await make();
|
|
490
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
491
|
+
await store.claim(claimArgs());
|
|
492
|
+
const late = await store.complete({
|
|
493
|
+
jobId: "a",
|
|
494
|
+
runnerId: "runner_1",
|
|
495
|
+
leaseId: "a-grant-that-ended",
|
|
496
|
+
envelope: ENVELOPE,
|
|
497
|
+
disposition: "ok"
|
|
498
|
+
});
|
|
499
|
+
expect(late).toEqual({ refused: "stale-lease" });
|
|
500
|
+
expect((await store.job(SITE, "a"))?.state).toBe("awaiting-payload");
|
|
501
|
+
expect((await store.job(SITE, "a"))?.result).toBeUndefined();
|
|
502
|
+
await done();
|
|
503
|
+
});
|
|
504
|
+
it("refuses a payload for a job that already finished [V1-6]", async () => {
|
|
505
|
+
const { store, done } = await make();
|
|
506
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
507
|
+
const [granted] = await store.claim(claimArgs());
|
|
508
|
+
await store.seal({ jobId: "a", siteId: SITE, envelope: ENVELOPE });
|
|
509
|
+
await store.takePayload({
|
|
510
|
+
jobId: "a",
|
|
511
|
+
runnerId: "runner_1",
|
|
512
|
+
leaseId: granted.lease.id
|
|
513
|
+
});
|
|
514
|
+
await store.complete({
|
|
515
|
+
jobId: "a",
|
|
516
|
+
runnerId: "runner_1",
|
|
517
|
+
leaseId: granted.lease.id,
|
|
518
|
+
envelope: ENVELOPE,
|
|
519
|
+
disposition: "ok"
|
|
520
|
+
});
|
|
521
|
+
const replay = await store.takePayload({
|
|
522
|
+
jobId: "a",
|
|
523
|
+
runnerId: "runner_1",
|
|
524
|
+
leaseId: granted.lease.id
|
|
525
|
+
});
|
|
526
|
+
expect(replay).toEqual({ refused: "terminal" });
|
|
527
|
+
expect((await store.job(SITE, "a"))?.state).toBe("done");
|
|
528
|
+
await done();
|
|
529
|
+
});
|
|
530
|
+
it("tells a runner nothing about another site's job id [V1-8]", async () => {
|
|
531
|
+
const { store, done } = await make();
|
|
532
|
+
await store.enqueue({
|
|
533
|
+
id: "secret",
|
|
534
|
+
siteId: "site_somebody_else",
|
|
535
|
+
stub: stub("secret")
|
|
536
|
+
});
|
|
537
|
+
await store.enqueue({ id: "mine", siteId: SITE, stub: stub("mine") });
|
|
538
|
+
const granted = await store.claim(claimArgs());
|
|
539
|
+
const mine = granted.find((job) => job.id === "mine");
|
|
540
|
+
const other = await store.takePayload({
|
|
541
|
+
jobId: "secret",
|
|
542
|
+
runnerId: "runner_1",
|
|
543
|
+
leaseId: mine.lease.id
|
|
544
|
+
});
|
|
545
|
+
const absent = await store.takePayload({
|
|
546
|
+
jobId: "no-such-job-anywhere",
|
|
547
|
+
runnerId: "runner_1",
|
|
548
|
+
leaseId: mine.lease.id
|
|
549
|
+
});
|
|
550
|
+
expect(other).toEqual(absent);
|
|
551
|
+
expect(other).toEqual({ refused: "not-found" });
|
|
552
|
+
await done();
|
|
553
|
+
});
|
|
554
|
+
it("offers a job to any device that serves its kind", async () => {
|
|
555
|
+
const { store, done } = await make();
|
|
556
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
557
|
+
const claimed = await store.claim(
|
|
558
|
+
claimArgs({ runnerId: "runner_any", kinds: /* @__PURE__ */ new Set(["llm.generate"]) })
|
|
559
|
+
);
|
|
560
|
+
expect(claimed.map((j) => j.id)).toEqual(["a"]);
|
|
561
|
+
await done();
|
|
562
|
+
});
|
|
563
|
+
it("does not offer a job to a device that serves a different kind", async () => {
|
|
564
|
+
const { store, done } = await make();
|
|
565
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
566
|
+
const claimed = await store.claim(
|
|
567
|
+
claimArgs({ runnerId: "runner_chat", kinds: /* @__PURE__ */ new Set(["llm.chat"]) })
|
|
568
|
+
);
|
|
569
|
+
expect(claimed).toEqual([]);
|
|
570
|
+
await done();
|
|
571
|
+
});
|
|
572
|
+
it("remembers a device, and what it says it can run", async () => {
|
|
573
|
+
const { store, done } = await make();
|
|
574
|
+
const device = publicIdentityOf(generateKeys(3e12));
|
|
575
|
+
await store.seen({
|
|
576
|
+
runnerId: "runner_1",
|
|
577
|
+
owner: "alice",
|
|
578
|
+
device,
|
|
579
|
+
capabilities: [capability("llama3")],
|
|
580
|
+
withheld: []
|
|
581
|
+
});
|
|
582
|
+
const known = await store.presence("runner_1");
|
|
583
|
+
expect(known?.owner).toBe("alice");
|
|
584
|
+
expect(known?.device.identity).toBe(device.identity);
|
|
585
|
+
expect(known?.capabilities.map((row) => row.model)).toEqual(["llama3"]);
|
|
586
|
+
await done();
|
|
587
|
+
});
|
|
588
|
+
it("replaces the matrix rather than merging it", async () => {
|
|
589
|
+
const { store, done } = await make();
|
|
590
|
+
const device = publicIdentityOf(generateKeys(3000000000001));
|
|
591
|
+
const seen = { runnerId: "runner_2", owner: "alice", device };
|
|
592
|
+
await store.seen({
|
|
593
|
+
...seen,
|
|
594
|
+
capabilities: [capability("llama3")],
|
|
595
|
+
withheld: []
|
|
596
|
+
});
|
|
597
|
+
await store.seen({
|
|
598
|
+
...seen,
|
|
599
|
+
capabilities: [capability("mistral")],
|
|
600
|
+
withheld: []
|
|
601
|
+
});
|
|
602
|
+
const known = await store.presence("runner_2");
|
|
603
|
+
expect(known?.capabilities.map((row) => row.model)).toEqual(["mistral"]);
|
|
604
|
+
await done();
|
|
605
|
+
});
|
|
606
|
+
it("keeps an empty matrix, because empty is an answer", async () => {
|
|
607
|
+
const { store, done } = await make();
|
|
608
|
+
const device = publicIdentityOf(generateKeys(3000000000002));
|
|
609
|
+
const seen = { runnerId: "runner_3", owner: "alice", device };
|
|
610
|
+
await store.seen({
|
|
611
|
+
...seen,
|
|
612
|
+
capabilities: [capability("llama3")],
|
|
613
|
+
withheld: []
|
|
614
|
+
});
|
|
615
|
+
await store.seen({ ...seen, capabilities: [], withheld: [] });
|
|
616
|
+
expect((await store.presence("runner_3"))?.capabilities).toEqual([]);
|
|
617
|
+
await done();
|
|
618
|
+
});
|
|
619
|
+
it("remembers which kinds are withheld, and drops them when resolved", async () => {
|
|
620
|
+
const { store, done } = await make();
|
|
621
|
+
const device = publicIdentityOf(generateKeys(3000000000004));
|
|
622
|
+
const seen = { runnerId: "runner_withheld", owner: "alice", device };
|
|
623
|
+
await store.seen({
|
|
624
|
+
...seen,
|
|
625
|
+
capabilities: [],
|
|
626
|
+
withheld: [
|
|
627
|
+
{
|
|
628
|
+
kind: "llm.generate",
|
|
629
|
+
claimants: [
|
|
630
|
+
{ service: "ollama", offer: "team" },
|
|
631
|
+
{ service: "mlx", offer: "private" }
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
]
|
|
635
|
+
});
|
|
636
|
+
const held = await store.presence("runner_withheld");
|
|
637
|
+
expect(held?.withheld.map((row) => row.kind)).toEqual(["llm.generate"]);
|
|
638
|
+
expect(held?.withheld[0]?.claimants.map((c) => c.service)).toEqual([
|
|
639
|
+
"ollama",
|
|
640
|
+
"mlx"
|
|
641
|
+
]);
|
|
642
|
+
await store.seen({
|
|
643
|
+
...seen,
|
|
644
|
+
capabilities: [capability("llama3")],
|
|
645
|
+
withheld: []
|
|
646
|
+
});
|
|
647
|
+
expect((await store.presence("runner_withheld"))?.withheld).toEqual([]);
|
|
648
|
+
await done();
|
|
649
|
+
});
|
|
650
|
+
it("stamps presence from its own clock, and moves it on the next beat", async () => {
|
|
651
|
+
const { store, done } = await make();
|
|
652
|
+
const device = publicIdentityOf(generateKeys(3000000000003));
|
|
653
|
+
const seen = { runnerId: "runner_4", owner: "alice", device };
|
|
654
|
+
const before = await store.now();
|
|
655
|
+
const first = await store.seen({
|
|
656
|
+
...seen,
|
|
657
|
+
capabilities: [],
|
|
658
|
+
withheld: []
|
|
659
|
+
});
|
|
660
|
+
expect(first.lastSeenAt).toBeGreaterThanOrEqual(before);
|
|
661
|
+
const later = await store.seen({
|
|
662
|
+
...seen,
|
|
663
|
+
capabilities: [],
|
|
664
|
+
withheld: []
|
|
665
|
+
});
|
|
666
|
+
expect(later.lastSeenAt).toBeGreaterThanOrEqual(first.lastSeenAt);
|
|
667
|
+
await done();
|
|
668
|
+
});
|
|
669
|
+
it("lists everyone it has seen", async () => {
|
|
670
|
+
const { store, done } = await make();
|
|
671
|
+
await store.seen({
|
|
672
|
+
runnerId: "runner_5",
|
|
673
|
+
owner: "alice",
|
|
674
|
+
device: publicIdentityOf(generateKeys(3000000000004)),
|
|
675
|
+
capabilities: [],
|
|
676
|
+
withheld: []
|
|
677
|
+
});
|
|
678
|
+
await store.seen({
|
|
679
|
+
runnerId: "runner_6",
|
|
680
|
+
owner: "bob",
|
|
681
|
+
device: publicIdentityOf(generateKeys(3000000000005)),
|
|
682
|
+
capabilities: [],
|
|
683
|
+
withheld: []
|
|
684
|
+
});
|
|
685
|
+
const everyone = await store.everyone();
|
|
686
|
+
const ids = everyone.map((row) => row.runnerId);
|
|
687
|
+
expect(ids).toContain("runner_5");
|
|
688
|
+
expect(ids).toContain("runner_6");
|
|
689
|
+
await done();
|
|
690
|
+
});
|
|
691
|
+
it("does not know a runner it has never seen", async () => {
|
|
692
|
+
const { store, done } = await make();
|
|
693
|
+
expect(await store.presence("runner_never")).toBeUndefined();
|
|
694
|
+
await done();
|
|
695
|
+
});
|
|
696
|
+
it("stamps deadlines from its own clock", async () => {
|
|
697
|
+
const { store, done } = await make();
|
|
698
|
+
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
699
|
+
const before = await store.now();
|
|
700
|
+
const [granted] = await store.claim(claimArgs({ leaseMs: 45e3 }));
|
|
701
|
+
const after = await store.now();
|
|
702
|
+
expect(granted.lease.expiresAt).toBeGreaterThanOrEqual(before + 45e3);
|
|
703
|
+
expect(granted.lease.expiresAt).toBeLessThanOrEqual(after + 45e3);
|
|
704
|
+
await done();
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
export {
|
|
709
|
+
CONTRACT_SITE,
|
|
710
|
+
describeStoreContract
|
|
711
|
+
};
|
|
712
|
+
//# sourceMappingURL=store-contract.js.map
|