@byollm/protocol 0.1.0-alpha.4 → 0.1.0-alpha.41

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.
@@ -503,7 +510,7 @@ declare const ClaimedJob: z.ZodObject<{
503
510
  public: "public";
504
511
  }>;
505
512
  owner: z.ZodString;
506
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
513
+ site: z.ZodOptional<z.ZodString>;
507
514
  lease: z.ZodObject<{
508
515
  id: z.ZodString;
509
516
  runnerId: z.ZodString;
@@ -516,7 +523,7 @@ type ClaimedJob = z.infer<typeof ClaimedJob>;
516
523
  *
517
524
  * byollm_003 Rev 1: a `named`/`public` result is attacker-controlled text.
518
525
  * The app must never render volunteer output as its own AI's answer without
519
- * knowing that is what it is ({@link MUSTS.RESULT_PROVENANCE}).
526
+ * knowing that is what it is ({@link MUSTS.PROVENANCE_NAMES_DEVICE}).
520
527
  */
521
528
  declare const ResultProvenance: z.ZodObject<{
522
529
  audience: z.ZodEnum<{
@@ -545,6 +552,30 @@ declare function provenanceFor(input: {
545
552
  backendClass: BackendClass;
546
553
  model: string;
547
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>;
548
579
  /** Successful outcome. */
549
580
  declare const JobResultOk: z.ZodObject<{
550
581
  outcome: z.ZodLiteral<"ok">;
@@ -575,6 +606,37 @@ declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
575
606
  outcome: z.ZodLiteral<"canceled">;
576
607
  }, z.core.$strict>], "outcome">;
577
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>;
578
640
  /** A completed job as delivered to the app, provenance attached. */
579
641
  declare const DeliveredResult: z.ZodObject<{
580
642
  jobId: z.ZodString;
@@ -614,6 +676,7 @@ declare const DeliveredResult: z.ZodObject<{
614
676
  model: z.ZodString;
615
677
  untrusted: z.ZodBoolean;
616
678
  }, z.core.$strict>>;
679
+ fallback: z.ZodOptional<z.ZodLiteral<true>>;
617
680
  }, z.core.$strict>;
618
681
  type DeliveredResult = z.infer<typeof DeliveredResult>;
619
682
  /**
@@ -677,12 +740,12 @@ declare const JobStub: z.ZodObject<{
677
740
  "llm.chat": "llm.chat";
678
741
  }>;
679
742
  owner: z.ZodString;
743
+ site: z.ZodString;
680
744
  audience: z.ZodEnum<{
681
745
  self: "self";
682
746
  named: "named";
683
747
  public: "public";
684
748
  }>;
685
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
686
749
  sizeClass: z.ZodEnum<{
687
750
  small: "small";
688
751
  medium: "medium";
@@ -701,12 +764,12 @@ declare const ClaimedStub: z.ZodObject<{
701
764
  "llm.chat": "llm.chat";
702
765
  }>;
703
766
  owner: z.ZodString;
767
+ site: z.ZodString;
704
768
  audience: z.ZodEnum<{
705
769
  self: "self";
706
770
  named: "named";
707
771
  public: "public";
708
772
  }>;
709
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
710
773
  sizeClass: z.ZodEnum<{
711
774
  small: "small";
712
775
  medium: "medium";
@@ -763,6 +826,18 @@ declare const StoredKeys: z.ZodObject<{
763
826
  createdAt: z.ZodNumber;
764
827
  }, z.core.$strict>;
765
828
  type StoredKeys = z.infer<typeof StoredKeys>;
829
+ /** Domain separator, so a signature over an encryption key cannot be
830
+ * replayed as a signature over anything else. */
831
+ /**
832
+ * What an encryption key's signature covers.
833
+ *
834
+ * Exported because a rotation is a real event this protocol has to be able to
835
+ * *test* — a record whose encryption key moved under an identity that signed
836
+ * the move is the one case pinning must refuse loudly, and building one
837
+ * outside this file otherwise means re-typing this string, which is how two
838
+ * copies of a constant start disagreeing.
839
+ */
840
+ declare const ENCRYPTION_KEY_CONTEXT = "byollm/v1/encryption-key";
766
841
  /** Generate a fresh pair of keypairs and bind them together. */
767
842
  declare function generateKeys(now: number): StoredKeys;
768
843
  /** The public half, for the wire. */
@@ -946,7 +1021,77 @@ declare function signRequest(keys: StoredKeys, input: {
946
1021
  issuedAt: number;
947
1022
  body: string;
948
1023
  }): RequestSignature;
949
- /** Why a signed request was refused. Never returned to the caller verbatim. */
1024
+ /**
1025
+ * The same scheme, for the party at the other end: a **site** calling a relay.
1026
+ *
1027
+ * A site talking to a relay is in exactly the daemon's position — an outbound
1028
+ * caller with an identity keypair the other side already pins — so it gets the
1029
+ * daemon's authentication rather than a second scheme. Bearer tokens for the
1030
+ * site plane were the alternative, and they would have reintroduced the
1031
+ * credential-in-a-file that §4.2 removed from the daemon plane, on the plane
1032
+ * that carries *every* site's traffic.
1033
+ *
1034
+ * Two things make this safe to build on the same canonical string:
1035
+ *
1036
+ * 1. **The endpoint is namespaced.** Site endpoints sign `site/enqueue`, never
1037
+ * `enqueue`. The daemon plane's `result` and the site plane's `results` are
1038
+ * one character apart, and a naming collision between planes must not be
1039
+ * what stands between a signature and a replay onto the wrong handler. The
1040
+ * prefix is applied *inside* these helpers, so the two ends cannot disagree
1041
+ * about it — the alternative is two implementations of one bound value,
1042
+ * which is this project's most-repeated bug.
1043
+ * 2. **The caller slot carries the site id.** `canonicalRequest` names that
1044
+ * field `runnerId` because the daemon plane got there first; here it holds
1045
+ * the site id, and the verifier looks the key up in the projection's site
1046
+ * registry rather than its device registry. The two registries never share
1047
+ * an entry, so a device signature cannot authenticate as a site.
1048
+ *
1049
+ * §4.2's replay argument carries over **only because the site plane's writes
1050
+ * are idempotent per addressed instance**, which is a property that had to be
1051
+ * built rather than found: `enqueue` reset a job of the same id, so a replayed
1052
+ * enqueue inside the freshness window returned a claimed job to the queue and
1053
+ * threw away a device's live lease. Identical in shape to the `release` bug
1054
+ * above, on the other plane. Anything added to the site plane later must be
1055
+ * idempotent by the instance it names, or this scheme does not cover it.
1056
+ */
1057
+ declare function signSiteRequest(keys: StoredKeys, input: {
1058
+ endpoint: string;
1059
+ siteId: string;
1060
+ issuedAt: number;
1061
+ body: string;
1062
+ }): RequestSignature;
1063
+ /** Verify a site's call against the identity the control plane registered. */
1064
+ declare function verifySiteRequest(input: {
1065
+ identityPublic: string;
1066
+ endpoint: string;
1067
+ body: string;
1068
+ signature: RequestSignature;
1069
+ now: number;
1070
+ maxSkewMs?: number;
1071
+ }): SignatureFailure | null;
1072
+ /**
1073
+ * Why a signed request was refused.
1074
+ *
1075
+ * **`bad-signature` is never returned verbatim; `stale` is, deliberately.**
1076
+ * They are different kinds of refusal and conflating them costs a real user
1077
+ * more than it costs an attacker.
1078
+ *
1079
+ * A bad signature is an authentication failure and the server says only
1080
+ * "unauthorized" — telling a prober which part they got wrong is free help.
1081
+ *
1082
+ * A stale timestamp is a **precondition** failure: the signature may be
1083
+ * perfectly valid and the caller's clock is simply wrong. Saying so reveals
1084
+ * nothing, for two reasons that both have to hold. The server's time is
1085
+ * already public — every response carries a `Date` header and the heartbeat
1086
+ * response returns `serverTime` outright. And freshness is checked *before*
1087
+ * the signature is verified, so a stale answer says nothing about whether the
1088
+ * signature was any good.
1089
+ *
1090
+ * What conflating them costs: a machine whose clock has drifted gets
1091
+ * `401 unauthorized` on every request, forever, with nothing anywhere pointing
1092
+ * at the clock. That is the shape byollm_013 was filed about — a refusal that
1093
+ * is correct, silent, and sends somebody to read our source.
1094
+ */
950
1095
  type SignatureFailure = "stale" | "bad-signature";
951
1096
  /**
952
1097
  * Verify a signed request against a runner's pinned identity key.
@@ -964,6 +1109,110 @@ declare function verifyRequest(input: {
964
1109
  maxSkewMs?: number;
965
1110
  }): SignatureFailure | null;
966
1111
 
1112
+ /**
1113
+ * Rotation — byollm_009 Amendment C.
1114
+ *
1115
+ * A site holding identity key **K1** wants to be known by **K2**. It publishes
1116
+ * a *succession*: K2, plus a signature by K1 over a statement naming both key
1117
+ * ids. That signature is the entire mechanism, and the reason rotation can be
1118
+ * automatic without becoming a hole is that **the relay cannot mint one** — it
1119
+ * never holds K1. It is the same trust step a daemon already performs at
1120
+ * pairing, applied to the site's own succession.
1121
+ *
1122
+ * ## Why the statement names both keys
1123
+ *
1124
+ * A signature over K2 alone could be lifted from this site's record and
1125
+ * replayed into another site's, moving *that* site to K2 — a key the attacker
1126
+ * holds. Naming the predecessor binds the succession to one chain, and it is
1127
+ * the reason `verifyLink` takes the id it expects to be succeeding from
1128
+ * rather than reading it out of the statement it is checking.
1129
+ */
1130
+ /** The domain separator. Distinct from every other thing an identity signs. */
1131
+ declare const SUCCESSION_CONTEXT = "byollm/v1/site-succession";
1132
+ /**
1133
+ * How long a retired key may still sign work — Amendment C, ruling 2.
1134
+ *
1135
+ * A protocol constant and not the site's to choose. Per-site overlap
1136
+ * arithmetic is exactly the kind of number that has to mean one thing
1137
+ * everywhere, and a site that could choose it could choose *forever*, which is
1138
+ * a two-key site permanently and a second key nobody ever notices retiring.
1139
+ *
1140
+ * Seven days: long enough that a daemon which polls daily and a laptop shut
1141
+ * for a long weekend both see the new record before the old key stops working,
1142
+ * short enough that "which key is live" is never an interesting question.
1143
+ */
1144
+ declare const RETIREMENT_WINDOW_MS: number;
1145
+ /**
1146
+ * The longest chain a daemon will walk — Amendment C, ruling 1.
1147
+ *
1148
+ * **A denial-of-service guard, not policy.** The bound exists so a projection
1149
+ * cannot make a daemon verify ten thousand signatures, not to express an
1150
+ * opinion about how often a site may rotate. A site that legitimately exceeds
1151
+ * it has a re-pair ahead of it, which is why it is generous: at one rotation a
1152
+ * quarter this is sixteen years.
1153
+ */
1154
+ declare const MAX_SUCCESSION_CHAIN = 64;
1155
+ /** One step of a chain: a key, and the signature by it over its successor. */
1156
+ declare const Succession: z.ZodObject<{
1157
+ identity: z.ZodObject<{
1158
+ identity: z.ZodString;
1159
+ encryption: z.ZodString;
1160
+ encryptionSig: z.ZodString;
1161
+ }, z.core.$strict>;
1162
+ signature: z.ZodString;
1163
+ }, z.core.$strict>;
1164
+ type Succession = z.infer<typeof Succession>;
1165
+ /** The exact bytes signed. One definition; both sides call it. */
1166
+ declare function successionStatement(fromKeyId: string, toKeyId: string): Uint8Array;
1167
+ /**
1168
+ * Sign a succession from the keys being retired to the identity taking over.
1169
+ *
1170
+ * Takes `StoredKeys` for the predecessor because only the holder of K1's
1171
+ * private half can produce this, which is the property the whole design rests
1172
+ * on. A site calls this once, at rotation, on the machine holding its keys.
1173
+ */
1174
+ declare function signSuccession(previous: StoredKeys, next: PublicIdentity): Succession;
1175
+ /**
1176
+ * Check one link: did `link.identity` sign over succeeding to `toKeyId`?
1177
+ *
1178
+ * `toKeyId` is passed in rather than read from anywhere in `link`, and that is
1179
+ * the load-bearing detail. A verifier that recovered the successor from the
1180
+ * signed statement would accept a statement about *any* successor, which is
1181
+ * the replay this design names in C.1 — the signature is genuine, the
1182
+ * successor it names is not the one being installed.
1183
+ */
1184
+ declare function verifyLink(link: Succession, toKeyId: string): boolean;
1185
+ /** Why a chain was refused, in the words a log line uses. */
1186
+ type SuccessionFailure = "no-chain" | "too-long" | "unknown-origin" | "broken-link";
1187
+ interface SuccessionWalk {
1188
+ /** The ids the chain passes through, oldest first, ending at the current. */
1189
+ readonly path: string[];
1190
+ /** The approved id the chain reached, when it reached one. */
1191
+ readonly from?: string;
1192
+ readonly failure?: SuccessionFailure;
1193
+ }
1194
+ /**
1195
+ * Walk a chain from the key being presented back to a key already approved.
1196
+ *
1197
+ * `chain` is ordered oldest last, as the projection carries it — so walking it
1198
+ * means starting at the current key and stepping backwards, each link proving
1199
+ * that its holder signed for the id in front of it.
1200
+ *
1201
+ * Returns the approved id it reached, or why it did not. **Deliberately
1202
+ * returns rather than throws**: a chain that does not verify is ordinary
1203
+ * hostile input, and the caller's job is to keep its existing pin and say so.
1204
+ *
1205
+ * `approved` is asked as a predicate rather than taken as a set because the
1206
+ * daemon's notion of "already approved" includes tombstoned ids — a site that
1207
+ * left the allowlist and came back is still a site this machine has vouched
1208
+ * for, and rotation must not become a way to launder that distinction away.
1209
+ */
1210
+ declare function walkSuccession(input: {
1211
+ current: string;
1212
+ chain: readonly Succession[];
1213
+ approved: (keyId: string) => boolean;
1214
+ }): SuccessionWalk;
1215
+
967
1216
  /**
968
1217
  * The normative MUSTs of protocol v0, as data.
969
1218
  *
@@ -997,12 +1246,45 @@ type MustEnforcer = "daemon" | "server" | "both";
997
1246
  * someone else's, so the kit cannot carry it.
998
1247
  * - `construction` — true by the shape of the code, where a test could only
999
1248
  * sample. A reviewer verifies it; a suite cannot.
1249
+ * ## When a MUST binds both sides — cloud_008 Tier 3
1250
+ *
1251
+ * `AUDIENCE_BOTH_SIDES` says the server and the daemon each enforce. The kit
1252
+ * passed **entirely** with the server's half deleted: every check drove a real
1253
+ * daemon, and a daemon refuses locally, so "the job did not run" looked
1254
+ * identical whichever side refused it. A full-honest-stack test proves only
1255
+ * the conjunction.
1256
+ *
1257
+ * So a `both`-enforced MUST needs **one check per party, each with the honest
1258
+ * counterpart removed** — C032 claims over the raw protocol precisely so no
1259
+ * daemon admission logic runs. Where a check strips one side, its comment
1260
+ * says which; where a MUST is enforced by both and only one side is checked,
1261
+ * that is a gap rather than coverage.
1262
+ *
1000
1263
  * - `operator` — a claim about how someone runs a deployment, verifiable only
1001
1264
  * by audit or by reading source. The honest category, and the one that
1002
1265
  * exists so a property nobody can check from outside is *labelled* as such
1003
1266
  * rather than laundered by association with the checkable ones.
1004
1267
  */
1005
1268
  type MustVerification = "conformance" | "adversarial" | "construction" | "operator";
1269
+ /**
1270
+ * How a MUST is verified — one kind, or several.
1271
+ *
1272
+ * Several is not hedging. `SITES_LOCALLY_APPROVED` is the case that forced it:
1273
+ * the fence is **construction** — a daemon cannot serve a site that is not in
1274
+ * its map, and admission refuses before a payload is fetched — while the
1275
+ * property that a *removed and re-offered* id is still refused needs a hostile
1276
+ * sequence of heartbeats no honest client would send, which is
1277
+ * **adversarial**. Recording one and dropping the other would either overstate
1278
+ * what a type check proves or understate what the suites do.
1279
+ *
1280
+ * The alternative was a second field for the second kind, which is two answers
1281
+ * to one question — the shape this project keeps deleting.
1282
+ */
1283
+ type MustVerifiedBy = MustVerification | readonly [MustVerification, ...MustVerification[]];
1284
+ /** The kinds a MUST claims, always as a list. */
1285
+ declare function kindsOf(must: {
1286
+ readonly verifiedBy: MustVerifiedBy;
1287
+ }): readonly MustVerification[];
1006
1288
  /** A single normative requirement of the protocol. */
1007
1289
  interface Must {
1008
1290
  /** Stable public id, cited by conformance output. */
@@ -1015,7 +1297,7 @@ interface Must {
1015
1297
  * How this is verified. `conformance` is the only kind the kit can assert;
1016
1298
  * see {@link MustVerification} for why the others exist.
1017
1299
  */
1018
- readonly verifiedBy: MustVerification;
1300
+ readonly verifiedBy: MustVerifiedBy;
1019
1301
  /** Spec section this was adjudicated in. */
1020
1302
  readonly source: string;
1021
1303
  }
@@ -1032,6 +1314,8 @@ declare const MUSTS: Readonly<{
1032
1314
  readonly PAIR_INTERACTIVE: Must;
1033
1315
  readonly PAIR_CODE_EXPIRES: Must;
1034
1316
  readonly VERSION_HANDSHAKE_REQUIRED: Must;
1317
+ readonly SITE_KEY_BY_STUB: Must;
1318
+ readonly SITES_LOCALLY_APPROVED: Must;
1035
1319
  readonly KEYS_EXCHANGED_AT_CONSENT: Must;
1036
1320
  readonly REQUESTS_SIGNED_NOT_BEARER: Must;
1037
1321
  readonly LEASE_SCOPED_BY_GRANT: Must;
@@ -1058,7 +1342,7 @@ declare const MUSTS: Readonly<{
1058
1342
  readonly TTL_EXPIRY: Must;
1059
1343
  readonly NO_RUNNER_SIGNAL: Must;
1060
1344
  readonly RESULT_IDEMPOTENT: Must;
1061
- readonly RESULT_PROVENANCE: Must;
1345
+ readonly PROVENANCE_NAMES_DEVICE: Must;
1062
1346
  readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
1063
1347
  readonly NO_SHELL_INTERPOLATION: Must;
1064
1348
  readonly NO_PAYLOAD_ROUTING: Must;
@@ -1066,11 +1350,18 @@ declare const MUSTS: Readonly<{
1066
1350
  readonly HTTP_BASE_URL_SAFE: Must;
1067
1351
  readonly OUTPUT_INERT: Must;
1068
1352
  readonly COMMUNITY_BUDGETS: Must;
1353
+ readonly REVOCATION_IMMEDIATE: Must;
1354
+ readonly CONSENT_BEFORE_ROUTE: Must;
1355
+ readonly ROSTER_NOT_DISCLOSED: Must;
1356
+ readonly EFFECTIVE_OFFER_ONLY: Must;
1357
+ readonly FALLBACK_LABELED: Must;
1358
+ readonly RELAY_BLIND: Must;
1359
+ readonly SHARED_COMPUTE_DISCLOSED: Must;
1069
1360
  }>;
1070
1361
  /** The id of any normative MUST. */
1071
1362
  type MustId = keyof typeof MUSTS;
1072
1363
  /** All MUST ids, for coverage checks. */
1073
- declare const MUST_IDS: readonly ("PAIR_ONE_USER" | "PAIR_INTERACTIVE" | "PAIR_CODE_EXPIRES" | "VERSION_HANDSHAKE_REQUIRED" | "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" | "RESULT_PROVENANCE" | "INGRESS_LOGGED_BEFORE_EXECUTION" | "NO_SHELL_INTERPOLATION" | "NO_PAYLOAD_ROUTING" | "STRIPPED_CHILD_ENV" | "HTTP_BASE_URL_SAFE" | "OUTPUT_INERT" | "COMMUNITY_BUDGETS")[];
1364
+ 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")[];
1074
1365
  /** Every MUST verified a particular way. */
1075
1366
  declare function mustsVerifiedBy(kind: MustVerification): MustId[];
1076
1367
 
@@ -1100,6 +1391,23 @@ interface VersionRefusal {
1100
1391
  readonly supported: readonly string[];
1101
1392
  readonly minimum: string;
1102
1393
  }
1394
+ /**
1395
+ * The version a request declares, wherever it carries it.
1396
+ *
1397
+ * A POST declares it in its body, which is where every request schema has
1398
+ * always put it. A GET has no body, and the relay has one — the site plane's
1399
+ * `pending` read — so it declares it in the query string instead.
1400
+ *
1401
+ * **Two carriers, one rule.** That asymmetry is HTTP's rather than ours, and
1402
+ * the alternative was worse in both directions: a header for everything would
1403
+ * change every existing daemon's request, and skipping GETs would leave an
1404
+ * endpoint outside the handshake — which is precisely the shape B.4 found,
1405
+ * where a whole plane was outside it.
1406
+ */
1407
+ declare function declaredVersion(input: {
1408
+ body?: unknown;
1409
+ query?: URLSearchParams;
1410
+ }): unknown;
1103
1411
  /**
1104
1412
  * Check the protocol version on an incoming request
1105
1413
  * ({@link MUSTS.VERSION_HANDSHAKE_REQUIRED}).
@@ -1298,15 +1606,14 @@ declare const PairPollResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
1298
1606
  status: z.ZodLiteral<"expired">;
1299
1607
  }, z.core.$strict>, z.ZodObject<{
1300
1608
  status: z.ZodLiteral<"approved">;
1301
- runnerToken: z.ZodString;
1302
1609
  runnerId: z.ZodString;
1303
1610
  owner: z.ZodString;
1304
1611
  ownerLabel: z.ZodOptional<z.ZodString>;
1305
- site: z.ZodObject<{
1612
+ sites: z.ZodRecord<z.ZodString, z.ZodObject<{
1306
1613
  identity: z.ZodString;
1307
1614
  encryption: z.ZodString;
1308
1615
  encryptionSig: z.ZodString;
1309
- }, z.core.$strict>;
1616
+ }, z.core.$strict>>;
1310
1617
  }, z.core.$strict>], "status">;
1311
1618
  type PairPollResponse = z.infer<typeof PairPollResponse>;
1312
1619
  declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -1418,12 +1725,12 @@ declare const ClaimResponse: z.ZodObject<{
1418
1725
  "llm.chat": "llm.chat";
1419
1726
  }>;
1420
1727
  owner: z.ZodString;
1728
+ site: z.ZodString;
1421
1729
  audience: z.ZodEnum<{
1422
1730
  self: "self";
1423
1731
  named: "named";
1424
1732
  public: "public";
1425
1733
  }>;
1426
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
1427
1734
  sizeClass: z.ZodEnum<{
1428
1735
  small: "small";
1429
1736
  medium: "medium";
@@ -1489,14 +1796,32 @@ declare const HeartbeatRequest: z.ZodObject<{
1489
1796
  }, z.core.$strict>;
1490
1797
  type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;
1491
1798
  declare const HeartbeatResponse: z.ZodObject<{
1492
- revoked: z.ZodBoolean;
1493
- cancel: z.ZodArray<z.ZodString>;
1494
- leases: z.ZodArray<z.ZodObject<{
1799
+ sites: z.ZodRecord<z.ZodString, z.ZodObject<{
1800
+ identity: z.ZodString;
1801
+ encryption: z.ZodString;
1802
+ encryptionSig: z.ZodString;
1803
+ }, z.core.$strict>>;
1804
+ successions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
1805
+ succeeds: z.ZodArray<z.ZodObject<{
1806
+ identity: z.ZodObject<{
1807
+ identity: z.ZodString;
1808
+ encryption: z.ZodString;
1809
+ encryptionSig: z.ZodString;
1810
+ }, z.core.$strict>;
1811
+ signature: z.ZodString;
1812
+ }, z.core.$strict>>;
1813
+ retiringUntil: z.ZodOptional<z.ZodNumber>;
1814
+ }, z.core.$strict>>>;
1815
+ cancel: z.ZodArray<z.ZodObject<{
1495
1816
  jobId: z.ZodString;
1496
- expiresAt: z.ZodNumber;
1817
+ leaseId: z.ZodString;
1818
+ }, z.core.$strict>>;
1819
+ lost: z.ZodArray<z.ZodObject<{
1820
+ jobId: z.ZodString;
1821
+ leaseId: z.ZodString;
1497
1822
  }, z.core.$strict>>;
1498
- lost: z.ZodArray<z.ZodString>;
1499
1823
  serverTime: z.ZodNumber;
1824
+ awaitingConsent: z.ZodArray<z.ZodString>;
1500
1825
  }, z.core.$strict>;
1501
1826
  type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
1502
1827
  /**
@@ -1521,6 +1846,7 @@ declare const ResultRequest: z.ZodObject<{
1521
1846
  protocolVersion: z.ZodLiteral<"0">;
1522
1847
  runnerId: z.ZodString;
1523
1848
  jobId: z.ZodString;
1849
+ leaseId: z.ZodString;
1524
1850
  envelope: z.ZodObject<{
1525
1851
  ciphertext: z.ZodString;
1526
1852
  recipientKeyId: z.ZodString;
@@ -1536,16 +1862,11 @@ declare const ResultRequest: z.ZodObject<{
1536
1862
  error: "error";
1537
1863
  canceled: "canceled";
1538
1864
  }>;
1539
- model: z.ZodString;
1540
- backendClass: z.ZodEnum<{
1541
- http: "http";
1542
- process: "process";
1543
- }>;
1544
- durationMs: z.ZodNumber;
1545
1865
  }, z.core.$strict>;
1546
1866
  type ResultRequest = z.infer<typeof ResultRequest>;
1547
1867
  declare const ResultResponse: z.ZodObject<{
1548
1868
  accepted: z.ZodBoolean;
1869
+ duplicate: z.ZodOptional<z.ZodBoolean>;
1549
1870
  state: z.ZodString;
1550
1871
  }, z.core.$strict>;
1551
1872
  type ResultResponse = z.infer<typeof ResultResponse>;
@@ -1557,9 +1878,9 @@ declare const ReleaseRequest: z.ZodObject<{
1557
1878
  leaseId: z.ZodString;
1558
1879
  }, z.core.$strip>>;
1559
1880
  reason: z.ZodEnum<{
1560
- revoked: "revoked";
1561
1881
  shutdown: "shutdown";
1562
1882
  pause: "pause";
1883
+ revoked: "revoked";
1563
1884
  "backend-down": "backend-down";
1564
1885
  refused: "refused";
1565
1886
  }>;
@@ -1582,7 +1903,11 @@ declare const WireErrorCode: z.ZodEnum<{
1582
1903
  revoked: "revoked";
1583
1904
  "bad-request": "bad-request";
1584
1905
  unauthorized: "unauthorized";
1906
+ forbidden: "forbidden";
1585
1907
  "not-found": "not-found";
1908
+ "not-ready": "not-ready";
1909
+ "too-late": "too-late";
1910
+ "clock-skew": "clock-skew";
1586
1911
  "rate-limited": "rate-limited";
1587
1912
  "server-error": "server-error";
1588
1913
  }>;
@@ -1593,18 +1918,26 @@ declare const WireError: z.ZodObject<{
1593
1918
  revoked: "revoked";
1594
1919
  "bad-request": "bad-request";
1595
1920
  unauthorized: "unauthorized";
1921
+ forbidden: "forbidden";
1596
1922
  "not-found": "not-found";
1923
+ "not-ready": "not-ready";
1924
+ "too-late": "too-late";
1925
+ "clock-skew": "clock-skew";
1597
1926
  "rate-limited": "rate-limited";
1598
1927
  "server-error": "server-error";
1599
1928
  }>;
1600
1929
  message: z.ZodString;
1930
+ supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
1931
+ minimum: z.ZodOptional<z.ZodString>;
1601
1932
  retryAfter: z.ZodOptional<z.ZodNumber>;
1933
+ serverTime: z.ZodOptional<z.ZodNumber>;
1934
+ maxSkewMs: z.ZodOptional<z.ZodNumber>;
1602
1935
  }, z.core.$strict>;
1603
1936
  type WireError = z.infer<typeof WireError>;
1604
1937
  /** HTTP status each error code is served with. */
1605
1938
  declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
1606
1939
  declare const FetchRequest: z.ZodObject<{
1607
- protocolVersion: z.ZodString;
1940
+ protocolVersion: z.ZodLiteral<"0">;
1608
1941
  runnerId: z.ZodString;
1609
1942
  jobId: z.ZodString;
1610
1943
  leaseId: z.ZodString;
@@ -1624,4 +1957,4 @@ declare const FetchResponse: z.ZodObject<{
1624
1957
  }, z.core.$strict>;
1625
1958
  type FetchResponse = z.infer<typeof FetchResponse>;
1626
1959
 
1627
- export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DeliveredResult, 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, 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, SIZE_CLASS_LIMITS, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, type SignatureFailure, SizeClass, type SpendConsent, StoredKeys, TERMINAL_STATES, type VersionRefusal, WireError, WireErrorCode, backendDescriptor, canTransition, canonicalRequest, checkProtocolVersion, cryptoReady, effectiveOfferScope, fingerprint, generateKeys, isBackendId, isJobKind, isLocalHost, isTerminal, keyId, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signRequest, signWith, sizeClassCeiling, sizeClassOf, verifyPublicIdentity, verifyRequest, verifyWith };
1960
+ export { AUDIENCES, Audience, BACKENDS, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, DeliveredResult, ENCRYPTION_KEY_CONTEXT, ENDPOINTS, ENVELOPE_MAX_AGE_MS, ERROR_STATUS, type Endpoint, type EnvelopeContext, EnvelopeDirection, type EnvelopeFailure, FetchRequest, FetchResponse, GeneratePayload, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, OFFER_SCOPES, OfferScope, type OpenResult, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, REFUSAL_MESSAGES, RETIREMENT_WINDOW_MS, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SizeClass, type SpendConsent, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, type VersionRefusal, WireError, WireErrorCode, backendDescriptor, canTransition, canonicalRequest, checkProtocolVersion, cryptoReady, declaredVersion, effectiveOfferScope, fingerprint, generateKeys, isBackendId, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signRequest, signSiteRequest, signSuccession, signWith, sizeClassCeiling, sizeClassOf, successionStatement, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession };