@byollm/relay 0.1.0-alpha.24 → 0.1.0-alpha.26
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 +4 -4
- package/dist/chunk-ZH2GB6FY.js +364 -0
- package/dist/chunk-ZH2GB6FY.js.map +1 -0
- package/dist/index.d.ts +70 -19
- package/dist/index.js +119 -350
- package/dist/index.js.map +1 -1
- package/dist/{store-ClYl7FqD.d.ts → store-DgzTq29P.d.ts} +46 -34
- package/dist/store-contract.d.ts +1 -1
- package/dist/store-contract.js +45 -32
- package/dist/store-contract.js.map +1 -1
- package/package.json +5 -5
|
@@ -143,15 +143,34 @@ interface ClaimInput {
|
|
|
143
143
|
readonly runnerId: string;
|
|
144
144
|
readonly owner: string;
|
|
145
145
|
readonly device: PublicIdentity;
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
/**
|
|
147
|
+
* The (site, owner) pairs this device may run work for — cloud_009 §3.
|
|
148
|
+
*
|
|
149
|
+
* **One set of pairs, not a set of sites and a set of owners.** Consent
|
|
150
|
+
* binds a user to a site, so the two cannot travel separately: a device
|
|
151
|
+
* whose owner consented to site A, serving a roster member who consented
|
|
152
|
+
* to site B, would have every element of both sets and no consented route
|
|
153
|
+
* between them. Two sets multiply; consent does not.
|
|
154
|
+
*
|
|
155
|
+
* Built by {@link Projection.routesFor} and matched with {@link routeKey},
|
|
156
|
+
* so the relay and a store in another repository agree on the encoding by
|
|
157
|
+
* calling the same function rather than by both spelling it out.
|
|
158
|
+
*
|
|
159
|
+
* This is the collapse that lets a claim stay one operation: a predicate
|
|
160
|
+
* cannot travel to a store over a network, and a set can.
|
|
161
|
+
*/
|
|
162
|
+
readonly routes: ReadonlySet<string>;
|
|
149
163
|
readonly kinds: ReadonlySet<string>;
|
|
150
|
-
/** Whose work it may run — the projection, already collapsed to data. */
|
|
151
|
-
readonly owners: ReadonlySet<string>;
|
|
152
164
|
readonly max: number;
|
|
153
165
|
readonly leaseMs: number;
|
|
154
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* How a (site, owner) route is written, so two implementations agree.
|
|
169
|
+
*
|
|
170
|
+
* The same `\u0000` the job key uses, for the same reason: it cannot appear
|
|
171
|
+
* in a site id or an owner id, so this is a key rather than a parser.
|
|
172
|
+
*/
|
|
173
|
+
declare const routeKey: (siteId: string, owner: string) => string;
|
|
155
174
|
/**
|
|
156
175
|
* Where the store's sense of time comes from — cloud_006 §3.4.
|
|
157
176
|
*
|
|
@@ -181,15 +200,6 @@ interface RelayStateOptions {
|
|
|
181
200
|
}
|
|
182
201
|
/** Why a lease-scoped operation was refused, in the caller's vocabulary. */
|
|
183
202
|
type HolderRefusal = "not-found" | "not-holder" | "stale-lease" | "not-ready";
|
|
184
|
-
/**
|
|
185
|
-
* In-memory routing state.
|
|
186
|
-
*
|
|
187
|
-
* Deliberately not durable. The skeleton proves the protocol, and the
|
|
188
|
-
* production hub replaces this with the closed multi-tenant router behind the
|
|
189
|
-
* same shape (cloud_004 §9). Anything a restart loses here is a job that
|
|
190
|
-
* returns to its site's queue — which is the behaviour a lapsed lease already
|
|
191
|
-
* has to produce, so nothing new needs to be true for this to be safe.
|
|
192
|
-
*/
|
|
193
203
|
declare class RelayState implements RoutingStore {
|
|
194
204
|
#private;
|
|
195
205
|
constructor(options?: RelayStateOptions);
|
|
@@ -217,10 +227,8 @@ declare class RelayState implements RoutingStore {
|
|
|
217
227
|
id: string;
|
|
218
228
|
siteId: string;
|
|
219
229
|
stub: JobStub;
|
|
220
|
-
}): Promise<RoutedJob
|
|
221
|
-
|
|
222
|
-
}>;
|
|
223
|
-
job(jobId: string): Promise<RoutedJob | undefined>;
|
|
230
|
+
}): Promise<RoutedJob>;
|
|
231
|
+
job(siteId: string, jobId: string): Promise<RoutedJob | undefined>;
|
|
224
232
|
jobs(): Promise<RoutedJob[]>;
|
|
225
233
|
/** Jobs a site must seal for, right now. */
|
|
226
234
|
awaiting(siteId: string): Promise<RoutedJob[]>;
|
|
@@ -379,8 +387,6 @@ declare class RelayState implements RoutingStore {
|
|
|
379
387
|
* express an opinion about who may route, only about what it was told. That is
|
|
380
388
|
* what keeps `RELAY_BLIND` a property of the shape rather than of the code.
|
|
381
389
|
*/
|
|
382
|
-
/** Why a stub was not taken. One value, and it names the whole reason. */
|
|
383
|
-
type EnqueueRefusal = "id-taken";
|
|
384
390
|
interface RoutingStore {
|
|
385
391
|
/** The one clock every deadline in this store is stamped from. */
|
|
386
392
|
now(): Promise<number>;
|
|
@@ -392,25 +398,31 @@ interface RoutingStore {
|
|
|
392
398
|
* not disturb work in flight (byollm_009 §4.2's replay argument rests on
|
|
393
399
|
* it).
|
|
394
400
|
*
|
|
395
|
-
* A republished id belonging to a **different** site is
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
* stub, and B's `seal` and `cancel` would then be refused for a job it
|
|
399
|
-
* believes it published. That reads as a broken relay and is a tenancy leak.
|
|
401
|
+
* A republished id belonging to a **different** site is a different job —
|
|
402
|
+
* cloud_009 §3. Keys are `(site, id)`, so two sites choosing one id collide
|
|
403
|
+
* nowhere and there is nothing to refuse.
|
|
400
404
|
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
+
* cloud_008 finding 58 closed this by refusing the collision, which was as
|
|
406
|
+
* far as a single-site relay could go and left a cross-tenant existence
|
|
407
|
+
* oracle: a site knows its own stub is well-formed, so any answer it can
|
|
408
|
+
* tell apart from success confirms that somebody else holds that id. The
|
|
409
|
+
* refusal is gone with the collision.
|
|
405
410
|
*/
|
|
406
411
|
enqueue(input: {
|
|
407
412
|
id: string;
|
|
408
413
|
siteId: string;
|
|
409
414
|
stub: JobStub;
|
|
410
|
-
}): Promise<RoutedJob
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
415
|
+
}): Promise<RoutedJob>;
|
|
416
|
+
/**
|
|
417
|
+
* One job, named by the site that published it — cloud_009 §3.
|
|
418
|
+
*
|
|
419
|
+
* The site id is a parameter rather than a scan, and the difference is
|
|
420
|
+
* finding eleven wearing a different hat: a reader that finds a job by id
|
|
421
|
+
* alone is a reader that can see every tenant's state through one door,
|
|
422
|
+
* which is exactly the anonymous read the debug page had and lost. The
|
|
423
|
+
* debug page is per-site or it is nothing.
|
|
424
|
+
*/
|
|
425
|
+
job(siteId: string, jobId: string): Promise<RoutedJob | undefined>;
|
|
414
426
|
jobs(): Promise<RoutedJob[]>;
|
|
415
427
|
/** Jobs a site must seal for, right now. */
|
|
416
428
|
awaiting(siteId: string): Promise<RoutedJob[]>;
|
|
@@ -533,4 +545,4 @@ interface RoutingStore {
|
|
|
533
545
|
sweep(): Promise<RoutedJob[]>;
|
|
534
546
|
}
|
|
535
547
|
|
|
536
|
-
export { AWAITING_PAYLOAD_MS as A, type ClaimInput as C, type
|
|
548
|
+
export { AWAITING_PAYLOAD_MS as A, type ClaimInput as C, type HolderRefusal as H, type Presence as P, type RoutingStore as R, RelayState as a, type ReleaseReason as b, type RoutedJob as c, type RoutedState as d, routeKey as r };
|
package/dist/store-contract.d.ts
CHANGED
package/dist/store-contract.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
routeKey
|
|
3
|
+
} from "./chunk-ZH2GB6FY.js";
|
|
4
|
+
|
|
1
5
|
// src/store-contract.ts
|
|
2
6
|
import { generateKeys, publicIdentityOf } from "@byollm/protocol";
|
|
3
7
|
import { describe, expect, it } from "vitest";
|
|
@@ -21,17 +25,21 @@ var ENVELOPE = {
|
|
|
21
25
|
direction: "payload",
|
|
22
26
|
deadlineAt: 41024448e5
|
|
23
27
|
};
|
|
24
|
-
var claimArgs = (over = {}) =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
var claimArgs = (over = {}) => {
|
|
29
|
+
const { owners = ["alice"], sites = [SITE], ...rest } = over;
|
|
30
|
+
return {
|
|
31
|
+
runnerId: "runner_1",
|
|
32
|
+
owner: "alice",
|
|
33
|
+
device: publicIdentityOf(generateKeys(Date.now())),
|
|
34
|
+
kinds: /* @__PURE__ */ new Set(["llm.generate"]),
|
|
35
|
+
routes: new Set(
|
|
36
|
+
sites.flatMap((site) => owners.map((owner) => routeKey(site, owner)))
|
|
37
|
+
),
|
|
38
|
+
max: 10,
|
|
39
|
+
leaseMs: 6e4,
|
|
40
|
+
...rest
|
|
41
|
+
};
|
|
42
|
+
};
|
|
35
43
|
function describeStoreContract(name, options) {
|
|
36
44
|
const { make, serialising, writeRawStub } = options;
|
|
37
45
|
describe(`the routing store \u2014 ${name}`, () => {
|
|
@@ -40,25 +48,28 @@ function describeStoreContract(name, options) {
|
|
|
40
48
|
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
41
49
|
await store.claim(claimArgs());
|
|
42
50
|
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
43
|
-
expect((await store.job("a"))?.state).toBe("awaiting-payload");
|
|
51
|
+
expect((await store.job(SITE, "a"))?.state).toBe("awaiting-payload");
|
|
44
52
|
await done();
|
|
45
53
|
});
|
|
46
|
-
it("
|
|
54
|
+
it("gives two sites the same id without either seeing the other", async () => {
|
|
47
55
|
const { store, done } = await make();
|
|
48
56
|
await store.enqueue({ id: "shared", siteId: SITE, stub: stub("shared") });
|
|
49
|
-
|
|
57
|
+
await store.enqueue({
|
|
50
58
|
id: "shared",
|
|
51
59
|
siteId: "site_other",
|
|
52
|
-
stub: stub("shared")
|
|
60
|
+
stub: stub("shared", "bob")
|
|
53
61
|
});
|
|
54
|
-
expect(
|
|
55
|
-
expect((await store.job("shared"))?.
|
|
62
|
+
expect((await store.job(SITE, "shared"))?.stub.owner).toBe("alice");
|
|
63
|
+
expect((await store.job("site_other", "shared"))?.stub.owner).toBe("bob");
|
|
64
|
+
const granted = await store.claim(claimArgs());
|
|
65
|
+
expect(granted.map((job) => job.id)).toEqual(["shared"]);
|
|
66
|
+
expect(granted[0]?.owner).toBe("alice");
|
|
56
67
|
const replay = await store.enqueue({
|
|
57
68
|
id: "shared",
|
|
58
69
|
siteId: SITE,
|
|
59
70
|
stub: stub("shared")
|
|
60
71
|
});
|
|
61
|
-
expect(replay).toEqual(await store.job("shared"));
|
|
72
|
+
expect(replay).toEqual(await store.job(SITE, "shared"));
|
|
62
73
|
await done();
|
|
63
74
|
});
|
|
64
75
|
it("grants only what the owners set allows", async () => {
|
|
@@ -108,7 +119,7 @@ function describeStoreContract(name, options) {
|
|
|
108
119
|
expect(renewed.renewed[0].expiresAt).toBeGreaterThan(
|
|
109
120
|
granted.lease.expiresAt
|
|
110
121
|
);
|
|
111
|
-
expect((await store.job("a"))?.claimedBy?.leaseExpiresAt).toBe(
|
|
122
|
+
expect((await store.job(SITE, "a"))?.claimedBy?.leaseExpiresAt).toBe(
|
|
112
123
|
renewed.renewed[0].expiresAt
|
|
113
124
|
);
|
|
114
125
|
await done();
|
|
@@ -117,7 +128,7 @@ function describeStoreContract(name, options) {
|
|
|
117
128
|
const { store, done } = await make();
|
|
118
129
|
await store.enqueue({ id: "a", siteId: SITE, stub: stub("a") });
|
|
119
130
|
const [granted] = await store.claim(claimArgs({ leaseMs: 1e3 }));
|
|
120
|
-
const before = (await store.job("a"))?.claimedBy?.leaseExpiresAt;
|
|
131
|
+
const before = (await store.job(SITE, "a"))?.claimedBy?.leaseExpiresAt;
|
|
121
132
|
const renewed = await store.renewLeases({
|
|
122
133
|
runnerId: "runner_1",
|
|
123
134
|
leases: [{ jobId: "a", leaseId: "a-grant-that-ended" }],
|
|
@@ -125,8 +136,10 @@ function describeStoreContract(name, options) {
|
|
|
125
136
|
});
|
|
126
137
|
expect(renewed.renewed).toEqual([]);
|
|
127
138
|
expect(renewed.lost).toEqual(["a"]);
|
|
128
|
-
expect((await store.job("a"))?.claimedBy?.leaseExpiresAt).toBe(
|
|
129
|
-
|
|
139
|
+
expect((await store.job(SITE, "a"))?.claimedBy?.leaseExpiresAt).toBe(
|
|
140
|
+
before
|
|
141
|
+
);
|
|
142
|
+
expect((await store.job(SITE, "a"))?.claimedBy?.leaseId).toBe(
|
|
130
143
|
granted.lease.id
|
|
131
144
|
);
|
|
132
145
|
await done();
|
|
@@ -144,7 +157,7 @@ function describeStoreContract(name, options) {
|
|
|
144
157
|
await writeRawStub(store, "old", JSON.stringify(legacy));
|
|
145
158
|
const granted = await store.claim(claimArgs({ max: 10 }));
|
|
146
159
|
expect(granted.map((job) => job.id)).toEqual(["good"]);
|
|
147
|
-
expect((await store.job("old"))?.state).toBe("queued");
|
|
160
|
+
expect((await store.job(SITE, "old"))?.state).toBe("queued");
|
|
148
161
|
await done();
|
|
149
162
|
});
|
|
150
163
|
it("honours a self job's audience", async () => {
|
|
@@ -155,10 +168,10 @@ function describeStoreContract(name, options) {
|
|
|
155
168
|
stub: { ...stub("private", "alice"), audience: "self" }
|
|
156
169
|
});
|
|
157
170
|
const granted = await store.claim(
|
|
158
|
-
claimArgs({ owner: "owner", owners:
|
|
171
|
+
claimArgs({ owner: "owner", owners: ["owner", "alice"] })
|
|
159
172
|
);
|
|
160
173
|
expect(granted).toEqual([]);
|
|
161
|
-
expect((await store.job("private"))?.state).toBe("queued");
|
|
174
|
+
expect((await store.job(SITE, "private"))?.state).toBe("queued");
|
|
162
175
|
await done();
|
|
163
176
|
});
|
|
164
177
|
it("still offers that job to its owner's own machine", async () => {
|
|
@@ -169,7 +182,7 @@ function describeStoreContract(name, options) {
|
|
|
169
182
|
stub: { ...stub("private", "alice"), audience: "self" }
|
|
170
183
|
});
|
|
171
184
|
const granted = await store.claim(
|
|
172
|
-
claimArgs({ owner: "alice", owners:
|
|
185
|
+
claimArgs({ owner: "alice", owners: ["alice"] })
|
|
173
186
|
);
|
|
174
187
|
expect(granted.map((job) => job.id)).toEqual(["private"]);
|
|
175
188
|
await done();
|
|
@@ -184,7 +197,7 @@ function describeStoreContract(name, options) {
|
|
|
184
197
|
reason: "refused"
|
|
185
198
|
});
|
|
186
199
|
expect(await store.claim(claimArgs())).toEqual([]);
|
|
187
|
-
expect((await store.job("a"))?.state).toBe("queued");
|
|
200
|
+
expect((await store.job(SITE, "a"))?.state).toBe("queued");
|
|
188
201
|
expect(
|
|
189
202
|
(await store.claim(claimArgs({ runnerId: "runner_2" }))).map(
|
|
190
203
|
(j) => j.id
|
|
@@ -212,7 +225,7 @@ function describeStoreContract(name, options) {
|
|
|
212
225
|
stub: { ...stub("late"), deadlineAt: Date.now() - 1e3 }
|
|
213
226
|
});
|
|
214
227
|
expect(await store.claim(claimArgs())).toEqual([]);
|
|
215
|
-
expect(await store.job("late")).toBeUndefined();
|
|
228
|
+
expect(await store.job(SITE, "late")).toBeUndefined();
|
|
216
229
|
await done();
|
|
217
230
|
});
|
|
218
231
|
it("names a cancelled job to the device holding it, and offers it to nobody", async () => {
|
|
@@ -250,7 +263,7 @@ function describeStoreContract(name, options) {
|
|
|
250
263
|
duplicate: true,
|
|
251
264
|
state: "done"
|
|
252
265
|
});
|
|
253
|
-
expect((await store.job("a"))?.disposition).toBe("ok");
|
|
266
|
+
expect((await store.job(SITE, "a"))?.disposition).toBe("ok");
|
|
254
267
|
await done();
|
|
255
268
|
});
|
|
256
269
|
it("answers the device that finished it with `duplicate`", async () => {
|
|
@@ -285,7 +298,7 @@ function describeStoreContract(name, options) {
|
|
|
285
298
|
disposition: "ok"
|
|
286
299
|
});
|
|
287
300
|
expect(stranger).toEqual({ refused: "not-holder" });
|
|
288
|
-
expect((await store.job("a"))?.disposition).toBe("ok");
|
|
301
|
+
expect((await store.job(SITE, "a"))?.disposition).toBe("ok");
|
|
289
302
|
await done();
|
|
290
303
|
});
|
|
291
304
|
it("refuses a result produced under a grant that ended", async () => {
|
|
@@ -300,8 +313,8 @@ function describeStoreContract(name, options) {
|
|
|
300
313
|
disposition: "ok"
|
|
301
314
|
});
|
|
302
315
|
expect(late).toEqual({ refused: "stale-lease" });
|
|
303
|
-
expect((await store.job("a"))?.state).toBe("awaiting-payload");
|
|
304
|
-
expect((await store.job("a"))?.result).toBeUndefined();
|
|
316
|
+
expect((await store.job(SITE, "a"))?.state).toBe("awaiting-payload");
|
|
317
|
+
expect((await store.job(SITE, "a"))?.result).toBeUndefined();
|
|
305
318
|
await done();
|
|
306
319
|
});
|
|
307
320
|
it("stamps deadlines from its own clock", async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store-contract.ts"],"sourcesContent":["import { generateKeys, publicIdentityOf, type JobStub } from \"@byollm/protocol\";\nimport { describe, expect, it } from \"vitest\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * The routing store's behaviour, written once and run against every\n * implementation — cloud_008 finding 54.\n *\n * There were two copies. This package tested `RelayState`; `byollm-cloud`\n * tested `ValkeyRoutingStore` with a file that opened by explaining it was\n * \"written once and parameterised… a second copy of the scenario is a second\n * place for two implementations to quietly diverge\" — and was itself the\n * second copy. It had drifted to sixteen cases against the eighteen in the\n * ledger, which is exactly the divergence the sentence predicted, happening\n * to the sentence.\n *\n * So the contract lives here, in the package that declares `RoutingStore`,\n * and both repositories import it. A store that cannot pass this is not a\n * routing store, whatever it implements.\n *\n * ## Why this ships in the published package\n *\n * Because the implementation that matters most is in another repository. A\n * contract only the author can run is a description; one a third party runs\n * against their own implementation is a contract. `@byollm/relay/store-contract`\n * is a subpath export so nothing that imports the relay itself pulls in a\n * test framework.\n *\n * ## What this cannot prove\n *\n * `CLAIM_ATOMIC` is a MUST and every case here would pass against a store\n * that read, decided and wrote in three steps — in one process there is\n * nothing to interleave. Concurrency belongs with the implementation that\n * has a network in it, and `byollm-cloud`'s suite runs it against Valkey with\n * many connections. Named here so the omission is a decision rather than an\n * oversight.\n */\n\n/** The control-plane site id these cases route under. */\nexport const CONTRACT_SITE = \"site_store\";\n\n/** A site's identity key id — byollm_009 Amendment A §A.3. */\nconst SITE_KEY_ID = \"BYOLLM-TEST-SITE-KEY-ID\";\n\nconst SITE = CONTRACT_SITE;\n\nconst stub = (id: string, owner = \"alice\"): JobStub => ({\n id,\n kind: \"llm.generate\",\n owner,\n site: SITE_KEY_ID,\n audience: \"self\",\n sizeClass: \"small\",\n streaming: false,\n deadlineAt: 4_102_444_800_000,\n});\n\nconst ENVELOPE = {\n ciphertext: \"AAAA\",\n recipientKeyId: \"r\",\n senderKeyId: \"s\",\n direction: \"payload\" as const,\n deadlineAt: 4_102_444_800_000,\n};\n\nconst claimArgs = (over: Record<string, unknown> = {}) => ({\n runnerId: \"runner_1\",\n owner: \"alice\",\n device: publicIdentityOf(generateKeys(Date.now())),\n siteId: SITE,\n kinds: new Set([\"llm.generate\"]),\n owners: new Set([\"alice\"]),\n max: 10,\n leaseMs: 60_000,\n ...over,\n});\n\nexport interface StoreContractOptions {\n /**\n * A fresh, empty store, and how to dispose of it.\n *\n * Arrow-typed rather than a method, because the caller passes these\n * around: a method signature lets `this` travel with the call, and a\n * factory read off an options object is exactly where that goes wrong.\n */\n readonly make: () => Promise<{\n store: RoutingStore;\n done: () => Promise<void>;\n }>;\n /**\n * Whether this store writes stubs as bytes and reads them back.\n *\n * An in-process store holds typed objects and cannot hold a stub it cannot\n * parse; a serialising one can, because what it wrote may have been written\n * by a previous version. The case that covers it is skipped rather than\n * hidden — a reader should know which half of this contract each store is\n * proving (cloud_008 §2.1a).\n */\n readonly serialising?: boolean;\n /** Write a stub's raw bytes, bypassing serialisation. */\n readonly writeRawStub?: (\n store: RoutingStore,\n id: string,\n raw: string,\n ) => Promise<void>;\n}\n\n/** Run the contract. Call inside a suite; it declares its own `describe`. */\nexport function describeStoreContract(\n name: string,\n options: StoreContractOptions,\n): void {\n const { make, serialising, writeRawStub } = options;\n describe(`the routing store — ${name}`, () => {\n it(\"is idempotent by job id\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n await store.claim(claimArgs());\n // A republish must not disturb work in flight — byollm_009 §4.2's replay\n // argument rests on it.\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n expect((await store.job(\"a\"))?.state).toBe(\"awaiting-payload\");\n await done();\n });\n\n it(\"refuses an id another site is already using\", async () => {\n // cloud_008 finding 58. Idempotence by bare id made a second site's\n // enqueue return the *first* site's job: site B would be handed A's stub,\n // and B's `seal` and `cancel` would then be refused for a job B believes\n // it published. Two tenants, one namespace, and the symptom is a relay\n // that looks broken to the innocent party.\n const { store, done } = await make();\n await store.enqueue({ id: \"shared\", siteId: SITE, stub: stub(\"shared\") });\n\n const collision = await store.enqueue({\n id: \"shared\",\n siteId: \"site_other\",\n stub: stub(\"shared\"),\n });\n\n expect(collision).toEqual({ refused: \"id-taken\" });\n // And the original is untouched: a refusal must not disturb the job it\n // refused on behalf of.\n expect((await store.job(\"shared\"))?.siteId).toBe(SITE);\n\n // The same site republishing is still absorbed, which is the behaviour\n // this exception is carved out of — byollm_009 §4.2's replay argument.\n const replay = await store.enqueue({\n id: \"shared\",\n siteId: SITE,\n stub: stub(\"shared\"),\n });\n expect(replay).toEqual(await store.job(\"shared\"));\n await done();\n });\n\n it(\"grants only what the owners set allows\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"mine\", siteId: SITE, stub: stub(\"mine\") });\n await store.enqueue({\n id: \"theirs\",\n siteId: SITE,\n stub: stub(\"theirs\", \"mallory\"),\n });\n const granted = await store.claim(claimArgs());\n expect(granted.map((job) => job.id)).toEqual([\"mine\"]);\n await done();\n });\n\n it(\"refuses a stale lease and honours the current one\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n await store.seal({ jobId: \"a\", siteId: SITE, envelope: ENVELOPE });\n\n expect(\n await store.takePayload({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: \"an-older-grant\",\n }),\n ).toEqual({ refused: \"stale-lease\" });\n\n expect(\n await store.takePayload({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n }),\n ).toEqual({ envelope: ENVELOPE });\n await done();\n });\n\n it(\"renews a grant it still holds, and extends the sweep with it\", async () => {\n // cloud_008 §0.6, at the contract level: renewal has to be one operation,\n // because between a read and a write another replica can sweep the lease\n // — and the renewal would then resurrect a grant on a job already back in\n // the queue. In Valkey that is a script; in memory it is a method; the\n // guarantee is the same one, so it is tested here rather than twice.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs({ leaseMs: 1_000 }));\n\n const renewed = await store.renewLeases({\n runnerId: \"runner_1\",\n leases: [{ jobId: \"a\", leaseId: granted!.lease.id }],\n leaseMs: 60_000,\n });\n\n expect(renewed.lost).toEqual([]);\n expect(renewed.renewed.map((r) => r.jobId)).toEqual([\"a\"]);\n // The store's own clock did the arithmetic — the caller passed a duration,\n // never an instant, which is what keeps two replicas from measuring one\n // lease against two clocks (cloud_006 §3.4).\n expect(renewed.renewed[0]!.expiresAt).toBeGreaterThan(\n granted!.lease.expiresAt,\n );\n\n // And it is the *stored* grant that moved, not just the number returned.\n // Reporting a renewal without performing one is the mutation that\n // survived the first version of the relay's own test for this.\n expect((await store.job(\"a\"))?.claimedBy?.leaseExpiresAt).toBe(\n renewed.renewed[0]!.expiresAt,\n );\n await done();\n });\n\n it(\"reports a lease the runner no longer holds as lost, and renews nothing\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs({ leaseMs: 1_000 }));\n const before = (await store.job(\"a\"))?.claimedBy?.leaseExpiresAt;\n\n const renewed = await store.renewLeases({\n runnerId: \"runner_1\",\n leases: [{ jobId: \"a\", leaseId: \"a-grant-that-ended\" }],\n leaseMs: 60_000,\n });\n\n expect(renewed.renewed).toEqual([]);\n expect(renewed.lost).toEqual([\"a\"]);\n // A refusal must not advance anything — the current holder's grant is\n // untouched by somebody else's stale renewal.\n expect((await store.job(\"a\"))?.claimedBy?.leaseExpiresAt).toBe(before);\n expect((await store.job(\"a\"))?.claimedBy?.leaseId).toBe(\n granted!.lease.id,\n );\n await done();\n });\n\n it(\"does not hand out a stub it cannot parse\", async () => {\n // cloud_008 §2.1a, and the case is not hypothetical: two jobs enqueued\n // before alpha.15 had no `site`, came back in a claim, and the daemon's\n // `.strict()` parse rejected the whole response. Two dead rows denied\n // claims to every device on the hub, and the queue could not drain past\n // them.\n //\n // The store had `JSON.parse(stub) as JobStub` — trusting what it wrote,\n // when what it wrote was written by a previous version. A routing store\n // outlives the deployment that filled it, so a wire change to `JobStub`\n // is a data migration whether or not anyone plans one.\n //\n // Only meaningful against a store that serialises: `RelayState` holds\n // typed objects and cannot reach this state, so it passes by\n // construction — which is worth saying rather than hiding, because a\n // reader should know which half of this contract each store is proving.\n const { store, done } = await make();\n if (!serialising || writeRawStub === undefined) {\n await done();\n return;\n }\n\n await store.enqueue({ id: \"good\", siteId: SITE, stub: stub(\"good\") });\n await store.enqueue({ id: \"old\", siteId: SITE, stub: stub(\"old\") });\n // Exactly what alpha.14 wrote: a stub with no `site`.\n const legacy = { ...stub(\"old\") } as Record<string, unknown>;\n delete legacy[\"site\"];\n await writeRawStub(store, \"old\", JSON.stringify(legacy));\n\n const granted = await store.claim(claimArgs({ max: 10 }));\n\n // The good one is still handed out — \"return nothing\" would pass a test\n // that only checked the bad one was absent, and would stop all routing.\n expect(granted.map((job) => job.id)).toEqual([\"good\"]);\n // And the bad one is back in the queue rather than stuck holding a lease\n // nobody received.\n expect((await store.job(\"old\"))?.state).toBe(\"queued\");\n await done();\n });\n\n it(\"honours a self job's audience\", async () => {\n // cloud_008 §2.1 at the contract level. The owners set is everyone this\n // device may run for, which for a Team owner's machine is the whole\n // roster — right for public and named, wrong for the one audience a user\n // picks because they want their own machine.\n const { store, done } = await make();\n await store.enqueue({\n id: \"private\",\n siteId: SITE,\n stub: { ...stub(\"private\", \"alice\"), audience: \"self\" },\n });\n\n // A roster owner's machine: may run alice's work in general.\n const granted = await store.claim(\n claimArgs({ owner: \"owner\", owners: new Set([\"owner\", \"alice\"]) }),\n );\n\n expect(granted).toEqual([]);\n expect((await store.job(\"private\"))?.state).toBe(\"queued\");\n await done();\n });\n\n it(\"still offers that job to its owner's own machine\", async () => {\n const { store, done } = await make();\n await store.enqueue({\n id: \"private\",\n siteId: SITE,\n stub: { ...stub(\"private\", \"alice\"), audience: \"self\" },\n });\n const granted = await store.claim(\n claimArgs({ owner: \"alice\", owners: new Set([\"alice\"]) }),\n );\n expect(granted.map((job) => job.id)).toEqual([\"private\"]);\n await done();\n });\n\n it(\"does not offer a job back to the runner that refused it\", async () => {\n // `REFUSAL_NOT_REOFFERED`. `releaseLeases` gained an **optional** reason,\n // which means a store can ignore it and still typecheck — this class has\n // now diverged from the interface that way twice, so the contract is the\n // only thing that catches it.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n\n await store.releaseLeases({\n runnerId: \"runner_1\",\n leases: [{ jobId: \"a\", leaseId: granted!.lease.id }],\n reason: \"refused\",\n });\n\n expect(await store.claim(claimArgs())).toEqual([]);\n expect((await store.job(\"a\"))?.state).toBe(\"queued\");\n // And somebody else may still run it.\n expect(\n (await store.claim(claimArgs({ runnerId: \"runner_2\" }))).map(\n (j) => j.id,\n ),\n ).toEqual([\"a\"]);\n await done();\n });\n\n it(\"keeps a job claimable by a runner that only went away\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n\n await store.releaseLeases({\n runnerId: \"runner_1\",\n leases: [{ jobId: \"a\", leaseId: granted!.lease.id }],\n reason: \"shutdown\",\n });\n\n expect((await store.claim(claimArgs())).map((j) => j.id)).toEqual([\"a\"]);\n await done();\n });\n\n it(\"stops offering a job past its deadline, and drops the ciphertext\", async () => {\n // cloud_008 §2.2. `deadlineAt` travelled on every stub and nothing read\n // it, so expired jobs were offered forever and their sealed payloads kept\n // for as long as the store lived.\n const { store, done } = await make();\n await store.enqueue({\n id: \"late\",\n siteId: SITE,\n stub: { ...stub(\"late\"), deadlineAt: Date.now() - 1_000 },\n });\n\n expect(await store.claim(claimArgs())).toEqual([]);\n expect(await store.job(\"late\")).toBeUndefined();\n await done();\n });\n\n it(\"names a cancelled job to the device holding it, and offers it to nobody\", async () => {\n // cloud_008 §2.2.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n await store.claim(claimArgs());\n\n expect(await store.cancel({ jobId: \"a\", siteId: SITE })).toBe(true);\n expect(await store.cancelRequests(\"runner_1\")).toEqual([\"a\"]);\n\n // Scoped to the caller's site: a site must not reach another's work by\n // guessing an id.\n expect(await store.cancel({ jobId: \"a\", siteId: \"someone_else\" })).toBe(\n false,\n );\n await done();\n });\n\n it(\"records a result once\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n\n const first = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n envelope: ENVELOPE,\n disposition: \"ok\",\n });\n const replay = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n envelope: { ...ENVELOPE, ciphertext: \"different\" },\n disposition: \"error\",\n });\n\n expect(first).toEqual({ accepted: true, state: \"done\" });\n // `duplicate` — cloud_008 §3.6. Case 18 above is the one that says why.\n expect(replay).toEqual({\n accepted: false,\n duplicate: true,\n state: \"done\",\n });\n // The property, not the boolean: the second result did not overwrite.\n expect((await store.job(\"a\"))?.disposition).toBe(\"ok\");\n await done();\n });\n\n it(\"answers the device that finished it with `duplicate`\", async () => {\n // Contract case 18 — cloud_008 §3.6, and the eighteenth exists because\n // the compiler cannot see any of this: the return type's `duplicate` is\n // optional, so a store that never sets it typechecks perfectly while\n // enforcing `RESULT_IDEMPOTENT` by accident or not at all.\n //\n // A daemon whose acknowledgment was lost hears that its answer is already\n // recorded, rather than that its lease is stale — the second message\n // invents a worry about a result safely on disk.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n\n const first = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n envelope: ENVELOPE,\n disposition: \"ok\",\n });\n expect(first).toEqual({ accepted: true, state: \"done\" });\n\n const replay = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n envelope: { ...ENVELOPE, ciphertext: \"a-different-result\" },\n disposition: \"error\",\n });\n expect(replay).toEqual({\n accepted: false,\n duplicate: true,\n state: \"done\",\n });\n\n // A *different* device, on a job that is terminal, gets the refusal it\n // would get for one that is not — otherwise the two answers differ and a\n // job id becomes a terminality probe.\n const stranger = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_2\",\n leaseId: granted!.lease.id,\n envelope: ENVELOPE,\n disposition: \"ok\",\n });\n expect(stranger).toEqual({ refused: \"not-holder\" });\n\n // And the first answer is what survived, which is the property rather\n // than any of the booleans above.\n expect((await store.job(\"a\"))?.disposition).toBe(\"ok\");\n await done();\n });\n\n it(\"refuses a result produced under a grant that ended\", async () => {\n // cloud_008 §1.4a at the contract level, and it belongs here more than\n // anywhere: `RoutingStore.complete` gained `leaseId` and this class kept\n // typechecking without it, because a parameter object with fewer\n // properties is assignable to one with more. The relay would have\n // enforced LEASE_HONORED on the memory store and not on the store that\n // runs in production, with nothing red on the way.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n await store.claim(claimArgs());\n\n const late = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: \"a-grant-that-ended\",\n envelope: ENVELOPE,\n disposition: \"ok\",\n });\n\n expect(late).toEqual({ refused: \"stale-lease\" });\n // Untouched: the current holder can still finish it.\n expect((await store.job(\"a\"))?.state).toBe(\"awaiting-payload\");\n expect((await store.job(\"a\"))?.result).toBeUndefined();\n await done();\n });\n\n it(\"stamps deadlines from its own clock\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const before = await store.now();\n const [granted] = await store.claim(claimArgs({ leaseMs: 45_000 }));\n const after = await store.now();\n\n // Bracketed rather than exact: a real store's clock moves between the two\n // reads, which is the whole reason it is the store's clock and not ours.\n expect(granted!.lease.expiresAt).toBeGreaterThanOrEqual(before + 45_000);\n expect(granted!.lease.expiresAt).toBeLessThanOrEqual(after + 45_000);\n await done();\n });\n });\n}\n"],"mappings":";AAAA,SAAS,cAAc,wBAAsC;AAC7D,SAAS,UAAU,QAAQ,UAAU;AAsC9B,IAAM,gBAAgB;AAG7B,IAAM,cAAc;AAEpB,IAAM,OAAO;AAEb,IAAM,OAAO,CAAC,IAAY,QAAQ,aAAsB;AAAA,EACtD;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AACd;AAEA,IAAM,WAAW;AAAA,EACf,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,WAAW;AAAA,EACX,YAAY;AACd;AAEA,IAAM,YAAY,CAAC,OAAgC,CAAC,OAAO;AAAA,EACzD,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ,iBAAiB,aAAa,KAAK,IAAI,CAAC,CAAC;AAAA,EACjD,QAAQ;AAAA,EACR,OAAO,oBAAI,IAAI,CAAC,cAAc,CAAC;AAAA,EAC/B,QAAQ,oBAAI,IAAI,CAAC,OAAO,CAAC;AAAA,EACzB,KAAK;AAAA,EACL,SAAS;AAAA,EACT,GAAG;AACL;AAiCO,SAAS,sBACd,MACA,SACM;AACN,QAAM,EAAE,MAAM,aAAa,aAAa,IAAI;AAC5C,WAAS,4BAAuB,IAAI,IAAI,MAAM;AAC5C,OAAG,2BAA2B,YAAY;AACxC,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,MAAM,MAAM,UAAU,CAAC;AAG7B,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,cAAQ,MAAM,MAAM,IAAI,GAAG,IAAI,KAAK,EAAE,KAAK,kBAAkB;AAC7D,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,+CAA+C,YAAY;AAM5D,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,UAAU,QAAQ,MAAM,MAAM,KAAK,QAAQ,EAAE,CAAC;AAExE,YAAM,YAAY,MAAM,MAAM,QAAQ;AAAA,QACpC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,MAAM,KAAK,QAAQ;AAAA,MACrB,CAAC;AAED,aAAO,SAAS,EAAE,QAAQ,EAAE,SAAS,WAAW,CAAC;AAGjD,cAAQ,MAAM,MAAM,IAAI,QAAQ,IAAI,MAAM,EAAE,KAAK,IAAI;AAIrD,YAAM,SAAS,MAAM,MAAM,QAAQ;AAAA,QACjC,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,MAAM,KAAK,QAAQ;AAAA,MACrB,CAAC;AACD,aAAO,MAAM,EAAE,QAAQ,MAAM,MAAM,IAAI,QAAQ,CAAC;AAChD,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,0CAA0C,YAAY;AACvD,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,QAAQ,QAAQ,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC;AACpE,YAAM,MAAM,QAAQ;AAAA,QAClB,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,MAAM,KAAK,UAAU,SAAS;AAAA,MAChC,CAAC;AACD,YAAM,UAAU,MAAM,MAAM,MAAM,UAAU,CAAC;AAC7C,aAAO,QAAQ,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC;AACrD,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,qDAAqD,YAAY;AAClE,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,CAAC,OAAO,IAAI,MAAM,MAAM,MAAM,UAAU,CAAC;AAC/C,YAAM,MAAM,KAAK,EAAE,OAAO,KAAK,QAAQ,MAAM,UAAU,SAAS,CAAC;AAEjE;AAAA,QACE,MAAM,MAAM,YAAY;AAAA,UACtB,OAAO;AAAA,UACP,UAAU;AAAA,UACV,SAAS;AAAA,QACX,CAAC;AAAA,MACH,EAAE,QAAQ,EAAE,SAAS,cAAc,CAAC;AAEpC;AAAA,QACE,MAAM,MAAM,YAAY;AAAA,UACtB,OAAO;AAAA,UACP,UAAU;AAAA,UACV,SAAS,QAAS,MAAM;AAAA,QAC1B,CAAC;AAAA,MACH,EAAE,QAAQ,EAAE,UAAU,SAAS,CAAC;AAChC,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,gEAAgE,YAAY;AAM7E,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,CAAC,OAAO,IAAI,MAAM,MAAM,MAAM,UAAU,EAAE,SAAS,IAAM,CAAC,CAAC;AAEjE,YAAM,UAAU,MAAM,MAAM,YAAY;AAAA,QACtC,UAAU;AAAA,QACV,QAAQ,CAAC,EAAE,OAAO,KAAK,SAAS,QAAS,MAAM,GAAG,CAAC;AAAA,QACnD,SAAS;AAAA,MACX,CAAC;AAED,aAAO,QAAQ,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC/B,aAAO,QAAQ,QAAQ,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC;AAIzD,aAAO,QAAQ,QAAQ,CAAC,EAAG,SAAS,EAAE;AAAA,QACpC,QAAS,MAAM;AAAA,MACjB;AAKA,cAAQ,MAAM,MAAM,IAAI,GAAG,IAAI,WAAW,cAAc,EAAE;AAAA,QACxD,QAAQ,QAAQ,CAAC,EAAG;AAAA,MACtB;AACA,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,0EAA0E,YAAY;AACvF,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,CAAC,OAAO,IAAI,MAAM,MAAM,MAAM,UAAU,EAAE,SAAS,IAAM,CAAC,CAAC;AACjE,YAAM,UAAU,MAAM,MAAM,IAAI,GAAG,IAAI,WAAW;AAElD,YAAM,UAAU,MAAM,MAAM,YAAY;AAAA,QACtC,UAAU;AAAA,QACV,QAAQ,CAAC,EAAE,OAAO,KAAK,SAAS,qBAAqB,CAAC;AAAA,QACtD,SAAS;AAAA,MACX,CAAC;AAED,aAAO,QAAQ,OAAO,EAAE,QAAQ,CAAC,CAAC;AAClC,aAAO,QAAQ,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC;AAGlC,cAAQ,MAAM,MAAM,IAAI,GAAG,IAAI,WAAW,cAAc,EAAE,KAAK,MAAM;AACrE,cAAQ,MAAM,MAAM,IAAI,GAAG,IAAI,WAAW,OAAO,EAAE;AAAA,QACjD,QAAS,MAAM;AAAA,MACjB;AACA,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,4CAA4C,YAAY;AAgBzD,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,UAAI,CAAC,eAAe,iBAAiB,QAAW;AAC9C,cAAM,KAAK;AACX;AAAA,MACF;AAEA,YAAM,MAAM,QAAQ,EAAE,IAAI,QAAQ,QAAQ,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC;AACpE,YAAM,MAAM,QAAQ,EAAE,IAAI,OAAO,QAAQ,MAAM,MAAM,KAAK,KAAK,EAAE,CAAC;AAElE,YAAM,SAAS,EAAE,GAAG,KAAK,KAAK,EAAE;AAChC,aAAO,OAAO,MAAM;AACpB,YAAM,aAAa,OAAO,OAAO,KAAK,UAAU,MAAM,CAAC;AAEvD,YAAM,UAAU,MAAM,MAAM,MAAM,UAAU,EAAE,KAAK,GAAG,CAAC,CAAC;AAIxD,aAAO,QAAQ,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC;AAGrD,cAAQ,MAAM,MAAM,IAAI,KAAK,IAAI,KAAK,EAAE,KAAK,QAAQ;AACrD,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,iCAAiC,YAAY;AAK9C,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ;AAAA,QAClB,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,MAAM,EAAE,GAAG,KAAK,WAAW,OAAO,GAAG,UAAU,OAAO;AAAA,MACxD,CAAC;AAGD,YAAM,UAAU,MAAM,MAAM;AAAA,QAC1B,UAAU,EAAE,OAAO,SAAS,QAAQ,oBAAI,IAAI,CAAC,SAAS,OAAO,CAAC,EAAE,CAAC;AAAA,MACnE;AAEA,aAAO,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC1B,cAAQ,MAAM,MAAM,IAAI,SAAS,IAAI,KAAK,EAAE,KAAK,QAAQ;AACzD,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,oDAAoD,YAAY;AACjE,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ;AAAA,QAClB,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,MAAM,EAAE,GAAG,KAAK,WAAW,OAAO,GAAG,UAAU,OAAO;AAAA,MACxD,CAAC;AACD,YAAM,UAAU,MAAM,MAAM;AAAA,QAC1B,UAAU,EAAE,OAAO,SAAS,QAAQ,oBAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;AAAA,MAC1D;AACA,aAAO,QAAQ,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC;AACxD,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,2DAA2D,YAAY;AAKxE,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,CAAC,OAAO,IAAI,MAAM,MAAM,MAAM,UAAU,CAAC;AAE/C,YAAM,MAAM,cAAc;AAAA,QACxB,UAAU;AAAA,QACV,QAAQ,CAAC,EAAE,OAAO,KAAK,SAAS,QAAS,MAAM,GAAG,CAAC;AAAA,QACnD,QAAQ;AAAA,MACV,CAAC;AAED,aAAO,MAAM,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACjD,cAAQ,MAAM,MAAM,IAAI,GAAG,IAAI,KAAK,EAAE,KAAK,QAAQ;AAEnD;AAAA,SACG,MAAM,MAAM,MAAM,UAAU,EAAE,UAAU,WAAW,CAAC,CAAC,GAAG;AAAA,UACvD,CAAC,MAAM,EAAE;AAAA,QACX;AAAA,MACF,EAAE,QAAQ,CAAC,GAAG,CAAC;AACf,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,yDAAyD,YAAY;AACtE,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,CAAC,OAAO,IAAI,MAAM,MAAM,MAAM,UAAU,CAAC;AAE/C,YAAM,MAAM,cAAc;AAAA,QACxB,UAAU;AAAA,QACV,QAAQ,CAAC,EAAE,OAAO,KAAK,SAAS,QAAS,MAAM,GAAG,CAAC;AAAA,QACnD,QAAQ;AAAA,MACV,CAAC;AAED,cAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC;AACvE,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,oEAAoE,YAAY;AAIjF,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ;AAAA,QAClB,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,MAAM,EAAE,GAAG,KAAK,MAAM,GAAG,YAAY,KAAK,IAAI,IAAI,IAAM;AAAA,MAC1D,CAAC;AAED,aAAO,MAAM,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACjD,aAAO,MAAM,MAAM,IAAI,MAAM,CAAC,EAAE,cAAc;AAC9C,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,2EAA2E,YAAY;AAExF,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,MAAM,MAAM,UAAU,CAAC;AAE7B,aAAO,MAAM,MAAM,OAAO,EAAE,OAAO,KAAK,QAAQ,KAAK,CAAC,CAAC,EAAE,KAAK,IAAI;AAClE,aAAO,MAAM,MAAM,eAAe,UAAU,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC;AAI5D,aAAO,MAAM,MAAM,OAAO,EAAE,OAAO,KAAK,QAAQ,eAAe,CAAC,CAAC,EAAE;AAAA,QACjE;AAAA,MACF;AACA,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,yBAAyB,YAAY;AACtC,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,CAAC,OAAO,IAAI,MAAM,MAAM,MAAM,UAAU,CAAC;AAE/C,YAAM,QAAQ,MAAM,MAAM,SAAS;AAAA,QACjC,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS,QAAS,MAAM;AAAA,QACxB,UAAU;AAAA,QACV,aAAa;AAAA,MACf,CAAC;AACD,YAAM,SAAS,MAAM,MAAM,SAAS;AAAA,QAClC,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS,QAAS,MAAM;AAAA,QACxB,UAAU,EAAE,GAAG,UAAU,YAAY,YAAY;AAAA,QACjD,aAAa;AAAA,MACf,CAAC;AAED,aAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,MAAM,OAAO,OAAO,CAAC;AAEvD,aAAO,MAAM,EAAE,QAAQ;AAAA,QACrB,UAAU;AAAA,QACV,WAAW;AAAA,QACX,OAAO;AAAA,MACT,CAAC;AAED,cAAQ,MAAM,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,KAAK,IAAI;AACrD,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,wDAAwD,YAAY;AASrE,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,CAAC,OAAO,IAAI,MAAM,MAAM,MAAM,UAAU,CAAC;AAE/C,YAAM,QAAQ,MAAM,MAAM,SAAS;AAAA,QACjC,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS,QAAS,MAAM;AAAA,QACxB,UAAU;AAAA,QACV,aAAa;AAAA,MACf,CAAC;AACD,aAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,MAAM,OAAO,OAAO,CAAC;AAEvD,YAAM,SAAS,MAAM,MAAM,SAAS;AAAA,QAClC,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS,QAAS,MAAM;AAAA,QACxB,UAAU,EAAE,GAAG,UAAU,YAAY,qBAAqB;AAAA,QAC1D,aAAa;AAAA,MACf,CAAC;AACD,aAAO,MAAM,EAAE,QAAQ;AAAA,QACrB,UAAU;AAAA,QACV,WAAW;AAAA,QACX,OAAO;AAAA,MACT,CAAC;AAKD,YAAM,WAAW,MAAM,MAAM,SAAS;AAAA,QACpC,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS,QAAS,MAAM;AAAA,QACxB,UAAU;AAAA,QACV,aAAa;AAAA,MACf,CAAC;AACD,aAAO,QAAQ,EAAE,QAAQ,EAAE,SAAS,aAAa,CAAC;AAIlD,cAAQ,MAAM,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,KAAK,IAAI;AACrD,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,sDAAsD,YAAY;AAOnE,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,MAAM,MAAM,UAAU,CAAC;AAE7B,YAAM,OAAO,MAAM,MAAM,SAAS;AAAA,QAChC,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU;AAAA,QACV,aAAa;AAAA,MACf,CAAC;AAED,aAAO,IAAI,EAAE,QAAQ,EAAE,SAAS,cAAc,CAAC;AAE/C,cAAQ,MAAM,MAAM,IAAI,GAAG,IAAI,KAAK,EAAE,KAAK,kBAAkB;AAC7D,cAAQ,MAAM,MAAM,IAAI,GAAG,IAAI,MAAM,EAAE,cAAc;AACrD,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,uCAAuC,YAAY;AACpD,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,KAAK;AACnC,YAAM,MAAM,QAAQ,EAAE,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC;AAC9D,YAAM,SAAS,MAAM,MAAM,IAAI;AAC/B,YAAM,CAAC,OAAO,IAAI,MAAM,MAAM,MAAM,UAAU,EAAE,SAAS,KAAO,CAAC,CAAC;AAClE,YAAM,QAAQ,MAAM,MAAM,IAAI;AAI9B,aAAO,QAAS,MAAM,SAAS,EAAE,uBAAuB,SAAS,IAAM;AACvE,aAAO,QAAS,MAAM,SAAS,EAAE,oBAAoB,QAAQ,IAAM;AACnE,YAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/store-contract.ts"],"sourcesContent":["import { generateKeys, publicIdentityOf, type JobStub } from \"@byollm/protocol\";\nimport { describe, expect, it } from \"vitest\";\nimport { routeKey } from \"./state.js\";\nimport type { RoutingStore } from \"./store.js\";\n\n/**\n * The routing store's behaviour, written once and run against every\n * implementation — cloud_008 finding 54.\n *\n * There were two copies. This package tested `RelayState`; `byollm-cloud`\n * tested `ValkeyRoutingStore` with a file that opened by explaining it was\n * \"written once and parameterised… a second copy of the scenario is a second\n * place for two implementations to quietly diverge\" — and was itself the\n * second copy. It had drifted to sixteen cases against the eighteen in the\n * ledger, which is exactly the divergence the sentence predicted, happening\n * to the sentence.\n *\n * So the contract lives here, in the package that declares `RoutingStore`,\n * and both repositories import it. A store that cannot pass this is not a\n * routing store, whatever it implements.\n *\n * ## Why this ships in the published package\n *\n * Because the implementation that matters most is in another repository. A\n * contract only the author can run is a description; one a third party runs\n * against their own implementation is a contract. `@byollm/relay/store-contract`\n * is a subpath export so nothing that imports the relay itself pulls in a\n * test framework.\n *\n * ## What this cannot prove\n *\n * `CLAIM_ATOMIC` is a MUST and every case here would pass against a store\n * that read, decided and wrote in three steps — in one process there is\n * nothing to interleave. Concurrency belongs with the implementation that\n * has a network in it, and `byollm-cloud`'s suite runs it against Valkey with\n * many connections. Named here so the omission is a decision rather than an\n * oversight.\n */\n\n/** The control-plane site id these cases route under. */\nexport const CONTRACT_SITE = \"site_store\";\n\n/** A site's identity key id — byollm_009 Amendment A §A.3. */\nconst SITE_KEY_ID = \"BYOLLM-TEST-SITE-KEY-ID\";\n\nconst SITE = CONTRACT_SITE;\n\nconst stub = (id: string, owner = \"alice\"): JobStub => ({\n id,\n kind: \"llm.generate\",\n owner,\n site: SITE_KEY_ID,\n audience: \"self\",\n sizeClass: \"small\",\n streaming: false,\n deadlineAt: 4_102_444_800_000,\n});\n\nconst ENVELOPE = {\n ciphertext: \"AAAA\",\n recipientKeyId: \"r\",\n senderKeyId: \"s\",\n direction: \"payload\" as const,\n deadlineAt: 4_102_444_800_000,\n};\n\n/**\n * A claim, with its routes written out — cloud_009 §3.\n *\n * `routes` replaced `siteId` + `owners`, and the cases that pass `owners`\n * below now pass the pairs those owners are reachable through. That rewrite\n * is the contract's own statement of the rule: there is no way to express\n * \"this owner, any site\" here, because that is not a thing consent says.\n */\nconst claimArgs = (\n over: { owners?: string[]; sites?: string[] } & Record<string, unknown> = {},\n) => {\n const { owners = [\"alice\"], sites = [SITE], ...rest } = over;\n return {\n runnerId: \"runner_1\",\n owner: \"alice\",\n device: publicIdentityOf(generateKeys(Date.now())),\n kinds: new Set([\"llm.generate\"]),\n routes: new Set(\n sites.flatMap((site) => owners.map((owner) => routeKey(site, owner))),\n ),\n max: 10,\n leaseMs: 60_000,\n ...rest,\n };\n};\n\nexport interface StoreContractOptions {\n /**\n * A fresh, empty store, and how to dispose of it.\n *\n * Arrow-typed rather than a method, because the caller passes these\n * around: a method signature lets `this` travel with the call, and a\n * factory read off an options object is exactly where that goes wrong.\n */\n readonly make: () => Promise<{\n store: RoutingStore;\n done: () => Promise<void>;\n }>;\n /**\n * Whether this store writes stubs as bytes and reads them back.\n *\n * An in-process store holds typed objects and cannot hold a stub it cannot\n * parse; a serialising one can, because what it wrote may have been written\n * by a previous version. The case that covers it is skipped rather than\n * hidden — a reader should know which half of this contract each store is\n * proving (cloud_008 §2.1a).\n */\n readonly serialising?: boolean;\n /** Write a stub's raw bytes, bypassing serialisation. */\n readonly writeRawStub?: (\n store: RoutingStore,\n id: string,\n raw: string,\n ) => Promise<void>;\n}\n\n/** Run the contract. Call inside a suite; it declares its own `describe`. */\nexport function describeStoreContract(\n name: string,\n options: StoreContractOptions,\n): void {\n const { make, serialising, writeRawStub } = options;\n describe(`the routing store — ${name}`, () => {\n it(\"is idempotent by job id\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n await store.claim(claimArgs());\n // A republish must not disturb work in flight — byollm_009 §4.2's replay\n // argument rests on it.\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n expect((await store.job(SITE, \"a\"))?.state).toBe(\"awaiting-payload\");\n await done();\n });\n\n it(\"gives two sites the same id without either seeing the other\", async () => {\n // cloud_009 §3. Keyed by the bare id, the second site's enqueue returned\n // the *first* site's job (cloud_008 finding 58), and the refusal that\n // fixed it was a cross-tenant existence oracle: a site knows its own stub\n // is well-formed, so any answer it can tell apart from success confirms\n // somebody else holds that id. Keyed by (site, id), the collision does\n // not exist and there is nothing to answer.\n const { store, done } = await make();\n await store.enqueue({ id: \"shared\", siteId: SITE, stub: stub(\"shared\") });\n await store.enqueue({\n id: \"shared\",\n siteId: \"site_other\",\n stub: stub(\"shared\", \"bob\"),\n });\n\n // Two jobs, each its own site's.\n expect((await store.job(SITE, \"shared\"))?.stub.owner).toBe(\"alice\");\n expect((await store.job(\"site_other\", \"shared\"))?.stub.owner).toBe(\"bob\");\n\n // And a claim for one site does not see the other's, which is the\n // property the key exists for rather than a restatement of it.\n const granted = await store.claim(claimArgs());\n expect(granted.map((job) => job.id)).toEqual([\"shared\"]);\n expect(granted[0]?.owner).toBe(\"alice\");\n\n // The same site republishing is still absorbed — byollm_009 §4.2's\n // replay argument, which is what idempotence is for.\n const replay = await store.enqueue({\n id: \"shared\",\n siteId: SITE,\n stub: stub(\"shared\"),\n });\n expect(replay).toEqual(await store.job(SITE, \"shared\"));\n await done();\n });\n\n it(\"grants only what the owners set allows\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"mine\", siteId: SITE, stub: stub(\"mine\") });\n await store.enqueue({\n id: \"theirs\",\n siteId: SITE,\n stub: stub(\"theirs\", \"mallory\"),\n });\n const granted = await store.claim(claimArgs());\n expect(granted.map((job) => job.id)).toEqual([\"mine\"]);\n await done();\n });\n\n it(\"refuses a stale lease and honours the current one\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n await store.seal({ jobId: \"a\", siteId: SITE, envelope: ENVELOPE });\n\n expect(\n await store.takePayload({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: \"an-older-grant\",\n }),\n ).toEqual({ refused: \"stale-lease\" });\n\n expect(\n await store.takePayload({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n }),\n ).toEqual({ envelope: ENVELOPE });\n await done();\n });\n\n it(\"renews a grant it still holds, and extends the sweep with it\", async () => {\n // cloud_008 §0.6, at the contract level: renewal has to be one operation,\n // because between a read and a write another replica can sweep the lease\n // — and the renewal would then resurrect a grant on a job already back in\n // the queue. In Valkey that is a script; in memory it is a method; the\n // guarantee is the same one, so it is tested here rather than twice.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs({ leaseMs: 1_000 }));\n\n const renewed = await store.renewLeases({\n runnerId: \"runner_1\",\n leases: [{ jobId: \"a\", leaseId: granted!.lease.id }],\n leaseMs: 60_000,\n });\n\n expect(renewed.lost).toEqual([]);\n expect(renewed.renewed.map((r) => r.jobId)).toEqual([\"a\"]);\n // The store's own clock did the arithmetic — the caller passed a duration,\n // never an instant, which is what keeps two replicas from measuring one\n // lease against two clocks (cloud_006 §3.4).\n expect(renewed.renewed[0]!.expiresAt).toBeGreaterThan(\n granted!.lease.expiresAt,\n );\n\n // And it is the *stored* grant that moved, not just the number returned.\n // Reporting a renewal without performing one is the mutation that\n // survived the first version of the relay's own test for this.\n expect((await store.job(SITE, \"a\"))?.claimedBy?.leaseExpiresAt).toBe(\n renewed.renewed[0]!.expiresAt,\n );\n await done();\n });\n\n it(\"reports a lease the runner no longer holds as lost, and renews nothing\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs({ leaseMs: 1_000 }));\n const before = (await store.job(SITE, \"a\"))?.claimedBy?.leaseExpiresAt;\n\n const renewed = await store.renewLeases({\n runnerId: \"runner_1\",\n leases: [{ jobId: \"a\", leaseId: \"a-grant-that-ended\" }],\n leaseMs: 60_000,\n });\n\n expect(renewed.renewed).toEqual([]);\n expect(renewed.lost).toEqual([\"a\"]);\n // A refusal must not advance anything — the current holder's grant is\n // untouched by somebody else's stale renewal.\n expect((await store.job(SITE, \"a\"))?.claimedBy?.leaseExpiresAt).toBe(\n before,\n );\n expect((await store.job(SITE, \"a\"))?.claimedBy?.leaseId).toBe(\n granted!.lease.id,\n );\n await done();\n });\n\n it(\"does not hand out a stub it cannot parse\", async () => {\n // cloud_008 §2.1a, and the case is not hypothetical: two jobs enqueued\n // before alpha.15 had no `site`, came back in a claim, and the daemon's\n // `.strict()` parse rejected the whole response. Two dead rows denied\n // claims to every device on the hub, and the queue could not drain past\n // them.\n //\n // The store had `JSON.parse(stub) as JobStub` — trusting what it wrote,\n // when what it wrote was written by a previous version. A routing store\n // outlives the deployment that filled it, so a wire change to `JobStub`\n // is a data migration whether or not anyone plans one.\n //\n // Only meaningful against a store that serialises: `RelayState` holds\n // typed objects and cannot reach this state, so it passes by\n // construction — which is worth saying rather than hiding, because a\n // reader should know which half of this contract each store is proving.\n const { store, done } = await make();\n if (!serialising || writeRawStub === undefined) {\n await done();\n return;\n }\n\n await store.enqueue({ id: \"good\", siteId: SITE, stub: stub(\"good\") });\n await store.enqueue({ id: \"old\", siteId: SITE, stub: stub(\"old\") });\n // Exactly what alpha.14 wrote: a stub with no `site`.\n const legacy = { ...stub(\"old\") } as Record<string, unknown>;\n delete legacy[\"site\"];\n await writeRawStub(store, \"old\", JSON.stringify(legacy));\n\n const granted = await store.claim(claimArgs({ max: 10 }));\n\n // The good one is still handed out — \"return nothing\" would pass a test\n // that only checked the bad one was absent, and would stop all routing.\n expect(granted.map((job) => job.id)).toEqual([\"good\"]);\n // And the bad one is back in the queue rather than stuck holding a lease\n // nobody received.\n expect((await store.job(SITE, \"old\"))?.state).toBe(\"queued\");\n await done();\n });\n\n it(\"honours a self job's audience\", async () => {\n // cloud_008 §2.1 at the contract level. The owners set is everyone this\n // device may run for, which for a Team owner's machine is the whole\n // roster — right for public and named, wrong for the one audience a user\n // picks because they want their own machine.\n const { store, done } = await make();\n await store.enqueue({\n id: \"private\",\n siteId: SITE,\n stub: { ...stub(\"private\", \"alice\"), audience: \"self\" },\n });\n\n // A roster owner's machine: may run alice's work in general.\n const granted = await store.claim(\n claimArgs({ owner: \"owner\", owners: [\"owner\", \"alice\"] }),\n );\n\n expect(granted).toEqual([]);\n expect((await store.job(SITE, \"private\"))?.state).toBe(\"queued\");\n await done();\n });\n\n it(\"still offers that job to its owner's own machine\", async () => {\n const { store, done } = await make();\n await store.enqueue({\n id: \"private\",\n siteId: SITE,\n stub: { ...stub(\"private\", \"alice\"), audience: \"self\" },\n });\n const granted = await store.claim(\n claimArgs({ owner: \"alice\", owners: [\"alice\"] }),\n );\n expect(granted.map((job) => job.id)).toEqual([\"private\"]);\n await done();\n });\n\n it(\"does not offer a job back to the runner that refused it\", async () => {\n // `REFUSAL_NOT_REOFFERED`. `releaseLeases` gained an **optional** reason,\n // which means a store can ignore it and still typecheck — this class has\n // now diverged from the interface that way twice, so the contract is the\n // only thing that catches it.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n\n await store.releaseLeases({\n runnerId: \"runner_1\",\n leases: [{ jobId: \"a\", leaseId: granted!.lease.id }],\n reason: \"refused\",\n });\n\n expect(await store.claim(claimArgs())).toEqual([]);\n expect((await store.job(SITE, \"a\"))?.state).toBe(\"queued\");\n // And somebody else may still run it.\n expect(\n (await store.claim(claimArgs({ runnerId: \"runner_2\" }))).map(\n (j) => j.id,\n ),\n ).toEqual([\"a\"]);\n await done();\n });\n\n it(\"keeps a job claimable by a runner that only went away\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n\n await store.releaseLeases({\n runnerId: \"runner_1\",\n leases: [{ jobId: \"a\", leaseId: granted!.lease.id }],\n reason: \"shutdown\",\n });\n\n expect((await store.claim(claimArgs())).map((j) => j.id)).toEqual([\"a\"]);\n await done();\n });\n\n it(\"stops offering a job past its deadline, and drops the ciphertext\", async () => {\n // cloud_008 §2.2. `deadlineAt` travelled on every stub and nothing read\n // it, so expired jobs were offered forever and their sealed payloads kept\n // for as long as the store lived.\n const { store, done } = await make();\n await store.enqueue({\n id: \"late\",\n siteId: SITE,\n stub: { ...stub(\"late\"), deadlineAt: Date.now() - 1_000 },\n });\n\n expect(await store.claim(claimArgs())).toEqual([]);\n expect(await store.job(SITE, \"late\")).toBeUndefined();\n await done();\n });\n\n it(\"names a cancelled job to the device holding it, and offers it to nobody\", async () => {\n // cloud_008 §2.2.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n await store.claim(claimArgs());\n\n expect(await store.cancel({ jobId: \"a\", siteId: SITE })).toBe(true);\n expect(await store.cancelRequests(\"runner_1\")).toEqual([\"a\"]);\n\n // Scoped to the caller's site: a site must not reach another's work by\n // guessing an id.\n expect(await store.cancel({ jobId: \"a\", siteId: \"someone_else\" })).toBe(\n false,\n );\n await done();\n });\n\n it(\"records a result once\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n\n const first = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n envelope: ENVELOPE,\n disposition: \"ok\",\n });\n const replay = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n envelope: { ...ENVELOPE, ciphertext: \"different\" },\n disposition: \"error\",\n });\n\n expect(first).toEqual({ accepted: true, state: \"done\" });\n // `duplicate` — cloud_008 §3.6. Case 18 above is the one that says why.\n expect(replay).toEqual({\n accepted: false,\n duplicate: true,\n state: \"done\",\n });\n // The property, not the boolean: the second result did not overwrite.\n expect((await store.job(SITE, \"a\"))?.disposition).toBe(\"ok\");\n await done();\n });\n\n it(\"answers the device that finished it with `duplicate`\", async () => {\n // Contract case 18 — cloud_008 §3.6, and the eighteenth exists because\n // the compiler cannot see any of this: the return type's `duplicate` is\n // optional, so a store that never sets it typechecks perfectly while\n // enforcing `RESULT_IDEMPOTENT` by accident or not at all.\n //\n // A daemon whose acknowledgment was lost hears that its answer is already\n // recorded, rather than that its lease is stale — the second message\n // invents a worry about a result safely on disk.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const [granted] = await store.claim(claimArgs());\n\n const first = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n envelope: ENVELOPE,\n disposition: \"ok\",\n });\n expect(first).toEqual({ accepted: true, state: \"done\" });\n\n const replay = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: granted!.lease.id,\n envelope: { ...ENVELOPE, ciphertext: \"a-different-result\" },\n disposition: \"error\",\n });\n expect(replay).toEqual({\n accepted: false,\n duplicate: true,\n state: \"done\",\n });\n\n // A *different* device, on a job that is terminal, gets the refusal it\n // would get for one that is not — otherwise the two answers differ and a\n // job id becomes a terminality probe.\n const stranger = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_2\",\n leaseId: granted!.lease.id,\n envelope: ENVELOPE,\n disposition: \"ok\",\n });\n expect(stranger).toEqual({ refused: \"not-holder\" });\n\n // And the first answer is what survived, which is the property rather\n // than any of the booleans above.\n expect((await store.job(SITE, \"a\"))?.disposition).toBe(\"ok\");\n await done();\n });\n\n it(\"refuses a result produced under a grant that ended\", async () => {\n // cloud_008 §1.4a at the contract level, and it belongs here more than\n // anywhere: `RoutingStore.complete` gained `leaseId` and this class kept\n // typechecking without it, because a parameter object with fewer\n // properties is assignable to one with more. The relay would have\n // enforced LEASE_HONORED on the memory store and not on the store that\n // runs in production, with nothing red on the way.\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n await store.claim(claimArgs());\n\n const late = await store.complete({\n jobId: \"a\",\n runnerId: \"runner_1\",\n leaseId: \"a-grant-that-ended\",\n envelope: ENVELOPE,\n disposition: \"ok\",\n });\n\n expect(late).toEqual({ refused: \"stale-lease\" });\n // Untouched: the current holder can still finish it.\n expect((await store.job(SITE, \"a\"))?.state).toBe(\"awaiting-payload\");\n expect((await store.job(SITE, \"a\"))?.result).toBeUndefined();\n await done();\n });\n\n it(\"stamps deadlines from its own clock\", async () => {\n const { store, done } = await make();\n await store.enqueue({ id: \"a\", siteId: SITE, stub: stub(\"a\") });\n const before = await store.now();\n const [granted] = await store.claim(claimArgs({ leaseMs: 45_000 }));\n const after = await store.now();\n\n // Bracketed rather than exact: a real store's clock moves between the two\n // reads, which is the whole reason it is the store's clock and not ours.\n expect(granted!.lease.expiresAt).toBeGreaterThanOrEqual(before + 45_000);\n expect(granted!.lease.expiresAt).toBeLessThanOrEqual(after + 45_000);\n await done();\n });\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byollm/relay",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The reference relay: routes sealed byollm jobs between sites and daemons, holding no decryption keys by construction.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"zod": "^4.1.13",
|
|
34
|
-
"@byollm/protocol": "0.1.0-alpha.
|
|
34
|
+
"@byollm/protocol": "0.1.0-alpha.26"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@supabase/supabase-js": "^2.112.2",
|
|
41
41
|
"vitest": "^4.1.10",
|
|
42
|
-
"
|
|
43
|
-
"@byollm/conformance": "0.1.0-alpha.
|
|
44
|
-
"byollm": "0.1.0-alpha.
|
|
42
|
+
"byollm": "0.1.0-alpha.26",
|
|
43
|
+
"@byollm/conformance": "0.1.0-alpha.26",
|
|
44
|
+
"@byollm/server": "0.1.0-alpha.26"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"vitest": ">=3"
|