@axiom-lattice/protocols 4.1.1 → 4.1.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.
@@ -0,0 +1,119 @@
1
+ import { snapshotExactRecord } from "./ExactDataSnapshot";
2
+
3
+ /** Queue execution behavior available to privileged host dispatchers. */
4
+ export type QueuedExecutionMode = "followup";
5
+
6
+ /** Trusted Project Room identity persisted with a privileged queue message. */
7
+ export interface ProjectRoomTrustedRunContext {
8
+ tenantId: string;
9
+ workspaceId: string;
10
+ projectId: string;
11
+ roomId: string;
12
+ sourceRoomMessageId: string;
13
+ membershipId: string;
14
+ assistantId: string;
15
+ inputMessageId: string;
16
+ role: "coordinator" | "specialist";
17
+ title: string;
18
+ responsibility?: string;
19
+ }
20
+
21
+ /** Trusted Project Task identity persisted with a privileged queue message. */
22
+ export interface ProjectTaskTrustedRunContext {
23
+ tenantId: string;
24
+ workspaceId: string;
25
+ projectId: string;
26
+ roomId: string;
27
+ membershipId: string;
28
+ assistantId: string;
29
+ taskId: string;
30
+ threadId: string;
31
+ inputMessageId: string;
32
+ }
33
+
34
+ /** Host-authenticated metadata that cannot be supplied through public Agent APIs. */
35
+ export interface TrustedRunContext {
36
+ projectRoom?: ProjectRoomTrustedRunContext;
37
+ projectTask?: ProjectTaskTrustedRunContext;
38
+ }
39
+
40
+ /**
41
+ * Strictly validates and clones host-authenticated queue context read from durable storage.
42
+ *
43
+ * @param value - Untrusted decoded database value.
44
+ * @returns A validated defensive clone of the trusted run context.
45
+ * @throws Error when the stored value does not exactly match the trusted context contract.
46
+ */
47
+ export function parseTrustedRunContext(value: unknown): TrustedRunContext {
48
+ const contextValues = snapshotExactRecord(value, [], ["projectRoom", "projectTask"]);
49
+ if (!contextValues || Object.keys(contextValues).length !== 1) {
50
+ throw new Error("Invalid trusted agent run context");
51
+ }
52
+ if (Object.prototype.hasOwnProperty.call(contextValues, "projectTask")) {
53
+ const requiredKeys = [
54
+ "tenantId", "workspaceId", "projectId", "roomId", "membershipId",
55
+ "assistantId", "taskId", "threadId", "inputMessageId",
56
+ ] as const;
57
+ const projectTaskValues = snapshotExactRecord(contextValues.projectTask, requiredKeys);
58
+ if (!projectTaskValues
59
+ || requiredKeys.some((key) => typeof projectTaskValues[key] !== "string" || projectTaskValues[key].length === 0)) {
60
+ throw new Error("Invalid trusted agent run context");
61
+ }
62
+ return {
63
+ projectTask: {
64
+ tenantId: projectTaskValues.tenantId as string,
65
+ workspaceId: projectTaskValues.workspaceId as string,
66
+ projectId: projectTaskValues.projectId as string,
67
+ roomId: projectTaskValues.roomId as string,
68
+ membershipId: projectTaskValues.membershipId as string,
69
+ assistantId: projectTaskValues.assistantId as string,
70
+ taskId: projectTaskValues.taskId as string,
71
+ threadId: projectTaskValues.threadId as string,
72
+ inputMessageId: projectTaskValues.inputMessageId as string,
73
+ },
74
+ };
75
+ }
76
+ const projectRoomValue = contextValues?.projectRoom;
77
+ const requiredKeys = [
78
+ "tenantId", "workspaceId", "projectId", "roomId", "sourceRoomMessageId", "membershipId",
79
+ "assistantId", "inputMessageId", "role", "title",
80
+ ] as const;
81
+ const projectRoomValues = snapshotExactRecord(projectRoomValue, requiredKeys, ["responsibility"]);
82
+ const hasResponsibility = projectRoomValues !== undefined
83
+ && Object.prototype.hasOwnProperty.call(projectRoomValues, "responsibility");
84
+ if (!projectRoomValues
85
+ || requiredKeys.some((key) => typeof projectRoomValues[key] !== "string" || projectRoomValues[key].length === 0)
86
+ || (hasResponsibility && projectRoomValues.responsibility !== undefined
87
+ && (typeof projectRoomValues.responsibility !== "string" || projectRoomValues.responsibility.length === 0))
88
+ || (projectRoomValues.role !== "coordinator" && projectRoomValues.role !== "specialist")) {
89
+ throw new Error("Invalid trusted agent run context");
90
+ }
91
+ const parsedProjectRoom: ProjectRoomTrustedRunContext = {
92
+ tenantId: projectRoomValues.tenantId as string,
93
+ workspaceId: projectRoomValues.workspaceId as string,
94
+ projectId: projectRoomValues.projectId as string,
95
+ roomId: projectRoomValues.roomId as string,
96
+ sourceRoomMessageId: projectRoomValues.sourceRoomMessageId as string,
97
+ membershipId: projectRoomValues.membershipId as string,
98
+ assistantId: projectRoomValues.assistantId as string,
99
+ inputMessageId: projectRoomValues.inputMessageId as string,
100
+ role: projectRoomValues.role,
101
+ title: projectRoomValues.title as string,
102
+ };
103
+ if (hasResponsibility && typeof projectRoomValues.responsibility === "string") {
104
+ parsedProjectRoom.responsibility = projectRoomValues.responsibility;
105
+ }
106
+ return { projectRoom: parsedProjectRoom };
107
+ }
108
+
109
+ /**
110
+ * Strictly validates a queued execution mode read from durable storage.
111
+ *
112
+ * @param value - Untrusted database value.
113
+ * @returns The validated execution mode.
114
+ * @throws Error when the stored value is not supported.
115
+ */
116
+ export function parseQueuedExecutionMode(value: unknown): QueuedExecutionMode {
117
+ if (value !== "followup") throw new Error("Invalid queued execution mode");
118
+ return value;
119
+ }
@@ -0,0 +1,36 @@
1
+ import type { BindingMutablePatch, BindingRegistry, CreateBindingInput } from "../BindingProtocol";
2
+
3
+ describe("BindingProtocol mutation contract", () => {
4
+ it("accepts mutable fields and rejects identity fields at compile time", () => {
5
+ const mutable: BindingMutablePatch = {
6
+ agentId: "agent-2", threadId: "thread-2", workspaceId: "workspace-2",
7
+ projectId: "project-2", threadMode: "fixed", senderDisplayName: "Sender",
8
+ senderMetadata: { source: "test" }, enabled: false,
9
+ };
10
+ expect(mutable.enabled).toBe(false);
11
+
12
+ // @ts-expect-error channel is immutable after binding creation
13
+ const invalid: BindingMutablePatch = { channel: "room" };
14
+ expect(invalid).toBeDefined();
15
+ });
16
+
17
+ it("requires tenant scope for mutation methods", () => {
18
+ const update: BindingRegistry["update"] = async (...args) => {
19
+ expect(args).toHaveLength(3);
20
+ throw new Error("not invoked");
21
+ };
22
+ const remove: BindingRegistry["delete"] = async (...args) => {
23
+ expect(args).toHaveLength(2);
24
+ };
25
+ expect(update).toBeDefined();
26
+ expect(remove).toBeDefined();
27
+ });
28
+
29
+ it("allows trusted callers to choose the initial enabled state", () => {
30
+ const input: CreateBindingInput = {
31
+ channel: "room", channelInstallationId: "room-internal:tenant-a", tenantId: "tenant-a",
32
+ senderId: "room:room-1:membership-1", agentId: "agent-1", enabled: false,
33
+ };
34
+ expect(input.enabled).toBe(false);
35
+ });
36
+ });
@@ -0,0 +1,105 @@
1
+ import { runInNewContext } from "node:vm";
2
+ import { snapshotExactArray, snapshotExactRecord } from "../ExactDataSnapshot";
3
+
4
+ describe("exact data snapshots", () => {
5
+ it("canonicalizes cross-realm and hostile-prototype records without reading their prototype", () => {
6
+ const crossRealm = runInNewContext("({ id: 'one', value: 2 })");
7
+ let inheritedReads = 0;
8
+ const prototype = Object.create(null);
9
+ Object.defineProperty(prototype, "constructor", {
10
+ get: () => { inheritedReads += 1; throw new Error("must not run"); },
11
+ });
12
+ Object.defineProperty(prototype, "inherited", {
13
+ get: () => { inheritedReads += 1; throw new Error("must not run"); },
14
+ });
15
+ const hostilePrototype = Object.assign(Object.create(prototype), { id: "two", value: 3 });
16
+
17
+ expect(snapshotExactRecord(crossRealm, ["id", "value"])).toEqual({ id: "one", value: 2 });
18
+ expect(snapshotExactRecord(hostilePrototype, ["id", "value"])).toEqual({ id: "two", value: 3 });
19
+ expect(inheritedReads).toBe(0);
20
+ });
21
+
22
+ it.each([
23
+ ["accessor", Object.defineProperty({ id: "one" }, "value", { enumerable: true, get: () => 2 })],
24
+ ["extra", { id: "one", value: 2, extra: true }],
25
+ ["non-enumerable", Object.defineProperty({ id: "one" }, "value", { enumerable: false, value: 2 })],
26
+ ["symbol", Object.assign({ id: "one", value: 2 }, { [Symbol("extra")]: true })],
27
+ ])("rejects an own %s record property", (_name, value) => {
28
+ expect(snapshotExactRecord(value, ["id", "value"])).toBeUndefined();
29
+ });
30
+
31
+ it("canonicalizes cross-realm arrays and rejects non-dense or accessor-bearing arrays", () => {
32
+ expect(snapshotExactArray(runInNewContext("['one', 'two']"))).toEqual(["one", "two"]);
33
+ const accessor = ["one"];
34
+ Object.defineProperty(accessor, "0", { enumerable: true, get: () => "one" });
35
+ expect(snapshotExactArray(accessor)).toBeUndefined();
36
+ expect(snapshotExactArray(Object.assign(["one"], { extra: true }))).toBeUndefined();
37
+ expect(snapshotExactArray(new Array(1))).toBeUndefined();
38
+ });
39
+
40
+ it("rejects record accessors without reading polluted descriptor prototypes", () => {
41
+ const originalValue = Object.getOwnPropertyDescriptor(Object.prototype, "value");
42
+ const originalEnumerable = Object.getOwnPropertyDescriptor(Object.prototype, "enumerable");
43
+ let inheritedReads = 0;
44
+ const accessor = Object.defineProperty({ id: "one" }, "value", {
45
+ enumerable: true,
46
+ get: () => 2,
47
+ });
48
+ try {
49
+ Object.defineProperty(Object.prototype, "value", {
50
+ configurable: true,
51
+ get: () => {
52
+ inheritedReads += 1;
53
+ return "forged";
54
+ },
55
+ });
56
+ Object.defineProperty(Object.prototype, "enumerable", Object.assign(Object.create(null), {
57
+ configurable: true,
58
+ get: () => {
59
+ inheritedReads += 1;
60
+ return true;
61
+ },
62
+ }));
63
+
64
+ expect(snapshotExactRecord(accessor, ["id", "value"])).toBeUndefined();
65
+ expect(inheritedReads).toBe(0);
66
+ } finally {
67
+ if (originalValue) Object.defineProperty(Object.prototype, "value", originalValue);
68
+ else delete (Object.prototype as { value?: unknown }).value;
69
+ if (originalEnumerable) Object.defineProperty(Object.prototype, "enumerable", originalEnumerable);
70
+ else delete (Object.prototype as { enumerable?: unknown }).enumerable;
71
+ }
72
+ });
73
+
74
+ it("rejects array accessors without reading polluted descriptor prototypes", () => {
75
+ const originalValue = Object.getOwnPropertyDescriptor(Object.prototype, "value");
76
+ const originalEnumerable = Object.getOwnPropertyDescriptor(Object.prototype, "enumerable");
77
+ let inheritedReads = 0;
78
+ const accessor = ["one"];
79
+ Object.defineProperty(accessor, "0", { enumerable: true, get: () => "one" });
80
+ try {
81
+ Object.defineProperty(Object.prototype, "value", {
82
+ configurable: true,
83
+ get: () => {
84
+ inheritedReads += 1;
85
+ return "forged";
86
+ },
87
+ });
88
+ Object.defineProperty(Object.prototype, "enumerable", Object.assign(Object.create(null), {
89
+ configurable: true,
90
+ get: () => {
91
+ inheritedReads += 1;
92
+ return true;
93
+ },
94
+ }));
95
+
96
+ expect(snapshotExactArray(accessor)).toBeUndefined();
97
+ expect(inheritedReads).toBe(0);
98
+ } finally {
99
+ if (originalValue) Object.defineProperty(Object.prototype, "value", originalValue);
100
+ else delete (Object.prototype as { value?: unknown }).value;
101
+ if (originalEnumerable) Object.defineProperty(Object.prototype, "enumerable", originalEnumerable);
102
+ else delete (Object.prototype as { enumerable?: unknown }).enumerable;
103
+ }
104
+ });
105
+ });
@@ -0,0 +1,48 @@
1
+ import type {
2
+ ProjectBotMembership,
3
+ ProjectRoomMessage,
4
+ ProjectRoomMessageCursor,
5
+ } from "../ProjectRoomProtocol";
6
+
7
+ describe("Project Room protocol types", () => {
8
+ it("models bot membership, system messages, and message cursors", () => {
9
+ const bot: ProjectBotMembership = {
10
+ id: "bm-1",
11
+ tenantId: "t-1",
12
+ workspaceId: "w-1",
13
+ projectId: "p-1",
14
+ roomId: "r-1",
15
+ assistantId: "a-1",
16
+ role: "coordinator",
17
+ title: "Lead",
18
+ mentionName: "lead",
19
+ status: "paused",
20
+ roomThreadId: "th-1",
21
+ joinedAt: new Date(0),
22
+ updatedAt: new Date(0),
23
+ };
24
+ const message: ProjectRoomMessage = {
25
+ id: "m-1",
26
+ tenantId: "t-1",
27
+ workspaceId: "w-1",
28
+ projectId: "p-1",
29
+ roomId: "r-1",
30
+ author: { type: "system" },
31
+ content: { type: "text", text: "Room ready" },
32
+ mentions: [],
33
+ source: "system",
34
+ idempotencyKey: "room-ready:p-1",
35
+ createdAt: new Date(0),
36
+ };
37
+ const cursor: ProjectRoomMessageCursor = {
38
+ createdAt: message.createdAt,
39
+ id: message.id,
40
+ };
41
+
42
+ expect([bot.status, message.source, cursor.id]).toEqual([
43
+ "paused",
44
+ "system",
45
+ "m-1",
46
+ ]);
47
+ });
48
+ });
@@ -0,0 +1,185 @@
1
+ import type {
2
+ ProjectRoomBrokerCapacityError,
3
+ ProjectRoomBusinessEvent,
4
+ ProjectRoomBusinessEventDraft,
5
+ ProjectRoomControlEvent,
6
+ ProjectRoomEventScope,
7
+ ProjectRoomEventSubscription,
8
+ ProjectRoomRealtimeActor,
9
+ } from "../ProjectRoomRealtimeProtocol";
10
+ import { runInNewContext } from "node:vm";
11
+ import {
12
+ isProjectRoomEventId,
13
+ parseProjectRoomEventId,
14
+ ProjectRoomCursorError,
15
+ toProjectRoomPublicBotMembership,
16
+ toProjectRoomPublicMembership,
17
+ toProjectRoomPublicMessage,
18
+ } from "../ProjectRoomRealtimeProtocol";
19
+
20
+ const message: ProjectRoomBusinessEvent = {
21
+ id: "550e8400-e29b-41d4-a716-446655440000:1",
22
+ type: "message.created",
23
+ occurredAt: "2026-09-03T00:00:00.000Z",
24
+ data: {
25
+ message: {
26
+ id: "message-1",
27
+ roomId: "room-1",
28
+ author: { type: "bot", membershipId: "bot-1" },
29
+ content: { type: "text", text: "Complete" },
30
+ mentions: [],
31
+ source: "agent",
32
+ createdAt: "2026-09-03T00:00:00.000Z",
33
+ },
34
+ },
35
+ };
36
+
37
+ const draft: ProjectRoomBusinessEventDraft = {
38
+ type: "task.changed",
39
+ occurredAt: "2026-09-03T00:00:01.000Z",
40
+ data: {
41
+ taskId: "task-1",
42
+ status: "completed",
43
+ ownerMembershipId: "bot-1",
44
+ updatedAt: "2026-09-03T00:00:01.000Z",
45
+ },
46
+ };
47
+
48
+ const control: ProjectRoomControlEvent = {
49
+ type: "resync",
50
+ data: { reason: "SERVER_RESTART" },
51
+ };
52
+ const scope: ProjectRoomEventScope = { tenantId: "tenant-1", roomId: "room-1", projectId: "project-1" };
53
+ const actor: ProjectRoomRealtimeActor = {
54
+ tenantId: "tenant-1",
55
+ userId: "user-1",
56
+ projectId: "project-1",
57
+ tokenExpiresAt: 1_788_393_600_000,
58
+ };
59
+ const subscription: ProjectRoomEventSubscription = {
60
+ replay: [message],
61
+ headEventId: message.id,
62
+ unsubscribe: () => undefined,
63
+ };
64
+
65
+ // @ts-expect-error unsupported business event types are not public protocol values
66
+ const dispatchEvent: ProjectRoomBusinessEventDraft = { type: "dispatch.changed", data: {} };
67
+ // @ts-expect-error control events never carry replay IDs
68
+ const identifiedControl: ProjectRoomControlEvent = { ...control, id: "epoch:1" };
69
+ const internalMessage: ProjectRoomBusinessEvent = {
70
+ ...message,
71
+ data: {
72
+ // @ts-expect-error internal assistant identity is not public message data
73
+ message: { ...message.data.message, assistantId: "assistant-1" },
74
+ },
75
+ };
76
+ // @ts-expect-error event IDs are canonical strings, not numbers
77
+ const numericEventId: ProjectRoomBusinessEvent = { ...message, id: 1 };
78
+
79
+ describe("Project Room realtime protocol", () => {
80
+ it("defines the exact public business, control, broker, and actor surfaces", () => {
81
+ expect([message.type, draft.type, control.type]).toEqual([
82
+ "message.created",
83
+ "task.changed",
84
+ "resync",
85
+ ]);
86
+ expect(JSON.stringify(message)).not.toContain("assistantId");
87
+ expect(JSON.stringify(message)).not.toContain("sourceId");
88
+ expect(scope).toEqual({ tenantId: "tenant-1", roomId: "room-1", projectId: "project-1" });
89
+ expect(actor).toEqual({
90
+ tenantId: "tenant-1",
91
+ userId: "user-1",
92
+ projectId: "project-1",
93
+ tokenExpiresAt: 1_788_393_600_000,
94
+ });
95
+ expect(subscription.headEventId).toBe(message.id);
96
+ expect(dispatchEvent).toBeDefined();
97
+ expect(identifiedControl).toBeDefined();
98
+ expect(internalMessage).toBeDefined();
99
+ expect(numericEventId).toBeDefined();
100
+ });
101
+
102
+ it("maps descriptor-safe internal records to public DTOs", () => {
103
+ const internalMessage = {
104
+ id: "message-1", tenantId: "tenant-1", workspaceId: "workspace-1", projectId: "project-1",
105
+ roomId: "room-1", author: { type: "bot", membershipId: "bot-1", assistantId: "assistant-1" },
106
+ content: { type: "text", text: "Complete" }, mentions: [], source: "agent", sourceId: "reply-1",
107
+ idempotencyKey: "idem-1", createdAt: new Date("2026-09-03T00:00:00.000Z"),
108
+ };
109
+ const internalMembership = {
110
+ id: "membership-1", tenantId: "tenant-1", projectId: "project-1", userId: "user-1",
111
+ role: "viewer", status: "active", joinedAt: new Date("2026-09-03T00:00:00.000Z"),
112
+ updatedAt: new Date("2026-09-03T00:00:01.000Z"),
113
+ };
114
+ const internalBot = {
115
+ id: "bot-1", tenantId: "tenant-1", workspaceId: "workspace-1", projectId: "project-1", roomId: "room-1",
116
+ assistantId: "assistant-1", role: "specialist", title: "Worker", responsibility: "Build",
117
+ mentionName: "worker", status: "active", roomThreadId: "thread-1",
118
+ joinedAt: new Date("2026-09-03T00:00:00.000Z"), updatedAt: new Date("2026-09-03T00:00:01.000Z"),
119
+ };
120
+ expect(toProjectRoomPublicMessage(internalMessage)).toEqual(message.data.message);
121
+ expect(toProjectRoomPublicMembership(internalMembership)).toEqual({
122
+ id: "membership-1", userId: "user-1", role: "viewer", status: "active",
123
+ joinedAt: "2026-09-03T00:00:00.000Z", updatedAt: "2026-09-03T00:00:01.000Z",
124
+ });
125
+ expect(toProjectRoomPublicBotMembership(internalBot)).toEqual({
126
+ id: "bot-1", role: "specialist", title: "Worker", responsibility: "Build", mentionName: "worker",
127
+ status: "active", joinedAt: "2026-09-03T00:00:00.000Z", updatedAt: "2026-09-03T00:00:01.000Z",
128
+ });
129
+ expect(JSON.stringify(toProjectRoomPublicMessage(internalMessage))).not.toMatch(
130
+ /tenantId|workspaceId|projectId|assistantId|thread|idempotency|sourceId/,
131
+ );
132
+ });
133
+
134
+ it("rejects malformed, accessor-backed, and extra internal records", () => {
135
+ expect(toProjectRoomPublicMessage({})).toBeUndefined();
136
+ expect(toProjectRoomPublicMembership({ id: "membership-1", extra: true })).toBeUndefined();
137
+ expect(toProjectRoomPublicBotMembership({})).toBeUndefined();
138
+ const accessor = { get id() { return "message-1"; } };
139
+ expect(toProjectRoomPublicMessage(accessor)).toBeUndefined();
140
+ });
141
+
142
+ it("reconstructs strict mention DTOs and rejects sensitive mention fields", () => {
143
+ const value = {
144
+ id: "message-1", tenantId: "tenant-1", workspaceId: "workspace-1", projectId: "project-1", roomId: "room-1",
145
+ author: { type: "human", userId: "user-1" }, content: { type: "text", text: "hello" }, source: "user", createdAt: new Date("2026-09-03T00:00:00.000Z"),
146
+ mentions: [{ type: "team" }, { type: "bot", membershipId: "bot-1" }],
147
+ };
148
+ const result = toProjectRoomPublicMessage(value);
149
+ expect(result?.mentions).toEqual([{ type: "team" }, { type: "bot", membershipId: "bot-1" }]);
150
+ expect(result?.mentions[0]).not.toBe(value.mentions[0]);
151
+ expect(toProjectRoomPublicMessage({ ...value, mentions: [{ type: "team", tenantId: "secret" }] })).toBeUndefined();
152
+ let reads = 0;
153
+ const accessor = Object.defineProperty({ type: "team" }, "type", { enumerable: true, get: () => { reads += 1; return "team"; } });
154
+ expect(toProjectRoomPublicMessage({ ...value, mentions: [accessor] })).toBeUndefined();
155
+ expect(reads).toBe(0);
156
+ });
157
+
158
+ it("parses only canonical safe-integer event IDs", () => {
159
+ expect(parseProjectRoomEventId(message.id)).toEqual({
160
+ epoch: "550e8400-e29b-41d4-a716-446655440000",
161
+ sequence: 1,
162
+ });
163
+ expect(isProjectRoomEventId(message.id)).toBe(true);
164
+ expect(isProjectRoomEventId("epoch:1")).toBe(false);
165
+ expect(isProjectRoomEventId("550E8400-E29B-41D4-A716-446655440000:1")).toBe(false);
166
+ expect(isProjectRoomEventId(`${message.id}x`)).toBe(false);
167
+ expect(isProjectRoomEventId(`${message.id.split(":")[0]}:1.0`)).toBe(false);
168
+ expect(isProjectRoomEventId(`${message.id.split(":")[0]}:${Number.MAX_SAFE_INTEGER + 1}`)).toBe(false);
169
+ expect(parseProjectRoomEventId("not-an-event-id")).toBeUndefined();
170
+ expect(isProjectRoomEventId(runInNewContext(`"${message.id}"`))).toBe(true);
171
+ expect(isProjectRoomEventId(runInNewContext(`new String("${message.id}")`))).toBe(false);
172
+ });
173
+
174
+ it("exposes typed cursor and capacity errors", () => {
175
+ const cursorError = new ProjectRoomCursorError("CURSOR_EXPIRED");
176
+ const capacityError: ProjectRoomBrokerCapacityError = {
177
+ name: "ProjectRoomBrokerCapacityError",
178
+ message: "sequence exhausted",
179
+ code: "PROJECT_ROOM_EVENT_SEQUENCE_EXHAUSTED",
180
+ };
181
+ expect(cursorError).toBeInstanceOf(Error);
182
+ expect(cursorError.code).toBe("CURSOR_EXPIRED");
183
+ expect(capacityError.code).toBe("PROJECT_ROOM_EVENT_SEQUENCE_EXHAUSTED");
184
+ });
185
+ });