@byollm/protocol 0.1.0-alpha.6 → 0.1.0-alpha.61

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,14 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ /**
4
+ * Its members, for anything that has to report what it accepts.
5
+ *
6
+ * Derived from the enum for the reason `JOB_KINDS` and `OFFER_SCOPES` are: a
7
+ * second list of the same words is a second thing to keep in step, and this
8
+ * one is read by the promotion gate to compare a deployed hub against a
9
+ * version about to be promoted.
10
+ */
11
+ declare const BACKEND_CLASSES: readonly ("http" | "process")[];
3
12
  /**
4
13
  * How a backend reaches its model — the taxonomy introduced in byollm_001
5
14
  * Rev 1 §A, because the two classes have different threat surfaces.
@@ -22,9 +31,9 @@ type BackendClass = z.infer<typeof BackendClass>;
22
31
  *
23
32
  * This replaced a two-valued `account` field that conflated two unrelated
24
33
  * 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.
34
+ * accepts an API key, so an owner could point it at a paid endpoint, share it,
35
+ * and donate their credit balance to strangers. The community budgets cap job
36
+ * *count*, not spend.
28
37
  *
29
38
  * - `free` — local compute. Costs electricity, not money. Shareable.
30
39
  * - `metered` — per-token billing against the owner's account. Legal to
@@ -104,11 +113,23 @@ declare const BACKENDS: Readonly<{
104
113
  readonly mistral: BackendDescriptor;
105
114
  readonly "openai-http": BackendDescriptor;
106
115
  readonly "claude-cli": BackendDescriptor;
116
+ /**
117
+ * OpenAI's Codex CLI, on a ChatGPT plan — byollm_016 stage 3.
118
+ *
119
+ * `subscription`, so `SUBSCRIPTION_SELF_LOCK` pins it to its owner's own
120
+ * work whatever the config says. That is load-bearing here in a way it is
121
+ * not for `claude-cli`: Codex is an *agent*, and its default feature set
122
+ * includes a shell tool, browser control and computer use. The daemon
123
+ * disables every one of them, verified against the shipped binary rather
124
+ * than assumed — see `codex-cli.ts` — but the self-lock is the floor under
125
+ * that verification rather than a duplicate of it.
126
+ */
127
+ readonly "codex-cli": BackendDescriptor;
107
128
  }>;
108
129
  /** The id of a registered backend. */
109
130
  type BackendId = keyof typeof BACKENDS;
110
131
  /** 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")[];
132
+ 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
133
  declare const BackendIdSchema: z.ZodEnum<{
113
134
  ollama: "ollama";
114
135
  mlx: "mlx";
@@ -128,6 +149,7 @@ declare const BackendIdSchema: z.ZodEnum<{
128
149
  mistral: "mistral";
129
150
  "openai-http": "openai-http";
130
151
  "claude-cli": "claude-cli";
152
+ "codex-cli": "codex-cli";
131
153
  }>;
132
154
  /** Narrow an arbitrary string to a registered backend id. */
133
155
  declare function isBackendId(value: string): value is BackendId;
@@ -152,50 +174,158 @@ declare function backendDescriptor(id: BackendId): BackendDescriptor;
152
174
  * an act by the machine's owner against their own account, and the threat
153
175
  * model here is a hostile *job*, not an owner routing around a rule that
154
176
  * exists to protect them. What this catches is the accident — a remote paid
155
- * endpoint offered `public` because nobody thought about the bill. See
177
+ * endpoint offered to a team because nobody thought about the bill. See
156
178
  * `docs/security.md` §4a.
157
179
  */
158
180
  declare function isLocalHost(hostname: string): boolean;
159
181
  /**
160
- * The cost class of a configured backend instance.
182
+ * Is this model name a hosted one billed by its vendor?
183
+ *
184
+ * Ollama serves cloud models through the same local endpoint as local ones,
185
+ * so the address says "free" about a model somebody is being charged for. The
186
+ * only thing that distinguishes them is the name, and the distinguishing part
187
+ * is the **tag** — everything after the last colon.
188
+ *
189
+ * End-anchored on the tag, which is what makes it decidable rather than a
190
+ * guess about substrings:
191
+ *
192
+ * - `glm-5.2:cloud` → cloud
193
+ * - `deepseek-v4-flash:0731-cloud` → cloud
194
+ * - `x:cloudless` → not cloud, the tag ends in "less"
195
+ * - `cloudmodel:7b` → not cloud, the tag is "7b"
196
+ * - `llama3.2` → not cloud, there is no tag at all
197
+ *
198
+ * An oddball like `:xcloud` classifies as cloud, and that is the **only
199
+ * permitted failure direction**: calling a free model metered narrows what an
200
+ * owner may share and costs nobody money, while the reverse hands somebody
201
+ * else's bill to a stranger.
202
+ */
203
+ declare function isCloudTaggedModel(model: string): boolean;
204
+ /**
205
+ * The cost class of a configured service.
161
206
  *
162
207
  * For every named provider this is whatever the registry says, full stop
163
208
  * ({@link MUSTS.COST_NOT_CONFIGURABLE}). For the generic `openai-http` entry
164
209
  * it is inferred from the base URL, and a base URL that cannot be parsed is
165
210
  * treated as `metered` — the expensive side, because guessing "free" wrong
166
211
  * costs the owner money.
212
+ *
213
+ * The model has the last word in one direction only. A local address with a
214
+ * cloud-tagged model is `metered`: Ollama proxies hosted models through
215
+ * `127.0.0.1`, so the endpoint is local and the bill is not. Read from the
216
+ * **configured value**, never from what the server lists — the owner's config
217
+ * is the thing they chose, and a server's catalogue is not theirs to be
218
+ * classified by.
219
+ */
220
+ declare function resolveCost(id: BackendId, baseUrl: string | undefined,
221
+ /**
222
+ * **Required, and that is the fix.**
223
+ *
224
+ * This was optional, and the no-re-derivation law was breached through the
225
+ * gap rather than by anybody copying the logic. `byollm offer` passed two of
226
+ * three arguments and `resolveConfig` passed three, so the same service was
227
+ * free to one and metered to the other: `glm-5.2:cloud` on a loopback
228
+ * address looks local until you read the tag. The command wrote a share the
229
+ * daemon then refused, and told its owner to run the command they had just
230
+ * run.
231
+ *
232
+ * A shared rule's signature admits no partial askers. `undefined` is still a
233
+ * legal *value* — a service genuinely without a model — but it has to be
234
+ * passed, so choosing to omit the model is a decision at the call site
235
+ * rather than a default nobody notices.
236
+ */
237
+ model: string | undefined): BackendCost;
238
+ /**
239
+ * Why a service costs what it costs — the same decision, said out loud.
240
+ *
241
+ * Consent has to name the rule that fired. The offer ceremony read
242
+ * "Any OpenAI-compatible server ... bills your account per token", which is
243
+ * false about the type — an owner's local qwen is `openai-http` and costs
244
+ * nothing but electricity — and so it gave a reason that its reader could
245
+ * check and find wrong. The thing that bills is the `:cloud` tag on one
246
+ * model, not the transport that carries it.
247
+ *
248
+ * One function decides and one function explains, and the second calls the
249
+ * first, so a message can never describe a classification the code did not
250
+ * make. Splitting them would be the same defect this signature was just
251
+ * hardened against, arriving as prose.
252
+ */
253
+ /**
254
+ * The product's name alone, without the parenthetical that classifies it.
255
+ *
256
+ * Every label in this registry does two jobs: it names a product and says what
257
+ * that product means for the person paying — "Claude CLI (your subscription)",
258
+ * "Ollama (local)". That is right for a list, where the parenthetical is the
259
+ * only classification on screen.
260
+ *
261
+ * It is wrong inside a sentence that states the classification itself, which
262
+ * then stutters: "my-claude runs on Claude CLI (your subscription), a
263
+ * subscription whose terms…". Prose wants the name; the sentence around it is
264
+ * already carrying the meaning.
265
+ *
266
+ * One definition rather than a regex at each call site — and the place to
267
+ * change if the registry ever splits the two facts into two fields, which is
268
+ * the better shape and not worth a migration today.
167
269
  */
168
- declare function resolveCost(id: BackendId, baseUrl: string | undefined): BackendCost;
270
+ declare function backendName(id: BackendId): string;
271
+ interface CostReason {
272
+ readonly cost: BackendCost;
273
+ /** The rule, in the words a person consenting needs. */
274
+ readonly because: string;
275
+ }
276
+ declare function classifyCost(id: BackendId, baseUrl: string | undefined, model: string | undefined): CostReason;
169
277
 
170
278
  /**
171
279
  * Who may run a job, declared by the app that enqueued it.
172
280
  *
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.
281
+ * - `private` — only the job owner's own devices.
282
+ * - `team` — a device whose owner admits this person.
283
+ *
284
+ * **One vocabulary, ruled 2026-08-24.** These were `self | named | public`
285
+ * while {@link OfferScope} used different words for the same idea, which would
286
+ * have left every seam where the two meet speaking two languages, and every
287
+ * doc explaining "self versus private" for ever. They are still independent
288
+ * axes — a job says who may run it, a service says who it will run for — and a
289
+ * job runs only where both agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}).
290
+ *
291
+ * **`public` is gone, ruled 2026-08-26 (byollm_016).** Not deprecated,
292
+ * removed, and removed from the OSS daemon too rather than parked as a
293
+ * community posture. The argument was a measurement rather than a preference:
294
+ * device-side admission had never once been exercised end to end, because
295
+ * every cross-user test ran against a publicly offered service and
296
+ * {@link matchAudience} returned ALLOWED for those *without consulting the
297
+ * device at all*. `public` was the off switch for admission, and an enum with
298
+ * a value that skips verification is a fail-open waiting for the wiring bug
299
+ * that reaches it. There is now no such value.
177
300
  */
178
301
  declare const Audience: z.ZodEnum<{
179
- self: "self";
180
- named: "named";
181
- public: "public";
302
+ private: "private";
303
+ team: "team";
182
304
  }>;
183
305
  type Audience = z.infer<typeof Audience>;
184
306
  /**
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}).
307
+ * What a device's owner is willing to run for other people, per service.
308
+ *
309
+ * - `private` the owner's own work only.
310
+ * - `team` — whoever the owner's authority admits. Membership is **central**,
311
+ * not per-person: the device follows what it is told by a signature it can
312
+ * check, rather than holding its own copy of who is in it (byollm_016).
313
+ *
314
+ * Two values, and no third that means "everyone". See {@link Audience} for
315
+ * why `public` was removed rather than parked, and note the shape of the
316
+ * remaining enum: **every value left requires the device to verify
317
+ * something.** `private` checks the owner; `team` checks admission. That is
318
+ * the property, not an accident of there being two.
188
319
  */
189
320
  declare const OfferScope: z.ZodEnum<{
190
- self: "self";
191
- named: "named";
192
- public: "public";
321
+ private: "private";
322
+ team: "team";
193
323
  }>;
194
324
  type OfferScope = z.infer<typeof OfferScope>;
195
325
  /** All audience values, in widening order. */
196
- declare const AUDIENCES: readonly ("self" | "named" | "public")[];
326
+ declare const AUDIENCES: readonly ("private" | "team")[];
197
327
  /** All offer scopes, in widening order. */
198
- declare const OFFER_SCOPES: readonly ("self" | "named" | "public")[];
328
+ declare const OFFER_SCOPES: readonly ("private" | "team")[];
199
329
  /**
200
330
  * Why a job was refused. Distinct codes because byollm_002 requires that
201
331
  * different truths never share a message — "no matching work" and "refused on
@@ -234,9 +364,9 @@ interface SpendConsent {
234
364
  * its matcher call, so no code path can observe a scope wider than the cost
235
365
  * class allows:
236
366
  *
237
- * - `subscription` is locked to `self` regardless of config
367
+ * - `subscription` is locked to `private` regardless of config
238
368
  * ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — someone else's terms.
239
- * - `metered` narrows to `self` unless the owner has explicitly acknowledged
369
+ * - `metered` narrows to `private` unless the owner has explicitly acknowledged
240
370
  * the spend ({@link MUSTS.METERED_DEFAULTS_SELF}) — their money.
241
371
  * - `free` passes through — their electricity.
242
372
  *
@@ -252,7 +382,8 @@ interface MatchJob {
252
382
  readonly audience: Audience;
253
383
  /**
254
384
  * 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
385
+ * `team` job. Defence in depth only, and direct-mode only it never
386
+ * reaches a daemon (cloud_008 §0.2), so the device's own admission is the
256
387
  * enforcing side ({@link MUSTS.NAMED_LOCAL_ALLOWLIST}).
257
388
  */
258
389
  readonly audienceAllow?: readonly string[] | undefined;
@@ -268,13 +399,24 @@ interface MatchDaemon {
268
399
  /** What the owner agreed to spend on others, for a `metered` backend. */
269
400
  readonly spend?: SpendConsent | undefined;
270
401
  /**
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.
402
+ * Has something **this device verified** admitted the job's owner?
403
+ *
404
+ * A predicate rather than a value so the protocol package stays free of
405
+ * both file I/O and signature state. What supplies it has changed twice and
406
+ * will change again — a local allowlist, then a held roster, and now a
407
+ * claim-time signed grant (Amendment J) — and the law it feeds has not
408
+ * changed at all: a `team` service runs a stranger's work only when
409
+ * somebody this device can check said so.
410
+ *
411
+ * The server passes a conservative `() => true`: it cannot know what a
412
+ * remote device verified and must not pretend to. The device is the
413
+ * enforcing side, which is the whole point of asking here.
414
+ *
415
+ * Named for the question, not for where the answer lives. This was called
416
+ * `locallyAllows`, and "locally" stopped being true the moment the answer
417
+ * came from a document somebody else signed.
276
418
  */
277
- readonly locallyAllows: (owner: string) => boolean;
419
+ readonly admits: (owner: string) => boolean;
278
420
  }
279
421
  /**
280
422
  * Decide whether a job may run on a daemon.
@@ -283,20 +425,20 @@ interface MatchDaemon {
283
425
  * 1. the job's audience must admit the daemon's owner, and
284
426
  * 2. the backend's offer scope must admit the job's owner.
285
427
  *
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
428
+ * The full four-way matrix (two audiences × two offer scopes) is asserted by
429
+ * the conformance kit. The function is pure and total so both the daemon
288
430
  * and the server can run the identical rule — the daemon refuses, and the
289
431
  * server refuses too (byollm_003 §Server-side MUSTs).
290
432
  *
291
433
  * @example
292
434
  * ```ts
293
435
  * const result = matchAudience(
294
- * { owner: "alice", audience: "named" },
436
+ * { owner: "alice", audience: "team" },
295
437
  * {
296
438
  * owner: "bob",
297
- * offerScope: "named",
439
+ * offerScope: "team",
298
440
  * cost: "free",
299
- * locallyAllows: (o) => o === "alice",
441
+ * admits: (o) => o === "alice",
300
442
  * },
301
443
  * );
302
444
  * // result.ok === true
@@ -314,9 +456,16 @@ declare const REFUSAL_MESSAGES: Readonly<Record<MatchRefusal, string>>;
314
456
  * Upper bounds on payload size, enforced at the schema so oversized input is
315
457
  * refused at parse time rather than somewhere deeper.
316
458
  *
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.
459
+ * All three are enforced cloud_008 Tier 4, finding 30. `maxTotalChars` was
460
+ * declared here and referenced nowhere, under this docstring's claim that the
461
+ * schema enforces them, so a chat payload of 256 messages at a million
462
+ * characters each parsed cleanly at sixty-four times the stated ceiling. The
463
+ * per-field limits were real and the aggregate one was a number in a frozen
464
+ * object.
465
+ *
466
+ * byollm_004 §4 requires stricter limits for community (`team`) jobs; those
467
+ * are applied on top of these by the daemon's budget check, which knows the
468
+ * job's audience. These are the absolute ceilings for any job.
320
469
  */
321
470
  declare const PAYLOAD_LIMITS: Readonly<{
322
471
  /** Max characters in any single text field. */
@@ -337,7 +486,7 @@ declare const ChatMessage: z.ZodObject<{
337
486
  assistant: "assistant";
338
487
  }>;
339
488
  content: z.ZodString;
340
- }, z.core.$strip>;
489
+ }, z.core.$strict>;
341
490
  type ChatMessage = z.infer<typeof ChatMessage>;
342
491
  /**
343
492
  * Payload for `llm.generate`.
@@ -364,7 +513,7 @@ declare const ChatPayload: z.ZodObject<{
364
513
  assistant: "assistant";
365
514
  }>;
366
515
  content: z.ZodString;
367
- }, z.core.$strip>>;
516
+ }, z.core.$strict>>;
368
517
  system: z.ZodOptional<z.ZodString>;
369
518
  }, z.core.$strict>;
370
519
  type ChatPayload = z.infer<typeof ChatPayload>;
@@ -391,7 +540,7 @@ declare const KindedPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
391
540
  prompt: z.ZodString;
392
541
  system: z.ZodOptional<z.ZodString>;
393
542
  }, z.core.$strict>;
394
- }, z.core.$strip>, z.ZodObject<{
543
+ }, z.core.$strict>, z.ZodObject<{
395
544
  kind: z.ZodLiteral<"llm.chat">;
396
545
  payload: z.ZodObject<{
397
546
  messages: z.ZodArray<z.ZodObject<{
@@ -401,10 +550,10 @@ declare const KindedPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
401
550
  assistant: "assistant";
402
551
  }>;
403
552
  content: z.ZodString;
404
- }, z.core.$strip>>;
553
+ }, z.core.$strict>>;
405
554
  system: z.ZodOptional<z.ZodString>;
406
555
  }, z.core.$strict>;
407
- }, z.core.$strip>], "kind">;
556
+ }, z.core.$strict>], "kind">;
408
557
  type KindedPayload = z.infer<typeof KindedPayload>;
409
558
  /** The payload type for a given kind. */
410
559
  type PayloadFor<K extends JobKind> = K extends "llm.generate" ? GeneratePayload : ChatPayload;
@@ -433,11 +582,11 @@ declare function payloadTextLength(kinded: KindedPayload): number;
433
582
  declare const JobState: z.ZodEnum<{
434
583
  ok: "ok";
435
584
  error: "error";
585
+ expired: "expired";
436
586
  queued: "queued";
437
587
  claimed: "claimed";
438
588
  running: "running";
439
589
  canceled: "canceled";
440
- expired: "expired";
441
590
  }>;
442
591
  type JobState = z.infer<typeof JobState>;
443
592
  /** States from which a job never moves again. */
@@ -451,7 +600,7 @@ declare const Lease: z.ZodObject<{
451
600
  id: z.ZodString;
452
601
  runnerId: z.ZodString;
453
602
  expiresAt: z.ZodNumber;
454
- }, z.core.$strip>;
603
+ }, z.core.$strict>;
455
604
  type Lease = z.infer<typeof Lease>;
456
605
  /** Payload union as it appears on a job record. */
457
606
  declare const JobPayload: z.ZodUnion<readonly [z.ZodObject<{
@@ -465,7 +614,7 @@ declare const JobPayload: z.ZodUnion<readonly [z.ZodObject<{
465
614
  assistant: "assistant";
466
615
  }>;
467
616
  content: z.ZodString;
468
- }, z.core.$strip>>;
617
+ }, z.core.$strict>>;
469
618
  system: z.ZodOptional<z.ZodString>;
470
619
  }, z.core.$strict>]>;
471
620
  type JobPayload = z.infer<typeof JobPayload>;
@@ -494,35 +643,34 @@ declare const ClaimedJob: z.ZodObject<{
494
643
  assistant: "assistant";
495
644
  }>;
496
645
  content: z.ZodString;
497
- }, z.core.$strip>>;
646
+ }, z.core.$strict>>;
498
647
  system: z.ZodOptional<z.ZodString>;
499
648
  }, z.core.$strict>]>;
500
649
  audience: z.ZodEnum<{
501
- self: "self";
502
- named: "named";
503
- public: "public";
650
+ private: "private";
651
+ team: "team";
504
652
  }>;
505
653
  owner: z.ZodString;
506
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
654
+ site: z.ZodOptional<z.ZodString>;
655
+ service: z.ZodOptional<z.ZodString>;
507
656
  lease: z.ZodObject<{
508
657
  id: z.ZodString;
509
658
  runnerId: z.ZodString;
510
659
  expiresAt: z.ZodNumber;
511
- }, z.core.$strip>;
660
+ }, z.core.$strict>;
512
661
  }, z.core.$strict>;
513
662
  type ClaimedJob = z.infer<typeof ClaimedJob>;
514
663
  /**
515
664
  * The provenance that travels with every result to the delivery seam.
516
665
  *
517
- * byollm_003 Rev 1: a `named`/`public` result is attacker-controlled text.
666
+ * byollm_003 Rev 1: a `team` result is attacker-controlled text.
518
667
  * 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}).
668
+ * knowing that is what it is ({@link MUSTS.PROVENANCE_NAMES_DEVICE}).
520
669
  */
521
670
  declare const ResultProvenance: z.ZodObject<{
522
671
  audience: z.ZodEnum<{
523
- self: "self";
524
- named: "named";
525
- public: "public";
672
+ private: "private";
673
+ team: "team";
526
674
  }>;
527
675
  runnerId: z.ZodString;
528
676
  runnerOwner: z.ZodString;
@@ -545,6 +693,30 @@ declare function provenanceFor(input: {
545
693
  backendClass: BackendClass;
546
694
  model: string;
547
695
  }): ResultProvenance;
696
+ /**
697
+ * What the daemon did, sealed with the answer — cloud_008 §2.5.
698
+ *
699
+ * These travelled in the clear on `ResultRequest`, which meant two things at
700
+ * once. On the direct plane the site believed unauthenticated fields beside
701
+ * an authenticated envelope — a daemon could seal one answer and *declare* it
702
+ * came from a different model, and only the field it did not sign would be
703
+ * recorded. Through a relay they reached a third party that acts on none of
704
+ * them, and `model` in particular is the kind of detail Amendment A's rule
705
+ * keeps off the wire.
706
+ *
707
+ * Sealed, they are the daemon's signed statement about its own run: the site
708
+ * opens them, nothing in between sees them, and the disposition check that
709
+ * already compares clear-text against ciphertext extends to cover them.
710
+ */
711
+ declare const RunMetadata: z.ZodObject<{
712
+ model: z.ZodString;
713
+ backendClass: z.ZodEnum<{
714
+ http: "http";
715
+ process: "process";
716
+ }>;
717
+ durationMs: z.ZodNumber;
718
+ }, z.core.$strict>;
719
+ type RunMetadata = z.infer<typeof RunMetadata>;
548
720
  /** Successful outcome. */
549
721
  declare const JobResultOk: z.ZodObject<{
550
722
  outcome: z.ZodLiteral<"ok">;
@@ -575,17 +747,98 @@ declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
575
747
  outcome: z.ZodLiteral<"canceled">;
576
748
  }, z.core.$strict>], "outcome">;
577
749
  type JobOutcome = z.infer<typeof JobOutcome>;
750
+ /**
751
+ * Why a job can never run — byollm_016 Phase B.
752
+ *
753
+ * Every one of these is **terminal**, and that is the whole point of naming
754
+ * them. A job that cannot be matched used to sit queued until its deadline,
755
+ * which reads exactly like a job that is merely waiting for a device to come
756
+ * online — so an app could not tell "any moment now" from "never", and neither
757
+ * could the person watching a spinner. Silence must never read as pending.
758
+ *
759
+ * They are decided by whoever knows first: the site's own SDK where it can see
760
+ * the answer without asking, the router where matching happens, and the daemon
761
+ * again on arrival under the both-sides rule. All three reason from the same
762
+ * list rather than three private vocabularies.
763
+ */
764
+ declare const RefusalReason: z.ZodEnum<{
765
+ "default-ambiguity": "default-ambiguity";
766
+ "default-unusable": "default-unusable";
767
+ }>;
768
+ type RefusalReason = z.infer<typeof RefusalReason>;
769
+ /**
770
+ * A terminal outcome nobody sealed — byollm_016 Phase B.
771
+ *
772
+ * Every other finished job carries an envelope encrypted by the device that
773
+ * ran it, which is what makes a result unforgeable. These have no device: the
774
+ * job was refused *before* anything could run it, so there is nobody to seal
775
+ * from and no content to seal.
776
+ *
777
+ * **What that costs, stated plainly.** This is the one terminal outcome a
778
+ * router can author. It is worth being exact about the power that grants,
779
+ * because "the relay can write this" sounds alarming until you compare it with
780
+ * what a relay could already do: drop the job, never offer it, and let it
781
+ * expire. A router-authored refusal is *denial of service by a shorter route*,
782
+ * which is a power the router has always had and which the trust model has
783
+ * always said it has. What it emphatically is **not** is forgery: this shape
784
+ * carries no envelope and no output, so it can never be mistaken for an answer
785
+ * a device produced. A relay still cannot fabricate a result, because that
786
+ * needs a signature it does not hold.
787
+ *
788
+ * So the rule this shape enforces by construction: a refusal may deny, and may
789
+ * never assert. Anything that claims work was *done* still comes sealed.
790
+ */
791
+ declare const JobRefused: z.ZodObject<{
792
+ outcome: z.ZodLiteral<"refused">;
793
+ reason: z.ZodEnum<{
794
+ "default-ambiguity": "default-ambiguity";
795
+ "default-unusable": "default-unusable";
796
+ }>;
797
+ message: z.ZodString;
798
+ }, z.core.$strict>;
799
+ type JobRefused = z.infer<typeof JobRefused>;
800
+ /**
801
+ * The plaintext inside a result envelope.
802
+ *
803
+ * The outcome and how it was produced, together, because they are one
804
+ * statement by one signer. A site that opened only the outcome would be
805
+ * trusting the envelope for the answer and the request body for everything
806
+ * about it.
807
+ */
808
+ declare const SealedOutcome: z.ZodObject<{
809
+ outcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
810
+ outcome: z.ZodLiteral<"ok">;
811
+ text: z.ZodString;
812
+ artifactUrl: z.ZodOptional<z.ZodURL>;
813
+ }, z.core.$strict>, z.ZodObject<{
814
+ outcome: z.ZodLiteral<"error">;
815
+ code: z.ZodString;
816
+ message: z.ZodString;
817
+ retryable: z.ZodBoolean;
818
+ }, z.core.$strict>, z.ZodObject<{
819
+ outcome: z.ZodLiteral<"canceled">;
820
+ }, z.core.$strict>], "outcome">;
821
+ ran: z.ZodObject<{
822
+ model: z.ZodString;
823
+ backendClass: z.ZodEnum<{
824
+ http: "http";
825
+ process: "process";
826
+ }>;
827
+ durationMs: z.ZodNumber;
828
+ }, z.core.$strict>;
829
+ }, z.core.$strict>;
830
+ type SealedOutcome = z.infer<typeof SealedOutcome>;
578
831
  /** A completed job as delivered to the app, provenance attached. */
579
832
  declare const DeliveredResult: z.ZodObject<{
580
833
  jobId: z.ZodString;
581
834
  state: z.ZodEnum<{
582
835
  ok: "ok";
583
836
  error: "error";
837
+ expired: "expired";
584
838
  queued: "queued";
585
839
  claimed: "claimed";
586
840
  running: "running";
587
841
  canceled: "canceled";
588
- expired: "expired";
589
842
  }>;
590
843
  outcome: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
591
844
  outcome: z.ZodLiteral<"ok">;
@@ -601,9 +854,8 @@ declare const DeliveredResult: z.ZodObject<{
601
854
  }, z.core.$strict>], "outcome">>;
602
855
  provenance: z.ZodOptional<z.ZodObject<{
603
856
  audience: z.ZodEnum<{
604
- self: "self";
605
- named: "named";
606
- public: "public";
857
+ private: "private";
858
+ team: "team";
607
859
  }>;
608
860
  runnerId: z.ZodString;
609
861
  runnerOwner: z.ZodString;
@@ -614,8 +866,11 @@ declare const DeliveredResult: z.ZodObject<{
614
866
  model: z.ZodString;
615
867
  untrusted: z.ZodBoolean;
616
868
  }, z.core.$strict>>;
869
+ fallback: z.ZodOptional<z.ZodLiteral<true>>;
617
870
  }, z.core.$strict>;
618
871
  type DeliveredResult = z.infer<typeof DeliveredResult>;
872
+ /** Its members, derived — see {@link BACKEND_CLASSES} for why. */
873
+ declare const SIZE_CLASSES: readonly ("small" | "medium" | "large" | "unbounded")[];
619
874
  /**
620
875
  * How big a payload is, in buckets — byollm_009 §6.
621
876
  *
@@ -677,12 +932,12 @@ declare const JobStub: z.ZodObject<{
677
932
  "llm.chat": "llm.chat";
678
933
  }>;
679
934
  owner: z.ZodString;
935
+ site: z.ZodString;
680
936
  audience: z.ZodEnum<{
681
- self: "self";
682
- named: "named";
683
- public: "public";
937
+ private: "private";
938
+ team: "team";
684
939
  }>;
685
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
940
+ purpose: z.ZodOptional<z.ZodString>;
686
941
  sizeClass: z.ZodEnum<{
687
942
  small: "small";
688
943
  medium: "medium";
@@ -693,7 +948,23 @@ declare const JobStub: z.ZodObject<{
693
948
  deadlineAt: z.ZodNumber;
694
949
  }, z.core.$strict>;
695
950
  type JobStub = z.infer<typeof JobStub>;
696
- /** A stub, plus the lease the claiming runner now holds for it. */
951
+ /**
952
+ * A stub, plus the lease the claiming runner now holds for it — and, on a
953
+ * relayed route, the grant that says it may run at all.
954
+ *
955
+ * The grant lives here rather than on {@link JobStub} because of *when* it is
956
+ * authored. A stub exists from enqueue; a grant is written at claim, against
957
+ * the membership and mapping true at that moment. That timing is the whole of
958
+ * Amendment J: a job queued yesterday for somebody removed this morning gets
959
+ * no grant when it is finally claimed, and a roster held on the device could
960
+ * never have known.
961
+ *
962
+ * Optional, and the absence is meaningful rather than lenient. A device that
963
+ * pinned a control-plane key at pairing **requires** one — a claimed job
964
+ * arriving without it is refused, not admitted by default. A device that
965
+ * pinned none is in direct mode, where there is no control plane to author
966
+ * anything and the owner's own work is the only work that runs.
967
+ */
697
968
  declare const ClaimedStub: z.ZodObject<{
698
969
  id: z.ZodString;
699
970
  kind: z.ZodEnum<{
@@ -701,12 +972,12 @@ declare const ClaimedStub: z.ZodObject<{
701
972
  "llm.chat": "llm.chat";
702
973
  }>;
703
974
  owner: z.ZodString;
975
+ site: z.ZodString;
704
976
  audience: z.ZodEnum<{
705
- self: "self";
706
- named: "named";
707
- public: "public";
977
+ private: "private";
978
+ team: "team";
708
979
  }>;
709
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
980
+ purpose: z.ZodOptional<z.ZodString>;
710
981
  sizeClass: z.ZodEnum<{
711
982
  small: "small";
712
983
  medium: "medium";
@@ -719,7 +990,19 @@ declare const ClaimedStub: z.ZodObject<{
719
990
  id: z.ZodString;
720
991
  runnerId: z.ZodString;
721
992
  expiresAt: z.ZodNumber;
722
- }, z.core.$strip>;
993
+ }, z.core.$strict>;
994
+ grant: z.ZodOptional<z.ZodObject<{
995
+ grantId: z.ZodString;
996
+ jobId: z.ZodString;
997
+ site: z.ZodString;
998
+ user: z.ZodString;
999
+ owner: z.ZodString;
1000
+ purpose: z.ZodString;
1001
+ kind: z.ZodString;
1002
+ service: z.ZodString;
1003
+ issuedAt: z.ZodNumber;
1004
+ signature: z.ZodString;
1005
+ }, z.core.$strict>>;
723
1006
  }, z.core.$strict>;
724
1007
  type ClaimedStub = z.infer<typeof ClaimedStub>;
725
1008
 
@@ -763,6 +1046,18 @@ declare const StoredKeys: z.ZodObject<{
763
1046
  createdAt: z.ZodNumber;
764
1047
  }, z.core.$strict>;
765
1048
  type StoredKeys = z.infer<typeof StoredKeys>;
1049
+ /** Domain separator, so a signature over an encryption key cannot be
1050
+ * replayed as a signature over anything else. */
1051
+ /**
1052
+ * What an encryption key's signature covers.
1053
+ *
1054
+ * Exported because a rotation is a real event this protocol has to be able to
1055
+ * *test* — a record whose encryption key moved under an identity that signed
1056
+ * the move is the one case pinning must refuse loudly, and building one
1057
+ * outside this file otherwise means re-typing this string, which is how two
1058
+ * copies of a constant start disagreeing.
1059
+ */
1060
+ declare const ENCRYPTION_KEY_CONTEXT = "byollm/v1/encryption-key";
766
1061
  /** Generate a fresh pair of keypairs and bind them together. */
767
1062
  declare function generateKeys(now: number): StoredKeys;
768
1063
  /** The public half, for the wire. */
@@ -777,7 +1072,17 @@ declare function publicIdentityOf(keys: StoredKeys): PublicIdentity;
777
1072
  */
778
1073
  declare function verifyPublicIdentity(identity: PublicIdentity): boolean;
779
1074
  /** Sign arbitrary bytes with an identity key. */
780
- declare function signWith(keys: StoredKeys, data: Uint8Array): string;
1075
+ /**
1076
+ * Sign bytes with an identity key.
1077
+ *
1078
+ * Takes only the private half it uses. A signer that demanded a whole
1079
+ * {@link StoredKeys} would make every caller hold an encryption keypair for a
1080
+ * job that has no encryption in it — and the control plane, which signs
1081
+ * rosters and opens nothing, would be generating and storing secret material
1082
+ * it can never need. Every existing caller passes a full `StoredKeys`, which
1083
+ * satisfies this.
1084
+ */
1085
+ declare function signWith(keys: Pick<StoredKeys, "identityPrivate">, data: Uint8Array): string;
781
1086
  /** Verify bytes against a raw Ed25519 public key. */
782
1087
  declare function verifyWith(identityPublic: string, data: Uint8Array, signature: string): boolean;
783
1088
  /**
@@ -946,7 +1251,77 @@ declare function signRequest(keys: StoredKeys, input: {
946
1251
  issuedAt: number;
947
1252
  body: string;
948
1253
  }): RequestSignature;
949
- /** Why a signed request was refused. Never returned to the caller verbatim. */
1254
+ /**
1255
+ * The same scheme, for the party at the other end: a **site** calling a relay.
1256
+ *
1257
+ * A site talking to a relay is in exactly the daemon's position — an outbound
1258
+ * caller with an identity keypair the other side already pins — so it gets the
1259
+ * daemon's authentication rather than a second scheme. Bearer tokens for the
1260
+ * site plane were the alternative, and they would have reintroduced the
1261
+ * credential-in-a-file that §4.2 removed from the daemon plane, on the plane
1262
+ * that carries *every* site's traffic.
1263
+ *
1264
+ * Two things make this safe to build on the same canonical string:
1265
+ *
1266
+ * 1. **The endpoint is namespaced.** Site endpoints sign `site/enqueue`, never
1267
+ * `enqueue`. The daemon plane's `result` and the site plane's `results` are
1268
+ * one character apart, and a naming collision between planes must not be
1269
+ * what stands between a signature and a replay onto the wrong handler. The
1270
+ * prefix is applied *inside* these helpers, so the two ends cannot disagree
1271
+ * about it — the alternative is two implementations of one bound value,
1272
+ * which is this project's most-repeated bug.
1273
+ * 2. **The caller slot carries the site id.** `canonicalRequest` names that
1274
+ * field `runnerId` because the daemon plane got there first; here it holds
1275
+ * the site id, and the verifier looks the key up in the projection's site
1276
+ * registry rather than its device registry. The two registries never share
1277
+ * an entry, so a device signature cannot authenticate as a site.
1278
+ *
1279
+ * §4.2's replay argument carries over **only because the site plane's writes
1280
+ * are idempotent per addressed instance**, which is a property that had to be
1281
+ * built rather than found: `enqueue` reset a job of the same id, so a replayed
1282
+ * enqueue inside the freshness window returned a claimed job to the queue and
1283
+ * threw away a device's live lease. Identical in shape to the `release` bug
1284
+ * above, on the other plane. Anything added to the site plane later must be
1285
+ * idempotent by the instance it names, or this scheme does not cover it.
1286
+ */
1287
+ declare function signSiteRequest(keys: StoredKeys, input: {
1288
+ endpoint: string;
1289
+ siteId: string;
1290
+ issuedAt: number;
1291
+ body: string;
1292
+ }): RequestSignature;
1293
+ /** Verify a site's call against the identity the control plane registered. */
1294
+ declare function verifySiteRequest(input: {
1295
+ identityPublic: string;
1296
+ endpoint: string;
1297
+ body: string;
1298
+ signature: RequestSignature;
1299
+ now: number;
1300
+ maxSkewMs?: number;
1301
+ }): SignatureFailure | null;
1302
+ /**
1303
+ * Why a signed request was refused.
1304
+ *
1305
+ * **`bad-signature` is never returned verbatim; `stale` is, deliberately.**
1306
+ * They are different kinds of refusal and conflating them costs a real user
1307
+ * more than it costs an attacker.
1308
+ *
1309
+ * A bad signature is an authentication failure and the server says only
1310
+ * "unauthorized" — telling a prober which part they got wrong is free help.
1311
+ *
1312
+ * A stale timestamp is a **precondition** failure: the signature may be
1313
+ * perfectly valid and the caller's clock is simply wrong. Saying so reveals
1314
+ * nothing, for two reasons that both have to hold. The server's time is
1315
+ * already public — every response carries a `Date` header and the heartbeat
1316
+ * response returns `serverTime` outright. And freshness is checked *before*
1317
+ * the signature is verified, so a stale answer says nothing about whether the
1318
+ * signature was any good.
1319
+ *
1320
+ * What conflating them costs: a machine whose clock has drifted gets
1321
+ * `401 unauthorized` on every request, forever, with nothing anywhere pointing
1322
+ * at the clock. That is the shape byollm_013 was filed about — a refusal that
1323
+ * is correct, silent, and sends somebody to read our source.
1324
+ */
950
1325
  type SignatureFailure = "stale" | "bad-signature";
951
1326
  /**
952
1327
  * Verify a signed request against a runner's pinned identity key.
@@ -964,6 +1339,399 @@ declare function verifyRequest(input: {
964
1339
  maxSkewMs?: number;
965
1340
  }): SignatureFailure | null;
966
1341
 
1342
+ /**
1343
+ * What a site says it needs — byollm_016 Amendment L.
1344
+ *
1345
+ * A site declares **purposes**, and each purpose lists the job kinds it uses.
1346
+ * A person then maps each purpose to one of their own services, on the consent
1347
+ * screen, and that mapping *is* the consent. The control plane joins the two
1348
+ * at claim time and signs the result into a grant.
1349
+ *
1350
+ * ## Why a site declares needs instead of naming services
1351
+ *
1352
+ * Because it cannot name one. The site's vocabulary is its own purposes; the
1353
+ * person's vocabulary is their services; and the two never meet. A site asks
1354
+ * for "writing assistant, llm.chat" and learns only whether that slot is
1355
+ * satisfiable — never which model answered, never whose machine, never even
1356
+ * the name of the service. Key-vs-value reaches its strongest form here: the
1357
+ * site cannot describe what it wants *or* name it, only ask for what it
1358
+ * declared.
1359
+ *
1360
+ * ## Keys are ids; labels are prose
1361
+ *
1362
+ * They are separate fields and nothing derives one from the other, which is
1363
+ * the amendment's ruling and worth restating where somebody will read it. A
1364
+ * key travels on every job and is what mappings are stored against, so it is
1365
+ * stable-or-nothing: renaming one deletes a purpose and creates another,
1366
+ * unmapping everybody who had chosen for it. A label is changeable whenever
1367
+ * the site likes and is the **only** thing a consent screen renders.
1368
+ */
1369
+ /**
1370
+ * The purpose a site gets when it declares no purposes of its own.
1371
+ *
1372
+ * Reserved, and refused by {@link Manifest} rather than by whatever handles
1373
+ * registration. A site with a single undifferentiated use has one purpose —
1374
+ * everything it does — and that purpose needs an id because mappings are
1375
+ * keyed by one. An id taken from the site's own vocabulary would collide the
1376
+ * day it declared a real purpose of the same name.
1377
+ *
1378
+ * **Never rendered.** "default → your Claude" tells a person nothing; a
1379
+ * consent screen shows the site's own name for this slot, because that is
1380
+ * what a single-purpose site's one purpose actually is.
1381
+ */
1382
+ declare const RESERVED_PURPOSE = "default";
1383
+ declare const Purpose: z.ZodObject<{
1384
+ label: z.ZodString;
1385
+ description: z.ZodOptional<z.ZodString>;
1386
+ kinds: z.ZodArray<z.ZodEnum<{
1387
+ "llm.generate": "llm.generate";
1388
+ "llm.chat": "llm.chat";
1389
+ }>>;
1390
+ }, z.core.$strict>;
1391
+ type Purpose = z.infer<typeof Purpose>;
1392
+ /**
1393
+ * Everything a site needs, by purpose key.
1394
+ *
1395
+ * At least one purpose: a site that declares none is a site that can enqueue
1396
+ * nothing, and accepting it would mean the first refusal a person saw came
1397
+ * from a job rather than from registration.
1398
+ */
1399
+ /**
1400
+ * How many purposes one site may declare.
1401
+ *
1402
+ * There was no bound at all: a site could declare fifty thousand, each one
1403
+ * individually valid, and the consent screen renders a slot per (purpose,
1404
+ * kind) — so the page that *is* the consent mechanism becomes unusable, and
1405
+ * the notification mail that enumerates slots grows with it.
1406
+ *
1407
+ * Thirty-two is chosen rather than derived, and the number is an argument: a
1408
+ * purpose is a thing a person reads and decides about one at a time, and a
1409
+ * screen asking more than about thirty separate questions has stopped being a
1410
+ * consent screen whatever it renders. Of Tomorrow Press declares five. A site
1411
+ * that genuinely needs more has a product question to answer before it has a
1412
+ * schema one.
1413
+ */
1414
+ declare const MAX_PURPOSES = 32;
1415
+ declare const Manifest: z.ZodRecord<z.ZodString, z.ZodObject<{
1416
+ label: z.ZodString;
1417
+ description: z.ZodOptional<z.ZodString>;
1418
+ kinds: z.ZodArray<z.ZodEnum<{
1419
+ "llm.generate": "llm.generate";
1420
+ "llm.chat": "llm.chat";
1421
+ }>>;
1422
+ }, z.core.$strict>>;
1423
+ type Manifest = z.infer<typeof Manifest>;
1424
+ /**
1425
+ * The manifest a site with no declared purposes is treated as having.
1426
+ *
1427
+ * The sugar in Amendment L, made explicit rather than special-cased
1428
+ * downstream: everything after this point sees a manifest with one purpose,
1429
+ * so no consent screen, mapping table or resolver needs a branch for the
1430
+ * flat-list case.
1431
+ *
1432
+ * The label is the caller's — a site's own name — because it is the one thing
1433
+ * that can make "everything this site does" read as a sentence about a
1434
+ * particular site rather than about software in general.
1435
+ */
1436
+ declare function singlePurposeManifest(input: {
1437
+ readonly label: string;
1438
+ readonly kinds: readonly JobKind[];
1439
+ }): Manifest;
1440
+
1441
+ /**
1442
+ * One job, one signature, one answer — byollm_016 Amendment J.
1443
+ *
1444
+ * A grant is the control plane's signed statement that a particular job may
1445
+ * run on a particular device, authored at claim time and verified against the
1446
+ * key that device pinned when it paired.
1447
+ *
1448
+ * ## What it replaced, and why the replacement is smaller
1449
+ *
1450
+ * Until 2026-08-26 a device held a signed **roster** and answered admission
1451
+ * from it. Amendment G's four properties were right and the mechanism was a
1452
+ * cache — one that bought nothing. On the cloud route the job path and the
1453
+ * roster path share fate: jobs arrive through the relay, so if the relay is
1454
+ * unreachable there are no jobs to admit and a locally held roster adds no
1455
+ * availability. What it did add was staleness, which is the only reason
1456
+ * `ROSTER_MAX_AGE_MS` existed: a bound on how long a removed person keeps
1457
+ * running. Authoring at claim collapses that bound to this document's own
1458
+ * lifetime — add somebody and their next job runs, remove them and their next
1459
+ * claim fails, including jobs already queued.
1460
+ *
1461
+ * It also collapses four questions into one signature. Consented, member,
1462
+ * admitted, and *which service* were four mechanisms answering separately;
1463
+ * they are now four fields of one statement, and the device verifies once.
1464
+ *
1465
+ * ## What it is not
1466
+ *
1467
+ * Amendment G property 1 outlawed admitting on a per-job assertion, and this
1468
+ * is per-job. The distinction is authorship: G outlawed trusting the
1469
+ * **relay's or site's unsigned** claim. A grant is signed by the control
1470
+ * plane with a key the device pinned at pairing, so the relay can withhold it
1471
+ * and cannot forge it — exactly the power a relay has over a job.
1472
+ * `RELAY_BLIND` is untouched: the relay delivers, it never authors.
1473
+ *
1474
+ * ## What the device still checks for itself
1475
+ *
1476
+ * A grant is necessary and not sufficient. Four checks stay on the device and
1477
+ * none of them is delegated:
1478
+ *
1479
+ * 1. the signature, against the pinned key;
1480
+ * 2. replay — {@link SignedGrant.grantId} is single-use;
1481
+ * 3. offer-consistency — the named service is one this device actually
1482
+ * offers, at a scope that includes this user;
1483
+ * 4. **private is absolute** — a `private` service runs for the paired owner
1484
+ * and nobody else, so no compromise of a control plane can grant somebody
1485
+ * else's job onto it.
1486
+ */
1487
+ /**
1488
+ * How long a grant is honoured after it was signed. Ruled 120s (2026-08-26).
1489
+ *
1490
+ * This bounds **acceptance**, not execution: a job admitted inside the window
1491
+ * runs to completion however long it takes. So the number only has to cover
1492
+ * the trip from the control plane signing to the device checking — claim,
1493
+ * deliver, verify — and every second past that is a second a captured grant
1494
+ * stays useful.
1495
+ *
1496
+ * Two minutes is generous for that trip and mean for the capture. It is also
1497
+ * the number ordinary clock drift is measured against, which is why
1498
+ * {@link CLOCK_SKEW_WARN_MS} sits well inside it: a device whose clock is off
1499
+ * by half the window would refuse real work, and must be told before it does.
1500
+ *
1501
+ * The verifier's policy, deliberately not a field on the document. An
1502
+ * `expiresAt` the signer chose would let whoever signs decide how long their
1503
+ * own statement stays good, and the party with the most reason to want a
1504
+ * longer window is the party being bounded.
1505
+ */
1506
+ declare const GRANT_MAX_AGE_MS = 120000;
1507
+ /**
1508
+ * Clock disagreement past which a device says so, before it starts refusing.
1509
+ *
1510
+ * Skew eats {@link GRANT_MAX_AGE_MS} directly — a device 60s behind its
1511
+ * relay's clock has half a window left, and one 120s behind has none and
1512
+ * refuses everything for a reason no refusal message would otherwise name.
1513
+ * Thirty seconds is a quarter of the window: far enough out to be a real
1514
+ * problem, early enough to be a warning rather than an outage.
1515
+ */
1516
+ declare const CLOCK_SKEW_WARN_MS = 30000;
1517
+ /**
1518
+ * Skew past which a freshness refusal names the clock instead of the grant.
1519
+ *
1520
+ * Five seconds, because below that the clock is not the story and saying so
1521
+ * would send somebody to check ntp about an unrelated failure. Above it, "this
1522
+ * grant expired" and "your clock is wrong" are the same event wearing
1523
+ * different words, and only one of them can be acted on.
1524
+ */
1525
+ declare const CLOCK_ATTRIBUTION_MS = 5000;
1526
+ /**
1527
+ * The domain separator.
1528
+ *
1529
+ * Every signature in this system says what kind of statement it is before it
1530
+ * says anything else. Without it, bytes signed for one purpose verify for
1531
+ * another — a grant and a request are both "bytes this key signed", and a
1532
+ * scheme that could not tell them apart would let one be replayed as the
1533
+ * other.
1534
+ */
1535
+ declare const GRANT_CONTEXT = "byollm/v1/grant";
1536
+ declare const SignedGrant: z.ZodObject<{
1537
+ grantId: z.ZodString;
1538
+ jobId: z.ZodString;
1539
+ site: z.ZodString;
1540
+ user: z.ZodString;
1541
+ owner: z.ZodString;
1542
+ purpose: z.ZodString;
1543
+ kind: z.ZodString;
1544
+ service: z.ZodString;
1545
+ issuedAt: z.ZodNumber;
1546
+ signature: z.ZodString;
1547
+ }, z.core.$strict>;
1548
+ type SignedGrant = z.infer<typeof SignedGrant>;
1549
+ /** Everything a grant says, before it is signed. */
1550
+ type GrantClaims = Omit<SignedGrant, "signature">;
1551
+ /**
1552
+ * Every field of {@link SignedGrant} except the signature, sorted.
1553
+ *
1554
+ * **Derived from the schema, never written out by hand.** The unsigned-field
1555
+ * attack is that somebody adds a field to the document, forgets to add it to
1556
+ * the bytes, and ships a value an intermediary can rewrite without breaking
1557
+ * any signature. A hand-maintained list is exactly the shape that fails: it
1558
+ * does not grow when the code does, and nothing about adding a field reminds
1559
+ * you it exists.
1560
+ *
1561
+ * Reading the shape closes it structurally rather than by review. A new field
1562
+ * is signed the moment it is declared, and grant.test.ts asserts this list
1563
+ * still covers the schema so a future zod version that hides `shape` fails
1564
+ * loudly instead of silently signing less.
1565
+ */
1566
+ declare const GRANT_SIGNED_FIELDS: readonly (keyof GrantClaims)[];
1567
+ /**
1568
+ * The exact bytes both sides sign and verify.
1569
+ *
1570
+ * JSON-encoded rather than joined with a separator, because a separator can
1571
+ * be imitated. Newline-joining `["a", "b\nc"]` and `["a\nb", "c"]` produces
1572
+ * identical bytes, so two different grants would share a signature — and the
1573
+ * values here include a site id and a user id, at least one of which comes
1574
+ * from somebody else's namespace. JSON escapes the separator it uses, so no
1575
+ * arrangement of field values can spell a different document.
1576
+ *
1577
+ * The context string leads, and the field order is the schema's own sorted
1578
+ * keys, so the encoding is canonical without anyone maintaining a list.
1579
+ */
1580
+ declare function grantStatement(claims: GrantClaims): Uint8Array;
1581
+ /** Sign a grant with the control plane's own key. */
1582
+ declare function signGrant(keys: Pick<StoredKeys, "identityPrivate">, claims: GrantClaims): SignedGrant;
1583
+ /**
1584
+ * Why a grant was refused.
1585
+ *
1586
+ * Split by remedy, because these send somebody to different places: fix your
1587
+ * clock, take it up with the relay, or nothing at all — you are being
1588
+ * attacked and the refusal worked.
1589
+ *
1590
+ * There is deliberately no `no-pinned-key` here. A device that pinned no
1591
+ * control-plane key never reaches this function: it is in direct mode, and
1592
+ * the question "is this grant good" does not arise. A value nothing can
1593
+ * return is a branch every caller has to handle and no test can reach.
1594
+ */
1595
+ type GrantRefusal =
1596
+ /** The signature does not verify against the pinned key. */
1597
+ "bad-signature"
1598
+ /** Genuine, and for a different device's owner. */
1599
+ | "wrong-owner"
1600
+ /** Genuine, and lifted from a different job. */
1601
+ | "wrong-job"
1602
+ /** Older than {@link GRANT_MAX_AGE_MS}. */
1603
+ | "expired"
1604
+ /**
1605
+ * Issued further in the future than clock drift explains.
1606
+ *
1607
+ * Checked, and not as pedantry: an `issuedAt` ahead of now extends a
1608
+ * grant's life past the bound, which is the whole thing being enforced.
1609
+ *
1610
+ * Tolerant by {@link CLOCK_SKEW_WARN_MS}, because it was tolerant by
1611
+ * nothing and that made ordinary drift a total outage — see
1612
+ * {@link verifyGrant}.
1613
+ */
1614
+ | "from-the-future";
1615
+ /**
1616
+ * Is this grant one this device may act on, right now?
1617
+ *
1618
+ * Document-level checks only. Replay, offer-consistency and the private rule
1619
+ * need state this function does not have and are the device's to apply — see
1620
+ * the class comment for the full list of four.
1621
+ */
1622
+ declare function verifyGrant(input: {
1623
+ grant: SignedGrant;
1624
+ owner: string;
1625
+ jobId: string;
1626
+ controlPlanePublic: string;
1627
+ now: number;
1628
+ maxAgeMs?: number;
1629
+ }): GrantRefusal | null;
1630
+
1631
+ /**
1632
+ * Rotation — byollm_009 Amendment C.
1633
+ *
1634
+ * A site holding identity key **K1** wants to be known by **K2**. It publishes
1635
+ * a *succession*: K2, plus a signature by K1 over a statement naming both key
1636
+ * ids. That signature is the entire mechanism, and the reason rotation can be
1637
+ * automatic without becoming a hole is that **the relay cannot mint one** — it
1638
+ * never holds K1. It is the same trust step a daemon already performs at
1639
+ * pairing, applied to the site's own succession.
1640
+ *
1641
+ * ## Why the statement names both keys
1642
+ *
1643
+ * A signature over K2 alone could be lifted from this site's record and
1644
+ * replayed into another site's, moving *that* site to K2 — a key the attacker
1645
+ * holds. Naming the predecessor binds the succession to one chain, and it is
1646
+ * the reason `verifyLink` takes the id it expects to be succeeding from
1647
+ * rather than reading it out of the statement it is checking.
1648
+ */
1649
+ /** The domain separator. Distinct from every other thing an identity signs. */
1650
+ declare const SUCCESSION_CONTEXT = "byollm/v1/site-succession";
1651
+ /**
1652
+ * How long a retired key may still sign work — Amendment C, ruling 2.
1653
+ *
1654
+ * A protocol constant and not the site's to choose. Per-site overlap
1655
+ * arithmetic is exactly the kind of number that has to mean one thing
1656
+ * everywhere, and a site that could choose it could choose *forever*, which is
1657
+ * a two-key site permanently and a second key nobody ever notices retiring.
1658
+ *
1659
+ * Seven days: long enough that a daemon which polls daily and a laptop shut
1660
+ * for a long weekend both see the new record before the old key stops working,
1661
+ * short enough that "which key is live" is never an interesting question.
1662
+ */
1663
+ declare const RETIREMENT_WINDOW_MS: number;
1664
+ /**
1665
+ * The longest chain a daemon will walk — Amendment C, ruling 1.
1666
+ *
1667
+ * **A denial-of-service guard, not policy.** The bound exists so a projection
1668
+ * cannot make a daemon verify ten thousand signatures, not to express an
1669
+ * opinion about how often a site may rotate. A site that legitimately exceeds
1670
+ * it has a re-pair ahead of it, which is why it is generous: at one rotation a
1671
+ * quarter this is sixteen years.
1672
+ */
1673
+ declare const MAX_SUCCESSION_CHAIN = 64;
1674
+ /** One step of a chain: a key, and the signature by it over its successor. */
1675
+ declare const Succession: z.ZodObject<{
1676
+ identity: z.ZodObject<{
1677
+ identity: z.ZodString;
1678
+ encryption: z.ZodString;
1679
+ encryptionSig: z.ZodString;
1680
+ }, z.core.$strict>;
1681
+ signature: z.ZodString;
1682
+ }, z.core.$strict>;
1683
+ type Succession = z.infer<typeof Succession>;
1684
+ /** The exact bytes signed. One definition; both sides call it. */
1685
+ declare function successionStatement(fromKeyId: string, toKeyId: string): Uint8Array;
1686
+ /**
1687
+ * Sign a succession from the keys being retired to the identity taking over.
1688
+ *
1689
+ * Takes `StoredKeys` for the predecessor because only the holder of K1's
1690
+ * private half can produce this, which is the property the whole design rests
1691
+ * on. A site calls this once, at rotation, on the machine holding its keys.
1692
+ */
1693
+ declare function signSuccession(previous: StoredKeys, next: PublicIdentity): Succession;
1694
+ /**
1695
+ * Check one link: did `link.identity` sign over succeeding to `toKeyId`?
1696
+ *
1697
+ * `toKeyId` is passed in rather than read from anywhere in `link`, and that is
1698
+ * the load-bearing detail. A verifier that recovered the successor from the
1699
+ * signed statement would accept a statement about *any* successor, which is
1700
+ * the replay this design names in C.1 — the signature is genuine, the
1701
+ * successor it names is not the one being installed.
1702
+ */
1703
+ declare function verifyLink(link: Succession, toKeyId: string): boolean;
1704
+ /** Why a chain was refused, in the words a log line uses. */
1705
+ type SuccessionFailure = "no-chain" | "too-long" | "unknown-origin" | "broken-link";
1706
+ interface SuccessionWalk {
1707
+ /** The ids the chain passes through, oldest first, ending at the current. */
1708
+ readonly path: string[];
1709
+ /** The approved id the chain reached, when it reached one. */
1710
+ readonly from?: string;
1711
+ readonly failure?: SuccessionFailure;
1712
+ }
1713
+ /**
1714
+ * Walk a chain from the key being presented back to a key already approved.
1715
+ *
1716
+ * `chain` is ordered oldest last, as the projection carries it — so walking it
1717
+ * means starting at the current key and stepping backwards, each link proving
1718
+ * that its holder signed for the id in front of it.
1719
+ *
1720
+ * Returns the approved id it reached, or why it did not. **Deliberately
1721
+ * returns rather than throws**: a chain that does not verify is ordinary
1722
+ * hostile input, and the caller's job is to keep its existing pin and say so.
1723
+ *
1724
+ * `approved` is asked as a predicate rather than taken as a set because the
1725
+ * daemon's notion of "already approved" includes tombstoned ids — a site that
1726
+ * left the allowlist and came back is still a site this machine has vouched
1727
+ * for, and rotation must not become a way to launder that distinction away.
1728
+ */
1729
+ declare function walkSuccession(input: {
1730
+ current: string;
1731
+ chain: readonly Succession[];
1732
+ approved: (keyId: string) => boolean;
1733
+ }): SuccessionWalk;
1734
+
967
1735
  /**
968
1736
  * The normative MUSTs of protocol v0, as data.
969
1737
  *
@@ -997,12 +1765,45 @@ type MustEnforcer = "daemon" | "server" | "both";
997
1765
  * someone else's, so the kit cannot carry it.
998
1766
  * - `construction` — true by the shape of the code, where a test could only
999
1767
  * sample. A reviewer verifies it; a suite cannot.
1768
+ * ## When a MUST binds both sides — cloud_008 Tier 3
1769
+ *
1770
+ * `AUDIENCE_BOTH_SIDES` says the server and the daemon each enforce. The kit
1771
+ * passed **entirely** with the server's half deleted: every check drove a real
1772
+ * daemon, and a daemon refuses locally, so "the job did not run" looked
1773
+ * identical whichever side refused it. A full-honest-stack test proves only
1774
+ * the conjunction.
1775
+ *
1776
+ * So a `both`-enforced MUST needs **one check per party, each with the honest
1777
+ * counterpart removed** — C032 claims over the raw protocol precisely so no
1778
+ * daemon admission logic runs. Where a check strips one side, its comment
1779
+ * says which; where a MUST is enforced by both and only one side is checked,
1780
+ * that is a gap rather than coverage.
1781
+ *
1000
1782
  * - `operator` — a claim about how someone runs a deployment, verifiable only
1001
1783
  * by audit or by reading source. The honest category, and the one that
1002
1784
  * exists so a property nobody can check from outside is *labelled* as such
1003
1785
  * rather than laundered by association with the checkable ones.
1004
1786
  */
1005
1787
  type MustVerification = "conformance" | "adversarial" | "construction" | "operator";
1788
+ /**
1789
+ * How a MUST is verified — one kind, or several.
1790
+ *
1791
+ * Several is not hedging. `SITES_LOCALLY_APPROVED` is the case that forced it:
1792
+ * the fence is **construction** — a daemon cannot serve a site that is not in
1793
+ * its map, and admission refuses before a payload is fetched — while the
1794
+ * property that a *removed and re-offered* id is still refused needs a hostile
1795
+ * sequence of heartbeats no honest client would send, which is
1796
+ * **adversarial**. Recording one and dropping the other would either overstate
1797
+ * what a type check proves or understate what the suites do.
1798
+ *
1799
+ * The alternative was a second field for the second kind, which is two answers
1800
+ * to one question — the shape this project keeps deleting.
1801
+ */
1802
+ type MustVerifiedBy = MustVerification | readonly [MustVerification, ...MustVerification[]];
1803
+ /** The kinds a MUST claims, always as a list. */
1804
+ declare function kindsOf(must: {
1805
+ readonly verifiedBy: MustVerifiedBy;
1806
+ }): readonly MustVerification[];
1006
1807
  /** A single normative requirement of the protocol. */
1007
1808
  interface Must {
1008
1809
  /** Stable public id, cited by conformance output. */
@@ -1015,7 +1816,7 @@ interface Must {
1015
1816
  * How this is verified. `conformance` is the only kind the kit can assert;
1016
1817
  * see {@link MustVerification} for why the others exist.
1017
1818
  */
1018
- readonly verifiedBy: MustVerification;
1819
+ readonly verifiedBy: MustVerifiedBy;
1019
1820
  /** Spec section this was adjudicated in. */
1020
1821
  readonly source: string;
1021
1822
  }
@@ -1032,6 +1833,8 @@ declare const MUSTS: Readonly<{
1032
1833
  readonly PAIR_INTERACTIVE: Must;
1033
1834
  readonly PAIR_CODE_EXPIRES: Must;
1034
1835
  readonly VERSION_HANDSHAKE_REQUIRED: Must;
1836
+ readonly SITE_KEY_BY_STUB: Must;
1837
+ readonly SITES_LOCALLY_APPROVED: Must;
1035
1838
  readonly KEYS_EXCHANGED_AT_CONSENT: Must;
1036
1839
  readonly REQUESTS_SIGNED_NOT_BEARER: Must;
1037
1840
  readonly LEASE_SCOPED_BY_GRANT: Must;
@@ -1058,24 +1861,74 @@ declare const MUSTS: Readonly<{
1058
1861
  readonly TTL_EXPIRY: Must;
1059
1862
  readonly NO_RUNNER_SIGNAL: Must;
1060
1863
  readonly RESULT_IDEMPOTENT: Must;
1061
- readonly RESULT_PROVENANCE: Must;
1864
+ readonly PROVENANCE_NAMES_DEVICE: Must;
1062
1865
  readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
1063
1866
  readonly NO_SHELL_INTERPOLATION: Must;
1867
+ /**
1868
+ * Amended for byollm_016 Phase B, and the amendment is deliberately narrow.
1869
+ *
1870
+ * A site may now name a **service** on the stub. The temptation is to read
1871
+ * that as a crack in this law, so the statement below says exactly where the
1872
+ * line is: a name selects from a menu the owner published, and resolves to a
1873
+ * model, backend, base URL and flags **only** through that owner's own
1874
+ * config. The site supplies a key; the owner supplies every value it maps
1875
+ * to. A name the owner does not advertise is refused rather than
1876
+ * substituted, because substitution is how "you may pick from my list" turns
1877
+ * into "you may ask for anything and get something".
1878
+ *
1879
+ * Two properties keep it from drifting into "sites demand models":
1880
+ *
1881
+ * 1. **Nothing the site sends is ever a value.** No model string, no URL,
1882
+ * no flag crosses the wire — only a key that means nothing off this
1883
+ * owner's machine.
1884
+ * 2. **It is a stub field, never a payload field.** The prompt cannot
1885
+ * reach it. That is unchanged and is the sentence the second clause
1886
+ * below still enforces verbatim.
1887
+ */
1064
1888
  readonly NO_PAYLOAD_ROUTING: Must;
1065
1889
  readonly STRIPPED_CHILD_ENV: Must;
1066
1890
  readonly HTTP_BASE_URL_SAFE: Must;
1067
1891
  readonly OUTPUT_INERT: Must;
1068
1892
  readonly COMMUNITY_BUDGETS: Must;
1893
+ readonly REVOCATION_IMMEDIATE: Must;
1894
+ readonly CONSENT_BEFORE_ROUTE: Must;
1895
+ readonly ROSTER_NOT_DISCLOSED: Must;
1896
+ readonly EFFECTIVE_OFFER_ONLY: Must;
1897
+ readonly FALLBACK_LABELED: Must;
1898
+ readonly RELAY_BLIND: Must;
1899
+ readonly SHARED_COMPUTE_DISCLOSED: Must;
1069
1900
  }>;
1070
1901
  /** The id of any normative MUST. */
1071
1902
  type MustId = keyof typeof MUSTS;
1072
1903
  /** 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")[];
1904
+ 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
1905
  /** Every MUST verified a particular way. */
1075
1906
  declare function mustsVerifiedBy(kind: MustVerification): MustId[];
1076
1907
 
1077
- /** Protocol version carried on every request; servers refuse what they can't speak. */
1078
- declare const PROTOCOL_VERSION: "0";
1908
+ /**
1909
+ * Protocol version carried on every request; servers refuse what they can't
1910
+ * speak.
1911
+ *
1912
+ * **`1` because byollm_016 changed the vocabulary** — byollm-review
1913
+ * 2026-08-27. `OfferScope` lost `public`, `self|named` became
1914
+ * `private|team`, `JobStub` lost `service` and gained `purpose`, and the
1915
+ * grant's site field changed namespace. The version stayed `0` through all of
1916
+ * it.
1917
+ *
1918
+ * The consequence was the failure the handshake exists to prevent, arriving
1919
+ * around it: a pre-rip daemon declares `0`, passes the version check, and
1920
+ * then fails whole-body schema validation with "request failed schema
1921
+ * validation" — no field named, no vocabulary named, no upgrade command. Once
1922
+ * every ten seconds, forever, while its owner watches a device go stale for
1923
+ * no stated reason. The check below was written because "a mismatch surfaced
1924
+ * as a generic bad-request" and "an error a user cannot act on is barely
1925
+ * better than a hang"; the number not moving is how that came back.
1926
+ *
1927
+ * A registry is a schema and an enum value is the contract — this project's
1928
+ * own words, from the release that silenced a fleet by adding a backend id.
1929
+ * The same sentence applies to removing an offer scope.
1930
+ */
1931
+ declare const PROTOCOL_VERSION: "1";
1079
1932
  /**
1080
1933
  * Every protocol version this build can serve, **oldest first**.
1081
1934
  *
@@ -1083,6 +1936,16 @@ declare const PROTOCOL_VERSION: "0";
1083
1936
  * the check is the point: a server supporting two versions through a
1084
1937
  * migration should not need a different code path from one supporting one.
1085
1938
  */
1939
+ /**
1940
+ * `0` is deliberately **not** here, though the list exists for exactly that.
1941
+ *
1942
+ * Supporting two versions through a migration is the shape this was built
1943
+ * for, and it is the wrong tool when the vocabularies are incompatible: a `0`
1944
+ * daemon sends `offer: "public"` and a `service` on its stubs, so accepting
1945
+ * its version only moves the refusal one layer down — to the schema error
1946
+ * that names nothing, which is the bug. Refusing the version is the whole
1947
+ * point, because that refusal says what to do.
1948
+ */
1086
1949
  declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
1087
1950
  /**
1088
1951
  * The oldest version this build will talk to — derived, not declared.
@@ -1100,6 +1963,23 @@ interface VersionRefusal {
1100
1963
  readonly supported: readonly string[];
1101
1964
  readonly minimum: string;
1102
1965
  }
1966
+ /**
1967
+ * The version a request declares, wherever it carries it.
1968
+ *
1969
+ * A POST declares it in its body, which is where every request schema has
1970
+ * always put it. A GET has no body, and the relay has one — the site plane's
1971
+ * `pending` read — so it declares it in the query string instead.
1972
+ *
1973
+ * **Two carriers, one rule.** That asymmetry is HTTP's rather than ours, and
1974
+ * the alternative was worse in both directions: a header for everything would
1975
+ * change every existing daemon's request, and skipping GETs would leave an
1976
+ * endpoint outside the handshake — which is precisely the shape B.4 found,
1977
+ * where a whole plane was outside it.
1978
+ */
1979
+ declare function declaredVersion(input: {
1980
+ body?: unknown;
1981
+ query?: URLSearchParams;
1982
+ }): unknown;
1103
1983
  /**
1104
1984
  * Check the protocol version on an incoming request
1105
1985
  * ({@link MUSTS.VERSION_HANDSHAKE_REQUIRED}).
@@ -1144,6 +2024,7 @@ declare const Capability: z.ZodObject<{
1144
2024
  "llm.generate": "llm.generate";
1145
2025
  "llm.chat": "llm.chat";
1146
2026
  }>;
2027
+ service: z.ZodString;
1147
2028
  backendId: z.ZodEnum<{
1148
2029
  ollama: "ollama";
1149
2030
  mlx: "mlx";
@@ -1163,6 +2044,7 @@ declare const Capability: z.ZodObject<{
1163
2044
  mistral: "mistral";
1164
2045
  "openai-http": "openai-http";
1165
2046
  "claude-cli": "claude-cli";
2047
+ "codex-cli": "codex-cli";
1166
2048
  }>;
1167
2049
  backendClass: z.ZodEnum<{
1168
2050
  http: "http";
@@ -1170,9 +2052,8 @@ declare const Capability: z.ZodObject<{
1170
2052
  }>;
1171
2053
  model: z.ZodString;
1172
2054
  offerScope: z.ZodEnum<{
1173
- self: "self";
1174
- named: "named";
1175
- public: "public";
2055
+ private: "private";
2056
+ team: "team";
1176
2057
  }>;
1177
2058
  }, z.core.$strict>;
1178
2059
  type Capability = z.infer<typeof Capability>;
@@ -1182,6 +2063,7 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
1182
2063
  "llm.generate": "llm.generate";
1183
2064
  "llm.chat": "llm.chat";
1184
2065
  }>;
2066
+ service: z.ZodString;
1185
2067
  backendId: z.ZodEnum<{
1186
2068
  ollama: "ollama";
1187
2069
  mlx: "mlx";
@@ -1201,6 +2083,7 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
1201
2083
  mistral: "mistral";
1202
2084
  "openai-http": "openai-http";
1203
2085
  "claude-cli": "claude-cli";
2086
+ "codex-cli": "codex-cli";
1204
2087
  }>;
1205
2088
  backendClass: z.ZodEnum<{
1206
2089
  http: "http";
@@ -1208,12 +2091,55 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
1208
2091
  }>;
1209
2092
  model: z.ZodString;
1210
2093
  offerScope: z.ZodEnum<{
1211
- self: "self";
1212
- named: "named";
1213
- public: "public";
2094
+ private: "private";
2095
+ team: "team";
1214
2096
  }>;
1215
2097
  }, z.core.$strict>>;
1216
2098
  type CapabilityMatrix = z.infer<typeof CapabilityMatrix>;
2099
+ /**
2100
+ * A kind this device could serve and deliberately does not — byollm_016.
2101
+ *
2102
+ * Two services answer one kind and the owner has not said which wins, so the
2103
+ * kind is not advertised. That is correct and, unsaid, invisible: the owner
2104
+ * adds a second service, jobs stop matching, and no surface explains it.
2105
+ *
2106
+ * It travels because the surfaces that must say so are not all on the device.
2107
+ * The owner's card names the claimants; a teammate's card says only that the
2108
+ * owner has a choice to make. Claimant **offer scopes** ride along so the hub
2109
+ * can compute that difference without the device deciding who is asking —
2110
+ * carry for computation, filter for display, the same shape the effective
2111
+ * offer already uses.
2112
+ */
2113
+ declare const WithheldKind: z.ZodObject<{
2114
+ kind: z.ZodEnum<{
2115
+ "llm.generate": "llm.generate";
2116
+ "llm.chat": "llm.chat";
2117
+ }>;
2118
+ claimants: z.ZodArray<z.ZodObject<{
2119
+ service: z.ZodString;
2120
+ offer: z.ZodEnum<{
2121
+ private: "private";
2122
+ team: "team";
2123
+ }>;
2124
+ }, z.core.$strict>>;
2125
+ }, z.core.$strict>;
2126
+ type WithheldKind = z.infer<typeof WithheldKind>;
2127
+ /**
2128
+ * One grant, named by both halves — V1-3.
2129
+ *
2130
+ * A job id is chosen per site, so the lease id is the unique thing an upstream
2131
+ * and a daemon can both point at. Anywhere a request says "this piece of work,
2132
+ * held by me", it says it with both.
2133
+ *
2134
+ * Declared once because it was written out twice — `activeLeases` and
2135
+ * `ReleaseRequest.leases` — and both needed the same `.strict()` added. Two
2136
+ * copies of a shape are two places to forget it.
2137
+ */
2138
+ declare const GrantRef: z.ZodObject<{
2139
+ jobId: z.ZodString;
2140
+ leaseId: z.ZodString;
2141
+ }, z.core.$strict>;
2142
+ type GrantRef = z.infer<typeof GrantRef>;
1217
2143
  /**
1218
2144
  * Pairing is a device-code exchange, not a pasted secret
1219
2145
  * ({@link MUSTS.PAIR_INTERACTIVE}). The daemon starts a pairing, shows the
@@ -1222,7 +2148,7 @@ type CapabilityMatrix = z.infer<typeof CapabilityMatrix>;
1222
2148
  * and nothing works over a copied string alone.
1223
2149
  */
1224
2150
  declare const PairStartRequest: z.ZodObject<{
1225
- protocolVersion: z.ZodLiteral<"0">;
2151
+ protocolVersion: z.ZodLiteral<"1">;
1226
2152
  action: z.ZodLiteral<"start">;
1227
2153
  daemon: z.ZodObject<{
1228
2154
  version: z.ZodString;
@@ -1232,7 +2158,7 @@ declare const PairStartRequest: z.ZodObject<{
1232
2158
  linux: "linux";
1233
2159
  win32: "win32";
1234
2160
  }>;
1235
- }, z.core.$strip>;
2161
+ }, z.core.$strict>;
1236
2162
  device: z.ZodObject<{
1237
2163
  identity: z.ZodString;
1238
2164
  encryption: z.ZodString;
@@ -1243,6 +2169,7 @@ declare const PairStartRequest: z.ZodObject<{
1243
2169
  "llm.generate": "llm.generate";
1244
2170
  "llm.chat": "llm.chat";
1245
2171
  }>;
2172
+ service: z.ZodString;
1246
2173
  backendId: z.ZodEnum<{
1247
2174
  ollama: "ollama";
1248
2175
  mlx: "mlx";
@@ -1262,6 +2189,7 @@ declare const PairStartRequest: z.ZodObject<{
1262
2189
  mistral: "mistral";
1263
2190
  "openai-http": "openai-http";
1264
2191
  "claude-cli": "claude-cli";
2192
+ "codex-cli": "codex-cli";
1265
2193
  }>;
1266
2194
  backendClass: z.ZodEnum<{
1267
2195
  http: "http";
@@ -1269,9 +2197,8 @@ declare const PairStartRequest: z.ZodObject<{
1269
2197
  }>;
1270
2198
  model: z.ZodString;
1271
2199
  offerScope: z.ZodEnum<{
1272
- self: "self";
1273
- named: "named";
1274
- public: "public";
2200
+ private: "private";
2201
+ team: "team";
1275
2202
  }>;
1276
2203
  }, z.core.$strict>>;
1277
2204
  }, z.core.$strict>;
@@ -1285,7 +2212,7 @@ declare const PairStartResponse: z.ZodObject<{
1285
2212
  }, z.core.$strict>;
1286
2213
  type PairStartResponse = z.infer<typeof PairStartResponse>;
1287
2214
  declare const PairPollRequest: z.ZodObject<{
1288
- protocolVersion: z.ZodLiteral<"0">;
2215
+ protocolVersion: z.ZodLiteral<"1">;
1289
2216
  action: z.ZodLiteral<"poll">;
1290
2217
  deviceCode: z.ZodString;
1291
2218
  }, z.core.$strict>;
@@ -1298,19 +2225,19 @@ declare const PairPollResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
1298
2225
  status: z.ZodLiteral<"expired">;
1299
2226
  }, z.core.$strict>, z.ZodObject<{
1300
2227
  status: z.ZodLiteral<"approved">;
1301
- runnerToken: z.ZodString;
1302
2228
  runnerId: z.ZodString;
1303
2229
  owner: z.ZodString;
1304
2230
  ownerLabel: z.ZodOptional<z.ZodString>;
1305
- site: z.ZodObject<{
2231
+ sites: z.ZodRecord<z.ZodString, z.ZodObject<{
1306
2232
  identity: z.ZodString;
1307
2233
  encryption: z.ZodString;
1308
2234
  encryptionSig: z.ZodString;
1309
- }, z.core.$strict>;
2235
+ }, z.core.$strict>>;
2236
+ controlPlanePublic: z.ZodOptional<z.ZodString>;
1310
2237
  }, z.core.$strict>], "status">;
1311
2238
  type PairPollResponse = z.infer<typeof PairPollResponse>;
1312
2239
  declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
1313
- protocolVersion: z.ZodLiteral<"0">;
2240
+ protocolVersion: z.ZodLiteral<"1">;
1314
2241
  action: z.ZodLiteral<"start">;
1315
2242
  daemon: z.ZodObject<{
1316
2243
  version: z.ZodString;
@@ -1320,7 +2247,7 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
1320
2247
  linux: "linux";
1321
2248
  win32: "win32";
1322
2249
  }>;
1323
- }, z.core.$strip>;
2250
+ }, z.core.$strict>;
1324
2251
  device: z.ZodObject<{
1325
2252
  identity: z.ZodString;
1326
2253
  encryption: z.ZodString;
@@ -1331,6 +2258,7 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
1331
2258
  "llm.generate": "llm.generate";
1332
2259
  "llm.chat": "llm.chat";
1333
2260
  }>;
2261
+ service: z.ZodString;
1334
2262
  backendId: z.ZodEnum<{
1335
2263
  ollama: "ollama";
1336
2264
  mlx: "mlx";
@@ -1350,6 +2278,7 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
1350
2278
  mistral: "mistral";
1351
2279
  "openai-http": "openai-http";
1352
2280
  "claude-cli": "claude-cli";
2281
+ "codex-cli": "codex-cli";
1353
2282
  }>;
1354
2283
  backendClass: z.ZodEnum<{
1355
2284
  http: "http";
@@ -1357,25 +2286,25 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
1357
2286
  }>;
1358
2287
  model: z.ZodString;
1359
2288
  offerScope: z.ZodEnum<{
1360
- self: "self";
1361
- named: "named";
1362
- public: "public";
2289
+ private: "private";
2290
+ team: "team";
1363
2291
  }>;
1364
2292
  }, z.core.$strict>>;
1365
2293
  }, z.core.$strict>, z.ZodObject<{
1366
- protocolVersion: z.ZodLiteral<"0">;
2294
+ protocolVersion: z.ZodLiteral<"1">;
1367
2295
  action: z.ZodLiteral<"poll">;
1368
2296
  deviceCode: z.ZodString;
1369
2297
  }, z.core.$strict>], "action">;
1370
2298
  type PairRequest = z.infer<typeof PairRequest>;
1371
2299
  declare const ClaimRequest: z.ZodObject<{
1372
- protocolVersion: z.ZodLiteral<"0">;
2300
+ protocolVersion: z.ZodLiteral<"1">;
1373
2301
  runnerId: z.ZodString;
1374
2302
  capabilities: z.ZodArray<z.ZodObject<{
1375
2303
  kind: z.ZodEnum<{
1376
2304
  "llm.generate": "llm.generate";
1377
2305
  "llm.chat": "llm.chat";
1378
2306
  }>;
2307
+ service: z.ZodString;
1379
2308
  backendId: z.ZodEnum<{
1380
2309
  ollama: "ollama";
1381
2310
  mlx: "mlx";
@@ -1395,6 +2324,7 @@ declare const ClaimRequest: z.ZodObject<{
1395
2324
  mistral: "mistral";
1396
2325
  "openai-http": "openai-http";
1397
2326
  "claude-cli": "claude-cli";
2327
+ "codex-cli": "codex-cli";
1398
2328
  }>;
1399
2329
  backendClass: z.ZodEnum<{
1400
2330
  http: "http";
@@ -1402,9 +2332,8 @@ declare const ClaimRequest: z.ZodObject<{
1402
2332
  }>;
1403
2333
  model: z.ZodString;
1404
2334
  offerScope: z.ZodEnum<{
1405
- self: "self";
1406
- named: "named";
1407
- public: "public";
2335
+ private: "private";
2336
+ team: "team";
1408
2337
  }>;
1409
2338
  }, z.core.$strict>>;
1410
2339
  max: z.ZodNumber;
@@ -1418,12 +2347,12 @@ declare const ClaimResponse: z.ZodObject<{
1418
2347
  "llm.chat": "llm.chat";
1419
2348
  }>;
1420
2349
  owner: z.ZodString;
2350
+ site: z.ZodString;
1421
2351
  audience: z.ZodEnum<{
1422
- self: "self";
1423
- named: "named";
1424
- public: "public";
2352
+ private: "private";
2353
+ team: "team";
1425
2354
  }>;
1426
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
2355
+ purpose: z.ZodOptional<z.ZodString>;
1427
2356
  sizeClass: z.ZodEnum<{
1428
2357
  small: "small";
1429
2358
  medium: "medium";
@@ -1436,13 +2365,25 @@ declare const ClaimResponse: z.ZodObject<{
1436
2365
  id: z.ZodString;
1437
2366
  runnerId: z.ZodString;
1438
2367
  expiresAt: z.ZodNumber;
1439
- }, z.core.$strip>;
2368
+ }, z.core.$strict>;
2369
+ grant: z.ZodOptional<z.ZodObject<{
2370
+ grantId: z.ZodString;
2371
+ jobId: z.ZodString;
2372
+ site: z.ZodString;
2373
+ user: z.ZodString;
2374
+ owner: z.ZodString;
2375
+ purpose: z.ZodString;
2376
+ kind: z.ZodString;
2377
+ service: z.ZodString;
2378
+ issuedAt: z.ZodNumber;
2379
+ signature: z.ZodString;
2380
+ }, z.core.$strict>>;
1440
2381
  }, z.core.$strict>>;
1441
2382
  leaseMs: z.ZodNumber;
1442
2383
  }, z.core.$strict>;
1443
2384
  type ClaimResponse = z.infer<typeof ClaimResponse>;
1444
2385
  declare const HeartbeatRequest: z.ZodObject<{
1445
- protocolVersion: z.ZodLiteral<"0">;
2386
+ protocolVersion: z.ZodLiteral<"1">;
1446
2387
  runnerId: z.ZodString;
1447
2388
  daemonVersion: z.ZodString;
1448
2389
  capabilities: z.ZodArray<z.ZodObject<{
@@ -1450,6 +2391,7 @@ declare const HeartbeatRequest: z.ZodObject<{
1450
2391
  "llm.generate": "llm.generate";
1451
2392
  "llm.chat": "llm.chat";
1452
2393
  }>;
2394
+ service: z.ZodString;
1453
2395
  backendId: z.ZodEnum<{
1454
2396
  ollama: "ollama";
1455
2397
  mlx: "mlx";
@@ -1469,6 +2411,7 @@ declare const HeartbeatRequest: z.ZodObject<{
1469
2411
  mistral: "mistral";
1470
2412
  "openai-http": "openai-http";
1471
2413
  "claude-cli": "claude-cli";
2414
+ "codex-cli": "codex-cli";
1472
2415
  }>;
1473
2416
  backendClass: z.ZodEnum<{
1474
2417
  http: "http";
@@ -1476,27 +2419,57 @@ declare const HeartbeatRequest: z.ZodObject<{
1476
2419
  }>;
1477
2420
  model: z.ZodString;
1478
2421
  offerScope: z.ZodEnum<{
1479
- self: "self";
1480
- named: "named";
1481
- public: "public";
2422
+ private: "private";
2423
+ team: "team";
1482
2424
  }>;
1483
2425
  }, z.core.$strict>>;
2426
+ withheld: z.ZodDefault<z.ZodArray<z.ZodObject<{
2427
+ kind: z.ZodEnum<{
2428
+ "llm.generate": "llm.generate";
2429
+ "llm.chat": "llm.chat";
2430
+ }>;
2431
+ claimants: z.ZodArray<z.ZodObject<{
2432
+ service: z.ZodString;
2433
+ offer: z.ZodEnum<{
2434
+ private: "private";
2435
+ team: "team";
2436
+ }>;
2437
+ }, z.core.$strict>>;
2438
+ }, z.core.$strict>>>;
1484
2439
  activeLeases: z.ZodArray<z.ZodObject<{
1485
2440
  jobId: z.ZodString;
1486
2441
  leaseId: z.ZodString;
1487
- }, z.core.$strip>>;
2442
+ }, z.core.$strict>>;
1488
2443
  paused: z.ZodBoolean;
1489
2444
  }, z.core.$strict>;
1490
2445
  type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;
1491
2446
  declare const HeartbeatResponse: z.ZodObject<{
1492
- revoked: z.ZodBoolean;
1493
- cancel: z.ZodArray<z.ZodString>;
1494
- leases: z.ZodArray<z.ZodObject<{
2447
+ sites: z.ZodRecord<z.ZodString, z.ZodObject<{
2448
+ identity: z.ZodString;
2449
+ encryption: z.ZodString;
2450
+ encryptionSig: z.ZodString;
2451
+ }, z.core.$strict>>;
2452
+ successions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
2453
+ succeeds: z.ZodArray<z.ZodObject<{
2454
+ identity: z.ZodObject<{
2455
+ identity: z.ZodString;
2456
+ encryption: z.ZodString;
2457
+ encryptionSig: z.ZodString;
2458
+ }, z.core.$strict>;
2459
+ signature: z.ZodString;
2460
+ }, z.core.$strict>>;
2461
+ retiringUntil: z.ZodOptional<z.ZodNumber>;
2462
+ }, z.core.$strict>>>;
2463
+ cancel: z.ZodArray<z.ZodObject<{
1495
2464
  jobId: z.ZodString;
1496
- expiresAt: z.ZodNumber;
2465
+ leaseId: z.ZodString;
2466
+ }, z.core.$strict>>;
2467
+ lost: z.ZodArray<z.ZodObject<{
2468
+ jobId: z.ZodString;
2469
+ leaseId: z.ZodString;
1497
2470
  }, z.core.$strict>>;
1498
- lost: z.ZodArray<z.ZodString>;
1499
2471
  serverTime: z.ZodNumber;
2472
+ awaitingConsent: z.ZodArray<z.ZodString>;
1500
2473
  }, z.core.$strict>;
1501
2474
  type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
1502
2475
  /**
@@ -1518,9 +2491,10 @@ declare const ResultDisposition: z.ZodEnum<{
1518
2491
  }>;
1519
2492
  type ResultDisposition = z.infer<typeof ResultDisposition>;
1520
2493
  declare const ResultRequest: z.ZodObject<{
1521
- protocolVersion: z.ZodLiteral<"0">;
2494
+ protocolVersion: z.ZodLiteral<"1">;
1522
2495
  runnerId: z.ZodString;
1523
2496
  jobId: z.ZodString;
2497
+ leaseId: z.ZodString;
1524
2498
  envelope: z.ZodObject<{
1525
2499
  ciphertext: z.ZodString;
1526
2500
  recipientKeyId: z.ZodString;
@@ -1536,32 +2510,27 @@ declare const ResultRequest: z.ZodObject<{
1536
2510
  error: "error";
1537
2511
  canceled: "canceled";
1538
2512
  }>;
1539
- model: z.ZodString;
1540
- backendClass: z.ZodEnum<{
1541
- http: "http";
1542
- process: "process";
1543
- }>;
1544
- durationMs: z.ZodNumber;
1545
2513
  }, z.core.$strict>;
1546
2514
  type ResultRequest = z.infer<typeof ResultRequest>;
1547
2515
  declare const ResultResponse: z.ZodObject<{
1548
2516
  accepted: z.ZodBoolean;
2517
+ duplicate: z.ZodOptional<z.ZodBoolean>;
1549
2518
  state: z.ZodString;
1550
2519
  }, z.core.$strict>;
1551
2520
  type ResultResponse = z.infer<typeof ResultResponse>;
1552
2521
  declare const ReleaseRequest: z.ZodObject<{
1553
- protocolVersion: z.ZodLiteral<"0">;
2522
+ protocolVersion: z.ZodLiteral<"1">;
1554
2523
  runnerId: z.ZodString;
1555
2524
  leases: z.ZodArray<z.ZodObject<{
1556
2525
  jobId: z.ZodString;
1557
2526
  leaseId: z.ZodString;
1558
- }, z.core.$strip>>;
2527
+ }, z.core.$strict>>;
1559
2528
  reason: z.ZodEnum<{
1560
- revoked: "revoked";
2529
+ refused: "refused";
1561
2530
  shutdown: "shutdown";
1562
2531
  pause: "pause";
2532
+ revoked: "revoked";
1563
2533
  "backend-down": "backend-down";
1564
- refused: "refused";
1565
2534
  }>;
1566
2535
  }, z.core.$strict>;
1567
2536
  type ReleaseRequest = z.infer<typeof ReleaseRequest>;
@@ -1582,7 +2551,11 @@ declare const WireErrorCode: z.ZodEnum<{
1582
2551
  revoked: "revoked";
1583
2552
  "bad-request": "bad-request";
1584
2553
  unauthorized: "unauthorized";
2554
+ forbidden: "forbidden";
1585
2555
  "not-found": "not-found";
2556
+ "not-ready": "not-ready";
2557
+ "too-late": "too-late";
2558
+ "clock-skew": "clock-skew";
1586
2559
  "rate-limited": "rate-limited";
1587
2560
  "server-error": "server-error";
1588
2561
  }>;
@@ -1593,18 +2566,26 @@ declare const WireError: z.ZodObject<{
1593
2566
  revoked: "revoked";
1594
2567
  "bad-request": "bad-request";
1595
2568
  unauthorized: "unauthorized";
2569
+ forbidden: "forbidden";
1596
2570
  "not-found": "not-found";
2571
+ "not-ready": "not-ready";
2572
+ "too-late": "too-late";
2573
+ "clock-skew": "clock-skew";
1597
2574
  "rate-limited": "rate-limited";
1598
2575
  "server-error": "server-error";
1599
2576
  }>;
1600
2577
  message: z.ZodString;
2578
+ supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
2579
+ minimum: z.ZodOptional<z.ZodString>;
1601
2580
  retryAfter: z.ZodOptional<z.ZodNumber>;
2581
+ serverTime: z.ZodOptional<z.ZodNumber>;
2582
+ maxSkewMs: z.ZodOptional<z.ZodNumber>;
1602
2583
  }, z.core.$strict>;
1603
2584
  type WireError = z.infer<typeof WireError>;
1604
2585
  /** HTTP status each error code is served with. */
1605
2586
  declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
1606
2587
  declare const FetchRequest: z.ZodObject<{
1607
- protocolVersion: z.ZodString;
2588
+ protocolVersion: z.ZodLiteral<"1">;
1608
2589
  runnerId: z.ZodString;
1609
2590
  jobId: z.ZodString;
1610
2591
  leaseId: z.ZodString;
@@ -1624,4 +2605,4 @@ declare const FetchResponse: z.ZodObject<{
1624
2605
  }, z.core.$strict>;
1625
2606
  type FetchResponse = z.infer<typeof FetchResponse>;
1626
2607
 
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 };
2608
+ export { 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_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, 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 };