@byollm/protocol 0.1.0-alpha.9 → 0.1.0-alpha.90
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/ABOUT-SHORT.md +7 -0
- package/ABOUT.md +60 -0
- package/README.md +120 -4
- package/dist/index.d.ts +2824 -1524
- package/dist/index.js +1903 -524
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,1675 +1,2975 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
4
|
+
* Protocol version carried on every request; servers refuse what they can't
|
|
5
|
+
* speak.
|
|
6
|
+
*
|
|
7
|
+
* **`1` because byollm_016 changed the vocabulary** — byollm-review
|
|
8
|
+
* 2026-08-27. `OfferScope` lost `public`, `self|named` became
|
|
9
|
+
* `private|team`, `JobStub` lost `service` and gained `purpose`, and the
|
|
10
|
+
* grant's site field changed namespace. The version stayed `0` through all of
|
|
11
|
+
* it.
|
|
12
|
+
*
|
|
13
|
+
* The consequence was the failure the handshake exists to prevent, arriving
|
|
14
|
+
* around it: a pre-rip daemon declares `0`, passes the version check, and
|
|
15
|
+
* then fails whole-body schema validation with "request failed schema
|
|
16
|
+
* validation" — no field named, no vocabulary named, no upgrade command. Once
|
|
17
|
+
* every ten seconds, forever, while its owner watches a device go stale for
|
|
18
|
+
* no stated reason. The check below was written because "a mismatch surfaced
|
|
19
|
+
* as a generic bad-request" and "an error a user cannot act on is barely
|
|
20
|
+
* better than a hang"; the number not moving is how that came back.
|
|
21
|
+
*
|
|
22
|
+
* A registry is a schema and an enum value is the contract — this project's
|
|
23
|
+
* own words, from the release that silenced a fleet by adding a backend id.
|
|
24
|
+
* The same sentence applies to removing an offer scope.
|
|
14
25
|
*/
|
|
15
|
-
declare const
|
|
16
|
-
http: "http";
|
|
17
|
-
process: "process";
|
|
18
|
-
}>;
|
|
19
|
-
type BackendClass = z.infer<typeof BackendClass>;
|
|
26
|
+
declare const PROTOCOL_VERSION: "1";
|
|
20
27
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
+
* Every protocol version this build can serve, **oldest first**.
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* - `subscription` — a vendor account whose terms forbid third-party work.
|
|
33
|
-
* Sharing is a terms violation, not merely expensive.
|
|
30
|
+
* One entry today. It is a list rather than a constant because the shape of
|
|
31
|
+
* the check is the point: a server supporting two versions through a
|
|
32
|
+
* migration should not need a different code path from one supporting one.
|
|
34
33
|
*/
|
|
35
|
-
declare const BackendCost: z.ZodEnum<{
|
|
36
|
-
free: "free";
|
|
37
|
-
metered: "metered";
|
|
38
|
-
subscription: "subscription";
|
|
39
|
-
}>;
|
|
40
|
-
type BackendCost = z.infer<typeof BackendCost>;
|
|
41
|
-
/** The immutable facts about a backend that the protocol reasons over. */
|
|
42
|
-
interface BackendDescriptor {
|
|
43
|
-
/** Stable backend id, as written in `byollm.config.json`. */
|
|
44
|
-
readonly id: string;
|
|
45
|
-
/** Human-readable name for the trust UI. */
|
|
46
|
-
readonly label: string;
|
|
47
|
-
/** Determines which isolation requirements apply. */
|
|
48
|
-
readonly class: BackendClass;
|
|
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;
|
|
59
|
-
/**
|
|
60
|
-
* Which adversarial corpus byollm_004 §5 runs against this backend. A
|
|
61
|
-
* backend cannot be registered without one — the coverage check in the
|
|
62
|
-
* adversarial suite enforces it.
|
|
63
|
-
*/
|
|
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;
|
|
70
|
-
}
|
|
71
34
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
35
|
+
* `0` is deliberately **not** here, though the list exists for exactly that.
|
|
36
|
+
*
|
|
37
|
+
* Supporting two versions through a migration is the shape this was built
|
|
38
|
+
* for, and it is the wrong tool when the vocabularies are incompatible: a `0`
|
|
39
|
+
* daemon sends `offer: "public"` and a `service` on its stubs, so accepting
|
|
40
|
+
* its version only moves the refusal one layer down — to the schema error
|
|
41
|
+
* that names nothing, which is the bug. Refusing the version is the whole
|
|
42
|
+
* point, because that refusal says what to do.
|
|
80
43
|
*/
|
|
81
|
-
declare const
|
|
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;
|
|
105
|
-
readonly "openai-http": BackendDescriptor;
|
|
106
|
-
readonly "claude-cli": BackendDescriptor;
|
|
107
|
-
}>;
|
|
108
|
-
/** The id of a registered backend. */
|
|
109
|
-
type BackendId = keyof typeof BACKENDS;
|
|
110
|
-
/** All registered backend ids — the adversarial coverage check iterates this. */
|
|
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")[];
|
|
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";
|
|
129
|
-
"openai-http": "openai-http";
|
|
130
|
-
"claude-cli": "claude-cli";
|
|
131
|
-
}>;
|
|
132
|
-
/** Narrow an arbitrary string to a registered backend id. */
|
|
133
|
-
declare function isBackendId(value: string): value is BackendId;
|
|
44
|
+
declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
|
|
134
45
|
/**
|
|
135
|
-
*
|
|
46
|
+
* The oldest version this build will talk to — derived, not declared.
|
|
136
47
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
48
|
+
* Stating it separately would be a second thing to keep in step with the list
|
|
49
|
+
* above, and the failure would be silent: a minimum that no longer matches
|
|
50
|
+
* what is supported produces a refusal naming a version the server would in
|
|
51
|
+
* fact have accepted.
|
|
139
52
|
*/
|
|
140
|
-
declare
|
|
53
|
+
declare const MIN_PROTOCOL_VERSION: string;
|
|
54
|
+
/** A structured refusal, so a daemon can say something useful to its owner. */
|
|
55
|
+
interface VersionRefusal {
|
|
56
|
+
readonly error: "unsupported-protocol-version";
|
|
57
|
+
readonly message: string;
|
|
58
|
+
readonly supported: readonly string[];
|
|
59
|
+
readonly minimum: string;
|
|
60
|
+
}
|
|
141
61
|
/**
|
|
142
|
-
*
|
|
62
|
+
* The version a request declares, wherever it carries it.
|
|
143
63
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* because "free" is derived from the address, not from what the config claims.
|
|
64
|
+
* A POST declares it in its body, which is where every request schema has
|
|
65
|
+
* always put it. A GET has no body, and the relay has one — the site plane's
|
|
66
|
+
* `pending` read — so it declares it in the query string instead.
|
|
148
67
|
*
|
|
149
|
-
* **
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
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.
|
|
68
|
+
* **Two carriers, one rule.** That asymmetry is HTTP's rather than ours, and
|
|
69
|
+
* the alternative was worse in both directions: a header for everything would
|
|
70
|
+
* change every existing daemon's request, and skipping GETs would leave an
|
|
71
|
+
* endpoint outside the handshake — which is precisely the shape B.4 found,
|
|
72
|
+
* where a whole plane was outside it.
|
|
157
73
|
*/
|
|
158
|
-
declare function
|
|
74
|
+
declare function declaredVersion(input: {
|
|
75
|
+
body?: unknown;
|
|
76
|
+
query?: URLSearchParams;
|
|
77
|
+
}): unknown;
|
|
159
78
|
/**
|
|
160
|
-
*
|
|
79
|
+
* Check the protocol version on an incoming request
|
|
80
|
+
* ({@link MUSTS.VERSION_HANDSHAKE_REQUIRED}).
|
|
161
81
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Who may run a job, declared by the app that enqueued it.
|
|
82
|
+
* Returns a refusal, or `null` to proceed.
|
|
83
|
+
*
|
|
84
|
+
* **A missing version is refused the same way a wrong one is.** That is the
|
|
85
|
+
* half worth stating: before this existed, the version travelled as a
|
|
86
|
+
* `z.literal` inside each endpoint's schema, so a mismatch surfaced as a
|
|
87
|
+
* generic `bad-request` — a daemon and a server discovered they disagreed by
|
|
88
|
+
* failing, with nothing in the response naming the disagreement. An error a
|
|
89
|
+
* user cannot act on is barely better than a hang.
|
|
172
90
|
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* pair in their *local* allowlist (byollm_001 Rev 1 §B).
|
|
176
|
-
* - `public` — any daemon offering `public` compute.
|
|
91
|
+
* The message names the fix, because the person reading it is usually the one
|
|
92
|
+
* who has to apply it.
|
|
177
93
|
*/
|
|
178
|
-
declare
|
|
179
|
-
self: "self";
|
|
180
|
-
named: "named";
|
|
181
|
-
public: "public";
|
|
182
|
-
}>;
|
|
183
|
-
type Audience = z.infer<typeof Audience>;
|
|
94
|
+
declare function checkProtocolVersion(body: unknown): VersionRefusal | null;
|
|
184
95
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
96
|
+
* How to upgrade a daemon, in one place.
|
|
97
|
+
*
|
|
98
|
+
* `@latest`, which is correct in both eras and therefore never has to be
|
|
99
|
+
* revisited: during a prerelease it resolves to the current alpha, and after
|
|
100
|
+
* one it resolves to the current stable.
|
|
101
|
+
*
|
|
102
|
+
* `@alpha` was considered and rejected. The argument for it was that `latest`
|
|
103
|
+
* is moved by hand — it needs a human with 2FA, deliberately — so it can lag
|
|
104
|
+
* the `alpha` tag. In practice that lag has been minutes, and the cost on the
|
|
105
|
+
* other side is permanent: the day this stops being a prerelease, `@alpha`
|
|
106
|
+
* starts meaning "the unstable one", and every user who followed this message
|
|
107
|
+
* is pinned to prereleases with nothing to tell them.
|
|
108
|
+
*
|
|
109
|
+
* Note this is deliberately *not* the rule `scripts/check-site.mjs` enforces
|
|
110
|
+
* on the docs, which requires `npx byollm@alpha`. That rule is about somebody
|
|
111
|
+
* choosing to install a prerelease knowingly, with the warning in front of
|
|
112
|
+
* them. This is an upgrade instruction handed to somebody who already has the
|
|
113
|
+
* daemon and needs a newer one — a different question with a different answer.
|
|
188
114
|
*/
|
|
189
|
-
declare const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
public: "public";
|
|
193
|
-
}>;
|
|
194
|
-
type OfferScope = z.infer<typeof OfferScope>;
|
|
195
|
-
/** All audience values, in widening order. */
|
|
196
|
-
declare const AUDIENCES: readonly ("self" | "named" | "public")[];
|
|
197
|
-
/** All offer scopes, in widening order. */
|
|
198
|
-
declare const OFFER_SCOPES: readonly ("self" | "named" | "public")[];
|
|
115
|
+
declare const UPGRADE_COMMAND: "npm i -g byollm@latest";
|
|
116
|
+
/** The path prefix all endpoints mount under. */
|
|
117
|
+
declare const PROTOCOL_PREFIX: "/byollm";
|
|
199
118
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
119
|
+
* The endpoint names, in the order byollm_001 lists them, plus `fetch`.
|
|
120
|
+
*
|
|
121
|
+
* `fetch` is byollm_009 §6's second phase: a claim returns a stub, and the
|
|
122
|
+
* payload is collected separately by the device that took it. Two steps
|
|
123
|
+
* rather than one because a payload can only be sealed once its recipient is
|
|
124
|
+
* known — which is also what makes multi-device free.
|
|
204
125
|
*/
|
|
205
|
-
declare const
|
|
206
|
-
|
|
207
|
-
"audience-self-other-owner": "audience-self-other-owner";
|
|
208
|
-
"not-locally-allowed": "not-locally-allowed";
|
|
209
|
-
"not-in-server-allowlist": "not-in-server-allowlist";
|
|
210
|
-
"offer-scope-too-narrow": "offer-scope-too-narrow";
|
|
211
|
-
"subscription-self-lock": "subscription-self-lock";
|
|
212
|
-
"metered-no-spend-consent": "metered-no-spend-consent";
|
|
213
|
-
"metered-ceiling-reached": "metered-ceiling-reached";
|
|
214
|
-
}>;
|
|
215
|
-
type MatchRefusal = z.infer<typeof MatchRefusal>;
|
|
216
|
-
/** The outcome of an audience match. */
|
|
217
|
-
type MatchResult = {
|
|
218
|
-
readonly ok: true;
|
|
219
|
-
} | {
|
|
220
|
-
readonly ok: false;
|
|
221
|
-
readonly refusal: MatchRefusal;
|
|
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
|
-
}
|
|
126
|
+
declare const ENDPOINTS: readonly ["pair", "claim", "fetch", "heartbeat", "result", "release"];
|
|
127
|
+
type Endpoint = (typeof ENDPOINTS)[number];
|
|
230
128
|
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
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.
|
|
129
|
+
* One entry of the capability matrix: a kind this daemon can actually serve,
|
|
130
|
+
* right now, with the backend and model that would serve it.
|
|
242
131
|
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
132
|
+
* Derived from owner config intersected with detected reality
|
|
133
|
+
* ({@link MUSTS.CAPABILITY_IS_DETECTED}) — a configured-but-unreachable
|
|
134
|
+
* backend must not appear here. Carries `backendClass` so the app can tell
|
|
135
|
+
* whether a result came from a sandboxed spawn or an HTTP call
|
|
136
|
+
* (byollm_001 Rev 1 §A).
|
|
245
137
|
*/
|
|
246
|
-
declare
|
|
247
|
-
/** The job-side facts a match needs. */
|
|
248
|
-
interface MatchJob {
|
|
249
|
-
/** The app's id for the user who enqueued the job. */
|
|
250
|
-
readonly owner: string;
|
|
251
|
-
/** Who the app says may run it. */
|
|
252
|
-
readonly audience: Audience;
|
|
253
|
-
/**
|
|
254
|
-
* Optional server-side restriction on which runner owners may take a
|
|
255
|
-
* `named` job. Defence in depth only — the daemon's local allowlist is the
|
|
256
|
-
* enforcing side ({@link MUSTS.NAMED_LOCAL_ALLOWLIST}).
|
|
257
|
-
*/
|
|
258
|
-
readonly audienceAllow?: readonly string[] | undefined;
|
|
259
|
-
}
|
|
260
|
-
/** The daemon-side facts a match needs. */
|
|
261
|
-
interface MatchDaemon {
|
|
262
|
-
/** The app's id for the user this daemon is paired to. */
|
|
263
|
-
readonly owner: string;
|
|
264
|
-
/** Effective scope of the backend that would run the job. */
|
|
265
|
-
readonly offerScope: OfferScope;
|
|
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;
|
|
270
|
-
/**
|
|
271
|
-
* Does this daemon's *local* allowlist admit the given owner for the server
|
|
272
|
-
* origin the job came from? Supplied as a predicate so the protocol package
|
|
273
|
-
* stays free of file I/O; the daemon passes its allowlist, the server
|
|
274
|
-
* passes a conservative `() => true` because it cannot know a remote
|
|
275
|
-
* daemon's local list and must not pretend to.
|
|
276
|
-
*/
|
|
277
|
-
readonly locallyAllows: (owner: string) => boolean;
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Decide whether a job may run on a daemon.
|
|
281
|
-
*
|
|
282
|
-
* Both sides must agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}):
|
|
283
|
-
* 1. the job's audience must admit the daemon's owner, and
|
|
284
|
-
* 2. the backend's offer scope must admit the job's owner.
|
|
285
|
-
*
|
|
286
|
-
* The full nine-way matrix (three audiences × three offer scopes) is asserted
|
|
287
|
-
* by the conformance kit. The function is pure and total so both the daemon
|
|
288
|
-
* and the server can run the identical rule — the daemon refuses, and the
|
|
289
|
-
* server refuses too (byollm_003 §Server-side MUSTs).
|
|
290
|
-
*
|
|
291
|
-
* @example
|
|
292
|
-
* ```ts
|
|
293
|
-
* const result = matchAudience(
|
|
294
|
-
* { owner: "alice", audience: "named" },
|
|
295
|
-
* {
|
|
296
|
-
* owner: "bob",
|
|
297
|
-
* offerScope: "named",
|
|
298
|
-
* cost: "free",
|
|
299
|
-
* locallyAllows: (o) => o === "alice",
|
|
300
|
-
* },
|
|
301
|
-
* );
|
|
302
|
-
* // result.ok === true
|
|
303
|
-
* ```
|
|
304
|
-
*/
|
|
305
|
-
declare function matchAudience(job: MatchJob, daemon: MatchDaemon): MatchResult;
|
|
306
|
-
/**
|
|
307
|
-
* Human-readable refusal text for the daemon's log and the trust UI.
|
|
308
|
-
* Each refusal reads as a distinct truth — byollm_002's "four different
|
|
309
|
-
* truths that must never share a message" applied to the audience axis.
|
|
310
|
-
*/
|
|
311
|
-
declare const REFUSAL_MESSAGES: Readonly<Record<MatchRefusal, string>>;
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Upper bounds on payload size, enforced at the schema so oversized input is
|
|
315
|
-
* refused at parse time rather than somewhere deeper.
|
|
316
|
-
*
|
|
317
|
-
* byollm_004 §4 requires stricter limits for community (`named`/`public`)
|
|
318
|
-
* jobs; those are applied on top of these by the daemon's budget check, which
|
|
319
|
-
* knows the job's audience. These are the absolute ceilings for any job.
|
|
320
|
-
*/
|
|
321
|
-
declare const PAYLOAD_LIMITS: Readonly<{
|
|
322
|
-
/** Max characters in any single text field. */
|
|
323
|
-
maxTextChars: 1000000;
|
|
324
|
-
/** Max messages in an `llm.chat` conversation. */
|
|
325
|
-
maxMessages: 256;
|
|
326
|
-
/** Max characters across the whole payload. */
|
|
327
|
-
maxTotalChars: 4000000;
|
|
328
|
-
}>;
|
|
329
|
-
/**
|
|
330
|
-
* A conversation turn. `role` is a closed enum — it is routing *within the
|
|
331
|
-
* model call*, not routing of the call, so it cannot select a backend.
|
|
332
|
-
*/
|
|
333
|
-
declare const ChatMessage: z.ZodObject<{
|
|
334
|
-
role: z.ZodEnum<{
|
|
335
|
-
system: "system";
|
|
336
|
-
user: "user";
|
|
337
|
-
assistant: "assistant";
|
|
338
|
-
}>;
|
|
339
|
-
content: z.ZodString;
|
|
340
|
-
}, z.core.$strip>;
|
|
341
|
-
type ChatMessage = z.infer<typeof ChatMessage>;
|
|
342
|
-
/**
|
|
343
|
-
* Payload for `llm.generate`.
|
|
344
|
-
*
|
|
345
|
-
* @remarks
|
|
346
|
-
* Text only, deliberately. byollm_004 §1 states the payload is "data handed
|
|
347
|
-
* to a model, never configuration and never a command", so v0 carries no
|
|
348
|
-
* sampling parameters, no model name, no base URL and no flags — those are
|
|
349
|
-
* owner-side route config. A future `params` field with an explicit closed
|
|
350
|
-
* allowlist and owner-set clamps is reserved; adding a field later is
|
|
351
|
-
* non-breaking, removing one is not.
|
|
352
|
-
*/
|
|
353
|
-
declare const GeneratePayload: z.ZodObject<{
|
|
354
|
-
prompt: z.ZodString;
|
|
355
|
-
system: z.ZodOptional<z.ZodString>;
|
|
356
|
-
}, z.core.$strict>;
|
|
357
|
-
type GeneratePayload = z.infer<typeof GeneratePayload>;
|
|
358
|
-
/** Payload for `llm.chat`. Text only, for the same reason as {@link GeneratePayload}. */
|
|
359
|
-
declare const ChatPayload: z.ZodObject<{
|
|
360
|
-
messages: z.ZodArray<z.ZodObject<{
|
|
361
|
-
role: z.ZodEnum<{
|
|
362
|
-
system: "system";
|
|
363
|
-
user: "user";
|
|
364
|
-
assistant: "assistant";
|
|
365
|
-
}>;
|
|
366
|
-
content: z.ZodString;
|
|
367
|
-
}, z.core.$strip>>;
|
|
368
|
-
system: z.ZodOptional<z.ZodString>;
|
|
369
|
-
}, z.core.$strict>;
|
|
370
|
-
type ChatPayload = z.infer<typeof ChatPayload>;
|
|
371
|
-
/**
|
|
372
|
-
* The job kinds a v1 daemon has handlers for.
|
|
373
|
-
*
|
|
374
|
-
* Kinds are resolved against handlers baked into the daemon
|
|
375
|
-
* ({@link MUSTS.KIND_TYPED_ONLY}); an unknown kind is refused, never guessed.
|
|
376
|
-
* Adding a kind is a protocol change with its own spec and threat review —
|
|
377
|
-
* notably any kind that needs tools, which byollm_004 §2 forbids as a payload
|
|
378
|
-
* flag.
|
|
379
|
-
*/
|
|
380
|
-
declare const JobKind: z.ZodEnum<{
|
|
381
|
-
"llm.generate": "llm.generate";
|
|
382
|
-
"llm.chat": "llm.chat";
|
|
383
|
-
}>;
|
|
384
|
-
type JobKind = z.infer<typeof JobKind>;
|
|
385
|
-
/** All v1 job kinds. */
|
|
386
|
-
declare const JOB_KINDS: readonly ("llm.generate" | "llm.chat")[];
|
|
387
|
-
/** A payload discriminated by its kind. */
|
|
388
|
-
declare const KindedPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
389
|
-
kind: z.ZodLiteral<"llm.generate">;
|
|
390
|
-
payload: z.ZodObject<{
|
|
391
|
-
prompt: z.ZodString;
|
|
392
|
-
system: z.ZodOptional<z.ZodString>;
|
|
393
|
-
}, z.core.$strict>;
|
|
394
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
395
|
-
kind: z.ZodLiteral<"llm.chat">;
|
|
396
|
-
payload: z.ZodObject<{
|
|
397
|
-
messages: z.ZodArray<z.ZodObject<{
|
|
398
|
-
role: z.ZodEnum<{
|
|
399
|
-
system: "system";
|
|
400
|
-
user: "user";
|
|
401
|
-
assistant: "assistant";
|
|
402
|
-
}>;
|
|
403
|
-
content: z.ZodString;
|
|
404
|
-
}, z.core.$strip>>;
|
|
405
|
-
system: z.ZodOptional<z.ZodString>;
|
|
406
|
-
}, z.core.$strict>;
|
|
407
|
-
}, z.core.$strip>], "kind">;
|
|
408
|
-
type KindedPayload = z.infer<typeof KindedPayload>;
|
|
409
|
-
/** The payload type for a given kind. */
|
|
410
|
-
type PayloadFor<K extends JobKind> = K extends "llm.generate" ? GeneratePayload : ChatPayload;
|
|
411
|
-
/** Narrow an arbitrary string to a known job kind. */
|
|
412
|
-
declare function isJobKind(value: string): value is JobKind;
|
|
413
|
-
/**
|
|
414
|
-
* Total character weight of a payload, used by the daemon's community budget
|
|
415
|
-
* check and by the server's payload-size limits.
|
|
416
|
-
*/
|
|
417
|
-
declare function payloadTextLength(kinded: KindedPayload): number;
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* The job lifecycle, made explicit by byollm_001 Rev 1 §D because the most
|
|
421
|
-
* user-visible failure mode — "nothing is running my job" — was previously
|
|
422
|
-
* unspecified.
|
|
423
|
-
*
|
|
424
|
-
* ```text
|
|
425
|
-
* queued ──claim──▶ claimed ──start──▶ running ──▶ ok | error | canceled
|
|
426
|
-
* │ │ │
|
|
427
|
-
* │ └──lease expiry─────┘
|
|
428
|
-
* │ ▼
|
|
429
|
-
* │ queued (reclaimable, no loss)
|
|
430
|
-
* └──ttl elapsed──▶ expired
|
|
431
|
-
* ```
|
|
432
|
-
*/
|
|
433
|
-
declare const JobState: z.ZodEnum<{
|
|
434
|
-
ok: "ok";
|
|
435
|
-
error: "error";
|
|
436
|
-
queued: "queued";
|
|
437
|
-
claimed: "claimed";
|
|
438
|
-
running: "running";
|
|
439
|
-
canceled: "canceled";
|
|
440
|
-
expired: "expired";
|
|
441
|
-
}>;
|
|
442
|
-
type JobState = z.infer<typeof JobState>;
|
|
443
|
-
/** States from which a job never moves again. */
|
|
444
|
-
declare const TERMINAL_STATES: readonly ["ok", "error", "canceled", "expired"];
|
|
445
|
-
/** Is this a state the job can never leave? */
|
|
446
|
-
declare function isTerminal(state: JobState): boolean;
|
|
447
|
-
/** May a job move from `from` to `to`? */
|
|
448
|
-
declare function canTransition(from: JobState, to: JobState): boolean;
|
|
449
|
-
/** A lease: the right to work on a job until `expiresAt`. */
|
|
450
|
-
declare const Lease: z.ZodObject<{
|
|
451
|
-
id: z.ZodString;
|
|
452
|
-
runnerId: z.ZodString;
|
|
453
|
-
expiresAt: z.ZodNumber;
|
|
454
|
-
}, z.core.$strip>;
|
|
455
|
-
type Lease = z.infer<typeof Lease>;
|
|
456
|
-
/** Payload union as it appears on a job record. */
|
|
457
|
-
declare const JobPayload: z.ZodUnion<readonly [z.ZodObject<{
|
|
458
|
-
prompt: z.ZodString;
|
|
459
|
-
system: z.ZodOptional<z.ZodString>;
|
|
460
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
461
|
-
messages: z.ZodArray<z.ZodObject<{
|
|
462
|
-
role: z.ZodEnum<{
|
|
463
|
-
system: "system";
|
|
464
|
-
user: "user";
|
|
465
|
-
assistant: "assistant";
|
|
466
|
-
}>;
|
|
467
|
-
content: z.ZodString;
|
|
468
|
-
}, z.core.$strip>>;
|
|
469
|
-
system: z.ZodOptional<z.ZodString>;
|
|
470
|
-
}, z.core.$strict>]>;
|
|
471
|
-
type JobPayload = z.infer<typeof JobPayload>;
|
|
472
|
-
/**
|
|
473
|
-
* A job as the daemon receives it from `/byollm/claim`.
|
|
474
|
-
*
|
|
475
|
-
* Note what is absent: no model, no backend, no base URL, no flags, no path.
|
|
476
|
-
* Those come from the machine owner's config only
|
|
477
|
-
* ({@link MUSTS.NO_PAYLOAD_ROUTING}). The wire shape is the first place that
|
|
478
|
-
* rule is enforced — there is no field to carry them.
|
|
479
|
-
*/
|
|
480
|
-
declare const ClaimedJob: z.ZodObject<{
|
|
481
|
-
id: z.ZodString;
|
|
138
|
+
declare const Capability: z.ZodObject<{
|
|
482
139
|
kind: z.ZodEnum<{
|
|
483
140
|
"llm.generate": "llm.generate";
|
|
484
141
|
"llm.chat": "llm.chat";
|
|
485
142
|
}>;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
lease: z.ZodObject<{
|
|
508
|
-
id: z.ZodString;
|
|
509
|
-
runnerId: z.ZodString;
|
|
510
|
-
expiresAt: z.ZodNumber;
|
|
511
|
-
}, z.core.$strip>;
|
|
512
|
-
}, z.core.$strict>;
|
|
513
|
-
type ClaimedJob = z.infer<typeof ClaimedJob>;
|
|
514
|
-
/**
|
|
515
|
-
* The provenance that travels with every result to the delivery seam.
|
|
516
|
-
*
|
|
517
|
-
* byollm_003 Rev 1: a `named`/`public` result is attacker-controlled text.
|
|
518
|
-
* The app must never render volunteer output as its own AI's answer without
|
|
519
|
-
* knowing that is what it is ({@link MUSTS.RESULT_PROVENANCE}).
|
|
520
|
-
*/
|
|
521
|
-
declare const ResultProvenance: z.ZodObject<{
|
|
522
|
-
audience: z.ZodEnum<{
|
|
523
|
-
self: "self";
|
|
524
|
-
named: "named";
|
|
525
|
-
public: "public";
|
|
143
|
+
service: z.ZodString;
|
|
144
|
+
backendId: z.ZodEnum<{
|
|
145
|
+
ollama: "ollama";
|
|
146
|
+
mlx: "mlx";
|
|
147
|
+
llamacpp: "llamacpp";
|
|
148
|
+
vllm: "vllm";
|
|
149
|
+
lmstudio: "lmstudio";
|
|
150
|
+
jan: "jan";
|
|
151
|
+
localai: "localai";
|
|
152
|
+
anthropic: "anthropic";
|
|
153
|
+
openai: "openai";
|
|
154
|
+
gemini: "gemini";
|
|
155
|
+
grok: "grok";
|
|
156
|
+
groq: "groq";
|
|
157
|
+
openrouter: "openrouter";
|
|
158
|
+
together: "together";
|
|
159
|
+
deepseek: "deepseek";
|
|
160
|
+
mistral: "mistral";
|
|
161
|
+
"openai-http": "openai-http";
|
|
162
|
+
"claude-cli": "claude-cli";
|
|
163
|
+
"codex-cli": "codex-cli";
|
|
526
164
|
}>;
|
|
527
|
-
runnerId: z.ZodString;
|
|
528
|
-
runnerOwner: z.ZodString;
|
|
529
165
|
backendClass: z.ZodEnum<{
|
|
530
166
|
http: "http";
|
|
531
167
|
process: "process";
|
|
532
168
|
}>;
|
|
533
169
|
model: z.ZodString;
|
|
534
|
-
|
|
170
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
171
|
+
offerScope: z.ZodEnum<{
|
|
172
|
+
private: "private";
|
|
173
|
+
team: "team";
|
|
174
|
+
}>;
|
|
535
175
|
}, z.core.$strict>;
|
|
536
|
-
type
|
|
537
|
-
/**
|
|
538
|
-
|
|
539
|
-
* supplied, so no caller can mark volunteer output as first-party.
|
|
540
|
-
*/
|
|
541
|
-
declare function provenanceFor(input: {
|
|
542
|
-
audience: Audience;
|
|
543
|
-
runnerId: string;
|
|
544
|
-
runnerOwner: string;
|
|
545
|
-
backendClass: BackendClass;
|
|
546
|
-
model: string;
|
|
547
|
-
}): ResultProvenance;
|
|
548
|
-
/** Successful outcome. */
|
|
549
|
-
declare const JobResultOk: z.ZodObject<{
|
|
550
|
-
outcome: z.ZodLiteral<"ok">;
|
|
551
|
-
text: z.ZodString;
|
|
552
|
-
artifactUrl: z.ZodOptional<z.ZodURL>;
|
|
553
|
-
}, z.core.$strict>;
|
|
554
|
-
/** Failed outcome. `code` is a stable machine string; `message` is for humans. */
|
|
555
|
-
declare const JobResultError: z.ZodObject<{
|
|
556
|
-
outcome: z.ZodLiteral<"error">;
|
|
557
|
-
code: z.ZodString;
|
|
558
|
-
message: z.ZodString;
|
|
559
|
-
retryable: z.ZodBoolean;
|
|
560
|
-
}, z.core.$strict>;
|
|
561
|
-
/** Cancelled outcome, reported by the daemon after honoring a cancel. */
|
|
562
|
-
declare const JobResultCanceled: z.ZodObject<{
|
|
563
|
-
outcome: z.ZodLiteral<"canceled">;
|
|
564
|
-
}, z.core.$strict>;
|
|
565
|
-
declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
566
|
-
outcome: z.ZodLiteral<"ok">;
|
|
567
|
-
text: z.ZodString;
|
|
568
|
-
artifactUrl: z.ZodOptional<z.ZodURL>;
|
|
569
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
570
|
-
outcome: z.ZodLiteral<"error">;
|
|
571
|
-
code: z.ZodString;
|
|
572
|
-
message: z.ZodString;
|
|
573
|
-
retryable: z.ZodBoolean;
|
|
574
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
575
|
-
outcome: z.ZodLiteral<"canceled">;
|
|
576
|
-
}, z.core.$strict>], "outcome">;
|
|
577
|
-
type JobOutcome = z.infer<typeof JobOutcome>;
|
|
578
|
-
/** A completed job as delivered to the app, provenance attached. */
|
|
579
|
-
declare const DeliveredResult: z.ZodObject<{
|
|
580
|
-
jobId: z.ZodString;
|
|
581
|
-
state: z.ZodEnum<{
|
|
582
|
-
ok: "ok";
|
|
583
|
-
error: "error";
|
|
584
|
-
queued: "queued";
|
|
585
|
-
claimed: "claimed";
|
|
586
|
-
running: "running";
|
|
587
|
-
canceled: "canceled";
|
|
588
|
-
expired: "expired";
|
|
589
|
-
}>;
|
|
590
|
-
outcome: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
591
|
-
outcome: z.ZodLiteral<"ok">;
|
|
592
|
-
text: z.ZodString;
|
|
593
|
-
artifactUrl: z.ZodOptional<z.ZodURL>;
|
|
594
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
595
|
-
outcome: z.ZodLiteral<"error">;
|
|
596
|
-
code: z.ZodString;
|
|
597
|
-
message: z.ZodString;
|
|
598
|
-
retryable: z.ZodBoolean;
|
|
599
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
600
|
-
outcome: z.ZodLiteral<"canceled">;
|
|
601
|
-
}, z.core.$strict>], "outcome">>;
|
|
602
|
-
provenance: z.ZodOptional<z.ZodObject<{
|
|
603
|
-
audience: z.ZodEnum<{
|
|
604
|
-
self: "self";
|
|
605
|
-
named: "named";
|
|
606
|
-
public: "public";
|
|
607
|
-
}>;
|
|
608
|
-
runnerId: z.ZodString;
|
|
609
|
-
runnerOwner: z.ZodString;
|
|
610
|
-
backendClass: z.ZodEnum<{
|
|
611
|
-
http: "http";
|
|
612
|
-
process: "process";
|
|
613
|
-
}>;
|
|
614
|
-
model: z.ZodString;
|
|
615
|
-
untrusted: z.ZodBoolean;
|
|
616
|
-
}, z.core.$strict>>;
|
|
617
|
-
}, z.core.$strict>;
|
|
618
|
-
type DeliveredResult = z.infer<typeof DeliveredResult>;
|
|
619
|
-
/**
|
|
620
|
-
* How big a payload is, in buckets — byollm_009 §6.
|
|
621
|
-
*
|
|
622
|
-
* A relay routes without reading, and matching a job to a machine needs some
|
|
623
|
-
* notion of size. Buckets rather than byte counts because the exact figure is
|
|
624
|
-
* a stronger fingerprint than the routing decision requires, and because a
|
|
625
|
-
* bucket survives compression and encoding changes that an exact count does
|
|
626
|
-
* not.
|
|
627
|
-
*
|
|
628
|
-
* `unbounded` exists for streamed jobs, which have no size when they start.
|
|
629
|
-
* It is reserved now rather than added later: byollm_009 §8.1 — adding a
|
|
630
|
-
* field to a published envelope is the v2 break all over again.
|
|
631
|
-
*/
|
|
632
|
-
declare const SizeClass: z.ZodEnum<{
|
|
633
|
-
small: "small";
|
|
634
|
-
medium: "medium";
|
|
635
|
-
large: "large";
|
|
636
|
-
unbounded: "unbounded";
|
|
637
|
-
}>;
|
|
638
|
-
type SizeClass = z.infer<typeof SizeClass>;
|
|
639
|
-
/** Where the bucket boundaries sit, in characters of payload text. */
|
|
640
|
-
declare const SIZE_CLASS_LIMITS: Readonly<{
|
|
641
|
-
small: 4000;
|
|
642
|
-
medium: 64000;
|
|
643
|
-
large: number;
|
|
644
|
-
}>;
|
|
645
|
-
/**
|
|
646
|
-
* The most a payload in this bucket can be.
|
|
647
|
-
*
|
|
648
|
-
* Used where a decision must be made from a stub, before the payload has been
|
|
649
|
-
* fetched — a budget check, for instance. Charging the bucket's ceiling is the
|
|
650
|
-
* conservative direction: it refuses slightly too eagerly rather than
|
|
651
|
-
* admitting work that turns out larger than the budget allowed.
|
|
652
|
-
*
|
|
653
|
-
* `unbounded` returns `Infinity`, which fails every ceiling. That is correct
|
|
654
|
-
* until byollm_006 defines how a streamed job is budgeted — failing closed on
|
|
655
|
-
* a case nobody has designed beats inventing an allowance for it.
|
|
656
|
-
*/
|
|
657
|
-
declare function sizeClassCeiling(sizeClass: SizeClass): number;
|
|
658
|
-
/** Bucket a payload by its text length. */
|
|
659
|
-
declare function sizeClassOf(textChars: number): SizeClass;
|
|
660
|
-
/**
|
|
661
|
-
* Everything an upstream may see about a job — byollm_009 §6.
|
|
662
|
-
*
|
|
663
|
-
* **This list is exhaustive and normative.** It is a commitment about the
|
|
664
|
-
* metadata surface, not an accident of what the implementation happens to
|
|
665
|
-
* send: an upstream that requires more has exceeded the protocol, and an
|
|
666
|
-
* endpoint that emits more has leaked past it
|
|
667
|
-
* ({@link MUSTS.STUB_METADATA_EXHAUSTIVE}).
|
|
668
|
-
*
|
|
669
|
-
* What is absent is the point. No payload, no model, no prompt, no result.
|
|
670
|
-
* `kind` is here because capability matching happens upstream; if a later
|
|
671
|
-
* revision moves matching to the daemon, `kind` moves into the ciphertext.
|
|
672
|
-
*/
|
|
673
|
-
declare const JobStub: z.ZodObject<{
|
|
674
|
-
id: z.ZodString;
|
|
176
|
+
type Capability = z.infer<typeof Capability>;
|
|
177
|
+
/** The capability matrix a daemon advertises. */
|
|
178
|
+
declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
|
|
675
179
|
kind: z.ZodEnum<{
|
|
676
180
|
"llm.generate": "llm.generate";
|
|
677
181
|
"llm.chat": "llm.chat";
|
|
678
182
|
}>;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
183
|
+
service: z.ZodString;
|
|
184
|
+
backendId: z.ZodEnum<{
|
|
185
|
+
ollama: "ollama";
|
|
186
|
+
mlx: "mlx";
|
|
187
|
+
llamacpp: "llamacpp";
|
|
188
|
+
vllm: "vllm";
|
|
189
|
+
lmstudio: "lmstudio";
|
|
190
|
+
jan: "jan";
|
|
191
|
+
localai: "localai";
|
|
192
|
+
anthropic: "anthropic";
|
|
193
|
+
openai: "openai";
|
|
194
|
+
gemini: "gemini";
|
|
195
|
+
grok: "grok";
|
|
196
|
+
groq: "groq";
|
|
197
|
+
openrouter: "openrouter";
|
|
198
|
+
together: "together";
|
|
199
|
+
deepseek: "deepseek";
|
|
200
|
+
mistral: "mistral";
|
|
201
|
+
"openai-http": "openai-http";
|
|
202
|
+
"claude-cli": "claude-cli";
|
|
203
|
+
"codex-cli": "codex-cli";
|
|
684
204
|
}>;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
medium: "medium";
|
|
689
|
-
large: "large";
|
|
690
|
-
unbounded: "unbounded";
|
|
205
|
+
backendClass: z.ZodEnum<{
|
|
206
|
+
http: "http";
|
|
207
|
+
process: "process";
|
|
691
208
|
}>;
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
209
|
+
model: z.ZodString;
|
|
210
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
211
|
+
offerScope: z.ZodEnum<{
|
|
212
|
+
private: "private";
|
|
213
|
+
team: "team";
|
|
214
|
+
}>;
|
|
215
|
+
}, z.core.$strict>>;
|
|
216
|
+
type CapabilityMatrix = z.infer<typeof CapabilityMatrix>;
|
|
217
|
+
/**
|
|
218
|
+
* A kind this device could serve and deliberately does not — byollm_016.
|
|
219
|
+
*
|
|
220
|
+
* Two services answer one kind and the owner has not said which wins, so the
|
|
221
|
+
* kind is not advertised. That is correct and, unsaid, invisible: the owner
|
|
222
|
+
* adds a second service, jobs stop matching, and no surface explains it.
|
|
223
|
+
*
|
|
224
|
+
* It travels because the surfaces that must say so are not all on the device.
|
|
225
|
+
* The owner's card names the claimants; a teammate's card says only that the
|
|
226
|
+
* owner has a choice to make. Claimant **offer scopes** ride along so the hub
|
|
227
|
+
* can compute that difference without the device deciding who is asking —
|
|
228
|
+
* carry for computation, filter for display, the same shape the effective
|
|
229
|
+
* offer already uses.
|
|
230
|
+
*/
|
|
231
|
+
declare const WithheldKind: z.ZodObject<{
|
|
699
232
|
kind: z.ZodEnum<{
|
|
700
233
|
"llm.generate": "llm.generate";
|
|
701
234
|
"llm.chat": "llm.chat";
|
|
702
235
|
}>;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
sizeClass: z.ZodEnum<{
|
|
711
|
-
small: "small";
|
|
712
|
-
medium: "medium";
|
|
713
|
-
large: "large";
|
|
714
|
-
unbounded: "unbounded";
|
|
715
|
-
}>;
|
|
716
|
-
streaming: z.ZodBoolean;
|
|
717
|
-
deadlineAt: z.ZodNumber;
|
|
718
|
-
lease: z.ZodObject<{
|
|
719
|
-
id: z.ZodString;
|
|
720
|
-
runnerId: z.ZodString;
|
|
721
|
-
expiresAt: z.ZodNumber;
|
|
722
|
-
}, z.core.$strip>;
|
|
236
|
+
claimants: z.ZodArray<z.ZodObject<{
|
|
237
|
+
service: z.ZodString;
|
|
238
|
+
offer: z.ZodEnum<{
|
|
239
|
+
private: "private";
|
|
240
|
+
team: "team";
|
|
241
|
+
}>;
|
|
242
|
+
}, z.core.$strict>>;
|
|
723
243
|
}, z.core.$strict>;
|
|
724
|
-
type
|
|
725
|
-
|
|
244
|
+
type WithheldKind = z.infer<typeof WithheldKind>;
|
|
726
245
|
/**
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
* **Two keypairs per party, and the split is load-bearing.** An Ed25519
|
|
730
|
-
* *identity* key signs; an X25519 *encryption* key receives sealed envelopes.
|
|
731
|
-
* The encryption key is signed by the identity key, and **the identity key is
|
|
732
|
-
* what gets pinned**. So "who sent this" and "who can read this" are answered
|
|
733
|
-
* by different keys — which is what lets an encryption key rotate without
|
|
734
|
-
* re-establishing trust, and what byollm_009 §6's signed-then-sealed envelope
|
|
735
|
-
* depends on.
|
|
246
|
+
* One grant, named by both halves — V1-3.
|
|
736
247
|
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
* install weight to a daemon that must land fast on a stranger's laptop.
|
|
248
|
+
* A job id is chosen per site, so the lease id is the unique thing an upstream
|
|
249
|
+
* and a daemon can both point at. Anywhere a request says "this piece of work,
|
|
250
|
+
* held by me", it says it with both.
|
|
741
251
|
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
* Node primitives is exactly the "novel construction" §2 rules out. Deferring
|
|
746
|
-
* the dependency is not the same as deferring the rule.
|
|
252
|
+
* Declared once because it was written out twice — `activeLeases` and
|
|
253
|
+
* `ReleaseRequest.leases` — and both needed the same `.strict()` added. Two
|
|
254
|
+
* copies of a shape are two places to forget it.
|
|
747
255
|
*/
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
encryption: z.ZodString;
|
|
752
|
-
encryptionSig: z.ZodString;
|
|
753
|
-
}, z.core.$strict>;
|
|
754
|
-
type PublicIdentity = z.infer<typeof PublicIdentity>;
|
|
755
|
-
/** Private key material, as stored on disk. Never leaves the machine. */
|
|
756
|
-
declare const StoredKeys: z.ZodObject<{
|
|
757
|
-
version: z.ZodLiteral<1>;
|
|
758
|
-
identityPublic: z.ZodString;
|
|
759
|
-
identityPrivate: z.ZodString;
|
|
760
|
-
encryptionPublic: z.ZodString;
|
|
761
|
-
encryptionPrivate: z.ZodString;
|
|
762
|
-
encryptionSig: z.ZodString;
|
|
763
|
-
createdAt: z.ZodNumber;
|
|
256
|
+
declare const GrantRef: z.ZodObject<{
|
|
257
|
+
jobId: z.ZodString;
|
|
258
|
+
leaseId: z.ZodString;
|
|
764
259
|
}, z.core.$strict>;
|
|
765
|
-
type
|
|
766
|
-
/** Generate a fresh pair of keypairs and bind them together. */
|
|
767
|
-
declare function generateKeys(now: number): StoredKeys;
|
|
768
|
-
/** The public half, for the wire. */
|
|
769
|
-
declare function publicIdentityOf(keys: StoredKeys): PublicIdentity;
|
|
260
|
+
type GrantRef = z.infer<typeof GrantRef>;
|
|
770
261
|
/**
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
* first sight.
|
|
262
|
+
* Pairing is a device-code exchange, not a pasted secret
|
|
263
|
+
* ({@link MUSTS.PAIR_INTERACTIVE}). The daemon starts a pairing, shows the
|
|
264
|
+
* user a short code and a URL, and polls until the user approves it inside
|
|
265
|
+
* the app's own authenticated session. Nothing listens on the user's machine
|
|
266
|
+
* and nothing works over a copied string alone.
|
|
777
267
|
*/
|
|
778
|
-
declare
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
268
|
+
declare const PairStartRequest: z.ZodObject<{
|
|
269
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
270
|
+
action: z.ZodLiteral<"start">;
|
|
271
|
+
daemon: z.ZodObject<{
|
|
272
|
+
version: z.ZodString;
|
|
273
|
+
label: z.ZodString;
|
|
274
|
+
platform: z.ZodEnum<{
|
|
275
|
+
darwin: "darwin";
|
|
276
|
+
linux: "linux";
|
|
277
|
+
win32: "win32";
|
|
278
|
+
}>;
|
|
279
|
+
}, z.core.$strict>;
|
|
280
|
+
device: z.ZodObject<{
|
|
281
|
+
identity: z.ZodString;
|
|
282
|
+
encryption: z.ZodString;
|
|
283
|
+
encryptionSig: z.ZodString;
|
|
284
|
+
}, z.core.$strict>;
|
|
285
|
+
capabilities: z.ZodArray<z.ZodObject<{
|
|
286
|
+
kind: z.ZodEnum<{
|
|
287
|
+
"llm.generate": "llm.generate";
|
|
288
|
+
"llm.chat": "llm.chat";
|
|
289
|
+
}>;
|
|
290
|
+
service: z.ZodString;
|
|
291
|
+
backendId: z.ZodEnum<{
|
|
292
|
+
ollama: "ollama";
|
|
293
|
+
mlx: "mlx";
|
|
294
|
+
llamacpp: "llamacpp";
|
|
295
|
+
vllm: "vllm";
|
|
296
|
+
lmstudio: "lmstudio";
|
|
297
|
+
jan: "jan";
|
|
298
|
+
localai: "localai";
|
|
299
|
+
anthropic: "anthropic";
|
|
300
|
+
openai: "openai";
|
|
301
|
+
gemini: "gemini";
|
|
302
|
+
grok: "grok";
|
|
303
|
+
groq: "groq";
|
|
304
|
+
openrouter: "openrouter";
|
|
305
|
+
together: "together";
|
|
306
|
+
deepseek: "deepseek";
|
|
307
|
+
mistral: "mistral";
|
|
308
|
+
"openai-http": "openai-http";
|
|
309
|
+
"claude-cli": "claude-cli";
|
|
310
|
+
"codex-cli": "codex-cli";
|
|
311
|
+
}>;
|
|
312
|
+
backendClass: z.ZodEnum<{
|
|
313
|
+
http: "http";
|
|
314
|
+
process: "process";
|
|
315
|
+
}>;
|
|
316
|
+
model: z.ZodString;
|
|
317
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
318
|
+
offerScope: z.ZodEnum<{
|
|
319
|
+
private: "private";
|
|
320
|
+
team: "team";
|
|
321
|
+
}>;
|
|
322
|
+
}, z.core.$strict>>;
|
|
323
|
+
}, z.core.$strict>;
|
|
324
|
+
type PairStartRequest = z.infer<typeof PairStartRequest>;
|
|
325
|
+
declare const PairStartResponse: z.ZodObject<{
|
|
326
|
+
deviceCode: z.ZodString;
|
|
327
|
+
userCode: z.ZodString;
|
|
328
|
+
verificationUrl: z.ZodURL;
|
|
329
|
+
expiresAt: z.ZodNumber;
|
|
330
|
+
pollIntervalMs: z.ZodNumber;
|
|
331
|
+
}, z.core.$strict>;
|
|
332
|
+
type PairStartResponse = z.infer<typeof PairStartResponse>;
|
|
333
|
+
declare const PairPollRequest: z.ZodObject<{
|
|
334
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
335
|
+
action: z.ZodLiteral<"poll">;
|
|
336
|
+
deviceCode: z.ZodString;
|
|
337
|
+
}, z.core.$strict>;
|
|
338
|
+
type PairPollRequest = z.infer<typeof PairPollRequest>;
|
|
339
|
+
declare const PairPollResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
340
|
+
status: z.ZodLiteral<"pending">;
|
|
341
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
342
|
+
status: z.ZodLiteral<"denied">;
|
|
343
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
344
|
+
status: z.ZodLiteral<"expired">;
|
|
345
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
346
|
+
status: z.ZodLiteral<"approved">;
|
|
347
|
+
runnerId: z.ZodString;
|
|
348
|
+
owner: z.ZodString;
|
|
349
|
+
ownerLabel: z.ZodOptional<z.ZodString>;
|
|
350
|
+
sites: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
351
|
+
identity: z.ZodString;
|
|
352
|
+
encryption: z.ZodString;
|
|
353
|
+
encryptionSig: z.ZodString;
|
|
354
|
+
}, z.core.$strict>>;
|
|
355
|
+
controlPlanePublic: z.ZodOptional<z.ZodString>;
|
|
356
|
+
}, z.core.$strict>], "status">;
|
|
357
|
+
type PairPollResponse = z.infer<typeof PairPollResponse>;
|
|
358
|
+
declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
359
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
360
|
+
action: z.ZodLiteral<"start">;
|
|
361
|
+
daemon: z.ZodObject<{
|
|
362
|
+
version: z.ZodString;
|
|
363
|
+
label: z.ZodString;
|
|
364
|
+
platform: z.ZodEnum<{
|
|
365
|
+
darwin: "darwin";
|
|
366
|
+
linux: "linux";
|
|
367
|
+
win32: "win32";
|
|
368
|
+
}>;
|
|
369
|
+
}, z.core.$strict>;
|
|
370
|
+
device: z.ZodObject<{
|
|
371
|
+
identity: z.ZodString;
|
|
372
|
+
encryption: z.ZodString;
|
|
373
|
+
encryptionSig: z.ZodString;
|
|
374
|
+
}, z.core.$strict>;
|
|
375
|
+
capabilities: z.ZodArray<z.ZodObject<{
|
|
376
|
+
kind: z.ZodEnum<{
|
|
377
|
+
"llm.generate": "llm.generate";
|
|
378
|
+
"llm.chat": "llm.chat";
|
|
379
|
+
}>;
|
|
380
|
+
service: z.ZodString;
|
|
381
|
+
backendId: z.ZodEnum<{
|
|
382
|
+
ollama: "ollama";
|
|
383
|
+
mlx: "mlx";
|
|
384
|
+
llamacpp: "llamacpp";
|
|
385
|
+
vllm: "vllm";
|
|
386
|
+
lmstudio: "lmstudio";
|
|
387
|
+
jan: "jan";
|
|
388
|
+
localai: "localai";
|
|
389
|
+
anthropic: "anthropic";
|
|
390
|
+
openai: "openai";
|
|
391
|
+
gemini: "gemini";
|
|
392
|
+
grok: "grok";
|
|
393
|
+
groq: "groq";
|
|
394
|
+
openrouter: "openrouter";
|
|
395
|
+
together: "together";
|
|
396
|
+
deepseek: "deepseek";
|
|
397
|
+
mistral: "mistral";
|
|
398
|
+
"openai-http": "openai-http";
|
|
399
|
+
"claude-cli": "claude-cli";
|
|
400
|
+
"codex-cli": "codex-cli";
|
|
401
|
+
}>;
|
|
402
|
+
backendClass: z.ZodEnum<{
|
|
403
|
+
http: "http";
|
|
404
|
+
process: "process";
|
|
405
|
+
}>;
|
|
406
|
+
model: z.ZodString;
|
|
407
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
408
|
+
offerScope: z.ZodEnum<{
|
|
409
|
+
private: "private";
|
|
410
|
+
team: "team";
|
|
411
|
+
}>;
|
|
412
|
+
}, z.core.$strict>>;
|
|
413
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
414
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
415
|
+
action: z.ZodLiteral<"poll">;
|
|
416
|
+
deviceCode: z.ZodString;
|
|
417
|
+
}, z.core.$strict>], "action">;
|
|
418
|
+
type PairRequest = z.infer<typeof PairRequest>;
|
|
419
|
+
declare const ClaimRequest: z.ZodObject<{
|
|
420
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
421
|
+
runnerId: z.ZodString;
|
|
422
|
+
capabilities: z.ZodArray<z.ZodObject<{
|
|
423
|
+
kind: z.ZodEnum<{
|
|
424
|
+
"llm.generate": "llm.generate";
|
|
425
|
+
"llm.chat": "llm.chat";
|
|
426
|
+
}>;
|
|
427
|
+
service: z.ZodString;
|
|
428
|
+
backendId: z.ZodEnum<{
|
|
429
|
+
ollama: "ollama";
|
|
430
|
+
mlx: "mlx";
|
|
431
|
+
llamacpp: "llamacpp";
|
|
432
|
+
vllm: "vllm";
|
|
433
|
+
lmstudio: "lmstudio";
|
|
434
|
+
jan: "jan";
|
|
435
|
+
localai: "localai";
|
|
436
|
+
anthropic: "anthropic";
|
|
437
|
+
openai: "openai";
|
|
438
|
+
gemini: "gemini";
|
|
439
|
+
grok: "grok";
|
|
440
|
+
groq: "groq";
|
|
441
|
+
openrouter: "openrouter";
|
|
442
|
+
together: "together";
|
|
443
|
+
deepseek: "deepseek";
|
|
444
|
+
mistral: "mistral";
|
|
445
|
+
"openai-http": "openai-http";
|
|
446
|
+
"claude-cli": "claude-cli";
|
|
447
|
+
"codex-cli": "codex-cli";
|
|
448
|
+
}>;
|
|
449
|
+
backendClass: z.ZodEnum<{
|
|
450
|
+
http: "http";
|
|
451
|
+
process: "process";
|
|
452
|
+
}>;
|
|
453
|
+
model: z.ZodString;
|
|
454
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
455
|
+
offerScope: z.ZodEnum<{
|
|
456
|
+
private: "private";
|
|
457
|
+
team: "team";
|
|
458
|
+
}>;
|
|
459
|
+
}, z.core.$strict>>;
|
|
460
|
+
max: z.ZodNumber;
|
|
461
|
+
}, z.core.$strict>;
|
|
462
|
+
type ClaimRequest = z.infer<typeof ClaimRequest>;
|
|
463
|
+
declare const ClaimResponse: z.ZodObject<{
|
|
464
|
+
jobs: z.ZodArray<z.ZodObject<{
|
|
465
|
+
id: z.ZodString;
|
|
466
|
+
kind: z.ZodEnum<{
|
|
467
|
+
"llm.generate": "llm.generate";
|
|
468
|
+
"llm.chat": "llm.chat";
|
|
469
|
+
}>;
|
|
470
|
+
owner: z.ZodString;
|
|
471
|
+
site: z.ZodString;
|
|
472
|
+
audience: z.ZodEnum<{
|
|
473
|
+
private: "private";
|
|
474
|
+
team: "team";
|
|
475
|
+
}>;
|
|
476
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
477
|
+
sizeClass: z.ZodEnum<{
|
|
478
|
+
small: "small";
|
|
479
|
+
medium: "medium";
|
|
480
|
+
large: "large";
|
|
481
|
+
unbounded: "unbounded";
|
|
482
|
+
}>;
|
|
483
|
+
streaming: z.ZodBoolean;
|
|
484
|
+
deadlineAt: z.ZodNumber;
|
|
485
|
+
lease: z.ZodObject<{
|
|
486
|
+
id: z.ZodString;
|
|
487
|
+
runnerId: z.ZodString;
|
|
488
|
+
expiresAt: z.ZodNumber;
|
|
489
|
+
}, z.core.$strict>;
|
|
490
|
+
grant: z.ZodOptional<z.ZodObject<{
|
|
491
|
+
grantId: z.ZodString;
|
|
492
|
+
jobId: z.ZodString;
|
|
493
|
+
site: z.ZodString;
|
|
494
|
+
user: z.ZodString;
|
|
495
|
+
owner: z.ZodString;
|
|
496
|
+
purpose: z.ZodString;
|
|
497
|
+
kind: z.ZodString;
|
|
498
|
+
service: z.ZodString;
|
|
499
|
+
issuedAt: z.ZodNumber;
|
|
500
|
+
signature: z.ZodString;
|
|
501
|
+
}, z.core.$strict>>;
|
|
502
|
+
}, z.core.$strict>>;
|
|
503
|
+
leaseMs: z.ZodNumber;
|
|
504
|
+
}, z.core.$strict>;
|
|
505
|
+
type ClaimResponse = z.infer<typeof ClaimResponse>;
|
|
506
|
+
declare const HeartbeatRequest: z.ZodObject<{
|
|
507
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
508
|
+
runnerId: z.ZodString;
|
|
509
|
+
daemonVersion: z.ZodString;
|
|
510
|
+
capabilities: z.ZodArray<z.ZodObject<{
|
|
511
|
+
kind: z.ZodEnum<{
|
|
512
|
+
"llm.generate": "llm.generate";
|
|
513
|
+
"llm.chat": "llm.chat";
|
|
514
|
+
}>;
|
|
515
|
+
service: z.ZodString;
|
|
516
|
+
backendId: z.ZodEnum<{
|
|
517
|
+
ollama: "ollama";
|
|
518
|
+
mlx: "mlx";
|
|
519
|
+
llamacpp: "llamacpp";
|
|
520
|
+
vllm: "vllm";
|
|
521
|
+
lmstudio: "lmstudio";
|
|
522
|
+
jan: "jan";
|
|
523
|
+
localai: "localai";
|
|
524
|
+
anthropic: "anthropic";
|
|
525
|
+
openai: "openai";
|
|
526
|
+
gemini: "gemini";
|
|
527
|
+
grok: "grok";
|
|
528
|
+
groq: "groq";
|
|
529
|
+
openrouter: "openrouter";
|
|
530
|
+
together: "together";
|
|
531
|
+
deepseek: "deepseek";
|
|
532
|
+
mistral: "mistral";
|
|
533
|
+
"openai-http": "openai-http";
|
|
534
|
+
"claude-cli": "claude-cli";
|
|
535
|
+
"codex-cli": "codex-cli";
|
|
536
|
+
}>;
|
|
537
|
+
backendClass: z.ZodEnum<{
|
|
538
|
+
http: "http";
|
|
539
|
+
process: "process";
|
|
540
|
+
}>;
|
|
541
|
+
model: z.ZodString;
|
|
542
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
543
|
+
offerScope: z.ZodEnum<{
|
|
544
|
+
private: "private";
|
|
545
|
+
team: "team";
|
|
546
|
+
}>;
|
|
547
|
+
}, z.core.$strict>>;
|
|
548
|
+
withheld: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
549
|
+
kind: z.ZodEnum<{
|
|
550
|
+
"llm.generate": "llm.generate";
|
|
551
|
+
"llm.chat": "llm.chat";
|
|
552
|
+
}>;
|
|
553
|
+
claimants: z.ZodArray<z.ZodObject<{
|
|
554
|
+
service: z.ZodString;
|
|
555
|
+
offer: z.ZodEnum<{
|
|
556
|
+
private: "private";
|
|
557
|
+
team: "team";
|
|
558
|
+
}>;
|
|
559
|
+
}, z.core.$strict>>;
|
|
560
|
+
}, z.core.$strict>>>;
|
|
561
|
+
activeLeases: z.ZodArray<z.ZodObject<{
|
|
562
|
+
jobId: z.ZodString;
|
|
563
|
+
leaseId: z.ZodString;
|
|
564
|
+
}, z.core.$strict>>;
|
|
565
|
+
paused: z.ZodBoolean;
|
|
566
|
+
}, z.core.$strict>;
|
|
567
|
+
type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;
|
|
568
|
+
declare const HeartbeatResponse: z.ZodObject<{
|
|
569
|
+
sites: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
570
|
+
identity: z.ZodString;
|
|
571
|
+
encryption: z.ZodString;
|
|
572
|
+
encryptionSig: z.ZodString;
|
|
573
|
+
}, z.core.$strict>>;
|
|
574
|
+
successions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
575
|
+
succeeds: z.ZodArray<z.ZodObject<{
|
|
576
|
+
identity: z.ZodObject<{
|
|
577
|
+
identity: z.ZodString;
|
|
578
|
+
encryption: z.ZodString;
|
|
579
|
+
encryptionSig: z.ZodString;
|
|
580
|
+
}, z.core.$strict>;
|
|
581
|
+
signature: z.ZodString;
|
|
582
|
+
}, z.core.$strict>>;
|
|
583
|
+
retiringUntil: z.ZodOptional<z.ZodNumber>;
|
|
584
|
+
}, z.core.$strict>>>;
|
|
585
|
+
cancel: z.ZodArray<z.ZodObject<{
|
|
586
|
+
jobId: z.ZodString;
|
|
587
|
+
leaseId: z.ZodString;
|
|
588
|
+
}, z.core.$strict>>;
|
|
589
|
+
lost: z.ZodArray<z.ZodObject<{
|
|
590
|
+
jobId: z.ZodString;
|
|
591
|
+
leaseId: z.ZodString;
|
|
592
|
+
}, z.core.$strict>>;
|
|
593
|
+
serverTime: z.ZodNumber;
|
|
594
|
+
awaitingConsent: z.ZodArray<z.ZodString>;
|
|
595
|
+
updateTo: z.ZodOptional<z.ZodString>;
|
|
596
|
+
}, z.core.$strict>;
|
|
597
|
+
type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
|
|
598
|
+
/**
|
|
599
|
+
* What an intermediary learns about how a job ended — byollm_009 §6.
|
|
600
|
+
*
|
|
601
|
+
* The discriminator and nothing else. A relay has to know a job reached a
|
|
602
|
+
* terminal state, and whether it failed, because that decides whether the job
|
|
603
|
+
* leaves the queue or the app may re-enqueue. It does not have to know what
|
|
604
|
+
* the model said, or what an error said, and this is where that line is drawn.
|
|
605
|
+
*
|
|
606
|
+
* Kept identical to `JobOutcome`'s discriminator rather than coarsened to
|
|
607
|
+
* ok/not-ok: a cancelled job and a failed one are different routing outcomes,
|
|
608
|
+
* and collapsing them would make the relay guess.
|
|
609
|
+
*/
|
|
610
|
+
declare const ResultDisposition: z.ZodEnum<{
|
|
611
|
+
error: "error";
|
|
612
|
+
ok: "ok";
|
|
613
|
+
canceled: "canceled";
|
|
614
|
+
}>;
|
|
615
|
+
type ResultDisposition = z.infer<typeof ResultDisposition>;
|
|
616
|
+
declare const ResultRequest: z.ZodObject<{
|
|
617
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
618
|
+
runnerId: z.ZodString;
|
|
619
|
+
jobId: z.ZodString;
|
|
620
|
+
leaseId: z.ZodString;
|
|
621
|
+
envelope: z.ZodObject<{
|
|
622
|
+
ciphertext: z.ZodString;
|
|
623
|
+
recipientKeyId: z.ZodString;
|
|
624
|
+
senderKeyId: z.ZodString;
|
|
625
|
+
direction: z.ZodEnum<{
|
|
626
|
+
payload: "payload";
|
|
627
|
+
result: "result";
|
|
628
|
+
}>;
|
|
629
|
+
deadlineAt: z.ZodNumber;
|
|
630
|
+
}, z.core.$strict>;
|
|
631
|
+
disposition: z.ZodEnum<{
|
|
632
|
+
error: "error";
|
|
633
|
+
ok: "ok";
|
|
634
|
+
canceled: "canceled";
|
|
635
|
+
}>;
|
|
636
|
+
}, z.core.$strict>;
|
|
637
|
+
type ResultRequest = z.infer<typeof ResultRequest>;
|
|
638
|
+
declare const ResultResponse: z.ZodObject<{
|
|
639
|
+
accepted: z.ZodBoolean;
|
|
640
|
+
duplicate: z.ZodOptional<z.ZodBoolean>;
|
|
641
|
+
state: z.ZodString;
|
|
642
|
+
}, z.core.$strict>;
|
|
643
|
+
type ResultResponse = z.infer<typeof ResultResponse>;
|
|
644
|
+
declare const ReleaseRequest: z.ZodObject<{
|
|
645
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
646
|
+
runnerId: z.ZodString;
|
|
647
|
+
leases: z.ZodArray<z.ZodObject<{
|
|
648
|
+
jobId: z.ZodString;
|
|
649
|
+
leaseId: z.ZodString;
|
|
650
|
+
}, z.core.$strict>>;
|
|
651
|
+
reason: z.ZodEnum<{
|
|
652
|
+
refused: "refused";
|
|
653
|
+
shutdown: "shutdown";
|
|
654
|
+
pause: "pause";
|
|
655
|
+
revoked: "revoked";
|
|
656
|
+
"backend-down": "backend-down";
|
|
657
|
+
}>;
|
|
658
|
+
}, z.core.$strict>;
|
|
659
|
+
type ReleaseRequest = z.infer<typeof ReleaseRequest>;
|
|
660
|
+
declare const ReleaseResponse: z.ZodObject<{
|
|
661
|
+
released: z.ZodArray<z.ZodString>;
|
|
662
|
+
}, z.core.$strict>;
|
|
663
|
+
type ReleaseResponse = z.infer<typeof ReleaseResponse>;
|
|
664
|
+
/**
|
|
665
|
+
* Wire error codes.
|
|
666
|
+
*
|
|
667
|
+
* byollm_002 requires that "server unreachable", "revoked", "no matching
|
|
668
|
+
* work" and "backend down" never share a message. Distinct codes here are how
|
|
669
|
+
* the daemon can tell three of those apart; the fourth is a transport failure
|
|
670
|
+
* with no response at all.
|
|
671
|
+
*/
|
|
672
|
+
declare const WireErrorCode: z.ZodEnum<{
|
|
673
|
+
"unsupported-protocol-version": "unsupported-protocol-version";
|
|
674
|
+
revoked: "revoked";
|
|
675
|
+
"bad-request": "bad-request";
|
|
676
|
+
"daemon-below-floor": "daemon-below-floor";
|
|
677
|
+
unauthorized: "unauthorized";
|
|
678
|
+
forbidden: "forbidden";
|
|
679
|
+
"not-found": "not-found";
|
|
680
|
+
"not-ready": "not-ready";
|
|
681
|
+
"too-late": "too-late";
|
|
682
|
+
"clock-skew": "clock-skew";
|
|
683
|
+
"rate-limited": "rate-limited";
|
|
684
|
+
"server-error": "server-error";
|
|
685
|
+
}>;
|
|
686
|
+
type WireErrorCode = z.infer<typeof WireErrorCode>;
|
|
687
|
+
declare const WireError: z.ZodObject<{
|
|
688
|
+
error: z.ZodEnum<{
|
|
689
|
+
"unsupported-protocol-version": "unsupported-protocol-version";
|
|
690
|
+
revoked: "revoked";
|
|
691
|
+
"bad-request": "bad-request";
|
|
692
|
+
"daemon-below-floor": "daemon-below-floor";
|
|
693
|
+
unauthorized: "unauthorized";
|
|
694
|
+
forbidden: "forbidden";
|
|
695
|
+
"not-found": "not-found";
|
|
696
|
+
"not-ready": "not-ready";
|
|
697
|
+
"too-late": "too-late";
|
|
698
|
+
"clock-skew": "clock-skew";
|
|
699
|
+
"rate-limited": "rate-limited";
|
|
700
|
+
"server-error": "server-error";
|
|
701
|
+
}>;
|
|
702
|
+
message: z.ZodString;
|
|
703
|
+
supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
704
|
+
minimum: z.ZodOptional<z.ZodString>;
|
|
705
|
+
floor: z.ZodOptional<z.ZodString>;
|
|
706
|
+
retryAfter: z.ZodOptional<z.ZodNumber>;
|
|
707
|
+
serverTime: z.ZodOptional<z.ZodNumber>;
|
|
708
|
+
maxSkewMs: z.ZodOptional<z.ZodNumber>;
|
|
709
|
+
}, z.core.$strict>;
|
|
710
|
+
type WireError = z.infer<typeof WireError>;
|
|
711
|
+
/** HTTP status each error code is served with. */
|
|
712
|
+
declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
|
|
713
|
+
declare const FetchRequest: z.ZodObject<{
|
|
714
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
715
|
+
runnerId: z.ZodString;
|
|
716
|
+
jobId: z.ZodString;
|
|
717
|
+
leaseId: z.ZodString;
|
|
718
|
+
}, z.core.$strict>;
|
|
719
|
+
type FetchRequest = z.infer<typeof FetchRequest>;
|
|
720
|
+
declare const FetchResponse: z.ZodObject<{
|
|
721
|
+
envelope: z.ZodObject<{
|
|
722
|
+
ciphertext: z.ZodString;
|
|
723
|
+
recipientKeyId: z.ZodString;
|
|
724
|
+
senderKeyId: z.ZodString;
|
|
725
|
+
direction: z.ZodEnum<{
|
|
726
|
+
payload: "payload";
|
|
727
|
+
result: "result";
|
|
728
|
+
}>;
|
|
729
|
+
deadlineAt: z.ZodNumber;
|
|
730
|
+
}, z.core.$strict>;
|
|
731
|
+
}, z.core.$strict>;
|
|
732
|
+
type FetchResponse = z.infer<typeof FetchResponse>;
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Who may be told about a new version — B053.
|
|
736
|
+
*
|
|
737
|
+
* `HeartbeatResponse.updateTo` is a new field on a `.strict()` schema, and
|
|
738
|
+
* strict means a daemon built before the field does not ignore it: it rejects
|
|
739
|
+
* the entire heartbeat. Send it to everybody and the message carrying the
|
|
740
|
+
* update is the message that takes offline the machines it was meant to
|
|
741
|
+
* update — the fleet-wide version of the failure this codebase keeps meeting
|
|
742
|
+
* one surface at a time.
|
|
743
|
+
*
|
|
744
|
+
* No handshake is needed to avoid that. `HeartbeatRequest.daemonVersion` is
|
|
745
|
+
* already on the wire, so the sender can simply decline to say anything a
|
|
746
|
+
* given listener cannot hear. The rule lives here, next to the field it
|
|
747
|
+
* governs, because a rule of this kind in a runbook is a rule that holds
|
|
748
|
+
* until the next person deploys.
|
|
749
|
+
*
|
|
750
|
+
* ## The other direction, deliberately not taken
|
|
751
|
+
*
|
|
752
|
+
* The tidier-looking design is a capability list on the request — the daemon
|
|
753
|
+
* says what it understands. It has a hole this one does not: that field is
|
|
754
|
+
* also new, the hub's schema is also strict, and an upgraded daemon sending
|
|
755
|
+
* it to a hub that has not deployed yet is refused outright. It makes the
|
|
756
|
+
* daemon's upgrade depend on the hub's, in a system where the daemon is the
|
|
757
|
+
* side we do not control the timing of.
|
|
758
|
+
*
|
|
759
|
+
* Reading a field that already exists has no such ordering problem: old
|
|
760
|
+
* daemons are never sent the new field, new daemons are, and neither needs
|
|
761
|
+
* the other to have moved first.
|
|
762
|
+
*/
|
|
763
|
+
/**
|
|
764
|
+
* The first daemon version whose schema has `updateTo` in it.
|
|
765
|
+
*
|
|
766
|
+
* Raising this is safe and lowering it is not, which is worth knowing before
|
|
767
|
+
* anybody tidies it: too high means some daemons miss an update they could
|
|
768
|
+
* have taken, and too low means their heartbeats are refused.
|
|
769
|
+
*/
|
|
770
|
+
declare const UPDATE_OFFER_SINCE = "0.1.0-alpha.83";
|
|
771
|
+
/**
|
|
772
|
+
* Semver ordering, only as far as this needs it.
|
|
773
|
+
*
|
|
774
|
+
* Numeric prerelease parts compare as numbers, which is the whole reason not
|
|
775
|
+
* to compare these as strings: `alpha.9` sorts after `alpha.83`
|
|
776
|
+
* lexicographically, and that mistake here means every daemon between .10 and
|
|
777
|
+
* .82 is treated as too old to hear about an update — or worse, in the other
|
|
778
|
+
* direction, sent a field it cannot parse.
|
|
779
|
+
*/
|
|
780
|
+
declare function compareVersions(a: string, b: string): number | undefined;
|
|
781
|
+
/**
|
|
782
|
+
* May this daemon be told about a new version?
|
|
783
|
+
*
|
|
784
|
+
* **A version this cannot parse is a no.** Unreadable is not permission: the
|
|
785
|
+
* consequence of guessing wrong in that direction is the daemon's heartbeat
|
|
786
|
+
* being refused, which is worse than it missing one update cycle. The same
|
|
787
|
+
* rule the rest of this protocol applies to unreadable answers, on the one
|
|
788
|
+
* field where getting it wrong is fleet-shaped.
|
|
789
|
+
*/
|
|
790
|
+
declare function mayOfferUpdate(daemonVersion: string): boolean;
|
|
791
|
+
/**
|
|
792
|
+
* The oldest daemon a hub will serve — B052, the floor.
|
|
793
|
+
*
|
|
794
|
+
* The updater's backstop and its opposite number. The updater moves machines
|
|
795
|
+
* that opted in; the floor is what moves the ones that did not, and it is the
|
|
796
|
+
* only mechanism that works on a daemon which is not listening for offers.
|
|
797
|
+
*
|
|
798
|
+
* Raising it is a deliberate act with a spec note, never automatic — a floor
|
|
799
|
+
* that followed `latest` would refuse every machine that had not updated in
|
|
800
|
+
* the last hour, which is the outage version of hygiene.
|
|
801
|
+
*/
|
|
802
|
+
interface FloorRefusal {
|
|
803
|
+
readonly error: "daemon-below-floor";
|
|
804
|
+
readonly message: string;
|
|
805
|
+
readonly floor: string;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Is this daemon too old to serve?
|
|
809
|
+
*
|
|
810
|
+
* **An unreadable version is NOT refused**, and that is the deliberate
|
|
811
|
+
* asymmetry with {@link mayOfferUpdate}, which treats an unreadable version
|
|
812
|
+
* as "do not offer". The two point the same way once you ask what the
|
|
813
|
+
* mistake costs: there, guessing yes sends a field that breaks the
|
|
814
|
+
* heartbeat; here, guessing yes takes a working machine out of service over
|
|
815
|
+
* a string it could not parse. Both decline to act when they cannot tell,
|
|
816
|
+
* and declining to act means opposite booleans.
|
|
817
|
+
*/
|
|
818
|
+
declare function checkDaemonFloor(input: {
|
|
819
|
+
readonly daemonVersion: string;
|
|
820
|
+
readonly floor: string;
|
|
821
|
+
/** How somebody fixes it. The floor is useless without the remedy. */
|
|
822
|
+
readonly upgradeCommand: string;
|
|
823
|
+
}): FloorRefusal | null;
|
|
824
|
+
/**
|
|
825
|
+
* The only way to put `updateTo` on a heartbeat — D1's first deploy
|
|
826
|
+
* condition, made structural.
|
|
827
|
+
*
|
|
828
|
+
* The condition was "the hub must not be ABLE to set `updateTo` raw". A rule
|
|
829
|
+
* that says "remember to call `mayOfferUpdate` first" is a rule that holds
|
|
830
|
+
* until somebody adds a second return site — and the heartbeat handler
|
|
831
|
+
* already has two. So the fence is not a thing to remember; it is the only
|
|
832
|
+
* function that produces the field, and it applies the check inside itself.
|
|
833
|
+
*
|
|
834
|
+
* Returns a spreadable object rather than a value, so the call site reads
|
|
835
|
+
* `...updateOfferFor(...)` and there is no `updateTo:` anywhere for a later
|
|
836
|
+
* hand to copy.
|
|
837
|
+
*
|
|
838
|
+
* Absent `offer` means this deployment is offering nothing, which is the
|
|
839
|
+
* default and the safe one: a hub that has not been told a version says
|
|
840
|
+
* nothing to anybody.
|
|
841
|
+
*/
|
|
842
|
+
declare function updateOfferFor(input: {
|
|
843
|
+
readonly offer: string | undefined;
|
|
844
|
+
readonly daemonVersion: string;
|
|
845
|
+
}): {
|
|
846
|
+
readonly updateTo?: string;
|
|
847
|
+
};
|
|
848
|
+
/**
|
|
849
|
+
* Source with its comments removed, for the checks below.
|
|
850
|
+
*
|
|
851
|
+
* Comments go first because a rule about a field has to be explainable in
|
|
852
|
+
* prose beside the code it governs — and the first version of the relay's
|
|
853
|
+
* fence test flagged its own explanation, which was written to say there
|
|
854
|
+
* must be no such write anywhere.
|
|
855
|
+
*/
|
|
856
|
+
declare function withoutComments(source: string): string;
|
|
857
|
+
/**
|
|
858
|
+
* Does this source mention a wire field at all?
|
|
859
|
+
*
|
|
860
|
+
* **A word, not a spelling.** The relay's fence test shipped scanning for
|
|
861
|
+
* `/\bupdateTo\s*:/`, which is one syntax of one way to set a property —
|
|
862
|
+
* `"updateTo": offer`, `res.updateTo = offer`, `res["updateTo"] = offer` and
|
|
863
|
+
* shorthand in a spread all walk straight past it. The check's claim was
|
|
864
|
+
* "there is nowhere else to call from"; what it enforced was a spelling.
|
|
865
|
+
* That is our own recurring law, and this is its sixth instance — the first
|
|
866
|
+
* inside a test written to be structural rather than remembered.
|
|
867
|
+
*
|
|
868
|
+
* So the question this asks is deliberately blunter than the rule it serves.
|
|
869
|
+
* A mention is not a write, and the false positives are the point: anything
|
|
870
|
+
* that names the field in code has to be looked at by a person, because the
|
|
871
|
+
* cost of the miss is every daemon that predates the field rejecting every
|
|
872
|
+
* heartbeat.
|
|
873
|
+
*
|
|
874
|
+
* Lives here, beside {@link updateOfferFor}, so the hub and the relay run one
|
|
875
|
+
* copy rather than two that drift — the field's hazard is fleet-wide and it
|
|
876
|
+
* crosses a repository boundary, which is exactly where a duplicated rule
|
|
877
|
+
* stops being the same rule.
|
|
878
|
+
*/
|
|
879
|
+
declare function mentionsWireField(source: string, field: string): boolean;
|
|
880
|
+
|
|
881
|
+
/** The full description — five sections, plus why it matters. */
|
|
882
|
+
declare const ABOUT = "# About BYOLLM\n\n**What BYOLLM is**\n\nBYOLLM lets you use your own AI on websites. You install one small program on\nyour computer. Then, websites that support BYOLLM can use the AI you already\nhave \u2014 a free model running on your machine, or an AI service you already pay\nfor \u2014 instead of the website paying for AI and passing the cost to you.\n\n**Why it matters**\n\nFor you:\n\n- Your favorite model, everywhere you go.\n- New models the moment you get them \u2013 not when a site gets around to adding\n them.\n- Encrypted end-to-end. Your prompts go to your own device; byollm.cloud can't\n read them.\n- Sites never learn which model you use, and your subscriptions are never\n shared.\n- Pay less. Sites that don't pay for AI can charge you less \u2013 or nothing.\n\nFor sites and developers:\n\n- Zero AI bills. Your users bring their own compute.\n- No floating money \u2013 you don't pay LLM bills up front and hope to collect\n later, and you never ask people to prepay just to try you.\n- Free trials that cost you nothing to offer.\n- Ship the AI features you kept private for fear of the API bill.\n- One small integration. Your users choose the models.\n\n**Your device**\n\nThe `byollm` program runs on your computer. It knows which AI services you have\nset up: free open-source models on your machine, metered services you pay per\nuse, or your own subscriptions like Claude Pro/Max. When a website you have\nenabled sends work, your device runs it with the service you chose. Your\nprompts are encrypted end-to-end to your own device. byollm.cloud passes them\nalong and cannot read them.\n\n**Sites**\n\nA website that wants to use BYOLLM says what it needs \u2014 \"writing help,\" \"chat,\"\nand so on. When you connect the site, you pick which of your services answers\neach one. The site never learns which model you use. You can turn a site off at\nany time, and it stops getting your work.\n\n**Teams (optional)**\n\nA team lets you share what runs on your devices with people you name \u2014 the free\nopen-source models on your machine, or a metered service with a spending limit\nyou set. Your subscription accounts (like Claude Pro/Max) are never shared with\nanyone. That is a rule, not a setting.\n\n**byollm.cloud (or your own relay)**\n\nMany sites, many devices, many people. byollm.cloud keeps track of who has\nallowed what and sends each job to the right device. It never sees your\nprompts. If you would rather run this part yourself, the relay is open source \u2014\nyou can run your own instead of using byollm.cloud.";
|
|
883
|
+
/**
|
|
884
|
+
* The first paragraph, which stands alone.
|
|
885
|
+
*
|
|
886
|
+
* What the welcome screen shows: somebody deciding whether to trust a site's
|
|
887
|
+
* button needs the whole idea in one breath, not a page.
|
|
888
|
+
*/
|
|
889
|
+
declare const ABOUT_SHORT_LEDE = "BYOLLM \u2013 Bring Your Own LLM \u2013 lets you use your own AI on websites you authorize. A small program installed on your machine lets you use your own models and subscriptions on any BYOLLM-integrated site, including new models the moment you get access \u2013 no site updates required. BYOLLM Cloud connects sites to your devices with end-to-end encryption, so no one, including us, can see your data.";
|
|
890
|
+
/** The rest, for surfaces with room. Shown before "Learn more →". */
|
|
891
|
+
declare const ABOUT_SHORT_TAIL = "Sites can charge you less because you bring your own \u2013 see why that matters \u2192. Teams can optionally share the free or metered services on their devices with people they name. Personal subscriptions are never shared.";
|
|
892
|
+
/** Both halves, for a surface that wants the paragraph entire. */
|
|
893
|
+
declare const ABOUT_SHORT = "BYOLLM \u2013 Bring Your Own LLM \u2013 lets you use your own AI on websites you authorize. A small program installed on your machine lets you use your own models and subscriptions on any BYOLLM-integrated site, including new models the moment you get access \u2013 no site updates required. BYOLLM Cloud connects sites to your devices with end-to-end encryption, so no one, including us, can see your data.\n\nSites can charge you less because you bring your own \u2013 see why that matters \u2192. Teams can optionally share the free or metered services on their devices with people they name. Personal subscriptions are never shared.";
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Its members, for anything that has to report what it accepts.
|
|
897
|
+
*
|
|
898
|
+
* Derived from the enum for the reason `JOB_KINDS` and `OFFER_SCOPES` are: a
|
|
899
|
+
* second list of the same words is a second thing to keep in step, and this
|
|
900
|
+
* one is read by the promotion gate to compare a deployed hub against a
|
|
901
|
+
* version about to be promoted.
|
|
902
|
+
*/
|
|
903
|
+
declare const BACKEND_CLASSES: readonly ("http" | "process")[];
|
|
904
|
+
/**
|
|
905
|
+
* How a backend reaches its model — the taxonomy introduced in byollm_001
|
|
906
|
+
* Rev 1 §A, because the two classes have different threat surfaces.
|
|
907
|
+
*
|
|
908
|
+
* - `http`: an OpenAI-compatible HTTP server (Ollama, `mlx_lm.server`,
|
|
909
|
+
* llama.cpp server, vLLM, and every hosted provider that speaks the same
|
|
910
|
+
* wire format). Spawns nothing, so byollm_004 §2's argv, stdin, env and
|
|
911
|
+
* sandbox requirements are not applicable by construction. Its threat
|
|
912
|
+
* surface is SSRF-shaped and bounded by {@link MUSTS.HTTP_BASE_URL_SAFE}.
|
|
913
|
+
* - `process`: spawns a binary (`claude` CLI today, `mlx_lm.lora` for a
|
|
914
|
+
* future `train.*` kind). All of byollm_004 §2 is mandatory here.
|
|
915
|
+
*/
|
|
916
|
+
declare const BackendClass: z.ZodEnum<{
|
|
917
|
+
http: "http";
|
|
918
|
+
process: "process";
|
|
919
|
+
}>;
|
|
920
|
+
type BackendClass = z.infer<typeof BackendClass>;
|
|
921
|
+
/**
|
|
922
|
+
* Who pays, and how — byollm_007.
|
|
923
|
+
*
|
|
924
|
+
* This replaced a two-valued `account` field that conflated two unrelated
|
|
925
|
+
* constraints and, in doing so, left a hole: `openai-http` was "open", but it
|
|
926
|
+
* accepts an API key, so an owner could point it at a paid endpoint, share it,
|
|
927
|
+
* and donate their credit balance to strangers. The community budgets cap job
|
|
928
|
+
* *count*, not spend.
|
|
929
|
+
*
|
|
930
|
+
* - `free` — local compute. Costs electricity, not money. Shareable.
|
|
931
|
+
* - `metered` — per-token billing against the owner's account. Legal to
|
|
932
|
+
* share and ruinous to share by accident.
|
|
933
|
+
* - `subscription` — a vendor account whose terms forbid third-party work.
|
|
934
|
+
* Sharing is a terms violation, not merely expensive.
|
|
935
|
+
*/
|
|
936
|
+
declare const BackendCost: z.ZodEnum<{
|
|
937
|
+
free: "free";
|
|
938
|
+
metered: "metered";
|
|
939
|
+
subscription: "subscription";
|
|
940
|
+
}>;
|
|
941
|
+
type BackendCost = z.infer<typeof BackendCost>;
|
|
942
|
+
/** The immutable facts about a backend that the protocol reasons over. */
|
|
943
|
+
interface BackendDescriptor {
|
|
944
|
+
/** Stable backend id, as written in `byollm.config.json`. */
|
|
945
|
+
readonly id: string;
|
|
946
|
+
/** Human-readable name for the trust UI. */
|
|
947
|
+
readonly label: string;
|
|
948
|
+
/** Determines which isolation requirements apply. */
|
|
949
|
+
readonly class: BackendClass;
|
|
950
|
+
/**
|
|
951
|
+
* Who pays. Fixed here for every named provider and **not overridable by
|
|
952
|
+
* configuration** ({@link MUSTS.COST_NOT_CONFIGURABLE}) — `openai` is
|
|
953
|
+
* metered because it is, and no setting changes that.
|
|
954
|
+
*
|
|
955
|
+
* `null` only for the generic {@link BACKENDS."openai-http"} entry, whose
|
|
956
|
+
* cost is inferred from its base URL instead
|
|
957
|
+
* ({@link MUSTS.REMOTE_IS_NEVER_FREE}).
|
|
958
|
+
*/
|
|
959
|
+
readonly cost: BackendCost | null;
|
|
960
|
+
/**
|
|
961
|
+
* Which adversarial corpus byollm_004 §5 runs against this backend. A
|
|
962
|
+
* backend cannot be registered without one — the coverage check in the
|
|
963
|
+
* adversarial suite enforces it.
|
|
964
|
+
*/
|
|
965
|
+
readonly adversarialCorpus: "process" | "http";
|
|
966
|
+
/**
|
|
967
|
+
* Where this provider lives, when that is knowable. Owner config may
|
|
968
|
+
* override it; a provider with no default requires one to be given.
|
|
969
|
+
*/
|
|
970
|
+
readonly defaultBaseUrl?: string;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* The backend registry.
|
|
974
|
+
*
|
|
975
|
+
* **Providers are entries, not implementations.** Every HTTP-class provider
|
|
976
|
+
* below shares the single `openai-http` transport, because they all speak
|
|
977
|
+
* OpenAI-compatible `/v1/chat/completions`. An entry adds a stable id, a cost
|
|
978
|
+
* class the owner cannot override, and a default base URL. Adding a provider
|
|
979
|
+
* is therefore one line and no new code — which is why the adversarial corpus
|
|
980
|
+
* still covers all of them, and why a PR adding one is reviewable at a glance.
|
|
981
|
+
*/
|
|
982
|
+
declare const BACKENDS: Readonly<{
|
|
983
|
+
readonly ollama: BackendDescriptor;
|
|
984
|
+
readonly mlx: BackendDescriptor;
|
|
985
|
+
readonly llamacpp: BackendDescriptor;
|
|
986
|
+
readonly vllm: BackendDescriptor;
|
|
987
|
+
readonly lmstudio: BackendDescriptor;
|
|
988
|
+
readonly jan: BackendDescriptor;
|
|
989
|
+
readonly localai: BackendDescriptor;
|
|
990
|
+
/**
|
|
991
|
+
* Note the pair: `anthropic` and {@link BACKENDS."claude-cli"} reach the
|
|
992
|
+
* same vendor and land in different cost classes. That is not an
|
|
993
|
+
* inconsistency — it is the axis working. One bills a key per token, the
|
|
994
|
+
* other runs under a personal plan whose terms cover one person's work. Who
|
|
995
|
+
* pays and under what terms is the question; which company is not.
|
|
996
|
+
*/
|
|
997
|
+
readonly anthropic: BackendDescriptor;
|
|
998
|
+
readonly openai: BackendDescriptor;
|
|
999
|
+
readonly gemini: BackendDescriptor;
|
|
1000
|
+
readonly grok: BackendDescriptor;
|
|
1001
|
+
readonly groq: BackendDescriptor;
|
|
1002
|
+
readonly openrouter: BackendDescriptor;
|
|
1003
|
+
readonly together: BackendDescriptor;
|
|
1004
|
+
readonly deepseek: BackendDescriptor;
|
|
1005
|
+
readonly mistral: BackendDescriptor;
|
|
1006
|
+
readonly "openai-http": BackendDescriptor;
|
|
1007
|
+
readonly "claude-cli": BackendDescriptor;
|
|
1008
|
+
/**
|
|
1009
|
+
* OpenAI's Codex CLI, on a ChatGPT plan — byollm_016 stage 3.
|
|
1010
|
+
*
|
|
1011
|
+
* `subscription`, so `SUBSCRIPTION_SELF_LOCK` pins it to its owner's own
|
|
1012
|
+
* work whatever the config says. That is load-bearing here in a way it is
|
|
1013
|
+
* not for `claude-cli`: Codex is an *agent*, and its default feature set
|
|
1014
|
+
* includes a shell tool, browser control and computer use. The daemon
|
|
1015
|
+
* disables every one of them, verified against the shipped binary rather
|
|
1016
|
+
* than assumed — see `codex-cli.ts` — but the self-lock is the floor under
|
|
1017
|
+
* that verification rather than a duplicate of it.
|
|
1018
|
+
*/
|
|
1019
|
+
readonly "codex-cli": BackendDescriptor;
|
|
1020
|
+
}>;
|
|
1021
|
+
/** The id of a registered backend. */
|
|
1022
|
+
type BackendId = keyof typeof BACKENDS;
|
|
1023
|
+
/** All registered backend ids — the adversarial coverage check iterates this. */
|
|
1024
|
+
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" | "codex-cli")[];
|
|
1025
|
+
declare const BackendIdSchema: z.ZodEnum<{
|
|
1026
|
+
ollama: "ollama";
|
|
1027
|
+
mlx: "mlx";
|
|
1028
|
+
llamacpp: "llamacpp";
|
|
1029
|
+
vllm: "vllm";
|
|
1030
|
+
lmstudio: "lmstudio";
|
|
1031
|
+
jan: "jan";
|
|
1032
|
+
localai: "localai";
|
|
1033
|
+
anthropic: "anthropic";
|
|
1034
|
+
openai: "openai";
|
|
1035
|
+
gemini: "gemini";
|
|
1036
|
+
grok: "grok";
|
|
1037
|
+
groq: "groq";
|
|
1038
|
+
openrouter: "openrouter";
|
|
1039
|
+
together: "together";
|
|
1040
|
+
deepseek: "deepseek";
|
|
1041
|
+
mistral: "mistral";
|
|
1042
|
+
"openai-http": "openai-http";
|
|
1043
|
+
"claude-cli": "claude-cli";
|
|
1044
|
+
"codex-cli": "codex-cli";
|
|
1045
|
+
}>;
|
|
1046
|
+
/** Narrow an arbitrary string to a registered backend id. */
|
|
1047
|
+
declare function isBackendId(value: string): value is BackendId;
|
|
1048
|
+
/**
|
|
1049
|
+
* Look up a backend descriptor.
|
|
785
1050
|
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1051
|
+
* @throws if the id is not registered — an unregistered backend has no
|
|
1052
|
+
* adversarial corpus, so refusing is the safe direction.
|
|
1053
|
+
*/
|
|
1054
|
+
declare function backendDescriptor(id: BackendId): BackendDescriptor;
|
|
1055
|
+
/**
|
|
1056
|
+
* Is this host local enough that compute there is free?
|
|
791
1057
|
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
1058
|
+
* Loopback and the private ranges only. This is the rule that makes
|
|
1059
|
+
* {@link MUSTS.REMOTE_IS_NEVER_FREE} enforceable rather than a promise: an
|
|
1060
|
+
* owner cannot reach a paid API through the generic backend and call it free,
|
|
1061
|
+
* because "free" is derived from the address, not from what the config claims.
|
|
1062
|
+
*
|
|
1063
|
+
* **What this cannot see.** The address is all it reads. A proxy on
|
|
1064
|
+
* `127.0.0.1` forwarding to a paid API classes as `free` and nothing
|
|
1065
|
+
* downstream will contradict it. That is deliberate: standing up a relay is
|
|
1066
|
+
* an act by the machine's owner against their own account, and the threat
|
|
1067
|
+
* model here is a hostile *job*, not an owner routing around a rule that
|
|
1068
|
+
* exists to protect them. What this catches is the accident — a remote paid
|
|
1069
|
+
* endpoint offered to a team because nobody thought about the bill. See
|
|
1070
|
+
* `docs/security.md` §4a.
|
|
794
1071
|
*/
|
|
795
|
-
declare function
|
|
796
|
-
/** The short id used in envelopes and provenance. Stable, and comparable. */
|
|
797
|
-
declare const keyId: (identityPublic: string) => string;
|
|
798
|
-
|
|
799
|
-
declare function cryptoReady(): Promise<void>;
|
|
1072
|
+
declare function isLocalHost(hostname: string): boolean;
|
|
800
1073
|
/**
|
|
801
|
-
*
|
|
1074
|
+
* Is this model name a hosted one billed by its vendor?
|
|
1075
|
+
*
|
|
1076
|
+
* Ollama serves cloud models through the same local endpoint as local ones,
|
|
1077
|
+
* so the address says "free" about a model somebody is being charged for. The
|
|
1078
|
+
* only thing that distinguishes them is the name, and the distinguishing part
|
|
1079
|
+
* is the **tag** — everything after the last colon.
|
|
1080
|
+
*
|
|
1081
|
+
* End-anchored on the tag, which is what makes it decidable rather than a
|
|
1082
|
+
* guess about substrings:
|
|
1083
|
+
*
|
|
1084
|
+
* - `glm-5.2:cloud` → cloud
|
|
1085
|
+
* - `deepseek-v4-flash:0731-cloud` → cloud
|
|
1086
|
+
* - `x:cloudless` → not cloud, the tag ends in "less"
|
|
1087
|
+
* - `cloudmodel:7b` → not cloud, the tag is "7b"
|
|
1088
|
+
* - `llama3.2` → not cloud, there is no tag at all
|
|
1089
|
+
*
|
|
1090
|
+
* An oddball like `:xcloud` classifies as cloud, and that is the **only
|
|
1091
|
+
* permitted failure direction**: calling a free model metered narrows what an
|
|
1092
|
+
* owner may share and costs nobody money, while the reverse hands somebody
|
|
1093
|
+
* else's bill to a stranger.
|
|
1094
|
+
*/
|
|
1095
|
+
declare function isCloudTaggedModel(model: string): boolean;
|
|
1096
|
+
/**
|
|
1097
|
+
* The cost class of a configured service.
|
|
802
1098
|
*
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
*
|
|
806
|
-
*
|
|
1099
|
+
* For every named provider this is whatever the registry says, full stop
|
|
1100
|
+
* ({@link MUSTS.COST_NOT_CONFIGURABLE}). For the generic `openai-http` entry
|
|
1101
|
+
* it is inferred from the base URL, and a base URL that cannot be parsed is
|
|
1102
|
+
* treated as `metered` — the expensive side, because guessing "free" wrong
|
|
1103
|
+
* costs the owner money.
|
|
807
1104
|
*
|
|
808
|
-
*
|
|
809
|
-
*
|
|
1105
|
+
* The model has the last word in one direction only. A local address with a
|
|
1106
|
+
* cloud-tagged model is `metered`: Ollama proxies hosted models through
|
|
1107
|
+
* `127.0.0.1`, so the endpoint is local and the bill is not. Read from the
|
|
1108
|
+
* **configured value**, never from what the server lists — the owner's config
|
|
1109
|
+
* is the thing they chose, and a server's catalogue is not theirs to be
|
|
1110
|
+
* classified by.
|
|
810
1111
|
*/
|
|
811
|
-
declare
|
|
812
|
-
/**
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1112
|
+
declare function resolveCost(id: BackendId, baseUrl: string | undefined,
|
|
1113
|
+
/**
|
|
1114
|
+
* **Required, and that is the fix.**
|
|
1115
|
+
*
|
|
1116
|
+
* This was optional, and the no-re-derivation law was breached through the
|
|
1117
|
+
* gap rather than by anybody copying the logic. `byollm offer` passed two of
|
|
1118
|
+
* three arguments and `resolveConfig` passed three, so the same service was
|
|
1119
|
+
* free to one and metered to the other: `glm-5.2:cloud` on a loopback
|
|
1120
|
+
* address looks local until you read the tag. The command wrote a share the
|
|
1121
|
+
* daemon then refused, and told its owner to run the command they had just
|
|
1122
|
+
* run.
|
|
1123
|
+
*
|
|
1124
|
+
* A shared rule's signature admits no partial askers. `undefined` is still a
|
|
1125
|
+
* legal *value* — a service genuinely without a model — but it has to be
|
|
1126
|
+
* passed, so choosing to omit the model is a decision at the call site
|
|
1127
|
+
* rather than a default nobody notices.
|
|
1128
|
+
*/
|
|
1129
|
+
model: string | undefined): BackendCost;
|
|
1130
|
+
/**
|
|
1131
|
+
* Why a service costs what it costs — the same decision, said out loud.
|
|
1132
|
+
*
|
|
1133
|
+
* Consent has to name the rule that fired. The offer ceremony read
|
|
1134
|
+
* "Any OpenAI-compatible server ... bills your account per token", which is
|
|
1135
|
+
* false about the type — an owner's local qwen is `openai-http` and costs
|
|
1136
|
+
* nothing but electricity — and so it gave a reason that its reader could
|
|
1137
|
+
* check and find wrong. The thing that bills is the `:cloud` tag on one
|
|
1138
|
+
* model, not the transport that carries it.
|
|
1139
|
+
*
|
|
1140
|
+
* One function decides and one function explains, and the second calls the
|
|
1141
|
+
* first, so a message can never describe a classification the code did not
|
|
1142
|
+
* make. Splitting them would be the same defect this signature was just
|
|
1143
|
+
* hardened against, arriving as prose.
|
|
1144
|
+
*/
|
|
1145
|
+
/**
|
|
1146
|
+
* The product's name alone, without the parenthetical that classifies it.
|
|
1147
|
+
*
|
|
1148
|
+
* Every label in this registry does two jobs: it names a product and says what
|
|
1149
|
+
* that product means for the person paying — "Claude CLI (your subscription)",
|
|
1150
|
+
* "Ollama (local)". That is right for a list, where the parenthetical is the
|
|
1151
|
+
* only classification on screen.
|
|
1152
|
+
*
|
|
1153
|
+
* It is wrong inside a sentence that states the classification itself, which
|
|
1154
|
+
* then stutters: "my-claude runs on Claude CLI (your subscription), a
|
|
1155
|
+
* subscription whose terms…". Prose wants the name; the sentence around it is
|
|
1156
|
+
* already carrying the meaning.
|
|
1157
|
+
*
|
|
1158
|
+
* One definition rather than a regex at each call site — and the place to
|
|
1159
|
+
* change if the registry ever splits the two facts into two fields, which is
|
|
1160
|
+
* the better shape and not worth a migration today.
|
|
1161
|
+
*/
|
|
1162
|
+
declare function backendName(id: BackendId): string;
|
|
1163
|
+
interface CostReason {
|
|
1164
|
+
readonly cost: BackendCost;
|
|
1165
|
+
/** The rule, in the words a person consenting needs. */
|
|
1166
|
+
readonly because: string;
|
|
836
1167
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1168
|
+
declare function classifyCost(id: BackendId, baseUrl: string | undefined, model: string | undefined): CostReason;
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Who may run a job, declared by the app that enqueued it.
|
|
1172
|
+
*
|
|
1173
|
+
* - `private` — only the job owner's own devices.
|
|
1174
|
+
* - `team` — a device whose owner admits this person.
|
|
1175
|
+
*
|
|
1176
|
+
* **One vocabulary, ruled 2026-08-24.** These were `self | named | public`
|
|
1177
|
+
* while {@link OfferScope} used different words for the same idea, which would
|
|
1178
|
+
* have left every seam where the two meet speaking two languages, and every
|
|
1179
|
+
* doc explaining "self versus private" for ever. They are still independent
|
|
1180
|
+
* axes — a job says who may run it, a service says who it will run for — and a
|
|
1181
|
+
* job runs only where both agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}).
|
|
1182
|
+
*
|
|
1183
|
+
* **`public` is gone, ruled 2026-08-26 (byollm_016).** Not deprecated,
|
|
1184
|
+
* removed, and removed from the OSS daemon too rather than parked as a
|
|
1185
|
+
* community posture. The argument was a measurement rather than a preference:
|
|
1186
|
+
* device-side admission had never once been exercised end to end, because
|
|
1187
|
+
* every cross-user test ran against a publicly offered service and
|
|
1188
|
+
* {@link matchAudience} returned ALLOWED for those *without consulting the
|
|
1189
|
+
* device at all*. `public` was the off switch for admission, and an enum with
|
|
1190
|
+
* a value that skips verification is a fail-open waiting for the wiring bug
|
|
1191
|
+
* that reaches it. There is now no such value.
|
|
1192
|
+
*/
|
|
1193
|
+
declare const Audience: z.ZodEnum<{
|
|
1194
|
+
private: "private";
|
|
1195
|
+
team: "team";
|
|
1196
|
+
}>;
|
|
1197
|
+
type Audience = z.infer<typeof Audience>;
|
|
1198
|
+
/**
|
|
1199
|
+
* What a device's owner is willing to run for other people, per service.
|
|
1200
|
+
*
|
|
1201
|
+
* - `private` — the owner's own work only.
|
|
1202
|
+
* - `team` — whoever the owner's authority admits. Membership is **central**,
|
|
1203
|
+
* not per-person: the device follows what it is told by a signature it can
|
|
1204
|
+
* check, rather than holding its own copy of who is in it (byollm_016).
|
|
1205
|
+
*
|
|
1206
|
+
* Two values, and no third that means "everyone". See {@link Audience} for
|
|
1207
|
+
* why `public` was removed rather than parked, and note the shape of the
|
|
1208
|
+
* remaining enum: **every value left requires the device to verify
|
|
1209
|
+
* something.** `private` checks the owner; `team` checks admission. That is
|
|
1210
|
+
* the property, not an accident of there being two.
|
|
1211
|
+
*/
|
|
1212
|
+
declare const OfferScope: z.ZodEnum<{
|
|
1213
|
+
private: "private";
|
|
1214
|
+
team: "team";
|
|
1215
|
+
}>;
|
|
1216
|
+
type OfferScope = z.infer<typeof OfferScope>;
|
|
1217
|
+
/** All audience values, in widening order. */
|
|
1218
|
+
declare const AUDIENCES: readonly ("private" | "team")[];
|
|
1219
|
+
/** All offer scopes, in widening order. */
|
|
1220
|
+
declare const OFFER_SCOPES: readonly ("private" | "team")[];
|
|
1221
|
+
/**
|
|
1222
|
+
* Why a job was refused. Distinct codes because byollm_002 requires that
|
|
1223
|
+
* different truths never share a message — "no matching work" and "refused on
|
|
1224
|
+
* principle" are not the same event, and a volunteer debugging their setup
|
|
1225
|
+
* needs to know which one happened.
|
|
1226
|
+
*/
|
|
1227
|
+
declare const MatchRefusal: z.ZodEnum<{
|
|
1228
|
+
"no-capability": "no-capability";
|
|
1229
|
+
"audience-self-other-owner": "audience-self-other-owner";
|
|
1230
|
+
"not-locally-allowed": "not-locally-allowed";
|
|
1231
|
+
"not-in-server-allowlist": "not-in-server-allowlist";
|
|
1232
|
+
"offer-scope-too-narrow": "offer-scope-too-narrow";
|
|
1233
|
+
"subscription-self-lock": "subscription-self-lock";
|
|
1234
|
+
"metered-no-spend-consent": "metered-no-spend-consent";
|
|
1235
|
+
"metered-ceiling-reached": "metered-ceiling-reached";
|
|
1236
|
+
}>;
|
|
1237
|
+
type MatchRefusal = z.infer<typeof MatchRefusal>;
|
|
1238
|
+
/** The outcome of an audience match. */
|
|
1239
|
+
type MatchResult = {
|
|
847
1240
|
readonly ok: true;
|
|
848
|
-
readonly plaintext: string;
|
|
849
1241
|
} | {
|
|
850
1242
|
readonly ok: false;
|
|
851
|
-
readonly
|
|
1243
|
+
readonly refusal: MatchRefusal;
|
|
852
1244
|
};
|
|
1245
|
+
/** What the owner has agreed to spend on other people's work, if anything. */
|
|
1246
|
+
interface SpendConsent {
|
|
1247
|
+
/** The owner explicitly acknowledged that sharing this backend costs money. */
|
|
1248
|
+
readonly acknowledged: boolean;
|
|
1249
|
+
/** Their ceiling. Absent means no ceiling was set, which is not consent. */
|
|
1250
|
+
readonly ceilingReached?: boolean;
|
|
1251
|
+
}
|
|
853
1252
|
/**
|
|
854
|
-
*
|
|
1253
|
+
* The effective offer scope of a backend.
|
|
855
1254
|
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
1255
|
+
* Three rules, applied at the one place both the daemon's config loader and
|
|
1256
|
+
* its matcher call, so no code path can observe a scope wider than the cost
|
|
1257
|
+
* class allows:
|
|
858
1258
|
*
|
|
859
|
-
*
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
*
|
|
1259
|
+
* - `subscription` is locked to `private` regardless of config
|
|
1260
|
+
* ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — someone else's terms.
|
|
1261
|
+
* - `metered` narrows to `private` unless the owner has explicitly acknowledged
|
|
1262
|
+
* the spend ({@link MUSTS.METERED_DEFAULTS_SELF}) — their money.
|
|
1263
|
+
* - `free` passes through — their electricity.
|
|
1264
|
+
*
|
|
1265
|
+
* Note the asymmetry: subscription can never be widened, metered can be
|
|
1266
|
+
* widened deliberately. Conflating those was byollm_007's bug.
|
|
863
1267
|
*/
|
|
864
|
-
declare function
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1268
|
+
declare function effectiveOfferScope(configured: OfferScope, cost: BackendCost, spend?: SpendConsent): OfferScope;
|
|
1269
|
+
/** The job-side facts a match needs. */
|
|
1270
|
+
interface MatchJob {
|
|
1271
|
+
/** The app's id for the user who enqueued the job. */
|
|
1272
|
+
readonly owner: string;
|
|
1273
|
+
/** Who the app says may run it. */
|
|
1274
|
+
readonly audience: Audience;
|
|
1275
|
+
/**
|
|
1276
|
+
* Optional server-side restriction on which runner owners may take a
|
|
1277
|
+
* `team` job. Defence in depth only, and direct-mode only — it never
|
|
1278
|
+
* reaches a daemon (cloud_008 §0.2), so the device's own admission is the
|
|
1279
|
+
* enforcing side ({@link MUSTS.NAMED_LOCAL_ALLOWLIST}).
|
|
1280
|
+
*/
|
|
1281
|
+
readonly audienceAllow?: readonly string[] | undefined;
|
|
1282
|
+
}
|
|
1283
|
+
/** The daemon-side facts a match needs. */
|
|
1284
|
+
interface MatchDaemon {
|
|
1285
|
+
/** The app's id for the user this daemon is paired to. */
|
|
1286
|
+
readonly owner: string;
|
|
1287
|
+
/** Effective scope of the backend that would run the job. */
|
|
1288
|
+
readonly offerScope: OfferScope;
|
|
1289
|
+
/** Who pays for that backend's tokens. */
|
|
1290
|
+
readonly cost: BackendCost;
|
|
1291
|
+
/** What the owner agreed to spend on others, for a `metered` backend. */
|
|
1292
|
+
readonly spend?: SpendConsent | undefined;
|
|
1293
|
+
/**
|
|
1294
|
+
* Has something **this device verified** admitted the job's owner?
|
|
1295
|
+
*
|
|
1296
|
+
* A predicate rather than a value so the protocol package stays free of
|
|
1297
|
+
* both file I/O and signature state. What supplies it has changed twice and
|
|
1298
|
+
* will change again — a local allowlist, then a held roster, and now a
|
|
1299
|
+
* claim-time signed grant (Amendment J) — and the law it feeds has not
|
|
1300
|
+
* changed at all: a `team` service runs a stranger's work only when
|
|
1301
|
+
* somebody this device can check said so.
|
|
1302
|
+
*
|
|
1303
|
+
* The server passes a conservative `() => true`: it cannot know what a
|
|
1304
|
+
* remote device verified and must not pretend to. The device is the
|
|
1305
|
+
* enforcing side, which is the whole point of asking here.
|
|
1306
|
+
*
|
|
1307
|
+
* Named for the question, not for where the answer lives. This was called
|
|
1308
|
+
* `locallyAllows`, and "locally" stopped being true the moment the answer
|
|
1309
|
+
* came from a document somebody else signed.
|
|
1310
|
+
*/
|
|
1311
|
+
readonly admits: (owner: string) => boolean;
|
|
1312
|
+
}
|
|
872
1313
|
/**
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
* Every authenticated call is signed by the calling device's identity key.
|
|
876
|
-
* There is no bearer token on the daemon plane: possession of a file no
|
|
877
|
-
* longer grants access, possession of a *key* does, and the key never leaves
|
|
878
|
-
* the machine.
|
|
879
|
-
*
|
|
880
|
-
* ## Why this is not the server-issued nonce the spec first described
|
|
1314
|
+
* Decide whether a job may run on a daemon.
|
|
881
1315
|
*
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
* credential, which is the thing being removed.
|
|
1316
|
+
* Both sides must agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}):
|
|
1317
|
+
* 1. the job's audience must admit the daemon's owner, and
|
|
1318
|
+
* 2. the backend's offer scope must admit the job's owner.
|
|
886
1319
|
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
*
|
|
891
|
-
* `RESULT_IDEMPOTENT` makes a replayed result a no-op, a replayed claim from
|
|
892
|
-
* the same runner returns what that runner already holds, and heartbeat and
|
|
893
|
-
* release are idempotent in effect. So a replay inside the freshness window
|
|
894
|
-
* gains an attacker nothing they could not obtain by forwarding the original,
|
|
895
|
-
* which a relay can do anyway.
|
|
1320
|
+
* The full four-way matrix (two audiences × two offer scopes) is asserted by
|
|
1321
|
+
* the conformance kit. The function is pure and total so both the daemon
|
|
1322
|
+
* and the server can run the identical rule — the daemon refuses, and the
|
|
1323
|
+
* server refuses too (byollm_003 §Server-side MUSTs).
|
|
896
1324
|
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
1325
|
+
* @example
|
|
1326
|
+
* ```ts
|
|
1327
|
+
* const result = matchAudience(
|
|
1328
|
+
* { owner: "alice", audience: "team" },
|
|
1329
|
+
* {
|
|
1330
|
+
* owner: "bob",
|
|
1331
|
+
* offerScope: "team",
|
|
1332
|
+
* cost: "free",
|
|
1333
|
+
* admits: (o) => o === "alice",
|
|
1334
|
+
* },
|
|
1335
|
+
* );
|
|
1336
|
+
* // result.ok === true
|
|
1337
|
+
* ```
|
|
1338
|
+
*/
|
|
1339
|
+
declare function matchAudience(job: MatchJob, daemon: MatchDaemon): MatchResult;
|
|
1340
|
+
/**
|
|
1341
|
+
* Human-readable refusal text for the daemon's log and the trust UI.
|
|
1342
|
+
* Each refusal reads as a distinct truth — byollm_002's "four different
|
|
1343
|
+
* truths that must never share a message" applied to the audience axis.
|
|
1344
|
+
*/
|
|
1345
|
+
declare const REFUSAL_MESSAGES: Readonly<Record<MatchRefusal, string>>;
|
|
1346
|
+
|
|
1347
|
+
/**
|
|
1348
|
+
* Upper bounds on payload size, enforced at the schema so oversized input is
|
|
1349
|
+
* refused at parse time rather than somewhere deeper.
|
|
900
1350
|
*
|
|
901
|
-
*
|
|
902
|
-
*
|
|
903
|
-
*
|
|
904
|
-
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
* nothing. `release` was idempotent per lease and ambiguous across them:
|
|
908
|
-
* it named a job and a runner, both of which survive a
|
|
909
|
-
* claim-release-reclaim cycle, so a replayed release yanked a later grant.
|
|
910
|
-
* Fixed by giving a lease its own id and requiring it.
|
|
1351
|
+
* All three are enforced — cloud_008 Tier 4, finding 30. `maxTotalChars` was
|
|
1352
|
+
* declared here and referenced nowhere, under this docstring's claim that the
|
|
1353
|
+
* schema enforces them, so a chat payload of 256 messages at a million
|
|
1354
|
+
* characters each parsed cleanly at sixty-four times the stated ceiling. The
|
|
1355
|
+
* per-field limits were real and the aggregate one was a number in a frozen
|
|
1356
|
+
* object.
|
|
911
1357
|
*
|
|
912
|
-
*
|
|
913
|
-
*
|
|
914
|
-
*
|
|
1358
|
+
* byollm_004 §4 requires stricter limits for community (`team`) jobs; those
|
|
1359
|
+
* are applied on top of these by the daemon's budget check, which knows the
|
|
1360
|
+
* job's audience. These are the absolute ceilings for any job.
|
|
915
1361
|
*/
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1362
|
+
declare const PAYLOAD_LIMITS: Readonly<{
|
|
1363
|
+
/** Max characters in any single text field. */
|
|
1364
|
+
maxTextChars: 1000000;
|
|
1365
|
+
/** Max messages in an `llm.chat` conversation. */
|
|
1366
|
+
maxMessages: 256;
|
|
1367
|
+
/** Max characters across the whole payload. */
|
|
1368
|
+
maxTotalChars: 4000000;
|
|
1369
|
+
}>;
|
|
1370
|
+
/**
|
|
1371
|
+
* A conversation turn. `role` is a closed enum — it is routing *within the
|
|
1372
|
+
* model call*, not routing of the call, so it cannot select a backend.
|
|
1373
|
+
*/
|
|
1374
|
+
declare const ChatMessage: z.ZodObject<{
|
|
1375
|
+
role: z.ZodEnum<{
|
|
1376
|
+
user: "user";
|
|
1377
|
+
system: "system";
|
|
1378
|
+
assistant: "assistant";
|
|
1379
|
+
}>;
|
|
1380
|
+
content: z.ZodString;
|
|
923
1381
|
}, z.core.$strict>;
|
|
924
|
-
type
|
|
1382
|
+
type ChatMessage = z.infer<typeof ChatMessage>;
|
|
925
1383
|
/**
|
|
926
|
-
*
|
|
927
|
-
*
|
|
928
|
-
* Newline-separated with a version prefix and a domain separator. Every field
|
|
929
|
-
* that decides what the request *does* is in here: leave one out and it
|
|
930
|
-
* becomes something an intermediary can change without breaking the
|
|
931
|
-
* signature.
|
|
1384
|
+
* Payload for `llm.generate`.
|
|
932
1385
|
*
|
|
933
|
-
*
|
|
934
|
-
*
|
|
1386
|
+
* @remarks
|
|
1387
|
+
* Text only, deliberately. byollm_004 §1 states the payload is "data handed
|
|
1388
|
+
* to a model, never configuration and never a command", so v0 carries no
|
|
1389
|
+
* sampling parameters, no model name, no base URL and no flags — those are
|
|
1390
|
+
* owner-side route config. A future `params` field with an explicit closed
|
|
1391
|
+
* allowlist and owner-set clamps is reserved; adding a field later is
|
|
1392
|
+
* non-breaking, removing one is not.
|
|
935
1393
|
*/
|
|
936
|
-
declare
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
}
|
|
1394
|
+
declare const GeneratePayload: z.ZodObject<{
|
|
1395
|
+
prompt: z.ZodString;
|
|
1396
|
+
system: z.ZodOptional<z.ZodString>;
|
|
1397
|
+
}, z.core.$strict>;
|
|
1398
|
+
type GeneratePayload = z.infer<typeof GeneratePayload>;
|
|
1399
|
+
/** Payload for `llm.chat`. Text only, for the same reason as {@link GeneratePayload}. */
|
|
1400
|
+
declare const ChatPayload: z.ZodObject<{
|
|
1401
|
+
messages: z.ZodArray<z.ZodObject<{
|
|
1402
|
+
role: z.ZodEnum<{
|
|
1403
|
+
user: "user";
|
|
1404
|
+
system: "system";
|
|
1405
|
+
assistant: "assistant";
|
|
1406
|
+
}>;
|
|
1407
|
+
content: z.ZodString;
|
|
1408
|
+
}, z.core.$strict>>;
|
|
1409
|
+
system: z.ZodOptional<z.ZodString>;
|
|
1410
|
+
}, z.core.$strict>;
|
|
1411
|
+
type ChatPayload = z.infer<typeof ChatPayload>;
|
|
949
1412
|
/**
|
|
950
|
-
* The
|
|
951
|
-
*
|
|
952
|
-
* A site talking to a relay is in exactly the daemon's position — an outbound
|
|
953
|
-
* caller with an identity keypair the other side already pins — so it gets the
|
|
954
|
-
* daemon's authentication rather than a second scheme. Bearer tokens for the
|
|
955
|
-
* site plane were the alternative, and they would have reintroduced the
|
|
956
|
-
* credential-in-a-file that §4.2 removed from the daemon plane, on the plane
|
|
957
|
-
* that carries *every* site's traffic.
|
|
1413
|
+
* The job kinds a v1 daemon has handlers for.
|
|
958
1414
|
*
|
|
959
|
-
*
|
|
1415
|
+
* Kinds are resolved against handlers baked into the daemon
|
|
1416
|
+
* ({@link MUSTS.KIND_TYPED_ONLY}); an unknown kind is refused, never guessed.
|
|
1417
|
+
* Adding a kind is a protocol change with its own spec and threat review —
|
|
1418
|
+
* notably any kind that needs tools, which byollm_004 §2 forbids as a payload
|
|
1419
|
+
* flag.
|
|
1420
|
+
*/
|
|
1421
|
+
declare const JobKind: z.ZodEnum<{
|
|
1422
|
+
"llm.generate": "llm.generate";
|
|
1423
|
+
"llm.chat": "llm.chat";
|
|
1424
|
+
}>;
|
|
1425
|
+
type JobKind = z.infer<typeof JobKind>;
|
|
1426
|
+
/** All v1 job kinds. */
|
|
1427
|
+
declare const JOB_KINDS: readonly ("llm.generate" | "llm.chat")[];
|
|
1428
|
+
/** A payload discriminated by its kind. */
|
|
1429
|
+
declare const KindedPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
1430
|
+
kind: z.ZodLiteral<"llm.generate">;
|
|
1431
|
+
payload: z.ZodObject<{
|
|
1432
|
+
prompt: z.ZodString;
|
|
1433
|
+
system: z.ZodOptional<z.ZodString>;
|
|
1434
|
+
}, z.core.$strict>;
|
|
1435
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1436
|
+
kind: z.ZodLiteral<"llm.chat">;
|
|
1437
|
+
payload: z.ZodObject<{
|
|
1438
|
+
messages: z.ZodArray<z.ZodObject<{
|
|
1439
|
+
role: z.ZodEnum<{
|
|
1440
|
+
user: "user";
|
|
1441
|
+
system: "system";
|
|
1442
|
+
assistant: "assistant";
|
|
1443
|
+
}>;
|
|
1444
|
+
content: z.ZodString;
|
|
1445
|
+
}, z.core.$strict>>;
|
|
1446
|
+
system: z.ZodOptional<z.ZodString>;
|
|
1447
|
+
}, z.core.$strict>;
|
|
1448
|
+
}, z.core.$strict>], "kind">;
|
|
1449
|
+
type KindedPayload = z.infer<typeof KindedPayload>;
|
|
1450
|
+
/** The payload type for a given kind. */
|
|
1451
|
+
type PayloadFor<K extends JobKind> = K extends "llm.generate" ? GeneratePayload : ChatPayload;
|
|
1452
|
+
/** Narrow an arbitrary string to a known job kind. */
|
|
1453
|
+
declare function isJobKind(value: string): value is JobKind;
|
|
1454
|
+
/**
|
|
1455
|
+
* Total character weight of a payload, used by the daemon's community budget
|
|
1456
|
+
* check and by the server's payload-size limits.
|
|
1457
|
+
*/
|
|
1458
|
+
declare function payloadTextLength(kinded: KindedPayload): number;
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* The job lifecycle, made explicit by byollm_001 Rev 1 §D because the most
|
|
1462
|
+
* user-visible failure mode — "nothing is running my job" — was previously
|
|
1463
|
+
* unspecified.
|
|
960
1464
|
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1465
|
+
* ```text
|
|
1466
|
+
* queued ──claim──▶ claimed ──start──▶ running ──▶ ok | error | canceled
|
|
1467
|
+
* │ │ │
|
|
1468
|
+
* │ └──lease expiry─────┘
|
|
1469
|
+
* │ ▼
|
|
1470
|
+
* │ queued (reclaimable, no loss)
|
|
1471
|
+
* └──ttl elapsed──▶ expired
|
|
1472
|
+
* ```
|
|
1473
|
+
*/
|
|
1474
|
+
declare const JobState: z.ZodEnum<{
|
|
1475
|
+
error: "error";
|
|
1476
|
+
ok: "ok";
|
|
1477
|
+
expired: "expired";
|
|
1478
|
+
queued: "queued";
|
|
1479
|
+
claimed: "claimed";
|
|
1480
|
+
running: "running";
|
|
1481
|
+
canceled: "canceled";
|
|
1482
|
+
}>;
|
|
1483
|
+
type JobState = z.infer<typeof JobState>;
|
|
1484
|
+
/** States from which a job never moves again. */
|
|
1485
|
+
declare const TERMINAL_STATES: readonly ["ok", "error", "canceled", "expired"];
|
|
1486
|
+
/** Is this a state the job can never leave? */
|
|
1487
|
+
declare function isTerminal(state: JobState): boolean;
|
|
1488
|
+
/** May a job move from `from` to `to`? */
|
|
1489
|
+
declare function canTransition(from: JobState, to: JobState): boolean;
|
|
1490
|
+
/** A lease: the right to work on a job until `expiresAt`. */
|
|
1491
|
+
declare const Lease: z.ZodObject<{
|
|
1492
|
+
id: z.ZodString;
|
|
1493
|
+
runnerId: z.ZodString;
|
|
1494
|
+
expiresAt: z.ZodNumber;
|
|
1495
|
+
}, z.core.$strict>;
|
|
1496
|
+
type Lease = z.infer<typeof Lease>;
|
|
1497
|
+
/** Payload union as it appears on a job record. */
|
|
1498
|
+
declare const JobPayload: z.ZodUnion<readonly [z.ZodObject<{
|
|
1499
|
+
prompt: z.ZodString;
|
|
1500
|
+
system: z.ZodOptional<z.ZodString>;
|
|
1501
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1502
|
+
messages: z.ZodArray<z.ZodObject<{
|
|
1503
|
+
role: z.ZodEnum<{
|
|
1504
|
+
user: "user";
|
|
1505
|
+
system: "system";
|
|
1506
|
+
assistant: "assistant";
|
|
1507
|
+
}>;
|
|
1508
|
+
content: z.ZodString;
|
|
1509
|
+
}, z.core.$strict>>;
|
|
1510
|
+
system: z.ZodOptional<z.ZodString>;
|
|
1511
|
+
}, z.core.$strict>]>;
|
|
1512
|
+
type JobPayload = z.infer<typeof JobPayload>;
|
|
1513
|
+
/**
|
|
1514
|
+
* A job as the daemon receives it from `/byollm/claim`.
|
|
973
1515
|
*
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
* threw away a device's live lease. Identical in shape to the `release` bug
|
|
979
|
-
* above, on the other plane. Anything added to the site plane later must be
|
|
980
|
-
* idempotent by the instance it names, or this scheme does not cover it.
|
|
1516
|
+
* Note what is absent: no model, no backend, no base URL, no flags, no path.
|
|
1517
|
+
* Those come from the machine owner's config only
|
|
1518
|
+
* ({@link MUSTS.NO_PAYLOAD_ROUTING}). The wire shape is the first place that
|
|
1519
|
+
* rule is enforced — there is no field to carry them.
|
|
981
1520
|
*/
|
|
982
|
-
declare
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1521
|
+
declare const ClaimedJob: z.ZodObject<{
|
|
1522
|
+
id: z.ZodString;
|
|
1523
|
+
kind: z.ZodEnum<{
|
|
1524
|
+
"llm.generate": "llm.generate";
|
|
1525
|
+
"llm.chat": "llm.chat";
|
|
1526
|
+
}>;
|
|
1527
|
+
payload: z.ZodUnion<readonly [z.ZodObject<{
|
|
1528
|
+
prompt: z.ZodString;
|
|
1529
|
+
system: z.ZodOptional<z.ZodString>;
|
|
1530
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1531
|
+
messages: z.ZodArray<z.ZodObject<{
|
|
1532
|
+
role: z.ZodEnum<{
|
|
1533
|
+
user: "user";
|
|
1534
|
+
system: "system";
|
|
1535
|
+
assistant: "assistant";
|
|
1536
|
+
}>;
|
|
1537
|
+
content: z.ZodString;
|
|
1538
|
+
}, z.core.$strict>>;
|
|
1539
|
+
system: z.ZodOptional<z.ZodString>;
|
|
1540
|
+
}, z.core.$strict>]>;
|
|
1541
|
+
audience: z.ZodEnum<{
|
|
1542
|
+
private: "private";
|
|
1543
|
+
team: "team";
|
|
1544
|
+
}>;
|
|
1545
|
+
owner: z.ZodString;
|
|
1546
|
+
site: z.ZodOptional<z.ZodString>;
|
|
1547
|
+
service: z.ZodOptional<z.ZodString>;
|
|
1548
|
+
deadlineAt: z.ZodOptional<z.ZodNumber>;
|
|
1549
|
+
lease: z.ZodObject<{
|
|
1550
|
+
id: z.ZodString;
|
|
1551
|
+
runnerId: z.ZodString;
|
|
1552
|
+
expiresAt: z.ZodNumber;
|
|
1553
|
+
}, z.core.$strict>;
|
|
1554
|
+
}, z.core.$strict>;
|
|
1555
|
+
type ClaimedJob = z.infer<typeof ClaimedJob>;
|
|
999
1556
|
/**
|
|
1000
|
-
*
|
|
1557
|
+
* The provenance that travels with every result to the delivery seam.
|
|
1001
1558
|
*
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1559
|
+
* byollm_003 Rev 1: a `team` result is attacker-controlled text.
|
|
1560
|
+
* The app must never render volunteer output as its own AI's answer without
|
|
1561
|
+
* knowing that is what it is ({@link MUSTS.PROVENANCE_NAMES_DEVICE}).
|
|
1005
1562
|
*/
|
|
1006
|
-
declare
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1563
|
+
declare const ResultProvenance: z.ZodObject<{
|
|
1564
|
+
audience: z.ZodEnum<{
|
|
1565
|
+
private: "private";
|
|
1566
|
+
team: "team";
|
|
1567
|
+
}>;
|
|
1568
|
+
runnerId: z.ZodString;
|
|
1569
|
+
runnerOwner: z.ZodString;
|
|
1570
|
+
backendClass: z.ZodEnum<{
|
|
1571
|
+
http: "http";
|
|
1572
|
+
process: "process";
|
|
1573
|
+
}>;
|
|
1574
|
+
model: z.ZodString;
|
|
1575
|
+
untrusted: z.ZodBoolean;
|
|
1576
|
+
}, z.core.$strict>;
|
|
1577
|
+
type ResultProvenance = z.infer<typeof ResultProvenance>;
|
|
1015
1578
|
/**
|
|
1016
|
-
*
|
|
1579
|
+
* Build provenance for a completed job. `untrusted` is derived, never
|
|
1580
|
+
* supplied, so no caller can mark volunteer output as first-party.
|
|
1581
|
+
*/
|
|
1582
|
+
declare function provenanceFor(input: {
|
|
1583
|
+
audience: Audience;
|
|
1584
|
+
runnerId: string;
|
|
1585
|
+
runnerOwner: string;
|
|
1586
|
+
backendClass: BackendClass;
|
|
1587
|
+
model: string;
|
|
1588
|
+
}): ResultProvenance;
|
|
1589
|
+
/**
|
|
1590
|
+
* What the daemon did, sealed with the answer — cloud_008 §2.5.
|
|
1591
|
+
*
|
|
1592
|
+
* These travelled in the clear on `ResultRequest`, which meant two things at
|
|
1593
|
+
* once. On the direct plane the site believed unauthenticated fields beside
|
|
1594
|
+
* an authenticated envelope — a daemon could seal one answer and *declare* it
|
|
1595
|
+
* came from a different model, and only the field it did not sign would be
|
|
1596
|
+
* recorded. Through a relay they reached a third party that acts on none of
|
|
1597
|
+
* them, and `model` in particular is the kind of detail Amendment A's rule
|
|
1598
|
+
* keeps off the wire.
|
|
1599
|
+
*
|
|
1600
|
+
* Sealed, they are the daemon's signed statement about its own run: the site
|
|
1601
|
+
* opens them, nothing in between sees them, and the disposition check that
|
|
1602
|
+
* already compares clear-text against ciphertext extends to cover them.
|
|
1603
|
+
*/
|
|
1604
|
+
/**
|
|
1605
|
+
* The closed set, as a schema — so the values exist once.
|
|
1017
1606
|
*
|
|
1018
|
-
*
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
*
|
|
1022
|
-
* new MUST cannot be added without a test and a test cannot silently drift
|
|
1023
|
-
* away from the statement it claims to prove.
|
|
1607
|
+
* A bare union would mean anything that has to VALIDATE a stop reason (the
|
|
1608
|
+
* ingress log, and the wire when step 4 lands) retyping the four strings
|
|
1609
|
+
* beside it. Instruction 9: one definition, both ends, and where a consumer
|
|
1610
|
+
* needs a runtime check the definition has to be one it can run.
|
|
1024
1611
|
*
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
1612
|
+
* The type below is inferred from this rather than written twice, so the
|
|
1613
|
+
* compiler and the validator cannot disagree about what a stop reason is.
|
|
1027
1614
|
*/
|
|
1028
|
-
|
|
1029
|
-
|
|
1615
|
+
declare const StopReasonSchema: z.ZodEnum<{
|
|
1616
|
+
length: "length";
|
|
1617
|
+
unknown: "unknown";
|
|
1618
|
+
end: "end";
|
|
1619
|
+
"stop-sequence": "stop-sequence";
|
|
1620
|
+
}>;
|
|
1621
|
+
type StopReason =
|
|
1622
|
+
/** The model finished on its own. */
|
|
1623
|
+
"end"
|
|
1624
|
+
/** The model stopped at its own output ceiling. */
|
|
1625
|
+
| "length"
|
|
1626
|
+
/** A configured stop token ended it. */
|
|
1627
|
+
| "stop-sequence"
|
|
1030
1628
|
/**
|
|
1031
|
-
*
|
|
1032
|
-
* enforces it, and is the one that decides what "byollm-compatible" means.
|
|
1629
|
+
* The adapter cannot tell, and says so.
|
|
1033
1630
|
*
|
|
1034
|
-
* The
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1631
|
+
* **The default, and never `"end"`.** An adapter nobody has updated — or
|
|
1632
|
+
* one somebody adds next year — must not be able to claim completion by
|
|
1633
|
+
* saying nothing. If absence meant "end", every un-updated adapter would go
|
|
1634
|
+
* on telling exactly the lie this exists to fix, and every new adapter
|
|
1635
|
+
* would inherit it in silence.
|
|
1039
1636
|
*
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
* (the hostile-payload corpus, or its unit tests). Real verification, and
|
|
1044
|
-
* it runs in CI — but it proves things about *our* daemon, not about
|
|
1045
|
-
* someone else's, so the kit cannot carry it.
|
|
1046
|
-
* - `construction` — true by the shape of the code, where a test could only
|
|
1047
|
-
* sample. A reviewer verifies it; a suite cannot.
|
|
1048
|
-
* - `operator` — a claim about how someone runs a deployment, verifiable only
|
|
1049
|
-
* by audit or by reading source. The honest category, and the one that
|
|
1050
|
-
* exists so a property nobody can check from outside is *labelled* as such
|
|
1051
|
-
* rather than laundered by association with the checkable ones.
|
|
1637
|
+
* It is the opposite-boolean rule this codebase keeps arriving at: when you
|
|
1638
|
+
* cannot tell, guess toward silence rather than toward a claim. "We do not
|
|
1639
|
+
* know" is a thing a site can act on; "it finished" when it did not is not.
|
|
1052
1640
|
*/
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1641
|
+
| "unknown";
|
|
1642
|
+
declare const RunMetadata: z.ZodObject<{
|
|
1643
|
+
model: z.ZodString;
|
|
1644
|
+
backendClass: z.ZodEnum<{
|
|
1645
|
+
http: "http";
|
|
1646
|
+
process: "process";
|
|
1647
|
+
}>;
|
|
1648
|
+
durationMs: z.ZodNumber;
|
|
1649
|
+
stop: z.ZodOptional<z.ZodEnum<{
|
|
1650
|
+
length: "length";
|
|
1651
|
+
unknown: "unknown";
|
|
1652
|
+
end: "end";
|
|
1653
|
+
"stop-sequence": "stop-sequence";
|
|
1654
|
+
}>>;
|
|
1655
|
+
stopReported: z.ZodOptional<z.ZodBoolean>;
|
|
1656
|
+
}, z.core.$strict>;
|
|
1657
|
+
type RunMetadata = z.infer<typeof RunMetadata>;
|
|
1658
|
+
/** Successful outcome. */
|
|
1659
|
+
declare const JobResultOk: z.ZodObject<{
|
|
1660
|
+
outcome: z.ZodLiteral<"ok">;
|
|
1661
|
+
text: z.ZodString;
|
|
1662
|
+
artifactUrl: z.ZodOptional<z.ZodURL>;
|
|
1663
|
+
}, z.core.$strict>;
|
|
1664
|
+
/** Failed outcome. `code` is a stable machine string; `message` is for humans. */
|
|
1665
|
+
declare const JobResultError: z.ZodObject<{
|
|
1666
|
+
outcome: z.ZodLiteral<"error">;
|
|
1667
|
+
code: z.ZodString;
|
|
1668
|
+
message: z.ZodString;
|
|
1669
|
+
retryable: z.ZodBoolean;
|
|
1670
|
+
}, z.core.$strict>;
|
|
1671
|
+
/** Cancelled outcome, reported by the daemon after honoring a cancel. */
|
|
1672
|
+
declare const JobResultCanceled: z.ZodObject<{
|
|
1673
|
+
outcome: z.ZodLiteral<"canceled">;
|
|
1674
|
+
}, z.core.$strict>;
|
|
1675
|
+
declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
1676
|
+
outcome: z.ZodLiteral<"ok">;
|
|
1677
|
+
text: z.ZodString;
|
|
1678
|
+
artifactUrl: z.ZodOptional<z.ZodURL>;
|
|
1679
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1680
|
+
outcome: z.ZodLiteral<"error">;
|
|
1681
|
+
code: z.ZodString;
|
|
1682
|
+
message: z.ZodString;
|
|
1683
|
+
retryable: z.ZodBoolean;
|
|
1684
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1685
|
+
outcome: z.ZodLiteral<"canceled">;
|
|
1686
|
+
}, z.core.$strict>], "outcome">;
|
|
1687
|
+
type JobOutcome = z.infer<typeof JobOutcome>;
|
|
1070
1688
|
/**
|
|
1071
|
-
*
|
|
1689
|
+
* Why a job can never run — byollm_016 Phase B.
|
|
1690
|
+
*
|
|
1691
|
+
* Every one of these is **terminal**, and that is the whole point of naming
|
|
1692
|
+
* them. A job that cannot be matched used to sit queued until its deadline,
|
|
1693
|
+
* which reads exactly like a job that is merely waiting for a device to come
|
|
1694
|
+
* online — so an app could not tell "any moment now" from "never", and neither
|
|
1695
|
+
* could the person watching a spinner. Silence must never read as pending.
|
|
1696
|
+
*
|
|
1697
|
+
* They are decided by whoever knows first: the site's own SDK where it can see
|
|
1698
|
+
* the answer without asking, the router where matching happens, and the daemon
|
|
1699
|
+
* again on arrival under the both-sides rule. All three reason from the same
|
|
1700
|
+
* list rather than three private vocabularies.
|
|
1701
|
+
*/
|
|
1702
|
+
declare const RefusalReason: z.ZodEnum<{
|
|
1703
|
+
"default-ambiguity": "default-ambiguity";
|
|
1704
|
+
"default-unusable": "default-unusable";
|
|
1705
|
+
}>;
|
|
1706
|
+
type RefusalReason = z.infer<typeof RefusalReason>;
|
|
1707
|
+
/**
|
|
1708
|
+
* A terminal outcome nobody sealed — byollm_016 Phase B.
|
|
1709
|
+
*
|
|
1710
|
+
* Every other finished job carries an envelope encrypted by the device that
|
|
1711
|
+
* ran it, which is what makes a result unforgeable. These have no device: the
|
|
1712
|
+
* job was refused *before* anything could run it, so there is nobody to seal
|
|
1713
|
+
* from and no content to seal.
|
|
1714
|
+
*
|
|
1715
|
+
* **What that costs, stated plainly.** This is the one terminal outcome a
|
|
1716
|
+
* router can author. It is worth being exact about the power that grants,
|
|
1717
|
+
* because "the relay can write this" sounds alarming until you compare it with
|
|
1718
|
+
* what a relay could already do: drop the job, never offer it, and let it
|
|
1719
|
+
* expire. A router-authored refusal is *denial of service by a shorter route*,
|
|
1720
|
+
* which is a power the router has always had and which the trust model has
|
|
1721
|
+
* always said it has. What it emphatically is **not** is forgery: this shape
|
|
1722
|
+
* carries no envelope and no output, so it can never be mistaken for an answer
|
|
1723
|
+
* a device produced. A relay still cannot fabricate a result, because that
|
|
1724
|
+
* needs a signature it does not hold.
|
|
1725
|
+
*
|
|
1726
|
+
* So the rule this shape enforces by construction: a refusal may deny, and may
|
|
1727
|
+
* never assert. Anything that claims work was *done* still comes sealed.
|
|
1728
|
+
*/
|
|
1729
|
+
declare const JobRefused: z.ZodObject<{
|
|
1730
|
+
outcome: z.ZodLiteral<"refused">;
|
|
1731
|
+
reason: z.ZodEnum<{
|
|
1732
|
+
"default-ambiguity": "default-ambiguity";
|
|
1733
|
+
"default-unusable": "default-unusable";
|
|
1734
|
+
}>;
|
|
1735
|
+
message: z.ZodString;
|
|
1736
|
+
}, z.core.$strict>;
|
|
1737
|
+
type JobRefused = z.infer<typeof JobRefused>;
|
|
1738
|
+
/**
|
|
1739
|
+
* The outward text for each reason, so a message cannot vary by call site.
|
|
1740
|
+
*
|
|
1741
|
+
* The mirror image of `REFUSAL_MESSAGES` in `audience.ts`, and the contrast is
|
|
1742
|
+
* worth holding in one thought. That table is read by the **owner** of the
|
|
1743
|
+
* device, where byollm_002's rule applies — four different truths must never
|
|
1744
|
+
* share a message, because a person debugging their own machine needs to know
|
|
1745
|
+
* which one they hit. This table is read by a **requester**, where the rule
|
|
1746
|
+
* inverts: two different truths must share a message exactly, because the
|
|
1747
|
+
* difference between them is somebody else's inventory.
|
|
1748
|
+
*
|
|
1749
|
+
* Same project, opposite requirements, and confusing them is how the oracle
|
|
1750
|
+
* comes back. Hence a table rather than prose at the throw site: three
|
|
1751
|
+
* refusals written in three places drift into three slightly different
|
|
1752
|
+
* sentences, and "slightly different" is all an oracle needs.
|
|
1753
|
+
*
|
|
1754
|
+
* ## Published, because the drift it prevents happens outside this repo — B103
|
|
1755
|
+
*
|
|
1756
|
+
* `JobRefused` and `RefusalReason` were exported and **this was not**, so we
|
|
1757
|
+
* shipped the vocabulary and withheld the sentences. Nothing here constructs a
|
|
1758
|
+
* refusal; the producers are sites and the hub, and every one of them would
|
|
1759
|
+
* have written its own message for a reason code we defined.
|
|
1760
|
+
*
|
|
1761
|
+
* **That is the drift this table argues against, made inevitable by the export
|
|
1762
|
+
* list** — and `message` is a required field on the envelope, so each of them
|
|
1763
|
+
* had to write something.
|
|
1764
|
+
*/
|
|
1765
|
+
declare const REFUSAL_TEXT: Readonly<Record<RefusalReason, string>>;
|
|
1766
|
+
/**
|
|
1767
|
+
* The plaintext inside a result envelope.
|
|
1072
1768
|
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1769
|
+
* The outcome and how it was produced, together, because they are one
|
|
1770
|
+
* statement by one signer. A site that opened only the outcome would be
|
|
1771
|
+
* trusting the envelope for the answer and the request body for everything
|
|
1772
|
+
* about it.
|
|
1077
1773
|
*/
|
|
1078
|
-
declare const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1774
|
+
declare const SealedOutcome: z.ZodObject<{
|
|
1775
|
+
outcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
1776
|
+
outcome: z.ZodLiteral<"ok">;
|
|
1777
|
+
text: z.ZodString;
|
|
1778
|
+
artifactUrl: z.ZodOptional<z.ZodURL>;
|
|
1779
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1780
|
+
outcome: z.ZodLiteral<"error">;
|
|
1781
|
+
code: z.ZodString;
|
|
1782
|
+
message: z.ZodString;
|
|
1783
|
+
retryable: z.ZodBoolean;
|
|
1784
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1785
|
+
outcome: z.ZodLiteral<"canceled">;
|
|
1786
|
+
}, z.core.$strict>], "outcome">;
|
|
1787
|
+
ran: z.ZodObject<{
|
|
1788
|
+
model: z.ZodString;
|
|
1789
|
+
backendClass: z.ZodEnum<{
|
|
1790
|
+
http: "http";
|
|
1791
|
+
process: "process";
|
|
1792
|
+
}>;
|
|
1793
|
+
durationMs: z.ZodNumber;
|
|
1794
|
+
stop: z.ZodOptional<z.ZodEnum<{
|
|
1795
|
+
length: "length";
|
|
1796
|
+
unknown: "unknown";
|
|
1797
|
+
end: "end";
|
|
1798
|
+
"stop-sequence": "stop-sequence";
|
|
1799
|
+
}>>;
|
|
1800
|
+
stopReported: z.ZodOptional<z.ZodBoolean>;
|
|
1801
|
+
}, z.core.$strict>;
|
|
1802
|
+
}, z.core.$strict>;
|
|
1803
|
+
type SealedOutcome = z.infer<typeof SealedOutcome>;
|
|
1804
|
+
/** A completed job as delivered to the app, provenance attached. */
|
|
1805
|
+
declare const DeliveredResult: z.ZodObject<{
|
|
1806
|
+
jobId: z.ZodString;
|
|
1807
|
+
state: z.ZodEnum<{
|
|
1808
|
+
error: "error";
|
|
1809
|
+
ok: "ok";
|
|
1810
|
+
expired: "expired";
|
|
1811
|
+
queued: "queued";
|
|
1812
|
+
claimed: "claimed";
|
|
1813
|
+
running: "running";
|
|
1814
|
+
canceled: "canceled";
|
|
1815
|
+
}>;
|
|
1816
|
+
outcome: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
1817
|
+
outcome: z.ZodLiteral<"ok">;
|
|
1818
|
+
text: z.ZodString;
|
|
1819
|
+
artifactUrl: z.ZodOptional<z.ZodURL>;
|
|
1820
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1821
|
+
outcome: z.ZodLiteral<"error">;
|
|
1822
|
+
code: z.ZodString;
|
|
1823
|
+
message: z.ZodString;
|
|
1824
|
+
retryable: z.ZodBoolean;
|
|
1825
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1826
|
+
outcome: z.ZodLiteral<"canceled">;
|
|
1827
|
+
}, z.core.$strict>], "outcome">>;
|
|
1828
|
+
provenance: z.ZodOptional<z.ZodObject<{
|
|
1829
|
+
audience: z.ZodEnum<{
|
|
1830
|
+
private: "private";
|
|
1831
|
+
team: "team";
|
|
1832
|
+
}>;
|
|
1833
|
+
runnerId: z.ZodString;
|
|
1834
|
+
runnerOwner: z.ZodString;
|
|
1835
|
+
backendClass: z.ZodEnum<{
|
|
1836
|
+
http: "http";
|
|
1837
|
+
process: "process";
|
|
1838
|
+
}>;
|
|
1839
|
+
model: z.ZodString;
|
|
1840
|
+
untrusted: z.ZodBoolean;
|
|
1841
|
+
}, z.core.$strict>>;
|
|
1842
|
+
fallback: z.ZodOptional<z.ZodLiteral<true>>;
|
|
1843
|
+
}, z.core.$strict>;
|
|
1844
|
+
type DeliveredResult = z.infer<typeof DeliveredResult>;
|
|
1845
|
+
/** Its members, derived — see {@link BACKEND_CLASSES} for why. */
|
|
1846
|
+
declare const SIZE_CLASSES: readonly ("small" | "medium" | "large" | "unbounded")[];
|
|
1847
|
+
/**
|
|
1848
|
+
* How big a payload is, in buckets — byollm_009 §6.
|
|
1849
|
+
*
|
|
1850
|
+
* A relay routes without reading, and matching a job to a machine needs some
|
|
1851
|
+
* notion of size. Buckets rather than byte counts because the exact figure is
|
|
1852
|
+
* a stronger fingerprint than the routing decision requires, and because a
|
|
1853
|
+
* bucket survives compression and encoding changes that an exact count does
|
|
1854
|
+
* not.
|
|
1855
|
+
*
|
|
1856
|
+
* **Two grains, on purpose — ratified 2026-08-28.** Metering a
|
|
1857
|
+
* GB-denominated plan needs real totals, and this is deliberately not where
|
|
1858
|
+
* they come from: exact bytes exist only as increment-only *monthly*
|
|
1859
|
+
* aggregates, and no per-job byte figure is ever persisted anywhere. The
|
|
1860
|
+
* record needs vagueness and the meter needs totals; neither borrows the
|
|
1861
|
+
* other's grain, which is why the consent screen's "roughly how big" stays
|
|
1862
|
+
* exactly true of everything retained about a job. If you are here because
|
|
1863
|
+
* you need a number, the aggregate is the one to reach for — adding a byte
|
|
1864
|
+
* count to this envelope would trade a promise for a convenience.
|
|
1865
|
+
*
|
|
1866
|
+
* `unbounded` exists for streamed jobs, which have no size when they start.
|
|
1867
|
+
* It is reserved now rather than added later: byollm_009 §8.1 — adding a
|
|
1868
|
+
* field to a published envelope is the v2 break all over again.
|
|
1869
|
+
*/
|
|
1870
|
+
declare const SizeClass: z.ZodEnum<{
|
|
1871
|
+
small: "small";
|
|
1872
|
+
medium: "medium";
|
|
1873
|
+
large: "large";
|
|
1874
|
+
unbounded: "unbounded";
|
|
1117
1875
|
}>;
|
|
1118
|
-
|
|
1119
|
-
type MustId = keyof typeof MUSTS;
|
|
1120
|
-
/** All MUST ids, for coverage checks. */
|
|
1121
|
-
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" | "RESULT_PROVENANCE" | "INGRESS_LOGGED_BEFORE_EXECUTION" | "NO_SHELL_INTERPOLATION" | "NO_PAYLOAD_ROUTING" | "STRIPPED_CHILD_ENV" | "HTTP_BASE_URL_SAFE" | "OUTPUT_INERT" | "COMMUNITY_BUDGETS")[];
|
|
1122
|
-
/** Every MUST verified a particular way. */
|
|
1123
|
-
declare function mustsVerifiedBy(kind: MustVerification): MustId[];
|
|
1124
|
-
|
|
1125
|
-
/** Protocol version carried on every request; servers refuse what they can't speak. */
|
|
1126
|
-
declare const PROTOCOL_VERSION: "0";
|
|
1876
|
+
type SizeClass = z.infer<typeof SizeClass>;
|
|
1127
1877
|
/**
|
|
1128
|
-
*
|
|
1878
|
+
* The most one envelope may be, in bytes — **6 MiB, ruled 2026-09-10.**
|
|
1129
1879
|
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1880
|
+
* ## Why six, which is two sentences and neither is about capacity
|
|
1881
|
+
*
|
|
1882
|
+
* Todd, 09-10: *"6 MB solves for just text and people don't try to push
|
|
1883
|
+
* images. We will add R2 for multimodal later."* **The cap is a SHAPE
|
|
1884
|
+
* decision** — it makes the envelope the wrong tool for a photo, which is the
|
|
1885
|
+
* point, because the right tool for a photo is object storage and a reference
|
|
1886
|
+
* to it rather than a bigger pipe.
|
|
1887
|
+
*
|
|
1888
|
+
* And the half that makes it aligned rather than merely chosen: **6 MiB sits
|
|
1889
|
+
* under a hosted box's memory and bandwidth limits.** A box is 320 MiB of RAM;
|
|
1890
|
+
* a 10 MiB envelope buffered is a real fraction of it. **Six is the number at
|
|
1891
|
+
* which the smallest thing we sell can hold the largest thing we accept.**
|
|
1892
|
+
*
|
|
1893
|
+
* Both sentences are here because a number without them is a number the next
|
|
1894
|
+
* person rounds up. It was 10 MiB from 2026-08-28 until this ruling.
|
|
1895
|
+
*
|
|
1896
|
+
* A **relay-memory safety rail**, not a plan feature: every tier has the same
|
|
1897
|
+
* ceiling, and differentiating tiers on it would be selling a safety limit as
|
|
1898
|
+
* a benefit. What it bounds is any single job, so no one message can make the
|
|
1899
|
+
* relay hold an unbounded amount of somebody else's memory.
|
|
1900
|
+
*
|
|
1901
|
+
* ## It stores nothing, and that is the design
|
|
1902
|
+
*
|
|
1903
|
+
* Enforced at ingress by refusing before acceptance, in both directions. A
|
|
1904
|
+
* ceiling on what the relay already has in hand needs no schema and no record:
|
|
1905
|
+
* the size is known for the length of the check and then it is gone. This
|
|
1906
|
+
* matters because the alternative — recording a size to enforce a limit
|
|
1907
|
+
* against — is precisely the per-job byte figure the metering ruling exists to
|
|
1908
|
+
* not have.
|
|
1909
|
+
*
|
|
1910
|
+
* ## Measured on the serialised envelope
|
|
1911
|
+
*
|
|
1912
|
+
* The same quantity the monthly rollup counts, deliberately. The relay stores
|
|
1913
|
+
* the serialised envelope and the meter measures what it stored, so a cap on
|
|
1914
|
+
* anything else — the ciphertext alone, the decoded length — would mean the
|
|
1915
|
+
* limit and the bill disagreed about what a byte is, and a job could be small
|
|
1916
|
+
* enough to accept and larger than it was charged as.
|
|
1133
1917
|
*/
|
|
1134
|
-
declare const
|
|
1918
|
+
declare const MAX_ENVELOPE_BYTES: number;
|
|
1135
1919
|
/**
|
|
1136
|
-
*
|
|
1920
|
+
* How big an envelope is, by the one measure that counts it.
|
|
1137
1921
|
*
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1922
|
+
* `JSON.stringify` because that is what the store persists and therefore what
|
|
1923
|
+
* the meter measures. Length in UTF-16 code units rather than encoded bytes:
|
|
1924
|
+
* it is the same number the store's own `HSTRLEN` reports, and the point of
|
|
1925
|
+
* this function is that one number answers both questions.
|
|
1142
1926
|
*/
|
|
1143
|
-
declare
|
|
1144
|
-
/** A structured refusal, so a daemon can say something useful to its owner. */
|
|
1145
|
-
interface VersionRefusal {
|
|
1146
|
-
readonly error: "unsupported-protocol-version";
|
|
1147
|
-
readonly message: string;
|
|
1148
|
-
readonly supported: readonly string[];
|
|
1149
|
-
readonly minimum: string;
|
|
1150
|
-
}
|
|
1927
|
+
declare function envelopeBytes(envelope: unknown): number;
|
|
1151
1928
|
/**
|
|
1152
|
-
*
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
*
|
|
1157
|
-
*
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1929
|
+
* A size somebody can act on, rounded **up** and only ever up.
|
|
1930
|
+
*
|
|
1931
|
+
* The rounding rule, in one place, because it has now been got wrong twice —
|
|
1932
|
+
* B072. `toFixed` rounds to nearest, so a message one byte over the line
|
|
1933
|
+
* printed "this message is 10.5 MB and the limit is 10.5 MB": a refusal that
|
|
1934
|
+
* reads as a contradiction, given to somebody who now has no idea what to
|
|
1935
|
+
* change.
|
|
1936
|
+
*
|
|
1937
|
+
* The relay found that, fixed it, and wrote the reasoning down beside the
|
|
1938
|
+
* fix. The SDK's refusal then rediscovered the identical bug, because it
|
|
1939
|
+
* copied the SENTENCE rather than the function — which is the same shape as
|
|
1940
|
+
* `MAX_BODY_BYTES` diverging from `MAX_ENVELOPE_BYTES`, and the same answer:
|
|
1941
|
+
* neither side holds the rule.
|
|
1942
|
+
*
|
|
1943
|
+
* Up is also the honest direction. Understating how far over a message is
|
|
1944
|
+
* sends somebody to trim a hundred bytes off something that needs to lose a
|
|
1945
|
+
* megabyte; overstating by a tenth costs them nothing.
|
|
1946
|
+
*/
|
|
1947
|
+
declare function describeBytes(bytes: number): string;
|
|
1948
|
+
/**
|
|
1949
|
+
* What a message that is too big is told, minus anything about pricing.
|
|
1163
1950
|
*
|
|
1164
|
-
*
|
|
1165
|
-
*
|
|
1951
|
+
* Shared because both planes refuse the same thing for the same reason. What
|
|
1952
|
+
* is NOT shared is the relay's "every plan has the same ceiling" — a hosted
|
|
1953
|
+
* sentence, and meaningless to somebody self-hosting the direct lane, where
|
|
1954
|
+
* there are no plans. One rule, two audiences: the rule travels, the words
|
|
1955
|
+
* about our billing do not.
|
|
1166
1956
|
*/
|
|
1167
|
-
declare function
|
|
1168
|
-
|
|
1169
|
-
|
|
1957
|
+
declare function tooLargeMessage(input: {
|
|
1958
|
+
readonly bytes: number;
|
|
1959
|
+
readonly limit: number;
|
|
1960
|
+
}): string;
|
|
1961
|
+
/** Where the bucket boundaries sit, in characters of payload text. */
|
|
1962
|
+
declare const SIZE_CLASS_LIMITS: Readonly<{
|
|
1963
|
+
small: 4000;
|
|
1964
|
+
medium: 64000;
|
|
1965
|
+
large: number;
|
|
1966
|
+
}>;
|
|
1170
1967
|
/**
|
|
1171
|
-
* The
|
|
1968
|
+
* The most a payload in this bucket can be.
|
|
1172
1969
|
*
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1970
|
+
* Used where a decision must be made from a stub, before the payload has been
|
|
1971
|
+
* fetched — a budget check, for instance. Charging the bucket's ceiling is the
|
|
1972
|
+
* conservative direction: it refuses slightly too eagerly rather than
|
|
1973
|
+
* admitting work that turns out larger than the budget allowed.
|
|
1974
|
+
*
|
|
1975
|
+
* `unbounded` returns `Infinity`, which fails every ceiling. That is correct
|
|
1976
|
+
* until byollm_006 defines how a streamed job is budgeted — failing closed on
|
|
1977
|
+
* a case nobody has designed beats inventing an allowance for it.
|
|
1177
1978
|
*/
|
|
1178
|
-
declare
|
|
1179
|
-
|
|
1979
|
+
declare function sizeClassCeiling(sizeClass: SizeClass): number;
|
|
1980
|
+
/** Bucket a payload by its text length. */
|
|
1981
|
+
declare function sizeClassOf(textChars: number): SizeClass;
|
|
1180
1982
|
/**
|
|
1181
|
-
*
|
|
1182
|
-
* right now, with the backend and model that would serve it.
|
|
1983
|
+
* Everything an upstream may see about a job — byollm_009 §6.
|
|
1183
1984
|
*
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1188
|
-
* (
|
|
1985
|
+
* **This list is exhaustive and normative.** It is a commitment about the
|
|
1986
|
+
* metadata surface, not an accident of what the implementation happens to
|
|
1987
|
+
* send: an upstream that requires more has exceeded the protocol, and an
|
|
1988
|
+
* endpoint that emits more has leaked past it
|
|
1989
|
+
* ({@link MUSTS.STUB_METADATA_EXHAUSTIVE}).
|
|
1990
|
+
*
|
|
1991
|
+
* What is absent is the point. No payload, no model, no prompt, no result.
|
|
1992
|
+
* `kind` is here because capability matching happens upstream; if a later
|
|
1993
|
+
* revision moves matching to the daemon, `kind` moves into the ciphertext.
|
|
1189
1994
|
*/
|
|
1190
|
-
declare const
|
|
1995
|
+
declare const JobStub: z.ZodObject<{
|
|
1996
|
+
id: z.ZodString;
|
|
1191
1997
|
kind: z.ZodEnum<{
|
|
1192
1998
|
"llm.generate": "llm.generate";
|
|
1193
1999
|
"llm.chat": "llm.chat";
|
|
1194
2000
|
}>;
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
lmstudio: "lmstudio";
|
|
1201
|
-
jan: "jan";
|
|
1202
|
-
localai: "localai";
|
|
1203
|
-
anthropic: "anthropic";
|
|
1204
|
-
openai: "openai";
|
|
1205
|
-
gemini: "gemini";
|
|
1206
|
-
grok: "grok";
|
|
1207
|
-
groq: "groq";
|
|
1208
|
-
openrouter: "openrouter";
|
|
1209
|
-
together: "together";
|
|
1210
|
-
deepseek: "deepseek";
|
|
1211
|
-
mistral: "mistral";
|
|
1212
|
-
"openai-http": "openai-http";
|
|
1213
|
-
"claude-cli": "claude-cli";
|
|
1214
|
-
}>;
|
|
1215
|
-
backendClass: z.ZodEnum<{
|
|
1216
|
-
http: "http";
|
|
1217
|
-
process: "process";
|
|
2001
|
+
owner: z.ZodString;
|
|
2002
|
+
site: z.ZodString;
|
|
2003
|
+
audience: z.ZodEnum<{
|
|
2004
|
+
private: "private";
|
|
2005
|
+
team: "team";
|
|
1218
2006
|
}>;
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
2007
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
2008
|
+
sizeClass: z.ZodEnum<{
|
|
2009
|
+
small: "small";
|
|
2010
|
+
medium: "medium";
|
|
2011
|
+
large: "large";
|
|
2012
|
+
unbounded: "unbounded";
|
|
1224
2013
|
}>;
|
|
2014
|
+
streaming: z.ZodBoolean;
|
|
2015
|
+
deadlineAt: z.ZodNumber;
|
|
1225
2016
|
}, z.core.$strict>;
|
|
1226
|
-
type
|
|
1227
|
-
/**
|
|
1228
|
-
|
|
2017
|
+
type JobStub = z.infer<typeof JobStub>;
|
|
2018
|
+
/**
|
|
2019
|
+
* A stub, plus the lease the claiming runner now holds for it — and, on a
|
|
2020
|
+
* relayed route, the grant that says it may run at all.
|
|
2021
|
+
*
|
|
2022
|
+
* The grant lives here rather than on {@link JobStub} because of *when* it is
|
|
2023
|
+
* authored. A stub exists from enqueue; a grant is written at claim, against
|
|
2024
|
+
* the membership and mapping true at that moment. That timing is the whole of
|
|
2025
|
+
* Amendment J: a job queued yesterday for somebody removed this morning gets
|
|
2026
|
+
* no grant when it is finally claimed, and a roster held on the device could
|
|
2027
|
+
* never have known.
|
|
2028
|
+
*
|
|
2029
|
+
* Optional, and the absence is meaningful rather than lenient. A device that
|
|
2030
|
+
* pinned a control-plane key at pairing **requires** one — a claimed job
|
|
2031
|
+
* arriving without it is refused, not admitted by default. A device that
|
|
2032
|
+
* pinned none is in direct mode, where there is no control plane to author
|
|
2033
|
+
* anything and the owner's own work is the only work that runs.
|
|
2034
|
+
*/
|
|
2035
|
+
declare const ClaimedStub: z.ZodObject<{
|
|
2036
|
+
id: z.ZodString;
|
|
1229
2037
|
kind: z.ZodEnum<{
|
|
1230
2038
|
"llm.generate": "llm.generate";
|
|
1231
2039
|
"llm.chat": "llm.chat";
|
|
1232
2040
|
}>;
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
lmstudio: "lmstudio";
|
|
1239
|
-
jan: "jan";
|
|
1240
|
-
localai: "localai";
|
|
1241
|
-
anthropic: "anthropic";
|
|
1242
|
-
openai: "openai";
|
|
1243
|
-
gemini: "gemini";
|
|
1244
|
-
grok: "grok";
|
|
1245
|
-
groq: "groq";
|
|
1246
|
-
openrouter: "openrouter";
|
|
1247
|
-
together: "together";
|
|
1248
|
-
deepseek: "deepseek";
|
|
1249
|
-
mistral: "mistral";
|
|
1250
|
-
"openai-http": "openai-http";
|
|
1251
|
-
"claude-cli": "claude-cli";
|
|
1252
|
-
}>;
|
|
1253
|
-
backendClass: z.ZodEnum<{
|
|
1254
|
-
http: "http";
|
|
1255
|
-
process: "process";
|
|
2041
|
+
owner: z.ZodString;
|
|
2042
|
+
site: z.ZodString;
|
|
2043
|
+
audience: z.ZodEnum<{
|
|
2044
|
+
private: "private";
|
|
2045
|
+
team: "team";
|
|
1256
2046
|
}>;
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
2047
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
2048
|
+
sizeClass: z.ZodEnum<{
|
|
2049
|
+
small: "small";
|
|
2050
|
+
medium: "medium";
|
|
2051
|
+
large: "large";
|
|
2052
|
+
unbounded: "unbounded";
|
|
1262
2053
|
}>;
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
* the app's own authenticated session. Nothing listens on the user's machine
|
|
1270
|
-
* and nothing works over a copied string alone.
|
|
1271
|
-
*/
|
|
1272
|
-
declare const PairStartRequest: z.ZodObject<{
|
|
1273
|
-
protocolVersion: z.ZodLiteral<"0">;
|
|
1274
|
-
action: z.ZodLiteral<"start">;
|
|
1275
|
-
daemon: z.ZodObject<{
|
|
1276
|
-
version: z.ZodString;
|
|
1277
|
-
label: z.ZodString;
|
|
1278
|
-
platform: z.ZodEnum<{
|
|
1279
|
-
darwin: "darwin";
|
|
1280
|
-
linux: "linux";
|
|
1281
|
-
win32: "win32";
|
|
1282
|
-
}>;
|
|
1283
|
-
}, z.core.$strip>;
|
|
1284
|
-
device: z.ZodObject<{
|
|
1285
|
-
identity: z.ZodString;
|
|
1286
|
-
encryption: z.ZodString;
|
|
1287
|
-
encryptionSig: z.ZodString;
|
|
2054
|
+
streaming: z.ZodBoolean;
|
|
2055
|
+
deadlineAt: z.ZodNumber;
|
|
2056
|
+
lease: z.ZodObject<{
|
|
2057
|
+
id: z.ZodString;
|
|
2058
|
+
runnerId: z.ZodString;
|
|
2059
|
+
expiresAt: z.ZodNumber;
|
|
1288
2060
|
}, z.core.$strict>;
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
jan: "jan";
|
|
1301
|
-
localai: "localai";
|
|
1302
|
-
anthropic: "anthropic";
|
|
1303
|
-
openai: "openai";
|
|
1304
|
-
gemini: "gemini";
|
|
1305
|
-
grok: "grok";
|
|
1306
|
-
groq: "groq";
|
|
1307
|
-
openrouter: "openrouter";
|
|
1308
|
-
together: "together";
|
|
1309
|
-
deepseek: "deepseek";
|
|
1310
|
-
mistral: "mistral";
|
|
1311
|
-
"openai-http": "openai-http";
|
|
1312
|
-
"claude-cli": "claude-cli";
|
|
1313
|
-
}>;
|
|
1314
|
-
backendClass: z.ZodEnum<{
|
|
1315
|
-
http: "http";
|
|
1316
|
-
process: "process";
|
|
1317
|
-
}>;
|
|
1318
|
-
model: z.ZodString;
|
|
1319
|
-
offerScope: z.ZodEnum<{
|
|
1320
|
-
self: "self";
|
|
1321
|
-
named: "named";
|
|
1322
|
-
public: "public";
|
|
1323
|
-
}>;
|
|
2061
|
+
grant: z.ZodOptional<z.ZodObject<{
|
|
2062
|
+
grantId: z.ZodString;
|
|
2063
|
+
jobId: z.ZodString;
|
|
2064
|
+
site: z.ZodString;
|
|
2065
|
+
user: z.ZodString;
|
|
2066
|
+
owner: z.ZodString;
|
|
2067
|
+
purpose: z.ZodString;
|
|
2068
|
+
kind: z.ZodString;
|
|
2069
|
+
service: z.ZodString;
|
|
2070
|
+
issuedAt: z.ZodNumber;
|
|
2071
|
+
signature: z.ZodString;
|
|
1324
2072
|
}, z.core.$strict>>;
|
|
1325
2073
|
}, z.core.$strict>;
|
|
1326
|
-
type
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
2074
|
+
type ClaimedStub = z.infer<typeof ClaimedStub>;
|
|
2075
|
+
|
|
2076
|
+
/**
|
|
2077
|
+
* Device and site keys — byollm_009 §3.
|
|
2078
|
+
*
|
|
2079
|
+
* **Two keypairs per party, and the split is load-bearing.** An Ed25519
|
|
2080
|
+
* *identity* key signs; an X25519 *encryption* key receives sealed envelopes.
|
|
2081
|
+
* The encryption key is signed by the identity key, and **the identity key is
|
|
2082
|
+
* what gets pinned**. So "who sent this" and "who can read this" are answered
|
|
2083
|
+
* by different keys — which is what lets an encryption key rotate without
|
|
2084
|
+
* re-establishing trust, and what byollm_009 §6's signed-then-sealed envelope
|
|
2085
|
+
* depends on.
|
|
2086
|
+
*
|
|
2087
|
+
* **No new dependency.** byollm_009 §2 says established primitives only, via
|
|
2088
|
+
* libsodium. Everything *this* module needs — Ed25519 signing, X25519 key
|
|
2089
|
+
* generation — Node provides natively, and using it costs nothing and adds no
|
|
2090
|
+
* install weight to a daemon that must land fast on a stranger's laptop.
|
|
2091
|
+
*
|
|
2092
|
+
* libsodium becomes necessary at envelope v2, where sealing does. That is a
|
|
2093
|
+
* real dependency decision and it belongs in the change that needs it: a
|
|
2094
|
+
* sealed box is a specific reviewed construction, and rebuilding it out of
|
|
2095
|
+
* Node primitives is exactly the "novel construction" §2 rules out. Deferring
|
|
2096
|
+
* the dependency is not the same as deferring the rule.
|
|
2097
|
+
*/
|
|
2098
|
+
/** A public identity, as it travels on the wire. All values base64url. */
|
|
2099
|
+
declare const PublicIdentity: z.ZodObject<{
|
|
2100
|
+
identity: z.ZodString;
|
|
2101
|
+
encryption: z.ZodString;
|
|
2102
|
+
encryptionSig: z.ZodString;
|
|
2103
|
+
}, z.core.$strict>;
|
|
2104
|
+
type PublicIdentity = z.infer<typeof PublicIdentity>;
|
|
2105
|
+
/** Private key material, as stored on disk. Never leaves the machine. */
|
|
2106
|
+
declare const StoredKeys: z.ZodObject<{
|
|
2107
|
+
version: z.ZodLiteral<1>;
|
|
2108
|
+
identityPublic: z.ZodString;
|
|
2109
|
+
identityPrivate: z.ZodString;
|
|
2110
|
+
encryptionPublic: z.ZodString;
|
|
2111
|
+
encryptionPrivate: z.ZodString;
|
|
2112
|
+
encryptionSig: z.ZodString;
|
|
2113
|
+
createdAt: z.ZodNumber;
|
|
1333
2114
|
}, z.core.$strict>;
|
|
1334
|
-
type
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
2115
|
+
type StoredKeys = z.infer<typeof StoredKeys>;
|
|
2116
|
+
/** Domain separator, so a signature over an encryption key cannot be
|
|
2117
|
+
* replayed as a signature over anything else. */
|
|
2118
|
+
/**
|
|
2119
|
+
* What an encryption key's signature covers.
|
|
2120
|
+
*
|
|
2121
|
+
* Exported because a rotation is a real event this protocol has to be able to
|
|
2122
|
+
* *test* — a record whose encryption key moved under an identity that signed
|
|
2123
|
+
* the move is the one case pinning must refuse loudly, and building one
|
|
2124
|
+
* outside this file otherwise means re-typing this string, which is how two
|
|
2125
|
+
* copies of a constant start disagreeing.
|
|
2126
|
+
*/
|
|
2127
|
+
declare const ENCRYPTION_KEY_CONTEXT = "byollm/v1/encryption-key";
|
|
2128
|
+
/** Generate a fresh pair of keypairs and bind them together. */
|
|
2129
|
+
declare function generateKeys(now: number): StoredKeys;
|
|
2130
|
+
/** The public half, for the wire. */
|
|
2131
|
+
declare function publicIdentityOf(keys: StoredKeys): PublicIdentity;
|
|
2132
|
+
/**
|
|
2133
|
+
* Check that an encryption key really belongs to the identity presenting it.
|
|
2134
|
+
*
|
|
2135
|
+
* Called on everything received, including from an upstream we otherwise
|
|
2136
|
+
* trust — the point of pinning the identity is that nothing else needs to be
|
|
2137
|
+
* trusted, and that only holds if this is checked every time rather than at
|
|
2138
|
+
* first sight.
|
|
2139
|
+
*/
|
|
2140
|
+
declare function verifyPublicIdentity(identity: PublicIdentity): boolean;
|
|
2141
|
+
/** Sign arbitrary bytes with an identity key. */
|
|
2142
|
+
/**
|
|
2143
|
+
* Sign bytes with an identity key.
|
|
2144
|
+
*
|
|
2145
|
+
* Takes only the private half it uses. A signer that demanded a whole
|
|
2146
|
+
* {@link StoredKeys} would make every caller hold an encryption keypair for a
|
|
2147
|
+
* job that has no encryption in it — and the control plane, which signs
|
|
2148
|
+
* rosters and opens nothing, would be generating and storing secret material
|
|
2149
|
+
* it can never need. Every existing caller passes a full `StoredKeys`, which
|
|
2150
|
+
* satisfies this.
|
|
2151
|
+
*/
|
|
2152
|
+
declare function signWith(keys: Pick<StoredKeys, "identityPrivate">, data: Uint8Array): string;
|
|
2153
|
+
/** Verify bytes against a raw Ed25519 public key. */
|
|
2154
|
+
declare function verifyWith(identityPublic: string, data: Uint8Array, signature: string): boolean;
|
|
2155
|
+
/**
|
|
2156
|
+
* A fingerprint a human can compare out loud.
|
|
2157
|
+
*
|
|
2158
|
+
* 120 bits of SHA-256 over the raw identity key, as six groups of four. Long
|
|
2159
|
+
* enough that grinding a colliding key is not worth anyone's afternoon, short
|
|
2160
|
+
* enough to read down a phone line — which is the whole point. A fingerprint
|
|
2161
|
+
* nobody can be bothered to compare provides no security at all, so
|
|
2162
|
+
* legibility is a security property here, not a nicety.
|
|
2163
|
+
*
|
|
2164
|
+
* Formatted with a `BYOLLM-` prefix so a pasted fingerprint is recognisable
|
|
2165
|
+
* out of context, in a support thread or a screenshot.
|
|
2166
|
+
*/
|
|
2167
|
+
declare function fingerprint(identityPublic: string): string;
|
|
2168
|
+
/** The short id used in envelopes and provenance. Stable, and comparable. */
|
|
2169
|
+
declare const keyId: (identityPublic: string) => string;
|
|
2170
|
+
|
|
2171
|
+
declare function cryptoReady(): Promise<void>;
|
|
2172
|
+
/**
|
|
2173
|
+
* How long a sealed payload is worth keeping, from creation.
|
|
2174
|
+
*
|
|
2175
|
+
* Bound into every envelope and recomputed when one is opened, so it lives
|
|
2176
|
+
* here rather than in the two places that need it. Two copies of a value the
|
|
2177
|
+
* signature depends on is the same bug as two clock readings: it works until
|
|
2178
|
+
* they disagree, and then nothing can be opened.
|
|
2179
|
+
*
|
|
2180
|
+
* Not a job's TTL. That answers how long the *work* is worth doing, belongs
|
|
2181
|
+
* to the app and the store, and may legitimately differ per deployment.
|
|
2182
|
+
*/
|
|
2183
|
+
declare const ENVELOPE_MAX_AGE_MS: number;
|
|
2184
|
+
/** Which leg an envelope belongs to. Bound into the signature. */
|
|
2185
|
+
declare const EnvelopeDirection: z.ZodEnum<{
|
|
2186
|
+
payload: "payload";
|
|
2187
|
+
result: "result";
|
|
2188
|
+
}>;
|
|
2189
|
+
type EnvelopeDirection = z.infer<typeof EnvelopeDirection>;
|
|
2190
|
+
declare const SealedEnvelope: z.ZodObject<{
|
|
2191
|
+
ciphertext: z.ZodString;
|
|
2192
|
+
recipientKeyId: z.ZodString;
|
|
2193
|
+
senderKeyId: z.ZodString;
|
|
2194
|
+
direction: z.ZodEnum<{
|
|
2195
|
+
payload: "payload";
|
|
2196
|
+
result: "result";
|
|
2197
|
+
}>;
|
|
2198
|
+
deadlineAt: z.ZodNumber;
|
|
1339
2199
|
}, z.core.$strict>;
|
|
1340
|
-
type
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
2200
|
+
type SealedEnvelope = z.infer<typeof SealedEnvelope>;
|
|
2201
|
+
/** Everything the signature covers besides the plaintext itself. */
|
|
2202
|
+
interface EnvelopeContext {
|
|
2203
|
+
readonly jobId: string;
|
|
2204
|
+
readonly senderKeyId: string;
|
|
2205
|
+
readonly recipientKeyId: string;
|
|
2206
|
+
readonly deadlineAt: number;
|
|
2207
|
+
readonly direction: EnvelopeDirection;
|
|
2208
|
+
}
|
|
2209
|
+
/** Seal a plaintext to a recipient, signed by the sender's identity. */
|
|
2210
|
+
declare function seal(input: {
|
|
2211
|
+
plaintext: string;
|
|
2212
|
+
senderKeys: StoredKeys;
|
|
2213
|
+
recipientEncryptionPublic: string;
|
|
2214
|
+
context: EnvelopeContext;
|
|
2215
|
+
}): Promise<SealedEnvelope>;
|
|
2216
|
+
/** Why an envelope was refused. Never distinguished to a remote caller. */
|
|
2217
|
+
type EnvelopeFailure = "not-for-us" | "unopenable" | "malformed" | "bad-signature" | "context-mismatch";
|
|
2218
|
+
type OpenResult = {
|
|
2219
|
+
readonly ok: true;
|
|
2220
|
+
readonly plaintext: string;
|
|
2221
|
+
} | {
|
|
2222
|
+
readonly ok: false;
|
|
2223
|
+
readonly reason: EnvelopeFailure;
|
|
2224
|
+
};
|
|
2225
|
+
/**
|
|
2226
|
+
* Open an envelope and verify it came from the pinned sender.
|
|
2227
|
+
*
|
|
2228
|
+
* Every failure returns rather than throws: this runs on input from the
|
|
2229
|
+
* network, and a crash here is a denial of service on the delivery path.
|
|
2230
|
+
*
|
|
2231
|
+
* The context is checked against the signature, not merely read from the
|
|
2232
|
+
* envelope. An envelope carries its own claims about who sent it and to
|
|
2233
|
+
* whom — believing those would authenticate the attacker's assertion rather
|
|
2234
|
+
* than the sender's key.
|
|
2235
|
+
*/
|
|
2236
|
+
declare function open(input: {
|
|
2237
|
+
envelope: SealedEnvelope;
|
|
2238
|
+
recipientKeys: StoredKeys;
|
|
2239
|
+
senderIdentityPublic: string;
|
|
2240
|
+
/** The deadline is taken from the envelope and checked against its signature. */
|
|
2241
|
+
expected: Omit<EnvelopeContext, "deadlineAt">;
|
|
2242
|
+
}): Promise<OpenResult>;
|
|
2243
|
+
|
|
2244
|
+
/**
|
|
2245
|
+
* Request signing — byollm_009 §4.2.
|
|
2246
|
+
*
|
|
2247
|
+
* Every authenticated call is signed by the calling device's identity key.
|
|
2248
|
+
* There is no bearer token on the daemon plane: possession of a file no
|
|
2249
|
+
* longer grants access, possession of a *key* does, and the key never leaves
|
|
2250
|
+
* the machine.
|
|
2251
|
+
*
|
|
2252
|
+
* ## Why this is not the server-issued nonce the spec first described
|
|
2253
|
+
*
|
|
2254
|
+
* byollm_009 §4.2 says "the upstream issues a nonce; the daemon signs it".
|
|
2255
|
+
* Implementing that costs one of two things: a round trip before every
|
|
2256
|
+
* request, or server-side session state — and sessions reintroduce a bearer
|
|
2257
|
+
* credential, which is the thing being removed.
|
|
2258
|
+
*
|
|
2259
|
+
* Signing *the request itself* gets the same property without either, because
|
|
2260
|
+
* of something the protocol already guarantees. A captured signature is valid
|
|
2261
|
+
* only for the exact request it covers — same endpoint, same runner, same
|
|
2262
|
+
* body — and every authenticated endpoint here is idempotent by design:
|
|
2263
|
+
* `RESULT_IDEMPOTENT` makes a replayed result a no-op, a replayed claim from
|
|
2264
|
+
* the same runner returns what that runner already holds, and heartbeat and
|
|
2265
|
+
* release are idempotent in effect. So a replay inside the freshness window
|
|
2266
|
+
* gains an attacker nothing they could not obtain by forwarding the original,
|
|
2267
|
+
* which a relay can do anyway.
|
|
2268
|
+
*
|
|
2269
|
+
* That is the whole argument, and it is worth stating because it rests
|
|
2270
|
+
* entirely on the endpoints being idempotent. Two ways that can fail, and the
|
|
2271
|
+
* second is the one that actually bit:
|
|
2272
|
+
*
|
|
2273
|
+
* 1. **A future endpoint that is not idempotent cannot use this scheme
|
|
2274
|
+
* unchanged** — it would need a server-issued nonce.
|
|
2275
|
+
* 2. **Idempotence must hold per *addressed instance*, not per endpoint.** A
|
|
2276
|
+
* request that names a mutable target — a lease, a session, a
|
|
2277
|
+
* subscription — must name the *instance*, or a replay lands on a
|
|
2278
|
+
* different one than the sender meant and the endpoint's idempotence buys
|
|
2279
|
+
* nothing. `release` was idempotent per lease and ambiguous across them:
|
|
2280
|
+
* it named a job and a runner, both of which survive a
|
|
2281
|
+
* claim-release-reclaim cycle, so a replayed release yanked a later grant.
|
|
2282
|
+
* Fixed by giving a lease its own id and requiring it.
|
|
2283
|
+
*
|
|
2284
|
+
* The rule for anything added later: if a signed request can be replayed onto
|
|
2285
|
+
* a target that has changed underneath it, the request has to say which
|
|
2286
|
+
* target it meant.
|
|
2287
|
+
*/
|
|
2288
|
+
/** How far a request's timestamp may be from the server's clock. */
|
|
2289
|
+
declare const MAX_CLOCK_SKEW_MS = 120000;
|
|
2290
|
+
/** The signed material a request carries. */
|
|
2291
|
+
declare const RequestSignature: z.ZodObject<{
|
|
1421
2292
|
runnerId: z.ZodString;
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
"llm.generate": "llm.generate";
|
|
1425
|
-
"llm.chat": "llm.chat";
|
|
1426
|
-
}>;
|
|
1427
|
-
backendId: z.ZodEnum<{
|
|
1428
|
-
ollama: "ollama";
|
|
1429
|
-
mlx: "mlx";
|
|
1430
|
-
llamacpp: "llamacpp";
|
|
1431
|
-
vllm: "vllm";
|
|
1432
|
-
lmstudio: "lmstudio";
|
|
1433
|
-
jan: "jan";
|
|
1434
|
-
localai: "localai";
|
|
1435
|
-
anthropic: "anthropic";
|
|
1436
|
-
openai: "openai";
|
|
1437
|
-
gemini: "gemini";
|
|
1438
|
-
grok: "grok";
|
|
1439
|
-
groq: "groq";
|
|
1440
|
-
openrouter: "openrouter";
|
|
1441
|
-
together: "together";
|
|
1442
|
-
deepseek: "deepseek";
|
|
1443
|
-
mistral: "mistral";
|
|
1444
|
-
"openai-http": "openai-http";
|
|
1445
|
-
"claude-cli": "claude-cli";
|
|
1446
|
-
}>;
|
|
1447
|
-
backendClass: z.ZodEnum<{
|
|
1448
|
-
http: "http";
|
|
1449
|
-
process: "process";
|
|
1450
|
-
}>;
|
|
1451
|
-
model: z.ZodString;
|
|
1452
|
-
offerScope: z.ZodEnum<{
|
|
1453
|
-
self: "self";
|
|
1454
|
-
named: "named";
|
|
1455
|
-
public: "public";
|
|
1456
|
-
}>;
|
|
1457
|
-
}, z.core.$strict>>;
|
|
1458
|
-
max: z.ZodNumber;
|
|
1459
|
-
}, z.core.$strict>;
|
|
1460
|
-
type ClaimRequest = z.infer<typeof ClaimRequest>;
|
|
1461
|
-
declare const ClaimResponse: z.ZodObject<{
|
|
1462
|
-
jobs: z.ZodArray<z.ZodObject<{
|
|
1463
|
-
id: z.ZodString;
|
|
1464
|
-
kind: z.ZodEnum<{
|
|
1465
|
-
"llm.generate": "llm.generate";
|
|
1466
|
-
"llm.chat": "llm.chat";
|
|
1467
|
-
}>;
|
|
1468
|
-
owner: z.ZodString;
|
|
1469
|
-
audience: z.ZodEnum<{
|
|
1470
|
-
self: "self";
|
|
1471
|
-
named: "named";
|
|
1472
|
-
public: "public";
|
|
1473
|
-
}>;
|
|
1474
|
-
audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1475
|
-
sizeClass: z.ZodEnum<{
|
|
1476
|
-
small: "small";
|
|
1477
|
-
medium: "medium";
|
|
1478
|
-
large: "large";
|
|
1479
|
-
unbounded: "unbounded";
|
|
1480
|
-
}>;
|
|
1481
|
-
streaming: z.ZodBoolean;
|
|
1482
|
-
deadlineAt: z.ZodNumber;
|
|
1483
|
-
lease: z.ZodObject<{
|
|
1484
|
-
id: z.ZodString;
|
|
1485
|
-
runnerId: z.ZodString;
|
|
1486
|
-
expiresAt: z.ZodNumber;
|
|
1487
|
-
}, z.core.$strip>;
|
|
1488
|
-
}, z.core.$strict>>;
|
|
1489
|
-
leaseMs: z.ZodNumber;
|
|
2293
|
+
issuedAt: z.ZodNumber;
|
|
2294
|
+
signature: z.ZodString;
|
|
1490
2295
|
}, z.core.$strict>;
|
|
1491
|
-
type
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
2296
|
+
type RequestSignature = z.infer<typeof RequestSignature>;
|
|
2297
|
+
/**
|
|
2298
|
+
* The exact bytes both sides sign and verify.
|
|
2299
|
+
*
|
|
2300
|
+
* Newline-separated with a version prefix and a domain separator. Every field
|
|
2301
|
+
* that decides what the request *does* is in here: leave one out and it
|
|
2302
|
+
* becomes something an intermediary can change without breaking the
|
|
2303
|
+
* signature.
|
|
2304
|
+
*
|
|
2305
|
+
* The body is included by hash rather than by value, so signing does not
|
|
2306
|
+
* depend on both sides serialising JSON identically — which they would not.
|
|
2307
|
+
*/
|
|
2308
|
+
declare function canonicalRequest(input: {
|
|
2309
|
+
endpoint: string;
|
|
2310
|
+
runnerId: string;
|
|
2311
|
+
issuedAt: number;
|
|
2312
|
+
body: string;
|
|
2313
|
+
}): Buffer;
|
|
2314
|
+
/** Sign an outgoing request with this machine's identity key. */
|
|
2315
|
+
declare function signRequest(keys: StoredKeys, input: {
|
|
2316
|
+
endpoint: string;
|
|
2317
|
+
runnerId: string;
|
|
2318
|
+
issuedAt: number;
|
|
2319
|
+
body: string;
|
|
2320
|
+
}): RequestSignature;
|
|
2321
|
+
/**
|
|
2322
|
+
* The same scheme, for the party at the other end: a **site** calling a relay.
|
|
2323
|
+
*
|
|
2324
|
+
* A site talking to a relay is in exactly the daemon's position — an outbound
|
|
2325
|
+
* caller with an identity keypair the other side already pins — so it gets the
|
|
2326
|
+
* daemon's authentication rather than a second scheme. Bearer tokens for the
|
|
2327
|
+
* site plane were the alternative, and they would have reintroduced the
|
|
2328
|
+
* credential-in-a-file that §4.2 removed from the daemon plane, on the plane
|
|
2329
|
+
* that carries *every* site's traffic.
|
|
2330
|
+
*
|
|
2331
|
+
* Two things make this safe to build on the same canonical string:
|
|
2332
|
+
*
|
|
2333
|
+
* 1. **The endpoint is namespaced.** Site endpoints sign `site/enqueue`, never
|
|
2334
|
+
* `enqueue`. The daemon plane's `result` and the site plane's `results` are
|
|
2335
|
+
* one character apart, and a naming collision between planes must not be
|
|
2336
|
+
* what stands between a signature and a replay onto the wrong handler. The
|
|
2337
|
+
* prefix is applied *inside* these helpers, so the two ends cannot disagree
|
|
2338
|
+
* about it — the alternative is two implementations of one bound value,
|
|
2339
|
+
* which is this project's most-repeated bug.
|
|
2340
|
+
* 2. **The caller slot carries the site id.** `canonicalRequest` names that
|
|
2341
|
+
* field `runnerId` because the daemon plane got there first; here it holds
|
|
2342
|
+
* the site id, and the verifier looks the key up in the projection's site
|
|
2343
|
+
* registry rather than its device registry. The two registries never share
|
|
2344
|
+
* an entry, so a device signature cannot authenticate as a site.
|
|
2345
|
+
*
|
|
2346
|
+
* §4.2's replay argument carries over **only because the site plane's writes
|
|
2347
|
+
* are idempotent per addressed instance**, which is a property that had to be
|
|
2348
|
+
* built rather than found: `enqueue` reset a job of the same id, so a replayed
|
|
2349
|
+
* enqueue inside the freshness window returned a claimed job to the queue and
|
|
2350
|
+
* threw away a device's live lease. Identical in shape to the `release` bug
|
|
2351
|
+
* above, on the other plane. Anything added to the site plane later must be
|
|
2352
|
+
* idempotent by the instance it names, or this scheme does not cover it.
|
|
2353
|
+
*/
|
|
2354
|
+
declare function signSiteRequest(keys: StoredKeys, input: {
|
|
2355
|
+
endpoint: string;
|
|
2356
|
+
siteId: string;
|
|
2357
|
+
issuedAt: number;
|
|
2358
|
+
body: string;
|
|
2359
|
+
}): RequestSignature;
|
|
2360
|
+
/** Verify a site's call against the identity the control plane registered. */
|
|
2361
|
+
declare function verifySiteRequest(input: {
|
|
2362
|
+
identityPublic: string;
|
|
2363
|
+
endpoint: string;
|
|
2364
|
+
body: string;
|
|
2365
|
+
signature: RequestSignature;
|
|
2366
|
+
now: number;
|
|
2367
|
+
maxSkewMs?: number;
|
|
2368
|
+
}): SignatureFailure | null;
|
|
2369
|
+
/**
|
|
2370
|
+
* Why a signed request was refused.
|
|
2371
|
+
*
|
|
2372
|
+
* **`bad-signature` is never returned verbatim; `stale` is, deliberately.**
|
|
2373
|
+
* They are different kinds of refusal and conflating them costs a real user
|
|
2374
|
+
* more than it costs an attacker.
|
|
2375
|
+
*
|
|
2376
|
+
* A bad signature is an authentication failure and the server says only
|
|
2377
|
+
* "unauthorized" — telling a prober which part they got wrong is free help.
|
|
2378
|
+
*
|
|
2379
|
+
* A stale timestamp is a **precondition** failure: the signature may be
|
|
2380
|
+
* perfectly valid and the caller's clock is simply wrong. Saying so reveals
|
|
2381
|
+
* nothing, for two reasons that both have to hold. The server's time is
|
|
2382
|
+
* already public — every response carries a `Date` header and the heartbeat
|
|
2383
|
+
* response returns `serverTime` outright. And freshness is checked *before*
|
|
2384
|
+
* the signature is verified, so a stale answer says nothing about whether the
|
|
2385
|
+
* signature was any good.
|
|
2386
|
+
*
|
|
2387
|
+
* What conflating them costs: a machine whose clock has drifted gets
|
|
2388
|
+
* `401 unauthorized` on every request, forever, with nothing anywhere pointing
|
|
2389
|
+
* at the clock. That is the shape byollm_013 was filed about — a refusal that
|
|
2390
|
+
* is correct, silent, and sends somebody to read our source.
|
|
2391
|
+
*/
|
|
2392
|
+
type SignatureFailure = "stale" | "bad-signature";
|
|
2393
|
+
/**
|
|
2394
|
+
* Verify a signed request against a runner's pinned identity key.
|
|
2395
|
+
*
|
|
2396
|
+
* Freshness is checked in **both** directions. A clock far ahead is as much a
|
|
2397
|
+
* problem as one behind: it would let a captured request stay replayable long
|
|
2398
|
+
* after it was made, which is the one thing the window exists to bound.
|
|
2399
|
+
*/
|
|
2400
|
+
declare function verifyRequest(input: {
|
|
2401
|
+
identityPublic: string;
|
|
2402
|
+
endpoint: string;
|
|
2403
|
+
body: string;
|
|
2404
|
+
signature: RequestSignature;
|
|
2405
|
+
now: number;
|
|
2406
|
+
maxSkewMs?: number;
|
|
2407
|
+
}): SignatureFailure | null;
|
|
2408
|
+
|
|
2409
|
+
/**
|
|
2410
|
+
* What a site says it needs — byollm_016 Amendment L.
|
|
2411
|
+
*
|
|
2412
|
+
* A site declares **purposes**, and each purpose lists the job kinds it uses.
|
|
2413
|
+
* A person then maps each purpose to one of their own services, on the consent
|
|
2414
|
+
* screen, and that mapping *is* the consent. The control plane joins the two
|
|
2415
|
+
* at claim time and signs the result into a grant.
|
|
2416
|
+
*
|
|
2417
|
+
* ## Why a site declares needs instead of naming services
|
|
2418
|
+
*
|
|
2419
|
+
* Because it cannot name one. The site's vocabulary is its own purposes; the
|
|
2420
|
+
* person's vocabulary is their services; and the two never meet. A site asks
|
|
2421
|
+
* for "writing assistant, llm.chat" and learns only whether that slot is
|
|
2422
|
+
* satisfiable — never which model answered, never whose machine, never even
|
|
2423
|
+
* the name of the service. Key-vs-value reaches its strongest form here: the
|
|
2424
|
+
* site cannot describe what it wants *or* name it, only ask for what it
|
|
2425
|
+
* declared.
|
|
2426
|
+
*
|
|
2427
|
+
* ## Keys are ids; labels are prose
|
|
2428
|
+
*
|
|
2429
|
+
* They are separate fields and nothing derives one from the other, which is
|
|
2430
|
+
* the amendment's ruling and worth restating where somebody will read it. A
|
|
2431
|
+
* key travels on every job and is what mappings are stored against, so it is
|
|
2432
|
+
* stable-or-nothing: renaming one deletes a purpose and creates another,
|
|
2433
|
+
* unmapping everybody who had chosen for it. A label is changeable whenever
|
|
2434
|
+
* the site likes and is the **only** thing a consent screen renders.
|
|
2435
|
+
*/
|
|
2436
|
+
/**
|
|
2437
|
+
* The purpose a site gets when it declares no purposes of its own.
|
|
2438
|
+
*
|
|
2439
|
+
* Reserved, and refused by {@link Manifest} rather than by whatever handles
|
|
2440
|
+
* registration. A site with a single undifferentiated use has one purpose —
|
|
2441
|
+
* everything it does — and that purpose needs an id because mappings are
|
|
2442
|
+
* keyed by one. An id taken from the site's own vocabulary would collide the
|
|
2443
|
+
* day it declared a real purpose of the same name.
|
|
2444
|
+
*
|
|
2445
|
+
* **Never rendered.** "default → your Claude" tells a person nothing; a
|
|
2446
|
+
* consent screen shows the site's own name for this slot, because that is
|
|
2447
|
+
* what a single-purpose site's one purpose actually is.
|
|
2448
|
+
*/
|
|
2449
|
+
declare const RESERVED_PURPOSE = "default";
|
|
2450
|
+
declare const Purpose: z.ZodObject<{
|
|
2451
|
+
label: z.ZodString;
|
|
2452
|
+
description: z.ZodOptional<z.ZodString>;
|
|
2453
|
+
kinds: z.ZodArray<z.ZodEnum<{
|
|
2454
|
+
"llm.generate": "llm.generate";
|
|
2455
|
+
"llm.chat": "llm.chat";
|
|
2456
|
+
}>>;
|
|
1537
2457
|
}, z.core.$strict>;
|
|
1538
|
-
type
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
2458
|
+
type Purpose = z.infer<typeof Purpose>;
|
|
2459
|
+
/**
|
|
2460
|
+
* Everything a site needs, by purpose key.
|
|
2461
|
+
*
|
|
2462
|
+
* At least one purpose: a site that declares none is a site that can enqueue
|
|
2463
|
+
* nothing, and accepting it would mean the first refusal a person saw came
|
|
2464
|
+
* from a job rather than from registration.
|
|
2465
|
+
*/
|
|
2466
|
+
/**
|
|
2467
|
+
* How many purposes one site may declare.
|
|
2468
|
+
*
|
|
2469
|
+
* There was no bound at all: a site could declare fifty thousand, each one
|
|
2470
|
+
* individually valid, and the consent screen renders a slot per (purpose,
|
|
2471
|
+
* kind) — so the page that *is* the consent mechanism becomes unusable, and
|
|
2472
|
+
* the notification mail that enumerates slots grows with it.
|
|
2473
|
+
*
|
|
2474
|
+
* Thirty-two is chosen rather than derived, and the number is an argument: a
|
|
2475
|
+
* purpose is a thing a person reads and decides about one at a time, and a
|
|
2476
|
+
* screen asking more than about thirty separate questions has stopped being a
|
|
2477
|
+
* consent screen whatever it renders. Of Tomorrow Press declares five. A site
|
|
2478
|
+
* that genuinely needs more has a product question to answer before it has a
|
|
2479
|
+
* schema one.
|
|
2480
|
+
*/
|
|
2481
|
+
declare const MAX_PURPOSES = 32;
|
|
2482
|
+
declare const Manifest: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
2483
|
+
label: z.ZodString;
|
|
2484
|
+
description: z.ZodOptional<z.ZodString>;
|
|
2485
|
+
kinds: z.ZodArray<z.ZodEnum<{
|
|
2486
|
+
"llm.generate": "llm.generate";
|
|
2487
|
+
"llm.chat": "llm.chat";
|
|
2488
|
+
}>>;
|
|
2489
|
+
}, z.core.$strict>>;
|
|
2490
|
+
type Manifest = z.infer<typeof Manifest>;
|
|
2491
|
+
/**
|
|
2492
|
+
* The manifest a site with no declared purposes is treated as having.
|
|
2493
|
+
*
|
|
2494
|
+
* The sugar in Amendment L, made explicit rather than special-cased
|
|
2495
|
+
* downstream: everything after this point sees a manifest with one purpose,
|
|
2496
|
+
* so no consent screen, mapping table or resolver needs a branch for the
|
|
2497
|
+
* flat-list case.
|
|
2498
|
+
*
|
|
2499
|
+
* The label is the caller's — a site's own name — because it is the one thing
|
|
2500
|
+
* that can make "everything this site does" read as a sentence about a
|
|
2501
|
+
* particular site rather than about software in general.
|
|
2502
|
+
*/
|
|
2503
|
+
declare function singlePurposeManifest(input: {
|
|
2504
|
+
readonly label: string;
|
|
2505
|
+
readonly kinds: readonly JobKind[];
|
|
2506
|
+
}): Manifest;
|
|
2507
|
+
|
|
2508
|
+
/**
|
|
2509
|
+
* One job, one signature, one answer — byollm_016 Amendment J.
|
|
2510
|
+
*
|
|
2511
|
+
* A grant is the control plane's signed statement that a particular job may
|
|
2512
|
+
* run on a particular device, authored at claim time and verified against the
|
|
2513
|
+
* key that device pinned when it paired.
|
|
2514
|
+
*
|
|
2515
|
+
* ## What it replaced, and why the replacement is smaller
|
|
2516
|
+
*
|
|
2517
|
+
* Until 2026-08-26 a device held a signed **roster** and answered admission
|
|
2518
|
+
* from it. Amendment G's four properties were right and the mechanism was a
|
|
2519
|
+
* cache — one that bought nothing. On the cloud route the job path and the
|
|
2520
|
+
* roster path share fate: jobs arrive through the relay, so if the relay is
|
|
2521
|
+
* unreachable there are no jobs to admit and a locally held roster adds no
|
|
2522
|
+
* availability. What it did add was staleness, which is the only reason
|
|
2523
|
+
* `ROSTER_MAX_AGE_MS` existed: a bound on how long a removed person keeps
|
|
2524
|
+
* running. Authoring at claim collapses that bound to this document's own
|
|
2525
|
+
* lifetime — add somebody and their next job runs, remove them and their next
|
|
2526
|
+
* claim fails, including jobs already queued.
|
|
2527
|
+
*
|
|
2528
|
+
* It also collapses four questions into one signature. Consented, member,
|
|
2529
|
+
* admitted, and *which service* were four mechanisms answering separately;
|
|
2530
|
+
* they are now four fields of one statement, and the device verifies once.
|
|
2531
|
+
*
|
|
2532
|
+
* ## What it is not
|
|
2533
|
+
*
|
|
2534
|
+
* Amendment G property 1 outlawed admitting on a per-job assertion, and this
|
|
2535
|
+
* is per-job. The distinction is authorship: G outlawed trusting the
|
|
2536
|
+
* **relay's or site's unsigned** claim. A grant is signed by the control
|
|
2537
|
+
* plane with a key the device pinned at pairing, so the relay can withhold it
|
|
2538
|
+
* and cannot forge it — exactly the power a relay has over a job.
|
|
2539
|
+
* `RELAY_BLIND` is untouched: the relay delivers, it never authors.
|
|
2540
|
+
*
|
|
2541
|
+
* ## What the device still checks for itself
|
|
2542
|
+
*
|
|
2543
|
+
* A grant is necessary and not sufficient. Four checks stay on the device and
|
|
2544
|
+
* none of them is delegated:
|
|
2545
|
+
*
|
|
2546
|
+
* 1. the signature, against the pinned key;
|
|
2547
|
+
* 2. replay — {@link SignedGrant.grantId} is single-use;
|
|
2548
|
+
* 3. offer-consistency — the named service is one this device actually
|
|
2549
|
+
* offers, at a scope that includes this user;
|
|
2550
|
+
* 4. **private is absolute** — a `private` service runs for the paired owner
|
|
2551
|
+
* and nobody else, so no compromise of a control plane can grant somebody
|
|
2552
|
+
* else's job onto it.
|
|
2553
|
+
*/
|
|
2554
|
+
/**
|
|
2555
|
+
* How long a grant is honoured after it was signed. Ruled 120s (2026-08-26).
|
|
2556
|
+
*
|
|
2557
|
+
* This bounds **acceptance**, not execution: a job admitted inside the window
|
|
2558
|
+
* runs to completion however long it takes. So the number only has to cover
|
|
2559
|
+
* the trip from the control plane signing to the device checking — claim,
|
|
2560
|
+
* deliver, verify — and every second past that is a second a captured grant
|
|
2561
|
+
* stays useful.
|
|
2562
|
+
*
|
|
2563
|
+
* Two minutes is generous for that trip and mean for the capture. It is also
|
|
2564
|
+
* the number ordinary clock drift is measured against, which is why
|
|
2565
|
+
* {@link CLOCK_SKEW_WARN_MS} sits well inside it: a device whose clock is off
|
|
2566
|
+
* by half the window would refuse real work, and must be told before it does.
|
|
2567
|
+
*
|
|
2568
|
+
* The verifier's policy, deliberately not a field on the document. An
|
|
2569
|
+
* `expiresAt` the signer chose would let whoever signs decide how long their
|
|
2570
|
+
* own statement stays good, and the party with the most reason to want a
|
|
2571
|
+
* longer window is the party being bounded.
|
|
2572
|
+
*/
|
|
2573
|
+
declare const GRANT_MAX_AGE_MS = 120000;
|
|
2574
|
+
/**
|
|
2575
|
+
* Clock disagreement past which a device says so, before it starts refusing.
|
|
2576
|
+
*
|
|
2577
|
+
* Skew eats {@link GRANT_MAX_AGE_MS} directly — a device 60s behind its
|
|
2578
|
+
* relay's clock has half a window left, and one 120s behind has none and
|
|
2579
|
+
* refuses everything for a reason no refusal message would otherwise name.
|
|
2580
|
+
* Thirty seconds is a quarter of the window: far enough out to be a real
|
|
2581
|
+
* problem, early enough to be a warning rather than an outage.
|
|
2582
|
+
*/
|
|
2583
|
+
declare const CLOCK_SKEW_WARN_MS = 30000;
|
|
2584
|
+
/**
|
|
2585
|
+
* Skew past which a freshness refusal names the clock instead of the grant.
|
|
2586
|
+
*
|
|
2587
|
+
* Five seconds, because below that the clock is not the story and saying so
|
|
2588
|
+
* would send somebody to check ntp about an unrelated failure. Above it, "this
|
|
2589
|
+
* grant expired" and "your clock is wrong" are the same event wearing
|
|
2590
|
+
* different words, and only one of them can be acted on.
|
|
2591
|
+
*/
|
|
2592
|
+
declare const CLOCK_ATTRIBUTION_MS = 5000;
|
|
2593
|
+
/**
|
|
2594
|
+
* The domain separator.
|
|
2595
|
+
*
|
|
2596
|
+
* Every signature in this system says what kind of statement it is before it
|
|
2597
|
+
* says anything else. Without it, bytes signed for one purpose verify for
|
|
2598
|
+
* another — a grant and a request are both "bytes this key signed", and a
|
|
2599
|
+
* scheme that could not tell them apart would let one be replayed as the
|
|
2600
|
+
* other.
|
|
2601
|
+
*/
|
|
2602
|
+
declare const GRANT_CONTEXT = "byollm/v1/grant";
|
|
2603
|
+
declare const SignedGrant: z.ZodObject<{
|
|
2604
|
+
grantId: z.ZodString;
|
|
2605
|
+
jobId: z.ZodString;
|
|
2606
|
+
site: z.ZodString;
|
|
2607
|
+
user: z.ZodString;
|
|
2608
|
+
owner: z.ZodString;
|
|
2609
|
+
purpose: z.ZodString;
|
|
2610
|
+
kind: z.ZodString;
|
|
2611
|
+
service: z.ZodString;
|
|
2612
|
+
issuedAt: z.ZodNumber;
|
|
2613
|
+
signature: z.ZodString;
|
|
1548
2614
|
}, z.core.$strict>;
|
|
1549
|
-
type
|
|
2615
|
+
type SignedGrant = z.infer<typeof SignedGrant>;
|
|
2616
|
+
/** Everything a grant says, before it is signed. */
|
|
2617
|
+
type GrantClaims = Omit<SignedGrant, "signature">;
|
|
2618
|
+
/**
|
|
2619
|
+
* Every field of {@link SignedGrant} except the signature, sorted.
|
|
2620
|
+
*
|
|
2621
|
+
* **Derived from the schema, never written out by hand.** The unsigned-field
|
|
2622
|
+
* attack is that somebody adds a field to the document, forgets to add it to
|
|
2623
|
+
* the bytes, and ships a value an intermediary can rewrite without breaking
|
|
2624
|
+
* any signature. A hand-maintained list is exactly the shape that fails: it
|
|
2625
|
+
* does not grow when the code does, and nothing about adding a field reminds
|
|
2626
|
+
* you it exists.
|
|
2627
|
+
*
|
|
2628
|
+
* Reading the shape closes it structurally rather than by review. A new field
|
|
2629
|
+
* is signed the moment it is declared, and grant.test.ts asserts this list
|
|
2630
|
+
* still covers the schema so a future zod version that hides `shape` fails
|
|
2631
|
+
* loudly instead of silently signing less.
|
|
2632
|
+
*/
|
|
2633
|
+
declare const GRANT_SIGNED_FIELDS: readonly (keyof GrantClaims)[];
|
|
2634
|
+
/**
|
|
2635
|
+
* The exact bytes both sides sign and verify.
|
|
2636
|
+
*
|
|
2637
|
+
* JSON-encoded rather than joined with a separator, because a separator can
|
|
2638
|
+
* be imitated. Newline-joining `["a", "b\nc"]` and `["a\nb", "c"]` produces
|
|
2639
|
+
* identical bytes, so two different grants would share a signature — and the
|
|
2640
|
+
* values here include a site id and a user id, at least one of which comes
|
|
2641
|
+
* from somebody else's namespace. JSON escapes the separator it uses, so no
|
|
2642
|
+
* arrangement of field values can spell a different document.
|
|
2643
|
+
*
|
|
2644
|
+
* The context string leads, and the field order is the schema's own sorted
|
|
2645
|
+
* keys, so the encoding is canonical without anyone maintaining a list.
|
|
2646
|
+
*/
|
|
2647
|
+
declare function grantStatement(claims: GrantClaims): Uint8Array;
|
|
2648
|
+
/** Sign a grant with the control plane's own key. */
|
|
2649
|
+
declare function signGrant(keys: Pick<StoredKeys, "identityPrivate">, claims: GrantClaims): SignedGrant;
|
|
2650
|
+
/**
|
|
2651
|
+
* Why a grant was refused.
|
|
2652
|
+
*
|
|
2653
|
+
* Split by remedy, because these send somebody to different places: fix your
|
|
2654
|
+
* clock, take it up with the relay, or nothing at all — you are being
|
|
2655
|
+
* attacked and the refusal worked.
|
|
2656
|
+
*
|
|
2657
|
+
* There is deliberately no `no-pinned-key` here. A device that pinned no
|
|
2658
|
+
* control-plane key never reaches this function: it is in direct mode, and
|
|
2659
|
+
* the question "is this grant good" does not arise. A value nothing can
|
|
2660
|
+
* return is a branch every caller has to handle and no test can reach.
|
|
2661
|
+
*/
|
|
2662
|
+
type GrantRefusal =
|
|
2663
|
+
/** The signature does not verify against the pinned key. */
|
|
2664
|
+
"bad-signature"
|
|
2665
|
+
/** Genuine, and for a different device's owner. */
|
|
2666
|
+
| "wrong-owner"
|
|
2667
|
+
/** Genuine, and lifted from a different job. */
|
|
2668
|
+
| "wrong-job"
|
|
2669
|
+
/** Older than {@link GRANT_MAX_AGE_MS}. */
|
|
2670
|
+
| "expired"
|
|
1550
2671
|
/**
|
|
1551
|
-
*
|
|
2672
|
+
* Issued further in the future than clock drift explains.
|
|
1552
2673
|
*
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
* leaves the queue or the app may re-enqueue. It does not have to know what
|
|
1556
|
-
* the model said, or what an error said, and this is where that line is drawn.
|
|
2674
|
+
* Checked, and not as pedantry: an `issuedAt` ahead of now extends a
|
|
2675
|
+
* grant's life past the bound, which is the whole thing being enforced.
|
|
1557
2676
|
*
|
|
1558
|
-
*
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
2677
|
+
* Tolerant by {@link CLOCK_SKEW_WARN_MS}, because it was tolerant by
|
|
2678
|
+
* nothing and that made ordinary drift a total outage — see
|
|
2679
|
+
* {@link verifyGrant}.
|
|
1561
2680
|
*/
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
2681
|
+
| "from-the-future";
|
|
2682
|
+
/**
|
|
2683
|
+
* Is this grant one this device may act on, right now?
|
|
2684
|
+
*
|
|
2685
|
+
* Document-level checks only. Replay, offer-consistency and the private rule
|
|
2686
|
+
* need state this function does not have and are the device's to apply — see
|
|
2687
|
+
* the class comment for the full list of four.
|
|
2688
|
+
*/
|
|
2689
|
+
declare function verifyGrant(input: {
|
|
2690
|
+
grant: SignedGrant;
|
|
2691
|
+
owner: string;
|
|
2692
|
+
jobId: string;
|
|
2693
|
+
controlPlanePublic: string;
|
|
2694
|
+
now: number;
|
|
2695
|
+
maxAgeMs?: number;
|
|
2696
|
+
}): GrantRefusal | null;
|
|
2697
|
+
|
|
2698
|
+
/**
|
|
2699
|
+
* Rotation — byollm_009 Amendment C.
|
|
2700
|
+
*
|
|
2701
|
+
* A site holding identity key **K1** wants to be known by **K2**. It publishes
|
|
2702
|
+
* a *succession*: K2, plus a signature by K1 over a statement naming both key
|
|
2703
|
+
* ids. That signature is the entire mechanism, and the reason rotation can be
|
|
2704
|
+
* automatic without becoming a hole is that **the relay cannot mint one** — it
|
|
2705
|
+
* never holds K1. It is the same trust step a daemon already performs at
|
|
2706
|
+
* pairing, applied to the site's own succession.
|
|
2707
|
+
*
|
|
2708
|
+
* ## Why the statement names both keys
|
|
2709
|
+
*
|
|
2710
|
+
* A signature over K2 alone could be lifted from this site's record and
|
|
2711
|
+
* replayed into another site's, moving *that* site to K2 — a key the attacker
|
|
2712
|
+
* holds. Naming the predecessor binds the succession to one chain, and it is
|
|
2713
|
+
* the reason `verifyLink` takes the id it expects to be succeeding from
|
|
2714
|
+
* rather than reading it out of the statement it is checking.
|
|
2715
|
+
*/
|
|
2716
|
+
/** The domain separator. Distinct from every other thing an identity signs. */
|
|
2717
|
+
declare const SUCCESSION_CONTEXT = "byollm/v1/site-succession";
|
|
2718
|
+
/**
|
|
2719
|
+
* How long a retired key may still sign work — Amendment C, ruling 2.
|
|
2720
|
+
*
|
|
2721
|
+
* A protocol constant and not the site's to choose. Per-site overlap
|
|
2722
|
+
* arithmetic is exactly the kind of number that has to mean one thing
|
|
2723
|
+
* everywhere, and a site that could choose it could choose *forever*, which is
|
|
2724
|
+
* a two-key site permanently and a second key nobody ever notices retiring.
|
|
2725
|
+
*
|
|
2726
|
+
* Seven days: long enough that a daemon which polls daily and a laptop shut
|
|
2727
|
+
* for a long weekend both see the new record before the old key stops working,
|
|
2728
|
+
* short enough that "which key is live" is never an interesting question.
|
|
2729
|
+
*/
|
|
2730
|
+
declare const RETIREMENT_WINDOW_MS: number;
|
|
2731
|
+
/**
|
|
2732
|
+
* The longest chain a daemon will walk — Amendment C, ruling 1.
|
|
2733
|
+
*
|
|
2734
|
+
* **A denial-of-service guard, not policy.** The bound exists so a projection
|
|
2735
|
+
* cannot make a daemon verify ten thousand signatures, not to express an
|
|
2736
|
+
* opinion about how often a site may rotate. A site that legitimately exceeds
|
|
2737
|
+
* it has a re-pair ahead of it, which is why it is generous: at one rotation a
|
|
2738
|
+
* quarter this is sixteen years.
|
|
2739
|
+
*/
|
|
2740
|
+
declare const MAX_SUCCESSION_CHAIN = 64;
|
|
2741
|
+
/** One step of a chain: a key, and the signature by it over its successor. */
|
|
2742
|
+
declare const Succession: z.ZodObject<{
|
|
2743
|
+
identity: z.ZodObject<{
|
|
2744
|
+
identity: z.ZodString;
|
|
2745
|
+
encryption: z.ZodString;
|
|
2746
|
+
encryptionSig: z.ZodString;
|
|
1581
2747
|
}, z.core.$strict>;
|
|
1582
|
-
|
|
1583
|
-
ok: "ok";
|
|
1584
|
-
error: "error";
|
|
1585
|
-
canceled: "canceled";
|
|
1586
|
-
}>;
|
|
1587
|
-
model: z.ZodString;
|
|
1588
|
-
backendClass: z.ZodEnum<{
|
|
1589
|
-
http: "http";
|
|
1590
|
-
process: "process";
|
|
1591
|
-
}>;
|
|
1592
|
-
durationMs: z.ZodNumber;
|
|
1593
|
-
}, z.core.$strict>;
|
|
1594
|
-
type ResultRequest = z.infer<typeof ResultRequest>;
|
|
1595
|
-
declare const ResultResponse: z.ZodObject<{
|
|
1596
|
-
accepted: z.ZodBoolean;
|
|
1597
|
-
state: z.ZodString;
|
|
1598
|
-
}, z.core.$strict>;
|
|
1599
|
-
type ResultResponse = z.infer<typeof ResultResponse>;
|
|
1600
|
-
declare const ReleaseRequest: z.ZodObject<{
|
|
1601
|
-
protocolVersion: z.ZodLiteral<"0">;
|
|
1602
|
-
runnerId: z.ZodString;
|
|
1603
|
-
leases: z.ZodArray<z.ZodObject<{
|
|
1604
|
-
jobId: z.ZodString;
|
|
1605
|
-
leaseId: z.ZodString;
|
|
1606
|
-
}, z.core.$strip>>;
|
|
1607
|
-
reason: z.ZodEnum<{
|
|
1608
|
-
revoked: "revoked";
|
|
1609
|
-
shutdown: "shutdown";
|
|
1610
|
-
pause: "pause";
|
|
1611
|
-
"backend-down": "backend-down";
|
|
1612
|
-
refused: "refused";
|
|
1613
|
-
}>;
|
|
1614
|
-
}, z.core.$strict>;
|
|
1615
|
-
type ReleaseRequest = z.infer<typeof ReleaseRequest>;
|
|
1616
|
-
declare const ReleaseResponse: z.ZodObject<{
|
|
1617
|
-
released: z.ZodArray<z.ZodString>;
|
|
2748
|
+
signature: z.ZodString;
|
|
1618
2749
|
}, z.core.$strict>;
|
|
1619
|
-
type
|
|
2750
|
+
type Succession = z.infer<typeof Succession>;
|
|
2751
|
+
/** The exact bytes signed. One definition; both sides call it. */
|
|
2752
|
+
declare function successionStatement(fromKeyId: string, toKeyId: string): Uint8Array;
|
|
1620
2753
|
/**
|
|
1621
|
-
*
|
|
2754
|
+
* Sign a succession from the keys being retired to the identity taking over.
|
|
1622
2755
|
*
|
|
1623
|
-
*
|
|
1624
|
-
*
|
|
1625
|
-
*
|
|
1626
|
-
* with no response at all.
|
|
2756
|
+
* Takes `StoredKeys` for the predecessor because only the holder of K1's
|
|
2757
|
+
* private half can produce this, which is the property the whole design rests
|
|
2758
|
+
* on. A site calls this once, at rotation, on the machine holding its keys.
|
|
1627
2759
|
*/
|
|
1628
|
-
declare
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
2760
|
+
declare function signSuccession(previous: StoredKeys, next: PublicIdentity): Succession;
|
|
2761
|
+
/**
|
|
2762
|
+
* Check one link: did `link.identity` sign over succeeding to `toKeyId`?
|
|
2763
|
+
*
|
|
2764
|
+
* `toKeyId` is passed in rather than read from anywhere in `link`, and that is
|
|
2765
|
+
* the load-bearing detail. A verifier that recovered the successor from the
|
|
2766
|
+
* signed statement would accept a statement about *any* successor, which is
|
|
2767
|
+
* the replay this design names in C.1 — the signature is genuine, the
|
|
2768
|
+
* successor it names is not the one being installed.
|
|
2769
|
+
*/
|
|
2770
|
+
declare function verifyLink(link: Succession, toKeyId: string): boolean;
|
|
2771
|
+
/** Why a chain was refused, in the words a log line uses. */
|
|
2772
|
+
type SuccessionFailure = "no-chain" | "too-long" | "unknown-origin" | "broken-link";
|
|
2773
|
+
interface SuccessionWalk {
|
|
2774
|
+
/** The ids the chain passes through, oldest first, ending at the current. */
|
|
2775
|
+
readonly path: string[];
|
|
2776
|
+
/** The approved id the chain reached, when it reached one. */
|
|
2777
|
+
readonly from?: string;
|
|
2778
|
+
readonly failure?: SuccessionFailure;
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Walk a chain from the key being presented back to a key already approved.
|
|
2782
|
+
*
|
|
2783
|
+
* `chain` is ordered oldest last, as the projection carries it — so walking it
|
|
2784
|
+
* means starting at the current key and stepping backwards, each link proving
|
|
2785
|
+
* that its holder signed for the id in front of it.
|
|
2786
|
+
*
|
|
2787
|
+
* Returns the approved id it reached, or why it did not. **Deliberately
|
|
2788
|
+
* returns rather than throws**: a chain that does not verify is ordinary
|
|
2789
|
+
* hostile input, and the caller's job is to keep its existing pin and say so.
|
|
2790
|
+
*
|
|
2791
|
+
* `approved` is asked as a predicate rather than taken as a set because the
|
|
2792
|
+
* daemon's notion of "already approved" includes tombstoned ids — a site that
|
|
2793
|
+
* left the allowlist and came back is still a site this machine has vouched
|
|
2794
|
+
* for, and rotation must not become a way to launder that distinction away.
|
|
2795
|
+
*/
|
|
2796
|
+
declare function walkSuccession(input: {
|
|
2797
|
+
current: string;
|
|
2798
|
+
chain: readonly Succession[];
|
|
2799
|
+
approved: (keyId: string) => boolean;
|
|
2800
|
+
}): SuccessionWalk;
|
|
2801
|
+
|
|
2802
|
+
/**
|
|
2803
|
+
* The normative MUSTs of protocol v0, as data.
|
|
2804
|
+
*
|
|
2805
|
+
* byollm_001 requires that "every MUST above has a conformance test id
|
|
2806
|
+
* referenced inline". Keeping the MUSTs as a frozen registry rather than
|
|
2807
|
+
* prose is what makes that requirement *checkable*: the conformance kit
|
|
2808
|
+
* imports {@link MUSTS} and fails if any id has no test asserting it, so a
|
|
2809
|
+
* new MUST cannot be added without a test and a test cannot silently drift
|
|
2810
|
+
* away from the statement it claims to prove.
|
|
2811
|
+
*
|
|
2812
|
+
* Ids are stable and public — third-party servers cite them in their
|
|
2813
|
+
* certification output.
|
|
2814
|
+
*/
|
|
2815
|
+
/** Which side of the wire is obliged to enforce a given MUST. */
|
|
2816
|
+
type MustEnforcer = "daemon" | "server" | "both";
|
|
2817
|
+
/**
|
|
2818
|
+
* How a MUST is actually verified — which is not the same question as who
|
|
2819
|
+
* enforces it, and is the one that decides what "byollm-compatible" means.
|
|
2820
|
+
*
|
|
2821
|
+
* The conformance kit's credibility rests on an implicit claim that every
|
|
2822
|
+
* MUST is checkable. Ten of them were not, and the kit reported that honestly
|
|
2823
|
+
* while nothing acted on it. Making the kind explicit turns "uncovered" from
|
|
2824
|
+
* a number needing a paragraph of explanation into a number that should be
|
|
2825
|
+
* zero.
|
|
2826
|
+
*
|
|
2827
|
+
* - `conformance` — the kit asserts it against *any* implementation. This is
|
|
2828
|
+
* the strong kind: a third party runs the suite and learns something.
|
|
2829
|
+
* - `adversarial` — proved by the reference daemon's own suites in this repo
|
|
2830
|
+
* (the hostile-payload corpus, or its unit tests). Real verification, and
|
|
2831
|
+
* it runs in CI — but it proves things about *our* daemon, not about
|
|
2832
|
+
* someone else's, so the kit cannot carry it.
|
|
2833
|
+
* - `construction` — true by the shape of the code, where a test could only
|
|
2834
|
+
* sample. A reviewer verifies it; a suite cannot.
|
|
2835
|
+
* ## When a MUST binds both sides — cloud_008 Tier 3
|
|
2836
|
+
*
|
|
2837
|
+
* `AUDIENCE_BOTH_SIDES` says the server and the daemon each enforce. The kit
|
|
2838
|
+
* passed **entirely** with the server's half deleted: every check drove a real
|
|
2839
|
+
* daemon, and a daemon refuses locally, so "the job did not run" looked
|
|
2840
|
+
* identical whichever side refused it. A full-honest-stack test proves only
|
|
2841
|
+
* the conjunction.
|
|
2842
|
+
*
|
|
2843
|
+
* So a `both`-enforced MUST needs **one check per party, each with the honest
|
|
2844
|
+
* counterpart removed** — C032 claims over the raw protocol precisely so no
|
|
2845
|
+
* daemon admission logic runs. Where a check strips one side, its comment
|
|
2846
|
+
* says which; where a MUST is enforced by both and only one side is checked,
|
|
2847
|
+
* that is a gap rather than coverage.
|
|
2848
|
+
*
|
|
2849
|
+
* - `operator` — a claim about how someone runs a deployment, verifiable only
|
|
2850
|
+
* by audit or by reading source. The honest category, and the one that
|
|
2851
|
+
* exists so a property nobody can check from outside is *labelled* as such
|
|
2852
|
+
* rather than laundered by association with the checkable ones.
|
|
2853
|
+
*/
|
|
2854
|
+
type MustVerification = "conformance" | "adversarial" | "construction" | "operator";
|
|
2855
|
+
/**
|
|
2856
|
+
* How a MUST is verified — one kind, or several.
|
|
2857
|
+
*
|
|
2858
|
+
* Several is not hedging. `SITES_LOCALLY_APPROVED` is the case that forced it:
|
|
2859
|
+
* the fence is **construction** — a daemon cannot serve a site that is not in
|
|
2860
|
+
* its map, and admission refuses before a payload is fetched — while the
|
|
2861
|
+
* property that a *removed and re-offered* id is still refused needs a hostile
|
|
2862
|
+
* sequence of heartbeats no honest client would send, which is
|
|
2863
|
+
* **adversarial**. Recording one and dropping the other would either overstate
|
|
2864
|
+
* what a type check proves or understate what the suites do.
|
|
2865
|
+
*
|
|
2866
|
+
* The alternative was a second field for the second kind, which is two answers
|
|
2867
|
+
* to one question — the shape this project keeps deleting.
|
|
2868
|
+
*/
|
|
2869
|
+
type MustVerifiedBy = MustVerification | readonly [MustVerification, ...MustVerification[]];
|
|
2870
|
+
/** The kinds a MUST claims, always as a list. */
|
|
2871
|
+
declare function kindsOf(must: {
|
|
2872
|
+
readonly verifiedBy: MustVerifiedBy;
|
|
2873
|
+
}): readonly MustVerification[];
|
|
2874
|
+
/** A single normative requirement of the protocol. */
|
|
2875
|
+
interface Must {
|
|
2876
|
+
/** Stable public id, cited by conformance output. */
|
|
2877
|
+
readonly id: string;
|
|
2878
|
+
/** The requirement, in MUST language. */
|
|
2879
|
+
readonly statement: string;
|
|
2880
|
+
/** Which implementation is obliged to enforce it. */
|
|
2881
|
+
readonly enforcedBy: MustEnforcer;
|
|
2882
|
+
/**
|
|
2883
|
+
* How this is verified. `conformance` is the only kind the kit can assert;
|
|
2884
|
+
* see {@link MustVerification} for why the others exist.
|
|
2885
|
+
*/
|
|
2886
|
+
readonly verifiedBy: MustVerifiedBy;
|
|
2887
|
+
/** Spec section this was adjudicated in. */
|
|
2888
|
+
readonly source: string;
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Every normative MUST in protocol v0.
|
|
2892
|
+
*
|
|
2893
|
+
* @remarks
|
|
2894
|
+
* Grouped by concern for readability; the conformance kit treats this as a
|
|
2895
|
+
* flat set. Adding an entry here without a corresponding conformance test is
|
|
2896
|
+
* a CI failure, by design.
|
|
2897
|
+
*/
|
|
2898
|
+
declare const MUSTS: Readonly<{
|
|
2899
|
+
readonly PAIR_ONE_USER: Must;
|
|
2900
|
+
readonly PAIR_INTERACTIVE: Must;
|
|
2901
|
+
readonly PAIR_CODE_EXPIRES: Must;
|
|
2902
|
+
readonly VERSION_HANDSHAKE_REQUIRED: Must;
|
|
2903
|
+
readonly SITE_KEY_BY_STUB: Must;
|
|
2904
|
+
readonly SITES_LOCALLY_APPROVED: Must;
|
|
2905
|
+
readonly KEYS_EXCHANGED_AT_CONSENT: Must;
|
|
2906
|
+
readonly REQUESTS_SIGNED_NOT_BEARER: Must;
|
|
2907
|
+
readonly LEASE_SCOPED_BY_GRANT: Must;
|
|
2908
|
+
readonly STUB_METADATA_EXHAUSTIVE: Must;
|
|
2909
|
+
readonly ENVELOPE_SEALED_AND_SIGNED: Must;
|
|
2910
|
+
readonly KIND_TYPED_ONLY: Must;
|
|
2911
|
+
readonly KIND_NO_CODE: Must;
|
|
2912
|
+
readonly CLAIM_REQUIRES_CAPABILITY: Must;
|
|
2913
|
+
readonly CAPABILITY_IS_DETECTED: Must;
|
|
2914
|
+
readonly CLAIM_ATOMIC: Must;
|
|
2915
|
+
readonly LEASE_HONORED: Must;
|
|
2916
|
+
readonly LEASE_RECLAIMABLE: Must;
|
|
2917
|
+
readonly AUDIENCE_BOTH_SIDES: Must;
|
|
2918
|
+
readonly SUBSCRIPTION_SELF_LOCK: Must;
|
|
2919
|
+
readonly METERED_DEFAULTS_SELF: Must;
|
|
2920
|
+
readonly METERED_REQUIRES_CEILING: Must;
|
|
2921
|
+
readonly COST_NOT_CONFIGURABLE: Must;
|
|
2922
|
+
readonly REMOTE_IS_NEVER_FREE: Must;
|
|
2923
|
+
readonly NAMED_LOCAL_ALLOWLIST: Must;
|
|
2924
|
+
readonly REFUSAL_NOT_REOFFERED: Must;
|
|
2925
|
+
readonly REVOCATION_HONORED: Must;
|
|
2926
|
+
readonly CANCEL_HONORED: Must;
|
|
2927
|
+
readonly DEPENDS_ON_GATING: Must;
|
|
2928
|
+
readonly TTL_EXPIRY: Must;
|
|
2929
|
+
readonly NO_RUNNER_SIGNAL: Must;
|
|
2930
|
+
readonly RESULT_IDEMPOTENT: Must;
|
|
2931
|
+
readonly PROVENANCE_NAMES_DEVICE: Must;
|
|
2932
|
+
readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
|
|
2933
|
+
readonly NO_SHELL_INTERPOLATION: Must;
|
|
2934
|
+
/**
|
|
2935
|
+
* Amended for byollm_016 Phase B, and the amendment is deliberately narrow.
|
|
2936
|
+
*
|
|
2937
|
+
* A site may now name a **service** on the stub. The temptation is to read
|
|
2938
|
+
* that as a crack in this law, so the statement below says exactly where the
|
|
2939
|
+
* line is: a name selects from a menu the owner published, and resolves to a
|
|
2940
|
+
* model, backend, base URL and flags **only** through that owner's own
|
|
2941
|
+
* config. The site supplies a key; the owner supplies every value it maps
|
|
2942
|
+
* to. A name the owner does not advertise is refused rather than
|
|
2943
|
+
* substituted, because substitution is how "you may pick from my list" turns
|
|
2944
|
+
* into "you may ask for anything and get something".
|
|
2945
|
+
*
|
|
2946
|
+
* Two properties keep it from drifting into "sites demand models":
|
|
2947
|
+
*
|
|
2948
|
+
* 1. **Nothing the site sends is ever a value.** No model string, no URL,
|
|
2949
|
+
* no flag crosses the wire — only a key that means nothing off this
|
|
2950
|
+
* owner's machine.
|
|
2951
|
+
* 2. **It is a stub field, never a payload field.** The prompt cannot
|
|
2952
|
+
* reach it. That is unchanged and is the sentence the second clause
|
|
2953
|
+
* below still enforces verbatim.
|
|
2954
|
+
*/
|
|
2955
|
+
readonly NO_PAYLOAD_ROUTING: Must;
|
|
2956
|
+
readonly STRIPPED_CHILD_ENV: Must;
|
|
2957
|
+
readonly HTTP_BASE_URL_SAFE: Must;
|
|
2958
|
+
readonly OUTPUT_INERT: Must;
|
|
2959
|
+
readonly COMMUNITY_BUDGETS: Must;
|
|
2960
|
+
readonly REVOCATION_IMMEDIATE: Must;
|
|
2961
|
+
readonly CONSENT_BEFORE_ROUTE: Must;
|
|
2962
|
+
readonly ROSTER_NOT_DISCLOSED: Must;
|
|
2963
|
+
readonly EFFECTIVE_OFFER_ONLY: Must;
|
|
2964
|
+
readonly FALLBACK_LABELED: Must;
|
|
2965
|
+
readonly RELAY_BLIND: Must;
|
|
2966
|
+
readonly SHARED_COMPUTE_DISCLOSED: Must;
|
|
1636
2967
|
}>;
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
unauthorized: "unauthorized";
|
|
1644
|
-
"not-found": "not-found";
|
|
1645
|
-
"rate-limited": "rate-limited";
|
|
1646
|
-
"server-error": "server-error";
|
|
1647
|
-
}>;
|
|
1648
|
-
message: z.ZodString;
|
|
1649
|
-
retryAfter: z.ZodOptional<z.ZodNumber>;
|
|
1650
|
-
}, z.core.$strict>;
|
|
1651
|
-
type WireError = z.infer<typeof WireError>;
|
|
1652
|
-
/** HTTP status each error code is served with. */
|
|
1653
|
-
declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
|
|
1654
|
-
declare const FetchRequest: z.ZodObject<{
|
|
1655
|
-
protocolVersion: z.ZodString;
|
|
1656
|
-
runnerId: z.ZodString;
|
|
1657
|
-
jobId: z.ZodString;
|
|
1658
|
-
leaseId: z.ZodString;
|
|
1659
|
-
}, z.core.$strict>;
|
|
1660
|
-
type FetchRequest = z.infer<typeof FetchRequest>;
|
|
1661
|
-
declare const FetchResponse: z.ZodObject<{
|
|
1662
|
-
envelope: z.ZodObject<{
|
|
1663
|
-
ciphertext: z.ZodString;
|
|
1664
|
-
recipientKeyId: z.ZodString;
|
|
1665
|
-
senderKeyId: z.ZodString;
|
|
1666
|
-
direction: z.ZodEnum<{
|
|
1667
|
-
payload: "payload";
|
|
1668
|
-
result: "result";
|
|
1669
|
-
}>;
|
|
1670
|
-
deadlineAt: z.ZodNumber;
|
|
1671
|
-
}, z.core.$strict>;
|
|
1672
|
-
}, z.core.$strict>;
|
|
1673
|
-
type FetchResponse = z.infer<typeof FetchResponse>;
|
|
2968
|
+
/** The id of any normative MUST. */
|
|
2969
|
+
type MustId = keyof typeof MUSTS;
|
|
2970
|
+
/** All MUST ids, for coverage checks. */
|
|
2971
|
+
declare const MUST_IDS: readonly ("PAIR_ONE_USER" | "PAIR_INTERACTIVE" | "PAIR_CODE_EXPIRES" | "VERSION_HANDSHAKE_REQUIRED" | "SITE_KEY_BY_STUB" | "SITES_LOCALLY_APPROVED" | "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")[];
|
|
2972
|
+
/** Every MUST verified a particular way. */
|
|
2973
|
+
declare function mustsVerifiedBy(kind: MustVerification): MustId[];
|
|
1674
2974
|
|
|
1675
|
-
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, SIZE_CLASS_LIMITS, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, 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 };
|
|
2975
|
+
export { ABOUT, ABOUT_SHORT, ABOUT_SHORT_LEDE, ABOUT_SHORT_TAIL, AUDIENCES, Audience, BACKENDS, BACKEND_CLASSES, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, CLOCK_ATTRIBUTION_MS, CLOCK_SKEW_WARN_MS, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DeliveredResult, ENCRYPTION_KEY_CONTEXT, ENDPOINTS, ENVELOPE_MAX_AGE_MS, ERROR_STATUS, type Endpoint, type EnvelopeContext, EnvelopeDirection, type EnvelopeFailure, FetchRequest, FetchResponse, type FloorRefusal, GRANT_CONTEXT, GRANT_MAX_AGE_MS, GRANT_SIGNED_FIELDS, GeneratePayload, type GrantClaims, GrantRef, type GrantRefusal, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobRefused, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_ENVELOPE_BYTES, MAX_PURPOSES, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, Manifest, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, OFFER_SCOPES, OfferScope, type OpenResult, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, Purpose, REFUSAL_MESSAGES, REFUSAL_TEXT, RESERVED_PURPOSE, RETIREMENT_WINDOW_MS, RefusalReason, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASSES, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SignedGrant, SizeClass, type SpendConsent, type StopReason, StopReasonSchema, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, UPDATE_OFFER_SINCE, UPGRADE_COMMAND, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, backendName, canTransition, canonicalRequest, checkDaemonFloor, checkProtocolVersion, classifyCost, compareVersions, cryptoReady, declaredVersion, describeBytes, effectiveOfferScope, envelopeBytes, fingerprint, generateKeys, grantStatement, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mayOfferUpdate, mentionsWireField, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signGrant, signRequest, signSiteRequest, signSuccession, signWith, singlePurposeManifest, sizeClassCeiling, sizeClassOf, successionStatement, tooLargeMessage, updateOfferFor, verifyGrant, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession, withoutComments };
|