@byok-sdk/cloud 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { BOARD_STATUSES, PRESENCE_LEVELS, CapabilityDeclarationSchema, NONCE_SIGNING_DOMAIN, hasCapability, isTenantId, tenantId, principalTenant, parseDeviceProofEnvelope, deviceProofSigningInput, parseCapabilityDeclaration, ByokCoreError, tenantKey, createInMemoryCoreStores, assertCapability, contentHash, isCoreConflictError, isCoreError, DEVICE_PROOF_HEADER, TRUTH_RECORD_KINDS, STORAGE_ERROR_CODES, STORAGE_ERROR_HTTP_STATUS } from '@byok-sdk/core';
2
2
  export { DEVICE_PROOF_HEADER, NONCE_SIGNING_DOMAIN, isTenantId, tenantId } from '@byok-sdk/core';
3
- import { ConfiguredToolsetsSchema, AgentEventOrUnknownSchema, DAEMON_TO_SERVER_TYPES, encodeEnvelope, decodeEnvelope, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, BYOK_CAPABILITIES_PATH, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, BYOK_BOARD_PATH, BYOK_BOARD_CLAIM_ROUTE, BYOK_BOARD_UNCLAIM_ROUTE, BYOK_BOARD_STATUS_ROUTE, BYOK_BOARD_STREAM_PATH, BYOK_PRESENCE_PATH, BYOK_ACTIVITY_PATH, BYOK_RECORDS_PATH, BYOK_RECORD_ROUTE, BYOK_SKILL_PACKS_PATH, BYOK_SKILL_PACK_FILE_ROUTE, BYOK_BLOBS_PATH, BYOK_BLOB_FINALIZE_ROUTE, BYOK_BLOB_URL_ROUTE, BYOK_BLOB_CONTENT_ROUTE, createEnvelope, PairRequestSchema, ChallengeRequestSchema, TokenRequestSchema, MessagesSendRequestSchema, CreateBlobRequestSchema, byokBlobContentPath } from '@byok-sdk/protocol';
3
+ import { AgentEventOrUnknownSchema, ConfiguredToolsetsSchema, DAEMON_TO_SERVER_TYPES, encodeEnvelope, decodeEnvelope, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, BYOK_CAPABILITIES_PATH, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, BYOK_BOARD_PATH, BYOK_BOARD_CLAIM_ROUTE, BYOK_BOARD_UNCLAIM_ROUTE, BYOK_BOARD_STATUS_ROUTE, BYOK_BOARD_STREAM_PATH, BYOK_PRESENCE_PATH, BYOK_ACTIVITY_PATH, BYOK_RECORDS_PATH, BYOK_RECORD_ROUTE, BYOK_SKILL_PACKS_PATH, BYOK_SKILL_PACK_FILE_ROUTE, BYOK_BLOBS_PATH, BYOK_BLOB_FINALIZE_ROUTE, BYOK_BLOB_URL_ROUTE, BYOK_BLOB_CONTENT_ROUTE, createEnvelope, PairRequestSchema, ChallengeRequestSchema, TokenRequestSchema, MessagesSendRequestSchema, CreateBlobRequestSchema, byokBlobContentPath } from '@byok-sdk/protocol';
4
4
  import { z } from 'zod';
5
5
  import { Hono } from 'hono';
6
6
 
@@ -332,12 +332,79 @@ function isCloudError(value, code) {
332
332
  return code === void 0 || value.code === code;
333
333
  }
334
334
 
335
+ // src/activity.ts
336
+ var DEFAULT_ACTIVITY_CAPACITY = 50;
337
+ var ActivityTaskIdSchema = z.string().min(1).max(200);
338
+ var SourceEnvelopeIdSchema = z.string().max(200).regex(/\S/, "sourceEnvelopeId must not be blank");
339
+ var ActivityAppendRequestSchema = z.object({
340
+ taskId: ActivityTaskIdSchema,
341
+ sourceEnvelopeId: SourceEnvelopeIdSchema,
342
+ batchSeq: z.number().int().nonnegative(),
343
+ events: z.array(AgentEventOrUnknownSchema).min(1),
344
+ dropped: z.number().int().nonnegative()
345
+ });
346
+ var TimelineEventSchema = z.object({
347
+ taskId: ActivityTaskIdSchema,
348
+ sourceEnvelopeId: SourceEnvelopeIdSchema,
349
+ batchSeq: z.number().int().nonnegative(),
350
+ eventIndex: z.number().int().nonnegative(),
351
+ receivedAt: z.iso.datetime(),
352
+ event: AgentEventOrUnknownSchema
353
+ });
354
+ function validateActivityAppend(input) {
355
+ if (!Number.isFinite(input.ttlMs) || input.ttlMs <= 0) {
356
+ throw new ByokCloudError(
357
+ "coordination_input_invalid",
358
+ `Activity ttl must be a positive number of milliseconds, received ${String(input.ttlMs)}.`
359
+ );
360
+ }
361
+ const capacity = input.capacity ?? DEFAULT_ACTIVITY_CAPACITY;
362
+ if (!Number.isSafeInteger(capacity) || capacity <= 0) {
363
+ throw new ByokCloudError(
364
+ "coordination_input_invalid",
365
+ `Activity capacity must be a positive integer, received ${String(capacity)}.`
366
+ );
367
+ }
368
+ const parsed = ActivityAppendRequestSchema.safeParse(input);
369
+ if (!parsed.success) {
370
+ throw new ByokCloudError(
371
+ "coordination_input_invalid",
372
+ "Activity batches require stable source identity, order, at least one valid event, and a non-negative dropped count."
373
+ );
374
+ }
375
+ return capacity;
376
+ }
377
+ function projectTimelineEvents(input, receivedAt) {
378
+ return input.events.map(
379
+ (event, eventIndex) => TimelineEventSchema.parse({
380
+ taskId: input.taskId,
381
+ sourceEnvelopeId: input.sourceEnvelopeId,
382
+ batchSeq: input.batchSeq,
383
+ eventIndex,
384
+ receivedAt,
385
+ event
386
+ })
387
+ );
388
+ }
389
+ function parseTimelineEvents(value) {
390
+ return z.array(TimelineEventSchema).parse(value);
391
+ }
392
+ function compareTimelineEvents(left, right) {
393
+ return left.batchSeq - right.batchSeq || left.eventIndex - right.eventIndex;
394
+ }
395
+ function activityCursor(entries) {
396
+ const last = entries.at(-1);
397
+ return last === void 0 ? void 0 : { batchSeq: last.batchSeq, eventIndex: last.eventIndex };
398
+ }
399
+ function activityTailKey(tenant, taskId) {
400
+ return tenantKey(tenant, taskId);
401
+ }
402
+
335
403
  // src/coordination.ts
336
404
  var DEFAULT_BOARD_CHANNEL_MAX_BYTES = 128;
337
405
  var DEFAULT_BOARD_TITLE_MAX_BYTES = 512;
338
406
  var DEFAULT_ACTIVITY_MAX_EVENTS = 50;
339
407
  var DEFAULT_ACTIVITY_MAX_BYTES = 64 * 1024;
340
- var DEFAULT_ACTIVITY_CAPACITY = 50;
341
408
  var DEFAULT_ACTIVITY_TTL_MS = 10 * 6e4;
342
409
  var DEFAULT_PRESENCE_TTL_MS = 9e4;
343
410
  var DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS = 5e3;
@@ -363,7 +430,7 @@ function assertBoardLabels(channel, title, limits) {
363
430
  );
364
431
  }
365
432
  }
366
- function activityDetails(events, dropped, bounds) {
433
+ function validateActivityEvents(events, dropped, bounds) {
367
434
  if (!Number.isSafeInteger(dropped) || dropped < 0) {
368
435
  throw new ByokCloudError(
369
436
  "coordination_input_invalid",
@@ -383,12 +450,18 @@ function activityDetails(events, dropped, bounds) {
383
450
  `Activity batch exceeds ${bounds.maxBytes} UTF-8 bytes.`
384
451
  );
385
452
  }
386
- return events.map((event) => JSON.stringify(event));
453
+ }
454
+ function validateActivityBatch(input, bounds) {
455
+ validateActivityEvents(input.events, input.dropped, bounds);
456
+ validateActivityAppend({ ...input, ttlMs: bounds.ttlMs, capacity: bounds.capacity });
387
457
  }
388
458
  async function appendActivityEvents(activity, input, bounds) {
459
+ validateActivityBatch(input, bounds);
389
460
  return activity.append({
390
461
  taskId: input.taskId,
391
- details: activityDetails(input.events, input.dropped, bounds),
462
+ sourceEnvelopeId: input.sourceEnvelopeId,
463
+ batchSeq: input.batchSeq,
464
+ events: input.events,
392
465
  dropped: input.dropped,
393
466
  ttlMs: bounds.ttlMs,
394
467
  capacity: bounds.capacity
@@ -441,8 +514,12 @@ function tenantStoresFor(principal, root) {
441
514
  list: () => core.presence.list(tenant)
442
515
  },
443
516
  activity: {
444
- append: (input) => core.activity.append(tenant, input),
445
- read: (taskId) => core.activity.read(tenant, taskId)
517
+ append: (input) => cloud.activity.append(tenant, input),
518
+ read: (taskId) => cloud.activity.read(tenant, taskId)
519
+ },
520
+ approvals: {
521
+ append: (input) => cloud.approvals.append(tenant, input),
522
+ read: (taskId) => cloud.approvals.read(tenant, taskId)
446
523
  },
447
524
  devices: {
448
525
  get: (deviceId) => cloud.devices.get(tenant, deviceId),
@@ -675,6 +752,7 @@ function tokenHandler(deps) {
675
752
  return c.json(response, 200);
676
753
  };
677
754
  }
755
+ var CLOUD_PROTOCOL_CAPABILITIES = ["result-document"];
678
756
  function sleep(ms) {
679
757
  return new Promise((resolve) => setTimeout(resolve, ms));
680
758
  }
@@ -703,15 +781,92 @@ function eventsHandler(deps) {
703
781
  });
704
782
  if (page.messages.length > 0) {
705
783
  const events = page.messages.map((message) => decodeEnvelope(message.body));
706
- const response2 = { events, cursor: page.nextSeq };
784
+ const response2 = {
785
+ events,
786
+ cursor: page.nextSeq,
787
+ capabilities: CLOUD_PROTOCOL_CAPABILITIES
788
+ };
707
789
  return c.json(response2, 200);
708
790
  }
709
791
  if (attempt < attempts - 1) await sleep(deps.longPollIntervalMs);
710
792
  }
711
- const response = { events: [], cursor };
793
+ const response = {
794
+ events: [],
795
+ cursor,
796
+ capabilities: CLOUD_PROTOCOL_CAPABILITIES
797
+ };
712
798
  return c.json(response, 200);
713
799
  };
714
800
  }
801
+ var DEFAULT_APPROVAL_TIMELINE_CAPACITY = 50;
802
+ var DEFAULT_APPROVAL_TIMELINE_TTL_MS = 10 * 60 * 1e3;
803
+ var APPROVAL_SUMMARY_MAX_BYTES = 16 * 1024;
804
+ var NonBlankIdSchema = z.string().max(200).regex(/\S/, "value must not be blank");
805
+ var TaskIdSchema = z.string().min(1).max(200);
806
+ var ApprovalTimelineEventSchema = z.discriminatedUnion("type", [
807
+ z.object({
808
+ type: z.literal("approval_requested"),
809
+ summary: z.string(),
810
+ approvalId: NonBlankIdSchema.optional()
811
+ }),
812
+ z.object({
813
+ type: z.literal("approval_resolved"),
814
+ approvalId: NonBlankIdSchema,
815
+ decision: z.enum(["approve", "reject"]),
816
+ resolvedBy: z.enum(["local"]),
817
+ at: z.iso.datetime({ offset: true })
818
+ })
819
+ ]);
820
+ var ApprovalObservationSchema = z.object({
821
+ taskId: TaskIdSchema,
822
+ sourceEnvelopeId: NonBlankIdSchema,
823
+ revision: z.number().int().positive(),
824
+ receivedAt: z.iso.datetime(),
825
+ event: ApprovalTimelineEventSchema
826
+ });
827
+ function validateApprovalTimelineAppend(input) {
828
+ const capacity = input.capacity ?? DEFAULT_APPROVAL_TIMELINE_CAPACITY;
829
+ const ttlMs = input.ttlMs ?? DEFAULT_APPROVAL_TIMELINE_TTL_MS;
830
+ if (!Number.isSafeInteger(capacity) || capacity <= 0) {
831
+ throw new ByokCloudError(
832
+ "coordination_input_invalid",
833
+ `Approval timeline capacity must be a positive integer, received ${String(capacity)}.`
834
+ );
835
+ }
836
+ if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
837
+ throw new ByokCloudError(
838
+ "coordination_input_invalid",
839
+ `Approval timeline ttl must be a positive number of milliseconds, received ${String(ttlMs)}.`
840
+ );
841
+ }
842
+ const parsed = z.object({
843
+ taskId: TaskIdSchema,
844
+ sourceEnvelopeId: NonBlankIdSchema,
845
+ event: ApprovalTimelineEventSchema
846
+ }).safeParse(input);
847
+ if (!parsed.success) {
848
+ throw new ByokCloudError(
849
+ "coordination_input_invalid",
850
+ "Approval observations require stable source identity and a valid native lifecycle event."
851
+ );
852
+ }
853
+ if (parsed.data.event.type === "approval_requested" && new TextEncoder().encode(parsed.data.event.summary).byteLength > APPROVAL_SUMMARY_MAX_BYTES) {
854
+ throw new ByokCloudError(
855
+ "coordination_input_invalid",
856
+ `Approval summary exceeds ${APPROVAL_SUMMARY_MAX_BYTES} UTF-8 bytes.`
857
+ );
858
+ }
859
+ return { capacity, ttlMs, event: parsed.data.event };
860
+ }
861
+ function approvalTimelineKey(tenant, taskId) {
862
+ return tenantKey(tenant, taskId);
863
+ }
864
+ function parseApprovalObservations(value) {
865
+ return z.array(ApprovalObservationSchema).parse(value);
866
+ }
867
+ function approvalTimelineCursor(entries) {
868
+ return entries.at(-1)?.revision;
869
+ }
715
870
  async function projectTerminalToReview(board, taskId) {
716
871
  const item = await board.get(taskId);
717
872
  if (item === void 0 || item.status !== "in_progress") return;
@@ -740,7 +895,16 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
740
895
  if (attempt?.ownerDeviceId !== void 0 && attempt.ownerDeviceId !== deviceId) return "rejected";
741
896
  if (envelope.type === "task.progress" && envelope.payload.events.length > 0) {
742
897
  try {
743
- activityDetails(envelope.payload.events, 0, activityBounds);
898
+ validateActivityBatch(
899
+ {
900
+ taskId,
901
+ sourceEnvelopeId: envelope.id,
902
+ batchSeq: envelope.payload.seq,
903
+ events: envelope.payload.events,
904
+ dropped: 0
905
+ },
906
+ activityBounds
907
+ );
744
908
  } catch (caught) {
745
909
  if (isCloudError(caught, "activity_batch_too_large") || isCloudError(caught, "coordination_input_invalid")) {
746
910
  return "rejected";
@@ -748,6 +912,15 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
748
912
  throw caught;
749
913
  }
750
914
  }
915
+ const approvalInput = approvalTimelineAppendInput(taskId, envelope);
916
+ if (approvalInput !== void 0) {
917
+ try {
918
+ validateApprovalTimelineAppend(approvalInput);
919
+ } catch (caught) {
920
+ if (isCloudError(caught, "coordination_input_invalid")) return "rejected";
921
+ throw caught;
922
+ }
923
+ }
751
924
  if (await stores.dedup.checkAndRecord(deviceId, envelope.id)) return "duplicate";
752
925
  await applyLifecycle(stores, deviceId, taskId, envelope, activityBounds);
753
926
  return "accepted";
@@ -767,11 +940,23 @@ async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds
767
940
  if (envelope.payload.events.length > 0) {
768
941
  await appendActivityEvents(
769
942
  stores.activity,
770
- { taskId, events: envelope.payload.events, dropped: 0 },
943
+ {
944
+ taskId,
945
+ sourceEnvelopeId: envelope.id,
946
+ batchSeq: envelope.payload.seq,
947
+ events: envelope.payload.events,
948
+ dropped: 0
949
+ },
771
950
  activityBounds
772
951
  );
773
952
  }
774
953
  return;
954
+ case "task.await_approval":
955
+ await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
956
+ return;
957
+ case "task.approval_resolved":
958
+ await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
959
+ return;
775
960
  case "task.complete":
776
961
  await recordTerminal(stores, taskId, envelope, "complete");
777
962
  return;
@@ -785,6 +970,34 @@ async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds
785
970
  return;
786
971
  }
787
972
  }
973
+ function approvalTimelineAppendInput(taskId, envelope) {
974
+ switch (envelope.type) {
975
+ case "task.await_approval":
976
+ return {
977
+ taskId,
978
+ sourceEnvelopeId: envelope.id,
979
+ event: {
980
+ type: "approval_requested",
981
+ summary: envelope.payload.summary,
982
+ ...envelope.payload.approvalId === void 0 ? {} : { approvalId: envelope.payload.approvalId }
983
+ }
984
+ };
985
+ case "task.approval_resolved":
986
+ return {
987
+ taskId,
988
+ sourceEnvelopeId: envelope.id,
989
+ event: {
990
+ type: "approval_resolved",
991
+ approvalId: envelope.payload.approvalId,
992
+ decision: envelope.payload.decision,
993
+ resolvedBy: envelope.payload.resolvedBy,
994
+ at: envelope.payload.at
995
+ }
996
+ };
997
+ default:
998
+ return void 0;
999
+ }
1000
+ }
788
1001
  async function recordTerminal(stores, taskId, envelope, status) {
789
1002
  const { created } = await stores.receipts.record({
790
1003
  key: terminalReceiptKey(taskId),
@@ -1063,11 +1276,6 @@ var PresenceBodySchema = z.object({
1063
1276
  detail: z.string().optional(),
1064
1277
  configuredToolsets: ConfiguredToolsetsSchema.optional()
1065
1278
  });
1066
- var ActivityBodySchema = z.object({
1067
- taskId: z.string().min(1).max(200),
1068
- events: z.array(AgentEventOrUnknownSchema),
1069
- dropped: z.number().int().nonnegative()
1070
- });
1071
1279
  function presencePublishHandler(deps) {
1072
1280
  return async (c) => {
1073
1281
  const authenticated = await authenticateDevice(c, deps);
@@ -1101,7 +1309,7 @@ function activityAppendHandler(deps) {
1101
1309
  return async (c) => {
1102
1310
  const authenticated = await authenticateDevice(c, deps);
1103
1311
  if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
1104
- const parsed = ActivityBodySchema.safeParse(await readJsonBody(c));
1312
+ const parsed = ActivityAppendRequestSchema.safeParse(await readJsonBody(c));
1105
1313
  if (!parsed.success) return c.json({ error: "invalid activity body" }, 400);
1106
1314
  try {
1107
1315
  return c.json(
@@ -1873,6 +2081,9 @@ function createByokCloud(options) {
1873
2081
  },
1874
2082
  readActivity(tenant, taskId) {
1875
2083
  return tenantStoresFor(controlPlane(tenant), root).activity.read(taskId);
2084
+ },
2085
+ readApprovalTimeline(tenant, taskId) {
2086
+ return tenantStoresFor(controlPlane(tenant), root).approvals.read(taskId);
1876
2087
  }
1877
2088
  };
1878
2089
  }
@@ -2287,11 +2498,130 @@ var InMemoryTaskAttemptStore = class {
2287
2498
  }
2288
2499
  };
2289
2500
 
2501
+ // src/stores/in-memory/activity.ts
2502
+ var InMemoryActivityStore = class {
2503
+ constructor(clock) {
2504
+ this.clock = clock;
2505
+ }
2506
+ clock;
2507
+ #tails = /* @__PURE__ */ new Map();
2508
+ async append(tenant, input) {
2509
+ const capacity = validateActivityAppend(input);
2510
+ const now = this.clock.now();
2511
+ const receivedAt = now.toISOString();
2512
+ const key = activityTailKey(tenant, input.taskId);
2513
+ const existing = this.#tails.get(key);
2514
+ const live = existing !== void 0 && receivedAt < existing.expiresAt ? existing : void 0;
2515
+ const incoming = projectTimelineEvents(input, receivedAt);
2516
+ for (const next of incoming) {
2517
+ const collision = live?.entries.find(
2518
+ (entry) => entry.batchSeq === next.batchSeq && entry.eventIndex === next.eventIndex && entry.sourceEnvelopeId !== next.sourceEnvelopeId
2519
+ );
2520
+ if (collision !== void 0) {
2521
+ throw new ByokCloudError(
2522
+ "coordination_input_invalid",
2523
+ `Activity order key (${next.batchSeq}, ${next.eventIndex}) already belongs to another source envelope.`
2524
+ );
2525
+ }
2526
+ }
2527
+ const allEntries = [...live?.entries ?? [], ...incoming].sort(compareTimelineEvents);
2528
+ const evicted = Math.max(allEntries.length - capacity, 0);
2529
+ const entries = allEntries.slice(evicted);
2530
+ const cursor = activityCursor(entries);
2531
+ const tail = {
2532
+ tenantId: tenant,
2533
+ taskId: input.taskId,
2534
+ entries,
2535
+ ...cursor === void 0 ? {} : { cursor },
2536
+ dropped: (live?.dropped ?? 0) + input.dropped + evicted,
2537
+ capacity,
2538
+ expiresAt: new Date(now.getTime() + input.ttlMs).toISOString()
2539
+ };
2540
+ this.#tails.set(key, tail);
2541
+ return tail;
2542
+ }
2543
+ async read(tenant, taskId) {
2544
+ const key = activityTailKey(tenant, taskId);
2545
+ const tail = this.#tails.get(key);
2546
+ if (tail === void 0) return void 0;
2547
+ if (this.clock.now().toISOString() >= tail.expiresAt) {
2548
+ this.#tails.delete(key);
2549
+ return void 0;
2550
+ }
2551
+ return tail;
2552
+ }
2553
+ };
2554
+
2555
+ // src/stores/in-memory/approval-timeline.ts
2556
+ var InMemoryApprovalTimelineStore = class {
2557
+ constructor(clock) {
2558
+ this.clock = clock;
2559
+ }
2560
+ clock;
2561
+ #tails = /* @__PURE__ */ new Map();
2562
+ async append(tenant, input) {
2563
+ const { capacity, ttlMs, event } = validateApprovalTimelineAppend(input);
2564
+ const now = this.clock.now();
2565
+ const receivedAt = now.toISOString();
2566
+ const key = approvalTimelineKey(tenant, input.taskId);
2567
+ const existing = this.#tails.get(key);
2568
+ const live = existing !== void 0 && receivedAt < existing.expiresAt ? existing : void 0;
2569
+ if (live !== void 0) {
2570
+ const duplicate = live.entries.find(
2571
+ (entry) => entry.sourceEnvelopeId === input.sourceEnvelopeId
2572
+ );
2573
+ if (duplicate !== void 0) {
2574
+ if (JSON.stringify(duplicate.event) !== JSON.stringify(event)) {
2575
+ throw new ByokCloudError(
2576
+ "coordination_input_invalid",
2577
+ "Approval source envelope identity already belongs to another lifecycle event."
2578
+ );
2579
+ }
2580
+ return live;
2581
+ }
2582
+ }
2583
+ const revision = (live?.cursor ?? 0) + 1;
2584
+ const observation = ApprovalObservationSchema.parse({
2585
+ taskId: input.taskId,
2586
+ sourceEnvelopeId: input.sourceEnvelopeId,
2587
+ revision,
2588
+ receivedAt,
2589
+ event
2590
+ });
2591
+ const allEntries = [...live?.entries ?? [], observation];
2592
+ const evicted = Math.max(allEntries.length - capacity, 0);
2593
+ const entries = allEntries.slice(evicted);
2594
+ const tail = {
2595
+ tenantId: tenant,
2596
+ taskId: input.taskId,
2597
+ entries,
2598
+ cursor: approvalTimelineCursor(entries),
2599
+ dropped: (live?.dropped ?? 0) + evicted,
2600
+ capacity,
2601
+ expiresAt: new Date(now.getTime() + ttlMs).toISOString()
2602
+ };
2603
+ this.#tails.set(key, tail);
2604
+ return tail;
2605
+ }
2606
+ async read(tenant, taskId) {
2607
+ const key = approvalTimelineKey(tenant, taskId);
2608
+ const tail = this.#tails.get(key);
2609
+ if (tail === void 0) return void 0;
2610
+ if (this.clock.now().toISOString() >= tail.expiresAt) {
2611
+ this.#tails.delete(key);
2612
+ return void 0;
2613
+ }
2614
+ return tail;
2615
+ }
2616
+ };
2617
+
2290
2618
  // src/stores/in-memory/index.ts
2291
2619
  function createInMemoryCloudStores(clock, crypto, objects) {
2292
2620
  const blobs = createInMemoryBlobs(clock, crypto, objects);
2293
2621
  return {
2294
2622
  stores: {
2623
+ activity: new InMemoryActivityStore(clock),
2624
+ approvals: new InMemoryApprovalTimelineStore(clock),
2295
2625
  devices: new InMemoryDeviceDirectory(),
2296
2626
  pairingCodes: new InMemoryPairingCodeStore(clock),
2297
2627
  nonces: new InMemoryNonceStore(clock, crypto),
@@ -2500,6 +2830,8 @@ var TASK_ATTEMPT_STATUSES = [
2500
2830
  "cancelled"
2501
2831
  ];
2502
2832
  var CLOUD_STORE_NAMES = [
2833
+ "activity",
2834
+ "approvals",
2503
2835
  "devices",
2504
2836
  "pairingCodes",
2505
2837
  "nonces",
@@ -2513,6 +2845,8 @@ var CLOUD_STORE_NAMES = [
2513
2845
 
2514
2846
  // src/stores/ports-contract.ts
2515
2847
  var CLOUD_PORT_METHODS = {
2848
+ activity: ["append", "read"],
2849
+ approvals: ["append", "read"],
2516
2850
  devices: ["register", "get", "revoke", "list", "resolveByDeviceId"],
2517
2851
  pairingCodes: ["issue", "redeem"],
2518
2852
  nonces: ["issue", "validate", "markUsed"],
@@ -2527,6 +2861,8 @@ var CLOUD_PORT_METHODS = {
2527
2861
  rateLimiter: ["consume"]
2528
2862
  };
2529
2863
  var CLOUD_PORT_INTERFACES = {
2864
+ activity: "ActivityStore",
2865
+ approvals: "ApprovalTimelineStore",
2530
2866
  devices: "DeviceDirectory",
2531
2867
  pairingCodes: "PairingCodeStore",
2532
2868
  nonces: "NonceStore",
@@ -2538,6 +2874,6 @@ var CLOUD_PORT_INTERFACES = {
2538
2874
  rateLimiter: "InboundRateLimiter"
2539
2875
  };
2540
2876
 
2541
- 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, DEFAULT_SKILL_PACK_PAGE_LIMIT, DEVICE_IDENTITY_PROOF_KEY_EPOCH, DEVICE_IDENTITY_PROOF_KEY_ID, InMemoryBlobContentProxy, InMemoryCloudBlobStore, InMemoryDeviceDirectory, InMemoryInboundDedupStore, InMemoryNonceStore, InMemoryPairingCodeStore, InMemoryProofRequestReceiptStore, InMemoryRequestReceiptStore, InMemoryTaskAttemptStore, MAX_DEVICE_PROOF_CLOCK_SKEW_MS, MAX_DEVICE_PROOF_HEADER_BYTES, MAX_DEVICE_PROOF_MAX_LIFETIME_MS, 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, projectTerminalResult, routeKey, tenantStoresFor, terminalReceiptKey, truthManifestMetadata, truthRecordMetadata, verifyNonceSignature };
2877
+ export { ACCESS_TOKEN_TTL_SECONDS, APPROVAL_SUMMARY_MAX_BYTES, ActivityAppendRequestSchema, AllowAllRateLimiter, ApprovalObservationSchema, ApprovalTimelineEventSchema, 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_APPROVAL_TIMELINE_CAPACITY, DEFAULT_APPROVAL_TIMELINE_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, DEFAULT_SKILL_PACK_PAGE_LIMIT, DEVICE_IDENTITY_PROOF_KEY_EPOCH, DEVICE_IDENTITY_PROOF_KEY_ID, InMemoryActivityStore, InMemoryBlobContentProxy, InMemoryCloudBlobStore, InMemoryDeviceDirectory, InMemoryInboundDedupStore, InMemoryNonceStore, InMemoryPairingCodeStore, InMemoryProofRequestReceiptStore, InMemoryRequestReceiptStore, InMemoryTaskAttemptStore, MAX_DEVICE_PROOF_CLOCK_SKEW_MS, MAX_DEVICE_PROOF_HEADER_BYTES, MAX_DEVICE_PROOF_MAX_LIFETIME_MS, 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, TimelineEventSchema, TruthBodyInputSchema, TruthCommitError, TruthCommitResponseSchema, TruthRecordKeySchema, TruthRecordMetadataSchema, TruthWriteRequestSchema, activityCursor, approvalTimelineCursor, authenticateBearer, authenticateDeviceProof, createAuthPlane, createByokCloud, createHmacTokenSigner, createInMemoryBlobs, createInMemoryByokCloud, createInMemoryCloudStores, createWebCrypto, declares, extractBearerToken, fullCapabilityDeclaration, handleInboundEnvelope, isCloudError, isTruthCommitError, parseApprovalObservations, parseTimelineEvents, projectTerminalResult, projectTimelineEvents, routeKey, tenantStoresFor, terminalReceiptKey, truthManifestMetadata, truthRecordMetadata, validateActivityAppend, validateApprovalTimelineAppend, verifyNonceSignature };
2542
2878
  //# sourceMappingURL=index.js.map
2543
2879
  //# sourceMappingURL=index.js.map