@byok-sdk/server 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,25 @@ The self-hosted SaaS-side reference coordinator: pairing, authenticated device
4
4
  HTTP/WebSocket/long-poll transport, task leasing, approvals, and in-memory
5
5
  stores over the frozen v1 protocol.
6
6
 
7
- Use `@byok-sdk/cloud` plus `@byok-sdk/cloud-postgres` for the durable hosted
7
+ Use `@byok-sdk/cloud` plus `@byok-sdk/cloud-dataplane` for the durable hosted
8
8
  composition.
9
9
 
10
+ Toolset-aware dispatch names logical device-local MCP toolsets; it never sends
11
+ their commands or credentials:
12
+
13
+ ```ts
14
+ const task = await server.dispatch({
15
+ deviceId,
16
+ instruction: 'Find five qualified prospects and draft follow-ups.',
17
+ runtime: 'claude',
18
+ policy: { mode: 'auto' },
19
+ requiredToolsets: ['salesko.prospecting'],
20
+ });
21
+ ```
22
+
23
+ The self-hosted coordinator rejects this call before task creation unless the
24
+ live device advertises `toolset-selection` and its `configuredToolsets`
25
+ inventory contains every required ID. `machines.list()` projects that same
26
+ logical-ID-only inventory; MCP commands and credentials remain device-local.
27
+
10
28
  MIT licensed. Node.js 22.19.0 or newer.
package/dist/auth.d.ts CHANGED
@@ -16,14 +16,19 @@ export declare const ACCESS_TOKEN_TTL_SECONDS: number;
16
16
  * another, and an attacker who can get a device to sign anything shaped like
17
17
  * a nonce holds a token-renewal credential.
18
18
  *
19
- * The client signs the same literal (`packages/client/src/daemon/device-keys.ts`).
19
+ * The literal lives in `@byok-sdk/core` (`src/pairing.ts`) and the client signs
20
+ * the same binding (`packages/client/src/daemon/device-keys.ts`) — it used to
21
+ * be three copies, each commented as byte-identical to the other two, which is
22
+ * an agreement that holds only until someone edits one. Re-exported here so
23
+ * this module's public surface is unchanged.
24
+ *
20
25
  * There is deliberately no dual mode: a raw, unprefixed nonce signature is
21
26
  * simply invalid here, with no flag, fallback, or grace window that would
22
27
  * make the old encoding acceptable again. Because the four packages have no
23
28
  * published compatibility contract yet, the recovery path for a device on
24
29
  * the old encoding is a re-pair, not a server-side shim.
25
30
  */
26
- export declare const NONCE_SIGNING_DOMAIN = "byok-nonce-v1\n";
31
+ export { NONCE_SIGNING_DOMAIN } from '@byok-sdk/core';
27
32
  /**
28
33
  * S1: server-local tenant identifier. A plain string alias for now — S2 moves
29
34
  * the branded/shared form into `@byok-sdk/core`, which does not exist yet, and
@@ -133,16 +138,23 @@ export declare class NonceStore {
133
138
  }
134
139
  /**
135
140
  * The ONLY nonce-signature check on this server (§6.2): the signed message is
136
- * {@link NONCE_SIGNING_DOMAIN} followed by the nonce. Applying the domain here
137
- * rather than at the call site is the point — there is one place that decides
138
- * what a device signature over a nonce means, so no route can be written that
139
- * accepts the undomained form.
141
+ * core's `nonceSigningBytes` — the domain followed by the nonce. Applying the
142
+ * domain here rather than at the call site is the point — there is one place
143
+ * that decides what a device signature over a nonce means, so no route can be
144
+ * written that accepts the undomained form.
140
145
  */
141
146
  export declare function verifyNonceSignature(devicePublicKey: string, nonce: string, signature: string): boolean;
142
147
  export declare function extractBearerToken(header: string | undefined): string | undefined;
143
148
  export interface AuthDeps {
144
149
  tokenSigner: TokenSigner;
145
150
  devices: DeviceRegistry;
151
+ /**
152
+ * The product THIS server instance serves (`createByokServer`'s
153
+ * `productId`). Part of authentication, not of routing: a device row paired
154
+ * into another product is not a principal here at all — see
155
+ * {@link authenticateBearer}.
156
+ */
157
+ productId: string;
146
158
  }
147
159
  /**
148
160
  * S1: the authenticated principal every authed surface works with. Built from
@@ -163,9 +175,19 @@ export interface AuthenticatedDevice {
163
175
  * S1 shape: the token's `(tenantId, deviceId)` are LOOKUP KEYS into the
164
176
  * registry, and the row that comes back is the authority. A token for a
165
177
  * device that no longer exists, one whose tenant does not own that device,
166
- * one whose product disagrees with the row, and one for a revoked device all
167
- * fail identically here and are indistinguishable to the caller there is
168
- * deliberately no "which of those was it" signal to hand back, so no route
169
- * can turn a 401 into a cross-tenant existence oracle.
178
+ * one whose product disagrees with the row, one whose row belongs to a
179
+ * different product than this instance serves, and one for a revoked device
180
+ * all fail identically here and are indistinguishable to the caller there
181
+ * is deliberately no "which of those was it" signal to hand back, so no route
182
+ * can turn a 401 into a cross-tenant (or cross-product) existence oracle.
183
+ *
184
+ * The last two checks are different facts and both are needed. Row vs claims
185
+ * says "the token belongs to this row"; row vs instance says "this row
186
+ * belongs to the product this server serves" — a single server can mint
187
+ * pairing codes for any product (`createPairingCode` takes the claims per
188
+ * code), so a row from another product is a real row holding a real token
189
+ * and is still not a principal here. `conn.hello`'s own product checks
190
+ * (`ws-server.ts`) validate the client's ANNOUNCEMENT, which is a third fact
191
+ * and stays where it is.
170
192
  */
171
193
  export declare function authenticateBearer(header: string | undefined, deps: AuthDeps): Promise<AuthenticatedDevice | undefined>;
package/dist/hub.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { WebSocket } from 'ws';
2
- import { type Envelope, type RuntimeId, type RuntimeInfo, type TaskState } from '@byok-sdk/protocol';
2
+ import { type Envelope, type RuntimeId, type RuntimeInfo, type TaskState, type ToolsetId } from '@byok-sdk/protocol';
3
3
  import type { DeviceRegistry } from './auth';
4
4
  import { RateLimiter } from './rate-limiter';
5
5
  import type { TaskStore } from './task-store';
@@ -29,7 +29,7 @@ import type { ByokServerEvent, DispatchInput, HubStats, MachineInfo, TaskHandle,
29
29
  * handlers below no longer carry their own device-mismatch checks.
30
30
  *
31
31
  * Outbound delivery (M1, §1.2/§9): every server -> daemon envelope
32
- * (`conn.ack`, `task.offer/approve/reject/cancel/steer`) gets a fresh
32
+ * (`conn.ack`, either task offer, `task.approve/reject/cancel/steer`) gets a fresh
33
33
  * per-device monotonic `seq` and is retained in a capped ring buffer
34
34
  * ({@link OUTBOX_RING_CAPACITY} entries) so it can be redelivered — in `seq`
35
35
  * order, skipping anything whose task has since reached a terminal state —
@@ -276,7 +276,7 @@ export declare class ConnectionHub {
276
276
  * connection this hub never learns capabilities for simply reads back
277
277
  * `undefined` from {@link getDeviceCapabilities}.
278
278
  */
279
- registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[]): void;
279
+ registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[], configuredToolsets?: readonly ToolsetId[]): void;
280
280
  sendConnAck(deviceId: string, capabilities: string[]): void;
281
281
  /**
282
282
  * Reconnection procedure step 3 (§9): redeliver, in `seq` order, every