@byollm/protocol 0.1.0-alpha.0

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.
@@ -0,0 +1,930 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * How a backend reaches its model — the taxonomy introduced in byollm_001
5
+ * Rev 1 §A, because the two classes have different threat surfaces.
6
+ *
7
+ * - `http`: an OpenAI-compatible HTTP server (Ollama, `mlx_lm.server`,
8
+ * llama.cpp server, vLLM). Spawns nothing, so byollm_004 §2's argv, stdin,
9
+ * env and sandbox requirements are not applicable by construction. Its
10
+ * threat surface is SSRF-shaped and bounded by {@link MUSTS.HTTP_BASE_URL_SAFE}.
11
+ * - `process`: spawns a binary (`claude` CLI today, `mlx_lm.lora` for a
12
+ * future `train.*` kind). All of byollm_004 §2 is mandatory here.
13
+ */
14
+ declare const BackendClass: z.ZodEnum<{
15
+ http: "http";
16
+ process: "process";
17
+ }>;
18
+ type BackendClass = z.infer<typeof BackendClass>;
19
+ /**
20
+ * Whose account pays for the inference.
21
+ *
22
+ * `subscription` backends run against a vendor account belonging to the
23
+ * machine's owner. They are hard-locked to an offer scope of `self`
24
+ * ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — one account executes one person's
25
+ * work. This is orthogonal to {@link BackendClass}: `claude-cli` is both
26
+ * process-class and subscription-class, while a future local `mlx_lm.lora`
27
+ * backend would be process-class and open.
28
+ */
29
+ declare const BackendAccount: z.ZodEnum<{
30
+ open: "open";
31
+ subscription: "subscription";
32
+ }>;
33
+ type BackendAccount = z.infer<typeof BackendAccount>;
34
+ /** The immutable facts about a backend that the protocol reasons over. */
35
+ interface BackendDescriptor {
36
+ /** Stable backend id, as written in `byollm.config.json`. */
37
+ readonly id: string;
38
+ /** Human-readable name for the trust UI. */
39
+ readonly label: string;
40
+ /** Determines which isolation requirements apply. */
41
+ readonly class: BackendClass;
42
+ /** Determines whether the offer scope can be widened past `self`. */
43
+ readonly account: BackendAccount;
44
+ /**
45
+ * Which adversarial corpus byollm_004 §5 runs against this backend. A
46
+ * backend cannot be registered without one — the coverage check in the
47
+ * adversarial suite enforces it.
48
+ */
49
+ readonly adversarialCorpus: "process" | "http";
50
+ }
51
+ /**
52
+ * The v1 backend registry.
53
+ *
54
+ * byollm_001 Rev 1 §A collapses four planned backends into one HTTP-class
55
+ * entry: Ollama, `mlx_lm.server`, llama.cpp server and vLLM all speak
56
+ * OpenAI-compatible `/v1/chat/completions`, so they are one backend with N
57
+ * owner-configured base URLs rather than four adapters. That is what puts
58
+ * MLX inference in v1.
59
+ */
60
+ declare const BACKENDS: Readonly<{
61
+ readonly "openai-http": BackendDescriptor;
62
+ readonly "claude-cli": BackendDescriptor;
63
+ }>;
64
+ /** The id of a registered backend. */
65
+ type BackendId = keyof typeof BACKENDS;
66
+ /** All registered backend ids — the adversarial coverage check iterates this. */
67
+ declare const BACKEND_IDS: readonly ("openai-http" | "claude-cli")[];
68
+ declare const BackendIdSchema: z.ZodEnum<{
69
+ "openai-http": "openai-http";
70
+ "claude-cli": "claude-cli";
71
+ }>;
72
+ /** Narrow an arbitrary string to a registered backend id. */
73
+ declare function isBackendId(value: string): value is BackendId;
74
+ /**
75
+ * Look up a backend descriptor.
76
+ *
77
+ * @throws if the id is not registered — an unregistered backend has no
78
+ * adversarial corpus, so refusing is the safe direction.
79
+ */
80
+ declare function backendDescriptor(id: BackendId): BackendDescriptor;
81
+
82
+ /**
83
+ * Who may run a job, declared by the app that enqueued it.
84
+ *
85
+ * - `self` — only the job owner's own daemon.
86
+ * - `named` — a daemon whose owner has explicitly allowed this (server, user)
87
+ * pair in their *local* allowlist (byollm_001 Rev 1 §B).
88
+ * - `public` — any daemon offering `public` compute.
89
+ */
90
+ declare const Audience: z.ZodEnum<{
91
+ self: "self";
92
+ named: "named";
93
+ public: "public";
94
+ }>;
95
+ type Audience = z.infer<typeof Audience>;
96
+ /**
97
+ * What a daemon backend is willing to run, declared by the machine's owner.
98
+ * Same three values as {@link Audience}, but the two are independent axes —
99
+ * a job runs only where both agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}).
100
+ */
101
+ declare const OfferScope: z.ZodEnum<{
102
+ self: "self";
103
+ named: "named";
104
+ public: "public";
105
+ }>;
106
+ type OfferScope = z.infer<typeof OfferScope>;
107
+ /** All audience values, in widening order. */
108
+ declare const AUDIENCES: readonly ("self" | "named" | "public")[];
109
+ /** All offer scopes, in widening order. */
110
+ declare const OFFER_SCOPES: readonly ("self" | "named" | "public")[];
111
+ /**
112
+ * Why a job was refused. Distinct codes because byollm_002 requires that
113
+ * different truths never share a message — "no matching work" and "refused on
114
+ * principle" are not the same event, and a volunteer debugging their setup
115
+ * needs to know which one happened.
116
+ */
117
+ declare const MatchRefusal: z.ZodEnum<{
118
+ "no-capability": "no-capability";
119
+ "audience-self-other-owner": "audience-self-other-owner";
120
+ "not-locally-allowed": "not-locally-allowed";
121
+ "not-in-server-allowlist": "not-in-server-allowlist";
122
+ "offer-scope-too-narrow": "offer-scope-too-narrow";
123
+ "subscription-self-lock": "subscription-self-lock";
124
+ }>;
125
+ type MatchRefusal = z.infer<typeof MatchRefusal>;
126
+ /** The outcome of an audience match. */
127
+ type MatchResult = {
128
+ readonly ok: true;
129
+ } | {
130
+ readonly ok: false;
131
+ readonly refusal: MatchRefusal;
132
+ };
133
+ /**
134
+ * The effective offer scope of a backend.
135
+ *
136
+ * A subscription-class backend is locked to `self` regardless of what config
137
+ * requests ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}). This is a protocol MUST,
138
+ * not a setting: the lock is applied here, at the one place both the daemon's
139
+ * config loader and its matcher call, so there is no code path that observes
140
+ * a widened subscription scope.
141
+ */
142
+ declare function effectiveOfferScope(configured: OfferScope, account: BackendAccount): OfferScope;
143
+ /** The job-side facts a match needs. */
144
+ interface MatchJob {
145
+ /** The app's id for the user who enqueued the job. */
146
+ readonly owner: string;
147
+ /** Who the app says may run it. */
148
+ readonly audience: Audience;
149
+ /**
150
+ * Optional server-side restriction on which runner owners may take a
151
+ * `named` job. Defence in depth only — the daemon's local allowlist is the
152
+ * enforcing side ({@link MUSTS.NAMED_LOCAL_ALLOWLIST}).
153
+ */
154
+ readonly audienceAllow?: readonly string[] | undefined;
155
+ }
156
+ /** The daemon-side facts a match needs. */
157
+ interface MatchDaemon {
158
+ /** The app's id for the user this daemon is paired to. */
159
+ readonly owner: string;
160
+ /** Effective scope of the backend that would run the job. */
161
+ readonly offerScope: OfferScope;
162
+ /** Account class of that backend. */
163
+ readonly account: BackendAccount;
164
+ /**
165
+ * Does this daemon's *local* allowlist admit the given owner for the server
166
+ * origin the job came from? Supplied as a predicate so the protocol package
167
+ * stays free of file I/O; the daemon passes its allowlist, the server
168
+ * passes a conservative `() => true` because it cannot know a remote
169
+ * daemon's local list and must not pretend to.
170
+ */
171
+ readonly locallyAllows: (owner: string) => boolean;
172
+ }
173
+ /**
174
+ * Decide whether a job may run on a daemon.
175
+ *
176
+ * Both sides must agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}):
177
+ * 1. the job's audience must admit the daemon's owner, and
178
+ * 2. the backend's offer scope must admit the job's owner.
179
+ *
180
+ * The full nine-way matrix (three audiences × three offer scopes) is asserted
181
+ * by the conformance kit. The function is pure and total so both the daemon
182
+ * and the server can run the identical rule — the daemon refuses, and the
183
+ * server refuses too (byollm_003 §Server-side MUSTs).
184
+ *
185
+ * @example
186
+ * ```ts
187
+ * const result = matchAudience(
188
+ * { owner: "alice", audience: "named" },
189
+ * {
190
+ * owner: "bob",
191
+ * offerScope: "named",
192
+ * account: "open",
193
+ * locallyAllows: (o) => o === "alice",
194
+ * },
195
+ * );
196
+ * // result.ok === true
197
+ * ```
198
+ */
199
+ declare function matchAudience(job: MatchJob, daemon: MatchDaemon): MatchResult;
200
+ /**
201
+ * Human-readable refusal text for the daemon's log and the trust UI.
202
+ * Each refusal reads as a distinct truth — byollm_002's "four different
203
+ * truths that must never share a message" applied to the audience axis.
204
+ */
205
+ declare const REFUSAL_MESSAGES: Readonly<Record<MatchRefusal, string>>;
206
+
207
+ /**
208
+ * Upper bounds on payload size, enforced at the schema so oversized input is
209
+ * refused at parse time rather than somewhere deeper.
210
+ *
211
+ * byollm_004 §4 requires stricter limits for community (`named`/`public`)
212
+ * jobs; those are applied on top of these by the daemon's budget check, which
213
+ * knows the job's audience. These are the absolute ceilings for any job.
214
+ */
215
+ declare const PAYLOAD_LIMITS: Readonly<{
216
+ /** Max characters in any single text field. */
217
+ maxTextChars: 1000000;
218
+ /** Max messages in an `llm.chat` conversation. */
219
+ maxMessages: 256;
220
+ /** Max characters across the whole payload. */
221
+ maxTotalChars: 4000000;
222
+ }>;
223
+ /**
224
+ * A conversation turn. `role` is a closed enum — it is routing *within the
225
+ * model call*, not routing of the call, so it cannot select a backend.
226
+ */
227
+ declare const ChatMessage: z.ZodObject<{
228
+ role: z.ZodEnum<{
229
+ system: "system";
230
+ user: "user";
231
+ assistant: "assistant";
232
+ }>;
233
+ content: z.ZodString;
234
+ }, z.core.$strip>;
235
+ type ChatMessage = z.infer<typeof ChatMessage>;
236
+ /**
237
+ * Payload for `llm.generate`.
238
+ *
239
+ * @remarks
240
+ * Text only, deliberately. byollm_004 §1 states the payload is "data handed
241
+ * to a model, never configuration and never a command", so v0 carries no
242
+ * sampling parameters, no model name, no base URL and no flags — those are
243
+ * owner-side route config. A future `params` field with an explicit closed
244
+ * allowlist and owner-set clamps is reserved; adding a field later is
245
+ * non-breaking, removing one is not.
246
+ */
247
+ declare const GeneratePayload: z.ZodObject<{
248
+ prompt: z.ZodString;
249
+ system: z.ZodOptional<z.ZodString>;
250
+ }, z.core.$strict>;
251
+ type GeneratePayload = z.infer<typeof GeneratePayload>;
252
+ /** Payload for `llm.chat`. Text only, for the same reason as {@link GeneratePayload}. */
253
+ declare const ChatPayload: z.ZodObject<{
254
+ messages: z.ZodArray<z.ZodObject<{
255
+ role: z.ZodEnum<{
256
+ system: "system";
257
+ user: "user";
258
+ assistant: "assistant";
259
+ }>;
260
+ content: z.ZodString;
261
+ }, z.core.$strip>>;
262
+ system: z.ZodOptional<z.ZodString>;
263
+ }, z.core.$strict>;
264
+ type ChatPayload = z.infer<typeof ChatPayload>;
265
+ /**
266
+ * The job kinds a v1 daemon has handlers for.
267
+ *
268
+ * Kinds are resolved against handlers baked into the daemon
269
+ * ({@link MUSTS.KIND_TYPED_ONLY}); an unknown kind is refused, never guessed.
270
+ * Adding a kind is a protocol change with its own spec and threat review —
271
+ * notably any kind that needs tools, which byollm_004 §2 forbids as a payload
272
+ * flag.
273
+ */
274
+ declare const JobKind: z.ZodEnum<{
275
+ "llm.generate": "llm.generate";
276
+ "llm.chat": "llm.chat";
277
+ }>;
278
+ type JobKind = z.infer<typeof JobKind>;
279
+ /** All v1 job kinds. */
280
+ declare const JOB_KINDS: readonly ("llm.generate" | "llm.chat")[];
281
+ /** A payload discriminated by its kind. */
282
+ declare const KindedPayload: z.ZodDiscriminatedUnion<[z.ZodObject<{
283
+ kind: z.ZodLiteral<"llm.generate">;
284
+ payload: z.ZodObject<{
285
+ prompt: z.ZodString;
286
+ system: z.ZodOptional<z.ZodString>;
287
+ }, z.core.$strict>;
288
+ }, z.core.$strip>, z.ZodObject<{
289
+ kind: z.ZodLiteral<"llm.chat">;
290
+ payload: z.ZodObject<{
291
+ messages: z.ZodArray<z.ZodObject<{
292
+ role: z.ZodEnum<{
293
+ system: "system";
294
+ user: "user";
295
+ assistant: "assistant";
296
+ }>;
297
+ content: z.ZodString;
298
+ }, z.core.$strip>>;
299
+ system: z.ZodOptional<z.ZodString>;
300
+ }, z.core.$strict>;
301
+ }, z.core.$strip>], "kind">;
302
+ type KindedPayload = z.infer<typeof KindedPayload>;
303
+ /** The payload type for a given kind. */
304
+ type PayloadFor<K extends JobKind> = K extends "llm.generate" ? GeneratePayload : ChatPayload;
305
+ /** Narrow an arbitrary string to a known job kind. */
306
+ declare function isJobKind(value: string): value is JobKind;
307
+ /**
308
+ * Total character weight of a payload, used by the daemon's community budget
309
+ * check and by the server's payload-size limits.
310
+ */
311
+ declare function payloadTextLength(kinded: KindedPayload): number;
312
+
313
+ /**
314
+ * The job lifecycle, made explicit by byollm_001 Rev 1 §D because the most
315
+ * user-visible failure mode — "nothing is running my job" — was previously
316
+ * unspecified.
317
+ *
318
+ * ```text
319
+ * queued ──claim──▶ claimed ──start──▶ running ──▶ ok | error | canceled
320
+ * │ │ │
321
+ * │ └──lease expiry─────┘
322
+ * │ ▼
323
+ * │ queued (reclaimable, no loss)
324
+ * └──ttl elapsed──▶ expired
325
+ * ```
326
+ */
327
+ declare const JobState: z.ZodEnum<{
328
+ ok: "ok";
329
+ error: "error";
330
+ queued: "queued";
331
+ claimed: "claimed";
332
+ running: "running";
333
+ canceled: "canceled";
334
+ expired: "expired";
335
+ }>;
336
+ type JobState = z.infer<typeof JobState>;
337
+ /** States from which a job never moves again. */
338
+ declare const TERMINAL_STATES: readonly ["ok", "error", "canceled", "expired"];
339
+ /** Is this a state the job can never leave? */
340
+ declare function isTerminal(state: JobState): boolean;
341
+ /** May a job move from `from` to `to`? */
342
+ declare function canTransition(from: JobState, to: JobState): boolean;
343
+ /** A lease: the right to work on a job until `expiresAt`. */
344
+ declare const Lease: z.ZodObject<{
345
+ runnerId: z.ZodString;
346
+ expiresAt: z.ZodNumber;
347
+ }, z.core.$strip>;
348
+ type Lease = z.infer<typeof Lease>;
349
+ /** Payload union as it appears on a job record. */
350
+ declare const JobPayload: z.ZodUnion<readonly [z.ZodObject<{
351
+ prompt: z.ZodString;
352
+ system: z.ZodOptional<z.ZodString>;
353
+ }, z.core.$strict>, z.ZodObject<{
354
+ messages: z.ZodArray<z.ZodObject<{
355
+ role: z.ZodEnum<{
356
+ system: "system";
357
+ user: "user";
358
+ assistant: "assistant";
359
+ }>;
360
+ content: z.ZodString;
361
+ }, z.core.$strip>>;
362
+ system: z.ZodOptional<z.ZodString>;
363
+ }, z.core.$strict>]>;
364
+ type JobPayload = z.infer<typeof JobPayload>;
365
+ /**
366
+ * A job as the daemon receives it from `/byollm/claim`.
367
+ *
368
+ * Note what is absent: no model, no backend, no base URL, no flags, no path.
369
+ * Those come from the machine owner's config only
370
+ * ({@link MUSTS.NO_PAYLOAD_ROUTING}). The wire shape is the first place that
371
+ * rule is enforced — there is no field to carry them.
372
+ */
373
+ declare const ClaimedJob: z.ZodObject<{
374
+ id: z.ZodString;
375
+ kind: z.ZodEnum<{
376
+ "llm.generate": "llm.generate";
377
+ "llm.chat": "llm.chat";
378
+ }>;
379
+ payload: z.ZodUnion<readonly [z.ZodObject<{
380
+ prompt: z.ZodString;
381
+ system: z.ZodOptional<z.ZodString>;
382
+ }, z.core.$strict>, z.ZodObject<{
383
+ messages: z.ZodArray<z.ZodObject<{
384
+ role: z.ZodEnum<{
385
+ system: "system";
386
+ user: "user";
387
+ assistant: "assistant";
388
+ }>;
389
+ content: z.ZodString;
390
+ }, z.core.$strip>>;
391
+ system: z.ZodOptional<z.ZodString>;
392
+ }, z.core.$strict>]>;
393
+ audience: z.ZodEnum<{
394
+ self: "self";
395
+ named: "named";
396
+ public: "public";
397
+ }>;
398
+ owner: z.ZodString;
399
+ audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
400
+ lease: z.ZodObject<{
401
+ runnerId: z.ZodString;
402
+ expiresAt: z.ZodNumber;
403
+ }, z.core.$strip>;
404
+ }, z.core.$strict>;
405
+ type ClaimedJob = z.infer<typeof ClaimedJob>;
406
+ /**
407
+ * The provenance that travels with every result to the delivery seam.
408
+ *
409
+ * byollm_003 Rev 1: a `named`/`public` result is attacker-controlled text.
410
+ * The app must never render volunteer output as its own AI's answer without
411
+ * knowing that is what it is ({@link MUSTS.RESULT_PROVENANCE}).
412
+ */
413
+ declare const ResultProvenance: z.ZodObject<{
414
+ audience: z.ZodEnum<{
415
+ self: "self";
416
+ named: "named";
417
+ public: "public";
418
+ }>;
419
+ runnerId: z.ZodString;
420
+ runnerOwner: z.ZodString;
421
+ backendClass: z.ZodEnum<{
422
+ http: "http";
423
+ process: "process";
424
+ }>;
425
+ model: z.ZodString;
426
+ untrusted: z.ZodBoolean;
427
+ }, z.core.$strict>;
428
+ type ResultProvenance = z.infer<typeof ResultProvenance>;
429
+ /**
430
+ * Build provenance for a completed job. `untrusted` is derived, never
431
+ * supplied, so no caller can mark volunteer output as first-party.
432
+ */
433
+ declare function provenanceFor(input: {
434
+ audience: Audience;
435
+ runnerId: string;
436
+ runnerOwner: string;
437
+ backendClass: BackendClass;
438
+ model: string;
439
+ }): ResultProvenance;
440
+ /** Successful outcome. */
441
+ declare const JobResultOk: z.ZodObject<{
442
+ outcome: z.ZodLiteral<"ok">;
443
+ text: z.ZodString;
444
+ artifactUrl: z.ZodOptional<z.ZodURL>;
445
+ }, z.core.$strict>;
446
+ /** Failed outcome. `code` is a stable machine string; `message` is for humans. */
447
+ declare const JobResultError: z.ZodObject<{
448
+ outcome: z.ZodLiteral<"error">;
449
+ code: z.ZodString;
450
+ message: z.ZodString;
451
+ retryable: z.ZodBoolean;
452
+ }, z.core.$strict>;
453
+ /** Cancelled outcome, reported by the daemon after honoring a cancel. */
454
+ declare const JobResultCanceled: z.ZodObject<{
455
+ outcome: z.ZodLiteral<"canceled">;
456
+ }, z.core.$strict>;
457
+ declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
458
+ outcome: z.ZodLiteral<"ok">;
459
+ text: z.ZodString;
460
+ artifactUrl: z.ZodOptional<z.ZodURL>;
461
+ }, z.core.$strict>, z.ZodObject<{
462
+ outcome: z.ZodLiteral<"error">;
463
+ code: z.ZodString;
464
+ message: z.ZodString;
465
+ retryable: z.ZodBoolean;
466
+ }, z.core.$strict>, z.ZodObject<{
467
+ outcome: z.ZodLiteral<"canceled">;
468
+ }, z.core.$strict>], "outcome">;
469
+ type JobOutcome = z.infer<typeof JobOutcome>;
470
+ /** A completed job as delivered to the app, provenance attached. */
471
+ declare const DeliveredResult: z.ZodObject<{
472
+ jobId: z.ZodString;
473
+ state: z.ZodEnum<{
474
+ ok: "ok";
475
+ error: "error";
476
+ queued: "queued";
477
+ claimed: "claimed";
478
+ running: "running";
479
+ canceled: "canceled";
480
+ expired: "expired";
481
+ }>;
482
+ outcome: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
483
+ outcome: z.ZodLiteral<"ok">;
484
+ text: z.ZodString;
485
+ artifactUrl: z.ZodOptional<z.ZodURL>;
486
+ }, z.core.$strict>, z.ZodObject<{
487
+ outcome: z.ZodLiteral<"error">;
488
+ code: z.ZodString;
489
+ message: z.ZodString;
490
+ retryable: z.ZodBoolean;
491
+ }, z.core.$strict>, z.ZodObject<{
492
+ outcome: z.ZodLiteral<"canceled">;
493
+ }, z.core.$strict>], "outcome">>;
494
+ provenance: z.ZodOptional<z.ZodObject<{
495
+ audience: z.ZodEnum<{
496
+ self: "self";
497
+ named: "named";
498
+ public: "public";
499
+ }>;
500
+ runnerId: z.ZodString;
501
+ runnerOwner: z.ZodString;
502
+ backendClass: z.ZodEnum<{
503
+ http: "http";
504
+ process: "process";
505
+ }>;
506
+ model: z.ZodString;
507
+ untrusted: z.ZodBoolean;
508
+ }, z.core.$strict>>;
509
+ }, z.core.$strict>;
510
+ type DeliveredResult = z.infer<typeof DeliveredResult>;
511
+
512
+ /**
513
+ * The normative MUSTs of protocol v0, as data.
514
+ *
515
+ * byollm_001 requires that "every MUST above has a conformance test id
516
+ * referenced inline". Keeping the MUSTs as a frozen registry rather than
517
+ * prose is what makes that requirement *checkable*: the conformance kit
518
+ * imports {@link MUSTS} and fails if any id has no test asserting it, so a
519
+ * new MUST cannot be added without a test and a test cannot silently drift
520
+ * away from the statement it claims to prove.
521
+ *
522
+ * Ids are stable and public — third-party servers cite them in their
523
+ * certification output.
524
+ */
525
+ /** Which side of the wire is obliged to enforce a given MUST. */
526
+ type MustEnforcer = "daemon" | "server" | "both";
527
+ /** A single normative requirement of the protocol. */
528
+ interface Must {
529
+ /** Stable public id, cited by conformance output. */
530
+ readonly id: string;
531
+ /** The requirement, in MUST language. */
532
+ readonly statement: string;
533
+ /** Which implementation is obliged to enforce it. */
534
+ readonly enforcedBy: MustEnforcer;
535
+ /** Spec section this was adjudicated in. */
536
+ readonly source: string;
537
+ }
538
+ /**
539
+ * Every normative MUST in protocol v0.
540
+ *
541
+ * @remarks
542
+ * Grouped by concern for readability; the conformance kit treats this as a
543
+ * flat set. Adding an entry here without a corresponding conformance test is
544
+ * a CI failure, by design.
545
+ */
546
+ declare const MUSTS: Readonly<{
547
+ readonly PAIR_ONE_USER: Must;
548
+ readonly PAIR_INTERACTIVE: Must;
549
+ readonly PAIR_CODE_EXPIRES: Must;
550
+ readonly KIND_TYPED_ONLY: Must;
551
+ readonly KIND_NO_CODE: Must;
552
+ readonly CLAIM_REQUIRES_CAPABILITY: Must;
553
+ readonly CAPABILITY_IS_DETECTED: Must;
554
+ readonly CLAIM_ATOMIC: Must;
555
+ readonly LEASE_HONORED: Must;
556
+ readonly LEASE_RECLAIMABLE: Must;
557
+ readonly AUDIENCE_BOTH_SIDES: Must;
558
+ readonly SUBSCRIPTION_SELF_LOCK: Must;
559
+ readonly NAMED_LOCAL_ALLOWLIST: Must;
560
+ readonly REFUSAL_NOT_REOFFERED: Must;
561
+ readonly REVOCATION_HONORED: Must;
562
+ readonly CANCEL_HONORED: Must;
563
+ readonly DEPENDS_ON_GATING: Must;
564
+ readonly TTL_EXPIRY: Must;
565
+ readonly NO_RUNNER_SIGNAL: Must;
566
+ readonly RESULT_IDEMPOTENT: Must;
567
+ readonly RESULT_PROVENANCE: Must;
568
+ readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
569
+ readonly NO_SHELL_INTERPOLATION: Must;
570
+ readonly NO_PAYLOAD_ROUTING: Must;
571
+ readonly STRIPPED_CHILD_ENV: Must;
572
+ readonly HTTP_BASE_URL_SAFE: Must;
573
+ readonly OUTPUT_INERT: Must;
574
+ readonly COMMUNITY_BUDGETS: Must;
575
+ }>;
576
+ /** The id of any normative MUST. */
577
+ type MustId = keyof typeof MUSTS;
578
+ /** All MUST ids, for coverage checks. */
579
+ declare const MUST_IDS: readonly ("PAIR_ONE_USER" | "PAIR_INTERACTIVE" | "PAIR_CODE_EXPIRES" | "KIND_TYPED_ONLY" | "KIND_NO_CODE" | "CLAIM_REQUIRES_CAPABILITY" | "CAPABILITY_IS_DETECTED" | "CLAIM_ATOMIC" | "LEASE_HONORED" | "LEASE_RECLAIMABLE" | "AUDIENCE_BOTH_SIDES" | "SUBSCRIPTION_SELF_LOCK" | "NAMED_LOCAL_ALLOWLIST" | "REFUSAL_NOT_REOFFERED" | "REVOCATION_HONORED" | "CANCEL_HONORED" | "DEPENDS_ON_GATING" | "TTL_EXPIRY" | "NO_RUNNER_SIGNAL" | "RESULT_IDEMPOTENT" | "RESULT_PROVENANCE" | "INGRESS_LOGGED_BEFORE_EXECUTION" | "NO_SHELL_INTERPOLATION" | "NO_PAYLOAD_ROUTING" | "STRIPPED_CHILD_ENV" | "HTTP_BASE_URL_SAFE" | "OUTPUT_INERT" | "COMMUNITY_BUDGETS")[];
580
+
581
+ /** Protocol version carried on every request; servers refuse what they can't speak. */
582
+ declare const PROTOCOL_VERSION: "0";
583
+ /** The path prefix all endpoints mount under. */
584
+ declare const PROTOCOL_PREFIX: "/byollm";
585
+ /** The five endpoint names, in the order byollm_001 lists them. */
586
+ declare const ENDPOINTS: readonly ["pair", "claim", "heartbeat", "result", "release"];
587
+ type Endpoint = (typeof ENDPOINTS)[number];
588
+ /**
589
+ * One entry of the capability matrix: a kind this daemon can actually serve,
590
+ * right now, with the backend and model that would serve it.
591
+ *
592
+ * Derived from owner config intersected with detected reality
593
+ * ({@link MUSTS.CAPABILITY_IS_DETECTED}) — a configured-but-unreachable
594
+ * backend must not appear here. Carries `backendClass` so the app can tell
595
+ * whether a result came from a sandboxed spawn or an HTTP call
596
+ * (byollm_001 Rev 1 §A).
597
+ */
598
+ declare const Capability: z.ZodObject<{
599
+ kind: z.ZodEnum<{
600
+ "llm.generate": "llm.generate";
601
+ "llm.chat": "llm.chat";
602
+ }>;
603
+ backendId: z.ZodEnum<{
604
+ "openai-http": "openai-http";
605
+ "claude-cli": "claude-cli";
606
+ }>;
607
+ backendClass: z.ZodEnum<{
608
+ http: "http";
609
+ process: "process";
610
+ }>;
611
+ model: z.ZodString;
612
+ offerScope: z.ZodEnum<{
613
+ self: "self";
614
+ named: "named";
615
+ public: "public";
616
+ }>;
617
+ }, z.core.$strict>;
618
+ type Capability = z.infer<typeof Capability>;
619
+ /** The capability matrix a daemon advertises. */
620
+ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
621
+ kind: z.ZodEnum<{
622
+ "llm.generate": "llm.generate";
623
+ "llm.chat": "llm.chat";
624
+ }>;
625
+ backendId: z.ZodEnum<{
626
+ "openai-http": "openai-http";
627
+ "claude-cli": "claude-cli";
628
+ }>;
629
+ backendClass: z.ZodEnum<{
630
+ http: "http";
631
+ process: "process";
632
+ }>;
633
+ model: z.ZodString;
634
+ offerScope: z.ZodEnum<{
635
+ self: "self";
636
+ named: "named";
637
+ public: "public";
638
+ }>;
639
+ }, z.core.$strict>>;
640
+ type CapabilityMatrix = z.infer<typeof CapabilityMatrix>;
641
+ /**
642
+ * Pairing is a device-code exchange, not a pasted secret
643
+ * ({@link MUSTS.PAIR_INTERACTIVE}). The daemon starts a pairing, shows the
644
+ * user a short code and a URL, and polls until the user approves it inside
645
+ * the app's own authenticated session. Nothing listens on the user's machine
646
+ * and nothing works over a copied string alone.
647
+ */
648
+ declare const PairStartRequest: z.ZodObject<{
649
+ protocolVersion: z.ZodLiteral<"0">;
650
+ action: z.ZodLiteral<"start">;
651
+ daemon: z.ZodObject<{
652
+ version: z.ZodString;
653
+ label: z.ZodString;
654
+ platform: z.ZodEnum<{
655
+ darwin: "darwin";
656
+ linux: "linux";
657
+ win32: "win32";
658
+ }>;
659
+ }, z.core.$strip>;
660
+ capabilities: z.ZodArray<z.ZodObject<{
661
+ kind: z.ZodEnum<{
662
+ "llm.generate": "llm.generate";
663
+ "llm.chat": "llm.chat";
664
+ }>;
665
+ backendId: z.ZodEnum<{
666
+ "openai-http": "openai-http";
667
+ "claude-cli": "claude-cli";
668
+ }>;
669
+ backendClass: z.ZodEnum<{
670
+ http: "http";
671
+ process: "process";
672
+ }>;
673
+ model: z.ZodString;
674
+ offerScope: z.ZodEnum<{
675
+ self: "self";
676
+ named: "named";
677
+ public: "public";
678
+ }>;
679
+ }, z.core.$strict>>;
680
+ }, z.core.$strict>;
681
+ type PairStartRequest = z.infer<typeof PairStartRequest>;
682
+ declare const PairStartResponse: z.ZodObject<{
683
+ deviceCode: z.ZodString;
684
+ userCode: z.ZodString;
685
+ verificationUrl: z.ZodURL;
686
+ expiresAt: z.ZodNumber;
687
+ pollIntervalMs: z.ZodNumber;
688
+ }, z.core.$strict>;
689
+ type PairStartResponse = z.infer<typeof PairStartResponse>;
690
+ declare const PairPollRequest: z.ZodObject<{
691
+ protocolVersion: z.ZodLiteral<"0">;
692
+ action: z.ZodLiteral<"poll">;
693
+ deviceCode: z.ZodString;
694
+ }, z.core.$strict>;
695
+ type PairPollRequest = z.infer<typeof PairPollRequest>;
696
+ declare const PairPollResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
697
+ status: z.ZodLiteral<"pending">;
698
+ }, z.core.$strict>, z.ZodObject<{
699
+ status: z.ZodLiteral<"denied">;
700
+ }, z.core.$strict>, z.ZodObject<{
701
+ status: z.ZodLiteral<"expired">;
702
+ }, z.core.$strict>, z.ZodObject<{
703
+ status: z.ZodLiteral<"approved">;
704
+ runnerToken: z.ZodString;
705
+ runnerId: z.ZodString;
706
+ owner: z.ZodString;
707
+ ownerLabel: z.ZodOptional<z.ZodString>;
708
+ }, z.core.$strict>], "status">;
709
+ type PairPollResponse = z.infer<typeof PairPollResponse>;
710
+ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
711
+ protocolVersion: z.ZodLiteral<"0">;
712
+ action: z.ZodLiteral<"start">;
713
+ daemon: z.ZodObject<{
714
+ version: z.ZodString;
715
+ label: z.ZodString;
716
+ platform: z.ZodEnum<{
717
+ darwin: "darwin";
718
+ linux: "linux";
719
+ win32: "win32";
720
+ }>;
721
+ }, z.core.$strip>;
722
+ capabilities: z.ZodArray<z.ZodObject<{
723
+ kind: z.ZodEnum<{
724
+ "llm.generate": "llm.generate";
725
+ "llm.chat": "llm.chat";
726
+ }>;
727
+ backendId: z.ZodEnum<{
728
+ "openai-http": "openai-http";
729
+ "claude-cli": "claude-cli";
730
+ }>;
731
+ backendClass: z.ZodEnum<{
732
+ http: "http";
733
+ process: "process";
734
+ }>;
735
+ model: z.ZodString;
736
+ offerScope: z.ZodEnum<{
737
+ self: "self";
738
+ named: "named";
739
+ public: "public";
740
+ }>;
741
+ }, z.core.$strict>>;
742
+ }, z.core.$strict>, z.ZodObject<{
743
+ protocolVersion: z.ZodLiteral<"0">;
744
+ action: z.ZodLiteral<"poll">;
745
+ deviceCode: z.ZodString;
746
+ }, z.core.$strict>], "action">;
747
+ type PairRequest = z.infer<typeof PairRequest>;
748
+ declare const ClaimRequest: z.ZodObject<{
749
+ protocolVersion: z.ZodLiteral<"0">;
750
+ runnerId: z.ZodString;
751
+ capabilities: z.ZodArray<z.ZodObject<{
752
+ kind: z.ZodEnum<{
753
+ "llm.generate": "llm.generate";
754
+ "llm.chat": "llm.chat";
755
+ }>;
756
+ backendId: z.ZodEnum<{
757
+ "openai-http": "openai-http";
758
+ "claude-cli": "claude-cli";
759
+ }>;
760
+ backendClass: z.ZodEnum<{
761
+ http: "http";
762
+ process: "process";
763
+ }>;
764
+ model: z.ZodString;
765
+ offerScope: z.ZodEnum<{
766
+ self: "self";
767
+ named: "named";
768
+ public: "public";
769
+ }>;
770
+ }, z.core.$strict>>;
771
+ max: z.ZodNumber;
772
+ }, z.core.$strict>;
773
+ type ClaimRequest = z.infer<typeof ClaimRequest>;
774
+ declare const ClaimResponse: z.ZodObject<{
775
+ jobs: z.ZodArray<z.ZodObject<{
776
+ id: z.ZodString;
777
+ kind: z.ZodEnum<{
778
+ "llm.generate": "llm.generate";
779
+ "llm.chat": "llm.chat";
780
+ }>;
781
+ payload: z.ZodUnion<readonly [z.ZodObject<{
782
+ prompt: z.ZodString;
783
+ system: z.ZodOptional<z.ZodString>;
784
+ }, z.core.$strict>, z.ZodObject<{
785
+ messages: z.ZodArray<z.ZodObject<{
786
+ role: z.ZodEnum<{
787
+ system: "system";
788
+ user: "user";
789
+ assistant: "assistant";
790
+ }>;
791
+ content: z.ZodString;
792
+ }, z.core.$strip>>;
793
+ system: z.ZodOptional<z.ZodString>;
794
+ }, z.core.$strict>]>;
795
+ audience: z.ZodEnum<{
796
+ self: "self";
797
+ named: "named";
798
+ public: "public";
799
+ }>;
800
+ owner: z.ZodString;
801
+ audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
802
+ lease: z.ZodObject<{
803
+ runnerId: z.ZodString;
804
+ expiresAt: z.ZodNumber;
805
+ }, z.core.$strip>;
806
+ }, z.core.$strict>>;
807
+ leaseMs: z.ZodNumber;
808
+ }, z.core.$strict>;
809
+ type ClaimResponse = z.infer<typeof ClaimResponse>;
810
+ declare const HeartbeatRequest: z.ZodObject<{
811
+ protocolVersion: z.ZodLiteral<"0">;
812
+ runnerId: z.ZodString;
813
+ daemonVersion: z.ZodString;
814
+ capabilities: z.ZodArray<z.ZodObject<{
815
+ kind: z.ZodEnum<{
816
+ "llm.generate": "llm.generate";
817
+ "llm.chat": "llm.chat";
818
+ }>;
819
+ backendId: z.ZodEnum<{
820
+ "openai-http": "openai-http";
821
+ "claude-cli": "claude-cli";
822
+ }>;
823
+ backendClass: z.ZodEnum<{
824
+ http: "http";
825
+ process: "process";
826
+ }>;
827
+ model: z.ZodString;
828
+ offerScope: z.ZodEnum<{
829
+ self: "self";
830
+ named: "named";
831
+ public: "public";
832
+ }>;
833
+ }, z.core.$strict>>;
834
+ activeJobIds: z.ZodArray<z.ZodString>;
835
+ paused: z.ZodBoolean;
836
+ }, z.core.$strict>;
837
+ type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;
838
+ declare const HeartbeatResponse: z.ZodObject<{
839
+ revoked: z.ZodBoolean;
840
+ cancel: z.ZodArray<z.ZodString>;
841
+ leases: z.ZodArray<z.ZodObject<{
842
+ jobId: z.ZodString;
843
+ expiresAt: z.ZodNumber;
844
+ }, z.core.$strict>>;
845
+ lost: z.ZodArray<z.ZodString>;
846
+ serverTime: z.ZodNumber;
847
+ }, z.core.$strict>;
848
+ type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
849
+ declare const ResultRequest: z.ZodObject<{
850
+ protocolVersion: z.ZodLiteral<"0">;
851
+ runnerId: z.ZodString;
852
+ jobId: z.ZodString;
853
+ outcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
854
+ outcome: z.ZodLiteral<"ok">;
855
+ text: z.ZodString;
856
+ artifactUrl: z.ZodOptional<z.ZodURL>;
857
+ }, z.core.$strict>, z.ZodObject<{
858
+ outcome: z.ZodLiteral<"error">;
859
+ code: z.ZodString;
860
+ message: z.ZodString;
861
+ retryable: z.ZodBoolean;
862
+ }, z.core.$strict>, z.ZodObject<{
863
+ outcome: z.ZodLiteral<"canceled">;
864
+ }, z.core.$strict>], "outcome">;
865
+ model: z.ZodString;
866
+ backendClass: z.ZodEnum<{
867
+ http: "http";
868
+ process: "process";
869
+ }>;
870
+ durationMs: z.ZodNumber;
871
+ }, z.core.$strict>;
872
+ type ResultRequest = z.infer<typeof ResultRequest>;
873
+ declare const ResultResponse: z.ZodObject<{
874
+ accepted: z.ZodBoolean;
875
+ state: z.ZodString;
876
+ }, z.core.$strict>;
877
+ type ResultResponse = z.infer<typeof ResultResponse>;
878
+ declare const ReleaseRequest: z.ZodObject<{
879
+ protocolVersion: z.ZodLiteral<"0">;
880
+ runnerId: z.ZodString;
881
+ jobIds: z.ZodArray<z.ZodString>;
882
+ reason: z.ZodEnum<{
883
+ revoked: "revoked";
884
+ shutdown: "shutdown";
885
+ pause: "pause";
886
+ "backend-down": "backend-down";
887
+ refused: "refused";
888
+ }>;
889
+ }, z.core.$strict>;
890
+ type ReleaseRequest = z.infer<typeof ReleaseRequest>;
891
+ declare const ReleaseResponse: z.ZodObject<{
892
+ released: z.ZodArray<z.ZodString>;
893
+ }, z.core.$strict>;
894
+ type ReleaseResponse = z.infer<typeof ReleaseResponse>;
895
+ /**
896
+ * Wire error codes.
897
+ *
898
+ * byollm_002 requires that "server unreachable", "revoked", "no matching
899
+ * work" and "backend down" never share a message. Distinct codes here are how
900
+ * the daemon can tell three of those apart; the fourth is a transport failure
901
+ * with no response at all.
902
+ */
903
+ declare const WireErrorCode: z.ZodEnum<{
904
+ revoked: "revoked";
905
+ "bad-request": "bad-request";
906
+ "unsupported-protocol-version": "unsupported-protocol-version";
907
+ unauthorized: "unauthorized";
908
+ "not-found": "not-found";
909
+ "rate-limited": "rate-limited";
910
+ "server-error": "server-error";
911
+ }>;
912
+ type WireErrorCode = z.infer<typeof WireErrorCode>;
913
+ declare const WireError: z.ZodObject<{
914
+ error: z.ZodEnum<{
915
+ revoked: "revoked";
916
+ "bad-request": "bad-request";
917
+ "unsupported-protocol-version": "unsupported-protocol-version";
918
+ unauthorized: "unauthorized";
919
+ "not-found": "not-found";
920
+ "rate-limited": "rate-limited";
921
+ "server-error": "server-error";
922
+ }>;
923
+ message: z.ZodString;
924
+ retryAfter: z.ZodOptional<z.ZodNumber>;
925
+ }, z.core.$strict>;
926
+ type WireError = z.infer<typeof WireError>;
927
+ /** HTTP status each error code is served with. */
928
+ declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
929
+
930
+ export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendAccount, BackendClass, type BackendDescriptor, type BackendId, BackendIdSchema, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, DeliveredResult, ENDPOINTS, ERROR_STATUS, type Endpoint, GeneratePayload, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobResultCanceled, JobResultError, JobResultOk, JobState, KindedPayload, Lease, MUSTS, MUST_IDS, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, OFFER_SCOPES, OfferScope, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, REFUSAL_MESSAGES, ReleaseRequest, ReleaseResponse, ResultProvenance, ResultRequest, ResultResponse, TERMINAL_STATES, WireError, WireErrorCode, backendDescriptor, canTransition, effectiveOfferScope, isBackendId, isJobKind, isTerminal, matchAudience, payloadTextLength, provenanceFor };