@byollm/protocol 0.1.0-alpha.1 → 0.1.0-alpha.3

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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  > [!WARNING]
2
- > **Alpha (`0.1.0-alpha.1`) — under active development. Don't use this yet.**
2
+ > **Alpha (`0.1.0-alpha.3`) — under active development. Don't use this yet.**
3
3
  >
4
4
  > Install it deliberately: `npm install @byollm/protocol@alpha`.
5
5
  >
@@ -12,6 +12,10 @@
12
12
  > bare install resolves here too. This notice is the only guard — deliberately
13
13
  > not an npm deprecation, which would read as *abandoned* rather than *early*.
14
14
  > Ask for `@alpha` explicitly so your lockfile records that you meant to.
15
+ >
16
+ > **`alpha.3` is a breaking change.** `BackendDescriptor.account` is gone —
17
+ > read `cost` (`free` / `metered` / `subscription`) instead. Four new MUSTs
18
+ > come with it; see `byollm_007`.
15
19
 
16
20
  # `@byollm/protocol`
17
21
 
package/dist/index.d.ts CHANGED
@@ -5,9 +5,10 @@ import { z } from 'zod';
5
5
  * Rev 1 §A, because the two classes have different threat surfaces.
6
6
  *
7
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}.
8
+ * llama.cpp server, vLLM, and every hosted provider that speaks the same
9
+ * wire format). Spawns nothing, so byollm_004 §2's argv, stdin, env and
10
+ * sandbox requirements are not applicable by construction. Its threat
11
+ * surface is SSRF-shaped and bounded by {@link MUSTS.HTTP_BASE_URL_SAFE}.
11
12
  * - `process`: spawns a binary (`claude` CLI today, `mlx_lm.lora` for a
12
13
  * future `train.*` kind). All of byollm_004 §2 is mandatory here.
13
14
  */
@@ -17,20 +18,26 @@ declare const BackendClass: z.ZodEnum<{
17
18
  }>;
18
19
  type BackendClass = z.infer<typeof BackendClass>;
19
20
  /**
20
- * Whose account pays for the inference.
21
+ * Who pays, and how — byollm_007.
21
22
  *
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.
23
+ * This replaced a two-valued `account` field that conflated two unrelated
24
+ * constraints and, in doing so, left a hole: `openai-http` was "open", but it
25
+ * accepts an API key, so an owner could point it at a paid endpoint, offer it
26
+ * `public`, and donate their credit balance to strangers. The community
27
+ * budgets cap job *count*, not spend.
28
+ *
29
+ * - `free` — local compute. Costs electricity, not money. Shareable.
30
+ * - `metered` — per-token billing against the owner's account. Legal to
31
+ * share and ruinous to share by accident.
32
+ * - `subscription` — a vendor account whose terms forbid third-party work.
33
+ * Sharing is a terms violation, not merely expensive.
28
34
  */
29
- declare const BackendAccount: z.ZodEnum<{
30
- open: "open";
35
+ declare const BackendCost: z.ZodEnum<{
36
+ free: "free";
37
+ metered: "metered";
31
38
  subscription: "subscription";
32
39
  }>;
33
- type BackendAccount = z.infer<typeof BackendAccount>;
40
+ type BackendCost = z.infer<typeof BackendCost>;
34
41
  /** The immutable facts about a backend that the protocol reasons over. */
35
42
  interface BackendDescriptor {
36
43
  /** Stable backend id, as written in `byollm.config.json`. */
@@ -39,33 +46,86 @@ interface BackendDescriptor {
39
46
  readonly label: string;
40
47
  /** Determines which isolation requirements apply. */
41
48
  readonly class: BackendClass;
42
- /** Determines whether the offer scope can be widened past `self`. */
43
- readonly account: BackendAccount;
49
+ /**
50
+ * Who pays. Fixed here for every named provider and **not overridable by
51
+ * configuration** ({@link MUSTS.COST_NOT_CONFIGURABLE}) — `openai` is
52
+ * metered because it is, and no setting changes that.
53
+ *
54
+ * `null` only for the generic {@link BACKENDS."openai-http"} entry, whose
55
+ * cost is inferred from its base URL instead
56
+ * ({@link MUSTS.REMOTE_IS_NEVER_FREE}).
57
+ */
58
+ readonly cost: BackendCost | null;
44
59
  /**
45
60
  * Which adversarial corpus byollm_004 §5 runs against this backend. A
46
61
  * backend cannot be registered without one — the coverage check in the
47
62
  * adversarial suite enforces it.
48
63
  */
49
64
  readonly adversarialCorpus: "process" | "http";
65
+ /**
66
+ * Where this provider lives, when that is knowable. Owner config may
67
+ * override it; a provider with no default requires one to be given.
68
+ */
69
+ readonly defaultBaseUrl?: string;
50
70
  }
51
71
  /**
52
- * The v1 backend registry.
72
+ * The backend registry.
53
73
  *
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.
74
+ * **Providers are entries, not implementations.** Every HTTP-class provider
75
+ * below shares the single `openai-http` transport, because they all speak
76
+ * OpenAI-compatible `/v1/chat/completions`. An entry adds a stable id, a cost
77
+ * class the owner cannot override, and a default base URL. Adding a provider
78
+ * is therefore one line and no new code — which is why the adversarial corpus
79
+ * still covers all of them, and why a PR adding one is reviewable at a glance.
59
80
  */
60
81
  declare const BACKENDS: Readonly<{
82
+ readonly ollama: BackendDescriptor;
83
+ readonly mlx: BackendDescriptor;
84
+ readonly llamacpp: BackendDescriptor;
85
+ readonly vllm: BackendDescriptor;
86
+ readonly lmstudio: BackendDescriptor;
87
+ readonly jan: BackendDescriptor;
88
+ readonly localai: BackendDescriptor;
89
+ /**
90
+ * Note the pair: `anthropic` and {@link BACKENDS."claude-cli"} reach the
91
+ * same vendor and land in different cost classes. That is not an
92
+ * inconsistency — it is the axis working. One bills a key per token, the
93
+ * other runs under a personal plan whose terms cover one person's work. Who
94
+ * pays and under what terms is the question; which company is not.
95
+ */
96
+ readonly anthropic: BackendDescriptor;
97
+ readonly openai: BackendDescriptor;
98
+ readonly gemini: BackendDescriptor;
99
+ readonly grok: BackendDescriptor;
100
+ readonly groq: BackendDescriptor;
101
+ readonly openrouter: BackendDescriptor;
102
+ readonly together: BackendDescriptor;
103
+ readonly deepseek: BackendDescriptor;
104
+ readonly mistral: BackendDescriptor;
61
105
  readonly "openai-http": BackendDescriptor;
62
106
  readonly "claude-cli": BackendDescriptor;
63
107
  }>;
64
108
  /** The id of a registered backend. */
65
109
  type BackendId = keyof typeof BACKENDS;
66
110
  /** All registered backend ids — the adversarial coverage check iterates this. */
67
- declare const BACKEND_IDS: readonly ("openai-http" | "claude-cli")[];
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")[];
68
112
  declare const BackendIdSchema: z.ZodEnum<{
113
+ ollama: "ollama";
114
+ mlx: "mlx";
115
+ llamacpp: "llamacpp";
116
+ vllm: "vllm";
117
+ lmstudio: "lmstudio";
118
+ jan: "jan";
119
+ localai: "localai";
120
+ anthropic: "anthropic";
121
+ openai: "openai";
122
+ gemini: "gemini";
123
+ grok: "grok";
124
+ groq: "groq";
125
+ openrouter: "openrouter";
126
+ together: "together";
127
+ deepseek: "deepseek";
128
+ mistral: "mistral";
69
129
  "openai-http": "openai-http";
70
130
  "claude-cli": "claude-cli";
71
131
  }>;
@@ -78,6 +138,34 @@ declare function isBackendId(value: string): value is BackendId;
78
138
  * adversarial corpus, so refusing is the safe direction.
79
139
  */
80
140
  declare function backendDescriptor(id: BackendId): BackendDescriptor;
141
+ /**
142
+ * Is this host local enough that compute there is free?
143
+ *
144
+ * Loopback and the private ranges only. This is the rule that makes
145
+ * {@link MUSTS.REMOTE_IS_NEVER_FREE} enforceable rather than a promise: an
146
+ * owner cannot reach a paid API through the generic backend and call it free,
147
+ * because "free" is derived from the address, not from what the config claims.
148
+ *
149
+ * **What this cannot see.** The address is all it reads. A proxy on
150
+ * `127.0.0.1` forwarding to a paid API classes as `free` and nothing
151
+ * downstream will contradict it. That is deliberate: standing up a relay is
152
+ * an act by the machine's owner against their own account, and the threat
153
+ * model here is a hostile *job*, not an owner routing around a rule that
154
+ * exists to protect them. What this catches is the accident — a remote paid
155
+ * endpoint offered `public` because nobody thought about the bill. See
156
+ * `docs/security.md` §4a.
157
+ */
158
+ declare function isLocalHost(hostname: string): boolean;
159
+ /**
160
+ * The cost class of a configured backend instance.
161
+ *
162
+ * For every named provider this is whatever the registry says, full stop
163
+ * ({@link MUSTS.COST_NOT_CONFIGURABLE}). For the generic `openai-http` entry
164
+ * it is inferred from the base URL, and a base URL that cannot be parsed is
165
+ * treated as `metered` — the expensive side, because guessing "free" wrong
166
+ * costs the owner money.
167
+ */
168
+ declare function resolveCost(id: BackendId, baseUrl: string | undefined): BackendCost;
81
169
 
82
170
  /**
83
171
  * Who may run a job, declared by the app that enqueued it.
@@ -121,6 +209,8 @@ declare const MatchRefusal: z.ZodEnum<{
121
209
  "not-in-server-allowlist": "not-in-server-allowlist";
122
210
  "offer-scope-too-narrow": "offer-scope-too-narrow";
123
211
  "subscription-self-lock": "subscription-self-lock";
212
+ "metered-no-spend-consent": "metered-no-spend-consent";
213
+ "metered-ceiling-reached": "metered-ceiling-reached";
124
214
  }>;
125
215
  type MatchRefusal = z.infer<typeof MatchRefusal>;
126
216
  /** The outcome of an audience match. */
@@ -130,16 +220,30 @@ type MatchResult = {
130
220
  readonly ok: false;
131
221
  readonly refusal: MatchRefusal;
132
222
  };
223
+ /** What the owner has agreed to spend on other people's work, if anything. */
224
+ interface SpendConsent {
225
+ /** The owner explicitly acknowledged that sharing this backend costs money. */
226
+ readonly acknowledged: boolean;
227
+ /** Their ceiling. Absent means no ceiling was set, which is not consent. */
228
+ readonly ceilingReached?: boolean;
229
+ }
133
230
  /**
134
231
  * The effective offer scope of a backend.
135
232
  *
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.
233
+ * Three rules, applied at the one place both the daemon's config loader and
234
+ * its matcher call, so no code path can observe a scope wider than the cost
235
+ * class allows:
236
+ *
237
+ * - `subscription` is locked to `self` regardless of config
238
+ * ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — someone else's terms.
239
+ * - `metered` narrows to `self` unless the owner has explicitly acknowledged
240
+ * the spend ({@link MUSTS.METERED_DEFAULTS_SELF}) — their money.
241
+ * - `free` passes through — their electricity.
242
+ *
243
+ * Note the asymmetry: subscription can never be widened, metered can be
244
+ * widened deliberately. Conflating those was byollm_007's bug.
141
245
  */
142
- declare function effectiveOfferScope(configured: OfferScope, account: BackendAccount): OfferScope;
246
+ declare function effectiveOfferScope(configured: OfferScope, cost: BackendCost, spend?: SpendConsent): OfferScope;
143
247
  /** The job-side facts a match needs. */
144
248
  interface MatchJob {
145
249
  /** The app's id for the user who enqueued the job. */
@@ -159,8 +263,10 @@ interface MatchDaemon {
159
263
  readonly owner: string;
160
264
  /** Effective scope of the backend that would run the job. */
161
265
  readonly offerScope: OfferScope;
162
- /** Account class of that backend. */
163
- readonly account: BackendAccount;
266
+ /** Who pays for that backend's tokens. */
267
+ readonly cost: BackendCost;
268
+ /** What the owner agreed to spend on others, for a `metered` backend. */
269
+ readonly spend?: SpendConsent | undefined;
164
270
  /**
165
271
  * Does this daemon's *local* allowlist admit the given owner for the server
166
272
  * origin the job came from? Supplied as a predicate so the protocol package
@@ -189,7 +295,7 @@ interface MatchDaemon {
189
295
  * {
190
296
  * owner: "bob",
191
297
  * offerScope: "named",
192
- * account: "open",
298
+ * cost: "free",
193
299
  * locallyAllows: (o) => o === "alice",
194
300
  * },
195
301
  * );
@@ -556,6 +662,10 @@ declare const MUSTS: Readonly<{
556
662
  readonly LEASE_RECLAIMABLE: Must;
557
663
  readonly AUDIENCE_BOTH_SIDES: Must;
558
664
  readonly SUBSCRIPTION_SELF_LOCK: Must;
665
+ readonly METERED_DEFAULTS_SELF: Must;
666
+ readonly METERED_REQUIRES_CEILING: Must;
667
+ readonly COST_NOT_CONFIGURABLE: Must;
668
+ readonly REMOTE_IS_NEVER_FREE: Must;
559
669
  readonly NAMED_LOCAL_ALLOWLIST: Must;
560
670
  readonly REFUSAL_NOT_REOFFERED: Must;
561
671
  readonly REVOCATION_HONORED: Must;
@@ -576,7 +686,7 @@ declare const MUSTS: Readonly<{
576
686
  /** The id of any normative MUST. */
577
687
  type MustId = keyof typeof MUSTS;
578
688
  /** 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")[];
689
+ 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" | "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")[];
580
690
 
581
691
  /** Protocol version carried on every request; servers refuse what they can't speak. */
582
692
  declare const PROTOCOL_VERSION: "0";
@@ -601,6 +711,22 @@ declare const Capability: z.ZodObject<{
601
711
  "llm.chat": "llm.chat";
602
712
  }>;
603
713
  backendId: z.ZodEnum<{
714
+ ollama: "ollama";
715
+ mlx: "mlx";
716
+ llamacpp: "llamacpp";
717
+ vllm: "vllm";
718
+ lmstudio: "lmstudio";
719
+ jan: "jan";
720
+ localai: "localai";
721
+ anthropic: "anthropic";
722
+ openai: "openai";
723
+ gemini: "gemini";
724
+ grok: "grok";
725
+ groq: "groq";
726
+ openrouter: "openrouter";
727
+ together: "together";
728
+ deepseek: "deepseek";
729
+ mistral: "mistral";
604
730
  "openai-http": "openai-http";
605
731
  "claude-cli": "claude-cli";
606
732
  }>;
@@ -623,6 +749,22 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
623
749
  "llm.chat": "llm.chat";
624
750
  }>;
625
751
  backendId: z.ZodEnum<{
752
+ ollama: "ollama";
753
+ mlx: "mlx";
754
+ llamacpp: "llamacpp";
755
+ vllm: "vllm";
756
+ lmstudio: "lmstudio";
757
+ jan: "jan";
758
+ localai: "localai";
759
+ anthropic: "anthropic";
760
+ openai: "openai";
761
+ gemini: "gemini";
762
+ grok: "grok";
763
+ groq: "groq";
764
+ openrouter: "openrouter";
765
+ together: "together";
766
+ deepseek: "deepseek";
767
+ mistral: "mistral";
626
768
  "openai-http": "openai-http";
627
769
  "claude-cli": "claude-cli";
628
770
  }>;
@@ -663,6 +805,22 @@ declare const PairStartRequest: z.ZodObject<{
663
805
  "llm.chat": "llm.chat";
664
806
  }>;
665
807
  backendId: z.ZodEnum<{
808
+ ollama: "ollama";
809
+ mlx: "mlx";
810
+ llamacpp: "llamacpp";
811
+ vllm: "vllm";
812
+ lmstudio: "lmstudio";
813
+ jan: "jan";
814
+ localai: "localai";
815
+ anthropic: "anthropic";
816
+ openai: "openai";
817
+ gemini: "gemini";
818
+ grok: "grok";
819
+ groq: "groq";
820
+ openrouter: "openrouter";
821
+ together: "together";
822
+ deepseek: "deepseek";
823
+ mistral: "mistral";
666
824
  "openai-http": "openai-http";
667
825
  "claude-cli": "claude-cli";
668
826
  }>;
@@ -725,6 +883,22 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
725
883
  "llm.chat": "llm.chat";
726
884
  }>;
727
885
  backendId: z.ZodEnum<{
886
+ ollama: "ollama";
887
+ mlx: "mlx";
888
+ llamacpp: "llamacpp";
889
+ vllm: "vllm";
890
+ lmstudio: "lmstudio";
891
+ jan: "jan";
892
+ localai: "localai";
893
+ anthropic: "anthropic";
894
+ openai: "openai";
895
+ gemini: "gemini";
896
+ grok: "grok";
897
+ groq: "groq";
898
+ openrouter: "openrouter";
899
+ together: "together";
900
+ deepseek: "deepseek";
901
+ mistral: "mistral";
728
902
  "openai-http": "openai-http";
729
903
  "claude-cli": "claude-cli";
730
904
  }>;
@@ -754,6 +928,22 @@ declare const ClaimRequest: z.ZodObject<{
754
928
  "llm.chat": "llm.chat";
755
929
  }>;
756
930
  backendId: z.ZodEnum<{
931
+ ollama: "ollama";
932
+ mlx: "mlx";
933
+ llamacpp: "llamacpp";
934
+ vllm: "vllm";
935
+ lmstudio: "lmstudio";
936
+ jan: "jan";
937
+ localai: "localai";
938
+ anthropic: "anthropic";
939
+ openai: "openai";
940
+ gemini: "gemini";
941
+ grok: "grok";
942
+ groq: "groq";
943
+ openrouter: "openrouter";
944
+ together: "together";
945
+ deepseek: "deepseek";
946
+ mistral: "mistral";
757
947
  "openai-http": "openai-http";
758
948
  "claude-cli": "claude-cli";
759
949
  }>;
@@ -817,6 +1007,22 @@ declare const HeartbeatRequest: z.ZodObject<{
817
1007
  "llm.chat": "llm.chat";
818
1008
  }>;
819
1009
  backendId: z.ZodEnum<{
1010
+ ollama: "ollama";
1011
+ mlx: "mlx";
1012
+ llamacpp: "llamacpp";
1013
+ vllm: "vllm";
1014
+ lmstudio: "lmstudio";
1015
+ jan: "jan";
1016
+ localai: "localai";
1017
+ anthropic: "anthropic";
1018
+ openai: "openai";
1019
+ gemini: "gemini";
1020
+ grok: "grok";
1021
+ groq: "groq";
1022
+ openrouter: "openrouter";
1023
+ together: "together";
1024
+ deepseek: "deepseek";
1025
+ mistral: "mistral";
820
1026
  "openai-http": "openai-http";
821
1027
  "claude-cli": "claude-cli";
822
1028
  }>;
@@ -927,4 +1133,4 @@ type WireError = z.infer<typeof WireError>;
927
1133
  /** HTTP status each error code is served with. */
928
1134
  declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
929
1135
 
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 };
1136
+ export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendClass, BackendCost, 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, type SpendConsent, TERMINAL_STATES, WireError, WireErrorCode, backendDescriptor, canTransition, effectiveOfferScope, isBackendId, isJobKind, isLocalHost, isTerminal, matchAudience, payloadTextLength, provenanceFor, resolveCost };
package/dist/index.js CHANGED
@@ -4,21 +4,163 @@ import { z as z2 } from "zod";
4
4
  // src/backends.ts
5
5
  import { z } from "zod";
6
6
  var BackendClass = z.enum(["http", "process"]);
7
- var BackendAccount = z.enum(["open", "subscription"]);
7
+ var BackendCost = z.enum(["free", "metered", "subscription"]);
8
8
  var backend = (b) => Object.freeze(b);
9
9
  var BACKENDS = Object.freeze({
10
+ // -- free: local compute, costs electricity ------------------------------
11
+ ollama: backend({
12
+ id: "ollama",
13
+ label: "Ollama (local)",
14
+ class: "http",
15
+ cost: "free",
16
+ adversarialCorpus: "http",
17
+ defaultBaseUrl: "http://127.0.0.1:11434/v1"
18
+ }),
19
+ mlx: backend({
20
+ id: "mlx",
21
+ label: "MLX (mlx_lm.server, local)",
22
+ class: "http",
23
+ cost: "free",
24
+ adversarialCorpus: "http",
25
+ defaultBaseUrl: "http://127.0.0.1:8080/v1"
26
+ }),
27
+ llamacpp: backend({
28
+ id: "llamacpp",
29
+ label: "llama.cpp server (local)",
30
+ class: "http",
31
+ cost: "free",
32
+ adversarialCorpus: "http",
33
+ defaultBaseUrl: "http://127.0.0.1:8080/v1"
34
+ }),
35
+ vllm: backend({
36
+ id: "vllm",
37
+ label: "vLLM (local)",
38
+ class: "http",
39
+ cost: "free",
40
+ adversarialCorpus: "http",
41
+ defaultBaseUrl: "http://127.0.0.1:8000/v1"
42
+ }),
43
+ lmstudio: backend({
44
+ id: "lmstudio",
45
+ label: "LM Studio (local)",
46
+ class: "http",
47
+ cost: "free",
48
+ adversarialCorpus: "http",
49
+ defaultBaseUrl: "http://127.0.0.1:1234/v1"
50
+ }),
51
+ jan: backend({
52
+ id: "jan",
53
+ label: "Jan (local)",
54
+ class: "http",
55
+ cost: "free",
56
+ adversarialCorpus: "http",
57
+ defaultBaseUrl: "http://127.0.0.1:1337/v1"
58
+ }),
59
+ localai: backend({
60
+ id: "localai",
61
+ label: "LocalAI (local)",
62
+ class: "http",
63
+ cost: "free",
64
+ adversarialCorpus: "http",
65
+ defaultBaseUrl: "http://127.0.0.1:8080/v1"
66
+ }),
67
+ // -- metered: the owner's money, per token -------------------------------
68
+ /**
69
+ * Note the pair: `anthropic` and {@link BACKENDS."claude-cli"} reach the
70
+ * same vendor and land in different cost classes. That is not an
71
+ * inconsistency — it is the axis working. One bills a key per token, the
72
+ * other runs under a personal plan whose terms cover one person's work. Who
73
+ * pays and under what terms is the question; which company is not.
74
+ */
75
+ anthropic: backend({
76
+ id: "anthropic",
77
+ label: "Anthropic (your API key)",
78
+ class: "http",
79
+ cost: "metered",
80
+ adversarialCorpus: "http",
81
+ defaultBaseUrl: "https://api.anthropic.com/v1"
82
+ }),
83
+ openai: backend({
84
+ id: "openai",
85
+ label: "OpenAI (your API key)",
86
+ class: "http",
87
+ cost: "metered",
88
+ adversarialCorpus: "http",
89
+ defaultBaseUrl: "https://api.openai.com/v1"
90
+ }),
91
+ gemini: backend({
92
+ id: "gemini",
93
+ label: "Google Gemini (your API key)",
94
+ class: "http",
95
+ cost: "metered",
96
+ adversarialCorpus: "http",
97
+ defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta/openai"
98
+ }),
99
+ grok: backend({
100
+ id: "grok",
101
+ label: "xAI Grok (your API key)",
102
+ class: "http",
103
+ cost: "metered",
104
+ adversarialCorpus: "http",
105
+ defaultBaseUrl: "https://api.x.ai/v1"
106
+ }),
107
+ groq: backend({
108
+ id: "groq",
109
+ label: "Groq (your API key)",
110
+ class: "http",
111
+ cost: "metered",
112
+ adversarialCorpus: "http",
113
+ defaultBaseUrl: "https://api.groq.com/openai/v1"
114
+ }),
115
+ openrouter: backend({
116
+ id: "openrouter",
117
+ label: "OpenRouter (your API key)",
118
+ class: "http",
119
+ cost: "metered",
120
+ adversarialCorpus: "http",
121
+ defaultBaseUrl: "https://openrouter.ai/api/v1"
122
+ }),
123
+ together: backend({
124
+ id: "together",
125
+ label: "Together AI (your API key)",
126
+ class: "http",
127
+ cost: "metered",
128
+ adversarialCorpus: "http",
129
+ defaultBaseUrl: "https://api.together.xyz/v1"
130
+ }),
131
+ deepseek: backend({
132
+ id: "deepseek",
133
+ label: "DeepSeek (your API key)",
134
+ class: "http",
135
+ cost: "metered",
136
+ adversarialCorpus: "http",
137
+ defaultBaseUrl: "https://api.deepseek.com/v1"
138
+ }),
139
+ mistral: backend({
140
+ id: "mistral",
141
+ label: "Mistral (your API key)",
142
+ class: "http",
143
+ cost: "metered",
144
+ adversarialCorpus: "http",
145
+ defaultBaseUrl: "https://api.mistral.ai/v1"
146
+ }),
147
+ // -- the escape hatch ----------------------------------------------------
10
148
  "openai-http": backend({
11
149
  id: "openai-http",
12
- label: "OpenAI-compatible HTTP server (Ollama, MLX, llama.cpp, vLLM)",
150
+ label: "Any OpenAI-compatible server",
13
151
  class: "http",
14
- account: "open",
152
+ // Unknown until the base URL is known: local means free, remote means
153
+ // metered, and the owner does not get to say otherwise
154
+ // ({@link MUSTS.REMOTE_IS_NEVER_FREE}).
155
+ cost: null,
15
156
  adversarialCorpus: "http"
16
157
  }),
158
+ // -- subscription: someone else's terms ----------------------------------
17
159
  "claude-cli": backend({
18
160
  id: "claude-cli",
19
161
  label: "Claude CLI (your subscription)",
20
162
  class: "process",
21
- account: "subscription",
163
+ cost: "subscription",
22
164
  adversarialCorpus: "process"
23
165
  })
24
166
  });
@@ -32,6 +174,27 @@ function isBackendId(value) {
32
174
  function backendDescriptor(id) {
33
175
  return BACKENDS[id];
34
176
  }
177
+ function isLocalHost(hostname) {
178
+ const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
179
+ if (host === "localhost" || host.endsWith(".localhost")) return true;
180
+ if (host === "::1") return true;
181
+ if (host.startsWith("127.")) return true;
182
+ if (host.startsWith("10.")) return true;
183
+ if (host.startsWith("192.168.")) return true;
184
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(host)) return true;
185
+ if (/^f[cd]/.test(host)) return true;
186
+ return false;
187
+ }
188
+ function resolveCost(id, baseUrl) {
189
+ const declared = BACKENDS[id].cost;
190
+ if (declared !== null) return declared;
191
+ if (baseUrl === void 0) return "metered";
192
+ try {
193
+ return isLocalHost(new URL(baseUrl).hostname) ? "free" : "metered";
194
+ } catch {
195
+ return "metered";
196
+ }
197
+ }
35
198
 
36
199
  // src/audience.ts
37
200
  var Audience = z2.enum(["self", "named", "public"]);
@@ -50,12 +213,18 @@ var MatchRefusal = z2.enum([
50
213
  /** The backend offers only `self` and the job belongs to someone else. */
51
214
  "offer-scope-too-narrow",
52
215
  /** The matched backend is subscription-class, which is locked to `self`. */
53
- "subscription-self-lock"
216
+ "subscription-self-lock",
217
+ /** The backend spends the owner's money and they have not agreed to share it. */
218
+ "metered-no-spend-consent",
219
+ /** The backend is shared but has spent its ceiling for now. */
220
+ "metered-ceiling-reached"
54
221
  ]);
55
222
  var ALLOWED = Object.freeze({ ok: true });
56
223
  var refuse = (refusal) => Object.freeze({ ok: false, refusal });
57
- function effectiveOfferScope(configured, account) {
58
- return account === "subscription" ? "self" : configured;
224
+ function effectiveOfferScope(configured, cost, spend) {
225
+ if (cost === "subscription") return "self";
226
+ if (cost === "metered" && spend?.acknowledged !== true) return "self";
227
+ return configured;
59
228
  }
60
229
  function matchAudience(job, daemon) {
61
230
  const sameOwner = job.owner === daemon.owner;
@@ -65,13 +234,25 @@ function matchAudience(job, daemon) {
65
234
  if (job.audience === "named" && !sameOwner && job.audienceAllow !== void 0 && !job.audienceAllow.includes(daemon.owner)) {
66
235
  return refuse("not-in-server-allowlist");
67
236
  }
68
- const scope = effectiveOfferScope(daemon.offerScope, daemon.account);
237
+ const scope = effectiveOfferScope(
238
+ daemon.offerScope,
239
+ daemon.cost,
240
+ daemon.spend
241
+ );
69
242
  if (sameOwner) {
70
243
  return ALLOWED;
71
244
  }
72
- if (daemon.account === "subscription") {
245
+ if (daemon.cost === "subscription") {
73
246
  return refuse("subscription-self-lock");
74
247
  }
248
+ if (daemon.cost === "metered") {
249
+ if (daemon.spend?.acknowledged !== true) {
250
+ return refuse("metered-no-spend-consent");
251
+ }
252
+ if (daemon.spend.ceilingReached === true) {
253
+ return refuse("metered-ceiling-reached");
254
+ }
255
+ }
75
256
  switch (scope) {
76
257
  case "self":
77
258
  return refuse("offer-scope-too-narrow");
@@ -87,7 +268,9 @@ var REFUSAL_MESSAGES = Object.freeze({
87
268
  "not-locally-allowed": "the job's owner is not on this machine's allowlist (byollm allow <server> <user>)",
88
269
  "not-in-server-allowlist": "the app restricted this job to named runners and this machine is not one of them",
89
270
  "offer-scope-too-narrow": "this backend is offered to its owner only (byollm offer <backend> named|public to widen)",
90
- "subscription-self-lock": "subscription-backed models run their owner's work only \u2014 this is a protocol rule, not a setting"
271
+ "subscription-self-lock": "subscription-backed models run their owner's work only \u2014 this is a protocol rule, not a setting",
272
+ "metered-no-spend-consent": "this backend bills its owner per token, and they have not agreed to spend it on other people's work",
273
+ "metered-ceiling-reached": "this backend is shared but has reached the spend ceiling its owner set"
91
274
  });
92
275
 
93
276
  // src/kinds.ts
@@ -319,6 +502,30 @@ var MUSTS = Object.freeze({
319
502
  enforcedBy: "daemon",
320
503
  source: "byollm_001 \xA7The audience model"
321
504
  }),
505
+ METERED_DEFAULTS_SELF: must({
506
+ id: "METERED_DEFAULTS_SELF",
507
+ statement: "A metered backend's effective offer scope MUST be 'self' unless the owner has explicitly acknowledged spending money on others' work.",
508
+ enforcedBy: "daemon",
509
+ source: "byollm_007 \xA74"
510
+ }),
511
+ METERED_REQUIRES_CEILING: must({
512
+ id: "METERED_REQUIRES_CEILING",
513
+ statement: "A widened metered backend MUST carry a spend ceiling, and the daemon MUST refuse community work once it is reached.",
514
+ enforcedBy: "daemon",
515
+ source: "byollm_007 \xA74"
516
+ }),
517
+ COST_NOT_CONFIGURABLE: must({
518
+ id: "COST_NOT_CONFIGURABLE",
519
+ statement: "A built-in provider's cost class MUST NOT be overridable by configuration.",
520
+ enforcedBy: "daemon",
521
+ source: "byollm_007 \xA72"
522
+ }),
523
+ REMOTE_IS_NEVER_FREE: must({
524
+ id: "REMOTE_IS_NEVER_FREE",
525
+ statement: "A generic HTTP backend whose base URL is not loopback or private MUST be treated as metered.",
526
+ enforcedBy: "daemon",
527
+ source: "byollm_007 \xA72"
528
+ }),
322
529
  NAMED_LOCAL_ALLOWLIST: must({
323
530
  id: "NAMED_LOCAL_ALLOWLIST",
324
531
  statement: "A 'named' job MUST be admitted only by the daemon's own local (server origin, user id) allowlist \u2014 never on the server's assertion alone.",
@@ -602,8 +809,8 @@ export {
602
809
  Audience,
603
810
  BACKENDS,
604
811
  BACKEND_IDS,
605
- BackendAccount,
606
812
  BackendClass,
813
+ BackendCost,
607
814
  BackendIdSchema,
608
815
  Capability,
609
816
  CapabilityMatrix,
@@ -655,9 +862,11 @@ export {
655
862
  effectiveOfferScope,
656
863
  isBackendId,
657
864
  isJobKind,
865
+ isLocalHost,
658
866
  isTerminal,
659
867
  matchAudience,
660
868
  payloadTextLength,
661
- provenanceFor
869
+ provenanceFor,
870
+ resolveCost
662
871
  };
663
872
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/audience.ts","../src/backends.ts","../src/kinds.ts","../src/job.ts","../src/musts.ts","../src/wire.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { type BackendAccount } from \"./backends.js\";\n\n/**\n * Who may run a job, declared by the app that enqueued it.\n *\n * - `self` — only the job owner's own daemon.\n * - `named` — a daemon whose owner has explicitly allowed this (server, user)\n * pair in their *local* allowlist (byollm_001 Rev 1 §B).\n * - `public` — any daemon offering `public` compute.\n */\nexport const Audience = z.enum([\"self\", \"named\", \"public\"]);\nexport type Audience = z.infer<typeof Audience>;\n\n/**\n * What a daemon backend is willing to run, declared by the machine's owner.\n * Same three values as {@link Audience}, but the two are independent axes —\n * a job runs only where both agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}).\n */\nexport const OfferScope = z.enum([\"self\", \"named\", \"public\"]);\nexport type OfferScope = z.infer<typeof OfferScope>;\n\n/** All audience values, in widening order. */\nexport const AUDIENCES = Object.freeze(Audience.options);\n/** All offer scopes, in widening order. */\nexport const OFFER_SCOPES = Object.freeze(OfferScope.options);\n\n/**\n * Why a job was refused. Distinct codes because byollm_002 requires that\n * different truths never share a message — \"no matching work\" and \"refused on\n * principle\" are not the same event, and a volunteer debugging their setup\n * needs to know which one happened.\n */\nexport const MatchRefusal = z.enum([\n /** The daemon advertises no capability for this kind. */\n \"no-capability\",\n /** Job is `self` but this daemon belongs to a different user. */\n \"audience-self-other-owner\",\n /** Job is `named` but this daemon's local allowlist does not admit the owner. */\n \"not-locally-allowed\",\n /** Job is `named`/`public` but the server's own allowlist excludes this runner. */\n \"not-in-server-allowlist\",\n /** The backend offers only `self` and the job belongs to someone else. */\n \"offer-scope-too-narrow\",\n /** The matched backend is subscription-class, which is locked to `self`. */\n \"subscription-self-lock\",\n]);\nexport type MatchRefusal = z.infer<typeof MatchRefusal>;\n\n/** The outcome of an audience match. */\nexport type MatchResult =\n | { readonly ok: true }\n | { readonly ok: false; readonly refusal: MatchRefusal };\n\nconst ALLOWED: MatchResult = Object.freeze({ ok: true as const });\nconst refuse = (refusal: MatchRefusal): MatchResult =>\n Object.freeze({ ok: false as const, refusal });\n\n/**\n * The effective offer scope of a backend.\n *\n * A subscription-class backend is locked to `self` regardless of what config\n * requests ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}). This is a protocol MUST,\n * not a setting: the lock is applied here, at the one place both the daemon's\n * config loader and its matcher call, so there is no code path that observes\n * a widened subscription scope.\n */\nexport function effectiveOfferScope(\n configured: OfferScope,\n account: BackendAccount,\n): OfferScope {\n return account === \"subscription\" ? \"self\" : configured;\n}\n\n/** The job-side facts a match needs. */\nexport interface MatchJob {\n /** The app's id for the user who enqueued the job. */\n readonly owner: string;\n /** Who the app says may run it. */\n readonly audience: Audience;\n /**\n * Optional server-side restriction on which runner owners may take a\n * `named` job. Defence in depth only — the daemon's local allowlist is the\n * enforcing side ({@link MUSTS.NAMED_LOCAL_ALLOWLIST}).\n */\n readonly audienceAllow?: readonly string[] | undefined;\n}\n\n/** The daemon-side facts a match needs. */\nexport interface MatchDaemon {\n /** The app's id for the user this daemon is paired to. */\n readonly owner: string;\n /** Effective scope of the backend that would run the job. */\n readonly offerScope: OfferScope;\n /** Account class of that backend. */\n readonly account: BackendAccount;\n /**\n * Does this daemon's *local* allowlist admit the given owner for the server\n * origin the job came from? Supplied as a predicate so the protocol package\n * stays free of file I/O; the daemon passes its allowlist, the server\n * passes a conservative `() => true` because it cannot know a remote\n * daemon's local list and must not pretend to.\n */\n readonly locallyAllows: (owner: string) => boolean;\n}\n\n/**\n * Decide whether a job may run on a daemon.\n *\n * Both sides must agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}):\n * 1. the job's audience must admit the daemon's owner, and\n * 2. the backend's offer scope must admit the job's owner.\n *\n * The full nine-way matrix (three audiences × three offer scopes) is asserted\n * by the conformance kit. The function is pure and total so both the daemon\n * and the server can run the identical rule — the daemon refuses, and the\n * server refuses too (byollm_003 §Server-side MUSTs).\n *\n * @example\n * ```ts\n * const result = matchAudience(\n * { owner: \"alice\", audience: \"named\" },\n * {\n * owner: \"bob\",\n * offerScope: \"named\",\n * account: \"open\",\n * locallyAllows: (o) => o === \"alice\",\n * },\n * );\n * // result.ok === true\n * ```\n */\nexport function matchAudience(job: MatchJob, daemon: MatchDaemon): MatchResult {\n const sameOwner = job.owner === daemon.owner;\n\n // --- Side 1: does the job's audience admit this daemon's owner? --------\n if (job.audience === \"self\" && !sameOwner) {\n return refuse(\"audience-self-other-owner\");\n }\n if (\n job.audience === \"named\" &&\n !sameOwner &&\n job.audienceAllow !== undefined &&\n !job.audienceAllow.includes(daemon.owner)\n ) {\n return refuse(\"not-in-server-allowlist\");\n }\n\n // --- Side 2: does the backend's offer scope admit the job's owner? -----\n // The lock is re-applied rather than trusted: a caller that passed a\n // widened scope for a subscription backend gets a refusal, not obedience.\n const scope = effectiveOfferScope(daemon.offerScope, daemon.account);\n\n if (sameOwner) {\n // A daemon always runs its own owner's work, at any scope.\n return ALLOWED;\n }\n\n if (daemon.account === \"subscription\") {\n return refuse(\"subscription-self-lock\");\n }\n\n switch (scope) {\n case \"self\":\n return refuse(\"offer-scope-too-narrow\");\n case \"named\":\n // byollm_001 Rev 1 §B: the daemon's own list decides, not the server's.\n return daemon.locallyAllows(job.owner)\n ? ALLOWED\n : refuse(\"not-locally-allowed\");\n case \"public\":\n return ALLOWED;\n }\n}\n\n/**\n * Human-readable refusal text for the daemon's log and the trust UI.\n * Each refusal reads as a distinct truth — byollm_002's \"four different\n * truths that must never share a message\" applied to the audience axis.\n */\nexport const REFUSAL_MESSAGES: Readonly<Record<MatchRefusal, string>> =\n Object.freeze({\n \"no-capability\":\n \"no backend on this machine is configured and healthy for that job kind\",\n \"audience-self-other-owner\":\n \"the job is private to its owner and this machine is paired to someone else\",\n \"not-locally-allowed\":\n \"the job's owner is not on this machine's allowlist (byollm allow <server> <user>)\",\n \"not-in-server-allowlist\":\n \"the app restricted this job to named runners and this machine is not one of them\",\n \"offer-scope-too-narrow\":\n \"this backend is offered to its owner only (byollm offer <backend> named|public to widen)\",\n \"subscription-self-lock\":\n \"subscription-backed models run their owner's work only — this is a protocol rule, not a setting\",\n });\n","import { z } from \"zod\";\n\n/**\n * How a backend reaches its model — the taxonomy introduced in byollm_001\n * Rev 1 §A, because the two classes have different threat surfaces.\n *\n * - `http`: an OpenAI-compatible HTTP server (Ollama, `mlx_lm.server`,\n * llama.cpp server, vLLM). Spawns nothing, so byollm_004 §2's argv, stdin,\n * env and sandbox requirements are not applicable by construction. Its\n * threat surface is SSRF-shaped and bounded by {@link MUSTS.HTTP_BASE_URL_SAFE}.\n * - `process`: spawns a binary (`claude` CLI today, `mlx_lm.lora` for a\n * future `train.*` kind). All of byollm_004 §2 is mandatory here.\n */\nexport const BackendClass = z.enum([\"http\", \"process\"]);\nexport type BackendClass = z.infer<typeof BackendClass>;\n\n/**\n * Whose account pays for the inference.\n *\n * `subscription` backends run against a vendor account belonging to the\n * machine's owner. They are hard-locked to an offer scope of `self`\n * ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — one account executes one person's\n * work. This is orthogonal to {@link BackendClass}: `claude-cli` is both\n * process-class and subscription-class, while a future local `mlx_lm.lora`\n * backend would be process-class and open.\n */\nexport const BackendAccount = z.enum([\"open\", \"subscription\"]);\nexport type BackendAccount = z.infer<typeof BackendAccount>;\n\n/** The immutable facts about a backend that the protocol reasons over. */\nexport interface BackendDescriptor {\n /** Stable backend id, as written in `byollm.config.json`. */\n readonly id: string;\n /** Human-readable name for the trust UI. */\n readonly label: string;\n /** Determines which isolation requirements apply. */\n readonly class: BackendClass;\n /** Determines whether the offer scope can be widened past `self`. */\n readonly account: BackendAccount;\n /**\n * Which adversarial corpus byollm_004 §5 runs against this backend. A\n * backend cannot be registered without one — the coverage check in the\n * adversarial suite enforces it.\n */\n readonly adversarialCorpus: \"process\" | \"http\";\n}\n\nconst backend = (b: BackendDescriptor): BackendDescriptor => Object.freeze(b);\n\n/**\n * The v1 backend registry.\n *\n * byollm_001 Rev 1 §A collapses four planned backends into one HTTP-class\n * entry: Ollama, `mlx_lm.server`, llama.cpp server and vLLM all speak\n * OpenAI-compatible `/v1/chat/completions`, so they are one backend with N\n * owner-configured base URLs rather than four adapters. That is what puts\n * MLX inference in v1.\n */\nexport const BACKENDS = Object.freeze({\n \"openai-http\": backend({\n id: \"openai-http\",\n label: \"OpenAI-compatible HTTP server (Ollama, MLX, llama.cpp, vLLM)\",\n class: \"http\",\n account: \"open\",\n adversarialCorpus: \"http\",\n }),\n \"claude-cli\": backend({\n id: \"claude-cli\",\n label: \"Claude CLI (your subscription)\",\n class: \"process\",\n account: \"subscription\",\n adversarialCorpus: \"process\",\n }),\n} as const satisfies Record<string, BackendDescriptor>);\n\n/** The id of a registered backend. */\nexport type BackendId = keyof typeof BACKENDS;\n\n/** All registered backend ids — the adversarial coverage check iterates this. */\nexport const BACKEND_IDS = Object.freeze(Object.keys(BACKENDS) as BackendId[]);\n\nexport const BackendIdSchema = z.enum(\n BACKEND_IDS as [BackendId, ...BackendId[]],\n);\n\n/** Narrow an arbitrary string to a registered backend id. */\nexport function isBackendId(value: string): value is BackendId {\n return Object.hasOwn(BACKENDS, value);\n}\n\n/**\n * Look up a backend descriptor.\n *\n * @throws if the id is not registered — an unregistered backend has no\n * adversarial corpus, so refusing is the safe direction.\n */\nexport function backendDescriptor(id: BackendId): BackendDescriptor {\n return BACKENDS[id];\n}\n","import { z } from \"zod\";\n\n/**\n * Upper bounds on payload size, enforced at the schema so oversized input is\n * refused at parse time rather than somewhere deeper.\n *\n * byollm_004 §4 requires stricter limits for community (`named`/`public`)\n * jobs; those are applied on top of these by the daemon's budget check, which\n * knows the job's audience. These are the absolute ceilings for any job.\n */\nexport const PAYLOAD_LIMITS = Object.freeze({\n /** Max characters in any single text field. */\n maxTextChars: 1_000_000,\n /** Max messages in an `llm.chat` conversation. */\n maxMessages: 256,\n /** Max characters across the whole payload. */\n maxTotalChars: 4_000_000,\n});\n\n/**\n * A conversation turn. `role` is a closed enum — it is routing *within the\n * model call*, not routing of the call, so it cannot select a backend.\n */\nexport const ChatMessage = z.object({\n role: z.enum([\"system\", \"user\", \"assistant\"]),\n content: z.string().max(PAYLOAD_LIMITS.maxTextChars),\n});\nexport type ChatMessage = z.infer<typeof ChatMessage>;\n\n/**\n * Payload for `llm.generate`.\n *\n * @remarks\n * Text only, deliberately. byollm_004 §1 states the payload is \"data handed\n * to a model, never configuration and never a command\", so v0 carries no\n * sampling parameters, no model name, no base URL and no flags — those are\n * owner-side route config. A future `params` field with an explicit closed\n * allowlist and owner-set clamps is reserved; adding a field later is\n * non-breaking, removing one is not.\n */\nexport const GeneratePayload = z\n .object({\n prompt: z.string().min(1).max(PAYLOAD_LIMITS.maxTextChars),\n system: z.string().max(PAYLOAD_LIMITS.maxTextChars).optional(),\n })\n .strict();\nexport type GeneratePayload = z.infer<typeof GeneratePayload>;\n\n/** Payload for `llm.chat`. Text only, for the same reason as {@link GeneratePayload}. */\nexport const ChatPayload = z\n .object({\n messages: z.array(ChatMessage).min(1).max(PAYLOAD_LIMITS.maxMessages),\n system: z.string().max(PAYLOAD_LIMITS.maxTextChars).optional(),\n })\n .strict();\nexport type ChatPayload = z.infer<typeof ChatPayload>;\n\n/**\n * The job kinds a v1 daemon has handlers for.\n *\n * Kinds are resolved against handlers baked into the daemon\n * ({@link MUSTS.KIND_TYPED_ONLY}); an unknown kind is refused, never guessed.\n * Adding a kind is a protocol change with its own spec and threat review —\n * notably any kind that needs tools, which byollm_004 §2 forbids as a payload\n * flag.\n */\nexport const JobKind = z.enum([\"llm.generate\", \"llm.chat\"]);\nexport type JobKind = z.infer<typeof JobKind>;\n\n/** All v1 job kinds. */\nexport const JOB_KINDS = Object.freeze(JobKind.options);\n\n/** A payload discriminated by its kind. */\nexport const KindedPayload = z.discriminatedUnion(\"kind\", [\n z.object({ kind: z.literal(\"llm.generate\"), payload: GeneratePayload }),\n z.object({ kind: z.literal(\"llm.chat\"), payload: ChatPayload }),\n]);\nexport type KindedPayload = z.infer<typeof KindedPayload>;\n\n/** The payload type for a given kind. */\nexport type PayloadFor<K extends JobKind> = K extends \"llm.generate\"\n ? GeneratePayload\n : ChatPayload;\n\n/** Narrow an arbitrary string to a known job kind. */\nexport function isJobKind(value: string): value is JobKind {\n return (JOB_KINDS as readonly string[]).includes(value);\n}\n\n/**\n * Total character weight of a payload, used by the daemon's community budget\n * check and by the server's payload-size limits.\n */\nexport function payloadTextLength(kinded: KindedPayload): number {\n if (kinded.kind === \"llm.generate\") {\n return kinded.payload.prompt.length + (kinded.payload.system?.length ?? 0);\n }\n const messages = kinded.payload.messages.reduce(\n (sum, m) => sum + m.content.length,\n 0,\n );\n return messages + (kinded.payload.system?.length ?? 0);\n}\n","import { z } from \"zod\";\nimport { Audience } from \"./audience.js\";\nimport { BackendClass } from \"./backends.js\";\nimport { ChatPayload, GeneratePayload, JobKind } from \"./kinds.js\";\n\n/**\n * The job lifecycle, made explicit by byollm_001 Rev 1 §D because the most\n * user-visible failure mode — \"nothing is running my job\" — was previously\n * unspecified.\n *\n * ```text\n * queued ──claim──▶ claimed ──start──▶ running ──▶ ok | error | canceled\n * │ │ │\n * │ └──lease expiry─────┘\n * │ ▼\n * │ queued (reclaimable, no loss)\n * └──ttl elapsed──▶ expired\n * ```\n */\nexport const JobState = z.enum([\n \"queued\",\n \"claimed\",\n \"running\",\n \"ok\",\n \"error\",\n \"canceled\",\n \"expired\",\n]);\nexport type JobState = z.infer<typeof JobState>;\n\n/** States from which a job never moves again. */\nexport const TERMINAL_STATES = Object.freeze([\n \"ok\",\n \"error\",\n \"canceled\",\n \"expired\",\n] as const satisfies readonly JobState[]);\n\n/** Is this a state the job can never leave? */\nexport function isTerminal(state: JobState): boolean {\n return (TERMINAL_STATES as readonly JobState[]).includes(state);\n}\n\n/**\n * The legal transitions. Held as data so the store adapters and the\n * conformance kit agree on one definition rather than three implementations.\n */\nconst TRANSITIONS: Readonly<Record<JobState, readonly JobState[]>> =\n Object.freeze({\n queued: [\"claimed\", \"expired\", \"canceled\"],\n // A claimed job returns to `queued` when its lease expires un-renewed\n // ({@link MUSTS.LEASE_RECLAIMABLE}).\n claimed: [\"running\", \"queued\", \"canceled\", \"error\"],\n running: [\"ok\", \"error\", \"canceled\", \"queued\"],\n ok: [],\n error: [],\n canceled: [],\n expired: [],\n });\n\n/** May a job move from `from` to `to`? */\nexport function canTransition(from: JobState, to: JobState): boolean {\n return TRANSITIONS[from].includes(to);\n}\n\n/** A lease: the right to work on a job until `expiresAt`. */\nexport const Lease = z.object({\n /** The runner holding the lease. */\n runnerId: z.string().min(1),\n /** Epoch milliseconds after which the claim is void. */\n expiresAt: z.number().int().positive(),\n});\nexport type Lease = z.infer<typeof Lease>;\n\n/** Payload union as it appears on a job record. */\nexport const JobPayload = z.union([GeneratePayload, ChatPayload]);\nexport type JobPayload = z.infer<typeof JobPayload>;\n\n/**\n * A job as the daemon receives it from `/byollm/claim`.\n *\n * Note what is absent: no model, no backend, no base URL, no flags, no path.\n * Those come from the machine owner's config only\n * ({@link MUSTS.NO_PAYLOAD_ROUTING}). The wire shape is the first place that\n * rule is enforced — there is no field to carry them.\n */\nexport const ClaimedJob = z\n .object({\n id: z.string().min(1),\n kind: JobKind,\n payload: JobPayload,\n audience: Audience,\n /** The app's id for the user who enqueued it. */\n owner: z.string().min(1),\n /** Runner owners the app restricted a `named` job to, if any. */\n audienceAllow: z.array(z.string().min(1)).optional(),\n lease: Lease,\n })\n .strict();\nexport type ClaimedJob = z.infer<typeof ClaimedJob>;\n\n/**\n * The provenance that travels with every result to the delivery seam.\n *\n * byollm_003 Rev 1: a `named`/`public` result is attacker-controlled text.\n * The app must never render volunteer output as its own AI's answer without\n * knowing that is what it is ({@link MUSTS.RESULT_PROVENANCE}).\n */\nexport const ResultProvenance = z\n .object({\n /** The audience the job ran under. */\n audience: Audience,\n /** The runner that produced it. */\n runnerId: z.string().min(1),\n /** The runner owner's id in this app's namespace. */\n runnerOwner: z.string().min(1),\n /** Which backend class produced it — an HTTP call or a sandboxed spawn. */\n backendClass: BackendClass,\n /** The model the runner reports having used. */\n model: z.string().min(1),\n /**\n * False only for `self` jobs. When true the app MUST treat `text` as\n * untrusted third-party content.\n */\n untrusted: z.boolean(),\n })\n .strict();\nexport type ResultProvenance = z.infer<typeof ResultProvenance>;\n\n/**\n * Build provenance for a completed job. `untrusted` is derived, never\n * supplied, so no caller can mark volunteer output as first-party.\n */\nexport function provenanceFor(input: {\n audience: Audience;\n runnerId: string;\n runnerOwner: string;\n backendClass: BackendClass;\n model: string;\n}): ResultProvenance {\n return {\n audience: input.audience,\n runnerId: input.runnerId,\n runnerOwner: input.runnerOwner,\n backendClass: input.backendClass,\n model: input.model,\n untrusted: input.audience !== \"self\",\n };\n}\n\n/** Successful outcome. */\nexport const JobResultOk = z\n .object({\n outcome: z.literal(\"ok\"),\n text: z.string(),\n /** Optional reference to a stored artifact; never a local path. */\n artifactUrl: z.url().optional(),\n })\n .strict();\n\n/** Failed outcome. `code` is a stable machine string; `message` is for humans. */\nexport const JobResultError = z\n .object({\n outcome: z.literal(\"error\"),\n code: z.string().min(1),\n message: z.string().min(1),\n /** Whether the app may reasonably re-enqueue. */\n retryable: z.boolean(),\n })\n .strict();\n\n/** Cancelled outcome, reported by the daemon after honoring a cancel. */\nexport const JobResultCanceled = z\n .object({\n outcome: z.literal(\"canceled\"),\n })\n .strict();\n\nexport const JobOutcome = z.discriminatedUnion(\"outcome\", [\n JobResultOk,\n JobResultError,\n JobResultCanceled,\n]);\nexport type JobOutcome = z.infer<typeof JobOutcome>;\n\n/** A completed job as delivered to the app, provenance attached. */\nexport const DeliveredResult = z\n .object({\n jobId: z.string().min(1),\n state: JobState,\n outcome: JobOutcome.optional(),\n provenance: ResultProvenance.optional(),\n })\n .strict();\nexport type DeliveredResult = z.infer<typeof DeliveredResult>;\n","/**\n * The normative MUSTs of protocol v0, as data.\n *\n * byollm_001 requires that \"every MUST above has a conformance test id\n * referenced inline\". Keeping the MUSTs as a frozen registry rather than\n * prose is what makes that requirement *checkable*: the conformance kit\n * imports {@link MUSTS} and fails if any id has no test asserting it, so a\n * new MUST cannot be added without a test and a test cannot silently drift\n * away from the statement it claims to prove.\n *\n * Ids are stable and public — third-party servers cite them in their\n * certification output.\n */\n\n/** Which side of the wire is obliged to enforce a given MUST. */\nexport type MustEnforcer = \"daemon\" | \"server\" | \"both\";\n\n/** A single normative requirement of the protocol. */\nexport interface Must {\n /** Stable public id, cited by conformance output. */\n readonly id: string;\n /** The requirement, in MUST language. */\n readonly statement: string;\n /** Which implementation is obliged to enforce it. */\n readonly enforcedBy: MustEnforcer;\n /** Spec section this was adjudicated in. */\n readonly source: string;\n}\n\nconst must = (m: Must): Must => Object.freeze(m);\n\n/**\n * Every normative MUST in protocol v0.\n *\n * @remarks\n * Grouped by concern for readability; the conformance kit treats this as a\n * flat set. Adding an entry here without a corresponding conformance test is\n * a CI failure, by design.\n */\nexport const MUSTS = Object.freeze({\n // ---- Pairing and identity -------------------------------------------\n PAIR_ONE_USER: must({\n id: \"PAIR_ONE_USER\",\n statement:\n \"A runner token MUST be bound to exactly one user; a daemon MUST refuse \" +\n \"work not attributable to its paired user.\",\n enforcedBy: \"both\",\n source: \"byollm_001 §MUSTs\",\n }),\n PAIR_INTERACTIVE: must({\n id: \"PAIR_INTERACTIVE\",\n statement:\n \"Pairing MUST be interactive (device-code approval in the app's own \" +\n \"session); a long-lived pasted secret MUST NOT be accepted as pairing.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.1\",\n }),\n PAIR_CODE_EXPIRES: must({\n id: \"PAIR_CODE_EXPIRES\",\n statement:\n \"An unapproved device code MUST expire and MUST NOT be redeemable after \" +\n \"expiry.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.1\",\n }),\n\n // ---- Typed job kinds --------------------------------------------------\n KIND_TYPED_ONLY: must({\n id: \"KIND_TYPED_ONLY\",\n statement:\n \"Job kinds MUST resolve against handlers baked into the daemon. A daemon \" +\n \"MUST refuse an unknown kind rather than guess.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §Jobs are typed data\",\n }),\n KIND_NO_CODE: must({\n id: \"KIND_NO_CODE\",\n statement:\n \"A server MUST NOT be able to convey code, a shell string, or a path to \" +\n \"execute; payloads are data handed to a model only.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §Jobs are typed data; byollm_004 §1\",\n }),\n\n // ---- Capability and claiming -----------------------------------------\n CLAIM_REQUIRES_CAPABILITY: must({\n id: \"CLAIM_REQUIRES_CAPABILITY\",\n statement:\n \"A daemon MUST NOT be given a job whose kind is absent from its \" +\n \"advertised capability matrix.\",\n enforcedBy: \"both\",\n source: \"byollm_001 §MUSTs\",\n }),\n CAPABILITY_IS_DETECTED: must({\n id: \"CAPABILITY_IS_DETECTED\",\n statement:\n \"An advertised capability matrix MUST be the intersection of owner \" +\n \"config and detected, healthy reality — never config alone.\",\n enforcedBy: \"daemon\",\n source: \"byollm_002 §Routing\",\n }),\n CLAIM_ATOMIC: must({\n id: \"CLAIM_ATOMIC\",\n statement:\n \"Claiming MUST be atomic: a job MUST NOT be handed to two runners \" +\n \"concurrently.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.2\",\n }),\n\n // ---- Leases -----------------------------------------------------------\n LEASE_HONORED: must({\n id: \"LEASE_HONORED\",\n statement:\n \"A daemon MUST stop work on a job whose lease it has failed to renew, \" +\n \"and MUST NOT report a result for an expired lease it no longer holds.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §MUSTs\",\n }),\n LEASE_RECLAIMABLE: must({\n id: \"LEASE_RECLAIMABLE\",\n statement:\n \"A lease that expires un-renewed MUST make its job claimable again with \" +\n \"no loss of the job.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.2\",\n }),\n\n // ---- Audience and offer scope ----------------------------------------\n AUDIENCE_BOTH_SIDES: must({\n id: \"AUDIENCE_BOTH_SIDES\",\n statement:\n \"A job MUST run on a daemon only if the daemon's offer scope admits the \" +\n \"job's owner AND the job's audience admits the daemon's owner.\",\n enforcedBy: \"both\",\n source: \"byollm_001 §The audience model\",\n }),\n SUBSCRIPTION_SELF_LOCK: must({\n id: \"SUBSCRIPTION_SELF_LOCK\",\n statement:\n \"A subscription-class backend's offer scope MUST be 'self' and MUST NOT \" +\n \"be widened by configuration.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §The audience model\",\n }),\n NAMED_LOCAL_ALLOWLIST: must({\n id: \"NAMED_LOCAL_ALLOWLIST\",\n statement:\n \"A 'named' job MUST be admitted only by the daemon's own local \" +\n \"(server origin, user id) allowlist — never on the server's assertion \" +\n \"alone.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 Rev 1 §B\",\n }),\n\n REFUSAL_NOT_REOFFERED: must({\n id: \"REFUSAL_NOT_REOFFERED\",\n statement:\n \"A server MUST NOT re-offer a job to a runner that released it with \" +\n \"reason 'refused'.\",\n enforcedBy: \"server\",\n source: \"byollm_001 Rev 1 §B (loop resolved in build review)\",\n }),\n\n // ---- Revocation and cancel -------------------------------------------\n REVOCATION_HONORED: must({\n id: \"REVOCATION_HONORED\",\n statement:\n \"A revoked daemon MUST stop claiming and MUST abandon in-flight work by \" +\n \"the next heartbeat at the latest.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §MUSTs\",\n }),\n CANCEL_HONORED: must({\n id: \"CANCEL_HONORED\",\n statement:\n \"A job id in a heartbeat response's cancel list MUST abort that job's \" +\n \"in-flight backend call and be reported as 'canceled'.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 Rev 1 §C\",\n }),\n\n // ---- Lifecycle, dependencies, delivery -------------------------------\n DEPENDS_ON_GATING: must({\n id: \"DEPENDS_ON_GATING\",\n statement:\n \"A job MUST NOT be claimable until every job in its dependsOn set has \" +\n \"reached the 'ok' state.\",\n enforcedBy: \"server\",\n source: \"byollm_001 Rev 1 §E\",\n }),\n TTL_EXPIRY: must({\n id: \"TTL_EXPIRY\",\n statement:\n \"An unclaimed job MUST become 'expired' once its TTL elapses, and the \" +\n \"TTL clock MUST start when the job becomes claimable, not at enqueue.\",\n enforcedBy: \"server\",\n source: \"byollm_001 Rev 1 §D (TTL clock resolved in build review)\",\n }),\n NO_RUNNER_SIGNAL: must({\n id: \"NO_RUNNER_SIGNAL\",\n statement:\n \"A server MUST surface noRunnerAvailable when no runner with matching \" +\n \"capability has heartbeated within the liveness window, and MUST NOT \" +\n \"raise it for a job still blocked on dependencies.\",\n enforcedBy: \"server\",\n source: \"byollm_001 Rev 1 §D\",\n }),\n RESULT_IDEMPOTENT: must({\n id: \"RESULT_IDEMPOTENT\",\n statement:\n \"Result submission MUST be idempotent by job id; the first terminal \" +\n \"outcome wins and later submissions MUST NOT change it.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.4\",\n }),\n RESULT_PROVENANCE: must({\n id: \"RESULT_PROVENANCE\",\n statement:\n \"A result from a non-'self' job MUST carry its provenance (audience and \" +\n \"runner) to the delivery seam so an app never treats volunteer output \" +\n \"as first-party.\",\n enforcedBy: \"server\",\n source: \"byollm_003 Rev 1 §Return-trip\",\n }),\n\n // ---- The trust surface -------------------------------------------------\n INGRESS_LOGGED_BEFORE_EXECUTION: must({\n id: \"INGRESS_LOGGED_BEFORE_EXECUTION\",\n statement:\n \"Every executed prompt MUST be appended to the local ingress log before \" +\n \"execution begins.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §MUSTs\",\n }),\n\n // ---- Execution isolation (byollm_004) ---------------------------------\n NO_SHELL_INTERPOLATION: must({\n id: \"NO_SHELL_INTERPOLATION\",\n statement:\n \"Process-class backends MUST be invoked with a fixed argv array and the \" +\n \"payload delivered on stdin; payload text MUST NOT reach a command line.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §2\",\n }),\n NO_PAYLOAD_ROUTING: must({\n id: \"NO_PAYLOAD_ROUTING\",\n statement:\n \"Model, backend, base URL, and flags MUST come from owner config only; \" +\n \"a payload MUST NOT influence any of them.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §2\",\n }),\n STRIPPED_CHILD_ENV: must({\n id: \"STRIPPED_CHILD_ENV\",\n statement:\n \"Process-class children MUST spawn with an allowlisted environment, a \" +\n \"scratch cwd, no inherited descriptors beyond std streams, and hard \" +\n \"timeout and output-size caps.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §2\",\n }),\n HTTP_BASE_URL_SAFE: must({\n id: \"HTTP_BASE_URL_SAFE\",\n statement:\n \"HTTP-class backends MUST send requests only to the owner-configured \" +\n \"base URL and MUST refuse base URLs resolving to cloud-metadata or \" +\n \"link-local addresses.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 Rev 1 §Backend taxonomy\",\n }),\n OUTPUT_INERT: must({\n id: \"OUTPUT_INERT\",\n statement:\n \"Returned text MUST be treated as inert bytes: never evaluated, never \" +\n \"written to a payload-named path, never interpolated into a shell or \" +\n \"into terminal control sequences when logged.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §2\",\n }),\n COMMUNITY_BUDGETS: must({\n id: \"COMMUNITY_BUDGETS\",\n statement:\n \"Jobs whose owner is not the daemon's owner MUST be subject to the \" +\n \"owner's rate limits, daily cap, and resource budget.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §4\",\n }),\n} as const satisfies Record<string, Must>);\n\n/** The id of any normative MUST. */\nexport type MustId = keyof typeof MUSTS;\n\n/** All MUST ids, for coverage checks. */\nexport const MUST_IDS = Object.freeze(Object.keys(MUSTS) as MustId[]);\n","import { z } from \"zod\";\nimport { OfferScope } from \"./audience.js\";\nimport { BackendClass, BackendIdSchema } from \"./backends.js\";\nimport { ClaimedJob, JobOutcome } from \"./job.js\";\nimport { JobKind } from \"./kinds.js\";\n\n/** Protocol version carried on every request; servers refuse what they can't speak. */\nexport const PROTOCOL_VERSION = \"0\" as const;\n\n/** The path prefix all endpoints mount under. */\nexport const PROTOCOL_PREFIX = \"/byollm\" as const;\n\n/** The five endpoint names, in the order byollm_001 lists them. */\nexport const ENDPOINTS = Object.freeze([\n \"pair\",\n \"claim\",\n \"heartbeat\",\n \"result\",\n \"release\",\n] as const);\nexport type Endpoint = (typeof ENDPOINTS)[number];\n\n/**\n * One entry of the capability matrix: a kind this daemon can actually serve,\n * right now, with the backend and model that would serve it.\n *\n * Derived from owner config intersected with detected reality\n * ({@link MUSTS.CAPABILITY_IS_DETECTED}) — a configured-but-unreachable\n * backend must not appear here. Carries `backendClass` so the app can tell\n * whether a result came from a sandboxed spawn or an HTTP call\n * (byollm_001 Rev 1 §A).\n */\nexport const Capability = z\n .object({\n kind: JobKind,\n backendId: BackendIdSchema,\n backendClass: BackendClass,\n model: z.string().min(1),\n offerScope: OfferScope,\n })\n .strict();\nexport type Capability = z.infer<typeof Capability>;\n\n/** The capability matrix a daemon advertises. */\nexport const CapabilityMatrix = z.array(Capability);\nexport type CapabilityMatrix = z.infer<typeof CapabilityMatrix>;\n\n// ---------------------------------------------------------------------------\n// 1. POST /byollm/pair — device-code flow\n// ---------------------------------------------------------------------------\n\n/**\n * Pairing is a device-code exchange, not a pasted secret\n * ({@link MUSTS.PAIR_INTERACTIVE}). The daemon starts a pairing, shows the\n * user a short code and a URL, and polls until the user approves it inside\n * the app's own authenticated session. Nothing listens on the user's machine\n * and nothing works over a copied string alone.\n */\nexport const PairStartRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n action: z.literal(\"start\"),\n daemon: z.object({\n version: z.string().min(1),\n /** Shown in the app's runner list so a user can tell their machines apart. */\n label: z.string().min(1).max(120),\n platform: z.enum([\"darwin\", \"linux\", \"win32\"]),\n }),\n capabilities: CapabilityMatrix,\n })\n .strict();\nexport type PairStartRequest = z.infer<typeof PairStartRequest>;\n\nexport const PairStartResponse = z\n .object({\n /** Secret the daemon polls with. Never shown to the user. */\n deviceCode: z.string().min(20),\n /** Short code the user reads and confirms in the browser. */\n userCode: z.string().min(4).max(16),\n /** Where the user approves. Must be on the server's own origin. */\n verificationUrl: z.url(),\n /** Epoch ms after which the code is dead ({@link MUSTS.PAIR_CODE_EXPIRES}). */\n expiresAt: z.number().int().positive(),\n /** How often the daemon may poll. */\n pollIntervalMs: z.number().int().min(500).max(60_000),\n })\n .strict();\nexport type PairStartResponse = z.infer<typeof PairStartResponse>;\n\nexport const PairPollRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n action: z.literal(\"poll\"),\n deviceCode: z.string().min(20),\n })\n .strict();\nexport type PairPollRequest = z.infer<typeof PairPollRequest>;\n\nexport const PairPollResponse = z.discriminatedUnion(\"status\", [\n z.object({ status: z.literal(\"pending\") }).strict(),\n z.object({ status: z.literal(\"denied\") }).strict(),\n z.object({ status: z.literal(\"expired\") }).strict(),\n z\n .object({\n status: z.literal(\"approved\"),\n /** Bearer token for every later call. Scoped to exactly one user. */\n runnerToken: z.string().min(20),\n runnerId: z.string().min(1),\n /** The app's id for the approving user — this daemon's owner forever. */\n owner: z.string().min(1),\n /** Display name for the trust UI, if the app offers one. */\n ownerLabel: z.string().optional(),\n })\n .strict(),\n]);\nexport type PairPollResponse = z.infer<typeof PairPollResponse>;\n\nexport const PairRequest = z.discriminatedUnion(\"action\", [\n PairStartRequest,\n PairPollRequest,\n]);\nexport type PairRequest = z.infer<typeof PairRequest>;\n\n// ---------------------------------------------------------------------------\n// 2. POST /byollm/claim\n// ---------------------------------------------------------------------------\n\nexport const ClaimRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n runnerId: z.string().min(1),\n /** Re-sent on every claim so a server never matches against a stale matrix. */\n capabilities: CapabilityMatrix,\n /** Upper bound on jobs to return; the server may return fewer. */\n max: z.number().int().min(1).max(64),\n })\n .strict();\nexport type ClaimRequest = z.infer<typeof ClaimRequest>;\n\nexport const ClaimResponse = z\n .object({\n jobs: z.array(ClaimedJob),\n /** Lease duration granted, so the daemon knows its renewal deadline. */\n leaseMs: z.number().int().positive(),\n })\n .strict();\nexport type ClaimResponse = z.infer<typeof ClaimResponse>;\n\n// ---------------------------------------------------------------------------\n// 3. POST /byollm/heartbeat\n// ---------------------------------------------------------------------------\n\nexport const HeartbeatRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n runnerId: z.string().min(1),\n daemonVersion: z.string().min(1),\n capabilities: CapabilityMatrix,\n /** Jobs this daemon believes it holds; the server renews their leases. */\n activeJobIds: z.array(z.string().min(1)),\n /** True while the owner has the daemon paused; the server stops offering work. */\n paused: z.boolean(),\n })\n .strict();\nexport type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;\n\nexport const HeartbeatResponse = z\n .object({\n /** Once true, the daemon stops claiming and abandons in-flight work. */\n revoked: z.boolean(),\n /**\n * Per-job cancel (byollm_001 Rev 1 §C). The daemon aborts these jobs'\n * in-flight backend calls and reports them `canceled`.\n */\n cancel: z.array(z.string().min(1)),\n /** Jobs whose leases were renewed, with their new expiry. */\n leases: z.array(\n z\n .object({\n jobId: z.string().min(1),\n expiresAt: z.number().int().positive(),\n })\n .strict(),\n ),\n /**\n * Jobs the daemon thinks it holds but the server has reassigned or\n * expired. The daemon must stop work on these and not report results.\n */\n lost: z.array(z.string().min(1)),\n /** Server clock, so a daemon with a skewed clock still honors leases. */\n serverTime: z.number().int().positive(),\n })\n .strict();\nexport type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;\n\n// ---------------------------------------------------------------------------\n// 4. POST /byollm/result\n// ---------------------------------------------------------------------------\n\nexport const ResultRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n runnerId: z.string().min(1),\n jobId: z.string().min(1),\n outcome: JobOutcome,\n /** Which model actually served it, for the result's provenance. */\n model: z.string().min(1),\n backendClass: BackendClass,\n /** Wall-clock milliseconds the backend call took. */\n durationMs: z.number().int().nonnegative(),\n })\n .strict();\nexport type ResultRequest = z.infer<typeof ResultRequest>;\n\nexport const ResultResponse = z\n .object({\n /**\n * False when the submission lost an idempotency race or the lease was\n * already gone — the daemon should discard, not retry\n * ({@link MUSTS.RESULT_IDEMPOTENT}).\n */\n accepted: z.boolean(),\n /** The job's state after this submission. */\n state: z.string().min(1),\n })\n .strict();\nexport type ResultResponse = z.infer<typeof ResultResponse>;\n\n// ---------------------------------------------------------------------------\n// 5. POST /byollm/release\n// ---------------------------------------------------------------------------\n\nexport const ReleaseRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n runnerId: z.string().min(1),\n jobIds: z.array(z.string().min(1)),\n /**\n * Why, so the app's runner list can say something true.\n *\n * `refused` is load-bearing, not cosmetic: the server cannot evaluate a\n * daemon's *local* `named` allowlist (§4.2), so it may legitimately offer\n * a job this daemon then declines. The server MUST record the refusal and\n * stop offering that job to that runner, or the pair would spin between\n * claim and release forever.\n */\n reason: z.enum([\"shutdown\", \"pause\", \"revoked\", \"backend-down\", \"refused\"]),\n })\n .strict();\nexport type ReleaseRequest = z.infer<typeof ReleaseRequest>;\n\nexport const ReleaseResponse = z\n .object({\n released: z.array(z.string().min(1)),\n })\n .strict();\nexport type ReleaseResponse = z.infer<typeof ReleaseResponse>;\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Wire error codes.\n *\n * byollm_002 requires that \"server unreachable\", \"revoked\", \"no matching\n * work\" and \"backend down\" never share a message. Distinct codes here are how\n * the daemon can tell three of those apart; the fourth is a transport failure\n * with no response at all.\n */\nexport const WireErrorCode = z.enum([\n \"bad-request\",\n \"unsupported-protocol-version\",\n \"unauthorized\",\n \"revoked\",\n \"not-found\",\n \"rate-limited\",\n \"server-error\",\n]);\nexport type WireErrorCode = z.infer<typeof WireErrorCode>;\n\nexport const WireError = z\n .object({\n error: WireErrorCode,\n message: z.string().min(1),\n /** Seconds; mirrors Retry-After for `rate-limited` and `server-error`. */\n retryAfter: z.number().int().nonnegative().optional(),\n })\n .strict();\nexport type WireError = z.infer<typeof WireError>;\n\n/** HTTP status each error code is served with. */\nexport const ERROR_STATUS: Readonly<Record<WireErrorCode, number>> =\n Object.freeze({\n \"bad-request\": 400,\n \"unsupported-protocol-version\": 400,\n unauthorized: 401,\n revoked: 403,\n \"not-found\": 404,\n \"rate-limited\": 429,\n \"server-error\": 500,\n });\n"],"mappings":";AAAA,SAAS,KAAAA,UAAS;;;ACAlB,SAAS,SAAS;AAaX,IAAM,eAAe,EAAE,KAAK,CAAC,QAAQ,SAAS,CAAC;AAa/C,IAAM,iBAAiB,EAAE,KAAK,CAAC,QAAQ,cAAc,CAAC;AAqB7D,IAAM,UAAU,CAAC,MAA4C,OAAO,OAAO,CAAC;AAWrE,IAAM,WAAW,OAAO,OAAO;AAAA,EACpC,eAAe,QAAQ;AAAA,IACrB,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,SAAS;AAAA,IACT,mBAAmB;AAAA,EACrB,CAAC;AAAA,EACD,cAAc,QAAQ;AAAA,IACpB,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,OAAO;AAAA,IACP,SAAS;AAAA,IACT,mBAAmB;AAAA,EACrB,CAAC;AACH,CAAsD;AAM/C,IAAM,cAAc,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAgB;AAEtE,IAAM,kBAAkB,EAAE;AAAA,EAC/B;AACF;AAGO,SAAS,YAAY,OAAmC;AAC7D,SAAO,OAAO,OAAO,UAAU,KAAK;AACtC;AAQO,SAAS,kBAAkB,IAAkC;AAClE,SAAO,SAAS,EAAE;AACpB;;;ADvFO,IAAM,WAAWC,GAAE,KAAK,CAAC,QAAQ,SAAS,QAAQ,CAAC;AAQnD,IAAM,aAAaA,GAAE,KAAK,CAAC,QAAQ,SAAS,QAAQ,CAAC;AAIrD,IAAM,YAAY,OAAO,OAAO,SAAS,OAAO;AAEhD,IAAM,eAAe,OAAO,OAAO,WAAW,OAAO;AAQrD,IAAM,eAAeA,GAAE,KAAK;AAAA;AAAA,EAEjC;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AACF,CAAC;AAQD,IAAM,UAAuB,OAAO,OAAO,EAAE,IAAI,KAAc,CAAC;AAChE,IAAM,SAAS,CAAC,YACd,OAAO,OAAO,EAAE,IAAI,OAAgB,QAAQ,CAAC;AAWxC,SAAS,oBACd,YACA,SACY;AACZ,SAAO,YAAY,iBAAiB,SAAS;AAC/C;AA4DO,SAAS,cAAc,KAAe,QAAkC;AAC7E,QAAM,YAAY,IAAI,UAAU,OAAO;AAGvC,MAAI,IAAI,aAAa,UAAU,CAAC,WAAW;AACzC,WAAO,OAAO,2BAA2B;AAAA,EAC3C;AACA,MACE,IAAI,aAAa,WACjB,CAAC,aACD,IAAI,kBAAkB,UACtB,CAAC,IAAI,cAAc,SAAS,OAAO,KAAK,GACxC;AACA,WAAO,OAAO,yBAAyB;AAAA,EACzC;AAKA,QAAM,QAAQ,oBAAoB,OAAO,YAAY,OAAO,OAAO;AAEnE,MAAI,WAAW;AAEb,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,YAAY,gBAAgB;AACrC,WAAO,OAAO,wBAAwB;AAAA,EACxC;AAEA,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,OAAO,wBAAwB;AAAA,IACxC,KAAK;AAEH,aAAO,OAAO,cAAc,IAAI,KAAK,IACjC,UACA,OAAO,qBAAqB;AAAA,IAClC,KAAK;AACH,aAAO;AAAA,EACX;AACF;AAOO,IAAM,mBACX,OAAO,OAAO;AAAA,EACZ,iBACE;AAAA,EACF,6BACE;AAAA,EACF,uBACE;AAAA,EACF,2BACE;AAAA,EACF,0BACE;AAAA,EACF,0BACE;AACJ,CAAC;;;AElMH,SAAS,KAAAC,UAAS;AAUX,IAAM,iBAAiB,OAAO,OAAO;AAAA;AAAA,EAE1C,cAAc;AAAA;AAAA,EAEd,aAAa;AAAA;AAAA,EAEb,eAAe;AACjB,CAAC;AAMM,IAAM,cAAcA,GAAE,OAAO;AAAA,EAClC,MAAMA,GAAE,KAAK,CAAC,UAAU,QAAQ,WAAW,CAAC;AAAA,EAC5C,SAASA,GAAE,OAAO,EAAE,IAAI,eAAe,YAAY;AACrD,CAAC;AAcM,IAAM,kBAAkBA,GAC5B,OAAO;AAAA,EACN,QAAQA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,eAAe,YAAY;AAAA,EACzD,QAAQA,GAAE,OAAO,EAAE,IAAI,eAAe,YAAY,EAAE,SAAS;AAC/D,CAAC,EACA,OAAO;AAIH,IAAM,cAAcA,GACxB,OAAO;AAAA,EACN,UAAUA,GAAE,MAAM,WAAW,EAAE,IAAI,CAAC,EAAE,IAAI,eAAe,WAAW;AAAA,EACpE,QAAQA,GAAE,OAAO,EAAE,IAAI,eAAe,YAAY,EAAE,SAAS;AAC/D,CAAC,EACA,OAAO;AAYH,IAAM,UAAUA,GAAE,KAAK,CAAC,gBAAgB,UAAU,CAAC;AAInD,IAAM,YAAY,OAAO,OAAO,QAAQ,OAAO;AAG/C,IAAM,gBAAgBA,GAAE,mBAAmB,QAAQ;AAAA,EACxDA,GAAE,OAAO,EAAE,MAAMA,GAAE,QAAQ,cAAc,GAAG,SAAS,gBAAgB,CAAC;AAAA,EACtEA,GAAE,OAAO,EAAE,MAAMA,GAAE,QAAQ,UAAU,GAAG,SAAS,YAAY,CAAC;AAChE,CAAC;AASM,SAAS,UAAU,OAAiC;AACzD,SAAQ,UAAgC,SAAS,KAAK;AACxD;AAMO,SAAS,kBAAkB,QAA+B;AAC/D,MAAI,OAAO,SAAS,gBAAgB;AAClC,WAAO,OAAO,QAAQ,OAAO,UAAU,OAAO,QAAQ,QAAQ,UAAU;AAAA,EAC1E;AACA,QAAM,WAAW,OAAO,QAAQ,SAAS;AAAA,IACvC,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ;AAAA,IAC5B;AAAA,EACF;AACA,SAAO,YAAY,OAAO,QAAQ,QAAQ,UAAU;AACtD;;;ACtGA,SAAS,KAAAC,UAAS;AAmBX,IAAM,WAAWC,GAAE,KAAK;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAIM,IAAM,kBAAkB,OAAO,OAAO;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAwC;AAGjC,SAAS,WAAW,OAA0B;AACnD,SAAQ,gBAAwC,SAAS,KAAK;AAChE;AAMA,IAAM,cACJ,OAAO,OAAO;AAAA,EACZ,QAAQ,CAAC,WAAW,WAAW,UAAU;AAAA;AAAA;AAAA,EAGzC,SAAS,CAAC,WAAW,UAAU,YAAY,OAAO;AAAA,EAClD,SAAS,CAAC,MAAM,SAAS,YAAY,QAAQ;AAAA,EAC7C,IAAI,CAAC;AAAA,EACL,OAAO,CAAC;AAAA,EACR,UAAU,CAAC;AAAA,EACX,SAAS,CAAC;AACZ,CAAC;AAGI,SAAS,cAAc,MAAgB,IAAuB;AACnE,SAAO,YAAY,IAAI,EAAE,SAAS,EAAE;AACtC;AAGO,IAAM,QAAQA,GAAE,OAAO;AAAA;AAAA,EAE5B,UAAUA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,EAE1B,WAAWA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AACvC,CAAC;AAIM,IAAM,aAAaA,GAAE,MAAM,CAAC,iBAAiB,WAAW,CAAC;AAWzD,IAAM,aAAaA,GACvB,OAAO;AAAA,EACN,IAAIA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACpB,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU;AAAA;AAAA,EAEV,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,EAEvB,eAAeA,GAAE,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,SAAS;AAAA,EACnD,OAAO;AACT,CAAC,EACA,OAAO;AAUH,IAAM,mBAAmBA,GAC7B,OAAO;AAAA;AAAA,EAEN,UAAU;AAAA;AAAA,EAEV,UAAUA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,EAE1B,aAAaA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,EAE7B,cAAc;AAAA;AAAA,EAEd,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKvB,WAAWA,GAAE,QAAQ;AACvB,CAAC,EACA,OAAO;AAOH,SAAS,cAAc,OAMT;AACnB,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,IAChB,aAAa,MAAM;AAAA,IACnB,cAAc,MAAM;AAAA,IACpB,OAAO,MAAM;AAAA,IACb,WAAW,MAAM,aAAa;AAAA,EAChC;AACF;AAGO,IAAM,cAAcA,GACxB,OAAO;AAAA,EACN,SAASA,GAAE,QAAQ,IAAI;AAAA,EACvB,MAAMA,GAAE,OAAO;AAAA;AAAA,EAEf,aAAaA,GAAE,IAAI,EAAE,SAAS;AAChC,CAAC,EACA,OAAO;AAGH,IAAM,iBAAiBA,GAC3B,OAAO;AAAA,EACN,SAASA,GAAE,QAAQ,OAAO;AAAA,EAC1B,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,SAASA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,EAEzB,WAAWA,GAAE,QAAQ;AACvB,CAAC,EACA,OAAO;AAGH,IAAM,oBAAoBA,GAC9B,OAAO;AAAA,EACN,SAASA,GAAE,QAAQ,UAAU;AAC/B,CAAC,EACA,OAAO;AAEH,IAAM,aAAaA,GAAE,mBAAmB,WAAW;AAAA,EACxD;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAIM,IAAM,kBAAkBA,GAC5B,OAAO;AAAA,EACN,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,OAAO;AAAA,EACP,SAAS,WAAW,SAAS;AAAA,EAC7B,YAAY,iBAAiB,SAAS;AACxC,CAAC,EACA,OAAO;;;ACpKV,IAAM,OAAO,CAAC,MAAkB,OAAO,OAAO,CAAC;AAUxC,IAAM,QAAQ,OAAO,OAAO;AAAA;AAAA,EAEjC,eAAe,KAAK;AAAA,IAClB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,kBAAkB,KAAK;AAAA,IACrB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,mBAAmB,KAAK;AAAA,IACtB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA;AAAA,EAGD,iBAAiB,KAAK;AAAA,IACpB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,cAAc,KAAK;AAAA,IACjB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA;AAAA,EAGD,2BAA2B,KAAK;AAAA,IAC9B,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,wBAAwB,KAAK;AAAA,IAC3B,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,cAAc,KAAK;AAAA,IACjB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA;AAAA,EAGD,eAAe,KAAK;AAAA,IAClB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,mBAAmB,KAAK;AAAA,IACtB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA;AAAA,EAGD,qBAAqB,KAAK;AAAA,IACxB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,wBAAwB,KAAK;AAAA,IAC3B,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,uBAAuB,KAAK;AAAA,IAC1B,IAAI;AAAA,IACJ,WACE;AAAA,IAGF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EAED,uBAAuB,KAAK;AAAA,IAC1B,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA;AAAA,EAGD,oBAAoB,KAAK;AAAA,IACvB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,gBAAgB,KAAK;AAAA,IACnB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA;AAAA,EAGD,mBAAmB,KAAK;AAAA,IACtB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,YAAY,KAAK;AAAA,IACf,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,kBAAkB,KAAK;AAAA,IACrB,IAAI;AAAA,IACJ,WACE;AAAA,IAGF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,mBAAmB,KAAK;AAAA,IACtB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,mBAAmB,KAAK;AAAA,IACtB,IAAI;AAAA,IACJ,WACE;AAAA,IAGF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA;AAAA,EAGD,iCAAiC,KAAK;AAAA,IACpC,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA;AAAA,EAGD,wBAAwB,KAAK;AAAA,IAC3B,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,oBAAoB,KAAK;AAAA,IACvB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,oBAAoB,KAAK;AAAA,IACvB,IAAI;AAAA,IACJ,WACE;AAAA,IAGF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,oBAAoB,KAAK;AAAA,IACvB,IAAI;AAAA,IACJ,WACE;AAAA,IAGF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,cAAc,KAAK;AAAA,IACjB,IAAI;AAAA,IACJ,WACE;AAAA,IAGF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AAAA,EACD,mBAAmB,KAAK;AAAA,IACtB,IAAI;AAAA,IACJ,WACE;AAAA,IAEF,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV,CAAC;AACH,CAAyC;AAMlC,IAAM,WAAW,OAAO,OAAO,OAAO,KAAK,KAAK,CAAa;;;ACtSpE,SAAS,KAAAC,UAAS;AAOX,IAAM,mBAAmB;AAGzB,IAAM,kBAAkB;AAGxB,IAAM,YAAY,OAAO,OAAO;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAU;AAaH,IAAM,aAAaC,GACvB,OAAO;AAAA,EACN,MAAM;AAAA,EACN,WAAW;AAAA,EACX,cAAc;AAAA,EACd,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,YAAY;AACd,CAAC,EACA,OAAO;AAIH,IAAM,mBAAmBA,GAAE,MAAM,UAAU;AAc3C,IAAM,mBAAmBA,GAC7B,OAAO;AAAA,EACN,iBAAiBA,GAAE,QAAQ,gBAAgB;AAAA,EAC3C,QAAQA,GAAE,QAAQ,OAAO;AAAA,EACzB,QAAQA,GAAE,OAAO;AAAA,IACf,SAASA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,IAEzB,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,IAChC,UAAUA,GAAE,KAAK,CAAC,UAAU,SAAS,OAAO,CAAC;AAAA,EAC/C,CAAC;AAAA,EACD,cAAc;AAChB,CAAC,EACA,OAAO;AAGH,IAAM,oBAAoBA,GAC9B,OAAO;AAAA;AAAA,EAEN,YAAYA,GAAE,OAAO,EAAE,IAAI,EAAE;AAAA;AAAA,EAE7B,UAAUA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA;AAAA,EAElC,iBAAiBA,GAAE,IAAI;AAAA;AAAA,EAEvB,WAAWA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA;AAAA,EAErC,gBAAgBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,IAAI,GAAM;AACtD,CAAC,EACA,OAAO;AAGH,IAAM,kBAAkBA,GAC5B,OAAO;AAAA,EACN,iBAAiBA,GAAE,QAAQ,gBAAgB;AAAA,EAC3C,QAAQA,GAAE,QAAQ,MAAM;AAAA,EACxB,YAAYA,GAAE,OAAO,EAAE,IAAI,EAAE;AAC/B,CAAC,EACA,OAAO;AAGH,IAAM,mBAAmBA,GAAE,mBAAmB,UAAU;AAAA,EAC7DA,GAAE,OAAO,EAAE,QAAQA,GAAE,QAAQ,SAAS,EAAE,CAAC,EAAE,OAAO;AAAA,EAClDA,GAAE,OAAO,EAAE,QAAQA,GAAE,QAAQ,QAAQ,EAAE,CAAC,EAAE,OAAO;AAAA,EACjDA,GAAE,OAAO,EAAE,QAAQA,GAAE,QAAQ,SAAS,EAAE,CAAC,EAAE,OAAO;AAAA,EAClDA,GACG,OAAO;AAAA,IACN,QAAQA,GAAE,QAAQ,UAAU;AAAA;AAAA,IAE5B,aAAaA,GAAE,OAAO,EAAE,IAAI,EAAE;AAAA,IAC9B,UAAUA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,IAE1B,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,IAEvB,YAAYA,GAAE,OAAO,EAAE,SAAS;AAAA,EAClC,CAAC,EACA,OAAO;AACZ,CAAC;AAGM,IAAM,cAAcA,GAAE,mBAAmB,UAAU;AAAA,EACxD;AAAA,EACA;AACF,CAAC;AAOM,IAAM,eAAeA,GACzB,OAAO;AAAA,EACN,iBAAiBA,GAAE,QAAQ,gBAAgB;AAAA,EAC3C,UAAUA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,EAE1B,cAAc;AAAA;AAAA,EAEd,KAAKA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AACrC,CAAC,EACA,OAAO;AAGH,IAAM,gBAAgBA,GAC1B,OAAO;AAAA,EACN,MAAMA,GAAE,MAAM,UAAU;AAAA;AAAA,EAExB,SAASA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AACrC,CAAC,EACA,OAAO;AAOH,IAAM,mBAAmBA,GAC7B,OAAO;AAAA,EACN,iBAAiBA,GAAE,QAAQ,gBAAgB;AAAA,EAC3C,UAAUA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,eAAeA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC/B,cAAc;AAAA;AAAA,EAEd,cAAcA,GAAE,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AAAA;AAAA,EAEvC,QAAQA,GAAE,QAAQ;AACpB,CAAC,EACA,OAAO;AAGH,IAAM,oBAAoBA,GAC9B,OAAO;AAAA;AAAA,EAEN,SAASA,GAAE,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,EAKnB,QAAQA,GAAE,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AAAA;AAAA,EAEjC,QAAQA,GAAE;AAAA,IACRA,GACG,OAAO;AAAA,MACN,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACvB,WAAWA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,IACvC,CAAC,EACA,OAAO;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,MAAMA,GAAE,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AAAA;AAAA,EAE/B,YAAYA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AACxC,CAAC,EACA,OAAO;AAOH,IAAM,gBAAgBA,GAC1B,OAAO;AAAA,EACN,iBAAiBA,GAAE,QAAQ,gBAAgB;AAAA,EAC3C,UAAUA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,SAAS;AAAA;AAAA,EAET,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,cAAc;AAAA;AAAA,EAEd,YAAYA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAC3C,CAAC,EACA,OAAO;AAGH,IAAM,iBAAiBA,GAC3B,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMN,UAAUA,GAAE,QAAQ;AAAA;AAAA,EAEpB,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AACzB,CAAC,EACA,OAAO;AAOH,IAAM,iBAAiBA,GAC3B,OAAO;AAAA,EACN,iBAAiBA,GAAE,QAAQ,gBAAgB;AAAA,EAC3C,UAAUA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,QAAQA,GAAE,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUjC,QAAQA,GAAE,KAAK,CAAC,YAAY,SAAS,WAAW,gBAAgB,SAAS,CAAC;AAC5E,CAAC,EACA,OAAO;AAGH,IAAM,kBAAkBA,GAC5B,OAAO;AAAA,EACN,UAAUA,GAAE,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC,EACA,OAAO;AAeH,IAAM,gBAAgBA,GAAE,KAAK;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,YAAYA,GACtB,OAAO;AAAA,EACN,OAAO;AAAA,EACP,SAASA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,EAEzB,YAAYA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;AACtD,CAAC,EACA,OAAO;AAIH,IAAM,eACX,OAAO,OAAO;AAAA,EACZ,eAAe;AAAA,EACf,gCAAgC;AAAA,EAChC,cAAc;AAAA,EACd,SAAS;AAAA,EACT,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAClB,CAAC;","names":["z","z","z","z","z","z","z"]}
1
+ {"version":3,"sources":["../src/audience.ts","../src/backends.ts","../src/kinds.ts","../src/job.ts","../src/musts.ts","../src/wire.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { type BackendCost } from \"./backends.js\";\n\n/**\n * Who may run a job, declared by the app that enqueued it.\n *\n * - `self` — only the job owner's own daemon.\n * - `named` — a daemon whose owner has explicitly allowed this (server, user)\n * pair in their *local* allowlist (byollm_001 Rev 1 §B).\n * - `public` — any daemon offering `public` compute.\n */\nexport const Audience = z.enum([\"self\", \"named\", \"public\"]);\nexport type Audience = z.infer<typeof Audience>;\n\n/**\n * What a daemon backend is willing to run, declared by the machine's owner.\n * Same three values as {@link Audience}, but the two are independent axes —\n * a job runs only where both agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}).\n */\nexport const OfferScope = z.enum([\"self\", \"named\", \"public\"]);\nexport type OfferScope = z.infer<typeof OfferScope>;\n\n/** All audience values, in widening order. */\nexport const AUDIENCES = Object.freeze(Audience.options);\n/** All offer scopes, in widening order. */\nexport const OFFER_SCOPES = Object.freeze(OfferScope.options);\n\n/**\n * Why a job was refused. Distinct codes because byollm_002 requires that\n * different truths never share a message — \"no matching work\" and \"refused on\n * principle\" are not the same event, and a volunteer debugging their setup\n * needs to know which one happened.\n */\nexport const MatchRefusal = z.enum([\n /** The daemon advertises no capability for this kind. */\n \"no-capability\",\n /** Job is `self` but this daemon belongs to a different user. */\n \"audience-self-other-owner\",\n /** Job is `named` but this daemon's local allowlist does not admit the owner. */\n \"not-locally-allowed\",\n /** Job is `named`/`public` but the server's own allowlist excludes this runner. */\n \"not-in-server-allowlist\",\n /** The backend offers only `self` and the job belongs to someone else. */\n \"offer-scope-too-narrow\",\n /** The matched backend is subscription-class, which is locked to `self`. */\n \"subscription-self-lock\",\n /** The backend spends the owner's money and they have not agreed to share it. */\n \"metered-no-spend-consent\",\n /** The backend is shared but has spent its ceiling for now. */\n \"metered-ceiling-reached\",\n]);\nexport type MatchRefusal = z.infer<typeof MatchRefusal>;\n\n/** The outcome of an audience match. */\nexport type MatchResult =\n | { readonly ok: true }\n | { readonly ok: false; readonly refusal: MatchRefusal };\n\nconst ALLOWED: MatchResult = Object.freeze({ ok: true as const });\nconst refuse = (refusal: MatchRefusal): MatchResult =>\n Object.freeze({ ok: false as const, refusal });\n\n/** What the owner has agreed to spend on other people's work, if anything. */\nexport interface SpendConsent {\n /** The owner explicitly acknowledged that sharing this backend costs money. */\n readonly acknowledged: boolean;\n /** Their ceiling. Absent means no ceiling was set, which is not consent. */\n readonly ceilingReached?: boolean;\n}\n\n/**\n * The effective offer scope of a backend.\n *\n * Three rules, applied at the one place both the daemon's config loader and\n * its matcher call, so no code path can observe a scope wider than the cost\n * class allows:\n *\n * - `subscription` is locked to `self` regardless of config\n * ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — someone else's terms.\n * - `metered` narrows to `self` unless the owner has explicitly acknowledged\n * the spend ({@link MUSTS.METERED_DEFAULTS_SELF}) — their money.\n * - `free` passes through — their electricity.\n *\n * Note the asymmetry: subscription can never be widened, metered can be\n * widened deliberately. Conflating those was byollm_007's bug.\n */\nexport function effectiveOfferScope(\n configured: OfferScope,\n cost: BackendCost,\n spend?: SpendConsent,\n): OfferScope {\n if (cost === \"subscription\") return \"self\";\n if (cost === \"metered\" && spend?.acknowledged !== true) return \"self\";\n return configured;\n}\n\n/** The job-side facts a match needs. */\nexport interface MatchJob {\n /** The app's id for the user who enqueued the job. */\n readonly owner: string;\n /** Who the app says may run it. */\n readonly audience: Audience;\n /**\n * Optional server-side restriction on which runner owners may take a\n * `named` job. Defence in depth only — the daemon's local allowlist is the\n * enforcing side ({@link MUSTS.NAMED_LOCAL_ALLOWLIST}).\n */\n readonly audienceAllow?: readonly string[] | undefined;\n}\n\n/** The daemon-side facts a match needs. */\nexport interface MatchDaemon {\n /** The app's id for the user this daemon is paired to. */\n readonly owner: string;\n /** Effective scope of the backend that would run the job. */\n readonly offerScope: OfferScope;\n /** Who pays for that backend's tokens. */\n readonly cost: BackendCost;\n /** What the owner agreed to spend on others, for a `metered` backend. */\n readonly spend?: SpendConsent | undefined;\n /**\n * Does this daemon's *local* allowlist admit the given owner for the server\n * origin the job came from? Supplied as a predicate so the protocol package\n * stays free of file I/O; the daemon passes its allowlist, the server\n * passes a conservative `() => true` because it cannot know a remote\n * daemon's local list and must not pretend to.\n */\n readonly locallyAllows: (owner: string) => boolean;\n}\n\n/**\n * Decide whether a job may run on a daemon.\n *\n * Both sides must agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}):\n * 1. the job's audience must admit the daemon's owner, and\n * 2. the backend's offer scope must admit the job's owner.\n *\n * The full nine-way matrix (three audiences × three offer scopes) is asserted\n * by the conformance kit. The function is pure and total so both the daemon\n * and the server can run the identical rule — the daemon refuses, and the\n * server refuses too (byollm_003 §Server-side MUSTs).\n *\n * @example\n * ```ts\n * const result = matchAudience(\n * { owner: \"alice\", audience: \"named\" },\n * {\n * owner: \"bob\",\n * offerScope: \"named\",\n * cost: \"free\",\n * locallyAllows: (o) => o === \"alice\",\n * },\n * );\n * // result.ok === true\n * ```\n */\nexport function matchAudience(job: MatchJob, daemon: MatchDaemon): MatchResult {\n const sameOwner = job.owner === daemon.owner;\n\n // --- Side 1: does the job's audience admit this daemon's owner? --------\n if (job.audience === \"self\" && !sameOwner) {\n return refuse(\"audience-self-other-owner\");\n }\n if (\n job.audience === \"named\" &&\n !sameOwner &&\n job.audienceAllow !== undefined &&\n !job.audienceAllow.includes(daemon.owner)\n ) {\n return refuse(\"not-in-server-allowlist\");\n }\n\n // --- Side 2: does the backend's offer scope admit the job's owner? -----\n // The lock is re-applied rather than trusted: a caller that passed a\n // widened scope for a subscription backend gets a refusal, not obedience.\n const scope = effectiveOfferScope(\n daemon.offerScope,\n daemon.cost,\n daemon.spend,\n );\n\n if (sameOwner) {\n // A daemon always runs its own owner's work, at any scope.\n return ALLOWED;\n }\n\n // Order matters for the message, not the outcome: all three refuse, and a\n // volunteer debugging their setup needs to know which truth applies.\n if (daemon.cost === \"subscription\") {\n return refuse(\"subscription-self-lock\");\n }\n if (daemon.cost === \"metered\") {\n if (daemon.spend?.acknowledged !== true) {\n return refuse(\"metered-no-spend-consent\");\n }\n if (daemon.spend.ceilingReached === true) {\n return refuse(\"metered-ceiling-reached\");\n }\n }\n\n switch (scope) {\n case \"self\":\n return refuse(\"offer-scope-too-narrow\");\n case \"named\":\n // byollm_001 Rev 1 §B: the daemon's own list decides, not the server's.\n return daemon.locallyAllows(job.owner)\n ? ALLOWED\n : refuse(\"not-locally-allowed\");\n case \"public\":\n return ALLOWED;\n }\n}\n\n/**\n * Human-readable refusal text for the daemon's log and the trust UI.\n * Each refusal reads as a distinct truth — byollm_002's \"four different\n * truths that must never share a message\" applied to the audience axis.\n */\nexport const REFUSAL_MESSAGES: Readonly<Record<MatchRefusal, string>> =\n Object.freeze({\n \"no-capability\":\n \"no backend on this machine is configured and healthy for that job kind\",\n \"audience-self-other-owner\":\n \"the job is private to its owner and this machine is paired to someone else\",\n \"not-locally-allowed\":\n \"the job's owner is not on this machine's allowlist (byollm allow <server> <user>)\",\n \"not-in-server-allowlist\":\n \"the app restricted this job to named runners and this machine is not one of them\",\n \"offer-scope-too-narrow\":\n \"this backend is offered to its owner only (byollm offer <backend> named|public to widen)\",\n \"subscription-self-lock\":\n \"subscription-backed models run their owner's work only — this is a protocol rule, not a setting\",\n \"metered-no-spend-consent\":\n \"this backend bills its owner per token, and they have not agreed to spend it on other people's work\",\n \"metered-ceiling-reached\":\n \"this backend is shared but has reached the spend ceiling its owner set\",\n });\n","import { z } from \"zod\";\n\n/**\n * How a backend reaches its model — the taxonomy introduced in byollm_001\n * Rev 1 §A, because the two classes have different threat surfaces.\n *\n * - `http`: an OpenAI-compatible HTTP server (Ollama, `mlx_lm.server`,\n * llama.cpp server, vLLM, and every hosted provider that speaks the same\n * wire format). Spawns nothing, so byollm_004 §2's argv, stdin, env and\n * sandbox requirements are not applicable by construction. Its threat\n * surface is SSRF-shaped and bounded by {@link MUSTS.HTTP_BASE_URL_SAFE}.\n * - `process`: spawns a binary (`claude` CLI today, `mlx_lm.lora` for a\n * future `train.*` kind). All of byollm_004 §2 is mandatory here.\n */\nexport const BackendClass = z.enum([\"http\", \"process\"]);\nexport type BackendClass = z.infer<typeof BackendClass>;\n\n/**\n * Who pays, and how — byollm_007.\n *\n * This replaced a two-valued `account` field that conflated two unrelated\n * constraints and, in doing so, left a hole: `openai-http` was \"open\", but it\n * accepts an API key, so an owner could point it at a paid endpoint, offer it\n * `public`, and donate their credit balance to strangers. The community\n * budgets cap job *count*, not spend.\n *\n * - `free` — local compute. Costs electricity, not money. Shareable.\n * - `metered` — per-token billing against the owner's account. Legal to\n * share and ruinous to share by accident.\n * - `subscription` — a vendor account whose terms forbid third-party work.\n * Sharing is a terms violation, not merely expensive.\n */\nexport const BackendCost = z.enum([\"free\", \"metered\", \"subscription\"]);\nexport type BackendCost = z.infer<typeof BackendCost>;\n\n/** The immutable facts about a backend that the protocol reasons over. */\nexport interface BackendDescriptor {\n /** Stable backend id, as written in `byollm.config.json`. */\n readonly id: string;\n /** Human-readable name for the trust UI. */\n readonly label: string;\n /** Determines which isolation requirements apply. */\n readonly class: BackendClass;\n /**\n * Who pays. Fixed here for every named provider and **not overridable by\n * configuration** ({@link MUSTS.COST_NOT_CONFIGURABLE}) — `openai` is\n * metered because it is, and no setting changes that.\n *\n * `null` only for the generic {@link BACKENDS.\"openai-http\"} entry, whose\n * cost is inferred from its base URL instead\n * ({@link MUSTS.REMOTE_IS_NEVER_FREE}).\n */\n readonly cost: BackendCost | null;\n /**\n * Which adversarial corpus byollm_004 §5 runs against this backend. A\n * backend cannot be registered without one — the coverage check in the\n * adversarial suite enforces it.\n */\n readonly adversarialCorpus: \"process\" | \"http\";\n /**\n * Where this provider lives, when that is knowable. Owner config may\n * override it; a provider with no default requires one to be given.\n */\n readonly defaultBaseUrl?: string;\n}\n\nconst backend = (b: BackendDescriptor): BackendDescriptor => Object.freeze(b);\n\n/**\n * The backend registry.\n *\n * **Providers are entries, not implementations.** Every HTTP-class provider\n * below shares the single `openai-http` transport, because they all speak\n * OpenAI-compatible `/v1/chat/completions`. An entry adds a stable id, a cost\n * class the owner cannot override, and a default base URL. Adding a provider\n * is therefore one line and no new code — which is why the adversarial corpus\n * still covers all of them, and why a PR adding one is reviewable at a glance.\n */\nexport const BACKENDS = Object.freeze({\n // -- free: local compute, costs electricity ------------------------------\n ollama: backend({\n id: \"ollama\",\n label: \"Ollama (local)\",\n class: \"http\",\n cost: \"free\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"http://127.0.0.1:11434/v1\",\n }),\n mlx: backend({\n id: \"mlx\",\n label: \"MLX (mlx_lm.server, local)\",\n class: \"http\",\n cost: \"free\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"http://127.0.0.1:8080/v1\",\n }),\n llamacpp: backend({\n id: \"llamacpp\",\n label: \"llama.cpp server (local)\",\n class: \"http\",\n cost: \"free\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"http://127.0.0.1:8080/v1\",\n }),\n vllm: backend({\n id: \"vllm\",\n label: \"vLLM (local)\",\n class: \"http\",\n cost: \"free\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"http://127.0.0.1:8000/v1\",\n }),\n lmstudio: backend({\n id: \"lmstudio\",\n label: \"LM Studio (local)\",\n class: \"http\",\n cost: \"free\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"http://127.0.0.1:1234/v1\",\n }),\n jan: backend({\n id: \"jan\",\n label: \"Jan (local)\",\n class: \"http\",\n cost: \"free\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"http://127.0.0.1:1337/v1\",\n }),\n localai: backend({\n id: \"localai\",\n label: \"LocalAI (local)\",\n class: \"http\",\n cost: \"free\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"http://127.0.0.1:8080/v1\",\n }),\n\n // -- metered: the owner's money, per token -------------------------------\n /**\n * Note the pair: `anthropic` and {@link BACKENDS.\"claude-cli\"} reach the\n * same vendor and land in different cost classes. That is not an\n * inconsistency — it is the axis working. One bills a key per token, the\n * other runs under a personal plan whose terms cover one person's work. Who\n * pays and under what terms is the question; which company is not.\n */\n anthropic: backend({\n id: \"anthropic\",\n label: \"Anthropic (your API key)\",\n class: \"http\",\n cost: \"metered\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"https://api.anthropic.com/v1\",\n }),\n openai: backend({\n id: \"openai\",\n label: \"OpenAI (your API key)\",\n class: \"http\",\n cost: \"metered\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"https://api.openai.com/v1\",\n }),\n gemini: backend({\n id: \"gemini\",\n label: \"Google Gemini (your API key)\",\n class: \"http\",\n cost: \"metered\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"https://generativelanguage.googleapis.com/v1beta/openai\",\n }),\n grok: backend({\n id: \"grok\",\n label: \"xAI Grok (your API key)\",\n class: \"http\",\n cost: \"metered\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"https://api.x.ai/v1\",\n }),\n groq: backend({\n id: \"groq\",\n label: \"Groq (your API key)\",\n class: \"http\",\n cost: \"metered\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"https://api.groq.com/openai/v1\",\n }),\n openrouter: backend({\n id: \"openrouter\",\n label: \"OpenRouter (your API key)\",\n class: \"http\",\n cost: \"metered\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"https://openrouter.ai/api/v1\",\n }),\n together: backend({\n id: \"together\",\n label: \"Together AI (your API key)\",\n class: \"http\",\n cost: \"metered\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"https://api.together.xyz/v1\",\n }),\n deepseek: backend({\n id: \"deepseek\",\n label: \"DeepSeek (your API key)\",\n class: \"http\",\n cost: \"metered\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"https://api.deepseek.com/v1\",\n }),\n mistral: backend({\n id: \"mistral\",\n label: \"Mistral (your API key)\",\n class: \"http\",\n cost: \"metered\",\n adversarialCorpus: \"http\",\n defaultBaseUrl: \"https://api.mistral.ai/v1\",\n }),\n\n // -- the escape hatch ----------------------------------------------------\n \"openai-http\": backend({\n id: \"openai-http\",\n label: \"Any OpenAI-compatible server\",\n class: \"http\",\n // Unknown until the base URL is known: local means free, remote means\n // metered, and the owner does not get to say otherwise\n // ({@link MUSTS.REMOTE_IS_NEVER_FREE}).\n cost: null,\n adversarialCorpus: \"http\",\n }),\n\n // -- subscription: someone else's terms ----------------------------------\n \"claude-cli\": backend({\n id: \"claude-cli\",\n label: \"Claude CLI (your subscription)\",\n class: \"process\",\n cost: \"subscription\",\n adversarialCorpus: \"process\",\n }),\n} as const satisfies Record<string, BackendDescriptor>);\n\n/** The id of a registered backend. */\nexport type BackendId = keyof typeof BACKENDS;\n\n/** All registered backend ids — the adversarial coverage check iterates this. */\nexport const BACKEND_IDS = Object.freeze(Object.keys(BACKENDS) as BackendId[]);\n\nexport const BackendIdSchema = z.enum(\n BACKEND_IDS as [BackendId, ...BackendId[]],\n);\n\n/** Narrow an arbitrary string to a registered backend id. */\nexport function isBackendId(value: string): value is BackendId {\n return Object.hasOwn(BACKENDS, value);\n}\n\n/**\n * Look up a backend descriptor.\n *\n * @throws if the id is not registered — an unregistered backend has no\n * adversarial corpus, so refusing is the safe direction.\n */\nexport function backendDescriptor(id: BackendId): BackendDescriptor {\n return BACKENDS[id];\n}\n\n/**\n * Is this host local enough that compute there is free?\n *\n * Loopback and the private ranges only. This is the rule that makes\n * {@link MUSTS.REMOTE_IS_NEVER_FREE} enforceable rather than a promise: an\n * owner cannot reach a paid API through the generic backend and call it free,\n * because \"free\" is derived from the address, not from what the config claims.\n *\n * **What this cannot see.** The address is all it reads. A proxy on\n * `127.0.0.1` forwarding to a paid API classes as `free` and nothing\n * downstream will contradict it. That is deliberate: standing up a relay is\n * an act by the machine's owner against their own account, and the threat\n * model here is a hostile *job*, not an owner routing around a rule that\n * exists to protect them. What this catches is the accident — a remote paid\n * endpoint offered `public` because nobody thought about the bill. See\n * `docs/security.md` §4a.\n */\nexport function isLocalHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return true;\n if (host === \"::1\") return true;\n if (host.startsWith(\"127.\")) return true;\n if (host.startsWith(\"10.\")) return true;\n if (host.startsWith(\"192.168.\")) return true;\n if (/^172\\.(1[6-9]|2\\d|3[01])\\./.test(host)) return true;\n // Unique local addresses (fc00::/7).\n if (/^f[cd]/.test(host)) return true;\n return false;\n}\n\n/**\n * The cost class of a configured backend instance.\n *\n * For every named provider this is whatever the registry says, full stop\n * ({@link MUSTS.COST_NOT_CONFIGURABLE}). For the generic `openai-http` entry\n * it is inferred from the base URL, and a base URL that cannot be parsed is\n * treated as `metered` — the expensive side, because guessing \"free\" wrong\n * costs the owner money.\n */\nexport function resolveCost(\n id: BackendId,\n baseUrl: string | undefined,\n): BackendCost {\n const declared = BACKENDS[id].cost;\n if (declared !== null) return declared;\n if (baseUrl === undefined) return \"metered\";\n try {\n return isLocalHost(new URL(baseUrl).hostname) ? \"free\" : \"metered\";\n } catch {\n return \"metered\";\n }\n}\n","import { z } from \"zod\";\n\n/**\n * Upper bounds on payload size, enforced at the schema so oversized input is\n * refused at parse time rather than somewhere deeper.\n *\n * byollm_004 §4 requires stricter limits for community (`named`/`public`)\n * jobs; those are applied on top of these by the daemon's budget check, which\n * knows the job's audience. These are the absolute ceilings for any job.\n */\nexport const PAYLOAD_LIMITS = Object.freeze({\n /** Max characters in any single text field. */\n maxTextChars: 1_000_000,\n /** Max messages in an `llm.chat` conversation. */\n maxMessages: 256,\n /** Max characters across the whole payload. */\n maxTotalChars: 4_000_000,\n});\n\n/**\n * A conversation turn. `role` is a closed enum — it is routing *within the\n * model call*, not routing of the call, so it cannot select a backend.\n */\nexport const ChatMessage = z.object({\n role: z.enum([\"system\", \"user\", \"assistant\"]),\n content: z.string().max(PAYLOAD_LIMITS.maxTextChars),\n});\nexport type ChatMessage = z.infer<typeof ChatMessage>;\n\n/**\n * Payload for `llm.generate`.\n *\n * @remarks\n * Text only, deliberately. byollm_004 §1 states the payload is \"data handed\n * to a model, never configuration and never a command\", so v0 carries no\n * sampling parameters, no model name, no base URL and no flags — those are\n * owner-side route config. A future `params` field with an explicit closed\n * allowlist and owner-set clamps is reserved; adding a field later is\n * non-breaking, removing one is not.\n */\nexport const GeneratePayload = z\n .object({\n prompt: z.string().min(1).max(PAYLOAD_LIMITS.maxTextChars),\n system: z.string().max(PAYLOAD_LIMITS.maxTextChars).optional(),\n })\n .strict();\nexport type GeneratePayload = z.infer<typeof GeneratePayload>;\n\n/** Payload for `llm.chat`. Text only, for the same reason as {@link GeneratePayload}. */\nexport const ChatPayload = z\n .object({\n messages: z.array(ChatMessage).min(1).max(PAYLOAD_LIMITS.maxMessages),\n system: z.string().max(PAYLOAD_LIMITS.maxTextChars).optional(),\n })\n .strict();\nexport type ChatPayload = z.infer<typeof ChatPayload>;\n\n/**\n * The job kinds a v1 daemon has handlers for.\n *\n * Kinds are resolved against handlers baked into the daemon\n * ({@link MUSTS.KIND_TYPED_ONLY}); an unknown kind is refused, never guessed.\n * Adding a kind is a protocol change with its own spec and threat review —\n * notably any kind that needs tools, which byollm_004 §2 forbids as a payload\n * flag.\n */\nexport const JobKind = z.enum([\"llm.generate\", \"llm.chat\"]);\nexport type JobKind = z.infer<typeof JobKind>;\n\n/** All v1 job kinds. */\nexport const JOB_KINDS = Object.freeze(JobKind.options);\n\n/** A payload discriminated by its kind. */\nexport const KindedPayload = z.discriminatedUnion(\"kind\", [\n z.object({ kind: z.literal(\"llm.generate\"), payload: GeneratePayload }),\n z.object({ kind: z.literal(\"llm.chat\"), payload: ChatPayload }),\n]);\nexport type KindedPayload = z.infer<typeof KindedPayload>;\n\n/** The payload type for a given kind. */\nexport type PayloadFor<K extends JobKind> = K extends \"llm.generate\"\n ? GeneratePayload\n : ChatPayload;\n\n/** Narrow an arbitrary string to a known job kind. */\nexport function isJobKind(value: string): value is JobKind {\n return (JOB_KINDS as readonly string[]).includes(value);\n}\n\n/**\n * Total character weight of a payload, used by the daemon's community budget\n * check and by the server's payload-size limits.\n */\nexport function payloadTextLength(kinded: KindedPayload): number {\n if (kinded.kind === \"llm.generate\") {\n return kinded.payload.prompt.length + (kinded.payload.system?.length ?? 0);\n }\n const messages = kinded.payload.messages.reduce(\n (sum, m) => sum + m.content.length,\n 0,\n );\n return messages + (kinded.payload.system?.length ?? 0);\n}\n","import { z } from \"zod\";\nimport { Audience } from \"./audience.js\";\nimport { BackendClass } from \"./backends.js\";\nimport { ChatPayload, GeneratePayload, JobKind } from \"./kinds.js\";\n\n/**\n * The job lifecycle, made explicit by byollm_001 Rev 1 §D because the most\n * user-visible failure mode — \"nothing is running my job\" — was previously\n * unspecified.\n *\n * ```text\n * queued ──claim──▶ claimed ──start──▶ running ──▶ ok | error | canceled\n * │ │ │\n * │ └──lease expiry─────┘\n * │ ▼\n * │ queued (reclaimable, no loss)\n * └──ttl elapsed──▶ expired\n * ```\n */\nexport const JobState = z.enum([\n \"queued\",\n \"claimed\",\n \"running\",\n \"ok\",\n \"error\",\n \"canceled\",\n \"expired\",\n]);\nexport type JobState = z.infer<typeof JobState>;\n\n/** States from which a job never moves again. */\nexport const TERMINAL_STATES = Object.freeze([\n \"ok\",\n \"error\",\n \"canceled\",\n \"expired\",\n] as const satisfies readonly JobState[]);\n\n/** Is this a state the job can never leave? */\nexport function isTerminal(state: JobState): boolean {\n return (TERMINAL_STATES as readonly JobState[]).includes(state);\n}\n\n/**\n * The legal transitions. Held as data so the store adapters and the\n * conformance kit agree on one definition rather than three implementations.\n */\nconst TRANSITIONS: Readonly<Record<JobState, readonly JobState[]>> =\n Object.freeze({\n queued: [\"claimed\", \"expired\", \"canceled\"],\n // A claimed job returns to `queued` when its lease expires un-renewed\n // ({@link MUSTS.LEASE_RECLAIMABLE}).\n claimed: [\"running\", \"queued\", \"canceled\", \"error\"],\n running: [\"ok\", \"error\", \"canceled\", \"queued\"],\n ok: [],\n error: [],\n canceled: [],\n expired: [],\n });\n\n/** May a job move from `from` to `to`? */\nexport function canTransition(from: JobState, to: JobState): boolean {\n return TRANSITIONS[from].includes(to);\n}\n\n/** A lease: the right to work on a job until `expiresAt`. */\nexport const Lease = z.object({\n /** The runner holding the lease. */\n runnerId: z.string().min(1),\n /** Epoch milliseconds after which the claim is void. */\n expiresAt: z.number().int().positive(),\n});\nexport type Lease = z.infer<typeof Lease>;\n\n/** Payload union as it appears on a job record. */\nexport const JobPayload = z.union([GeneratePayload, ChatPayload]);\nexport type JobPayload = z.infer<typeof JobPayload>;\n\n/**\n * A job as the daemon receives it from `/byollm/claim`.\n *\n * Note what is absent: no model, no backend, no base URL, no flags, no path.\n * Those come from the machine owner's config only\n * ({@link MUSTS.NO_PAYLOAD_ROUTING}). The wire shape is the first place that\n * rule is enforced — there is no field to carry them.\n */\nexport const ClaimedJob = z\n .object({\n id: z.string().min(1),\n kind: JobKind,\n payload: JobPayload,\n audience: Audience,\n /** The app's id for the user who enqueued it. */\n owner: z.string().min(1),\n /** Runner owners the app restricted a `named` job to, if any. */\n audienceAllow: z.array(z.string().min(1)).optional(),\n lease: Lease,\n })\n .strict();\nexport type ClaimedJob = z.infer<typeof ClaimedJob>;\n\n/**\n * The provenance that travels with every result to the delivery seam.\n *\n * byollm_003 Rev 1: a `named`/`public` result is attacker-controlled text.\n * The app must never render volunteer output as its own AI's answer without\n * knowing that is what it is ({@link MUSTS.RESULT_PROVENANCE}).\n */\nexport const ResultProvenance = z\n .object({\n /** The audience the job ran under. */\n audience: Audience,\n /** The runner that produced it. */\n runnerId: z.string().min(1),\n /** The runner owner's id in this app's namespace. */\n runnerOwner: z.string().min(1),\n /** Which backend class produced it — an HTTP call or a sandboxed spawn. */\n backendClass: BackendClass,\n /** The model the runner reports having used. */\n model: z.string().min(1),\n /**\n * False only for `self` jobs. When true the app MUST treat `text` as\n * untrusted third-party content.\n */\n untrusted: z.boolean(),\n })\n .strict();\nexport type ResultProvenance = z.infer<typeof ResultProvenance>;\n\n/**\n * Build provenance for a completed job. `untrusted` is derived, never\n * supplied, so no caller can mark volunteer output as first-party.\n */\nexport function provenanceFor(input: {\n audience: Audience;\n runnerId: string;\n runnerOwner: string;\n backendClass: BackendClass;\n model: string;\n}): ResultProvenance {\n return {\n audience: input.audience,\n runnerId: input.runnerId,\n runnerOwner: input.runnerOwner,\n backendClass: input.backendClass,\n model: input.model,\n untrusted: input.audience !== \"self\",\n };\n}\n\n/** Successful outcome. */\nexport const JobResultOk = z\n .object({\n outcome: z.literal(\"ok\"),\n text: z.string(),\n /** Optional reference to a stored artifact; never a local path. */\n artifactUrl: z.url().optional(),\n })\n .strict();\n\n/** Failed outcome. `code` is a stable machine string; `message` is for humans. */\nexport const JobResultError = z\n .object({\n outcome: z.literal(\"error\"),\n code: z.string().min(1),\n message: z.string().min(1),\n /** Whether the app may reasonably re-enqueue. */\n retryable: z.boolean(),\n })\n .strict();\n\n/** Cancelled outcome, reported by the daemon after honoring a cancel. */\nexport const JobResultCanceled = z\n .object({\n outcome: z.literal(\"canceled\"),\n })\n .strict();\n\nexport const JobOutcome = z.discriminatedUnion(\"outcome\", [\n JobResultOk,\n JobResultError,\n JobResultCanceled,\n]);\nexport type JobOutcome = z.infer<typeof JobOutcome>;\n\n/** A completed job as delivered to the app, provenance attached. */\nexport const DeliveredResult = z\n .object({\n jobId: z.string().min(1),\n state: JobState,\n outcome: JobOutcome.optional(),\n provenance: ResultProvenance.optional(),\n })\n .strict();\nexport type DeliveredResult = z.infer<typeof DeliveredResult>;\n","/**\n * The normative MUSTs of protocol v0, as data.\n *\n * byollm_001 requires that \"every MUST above has a conformance test id\n * referenced inline\". Keeping the MUSTs as a frozen registry rather than\n * prose is what makes that requirement *checkable*: the conformance kit\n * imports {@link MUSTS} and fails if any id has no test asserting it, so a\n * new MUST cannot be added without a test and a test cannot silently drift\n * away from the statement it claims to prove.\n *\n * Ids are stable and public — third-party servers cite them in their\n * certification output.\n */\n\n/** Which side of the wire is obliged to enforce a given MUST. */\nexport type MustEnforcer = \"daemon\" | \"server\" | \"both\";\n\n/** A single normative requirement of the protocol. */\nexport interface Must {\n /** Stable public id, cited by conformance output. */\n readonly id: string;\n /** The requirement, in MUST language. */\n readonly statement: string;\n /** Which implementation is obliged to enforce it. */\n readonly enforcedBy: MustEnforcer;\n /** Spec section this was adjudicated in. */\n readonly source: string;\n}\n\nconst must = (m: Must): Must => Object.freeze(m);\n\n/**\n * Every normative MUST in protocol v0.\n *\n * @remarks\n * Grouped by concern for readability; the conformance kit treats this as a\n * flat set. Adding an entry here without a corresponding conformance test is\n * a CI failure, by design.\n */\nexport const MUSTS = Object.freeze({\n // ---- Pairing and identity -------------------------------------------\n PAIR_ONE_USER: must({\n id: \"PAIR_ONE_USER\",\n statement:\n \"A runner token MUST be bound to exactly one user; a daemon MUST refuse \" +\n \"work not attributable to its paired user.\",\n enforcedBy: \"both\",\n source: \"byollm_001 §MUSTs\",\n }),\n PAIR_INTERACTIVE: must({\n id: \"PAIR_INTERACTIVE\",\n statement:\n \"Pairing MUST be interactive (device-code approval in the app's own \" +\n \"session); a long-lived pasted secret MUST NOT be accepted as pairing.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.1\",\n }),\n PAIR_CODE_EXPIRES: must({\n id: \"PAIR_CODE_EXPIRES\",\n statement:\n \"An unapproved device code MUST expire and MUST NOT be redeemable after \" +\n \"expiry.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.1\",\n }),\n\n // ---- Typed job kinds --------------------------------------------------\n KIND_TYPED_ONLY: must({\n id: \"KIND_TYPED_ONLY\",\n statement:\n \"Job kinds MUST resolve against handlers baked into the daemon. A daemon \" +\n \"MUST refuse an unknown kind rather than guess.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §Jobs are typed data\",\n }),\n KIND_NO_CODE: must({\n id: \"KIND_NO_CODE\",\n statement:\n \"A server MUST NOT be able to convey code, a shell string, or a path to \" +\n \"execute; payloads are data handed to a model only.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §Jobs are typed data; byollm_004 §1\",\n }),\n\n // ---- Capability and claiming -----------------------------------------\n CLAIM_REQUIRES_CAPABILITY: must({\n id: \"CLAIM_REQUIRES_CAPABILITY\",\n statement:\n \"A daemon MUST NOT be given a job whose kind is absent from its \" +\n \"advertised capability matrix.\",\n enforcedBy: \"both\",\n source: \"byollm_001 §MUSTs\",\n }),\n CAPABILITY_IS_DETECTED: must({\n id: \"CAPABILITY_IS_DETECTED\",\n statement:\n \"An advertised capability matrix MUST be the intersection of owner \" +\n \"config and detected, healthy reality — never config alone.\",\n enforcedBy: \"daemon\",\n source: \"byollm_002 §Routing\",\n }),\n CLAIM_ATOMIC: must({\n id: \"CLAIM_ATOMIC\",\n statement:\n \"Claiming MUST be atomic: a job MUST NOT be handed to two runners \" +\n \"concurrently.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.2\",\n }),\n\n // ---- Leases -----------------------------------------------------------\n LEASE_HONORED: must({\n id: \"LEASE_HONORED\",\n statement:\n \"A daemon MUST stop work on a job whose lease it has failed to renew, \" +\n \"and MUST NOT report a result for an expired lease it no longer holds.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §MUSTs\",\n }),\n LEASE_RECLAIMABLE: must({\n id: \"LEASE_RECLAIMABLE\",\n statement:\n \"A lease that expires un-renewed MUST make its job claimable again with \" +\n \"no loss of the job.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.2\",\n }),\n\n // ---- Audience and offer scope ----------------------------------------\n AUDIENCE_BOTH_SIDES: must({\n id: \"AUDIENCE_BOTH_SIDES\",\n statement:\n \"A job MUST run on a daemon only if the daemon's offer scope admits the \" +\n \"job's owner AND the job's audience admits the daemon's owner.\",\n enforcedBy: \"both\",\n source: \"byollm_001 §The audience model\",\n }),\n SUBSCRIPTION_SELF_LOCK: must({\n id: \"SUBSCRIPTION_SELF_LOCK\",\n statement:\n \"A subscription-class backend's offer scope MUST be 'self' and MUST NOT \" +\n \"be widened by configuration.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §The audience model\",\n }),\n METERED_DEFAULTS_SELF: must({\n id: \"METERED_DEFAULTS_SELF\",\n statement:\n \"A metered backend's effective offer scope MUST be 'self' unless the \" +\n \"owner has explicitly acknowledged spending money on others' work.\",\n enforcedBy: \"daemon\",\n source: \"byollm_007 §4\",\n }),\n METERED_REQUIRES_CEILING: must({\n id: \"METERED_REQUIRES_CEILING\",\n statement:\n \"A widened metered backend MUST carry a spend ceiling, and the daemon \" +\n \"MUST refuse community work once it is reached.\",\n enforcedBy: \"daemon\",\n source: \"byollm_007 §4\",\n }),\n COST_NOT_CONFIGURABLE: must({\n id: \"COST_NOT_CONFIGURABLE\",\n statement:\n \"A built-in provider's cost class MUST NOT be overridable by \" +\n \"configuration.\",\n enforcedBy: \"daemon\",\n source: \"byollm_007 §2\",\n }),\n REMOTE_IS_NEVER_FREE: must({\n id: \"REMOTE_IS_NEVER_FREE\",\n statement:\n \"A generic HTTP backend whose base URL is not loopback or private MUST \" +\n \"be treated as metered.\",\n enforcedBy: \"daemon\",\n source: \"byollm_007 §2\",\n }),\n\n NAMED_LOCAL_ALLOWLIST: must({\n id: \"NAMED_LOCAL_ALLOWLIST\",\n statement:\n \"A 'named' job MUST be admitted only by the daemon's own local \" +\n \"(server origin, user id) allowlist — never on the server's assertion \" +\n \"alone.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 Rev 1 §B\",\n }),\n\n REFUSAL_NOT_REOFFERED: must({\n id: \"REFUSAL_NOT_REOFFERED\",\n statement:\n \"A server MUST NOT re-offer a job to a runner that released it with \" +\n \"reason 'refused'.\",\n enforcedBy: \"server\",\n source: \"byollm_001 Rev 1 §B (loop resolved in build review)\",\n }),\n\n // ---- Revocation and cancel -------------------------------------------\n REVOCATION_HONORED: must({\n id: \"REVOCATION_HONORED\",\n statement:\n \"A revoked daemon MUST stop claiming and MUST abandon in-flight work by \" +\n \"the next heartbeat at the latest.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §MUSTs\",\n }),\n CANCEL_HONORED: must({\n id: \"CANCEL_HONORED\",\n statement:\n \"A job id in a heartbeat response's cancel list MUST abort that job's \" +\n \"in-flight backend call and be reported as 'canceled'.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 Rev 1 §C\",\n }),\n\n // ---- Lifecycle, dependencies, delivery -------------------------------\n DEPENDS_ON_GATING: must({\n id: \"DEPENDS_ON_GATING\",\n statement:\n \"A job MUST NOT be claimable until every job in its dependsOn set has \" +\n \"reached the 'ok' state.\",\n enforcedBy: \"server\",\n source: \"byollm_001 Rev 1 §E\",\n }),\n TTL_EXPIRY: must({\n id: \"TTL_EXPIRY\",\n statement:\n \"An unclaimed job MUST become 'expired' once its TTL elapses, and the \" +\n \"TTL clock MUST start when the job becomes claimable, not at enqueue.\",\n enforcedBy: \"server\",\n source: \"byollm_001 Rev 1 §D (TTL clock resolved in build review)\",\n }),\n NO_RUNNER_SIGNAL: must({\n id: \"NO_RUNNER_SIGNAL\",\n statement:\n \"A server MUST surface noRunnerAvailable when no runner with matching \" +\n \"capability has heartbeated within the liveness window, and MUST NOT \" +\n \"raise it for a job still blocked on dependencies.\",\n enforcedBy: \"server\",\n source: \"byollm_001 Rev 1 §D\",\n }),\n RESULT_IDEMPOTENT: must({\n id: \"RESULT_IDEMPOTENT\",\n statement:\n \"Result submission MUST be idempotent by job id; the first terminal \" +\n \"outcome wins and later submissions MUST NOT change it.\",\n enforcedBy: \"server\",\n source: \"byollm_001 §Endpoints.4\",\n }),\n RESULT_PROVENANCE: must({\n id: \"RESULT_PROVENANCE\",\n statement:\n \"A result from a non-'self' job MUST carry its provenance (audience and \" +\n \"runner) to the delivery seam so an app never treats volunteer output \" +\n \"as first-party.\",\n enforcedBy: \"server\",\n source: \"byollm_003 Rev 1 §Return-trip\",\n }),\n\n // ---- The trust surface -------------------------------------------------\n INGRESS_LOGGED_BEFORE_EXECUTION: must({\n id: \"INGRESS_LOGGED_BEFORE_EXECUTION\",\n statement:\n \"Every executed prompt MUST be appended to the local ingress log before \" +\n \"execution begins.\",\n enforcedBy: \"daemon\",\n source: \"byollm_001 §MUSTs\",\n }),\n\n // ---- Execution isolation (byollm_004) ---------------------------------\n NO_SHELL_INTERPOLATION: must({\n id: \"NO_SHELL_INTERPOLATION\",\n statement:\n \"Process-class backends MUST be invoked with a fixed argv array and the \" +\n \"payload delivered on stdin; payload text MUST NOT reach a command line.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §2\",\n }),\n NO_PAYLOAD_ROUTING: must({\n id: \"NO_PAYLOAD_ROUTING\",\n statement:\n \"Model, backend, base URL, and flags MUST come from owner config only; \" +\n \"a payload MUST NOT influence any of them.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §2\",\n }),\n STRIPPED_CHILD_ENV: must({\n id: \"STRIPPED_CHILD_ENV\",\n statement:\n \"Process-class children MUST spawn with an allowlisted environment, a \" +\n \"scratch cwd, no inherited descriptors beyond std streams, and hard \" +\n \"timeout and output-size caps.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §2\",\n }),\n HTTP_BASE_URL_SAFE: must({\n id: \"HTTP_BASE_URL_SAFE\",\n statement:\n \"HTTP-class backends MUST send requests only to the owner-configured \" +\n \"base URL and MUST refuse base URLs resolving to cloud-metadata or \" +\n \"link-local addresses.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 Rev 1 §Backend taxonomy\",\n }),\n OUTPUT_INERT: must({\n id: \"OUTPUT_INERT\",\n statement:\n \"Returned text MUST be treated as inert bytes: never evaluated, never \" +\n \"written to a payload-named path, never interpolated into a shell or \" +\n \"into terminal control sequences when logged.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §2\",\n }),\n COMMUNITY_BUDGETS: must({\n id: \"COMMUNITY_BUDGETS\",\n statement:\n \"Jobs whose owner is not the daemon's owner MUST be subject to the \" +\n \"owner's rate limits, daily cap, and resource budget.\",\n enforcedBy: \"daemon\",\n source: \"byollm_004 §4\",\n }),\n} as const satisfies Record<string, Must>);\n\n/** The id of any normative MUST. */\nexport type MustId = keyof typeof MUSTS;\n\n/** All MUST ids, for coverage checks. */\nexport const MUST_IDS = Object.freeze(Object.keys(MUSTS) as MustId[]);\n","import { z } from \"zod\";\nimport { OfferScope } from \"./audience.js\";\nimport { BackendClass, BackendIdSchema } from \"./backends.js\";\nimport { ClaimedJob, JobOutcome } from \"./job.js\";\nimport { JobKind } from \"./kinds.js\";\n\n/** Protocol version carried on every request; servers refuse what they can't speak. */\nexport const PROTOCOL_VERSION = \"0\" as const;\n\n/** The path prefix all endpoints mount under. */\nexport const PROTOCOL_PREFIX = \"/byollm\" as const;\n\n/** The five endpoint names, in the order byollm_001 lists them. */\nexport const ENDPOINTS = Object.freeze([\n \"pair\",\n \"claim\",\n \"heartbeat\",\n \"result\",\n \"release\",\n] as const);\nexport type Endpoint = (typeof ENDPOINTS)[number];\n\n/**\n * One entry of the capability matrix: a kind this daemon can actually serve,\n * right now, with the backend and model that would serve it.\n *\n * Derived from owner config intersected with detected reality\n * ({@link MUSTS.CAPABILITY_IS_DETECTED}) — a configured-but-unreachable\n * backend must not appear here. Carries `backendClass` so the app can tell\n * whether a result came from a sandboxed spawn or an HTTP call\n * (byollm_001 Rev 1 §A).\n */\nexport const Capability = z\n .object({\n kind: JobKind,\n backendId: BackendIdSchema,\n backendClass: BackendClass,\n model: z.string().min(1),\n offerScope: OfferScope,\n })\n .strict();\nexport type Capability = z.infer<typeof Capability>;\n\n/** The capability matrix a daemon advertises. */\nexport const CapabilityMatrix = z.array(Capability);\nexport type CapabilityMatrix = z.infer<typeof CapabilityMatrix>;\n\n// ---------------------------------------------------------------------------\n// 1. POST /byollm/pair — device-code flow\n// ---------------------------------------------------------------------------\n\n/**\n * Pairing is a device-code exchange, not a pasted secret\n * ({@link MUSTS.PAIR_INTERACTIVE}). The daemon starts a pairing, shows the\n * user a short code and a URL, and polls until the user approves it inside\n * the app's own authenticated session. Nothing listens on the user's machine\n * and nothing works over a copied string alone.\n */\nexport const PairStartRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n action: z.literal(\"start\"),\n daemon: z.object({\n version: z.string().min(1),\n /** Shown in the app's runner list so a user can tell their machines apart. */\n label: z.string().min(1).max(120),\n platform: z.enum([\"darwin\", \"linux\", \"win32\"]),\n }),\n capabilities: CapabilityMatrix,\n })\n .strict();\nexport type PairStartRequest = z.infer<typeof PairStartRequest>;\n\nexport const PairStartResponse = z\n .object({\n /** Secret the daemon polls with. Never shown to the user. */\n deviceCode: z.string().min(20),\n /** Short code the user reads and confirms in the browser. */\n userCode: z.string().min(4).max(16),\n /** Where the user approves. Must be on the server's own origin. */\n verificationUrl: z.url(),\n /** Epoch ms after which the code is dead ({@link MUSTS.PAIR_CODE_EXPIRES}). */\n expiresAt: z.number().int().positive(),\n /** How often the daemon may poll. */\n pollIntervalMs: z.number().int().min(500).max(60_000),\n })\n .strict();\nexport type PairStartResponse = z.infer<typeof PairStartResponse>;\n\nexport const PairPollRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n action: z.literal(\"poll\"),\n deviceCode: z.string().min(20),\n })\n .strict();\nexport type PairPollRequest = z.infer<typeof PairPollRequest>;\n\nexport const PairPollResponse = z.discriminatedUnion(\"status\", [\n z.object({ status: z.literal(\"pending\") }).strict(),\n z.object({ status: z.literal(\"denied\") }).strict(),\n z.object({ status: z.literal(\"expired\") }).strict(),\n z\n .object({\n status: z.literal(\"approved\"),\n /** Bearer token for every later call. Scoped to exactly one user. */\n runnerToken: z.string().min(20),\n runnerId: z.string().min(1),\n /** The app's id for the approving user — this daemon's owner forever. */\n owner: z.string().min(1),\n /** Display name for the trust UI, if the app offers one. */\n ownerLabel: z.string().optional(),\n })\n .strict(),\n]);\nexport type PairPollResponse = z.infer<typeof PairPollResponse>;\n\nexport const PairRequest = z.discriminatedUnion(\"action\", [\n PairStartRequest,\n PairPollRequest,\n]);\nexport type PairRequest = z.infer<typeof PairRequest>;\n\n// ---------------------------------------------------------------------------\n// 2. POST /byollm/claim\n// ---------------------------------------------------------------------------\n\nexport const ClaimRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n runnerId: z.string().min(1),\n /** Re-sent on every claim so a server never matches against a stale matrix. */\n capabilities: CapabilityMatrix,\n /** Upper bound on jobs to return; the server may return fewer. */\n max: z.number().int().min(1).max(64),\n })\n .strict();\nexport type ClaimRequest = z.infer<typeof ClaimRequest>;\n\nexport const ClaimResponse = z\n .object({\n jobs: z.array(ClaimedJob),\n /** Lease duration granted, so the daemon knows its renewal deadline. */\n leaseMs: z.number().int().positive(),\n })\n .strict();\nexport type ClaimResponse = z.infer<typeof ClaimResponse>;\n\n// ---------------------------------------------------------------------------\n// 3. POST /byollm/heartbeat\n// ---------------------------------------------------------------------------\n\nexport const HeartbeatRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n runnerId: z.string().min(1),\n daemonVersion: z.string().min(1),\n capabilities: CapabilityMatrix,\n /** Jobs this daemon believes it holds; the server renews their leases. */\n activeJobIds: z.array(z.string().min(1)),\n /** True while the owner has the daemon paused; the server stops offering work. */\n paused: z.boolean(),\n })\n .strict();\nexport type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;\n\nexport const HeartbeatResponse = z\n .object({\n /** Once true, the daemon stops claiming and abandons in-flight work. */\n revoked: z.boolean(),\n /**\n * Per-job cancel (byollm_001 Rev 1 §C). The daemon aborts these jobs'\n * in-flight backend calls and reports them `canceled`.\n */\n cancel: z.array(z.string().min(1)),\n /** Jobs whose leases were renewed, with their new expiry. */\n leases: z.array(\n z\n .object({\n jobId: z.string().min(1),\n expiresAt: z.number().int().positive(),\n })\n .strict(),\n ),\n /**\n * Jobs the daemon thinks it holds but the server has reassigned or\n * expired. The daemon must stop work on these and not report results.\n */\n lost: z.array(z.string().min(1)),\n /** Server clock, so a daemon with a skewed clock still honors leases. */\n serverTime: z.number().int().positive(),\n })\n .strict();\nexport type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;\n\n// ---------------------------------------------------------------------------\n// 4. POST /byollm/result\n// ---------------------------------------------------------------------------\n\nexport const ResultRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n runnerId: z.string().min(1),\n jobId: z.string().min(1),\n outcome: JobOutcome,\n /** Which model actually served it, for the result's provenance. */\n model: z.string().min(1),\n backendClass: BackendClass,\n /** Wall-clock milliseconds the backend call took. */\n durationMs: z.number().int().nonnegative(),\n })\n .strict();\nexport type ResultRequest = z.infer<typeof ResultRequest>;\n\nexport const ResultResponse = z\n .object({\n /**\n * False when the submission lost an idempotency race or the lease was\n * already gone — the daemon should discard, not retry\n * ({@link MUSTS.RESULT_IDEMPOTENT}).\n */\n accepted: z.boolean(),\n /** The job's state after this submission. */\n state: z.string().min(1),\n })\n .strict();\nexport type ResultResponse = z.infer<typeof ResultResponse>;\n\n// ---------------------------------------------------------------------------\n// 5. POST /byollm/release\n// ---------------------------------------------------------------------------\n\nexport const ReleaseRequest = z\n .object({\n protocolVersion: z.literal(PROTOCOL_VERSION),\n runnerId: z.string().min(1),\n jobIds: z.array(z.string().min(1)),\n /**\n * Why, so the app's runner list can say something true.\n *\n * `refused` is load-bearing, not cosmetic: the server cannot evaluate a\n * daemon's *local* `named` allowlist (§4.2), so it may legitimately offer\n * a job this daemon then declines. The server MUST record the refusal and\n * stop offering that job to that runner, or the pair would spin between\n * claim and release forever.\n */\n reason: z.enum([\"shutdown\", \"pause\", \"revoked\", \"backend-down\", \"refused\"]),\n })\n .strict();\nexport type ReleaseRequest = z.infer<typeof ReleaseRequest>;\n\nexport const ReleaseResponse = z\n .object({\n released: z.array(z.string().min(1)),\n })\n .strict();\nexport type ReleaseResponse = z.infer<typeof ReleaseResponse>;\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Wire error codes.\n *\n * byollm_002 requires that \"server unreachable\", \"revoked\", \"no matching\n * work\" and \"backend down\" never share a message. Distinct codes here are how\n * the daemon can tell three of those apart; the fourth is a transport failure\n * with no response at all.\n */\nexport const WireErrorCode = z.enum([\n \"bad-request\",\n \"unsupported-protocol-version\",\n \"unauthorized\",\n \"revoked\",\n \"not-found\",\n \"rate-limited\",\n \"server-error\",\n]);\nexport type WireErrorCode = z.infer<typeof WireErrorCode>;\n\nexport const WireError = z\n .object({\n error: WireErrorCode,\n message: z.string().min(1),\n /** Seconds; mirrors Retry-After for `rate-limited` and `server-error`. */\n retryAfter: z.number().int().nonnegative().optional(),\n })\n .strict();\nexport type WireError = z.infer<typeof WireError>;\n\n/** HTTP status each error code is served with. */\nexport const ERROR_STATUS: Readonly<Record<WireErrorCode, number>> =\n Object.freeze({\n \"bad-request\": 400,\n \"unsupported-protocol-version\": 400,\n unauthorized: 401,\n revoked: 403,\n \"not-found\": 404,\n \"rate-limited\": 429,\n \"server-error\": 500,\n });\n"],"mappings":"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","names":["z","z","z","z","z","z","z"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byollm/protocol",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "The BYOLLM wire contract — types, zod schemas, and the audience rules both the daemon and server enforce. ALPHA: under active development.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "README.md"
18
18
  ],
19
19
  "engines": {
20
- "node": ">=22.12"
20
+ "node": ">=22.14"
21
21
  },
22
22
  "dependencies": {
23
23
  "zod": "^4.4.3"
@@ -26,6 +26,14 @@
26
26
  "access": "public"
27
27
  },
28
28
  "homepage": "https://byo-llm.com",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/oftomorrowinc/byollm.git",
32
+ "directory": "packages/protocol"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/oftomorrowinc/byollm/issues"
36
+ },
29
37
  "scripts": {
30
38
  "build": "tsup",
31
39
  "clean": "rm -rf dist *.tsbuildinfo"