@byollm/protocol 0.1.0-alpha.7 → 0.1.0-alpha.71
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 +106 -4
- package/dist/index.d.ts +1178 -132
- package/dist/index.js +1426 -327
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
/** The full description — five sections, plus why it matters. */
|
|
4
|
+
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.";
|
|
5
|
+
/**
|
|
6
|
+
* The first paragraph, which stands alone.
|
|
7
|
+
*
|
|
8
|
+
* What the welcome screen shows: somebody deciding whether to trust a site's
|
|
9
|
+
* button needs the whole idea in one breath, not a page.
|
|
10
|
+
*/
|
|
11
|
+
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.";
|
|
12
|
+
/** The rest, for surfaces with room. Shown before "Learn more →". */
|
|
13
|
+
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.";
|
|
14
|
+
/** Both halves, for a surface that wants the paragraph entire. */
|
|
15
|
+
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.";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Its members, for anything that has to report what it accepts.
|
|
19
|
+
*
|
|
20
|
+
* Derived from the enum for the reason `JOB_KINDS` and `OFFER_SCOPES` are: a
|
|
21
|
+
* second list of the same words is a second thing to keep in step, and this
|
|
22
|
+
* one is read by the promotion gate to compare a deployed hub against a
|
|
23
|
+
* version about to be promoted.
|
|
24
|
+
*/
|
|
25
|
+
declare const BACKEND_CLASSES: readonly ("http" | "process")[];
|
|
3
26
|
/**
|
|
4
27
|
* How a backend reaches its model — the taxonomy introduced in byollm_001
|
|
5
28
|
* Rev 1 §A, because the two classes have different threat surfaces.
|
|
@@ -22,9 +45,9 @@ type BackendClass = z.infer<typeof BackendClass>;
|
|
|
22
45
|
*
|
|
23
46
|
* This replaced a two-valued `account` field that conflated two unrelated
|
|
24
47
|
* 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,
|
|
26
|
-
*
|
|
27
|
-
*
|
|
48
|
+
* accepts an API key, so an owner could point it at a paid endpoint, share it,
|
|
49
|
+
* and donate their credit balance to strangers. The community budgets cap job
|
|
50
|
+
* *count*, not spend.
|
|
28
51
|
*
|
|
29
52
|
* - `free` — local compute. Costs electricity, not money. Shareable.
|
|
30
53
|
* - `metered` — per-token billing against the owner's account. Legal to
|
|
@@ -104,11 +127,23 @@ declare const BACKENDS: Readonly<{
|
|
|
104
127
|
readonly mistral: BackendDescriptor;
|
|
105
128
|
readonly "openai-http": BackendDescriptor;
|
|
106
129
|
readonly "claude-cli": BackendDescriptor;
|
|
130
|
+
/**
|
|
131
|
+
* OpenAI's Codex CLI, on a ChatGPT plan — byollm_016 stage 3.
|
|
132
|
+
*
|
|
133
|
+
* `subscription`, so `SUBSCRIPTION_SELF_LOCK` pins it to its owner's own
|
|
134
|
+
* work whatever the config says. That is load-bearing here in a way it is
|
|
135
|
+
* not for `claude-cli`: Codex is an *agent*, and its default feature set
|
|
136
|
+
* includes a shell tool, browser control and computer use. The daemon
|
|
137
|
+
* disables every one of them, verified against the shipped binary rather
|
|
138
|
+
* than assumed — see `codex-cli.ts` — but the self-lock is the floor under
|
|
139
|
+
* that verification rather than a duplicate of it.
|
|
140
|
+
*/
|
|
141
|
+
readonly "codex-cli": BackendDescriptor;
|
|
107
142
|
}>;
|
|
108
143
|
/** The id of a registered backend. */
|
|
109
144
|
type BackendId = keyof typeof BACKENDS;
|
|
110
145
|
/** 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")[];
|
|
146
|
+
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")[];
|
|
112
147
|
declare const BackendIdSchema: z.ZodEnum<{
|
|
113
148
|
ollama: "ollama";
|
|
114
149
|
mlx: "mlx";
|
|
@@ -128,6 +163,7 @@ declare const BackendIdSchema: z.ZodEnum<{
|
|
|
128
163
|
mistral: "mistral";
|
|
129
164
|
"openai-http": "openai-http";
|
|
130
165
|
"claude-cli": "claude-cli";
|
|
166
|
+
"codex-cli": "codex-cli";
|
|
131
167
|
}>;
|
|
132
168
|
/** Narrow an arbitrary string to a registered backend id. */
|
|
133
169
|
declare function isBackendId(value: string): value is BackendId;
|
|
@@ -152,50 +188,158 @@ declare function backendDescriptor(id: BackendId): BackendDescriptor;
|
|
|
152
188
|
* an act by the machine's owner against their own account, and the threat
|
|
153
189
|
* model here is a hostile *job*, not an owner routing around a rule that
|
|
154
190
|
* exists to protect them. What this catches is the accident — a remote paid
|
|
155
|
-
* endpoint offered
|
|
191
|
+
* endpoint offered to a team because nobody thought about the bill. See
|
|
156
192
|
* `docs/security.md` §4a.
|
|
157
193
|
*/
|
|
158
194
|
declare function isLocalHost(hostname: string): boolean;
|
|
159
195
|
/**
|
|
160
|
-
*
|
|
196
|
+
* Is this model name a hosted one billed by its vendor?
|
|
197
|
+
*
|
|
198
|
+
* Ollama serves cloud models through the same local endpoint as local ones,
|
|
199
|
+
* so the address says "free" about a model somebody is being charged for. The
|
|
200
|
+
* only thing that distinguishes them is the name, and the distinguishing part
|
|
201
|
+
* is the **tag** — everything after the last colon.
|
|
202
|
+
*
|
|
203
|
+
* End-anchored on the tag, which is what makes it decidable rather than a
|
|
204
|
+
* guess about substrings:
|
|
205
|
+
*
|
|
206
|
+
* - `glm-5.2:cloud` → cloud
|
|
207
|
+
* - `deepseek-v4-flash:0731-cloud` → cloud
|
|
208
|
+
* - `x:cloudless` → not cloud, the tag ends in "less"
|
|
209
|
+
* - `cloudmodel:7b` → not cloud, the tag is "7b"
|
|
210
|
+
* - `llama3.2` → not cloud, there is no tag at all
|
|
211
|
+
*
|
|
212
|
+
* An oddball like `:xcloud` classifies as cloud, and that is the **only
|
|
213
|
+
* permitted failure direction**: calling a free model metered narrows what an
|
|
214
|
+
* owner may share and costs nobody money, while the reverse hands somebody
|
|
215
|
+
* else's bill to a stranger.
|
|
216
|
+
*/
|
|
217
|
+
declare function isCloudTaggedModel(model: string): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* The cost class of a configured service.
|
|
161
220
|
*
|
|
162
221
|
* For every named provider this is whatever the registry says, full stop
|
|
163
222
|
* ({@link MUSTS.COST_NOT_CONFIGURABLE}). For the generic `openai-http` entry
|
|
164
223
|
* it is inferred from the base URL, and a base URL that cannot be parsed is
|
|
165
224
|
* treated as `metered` — the expensive side, because guessing "free" wrong
|
|
166
225
|
* costs the owner money.
|
|
226
|
+
*
|
|
227
|
+
* The model has the last word in one direction only. A local address with a
|
|
228
|
+
* cloud-tagged model is `metered`: Ollama proxies hosted models through
|
|
229
|
+
* `127.0.0.1`, so the endpoint is local and the bill is not. Read from the
|
|
230
|
+
* **configured value**, never from what the server lists — the owner's config
|
|
231
|
+
* is the thing they chose, and a server's catalogue is not theirs to be
|
|
232
|
+
* classified by.
|
|
233
|
+
*/
|
|
234
|
+
declare function resolveCost(id: BackendId, baseUrl: string | undefined,
|
|
235
|
+
/**
|
|
236
|
+
* **Required, and that is the fix.**
|
|
237
|
+
*
|
|
238
|
+
* This was optional, and the no-re-derivation law was breached through the
|
|
239
|
+
* gap rather than by anybody copying the logic. `byollm offer` passed two of
|
|
240
|
+
* three arguments and `resolveConfig` passed three, so the same service was
|
|
241
|
+
* free to one and metered to the other: `glm-5.2:cloud` on a loopback
|
|
242
|
+
* address looks local until you read the tag. The command wrote a share the
|
|
243
|
+
* daemon then refused, and told its owner to run the command they had just
|
|
244
|
+
* run.
|
|
245
|
+
*
|
|
246
|
+
* A shared rule's signature admits no partial askers. `undefined` is still a
|
|
247
|
+
* legal *value* — a service genuinely without a model — but it has to be
|
|
248
|
+
* passed, so choosing to omit the model is a decision at the call site
|
|
249
|
+
* rather than a default nobody notices.
|
|
167
250
|
*/
|
|
168
|
-
|
|
251
|
+
model: string | undefined): BackendCost;
|
|
252
|
+
/**
|
|
253
|
+
* Why a service costs what it costs — the same decision, said out loud.
|
|
254
|
+
*
|
|
255
|
+
* Consent has to name the rule that fired. The offer ceremony read
|
|
256
|
+
* "Any OpenAI-compatible server ... bills your account per token", which is
|
|
257
|
+
* false about the type — an owner's local qwen is `openai-http` and costs
|
|
258
|
+
* nothing but electricity — and so it gave a reason that its reader could
|
|
259
|
+
* check and find wrong. The thing that bills is the `:cloud` tag on one
|
|
260
|
+
* model, not the transport that carries it.
|
|
261
|
+
*
|
|
262
|
+
* One function decides and one function explains, and the second calls the
|
|
263
|
+
* first, so a message can never describe a classification the code did not
|
|
264
|
+
* make. Splitting them would be the same defect this signature was just
|
|
265
|
+
* hardened against, arriving as prose.
|
|
266
|
+
*/
|
|
267
|
+
/**
|
|
268
|
+
* The product's name alone, without the parenthetical that classifies it.
|
|
269
|
+
*
|
|
270
|
+
* Every label in this registry does two jobs: it names a product and says what
|
|
271
|
+
* that product means for the person paying — "Claude CLI (your subscription)",
|
|
272
|
+
* "Ollama (local)". That is right for a list, where the parenthetical is the
|
|
273
|
+
* only classification on screen.
|
|
274
|
+
*
|
|
275
|
+
* It is wrong inside a sentence that states the classification itself, which
|
|
276
|
+
* then stutters: "my-claude runs on Claude CLI (your subscription), a
|
|
277
|
+
* subscription whose terms…". Prose wants the name; the sentence around it is
|
|
278
|
+
* already carrying the meaning.
|
|
279
|
+
*
|
|
280
|
+
* One definition rather than a regex at each call site — and the place to
|
|
281
|
+
* change if the registry ever splits the two facts into two fields, which is
|
|
282
|
+
* the better shape and not worth a migration today.
|
|
283
|
+
*/
|
|
284
|
+
declare function backendName(id: BackendId): string;
|
|
285
|
+
interface CostReason {
|
|
286
|
+
readonly cost: BackendCost;
|
|
287
|
+
/** The rule, in the words a person consenting needs. */
|
|
288
|
+
readonly because: string;
|
|
289
|
+
}
|
|
290
|
+
declare function classifyCost(id: BackendId, baseUrl: string | undefined, model: string | undefined): CostReason;
|
|
169
291
|
|
|
170
292
|
/**
|
|
171
293
|
* Who may run a job, declared by the app that enqueued it.
|
|
172
294
|
*
|
|
173
|
-
* - `
|
|
174
|
-
* - `
|
|
175
|
-
*
|
|
176
|
-
* - `
|
|
295
|
+
* - `private` — only the job owner's own devices.
|
|
296
|
+
* - `team` — a device whose owner admits this person.
|
|
297
|
+
*
|
|
298
|
+
* **One vocabulary, ruled 2026-08-24.** These were `self | named | public`
|
|
299
|
+
* while {@link OfferScope} used different words for the same idea, which would
|
|
300
|
+
* have left every seam where the two meet speaking two languages, and every
|
|
301
|
+
* doc explaining "self versus private" for ever. They are still independent
|
|
302
|
+
* axes — a job says who may run it, a service says who it will run for — and a
|
|
303
|
+
* job runs only where both agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}).
|
|
304
|
+
*
|
|
305
|
+
* **`public` is gone, ruled 2026-08-26 (byollm_016).** Not deprecated,
|
|
306
|
+
* removed, and removed from the OSS daemon too rather than parked as a
|
|
307
|
+
* community posture. The argument was a measurement rather than a preference:
|
|
308
|
+
* device-side admission had never once been exercised end to end, because
|
|
309
|
+
* every cross-user test ran against a publicly offered service and
|
|
310
|
+
* {@link matchAudience} returned ALLOWED for those *without consulting the
|
|
311
|
+
* device at all*. `public` was the off switch for admission, and an enum with
|
|
312
|
+
* a value that skips verification is a fail-open waiting for the wiring bug
|
|
313
|
+
* that reaches it. There is now no such value.
|
|
177
314
|
*/
|
|
178
315
|
declare const Audience: z.ZodEnum<{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
public: "public";
|
|
316
|
+
private: "private";
|
|
317
|
+
team: "team";
|
|
182
318
|
}>;
|
|
183
319
|
type Audience = z.infer<typeof Audience>;
|
|
184
320
|
/**
|
|
185
|
-
* What a
|
|
186
|
-
*
|
|
187
|
-
*
|
|
321
|
+
* What a device's owner is willing to run for other people, per service.
|
|
322
|
+
*
|
|
323
|
+
* - `private` — the owner's own work only.
|
|
324
|
+
* - `team` — whoever the owner's authority admits. Membership is **central**,
|
|
325
|
+
* not per-person: the device follows what it is told by a signature it can
|
|
326
|
+
* check, rather than holding its own copy of who is in it (byollm_016).
|
|
327
|
+
*
|
|
328
|
+
* Two values, and no third that means "everyone". See {@link Audience} for
|
|
329
|
+
* why `public` was removed rather than parked, and note the shape of the
|
|
330
|
+
* remaining enum: **every value left requires the device to verify
|
|
331
|
+
* something.** `private` checks the owner; `team` checks admission. That is
|
|
332
|
+
* the property, not an accident of there being two.
|
|
188
333
|
*/
|
|
189
334
|
declare const OfferScope: z.ZodEnum<{
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
public: "public";
|
|
335
|
+
private: "private";
|
|
336
|
+
team: "team";
|
|
193
337
|
}>;
|
|
194
338
|
type OfferScope = z.infer<typeof OfferScope>;
|
|
195
339
|
/** All audience values, in widening order. */
|
|
196
|
-
declare const AUDIENCES: readonly ("
|
|
340
|
+
declare const AUDIENCES: readonly ("private" | "team")[];
|
|
197
341
|
/** All offer scopes, in widening order. */
|
|
198
|
-
declare const OFFER_SCOPES: readonly ("
|
|
342
|
+
declare const OFFER_SCOPES: readonly ("private" | "team")[];
|
|
199
343
|
/**
|
|
200
344
|
* Why a job was refused. Distinct codes because byollm_002 requires that
|
|
201
345
|
* different truths never share a message — "no matching work" and "refused on
|
|
@@ -234,9 +378,9 @@ interface SpendConsent {
|
|
|
234
378
|
* its matcher call, so no code path can observe a scope wider than the cost
|
|
235
379
|
* class allows:
|
|
236
380
|
*
|
|
237
|
-
* - `subscription` is locked to `
|
|
381
|
+
* - `subscription` is locked to `private` regardless of config
|
|
238
382
|
* ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — someone else's terms.
|
|
239
|
-
* - `metered` narrows to `
|
|
383
|
+
* - `metered` narrows to `private` unless the owner has explicitly acknowledged
|
|
240
384
|
* the spend ({@link MUSTS.METERED_DEFAULTS_SELF}) — their money.
|
|
241
385
|
* - `free` passes through — their electricity.
|
|
242
386
|
*
|
|
@@ -252,7 +396,8 @@ interface MatchJob {
|
|
|
252
396
|
readonly audience: Audience;
|
|
253
397
|
/**
|
|
254
398
|
* Optional server-side restriction on which runner owners may take a
|
|
255
|
-
* `
|
|
399
|
+
* `team` job. Defence in depth only, and direct-mode only — it never
|
|
400
|
+
* reaches a daemon (cloud_008 §0.2), so the device's own admission is the
|
|
256
401
|
* enforcing side ({@link MUSTS.NAMED_LOCAL_ALLOWLIST}).
|
|
257
402
|
*/
|
|
258
403
|
readonly audienceAllow?: readonly string[] | undefined;
|
|
@@ -268,13 +413,24 @@ interface MatchDaemon {
|
|
|
268
413
|
/** What the owner agreed to spend on others, for a `metered` backend. */
|
|
269
414
|
readonly spend?: SpendConsent | undefined;
|
|
270
415
|
/**
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
416
|
+
* Has something **this device verified** admitted the job's owner?
|
|
417
|
+
*
|
|
418
|
+
* A predicate rather than a value so the protocol package stays free of
|
|
419
|
+
* both file I/O and signature state. What supplies it has changed twice and
|
|
420
|
+
* will change again — a local allowlist, then a held roster, and now a
|
|
421
|
+
* claim-time signed grant (Amendment J) — and the law it feeds has not
|
|
422
|
+
* changed at all: a `team` service runs a stranger's work only when
|
|
423
|
+
* somebody this device can check said so.
|
|
424
|
+
*
|
|
425
|
+
* The server passes a conservative `() => true`: it cannot know what a
|
|
426
|
+
* remote device verified and must not pretend to. The device is the
|
|
427
|
+
* enforcing side, which is the whole point of asking here.
|
|
428
|
+
*
|
|
429
|
+
* Named for the question, not for where the answer lives. This was called
|
|
430
|
+
* `locallyAllows`, and "locally" stopped being true the moment the answer
|
|
431
|
+
* came from a document somebody else signed.
|
|
276
432
|
*/
|
|
277
|
-
readonly
|
|
433
|
+
readonly admits: (owner: string) => boolean;
|
|
278
434
|
}
|
|
279
435
|
/**
|
|
280
436
|
* Decide whether a job may run on a daemon.
|
|
@@ -283,20 +439,20 @@ interface MatchDaemon {
|
|
|
283
439
|
* 1. the job's audience must admit the daemon's owner, and
|
|
284
440
|
* 2. the backend's offer scope must admit the job's owner.
|
|
285
441
|
*
|
|
286
|
-
* The full
|
|
287
|
-
*
|
|
442
|
+
* The full four-way matrix (two audiences × two offer scopes) is asserted by
|
|
443
|
+
* the conformance kit. The function is pure and total so both the daemon
|
|
288
444
|
* and the server can run the identical rule — the daemon refuses, and the
|
|
289
445
|
* server refuses too (byollm_003 §Server-side MUSTs).
|
|
290
446
|
*
|
|
291
447
|
* @example
|
|
292
448
|
* ```ts
|
|
293
449
|
* const result = matchAudience(
|
|
294
|
-
* { owner: "alice", audience: "
|
|
450
|
+
* { owner: "alice", audience: "team" },
|
|
295
451
|
* {
|
|
296
452
|
* owner: "bob",
|
|
297
|
-
* offerScope: "
|
|
453
|
+
* offerScope: "team",
|
|
298
454
|
* cost: "free",
|
|
299
|
-
*
|
|
455
|
+
* admits: (o) => o === "alice",
|
|
300
456
|
* },
|
|
301
457
|
* );
|
|
302
458
|
* // result.ok === true
|
|
@@ -314,9 +470,16 @@ declare const REFUSAL_MESSAGES: Readonly<Record<MatchRefusal, string>>;
|
|
|
314
470
|
* Upper bounds on payload size, enforced at the schema so oversized input is
|
|
315
471
|
* refused at parse time rather than somewhere deeper.
|
|
316
472
|
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
473
|
+
* All three are enforced — cloud_008 Tier 4, finding 30. `maxTotalChars` was
|
|
474
|
+
* declared here and referenced nowhere, under this docstring's claim that the
|
|
475
|
+
* schema enforces them, so a chat payload of 256 messages at a million
|
|
476
|
+
* characters each parsed cleanly at sixty-four times the stated ceiling. The
|
|
477
|
+
* per-field limits were real and the aggregate one was a number in a frozen
|
|
478
|
+
* object.
|
|
479
|
+
*
|
|
480
|
+
* byollm_004 §4 requires stricter limits for community (`team`) jobs; those
|
|
481
|
+
* are applied on top of these by the daemon's budget check, which knows the
|
|
482
|
+
* job's audience. These are the absolute ceilings for any job.
|
|
320
483
|
*/
|
|
321
484
|
declare const PAYLOAD_LIMITS: Readonly<{
|
|
322
485
|
/** Max characters in any single text field. */
|
|
@@ -337,7 +500,7 @@ declare const ChatMessage: z.ZodObject<{
|
|
|
337
500
|
assistant: "assistant";
|
|
338
501
|
}>;
|
|
339
502
|
content: z.ZodString;
|
|
340
|
-
}, z.core.$
|
|
503
|
+
}, z.core.$strict>;
|
|
341
504
|
type ChatMessage = z.infer<typeof ChatMessage>;
|
|
342
505
|
/**
|
|
343
506
|
* Payload for `llm.generate`.
|
|
@@ -364,7 +527,7 @@ declare const ChatPayload: z.ZodObject<{
|
|
|
364
527
|
assistant: "assistant";
|
|
365
528
|
}>;
|
|
366
529
|
content: z.ZodString;
|
|
367
|
-
}, z.core.$
|
|
530
|
+
}, z.core.$strict>>;
|
|
368
531
|
system: z.ZodOptional<z.ZodString>;
|
|
369
532
|
}, z.core.$strict>;
|
|
370
533
|
type ChatPayload = z.infer<typeof ChatPayload>;
|
|
@@ -391,7 +554,7 @@ declare const KindedPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
391
554
|
prompt: z.ZodString;
|
|
392
555
|
system: z.ZodOptional<z.ZodString>;
|
|
393
556
|
}, z.core.$strict>;
|
|
394
|
-
}, z.core.$
|
|
557
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
395
558
|
kind: z.ZodLiteral<"llm.chat">;
|
|
396
559
|
payload: z.ZodObject<{
|
|
397
560
|
messages: z.ZodArray<z.ZodObject<{
|
|
@@ -401,10 +564,10 @@ declare const KindedPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
401
564
|
assistant: "assistant";
|
|
402
565
|
}>;
|
|
403
566
|
content: z.ZodString;
|
|
404
|
-
}, z.core.$
|
|
567
|
+
}, z.core.$strict>>;
|
|
405
568
|
system: z.ZodOptional<z.ZodString>;
|
|
406
569
|
}, z.core.$strict>;
|
|
407
|
-
}, z.core.$
|
|
570
|
+
}, z.core.$strict>], "kind">;
|
|
408
571
|
type KindedPayload = z.infer<typeof KindedPayload>;
|
|
409
572
|
/** The payload type for a given kind. */
|
|
410
573
|
type PayloadFor<K extends JobKind> = K extends "llm.generate" ? GeneratePayload : ChatPayload;
|
|
@@ -433,11 +596,11 @@ declare function payloadTextLength(kinded: KindedPayload): number;
|
|
|
433
596
|
declare const JobState: z.ZodEnum<{
|
|
434
597
|
ok: "ok";
|
|
435
598
|
error: "error";
|
|
599
|
+
expired: "expired";
|
|
436
600
|
queued: "queued";
|
|
437
601
|
claimed: "claimed";
|
|
438
602
|
running: "running";
|
|
439
603
|
canceled: "canceled";
|
|
440
|
-
expired: "expired";
|
|
441
604
|
}>;
|
|
442
605
|
type JobState = z.infer<typeof JobState>;
|
|
443
606
|
/** States from which a job never moves again. */
|
|
@@ -451,7 +614,7 @@ declare const Lease: z.ZodObject<{
|
|
|
451
614
|
id: z.ZodString;
|
|
452
615
|
runnerId: z.ZodString;
|
|
453
616
|
expiresAt: z.ZodNumber;
|
|
454
|
-
}, z.core.$
|
|
617
|
+
}, z.core.$strict>;
|
|
455
618
|
type Lease = z.infer<typeof Lease>;
|
|
456
619
|
/** Payload union as it appears on a job record. */
|
|
457
620
|
declare const JobPayload: z.ZodUnion<readonly [z.ZodObject<{
|
|
@@ -465,7 +628,7 @@ declare const JobPayload: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
465
628
|
assistant: "assistant";
|
|
466
629
|
}>;
|
|
467
630
|
content: z.ZodString;
|
|
468
|
-
}, z.core.$
|
|
631
|
+
}, z.core.$strict>>;
|
|
469
632
|
system: z.ZodOptional<z.ZodString>;
|
|
470
633
|
}, z.core.$strict>]>;
|
|
471
634
|
type JobPayload = z.infer<typeof JobPayload>;
|
|
@@ -494,35 +657,34 @@ declare const ClaimedJob: z.ZodObject<{
|
|
|
494
657
|
assistant: "assistant";
|
|
495
658
|
}>;
|
|
496
659
|
content: z.ZodString;
|
|
497
|
-
}, z.core.$
|
|
660
|
+
}, z.core.$strict>>;
|
|
498
661
|
system: z.ZodOptional<z.ZodString>;
|
|
499
662
|
}, z.core.$strict>]>;
|
|
500
663
|
audience: z.ZodEnum<{
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
public: "public";
|
|
664
|
+
private: "private";
|
|
665
|
+
team: "team";
|
|
504
666
|
}>;
|
|
505
667
|
owner: z.ZodString;
|
|
506
|
-
|
|
668
|
+
site: z.ZodOptional<z.ZodString>;
|
|
669
|
+
service: z.ZodOptional<z.ZodString>;
|
|
507
670
|
lease: z.ZodObject<{
|
|
508
671
|
id: z.ZodString;
|
|
509
672
|
runnerId: z.ZodString;
|
|
510
673
|
expiresAt: z.ZodNumber;
|
|
511
|
-
}, z.core.$
|
|
674
|
+
}, z.core.$strict>;
|
|
512
675
|
}, z.core.$strict>;
|
|
513
676
|
type ClaimedJob = z.infer<typeof ClaimedJob>;
|
|
514
677
|
/**
|
|
515
678
|
* The provenance that travels with every result to the delivery seam.
|
|
516
679
|
*
|
|
517
|
-
* byollm_003 Rev 1: a `
|
|
680
|
+
* byollm_003 Rev 1: a `team` result is attacker-controlled text.
|
|
518
681
|
* The app must never render volunteer output as its own AI's answer without
|
|
519
|
-
* knowing that is what it is ({@link MUSTS.
|
|
682
|
+
* knowing that is what it is ({@link MUSTS.PROVENANCE_NAMES_DEVICE}).
|
|
520
683
|
*/
|
|
521
684
|
declare const ResultProvenance: z.ZodObject<{
|
|
522
685
|
audience: z.ZodEnum<{
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
public: "public";
|
|
686
|
+
private: "private";
|
|
687
|
+
team: "team";
|
|
526
688
|
}>;
|
|
527
689
|
runnerId: z.ZodString;
|
|
528
690
|
runnerOwner: z.ZodString;
|
|
@@ -545,6 +707,30 @@ declare function provenanceFor(input: {
|
|
|
545
707
|
backendClass: BackendClass;
|
|
546
708
|
model: string;
|
|
547
709
|
}): ResultProvenance;
|
|
710
|
+
/**
|
|
711
|
+
* What the daemon did, sealed with the answer — cloud_008 §2.5.
|
|
712
|
+
*
|
|
713
|
+
* These travelled in the clear on `ResultRequest`, which meant two things at
|
|
714
|
+
* once. On the direct plane the site believed unauthenticated fields beside
|
|
715
|
+
* an authenticated envelope — a daemon could seal one answer and *declare* it
|
|
716
|
+
* came from a different model, and only the field it did not sign would be
|
|
717
|
+
* recorded. Through a relay they reached a third party that acts on none of
|
|
718
|
+
* them, and `model` in particular is the kind of detail Amendment A's rule
|
|
719
|
+
* keeps off the wire.
|
|
720
|
+
*
|
|
721
|
+
* Sealed, they are the daemon's signed statement about its own run: the site
|
|
722
|
+
* opens them, nothing in between sees them, and the disposition check that
|
|
723
|
+
* already compares clear-text against ciphertext extends to cover them.
|
|
724
|
+
*/
|
|
725
|
+
declare const RunMetadata: z.ZodObject<{
|
|
726
|
+
model: z.ZodString;
|
|
727
|
+
backendClass: z.ZodEnum<{
|
|
728
|
+
http: "http";
|
|
729
|
+
process: "process";
|
|
730
|
+
}>;
|
|
731
|
+
durationMs: z.ZodNumber;
|
|
732
|
+
}, z.core.$strict>;
|
|
733
|
+
type RunMetadata = z.infer<typeof RunMetadata>;
|
|
548
734
|
/** Successful outcome. */
|
|
549
735
|
declare const JobResultOk: z.ZodObject<{
|
|
550
736
|
outcome: z.ZodLiteral<"ok">;
|
|
@@ -575,17 +761,98 @@ declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
575
761
|
outcome: z.ZodLiteral<"canceled">;
|
|
576
762
|
}, z.core.$strict>], "outcome">;
|
|
577
763
|
type JobOutcome = z.infer<typeof JobOutcome>;
|
|
764
|
+
/**
|
|
765
|
+
* Why a job can never run — byollm_016 Phase B.
|
|
766
|
+
*
|
|
767
|
+
* Every one of these is **terminal**, and that is the whole point of naming
|
|
768
|
+
* them. A job that cannot be matched used to sit queued until its deadline,
|
|
769
|
+
* which reads exactly like a job that is merely waiting for a device to come
|
|
770
|
+
* online — so an app could not tell "any moment now" from "never", and neither
|
|
771
|
+
* could the person watching a spinner. Silence must never read as pending.
|
|
772
|
+
*
|
|
773
|
+
* They are decided by whoever knows first: the site's own SDK where it can see
|
|
774
|
+
* the answer without asking, the router where matching happens, and the daemon
|
|
775
|
+
* again on arrival under the both-sides rule. All three reason from the same
|
|
776
|
+
* list rather than three private vocabularies.
|
|
777
|
+
*/
|
|
778
|
+
declare const RefusalReason: z.ZodEnum<{
|
|
779
|
+
"default-ambiguity": "default-ambiguity";
|
|
780
|
+
"default-unusable": "default-unusable";
|
|
781
|
+
}>;
|
|
782
|
+
type RefusalReason = z.infer<typeof RefusalReason>;
|
|
783
|
+
/**
|
|
784
|
+
* A terminal outcome nobody sealed — byollm_016 Phase B.
|
|
785
|
+
*
|
|
786
|
+
* Every other finished job carries an envelope encrypted by the device that
|
|
787
|
+
* ran it, which is what makes a result unforgeable. These have no device: the
|
|
788
|
+
* job was refused *before* anything could run it, so there is nobody to seal
|
|
789
|
+
* from and no content to seal.
|
|
790
|
+
*
|
|
791
|
+
* **What that costs, stated plainly.** This is the one terminal outcome a
|
|
792
|
+
* router can author. It is worth being exact about the power that grants,
|
|
793
|
+
* because "the relay can write this" sounds alarming until you compare it with
|
|
794
|
+
* what a relay could already do: drop the job, never offer it, and let it
|
|
795
|
+
* expire. A router-authored refusal is *denial of service by a shorter route*,
|
|
796
|
+
* which is a power the router has always had and which the trust model has
|
|
797
|
+
* always said it has. What it emphatically is **not** is forgery: this shape
|
|
798
|
+
* carries no envelope and no output, so it can never be mistaken for an answer
|
|
799
|
+
* a device produced. A relay still cannot fabricate a result, because that
|
|
800
|
+
* needs a signature it does not hold.
|
|
801
|
+
*
|
|
802
|
+
* So the rule this shape enforces by construction: a refusal may deny, and may
|
|
803
|
+
* never assert. Anything that claims work was *done* still comes sealed.
|
|
804
|
+
*/
|
|
805
|
+
declare const JobRefused: z.ZodObject<{
|
|
806
|
+
outcome: z.ZodLiteral<"refused">;
|
|
807
|
+
reason: z.ZodEnum<{
|
|
808
|
+
"default-ambiguity": "default-ambiguity";
|
|
809
|
+
"default-unusable": "default-unusable";
|
|
810
|
+
}>;
|
|
811
|
+
message: z.ZodString;
|
|
812
|
+
}, z.core.$strict>;
|
|
813
|
+
type JobRefused = z.infer<typeof JobRefused>;
|
|
814
|
+
/**
|
|
815
|
+
* The plaintext inside a result envelope.
|
|
816
|
+
*
|
|
817
|
+
* The outcome and how it was produced, together, because they are one
|
|
818
|
+
* statement by one signer. A site that opened only the outcome would be
|
|
819
|
+
* trusting the envelope for the answer and the request body for everything
|
|
820
|
+
* about it.
|
|
821
|
+
*/
|
|
822
|
+
declare const SealedOutcome: z.ZodObject<{
|
|
823
|
+
outcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
824
|
+
outcome: z.ZodLiteral<"ok">;
|
|
825
|
+
text: z.ZodString;
|
|
826
|
+
artifactUrl: z.ZodOptional<z.ZodURL>;
|
|
827
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
828
|
+
outcome: z.ZodLiteral<"error">;
|
|
829
|
+
code: z.ZodString;
|
|
830
|
+
message: z.ZodString;
|
|
831
|
+
retryable: z.ZodBoolean;
|
|
832
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
833
|
+
outcome: z.ZodLiteral<"canceled">;
|
|
834
|
+
}, z.core.$strict>], "outcome">;
|
|
835
|
+
ran: z.ZodObject<{
|
|
836
|
+
model: z.ZodString;
|
|
837
|
+
backendClass: z.ZodEnum<{
|
|
838
|
+
http: "http";
|
|
839
|
+
process: "process";
|
|
840
|
+
}>;
|
|
841
|
+
durationMs: z.ZodNumber;
|
|
842
|
+
}, z.core.$strict>;
|
|
843
|
+
}, z.core.$strict>;
|
|
844
|
+
type SealedOutcome = z.infer<typeof SealedOutcome>;
|
|
578
845
|
/** A completed job as delivered to the app, provenance attached. */
|
|
579
846
|
declare const DeliveredResult: z.ZodObject<{
|
|
580
847
|
jobId: z.ZodString;
|
|
581
848
|
state: z.ZodEnum<{
|
|
582
849
|
ok: "ok";
|
|
583
850
|
error: "error";
|
|
851
|
+
expired: "expired";
|
|
584
852
|
queued: "queued";
|
|
585
853
|
claimed: "claimed";
|
|
586
854
|
running: "running";
|
|
587
855
|
canceled: "canceled";
|
|
588
|
-
expired: "expired";
|
|
589
856
|
}>;
|
|
590
857
|
outcome: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
591
858
|
outcome: z.ZodLiteral<"ok">;
|
|
@@ -601,9 +868,8 @@ declare const DeliveredResult: z.ZodObject<{
|
|
|
601
868
|
}, z.core.$strict>], "outcome">>;
|
|
602
869
|
provenance: z.ZodOptional<z.ZodObject<{
|
|
603
870
|
audience: z.ZodEnum<{
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
public: "public";
|
|
871
|
+
private: "private";
|
|
872
|
+
team: "team";
|
|
607
873
|
}>;
|
|
608
874
|
runnerId: z.ZodString;
|
|
609
875
|
runnerOwner: z.ZodString;
|
|
@@ -614,8 +880,11 @@ declare const DeliveredResult: z.ZodObject<{
|
|
|
614
880
|
model: z.ZodString;
|
|
615
881
|
untrusted: z.ZodBoolean;
|
|
616
882
|
}, z.core.$strict>>;
|
|
883
|
+
fallback: z.ZodOptional<z.ZodLiteral<true>>;
|
|
617
884
|
}, z.core.$strict>;
|
|
618
885
|
type DeliveredResult = z.infer<typeof DeliveredResult>;
|
|
886
|
+
/** Its members, derived — see {@link BACKEND_CLASSES} for why. */
|
|
887
|
+
declare const SIZE_CLASSES: readonly ("small" | "medium" | "large" | "unbounded")[];
|
|
619
888
|
/**
|
|
620
889
|
* How big a payload is, in buckets — byollm_009 §6.
|
|
621
890
|
*
|
|
@@ -625,6 +894,16 @@ type DeliveredResult = z.infer<typeof DeliveredResult>;
|
|
|
625
894
|
* bucket survives compression and encoding changes that an exact count does
|
|
626
895
|
* not.
|
|
627
896
|
*
|
|
897
|
+
* **Two grains, on purpose — ratified 2026-08-28.** Metering a
|
|
898
|
+
* GB-denominated plan needs real totals, and this is deliberately not where
|
|
899
|
+
* they come from: exact bytes exist only as increment-only *monthly*
|
|
900
|
+
* aggregates, and no per-job byte figure is ever persisted anywhere. The
|
|
901
|
+
* record needs vagueness and the meter needs totals; neither borrows the
|
|
902
|
+
* other's grain, which is why the consent screen's "roughly how big" stays
|
|
903
|
+
* exactly true of everything retained about a job. If you are here because
|
|
904
|
+
* you need a number, the aggregate is the one to reach for — adding a byte
|
|
905
|
+
* count to this envelope would trade a promise for a convenience.
|
|
906
|
+
*
|
|
628
907
|
* `unbounded` exists for streamed jobs, which have no size when they start.
|
|
629
908
|
* It is reserved now rather than added later: byollm_009 §8.1 — adding a
|
|
630
909
|
* field to a published envelope is the v2 break all over again.
|
|
@@ -636,6 +915,41 @@ declare const SizeClass: z.ZodEnum<{
|
|
|
636
915
|
unbounded: "unbounded";
|
|
637
916
|
}>;
|
|
638
917
|
type SizeClass = z.infer<typeof SizeClass>;
|
|
918
|
+
/**
|
|
919
|
+
* The most one envelope may be, in bytes — ratified 2026-08-28.
|
|
920
|
+
*
|
|
921
|
+
* A **relay-memory safety rail**, not a plan feature: every tier has the same
|
|
922
|
+
* ceiling, and differentiating tiers on it would be selling a safety limit as
|
|
923
|
+
* a benefit. What it bounds is any single job, so no one message can make the
|
|
924
|
+
* relay hold an unbounded amount of somebody else's memory.
|
|
925
|
+
*
|
|
926
|
+
* ## It stores nothing, and that is the design
|
|
927
|
+
*
|
|
928
|
+
* Enforced at ingress by refusing before acceptance, in both directions. A
|
|
929
|
+
* ceiling on what the relay already has in hand needs no schema and no record:
|
|
930
|
+
* the size is known for the length of the check and then it is gone. This
|
|
931
|
+
* matters because the alternative — recording a size to enforce a limit
|
|
932
|
+
* against — is precisely the per-job byte figure the metering ruling exists to
|
|
933
|
+
* not have.
|
|
934
|
+
*
|
|
935
|
+
* ## Measured on the serialised envelope
|
|
936
|
+
*
|
|
937
|
+
* The same quantity the monthly rollup counts, deliberately. The relay stores
|
|
938
|
+
* the serialised envelope and the meter measures what it stored, so a cap on
|
|
939
|
+
* anything else — the ciphertext alone, the decoded length — would mean the
|
|
940
|
+
* limit and the bill disagreed about what a byte is, and a job could be small
|
|
941
|
+
* enough to accept and larger than it was charged as.
|
|
942
|
+
*/
|
|
943
|
+
declare const MAX_ENVELOPE_BYTES: number;
|
|
944
|
+
/**
|
|
945
|
+
* How big an envelope is, by the one measure that counts it.
|
|
946
|
+
*
|
|
947
|
+
* `JSON.stringify` because that is what the store persists and therefore what
|
|
948
|
+
* the meter measures. Length in UTF-16 code units rather than encoded bytes:
|
|
949
|
+
* it is the same number the store's own `HSTRLEN` reports, and the point of
|
|
950
|
+
* this function is that one number answers both questions.
|
|
951
|
+
*/
|
|
952
|
+
declare function envelopeBytes(envelope: unknown): number;
|
|
639
953
|
/** Where the bucket boundaries sit, in characters of payload text. */
|
|
640
954
|
declare const SIZE_CLASS_LIMITS: Readonly<{
|
|
641
955
|
small: 4000;
|
|
@@ -677,12 +991,12 @@ declare const JobStub: z.ZodObject<{
|
|
|
677
991
|
"llm.chat": "llm.chat";
|
|
678
992
|
}>;
|
|
679
993
|
owner: z.ZodString;
|
|
994
|
+
site: z.ZodString;
|
|
680
995
|
audience: z.ZodEnum<{
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
public: "public";
|
|
996
|
+
private: "private";
|
|
997
|
+
team: "team";
|
|
684
998
|
}>;
|
|
685
|
-
|
|
999
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
686
1000
|
sizeClass: z.ZodEnum<{
|
|
687
1001
|
small: "small";
|
|
688
1002
|
medium: "medium";
|
|
@@ -693,7 +1007,23 @@ declare const JobStub: z.ZodObject<{
|
|
|
693
1007
|
deadlineAt: z.ZodNumber;
|
|
694
1008
|
}, z.core.$strict>;
|
|
695
1009
|
type JobStub = z.infer<typeof JobStub>;
|
|
696
|
-
/**
|
|
1010
|
+
/**
|
|
1011
|
+
* A stub, plus the lease the claiming runner now holds for it — and, on a
|
|
1012
|
+
* relayed route, the grant that says it may run at all.
|
|
1013
|
+
*
|
|
1014
|
+
* The grant lives here rather than on {@link JobStub} because of *when* it is
|
|
1015
|
+
* authored. A stub exists from enqueue; a grant is written at claim, against
|
|
1016
|
+
* the membership and mapping true at that moment. That timing is the whole of
|
|
1017
|
+
* Amendment J: a job queued yesterday for somebody removed this morning gets
|
|
1018
|
+
* no grant when it is finally claimed, and a roster held on the device could
|
|
1019
|
+
* never have known.
|
|
1020
|
+
*
|
|
1021
|
+
* Optional, and the absence is meaningful rather than lenient. A device that
|
|
1022
|
+
* pinned a control-plane key at pairing **requires** one — a claimed job
|
|
1023
|
+
* arriving without it is refused, not admitted by default. A device that
|
|
1024
|
+
* pinned none is in direct mode, where there is no control plane to author
|
|
1025
|
+
* anything and the owner's own work is the only work that runs.
|
|
1026
|
+
*/
|
|
697
1027
|
declare const ClaimedStub: z.ZodObject<{
|
|
698
1028
|
id: z.ZodString;
|
|
699
1029
|
kind: z.ZodEnum<{
|
|
@@ -701,12 +1031,12 @@ declare const ClaimedStub: z.ZodObject<{
|
|
|
701
1031
|
"llm.chat": "llm.chat";
|
|
702
1032
|
}>;
|
|
703
1033
|
owner: z.ZodString;
|
|
1034
|
+
site: z.ZodString;
|
|
704
1035
|
audience: z.ZodEnum<{
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
public: "public";
|
|
1036
|
+
private: "private";
|
|
1037
|
+
team: "team";
|
|
708
1038
|
}>;
|
|
709
|
-
|
|
1039
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
710
1040
|
sizeClass: z.ZodEnum<{
|
|
711
1041
|
small: "small";
|
|
712
1042
|
medium: "medium";
|
|
@@ -719,7 +1049,19 @@ declare const ClaimedStub: z.ZodObject<{
|
|
|
719
1049
|
id: z.ZodString;
|
|
720
1050
|
runnerId: z.ZodString;
|
|
721
1051
|
expiresAt: z.ZodNumber;
|
|
722
|
-
}, z.core.$
|
|
1052
|
+
}, z.core.$strict>;
|
|
1053
|
+
grant: z.ZodOptional<z.ZodObject<{
|
|
1054
|
+
grantId: z.ZodString;
|
|
1055
|
+
jobId: z.ZodString;
|
|
1056
|
+
site: z.ZodString;
|
|
1057
|
+
user: z.ZodString;
|
|
1058
|
+
owner: z.ZodString;
|
|
1059
|
+
purpose: z.ZodString;
|
|
1060
|
+
kind: z.ZodString;
|
|
1061
|
+
service: z.ZodString;
|
|
1062
|
+
issuedAt: z.ZodNumber;
|
|
1063
|
+
signature: z.ZodString;
|
|
1064
|
+
}, z.core.$strict>>;
|
|
723
1065
|
}, z.core.$strict>;
|
|
724
1066
|
type ClaimedStub = z.infer<typeof ClaimedStub>;
|
|
725
1067
|
|
|
@@ -763,6 +1105,18 @@ declare const StoredKeys: z.ZodObject<{
|
|
|
763
1105
|
createdAt: z.ZodNumber;
|
|
764
1106
|
}, z.core.$strict>;
|
|
765
1107
|
type StoredKeys = z.infer<typeof StoredKeys>;
|
|
1108
|
+
/** Domain separator, so a signature over an encryption key cannot be
|
|
1109
|
+
* replayed as a signature over anything else. */
|
|
1110
|
+
/**
|
|
1111
|
+
* What an encryption key's signature covers.
|
|
1112
|
+
*
|
|
1113
|
+
* Exported because a rotation is a real event this protocol has to be able to
|
|
1114
|
+
* *test* — a record whose encryption key moved under an identity that signed
|
|
1115
|
+
* the move is the one case pinning must refuse loudly, and building one
|
|
1116
|
+
* outside this file otherwise means re-typing this string, which is how two
|
|
1117
|
+
* copies of a constant start disagreeing.
|
|
1118
|
+
*/
|
|
1119
|
+
declare const ENCRYPTION_KEY_CONTEXT = "byollm/v1/encryption-key";
|
|
766
1120
|
/** Generate a fresh pair of keypairs and bind them together. */
|
|
767
1121
|
declare function generateKeys(now: number): StoredKeys;
|
|
768
1122
|
/** The public half, for the wire. */
|
|
@@ -777,7 +1131,17 @@ declare function publicIdentityOf(keys: StoredKeys): PublicIdentity;
|
|
|
777
1131
|
*/
|
|
778
1132
|
declare function verifyPublicIdentity(identity: PublicIdentity): boolean;
|
|
779
1133
|
/** Sign arbitrary bytes with an identity key. */
|
|
780
|
-
|
|
1134
|
+
/**
|
|
1135
|
+
* Sign bytes with an identity key.
|
|
1136
|
+
*
|
|
1137
|
+
* Takes only the private half it uses. A signer that demanded a whole
|
|
1138
|
+
* {@link StoredKeys} would make every caller hold an encryption keypair for a
|
|
1139
|
+
* job that has no encryption in it — and the control plane, which signs
|
|
1140
|
+
* rosters and opens nothing, would be generating and storing secret material
|
|
1141
|
+
* it can never need. Every existing caller passes a full `StoredKeys`, which
|
|
1142
|
+
* satisfies this.
|
|
1143
|
+
*/
|
|
1144
|
+
declare function signWith(keys: Pick<StoredKeys, "identityPrivate">, data: Uint8Array): string;
|
|
781
1145
|
/** Verify bytes against a raw Ed25519 public key. */
|
|
782
1146
|
declare function verifyWith(identityPublic: string, data: Uint8Array, signature: string): boolean;
|
|
783
1147
|
/**
|
|
@@ -946,7 +1310,77 @@ declare function signRequest(keys: StoredKeys, input: {
|
|
|
946
1310
|
issuedAt: number;
|
|
947
1311
|
body: string;
|
|
948
1312
|
}): RequestSignature;
|
|
949
|
-
/**
|
|
1313
|
+
/**
|
|
1314
|
+
* The same scheme, for the party at the other end: a **site** calling a relay.
|
|
1315
|
+
*
|
|
1316
|
+
* A site talking to a relay is in exactly the daemon's position — an outbound
|
|
1317
|
+
* caller with an identity keypair the other side already pins — so it gets the
|
|
1318
|
+
* daemon's authentication rather than a second scheme. Bearer tokens for the
|
|
1319
|
+
* site plane were the alternative, and they would have reintroduced the
|
|
1320
|
+
* credential-in-a-file that §4.2 removed from the daemon plane, on the plane
|
|
1321
|
+
* that carries *every* site's traffic.
|
|
1322
|
+
*
|
|
1323
|
+
* Two things make this safe to build on the same canonical string:
|
|
1324
|
+
*
|
|
1325
|
+
* 1. **The endpoint is namespaced.** Site endpoints sign `site/enqueue`, never
|
|
1326
|
+
* `enqueue`. The daemon plane's `result` and the site plane's `results` are
|
|
1327
|
+
* one character apart, and a naming collision between planes must not be
|
|
1328
|
+
* what stands between a signature and a replay onto the wrong handler. The
|
|
1329
|
+
* prefix is applied *inside* these helpers, so the two ends cannot disagree
|
|
1330
|
+
* about it — the alternative is two implementations of one bound value,
|
|
1331
|
+
* which is this project's most-repeated bug.
|
|
1332
|
+
* 2. **The caller slot carries the site id.** `canonicalRequest` names that
|
|
1333
|
+
* field `runnerId` because the daemon plane got there first; here it holds
|
|
1334
|
+
* the site id, and the verifier looks the key up in the projection's site
|
|
1335
|
+
* registry rather than its device registry. The two registries never share
|
|
1336
|
+
* an entry, so a device signature cannot authenticate as a site.
|
|
1337
|
+
*
|
|
1338
|
+
* §4.2's replay argument carries over **only because the site plane's writes
|
|
1339
|
+
* are idempotent per addressed instance**, which is a property that had to be
|
|
1340
|
+
* built rather than found: `enqueue` reset a job of the same id, so a replayed
|
|
1341
|
+
* enqueue inside the freshness window returned a claimed job to the queue and
|
|
1342
|
+
* threw away a device's live lease. Identical in shape to the `release` bug
|
|
1343
|
+
* above, on the other plane. Anything added to the site plane later must be
|
|
1344
|
+
* idempotent by the instance it names, or this scheme does not cover it.
|
|
1345
|
+
*/
|
|
1346
|
+
declare function signSiteRequest(keys: StoredKeys, input: {
|
|
1347
|
+
endpoint: string;
|
|
1348
|
+
siteId: string;
|
|
1349
|
+
issuedAt: number;
|
|
1350
|
+
body: string;
|
|
1351
|
+
}): RequestSignature;
|
|
1352
|
+
/** Verify a site's call against the identity the control plane registered. */
|
|
1353
|
+
declare function verifySiteRequest(input: {
|
|
1354
|
+
identityPublic: string;
|
|
1355
|
+
endpoint: string;
|
|
1356
|
+
body: string;
|
|
1357
|
+
signature: RequestSignature;
|
|
1358
|
+
now: number;
|
|
1359
|
+
maxSkewMs?: number;
|
|
1360
|
+
}): SignatureFailure | null;
|
|
1361
|
+
/**
|
|
1362
|
+
* Why a signed request was refused.
|
|
1363
|
+
*
|
|
1364
|
+
* **`bad-signature` is never returned verbatim; `stale` is, deliberately.**
|
|
1365
|
+
* They are different kinds of refusal and conflating them costs a real user
|
|
1366
|
+
* more than it costs an attacker.
|
|
1367
|
+
*
|
|
1368
|
+
* A bad signature is an authentication failure and the server says only
|
|
1369
|
+
* "unauthorized" — telling a prober which part they got wrong is free help.
|
|
1370
|
+
*
|
|
1371
|
+
* A stale timestamp is a **precondition** failure: the signature may be
|
|
1372
|
+
* perfectly valid and the caller's clock is simply wrong. Saying so reveals
|
|
1373
|
+
* nothing, for two reasons that both have to hold. The server's time is
|
|
1374
|
+
* already public — every response carries a `Date` header and the heartbeat
|
|
1375
|
+
* response returns `serverTime` outright. And freshness is checked *before*
|
|
1376
|
+
* the signature is verified, so a stale answer says nothing about whether the
|
|
1377
|
+
* signature was any good.
|
|
1378
|
+
*
|
|
1379
|
+
* What conflating them costs: a machine whose clock has drifted gets
|
|
1380
|
+
* `401 unauthorized` on every request, forever, with nothing anywhere pointing
|
|
1381
|
+
* at the clock. That is the shape byollm_013 was filed about — a refusal that
|
|
1382
|
+
* is correct, silent, and sends somebody to read our source.
|
|
1383
|
+
*/
|
|
950
1384
|
type SignatureFailure = "stale" | "bad-signature";
|
|
951
1385
|
/**
|
|
952
1386
|
* Verify a signed request against a runner's pinned identity key.
|
|
@@ -964,6 +1398,399 @@ declare function verifyRequest(input: {
|
|
|
964
1398
|
maxSkewMs?: number;
|
|
965
1399
|
}): SignatureFailure | null;
|
|
966
1400
|
|
|
1401
|
+
/**
|
|
1402
|
+
* What a site says it needs — byollm_016 Amendment L.
|
|
1403
|
+
*
|
|
1404
|
+
* A site declares **purposes**, and each purpose lists the job kinds it uses.
|
|
1405
|
+
* A person then maps each purpose to one of their own services, on the consent
|
|
1406
|
+
* screen, and that mapping *is* the consent. The control plane joins the two
|
|
1407
|
+
* at claim time and signs the result into a grant.
|
|
1408
|
+
*
|
|
1409
|
+
* ## Why a site declares needs instead of naming services
|
|
1410
|
+
*
|
|
1411
|
+
* Because it cannot name one. The site's vocabulary is its own purposes; the
|
|
1412
|
+
* person's vocabulary is their services; and the two never meet. A site asks
|
|
1413
|
+
* for "writing assistant, llm.chat" and learns only whether that slot is
|
|
1414
|
+
* satisfiable — never which model answered, never whose machine, never even
|
|
1415
|
+
* the name of the service. Key-vs-value reaches its strongest form here: the
|
|
1416
|
+
* site cannot describe what it wants *or* name it, only ask for what it
|
|
1417
|
+
* declared.
|
|
1418
|
+
*
|
|
1419
|
+
* ## Keys are ids; labels are prose
|
|
1420
|
+
*
|
|
1421
|
+
* They are separate fields and nothing derives one from the other, which is
|
|
1422
|
+
* the amendment's ruling and worth restating where somebody will read it. A
|
|
1423
|
+
* key travels on every job and is what mappings are stored against, so it is
|
|
1424
|
+
* stable-or-nothing: renaming one deletes a purpose and creates another,
|
|
1425
|
+
* unmapping everybody who had chosen for it. A label is changeable whenever
|
|
1426
|
+
* the site likes and is the **only** thing a consent screen renders.
|
|
1427
|
+
*/
|
|
1428
|
+
/**
|
|
1429
|
+
* The purpose a site gets when it declares no purposes of its own.
|
|
1430
|
+
*
|
|
1431
|
+
* Reserved, and refused by {@link Manifest} rather than by whatever handles
|
|
1432
|
+
* registration. A site with a single undifferentiated use has one purpose —
|
|
1433
|
+
* everything it does — and that purpose needs an id because mappings are
|
|
1434
|
+
* keyed by one. An id taken from the site's own vocabulary would collide the
|
|
1435
|
+
* day it declared a real purpose of the same name.
|
|
1436
|
+
*
|
|
1437
|
+
* **Never rendered.** "default → your Claude" tells a person nothing; a
|
|
1438
|
+
* consent screen shows the site's own name for this slot, because that is
|
|
1439
|
+
* what a single-purpose site's one purpose actually is.
|
|
1440
|
+
*/
|
|
1441
|
+
declare const RESERVED_PURPOSE = "default";
|
|
1442
|
+
declare const Purpose: z.ZodObject<{
|
|
1443
|
+
label: z.ZodString;
|
|
1444
|
+
description: z.ZodOptional<z.ZodString>;
|
|
1445
|
+
kinds: z.ZodArray<z.ZodEnum<{
|
|
1446
|
+
"llm.generate": "llm.generate";
|
|
1447
|
+
"llm.chat": "llm.chat";
|
|
1448
|
+
}>>;
|
|
1449
|
+
}, z.core.$strict>;
|
|
1450
|
+
type Purpose = z.infer<typeof Purpose>;
|
|
1451
|
+
/**
|
|
1452
|
+
* Everything a site needs, by purpose key.
|
|
1453
|
+
*
|
|
1454
|
+
* At least one purpose: a site that declares none is a site that can enqueue
|
|
1455
|
+
* nothing, and accepting it would mean the first refusal a person saw came
|
|
1456
|
+
* from a job rather than from registration.
|
|
1457
|
+
*/
|
|
1458
|
+
/**
|
|
1459
|
+
* How many purposes one site may declare.
|
|
1460
|
+
*
|
|
1461
|
+
* There was no bound at all: a site could declare fifty thousand, each one
|
|
1462
|
+
* individually valid, and the consent screen renders a slot per (purpose,
|
|
1463
|
+
* kind) — so the page that *is* the consent mechanism becomes unusable, and
|
|
1464
|
+
* the notification mail that enumerates slots grows with it.
|
|
1465
|
+
*
|
|
1466
|
+
* Thirty-two is chosen rather than derived, and the number is an argument: a
|
|
1467
|
+
* purpose is a thing a person reads and decides about one at a time, and a
|
|
1468
|
+
* screen asking more than about thirty separate questions has stopped being a
|
|
1469
|
+
* consent screen whatever it renders. Of Tomorrow Press declares five. A site
|
|
1470
|
+
* that genuinely needs more has a product question to answer before it has a
|
|
1471
|
+
* schema one.
|
|
1472
|
+
*/
|
|
1473
|
+
declare const MAX_PURPOSES = 32;
|
|
1474
|
+
declare const Manifest: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
1475
|
+
label: z.ZodString;
|
|
1476
|
+
description: z.ZodOptional<z.ZodString>;
|
|
1477
|
+
kinds: z.ZodArray<z.ZodEnum<{
|
|
1478
|
+
"llm.generate": "llm.generate";
|
|
1479
|
+
"llm.chat": "llm.chat";
|
|
1480
|
+
}>>;
|
|
1481
|
+
}, z.core.$strict>>;
|
|
1482
|
+
type Manifest = z.infer<typeof Manifest>;
|
|
1483
|
+
/**
|
|
1484
|
+
* The manifest a site with no declared purposes is treated as having.
|
|
1485
|
+
*
|
|
1486
|
+
* The sugar in Amendment L, made explicit rather than special-cased
|
|
1487
|
+
* downstream: everything after this point sees a manifest with one purpose,
|
|
1488
|
+
* so no consent screen, mapping table or resolver needs a branch for the
|
|
1489
|
+
* flat-list case.
|
|
1490
|
+
*
|
|
1491
|
+
* The label is the caller's — a site's own name — because it is the one thing
|
|
1492
|
+
* that can make "everything this site does" read as a sentence about a
|
|
1493
|
+
* particular site rather than about software in general.
|
|
1494
|
+
*/
|
|
1495
|
+
declare function singlePurposeManifest(input: {
|
|
1496
|
+
readonly label: string;
|
|
1497
|
+
readonly kinds: readonly JobKind[];
|
|
1498
|
+
}): Manifest;
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* One job, one signature, one answer — byollm_016 Amendment J.
|
|
1502
|
+
*
|
|
1503
|
+
* A grant is the control plane's signed statement that a particular job may
|
|
1504
|
+
* run on a particular device, authored at claim time and verified against the
|
|
1505
|
+
* key that device pinned when it paired.
|
|
1506
|
+
*
|
|
1507
|
+
* ## What it replaced, and why the replacement is smaller
|
|
1508
|
+
*
|
|
1509
|
+
* Until 2026-08-26 a device held a signed **roster** and answered admission
|
|
1510
|
+
* from it. Amendment G's four properties were right and the mechanism was a
|
|
1511
|
+
* cache — one that bought nothing. On the cloud route the job path and the
|
|
1512
|
+
* roster path share fate: jobs arrive through the relay, so if the relay is
|
|
1513
|
+
* unreachable there are no jobs to admit and a locally held roster adds no
|
|
1514
|
+
* availability. What it did add was staleness, which is the only reason
|
|
1515
|
+
* `ROSTER_MAX_AGE_MS` existed: a bound on how long a removed person keeps
|
|
1516
|
+
* running. Authoring at claim collapses that bound to this document's own
|
|
1517
|
+
* lifetime — add somebody and their next job runs, remove them and their next
|
|
1518
|
+
* claim fails, including jobs already queued.
|
|
1519
|
+
*
|
|
1520
|
+
* It also collapses four questions into one signature. Consented, member,
|
|
1521
|
+
* admitted, and *which service* were four mechanisms answering separately;
|
|
1522
|
+
* they are now four fields of one statement, and the device verifies once.
|
|
1523
|
+
*
|
|
1524
|
+
* ## What it is not
|
|
1525
|
+
*
|
|
1526
|
+
* Amendment G property 1 outlawed admitting on a per-job assertion, and this
|
|
1527
|
+
* is per-job. The distinction is authorship: G outlawed trusting the
|
|
1528
|
+
* **relay's or site's unsigned** claim. A grant is signed by the control
|
|
1529
|
+
* plane with a key the device pinned at pairing, so the relay can withhold it
|
|
1530
|
+
* and cannot forge it — exactly the power a relay has over a job.
|
|
1531
|
+
* `RELAY_BLIND` is untouched: the relay delivers, it never authors.
|
|
1532
|
+
*
|
|
1533
|
+
* ## What the device still checks for itself
|
|
1534
|
+
*
|
|
1535
|
+
* A grant is necessary and not sufficient. Four checks stay on the device and
|
|
1536
|
+
* none of them is delegated:
|
|
1537
|
+
*
|
|
1538
|
+
* 1. the signature, against the pinned key;
|
|
1539
|
+
* 2. replay — {@link SignedGrant.grantId} is single-use;
|
|
1540
|
+
* 3. offer-consistency — the named service is one this device actually
|
|
1541
|
+
* offers, at a scope that includes this user;
|
|
1542
|
+
* 4. **private is absolute** — a `private` service runs for the paired owner
|
|
1543
|
+
* and nobody else, so no compromise of a control plane can grant somebody
|
|
1544
|
+
* else's job onto it.
|
|
1545
|
+
*/
|
|
1546
|
+
/**
|
|
1547
|
+
* How long a grant is honoured after it was signed. Ruled 120s (2026-08-26).
|
|
1548
|
+
*
|
|
1549
|
+
* This bounds **acceptance**, not execution: a job admitted inside the window
|
|
1550
|
+
* runs to completion however long it takes. So the number only has to cover
|
|
1551
|
+
* the trip from the control plane signing to the device checking — claim,
|
|
1552
|
+
* deliver, verify — and every second past that is a second a captured grant
|
|
1553
|
+
* stays useful.
|
|
1554
|
+
*
|
|
1555
|
+
* Two minutes is generous for that trip and mean for the capture. It is also
|
|
1556
|
+
* the number ordinary clock drift is measured against, which is why
|
|
1557
|
+
* {@link CLOCK_SKEW_WARN_MS} sits well inside it: a device whose clock is off
|
|
1558
|
+
* by half the window would refuse real work, and must be told before it does.
|
|
1559
|
+
*
|
|
1560
|
+
* The verifier's policy, deliberately not a field on the document. An
|
|
1561
|
+
* `expiresAt` the signer chose would let whoever signs decide how long their
|
|
1562
|
+
* own statement stays good, and the party with the most reason to want a
|
|
1563
|
+
* longer window is the party being bounded.
|
|
1564
|
+
*/
|
|
1565
|
+
declare const GRANT_MAX_AGE_MS = 120000;
|
|
1566
|
+
/**
|
|
1567
|
+
* Clock disagreement past which a device says so, before it starts refusing.
|
|
1568
|
+
*
|
|
1569
|
+
* Skew eats {@link GRANT_MAX_AGE_MS} directly — a device 60s behind its
|
|
1570
|
+
* relay's clock has half a window left, and one 120s behind has none and
|
|
1571
|
+
* refuses everything for a reason no refusal message would otherwise name.
|
|
1572
|
+
* Thirty seconds is a quarter of the window: far enough out to be a real
|
|
1573
|
+
* problem, early enough to be a warning rather than an outage.
|
|
1574
|
+
*/
|
|
1575
|
+
declare const CLOCK_SKEW_WARN_MS = 30000;
|
|
1576
|
+
/**
|
|
1577
|
+
* Skew past which a freshness refusal names the clock instead of the grant.
|
|
1578
|
+
*
|
|
1579
|
+
* Five seconds, because below that the clock is not the story and saying so
|
|
1580
|
+
* would send somebody to check ntp about an unrelated failure. Above it, "this
|
|
1581
|
+
* grant expired" and "your clock is wrong" are the same event wearing
|
|
1582
|
+
* different words, and only one of them can be acted on.
|
|
1583
|
+
*/
|
|
1584
|
+
declare const CLOCK_ATTRIBUTION_MS = 5000;
|
|
1585
|
+
/**
|
|
1586
|
+
* The domain separator.
|
|
1587
|
+
*
|
|
1588
|
+
* Every signature in this system says what kind of statement it is before it
|
|
1589
|
+
* says anything else. Without it, bytes signed for one purpose verify for
|
|
1590
|
+
* another — a grant and a request are both "bytes this key signed", and a
|
|
1591
|
+
* scheme that could not tell them apart would let one be replayed as the
|
|
1592
|
+
* other.
|
|
1593
|
+
*/
|
|
1594
|
+
declare const GRANT_CONTEXT = "byollm/v1/grant";
|
|
1595
|
+
declare const SignedGrant: z.ZodObject<{
|
|
1596
|
+
grantId: z.ZodString;
|
|
1597
|
+
jobId: z.ZodString;
|
|
1598
|
+
site: z.ZodString;
|
|
1599
|
+
user: z.ZodString;
|
|
1600
|
+
owner: z.ZodString;
|
|
1601
|
+
purpose: z.ZodString;
|
|
1602
|
+
kind: z.ZodString;
|
|
1603
|
+
service: z.ZodString;
|
|
1604
|
+
issuedAt: z.ZodNumber;
|
|
1605
|
+
signature: z.ZodString;
|
|
1606
|
+
}, z.core.$strict>;
|
|
1607
|
+
type SignedGrant = z.infer<typeof SignedGrant>;
|
|
1608
|
+
/** Everything a grant says, before it is signed. */
|
|
1609
|
+
type GrantClaims = Omit<SignedGrant, "signature">;
|
|
1610
|
+
/**
|
|
1611
|
+
* Every field of {@link SignedGrant} except the signature, sorted.
|
|
1612
|
+
*
|
|
1613
|
+
* **Derived from the schema, never written out by hand.** The unsigned-field
|
|
1614
|
+
* attack is that somebody adds a field to the document, forgets to add it to
|
|
1615
|
+
* the bytes, and ships a value an intermediary can rewrite without breaking
|
|
1616
|
+
* any signature. A hand-maintained list is exactly the shape that fails: it
|
|
1617
|
+
* does not grow when the code does, and nothing about adding a field reminds
|
|
1618
|
+
* you it exists.
|
|
1619
|
+
*
|
|
1620
|
+
* Reading the shape closes it structurally rather than by review. A new field
|
|
1621
|
+
* is signed the moment it is declared, and grant.test.ts asserts this list
|
|
1622
|
+
* still covers the schema so a future zod version that hides `shape` fails
|
|
1623
|
+
* loudly instead of silently signing less.
|
|
1624
|
+
*/
|
|
1625
|
+
declare const GRANT_SIGNED_FIELDS: readonly (keyof GrantClaims)[];
|
|
1626
|
+
/**
|
|
1627
|
+
* The exact bytes both sides sign and verify.
|
|
1628
|
+
*
|
|
1629
|
+
* JSON-encoded rather than joined with a separator, because a separator can
|
|
1630
|
+
* be imitated. Newline-joining `["a", "b\nc"]` and `["a\nb", "c"]` produces
|
|
1631
|
+
* identical bytes, so two different grants would share a signature — and the
|
|
1632
|
+
* values here include a site id and a user id, at least one of which comes
|
|
1633
|
+
* from somebody else's namespace. JSON escapes the separator it uses, so no
|
|
1634
|
+
* arrangement of field values can spell a different document.
|
|
1635
|
+
*
|
|
1636
|
+
* The context string leads, and the field order is the schema's own sorted
|
|
1637
|
+
* keys, so the encoding is canonical without anyone maintaining a list.
|
|
1638
|
+
*/
|
|
1639
|
+
declare function grantStatement(claims: GrantClaims): Uint8Array;
|
|
1640
|
+
/** Sign a grant with the control plane's own key. */
|
|
1641
|
+
declare function signGrant(keys: Pick<StoredKeys, "identityPrivate">, claims: GrantClaims): SignedGrant;
|
|
1642
|
+
/**
|
|
1643
|
+
* Why a grant was refused.
|
|
1644
|
+
*
|
|
1645
|
+
* Split by remedy, because these send somebody to different places: fix your
|
|
1646
|
+
* clock, take it up with the relay, or nothing at all — you are being
|
|
1647
|
+
* attacked and the refusal worked.
|
|
1648
|
+
*
|
|
1649
|
+
* There is deliberately no `no-pinned-key` here. A device that pinned no
|
|
1650
|
+
* control-plane key never reaches this function: it is in direct mode, and
|
|
1651
|
+
* the question "is this grant good" does not arise. A value nothing can
|
|
1652
|
+
* return is a branch every caller has to handle and no test can reach.
|
|
1653
|
+
*/
|
|
1654
|
+
type GrantRefusal =
|
|
1655
|
+
/** The signature does not verify against the pinned key. */
|
|
1656
|
+
"bad-signature"
|
|
1657
|
+
/** Genuine, and for a different device's owner. */
|
|
1658
|
+
| "wrong-owner"
|
|
1659
|
+
/** Genuine, and lifted from a different job. */
|
|
1660
|
+
| "wrong-job"
|
|
1661
|
+
/** Older than {@link GRANT_MAX_AGE_MS}. */
|
|
1662
|
+
| "expired"
|
|
1663
|
+
/**
|
|
1664
|
+
* Issued further in the future than clock drift explains.
|
|
1665
|
+
*
|
|
1666
|
+
* Checked, and not as pedantry: an `issuedAt` ahead of now extends a
|
|
1667
|
+
* grant's life past the bound, which is the whole thing being enforced.
|
|
1668
|
+
*
|
|
1669
|
+
* Tolerant by {@link CLOCK_SKEW_WARN_MS}, because it was tolerant by
|
|
1670
|
+
* nothing and that made ordinary drift a total outage — see
|
|
1671
|
+
* {@link verifyGrant}.
|
|
1672
|
+
*/
|
|
1673
|
+
| "from-the-future";
|
|
1674
|
+
/**
|
|
1675
|
+
* Is this grant one this device may act on, right now?
|
|
1676
|
+
*
|
|
1677
|
+
* Document-level checks only. Replay, offer-consistency and the private rule
|
|
1678
|
+
* need state this function does not have and are the device's to apply — see
|
|
1679
|
+
* the class comment for the full list of four.
|
|
1680
|
+
*/
|
|
1681
|
+
declare function verifyGrant(input: {
|
|
1682
|
+
grant: SignedGrant;
|
|
1683
|
+
owner: string;
|
|
1684
|
+
jobId: string;
|
|
1685
|
+
controlPlanePublic: string;
|
|
1686
|
+
now: number;
|
|
1687
|
+
maxAgeMs?: number;
|
|
1688
|
+
}): GrantRefusal | null;
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* Rotation — byollm_009 Amendment C.
|
|
1692
|
+
*
|
|
1693
|
+
* A site holding identity key **K1** wants to be known by **K2**. It publishes
|
|
1694
|
+
* a *succession*: K2, plus a signature by K1 over a statement naming both key
|
|
1695
|
+
* ids. That signature is the entire mechanism, and the reason rotation can be
|
|
1696
|
+
* automatic without becoming a hole is that **the relay cannot mint one** — it
|
|
1697
|
+
* never holds K1. It is the same trust step a daemon already performs at
|
|
1698
|
+
* pairing, applied to the site's own succession.
|
|
1699
|
+
*
|
|
1700
|
+
* ## Why the statement names both keys
|
|
1701
|
+
*
|
|
1702
|
+
* A signature over K2 alone could be lifted from this site's record and
|
|
1703
|
+
* replayed into another site's, moving *that* site to K2 — a key the attacker
|
|
1704
|
+
* holds. Naming the predecessor binds the succession to one chain, and it is
|
|
1705
|
+
* the reason `verifyLink` takes the id it expects to be succeeding from
|
|
1706
|
+
* rather than reading it out of the statement it is checking.
|
|
1707
|
+
*/
|
|
1708
|
+
/** The domain separator. Distinct from every other thing an identity signs. */
|
|
1709
|
+
declare const SUCCESSION_CONTEXT = "byollm/v1/site-succession";
|
|
1710
|
+
/**
|
|
1711
|
+
* How long a retired key may still sign work — Amendment C, ruling 2.
|
|
1712
|
+
*
|
|
1713
|
+
* A protocol constant and not the site's to choose. Per-site overlap
|
|
1714
|
+
* arithmetic is exactly the kind of number that has to mean one thing
|
|
1715
|
+
* everywhere, and a site that could choose it could choose *forever*, which is
|
|
1716
|
+
* a two-key site permanently and a second key nobody ever notices retiring.
|
|
1717
|
+
*
|
|
1718
|
+
* Seven days: long enough that a daemon which polls daily and a laptop shut
|
|
1719
|
+
* for a long weekend both see the new record before the old key stops working,
|
|
1720
|
+
* short enough that "which key is live" is never an interesting question.
|
|
1721
|
+
*/
|
|
1722
|
+
declare const RETIREMENT_WINDOW_MS: number;
|
|
1723
|
+
/**
|
|
1724
|
+
* The longest chain a daemon will walk — Amendment C, ruling 1.
|
|
1725
|
+
*
|
|
1726
|
+
* **A denial-of-service guard, not policy.** The bound exists so a projection
|
|
1727
|
+
* cannot make a daemon verify ten thousand signatures, not to express an
|
|
1728
|
+
* opinion about how often a site may rotate. A site that legitimately exceeds
|
|
1729
|
+
* it has a re-pair ahead of it, which is why it is generous: at one rotation a
|
|
1730
|
+
* quarter this is sixteen years.
|
|
1731
|
+
*/
|
|
1732
|
+
declare const MAX_SUCCESSION_CHAIN = 64;
|
|
1733
|
+
/** One step of a chain: a key, and the signature by it over its successor. */
|
|
1734
|
+
declare const Succession: z.ZodObject<{
|
|
1735
|
+
identity: z.ZodObject<{
|
|
1736
|
+
identity: z.ZodString;
|
|
1737
|
+
encryption: z.ZodString;
|
|
1738
|
+
encryptionSig: z.ZodString;
|
|
1739
|
+
}, z.core.$strict>;
|
|
1740
|
+
signature: z.ZodString;
|
|
1741
|
+
}, z.core.$strict>;
|
|
1742
|
+
type Succession = z.infer<typeof Succession>;
|
|
1743
|
+
/** The exact bytes signed. One definition; both sides call it. */
|
|
1744
|
+
declare function successionStatement(fromKeyId: string, toKeyId: string): Uint8Array;
|
|
1745
|
+
/**
|
|
1746
|
+
* Sign a succession from the keys being retired to the identity taking over.
|
|
1747
|
+
*
|
|
1748
|
+
* Takes `StoredKeys` for the predecessor because only the holder of K1's
|
|
1749
|
+
* private half can produce this, which is the property the whole design rests
|
|
1750
|
+
* on. A site calls this once, at rotation, on the machine holding its keys.
|
|
1751
|
+
*/
|
|
1752
|
+
declare function signSuccession(previous: StoredKeys, next: PublicIdentity): Succession;
|
|
1753
|
+
/**
|
|
1754
|
+
* Check one link: did `link.identity` sign over succeeding to `toKeyId`?
|
|
1755
|
+
*
|
|
1756
|
+
* `toKeyId` is passed in rather than read from anywhere in `link`, and that is
|
|
1757
|
+
* the load-bearing detail. A verifier that recovered the successor from the
|
|
1758
|
+
* signed statement would accept a statement about *any* successor, which is
|
|
1759
|
+
* the replay this design names in C.1 — the signature is genuine, the
|
|
1760
|
+
* successor it names is not the one being installed.
|
|
1761
|
+
*/
|
|
1762
|
+
declare function verifyLink(link: Succession, toKeyId: string): boolean;
|
|
1763
|
+
/** Why a chain was refused, in the words a log line uses. */
|
|
1764
|
+
type SuccessionFailure = "no-chain" | "too-long" | "unknown-origin" | "broken-link";
|
|
1765
|
+
interface SuccessionWalk {
|
|
1766
|
+
/** The ids the chain passes through, oldest first, ending at the current. */
|
|
1767
|
+
readonly path: string[];
|
|
1768
|
+
/** The approved id the chain reached, when it reached one. */
|
|
1769
|
+
readonly from?: string;
|
|
1770
|
+
readonly failure?: SuccessionFailure;
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Walk a chain from the key being presented back to a key already approved.
|
|
1774
|
+
*
|
|
1775
|
+
* `chain` is ordered oldest last, as the projection carries it — so walking it
|
|
1776
|
+
* means starting at the current key and stepping backwards, each link proving
|
|
1777
|
+
* that its holder signed for the id in front of it.
|
|
1778
|
+
*
|
|
1779
|
+
* Returns the approved id it reached, or why it did not. **Deliberately
|
|
1780
|
+
* returns rather than throws**: a chain that does not verify is ordinary
|
|
1781
|
+
* hostile input, and the caller's job is to keep its existing pin and say so.
|
|
1782
|
+
*
|
|
1783
|
+
* `approved` is asked as a predicate rather than taken as a set because the
|
|
1784
|
+
* daemon's notion of "already approved" includes tombstoned ids — a site that
|
|
1785
|
+
* left the allowlist and came back is still a site this machine has vouched
|
|
1786
|
+
* for, and rotation must not become a way to launder that distinction away.
|
|
1787
|
+
*/
|
|
1788
|
+
declare function walkSuccession(input: {
|
|
1789
|
+
current: string;
|
|
1790
|
+
chain: readonly Succession[];
|
|
1791
|
+
approved: (keyId: string) => boolean;
|
|
1792
|
+
}): SuccessionWalk;
|
|
1793
|
+
|
|
967
1794
|
/**
|
|
968
1795
|
* The normative MUSTs of protocol v0, as data.
|
|
969
1796
|
*
|
|
@@ -997,12 +1824,45 @@ type MustEnforcer = "daemon" | "server" | "both";
|
|
|
997
1824
|
* someone else's, so the kit cannot carry it.
|
|
998
1825
|
* - `construction` — true by the shape of the code, where a test could only
|
|
999
1826
|
* sample. A reviewer verifies it; a suite cannot.
|
|
1827
|
+
* ## When a MUST binds both sides — cloud_008 Tier 3
|
|
1828
|
+
*
|
|
1829
|
+
* `AUDIENCE_BOTH_SIDES` says the server and the daemon each enforce. The kit
|
|
1830
|
+
* passed **entirely** with the server's half deleted: every check drove a real
|
|
1831
|
+
* daemon, and a daemon refuses locally, so "the job did not run" looked
|
|
1832
|
+
* identical whichever side refused it. A full-honest-stack test proves only
|
|
1833
|
+
* the conjunction.
|
|
1834
|
+
*
|
|
1835
|
+
* So a `both`-enforced MUST needs **one check per party, each with the honest
|
|
1836
|
+
* counterpart removed** — C032 claims over the raw protocol precisely so no
|
|
1837
|
+
* daemon admission logic runs. Where a check strips one side, its comment
|
|
1838
|
+
* says which; where a MUST is enforced by both and only one side is checked,
|
|
1839
|
+
* that is a gap rather than coverage.
|
|
1840
|
+
*
|
|
1000
1841
|
* - `operator` — a claim about how someone runs a deployment, verifiable only
|
|
1001
1842
|
* by audit or by reading source. The honest category, and the one that
|
|
1002
1843
|
* exists so a property nobody can check from outside is *labelled* as such
|
|
1003
1844
|
* rather than laundered by association with the checkable ones.
|
|
1004
1845
|
*/
|
|
1005
1846
|
type MustVerification = "conformance" | "adversarial" | "construction" | "operator";
|
|
1847
|
+
/**
|
|
1848
|
+
* How a MUST is verified — one kind, or several.
|
|
1849
|
+
*
|
|
1850
|
+
* Several is not hedging. `SITES_LOCALLY_APPROVED` is the case that forced it:
|
|
1851
|
+
* the fence is **construction** — a daemon cannot serve a site that is not in
|
|
1852
|
+
* its map, and admission refuses before a payload is fetched — while the
|
|
1853
|
+
* property that a *removed and re-offered* id is still refused needs a hostile
|
|
1854
|
+
* sequence of heartbeats no honest client would send, which is
|
|
1855
|
+
* **adversarial**. Recording one and dropping the other would either overstate
|
|
1856
|
+
* what a type check proves or understate what the suites do.
|
|
1857
|
+
*
|
|
1858
|
+
* The alternative was a second field for the second kind, which is two answers
|
|
1859
|
+
* to one question — the shape this project keeps deleting.
|
|
1860
|
+
*/
|
|
1861
|
+
type MustVerifiedBy = MustVerification | readonly [MustVerification, ...MustVerification[]];
|
|
1862
|
+
/** The kinds a MUST claims, always as a list. */
|
|
1863
|
+
declare function kindsOf(must: {
|
|
1864
|
+
readonly verifiedBy: MustVerifiedBy;
|
|
1865
|
+
}): readonly MustVerification[];
|
|
1006
1866
|
/** A single normative requirement of the protocol. */
|
|
1007
1867
|
interface Must {
|
|
1008
1868
|
/** Stable public id, cited by conformance output. */
|
|
@@ -1015,7 +1875,7 @@ interface Must {
|
|
|
1015
1875
|
* How this is verified. `conformance` is the only kind the kit can assert;
|
|
1016
1876
|
* see {@link MustVerification} for why the others exist.
|
|
1017
1877
|
*/
|
|
1018
|
-
readonly verifiedBy:
|
|
1878
|
+
readonly verifiedBy: MustVerifiedBy;
|
|
1019
1879
|
/** Spec section this was adjudicated in. */
|
|
1020
1880
|
readonly source: string;
|
|
1021
1881
|
}
|
|
@@ -1032,6 +1892,8 @@ declare const MUSTS: Readonly<{
|
|
|
1032
1892
|
readonly PAIR_INTERACTIVE: Must;
|
|
1033
1893
|
readonly PAIR_CODE_EXPIRES: Must;
|
|
1034
1894
|
readonly VERSION_HANDSHAKE_REQUIRED: Must;
|
|
1895
|
+
readonly SITE_KEY_BY_STUB: Must;
|
|
1896
|
+
readonly SITES_LOCALLY_APPROVED: Must;
|
|
1035
1897
|
readonly KEYS_EXCHANGED_AT_CONSENT: Must;
|
|
1036
1898
|
readonly REQUESTS_SIGNED_NOT_BEARER: Must;
|
|
1037
1899
|
readonly LEASE_SCOPED_BY_GRANT: Must;
|
|
@@ -1058,24 +1920,74 @@ declare const MUSTS: Readonly<{
|
|
|
1058
1920
|
readonly TTL_EXPIRY: Must;
|
|
1059
1921
|
readonly NO_RUNNER_SIGNAL: Must;
|
|
1060
1922
|
readonly RESULT_IDEMPOTENT: Must;
|
|
1061
|
-
readonly
|
|
1923
|
+
readonly PROVENANCE_NAMES_DEVICE: Must;
|
|
1062
1924
|
readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
|
|
1063
1925
|
readonly NO_SHELL_INTERPOLATION: Must;
|
|
1926
|
+
/**
|
|
1927
|
+
* Amended for byollm_016 Phase B, and the amendment is deliberately narrow.
|
|
1928
|
+
*
|
|
1929
|
+
* A site may now name a **service** on the stub. The temptation is to read
|
|
1930
|
+
* that as a crack in this law, so the statement below says exactly where the
|
|
1931
|
+
* line is: a name selects from a menu the owner published, and resolves to a
|
|
1932
|
+
* model, backend, base URL and flags **only** through that owner's own
|
|
1933
|
+
* config. The site supplies a key; the owner supplies every value it maps
|
|
1934
|
+
* to. A name the owner does not advertise is refused rather than
|
|
1935
|
+
* substituted, because substitution is how "you may pick from my list" turns
|
|
1936
|
+
* into "you may ask for anything and get something".
|
|
1937
|
+
*
|
|
1938
|
+
* Two properties keep it from drifting into "sites demand models":
|
|
1939
|
+
*
|
|
1940
|
+
* 1. **Nothing the site sends is ever a value.** No model string, no URL,
|
|
1941
|
+
* no flag crosses the wire — only a key that means nothing off this
|
|
1942
|
+
* owner's machine.
|
|
1943
|
+
* 2. **It is a stub field, never a payload field.** The prompt cannot
|
|
1944
|
+
* reach it. That is unchanged and is the sentence the second clause
|
|
1945
|
+
* below still enforces verbatim.
|
|
1946
|
+
*/
|
|
1064
1947
|
readonly NO_PAYLOAD_ROUTING: Must;
|
|
1065
1948
|
readonly STRIPPED_CHILD_ENV: Must;
|
|
1066
1949
|
readonly HTTP_BASE_URL_SAFE: Must;
|
|
1067
1950
|
readonly OUTPUT_INERT: Must;
|
|
1068
1951
|
readonly COMMUNITY_BUDGETS: Must;
|
|
1952
|
+
readonly REVOCATION_IMMEDIATE: Must;
|
|
1953
|
+
readonly CONSENT_BEFORE_ROUTE: Must;
|
|
1954
|
+
readonly ROSTER_NOT_DISCLOSED: Must;
|
|
1955
|
+
readonly EFFECTIVE_OFFER_ONLY: Must;
|
|
1956
|
+
readonly FALLBACK_LABELED: Must;
|
|
1957
|
+
readonly RELAY_BLIND: Must;
|
|
1958
|
+
readonly SHARED_COMPUTE_DISCLOSED: Must;
|
|
1069
1959
|
}>;
|
|
1070
1960
|
/** The id of any normative MUST. */
|
|
1071
1961
|
type MustId = keyof typeof MUSTS;
|
|
1072
1962
|
/** All MUST ids, for coverage checks. */
|
|
1073
|
-
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" | "
|
|
1963
|
+
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")[];
|
|
1074
1964
|
/** Every MUST verified a particular way. */
|
|
1075
1965
|
declare function mustsVerifiedBy(kind: MustVerification): MustId[];
|
|
1076
1966
|
|
|
1077
|
-
/**
|
|
1078
|
-
|
|
1967
|
+
/**
|
|
1968
|
+
* Protocol version carried on every request; servers refuse what they can't
|
|
1969
|
+
* speak.
|
|
1970
|
+
*
|
|
1971
|
+
* **`1` because byollm_016 changed the vocabulary** — byollm-review
|
|
1972
|
+
* 2026-08-27. `OfferScope` lost `public`, `self|named` became
|
|
1973
|
+
* `private|team`, `JobStub` lost `service` and gained `purpose`, and the
|
|
1974
|
+
* grant's site field changed namespace. The version stayed `0` through all of
|
|
1975
|
+
* it.
|
|
1976
|
+
*
|
|
1977
|
+
* The consequence was the failure the handshake exists to prevent, arriving
|
|
1978
|
+
* around it: a pre-rip daemon declares `0`, passes the version check, and
|
|
1979
|
+
* then fails whole-body schema validation with "request failed schema
|
|
1980
|
+
* validation" — no field named, no vocabulary named, no upgrade command. Once
|
|
1981
|
+
* every ten seconds, forever, while its owner watches a device go stale for
|
|
1982
|
+
* no stated reason. The check below was written because "a mismatch surfaced
|
|
1983
|
+
* as a generic bad-request" and "an error a user cannot act on is barely
|
|
1984
|
+
* better than a hang"; the number not moving is how that came back.
|
|
1985
|
+
*
|
|
1986
|
+
* A registry is a schema and an enum value is the contract — this project's
|
|
1987
|
+
* own words, from the release that silenced a fleet by adding a backend id.
|
|
1988
|
+
* The same sentence applies to removing an offer scope.
|
|
1989
|
+
*/
|
|
1990
|
+
declare const PROTOCOL_VERSION: "1";
|
|
1079
1991
|
/**
|
|
1080
1992
|
* Every protocol version this build can serve, **oldest first**.
|
|
1081
1993
|
*
|
|
@@ -1083,6 +1995,16 @@ declare const PROTOCOL_VERSION: "0";
|
|
|
1083
1995
|
* the check is the point: a server supporting two versions through a
|
|
1084
1996
|
* migration should not need a different code path from one supporting one.
|
|
1085
1997
|
*/
|
|
1998
|
+
/**
|
|
1999
|
+
* `0` is deliberately **not** here, though the list exists for exactly that.
|
|
2000
|
+
*
|
|
2001
|
+
* Supporting two versions through a migration is the shape this was built
|
|
2002
|
+
* for, and it is the wrong tool when the vocabularies are incompatible: a `0`
|
|
2003
|
+
* daemon sends `offer: "public"` and a `service` on its stubs, so accepting
|
|
2004
|
+
* its version only moves the refusal one layer down — to the schema error
|
|
2005
|
+
* that names nothing, which is the bug. Refusing the version is the whole
|
|
2006
|
+
* point, because that refusal says what to do.
|
|
2007
|
+
*/
|
|
1086
2008
|
declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
|
|
1087
2009
|
/**
|
|
1088
2010
|
* The oldest version this build will talk to — derived, not declared.
|
|
@@ -1100,6 +2022,23 @@ interface VersionRefusal {
|
|
|
1100
2022
|
readonly supported: readonly string[];
|
|
1101
2023
|
readonly minimum: string;
|
|
1102
2024
|
}
|
|
2025
|
+
/**
|
|
2026
|
+
* The version a request declares, wherever it carries it.
|
|
2027
|
+
*
|
|
2028
|
+
* A POST declares it in its body, which is where every request schema has
|
|
2029
|
+
* always put it. A GET has no body, and the relay has one — the site plane's
|
|
2030
|
+
* `pending` read — so it declares it in the query string instead.
|
|
2031
|
+
*
|
|
2032
|
+
* **Two carriers, one rule.** That asymmetry is HTTP's rather than ours, and
|
|
2033
|
+
* the alternative was worse in both directions: a header for everything would
|
|
2034
|
+
* change every existing daemon's request, and skipping GETs would leave an
|
|
2035
|
+
* endpoint outside the handshake — which is precisely the shape B.4 found,
|
|
2036
|
+
* where a whole plane was outside it.
|
|
2037
|
+
*/
|
|
2038
|
+
declare function declaredVersion(input: {
|
|
2039
|
+
body?: unknown;
|
|
2040
|
+
query?: URLSearchParams;
|
|
2041
|
+
}): unknown;
|
|
1103
2042
|
/**
|
|
1104
2043
|
* Check the protocol version on an incoming request
|
|
1105
2044
|
* ({@link MUSTS.VERSION_HANDSHAKE_REQUIRED}).
|
|
@@ -1144,6 +2083,7 @@ declare const Capability: z.ZodObject<{
|
|
|
1144
2083
|
"llm.generate": "llm.generate";
|
|
1145
2084
|
"llm.chat": "llm.chat";
|
|
1146
2085
|
}>;
|
|
2086
|
+
service: z.ZodString;
|
|
1147
2087
|
backendId: z.ZodEnum<{
|
|
1148
2088
|
ollama: "ollama";
|
|
1149
2089
|
mlx: "mlx";
|
|
@@ -1163,16 +2103,17 @@ declare const Capability: z.ZodObject<{
|
|
|
1163
2103
|
mistral: "mistral";
|
|
1164
2104
|
"openai-http": "openai-http";
|
|
1165
2105
|
"claude-cli": "claude-cli";
|
|
2106
|
+
"codex-cli": "codex-cli";
|
|
1166
2107
|
}>;
|
|
1167
2108
|
backendClass: z.ZodEnum<{
|
|
1168
2109
|
http: "http";
|
|
1169
2110
|
process: "process";
|
|
1170
2111
|
}>;
|
|
1171
2112
|
model: z.ZodString;
|
|
2113
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1172
2114
|
offerScope: z.ZodEnum<{
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
public: "public";
|
|
2115
|
+
private: "private";
|
|
2116
|
+
team: "team";
|
|
1176
2117
|
}>;
|
|
1177
2118
|
}, z.core.$strict>;
|
|
1178
2119
|
type Capability = z.infer<typeof Capability>;
|
|
@@ -1182,6 +2123,7 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
|
|
|
1182
2123
|
"llm.generate": "llm.generate";
|
|
1183
2124
|
"llm.chat": "llm.chat";
|
|
1184
2125
|
}>;
|
|
2126
|
+
service: z.ZodString;
|
|
1185
2127
|
backendId: z.ZodEnum<{
|
|
1186
2128
|
ollama: "ollama";
|
|
1187
2129
|
mlx: "mlx";
|
|
@@ -1201,19 +2143,64 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
|
|
|
1201
2143
|
mistral: "mistral";
|
|
1202
2144
|
"openai-http": "openai-http";
|
|
1203
2145
|
"claude-cli": "claude-cli";
|
|
2146
|
+
"codex-cli": "codex-cli";
|
|
1204
2147
|
}>;
|
|
1205
2148
|
backendClass: z.ZodEnum<{
|
|
1206
2149
|
http: "http";
|
|
1207
2150
|
process: "process";
|
|
1208
2151
|
}>;
|
|
1209
2152
|
model: z.ZodString;
|
|
2153
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1210
2154
|
offerScope: z.ZodEnum<{
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
public: "public";
|
|
2155
|
+
private: "private";
|
|
2156
|
+
team: "team";
|
|
1214
2157
|
}>;
|
|
1215
2158
|
}, z.core.$strict>>;
|
|
1216
2159
|
type CapabilityMatrix = z.infer<typeof CapabilityMatrix>;
|
|
2160
|
+
/**
|
|
2161
|
+
* A kind this device could serve and deliberately does not — byollm_016.
|
|
2162
|
+
*
|
|
2163
|
+
* Two services answer one kind and the owner has not said which wins, so the
|
|
2164
|
+
* kind is not advertised. That is correct and, unsaid, invisible: the owner
|
|
2165
|
+
* adds a second service, jobs stop matching, and no surface explains it.
|
|
2166
|
+
*
|
|
2167
|
+
* It travels because the surfaces that must say so are not all on the device.
|
|
2168
|
+
* The owner's card names the claimants; a teammate's card says only that the
|
|
2169
|
+
* owner has a choice to make. Claimant **offer scopes** ride along so the hub
|
|
2170
|
+
* can compute that difference without the device deciding who is asking —
|
|
2171
|
+
* carry for computation, filter for display, the same shape the effective
|
|
2172
|
+
* offer already uses.
|
|
2173
|
+
*/
|
|
2174
|
+
declare const WithheldKind: z.ZodObject<{
|
|
2175
|
+
kind: z.ZodEnum<{
|
|
2176
|
+
"llm.generate": "llm.generate";
|
|
2177
|
+
"llm.chat": "llm.chat";
|
|
2178
|
+
}>;
|
|
2179
|
+
claimants: z.ZodArray<z.ZodObject<{
|
|
2180
|
+
service: z.ZodString;
|
|
2181
|
+
offer: z.ZodEnum<{
|
|
2182
|
+
private: "private";
|
|
2183
|
+
team: "team";
|
|
2184
|
+
}>;
|
|
2185
|
+
}, z.core.$strict>>;
|
|
2186
|
+
}, z.core.$strict>;
|
|
2187
|
+
type WithheldKind = z.infer<typeof WithheldKind>;
|
|
2188
|
+
/**
|
|
2189
|
+
* One grant, named by both halves — V1-3.
|
|
2190
|
+
*
|
|
2191
|
+
* A job id is chosen per site, so the lease id is the unique thing an upstream
|
|
2192
|
+
* and a daemon can both point at. Anywhere a request says "this piece of work,
|
|
2193
|
+
* held by me", it says it with both.
|
|
2194
|
+
*
|
|
2195
|
+
* Declared once because it was written out twice — `activeLeases` and
|
|
2196
|
+
* `ReleaseRequest.leases` — and both needed the same `.strict()` added. Two
|
|
2197
|
+
* copies of a shape are two places to forget it.
|
|
2198
|
+
*/
|
|
2199
|
+
declare const GrantRef: z.ZodObject<{
|
|
2200
|
+
jobId: z.ZodString;
|
|
2201
|
+
leaseId: z.ZodString;
|
|
2202
|
+
}, z.core.$strict>;
|
|
2203
|
+
type GrantRef = z.infer<typeof GrantRef>;
|
|
1217
2204
|
/**
|
|
1218
2205
|
* Pairing is a device-code exchange, not a pasted secret
|
|
1219
2206
|
* ({@link MUSTS.PAIR_INTERACTIVE}). The daemon starts a pairing, shows the
|
|
@@ -1222,7 +2209,7 @@ type CapabilityMatrix = z.infer<typeof CapabilityMatrix>;
|
|
|
1222
2209
|
* and nothing works over a copied string alone.
|
|
1223
2210
|
*/
|
|
1224
2211
|
declare const PairStartRequest: z.ZodObject<{
|
|
1225
|
-
protocolVersion: z.ZodLiteral<"
|
|
2212
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
1226
2213
|
action: z.ZodLiteral<"start">;
|
|
1227
2214
|
daemon: z.ZodObject<{
|
|
1228
2215
|
version: z.ZodString;
|
|
@@ -1232,7 +2219,7 @@ declare const PairStartRequest: z.ZodObject<{
|
|
|
1232
2219
|
linux: "linux";
|
|
1233
2220
|
win32: "win32";
|
|
1234
2221
|
}>;
|
|
1235
|
-
}, z.core.$
|
|
2222
|
+
}, z.core.$strict>;
|
|
1236
2223
|
device: z.ZodObject<{
|
|
1237
2224
|
identity: z.ZodString;
|
|
1238
2225
|
encryption: z.ZodString;
|
|
@@ -1243,6 +2230,7 @@ declare const PairStartRequest: z.ZodObject<{
|
|
|
1243
2230
|
"llm.generate": "llm.generate";
|
|
1244
2231
|
"llm.chat": "llm.chat";
|
|
1245
2232
|
}>;
|
|
2233
|
+
service: z.ZodString;
|
|
1246
2234
|
backendId: z.ZodEnum<{
|
|
1247
2235
|
ollama: "ollama";
|
|
1248
2236
|
mlx: "mlx";
|
|
@@ -1262,16 +2250,17 @@ declare const PairStartRequest: z.ZodObject<{
|
|
|
1262
2250
|
mistral: "mistral";
|
|
1263
2251
|
"openai-http": "openai-http";
|
|
1264
2252
|
"claude-cli": "claude-cli";
|
|
2253
|
+
"codex-cli": "codex-cli";
|
|
1265
2254
|
}>;
|
|
1266
2255
|
backendClass: z.ZodEnum<{
|
|
1267
2256
|
http: "http";
|
|
1268
2257
|
process: "process";
|
|
1269
2258
|
}>;
|
|
1270
2259
|
model: z.ZodString;
|
|
2260
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1271
2261
|
offerScope: z.ZodEnum<{
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
public: "public";
|
|
2262
|
+
private: "private";
|
|
2263
|
+
team: "team";
|
|
1275
2264
|
}>;
|
|
1276
2265
|
}, z.core.$strict>>;
|
|
1277
2266
|
}, z.core.$strict>;
|
|
@@ -1285,7 +2274,7 @@ declare const PairStartResponse: z.ZodObject<{
|
|
|
1285
2274
|
}, z.core.$strict>;
|
|
1286
2275
|
type PairStartResponse = z.infer<typeof PairStartResponse>;
|
|
1287
2276
|
declare const PairPollRequest: z.ZodObject<{
|
|
1288
|
-
protocolVersion: z.ZodLiteral<"
|
|
2277
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
1289
2278
|
action: z.ZodLiteral<"poll">;
|
|
1290
2279
|
deviceCode: z.ZodString;
|
|
1291
2280
|
}, z.core.$strict>;
|
|
@@ -1298,19 +2287,19 @@ declare const PairPollResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1298
2287
|
status: z.ZodLiteral<"expired">;
|
|
1299
2288
|
}, z.core.$strict>, z.ZodObject<{
|
|
1300
2289
|
status: z.ZodLiteral<"approved">;
|
|
1301
|
-
runnerToken: z.ZodString;
|
|
1302
2290
|
runnerId: z.ZodString;
|
|
1303
2291
|
owner: z.ZodString;
|
|
1304
2292
|
ownerLabel: z.ZodOptional<z.ZodString>;
|
|
1305
|
-
|
|
2293
|
+
sites: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
1306
2294
|
identity: z.ZodString;
|
|
1307
2295
|
encryption: z.ZodString;
|
|
1308
2296
|
encryptionSig: z.ZodString;
|
|
1309
|
-
}, z.core.$strict
|
|
2297
|
+
}, z.core.$strict>>;
|
|
2298
|
+
controlPlanePublic: z.ZodOptional<z.ZodString>;
|
|
1310
2299
|
}, z.core.$strict>], "status">;
|
|
1311
2300
|
type PairPollResponse = z.infer<typeof PairPollResponse>;
|
|
1312
2301
|
declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
1313
|
-
protocolVersion: z.ZodLiteral<"
|
|
2302
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
1314
2303
|
action: z.ZodLiteral<"start">;
|
|
1315
2304
|
daemon: z.ZodObject<{
|
|
1316
2305
|
version: z.ZodString;
|
|
@@ -1320,7 +2309,7 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1320
2309
|
linux: "linux";
|
|
1321
2310
|
win32: "win32";
|
|
1322
2311
|
}>;
|
|
1323
|
-
}, z.core.$
|
|
2312
|
+
}, z.core.$strict>;
|
|
1324
2313
|
device: z.ZodObject<{
|
|
1325
2314
|
identity: z.ZodString;
|
|
1326
2315
|
encryption: z.ZodString;
|
|
@@ -1331,6 +2320,7 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1331
2320
|
"llm.generate": "llm.generate";
|
|
1332
2321
|
"llm.chat": "llm.chat";
|
|
1333
2322
|
}>;
|
|
2323
|
+
service: z.ZodString;
|
|
1334
2324
|
backendId: z.ZodEnum<{
|
|
1335
2325
|
ollama: "ollama";
|
|
1336
2326
|
mlx: "mlx";
|
|
@@ -1350,32 +2340,34 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1350
2340
|
mistral: "mistral";
|
|
1351
2341
|
"openai-http": "openai-http";
|
|
1352
2342
|
"claude-cli": "claude-cli";
|
|
2343
|
+
"codex-cli": "codex-cli";
|
|
1353
2344
|
}>;
|
|
1354
2345
|
backendClass: z.ZodEnum<{
|
|
1355
2346
|
http: "http";
|
|
1356
2347
|
process: "process";
|
|
1357
2348
|
}>;
|
|
1358
2349
|
model: z.ZodString;
|
|
2350
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1359
2351
|
offerScope: z.ZodEnum<{
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
public: "public";
|
|
2352
|
+
private: "private";
|
|
2353
|
+
team: "team";
|
|
1363
2354
|
}>;
|
|
1364
2355
|
}, z.core.$strict>>;
|
|
1365
2356
|
}, z.core.$strict>, z.ZodObject<{
|
|
1366
|
-
protocolVersion: z.ZodLiteral<"
|
|
2357
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
1367
2358
|
action: z.ZodLiteral<"poll">;
|
|
1368
2359
|
deviceCode: z.ZodString;
|
|
1369
2360
|
}, z.core.$strict>], "action">;
|
|
1370
2361
|
type PairRequest = z.infer<typeof PairRequest>;
|
|
1371
2362
|
declare const ClaimRequest: z.ZodObject<{
|
|
1372
|
-
protocolVersion: z.ZodLiteral<"
|
|
2363
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
1373
2364
|
runnerId: z.ZodString;
|
|
1374
2365
|
capabilities: z.ZodArray<z.ZodObject<{
|
|
1375
2366
|
kind: z.ZodEnum<{
|
|
1376
2367
|
"llm.generate": "llm.generate";
|
|
1377
2368
|
"llm.chat": "llm.chat";
|
|
1378
2369
|
}>;
|
|
2370
|
+
service: z.ZodString;
|
|
1379
2371
|
backendId: z.ZodEnum<{
|
|
1380
2372
|
ollama: "ollama";
|
|
1381
2373
|
mlx: "mlx";
|
|
@@ -1395,16 +2387,17 @@ declare const ClaimRequest: z.ZodObject<{
|
|
|
1395
2387
|
mistral: "mistral";
|
|
1396
2388
|
"openai-http": "openai-http";
|
|
1397
2389
|
"claude-cli": "claude-cli";
|
|
2390
|
+
"codex-cli": "codex-cli";
|
|
1398
2391
|
}>;
|
|
1399
2392
|
backendClass: z.ZodEnum<{
|
|
1400
2393
|
http: "http";
|
|
1401
2394
|
process: "process";
|
|
1402
2395
|
}>;
|
|
1403
2396
|
model: z.ZodString;
|
|
2397
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1404
2398
|
offerScope: z.ZodEnum<{
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
public: "public";
|
|
2399
|
+
private: "private";
|
|
2400
|
+
team: "team";
|
|
1408
2401
|
}>;
|
|
1409
2402
|
}, z.core.$strict>>;
|
|
1410
2403
|
max: z.ZodNumber;
|
|
@@ -1418,12 +2411,12 @@ declare const ClaimResponse: z.ZodObject<{
|
|
|
1418
2411
|
"llm.chat": "llm.chat";
|
|
1419
2412
|
}>;
|
|
1420
2413
|
owner: z.ZodString;
|
|
2414
|
+
site: z.ZodString;
|
|
1421
2415
|
audience: z.ZodEnum<{
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
public: "public";
|
|
2416
|
+
private: "private";
|
|
2417
|
+
team: "team";
|
|
1425
2418
|
}>;
|
|
1426
|
-
|
|
2419
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
1427
2420
|
sizeClass: z.ZodEnum<{
|
|
1428
2421
|
small: "small";
|
|
1429
2422
|
medium: "medium";
|
|
@@ -1436,13 +2429,25 @@ declare const ClaimResponse: z.ZodObject<{
|
|
|
1436
2429
|
id: z.ZodString;
|
|
1437
2430
|
runnerId: z.ZodString;
|
|
1438
2431
|
expiresAt: z.ZodNumber;
|
|
1439
|
-
}, z.core.$
|
|
2432
|
+
}, z.core.$strict>;
|
|
2433
|
+
grant: z.ZodOptional<z.ZodObject<{
|
|
2434
|
+
grantId: z.ZodString;
|
|
2435
|
+
jobId: z.ZodString;
|
|
2436
|
+
site: z.ZodString;
|
|
2437
|
+
user: z.ZodString;
|
|
2438
|
+
owner: z.ZodString;
|
|
2439
|
+
purpose: z.ZodString;
|
|
2440
|
+
kind: z.ZodString;
|
|
2441
|
+
service: z.ZodString;
|
|
2442
|
+
issuedAt: z.ZodNumber;
|
|
2443
|
+
signature: z.ZodString;
|
|
2444
|
+
}, z.core.$strict>>;
|
|
1440
2445
|
}, z.core.$strict>>;
|
|
1441
2446
|
leaseMs: z.ZodNumber;
|
|
1442
2447
|
}, z.core.$strict>;
|
|
1443
2448
|
type ClaimResponse = z.infer<typeof ClaimResponse>;
|
|
1444
2449
|
declare const HeartbeatRequest: z.ZodObject<{
|
|
1445
|
-
protocolVersion: z.ZodLiteral<"
|
|
2450
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
1446
2451
|
runnerId: z.ZodString;
|
|
1447
2452
|
daemonVersion: z.ZodString;
|
|
1448
2453
|
capabilities: z.ZodArray<z.ZodObject<{
|
|
@@ -1450,6 +2455,7 @@ declare const HeartbeatRequest: z.ZodObject<{
|
|
|
1450
2455
|
"llm.generate": "llm.generate";
|
|
1451
2456
|
"llm.chat": "llm.chat";
|
|
1452
2457
|
}>;
|
|
2458
|
+
service: z.ZodString;
|
|
1453
2459
|
backendId: z.ZodEnum<{
|
|
1454
2460
|
ollama: "ollama";
|
|
1455
2461
|
mlx: "mlx";
|
|
@@ -1469,34 +2475,66 @@ declare const HeartbeatRequest: z.ZodObject<{
|
|
|
1469
2475
|
mistral: "mistral";
|
|
1470
2476
|
"openai-http": "openai-http";
|
|
1471
2477
|
"claude-cli": "claude-cli";
|
|
2478
|
+
"codex-cli": "codex-cli";
|
|
1472
2479
|
}>;
|
|
1473
2480
|
backendClass: z.ZodEnum<{
|
|
1474
2481
|
http: "http";
|
|
1475
2482
|
process: "process";
|
|
1476
2483
|
}>;
|
|
1477
2484
|
model: z.ZodString;
|
|
2485
|
+
knownModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
1478
2486
|
offerScope: z.ZodEnum<{
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
public: "public";
|
|
2487
|
+
private: "private";
|
|
2488
|
+
team: "team";
|
|
1482
2489
|
}>;
|
|
1483
2490
|
}, z.core.$strict>>;
|
|
2491
|
+
withheld: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
2492
|
+
kind: z.ZodEnum<{
|
|
2493
|
+
"llm.generate": "llm.generate";
|
|
2494
|
+
"llm.chat": "llm.chat";
|
|
2495
|
+
}>;
|
|
2496
|
+
claimants: z.ZodArray<z.ZodObject<{
|
|
2497
|
+
service: z.ZodString;
|
|
2498
|
+
offer: z.ZodEnum<{
|
|
2499
|
+
private: "private";
|
|
2500
|
+
team: "team";
|
|
2501
|
+
}>;
|
|
2502
|
+
}, z.core.$strict>>;
|
|
2503
|
+
}, z.core.$strict>>>;
|
|
1484
2504
|
activeLeases: z.ZodArray<z.ZodObject<{
|
|
1485
2505
|
jobId: z.ZodString;
|
|
1486
2506
|
leaseId: z.ZodString;
|
|
1487
|
-
}, z.core.$
|
|
2507
|
+
}, z.core.$strict>>;
|
|
1488
2508
|
paused: z.ZodBoolean;
|
|
1489
2509
|
}, z.core.$strict>;
|
|
1490
2510
|
type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;
|
|
1491
2511
|
declare const HeartbeatResponse: z.ZodObject<{
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
2512
|
+
sites: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
2513
|
+
identity: z.ZodString;
|
|
2514
|
+
encryption: z.ZodString;
|
|
2515
|
+
encryptionSig: z.ZodString;
|
|
2516
|
+
}, z.core.$strict>>;
|
|
2517
|
+
successions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
2518
|
+
succeeds: z.ZodArray<z.ZodObject<{
|
|
2519
|
+
identity: z.ZodObject<{
|
|
2520
|
+
identity: z.ZodString;
|
|
2521
|
+
encryption: z.ZodString;
|
|
2522
|
+
encryptionSig: z.ZodString;
|
|
2523
|
+
}, z.core.$strict>;
|
|
2524
|
+
signature: z.ZodString;
|
|
2525
|
+
}, z.core.$strict>>;
|
|
2526
|
+
retiringUntil: z.ZodOptional<z.ZodNumber>;
|
|
2527
|
+
}, z.core.$strict>>>;
|
|
2528
|
+
cancel: z.ZodArray<z.ZodObject<{
|
|
1495
2529
|
jobId: z.ZodString;
|
|
1496
|
-
|
|
2530
|
+
leaseId: z.ZodString;
|
|
2531
|
+
}, z.core.$strict>>;
|
|
2532
|
+
lost: z.ZodArray<z.ZodObject<{
|
|
2533
|
+
jobId: z.ZodString;
|
|
2534
|
+
leaseId: z.ZodString;
|
|
1497
2535
|
}, z.core.$strict>>;
|
|
1498
|
-
lost: z.ZodArray<z.ZodString>;
|
|
1499
2536
|
serverTime: z.ZodNumber;
|
|
2537
|
+
awaitingConsent: z.ZodArray<z.ZodString>;
|
|
1500
2538
|
}, z.core.$strict>;
|
|
1501
2539
|
type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
|
|
1502
2540
|
/**
|
|
@@ -1518,9 +2556,10 @@ declare const ResultDisposition: z.ZodEnum<{
|
|
|
1518
2556
|
}>;
|
|
1519
2557
|
type ResultDisposition = z.infer<typeof ResultDisposition>;
|
|
1520
2558
|
declare const ResultRequest: z.ZodObject<{
|
|
1521
|
-
protocolVersion: z.ZodLiteral<"
|
|
2559
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
1522
2560
|
runnerId: z.ZodString;
|
|
1523
2561
|
jobId: z.ZodString;
|
|
2562
|
+
leaseId: z.ZodString;
|
|
1524
2563
|
envelope: z.ZodObject<{
|
|
1525
2564
|
ciphertext: z.ZodString;
|
|
1526
2565
|
recipientKeyId: z.ZodString;
|
|
@@ -1536,32 +2575,27 @@ declare const ResultRequest: z.ZodObject<{
|
|
|
1536
2575
|
error: "error";
|
|
1537
2576
|
canceled: "canceled";
|
|
1538
2577
|
}>;
|
|
1539
|
-
model: z.ZodString;
|
|
1540
|
-
backendClass: z.ZodEnum<{
|
|
1541
|
-
http: "http";
|
|
1542
|
-
process: "process";
|
|
1543
|
-
}>;
|
|
1544
|
-
durationMs: z.ZodNumber;
|
|
1545
2578
|
}, z.core.$strict>;
|
|
1546
2579
|
type ResultRequest = z.infer<typeof ResultRequest>;
|
|
1547
2580
|
declare const ResultResponse: z.ZodObject<{
|
|
1548
2581
|
accepted: z.ZodBoolean;
|
|
2582
|
+
duplicate: z.ZodOptional<z.ZodBoolean>;
|
|
1549
2583
|
state: z.ZodString;
|
|
1550
2584
|
}, z.core.$strict>;
|
|
1551
2585
|
type ResultResponse = z.infer<typeof ResultResponse>;
|
|
1552
2586
|
declare const ReleaseRequest: z.ZodObject<{
|
|
1553
|
-
protocolVersion: z.ZodLiteral<"
|
|
2587
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
1554
2588
|
runnerId: z.ZodString;
|
|
1555
2589
|
leases: z.ZodArray<z.ZodObject<{
|
|
1556
2590
|
jobId: z.ZodString;
|
|
1557
2591
|
leaseId: z.ZodString;
|
|
1558
|
-
}, z.core.$
|
|
2592
|
+
}, z.core.$strict>>;
|
|
1559
2593
|
reason: z.ZodEnum<{
|
|
1560
|
-
|
|
2594
|
+
refused: "refused";
|
|
1561
2595
|
shutdown: "shutdown";
|
|
1562
2596
|
pause: "pause";
|
|
2597
|
+
revoked: "revoked";
|
|
1563
2598
|
"backend-down": "backend-down";
|
|
1564
|
-
refused: "refused";
|
|
1565
2599
|
}>;
|
|
1566
2600
|
}, z.core.$strict>;
|
|
1567
2601
|
type ReleaseRequest = z.infer<typeof ReleaseRequest>;
|
|
@@ -1582,7 +2616,11 @@ declare const WireErrorCode: z.ZodEnum<{
|
|
|
1582
2616
|
revoked: "revoked";
|
|
1583
2617
|
"bad-request": "bad-request";
|
|
1584
2618
|
unauthorized: "unauthorized";
|
|
2619
|
+
forbidden: "forbidden";
|
|
1585
2620
|
"not-found": "not-found";
|
|
2621
|
+
"not-ready": "not-ready";
|
|
2622
|
+
"too-late": "too-late";
|
|
2623
|
+
"clock-skew": "clock-skew";
|
|
1586
2624
|
"rate-limited": "rate-limited";
|
|
1587
2625
|
"server-error": "server-error";
|
|
1588
2626
|
}>;
|
|
@@ -1593,18 +2631,26 @@ declare const WireError: z.ZodObject<{
|
|
|
1593
2631
|
revoked: "revoked";
|
|
1594
2632
|
"bad-request": "bad-request";
|
|
1595
2633
|
unauthorized: "unauthorized";
|
|
2634
|
+
forbidden: "forbidden";
|
|
1596
2635
|
"not-found": "not-found";
|
|
2636
|
+
"not-ready": "not-ready";
|
|
2637
|
+
"too-late": "too-late";
|
|
2638
|
+
"clock-skew": "clock-skew";
|
|
1597
2639
|
"rate-limited": "rate-limited";
|
|
1598
2640
|
"server-error": "server-error";
|
|
1599
2641
|
}>;
|
|
1600
2642
|
message: z.ZodString;
|
|
2643
|
+
supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2644
|
+
minimum: z.ZodOptional<z.ZodString>;
|
|
1601
2645
|
retryAfter: z.ZodOptional<z.ZodNumber>;
|
|
2646
|
+
serverTime: z.ZodOptional<z.ZodNumber>;
|
|
2647
|
+
maxSkewMs: z.ZodOptional<z.ZodNumber>;
|
|
1602
2648
|
}, z.core.$strict>;
|
|
1603
2649
|
type WireError = z.infer<typeof WireError>;
|
|
1604
2650
|
/** HTTP status each error code is served with. */
|
|
1605
2651
|
declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
|
|
1606
2652
|
declare const FetchRequest: z.ZodObject<{
|
|
1607
|
-
protocolVersion: z.
|
|
2653
|
+
protocolVersion: z.ZodLiteral<"1">;
|
|
1608
2654
|
runnerId: z.ZodString;
|
|
1609
2655
|
jobId: z.ZodString;
|
|
1610
2656
|
leaseId: z.ZodString;
|
|
@@ -1624,4 +2670,4 @@ declare const FetchResponse: z.ZodObject<{
|
|
|
1624
2670
|
}, z.core.$strict>;
|
|
1625
2671
|
type FetchResponse = z.infer<typeof FetchResponse>;
|
|
1626
2672
|
|
|
1627
|
-
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, signWith, sizeClassCeiling, sizeClassOf, verifyPublicIdentity, verifyRequest, verifyWith };
|
|
2673
|
+
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, 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, 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, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, backendName, canTransition, canonicalRequest, checkProtocolVersion, classifyCost, cryptoReady, declaredVersion, effectiveOfferScope, envelopeBytes, fingerprint, generateKeys, grantStatement, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signGrant, signRequest, signSiteRequest, signSuccession, signWith, singlePurposeManifest, sizeClassCeiling, sizeClassOf, successionStatement, verifyGrant, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession };
|