@byollm/relay 0.1.0-alpha.10
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/LICENSE +21 -0
- package/README.md +136 -0
- package/dist/index.d.ts +416 -0
- package/dist/index.js +878 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Of Tomorrow, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
> [!WARNING]
|
|
2
|
+
> **Alpha (`0.1.0-alpha.10`) — under active development. Don't use this yet.**
|
|
3
|
+
>
|
|
4
|
+
> This is a walking skeleton. It routes real jobs between real daemons and real
|
|
5
|
+
> sites, and it is the fixture byollm_009 freezes against — but it keeps its
|
|
6
|
+
> state in memory, serves one site, and has never run anywhere but a test.
|
|
7
|
+
|
|
8
|
+
# `@byollm/relay`
|
|
9
|
+
|
|
10
|
+
The **reference relay**: it routes byollm jobs between a site and someone's
|
|
11
|
+
machine while holding no key that can open either end's traffic.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
site ──stub──▶ relay ◀──claim── daemon
|
|
15
|
+
◀─who claimed?─┤
|
|
16
|
+
──sealed payload─▶ ──────────▶ (opened only on the device)
|
|
17
|
+
◀────────────── sealed result ◀──
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Why a relay can be blind
|
|
21
|
+
|
|
22
|
+
A payload is encrypted to the machine that runs it. Nobody knows which machine
|
|
23
|
+
that is until one claims the job — so the site publishes a **stub** first
|
|
24
|
+
(byollm_009 §6: user, kind, size class, audience, deadline, streaming flag, and
|
|
25
|
+
nothing else), a daemon claims it, and only then does the site seal the work to
|
|
26
|
+
that specific device.
|
|
27
|
+
|
|
28
|
+
The relay is a directory in that exchange, not a participant. It says "this
|
|
29
|
+
device claimed your job, here is its public key" and carries what comes back.
|
|
30
|
+
It cannot read a payload because it was never a recipient, and it cannot
|
|
31
|
+
substitute one because the daemon verifies every envelope against the site
|
|
32
|
+
identity it pinned at consent.
|
|
33
|
+
|
|
34
|
+
That is not a policy this code follows. It is a shape it has: no type in this
|
|
35
|
+
package has a field that could hold a private key, so making this relay able to
|
|
36
|
+
read a payload means changing its types — a review someone has to justify
|
|
37
|
+
rather than a line someone can slip in.
|
|
38
|
+
|
|
39
|
+
## Why it ships open
|
|
40
|
+
|
|
41
|
+
It is the conformance kit's reference relay, and the kit is public — so it
|
|
42
|
+
starts where it ends rather than being written closed and ported. A relay that
|
|
43
|
+
claims to be blind should be readable by the people trusting it, and a
|
|
44
|
+
third-party daemon testing hub mode should test against real code rather than a
|
|
45
|
+
mock of it.
|
|
46
|
+
|
|
47
|
+
The production hub — multi-tenant routing, presence at scale, billing, ops — is
|
|
48
|
+
built on these same interfaces and is not this.
|
|
49
|
+
|
|
50
|
+
## `awaiting-payload`
|
|
51
|
+
|
|
52
|
+
byollm_009 §7 described a state the direct plane cannot produce. There the site
|
|
53
|
+
*is* the upstream: it seals when asked, so a job is never claimed-but-unsealed.
|
|
54
|
+
Here they are different parties, and the gap between them is a state with its
|
|
55
|
+
own clock — separate from the lease and from the job's TTL, because they answer
|
|
56
|
+
different questions:
|
|
57
|
+
|
|
58
|
+
| clock | question |
|
|
59
|
+
| ------------------ | ---------------------------------------------- |
|
|
60
|
+
| TTL | is this work still worth doing? |
|
|
61
|
+
| lease | how long does this device get to run it? |
|
|
62
|
+
| `awaiting-payload` | how long do we wait for a site that went away? |
|
|
63
|
+
|
|
64
|
+
When it fires, the stub returns to the queue and nothing is lost.
|
|
65
|
+
|
|
66
|
+
## Consent
|
|
67
|
+
|
|
68
|
+
The relay routes nothing without a consent record, and it cannot create one —
|
|
69
|
+
consent is a decision made elsewhere and projected in. In the skeleton that
|
|
70
|
+
projection is a fixture file; later it is whatever the control plane serves.
|
|
71
|
+
The shape is deliberately small, because it is a contract: anything added to it
|
|
72
|
+
has to be something a control plane can actually know, and a decision rather
|
|
73
|
+
than something the relay could observe for itself.
|
|
74
|
+
|
|
75
|
+
## Both callers sign
|
|
76
|
+
|
|
77
|
+
A daemon signs every request with its device key. **A site signs every request
|
|
78
|
+
with its site key** — the same key the control plane registered, the same key
|
|
79
|
+
daemons pin at pairing, verified against the `sites` half of the projection.
|
|
80
|
+
Nothing here trusts a `siteId` in a body or a query string.
|
|
81
|
+
|
|
82
|
+
That is newer than the rest of this package. The site plane took the caller's
|
|
83
|
+
word for who it was until `0.1.0-alpha.10`, which on a relay reachable from the
|
|
84
|
+
internet is an open enqueue endpoint into consenting users' machines and an
|
|
85
|
+
open read of who is online. It was blind the whole time — nothing could open a
|
|
86
|
+
payload — and blind is not the same as safe.
|
|
87
|
+
|
|
88
|
+
If you are running this: the site plane is authenticated but this is still a
|
|
89
|
+
single-tenant relay with in-memory state. One site, one replica.
|
|
90
|
+
|
|
91
|
+
## Running it
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { Relay } from "@byollm/relay";
|
|
95
|
+
|
|
96
|
+
const relay = new Relay({
|
|
97
|
+
siteId: "site_demo",
|
|
98
|
+
fixture: {
|
|
99
|
+
// The site registry: one home for a site's public identity, used both to
|
|
100
|
+
// tell daemons who to pin and to check the site's own signatures.
|
|
101
|
+
sites: [{ siteId: "site_demo", site: sitePublicKeys }],
|
|
102
|
+
consents: [{ owner: "alice", siteId: "site_demo" }],
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// One fetch handler: the daemon plane, the site plane, and /debug.
|
|
107
|
+
const response = await relay.handle(request);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`/debug` renders every routed job, its state, who claimed it, and how long an
|
|
111
|
+
`awaiting-payload` timer has left. It shows no prompt or result text — not
|
|
112
|
+
because it filters them out, but because the relay does not have them.
|
|
113
|
+
|
|
114
|
+
**Do not serve `/debug` on the internet.** It shows no payloads and it does
|
|
115
|
+
show who is online, which device holds what, and every lease in flight — the
|
|
116
|
+
same metadata the site plane's signatures exist to protect, through a
|
|
117
|
+
different door. Whoever serves this package decides that, which is why the
|
|
118
|
+
route is still here: refuse it at your gateway and reach it through an
|
|
119
|
+
authenticated channel instead.
|
|
120
|
+
|
|
121
|
+
## Auditing a deployment
|
|
122
|
+
|
|
123
|
+
`@byollm/conformance` ships a posture audit that holds nothing but a URL,
|
|
124
|
+
which is what an attacker has:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx byollm-audit-deployment https://your-relay.example
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
It exists because eight of byollm_009's findings came from a suite in which
|
|
131
|
+
nothing was ever a stranger — the site had a reference to the relay object and
|
|
132
|
+
called it. A harness that invokes the system under test directly cannot see
|
|
133
|
+
anything about how the system is *reached*, and the ninth finding was in that
|
|
134
|
+
gap. Safe to run against production: nothing writes, nothing floods.
|
|
135
|
+
|
|
136
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { PublicIdentity, JobStub, SealedEnvelope } from '@byollm/protocol';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The relay's routing state — byollm_009 §7, reachable at last.
|
|
6
|
+
*
|
|
7
|
+
* §7 described a state machine the direct plane could not produce. There, the
|
|
8
|
+
* site and the upstream are the same party: it seals when it likes, and a job
|
|
9
|
+
* is never claimed-but-unsealed. Here they are different parties, and the gap
|
|
10
|
+
* between them is a state:
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* queued ──claim──▶ awaiting-payload ──sealed──▶ ready ──fetch──▶ running
|
|
14
|
+
* ▲ │ │
|
|
15
|
+
* └────────────────────┘ ▼
|
|
16
|
+
* site never seals, or seals too late ok | error | canceled
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The relay cannot seal, so it cannot shortcut this. A payload is encrypted
|
|
20
|
+
* to *the device that claimed it*, and nobody knows which device that is until
|
|
21
|
+
* the claim happens — which is precisely why claim-then-fetch makes a blind
|
|
22
|
+
* relay possible at all. The window is the price.
|
|
23
|
+
*
|
|
24
|
+
* ## What the relay holds, and what it cannot
|
|
25
|
+
*
|
|
26
|
+
* Stubs (metadata the site chose to publish), sealed envelopes it cannot open,
|
|
27
|
+
* and public keys. There is no field on any type in this file that could hold
|
|
28
|
+
* a private key or a plaintext, which is `RELAY_BLIND` expressed as a data
|
|
29
|
+
* model rather than as a policy.
|
|
30
|
+
*/
|
|
31
|
+
/** Where a routed job is. */
|
|
32
|
+
type RoutedState = "queued" | "awaiting-payload" | "ready" | "running" | "done";
|
|
33
|
+
/**
|
|
34
|
+
* How long a site has to seal after one of its jobs is claimed.
|
|
35
|
+
*
|
|
36
|
+
* **Distinct from the lease, and distinct from the job's TTL** — byollm_009
|
|
37
|
+
* §7.1. Three clocks, three different questions:
|
|
38
|
+
*
|
|
39
|
+
* - the **TTL** asks how long the work is worth doing at all;
|
|
40
|
+
* - the **lease** asks how long this device gets to run it;
|
|
41
|
+
* - this asks how long we wait for a site that has gone away.
|
|
42
|
+
*
|
|
43
|
+
* Collapsing any pair of them looks harmless until a site restarts during a
|
|
44
|
+
* deploy: with only a lease, the device sits politely holding a job whose
|
|
45
|
+
* payload will never arrive, and the lease's whole minute is spent waiting on
|
|
46
|
+
* a party that is not coming back. Short, because a site that is up answers in
|
|
47
|
+
* milliseconds and a site that is down will not answer sooner for waiting.
|
|
48
|
+
*/
|
|
49
|
+
declare const AWAITING_PAYLOAD_MS = 10000;
|
|
50
|
+
/** A job the relay is routing. Metadata and ciphertext, nothing else. */
|
|
51
|
+
interface RoutedJob {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
/** Which site enqueued it — the party that will be asked to seal. */
|
|
54
|
+
readonly siteId: string;
|
|
55
|
+
/**
|
|
56
|
+
* Everything the relay knows about the work, which is everything the site
|
|
57
|
+
* chose to publish and not one field more (byollm_009 §6).
|
|
58
|
+
*/
|
|
59
|
+
readonly stub: JobStub;
|
|
60
|
+
state: RoutedState;
|
|
61
|
+
/** Set from the claim; the site seals to these keys. */
|
|
62
|
+
claimedBy?: {
|
|
63
|
+
readonly runnerId: string;
|
|
64
|
+
readonly owner: string;
|
|
65
|
+
readonly device: PublicIdentity;
|
|
66
|
+
readonly leaseId: string;
|
|
67
|
+
readonly leaseExpiresAt: number;
|
|
68
|
+
};
|
|
69
|
+
/** When {@link AWAITING_PAYLOAD_MS} runs out for this claim. */
|
|
70
|
+
awaitingUntil?: number;
|
|
71
|
+
/** Sealed to the claiming device by the site. Opaque here. */
|
|
72
|
+
payload?: SealedEnvelope;
|
|
73
|
+
/** Sealed to the site by the device. Opaque here. */
|
|
74
|
+
result?: SealedEnvelope;
|
|
75
|
+
/**
|
|
76
|
+
* The result's clear-text discriminator — byollm_009 §6.1.
|
|
77
|
+
*
|
|
78
|
+
* The one outcome fact the relay is given, and the reason it is given:
|
|
79
|
+
* without it the relay cannot stop dispatching a finished job. A routing
|
|
80
|
+
* hint and never a fact — the *site* verifies it against the sealed
|
|
81
|
+
* outcome, because only the site can open the envelope. The relay acts on
|
|
82
|
+
* it and is entitled to be wrong; a lying daemon costs it a dispatch
|
|
83
|
+
* decision, not a security property.
|
|
84
|
+
*/
|
|
85
|
+
disposition?: "ok" | "error" | "canceled";
|
|
86
|
+
}
|
|
87
|
+
/** A device the relay has seen recently. */
|
|
88
|
+
interface Presence {
|
|
89
|
+
readonly runnerId: string;
|
|
90
|
+
readonly owner: string;
|
|
91
|
+
readonly device: PublicIdentity;
|
|
92
|
+
lastSeenAt: number;
|
|
93
|
+
/** Set on revocation so the next request is refused rather than routed. */
|
|
94
|
+
revoked: boolean;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* In-memory routing state.
|
|
98
|
+
*
|
|
99
|
+
* Deliberately not durable. The skeleton proves the protocol, and the
|
|
100
|
+
* production hub replaces this with the closed multi-tenant router behind the
|
|
101
|
+
* same shape (cloud_004 §9). Anything a restart loses here is a job that
|
|
102
|
+
* returns to its site's queue — which is the behaviour a lapsed lease already
|
|
103
|
+
* has to produce, so nothing new needs to be true for this to be safe.
|
|
104
|
+
*/
|
|
105
|
+
declare class RelayState {
|
|
106
|
+
#private;
|
|
107
|
+
/**
|
|
108
|
+
* Take a stub for routing. The payload is not here and will not be.
|
|
109
|
+
*
|
|
110
|
+
* **Idempotent by job id, and that is a security property rather than a
|
|
111
|
+
* convenience.** Site-plane calls are authenticated by signature, and
|
|
112
|
+
* byollm_009 §4.2's argument for signing the request instead of a
|
|
113
|
+
* server-issued nonce rests entirely on every write being idempotent per the
|
|
114
|
+
* instance it names. This one was not: re-enqueueing a known id built a
|
|
115
|
+
* fresh `queued` job over the top of the old one, discarding a live claim,
|
|
116
|
+
* its lease and any payload the site had already sealed to a device. A
|
|
117
|
+
* replayed enqueue inside the two-minute freshness window was therefore a
|
|
118
|
+
* way to yank a job back from the machine running it — the `release` bug of
|
|
119
|
+
* §4.2, rediscovered on the other plane.
|
|
120
|
+
*
|
|
121
|
+
* So a known id returns what is already routing, unchanged. A site that
|
|
122
|
+
* restarts and republishes its queue is the normal case, and it must not
|
|
123
|
+
* disturb work in flight.
|
|
124
|
+
*/
|
|
125
|
+
enqueue(input: {
|
|
126
|
+
id: string;
|
|
127
|
+
siteId: string;
|
|
128
|
+
stub: JobStub;
|
|
129
|
+
}): RoutedJob;
|
|
130
|
+
job(jobId: string): RoutedJob | undefined;
|
|
131
|
+
jobs(): RoutedJob[];
|
|
132
|
+
/** Jobs a site must seal for, right now. */
|
|
133
|
+
awaiting(siteId: string): RoutedJob[];
|
|
134
|
+
/** Sealed results waiting to go home. */
|
|
135
|
+
finished(siteId: string): RoutedJob[];
|
|
136
|
+
seen(presence: Omit<Presence, "revoked">): Presence;
|
|
137
|
+
presence(runnerId: string): Presence | undefined;
|
|
138
|
+
everyone(): Presence[];
|
|
139
|
+
/**
|
|
140
|
+
* Return a job to the queue, forgetting the claim.
|
|
141
|
+
*
|
|
142
|
+
* The stub survives; nothing is lost. That is `LEASE_RECLAIMABLE` and it is
|
|
143
|
+
* why the awaiting-payload timeout is cheap to fire: the worst case is that
|
|
144
|
+
* a device did nothing for ten seconds and another one gets a turn.
|
|
145
|
+
*/
|
|
146
|
+
requeue(job: RoutedJob): void;
|
|
147
|
+
/**
|
|
148
|
+
* Fire whatever the clock says is due, and report it.
|
|
149
|
+
*
|
|
150
|
+
* Returns the jobs it requeued so a caller can log or surface them — a
|
|
151
|
+
* timeout that fires invisibly is indistinguishable from a job that was
|
|
152
|
+
* never claimed, and those want very different debugging.
|
|
153
|
+
*/
|
|
154
|
+
sweep(now: number): RoutedJob[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
declare function debugPage(state: RelayState, now: number): string;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* What the relay is told about the world — cloud_004 §14.
|
|
161
|
+
*
|
|
162
|
+
* The relay decides nothing about who may talk to whom. It is handed a
|
|
163
|
+
* projection of the control plane and routes according to it. Today that
|
|
164
|
+
* projection is a file; later it is whatever the suite serves. Either way the
|
|
165
|
+
* relay's own state is derived and disposable: delete it and the fixture
|
|
166
|
+
* rebuilds it.
|
|
167
|
+
*
|
|
168
|
+
* ## This shape is a contract, not a test convenience
|
|
169
|
+
*
|
|
170
|
+
* cloud_004 §14 flags it and the flag is worth repeating here, where someone
|
|
171
|
+
* will be tempted to add a field: **this is the projection contract.** The
|
|
172
|
+
* first real control plane will be written to produce whatever this says, and
|
|
173
|
+
* a field added carelessly now is a field the suite must produce forever.
|
|
174
|
+
*
|
|
175
|
+
* So two rules for anything added later:
|
|
176
|
+
*
|
|
177
|
+
* 1. **It must be something a control plane can actually know.** The relay
|
|
178
|
+
* cannot be given facts that only a daemon or only a site holds — that is
|
|
179
|
+
* how a blind relay stops being blind, one convenient field at a time.
|
|
180
|
+
* 2. **It must be a decision, not a derivation.** Consent is a decision.
|
|
181
|
+
* Presence is not: the relay learns that from heartbeats. Anything the
|
|
182
|
+
* relay can observe does not belong in the projection.
|
|
183
|
+
*
|
|
184
|
+
* ## What is deliberately absent
|
|
185
|
+
*
|
|
186
|
+
* No private keys, of any party, ever. The relay holds public keys so it can
|
|
187
|
+
* *verify* signatures and *tell a site who to seal to*. It holds no key that
|
|
188
|
+
* can open anything, and {@link RelayFixture} has no field where one could be
|
|
189
|
+
* put — `RELAY_BLIND` as a type, not as a promise.
|
|
190
|
+
*/
|
|
191
|
+
/**
|
|
192
|
+
* A site the control plane registered and domain-verified — cloud_004 §5.
|
|
193
|
+
*
|
|
194
|
+
* **The one authority for a site's public identity.** It used to be inlined on
|
|
195
|
+
* every consent record, which meant a site's key had as many homes as it had
|
|
196
|
+
* users and nothing checked they agreed — the exact shape this project has now
|
|
197
|
+
* found in a version constant, a clock read, an envelope deadline, a reseal
|
|
198
|
+
* implementation, a package list and a docs page. Consents now reference a
|
|
199
|
+
* site by id and the key is looked up here.
|
|
200
|
+
*
|
|
201
|
+
* The relay needs it for two things it cannot do without:
|
|
202
|
+
*
|
|
203
|
+
* 1. **Telling a daemon who to pin** at pairing — the key that makes relayed
|
|
204
|
+
* work unforgeable, since the relay holds no key that could produce it.
|
|
205
|
+
* 2. **Authenticating the site plane.** A site calls a relay the way a daemon
|
|
206
|
+
* does, signing with this identity, and this is the key those signatures
|
|
207
|
+
* are checked against.
|
|
208
|
+
*/
|
|
209
|
+
declare const SiteRecord: z.ZodObject<{
|
|
210
|
+
siteId: z.ZodString;
|
|
211
|
+
site: z.ZodObject<{
|
|
212
|
+
identity: z.ZodString;
|
|
213
|
+
encryption: z.ZodString;
|
|
214
|
+
encryptionSig: z.ZodString;
|
|
215
|
+
}, z.core.$strict>;
|
|
216
|
+
}, z.core.$strict>;
|
|
217
|
+
type SiteRecord = z.infer<typeof SiteRecord>;
|
|
218
|
+
/**
|
|
219
|
+
* A user's decision to let one site use their compute — cloud_004 §3.
|
|
220
|
+
*
|
|
221
|
+
* `CONSENT_BEFORE_ROUTE`: with no record here, the relay refuses to route,
|
|
222
|
+
* and there is no discovery path that creates one. Consent is a click in the
|
|
223
|
+
* control plane; the relay only ever reads the result.
|
|
224
|
+
*/
|
|
225
|
+
declare const ConsentRecord: z.ZodObject<{
|
|
226
|
+
owner: z.ZodString;
|
|
227
|
+
siteId: z.ZodString;
|
|
228
|
+
}, z.core.$strict>;
|
|
229
|
+
type ConsentRecord = z.infer<typeof ConsentRecord>;
|
|
230
|
+
/**
|
|
231
|
+
* A named group whose members may use a shared machine — cloud_004 §11.
|
|
232
|
+
*
|
|
233
|
+
* The roster lives here and **never reaches a site**. A site learns whether a
|
|
234
|
+
* consenting user has reachable compute; it never learns who else is on the
|
|
235
|
+
* roster. That is `ROSTERS_NEVER_LEAK` in cloud_004 §11.4, and the reason
|
|
236
|
+
* this type has no outbound representation anywhere in this package.
|
|
237
|
+
*/
|
|
238
|
+
declare const RosterRecord: z.ZodObject<{
|
|
239
|
+
id: z.ZodString;
|
|
240
|
+
owner: z.ZodString;
|
|
241
|
+
members: z.ZodArray<z.ZodString>;
|
|
242
|
+
}, z.core.$strict>;
|
|
243
|
+
type RosterRecord = z.infer<typeof RosterRecord>;
|
|
244
|
+
/**
|
|
245
|
+
* A device its owner has approved — cloud_005 §7.1.
|
|
246
|
+
*
|
|
247
|
+
* The relay refuses a device that is not here, and that refusal is the point.
|
|
248
|
+
* byollm_009's seventh finding stopped a daemon from *naming* itself; this
|
|
249
|
+
* stops it from *keying* itself. A device that presents keys nobody approved
|
|
250
|
+
* is a device whose owner never saw a fingerprint, and pairing it would make
|
|
251
|
+
* the relay the authority on identity — which is exactly what it must not be.
|
|
252
|
+
*
|
|
253
|
+
* The three-party shape consent already has, applied to identity: the device
|
|
254
|
+
* asserts, a human confirms in the control plane, the relay checks.
|
|
255
|
+
*/
|
|
256
|
+
declare const DeviceRecord: z.ZodObject<{
|
|
257
|
+
owner: z.ZodString;
|
|
258
|
+
runnerId: z.ZodString;
|
|
259
|
+
device: z.ZodObject<{
|
|
260
|
+
identity: z.ZodString;
|
|
261
|
+
encryption: z.ZodString;
|
|
262
|
+
encryptionSig: z.ZodString;
|
|
263
|
+
}, z.core.$strict>;
|
|
264
|
+
}, z.core.$strict>;
|
|
265
|
+
type DeviceRecord = z.infer<typeof DeviceRecord>;
|
|
266
|
+
/** A revoked route, named by its parts. */
|
|
267
|
+
declare const RevocationRecord: z.ZodObject<{
|
|
268
|
+
owner: z.ZodString;
|
|
269
|
+
siteId: z.ZodString;
|
|
270
|
+
}, z.core.$strict>;
|
|
271
|
+
type RevocationRecord = z.infer<typeof RevocationRecord>;
|
|
272
|
+
declare const RelayFixture: z.ZodObject<{
|
|
273
|
+
sites: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
274
|
+
siteId: z.ZodString;
|
|
275
|
+
site: z.ZodObject<{
|
|
276
|
+
identity: z.ZodString;
|
|
277
|
+
encryption: z.ZodString;
|
|
278
|
+
encryptionSig: z.ZodString;
|
|
279
|
+
}, z.core.$strict>;
|
|
280
|
+
}, z.core.$strict>>>;
|
|
281
|
+
consents: z.ZodArray<z.ZodObject<{
|
|
282
|
+
owner: z.ZodString;
|
|
283
|
+
siteId: z.ZodString;
|
|
284
|
+
}, z.core.$strict>>;
|
|
285
|
+
devices: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
286
|
+
owner: z.ZodString;
|
|
287
|
+
runnerId: z.ZodString;
|
|
288
|
+
device: z.ZodObject<{
|
|
289
|
+
identity: z.ZodString;
|
|
290
|
+
encryption: z.ZodString;
|
|
291
|
+
encryptionSig: z.ZodString;
|
|
292
|
+
}, z.core.$strict>;
|
|
293
|
+
}, z.core.$strict>>>;
|
|
294
|
+
rosters: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
295
|
+
id: z.ZodString;
|
|
296
|
+
owner: z.ZodString;
|
|
297
|
+
members: z.ZodArray<z.ZodString>;
|
|
298
|
+
}, z.core.$strict>>>;
|
|
299
|
+
revoked: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
300
|
+
owner: z.ZodString;
|
|
301
|
+
siteId: z.ZodString;
|
|
302
|
+
}, z.core.$strict>>>;
|
|
303
|
+
}, z.core.$strict>;
|
|
304
|
+
type RelayFixture = z.infer<typeof RelayFixture>;
|
|
305
|
+
/** An empty projection: nothing consented, so nothing routes. */
|
|
306
|
+
declare const EMPTY_FIXTURE: RelayFixture;
|
|
307
|
+
/**
|
|
308
|
+
* The relay's read-only view of the projection.
|
|
309
|
+
*
|
|
310
|
+
* Deliberately a handful of questions rather than the raw fixture: every
|
|
311
|
+
* caller asking "may this route?" through one method is what makes
|
|
312
|
+
* `CONSENT_BEFORE_ROUTE` reviewable, and it leaves room for the projection to
|
|
313
|
+
* become a service without touching a single call site.
|
|
314
|
+
*/
|
|
315
|
+
declare class Projection {
|
|
316
|
+
#private;
|
|
317
|
+
constructor(fixture?: RelayFixture);
|
|
318
|
+
/** Replace the projection wholesale — the control plane pushed a new one. */
|
|
319
|
+
replace(fixture: RelayFixture): void;
|
|
320
|
+
/**
|
|
321
|
+
* The site this id names, if the control plane registered it.
|
|
322
|
+
*
|
|
323
|
+
* The only source of a site's public identity in this package. Everything
|
|
324
|
+
* that pins, verifies or seals to a site starts here.
|
|
325
|
+
*/
|
|
326
|
+
siteFor(siteId: string): SiteRecord | null;
|
|
327
|
+
/**
|
|
328
|
+
* The device this runner id names, if a human approved it.
|
|
329
|
+
*
|
|
330
|
+
* Returns null for a device the control plane does not know, which is how
|
|
331
|
+
* the relay refuses to be the authority on identity.
|
|
332
|
+
*/
|
|
333
|
+
deviceFor(runnerId: string): DeviceRecord | null;
|
|
334
|
+
/** The device approved for these exact keys, if any. */
|
|
335
|
+
deviceByFingerprint(identityPublic: string): DeviceRecord | null;
|
|
336
|
+
/** The consent binding this owner to this site, if it exists and stands. */
|
|
337
|
+
consentFor(owner: string, siteId: string): ConsentRecord | null;
|
|
338
|
+
/**
|
|
339
|
+
* May this device's owner run work belonging to `jobOwner`?
|
|
340
|
+
*
|
|
341
|
+
* The relay's half of `AUDIENCE_BOTH_SIDES`. It is only ever a *narrowing*:
|
|
342
|
+
* the daemon re-checks its own allowlist locally and may still refuse, and
|
|
343
|
+
* the site's audience already bounded who could be offered the job. A relay
|
|
344
|
+
* that answered `true` for everyone would not widen anything — which is
|
|
345
|
+
* exactly the property that lets it be blind.
|
|
346
|
+
*/
|
|
347
|
+
mayRunFor(deviceOwner: string, jobOwner: string): boolean;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* `@byollm/relay` — the reference relay (cloud_004 §14).
|
|
352
|
+
*
|
|
353
|
+
* A blind relay between byollm sites and daemons: it routes stubs, hands over
|
|
354
|
+
* sealed envelopes it cannot open, and knows who is online. It is the first
|
|
355
|
+
* consumer of byollm_009's session layer that is neither the site nor the
|
|
356
|
+
* device, which makes it the thing that proves the protocol's central claim.
|
|
357
|
+
*
|
|
358
|
+
* ## Why this ships open
|
|
359
|
+
*
|
|
360
|
+
* It is the conformance kit's reference relay, and the kit is public — so it
|
|
361
|
+
* starts where it ends rather than being written closed and ported. A relay
|
|
362
|
+
* that claims to be blind should be readable by the people trusting it, and a
|
|
363
|
+
* third-party daemon testing hub mode should test against real code rather
|
|
364
|
+
* than a mock of it. The production hub — multi-tenant routing, presence at
|
|
365
|
+
* scale, billing, ops — is built on these same interfaces and is not this.
|
|
366
|
+
*
|
|
367
|
+
* ## Blind by construction, not by policy
|
|
368
|
+
*
|
|
369
|
+
* {@link RelayOptions} has no field that can hold a private key, and no type
|
|
370
|
+
* in this package has one either. `RELAY_BLIND` is therefore not a rule the
|
|
371
|
+
* code follows; it is a shape the code has. The only way to make this relay
|
|
372
|
+
* able to read a payload is to change its types, which is a review someone
|
|
373
|
+
* would have to justify rather than a line someone could slip in.
|
|
374
|
+
*/
|
|
375
|
+
interface RelayOptions {
|
|
376
|
+
/**
|
|
377
|
+
* Which site this relay routes for.
|
|
378
|
+
*
|
|
379
|
+
* One, in the skeleton. Multi-tenant routing is the closed piece
|
|
380
|
+
* (cloud_004 §9), and it replaces this field rather than extending it.
|
|
381
|
+
*/
|
|
382
|
+
readonly siteId: string;
|
|
383
|
+
/** Consent and rosters, projected from the control plane. */
|
|
384
|
+
readonly fixture?: RelayFixture;
|
|
385
|
+
/** How long a claim is good for. */
|
|
386
|
+
readonly leaseMs?: number;
|
|
387
|
+
/** Injectable clock, so tests move time instead of sleeping. */
|
|
388
|
+
readonly now?: () => number;
|
|
389
|
+
/** Where the daemon plane is mounted. */
|
|
390
|
+
readonly basePath?: string;
|
|
391
|
+
}
|
|
392
|
+
/** A running relay: one fetch handler, two planes, one debug page. */
|
|
393
|
+
declare class Relay {
|
|
394
|
+
#private;
|
|
395
|
+
readonly state: RelayState;
|
|
396
|
+
readonly projection: Projection;
|
|
397
|
+
constructor(options: RelayOptions);
|
|
398
|
+
/** Replace the projection — a control-plane push, or a fixture edit. */
|
|
399
|
+
project(fixture: RelayFixture): void;
|
|
400
|
+
/**
|
|
401
|
+
* Fire due timers and report what moved.
|
|
402
|
+
*
|
|
403
|
+
* Exposed rather than run on an interval so a test can drive it, and so the
|
|
404
|
+
* production hub can decide its own scheduling. The relay never needs a
|
|
405
|
+
* timer to be *correct* — every read path sweeps first — but a job whose
|
|
406
|
+
* site vanished should return to the queue without waiting for someone to
|
|
407
|
+
* ask about it.
|
|
408
|
+
*/
|
|
409
|
+
sweep(): {
|
|
410
|
+
requeued: string[];
|
|
411
|
+
};
|
|
412
|
+
/** The whole HTTP surface. */
|
|
413
|
+
handle(request: Request): Promise<Response>;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export { AWAITING_PAYLOAD_MS, ConsentRecord, DeviceRecord, EMPTY_FIXTURE, type Presence, Projection, Relay, RelayFixture, RelayFixture as RelayFixtureSchema, type RelayOptions, RelayState, RevocationRecord, RosterRecord, type RoutedJob, type RoutedState, SiteRecord, debugPage };
|