@byollm/relay 0.1.0-alpha.7 → 0.1.0-alpha.70
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 +166 -4
- package/dist/chunk-OB6LPEEE.js +405 -0
- package/dist/chunk-OB6LPEEE.js.map +1 -0
- package/dist/index.d.ts +496 -153
- package/dist/index.js +1041 -271
- package/dist/index.js.map +1 -1
- package/dist/store-DPCLO12l.d.ts +655 -0
- package/dist/store-contract.d.ts +67 -0
- package/dist/store-contract.js +625 -0
- package/dist/store-contract.js.map +1 -0
- package/package.json +19 -4
|
@@ -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
|