@byollm/protocol 0.1.0-alpha.14 → 0.1.0-alpha.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  > [!WARNING]
2
- > **Alpha (`0.1.0-alpha.14`) — under active development. Don't use this yet.**
2
+ > **Alpha (`0.1.0-alpha.16`) — under active development. Don't use this yet.**
3
3
  >
4
4
  > Install it deliberately: `npm install @byollm/protocol@alpha`.
5
5
  >
@@ -11,7 +11,34 @@
11
11
  > npm assigns `latest` on a first publish and won't let it be removed, so a
12
12
  > bare install resolves here too. This notice is the only guard — deliberately
13
13
  > not an npm deprecation, which would read as *abandoned* rather than *early*.
14
- > Ask for `@alpha` explicitly so your lockfile records that you meant to.
14
+ > Ask for `@alpha` explicitly so your lockfile records that you meant to.>
15
+ > **`alpha.15` is a breaking wire change, and it breaks daemons and relays —
16
+ > not app authors.** If you call `app.enqueue(...)` and read results, nothing
17
+ > in your code changes. If you run a daemon or an upstream, every package must
18
+ > move together: a mixed pair refuses on both sides, because both ends parse
19
+ > `.strict()`.
20
+ >
21
+ > What moved, all of it reconciling the frozen `byollm_009` with its code:
22
+ > `JobStub` gains `site` (the site's identity key id) and loses
23
+ > `audienceAllow`; `ResultRequest` gains `leaseId`; `HeartbeatResponse` loses
24
+ > `leases`, which nothing read; `WireErrorCode` gains `not-ready`,
25
+ > `clock-skew` and `forbidden`, and `403` is `forbidden` rather than
26
+ > `unauthorized`. `RESULT_PROVENANCE` is superseded by
27
+ > `PROVENANCE_NAMES_DEVICE`. See `byollm_009` Amendment A.>
28
+ > **`alpha.16` is a breaking wire change — daemons and relays again, not app
29
+ > authors.** `app.enqueue(...)` and reading results are unchanged. All five
30
+ > packages move together: both ends parse `.strict()`, so a mixed pair
31
+ > refuses.
32
+ >
33
+ > What moved, all of it Tier 2 of `cloud_008`: `model`, `backendClass` and
34
+ > `durationMs` come off `ResultRequest` and are sealed **inside** the result
35
+ > envelope as `SealedOutcome = { outcome, ran }` — so a daemon can no longer
36
+ > declare a model it did not sign, and a relay carries neither.
37
+ > `HeartbeatResponse` loses `leases` (nothing read it) and now reports real
38
+ > cancellations instead of an empty list. `WireErrorCode` gains `forbidden`
39
+ > for 403, leaving `unauthorized` at exactly 401. The relay gained a
40
+ > site-plane `cancel` endpoint, honours `stub.deadlineAt`, honours
41
+ > `stub.audience`, and remembers a refusal.
15
42
  >
16
43
  > **`alpha.3` is a breaking change.** `BackendDescriptor.account` is gone —
17
44
  > read `cost` (`free` / `metered` / `subscription`) instead. Four new MUSTs
package/dist/index.d.ts CHANGED
@@ -515,7 +515,7 @@ type ClaimedJob = z.infer<typeof ClaimedJob>;
515
515
  *
516
516
  * byollm_003 Rev 1: a `named`/`public` result is attacker-controlled text.
517
517
  * The app must never render volunteer output as its own AI's answer without
518
- * knowing that is what it is ({@link MUSTS.RESULT_PROVENANCE}).
518
+ * knowing that is what it is ({@link MUSTS.PROVENANCE_NAMES_DEVICE}).
519
519
  */
520
520
  declare const ResultProvenance: z.ZodObject<{
521
521
  audience: z.ZodEnum<{
@@ -544,6 +544,30 @@ declare function provenanceFor(input: {
544
544
  backendClass: BackendClass;
545
545
  model: string;
546
546
  }): ResultProvenance;
547
+ /**
548
+ * What the daemon did, sealed with the answer — cloud_008 §2.5.
549
+ *
550
+ * These travelled in the clear on `ResultRequest`, which meant two things at
551
+ * once. On the direct plane the site believed unauthenticated fields beside
552
+ * an authenticated envelope — a daemon could seal one answer and *declare* it
553
+ * came from a different model, and only the field it did not sign would be
554
+ * recorded. Through a relay they reached a third party that acts on none of
555
+ * them, and `model` in particular is the kind of detail Amendment A's rule
556
+ * keeps off the wire.
557
+ *
558
+ * Sealed, they are the daemon's signed statement about its own run: the site
559
+ * opens them, nothing in between sees them, and the disposition check that
560
+ * already compares clear-text against ciphertext extends to cover them.
561
+ */
562
+ declare const RunMetadata: z.ZodObject<{
563
+ model: z.ZodString;
564
+ backendClass: z.ZodEnum<{
565
+ http: "http";
566
+ process: "process";
567
+ }>;
568
+ durationMs: z.ZodNumber;
569
+ }, z.core.$strict>;
570
+ type RunMetadata = z.infer<typeof RunMetadata>;
547
571
  /** Successful outcome. */
548
572
  declare const JobResultOk: z.ZodObject<{
549
573
  outcome: z.ZodLiteral<"ok">;
@@ -574,6 +598,37 @@ declare const JobOutcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
574
598
  outcome: z.ZodLiteral<"canceled">;
575
599
  }, z.core.$strict>], "outcome">;
576
600
  type JobOutcome = z.infer<typeof JobOutcome>;
601
+ /**
602
+ * The plaintext inside a result envelope.
603
+ *
604
+ * The outcome and how it was produced, together, because they are one
605
+ * statement by one signer. A site that opened only the outcome would be
606
+ * trusting the envelope for the answer and the request body for everything
607
+ * about it.
608
+ */
609
+ declare const SealedOutcome: z.ZodObject<{
610
+ outcome: z.ZodDiscriminatedUnion<[z.ZodObject<{
611
+ outcome: z.ZodLiteral<"ok">;
612
+ text: z.ZodString;
613
+ artifactUrl: z.ZodOptional<z.ZodURL>;
614
+ }, z.core.$strict>, z.ZodObject<{
615
+ outcome: z.ZodLiteral<"error">;
616
+ code: z.ZodString;
617
+ message: z.ZodString;
618
+ retryable: z.ZodBoolean;
619
+ }, z.core.$strict>, z.ZodObject<{
620
+ outcome: z.ZodLiteral<"canceled">;
621
+ }, z.core.$strict>], "outcome">;
622
+ ran: z.ZodObject<{
623
+ model: z.ZodString;
624
+ backendClass: z.ZodEnum<{
625
+ http: "http";
626
+ process: "process";
627
+ }>;
628
+ durationMs: z.ZodNumber;
629
+ }, z.core.$strict>;
630
+ }, z.core.$strict>;
631
+ type SealedOutcome = z.infer<typeof SealedOutcome>;
577
632
  /** A completed job as delivered to the app, provenance attached. */
578
633
  declare const DeliveredResult: z.ZodObject<{
579
634
  jobId: z.ZodString;
@@ -676,6 +731,7 @@ declare const JobStub: z.ZodObject<{
676
731
  "llm.chat": "llm.chat";
677
732
  }>;
678
733
  owner: z.ZodString;
734
+ site: z.ZodString;
679
735
  audience: z.ZodEnum<{
680
736
  self: "self";
681
737
  named: "named";
@@ -699,6 +755,7 @@ declare const ClaimedStub: z.ZodObject<{
699
755
  "llm.chat": "llm.chat";
700
756
  }>;
701
757
  owner: z.ZodString;
758
+ site: z.ZodString;
702
759
  audience: z.ZodEnum<{
703
760
  self: "self";
704
761
  named: "named";
@@ -1125,7 +1182,7 @@ declare const MUSTS: Readonly<{
1125
1182
  readonly TTL_EXPIRY: Must;
1126
1183
  readonly NO_RUNNER_SIGNAL: Must;
1127
1184
  readonly RESULT_IDEMPOTENT: Must;
1128
- readonly RESULT_PROVENANCE: Must;
1185
+ readonly PROVENANCE_NAMES_DEVICE: Must;
1129
1186
  readonly INGRESS_LOGGED_BEFORE_EXECUTION: Must;
1130
1187
  readonly NO_SHELL_INTERPOLATION: Must;
1131
1188
  readonly NO_PAYLOAD_ROUTING: Must;
@@ -1133,11 +1190,18 @@ declare const MUSTS: Readonly<{
1133
1190
  readonly HTTP_BASE_URL_SAFE: Must;
1134
1191
  readonly OUTPUT_INERT: Must;
1135
1192
  readonly COMMUNITY_BUDGETS: Must;
1193
+ readonly REVOCATION_IMMEDIATE: Must;
1194
+ readonly CONSENT_BEFORE_ROUTE: Must;
1195
+ readonly ROSTER_NOT_DISCLOSED: Must;
1196
+ readonly EFFECTIVE_OFFER_ONLY: Must;
1197
+ readonly FALLBACK_LABELED: Must;
1198
+ readonly RELAY_BLIND: Must;
1199
+ readonly SHARED_COMPUTE_DISCLOSED: Must;
1136
1200
  }>;
1137
1201
  /** The id of any normative MUST. */
1138
1202
  type MustId = keyof typeof MUSTS;
1139
1203
  /** All MUST ids, for coverage checks. */
1140
- 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")[];
1204
+ 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" | "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")[];
1141
1205
  /** Every MUST verified a particular way. */
1142
1206
  declare function mustsVerifiedBy(kind: MustVerification): MustId[];
1143
1207
 
@@ -1485,6 +1549,7 @@ declare const ClaimResponse: z.ZodObject<{
1485
1549
  "llm.chat": "llm.chat";
1486
1550
  }>;
1487
1551
  owner: z.ZodString;
1552
+ site: z.ZodString;
1488
1553
  audience: z.ZodEnum<{
1489
1554
  self: "self";
1490
1555
  named: "named";
@@ -1557,10 +1622,6 @@ type HeartbeatRequest = z.infer<typeof HeartbeatRequest>;
1557
1622
  declare const HeartbeatResponse: z.ZodObject<{
1558
1623
  revoked: z.ZodBoolean;
1559
1624
  cancel: z.ZodArray<z.ZodString>;
1560
- leases: z.ZodArray<z.ZodObject<{
1561
- jobId: z.ZodString;
1562
- expiresAt: z.ZodNumber;
1563
- }, z.core.$strict>>;
1564
1625
  lost: z.ZodArray<z.ZodString>;
1565
1626
  serverTime: z.ZodNumber;
1566
1627
  }, z.core.$strict>;
@@ -1587,6 +1648,7 @@ declare const ResultRequest: z.ZodObject<{
1587
1648
  protocolVersion: z.ZodLiteral<"0">;
1588
1649
  runnerId: z.ZodString;
1589
1650
  jobId: z.ZodString;
1651
+ leaseId: z.ZodString;
1590
1652
  envelope: z.ZodObject<{
1591
1653
  ciphertext: z.ZodString;
1592
1654
  recipientKeyId: z.ZodString;
@@ -1602,12 +1664,6 @@ declare const ResultRequest: z.ZodObject<{
1602
1664
  error: "error";
1603
1665
  canceled: "canceled";
1604
1666
  }>;
1605
- model: z.ZodString;
1606
- backendClass: z.ZodEnum<{
1607
- http: "http";
1608
- process: "process";
1609
- }>;
1610
- durationMs: z.ZodNumber;
1611
1667
  }, z.core.$strict>;
1612
1668
  type ResultRequest = z.infer<typeof ResultRequest>;
1613
1669
  declare const ResultResponse: z.ZodObject<{
@@ -1648,7 +1704,10 @@ declare const WireErrorCode: z.ZodEnum<{
1648
1704
  revoked: "revoked";
1649
1705
  "bad-request": "bad-request";
1650
1706
  unauthorized: "unauthorized";
1707
+ forbidden: "forbidden";
1651
1708
  "not-found": "not-found";
1709
+ "not-ready": "not-ready";
1710
+ "clock-skew": "clock-skew";
1652
1711
  "rate-limited": "rate-limited";
1653
1712
  "server-error": "server-error";
1654
1713
  }>;
@@ -1659,12 +1718,17 @@ declare const WireError: z.ZodObject<{
1659
1718
  revoked: "revoked";
1660
1719
  "bad-request": "bad-request";
1661
1720
  unauthorized: "unauthorized";
1721
+ forbidden: "forbidden";
1662
1722
  "not-found": "not-found";
1723
+ "not-ready": "not-ready";
1724
+ "clock-skew": "clock-skew";
1663
1725
  "rate-limited": "rate-limited";
1664
1726
  "server-error": "server-error";
1665
1727
  }>;
1666
1728
  message: z.ZodString;
1667
1729
  retryAfter: z.ZodOptional<z.ZodNumber>;
1730
+ serverTime: z.ZodOptional<z.ZodNumber>;
1731
+ maxSkewMs: z.ZodOptional<z.ZodNumber>;
1668
1732
  }, z.core.$strict>;
1669
1733
  type WireError = z.infer<typeof WireError>;
1670
1734
  /** HTTP status each error code is served with. */
@@ -1690,4 +1754,4 @@ declare const FetchResponse: z.ZodObject<{
1690
1754
  }, z.core.$strict>;
1691
1755
  type FetchResponse = z.infer<typeof FetchResponse>;
1692
1756
 
1693
- 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, signSiteRequest, signWith, sizeClassCeiling, sizeClassOf, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith };
1757
+ 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, 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, effectiveOfferScope, fingerprint, generateKeys, isBackendId, isJobKind, isLocalHost, isTerminal, keyId, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signRequest, signSiteRequest, signWith, sizeClassCeiling, sizeClassOf, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith };
package/dist/index.js CHANGED
@@ -411,6 +411,13 @@ function provenanceFor(input) {
411
411
  untrusted: input.audience !== "self"
412
412
  };
413
413
  }
414
+ var RunMetadata = z4.object({
415
+ /** Which model actually served it. */
416
+ model: z4.string().min(1),
417
+ backendClass: BackendClass,
418
+ /** Wall-clock milliseconds the backend call took. */
419
+ durationMs: z4.number().int().nonnegative()
420
+ }).strict();
414
421
  var JobResultOk = z4.object({
415
422
  outcome: z4.literal("ok"),
416
423
  text: z4.string(),
@@ -432,6 +439,7 @@ var JobOutcome = z4.discriminatedUnion("outcome", [
432
439
  JobResultError,
433
440
  JobResultCanceled
434
441
  ]);
442
+ var SealedOutcome = z4.object({ outcome: JobOutcome, ran: RunMetadata }).strict();
435
443
  var DeliveredResult = z4.object({
436
444
  jobId: z4.string().min(1),
437
445
  state: JobState,
@@ -458,6 +466,30 @@ var JobStub = z4.object({
458
466
  kind: JobKind,
459
467
  /** The app's id for the user who enqueued it. */
460
468
  owner: z4.string().min(1),
469
+ /**
470
+ * Which site this job belongs to — byollm_009 Amendment A §A.3.
471
+ *
472
+ * **The site's identity key id**, not an id somebody assigned it. §6 has
473
+ * listed `site` since this spec was frozen; the schema never carried it,
474
+ * which is the drift the amendment closes.
475
+ *
476
+ * A key id rather than an opaque handle for one reason above the others:
477
+ * it makes the stub *self-describing* instead of a pointer into somebody
478
+ * else's table. A daemon holds this key id already, from pinning, so it
479
+ * can check `stub.site` against the payload envelope's `senderKeyId`
480
+ * without a lookup and without trusting the party that routed it. An
481
+ * opaque id can only be believed.
482
+ *
483
+ * It also avoids inventing a second namespace for a thing that has a
484
+ * canonical one — the shape of finding 41 (two owner namespaces compared
485
+ * for equality) and of finding fourteen before it.
486
+ *
487
+ * Rotation is a designed transition rather than a cost: a site publishes a
488
+ * new identity signed by the outgoing one, both are valid through an
489
+ * overlap window, and a daemon re-keys its own map by verifying that
490
+ * signature against the key it already pinned (§A.3.1).
491
+ */
492
+ site: z4.string().min(1),
461
493
  audience: Audience,
462
494
  // `audienceAllow` is **not** here, and its absence is the enforcement —
463
495
  // cloud_008 §0.2.
@@ -1020,12 +1052,12 @@ var MUSTS = Object.freeze({
1020
1052
  verifiedBy: "conformance",
1021
1053
  source: "byollm_001 \xA7Endpoints.4"
1022
1054
  }),
1023
- RESULT_PROVENANCE: must({
1024
- id: "RESULT_PROVENANCE",
1025
- statement: "A result from a non-'self' job MUST carry its provenance (audience and runner) to the delivery seam so an app never treats volunteer output as first-party.",
1055
+ PROVENANCE_NAMES_DEVICE: must({
1056
+ id: "PROVENANCE_NAMES_DEVICE",
1057
+ statement: "A result MUST carry the claiming device's key id and its relationship to the requester, to the delivery seam, so an app never treats volunteer output as first-party. The key id MUST be the device the upstream granted the lease to, and a result whose signature does not verify against that device MUST be refused rather than recorded.",
1026
1058
  enforcedBy: "server",
1027
1059
  verifiedBy: "conformance",
1028
- source: "byollm_003 Rev 1 \xA7Return-trip"
1060
+ source: "byollm_009 \xA711"
1029
1061
  }),
1030
1062
  // ---- The trust surface -------------------------------------------------
1031
1063
  INGRESS_LOGGED_BEFORE_EXECUTION: must({
@@ -1077,8 +1109,92 @@ var MUSTS = Object.freeze({
1077
1109
  enforcedBy: "daemon",
1078
1110
  verifiedBy: "adversarial",
1079
1111
  source: "byollm_004 \xA74"
1112
+ }),
1113
+ REVOCATION_IMMEDIATE: must({
1114
+ id: "REVOCATION_IMMEDIATE",
1115
+ statement: "Revocation MUST take effect at the upstream at once \u2014 a revoked runner MUST NOT be granted further work from the moment the record changes \u2014 and MUST reach the daemon by its next heartbeat.",
1116
+ // Both, and stated as one sentence with two obligations rather than
1117
+ // folded into REVOCATION_HONORED. That one binds the *daemon*: a revoked
1118
+ // daemon stops claiming and abandons in-flight work. This binds the
1119
+ // *upstream*. byollm_009 §5 is explicit that the pair is the point — "a
1120
+ // revocation enforced at one end survives a compromise of that end" — and
1121
+ // one entry covering both would make a compromised daemon look compliant.
1122
+ enforcedBy: "both",
1123
+ verifiedBy: "conformance",
1124
+ source: "byollm_009 \xA711"
1125
+ }),
1126
+ CONSENT_BEFORE_ROUTE: must({
1127
+ id: "CONSENT_BEFORE_ROUTE",
1128
+ statement: "An upstream MUST NOT route a job to a device without a record binding that user, that site and that scope. There MUST be no discovery path by which a device receives work it was never granted.",
1129
+ enforcedBy: "server",
1130
+ verifiedBy: "conformance",
1131
+ source: "byollm_009 \xA711"
1132
+ }),
1133
+ ROSTER_NOT_DISCLOSED: must({
1134
+ id: "ROSTER_NOT_DISCLOSED",
1135
+ statement: "A site MUST NOT learn the membership of a group whose compute it uses, and MUST NOT publish membership to a routing party. No wire message may carry a list of who may run a job.",
1136
+ // Checkable since cloud_008 §0.2 took `audienceAllow` off the stub: the
1137
+ // property now holds by *absence*, and absence is exactly what a strict
1138
+ // schema and a serialised stub can be asked about. Before that it was a
1139
+ // sentence — and one this project cited in code comments, tests and two
1140
+ // specs as though it were enforced data, which is why it is worth
1141
+ // stating precisely rather than generously.
1142
+ enforcedBy: "both",
1143
+ verifiedBy: "conformance",
1144
+ source: "byollm_009 \xA711"
1145
+ }),
1146
+ EFFECTIVE_OFFER_ONLY: must({
1147
+ id: "EFFECTIVE_OFFER_ONLY",
1148
+ statement: "A daemon MUST declare effective offers only. An upstream MUST NOT receive raw config, allowlists, or capacity the owner has not shared, and MUST act on the declared offer rather than on what was asked for.",
1149
+ enforcedBy: "both",
1150
+ verifiedBy: "conformance",
1151
+ source: "byollm_009 \xA711"
1152
+ }),
1153
+ FALLBACK_LABELED: must({
1154
+ id: "FALLBACK_LABELED",
1155
+ statement: "Work served by anything other than the user's own compute MUST be labelled as such wherever it is reported, and MUST NOT be silently substituted.",
1156
+ // `construction` today, and deliberately not `conformance`. Nothing on
1157
+ // the wire yet distinguishes a fallback from any other community job —
1158
+ // the ledger that would give it a surface is unbuilt — so a check would
1159
+ // have to assert something it cannot observe. Promoted the day that
1160
+ // surface exists. Marking it `conformance` now would put "verified"
1161
+ // beside a property no third party can see, which is the one thing the
1162
+ // kinds exist to prevent.
1163
+ enforcedBy: "both",
1164
+ verifiedBy: "construction",
1165
+ source: "byollm_009 \xA711"
1166
+ }),
1167
+ RELAY_BLIND: must({
1168
+ id: "RELAY_BLIND",
1169
+ statement: "A relay MUST NOT hold any key capable of decrypting a payload, a result, or a delta frame.",
1170
+ // Operator: a third party can read the relay's types and see there is
1171
+ // nowhere to put such a key, but the kit certifies a *server* and cannot
1172
+ // reach inside somebody's deployment to prove what it holds.
1173
+ enforcedBy: "server",
1174
+ verifiedBy: "operator",
1175
+ source: "byollm_009 \xA711"
1176
+ }),
1177
+ SHARED_COMPUTE_DISCLOSED: must({
1178
+ id: "SHARED_COMPUTE_DISCLOSED",
1179
+ statement: "Before a user's work first runs on compute they do not own, they MUST be told in plain language that the machine's owner can see it.",
1180
+ // Operator, and cloud_008 §0.3 is why the classification now comes with a
1181
+ // standing answer rather than a standing question. The screen is not
1182
+ // wire-observable, but the *string the server composes* is, and it is
1183
+ // now unit-tested with the two false sentences forbidden by name. The
1184
+ // kind stays `operator` because a third-party site can still render
1185
+ // whatever it likes; what changed is that the part inside our own
1186
+ // boundary stopped depending on somebody remembering to audit it.
1187
+ enforcedBy: "server",
1188
+ verifiedBy: "operator",
1189
+ source: "byollm_009 \xA711"
1080
1190
  })
1081
1191
  });
1192
+ var RETIRED_MUSTS = Object.freeze({
1193
+ RESULT_PROVENANCE: {
1194
+ supersededBy: "PROVENANCE_NAMES_DEVICE",
1195
+ note: "Strengthened, not renamed: attribution is now by proof of possession \u2014 the result's signature must verify against the device the upstream granted the lease to \u2014 rather than by a provenance label travelling beside it. byollm_009 \xA711 states the stronger form."
1196
+ }
1197
+ });
1082
1198
  var MUST_IDS = Object.freeze(Object.keys(MUSTS));
1083
1199
  function mustsVerifiedBy(kind) {
1084
1200
  return MUST_IDS.filter((id) => MUSTS[id].verifiedBy === kind);
@@ -1233,13 +1349,26 @@ var HeartbeatResponse = z8.object({
1233
1349
  * in-flight backend calls and reports them `canceled`.
1234
1350
  */
1235
1351
  cancel: z8.array(z8.string().min(1)),
1236
- /** Jobs whose leases were renewed, with their new expiry. */
1237
- leases: z8.array(
1238
- z8.object({
1239
- jobId: z8.string().min(1),
1240
- expiresAt: z8.number().int().positive()
1241
- }).strict()
1242
- ),
1352
+ // `leases` is deliberately absent cloud_008 §1.4b, finding 16.
1353
+ //
1354
+ // It carried "these leases were renewed, and here is the new expiry", and
1355
+ // **no daemon ever read it.** A mutation returning an empty list while
1356
+ // renewing correctly survived every test, which is what made it visible.
1357
+ //
1358
+ // It is neither a class nor membership, so Amendment A's rule does not
1359
+ // decide it — the older test does: nothing reads it, so it is dead wire.
1360
+ // §6's exhaustiveness is a commitment about what an upstream can see, and
1361
+ // it applies to every message rather than only to the stub.
1362
+ //
1363
+ // `lost` is the actionable signal and always was: a daemon stops work on
1364
+ // a lease it no longer holds. "Renewed" was the same question answered a
1365
+ // second time, and a second answer can only agree or contradict.
1366
+ //
1367
+ // Renewal itself is untouched — the upstream still extends the grants a
1368
+ // heartbeat names, which is what §0.6 fixed. What ended is telling the
1369
+ // daemon about it in a field it ignored. If an upstream ever needs to
1370
+ // push lease decisions, that is a new field with a reader, added on
1371
+ // purpose.
1243
1372
  /**
1244
1373
  * Jobs the daemon thinks it holds but the server has reassigned or
1245
1374
  * expired. The daemon must stop work on these and not report results.
@@ -1253,6 +1382,29 @@ var ResultRequest = z8.object({
1253
1382
  protocolVersion: z8.literal(PROTOCOL_VERSION),
1254
1383
  runnerId: z8.string().min(1),
1255
1384
  jobId: z8.string().min(1),
1385
+ /**
1386
+ * The grant this result was produced under — cloud_008 §1.4a.
1387
+ *
1388
+ * `fetch` has always named its lease, with the reasoning written beside
1389
+ * it: a request that names only the job would be answerable for whatever
1390
+ * lease exists when it arrives. **The operation that writes the result did
1391
+ * not**, on either plane, and checked only the runner id — which survives
1392
+ * a claim-release-reclaim cycle, so a device whose grant had been swept
1393
+ * and reissued could still land a result for a job it no longer held.
1394
+ *
1395
+ * Found by tracing a mutation that survived in §0.6: the lease lapsed, the
1396
+ * sweep requeued, the daemon re-claimed under a new grant, and the
1397
+ * original run finished and posted anyway. The relay marked the job done
1398
+ * with a result the site cannot open — it verifies the envelope against
1399
+ * the *current* holder's device, so the crypto contains the substitution —
1400
+ * and then refused the real holder's result as a replay. A lost job, in
1401
+ * silence.
1402
+ *
1403
+ * `LEASE_HONORED` is a statement about a lease *instance*. That was
1404
+ * learned once already, when a replayed release yanked a later grant, and
1405
+ * it applies here for the same reason.
1406
+ */
1407
+ leaseId: z8.string().min(1),
1256
1408
  /**
1257
1409
  * The outcome, sealed to the site and signed by the device.
1258
1410
  *
@@ -1268,12 +1420,21 @@ var ResultRequest = z8.object({
1268
1420
  * fact: believing it unverified would let a daemon mark a job `ok` while
1269
1421
  * sealing an error, and only the app would ever find out.
1270
1422
  */
1271
- disposition: ResultDisposition,
1272
- /** Which model actually served it, for the result's provenance. */
1273
- model: z8.string().min(1),
1274
- backendClass: BackendClass,
1275
- /** Wall-clock milliseconds the backend call took. */
1276
- durationMs: z8.number().int().nonnegative()
1423
+ disposition: ResultDisposition
1424
+ // `model`, `backendClass` and `durationMs` are **inside the envelope**
1425
+ // cloud_008 §2.5. See {@link RunMetadata}.
1426
+ //
1427
+ // They were here, in the clear, and that was two problems wearing one
1428
+ // coat. On the direct plane the site recorded unauthenticated fields
1429
+ // beside an authenticated answer: a daemon could seal one result and
1430
+ // declare a different model, and only the unsigned half would reach the
1431
+ // app. Through a relay they reached a third party that acts on none of
1432
+ // them — `model` in particular being the sort of detail Amendment A's
1433
+ // rule keeps off the wire.
1434
+ //
1435
+ // `disposition` stays, and the difference is the test: a relay *routes*
1436
+ // on it, so it is a class a routing party consumes. Nobody between the
1437
+ // two ends consumes these.
1277
1438
  }).strict();
1278
1439
  var ResultResponse = z8.object({
1279
1440
  /**
@@ -1315,9 +1476,40 @@ var ReleaseResponse = z8.object({
1315
1476
  var WireErrorCode = z8.enum([
1316
1477
  "bad-request",
1317
1478
  "unsupported-protocol-version",
1479
+ // "We do not know who you are." Exactly 401, and only that — cloud_008
1480
+ // §1.4d.
1318
1481
  "unauthorized",
1482
+ /**
1483
+ * "We know exactly who you are, and the answer is no." Exactly 403.
1484
+ *
1485
+ * Five refusals across both planes served 403 with `unauthorized`, whose
1486
+ * table entry is 401: a revoked device, a site claiming another site's
1487
+ * stub, a job you do not hold, a device belonging to another owner, a
1488
+ * relay that does not route for you. Every one of them is an *identified*
1489
+ * caller being refused.
1490
+ *
1491
+ * Collapsing the two loses a distinction that matters everywhere it is
1492
+ * read: a revoked daemon would look like an unsigned one in every log and
1493
+ * every client branch, and "check your keys" is the wrong advice for both
1494
+ * of them in opposite directions.
1495
+ */
1496
+ "forbidden",
1319
1497
  "revoked",
1320
1498
  "not-found",
1499
+ // Claimed, but the site has not sealed the payload yet — cloud_008 §1.4.
1500
+ //
1501
+ // A daemon must retry rather than abandon: the job is legitimately still
1502
+ // its own until the lease or the awaiting-payload clock says otherwise.
1503
+ // That is why it cannot be `not-found` or `server-error`, and why it was
1504
+ // the protocol gap that produced a bare 409 in the first place.
1505
+ "not-ready",
1506
+ // The caller's clock is too far from ours to judge a signature's freshness.
1507
+ //
1508
+ // Split out from `unauthorized` because the remedy is completely different
1509
+ // and only the server can tell them apart: a bad signature means the key is
1510
+ // wrong, this means the machine's time is wrong. A daemon reporting it as a
1511
+ // generic rejection sends its owner looking at their network.
1512
+ "clock-skew",
1321
1513
  "rate-limited",
1322
1514
  "server-error"
1323
1515
  ]);
@@ -1325,14 +1517,46 @@ var WireError = z8.object({
1325
1517
  error: WireErrorCode,
1326
1518
  message: z8.string().min(1),
1327
1519
  /** Seconds; mirrors Retry-After for `rate-limited` and `server-error`. */
1328
- retryAfter: z8.number().int().nonnegative().optional()
1329
- }).strict();
1520
+ retryAfter: z8.number().int().nonnegative().optional(),
1521
+ /**
1522
+ * The server's clock, and the window it allows. `clock-skew` only.
1523
+ *
1524
+ * So the far side can say *how far off* rather than *that something is
1525
+ * wrong* — the difference between "adjust your clock by four minutes" and
1526
+ * "something is wrong with your connection". Not a disclosure: the
1527
+ * heartbeat response returns the same value, and so does every `Date`
1528
+ * header.
1529
+ */
1530
+ serverTime: z8.number().int().positive().optional(),
1531
+ maxSkewMs: z8.number().int().positive().optional()
1532
+ }).strict().superRefine((error, ctx) => {
1533
+ const skew = error.error === "clock-skew";
1534
+ const carried = error.serverTime !== void 0 || error.maxSkewMs !== void 0;
1535
+ if (skew && !carried) {
1536
+ ctx.addIssue({
1537
+ code: "custom",
1538
+ message: "clock-skew must carry serverTime and maxSkewMs"
1539
+ });
1540
+ }
1541
+ if (!skew && carried) {
1542
+ ctx.addIssue({
1543
+ code: "custom",
1544
+ message: `${error.error} must not carry serverTime or maxSkewMs`
1545
+ });
1546
+ }
1547
+ });
1330
1548
  var ERROR_STATUS = Object.freeze({
1331
1549
  "bad-request": 400,
1332
1550
  "unsupported-protocol-version": 400,
1333
1551
  unauthorized: 401,
1552
+ forbidden: 403,
1334
1553
  revoked: 403,
1335
1554
  "not-found": 404,
1555
+ // 409, not 404: the job exists and is yours, it is simply not ready.
1556
+ "not-ready": 409,
1557
+ // 401 alongside `unauthorized`, because that is what it is — the
1558
+ // signature could not be judged. The code is what carries the remedy.
1559
+ "clock-skew": 401,
1336
1560
  "rate-limited": 429,
1337
1561
  "server-error": 500
1338
1562
  });
@@ -1421,9 +1645,11 @@ export {
1421
1645
  ResultProvenance,
1422
1646
  ResultRequest,
1423
1647
  ResultResponse,
1648
+ RunMetadata,
1424
1649
  SIZE_CLASS_LIMITS,
1425
1650
  SUPPORTED_PROTOCOL_VERSIONS,
1426
1651
  SealedEnvelope,
1652
+ SealedOutcome,
1427
1653
  SizeClass,
1428
1654
  StoredKeys,
1429
1655
  TERMINAL_STATES,