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

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;
@@ -677,12 +739,12 @@ declare const JobStub: z.ZodObject<{
677
739
  "llm.chat": "llm.chat";
678
740
  }>;
679
741
  owner: z.ZodString;
742
+ site: z.ZodString;
680
743
  audience: z.ZodEnum<{
681
744
  self: "self";
682
745
  named: "named";
683
746
  public: "public";
684
747
  }>;
685
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
686
748
  sizeClass: z.ZodEnum<{
687
749
  small: "small";
688
750
  medium: "medium";
@@ -701,12 +763,12 @@ declare const ClaimedStub: z.ZodObject<{
701
763
  "llm.chat": "llm.chat";
702
764
  }>;
703
765
  owner: z.ZodString;
766
+ site: z.ZodString;
704
767
  audience: z.ZodEnum<{
705
768
  self: "self";
706
769
  named: "named";
707
770
  public: "public";
708
771
  }>;
709
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
710
772
  sizeClass: z.ZodEnum<{
711
773
  small: "small";
712
774
  medium: "medium";
@@ -763,6 +825,18 @@ declare const StoredKeys: z.ZodObject<{
763
825
  createdAt: z.ZodNumber;
764
826
  }, z.core.$strict>;
765
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";
766
840
  /** Generate a fresh pair of keypairs and bind them together. */
767
841
  declare function generateKeys(now: number): StoredKeys;
768
842
  /** The public half, for the wire. */
@@ -946,7 +1020,77 @@ declare function signRequest(keys: StoredKeys, input: {
946
1020
  issuedAt: number;
947
1021
  body: string;
948
1022
  }): RequestSignature;
949
- /** Why a signed request was refused. Never returned to the caller verbatim. */
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
+ */
950
1094
  type SignatureFailure = "stale" | "bad-signature";
951
1095
  /**
952
1096
  * Verify a signed request against a runner's pinned identity key.
@@ -964,6 +1108,110 @@ declare function verifyRequest(input: {
964
1108
  maxSkewMs?: number;
965
1109
  }): SignatureFailure | null;
966
1110
 
1111
+ /**
1112
+ * Rotation — byollm_009 Amendment C.
1113
+ *
1114
+ * A site holding identity key **K1** wants to be known by **K2**. It publishes
1115
+ * a *succession*: K2, plus a signature by K1 over a statement naming both key
1116
+ * ids. That signature is the entire mechanism, and the reason rotation can be
1117
+ * automatic without becoming a hole is that **the relay cannot mint one** — it
1118
+ * never holds K1. It is the same trust step a daemon already performs at
1119
+ * pairing, applied to the site's own succession.
1120
+ *
1121
+ * ## Why the statement names both keys
1122
+ *
1123
+ * A signature over K2 alone could be lifted from this site's record and
1124
+ * replayed into another site's, moving *that* site to K2 — a key the attacker
1125
+ * holds. Naming the predecessor binds the succession to one chain, and it is
1126
+ * the reason `verifyLink` takes the id it expects to be succeeding from
1127
+ * rather than reading it out of the statement it is checking.
1128
+ */
1129
+ /** The domain separator. Distinct from every other thing an identity signs. */
1130
+ declare const SUCCESSION_CONTEXT = "byollm/v1/site-succession";
1131
+ /**
1132
+ * How long a retired key may still sign work — Amendment C, ruling 2.
1133
+ *
1134
+ * A protocol constant and not the site's to choose. Per-site overlap
1135
+ * arithmetic is exactly the kind of number that has to mean one thing
1136
+ * everywhere, and a site that could choose it could choose *forever*, which is
1137
+ * a two-key site permanently and a second key nobody ever notices retiring.
1138
+ *
1139
+ * Seven days: long enough that a daemon which polls daily and a laptop shut
1140
+ * for a long weekend both see the new record before the old key stops working,
1141
+ * short enough that "which key is live" is never an interesting question.
1142
+ */
1143
+ declare const RETIREMENT_WINDOW_MS: number;
1144
+ /**
1145
+ * The longest chain a daemon will walk — Amendment C, ruling 1.
1146
+ *
1147
+ * **A denial-of-service guard, not policy.** The bound exists so a projection
1148
+ * cannot make a daemon verify ten thousand signatures, not to express an
1149
+ * opinion about how often a site may rotate. A site that legitimately exceeds
1150
+ * it has a re-pair ahead of it, which is why it is generous: at one rotation a
1151
+ * quarter this is sixteen years.
1152
+ */
1153
+ declare const MAX_SUCCESSION_CHAIN = 64;
1154
+ /** One step of a chain: a key, and the signature by it over its successor. */
1155
+ declare const Succession: z.ZodObject<{
1156
+ identity: z.ZodObject<{
1157
+ identity: z.ZodString;
1158
+ encryption: z.ZodString;
1159
+ encryptionSig: z.ZodString;
1160
+ }, z.core.$strict>;
1161
+ signature: z.ZodString;
1162
+ }, z.core.$strict>;
1163
+ type Succession = z.infer<typeof Succession>;
1164
+ /** The exact bytes signed. One definition; both sides call it. */
1165
+ declare function successionStatement(fromKeyId: string, toKeyId: string): Uint8Array;
1166
+ /**
1167
+ * Sign a succession from the keys being retired to the identity taking over.
1168
+ *
1169
+ * Takes `StoredKeys` for the predecessor because only the holder of K1's
1170
+ * private half can produce this, which is the property the whole design rests
1171
+ * on. A site calls this once, at rotation, on the machine holding its keys.
1172
+ */
1173
+ declare function signSuccession(previous: StoredKeys, next: PublicIdentity): Succession;
1174
+ /**
1175
+ * Check one link: did `link.identity` sign over succeeding to `toKeyId`?
1176
+ *
1177
+ * `toKeyId` is passed in rather than read from anywhere in `link`, and that is
1178
+ * the load-bearing detail. A verifier that recovered the successor from the
1179
+ * signed statement would accept a statement about *any* successor, which is
1180
+ * the replay this design names in C.1 — the signature is genuine, the
1181
+ * successor it names is not the one being installed.
1182
+ */
1183
+ declare function verifyLink(link: Succession, toKeyId: string): boolean;
1184
+ /** Why a chain was refused, in the words a log line uses. */
1185
+ type SuccessionFailure = "no-chain" | "too-long" | "unknown-origin" | "broken-link";
1186
+ interface SuccessionWalk {
1187
+ /** The ids the chain passes through, oldest first, ending at the current. */
1188
+ readonly path: string[];
1189
+ /** The approved id the chain reached, when it reached one. */
1190
+ readonly from?: string;
1191
+ readonly failure?: SuccessionFailure;
1192
+ }
1193
+ /**
1194
+ * Walk a chain from the key being presented back to a key already approved.
1195
+ *
1196
+ * `chain` is ordered oldest last, as the projection carries it — so walking it
1197
+ * means starting at the current key and stepping backwards, each link proving
1198
+ * that its holder signed for the id in front of it.
1199
+ *
1200
+ * Returns the approved id it reached, or why it did not. **Deliberately
1201
+ * returns rather than throws**: a chain that does not verify is ordinary
1202
+ * hostile input, and the caller's job is to keep its existing pin and say so.
1203
+ *
1204
+ * `approved` is asked as a predicate rather than taken as a set because the
1205
+ * daemon's notion of "already approved" includes tombstoned ids — a site that
1206
+ * left the allowlist and came back is still a site this machine has vouched
1207
+ * for, and rotation must not become a way to launder that distinction away.
1208
+ */
1209
+ declare function walkSuccession(input: {
1210
+ current: string;
1211
+ chain: readonly Succession[];
1212
+ approved: (keyId: string) => boolean;
1213
+ }): SuccessionWalk;
1214
+
967
1215
  /**
968
1216
  * The normative MUSTs of protocol v0, as data.
969
1217
  *
@@ -997,12 +1245,45 @@ type MustEnforcer = "daemon" | "server" | "both";
997
1245
  * someone else's, so the kit cannot carry it.
998
1246
  * - `construction` — true by the shape of the code, where a test could only
999
1247
  * sample. A reviewer verifies it; a suite cannot.
1248
+ * ## When a MUST binds both sides — cloud_008 Tier 3
1249
+ *
1250
+ * `AUDIENCE_BOTH_SIDES` says the server and the daemon each enforce. The kit
1251
+ * passed **entirely** with the server's half deleted: every check drove a real
1252
+ * daemon, and a daemon refuses locally, so "the job did not run" looked
1253
+ * identical whichever side refused it. A full-honest-stack test proves only
1254
+ * the conjunction.
1255
+ *
1256
+ * So a `both`-enforced MUST needs **one check per party, each with the honest
1257
+ * counterpart removed** — C032 claims over the raw protocol precisely so no
1258
+ * daemon admission logic runs. Where a check strips one side, its comment
1259
+ * says which; where a MUST is enforced by both and only one side is checked,
1260
+ * that is a gap rather than coverage.
1261
+ *
1000
1262
  * - `operator` — a claim about how someone runs a deployment, verifiable only
1001
1263
  * by audit or by reading source. The honest category, and the one that
1002
1264
  * exists so a property nobody can check from outside is *labelled* as such
1003
1265
  * rather than laundered by association with the checkable ones.
1004
1266
  */
1005
1267
  type MustVerification = "conformance" | "adversarial" | "construction" | "operator";
1268
+ /**
1269
+ * How a MUST is verified — one kind, or several.
1270
+ *
1271
+ * Several is not hedging. `SITES_LOCALLY_APPROVED` is the case that forced it:
1272
+ * the fence is **construction** — a daemon cannot serve a site that is not in
1273
+ * its map, and admission refuses before a payload is fetched — while the
1274
+ * property that a *removed and re-offered* id is still refused needs a hostile
1275
+ * sequence of heartbeats no honest client would send, which is
1276
+ * **adversarial**. Recording one and dropping the other would either overstate
1277
+ * what a type check proves or understate what the suites do.
1278
+ *
1279
+ * The alternative was a second field for the second kind, which is two answers
1280
+ * to one question — the shape this project keeps deleting.
1281
+ */
1282
+ type MustVerifiedBy = MustVerification | readonly [MustVerification, ...MustVerification[]];
1283
+ /** The kinds a MUST claims, always as a list. */
1284
+ declare function kindsOf(must: {
1285
+ readonly verifiedBy: MustVerifiedBy;
1286
+ }): readonly MustVerification[];
1006
1287
  /** A single normative requirement of the protocol. */
1007
1288
  interface Must {
1008
1289
  /** Stable public id, cited by conformance output. */
@@ -1015,7 +1296,7 @@ interface Must {
1015
1296
  * How this is verified. `conformance` is the only kind the kit can assert;
1016
1297
  * see {@link MustVerification} for why the others exist.
1017
1298
  */
1018
- readonly verifiedBy: MustVerification;
1299
+ readonly verifiedBy: MustVerifiedBy;
1019
1300
  /** Spec section this was adjudicated in. */
1020
1301
  readonly source: string;
1021
1302
  }
@@ -1032,6 +1313,8 @@ declare const MUSTS: Readonly<{
1032
1313
  readonly PAIR_INTERACTIVE: Must;
1033
1314
  readonly PAIR_CODE_EXPIRES: Must;
1034
1315
  readonly VERSION_HANDSHAKE_REQUIRED: Must;
1316
+ readonly SITE_KEY_BY_STUB: Must;
1317
+ readonly SITES_LOCALLY_APPROVED: Must;
1035
1318
  readonly KEYS_EXCHANGED_AT_CONSENT: Must;
1036
1319
  readonly REQUESTS_SIGNED_NOT_BEARER: Must;
1037
1320
  readonly LEASE_SCOPED_BY_GRANT: Must;
@@ -1058,7 +1341,7 @@ declare const MUSTS: Readonly<{
1058
1341
  readonly TTL_EXPIRY: Must;
1059
1342
  readonly NO_RUNNER_SIGNAL: Must;
1060
1343
  readonly RESULT_IDEMPOTENT: Must;
1061
- readonly RESULT_PROVENANCE: Must;
1344
+ readonly PROVENANCE_NAMES_DEVICE: Must;
1062
1345
  readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
1063
1346
  readonly NO_SHELL_INTERPOLATION: Must;
1064
1347
  readonly NO_PAYLOAD_ROUTING: Must;
@@ -1066,11 +1349,18 @@ declare const MUSTS: Readonly<{
1066
1349
  readonly HTTP_BASE_URL_SAFE: Must;
1067
1350
  readonly OUTPUT_INERT: Must;
1068
1351
  readonly COMMUNITY_BUDGETS: Must;
1352
+ readonly REVOCATION_IMMEDIATE: Must;
1353
+ readonly CONSENT_BEFORE_ROUTE: Must;
1354
+ readonly ROSTER_NOT_DISCLOSED: Must;
1355
+ readonly EFFECTIVE_OFFER_ONLY: Must;
1356
+ readonly FALLBACK_LABELED: Must;
1357
+ readonly RELAY_BLIND: Must;
1358
+ readonly SHARED_COMPUTE_DISCLOSED: Must;
1069
1359
  }>;
1070
1360
  /** The id of any normative MUST. */
1071
1361
  type MustId = keyof typeof MUSTS;
1072
1362
  /** 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")[];
1363
+ 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
1364
  /** Every MUST verified a particular way. */
1075
1365
  declare function mustsVerifiedBy(kind: MustVerification): MustId[];
1076
1366
 
@@ -1100,6 +1390,23 @@ interface VersionRefusal {
1100
1390
  readonly supported: readonly string[];
1101
1391
  readonly minimum: string;
1102
1392
  }
1393
+ /**
1394
+ * The version a request declares, wherever it carries it.
1395
+ *
1396
+ * A POST declares it in its body, which is where every request schema has
1397
+ * always put it. A GET has no body, and the relay has one — the site plane's
1398
+ * `pending` read — so it declares it in the query string instead.
1399
+ *
1400
+ * **Two carriers, one rule.** That asymmetry is HTTP's rather than ours, and
1401
+ * the alternative was worse in both directions: a header for everything would
1402
+ * change every existing daemon's request, and skipping GETs would leave an
1403
+ * endpoint outside the handshake — which is precisely the shape B.4 found,
1404
+ * where a whole plane was outside it.
1405
+ */
1406
+ declare function declaredVersion(input: {
1407
+ body?: unknown;
1408
+ query?: URLSearchParams;
1409
+ }): unknown;
1103
1410
  /**
1104
1411
  * Check the protocol version on an incoming request
1105
1412
  * ({@link MUSTS.VERSION_HANDSHAKE_REQUIRED}).
@@ -1298,15 +1605,14 @@ declare const PairPollResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
1298
1605
  status: z.ZodLiteral<"expired">;
1299
1606
  }, z.core.$strict>, z.ZodObject<{
1300
1607
  status: z.ZodLiteral<"approved">;
1301
- runnerToken: z.ZodString;
1302
1608
  runnerId: z.ZodString;
1303
1609
  owner: z.ZodString;
1304
1610
  ownerLabel: z.ZodOptional<z.ZodString>;
1305
- site: z.ZodObject<{
1611
+ sites: z.ZodRecord<z.ZodString, z.ZodObject<{
1306
1612
  identity: z.ZodString;
1307
1613
  encryption: z.ZodString;
1308
1614
  encryptionSig: z.ZodString;
1309
- }, z.core.$strict>;
1615
+ }, z.core.$strict>>;
1310
1616
  }, z.core.$strict>], "status">;
1311
1617
  type PairPollResponse = z.infer<typeof PairPollResponse>;
1312
1618
  declare const PairRequest: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -1418,12 +1724,12 @@ declare const ClaimResponse: z.ZodObject<{
1418
1724
  "llm.chat": "llm.chat";
1419
1725
  }>;
1420
1726
  owner: z.ZodString;
1727
+ site: z.ZodString;
1421
1728
  audience: z.ZodEnum<{
1422
1729
  self: "self";
1423
1730
  named: "named";
1424
1731
  public: "public";
1425
1732
  }>;
1426
- audienceAllow: z.ZodOptional<z.ZodArray<z.ZodString>>;
1427
1733
  sizeClass: z.ZodEnum<{
1428
1734
  small: "small";
1429
1735
  medium: "medium";
@@ -1489,14 +1795,32 @@ declare const HeartbeatRequest: z.ZodObject<{
1489
1795
  }, z.core.$strict>;
1490
1796
  type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;
1491
1797
  declare const HeartbeatResponse: z.ZodObject<{
1492
- revoked: z.ZodBoolean;
1493
- cancel: z.ZodArray<z.ZodString>;
1494
- leases: z.ZodArray<z.ZodObject<{
1798
+ sites: z.ZodRecord<z.ZodString, z.ZodObject<{
1799
+ identity: z.ZodString;
1800
+ encryption: z.ZodString;
1801
+ encryptionSig: z.ZodString;
1802
+ }, z.core.$strict>>;
1803
+ successions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
1804
+ succeeds: z.ZodArray<z.ZodObject<{
1805
+ identity: z.ZodObject<{
1806
+ identity: z.ZodString;
1807
+ encryption: z.ZodString;
1808
+ encryptionSig: z.ZodString;
1809
+ }, z.core.$strict>;
1810
+ signature: z.ZodString;
1811
+ }, z.core.$strict>>;
1812
+ retiringUntil: z.ZodOptional<z.ZodNumber>;
1813
+ }, z.core.$strict>>>;
1814
+ cancel: z.ZodArray<z.ZodObject<{
1495
1815
  jobId: z.ZodString;
1496
- expiresAt: z.ZodNumber;
1816
+ leaseId: z.ZodString;
1817
+ }, z.core.$strict>>;
1818
+ lost: z.ZodArray<z.ZodObject<{
1819
+ jobId: z.ZodString;
1820
+ leaseId: z.ZodString;
1497
1821
  }, z.core.$strict>>;
1498
- lost: z.ZodArray<z.ZodString>;
1499
1822
  serverTime: z.ZodNumber;
1823
+ awaitingConsent: z.ZodArray<z.ZodString>;
1500
1824
  }, z.core.$strict>;
1501
1825
  type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
1502
1826
  /**
@@ -1521,6 +1845,7 @@ declare const ResultRequest: z.ZodObject<{
1521
1845
  protocolVersion: z.ZodLiteral<"0">;
1522
1846
  runnerId: z.ZodString;
1523
1847
  jobId: z.ZodString;
1848
+ leaseId: z.ZodString;
1524
1849
  envelope: z.ZodObject<{
1525
1850
  ciphertext: z.ZodString;
1526
1851
  recipientKeyId: z.ZodString;
@@ -1536,16 +1861,11 @@ declare const ResultRequest: z.ZodObject<{
1536
1861
  error: "error";
1537
1862
  canceled: "canceled";
1538
1863
  }>;
1539
- model: z.ZodString;
1540
- backendClass: z.ZodEnum<{
1541
- http: "http";
1542
- process: "process";
1543
- }>;
1544
- durationMs: z.ZodNumber;
1545
1864
  }, z.core.$strict>;
1546
1865
  type ResultRequest = z.infer<typeof ResultRequest>;
1547
1866
  declare const ResultResponse: z.ZodObject<{
1548
1867
  accepted: z.ZodBoolean;
1868
+ duplicate: z.ZodOptional<z.ZodBoolean>;
1549
1869
  state: z.ZodString;
1550
1870
  }, z.core.$strict>;
1551
1871
  type ResultResponse = z.infer<typeof ResultResponse>;
@@ -1557,9 +1877,9 @@ declare const ReleaseRequest: z.ZodObject<{
1557
1877
  leaseId: z.ZodString;
1558
1878
  }, z.core.$strip>>;
1559
1879
  reason: z.ZodEnum<{
1560
- revoked: "revoked";
1561
1880
  shutdown: "shutdown";
1562
1881
  pause: "pause";
1882
+ revoked: "revoked";
1563
1883
  "backend-down": "backend-down";
1564
1884
  refused: "refused";
1565
1885
  }>;
@@ -1582,7 +1902,11 @@ declare const WireErrorCode: z.ZodEnum<{
1582
1902
  revoked: "revoked";
1583
1903
  "bad-request": "bad-request";
1584
1904
  unauthorized: "unauthorized";
1905
+ forbidden: "forbidden";
1585
1906
  "not-found": "not-found";
1907
+ "not-ready": "not-ready";
1908
+ "too-late": "too-late";
1909
+ "clock-skew": "clock-skew";
1586
1910
  "rate-limited": "rate-limited";
1587
1911
  "server-error": "server-error";
1588
1912
  }>;
@@ -1593,18 +1917,26 @@ declare const WireError: z.ZodObject<{
1593
1917
  revoked: "revoked";
1594
1918
  "bad-request": "bad-request";
1595
1919
  unauthorized: "unauthorized";
1920
+ forbidden: "forbidden";
1596
1921
  "not-found": "not-found";
1922
+ "not-ready": "not-ready";
1923
+ "too-late": "too-late";
1924
+ "clock-skew": "clock-skew";
1597
1925
  "rate-limited": "rate-limited";
1598
1926
  "server-error": "server-error";
1599
1927
  }>;
1600
1928
  message: z.ZodString;
1929
+ supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
1930
+ minimum: z.ZodOptional<z.ZodString>;
1601
1931
  retryAfter: z.ZodOptional<z.ZodNumber>;
1932
+ serverTime: z.ZodOptional<z.ZodNumber>;
1933
+ maxSkewMs: z.ZodOptional<z.ZodNumber>;
1602
1934
  }, z.core.$strict>;
1603
1935
  type WireError = z.infer<typeof WireError>;
1604
1936
  /** HTTP status each error code is served with. */
1605
1937
  declare const ERROR_STATUS: Readonly<Record<WireErrorCode, number>>;
1606
1938
  declare const FetchRequest: z.ZodObject<{
1607
- protocolVersion: z.ZodString;
1939
+ protocolVersion: z.ZodLiteral<"0">;
1608
1940
  runnerId: z.ZodString;
1609
1941
  jobId: z.ZodString;
1610
1942
  leaseId: z.ZodString;
@@ -1624,4 +1956,4 @@ declare const FetchResponse: z.ZodObject<{
1624
1956
  }, z.core.$strict>;
1625
1957
  type FetchResponse = z.infer<typeof FetchResponse>;
1626
1958
 
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 };
1959
+ 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 };