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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -314,6 +314,13 @@ declare const REFUSAL_MESSAGES: Readonly<Record<MatchRefusal, string>>;
314
314
  * Upper bounds on payload size, enforced at the schema so oversized input is
315
315
  * refused at parse time rather than somewhere deeper.
316
316
  *
317
+ * All three are enforced — cloud_008 Tier 4, finding 30. `maxTotalChars` was
318
+ * declared here and referenced nowhere, under this docstring's claim that the
319
+ * schema enforces them, so a chat payload of 256 messages at a million
320
+ * characters each parsed cleanly at sixty-four times the stated ceiling. The
321
+ * per-field limits were real and the aggregate one was a number in a frozen
322
+ * object.
323
+ *
317
324
  * byollm_004 §4 requires stricter limits for community (`named`/`public`)
318
325
  * jobs; those are applied on top of these by the daemon's budget check, which
319
326
  * knows the job's audience. These are the absolute ceilings for any job.
@@ -448,6 +455,7 @@ declare function isTerminal(state: JobState): boolean;
448
455
  declare function canTransition(from: JobState, to: JobState): boolean;
449
456
  /** A lease: the right to work on a job until `expiresAt`. */
450
457
  declare const Lease: z.ZodObject<{
458
+ id: z.ZodString;
451
459
  runnerId: z.ZodString;
452
460
  expiresAt: z.ZodNumber;
453
461
  }, z.core.$strip>;
@@ -502,8 +510,9 @@ declare const ClaimedJob: z.ZodObject<{
502
510
  public: "public";
503
511
  }>;
504
512
  owner: z.ZodString;
505
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
513
+ site: z.ZodOptional<z.ZodString>;
506
514
  lease: z.ZodObject<{
515
+ id: z.ZodString;
507
516
  runnerId: z.ZodString;
508
517
  expiresAt: z.ZodNumber;
509
518
  }, z.core.$strip>;
@@ -514,7 +523,7 @@ type ClaimedJob = z.infer<typeof ClaimedJob>;
514
523
  *
515
524
  * byollm_003 Rev 1: a `named`/`public` result is attacker-controlled text.
516
525
  * The app must never render volunteer output as its own AI's answer without
517
- * knowing that is what it is ({@link MUSTS.RESULT_PROVENANCE}).
526
+ * knowing that is what it is ({@link MUSTS.PROVENANCE_NAMES_DEVICE}).
518
527
  */
519
528
  declare const ResultProvenance: z.ZodObject<{
520
529
  audience: z.ZodEnum<{
@@ -543,6 +552,30 @@ declare function provenanceFor(input: {
543
552
  backendClass: BackendClass;
544
553
  model: string;
545
554
  }): ResultProvenance;
555
+ /**
556
+ * What the daemon did, sealed with the answer — cloud_008 §2.5.
557
+ *
558
+ * These travelled in the clear on `ResultRequest`, which meant two things at
559
+ * once. On the direct plane the site believed unauthenticated fields beside
560
+ * an authenticated envelope — a daemon could seal one answer and *declare* it
561
+ * came from a different model, and only the field it did not sign would be
562
+ * recorded. Through a relay they reached a third party that acts on none of
563
+ * them, and `model` in particular is the kind of detail Amendment A's rule
564
+ * keeps off the wire.
565
+ *
566
+ * Sealed, they are the daemon's signed statement about its own run: the site
567
+ * opens them, nothing in between sees them, and the disposition check that
568
+ * already compares clear-text against ciphertext extends to cover them.
569
+ */
570
+ declare const RunMetadata: z.ZodObject<{
571
+ model: z.ZodString;
572
+ backendClass: z.ZodEnum<{
573
+ http: "http";
574
+ process: "process";
575
+ }>;
576
+ durationMs: z.ZodNumber;
577
+ }, z.core.$strict>;
578
+ type RunMetadata = z.infer<typeof RunMetadata>;
546
579
  /** Successful outcome. */
547
580
  declare const JobResultOk: z.ZodObject<{
548
581
  outcome: z.ZodLiteral<"ok">;
@@ -573,6 +606,37 @@ declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
573
606
  outcome: z.ZodLiteral<"canceled">;
574
607
  }, z.core.$strict>], "outcome">;
575
608
  type JobOutcome = z.infer<typeof JobOutcome>;
609
+ /**
610
+ * The plaintext inside a result envelope.
611
+ *
612
+ * The outcome and how it was produced, together, because they are one
613
+ * statement by one signer. A site that opened only the outcome would be
614
+ * trusting the envelope for the answer and the request body for everything
615
+ * about it.
616
+ */
617
+ declare const SealedOutcome: z.ZodObject<{
618
+ outcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
619
+ outcome: z.ZodLiteral<"ok">;
620
+ text: z.ZodString;
621
+ artifactUrl: z.ZodOptional<z.ZodURL>;
622
+ }, z.core.$strict>, z.ZodObject<{
623
+ outcome: z.ZodLiteral<"error">;
624
+ code: z.ZodString;
625
+ message: z.ZodString;
626
+ retryable: z.ZodBoolean;
627
+ }, z.core.$strict>, z.ZodObject<{
628
+ outcome: z.ZodLiteral<"canceled">;
629
+ }, z.core.$strict>], "outcome">;
630
+ ran: z.ZodObject<{
631
+ model: z.ZodString;
632
+ backendClass: z.ZodEnum<{
633
+ http: "http";
634
+ process: "process";
635
+ }>;
636
+ durationMs: z.ZodNumber;
637
+ }, z.core.$strict>;
638
+ }, z.core.$strict>;
639
+ type SealedOutcome = z.infer<typeof SealedOutcome>;
576
640
  /** A completed job as delivered to the app, provenance attached. */
577
641
  declare const DeliveredResult: z.ZodObject<{
578
642
  jobId: z.ZodString;
@@ -614,6 +678,435 @@ declare const DeliveredResult: z.ZodObject<{
614
678
  }, z.core.$strict>>;
615
679
  }, z.core.$strict>;
616
680
  type DeliveredResult = z.infer<typeof DeliveredResult>;
681
+ /**
682
+ * How big a payload is, in buckets — byollm_009 §6.
683
+ *
684
+ * A relay routes without reading, and matching a job to a machine needs some
685
+ * notion of size. Buckets rather than byte counts because the exact figure is
686
+ * a stronger fingerprint than the routing decision requires, and because a
687
+ * bucket survives compression and encoding changes that an exact count does
688
+ * not.
689
+ *
690
+ * `unbounded` exists for streamed jobs, which have no size when they start.
691
+ * It is reserved now rather than added later: byollm_009 §8.1 — adding a
692
+ * field to a published envelope is the v2 break all over again.
693
+ */
694
+ declare const SizeClass: z.ZodEnum<{
695
+ small: "small";
696
+ medium: "medium";
697
+ large: "large";
698
+ unbounded: "unbounded";
699
+ }>;
700
+ type SizeClass = z.infer<typeof SizeClass>;
701
+ /** Where the bucket boundaries sit, in characters of payload text. */
702
+ declare const SIZE_CLASS_LIMITS: Readonly<{
703
+ small: 4000;
704
+ medium: 64000;
705
+ large: number;
706
+ }>;
707
+ /**
708
+ * The most a payload in this bucket can be.
709
+ *
710
+ * Used where a decision must be made from a stub, before the payload has been
711
+ * fetched — a budget check, for instance. Charging the bucket's ceiling is the
712
+ * conservative direction: it refuses slightly too eagerly rather than
713
+ * admitting work that turns out larger than the budget allowed.
714
+ *
715
+ * `unbounded` returns `Infinity`, which fails every ceiling. That is correct
716
+ * until byollm_006 defines how a streamed job is budgeted — failing closed on
717
+ * a case nobody has designed beats inventing an allowance for it.
718
+ */
719
+ declare function sizeClassCeiling(sizeClass: SizeClass): number;
720
+ /** Bucket a payload by its text length. */
721
+ declare function sizeClassOf(textChars: number): SizeClass;
722
+ /**
723
+ * Everything an upstream may see about a job — byollm_009 §6.
724
+ *
725
+ * **This list is exhaustive and normative.** It is a commitment about the
726
+ * metadata surface, not an accident of what the implementation happens to
727
+ * send: an upstream that requires more has exceeded the protocol, and an
728
+ * endpoint that emits more has leaked past it
729
+ * ({@link MUSTS.STUB_METADATA_EXHAUSTIVE}).
730
+ *
731
+ * What is absent is the point. No payload, no model, no prompt, no result.
732
+ * `kind` is here because capability matching happens upstream; if a later
733
+ * revision moves matching to the daemon, `kind` moves into the ciphertext.
734
+ */
735
+ declare const JobStub: z.ZodObject<{
736
+ id: z.ZodString;
737
+ kind: z.ZodEnum<{
738
+ "llm.generate": "llm.generate";
739
+ "llm.chat": "llm.chat";
740
+ }>;
741
+ owner: z.ZodString;
742
+ site: z.ZodString;
743
+ audience: z.ZodEnum<{
744
+ self: "self";
745
+ named: "named";
746
+ public: "public";
747
+ }>;
748
+ sizeClass: z.ZodEnum<{
749
+ small: "small";
750
+ medium: "medium";
751
+ large: "large";
752
+ unbounded: "unbounded";
753
+ }>;
754
+ streaming: z.ZodBoolean;
755
+ deadlineAt: z.ZodNumber;
756
+ }, z.core.$strict>;
757
+ type JobStub = z.infer<typeof JobStub>;
758
+ /** A stub, plus the lease the claiming runner now holds for it. */
759
+ declare const ClaimedStub: z.ZodObject<{
760
+ id: z.ZodString;
761
+ kind: z.ZodEnum<{
762
+ "llm.generate": "llm.generate";
763
+ "llm.chat": "llm.chat";
764
+ }>;
765
+ owner: z.ZodString;
766
+ site: z.ZodString;
767
+ audience: z.ZodEnum<{
768
+ self: "self";
769
+ named: "named";
770
+ public: "public";
771
+ }>;
772
+ sizeClass: z.ZodEnum<{
773
+ small: "small";
774
+ medium: "medium";
775
+ large: "large";
776
+ unbounded: "unbounded";
777
+ }>;
778
+ streaming: z.ZodBoolean;
779
+ deadlineAt: z.ZodNumber;
780
+ lease: z.ZodObject<{
781
+ id: z.ZodString;
782
+ runnerId: z.ZodString;
783
+ expiresAt: z.ZodNumber;
784
+ }, z.core.$strip>;
785
+ }, z.core.$strict>;
786
+ type ClaimedStub = z.infer<typeof ClaimedStub>;
787
+
788
+ /**
789
+ * Device and site keys — byollm_009 §3.
790
+ *
791
+ * **Two keypairs per party, and the split is load-bearing.** An Ed25519
792
+ * *identity* key signs; an X25519 *encryption* key receives sealed envelopes.
793
+ * The encryption key is signed by the identity key, and **the identity key is
794
+ * what gets pinned**. So "who sent this" and "who can read this" are answered
795
+ * by different keys — which is what lets an encryption key rotate without
796
+ * re-establishing trust, and what byollm_009 §6's signed-then-sealed envelope
797
+ * depends on.
798
+ *
799
+ * **No new dependency.** byollm_009 §2 says established primitives only, via
800
+ * libsodium. Everything *this* module needs — Ed25519 signing, X25519 key
801
+ * generation — Node provides natively, and using it costs nothing and adds no
802
+ * install weight to a daemon that must land fast on a stranger's laptop.
803
+ *
804
+ * libsodium becomes necessary at envelope v2, where sealing does. That is a
805
+ * real dependency decision and it belongs in the change that needs it: a
806
+ * sealed box is a specific reviewed construction, and rebuilding it out of
807
+ * Node primitives is exactly the "novel construction" §2 rules out. Deferring
808
+ * the dependency is not the same as deferring the rule.
809
+ */
810
+ /** A public identity, as it travels on the wire. All values base64url. */
811
+ declare const PublicIdentity: z.ZodObject<{
812
+ identity: z.ZodString;
813
+ encryption: z.ZodString;
814
+ encryptionSig: z.ZodString;
815
+ }, z.core.$strict>;
816
+ type PublicIdentity = z.infer<typeof PublicIdentity>;
817
+ /** Private key material, as stored on disk. Never leaves the machine. */
818
+ declare const StoredKeys: z.ZodObject<{
819
+ version: z.ZodLiteral<1>;
820
+ identityPublic: z.ZodString;
821
+ identityPrivate: z.ZodString;
822
+ encryptionPublic: z.ZodString;
823
+ encryptionPrivate: z.ZodString;
824
+ encryptionSig: z.ZodString;
825
+ createdAt: z.ZodNumber;
826
+ }, z.core.$strict>;
827
+ type StoredKeys = z.infer<typeof StoredKeys>;
828
+ /** Domain separator, so a signature over an encryption key cannot be
829
+ * replayed as a signature over anything else. */
830
+ /**
831
+ * What an encryption key's signature covers.
832
+ *
833
+ * Exported because a rotation is a real event this protocol has to be able to
834
+ * *test* — a record whose encryption key moved under an identity that signed
835
+ * the move is the one case pinning must refuse loudly, and building one
836
+ * outside this file otherwise means re-typing this string, which is how two
837
+ * copies of a constant start disagreeing.
838
+ */
839
+ declare const ENCRYPTION_KEY_CONTEXT = "byollm/v1/encryption-key";
840
+ /** Generate a fresh pair of keypairs and bind them together. */
841
+ declare function generateKeys(now: number): StoredKeys;
842
+ /** The public half, for the wire. */
843
+ declare function publicIdentityOf(keys: StoredKeys): PublicIdentity;
844
+ /**
845
+ * Check that an encryption key really belongs to the identity presenting it.
846
+ *
847
+ * Called on everything received, including from an upstream we otherwise
848
+ * trust — the point of pinning the identity is that nothing else needs to be
849
+ * trusted, and that only holds if this is checked every time rather than at
850
+ * first sight.
851
+ */
852
+ declare function verifyPublicIdentity(identity: PublicIdentity): boolean;
853
+ /** Sign arbitrary bytes with an identity key. */
854
+ declare function signWith(keys: StoredKeys, data: Uint8Array): string;
855
+ /** Verify bytes against a raw Ed25519 public key. */
856
+ declare function verifyWith(identityPublic: string, data: Uint8Array, signature: string): boolean;
857
+ /**
858
+ * A fingerprint a human can compare out loud.
859
+ *
860
+ * 120 bits of SHA-256 over the raw identity key, as six groups of four. Long
861
+ * enough that grinding a colliding key is not worth anyone's afternoon, short
862
+ * enough to read down a phone line — which is the whole point. A fingerprint
863
+ * nobody can be bothered to compare provides no security at all, so
864
+ * legibility is a security property here, not a nicety.
865
+ *
866
+ * Formatted with a `BYOLLM-` prefix so a pasted fingerprint is recognisable
867
+ * out of context, in a support thread or a screenshot.
868
+ */
869
+ declare function fingerprint(identityPublic: string): string;
870
+ /** The short id used in envelopes and provenance. Stable, and comparable. */
871
+ declare const keyId: (identityPublic: string) => string;
872
+
873
+ declare function cryptoReady(): Promise<void>;
874
+ /**
875
+ * How long a sealed payload is worth keeping, from creation.
876
+ *
877
+ * Bound into every envelope and recomputed when one is opened, so it lives
878
+ * here rather than in the two places that need it. Two copies of a value the
879
+ * signature depends on is the same bug as two clock readings: it works until
880
+ * they disagree, and then nothing can be opened.
881
+ *
882
+ * Not a job's TTL. That answers how long the *work* is worth doing, belongs
883
+ * to the app and the store, and may legitimately differ per deployment.
884
+ */
885
+ declare const ENVELOPE_MAX_AGE_MS: number;
886
+ /** Which leg an envelope belongs to. Bound into the signature. */
887
+ declare const EnvelopeDirection: z.ZodEnum<{
888
+ payload: "payload";
889
+ result: "result";
890
+ }>;
891
+ type EnvelopeDirection = z.infer<typeof EnvelopeDirection>;
892
+ declare const SealedEnvelope: z.ZodObject<{
893
+ ciphertext: z.ZodString;
894
+ recipientKeyId: z.ZodString;
895
+ senderKeyId: z.ZodString;
896
+ direction: z.ZodEnum<{
897
+ payload: "payload";
898
+ result: "result";
899
+ }>;
900
+ deadlineAt: z.ZodNumber;
901
+ }, z.core.$strict>;
902
+ type SealedEnvelope = z.infer<typeof SealedEnvelope>;
903
+ /** Everything the signature covers besides the plaintext itself. */
904
+ interface EnvelopeContext {
905
+ readonly jobId: string;
906
+ readonly senderKeyId: string;
907
+ readonly recipientKeyId: string;
908
+ readonly deadlineAt: number;
909
+ readonly direction: EnvelopeDirection;
910
+ }
911
+ /** Seal a plaintext to a recipient, signed by the sender's identity. */
912
+ declare function seal(input: {
913
+ plaintext: string;
914
+ senderKeys: StoredKeys;
915
+ recipientEncryptionPublic: string;
916
+ context: EnvelopeContext;
917
+ }): Promise<SealedEnvelope>;
918
+ /** Why an envelope was refused. Never distinguished to a remote caller. */
919
+ type EnvelopeFailure = "not-for-us" | "unopenable" | "malformed" | "bad-signature" | "context-mismatch";
920
+ type OpenResult = {
921
+ readonly ok: true;
922
+ readonly plaintext: string;
923
+ } | {
924
+ readonly ok: false;
925
+ readonly reason: EnvelopeFailure;
926
+ };
927
+ /**
928
+ * Open an envelope and verify it came from the pinned sender.
929
+ *
930
+ * Every failure returns rather than throws: this runs on input from the
931
+ * network, and a crash here is a denial of service on the delivery path.
932
+ *
933
+ * The context is checked against the signature, not merely read from the
934
+ * envelope. An envelope carries its own claims about who sent it and to
935
+ * whom — believing those would authenticate the attacker's assertion rather
936
+ * than the sender's key.
937
+ */
938
+ declare function open(input: {
939
+ envelope: SealedEnvelope;
940
+ recipientKeys: StoredKeys;
941
+ senderIdentityPublic: string;
942
+ /** The deadline is taken from the envelope and checked against its signature. */
943
+ expected: Omit<EnvelopeContext, "deadlineAt">;
944
+ }): Promise<OpenResult>;
945
+
946
+ /**
947
+ * Request signing — byollm_009 §4.2.
948
+ *
949
+ * Every authenticated call is signed by the calling device's identity key.
950
+ * There is no bearer token on the daemon plane: possession of a file no
951
+ * longer grants access, possession of a *key* does, and the key never leaves
952
+ * the machine.
953
+ *
954
+ * ## Why this is not the server-issued nonce the spec first described
955
+ *
956
+ * byollm_009 §4.2 says "the upstream issues a nonce; the daemon signs it".
957
+ * Implementing that costs one of two things: a round trip before every
958
+ * request, or server-side session state — and sessions reintroduce a bearer
959
+ * credential, which is the thing being removed.
960
+ *
961
+ * Signing *the request itself* gets the same property without either, because
962
+ * of something the protocol already guarantees. A captured signature is valid
963
+ * only for the exact request it covers — same endpoint, same runner, same
964
+ * body — and every authenticated endpoint here is idempotent by design:
965
+ * `RESULT_IDEMPOTENT` makes a replayed result a no-op, a replayed claim from
966
+ * the same runner returns what that runner already holds, and heartbeat and
967
+ * release are idempotent in effect. So a replay inside the freshness window
968
+ * gains an attacker nothing they could not obtain by forwarding the original,
969
+ * which a relay can do anyway.
970
+ *
971
+ * That is the whole argument, and it is worth stating because it rests
972
+ * entirely on the endpoints being idempotent. Two ways that can fail, and the
973
+ * second is the one that actually bit:
974
+ *
975
+ * 1. **A future endpoint that is not idempotent cannot use this scheme
976
+ * unchanged** — it would need a server-issued nonce.
977
+ * 2. **Idempotence must hold per *addressed instance*, not per endpoint.** A
978
+ * request that names a mutable target — a lease, a session, a
979
+ * subscription — must name the *instance*, or a replay lands on a
980
+ * different one than the sender meant and the endpoint's idempotence buys
981
+ * nothing. `release` was idempotent per lease and ambiguous across them:
982
+ * it named a job and a runner, both of which survive a
983
+ * claim-release-reclaim cycle, so a replayed release yanked a later grant.
984
+ * Fixed by giving a lease its own id and requiring it.
985
+ *
986
+ * The rule for anything added later: if a signed request can be replayed onto
987
+ * a target that has changed underneath it, the request has to say which
988
+ * target it meant.
989
+ */
990
+ /** How far a request's timestamp may be from the server's clock. */
991
+ declare const MAX_CLOCK_SKEW_MS = 120000;
992
+ /** The signed material a request carries. */
993
+ declare const RequestSignature: z.ZodObject<{
994
+ runnerId: z.ZodString;
995
+ issuedAt: z.ZodNumber;
996
+ signature: z.ZodString;
997
+ }, z.core.$strict>;
998
+ type RequestSignature = z.infer<typeof RequestSignature>;
999
+ /**
1000
+ * The exact bytes both sides sign and verify.
1001
+ *
1002
+ * Newline-separated with a version prefix and a domain separator. Every field
1003
+ * that decides what the request *does* is in here: leave one out and it
1004
+ * becomes something an intermediary can change without breaking the
1005
+ * signature.
1006
+ *
1007
+ * The body is included by hash rather than by value, so signing does not
1008
+ * depend on both sides serialising JSON identically — which they would not.
1009
+ */
1010
+ declare function canonicalRequest(input: {
1011
+ endpoint: string;
1012
+ runnerId: string;
1013
+ issuedAt: number;
1014
+ body: string;
1015
+ }): Buffer;
1016
+ /** Sign an outgoing request with this machine's identity key. */
1017
+ declare function signRequest(keys: StoredKeys, input: {
1018
+ endpoint: string;
1019
+ runnerId: string;
1020
+ issuedAt: number;
1021
+ body: string;
1022
+ }): RequestSignature;
1023
+ /**
1024
+ * The same scheme, for the party at the other end: a **site** calling a relay.
1025
+ *
1026
+ * A site talking to a relay is in exactly the daemon's position — an outbound
1027
+ * caller with an identity keypair the other side already pins — so it gets the
1028
+ * daemon's authentication rather than a second scheme. Bearer tokens for the
1029
+ * site plane were the alternative, and they would have reintroduced the
1030
+ * credential-in-a-file that §4.2 removed from the daemon plane, on the plane
1031
+ * that carries *every* site's traffic.
1032
+ *
1033
+ * Two things make this safe to build on the same canonical string:
1034
+ *
1035
+ * 1. **The endpoint is namespaced.** Site endpoints sign `site/enqueue`, never
1036
+ * `enqueue`. The daemon plane's `result` and the site plane's `results` are
1037
+ * one character apart, and a naming collision between planes must not be
1038
+ * what stands between a signature and a replay onto the wrong handler. The
1039
+ * prefix is applied *inside* these helpers, so the two ends cannot disagree
1040
+ * about it — the alternative is two implementations of one bound value,
1041
+ * which is this project's most-repeated bug.
1042
+ * 2. **The caller slot carries the site id.** `canonicalRequest` names that
1043
+ * field `runnerId` because the daemon plane got there first; here it holds
1044
+ * the site id, and the verifier looks the key up in the projection's site
1045
+ * registry rather than its device registry. The two registries never share
1046
+ * an entry, so a device signature cannot authenticate as a site.
1047
+ *
1048
+ * §4.2's replay argument carries over **only because the site plane's writes
1049
+ * are idempotent per addressed instance**, which is a property that had to be
1050
+ * built rather than found: `enqueue` reset a job of the same id, so a replayed
1051
+ * enqueue inside the freshness window returned a claimed job to the queue and
1052
+ * threw away a device's live lease. Identical in shape to the `release` bug
1053
+ * above, on the other plane. Anything added to the site plane later must be
1054
+ * idempotent by the instance it names, or this scheme does not cover it.
1055
+ */
1056
+ declare function signSiteRequest(keys: StoredKeys, input: {
1057
+ endpoint: string;
1058
+ siteId: string;
1059
+ issuedAt: number;
1060
+ body: string;
1061
+ }): RequestSignature;
1062
+ /** Verify a site's call against the identity the control plane registered. */
1063
+ declare function verifySiteRequest(input: {
1064
+ identityPublic: string;
1065
+ endpoint: string;
1066
+ body: string;
1067
+ signature: RequestSignature;
1068
+ now: number;
1069
+ maxSkewMs?: number;
1070
+ }): SignatureFailure | null;
1071
+ /**
1072
+ * Why a signed request was refused.
1073
+ *
1074
+ * **`bad-signature` is never returned verbatim; `stale` is, deliberately.**
1075
+ * They are different kinds of refusal and conflating them costs a real user
1076
+ * more than it costs an attacker.
1077
+ *
1078
+ * A bad signature is an authentication failure and the server says only
1079
+ * "unauthorized" — telling a prober which part they got wrong is free help.
1080
+ *
1081
+ * A stale timestamp is a **precondition** failure: the signature may be
1082
+ * perfectly valid and the caller's clock is simply wrong. Saying so reveals
1083
+ * nothing, for two reasons that both have to hold. The server's time is
1084
+ * already public — every response carries a `Date` header and the heartbeat
1085
+ * response returns `serverTime` outright. And freshness is checked *before*
1086
+ * the signature is verified, so a stale answer says nothing about whether the
1087
+ * signature was any good.
1088
+ *
1089
+ * What conflating them costs: a machine whose clock has drifted gets
1090
+ * `401 unauthorized` on every request, forever, with nothing anywhere pointing
1091
+ * at the clock. That is the shape byollm_013 was filed about — a refusal that
1092
+ * is correct, silent, and sends somebody to read our source.
1093
+ */
1094
+ type SignatureFailure = "stale" | "bad-signature";
1095
+ /**
1096
+ * Verify a signed request against a runner's pinned identity key.
1097
+ *
1098
+ * Freshness is checked in **both** directions. A clock far ahead is as much a
1099
+ * problem as one behind: it would let a captured request stay replayable long
1100
+ * after it was made, which is the one thing the window exists to bound.
1101
+ */
1102
+ declare function verifyRequest(input: {
1103
+ identityPublic: string;
1104
+ endpoint: string;
1105
+ body: string;
1106
+ signature: RequestSignature;
1107
+ now: number;
1108
+ maxSkewMs?: number;
1109
+ }): SignatureFailure | null;
617
1110
 
618
1111
  /**
619
1112
  * The normative MUSTs of protocol v0, as data.
@@ -630,6 +1123,63 @@ type DeliveredResult = z.infer<typeof DeliveredResult>;
630
1123
  */
631
1124
  /** Which side of the wire is obliged to enforce a given MUST. */
632
1125
  type MustEnforcer = "daemon" | "server" | "both";
1126
+ /**
1127
+ * How a MUST is actually verified — which is not the same question as who
1128
+ * enforces it, and is the one that decides what "byollm-compatible" means.
1129
+ *
1130
+ * The conformance kit's credibility rests on an implicit claim that every
1131
+ * MUST is checkable. Ten of them were not, and the kit reported that honestly
1132
+ * while nothing acted on it. Making the kind explicit turns "uncovered" from
1133
+ * a number needing a paragraph of explanation into a number that should be
1134
+ * zero.
1135
+ *
1136
+ * - `conformance` — the kit asserts it against *any* implementation. This is
1137
+ * the strong kind: a third party runs the suite and learns something.
1138
+ * - `adversarial` — proved by the reference daemon's own suites in this repo
1139
+ * (the hostile-payload corpus, or its unit tests). Real verification, and
1140
+ * it runs in CI — but it proves things about *our* daemon, not about
1141
+ * someone else's, so the kit cannot carry it.
1142
+ * - `construction` — true by the shape of the code, where a test could only
1143
+ * sample. A reviewer verifies it; a suite cannot.
1144
+ * ## When a MUST binds both sides — cloud_008 Tier 3
1145
+ *
1146
+ * `AUDIENCE_BOTH_SIDES` says the server and the daemon each enforce. The kit
1147
+ * passed **entirely** with the server's half deleted: every check drove a real
1148
+ * daemon, and a daemon refuses locally, so "the job did not run" looked
1149
+ * identical whichever side refused it. A full-honest-stack test proves only
1150
+ * the conjunction.
1151
+ *
1152
+ * So a `both`-enforced MUST needs **one check per party, each with the honest
1153
+ * counterpart removed** — C032 claims over the raw protocol precisely so no
1154
+ * daemon admission logic runs. Where a check strips one side, its comment
1155
+ * says which; where a MUST is enforced by both and only one side is checked,
1156
+ * that is a gap rather than coverage.
1157
+ *
1158
+ * - `operator` — a claim about how someone runs a deployment, verifiable only
1159
+ * by audit or by reading source. The honest category, and the one that
1160
+ * exists so a property nobody can check from outside is *labelled* as such
1161
+ * rather than laundered by association with the checkable ones.
1162
+ */
1163
+ type MustVerification = "conformance" | "adversarial" | "construction" | "operator";
1164
+ /**
1165
+ * How a MUST is verified — one kind, or several.
1166
+ *
1167
+ * Several is not hedging. `SITES_LOCALLY_APPROVED` is the case that forced it:
1168
+ * the fence is **construction** — a daemon cannot serve a site that is not in
1169
+ * its map, and admission refuses before a payload is fetched — while the
1170
+ * property that a *removed and re-offered* id is still refused needs a hostile
1171
+ * sequence of heartbeats no honest client would send, which is
1172
+ * **adversarial**. Recording one and dropping the other would either overstate
1173
+ * what a type check proves or understate what the suites do.
1174
+ *
1175
+ * The alternative was a second field for the second kind, which is two answers
1176
+ * to one question — the shape this project keeps deleting.
1177
+ */
1178
+ type MustVerifiedBy = MustVerification | readonly [MustVerification, ...MustVerification[]];
1179
+ /** The kinds a MUST claims, always as a list. */
1180
+ declare function kindsOf(must: {
1181
+ readonly verifiedBy: MustVerifiedBy;
1182
+ }): readonly MustVerification[];
633
1183
  /** A single normative requirement of the protocol. */
634
1184
  interface Must {
635
1185
  /** Stable public id, cited by conformance output. */
@@ -638,6 +1188,11 @@ interface Must {
638
1188
  readonly statement: string;
639
1189
  /** Which implementation is obliged to enforce it. */
640
1190
  readonly enforcedBy: MustEnforcer;
1191
+ /**
1192
+ * How this is verified. `conformance` is the only kind the kit can assert;
1193
+ * see {@link MustVerification} for why the others exist.
1194
+ */
1195
+ readonly verifiedBy: MustVerifiedBy;
641
1196
  /** Spec section this was adjudicated in. */
642
1197
  readonly source: string;
643
1198
  }
@@ -653,6 +1208,14 @@ declare const MUSTS: Readonly<{
653
1208
  readonly PAIR_ONE_USER: Must;
654
1209
  readonly PAIR_INTERACTIVE: Must;
655
1210
  readonly PAIR_CODE_EXPIRES: Must;
1211
+ readonly VERSION_HANDSHAKE_REQUIRED: Must;
1212
+ readonly SITE_KEY_BY_STUB: Must;
1213
+ readonly SITES_LOCALLY_APPROVED: Must;
1214
+ readonly KEYS_EXCHANGED_AT_CONSENT: Must;
1215
+ readonly REQUESTS_SIGNED_NOT_BEARER: Must;
1216
+ readonly LEASE_SCOPED_BY_GRANT: Must;
1217
+ readonly STUB_METADATA_EXHAUSTIVE: Must;
1218
+ readonly ENVELOPE_SEALED_AND_SIGNED: Must;
656
1219
  readonly KIND_TYPED_ONLY: Must;
657
1220
  readonly KIND_NO_CODE: Must;
658
1221
  readonly CLAIM_REQUIRES_CAPABILITY: Must;
@@ -674,7 +1237,7 @@ declare const MUSTS: Readonly<{
674
1237
  readonly TTL_EXPIRY: Must;
675
1238
  readonly NO_RUNNER_SIGNAL: Must;
676
1239
  readonly RESULT_IDEMPOTENT: Must;
677
- readonly RESULT_PROVENANCE: Must;
1240
+ readonly PROVENANCE_NAMES_DEVICE: Must;
678
1241
  readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
679
1242
  readonly NO_SHELL_INTERPOLATION: Must;
680
1243
  readonly NO_PAYLOAD_ROUTING: Must;
@@ -682,18 +1245,92 @@ declare const MUSTS: Readonly<{
682
1245
  readonly HTTP_BASE_URL_SAFE: Must;
683
1246
  readonly OUTPUT_INERT: Must;
684
1247
  readonly COMMUNITY_BUDGETS: Must;
1248
+ readonly REVOCATION_IMMEDIATE: Must;
1249
+ readonly CONSENT_BEFORE_ROUTE: Must;
1250
+ readonly ROSTER_NOT_DISCLOSED: Must;
1251
+ readonly EFFECTIVE_OFFER_ONLY: Must;
1252
+ readonly FALLBACK_LABELED: Must;
1253
+ readonly RELAY_BLIND: Must;
1254
+ readonly SHARED_COMPUTE_DISCLOSED: Must;
685
1255
  }>;
686
1256
  /** The id of any normative MUST. */
687
1257
  type MustId = keyof typeof MUSTS;
688
1258
  /** All MUST ids, for coverage checks. */
689
- declare const MUST_IDS: readonly ("PAIR_ONE_USER" | "PAIR_INTERACTIVE" | "PAIR_CODE_EXPIRES" | "KIND_TYPED_ONLY" | "KIND_NO_CODE" | "CLAIM_REQUIRES_CAPABILITY" | "CAPABILITY_IS_DETECTED" | "CLAIM_ATOMIC" | "LEASE_HONORED" | "LEASE_RECLAIMABLE" | "AUDIENCE_BOTH_SIDES" | "SUBSCRIPTION_SELF_LOCK" | "METERED_DEFAULTS_SELF" | "METERED_REQUIRES_CEILING" | "COST_NOT_CONFIGURABLE" | "REMOTE_IS_NEVER_FREE" | "NAMED_LOCAL_ALLOWLIST" | "REFUSAL_NOT_REOFFERED" | "REVOCATION_HONORED" | "CANCEL_HONORED" | "DEPENDS_ON_GATING" | "TTL_EXPIRY" | "NO_RUNNER_SIGNAL" | "RESULT_IDEMPOTENT" | "RESULT_PROVENANCE" | "INGRESS_LOGGED_BEFORE_EXECUTION" | "NO_SHELL_INTERPOLATION" | "NO_PAYLOAD_ROUTING" | "STRIPPED_CHILD_ENV" | "HTTP_BASE_URL_SAFE" | "OUTPUT_INERT" | "COMMUNITY_BUDGETS")[];
1259
+ declare const MUST_IDS: readonly ("PAIR_ONE_USER" | "PAIR_INTERACTIVE" | "PAIR_CODE_EXPIRES" | "VERSION_HANDSHAKE_REQUIRED" | "SITE_KEY_BY_STUB" | "SITES_LOCALLY_APPROVED" | "KEYS_EXCHANGED_AT_CONSENT" | "REQUESTS_SIGNED_NOT_BEARER" | "LEASE_SCOPED_BY_GRANT" | "STUB_METADATA_EXHAUSTIVE" | "ENVELOPE_SEALED_AND_SIGNED" | "KIND_TYPED_ONLY" | "KIND_NO_CODE" | "CLAIM_REQUIRES_CAPABILITY" | "CAPABILITY_IS_DETECTED" | "CLAIM_ATOMIC" | "LEASE_HONORED" | "LEASE_RECLAIMABLE" | "AUDIENCE_BOTH_SIDES" | "SUBSCRIPTION_SELF_LOCK" | "METERED_DEFAULTS_SELF" | "METERED_REQUIRES_CEILING" | "COST_NOT_CONFIGURABLE" | "REMOTE_IS_NEVER_FREE" | "NAMED_LOCAL_ALLOWLIST" | "REFUSAL_NOT_REOFFERED" | "REVOCATION_HONORED" | "CANCEL_HONORED" | "DEPENDS_ON_GATING" | "TTL_EXPIRY" | "NO_RUNNER_SIGNAL" | "RESULT_IDEMPOTENT" | "PROVENANCE_NAMES_DEVICE" | "INGRESS_LOGGED_BEFORE_EXECUTION" | "NO_SHELL_INTERPOLATION" | "NO_PAYLOAD_ROUTING" | "STRIPPED_CHILD_ENV" | "HTTP_BASE_URL_SAFE" | "OUTPUT_INERT" | "COMMUNITY_BUDGETS" | "REVOCATION_IMMEDIATE" | "CONSENT_BEFORE_ROUTE" | "ROSTER_NOT_DISCLOSED" | "EFFECTIVE_OFFER_ONLY" | "FALLBACK_LABELED" | "RELAY_BLIND" | "SHARED_COMPUTE_DISCLOSED")[];
1260
+ /** Every MUST verified a particular way. */
1261
+ declare function mustsVerifiedBy(kind: MustVerification): MustId[];
690
1262
 
691
1263
  /** Protocol version carried on every request; servers refuse what they can't speak. */
692
1264
  declare const PROTOCOL_VERSION: "0";
1265
+ /**
1266
+ * Every protocol version this build can serve, **oldest first**.
1267
+ *
1268
+ * One entry today. It is a list rather than a constant because the shape of
1269
+ * the check is the point: a server supporting two versions through a
1270
+ * migration should not need a different code path from one supporting one.
1271
+ */
1272
+ declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
1273
+ /**
1274
+ * The oldest version this build will talk to — derived, not declared.
1275
+ *
1276
+ * Stating it separately would be a second thing to keep in step with the list
1277
+ * above, and the failure would be silent: a minimum that no longer matches
1278
+ * what is supported produces a refusal naming a version the server would in
1279
+ * fact have accepted.
1280
+ */
1281
+ declare const MIN_PROTOCOL_VERSION: string;
1282
+ /** A structured refusal, so a daemon can say something useful to its owner. */
1283
+ interface VersionRefusal {
1284
+ readonly error: "unsupported-protocol-version";
1285
+ readonly message: string;
1286
+ readonly supported: readonly string[];
1287
+ readonly minimum: string;
1288
+ }
1289
+ /**
1290
+ * The version a request declares, wherever it carries it.
1291
+ *
1292
+ * A POST declares it in its body, which is where every request schema has
1293
+ * always put it. A GET has no body, and the relay has one — the site plane's
1294
+ * `pending` read — so it declares it in the query string instead.
1295
+ *
1296
+ * **Two carriers, one rule.** That asymmetry is HTTP's rather than ours, and
1297
+ * the alternative was worse in both directions: a header for everything would
1298
+ * change every existing daemon's request, and skipping GETs would leave an
1299
+ * endpoint outside the handshake — which is precisely the shape B.4 found,
1300
+ * where a whole plane was outside it.
1301
+ */
1302
+ declare function declaredVersion(input: {
1303
+ body?: unknown;
1304
+ query?: URLSearchParams;
1305
+ }): unknown;
1306
+ /**
1307
+ * Check the protocol version on an incoming request
1308
+ * ({@link MUSTS.VERSION_HANDSHAKE_REQUIRED}).
1309
+ *
1310
+ * Returns a refusal, or `null` to proceed.
1311
+ *
1312
+ * **A missing version is refused the same way a wrong one is.** That is the
1313
+ * half worth stating: before this existed, the version travelled as a
1314
+ * `z.literal` inside each endpoint's schema, so a mismatch surfaced as a
1315
+ * generic `bad-request` — a daemon and a server discovered they disagreed by
1316
+ * failing, with nothing in the response naming the disagreement. An error a
1317
+ * user cannot act on is barely better than a hang.
1318
+ *
1319
+ * The message names the fix, because the person reading it is usually the one
1320
+ * who has to apply it.
1321
+ */
1322
+ declare function checkProtocolVersion(body: unknown): VersionRefusal | null;
693
1323
  /** The path prefix all endpoints mount under. */
694
1324
  declare const PROTOCOL_PREFIX: "/byollm";
695
- /** The five endpoint names, in the order byollm_001 lists them. */
696
- declare const ENDPOINTS: readonly ["pair", "claim", "heartbeat", "result", "release"];
1325
+ /**
1326
+ * The endpoint names, in the order byollm_001 lists them, plus `fetch`.
1327
+ *
1328
+ * `fetch` is byollm_009 §6's second phase: a claim returns a stub, and the
1329
+ * payload is collected separately by the device that took it. Two steps
1330
+ * rather than one because a payload can only be sealed once its recipient is
1331
+ * known — which is also what makes multi-device free.
1332
+ */
1333
+ declare const ENDPOINTS: readonly ["pair", "claim", "fetch", "heartbeat", "result", "release"];
697
1334
  type Endpoint = (typeof ENDPOINTS)[number];
698
1335
  /**
699
1336
  * One entry of the capability matrix: a kind this daemon can actually serve,
@@ -799,6 +1436,11 @@ declare const PairStartRequest: z.ZodObject<{
799
1436
  win32: "win32";
800
1437
  }>;
801
1438
  }, z.core.$strip>;
1439
+ device: z.ZodObject<{
1440
+ identity: z.ZodString;
1441
+ encryption: z.ZodString;
1442
+ encryptionSig: z.ZodString;
1443
+ }, z.core.$strict>;
802
1444
  capabilities: z.ZodArray<z.ZodObject<{
803
1445
  kind: z.ZodEnum<{
804
1446
  "llm.generate": "llm.generate";
@@ -859,10 +1501,14 @@ declare const PairPollResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
859
1501
  status: z.ZodLiteral<"expired">;
860
1502
  }, z.core.$strict>, z.ZodObject<{
861
1503
  status: z.ZodLiteral<"approved">;
862
- runnerToken: z.ZodString;
863
1504
  runnerId: z.ZodString;
864
1505
  owner: z.ZodString;
865
1506
  ownerLabel: z.ZodOptional<z.ZodString>;
1507
+ sites: z.ZodRecord<z.ZodString, z.ZodObject<{
1508
+ identity: z.ZodString;
1509
+ encryption: z.ZodString;
1510
+ encryptionSig: z.ZodString;
1511
+ }, z.core.$strict>>;
866
1512
  }, z.core.$strict>], "status">;
867
1513
  type PairPollResponse = z.infer<typeof PairPollResponse>;
868
1514
  declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -877,6 +1523,11 @@ declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
877
1523
  win32: "win32";
878
1524
  }>;
879
1525
  }, z.core.$strip>;
1526
+ device: z.ZodObject<{
1527
+ identity: z.ZodString;
1528
+ encryption: z.ZodString;
1529
+ encryptionSig: z.ZodString;
1530
+ }, z.core.$strict>;
880
1531
  capabilities: z.ZodArray<z.ZodObject<{
881
1532
  kind: z.ZodEnum<{
882
1533
  "llm.generate": "llm.generate";
@@ -968,28 +1619,23 @@ declare const ClaimResponse: z.ZodObject<{
968
1619
  "llm.generate": "llm.generate";
969
1620
  "llm.chat": "llm.chat";
970
1621
  }>;
971
- payload: z.ZodUnion<readonly [z.ZodObject<{
972
- prompt: z.ZodString;
973
- system: z.ZodOptional<z.ZodString>;
974
- }, z.core.$strict>, z.ZodObject<{
975
- messages: z.ZodArray<z.ZodObject<{
976
- role: z.ZodEnum<{
977
- system: "system";
978
- user: "user";
979
- assistant: "assistant";
980
- }>;
981
- content: z.ZodString;
982
- }, z.core.$strip>>;
983
- system: z.ZodOptional<z.ZodString>;
984
- }, z.core.$strict>]>;
1622
+ owner: z.ZodString;
1623
+ site: z.ZodString;
985
1624
  audience: z.ZodEnum<{
986
1625
  self: "self";
987
1626
  named: "named";
988
1627
  public: "public";
989
1628
  }>;
990
- owner: z.ZodString;
991
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
1629
+ sizeClass: z.ZodEnum<{
1630
+ small: "small";
1631
+ medium: "medium";
1632
+ large: "large";
1633
+ unbounded: "unbounded";
1634
+ }>;
1635
+ streaming: z.ZodBoolean;
1636
+ deadlineAt: z.ZodNumber;
992
1637
  lease: z.ZodObject<{
1638
+ id: z.ZodString;
993
1639
  runnerId: z.ZodString;
994
1640
  expiresAt: z.ZodNumber;
995
1641
  }, z.core.$strip>;
@@ -1037,58 +1683,88 @@ declare const HeartbeatRequest: z.ZodObject<{
1037
1683
  public: "public";
1038
1684
  }>;
1039
1685
  }, z.core.$strict>>;
1040
- activeJobIds: z.ZodArray<z.ZodString>;
1686
+ activeLeases: z.ZodArray<z.ZodObject<{
1687
+ jobId: z.ZodString;
1688
+ leaseId: z.ZodString;
1689
+ }, z.core.$strip>>;
1041
1690
  paused: z.ZodBoolean;
1042
1691
  }, z.core.$strict>;
1043
1692
  type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;
1044
1693
  declare const HeartbeatResponse: z.ZodObject<{
1045
- revoked: z.ZodBoolean;
1046
- cancel: z.ZodArray<z.ZodString>;
1047
- leases: z.ZodArray<z.ZodObject<{
1694
+ sites: z.ZodRecord<z.ZodString, z.ZodObject<{
1695
+ identity: z.ZodString;
1696
+ encryption: z.ZodString;
1697
+ encryptionSig: z.ZodString;
1698
+ }, z.core.$strict>>;
1699
+ cancel: z.ZodArray<z.ZodObject<{
1048
1700
  jobId: z.ZodString;
1049
- expiresAt: z.ZodNumber;
1701
+ leaseId: z.ZodString;
1702
+ }, z.core.$strict>>;
1703
+ lost: z.ZodArray<z.ZodObject<{
1704
+ jobId: z.ZodString;
1705
+ leaseId: z.ZodString;
1050
1706
  }, z.core.$strict>>;
1051
- lost: z.ZodArray<z.ZodString>;
1052
1707
  serverTime: z.ZodNumber;
1708
+ awaitingConsent: z.ZodArray<z.ZodString>;
1053
1709
  }, z.core.$strict>;
1054
1710
  type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
1711
+ /**
1712
+ * What an intermediary learns about how a job ended — byollm_009 §6.
1713
+ *
1714
+ * The discriminator and nothing else. A relay has to know a job reached a
1715
+ * terminal state, and whether it failed, because that decides whether the job
1716
+ * leaves the queue or the app may re-enqueue. It does not have to know what
1717
+ * the model said, or what an error said, and this is where that line is drawn.
1718
+ *
1719
+ * Kept identical to `JobOutcome`'s discriminator rather than coarsened to
1720
+ * ok/not-ok: a cancelled job and a failed one are different routing outcomes,
1721
+ * and collapsing them would make the relay guess.
1722
+ */
1723
+ declare const ResultDisposition: z.ZodEnum<{
1724
+ ok: "ok";
1725
+ error: "error";
1726
+ canceled: "canceled";
1727
+ }>;
1728
+ type ResultDisposition = z.infer<typeof ResultDisposition>;
1055
1729
  declare const ResultRequest: z.ZodObject<{
1056
1730
  protocolVersion: z.ZodLiteral<"0">;
1057
1731
  runnerId: z.ZodString;
1058
1732
  jobId: z.ZodString;
1059
- outcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
1060
- outcome: z.ZodLiteral<"ok">;
1061
- text: z.ZodString;
1062
- artifactUrl: z.ZodOptional<z.ZodURL>;
1063
- }, z.core.$strict>, z.ZodObject<{
1064
- outcome: z.ZodLiteral<"error">;
1065
- code: z.ZodString;
1066
- message: z.ZodString;
1067
- retryable: z.ZodBoolean;
1068
- }, z.core.$strict>, z.ZodObject<{
1069
- outcome: z.ZodLiteral<"canceled">;
1070
- }, z.core.$strict>], "outcome">;
1071
- model: z.ZodString;
1072
- backendClass: z.ZodEnum<{
1073
- http: "http";
1074
- process: "process";
1733
+ leaseId: z.ZodString;
1734
+ envelope: z.ZodObject<{
1735
+ ciphertext: z.ZodString;
1736
+ recipientKeyId: z.ZodString;
1737
+ senderKeyId: z.ZodString;
1738
+ direction: z.ZodEnum<{
1739
+ payload: "payload";
1740
+ result: "result";
1741
+ }>;
1742
+ deadlineAt: z.ZodNumber;
1743
+ }, z.core.$strict>;
1744
+ disposition: z.ZodEnum<{
1745
+ ok: "ok";
1746
+ error: "error";
1747
+ canceled: "canceled";
1075
1748
  }>;
1076
- durationMs: z.ZodNumber;
1077
1749
  }, z.core.$strict>;
1078
1750
  type ResultRequest = z.infer<typeof ResultRequest>;
1079
1751
  declare const ResultResponse: z.ZodObject<{
1080
1752
  accepted: z.ZodBoolean;
1753
+ duplicate: z.ZodOptional<z.ZodBoolean>;
1081
1754
  state: z.ZodString;
1082
1755
  }, z.core.$strict>;
1083
1756
  type ResultResponse = z.infer<typeof ResultResponse>;
1084
1757
  declare const ReleaseRequest: z.ZodObject<{
1085
1758
  protocolVersion: z.ZodLiteral<"0">;
1086
1759
  runnerId: z.ZodString;
1087
- jobIds: z.ZodArray<z.ZodString>;
1760
+ leases: z.ZodArray<z.ZodObject<{
1761
+ jobId: z.ZodString;
1762
+ leaseId: z.ZodString;
1763
+ }, z.core.$strip>>;
1088
1764
  reason: z.ZodEnum<{
1089
- revoked: "revoked";
1090
1765
  shutdown: "shutdown";
1091
1766
  pause: "pause";
1767
+ revoked: "revoked";
1092
1768
  "backend-down": "backend-down";
1093
1769
  refused: "refused";
1094
1770
  }>;
@@ -1107,30 +1783,62 @@ type ReleaseResponse = z.infer<typeof ReleaseResponse>;
1107
1783
  * with no response at all.
1108
1784
  */
1109
1785
  declare const WireErrorCode: z.ZodEnum<{
1786
+ "unsupported-protocol-version": "unsupported-protocol-version";
1110
1787
  revoked: "revoked";
1111
1788
  "bad-request": "bad-request";
1112
- "unsupported-protocol-version": "unsupported-protocol-version";
1113
1789
  unauthorized: "unauthorized";
1790
+ forbidden: "forbidden";
1114
1791
  "not-found": "not-found";
1792
+ "not-ready": "not-ready";
1793
+ "too-late": "too-late";
1794
+ "clock-skew": "clock-skew";
1115
1795
  "rate-limited": "rate-limited";
1116
1796
  "server-error": "server-error";
1117
1797
  }>;
1118
1798
  type WireErrorCode = z.infer<typeof WireErrorCode>;
1119
1799
  declare const WireError: z.ZodObject<{
1120
1800
  error: z.ZodEnum<{
1801
+ "unsupported-protocol-version": "unsupported-protocol-version";
1121
1802
  revoked: "revoked";
1122
1803
  "bad-request": "bad-request";
1123
- "unsupported-protocol-version": "unsupported-protocol-version";
1124
1804
  unauthorized: "unauthorized";
1805
+ forbidden: "forbidden";
1125
1806
  "not-found": "not-found";
1807
+ "not-ready": "not-ready";
1808
+ "too-late": "too-late";
1809
+ "clock-skew": "clock-skew";
1126
1810
  "rate-limited": "rate-limited";
1127
1811
  "server-error": "server-error";
1128
1812
  }>;
1129
1813
  message: z.ZodString;
1814
+ supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
1815
+ minimum: z.ZodOptional<z.ZodString>;
1130
1816
  retryAfter: z.ZodOptional<z.ZodNumber>;
1817
+ serverTime: z.ZodOptional<z.ZodNumber>;
1818
+ maxSkewMs: z.ZodOptional<z.ZodNumber>;
1131
1819
  }, z.core.$strict>;
1132
1820
  type WireError = z.infer<typeof WireError>;
1133
1821
  /** HTTP status each error code is served with. */
1134
1822
  declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
1823
+ declare const FetchRequest: z.ZodObject<{
1824
+ protocolVersion: z.ZodLiteral<"0">;
1825
+ runnerId: z.ZodString;
1826
+ jobId: z.ZodString;
1827
+ leaseId: z.ZodString;
1828
+ }, z.core.$strict>;
1829
+ type FetchRequest = z.infer<typeof FetchRequest>;
1830
+ declare const FetchResponse: z.ZodObject<{
1831
+ envelope: z.ZodObject<{
1832
+ ciphertext: z.ZodString;
1833
+ recipientKeyId: z.ZodString;
1834
+ senderKeyId: z.ZodString;
1835
+ direction: z.ZodEnum<{
1836
+ payload: "payload";
1837
+ result: "result";
1838
+ }>;
1839
+ deadlineAt: z.ZodNumber;
1840
+ }, z.core.$strict>;
1841
+ }, z.core.$strict>;
1842
+ type FetchResponse = z.infer<typeof FetchResponse>;
1135
1843
 
1136
- export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, DeliveredResult, ENDPOINTS, ERROR_STATUS, type Endpoint, GeneratePayload, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobResultCanceled, JobResultError, JobResultOk, JobState, KindedPayload, Lease, MUSTS, MUST_IDS, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, OFFER_SCOPES, OfferScope, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, REFUSAL_MESSAGES, ReleaseRequest, ReleaseResponse, ResultProvenance, ResultRequest, ResultResponse, type SpendConsent, TERMINAL_STATES, WireError, WireErrorCode, backendDescriptor, canTransition, effectiveOfferScope, isBackendId, isJobKind, isLocalHost, isTerminal, matchAudience, payloadTextLength, provenanceFor, resolveCost };
1844
+ 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, 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, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASS_LIMITS, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SizeClass, type SpendConsent, StoredKeys, 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, signWith, sizeClassCeiling, sizeClassOf, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith };