@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/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, offer it
26
- * `public`, and donate their credit balance to strangers. The community
27
- * budgets cap job *count*, not spend.
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 `public` because nobody thought about the bill. See
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
- * The cost class of a configured backend instance.
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
- declare function resolveCost(id: BackendId, baseUrl: string | undefined): BackendCost;
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
- * - `self` — only the job owner's own daemon.
174
- * - `named` — a daemon whose owner has explicitly allowed this (server, user)
175
- * pair in their *local* allowlist (byollm_001 Rev 1 §B).
176
- * - `public` any daemon offering `public` compute.
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
- self: "self";
180
- named: "named";
181
- public: "public";
316
+ private: "private";
317
+ team: "team";
182
318
  }>;
183
319
  type Audience = z.infer<typeof Audience>;
184
320
  /**
185
- * What a daemon backend is willing to run, declared by the machine's owner.
186
- * Same three values as {@link Audience}, but the two are independent axes —
187
- * a job runs only where both agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}).
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
- self: "self";
191
- named: "named";
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 ("self" | "named" | "public")[];
340
+ declare const AUDIENCES: readonly ("private" | "team")[];
197
341
  /** All offer scopes, in widening order. */
198
- declare const OFFER_SCOPES: readonly ("self" | "named" | "public")[];
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 `self` regardless of config
381
+ * - `subscription` is locked to `private` regardless of config
238
382
  * ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — someone else's terms.
239
- * - `metered` narrows to `self` unless the owner has explicitly acknowledged
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
- * `named` job. Defence in depth only the daemon's local allowlist is the
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
- * Does this daemon's *local* allowlist admit the given owner for the server
272
- * origin the job came from? Supplied as a predicate so the protocol package
273
- * stays free of file I/O; the daemon passes its allowlist, the server
274
- * passes a conservative `() => true` because it cannot know a remote
275
- * daemon's local list and must not pretend to.
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 locallyAllows: (owner: string) => boolean;
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 nine-way matrix (three audiences × three offer scopes) is asserted
287
- * by the conformance kit. The function is pure and total so both the daemon
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: "named" },
450
+ * { owner: "alice", audience: "team" },
295
451
  * {
296
452
  * owner: "bob",
297
- * offerScope: "named",
453
+ * offerScope: "team",
298
454
  * cost: "free",
299
- * locallyAllows: (o) => o === "alice",
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
- * byollm_004 §4 requires stricter limits for community (`named`/`public`)
318
- * jobs; those are applied on top of these by the daemon's budget check, which
319
- * knows the job's audience. These are the absolute ceilings for any job.
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.$strip>;
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.$strip>>;
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.$strip>, z.ZodObject<{
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.$strip>>;
567
+ }, z.core.$strict>>;
405
568
  system: z.ZodOptional<z.ZodString>;
406
569
  }, z.core.$strict>;
407
- }, z.core.$strip>], "kind">;
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.$strip>;
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.$strip>>;
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.$strip>>;
660
+ }, z.core.$strict>>;
498
661
  system: z.ZodOptional<z.ZodString>;
499
662
  }, z.core.$strict>]>;
500
663
  audience: z.ZodEnum<{
501
- self: "self";
502
- named: "named";
503
- public: "public";
664
+ private: "private";
665
+ team: "team";
504
666
  }>;
505
667
  owner: z.ZodString;
506
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
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.$strip>;
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 `named`/`public` result is attacker-controlled text.
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.RESULT_PROVENANCE}).
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
- self: "self";
524
- named: "named";
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
- self: "self";
605
- named: "named";
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
- self: "self";
682
- named: "named";
683
- public: "public";
996
+ private: "private";
997
+ team: "team";
684
998
  }>;
685
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
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
- /** A stub, plus the lease the claiming runner now holds for it. */
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
- self: "self";
706
- named: "named";
707
- public: "public";
1036
+ private: "private";
1037
+ team: "team";
708
1038
  }>;
709
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
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.$strip>;
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
- declare function signWith(keys: StoredKeys, data: Uint8Array): string;
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
- /** Why a signed request was refused. Never returned to the caller verbatim. */
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: MustVerification;
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 RESULT_PROVENANCE: Must;
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" | "RESULT_PROVENANCE" | "INGRESS_LOGGED_BEFORE_EXECUTION" | "NO_SHELL_INTERPOLATION" | "NO_PAYLOAD_ROUTING" | "STRIPPED_CHILD_ENV" | "HTTP_BASE_URL_SAFE" | "OUTPUT_INERT" | "COMMUNITY_BUDGETS")[];
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
- /** Protocol version carried on every request; servers refuse what they can't speak. */
1078
- declare const PROTOCOL_VERSION: "0";
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
- self: "self";
1174
- named: "named";
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
- self: "self";
1212
- named: "named";
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<"0">;
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.$strip>;
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
- self: "self";
1273
- named: "named";
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<"0">;
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
- site: z.ZodObject<{
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<"0">;
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.$strip>;
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
- self: "self";
1361
- named: "named";
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<"0">;
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<"0">;
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
- self: "self";
1406
- named: "named";
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
- self: "self";
1423
- named: "named";
1424
- public: "public";
2416
+ private: "private";
2417
+ team: "team";
1425
2418
  }>;
1426
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
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.$strip>;
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<"0">;
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
- self: "self";
1480
- named: "named";
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.$strip>>;
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
- revoked: z.ZodBoolean;
1493
- cancel: z.ZodArray<z.ZodString>;
1494
- leases: z.ZodArray<z.ZodObject<{
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
- expiresAt: z.ZodNumber;
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<"0">;
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<"0">;
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.$strip>>;
2592
+ }, z.core.$strict>>;
1559
2593
  reason: z.ZodEnum<{
1560
- revoked: "revoked";
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.ZodString;
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 };