@byollm/protocol 0.1.0-alpha.81 → 0.1.0-alpha.83
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 +1 -1
- package/dist/index.d.ts +100 -5
- package/dist/index.js +128 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Who may be told about a new version — B053.
|
|
5
|
+
*
|
|
6
|
+
* `HeartbeatResponse.updateTo` is a new field on a `.strict()` schema, and
|
|
7
|
+
* strict means a daemon built before the field does not ignore it: it rejects
|
|
8
|
+
* the entire heartbeat. Send it to everybody and the message carrying the
|
|
9
|
+
* update is the message that takes offline the machines it was meant to
|
|
10
|
+
* update — the fleet-wide version of the failure this codebase keeps meeting
|
|
11
|
+
* one surface at a time.
|
|
12
|
+
*
|
|
13
|
+
* No handshake is needed to avoid that. `HeartbeatRequest.daemonVersion` is
|
|
14
|
+
* already on the wire, so the sender can simply decline to say anything a
|
|
15
|
+
* given listener cannot hear. The rule lives here, next to the field it
|
|
16
|
+
* governs, because a rule of this kind in a runbook is a rule that holds
|
|
17
|
+
* until the next person deploys.
|
|
18
|
+
*
|
|
19
|
+
* ## The other direction, deliberately not taken
|
|
20
|
+
*
|
|
21
|
+
* The tidier-looking design is a capability list on the request — the daemon
|
|
22
|
+
* says what it understands. It has a hole this one does not: that field is
|
|
23
|
+
* also new, the hub's schema is also strict, and an upgraded daemon sending
|
|
24
|
+
* it to a hub that has not deployed yet is refused outright. It makes the
|
|
25
|
+
* daemon's upgrade depend on the hub's, in a system where the daemon is the
|
|
26
|
+
* side we do not control the timing of.
|
|
27
|
+
*
|
|
28
|
+
* Reading a field that already exists has no such ordering problem: old
|
|
29
|
+
* daemons are never sent the new field, new daemons are, and neither needs
|
|
30
|
+
* the other to have moved first.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The first daemon version whose schema has `updateTo` in it.
|
|
34
|
+
*
|
|
35
|
+
* Raising this is safe and lowering it is not, which is worth knowing before
|
|
36
|
+
* anybody tidies it: too high means some daemons miss an update they could
|
|
37
|
+
* have taken, and too low means their heartbeats are refused.
|
|
38
|
+
*/
|
|
39
|
+
declare const UPDATE_OFFER_SINCE = "0.1.0-alpha.83";
|
|
40
|
+
/**
|
|
41
|
+
* Semver ordering, only as far as this needs it.
|
|
42
|
+
*
|
|
43
|
+
* Numeric prerelease parts compare as numbers, which is the whole reason not
|
|
44
|
+
* to compare these as strings: `alpha.9` sorts after `alpha.83`
|
|
45
|
+
* lexicographically, and that mistake here means every daemon between .10 and
|
|
46
|
+
* .82 is treated as too old to hear about an update — or worse, in the other
|
|
47
|
+
* direction, sent a field it cannot parse.
|
|
48
|
+
*/
|
|
49
|
+
declare function compareVersions(a: string, b: string): number | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* May this daemon be told about a new version?
|
|
52
|
+
*
|
|
53
|
+
* **A version this cannot parse is a no.** Unreadable is not permission: the
|
|
54
|
+
* consequence of guessing wrong in that direction is the daemon's heartbeat
|
|
55
|
+
* being refused, which is worse than it missing one update cycle. The same
|
|
56
|
+
* rule the rest of this protocol applies to unreadable answers, on the one
|
|
57
|
+
* field where getting it wrong is fleet-shaped.
|
|
58
|
+
*/
|
|
59
|
+
declare function mayOfferUpdate(daemonVersion: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The oldest daemon a hub will serve — B052, the floor.
|
|
62
|
+
*
|
|
63
|
+
* The updater's backstop and its opposite number. The updater moves machines
|
|
64
|
+
* that opted in; the floor is what moves the ones that did not, and it is the
|
|
65
|
+
* only mechanism that works on a daemon which is not listening for offers.
|
|
66
|
+
*
|
|
67
|
+
* Raising it is a deliberate act with a spec note, never automatic — a floor
|
|
68
|
+
* that followed `latest` would refuse every machine that had not updated in
|
|
69
|
+
* the last hour, which is the outage version of hygiene.
|
|
70
|
+
*/
|
|
71
|
+
interface FloorRefusal {
|
|
72
|
+
readonly error: "daemon-below-floor";
|
|
73
|
+
readonly message: string;
|
|
74
|
+
readonly floor: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Is this daemon too old to serve?
|
|
78
|
+
*
|
|
79
|
+
* **An unreadable version is NOT refused**, and that is the deliberate
|
|
80
|
+
* asymmetry with {@link mayOfferUpdate}, which treats an unreadable version
|
|
81
|
+
* as "do not offer". The two point the same way once you ask what the
|
|
82
|
+
* mistake costs: there, guessing yes sends a field that breaks the
|
|
83
|
+
* heartbeat; here, guessing yes takes a working machine out of service over
|
|
84
|
+
* a string it could not parse. Both decline to act when they cannot tell,
|
|
85
|
+
* and declining to act means opposite booleans.
|
|
86
|
+
*/
|
|
87
|
+
declare function checkDaemonFloor(input: {
|
|
88
|
+
readonly daemonVersion: string;
|
|
89
|
+
readonly floor: string;
|
|
90
|
+
/** How somebody fixes it. The floor is useless without the remedy. */
|
|
91
|
+
readonly upgradeCommand: string;
|
|
92
|
+
}): FloorRefusal | null;
|
|
93
|
+
|
|
3
94
|
/** The full description — five sections, plus why it matters. */
|
|
4
95
|
declare const ABOUT = "# About BYOLLM\n\n**What BYOLLM is**\n\nBYOLLM lets you use your own AI on websites. You install one small program on\nyour computer. Then, websites that support BYOLLM can use the AI you already\nhave \u2014 a free model running on your machine, or an AI service you already pay\nfor \u2014 instead of the website paying for AI and passing the cost to you.\n\n**Why it matters**\n\nFor you:\n\n- Your favorite model, everywhere you go.\n- New models the moment you get them \u2013 not when a site gets around to adding\n them.\n- Encrypted end-to-end. Your prompts go to your own device; byollm.cloud can't\n read them.\n- Sites never learn which model you use, and your subscriptions are never\n shared.\n- Pay less. Sites that don't pay for AI can charge you less \u2013 or nothing.\n\nFor sites and developers:\n\n- Zero AI bills. Your users bring their own compute.\n- No floating money \u2013 you don't pay LLM bills up front and hope to collect\n later, and you never ask people to prepay just to try you.\n- Free trials that cost you nothing to offer.\n- Ship the AI features you kept private for fear of the API bill.\n- One small integration. Your users choose the models.\n\n**Your device**\n\nThe `byollm` program runs on your computer. It knows which AI services you have\nset up: free open-source models on your machine, metered services you pay per\nuse, or your own subscriptions like Claude Pro/Max. When a website you have\nenabled sends work, your device runs it with the service you chose. Your\nprompts are encrypted end-to-end to your own device. byollm.cloud passes them\nalong and cannot read them.\n\n**Sites**\n\nA website that wants to use BYOLLM says what it needs \u2014 \"writing help,\" \"chat,\"\nand so on. When you connect the site, you pick which of your services answers\neach one. The site never learns which model you use. You can turn a site off at\nany time, and it stops getting your work.\n\n**Teams (optional)**\n\nA team lets you share what runs on your devices with people you name \u2014 the free\nopen-source models on your machine, or a metered service with a spending limit\nyou set. Your subscription accounts (like Claude Pro/Max) are never shared with\nanyone. That is a rule, not a setting.\n\n**byollm.cloud (or your own relay)**\n\nMany sites, many devices, many people. byollm.cloud keeps track of who has\nallowed what and sends each job to the right device. It never sees your\nprompts. If you would rather run this part yourself, the relay is open source \u2014\nyou can run your own instead of using byollm.cloud.";
|
|
5
96
|
/**
|
|
@@ -594,8 +685,8 @@ declare function payloadTextLength(kinded: KindedPayload): number;
|
|
|
594
685
|
* ```
|
|
595
686
|
*/
|
|
596
687
|
declare const JobState: z.ZodEnum<{
|
|
597
|
-
ok: "ok";
|
|
598
688
|
error: "error";
|
|
689
|
+
ok: "ok";
|
|
599
690
|
expired: "expired";
|
|
600
691
|
queued: "queued";
|
|
601
692
|
claimed: "claimed";
|
|
@@ -846,8 +937,8 @@ type SealedOutcome = z.infer<typeof SealedOutcome>;
|
|
|
846
937
|
declare const DeliveredResult: z.ZodObject<{
|
|
847
938
|
jobId: z.ZodString;
|
|
848
939
|
state: z.ZodEnum<{
|
|
849
|
-
ok: "ok";
|
|
850
940
|
error: "error";
|
|
941
|
+
ok: "ok";
|
|
851
942
|
expired: "expired";
|
|
852
943
|
queued: "queued";
|
|
853
944
|
claimed: "claimed";
|
|
@@ -2535,6 +2626,7 @@ declare const HeartbeatResponse: z.ZodObject<{
|
|
|
2535
2626
|
}, z.core.$strict>>;
|
|
2536
2627
|
serverTime: z.ZodNumber;
|
|
2537
2628
|
awaitingConsent: z.ZodArray<z.ZodString>;
|
|
2629
|
+
updateTo: z.ZodOptional<z.ZodString>;
|
|
2538
2630
|
}, z.core.$strict>;
|
|
2539
2631
|
type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
|
|
2540
2632
|
/**
|
|
@@ -2550,8 +2642,8 @@ type HeartbeatResponse = z.infer<typeof HeartbeatResponse>;
|
|
|
2550
2642
|
* and collapsing them would make the relay guess.
|
|
2551
2643
|
*/
|
|
2552
2644
|
declare const ResultDisposition: z.ZodEnum<{
|
|
2553
|
-
ok: "ok";
|
|
2554
2645
|
error: "error";
|
|
2646
|
+
ok: "ok";
|
|
2555
2647
|
canceled: "canceled";
|
|
2556
2648
|
}>;
|
|
2557
2649
|
type ResultDisposition = z.infer<typeof ResultDisposition>;
|
|
@@ -2571,8 +2663,8 @@ declare const ResultRequest: z.ZodObject<{
|
|
|
2571
2663
|
deadlineAt: z.ZodNumber;
|
|
2572
2664
|
}, z.core.$strict>;
|
|
2573
2665
|
disposition: z.ZodEnum<{
|
|
2574
|
-
ok: "ok";
|
|
2575
2666
|
error: "error";
|
|
2667
|
+
ok: "ok";
|
|
2576
2668
|
canceled: "canceled";
|
|
2577
2669
|
}>;
|
|
2578
2670
|
}, z.core.$strict>;
|
|
@@ -2612,6 +2704,7 @@ type ReleaseResponse = z.infer<typeof ReleaseResponse>;
|
|
|
2612
2704
|
* with no response at all.
|
|
2613
2705
|
*/
|
|
2614
2706
|
declare const WireErrorCode: z.ZodEnum<{
|
|
2707
|
+
"daemon-below-floor": "daemon-below-floor";
|
|
2615
2708
|
"unsupported-protocol-version": "unsupported-protocol-version";
|
|
2616
2709
|
revoked: "revoked";
|
|
2617
2710
|
"bad-request": "bad-request";
|
|
@@ -2627,6 +2720,7 @@ declare const WireErrorCode: z.ZodEnum<{
|
|
|
2627
2720
|
type WireErrorCode = z.infer<typeof WireErrorCode>;
|
|
2628
2721
|
declare const WireError: z.ZodObject<{
|
|
2629
2722
|
error: z.ZodEnum<{
|
|
2723
|
+
"daemon-below-floor": "daemon-below-floor";
|
|
2630
2724
|
"unsupported-protocol-version": "unsupported-protocol-version";
|
|
2631
2725
|
revoked: "revoked";
|
|
2632
2726
|
"bad-request": "bad-request";
|
|
@@ -2642,6 +2736,7 @@ declare const WireError: z.ZodObject<{
|
|
|
2642
2736
|
message: z.ZodString;
|
|
2643
2737
|
supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2644
2738
|
minimum: z.ZodOptional<z.ZodString>;
|
|
2739
|
+
floor: z.ZodOptional<z.ZodString>;
|
|
2645
2740
|
retryAfter: z.ZodOptional<z.ZodNumber>;
|
|
2646
2741
|
serverTime: z.ZodOptional<z.ZodNumber>;
|
|
2647
2742
|
maxSkewMs: z.ZodOptional<z.ZodNumber>;
|
|
@@ -2670,4 +2765,4 @@ declare const FetchResponse: z.ZodObject<{
|
|
|
2670
2765
|
}, z.core.$strict>;
|
|
2671
2766
|
type FetchResponse = z.infer<typeof FetchResponse>;
|
|
2672
2767
|
|
|
2673
|
-
export { ABOUT, ABOUT_SHORT, ABOUT_SHORT_LEDE, ABOUT_SHORT_TAIL, AUDIENCES, Audience, BACKENDS, BACKEND_CLASSES, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, CLOCK_ATTRIBUTION_MS, CLOCK_SKEW_WARN_MS, 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, GRANT_CONTEXT, GRANT_MAX_AGE_MS, GRANT_SIGNED_FIELDS, GeneratePayload, type GrantClaims, GrantRef, type GrantRefusal, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobRefused, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_ENVELOPE_BYTES, MAX_PURPOSES, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, Manifest, 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, Purpose, REFUSAL_MESSAGES, RESERVED_PURPOSE, RETIREMENT_WINDOW_MS, RefusalReason, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASSES, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SignedGrant, SizeClass, type SpendConsent, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, backendName, canTransition, canonicalRequest, checkProtocolVersion, classifyCost, cryptoReady, declaredVersion, effectiveOfferScope, envelopeBytes, fingerprint, generateKeys, grantStatement, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signGrant, signRequest, signSiteRequest, signSuccession, signWith, singlePurposeManifest, sizeClassCeiling, sizeClassOf, successionStatement, verifyGrant, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession };
|
|
2768
|
+
export { ABOUT, ABOUT_SHORT, ABOUT_SHORT_LEDE, ABOUT_SHORT_TAIL, AUDIENCES, Audience, BACKENDS, BACKEND_CLASSES, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, CLOCK_ATTRIBUTION_MS, CLOCK_SKEW_WARN_MS, 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, type FloorRefusal, GRANT_CONTEXT, GRANT_MAX_AGE_MS, GRANT_SIGNED_FIELDS, GeneratePayload, type GrantClaims, GrantRef, type GrantRefusal, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobRefused, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_ENVELOPE_BYTES, MAX_PURPOSES, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, Manifest, 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, Purpose, REFUSAL_MESSAGES, RESERVED_PURPOSE, RETIREMENT_WINDOW_MS, RefusalReason, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASSES, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SignedGrant, SizeClass, type SpendConsent, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, UPDATE_OFFER_SINCE, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, backendName, canTransition, canonicalRequest, checkDaemonFloor, checkProtocolVersion, classifyCost, compareVersions, cryptoReady, declaredVersion, effectiveOfferScope, envelopeBytes, fingerprint, generateKeys, grantStatement, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mayOfferUpdate, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signGrant, signRequest, signSiteRequest, signSuccession, signWith, singlePurposeManifest, sizeClassCeiling, sizeClassOf, successionStatement, verifyGrant, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
// src/update-offer.ts
|
|
2
|
+
var UPDATE_OFFER_SINCE = "0.1.0-alpha.83";
|
|
3
|
+
function parse(version) {
|
|
4
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
|
|
5
|
+
version
|
|
6
|
+
);
|
|
7
|
+
if (match === null) return void 0;
|
|
8
|
+
return {
|
|
9
|
+
release: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
10
|
+
pre: match[4] === void 0 ? [] : match[4].split(".").map((part) => /^\d+$/.test(part) ? Number(part) : part)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function compareVersions(a, b) {
|
|
14
|
+
const left = parse(a);
|
|
15
|
+
const right = parse(b);
|
|
16
|
+
if (left === void 0 || right === void 0) return void 0;
|
|
17
|
+
for (let i = 0; i < 3; i += 1) {
|
|
18
|
+
const diff = (left.release[i] ?? 0) - (right.release[i] ?? 0);
|
|
19
|
+
if (diff !== 0) return diff < 0 ? -1 : 1;
|
|
20
|
+
}
|
|
21
|
+
if (left.pre.length === 0 && right.pre.length > 0) return 1;
|
|
22
|
+
if (left.pre.length > 0 && right.pre.length === 0) return -1;
|
|
23
|
+
for (let i = 0; i < Math.max(left.pre.length, right.pre.length); i += 1) {
|
|
24
|
+
const l = left.pre[i];
|
|
25
|
+
const r = right.pre[i];
|
|
26
|
+
if (l === void 0) return -1;
|
|
27
|
+
if (r === void 0) return 1;
|
|
28
|
+
if (l === r) continue;
|
|
29
|
+
if (typeof l === "number" && typeof r === "number") return l < r ? -1 : 1;
|
|
30
|
+
if (typeof l === "number") return -1;
|
|
31
|
+
if (typeof r === "number") return 1;
|
|
32
|
+
return l < r ? -1 : 1;
|
|
33
|
+
}
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
function mayOfferUpdate(daemonVersion) {
|
|
37
|
+
const order = compareVersions(daemonVersion, UPDATE_OFFER_SINCE);
|
|
38
|
+
return order !== void 0 && order >= 0;
|
|
39
|
+
}
|
|
40
|
+
function checkDaemonFloor(input) {
|
|
41
|
+
const order = compareVersions(input.daemonVersion, input.floor);
|
|
42
|
+
if (order === void 0 || order >= 0) return null;
|
|
43
|
+
return {
|
|
44
|
+
error: "daemon-below-floor",
|
|
45
|
+
message: `byollm ${input.daemonVersion} is below the supported floor (${input.floor}). Run \`${input.upgradeCommand}\`, then \`byollm start\`.`,
|
|
46
|
+
floor: input.floor
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
1
50
|
// src/about.ts
|
|
2
51
|
var ABOUT = `# About BYOLLM
|
|
3
52
|
|
|
@@ -2108,7 +2157,30 @@ var HeartbeatResponse = z11.object({
|
|
|
2108
2157
|
* operator stopped it" — one word with two subjects on two halves of one
|
|
2109
2158
|
* exchange is a confusion nobody untangles from a log.
|
|
2110
2159
|
*/
|
|
2111
|
-
awaitingConsent: z11.array(z11.string().min(1))
|
|
2160
|
+
awaitingConsent: z11.array(z11.string().min(1)),
|
|
2161
|
+
/**
|
|
2162
|
+
* A version this daemon should move itself to — B053.
|
|
2163
|
+
*
|
|
2164
|
+
* The channel the auto-updater reads, and it is the channel the daemon
|
|
2165
|
+
* already polls rather than a new phone-home, which is what the ruling
|
|
2166
|
+
* asked for (016 §Auto-update).
|
|
2167
|
+
*
|
|
2168
|
+
* **The hub may not send this to every daemon.** This schema is
|
|
2169
|
+
* `.strict()`, so a daemon built before the field exists does not ignore
|
|
2170
|
+
* it — it rejects the whole heartbeat and stops working. Which would mean
|
|
2171
|
+
* the message carrying the update is the message that breaks the machines
|
|
2172
|
+
* it was meant to update.
|
|
2173
|
+
*
|
|
2174
|
+
* That is decidable without any new handshake, because the request
|
|
2175
|
+
* already carries `daemonVersion`. {@link mayOfferUpdate} is the rule,
|
|
2176
|
+
* kept here as code rather than as a paragraph in a runbook, so both
|
|
2177
|
+
* sides read the same one.
|
|
2178
|
+
*
|
|
2179
|
+
* Exact versions only, never a tag: the daemon refuses anything else, and
|
|
2180
|
+
* a fleet resolving one tag at different minutes is a fleet on different
|
|
2181
|
+
* builds reporting one number.
|
|
2182
|
+
*/
|
|
2183
|
+
updateTo: z11.string().min(1).optional()
|
|
2112
2184
|
}).strict();
|
|
2113
2185
|
var ResultDisposition = z11.enum(["ok", "error", "canceled"]);
|
|
2114
2186
|
var ResultRequest = z11.object({
|
|
@@ -2220,6 +2292,20 @@ var ReleaseResponse = z11.object({
|
|
|
2220
2292
|
var WireErrorCode = z11.enum([
|
|
2221
2293
|
"bad-request",
|
|
2222
2294
|
"unsupported-protocol-version",
|
|
2295
|
+
/**
|
|
2296
|
+
* The daemon is older than this hub will serve — B052.
|
|
2297
|
+
*
|
|
2298
|
+
* Distinct from `unsupported-protocol-version`, which is about the
|
|
2299
|
+
* contract; this is about the build. A daemon can speak protocol 1
|
|
2300
|
+
* perfectly and still be old enough that we would rather move it than keep
|
|
2301
|
+
* carrying it — and the two need different remedies in the message, since
|
|
2302
|
+
* one is "your daemon and this server disagree" and the other is "yours
|
|
2303
|
+
* works, and it is time".
|
|
2304
|
+
*
|
|
2305
|
+
* The floor is the backstop to the auto-updater (B053), and the only lever
|
|
2306
|
+
* that reaches a machine which never opted into offers.
|
|
2307
|
+
*/
|
|
2308
|
+
"daemon-below-floor",
|
|
2223
2309
|
// "We do not know who you are." Exactly 401, and only that — cloud_008
|
|
2224
2310
|
// §1.4d.
|
|
2225
2311
|
"unauthorized",
|
|
@@ -2285,6 +2371,16 @@ var WireError = z11.object({
|
|
|
2285
2371
|
*/
|
|
2286
2372
|
supported: z11.array(z11.string().min(1)).optional(),
|
|
2287
2373
|
minimum: z11.string().min(1).optional(),
|
|
2374
|
+
/**
|
|
2375
|
+
* The oldest daemon this hub serves, on `daemon-below-floor` — B052.
|
|
2376
|
+
*
|
|
2377
|
+
* Carried for the same reason `supported` and `minimum` are: a refusal
|
|
2378
|
+
* that cannot be branched on is a refusal a client can only print. The
|
|
2379
|
+
* message already names the floor for a person; this names it for the
|
|
2380
|
+
* code, so a surface can say "you are two versions under" without
|
|
2381
|
+
* parsing English.
|
|
2382
|
+
*/
|
|
2383
|
+
floor: z11.string().min(1).optional(),
|
|
2288
2384
|
/** Seconds; mirrors Retry-After for `rate-limited` and `server-error`. */
|
|
2289
2385
|
retryAfter: z11.number().int().nonnegative().optional(),
|
|
2290
2386
|
/**
|
|
@@ -2313,6 +2409,19 @@ var WireError = z11.object({
|
|
|
2313
2409
|
message: `${error.error} must not carry serverTime or maxSkewMs`
|
|
2314
2410
|
});
|
|
2315
2411
|
}
|
|
2412
|
+
const floored = error.error === "daemon-below-floor";
|
|
2413
|
+
if (floored && error.floor === void 0) {
|
|
2414
|
+
ctx.addIssue({
|
|
2415
|
+
code: "custom",
|
|
2416
|
+
message: "daemon-below-floor must carry floor"
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
if (!floored && error.floor !== void 0) {
|
|
2420
|
+
ctx.addIssue({
|
|
2421
|
+
code: "custom",
|
|
2422
|
+
message: `${error.error} must not carry floor`
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2316
2425
|
const version = error.error === "unsupported-protocol-version";
|
|
2317
2426
|
const versionFields = error.supported !== void 0 || error.minimum !== void 0;
|
|
2318
2427
|
if (version && !versionFields) {
|
|
@@ -2331,6 +2440,20 @@ var WireError = z11.object({
|
|
|
2331
2440
|
var ERROR_STATUS = Object.freeze({
|
|
2332
2441
|
"bad-request": 400,
|
|
2333
2442
|
"unsupported-protocol-version": 400,
|
|
2443
|
+
/**
|
|
2444
|
+
* 426 Upgrade Required — B052, and it is the one status that says this.
|
|
2445
|
+
*
|
|
2446
|
+
* Not 403, which this daemon reads as a permission problem and which
|
|
2447
|
+
* sits beside `revoked` in every log. Not 400, which reads as a
|
|
2448
|
+
* malformed request; the request was perfect and the sender is old.
|
|
2449
|
+
*
|
|
2450
|
+
* It also fails safely on a daemon that predates the code: 426 is not in
|
|
2451
|
+
* that switch, so it lands on the 4xx default — `rejected`, which is
|
|
2452
|
+
* "never retried: the request is wrong, and repeating it stays wrong".
|
|
2453
|
+
* Vaguer than the remedy, and the right behaviour, which is what a
|
|
2454
|
+
* fallback has to be.
|
|
2455
|
+
*/
|
|
2456
|
+
"daemon-below-floor": 426,
|
|
2334
2457
|
unauthorized: 401,
|
|
2335
2458
|
forbidden: 403,
|
|
2336
2459
|
revoked: 403,
|
|
@@ -2472,6 +2595,7 @@ export {
|
|
|
2472
2595
|
StoredKeys,
|
|
2473
2596
|
Succession,
|
|
2474
2597
|
TERMINAL_STATES,
|
|
2598
|
+
UPDATE_OFFER_SINCE,
|
|
2475
2599
|
WireError,
|
|
2476
2600
|
WireErrorCode,
|
|
2477
2601
|
WithheldKind,
|
|
@@ -2479,8 +2603,10 @@ export {
|
|
|
2479
2603
|
backendName,
|
|
2480
2604
|
canTransition,
|
|
2481
2605
|
canonicalRequest,
|
|
2606
|
+
checkDaemonFloor,
|
|
2482
2607
|
checkProtocolVersion,
|
|
2483
2608
|
classifyCost,
|
|
2609
|
+
compareVersions,
|
|
2484
2610
|
cryptoReady,
|
|
2485
2611
|
declaredVersion,
|
|
2486
2612
|
effectiveOfferScope,
|
|
@@ -2496,6 +2622,7 @@ export {
|
|
|
2496
2622
|
keyId,
|
|
2497
2623
|
kindsOf,
|
|
2498
2624
|
matchAudience,
|
|
2625
|
+
mayOfferUpdate,
|
|
2499
2626
|
mustsVerifiedBy,
|
|
2500
2627
|
open,
|
|
2501
2628
|
payloadTextLength,
|