@byollm/conformance 0.1.0-alpha.5 → 0.1.0-alpha.50
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 +107 -5
- package/dist/audit-cli.d.ts +1 -0
- package/dist/audit-cli.js +25 -0
- package/dist/audit-cli.js.map +1 -0
- package/dist/chunk-4ML6G7NC.js +469 -0
- package/dist/chunk-4ML6G7NC.js.map +1 -0
- package/dist/{chunk-EXNALQ5D.js → chunk-FCX5R4LZ.js} +323 -84
- package/dist/chunk-FCX5R4LZ.js.map +1 -0
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +107 -5
- package/dist/index.js +9 -1
- package/package.json +8 -6
- package/dist/chunk-EXNALQ5D.js.map +0 -1
|
@@ -94,9 +94,11 @@ function daemonConfig(options) {
|
|
|
94
94
|
const baseUrl = metered ? metered.baseUrl : options.subscription ? void 0 : "http://127.0.0.1:11434/v1";
|
|
95
95
|
return resolveConfig(
|
|
96
96
|
DaemonConfig.parse({
|
|
97
|
-
|
|
97
|
+
services: {
|
|
98
98
|
primary: {
|
|
99
|
-
|
|
99
|
+
model: "echo-model",
|
|
100
|
+
kinds: ["llm.generate", "llm.chat"],
|
|
101
|
+
type: backendId,
|
|
100
102
|
...baseUrl === void 0 ? {} : { baseUrl },
|
|
101
103
|
offer: options.offer,
|
|
102
104
|
...metered === void 0 ? {} : {
|
|
@@ -107,10 +109,6 @@ function daemonConfig(options) {
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
},
|
|
110
|
-
routes: {
|
|
111
|
-
"llm.generate": { backend: "primary", model: "echo-model" },
|
|
112
|
-
"llm.chat": { backend: "primary", model: "echo-model" }
|
|
113
|
-
},
|
|
114
112
|
concurrency: 4
|
|
115
113
|
})
|
|
116
114
|
);
|
|
@@ -118,7 +116,7 @@ function daemonConfig(options) {
|
|
|
118
116
|
async function pairDaemon(target, options) {
|
|
119
117
|
const home = await mkdtemp(join(tmpdir(), "byollm-conformance-"));
|
|
120
118
|
const loaded = daemonConfig({
|
|
121
|
-
offer: options.offer ?? "
|
|
119
|
+
offer: options.offer ?? "private",
|
|
122
120
|
subscription: options.subscription ?? false,
|
|
123
121
|
...options.metered === void 0 ? {} : { metered: options.metered }
|
|
124
122
|
});
|
|
@@ -140,6 +138,8 @@ async function pairDaemon(target, options) {
|
|
|
140
138
|
const fetchImpl = (input, init) => target.fetch(new Request(input, init));
|
|
141
139
|
const capabilities = loaded.routes.map((route) => ({
|
|
142
140
|
kind: route.kind,
|
|
141
|
+
service: route.service,
|
|
142
|
+
isDefault: true,
|
|
143
143
|
backendId: route.backendId,
|
|
144
144
|
backendClass: route.backendClass,
|
|
145
145
|
model: route.model,
|
|
@@ -203,8 +203,10 @@ async function pairDaemon(target, options) {
|
|
|
203
203
|
owner: result.pairing.owner,
|
|
204
204
|
identity: {
|
|
205
205
|
keys: () => deviceIdentity.load(Date.now()),
|
|
206
|
-
// Pinned at pairing, exactly as a real daemon does
|
|
207
|
-
|
|
206
|
+
// Pinned at pairing, exactly as a real daemon does — the set the
|
|
207
|
+
// upstream answered with, keyed by each site's identity key id
|
|
208
|
+
// (cloud_009 §5). A direct site is one entry.
|
|
209
|
+
sites: new Map(Object.entries(result.pairing.sites))
|
|
208
210
|
},
|
|
209
211
|
daemonVersion: "conformance",
|
|
210
212
|
loaded,
|
|
@@ -220,10 +222,9 @@ async function pairDaemon(target, options) {
|
|
|
220
222
|
allowlist,
|
|
221
223
|
runnerId: result.pairing.runnerId,
|
|
222
224
|
owner: result.pairing.owner,
|
|
223
|
-
token: result.pairing.token,
|
|
224
225
|
keys: await deviceIdentity.load(Date.now()),
|
|
225
226
|
identityKeys: () => deviceIdentity.load(Date.now()),
|
|
226
|
-
sitePinned: result.pairing.
|
|
227
|
+
sitePinned: Object.values(result.pairing.sites)[0],
|
|
227
228
|
home,
|
|
228
229
|
ingress,
|
|
229
230
|
spend,
|
|
@@ -309,6 +310,37 @@ async function claimOne(target, daemon) {
|
|
|
309
310
|
if (!job) throw new Error("claim returned no jobs");
|
|
310
311
|
return job;
|
|
311
312
|
}
|
|
313
|
+
async function claimRaw(target, daemon, capabilityOverride) {
|
|
314
|
+
const capabilities = capabilityOverride ?? await daemon.runner.detectCapabilities();
|
|
315
|
+
const body = JSON.stringify({
|
|
316
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
317
|
+
runnerId: daemon.runnerId,
|
|
318
|
+
capabilities,
|
|
319
|
+
max: 10
|
|
320
|
+
});
|
|
321
|
+
const signature = signRequest(daemon.keys, {
|
|
322
|
+
endpoint: "claim",
|
|
323
|
+
runnerId: daemon.runnerId,
|
|
324
|
+
issuedAt: Date.now(),
|
|
325
|
+
body
|
|
326
|
+
});
|
|
327
|
+
const response = await target.fetch(
|
|
328
|
+
new Request(`${target.origin}/byollm/claim`, {
|
|
329
|
+
method: "POST",
|
|
330
|
+
headers: {
|
|
331
|
+
"content-type": "application/json",
|
|
332
|
+
"x-byollm-runner": signature.runnerId,
|
|
333
|
+
"x-byollm-issued-at": String(signature.issuedAt),
|
|
334
|
+
"x-byollm-signature": signature.signature
|
|
335
|
+
},
|
|
336
|
+
body
|
|
337
|
+
})
|
|
338
|
+
);
|
|
339
|
+
if (response.status !== 200) {
|
|
340
|
+
throw new Error(`claim answered ${String(response.status)}`);
|
|
341
|
+
}
|
|
342
|
+
return (await response.json()).jobs;
|
|
343
|
+
}
|
|
312
344
|
async function releaseLease(target, daemon, jobId, leaseId) {
|
|
313
345
|
const body = JSON.stringify({
|
|
314
346
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -383,7 +415,11 @@ async function postResult(target, daemon, input) {
|
|
|
383
415
|
const keys = await daemon.identityKeys();
|
|
384
416
|
const sealer = input.sealWith ?? keys;
|
|
385
417
|
const envelope = await seal({
|
|
386
|
-
|
|
418
|
+
// `{ outcome, ran }` — cloud_008 §2.5.
|
|
419
|
+
plaintext: JSON.stringify({
|
|
420
|
+
outcome: input.outcome,
|
|
421
|
+
ran: { model: "test-model", backendClass: "http", durationMs: 1 }
|
|
422
|
+
}),
|
|
387
423
|
senderKeys: sealer,
|
|
388
424
|
recipientEncryptionPublic: daemon.sitePinned.encryption,
|
|
389
425
|
context: {
|
|
@@ -401,11 +437,9 @@ async function postResult(target, daemon, input) {
|
|
|
401
437
|
protocolVersion: PROTOCOL_VERSION,
|
|
402
438
|
runnerId: daemon.runnerId,
|
|
403
439
|
jobId: input.jobId,
|
|
440
|
+
leaseId: input.leaseId,
|
|
404
441
|
envelope,
|
|
405
|
-
disposition: input.disposition ?? input.outcome.outcome
|
|
406
|
-
model: "conformance-model",
|
|
407
|
-
backendClass: "http",
|
|
408
|
-
durationMs: 1
|
|
442
|
+
disposition: input.disposition ?? input.outcome.outcome
|
|
409
443
|
});
|
|
410
444
|
const signature = signRequest(daemon.keys, {
|
|
411
445
|
endpoint: "result",
|
|
@@ -445,7 +479,7 @@ async function fetchGenuine(target, daemon, owner = "alice") {
|
|
|
445
479
|
// The target's own name for the user, not the id it mapped that to —
|
|
446
480
|
// passing a mapped id back in addresses a user the target never made.
|
|
447
481
|
owner,
|
|
448
|
-
audience: "
|
|
482
|
+
audience: "private"
|
|
449
483
|
});
|
|
450
484
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
451
485
|
try {
|
|
@@ -491,7 +525,7 @@ var CHECKS = [
|
|
|
491
525
|
kind: "llm.generate",
|
|
492
526
|
payload: prompt("alice's private prompt"),
|
|
493
527
|
owner: "alice",
|
|
494
|
-
audience: "
|
|
528
|
+
audience: "private"
|
|
495
529
|
});
|
|
496
530
|
await bob.runner.tick();
|
|
497
531
|
await sleep(50);
|
|
@@ -562,6 +596,26 @@ var CHECKS = [
|
|
|
562
596
|
daemon.backend.seen.length > before,
|
|
563
597
|
"the advertised kind never reached the backend"
|
|
564
598
|
);
|
|
599
|
+
const chat = await target.enqueue({
|
|
600
|
+
kind: "llm.chat",
|
|
601
|
+
payload: { messages: [{ role: "user", content: "not for you" }] },
|
|
602
|
+
owner: "alice"
|
|
603
|
+
});
|
|
604
|
+
const generateOnly = await claimRaw(target, daemon, [
|
|
605
|
+
{
|
|
606
|
+
kind: "llm.generate",
|
|
607
|
+
service: "local",
|
|
608
|
+
isDefault: true,
|
|
609
|
+
backendId: "openai-http",
|
|
610
|
+
backendClass: "http",
|
|
611
|
+
model: "echo-model",
|
|
612
|
+
offerScope: "private"
|
|
613
|
+
}
|
|
614
|
+
]);
|
|
615
|
+
assert(
|
|
616
|
+
!generateOnly.some((offered) => offered.id === chat.id),
|
|
617
|
+
"a server offered `llm.chat` to a claim advertising only `llm.generate`"
|
|
618
|
+
);
|
|
565
619
|
} finally {
|
|
566
620
|
await daemon.dispose();
|
|
567
621
|
}
|
|
@@ -579,7 +633,7 @@ var CHECKS = [
|
|
|
579
633
|
owner: "alice"
|
|
580
634
|
});
|
|
581
635
|
dead.backend.hangMs = 6e4;
|
|
582
|
-
await dead
|
|
636
|
+
const firstLease = await claimOne(target, dead);
|
|
583
637
|
await waitFor(
|
|
584
638
|
async () => {
|
|
585
639
|
const state = await target.job(job.id);
|
|
@@ -594,12 +648,43 @@ var CHECKS = [
|
|
|
594
648
|
label: "alive"
|
|
595
649
|
});
|
|
596
650
|
try {
|
|
597
|
-
await alive
|
|
598
|
-
|
|
599
|
-
|
|
651
|
+
const reclaimed = await claimOne(target, alive);
|
|
652
|
+
assert(
|
|
653
|
+
reclaimed.id === job.id,
|
|
654
|
+
"the reclaiming daemon did not get the job"
|
|
655
|
+
);
|
|
656
|
+
const late = await postResult(target, dead, {
|
|
657
|
+
jobId: job.id,
|
|
658
|
+
leaseId: firstLease.lease.id,
|
|
659
|
+
outcome: { outcome: "ok", text: "from the machine that vanished" }
|
|
600
660
|
});
|
|
661
|
+
const lateBody = await late.json().catch(() => ({}));
|
|
662
|
+
assert(
|
|
663
|
+
lateBody.accepted !== true,
|
|
664
|
+
"a site accepted a result from a runner whose lease had lapsed"
|
|
665
|
+
);
|
|
666
|
+
const midflight = await target.job(job.id);
|
|
667
|
+
assert(
|
|
668
|
+
!midflight?.outcome,
|
|
669
|
+
"a lapsed holder's result was recorded over a live grant"
|
|
670
|
+
);
|
|
671
|
+
const proper = await postResult(target, alive, {
|
|
672
|
+
jobId: job.id,
|
|
673
|
+
leaseId: reclaimed.lease.id,
|
|
674
|
+
outcome: { outcome: "ok", text: "from the machine that took over" }
|
|
675
|
+
});
|
|
676
|
+
assert(
|
|
677
|
+
proper.status === 200,
|
|
678
|
+
`the reclaiming daemon could not finish the job (${String(proper.status)})`
|
|
679
|
+
);
|
|
680
|
+
const final = await target.job(job.id);
|
|
681
|
+
assert(
|
|
682
|
+
final?.outcome?.text === "from the machine that took over",
|
|
683
|
+
"the reclaimed job did not record the current holder's result"
|
|
684
|
+
);
|
|
601
685
|
} finally {
|
|
602
686
|
await alive.dispose();
|
|
687
|
+
await dead.dispose();
|
|
603
688
|
}
|
|
604
689
|
}
|
|
605
690
|
},
|
|
@@ -609,15 +694,15 @@ var CHECKS = [
|
|
|
609
694
|
musts: ["AUDIENCE_BOTH_SIDES", "NAMED_LOCAL_ALLOWLIST"],
|
|
610
695
|
async run(target) {
|
|
611
696
|
const expected = {
|
|
612
|
-
"
|
|
613
|
-
"
|
|
614
|
-
"
|
|
615
|
-
"
|
|
616
|
-
"
|
|
697
|
+
"private:private": false,
|
|
698
|
+
"private:team": false,
|
|
699
|
+
"private:public": false,
|
|
700
|
+
"team:private": false,
|
|
701
|
+
"team:team": false,
|
|
617
702
|
// refused locally — allowlist is empty
|
|
618
|
-
"
|
|
619
|
-
"public:
|
|
620
|
-
"public:
|
|
703
|
+
"team:public": true,
|
|
704
|
+
"public:private": false,
|
|
705
|
+
"public:team": false,
|
|
621
706
|
// refused locally — allowlist is empty
|
|
622
707
|
"public:public": true
|
|
623
708
|
};
|
|
@@ -653,13 +738,13 @@ var CHECKS = [
|
|
|
653
738
|
title: "a named job runs only once the daemon's own allowlist admits it",
|
|
654
739
|
musts: ["NAMED_LOCAL_ALLOWLIST", "REFUSAL_NOT_REOFFERED"],
|
|
655
740
|
async run(target) {
|
|
656
|
-
const bob = await pairDaemon(target, { owner: "bob", offer: "
|
|
741
|
+
const bob = await pairDaemon(target, { owner: "bob", offer: "team" });
|
|
657
742
|
try {
|
|
658
743
|
const refused = await target.enqueue({
|
|
659
744
|
kind: "llm.generate",
|
|
660
745
|
payload: prompt("before"),
|
|
661
746
|
owner: "alice",
|
|
662
|
-
audience: "
|
|
747
|
+
audience: "team"
|
|
663
748
|
});
|
|
664
749
|
await bob.runner.tick();
|
|
665
750
|
await sleep(80);
|
|
@@ -667,12 +752,10 @@ var CHECKS = [
|
|
|
667
752
|
(await target.job(refused.id))?.state !== "ok",
|
|
668
753
|
"a named job ran without the daemon's local allowlist admitting it"
|
|
669
754
|
);
|
|
670
|
-
const
|
|
671
|
-
await bob.runner.tick();
|
|
672
|
-
await sleep(50);
|
|
755
|
+
const reoffered = await claimRaw(target, bob);
|
|
673
756
|
assert(
|
|
674
|
-
|
|
675
|
-
"a
|
|
757
|
+
!reoffered.some((job) => job.id === refused.id),
|
|
758
|
+
"a server re-offered a job to the runner that refused it"
|
|
676
759
|
);
|
|
677
760
|
await bob.allowlist.add(
|
|
678
761
|
{ origin: target.origin, owner: await ownerIdFor(target, "alice") },
|
|
@@ -682,7 +765,7 @@ var CHECKS = [
|
|
|
682
765
|
kind: "llm.generate",
|
|
683
766
|
payload: prompt("after"),
|
|
684
767
|
owner: "alice",
|
|
685
|
-
audience: "
|
|
768
|
+
audience: "team"
|
|
686
769
|
});
|
|
687
770
|
await bob.runner.tick();
|
|
688
771
|
await waitFor(
|
|
@@ -730,7 +813,7 @@ var CHECKS = [
|
|
|
730
813
|
kind: "llm.generate",
|
|
731
814
|
payload: prompt("my own work"),
|
|
732
815
|
owner: "bob",
|
|
733
|
-
audience: "
|
|
816
|
+
audience: "private"
|
|
734
817
|
});
|
|
735
818
|
await bob.runner.tick();
|
|
736
819
|
await waitFor(async () => (await target.job(own.id))?.state === "ok", {
|
|
@@ -744,7 +827,12 @@ var CHECKS = [
|
|
|
744
827
|
{
|
|
745
828
|
id: "C008_REVOCATION",
|
|
746
829
|
title: "a revoked daemon stops mid-queue",
|
|
747
|
-
|
|
830
|
+
// Both halves, and this check already proved both: the daemon learns it
|
|
831
|
+
// is revoked (`REVOCATION_HONORED`), *and* the upstream leaves the job
|
|
832
|
+
// queued rather than granting it (`REVOCATION_IMMEDIATE`). The second
|
|
833
|
+
// assertion was here and cited nothing — which is how a MUST comes to be
|
|
834
|
+
// declared in a spec, absent from the registry, and tested all along.
|
|
835
|
+
musts: ["REVOCATION_HONORED", "REVOCATION_IMMEDIATE"],
|
|
748
836
|
async run(target) {
|
|
749
837
|
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
750
838
|
try {
|
|
@@ -809,32 +897,60 @@ var CHECKS = [
|
|
|
809
897
|
payload: prompt("once"),
|
|
810
898
|
owner: "alice"
|
|
811
899
|
});
|
|
812
|
-
await daemon
|
|
813
|
-
|
|
814
|
-
|
|
900
|
+
const claimed = await claimOne(target, daemon);
|
|
901
|
+
assert(claimed.id === job.id, "the harness could not claim its job");
|
|
902
|
+
const first = await postResult(target, daemon, {
|
|
903
|
+
jobId: job.id,
|
|
904
|
+
leaseId: claimed.lease.id,
|
|
905
|
+
outcome: { outcome: "ok", text: "the answer that counts" }
|
|
815
906
|
});
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
907
|
+
assert(
|
|
908
|
+
first.status === 200,
|
|
909
|
+
`a site refused the first result (${String(first.status)})`
|
|
910
|
+
);
|
|
911
|
+
const replay = await postResult(target, daemon, {
|
|
912
|
+
jobId: job.id,
|
|
913
|
+
leaseId: claimed.lease.id,
|
|
914
|
+
outcome: { outcome: "ok", text: "SECOND ANSWER" }
|
|
915
|
+
});
|
|
916
|
+
assert(
|
|
917
|
+
replay.status === 200,
|
|
918
|
+
`a replayed result was rejected rather than ignored (${String(replay.status)})`
|
|
919
|
+
);
|
|
920
|
+
const body = await replay.json();
|
|
921
|
+
assert(
|
|
922
|
+
body.accepted === false,
|
|
923
|
+
"a site reported a replayed result as newly accepted"
|
|
924
|
+
);
|
|
925
|
+
assert(
|
|
926
|
+
body.duplicate === true,
|
|
927
|
+
"a replay from the device that finished the job was not called a duplicate"
|
|
831
928
|
);
|
|
832
|
-
void response;
|
|
833
929
|
const after = await target.job(job.id);
|
|
834
930
|
assert(
|
|
835
|
-
after?.outcome?.text ===
|
|
836
|
-
|
|
931
|
+
after?.outcome?.text === "the answer that counts",
|
|
932
|
+
`a second result overwrote the first (${String(after?.outcome?.text)})`
|
|
837
933
|
);
|
|
934
|
+
const stranger = await pairDaemon(target, { owner: "alice" });
|
|
935
|
+
try {
|
|
936
|
+
const foreign = await postResult(target, stranger, {
|
|
937
|
+
jobId: job.id,
|
|
938
|
+
leaseId: claimed.lease.id,
|
|
939
|
+
outcome: { outcome: "ok", text: "not this device's to answer" }
|
|
940
|
+
});
|
|
941
|
+
const foreignBody = await foreign.json().catch(() => ({}));
|
|
942
|
+
assert(
|
|
943
|
+
foreignBody["duplicate"] !== true,
|
|
944
|
+
"a site told a device that never held this job it was a duplicate"
|
|
945
|
+
);
|
|
946
|
+
const stillFirst = await target.job(job.id);
|
|
947
|
+
assert(
|
|
948
|
+
stillFirst?.outcome?.text === "the answer that counts",
|
|
949
|
+
"a stranger's result overwrote a terminal job"
|
|
950
|
+
);
|
|
951
|
+
} finally {
|
|
952
|
+
await stranger.dispose();
|
|
953
|
+
}
|
|
838
954
|
} finally {
|
|
839
955
|
await daemon.dispose();
|
|
840
956
|
}
|
|
@@ -852,13 +968,13 @@ var CHECKS = [
|
|
|
852
968
|
kind: "llm.generate",
|
|
853
969
|
payload: prompt("step one"),
|
|
854
970
|
owner: "bob",
|
|
855
|
-
audience: "
|
|
971
|
+
audience: "private"
|
|
856
972
|
});
|
|
857
973
|
const second = await target.enqueue({
|
|
858
974
|
kind: "llm.generate",
|
|
859
975
|
payload: prompt("step two"),
|
|
860
976
|
owner: "alice",
|
|
861
|
-
audience: "
|
|
977
|
+
audience: "private",
|
|
862
978
|
dependsOn: [first.id]
|
|
863
979
|
});
|
|
864
980
|
await alice.runner.tick();
|
|
@@ -949,7 +1065,11 @@ var CHECKS = [
|
|
|
949
1065
|
{
|
|
950
1066
|
id: "C014_RESULT_PROVENANCE",
|
|
951
1067
|
title: "a community result arrives marked untrusted, a self result does not",
|
|
952
|
-
|
|
1068
|
+
// `PROVENANCE_NAMES_DEVICE` supersedes `RESULT_PROVENANCE` — a
|
|
1069
|
+
// strengthening rather than a rename. C030 is the other half: a label
|
|
1070
|
+
// means nothing unless a result whose signature does not verify against
|
|
1071
|
+
// the granted device is refused rather than recorded.
|
|
1072
|
+
musts: ["PROVENANCE_NAMES_DEVICE"],
|
|
953
1073
|
async run(target) {
|
|
954
1074
|
const bob = await pairDaemon(target, { owner: "bob", offer: "public" });
|
|
955
1075
|
try {
|
|
@@ -977,7 +1097,7 @@ var CHECKS = [
|
|
|
977
1097
|
kind: "llm.generate",
|
|
978
1098
|
payload: prompt("my own"),
|
|
979
1099
|
owner: "bob",
|
|
980
|
-
audience: "
|
|
1100
|
+
audience: "private"
|
|
981
1101
|
});
|
|
982
1102
|
await bob.runner.tick();
|
|
983
1103
|
await waitFor(async () => (await target.job(own.id))?.state === "ok", {
|
|
@@ -1031,7 +1151,13 @@ var CHECKS = [
|
|
|
1031
1151
|
{
|
|
1032
1152
|
id: "C016_UNAUTHENTICATED_REFUSED",
|
|
1033
1153
|
title: "the protocol endpoints refuse an unknown token",
|
|
1034
|
-
|
|
1154
|
+
// `CONSENT_BEFORE_ROUTE` on this plane. A relay has a consent record; a
|
|
1155
|
+
// direct site has pairing, and it is the same obligation — an upstream
|
|
1156
|
+
// routes to a device it has a record binding, and there is no discovery
|
|
1157
|
+
// path by which an unbound device receives work. Every endpoint is
|
|
1158
|
+
// checked rather than just `claim`, which is what makes it the absence
|
|
1159
|
+
// of a path rather than the absence of one door.
|
|
1160
|
+
musts: ["PAIR_ONE_USER", "CONSENT_BEFORE_ROUTE"],
|
|
1035
1161
|
async run(target) {
|
|
1036
1162
|
for (const endpoint of ["claim", "heartbeat", "result", "release"]) {
|
|
1037
1163
|
const response = await target.fetch(
|
|
@@ -1054,7 +1180,15 @@ var CHECKS = [
|
|
|
1054
1180
|
{
|
|
1055
1181
|
id: "C017_METERED_DEFAULTS_SELF",
|
|
1056
1182
|
title: "a paid backend is not shared until its owner says so, with a ceiling",
|
|
1057
|
-
|
|
1183
|
+
// `EFFECTIVE_OFFER_ONLY` too: bob asks for `public`, what reaches the
|
|
1184
|
+
// server is `self`, and the server acts on what it was told rather than
|
|
1185
|
+
// on what was wanted. That *is* the effective-offer rule, proved here
|
|
1186
|
+
// without being named.
|
|
1187
|
+
musts: [
|
|
1188
|
+
"METERED_DEFAULTS_SELF",
|
|
1189
|
+
"COST_NOT_CONFIGURABLE",
|
|
1190
|
+
"EFFECTIVE_OFFER_ONLY"
|
|
1191
|
+
],
|
|
1058
1192
|
async run(target) {
|
|
1059
1193
|
const bob = await pairDaemon(target, {
|
|
1060
1194
|
owner: "bob",
|
|
@@ -1066,7 +1200,7 @@ var CHECKS = [
|
|
|
1066
1200
|
});
|
|
1067
1201
|
try {
|
|
1068
1202
|
assert(
|
|
1069
|
-
bob.loaded.routes.every((route) => route.offerScope === "
|
|
1203
|
+
bob.loaded.routes.every((route) => route.offerScope === "private"),
|
|
1070
1204
|
"a metered backend was advertised beyond its owner without consent"
|
|
1071
1205
|
);
|
|
1072
1206
|
assert(
|
|
@@ -1107,7 +1241,7 @@ var CHECKS = [
|
|
|
1107
1241
|
kind: "llm.generate",
|
|
1108
1242
|
payload: prompt("my own work"),
|
|
1109
1243
|
owner: "bob",
|
|
1110
|
-
audience: "
|
|
1244
|
+
audience: "private"
|
|
1111
1245
|
});
|
|
1112
1246
|
await bob.runner.tick();
|
|
1113
1247
|
await waitFor(async () => (await target.job(own.id))?.state === "ok", {
|
|
@@ -1183,7 +1317,7 @@ var CHECKS = [
|
|
|
1183
1317
|
kind: "llm.generate",
|
|
1184
1318
|
payload: prompt("my own work, my own key"),
|
|
1185
1319
|
owner: "bob",
|
|
1186
|
-
audience: "
|
|
1320
|
+
audience: "private"
|
|
1187
1321
|
});
|
|
1188
1322
|
await bob.runner.tick();
|
|
1189
1323
|
await waitFor(async () => (await target.job(own.id))?.state === "ok", {
|
|
@@ -1206,7 +1340,7 @@ var CHECKS = [
|
|
|
1206
1340
|
kind: "llm.generate",
|
|
1207
1341
|
payload: prompt("only once, please"),
|
|
1208
1342
|
owner: "alice",
|
|
1209
|
-
audience: "
|
|
1343
|
+
audience: "private"
|
|
1210
1344
|
});
|
|
1211
1345
|
await Promise.all([a.runner.tick(), b.runner.tick()]);
|
|
1212
1346
|
await waitFor(async () => (await target.job(job.id))?.state === "ok", {
|
|
@@ -1334,7 +1468,7 @@ var CHECKS = [
|
|
|
1334
1468
|
baseUrl: "http://evil.test/v1"
|
|
1335
1469
|
},
|
|
1336
1470
|
owner: "alice",
|
|
1337
|
-
audience: "
|
|
1471
|
+
audience: "private"
|
|
1338
1472
|
});
|
|
1339
1473
|
} catch {
|
|
1340
1474
|
refused = true;
|
|
@@ -1364,7 +1498,7 @@ var CHECKS = [
|
|
|
1364
1498
|
kind: "llm.generate",
|
|
1365
1499
|
payload: prompt("ordinary text"),
|
|
1366
1500
|
owner: "alice",
|
|
1367
|
-
audience: "
|
|
1501
|
+
audience: "private"
|
|
1368
1502
|
});
|
|
1369
1503
|
await daemon.runner.tick();
|
|
1370
1504
|
await waitFor(async () => (await target.job(ok.id))?.state === "ok", {
|
|
@@ -1471,7 +1605,7 @@ var CHECKS = [
|
|
|
1471
1605
|
};
|
|
1472
1606
|
const pending = await poll();
|
|
1473
1607
|
assert(
|
|
1474
|
-
pending["
|
|
1608
|
+
pending["sites"] === void 0,
|
|
1475
1609
|
"a pending poll disclosed the site's keys before anyone approved"
|
|
1476
1610
|
);
|
|
1477
1611
|
await target.approvePairing(pairing.userCode, "alice");
|
|
@@ -1480,8 +1614,19 @@ var CHECKS = [
|
|
|
1480
1614
|
approved["status"] === "approved",
|
|
1481
1615
|
`poll after approval said "${String(approved["status"])}"`
|
|
1482
1616
|
);
|
|
1483
|
-
const
|
|
1484
|
-
assert(
|
|
1617
|
+
const offered = approved["sites"];
|
|
1618
|
+
assert(
|
|
1619
|
+
typeof offered === "object" && offered !== null,
|
|
1620
|
+
"the approval carried no sites to pin"
|
|
1621
|
+
);
|
|
1622
|
+
const parsed = Object.values(offered).map(
|
|
1623
|
+
(value) => PublicIdentity.safeParse(value)
|
|
1624
|
+
);
|
|
1625
|
+
assert(
|
|
1626
|
+
parsed.length > 0 && parsed.every((entry) => entry.success),
|
|
1627
|
+
"the approval carried no usable site identity"
|
|
1628
|
+
);
|
|
1629
|
+
const site = parsed[0];
|
|
1485
1630
|
assert(
|
|
1486
1631
|
verifyPublicIdentity(site.data),
|
|
1487
1632
|
"the site's encryption key is not signed by the identity it presented"
|
|
@@ -1571,7 +1716,7 @@ var CHECKS = [
|
|
|
1571
1716
|
kind: "llm.generate",
|
|
1572
1717
|
payload: prompt("run me once"),
|
|
1573
1718
|
owner: "alice",
|
|
1574
|
-
audience: "
|
|
1719
|
+
audience: "private"
|
|
1575
1720
|
});
|
|
1576
1721
|
const first = await claimOne(target, daemon);
|
|
1577
1722
|
assert(
|
|
@@ -1611,7 +1756,7 @@ var CHECKS = [
|
|
|
1611
1756
|
kind: "llm.generate",
|
|
1612
1757
|
payload: prompt("this must not appear in a claim response"),
|
|
1613
1758
|
owner: "alice",
|
|
1614
|
-
audience: "
|
|
1759
|
+
audience: "private"
|
|
1615
1760
|
});
|
|
1616
1761
|
const stub = await claimOne(target, daemon);
|
|
1617
1762
|
const asRecord = stub;
|
|
@@ -1676,7 +1821,7 @@ var CHECKS = [
|
|
|
1676
1821
|
kind: "llm.generate",
|
|
1677
1822
|
payload: prompt(secret),
|
|
1678
1823
|
owner: "alice",
|
|
1679
|
-
audience: "
|
|
1824
|
+
audience: "private"
|
|
1680
1825
|
});
|
|
1681
1826
|
const stored = await target.job(job.id);
|
|
1682
1827
|
assert(
|
|
@@ -1754,7 +1899,11 @@ var CHECKS = [
|
|
|
1754
1899
|
{
|
|
1755
1900
|
id: "C030_SITE_REFUSES_UNSIGNED_RESULTS",
|
|
1756
1901
|
title: "a site refuses a result not signed by the device that ran it",
|
|
1757
|
-
|
|
1902
|
+
// The proof-of-possession half of `PROVENANCE_NAMES_DEVICE`: attribution
|
|
1903
|
+
// by a signature that verifies against the device the lease was granted
|
|
1904
|
+
// to, rather than by a key id carried beside the result. Carrying an id
|
|
1905
|
+
// is not proving possession, and a forger writes whatever it likes.
|
|
1906
|
+
musts: ["ENVELOPE_SEALED_AND_SIGNED", "PROVENANCE_NAMES_DEVICE"],
|
|
1758
1907
|
async run(target) {
|
|
1759
1908
|
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
1760
1909
|
try {
|
|
@@ -1768,6 +1917,7 @@ var CHECKS = [
|
|
|
1768
1917
|
const relay = generateKeys(Date.now());
|
|
1769
1918
|
const forged = await postResult(target, daemon, {
|
|
1770
1919
|
jobId: job.id,
|
|
1920
|
+
leaseId: claimed.lease.id,
|
|
1771
1921
|
outcome: { outcome: "ok", text: "an answer the device never gave" },
|
|
1772
1922
|
sealWith: relay
|
|
1773
1923
|
});
|
|
@@ -1782,6 +1932,7 @@ var CHECKS = [
|
|
|
1782
1932
|
);
|
|
1783
1933
|
const real = await postResult(target, daemon, {
|
|
1784
1934
|
jobId: job.id,
|
|
1935
|
+
leaseId: claimed.lease.id,
|
|
1785
1936
|
outcome: { outcome: "ok", text: "the genuine answer" }
|
|
1786
1937
|
});
|
|
1787
1938
|
assert(
|
|
@@ -1790,6 +1941,7 @@ var CHECKS = [
|
|
|
1790
1941
|
);
|
|
1791
1942
|
const lying = await postResult(target, daemon, {
|
|
1792
1943
|
jobId: job.id,
|
|
1944
|
+
leaseId: claimed.lease.id,
|
|
1793
1945
|
outcome: {
|
|
1794
1946
|
outcome: "error",
|
|
1795
1947
|
code: "backend-error",
|
|
@@ -1806,11 +1958,98 @@ var CHECKS = [
|
|
|
1806
1958
|
await daemon.dispose();
|
|
1807
1959
|
}
|
|
1808
1960
|
}
|
|
1961
|
+
},
|
|
1962
|
+
{
|
|
1963
|
+
id: "C032_SERVER_REFUSES_TO_OFFER",
|
|
1964
|
+
title: "a claim is not answered with work the claimer may not run",
|
|
1965
|
+
musts: ["AUDIENCE_BOTH_SIDES"],
|
|
1966
|
+
async run(target) {
|
|
1967
|
+
const bob = await pairDaemon(target, { owner: "bob", offer: "public" });
|
|
1968
|
+
try {
|
|
1969
|
+
const priv = await target.enqueue({
|
|
1970
|
+
kind: "llm.generate",
|
|
1971
|
+
payload: prompt("alice's own machines only"),
|
|
1972
|
+
owner: "alice",
|
|
1973
|
+
audience: "private"
|
|
1974
|
+
});
|
|
1975
|
+
const offered = await claimRaw(target, bob);
|
|
1976
|
+
assert(
|
|
1977
|
+
!offered.some((job) => job.id === priv.id),
|
|
1978
|
+
"a server offered a `self` job to a device its owner does not own"
|
|
1979
|
+
);
|
|
1980
|
+
const shared = await target.enqueue({
|
|
1981
|
+
kind: "llm.generate",
|
|
1982
|
+
payload: prompt("anyone may run this"),
|
|
1983
|
+
owner: "alice",
|
|
1984
|
+
audience: "public"
|
|
1985
|
+
});
|
|
1986
|
+
const second = await claimRaw(target, bob);
|
|
1987
|
+
assert(
|
|
1988
|
+
second.some((job) => job.id === shared.id),
|
|
1989
|
+
"a server withheld a `public` job from a public-offering device"
|
|
1990
|
+
);
|
|
1991
|
+
} finally {
|
|
1992
|
+
await bob.dispose();
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
id: "C031_ROSTER_NOT_DISCLOSED",
|
|
1998
|
+
title: "a claimed stub carries no list of who may run the job",
|
|
1999
|
+
musts: ["ROSTER_NOT_DISCLOSED"],
|
|
2000
|
+
async run(target) {
|
|
2001
|
+
const daemon = await pairDaemon(target, { owner: "alice" });
|
|
2002
|
+
try {
|
|
2003
|
+
const job = await target.enqueue({
|
|
2004
|
+
kind: "llm.generate",
|
|
2005
|
+
payload: prompt("who else is on this roster"),
|
|
2006
|
+
owner: "alice",
|
|
2007
|
+
audience: "team",
|
|
2008
|
+
// The site restricts the job to people who are not this daemon's
|
|
2009
|
+
// owner. A stub that carried the list would be handing a routing
|
|
2010
|
+
// party the membership of alice's group.
|
|
2011
|
+
audienceAllow: ["alice", "carol", "erin"]
|
|
2012
|
+
});
|
|
2013
|
+
const claimed = await claimOne(target, daemon);
|
|
2014
|
+
assert(
|
|
2015
|
+
claimed.id === job.id,
|
|
2016
|
+
"the harness could not claim its own named job"
|
|
2017
|
+
);
|
|
2018
|
+
const asRecord = claimed;
|
|
2019
|
+
assert(
|
|
2020
|
+
asRecord["audienceAllow"] === void 0,
|
|
2021
|
+
"a claimed stub carried audienceAllow"
|
|
2022
|
+
);
|
|
2023
|
+
const parsed = ClaimedStub.safeParse(claimed);
|
|
2024
|
+
assert(
|
|
2025
|
+
parsed.success,
|
|
2026
|
+
"the claim response is not a valid stub, so its fields prove nothing"
|
|
2027
|
+
);
|
|
2028
|
+
const wire = JSON.stringify(claimed);
|
|
2029
|
+
for (const member of ["carol", "erin"]) {
|
|
2030
|
+
assert(
|
|
2031
|
+
!wire.includes(member),
|
|
2032
|
+
`a claimed stub disclosed roster member "${member}"`
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
assert(
|
|
2036
|
+
claimed.audience === "team",
|
|
2037
|
+
"the stub lost the audience routing decides on"
|
|
2038
|
+
);
|
|
2039
|
+
assert(
|
|
2040
|
+
typeof claimed.owner === "string" && claimed.owner.length > 0,
|
|
2041
|
+
"the stub lost the owner"
|
|
2042
|
+
);
|
|
2043
|
+
} finally {
|
|
2044
|
+
await daemon.dispose();
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
1809
2047
|
}
|
|
1810
2048
|
];
|
|
1811
2049
|
|
|
1812
2050
|
// src/certify.ts
|
|
1813
2051
|
import {
|
|
2052
|
+
kindsOf,
|
|
1814
2053
|
MUSTS,
|
|
1815
2054
|
MUST_IDS,
|
|
1816
2055
|
mustsVerifiedBy
|
|
@@ -1854,7 +2093,7 @@ function uncoveredMusts(checks = CHECKS) {
|
|
|
1854
2093
|
return mustsVerifiedBy("conformance").filter((id) => !covered.has(id));
|
|
1855
2094
|
}
|
|
1856
2095
|
function miscoveredMusts(checks = CHECKS) {
|
|
1857
|
-
return [...new Set(checks.flatMap((check) => check.musts))].filter((id) => MUSTS[id].
|
|
2096
|
+
return [...new Set(checks.flatMap((check) => check.musts))].filter((id) => !kindsOf(MUSTS[id]).includes("conformance")).sort();
|
|
1858
2097
|
}
|
|
1859
2098
|
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.)";
|
|
1860
2099
|
function formatReport(report) {
|
|
@@ -1882,13 +2121,13 @@ function formatReport(report) {
|
|
|
1882
2121
|
}
|
|
1883
2122
|
}
|
|
1884
2123
|
const elsewhere = MUST_IDS.filter(
|
|
1885
|
-
(id) => MUSTS[id].
|
|
2124
|
+
(id) => !kindsOf(MUSTS[id]).includes("conformance")
|
|
1886
2125
|
);
|
|
1887
2126
|
if (elsewhere.length > 0) {
|
|
1888
2127
|
lines.push("");
|
|
1889
2128
|
lines.push(" Verified elsewhere, not by this kit:");
|
|
1890
2129
|
for (const kind of ["adversarial", "construction", "operator"]) {
|
|
1891
|
-
const ids = elsewhere.filter((id) => MUSTS[id].
|
|
2130
|
+
const ids = elsewhere.filter((id) => kindsOf(MUSTS[id]).includes(kind));
|
|
1892
2131
|
if (ids.length === 0) continue;
|
|
1893
2132
|
lines.push(` ${kind}: ${ids.join(", ")}`);
|
|
1894
2133
|
}
|
|
@@ -1911,4 +2150,4 @@ export {
|
|
|
1911
2150
|
miscoveredMusts,
|
|
1912
2151
|
formatReport
|
|
1913
2152
|
};
|
|
1914
|
-
//# sourceMappingURL=chunk-
|
|
2153
|
+
//# sourceMappingURL=chunk-FCX5R4LZ.js.map
|