@byollm/protocol 0.1.0-alpha.57 → 0.1.0-alpha.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -255,22 +255,28 @@ function classifyCost(id, baseUrl, model) {
255
255
  }
256
256
 
257
257
  // src/audience.ts
258
- var Audience = z2.enum(["private", "team", "public"]);
259
- var OfferScope = z2.enum(["private", "team", "public"]);
258
+ var Audience = z2.enum(["private", "team"]);
259
+ var OfferScope = z2.enum(["private", "team"]);
260
260
  var AUDIENCES = Object.freeze(Audience.options);
261
261
  var OFFER_SCOPES = Object.freeze(OfferScope.options);
262
262
  var MatchRefusal = z2.enum([
263
263
  /** The daemon advertises no capability for this kind. */
264
264
  "no-capability",
265
- /** Job is `self` but this daemon belongs to a different user. */
265
+ /** Job is `private` but this daemon belongs to a different user. */
266
266
  "audience-self-other-owner",
267
- /** Job is `named` but this daemon's local allowlist does not admit the owner. */
267
+ /**
268
+ * Job is `team` and nothing this device verified admits the job's owner.
269
+ *
270
+ * The id predates the grant and is kept, because ids are public and cited
271
+ * by conformance output. What it means has not moved: this device was not
272
+ * shown anything it could check.
273
+ */
268
274
  "not-locally-allowed",
269
- /** Job is `named`/`public` but the server's own allowlist excludes this runner. */
275
+ /** Job is `team` but the server's own allowlist excludes this runner. */
270
276
  "not-in-server-allowlist",
271
- /** The backend offers only `self` and the job belongs to someone else. */
277
+ /** The service offers only `private` and the job belongs to someone else. */
272
278
  "offer-scope-too-narrow",
273
- /** The matched backend is subscription-class, which is locked to `self`. */
279
+ /** The matched backend is subscription-class, which is locked to `private`. */
274
280
  "subscription-self-lock",
275
281
  /** The backend spends the owner's money and they have not agreed to share it. */
276
282
  "metered-no-spend-consent",
@@ -315,17 +321,15 @@ function matchAudience(job, daemon) {
315
321
  case "private":
316
322
  return refuse("offer-scope-too-narrow");
317
323
  case "team":
318
- return daemon.locallyAllows(job.owner) ? ALLOWED : refuse("not-locally-allowed");
319
- case "public":
320
- return ALLOWED;
324
+ return daemon.admits(job.owner) ? ALLOWED : refuse("not-locally-allowed");
321
325
  }
322
326
  }
323
327
  var REFUSAL_MESSAGES = Object.freeze({
324
328
  "no-capability": "no backend on this device is configured and healthy for that job kind",
325
329
  "audience-self-other-owner": "the job is private to its owner and this device is paired to someone else",
326
- "not-locally-allowed": "the job's owner is not on this device's allowlist (byollm allow <server> <user>)",
330
+ "not-locally-allowed": "nothing this device can verify says the job's owner may use it",
327
331
  "not-in-server-allowlist": "the app restricted this job to named runners and this device is not one of them",
328
- "offer-scope-too-narrow": "this backend is offered to its owner only (byollm offer <backend> named|public to widen)",
332
+ "offer-scope-too-narrow": "this service is offered to its owner only (`byollm offer <service> team` to widen)",
329
333
  "subscription-self-lock": "subscription-backed models run their owner's work only \u2014 this is a protocol rule, not a setting",
330
334
  "metered-no-spend-consent": "this backend bills its owner per token, and they have not agreed to spend it on other people's work",
331
335
  "metered-ceiling-reached": "this backend is shared but has reached the spend ceiling its owner set"
@@ -344,7 +348,7 @@ var PAYLOAD_LIMITS = Object.freeze({
344
348
  var ChatMessage = z3.object({
345
349
  role: z3.enum(["system", "user", "assistant"]),
346
350
  content: z3.string().max(PAYLOAD_LIMITS.maxTextChars)
347
- });
351
+ }).strict();
348
352
  var GeneratePayload = z3.object({
349
353
  prompt: z3.string().min(1).max(PAYLOAD_LIMITS.maxTextChars),
350
354
  system: z3.string().max(PAYLOAD_LIMITS.maxTextChars).optional()
@@ -366,8 +370,11 @@ var ChatPayload = z3.object({
366
370
  var JobKind = z3.enum(["llm.generate", "llm.chat"]);
367
371
  var JOB_KINDS = Object.freeze(JobKind.options);
368
372
  var KindedPayload = z3.discriminatedUnion("kind", [
369
- z3.object({ kind: z3.literal("llm.generate"), payload: GeneratePayload }),
370
- z3.object({ kind: z3.literal("llm.chat"), payload: ChatPayload })
373
+ // Strict on the wrappers too. A union member that strips is a door beside
374
+ // the one that is locked: the payloads inside are strict, and an extra key
375
+ // on the envelope vanished just as quietly.
376
+ z3.object({ kind: z3.literal("llm.generate"), payload: GeneratePayload }).strict(),
377
+ z3.object({ kind: z3.literal("llm.chat"), payload: ChatPayload }).strict()
371
378
  ]);
372
379
  function isJobKind(value) {
373
380
  return JOB_KINDS.includes(value);
@@ -384,8 +391,243 @@ function payloadTextLength(kinded) {
384
391
  }
385
392
 
386
393
  // src/job.ts
394
+ import { z as z6 } from "zod";
395
+
396
+ // src/grant.ts
397
+ import { Buffer as Buffer2 } from "buffer";
398
+ import { z as z5 } from "zod";
399
+
400
+ // src/keys.ts
401
+ import {
402
+ createHash,
403
+ createPrivateKey,
404
+ createPublicKey,
405
+ generateKeyPairSync,
406
+ sign,
407
+ verify
408
+ } from "crypto";
387
409
  import { z as z4 } from "zod";
388
- var JobState = z4.enum([
410
+ var PublicIdentity = z4.object({
411
+ /** Raw Ed25519 public key. The pinned one. */
412
+ identity: z4.string().min(1),
413
+ /** Raw X25519 public key, for sealing to this party. */
414
+ encryption: z4.string().min(1),
415
+ /**
416
+ * Ed25519 signature over the encryption key, by the identity key.
417
+ *
418
+ * This is what stops an upstream substituting an encryption key of its
419
+ * own while relaying a genuine identity: the receiver pins the identity
420
+ * and refuses any encryption key not signed by it.
421
+ */
422
+ encryptionSig: z4.string().min(1)
423
+ }).strict();
424
+ var StoredKeys = z4.object({
425
+ version: z4.literal(1),
426
+ identityPublic: z4.string().min(1),
427
+ identityPrivate: z4.string().min(1),
428
+ encryptionPublic: z4.string().min(1),
429
+ encryptionPrivate: z4.string().min(1),
430
+ encryptionSig: z4.string().min(1),
431
+ createdAt: z4.number().int().positive()
432
+ }).strict();
433
+ var ENCRYPTION_KEY_CONTEXT = "byollm/v1/encryption-key";
434
+ function rawPublic(key) {
435
+ const jwk = key.export({ format: "jwk" });
436
+ const x = jwk.x;
437
+ if (typeof x !== "string") throw new Error("key has no raw public component");
438
+ return x;
439
+ }
440
+ function importPublic(raw, crv) {
441
+ return createPublicKey({ key: { kty: "OKP", crv, x: raw }, format: "jwk" });
442
+ }
443
+ function importPrivate(stored) {
444
+ return createPrivateKey({
445
+ key: Buffer.from(stored, "base64"),
446
+ type: "pkcs8",
447
+ format: "der"
448
+ });
449
+ }
450
+ var exportPrivate = (key) => key.export({ type: "pkcs8", format: "der" }).toString("base64");
451
+ function generateKeys(now) {
452
+ const identity = generateKeyPairSync("ed25519");
453
+ const encryption = generateKeyPairSync("x25519");
454
+ const encryptionPublic = rawPublic(encryption.publicKey);
455
+ return {
456
+ version: 1,
457
+ identityPublic: rawPublic(identity.publicKey),
458
+ identityPrivate: exportPrivate(identity.privateKey),
459
+ encryptionPublic,
460
+ encryptionPrivate: exportPrivate(encryption.privateKey),
461
+ encryptionSig: sign(
462
+ null,
463
+ Buffer.from(`${ENCRYPTION_KEY_CONTEXT}:${encryptionPublic}`),
464
+ identity.privateKey
465
+ ).toString("base64url"),
466
+ createdAt: now
467
+ };
468
+ }
469
+ function publicIdentityOf(keys) {
470
+ return {
471
+ identity: keys.identityPublic,
472
+ encryption: keys.encryptionPublic,
473
+ encryptionSig: keys.encryptionSig
474
+ };
475
+ }
476
+ function verifyPublicIdentity(identity) {
477
+ try {
478
+ return verify(
479
+ null,
480
+ Buffer.from(`${ENCRYPTION_KEY_CONTEXT}:${identity.encryption}`),
481
+ importPublic(identity.identity, "Ed25519"),
482
+ Buffer.from(identity.encryptionSig, "base64url")
483
+ );
484
+ } catch {
485
+ return false;
486
+ }
487
+ }
488
+ function signWith(keys, data) {
489
+ return sign(null, data, importPrivate(keys.identityPrivate)).toString(
490
+ "base64url"
491
+ );
492
+ }
493
+ function verifyWith(identityPublic, data, signature) {
494
+ try {
495
+ return verify(
496
+ null,
497
+ data,
498
+ importPublic(identityPublic, "Ed25519"),
499
+ Buffer.from(signature, "base64url")
500
+ );
501
+ } catch {
502
+ return false;
503
+ }
504
+ }
505
+ var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
506
+ function fingerprint(identityPublic) {
507
+ const digest = createHash("sha256").update(Buffer.from(identityPublic, "base64url")).digest();
508
+ let bits = 0;
509
+ let value = 0;
510
+ let out = "";
511
+ for (const byte of digest.subarray(0, 15)) {
512
+ value = value << 8 | byte;
513
+ bits += 8;
514
+ while (bits >= 5) {
515
+ out += ALPHABET.charAt(value >>> bits - 5 & 31);
516
+ bits -= 5;
517
+ }
518
+ }
519
+ const groups = out.match(/.{1,4}/g) ?? [];
520
+ return `BYOLLM-${groups.join("-")}`;
521
+ }
522
+ var keyId = (identityPublic) => fingerprint(identityPublic);
523
+
524
+ // src/grant.ts
525
+ var GRANT_MAX_AGE_MS = 12e4;
526
+ var CLOCK_SKEW_WARN_MS = 3e4;
527
+ var CLOCK_ATTRIBUTION_MS = 5e3;
528
+ var GRANT_CONTEXT = "byollm/v1/grant";
529
+ var SignedGrant = z5.object({
530
+ /**
531
+ * This grant's own id — what makes it single-use.
532
+ *
533
+ * **Not the job id, and the difference is load-bearing.** Binding
534
+ * single-use to `jobId` would refuse a legitimate retry: a claim that
535
+ * times out is re-claimed, the control plane authors a second grant for
536
+ * the same job, and a device that recorded the job id as spent would
537
+ * reject its own recovery. A fresh id per authorship replays nothing and
538
+ * retries fine.
539
+ */
540
+ grantId: z5.string().min(1),
541
+ /**
542
+ * The job this grant admits, and only this one.
543
+ *
544
+ * A grant lifted from one job and presented for another is the obvious
545
+ * attack, and this field is why it fails.
546
+ */
547
+ jobId: z5.string().min(1),
548
+ /**
549
+ * The site the work came from, **as a key id** — byollm-review 2026-08-27.
550
+ *
551
+ * This was `siteId`, holding the site's id in the control plane's
552
+ * namespace, and it was signed by the engine and read by nobody. A signed
553
+ * field nobody checks is not a weak guarantee, it is the appearance of
554
+ * one: the design says "the grant carries the site", and nothing anywhere
555
+ * compared it to anything.
556
+ *
557
+ * It could not be compared. Job ids are chosen per site, so a grant
558
+ * authored for (site A, `job_1`) satisfied every device check against a
559
+ * stub naming (site B, `job_1`) — but the device holds sites only by the
560
+ * key ids it pinned, and had no way to relate a control-plane uuid to
561
+ * one. Checking the field would have meant a lookup through the party the
562
+ * grant exists to distrust.
563
+ *
564
+ * So the namespace changes to the one the device already has, and the
565
+ * name changes with it: this is the same value as {@link JobStub.site},
566
+ * compared directly, no lookup and nothing to believe. The control-plane
567
+ * id is not carried alongside — it had no reader, and keeping an
568
+ * unchecked field beside a checked one is how this hole was dug.
569
+ */
570
+ site: z5.string().min(1),
571
+ /** Whose job it is — the person the site enqueued for. */
572
+ user: z5.string().min(1),
573
+ /**
574
+ * Whose device it is for.
575
+ *
576
+ * Passed to {@link verifyGrant} rather than read out of the document, for
577
+ * the reason every verifier here takes its subject as an argument: a
578
+ * verifier that recovered the owner from the signed bytes would accept a
579
+ * genuine grant belonging to somebody else and pass every check.
580
+ */
581
+ owner: z5.string().min(1),
582
+ /** The site purpose this job serves — byollm_016 Amendment L. */
583
+ purpose: z5.string().min(1),
584
+ /** The kind of work. */
585
+ kind: z5.string().min(1),
586
+ /**
587
+ * The service the control plane resolved this (purpose, kind) to, from
588
+ * the user's own mapping.
589
+ *
590
+ * Selection is the control plane's; **offer-consistency is the
591
+ * device's**. A device verifies it actually offers this service, at a
592
+ * scope that includes {@link user}, before running anything.
593
+ */
594
+ service: z5.string().min(1),
595
+ /** When the control plane signed it — epoch ms, the only anchor for age. */
596
+ issuedAt: z5.number().int().positive(),
597
+ /** Base64url Ed25519 over {@link grantStatement}. */
598
+ signature: z5.string().min(1)
599
+ }).strict();
600
+ var GRANT_SIGNED_FIELDS = Object.freeze(
601
+ Object.keys(SignedGrant.shape).filter((key) => key !== "signature").sort()
602
+ );
603
+ function grantStatement(claims) {
604
+ return Buffer2.from(
605
+ JSON.stringify([
606
+ GRANT_CONTEXT,
607
+ ...GRANT_SIGNED_FIELDS.map((field) => claims[field])
608
+ ]),
609
+ "utf8"
610
+ );
611
+ }
612
+ function signGrant(keys, claims) {
613
+ return { ...claims, signature: signWith(keys, grantStatement(claims)) };
614
+ }
615
+ function verifyGrant(input) {
616
+ const { grant, now } = input;
617
+ if (grant.owner !== input.owner) return "wrong-owner";
618
+ if (grant.jobId !== input.jobId) return "wrong-job";
619
+ const age = now - grant.issuedAt;
620
+ if (age < -CLOCK_SKEW_WARN_MS) return "from-the-future";
621
+ if (age > (input.maxAgeMs ?? GRANT_MAX_AGE_MS)) return "expired";
622
+ return verifyWith(
623
+ input.controlPlanePublic,
624
+ grantStatement(grant),
625
+ grant.signature
626
+ ) ? null : "bad-signature";
627
+ }
628
+
629
+ // src/job.ts
630
+ var JobState = z6.enum([
389
631
  "queued",
390
632
  "claimed",
391
633
  "running",
@@ -417,7 +659,7 @@ var TRANSITIONS = Object.freeze({
417
659
  function canTransition(from, to) {
418
660
  return TRANSITIONS[from].includes(to);
419
661
  }
420
- var Lease = z4.object({
662
+ var Lease = z6.object({
421
663
  /**
422
664
  * Identifies *this* grant, not just its holder.
423
665
  *
@@ -432,20 +674,20 @@ var Lease = z4.object({
432
674
  * and release *is*, per lease, but not across leases, because nothing in
433
675
  * the request said which one.
434
676
  */
435
- id: z4.string().min(1),
677
+ id: z6.string().min(1),
436
678
  /** The runner holding the lease. */
437
- runnerId: z4.string().min(1),
679
+ runnerId: z6.string().min(1),
438
680
  /** Epoch milliseconds after which the claim is void. */
439
- expiresAt: z4.number().int().positive()
440
- });
441
- var JobPayload = z4.union([GeneratePayload, ChatPayload]);
442
- var ClaimedJob = z4.object({
443
- id: z4.string().min(1),
681
+ expiresAt: z6.number().int().positive()
682
+ }).strict();
683
+ var JobPayload = z6.union([GeneratePayload, ChatPayload]);
684
+ var ClaimedJob = z6.object({
685
+ id: z6.string().min(1),
444
686
  kind: JobKind,
445
687
  payload: JobPayload,
446
688
  audience: Audience,
447
689
  /** The app's id for the user who enqueued it. */
448
- owner: z4.string().min(1),
690
+ owner: z6.string().min(1),
449
691
  /**
450
692
  * Which site's job — V1-3.
451
693
  *
@@ -458,39 +700,42 @@ var ClaimedJob = z4.object({
458
700
  * one, and so this reads as what it is: a fact about where the work came
459
701
  * from, not a second copy of the routing key.
460
702
  */
461
- site: z4.string().min(1).optional(),
703
+ site: z6.string().min(1).optional(),
462
704
  /**
463
- * Which service the site named, if it named one byollm_016 Phase B.
705
+ * Which of the owner's services runs thisresolved, not requested.
706
+ *
707
+ * The daemon picks the backend from this, so it has to be the answer
708
+ * rather than a wish. On a relayed route it is copied off the **grant**,
709
+ * where a control plane put the person's own mapping and signed it; a
710
+ * site never named it and could not.
464
711
  *
465
- * The same shape `site` documents above, and added for the same reason:
466
- * the stub carries it, the opened job did not, and everything downstream
467
- * of the payload lost it. Here that loss was not cosmetic — the daemon
468
- * picks the backend from this, so a job that selected a non-default
469
- * service would have been served by the default instead, which is the
470
- * substitution `NO_PAYLOAD_ROUTING` forbids.
712
+ * It used to be what the site asked for, which made a job that selected a
713
+ * non-default service liable to be served by the default instead the
714
+ * substitution `NO_PAYLOAD_ROUTING` forbids. Amendment L removed the
715
+ * asking; what is left is the answering.
471
716
  *
472
- * Optional, because absent means "the owner's default" and that is every
473
- * job written before selection existed.
717
+ * Optional, because direct mode has no control plane to resolve anything
718
+ * and the owner's own defaults answer under the ambiguity law.
474
719
  */
475
- service: z4.string().min(1).optional(),
720
+ service: z6.string().min(1).optional(),
476
721
  lease: Lease
477
722
  }).strict();
478
- var ResultProvenance = z4.object({
723
+ var ResultProvenance = z6.object({
479
724
  /** The audience the job ran under. */
480
725
  audience: Audience,
481
726
  /** The runner that produced it. */
482
- runnerId: z4.string().min(1),
727
+ runnerId: z6.string().min(1),
483
728
  /** The runner owner's id in this app's namespace. */
484
- runnerOwner: z4.string().min(1),
729
+ runnerOwner: z6.string().min(1),
485
730
  /** Which backend class produced it — an HTTP call or a sandboxed spawn. */
486
731
  backendClass: BackendClass,
487
732
  /** The model the runner reports having used. */
488
- model: z4.string().min(1),
733
+ model: z6.string().min(1),
489
734
  /**
490
735
  * False only for `self` jobs. When true the app MUST treat `text` as
491
736
  * untrusted third-party content.
492
737
  */
493
- untrusted: z4.boolean()
738
+ untrusted: z6.boolean()
494
739
  }).strict();
495
740
  function provenanceFor(input) {
496
741
  return {
@@ -502,62 +747,47 @@ function provenanceFor(input) {
502
747
  untrusted: input.audience !== "private"
503
748
  };
504
749
  }
505
- var RunMetadata = z4.object({
750
+ var RunMetadata = z6.object({
506
751
  /** Which model actually served it. */
507
- model: z4.string().min(1),
752
+ model: z6.string().min(1),
508
753
  backendClass: BackendClass,
509
754
  /** Wall-clock milliseconds the backend call took. */
510
- durationMs: z4.number().int().nonnegative()
755
+ durationMs: z6.number().int().nonnegative()
511
756
  }).strict();
512
- var JobResultOk = z4.object({
513
- outcome: z4.literal("ok"),
514
- text: z4.string(),
757
+ var JobResultOk = z6.object({
758
+ outcome: z6.literal("ok"),
759
+ text: z6.string(),
515
760
  /** Optional reference to a stored artifact; never a local path. */
516
- artifactUrl: z4.url().optional()
761
+ artifactUrl: z6.url().optional()
517
762
  }).strict();
518
- var JobResultError = z4.object({
519
- outcome: z4.literal("error"),
520
- code: z4.string().min(1),
521
- message: z4.string().min(1),
763
+ var JobResultError = z6.object({
764
+ outcome: z6.literal("error"),
765
+ code: z6.string().min(1),
766
+ message: z6.string().min(1),
522
767
  /** Whether the app may reasonably re-enqueue. */
523
- retryable: z4.boolean()
768
+ retryable: z6.boolean()
524
769
  }).strict();
525
- var JobResultCanceled = z4.object({
526
- outcome: z4.literal("canceled")
770
+ var JobResultCanceled = z6.object({
771
+ outcome: z6.literal("canceled")
527
772
  }).strict();
528
- var JobOutcome = z4.discriminatedUnion("outcome", [
773
+ var JobOutcome = z6.discriminatedUnion("outcome", [
529
774
  JobResultOk,
530
775
  JobResultError,
531
776
  JobResultCanceled
532
777
  ]);
533
- var RefusalReason = z4.enum([
534
- /**
535
- * A selection this requester cannot be served — byollm_016 Phase B.
536
- *
537
- * **One value for two causes, and the collapse is the security property.**
538
- * A named service may be unknown to this owner, or known and not offered to
539
- * this requester. Those are different facts and the requester may learn
540
- * neither, because telling them apart turns refusal wording into an
541
- * inventory oracle: probe names, sort the answers, and enumerate a device
542
- * you were never offered. The finer cause lives owner-side, where the person
543
- * reading it already owns the machine — see {@link SelectionFailure}.
544
- *
545
- * The first draft of this enum had both causes on the wire with a comment
546
- * claiming they disclosed identically. They did not; the comment described a
547
- * property the code lacked, which is the more dangerous half of that mistake.
548
- */
549
- "select-unavailable",
778
+ var RefusalReason = z6.enum([
550
779
  /**
551
780
  * Two or more services answer this kind and the owner has named no default,
552
781
  * so the kind is withheld. Nobody may pick on the owner's behalf — the wrong
553
782
  * guess is the metered one.
554
783
  *
555
- * Not collapsed into the value above, and the reason is that a kind is not
556
- * probeable. There are two kinds; a requester asking about one is not
557
- * enumerating a namespace, and learns nothing they could not learn by
558
- * looking at what the device advertises. It is also already what a roster
559
- * member sees on the devices page `awaitingDefault` carries exactly this,
560
- * by kind, for exactly this reason.
784
+ * Told apart from its neighbour deliberately, and the line is whether a
785
+ * requester can walk a namespace. There are two kinds; asking about one
786
+ * enumerates nothing they could not learn from what the device advertises,
787
+ * and the difference is actionable "the owner has not chosen" is fixable
788
+ * by the owner, "the default cannot serve you" is not. It is also already
789
+ * what a team member sees on the devices page: `awaitingDefault` carries
790
+ * exactly this, by kind, for exactly this reason.
561
791
  */
562
792
  "default-ambiguity",
563
793
  /**
@@ -566,34 +796,28 @@ var RefusalReason = z4.enum([
566
796
  *
567
797
  * The specimen: an owner's default for `llm.chat` is their Claude
568
798
  * subscription, self-locked by `SUBSCRIPTION_SELF_LOCK`. A team member's
569
- * unselected job resolves to it and can never be served by it. That must be
570
- * a refusal on the spot, not a wait that expires an hour later looking like
571
- * nobody was online.
799
+ * job resolves to it and can never be served by it. That must be a refusal
800
+ * on the spot, not a wait that expires an hour later looking like nobody
801
+ * was online.
572
802
  *
573
- * Bounded like the value above and probeable for the same reason it is not:
574
- * the requester named nothing, so there is no name space to walk.
803
+ * Bounded like the value above, and unprobeable for the same reason: the
804
+ * requester named nothing, so there is no name space to walk.
575
805
  */
576
806
  "default-unusable"
577
807
  ]);
578
- var JobRefused = z4.object({
579
- outcome: z4.literal("refused"),
808
+ var JobRefused = z6.object({
809
+ outcome: z6.literal("refused"),
580
810
  reason: RefusalReason,
581
811
  /** Plain words for a human reading a log, never parsed. */
582
- message: z4.string().min(1)
812
+ message: z6.string().min(1)
583
813
  }).strict();
584
814
  var REFUSAL_TEXT = Object.freeze({
585
- "select-unavailable": "that service is not available to you on this device",
586
815
  "default-ambiguity": "this device serves that kind from more than one service and its owner has not chosen which",
587
816
  "default-unusable": "this device's default for that kind cannot run work for you"
588
817
  });
589
- var REFUSED_SELECTION = Object.freeze({
590
- outcome: "refused",
591
- reason: "select-unavailable",
592
- message: REFUSAL_TEXT["select-unavailable"]
593
- });
594
- var SealedOutcome = z4.object({ outcome: JobOutcome, ran: RunMetadata }).strict();
595
- var DeliveredResult = z4.object({
596
- jobId: z4.string().min(1),
818
+ var SealedOutcome = z6.object({ outcome: JobOutcome, ran: RunMetadata }).strict();
819
+ var DeliveredResult = z6.object({
820
+ jobId: z6.string().min(1),
597
821
  state: JobState,
598
822
  outcome: JobOutcome.optional(),
599
823
  provenance: ResultProvenance.optional(),
@@ -611,9 +835,9 @@ var DeliveredResult = z4.object({
611
835
  * runner ran the job, and the *server* stamps it — an app cannot supply
612
836
  * a substitute that hides what it is.
613
837
  */
614
- fallback: z4.literal(true).optional()
838
+ fallback: z6.literal(true).optional()
615
839
  }).strict();
616
- var SizeClass = z4.enum(["small", "medium", "large", "unbounded"]);
840
+ var SizeClass = z6.enum(["small", "medium", "large", "unbounded"]);
617
841
  var SIZE_CLASS_LIMITS = Object.freeze({
618
842
  small: 4e3,
619
843
  medium: 64e3,
@@ -628,11 +852,11 @@ function sizeClassOf(textChars) {
628
852
  if (textChars <= SIZE_CLASS_LIMITS.medium) return "medium";
629
853
  return "large";
630
854
  }
631
- var JobStub = z4.object({
632
- id: z4.string().min(1),
855
+ var JobStub = z6.object({
856
+ id: z6.string().min(1),
633
857
  kind: JobKind,
634
858
  /** The app's id for the user who enqueued it. */
635
- owner: z4.string().min(1),
859
+ owner: z6.string().min(1),
636
860
  /**
637
861
  * Which site this job belongs to — byollm_009 Amendment A §A.3.
638
862
  *
@@ -656,13 +880,13 @@ var JobStub = z4.object({
656
880
  * overlap window, and a daemon re-keys its own map by verifying that
657
881
  * signature against the key it already pinned (§A.3.1).
658
882
  */
659
- site: z4.string().min(1),
883
+ site: z6.string().min(1),
660
884
  audience: Audience,
661
885
  // `audienceAllow` is **not** here, and its absence is the enforcement —
662
886
  // cloud_008 §0.2.
663
887
  //
664
888
  // It was a list of the people who may run a job, travelling to every
665
- // routing party on every `named` job. byollm_001 Rev 1 §B settled who
889
+ // routing party on every shared job. byollm_001 Rev 1 §B settled who
666
890
  // decides that long before this schema existed: *the daemon's own list
667
891
  // decides, not the server's*, and `allowlist.predicateFor(origin)` is the
668
892
  // enforcement in both lanes. So this was a second answer to a question the
@@ -679,181 +903,68 @@ var JobStub = z4.object({
679
903
  // with it before offering. That is server-internal, where the party
680
904
  // holding the list authored it.
681
905
  /**
682
- * Which of the owner's services should answerbyollm_016 Phase B.
683
- *
684
- * **A selection from a menu, never a demand.** The owner advertises named
685
- * services; a site may name one of them, and that is the entire power the
686
- * field grants. It carries no model, no base URL, no flags — the daemon
687
- * resolves the name against its own config and nothing else, so what
688
- * actually runs is still decided exclusively by the person who owns the
689
- * hardware. A name that is not on that owner's menu is refused
690
- * (`select-unadvertised`), never silently substituted, because a
691
- * substitution is how "select" would quietly become "whatever we had".
692
- *
693
- * Absent means "the owner's default for this kind", which is the only
694
- * behaviour Phase A had.
695
- *
696
- * It travels because the router matches on it, under the rule the absent
697
- * `audienceAllow` above establishes: *a class the router acts on may
698
- * travel; membership never does.* This is a class.
699
- *
700
- * It is a **stub** field and never a payload field, which is the line
906
+ * Which of the site's declared purposes this job serves Amendment L.
907
+ *
908
+ * **A need, never a name.** The site's vocabulary is its own purposes;
909
+ * the person's is their services; and the two never meet. This field says
910
+ * "writing-assistant", and a control plane joins it to whatever that
911
+ * person mapped it to. The site learns only whether the slot was
912
+ * satisfiable.
913
+ *
914
+ * It replaced `service`, which let a site name one of the owner's
915
+ * services directly. That field is gone from both routes (Amendment L
916
+ * rider) and its refusal machinery with it — including the collapsed
917
+ * `select-unavailable`, which existed so that "no such service" and "not
918
+ * offered to you" could not be told apart. There is nothing left to
919
+ * probe: **a vocabulary that never crosses the boundary cannot be
920
+ * enumerated across it**, which is a stronger guarantee than the one the
921
+ * collapse gave.
922
+ *
923
+ * It travels for the reason the absent `audienceAllow` establishes: *a
924
+ * class the router acts on may travel; membership never does.* A purpose
925
+ * is a class, and the control plane acts on it.
926
+ *
927
+ * Optional because direct mode has no control plane to hold a mapping and
928
+ * is kind-only: the owner's own config and defaults answer, under the
929
+ * ambiguity law as shipped. Absent on a relayed route resolves against
930
+ * the site's reserved purpose, which a site that declared its own
931
+ * purposes will not have mapped — so the slot reads as unmapped and the
932
+ * site falls back, loudly enough and without a special case.
933
+ *
934
+ * A **stub** field and never a payload field, which is the line
701
935
  * `NO_PAYLOAD_ROUTING` draws: the prompt cannot reach it, so no amount of
702
936
  * user text can influence what runs.
703
937
  */
704
- service: z4.string().min(1).optional(),
938
+ purpose: z6.string().min(1).optional(),
705
939
  sizeClass: SizeClass,
706
940
  /** Reserved for byollm_006. False until streaming exists. */
707
- streaming: z4.boolean(),
941
+ streaming: z6.boolean(),
708
942
  /** Epoch ms after which the work is pointless; bounds ciphertext retention. */
709
- deadlineAt: z4.number().int().positive()
943
+ deadlineAt: z6.number().int().positive()
944
+ }).strict();
945
+ var ClaimedStub = JobStub.extend({
946
+ lease: Lease,
947
+ grant: SignedGrant.optional()
710
948
  }).strict();
711
- var ClaimedStub = JobStub.extend({ lease: Lease }).strict();
712
949
 
713
950
  // src/envelope.ts
714
951
  import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2 } from "crypto";
715
952
  import sodium from "libsodium-wrappers";
716
- import { z as z6 } from "zod";
717
-
718
- // src/keys.ts
719
- import {
720
- createHash,
721
- createPrivateKey,
722
- createPublicKey,
723
- generateKeyPairSync,
724
- sign,
725
- verify
726
- } from "crypto";
727
- import { z as z5 } from "zod";
728
- var PublicIdentity = z5.object({
729
- /** Raw Ed25519 public key. The pinned one. */
730
- identity: z5.string().min(1),
731
- /** Raw X25519 public key, for sealing to this party. */
732
- encryption: z5.string().min(1),
733
- /**
734
- * Ed25519 signature over the encryption key, by the identity key.
735
- *
736
- * This is what stops an upstream substituting an encryption key of its
737
- * own while relaying a genuine identity: the receiver pins the identity
738
- * and refuses any encryption key not signed by it.
739
- */
740
- encryptionSig: z5.string().min(1)
741
- }).strict();
742
- var StoredKeys = z5.object({
743
- version: z5.literal(1),
744
- identityPublic: z5.string().min(1),
745
- identityPrivate: z5.string().min(1),
746
- encryptionPublic: z5.string().min(1),
747
- encryptionPrivate: z5.string().min(1),
748
- encryptionSig: z5.string().min(1),
749
- createdAt: z5.number().int().positive()
750
- }).strict();
751
- var ENCRYPTION_KEY_CONTEXT = "byollm/v1/encryption-key";
752
- function rawPublic(key) {
753
- const jwk = key.export({ format: "jwk" });
754
- const x = jwk.x;
755
- if (typeof x !== "string") throw new Error("key has no raw public component");
756
- return x;
757
- }
758
- function importPublic(raw, crv) {
759
- return createPublicKey({ key: { kty: "OKP", crv, x: raw }, format: "jwk" });
760
- }
761
- function importPrivate(stored) {
762
- return createPrivateKey({
763
- key: Buffer.from(stored, "base64"),
764
- type: "pkcs8",
765
- format: "der"
766
- });
767
- }
768
- var exportPrivate = (key) => key.export({ type: "pkcs8", format: "der" }).toString("base64");
769
- function generateKeys(now) {
770
- const identity = generateKeyPairSync("ed25519");
771
- const encryption = generateKeyPairSync("x25519");
772
- const encryptionPublic = rawPublic(encryption.publicKey);
773
- return {
774
- version: 1,
775
- identityPublic: rawPublic(identity.publicKey),
776
- identityPrivate: exportPrivate(identity.privateKey),
777
- encryptionPublic,
778
- encryptionPrivate: exportPrivate(encryption.privateKey),
779
- encryptionSig: sign(
780
- null,
781
- Buffer.from(`${ENCRYPTION_KEY_CONTEXT}:${encryptionPublic}`),
782
- identity.privateKey
783
- ).toString("base64url"),
784
- createdAt: now
785
- };
786
- }
787
- function publicIdentityOf(keys) {
788
- return {
789
- identity: keys.identityPublic,
790
- encryption: keys.encryptionPublic,
791
- encryptionSig: keys.encryptionSig
792
- };
793
- }
794
- function verifyPublicIdentity(identity) {
795
- try {
796
- return verify(
797
- null,
798
- Buffer.from(`${ENCRYPTION_KEY_CONTEXT}:${identity.encryption}`),
799
- importPublic(identity.identity, "Ed25519"),
800
- Buffer.from(identity.encryptionSig, "base64url")
801
- );
802
- } catch {
803
- return false;
804
- }
805
- }
806
- function signWith(keys, data) {
807
- return sign(null, data, importPrivate(keys.identityPrivate)).toString(
808
- "base64url"
809
- );
810
- }
811
- function verifyWith(identityPublic, data, signature) {
812
- try {
813
- return verify(
814
- null,
815
- data,
816
- importPublic(identityPublic, "Ed25519"),
817
- Buffer.from(signature, "base64url")
818
- );
819
- } catch {
820
- return false;
821
- }
822
- }
823
- var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
824
- function fingerprint(identityPublic) {
825
- const digest = createHash("sha256").update(Buffer.from(identityPublic, "base64url")).digest();
826
- let bits = 0;
827
- let value = 0;
828
- let out = "";
829
- for (const byte of digest.subarray(0, 15)) {
830
- value = value << 8 | byte;
831
- bits += 8;
832
- while (bits >= 5) {
833
- out += ALPHABET.charAt(value >>> bits - 5 & 31);
834
- bits -= 5;
835
- }
836
- }
837
- const groups = out.match(/.{1,4}/g) ?? [];
838
- return `BYOLLM-${groups.join("-")}`;
839
- }
840
- var keyId = (identityPublic) => fingerprint(identityPublic);
841
-
842
- // src/envelope.ts
953
+ import { z as z7 } from "zod";
843
954
  var readied;
844
955
  async function cryptoReady() {
845
956
  readied ??= sodium.ready;
846
957
  await readied;
847
958
  }
848
959
  var ENVELOPE_MAX_AGE_MS = 24 * 60 * 6e4;
849
- var EnvelopeDirection = z6.enum(["payload", "result"]);
850
- var SealedEnvelope = z6.object({
960
+ var EnvelopeDirection = z7.enum(["payload", "result"]);
961
+ var SealedEnvelope = z7.object({
851
962
  /** Base64url `crypto_box_seal` output over the signed plaintext. */
852
- ciphertext: z6.string().min(1),
963
+ ciphertext: z7.string().min(1),
853
964
  /** Who this was sealed to — the recipient checks it is them. */
854
- recipientKeyId: z6.string().min(1),
965
+ recipientKeyId: z7.string().min(1),
855
966
  /** Who signed it — the recipient checks this against its pin. */
856
- senderKeyId: z6.string().min(1),
967
+ senderKeyId: z7.string().min(1),
857
968
  direction: EnvelopeDirection,
858
969
  /**
859
970
  * When this ciphertext stops being worth keeping.
@@ -867,7 +978,7 @@ var SealedEnvelope = z6.object({
867
978
  * Not trusted as written: it is also inside the signature, so a changed
868
979
  * deadline fails to verify.
869
980
  */
870
- deadlineAt: z6.number().int().positive()
981
+ deadlineAt: z7.number().int().positive()
871
982
  }).strict();
872
983
  function signedBody(context, plaintext) {
873
984
  return Buffer.from(
@@ -962,15 +1073,15 @@ async function open(input) {
962
1073
 
963
1074
  // src/signing.ts
964
1075
  import { createHash as createHash2 } from "crypto";
965
- import { z as z7 } from "zod";
1076
+ import { z as z8 } from "zod";
966
1077
  var MAX_CLOCK_SKEW_MS = 12e4;
967
- var RequestSignature = z7.object({
1078
+ var RequestSignature = z8.object({
968
1079
  /** Which runner is calling. The server looks up its pinned identity. */
969
- runnerId: z7.string().min(1),
1080
+ runnerId: z8.string().min(1),
970
1081
  /** Epoch ms, bounded by {@link MAX_CLOCK_SKEW_MS}. */
971
- issuedAt: z7.number().int().positive(),
1082
+ issuedAt: z8.number().int().positive(),
972
1083
  /** Base64url Ed25519 signature over {@link canonicalRequest}. */
973
- signature: z7.string().min(1)
1084
+ signature: z8.string().min(1)
974
1085
  }).strict();
975
1086
  function canonicalRequest(input) {
976
1087
  const digest = createHash2("sha256").update(input.body, "utf8").digest("hex");
@@ -1023,73 +1134,62 @@ function verifyRequest(input) {
1023
1134
  return ok ? null : "bad-signature";
1024
1135
  }
1025
1136
 
1026
- // src/roster.ts
1027
- import { Buffer as Buffer2 } from "buffer";
1028
- import { z as z8 } from "zod";
1029
- var ROSTER_MAX_AGE_MS = 60 * 6e4;
1030
- var ROSTER_CONTEXT = "byollm/v1/roster";
1031
- var SignedRoster = z8.object({
1032
- /** Whose devices this roster governs. */
1033
- owner: z8.string().min(1),
1137
+ // src/manifest.ts
1138
+ import { z as z9 } from "zod";
1139
+ var RESERVED_PURPOSE = "default";
1140
+ var RENDERABLE = /^[^\p{Cc}\p{Cf}\p{Cs}\p{Co}]+$/u;
1141
+ var renderable = (max, what) => z9.string().min(1).max(max).regex(
1142
+ RENDERABLE,
1143
+ `a ${what} is text a person reads \u2014 no control characters, direction overrides or zero-width padding`
1144
+ ).refine((value) => value.trim() !== "", {
1145
+ message: `a ${what} cannot be blank`
1146
+ });
1147
+ var PurposeKey = z9.string().regex(
1148
+ /^[a-z0-9][a-z0-9-]*$/,
1149
+ "a purpose key is a lowercase slug \u2014 letters, digits and hyphens"
1150
+ ).max(64);
1151
+ var Purpose = z9.object({
1034
1152
  /**
1035
- * Who may have their `team` work run on this owner's devices.
1153
+ * What a person reads on the consent screen. The only rendered field.
1036
1154
  *
1037
- * Owner ids, sorted, so the same membership always produces the same
1038
- * bytes a document that differed by iteration order would produce a new
1039
- * signature every read and no way to tell a change from a shuffle.
1155
+ * Declared rather than derived from the key, because a key is a
1156
+ * compromise between machines and this is not. "Writing Assistant" is
1157
+ * what somebody understands; `writing-assistant` is what travels.
1040
1158
  */
1041
- members: z8.array(z8.string().min(1)),
1159
+ label: renderable(80, "label"),
1160
+ /** One line of context for the consent screen. Optional. */
1161
+ description: renderable(280, "description").optional(),
1042
1162
  /**
1043
- * When the control plane signed it — epoch ms, and the **only** honest
1044
- * anchor for age.
1163
+ * The kinds this purpose uses.
1045
1164
  *
1046
- * Not when the daemon received it. A relay that simply withholds updates
1047
- * would otherwise keep a removed member served forever, which is the
1048
- * attack {@link ROSTER_MAX_AGE_MS} exists to bound.
1165
+ * A purpose may span kinds, and a mapping is per (purpose, kind) — so a
1166
+ * person can send this purpose's chat to one service and its generation
1167
+ * to another. Listing a kind here is what makes that slot appear.
1049
1168
  */
1050
- issuedAt: z8.number().int().positive(),
1051
- /** Base64url Ed25519 over {@link rosterStatement}. */
1052
- signature: z8.string().min(1)
1169
+ kinds: z9.array(JobKind).min(1).max(JOB_KINDS.length).refine((kinds) => new Set(kinds).size === kinds.length, {
1170
+ message: "a purpose lists each kind once"
1171
+ })
1053
1172
  }).strict();
1054
- function rosterStatement(input) {
1055
- return Buffer2.from(
1056
- [
1057
- ROSTER_CONTEXT,
1058
- input.owner,
1059
- String(input.issuedAt),
1060
- [...input.members].sort().join("\0")
1061
- ].join("\n"),
1062
- "utf8"
1063
- );
1064
- }
1065
- function signRoster(keys, input) {
1066
- const members = [...input.members].sort();
1173
+ var MAX_PURPOSES = 32;
1174
+ var Manifest = z9.record(PurposeKey, Purpose).refine((manifest) => Object.keys(manifest).length > 0, {
1175
+ message: "a manifest declares at least one purpose"
1176
+ }).refine((manifest) => Object.keys(manifest).length <= MAX_PURPOSES, {
1177
+ message: `a manifest declares at most ${String(MAX_PURPOSES)} purposes \u2014 a consent screen is a set of questions somebody answers one at a time`
1178
+ }).refine((manifest) => !(RESERVED_PURPOSE in manifest), {
1179
+ message: `"${RESERVED_PURPOSE}" is reserved for a site that declares no purposes of its own \u2014 give this one a name from your own vocabulary`
1180
+ });
1181
+ function singlePurposeManifest(input) {
1067
1182
  return {
1068
- owner: input.owner,
1069
- members,
1070
- issuedAt: input.issuedAt,
1071
- signature: signWith(keys, rosterStatement({ ...input, members }))
1183
+ [RESERVED_PURPOSE]: { label: input.label, kinds: [...input.kinds] }
1072
1184
  };
1073
1185
  }
1074
- function verifyRoster(input) {
1075
- const { roster, owner, now } = input;
1076
- if (roster.owner !== owner) return "wrong-owner";
1077
- const age = now - roster.issuedAt;
1078
- if (age < 0) return "from-the-future";
1079
- if (age > (input.maxAgeMs ?? ROSTER_MAX_AGE_MS)) return "stale";
1080
- return verifyWith(
1081
- input.controlPlanePublic,
1082
- rosterStatement(roster),
1083
- roster.signature
1084
- ) ? null : "bad-signature";
1085
- }
1086
1186
 
1087
1187
  // src/succession.ts
1088
- import { z as z9 } from "zod";
1188
+ import { z as z10 } from "zod";
1089
1189
  var SUCCESSION_CONTEXT = "byollm/v1/site-succession";
1090
1190
  var RETIREMENT_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
1091
1191
  var MAX_SUCCESSION_CHAIN = 64;
1092
- var Succession = z9.object({
1192
+ var Succession = z10.object({
1093
1193
  /**
1094
1194
  * The predecessor's public identity — K1, in full.
1095
1195
  *
@@ -1099,7 +1199,7 @@ var Succession = z9.object({
1099
1199
  */
1100
1200
  identity: PublicIdentity,
1101
1201
  /** K1's signature over the statement naming K1 and its successor. */
1102
- signature: z9.string().min(1)
1202
+ signature: z10.string().min(1)
1103
1203
  }).strict();
1104
1204
  function successionStatement(fromKeyId, toKeyId) {
1105
1205
  return Buffer.from(`${SUCCESSION_CONTEXT}:${fromKeyId}:${toKeyId}`);
@@ -1193,13 +1293,15 @@ var MUSTS = Object.freeze({
1193
1293
  }),
1194
1294
  SITES_LOCALLY_APPROVED: must({
1195
1295
  id: "SITES_LOCALLY_APPROVED",
1196
- statement: "A daemon MUST NOT run work for a site it has not approved on the machine itself. An upstream may propose a site set; a site the daemon has never approved MUST be offered to its owner and served nothing until they approve it. A key that has changed for an already-approved id MUST be refused for the life of the pairing, including after that id has left the set and returned. A **verified succession** is not a changed key: a new key id carrying a signature, by a key this daemon has already approved, over a statement naming both key ids MUST be accepted without a new local approval \u2014 provided the control plane projects the same successor \u2014 and MUST be announced rather than applied silently.",
1296
+ statement: "A daemon MUST NOT run work for a site on an upstream's word alone. An upstream may propose a site set; work for any site in it MUST additionally carry a grant signed by the control-plane key this daemon pinned at pairing. A key that has changed for an id this daemon already pinned MUST be refused for the life of the pairing, including after that id has left the set and returned. A **verified succession** is not a changed key: a new key id carrying a signature, by a key this daemon has already pinned, over a statement naming both key ids MUST be accepted \u2014 provided the control plane projects the same successor \u2014 and MUST be announced rather than applied silently. The first job from a site this daemon has never served MUST be announced at the machine.",
1197
1297
  enforcedBy: "daemon",
1198
1298
  // Two kinds, and the second is the one that matters — V1-1.
1199
1299
  //
1200
1300
  // `construction`: the daemon cannot serve a site that is not in its
1201
- // pinned map, and admission refuses before a payload is fetched, so the
1202
- // ordinary path cannot reach a site nobody approved.
1301
+ // pinned map, and admission refuses before a payload is fetched and
1302
+ // since byollm_016 Amendment K, being in the map is no longer sufficient
1303
+ // either: a signed grant is, and the relay proposing the set cannot
1304
+ // produce one.
1203
1305
  //
1204
1306
  // `adversarial`: the property that survives is about a *sequence* —
1205
1307
  // remove the id, re-offer it under a different key — which no honest
@@ -1352,7 +1454,7 @@ var MUSTS = Object.freeze({
1352
1454
  }),
1353
1455
  NAMED_LOCAL_ALLOWLIST: must({
1354
1456
  id: "NAMED_LOCAL_ALLOWLIST",
1355
- 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.",
1457
+ statement: "A 'team' job MUST be admitted only by something the device itself verified, keyed by (server origin, user id) \u2014 never on the routing party's assertion alone.",
1356
1458
  enforcedBy: "daemon",
1357
1459
  verifiedBy: "conformance",
1358
1460
  source: "byollm_001 Rev 1 \xA7B"
@@ -1578,7 +1680,7 @@ function mustsVerifiedBy(kind) {
1578
1680
  }
1579
1681
 
1580
1682
  // src/wire.ts
1581
- import { z as z10 } from "zod";
1683
+ import { z as z11 } from "zod";
1582
1684
  var PROTOCOL_VERSION = "0";
1583
1685
  var SUPPORTED_PROTOCOL_VERSIONS = Object.freeze([
1584
1686
  PROTOCOL_VERSION
@@ -1621,47 +1723,45 @@ var ENDPOINTS = Object.freeze([
1621
1723
  "result",
1622
1724
  "release"
1623
1725
  ]);
1624
- var Capability = z10.object({
1726
+ var Capability = z11.object({
1625
1727
  kind: JobKind,
1626
1728
  /**
1627
1729
  * The owner's name for the service answering this kind — byollm_016.
1628
1730
  *
1629
1731
  * A device advertises *which* of its services serves a kind, not merely
1630
- * that something does. Phase B lets a job select by this name; until then
1631
- * it is what a device page shows and what a default is chosen between.
1732
+ * that something does. **A site never sees this**, and never did after
1733
+ * Amendment L: it is what a control plane resolves a person's mapping
1734
+ * against, so that the service a grant names is one this device actually
1735
+ * offers rather than one somebody invented.
1736
+ *
1737
+ * `isDefault` used to sit beside it, saying which row an unselected job
1738
+ * took. Nothing selects any more — a job names a purpose and a person's
1739
+ * mapping names a service — so there is no unselected job for a default
1740
+ * to catch, and the field went with the machinery it served.
1632
1741
  */
1633
- service: z10.string().min(1),
1634
- /**
1635
- * Whether this row is the default for its kind.
1636
- *
1637
- * Stated rather than inferred from being the only row, which is true in
1638
- * Phase A and stops being true the moment Phase B advertises every
1639
- * selectable service per kind. A consumer that learned "default means
1640
- * alone" would have to unlearn it, and the ones that did not would be
1641
- * quietly wrong. One field now, no second shape later.
1642
- */
1643
- isDefault: z10.boolean(),
1742
+ service: z11.string().min(1),
1644
1743
  backendId: BackendIdSchema,
1645
1744
  backendClass: BackendClass,
1646
- model: z10.string().min(1),
1745
+ model: z11.string().min(1),
1647
1746
  offerScope: OfferScope
1648
1747
  }).strict();
1649
- var CapabilityMatrix = z10.array(Capability);
1650
- var WithheldKind = z10.object({
1748
+ var CapabilityMatrix = z11.array(Capability);
1749
+ var WithheldKind = z11.object({
1651
1750
  kind: JobKind,
1652
- claimants: z10.array(
1653
- z10.object({ service: z10.string().min(1), offer: OfferScope }).strict()
1751
+ claimants: z11.array(
1752
+ z11.object({ service: z11.string().min(1), offer: OfferScope }).strict()
1654
1753
  ).min(2)
1655
1754
  }).strict();
1656
- var PairStartRequest = z10.object({
1657
- protocolVersion: z10.literal(PROTOCOL_VERSION),
1658
- action: z10.literal("start"),
1659
- daemon: z10.object({
1660
- version: z10.string().min(1),
1755
+ var GrantRef = z11.object({ jobId: z11.string().min(1), leaseId: z11.string().min(1) }).strict();
1756
+ var PairStartRequest = z11.object({
1757
+ protocolVersion: z11.literal(PROTOCOL_VERSION),
1758
+ action: z11.literal("start"),
1759
+ daemon: z11.object({
1760
+ version: z11.string().min(1),
1661
1761
  /** Shown in the app's runner list so a user can tell their machines apart. */
1662
- label: z10.string().min(1).max(120),
1663
- platform: z10.enum(["darwin", "linux", "win32"])
1664
- }),
1762
+ label: z11.string().min(1).max(120),
1763
+ platform: z11.enum(["darwin", "linux", "win32"])
1764
+ }).strict(),
1665
1765
  /**
1666
1766
  * This machine's public keys (byollm_009 §5).
1667
1767
  *
@@ -1672,29 +1772,29 @@ var PairStartRequest = z10.object({
1672
1772
  device: PublicIdentity,
1673
1773
  capabilities: CapabilityMatrix
1674
1774
  }).strict();
1675
- var PairStartResponse = z10.object({
1775
+ var PairStartResponse = z11.object({
1676
1776
  /** Secret the daemon polls with. Never shown to the user. */
1677
- deviceCode: z10.string().min(20),
1777
+ deviceCode: z11.string().min(20),
1678
1778
  /** Short code the user reads and confirms in the browser. */
1679
- userCode: z10.string().min(4).max(16),
1779
+ userCode: z11.string().min(4).max(16),
1680
1780
  /** Where the user approves. Must be on the server's own origin. */
1681
- verificationUrl: z10.url(),
1781
+ verificationUrl: z11.url(),
1682
1782
  /** Epoch ms after which the code is dead ({@link MUSTS.PAIR_CODE_EXPIRES}). */
1683
- expiresAt: z10.number().int().positive(),
1783
+ expiresAt: z11.number().int().positive(),
1684
1784
  /** How often the daemon may poll. */
1685
- pollIntervalMs: z10.number().int().min(500).max(6e4)
1785
+ pollIntervalMs: z11.number().int().min(500).max(6e4)
1686
1786
  }).strict();
1687
- var PairPollRequest = z10.object({
1688
- protocolVersion: z10.literal(PROTOCOL_VERSION),
1689
- action: z10.literal("poll"),
1690
- deviceCode: z10.string().min(20)
1787
+ var PairPollRequest = z11.object({
1788
+ protocolVersion: z11.literal(PROTOCOL_VERSION),
1789
+ action: z11.literal("poll"),
1790
+ deviceCode: z11.string().min(20)
1691
1791
  }).strict();
1692
- var PairPollResponse = z10.discriminatedUnion("status", [
1693
- z10.object({ status: z10.literal("pending") }).strict(),
1694
- z10.object({ status: z10.literal("denied") }).strict(),
1695
- z10.object({ status: z10.literal("expired") }).strict(),
1696
- z10.object({
1697
- status: z10.literal("approved"),
1792
+ var PairPollResponse = z11.discriminatedUnion("status", [
1793
+ z11.object({ status: z11.literal("pending") }).strict(),
1794
+ z11.object({ status: z11.literal("denied") }).strict(),
1795
+ z11.object({ status: z11.literal("expired") }).strict(),
1796
+ z11.object({
1797
+ status: z11.literal("approved"),
1698
1798
  // `runnerToken` is gone — cloud_008 §2.4, finding 37.
1699
1799
  //
1700
1800
  // It was minted here, hashed into `RunnerRecord.tokenHash`, written to
@@ -1711,11 +1811,11 @@ var PairPollResponse = z10.discriminatedUnion("status", [
1711
1811
  // `REQUESTS_SIGNED_NOT_BEARER` was already the rule and was already
1712
1812
  // enforced — every authenticated call is signed by the device's pinned
1713
1813
  // identity key. This removes the thing the MUST is named after.
1714
- runnerId: z10.string().min(1),
1814
+ runnerId: z11.string().min(1),
1715
1815
  /** The app's id for the approving user — this daemon's owner forever. */
1716
- owner: z10.string().min(1),
1816
+ owner: z11.string().min(1),
1717
1817
  /** Display name for the trust UI, if the app offers one. */
1718
- ownerLabel: z10.string().optional(),
1818
+ ownerLabel: z11.string().optional(),
1719
1819
  /**
1720
1820
  * The sites this pairing covers, for the daemon to pin (byollm_009 §5),
1721
1821
  * keyed by each site's identity key id — cloud_009 §5.
@@ -1734,55 +1834,55 @@ var PairPollResponse = z10.discriminatedUnion("status", [
1734
1834
  * runner's lookup is a map read rather than a join across two
1735
1835
  * namespaces.
1736
1836
  */
1737
- sites: z10.record(z10.string().min(1), PublicIdentity),
1837
+ sites: z11.record(z11.string().min(1), PublicIdentity),
1738
1838
  /**
1739
- * The control plane's roster-signing key, pinned here — Amendment G.
1839
+ * The control plane's grant-signing key, pinned here — Amendment J.
1740
1840
  *
1741
1841
  * **Pairing is when, and that is the whole question.** Pairing is
1742
1842
  * already the ceremony where an owner proves out of band that this
1743
1843
  * device is theirs, so a key learned here rides trust that has already
1744
- * happened. The rejected alternative is trust-on-first-roster, and it
1745
- * is rejected because it hands the decision back to the relay: a daemon
1746
- * that learns whose signature to trust from the first roster to arrive
1747
- * has its membership authority chosen by whoever controls delivery.
1844
+ * happened. The rejected alternative is trust-on-first-grant, and it is
1845
+ * rejected because it hands the decision back to the relay: a daemon
1846
+ * that learns whose signature to trust from the first grant to arrive
1847
+ * has its admission authority chosen by whoever controls delivery.
1748
1848
  *
1749
1849
  * Optional on the wire, and only on the wire: a direct-mode server has
1750
- * no control plane and signs no rosters, and a daemon that never
1751
- * receives one simply serves no `team` job through it. It is not
1752
- * optional for a hub a hub that omitted it would be asking devices to
1753
- * accept rosters from nobody in particular.
1850
+ * no control plane and signs nothing, and a daemon that receives no key
1851
+ * serves its owner alone. It is not optional for a relay with a control
1852
+ * planeone that omitted it would be asking devices to accept grants
1853
+ * from nobody in particular, and would find every job refused.
1754
1854
  *
1755
- * Rotation is Amendment C's, with no path where a roster teaches a
1855
+ * Rotation is Amendment C's, with no path where a grant teaches a
1756
1856
  * daemon a new key.
1757
1857
  */
1758
- controlPlanePublic: z10.string().min(1).optional()
1858
+ controlPlanePublic: z11.string().min(1).optional()
1759
1859
  }).strict()
1760
1860
  ]);
1761
- var PairRequest = z10.discriminatedUnion("action", [
1861
+ var PairRequest = z11.discriminatedUnion("action", [
1762
1862
  PairStartRequest,
1763
1863
  PairPollRequest
1764
1864
  ]);
1765
- var ClaimRequest = z10.object({
1766
- protocolVersion: z10.literal(PROTOCOL_VERSION),
1767
- runnerId: z10.string().min(1),
1865
+ var ClaimRequest = z11.object({
1866
+ protocolVersion: z11.literal(PROTOCOL_VERSION),
1867
+ runnerId: z11.string().min(1),
1768
1868
  /** Re-sent on every claim so a server never matches against a stale matrix. */
1769
1869
  capabilities: CapabilityMatrix,
1770
1870
  /** Upper bound on jobs to return; the server may return fewer. */
1771
- max: z10.number().int().min(1).max(64)
1871
+ max: z11.number().int().min(1).max(64)
1772
1872
  }).strict();
1773
- var ClaimResponse = z10.object({
1873
+ var ClaimResponse = z11.object({
1774
1874
  /**
1775
1875
  * Stubs, not jobs. The payload arrives from `fetch`, sealed to whichever
1776
1876
  * device claimed — see {@link JobStub} for the exhaustive metadata list.
1777
1877
  */
1778
- jobs: z10.array(ClaimedStub),
1878
+ jobs: z11.array(ClaimedStub),
1779
1879
  /** Lease duration granted, so the daemon knows its renewal deadline. */
1780
- leaseMs: z10.number().int().positive()
1880
+ leaseMs: z11.number().int().positive()
1781
1881
  }).strict();
1782
- var HeartbeatRequest = z10.object({
1783
- protocolVersion: z10.literal(PROTOCOL_VERSION),
1784
- runnerId: z10.string().min(1),
1785
- daemonVersion: z10.string().min(1),
1882
+ var HeartbeatRequest = z11.object({
1883
+ protocolVersion: z11.literal(PROTOCOL_VERSION),
1884
+ runnerId: z11.string().min(1),
1885
+ daemonVersion: z11.string().min(1),
1786
1886
  capabilities: CapabilityMatrix,
1787
1887
  /**
1788
1888
  * Kinds this device is withholding, and why it can be said.
@@ -1791,20 +1891,18 @@ var HeartbeatRequest = z10.object({
1791
1891
  * older daemon against a newer hub is simply a device with no withheld
1792
1892
  * kinds rather than a parse failure.
1793
1893
  */
1794
- withheld: z10.array(WithheldKind).default([]),
1894
+ withheld: z11.array(WithheldKind).default([]),
1795
1895
  /**
1796
1896
  * Leases this daemon believes it holds; the server renews exactly these.
1797
1897
  *
1798
1898
  * Lease ids rather than job ids, so a replayed heartbeat cannot renew a
1799
1899
  * grant the runner no longer holds — see {@link Lease.id}.
1800
1900
  */
1801
- activeLeases: z10.array(
1802
- z10.object({ jobId: z10.string().min(1), leaseId: z10.string().min(1) })
1803
- ),
1901
+ activeLeases: z11.array(GrantRef),
1804
1902
  /** True while the owner has the daemon paused; the server stops offering work. */
1805
- paused: z10.boolean()
1903
+ paused: z11.boolean()
1806
1904
  }).strict();
1807
- var HeartbeatResponse = z10.object({
1905
+ var HeartbeatResponse = z11.object({
1808
1906
  /**
1809
1907
  * The sites this daemon may serve, right now — cloud_008 finding 59.
1810
1908
  *
@@ -1822,7 +1920,7 @@ var HeartbeatResponse = z10.object({
1822
1920
  * rather than being told a second time — two fields for one fact is how
1823
1921
  * they drift.
1824
1922
  */
1825
- sites: z10.record(z10.string().min(1), PublicIdentity),
1923
+ sites: z11.record(z11.string().min(1), PublicIdentity),
1826
1924
  /**
1827
1925
  * How a site's current key traces back to one this daemon already holds —
1828
1926
  * byollm_009 Amendment C.
@@ -1840,11 +1938,11 @@ var HeartbeatResponse = z10.object({
1840
1938
  * history is public by construction, because a daemon that cannot read it
1841
1939
  * cannot verify it.
1842
1940
  */
1843
- successions: z10.record(
1844
- z10.string().min(1),
1845
- z10.object({
1941
+ successions: z11.record(
1942
+ z11.string().min(1),
1943
+ z11.object({
1846
1944
  /** Oldest last, as the projection carries it. */
1847
- succeeds: z10.array(Succession).max(MAX_SUCCESSION_CHAIN),
1945
+ succeeds: z11.array(Succession).max(MAX_SUCCESSION_CHAIN),
1848
1946
  /**
1849
1947
  * Until when the superseded key may still sign work — epoch ms.
1850
1948
  *
@@ -1853,7 +1951,7 @@ var HeartbeatResponse = z10.object({
1853
1951
  * window indefinitely would be a two-key site forever, decided by
1854
1952
  * the party this design does not trust.
1855
1953
  */
1856
- retiringUntil: z10.number().int().positive().optional()
1954
+ retiringUntil: z11.number().int().positive().optional()
1857
1955
  }).strict()
1858
1956
  ).optional(),
1859
1957
  /**
@@ -1866,8 +1964,8 @@ var HeartbeatResponse = z10.object({
1866
1964
  * is the unique grant and the daemon already keys its work by it; this is
1867
1965
  * the same shape `activeLeases` sends in the other direction.
1868
1966
  */
1869
- cancel: z10.array(
1870
- z10.object({ jobId: z10.string().min(1), leaseId: z10.string().min(1) }).strict()
1967
+ cancel: z11.array(
1968
+ z11.object({ jobId: z11.string().min(1), leaseId: z11.string().min(1) }).strict()
1871
1969
  ),
1872
1970
  // `leases` is deliberately absent — cloud_008 §1.4b, finding 16.
1873
1971
  //
@@ -1897,11 +1995,11 @@ var HeartbeatResponse = z10.object({
1897
1995
  * ambiguous across sites, and "the lease you no longer hold" is exactly
1898
1996
  * what this field means anyway.
1899
1997
  */
1900
- lost: z10.array(
1901
- z10.object({ jobId: z10.string().min(1), leaseId: z10.string().min(1) }).strict()
1998
+ lost: z11.array(
1999
+ z11.object({ jobId: z11.string().min(1), leaseId: z11.string().min(1) }).strict()
1902
2000
  ),
1903
2001
  /** Server clock, so a daemon with a skewed clock still honors leases. */
1904
- serverTime: z10.number().int().positive(),
2002
+ serverTime: z11.number().int().positive(),
1905
2003
  /**
1906
2004
  * Sites whose disclosure the user must read again before work moves —
1907
2005
  * cloud_008 finding 48, named rather than counted.
@@ -1916,28 +2014,13 @@ var HeartbeatResponse = z10.object({
1916
2014
  * operator stopped it" — one word with two subjects on two halves of one
1917
2015
  * exchange is a confusion nobody untangles from a log.
1918
2016
  */
1919
- awaitingConsent: z10.array(z10.string().min(1)),
1920
- /**
1921
- * Who this owner's devices may serve `team` work for — Amendment G.
1922
- *
1923
- * Carried by the relay and authored by nobody it can reach. The daemon
1924
- * verifies it against the key pinned at pairing and admits from its own
1925
- * held copy, so this field is delivery and not instruction: withholding
1926
- * it narrows a device, and editing it is caught.
1927
- *
1928
- * Optional because a roster is a cloud-mode fact — direct mode has no
1929
- * control plane to author one — and because a daemon that has never
1930
- * received one must narrow rather than fail. Absent is not "admit
1931
- * nobody"; {@link ROSTER_MAX_AGE_MS} is what makes absence bite, and it
1932
- * bites the same way for a roster withheld as for one never sent.
1933
- */
1934
- roster: SignedRoster.optional()
2017
+ awaitingConsent: z11.array(z11.string().min(1))
1935
2018
  }).strict();
1936
- var ResultDisposition = z10.enum(["ok", "error", "canceled"]);
1937
- var ResultRequest = z10.object({
1938
- protocolVersion: z10.literal(PROTOCOL_VERSION),
1939
- runnerId: z10.string().min(1),
1940
- jobId: z10.string().min(1),
2019
+ var ResultDisposition = z11.enum(["ok", "error", "canceled"]);
2020
+ var ResultRequest = z11.object({
2021
+ protocolVersion: z11.literal(PROTOCOL_VERSION),
2022
+ runnerId: z11.string().min(1),
2023
+ jobId: z11.string().min(1),
1941
2024
  /**
1942
2025
  * The grant this result was produced under — cloud_008 §1.4a.
1943
2026
  *
@@ -1960,7 +2043,7 @@ var ResultRequest = z10.object({
1960
2043
  * learned once already, when a replayed release yanked a later grant, and
1961
2044
  * it applies here for the same reason.
1962
2045
  */
1963
- leaseId: z10.string().min(1),
2046
+ leaseId: z11.string().min(1),
1964
2047
  /**
1965
2048
  * The outcome, sealed to the site and signed by the device.
1966
2049
  *
@@ -1992,12 +2075,12 @@ var ResultRequest = z10.object({
1992
2075
  // on it, so it is a class a routing party consumes. Nobody between the
1993
2076
  // two ends consumes these.
1994
2077
  }).strict();
1995
- var ResultResponse = z10.object({
2078
+ var ResultResponse = z11.object({
1996
2079
  /**
1997
2080
  * False when this submission wrote nothing — the daemon should discard,
1998
2081
  * not retry ({@link MUSTS.RESULT_IDEMPOTENT}).
1999
2082
  */
2000
- accepted: z10.boolean(),
2083
+ accepted: z11.boolean(),
2001
2084
  /**
2002
2085
  * True when this device had already recorded this job's result.
2003
2086
  *
@@ -2011,13 +2094,13 @@ var ResultResponse = z10.object({
2011
2094
  * the same refusal it would get for a job that is *not* terminal, so a job
2012
2095
  * id cannot be used as a terminality probe.
2013
2096
  */
2014
- duplicate: z10.boolean().optional(),
2097
+ duplicate: z11.boolean().optional(),
2015
2098
  /** The job's state after this submission. */
2016
- state: z10.string().min(1)
2099
+ state: z11.string().min(1)
2017
2100
  }).strict();
2018
- var ReleaseRequest = z10.object({
2019
- protocolVersion: z10.literal(PROTOCOL_VERSION),
2020
- runnerId: z10.string().min(1),
2101
+ var ReleaseRequest = z11.object({
2102
+ protocolVersion: z11.literal(PROTOCOL_VERSION),
2103
+ runnerId: z11.string().min(1),
2021
2104
  /**
2022
2105
  * Which leases to release — the grant, not just the job.
2023
2106
  *
@@ -2025,24 +2108,22 @@ var ReleaseRequest = z10.object({
2025
2108
  * moment it arrives, which for a replayed request is not the lease the
2026
2109
  * daemon meant. See {@link Lease.id}.
2027
2110
  */
2028
- leases: z10.array(
2029
- z10.object({ jobId: z10.string().min(1), leaseId: z10.string().min(1) })
2030
- ),
2111
+ leases: z11.array(GrantRef),
2031
2112
  /**
2032
2113
  * Why, so the app's runner list can say something true.
2033
2114
  *
2034
- * `refused` is load-bearing, not cosmetic: the server cannot evaluate a
2035
- * daemon's *local* `named` allowlist (§4.2), so it may legitimately offer
2115
+ * `refused` is load-bearing, not cosmetic: the server cannot evaluate
2116
+ * what a device will admit (§4.2), so it may legitimately offer
2036
2117
  * a job this daemon then declines. The server MUST record the refusal and
2037
2118
  * stop offering that job to that runner, or the pair would spin between
2038
2119
  * claim and release forever.
2039
2120
  */
2040
- reason: z10.enum(["shutdown", "pause", "revoked", "backend-down", "refused"])
2121
+ reason: z11.enum(["shutdown", "pause", "revoked", "backend-down", "refused"])
2041
2122
  }).strict();
2042
- var ReleaseResponse = z10.object({
2043
- released: z10.array(z10.string().min(1))
2123
+ var ReleaseResponse = z11.object({
2124
+ released: z11.array(z11.string().min(1))
2044
2125
  }).strict();
2045
- var WireErrorCode = z10.enum([
2126
+ var WireErrorCode = z11.enum([
2046
2127
  "bad-request",
2047
2128
  "unsupported-protocol-version",
2048
2129
  // "We do not know who you are." Exactly 401, and only that — cloud_008
@@ -2092,9 +2173,9 @@ var WireErrorCode = z10.enum([
2092
2173
  "rate-limited",
2093
2174
  "server-error"
2094
2175
  ]);
2095
- var WireError = z10.object({
2176
+ var WireError = z11.object({
2096
2177
  error: WireErrorCode,
2097
- message: z10.string().min(1),
2178
+ message: z11.string().min(1),
2098
2179
  /**
2099
2180
  * What this server speaks, on `unsupported-protocol-version` — §B.4.
2100
2181
  *
@@ -2108,10 +2189,10 @@ var WireError = z10.object({
2108
2189
  * Modelled the way `clock-skew`'s two fields already are — code-specific
2109
2190
  * extras, refused on any other code by the refinement below.
2110
2191
  */
2111
- supported: z10.array(z10.string().min(1)).optional(),
2112
- minimum: z10.string().min(1).optional(),
2192
+ supported: z11.array(z11.string().min(1)).optional(),
2193
+ minimum: z11.string().min(1).optional(),
2113
2194
  /** Seconds; mirrors Retry-After for `rate-limited` and `server-error`. */
2114
- retryAfter: z10.number().int().nonnegative().optional(),
2195
+ retryAfter: z11.number().int().nonnegative().optional(),
2115
2196
  /**
2116
2197
  * The server's clock, and the window it allows. `clock-skew` only.
2117
2198
  *
@@ -2121,8 +2202,8 @@ var WireError = z10.object({
2121
2202
  * heartbeat response returns the same value, and so does every `Date`
2122
2203
  * header.
2123
2204
  */
2124
- serverTime: z10.number().int().positive().optional(),
2125
- maxSkewMs: z10.number().int().positive().optional()
2205
+ serverTime: z11.number().int().positive().optional(),
2206
+ maxSkewMs: z11.number().int().positive().optional()
2126
2207
  }).strict().superRefine((error, ctx) => {
2127
2208
  const skew = error.error === "clock-skew";
2128
2209
  const carried = error.serverTime !== void 0 || error.maxSkewMs !== void 0;
@@ -2173,16 +2254,16 @@ var ERROR_STATUS = Object.freeze({
2173
2254
  "rate-limited": 429,
2174
2255
  "server-error": 500
2175
2256
  });
2176
- var FetchRequest = z10.object({
2257
+ var FetchRequest = z11.object({
2177
2258
  // `literal`, like every other request — V1-17. This one said
2178
2259
  // `string().min(1)`, so a daemon speaking a version this server does not
2179
2260
  // know got past the handshake on the one endpoint that hands over a
2180
2261
  // sealed payload. The version check exists so that a mismatch is a named
2181
2262
  // refusal rather than a schema failure three fields later; here it was
2182
2263
  // neither.
2183
- protocolVersion: z10.literal(PROTOCOL_VERSION),
2184
- runnerId: z10.string().min(1),
2185
- jobId: z10.string().min(1),
2264
+ protocolVersion: z11.literal(PROTOCOL_VERSION),
2265
+ runnerId: z11.string().min(1),
2266
+ jobId: z11.string().min(1),
2186
2267
  /**
2187
2268
  * The grant this daemon holds.
2188
2269
  *
@@ -2190,9 +2271,9 @@ var FetchRequest = z10.object({
2190
2271
  * only the job would be answerable for whatever lease exists when it
2191
2272
  * arrives ({@link Lease.id}).
2192
2273
  */
2193
- leaseId: z10.string().min(1)
2274
+ leaseId: z11.string().min(1)
2194
2275
  }).strict();
2195
- var FetchResponse = z10.object({
2276
+ var FetchResponse = z11.object({
2196
2277
  /**
2197
2278
  * The work, sealed to the device that claimed it — byollm_009 §6.
2198
2279
  *
@@ -2211,6 +2292,8 @@ export {
2211
2292
  BackendClass,
2212
2293
  BackendCost,
2213
2294
  BackendIdSchema,
2295
+ CLOCK_ATTRIBUTION_MS,
2296
+ CLOCK_SKEW_WARN_MS,
2214
2297
  Capability,
2215
2298
  CapabilityMatrix,
2216
2299
  ChatMessage,
@@ -2227,7 +2310,11 @@ export {
2227
2310
  EnvelopeDirection,
2228
2311
  FetchRequest,
2229
2312
  FetchResponse,
2313
+ GRANT_CONTEXT,
2314
+ GRANT_MAX_AGE_MS,
2315
+ GRANT_SIGNED_FIELDS,
2230
2316
  GeneratePayload,
2317
+ GrantRef,
2231
2318
  HeartbeatRequest,
2232
2319
  HeartbeatResponse,
2233
2320
  JOB_KINDS,
@@ -2247,6 +2334,7 @@ export {
2247
2334
  MIN_PROTOCOL_VERSION,
2248
2335
  MUSTS,
2249
2336
  MUST_IDS,
2337
+ Manifest,
2250
2338
  MatchRefusal,
2251
2339
  OFFER_SCOPES,
2252
2340
  OfferScope,
@@ -2259,10 +2347,10 @@ export {
2259
2347
  PairStartRequest,
2260
2348
  PairStartResponse,
2261
2349
  PublicIdentity,
2350
+ Purpose,
2262
2351
  REFUSAL_MESSAGES,
2352
+ RESERVED_PURPOSE,
2263
2353
  RETIREMENT_WINDOW_MS,
2264
- ROSTER_CONTEXT,
2265
- ROSTER_MAX_AGE_MS,
2266
2354
  RefusalReason,
2267
2355
  ReleaseRequest,
2268
2356
  ReleaseResponse,
@@ -2277,7 +2365,7 @@ export {
2277
2365
  SUPPORTED_PROTOCOL_VERSIONS,
2278
2366
  SealedEnvelope,
2279
2367
  SealedOutcome,
2280
- SignedRoster,
2368
+ SignedGrant,
2281
2369
  SizeClass,
2282
2370
  StoredKeys,
2283
2371
  Succession,
@@ -2296,6 +2384,7 @@ export {
2296
2384
  effectiveOfferScope,
2297
2385
  fingerprint,
2298
2386
  generateKeys,
2387
+ grantStatement,
2299
2388
  isBackendId,
2300
2389
  isCloudTaggedModel,
2301
2390
  isJobKind,
@@ -2310,20 +2399,20 @@ export {
2310
2399
  provenanceFor,
2311
2400
  publicIdentityOf,
2312
2401
  resolveCost,
2313
- rosterStatement,
2314
2402
  seal,
2403
+ signGrant,
2315
2404
  signRequest,
2316
- signRoster,
2317
2405
  signSiteRequest,
2318
2406
  signSuccession,
2319
2407
  signWith,
2408
+ singlePurposeManifest,
2320
2409
  sizeClassCeiling,
2321
2410
  sizeClassOf,
2322
2411
  successionStatement,
2412
+ verifyGrant,
2323
2413
  verifyLink,
2324
2414
  verifyPublicIdentity,
2325
2415
  verifyRequest,
2326
- verifyRoster,
2327
2416
  verifySiteRequest,
2328
2417
  verifyWith,
2329
2418
  walkSuccession