@byollm/server 0.1.0-alpha.3 → 0.1.0-alpha.30
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 +151 -12
- package/bin/keygen.mjs +21 -0
- package/dist/chunk-K5E6JS5A.js +655 -0
- package/dist/chunk-K5E6JS5A.js.map +1 -0
- package/dist/{handlers-D7lWfwno.d.ts → handlers-BJYm2kdq.d.ts} +27 -4
- package/dist/index.d.ts +194 -17
- package/dist/index.js +494 -44
- package/dist/index.js.map +1 -1
- package/dist/next.d.ts +40 -8
- package/dist/next.js +8 -2
- package/dist/next.js.map +1 -1
- package/dist/store-Dno2fnHH.d.ts +436 -0
- package/dist/supabase/index.d.ts +1 -1
- package/dist/supabase/index.js +115 -42
- package/dist/supabase/index.js.map +1 -1
- package/package.json +7 -3
- package/supabase/migrations/20260809000000_byollm_runner.sql +22 -3
- package/supabase/migrations/20260819000000_drop_runner_token.sql +87 -0
- package/supabase/migrations/20260819010000_completed_by_lease_id.sql +25 -0
- package/supabase/migrations/20260821000000_rename_collected.sql +91 -0
- package/dist/chunk-HL6EYHQ7.js +0 -422
- package/dist/chunk-HL6EYHQ7.js.map +0 -1
- package/dist/store-D23N6iiP.d.ts +0 -255
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
> [!WARNING]
|
|
2
|
-
> **Alpha (`0.1.0-alpha.
|
|
2
|
+
> **Alpha (`0.1.0-alpha.30`) — under active development. Don't use this yet.**
|
|
3
3
|
>
|
|
4
4
|
> Install it deliberately: `npm install @byollm/server@alpha`.
|
|
5
5
|
>
|
|
@@ -11,7 +11,105 @@
|
|
|
11
11
|
> npm assigns `latest` on a first publish and won't let it be removed, so a
|
|
12
12
|
> bare install resolves here too. This notice is the only guard — deliberately
|
|
13
13
|
> not an npm deprecation, which would read as *abandoned* rather than *early*.
|
|
14
|
-
> Ask for `@alpha` explicitly so your lockfile records that you meant to
|
|
14
|
+
> Ask for `@alpha` explicitly so your lockfile records that you meant to.>
|
|
15
|
+
> **`alpha.15` is a breaking wire change, and it breaks daemons and relays —
|
|
16
|
+
> not app authors.** If you call `app.enqueue(...)` and read results, nothing
|
|
17
|
+
> in your code changes. If you run a daemon or an upstream, every package must
|
|
18
|
+
> move together: a mixed pair refuses on both sides, because both ends parse
|
|
19
|
+
> `.strict()`.
|
|
20
|
+
>
|
|
21
|
+
> What moved, all of it reconciling the frozen `byollm_009` with its code:
|
|
22
|
+
> `JobStub` gains `site` (the site's identity key id) and loses
|
|
23
|
+
> `audienceAllow`; `ResultRequest` gains `leaseId`; `HeartbeatResponse` loses
|
|
24
|
+
> `leases`, which nothing read; `WireErrorCode` gains `not-ready`,
|
|
25
|
+
> `clock-skew` and `forbidden`, and `403` is `forbidden` rather than
|
|
26
|
+
> `unauthorized`. `RESULT_PROVENANCE` is superseded by
|
|
27
|
+
> `PROVENANCE_NAMES_DEVICE`. See `byollm_009` Amendment A.>
|
|
28
|
+
> **`alpha.16` is a breaking wire change — daemons and relays again, not app
|
|
29
|
+
> authors.** `app.enqueue(...)` and reading results are unchanged. All five
|
|
30
|
+
> packages move together: both ends parse `.strict()`, so a mixed pair
|
|
31
|
+
> refuses.
|
|
32
|
+
>
|
|
33
|
+
> What moved, all of it Tier 2 of `cloud_008`: `model`, `backendClass` and
|
|
34
|
+
> `durationMs` come off `ResultRequest` and are sealed **inside** the result
|
|
35
|
+
> envelope as `SealedOutcome = { outcome, ran }` — so a daemon can no longer
|
|
36
|
+
> declare a model it did not sign, and a relay carries neither.
|
|
37
|
+
> `HeartbeatResponse` loses `leases` (nothing read it) and now reports real
|
|
38
|
+
> cancellations instead of an empty list. `WireErrorCode` gains `forbidden`
|
|
39
|
+
> for 403, leaving `unauthorized` at exactly 401. The relay gained a
|
|
40
|
+
> site-plane `cancel` endpoint, honours `stub.deadlineAt`, honours
|
|
41
|
+
> `stub.audience`, and remembers a refusal.>
|
|
42
|
+
> **`alpha.17` is additive** — no wire change. It exports `ReleaseReason`,
|
|
43
|
+
> which `RoutingStore.releaseLeases` names and the package did not export, so
|
|
44
|
+
> the interface was unimplementable outside this repo.>
|
|
45
|
+
> **`alpha.18` is a breaking wire change — daemons and relays, not app
|
|
46
|
+
> authors.** `app.enqueue(...)` and reading results are unchanged. All five
|
|
47
|
+
> packages move together.
|
|
48
|
+
>
|
|
49
|
+
> The **bearer token is gone**: off `PairPollResponse`, off the runner row,
|
|
50
|
+
> off the daemon's pairings file, out of the adapter's schema. It was minted,
|
|
51
|
+
> hashed and stored on two disks and never sent, looked up or compared —
|
|
52
|
+
> `REQUESTS_SIGNED_NOT_BEARER` was enforced by signatures the whole time. If
|
|
53
|
+
> you run the Supabase adapter, apply
|
|
54
|
+
> `20260819000000_drop_runner_token.sql`; `byollm_approve_pairing` now takes
|
|
55
|
+
> one argument. A pairings file written by an older daemon still loads.
|
|
56
|
+
>
|
|
57
|
+
> `model`, `backendClass` and `durationMs` moved **inside** the sealed result
|
|
58
|
+
> (`SealedOutcome = { outcome, ran }`), so a daemon cannot declare a model it
|
|
59
|
+
> did not sign and a relay carries none of them. Writing a `RoutingStore`?
|
|
60
|
+
> `releaseLeases` takes an optional `reason` and `complete` requires
|
|
61
|
+
> `leaseId`, and **an implementation that ignores either still typechecks** —
|
|
62
|
+
> run the store contract tests.>
|
|
63
|
+
> **`alpha.19` is additive on the wire and a behaviour change in every
|
|
64
|
+
> store.** `ResultResponse` gains an optional `duplicate`. Nothing is removed,
|
|
65
|
+
> so an older daemon keeps working — but the *order* two rules are checked in
|
|
66
|
+
> has changed, and a `RoutingStore` implementation must change with it.
|
|
67
|
+
>
|
|
68
|
+
> `complete` now checks **terminal state before holder**, scoped to the device
|
|
69
|
+
> that finished the job: a replay from that device is answered `duplicate:
|
|
70
|
+
> true` with a 2xx, and anyone else gets exactly the refusal they would get
|
|
71
|
+
> for a job that is not terminal. Previously `RESULT_IDEMPOTENT` held only
|
|
72
|
+
> because the lease is nulled on success, so the holder check tripped first —
|
|
73
|
+
> deleting the idempotency branch failed no test. Run the store contract
|
|
74
|
+
> tests; the compiler cannot see this.
|
|
75
|
+
>
|
|
76
|
+
> **`alpha.7` breaks nobody who is not implementing a relay.** `@byollm/relay`'s
|
|
77
|
+
> projection gains `devices` — the device keys a control plane has approved —
|
|
78
|
+
> and the relay refuses to pair a device that is not in it. `revoked` becomes
|
|
79
|
+
> `{owner, siteId}` instead of a composite string. Sites, daemons, stores and
|
|
80
|
+
> the wire format are all untouched; no runner re-pairs.
|
|
81
|
+
>
|
|
82
|
+
> **`alpha.5` broke store adapters, and nothing else.** If you implement
|
|
83
|
+
> `JobStore` yourself, two changes are required: a new `adopt(args)` method
|
|
84
|
+
> (record a lease granted by an upstream this store does not own), and
|
|
85
|
+
> `CompleteArgs.runnerId` is replaced by `holder` — a discriminated union
|
|
86
|
+
> naming either a runner or a lease. Apps, daemons and the wire format are
|
|
87
|
+
> unaffected; `@byollm/conformance` will tell you if you missed one.
|
|
88
|
+
>
|
|
89
|
+
> **`alpha.4` broke every integration.** Three things changed for you:
|
|
90
|
+
>
|
|
91
|
+
> 1. **`siteKeys` is required.** Run `npx @byollm/server@alpha keygen` once,
|
|
92
|
+
> set `BYOLLM_SITE_KEYS`, and pass it to `ByollmApp` and `createHandler`.
|
|
93
|
+
> Once — not per deploy, never at startup.
|
|
94
|
+
> 2. **`createHandler` takes a function.** `next build` imports route modules
|
|
95
|
+
> with no secrets present, so a config object fails the build.
|
|
96
|
+
> 3. **Every paired runner re-pairs.** Bearer tokens are replaced by per-request
|
|
97
|
+
> signatures against a pinned device key, so old tokens authenticate nothing.
|
|
98
|
+
>
|
|
99
|
+
> Your store adapter is unaffected: payloads and results are sealed before they
|
|
100
|
+
> reach it, and `JobStore` did not change.
|
|
101
|
+
|
|
102
|
+
<!-- release-note 0.1.0-alpha.21 -->
|
|
103
|
+
> [!NOTE]
|
|
104
|
+
> **`0.1.0-alpha.20` is not a complete release — do not pin it.** Four
|
|
105
|
+
> packages published and `@byollm/server` did not: a Sigstore
|
|
106
|
+
> transparency-log 409 on its provenance attestation. The workflow's
|
|
107
|
+
> "already published" guard correctly refuses to resume a partial publish,
|
|
108
|
+
> so `0.1.0-alpha.30` is that release, whole.
|
|
109
|
+
>
|
|
110
|
+
> If you run the Supabase adapter, `alpha.21` needs
|
|
111
|
+
> `20260819010000_completed_by_lease_id.sql`: alpha.19 shipped §3.6's
|
|
112
|
+
> ordering without the column it stores the grant in.
|
|
15
113
|
|
|
16
114
|
# `@byollm/server`
|
|
17
115
|
|
|
@@ -30,28 +128,68 @@ npm install @byollm/server
|
|
|
30
128
|
```ts
|
|
31
129
|
// app/api/byollm/[...route]/route.ts
|
|
32
130
|
import { createHandler } from "@byollm/server/next";
|
|
33
|
-
import {
|
|
131
|
+
import { siteKeysFromEnv } from "@byollm/server";
|
|
132
|
+
import { getStore } from "@/lib/byollm";
|
|
34
133
|
|
|
35
|
-
export const { POST } = createHandler({
|
|
36
|
-
store,
|
|
134
|
+
export const { POST } = createHandler(() => ({
|
|
135
|
+
store: getStore(),
|
|
136
|
+
siteKeys: siteKeysFromEnv("BYOLLM_SITE_KEYS"),
|
|
37
137
|
verificationUrl: "https://your-app.com/settings/runners",
|
|
38
|
-
|
|
138
|
+
// Next serves this route under /api, so say where it is mounted. The
|
|
139
|
+
// handler matches the full path and will 404 without this.
|
|
140
|
+
basePath: "/api/byollm",
|
|
141
|
+
}));
|
|
39
142
|
```
|
|
40
143
|
|
|
144
|
+
**Pass a function, not an object.** `next build` imports every route module to
|
|
145
|
+
collect page data, in an environment that has no secrets. A config object is
|
|
146
|
+
constructed during that import, so the build fails on credentials it cannot
|
|
147
|
+
have. A function is not called until the first request.
|
|
148
|
+
|
|
149
|
+
Then pair against that same path — `byollm connect https://your-app.com/api`.
|
|
150
|
+
The daemon appends `/byollm/<endpoint>` to whatever origin it is given, so
|
|
151
|
+
connecting to the bare domain looks for `/byollm/claim` and finds nothing.
|
|
152
|
+
To serve at `/byollm` instead, put the route at `app/byollm/[...route]/route.ts`,
|
|
153
|
+
drop `basePath`, and pair against the bare domain.
|
|
154
|
+
|
|
41
155
|
**2. Pick a store.**
|
|
42
156
|
|
|
43
157
|
```ts
|
|
44
158
|
// lib/byollm.ts
|
|
45
|
-
import { ByollmApp, MemoryStore } from "@byollm/server";
|
|
159
|
+
import { ByollmApp, MemoryStore, siteKeysFromEnv } from "@byollm/server";
|
|
46
160
|
|
|
47
|
-
|
|
48
|
-
|
|
161
|
+
// Lazily, and memoized, for the same reason the mount takes a function: a
|
|
162
|
+
// module-scope `new` runs during `next build`.
|
|
163
|
+
let store: MemoryStore | undefined;
|
|
164
|
+
export function getStore(): MemoryStore {
|
|
165
|
+
return (store ??= new MemoryStore());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let app: ByollmApp | undefined;
|
|
169
|
+
export function getApp(): ByollmApp {
|
|
170
|
+
return (app ??= new ByollmApp({
|
|
171
|
+
store: getStore(),
|
|
172
|
+
siteKeys: siteKeysFromEnv("BYOLLM_SITE_KEYS"),
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
49
175
|
```
|
|
50
176
|
|
|
177
|
+
Generate that identity once, and keep it:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npx @byollm/server@alpha keygen # prints BYOLLM_SITE_KEYS=...
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Once, not per deploy and never at startup — a daemon pins this identity when
|
|
184
|
+
its owner approves the pairing, and regenerating it means every paired machine
|
|
185
|
+
must pair again. Generating at startup fails only under horizontal scale: each
|
|
186
|
+
instance would have a different identity, and a daemon would be refused by
|
|
187
|
+
whichever one it did not pair with.
|
|
188
|
+
|
|
51
189
|
**3. Enqueue.**
|
|
52
190
|
|
|
53
191
|
```ts
|
|
54
|
-
const job = await
|
|
192
|
+
const job = await getApp().enqueue({
|
|
55
193
|
kind: "llm.generate",
|
|
56
194
|
audience: "self", // this user's own machine only — the default
|
|
57
195
|
owner: userId,
|
|
@@ -75,7 +213,7 @@ types the code their daemon showed them:
|
|
|
75
213
|
|
|
76
214
|
```ts
|
|
77
215
|
// The owner comes from YOUR session. A daemon can never assert who it is.
|
|
78
|
-
const runner = await
|
|
216
|
+
const runner = await getApp().approvePairing({
|
|
79
217
|
userCode: formData.get("code"),
|
|
80
218
|
owner: session.userId,
|
|
81
219
|
});
|
|
@@ -103,6 +241,7 @@ import {
|
|
|
103
241
|
const store = supabaseStore({ client: serviceRoleClient });
|
|
104
242
|
const app = new ByollmApp({
|
|
105
243
|
store,
|
|
244
|
+
siteKeys: siteKeysFromEnv("BYOLLM_SITE_KEYS"),
|
|
106
245
|
delivery: supabaseRealtimeDelivery(serviceRoleClient),
|
|
107
246
|
});
|
|
108
247
|
```
|
|
@@ -123,7 +262,7 @@ and `untrusted` is derived from the audience — you cannot mark volunteer
|
|
|
123
262
|
output as first-party:
|
|
124
263
|
|
|
125
264
|
```ts
|
|
126
|
-
const { outcome, provenance } = await
|
|
265
|
+
const { outcome, provenance } = await getApp().result(jobId);
|
|
127
266
|
if (provenance?.untrusted) {
|
|
128
267
|
// Do not render as trusted HTML. Do not feed to a privileged step.
|
|
129
268
|
// Disclose where it came from.
|
package/bin/keygen.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `npx @byollm/server keygen` — make a site identity, once.
|
|
4
|
+
*
|
|
5
|
+
* Prints an env-file fragment. Deliberately not written to a file: key
|
|
6
|
+
* material that lands on disk by default tends to end up committed, and the
|
|
7
|
+
* one place it should live is wherever this deployment keeps its secrets.
|
|
8
|
+
*/
|
|
9
|
+
import { formatSiteKeys, generateSiteKeys } from "../dist/index.js";
|
|
10
|
+
|
|
11
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
12
|
+
process.stdout.write(
|
|
13
|
+
"usage: npx @byollm/server keygen\n\n" +
|
|
14
|
+
"Generates this site's byollm identity and prints it as an env line.\n" +
|
|
15
|
+
"Run it once. Store the result as a secret. Regenerating it makes every\n" +
|
|
16
|
+
"daemon that has paired with this site pair again.\n",
|
|
17
|
+
);
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
process.stdout.write(formatSiteKeys(generateSiteKeys()));
|