@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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/auth/bearer.d.ts +25 -0
  4. package/dist/auth/device-proof.d.ts +38 -0
  5. package/dist/auth/plane.d.ts +67 -0
  6. package/dist/auth/tokens.d.ts +37 -0
  7. package/dist/auth/verify.d.ts +22 -0
  8. package/dist/board-projection.d.ts +9 -0
  9. package/dist/capabilities.d.ts +71 -0
  10. package/dist/cloud.d.ts +123 -0
  11. package/dist/composition/in-memory.d.ts +55 -0
  12. package/dist/coordination-client.d.ts +87 -0
  13. package/dist/coordination.d.ts +29 -0
  14. package/dist/crypto/port.d.ts +44 -0
  15. package/dist/crypto/web-crypto.d.ts +28 -0
  16. package/dist/errors.d.ts +45 -0
  17. package/dist/handlers/auth.d.ts +21 -0
  18. package/dist/handlers/blobs.d.ts +39 -0
  19. package/dist/handlers/board.d.ts +21 -0
  20. package/dist/handlers/capabilities.d.ts +14 -0
  21. package/dist/handlers/events.d.ts +33 -0
  22. package/dist/handlers/messages.d.ts +28 -0
  23. package/dist/handlers/presence.d.ts +13 -0
  24. package/dist/handlers/shared.d.ts +30 -0
  25. package/dist/handlers/truth.d.ts +15 -0
  26. package/dist/inbound.d.ts +35 -0
  27. package/dist/index.d.ts +57 -0
  28. package/dist/index.js +2419 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/router/registry.d.ts +56 -0
  31. package/dist/stores/in-memory/blobs.d.ts +89 -0
  32. package/dist/stores/in-memory/dedup.d.ts +17 -0
  33. package/dist/stores/in-memory/device-directory.d.ts +19 -0
  34. package/dist/stores/in-memory/index.d.ts +36 -0
  35. package/dist/stores/in-memory/nonces.d.ts +22 -0
  36. package/dist/stores/in-memory/pairing-codes.d.ts +18 -0
  37. package/dist/stores/in-memory/proof-receipts.d.ts +11 -0
  38. package/dist/stores/in-memory/rate-limiter.d.ts +13 -0
  39. package/dist/stores/in-memory/receipts.d.ts +21 -0
  40. package/dist/stores/in-memory/sequence.d.ts +11 -0
  41. package/dist/stores/in-memory/task-attempts.d.ts +36 -0
  42. package/dist/stores/ports-contract.d.ts +20 -0
  43. package/dist/stores/ports.d.ts +309 -0
  44. package/dist/tenant-stores.d.ts +124 -0
  45. package/dist/truth/contract.d.ts +149 -0
  46. package/dist/truth/errors.d.ts +8 -0
  47. package/package.json +52 -0
package/dist/index.js ADDED
@@ -0,0 +1,2419 @@
1
+ import { BOARD_STATUSES, PRESENCE_LEVELS, CapabilityDeclarationSchema, hasCapability, isTenantId, tenantId, principalTenant, parseDeviceProofEnvelope, deviceProofSigningInput, contentHash, parseCapabilityDeclaration, ByokCoreError, tenantKey, createInMemoryCoreStores, assertCapability, isCoreConflictError, isCoreError, TRUTH_RECORD_KINDS, STORAGE_ERROR_CODES, STORAGE_ERROR_HTTP_STATUS } from '@byok-sdk/core';
2
+ export { isTenantId, tenantId } from '@byok-sdk/core';
3
+ import { AgentEventOrUnknownSchema, DAEMON_TO_SERVER_TYPES, encodeEnvelope, createEnvelope, PairRequestSchema, ChallengeRequestSchema, TokenRequestSchema, decodeEnvelope, MessagesSendRequestSchema, CreateBlobRequestSchema } from '@byok-sdk/protocol';
4
+ import { z } from 'zod';
5
+ import { Hono } from 'hono';
6
+
7
+ // src/index.ts
8
+
9
+ // src/crypto/web-crypto.ts
10
+ var PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
11
+ var HEX_DIGITS = "0123456789abcdef";
12
+ function base64UrlEncode(bytes) {
13
+ let binary = "";
14
+ for (const byte of bytes) binary += String.fromCharCode(byte);
15
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
16
+ }
17
+ function base64UrlDecode(value) {
18
+ const padded = value.replaceAll("-", "+").replaceAll("_", "/");
19
+ try {
20
+ const binary = atob(padded);
21
+ const bytes = new Uint8Array(binary.length);
22
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
23
+ return bytes;
24
+ } catch {
25
+ return void 0;
26
+ }
27
+ }
28
+ function toHex(bytes) {
29
+ let out = "";
30
+ for (const byte of bytes) {
31
+ out += HEX_DIGITS[byte >> 4 & 15];
32
+ out += HEX_DIGITS[byte & 15];
33
+ }
34
+ return out;
35
+ }
36
+ function subtle() {
37
+ const webCrypto = globalThis.crypto;
38
+ if (webCrypto?.subtle === void 0) {
39
+ throw new Error("@byok-sdk/cloud requires a WebCrypto implementation on globalThis.crypto");
40
+ }
41
+ return webCrypto.subtle;
42
+ }
43
+ function createWebCrypto() {
44
+ return {
45
+ randomUuid() {
46
+ return globalThis.crypto.randomUUID();
47
+ },
48
+ randomToken(byteLength) {
49
+ return base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(byteLength)));
50
+ },
51
+ randomPairingCode(length) {
52
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(length));
53
+ let out = "";
54
+ for (const byte of bytes) {
55
+ out += PAIRING_CODE_ALPHABET[byte % PAIRING_CODE_ALPHABET.length];
56
+ }
57
+ return out;
58
+ },
59
+ async verifyEd25519(publicKeyBase64Url, message, signature) {
60
+ const signatureBytes = base64UrlDecode(signature);
61
+ if (signatureBytes === void 0) return false;
62
+ try {
63
+ const key = await subtle().importKey(
64
+ "jwk",
65
+ { kty: "OKP", crv: "Ed25519", x: publicKeyBase64Url },
66
+ { name: "Ed25519" },
67
+ false,
68
+ ["verify"]
69
+ );
70
+ const messageBytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
71
+ return await subtle().verify({ name: "Ed25519" }, key, signatureBytes, messageBytes);
72
+ } catch {
73
+ return false;
74
+ }
75
+ },
76
+ async hmacSha256(secret, message) {
77
+ const key = await subtle().importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
78
+ const signature = await subtle().sign("HMAC", key, new TextEncoder().encode(message));
79
+ return base64UrlEncode(new Uint8Array(signature));
80
+ },
81
+ async sha256(data) {
82
+ const digest = await subtle().digest("SHA-256", data);
83
+ return `sha256:${toHex(new Uint8Array(digest))}`;
84
+ },
85
+ timingSafeEqual(left, right) {
86
+ const leftBytes = base64UrlDecode(left);
87
+ const rightBytes = base64UrlDecode(right);
88
+ if (leftBytes === void 0 || rightBytes === void 0) return false;
89
+ if (leftBytes.length !== rightBytes.length) return false;
90
+ let diff = 0;
91
+ for (let index = 0; index < leftBytes.length; index += 1) {
92
+ diff |= (leftBytes[index] ?? 0) ^ (rightBytes[index] ?? 0);
93
+ }
94
+ return diff === 0;
95
+ }
96
+ };
97
+ }
98
+
99
+ // src/auth/tokens.ts
100
+ var ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
101
+ function encodeSegment(value) {
102
+ return base64UrlEncode(new TextEncoder().encode(JSON.stringify(value)));
103
+ }
104
+ function createHmacTokenSigner(secret, clock) {
105
+ async function signingKey(usage) {
106
+ return globalThis.crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, [usage]);
107
+ }
108
+ async function signature(signingInput) {
109
+ const key = await signingKey("sign");
110
+ const bytes = await globalThis.crypto.subtle.sign("HMAC", key, new TextEncoder().encode(signingInput));
111
+ return base64UrlEncode(new Uint8Array(bytes));
112
+ }
113
+ return {
114
+ async sign(claims, expiresInSeconds) {
115
+ const issuedAt = Math.floor(clock.now().getTime() / 1e3);
116
+ const header = encodeSegment({ alg: "HS256", typ: "JWT" });
117
+ const payload = encodeSegment({
118
+ deviceId: claims.deviceId,
119
+ tenantId: claims.tenantId,
120
+ productId: claims.productId,
121
+ iat: issuedAt,
122
+ exp: issuedAt + expiresInSeconds
123
+ });
124
+ const signingInput = `${header}.${payload}`;
125
+ return `${signingInput}.${await signature(signingInput)}`;
126
+ },
127
+ async verify(token) {
128
+ const parts = token.split(".");
129
+ if (parts.length !== 3) return void 0;
130
+ const [header, payload, provided] = parts;
131
+ const key = await signingKey("verify");
132
+ const providedBytes = base64UrlDecode(provided);
133
+ if (providedBytes === void 0) return void 0;
134
+ const ok = await globalThis.crypto.subtle.verify(
135
+ "HMAC",
136
+ key,
137
+ providedBytes,
138
+ new TextEncoder().encode(`${header}.${payload}`)
139
+ );
140
+ if (!ok) return void 0;
141
+ const headerBytes = base64UrlDecode(header);
142
+ const payloadBytes = base64UrlDecode(payload);
143
+ if (headerBytes === void 0 || payloadBytes === void 0) return void 0;
144
+ let decodedHeader;
145
+ let decodedPayload;
146
+ try {
147
+ decodedHeader = JSON.parse(new TextDecoder().decode(headerBytes));
148
+ decodedPayload = JSON.parse(new TextDecoder().decode(payloadBytes));
149
+ } catch {
150
+ return void 0;
151
+ }
152
+ const alg = decodedHeader?.alg;
153
+ if (alg !== "HS256") return void 0;
154
+ const { deviceId, tenantId: tenantId4, productId, exp } = decodedPayload;
155
+ if (typeof deviceId !== "string" || typeof tenantId4 !== "string" || typeof productId !== "string") {
156
+ return void 0;
157
+ }
158
+ if (typeof exp !== "number" || clock.now().getTime() / 1e3 >= exp) return void 0;
159
+ return { deviceId, tenantId: tenantId4, productId };
160
+ }
161
+ };
162
+ }
163
+
164
+ // src/auth/verify.ts
165
+ var NONCE_SIGNING_DOMAIN = "byok-nonce-v1\n";
166
+ function verifyNonceSignature(crypto, devicePublicKey, nonce, signature) {
167
+ return crypto.verifyEd25519(devicePublicKey, NONCE_SIGNING_DOMAIN + nonce, signature);
168
+ }
169
+
170
+ // src/auth/plane.ts
171
+ var PAIRING_CODE_TTL_MS = 10 * 60 * 1e3;
172
+ var PAIRING_CODE_LENGTH = 8;
173
+ var DEVICE_IDENTITY_PROOF_KEY_ID = "identity";
174
+ var DEVICE_IDENTITY_PROOF_KEY_EPOCH = 0;
175
+ function createAuthPlane(deps) {
176
+ const { stores, crypto, clock, tokenSigner } = deps;
177
+ const ttlSeconds = deps.accessTokenTtlSeconds ?? ACCESS_TOKEN_TTL_SECONDS;
178
+ return {
179
+ async createPairingCode(tenant, input) {
180
+ const expiresAt = new Date(clock.now().getTime() + (input.ttlMs ?? PAIRING_CODE_TTL_MS)).toISOString();
181
+ return stores.pairingCodes.issue(tenant, {
182
+ code: crypto.randomPairingCode(PAIRING_CODE_LENGTH),
183
+ productId: input.productId,
184
+ expiresAt
185
+ });
186
+ },
187
+ async redeemAndRegister(input) {
188
+ const claims = await stores.pairingCodes.redeem(input.pairingCode);
189
+ if (claims === void 0) return void 0;
190
+ return stores.devices.register(claims.tenantId, {
191
+ productId: claims.productId,
192
+ deviceId: `dev_${crypto.randomUuid()}`,
193
+ deviceName: input.deviceName,
194
+ devicePublicKey: input.devicePublicKey,
195
+ proofKeyId: DEVICE_IDENTITY_PROOF_KEY_ID,
196
+ proofKeyEpoch: DEVICE_IDENTITY_PROOF_KEY_EPOCH
197
+ });
198
+ },
199
+ async resolveDevice(deviceId) {
200
+ const device = await stores.devices.resolveByDeviceId(deviceId);
201
+ if (device === void 0 || device.revoked) return void 0;
202
+ return device;
203
+ },
204
+ issueNonce(device) {
205
+ return stores.nonces.issue(device.tenantId, device.deviceId);
206
+ },
207
+ validateNonce(device, nonce) {
208
+ return stores.nonces.validate(device.tenantId, device.deviceId, nonce);
209
+ },
210
+ consumeNonce(device, nonce) {
211
+ return stores.nonces.markUsed(device.tenantId, nonce);
212
+ },
213
+ verifySignature(device, nonce, signature) {
214
+ return verifyNonceSignature(crypto, device.devicePublicKey, nonce, signature);
215
+ },
216
+ async mintAccessToken(device) {
217
+ const accessToken = await tokenSigner.sign(
218
+ { deviceId: device.deviceId, tenantId: device.tenantId, productId: device.productId },
219
+ ttlSeconds
220
+ );
221
+ return {
222
+ accessToken,
223
+ expiresAt: new Date(clock.now().getTime() + ttlSeconds * 1e3).toISOString()
224
+ };
225
+ }
226
+ };
227
+ }
228
+ var CLOUD_CAPABILITIES = {
229
+ /** `GET /byok/events` long-poll receive (§8). */
230
+ eventsLongPoll: "events.longpoll",
231
+ /** `POST /byok/messages` batched send (§8.2). */
232
+ messagesBatch: "messages.batch",
233
+ /** The three bearer-authed blob routes: reserve/grant, explicit finalize, committed-only download (§7). */
234
+ blobsPresigned: "blobs.presigned",
235
+ /**
236
+ * The two presigned `/byok/blobs/:id/content` routes — cloud carrying the
237
+ * bytes itself.
238
+ *
239
+ * Split out of `blobs.presigned` because it was one capability describing two
240
+ * separable facts. A composition whose bytes live in object storage mints
241
+ * grants (`blobs.presigned`) but has no byte-proxy path at all, and saying so
242
+ * by declaration is ADR-010's whole posture: a client reads what a deployment
243
+ * serves, it never probes a `/content` route and interprets the status code.
244
+ *
245
+ * Spelled all-lowercase because core's declaration schema pins capability
246
+ * names to `/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/` — the same reason the sibling
247
+ * above reads `events.longpoll` and not `events.longPoll`.
248
+ */
249
+ blobsContentProxy: "blobs.contentproxy",
250
+ /** Board list/claim/unclaim/status routes. Polling is first-class under this declaration. */
251
+ boardCoordination: "board.coordination",
252
+ /** Additional SSE transport for the same board read model. */
253
+ boardSse: "board.sse",
254
+ /** Device-scoped five-level presence publication. */
255
+ presenceHints: "presence.hints",
256
+ /** Bounded task activity batch publication. */
257
+ activityTail: "activity.tail",
258
+ /** Request-bound device proof record manifest/read/write surface (S6). */
259
+ truthRecords: "truth.records"
260
+ };
261
+ var CapabilitiesResponseSchema = CapabilityDeclarationSchema;
262
+ function fullCapabilityDeclaration(version = 1, options = {}) {
263
+ return {
264
+ schema: "byok-capabilities-v1",
265
+ version,
266
+ capabilities: Object.values(CLOUD_CAPABILITIES).filter(
267
+ (capability) => capability !== CLOUD_CAPABILITIES.truthRecords || options.includeTruthRecords === true
268
+ )
269
+ };
270
+ }
271
+ function declares(declaration, capability) {
272
+ return hasCapability(declaration, capability);
273
+ }
274
+
275
+ // src/errors.ts
276
+ var CLOUD_ERROR_CODES = {
277
+ /** A pairing code that is unknown, expired, or already redeemed (§6.1). */
278
+ pairing_code_invalid: "pairing_code_invalid",
279
+ /** The composition handed a device row whose tenant is not a mintable `TenantId`. */
280
+ device_tenant_invalid: "device_tenant_invalid",
281
+ /**
282
+ * The mailbox assigned a row `seq` that disagrees with the delivery `seq`
283
+ * baked into the enqueued envelope. Loud rather than silent: those two
284
+ * numbers ARE the daemon's redelivery cursor, and a composition whose
285
+ * mailbox numbers rows differently from `DeviceSequenceStore` would
286
+ * mis-deliver every subsequent poll.
287
+ */
288
+ mailbox_seq_mismatch: "mailbox_seq_mismatch",
289
+ /** A capability declaration the host supplied that core refused. */
290
+ capability_declaration_invalid: "capability_declaration_invalid",
291
+ /**
292
+ * The declaration names a capability this composition cannot serve, so the
293
+ * deployment would publish a surface it does not have (ADR-010).
294
+ *
295
+ * Construction-time and fatal. A client learns what a deployment supports by
296
+ * READING the declaration and is entitled to act on it without probing, so a
297
+ * declaration that over-states is not a degraded deployment — it is a
298
+ * deployment whose one honest interface lies.
299
+ */
300
+ capability_over_declared: "capability_over_declared",
301
+ /** Host-supplied board labels or coordination input exceeded the explicit contract. */
302
+ coordination_input_invalid: "coordination_input_invalid",
303
+ /** A progress/activity batch exceeded the configured event or byte ceiling. */
304
+ activity_batch_too_large: "activity_batch_too_large"
305
+ };
306
+ var ByokCloudError = class extends Error {
307
+ code;
308
+ constructor(code, message, options) {
309
+ super(message, options);
310
+ this.name = "ByokCloudError";
311
+ this.code = code;
312
+ }
313
+ };
314
+ function isCloudError(value, code) {
315
+ if (!(value instanceof ByokCloudError)) return false;
316
+ return code === void 0 || value.code === code;
317
+ }
318
+
319
+ // src/coordination.ts
320
+ var DEFAULT_BOARD_CHANNEL_MAX_BYTES = 128;
321
+ var DEFAULT_BOARD_TITLE_MAX_BYTES = 512;
322
+ var DEFAULT_ACTIVITY_MAX_EVENTS = 50;
323
+ var DEFAULT_ACTIVITY_MAX_BYTES = 64 * 1024;
324
+ var DEFAULT_ACTIVITY_CAPACITY = 50;
325
+ var DEFAULT_ACTIVITY_TTL_MS = 10 * 6e4;
326
+ var DEFAULT_PRESENCE_TTL_MS = 9e4;
327
+ var DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS = 5e3;
328
+ var DEFAULT_PRESENCE_DETAIL_MAX_BYTES = 512;
329
+ var encoder = new TextEncoder();
330
+ var DEFAULT_ACTIVITY_BOUNDS = {
331
+ maxEvents: DEFAULT_ACTIVITY_MAX_EVENTS,
332
+ maxBytes: DEFAULT_ACTIVITY_MAX_BYTES,
333
+ capacity: DEFAULT_ACTIVITY_CAPACITY,
334
+ ttlMs: DEFAULT_ACTIVITY_TTL_MS
335
+ };
336
+ function assertBoardLabels(channel, title, limits) {
337
+ if (channel.length === 0 || encoder.encode(channel).length > limits.channelMaxBytes) {
338
+ throw new ByokCloudError(
339
+ "coordination_input_invalid",
340
+ `Board channel must contain 1..${limits.channelMaxBytes} UTF-8 bytes.`
341
+ );
342
+ }
343
+ if (title.length === 0 || encoder.encode(title).length > limits.titleMaxBytes) {
344
+ throw new ByokCloudError(
345
+ "coordination_input_invalid",
346
+ `Board title must contain 1..${limits.titleMaxBytes} UTF-8 bytes.`
347
+ );
348
+ }
349
+ }
350
+ function activityDetails(events, dropped, bounds) {
351
+ if (!Number.isSafeInteger(dropped) || dropped < 0) {
352
+ throw new ByokCloudError(
353
+ "coordination_input_invalid",
354
+ "Activity dropped must be a non-negative integer."
355
+ );
356
+ }
357
+ if (events.length === 0 || events.length > bounds.maxEvents) {
358
+ throw new ByokCloudError(
359
+ "activity_batch_too_large",
360
+ `Activity batch must contain 1..${bounds.maxEvents} events.`
361
+ );
362
+ }
363
+ const encoded = JSON.stringify(events);
364
+ if (encoder.encode(encoded).length > bounds.maxBytes) {
365
+ throw new ByokCloudError(
366
+ "activity_batch_too_large",
367
+ `Activity batch exceeds ${bounds.maxBytes} UTF-8 bytes.`
368
+ );
369
+ }
370
+ return events.map((event) => JSON.stringify(event));
371
+ }
372
+ async function appendActivityEvents(activity, input, bounds) {
373
+ return activity.append({
374
+ taskId: input.taskId,
375
+ details: activityDetails(input.events, input.dropped, bounds),
376
+ dropped: input.dropped,
377
+ ttlMs: bounds.ttlMs,
378
+ capacity: bounds.capacity
379
+ });
380
+ }
381
+ function extractBearerToken(header) {
382
+ if (header === void 0) return void 0;
383
+ const match = /^Bearer\s+(.+)$/i.exec(header);
384
+ return match?.[1];
385
+ }
386
+ async function authenticateBearer(header, deps) {
387
+ const token = extractBearerToken(header);
388
+ if (token === void 0) return void 0;
389
+ const claims = await deps.tokenSigner.verify(token);
390
+ if (claims === void 0) return void 0;
391
+ if (!isTenantId(claims.tenantId)) return void 0;
392
+ const device = await deps.devices.get(tenantId(claims.tenantId), claims.deviceId);
393
+ if (device === void 0 || device.revoked) return void 0;
394
+ if (device.productId !== claims.productId) return void 0;
395
+ return {
396
+ kind: "device",
397
+ tenantId: device.tenantId,
398
+ productId: device.productId,
399
+ deviceId: device.deviceId
400
+ };
401
+ }
402
+ function tenantStoresFor(principal, root) {
403
+ const tenant = principalTenant(principal);
404
+ const { core, cloud } = root;
405
+ return {
406
+ tenant,
407
+ principal,
408
+ mailbox: {
409
+ append: (input) => core.mailbox.append(tenant, input),
410
+ readAfter: (query) => core.mailbox.readAfter(tenant, query),
411
+ advanceCursor: (input) => core.mailbox.advanceCursor(tenant, input),
412
+ readCursor: (deviceId) => core.mailbox.readCursor(tenant, deviceId)
413
+ },
414
+ board: {
415
+ create: (input) => core.board.create(tenant, input),
416
+ get: (itemId) => core.board.get(tenant, itemId),
417
+ list: (query) => core.board.list(tenant, query),
418
+ claim: (input) => core.board.claim(tenant, input),
419
+ unclaim: (input) => core.board.unclaim(tenant, input),
420
+ updateStatus: (input) => core.board.updateStatus(tenant, input)
421
+ },
422
+ presence: {
423
+ publish: (input) => core.presence.publish(tenant, input),
424
+ read: (deviceId) => core.presence.read(tenant, deviceId),
425
+ list: () => core.presence.list(tenant)
426
+ },
427
+ activity: {
428
+ append: (input) => core.activity.append(tenant, input),
429
+ read: (taskId) => core.activity.read(tenant, taskId)
430
+ },
431
+ devices: {
432
+ get: (deviceId) => cloud.devices.get(tenant, deviceId),
433
+ list: () => cloud.devices.list(tenant),
434
+ revoke: (deviceId) => cloud.devices.revoke(tenant, deviceId)
435
+ },
436
+ tasks: {
437
+ open: (input) => cloud.tasks.open(tenant, input),
438
+ get: (taskId) => cloud.tasks.get(tenant, taskId),
439
+ claim: (input) => cloud.tasks.claim(tenant, input),
440
+ recordStatus: (input) => cloud.tasks.recordStatus(tenant, input)
441
+ },
442
+ dedup: {
443
+ checkAndRecord: (deviceId, envelopeId) => cloud.dedup.checkAndRecord(tenant, deviceId, envelopeId)
444
+ },
445
+ receipts: {
446
+ record: (input) => cloud.receipts.record(tenant, input),
447
+ get: (key) => cloud.receipts.get(tenant, key)
448
+ },
449
+ blobs: {
450
+ createUpload: (reservation) => cloud.blobs.createUpload(tenant, reservation),
451
+ observeUpload: (blobId, reservation) => cloud.blobs.observeUpload(tenant, blobId, reservation),
452
+ getDownloadUrl: (blobId) => cloud.blobs.getDownloadUrl(tenant, blobId)
453
+ },
454
+ quota: {
455
+ readReservation: (reservationId) => core.quota.readReservation(tenant, reservationId),
456
+ reserve: (input) => core.quota.reserve(tenant, input),
457
+ finalizeReservation: (input) => core.quota.finalizeReservation(tenant, input),
458
+ abortReservation: (reservationId) => core.quota.abortReservation(tenant, reservationId)
459
+ },
460
+ sequence: {
461
+ next: (deviceId) => cloud.sequence.next(tenant, deviceId)
462
+ },
463
+ rateLimiter: {
464
+ consume: (deviceId) => cloud.rateLimiter.consume(tenant, deviceId)
465
+ }
466
+ };
467
+ }
468
+
469
+ // src/handlers/shared.ts
470
+ async function readJsonBody(c) {
471
+ try {
472
+ return await c.req.json();
473
+ } catch {
474
+ return void 0;
475
+ }
476
+ }
477
+ async function authenticateDevice(c, deps) {
478
+ const device = await authenticateBearer(c.req.header("authorization"), deps.bearer);
479
+ if (device === void 0) return void 0;
480
+ return { device, stores: tenantStoresFor(device, deps.root) };
481
+ }
482
+
483
+ // src/handlers/blobs.ts
484
+ var BLOB_RESERVATION_TTL_MS = 15 * 60 * 1e3;
485
+ var SignedUrlQuerySchema = z.object({
486
+ sig: z.string().min(1),
487
+ exp: z.coerce.number().finite()
488
+ });
489
+ function createBlobHandler(deps) {
490
+ return async (c) => {
491
+ const authenticated = await authenticateDevice(c, deps);
492
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
493
+ const parsed = CreateBlobRequestSchema.safeParse(await readJsonBody(c));
494
+ if (!parsed.success) return c.json({ error: "size, contentType, and contentHash are required" }, 400);
495
+ if (parsed.data.size > deps.maxBlobSizeBytes) {
496
+ return c.json({ error: `blob exceeds max size of ${deps.maxBlobSizeBytes} bytes` }, 413);
497
+ }
498
+ const reservationId = idempotencyKey(c);
499
+ if (reservationId === void 0) {
500
+ return c.json({ error: "Idempotency-Key header is required" }, 400);
501
+ }
502
+ try {
503
+ const reservation = await authenticated.stores.quota.reserve({
504
+ reservationId,
505
+ kind: "object",
506
+ expectedBytes: BigInt(parsed.data.size),
507
+ contentHash: contentHash(parsed.data.contentHash),
508
+ contentType: parsed.data.contentType,
509
+ ttlMs: BLOB_RESERVATION_TTL_MS
510
+ });
511
+ try {
512
+ const { blobId, uploadUrl } = await authenticated.stores.blobs.createUpload(reservation);
513
+ const response = { blobId, uploadUrl };
514
+ return c.json(response, 200);
515
+ } catch (error) {
516
+ await authenticated.stores.quota.abortReservation(reservationId).catch(() => void 0);
517
+ const rendered = renderStorageError(c, error);
518
+ if (rendered !== void 0) return rendered;
519
+ throw error;
520
+ }
521
+ } catch (error) {
522
+ const rendered = renderStorageError(c, error);
523
+ if (rendered !== void 0) return rendered;
524
+ throw error;
525
+ }
526
+ };
527
+ }
528
+ function finalizeBlobHandler(deps) {
529
+ return async (c) => {
530
+ const authenticated = await authenticateDevice(c, deps);
531
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
532
+ const reservationId = idempotencyKey(c);
533
+ if (reservationId === void 0) {
534
+ return c.json({ error: "Idempotency-Key header is required" }, 400);
535
+ }
536
+ try {
537
+ const reservation = await authenticated.stores.quota.readReservation(reservationId);
538
+ if (reservation === void 0) {
539
+ return c.json({ error: "storage_reservation_not_found" }, 404);
540
+ }
541
+ const blobId = c.req.param("id") ?? "";
542
+ const observed = await authenticated.stores.blobs.observeUpload(
543
+ blobId,
544
+ reservation
545
+ );
546
+ if (observed === void 0) {
547
+ await authenticated.stores.quota.abortReservation(reservationId);
548
+ return c.json({ error: "storage_integrity_mismatch" }, 422);
549
+ }
550
+ await authenticated.stores.quota.finalizeReservation({
551
+ reservationId,
552
+ ...observed
553
+ });
554
+ return c.body(null, 204);
555
+ } catch (error) {
556
+ const rendered = renderStorageError(c, error);
557
+ if (rendered !== void 0) return rendered;
558
+ throw error;
559
+ }
560
+ };
561
+ }
562
+ function blobDownloadUrlHandler(deps) {
563
+ return async (c) => {
564
+ const authenticated = await authenticateDevice(c, deps);
565
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
566
+ const downloadUrl = await authenticated.stores.blobs.getDownloadUrl(c.req.param("id") ?? "");
567
+ if (downloadUrl === void 0) return c.json({ error: "blob not found" }, 404);
568
+ const response = { downloadUrl };
569
+ return c.json(response, 200);
570
+ };
571
+ }
572
+ function blobUploadContentHandler(deps) {
573
+ return async (c) => {
574
+ const blobId = c.req.param("id") ?? "";
575
+ const query = SignedUrlQuerySchema.safeParse({ sig: c.req.query("sig"), exp: c.req.query("exp") });
576
+ if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "put", query.data.sig, query.data.exp)) {
577
+ return c.json({ error: "invalid or expired signature" }, 401);
578
+ }
579
+ const result = await deps.contentProxy.writeContent(blobId, new Uint8Array(await c.req.arrayBuffer()));
580
+ if (!result.ok) return c.json({ error: result.reason }, 422);
581
+ return c.body(null, 204);
582
+ };
583
+ }
584
+ function blobDownloadContentHandler(deps) {
585
+ return async (c) => {
586
+ const blobId = c.req.param("id") ?? "";
587
+ const query = SignedUrlQuerySchema.safeParse({ sig: c.req.query("sig"), exp: c.req.query("exp") });
588
+ if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "get", query.data.sig, query.data.exp)) {
589
+ return c.json({ error: "invalid or expired signature" }, 401);
590
+ }
591
+ const content = await deps.contentProxy.readContent(blobId);
592
+ if (content === void 0) return c.json({ error: "blob not found" }, 404);
593
+ return c.body(new Uint8Array(content.data), 200, { "content-type": content.contentType });
594
+ };
595
+ }
596
+ function idempotencyKey(c) {
597
+ const value = c.req.header("Idempotency-Key");
598
+ if (value === void 0 || value.length === 0 || value.length > 200) return void 0;
599
+ return value;
600
+ }
601
+ function renderStorageError(c, error) {
602
+ if (!isCoreError(error)) return void 0;
603
+ if (STORAGE_ERROR_CODES.includes(error.code)) {
604
+ const code = error.code;
605
+ return c.json({ error: code }, STORAGE_ERROR_HTTP_STATUS[code]);
606
+ }
607
+ if (error.code === "storage_reservation_not_found" || error.code === "object_not_found") {
608
+ return c.json({ error: error.code }, 404);
609
+ }
610
+ if (error.code === "storage_entitlement_missing" || error.code === "object_state_invalid") {
611
+ return c.json({ error: error.code }, 409);
612
+ }
613
+ if (error.code === "content_hash_invalid") {
614
+ return c.json({ error: error.code }, 400);
615
+ }
616
+ return void 0;
617
+ }
618
+
619
+ // src/handlers/capabilities.ts
620
+ function capabilitiesHandler(deps) {
621
+ return async (c) => c.json(deps.declaration, 200);
622
+ }
623
+ function pairHandler(deps) {
624
+ return async (c) => {
625
+ const parsed = PairRequestSchema.safeParse(await readJsonBody(c));
626
+ if (!parsed.success) {
627
+ return c.json({ error: "pairingCode, deviceName, and devicePublicKey are required strings" }, 400);
628
+ }
629
+ const device = await deps.auth.redeemAndRegister(parsed.data);
630
+ if (device === void 0) return c.json({ error: "invalid pairing code" }, 401);
631
+ const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
632
+ const response = { deviceId: device.deviceId, accessToken, refreshHint: expiresAt };
633
+ return c.json(response, 200);
634
+ };
635
+ }
636
+ function challengeHandler(deps) {
637
+ return async (c) => {
638
+ const parsed = ChallengeRequestSchema.safeParse(await readJsonBody(c));
639
+ if (!parsed.success) return c.json({ error: "deviceId is required" }, 400);
640
+ const device = await deps.auth.resolveDevice(parsed.data.deviceId);
641
+ if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
642
+ const response = { nonce: await deps.auth.issueNonce(device) };
643
+ return c.json(response, 200);
644
+ };
645
+ }
646
+ function tokenHandler(deps) {
647
+ return async (c) => {
648
+ const parsed = TokenRequestSchema.safeParse(await readJsonBody(c));
649
+ if (!parsed.success) return c.json({ error: "deviceId, nonce, and signature are required" }, 400);
650
+ const { deviceId, nonce, signature } = parsed.data;
651
+ const device = await deps.auth.resolveDevice(deviceId);
652
+ if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
653
+ if (!await deps.auth.validateNonce(device, nonce)) {
654
+ return c.json({ error: "invalid, expired, or already-used nonce" }, 401);
655
+ }
656
+ if (!await deps.auth.verifySignature(device, nonce, signature)) {
657
+ return c.json({ error: "invalid signature" }, 401);
658
+ }
659
+ await deps.auth.consumeNonce(device, nonce);
660
+ const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
661
+ const response = { accessToken, expiresAt };
662
+ return c.json(response, 200);
663
+ };
664
+ }
665
+ function sleep(ms) {
666
+ return new Promise((resolve) => setTimeout(resolve, ms));
667
+ }
668
+ function eventsHandler(deps) {
669
+ return async (c) => {
670
+ const authenticated = await authenticateDevice(c, deps);
671
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
672
+ const { device, stores } = authenticated;
673
+ const cursorRaw = c.req.query("cursor");
674
+ let cursor = 0;
675
+ if (cursorRaw !== void 0) {
676
+ const parsedCursor = Number(cursorRaw);
677
+ if (!Number.isInteger(parsedCursor) || parsedCursor < 0) return c.json({ error: "invalid cursor" }, 400);
678
+ cursor = parsedCursor;
679
+ }
680
+ const acked = await stores.mailbox.readCursor(device.deviceId);
681
+ if (cursor > acked.ackedSeq) {
682
+ await stores.mailbox.advanceCursor({ deviceId: device.deviceId, ackedSeq: cursor });
683
+ }
684
+ const attempts = Math.max(1, Math.ceil(deps.longPollHoldMs / deps.longPollIntervalMs));
685
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
686
+ const page = await stores.mailbox.readAfter({
687
+ deviceId: device.deviceId,
688
+ afterSeq: cursor,
689
+ limit: deps.pageLimit
690
+ });
691
+ if (page.messages.length > 0) {
692
+ const events = page.messages.map((message) => decodeEnvelope(message.body));
693
+ const response2 = { events, cursor: page.nextSeq };
694
+ return c.json(response2, 200);
695
+ }
696
+ if (attempt < attempts - 1) await sleep(deps.longPollIntervalMs);
697
+ }
698
+ const response = { events: [], cursor };
699
+ return c.json(response, 200);
700
+ };
701
+ }
702
+ async function projectTerminalToReview(board, taskId) {
703
+ const item = await board.get(taskId);
704
+ if (item === void 0 || item.status !== "in_progress") return;
705
+ try {
706
+ await board.updateStatus({
707
+ itemId: taskId,
708
+ expectedStatus: "in_progress",
709
+ status: "in_review"
710
+ });
711
+ } catch (caught) {
712
+ if (isCoreConflictError(caught)) return;
713
+ throw caught;
714
+ }
715
+ }
716
+
717
+ // src/inbound.ts
718
+ function terminalReceiptKey(taskId) {
719
+ return `task:${taskId}:terminal`;
720
+ }
721
+ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds = DEFAULT_ACTIVITY_BOUNDS) {
722
+ if (!await stores.rateLimiter.consume(deviceId)) return "rate_limited";
723
+ if (!DAEMON_TO_SERVER_TYPES.includes(envelope.type)) return "rejected";
724
+ const taskId = envelope.task_id;
725
+ if (taskId === void 0) return "rejected";
726
+ const attempt = await stores.tasks.get(taskId);
727
+ if (attempt?.ownerDeviceId !== void 0 && attempt.ownerDeviceId !== deviceId) return "rejected";
728
+ if (envelope.type === "task.progress" && envelope.payload.events.length > 0) {
729
+ try {
730
+ activityDetails(envelope.payload.events, 0, activityBounds);
731
+ } catch (caught) {
732
+ if (isCloudError(caught, "activity_batch_too_large") || isCloudError(caught, "coordination_input_invalid")) {
733
+ return "rejected";
734
+ }
735
+ throw caught;
736
+ }
737
+ }
738
+ if (await stores.dedup.checkAndRecord(deviceId, envelope.id)) return "duplicate";
739
+ await applyLifecycle(stores, deviceId, taskId, envelope, activityBounds);
740
+ return "accepted";
741
+ }
742
+ async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds) {
743
+ switch (envelope.type) {
744
+ case "task.claim":
745
+ await stores.tasks.claim({ taskId, deviceId });
746
+ return;
747
+ case "task.started":
748
+ await stores.tasks.recordStatus({ taskId, status: "running" });
749
+ return;
750
+ case "task.decline":
751
+ await stores.tasks.recordStatus({ taskId, status: "failed" });
752
+ return;
753
+ case "task.progress":
754
+ if (envelope.payload.events.length > 0) {
755
+ await appendActivityEvents(
756
+ stores.activity,
757
+ { taskId, events: envelope.payload.events, dropped: 0 },
758
+ activityBounds
759
+ );
760
+ }
761
+ return;
762
+ case "task.complete":
763
+ await recordTerminal(stores, taskId, envelope, "complete");
764
+ return;
765
+ case "task.fail":
766
+ await recordTerminal(stores, taskId, envelope, "failed");
767
+ return;
768
+ case "task.cancelled":
769
+ await recordTerminal(stores, taskId, envelope, "cancelled");
770
+ return;
771
+ default:
772
+ return;
773
+ }
774
+ }
775
+ async function recordTerminal(stores, taskId, envelope, status) {
776
+ const { created } = await stores.receipts.record({
777
+ key: terminalReceiptKey(taskId),
778
+ body: encodeEnvelope(envelope)
779
+ });
780
+ if (!created) return;
781
+ await stores.tasks.recordStatus({ taskId, status });
782
+ await projectTerminalToReview(stores.board, taskId);
783
+ }
784
+
785
+ // src/handlers/messages.ts
786
+ function messagesHandler(deps) {
787
+ return async (c) => {
788
+ const authenticated = await authenticateDevice(c, deps);
789
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
790
+ const { device, stores } = authenticated;
791
+ const parsed = MessagesSendRequestSchema.safeParse(await readJsonBody(c));
792
+ if (!parsed.success) return c.json({ error: "messages must be an array of envelopes" }, 400);
793
+ let accepted = 0;
794
+ let rejected = 0;
795
+ for (const envelope of parsed.data.messages) {
796
+ const outcome = await handleInboundEnvelope(
797
+ stores,
798
+ device.deviceId,
799
+ envelope,
800
+ deps.activityBounds
801
+ );
802
+ if (outcome === "rate_limited") return c.json({ error: "rate limit exceeded" }, 429);
803
+ if (outcome === "rejected") rejected += 1;
804
+ else accepted += 1;
805
+ }
806
+ const response = rejected > 0 ? { accepted, rejected } : { accepted };
807
+ return c.json(response, 200);
808
+ };
809
+ }
810
+ var DEFAULT_BOARD_PAGE_LIMIT = 50;
811
+ var DEFAULT_BOARD_STREAM_QUERY_INTERVAL_MS = 5e3;
812
+ var DEFAULT_BOARD_STREAM_HEARTBEAT_INTERVAL_MS = 15e3;
813
+ var DEFAULT_BOARD_STREAM_RECONCILIATION_INTERVAL_MS = 12e4;
814
+ var ClaimBodySchema = z.object({ expectedStatus: z.enum(BOARD_STATUSES).optional() });
815
+ var StatusBodySchema = z.object({
816
+ expectedStatus: z.enum(BOARD_STATUSES),
817
+ status: z.enum(BOARD_STATUSES)
818
+ });
819
+ function parseNonNegativeInteger(value) {
820
+ if (value === void 0) return 0;
821
+ const parsed = Number(value);
822
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
823
+ }
824
+ function parsePositiveInteger(value, fallback) {
825
+ if (value === void 0) return fallback;
826
+ const parsed = Number(value);
827
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
828
+ }
829
+ function boardQuery(c, pageLimit) {
830
+ const afterSeq = parseNonNegativeInteger(c.req.query("since"));
831
+ const limit = parsePositiveInteger(c.req.query("limit"), pageLimit);
832
+ const statusRaw = c.req.query("status");
833
+ if (afterSeq === void 0 || limit === void 0 || limit > pageLimit) return void 0;
834
+ if (statusRaw !== void 0 && !BOARD_STATUSES.includes(statusRaw)) {
835
+ return void 0;
836
+ }
837
+ return {
838
+ afterSeq,
839
+ limit,
840
+ ...c.req.query("channel") === void 0 ? {} : { channel: c.req.query("channel") },
841
+ ...statusRaw === void 0 ? {} : { status: statusRaw }
842
+ };
843
+ }
844
+ function boardListHandler(deps) {
845
+ return async (c) => {
846
+ const authenticated = await authenticateDevice(c, deps);
847
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
848
+ const query = boardQuery(c, deps.pageLimit);
849
+ if (query === void 0) return c.json({ error: "invalid board query" }, 400);
850
+ return c.json(await authenticated.stores.board.list(query), 200);
851
+ };
852
+ }
853
+ function boardClaimHandler(deps) {
854
+ return async (c) => {
855
+ const authenticated = await authenticateDevice(c, deps);
856
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
857
+ const parsed = ClaimBodySchema.safeParse(await readJsonBody(c));
858
+ if (!parsed.success) return c.json({ error: "invalid claim body" }, 400);
859
+ const itemId = c.req.param("id");
860
+ if (itemId === void 0 || itemId.length === 0) return c.json({ error: "invalid board item id" }, 400);
861
+ try {
862
+ const item = await authenticated.stores.board.claim({
863
+ itemId,
864
+ holderId: authenticated.device.deviceId,
865
+ ...parsed.data.expectedStatus === void 0 ? {} : { expectedStatus: parsed.data.expectedStatus }
866
+ });
867
+ return c.json(item, 200);
868
+ } catch (caught) {
869
+ return boardFailure(c, caught);
870
+ }
871
+ };
872
+ }
873
+ function boardUnclaimHandler(deps) {
874
+ return async (c) => {
875
+ const authenticated = await authenticateDevice(c, deps);
876
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
877
+ const itemId = c.req.param("id");
878
+ if (itemId === void 0 || itemId.length === 0) return c.json({ error: "invalid board item id" }, 400);
879
+ try {
880
+ return c.json(
881
+ await authenticated.stores.board.unclaim({
882
+ itemId,
883
+ holderId: authenticated.device.deviceId
884
+ }),
885
+ 200
886
+ );
887
+ } catch (caught) {
888
+ return boardFailure(c, caught);
889
+ }
890
+ };
891
+ }
892
+ function boardStatusHandler(deps) {
893
+ return async (c) => {
894
+ const authenticated = await authenticateDevice(c, deps);
895
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
896
+ const parsed = StatusBodySchema.safeParse(await readJsonBody(c));
897
+ if (!parsed.success) return c.json({ error: "invalid status body" }, 400);
898
+ const itemId = c.req.param("id");
899
+ if (itemId === void 0 || itemId.length === 0) return c.json({ error: "invalid board item id" }, 400);
900
+ if (parsed.data.status === "done") {
901
+ return c.json({ error: "done requires host review acceptance" }, 403);
902
+ }
903
+ try {
904
+ return c.json(
905
+ await authenticated.stores.board.updateStatus({
906
+ itemId,
907
+ expectedStatus: parsed.data.expectedStatus,
908
+ status: parsed.data.status,
909
+ holderId: authenticated.device.deviceId
910
+ }),
911
+ 200
912
+ );
913
+ } catch (caught) {
914
+ return boardFailure(c, caught);
915
+ }
916
+ };
917
+ }
918
+ function boardStreamHandler(deps) {
919
+ return async (c) => {
920
+ const firstAuth = await authenticateDevice(c, deps);
921
+ if (firstAuth === void 0) return c.json({ error: "unauthorized" }, 401);
922
+ const query = boardQuery(c, deps.pageLimit);
923
+ if (query === void 0) return c.json({ error: "invalid board query" }, 400);
924
+ const querySince = query.afterSeq ?? 0;
925
+ const headerRaw = c.req.header("last-event-id");
926
+ const headerSince = headerRaw === void 0 ? void 0 : parseNonNegativeInteger(headerRaw);
927
+ if (headerRaw !== void 0 && headerSince === void 0) {
928
+ return c.json({ error: "invalid Last-Event-ID" }, 400);
929
+ }
930
+ if (headerSince !== void 0 && c.req.query("since") !== void 0 && headerSince !== querySince) {
931
+ return c.json({ error: "conflicting stream cursors" }, 400);
932
+ }
933
+ let stopped = false;
934
+ const stopController = new AbortController();
935
+ const signal = c.req.raw.signal;
936
+ const stream = new ReadableStream({
937
+ start(controller) {
938
+ const onAbort = () => {
939
+ stopped = true;
940
+ stopController.abort();
941
+ closeController(controller);
942
+ };
943
+ signal.addEventListener("abort", onAbort, { once: true });
944
+ void pumpBoardStream(
945
+ controller,
946
+ c,
947
+ deps,
948
+ { ...query, afterSeq: headerSince ?? querySince },
949
+ () => stopped || signal.aborted,
950
+ stopController.signal
951
+ ).finally(() => {
952
+ stopped = true;
953
+ stopController.abort();
954
+ signal.removeEventListener("abort", onAbort);
955
+ closeController(controller);
956
+ });
957
+ },
958
+ cancel() {
959
+ stopped = true;
960
+ stopController.abort();
961
+ }
962
+ });
963
+ return new Response(stream, {
964
+ status: 200,
965
+ headers: {
966
+ "cache-control": "no-cache, no-transform",
967
+ connection: "keep-alive",
968
+ "content-type": "text/event-stream; charset=utf-8"
969
+ }
970
+ });
971
+ };
972
+ }
973
+ async function pumpBoardStream(controller, c, deps, initialQuery, isStopped, stopSignal) {
974
+ let cursor = initialQuery.afterSeq ?? 0;
975
+ let lastHeartbeat = performance.now();
976
+ let lastReconcile = performance.now();
977
+ while (!isStopped()) {
978
+ const authenticated = await authenticateDevice(c, deps);
979
+ if (authenticated === void 0) return;
980
+ const page = await authenticated.stores.board.list({ ...initialQuery, afterSeq: cursor });
981
+ for (const item of page.items) {
982
+ enqueue(controller, `event: board
983
+ id: ${item.boardSeq}
984
+ data: ${JSON.stringify(item)}
985
+
986
+ `);
987
+ cursor = item.boardSeq;
988
+ }
989
+ if (page.hasMore) continue;
990
+ const now = performance.now();
991
+ if (now - lastReconcile >= deps.reconciliationIntervalMs) {
992
+ enqueue(controller, `event: reconcile
993
+ data: ${JSON.stringify({ since: 0 })}
994
+
995
+ `);
996
+ lastReconcile = now;
997
+ }
998
+ if (now - lastHeartbeat >= deps.heartbeatIntervalMs) {
999
+ enqueue(controller, ": heartbeat\n\n");
1000
+ lastHeartbeat = now;
1001
+ }
1002
+ await abortableSleep(deps.queryIntervalMs, stopSignal);
1003
+ }
1004
+ }
1005
+ function enqueue(controller, value) {
1006
+ try {
1007
+ controller.enqueue(new TextEncoder().encode(value));
1008
+ } catch {
1009
+ }
1010
+ }
1011
+ function closeController(controller) {
1012
+ try {
1013
+ controller.close();
1014
+ } catch {
1015
+ }
1016
+ }
1017
+ async function abortableSleep(ms, signal) {
1018
+ if (signal.aborted) return;
1019
+ await new Promise((resolve) => {
1020
+ let timer;
1021
+ const finish = () => {
1022
+ if (timer !== void 0) clearTimeout(timer);
1023
+ signal.removeEventListener("abort", finish);
1024
+ resolve();
1025
+ };
1026
+ timer = setTimeout(finish, ms);
1027
+ signal.addEventListener("abort", finish, { once: true });
1028
+ if (signal.aborted) finish();
1029
+ });
1030
+ }
1031
+ function boardFailure(c, caught) {
1032
+ if (isCoreConflictError(caught)) {
1033
+ const holder = caught.current.assignee;
1034
+ return c.json(
1035
+ {
1036
+ error: caught.code,
1037
+ current: caught.current,
1038
+ observedAt: caught.observedAt,
1039
+ ...holder === void 0 ? {} : { holder: { ...holder, observedAt: caught.observedAt } }
1040
+ },
1041
+ 409
1042
+ );
1043
+ }
1044
+ if (isCoreError(caught, "board_item_not_found")) return c.json({ error: caught.code }, 404);
1045
+ if (isCoreError(caught, "board_not_held")) return c.json({ error: caught.code }, 409);
1046
+ throw caught;
1047
+ }
1048
+ var PresenceBodySchema = z.object({
1049
+ level: z.enum(PRESENCE_LEVELS),
1050
+ detail: z.string().optional()
1051
+ });
1052
+ var ActivityBodySchema = z.object({
1053
+ taskId: z.string().min(1).max(200),
1054
+ events: z.array(AgentEventOrUnknownSchema),
1055
+ dropped: z.number().int().nonnegative()
1056
+ });
1057
+ function presencePublishHandler(deps) {
1058
+ return async (c) => {
1059
+ const authenticated = await authenticateDevice(c, deps);
1060
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
1061
+ const parsed = PresenceBodySchema.safeParse(await readJsonBody(c));
1062
+ if (!parsed.success) return c.json({ error: "invalid presence body" }, 400);
1063
+ if (parsed.data.detail !== void 0 && new TextEncoder().encode(parsed.data.detail).length > deps.detailMaxBytes) {
1064
+ return c.json({ error: "presence detail too large" }, 413);
1065
+ }
1066
+ try {
1067
+ return c.json(
1068
+ await authenticated.stores.presence.publish({
1069
+ deviceId: authenticated.device.deviceId,
1070
+ level: parsed.data.level,
1071
+ ...parsed.data.detail === void 0 ? {} : { detail: parsed.data.detail },
1072
+ ttlMs: deps.ttlMs,
1073
+ minimumIntervalMs: deps.minimumIntervalMs
1074
+ }),
1075
+ 200
1076
+ );
1077
+ } catch (caught) {
1078
+ if (isCoreError(caught, "hint_rate_limited")) {
1079
+ return c.json({ error: caught.code }, 429);
1080
+ }
1081
+ throw caught;
1082
+ }
1083
+ };
1084
+ }
1085
+ function activityAppendHandler(deps) {
1086
+ return async (c) => {
1087
+ const authenticated = await authenticateDevice(c, deps);
1088
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
1089
+ const parsed = ActivityBodySchema.safeParse(await readJsonBody(c));
1090
+ if (!parsed.success) return c.json({ error: "invalid activity body" }, 400);
1091
+ try {
1092
+ return c.json(
1093
+ await appendActivityEvents(authenticated.stores.activity, parsed.data, deps.bounds),
1094
+ 200
1095
+ );
1096
+ } catch (caught) {
1097
+ if (isCloudError(caught, "activity_batch_too_large")) {
1098
+ return c.json({ error: caught.code }, 413);
1099
+ }
1100
+ if (isCloudError(caught, "coordination_input_invalid")) {
1101
+ return c.json({ error: caught.code }, 400);
1102
+ }
1103
+ throw caught;
1104
+ }
1105
+ };
1106
+ }
1107
+ var DEFAULT_DEVICE_PROOF_CLOCK_SKEW_MS = 6e4;
1108
+ var MAX_DEVICE_PROOF_CLOCK_SKEW_MS = 5 * 6e4;
1109
+ var DEFAULT_DEVICE_PROOF_MAX_LIFETIME_MS = 5 * 6e4;
1110
+ var MAX_DEVICE_PROOF_MAX_LIFETIME_MS = 15 * 6e4;
1111
+ function validBound(value, maximum) {
1112
+ return Number.isSafeInteger(value) && value >= 0 && value <= maximum;
1113
+ }
1114
+ function parseInstant(value) {
1115
+ const parsed = Date.parse(value);
1116
+ return Number.isFinite(parsed) ? parsed : void 0;
1117
+ }
1118
+ function timeBindingIsValid(envelope, now, clockSkewMs, maxLifetimeMs) {
1119
+ const issuedAt = parseInstant(envelope.protected.issuedAt);
1120
+ if (issuedAt === void 0) return false;
1121
+ if (issuedAt < now - clockSkewMs || issuedAt > now + clockSkewMs) return false;
1122
+ const expiry = envelope.protected.expiresAt;
1123
+ if (expiry === void 0) return true;
1124
+ const expiresAt = parseInstant(expiry);
1125
+ if (expiresAt === void 0 || expiresAt < issuedAt) return false;
1126
+ if (expiresAt - issuedAt > maxLifetimeMs) return false;
1127
+ return now <= expiresAt + clockSkewMs;
1128
+ }
1129
+ async function authenticateDeviceProof(input, request, deps) {
1130
+ const clockSkewMs = deps.clockSkewMs ?? DEFAULT_DEVICE_PROOF_CLOCK_SKEW_MS;
1131
+ const maxLifetimeMs = deps.maxLifetimeMs ?? DEFAULT_DEVICE_PROOF_MAX_LIFETIME_MS;
1132
+ if (!validBound(clockSkewMs, MAX_DEVICE_PROOF_CLOCK_SKEW_MS)) return void 0;
1133
+ if (!validBound(maxLifetimeMs, MAX_DEVICE_PROOF_MAX_LIFETIME_MS)) return void 0;
1134
+ let envelope;
1135
+ try {
1136
+ envelope = parseDeviceProofEnvelope(input);
1137
+ } catch {
1138
+ return void 0;
1139
+ }
1140
+ const claims = envelope.protected;
1141
+ if (claims.operation !== request.operation || claims.resource !== request.resource) {
1142
+ return void 0;
1143
+ }
1144
+ if (claims.operationId !== void 0) return void 0;
1145
+ if (claims.method !== request.method || claims.path !== request.path) return void 0;
1146
+ if (claims.bodySize !== request.body.byteLength) return void 0;
1147
+ if (await deps.crypto.sha256(request.body) !== claims.bodySha256) return void 0;
1148
+ if (!isTenantId(claims.tenantId)) return void 0;
1149
+ const row = await deps.devices.get(tenantId(claims.tenantId), claims.deviceId);
1150
+ if (row === void 0 || row.revoked) return void 0;
1151
+ if (row.productId !== claims.productId) return void 0;
1152
+ if (row.proofKeyId !== claims.keyId || row.proofKeyEpoch !== claims.keyEpoch) {
1153
+ return void 0;
1154
+ }
1155
+ if (!timeBindingIsValid(envelope, deps.clock.now().getTime(), clockSkewMs, maxLifetimeMs)) {
1156
+ return void 0;
1157
+ }
1158
+ const verified = await deps.crypto.verifyEd25519(
1159
+ row.devicePublicKey,
1160
+ deviceProofSigningInput(claims),
1161
+ envelope.signature
1162
+ );
1163
+ if (!verified) return void 0;
1164
+ return {
1165
+ device: {
1166
+ kind: "device",
1167
+ tenantId: row.tenantId,
1168
+ productId: row.productId,
1169
+ deviceId: row.deviceId
1170
+ },
1171
+ requestId: claims.requestId,
1172
+ operation: claims.operation,
1173
+ resource: claims.resource,
1174
+ bodySha256: claims.bodySha256,
1175
+ bodySize: BigInt(claims.bodySize),
1176
+ keyId: row.proofKeyId,
1177
+ keyEpoch: row.proofKeyEpoch
1178
+ };
1179
+ }
1180
+ var TRUTH_RECORD_CAPABILITY = "truth.records";
1181
+ var TRUTH_INLINE_CONTENT_TYPE = "application/vnd.byok.truth+utf8";
1182
+ var TRUTH_REQUEST_ID_MAX_LENGTH = 120;
1183
+ var TRUTH_RECORD_KEY_MAX_LENGTH = 200;
1184
+ var TRUTH_LABEL_MAX_LENGTH = 200;
1185
+ var TRUTH_BATCH_MAX_RECORDS = 32;
1186
+ var TRUTH_MANIFEST_MAX_LIMIT = 100;
1187
+ var CONTENT_HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
1188
+ var RECORD_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
1189
+ var TruthRecordKeySchema = z.string().min(1).max(TRUTH_RECORD_KEY_MAX_LENGTH).regex(RECORD_KEY_PATTERN).refine((value) => value !== "." && value !== "..");
1190
+ var TruthBodyInputSchema = z.discriminatedUnion("kind", [
1191
+ z.strictObject({
1192
+ kind: z.literal("inline"),
1193
+ content: z.string(),
1194
+ contentHash: z.string().regex(CONTENT_HASH_PATTERN)
1195
+ }),
1196
+ z.strictObject({
1197
+ kind: z.literal("object"),
1198
+ contentHash: z.string().regex(CONTENT_HASH_PATTERN),
1199
+ byteSize: z.number().int().nonnegative().safe()
1200
+ })
1201
+ ]);
1202
+ var SnapshotCandidateSchema = z.strictObject({
1203
+ kind: z.enum(["profile", "memory"]),
1204
+ recordKey: TruthRecordKeySchema,
1205
+ expectedRev: z.number().int().nonnegative().safe(),
1206
+ body: TruthBodyInputSchema,
1207
+ label: z.string().max(TRUTH_LABEL_MAX_LENGTH).optional()
1208
+ });
1209
+ var TruthWriteRequestSchema = z.strictObject({
1210
+ expectedRev: z.number().int().nonnegative().safe().optional(),
1211
+ body: TruthBodyInputSchema,
1212
+ label: z.string().max(TRUTH_LABEL_MAX_LENGTH).optional(),
1213
+ snapshots: z.array(SnapshotCandidateSchema).max(TRUTH_BATCH_MAX_RECORDS - 1).optional()
1214
+ });
1215
+ var TruthRecordMetadataSchema = z.strictObject({
1216
+ kind: z.enum(["task.terminal", "profile", "memory"]),
1217
+ recordKey: TruthRecordKeySchema,
1218
+ rev: z.number().int().positive().safe(),
1219
+ contentHash: z.string().regex(CONTENT_HASH_PATTERN),
1220
+ byteSize: z.number().int().nonnegative().safe(),
1221
+ label: z.string().max(TRUTH_LABEL_MAX_LENGTH).optional(),
1222
+ updatedAt: z.string().datetime({ offset: true })
1223
+ });
1224
+ var TruthCommitResponseSchema = z.strictObject({
1225
+ primary: TruthRecordMetadataSchema,
1226
+ snapshots: z.array(TruthRecordMetadataSchema).max(TRUTH_BATCH_MAX_RECORDS - 1)
1227
+ });
1228
+ function truthRecordMetadata(record) {
1229
+ const byteSize = Number(record.byteSize);
1230
+ if (!Number.isSafeInteger(byteSize)) throw new Error("truth record byte size exceeds JSON range");
1231
+ return {
1232
+ kind: record.kind,
1233
+ recordKey: record.recordKey,
1234
+ rev: record.rev,
1235
+ contentHash: record.contentHash,
1236
+ byteSize,
1237
+ ...record.label === void 0 ? {} : { label: record.label },
1238
+ updatedAt: record.writtenAt
1239
+ };
1240
+ }
1241
+ function truthManifestMetadata(entry) {
1242
+ const byteSize = Number(entry.byteSize);
1243
+ if (!Number.isSafeInteger(byteSize)) throw new Error("truth manifest byte size exceeds JSON range");
1244
+ return {
1245
+ kind: entry.kind,
1246
+ recordKey: entry.recordKey,
1247
+ rev: entry.rev,
1248
+ contentHash: entry.contentHash,
1249
+ byteSize,
1250
+ ...entry.label === void 0 ? {} : { label: entry.label },
1251
+ updatedAt: entry.updatedAt
1252
+ };
1253
+ }
1254
+
1255
+ // src/truth/errors.ts
1256
+ var TruthCommitError = class extends Error {
1257
+ code;
1258
+ current;
1259
+ constructor(code, message, current) {
1260
+ super(message);
1261
+ this.name = "TruthCommitError";
1262
+ this.code = code;
1263
+ this.current = current;
1264
+ }
1265
+ };
1266
+ function isTruthCommitError(value) {
1267
+ return value instanceof TruthCommitError;
1268
+ }
1269
+
1270
+ // src/handlers/truth.ts
1271
+ var DEVICE_PROOF_HEADER = "x-byok-device-proof";
1272
+ var DEFAULT_MAX_TRUTH_REQUEST_BYTES = 2 * 1024 * 1024;
1273
+ var MAX_DEVICE_PROOF_HEADER_BYTES = 16 * 1024;
1274
+ var EMPTY_BODY = new Uint8Array();
1275
+ var RECORD_PREFIX_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
1276
+ function truthManifestHandler(deps) {
1277
+ return async (c) => {
1278
+ const authenticated = await authenticateTruthRequest(c, deps, {
1279
+ body: EMPTY_BODY,
1280
+ operation: "truth.list",
1281
+ resource: "records"
1282
+ });
1283
+ if (authenticated === void 0) return unauthorized(c);
1284
+ const query = manifestQuery(c);
1285
+ if (query === void 0) return c.json({ error: "invalid truth manifest query" }, 400);
1286
+ const records = await deps.truth.listManifest(principalTenant(authenticated.device), query);
1287
+ return c.json({ records: records.map(truthManifestMetadata) }, 200);
1288
+ };
1289
+ }
1290
+ function truthGetHandler(deps) {
1291
+ return async (c) => {
1292
+ const resource = rawRecordResource(c);
1293
+ const authenticated = await authenticateTruthRequest(c, deps, {
1294
+ body: EMPTY_BODY,
1295
+ operation: "truth.read",
1296
+ resource
1297
+ });
1298
+ if (authenticated === void 0) return unauthorized(c);
1299
+ const selector = recordSelector(c);
1300
+ if (selector === void 0) return c.json({ error: "invalid truth record selector" }, 400);
1301
+ const record = await deps.truth.getRecord(principalTenant(authenticated.device), selector);
1302
+ if (record === void 0) return c.json({ error: "truth_record_not_found" }, 404);
1303
+ const metadata = truthRecordMetadata(record);
1304
+ if (record.body.kind === "inline") {
1305
+ return c.json({ ...metadata, body: { kind: "inline", content: record.body.body } }, 200);
1306
+ }
1307
+ const downloadUrl = await deps.objectDownloads.getDownloadUrl(
1308
+ principalTenant(authenticated.device),
1309
+ record.body.hash
1310
+ );
1311
+ if (downloadUrl === void 0) return c.json({ error: "object_not_found" }, 404);
1312
+ return c.json({ ...metadata, body: { kind: "object", downloadUrl } }, 200);
1313
+ };
1314
+ }
1315
+ function truthPutHandler(deps) {
1316
+ return async (c) => {
1317
+ const body = await readBoundedBody(c, deps.maxRequestBytes);
1318
+ if (body === "too_large") return c.json({ error: "truth request body too large" }, 413);
1319
+ if (body === void 0) return c.json({ error: "invalid truth request body" }, 400);
1320
+ const authenticated = await authenticateTruthRequest(c, deps, {
1321
+ body,
1322
+ operation: "truth.write",
1323
+ resource: rawRecordResource(c)
1324
+ });
1325
+ if (authenticated === void 0) return unauthorized(c);
1326
+ const selector = recordSelector(c);
1327
+ if (selector === void 0) return c.json({ error: "invalid truth record selector" }, 400);
1328
+ const parsedJson = parseJsonBytes(body);
1329
+ const parsed = TruthWriteRequestSchema.safeParse(parsedJson);
1330
+ if (!parsed.success) return c.json({ error: "invalid truth write request" }, 400);
1331
+ const writes = prepareWrites(selector.kind, selector.recordKey, parsed.data);
1332
+ if (writes === void 0) return c.json({ error: "invalid truth write model" }, 400);
1333
+ const input = {
1334
+ deviceId: authenticated.device.deviceId,
1335
+ requestId: authenticated.requestId,
1336
+ operation: authenticated.operation,
1337
+ resource: authenticated.resource,
1338
+ proofBodySha256: authenticated.bodySha256,
1339
+ proofBodySize: authenticated.bodySize,
1340
+ writes
1341
+ };
1342
+ try {
1343
+ const result = await deps.truth.commit(principalTenant(authenticated.device), input);
1344
+ return c.json(result.response, 200, { "x-byok-replayed": result.replayed ? "true" : "false" });
1345
+ } catch (caught) {
1346
+ return truthFailure(c, caught);
1347
+ }
1348
+ };
1349
+ }
1350
+ function recordSelector(c) {
1351
+ const kind = c.req.param("kind");
1352
+ const recordKey = c.req.param("key");
1353
+ if (!TRUTH_RECORD_KINDS.includes(kind)) return void 0;
1354
+ const parsedKey = TruthRecordKeySchema.safeParse(recordKey);
1355
+ if (!parsedKey.success) return void 0;
1356
+ return { kind, recordKey: parsedKey.data };
1357
+ }
1358
+ function rawRecordResource(c) {
1359
+ return `${c.req.param("kind") ?? ""}/${c.req.param("key") ?? ""}`;
1360
+ }
1361
+ function manifestQuery(c) {
1362
+ const url = new URL(c.req.url);
1363
+ for (const key of url.searchParams.keys()) {
1364
+ if (key !== "kind" && key !== "prefix" && key !== "limit" || url.searchParams.getAll(key).length !== 1) {
1365
+ return void 0;
1366
+ }
1367
+ }
1368
+ const kind = url.searchParams.get("kind") ?? void 0;
1369
+ if (kind !== void 0 && !TRUTH_RECORD_KINDS.includes(kind)) return void 0;
1370
+ const keyPrefix = url.searchParams.get("prefix") ?? void 0;
1371
+ if (keyPrefix !== void 0 && (keyPrefix.length === 0 || keyPrefix.length > 200 || !RECORD_PREFIX_PATTERN.test(keyPrefix))) {
1372
+ return void 0;
1373
+ }
1374
+ const limitRaw = url.searchParams.get("limit") ?? void 0;
1375
+ const limit = limitRaw === void 0 ? TRUTH_MANIFEST_MAX_LIMIT : Number(limitRaw);
1376
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > TRUTH_MANIFEST_MAX_LIMIT) return void 0;
1377
+ return {
1378
+ ...kind === void 0 ? {} : { kind },
1379
+ ...keyPrefix === void 0 ? {} : { keyPrefix },
1380
+ limit
1381
+ };
1382
+ }
1383
+ function prepareWrites(kind, recordKey, request) {
1384
+ if (kind === "task.terminal") {
1385
+ if (request.expectedRev !== void 0) return void 0;
1386
+ } else if (request.expectedRev === void 0 || request.snapshots !== void 0) {
1387
+ return void 0;
1388
+ }
1389
+ const primary = prepareWrite(kind, recordKey, request.body, request.expectedRev, request.label);
1390
+ const snapshots = (request.snapshots ?? []).map(
1391
+ (snapshot) => prepareWrite(
1392
+ snapshot.kind,
1393
+ snapshot.recordKey,
1394
+ snapshot.body,
1395
+ snapshot.expectedRev,
1396
+ snapshot.label
1397
+ )
1398
+ );
1399
+ return [primary, ...snapshots];
1400
+ }
1401
+ function prepareWrite(kind, recordKey, input, expectedRev, label) {
1402
+ const hash = contentHash(input.contentHash);
1403
+ const body = input.kind === "inline" ? { kind: "inline", body: input.content } : { kind: "object", hash };
1404
+ const byteSize = input.kind === "inline" ? BigInt(new TextEncoder().encode(input.content).byteLength) : BigInt(input.byteSize);
1405
+ if (kind === "task.terminal") {
1406
+ return { kind, recordKey, contentHash: hash, byteSize, body, ...label === void 0 ? {} : { label } };
1407
+ }
1408
+ if (expectedRev === void 0) throw new Error("snapshot expectedRev was not validated");
1409
+ return {
1410
+ kind,
1411
+ recordKey,
1412
+ expectedRev,
1413
+ contentHash: hash,
1414
+ byteSize,
1415
+ body,
1416
+ ...label === void 0 ? {} : { label }
1417
+ };
1418
+ }
1419
+ async function authenticateTruthRequest(c, deps, request) {
1420
+ const proof = decodeProofHeader(c.req.header(DEVICE_PROOF_HEADER));
1421
+ if (proof === void 0) return void 0;
1422
+ const url = new URL(c.req.url);
1423
+ return authenticateDeviceProof(
1424
+ proof,
1425
+ {
1426
+ method: c.req.method,
1427
+ path: `${url.pathname}${url.search}`,
1428
+ operation: request.operation,
1429
+ resource: request.resource,
1430
+ body: request.body
1431
+ },
1432
+ deps.proof
1433
+ );
1434
+ }
1435
+ function decodeProofHeader(value) {
1436
+ if (value === void 0 || value.length === 0 || value.length > MAX_DEVICE_PROOF_HEADER_BYTES) {
1437
+ return void 0;
1438
+ }
1439
+ if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1) return void 0;
1440
+ try {
1441
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
1442
+ const binary = atob(padded);
1443
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
1444
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
1445
+ } catch {
1446
+ return void 0;
1447
+ }
1448
+ }
1449
+ async function readBoundedBody(c, maximum) {
1450
+ const length = c.req.header("content-length");
1451
+ if (length !== void 0) {
1452
+ const parsed = Number(length);
1453
+ if (!Number.isSafeInteger(parsed) || parsed < 0) return void 0;
1454
+ if (parsed > maximum) return "too_large";
1455
+ }
1456
+ try {
1457
+ const bytes = new Uint8Array(await c.req.arrayBuffer());
1458
+ return bytes.byteLength > maximum ? "too_large" : bytes;
1459
+ } catch {
1460
+ return void 0;
1461
+ }
1462
+ }
1463
+ function parseJsonBytes(bytes) {
1464
+ try {
1465
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
1466
+ } catch {
1467
+ return void 0;
1468
+ }
1469
+ }
1470
+ function unauthorized(c) {
1471
+ return c.json({ error: "unauthorized" }, 401);
1472
+ }
1473
+ function truthFailure(c, caught) {
1474
+ if (isCoreConflictError(caught)) {
1475
+ const current = caught.current === void 0 ? null : truthRecordMetadata(caught.current);
1476
+ return c.json({ error: caught.code, current, observedAt: caught.observedAt }, 409);
1477
+ }
1478
+ if (isTruthCommitError(caught)) {
1479
+ return c.json({ error: caught.code, ...caught.current === void 0 ? {} : { current: caught.current } }, 409);
1480
+ }
1481
+ if (isCoreError(caught)) {
1482
+ if (STORAGE_ERROR_CODES.includes(caught.code)) {
1483
+ const code = caught.code;
1484
+ return c.json({ error: code }, STORAGE_ERROR_HTTP_STATUS[code]);
1485
+ }
1486
+ if (caught.code === "storage_entitlement_missing" || caught.code === "object_state_invalid") {
1487
+ return c.json({ error: caught.code }, 409);
1488
+ }
1489
+ if (caught.code === "content_hash_invalid") return c.json({ error: caught.code }, 400);
1490
+ }
1491
+ throw caught;
1492
+ }
1493
+ var ROUTE_CLASSES = ["device", "proof", "presigned", "public"];
1494
+ var ROUTE_METHODS = ["GET", "POST", "PUT"];
1495
+ function routeKey(route) {
1496
+ return `${route.method} ${route.path}`;
1497
+ }
1498
+ var CloudRouteRegistry = class {
1499
+ #app = new Hono();
1500
+ #routes = [];
1501
+ register(descriptor, handler) {
1502
+ if (!ROUTE_CLASSES.includes(descriptor.class)) {
1503
+ throw new Error(`Route ${routeKey(descriptor)} has no valid isolation class`);
1504
+ }
1505
+ if (!ROUTE_METHODS.includes(descriptor.method)) {
1506
+ throw new Error(`Route ${routeKey(descriptor)} uses an unsupported method`);
1507
+ }
1508
+ if (this.#routes.some((existing) => routeKey(existing) === routeKey(descriptor))) {
1509
+ throw new Error(`Route ${routeKey(descriptor)} is already registered`);
1510
+ }
1511
+ this.#routes.push(descriptor);
1512
+ this.#app.on(descriptor.method, descriptor.path, handler);
1513
+ }
1514
+ /** The inventory, in registration order. */
1515
+ get routes() {
1516
+ return this.#routes;
1517
+ }
1518
+ /** What the router actually mounted, read back off Hono itself — the other half of the I1 comparison. */
1519
+ get mounted() {
1520
+ return this.#app.routes.map((route) => ({ method: route.method, path: route.path }));
1521
+ }
1522
+ get fetch() {
1523
+ return this.#app.fetch;
1524
+ }
1525
+ };
1526
+
1527
+ // src/cloud.ts
1528
+ var DEFAULT_MAX_BLOB_SIZE_BYTES = 100 * 1024 * 1024;
1529
+ var DEFAULT_LONG_POLL_HOLD_MS = 5e4;
1530
+ var DEFAULT_LONG_POLL_INTERVAL_MS = 250;
1531
+ var DEFAULT_EVENTS_PAGE_LIMIT = 50;
1532
+ function createByokCloud(options) {
1533
+ const declaration = parseDeclaration(options.capabilities);
1534
+ assertNoOverDeclaration(
1535
+ declaration,
1536
+ options.blobContentProxy,
1537
+ options.truthCommitter,
1538
+ options.truthObjectDownloads
1539
+ );
1540
+ const root = { core: options.core, cloud: options.cloud };
1541
+ const auth = createAuthPlane({
1542
+ stores: options.cloud,
1543
+ crypto: options.crypto,
1544
+ clock: options.clock,
1545
+ tokenSigner: options.tokenSigner,
1546
+ ...options.accessTokenTtlSeconds !== void 0 ? { accessTokenTtlSeconds: options.accessTokenTtlSeconds } : {}
1547
+ });
1548
+ const deviceRouteDeps = {
1549
+ root,
1550
+ bearer: { tokenSigner: options.tokenSigner, devices: options.cloud.devices }
1551
+ };
1552
+ const registry = new CloudRouteRegistry();
1553
+ const activityBounds = {
1554
+ maxEvents: options.activityMaxEvents ?? DEFAULT_ACTIVITY_MAX_EVENTS,
1555
+ maxBytes: options.activityMaxBytes ?? DEFAULT_ACTIVITY_MAX_BYTES,
1556
+ capacity: options.activityCapacity ?? DEFAULT_ACTIVITY_CAPACITY,
1557
+ ttlMs: options.activityTtlMs ?? DEFAULT_ACTIVITY_TTL_MS
1558
+ };
1559
+ registry.register({ method: "POST", path: "/byok/pair", class: "public" }, pairHandler({ auth }));
1560
+ registry.register({ method: "POST", path: "/byok/challenge", class: "public" }, challengeHandler({ auth }));
1561
+ registry.register({ method: "POST", path: "/byok/token", class: "public" }, tokenHandler({ auth }));
1562
+ registry.register(
1563
+ { method: "GET", path: "/byok/capabilities", class: "public" },
1564
+ capabilitiesHandler({ declaration })
1565
+ );
1566
+ if (declares(declaration, CLOUD_CAPABILITIES.eventsLongPoll)) {
1567
+ registry.register(
1568
+ { method: "GET", path: "/byok/events", class: "device" },
1569
+ eventsHandler({
1570
+ ...deviceRouteDeps,
1571
+ longPollHoldMs: options.longPollHoldMs ?? DEFAULT_LONG_POLL_HOLD_MS,
1572
+ longPollIntervalMs: options.longPollIntervalMs ?? DEFAULT_LONG_POLL_INTERVAL_MS,
1573
+ pageLimit: options.eventsPageLimit ?? DEFAULT_EVENTS_PAGE_LIMIT
1574
+ })
1575
+ );
1576
+ }
1577
+ if (declares(declaration, CLOUD_CAPABILITIES.messagesBatch)) {
1578
+ registry.register(
1579
+ { method: "POST", path: "/byok/messages", class: "device" },
1580
+ messagesHandler({ ...deviceRouteDeps, activityBounds })
1581
+ );
1582
+ }
1583
+ if (declares(declaration, CLOUD_CAPABILITIES.boardCoordination)) {
1584
+ const boardDeps = {
1585
+ ...deviceRouteDeps,
1586
+ pageLimit: options.boardPageLimit ?? DEFAULT_BOARD_PAGE_LIMIT
1587
+ };
1588
+ registry.register({ method: "GET", path: "/byok/board", class: "device" }, boardListHandler(boardDeps));
1589
+ registry.register(
1590
+ { method: "POST", path: "/byok/board/:id/claim", class: "device" },
1591
+ boardClaimHandler(deviceRouteDeps)
1592
+ );
1593
+ registry.register(
1594
+ { method: "POST", path: "/byok/board/:id/unclaim", class: "device" },
1595
+ boardUnclaimHandler(deviceRouteDeps)
1596
+ );
1597
+ registry.register(
1598
+ { method: "POST", path: "/byok/board/:id/status", class: "device" },
1599
+ boardStatusHandler(deviceRouteDeps)
1600
+ );
1601
+ }
1602
+ if (declares(declaration, CLOUD_CAPABILITIES.boardSse)) {
1603
+ registry.register(
1604
+ { method: "GET", path: "/byok/board/stream", class: "device" },
1605
+ boardStreamHandler({
1606
+ ...deviceRouteDeps,
1607
+ pageLimit: options.boardPageLimit ?? DEFAULT_BOARD_PAGE_LIMIT,
1608
+ queryIntervalMs: options.boardStreamQueryIntervalMs ?? DEFAULT_BOARD_STREAM_QUERY_INTERVAL_MS,
1609
+ heartbeatIntervalMs: options.boardStreamHeartbeatIntervalMs ?? DEFAULT_BOARD_STREAM_HEARTBEAT_INTERVAL_MS,
1610
+ reconciliationIntervalMs: options.boardStreamReconciliationIntervalMs ?? DEFAULT_BOARD_STREAM_RECONCILIATION_INTERVAL_MS
1611
+ })
1612
+ );
1613
+ }
1614
+ if (declares(declaration, CLOUD_CAPABILITIES.presenceHints)) {
1615
+ registry.register(
1616
+ { method: "PUT", path: "/byok/presence", class: "device" },
1617
+ presencePublishHandler({
1618
+ ...deviceRouteDeps,
1619
+ ttlMs: options.presenceTtlMs ?? DEFAULT_PRESENCE_TTL_MS,
1620
+ minimumIntervalMs: options.presenceMinimumIntervalMs ?? DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS,
1621
+ detailMaxBytes: options.presenceDetailMaxBytes ?? DEFAULT_PRESENCE_DETAIL_MAX_BYTES
1622
+ })
1623
+ );
1624
+ }
1625
+ if (declares(declaration, CLOUD_CAPABILITIES.activityTail)) {
1626
+ registry.register(
1627
+ { method: "POST", path: "/byok/activity", class: "device" },
1628
+ activityAppendHandler({ ...deviceRouteDeps, bounds: activityBounds })
1629
+ );
1630
+ }
1631
+ const truthCommitter = options.truthCommitter;
1632
+ const truthObjectDownloads = options.truthObjectDownloads;
1633
+ if (truthCommitter !== void 0 && truthObjectDownloads !== void 0 && declares(declaration, CLOUD_CAPABILITIES.truthRecords)) {
1634
+ const truthDeps = {
1635
+ proof: {
1636
+ devices: options.cloud.devices,
1637
+ crypto: options.crypto,
1638
+ clock: options.clock
1639
+ },
1640
+ truth: truthCommitter,
1641
+ objectDownloads: truthObjectDownloads,
1642
+ maxRequestBytes: options.maxTruthRequestBytes ?? DEFAULT_MAX_TRUTH_REQUEST_BYTES
1643
+ };
1644
+ registry.register(
1645
+ { method: "GET", path: "/byok/records", class: "proof" },
1646
+ truthManifestHandler(truthDeps)
1647
+ );
1648
+ registry.register(
1649
+ { method: "GET", path: "/byok/records/:kind/:key", class: "proof" },
1650
+ truthGetHandler(truthDeps)
1651
+ );
1652
+ registry.register(
1653
+ { method: "PUT", path: "/byok/records/:kind/:key", class: "proof" },
1654
+ truthPutHandler(truthDeps)
1655
+ );
1656
+ }
1657
+ if (declares(declaration, CLOUD_CAPABILITIES.blobsPresigned)) {
1658
+ const blobDeps = {
1659
+ ...deviceRouteDeps,
1660
+ maxBlobSizeBytes: options.maxBlobSizeBytes ?? DEFAULT_MAX_BLOB_SIZE_BYTES
1661
+ };
1662
+ registry.register({ method: "POST", path: "/byok/blobs", class: "device" }, createBlobHandler(blobDeps));
1663
+ registry.register({ method: "POST", path: "/byok/blobs/:id/finalize", class: "device" }, finalizeBlobHandler(blobDeps));
1664
+ registry.register({ method: "GET", path: "/byok/blobs/:id/url", class: "device" }, blobDownloadUrlHandler(blobDeps));
1665
+ }
1666
+ const contentProxy = options.blobContentProxy;
1667
+ if (contentProxy !== void 0 && declares(declaration, CLOUD_CAPABILITIES.blobsContentProxy)) {
1668
+ const contentDeps = { contentProxy };
1669
+ registry.register(
1670
+ { method: "PUT", path: "/byok/blobs/:id/content", class: "presigned" },
1671
+ blobUploadContentHandler(contentDeps)
1672
+ );
1673
+ registry.register(
1674
+ { method: "GET", path: "/byok/blobs/:id/content", class: "presigned" },
1675
+ blobDownloadContentHandler(contentDeps)
1676
+ );
1677
+ }
1678
+ const operatorId = options.operatorId ?? "host";
1679
+ function controlPlane(tenant) {
1680
+ return { kind: "control-plane", tenantId: tenant, operatorId };
1681
+ }
1682
+ return {
1683
+ fetch: registry.fetch,
1684
+ routes: registry.routes,
1685
+ mountedRoutes: registry.mounted,
1686
+ capabilities: declaration,
1687
+ createPairingCode(tenant, input) {
1688
+ return auth.createPairingCode(tenant, input);
1689
+ },
1690
+ async enqueueOffer(tenant, deviceId, input) {
1691
+ const stores = tenantStoresFor(controlPlane(tenant), root);
1692
+ const taskId = input.taskId ?? `task_${options.crypto.randomUuid()}`;
1693
+ const seq = await stores.sequence.next(deviceId);
1694
+ const envelope = createEnvelope("task.offer", input.payload, { taskId, seq });
1695
+ const body = encodeEnvelope(envelope);
1696
+ const bytes = new TextEncoder().encode(body);
1697
+ const message = await stores.mailbox.append({
1698
+ deviceId,
1699
+ body,
1700
+ bodyHash: contentHash(await options.crypto.sha256(bytes)),
1701
+ byteSize: BigInt(bytes.length),
1702
+ messageId: envelope.id
1703
+ });
1704
+ if (message.seq !== seq) {
1705
+ throw new ByokCloudError(
1706
+ "mailbox_seq_mismatch",
1707
+ `Mailbox numbered this offer ${message.seq} while the delivery sequence allocated ${seq}; the daemon's redelivery cursor would be wrong.`
1708
+ );
1709
+ }
1710
+ const attempt = await stores.tasks.open({ taskId, deviceId });
1711
+ return { taskId, seq, envelope, attempt };
1712
+ },
1713
+ readTaskAttempt(tenant, taskId) {
1714
+ return tenantStoresFor(controlPlane(tenant), root).tasks.get(taskId);
1715
+ },
1716
+ readTerminalReceipt(tenant, taskId) {
1717
+ return tenantStoresFor(controlPlane(tenant), root).receipts.get(terminalReceiptKey(taskId));
1718
+ },
1719
+ listDevices(tenant) {
1720
+ return tenantStoresFor(controlPlane(tenant), root).devices.list();
1721
+ },
1722
+ revokeDevice(tenant, deviceId) {
1723
+ return tenantStoresFor(controlPlane(tenant), root).devices.revoke(deviceId);
1724
+ },
1725
+ createBoardItem(tenant, input) {
1726
+ assertBoardLabels(input.channel, input.title, {
1727
+ channelMaxBytes: options.boardChannelMaxBytes ?? DEFAULT_BOARD_CHANNEL_MAX_BYTES,
1728
+ titleMaxBytes: options.boardTitleMaxBytes ?? DEFAULT_BOARD_TITLE_MAX_BYTES
1729
+ });
1730
+ return tenantStoresFor(controlPlane(tenant), root).board.create(input);
1731
+ },
1732
+ listBoardItems(tenant, query) {
1733
+ return tenantStoresFor(controlPlane(tenant), root).board.list(query);
1734
+ },
1735
+ acceptBoardItem(tenant, itemId) {
1736
+ return tenantStoresFor(controlPlane(tenant), root).board.updateStatus({
1737
+ itemId,
1738
+ expectedStatus: "in_review",
1739
+ status: "done"
1740
+ });
1741
+ },
1742
+ listPresence(tenant) {
1743
+ return tenantStoresFor(controlPlane(tenant), root).presence.list();
1744
+ },
1745
+ readActivity(tenant, taskId) {
1746
+ return tenantStoresFor(controlPlane(tenant), root).activity.read(taskId);
1747
+ }
1748
+ };
1749
+ }
1750
+ function assertNoOverDeclaration(declaration, contentProxy, truthCommitter, truthObjectDownloads) {
1751
+ if (declares(declaration, CLOUD_CAPABILITIES.boardSse) && !declares(declaration, CLOUD_CAPABILITIES.boardCoordination)) {
1752
+ throw new ByokCloudError(
1753
+ "capability_over_declared",
1754
+ `${CLOUD_CAPABILITIES.boardSse} requires ${CLOUD_CAPABILITIES.boardCoordination}; without polling, reconciliation and declared fallback cannot be served.`
1755
+ );
1756
+ }
1757
+ if (contentProxy === void 0 && declares(declaration, CLOUD_CAPABILITIES.blobsContentProxy)) {
1758
+ throw new ByokCloudError(
1759
+ "capability_over_declared",
1760
+ `This deployment declares ${CLOUD_CAPABILITIES.blobsContentProxy} but was given no BlobContentProxy, so both /byok/blobs/:id/content routes would be published and unserved.`
1761
+ );
1762
+ }
1763
+ if (truthCommitter === void 0 && declares(declaration, CLOUD_CAPABILITIES.truthRecords)) {
1764
+ throw new ByokCloudError(
1765
+ "capability_over_declared",
1766
+ `This deployment declares ${CLOUD_CAPABILITIES.truthRecords} but was given no atomic TruthCommitter.`
1767
+ );
1768
+ }
1769
+ if (truthObjectDownloads === void 0 && declares(declaration, CLOUD_CAPABILITIES.truthRecords)) {
1770
+ throw new ByokCloudError(
1771
+ "capability_over_declared",
1772
+ `This deployment declares ${CLOUD_CAPABILITIES.truthRecords} but was given no content-hash keyed TruthObjectDownloads authority.`
1773
+ );
1774
+ }
1775
+ }
1776
+ function parseDeclaration(declaration) {
1777
+ try {
1778
+ return parseCapabilityDeclaration(declaration);
1779
+ } catch (cause) {
1780
+ throw new ByokCloudError(
1781
+ "capability_declaration_invalid",
1782
+ "The capability declaration this deployment was configured with is not a valid ADR-010 declaration.",
1783
+ { cause }
1784
+ );
1785
+ }
1786
+ }
1787
+
1788
+ // src/stores/in-memory/rate-limiter.ts
1789
+ var AllowAllRateLimiter = class {
1790
+ async consume(_tenant, _deviceId) {
1791
+ return true;
1792
+ }
1793
+ };
1794
+ var BLOB_URL_TTL_MS = 15 * 60 * 1e3;
1795
+ var SIGNING_SECRET_BYTES = 32;
1796
+ var InMemoryBlobRegistry = class {
1797
+ blobs = /* @__PURE__ */ new Map();
1798
+ reservationBlobs = /* @__PURE__ */ new Map();
1799
+ clock;
1800
+ crypto;
1801
+ secret;
1802
+ urlTtlMs;
1803
+ constructor(clock, crypto, options) {
1804
+ this.clock = clock;
1805
+ this.crypto = crypto;
1806
+ this.secret = globalThis.crypto.getRandomValues(new Uint8Array(SIGNING_SECRET_BYTES));
1807
+ this.urlTtlMs = options.urlTtlMs ?? BLOB_URL_TTL_MS;
1808
+ }
1809
+ async signUrl(blobId, action) {
1810
+ const exp = this.clock.now().getTime() + this.urlTtlMs;
1811
+ const sig = await this.computeSig(blobId, action, exp);
1812
+ return `/byok/blobs/${blobId}/content?sig=${sig}&exp=${exp}`;
1813
+ }
1814
+ computeSig(blobId, action, exp) {
1815
+ return this.crypto.hmacSha256(this.secret, `${blobId}:${action}:${exp}`);
1816
+ }
1817
+ };
1818
+ var InMemoryCloudBlobStore = class {
1819
+ #registry;
1820
+ #objects;
1821
+ constructor(registry, objects) {
1822
+ this.#registry = registry;
1823
+ this.#objects = objects;
1824
+ }
1825
+ async createUpload(tenant, reservation) {
1826
+ assertReservedObject(tenant, reservation);
1827
+ const reservationKey = `${tenant}\0${reservation.reservationId}`;
1828
+ const existingBlobId = this.#registry.reservationBlobs.get(reservationKey);
1829
+ if (existingBlobId !== void 0) {
1830
+ const manifest = await this.#objects.get(tenant, reservation.contentHash);
1831
+ if (manifest?.state === "committed") {
1832
+ throw new ByokCoreError(
1833
+ "object_state_invalid",
1834
+ `Object ${reservation.contentHash} is already committed and immutable.`
1835
+ );
1836
+ }
1837
+ return { blobId: existingBlobId, uploadUrl: await this.#registry.signUrl(existingBlobId, "put") };
1838
+ }
1839
+ const entry = await this.#objects.putManifest(tenant, {
1840
+ hash: reservation.contentHash,
1841
+ byteSize: reservation.expectedBytes,
1842
+ contentType: reservation.contentType
1843
+ });
1844
+ if (entry.byteSize !== reservation.expectedBytes || entry.contentType !== reservation.contentType) {
1845
+ throw new ByokCoreError(
1846
+ "storage_integrity_mismatch",
1847
+ `Object ${reservation.contentHash} already binds a different storage declaration.`
1848
+ );
1849
+ }
1850
+ if (entry.state === "committed") {
1851
+ throw new ByokCoreError(
1852
+ "object_state_invalid",
1853
+ `Object ${reservation.contentHash} is already committed and immutable.`
1854
+ );
1855
+ }
1856
+ const blobId = `blob_${this.#registry.crypto.randomUuid()}`;
1857
+ this.#registry.blobs.set(blobId, {
1858
+ tenantId: tenant,
1859
+ reservationId: reservation.reservationId,
1860
+ contentHash: reservation.contentHash,
1861
+ byteSize: reservation.expectedBytes,
1862
+ contentType: reservation.contentType,
1863
+ uploaded: false
1864
+ });
1865
+ this.#registry.reservationBlobs.set(reservationKey, blobId);
1866
+ return { blobId, uploadUrl: await this.#registry.signUrl(blobId, "put") };
1867
+ }
1868
+ async observeUpload(tenant, blobId, reservation) {
1869
+ const record = this.#registry.blobs.get(blobId);
1870
+ if (record === void 0 || record.tenantId !== tenant || reservation.tenantId !== tenant || record.reservationId !== reservation.reservationId || record.contentHash !== reservation.contentHash || !record.uploaded) {
1871
+ return void 0;
1872
+ }
1873
+ return {
1874
+ observedByteSize: BigInt(record.data?.length ?? 0),
1875
+ observedContentType: record.contentType
1876
+ };
1877
+ }
1878
+ async getDownloadUrl(tenant, blobId) {
1879
+ const record = this.#registry.blobs.get(blobId);
1880
+ if (record === void 0 || record.tenantId !== tenant || !record.uploaded) return void 0;
1881
+ const manifest = await this.#objects.get(tenant, record.contentHash);
1882
+ if (manifest?.state !== "committed") return void 0;
1883
+ return this.#registry.signUrl(blobId, "get");
1884
+ }
1885
+ };
1886
+ var InMemoryBlobContentProxy = class {
1887
+ #registry;
1888
+ constructor(registry) {
1889
+ this.#registry = registry;
1890
+ }
1891
+ async verifySignedUrl(blobId, action, sig, exp) {
1892
+ if (!Number.isFinite(exp) || this.#registry.clock.now().getTime() > exp) return false;
1893
+ const expected = await this.#registry.computeSig(blobId, action, exp);
1894
+ return this.#registry.crypto.timingSafeEqual(expected, sig);
1895
+ }
1896
+ async writeContent(blobId, data) {
1897
+ const record = this.#registry.blobs.get(blobId);
1898
+ if (record === void 0) return { ok: false, reason: "unknown blobId" };
1899
+ if (BigInt(data.length) !== record.byteSize) {
1900
+ return { ok: false, reason: `size mismatch: declared ${String(record.byteSize)}, received ${data.length}` };
1901
+ }
1902
+ const actualHash = await this.#registry.crypto.sha256(data);
1903
+ if (actualHash !== record.contentHash) {
1904
+ return { ok: false, reason: "contentHash mismatch" };
1905
+ }
1906
+ record.data = data;
1907
+ record.uploaded = true;
1908
+ return { ok: true };
1909
+ }
1910
+ async readContent(blobId) {
1911
+ const record = this.#registry.blobs.get(blobId);
1912
+ if (record === void 0 || !record.uploaded || record.data === void 0) return void 0;
1913
+ return { data: record.data, contentType: record.contentType };
1914
+ }
1915
+ };
1916
+ function createInMemoryBlobs(clock, crypto, objects, options = {}) {
1917
+ const registry = new InMemoryBlobRegistry(clock, crypto, options);
1918
+ return {
1919
+ blobs: new InMemoryCloudBlobStore(registry, objects),
1920
+ contentProxy: new InMemoryBlobContentProxy(registry)
1921
+ };
1922
+ }
1923
+ function assertReservedObject(tenant, reservation) {
1924
+ if (reservation.tenantId === tenant && reservation.kind === "object" && reservation.state === "reserved" && reservation.expectedBytes >= 0n) {
1925
+ return;
1926
+ }
1927
+ throw new ByokCoreError(
1928
+ "storage_integrity_mismatch",
1929
+ "An upload grant requires a reserved object reservation owned by this tenant."
1930
+ );
1931
+ }
1932
+ var InMemoryDeviceDirectory = class {
1933
+ #byTenant = /* @__PURE__ */ new Map();
1934
+ #byDeviceId = /* @__PURE__ */ new Map();
1935
+ async register(tenant, input) {
1936
+ const record = {
1937
+ tenantId: tenant,
1938
+ productId: input.productId,
1939
+ deviceId: input.deviceId,
1940
+ deviceName: input.deviceName,
1941
+ devicePublicKey: input.devicePublicKey,
1942
+ proofKeyId: input.proofKeyId,
1943
+ proofKeyEpoch: input.proofKeyEpoch,
1944
+ revoked: false
1945
+ };
1946
+ const key = tenantKey(tenant, record.deviceId);
1947
+ this.#byTenant.set(key, record);
1948
+ this.#byDeviceId.set(record.deviceId, key);
1949
+ return record;
1950
+ }
1951
+ async get(tenant, deviceId) {
1952
+ return this.#byTenant.get(tenantKey(tenant, deviceId));
1953
+ }
1954
+ async revoke(tenant, deviceId) {
1955
+ const key = tenantKey(tenant, deviceId);
1956
+ const record = this.#byTenant.get(key);
1957
+ if (record === void 0) return;
1958
+ this.#byTenant.set(key, { ...record, revoked: true });
1959
+ }
1960
+ async list(tenant) {
1961
+ const prefix = tenantKey(tenant, "");
1962
+ return [...this.#byTenant.entries()].filter(([key]) => key.startsWith(prefix)).map(([, record]) => record);
1963
+ }
1964
+ async resolveByDeviceId(deviceId) {
1965
+ const key = this.#byDeviceId.get(deviceId);
1966
+ return key === void 0 ? void 0 : this.#byTenant.get(key);
1967
+ }
1968
+ };
1969
+ var InMemoryDeviceSequenceStore = class {
1970
+ #next = /* @__PURE__ */ new Map();
1971
+ async next(tenant, deviceId) {
1972
+ const key = tenantKey(tenant, deviceId);
1973
+ const seq = this.#next.get(key) ?? 1;
1974
+ this.#next.set(key, seq + 1);
1975
+ return seq;
1976
+ }
1977
+ };
1978
+ var DEDUP_RING_CAPACITY = 1024;
1979
+ var InMemoryInboundDedupStore = class {
1980
+ #rings = /* @__PURE__ */ new Map();
1981
+ #capacity;
1982
+ constructor(capacity = DEDUP_RING_CAPACITY) {
1983
+ this.#capacity = capacity;
1984
+ }
1985
+ async checkAndRecord(tenant, deviceId, envelopeId) {
1986
+ const key = tenantKey(tenant, deviceId);
1987
+ let ring = this.#rings.get(key);
1988
+ if (ring === void 0) {
1989
+ ring = /* @__PURE__ */ new Set();
1990
+ this.#rings.set(key, ring);
1991
+ }
1992
+ if (ring.has(envelopeId)) return true;
1993
+ ring.add(envelopeId);
1994
+ if (ring.size > this.#capacity) {
1995
+ const oldest = ring.values().next().value;
1996
+ if (oldest !== void 0) ring.delete(oldest);
1997
+ }
1998
+ return false;
1999
+ }
2000
+ };
2001
+ var NONCE_TTL_MS = 5 * 60 * 1e3;
2002
+ var NONCE_BYTES = 24;
2003
+ var InMemoryNonceStore = class {
2004
+ #nonces = /* @__PURE__ */ new Map();
2005
+ #clock;
2006
+ #crypto;
2007
+ #ttlMs;
2008
+ constructor(clock, crypto, ttlMs = NONCE_TTL_MS) {
2009
+ this.#clock = clock;
2010
+ this.#crypto = crypto;
2011
+ this.#ttlMs = ttlMs;
2012
+ }
2013
+ /** Number of records currently held (post-sweep). Test-facing only. */
2014
+ get size() {
2015
+ return this.#nonces.size;
2016
+ }
2017
+ async issue(tenant, deviceId) {
2018
+ const nowMs = this.#clock.now().getTime();
2019
+ for (const [nonce2, record] of this.#nonces) {
2020
+ if (record.used || record.expiresAtMs <= nowMs) this.#nonces.delete(nonce2);
2021
+ }
2022
+ const nonce = this.#crypto.randomToken(NONCE_BYTES);
2023
+ this.#nonces.set(nonce, {
2024
+ owner: tenantKey(tenant, deviceId),
2025
+ expiresAtMs: nowMs + this.#ttlMs,
2026
+ used: false
2027
+ });
2028
+ return nonce;
2029
+ }
2030
+ async validate(tenant, deviceId, nonce) {
2031
+ const record = this.#nonces.get(nonce);
2032
+ if (record === void 0) return false;
2033
+ if (record.used) return false;
2034
+ if (record.owner !== tenantKey(tenant, deviceId)) return false;
2035
+ return this.#clock.now().getTime() <= record.expiresAtMs;
2036
+ }
2037
+ async markUsed(tenant, nonce) {
2038
+ const record = this.#nonces.get(nonce);
2039
+ if (record === void 0) return;
2040
+ if (!record.owner.startsWith(tenantKey(tenant, ""))) return;
2041
+ record.used = true;
2042
+ }
2043
+ };
2044
+
2045
+ // src/stores/in-memory/pairing-codes.ts
2046
+ var InMemoryPairingCodeStore = class {
2047
+ #codes = /* @__PURE__ */ new Map();
2048
+ #clock;
2049
+ constructor(clock) {
2050
+ this.#clock = clock;
2051
+ }
2052
+ async issue(tenant, input) {
2053
+ this.#codes.set(input.code, {
2054
+ claims: { tenantId: tenant, productId: input.productId },
2055
+ expiresAtMs: new Date(input.expiresAt).getTime(),
2056
+ used: false
2057
+ });
2058
+ return { code: input.code, expiresAt: input.expiresAt };
2059
+ }
2060
+ async redeem(code) {
2061
+ const record = this.#codes.get(code);
2062
+ if (record === void 0) return void 0;
2063
+ if (record.used) return void 0;
2064
+ if (this.#clock.now().getTime() > record.expiresAtMs) return void 0;
2065
+ record.used = true;
2066
+ return record.claims;
2067
+ }
2068
+ };
2069
+ var InMemoryRequestReceiptStore = class {
2070
+ #receipts = /* @__PURE__ */ new Map();
2071
+ #clock;
2072
+ constructor(clock) {
2073
+ this.#clock = clock;
2074
+ }
2075
+ async record(tenant, input) {
2076
+ const key = tenantKey(tenant, input.key);
2077
+ const existing = this.#receipts.get(key);
2078
+ if (existing !== void 0) return { receipt: existing, created: false };
2079
+ const receipt = {
2080
+ tenantId: tenant,
2081
+ key: input.key,
2082
+ body: input.body,
2083
+ recordedAt: this.#clock.now().toISOString()
2084
+ };
2085
+ this.#receipts.set(key, receipt);
2086
+ return { receipt, created: true };
2087
+ }
2088
+ async get(tenant, key) {
2089
+ return this.#receipts.get(tenantKey(tenant, key));
2090
+ }
2091
+ };
2092
+ var InMemoryProofRequestReceiptStore = class {
2093
+ #receipts = /* @__PURE__ */ new Map();
2094
+ #clock;
2095
+ constructor(clock) {
2096
+ this.#clock = clock;
2097
+ }
2098
+ async record(tenant, input) {
2099
+ const key = tenantKey(tenant, input.deviceId, input.requestId);
2100
+ const existing = this.#receipts.get(key);
2101
+ if (existing !== void 0) return { receipt: existing, created: false };
2102
+ const receipt = {
2103
+ tenantId: tenant,
2104
+ ...input,
2105
+ recordedAt: this.#clock.now().toISOString()
2106
+ };
2107
+ this.#receipts.set(key, receipt);
2108
+ return { receipt, created: true };
2109
+ }
2110
+ async get(tenant, deviceId, requestId) {
2111
+ return this.#receipts.get(tenantKey(tenant, deviceId, requestId));
2112
+ }
2113
+ };
2114
+ var InMemoryTaskAttemptStore = class {
2115
+ #attempts = /* @__PURE__ */ new Map();
2116
+ #clock;
2117
+ constructor(clock) {
2118
+ this.#clock = clock;
2119
+ }
2120
+ async open(tenant, input) {
2121
+ const key = tenantKey(tenant, input.taskId);
2122
+ const existing = this.#attempts.get(key);
2123
+ if (existing !== void 0) return existing;
2124
+ const attempt = {
2125
+ tenantId: tenant,
2126
+ taskId: input.taskId,
2127
+ deviceId: input.deviceId,
2128
+ status: "offered",
2129
+ updatedAt: this.#now()
2130
+ };
2131
+ this.#attempts.set(key, attempt);
2132
+ return attempt;
2133
+ }
2134
+ async get(tenant, taskId) {
2135
+ return this.#attempts.get(tenantKey(tenant, taskId));
2136
+ }
2137
+ async claim(tenant, input) {
2138
+ const key = tenantKey(tenant, input.taskId);
2139
+ const existing = this.#attempts.get(key);
2140
+ if (existing === void 0) return void 0;
2141
+ if (existing.ownerDeviceId !== void 0) return existing;
2142
+ const claimed = {
2143
+ ...existing,
2144
+ ownerDeviceId: input.deviceId,
2145
+ status: "claimed",
2146
+ updatedAt: this.#now()
2147
+ };
2148
+ this.#attempts.set(key, claimed);
2149
+ return claimed;
2150
+ }
2151
+ async recordStatus(tenant, input) {
2152
+ const key = tenantKey(tenant, input.taskId);
2153
+ const existing = this.#attempts.get(key);
2154
+ if (existing === void 0) return void 0;
2155
+ const updated = { ...existing, status: input.status, updatedAt: this.#now() };
2156
+ this.#attempts.set(key, updated);
2157
+ return updated;
2158
+ }
2159
+ #now() {
2160
+ return this.#clock.now().toISOString();
2161
+ }
2162
+ };
2163
+
2164
+ // src/stores/in-memory/index.ts
2165
+ function createInMemoryCloudStores(clock, crypto, objects) {
2166
+ const blobs = createInMemoryBlobs(clock, crypto, objects);
2167
+ return {
2168
+ stores: {
2169
+ devices: new InMemoryDeviceDirectory(),
2170
+ pairingCodes: new InMemoryPairingCodeStore(clock),
2171
+ nonces: new InMemoryNonceStore(clock, crypto),
2172
+ dedup: new InMemoryInboundDedupStore(),
2173
+ tasks: new InMemoryTaskAttemptStore(clock),
2174
+ receipts: new InMemoryRequestReceiptStore(clock),
2175
+ proofReceipts: new InMemoryProofRequestReceiptStore(clock),
2176
+ sequence: new InMemoryDeviceSequenceStore(),
2177
+ blobs: blobs.blobs,
2178
+ rateLimiter: new AllowAllRateLimiter()
2179
+ },
2180
+ blobContentProxy: blobs.contentProxy
2181
+ };
2182
+ }
2183
+
2184
+ // src/composition/in-memory.ts
2185
+ var TOKEN_SECRET_BYTES = 32;
2186
+ function systemClock() {
2187
+ return { now: () => /* @__PURE__ */ new Date() };
2188
+ }
2189
+ function createInMemoryByokCloud(options = {}) {
2190
+ const clock = options.clock ?? systemClock();
2191
+ const crypto = options.crypto ?? createWebCrypto();
2192
+ const core = createInMemoryCoreStores({ clock }).stores;
2193
+ const { stores, blobContentProxy } = createInMemoryCloudStores(clock, crypto, core.objects);
2194
+ const tokenSigner = options.tokenSigner ?? createHmacTokenSigner(globalThis.crypto.getRandomValues(new Uint8Array(TOKEN_SECRET_BYTES)), clock);
2195
+ const cloud = createByokCloud({
2196
+ core,
2197
+ cloud: stores,
2198
+ // This composition has nowhere else to put bytes, so it supplies the proxy
2199
+ // and `fullCapabilityDeclaration()` declares `blobs.contentproxy`. That
2200
+ // pairing is what keeps hosted-in-memory behavior identical to what it was
2201
+ // before the port narrowed.
2202
+ blobContentProxy,
2203
+ crypto,
2204
+ tokenSigner,
2205
+ clock,
2206
+ capabilities: options.capabilities ?? fullCapabilityDeclaration(),
2207
+ ...options.truthCommitter === void 0 ? {} : { truthCommitter: options.truthCommitter },
2208
+ ...options.truthObjectDownloads === void 0 ? {} : { truthObjectDownloads: options.truthObjectDownloads },
2209
+ ...options.operatorId !== void 0 ? { operatorId: options.operatorId } : {},
2210
+ ...options.maxBlobSizeBytes !== void 0 ? { maxBlobSizeBytes: options.maxBlobSizeBytes } : {},
2211
+ ...options.longPollHoldMs !== void 0 ? { longPollHoldMs: options.longPollHoldMs } : {},
2212
+ ...options.longPollIntervalMs !== void 0 ? { longPollIntervalMs: options.longPollIntervalMs } : {},
2213
+ ...options.eventsPageLimit !== void 0 ? { eventsPageLimit: options.eventsPageLimit } : {},
2214
+ ...options.accessTokenTtlSeconds !== void 0 ? { accessTokenTtlSeconds: options.accessTokenTtlSeconds } : {},
2215
+ ...options.boardPageLimit === void 0 ? {} : { boardPageLimit: options.boardPageLimit },
2216
+ ...options.boardStreamQueryIntervalMs === void 0 ? {} : { boardStreamQueryIntervalMs: options.boardStreamQueryIntervalMs },
2217
+ ...options.boardStreamHeartbeatIntervalMs === void 0 ? {} : { boardStreamHeartbeatIntervalMs: options.boardStreamHeartbeatIntervalMs },
2218
+ ...options.boardStreamReconciliationIntervalMs === void 0 ? {} : { boardStreamReconciliationIntervalMs: options.boardStreamReconciliationIntervalMs },
2219
+ ...options.boardChannelMaxBytes === void 0 ? {} : { boardChannelMaxBytes: options.boardChannelMaxBytes },
2220
+ ...options.boardTitleMaxBytes === void 0 ? {} : { boardTitleMaxBytes: options.boardTitleMaxBytes },
2221
+ ...options.presenceTtlMs === void 0 ? {} : { presenceTtlMs: options.presenceTtlMs },
2222
+ ...options.presenceMinimumIntervalMs === void 0 ? {} : { presenceMinimumIntervalMs: options.presenceMinimumIntervalMs },
2223
+ ...options.presenceDetailMaxBytes === void 0 ? {} : { presenceDetailMaxBytes: options.presenceDetailMaxBytes },
2224
+ ...options.activityMaxEvents === void 0 ? {} : { activityMaxEvents: options.activityMaxEvents },
2225
+ ...options.activityMaxBytes === void 0 ? {} : { activityMaxBytes: options.activityMaxBytes },
2226
+ ...options.activityCapacity === void 0 ? {} : { activityCapacity: options.activityCapacity },
2227
+ ...options.activityTtlMs === void 0 ? {} : { activityTtlMs: options.activityTtlMs },
2228
+ ...options.maxTruthRequestBytes === void 0 ? {} : { maxTruthRequestBytes: options.maxTruthRequestBytes }
2229
+ });
2230
+ return { cloud, core, stores, blobContentProxy, clock, crypto };
2231
+ }
2232
+ var BoardFeedItemSchema = z.object({
2233
+ tenantId: z.string(),
2234
+ itemId: z.string(),
2235
+ channel: z.string(),
2236
+ title: z.string(),
2237
+ status: z.enum(BOARD_STATUSES),
2238
+ assignee: z.object({ holderId: z.string(), heldSince: z.string() }).optional(),
2239
+ boardSeq: z.number().int().nonnegative(),
2240
+ createdAt: z.string(),
2241
+ updatedAt: z.string()
2242
+ });
2243
+ var BoardFeedPageSchema = z.object({
2244
+ items: z.array(BoardFeedItemSchema),
2245
+ nextSeq: z.number().int().nonnegative(),
2246
+ hasMore: z.boolean()
2247
+ });
2248
+ var BoardFeedRetryableError = class extends Error {
2249
+ constructor(message, options) {
2250
+ super(message, options);
2251
+ this.name = "BoardFeedRetryableError";
2252
+ }
2253
+ };
2254
+ var BoardFeedStoppedError = class extends Error {
2255
+ constructor(message) {
2256
+ super(message);
2257
+ this.name = "BoardFeedStoppedError";
2258
+ }
2259
+ };
2260
+ var BoardFeedClient = class {
2261
+ mode;
2262
+ #base;
2263
+ #accessToken;
2264
+ #fetch;
2265
+ #idleWatchdogMs;
2266
+ constructor(options) {
2267
+ assertCapability(options.capabilities, CLOUD_CAPABILITIES.boardCoordination);
2268
+ this.mode = hasCapability(options.capabilities, CLOUD_CAPABILITIES.boardSse) ? "sse" : "poll";
2269
+ this.#base = new URL(options.baseUrl);
2270
+ this.#accessToken = options.accessToken;
2271
+ this.#fetch = options.fetch ?? globalThis.fetch;
2272
+ this.#idleWatchdogMs = options.idleWatchdogMs ?? 3e4;
2273
+ }
2274
+ async readOnce(afterSeq, signal) {
2275
+ if (!Number.isSafeInteger(afterSeq) || afterSeq < 0) {
2276
+ throw new TypeError("afterSeq must be a non-negative safe integer.");
2277
+ }
2278
+ return this.mode === "sse" ? this.#readSseOnce(afterSeq, signal) : this.#readPollOnce(afterSeq, signal);
2279
+ }
2280
+ async #readPollOnce(afterSeq, signal) {
2281
+ const url = new URL("/byok/board", this.#base);
2282
+ url.searchParams.set("since", String(afterSeq));
2283
+ const response = await this.#fetch(url, {
2284
+ headers: { authorization: `Bearer ${this.#accessToken}` },
2285
+ ...signal === void 0 ? {} : { signal }
2286
+ });
2287
+ assertBoardResponse(response, "poll");
2288
+ return { type: "poll", page: BoardFeedPageSchema.parse(await response.json()) };
2289
+ }
2290
+ async #readSseOnce(afterSeq, signal) {
2291
+ const url = new URL("/byok/board/stream", this.#base);
2292
+ const response = await this.#fetch(url, {
2293
+ headers: {
2294
+ accept: "text/event-stream",
2295
+ authorization: `Bearer ${this.#accessToken}`,
2296
+ "last-event-id": String(afterSeq)
2297
+ },
2298
+ ...signal === void 0 ? {} : { signal }
2299
+ });
2300
+ assertBoardResponse(response, "SSE");
2301
+ if (response.body === null) throw new BoardFeedRetryableError("Board SSE response had no body.");
2302
+ const reader = response.body.getReader();
2303
+ const decoder = new TextDecoder();
2304
+ let buffer = "";
2305
+ try {
2306
+ while (true) {
2307
+ const chunk = await readWithWatchdog(reader, this.#idleWatchdogMs);
2308
+ if (chunk.done) throw new BoardFeedRetryableError("Board SSE ended before the next event.");
2309
+ buffer += decoder.decode(chunk.value, { stream: true }).replaceAll("\r\n", "\n");
2310
+ let boundary = buffer.indexOf("\n\n");
2311
+ while (boundary >= 0) {
2312
+ const frame = buffer.slice(0, boundary);
2313
+ buffer = buffer.slice(boundary + 2);
2314
+ const parsed = parseSseFrame(frame);
2315
+ if (parsed !== void 0) return parsed;
2316
+ boundary = buffer.indexOf("\n\n");
2317
+ }
2318
+ }
2319
+ } finally {
2320
+ await reader.cancel().catch(() => void 0);
2321
+ }
2322
+ }
2323
+ };
2324
+ function assertBoardResponse(response, transport) {
2325
+ if (response.status === 401) {
2326
+ throw new BoardFeedStoppedError("Board credential is no longer valid.");
2327
+ }
2328
+ if (response.ok) return;
2329
+ if (response.status >= 500) {
2330
+ throw new BoardFeedRetryableError(`Board ${transport} failed with HTTP ${response.status}.`);
2331
+ }
2332
+ throw new BoardFeedStoppedError(
2333
+ `Board ${transport} failed permanently with HTTP ${response.status}.`
2334
+ );
2335
+ }
2336
+ async function readWithWatchdog(reader, timeoutMs) {
2337
+ let timer;
2338
+ try {
2339
+ return await Promise.race([
2340
+ reader.read(),
2341
+ new Promise((_, reject) => {
2342
+ timer = setTimeout(
2343
+ () => reject(new BoardFeedRetryableError("Board SSE idle watchdog expired.")),
2344
+ timeoutMs
2345
+ );
2346
+ })
2347
+ ]);
2348
+ } finally {
2349
+ if (timer !== void 0) clearTimeout(timer);
2350
+ }
2351
+ }
2352
+ function parseSseFrame(frame) {
2353
+ if (frame.length === 0 || frame.startsWith(":")) return void 0;
2354
+ let event = "message";
2355
+ const data = [];
2356
+ for (const line of frame.split("\n")) {
2357
+ if (line.startsWith("event:")) event = line.slice("event:".length).trimStart();
2358
+ if (line.startsWith("data:")) data.push(line.slice("data:".length).trimStart());
2359
+ }
2360
+ if (event === "reconcile") return { type: "reconcile" };
2361
+ if (event !== "board") return void 0;
2362
+ if (data.length === 0) throw new SyntaxError("Board SSE board event had no data.");
2363
+ return { type: "board", item: BoardFeedItemSchema.parse(JSON.parse(data.join("\n"))) };
2364
+ }
2365
+
2366
+ // src/stores/ports.ts
2367
+ var TASK_ATTEMPT_STATUSES = [
2368
+ "offered",
2369
+ "claimed",
2370
+ "running",
2371
+ "complete",
2372
+ "failed",
2373
+ "cancelled"
2374
+ ];
2375
+ var CLOUD_STORE_NAMES = [
2376
+ "devices",
2377
+ "pairingCodes",
2378
+ "nonces",
2379
+ "dedup",
2380
+ "tasks",
2381
+ "receipts",
2382
+ "proofReceipts",
2383
+ "sequence",
2384
+ "blobs",
2385
+ "rateLimiter"
2386
+ ];
2387
+
2388
+ // src/stores/ports-contract.ts
2389
+ var CLOUD_PORT_METHODS = {
2390
+ devices: ["register", "get", "revoke", "list", "resolveByDeviceId"],
2391
+ pairingCodes: ["issue", "redeem"],
2392
+ nonces: ["issue", "validate", "markUsed"],
2393
+ dedup: ["checkAndRecord"],
2394
+ tasks: ["open", "get", "claim", "recordStatus"],
2395
+ receipts: ["record", "get"],
2396
+ proofReceipts: ["record", "get"],
2397
+ sequence: ["next"],
2398
+ // Three methods, not six: the byte-proxy trio moved to `BlobContentProxy`,
2399
+ // which is a composition input rather than a port and therefore has no row
2400
+ // in this table (docs/researches/s4a-dataplane-design.md §6).
2401
+ blobs: ["createUpload", "observeUpload", "getDownloadUrl"],
2402
+ rateLimiter: ["consume"]
2403
+ };
2404
+ var CLOUD_PORT_INTERFACES = {
2405
+ devices: "DeviceDirectory",
2406
+ pairingCodes: "PairingCodeStore",
2407
+ nonces: "NonceStore",
2408
+ dedup: "InboundDedupStore",
2409
+ tasks: "TaskAttemptStore",
2410
+ receipts: "RequestReceiptStore",
2411
+ proofReceipts: "ProofRequestReceiptStore",
2412
+ sequence: "DeviceSequenceStore",
2413
+ blobs: "CloudBlobStore",
2414
+ rateLimiter: "InboundRateLimiter"
2415
+ };
2416
+
2417
+ export { ACCESS_TOKEN_TTL_SECONDS, AllowAllRateLimiter, BLOB_URL_TTL_MS, BoardFeedClient, BoardFeedRetryableError, BoardFeedStoppedError, ByokCloudError, CLOUD_CAPABILITIES, CLOUD_ERROR_CODES, CLOUD_PORT_INTERFACES, CLOUD_PORT_METHODS, CLOUD_STORE_NAMES, CapabilitiesResponseSchema, CloudRouteRegistry, DEDUP_RING_CAPACITY, 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_PAGE_LIMIT, DEFAULT_BOARD_STREAM_HEARTBEAT_INTERVAL_MS, DEFAULT_BOARD_STREAM_QUERY_INTERVAL_MS, DEFAULT_BOARD_STREAM_RECONCILIATION_INTERVAL_MS, DEFAULT_BOARD_TITLE_MAX_BYTES, DEFAULT_DEVICE_PROOF_CLOCK_SKEW_MS, DEFAULT_DEVICE_PROOF_MAX_LIFETIME_MS, DEFAULT_EVENTS_PAGE_LIMIT, DEFAULT_LONG_POLL_HOLD_MS, DEFAULT_LONG_POLL_INTERVAL_MS, DEFAULT_MAX_BLOB_SIZE_BYTES, DEFAULT_MAX_TRUTH_REQUEST_BYTES, DEFAULT_PRESENCE_DETAIL_MAX_BYTES, DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS, DEFAULT_PRESENCE_TTL_MS, DEVICE_IDENTITY_PROOF_KEY_EPOCH, DEVICE_IDENTITY_PROOF_KEY_ID, DEVICE_PROOF_HEADER, InMemoryBlobContentProxy, InMemoryCloudBlobStore, InMemoryDeviceDirectory, InMemoryDeviceSequenceStore, InMemoryInboundDedupStore, InMemoryNonceStore, InMemoryPairingCodeStore, InMemoryProofRequestReceiptStore, InMemoryRequestReceiptStore, InMemoryTaskAttemptStore, MAX_DEVICE_PROOF_CLOCK_SKEW_MS, MAX_DEVICE_PROOF_HEADER_BYTES, MAX_DEVICE_PROOF_MAX_LIFETIME_MS, NONCE_SIGNING_DOMAIN, NONCE_TTL_MS, PAIRING_CODE_TTL_MS, ROUTE_CLASSES, ROUTE_METHODS, TASK_ATTEMPT_STATUSES, 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, TruthCommitError, TruthCommitResponseSchema, TruthRecordKeySchema, TruthRecordMetadataSchema, TruthWriteRequestSchema, authenticateBearer, authenticateDeviceProof, createAuthPlane, createByokCloud, createHmacTokenSigner, createInMemoryBlobs, createInMemoryByokCloud, createInMemoryCloudStores, createWebCrypto, declares, extractBearerToken, fullCapabilityDeclaration, handleInboundEnvelope, isCloudError, isTruthCommitError, routeKey, tenantStoresFor, terminalReceiptKey, truthManifestMetadata, truthRecordMetadata, verifyNonceSignature };
2418
+ //# sourceMappingURL=index.js.map
2419
+ //# sourceMappingURL=index.js.map