@byok-sdk/server 0.10.2 → 0.12.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.
@@ -1,3 +1,4 @@
1
+ import type { TenantId } from './auth';
1
2
  /**
2
3
  * Blob flows (docs/protocol.md §7): `POST /byok/blobs` declares a blob and
3
4
  * gets back a presigned upload URL; the caller `PUT`s the bytes there
@@ -8,9 +9,9 @@
8
9
  *
9
10
  * `BlobStore` is interface-shaped so a SaaS can swap in a real object-store
10
11
  * (S3/GCS/R2 presigned URLs) later; {@link LocalDiskBlobStore} is the M1
11
- * reference implementation (single-process, in-memory metadata + files on
12
- * disk) — good enough for local dev and the SDK's own tests, not meant to
13
- * survive a restart or run multi-process.
12
+ * reference implementation (single-process, persisted metadata + files on
13
+ * disk) — good enough for local dev and the SDK's own tests, including a
14
+ * restart of the same directory; it is not multi-process storage.
14
15
  */
15
16
  export interface CreateUploadInput {
16
17
  size: number;
@@ -28,19 +29,25 @@ export interface ReadContentResult {
28
29
  data: Buffer;
29
30
  contentType: string;
30
31
  }
32
+ /** The upload reservation HTTP must resolve before it starts retaining bytes. */
33
+ export interface BlobUploadReservation {
34
+ size: number;
35
+ }
31
36
  export declare class BlobDeclarationConflictError extends Error {
32
37
  constructor(blobId: string);
33
38
  }
34
39
  export interface BlobStore {
35
40
  /** Declare a blob before upload; an explicit id makes the declaration idempotent across host restart. */
36
- createUpload(input: CreateUploadInput, blobId?: string): Promise<{
41
+ createUpload(tenantId: TenantId, input: CreateUploadInput, blobId?: string): Promise<{
37
42
  blobId: string;
38
43
  uploadUrl: string;
39
44
  }>;
40
45
  /** A presigned GET URL for a blob that has finished uploading, or `undefined` if unknown/not yet uploaded. */
41
- getDownloadUrl(blobId: string): Promise<string | undefined>;
46
+ getDownloadUrl(tenantId: TenantId, blobId: string): Promise<string | undefined>;
42
47
  /** Whether `blobId` is known *and* has finished uploading. */
43
- exists(blobId: string): Promise<boolean>;
48
+ exists(tenantId: TenantId, blobId: string): Promise<boolean>;
49
+ /** Resolve a capability URL's immutable declared size before consuming its body. */
50
+ getUploadReservation(blobId: string): Promise<BlobUploadReservation | undefined>;
44
51
  /** Verify a presigned content URL's `sig`/`exp` query params for `action`. */
45
52
  verifySignedUrl(blobId: string, action: 'put' | 'get', sig: string, exp: number): boolean;
46
53
  /** Accept uploaded bytes; rejects (without storing) on size/hash mismatch against the `createUpload` declaration. */
@@ -54,24 +61,29 @@ export interface LocalDiskBlobStoreOptions {
54
61
  /** How long a presigned upload/download URL stays valid, ms. Default 15 minutes. */
55
62
  urlTtlMs?: number;
56
63
  }
57
- /** Local-disk reference {@link BlobStore}: in-memory metadata, content on disk, HMAC-signed expiring URLs. */
64
+ /** Local-disk reference {@link BlobStore}: persisted metadata/content and HMAC-signed expiring URLs. */
58
65
  export declare class LocalDiskBlobStore implements BlobStore {
59
- private readonly secret;
66
+ private secret;
60
67
  private readonly directory;
68
+ private readonly metadataPath;
61
69
  private readonly urlTtlMs;
62
70
  private readonly blobs;
63
71
  private readonly ready;
72
+ private metadataWriteTail;
64
73
  constructor(opts?: LocalDiskBlobStoreOptions);
65
- createUpload(input: CreateUploadInput, requestedBlobId?: string): Promise<{
74
+ createUpload(tenantId: TenantId, input: CreateUploadInput, requestedBlobId?: string): Promise<{
66
75
  blobId: string;
67
76
  uploadUrl: string;
68
77
  }>;
69
- getDownloadUrl(blobId: string): Promise<string | undefined>;
70
- exists(blobId: string): Promise<boolean>;
78
+ getDownloadUrl(tenantId: TenantId, blobId: string): Promise<string | undefined>;
79
+ exists(tenantId: TenantId, blobId: string): Promise<boolean>;
80
+ getUploadReservation(blobId: string): Promise<BlobUploadReservation | undefined>;
71
81
  verifySignedUrl(blobId: string, action: 'put' | 'get', sig: string, exp: number): boolean;
72
82
  writeContent(blobId: string, data: Buffer): Promise<WriteContentResult>;
73
83
  readContent(blobId: string): Promise<ReadContentResult | undefined>;
74
84
  private pathFor;
85
+ private loadMetadata;
86
+ private persistMetadata;
75
87
  private computeSig;
76
88
  private signUrl;
77
89
  }
package/dist/hub.d.ts CHANGED
@@ -194,6 +194,12 @@ export declare class AgentHomeProjectionCompletionError extends Error {
194
194
  readonly code: AgentHomeProjectionCompletionErrorCode;
195
195
  constructor(code: AgentHomeProjectionCompletionErrorCode, message: string);
196
196
  }
197
+ /** A requested delivery cursor predates controls this bounded hub can recover. */
198
+ export declare class ReplayGapError extends Error {
199
+ readonly cursor: number;
200
+ readonly recoverableFrom: number;
201
+ constructor(cursor: number, recoverableFrom: number);
202
+ }
197
203
  export declare class ConnectionHub {
198
204
  private readonly taskStore;
199
205
  private readonly devices;
@@ -211,6 +217,7 @@ export declare class ConnectionHub {
211
217
  private readonly rateLimiter;
212
218
  private readonly agentMessage?;
213
219
  private readonly connections;
220
+ private readonly connectionEpochs;
214
221
  private readonly outboxes;
215
222
  /** Idempotency window per device (N3) — recent inbound envelope ids, capped at {@link DEDUP_RING_CAPACITY}. */
216
223
  private readonly dedupRings;
@@ -302,7 +309,9 @@ export declare class ConnectionHub {
302
309
  * connection this hub never learns capabilities for simply reads back
303
310
  * `undefined` from {@link getDeviceCapabilities}.
304
311
  */
305
- registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[], configuredToolsets?: readonly ToolsetId[], clientVersion?: string): void;
312
+ registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[], configuredToolsets?: readonly ToolsetId[], clientVersion?: string): number;
313
+ /** True only while this exact WS registration remains the device authority. */
314
+ isCurrentConnection(deviceId: string, ws: WebSocket, epoch: number): boolean;
306
315
  sendConnAck(deviceId: string, capabilities: string[]): void;
307
316
  /**
308
317
  * Reconnection procedure step 3 (§9): redeliver, in `seq` order, every
@@ -897,10 +906,17 @@ export declare class ConnectionHub {
897
906
  * on any future reconnect/poll.
898
907
  */
899
908
  private collectRelevant;
909
+ /**
910
+ * A caller at `recoverableFrom - 1` can still receive the first retained
911
+ * control. Anything earlier would be a partial tail and must fail closed.
912
+ */
913
+ assertReplayAvailable(deviceId: string, cursor: number, pendingOutbound?: number): void;
914
+ private isRecoverableEntry;
900
915
  private isTaskTerminal;
901
916
  /** The highest `seq` assigned to `deviceId` so far — the redelivery cursor to hand back on a poll/reconnect. */
902
917
  private currentCursor;
903
918
  private getOrCreateOutbox;
919
+ private nextConnectionEpoch;
904
920
  listMachines(): MachineInfo[];
905
921
  /**
906
922
  * M5 (approval targeting, hello-capability plumbing): the capability flags
package/dist/index.d.ts CHANGED
@@ -26,8 +26,8 @@ export { SteerRejectedError } from './hub';
26
26
  export type { SteerRejectionCode } from './hub';
27
27
  export { AgentHomeProjectionCompletionError } from './hub';
28
28
  export type { AgentHomeProjectionCompletionErrorCode } from './hub';
29
- export { PairingCodeInvalidError } from './pairing';
30
- export type { PairingCodeClaims, PairingCodeInfo } from './pairing';
29
+ export { PairingAttemptConflictError, PairingCodeInvalidError } from './pairing';
30
+ export type { PairingAttemptBinding, PairingCodeClaims, PairingCodeInfo, PairingCompletion } from './pairing';
31
31
  export type { AccessTokenClaims, AuthenticatedDevice, DeviceRecord, TenantId, TokenSigner, } from './auth';
32
32
  /**
33
33
  * S1: `DeviceRegistry` itself is deliberately NOT exported. Its
@@ -38,7 +38,7 @@ export type { AccessTokenClaims, AuthenticatedDevice, DeviceRecord, TenantId, To
38
38
  * cross-tenant device oracle for free.
39
39
  */
40
40
  export { createHmacTokenSigner } from './auth';
41
- export type { BlobStore, CreateUploadInput, ReadContentResult, WriteContentResult, } from './blob-store';
41
+ export type { BlobStore, BlobUploadReservation, CreateUploadInput, ReadContentResult, WriteContentResult, } from './blob-store';
42
42
  export { LocalDiskBlobStore } from './blob-store';
43
43
  export type { SqliteTaskStoreOptions } from './sqlite-task-store';
44
44
  export { SqliteTaskStore } from './sqlite-task-store';