@byollm/protocol 0.1.0-alpha.43 → 0.1.0-alpha.44

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.43`) — under active development. Don't use this yet.**
2
+ > **Alpha (`0.1.0-alpha.44`) — under active development. Don't use this yet.**
3
3
  >
4
4
  > Install it deliberately: `npm install @byollm/protocol@alpha`.
5
5
  >
@@ -83,7 +83,7 @@
83
83
  > packages published and `@byollm/server` did not: a Sigstore
84
84
  > transparency-log 409 on its provenance attestation. The workflow's
85
85
  > "already published" guard correctly refuses to resume a partial publish,
86
- > so `0.1.0-alpha.43` is that release, whole.
86
+ > so `0.1.0-alpha.44` is that release, whole.
87
87
  >
88
88
  > If you run the Supabase adapter, `alpha.21` needs
89
89
  > `20260819010000_completed_by_lease_id.sql`: alpha.19 shipped §3.6's
@@ -91,7 +91,7 @@
91
91
 
92
92
  <!-- release-note 0.1.0-alpha.40 -->
93
93
  **`byollm install` — stop keeping a terminal open.** The daemon can now run
94
- under your machine's own supervisor and restart itself if it stops: a launchd
94
+ under your computer's own supervisor and restart itself if it stops: a launchd
95
95
  agent on macOS, a `systemd --user` unit on Linux, a logon task on Windows. All
96
96
  user-level — no root, no system directories, and `byollm uninstall` takes it
97
97
  away. `byollm status` gained a line saying whether it is actually supervised
@@ -115,7 +115,7 @@ const { outcome, fallback } = await job.result({
115
115
 
116
116
  Whatever you return, `result()` labels it `fallback: true` — the stamp is
117
117
  applied by the wait, not taken from you, so an answer that did not run on
118
- somebody's machine cannot be reported as though it did (`FALLBACK_LABELED`).
118
+ somebody's device cannot be reported as though it did (`FALLBACK_LABELED`).
119
119
  Both delivery channels do it, polling and Supabase Realtime. Records still
120
120
  work; they just get labelled too.
121
121
 
@@ -166,10 +166,10 @@ job's owner.
166
166
  import { matchAudience } from "@byollm/protocol";
167
167
 
168
168
  matchAudience(
169
- { owner: "alice", audience: "named" },
169
+ { owner: "alice", audience: "team" },
170
170
  {
171
171
  owner: "bob",
172
- offerScope: "named",
172
+ offerScope: "team",
173
173
  account: "open",
174
174
  // The daemon's OWN allowlist decides — never the server's assertion.
175
175
  locallyAllows: (owner) => owner === "alice",
package/dist/index.d.ts CHANGED
@@ -157,45 +157,98 @@ declare function backendDescriptor(id: BackendId): BackendDescriptor;
157
157
  */
158
158
  declare function isLocalHost(hostname: string): boolean;
159
159
  /**
160
- * The cost class of a configured backend instance.
160
+ * Is this model name a hosted one billed by its vendor?
161
+ *
162
+ * Ollama serves cloud models through the same local endpoint as local ones,
163
+ * so the address says "free" about a model somebody is being charged for. The
164
+ * only thing that distinguishes them is the name, and the distinguishing part
165
+ * is the **tag** — everything after the last colon.
166
+ *
167
+ * End-anchored on the tag, which is what makes it decidable rather than a
168
+ * guess about substrings:
169
+ *
170
+ * - `glm-5.2:cloud` → cloud
171
+ * - `deepseek-v4-flash:0731-cloud` → cloud
172
+ * - `x:cloudless` → not cloud, the tag ends in "less"
173
+ * - `cloudmodel:7b` → not cloud, the tag is "7b"
174
+ * - `llama3.2` → not cloud, there is no tag at all
175
+ *
176
+ * An oddball like `:xcloud` classifies as cloud, and that is the **only
177
+ * permitted failure direction**: calling a free model metered narrows what an
178
+ * owner may share and costs nobody money, while the reverse hands somebody
179
+ * else's bill to a stranger.
180
+ */
181
+ declare function isCloudTaggedModel(model: string): boolean;
182
+ /**
183
+ * The cost class of a configured service.
161
184
  *
162
185
  * For every named provider this is whatever the registry says, full stop
163
186
  * ({@link MUSTS.COST_NOT_CONFIGURABLE}). For the generic `openai-http` entry
164
187
  * it is inferred from the base URL, and a base URL that cannot be parsed is
165
188
  * treated as `metered` — the expensive side, because guessing "free" wrong
166
189
  * costs the owner money.
190
+ *
191
+ * The model has the last word in one direction only. A local address with a
192
+ * cloud-tagged model is `metered`: Ollama proxies hosted models through
193
+ * `127.0.0.1`, so the endpoint is local and the bill is not. Read from the
194
+ * **configured value**, never from what the server lists — the owner's config
195
+ * is the thing they chose, and a server's catalogue is not theirs to be
196
+ * classified by.
167
197
  */
168
- declare function resolveCost(id: BackendId, baseUrl: string | undefined): BackendCost;
198
+ declare function resolveCost(id: BackendId, baseUrl: string | undefined, model?: string): BackendCost;
169
199
 
170
200
  /**
171
201
  * Who may run a job, declared by the app that enqueued it.
172
202
  *
173
- * - `self` — only the job owner's own daemon.
174
- * - `named` — a daemon whose owner has explicitly allowed this (server, user)
175
- * pair in their *local* allowlist (byollm_001 Rev 1 §B).
176
- * - `public` — any daemon offering `public` compute.
203
+ * - `private` — only the job owner's own devices.
204
+ * - `team` — a device whose owner has this person on their roster.
205
+ * - `public` any device offering `public` compute.
206
+ *
207
+ * **One vocabulary, ruled 2026-08-24.** These were `self | named | public`
208
+ * while {@link OfferScope} became `private | team | public`, which would have
209
+ * left every seam where the two meet speaking two languages for one idea, and
210
+ * every doc explaining "self versus private" for ever. They are still
211
+ * independent axes — a job says who may run it, a service says who it will run
212
+ * for — and a job runs only where both agree
213
+ * ({@link MUSTS.AUDIENCE_BOTH_SIDES}). Sharing the words costs nothing and
214
+ * saves a mapping layer nobody would have enjoyed maintaining.
177
215
  */
178
216
  declare const Audience: z.ZodEnum<{
179
- self: "self";
180
- named: "named";
217
+ private: "private";
218
+ team: "team";
181
219
  public: "public";
182
220
  }>;
183
221
  type Audience = z.infer<typeof Audience>;
184
222
  /**
185
- * What a daemon backend is willing to run, declared by the machine's owner.
186
- * Same three values as {@link Audience}, but the two are independent axes —
187
- * a job runs only where both agree ({@link MUSTS.AUDIENCE_BOTH_SIDES}).
223
+ * What a device's owner is willing to run for other people, per service.
224
+ *
225
+ * - `private` the owner's own work only.
226
+ * - `team` — the owner's roster. Membership is **central**, not per-person:
227
+ * the device follows the roster rather than holding its own copy of who is
228
+ * in it (byollm_016, 2026-08-24).
229
+ * - `public` — any job, from anyone. This is the community posture the daemon
230
+ * has always had (`byollm offer <service> public`), bounded by community
231
+ * budgets. byollm_016 parks it as a **hosted-product** surface — the cloud
232
+ * dashboard does not offer it — which is not the same as removing it from
233
+ * the daemon, and the CLI still accepts it.
234
+ *
235
+ * Deliberately *not* the same words as {@link Audience}, which they used to
236
+ * share. They are independent axes — a job says who may run it, a service says
237
+ * who it will run for — and identical spellings made that read as one concept
238
+ * with two homes. A job runs only where both agree
239
+ * ({@link MUSTS.AUDIENCE_BOTH_SIDES}); {@link matches} is the one place the
240
+ * correspondence lives.
188
241
  */
189
242
  declare const OfferScope: z.ZodEnum<{
190
- self: "self";
191
- named: "named";
243
+ private: "private";
244
+ team: "team";
192
245
  public: "public";
193
246
  }>;
194
247
  type OfferScope = z.infer<typeof OfferScope>;
195
248
  /** All audience values, in widening order. */
196
- declare const AUDIENCES: readonly ("self" | "named" | "public")[];
249
+ declare const AUDIENCES: readonly ("private" | "team" | "public")[];
197
250
  /** All offer scopes, in widening order. */
198
- declare const OFFER_SCOPES: readonly ("self" | "named" | "public")[];
251
+ declare const OFFER_SCOPES: readonly ("private" | "team" | "public")[];
199
252
  /**
200
253
  * Why a job was refused. Distinct codes because byollm_002 requires that
201
254
  * different truths never share a message — "no matching work" and "refused on
@@ -236,7 +289,7 @@ interface SpendConsent {
236
289
  *
237
290
  * - `subscription` is locked to `self` regardless of config
238
291
  * ({@link MUSTS.SUBSCRIPTION_SELF_LOCK}) — someone else's terms.
239
- * - `metered` narrows to `self` unless the owner has explicitly acknowledged
292
+ * - `metered` narrows to `private` unless the owner has explicitly acknowledged
240
293
  * the spend ({@link MUSTS.METERED_DEFAULTS_SELF}) — their money.
241
294
  * - `free` passes through — their electricity.
242
295
  *
@@ -291,10 +344,10 @@ interface MatchDaemon {
291
344
  * @example
292
345
  * ```ts
293
346
  * const result = matchAudience(
294
- * { owner: "alice", audience: "named" },
347
+ * { owner: "alice", audience: "team" },
295
348
  * {
296
349
  * owner: "bob",
297
- * offerScope: "named",
350
+ * offerScope: "team",
298
351
  * cost: "free",
299
352
  * locallyAllows: (o) => o === "alice",
300
353
  * },
@@ -505,8 +558,8 @@ declare const ClaimedJob: z.ZodObject<{
505
558
  system: z.ZodOptional<z.ZodString>;
506
559
  }, z.core.$strict>]>;
507
560
  audience: z.ZodEnum<{
508
- self: "self";
509
- named: "named";
561
+ private: "private";
562
+ team: "team";
510
563
  public: "public";
511
564
  }>;
512
565
  owner: z.ZodString;
@@ -527,8 +580,8 @@ type ClaimedJob = z.infer<typeof ClaimedJob>;
527
580
  */
528
581
  declare const ResultProvenance: z.ZodObject<{
529
582
  audience: z.ZodEnum<{
530
- self: "self";
531
- named: "named";
583
+ private: "private";
584
+ team: "team";
532
585
  public: "public";
533
586
  }>;
534
587
  runnerId: z.ZodString;
@@ -663,8 +716,8 @@ declare const DeliveredResult: z.ZodObject<{
663
716
  }, z.core.$strict>], "outcome">>;
664
717
  provenance: z.ZodOptional<z.ZodObject<{
665
718
  audience: z.ZodEnum<{
666
- self: "self";
667
- named: "named";
719
+ private: "private";
720
+ team: "team";
668
721
  public: "public";
669
722
  }>;
670
723
  runnerId: z.ZodString;
@@ -742,8 +795,8 @@ declare const JobStub: z.ZodObject<{
742
795
  owner: z.ZodString;
743
796
  site: z.ZodString;
744
797
  audience: z.ZodEnum<{
745
- self: "self";
746
- named: "named";
798
+ private: "private";
799
+ team: "team";
747
800
  public: "public";
748
801
  }>;
749
802
  sizeClass: z.ZodEnum<{
@@ -766,8 +819,8 @@ declare const ClaimedStub: z.ZodObject<{
766
819
  owner: z.ZodString;
767
820
  site: z.ZodString;
768
821
  audience: z.ZodEnum<{
769
- self: "self";
770
- named: "named";
822
+ private: "private";
823
+ team: "team";
771
824
  public: "public";
772
825
  }>;
773
826
  sizeClass: z.ZodEnum<{
@@ -1452,6 +1505,8 @@ declare const Capability: z.ZodObject<{
1452
1505
  "llm.generate": "llm.generate";
1453
1506
  "llm.chat": "llm.chat";
1454
1507
  }>;
1508
+ service: z.ZodString;
1509
+ isDefault: z.ZodBoolean;
1455
1510
  backendId: z.ZodEnum<{
1456
1511
  ollama: "ollama";
1457
1512
  mlx: "mlx";
@@ -1478,8 +1533,8 @@ declare const Capability: z.ZodObject<{
1478
1533
  }>;
1479
1534
  model: z.ZodString;
1480
1535
  offerScope: z.ZodEnum<{
1481
- self: "self";
1482
- named: "named";
1536
+ private: "private";
1537
+ team: "team";
1483
1538
  public: "public";
1484
1539
  }>;
1485
1540
  }, z.core.$strict>;
@@ -1490,6 +1545,8 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
1490
1545
  "llm.generate": "llm.generate";
1491
1546
  "llm.chat": "llm.chat";
1492
1547
  }>;
1548
+ service: z.ZodString;
1549
+ isDefault: z.ZodBoolean;
1493
1550
  backendId: z.ZodEnum<{
1494
1551
  ollama: "ollama";
1495
1552
  mlx: "mlx";
@@ -1516,12 +1573,41 @@ declare const CapabilityMatrix: z.ZodArray<z.ZodObject<{
1516
1573
  }>;
1517
1574
  model: z.ZodString;
1518
1575
  offerScope: z.ZodEnum<{
1519
- self: "self";
1520
- named: "named";
1576
+ private: "private";
1577
+ team: "team";
1521
1578
  public: "public";
1522
1579
  }>;
1523
1580
  }, z.core.$strict>>;
1524
1581
  type CapabilityMatrix = z.infer<typeof CapabilityMatrix>;
1582
+ /**
1583
+ * A kind this device could serve and deliberately does not — byollm_016.
1584
+ *
1585
+ * Two services answer one kind and the owner has not said which wins, so the
1586
+ * kind is not advertised. That is correct and, unsaid, invisible: the owner
1587
+ * adds a second service, jobs stop matching, and no surface explains it.
1588
+ *
1589
+ * It travels because the surfaces that must say so are not all on the device.
1590
+ * The owner's card names the claimants; a teammate's card says only that the
1591
+ * owner has a choice to make. Claimant **offer scopes** ride along so the hub
1592
+ * can compute that difference without the device deciding who is asking —
1593
+ * carry for computation, filter for display, the same shape the effective
1594
+ * offer already uses.
1595
+ */
1596
+ declare const WithheldKind: z.ZodObject<{
1597
+ kind: z.ZodEnum<{
1598
+ "llm.generate": "llm.generate";
1599
+ "llm.chat": "llm.chat";
1600
+ }>;
1601
+ claimants: z.ZodArray<z.ZodObject<{
1602
+ service: z.ZodString;
1603
+ offer: z.ZodEnum<{
1604
+ private: "private";
1605
+ team: "team";
1606
+ public: "public";
1607
+ }>;
1608
+ }, z.core.$strict>>;
1609
+ }, z.core.$strict>;
1610
+ type WithheldKind = z.infer<typeof WithheldKind>;
1525
1611
  /**
1526
1612
  * Pairing is a device-code exchange, not a pasted secret
1527
1613
  * ({@link MUSTS.PAIR_INTERACTIVE}). The daemon starts a pairing, shows the
@@ -1551,6 +1637,8 @@ declare const PairStartRequest: z.ZodObject<{
1551
1637
  "llm.generate": "llm.generate";
1552
1638
  "llm.chat": "llm.chat";
1553
1639
  }>;
1640
+ service: z.ZodString;
1641
+ isDefault: z.ZodBoolean;
1554
1642
  backendId: z.ZodEnum<{
1555
1643
  ollama: "ollama";
1556
1644
  mlx: "mlx";
@@ -1577,8 +1665,8 @@ declare const PairStartRequest: z.ZodObject<{
1577
1665
  }>;
1578
1666
  model: z.ZodString;
1579
1667
  offerScope: z.ZodEnum<{
1580
- self: "self";
1581
- named: "named";
1668
+ private: "private";
1669
+ team: "team";
1582
1670
  public: "public";
1583
1671
  }>;
1584
1672
  }, z.core.$strict>>;
@@ -1638,6 +1726,8 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
1638
1726
  "llm.generate": "llm.generate";
1639
1727
  "llm.chat": "llm.chat";
1640
1728
  }>;
1729
+ service: z.ZodString;
1730
+ isDefault: z.ZodBoolean;
1641
1731
  backendId: z.ZodEnum<{
1642
1732
  ollama: "ollama";
1643
1733
  mlx: "mlx";
@@ -1664,8 +1754,8 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
1664
1754
  }>;
1665
1755
  model: z.ZodString;
1666
1756
  offerScope: z.ZodEnum<{
1667
- self: "self";
1668
- named: "named";
1757
+ private: "private";
1758
+ team: "team";
1669
1759
  public: "public";
1670
1760
  }>;
1671
1761
  }, z.core.$strict>>;
@@ -1683,6 +1773,8 @@ declare const ClaimRequest: z.ZodObject<{
1683
1773
  "llm.generate": "llm.generate";
1684
1774
  "llm.chat": "llm.chat";
1685
1775
  }>;
1776
+ service: z.ZodString;
1777
+ isDefault: z.ZodBoolean;
1686
1778
  backendId: z.ZodEnum<{
1687
1779
  ollama: "ollama";
1688
1780
  mlx: "mlx";
@@ -1709,8 +1801,8 @@ declare const ClaimRequest: z.ZodObject<{
1709
1801
  }>;
1710
1802
  model: z.ZodString;
1711
1803
  offerScope: z.ZodEnum<{
1712
- self: "self";
1713
- named: "named";
1804
+ private: "private";
1805
+ team: "team";
1714
1806
  public: "public";
1715
1807
  }>;
1716
1808
  }, z.core.$strict>>;
@@ -1727,8 +1819,8 @@ declare const ClaimResponse: z.ZodObject<{
1727
1819
  owner: z.ZodString;
1728
1820
  site: z.ZodString;
1729
1821
  audience: z.ZodEnum<{
1730
- self: "self";
1731
- named: "named";
1822
+ private: "private";
1823
+ team: "team";
1732
1824
  public: "public";
1733
1825
  }>;
1734
1826
  sizeClass: z.ZodEnum<{
@@ -1757,6 +1849,8 @@ declare const HeartbeatRequest: z.ZodObject<{
1757
1849
  "llm.generate": "llm.generate";
1758
1850
  "llm.chat": "llm.chat";
1759
1851
  }>;
1852
+ service: z.ZodString;
1853
+ isDefault: z.ZodBoolean;
1760
1854
  backendId: z.ZodEnum<{
1761
1855
  ollama: "ollama";
1762
1856
  mlx: "mlx";
@@ -1783,11 +1877,25 @@ declare const HeartbeatRequest: z.ZodObject<{
1783
1877
  }>;
1784
1878
  model: z.ZodString;
1785
1879
  offerScope: z.ZodEnum<{
1786
- self: "self";
1787
- named: "named";
1880
+ private: "private";
1881
+ team: "team";
1788
1882
  public: "public";
1789
1883
  }>;
1790
1884
  }, z.core.$strict>>;
1885
+ withheld: z.ZodDefault<z.ZodArray<z.ZodObject<{
1886
+ kind: z.ZodEnum<{
1887
+ "llm.generate": "llm.generate";
1888
+ "llm.chat": "llm.chat";
1889
+ }>;
1890
+ claimants: z.ZodArray<z.ZodObject<{
1891
+ service: z.ZodString;
1892
+ offer: z.ZodEnum<{
1893
+ private: "private";
1894
+ team: "team";
1895
+ public: "public";
1896
+ }>;
1897
+ }, z.core.$strict>>;
1898
+ }, z.core.$strict>>>;
1791
1899
  activeLeases: z.ZodArray<z.ZodObject<{
1792
1900
  jobId: z.ZodString;
1793
1901
  leaseId: z.ZodString;
@@ -1957,4 +2065,4 @@ declare const FetchResponse: z.ZodObject<{
1957
2065
  }, z.core.$strict>;
1958
2066
  type FetchResponse = z.infer<typeof FetchResponse>;
1959
2067
 
1960
- export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DeliveredResult, ENCRYPTION_KEY_CONTEXT, ENDPOINTS, ENVELOPE_MAX_AGE_MS, ERROR_STATUS, type Endpoint, type EnvelopeContext, EnvelopeDirection, type EnvelopeFailure, FetchRequest, FetchResponse, GeneratePayload, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, OFFER_SCOPES, OfferScope, type OpenResult, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, REFUSAL_MESSAGES, RETIREMENT_WINDOW_MS, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SizeClass, type SpendConsent, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, type VersionRefusal, WireError, WireErrorCode, backendDescriptor, canTransition, canonicalRequest, checkProtocolVersion, cryptoReady, declaredVersion, effectiveOfferScope, fingerprint, generateKeys, isBackendId, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signRequest, signSiteRequest, signSuccession, signWith, sizeClassCeiling, sizeClassOf, successionStatement, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession };
2068
+ export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DeliveredResult, ENCRYPTION_KEY_CONTEXT, ENDPOINTS, ENVELOPE_MAX_AGE_MS, ERROR_STATUS, type Endpoint, type EnvelopeContext, EnvelopeDirection, type EnvelopeFailure, FetchRequest, FetchResponse, GeneratePayload, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, OFFER_SCOPES, OfferScope, type OpenResult, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, REFUSAL_MESSAGES, RETIREMENT_WINDOW_MS, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SizeClass, type SpendConsent, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, canTransition, canonicalRequest, checkProtocolVersion, cryptoReady, declaredVersion, effectiveOfferScope, fingerprint, generateKeys, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signRequest, signSiteRequest, signSuccession, signWith, sizeClassCeiling, sizeClassOf, successionStatement, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession };
package/dist/index.js CHANGED
@@ -189,9 +189,13 @@ function isLocalHost(hostname) {
189
189
  if (host.startsWith("192.168.")) return true;
190
190
  return /^172\.(1[6-9]|2\d|3[01])\./.test(host);
191
191
  }
192
- function resolveCost(id, baseUrl) {
192
+ function isCloudTaggedModel(model) {
193
+ return /:[^:]*cloud$/.test(model);
194
+ }
195
+ function resolveCost(id, baseUrl, model) {
193
196
  const declared = BACKENDS[id].cost;
194
197
  if (declared !== null) return declared;
198
+ if (model !== void 0 && isCloudTaggedModel(model)) return "metered";
195
199
  if (baseUrl === void 0) return "metered";
196
200
  try {
197
201
  return isLocalHost(new URL(baseUrl).hostname) ? "free" : "metered";
@@ -201,8 +205,8 @@ function resolveCost(id, baseUrl) {
201
205
  }
202
206
 
203
207
  // src/audience.ts
204
- var Audience = z2.enum(["self", "named", "public"]);
205
- var OfferScope = z2.enum(["self", "named", "public"]);
208
+ var Audience = z2.enum(["private", "team", "public"]);
209
+ var OfferScope = z2.enum(["private", "team", "public"]);
206
210
  var AUDIENCES = Object.freeze(Audience.options);
207
211
  var OFFER_SCOPES = Object.freeze(OfferScope.options);
208
212
  var MatchRefusal = z2.enum([
@@ -226,16 +230,16 @@ var MatchRefusal = z2.enum([
226
230
  var ALLOWED = Object.freeze({ ok: true });
227
231
  var refuse = (refusal) => Object.freeze({ ok: false, refusal });
228
232
  function effectiveOfferScope(configured, cost, spend) {
229
- if (cost === "subscription") return "self";
230
- if (cost === "metered" && spend?.acknowledged !== true) return "self";
233
+ if (cost === "subscription") return "private";
234
+ if (cost === "metered" && spend?.acknowledged !== true) return "private";
231
235
  return configured;
232
236
  }
233
237
  function matchAudience(job, daemon) {
234
238
  const sameOwner = job.owner === daemon.owner;
235
- if (job.audience === "self" && !sameOwner) {
239
+ if (job.audience === "private" && !sameOwner) {
236
240
  return refuse("audience-self-other-owner");
237
241
  }
238
- if (job.audience === "named" && !sameOwner && job.audienceAllow !== void 0 && !job.audienceAllow.includes(daemon.owner)) {
242
+ if (job.audience === "team" && !sameOwner && job.audienceAllow !== void 0 && !job.audienceAllow.includes(daemon.owner)) {
239
243
  return refuse("not-in-server-allowlist");
240
244
  }
241
245
  const scope = effectiveOfferScope(
@@ -258,19 +262,19 @@ function matchAudience(job, daemon) {
258
262
  }
259
263
  }
260
264
  switch (scope) {
261
- case "self":
265
+ case "private":
262
266
  return refuse("offer-scope-too-narrow");
263
- case "named":
267
+ case "team":
264
268
  return daemon.locallyAllows(job.owner) ? ALLOWED : refuse("not-locally-allowed");
265
269
  case "public":
266
270
  return ALLOWED;
267
271
  }
268
272
  }
269
273
  var REFUSAL_MESSAGES = Object.freeze({
270
- "no-capability": "no backend on this machine is configured and healthy for that job kind",
271
- "audience-self-other-owner": "the job is private to its owner and this machine is paired to someone else",
272
- "not-locally-allowed": "the job's owner is not on this machine's allowlist (byollm allow <server> <user>)",
273
- "not-in-server-allowlist": "the app restricted this job to named runners and this machine is not one of them",
274
+ "no-capability": "no backend on this device is configured and healthy for that job kind",
275
+ "audience-self-other-owner": "the job is private to its owner and this device is paired to someone else",
276
+ "not-locally-allowed": "the job's owner is not on this device's allowlist (byollm allow <server> <user>)",
277
+ "not-in-server-allowlist": "the app restricted this job to named runners and this device is not one of them",
274
278
  "offer-scope-too-narrow": "this backend is offered to its owner only (byollm offer <backend> named|public to widen)",
275
279
  "subscription-self-lock": "subscription-backed models run their owner's work only \u2014 this is a protocol rule, not a setting",
276
280
  "metered-no-spend-consent": "this backend bills its owner per token, and they have not agreed to spend it on other people's work",
@@ -431,7 +435,7 @@ function provenanceFor(input) {
431
435
  runnerOwner: input.runnerOwner,
432
436
  backendClass: input.backendClass,
433
437
  model: input.model,
434
- untrusted: input.audience !== "self"
438
+ untrusted: input.audience !== "private"
435
439
  };
436
440
  }
437
441
  var RunMetadata = z4.object({
@@ -1388,12 +1392,36 @@ var ENDPOINTS = Object.freeze([
1388
1392
  ]);
1389
1393
  var Capability = z9.object({
1390
1394
  kind: JobKind,
1395
+ /**
1396
+ * The owner's name for the service answering this kind — byollm_016.
1397
+ *
1398
+ * A device advertises *which* of its services serves a kind, not merely
1399
+ * that something does. Phase B lets a job select by this name; until then
1400
+ * it is what a device page shows and what a default is chosen between.
1401
+ */
1402
+ service: z9.string().min(1),
1403
+ /**
1404
+ * Whether this row is the default for its kind.
1405
+ *
1406
+ * Stated rather than inferred from being the only row, which is true in
1407
+ * Phase A and stops being true the moment Phase B advertises every
1408
+ * selectable service per kind. A consumer that learned "default means
1409
+ * alone" would have to unlearn it, and the ones that did not would be
1410
+ * quietly wrong. One field now, no second shape later.
1411
+ */
1412
+ isDefault: z9.boolean(),
1391
1413
  backendId: BackendIdSchema,
1392
1414
  backendClass: BackendClass,
1393
1415
  model: z9.string().min(1),
1394
1416
  offerScope: OfferScope
1395
1417
  }).strict();
1396
1418
  var CapabilityMatrix = z9.array(Capability);
1419
+ var WithheldKind = z9.object({
1420
+ kind: JobKind,
1421
+ claimants: z9.array(
1422
+ z9.object({ service: z9.string().min(1), offer: OfferScope }).strict()
1423
+ ).min(2)
1424
+ }).strict();
1397
1425
  var PairStartRequest = z9.object({
1398
1426
  protocolVersion: z9.literal(PROTOCOL_VERSION),
1399
1427
  action: z9.literal("start"),
@@ -1504,6 +1532,14 @@ var HeartbeatRequest = z9.object({
1504
1532
  runnerId: z9.string().min(1),
1505
1533
  daemonVersion: z9.string().min(1),
1506
1534
  capabilities: CapabilityMatrix,
1535
+ /**
1536
+ * Kinds this device is withholding, and why it can be said.
1537
+ *
1538
+ * Optional so a daemon that has nothing withheld sends nothing, and so an
1539
+ * older daemon against a newer hub is simply a device with no withheld
1540
+ * kinds rather than a parse failure.
1541
+ */
1542
+ withheld: z9.array(WithheldKind).default([]),
1507
1543
  /**
1508
1544
  * Leases this daemon believes it holds; the server renews exactly these.
1509
1545
  *
@@ -1976,6 +2012,7 @@ export {
1976
2012
  TERMINAL_STATES,
1977
2013
  WireError,
1978
2014
  WireErrorCode,
2015
+ WithheldKind,
1979
2016
  backendDescriptor,
1980
2017
  canTransition,
1981
2018
  canonicalRequest,
@@ -1986,6 +2023,7 @@ export {
1986
2023
  fingerprint,
1987
2024
  generateKeys,
1988
2025
  isBackendId,
2026
+ isCloudTaggedModel,
1989
2027
  isJobKind,
1990
2028
  isLocalHost,
1991
2029
  isTerminal,