@byok-sdk/cloud 0.11.0 → 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.
- package/dist/auth/plane.d.ts +3 -4
- package/dist/handlers/blobs.d.ts +2 -0
- package/dist/handlers/events.d.ts +3 -3
- package/dist/inbound.d.ts +4 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +308 -104
- package/dist/index.js.map +1 -1
- package/dist/stores/in-memory/blobs.d.ts +1 -0
- package/dist/stores/in-memory/nonces.d.ts +1 -2
- package/dist/stores/in-memory/pairing-codes.d.ts +9 -5
- package/dist/stores/in-memory/task-attempts.d.ts +21 -1
- package/dist/stores/ports.d.ts +73 -22
- package/dist/tenant-stores.d.ts +21 -1
- package/package.json +3 -3
|
@@ -77,6 +77,7 @@ export declare class InMemoryBlobContentProxy implements BlobContentProxy {
|
|
|
77
77
|
#private;
|
|
78
78
|
constructor(registry: InMemoryBlobRegistry);
|
|
79
79
|
verifySignedUrl(blobId: string, action: 'put' | 'get', sig: string, exp: number): Promise<boolean>;
|
|
80
|
+
expectedUploadBytes(blobId: string): Promise<bigint | undefined>;
|
|
80
81
|
writeContent(blobId: string, data: Uint8Array): Promise<BlobWriteResult>;
|
|
81
82
|
/**
|
|
82
83
|
* Never returns `{ok:false}`: this composition holds the bytes in the same
|
|
@@ -17,6 +17,5 @@ export declare class InMemoryNonceStore implements NonceStore {
|
|
|
17
17
|
/** Number of records currently held (post-sweep). Test-facing only. */
|
|
18
18
|
get size(): number;
|
|
19
19
|
issue(tenant: TenantId, deviceId: string): Promise<string>;
|
|
20
|
-
|
|
21
|
-
markUsed(tenant: TenantId, nonce: string): Promise<void>;
|
|
20
|
+
consumeIfValid(tenant: TenantId, deviceId: string, nonce: string): Promise<boolean>;
|
|
22
21
|
}
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
* In-memory {@link PairingCodeStore}: single-use codes bound to the tenant and
|
|
3
3
|
* product they were minted for.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Enrollment answers `undefined` for unknown, expired, and already-used alike.
|
|
6
6
|
* The reference server distinguishes those three in its 401 text; a hosted,
|
|
7
7
|
* multi-tenant surface deliberately does not — the code is a bearer credential
|
|
8
8
|
* addressable across every tenant, and "already used" versus "never existed"
|
|
9
9
|
* is exactly the difference an attacker enumerating codes would pay for.
|
|
10
|
+
*
|
|
11
|
+
* Code issuance and enrollment project this one authority into separate ports.
|
|
12
|
+
* The enrollment path serializes each code through registration and flips
|
|
13
|
+
* `used` only after its shared device directory has accepted the row.
|
|
10
14
|
*/
|
|
11
15
|
import type { Clock, TenantId } from '@byok-sdk/core';
|
|
12
|
-
import type {
|
|
13
|
-
export declare class InMemoryPairingCodeStore implements PairingCodeStore {
|
|
16
|
+
import type { DeviceDirectory, DeviceRecord, PairingEnrollment, PairingEnrollmentInput, PairingCodeInfo, PairingCodeIssueInput, PairingCodeStore } from '../ports';
|
|
17
|
+
export declare class InMemoryPairingCodeStore implements PairingCodeStore, PairingEnrollment {
|
|
14
18
|
#private;
|
|
15
|
-
constructor(clock: Clock);
|
|
19
|
+
constructor(clock: Clock, devices: DeviceDirectory);
|
|
16
20
|
issue(tenant: TenantId, input: PairingCodeIssueInput): Promise<PairingCodeInfo>;
|
|
17
|
-
|
|
21
|
+
redeemAndRegister(input: PairingEnrollmentInput): Promise<DeviceRecord | undefined>;
|
|
18
22
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* unfalsifiable.
|
|
17
17
|
*/
|
|
18
18
|
import { type Clock, type TenantId } from '@byok-sdk/core';
|
|
19
|
-
import type
|
|
19
|
+
import { type AgentMessageAdmission, type AgentRef, type TaskAttempt, type TaskAttemptStatus, type TaskAttemptStore } from '../ports';
|
|
20
20
|
export declare class InMemoryTaskAttemptStore implements TaskAttemptStore {
|
|
21
21
|
#private;
|
|
22
22
|
constructor(clock: Clock, state?: InMemoryTaskAttemptState);
|
|
@@ -33,6 +33,25 @@ export declare class InMemoryTaskAttemptStore implements TaskAttemptStore {
|
|
|
33
33
|
readonly attempt: TaskAttempt;
|
|
34
34
|
readonly created: boolean;
|
|
35
35
|
}>;
|
|
36
|
+
reserveAgentMessage(tenant: TenantId, input: {
|
|
37
|
+
readonly taskId: string;
|
|
38
|
+
readonly deviceId: string;
|
|
39
|
+
readonly messageId: string;
|
|
40
|
+
readonly payloadBody: string;
|
|
41
|
+
}): Promise<'reserved' | 'pending' | 'rejected'>;
|
|
42
|
+
readAgentMessage(tenant: TenantId, input: {
|
|
43
|
+
readonly taskId: string;
|
|
44
|
+
readonly deviceId: string;
|
|
45
|
+
readonly messageId: string;
|
|
46
|
+
readonly payloadBody: string;
|
|
47
|
+
}): Promise<AgentMessageAdmission | undefined>;
|
|
48
|
+
finalizeAgentMessage(tenant: TenantId, input: {
|
|
49
|
+
readonly taskId: string;
|
|
50
|
+
readonly deviceId: string;
|
|
51
|
+
readonly messageId: string;
|
|
52
|
+
readonly payloadBody: string;
|
|
53
|
+
readonly terminalBody: string;
|
|
54
|
+
}): Promise<AgentMessageAdmission | undefined>;
|
|
36
55
|
get(tenant: TenantId, taskId: string): Promise<TaskAttempt | undefined>;
|
|
37
56
|
getMany(tenant: TenantId, taskIds: readonly string[]): Promise<readonly TaskAttempt[]>;
|
|
38
57
|
claim(tenant: TenantId, input: {
|
|
@@ -50,6 +69,7 @@ export declare class InMemoryTaskAttemptStore implements TaskAttemptStore {
|
|
|
50
69
|
export declare class InMemoryTaskAttemptState {
|
|
51
70
|
#private;
|
|
52
71
|
readonly attempts: Map<string, TaskAttempt>;
|
|
72
|
+
readonly messageAdmissions: Map<string, AgentMessageAdmission>;
|
|
53
73
|
constructor(clock: Clock);
|
|
54
74
|
now(): string;
|
|
55
75
|
/** Serialize every state-changing operation for one tenant/task key. */
|
package/dist/stores/ports.d.ts
CHANGED
|
@@ -13,20 +13,18 @@
|
|
|
13
13
|
* 1. **Async.** Every method returns a `Promise`, so a SQL or KV composition
|
|
14
14
|
* can implement the same contract the in-memory reference does.
|
|
15
15
|
* 2. **Tenant-first.** Every method's first parameter is a required
|
|
16
|
-
* `TenantId` — with
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* `TenantId` — with one documented lookup exception below. Pairing code
|
|
17
|
+
* consumption is a separate composition-owned enrollment operation: its
|
|
18
|
+
* bearer code resolves the tenant internally and it returns only the
|
|
19
|
+
* resulting device row.
|
|
19
20
|
*
|
|
20
|
-
* The
|
|
21
|
+
* The lookup exception:
|
|
21
22
|
*
|
|
22
23
|
* - {@link DeviceDirectory.resolveByDeviceId} — `POST /byok/challenge` and
|
|
23
24
|
* `POST /byok/token` carry only a deviceId (the pinned wire contract), and
|
|
24
25
|
* the row is what tells the deployment which tenant to mint for.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* Neither is reachable through {@link TenantStores} (see `tenant-stores.ts`),
|
|
29
|
-
* so a device-facing handler cannot call them.
|
|
26
|
+
* It is not reachable through {@link TenantStores} (see `tenant-stores.ts`),
|
|
27
|
+
* so a device-facing handler cannot call it.
|
|
30
28
|
*
|
|
31
29
|
* The byte-proxy trio that used to be a third pre-tenant exception is no
|
|
32
30
|
* longer part of this bundle at all: it moved to {@link BlobContentProxy},
|
|
@@ -151,21 +149,38 @@ export interface PairingCodeIssueInput {
|
|
|
151
149
|
}
|
|
152
150
|
export interface PairingCodeStore {
|
|
153
151
|
issue(tenant: TenantId, input: PairingCodeIssueInput): Promise<PairingCodeInfo>;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The pre-tenant facts a pairing request carries into enrollment. Tenant and
|
|
155
|
+
* product are deliberately absent: the guarded pairing-code row is their sole
|
|
156
|
+
* authority, and no caller can choose the registration scope.
|
|
157
|
+
*/
|
|
158
|
+
export interface PairingEnrollmentInput {
|
|
159
|
+
readonly pairingCode: string;
|
|
160
|
+
readonly deviceId: string;
|
|
161
|
+
readonly deviceName: string;
|
|
162
|
+
readonly devicePublicKey: string;
|
|
163
|
+
readonly proofKeyId: string;
|
|
164
|
+
readonly proofKeyEpoch: number;
|
|
165
|
+
readonly machineId?: string;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The only pairing-code consumption operation. Implementations atomically
|
|
169
|
+
* consume a valid code, apply machine supersession/state cleanup, and register
|
|
170
|
+
* the device; failures leave the code retryable. Unknown, expired, and already
|
|
171
|
+
* consumed codes all return `undefined`.
|
|
172
|
+
*/
|
|
173
|
+
export interface PairingEnrollment {
|
|
174
|
+
redeemAndRegister(input: PairingEnrollmentInput): Promise<DeviceRecord | undefined>;
|
|
162
175
|
}
|
|
163
176
|
export interface NonceStore {
|
|
164
177
|
issue(tenant: TenantId, deviceId: string): Promise<string>;
|
|
165
|
-
/**
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Atomically consume a nonce only when it belongs to this exact (tenant,
|
|
180
|
+
* device), is unexpired, and has not already been consumed. Returns `true`
|
|
181
|
+
* for the sole winner; every other case returns `false`.
|
|
182
|
+
*/
|
|
183
|
+
consumeIfValid(tenant: TenantId, deviceId: string, nonce: string): Promise<boolean>;
|
|
169
184
|
}
|
|
170
185
|
export interface InboundDedupStore {
|
|
171
186
|
/**
|
|
@@ -178,6 +193,12 @@ export interface InboundDedupStore {
|
|
|
178
193
|
}
|
|
179
194
|
export declare const TASK_ATTEMPT_STATUSES: readonly ['offered', 'claimed', 'running', 'cancel_requested', 'complete', 'failed', 'cancelled'];
|
|
180
195
|
export type TaskAttemptStatus = (typeof TASK_ATTEMPT_STATUSES)[number];
|
|
196
|
+
/** The durable first-message reservation and, once present, its terminal disposition. */
|
|
197
|
+
export interface AgentMessageAdmission {
|
|
198
|
+
readonly messageId: string;
|
|
199
|
+
readonly payloadBody: string;
|
|
200
|
+
readonly terminalBody?: string;
|
|
201
|
+
}
|
|
181
202
|
export interface TaskAttempt {
|
|
182
203
|
readonly tenantId: TenantId;
|
|
183
204
|
readonly taskId: string;
|
|
@@ -216,6 +237,33 @@ export interface TaskAttemptStore {
|
|
|
216
237
|
readonly attempt: TaskAttempt;
|
|
217
238
|
readonly created: boolean;
|
|
218
239
|
}>;
|
|
240
|
+
/**
|
|
241
|
+
* Atomically binds one message payload to a live task before an external
|
|
242
|
+
* consumer can run. `pending` is an existing exact reservation whose terminal
|
|
243
|
+
* receipt has not been recorded yet; callers must fail closed rather than
|
|
244
|
+
* invoke the consumer again.
|
|
245
|
+
*/
|
|
246
|
+
reserveAgentMessage(tenant: TenantId, input: {
|
|
247
|
+
readonly taskId: string;
|
|
248
|
+
readonly deviceId: string;
|
|
249
|
+
readonly messageId: string;
|
|
250
|
+
readonly payloadBody: string;
|
|
251
|
+
}): Promise<'reserved' | 'pending' | 'rejected'>;
|
|
252
|
+
/** Read only an exact reservation; conflicting task/message bindings are not observable. */
|
|
253
|
+
readAgentMessage(tenant: TenantId, input: {
|
|
254
|
+
readonly taskId: string;
|
|
255
|
+
readonly deviceId: string;
|
|
256
|
+
readonly messageId: string;
|
|
257
|
+
readonly payloadBody: string;
|
|
258
|
+
}): Promise<AgentMessageAdmission | undefined>;
|
|
259
|
+
/** CAS the exact reservation to one immutable terminal disposition body. */
|
|
260
|
+
finalizeAgentMessage(tenant: TenantId, input: {
|
|
261
|
+
readonly taskId: string;
|
|
262
|
+
readonly deviceId: string;
|
|
263
|
+
readonly messageId: string;
|
|
264
|
+
readonly payloadBody: string;
|
|
265
|
+
readonly terminalBody: string;
|
|
266
|
+
}): Promise<AgentMessageAdmission | undefined>;
|
|
219
267
|
get(tenant: TenantId, taskId: string): Promise<TaskAttempt | undefined>;
|
|
220
268
|
/** Batch lookup used by mailbox projection; implementations must not turn one poll into N queries. */
|
|
221
269
|
getMany(tenant: TenantId, taskIds: readonly string[]): Promise<readonly TaskAttempt[]>;
|
|
@@ -411,6 +459,8 @@ export interface CloudBlobStore {
|
|
|
411
459
|
*/
|
|
412
460
|
export interface BlobContentProxy {
|
|
413
461
|
verifySignedUrl(blobId: string, action: 'put' | 'get', sig: string, exp: number): Promise<boolean>;
|
|
462
|
+
/** Authoritative declared size for a signed PUT; no byte buffering precedes this lookup. */
|
|
463
|
+
expectedUploadBytes(blobId: string): Promise<bigint | undefined>;
|
|
414
464
|
writeContent(blobId: string, data: Uint8Array): Promise<BlobWriteResult>;
|
|
415
465
|
/** `undefined` = no such blob (404); a `{ok:false}` result = the blob exists but its bytes could not be proxied (502, distinguished by {@link BlobReadErrorCode}). */
|
|
416
466
|
readContent(blobId: string): Promise<BlobReadResult | undefined>;
|
|
@@ -436,6 +486,7 @@ export interface CloudStores {
|
|
|
436
486
|
readonly approvals: ApprovalTimelineStore;
|
|
437
487
|
readonly devices: DeviceDirectory;
|
|
438
488
|
readonly pairingCodes: PairingCodeStore;
|
|
489
|
+
readonly pairing: PairingEnrollment;
|
|
439
490
|
readonly nonces: NonceStore;
|
|
440
491
|
readonly dedup: InboundDedupStore;
|
|
441
492
|
readonly tasks: TaskAttemptStore;
|
|
@@ -447,5 +498,5 @@ export interface CloudStores {
|
|
|
447
498
|
readonly rateLimiter: InboundRateLimiter;
|
|
448
499
|
}
|
|
449
500
|
/** Names of every port in {@link CloudStores}, in contract order. */
|
|
450
|
-
export declare const CLOUD_STORE_NAMES: readonly ['activity', 'approvals', 'devices', 'pairingCodes', 'nonces', 'dedup', 'tasks', 'cancellations', 'receipts', 'egress', 'proofReceipts', 'blobs', 'rateLimiter'];
|
|
501
|
+
export declare const CLOUD_STORE_NAMES: readonly ['activity', 'approvals', 'devices', 'pairingCodes', 'pairing', 'nonces', 'dedup', 'tasks', 'cancellations', 'receipts', 'egress', 'proofReceipts', 'blobs', 'rateLimiter'];
|
|
451
502
|
export type CloudStoreName = (typeof CLOUD_STORE_NAMES)[number];
|
package/dist/tenant-stores.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* presigned blob calls) are deliberately absent from this surface: a
|
|
22
22
|
* device-facing handler must not be able to reach them.
|
|
23
23
|
*/
|
|
24
|
-
import { type BoardClaimInput, type BoardItem, type BoardItemInput, type BoardListQuery, type BoardPage, type BoardStatusUpdateInput, type BoardUnclaimInput, type CoreStores, type MailboxAdvanceCursorInput, type MailboxAppendInput, type MailboxCursorState, type MailboxMessage, type MailboxPage, type MailboxReadQuery, type Principal, type PresenceHint, type PresenceHintInput, type TenantReadiness, type StorageFinalizeInput, type StorageFinalizeResult, type StorageReservation, type StorageReservationInput, type TenantId } from '@byok-sdk/core';
|
|
24
|
+
import { type BoardClaimInput, type BoardItem, type BoardItemInput, type BoardListQuery, type BoardPage, type BoardStatusUpdateInput, type BoardUnclaimInput, type CoreStores, type MailboxAdvanceCursorInput, type MailboxAppendInput, type MailboxCursorState, type MailboxMessage, type MailboxPage, type MailboxReadQuery, type MailboxRecordDeliveryInput, type Principal, type PresenceHint, type PresenceHintInput, type TenantReadiness, type StorageFinalizeInput, type StorageFinalizeResult, type StorageReservation, type StorageReservationInput, type TenantId } from '@byok-sdk/core';
|
|
25
25
|
import type { ActivityAppendInput, ActivityTail } from './activity';
|
|
26
26
|
import type { ApprovalTimelineAppendInput, ApprovalTimelineTail } from './approval-timeline';
|
|
27
27
|
import type { BlobObservation, CloudStores, DeviceRecord, AgentEgressRecord, RequestReceipt, TaskCancellationMutation, TaskCancellationRequest, TaskAttempt, TaskAttemptStatus } from './stores/ports';
|
|
@@ -29,6 +29,7 @@ export interface TenantBoundMailbox {
|
|
|
29
29
|
append(input: MailboxAppendInput): Promise<MailboxMessage>;
|
|
30
30
|
/** Pure read. Never advances the cursor — the daemon's next poll is the only ack. */
|
|
31
31
|
readAfter(query: MailboxReadQuery): Promise<MailboxPage>;
|
|
32
|
+
recordDelivery(input: MailboxRecordDeliveryInput): Promise<MailboxCursorState>;
|
|
32
33
|
advanceCursor(input: MailboxAdvanceCursorInput): Promise<MailboxCursorState>;
|
|
33
34
|
readCursor(deviceId: string): Promise<MailboxCursorState>;
|
|
34
35
|
}
|
|
@@ -77,6 +78,25 @@ export interface TenantBoundTaskAttempts {
|
|
|
77
78
|
readonly attempt: TaskAttempt;
|
|
78
79
|
readonly created: boolean;
|
|
79
80
|
}>;
|
|
81
|
+
reserveAgentMessage(input: {
|
|
82
|
+
readonly taskId: string;
|
|
83
|
+
readonly deviceId: string;
|
|
84
|
+
readonly messageId: string;
|
|
85
|
+
readonly payloadBody: string;
|
|
86
|
+
}): Promise<'reserved' | 'pending' | 'rejected'>;
|
|
87
|
+
readAgentMessage(input: {
|
|
88
|
+
readonly taskId: string;
|
|
89
|
+
readonly deviceId: string;
|
|
90
|
+
readonly messageId: string;
|
|
91
|
+
readonly payloadBody: string;
|
|
92
|
+
}): Promise<import('./stores/ports').AgentMessageAdmission | undefined>;
|
|
93
|
+
finalizeAgentMessage(input: {
|
|
94
|
+
readonly taskId: string;
|
|
95
|
+
readonly deviceId: string;
|
|
96
|
+
readonly messageId: string;
|
|
97
|
+
readonly payloadBody: string;
|
|
98
|
+
readonly terminalBody: string;
|
|
99
|
+
}): Promise<import('./stores/ports').AgentMessageAdmission | undefined>;
|
|
80
100
|
get(taskId: string): Promise<TaskAttempt | undefined>;
|
|
81
101
|
getMany(taskIds: readonly string[]): Promise<readonly TaskAttempt[]>;
|
|
82
102
|
claim(input: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byok-sdk/cloud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "BYOK SDK hosted device surface: stateless frozen-v1 HTTP handlers over tenant-first @byok-sdk/core ports",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"clean": "rm -rf dist"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@byok-sdk/core": "0.
|
|
48
|
-
"@byok-sdk/protocol": "0.
|
|
47
|
+
"@byok-sdk/core": "0.12.0",
|
|
48
|
+
"@byok-sdk/protocol": "0.12.0",
|
|
49
49
|
"hono": "^4.12.30",
|
|
50
50
|
"zod": "^4.4.3"
|
|
51
51
|
}
|