@byok-sdk/server 0.1.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/LICENSE +21 -0
- package/README.md +10 -0
- package/dist/auth.d.ts +171 -0
- package/dist/blob-store.d.ts +77 -0
- package/dist/event-queue.d.ts +20 -0
- package/dist/heartbeat.d.ts +30 -0
- package/dist/http.d.ts +24 -0
- package/dist/hub.d.ts +861 -0
- package/dist/ids.d.ts +3 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +2675 -0
- package/dist/index.js.map +1 -0
- package/dist/pairing.d.ts +58 -0
- package/dist/rate-limiter.d.ts +114 -0
- package/dist/sqlite-blob-store.d.ts +89 -0
- package/dist/sqlite-support.d.ts +84 -0
- package/dist/sqlite-task-store.d.ts +123 -0
- package/dist/task-store.d.ts +125 -0
- package/dist/types.d.ts +378 -0
- package/dist/ws-server.d.ts +20 -0
- package/package.json +56 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts","../src/blob-store.ts","../src/ids.ts","../src/pairing.ts","../src/http.ts","../src/event-queue.ts","../src/rate-limiter.ts","../src/hub.ts","../src/task-store.ts","../src/heartbeat.ts","../src/ws-server.ts","../src/sqlite-support.ts","../src/sqlite-task-store.ts","../src/sqlite-blob-store.ts","../src/index.ts"],"names":["verifyEd25519Raw","randomBytes","randomUUID","createHash","canTransition","PROTOCOL_VERSION","require","path","SCHEMA","DEFAULT_URL_TTL_MS","sha256Hex","timingSafeEqual","createHmac"],"mappings":"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","file":"index.js","sourcesContent":["import { createPublicKey, randomBytes, verify as verifyEd25519Raw } from 'node:crypto';\nimport { jwtVerify, SignJWT } from 'jose';\n\n/**\n * Auth v2 (docs/protocol.md §6): device identity (Ed25519 keypair, public\n * half registered at pairing time), single-use nonce challenge/response for\n * token renewal, and JWT access tokens. Kept separate from `pairing.ts`\n * (which now only owns the one-time pairing-code lifecycle) because these\n * concerns span every authed surface (WSS upgrade, blob routes, events\n * long-poll), not just `POST /byok/pair`.\n */\n\n/** Access tokens are JWTs with a ~1h lifetime (docs/protocol.md §6.1/§6.2). */\nexport const ACCESS_TOKEN_TTL_SECONDS = 60 * 60;\n\n/** A challenge nonce is single-use and expires after ~5min (docs/protocol.md §6.2). */\nconst NONCE_TTL_MS = 5 * 60 * 1000;\n\n/**\n * S1 (GAP-004): the domain-separation prefix a device signs along with a\n * challenge nonce. The device key is a long-lived identity key that later\n * planes (S6 device proof) will also sign structured messages with; without a\n * domain tag, a signature produced for one purpose is a valid signature for\n * another, and an attacker who can get a device to sign anything shaped like\n * a nonce holds a token-renewal credential.\n *\n * The client signs the same literal (`packages/client/src/daemon/device-keys.ts`).\n * There is deliberately no dual mode: a raw, unprefixed nonce signature is\n * simply invalid here, with no flag, fallback, or grace window that would\n * make the old encoding acceptable again. Because the four packages have no\n * published compatibility contract yet, the recovery path for a device on\n * the old encoding is a re-pair, not a server-side shim.\n */\nexport const NONCE_SIGNING_DOMAIN = 'byok-nonce-v1\\n';\n\n// ---------------------------------------------------------------------------\n// TokenSigner — interface-shaped so a SaaS can supply its own signer later\n// (e.g. an asymmetric org-wide signer, or one backed by a KMS) without this\n// package caring how tokens are actually signed/verified. The default is an\n// in-memory HS256 secret generated fresh per server instance — good enough\n// for a single-process reference server, not meant to survive a restart.\n// ---------------------------------------------------------------------------\n\n/**\n * S1: server-local tenant identifier. A plain string alias for now — S2 moves\n * the branded/shared form into `@byok-sdk/core`, which does not exist yet, and\n * depending on an unbuilt package would be worse than naming the concept\n * here. What matters at this stage is that every identity-carrying shape in\n * this package names the tenant explicitly and required, never optional.\n */\nexport type TenantId = string;\n\n/**\n * S1: an access token binds a device to the tenant AND product its row was\n * paired into. All three are required — there is no tenant-less token shape.\n * These are LOOKUP KEYS, not authority: `authenticateBearer` resolves them\n * against the device registry and answers with the ROW's identity (see\n * {@link AuthenticatedDevice}).\n */\nexport interface AccessTokenClaims {\n deviceId: string;\n tenantId: TenantId;\n productId: string;\n}\n\nexport interface TokenSigner {\n sign(claims: AccessTokenClaims, expiresInSeconds: number): Promise<string>;\n /** Returns the claims for a valid, unexpired token, or `undefined` if invalid/expired/malformed. */\n verify(token: string): Promise<AccessTokenClaims | undefined>;\n}\n\n/** Default {@link TokenSigner}: HS256 over a random 32-byte secret held in memory for this process's lifetime. */\nexport function createHmacTokenSigner(secret: Uint8Array = randomBytes(32)): TokenSigner {\n return {\n async sign(claims, expiresInSeconds) {\n return new SignJWT({ deviceId: claims.deviceId, tenantId: claims.tenantId, productId: claims.productId })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(Math.floor(Date.now() / 1000) + expiresInSeconds)\n .sign(secret);\n },\n async verify(token) {\n try {\n const { payload } = await jwtVerify(token, secret);\n // A token missing any leg of the identity triple is not a\n // partially-usable token — it is not a token this server minted.\n if (typeof payload.deviceId !== 'string') return undefined;\n if (typeof payload.tenantId !== 'string') return undefined;\n if (typeof payload.productId !== 'string') return undefined;\n return { deviceId: payload.deviceId, tenantId: payload.tenantId, productId: payload.productId };\n } catch {\n return undefined;\n }\n },\n };\n}\n\n/** Mint a fresh access token + its ISO-8601 expiry, per {@link ACCESS_TOKEN_TTL_SECONDS}. */\nexport async function mintAccessToken(\n signer: TokenSigner,\n claims: AccessTokenClaims,\n): Promise<{ accessToken: string; expiresAt: string }> {\n const accessToken = await signer.sign(claims, ACCESS_TOKEN_TTL_SECONDS);\n const expiresAt = new Date(Date.now() + ACCESS_TOKEN_TTL_SECONDS * 1000).toISOString();\n return { accessToken, expiresAt };\n}\n\n// ---------------------------------------------------------------------------\n// DeviceRegistry — device identity directory, keyed by (tenantId, deviceId).\n// Registered at `/byok/pair` time from the redeemed pairing-code claims;\n// consulted by every authed surface so a revoked device's JWT (even if not\n// yet expired) stops working immediately (§6.3).\n//\n// S1: every lookup that has a tenant in scope MUST pass it — the composite\n// key is the lookup, not a post-hoc comparison, so a token whose tenant does\n// not own the device simply finds nothing. The one exception is\n// `resolveByDeviceId`, kept off this package's public entry point on purpose;\n// see its own doc comment.\n// ---------------------------------------------------------------------------\n\nexport interface DeviceRecord {\n /** S1: the tenant this device was paired into. Comes from the pairing-code claims and is never client-supplied. */\n tenantId: TenantId;\n /** S1: the product this device was paired into — checked against `conn.hello.productId` and against every token's claims. */\n productId: string;\n deviceId: string;\n deviceName: string;\n /** Ed25519 public key, base64url-encoded (JWK `x` form — see {@link verifyEd25519Signature}). */\n devicePublicKey: string;\n revoked: boolean;\n}\n\n/** Everything `POST /byok/pair` knows at registration time; `revoked` is the registry's own to set. */\nexport type DeviceRegistration = Omit<DeviceRecord, 'revoked'>;\n\nexport class DeviceRegistry {\n /** Keyed by {@link DeviceRegistry.key} — `(tenantId, deviceId)`. */\n private readonly devices = new Map<string, DeviceRecord>();\n /**\n * Secondary index over the SAME record objects, for the two pre-tenant\n * endpoints only (see {@link resolveByDeviceId}). Holding the same object\n * reference means a revocation applied through the composite key is\n * immediately visible here too — there is no second copy to keep in sync.\n */\n private readonly byDeviceId = new Map<string, DeviceRecord>();\n\n private static key(tenantId: TenantId, deviceId: string): string {\n // NUL separator: neither a tenantId nor a deviceId can contain one, so no\n // pair of distinct identities can collide onto the same key.\n return `${tenantId}\\u0000${deviceId}`;\n }\n\n /**\n * Write a device row. Every identity field is required by\n * {@link DeviceRegistration}, so a row with no tenant cannot be constructed\n * — which is the whole point of S1.\n */\n register(device: DeviceRegistration): void {\n const record: DeviceRecord = { ...device, revoked: false };\n this.devices.set(DeviceRegistry.key(record.tenantId, record.deviceId), record);\n this.byDeviceId.set(record.deviceId, record);\n }\n\n /** The row `tenantId` owns under `deviceId`, or `undefined` — including when the device exists under a DIFFERENT tenant. */\n get(tenantId: TenantId, deviceId: string): DeviceRecord | undefined {\n return this.devices.get(DeviceRegistry.key(tenantId, deviceId));\n }\n\n /**\n * Revoke a device (public API via `createByokServer(...).devices.revoke`).\n * Its next `/byok/challenge`, `/byok/token`, WSS connect, or authed HTTP\n * call gets a 401; the daemon's only recourse is to re-run `/byok/pair`\n * (docs/protocol.md §6.3). A tenant can only revoke its own devices: a\n * (tenantId, deviceId) pair it does not own resolves to nothing and this is\n * a no-op.\n */\n revoke(tenantId: TenantId, deviceId: string): void {\n const record = this.get(tenantId, deviceId);\n if (record) record.revoked = true;\n }\n\n /** Every known device row, across tenants — the in-process read model behind `ByokServer.machines.list()`. */\n list(): DeviceRecord[] {\n return [...this.devices.values()];\n }\n\n /**\n * Resolve a device by its globally-unique id alone, WITHOUT a tenant in\n * scope. Exists for exactly two callers — `POST /byok/challenge` and\n * `POST /byok/token` — because those two carry no tenant at all: their\n * request DTOs are the pinned wire contract (docs/protocol.md §6.2), the\n * device authenticates by key possession, and the row itself is what tells\n * the server which tenant to mint the next token for. Everything with a\n * token (and therefore a tenant) in scope goes through {@link get}.\n *\n * Deliberately NOT re-exported from this package's entry point (`index.ts`\n * exports no naked-lookup surface at all), so no embedder can turn it into\n * a cross-tenant device oracle: the only reachable public device surface is\n * tenant-first.\n */\n resolveByDeviceId(deviceId: string): DeviceRecord | undefined {\n return this.byDeviceId.get(deviceId);\n }\n}\n\n// ---------------------------------------------------------------------------\n// NonceStore — single-use challenge nonces for token renewal (§6.2).\n// ---------------------------------------------------------------------------\n\ninterface NonceRecord {\n deviceId: string;\n expiresAt: number;\n used: boolean;\n}\n\nexport class NonceStore {\n private readonly nonces = new Map<string, NonceRecord>();\n\n /** Number of nonce records currently held (post-sweep). Exposed for tests only. */\n get size(): number {\n return this.nonces.size;\n }\n\n /**\n * Drop every used or expired record. A long-lived server never calls this\n * on a timer, so `issue()` sweeps inline — a full-Map scan is fine at\n * reference-impl scale (single-digit nonces per device, ~5min TTL).\n */\n private sweep(now: number): void {\n for (const [nonce, record] of this.nonces) {\n if (record.used || record.expiresAt <= now) {\n this.nonces.delete(nonce);\n }\n }\n }\n\n issue(deviceId: string): string {\n const now = Date.now();\n this.sweep(now);\n const nonce = randomBytes(24).toString('base64url');\n this.nonces.set(nonce, { deviceId, expiresAt: now + NONCE_TTL_MS, used: false });\n return nonce;\n }\n\n /** `true` iff `nonce` exists, belongs to `deviceId`, is unexpired, and hasn't been consumed yet. Does not mutate. */\n validate(deviceId: string, nonce: string): boolean {\n const record = this.nonces.get(nonce);\n if (!record) return false;\n if (record.used) return false;\n if (record.deviceId !== deviceId) return false;\n if (Date.now() > record.expiresAt) return false;\n return true;\n }\n\n /** Mark `nonce` consumed so a replay of the same (deviceId, nonce, signature) is rejected. */\n markUsed(nonce: string): void {\n const record = this.nonces.get(nonce);\n if (record) record.used = true;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Ed25519 signature verification (node:crypto — no new crypto dependency).\n// ---------------------------------------------------------------------------\n\n/**\n * Verify `signature` (base64url) is a valid Ed25519 signature over `message`\n * (UTF-8) made by the private key matching `devicePublicKey` (base64url raw\n * 32-byte Ed25519 public key — the same encoding as a JWK's `x` field, which\n * is how we hand it to `node:crypto` without needing DER/SPKI conversion).\n */\nfunction verifyEd25519Signature(devicePublicKey: string, message: string, signature: string): boolean {\n try {\n const keyObject = createPublicKey({\n key: { kty: 'OKP', crv: 'Ed25519', x: devicePublicKey },\n format: 'jwk',\n });\n return verifyEd25519Raw(null, Buffer.from(message, 'utf8'), keyObject, Buffer.from(signature, 'base64url'));\n } catch {\n return false;\n }\n}\n\n/**\n * The ONLY nonce-signature check on this server (§6.2): the signed message is\n * {@link NONCE_SIGNING_DOMAIN} followed by the nonce. Applying the domain here\n * rather than at the call site is the point — there is one place that decides\n * what a device signature over a nonce means, so no route can be written that\n * accepts the undomained form.\n */\nexport function verifyNonceSignature(devicePublicKey: string, nonce: string, signature: string): boolean {\n return verifyEd25519Signature(devicePublicKey, NONCE_SIGNING_DOMAIN + nonce, signature);\n}\n\n// ---------------------------------------------------------------------------\n// Bearer-token extraction + authentication shared by ws-server.ts and http.ts.\n// ---------------------------------------------------------------------------\n\nexport function extractBearerToken(header: string | undefined): string | undefined {\n if (!header) return undefined;\n const match = /^Bearer\\s+(.+)$/i.exec(header);\n return match?.[1];\n}\n\nexport interface AuthDeps {\n tokenSigner: TokenSigner;\n devices: DeviceRegistry;\n}\n\n/**\n * S1: the authenticated principal every authed surface works with. Built from\n * the DEVICE ROW, never from the token payload — the token's claims are only\n * the keys used to find that row (see {@link authenticateBearer}). A caller\n * holding one of these is holding identity the registry vouched for.\n */\nexport interface AuthenticatedDevice {\n deviceId: string;\n tenantId: TenantId;\n productId: string;\n}\n\n/**\n * Resolve an `Authorization: Bearer <jwt>` header to an {@link AuthenticatedDevice},\n * or `undefined` — the single check every authed HTTP route and the WSS\n * upgrade share.\n *\n * S1 shape: the token's `(tenantId, deviceId)` are LOOKUP KEYS into the\n * registry, and the row that comes back is the authority. A token for a\n * device that no longer exists, one whose tenant does not own that device,\n * one whose product disagrees with the row, and one for a revoked device all\n * fail identically here and are indistinguishable to the caller — there is\n * deliberately no \"which of those was it\" signal to hand back, so no route\n * can turn a 401 into a cross-tenant existence oracle.\n */\nexport async function authenticateBearer(\n header: string | undefined,\n deps: AuthDeps,\n): Promise<AuthenticatedDevice | undefined> {\n const token = extractBearerToken(header);\n if (!token) return undefined;\n const claims = await deps.tokenSigner.verify(token);\n if (!claims) return undefined;\n const device = deps.devices.get(claims.tenantId, claims.deviceId);\n if (!device || device.revoked) return undefined;\n if (device.productId !== claims.productId) return undefined;\n return { deviceId: device.deviceId, tenantId: device.tenantId, productId: device.productId };\n}\n","import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { mkdtempSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport path from 'node:path';\n\n/**\n * Blob flows (docs/protocol.md §7): `POST /byok/blobs` declares a blob and\n * gets back a presigned upload URL; the caller `PUT`s the bytes there\n * directly (no bearer auth on that URL — the HMAC signature + expiry *is*\n * the auth); `GET /byok/blobs/:id/url` mints a presigned download URL the\n * same way. `BlobRef` itself (`@byok-sdk/protocol`'s `blob.ts`) is unchanged;\n * this module is what produces the URLs a `BlobRef` points at.\n *\n * `BlobStore` is interface-shaped so a SaaS can swap in a real object-store\n * (S3/GCS/R2 presigned URLs) later; {@link LocalDiskBlobStore} is the M1\n * reference implementation (single-process, in-memory metadata + files on\n * disk) — good enough for local dev and the SDK's own tests, not meant to\n * survive a restart or run multi-process.\n */\n\nexport interface CreateUploadInput {\n size: number;\n contentType: string;\n /** Content-addressed hash the server verifies the uploaded bytes against (§7). Reference impl assumes hex-encoded SHA-256. */\n contentHash: string;\n}\n\nexport type WriteContentResult = { ok: true } | { ok: false; reason: string };\n\nexport interface ReadContentResult {\n data: Buffer;\n contentType: string;\n}\n\nexport class BlobDeclarationConflictError extends Error {\n constructor(blobId: string) {\n super(`Blob ${blobId} already binds a different declaration.`);\n this.name = 'BlobDeclarationConflictError';\n }\n}\n\nexport interface BlobStore {\n /** Declare a blob before upload; an explicit id makes the declaration idempotent across host restart. */\n createUpload(input: CreateUploadInput, blobId?: string): Promise<{ blobId: string; uploadUrl: string }>;\n /** A presigned GET URL for a blob that has finished uploading, or `undefined` if unknown/not yet uploaded. */\n getDownloadUrl(blobId: string): Promise<string | undefined>;\n /** Whether `blobId` is known *and* has finished uploading. */\n exists(blobId: string): Promise<boolean>;\n /** Verify a presigned content URL's `sig`/`exp` query params for `action`. */\n verifySignedUrl(blobId: string, action: 'put' | 'get', sig: string, exp: number): boolean;\n /** Accept uploaded bytes; rejects (without storing) on size/hash mismatch against the `createUpload` declaration. */\n writeContent(blobId: string, data: Buffer): Promise<WriteContentResult>;\n /** Read back previously-uploaded bytes, or `undefined` if unknown/not yet uploaded. */\n readContent(blobId: string): Promise<ReadContentResult | undefined>;\n}\n\ninterface BlobRecord {\n meta: CreateUploadInput;\n uploaded: boolean;\n}\n\nexport interface LocalDiskBlobStoreOptions {\n /** Directory blob content is written under. Defaults to a fresh OS temp dir. */\n directory?: string;\n /** How long a presigned upload/download URL stays valid, ms. Default 15 minutes. */\n urlTtlMs?: number;\n}\n\nconst DEFAULT_URL_TTL_MS = 15 * 60 * 1000;\n\n/** docs/protocol.md §7 now pins the canonical `contentHash` format (`sha256:<64 lowercase hex>`, finding F9 — `CONTENT_HASH_RE` in `@byok-sdk/protocol`'s `blob.ts`), enforced at the schema level on every inbound `CreateBlobRequest`/`BlobRef`. Comparison here is therefore a straight string match — no normalization, no compat shim; anything else was already rejected before reaching this store. */\nfunction sha256Hex(data: Buffer): string {\n return `sha256:${createHash('sha256').update(data).digest('hex')}`;\n}\n\n/** Local-disk reference {@link BlobStore}: in-memory metadata, content on disk, HMAC-signed expiring URLs. */\nexport class LocalDiskBlobStore implements BlobStore {\n private readonly secret = randomBytes(32);\n private readonly directory: string;\n private readonly urlTtlMs: number;\n private readonly blobs = new Map<string, BlobRecord>();\n private readonly ready: Promise<void>;\n\n constructor(opts: LocalDiskBlobStoreOptions = {}) {\n this.directory = opts.directory ?? mkdtempSync(path.join(tmpdir(), 'byok-blobs-'));\n this.urlTtlMs = opts.urlTtlMs ?? DEFAULT_URL_TTL_MS;\n this.ready = mkdir(this.directory, { recursive: true }).then(() => undefined);\n }\n\n async createUpload(input: CreateUploadInput, requestedBlobId?: string): Promise<{ blobId: string; uploadUrl: string }> {\n await this.ready;\n const blobId = requestedBlobId ?? `blob_${randomUUID()}`;\n const existing = this.blobs.get(blobId);\n if (existing !== undefined) {\n if (\n existing.meta.size !== input.size ||\n existing.meta.contentType !== input.contentType ||\n existing.meta.contentHash !== input.contentHash\n ) {\n throw new BlobDeclarationConflictError(blobId);\n }\n return { blobId, uploadUrl: this.signUrl(blobId, 'put') };\n }\n this.blobs.set(blobId, { meta: input, uploaded: false });\n return { blobId, uploadUrl: this.signUrl(blobId, 'put') };\n }\n\n async getDownloadUrl(blobId: string): Promise<string | undefined> {\n const record = this.blobs.get(blobId);\n if (!record?.uploaded) return undefined;\n return this.signUrl(blobId, 'get');\n }\n\n async exists(blobId: string): Promise<boolean> {\n return this.blobs.get(blobId)?.uploaded ?? false;\n }\n\n verifySignedUrl(blobId: string, action: 'put' | 'get', sig: string, exp: number): boolean {\n if (!Number.isFinite(exp) || Date.now() > exp) return false;\n const expected = this.computeSig(blobId, action, exp);\n const expectedBuf = Buffer.from(expected, 'base64url');\n const actualBuf = Buffer.from(sig, 'base64url');\n if (expectedBuf.length !== actualBuf.length) return false;\n return timingSafeEqual(expectedBuf, actualBuf);\n }\n\n async writeContent(blobId: string, data: Buffer): Promise<WriteContentResult> {\n await this.ready;\n const record = this.blobs.get(blobId);\n if (!record) return { ok: false, reason: 'unknown blobId' };\n if (record.uploaded) return { ok: false, reason: 'blob already uploaded' };\n if (data.length !== record.meta.size) {\n return { ok: false, reason: `size mismatch: declared ${record.meta.size}, received ${data.length}` };\n }\n const actualHash = sha256Hex(data);\n if (actualHash !== record.meta.contentHash) {\n return { ok: false, reason: 'contentHash mismatch' };\n }\n await writeFile(this.pathFor(blobId), data);\n record.uploaded = true;\n return { ok: true };\n }\n\n async readContent(blobId: string): Promise<ReadContentResult | undefined> {\n const record = this.blobs.get(blobId);\n if (!record?.uploaded) return undefined;\n const data = await readFile(this.pathFor(blobId));\n return { data, contentType: record.meta.contentType };\n }\n\n private pathFor(blobId: string): string {\n return path.join(this.directory, blobId);\n }\n\n private computeSig(blobId: string, action: 'put' | 'get', exp: number): string {\n return createHmac('sha256', this.secret).update(`${blobId}:${action}:${exp}`).digest('base64url');\n }\n\n private signUrl(blobId: string, action: 'put' | 'get'): string {\n const exp = Date.now() + this.urlTtlMs;\n const sig = this.computeSig(blobId, action, exp);\n return `/byok/blobs/${blobId}/content?sig=${sig}&exp=${exp}`;\n }\n}\n","import { randomBytes, randomUUID } from 'node:crypto';\n\n/** Short, human-typeable pairing code (uppercase alnum, unambiguous alphabet). */\nconst PAIRING_CODE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';\n\nexport function generatePairingCode(length = 8): string {\n const bytes = randomBytes(length);\n let out = '';\n for (let i = 0; i < length; i++) {\n out += PAIRING_CODE_ALPHABET[bytes[i]! % PAIRING_CODE_ALPHABET.length];\n }\n return out;\n}\n\nexport function generateDeviceId(): string {\n return `dev_${randomUUID()}`;\n}\n\nexport function generateTaskId(): string {\n return `task_${randomUUID()}`;\n}\n","import type { TenantId } from './auth';\nimport { generatePairingCode } from './ids';\n\nconst PAIRING_CODE_TTL_MS = 10 * 60 * 1000; // ~10min, per spec\n\n/**\n * S1: the tenant identity a pairing code carries. Minted out-of-band by the\n * SaaS's own auth/device-flow UI — the only party that knows which tenant a\n * human is acting for — and returned by {@link PairingManager.redeemPairingCode}\n * so `POST /byok/pair` can write it onto the device row in the same\n * synchronous step that consumes the code.\n *\n * Deliberately NOT a wire field: `PairRequest` has no tenant of its own\n * (docs/protocol.md §6.1), so a device can never name the tenant it lands in.\n * These claims are the single source of truth for the row, and the row — not\n * a later token, and never client input — is what every authed surface\n * checks against.\n */\nexport interface PairingCodeClaims {\n tenantId: TenantId;\n productId: string;\n}\n\nexport interface PairingCodeInfo {\n code: string;\n expiresAt: string;\n}\n\ninterface PairingCodeRecord {\n code: string;\n claims: PairingCodeClaims;\n expiresAt: number;\n used: boolean;\n}\n\n/** Thrown when a pairing code is missing, expired, or already used. */\nexport class PairingCodeInvalidError extends Error {\n constructor(reason: string) {\n super(`invalid pairing code: ${reason}`);\n this.name = 'PairingCodeInvalidError';\n }\n}\n\n/**\n * In-memory pairing-code lifecycle: single-use, ~10min TTL codes minted\n * out-of-band (by the SaaS's own auth/device-flow UI) and redeemed exactly\n * once by `POST /byok/pair`.\n *\n * Device identity (deviceId/deviceName/devicePublicKey/revocation) and\n * token issuance moved to `auth.ts`'s `DeviceRegistry`/`TokenSigner` as of\n * Auth v2 (docs/protocol.md §6) — this class knows about devices only to the\n * extent of carrying the {@link PairingCodeClaims} that decide which tenant\n * and product the device being paired will belong to (S1).\n */\nexport class PairingManager {\n private readonly codes = new Map<string, PairingCodeRecord>();\n\n /**\n * Mint a single-use code bound to `claims`. Claims are REQUIRED — a\n * claimless mint is a compile error, and (for a JS caller, or a claims\n * object assembled from untyped config) a runtime {@link TypeError}. There\n * is no default tenant and no default product: a device with no tenant\n * must be inexpressible, so the failure happens here, at the mint, rather\n * than being filled in downstream.\n */\n createPairingCode(claims: PairingCodeClaims): PairingCodeInfo {\n const validated = validatePairingCodeClaims(claims);\n const code = generatePairingCode();\n const expiresAt = Date.now() + PAIRING_CODE_TTL_MS;\n this.codes.set(code, { code, claims: validated, expiresAt, used: false });\n return { code, expiresAt: new Date(expiresAt).toISOString() };\n }\n\n /**\n * Validate and consume a pairing code, returning the {@link PairingCodeClaims}\n * it was minted with. Throws {@link PairingCodeInvalidError} if the code is\n * unknown, expired, or already used — callers (the HTTP handler) map that to\n * a 401. Single-use is what makes the caller's \"redeem, then register the\n * device row with these claims\" sequence safe: a second redeem of the same\n * code can never reach the registration step at all.\n */\n redeemPairingCode(code: string): PairingCodeClaims {\n const record = this.codes.get(code);\n if (!record) {\n throw new PairingCodeInvalidError('unknown code');\n }\n if (record.used) {\n throw new PairingCodeInvalidError('code already used');\n }\n if (Date.now() > record.expiresAt) {\n throw new PairingCodeInvalidError('code expired');\n }\n record.used = true;\n return record.claims;\n }\n}\n\n/**\n * Reject anything that isn't a complete claims pair, and return a copy so a\n * later mutation of the caller's object cannot retroactively change which\n * tenant a already-minted code redeems into.\n */\nfunction validatePairingCodeClaims(claims: PairingCodeClaims): PairingCodeClaims {\n if (typeof claims !== 'object' || claims === null) {\n throw new TypeError('createPairingCode requires { tenantId, productId } claims');\n }\n const { tenantId, productId } = claims;\n if (typeof tenantId !== 'string' || tenantId.length === 0) {\n throw new TypeError('createPairingCode requires a non-empty tenantId');\n }\n if (typeof productId !== 'string' || productId.length === 0) {\n throw new TypeError('createPairingCode requires a non-empty productId');\n }\n return { tenantId, productId };\n}\n","import { createHash } from 'node:crypto';\nimport { Hono, type Context } from 'hono';\nimport {\n ChallengeRequestSchema,\n CreateBlobRequestSchema,\n MessagesSendRequestSchema,\n PairRequestSchema,\n TokenRequestSchema,\n type BlobDownloadUrlResponse,\n type ChallengeResponse,\n type CreateBlobResponse,\n type EventsPollResponse,\n type MessagesSendResponse,\n type PairResponse,\n type TokenResponse,\n} from '@byok-sdk/protocol';\nimport { authenticateBearer, mintAccessToken, verifyNonceSignature, type AuthDeps, type NonceStore } from './auth';\nimport { BlobDeclarationConflictError, type BlobStore } from './blob-store';\nimport type { ConnectionHub } from './hub';\nimport { generateDeviceId } from './ids';\nimport { PairingCodeInvalidError, type PairingCodeClaims, type PairingManager } from './pairing';\n\nexport interface HttpDeps extends AuthDeps {\n pairing: PairingManager;\n nonces: NonceStore;\n blobStore: BlobStore;\n hub: ConnectionHub;\n /** Per-product blob size ceiling in bytes (§7). */\n maxBlobSizeBytes: number;\n /** How long `GET /byok/events` holds an empty poll open before returning, ms (§8). */\n longPollHoldMs: number;\n /** M4 Phase 4 (part B.2): opt-in `GET /healthz` liveness route — see `CreateByokServerOptions.healthzRoute`'s doc comment (`types.ts`) for the full contract. Default `false` (no route mounted). */\n healthzRoute?: boolean;\n}\n\nasync function readJsonBody(c: Context): Promise<unknown> {\n try {\n return await c.req.json();\n } catch {\n return undefined;\n }\n}\n\n/**\n * The HTTP half of the pinned wire contract: pairing, token renewal, blob\n * flows, and the long-poll events fallback (docs/protocol.md §6-§8). WS\n * upgrade handling lives in `ws-server.ts` (raw Node `http.Server` upgrade,\n * not routable through Hono's fetch handler).\n */\nexport function buildHonoApp(deps: HttpDeps): Hono {\n const app = new Hono();\n const serverStartedAtMs = Date.now();\n\n // -------------------------------------------------------------------\n // Liveness (M4 Phase 4, part B.2) — opt-in, GET /healthz.\n //\n // Auth posture: deliberately UNAUTHENTICATED (no bearer check) — a\n // liveness probe (e.g. a container orchestrator / load balancer health\n // check) must not need a device credential, and the response is\n // correspondingly minimal: no sensitive data (no device ids, no counts),\n // liveness only. `ConnectionHub.stats()` (hub.ts) is the richer,\n // in-process-only surface for anything more detailed — deliberately never\n // routed here or anywhere else over HTTP by this SDK; an embedder that\n // wants that surfaced remotely builds its own authenticated route around\n // `ByokServer.stats()`.\n // -------------------------------------------------------------------\n\n if (deps.healthzRoute) {\n app.get('/healthz', (c) => c.json({ ok: true, uptimeMs: Date.now() - serverStartedAtMs }, 200));\n }\n\n // -------------------------------------------------------------------\n // Auth v2 (§6)\n // -------------------------------------------------------------------\n\n app.post('/byok/pair', async (c) => {\n const parsed = PairRequestSchema.safeParse(await readJsonBody(c));\n if (!parsed.success) {\n return c.json({ error: 'pairingCode, deviceName, and devicePublicKey are required strings' }, 400);\n }\n const { pairingCode, deviceName, devicePublicKey } = parsed.data;\n\n // S1: the redeemed code's claims are the ONLY source of the device's\n // tenant/product — `PairRequest` carries neither, so a device can never\n // choose where it lands. Redeem and registration happen in one\n // synchronous step, and the code's single-use semantics (`pairing.ts`)\n // are what make that step exclusive: a second redeem throws above and\n // never reaches the row write below.\n let claims: PairingCodeClaims;\n try {\n claims = deps.pairing.redeemPairingCode(pairingCode);\n } catch (err) {\n if (err instanceof PairingCodeInvalidError) {\n return c.json({ error: err.message }, 401);\n }\n throw err;\n }\n\n const deviceId = generateDeviceId();\n deps.devices.register({\n tenantId: claims.tenantId,\n productId: claims.productId,\n deviceId,\n deviceName,\n devicePublicKey,\n });\n const { accessToken, expiresAt } = await mintAccessToken(deps.tokenSigner, {\n deviceId,\n tenantId: claims.tenantId,\n productId: claims.productId,\n });\n\n const response: PairResponse = { deviceId, accessToken, refreshHint: expiresAt };\n return c.json(response, 200);\n });\n\n app.post('/byok/challenge', async (c) => {\n const parsed = ChallengeRequestSchema.safeParse(await readJsonBody(c));\n if (!parsed.success) return c.json({ error: 'deviceId is required' }, 400);\n const { deviceId } = parsed.data;\n\n // Pre-tenant by construction: `ChallengeRequest` is the pinned wire\n // contract and carries only a deviceId, so the row is what tells this\n // server which tenant the device belongs to (see\n // `DeviceRegistry.resolveByDeviceId`). Unknown and revoked answer\n // identically — no existence oracle.\n const device = deps.devices.resolveByDeviceId(deviceId);\n if (!device || device.revoked) {\n return c.json({ error: 'unknown or revoked device' }, 401);\n }\n\n const nonce = deps.nonces.issue(deviceId);\n const response: ChallengeResponse = { nonce };\n return c.json(response, 200);\n });\n\n app.post('/byok/token', async (c) => {\n const parsed = TokenRequestSchema.safeParse(await readJsonBody(c));\n if (!parsed.success) return c.json({ error: 'deviceId, nonce, and signature are required' }, 400);\n const { deviceId, nonce, signature } = parsed.data;\n\n // Same pre-tenant resolution as `/byok/challenge` above: key possession\n // is the credential here, and the row supplies the tenant/product the\n // renewed token gets bound to.\n const device = deps.devices.resolveByDeviceId(deviceId);\n if (!device || device.revoked) {\n return c.json({ error: 'unknown or revoked device' }, 401);\n }\n if (!deps.nonces.validate(deviceId, nonce)) {\n return c.json({ error: 'invalid, expired, or already-used nonce' }, 401);\n }\n // S1 (GAP-004): domain-separated — the device signs `byok-nonce-v1\\n` +\n // nonce (`auth.ts`'s `NONCE_SIGNING_DOMAIN`). A raw, unprefixed signature\n // over the bare nonce is invalid here; there is no second accepted form.\n if (!verifyNonceSignature(device.devicePublicKey, nonce, signature)) {\n return c.json({ error: 'invalid signature' }, 401);\n }\n // Only burn the nonce on a fully-verified success (§6.2) — an invalid\n // signature attempt doesn't consume the legitimate device's nonce.\n deps.nonces.markUsed(nonce);\n\n const { accessToken, expiresAt } = await mintAccessToken(deps.tokenSigner, {\n deviceId: device.deviceId,\n tenantId: device.tenantId,\n productId: device.productId,\n });\n const response: TokenResponse = { accessToken, expiresAt };\n return c.json(response, 200);\n });\n\n // -------------------------------------------------------------------\n // Blob flows (§7) — POST/GET are bearer-authed; the two `/content`\n // routes are pre-signed (HMAC `sig`+`exp`), not bearer-authed, since\n // they're meant to be hit directly (e.g. from a browser) without a JWT.\n //\n // M4 Phase 4 (part A): deliberately NOT covered by `ConnectionHub`'s\n // per-device envelope rate limiter — the two `/content` routes below have\n // no deviceId in scope at all (presigned by blobId, not bearer-authed), so\n // a shared \"per-device\" limiter can't apply to them uniformly, and sharing\n // one bucket with tiny, frequent envelope traffic on the two routes that\n // DO have a deviceId would let occasional large blob calls unintentionally\n // trip a device's WS connection closed for unrelated reasons.\n // -------------------------------------------------------------------\n\n app.post('/byok/blobs', async (c) => {\n const principal = await authenticateBearer(c.req.header('authorization'), deps);\n if (!principal) return c.json({ error: 'unauthorized' }, 401);\n\n const parsed = CreateBlobRequestSchema.safeParse(await readJsonBody(c));\n if (!parsed.success) return c.json({ error: 'size, contentType, and contentHash are required' }, 400);\n if (parsed.data.size > deps.maxBlobSizeBytes) {\n return c.json({ error: `blob exceeds max size of ${deps.maxBlobSizeBytes} bytes` }, 413);\n }\n\n const reservationId = c.req.header('idempotency-key');\n if (!reservationId || reservationId.length > 200) {\n return c.json({ error: 'Idempotency-Key header is required' }, 400);\n }\n\n const blobId = reservationBlobId(principal.tenantId, reservationId);\n try {\n const created = await deps.blobStore.createUpload(parsed.data, blobId);\n const response: CreateBlobResponse = created;\n return c.json(response, 200);\n } catch (error) {\n if (error instanceof BlobDeclarationConflictError) {\n return c.json({ error: 'storage_integrity_mismatch' }, 422);\n }\n throw error;\n }\n });\n\n app.post('/byok/blobs/:id/finalize', async (c) => {\n const principal = await authenticateBearer(c.req.header('authorization'), deps);\n if (!principal) return c.json({ error: 'unauthorized' }, 401);\n\n const reservationId = c.req.header('idempotency-key');\n if (!reservationId || reservationId.length > 200) {\n return c.json({ error: 'Idempotency-Key header is required' }, 400);\n }\n const blobId = c.req.param('id');\n if (reservationBlobId(principal.tenantId, reservationId) !== blobId) {\n return c.json({ error: 'storage_integrity_mismatch' }, 422);\n }\n if (!(await deps.blobStore.exists(blobId))) {\n return c.json({ error: 'storage_reservation_not_found' }, 404);\n }\n return c.body(null, 204);\n });\n\n app.get('/byok/blobs/:id/url', async (c) => {\n const principal = await authenticateBearer(c.req.header('authorization'), deps);\n if (!principal) return c.json({ error: 'unauthorized' }, 401);\n\n const downloadUrl = await deps.blobStore.getDownloadUrl(c.req.param('id'));\n if (!downloadUrl) return c.json({ error: 'blob not found' }, 404);\n\n const response: BlobDownloadUrlResponse = { downloadUrl };\n return c.json(response, 200);\n });\n\n app.put('/byok/blobs/:id/content', async (c) => {\n const blobId = c.req.param('id');\n const { sig, exp } = signedUrlParams(c.req.query('sig'), c.req.query('exp'));\n if (!sig || exp === undefined || !deps.blobStore.verifySignedUrl(blobId, 'put', sig, exp)) {\n return c.json({ error: 'invalid or expired signature' }, 401);\n }\n\n const data = Buffer.from(await c.req.arrayBuffer());\n const result = await deps.blobStore.writeContent(blobId, data);\n if (!result.ok) return c.json({ error: result.reason }, 422);\n return c.body(null, 204);\n });\n\n app.get('/byok/blobs/:id/content', async (c) => {\n const blobId = c.req.param('id');\n const { sig, exp } = signedUrlParams(c.req.query('sig'), c.req.query('exp'));\n if (!sig || exp === undefined || !deps.blobStore.verifySignedUrl(blobId, 'get', sig, exp)) {\n return c.json({ error: 'invalid or expired signature' }, 401);\n }\n\n const content = await deps.blobStore.readContent(blobId);\n if (!content) return c.json({ error: 'blob not found' }, 404);\n return c.body(new Uint8Array(content.data), 200, { 'content-type': content.contentType });\n });\n\n // -------------------------------------------------------------------\n // Long-poll fallback (§8)\n // -------------------------------------------------------------------\n\n app.get('/byok/events', async (c) => {\n const principal = await authenticateBearer(c.req.header('authorization'), deps);\n if (!principal) return c.json({ error: 'unauthorized' }, 401);\n\n const cursorRaw = c.req.query('cursor');\n let cursor = 0;\n if (cursorRaw !== undefined) {\n const parsedCursor = Number(cursorRaw);\n if (!Number.isInteger(parsedCursor) || parsedCursor < 0) return c.json({ error: 'invalid cursor' }, 400);\n cursor = parsedCursor;\n }\n\n const result = await deps.hub.pollEvents(principal.deviceId, cursor, deps.longPollHoldMs);\n const response: EventsPollResponse = result;\n return c.json(response, 200);\n });\n\n // -------------------------------------------------------------------\n // Finding F6: daemon->server send while long-polling (§8). A device\n // long-polling for S->D traffic has no live WS to carry its own outbound\n // envelopes — this batches them over authed HTTP instead. Each envelope is\n // routed through the exact same inbound gate a WS connection's messages\n // get (`hub.handleInbound`), so claim/progress/complete/etc. behave\n // identically regardless of which transport carried them. The batch itself\n // stays tolerant at the schema level (one malformed/wrong-direction\n // envelope does not 400 the whole request, finding P2) — `handleInbound`\n // rejects per-envelope; only a structurally invalid `Envelope` (fails\n // `EnvelopeSchema`) or an oversized batch (`MessagesSendRequestSchema`'s\n // cap) 400s here.\n //\n // M4 Phase 4 (part A): a long-polling device has no live WS connection for\n // `hub.handleInbound`'s rate-limit gate to close on exceed (see\n // `hub.ts`'s `handleRateLimited`) — this route is that transport's own\n // enforcement instead. The moment any envelope in the batch comes back\n // `'rate_limited'`, this stops processing the REST of the batch (its\n // bucket is empty, so every remaining envelope would rate-limit too — no\n // reason to keep spending gate checks or piling up more\n // `device.rate_limited` events for one request) and answers the WHOLE\n // request 429, same `{error}` shape every other error response on this\n // app already uses. Envelopes processed earlier in the same batch (before\n // the limit was hit) already took effect regardless of this response —\n // no rollback semantics exist here, same as any other partial-batch\n // outcome — but that's safe: every `task.*` type is idempotent (§9), so a\n // client that retries the same batch after backing off (matching this\n // transport's existing 2s-default retry-on-non-ok convention,\n // `long-poll-transport.ts`) simply gets `'duplicate'` for whatever already\n // landed.\n // -------------------------------------------------------------------\n\n app.post('/byok/messages', async (c) => {\n const principal = await authenticateBearer(c.req.header('authorization'), deps);\n if (!principal) return c.json({ error: 'unauthorized' }, 401);\n\n const parsed = MessagesSendRequestSchema.safeParse(await readJsonBody(c));\n if (!parsed.success) return c.json({ error: 'messages must be an array of envelopes' }, 400);\n\n let accepted = 0;\n let rejected = 0;\n for (const envelope of parsed.data.messages) {\n const result = deps.hub.handleInbound(principal.deviceId, envelope);\n if (result === 'rate_limited') {\n return c.json({ error: 'rate limit exceeded' }, 429);\n }\n // A duplicate is still a wire-level success (§8.2/§9's idempotency\n // window) — it just didn't re-run a handler. Only a gate rejection\n // (wrong-direction type, or an ownership mismatch, N2) is excluded.\n if (result === 'rejected') rejected++;\n else accepted++;\n }\n\n // `rejected` is additive and omitted entirely when zero, so a batch with\n // nothing rejected keeps the pre-P2 `{ accepted }` response shape.\n const response: MessagesSendResponse = rejected > 0 ? { accepted, rejected } : { accepted };\n return c.json(response, 200);\n });\n\n // M4 Phase 3 note: task approval (resolving a task currently\n // `AwaitApproval`) is deliberately NOT exposed as an HTTP route on this\n // app. Every route above is the pinned device<->server wire contract\n // (docs/protocol.md), bearer-authed with a DEVICE's own access token —\n // approval is an operator/embedder-side action, and a device-bearer-authed\n // route would let any validly-paired device approve/reject ANY task, not\n // just its own. The supported entry point is `ConnectionHub.approveTask`/\n // `rejectTask` (hub.ts) called in-process — an embedder builds its own\n // operator-facing surface (its own auth, its own routes) on top of that,\n // exactly like `examples/basic/server.ts`'s own `/api/tasks/:taskId/approve`\n // and `/api/tasks/:taskId/reject`, which call `TaskHandle.approve()`/\n // `reject()` (themselves thin wrappers over `approveTask`/`rejectTask`)\n // directly rather than proxying through any bearer-authed SDK route.\n\n return app;\n}\n\nfunction reservationBlobId(tenantId: string, reservationId: string): string {\n return `blob_${createHash('sha256').update(`${tenantId}\\u0000${reservationId}`).digest('hex')}`;\n}\n\nfunction signedUrlParams(sig: string | undefined, expRaw: string | undefined): { sig?: string; exp?: number } {\n if (!sig || expRaw === undefined) return {};\n const exp = Number(expRaw);\n if (!Number.isFinite(exp)) return {};\n return { sig, exp };\n}\n","/**\n * A tiny append-only, multi-reader async queue. `push` never blocks; `close`\n * marks the queue done. `subscribe()` returns a fresh async iterator that\n * always replays from the beginning of the buffer, so a consumer that calls\n * `events()` at any point still \"sees everything\" for that task's lifetime.\n *\n * Framework-agnostic on purpose (no Node/WS/Hono types here) so it can be\n * unit-tested and reused regardless of transport.\n */\nexport class AsyncEventQueue<T> {\n private readonly buffer: T[] = [];\n private closed = false;\n private waiters: Array<() => void> = [];\n\n push(value: T): void {\n if (this.closed) return;\n this.buffer.push(value);\n this.wake();\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n this.wake();\n }\n\n private wake(): void {\n const waiters = this.waiters;\n this.waiters = [];\n for (const resolve of waiters) resolve();\n }\n\n private waitForMore(): Promise<void> {\n return new Promise((resolve) => this.waiters.push(resolve));\n }\n\n /** Async-iterate the buffer from index 0, waiting for new pushes until closed. */\n subscribe(): AsyncIterable<T> {\n const queue = this;\n return {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n let index = 0;\n return {\n async next(): Promise<IteratorResult<T>> {\n for (;;) {\n if (index < queue.buffer.length) {\n return { value: queue.buffer[index++]!, done: false };\n }\n if (queue.closed) {\n return { value: undefined, done: true };\n }\n await queue.waitForMore();\n }\n },\n };\n },\n };\n }\n}\n","/**\n * M4 Phase 4 (part A): per-key token bucket, used by `ConnectionHub`\n * (`hub.ts`) to rate-limit inbound daemon->server envelopes per device.\n * Framework-agnostic on purpose (no hub/transport types here) so it stays\n * unit-testable in isolation, mirroring `event-queue.ts`'s own\n * transport-agnostic split.\n *\n * Token bucket, not a fixed window: `burst` tokens are available immediately\n * (accommodating a legitimate short spike — e.g. a reconnect's redelivery\n * catch-up), refilling continuously at `messagesPerSecond` tokens/sec up to\n * that same `burst` ceiling. A bucket is created lazily per key on first use\n * and persists for as long as it stays active — deliberately NOT reset when\n * a device disconnects/reconnects (see `ConnectionHub`'s own use of this\n * class): resetting on reconnect would let a device that just got\n * disconnected for exceeding its budget immediately burst again on\n * reconnect, defeating the limit entirely. It IS dropped once idle long\n * enough that keeping it around would be pointless — see\n * `evictIdleBucketsIfDue`.\n *\n * Construction validates `messagesPerSecond`/`burst` fail-fast (throws\n * `TypeError` on anything non-finite or <= 0) rather than silently building\n * a limiter that either divides into `NaN` token math or (an `Infinity`\n * burst/rate) never actually limits anything.\n *\n * Finding R5 (cross-model re-review — F10 residual): `burst` specifically\n * must be `>= 1`, not merely `> 0`. A `0 < burst < 1` value used to pass\n * the old `<= 0` check cleanly, but `consume()`'s own debit logic\n * (`if (bucket.tokens < 1) return false;`) can NEVER succeed once the\n * bucket's own CEILING (`burst`) is itself below 1 — `Math.min(this.burst,\n * ...)` caps refill there, so `tokens` can never reach 1 no matter how\n * long the bucket sits idle. The old validation let this construct\n * silently — a limiter that rejects every single message, forever, for\n * every key, is not a rate LIMIT, it's a permanent, total block; that\n * should be a construction-time error, not a runtime surprise discovered\n * once real traffic starts getting rejected.\n */\n\nexport interface RateLimiterOptions {\n /** Sustained refill rate, tokens (i.e. messages) per second. Must be a finite number > 0. Default 50. */\n messagesPerSecond?: number;\n /** Bucket capacity — how many messages may arrive back-to-back before the limit engages. Must be a finite number >= 1 (finding R5 — see the module doc comment for why `< 1` can never let a single message through, ever). Default 100. */\n burst?: number;\n /** Hard cap on how many distinct keys (finding R5) this limiter tracks at once — see {@link DEFAULT_MAX_TRACKED_DEVICES}'s own doc comment. Must be a finite number >= 1. Default 10,000. */\n maxTrackedDevices?: number;\n}\n\nconst DEFAULT_MESSAGES_PER_SECOND = 50;\nconst DEFAULT_BURST = 100;\n/**\n * Finding R5 (cross-model re-review — F10 residual): hard cap on\n * `RateLimiter.buckets`' size — see {@link RateLimiter.evictOldestIfAtCapacity}\n * for the eviction policy this backs. Without a HARD cap, the existing idle\n * sweep (`evictIdleBucketsIfDue`) only ever reclaims a bucket once it's\n * been quiet for `idleEvictionThresholdMs` — a flood of many THOUSANDS of\n * genuinely-distinct, ACTIVELY-used keys arriving faster than they ever go\n * idle (a legitimately huge fleet, or a same-shape Sybil-style attack\n * registering many fake device ids) would otherwise grow `buckets` without\n * bound regardless of the idle sweep, exactly the unbounded-growth shape\n * this codebase caps everywhere else (`MAX_PENDING_APPROVALS`,\n * `MAX_TRACKED_TASKS`, `MAX_LIVE_TASK_ANCHORS`, ...). 10,000 is generously\n * sized for any plausible real deployment while still being a genuine,\n * enforced ceiling — not a real-world limit this is expected to ever\n * approach in ordinary operation.\n */\nconst DEFAULT_MAX_TRACKED_DEVICES = 10_000;\n\n/**\n * Sweep cadence for idle-bucket eviction (`evictIdleBucketsIfDue`), in\n * number of `consume()` calls across ALL keys — not a wall-clock timer, so\n * this class stays fully synchronous and framework-agnostic (per this\n * file's own top doc comment): no timer handle to create, `unref`, or clean\n * up on shutdown, and no lifecycle wiring needed in `ConnectionHub`.\n */\nconst EVICTION_SWEEP_EVERY_N_CALLS = 1000;\n\ninterface Bucket {\n tokens: number;\n lastRefillMs: number;\n}\n\nexport class RateLimiter {\n private readonly messagesPerSecond: number;\n private readonly burst: number;\n private readonly buckets = new Map<string, Bucket>();\n /**\n * Wall-clock idle duration (ms) after which a bucket is GUARANTEED to\n * already be refilled to `burst`, regardless of its actual token count at\n * last touch — i.e. the time to go from 0 tokens to `burst` at this\n * instance's configured rate. `evictIdleBucketsIfDue` uses this as the\n * eviction threshold: dropping an entry idle at least this long and\n * recreating it fresh (tokens = burst) on the next `consume()` is\n * therefore behaviorally IDENTICAL to refilling it in place would have\n * been — both cap at `burst` — so eviction is semantically invisible to\n * the caller.\n */\n private readonly idleEvictionThresholdMs: number;\n /** Finding R5: hard cap on `buckets.size` — see {@link DEFAULT_MAX_TRACKED_DEVICES}'s own doc comment. */\n private readonly maxTrackedDevices: number;\n /** Calls to `consume()` since the last sweep — see `EVICTION_SWEEP_EVERY_N_CALLS`. */\n private callsSinceSweep = 0;\n\n constructor(opts: RateLimiterOptions = {}) {\n const messagesPerSecond = opts.messagesPerSecond ?? DEFAULT_MESSAGES_PER_SECOND;\n const burst = opts.burst ?? DEFAULT_BURST;\n const maxTrackedDevices = opts.maxTrackedDevices ?? DEFAULT_MAX_TRACKED_DEVICES;\n if (!Number.isFinite(messagesPerSecond) || messagesPerSecond <= 0) {\n throw new TypeError(`RateLimiter: messagesPerSecond must be a finite number > 0, got ${messagesPerSecond}`);\n }\n // Finding R5: `< 1`, not `<= 0` — see the module doc comment for why a\n // burst between 0 and 1 (exclusive) constructs a limiter that can never\n // let a single message through, ever, for any key.\n if (!Number.isFinite(burst) || burst < 1) {\n throw new TypeError(`RateLimiter: burst must be a finite number >= 1, got ${burst}`);\n }\n if (!Number.isFinite(maxTrackedDevices) || maxTrackedDevices < 1) {\n throw new TypeError(`RateLimiter: maxTrackedDevices must be a finite number >= 1, got ${maxTrackedDevices}`);\n }\n this.messagesPerSecond = messagesPerSecond;\n this.burst = burst;\n this.maxTrackedDevices = maxTrackedDevices;\n this.idleEvictionThresholdMs = (burst / messagesPerSecond) * 1000;\n }\n\n /**\n * Debit one token from `key`'s bucket, refilling first for however much\n * wall-clock time has elapsed since its last refill. Returns `false`\n * (and debits nothing) when the bucket is currently empty — the caller is\n * over budget right now.\n */\n consume(key: string): boolean {\n const now = Date.now();\n this.evictIdleBucketsIfDue(now);\n\n let bucket = this.buckets.get(key);\n if (!bucket) {\n // Finding R5: enforce the hard cap right before growing the map with\n // a genuinely NEW key — see `evictOldestIfAtCapacity`'s own doc\n // comment for the eviction policy and its equivalence/best-effort\n // split.\n this.evictOldestIfAtCapacity();\n bucket = { tokens: this.burst, lastRefillMs: now };\n this.buckets.set(key, bucket);\n } else {\n const elapsedMs = now - bucket.lastRefillMs;\n if (elapsedMs > 0) {\n bucket.tokens = Math.min(this.burst, bucket.tokens + (elapsedMs / 1000) * this.messagesPerSecond);\n bucket.lastRefillMs = now;\n }\n }\n\n if (bucket.tokens < 1) return false;\n bucket.tokens -= 1;\n return true;\n }\n\n /**\n * Every `EVICTION_SWEEP_EVERY_N_CALLS` calls to `consume()`, drops every\n * bucket idle for at least `idleEvictionThresholdMs` (see that field's doc\n * comment for why this is safe). Without this, `buckets` would hold one\n * permanent entry per historical key forever — every device that ever\n * connected, even long after it disconnected for good — growing without\n * bound over a long-lived server's lifetime.\n */\n private evictIdleBucketsIfDue(now: number): void {\n this.callsSinceSweep++;\n if (this.callsSinceSweep < EVICTION_SWEEP_EVERY_N_CALLS) return;\n this.callsSinceSweep = 0;\n for (const [key, bucket] of this.buckets) {\n if (now - bucket.lastRefillMs >= this.idleEvictionThresholdMs) {\n this.buckets.delete(key);\n }\n }\n }\n\n /**\n * Finding R5 (cross-model re-review — F10 residual): called right before\n * inserting a bucket for a genuinely NEW key, evicting the single\n * LEAST-RECENTLY-refilled entry if `buckets` is already at\n * `maxTrackedDevices` — an O(n) scan, but one that only ever runs once\n * the map is already at its hard ceiling (a rare/bounded event under\n * ordinary operation, not a per-call cost), mirroring this codebase's own\n * established \"acceptable O(n) for a rare/bounded case\" precedent (e.g.\n * `audit-log.ts`'s `compactPreservingLiveTasks` during rotation).\n *\n * Equivalence split (stated explicitly, not left implied):\n * - For any evicted bucket that was ALREADY idle for at least\n * `idleEvictionThresholdMs` (i.e. `evictIdleBucketsIfDue` would have\n * reclaimed it anyway, just not yet — sweeps only run every\n * `EVICTION_SWEEP_EVERY_N_CALLS` calls, not continuously), eviction is\n * PROVABLY equivalent to an in-place refill: both cap at `burst`, so a\n * caller can never observe the difference (see `idleEvictionThresholdMs`'s\n * own doc comment for the identical reasoning `evictIdleBucketsIfDue`\n * already relies on).\n * - For a bucket evicted EARLY — still within its idle threshold, forced\n * out only because `buckets` is at capacity (many thousands of\n * genuinely-distinct, actively-used keys, not a quiet one) — this is\n * BEST-EFFORT, not equivalence-preserving: whatever partial token debt\n * that key had is discarded, and its very next `consume()` call starts\n * completely fresh (`tokens: this.burst`), a strictly MORE permissive\n * outcome than if it had kept its place. This is an accepted,\n * deliberately bounded trade-off — it only ever engages under\n * cardinality far beyond any plausible real deployment — favoring\n * bounded memory over perfect per-key continuity in that one extreme\n * case.\n */\n private evictOldestIfAtCapacity(): void {\n if (this.buckets.size < this.maxTrackedDevices) return;\n let oldestKey: string | undefined;\n let oldestLastRefillMs = Infinity;\n for (const [key, bucket] of this.buckets) {\n if (bucket.lastRefillMs < oldestLastRefillMs) {\n oldestLastRefillMs = bucket.lastRefillMs;\n oldestKey = key;\n }\n }\n if (oldestKey !== undefined) this.buckets.delete(oldestKey);\n }\n}\n","import type { WebSocket } from 'ws';\nimport {\n canTransition,\n createEnvelope,\n DAEMON_TO_SERVER_TYPES,\n encodeEnvelope,\n PROTOCOL_VERSION,\n TASK_STATES,\n type CreateEnvelopeOptions,\n type Envelope,\n type MessageType,\n type PermissionPolicy,\n type RuntimeId,\n type RuntimeInfo,\n type TaskApprovalResolvedPayload,\n type TaskArtifactPayload,\n type TaskAwaitApprovalPayload,\n type TaskCancelledPayload,\n type TaskClaimPayload,\n type TaskCompletePayload,\n type TaskDeclinePayload,\n type TaskFailPayload,\n type TaskProgressPayload,\n type TaskStartedPayload,\n type TaskState,\n} from '@byok-sdk/protocol';\nimport type { DeviceRegistry } from './auth';\nimport { AsyncEventQueue } from './event-queue';\nimport { generateTaskId } from './ids';\nimport { RateLimiter } from './rate-limiter';\nimport type { TaskStore } from './task-store';\nimport type {\n ByokServerEvent,\n DispatchInput,\n HubStats,\n MachineInfo,\n ServerTaskEvent,\n TaskHandle,\n TaskResult,\n TaskSnapshot,\n} from './types';\n\n/**\n * M0 default policy when `dispatch()` is called without one. `TaskOfferPayload.policy`\n * is required on the wire even though our `dispatch()` input makes it optional, so we\n * need a safe default. Per the plan's fail-closed principle (\"越权需要本地确认\"),\n * default to `confirm` rather than the more permissive `auto`.\n */\nconst DEFAULT_POLICY: PermissionPolicy = { mode: 'confirm' };\n\n/** Per-device redelivery ring buffer size (§9: \"keep the last K envelopes per device\"). */\nconst OUTBOX_RING_CAPACITY = 500;\n\n/** Per-device idempotency window (N3): how many recent inbound envelope ids {@link ConnectionHub.handleInbound} remembers for dedup, oldest evicted first once full. */\nconst DEDUP_RING_CAPACITY = 1024;\n\n/**\n * Upper bound on the task-lease reaper's sweep-tick period (see\n * `ConnectionHub`'s constructor and its `sweepLeases` method, in the\n * \"task-lease reaper\" section below). A short (e.g. test-injected)\n * `taskLeaseMs` sweeps at its own granularity so it's still checked\n * promptly; a realistic multi-minute lease sweeps at this fixed, cheap\n * resolution instead of scaling the interval down with it.\n */\nconst MAX_LEASE_REAPER_SWEEP_INTERVAL_MS = 30_000;\n\nfunction isTerminal(state: TaskState): state is Extract<TaskState, 'Complete' | 'Failed' | 'Cancelled'> {\n return state === 'Complete' || state === 'Failed' || state === 'Cancelled';\n}\n\n/** Claimed/Running/AwaitApproval — the task-lease reaper's reapable set. `Offered` is excluded: it has no owning device yet, so there's nothing to be \"dark\". */\nfunction isClaimedState(state: TaskState): state is Extract<TaskState, 'Claimed' | 'Running' | 'AwaitApproval'> {\n return state === 'Claimed' || state === 'Running' || state === 'AwaitApproval';\n}\n\n/** A device's live transport (WS or long-poll — never both, §8) plus last-known metadata. */\ninterface ConnectionState {\n ws?: WebSocket;\n connected: boolean;\n lastSeen: string;\n runtimes?: RuntimeInfo[];\n /**\n * M5 (approval targeting, hello-capability plumbing): the capability flags\n * this connection's `conn.hello` advertised — `undefined` for a device\n * this hub has never seen a hello from at all (mirrors `runtimes`' own\n * optionality). Read by {@link ConnectionHub.getDeviceCapabilities}.\n * Carried forward across a WS -> long-poll transport switch\n * ({@link takeOverAsLongPoll}) exactly like `runtimes` already is — a\n * device doesn't re-send `conn.hello` just because its transport changed,\n * so whatever it advertised on its live WS handshake must survive the\n * switch, not silently reset to `undefined`.\n */\n capabilities?: readonly string[];\n /**\n * Epoch-ms instant this device most recently transitioned from alive to\n * dark (set by {@link ConnectionHub.handleDisconnect}), or `undefined`\n * while alive. This is the task-lease reaper's condition (b) clock start —\n * kept independent of any task's own last-activity timestamp so that a\n * task idle-past-TTL while still connected still gets a fresh, full\n * `taskLeaseMs` countdown from the moment its device actually went dark,\n * not from however stale that activity already was (see `sweepLeases`'s\n * doc comment for the bug this fixes).\n */\n darkSince?: number;\n}\n\n/** One envelope this server has sent (or would have sent) to a device, retained for redelivery. */\ninterface OutboxEntry {\n seq: number;\n /** Absent for `conn.ack` (not task-scoped) — such entries are never redelivered/polled. */\n taskId?: string;\n envelope: Envelope;\n /**\n * N1/F4: `task.cancel`/`task.reject` move their task to a terminal state\n * *before* being queued here (`cancelTask`/`rejectTask`'s own\n * mark-terminal-then-send order) — without this flag, `collectRelevant`'s\n * terminal-task filter would always exclude them, so a daemon that missed\n * the live send (e.g. dropped mid-cancel) could never have it\n * redelivered. Only those two types set this (see `sendToDevice`);\n * `task.approve`/`task.steer` never fire on an already-terminal task in\n * the first place (`approveTask`/`steerTask` both require a specific\n * non-terminal state), so the exemption would be moot for them.\n */\n redeliverThroughTerminal?: boolean;\n}\n\n/** Per-device outbound sequence counter + bounded history (§1.2, §9). */\ninterface DeviceOutbox {\n nextSeq: number;\n ring: OutboxEntry[];\n}\n\ninterface LongPollWaiter {\n cursor: number;\n resolve: (result: { events: Envelope[]; cursor: number }) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface TaskRuntime {\n queue: AsyncEventQueue<ServerTaskEvent>;\n resolveResult: (result: TaskResult) => void;\n result: Promise<TaskResult>;\n}\n\n/** Fields hub.ts ever patches alongside a state transition. */\ntype TaskPatch = Partial<\n Pick<\n TaskSnapshot,\n 'deviceId' | 'sessionRef' | 'result' | 'pendingApprovalId' | 'claimedRuntime' | 'claimedRuntimeCapabilities'\n >\n>;\n\n/**\n * The connection hub: tracks each device's live transport (WS or long-poll —\n * never both at once, see {@link takeOverAsLongPoll}), routes `dispatch()`'d\n * tasks to a device, and processes inbound task.* envelopes from daemons.\n *\n * Routing (M1): every `task.*` envelope carries a *required* envelope\n * `task_id` — the sole routing key, both directions. No payload duplicates\n * it, and none of the handlers below need to guard against a missing one\n * (the wire schema already rejects such an envelope before it reaches here).\n *\n * Inbound gate ({@link handleInbound}): the single choke point both\n * transports (`ws-server.ts`'s WS message handler, `http.ts`'s\n * `POST /byok/messages`) call instead of reaching into per-type handlers\n * directly. Runs, in order: (1) type-allow — only `DAEMON_TO_SERVER_TYPES`\n * may pass, a server -> daemon type arriving inbound is rejected (P2); (2)\n * ownership (N2) — an envelope for a task already owned by a *different*\n * device is dropped and logged, never force-failed (force-failing on an\n * authz mismatch would let an attacker who merely guesses a `taskId` kill\n * the real owner's task); (3) dedup (N3) — an envelope `id` already seen\n * from this device is a no-op, making the at-least-once wire (§9)\n * effectively at-most-once server-side; (4) dispatch to the per-type\n * handler. Because ownership is enforced once, centrally, here, the\n * handlers below no longer carry their own device-mismatch checks.\n *\n * Outbound delivery (M1, §1.2/§9): every server -> daemon envelope\n * (`conn.ack`, `task.offer/approve/reject/cancel/steer`) gets a fresh\n * per-device monotonic `seq` and is retained in a capped ring buffer\n * ({@link OUTBOX_RING_CAPACITY} entries) so it can be redelivered — in `seq`\n * order, skipping anything whose task has since reached a terminal state —\n * on reconnect (`redeliverAfterReconnect`) or long-poll\n * (`pollEvents`/`collectRelevant`). `conn.ack` has no task association, so\n * it's retained (for seq-counting purposes only) but never redelivered.\n * Exception (N1/F4): `task.cancel`/`task.reject` are exempt from the\n * terminal-task skip (`OutboxEntry.redeliverThroughTerminal`) because both\n * move their own task to a terminal state before being queued — without the\n * exemption they could never qualify for redelivery even when the original\n * send never reached the daemon.\n *\n * State-machine (M1): `task.claim` only claims (`Offered -> Claimed`); it no\n * longer implies `Running`. The daemon reports `Claimed -> Running`\n * explicitly via `task.started` once its runtime session actually starts\n * (§3.1). `task.decline` reports a pre-claim fail-closed rejection\n * (`Offered -> Failed`, §3.2). `task.cancelled` is dual-purpose: an\n * idempotent ack when the server already cancelled the task itself, or the\n * authoritative trigger when the daemon observed the cancellation first\n * (§3.3). Per §9, `task.complete`/`task.fail`/`task.cancelled` arriving for\n * an already-terminal task are silently dropped as stale/duplicate — not a\n * warning; this is what naturally resolves the M0 gatekeeper's cancel-race\n * `console.warn` finding (a late `task.fail`/`task.cancelled` racing a\n * server-initiated cancel is exactly this case).\n *\n * Task lease (M2): a periodic sweep (started in the constructor) reaps a\n * `Claimed`/`Running`/`AwaitApproval` task to\n * `Failed(retryable: true, reason: 'lease-expired')` once its owning device\n * has been dark (disconnected, or long-poll-silent) AND the task itself has\n * had no inbound activity for `taskLeaseMs` — see the \"task-lease reaper\"\n * section further down for the full design, including why this does not\n * reintroduce the disconnect-alone-fails-the-task bug M1 removed above.\n */\n/**\n * M4 Phase 3: thrown by {@link ConnectionHub.approveTask}/{@link\n * ConnectionHub.rejectTask} for a `taskId` this hub has no record of at all\n * — mirrors `task-store.ts`'s `IllegalTaskTransitionError` (typed error\n * class + `instanceof` dispatch is this codebase's own established idiom for\n * mapping a domain error to the right status code). Distinguished from\n * {@link TaskNotAwaitingApprovalError} so a caller CAN tell the two failure\n * modes apart (e.g. 404 vs. 409) instead of only ever seeing a single\n * generic `Error`. There is no bearer-authed HTTP route for this on\n * `http.ts`'s own app (see that file's own closing comment for why) — the\n * supported entry point is calling `approveTask`/`rejectTask` directly, or\n * via `TaskHandle.approve()`/`reject()` (thin wrappers over the same two\n * methods); an embedder builds its own operator-facing surface on top of\n * that, exactly like `examples/basic/server.ts`'s own\n * `/api/tasks/:taskId/approve`/`reject` routes do.\n */\nexport class UnknownTaskError extends Error {\n constructor(public readonly taskId: string) {\n super(`unknown taskId: ${taskId}`);\n this.name = 'UnknownTaskError';\n }\n}\n\n/**\n * Thrown by {@link ConnectionHub.approveTask}/{@link ConnectionHub.rejectTask}\n * when the task exists but isn't currently `AwaitApproval` — see {@link\n * UnknownTaskError}'s own doc comment for why this is a distinct class.\n * `verb` keeps the exact pre-existing message wording per call site\n * (\"cannot approve ...\" vs. \"cannot reject ...\") byte-for-byte unchanged —\n * this message is user-visible today (e.g. `examples/basic`'s own\n * `/api/tasks/:taskId/approve` surfaces `err.message` straight to the\n * caller), so only the error's TYPE changes here, not its text.\n */\nexport class TaskNotAwaitingApprovalError extends Error {\n constructor(\n public readonly taskId: string,\n public readonly state: TaskState,\n verb: 'approve' | 'reject',\n ) {\n super(`cannot ${verb} task ${taskId}: not awaiting approval (state ${state})`);\n this.name = 'TaskNotAwaitingApprovalError';\n }\n}\n\n/**\n * M5 (approval targeting): thrown by {@link ConnectionHub.approveTask}/\n * {@link ConnectionHub.rejectTask} when an operator-supplied\n * `opts.approvalId` is provided but does NOT match this hub's own\n * last-recorded {@link TaskSnapshot.pendingApprovalId} for `taskId` — i.e.\n * the caller is targeting a SPECIFIC approval that this server's record\n * shows has already been superseded by a newer one (see `onAwaitApproval`'s\n * own doc comment for how that happens). Distinct from\n * {@link TaskNotAwaitingApprovalError} (the task isn't `AwaitApproval` at\n * all right now — checked FIRST, so it still wins when both would apply):\n * this error means the task genuinely IS awaiting approval, just not the one\n * the caller thinks it is. Thrown before any state change and before any\n * wire message is sent — a stale-id call has zero side effects, same as any\n * other validation failure in this file.\n */\nexport class StaleApprovalError extends Error {\n constructor(\n public readonly taskId: string,\n public readonly requestedApprovalId: string,\n public readonly currentApprovalId: string | undefined,\n ) {\n super(\n `cannot resolve approval ${requestedApprovalId} for task ${taskId}: the currently pending approval is ${\n currentApprovalId ?? '(none recorded)'\n }`,\n );\n this.name = 'StaleApprovalError';\n }\n}\n\n/**\n * S0 (GAP-002): why {@link ConnectionHub.steerTask} refused. Stable strings —\n * a caller (an embedder's operator UI, an HTTP surface mapping this to a\n * status code) switches on these rather than matching error text.\n *\n * - `task_terminal` — the task already reached `Complete`/`Failed`/\n * `Cancelled`. Checked FIRST, so a terminal task that is also (obviously)\n * not `Running` reports the more specific truth, and a steer racing a\n * terminal transition always resolves terminal-first.\n * - `task_not_running` — the task exists and is live, but is `Offered`/\n * `Claimed`/`AwaitApproval`; there is no running turn to steer yet.\n * - `steer_unsupported_runtime` — the runtime that CLAIMED this task cannot\n * be steered, per the claim-time capability snapshot\n * (`TaskSnapshot.claimedRuntimeCapabilities`, sourced from the claiming\n * adapter's own `task.claim.capabilities`). Fail-closed: a MISSING snapshot\n * rejects under this same code, because \"unknown\" is not \"supported\" (see\n * {@link SteerRejectedError}'s own doc comment).\n */\nexport type SteerRejectionCode = 'steer_unsupported_runtime' | 'task_not_running' | 'task_terminal';\n\nfunction steerRejectionMessage(\n taskId: string,\n code: SteerRejectionCode,\n state: TaskState,\n runtime: RuntimeId | undefined,\n): string {\n switch (code) {\n case 'task_terminal':\n return `cannot steer task ${taskId}: task is already terminal (state ${state})`;\n case 'task_not_running':\n // Byte-for-byte the pre-S0 message for this case — only the error's\n // TYPE changed here, mirroring how M4 typed `approveTask`'s failures.\n return `cannot steer task ${taskId}: not running (state ${state})`;\n case 'steer_unsupported_runtime':\n return `cannot steer task ${taskId}: claimed runtime ${runtime ?? '(unknown)'} does not support steering`;\n }\n}\n\n/**\n * S0 (GAP-002): thrown by {@link ConnectionHub.steerTask} instead of the\n * pre-S0 generic `Error`, so a caller can tell WHY a steer was refused\n * without matching on message text — same typed-error idiom as\n * {@link UnknownTaskError}/{@link TaskNotAwaitingApprovalError}/\n * {@link StaleApprovalError} above.\n *\n * The gap this closes: pre-S0 `steerTask` sent `task.steer` to ANY `Running`\n * task, but only pi's adapter implements steering — Claude's and Codex's\n * throw on receipt (`claude-adapter.ts`, `codex-adapter.ts`), which stalls\n * the client's redelivery cursor at that seq and loops forever. So the\n * decision has to be made server-side, from per-runtime truth, BEFORE an\n * envelope exists.\n *\n * Fail-closed on unknown, deliberately: `steer_unsupported_runtime` covers\n * both \"the claiming adapter reported `steer: false`\" and \"this server has no\n * capability snapshot for this task at all\" (a pre-D-4 daemon whose claim\n * carried no `capabilities`, a task record predating S0). Refusing an unknown\n * is a recoverable operator-visible error; guessing \"supported\" reintroduces\n * the exact permanent cursor stall this gate exists to prevent.\n *\n * Thrown before any state change and before any wire message is built — a\n * rejected steer has zero side effects, same as every other validation\n * failure in this file.\n *\n * SINGLE SOURCE, deliberately: the gate reads the claim payload and NOTHING\n * from the connection layer — not {@link getDeviceCapabilities} (the\n * CONNECTION-level `conn.hello` flag list) and not `ConnectionState.runtimes`.\n * Two reasons, either sufficient. First, scope: connection-level data is\n * discovery describing a daemon BUILD, not the per-runtime, per-task,\n * claim-time truth this gate needs — conflating the two is the original bug.\n * Second, reach: `conn.hello` is transport-shaped. A long-poll-only daemon\n * never sends one (sole sender: `ws-transport.ts:192`, `packages/client`), so\n * a connection-sourced snapshot is permanently `undefined` for an entire\n * transport and a fail-closed gate reading it disables steer across that whole\n * deployment surface — a regression, not a safety property. The claim, by\n * contrast, is the message that establishes the task↔runtime binding on every\n * transport, so the gate's input now shares a lifecycle with the thing it\n * judges. Adding a connection-level fallback here would restore both defects\n * at once and is what this design exists to forbid.\n */\nexport class SteerRejectedError extends Error {\n constructor(\n public readonly taskId: string,\n public readonly code: SteerRejectionCode,\n /** The task's state at the moment the steer was refused. */\n public readonly state: TaskState,\n /** `TaskSnapshot.claimedRuntime` — `undefined` when nothing was ever recorded (which is itself a reason `steer_unsupported_runtime` can fire). */\n public readonly runtime: RuntimeId | undefined,\n ) {\n super(steerRejectionMessage(taskId, code, state, runtime));\n this.name = 'SteerRejectedError';\n }\n}\n\nexport class ConnectionHub {\n private readonly connections = new Map<string, ConnectionState>();\n private readonly outboxes = new Map<string, DeviceOutbox>();\n /** Idempotency window per device (N3) — recent inbound envelope ids, capped at {@link DEDUP_RING_CAPACITY}. */\n private readonly dedupRings = new Map<string, Set<string>>();\n private readonly longPollWaiters = new Map<string, LongPollWaiter>();\n private readonly runtimes = new Map<string, TaskRuntime>();\n private readonly serverEvents = new AsyncEventQueue<ByokServerEvent>();\n /**\n * Per-task last-inbound-activity timestamp (epoch ms) — the task-lease\n * reaper's condition (c), see the \"task-lease reaper\" section below. Reset\n * on every accepted inbound `task.*` envelope ({@link recordTaskActivity},\n * called from {@link dispatchToHandler}); cleared once the task reaches a\n * terminal state ({@link onStateChange}), so this map only ever holds\n * entries for currently non-terminal claimed tasks.\n */\n private readonly taskActivity = new Map<string, number>();\n /** The task-lease reaper's own periodic sweep timer — see the constructor and `sweepLeases` below. */\n private readonly leaseReaperTimer: ReturnType<typeof setInterval>;\n /** {@link ConnectionHub.stats}'s `uptimeMs` origin — this hub's own construction instant. */\n private readonly startedAtMs = Date.now();\n /** {@link ConnectionHub.stats}'s `envelopesIn` — every {@link handleInbound} call, every outcome. */\n private envelopesInCount = 0;\n /** {@link ConnectionHub.stats}'s `envelopesOut` — every envelope built via the single outbound choke point, {@link sendToDevice}. */\n private envelopesOutCount = 0;\n /** {@link ConnectionHub.stats}'s `dedupDrops` (N3). */\n private dedupDropCount = 0;\n /** {@link ConnectionHub.stats}'s `rateLimitEvents` — see {@link handleRateLimited}. */\n private rateLimitEventCount = 0;\n /**\n * M4 Phase 4 (gatekeeper LOW advisory): devices that have already had a\n * `device.rate_limited` embedder event emitted for their CURRENT\n * over-budget episode — see {@link handleRateLimited}'s own doc comment.\n * Coalescing state only; {@link rateLimitEventCount} still counts every\n * single hit regardless of what this suppresses.\n */\n private readonly rateLimitEventEmittedFor = new Set<string>();\n\n constructor(\n private readonly taskStore: TaskStore,\n private readonly devices: DeviceRegistry,\n /** See {@link CreateByokServerOptions.taskLeaseMs} — already defaulted by `createByokServer` before reaching here. */\n private readonly taskLeaseMs: number,\n /**\n * M4 Phase 4 (part A): per-device inbound-envelope token bucket — see\n * {@link CreateByokServerOptions.rateLimit} (already defaulted by\n * `createByokServer` before reaching here) and {@link handleInbound}'s\n * own doc comment for where it's enforced. Defaults to a fresh\n * default-configured `RateLimiter` so every existing direct-construction\n * call site (this hub is constructed directly by several tests) keeps\n * working unchanged.\n */\n private readonly rateLimiter: RateLimiter = new RateLimiter(),\n ) {\n // A short (e.g. test-injected) taskLeaseMs sweeps at its own\n // granularity so a short lease is still caught promptly; a realistic\n // multi-minute lease sweeps at a fixed, cheap resolution instead of\n // scaling the interval down with it. Unref'd so this timer never keeps\n // the process alive on its own (mirrors heartbeat.ts's own timer).\n const sweepIntervalMs = Math.min(Math.max(taskLeaseMs, 10), MAX_LEASE_REAPER_SWEEP_INTERVAL_MS);\n this.leaseReaperTimer = setInterval(() => this.sweepLeases(), sweepIntervalMs);\n this.leaseReaperTimer.unref?.();\n }\n\n /**\n * Stop the task-lease reaper's sweep timer — called by `ByokServer.stop()`\n * (`index.ts`) on shutdown. Idempotent: clearing an already-cleared\n * interval is a safe no-op.\n */\n stopLeaseReaper(): void {\n clearInterval(this.leaseReaperTimer);\n }\n\n /** The top-level `events` feed returned by `createByokServer` — see {@link ByokServerEvent}. */\n subscribeServerEvents(): AsyncIterable<ByokServerEvent> {\n return this.serverEvents.subscribe();\n }\n\n // ---------------------------------------------------------------------\n // connection lifecycle — called from ws-server.ts / http.ts\n // ---------------------------------------------------------------------\n\n /**\n * A daemon completed the WS handshake (`conn.hello`). Does not itself send\n * `conn.ack` or redeliver — see {@link sendConnAck}/{@link redeliverAfterReconnect}.\n *\n * `capabilities` (M5, hello-capability plumbing): the daemon's own\n * `conn.hello.capabilities` — previously silently ignored end to end (a\n * verified gap: `ws-server.ts` forwarded only `runtimes`). Optional so\n * every pre-M5 direct-construction call site (several tests construct a\n * `ConnectionHub` and call this directly) keeps working unchanged; a\n * connection this hub never learns capabilities for simply reads back\n * `undefined` from {@link getDeviceCapabilities}.\n */\n registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[]): void {\n const at = new Date().toISOString();\n this.connections.set(deviceId, { ws, connected: true, lastSeen: at, runtimes, capabilities });\n this.serverEvents.push({ kind: 'device.connected', deviceId, at });\n // Last-transport-wins (§8): a WS connection supersedes any long-poll\n // request currently held open for this device — let it complete now\n // instead of leaving it hanging until its own timeout.\n this.settleLongPollWaiter(deviceId);\n }\n\n sendConnAck(deviceId: string, capabilities: string[]): void {\n this.sendToDevice(\n deviceId,\n 'conn.ack',\n {\n protocolVersion: PROTOCOL_VERSION,\n capabilities,\n serverTime: new Date().toISOString(),\n },\n {}, // conn.ack needs neither taskId nor sessionRef\n );\n }\n\n /**\n * Reconnection procedure step 3 (§9): redeliver, in `seq` order, every\n * retained envelope with `seq > cursor` that still belongs to a\n * non-terminal task. Called after `conn.ack` (step 2), per the spec.\n */\n redeliverAfterReconnect(deviceId: string, cursor: number): void {\n const conn = this.connections.get(deviceId);\n if (!conn?.ws || !conn.connected) return;\n for (const envelope of this.collectRelevant(deviceId, cursor)) {\n conn.ws.send(encodeEnvelope(envelope));\n }\n }\n\n /**\n * A device's WS socket closed. `ws` identifies *which* socket closed: if\n * it's no longer the one this device's connection state points at (a\n * newer WS reconnected, or long-poll took over — \"last transport wins\"),\n * this close is for a stale/superseded socket and the device isn't\n * actually gone, so the bookkeeping below is skipped entirely.\n *\n * M1 note: the M0 server force-failed/cancelled every in-flight task for a\n * device the instant it disconnected, on the stated premise that \"a task\n * still in flight for a device that just disconnected can't be resumed, so\n * it's terminated\" — true only in the absence of a redelivery cursor. M1\n * adds exactly that (§9): a task's in-flight state is retained\n * independently of any one connection, specifically so it can survive a\n * disconnect and resume via redelivery once the device reconnects. Failing\n * tasks here would make that feature unreachable in practice (nothing\n * would ever still be non-terminal by the time a reconnect happened), so\n * this now only updates connection bookkeeping and leaves task state\n * alone. A task left in-flight by a device that never reconnects stays\n * that way until the SaaS embedder explicitly cancels it — no\n * disconnect-timeout is specified by the protocol, so none is invented\n * here (see the M1-2 report's contract-gap notes).\n */\n handleDisconnect(deviceId: string, ws: WebSocket): void {\n const conn = this.connections.get(deviceId);\n if (!conn || conn.ws !== ws) return;\n\n conn.connected = false;\n conn.ws = undefined;\n conn.lastSeen = new Date().toISOString();\n // The task-lease reaper's condition (b) clock starts here, now — not at\n // whatever a claimed task's own last-activity timestamp happened to be\n // (see `sweepLeases`'s doc comment).\n conn.darkSince = Date.now();\n this.serverEvents.push({ kind: 'device.disconnected', deviceId, at: conn.lastSeen });\n this.settleLongPollWaiter(deviceId);\n }\n\n // ---------------------------------------------------------------------\n // long-poll fallback (§8) — GET /byok/events, called from http.ts\n // ---------------------------------------------------------------------\n\n /**\n * Resolve immediately if there are already-relevant events past `cursor`;\n * otherwise hold for up to `holdMs` and resolve with an empty result if\n * nothing arrives. A device may be connected via WS or long-poll, not\n * both simultaneously — a poll here supersedes (closes) any live WS for\n * this device (\"last one wins\", documented at the type level on\n * {@link ConnectionState}).\n */\n async pollEvents(deviceId: string, cursor: number, holdMs: number): Promise<{ events: Envelope[]; cursor: number }> {\n this.takeOverAsLongPoll(deviceId);\n this.settleLongPollWaiter(deviceId); // in case a previous poll for this device is still outstanding\n\n const immediate = this.collectRelevant(deviceId, cursor);\n if (immediate.length > 0) {\n return { events: immediate, cursor: this.currentCursor(deviceId) };\n }\n\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this.longPollWaiters.delete(deviceId);\n resolve({ events: [], cursor: this.currentCursor(deviceId) });\n }, holdMs);\n timer.unref?.();\n this.longPollWaiters.set(deviceId, { cursor, resolve, timer });\n });\n }\n\n /** Make long-poll this device's active transport, closing any live WS (\"last one wins\", §8). */\n private takeOverAsLongPoll(deviceId: string): void {\n const conn = this.connections.get(deviceId);\n const at = new Date().toISOString();\n const wasFreshlyConnected = !conn || conn.ws !== undefined || !conn.connected;\n\n if (conn?.ws) {\n const ws = conn.ws;\n // Detach first so the 'close' event this triggers sees a connection\n // state that's already moved on and skips handleDisconnect's\n // task-failure logic — the device isn't gone, it's switching transport.\n // M5: `capabilities` carried forward alongside `runtimes` — a device\n // doesn't re-send `conn.hello` just because its transport switched, so\n // whatever it advertised on the live WS handshake must survive this\n // switch, not silently reset to `undefined` (see `ConnectionState.\n // capabilities`'s own doc comment).\n this.connections.set(deviceId, { connected: true, lastSeen: at, runtimes: conn.runtimes, capabilities: conn.capabilities });\n ws.close(1000, 'superseded by long-poll connection');\n } else if (!conn) {\n this.connections.set(deviceId, { connected: true, lastSeen: at });\n } else {\n conn.connected = true;\n conn.lastSeen = at;\n conn.darkSince = undefined; // alive again — clear any dark-clock start from a previous disconnect\n }\n\n if (wasFreshlyConnected) {\n this.serverEvents.push({ kind: 'device.connected', deviceId, at });\n }\n }\n\n /** Resolve (settle) any long-poll request currently held open for `deviceId`, if one exists. */\n private settleLongPollWaiter(deviceId: string): void {\n const waiter = this.longPollWaiters.get(deviceId);\n if (!waiter) return;\n this.longPollWaiters.delete(deviceId);\n clearTimeout(waiter.timer);\n waiter.resolve({ events: this.collectRelevant(deviceId, waiter.cursor), cursor: this.currentCursor(deviceId) });\n }\n\n // ---------------------------------------------------------------------\n // inbound envelopes from a connected daemon\n // ---------------------------------------------------------------------\n\n /**\n * Single inbound choke point for every daemon -> server envelope (N2/N3/\n * P2) — called by both the WS path (`ws-server.ts`) and the long-poll send\n * path (`POST /byok/messages`, `http.ts`) in place of reaching into\n * per-type handlers directly. Runs a fixed gate, in order:\n *\n * 0. **rate limit (M4 Phase 4, part A)** — one token debited from this\n * device's bucket ({@link rateLimiter}) for EVERY inbound envelope,\n * before anything else runs (including the type-allow check below) —\n * a flood of garbage-typed envelopes must cost the same budget as a\n * flood of well-formed ones. Checked first specifically so an\n * over-budget device is turned away as cheaply as possible, before any\n * taskStore lookup or dedup bookkeeping. See {@link handleRateLimited}\n * for what happens on exceed (never a silent drop).\n * 1. **type-allow (P2)** — only {@link DAEMON_TO_SERVER_TYPES} may pass; a\n * server -> daemon type (or anything unrecognized, e.g. a stale/future\n * `conn.hello` outside the handshake) arriving inbound is rejected\n * before it's dispatched or counted accepted.\n * 2. **ownership (N2)** — an envelope for a task already owned by a\n * *different* device is dropped (logged), never force-failed:\n * force-failing on an authz mismatch would let an attacker who merely\n * guesses a `taskId` kill the real owner's task (a DoS). A task with no\n * owner yet, or that doesn't exist at all, is not rejected here — the\n * per-type handler's own no-op-on-missing-record behavior covers the\n * latter.\n * 3. **dedup (N3)** — an envelope `id` already seen from this device is a\n * no-op: the wire is at-least-once (§9), this makes server-side\n * processing at-most-once. Check-and-record is synchronous (Node is\n * single-threaded), so it's atomic with respect to any other envelope\n * for this device.\n * 4. **dispatch** — handed to the existing per-type `on*` handler.\n *\n * Returns which outcome applied. A duplicate still counts as `accepted` on\n * the `POST /byok/messages` wire (§8.2) — an idempotent replay is a\n * wire-level success even though no handler ran a second time; only\n * `rejected`/`rate_limited` (gate steps 0-2) are excluded from that count.\n */\n handleInbound(deviceId: string, envelope: Envelope): 'accepted' | 'duplicate' | 'rejected' | 'rate_limited' {\n this.envelopesInCount++;\n\n if (!this.rateLimiter.consume(deviceId)) {\n this.handleRateLimited(deviceId);\n return 'rate_limited';\n }\n // Back under budget — clear any earlier suppression (see\n // `rateLimitEventEmittedFor`'s own doc comment) so the NEXT time this\n // device floods, it's treated as a fresh episode and gets its own\n // embedder event rather than being silently coalesced into a flood it\n // already recovered from.\n this.rateLimitEventEmittedFor.delete(deviceId);\n\n if (!(DAEMON_TO_SERVER_TYPES as readonly MessageType[]).includes(envelope.type)) {\n return 'rejected';\n }\n\n const taskId = envelope.task_id;\n if (taskId === undefined) return 'rejected'; // schema guarantees every DAEMON_TO_SERVER_TYPES member carries task_id; defensive only.\n\n const record = this.taskStore.get(taskId);\n if (record && record.deviceId !== undefined && record.deviceId !== deviceId) {\n console.warn(`[byok/server] dropping ${envelope.type} for ${taskId}: owned by a different device`);\n return 'rejected';\n }\n\n if (this.checkAndRecordDuplicate(deviceId, envelope.id)) {\n this.dedupDropCount++;\n return 'duplicate';\n }\n\n this.dispatchToHandler(deviceId, taskId, envelope);\n return 'accepted';\n }\n\n /**\n * M4 Phase 4 (part A): `deviceId` just exceeded its inbound-envelope rate\n * limit. Never a silent drop: counts the occurrence\n * ({@link rateLimitEventCount}, surfaced via {@link stats} — every single\n * hit, unconditionally) and, the FIRST time in this over-budget episode\n * only, emits an embedder-facing `device.rate_limited`\n * {@link ByokServerEvent} — see that variant's own doc comment (`types.ts`)\n * for the full per-transport enforcement shape.\n *\n * Gatekeeper LOW advisory (event amplification): a single flood can make\n * `handleInbound` call this many times in a row — e.g. several WS frames\n * already in flight before the close below actually lands, or a\n * long-poll device retrying its `POST /byok/messages` before its bucket\n * has refilled. Without coalescing, an embedder subscribed to\n * `events.subscribe()` would see one `device.rate_limited` per hit, which\n * is noisy for what is really ONE ongoing episode of one device\n * flooding. `rateLimitEventEmittedFor` suppresses the repeats: this\n * method only pushes the event the first time it sees a given `deviceId`\n * since `handleInbound`'s own success path last cleared it (i.e. since\n * this device was last confirmed back under budget) — the COUNTER above\n * is entirely unaffected by this and still increments on every call,\n * unconditionally.\n *\n * This method only handles the WS half of the enforcement shape (closing\n * the live connection, if any, so the client's existing backoff+reconnect\n * takes over — mirrors `takeOverAsLongPoll`'s own `ws.close`, the only\n * other place this hub closes a device's socket directly); a long-poll\n * device has no live `ws` to close here at all (`conn.ws` is `undefined`\n * while long-polling — see {@link ConnectionState}), so `http.ts`'s\n * `/byok/messages` handler maps this same `'rate_limited'` `handleInbound`\n * outcome to an HTTP 429 for that transport instead.\n */\n private handleRateLimited(deviceId: string): void {\n this.rateLimitEventCount++;\n if (!this.rateLimitEventEmittedFor.has(deviceId)) {\n this.rateLimitEventEmittedFor.add(deviceId);\n const at = new Date().toISOString();\n this.serverEvents.push({ kind: 'device.rate_limited', deviceId, at });\n }\n const conn = this.connections.get(deviceId);\n if (conn?.ws) {\n // 1008 = Policy Violation (RFC 6455) — distinct from the 1002\n // protocol-error closes ws-server.ts uses for a malformed handshake;\n // this connection was well-formed, it just sent too much of it.\n conn.ws.close(1008, 'rate limit exceeded');\n }\n }\n\n /**\n * Idempotency check-and-record (N3): `true` (duplicate) if `id` was\n * already seen for `deviceId`; otherwise records it and returns `false`.\n * Bounded to {@link DEDUP_RING_CAPACITY} ids per device — a ring, not an\n * unbounded set — evicting the oldest once full.\n */\n private checkAndRecordDuplicate(deviceId: string, id: string): boolean {\n let seen = this.dedupRings.get(deviceId);\n if (!seen) {\n seen = new Set<string>();\n this.dedupRings.set(deviceId, seen);\n }\n if (seen.has(id)) return true;\n seen.add(id);\n if (seen.size > DEDUP_RING_CAPACITY) {\n const oldest = seen.values().next().value;\n if (oldest !== undefined) seen.delete(oldest);\n }\n return false;\n }\n\n /**\n * Route one already-gated envelope (see {@link handleInbound}) to its\n * per-type handler. Type-allow/ownership/dedup have already run by the\n * time this executes, so the handlers below no longer need their own\n * device-mismatch checks — that authz decision now lives solely in\n * `handleInbound` (N2).\n *\n * Also the task-lease reaper's activity checkpoint\n * ({@link recordTaskActivity}): every envelope for a task that currently\n * *exists and is non-terminal* counts as proof of life for `taskId`'s\n * lease, regardless of what its per-type handler below ends up doing with\n * it (including a no-op/stale drop) — see the \"task-lease reaper\" section\n * further down for why. Deliberately gated on the record's existence and\n * non-terminal state *here*, before dispatch: `taskActivity` must never\n * gain an entry for a taskId that doesn't exist (a nonexistent/garbage id\n * an authenticated-but-malicious daemon could send indefinitely — an\n * unbounded-growth vector, since `taskId`s aren't deduped the way envelope\n * `id`s are) or for one that's already terminal (a stale/late message for\n * a finished task — `onStateChange` deletes the entry on the *real*\n * terminal transition, but a stale message arriving *after* that would\n * otherwise silently recreate it, since every per-type handler's own\n * terminal/unknown-task guard runs — and early-returns — only *after*\n * this would already have recorded activity).\n */\n private dispatchToHandler(deviceId: string, taskId: string, envelope: Envelope): void {\n const record = this.taskStore.get(taskId);\n if (record && !isTerminal(record.state)) {\n this.recordTaskActivity(taskId);\n }\n switch (envelope.type) {\n case 'task.claim':\n this.onClaim(deviceId, envelope.task_id, envelope.payload);\n return;\n case 'task.started':\n this.onStarted(envelope.task_id, envelope.payload);\n return;\n case 'task.decline':\n this.onDecline(envelope.task_id, envelope.payload);\n return;\n case 'task.progress':\n this.onProgress(envelope.task_id, envelope.payload);\n return;\n case 'task.artifact':\n this.onArtifact(envelope.task_id, envelope.payload);\n return;\n case 'task.await_approval':\n this.onAwaitApproval(envelope.task_id, envelope.payload);\n return;\n case 'task.complete':\n this.onComplete(envelope.task_id, envelope.payload);\n return;\n case 'task.fail':\n this.onFail(envelope.task_id, envelope.payload);\n return;\n case 'task.cancelled':\n this.onCancelled(envelope.task_id, envelope.payload);\n return;\n case 'task.approval_resolved':\n this.onApprovalResolved(envelope.task_id, envelope.payload);\n return;\n default:\n // conn.hello is handled during the handshake (ws-server.ts); the\n // remaining types (conn.ack/task.offer/approve/reject/cancel/steer)\n // are server->daemon only, and handleInbound's type-allow gate\n // already rejects those before this is ever reached. Kept as a safe\n // no-op default (rather than throwing) purely so this switch stays\n // exhaustive over the full MessageType union — not a live path.\n return;\n }\n }\n\n /** Reset the task-lease reaper's per-task clock (condition (c) in the \"task-lease reaper\" section below). */\n private recordTaskActivity(taskId: string): void {\n this.taskActivity.set(taskId, Date.now());\n }\n\n /**\n * Ownership (record.deviceId matching the connection's authenticated\n * deviceId) is enforced centrally by {@link handleInbound} (N2) before this\n * runs; only the idempotent-claim CAS and the first-claim device patch\n * happen here.\n *\n * M5 (claimed runtime, docs/protocol.md §3.1): `payload.runtime` — the\n * ACTUAL adapter the daemon selected (`TaskRunner.pickAdapter`,\n * `packages/client`'s `task-runner.ts`) — is recorded into\n * `TaskSnapshot.claimedRuntime` alongside the device patch, distinct from\n * the pre-existing `TaskSnapshot.runtime` (the merely REQUESTED runtime,\n * untouched here and set only once, at `dispatch()` time). Only ever\n * written on the FIRST real claim: the idempotent-CAS early return above\n * fires before this for a retried claim from a device that already owns\n * the task, so a redelivered/retried `task.claim` can never overwrite an\n * already-recorded `claimedRuntime` — including with a stale or absent\n * value from an out-of-order retry.\n *\n * S0/D-4 (claim-time capability snapshot): `payload.capabilities` — the\n * claiming adapter's OWN self-report, carried on this same `task.claim`\n * (docs/protocol.md §2.4) — supplies\n * `TaskSnapshot.claimedRuntimeCapabilities`, written in the same patch and\n * therefore under the same write-exactly-once property as `claimedRuntime`.\n *\n * Taken from the payload and from nowhere else. This hub deliberately does\n * NOT consult connection state (`conn.hello.runtimes[]`) for it — see\n * {@link SteerRejectedError} for why that source is structurally wrong for a\n * control decision, and that field's own doc comment (`types.ts`) for why\n * this is snapshotted rather than read live at steer time. A claim that\n * carries no `capabilities` (a pre-D-4 daemon) records `undefined`, which\n * the gate reads as \"unknown\" and refuses.\n */\n private onClaim(deviceId: string, taskId: string, payload: TaskClaimPayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n // Claim is an idempotent CAS: a retried claim from the device that\n // already owns this task (e.g. the daemon didn't see the first claim's\n // effect land before retrying) is a no-op, not an illegal-transition\n // failure.\n if (record.state === 'Claimed' || record.state === 'Running') return;\n this.applyOrFail(taskId, 'Claimed', {\n deviceId,\n claimedRuntime: payload.runtime,\n claimedRuntimeCapabilities: payload.capabilities,\n });\n // NOTE (M1 gap #2): claiming no longer implies Running — the daemon\n // reports that explicitly via task.started (see onStarted) once its\n // runtime session actually starts.\n }\n\n /**\n * `Claimed -> Running` (§3.1) — a daemon actually starting the runtime\n * session, distinct from merely claiming. Ownership is already enforced\n * by {@link handleInbound} (N2) before this runs.\n */\n private onStarted(taskId: string, _payload: TaskStartedPayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n if (record.state === 'Running') return; // idempotent (§3.1): already running, no-op.\n if (isTerminal(record.state)) return; // stale/duplicate — task already moved on.\n this.applyOrFail(taskId, 'Running', {});\n }\n\n /**\n * `Offered -> Failed` (§3.2) — a fail-closed pre-claim rejection. Only\n * ever legal from `Offered`; anything else is stale. Ownership is already\n * enforced by {@link handleInbound} (N2) before this runs.\n */\n private onDecline(taskId: string, payload: TaskDeclinePayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n if (record.state !== 'Offered') return;\n this.applyOrFail(taskId, 'Failed', {\n result: { state: 'Failed', reason: payload.reason, retryable: payload.retryable },\n });\n }\n\n private onProgress(taskId: string, payload: TaskProgressPayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n const resumed = this.resumeIfImplicitlyApproved(record);\n if (resumed.state !== 'Running') {\n this.forceFailOrDrop(taskId, 'task.progress received while not Running');\n return;\n }\n const runtime = this.runtimes.get(taskId);\n if (!runtime) return;\n for (const event of payload.events) {\n runtime.queue.push({ kind: 'agent', event });\n }\n }\n\n private onArtifact(taskId: string, payload: TaskArtifactPayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n const resumed = this.resumeIfImplicitlyApproved(record);\n if (resumed.state !== 'Running') {\n this.forceFailOrDrop(taskId, 'task.artifact received while not Running');\n return;\n }\n const runtime = this.runtimes.get(taskId);\n if (!runtime) return;\n runtime.queue.push({ kind: 'artifact', artifact: payload });\n }\n\n private onAwaitApproval(taskId: string, payload: TaskAwaitApprovalPayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n // Idempotent no-op (N3), mirroring onStarted's already-Running guard: a\n // repeat task.await_approval while already AwaitApproval must not be\n // treated as an illegal self-transition and force-failed.\n // AwaitApproval -> AwaitApproval is deliberately NOT in TASK_TRANSITIONS\n // (task-state.ts) — this early return is where the idempotency lives\n // instead.\n if (record.state === 'AwaitApproval') {\n // M5 (approval targeting): a re-delivered/duplicate task.await_approval\n // while already AwaitApproval is normally a pure idempotent no-op (see\n // above) — but if it carries a DIFFERENT approvalId than what's\n // currently stored, the daemon has moved on to a fresh approval (e.g.\n // resolved the previous one entirely locally, then dispatched a new\n // one) that this server's own record hasn't caught up to yet (no\n // `approval_resolved` capability negotiated, or a legacy daemon).\n // Updating the stored id here — via `setPendingApprovalId`, NOT\n // `transition` (which `AwaitApproval -> AwaitApproval` would reject as\n // illegal) — is what keeps a later `approveTask`/`rejectTask` from\n // matching against an id the daemon has already superseded.\n //\n // `setPendingApprovalId` is OPTIONAL on `TaskStore` (a legacy embedder\n // store predating M5 never implements it) — guarded here; a store\n // without it simply never persists the superseding id, degrading to\n // untargeted behavior (see `TaskStore.setPendingApprovalId`'s own doc\n // comment). The observability push below still fires regardless, since\n // it's about surfacing the NEW summary to an operator, independent of\n // whether the store could record the id.\n if (payload.approvalId !== undefined && payload.approvalId !== record.pendingApprovalId) {\n this.taskStore.setPendingApprovalId?.(taskId, payload.approvalId);\n // S2 fix: previously this branch updated the stored id and returned\n // silently — an operator watching TaskHandle.events() kept seeing\n // approval A's summary even though the stored target had already\n // moved to B. Re-emit the SAME event the main (first-entry) branch\n // below emits, carrying the NEW summary/approvalId, so an operator\n // UI re-renders the approval prompt it's showing.\n this.runtimes.get(taskId)?.queue.push({ kind: 'await_approval', summary: payload.summary });\n }\n return;\n }\n this.applyOrFail(taskId, 'AwaitApproval', { pendingApprovalId: payload.approvalId });\n const after = this.taskStore.get(taskId);\n if (after?.state !== 'AwaitApproval') return; // fell back to Failed, or task was unknown\n this.runtimes.get(taskId)?.queue.push({ kind: 'await_approval', summary: payload.summary });\n }\n\n private onComplete(taskId: string, payload: TaskCompletePayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n // Terminal messages arriving for an already-terminal task are stale/\n // duplicate (§9) — drop silently, not a warning.\n if (isTerminal(record.state)) return;\n // Implicit-resume first (no-op unless still AwaitApproval) so a task\n // completing right after a LOCAL-only approval lands on the normal\n // Running -> Complete edge below, not the illegal AwaitApproval ->\n // Complete edge — see resumeIfImplicitlyApproved's own doc comment.\n this.resumeIfImplicitlyApproved(record);\n const result: TaskResult = {\n state: 'Complete',\n summary: payload.summary,\n sessionRef: payload.sessionRef,\n artifactRefs: payload.artifactRefs,\n };\n this.applyOrFail(taskId, 'Complete', { result, sessionRef: payload.sessionRef });\n }\n\n private onFail(taskId: string, payload: TaskFailPayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n // Same stale-terminal-message rule as onComplete (§9) — this is what\n // resolves the M0 gatekeeper's cancel-race finding: a task.fail racing a\n // server-initiated cancel that already landed is now a silent drop.\n if (isTerminal(record.state)) return;\n const result: TaskResult = { state: 'Failed', reason: payload.reason, retryable: payload.retryable };\n this.applyOrFail(taskId, 'Failed', { result });\n }\n\n /**\n * Dual-purpose on receipt (§3.3): if the server already moved this task to\n * `Cancelled` on its own action (the common case — `cancelTask()` is\n * authoritative immediately, §4), this is a late idempotent ack — silent,\n * not a warning (this is the other half of the M0 gatekeeper finding this\n * change resolves). Otherwise it's the authoritative trigger for a\n * cancellation the daemon observed that the server didn't initiate.\n * Ownership is already enforced by {@link handleInbound} (N2) before this\n * runs.\n */\n private onCancelled(taskId: string, payload: TaskCancelledPayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n if (record.state === 'Cancelled') return;\n if (isTerminal(record.state)) return; // Complete/Failed already reached — stale (§9), drop silently.\n this.applyOrFail(taskId, 'Cancelled', { result: { state: 'Cancelled', reason: payload.reason } });\n }\n\n /**\n * M4 (additive-minor, `task.approval_resolved`): the EXPLICIT counterpart\n * to {@link resumeIfImplicitlyApproved} — a daemon that resolved a pending\n * approval entirely LOCALLY now reports it immediately, instead of the\n * server only finding out after the fact once evidence (a later\n * `task.progress`/`task.artifact`/`task.complete`) proves it.\n *\n * Relationship to the implicit path (both stay, permanently — this is not\n * a replacement): {@link resumeIfImplicitlyApproved} remains completely\n * untouched as the fallback for (a) an old daemon that predates this\n * message, and (b) a daemon connected to an old server that never\n * advertised the `approval_resolved` capability flag (`version.ts`) at\n * handshake time — in either case the daemon never sends this message at\n * all (see `packages/client`'s `task-runner.ts`), and the server keeps\n * inferring the resolution from evidence exactly as it did before this\n * message existed. When THIS message does arrive first, it already moves\n * the record out of `AwaitApproval` (see below) — so by the time any\n * following `task.progress`/etc. reaches `onProgress`/`onArtifact`/\n * `onComplete`, `resumeIfImplicitlyApproved`'s own `record.state !==\n * 'AwaitApproval'` guard is already true and it no-ops, never firing its\n * own `task.approval_resolved_implicit` event a second time for the same\n * resolution. The two mechanisms race harmlessly: whichever one the\n * server processes first is the one that actually performs the\n * transition; the other is naturally inert once it runs.\n *\n * Three outcomes, mirroring this file's existing per-type idempotency\n * conventions:\n * - `AwaitApproval` (the expected case): legal transition to `Running`\n * (an existing `TASK_TRANSITIONS` edge, the same one `approveTask`\n * itself uses) plus a `task.approval_resolved` {@link ByokServerEvent}\n * carrying `approvalId`/`decision`/`resolvedBy` for an embedder to\n * observe.\n * - Already `Running` (evidence — or the implicit path — already beat\n * this message to it): idempotent no-op, silent, mirroring\n * `onStarted`'s own already-running guard.\n * - Terminal, or a state that was never `AwaitApproval` in the first\n * place (`Offered`/`Claimed` — a genuinely out-of-sequence report):\n * stale no-op with a `console.warn`, matching this file's existing\n * stale-message convention (`forceFailOrDrop`, `handleInbound`'s\n * ownership-mismatch drop) — never force-failed, since a late/\n * redelivered report about a task that has already moved on is not\n * evidence of anything currently wrong with it.\n *\n * This is also the residual-race resolution the accompanying protocol/docs\n * update documents: a SaaS decision (`approveTask`/`rejectTask`) already in\n * flight when the local resolution happens can still land on the server\n * FIRST and move the record to a terminal state before this message\n * arrives — in that case this message hits the terminal branch above and\n * is a stale no-op, exactly like any other late message for an\n * already-terminal task. The window for that crossing is now\n * network-latency-sized (how long this message takes to arrive), not\n * \"until the next progress message\" the way the pre-existing implicit-only\n * inference left it.\n */\n private onApprovalResolved(taskId: string, payload: TaskApprovalResolvedPayload): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n if (record.state === 'Running') return; // evidence (or the implicit path) already beat this message to it — idempotent no-op, mirrors onStarted's own guard.\n if (record.state !== 'AwaitApproval') {\n // Terminal (a SaaS decision — or a redelivered terminal message —\n // already crossed this one and won), or a state that never reached\n // AwaitApproval at all (Offered/Claimed) — genuinely stale/\n // out-of-sequence either way. Never force-failed: a late report about\n // a task that has already moved on is not itself evidence of anything\n // wrong with that task.\n console.warn(\n `[byok/server] dropping task.approval_resolved for ${taskId}: not awaiting approval (state ${record.state})`,\n );\n return;\n }\n // M5 (approval targeting): both sides know a SPECIFIC approvalId here —\n // the daemon's own report, and this server's own last-recorded\n // `pendingApprovalId` (set by `onAwaitApproval`). If they disagree, the\n // daemon has already moved on to resolving a DIFFERENT (newer) approval\n // than the one this message is about — applying it anyway would\n // incorrectly move this record to `Running` for the wrong approval.\n // Stale no-op: warn, leave state untouched entirely — deliberately NOT\n // `resumeIfImplicitlyApproved`'s unconditional AwaitApproval->Running\n // edge either, since that path is for \"moved on with no explicit id info\n // at all,\" a materially different situation from \"we have two specific,\n // disagreeing ids.\"\n if (payload.approvalId !== undefined && record.pendingApprovalId !== undefined && payload.approvalId !== record.pendingApprovalId) {\n console.warn(\n `[byok/server] stale task.approval_resolved for ${taskId}: reported approvalId ${payload.approvalId} does not match the currently pending ${record.pendingApprovalId}`,\n );\n return;\n }\n this.applyOrFail(taskId, 'Running', {});\n const after = this.taskStore.get(taskId);\n if (after?.state !== 'Running') return; // defensive: AwaitApproval -> Running is always a legal TASK_TRANSITIONS edge, so applyOrFail should never actually fall back to Failed here.\n // M5 (hello-capability plumbing): `targeted` is observability-only,\n // driven by the REPORTING device's advertised capability flag, not by\n // whether this particular message happened to carry a matching id (see\n // `ByokServerEvent`'s own doc comment, `types.ts`, and the\n // `approval-targeting` flag's own doc comment, `version.ts`).\n const targeted =\n record.deviceId !== undefined && (this.getDeviceCapabilities(record.deviceId)?.includes('approval-targeting') ?? false);\n this.serverEvents.push({\n kind: 'task.approval_resolved',\n taskId,\n approvalId: payload.approvalId,\n decision: payload.decision,\n resolvedBy: payload.resolvedBy,\n at: after.updatedAt,\n targeted,\n });\n }\n\n // ---------------------------------------------------------------------\n // transition helpers — the single place \"illegal transition\" is handled\n // ---------------------------------------------------------------------\n\n /**\n * M5 (approval targeting): single low-level wrapper around\n * `TaskStore.transition` that every ACTUAL state-changing write in this\n * file goes through — {@link applyOrFail}'s legal-transition branch,\n * {@link forceFailOrDrop}, and {@link resumeIfImplicitlyApproved} (the one\n * caller that transitions WITHOUT going through `applyOrFail` at all).\n * Two responsibilities, folded in here once rather than duplicated at\n * each call site:\n *\n * 1. Clears `pendingApprovalId` whenever `record` is LEAVING\n * `AwaitApproval` (`record.state === 'AwaitApproval' && to !==\n * 'AwaitApproval'`) — the id this hub last recorded for a task's\n * pending approval ({@link onAwaitApproval}) is meaningless the\n * instant that task is no longer awaiting it. Clearing it here,\n * centrally, is what guarantees a FUTURE `AwaitApproval` cycle for\n * the SAME task always starts from a clean slate instead of silently\n * inheriting a stale id from a previous cycle (which would make a\n * stale-approval check against the NEW cycle's real pending id\n * spuriously pass just because a leftover value happened to still be\n * sitting in the record).\n * 2. Calls {@link onStateChange} — every call site already did this\n * immediately after its own `transition` call; folding it in here\n * removes the duplication and the chance of a future call site\n * forgetting it.\n */\n private transitionTask(taskId: string, record: TaskSnapshot, to: TaskState, patch: TaskPatch): TaskSnapshot {\n const finalPatch: TaskPatch =\n record.state === 'AwaitApproval' && to !== 'AwaitApproval' ? { ...patch, pendingApprovalId: undefined } : patch;\n const updated = this.taskStore.transition(taskId, to, finalPatch);\n this.onStateChange(updated);\n return updated;\n }\n\n /**\n * Apply `taskId`'s state -> `target`. If that's illegal per\n * `TASK_TRANSITIONS`, fall back to `Failed` (if reachable from the current\n * state); this is the \"illegal transition = error + task.fail path\" rule.\n */\n private applyOrFail(taskId: string, target: TaskState, patch: TaskPatch): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n if (canTransition(record.state, target)) {\n this.transitionTask(taskId, record, target, patch);\n return;\n }\n this.forceFailOrDrop(taskId, `illegal transition ${record.state} -> ${target}`);\n }\n\n /**\n * M4 Phase 3 hardening (orchestrator-directed fix for the server-state-\n * machine trace finding): a task can be resolved entirely OUT-OF-BAND, on\n * the daemon side only (M4 Phase 3's local `approvals.resolve`\n * control-socket path, `packages/client`) — the server never sees a wire\n * `task.approve`/`task.reject` for it, so its own record sits in\n * `AwaitApproval` even though the daemon already resumed and moved on.\n *\n * The daemon is the execution authority in this security model (the SaaS\n * only ever *proposes* — see docs/spec.md); the daemon sending ANY further\n * task.* traffic for a task the server still thinks is `AwaitApproval` is\n * itself sufficient proof the approval was resolved locally, one way or\n * another. Rather than force-failing/dropping that traffic (the pre-fix\n * behavior — `onProgress`/`onArtifact`'s own `!== 'Running'` guard,\n * `onComplete`'s illegal-transition fallback), this applies the exact same\n * `AwaitApproval -> Running` edge `approveTask` already uses (a\n * pre-existing legal `TASK_TRANSITIONS` edge, not a new one) through the\n * normal transition path — `taskStore.transition` + `onStateChange`, same\n * as `applyOrFail`'s own legal-transition branch — so every existing\n * consumer of task state (§, `TaskHandle.events()`, the lease reaper's\n * `taskActivity`) observes it exactly as it would a real wire\n * `task.approve`. Then emits `task.approval_resolved_implicit` (a\n * `ByokServerEvent`, NOT a wire message — see that type's own doc comment)\n * so an embedder can distinguish this from an operator-driven approval.\n *\n * M4 (additive-minor, superseding this method's own former \"deferred\"\n * framing): a first-class `task.approval_resolved` WIRE notification now\n * exists (`onApprovalResolved`, below) — a daemon that supports it, talking\n * to a server that advertised the `approval_resolved` capability flag\n * (`version.ts`), reports a local resolution explicitly and immediately\n * instead of leaving the server to infer it here. This method is\n * UNTOUCHED and remains the permanent fallback for the N/N-1 cases where\n * that explicit report never arrives (an old daemon, or an old server this\n * daemon is talking to) — see `onApprovalResolved`'s own doc comment for\n * the full relationship between the two paths, including why they can\n * never both fire for the same resolution.\n *\n * No-op (returns `record` unchanged) for any state other than\n * `AwaitApproval` — every other guard (terminal, pre-claim, already-\n * Running) keeps exactly its current behavior. `onFail`/`onCancelled`\n * never call this: `Failed`/`Cancelled` are already direct, legal edges\n * from `AwaitApproval`, so they never hit the illegal-transition path this\n * exists to avoid in the first place.\n */\n private resumeIfImplicitlyApproved(record: TaskSnapshot): TaskSnapshot {\n if (record.state !== 'AwaitApproval') return record;\n // M5: routed through the shared `transitionTask` chokepoint (not\n // `taskStore.transition` + `onStateChange` directly, as before) so\n // `pendingApprovalId` is cleared here exactly like every other path that\n // leaves `AwaitApproval` — this is the one caller in this file that\n // transitions without going through `applyOrFail` at all.\n const updated = this.transitionTask(record.taskId, record, 'Running', {});\n this.serverEvents.push({ kind: 'task.approval_resolved_implicit', taskId: record.taskId, at: updated.updatedAt });\n return updated;\n }\n\n /**\n * A daemon message didn't fit the task's current state (e.g. progress\n * while AwaitApproval). Force the task to `Failed` if that's reachable;\n * otherwise it's already terminal (or `Offered`, which has no Failed edge)\n * and there's nothing safe to do but log + drop.\n */\n private forceFailOrDrop(taskId: string, reason: string): void {\n const record = this.taskStore.get(taskId);\n if (!record) return;\n if (canTransition(record.state, 'Failed')) {\n this.transitionTask(taskId, record, 'Failed', {\n result: { state: 'Failed', reason, retryable: false },\n });\n return;\n }\n console.warn(`[byok/server] dropping message for ${taskId} (state ${record.state}): ${reason}`);\n }\n\n private onStateChange(record: TaskSnapshot): void {\n this.serverEvents.push({\n kind: 'task.state',\n taskId: record.taskId,\n state: record.state,\n at: record.updatedAt,\n // M5 (claimed runtime): mirrors the snapshot's own field verbatim —\n // see ByokServerEvent's 'task.state' variant doc comment (types.ts).\n claimedRuntime: record.claimedRuntime,\n });\n if (isTerminal(record.state)) {\n // Lease-reaper bookkeeping ends here for every terminal path, not just\n // the reaper's own reapTask() — see the \"task-lease reaper\" section\n // below. Placed ahead of the runtime-lookup early-return so this\n // always runs regardless of whether a TaskHandle is still around.\n this.taskActivity.delete(record.taskId);\n }\n const runtime = this.runtimes.get(record.taskId);\n if (!runtime) return;\n runtime.queue.push({ kind: 'state', state: record.state, at: record.updatedAt });\n if (isTerminal(record.state)) {\n runtime.resolveResult(record.result ?? { state: record.state });\n runtime.queue.close();\n }\n }\n\n // ---------------------------------------------------------------------\n // task-lease reaper (Decision: Failed(retryable:true) on dark-device\n // timeout — no new task state, no new wire message)\n // ---------------------------------------------------------------------\n\n /**\n * Task lease: a backstop for a device that goes dark mid-task and never\n * comes back — distinct from, and layered on top of, M1's redelivery\n * (docs/protocol.md §9), which already handles \"device reconnects within\n * the window, nothing lost.\" Decision (user+design): reuse the existing\n * `Failed` terminal state and its `retryable` flag —\n * `Failed(retryable: true, reason: 'lease-expired')` — exactly like any\n * other `task.fail`. The embedder is expected to treat this exactly like\n * any other retryable failure: re-dispatch as a brand-new task.\n *\n * Implemented as a periodic sweep (see the constructor), not a per-task\n * timer, so a device that goes dark *after* being idle-but-connected for a\n * while is still caught on a later tick without needing extra bookkeeping\n * at disconnect time. `sweepLeases` reaps a task only when ALL of the\n * following hold, checked fresh on every tick (never cached):\n *\n * (a) the task is in a non-terminal *claimed* state — `Claimed`,\n * `Running`, or `AwaitApproval` ({@link isClaimedState}). `Offered`\n * is excluded: it has no owning device yet, so there's nothing to\n * be \"dark\".\n * (b) the owning device is dark right now ({@link deviceDarkSince}\n * returns a timestamp rather than `undefined`) — disconnected\n * outright, or (long-poll only) hasn't been seen since before the\n * lease window. A live WS connection is never dark from the\n * reaper's point of view: `heartbeat.ts` already independently\n * proves liveness at the transport level and flips\n * `connected: false` via `handleDisconnect` once it stops getting\n * pongs — the reaper just reads that flag rather than re-deriving\n * it. `deviceDarkSince` also returns *when* darkness started\n * ({@link ConnectionState.darkSince}, set the instant\n * `handleDisconnect` flips the connection dark) — that instant\n * feeds condition (c), below.\n * (c) a full `taskLeaseMs` has elapsed since the *later* of: the task's\n * own last inbound-activity timestamp ({@link taskActivity}, reset\n * in {@link dispatchToHandler} on every accepted envelope for a\n * known, non-terminal task — claim, started, progress, artifact,\n * await_approval, anything), and (b)'s dark-since instant. Taking\n * the *later* of the two — not the activity timestamp alone — is\n * what makes a device going dark start a fresh, full countdown\n * instead of reusing whatever (possibly already-stale) activity\n * timestamp the task happened to have: a task can be legitimately\n * idle *while connected* for longer than `taskLeaseMs` (a long turn\n * with no progress events, or just a quiet stretch) without being\n * touched — see (b) — but the instant such a task's device\n * disconnects, that stale activity timestamp must NOT immediately\n * satisfy (c) on its own, or the task would get reaped within one\n * sweep tick of disconnect instead of waiting the full window. That\n * was a real bug (a disconnect-after-long-idle reap effectively\n * indistinguishable from the M0 disconnect-alone-fails-the-task\n * behavior M1 removed, below); anchoring (c) to\n * `max(lastActivity, darkSince)` fixes it — idle time that elapsed\n * *before* the device went dark no longer counts toward the lease,\n * only silence *after* dark-start does.\n *\n * (b) and (c) are deliberately independent clocks, not one merged check.\n * The property this most exists to protect: a *connected*, momentarily\n * idle device mid-turn must never be reaped, no matter how long\n * `taskLeaseMs` is — condition (b) alone blocks that regardless of (c).\n * This is also what keeps this from reintroducing the M0 bug M1\n * deliberately removed (see `handleDisconnect`'s own doc comment above) —\n * M0 force-failed a task the instant its device disconnected; M1\n * correctly stopped doing that so a task could survive a disconnect and\n * resume via redelivery. This reaper does not revert that: disconnect\n * ALONE still does nothing here either — (c) still has to independently\n * hold, and per the `max(...)` above it only will once a full\n * `taskLeaseMs` has genuinely elapsed *since the device went dark*, no\n * matter how stale the task's own activity timestamp already was at that\n * moment.\n *\n * Interaction with redelivery (§9): redelivery is what handles \"the\n * device came back within the window\" — nothing to reap, normal traffic\n * resumes. This reaper is what handles \"it never came back.\" Idempotent\n * claim (`onClaim`'s CAS) still protects server-side bookkeeping if a\n * device wakes up *after* its task was already reaped and retries a stale\n * claim/progress/etc. for it: every per-type handler's existing\n * stale/terminal-task guard (§9) drops it as a no-op, same as any other\n * late message for an already-terminal task — no new guard was needed for\n * that here.\n *\n * Accepted residual (by design, not a bug): idempotent claim protects\n * *server-side* state, not the device's own local side effects. A dark\n * device that wakes up after its task has already been reaped may still\n * be mid-way through running real local work (file writes, shell\n * commands, whatever the runtime adapter was doing) for a task the server\n * has since moved on from — and that the embedder may have already\n * re-dispatched elsewhere. There is no way to remotely guarantee a\n * truly-dark device stops running; the mitigation is entirely\n * `taskLeaseMs` being set far larger than any realistic task duration, so\n * this can only happen to a device that was genuinely gone for a very\n * long time, not a normal slow turn.\n */\n private sweepLeases(): void {\n const now = Date.now();\n for (const record of this.taskStore.list()) {\n if (!isClaimedState(record.state) || !record.deviceId) continue;\n const darkSince = this.deviceDarkSince(record.deviceId, now);\n if (darkSince === undefined) continue;\n const lastActivity = this.taskActivity.get(record.taskId) ?? Date.parse(record.updatedAt);\n // The later of the two — see condition (c) above: a device going dark\n // must always start a fresh, full `taskLeaseMs` countdown, even when\n // the task's own activity was already stale at that moment.\n const silentSince = Math.max(lastActivity, darkSince);\n if (now - silentSince < this.taskLeaseMs) continue;\n this.reapTask(record.taskId);\n }\n }\n\n /**\n * Condition (b) above: `undefined` while `deviceId`'s connection counts as\n * alive (never reapable, no matter how stale (c) is); otherwise the\n * epoch-ms instant it began counting as \"dark\" for lease purposes.\n * `sweepLeases` combines this with (c)'s own last-activity instant via\n * `max(...)` so the full `taskLeaseMs` silence window is always measured\n * from whichever of the two happened later.\n */\n private deviceDarkSince(deviceId: string, nowMs: number): number | undefined {\n const conn = this.connections.get(deviceId);\n if (!conn || !conn.connected) {\n // Disconnected. `darkSince` is set the instant `handleDisconnect`\n // flips `connected` false — using that (not \"now\", and not the\n // task's own possibly-much-older activity timestamp) is what anchors\n // the silence window to when the device actually went dark. The `?? 0`\n // fallback is defensive only, for a deviceId with no connection state\n // at all (in practice `record.deviceId` implies one was registered).\n return conn?.darkSince ?? 0;\n }\n if (conn.ws) return undefined; // live WS — heartbeat.ts is the liveness proof for this transport; never dark\n // Long-poll: dark once its own liveness signal (refreshed on every poll\n // call, §8) has gone stale for a full lease window — the instant it's\n // considered to have gone dark is the last time it was confirmed alive.\n const lastSeenMs = Date.parse(conn.lastSeen);\n return nowMs - lastSeenMs >= this.taskLeaseMs ? lastSeenMs : undefined;\n }\n\n /** Reap one lease-expired task through the exact same TaskStore/canTransition path — and terminal-event emission — as any other `task.fail` (see {@link applyOrFail}). */\n private reapTask(taskId: string): void {\n const record = this.taskStore.get(taskId);\n if (!record || isTerminal(record.state)) return; // resolved by something else between this tick's scan and now\n this.applyOrFail(taskId, 'Failed', {\n result: { state: 'Failed', reason: 'lease-expired', retryable: true },\n });\n }\n\n // ---------------------------------------------------------------------\n // dispatch() and the TaskHandle it returns\n // ---------------------------------------------------------------------\n\n async dispatch(input: DispatchInput): Promise<TaskHandle> {\n const deviceId = input.deviceId ?? this.pickFirstConnectedDevice();\n // M0 routing has no queue-until-connect: reject clearly instead of\n // silently queuing a task nothing will ever claim.\n if (!deviceId || !this.connections.get(deviceId)?.connected) {\n throw new Error(\n deviceId\n ? `device ${deviceId} is not connected`\n : 'no connected device to dispatch to (M0 does not queue tasks until a device connects)',\n );\n }\n\n const taskId = generateTaskId();\n const policy = input.policy ?? DEFAULT_POLICY;\n const record = this.taskStore.create({\n taskId,\n instruction: input.instruction,\n runtime: input.runtime,\n policy,\n deviceId,\n sessionRef: input.sessionRef,\n });\n\n const queue = new AsyncEventQueue<ServerTaskEvent>();\n let resolveResult!: (result: TaskResult) => void;\n const result = new Promise<TaskResult>((resolve) => {\n resolveResult = resolve;\n });\n this.runtimes.set(taskId, { queue, resolveResult, result });\n queue.push({ kind: 'state', state: record.state, at: record.createdAt });\n this.serverEvents.push({ kind: 'task.created', taskId, at: record.createdAt });\n\n this.sendToDevice(\n deviceId,\n 'task.offer',\n {\n instruction: input.instruction,\n policy,\n runtime: input.runtime,\n sessionRef: input.sessionRef,\n },\n { taskId, sessionRef: input.sessionRef },\n );\n\n return this.buildTaskHandle(taskId);\n }\n\n private buildTaskHandle(taskId: string): TaskHandle {\n const hub = this;\n return {\n taskId,\n events(): AsyncIterable<ServerTaskEvent> {\n const runtime = hub.runtimes.get(taskId);\n if (!runtime) throw new Error(`unknown taskId: ${taskId}`);\n return runtime.queue.subscribe();\n },\n cancel(reason?: string): Promise<void> {\n return hub.cancelTask(taskId, reason);\n },\n approve(opts?: { approvalId?: string }): Promise<void> {\n return hub.approveTask(taskId, opts);\n },\n reject(reason?: string, opts?: { approvalId?: string }): Promise<void> {\n return hub.rejectTask(taskId, reason, opts);\n },\n steer(text: string): Promise<void> {\n return hub.steerTask(taskId, text);\n },\n result(): Promise<TaskResult> {\n const runtime = hub.runtimes.get(taskId);\n if (!runtime) throw new Error(`unknown taskId: ${taskId}`);\n return runtime.result;\n },\n };\n }\n\n /** Idempotent: cancelling an already-terminal task is a no-op, not an error. */\n private async cancelTask(taskId: string, reason?: string): Promise<void> {\n const record = this.taskStore.get(taskId);\n if (!record) throw new Error(`unknown taskId: ${taskId}`);\n if (isTerminal(record.state)) return;\n // The protocol defines no daemon ack for task.cancel, so the server-side\n // Cancelled state is authoritative immediately; task.cancel is forwarded\n // as a best-effort notification for the daemon to stop local work.\n this.applyOrFail(taskId, 'Cancelled', { result: { state: 'Cancelled', reason } });\n if (record.deviceId) {\n this.sendToDevice(record.deviceId, 'task.cancel', { reason }, { taskId });\n }\n }\n\n /**\n * M4 Phase 3: made public (was private through M3) so an embedder can call\n * it directly from its own operator-facing surface — there is no\n * bearer-authed HTTP route for this on `http.ts`'s own app (see\n * `UnknownTaskError`'s own doc comment for why, and\n * `examples/basic/server.ts`'s `/api/tasks/:taskId/approve` for the\n * intended shape of that embedder-built surface). See this file's own\n * `UnknownTaskError`/`TaskNotAwaitingApprovalError` doc comments for why\n * the two failure modes are now distinct typed errors rather than a\n * single generic `Error`. Every thrown message's TEXT is byte-for-byte\n * unchanged from M2/M3 — only the error's type changed (this is still also\n * reachable via `TaskHandle.approve()`, unaffected).\n */\n /**\n * M5 (approval targeting, docs/protocol.md §5.3): `opts.approvalId`\n * targets a SPECIFIC pending approval rather than \"whichever one is\n * currently pending\" (the pre-M5 default, unchanged when `opts` is\n * omitted). Validated FIRST, before any state change or wire send: if\n * `opts.approvalId` is supplied and this hub has a recorded\n * `pendingApprovalId` for `taskId` that DIFFERS, throws\n * {@link StaleApprovalError} — no transition, no `task.approve` sent. If\n * this hub never recorded a `pendingApprovalId` (a legacy daemon that\n * never reported one), the call proceeds untargeted exactly as before.\n * The outgoing `task.approve` carries `approvalId`: the caller-supplied\n * one if given, else this hub's own recorded one, else omitted entirely\n * (legacy wire shape) — so the daemon can apply its own exact-match check\n * whenever this server has an id to offer at all.\n */\n async approveTask(taskId: string, opts?: { approvalId?: string }): Promise<void> {\n const record = this.taskStore.get(taskId);\n if (!record) throw new UnknownTaskError(taskId);\n if (record.state !== 'AwaitApproval') {\n throw new TaskNotAwaitingApprovalError(taskId, record.state, 'approve');\n }\n if (opts?.approvalId !== undefined && record.pendingApprovalId !== undefined && opts.approvalId !== record.pendingApprovalId) {\n throw new StaleApprovalError(taskId, opts.approvalId, record.pendingApprovalId);\n }\n this.applyOrFail(taskId, 'Running', {});\n if (record.deviceId) {\n const approvalId = opts?.approvalId ?? record.pendingApprovalId;\n this.sendToDevice(record.deviceId, 'task.approve', { approvalId }, { taskId });\n }\n }\n\n /**\n * M4 Phase 3: made public — see {@link ConnectionHub.approveTask}'s own\n * doc comment for the full rationale (identical reasoning applies here).\n * M5: same `opts.approvalId` targeting semantics as `approveTask` above —\n * see that method's own doc comment.\n */\n async rejectTask(taskId: string, reason?: string, opts?: { approvalId?: string }): Promise<void> {\n const record = this.taskStore.get(taskId);\n if (!record) throw new UnknownTaskError(taskId);\n if (record.state !== 'AwaitApproval') {\n throw new TaskNotAwaitingApprovalError(taskId, record.state, 'reject');\n }\n if (opts?.approvalId !== undefined && record.pendingApprovalId !== undefined && opts.approvalId !== record.pendingApprovalId) {\n throw new StaleApprovalError(taskId, opts.approvalId, record.pendingApprovalId);\n }\n this.applyOrFail(taskId, 'Failed', {\n result: { state: 'Failed', reason: reason ?? 'approval rejected', retryable: false },\n });\n if (record.deviceId) {\n const approvalId = opts?.approvalId ?? record.pendingApprovalId;\n this.sendToDevice(record.deviceId, 'task.reject', { reason, approvalId }, { taskId });\n }\n }\n\n /**\n * S0 (GAP-002): a task-level gate, evaluated in full before any envelope is\n * built — see {@link SteerRejectedError} for the gap this closes and why an\n * unknown capability must refuse rather than proceed. Order matters:\n *\n * 1. unknown task — unchanged pre-S0 `Error` (this is not a steer-policy\n * decision, and `TaskHandle.steer` can only be reached with a taskId\n * this hub minted, so it's a programming error, not an operator one);\n * 2. terminal (`Complete`/`Failed`/`Cancelled`) -> `task_terminal`,\n * checked BEFORE the `Running` check so a steer racing a terminal\n * transition always resolves terminal-first;\n * 3. not `Running` (`Offered`/`Claimed`/`AwaitApproval`) ->\n * `task_not_running`;\n * 4. the claim-time snapshot does not positively say `steer: true` ->\n * `steer_unsupported_runtime`, including when there is no snapshot at\n * all (fail-closed);\n * 5. only then, the pre-existing device-liveness check and the send.\n *\n * Step 4 reads `TaskSnapshot.claimedRuntimeCapabilities` — the per-runtime,\n * per-task value frozen at claim time from the claiming adapter's own\n * `task.claim.capabilities` — and reads NO connection state whatsoever:\n * not {@link getDeviceCapabilities}, not `ConnectionState.runtimes`, and\n * with no fallback to either when the snapshot is absent. See\n * {@link SteerRejectedError} for why a connection-sourced input is wrong\n * both in scope (describes a daemon build, not this task's runtime) and in\n * reach (absent entirely on long-poll-only daemons).\n */\n private async steerTask(taskId: string, text: string): Promise<void> {\n const record = this.taskStore.get(taskId);\n if (!record) throw new Error(`unknown taskId: ${taskId}`);\n if (isTerminal(record.state)) {\n throw new SteerRejectedError(taskId, 'task_terminal', record.state, record.claimedRuntime);\n }\n if (record.state !== 'Running') {\n throw new SteerRejectedError(taskId, 'task_not_running', record.state, record.claimedRuntime);\n }\n if (record.claimedRuntimeCapabilities?.steer !== true) {\n throw new SteerRejectedError(taskId, 'steer_unsupported_runtime', record.state, record.claimedRuntime);\n }\n if (!record.deviceId || !this.connections.get(record.deviceId)?.connected) {\n throw new Error(`device for task ${taskId} is not connected`);\n }\n this.sendToDevice(record.deviceId, 'task.steer', { text }, { taskId });\n }\n\n private pickFirstConnectedDevice(): string | undefined {\n for (const [deviceId, conn] of this.connections) {\n if (conn.connected) return deviceId;\n }\n return undefined;\n }\n\n // ---------------------------------------------------------------------\n // outbound envelope delivery + per-device seq/redelivery bookkeeping (§1.2, §9)\n // ---------------------------------------------------------------------\n\n /**\n * Build a server -> daemon envelope with a fresh per-device `seq`, retain\n * it in that device's outbox ring buffer, and deliver it now if a live\n * transport is available (WS send, or wake a pending long-poll).\n *\n * `opts`'s type mirrors `createEnvelope`'s own per-type conditional\n * requiredness (finding F1) minus `seq` (computed fresh right here on\n * every call, never caller-supplied) — so every one of this method's 6\n * callers below must supply `taskId` for the 5 types that need it\n * (everything except `conn.ack`), same as calling `createEnvelope`\n * directly would require.\n */\n private sendToDevice<T extends 'conn.ack' | 'task.offer' | 'task.approve' | 'task.reject' | 'task.cancel' | 'task.steer'>(\n deviceId: string,\n type: T,\n payload: Parameters<typeof createEnvelope<T>>[1],\n opts: Omit<CreateEnvelopeOptions<T>, 'seq' | 'v' | 'id' | 'ts'>,\n ): Extract<Envelope, { type: T }> {\n this.envelopesOutCount++;\n const outbox = this.getOrCreateOutbox(deviceId);\n const seq = outbox.nextSeq++;\n // `createEnvelope`'s own public signature conditionally requires `opts`\n // per-T via a rest-parameter tuple (see codec.ts) — that detail doesn't\n // survive being re-forwarded through another generic function boundary\n // here, so this cast just restates what `opts`'s type above already\n // guarantees the caller supplied (taskId when T needs it).\n const combinedOpts = { ...opts, seq } as CreateEnvelopeOptions<T>;\n const envelope = createEnvelope(type, payload, combinedOpts);\n const taskId = (opts as { taskId?: string }).taskId;\n // N1/F4: task.cancel/task.reject move their task to Cancelled/Failed\n // (terminal) before this call — see cancelTask/rejectTask's own\n // mark-terminal-then-send order — so without this exemption\n // collectRelevant's terminal-task filter would always exclude them.\n // task.approve/task.steer never fire on an already-terminal task in the\n // first place (approveTask/steerTask both require a specific\n // non-terminal state), so the exemption is moot — and correctly\n // omitted — for them.\n const redeliverThroughTerminal = type === 'task.cancel' || type === 'task.reject';\n outbox.ring.push({ seq, taskId, envelope, redeliverThroughTerminal });\n if (outbox.ring.length > OUTBOX_RING_CAPACITY) outbox.ring.shift();\n this.deliverToDevice(deviceId, envelope);\n return envelope;\n }\n\n private deliverToDevice(deviceId: string, envelope: Envelope): void {\n const conn = this.connections.get(deviceId);\n if (conn?.connected && conn.ws) {\n conn.ws.send(encodeEnvelope(envelope));\n }\n // A device might be long-polling instead of WS-connected — wake it so\n // the new envelope is delivered immediately rather than at the next\n // poll's timeout.\n this.settleLongPollWaiter(deviceId);\n }\n\n /**\n * Retained envelopes for `deviceId` with `seq > cursor` that still belong\n * to a non-terminal task — OR are explicitly exempted from that filter\n * (`redeliverThroughTerminal`, N1/F4: `task.cancel`/`task.reject`) — in\n * `seq` order. The `seq > cursor` bound is what naturally stops an\n * exempted entry from redelivering forever: once the daemon acks it (its\n * reported cursor advances past that `seq`), it no longer qualifies here\n * on any future reconnect/poll.\n */\n private collectRelevant(deviceId: string, cursor: number): Envelope[] {\n const outbox = this.outboxes.get(deviceId);\n if (!outbox) return [];\n return outbox.ring\n .filter(\n (entry) =>\n entry.seq > cursor &&\n entry.taskId !== undefined &&\n (!this.isTaskTerminal(entry.taskId) || entry.redeliverThroughTerminal),\n )\n .map((entry) => entry.envelope);\n }\n\n private isTaskTerminal(taskId: string): boolean {\n const record = this.taskStore.get(taskId);\n return !record || isTerminal(record.state);\n }\n\n /** The highest `seq` assigned to `deviceId` so far — the redelivery cursor to hand back on a poll/reconnect. */\n private currentCursor(deviceId: string): number {\n const outbox = this.outboxes.get(deviceId);\n return outbox ? outbox.nextSeq - 1 : 0;\n }\n\n private getOrCreateOutbox(deviceId: string): DeviceOutbox {\n let outbox = this.outboxes.get(deviceId);\n if (!outbox) {\n outbox = { nextSeq: 1, ring: [] };\n this.outboxes.set(deviceId, outbox);\n }\n return outbox;\n }\n\n // ---------------------------------------------------------------------\n // read-only accessors backing the public `machines` / `tasks` API\n // ---------------------------------------------------------------------\n\n listMachines(): MachineInfo[] {\n return this.devices.list().map(({ deviceId, deviceName }) => {\n const conn = this.connections.get(deviceId);\n return {\n deviceId,\n deviceName,\n connected: conn?.connected ?? false,\n lastSeen: conn?.lastSeen,\n runtimes: conn?.runtimes,\n };\n });\n }\n\n /**\n * M5 (approval targeting, hello-capability plumbing): the capability flags\n * `deviceId`'s CURRENT connection advertised in its `conn.hello` —\n * `undefined` if this hub has no connection state for the device at all,\n * or one that never had capabilities recorded (a pre-M5 daemon, or a\n * device this hub only ever saw over long-poll with no prior WS hello —\n * see `ConnectionState.capabilities`'s own doc comment). Read fresh from\n * live connection state, mirroring `listMachines()`'s own convention; an\n * embedder can use this to distinguish a targeting-capable device from a\n * legacy one for its own observability/UI purposes (see `version.ts`'s\n * `approval-targeting` flag doc comment for why this is informational\n * only, never a correctness gate).\n */\n getDeviceCapabilities(deviceId: string): readonly string[] | undefined {\n return this.connections.get(deviceId)?.capabilities;\n }\n\n getTask(taskId: string): TaskSnapshot | undefined {\n return this.taskStore.get(taskId);\n }\n\n listTasks(): TaskSnapshot[] {\n return this.taskStore.list();\n }\n\n // ---------------------------------------------------------------------\n // observability (M4 Phase 4, part B.1) — in-process only; see\n // `types.ts`'s `HubStats`/`CreateByokServerOptions.healthzRoute` doc\n // comments for why this is never exposed over HTTP by this SDK itself.\n // ---------------------------------------------------------------------\n\n /**\n * A plain, serializable snapshot of this hub's current state, derived from\n * existing structures (`connections`, `taskStore`) plus the small counters\n * this file already maintains for exactly this purpose — no new\n * bookkeeping structures beyond those counters. See {@link HubStats}\n * (`types.ts`) for the full field-by-field contract.\n */\n stats(): HubStats {\n const taskCountsByState = Object.fromEntries(TASK_STATES.map((state) => [state, 0])) as Record<TaskState, number>;\n for (const record of this.taskStore.list()) {\n taskCountsByState[record.state]++;\n }\n\n let connectedDeviceCount = 0;\n for (const conn of this.connections.values()) {\n if (conn.connected) connectedDeviceCount++;\n }\n\n return {\n connectedDeviceCount,\n taskCountsByState,\n envelopesIn: this.envelopesInCount,\n envelopesOut: this.envelopesOutCount,\n dedupDrops: this.dedupDropCount,\n rateLimitEvents: this.rateLimitEventCount,\n uptimeMs: Date.now() - this.startedAtMs,\n };\n }\n}\n","import { canTransition, type PermissionPolicy, type RuntimeId, type TaskState } from '@byok-sdk/protocol';\nimport type { TaskSnapshot } from './types';\n\n/** Thrown by a {@link TaskStore}'s `transition` when `from -> to` is not in TASK_TRANSITIONS. Every implementation (in-memory, SQLite, or otherwise) must throw this rather than silently applying an invalid move. */\nexport class IllegalTaskTransitionError extends Error {\n constructor(\n public readonly taskId: string,\n public readonly from: TaskState,\n public readonly to: TaskState,\n ) {\n super(`illegal task transition for ${taskId}: ${from} -> ${to}`);\n this.name = 'IllegalTaskTransitionError';\n }\n}\n\nexport interface CreateTaskInput {\n taskId: string;\n instruction: string;\n runtime?: RuntimeId;\n policy: PermissionPolicy;\n deviceId?: string;\n sessionRef?: string;\n}\n\n/** A task's full persisted state, as tracked by any {@link TaskStore} implementation. Same shape as {@link TaskSnapshot} — kept as its own (structurally identical) type so this storage-layer contract can evolve independently of the SDK-facing `TaskSnapshot` if a future need arises. */\nexport interface TaskRecord extends TaskSnapshot {}\n\n/**\n * Storage contract for task records — the M3 injection point, mirroring how\n * {@link BlobStore} (`blob-store.ts`) is injectable: `createByokServer`\n * (`index.ts`) accepts `opts.taskStore`, defaulting to {@link InMemoryTaskStore}\n * so nothing breaks for an embedder that doesn't override it. `ConnectionHub`\n * (`hub.ts`) is written against this interface only — it never references\n * {@link InMemoryTaskStore} (or any other concrete implementation) directly —\n * so a persistent implementation such as `sqlite-task-store.ts`'s\n * `SqliteTaskStore` (M3) drops in with zero changes anywhere else.\n *\n * Every implementation MUST enforce the protocol's `TASK_TRANSITIONS` state\n * machine (via `canTransition`) inside `transition`, throwing\n * {@link IllegalTaskTransitionError} rather than silently applying an\n * invalid move — this is part of the interface's contract, not just an\n * `InMemoryTaskStore` implementation detail. `ConnectionHub`'s `applyOrFail`\n * (`hub.ts`) relies on that exception type to decide \"illegal transition ->\n * force `Failed` if possible, else drop\".\n */\nexport interface TaskStore {\n /** Create a new task record in the `Offered` state. */\n create(input: CreateTaskInput): TaskRecord;\n /** Look up a task by id, or `undefined` if unknown. */\n get(taskId: string): TaskRecord | undefined;\n /** All known tasks. */\n list(): TaskRecord[];\n /**\n * Apply `taskId`'s state -> `to`, merging `patch` into the record. Must\n * throw {@link IllegalTaskTransitionError} if the move isn't legal per\n * `TASK_TRANSITIONS`, and a plain `Error` (message: `` `unknown taskId:\n * ${taskId}` ``) if the task doesn't exist at all —\n * {@link InMemoryTaskStore.transition}'s existing message format, which\n * some tests match on.\n */\n transition(taskId: string, to: TaskState, patch?: Partial<Omit<TaskRecord, 'taskId' | 'state'>>): TaskRecord;\n /**\n * M5 (approval targeting): update `taskId`'s `pendingApprovalId` WITHOUT a\n * state transition. Needed because `AwaitApproval -> AwaitApproval` is\n * deliberately not a legal `TASK_TRANSITIONS` edge (`@byok-sdk/protocol`'s\n * `task-state.ts`) — self-transitions aren't part of the frozen wire state\n * machine, so `transition` above cannot be used to update this field while\n * the record STAYS in `AwaitApproval` — yet a re-sent/updated\n * `task.await_approval` carrying a NEWER id while the record is ALREADY\n * `AwaitApproval` must still be reflected (see `ConnectionHub.\n * onAwaitApproval`, `hub.ts`). Every state-CHANGING write still goes\n * through `transition`; this is the one narrow exception for a same-state\n * field update. Returns `undefined` for an unknown `taskId` rather than\n * throwing — this is a best-effort bookkeeping update, not a\n * state-machine-enforced operation like `transition`.\n *\n * OPTIONAL: a `TaskStore` implementation predating M5 (a custom embedder\n * store written against the pre-M5 interface) doesn't have this method at\n * all — every call site in `hub.ts` guards its absence with `?.()`. A\n * store that omits it simply never records a superseding\n * `pendingApprovalId` on the same-state redelivery path (the first entry\n * into `AwaitApproval` still records one via `transition`'s own patch,\n * same as ever); an operator-supplied `opts.approvalId` on\n * `approveTask`/`rejectTask` then has nothing recorded to compare against\n * and proceeds untargeted, exactly like a legacy daemon that never\n * reported an id at all — a graceful degrade, not a broken embedder.\n * Every CURRENT implementation in this package ({@link InMemoryTaskStore},\n * `sqlite-task-store.ts`'s `SqliteTaskStore`) still implements it as a\n * required, always-present method — optionality is a concession to\n * EXISTING third-party implementations of this interface, not a hint that\n * a new one should skip it.\n */\n setPendingApprovalId?(taskId: string, pendingApprovalId: string | undefined): TaskRecord | undefined;\n}\n\n/**\n * Plain, framework-agnostic in-memory {@link TaskStore}. Enforces the\n * protocol's `TASK_TRANSITIONS` state machine via `canTransition` — every\n * state change must go through {@link transition}, which throws\n * {@link IllegalTaskTransitionError} rather than silently applying an invalid\n * move. Callers (the connection hub) decide what to do with that error; see\n * `hub.ts`'s `applyOrFail` for the \"illegal transition -> force Failed if\n * possible, else drop\" policy.\n *\n * M0/M1/M2 reference default — loses all state on process restart. See\n * `sqlite-task-store.ts`'s `SqliteTaskStore` (M3) for a persistent\n * alternative implementing the same {@link TaskStore} contract.\n */\nexport class InMemoryTaskStore implements TaskStore {\n private readonly tasks = new Map<string, TaskRecord>();\n\n create(input: CreateTaskInput): TaskRecord {\n const now = new Date().toISOString();\n const record: TaskRecord = {\n taskId: input.taskId,\n state: 'Offered',\n instruction: input.instruction,\n runtime: input.runtime,\n policy: input.policy,\n deviceId: input.deviceId,\n sessionRef: input.sessionRef,\n createdAt: now,\n updatedAt: now,\n };\n this.tasks.set(record.taskId, record);\n return record;\n }\n\n get(taskId: string): TaskRecord | undefined {\n return this.tasks.get(taskId);\n }\n\n list(): TaskRecord[] {\n return [...this.tasks.values()];\n }\n\n /**\n * Apply `taskId`'s state -> `to`, merging `patch` into the record. Throws\n * {@link IllegalTaskTransitionError} if the move isn't legal per\n * `TASK_TRANSITIONS`, and if the task doesn't exist at all.\n */\n transition(taskId: string, to: TaskState, patch: Partial<Omit<TaskRecord, 'taskId' | 'state'>> = {}): TaskRecord {\n const record = this.tasks.get(taskId);\n if (!record) {\n throw new Error(`unknown taskId: ${taskId}`);\n }\n if (!canTransition(record.state, to)) {\n throw new IllegalTaskTransitionError(taskId, record.state, to);\n }\n const updated: TaskRecord = {\n ...record,\n ...patch,\n state: to,\n updatedAt: new Date().toISOString(),\n };\n this.tasks.set(taskId, updated);\n return updated;\n }\n\n /**\n * See {@link TaskStore.setPendingApprovalId}'s own doc comment for the\n * full rationale. State-guarded (S3 hardening): a write only applies while\n * `taskId` is still `AwaitApproval` — mirrors `SqliteTaskStore`'s own `AND\n * state = 'AwaitApproval'` CAS predicate (`sqlite-task-store.ts`) for\n * symmetry between the two reference implementations, guarding against a\n * laggard caller resurrecting a pending id after the task already left\n * `AwaitApproval` (e.g. a queued/delayed `task.await_approval` processed\n * after a real `approveTask`/`rejectTask` already transitioned it\n * elsewhere). A non-matching call is a no-op: returns the record exactly\n * as it currently stands, not the caller's requested (rejected) value.\n */\n setPendingApprovalId(taskId: string, pendingApprovalId: string | undefined): TaskRecord | undefined {\n const record = this.tasks.get(taskId);\n if (!record) return undefined;\n if (record.state !== 'AwaitApproval') return record;\n const updated: TaskRecord = { ...record, pendingApprovalId, updatedAt: new Date().toISOString() };\n this.tasks.set(taskId, updated);\n return updated;\n }\n}\n","/**\n * WS-native ping/pong liveness check (pinned here per the M1-2 task brief,\n * not in docs/protocol.md — this is a server implementation detail, not a\n * wire message). The server pings every `intervalMs` (default 30s);\n * `ws`'s WebSocket automatically replies to a protocol-level ping with a\n * protocol-level pong on any spec-compliant peer, so a healthy connection\n * just keeps ticking. After `maxMissedPongs` (default 2) consecutive\n * unanswered pings, the connection is terminated.\n *\n * Deliberately decoupled from the real `ws` package's `WebSocket` type (only\n * the four methods below are used) so the scheduling logic can be unit\n * tested with a plain stub + fake timers instead of a real socket — a real\n * peer auto-pongs, so there's no way to exercise \"missed pong\" through an\n * actual WS round-trip in a test.\n */\nexport interface HeartbeatSocket {\n ping(): void;\n terminate(): void;\n on(event: 'pong', listener: () => void): unknown;\n off(event: 'pong', listener: () => void): unknown;\n}\n\nexport interface HeartbeatOptions {\n intervalMs?: number;\n maxMissedPongs?: number;\n}\n\nexport interface Heartbeat {\n /** Stop the ping timer and detach the pong listener (e.g. on normal connection close). */\n stop(): void;\n}\n\nconst DEFAULT_INTERVAL_MS = 30_000;\nconst DEFAULT_MAX_MISSED_PONGS = 2;\n\nexport function startHeartbeat(ws: HeartbeatSocket, opts: HeartbeatOptions = {}): Heartbeat {\n const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;\n const maxMissedPongs = opts.maxMissedPongs ?? DEFAULT_MAX_MISSED_PONGS;\n\n let awaitingPong = false;\n let missed = 0;\n\n const onPong = () => {\n awaitingPong = false;\n missed = 0;\n };\n ws.on('pong', onPong);\n\n const timer: ReturnType<typeof setInterval> = setInterval(() => {\n if (awaitingPong) {\n missed++;\n if (missed >= maxMissedPongs) {\n clearInterval(timer);\n ws.terminate();\n return;\n }\n }\n awaitingPong = true;\n ws.ping();\n }, intervalMs);\n timer.unref?.();\n\n return {\n stop(): void {\n clearInterval(timer);\n ws.off('pong', onPong);\n },\n };\n}\n","import type { IncomingMessage, Server as HttpServer } from 'node:http';\nimport type { Duplex } from 'node:stream';\nimport { CAPABILITY_FLAGS, decodeEnvelope, PROTOCOL_VERSION, type ConnHelloPayload } from '@byok-sdk/protocol';\nimport { WebSocketServer, type RawData, type WebSocket } from 'ws';\nimport { authenticateBearer, type AuthDeps, type AuthenticatedDevice } from './auth';\nimport { startHeartbeat, type Heartbeat } from './heartbeat';\nimport type { ConnectionHub } from './hub';\n\nconst WS_PATH = '/byok/ws';\n\n/**\n * Capability flags this server implements. Unlike the M0 server, `blob-upload`\n * is no longer withheld — the blob store (§7, `blob-store.ts`) is now\n * implemented, so both flags are advertised.\n */\nconst SUPPORTED_CAPABILITIES: string[] = [...CAPABILITY_FLAGS];\n\nfunction matchesWsPath(url: string): boolean {\n return url.split('?')[0] === WS_PATH;\n}\n\n/**\n * Reject an upgrade request with a plain HTTP response, mirroring `ws`'s own\n * internal `abortHandshake` helper: write the full response via `.end()` (so\n * it's flushed before closing) and only `destroy()` once `'finish'` fires,\n * rather than destroying immediately after `write()` and risking the client\n * seeing a reset instead of the response.\n */\nfunction rejectUpgrade(socket: Duplex, code: number, message: string): void {\n const body = message;\n const headers = [\n `HTTP/1.1 ${code} ${message}`,\n 'Connection: close',\n 'Content-Type: text/plain',\n `Content-Length: ${Buffer.byteLength(body)}`,\n '',\n '',\n ].join('\\r\\n');\n socket.once('finish', () => socket.destroy());\n socket.end(headers + body);\n}\n\nfunction toDecodable(data: RawData): string | Uint8Array {\n if (typeof data === 'string') return data;\n if (Array.isArray(data)) return new Uint8Array(Buffer.concat(data));\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n return data; // Buffer, which is a Uint8Array\n}\n\ninterface AttachDeps extends AuthDeps {\n hub: ConnectionHub;\n productId: string;\n /** WS-native ping interval, ms. Defaults inside `heartbeat.ts` (30s) if omitted. */\n heartbeatIntervalMs?: number;\n}\n\n/**\n * Wire up the `GET /byok/ws` upgrade on a raw Node HTTP server (the one\n * `@hono/node-server`'s `serve()` returns). Auth happens on the upgrade\n * request itself via `Authorization: Bearer <accessToken>` (a JWT minted by\n * `/byok/pair` or `/byok/token` — Auth v2, §6); an invalid, expired, or\n * revoked token gets a 401 and the socket is destroyed. Handshake\n * (`conn.hello` -> `conn.ack`) happens on the first WS message once\n * upgraded.\n */\nexport function attachWebSocket(server: HttpServer, deps: AttachDeps): void {\n const wss = new WebSocketServer({ noServer: true });\n\n server.on('upgrade', (req: IncomingMessage, socket, head) => {\n if (!matchesWsPath(req.url ?? '')) return; // not ours; leave it for any other listener\n\n void (async () => {\n const principal = await authenticateBearer(req.headers.authorization, deps);\n if (!principal) {\n rejectUpgrade(socket, 401, 'Unauthorized');\n return;\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n handleConnection(ws, principal, deps);\n });\n })();\n });\n}\n\nfunction handleConnection(ws: WebSocket, principal: AuthenticatedDevice, deps: AttachDeps): void {\n const { deviceId } = principal;\n let helloReceived = false;\n let heartbeat: Heartbeat | undefined;\n\n ws.once('message', (data: RawData) => {\n let envelope;\n try {\n envelope = decodeEnvelope(toDecodable(data));\n } catch {\n ws.close(1002, 'expected conn.hello');\n return;\n }\n if (envelope.type !== 'conn.hello') {\n ws.close(1002, 'expected conn.hello');\n return;\n }\n\n const payload: ConnHelloPayload = envelope.payload;\n if (!payload.protocolVersions.includes(PROTOCOL_VERSION)) {\n ws.close(1002, 'unsupported protocol version');\n return;\n }\n // One daemon process is always scoped to one product (see plan: \"一产品\n // 一 daemon 进程\"); a mismatched productId means this connection is for a\n // different embedding SaaS than this server instance serves.\n if (payload.productId !== deps.productId) {\n ws.close(1002, 'productId mismatch');\n return;\n }\n // S1: the instance check above says \"this server serves that product\";\n // this one says \"the DEVICE was paired into that product\". They are\n // different facts — a device paired under one product's claims must not\n // register a connection as another, even on a server instance that\n // happens to serve the announced one. Checked here, before\n // `registerConnection`, so a mismatched daemon never reaches the hub.\n if (payload.productId !== principal.productId) {\n ws.close(1002, 'productId does not match the device record');\n return;\n }\n if (payload.deviceId !== deviceId) {\n ws.close(1002, 'deviceId does not match authenticated token');\n return;\n }\n\n helloReceived = true;\n // M5 (hello-capability plumbing): previously only `runtimes` was\n // forwarded — `payload.capabilities` was silently ignored end to end.\n deps.hub.registerConnection(deviceId, ws, payload.runtimes, payload.capabilities);\n deps.hub.sendConnAck(deviceId, SUPPORTED_CAPABILITIES);\n // Reconnection procedure step 3 (§9): redeliver anything still relevant\n // sent since the daemon's last-seen `seq`. Omitted on a device's\n // first-ever connection (no cursor to redeliver from).\n if (payload.cursor !== undefined) {\n deps.hub.redeliverAfterReconnect(deviceId, payload.cursor);\n }\n\n heartbeat = startHeartbeat(ws, { intervalMs: deps.heartbeatIntervalMs });\n\n ws.on('message', (msgData: RawData) => {\n let msg;\n try {\n msg = decodeEnvelope(toDecodable(msgData));\n } catch (err) {\n console.warn(`[byok/server] dropping unparsable frame from device ${deviceId}:`, err);\n return;\n }\n deps.hub.handleInbound(deviceId, msg);\n });\n });\n\n ws.on('close', () => {\n heartbeat?.stop();\n if (helloReceived) {\n deps.hub.handleDisconnect(deviceId, ws);\n }\n });\n}\n","import { chmodSync, existsSync, mkdirSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname } from 'node:path';\nimport type { DatabaseSync, DatabaseSyncOptions } from 'node:sqlite';\n\n/**\n * Thrown when `node:sqlite` isn't available in the running Node.js binary.\n * `node:sqlite` shipped in Node.js 22.5.0 (https://nodejs.org/api/sqlite.html)\n * and remains marked experimental there (an `ExperimentalWarning` on stderr\n * is expected and harmless — not an error). The SQLite-backed reference\n * stores in this package (`SqliteTaskStore`, `SqliteBlobStore`) deliberately\n * depend on nothing else — no `better-sqlite3` or other native module —\n * because staying at zero native dependencies is required to keep\n * `@byok-sdk/server` trivially packageable across platforms. The tradeoff is\n * that these stores simply don't work below Node 22.5; this error says so\n * clearly and up front, instead of letting a cryptic `Cannot find module\n * 'node:sqlite'` surface from deep inside a query.\n */\nexport class SqliteUnavailableError extends Error {\n constructor(cause: unknown) {\n super(\n 'node:sqlite is unavailable in this Node.js runtime. The SQLite-backed reference ' +\n 'stores (SqliteTaskStore/SqliteBlobStore) require Node.js 22.5+ with the built-in ' +\n '`node:sqlite` module (no native dependency is used or allowed here). Upgrade Node.js, ' +\n 'or use the default InMemoryTaskStore / LocalDiskBlobStore instead.',\n );\n this.name = 'SqliteUnavailableError';\n this.cause = cause;\n }\n}\n\n/** `node:sqlite`'s minimum Node.js version (https://nodejs.org/api/sqlite.html). */\nconst MIN_NODE_MAJOR = 22;\nconst MIN_NODE_MINOR = 5;\n\n/**\n * Whether `nodeVersion` (a `major.minor.patch` string shaped like\n * `process.versions.node`) is new enough to have `node:sqlite` at all.\n * Exported only for this package's own tests to exercise the guard\n * deterministically (this dev/CI machine is already on a qualifying Node,\n * so the real \"unavailable\" path can't be triggered end-to-end here) — not\n * re-exported from `index.ts`, so not part of the public package API.\n * Unparsable input returns `true` (don't false-negative on a version string\n * shape this hasn't seen before): `loadSqliteModule`'s own `require` call is\n * the real, authoritative gate; this check only exists to turn the common\n * case (a too-old Node) into a clear message instead of a cryptic one.\n *\n * Not a sufficient capability check on its own: `node:sqlite` shipped in\n * Node 22.5.0 behind the `--experimental-sqlite` flag and only became usable\n * unflagged in a later 22.x release (https://nodejs.org/api/sqlite.html), so\n * a runtime that satisfies this version check can still fail to actually\n * load the module. Use {@link isSqliteAvailable} when the question is \"can I\n * use `node:sqlite` right now\", not \"is the Node version new enough for it\n * to exist at all\".\n */\nexport function isSqliteCapableNodeVersion(nodeVersion: string): boolean {\n const [majorStr, minorStr] = nodeVersion.split('.');\n const major = Number(majorStr);\n const minor = Number(minorStr);\n if (!Number.isFinite(major) || !Number.isFinite(minor)) return true;\n return major > MIN_NODE_MAJOR || (major === MIN_NODE_MAJOR && minor >= MIN_NODE_MINOR);\n}\n\nlet sqliteModule: typeof import('node:sqlite') | undefined;\n\n/**\n * Synchronously load `node:sqlite` via `createRequire` rather than a dynamic\n * `import()`. Every reference store in this package (`InMemoryTaskStore`,\n * `LocalDiskBlobStore`) constructs synchronously via `new`, and\n * `node:sqlite`'s own API (`DatabaseSync`) is itself fully synchronous — an\n * async factory here would be the odd one out and would leak into every\n * caller (`createByokServer`, the example, tests). `node:sqlite` is a\n * built-in, so `require('node:sqlite')` resolves it even from this\n * `\"type\": \"module\"` package. Throws {@link SqliteUnavailableError} (not a\n * raw `MODULE_NOT_FOUND`/version-mismatch trace) when it can't be loaded —\n * checking the Node version first (see {@link isSqliteCapableNodeVersion})\n * gives the common \"Node too old\" case a specific, actionable message\n * before even attempting the `require`.\n */\nfunction loadSqliteModule(): typeof import('node:sqlite') {\n if (sqliteModule) return sqliteModule;\n if (!isSqliteCapableNodeVersion(process.versions.node)) {\n throw new SqliteUnavailableError(\n new Error(\n `node:sqlite requires Node.js ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR}+; detected ${process.versions.node}`,\n ),\n );\n }\n try {\n const require = createRequire(import.meta.url);\n sqliteModule = require('node:sqlite') as typeof import('node:sqlite');\n return sqliteModule;\n } catch (err) {\n throw new SqliteUnavailableError(err);\n }\n}\n\n/**\n * Whether `node:sqlite` can ACTUALLY be loaded right now — the authoritative\n * capability check, in contrast to {@link isSqliteCapableNodeVersion}'s\n * version-string heuristic. This is what this package's own test suite uses\n * to decide whether to skip the SQLite-backed reference-store tests (rather\n * than fail them), and what anything else should call before assuming a\n * `SqliteTaskStore`/`SqliteBlobStore` can be constructed: it attempts the\n * real `require('node:sqlite')` (via {@link loadSqliteModule}, memoized) and\n * reports whether that succeeded, so it agrees with reality regardless of\n * whether the current runtime is old, too-new-but-flagged, or fully capable.\n */\nexport function isSqliteAvailable(): boolean {\n try {\n loadSqliteModule();\n return true;\n } catch {\n return false;\n }\n}\n\n/** Busy-timeout (ms) shared by both SQLite stores: long enough that two same-process `DatabaseSync` connections opened against the same file back-to-back (e.g. a test proving restart-safety by opening a \"second instance\" against the same path) don't spuriously throw `SQLITE_BUSY` under momentary write-lock contention. */\nconst DEFAULT_BUSY_TIMEOUT_MS = 5_000;\n\n/** POSIX mode both SQLite reference-store db files (and their WAL/SHM siblings) are secured to — owner read/write only, no group/other access. Matches the convention the client-side device credential store already uses (`packages/client/src/daemon/store.ts`). */\nconst SECURE_FILE_MODE = 0o600;\n/** POSIX mode applied to a freshly-created directory holding a SQLite reference-store db file — owner read/write/execute only. Same rationale as {@link SECURE_FILE_MODE}. */\nconst SECURE_DIR_MODE = 0o700;\n\n/**\n * Open (or create) a `node:sqlite` `DatabaseSync` at `path`, applying the\n * pragmas both reference SQLite stores share: WAL journaling for a\n * file-backed database (allows a reader and a writer to proceed without\n * blocking each other, and is what makes \"close instance A, open instance B\n * on the same file\" — the restart-safety story — reliable) and a busy\n * timeout (see {@link DEFAULT_BUSY_TIMEOUT_MS}). WAL is skipped for\n * `:memory:`, where it's meaningless. For a file-backed path whose parent\n * directory doesn't exist yet, creates it (recursively) at\n * {@link SECURE_DIR_MODE} — mirroring the client-side device store's\n * convention of never leaving a credential directory at a permissive\n * default mode. Throws {@link SqliteUnavailableError} if `node:sqlite`\n * itself can't be loaded (Node <22.5, or a flagged intermediate 22.x — see\n * {@link isSqliteAvailable}) — callers don't need their own guard for that;\n * this is the single choke point.\n */\nexport function openSqliteDatabase(path: string, options?: DatabaseSyncOptions): DatabaseSync {\n const { DatabaseSync } = loadSqliteModule();\n if (path !== ':memory:') {\n mkdirSync(dirname(path), { recursive: true, mode: SECURE_DIR_MODE });\n }\n const db = new DatabaseSync(path, { timeout: DEFAULT_BUSY_TIMEOUT_MS, ...options });\n if (path !== ':memory:') {\n db.exec('PRAGMA journal_mode = WAL;');\n }\n return db;\n}\n\n/**\n * Restrict `dbPath` and its WAL/SHM sibling files (`<path>-wal`,\n * `<path>-shm` — created by SQLite itself once WAL mode is active and a\n * write has happened) to owner-only read/write ({@link SECURE_FILE_MODE}).\n * Both SQLite reference stores hold sensitive bytes on disk with no other\n * access-control layer of their own — `SqliteBlobStore` an HMAC signing\n * secret plus arbitrary uploaded blob content, `SqliteTaskStore` task\n * instructions and device/session refs — so a real (non-`:memory:`)\n * database file must not be left at whatever permissive mode the process'\n * umask would otherwise give it.\n *\n * Call this AFTER the schema has been created against `dbPath` (so the\n * WAL/SHM files, which SQLite creates lazily on first write, already exist)\n * — a sibling file that doesn't exist yet is silently skipped rather than\n * treated as an error. No-op for `:memory:`.\n */\nexport function secureSqliteFilePermissions(dbPath: string): void {\n if (dbPath === ':memory:') return;\n for (const candidate of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {\n if (existsSync(candidate)) {\n chmodSync(candidate, SECURE_FILE_MODE);\n }\n }\n}\n","import { canTransition, type TaskState } from '@byok-sdk/protocol';\nimport type { DatabaseSync, StatementSync } from 'node:sqlite';\nimport { openSqliteDatabase, secureSqliteFilePermissions } from './sqlite-support';\nimport { IllegalTaskTransitionError, type CreateTaskInput, type TaskRecord, type TaskStore } from './task-store';\nimport type { TaskResult } from './types';\n\nexport interface SqliteTaskStoreOptions {\n /**\n * Database file path. Use `:memory:` to exercise the SQLite code path\n * without a temp file (e.g. schema/query correctness tests) — but note\n * that defeats the entire point of this store (restart-safety), since an\n * in-memory SQLite database vanishes with the process exactly like\n * `InMemoryTaskStore` does. Real persistence requires a real file path.\n */\n path: string;\n}\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS tasks (\n task_id TEXT PRIMARY KEY,\n state TEXT NOT NULL,\n instruction TEXT NOT NULL,\n runtime TEXT,\n policy_json TEXT NOT NULL,\n device_id TEXT,\n session_ref TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n result_json TEXT,\n pending_approval_id TEXT,\n claimed_runtime TEXT,\n claimed_runtime_capabilities_json TEXT\n);\n`;\n\n/**\n * M5 (approval targeting; claimed runtime) / S0 (claim-time capability\n * snapshot): this repo has NO migration\n * machinery — every table is `CREATE TABLE IF NOT EXISTS` only (no `PRAGMA\n * user_version`, no prior `ALTER TABLE` precedent), which is a no-op against\n * a database file created by an OLDER version of this store that's missing a\n * column added later (`pending_approval_id`, `claimed_runtime`). `SCHEMA`\n * above already includes every CURRENT column for a brand-new database; this\n * is the idempotent after-the-fact fallback for a pre-existing file that\n * predates one (or more) of them: inspect `PRAGMA table_info(tasks)` and\n * `ALTER TABLE ... ADD COLUMN` whatever's missing, independently per column —\n * a file missing only the NEWEST column (e.g. one created by a build that\n * already had `pending_approval_id`) picks up just `claimed_runtime`, exactly\n * as cleanly as a much older file missing both. Add a new entry here (name a\n * nullable column so an `ADD COLUMN` with no default is valid) the next time\n * a column is added — this hook is written to be reused, not re-derived.\n */\nconst ADDITIVE_COLUMNS: ReadonlyArray<{ name: string; ddl: string }> = [\n { name: 'pending_approval_id', ddl: 'ALTER TABLE tasks ADD COLUMN pending_approval_id TEXT' },\n { name: 'claimed_runtime', ddl: 'ALTER TABLE tasks ADD COLUMN claimed_runtime TEXT' },\n // S0 (GAP-002): the claim-time `RuntimeCapabilities` snapshot, stored as\n // the JSON text of that closed, protocol-validated object (same idiom as\n // `policy_json`/`result_json` above — no column-per-flag, so a future\n // capability flag needs no further schema move). NULL means \"no snapshot\",\n // which the steer gate (`hub.ts`) treats as a refusal, never as a default.\n {\n name: 'claimed_runtime_capabilities_json',\n ddl: 'ALTER TABLE tasks ADD COLUMN claimed_runtime_capabilities_json TEXT',\n },\n];\n\nfunction currentTaskColumns(db: DatabaseSync): Set<string> {\n return new Set((db.prepare('PRAGMA table_info(tasks)').all() as Array<{ name: string }>).map((c) => c.name));\n}\n\n/**\n * S5 hardening: two processes (or two `SqliteTaskStore` instances in this\n * one) can both construct against the same pre-existing file at close to the\n * same instant, both see a given column missing via the `PRAGMA table_info`\n * read below, and both attempt the same `ALTER TABLE ... ADD COLUMN` —\n * SQLite allows only one to actually add it; the loser's `db.exec` throws\n * `duplicate column name`. That failure means the OTHER writer already won —\n * the column now genuinely exists, which is exactly the end state this\n * function is trying to reach — so it's caught here, the column list is\n * re-inspected fresh, and this function proceeds normally (no throw) once\n * confirmed. Anything else (a real schema problem, a disk error, a\n * `duplicate column name` for a DIFFERENT column than expected) is rethrown\n * unchanged — this only swallows the exact race this is written for.\n *\n * Exported only for this package's own tests to exercise the race\n * deterministically (mirrors `sqlite-support.ts`'s\n * `isSqliteCapableNodeVersion` convention) — not re-exported from\n * `index.ts`, so not part of the public package API.\n */\nexport function ensureAdditiveColumns(db: DatabaseSync): void {\n const existing = currentTaskColumns(db);\n for (const column of ADDITIVE_COLUMNS) {\n if (existing.has(column.name)) continue;\n try {\n db.exec(column.ddl);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (!/duplicate column name/i.test(message)) {\n throw err;\n }\n if (!currentTaskColumns(db).has(column.name)) {\n // Genuinely not there despite the \"duplicate column\" message (an\n // unexpected/unrelated failure) — don't swallow it.\n throw err;\n }\n // Lost the race, but the column is confirmed present now — the\n // outcome this function exists to reach either way. Move on to the\n // next column.\n }\n }\n}\n\nfunction rowToRecord(row: Record<string, unknown>): TaskRecord {\n const resultJson = row.result_json as string | null;\n const claimedRuntimeCapabilitiesJson = row.claimed_runtime_capabilities_json as string | null;\n return {\n taskId: row.task_id as string,\n state: row.state as TaskState,\n instruction: row.instruction as string,\n runtime: ((row.runtime as string | null) ?? undefined) as TaskRecord['runtime'],\n policy: JSON.parse(row.policy_json as string) as TaskRecord['policy'],\n deviceId: (row.device_id as string | null) ?? undefined,\n sessionRef: (row.session_ref as string | null) ?? undefined,\n createdAt: row.created_at as string,\n updatedAt: row.updated_at as string,\n result: resultJson ? (JSON.parse(resultJson) as TaskResult) : undefined,\n pendingApprovalId: (row.pending_approval_id as string | null) ?? undefined,\n claimedRuntime: ((row.claimed_runtime as string | null) ?? undefined) as TaskRecord['claimedRuntime'],\n claimedRuntimeCapabilities: claimedRuntimeCapabilitiesJson\n ? (JSON.parse(claimedRuntimeCapabilitiesJson) as TaskRecord['claimedRuntimeCapabilities'])\n : undefined,\n };\n}\n\n/**\n * Persistent {@link TaskStore} backed by the Node.js built-in `node:sqlite`\n * module — no native dependency (`sqlite-support.ts`'s doc comment explains\n * why that's a hard requirement here). A fresh instance pointed at the same\n * database file recovers every task's full RECORD (instruction, policy,\n * device/session refs, result) exactly as `InMemoryTaskStore` would have\n * held it in memory — this is the M3 \"task records survive a process\n * restart\" story.\n *\n * Scope of that claim, precisely: this is RECORD persistence, not live\n * active-task recovery. A fresh `ConnectionHub` (`hub.ts`) wired to a\n * reopened store starts with empty runtimes/result-promises/event-queues/\n * device-registry/outboxes — so a task that was `Running` at restart comes\n * back as a `Running` *record* you can read and further `transition()`, not\n * as a task with its device/runtime connection reattached that will go on\n * to actually produce more events. Recovering/resuming an in-flight task's\n * live connection is a larger feature and out of scope here.\n *\n * Every write goes through a compare-and-set `UPDATE ... WHERE task_id = ?\n * AND state = ?` (the state {@link transition} just validated against), not\n * an unconditional update: two connections racing on the same task (both\n * reading e.g. `Running`, both independently validating a different target\n * state) can't both commit, which would otherwise let the later write\n * silently perform an illegal transition — including terminal -> terminal —\n * that neither validation call would have allowed had it seen the other's\n * write first. A lost compare-and-set re-reads the row and either\n * re-validates the requested move against the state that actually won, or\n * throws {@link IllegalTaskTransitionError} against it.\n *\n * Requires Node.js 22.5+ (`node:sqlite`'s minimum); constructing this on an\n * older/unsupported runtime throws `SqliteUnavailableError`\n * (`sqlite-support.ts`) with a clear message rather than a cryptic \"Cannot\n * find module\" trace.\n *\n * Enforces the exact same `TASK_TRANSITIONS`/`canTransition` state machine\n * as `InMemoryTaskStore`, via the same {@link IllegalTaskTransitionError}.\n */\nexport class SqliteTaskStore implements TaskStore {\n private readonly db: DatabaseSync;\n private readonly insertStmt: StatementSync;\n private readonly updateStmt: StatementSync;\n private readonly selectStmt: StatementSync;\n private readonly selectAllStmt: StatementSync;\n private readonly updatePendingApprovalIdStmt: StatementSync;\n\n constructor(opts: SqliteTaskStoreOptions) {\n this.db = openSqliteDatabase(opts.path);\n this.db.exec(SCHEMA);\n // M5 (approval targeting): idempotent — a brand-new database already got\n // every column from SCHEMA above; this only ever does real work against\n // a pre-existing file created by an older version of this store. See\n // `ensureAdditiveColumns`'s own doc comment.\n ensureAdditiveColumns(this.db);\n secureSqliteFilePermissions(opts.path);\n this.insertStmt = this.db.prepare(\n `INSERT INTO tasks\n (task_id, state, instruction, runtime, policy_json, device_id, session_ref, created_at, updated_at, result_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n // A separate UPDATE-in-place (rather than reusing `INSERT OR REPLACE`,\n // which is a delete+insert under the hood) so a transitioned task keeps\n // its original `rowid` — `list()`'s `ORDER BY rowid ASC` must stay in\n // creation order across transitions, mirroring `InMemoryTaskStore`'s\n // `Map`, where `.set()` on an existing key never moves it in iteration\n // order either.\n //\n // `AND state = ?` makes this a compare-and-set rather than an\n // unconditional update: `transition()` binds the FROM-state it just\n // validated against as the last parameter, so the write only lands if\n // the row is still in that state — see `transition()`'s doc comment.\n this.updateStmt = this.db.prepare(\n `UPDATE tasks SET\n state = ?, instruction = ?, runtime = ?, policy_json = ?, device_id = ?,\n session_ref = ?, created_at = ?, updated_at = ?, result_json = ?, pending_approval_id = ?,\n claimed_runtime = ?, claimed_runtime_capabilities_json = ?\n WHERE task_id = ? AND state = ?`,\n );\n this.selectStmt = this.db.prepare('SELECT * FROM tasks WHERE task_id = ?');\n this.selectAllStmt = this.db.prepare('SELECT * FROM tasks ORDER BY rowid ASC');\n // M5: the one narrow same-state field update — see\n // `TaskStore.setPendingApprovalId`'s own doc comment for why this can't\n // go through `updateStmt`/`transition` (no state change to validate\n // against).\n //\n // S3 hardening: `AND state = 'AwaitApproval'` makes this a CAS guard\n // (mirroring `updateStmt`'s own `AND state = ?` above, just against a\n // fixed literal rather than a bound from-state, since this method never\n // changes `state` itself) — without it, a laggard writer (e.g. a\n // delayed/queued `task.await_approval` processed after the task already\n // left `AwaitApproval` via a real `approveTask`/`rejectTask` elsewhere)\n // could resurrect a pending id for a task that has already moved on.\n // Zero rows affected is a legitimate no-op outcome, not an error — see\n // `setPendingApprovalId` below.\n this.updatePendingApprovalIdStmt = this.db.prepare(\n \"UPDATE tasks SET pending_approval_id = ?, updated_at = ? WHERE task_id = ? AND state = 'AwaitApproval'\",\n );\n }\n\n create(input: CreateTaskInput): TaskRecord {\n const now = new Date().toISOString();\n const record: TaskRecord = {\n taskId: input.taskId,\n state: 'Offered',\n instruction: input.instruction,\n runtime: input.runtime,\n policy: input.policy,\n deviceId: input.deviceId,\n sessionRef: input.sessionRef,\n createdAt: now,\n updatedAt: now,\n };\n this.insertStmt.run(\n record.taskId,\n record.state,\n record.instruction,\n record.runtime ?? null,\n JSON.stringify(record.policy),\n record.deviceId ?? null,\n record.sessionRef ?? null,\n record.createdAt,\n record.updatedAt,\n record.result ? JSON.stringify(record.result) : null,\n );\n return record;\n }\n\n get(taskId: string): TaskRecord | undefined {\n const row = this.selectStmt.get(taskId);\n return row ? rowToRecord(row) : undefined;\n }\n\n list(): TaskRecord[] {\n return this.selectAllStmt.all().map(rowToRecord);\n }\n\n /**\n * Apply `taskId`'s state -> `to`, merging `patch` into the record. Throws\n * {@link IllegalTaskTransitionError} if the move isn't legal per\n * `TASK_TRANSITIONS`, and if the task doesn't exist at all — identical\n * contract and error shapes to `InMemoryTaskStore.transition`.\n *\n * Implemented as a compare-and-set retry loop rather than a single\n * read-validate-write, because two separate connections (two processes,\n * or two `SqliteTaskStore` instances in this one) can both read the same\n * current state and both validate a move against it before either writes.\n * An unconditional `UPDATE` would let whichever commits last silently win\n * — including an illegal terminal -> terminal transition neither\n * validation call would have allowed with up-to-date information. Each\n * iteration here reads the CURRENT state fresh, validates `to` against\n * it, then writes with `WHERE state = <the state just validated>`\n * (`updateStmt`). If zero rows changed, some other writer committed\n * between this read and this write, so the loop re-reads and either\n * re-validates `to` against whatever the state actually is now, or throws\n * {@link IllegalTaskTransitionError} against it — the same outcome a\n * caller would get if it happened to run a moment later.\n */\n transition(taskId: string, to: TaskState, patch: Partial<Omit<TaskRecord, 'taskId' | 'state'>> = {}): TaskRecord {\n // Bounded CAS retry: each miss means another writer committed between our\n // read and write. A legal state cycle (e.g. Running <-> AwaitApproval)\n // driven continuously by another process could otherwise starve this call\n // forever, so cap the attempts and surface a contention error rather than\n // spin unbounded. The cap is far above any real contention depth.\n const MAX_CAS_ATTEMPTS = 100;\n for (let attempt = 0; attempt < MAX_CAS_ATTEMPTS; attempt++) {\n const record = this.get(taskId);\n if (!record) {\n throw new Error(`unknown taskId: ${taskId}`);\n }\n if (!canTransition(record.state, to)) {\n throw new IllegalTaskTransitionError(taskId, record.state, to);\n }\n const updated: TaskRecord = {\n ...record,\n ...patch,\n state: to,\n updatedAt: new Date().toISOString(),\n };\n const { changes } = this.updateStmt.run(\n updated.state,\n updated.instruction,\n updated.runtime ?? null,\n JSON.stringify(updated.policy),\n updated.deviceId ?? null,\n updated.sessionRef ?? null,\n updated.createdAt,\n updated.updatedAt,\n updated.result ? JSON.stringify(updated.result) : null,\n updated.pendingApprovalId ?? null,\n updated.claimedRuntime ?? null,\n updated.claimedRuntimeCapabilities ? JSON.stringify(updated.claimedRuntimeCapabilities) : null,\n updated.taskId,\n record.state,\n );\n if (Number(changes) > 0) {\n return updated;\n }\n // Lost the compare-and-set: `taskId` changed state under us between\n // the read and the write above. Loop back and re-validate against\n // the (now current) real state instead of returning stale success.\n }\n throw new Error(\n `transition contention: taskId ${taskId} kept changing state under concurrent writers after ${MAX_CAS_ATTEMPTS} attempts`,\n );\n }\n\n /**\n * See {@link TaskStore.setPendingApprovalId}'s own doc comment for the\n * full rationale, and `updatePendingApprovalIdStmt`'s own doc comment\n * (constructor, above) for the S3 CAS-guard rationale. The guarded\n * statement affecting 0 rows means `taskId` is no longer `AwaitApproval`\n * (or vanished) as of the write — a legitimate no-op, not an error: this\n * method never throws for a state mismatch (best-effort bookkeeping, same\n * as its unconditional pre-S3 form). Returns a FRESH read in that case\n * rather than the caller's now-stale pre-write snapshot, so a caller sees\n * what's actually stored.\n */\n setPendingApprovalId(taskId: string, pendingApprovalId: string | undefined): TaskRecord | undefined {\n const record = this.get(taskId);\n if (!record) return undefined;\n const updatedAt = new Date().toISOString();\n const { changes } = this.updatePendingApprovalIdStmt.run(pendingApprovalId ?? null, updatedAt, taskId);\n if (Number(changes) === 0) {\n return this.get(taskId);\n }\n return { ...record, pendingApprovalId, updatedAt };\n }\n\n /**\n * Close the underlying database connection. Not part of the `TaskStore`\n * interface (an in-memory store has nothing to close) — call this\n * explicitly when a store instance is done, e.g. before opening a second\n * instance against the same file, or on process shutdown.\n */\n close(): void {\n this.db.close();\n }\n}\n","import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';\nimport type { DatabaseSync, StatementSync } from 'node:sqlite';\nimport {\n BlobDeclarationConflictError,\n type BlobStore,\n type CreateUploadInput,\n type ReadContentResult,\n type WriteContentResult,\n} from './blob-store';\nimport { openSqliteDatabase, secureSqliteFilePermissions } from './sqlite-support';\n\nexport interface SqliteBlobStoreOptions {\n /**\n * Database file path. Use `:memory:` to exercise the SQLite code path\n * without persistence — defeats this store's whole purpose (same caveat\n * as `SqliteTaskStore`'s `:memory:` option); real restart-safety requires\n * a real file path.\n */\n path: string;\n /** How long a presigned upload/download URL stays valid, ms. Default 15 minutes — same default as `LocalDiskBlobStore`. */\n urlTtlMs?: number;\n /**\n * HMAC signing key for presigned URLs. Defaults to a key generated once\n * and persisted in this same database (a `meta` table) — so, unlike\n * `LocalDiskBlobStore`'s fresh-per-instance `randomBytes(32)` (fine there\n * only because its metadata doesn't survive a restart either), the\n * default here is *already* stable across restarts: a URL signed by one\n * process instance still verifies against a later instance pointed at\n * the same database file. Pass this explicitly only if the key needs to\n * live outside the database (e.g. shared across multiple database files,\n * or rotated independently of the data).\n */\n signingKey?: Buffer;\n}\n\n/** Exported (only) so `sqlite-blob-store.test.ts` can apply the same schema to a raw `DatabaseSync` connection when testing {@link loadOrCreateSigningSecret}'s concurrency behavior directly. */\nexport const SCHEMA = `\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS blobs (\n blob_id TEXT PRIMARY KEY,\n size INTEGER NOT NULL,\n content_type TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n uploaded INTEGER NOT NULL DEFAULT 0,\n data BLOB\n);\n`;\n\nconst DEFAULT_URL_TTL_MS = 15 * 60 * 1000;\nconst SIGNING_SECRET_META_KEY = 'signing_secret';\n\n/** §7 canonical form: `sha256:<64 lowercase hex>` (finding F9, enforced at the schema level on every inbound request before it reaches any `BlobStore`) — comparison here is a straight string match, mirroring `LocalDiskBlobStore`. */\nfunction sha256Hex(data: Buffer): string {\n return `sha256:${createHash('sha256').update(data).digest('hex')}`;\n}\n\n/** Wrap a `node:sqlite` BLOB read (a `Uint8Array`-shaped value) as a `Buffer` view over the same bytes, no copy. */\nfunction toBuffer(value: unknown): Buffer {\n const view = value as Uint8Array;\n return Buffer.from(view.buffer, view.byteOffset, view.byteLength);\n}\n\ninterface BlobRow {\n size: number;\n content_type: string;\n content_hash: string;\n uploaded: number;\n data: unknown;\n}\n\n/**\n * Atomically load the persisted HMAC signing secret from `db`'s `meta`\n * table, generating and persisting one if none exists yet.\n *\n * Safe under two `DatabaseSync` connections racing on the same file — e.g.\n * two fresh `SqliteBlobStore` instances constructed against a brand-new\n * database at nearly the same moment. Both may see no existing row (via the\n * initial `SELECT`) and both generate a candidate secret, but `INSERT OR\n * IGNORE` guarantees at most one candidate is ever persisted, and —\n * critically — EVERY caller unconditionally re-reads the row afterward and\n * returns THAT value, never its own locally-generated candidate. Without\n * that re-read (the bug this fixes: the previous implementation used\n * `INSERT OR REPLACE` and returned its own candidate unconditionally), a\n * caller whose candidate lost the race would keep using its own discarded\n * value in memory — so a presigned URL it signs would fail to verify\n * against any other instance, which persisted (and uses) the winning value.\n *\n * `generateCandidate` defaults to `randomBytes(32)`; overridable so\n * `sqlite-blob-store.test.ts` can deterministically force the race window —\n * real callers never need to pass it.\n */\nexport function loadOrCreateSigningSecret(\n db: DatabaseSync,\n generateCandidate: () => Buffer = () => randomBytes(32),\n): Buffer {\n const selectStmt = db.prepare('SELECT value FROM meta WHERE key = ?');\n const existing = selectStmt.get(SIGNING_SECRET_META_KEY) as { value: string } | undefined;\n if (existing) return Buffer.from(existing.value, 'hex');\n\n const candidate = generateCandidate();\n db.prepare('INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)').run(\n SIGNING_SECRET_META_KEY,\n candidate.toString('hex'),\n );\n\n // Re-read regardless of whether OUR insert is what actually landed: if a\n // concurrent connection's INSERT committed first, this returns ITS value.\n const persisted = selectStmt.get(SIGNING_SECRET_META_KEY) as { value: string } | undefined;\n if (!persisted) {\n // Unreachable in practice — INSERT OR IGNORE guarantees a row exists by\n // now (ours or a racing connection's). A loud failure here rather than\n // silently generating yet another candidate keeps this from masking a\n // genuinely broken database.\n throw new Error('failed to initialize SQLite blob store signing secret');\n }\n return Buffer.from(persisted.value, 'hex');\n}\n\n/**\n * Persistent {@link BlobStore} backed by `node:sqlite` — no native\n * dependency, same rationale as `SqliteTaskStore` (`sqlite-support.ts`).\n * Metadata AND content bytes both live in the same database file (a `data\n * BLOB` column), so a fresh instance pointed at the same file recovers\n * everything: declared blobs, upload state, and the bytes themselves,\n * byte-for-byte.\n *\n * Presigned URLs use the same HMAC-signed-query-param scheme as\n * `LocalDiskBlobStore` (`/byok/blobs/:id/content?sig=...&exp=...`, verified\n * generically by `http.ts` via {@link verifySignedUrl} regardless of which\n * `BlobStore` is plugged in) — the only difference is where the signing\n * secret comes from; see {@link SqliteBlobStoreOptions.signingKey}.\n *\n * Requires Node.js 22.5+ (`node:sqlite`'s minimum); constructing this on an\n * unsupported runtime throws `SqliteUnavailableError` (`sqlite-support.ts`).\n */\nexport class SqliteBlobStore implements BlobStore {\n private readonly db: DatabaseSync;\n private readonly urlTtlMs: number;\n private readonly secret: Buffer;\n private readonly insertBlobStmt: StatementSync;\n private readonly selectBlobStmt: StatementSync;\n private readonly selectUploadedStmt: StatementSync;\n private readonly writeContentStmt: StatementSync;\n\n constructor(opts: SqliteBlobStoreOptions) {\n this.db = openSqliteDatabase(opts.path);\n this.db.exec(SCHEMA);\n secureSqliteFilePermissions(opts.path);\n this.urlTtlMs = opts.urlTtlMs ?? DEFAULT_URL_TTL_MS;\n this.secret = opts.signingKey ?? loadOrCreateSigningSecret(this.db);\n\n this.insertBlobStmt = this.db.prepare(\n `INSERT INTO blobs (blob_id, size, content_type, content_hash, uploaded, data)\n VALUES (?, ?, ?, ?, 0, NULL)`,\n );\n this.selectBlobStmt = this.db.prepare(\n 'SELECT size, content_type, content_hash, uploaded, data FROM blobs WHERE blob_id = ?',\n );\n this.selectUploadedStmt = this.db.prepare('SELECT uploaded FROM blobs WHERE blob_id = ?');\n this.writeContentStmt = this.db.prepare('UPDATE blobs SET data = ?, uploaded = 1 WHERE blob_id = ?');\n }\n\n async createUpload(input: CreateUploadInput, requestedBlobId?: string): Promise<{ blobId: string; uploadUrl: string }> {\n const blobId = requestedBlobId ?? `blob_${randomUUID()}`;\n const existing = this.selectBlobStmt.get(blobId) as BlobRow | undefined;\n if (existing !== undefined) {\n if (\n existing.size !== input.size ||\n existing.content_type !== input.contentType ||\n existing.content_hash !== input.contentHash\n ) {\n throw new BlobDeclarationConflictError(blobId);\n }\n return { blobId, uploadUrl: this.signUrl(blobId, 'put') };\n }\n this.insertBlobStmt.run(blobId, input.size, input.contentType, input.contentHash);\n return { blobId, uploadUrl: this.signUrl(blobId, 'put') };\n }\n\n async getDownloadUrl(blobId: string): Promise<string | undefined> {\n const row = this.selectUploadedStmt.get(blobId) as { uploaded: number } | undefined;\n if (!row?.uploaded) return undefined;\n return this.signUrl(blobId, 'get');\n }\n\n async exists(blobId: string): Promise<boolean> {\n const row = this.selectUploadedStmt.get(blobId) as { uploaded: number } | undefined;\n return Boolean(row?.uploaded);\n }\n\n verifySignedUrl(blobId: string, action: 'put' | 'get', sig: string, exp: number): boolean {\n if (!Number.isFinite(exp) || Date.now() > exp) return false;\n const expected = this.computeSig(blobId, action, exp);\n const expectedBuf = Buffer.from(expected, 'base64url');\n const actualBuf = Buffer.from(sig, 'base64url');\n if (expectedBuf.length !== actualBuf.length) return false;\n return timingSafeEqual(expectedBuf, actualBuf);\n }\n\n async writeContent(blobId: string, data: Buffer): Promise<WriteContentResult> {\n const row = this.selectBlobStmt.get(blobId) as BlobRow | undefined;\n if (!row) return { ok: false, reason: 'unknown blobId' };\n if (row.uploaded) return { ok: false, reason: 'blob already uploaded' };\n if (data.length !== row.size) {\n return { ok: false, reason: `size mismatch: declared ${row.size}, received ${data.length}` };\n }\n const actualHash = sha256Hex(data);\n if (actualHash !== row.content_hash) {\n return { ok: false, reason: 'contentHash mismatch' };\n }\n this.writeContentStmt.run(data, blobId);\n return { ok: true };\n }\n\n async readContent(blobId: string): Promise<ReadContentResult | undefined> {\n const row = this.selectBlobStmt.get(blobId) as BlobRow | undefined;\n if (!row?.uploaded || row.data === null || row.data === undefined) return undefined;\n return { data: toBuffer(row.data), contentType: row.content_type };\n }\n\n /** Close the underlying database connection — see `SqliteTaskStore.close`'s doc comment; same rationale. */\n close(): void {\n this.db.close();\n }\n\n private computeSig(blobId: string, action: 'put' | 'get', exp: number): string {\n return createHmac('sha256', this.secret).update(`${blobId}:${action}:${exp}`).digest('base64url');\n }\n\n private signUrl(blobId: string, action: 'put' | 'get'): string {\n const exp = Date.now() + this.urlTtlMs;\n const sig = this.computeSig(blobId, action, exp);\n return `/byok/blobs/${blobId}/content?sig=${sig}&exp=${exp}`;\n }\n}\n","import type { Server as HttpServer } from 'node:http';\nimport type { Hono } from 'hono';\nimport { createHmacTokenSigner, DeviceRegistry, NonceStore, type TenantId } from './auth';\nimport { LocalDiskBlobStore } from './blob-store';\nimport { buildHonoApp } from './http';\nimport { ConnectionHub } from './hub';\nimport { PairingManager, type PairingCodeClaims, type PairingCodeInfo } from './pairing';\nimport { RateLimiter } from './rate-limiter';\nimport { InMemoryTaskStore } from './task-store';\nimport { attachWebSocket as attachWsUpgrade } from './ws-server';\nimport type {\n ByokServerEvent,\n CreateByokServerOptions,\n DispatchInput,\n HubStats,\n MachineInfo,\n TaskHandle,\n TaskSnapshot,\n} from './types';\n\nexport type {\n ByokServerEvent,\n CreateByokServerOptions,\n DispatchInput,\n HubStats,\n MachineInfo,\n ServerTaskEvent,\n TaskHandle,\n TaskResult,\n TaskSnapshot,\n} from './types';\nexport type { CreateTaskInput, TaskRecord, TaskStore } from './task-store';\nexport { IllegalTaskTransitionError, InMemoryTaskStore } from './task-store';\n/**\n * M5 (approval targeting, docs/protocol.md §5.3): previously unreachable via\n * this package's public entry point (only importable from the internal\n * `./hub` path) — `TaskHandle.approve`/`reject`'s `opts.approvalId` targeting\n * (`types.ts`) throws this, so a caller needs it exported here to\n * `instanceof`-check/inspect it. See `hub.ts`'s own doc comment for the full\n * staleness semantics.\n */\nexport { StaleApprovalError } from './hub';\n/**\n * S0 (GAP-002): `TaskHandle.steer` (`types.ts`) throws this when the runtime\n * that claimed the task cannot be steered, when the task isn't running, or\n * when it's already terminal — a caller needs the class to `instanceof`-check\n * it and the code union to switch on. See `hub.ts`'s own doc comments for the\n * full gate order and the fail-closed-on-unknown rationale.\n */\nexport { SteerRejectedError } from './hub';\nexport type { SteerRejectionCode } from './hub';\nexport { PairingCodeInvalidError } from './pairing';\nexport type { PairingCodeClaims, PairingCodeInfo } from './pairing';\nexport type {\n AccessTokenClaims,\n AuthenticatedDevice,\n DeviceRecord,\n TenantId,\n TokenSigner,\n} from './auth';\n/**\n * S1: `DeviceRegistry` itself is deliberately NOT exported. Its\n * tenant-scoped surface is reachable through `ByokServer.devices` (below),\n * and the one method that resolves a device without a tenant in scope\n * (`resolveByDeviceId`, for the two pre-tenant wire endpoints) exists only\n * inside this package — exporting the class would hand every embedder a\n * cross-tenant device oracle for free.\n */\nexport { createHmacTokenSigner } from './auth';\nexport type {\n BlobStore,\n CreateUploadInput,\n ReadContentResult,\n WriteContentResult,\n} from './blob-store';\nexport { LocalDiskBlobStore } from './blob-store';\nexport type { SqliteTaskStoreOptions } from './sqlite-task-store';\nexport { SqliteTaskStore } from './sqlite-task-store';\nexport type { SqliteBlobStoreOptions } from './sqlite-blob-store';\nexport { SqliteBlobStore } from './sqlite-blob-store';\nexport { SqliteUnavailableError } from './sqlite-support';\nexport type { RateLimiterOptions } from './rate-limiter';\n\n/** Per-product blob size ceiling (§7): 100MB unless overridden. */\nconst DEFAULT_MAX_BLOB_SIZE_BYTES = 100 * 1024 * 1024;\n/** `GET /byok/events` hold duration (§8): ~50s unless overridden (e.g. for tests). */\nconst DEFAULT_LONG_POLL_HOLD_MS = 50_000;\n/**\n * Task lease ceiling (M2, Decision: `Failed(retryable:true)` on dark-device\n * timeout — no new task state, no new wire message; see `ConnectionHub`'s\n * lease-reaper doc comment in `hub.ts` for the full design). Deliberately\n * generous — 30 minutes, i.e. far larger than any realistic single task\n * turn — since it exists purely as a backstop for a device that never\n * reconnects at all, not a normal-latency timeout.\n */\nconst DEFAULT_TASK_LEASE_MS = 30 * 60_000;\n\n/** The object `createByokServer` returns — the SaaS-embedder-facing surface. */\nexport interface ByokServer {\n /** Hono app exposing the pair/challenge/token/blob/events HTTP routes. Mount it, or use its `.fetch` with `@hono/node-server`. */\n hono: Hono;\n /** Wire up the `GET /byok/ws` upgrade on the raw Node HTTP server serving `hono`. */\n attachWebSocket(server: HttpServer): void;\n pairing: {\n /**\n * S1: minting a code REQUIRES the tenant and product the redeeming\n * device will be paired into (docs/protocol.md §6.1) — the SaaS's own\n * auth/device-flow UI is the only party that knows them, and the device\n * never gets to name its own. There is no claimless overload.\n */\n createPairingCode(claims: PairingCodeClaims): PairingCodeInfo;\n };\n dispatch(input: DispatchInput): Promise<TaskHandle>;\n tasks: {\n get(taskId: string): TaskSnapshot | undefined;\n list(): TaskSnapshot[];\n };\n machines: {\n list(): MachineInfo[];\n };\n events: {\n subscribe(): AsyncIterable<ByokServerEvent>;\n };\n /**\n * Device revocation (§6.3) — server-side only, no wire message. Revoking a\n * device makes its next `/byok/challenge`, `/byok/token`, WSS connect, or\n * authed HTTP call get a 401; its only recourse is to re-run `/byok/pair`.\n *\n * S1: tenant-first, and a tenant can only revoke a device it owns — a\n * `(tenantId, deviceId)` pair belonging to someone else resolves to\n * nothing and this is a silent no-op rather than a cross-tenant write.\n */\n devices: {\n revoke(tenantId: TenantId, deviceId: string): void;\n };\n /**\n * Stop background timers owned by this server instance — currently just\n * the task-lease reaper (`ConnectionHub.stopLeaseReaper`, `hub.ts`). Call\n * this on shutdown so nothing keeps the process alive or leaks a handle in\n * tests; safe to call more than once.\n */\n stop(): void;\n /**\n * M4 Phase 4 (part B.1): a plain, serializable in-process snapshot of this\n * hub's current state — connected device count, task counts by state,\n * envelope in/out totals, dedup drops, rate-limit events, and uptime. See\n * {@link HubStats} for the full contract. Deliberately in-process only —\n * never exposed over HTTP by this SDK itself (see\n * `CreateByokServerOptions.healthzRoute`'s doc comment); an embedder that\n * wants any of this surfaced remotely builds its own authenticated route\n * around this method.\n */\n stats(): HubStats;\n}\n\n/**\n * In-memory reference implementation of the SaaS-side coordinator: Auth v2\n * device pairing/renewal/revocation, a WS + long-poll connection hub with\n * at-least-once redelivery, a local-disk blob store, and task dispatch/\n * lifecycle tracking. See the per-module doc comments (`auth.ts`,\n * `blob-store.ts`, `hub.ts`, `pairing.ts`, `ws-server.ts`) for what's a\n * pinned wire/HTTP contract (docs/protocol.md) versus a reference-impl\n * choice a SaaS embedder might swap out (`tokenSigner`, `blobStore`,\n * `taskStore` — the latter two default to in-memory/local-disk and lose all\n * state on restart; see `sqlite-task-store.ts`/`sqlite-blob-store.ts` for\n * persistent M3 alternatives implementing the same interfaces).\n */\nexport function createByokServer(opts: CreateByokServerOptions): ByokServer {\n const pairing = new PairingManager();\n const devices = new DeviceRegistry();\n const nonces = new NonceStore();\n const tokenSigner = opts.tokenSigner ?? createHmacTokenSigner();\n const blobStore = opts.blobStore ?? new LocalDiskBlobStore();\n const maxBlobSizeBytes = opts.maxBlobSizeBytes ?? DEFAULT_MAX_BLOB_SIZE_BYTES;\n const longPollHoldMs = opts.longPollHoldMs ?? DEFAULT_LONG_POLL_HOLD_MS;\n const taskLeaseMs = opts.taskLeaseMs ?? DEFAULT_TASK_LEASE_MS;\n\n const rateLimiter = new RateLimiter(opts.rateLimit);\n\n const taskStore = opts.taskStore ?? new InMemoryTaskStore();\n const hub = new ConnectionHub(taskStore, devices, taskLeaseMs, rateLimiter);\n const hono = buildHonoApp({\n pairing,\n devices,\n nonces,\n tokenSigner,\n blobStore,\n maxBlobSizeBytes,\n longPollHoldMs,\n hub,\n healthzRoute: opts.healthzRoute ?? false,\n });\n\n return {\n hono,\n attachWebSocket(server: HttpServer): void {\n attachWsUpgrade(server, {\n devices,\n tokenSigner,\n hub,\n productId: opts.productId,\n heartbeatIntervalMs: opts.heartbeatIntervalMs,\n });\n },\n pairing: {\n createPairingCode: (claims: PairingCodeClaims) => pairing.createPairingCode(claims),\n },\n dispatch: (input: DispatchInput) => hub.dispatch(input),\n tasks: {\n get: (taskId: string) => hub.getTask(taskId),\n list: () => hub.listTasks(),\n },\n machines: {\n list: () => hub.listMachines(),\n },\n events: {\n subscribe: () => hub.subscribeServerEvents(),\n },\n devices: {\n revoke: (tenantId: TenantId, deviceId: string) => devices.revoke(tenantId, deviceId),\n },\n stop(): void {\n hub.stopLeaseReaper();\n },\n stats: () => hub.stats(),\n };\n}\n"]}
|