@byollm/protocol 0.1.0-alpha.57 → 0.1.0-alpha.58
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 +2 -2
- package/dist/index.d.ts +342 -151
- package/dist/index.js +514 -459
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -255,22 +255,28 @@ function classifyCost(id, baseUrl, model) {
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
// src/audience.ts
|
|
258
|
-
var Audience = z2.enum(["private", "team"
|
|
259
|
-
var OfferScope = z2.enum(["private", "team"
|
|
258
|
+
var Audience = z2.enum(["private", "team"]);
|
|
259
|
+
var OfferScope = z2.enum(["private", "team"]);
|
|
260
260
|
var AUDIENCES = Object.freeze(Audience.options);
|
|
261
261
|
var OFFER_SCOPES = Object.freeze(OfferScope.options);
|
|
262
262
|
var MatchRefusal = z2.enum([
|
|
263
263
|
/** The daemon advertises no capability for this kind. */
|
|
264
264
|
"no-capability",
|
|
265
|
-
/** Job is `
|
|
265
|
+
/** Job is `private` but this daemon belongs to a different user. */
|
|
266
266
|
"audience-self-other-owner",
|
|
267
|
-
/**
|
|
267
|
+
/**
|
|
268
|
+
* Job is `team` and nothing this device verified admits the job's owner.
|
|
269
|
+
*
|
|
270
|
+
* The id predates the grant and is kept, because ids are public and cited
|
|
271
|
+
* by conformance output. What it means has not moved: this device was not
|
|
272
|
+
* shown anything it could check.
|
|
273
|
+
*/
|
|
268
274
|
"not-locally-allowed",
|
|
269
|
-
/** Job is `
|
|
275
|
+
/** Job is `team` but the server's own allowlist excludes this runner. */
|
|
270
276
|
"not-in-server-allowlist",
|
|
271
|
-
/** The
|
|
277
|
+
/** The service offers only `private` and the job belongs to someone else. */
|
|
272
278
|
"offer-scope-too-narrow",
|
|
273
|
-
/** The matched backend is subscription-class, which is locked to `
|
|
279
|
+
/** The matched backend is subscription-class, which is locked to `private`. */
|
|
274
280
|
"subscription-self-lock",
|
|
275
281
|
/** The backend spends the owner's money and they have not agreed to share it. */
|
|
276
282
|
"metered-no-spend-consent",
|
|
@@ -315,17 +321,15 @@ function matchAudience(job, daemon) {
|
|
|
315
321
|
case "private":
|
|
316
322
|
return refuse("offer-scope-too-narrow");
|
|
317
323
|
case "team":
|
|
318
|
-
return daemon.
|
|
319
|
-
case "public":
|
|
320
|
-
return ALLOWED;
|
|
324
|
+
return daemon.admits(job.owner) ? ALLOWED : refuse("not-locally-allowed");
|
|
321
325
|
}
|
|
322
326
|
}
|
|
323
327
|
var REFUSAL_MESSAGES = Object.freeze({
|
|
324
328
|
"no-capability": "no backend on this device is configured and healthy for that job kind",
|
|
325
329
|
"audience-self-other-owner": "the job is private to its owner and this device is paired to someone else",
|
|
326
|
-
"not-locally-allowed": "
|
|
330
|
+
"not-locally-allowed": "nothing this device can verify says the job's owner may use it",
|
|
327
331
|
"not-in-server-allowlist": "the app restricted this job to named runners and this device is not one of them",
|
|
328
|
-
"offer-scope-too-narrow": "this
|
|
332
|
+
"offer-scope-too-narrow": "this service is offered to its owner only (`byollm offer <service> team` to widen)",
|
|
329
333
|
"subscription-self-lock": "subscription-backed models run their owner's work only \u2014 this is a protocol rule, not a setting",
|
|
330
334
|
"metered-no-spend-consent": "this backend bills its owner per token, and they have not agreed to spend it on other people's work",
|
|
331
335
|
"metered-ceiling-reached": "this backend is shared but has reached the spend ceiling its owner set"
|
|
@@ -384,8 +388,222 @@ function payloadTextLength(kinded) {
|
|
|
384
388
|
}
|
|
385
389
|
|
|
386
390
|
// src/job.ts
|
|
391
|
+
import { z as z6 } from "zod";
|
|
392
|
+
|
|
393
|
+
// src/grant.ts
|
|
394
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
395
|
+
import { z as z5 } from "zod";
|
|
396
|
+
|
|
397
|
+
// src/keys.ts
|
|
398
|
+
import {
|
|
399
|
+
createHash,
|
|
400
|
+
createPrivateKey,
|
|
401
|
+
createPublicKey,
|
|
402
|
+
generateKeyPairSync,
|
|
403
|
+
sign,
|
|
404
|
+
verify
|
|
405
|
+
} from "crypto";
|
|
387
406
|
import { z as z4 } from "zod";
|
|
388
|
-
var
|
|
407
|
+
var PublicIdentity = z4.object({
|
|
408
|
+
/** Raw Ed25519 public key. The pinned one. */
|
|
409
|
+
identity: z4.string().min(1),
|
|
410
|
+
/** Raw X25519 public key, for sealing to this party. */
|
|
411
|
+
encryption: z4.string().min(1),
|
|
412
|
+
/**
|
|
413
|
+
* Ed25519 signature over the encryption key, by the identity key.
|
|
414
|
+
*
|
|
415
|
+
* This is what stops an upstream substituting an encryption key of its
|
|
416
|
+
* own while relaying a genuine identity: the receiver pins the identity
|
|
417
|
+
* and refuses any encryption key not signed by it.
|
|
418
|
+
*/
|
|
419
|
+
encryptionSig: z4.string().min(1)
|
|
420
|
+
}).strict();
|
|
421
|
+
var StoredKeys = z4.object({
|
|
422
|
+
version: z4.literal(1),
|
|
423
|
+
identityPublic: z4.string().min(1),
|
|
424
|
+
identityPrivate: z4.string().min(1),
|
|
425
|
+
encryptionPublic: z4.string().min(1),
|
|
426
|
+
encryptionPrivate: z4.string().min(1),
|
|
427
|
+
encryptionSig: z4.string().min(1),
|
|
428
|
+
createdAt: z4.number().int().positive()
|
|
429
|
+
}).strict();
|
|
430
|
+
var ENCRYPTION_KEY_CONTEXT = "byollm/v1/encryption-key";
|
|
431
|
+
function rawPublic(key) {
|
|
432
|
+
const jwk = key.export({ format: "jwk" });
|
|
433
|
+
const x = jwk.x;
|
|
434
|
+
if (typeof x !== "string") throw new Error("key has no raw public component");
|
|
435
|
+
return x;
|
|
436
|
+
}
|
|
437
|
+
function importPublic(raw, crv) {
|
|
438
|
+
return createPublicKey({ key: { kty: "OKP", crv, x: raw }, format: "jwk" });
|
|
439
|
+
}
|
|
440
|
+
function importPrivate(stored) {
|
|
441
|
+
return createPrivateKey({
|
|
442
|
+
key: Buffer.from(stored, "base64"),
|
|
443
|
+
type: "pkcs8",
|
|
444
|
+
format: "der"
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
var exportPrivate = (key) => key.export({ type: "pkcs8", format: "der" }).toString("base64");
|
|
448
|
+
function generateKeys(now) {
|
|
449
|
+
const identity = generateKeyPairSync("ed25519");
|
|
450
|
+
const encryption = generateKeyPairSync("x25519");
|
|
451
|
+
const encryptionPublic = rawPublic(encryption.publicKey);
|
|
452
|
+
return {
|
|
453
|
+
version: 1,
|
|
454
|
+
identityPublic: rawPublic(identity.publicKey),
|
|
455
|
+
identityPrivate: exportPrivate(identity.privateKey),
|
|
456
|
+
encryptionPublic,
|
|
457
|
+
encryptionPrivate: exportPrivate(encryption.privateKey),
|
|
458
|
+
encryptionSig: sign(
|
|
459
|
+
null,
|
|
460
|
+
Buffer.from(`${ENCRYPTION_KEY_CONTEXT}:${encryptionPublic}`),
|
|
461
|
+
identity.privateKey
|
|
462
|
+
).toString("base64url"),
|
|
463
|
+
createdAt: now
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function publicIdentityOf(keys) {
|
|
467
|
+
return {
|
|
468
|
+
identity: keys.identityPublic,
|
|
469
|
+
encryption: keys.encryptionPublic,
|
|
470
|
+
encryptionSig: keys.encryptionSig
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function verifyPublicIdentity(identity) {
|
|
474
|
+
try {
|
|
475
|
+
return verify(
|
|
476
|
+
null,
|
|
477
|
+
Buffer.from(`${ENCRYPTION_KEY_CONTEXT}:${identity.encryption}`),
|
|
478
|
+
importPublic(identity.identity, "Ed25519"),
|
|
479
|
+
Buffer.from(identity.encryptionSig, "base64url")
|
|
480
|
+
);
|
|
481
|
+
} catch {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function signWith(keys, data) {
|
|
486
|
+
return sign(null, data, importPrivate(keys.identityPrivate)).toString(
|
|
487
|
+
"base64url"
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
function verifyWith(identityPublic, data, signature) {
|
|
491
|
+
try {
|
|
492
|
+
return verify(
|
|
493
|
+
null,
|
|
494
|
+
data,
|
|
495
|
+
importPublic(identityPublic, "Ed25519"),
|
|
496
|
+
Buffer.from(signature, "base64url")
|
|
497
|
+
);
|
|
498
|
+
} catch {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
503
|
+
function fingerprint(identityPublic) {
|
|
504
|
+
const digest = createHash("sha256").update(Buffer.from(identityPublic, "base64url")).digest();
|
|
505
|
+
let bits = 0;
|
|
506
|
+
let value = 0;
|
|
507
|
+
let out = "";
|
|
508
|
+
for (const byte of digest.subarray(0, 15)) {
|
|
509
|
+
value = value << 8 | byte;
|
|
510
|
+
bits += 8;
|
|
511
|
+
while (bits >= 5) {
|
|
512
|
+
out += ALPHABET.charAt(value >>> bits - 5 & 31);
|
|
513
|
+
bits -= 5;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const groups = out.match(/.{1,4}/g) ?? [];
|
|
517
|
+
return `BYOLLM-${groups.join("-")}`;
|
|
518
|
+
}
|
|
519
|
+
var keyId = (identityPublic) => fingerprint(identityPublic);
|
|
520
|
+
|
|
521
|
+
// src/grant.ts
|
|
522
|
+
var GRANT_MAX_AGE_MS = 12e4;
|
|
523
|
+
var CLOCK_SKEW_WARN_MS = 3e4;
|
|
524
|
+
var CLOCK_ATTRIBUTION_MS = 5e3;
|
|
525
|
+
var GRANT_CONTEXT = "byollm/v1/grant";
|
|
526
|
+
var SignedGrant = z5.object({
|
|
527
|
+
/**
|
|
528
|
+
* This grant's own id — what makes it single-use.
|
|
529
|
+
*
|
|
530
|
+
* **Not the job id, and the difference is load-bearing.** Binding
|
|
531
|
+
* single-use to `jobId` would refuse a legitimate retry: a claim that
|
|
532
|
+
* times out is re-claimed, the control plane authors a second grant for
|
|
533
|
+
* the same job, and a device that recorded the job id as spent would
|
|
534
|
+
* reject its own recovery. A fresh id per authorship replays nothing and
|
|
535
|
+
* retries fine.
|
|
536
|
+
*/
|
|
537
|
+
grantId: z5.string().min(1),
|
|
538
|
+
/**
|
|
539
|
+
* The job this grant admits, and only this one.
|
|
540
|
+
*
|
|
541
|
+
* A grant lifted from one job and presented for another is the obvious
|
|
542
|
+
* attack, and this field is why it fails.
|
|
543
|
+
*/
|
|
544
|
+
jobId: z5.string().min(1),
|
|
545
|
+
/** The site the work came from. */
|
|
546
|
+
siteId: z5.string().min(1),
|
|
547
|
+
/** Whose job it is — the person the site enqueued for. */
|
|
548
|
+
user: z5.string().min(1),
|
|
549
|
+
/**
|
|
550
|
+
* Whose device it is for.
|
|
551
|
+
*
|
|
552
|
+
* Passed to {@link verifyGrant} rather than read out of the document, for
|
|
553
|
+
* the reason every verifier here takes its subject as an argument: a
|
|
554
|
+
* verifier that recovered the owner from the signed bytes would accept a
|
|
555
|
+
* genuine grant belonging to somebody else and pass every check.
|
|
556
|
+
*/
|
|
557
|
+
owner: z5.string().min(1),
|
|
558
|
+
/** The site purpose this job serves — byollm_016 Amendment L. */
|
|
559
|
+
purpose: z5.string().min(1),
|
|
560
|
+
/** The kind of work. */
|
|
561
|
+
kind: z5.string().min(1),
|
|
562
|
+
/**
|
|
563
|
+
* The service the control plane resolved this (purpose, kind) to, from
|
|
564
|
+
* the user's own mapping.
|
|
565
|
+
*
|
|
566
|
+
* Selection is the control plane's; **offer-consistency is the
|
|
567
|
+
* device's**. A device verifies it actually offers this service, at a
|
|
568
|
+
* scope that includes {@link user}, before running anything.
|
|
569
|
+
*/
|
|
570
|
+
service: z5.string().min(1),
|
|
571
|
+
/** When the control plane signed it — epoch ms, the only anchor for age. */
|
|
572
|
+
issuedAt: z5.number().int().positive(),
|
|
573
|
+
/** Base64url Ed25519 over {@link grantStatement}. */
|
|
574
|
+
signature: z5.string().min(1)
|
|
575
|
+
}).strict();
|
|
576
|
+
var GRANT_SIGNED_FIELDS = Object.freeze(
|
|
577
|
+
Object.keys(SignedGrant.shape).filter((key) => key !== "signature").sort()
|
|
578
|
+
);
|
|
579
|
+
function grantStatement(claims) {
|
|
580
|
+
return Buffer2.from(
|
|
581
|
+
JSON.stringify([
|
|
582
|
+
GRANT_CONTEXT,
|
|
583
|
+
...GRANT_SIGNED_FIELDS.map((field) => claims[field])
|
|
584
|
+
]),
|
|
585
|
+
"utf8"
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
function signGrant(keys, claims) {
|
|
589
|
+
return { ...claims, signature: signWith(keys, grantStatement(claims)) };
|
|
590
|
+
}
|
|
591
|
+
function verifyGrant(input) {
|
|
592
|
+
const { grant, now } = input;
|
|
593
|
+
if (grant.owner !== input.owner) return "wrong-owner";
|
|
594
|
+
if (grant.jobId !== input.jobId) return "wrong-job";
|
|
595
|
+
const age = now - grant.issuedAt;
|
|
596
|
+
if (age < 0) return "from-the-future";
|
|
597
|
+
if (age > (input.maxAgeMs ?? GRANT_MAX_AGE_MS)) return "expired";
|
|
598
|
+
return verifyWith(
|
|
599
|
+
input.controlPlanePublic,
|
|
600
|
+
grantStatement(grant),
|
|
601
|
+
grant.signature
|
|
602
|
+
) ? null : "bad-signature";
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/job.ts
|
|
606
|
+
var JobState = z6.enum([
|
|
389
607
|
"queued",
|
|
390
608
|
"claimed",
|
|
391
609
|
"running",
|
|
@@ -417,7 +635,7 @@ var TRANSITIONS = Object.freeze({
|
|
|
417
635
|
function canTransition(from, to) {
|
|
418
636
|
return TRANSITIONS[from].includes(to);
|
|
419
637
|
}
|
|
420
|
-
var Lease =
|
|
638
|
+
var Lease = z6.object({
|
|
421
639
|
/**
|
|
422
640
|
* Identifies *this* grant, not just its holder.
|
|
423
641
|
*
|
|
@@ -432,20 +650,20 @@ var Lease = z4.object({
|
|
|
432
650
|
* and release *is*, per lease, but not across leases, because nothing in
|
|
433
651
|
* the request said which one.
|
|
434
652
|
*/
|
|
435
|
-
id:
|
|
653
|
+
id: z6.string().min(1),
|
|
436
654
|
/** The runner holding the lease. */
|
|
437
|
-
runnerId:
|
|
655
|
+
runnerId: z6.string().min(1),
|
|
438
656
|
/** Epoch milliseconds after which the claim is void. */
|
|
439
|
-
expiresAt:
|
|
657
|
+
expiresAt: z6.number().int().positive()
|
|
440
658
|
});
|
|
441
|
-
var JobPayload =
|
|
442
|
-
var ClaimedJob =
|
|
443
|
-
id:
|
|
659
|
+
var JobPayload = z6.union([GeneratePayload, ChatPayload]);
|
|
660
|
+
var ClaimedJob = z6.object({
|
|
661
|
+
id: z6.string().min(1),
|
|
444
662
|
kind: JobKind,
|
|
445
663
|
payload: JobPayload,
|
|
446
664
|
audience: Audience,
|
|
447
665
|
/** The app's id for the user who enqueued it. */
|
|
448
|
-
owner:
|
|
666
|
+
owner: z6.string().min(1),
|
|
449
667
|
/**
|
|
450
668
|
* Which site's job — V1-3.
|
|
451
669
|
*
|
|
@@ -458,39 +676,42 @@ var ClaimedJob = z4.object({
|
|
|
458
676
|
* one, and so this reads as what it is: a fact about where the work came
|
|
459
677
|
* from, not a second copy of the routing key.
|
|
460
678
|
*/
|
|
461
|
-
site:
|
|
679
|
+
site: z6.string().min(1).optional(),
|
|
462
680
|
/**
|
|
463
|
-
* Which
|
|
681
|
+
* Which of the owner's services runs this — resolved, not requested.
|
|
682
|
+
*
|
|
683
|
+
* The daemon picks the backend from this, so it has to be the answer
|
|
684
|
+
* rather than a wish. On a relayed route it is copied off the **grant**,
|
|
685
|
+
* where a control plane put the person's own mapping and signed it; a
|
|
686
|
+
* site never named it and could not.
|
|
464
687
|
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
* service would have been served by the default instead, which is the
|
|
470
|
-
* substitution `NO_PAYLOAD_ROUTING` forbids.
|
|
688
|
+
* It used to be what the site asked for, which made a job that selected a
|
|
689
|
+
* non-default service liable to be served by the default instead — the
|
|
690
|
+
* substitution `NO_PAYLOAD_ROUTING` forbids. Amendment L removed the
|
|
691
|
+
* asking; what is left is the answering.
|
|
471
692
|
*
|
|
472
|
-
* Optional, because
|
|
473
|
-
*
|
|
693
|
+
* Optional, because direct mode has no control plane to resolve anything
|
|
694
|
+
* and the owner's own defaults answer under the ambiguity law.
|
|
474
695
|
*/
|
|
475
|
-
service:
|
|
696
|
+
service: z6.string().min(1).optional(),
|
|
476
697
|
lease: Lease
|
|
477
698
|
}).strict();
|
|
478
|
-
var ResultProvenance =
|
|
699
|
+
var ResultProvenance = z6.object({
|
|
479
700
|
/** The audience the job ran under. */
|
|
480
701
|
audience: Audience,
|
|
481
702
|
/** The runner that produced it. */
|
|
482
|
-
runnerId:
|
|
703
|
+
runnerId: z6.string().min(1),
|
|
483
704
|
/** The runner owner's id in this app's namespace. */
|
|
484
|
-
runnerOwner:
|
|
705
|
+
runnerOwner: z6.string().min(1),
|
|
485
706
|
/** Which backend class produced it — an HTTP call or a sandboxed spawn. */
|
|
486
707
|
backendClass: BackendClass,
|
|
487
708
|
/** The model the runner reports having used. */
|
|
488
|
-
model:
|
|
709
|
+
model: z6.string().min(1),
|
|
489
710
|
/**
|
|
490
711
|
* False only for `self` jobs. When true the app MUST treat `text` as
|
|
491
712
|
* untrusted third-party content.
|
|
492
713
|
*/
|
|
493
|
-
untrusted:
|
|
714
|
+
untrusted: z6.boolean()
|
|
494
715
|
}).strict();
|
|
495
716
|
function provenanceFor(input) {
|
|
496
717
|
return {
|
|
@@ -502,62 +723,47 @@ function provenanceFor(input) {
|
|
|
502
723
|
untrusted: input.audience !== "private"
|
|
503
724
|
};
|
|
504
725
|
}
|
|
505
|
-
var RunMetadata =
|
|
726
|
+
var RunMetadata = z6.object({
|
|
506
727
|
/** Which model actually served it. */
|
|
507
|
-
model:
|
|
728
|
+
model: z6.string().min(1),
|
|
508
729
|
backendClass: BackendClass,
|
|
509
730
|
/** Wall-clock milliseconds the backend call took. */
|
|
510
|
-
durationMs:
|
|
731
|
+
durationMs: z6.number().int().nonnegative()
|
|
511
732
|
}).strict();
|
|
512
|
-
var JobResultOk =
|
|
513
|
-
outcome:
|
|
514
|
-
text:
|
|
733
|
+
var JobResultOk = z6.object({
|
|
734
|
+
outcome: z6.literal("ok"),
|
|
735
|
+
text: z6.string(),
|
|
515
736
|
/** Optional reference to a stored artifact; never a local path. */
|
|
516
|
-
artifactUrl:
|
|
737
|
+
artifactUrl: z6.url().optional()
|
|
517
738
|
}).strict();
|
|
518
|
-
var JobResultError =
|
|
519
|
-
outcome:
|
|
520
|
-
code:
|
|
521
|
-
message:
|
|
739
|
+
var JobResultError = z6.object({
|
|
740
|
+
outcome: z6.literal("error"),
|
|
741
|
+
code: z6.string().min(1),
|
|
742
|
+
message: z6.string().min(1),
|
|
522
743
|
/** Whether the app may reasonably re-enqueue. */
|
|
523
|
-
retryable:
|
|
744
|
+
retryable: z6.boolean()
|
|
524
745
|
}).strict();
|
|
525
|
-
var JobResultCanceled =
|
|
526
|
-
outcome:
|
|
746
|
+
var JobResultCanceled = z6.object({
|
|
747
|
+
outcome: z6.literal("canceled")
|
|
527
748
|
}).strict();
|
|
528
|
-
var JobOutcome =
|
|
749
|
+
var JobOutcome = z6.discriminatedUnion("outcome", [
|
|
529
750
|
JobResultOk,
|
|
530
751
|
JobResultError,
|
|
531
752
|
JobResultCanceled
|
|
532
753
|
]);
|
|
533
|
-
var RefusalReason =
|
|
534
|
-
/**
|
|
535
|
-
* A selection this requester cannot be served — byollm_016 Phase B.
|
|
536
|
-
*
|
|
537
|
-
* **One value for two causes, and the collapse is the security property.**
|
|
538
|
-
* A named service may be unknown to this owner, or known and not offered to
|
|
539
|
-
* this requester. Those are different facts and the requester may learn
|
|
540
|
-
* neither, because telling them apart turns refusal wording into an
|
|
541
|
-
* inventory oracle: probe names, sort the answers, and enumerate a device
|
|
542
|
-
* you were never offered. The finer cause lives owner-side, where the person
|
|
543
|
-
* reading it already owns the machine — see {@link SelectionFailure}.
|
|
544
|
-
*
|
|
545
|
-
* The first draft of this enum had both causes on the wire with a comment
|
|
546
|
-
* claiming they disclosed identically. They did not; the comment described a
|
|
547
|
-
* property the code lacked, which is the more dangerous half of that mistake.
|
|
548
|
-
*/
|
|
549
|
-
"select-unavailable",
|
|
754
|
+
var RefusalReason = z6.enum([
|
|
550
755
|
/**
|
|
551
756
|
* Two or more services answer this kind and the owner has named no default,
|
|
552
757
|
* so the kind is withheld. Nobody may pick on the owner's behalf — the wrong
|
|
553
758
|
* guess is the metered one.
|
|
554
759
|
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
760
|
+
* Told apart from its neighbour deliberately, and the line is whether a
|
|
761
|
+
* requester can walk a namespace. There are two kinds; asking about one
|
|
762
|
+
* enumerates nothing they could not learn from what the device advertises,
|
|
763
|
+
* and the difference is actionable — "the owner has not chosen" is fixable
|
|
764
|
+
* by the owner, "the default cannot serve you" is not. It is also already
|
|
765
|
+
* what a team member sees on the devices page: `awaitingDefault` carries
|
|
766
|
+
* exactly this, by kind, for exactly this reason.
|
|
561
767
|
*/
|
|
562
768
|
"default-ambiguity",
|
|
563
769
|
/**
|
|
@@ -566,34 +772,28 @@ var RefusalReason = z4.enum([
|
|
|
566
772
|
*
|
|
567
773
|
* The specimen: an owner's default for `llm.chat` is their Claude
|
|
568
774
|
* subscription, self-locked by `SUBSCRIPTION_SELF_LOCK`. A team member's
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
*
|
|
775
|
+
* job resolves to it and can never be served by it. That must be a refusal
|
|
776
|
+
* on the spot, not a wait that expires an hour later looking like nobody
|
|
777
|
+
* was online.
|
|
572
778
|
*
|
|
573
|
-
* Bounded like the value above and
|
|
574
|
-
*
|
|
779
|
+
* Bounded like the value above, and unprobeable for the same reason: the
|
|
780
|
+
* requester named nothing, so there is no name space to walk.
|
|
575
781
|
*/
|
|
576
782
|
"default-unusable"
|
|
577
783
|
]);
|
|
578
|
-
var JobRefused =
|
|
579
|
-
outcome:
|
|
784
|
+
var JobRefused = z6.object({
|
|
785
|
+
outcome: z6.literal("refused"),
|
|
580
786
|
reason: RefusalReason,
|
|
581
787
|
/** Plain words for a human reading a log, never parsed. */
|
|
582
|
-
message:
|
|
788
|
+
message: z6.string().min(1)
|
|
583
789
|
}).strict();
|
|
584
790
|
var REFUSAL_TEXT = Object.freeze({
|
|
585
|
-
"select-unavailable": "that service is not available to you on this device",
|
|
586
791
|
"default-ambiguity": "this device serves that kind from more than one service and its owner has not chosen which",
|
|
587
792
|
"default-unusable": "this device's default for that kind cannot run work for you"
|
|
588
793
|
});
|
|
589
|
-
var
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
message: REFUSAL_TEXT["select-unavailable"]
|
|
593
|
-
});
|
|
594
|
-
var SealedOutcome = z4.object({ outcome: JobOutcome, ran: RunMetadata }).strict();
|
|
595
|
-
var DeliveredResult = z4.object({
|
|
596
|
-
jobId: z4.string().min(1),
|
|
794
|
+
var SealedOutcome = z6.object({ outcome: JobOutcome, ran: RunMetadata }).strict();
|
|
795
|
+
var DeliveredResult = z6.object({
|
|
796
|
+
jobId: z6.string().min(1),
|
|
597
797
|
state: JobState,
|
|
598
798
|
outcome: JobOutcome.optional(),
|
|
599
799
|
provenance: ResultProvenance.optional(),
|
|
@@ -611,9 +811,9 @@ var DeliveredResult = z4.object({
|
|
|
611
811
|
* runner ran the job, and the *server* stamps it — an app cannot supply
|
|
612
812
|
* a substitute that hides what it is.
|
|
613
813
|
*/
|
|
614
|
-
fallback:
|
|
814
|
+
fallback: z6.literal(true).optional()
|
|
615
815
|
}).strict();
|
|
616
|
-
var SizeClass =
|
|
816
|
+
var SizeClass = z6.enum(["small", "medium", "large", "unbounded"]);
|
|
617
817
|
var SIZE_CLASS_LIMITS = Object.freeze({
|
|
618
818
|
small: 4e3,
|
|
619
819
|
medium: 64e3,
|
|
@@ -628,11 +828,11 @@ function sizeClassOf(textChars) {
|
|
|
628
828
|
if (textChars <= SIZE_CLASS_LIMITS.medium) return "medium";
|
|
629
829
|
return "large";
|
|
630
830
|
}
|
|
631
|
-
var JobStub =
|
|
632
|
-
id:
|
|
831
|
+
var JobStub = z6.object({
|
|
832
|
+
id: z6.string().min(1),
|
|
633
833
|
kind: JobKind,
|
|
634
834
|
/** The app's id for the user who enqueued it. */
|
|
635
|
-
owner:
|
|
835
|
+
owner: z6.string().min(1),
|
|
636
836
|
/**
|
|
637
837
|
* Which site this job belongs to — byollm_009 Amendment A §A.3.
|
|
638
838
|
*
|
|
@@ -656,13 +856,13 @@ var JobStub = z4.object({
|
|
|
656
856
|
* overlap window, and a daemon re-keys its own map by verifying that
|
|
657
857
|
* signature against the key it already pinned (§A.3.1).
|
|
658
858
|
*/
|
|
659
|
-
site:
|
|
859
|
+
site: z6.string().min(1),
|
|
660
860
|
audience: Audience,
|
|
661
861
|
// `audienceAllow` is **not** here, and its absence is the enforcement —
|
|
662
862
|
// cloud_008 §0.2.
|
|
663
863
|
//
|
|
664
864
|
// It was a list of the people who may run a job, travelling to every
|
|
665
|
-
// routing party on every
|
|
865
|
+
// routing party on every shared job. byollm_001 Rev 1 §B settled who
|
|
666
866
|
// decides that long before this schema existed: *the daemon's own list
|
|
667
867
|
// decides, not the server's*, and `allowlist.predicateFor(origin)` is the
|
|
668
868
|
// enforcement in both lanes. So this was a second answer to a question the
|
|
@@ -679,181 +879,68 @@ var JobStub = z4.object({
|
|
|
679
879
|
// with it before offering. That is server-internal, where the party
|
|
680
880
|
// holding the list authored it.
|
|
681
881
|
/**
|
|
682
|
-
* Which of the
|
|
683
|
-
*
|
|
684
|
-
* **A
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
882
|
+
* Which of the site's declared purposes this job serves — Amendment L.
|
|
883
|
+
*
|
|
884
|
+
* **A need, never a name.** The site's vocabulary is its own purposes;
|
|
885
|
+
* the person's is their services; and the two never meet. This field says
|
|
886
|
+
* "writing-assistant", and a control plane joins it to whatever that
|
|
887
|
+
* person mapped it to. The site learns only whether the slot was
|
|
888
|
+
* satisfiable.
|
|
889
|
+
*
|
|
890
|
+
* It replaced `service`, which let a site name one of the owner's
|
|
891
|
+
* services directly. That field is gone from both routes (Amendment L
|
|
892
|
+
* rider) and its refusal machinery with it — including the collapsed
|
|
893
|
+
* `select-unavailable`, which existed so that "no such service" and "not
|
|
894
|
+
* offered to you" could not be told apart. There is nothing left to
|
|
895
|
+
* probe: **a vocabulary that never crosses the boundary cannot be
|
|
896
|
+
* enumerated across it**, which is a stronger guarantee than the one the
|
|
897
|
+
* collapse gave.
|
|
898
|
+
*
|
|
899
|
+
* It travels for the reason the absent `audienceAllow` establishes: *a
|
|
900
|
+
* class the router acts on may travel; membership never does.* A purpose
|
|
901
|
+
* is a class, and the control plane acts on it.
|
|
902
|
+
*
|
|
903
|
+
* Optional because direct mode has no control plane to hold a mapping and
|
|
904
|
+
* is kind-only: the owner's own config and defaults answer, under the
|
|
905
|
+
* ambiguity law as shipped. Absent on a relayed route resolves against
|
|
906
|
+
* the site's reserved purpose, which a site that declared its own
|
|
907
|
+
* purposes will not have mapped — so the slot reads as unmapped and the
|
|
908
|
+
* site falls back, loudly enough and without a special case.
|
|
909
|
+
*
|
|
910
|
+
* A **stub** field and never a payload field, which is the line
|
|
701
911
|
* `NO_PAYLOAD_ROUTING` draws: the prompt cannot reach it, so no amount of
|
|
702
912
|
* user text can influence what runs.
|
|
703
913
|
*/
|
|
704
|
-
|
|
914
|
+
purpose: z6.string().min(1).optional(),
|
|
705
915
|
sizeClass: SizeClass,
|
|
706
916
|
/** Reserved for byollm_006. False until streaming exists. */
|
|
707
|
-
streaming:
|
|
917
|
+
streaming: z6.boolean(),
|
|
708
918
|
/** Epoch ms after which the work is pointless; bounds ciphertext retention. */
|
|
709
|
-
deadlineAt:
|
|
919
|
+
deadlineAt: z6.number().int().positive()
|
|
920
|
+
}).strict();
|
|
921
|
+
var ClaimedStub = JobStub.extend({
|
|
922
|
+
lease: Lease,
|
|
923
|
+
grant: SignedGrant.optional()
|
|
710
924
|
}).strict();
|
|
711
|
-
var ClaimedStub = JobStub.extend({ lease: Lease }).strict();
|
|
712
925
|
|
|
713
926
|
// src/envelope.ts
|
|
714
927
|
import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2 } from "crypto";
|
|
715
928
|
import sodium from "libsodium-wrappers";
|
|
716
|
-
import { z as
|
|
717
|
-
|
|
718
|
-
// src/keys.ts
|
|
719
|
-
import {
|
|
720
|
-
createHash,
|
|
721
|
-
createPrivateKey,
|
|
722
|
-
createPublicKey,
|
|
723
|
-
generateKeyPairSync,
|
|
724
|
-
sign,
|
|
725
|
-
verify
|
|
726
|
-
} from "crypto";
|
|
727
|
-
import { z as z5 } from "zod";
|
|
728
|
-
var PublicIdentity = z5.object({
|
|
729
|
-
/** Raw Ed25519 public key. The pinned one. */
|
|
730
|
-
identity: z5.string().min(1),
|
|
731
|
-
/** Raw X25519 public key, for sealing to this party. */
|
|
732
|
-
encryption: z5.string().min(1),
|
|
733
|
-
/**
|
|
734
|
-
* Ed25519 signature over the encryption key, by the identity key.
|
|
735
|
-
*
|
|
736
|
-
* This is what stops an upstream substituting an encryption key of its
|
|
737
|
-
* own while relaying a genuine identity: the receiver pins the identity
|
|
738
|
-
* and refuses any encryption key not signed by it.
|
|
739
|
-
*/
|
|
740
|
-
encryptionSig: z5.string().min(1)
|
|
741
|
-
}).strict();
|
|
742
|
-
var StoredKeys = z5.object({
|
|
743
|
-
version: z5.literal(1),
|
|
744
|
-
identityPublic: z5.string().min(1),
|
|
745
|
-
identityPrivate: z5.string().min(1),
|
|
746
|
-
encryptionPublic: z5.string().min(1),
|
|
747
|
-
encryptionPrivate: z5.string().min(1),
|
|
748
|
-
encryptionSig: z5.string().min(1),
|
|
749
|
-
createdAt: z5.number().int().positive()
|
|
750
|
-
}).strict();
|
|
751
|
-
var ENCRYPTION_KEY_CONTEXT = "byollm/v1/encryption-key";
|
|
752
|
-
function rawPublic(key) {
|
|
753
|
-
const jwk = key.export({ format: "jwk" });
|
|
754
|
-
const x = jwk.x;
|
|
755
|
-
if (typeof x !== "string") throw new Error("key has no raw public component");
|
|
756
|
-
return x;
|
|
757
|
-
}
|
|
758
|
-
function importPublic(raw, crv) {
|
|
759
|
-
return createPublicKey({ key: { kty: "OKP", crv, x: raw }, format: "jwk" });
|
|
760
|
-
}
|
|
761
|
-
function importPrivate(stored) {
|
|
762
|
-
return createPrivateKey({
|
|
763
|
-
key: Buffer.from(stored, "base64"),
|
|
764
|
-
type: "pkcs8",
|
|
765
|
-
format: "der"
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
var exportPrivate = (key) => key.export({ type: "pkcs8", format: "der" }).toString("base64");
|
|
769
|
-
function generateKeys(now) {
|
|
770
|
-
const identity = generateKeyPairSync("ed25519");
|
|
771
|
-
const encryption = generateKeyPairSync("x25519");
|
|
772
|
-
const encryptionPublic = rawPublic(encryption.publicKey);
|
|
773
|
-
return {
|
|
774
|
-
version: 1,
|
|
775
|
-
identityPublic: rawPublic(identity.publicKey),
|
|
776
|
-
identityPrivate: exportPrivate(identity.privateKey),
|
|
777
|
-
encryptionPublic,
|
|
778
|
-
encryptionPrivate: exportPrivate(encryption.privateKey),
|
|
779
|
-
encryptionSig: sign(
|
|
780
|
-
null,
|
|
781
|
-
Buffer.from(`${ENCRYPTION_KEY_CONTEXT}:${encryptionPublic}`),
|
|
782
|
-
identity.privateKey
|
|
783
|
-
).toString("base64url"),
|
|
784
|
-
createdAt: now
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
function publicIdentityOf(keys) {
|
|
788
|
-
return {
|
|
789
|
-
identity: keys.identityPublic,
|
|
790
|
-
encryption: keys.encryptionPublic,
|
|
791
|
-
encryptionSig: keys.encryptionSig
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
function verifyPublicIdentity(identity) {
|
|
795
|
-
try {
|
|
796
|
-
return verify(
|
|
797
|
-
null,
|
|
798
|
-
Buffer.from(`${ENCRYPTION_KEY_CONTEXT}:${identity.encryption}`),
|
|
799
|
-
importPublic(identity.identity, "Ed25519"),
|
|
800
|
-
Buffer.from(identity.encryptionSig, "base64url")
|
|
801
|
-
);
|
|
802
|
-
} catch {
|
|
803
|
-
return false;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
function signWith(keys, data) {
|
|
807
|
-
return sign(null, data, importPrivate(keys.identityPrivate)).toString(
|
|
808
|
-
"base64url"
|
|
809
|
-
);
|
|
810
|
-
}
|
|
811
|
-
function verifyWith(identityPublic, data, signature) {
|
|
812
|
-
try {
|
|
813
|
-
return verify(
|
|
814
|
-
null,
|
|
815
|
-
data,
|
|
816
|
-
importPublic(identityPublic, "Ed25519"),
|
|
817
|
-
Buffer.from(signature, "base64url")
|
|
818
|
-
);
|
|
819
|
-
} catch {
|
|
820
|
-
return false;
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
824
|
-
function fingerprint(identityPublic) {
|
|
825
|
-
const digest = createHash("sha256").update(Buffer.from(identityPublic, "base64url")).digest();
|
|
826
|
-
let bits = 0;
|
|
827
|
-
let value = 0;
|
|
828
|
-
let out = "";
|
|
829
|
-
for (const byte of digest.subarray(0, 15)) {
|
|
830
|
-
value = value << 8 | byte;
|
|
831
|
-
bits += 8;
|
|
832
|
-
while (bits >= 5) {
|
|
833
|
-
out += ALPHABET.charAt(value >>> bits - 5 & 31);
|
|
834
|
-
bits -= 5;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
const groups = out.match(/.{1,4}/g) ?? [];
|
|
838
|
-
return `BYOLLM-${groups.join("-")}`;
|
|
839
|
-
}
|
|
840
|
-
var keyId = (identityPublic) => fingerprint(identityPublic);
|
|
841
|
-
|
|
842
|
-
// src/envelope.ts
|
|
929
|
+
import { z as z7 } from "zod";
|
|
843
930
|
var readied;
|
|
844
931
|
async function cryptoReady() {
|
|
845
932
|
readied ??= sodium.ready;
|
|
846
933
|
await readied;
|
|
847
934
|
}
|
|
848
935
|
var ENVELOPE_MAX_AGE_MS = 24 * 60 * 6e4;
|
|
849
|
-
var EnvelopeDirection =
|
|
850
|
-
var SealedEnvelope =
|
|
936
|
+
var EnvelopeDirection = z7.enum(["payload", "result"]);
|
|
937
|
+
var SealedEnvelope = z7.object({
|
|
851
938
|
/** Base64url `crypto_box_seal` output over the signed plaintext. */
|
|
852
|
-
ciphertext:
|
|
939
|
+
ciphertext: z7.string().min(1),
|
|
853
940
|
/** Who this was sealed to — the recipient checks it is them. */
|
|
854
|
-
recipientKeyId:
|
|
941
|
+
recipientKeyId: z7.string().min(1),
|
|
855
942
|
/** Who signed it — the recipient checks this against its pin. */
|
|
856
|
-
senderKeyId:
|
|
943
|
+
senderKeyId: z7.string().min(1),
|
|
857
944
|
direction: EnvelopeDirection,
|
|
858
945
|
/**
|
|
859
946
|
* When this ciphertext stops being worth keeping.
|
|
@@ -867,7 +954,7 @@ var SealedEnvelope = z6.object({
|
|
|
867
954
|
* Not trusted as written: it is also inside the signature, so a changed
|
|
868
955
|
* deadline fails to verify.
|
|
869
956
|
*/
|
|
870
|
-
deadlineAt:
|
|
957
|
+
deadlineAt: z7.number().int().positive()
|
|
871
958
|
}).strict();
|
|
872
959
|
function signedBody(context, plaintext) {
|
|
873
960
|
return Buffer.from(
|
|
@@ -962,15 +1049,15 @@ async function open(input) {
|
|
|
962
1049
|
|
|
963
1050
|
// src/signing.ts
|
|
964
1051
|
import { createHash as createHash2 } from "crypto";
|
|
965
|
-
import { z as
|
|
1052
|
+
import { z as z8 } from "zod";
|
|
966
1053
|
var MAX_CLOCK_SKEW_MS = 12e4;
|
|
967
|
-
var RequestSignature =
|
|
1054
|
+
var RequestSignature = z8.object({
|
|
968
1055
|
/** Which runner is calling. The server looks up its pinned identity. */
|
|
969
|
-
runnerId:
|
|
1056
|
+
runnerId: z8.string().min(1),
|
|
970
1057
|
/** Epoch ms, bounded by {@link MAX_CLOCK_SKEW_MS}. */
|
|
971
|
-
issuedAt:
|
|
1058
|
+
issuedAt: z8.number().int().positive(),
|
|
972
1059
|
/** Base64url Ed25519 signature over {@link canonicalRequest}. */
|
|
973
|
-
signature:
|
|
1060
|
+
signature: z8.string().min(1)
|
|
974
1061
|
}).strict();
|
|
975
1062
|
function canonicalRequest(input) {
|
|
976
1063
|
const digest = createHash2("sha256").update(input.body, "utf8").digest("hex");
|
|
@@ -1023,73 +1110,50 @@ function verifyRequest(input) {
|
|
|
1023
1110
|
return ok ? null : "bad-signature";
|
|
1024
1111
|
}
|
|
1025
1112
|
|
|
1026
|
-
// src/
|
|
1027
|
-
import {
|
|
1028
|
-
|
|
1029
|
-
var
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1113
|
+
// src/manifest.ts
|
|
1114
|
+
import { z as z9 } from "zod";
|
|
1115
|
+
var RESERVED_PURPOSE = "default";
|
|
1116
|
+
var PurposeKey = z9.string().regex(
|
|
1117
|
+
/^[a-z0-9][a-z0-9-]*$/,
|
|
1118
|
+
"a purpose key is a lowercase slug \u2014 letters, digits and hyphens"
|
|
1119
|
+
).max(64);
|
|
1120
|
+
var Purpose = z9.object({
|
|
1034
1121
|
/**
|
|
1035
|
-
*
|
|
1122
|
+
* What a person reads on the consent screen. The only rendered field.
|
|
1036
1123
|
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1124
|
+
* Declared rather than derived from the key, because a key is a
|
|
1125
|
+
* compromise between machines and this is not. "Writing Assistant" is
|
|
1126
|
+
* what somebody understands; `writing-assistant` is what travels.
|
|
1040
1127
|
*/
|
|
1041
|
-
|
|
1128
|
+
label: z9.string().min(1).max(80),
|
|
1129
|
+
/** One line of context for the consent screen. Optional. */
|
|
1130
|
+
description: z9.string().min(1).max(280).optional(),
|
|
1042
1131
|
/**
|
|
1043
|
-
*
|
|
1044
|
-
* anchor for age.
|
|
1132
|
+
* The kinds this purpose uses.
|
|
1045
1133
|
*
|
|
1046
|
-
*
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1134
|
+
* A purpose may span kinds, and a mapping is per (purpose, kind) — so a
|
|
1135
|
+
* person can send this purpose's chat to one service and its generation
|
|
1136
|
+
* to another. Listing a kind here is what makes that slot appear.
|
|
1049
1137
|
*/
|
|
1050
|
-
|
|
1051
|
-
/** Base64url Ed25519 over {@link rosterStatement}. */
|
|
1052
|
-
signature: z8.string().min(1)
|
|
1138
|
+
kinds: z9.array(JobKind).min(1)
|
|
1053
1139
|
}).strict();
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
[...input.members].sort().join("\0")
|
|
1061
|
-
].join("\n"),
|
|
1062
|
-
"utf8"
|
|
1063
|
-
);
|
|
1064
|
-
}
|
|
1065
|
-
function signRoster(keys, input) {
|
|
1066
|
-
const members = [...input.members].sort();
|
|
1140
|
+
var Manifest = z9.record(PurposeKey, Purpose).refine((manifest) => Object.keys(manifest).length > 0, {
|
|
1141
|
+
message: "a manifest declares at least one purpose"
|
|
1142
|
+
}).refine((manifest) => !(RESERVED_PURPOSE in manifest), {
|
|
1143
|
+
message: `"${RESERVED_PURPOSE}" is reserved for a site that declares no purposes of its own \u2014 give this one a name from your own vocabulary`
|
|
1144
|
+
});
|
|
1145
|
+
function singlePurposeManifest(input) {
|
|
1067
1146
|
return {
|
|
1068
|
-
|
|
1069
|
-
members,
|
|
1070
|
-
issuedAt: input.issuedAt,
|
|
1071
|
-
signature: signWith(keys, rosterStatement({ ...input, members }))
|
|
1147
|
+
[RESERVED_PURPOSE]: { label: input.label, kinds: [...input.kinds] }
|
|
1072
1148
|
};
|
|
1073
1149
|
}
|
|
1074
|
-
function verifyRoster(input) {
|
|
1075
|
-
const { roster, owner, now } = input;
|
|
1076
|
-
if (roster.owner !== owner) return "wrong-owner";
|
|
1077
|
-
const age = now - roster.issuedAt;
|
|
1078
|
-
if (age < 0) return "from-the-future";
|
|
1079
|
-
if (age > (input.maxAgeMs ?? ROSTER_MAX_AGE_MS)) return "stale";
|
|
1080
|
-
return verifyWith(
|
|
1081
|
-
input.controlPlanePublic,
|
|
1082
|
-
rosterStatement(roster),
|
|
1083
|
-
roster.signature
|
|
1084
|
-
) ? null : "bad-signature";
|
|
1085
|
-
}
|
|
1086
1150
|
|
|
1087
1151
|
// src/succession.ts
|
|
1088
|
-
import { z as
|
|
1152
|
+
import { z as z10 } from "zod";
|
|
1089
1153
|
var SUCCESSION_CONTEXT = "byollm/v1/site-succession";
|
|
1090
1154
|
var RETIREMENT_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1091
1155
|
var MAX_SUCCESSION_CHAIN = 64;
|
|
1092
|
-
var Succession =
|
|
1156
|
+
var Succession = z10.object({
|
|
1093
1157
|
/**
|
|
1094
1158
|
* The predecessor's public identity — K1, in full.
|
|
1095
1159
|
*
|
|
@@ -1099,7 +1163,7 @@ var Succession = z9.object({
|
|
|
1099
1163
|
*/
|
|
1100
1164
|
identity: PublicIdentity,
|
|
1101
1165
|
/** K1's signature over the statement naming K1 and its successor. */
|
|
1102
|
-
signature:
|
|
1166
|
+
signature: z10.string().min(1)
|
|
1103
1167
|
}).strict();
|
|
1104
1168
|
function successionStatement(fromKeyId, toKeyId) {
|
|
1105
1169
|
return Buffer.from(`${SUCCESSION_CONTEXT}:${fromKeyId}:${toKeyId}`);
|
|
@@ -1193,13 +1257,15 @@ var MUSTS = Object.freeze({
|
|
|
1193
1257
|
}),
|
|
1194
1258
|
SITES_LOCALLY_APPROVED: must({
|
|
1195
1259
|
id: "SITES_LOCALLY_APPROVED",
|
|
1196
|
-
statement: "A daemon MUST NOT run work for a site
|
|
1260
|
+
statement: "A daemon MUST NOT run work for a site on an upstream's word alone. An upstream may propose a site set; work for any site in it MUST additionally carry a grant signed by the control-plane key this daemon pinned at pairing. A key that has changed for an id this daemon already pinned MUST be refused for the life of the pairing, including after that id has left the set and returned. A **verified succession** is not a changed key: a new key id carrying a signature, by a key this daemon has already pinned, over a statement naming both key ids MUST be accepted \u2014 provided the control plane projects the same successor \u2014 and MUST be announced rather than applied silently. The first job from a site this daemon has never served MUST be announced at the machine.",
|
|
1197
1261
|
enforcedBy: "daemon",
|
|
1198
1262
|
// Two kinds, and the second is the one that matters — V1-1.
|
|
1199
1263
|
//
|
|
1200
1264
|
// `construction`: the daemon cannot serve a site that is not in its
|
|
1201
|
-
// pinned map, and admission refuses before a payload is fetched
|
|
1202
|
-
//
|
|
1265
|
+
// pinned map, and admission refuses before a payload is fetched — and
|
|
1266
|
+
// since byollm_016 Amendment K, being in the map is no longer sufficient
|
|
1267
|
+
// either: a signed grant is, and the relay proposing the set cannot
|
|
1268
|
+
// produce one.
|
|
1203
1269
|
//
|
|
1204
1270
|
// `adversarial`: the property that survives is about a *sequence* —
|
|
1205
1271
|
// remove the id, re-offer it under a different key — which no honest
|
|
@@ -1352,7 +1418,7 @@ var MUSTS = Object.freeze({
|
|
|
1352
1418
|
}),
|
|
1353
1419
|
NAMED_LOCAL_ALLOWLIST: must({
|
|
1354
1420
|
id: "NAMED_LOCAL_ALLOWLIST",
|
|
1355
|
-
statement: "A '
|
|
1421
|
+
statement: "A 'team' job MUST be admitted only by something the device itself verified, keyed by (server origin, user id) \u2014 never on the routing party's assertion alone.",
|
|
1356
1422
|
enforcedBy: "daemon",
|
|
1357
1423
|
verifiedBy: "conformance",
|
|
1358
1424
|
source: "byollm_001 Rev 1 \xA7B"
|
|
@@ -1578,7 +1644,7 @@ function mustsVerifiedBy(kind) {
|
|
|
1578
1644
|
}
|
|
1579
1645
|
|
|
1580
1646
|
// src/wire.ts
|
|
1581
|
-
import { z as
|
|
1647
|
+
import { z as z11 } from "zod";
|
|
1582
1648
|
var PROTOCOL_VERSION = "0";
|
|
1583
1649
|
var SUPPORTED_PROTOCOL_VERSIONS = Object.freeze([
|
|
1584
1650
|
PROTOCOL_VERSION
|
|
@@ -1621,46 +1687,43 @@ var ENDPOINTS = Object.freeze([
|
|
|
1621
1687
|
"result",
|
|
1622
1688
|
"release"
|
|
1623
1689
|
]);
|
|
1624
|
-
var Capability =
|
|
1690
|
+
var Capability = z11.object({
|
|
1625
1691
|
kind: JobKind,
|
|
1626
1692
|
/**
|
|
1627
1693
|
* The owner's name for the service answering this kind — byollm_016.
|
|
1628
1694
|
*
|
|
1629
1695
|
* A device advertises *which* of its services serves a kind, not merely
|
|
1630
|
-
* that something does.
|
|
1631
|
-
* it is what a
|
|
1696
|
+
* that something does. **A site never sees this**, and never did after
|
|
1697
|
+
* Amendment L: it is what a control plane resolves a person's mapping
|
|
1698
|
+
* against, so that the service a grant names is one this device actually
|
|
1699
|
+
* offers rather than one somebody invented.
|
|
1700
|
+
*
|
|
1701
|
+
* `isDefault` used to sit beside it, saying which row an unselected job
|
|
1702
|
+
* took. Nothing selects any more — a job names a purpose and a person's
|
|
1703
|
+
* mapping names a service — so there is no unselected job for a default
|
|
1704
|
+
* to catch, and the field went with the machinery it served.
|
|
1632
1705
|
*/
|
|
1633
|
-
service:
|
|
1634
|
-
/**
|
|
1635
|
-
* Whether this row is the default for its kind.
|
|
1636
|
-
*
|
|
1637
|
-
* Stated rather than inferred from being the only row, which is true in
|
|
1638
|
-
* Phase A and stops being true the moment Phase B advertises every
|
|
1639
|
-
* selectable service per kind. A consumer that learned "default means
|
|
1640
|
-
* alone" would have to unlearn it, and the ones that did not would be
|
|
1641
|
-
* quietly wrong. One field now, no second shape later.
|
|
1642
|
-
*/
|
|
1643
|
-
isDefault: z10.boolean(),
|
|
1706
|
+
service: z11.string().min(1),
|
|
1644
1707
|
backendId: BackendIdSchema,
|
|
1645
1708
|
backendClass: BackendClass,
|
|
1646
|
-
model:
|
|
1709
|
+
model: z11.string().min(1),
|
|
1647
1710
|
offerScope: OfferScope
|
|
1648
1711
|
}).strict();
|
|
1649
|
-
var CapabilityMatrix =
|
|
1650
|
-
var WithheldKind =
|
|
1712
|
+
var CapabilityMatrix = z11.array(Capability);
|
|
1713
|
+
var WithheldKind = z11.object({
|
|
1651
1714
|
kind: JobKind,
|
|
1652
|
-
claimants:
|
|
1653
|
-
|
|
1715
|
+
claimants: z11.array(
|
|
1716
|
+
z11.object({ service: z11.string().min(1), offer: OfferScope }).strict()
|
|
1654
1717
|
).min(2)
|
|
1655
1718
|
}).strict();
|
|
1656
|
-
var PairStartRequest =
|
|
1657
|
-
protocolVersion:
|
|
1658
|
-
action:
|
|
1659
|
-
daemon:
|
|
1660
|
-
version:
|
|
1719
|
+
var PairStartRequest = z11.object({
|
|
1720
|
+
protocolVersion: z11.literal(PROTOCOL_VERSION),
|
|
1721
|
+
action: z11.literal("start"),
|
|
1722
|
+
daemon: z11.object({
|
|
1723
|
+
version: z11.string().min(1),
|
|
1661
1724
|
/** Shown in the app's runner list so a user can tell their machines apart. */
|
|
1662
|
-
label:
|
|
1663
|
-
platform:
|
|
1725
|
+
label: z11.string().min(1).max(120),
|
|
1726
|
+
platform: z11.enum(["darwin", "linux", "win32"])
|
|
1664
1727
|
}),
|
|
1665
1728
|
/**
|
|
1666
1729
|
* This machine's public keys (byollm_009 §5).
|
|
@@ -1672,29 +1735,29 @@ var PairStartRequest = z10.object({
|
|
|
1672
1735
|
device: PublicIdentity,
|
|
1673
1736
|
capabilities: CapabilityMatrix
|
|
1674
1737
|
}).strict();
|
|
1675
|
-
var PairStartResponse =
|
|
1738
|
+
var PairStartResponse = z11.object({
|
|
1676
1739
|
/** Secret the daemon polls with. Never shown to the user. */
|
|
1677
|
-
deviceCode:
|
|
1740
|
+
deviceCode: z11.string().min(20),
|
|
1678
1741
|
/** Short code the user reads and confirms in the browser. */
|
|
1679
|
-
userCode:
|
|
1742
|
+
userCode: z11.string().min(4).max(16),
|
|
1680
1743
|
/** Where the user approves. Must be on the server's own origin. */
|
|
1681
|
-
verificationUrl:
|
|
1744
|
+
verificationUrl: z11.url(),
|
|
1682
1745
|
/** Epoch ms after which the code is dead ({@link MUSTS.PAIR_CODE_EXPIRES}). */
|
|
1683
|
-
expiresAt:
|
|
1746
|
+
expiresAt: z11.number().int().positive(),
|
|
1684
1747
|
/** How often the daemon may poll. */
|
|
1685
|
-
pollIntervalMs:
|
|
1748
|
+
pollIntervalMs: z11.number().int().min(500).max(6e4)
|
|
1686
1749
|
}).strict();
|
|
1687
|
-
var PairPollRequest =
|
|
1688
|
-
protocolVersion:
|
|
1689
|
-
action:
|
|
1690
|
-
deviceCode:
|
|
1750
|
+
var PairPollRequest = z11.object({
|
|
1751
|
+
protocolVersion: z11.literal(PROTOCOL_VERSION),
|
|
1752
|
+
action: z11.literal("poll"),
|
|
1753
|
+
deviceCode: z11.string().min(20)
|
|
1691
1754
|
}).strict();
|
|
1692
|
-
var PairPollResponse =
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
status:
|
|
1755
|
+
var PairPollResponse = z11.discriminatedUnion("status", [
|
|
1756
|
+
z11.object({ status: z11.literal("pending") }).strict(),
|
|
1757
|
+
z11.object({ status: z11.literal("denied") }).strict(),
|
|
1758
|
+
z11.object({ status: z11.literal("expired") }).strict(),
|
|
1759
|
+
z11.object({
|
|
1760
|
+
status: z11.literal("approved"),
|
|
1698
1761
|
// `runnerToken` is gone — cloud_008 §2.4, finding 37.
|
|
1699
1762
|
//
|
|
1700
1763
|
// It was minted here, hashed into `RunnerRecord.tokenHash`, written to
|
|
@@ -1711,11 +1774,11 @@ var PairPollResponse = z10.discriminatedUnion("status", [
|
|
|
1711
1774
|
// `REQUESTS_SIGNED_NOT_BEARER` was already the rule and was already
|
|
1712
1775
|
// enforced — every authenticated call is signed by the device's pinned
|
|
1713
1776
|
// identity key. This removes the thing the MUST is named after.
|
|
1714
|
-
runnerId:
|
|
1777
|
+
runnerId: z11.string().min(1),
|
|
1715
1778
|
/** The app's id for the approving user — this daemon's owner forever. */
|
|
1716
|
-
owner:
|
|
1779
|
+
owner: z11.string().min(1),
|
|
1717
1780
|
/** Display name for the trust UI, if the app offers one. */
|
|
1718
|
-
ownerLabel:
|
|
1781
|
+
ownerLabel: z11.string().optional(),
|
|
1719
1782
|
/**
|
|
1720
1783
|
* The sites this pairing covers, for the daemon to pin (byollm_009 §5),
|
|
1721
1784
|
* keyed by each site's identity key id — cloud_009 §5.
|
|
@@ -1734,55 +1797,55 @@ var PairPollResponse = z10.discriminatedUnion("status", [
|
|
|
1734
1797
|
* runner's lookup is a map read rather than a join across two
|
|
1735
1798
|
* namespaces.
|
|
1736
1799
|
*/
|
|
1737
|
-
sites:
|
|
1800
|
+
sites: z11.record(z11.string().min(1), PublicIdentity),
|
|
1738
1801
|
/**
|
|
1739
|
-
* The control plane's
|
|
1802
|
+
* The control plane's grant-signing key, pinned here — Amendment J.
|
|
1740
1803
|
*
|
|
1741
1804
|
* **Pairing is when, and that is the whole question.** Pairing is
|
|
1742
1805
|
* already the ceremony where an owner proves out of band that this
|
|
1743
1806
|
* device is theirs, so a key learned here rides trust that has already
|
|
1744
|
-
* happened. The rejected alternative is trust-on-first-
|
|
1745
|
-
*
|
|
1746
|
-
* that learns whose signature to trust from the first
|
|
1747
|
-
* has its
|
|
1807
|
+
* happened. The rejected alternative is trust-on-first-grant, and it is
|
|
1808
|
+
* rejected because it hands the decision back to the relay: a daemon
|
|
1809
|
+
* that learns whose signature to trust from the first grant to arrive
|
|
1810
|
+
* has its admission authority chosen by whoever controls delivery.
|
|
1748
1811
|
*
|
|
1749
1812
|
* Optional on the wire, and only on the wire: a direct-mode server has
|
|
1750
|
-
* no control plane and signs
|
|
1751
|
-
*
|
|
1752
|
-
*
|
|
1753
|
-
*
|
|
1813
|
+
* no control plane and signs nothing, and a daemon that receives no key
|
|
1814
|
+
* serves its owner alone. It is not optional for a relay with a control
|
|
1815
|
+
* plane — one that omitted it would be asking devices to accept grants
|
|
1816
|
+
* from nobody in particular, and would find every job refused.
|
|
1754
1817
|
*
|
|
1755
|
-
* Rotation is Amendment C's, with no path where a
|
|
1818
|
+
* Rotation is Amendment C's, with no path where a grant teaches a
|
|
1756
1819
|
* daemon a new key.
|
|
1757
1820
|
*/
|
|
1758
|
-
controlPlanePublic:
|
|
1821
|
+
controlPlanePublic: z11.string().min(1).optional()
|
|
1759
1822
|
}).strict()
|
|
1760
1823
|
]);
|
|
1761
|
-
var PairRequest =
|
|
1824
|
+
var PairRequest = z11.discriminatedUnion("action", [
|
|
1762
1825
|
PairStartRequest,
|
|
1763
1826
|
PairPollRequest
|
|
1764
1827
|
]);
|
|
1765
|
-
var ClaimRequest =
|
|
1766
|
-
protocolVersion:
|
|
1767
|
-
runnerId:
|
|
1828
|
+
var ClaimRequest = z11.object({
|
|
1829
|
+
protocolVersion: z11.literal(PROTOCOL_VERSION),
|
|
1830
|
+
runnerId: z11.string().min(1),
|
|
1768
1831
|
/** Re-sent on every claim so a server never matches against a stale matrix. */
|
|
1769
1832
|
capabilities: CapabilityMatrix,
|
|
1770
1833
|
/** Upper bound on jobs to return; the server may return fewer. */
|
|
1771
|
-
max:
|
|
1834
|
+
max: z11.number().int().min(1).max(64)
|
|
1772
1835
|
}).strict();
|
|
1773
|
-
var ClaimResponse =
|
|
1836
|
+
var ClaimResponse = z11.object({
|
|
1774
1837
|
/**
|
|
1775
1838
|
* Stubs, not jobs. The payload arrives from `fetch`, sealed to whichever
|
|
1776
1839
|
* device claimed — see {@link JobStub} for the exhaustive metadata list.
|
|
1777
1840
|
*/
|
|
1778
|
-
jobs:
|
|
1841
|
+
jobs: z11.array(ClaimedStub),
|
|
1779
1842
|
/** Lease duration granted, so the daemon knows its renewal deadline. */
|
|
1780
|
-
leaseMs:
|
|
1843
|
+
leaseMs: z11.number().int().positive()
|
|
1781
1844
|
}).strict();
|
|
1782
|
-
var HeartbeatRequest =
|
|
1783
|
-
protocolVersion:
|
|
1784
|
-
runnerId:
|
|
1785
|
-
daemonVersion:
|
|
1845
|
+
var HeartbeatRequest = z11.object({
|
|
1846
|
+
protocolVersion: z11.literal(PROTOCOL_VERSION),
|
|
1847
|
+
runnerId: z11.string().min(1),
|
|
1848
|
+
daemonVersion: z11.string().min(1),
|
|
1786
1849
|
capabilities: CapabilityMatrix,
|
|
1787
1850
|
/**
|
|
1788
1851
|
* Kinds this device is withholding, and why it can be said.
|
|
@@ -1791,20 +1854,20 @@ var HeartbeatRequest = z10.object({
|
|
|
1791
1854
|
* older daemon against a newer hub is simply a device with no withheld
|
|
1792
1855
|
* kinds rather than a parse failure.
|
|
1793
1856
|
*/
|
|
1794
|
-
withheld:
|
|
1857
|
+
withheld: z11.array(WithheldKind).default([]),
|
|
1795
1858
|
/**
|
|
1796
1859
|
* Leases this daemon believes it holds; the server renews exactly these.
|
|
1797
1860
|
*
|
|
1798
1861
|
* Lease ids rather than job ids, so a replayed heartbeat cannot renew a
|
|
1799
1862
|
* grant the runner no longer holds — see {@link Lease.id}.
|
|
1800
1863
|
*/
|
|
1801
|
-
activeLeases:
|
|
1802
|
-
|
|
1864
|
+
activeLeases: z11.array(
|
|
1865
|
+
z11.object({ jobId: z11.string().min(1), leaseId: z11.string().min(1) })
|
|
1803
1866
|
),
|
|
1804
1867
|
/** True while the owner has the daemon paused; the server stops offering work. */
|
|
1805
|
-
paused:
|
|
1868
|
+
paused: z11.boolean()
|
|
1806
1869
|
}).strict();
|
|
1807
|
-
var HeartbeatResponse =
|
|
1870
|
+
var HeartbeatResponse = z11.object({
|
|
1808
1871
|
/**
|
|
1809
1872
|
* The sites this daemon may serve, right now — cloud_008 finding 59.
|
|
1810
1873
|
*
|
|
@@ -1822,7 +1885,7 @@ var HeartbeatResponse = z10.object({
|
|
|
1822
1885
|
* rather than being told a second time — two fields for one fact is how
|
|
1823
1886
|
* they drift.
|
|
1824
1887
|
*/
|
|
1825
|
-
sites:
|
|
1888
|
+
sites: z11.record(z11.string().min(1), PublicIdentity),
|
|
1826
1889
|
/**
|
|
1827
1890
|
* How a site's current key traces back to one this daemon already holds —
|
|
1828
1891
|
* byollm_009 Amendment C.
|
|
@@ -1840,11 +1903,11 @@ var HeartbeatResponse = z10.object({
|
|
|
1840
1903
|
* history is public by construction, because a daemon that cannot read it
|
|
1841
1904
|
* cannot verify it.
|
|
1842
1905
|
*/
|
|
1843
|
-
successions:
|
|
1844
|
-
|
|
1845
|
-
|
|
1906
|
+
successions: z11.record(
|
|
1907
|
+
z11.string().min(1),
|
|
1908
|
+
z11.object({
|
|
1846
1909
|
/** Oldest last, as the projection carries it. */
|
|
1847
|
-
succeeds:
|
|
1910
|
+
succeeds: z11.array(Succession).max(MAX_SUCCESSION_CHAIN),
|
|
1848
1911
|
/**
|
|
1849
1912
|
* Until when the superseded key may still sign work — epoch ms.
|
|
1850
1913
|
*
|
|
@@ -1853,7 +1916,7 @@ var HeartbeatResponse = z10.object({
|
|
|
1853
1916
|
* window indefinitely would be a two-key site forever, decided by
|
|
1854
1917
|
* the party this design does not trust.
|
|
1855
1918
|
*/
|
|
1856
|
-
retiringUntil:
|
|
1919
|
+
retiringUntil: z11.number().int().positive().optional()
|
|
1857
1920
|
}).strict()
|
|
1858
1921
|
).optional(),
|
|
1859
1922
|
/**
|
|
@@ -1866,8 +1929,8 @@ var HeartbeatResponse = z10.object({
|
|
|
1866
1929
|
* is the unique grant and the daemon already keys its work by it; this is
|
|
1867
1930
|
* the same shape `activeLeases` sends in the other direction.
|
|
1868
1931
|
*/
|
|
1869
|
-
cancel:
|
|
1870
|
-
|
|
1932
|
+
cancel: z11.array(
|
|
1933
|
+
z11.object({ jobId: z11.string().min(1), leaseId: z11.string().min(1) }).strict()
|
|
1871
1934
|
),
|
|
1872
1935
|
// `leases` is deliberately absent — cloud_008 §1.4b, finding 16.
|
|
1873
1936
|
//
|
|
@@ -1897,11 +1960,11 @@ var HeartbeatResponse = z10.object({
|
|
|
1897
1960
|
* ambiguous across sites, and "the lease you no longer hold" is exactly
|
|
1898
1961
|
* what this field means anyway.
|
|
1899
1962
|
*/
|
|
1900
|
-
lost:
|
|
1901
|
-
|
|
1963
|
+
lost: z11.array(
|
|
1964
|
+
z11.object({ jobId: z11.string().min(1), leaseId: z11.string().min(1) }).strict()
|
|
1902
1965
|
),
|
|
1903
1966
|
/** Server clock, so a daemon with a skewed clock still honors leases. */
|
|
1904
|
-
serverTime:
|
|
1967
|
+
serverTime: z11.number().int().positive(),
|
|
1905
1968
|
/**
|
|
1906
1969
|
* Sites whose disclosure the user must read again before work moves —
|
|
1907
1970
|
* cloud_008 finding 48, named rather than counted.
|
|
@@ -1916,28 +1979,13 @@ var HeartbeatResponse = z10.object({
|
|
|
1916
1979
|
* operator stopped it" — one word with two subjects on two halves of one
|
|
1917
1980
|
* exchange is a confusion nobody untangles from a log.
|
|
1918
1981
|
*/
|
|
1919
|
-
awaitingConsent:
|
|
1920
|
-
/**
|
|
1921
|
-
* Who this owner's devices may serve `team` work for — Amendment G.
|
|
1922
|
-
*
|
|
1923
|
-
* Carried by the relay and authored by nobody it can reach. The daemon
|
|
1924
|
-
* verifies it against the key pinned at pairing and admits from its own
|
|
1925
|
-
* held copy, so this field is delivery and not instruction: withholding
|
|
1926
|
-
* it narrows a device, and editing it is caught.
|
|
1927
|
-
*
|
|
1928
|
-
* Optional because a roster is a cloud-mode fact — direct mode has no
|
|
1929
|
-
* control plane to author one — and because a daemon that has never
|
|
1930
|
-
* received one must narrow rather than fail. Absent is not "admit
|
|
1931
|
-
* nobody"; {@link ROSTER_MAX_AGE_MS} is what makes absence bite, and it
|
|
1932
|
-
* bites the same way for a roster withheld as for one never sent.
|
|
1933
|
-
*/
|
|
1934
|
-
roster: SignedRoster.optional()
|
|
1982
|
+
awaitingConsent: z11.array(z11.string().min(1))
|
|
1935
1983
|
}).strict();
|
|
1936
|
-
var ResultDisposition =
|
|
1937
|
-
var ResultRequest =
|
|
1938
|
-
protocolVersion:
|
|
1939
|
-
runnerId:
|
|
1940
|
-
jobId:
|
|
1984
|
+
var ResultDisposition = z11.enum(["ok", "error", "canceled"]);
|
|
1985
|
+
var ResultRequest = z11.object({
|
|
1986
|
+
protocolVersion: z11.literal(PROTOCOL_VERSION),
|
|
1987
|
+
runnerId: z11.string().min(1),
|
|
1988
|
+
jobId: z11.string().min(1),
|
|
1941
1989
|
/**
|
|
1942
1990
|
* The grant this result was produced under — cloud_008 §1.4a.
|
|
1943
1991
|
*
|
|
@@ -1960,7 +2008,7 @@ var ResultRequest = z10.object({
|
|
|
1960
2008
|
* learned once already, when a replayed release yanked a later grant, and
|
|
1961
2009
|
* it applies here for the same reason.
|
|
1962
2010
|
*/
|
|
1963
|
-
leaseId:
|
|
2011
|
+
leaseId: z11.string().min(1),
|
|
1964
2012
|
/**
|
|
1965
2013
|
* The outcome, sealed to the site and signed by the device.
|
|
1966
2014
|
*
|
|
@@ -1992,12 +2040,12 @@ var ResultRequest = z10.object({
|
|
|
1992
2040
|
// on it, so it is a class a routing party consumes. Nobody between the
|
|
1993
2041
|
// two ends consumes these.
|
|
1994
2042
|
}).strict();
|
|
1995
|
-
var ResultResponse =
|
|
2043
|
+
var ResultResponse = z11.object({
|
|
1996
2044
|
/**
|
|
1997
2045
|
* False when this submission wrote nothing — the daemon should discard,
|
|
1998
2046
|
* not retry ({@link MUSTS.RESULT_IDEMPOTENT}).
|
|
1999
2047
|
*/
|
|
2000
|
-
accepted:
|
|
2048
|
+
accepted: z11.boolean(),
|
|
2001
2049
|
/**
|
|
2002
2050
|
* True when this device had already recorded this job's result.
|
|
2003
2051
|
*
|
|
@@ -2011,13 +2059,13 @@ var ResultResponse = z10.object({
|
|
|
2011
2059
|
* the same refusal it would get for a job that is *not* terminal, so a job
|
|
2012
2060
|
* id cannot be used as a terminality probe.
|
|
2013
2061
|
*/
|
|
2014
|
-
duplicate:
|
|
2062
|
+
duplicate: z11.boolean().optional(),
|
|
2015
2063
|
/** The job's state after this submission. */
|
|
2016
|
-
state:
|
|
2064
|
+
state: z11.string().min(1)
|
|
2017
2065
|
}).strict();
|
|
2018
|
-
var ReleaseRequest =
|
|
2019
|
-
protocolVersion:
|
|
2020
|
-
runnerId:
|
|
2066
|
+
var ReleaseRequest = z11.object({
|
|
2067
|
+
protocolVersion: z11.literal(PROTOCOL_VERSION),
|
|
2068
|
+
runnerId: z11.string().min(1),
|
|
2021
2069
|
/**
|
|
2022
2070
|
* Which leases to release — the grant, not just the job.
|
|
2023
2071
|
*
|
|
@@ -2025,24 +2073,24 @@ var ReleaseRequest = z10.object({
|
|
|
2025
2073
|
* moment it arrives, which for a replayed request is not the lease the
|
|
2026
2074
|
* daemon meant. See {@link Lease.id}.
|
|
2027
2075
|
*/
|
|
2028
|
-
leases:
|
|
2029
|
-
|
|
2076
|
+
leases: z11.array(
|
|
2077
|
+
z11.object({ jobId: z11.string().min(1), leaseId: z11.string().min(1) })
|
|
2030
2078
|
),
|
|
2031
2079
|
/**
|
|
2032
2080
|
* Why, so the app's runner list can say something true.
|
|
2033
2081
|
*
|
|
2034
|
-
* `refused` is load-bearing, not cosmetic: the server cannot evaluate
|
|
2035
|
-
*
|
|
2082
|
+
* `refused` is load-bearing, not cosmetic: the server cannot evaluate
|
|
2083
|
+
* what a device will admit (§4.2), so it may legitimately offer
|
|
2036
2084
|
* a job this daemon then declines. The server MUST record the refusal and
|
|
2037
2085
|
* stop offering that job to that runner, or the pair would spin between
|
|
2038
2086
|
* claim and release forever.
|
|
2039
2087
|
*/
|
|
2040
|
-
reason:
|
|
2088
|
+
reason: z11.enum(["shutdown", "pause", "revoked", "backend-down", "refused"])
|
|
2041
2089
|
}).strict();
|
|
2042
|
-
var ReleaseResponse =
|
|
2043
|
-
released:
|
|
2090
|
+
var ReleaseResponse = z11.object({
|
|
2091
|
+
released: z11.array(z11.string().min(1))
|
|
2044
2092
|
}).strict();
|
|
2045
|
-
var WireErrorCode =
|
|
2093
|
+
var WireErrorCode = z11.enum([
|
|
2046
2094
|
"bad-request",
|
|
2047
2095
|
"unsupported-protocol-version",
|
|
2048
2096
|
// "We do not know who you are." Exactly 401, and only that — cloud_008
|
|
@@ -2092,9 +2140,9 @@ var WireErrorCode = z10.enum([
|
|
|
2092
2140
|
"rate-limited",
|
|
2093
2141
|
"server-error"
|
|
2094
2142
|
]);
|
|
2095
|
-
var WireError =
|
|
2143
|
+
var WireError = z11.object({
|
|
2096
2144
|
error: WireErrorCode,
|
|
2097
|
-
message:
|
|
2145
|
+
message: z11.string().min(1),
|
|
2098
2146
|
/**
|
|
2099
2147
|
* What this server speaks, on `unsupported-protocol-version` — §B.4.
|
|
2100
2148
|
*
|
|
@@ -2108,10 +2156,10 @@ var WireError = z10.object({
|
|
|
2108
2156
|
* Modelled the way `clock-skew`'s two fields already are — code-specific
|
|
2109
2157
|
* extras, refused on any other code by the refinement below.
|
|
2110
2158
|
*/
|
|
2111
|
-
supported:
|
|
2112
|
-
minimum:
|
|
2159
|
+
supported: z11.array(z11.string().min(1)).optional(),
|
|
2160
|
+
minimum: z11.string().min(1).optional(),
|
|
2113
2161
|
/** Seconds; mirrors Retry-After for `rate-limited` and `server-error`. */
|
|
2114
|
-
retryAfter:
|
|
2162
|
+
retryAfter: z11.number().int().nonnegative().optional(),
|
|
2115
2163
|
/**
|
|
2116
2164
|
* The server's clock, and the window it allows. `clock-skew` only.
|
|
2117
2165
|
*
|
|
@@ -2121,8 +2169,8 @@ var WireError = z10.object({
|
|
|
2121
2169
|
* heartbeat response returns the same value, and so does every `Date`
|
|
2122
2170
|
* header.
|
|
2123
2171
|
*/
|
|
2124
|
-
serverTime:
|
|
2125
|
-
maxSkewMs:
|
|
2172
|
+
serverTime: z11.number().int().positive().optional(),
|
|
2173
|
+
maxSkewMs: z11.number().int().positive().optional()
|
|
2126
2174
|
}).strict().superRefine((error, ctx) => {
|
|
2127
2175
|
const skew = error.error === "clock-skew";
|
|
2128
2176
|
const carried = error.serverTime !== void 0 || error.maxSkewMs !== void 0;
|
|
@@ -2173,16 +2221,16 @@ var ERROR_STATUS = Object.freeze({
|
|
|
2173
2221
|
"rate-limited": 429,
|
|
2174
2222
|
"server-error": 500
|
|
2175
2223
|
});
|
|
2176
|
-
var FetchRequest =
|
|
2224
|
+
var FetchRequest = z11.object({
|
|
2177
2225
|
// `literal`, like every other request — V1-17. This one said
|
|
2178
2226
|
// `string().min(1)`, so a daemon speaking a version this server does not
|
|
2179
2227
|
// know got past the handshake on the one endpoint that hands over a
|
|
2180
2228
|
// sealed payload. The version check exists so that a mismatch is a named
|
|
2181
2229
|
// refusal rather than a schema failure three fields later; here it was
|
|
2182
2230
|
// neither.
|
|
2183
|
-
protocolVersion:
|
|
2184
|
-
runnerId:
|
|
2185
|
-
jobId:
|
|
2231
|
+
protocolVersion: z11.literal(PROTOCOL_VERSION),
|
|
2232
|
+
runnerId: z11.string().min(1),
|
|
2233
|
+
jobId: z11.string().min(1),
|
|
2186
2234
|
/**
|
|
2187
2235
|
* The grant this daemon holds.
|
|
2188
2236
|
*
|
|
@@ -2190,9 +2238,9 @@ var FetchRequest = z10.object({
|
|
|
2190
2238
|
* only the job would be answerable for whatever lease exists when it
|
|
2191
2239
|
* arrives ({@link Lease.id}).
|
|
2192
2240
|
*/
|
|
2193
|
-
leaseId:
|
|
2241
|
+
leaseId: z11.string().min(1)
|
|
2194
2242
|
}).strict();
|
|
2195
|
-
var FetchResponse =
|
|
2243
|
+
var FetchResponse = z11.object({
|
|
2196
2244
|
/**
|
|
2197
2245
|
* The work, sealed to the device that claimed it — byollm_009 §6.
|
|
2198
2246
|
*
|
|
@@ -2211,6 +2259,8 @@ export {
|
|
|
2211
2259
|
BackendClass,
|
|
2212
2260
|
BackendCost,
|
|
2213
2261
|
BackendIdSchema,
|
|
2262
|
+
CLOCK_ATTRIBUTION_MS,
|
|
2263
|
+
CLOCK_SKEW_WARN_MS,
|
|
2214
2264
|
Capability,
|
|
2215
2265
|
CapabilityMatrix,
|
|
2216
2266
|
ChatMessage,
|
|
@@ -2227,6 +2277,9 @@ export {
|
|
|
2227
2277
|
EnvelopeDirection,
|
|
2228
2278
|
FetchRequest,
|
|
2229
2279
|
FetchResponse,
|
|
2280
|
+
GRANT_CONTEXT,
|
|
2281
|
+
GRANT_MAX_AGE_MS,
|
|
2282
|
+
GRANT_SIGNED_FIELDS,
|
|
2230
2283
|
GeneratePayload,
|
|
2231
2284
|
HeartbeatRequest,
|
|
2232
2285
|
HeartbeatResponse,
|
|
@@ -2247,6 +2300,7 @@ export {
|
|
|
2247
2300
|
MIN_PROTOCOL_VERSION,
|
|
2248
2301
|
MUSTS,
|
|
2249
2302
|
MUST_IDS,
|
|
2303
|
+
Manifest,
|
|
2250
2304
|
MatchRefusal,
|
|
2251
2305
|
OFFER_SCOPES,
|
|
2252
2306
|
OfferScope,
|
|
@@ -2259,10 +2313,10 @@ export {
|
|
|
2259
2313
|
PairStartRequest,
|
|
2260
2314
|
PairStartResponse,
|
|
2261
2315
|
PublicIdentity,
|
|
2316
|
+
Purpose,
|
|
2262
2317
|
REFUSAL_MESSAGES,
|
|
2318
|
+
RESERVED_PURPOSE,
|
|
2263
2319
|
RETIREMENT_WINDOW_MS,
|
|
2264
|
-
ROSTER_CONTEXT,
|
|
2265
|
-
ROSTER_MAX_AGE_MS,
|
|
2266
2320
|
RefusalReason,
|
|
2267
2321
|
ReleaseRequest,
|
|
2268
2322
|
ReleaseResponse,
|
|
@@ -2277,7 +2331,7 @@ export {
|
|
|
2277
2331
|
SUPPORTED_PROTOCOL_VERSIONS,
|
|
2278
2332
|
SealedEnvelope,
|
|
2279
2333
|
SealedOutcome,
|
|
2280
|
-
|
|
2334
|
+
SignedGrant,
|
|
2281
2335
|
SizeClass,
|
|
2282
2336
|
StoredKeys,
|
|
2283
2337
|
Succession,
|
|
@@ -2296,6 +2350,7 @@ export {
|
|
|
2296
2350
|
effectiveOfferScope,
|
|
2297
2351
|
fingerprint,
|
|
2298
2352
|
generateKeys,
|
|
2353
|
+
grantStatement,
|
|
2299
2354
|
isBackendId,
|
|
2300
2355
|
isCloudTaggedModel,
|
|
2301
2356
|
isJobKind,
|
|
@@ -2310,20 +2365,20 @@ export {
|
|
|
2310
2365
|
provenanceFor,
|
|
2311
2366
|
publicIdentityOf,
|
|
2312
2367
|
resolveCost,
|
|
2313
|
-
rosterStatement,
|
|
2314
2368
|
seal,
|
|
2369
|
+
signGrant,
|
|
2315
2370
|
signRequest,
|
|
2316
|
-
signRoster,
|
|
2317
2371
|
signSiteRequest,
|
|
2318
2372
|
signSuccession,
|
|
2319
2373
|
signWith,
|
|
2374
|
+
singlePurposeManifest,
|
|
2320
2375
|
sizeClassCeiling,
|
|
2321
2376
|
sizeClassOf,
|
|
2322
2377
|
successionStatement,
|
|
2378
|
+
verifyGrant,
|
|
2323
2379
|
verifyLink,
|
|
2324
2380
|
verifyPublicIdentity,
|
|
2325
2381
|
verifyRequest,
|
|
2326
|
-
verifyRoster,
|
|
2327
2382
|
verifySiteRequest,
|
|
2328
2383
|
verifyWith,
|
|
2329
2384
|
walkSuccession
|