@byok-sdk/cloud 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 +9 -0
- package/dist/auth/bearer.d.ts +25 -0
- package/dist/auth/device-proof.d.ts +38 -0
- package/dist/auth/plane.d.ts +67 -0
- package/dist/auth/tokens.d.ts +37 -0
- package/dist/auth/verify.d.ts +22 -0
- package/dist/board-projection.d.ts +9 -0
- package/dist/capabilities.d.ts +71 -0
- package/dist/cloud.d.ts +123 -0
- package/dist/composition/in-memory.d.ts +55 -0
- package/dist/coordination-client.d.ts +87 -0
- package/dist/coordination.d.ts +29 -0
- package/dist/crypto/port.d.ts +44 -0
- package/dist/crypto/web-crypto.d.ts +28 -0
- package/dist/errors.d.ts +45 -0
- package/dist/handlers/auth.d.ts +21 -0
- package/dist/handlers/blobs.d.ts +39 -0
- package/dist/handlers/board.d.ts +21 -0
- package/dist/handlers/capabilities.d.ts +14 -0
- package/dist/handlers/events.d.ts +33 -0
- package/dist/handlers/messages.d.ts +28 -0
- package/dist/handlers/presence.d.ts +13 -0
- package/dist/handlers/shared.d.ts +30 -0
- package/dist/handlers/truth.d.ts +15 -0
- package/dist/inbound.d.ts +35 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +2419 -0
- package/dist/index.js.map +1 -0
- package/dist/router/registry.d.ts +56 -0
- package/dist/stores/in-memory/blobs.d.ts +89 -0
- package/dist/stores/in-memory/dedup.d.ts +17 -0
- package/dist/stores/in-memory/device-directory.d.ts +19 -0
- package/dist/stores/in-memory/index.d.ts +36 -0
- package/dist/stores/in-memory/nonces.d.ts +22 -0
- package/dist/stores/in-memory/pairing-codes.d.ts +18 -0
- package/dist/stores/in-memory/proof-receipts.d.ts +11 -0
- package/dist/stores/in-memory/rate-limiter.d.ts +13 -0
- package/dist/stores/in-memory/receipts.d.ts +21 -0
- package/dist/stores/in-memory/sequence.d.ts +11 -0
- package/dist/stores/in-memory/task-attempts.d.ts +36 -0
- package/dist/stores/ports-contract.d.ts +20 -0
- package/dist/stores/ports.d.ts +309 -0
- package/dist/tenant-stores.d.ts +124 -0
- package/dist/truth/contract.d.ts +149 -0
- package/dist/truth/errors.d.ts +8 -0
- package/package.json +52 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reference {@link CloudCrypto}, built entirely on the WebCrypto API that
|
|
3
|
+
* Node >=20, Cloudflare Workers, and Deno all expose as `globalThis.crypto`.
|
|
4
|
+
*
|
|
5
|
+
* No `node:crypto`, no dependency: the point of the port is that a hosted
|
|
6
|
+
* composition stays runtime-neutral, and an adapter that reached for a Node
|
|
7
|
+
* built-in here would make that claim false for the default composition.
|
|
8
|
+
*/
|
|
9
|
+
import type { CloudCrypto } from './port';
|
|
10
|
+
export declare function base64UrlEncode(bytes: Uint8Array): string;
|
|
11
|
+
/**
|
|
12
|
+
* Decode base64url. Returns `undefined` rather than throwing for input that
|
|
13
|
+
* is not base64url at all — every caller here is verifying attacker-supplied
|
|
14
|
+
* material, where "not decodable" and "does not verify" must be the same
|
|
15
|
+
* answer.
|
|
16
|
+
*/
|
|
17
|
+
export declare function base64UrlDecode(value: string): Uint8Array | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* WebCrypto's own types are derived from the runtime's `globalThis.crypto`
|
|
20
|
+
* rather than named directly: `CryptoKey`/`SubtleCrypto`/`KeyUsage` are DOM
|
|
21
|
+
* lib globals, and this package compiles without the DOM lib (a Workers build
|
|
22
|
+
* has no DOM either). Deriving keeps the types exact without pulling in a
|
|
23
|
+
* `node:` import or widening `lib`.
|
|
24
|
+
*/
|
|
25
|
+
export type SubtleCryptoLike = typeof globalThis.crypto.subtle;
|
|
26
|
+
export type CryptoKeyLike = Awaited<ReturnType<SubtleCryptoLike['importKey']>>;
|
|
27
|
+
export type KeyUsageLike = Parameters<SubtleCryptoLike['importKey']>[4][number];
|
|
28
|
+
export declare function createWebCrypto(): CloudCrypto;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one error taxonomy for `@byok-sdk/cloud`.
|
|
3
|
+
*
|
|
4
|
+
* Same idiom as `@byok-sdk/core`'s `errors.ts`: one class, code-based branching,
|
|
5
|
+
* so a composition maps failures onto HTTP with a code table instead of an
|
|
6
|
+
* `instanceof` chain. Cloud does not re-export core's codes — a cloud error is
|
|
7
|
+
* about the hosted surface (a store contract the composition broke, a
|
|
8
|
+
* declaration the host mis-configured), and core errors travel up unchanged.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CLOUD_ERROR_CODES: {
|
|
11
|
+
/** A pairing code that is unknown, expired, or already redeemed (§6.1). */
|
|
12
|
+
readonly pairing_code_invalid: 'pairing_code_invalid';
|
|
13
|
+
/** The composition handed a device row whose tenant is not a mintable `TenantId`. */
|
|
14
|
+
readonly device_tenant_invalid: 'device_tenant_invalid';
|
|
15
|
+
/**
|
|
16
|
+
* The mailbox assigned a row `seq` that disagrees with the delivery `seq`
|
|
17
|
+
* baked into the enqueued envelope. Loud rather than silent: those two
|
|
18
|
+
* numbers ARE the daemon's redelivery cursor, and a composition whose
|
|
19
|
+
* mailbox numbers rows differently from `DeviceSequenceStore` would
|
|
20
|
+
* mis-deliver every subsequent poll.
|
|
21
|
+
*/
|
|
22
|
+
readonly mailbox_seq_mismatch: 'mailbox_seq_mismatch';
|
|
23
|
+
/** A capability declaration the host supplied that core refused. */
|
|
24
|
+
readonly capability_declaration_invalid: 'capability_declaration_invalid';
|
|
25
|
+
/**
|
|
26
|
+
* The declaration names a capability this composition cannot serve, so the
|
|
27
|
+
* deployment would publish a surface it does not have (ADR-010).
|
|
28
|
+
*
|
|
29
|
+
* Construction-time and fatal. A client learns what a deployment supports by
|
|
30
|
+
* READING the declaration and is entitled to act on it without probing, so a
|
|
31
|
+
* declaration that over-states is not a degraded deployment — it is a
|
|
32
|
+
* deployment whose one honest interface lies.
|
|
33
|
+
*/
|
|
34
|
+
readonly capability_over_declared: 'capability_over_declared';
|
|
35
|
+
/** Host-supplied board labels or coordination input exceeded the explicit contract. */
|
|
36
|
+
readonly coordination_input_invalid: 'coordination_input_invalid';
|
|
37
|
+
/** A progress/activity batch exceeded the configured event or byte ceiling. */
|
|
38
|
+
readonly activity_batch_too_large: 'activity_batch_too_large';
|
|
39
|
+
};
|
|
40
|
+
export type CloudErrorCode = (typeof CLOUD_ERROR_CODES)[keyof typeof CLOUD_ERROR_CODES];
|
|
41
|
+
export declare class ByokCloudError extends Error {
|
|
42
|
+
readonly code: CloudErrorCode;
|
|
43
|
+
constructor(code: CloudErrorCode, message: string, options?: ErrorOptions);
|
|
44
|
+
}
|
|
45
|
+
export declare function isCloudError(value: unknown, code?: CloudErrorCode): value is ByokCloudError;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth v2 (docs/protocol.md §6): pair, challenge, token.
|
|
3
|
+
*
|
|
4
|
+
* Byte-for-byte the same request/response DTOs the daemon already speaks,
|
|
5
|
+
* because they come from `@byok-sdk/protocol` rather than from a hosted copy of
|
|
6
|
+
* them. The behavior differences a hosted surface DOES make are two, both
|
|
7
|
+
* narrowing:
|
|
8
|
+
*
|
|
9
|
+
* - a pairing code that is unknown, expired, or already used gets one 401 with
|
|
10
|
+
* one message (the reference server names the reason);
|
|
11
|
+
* - an unknown device and a revoked device were already indistinguishable on
|
|
12
|
+
* the reference server, and stay so here.
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from 'hono';
|
|
15
|
+
import type { AuthPlane } from '../auth/plane';
|
|
16
|
+
export interface AuthRouteDeps {
|
|
17
|
+
readonly auth: AuthPlane;
|
|
18
|
+
}
|
|
19
|
+
export declare function pairHandler(deps: AuthRouteDeps): (c: Context) => Promise<Response>;
|
|
20
|
+
export declare function challengeHandler(deps: AuthRouteDeps): (c: Context) => Promise<Response>;
|
|
21
|
+
export declare function tokenHandler(deps: AuthRouteDeps): (c: Context) => Promise<Response>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blob flows (§7).
|
|
3
|
+
*
|
|
4
|
+
* Two route classes on purpose: `POST /byok/blobs` and `GET
|
|
5
|
+
* /byok/blobs/:id/url` are bearer-authed and therefore tenant-closed (a
|
|
6
|
+
* download URL is minted only for a blob the caller's own tenant owns, and a
|
|
7
|
+
* foreign blob is indistinguishable from a missing one); the two `/content`
|
|
8
|
+
* routes are presigned, because they are meant to be hit directly — by a
|
|
9
|
+
* browser, by a `PUT` from a daemon that holds only the signed URL — with no
|
|
10
|
+
* JWT in scope at all. The HMAC signature over the blob id plus its expiry IS
|
|
11
|
+
* the credential there, which is why those two carry the `presigned` class in
|
|
12
|
+
* the route inventory rather than being lumped in with the device routes.
|
|
13
|
+
*
|
|
14
|
+
* The split runs all the way down: the bearer-authed pair reaches blobs only
|
|
15
|
+
* through the tenant facade and never holds a naked store, and the presigned
|
|
16
|
+
* pair holds only a {@link BlobContentProxy} and no bearer deps. A composition
|
|
17
|
+
* without a proxy does not mount them (`cloud.ts`).
|
|
18
|
+
*/
|
|
19
|
+
import type { Context } from 'hono';
|
|
20
|
+
import type { BlobContentProxy } from '../stores/ports';
|
|
21
|
+
import { type DeviceRouteDeps } from './shared';
|
|
22
|
+
/** The three bearer-authed routes: everything they touch goes through the tenant facade. */
|
|
23
|
+
export interface BlobRouteDeps extends DeviceRouteDeps {
|
|
24
|
+
readonly maxBlobSizeBytes: number;
|
|
25
|
+
}
|
|
26
|
+
/** Reservation and presign grants deliberately share one expiry horizon. */
|
|
27
|
+
export declare const BLOB_RESERVATION_TTL_MS: number;
|
|
28
|
+
/**
|
|
29
|
+
* The two presigned routes. No bearer deps at all — there is no principal to
|
|
30
|
+
* build a facade from, and the proxy is the only thing they need.
|
|
31
|
+
*/
|
|
32
|
+
export interface BlobContentRouteDeps {
|
|
33
|
+
readonly contentProxy: BlobContentProxy;
|
|
34
|
+
}
|
|
35
|
+
export declare function createBlobHandler(deps: BlobRouteDeps): (c: Context) => Promise<Response>;
|
|
36
|
+
export declare function finalizeBlobHandler(deps: BlobRouteDeps): (c: Context) => Promise<Response>;
|
|
37
|
+
export declare function blobDownloadUrlHandler(deps: BlobRouteDeps): (c: Context) => Promise<Response>;
|
|
38
|
+
export declare function blobUploadContentHandler(deps: BlobContentRouteDeps): (c: Context) => Promise<Response>;
|
|
39
|
+
export declare function blobDownloadContentHandler(deps: BlobContentRouteDeps): (c: Context) => Promise<Response>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type BoardPage } from '@byok-sdk/core';
|
|
2
|
+
import type { Context } from 'hono';
|
|
3
|
+
import { type DeviceRouteDeps } from './shared';
|
|
4
|
+
export declare const DEFAULT_BOARD_PAGE_LIMIT = 50;
|
|
5
|
+
export declare const DEFAULT_BOARD_STREAM_QUERY_INTERVAL_MS = 5000;
|
|
6
|
+
export declare const DEFAULT_BOARD_STREAM_HEARTBEAT_INTERVAL_MS = 15000;
|
|
7
|
+
export declare const DEFAULT_BOARD_STREAM_RECONCILIATION_INTERVAL_MS = 120000;
|
|
8
|
+
export interface BoardRouteDeps extends DeviceRouteDeps {
|
|
9
|
+
readonly pageLimit: number;
|
|
10
|
+
}
|
|
11
|
+
export interface BoardStreamRouteDeps extends BoardRouteDeps {
|
|
12
|
+
readonly queryIntervalMs: number;
|
|
13
|
+
readonly heartbeatIntervalMs: number;
|
|
14
|
+
readonly reconciliationIntervalMs: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function boardListHandler(deps: BoardRouteDeps): (c: Context) => Promise<Response>;
|
|
17
|
+
export declare function boardClaimHandler(deps: DeviceRouteDeps): (c: Context) => Promise<Response>;
|
|
18
|
+
export declare function boardUnclaimHandler(deps: DeviceRouteDeps): (c: Context) => Promise<Response>;
|
|
19
|
+
export declare function boardStatusHandler(deps: DeviceRouteDeps): (c: Context) => Promise<Response>;
|
|
20
|
+
export declare function boardStreamHandler(deps: BoardStreamRouteDeps): (c: Context) => Promise<Response>;
|
|
21
|
+
export type BoardPollResponse = BoardPage;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /byok/capabilities` — the declaration route (ADR-010).
|
|
3
|
+
*
|
|
4
|
+
* Public by design: a client has to be able to read what a deployment supports
|
|
5
|
+
* before it holds a credential, and the declaration is a deployment-level
|
|
6
|
+
* fact, not a tenant-level one. Nothing tenant-scoped may ever be added to
|
|
7
|
+
* this response for exactly that reason.
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from 'hono';
|
|
10
|
+
import type { CapabilityDeclaration } from '@byok-sdk/core';
|
|
11
|
+
export interface CapabilitiesRouteDeps {
|
|
12
|
+
readonly declaration: CapabilityDeclaration;
|
|
13
|
+
}
|
|
14
|
+
export declare function capabilitiesHandler(deps: CapabilitiesRouteDeps): (c: Context) => Promise<Response>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /byok/events?cursor=N` — the long-poll receive half (§8).
|
|
3
|
+
*
|
|
4
|
+
* Three properties the daemon's transport depends on, reproduced against the
|
|
5
|
+
* core mailbox rather than an in-process outbox:
|
|
6
|
+
*
|
|
7
|
+
* - **Reading is not acknowledging.** `readAfter` never moves the cursor. The
|
|
8
|
+
* ONLY ack is the cursor the daemon brings back on its NEXT poll, after it
|
|
9
|
+
* has durably processed what it was handed — which is why the ack below
|
|
10
|
+
* happens before the read, against the incoming `cursor`, and why a poll
|
|
11
|
+
* that hands back the same cursor replays the same page forever.
|
|
12
|
+
* - **The ack is monotonic.** A cursor at or below what the device already
|
|
13
|
+
* acked carries no new information and is not an ack attempt; only a
|
|
14
|
+
* strictly higher one advances. The store's own
|
|
15
|
+
* `mailbox_cursor_regression` guard stays the authority on going backwards
|
|
16
|
+
* (asserted directly in `src/__tests__/mailbox-cursor.test.ts`).
|
|
17
|
+
* - **The hold.** An empty poll is held open instead of answered immediately,
|
|
18
|
+
* so an idle daemon is not a busy-loop. The hold is a bounded re-read, not a
|
|
19
|
+
* registered waiter: a waiter map is exactly the cross-request state a
|
|
20
|
+
* stateless handler may not keep (S3.5 boxes 14-15), and a second cloud
|
|
21
|
+
* instance would not see it anyway.
|
|
22
|
+
*/
|
|
23
|
+
import type { Context } from 'hono';
|
|
24
|
+
import { type DeviceRouteDeps } from './shared';
|
|
25
|
+
export interface EventsRouteDeps extends DeviceRouteDeps {
|
|
26
|
+
/** How long an empty poll is held open, ms. */
|
|
27
|
+
readonly longPollHoldMs: number;
|
|
28
|
+
/** How often the mailbox is re-read while holding, ms. */
|
|
29
|
+
readonly longPollIntervalMs: number;
|
|
30
|
+
/** Max rows per response. */
|
|
31
|
+
readonly pageLimit: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function eventsHandler(deps: EventsRouteDeps): (c: Context) => Promise<Response>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /byok/messages` — the long-poll send half (§8.2).
|
|
3
|
+
*
|
|
4
|
+
* A device long-polling for cloud -> daemon traffic has no live socket to
|
|
5
|
+
* carry its own outbound envelopes; this batches them over authed HTTP. Every
|
|
6
|
+
* envelope goes through the same gate (`../inbound.ts`) in the same order, so
|
|
7
|
+
* claim/progress/complete behave identically no matter which transport carried
|
|
8
|
+
* them.
|
|
9
|
+
*
|
|
10
|
+
* The batch stays tolerant at the schema level — one malformed envelope must
|
|
11
|
+
* not 400 the whole request — so only a structurally invalid `Envelope` or an
|
|
12
|
+
* oversized batch (`MAX_MESSAGES_PER_BATCH`) fails the request outright.
|
|
13
|
+
*
|
|
14
|
+
* Rate limiting is per REQUEST, not per envelope: the moment any envelope
|
|
15
|
+
* comes back `rate_limited`, the rest of the batch is abandoned (its bucket is
|
|
16
|
+
* empty, so every remaining envelope would limit too) and the WHOLE request
|
|
17
|
+
* answers 429. Envelopes processed earlier in the same batch already took
|
|
18
|
+
* effect — there are no rollback semantics here — which is safe because every
|
|
19
|
+
* `task.*` type is idempotent (§9): a client that retries the same batch gets
|
|
20
|
+
* `duplicate` for whatever already landed.
|
|
21
|
+
*/
|
|
22
|
+
import type { Context } from 'hono';
|
|
23
|
+
import type { ActivityBounds } from '../coordination';
|
|
24
|
+
import { type DeviceRouteDeps } from './shared';
|
|
25
|
+
export interface MessagesRouteDeps extends DeviceRouteDeps {
|
|
26
|
+
readonly activityBounds: ActivityBounds;
|
|
27
|
+
}
|
|
28
|
+
export declare function messagesHandler(deps: MessagesRouteDeps): (c: Context) => Promise<Response>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import { type ActivityBounds } from '../coordination';
|
|
3
|
+
import { type DeviceRouteDeps } from './shared';
|
|
4
|
+
export interface PresenceRouteDeps extends DeviceRouteDeps {
|
|
5
|
+
readonly ttlMs: number;
|
|
6
|
+
readonly minimumIntervalMs: number;
|
|
7
|
+
readonly detailMaxBytes: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ActivityRouteDeps extends DeviceRouteDeps {
|
|
10
|
+
readonly bounds: ActivityBounds;
|
|
11
|
+
}
|
|
12
|
+
export declare function presencePublishHandler(deps: PresenceRouteDeps): (c: Context) => Promise<Response>;
|
|
13
|
+
export declare function activityAppendHandler(deps: ActivityRouteDeps): (c: Context) => Promise<Response>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two things every device-facing handler starts from: a tolerant JSON body
|
|
3
|
+
* read, and the bearer -> principal -> tenant-closed-facade step.
|
|
4
|
+
*
|
|
5
|
+
* Once {@link authenticateDevice} has answered, a handler holds a
|
|
6
|
+
* `TenantStores` and a `DevicePrincipal` and never sees a `TenantId` again.
|
|
7
|
+
*/
|
|
8
|
+
import type { Context } from 'hono';
|
|
9
|
+
import type { DevicePrincipal } from '@byok-sdk/core';
|
|
10
|
+
import { type BearerAuthDeps } from '../auth/bearer';
|
|
11
|
+
import { type CloudRootStores, type TenantStores } from '../tenant-stores';
|
|
12
|
+
/** Every error response on this surface is `{ error }` — the same shape the reference server uses. */
|
|
13
|
+
export interface ErrorBody {
|
|
14
|
+
readonly error: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function readJsonBody(c: Context): Promise<unknown>;
|
|
17
|
+
export interface DeviceRouteDeps {
|
|
18
|
+
readonly bearer: BearerAuthDeps;
|
|
19
|
+
readonly root: CloudRootStores;
|
|
20
|
+
}
|
|
21
|
+
export interface AuthenticatedDeviceContext {
|
|
22
|
+
readonly device: DevicePrincipal;
|
|
23
|
+
readonly stores: TenantStores;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `undefined` means "answer 401" — and it means that for a missing header, a
|
|
27
|
+
* forged token, an expired token, a revoked device, a product mismatch, and a
|
|
28
|
+
* token whose tenant does not own the device, indistinguishably.
|
|
29
|
+
*/
|
|
30
|
+
export declare function authenticateDevice(c: Context, deps: DeviceRouteDeps): Promise<AuthenticatedDeviceContext | undefined>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import { type DeviceProofAuthDeps } from '../auth/device-proof';
|
|
3
|
+
import { type TruthCommitter, type TruthObjectDownloads } from '../truth/contract';
|
|
4
|
+
export declare const DEVICE_PROOF_HEADER = "x-byok-device-proof";
|
|
5
|
+
export declare const DEFAULT_MAX_TRUTH_REQUEST_BYTES: number;
|
|
6
|
+
export declare const MAX_DEVICE_PROOF_HEADER_BYTES: number;
|
|
7
|
+
export interface TruthRouteDeps {
|
|
8
|
+
readonly proof: DeviceProofAuthDeps;
|
|
9
|
+
readonly truth: TruthCommitter;
|
|
10
|
+
readonly objectDownloads: TruthObjectDownloads;
|
|
11
|
+
readonly maxRequestBytes: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function truthManifestHandler(deps: TruthRouteDeps): (c: Context) => Promise<Response>;
|
|
14
|
+
export declare function truthGetHandler(deps: TruthRouteDeps): (c: Context) => Promise<Response>;
|
|
15
|
+
export declare function truthPutHandler(deps: TruthRouteDeps): (c: Context) => Promise<Response>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single inbound choke point for every daemon -> cloud envelope.
|
|
3
|
+
*
|
|
4
|
+
* The reference server runs this gate inside a live `ConnectionHub`; here it
|
|
5
|
+
* is a pure function over a tenant-closed facade, so the same order holds with
|
|
6
|
+
* no connection, no session, and no cross-request state:
|
|
7
|
+
*
|
|
8
|
+
* 0. **rate limit** — one token per inbound envelope, debited BEFORE anything
|
|
9
|
+
* else, so a flood of garbage-typed envelopes costs the same budget as a
|
|
10
|
+
* flood of well-formed ones. S3a's reference limiter allows everything; the
|
|
11
|
+
* seam is what matters at this position.
|
|
12
|
+
* 1. **type-allow** — only `DAEMON_TO_SERVER_TYPES` may pass. A server ->
|
|
13
|
+
* daemon type arriving inbound, or anything unrecognized, is rejected
|
|
14
|
+
* before it is dispatched or counted accepted.
|
|
15
|
+
* 2. **ownership** — an envelope for a task already owned by a DIFFERENT
|
|
16
|
+
* device is dropped, never force-failed: force-failing on an authz mismatch
|
|
17
|
+
* would let an attacker who merely guesses a `taskId` kill the real owner's
|
|
18
|
+
* task. A task with no owner yet, or that this tenant does not have at all,
|
|
19
|
+
* is not rejected here — the store's own no-op-on-missing behavior covers
|
|
20
|
+
* the latter, and it covers it per tenant, so a guessed id from another
|
|
21
|
+
* tenant writes nothing anywhere.
|
|
22
|
+
* 3. **dedup** — an envelope id already seen from this device is a no-op. The
|
|
23
|
+
* wire is at-least-once (§9); this makes processing at-most-once.
|
|
24
|
+
* 4. **apply** — the lifecycle write.
|
|
25
|
+
*
|
|
26
|
+
* A duplicate is still a wire-level success (§8.2): it just did not re-run
|
|
27
|
+
* anything. Only `rejected`/`rate_limited` are excluded from `accepted`.
|
|
28
|
+
*/
|
|
29
|
+
import { type Envelope } from '@byok-sdk/protocol';
|
|
30
|
+
import { type ActivityBounds } from './coordination';
|
|
31
|
+
import type { TenantStores } from './tenant-stores';
|
|
32
|
+
export type InboundOutcome = 'accepted' | 'duplicate' | 'rejected' | 'rate_limited';
|
|
33
|
+
/** Receipt key a task's terminal is recorded under — the idempotency seam S3b's journal will share. */
|
|
34
|
+
export declare function terminalReceiptKey(taskId: string): string;
|
|
35
|
+
export declare function handleInboundEnvelope(stores: TenantStores, deviceId: string, envelope: Envelope, activityBounds?: ActivityBounds): Promise<InboundOutcome>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@byok-sdk/cloud` — the hosted device surface.
|
|
3
|
+
*
|
|
4
|
+
* Cloud sits BESIDE `@byok-sdk/server`, not above it (§12.1): `cloud → core +
|
|
5
|
+
* protocol`, and never `cloud → server`. The self-hosted embedded coordinator
|
|
6
|
+
* stays the self-hosted option; this package serves the same frozen v1 device
|
|
7
|
+
* wire contract statelessly, over `@byok-sdk/core` ports plus the cloud-local auth
|
|
8
|
+
* and task ports in `stores/ports.ts`.
|
|
9
|
+
*
|
|
10
|
+
* `src/__tests__/constraints.test.ts` asserts the properties this export list
|
|
11
|
+
* implies: no `@byok-sdk/server` import, no `node:` import, no module-level
|
|
12
|
+
* mutable state, no session/Running map, and no route mounted outside the I1
|
|
13
|
+
* registry.
|
|
14
|
+
*/
|
|
15
|
+
export { isTenantId, tenantId } from '@byok-sdk/core';
|
|
16
|
+
export type { TenantId } from '@byok-sdk/core';
|
|
17
|
+
export { createByokCloud } from './cloud';
|
|
18
|
+
export type { ByokCloud, ByokCloudOptions, EnqueueOfferInput, EnqueuedOffer } from './cloud';
|
|
19
|
+
export { DEFAULT_EVENTS_PAGE_LIMIT, DEFAULT_LONG_POLL_HOLD_MS, DEFAULT_LONG_POLL_INTERVAL_MS, DEFAULT_MAX_BLOB_SIZE_BYTES, } from './cloud';
|
|
20
|
+
export { DEFAULT_BOARD_PAGE_LIMIT, DEFAULT_BOARD_STREAM_HEARTBEAT_INTERVAL_MS, DEFAULT_BOARD_STREAM_QUERY_INTERVAL_MS, DEFAULT_BOARD_STREAM_RECONCILIATION_INTERVAL_MS, } from './handlers/board';
|
|
21
|
+
export { createInMemoryByokCloud } from './composition/in-memory';
|
|
22
|
+
export type { InMemoryByokCloud, InMemoryByokCloudOptions } from './composition/in-memory';
|
|
23
|
+
export { ByokCloudError, CLOUD_ERROR_CODES, isCloudError } from './errors';
|
|
24
|
+
export type { CloudErrorCode } from './errors';
|
|
25
|
+
export { CLOUD_CAPABILITIES, CapabilitiesResponseSchema, declares, fullCapabilityDeclaration, } from './capabilities';
|
|
26
|
+
export type { CapabilitiesResponse, CloudCapability, FullCapabilityDeclarationOptions, } from './capabilities';
|
|
27
|
+
export { CloudRouteRegistry, ROUTE_CLASSES, ROUTE_METHODS, routeKey } from './router/registry';
|
|
28
|
+
export type { CloudRouteHandler, RouteClass, RouteDescriptor, RouteMethod } from './router/registry';
|
|
29
|
+
export { ACCESS_TOKEN_TTL_SECONDS, createHmacTokenSigner } from './auth/tokens';
|
|
30
|
+
export type { AccessTokenClaims, TokenSigner } from './auth/tokens';
|
|
31
|
+
export { NONCE_SIGNING_DOMAIN, verifyNonceSignature } from './auth/verify';
|
|
32
|
+
export { authenticateBearer, extractBearerToken } from './auth/bearer';
|
|
33
|
+
export type { BearerAuthDeps } from './auth/bearer';
|
|
34
|
+
export { DEFAULT_DEVICE_PROOF_CLOCK_SKEW_MS, DEFAULT_DEVICE_PROOF_MAX_LIFETIME_MS, MAX_DEVICE_PROOF_CLOCK_SKEW_MS, MAX_DEVICE_PROOF_MAX_LIFETIME_MS, authenticateDeviceProof, } from './auth/device-proof';
|
|
35
|
+
export type { AuthenticatedDeviceProof, DeviceProofAuthDeps, DeviceProofRequestBinding, } from './auth/device-proof';
|
|
36
|
+
export { DEVICE_IDENTITY_PROOF_KEY_EPOCH, DEVICE_IDENTITY_PROOF_KEY_ID, PAIRING_CODE_TTL_MS, createAuthPlane, } from './auth/plane';
|
|
37
|
+
export type { AuthPlane, AuthPlaneDeps, MintedAccessToken, PairInput } from './auth/plane';
|
|
38
|
+
export { createWebCrypto } from './crypto/web-crypto';
|
|
39
|
+
export type { CloudCrypto } from './crypto/port';
|
|
40
|
+
export { handleInboundEnvelope, terminalReceiptKey } from './inbound';
|
|
41
|
+
export type { InboundOutcome } from './inbound';
|
|
42
|
+
export { tenantStoresFor } from './tenant-stores';
|
|
43
|
+
export type { TenantBoundActivity, TenantBoundBoard, CloudRootStores, TenantBoundBlobs, TenantBoundDedup, TenantBoundDevices, TenantBoundMailbox, TenantBoundPresence, TenantBoundQuota, TenantBoundRateLimiter, TenantBoundReceipts, TenantBoundSequence, TenantBoundTaskAttempts, TenantStores, } from './tenant-stores';
|
|
44
|
+
export { DEFAULT_ACTIVITY_BOUNDS, DEFAULT_ACTIVITY_CAPACITY, DEFAULT_ACTIVITY_MAX_BYTES, DEFAULT_ACTIVITY_MAX_EVENTS, DEFAULT_ACTIVITY_TTL_MS, DEFAULT_BOARD_CHANNEL_MAX_BYTES, DEFAULT_BOARD_TITLE_MAX_BYTES, DEFAULT_PRESENCE_DETAIL_MAX_BYTES, DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS, DEFAULT_PRESENCE_TTL_MS, } from './coordination';
|
|
45
|
+
export type { ActivityBounds } from './coordination';
|
|
46
|
+
export { BoardFeedClient, BoardFeedRetryableError, BoardFeedStoppedError } from './coordination-client';
|
|
47
|
+
export type { BoardFeedClientOptions, BoardFeedItem, BoardFeedMode, BoardFeedPage, BoardFeedRead, } from './coordination-client';
|
|
48
|
+
export { TRUTH_BATCH_MAX_RECORDS, TRUTH_INLINE_CONTENT_TYPE, TRUTH_LABEL_MAX_LENGTH, TRUTH_MANIFEST_MAX_LIMIT, TRUTH_RECORD_CAPABILITY, TRUTH_RECORD_KEY_MAX_LENGTH, TRUTH_REQUEST_ID_MAX_LENGTH, TruthBodyInputSchema, TruthCommitResponseSchema, TruthRecordKeySchema, TruthRecordMetadataSchema, TruthWriteRequestSchema, truthManifestMetadata, truthRecordMetadata, } from './truth/contract';
|
|
49
|
+
export { DEFAULT_MAX_TRUTH_REQUEST_BYTES, DEVICE_PROOF_HEADER, MAX_DEVICE_PROOF_HEADER_BYTES, } from './handlers/truth';
|
|
50
|
+
export type { PreparedTruthWrite, TruthBodyInput, TruthCommitInput, TruthCommitResponse, TruthCommitResult, TruthCommitter, TruthObjectDownloads, TruthRecordMetadata, TruthWriteRequest, } from './truth/contract';
|
|
51
|
+
export { TruthCommitError, isTruthCommitError } from './truth/errors';
|
|
52
|
+
export type { TruthCommitErrorCode } from './truth/errors';
|
|
53
|
+
export { CLOUD_STORE_NAMES, TASK_ATTEMPT_STATUSES } from './stores/ports';
|
|
54
|
+
export { CLOUD_PORT_INTERFACES, CLOUD_PORT_METHODS } from './stores/ports-contract';
|
|
55
|
+
export type { BlobContent, BlobContentProxy, BlobDeclaration, BlobObservation, BlobWriteResult, CloudBlobStore, CloudStoreName, CloudStores, DeviceDirectory, DeviceRecord, DeviceRegistration, DeviceSequenceStore, InboundDedupStore, InboundRateLimiter, NonceStore, PairingCodeClaims, PairingCodeInfo, PairingCodeIssueInput, PairingCodeStore, ProofRequestReceipt, ProofRequestReceiptInput, ProofRequestReceiptStore, RequestReceipt, RequestReceiptStore, TaskAttempt, TaskAttemptStatus, TaskAttemptStore, } from './stores/ports';
|
|
56
|
+
export { AllowAllRateLimiter, BLOB_URL_TTL_MS, DEDUP_RING_CAPACITY, InMemoryBlobContentProxy, InMemoryCloudBlobStore, InMemoryDeviceDirectory, InMemoryDeviceSequenceStore, InMemoryInboundDedupStore, InMemoryNonceStore, InMemoryPairingCodeStore, InMemoryRequestReceiptStore, InMemoryProofRequestReceiptStore, InMemoryTaskAttemptStore, NONCE_TTL_MS, createInMemoryBlobs, createInMemoryCloudStores, } from './stores/in-memory/index';
|
|
57
|
+
export type { InMemoryBlobStoreOptions, InMemoryBlobs, InMemoryCloudComposition, } from './stores/in-memory/index';
|