@byollm/protocol 0.1.0-alpha.2 → 0.1.0-alpha.21
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 +66 -2
- package/dist/index.d.ts +921 -80
- package/dist/index.js +1158 -105
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -5,9 +5,10 @@ import { z } from 'zod';
|
|
|
5
5
|
* Rev 1 §A, because the two classes have different threat surfaces.
|
|
6
6
|
*
|
|
7
7
|
* - `http`: an OpenAI-compatible HTTP server (Ollama, `mlx_lm.server`,
|
|
8
|
-
* llama.cpp server, vLLM
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* llama.cpp server, vLLM, and every hosted provider that speaks the same
|
|
9
|
+
* wire format). Spawns nothing, so byollm_004 §2's argv, stdin, env and
|
|
10
|
+
* sandbox requirements are not applicable by construction. Its threat
|
|
11
|
+
* surface is SSRF-shaped and bounded by {@link MUSTS.HTTP_BASE_URL_SAFE}.
|
|
11
12
|
* - `process`: spawns a binary (`claude` CLI today, `mlx_lm.lora` for a
|
|
12
13
|
* future `train.*` kind). All of byollm_004 §2 is mandatory here.
|
|
13
14
|
*/
|
|
@@ -17,20 +18,26 @@ declare const BackendClass: z.ZodEnum<{
|
|
|
17
18
|
}>;
|
|
18
19
|
type BackendClass = z.infer<typeof BackendClass>;
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
+
* Who pays, and how — byollm_007.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* This replaced a two-valued `account` field that conflated two unrelated
|
|
24
|
+
* constraints and, in doing so, left a hole: `openai-http` was "open", but it
|
|
25
|
+
* accepts an API key, so an owner could point it at a paid endpoint, offer it
|
|
26
|
+
* `public`, and donate their credit balance to strangers. The community
|
|
27
|
+
* budgets cap job *count*, not spend.
|
|
28
|
+
*
|
|
29
|
+
* - `free` — local compute. Costs electricity, not money. Shareable.
|
|
30
|
+
* - `metered` — per-token billing against the owner's account. Legal to
|
|
31
|
+
* share and ruinous to share by accident.
|
|
32
|
+
* - `subscription` — a vendor account whose terms forbid third-party work.
|
|
33
|
+
* Sharing is a terms violation, not merely expensive.
|
|
28
34
|
*/
|
|
29
|
-
declare const
|
|
30
|
-
|
|
35
|
+
declare const BackendCost: z.ZodEnum<{
|
|
36
|
+
free: "free";
|
|
37
|
+
metered: "metered";
|
|
31
38
|
subscription: "subscription";
|
|
32
39
|
}>;
|
|
33
|
-
type
|
|
40
|
+
type BackendCost = z.infer<typeof BackendCost>;
|
|
34
41
|
/** The immutable facts about a backend that the protocol reasons over. */
|
|
35
42
|
interface BackendDescriptor {
|
|
36
43
|
/** Stable backend id, as written in `byollm.config.json`. */
|
|
@@ -39,33 +46,86 @@ interface BackendDescriptor {
|
|
|
39
46
|
readonly label: string;
|
|
40
47
|
/** Determines which isolation requirements apply. */
|
|
41
48
|
readonly class: BackendClass;
|
|
42
|
-
/**
|
|
43
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Who pays. Fixed here for every named provider and **not overridable by
|
|
51
|
+
* configuration** ({@link MUSTS.COST_NOT_CONFIGURABLE}) — `openai` is
|
|
52
|
+
* metered because it is, and no setting changes that.
|
|
53
|
+
*
|
|
54
|
+
* `null` only for the generic {@link BACKENDS."openai-http"} entry, whose
|
|
55
|
+
* cost is inferred from its base URL instead
|
|
56
|
+
* ({@link MUSTS.REMOTE_IS_NEVER_FREE}).
|
|
57
|
+
*/
|
|
58
|
+
readonly cost: BackendCost | null;
|
|
44
59
|
/**
|
|
45
60
|
* Which adversarial corpus byollm_004 §5 runs against this backend. A
|
|
46
61
|
* backend cannot be registered without one — the coverage check in the
|
|
47
62
|
* adversarial suite enforces it.
|
|
48
63
|
*/
|
|
49
64
|
readonly adversarialCorpus: "process" | "http";
|
|
65
|
+
/**
|
|
66
|
+
* Where this provider lives, when that is knowable. Owner config may
|
|
67
|
+
* override it; a provider with no default requires one to be given.
|
|
68
|
+
*/
|
|
69
|
+
readonly defaultBaseUrl?: string;
|
|
50
70
|
}
|
|
51
71
|
/**
|
|
52
|
-
* The
|
|
72
|
+
* The backend registry.
|
|
53
73
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* OpenAI-compatible `/v1/chat/completions
|
|
57
|
-
* owner
|
|
58
|
-
*
|
|
74
|
+
* **Providers are entries, not implementations.** Every HTTP-class provider
|
|
75
|
+
* below shares the single `openai-http` transport, because they all speak
|
|
76
|
+
* OpenAI-compatible `/v1/chat/completions`. An entry adds a stable id, a cost
|
|
77
|
+
* class the owner cannot override, and a default base URL. Adding a provider
|
|
78
|
+
* is therefore one line and no new code — which is why the adversarial corpus
|
|
79
|
+
* still covers all of them, and why a PR adding one is reviewable at a glance.
|
|
59
80
|
*/
|
|
60
81
|
declare const BACKENDS: Readonly<{
|
|
82
|
+
readonly ollama: BackendDescriptor;
|
|
83
|
+
readonly mlx: BackendDescriptor;
|
|
84
|
+
readonly llamacpp: BackendDescriptor;
|
|
85
|
+
readonly vllm: BackendDescriptor;
|
|
86
|
+
readonly lmstudio: BackendDescriptor;
|
|
87
|
+
readonly jan: BackendDescriptor;
|
|
88
|
+
readonly localai: BackendDescriptor;
|
|
89
|
+
/**
|
|
90
|
+
* Note the pair: `anthropic` and {@link BACKENDS."claude-cli"} reach the
|
|
91
|
+
* same vendor and land in different cost classes. That is not an
|
|
92
|
+
* inconsistency — it is the axis working. One bills a key per token, the
|
|
93
|
+
* other runs under a personal plan whose terms cover one person's work. Who
|
|
94
|
+
* pays and under what terms is the question; which company is not.
|
|
95
|
+
*/
|
|
96
|
+
readonly anthropic: BackendDescriptor;
|
|
97
|
+
readonly openai: BackendDescriptor;
|
|
98
|
+
readonly gemini: BackendDescriptor;
|
|
99
|
+
readonly grok: BackendDescriptor;
|
|
100
|
+
readonly groq: BackendDescriptor;
|
|
101
|
+
readonly openrouter: BackendDescriptor;
|
|
102
|
+
readonly together: BackendDescriptor;
|
|
103
|
+
readonly deepseek: BackendDescriptor;
|
|
104
|
+
readonly mistral: BackendDescriptor;
|
|
61
105
|
readonly "openai-http": BackendDescriptor;
|
|
62
106
|
readonly "claude-cli": BackendDescriptor;
|
|
63
107
|
}>;
|
|
64
108
|
/** The id of a registered backend. */
|
|
65
109
|
type BackendId = keyof typeof BACKENDS;
|
|
66
110
|
/** All registered backend ids — the adversarial coverage check iterates this. */
|
|
67
|
-
declare const BACKEND_IDS: readonly ("openai-http" | "claude-cli")[];
|
|
111
|
+
declare const BACKEND_IDS: readonly ("ollama" | "mlx" | "llamacpp" | "vllm" | "lmstudio" | "jan" | "localai" | "anthropic" | "openai" | "gemini" | "grok" | "groq" | "openrouter" | "together" | "deepseek" | "mistral" | "openai-http" | "claude-cli")[];
|
|
68
112
|
declare const BackendIdSchema: z.ZodEnum<{
|
|
113
|
+
ollama: "ollama";
|
|
114
|
+
mlx: "mlx";
|
|
115
|
+
llamacpp: "llamacpp";
|
|
116
|
+
vllm: "vllm";
|
|
117
|
+
lmstudio: "lmstudio";
|
|
118
|
+
jan: "jan";
|
|
119
|
+
localai: "localai";
|
|
120
|
+
anthropic: "anthropic";
|
|
121
|
+
openai: "openai";
|
|
122
|
+
gemini: "gemini";
|
|
123
|
+
grok: "grok";
|
|
124
|
+
groq: "groq";
|
|
125
|
+
openrouter: "openrouter";
|
|
126
|
+
together: "together";
|
|
127
|
+
deepseek: "deepseek";
|
|
128
|
+
mistral: "mistral";
|
|
69
129
|
"openai-http": "openai-http";
|
|
70
130
|
"claude-cli": "claude-cli";
|
|
71
131
|
}>;
|
|
@@ -78,6 +138,34 @@ declare function isBackendId(value: string): value is BackendId;
|
|
|
78
138
|
* adversarial corpus, so refusing is the safe direction.
|
|
79
139
|
*/
|
|
80
140
|
declare function backendDescriptor(id: BackendId): BackendDescriptor;
|
|
141
|
+
/**
|
|
142
|
+
* Is this host local enough that compute there is free?
|
|
143
|
+
*
|
|
144
|
+
* Loopback and the private ranges only. This is the rule that makes
|
|
145
|
+
* {@link MUSTS.REMOTE_IS_NEVER_FREE} enforceable rather than a promise: an
|
|
146
|
+
* owner cannot reach a paid API through the generic backend and call it free,
|
|
147
|
+
* because "free" is derived from the address, not from what the config claims.
|
|
148
|
+
*
|
|
149
|
+
* **What this cannot see.** The address is all it reads. A proxy on
|
|
150
|
+
* `127.0.0.1` forwarding to a paid API classes as `free` and nothing
|
|
151
|
+
* downstream will contradict it. That is deliberate: standing up a relay is
|
|
152
|
+
* an act by the machine's owner against their own account, and the threat
|
|
153
|
+
* model here is a hostile *job*, not an owner routing around a rule that
|
|
154
|
+
* exists to protect them. What this catches is the accident — a remote paid
|
|
155
|
+
* endpoint offered `public` because nobody thought about the bill. See
|
|
156
|
+
* `docs/security.md` §4a.
|
|
157
|
+
*/
|
|
158
|
+
declare function isLocalHost(hostname: string): boolean;
|
|
159
|
+
/**
|
|
160
|
+
* The cost class of a configured backend instance.
|
|
161
|
+
*
|
|
162
|
+
* For every named provider this is whatever the registry says, full stop
|
|
163
|
+
* ({@link MUSTS.COST_NOT_CONFIGURABLE}). For the generic `openai-http` entry
|
|
164
|
+
* it is inferred from the base URL, and a base URL that cannot be parsed is
|
|
165
|
+
* treated as `metered` — the expensive side, because guessing "free" wrong
|
|
166
|
+
* costs the owner money.
|
|
167
|
+
*/
|
|
168
|
+
declare function resolveCost(id: BackendId, baseUrl: string | undefined): BackendCost;
|
|
81
169
|
|
|
82
170
|
/**
|
|
83
171
|
* Who may run a job, declared by the app that enqueued it.
|
|
@@ -121,6 +209,8 @@ declare const MatchRefusal: z.ZodEnum<{
|
|
|
121
209
|
"not-in-server-allowlist": "not-in-server-allowlist";
|
|
122
210
|
"offer-scope-too-narrow": "offer-scope-too-narrow";
|
|
123
211
|
"subscription-self-lock": "subscription-self-lock";
|
|
212
|
+
"metered-no-spend-consent": "metered-no-spend-consent";
|
|
213
|
+
"metered-ceiling-reached": "metered-ceiling-reached";
|
|
124
214
|
}>;
|
|
125
215
|
type MatchRefusal = z.infer<typeof MatchRefusal>;
|
|
126
216
|
/** The outcome of an audience match. */
|
|
@@ -130,16 +220,30 @@ type MatchResult = {
|
|
|
130
220
|
readonly ok: false;
|
|
131
221
|
readonly refusal: MatchRefusal;
|
|
132
222
|
};
|
|
223
|
+
/** What the owner has agreed to spend on other people's work, if anything. */
|
|
224
|
+
interface SpendConsent {
|
|
225
|
+
/** The owner explicitly acknowledged that sharing this backend costs money. */
|
|
226
|
+
readonly acknowledged: boolean;
|
|
227
|
+
/** Their ceiling. Absent means no ceiling was set, which is not consent. */
|
|
228
|
+
readonly ceilingReached?: boolean;
|
|
229
|
+
}
|
|
133
230
|
/**
|
|
134
231
|
* The effective offer scope of a backend.
|
|
135
232
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
233
|
+
* Three rules, applied at the one place both the daemon's config loader and
|
|
234
|
+
* its matcher call, so no code path can observe a scope wider than the cost
|
|
235
|
+
* class allows:
|
|
236
|
+
*
|
|
237
|
+
* - `subscription` is locked to `self` regardless of config
|
|
238
|
+
* ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — someone else's terms.
|
|
239
|
+
* - `metered` narrows to `self` unless the owner has explicitly acknowledged
|
|
240
|
+
* the spend ({@link MUSTS.METERED_DEFAULTS_SELF}) — their money.
|
|
241
|
+
* - `free` passes through — their electricity.
|
|
242
|
+
*
|
|
243
|
+
* Note the asymmetry: subscription can never be widened, metered can be
|
|
244
|
+
* widened deliberately. Conflating those was byollm_007's bug.
|
|
141
245
|
*/
|
|
142
|
-
declare function effectiveOfferScope(configured: OfferScope,
|
|
246
|
+
declare function effectiveOfferScope(configured: OfferScope, cost: BackendCost, spend?: SpendConsent): OfferScope;
|
|
143
247
|
/** The job-side facts a match needs. */
|
|
144
248
|
interface MatchJob {
|
|
145
249
|
/** The app's id for the user who enqueued the job. */
|
|
@@ -159,8 +263,10 @@ interface MatchDaemon {
|
|
|
159
263
|
readonly owner: string;
|
|
160
264
|
/** Effective scope of the backend that would run the job. */
|
|
161
265
|
readonly offerScope: OfferScope;
|
|
162
|
-
/**
|
|
163
|
-
readonly
|
|
266
|
+
/** Who pays for that backend's tokens. */
|
|
267
|
+
readonly cost: BackendCost;
|
|
268
|
+
/** What the owner agreed to spend on others, for a `metered` backend. */
|
|
269
|
+
readonly spend?: SpendConsent | undefined;
|
|
164
270
|
/**
|
|
165
271
|
* Does this daemon's *local* allowlist admit the given owner for the server
|
|
166
272
|
* origin the job came from? Supplied as a predicate so the protocol package
|
|
@@ -189,7 +295,7 @@ interface MatchDaemon {
|
|
|
189
295
|
* {
|
|
190
296
|
* owner: "bob",
|
|
191
297
|
* offerScope: "named",
|
|
192
|
-
*
|
|
298
|
+
* cost: "free",
|
|
193
299
|
* locallyAllows: (o) => o === "alice",
|
|
194
300
|
* },
|
|
195
301
|
* );
|
|
@@ -342,6 +448,7 @@ declare function isTerminal(state: JobState): boolean;
|
|
|
342
448
|
declare function canTransition(from: JobState, to: JobState): boolean;
|
|
343
449
|
/** A lease: the right to work on a job until `expiresAt`. */
|
|
344
450
|
declare const Lease: z.ZodObject<{
|
|
451
|
+
id: z.ZodString;
|
|
345
452
|
runnerId: z.ZodString;
|
|
346
453
|
expiresAt: z.ZodNumber;
|
|
347
454
|
}, z.core.$strip>;
|
|
@@ -396,8 +503,8 @@ declare const ClaimedJob: z.ZodObject<{
|
|
|
396
503
|
public: "public";
|
|
397
504
|
}>;
|
|
398
505
|
owner: z.ZodString;
|
|
399
|
-
audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
400
506
|
lease: z.ZodObject<{
|
|
507
|
+
id: z.ZodString;
|
|
401
508
|
runnerId: z.ZodString;
|
|
402
509
|
expiresAt: z.ZodNumber;
|
|
403
510
|
}, z.core.$strip>;
|
|
@@ -408,7 +515,7 @@ type ClaimedJob = z.infer<typeof ClaimedJob>;
|
|
|
408
515
|
*
|
|
409
516
|
* byollm_003 Rev 1: a `named`/`public` result is attacker-controlled text.
|
|
410
517
|
* The app must never render volunteer output as its own AI's answer without
|
|
411
|
-
* knowing that is what it is ({@link MUSTS.
|
|
518
|
+
* knowing that is what it is ({@link MUSTS.PROVENANCE_NAMES_DEVICE}).
|
|
412
519
|
*/
|
|
413
520
|
declare const ResultProvenance: z.ZodObject<{
|
|
414
521
|
audience: z.ZodEnum<{
|
|
@@ -437,6 +544,30 @@ declare function provenanceFor(input: {
|
|
|
437
544
|
backendClass: BackendClass;
|
|
438
545
|
model: string;
|
|
439
546
|
}): ResultProvenance;
|
|
547
|
+
/**
|
|
548
|
+
* What the daemon did, sealed with the answer — cloud_008 §2.5.
|
|
549
|
+
*
|
|
550
|
+
* These travelled in the clear on `ResultRequest`, which meant two things at
|
|
551
|
+
* once. On the direct plane the site believed unauthenticated fields beside
|
|
552
|
+
* an authenticated envelope — a daemon could seal one answer and *declare* it
|
|
553
|
+
* came from a different model, and only the field it did not sign would be
|
|
554
|
+
* recorded. Through a relay they reached a third party that acts on none of
|
|
555
|
+
* them, and `model` in particular is the kind of detail Amendment A's rule
|
|
556
|
+
* keeps off the wire.
|
|
557
|
+
*
|
|
558
|
+
* Sealed, they are the daemon's signed statement about its own run: the site
|
|
559
|
+
* opens them, nothing in between sees them, and the disposition check that
|
|
560
|
+
* already compares clear-text against ciphertext extends to cover them.
|
|
561
|
+
*/
|
|
562
|
+
declare const RunMetadata: z.ZodObject<{
|
|
563
|
+
model: z.ZodString;
|
|
564
|
+
backendClass: z.ZodEnum<{
|
|
565
|
+
http: "http";
|
|
566
|
+
process: "process";
|
|
567
|
+
}>;
|
|
568
|
+
durationMs: z.ZodNumber;
|
|
569
|
+
}, z.core.$strict>;
|
|
570
|
+
type RunMetadata = z.infer<typeof RunMetadata>;
|
|
440
571
|
/** Successful outcome. */
|
|
441
572
|
declare const JobResultOk: z.ZodObject<{
|
|
442
573
|
outcome: z.ZodLiteral<"ok">;
|
|
@@ -467,6 +598,37 @@ declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
467
598
|
outcome: z.ZodLiteral<"canceled">;
|
|
468
599
|
}, z.core.$strict>], "outcome">;
|
|
469
600
|
type JobOutcome = z.infer<typeof JobOutcome>;
|
|
601
|
+
/**
|
|
602
|
+
* The plaintext inside a result envelope.
|
|
603
|
+
*
|
|
604
|
+
* The outcome and how it was produced, together, because they are one
|
|
605
|
+
* statement by one signer. A site that opened only the outcome would be
|
|
606
|
+
* trusting the envelope for the answer and the request body for everything
|
|
607
|
+
* about it.
|
|
608
|
+
*/
|
|
609
|
+
declare const SealedOutcome: z.ZodObject<{
|
|
610
|
+
outcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
611
|
+
outcome: z.ZodLiteral<"ok">;
|
|
612
|
+
text: z.ZodString;
|
|
613
|
+
artifactUrl: z.ZodOptional<z.ZodURL>;
|
|
614
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
615
|
+
outcome: z.ZodLiteral<"error">;
|
|
616
|
+
code: z.ZodString;
|
|
617
|
+
message: z.ZodString;
|
|
618
|
+
retryable: z.ZodBoolean;
|
|
619
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
620
|
+
outcome: z.ZodLiteral<"canceled">;
|
|
621
|
+
}, z.core.$strict>], "outcome">;
|
|
622
|
+
ran: z.ZodObject<{
|
|
623
|
+
model: z.ZodString;
|
|
624
|
+
backendClass: z.ZodEnum<{
|
|
625
|
+
http: "http";
|
|
626
|
+
process: "process";
|
|
627
|
+
}>;
|
|
628
|
+
durationMs: z.ZodNumber;
|
|
629
|
+
}, z.core.$strict>;
|
|
630
|
+
}, z.core.$strict>;
|
|
631
|
+
type SealedOutcome = z.infer<typeof SealedOutcome>;
|
|
470
632
|
/** A completed job as delivered to the app, provenance attached. */
|
|
471
633
|
declare const DeliveredResult: z.ZodObject<{
|
|
472
634
|
jobId: z.ZodString;
|
|
@@ -508,6 +670,423 @@ declare const DeliveredResult: z.ZodObject<{
|
|
|
508
670
|
}, z.core.$strict>>;
|
|
509
671
|
}, z.core.$strict>;
|
|
510
672
|
type DeliveredResult = z.infer<typeof DeliveredResult>;
|
|
673
|
+
/**
|
|
674
|
+
* How big a payload is, in buckets — byollm_009 §6.
|
|
675
|
+
*
|
|
676
|
+
* A relay routes without reading, and matching a job to a machine needs some
|
|
677
|
+
* notion of size. Buckets rather than byte counts because the exact figure is
|
|
678
|
+
* a stronger fingerprint than the routing decision requires, and because a
|
|
679
|
+
* bucket survives compression and encoding changes that an exact count does
|
|
680
|
+
* not.
|
|
681
|
+
*
|
|
682
|
+
* `unbounded` exists for streamed jobs, which have no size when they start.
|
|
683
|
+
* It is reserved now rather than added later: byollm_009 §8.1 — adding a
|
|
684
|
+
* field to a published envelope is the v2 break all over again.
|
|
685
|
+
*/
|
|
686
|
+
declare const SizeClass: z.ZodEnum<{
|
|
687
|
+
small: "small";
|
|
688
|
+
medium: "medium";
|
|
689
|
+
large: "large";
|
|
690
|
+
unbounded: "unbounded";
|
|
691
|
+
}>;
|
|
692
|
+
type SizeClass = z.infer<typeof SizeClass>;
|
|
693
|
+
/** Where the bucket boundaries sit, in characters of payload text. */
|
|
694
|
+
declare const SIZE_CLASS_LIMITS: Readonly<{
|
|
695
|
+
small: 4000;
|
|
696
|
+
medium: 64000;
|
|
697
|
+
large: number;
|
|
698
|
+
}>;
|
|
699
|
+
/**
|
|
700
|
+
* The most a payload in this bucket can be.
|
|
701
|
+
*
|
|
702
|
+
* Used where a decision must be made from a stub, before the payload has been
|
|
703
|
+
* fetched — a budget check, for instance. Charging the bucket's ceiling is the
|
|
704
|
+
* conservative direction: it refuses slightly too eagerly rather than
|
|
705
|
+
* admitting work that turns out larger than the budget allowed.
|
|
706
|
+
*
|
|
707
|
+
* `unbounded` returns `Infinity`, which fails every ceiling. That is correct
|
|
708
|
+
* until byollm_006 defines how a streamed job is budgeted — failing closed on
|
|
709
|
+
* a case nobody has designed beats inventing an allowance for it.
|
|
710
|
+
*/
|
|
711
|
+
declare function sizeClassCeiling(sizeClass: SizeClass): number;
|
|
712
|
+
/** Bucket a payload by its text length. */
|
|
713
|
+
declare function sizeClassOf(textChars: number): SizeClass;
|
|
714
|
+
/**
|
|
715
|
+
* Everything an upstream may see about a job — byollm_009 §6.
|
|
716
|
+
*
|
|
717
|
+
* **This list is exhaustive and normative.** It is a commitment about the
|
|
718
|
+
* metadata surface, not an accident of what the implementation happens to
|
|
719
|
+
* send: an upstream that requires more has exceeded the protocol, and an
|
|
720
|
+
* endpoint that emits more has leaked past it
|
|
721
|
+
* ({@link MUSTS.STUB_METADATA_EXHAUSTIVE}).
|
|
722
|
+
*
|
|
723
|
+
* What is absent is the point. No payload, no model, no prompt, no result.
|
|
724
|
+
* `kind` is here because capability matching happens upstream; if a later
|
|
725
|
+
* revision moves matching to the daemon, `kind` moves into the ciphertext.
|
|
726
|
+
*/
|
|
727
|
+
declare const JobStub: z.ZodObject<{
|
|
728
|
+
id: z.ZodString;
|
|
729
|
+
kind: z.ZodEnum<{
|
|
730
|
+
"llm.generate": "llm.generate";
|
|
731
|
+
"llm.chat": "llm.chat";
|
|
732
|
+
}>;
|
|
733
|
+
owner: z.ZodString;
|
|
734
|
+
site: z.ZodString;
|
|
735
|
+
audience: z.ZodEnum<{
|
|
736
|
+
self: "self";
|
|
737
|
+
named: "named";
|
|
738
|
+
public: "public";
|
|
739
|
+
}>;
|
|
740
|
+
sizeClass: z.ZodEnum<{
|
|
741
|
+
small: "small";
|
|
742
|
+
medium: "medium";
|
|
743
|
+
large: "large";
|
|
744
|
+
unbounded: "unbounded";
|
|
745
|
+
}>;
|
|
746
|
+
streaming: z.ZodBoolean;
|
|
747
|
+
deadlineAt: z.ZodNumber;
|
|
748
|
+
}, z.core.$strict>;
|
|
749
|
+
type JobStub = z.infer<typeof JobStub>;
|
|
750
|
+
/** A stub, plus the lease the claiming runner now holds for it. */
|
|
751
|
+
declare const ClaimedStub: z.ZodObject<{
|
|
752
|
+
id: z.ZodString;
|
|
753
|
+
kind: z.ZodEnum<{
|
|
754
|
+
"llm.generate": "llm.generate";
|
|
755
|
+
"llm.chat": "llm.chat";
|
|
756
|
+
}>;
|
|
757
|
+
owner: z.ZodString;
|
|
758
|
+
site: z.ZodString;
|
|
759
|
+
audience: z.ZodEnum<{
|
|
760
|
+
self: "self";
|
|
761
|
+
named: "named";
|
|
762
|
+
public: "public";
|
|
763
|
+
}>;
|
|
764
|
+
sizeClass: z.ZodEnum<{
|
|
765
|
+
small: "small";
|
|
766
|
+
medium: "medium";
|
|
767
|
+
large: "large";
|
|
768
|
+
unbounded: "unbounded";
|
|
769
|
+
}>;
|
|
770
|
+
streaming: z.ZodBoolean;
|
|
771
|
+
deadlineAt: z.ZodNumber;
|
|
772
|
+
lease: z.ZodObject<{
|
|
773
|
+
id: z.ZodString;
|
|
774
|
+
runnerId: z.ZodString;
|
|
775
|
+
expiresAt: z.ZodNumber;
|
|
776
|
+
}, z.core.$strip>;
|
|
777
|
+
}, z.core.$strict>;
|
|
778
|
+
type ClaimedStub = z.infer<typeof ClaimedStub>;
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Device and site keys — byollm_009 §3.
|
|
782
|
+
*
|
|
783
|
+
* **Two keypairs per party, and the split is load-bearing.** An Ed25519
|
|
784
|
+
* *identity* key signs; an X25519 *encryption* key receives sealed envelopes.
|
|
785
|
+
* The encryption key is signed by the identity key, and **the identity key is
|
|
786
|
+
* what gets pinned**. So "who sent this" and "who can read this" are answered
|
|
787
|
+
* by different keys — which is what lets an encryption key rotate without
|
|
788
|
+
* re-establishing trust, and what byollm_009 §6's signed-then-sealed envelope
|
|
789
|
+
* depends on.
|
|
790
|
+
*
|
|
791
|
+
* **No new dependency.** byollm_009 §2 says established primitives only, via
|
|
792
|
+
* libsodium. Everything *this* module needs — Ed25519 signing, X25519 key
|
|
793
|
+
* generation — Node provides natively, and using it costs nothing and adds no
|
|
794
|
+
* install weight to a daemon that must land fast on a stranger's laptop.
|
|
795
|
+
*
|
|
796
|
+
* libsodium becomes necessary at envelope v2, where sealing does. That is a
|
|
797
|
+
* real dependency decision and it belongs in the change that needs it: a
|
|
798
|
+
* sealed box is a specific reviewed construction, and rebuilding it out of
|
|
799
|
+
* Node primitives is exactly the "novel construction" §2 rules out. Deferring
|
|
800
|
+
* the dependency is not the same as deferring the rule.
|
|
801
|
+
*/
|
|
802
|
+
/** A public identity, as it travels on the wire. All values base64url. */
|
|
803
|
+
declare const PublicIdentity: z.ZodObject<{
|
|
804
|
+
identity: z.ZodString;
|
|
805
|
+
encryption: z.ZodString;
|
|
806
|
+
encryptionSig: z.ZodString;
|
|
807
|
+
}, z.core.$strict>;
|
|
808
|
+
type PublicIdentity = z.infer<typeof PublicIdentity>;
|
|
809
|
+
/** Private key material, as stored on disk. Never leaves the machine. */
|
|
810
|
+
declare const StoredKeys: z.ZodObject<{
|
|
811
|
+
version: z.ZodLiteral<1>;
|
|
812
|
+
identityPublic: z.ZodString;
|
|
813
|
+
identityPrivate: z.ZodString;
|
|
814
|
+
encryptionPublic: z.ZodString;
|
|
815
|
+
encryptionPrivate: z.ZodString;
|
|
816
|
+
encryptionSig: z.ZodString;
|
|
817
|
+
createdAt: z.ZodNumber;
|
|
818
|
+
}, z.core.$strict>;
|
|
819
|
+
type StoredKeys = z.infer<typeof StoredKeys>;
|
|
820
|
+
/** Generate a fresh pair of keypairs and bind them together. */
|
|
821
|
+
declare function generateKeys(now: number): StoredKeys;
|
|
822
|
+
/** The public half, for the wire. */
|
|
823
|
+
declare function publicIdentityOf(keys: StoredKeys): PublicIdentity;
|
|
824
|
+
/**
|
|
825
|
+
* Check that an encryption key really belongs to the identity presenting it.
|
|
826
|
+
*
|
|
827
|
+
* Called on everything received, including from an upstream we otherwise
|
|
828
|
+
* trust — the point of pinning the identity is that nothing else needs to be
|
|
829
|
+
* trusted, and that only holds if this is checked every time rather than at
|
|
830
|
+
* first sight.
|
|
831
|
+
*/
|
|
832
|
+
declare function verifyPublicIdentity(identity: PublicIdentity): boolean;
|
|
833
|
+
/** Sign arbitrary bytes with an identity key. */
|
|
834
|
+
declare function signWith(keys: StoredKeys, data: Uint8Array): string;
|
|
835
|
+
/** Verify bytes against a raw Ed25519 public key. */
|
|
836
|
+
declare function verifyWith(identityPublic: string, data: Uint8Array, signature: string): boolean;
|
|
837
|
+
/**
|
|
838
|
+
* A fingerprint a human can compare out loud.
|
|
839
|
+
*
|
|
840
|
+
* 120 bits of SHA-256 over the raw identity key, as six groups of four. Long
|
|
841
|
+
* enough that grinding a colliding key is not worth anyone's afternoon, short
|
|
842
|
+
* enough to read down a phone line — which is the whole point. A fingerprint
|
|
843
|
+
* nobody can be bothered to compare provides no security at all, so
|
|
844
|
+
* legibility is a security property here, not a nicety.
|
|
845
|
+
*
|
|
846
|
+
* Formatted with a `BYOLLM-` prefix so a pasted fingerprint is recognisable
|
|
847
|
+
* out of context, in a support thread or a screenshot.
|
|
848
|
+
*/
|
|
849
|
+
declare function fingerprint(identityPublic: string): string;
|
|
850
|
+
/** The short id used in envelopes and provenance. Stable, and comparable. */
|
|
851
|
+
declare const keyId: (identityPublic: string) => string;
|
|
852
|
+
|
|
853
|
+
declare function cryptoReady(): Promise<void>;
|
|
854
|
+
/**
|
|
855
|
+
* How long a sealed payload is worth keeping, from creation.
|
|
856
|
+
*
|
|
857
|
+
* Bound into every envelope and recomputed when one is opened, so it lives
|
|
858
|
+
* here rather than in the two places that need it. Two copies of a value the
|
|
859
|
+
* signature depends on is the same bug as two clock readings: it works until
|
|
860
|
+
* they disagree, and then nothing can be opened.
|
|
861
|
+
*
|
|
862
|
+
* Not a job's TTL. That answers how long the *work* is worth doing, belongs
|
|
863
|
+
* to the app and the store, and may legitimately differ per deployment.
|
|
864
|
+
*/
|
|
865
|
+
declare const ENVELOPE_MAX_AGE_MS: number;
|
|
866
|
+
/** Which leg an envelope belongs to. Bound into the signature. */
|
|
867
|
+
declare const EnvelopeDirection: z.ZodEnum<{
|
|
868
|
+
payload: "payload";
|
|
869
|
+
result: "result";
|
|
870
|
+
}>;
|
|
871
|
+
type EnvelopeDirection = z.infer<typeof EnvelopeDirection>;
|
|
872
|
+
declare const SealedEnvelope: z.ZodObject<{
|
|
873
|
+
ciphertext: z.ZodString;
|
|
874
|
+
recipientKeyId: z.ZodString;
|
|
875
|
+
senderKeyId: z.ZodString;
|
|
876
|
+
direction: z.ZodEnum<{
|
|
877
|
+
payload: "payload";
|
|
878
|
+
result: "result";
|
|
879
|
+
}>;
|
|
880
|
+
deadlineAt: z.ZodNumber;
|
|
881
|
+
}, z.core.$strict>;
|
|
882
|
+
type SealedEnvelope = z.infer<typeof SealedEnvelope>;
|
|
883
|
+
/** Everything the signature covers besides the plaintext itself. */
|
|
884
|
+
interface EnvelopeContext {
|
|
885
|
+
readonly jobId: string;
|
|
886
|
+
readonly senderKeyId: string;
|
|
887
|
+
readonly recipientKeyId: string;
|
|
888
|
+
readonly deadlineAt: number;
|
|
889
|
+
readonly direction: EnvelopeDirection;
|
|
890
|
+
}
|
|
891
|
+
/** Seal a plaintext to a recipient, signed by the sender's identity. */
|
|
892
|
+
declare function seal(input: {
|
|
893
|
+
plaintext: string;
|
|
894
|
+
senderKeys: StoredKeys;
|
|
895
|
+
recipientEncryptionPublic: string;
|
|
896
|
+
context: EnvelopeContext;
|
|
897
|
+
}): Promise<SealedEnvelope>;
|
|
898
|
+
/** Why an envelope was refused. Never distinguished to a remote caller. */
|
|
899
|
+
type EnvelopeFailure = "not-for-us" | "unopenable" | "malformed" | "bad-signature" | "context-mismatch";
|
|
900
|
+
type OpenResult = {
|
|
901
|
+
readonly ok: true;
|
|
902
|
+
readonly plaintext: string;
|
|
903
|
+
} | {
|
|
904
|
+
readonly ok: false;
|
|
905
|
+
readonly reason: EnvelopeFailure;
|
|
906
|
+
};
|
|
907
|
+
/**
|
|
908
|
+
* Open an envelope and verify it came from the pinned sender.
|
|
909
|
+
*
|
|
910
|
+
* Every failure returns rather than throws: this runs on input from the
|
|
911
|
+
* network, and a crash here is a denial of service on the delivery path.
|
|
912
|
+
*
|
|
913
|
+
* The context is checked against the signature, not merely read from the
|
|
914
|
+
* envelope. An envelope carries its own claims about who sent it and to
|
|
915
|
+
* whom — believing those would authenticate the attacker's assertion rather
|
|
916
|
+
* than the sender's key.
|
|
917
|
+
*/
|
|
918
|
+
declare function open(input: {
|
|
919
|
+
envelope: SealedEnvelope;
|
|
920
|
+
recipientKeys: StoredKeys;
|
|
921
|
+
senderIdentityPublic: string;
|
|
922
|
+
/** The deadline is taken from the envelope and checked against its signature. */
|
|
923
|
+
expected: Omit<EnvelopeContext, "deadlineAt">;
|
|
924
|
+
}): Promise<OpenResult>;
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Request signing — byollm_009 §4.2.
|
|
928
|
+
*
|
|
929
|
+
* Every authenticated call is signed by the calling device's identity key.
|
|
930
|
+
* There is no bearer token on the daemon plane: possession of a file no
|
|
931
|
+
* longer grants access, possession of a *key* does, and the key never leaves
|
|
932
|
+
* the machine.
|
|
933
|
+
*
|
|
934
|
+
* ## Why this is not the server-issued nonce the spec first described
|
|
935
|
+
*
|
|
936
|
+
* byollm_009 §4.2 says "the upstream issues a nonce; the daemon signs it".
|
|
937
|
+
* Implementing that costs one of two things: a round trip before every
|
|
938
|
+
* request, or server-side session state — and sessions reintroduce a bearer
|
|
939
|
+
* credential, which is the thing being removed.
|
|
940
|
+
*
|
|
941
|
+
* Signing *the request itself* gets the same property without either, because
|
|
942
|
+
* of something the protocol already guarantees. A captured signature is valid
|
|
943
|
+
* only for the exact request it covers — same endpoint, same runner, same
|
|
944
|
+
* body — and every authenticated endpoint here is idempotent by design:
|
|
945
|
+
* `RESULT_IDEMPOTENT` makes a replayed result a no-op, a replayed claim from
|
|
946
|
+
* the same runner returns what that runner already holds, and heartbeat and
|
|
947
|
+
* release are idempotent in effect. So a replay inside the freshness window
|
|
948
|
+
* gains an attacker nothing they could not obtain by forwarding the original,
|
|
949
|
+
* which a relay can do anyway.
|
|
950
|
+
*
|
|
951
|
+
* That is the whole argument, and it is worth stating because it rests
|
|
952
|
+
* entirely on the endpoints being idempotent. Two ways that can fail, and the
|
|
953
|
+
* second is the one that actually bit:
|
|
954
|
+
*
|
|
955
|
+
* 1. **A future endpoint that is not idempotent cannot use this scheme
|
|
956
|
+
* unchanged** — it would need a server-issued nonce.
|
|
957
|
+
* 2. **Idempotence must hold per *addressed instance*, not per endpoint.** A
|
|
958
|
+
* request that names a mutable target — a lease, a session, a
|
|
959
|
+
* subscription — must name the *instance*, or a replay lands on a
|
|
960
|
+
* different one than the sender meant and the endpoint's idempotence buys
|
|
961
|
+
* nothing. `release` was idempotent per lease and ambiguous across them:
|
|
962
|
+
* it named a job and a runner, both of which survive a
|
|
963
|
+
* claim-release-reclaim cycle, so a replayed release yanked a later grant.
|
|
964
|
+
* Fixed by giving a lease its own id and requiring it.
|
|
965
|
+
*
|
|
966
|
+
* The rule for anything added later: if a signed request can be replayed onto
|
|
967
|
+
* a target that has changed underneath it, the request has to say which
|
|
968
|
+
* target it meant.
|
|
969
|
+
*/
|
|
970
|
+
/** How far a request's timestamp may be from the server's clock. */
|
|
971
|
+
declare const MAX_CLOCK_SKEW_MS = 120000;
|
|
972
|
+
/** The signed material a request carries. */
|
|
973
|
+
declare const RequestSignature: z.ZodObject<{
|
|
974
|
+
runnerId: z.ZodString;
|
|
975
|
+
issuedAt: z.ZodNumber;
|
|
976
|
+
signature: z.ZodString;
|
|
977
|
+
}, z.core.$strict>;
|
|
978
|
+
type RequestSignature = z.infer<typeof RequestSignature>;
|
|
979
|
+
/**
|
|
980
|
+
* The exact bytes both sides sign and verify.
|
|
981
|
+
*
|
|
982
|
+
* Newline-separated with a version prefix and a domain separator. Every field
|
|
983
|
+
* that decides what the request *does* is in here: leave one out and it
|
|
984
|
+
* becomes something an intermediary can change without breaking the
|
|
985
|
+
* signature.
|
|
986
|
+
*
|
|
987
|
+
* The body is included by hash rather than by value, so signing does not
|
|
988
|
+
* depend on both sides serialising JSON identically — which they would not.
|
|
989
|
+
*/
|
|
990
|
+
declare function canonicalRequest(input: {
|
|
991
|
+
endpoint: string;
|
|
992
|
+
runnerId: string;
|
|
993
|
+
issuedAt: number;
|
|
994
|
+
body: string;
|
|
995
|
+
}): Buffer;
|
|
996
|
+
/** Sign an outgoing request with this machine's identity key. */
|
|
997
|
+
declare function signRequest(keys: StoredKeys, input: {
|
|
998
|
+
endpoint: string;
|
|
999
|
+
runnerId: string;
|
|
1000
|
+
issuedAt: number;
|
|
1001
|
+
body: string;
|
|
1002
|
+
}): RequestSignature;
|
|
1003
|
+
/**
|
|
1004
|
+
* The same scheme, for the party at the other end: a **site** calling a relay.
|
|
1005
|
+
*
|
|
1006
|
+
* A site talking to a relay is in exactly the daemon's position — an outbound
|
|
1007
|
+
* caller with an identity keypair the other side already pins — so it gets the
|
|
1008
|
+
* daemon's authentication rather than a second scheme. Bearer tokens for the
|
|
1009
|
+
* site plane were the alternative, and they would have reintroduced the
|
|
1010
|
+
* credential-in-a-file that §4.2 removed from the daemon plane, on the plane
|
|
1011
|
+
* that carries *every* site's traffic.
|
|
1012
|
+
*
|
|
1013
|
+
* Two things make this safe to build on the same canonical string:
|
|
1014
|
+
*
|
|
1015
|
+
* 1. **The endpoint is namespaced.** Site endpoints sign `site/enqueue`, never
|
|
1016
|
+
* `enqueue`. The daemon plane's `result` and the site plane's `results` are
|
|
1017
|
+
* one character apart, and a naming collision between planes must not be
|
|
1018
|
+
* what stands between a signature and a replay onto the wrong handler. The
|
|
1019
|
+
* prefix is applied *inside* these helpers, so the two ends cannot disagree
|
|
1020
|
+
* about it — the alternative is two implementations of one bound value,
|
|
1021
|
+
* which is this project's most-repeated bug.
|
|
1022
|
+
* 2. **The caller slot carries the site id.** `canonicalRequest` names that
|
|
1023
|
+
* field `runnerId` because the daemon plane got there first; here it holds
|
|
1024
|
+
* the site id, and the verifier looks the key up in the projection's site
|
|
1025
|
+
* registry rather than its device registry. The two registries never share
|
|
1026
|
+
* an entry, so a device signature cannot authenticate as a site.
|
|
1027
|
+
*
|
|
1028
|
+
* §4.2's replay argument carries over **only because the site plane's writes
|
|
1029
|
+
* are idempotent per addressed instance**, which is a property that had to be
|
|
1030
|
+
* built rather than found: `enqueue` reset a job of the same id, so a replayed
|
|
1031
|
+
* enqueue inside the freshness window returned a claimed job to the queue and
|
|
1032
|
+
* threw away a device's live lease. Identical in shape to the `release` bug
|
|
1033
|
+
* above, on the other plane. Anything added to the site plane later must be
|
|
1034
|
+
* idempotent by the instance it names, or this scheme does not cover it.
|
|
1035
|
+
*/
|
|
1036
|
+
declare function signSiteRequest(keys: StoredKeys, input: {
|
|
1037
|
+
endpoint: string;
|
|
1038
|
+
siteId: string;
|
|
1039
|
+
issuedAt: number;
|
|
1040
|
+
body: string;
|
|
1041
|
+
}): RequestSignature;
|
|
1042
|
+
/** Verify a site's call against the identity the control plane registered. */
|
|
1043
|
+
declare function verifySiteRequest(input: {
|
|
1044
|
+
identityPublic: string;
|
|
1045
|
+
endpoint: string;
|
|
1046
|
+
body: string;
|
|
1047
|
+
signature: RequestSignature;
|
|
1048
|
+
now: number;
|
|
1049
|
+
maxSkewMs?: number;
|
|
1050
|
+
}): SignatureFailure | null;
|
|
1051
|
+
/**
|
|
1052
|
+
* Why a signed request was refused.
|
|
1053
|
+
*
|
|
1054
|
+
* **`bad-signature` is never returned verbatim; `stale` is, deliberately.**
|
|
1055
|
+
* They are different kinds of refusal and conflating them costs a real user
|
|
1056
|
+
* more than it costs an attacker.
|
|
1057
|
+
*
|
|
1058
|
+
* A bad signature is an authentication failure and the server says only
|
|
1059
|
+
* "unauthorized" — telling a prober which part they got wrong is free help.
|
|
1060
|
+
*
|
|
1061
|
+
* A stale timestamp is a **precondition** failure: the signature may be
|
|
1062
|
+
* perfectly valid and the caller's clock is simply wrong. Saying so reveals
|
|
1063
|
+
* nothing, for two reasons that both have to hold. The server's time is
|
|
1064
|
+
* already public — every response carries a `Date` header and the heartbeat
|
|
1065
|
+
* response returns `serverTime` outright. And freshness is checked *before*
|
|
1066
|
+
* the signature is verified, so a stale answer says nothing about whether the
|
|
1067
|
+
* signature was any good.
|
|
1068
|
+
*
|
|
1069
|
+
* What conflating them costs: a machine whose clock has drifted gets
|
|
1070
|
+
* `401 unauthorized` on every request, forever, with nothing anywhere pointing
|
|
1071
|
+
* at the clock. That is the shape byollm_013 was filed about — a refusal that
|
|
1072
|
+
* is correct, silent, and sends somebody to read our source.
|
|
1073
|
+
*/
|
|
1074
|
+
type SignatureFailure = "stale" | "bad-signature";
|
|
1075
|
+
/**
|
|
1076
|
+
* Verify a signed request against a runner's pinned identity key.
|
|
1077
|
+
*
|
|
1078
|
+
* Freshness is checked in **both** directions. A clock far ahead is as much a
|
|
1079
|
+
* problem as one behind: it would let a captured request stay replayable long
|
|
1080
|
+
* after it was made, which is the one thing the window exists to bound.
|
|
1081
|
+
*/
|
|
1082
|
+
declare function verifyRequest(input: {
|
|
1083
|
+
identityPublic: string;
|
|
1084
|
+
endpoint: string;
|
|
1085
|
+
body: string;
|
|
1086
|
+
signature: RequestSignature;
|
|
1087
|
+
now: number;
|
|
1088
|
+
maxSkewMs?: number;
|
|
1089
|
+
}): SignatureFailure | null;
|
|
511
1090
|
|
|
512
1091
|
/**
|
|
513
1092
|
* The normative MUSTs of protocol v0, as data.
|
|
@@ -524,6 +1103,44 @@ type DeliveredResult = z.infer<typeof DeliveredResult>;
|
|
|
524
1103
|
*/
|
|
525
1104
|
/** Which side of the wire is obliged to enforce a given MUST. */
|
|
526
1105
|
type MustEnforcer = "daemon" | "server" | "both";
|
|
1106
|
+
/**
|
|
1107
|
+
* How a MUST is actually verified — which is not the same question as who
|
|
1108
|
+
* enforces it, and is the one that decides what "byollm-compatible" means.
|
|
1109
|
+
*
|
|
1110
|
+
* The conformance kit's credibility rests on an implicit claim that every
|
|
1111
|
+
* MUST is checkable. Ten of them were not, and the kit reported that honestly
|
|
1112
|
+
* while nothing acted on it. Making the kind explicit turns "uncovered" from
|
|
1113
|
+
* a number needing a paragraph of explanation into a number that should be
|
|
1114
|
+
* zero.
|
|
1115
|
+
*
|
|
1116
|
+
* - `conformance` — the kit asserts it against *any* implementation. This is
|
|
1117
|
+
* the strong kind: a third party runs the suite and learns something.
|
|
1118
|
+
* - `adversarial` — proved by the reference daemon's own suites in this repo
|
|
1119
|
+
* (the hostile-payload corpus, or its unit tests). Real verification, and
|
|
1120
|
+
* it runs in CI — but it proves things about *our* daemon, not about
|
|
1121
|
+
* someone else's, so the kit cannot carry it.
|
|
1122
|
+
* - `construction` — true by the shape of the code, where a test could only
|
|
1123
|
+
* sample. A reviewer verifies it; a suite cannot.
|
|
1124
|
+
* ## When a MUST binds both sides — cloud_008 Tier 3
|
|
1125
|
+
*
|
|
1126
|
+
* `AUDIENCE_BOTH_SIDES` says the server and the daemon each enforce. The kit
|
|
1127
|
+
* passed **entirely** with the server's half deleted: every check drove a real
|
|
1128
|
+
* daemon, and a daemon refuses locally, so "the job did not run" looked
|
|
1129
|
+
* identical whichever side refused it. A full-honest-stack test proves only
|
|
1130
|
+
* the conjunction.
|
|
1131
|
+
*
|
|
1132
|
+
* So a `both`-enforced MUST needs **one check per party, each with the honest
|
|
1133
|
+
* counterpart removed** — C032 claims over the raw protocol precisely so no
|
|
1134
|
+
* daemon admission logic runs. Where a check strips one side, its comment
|
|
1135
|
+
* says which; where a MUST is enforced by both and only one side is checked,
|
|
1136
|
+
* that is a gap rather than coverage.
|
|
1137
|
+
*
|
|
1138
|
+
* - `operator` — a claim about how someone runs a deployment, verifiable only
|
|
1139
|
+
* by audit or by reading source. The honest category, and the one that
|
|
1140
|
+
* exists so a property nobody can check from outside is *labelled* as such
|
|
1141
|
+
* rather than laundered by association with the checkable ones.
|
|
1142
|
+
*/
|
|
1143
|
+
type MustVerification = "conformance" | "adversarial" | "construction" | "operator";
|
|
527
1144
|
/** A single normative requirement of the protocol. */
|
|
528
1145
|
interface Must {
|
|
529
1146
|
/** Stable public id, cited by conformance output. */
|
|
@@ -532,6 +1149,11 @@ interface Must {
|
|
|
532
1149
|
readonly statement: string;
|
|
533
1150
|
/** Which implementation is obliged to enforce it. */
|
|
534
1151
|
readonly enforcedBy: MustEnforcer;
|
|
1152
|
+
/**
|
|
1153
|
+
* How this is verified. `conformance` is the only kind the kit can assert;
|
|
1154
|
+
* see {@link MustVerification} for why the others exist.
|
|
1155
|
+
*/
|
|
1156
|
+
readonly verifiedBy: MustVerification;
|
|
535
1157
|
/** Spec section this was adjudicated in. */
|
|
536
1158
|
readonly source: string;
|
|
537
1159
|
}
|
|
@@ -547,6 +1169,12 @@ declare const MUSTS: Readonly<{
|
|
|
547
1169
|
readonly PAIR_ONE_USER: Must;
|
|
548
1170
|
readonly PAIR_INTERACTIVE: Must;
|
|
549
1171
|
readonly PAIR_CODE_EXPIRES: Must;
|
|
1172
|
+
readonly VERSION_HANDSHAKE_REQUIRED: Must;
|
|
1173
|
+
readonly KEYS_EXCHANGED_AT_CONSENT: Must;
|
|
1174
|
+
readonly REQUESTS_SIGNED_NOT_BEARER: Must;
|
|
1175
|
+
readonly LEASE_SCOPED_BY_GRANT: Must;
|
|
1176
|
+
readonly STUB_METADATA_EXHAUSTIVE: Must;
|
|
1177
|
+
readonly ENVELOPE_SEALED_AND_SIGNED: Must;
|
|
550
1178
|
readonly KIND_TYPED_ONLY: Must;
|
|
551
1179
|
readonly KIND_NO_CODE: Must;
|
|
552
1180
|
readonly CLAIM_REQUIRES_CAPABILITY: Must;
|
|
@@ -556,6 +1184,10 @@ declare const MUSTS: Readonly<{
|
|
|
556
1184
|
readonly LEASE_RECLAIMABLE: Must;
|
|
557
1185
|
readonly AUDIENCE_BOTH_SIDES: Must;
|
|
558
1186
|
readonly SUBSCRIPTION_SELF_LOCK: Must;
|
|
1187
|
+
readonly METERED_DEFAULTS_SELF: Must;
|
|
1188
|
+
readonly METERED_REQUIRES_CEILING: Must;
|
|
1189
|
+
readonly COST_NOT_CONFIGURABLE: Must;
|
|
1190
|
+
readonly REMOTE_IS_NEVER_FREE: Must;
|
|
559
1191
|
readonly NAMED_LOCAL_ALLOWLIST: Must;
|
|
560
1192
|
readonly REFUSAL_NOT_REOFFERED: Must;
|
|
561
1193
|
readonly REVOCATION_HONORED: Must;
|
|
@@ -564,7 +1196,7 @@ declare const MUSTS: Readonly<{
|
|
|
564
1196
|
readonly TTL_EXPIRY: Must;
|
|
565
1197
|
readonly NO_RUNNER_SIGNAL: Must;
|
|
566
1198
|
readonly RESULT_IDEMPOTENT: Must;
|
|
567
|
-
readonly
|
|
1199
|
+
readonly PROVENANCE_NAMES_DEVICE: Must;
|
|
568
1200
|
readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
|
|
569
1201
|
readonly NO_SHELL_INTERPOLATION: Must;
|
|
570
1202
|
readonly NO_PAYLOAD_ROUTING: Must;
|
|
@@ -572,18 +1204,75 @@ declare const MUSTS: Readonly<{
|
|
|
572
1204
|
readonly HTTP_BASE_URL_SAFE: Must;
|
|
573
1205
|
readonly OUTPUT_INERT: Must;
|
|
574
1206
|
readonly COMMUNITY_BUDGETS: Must;
|
|
1207
|
+
readonly REVOCATION_IMMEDIATE: Must;
|
|
1208
|
+
readonly CONSENT_BEFORE_ROUTE: Must;
|
|
1209
|
+
readonly ROSTER_NOT_DISCLOSED: Must;
|
|
1210
|
+
readonly EFFECTIVE_OFFER_ONLY: Must;
|
|
1211
|
+
readonly FALLBACK_LABELED: Must;
|
|
1212
|
+
readonly RELAY_BLIND: Must;
|
|
1213
|
+
readonly SHARED_COMPUTE_DISCLOSED: Must;
|
|
575
1214
|
}>;
|
|
576
1215
|
/** The id of any normative MUST. */
|
|
577
1216
|
type MustId = keyof typeof MUSTS;
|
|
578
1217
|
/** All MUST ids, for coverage checks. */
|
|
579
|
-
declare const MUST_IDS: readonly ("PAIR_ONE_USER" | "PAIR_INTERACTIVE" | "PAIR_CODE_EXPIRES" | "KIND_TYPED_ONLY" | "KIND_NO_CODE" | "CLAIM_REQUIRES_CAPABILITY" | "CAPABILITY_IS_DETECTED" | "CLAIM_ATOMIC" | "LEASE_HONORED" | "LEASE_RECLAIMABLE" | "AUDIENCE_BOTH_SIDES" | "SUBSCRIPTION_SELF_LOCK" | "NAMED_LOCAL_ALLOWLIST" | "REFUSAL_NOT_REOFFERED" | "REVOCATION_HONORED" | "CANCEL_HONORED" | "DEPENDS_ON_GATING" | "TTL_EXPIRY" | "NO_RUNNER_SIGNAL" | "RESULT_IDEMPOTENT" | "
|
|
1218
|
+
declare const MUST_IDS: readonly ("PAIR_ONE_USER" | "PAIR_INTERACTIVE" | "PAIR_CODE_EXPIRES" | "VERSION_HANDSHAKE_REQUIRED" | "KEYS_EXCHANGED_AT_CONSENT" | "REQUESTS_SIGNED_NOT_BEARER" | "LEASE_SCOPED_BY_GRANT" | "STUB_METADATA_EXHAUSTIVE" | "ENVELOPE_SEALED_AND_SIGNED" | "KIND_TYPED_ONLY" | "KIND_NO_CODE" | "CLAIM_REQUIRES_CAPABILITY" | "CAPABILITY_IS_DETECTED" | "CLAIM_ATOMIC" | "LEASE_HONORED" | "LEASE_RECLAIMABLE" | "AUDIENCE_BOTH_SIDES" | "SUBSCRIPTION_SELF_LOCK" | "METERED_DEFAULTS_SELF" | "METERED_REQUIRES_CEILING" | "COST_NOT_CONFIGURABLE" | "REMOTE_IS_NEVER_FREE" | "NAMED_LOCAL_ALLOWLIST" | "REFUSAL_NOT_REOFFERED" | "REVOCATION_HONORED" | "CANCEL_HONORED" | "DEPENDS_ON_GATING" | "TTL_EXPIRY" | "NO_RUNNER_SIGNAL" | "RESULT_IDEMPOTENT" | "PROVENANCE_NAMES_DEVICE" | "INGRESS_LOGGED_BEFORE_EXECUTION" | "NO_SHELL_INTERPOLATION" | "NO_PAYLOAD_ROUTING" | "STRIPPED_CHILD_ENV" | "HTTP_BASE_URL_SAFE" | "OUTPUT_INERT" | "COMMUNITY_BUDGETS" | "REVOCATION_IMMEDIATE" | "CONSENT_BEFORE_ROUTE" | "ROSTER_NOT_DISCLOSED" | "EFFECTIVE_OFFER_ONLY" | "FALLBACK_LABELED" | "RELAY_BLIND" | "SHARED_COMPUTE_DISCLOSED")[];
|
|
1219
|
+
/** Every MUST verified a particular way. */
|
|
1220
|
+
declare function mustsVerifiedBy(kind: MustVerification): MustId[];
|
|
580
1221
|
|
|
581
1222
|
/** Protocol version carried on every request; servers refuse what they can't speak. */
|
|
582
1223
|
declare const PROTOCOL_VERSION: "0";
|
|
1224
|
+
/**
|
|
1225
|
+
* Every protocol version this build can serve, **oldest first**.
|
|
1226
|
+
*
|
|
1227
|
+
* One entry today. It is a list rather than a constant because the shape of
|
|
1228
|
+
* the check is the point: a server supporting two versions through a
|
|
1229
|
+
* migration should not need a different code path from one supporting one.
|
|
1230
|
+
*/
|
|
1231
|
+
declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
|
|
1232
|
+
/**
|
|
1233
|
+
* The oldest version this build will talk to — derived, not declared.
|
|
1234
|
+
*
|
|
1235
|
+
* Stating it separately would be a second thing to keep in step with the list
|
|
1236
|
+
* above, and the failure would be silent: a minimum that no longer matches
|
|
1237
|
+
* what is supported produces a refusal naming a version the server would in
|
|
1238
|
+
* fact have accepted.
|
|
1239
|
+
*/
|
|
1240
|
+
declare const MIN_PROTOCOL_VERSION: string;
|
|
1241
|
+
/** A structured refusal, so a daemon can say something useful to its owner. */
|
|
1242
|
+
interface VersionRefusal {
|
|
1243
|
+
readonly error: "unsupported-protocol-version";
|
|
1244
|
+
readonly message: string;
|
|
1245
|
+
readonly supported: readonly string[];
|
|
1246
|
+
readonly minimum: string;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Check the protocol version on an incoming request
|
|
1250
|
+
* ({@link MUSTS.VERSION_HANDSHAKE_REQUIRED}).
|
|
1251
|
+
*
|
|
1252
|
+
* Returns a refusal, or `null` to proceed.
|
|
1253
|
+
*
|
|
1254
|
+
* **A missing version is refused the same way a wrong one is.** That is the
|
|
1255
|
+
* half worth stating: before this existed, the version travelled as a
|
|
1256
|
+
* `z.literal` inside each endpoint's schema, so a mismatch surfaced as a
|
|
1257
|
+
* generic `bad-request` — a daemon and a server discovered they disagreed by
|
|
1258
|
+
* failing, with nothing in the response naming the disagreement. An error a
|
|
1259
|
+
* user cannot act on is barely better than a hang.
|
|
1260
|
+
*
|
|
1261
|
+
* The message names the fix, because the person reading it is usually the one
|
|
1262
|
+
* who has to apply it.
|
|
1263
|
+
*/
|
|
1264
|
+
declare function checkProtocolVersion(body: unknown): VersionRefusal | null;
|
|
583
1265
|
/** The path prefix all endpoints mount under. */
|
|
584
1266
|
declare const PROTOCOL_PREFIX: "/byollm";
|
|
585
|
-
/**
|
|
586
|
-
|
|
1267
|
+
/**
|
|
1268
|
+
* The endpoint names, in the order byollm_001 lists them, plus `fetch`.
|
|
1269
|
+
*
|
|
1270
|
+
* `fetch` is byollm_009 §6's second phase: a claim returns a stub, and the
|
|
1271
|
+
* payload is collected separately by the device that took it. Two steps
|
|
1272
|
+
* rather than one because a payload can only be sealed once its recipient is
|
|
1273
|
+
* known — which is also what makes multi-device free.
|
|
1274
|
+
*/
|
|
1275
|
+
declare const ENDPOINTS: readonly ["pair", "claim", "fetch", "heartbeat", "result", "release"];
|
|
587
1276
|
type Endpoint = (typeof ENDPOINTS)[number];
|
|
588
1277
|
/**
|
|
589
1278
|
* One entry of the capability matrix: a kind this daemon can actually serve,
|
|
@@ -601,6 +1290,22 @@ declare const Capability: z.ZodObject<{
|
|
|
601
1290
|
"llm.chat": "llm.chat";
|
|
602
1291
|
}>;
|
|
603
1292
|
backendId: z.ZodEnum<{
|
|
1293
|
+
ollama: "ollama";
|
|
1294
|
+
mlx: "mlx";
|
|
1295
|
+
llamacpp: "llamacpp";
|
|
1296
|
+
vllm: "vllm";
|
|
1297
|
+
lmstudio: "lmstudio";
|
|
1298
|
+
jan: "jan";
|
|
1299
|
+
localai: "localai";
|
|
1300
|
+
anthropic: "anthropic";
|
|
1301
|
+
openai: "openai";
|
|
1302
|
+
gemini: "gemini";
|
|
1303
|
+
grok: "grok";
|
|
1304
|
+
groq: "groq";
|
|
1305
|
+
openrouter: "openrouter";
|
|
1306
|
+
together: "together";
|
|
1307
|
+
deepseek: "deepseek";
|
|
1308
|
+
mistral: "mistral";
|
|
604
1309
|
"openai-http": "openai-http";
|
|
605
1310
|
"claude-cli": "claude-cli";
|
|
606
1311
|
}>;
|
|
@@ -623,6 +1328,22 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
|
|
|
623
1328
|
"llm.chat": "llm.chat";
|
|
624
1329
|
}>;
|
|
625
1330
|
backendId: z.ZodEnum<{
|
|
1331
|
+
ollama: "ollama";
|
|
1332
|
+
mlx: "mlx";
|
|
1333
|
+
llamacpp: "llamacpp";
|
|
1334
|
+
vllm: "vllm";
|
|
1335
|
+
lmstudio: "lmstudio";
|
|
1336
|
+
jan: "jan";
|
|
1337
|
+
localai: "localai";
|
|
1338
|
+
anthropic: "anthropic";
|
|
1339
|
+
openai: "openai";
|
|
1340
|
+
gemini: "gemini";
|
|
1341
|
+
grok: "grok";
|
|
1342
|
+
groq: "groq";
|
|
1343
|
+
openrouter: "openrouter";
|
|
1344
|
+
together: "together";
|
|
1345
|
+
deepseek: "deepseek";
|
|
1346
|
+
mistral: "mistral";
|
|
626
1347
|
"openai-http": "openai-http";
|
|
627
1348
|
"claude-cli": "claude-cli";
|
|
628
1349
|
}>;
|
|
@@ -657,12 +1378,33 @@ declare const PairStartRequest: z.ZodObject<{
|
|
|
657
1378
|
win32: "win32";
|
|
658
1379
|
}>;
|
|
659
1380
|
}, z.core.$strip>;
|
|
1381
|
+
device: z.ZodObject<{
|
|
1382
|
+
identity: z.ZodString;
|
|
1383
|
+
encryption: z.ZodString;
|
|
1384
|
+
encryptionSig: z.ZodString;
|
|
1385
|
+
}, z.core.$strict>;
|
|
660
1386
|
capabilities: z.ZodArray<z.ZodObject<{
|
|
661
1387
|
kind: z.ZodEnum<{
|
|
662
1388
|
"llm.generate": "llm.generate";
|
|
663
1389
|
"llm.chat": "llm.chat";
|
|
664
1390
|
}>;
|
|
665
1391
|
backendId: z.ZodEnum<{
|
|
1392
|
+
ollama: "ollama";
|
|
1393
|
+
mlx: "mlx";
|
|
1394
|
+
llamacpp: "llamacpp";
|
|
1395
|
+
vllm: "vllm";
|
|
1396
|
+
lmstudio: "lmstudio";
|
|
1397
|
+
jan: "jan";
|
|
1398
|
+
localai: "localai";
|
|
1399
|
+
anthropic: "anthropic";
|
|
1400
|
+
openai: "openai";
|
|
1401
|
+
gemini: "gemini";
|
|
1402
|
+
grok: "grok";
|
|
1403
|
+
groq: "groq";
|
|
1404
|
+
openrouter: "openrouter";
|
|
1405
|
+
together: "together";
|
|
1406
|
+
deepseek: "deepseek";
|
|
1407
|
+
mistral: "mistral";
|
|
666
1408
|
"openai-http": "openai-http";
|
|
667
1409
|
"claude-cli": "claude-cli";
|
|
668
1410
|
}>;
|
|
@@ -701,10 +1443,14 @@ declare const PairPollResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
701
1443
|
status: z.ZodLiteral<"expired">;
|
|
702
1444
|
}, z.core.$strict>, z.ZodObject<{
|
|
703
1445
|
status: z.ZodLiteral<"approved">;
|
|
704
|
-
runnerToken: z.ZodString;
|
|
705
1446
|
runnerId: z.ZodString;
|
|
706
1447
|
owner: z.ZodString;
|
|
707
1448
|
ownerLabel: z.ZodOptional<z.ZodString>;
|
|
1449
|
+
site: z.ZodObject<{
|
|
1450
|
+
identity: z.ZodString;
|
|
1451
|
+
encryption: z.ZodString;
|
|
1452
|
+
encryptionSig: z.ZodString;
|
|
1453
|
+
}, z.core.$strict>;
|
|
708
1454
|
}, z.core.$strict>], "status">;
|
|
709
1455
|
type PairPollResponse = z.infer<typeof PairPollResponse>;
|
|
710
1456
|
declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -719,12 +1465,33 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
719
1465
|
win32: "win32";
|
|
720
1466
|
}>;
|
|
721
1467
|
}, z.core.$strip>;
|
|
1468
|
+
device: z.ZodObject<{
|
|
1469
|
+
identity: z.ZodString;
|
|
1470
|
+
encryption: z.ZodString;
|
|
1471
|
+
encryptionSig: z.ZodString;
|
|
1472
|
+
}, z.core.$strict>;
|
|
722
1473
|
capabilities: z.ZodArray<z.ZodObject<{
|
|
723
1474
|
kind: z.ZodEnum<{
|
|
724
1475
|
"llm.generate": "llm.generate";
|
|
725
1476
|
"llm.chat": "llm.chat";
|
|
726
1477
|
}>;
|
|
727
1478
|
backendId: z.ZodEnum<{
|
|
1479
|
+
ollama: "ollama";
|
|
1480
|
+
mlx: "mlx";
|
|
1481
|
+
llamacpp: "llamacpp";
|
|
1482
|
+
vllm: "vllm";
|
|
1483
|
+
lmstudio: "lmstudio";
|
|
1484
|
+
jan: "jan";
|
|
1485
|
+
localai: "localai";
|
|
1486
|
+
anthropic: "anthropic";
|
|
1487
|
+
openai: "openai";
|
|
1488
|
+
gemini: "gemini";
|
|
1489
|
+
grok: "grok";
|
|
1490
|
+
groq: "groq";
|
|
1491
|
+
openrouter: "openrouter";
|
|
1492
|
+
together: "together";
|
|
1493
|
+
deepseek: "deepseek";
|
|
1494
|
+
mistral: "mistral";
|
|
728
1495
|
"openai-http": "openai-http";
|
|
729
1496
|
"claude-cli": "claude-cli";
|
|
730
1497
|
}>;
|
|
@@ -754,6 +1521,22 @@ declare const ClaimRequest: z.ZodObject<{
|
|
|
754
1521
|
"llm.chat": "llm.chat";
|
|
755
1522
|
}>;
|
|
756
1523
|
backendId: z.ZodEnum<{
|
|
1524
|
+
ollama: "ollama";
|
|
1525
|
+
mlx: "mlx";
|
|
1526
|
+
llamacpp: "llamacpp";
|
|
1527
|
+
vllm: "vllm";
|
|
1528
|
+
lmstudio: "lmstudio";
|
|
1529
|
+
jan: "jan";
|
|
1530
|
+
localai: "localai";
|
|
1531
|
+
anthropic: "anthropic";
|
|
1532
|
+
openai: "openai";
|
|
1533
|
+
gemini: "gemini";
|
|
1534
|
+
grok: "grok";
|
|
1535
|
+
groq: "groq";
|
|
1536
|
+
openrouter: "openrouter";
|
|
1537
|
+
together: "together";
|
|
1538
|
+
deepseek: "deepseek";
|
|
1539
|
+
mistral: "mistral";
|
|
757
1540
|
"openai-http": "openai-http";
|
|
758
1541
|
"claude-cli": "claude-cli";
|
|
759
1542
|
}>;
|
|
@@ -778,28 +1561,23 @@ declare const ClaimResponse: z.ZodObject<{
|
|
|
778
1561
|
"llm.generate": "llm.generate";
|
|
779
1562
|
"llm.chat": "llm.chat";
|
|
780
1563
|
}>;
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
system: z.ZodOptional<z.ZodString>;
|
|
784
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
785
|
-
messages: z.ZodArray<z.ZodObject<{
|
|
786
|
-
role: z.ZodEnum<{
|
|
787
|
-
system: "system";
|
|
788
|
-
user: "user";
|
|
789
|
-
assistant: "assistant";
|
|
790
|
-
}>;
|
|
791
|
-
content: z.ZodString;
|
|
792
|
-
}, z.core.$strip>>;
|
|
793
|
-
system: z.ZodOptional<z.ZodString>;
|
|
794
|
-
}, z.core.$strict>]>;
|
|
1564
|
+
owner: z.ZodString;
|
|
1565
|
+
site: z.ZodString;
|
|
795
1566
|
audience: z.ZodEnum<{
|
|
796
1567
|
self: "self";
|
|
797
1568
|
named: "named";
|
|
798
1569
|
public: "public";
|
|
799
1570
|
}>;
|
|
800
|
-
|
|
801
|
-
|
|
1571
|
+
sizeClass: z.ZodEnum<{
|
|
1572
|
+
small: "small";
|
|
1573
|
+
medium: "medium";
|
|
1574
|
+
large: "large";
|
|
1575
|
+
unbounded: "unbounded";
|
|
1576
|
+
}>;
|
|
1577
|
+
streaming: z.ZodBoolean;
|
|
1578
|
+
deadlineAt: z.ZodNumber;
|
|
802
1579
|
lease: z.ZodObject<{
|
|
1580
|
+
id: z.ZodString;
|
|
803
1581
|
runnerId: z.ZodString;
|
|
804
1582
|
expiresAt: z.ZodNumber;
|
|
805
1583
|
}, z.core.$strip>;
|
|
@@ -817,6 +1595,22 @@ declare const HeartbeatRequest: z.ZodObject<{
|
|
|
817
1595
|
"llm.chat": "llm.chat";
|
|
818
1596
|
}>;
|
|
819
1597
|
backendId: z.ZodEnum<{
|
|
1598
|
+
ollama: "ollama";
|
|
1599
|
+
mlx: "mlx";
|
|
1600
|
+
llamacpp: "llamacpp";
|
|
1601
|
+
vllm: "vllm";
|
|
1602
|
+
lmstudio: "lmstudio";
|
|
1603
|
+
jan: "jan";
|
|
1604
|
+
localai: "localai";
|
|
1605
|
+
anthropic: "anthropic";
|
|
1606
|
+
openai: "openai";
|
|
1607
|
+
gemini: "gemini";
|
|
1608
|
+
grok: "grok";
|
|
1609
|
+
groq: "groq";
|
|
1610
|
+
openrouter: "openrouter";
|
|
1611
|
+
together: "together";
|
|
1612
|
+
deepseek: "deepseek";
|
|
1613
|
+
mistral: "mistral";
|
|
820
1614
|
"openai-http": "openai-http";
|
|
821
1615
|
"claude-cli": "claude-cli";
|
|
822
1616
|
}>;
|
|
@@ -831,54 +1625,73 @@ declare const HeartbeatRequest: z.ZodObject<{
|
|
|
831
1625
|
public: "public";
|
|
832
1626
|
}>;
|
|
833
1627
|
}, z.core.$strict>>;
|
|
834
|
-
|
|
1628
|
+
activeLeases: z.ZodArray<z.ZodObject<{
|
|
1629
|
+
jobId: z.ZodString;
|
|
1630
|
+
leaseId: z.ZodString;
|
|
1631
|
+
}, z.core.$strip>>;
|
|
835
1632
|
paused: z.ZodBoolean;
|
|
836
1633
|
}, z.core.$strict>;
|
|
837
1634
|
type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;
|
|
838
1635
|
declare const HeartbeatResponse: z.ZodObject<{
|
|
839
1636
|
revoked: z.ZodBoolean;
|
|
840
1637
|
cancel: z.ZodArray<z.ZodString>;
|
|
841
|
-
leases: z.ZodArray<z.ZodObject<{
|
|
842
|
-
jobId: z.ZodString;
|
|
843
|
-
expiresAt: z.ZodNumber;
|
|
844
|
-
}, z.core.$strict>>;
|
|
845
1638
|
lost: z.ZodArray<z.ZodString>;
|
|
846
1639
|
serverTime: z.ZodNumber;
|
|
847
1640
|
}, z.core.$strict>;
|
|
848
1641
|
type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
|
|
1642
|
+
/**
|
|
1643
|
+
* What an intermediary learns about how a job ended — byollm_009 §6.
|
|
1644
|
+
*
|
|
1645
|
+
* The discriminator and nothing else. A relay has to know a job reached a
|
|
1646
|
+
* terminal state, and whether it failed, because that decides whether the job
|
|
1647
|
+
* leaves the queue or the app may re-enqueue. It does not have to know what
|
|
1648
|
+
* the model said, or what an error said, and this is where that line is drawn.
|
|
1649
|
+
*
|
|
1650
|
+
* Kept identical to `JobOutcome`'s discriminator rather than coarsened to
|
|
1651
|
+
* ok/not-ok: a cancelled job and a failed one are different routing outcomes,
|
|
1652
|
+
* and collapsing them would make the relay guess.
|
|
1653
|
+
*/
|
|
1654
|
+
declare const ResultDisposition: z.ZodEnum<{
|
|
1655
|
+
ok: "ok";
|
|
1656
|
+
error: "error";
|
|
1657
|
+
canceled: "canceled";
|
|
1658
|
+
}>;
|
|
1659
|
+
type ResultDisposition = z.infer<typeof ResultDisposition>;
|
|
849
1660
|
declare const ResultRequest: z.ZodObject<{
|
|
850
1661
|
protocolVersion: z.ZodLiteral<"0">;
|
|
851
1662
|
runnerId: z.ZodString;
|
|
852
1663
|
jobId: z.ZodString;
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
process: "process";
|
|
1664
|
+
leaseId: z.ZodString;
|
|
1665
|
+
envelope: z.ZodObject<{
|
|
1666
|
+
ciphertext: z.ZodString;
|
|
1667
|
+
recipientKeyId: z.ZodString;
|
|
1668
|
+
senderKeyId: z.ZodString;
|
|
1669
|
+
direction: z.ZodEnum<{
|
|
1670
|
+
payload: "payload";
|
|
1671
|
+
result: "result";
|
|
1672
|
+
}>;
|
|
1673
|
+
deadlineAt: z.ZodNumber;
|
|
1674
|
+
}, z.core.$strict>;
|
|
1675
|
+
disposition: z.ZodEnum<{
|
|
1676
|
+
ok: "ok";
|
|
1677
|
+
error: "error";
|
|
1678
|
+
canceled: "canceled";
|
|
869
1679
|
}>;
|
|
870
|
-
durationMs: z.ZodNumber;
|
|
871
1680
|
}, z.core.$strict>;
|
|
872
1681
|
type ResultRequest = z.infer<typeof ResultRequest>;
|
|
873
1682
|
declare const ResultResponse: z.ZodObject<{
|
|
874
1683
|
accepted: z.ZodBoolean;
|
|
1684
|
+
duplicate: z.ZodOptional<z.ZodBoolean>;
|
|
875
1685
|
state: z.ZodString;
|
|
876
1686
|
}, z.core.$strict>;
|
|
877
1687
|
type ResultResponse = z.infer<typeof ResultResponse>;
|
|
878
1688
|
declare const ReleaseRequest: z.ZodObject<{
|
|
879
1689
|
protocolVersion: z.ZodLiteral<"0">;
|
|
880
1690
|
runnerId: z.ZodString;
|
|
881
|
-
|
|
1691
|
+
leases: z.ZodArray<z.ZodObject<{
|
|
1692
|
+
jobId: z.ZodString;
|
|
1693
|
+
leaseId: z.ZodString;
|
|
1694
|
+
}, z.core.$strip>>;
|
|
882
1695
|
reason: z.ZodEnum<{
|
|
883
1696
|
revoked: "revoked";
|
|
884
1697
|
shutdown: "shutdown";
|
|
@@ -901,30 +1714,58 @@ type ReleaseResponse = z.infer<typeof ReleaseResponse>;
|
|
|
901
1714
|
* with no response at all.
|
|
902
1715
|
*/
|
|
903
1716
|
declare const WireErrorCode: z.ZodEnum<{
|
|
1717
|
+
"unsupported-protocol-version": "unsupported-protocol-version";
|
|
904
1718
|
revoked: "revoked";
|
|
905
1719
|
"bad-request": "bad-request";
|
|
906
|
-
"unsupported-protocol-version": "unsupported-protocol-version";
|
|
907
1720
|
unauthorized: "unauthorized";
|
|
1721
|
+
forbidden: "forbidden";
|
|
908
1722
|
"not-found": "not-found";
|
|
1723
|
+
"not-ready": "not-ready";
|
|
1724
|
+
"clock-skew": "clock-skew";
|
|
909
1725
|
"rate-limited": "rate-limited";
|
|
910
1726
|
"server-error": "server-error";
|
|
911
1727
|
}>;
|
|
912
1728
|
type WireErrorCode = z.infer<typeof WireErrorCode>;
|
|
913
1729
|
declare const WireError: z.ZodObject<{
|
|
914
1730
|
error: z.ZodEnum<{
|
|
1731
|
+
"unsupported-protocol-version": "unsupported-protocol-version";
|
|
915
1732
|
revoked: "revoked";
|
|
916
1733
|
"bad-request": "bad-request";
|
|
917
|
-
"unsupported-protocol-version": "unsupported-protocol-version";
|
|
918
1734
|
unauthorized: "unauthorized";
|
|
1735
|
+
forbidden: "forbidden";
|
|
919
1736
|
"not-found": "not-found";
|
|
1737
|
+
"not-ready": "not-ready";
|
|
1738
|
+
"clock-skew": "clock-skew";
|
|
920
1739
|
"rate-limited": "rate-limited";
|
|
921
1740
|
"server-error": "server-error";
|
|
922
1741
|
}>;
|
|
923
1742
|
message: z.ZodString;
|
|
924
1743
|
retryAfter: z.ZodOptional<z.ZodNumber>;
|
|
1744
|
+
serverTime: z.ZodOptional<z.ZodNumber>;
|
|
1745
|
+
maxSkewMs: z.ZodOptional<z.ZodNumber>;
|
|
925
1746
|
}, z.core.$strict>;
|
|
926
1747
|
type WireError = z.infer<typeof WireError>;
|
|
927
1748
|
/** HTTP status each error code is served with. */
|
|
928
1749
|
declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
|
|
1750
|
+
declare const FetchRequest: z.ZodObject<{
|
|
1751
|
+
protocolVersion: z.ZodString;
|
|
1752
|
+
runnerId: z.ZodString;
|
|
1753
|
+
jobId: z.ZodString;
|
|
1754
|
+
leaseId: z.ZodString;
|
|
1755
|
+
}, z.core.$strict>;
|
|
1756
|
+
type FetchRequest = z.infer<typeof FetchRequest>;
|
|
1757
|
+
declare const FetchResponse: z.ZodObject<{
|
|
1758
|
+
envelope: z.ZodObject<{
|
|
1759
|
+
ciphertext: z.ZodString;
|
|
1760
|
+
recipientKeyId: z.ZodString;
|
|
1761
|
+
senderKeyId: z.ZodString;
|
|
1762
|
+
direction: z.ZodEnum<{
|
|
1763
|
+
payload: "payload";
|
|
1764
|
+
result: "result";
|
|
1765
|
+
}>;
|
|
1766
|
+
deadlineAt: z.ZodNumber;
|
|
1767
|
+
}, z.core.$strict>;
|
|
1768
|
+
}, z.core.$strict>;
|
|
1769
|
+
type FetchResponse = z.infer<typeof FetchResponse>;
|
|
929
1770
|
|
|
930
|
-
export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS,
|
|
1771
|
+
export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DeliveredResult, ENDPOINTS, ENVELOPE_MAX_AGE_MS, ERROR_STATUS, type Endpoint, type EnvelopeContext, EnvelopeDirection, type EnvelopeFailure, FetchRequest, FetchResponse, GeneratePayload, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, OFFER_SCOPES, OfferScope, type OpenResult, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, REFUSAL_MESSAGES, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASS_LIMITS, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SizeClass, type SpendConsent, StoredKeys, TERMINAL_STATES, type VersionRefusal, WireError, WireErrorCode, backendDescriptor, canTransition, canonicalRequest, checkProtocolVersion, cryptoReady, effectiveOfferScope, fingerprint, generateKeys, isBackendId, isJobKind, isLocalHost, isTerminal, keyId, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signRequest, signSiteRequest, signWith, sizeClassCeiling, sizeClassOf, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith };
|