@byok-sdk/server 0.11.0 → 0.13.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.
@@ -0,0 +1,71 @@
1
+ import type { RuntimeInfo, ToolsetId } from '@byok-sdk/protocol';
2
+ /**
3
+ * What this process has OBSERVED about a device's live connection.
4
+ *
5
+ * This is not a second authority over anything durable. The device row
6
+ * (`DeviceRecord`, `@byok-sdk/cloud`) owns identity and the capability list
7
+ * every admission gate reads; the mailbox owns delivery. What lives here is the
8
+ * part of `conn.hello` the kernel deliberately does not persist — the runtime
9
+ * DISCOVERY block and the client's self-reported version/toolset inventory —
10
+ * plus "when did we last hear from this device at all".
11
+ *
12
+ * Why the kernel does not persist it: `conn.hello.runtimes` describes what a
13
+ * device build could run, changes on every daemon restart, and authorizes
14
+ * nothing (the steer gate reads the CLAIM snapshot, never this). Storing it
15
+ * durably would create a stale second description of a device that outlives the
16
+ * process that saw it. Keeping it as an in-process observation is honest about
17
+ * its lifetime: restart the server and it is gone, exactly like the connection
18
+ * it describes.
19
+ *
20
+ * `connected` is therefore "observed alive and not since forgotten", not "a
21
+ * socket is open" — there are no sockets any more. Two observations set it: the
22
+ * device's own `conn.hello` over `POST /byok/messages`, and a `GET /byok/events`
23
+ * poll (a device that is polling is present even if it never announced). One
24
+ * clears it: revocation, which deletes the device row and everything scoped to
25
+ * it.
26
+ */
27
+ export interface DeviceConnection {
28
+ connected: boolean;
29
+ /** ISO-8601 instant of the most recent observation. */
30
+ lastSeen: string;
31
+ clientVersion?: string;
32
+ runtimes?: RuntimeInfo[];
33
+ configuredToolsets?: ToolsetId[];
34
+ }
35
+ /** `conn.hello`'s discovery half, as observed on one accepted announcement. */
36
+ export interface DeviceAnnouncement {
37
+ readonly clientVersion?: string;
38
+ readonly runtimes?: readonly RuntimeInfo[];
39
+ readonly configuredToolsets?: readonly ToolsetId[];
40
+ }
41
+ /**
42
+ * In-process device observations, in first-observation order.
43
+ *
44
+ * Insertion order is load-bearing for ambient dispatch selection
45
+ * (`device-selection.ts`): "the first connected device" must be stable and
46
+ * explainable, and a `Map` gives that for free without a second index.
47
+ */
48
+ export declare class DeviceConnections {
49
+ #private;
50
+ /**
51
+ * Record an accepted `conn.hello`. Discovery fields are REPLACED wholesale,
52
+ * never merged: a daemon that restarted with a runtime removed must not keep
53
+ * advertising it because an older hello mentioned it.
54
+ */
55
+ announce(deviceId: string, announcement: DeviceAnnouncement, at: string): void;
56
+ /**
57
+ * Record any other sign of life (an inbound envelope, a long-poll read).
58
+ * Deliberately additive: it refreshes `lastSeen` and marks the device present
59
+ * without touching the discovery block, because none of those signals carry
60
+ * one and clearing it would lose what the last hello said.
61
+ */
62
+ touch(deviceId: string, at: string): void;
63
+ get(deviceId: string): DeviceConnection | undefined;
64
+ isConnected(deviceId: string): boolean;
65
+ connectedCount(): number;
66
+ /** Device ids in first-observation order — the order ambient selection walks. */
67
+ ids(): readonly string[];
68
+ /** Drop everything scoped to a device. Called when its registration is deleted (§6.3). */
69
+ forget(deviceId: string): void;
70
+ clear(): void;
71
+ }
@@ -0,0 +1,30 @@
1
+ import { type ToolsetId } from '@byok-sdk/protocol';
2
+ /**
3
+ * Ambient device selection for a `dispatch()` that named no `deviceId`.
4
+ *
5
+ * `DispatchInput.deviceId` stays optional (ADR-034), so this rule survives the
6
+ * fold. It is a SCHEDULING convenience and nothing more: every admission gate
7
+ * that actually protects something — Agent capability, strict-agent-only,
8
+ * egress, toolset selection — runs afterwards against the durable device row
9
+ * inside the kernel, on the device this picked exactly as on one the caller
10
+ * named. Picking wrong therefore costs a refusal, never a wrongly-authorized
11
+ * dispatch.
12
+ *
13
+ * "First connected" means first OBSERVED (`connections.ts` preserves
14
+ * first-observation order), which is stable and explainable, unlike any
15
+ * load-shaped ordering this package has no information to compute.
16
+ */
17
+ export interface DeviceCandidate {
18
+ readonly deviceId: string;
19
+ /** The durable capability list from the device's last accepted `conn.hello`. */
20
+ readonly capabilities: readonly string[] | undefined;
21
+ /** The logical toolset inventory the same announcement reported, if any. */
22
+ readonly configuredToolsets: readonly ToolsetId[] | undefined;
23
+ }
24
+ export interface AmbientSelectionQuery {
25
+ /** Every toolset must be present; an unknown inventory is never guessed at. */
26
+ readonly requiredToolsets?: readonly ToolsetId[];
27
+ /** Agent-bound dispatch is the only caller allowed to land on a strict-agent-only device. */
28
+ readonly allowStrictAgentOnly?: boolean;
29
+ }
30
+ export declare function pickFirstConnectedDevice(candidates: readonly DeviceCandidate[], query?: AmbientSelectionQuery): string | undefined;
@@ -1,18 +1,53 @@
1
1
  /**
2
2
  * A tiny append-only, multi-reader async queue. `push` never blocks; `close`
3
3
  * marks the queue done. `subscribe()` returns a fresh async iterator that
4
- * always replays from the beginning of the buffer, so a consumer that calls
5
- * `events()` at any point still "sees everything" for that task's lifetime.
4
+ * always replays from the beginning of the retained buffer, so a consumer that
5
+ * calls `events()` at any point still sees everything still retained for that
6
+ * task's lifetime.
7
+ *
8
+ * Bounded, deliberately (WP3B §3): this queue is a NOTIFICATION relay, not a
9
+ * record of what happened — the durable facts live in the cloud stores a
10
+ * `ByokServer` reads back (`tasks.get`, `TaskHandle.result()`). A consumer that
11
+ * stops iterating must therefore cost bounded memory, not unbounded growth. On
12
+ * overflow the OLDEST entries are dropped and, exactly once per queue, a
13
+ * caller-supplied `truncationMarker` is appended so a reader can tell a
14
+ * complete feed from a clipped one instead of silently seeing a gap.
6
15
  *
7
16
  * Framework-agnostic on purpose (no Node/WS/Hono types here) so it can be
8
17
  * unit-tested and reused regardless of transport.
9
18
  */
19
+ export interface AsyncEventQueueOptions<T> {
20
+ /**
21
+ * Maximum entries retained before drop-oldest engages. Omitted means
22
+ * unbounded — only appropriate for a queue whose producer is itself bounded.
23
+ */
24
+ readonly maxBuffered?: number;
25
+ /**
26
+ * Appended once, after the first drop, so the truncation is observable.
27
+ * Omitted means a bounded queue that drops silently.
28
+ */
29
+ readonly truncationMarker?: T;
30
+ }
10
31
  export declare class AsyncEventQueue<T> {
11
32
  private readonly buffer;
33
+ private nextSequence;
12
34
  private closed;
13
35
  private waiters;
36
+ private readonly maxBuffered;
37
+ private readonly truncationMarker;
38
+ private truncationNoted;
39
+ constructor(options?: AsyncEventQueueOptions<T>);
40
+ /** True once this queue has dropped at least one entry. */
41
+ get truncated(): boolean;
14
42
  push(value: T): void;
15
43
  close(): void;
44
+ /**
45
+ * Drop the oldest entries back down to the bound. The marker is queue
46
+ * metadata, not a buffered entry: every subscriber observes it at most once
47
+ * after the first drop, while all retained capacity remains available to
48
+ * real events.
49
+ */
50
+ private enforceBound;
16
51
  private wake;
17
52
  private waitForMore;
18
53
  /** Async-iterate the buffer from index 0, waiting for new pushes until closed. */
package/dist/index.d.ts CHANGED
@@ -1,65 +1,85 @@
1
- import type { Server as HttpServer } from 'node:http';
2
- import type { Hono } from 'hono';
3
- import { type TenantId } from './auth';
4
- import { type PairingCodeClaims, type PairingCodeInfo } from './pairing';
1
+ import { Hono } from 'hono';
2
+ import { type PairingCodeInfo } from '@byok-sdk/cloud';
3
+ import { type AgentRef } from '@byok-sdk/protocol';
4
+ import type { MailboxRetentionInput, MailboxRetentionResult } from '@byok-sdk/core';
5
5
  import type { ByokServerEvent, AgentContentReadRequest, AgentHomeProjectionRequest, AgentHomeProjectionStatusReadback, AgentEgressReceipt, CreateByokServerOptions, DispatchInput, FreshAgentEgressDispatchInput, HubStats, MachineInfo, TaskHandle, TaskSnapshot } from './types';
6
- export type { ByokServerEvent, AgentContentReadRequest, AgentHomeProjectionRequest, AgentHomeProjectionStatusReadback, AgentEgressReceipt, CreateByokServerOptions, DispatchInput, FreshAgentEgressDispatchInput, HubStats, MachineInfo, ServerTaskEvent, TaskHandle, TaskResult, TaskSnapshot, } from './types';
7
- export type { CreateTaskInput, TaskRecord, TaskStore } from './task-store';
8
- export { IllegalTaskTransitionError, InMemoryTaskStore } from './task-store';
6
+ export type { ByokServerEvent, AgentContentReadRequest, AgentHomeProjectionRequest, AgentHomeProjectionStatusReadback, AgentEgressReceipt, ByokServerStorage, CreateByokServerOptions, DispatchInput, FreshAgentEgressDispatchInput, HubStats, MachineInfo, ServerTaskEvent, TaskHandle, TaskResult, TaskSnapshot, } from './types';
9
7
  /**
10
- * M5 (approval targeting, docs/protocol.md §5.3): previously unreachable via
11
- * this package's public entry point (only importable from the internal
12
- * `./hub` path) — `TaskHandle.approve`/`reject`'s `opts.approvalId` targeting
13
- * (`types.ts`) throws this, so a caller needs it exported here to
14
- * `instanceof`-check/inspect it. See `hub.ts`'s own doc comment for the full
15
- * staleness semantics.
8
+ * M5 (approval targeting, docs/protocol.md §5.3): `TaskHandle.approve`/`reject`'s
9
+ * `opts.approvalId` targeting throws this when the id names an approval the
10
+ * task has already superseded, so a caller needs it to `instanceof`-check and
11
+ * inspect the two ids. Re-exported from `@byok-sdk/cloud`, which owns the gate
12
+ * both the embedded and the hosted surface are decided by — one class, one
13
+ * `instanceof` that works across both.
16
14
  */
17
- export { StaleApprovalError } from './hub';
15
+ export { StaleApprovalError } from '@byok-sdk/cloud';
18
16
  /**
19
- * S0 (GAP-002): `TaskHandle.steer` (`types.ts`) throws this when the runtime
20
- * that claimed the task cannot be steered, when the task isn't running, or
21
- * when it's already terminal — a caller needs the class to `instanceof`-check
22
- * it and the code union to switch on. See `hub.ts`'s own doc comments for the
23
- * full gate order and the fail-closed-on-unknown rationale.
17
+ * S0 (GAP-002): `TaskHandle.steer` throws this when the runtime that claimed
18
+ * the task cannot be steered, when the task isn't running, or when it's already
19
+ * terminal. The GATE is the kernel's — it reads the claim-time capability
20
+ * snapshot and nothing else — and so is the `code`. The CLASS is this
21
+ * package's, because it carries `state: TaskState`, the wire vocabulary this
22
+ * surface speaks and the kernel deliberately has no field for. See
23
+ * `task-handle.ts` for the full reasoning and for why `SteerRejectionCode` and
24
+ * {@link StaleApprovalError} stay kernel re-exports.
24
25
  */
25
- export { SteerRejectedError } from './hub';
26
- export type { SteerRejectionCode } from './hub';
27
- export { AgentHomeProjectionCompletionError } from './hub';
28
- export type { AgentHomeProjectionCompletionErrorCode } from './hub';
29
- export { PairingCodeInvalidError } from './pairing';
30
- export type { PairingCodeClaims, PairingCodeInfo } from './pairing';
31
- export type { AccessTokenClaims, AuthenticatedDevice, DeviceRecord, TenantId, TokenSigner, } from './auth';
26
+ export { SteerRejectedError } from './task-handle';
27
+ export type { SteerRejectionCode } from '@byok-sdk/cloud';
32
28
  /**
33
- * S1: `DeviceRegistry` itself is deliberately NOT exported. Its
34
- * tenant-scoped surface is reachable through `ByokServer.devices` (below),
35
- * and the one method that resolves a device without a tenant in scope
36
- * (`resolveByDeviceId`, for the two pre-tenant wire endpoints) exists only
37
- * inside this package — exporting the class would hand every embedder a
38
- * cross-tenant device oracle for free.
29
+ * Auth v2 types an embedder needs to talk about devices and tokens. All owned
30
+ * by `@byok-sdk/cloud` now — this package no longer has an auth plane of its
31
+ * own to keep in agreement with one.
39
32
  */
40
- export { createHmacTokenSigner } from './auth';
41
- export type { BlobStore, CreateUploadInput, ReadContentResult, WriteContentResult, } from './blob-store';
42
- export { LocalDiskBlobStore } from './blob-store';
43
- export type { SqliteTaskStoreOptions } from './sqlite-task-store';
44
- export { SqliteTaskStore } from './sqlite-task-store';
45
- export type { SqliteBlobStoreOptions } from './sqlite-blob-store';
46
- export { SqliteBlobStore } from './sqlite-blob-store';
33
+ export type { AccessTokenClaims, DeviceRecord, PairingCodeInfo, TenantId, TokenSigner } from '@byok-sdk/cloud';
34
+ export { createHmacTokenSigner } from '@byok-sdk/cloud';
35
+ /** Cutoffs and result of {@link ByokServer.mailbox.collectRetired}, owned by `@byok-sdk/core`. */
36
+ export type { MailboxRetentionInput, MailboxRetentionResult } from '@byok-sdk/core';
47
37
  export { SqliteUnavailableError } from './sqlite-support';
48
38
  export type { RateLimiterOptions } from './rate-limiter';
39
+ export { DEFAULT_TASK_EVENT_BUFFER_LIMIT, DEFAULT_TASK_EVENT_RETENTION_MS } from './relay';
40
+ /** Page size `tasks.list()` uses when the caller names none. */
41
+ export declare const DEFAULT_TASK_PAGE_LIMIT = 100;
42
+ /** Input to {@link ByokServer.pairing.createPairingCode}. */
43
+ export interface CreatePairingCodeInput {
44
+ /**
45
+ * The product the redeeming device pairs into. Must be this instance's own
46
+ * `productId`: an embedded server serves exactly one product, and a code for
47
+ * some other product would mint a device every bearer-authed route then
48
+ * refuses (`instanceProductId`, `@byok-sdk/cloud`). Fail closed rather than
49
+ * silently issuing an unusable code.
50
+ *
51
+ * The TENANT is not a parameter: it is derived from `productId` once, at
52
+ * construction (`serverTenantId`, `stores.ts`), because this surface has no
53
+ * second tenant to name.
54
+ */
55
+ readonly productId: string;
56
+ /** Overrides the default single-use code lifetime. */
57
+ readonly ttlMs?: number;
58
+ }
59
+ /** One bounded page of this server's tasks. */
60
+ export interface TaskPage {
61
+ readonly tasks: readonly TaskSnapshot[];
62
+ /**
63
+ * Pass as the next call's `cursor`. ABSENT means the walk is over — a caller
64
+ * stops on absence, not on an empty page, so a page that exactly fills
65
+ * `limit` with nothing after it still terminates.
66
+ */
67
+ readonly nextCursor?: string;
68
+ }
69
+ /** Query for {@link ByokServer.tasks.list}. */
70
+ export interface TaskListQuery {
71
+ /** Maximum snapshots in the page. Defaults to {@link DEFAULT_TASK_PAGE_LIMIT}. */
72
+ readonly limit?: number;
73
+ /** The `nextCursor` from the previous page; absent starts at the beginning. */
74
+ readonly cursor?: string;
75
+ }
49
76
  /** The object `createByokServer` returns — the SaaS-embedder-facing surface. */
50
77
  export interface ByokServer {
51
- /** Hono app exposing the pair/challenge/token/blob/events HTTP routes. Mount it, or use its `.fetch` with `@hono/node-server`. */
78
+ /** Hono app exposing every device route, plus the opt-in `/healthz`. Mount it, or use its `.fetch` with `@hono/node-server`. */
52
79
  hono: Hono;
53
- /** Wire up the `GET /byok/ws` upgrade on the raw Node HTTP server serving `hono`. */
54
- attachWebSocket(server: HttpServer): void;
55
80
  pairing: {
56
- /**
57
- * S1: minting a code REQUIRES the tenant and product the redeeming
58
- * device will be paired into (docs/protocol.md §6.1) — the SaaS's own
59
- * auth/device-flow UI is the only party that knows them, and the device
60
- * never gets to name its own. There is no claimless overload.
61
- */
62
- createPairingCode(claims: PairingCodeClaims): PairingCodeInfo;
81
+ /** Mint a single-use pairing code for this server's product and tenant (docs/protocol.md §6.1). */
82
+ createPairingCode(input: CreatePairingCodeInput): Promise<PairingCodeInfo>;
63
83
  };
64
84
  dispatch(input: DispatchInput): Promise<TaskHandle>;
65
85
  /** Dispatch a fresh Agent execution whose runtime will mint its session after start. */
@@ -68,18 +88,28 @@ export interface ByokServer {
68
88
  requestAgentContentRead(input: AgentContentReadRequest): Promise<void>;
69
89
  /** Enqueue one task-free, exact-device Agent-home projection. */
70
90
  enqueueAgentHomeProjection(input: AgentHomeProjectionRequest): Promise<AgentHomeProjectionStatusReadback>;
71
- /** Reference-only in-process status readback; production durability belongs to @byok-sdk/cloud stores. */
72
- readAgentHomeProjection(deviceId: string, requestId: string): AgentHomeProjectionStatusReadback | undefined;
91
+ /** Durable desired-state and terminal-outcome readback for one exact device-and-Agent request. */
92
+ readAgentHomeProjection(deviceId: string, agentRef: AgentRef, requestId: string): Promise<AgentHomeProjectionStatusReadback | undefined>;
73
93
  tasks: {
74
- get(taskId: string): TaskSnapshot | undefined;
75
- list(): TaskSnapshot[];
94
+ get(taskId: string): Promise<TaskSnapshot | undefined>;
95
+ /**
96
+ * One bounded page, keyset-paged by task id. Paged rather than "all of
97
+ * them" because the underlying store is: an unbounded `list()` would have
98
+ * to walk every page internally and hand back a snapshot that was never
99
+ * consistent at any single instant.
100
+ */
101
+ list(query?: TaskListQuery): Promise<TaskPage>;
102
+ };
103
+ /** Trusted embedder access to committed blob download grants. */
104
+ blobs: {
105
+ getDownloadUrl(blobId: string): Promise<string | undefined>;
76
106
  };
77
- /** Reference-server reliable egress receipt readback. */
107
+ /** Reliable Agent egress receipt readback. */
78
108
  egress: {
79
- get(deviceId: string, eventId: string): AgentEgressReceipt | undefined;
109
+ get(deviceId: string, agentRef: AgentRef, eventId: string): Promise<AgentEgressReceipt | undefined>;
80
110
  };
81
111
  machines: {
82
- list(): MachineInfo[];
112
+ list(): Promise<MachineInfo[]>;
83
113
  };
84
114
  events: {
85
115
  subscribe(): AsyncIterable<ByokServerEvent>;
@@ -87,48 +117,85 @@ export interface ByokServer {
87
117
  /**
88
118
  * Device revocation (§6.3) — server-side only, no wire message. Revoking a
89
119
  * device DELETES its registration, so its next `/byok/challenge`,
90
- * `/byok/token`, WSS connect, or authed HTTP call gets a 401 — the same
91
- * answer as for a device id that was never registered — and its only
92
- * recourse is to re-run `/byok/pair`. The device-scoped state the row
93
- * owned (outstanding challenge nonces, presence, inbound dedup) is deleted
94
- * with it; what the device DID (tasks, receipts) is history and survives.
120
+ * `/byok/token`, or authed HTTP call gets a 401 — the same answer as for a
121
+ * device id that was never registered — and its only recourse is to re-run
122
+ * `/byok/pair`. The device-scoped state the row owned (outstanding challenge
123
+ * nonces, presence, inbound dedup) is deleted with it; what the device DID
124
+ * (tasks, receipts) is history and survives.
95
125
  *
96
- * S1: tenant-first, and a tenant can only revoke a device it owns — a
97
- * `(tenantId, deviceId)` pair belonging to someone else resolves to
98
- * nothing and this is a silent no-op rather than a cross-tenant write.
126
+ * DEVICE-ID ONLY. The hosted control plane's own revocation is tenant-first
127
+ * (a tenant may only revoke a device it owns), but an embedded server owns
128
+ * exactly ONE tenant and binds it here itself: `TenantId` is a branded type an
129
+ * embedder cannot mint, and nothing on this surface — `ByokServer`,
130
+ * `MachineInfo`, `PairingCodeInfo` — hands one back, so a tenant-first
131
+ * parameter would make this method uncallable from outside the package rather
132
+ * than safer. The scoping it provided is unchanged, just not the caller's to
133
+ * state: a device id this server does not know resolves to nothing and is a
134
+ * silent no-op.
99
135
  */
100
136
  devices: {
101
- revoke(tenantId: TenantId, deviceId: string): void;
137
+ revoke(deviceId: string): Promise<void>;
102
138
  };
103
139
  /**
104
- * Stop background timers owned by this server instance — currently just
105
- * the task-lease reaper (`ConnectionHub.stopLeaseReaper`, `hub.ts`). Call
106
- * this on shutdown so nothing keeps the process alive or leaks a handle in
107
- * tests; safe to call more than once.
140
+ * Mailbox retention for this server's tenant — the host control-plane
141
+ * operation core defines (`MailboxStore.collectRetired`), forwarded verbatim.
142
+ *
143
+ * A pass-through, deliberately, and NOT a retention policy: the caller names
144
+ * both cutoffs, so this package invents no TTL, runs no timer, and holds no
145
+ * second opinion about when a device's undelivered work is declared lost.
146
+ * Nothing in `@byok-sdk/core`, `@byok-sdk/cloud` or this façade drives the
147
+ * sweep on its own, which is exactly why an embedder needs a way to reach it:
148
+ * without one, an embedded server retires nothing, ever, and the
149
+ * `cursor_too_old` floor can never move.
150
+ *
151
+ * Acked rows appended before `ackedBefore` are DELETED; unacked rows appended
152
+ * before `expireUnackedBefore` are dead-lettered as `expired` and stay
153
+ * visible, which is what moves `recoverableFrom` and turns a device polling
154
+ * from a lost cursor into a `409 cursor_too_old` resync instead of a silently
155
+ * short page. Both cutoffs must be canonical ISO-8601 UTC.
156
+ */
157
+ mailbox: {
158
+ collectRetired(input: MailboxRetentionInput): Promise<MailboxRetentionResult>;
159
+ };
160
+ /**
161
+ * Release what this instance holds: the relay's per-task feeds and their
162
+ * reclamation timers, and the connection observations. Call it on shutdown so
163
+ * nothing keeps the process alive or leaks a handle in tests; safe to call
164
+ * more than once. SQLite embedders that need to await handle release should
165
+ * call {@link ByokServer.close} instead.
108
166
  */
109
167
  stop(): void;
168
+ /** Drain pending store calls and release the selected storage authority. */
169
+ close(): Promise<void>;
110
170
  /**
111
- * M4 Phase 4 (part B.1): a plain, serializable in-process snapshot of this
112
- * hub's current state — connected device count, task counts by state,
113
- * envelope in/out totals, dedup drops, rate-limit events, and uptime. See
114
- * {@link HubStats} for the full contract. Deliberately in-process only —
115
- * never exposed over HTTP by this SDK itself (see
116
- * `CreateByokServerOptions.healthzRoute`'s doc comment); an embedder that
117
- * wants any of this surfaced remotely builds its own authenticated route
118
- * around this method.
171
+ * A plain, serializable snapshot of this server's current state. See
172
+ * {@link HubStats} for the field-by-field contract.
173
+ *
174
+ * Async because `taskCountsByState` is COMPUTED from the durable task store
175
+ * on every call rather than mirrored into a counter this package would then
176
+ * have to keep in agreement with it — that mirror was the second task
177
+ * authority the fold exists to remove. Deliberately in-process only: never
178
+ * exposed over HTTP by this SDK itself (see
179
+ * `CreateByokServerOptions.healthzRoute`); an embedder that wants any of it
180
+ * surfaced remotely builds its own authenticated route around this method.
119
181
  */
120
- stats(): HubStats;
182
+ stats(): Promise<HubStats>;
121
183
  }
122
184
  /**
123
- * In-memory reference implementation of the SaaS-side coordinator: Auth v2
124
- * device pairing/renewal/revocation, a WS + long-poll connection hub with
125
- * at-least-once redelivery, a local-disk blob store, and task dispatch/
126
- * lifecycle tracking. See the per-module doc comments (`auth.ts`,
127
- * `blob-store.ts`, `hub.ts`, `pairing.ts`, `ws-server.ts`) for what's a
128
- * pinned wire/HTTP contract (docs/protocol.md) versus a reference-impl
129
- * choice a SaaS embedder might swap out (`tokenSigner`, `blobStore`,
130
- * `taskStore` — the latter two default to in-memory/local-disk and lose all
131
- * state on restart; see `sqlite-task-store.ts`/`sqlite-blob-store.ts` for
132
- * persistent M3 alternatives implementing the same interfaces).
185
+ * Embedded reference coordinator: a thin façade over `@byok-sdk/cloud`'s
186
+ * kernel, composed against the explicitly selected embedded stores.
187
+ *
188
+ * What that means concretely — and it is the whole point of WP3B — is that this
189
+ * package owns NO coordination semantics any more. Pairing, tokens, the inbound
190
+ * gate, task ownership, first-terminal-wins, approvals, steering, cancellation,
191
+ * long-poll redelivery and the `cursor_too_old` floor are all the kernel's, and
192
+ * a device cannot tell this from a hosted deployment. What is left here is the
193
+ * embedded shape: one product, one tenant, a `TaskHandle` for hosts that want
194
+ * one, an in-process notification relay, and the observability an embedder used
195
+ * to get from the hub.
196
+ *
197
+ * State is in-memory by default. Explicit SQLite mode persists the six
198
+ * coordination interfaces whose contracts cross a process restart; all other
199
+ * ports remain process-local.
133
200
  */
134
201
  export declare function createByokServer(opts: CreateByokServerOptions): ByokServer;