@axiom-lattice/protocols 4.1.0 → 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.
Files changed (39) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +12 -0
  3. package/dist/index.d.mts +1021 -26
  4. package/dist/index.d.ts +1021 -26
  5. package/dist/index.js +448 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +420 -1
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +3 -2
  10. package/src/BindingProtocol.ts +87 -11
  11. package/src/CapabilityBundleStoreProtocol.ts +78 -0
  12. package/src/CapabilityRuntimeProtocol.ts +82 -0
  13. package/src/ChannelInstallationStoreProtocol.ts +23 -4
  14. package/src/ExactDataSnapshot.ts +119 -0
  15. package/src/PluginProtocol.ts +59 -7
  16. package/src/ProjectBotMembershipStoreProtocol.ts +48 -0
  17. package/src/ProjectMembershipStoreProtocol.ts +58 -0
  18. package/src/ProjectRoomMessageStoreProtocol.ts +26 -0
  19. package/src/ProjectRoomProtocol.ts +143 -0
  20. package/src/ProjectRoomRealtimeProtocol.ts +349 -0
  21. package/src/ProjectRoomStoreProtocol.ts +16 -0
  22. package/src/SkillStoreProtocol.ts +30 -0
  23. package/src/TaskBeliefProtocol.ts +6 -1
  24. package/src/TaskStoreProtocol.ts +66 -2
  25. package/src/TaskWorkItemProtocol.ts +138 -0
  26. package/src/TrustedRunContextProtocol.ts +119 -0
  27. package/src/WorkspaceStoreProtocol.ts +33 -0
  28. package/src/__tests__/BindingProtocol.test.ts +36 -0
  29. package/src/__tests__/ExactDataSnapshot.test.ts +105 -0
  30. package/src/__tests__/ProjectRoomProtocol.test.ts +48 -0
  31. package/src/__tests__/ProjectRoomRealtimeProtocol.test.ts +185 -0
  32. package/src/__tests__/ProjectRoomStores.test.ts +363 -0
  33. package/src/__tests__/ProjectTaskProtocol.test.ts +29 -0
  34. package/src/__tests__/TaskWorkItemProtocol.test.ts +111 -0
  35. package/src/__tests__/TrustedRunContextProtocol.test.ts +265 -0
  36. package/src/__tests__/capability-bundle-types.test.ts +177 -0
  37. package/src/index.ts +13 -0
  38. package/tsconfig.type-tests.json +9 -0
  39. package/type-tests/task-work-item-store-compatibility.ts +37 -0
@@ -0,0 +1,58 @@
1
+ import type {
2
+ ProjectHumanRole,
3
+ ProjectMembership,
4
+ } from "./ProjectRoomProtocol";
5
+
6
+ /** Result of an atomic human membership mutation, including its committed row. */
7
+ export type ProjectMembershipMutationResult =
8
+ | { kind: "updated" | "removed"; membership: ProjectMembership }
9
+ | { kind: "not_found" | "conflict" | "last_owner" };
10
+
11
+ /** Persistence operations for human membership in a project. */
12
+ export interface ProjectMembershipStore {
13
+ /** Lists memberships belonging to a project. */
14
+ list(tenantId: string, projectId: string): Promise<ProjectMembership[]>;
15
+
16
+ /** Finds a user's membership in a project, if one exists. */
17
+ findByUser(tenantId: string, projectId: string, userId: string): Promise<ProjectMembership | null>;
18
+
19
+ /** Creates a project membership. */
20
+ create(input: Omit<ProjectMembership, "joinedAt" | "updatedAt">): Promise<ProjectMembership>;
21
+
22
+ /**
23
+ * Atomically initializes an empty Project with its first owner.
24
+ * `created` means this call initialized the Project; `existing` means the
25
+ * requested user already owns the initialized Project and this was an
26
+ * idempotent retry; `already_initialized` means membership rows exist but
27
+ * this call did not produce either initial-owner outcome.
28
+ */
29
+ createInitialOwner(
30
+ input: Omit<ProjectMembership, "role" | "status" | "joinedAt" | "updatedAt">,
31
+ ): Promise<
32
+ | { kind: "created" | "existing"; membership: ProjectMembership }
33
+ | { kind: "already_initialized" }
34
+ >;
35
+
36
+ /**
37
+ * Updates a role within the tenant using optimistic concurrency and owner
38
+ * safeguards. `not_found` means the membership is not in the tenant;
39
+ * `conflict` means `updatedAt` differs from `expectedUpdatedAt`.
40
+ */
41
+ updateRole(input: {
42
+ tenantId: string;
43
+ id: string;
44
+ role: ProjectHumanRole;
45
+ expectedUpdatedAt: Date;
46
+ }): Promise<ProjectMembershipMutationResult>;
47
+
48
+ /**
49
+ * Removes a membership within the tenant using optimistic concurrency and
50
+ * owner safeguards. `not_found` means the membership is not in the tenant;
51
+ * `conflict` means `updatedAt` differs from `expectedUpdatedAt`.
52
+ */
53
+ remove(input: {
54
+ tenantId: string;
55
+ id: string;
56
+ expectedUpdatedAt: Date;
57
+ }): Promise<ProjectMembershipMutationResult>;
58
+ }
@@ -0,0 +1,26 @@
1
+ import type {
2
+ ProjectRoomMessage,
3
+ ProjectRoomMessageCursor,
4
+ } from "./ProjectRoomProtocol";
5
+
6
+ /** Persistence operations for messages in a project room. */
7
+ export interface ProjectRoomMessageStore {
8
+ /** Creates a room message. */
9
+ create(input: Omit<ProjectRoomMessage, "createdAt">): Promise<ProjectRoomMessage>;
10
+
11
+ /** Creates or returns the existing room message for an idempotency key. */
12
+ createIdempotent(
13
+ input: Omit<ProjectRoomMessage, "createdAt"> & { idempotencyKey: string },
14
+ ): Promise<ProjectRoomMessage>;
15
+
16
+ /** Lists messages strictly before an optional cursor, up to the requested limit. */
17
+ list(input: {
18
+ tenantId: string;
19
+ roomId: string;
20
+ before?: ProjectRoomMessageCursor;
21
+ limit: number;
22
+ }): Promise<ProjectRoomMessage[]>;
23
+
24
+ /** Finds a room message by identifier within a tenant. */
25
+ findById(tenantId: string, id: string): Promise<ProjectRoomMessage | null>;
26
+ }
@@ -0,0 +1,143 @@
1
+ /** Human access roles available within a project. */
2
+ export type ProjectHumanRole = "owner" | "admin" | "member" | "viewer";
3
+
4
+ /** Lifecycle states for a human project membership. */
5
+ export type ProjectMembershipStatus = "active" | "removed";
6
+
7
+ /** Roles a bot can hold within a project room. */
8
+ export type ProjectBotRole = "coordinator" | "specialist";
9
+
10
+ /** Lifecycle states for a bot room membership. */
11
+ export type ProjectBotMembershipStatus = "active" | "paused" | "removed";
12
+
13
+ /** Origins supported by project room messages. */
14
+ export type ProjectRoomMessageSource =
15
+ | "user"
16
+ | "agent"
17
+ | "task"
18
+ | "routine"
19
+ | "system";
20
+
21
+ /** The main room associated with a project. */
22
+ export interface ProjectRoom {
23
+ id: string;
24
+ tenantId: string;
25
+ workspaceId: string;
26
+ projectId: string;
27
+ type: "main";
28
+ name: string;
29
+ createdAt: Date;
30
+ updatedAt: Date;
31
+ }
32
+
33
+ /** A user's membership and access role within a project. */
34
+ export interface ProjectMembership {
35
+ id: string;
36
+ tenantId: string;
37
+ projectId: string;
38
+ userId: string;
39
+ role: ProjectHumanRole;
40
+ status: ProjectMembershipStatus;
41
+ joinedAt: Date;
42
+ updatedAt: Date;
43
+ }
44
+
45
+ /** A bot's role, presentation, and execution thread within a project room. */
46
+ export interface ProjectBotMembership {
47
+ id: string;
48
+ tenantId: string;
49
+ workspaceId: string;
50
+ projectId: string;
51
+ roomId: string;
52
+ assistantId: string;
53
+ role: ProjectBotRole;
54
+ title: string;
55
+ responsibility?: string;
56
+ mentionName: string;
57
+ status: ProjectBotMembershipStatus;
58
+ roomThreadId: string;
59
+ joinedAt: Date;
60
+ updatedAt: Date;
61
+ }
62
+
63
+ /** Identifies a human author of a project room message. */
64
+ interface ProjectRoomMessageHumanAuthor {
65
+ type: "human";
66
+ userId: string;
67
+ }
68
+
69
+ /** Identifies a bot membership as the author of a project room message. */
70
+ interface ProjectRoomMessageBotAuthor {
71
+ type: "bot";
72
+ membershipId: string;
73
+ assistantId: string;
74
+ }
75
+
76
+ /** Identifies the system as the author of a project room message. */
77
+ interface ProjectRoomMessageSystemAuthor {
78
+ type: "system";
79
+ }
80
+
81
+ /** The discriminated author variants supported by project room messages. */
82
+ export type ProjectRoomMessageAuthor =
83
+ | ProjectRoomMessageHumanAuthor
84
+ | ProjectRoomMessageBotAuthor
85
+ | ProjectRoomMessageSystemAuthor;
86
+
87
+ /** A bot or the whole team targeted by a room message. */
88
+ export type ProjectRoomMention =
89
+ | { type: "bot"; membershipId: string }
90
+ | { type: "team" };
91
+
92
+ /** Text payload carried by a project room message. */
93
+ interface ProjectRoomTextContent {
94
+ type: "text";
95
+ text: string;
96
+ }
97
+
98
+ /** A message posted to a project room. */
99
+ export interface ProjectRoomMessage {
100
+ id: string;
101
+ tenantId: string;
102
+ workspaceId: string;
103
+ projectId: string;
104
+ roomId: string;
105
+ author: ProjectRoomMessageAuthor;
106
+ content: ProjectRoomTextContent;
107
+ mentions: ProjectRoomMention[];
108
+ replyToMessageId?: string;
109
+ source: ProjectRoomMessageSource;
110
+ sourceId?: string;
111
+ idempotencyKey?: string;
112
+ createdAt: Date;
113
+ }
114
+
115
+ /** Stable position used to page through project room messages. */
116
+ export interface ProjectRoomMessageCursor {
117
+ createdAt: Date;
118
+ id: string;
119
+ }
120
+
121
+ /** Metadata linking a room-presence thread to its project room bot membership. */
122
+ export interface ProjectRoomThreadMetadata {
123
+ source: "project_room";
124
+ kind: "room_presence";
125
+ workspaceId: string;
126
+ projectId: string;
127
+ roomId: string;
128
+ membershipId: string;
129
+ assistantId: string;
130
+ }
131
+
132
+ /** Metadata linking a task-execution thread to its owning project task and bot membership. */
133
+ export interface ProjectTaskThreadMetadata {
134
+ source: "project_task";
135
+ kind: "task_execution";
136
+ workspaceId: string;
137
+ projectId: string;
138
+ roomId: string;
139
+ membershipId: string;
140
+ assistantId: string;
141
+ taskId: string;
142
+ parentTaskId?: string;
143
+ }
@@ -0,0 +1,349 @@
1
+ import type {
2
+ ProjectBotMembershipStatus,
3
+ ProjectBotRole,
4
+ ProjectHumanRole,
5
+ ProjectMembershipStatus,
6
+ ProjectRoomMention,
7
+ ProjectRoomMessageSource,
8
+ } from "./ProjectRoomProtocol";
9
+ import type { TaskItem } from "./TaskStoreProtocol";
10
+ import { snapshotExactArray, snapshotExactRecord } from "./ExactDataSnapshot";
11
+
12
+ /** A message shape safe to expose to Project Room clients. */
13
+ export interface ProjectRoomPublicMessage {
14
+ id: string;
15
+ roomId: string;
16
+ author:
17
+ | { type: "human"; userId: string }
18
+ | { type: "bot"; membershipId: string }
19
+ | { type: "system" };
20
+ content: { type: "text"; text: string };
21
+ mentions: ProjectRoomMention[];
22
+ replyToMessageId?: string;
23
+ source: ProjectRoomMessageSource;
24
+ createdAt: string;
25
+ }
26
+
27
+ /** A human membership shape safe to expose to Project Room clients. */
28
+ export interface ProjectRoomPublicMembership {
29
+ id: string;
30
+ userId: string;
31
+ role: ProjectHumanRole;
32
+ status: ProjectMembershipStatus;
33
+ joinedAt: string;
34
+ updatedAt: string;
35
+ }
36
+
37
+ /** A bot membership shape safe to expose to Project Room clients. */
38
+ export interface ProjectRoomPublicBotMembership {
39
+ id: string;
40
+ role: ProjectBotRole;
41
+ title: string;
42
+ responsibility?: string;
43
+ mentionName: string;
44
+ status: ProjectBotMembershipStatus;
45
+ joinedAt: string;
46
+ updatedAt: string;
47
+ }
48
+
49
+ /** A fully identified business event carried by the realtime stream. */
50
+ export interface ProjectRoomEventOf<TType extends string, TData> {
51
+ id: string;
52
+ type: TType;
53
+ occurredAt: string;
54
+ data: TData;
55
+ }
56
+
57
+ /** A newly committed message event. */
58
+ export type ProjectRoomMessageCreatedEvent = ProjectRoomEventOf<
59
+ "message.created",
60
+ { message: ProjectRoomPublicMessage }
61
+ >;
62
+
63
+ /** A changed bot roster event. */
64
+ export type ProjectRoomRosterChangedEvent = ProjectRoomEventOf<
65
+ "roster.changed",
66
+ { change: "added" | "updated" | "paused" | "resumed" | "removed"; membership: ProjectRoomPublicBotMembership }
67
+ >;
68
+
69
+ /** A changed human membership event. */
70
+ export type ProjectRoomMembershipChangedEvent = ProjectRoomEventOf<
71
+ "membership.changed",
72
+ { change: "added" | "role_changed" | "removed"; membership: ProjectRoomPublicMembership }
73
+ >;
74
+
75
+ /** A changed Project Task fact event. */
76
+ export type ProjectRoomTaskChangedEvent = ProjectRoomEventOf<
77
+ "task.changed",
78
+ {
79
+ taskId: string;
80
+ status: TaskItem["status"];
81
+ ownerMembershipId: string;
82
+ updatedAt: string;
83
+ }
84
+ >;
85
+
86
+ /** All identified business events retained by the realtime broker. */
87
+ export type ProjectRoomBusinessEvent =
88
+ | ProjectRoomMessageCreatedEvent
89
+ | ProjectRoomRosterChangedEvent
90
+ | ProjectRoomMembershipChangedEvent
91
+ | ProjectRoomTaskChangedEvent;
92
+
93
+ /** A business event before the broker assigns its process-local ID. */
94
+ export type ProjectRoomBusinessEventDraft =
95
+ | Omit<ProjectRoomMessageCreatedEvent, "id">
96
+ | Omit<ProjectRoomRosterChangedEvent, "id">
97
+ | Omit<ProjectRoomMembershipChangedEvent, "id">
98
+ | Omit<ProjectRoomTaskChangedEvent, "id">;
99
+
100
+ /** A connection control event; control events are never replayed. */
101
+ export type ProjectRoomControlEvent =
102
+ | { type: "ready"; data: { epoch: string; headEventId: string | null } }
103
+ | { type: "resync"; data: { reason: "SERVER_RESTART" | "CURSOR_EXPIRED" | "SLOW_CONSUMER" } }
104
+ | { type: "access.revoked"; data: { reason: "PROJECT_ACCESS_REVOKED" | "TOKEN_EXPIRED" } };
105
+
106
+ /** The authenticated identity used by Project Room realtime access checks. */
107
+ export interface ProjectRoomRealtimeActor {
108
+ tenantId: string;
109
+ userId: string;
110
+ projectId: string;
111
+ tokenExpiresAt: number;
112
+ }
113
+
114
+ /** Writable HTTP socket surface required by the bounded SSE transport. */
115
+ export interface ProjectRoomSseWritable {
116
+ write(chunk: string): boolean;
117
+ end(): void;
118
+ destroy(): void;
119
+ on(event: "close" | "error" | "drain", listener: () => void): this;
120
+ off(event: "close" | "error" | "drain", listener: () => void): this;
121
+ }
122
+
123
+ /** The scope used to isolate events between tenant rooms. */
124
+ export interface ProjectRoomEventScope {
125
+ tenantId: string;
126
+ roomId: string;
127
+ projectId: string;
128
+ }
129
+
130
+ /** An internal broker event carrying scope that is removed before public serialization. */
131
+ export type ProjectRoomScopedBusinessEvent = ProjectRoomBusinessEvent & {
132
+ scope: ProjectRoomEventScope;
133
+ };
134
+
135
+ /** A broker subscription containing replay and its room head. */
136
+ export interface ProjectRoomEventSubscription {
137
+ replay: ProjectRoomBusinessEvent[];
138
+ headEventId: string | null;
139
+ unsubscribe(): void;
140
+ }
141
+
142
+ /** The narrow broker contract consumed by realtime publishers and services. */
143
+ export interface ProjectRoomEventBrokerProtocol {
144
+ readonly epoch: string;
145
+ publish(scope: ProjectRoomEventScope, draft: ProjectRoomBusinessEventDraft): ProjectRoomScopedBusinessEvent;
146
+ subscribe(
147
+ scope: ProjectRoomEventScope,
148
+ afterEventId: string | undefined,
149
+ listener: (event: ProjectRoomBusinessEvent) => void,
150
+ ): ProjectRoomEventSubscription;
151
+ close(): void;
152
+ }
153
+
154
+ /** A typed cursor failure requiring client REST resynchronization. */
155
+ export class ProjectRoomCursorError extends Error {
156
+ readonly name = "ProjectRoomCursorError";
157
+
158
+ constructor(readonly code: "SERVER_RESTART" | "CURSOR_EXPIRED") {
159
+ super(`Project Room realtime cursor requires resynchronization: ${code}`);
160
+ }
161
+ }
162
+
163
+ /** A typed failure raised when the process-local event sequence is exhausted. */
164
+ export class ProjectRoomBrokerCapacityError extends Error {
165
+ readonly name = "ProjectRoomBrokerCapacityError";
166
+ readonly code = "PROJECT_ROOM_EVENT_SEQUENCE_EXHAUSTED" as const;
167
+
168
+ constructor() {
169
+ super("Project Room realtime event sequence is exhausted");
170
+ }
171
+ }
172
+
173
+ const PROJECT_ROOM_EVENT_ID_PATTERN = /^([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}):([1-9][0-9]*)$/i;
174
+
175
+ /** The parsed components of a canonical Project Room event ID. */
176
+ export interface ProjectRoomEventId {
177
+ epoch: string;
178
+ sequence: number;
179
+ }
180
+
181
+ /** Parses a canonical event ID, returning undefined for malformed or unsafe IDs. */
182
+ export function parseProjectRoomEventId(value: unknown): ProjectRoomEventId | undefined {
183
+ if (typeof value !== "string") return undefined;
184
+ const match = PROJECT_ROOM_EVENT_ID_PATTERN.exec(value);
185
+ if (!match) return undefined;
186
+ const sequence = Number(match[2]);
187
+ if (!Number.isSafeInteger(sequence)) return undefined;
188
+ if (match[1] !== match[1].toLowerCase()) return undefined;
189
+ return { epoch: match[1], sequence };
190
+ }
191
+
192
+ /** Checks an event ID without relying on realm-specific object identity. */
193
+ export function isProjectRoomEventId(value: unknown): value is string {
194
+ return parseProjectRoomEventId(value) !== undefined;
195
+ }
196
+
197
+ function isoDate(value: unknown): string | undefined {
198
+ if (typeof value !== "object" || value === null) return undefined;
199
+ try {
200
+ const time = Date.prototype.getTime.call(value);
201
+ return Number.isFinite(time) ? new Date(time).toISOString() : undefined;
202
+ } catch {
203
+ return undefined;
204
+ }
205
+ }
206
+
207
+ function stringField(record: Record<string, unknown>, key: string): string | undefined {
208
+ return typeof record[key] === "string" ? record[key] : undefined;
209
+ }
210
+
211
+ function isOneOf<T extends string>(value: unknown, values: readonly T[]): value is T {
212
+ return typeof value === "string" && values.includes(value as T);
213
+ }
214
+
215
+ const messageSources = ["user", "agent", "task", "routine", "system"] as const;
216
+ const humanRoles = ["owner", "admin", "member", "viewer"] as const;
217
+ const membershipStatuses = ["active", "removed"] as const;
218
+ const botRoles = ["coordinator", "specialist"] as const;
219
+ const botStatuses = ["active", "paused", "removed"] as const;
220
+
221
+ function mapPublicMessageRecord(record: Record<string, unknown>): ProjectRoomPublicMessage | undefined {
222
+ const id = stringField(record, "id");
223
+ const roomId = stringField(record, "roomId");
224
+ const source = stringField(record, "source");
225
+ const createdAt = isoDate(record.createdAt);
226
+ const content = snapshotExactRecord(record.content, ["type", "text"]);
227
+ const mentions = snapshotPublicMentions(record.mentions);
228
+ const author = snapshotExactRecord(record.author, ["type"], ["userId", "membershipId", "assistantId"]);
229
+ if (!id || !roomId || !isOneOf(source, messageSources) || !createdAt || !content || content.type !== "text"
230
+ || typeof content.text !== "string" || !mentions || !author || typeof author.type !== "string") return undefined;
231
+ const publicAuthor = author.type === "human" && typeof author.userId === "string"
232
+ ? { type: "human" as const, userId: author.userId }
233
+ : author.type === "bot" && typeof author.membershipId === "string"
234
+ ? { type: "bot" as const, membershipId: author.membershipId }
235
+ : author.type === "system" ? { type: "system" as const } : undefined;
236
+ if (!publicAuthor) return undefined;
237
+ const result: ProjectRoomPublicMessage = { id, roomId, author: publicAuthor, content: { type: "text", text: content.text }, mentions: mentions as ProjectRoomMention[], source: source as ProjectRoomMessageSource, createdAt };
238
+ if (record.replyToMessageId !== undefined) {
239
+ if (typeof record.replyToMessageId !== "string") return undefined;
240
+ result.replyToMessageId = record.replyToMessageId;
241
+ }
242
+ return result;
243
+ }
244
+
245
+ function snapshotPublicMentions(value: unknown): ProjectRoomMention[] | undefined {
246
+ const rows = snapshotExactArray(value);
247
+ if (!rows) return undefined;
248
+ const result: ProjectRoomMention[] = [];
249
+ for (const row of rows) {
250
+ const team = snapshotExactRecord(row, ["type"]);
251
+ if (team?.type === "team") { result.push({ type: "team" }); continue; }
252
+ const bot = snapshotExactRecord(row, ["type", "membershipId"]);
253
+ if (bot?.type === "bot" && typeof bot.membershipId === "string") {
254
+ result.push({ type: "bot", membershipId: bot.membershipId });
255
+ continue;
256
+ }
257
+ return undefined;
258
+ }
259
+ return result;
260
+ }
261
+
262
+ function mapPublicMembershipRecord(record: Record<string, unknown>): ProjectRoomPublicMembership | undefined {
263
+ const joinedAt = isoDate(record.joinedAt); const updatedAt = isoDate(record.updatedAt);
264
+ if (typeof record.id !== "string" || typeof record.userId !== "string" || !isOneOf(record.role, humanRoles) || !isOneOf(record.status, membershipStatuses) || !joinedAt || !updatedAt) return undefined;
265
+ return { id: record.id, userId: record.userId, role: record.role, status: record.status, joinedAt, updatedAt };
266
+ }
267
+
268
+ function mapPublicBotMembershipRecord(record: Record<string, unknown>): ProjectRoomPublicBotMembership | undefined {
269
+ const joinedAt = isoDate(record.joinedAt); const updatedAt = isoDate(record.updatedAt);
270
+ if (typeof record.id !== "string" || !isOneOf(record.role, botRoles) || typeof record.title !== "string" || typeof record.mentionName !== "string" || !isOneOf(record.status, botStatuses) || !joinedAt || !updatedAt || record.responsibility !== undefined && typeof record.responsibility !== "string") return undefined;
271
+ return { id: record.id, role: record.role, title: record.title, ...(record.responsibility === undefined ? {} : { responsibility: record.responsibility }), mentionName: record.mentionName, status: record.status, joinedAt, updatedAt };
272
+ }
273
+
274
+ /** Maps a canonical internal message to the strict public message DTO. */
275
+ export function toProjectRoomPublicMessage(value: unknown): ProjectRoomPublicMessage | undefined {
276
+ const record = snapshotExactRecord(value, [
277
+ "id", "tenantId", "workspaceId", "projectId", "roomId", "author", "content", "mentions", "source", "createdAt",
278
+ ], ["replyToMessageId", "sourceId", "idempotencyKey"]);
279
+ if (!record) return undefined;
280
+ return mapPublicMessageRecord(record);
281
+ }
282
+
283
+ /** A descriptor-safe message projection together with its canonical realtime scope. */
284
+ export function snapshotProjectRoomMessageRealtime(value: unknown): {
285
+ scope: { tenantId: string; roomId: string; projectId: string };
286
+ publicMessage: ProjectRoomPublicMessage;
287
+ } | undefined {
288
+ const record = snapshotExactRecord(value, ["id", "tenantId", "workspaceId", "projectId", "roomId", "author", "content", "mentions", "source", "createdAt"], ["replyToMessageId", "sourceId", "idempotencyKey"]);
289
+ if (!record) return undefined;
290
+ const publicMessage = mapPublicMessageRecord(record);
291
+ if (!publicMessage || typeof record.tenantId !== "string" || typeof record.projectId !== "string") return undefined;
292
+ return { scope: { tenantId: record.tenantId, roomId: publicMessage.roomId, projectId: record.projectId }, publicMessage };
293
+ }
294
+
295
+ /** Maps a canonical internal human membership to the strict public DTO. */
296
+ export function toProjectRoomPublicMembership(value: unknown): ProjectRoomPublicMembership | undefined {
297
+ const record = snapshotExactRecord(value,
298
+ ["id", "tenantId", "projectId", "userId", "role", "status", "joinedAt", "updatedAt"]);
299
+ if (!record || typeof record.id !== "string" || typeof record.userId !== "string"
300
+ || !isOneOf(record.role, humanRoles) || !isOneOf(record.status, membershipStatuses)) return undefined;
301
+ const joinedAt = isoDate(record.joinedAt);
302
+ const updatedAt = isoDate(record.updatedAt);
303
+ if (!joinedAt || !updatedAt) return undefined;
304
+ return { id: record.id, userId: record.userId, role: record.role,
305
+ status: record.status, joinedAt, updatedAt };
306
+ }
307
+
308
+ /** A descriptor-safe human membership projection with canonical tenant/project scope. */
309
+ export function snapshotProjectRoomMembershipRealtime(value: unknown): {
310
+ scope: { tenantId: string; projectId: string };
311
+ publicMembership: ProjectRoomPublicMembership;
312
+ } | undefined {
313
+ const record = snapshotExactRecord(value, ["id", "tenantId", "projectId", "userId", "role", "status", "joinedAt", "updatedAt"]);
314
+ if (!record) return undefined;
315
+ const publicMembership = mapPublicMembershipRecord(record);
316
+ if (!publicMembership || typeof record.tenantId !== "string" || typeof record.projectId !== "string") return undefined;
317
+ return { scope: { tenantId: record.tenantId, projectId: record.projectId }, publicMembership };
318
+ }
319
+
320
+ /** Maps a canonical internal bot membership to the strict public DTO. */
321
+ export function toProjectRoomPublicBotMembership(value: unknown): ProjectRoomPublicBotMembership | undefined {
322
+ const record = snapshotExactRecord(value,
323
+ ["id", "tenantId", "workspaceId", "projectId", "roomId", "assistantId", "role", "title", "mentionName", "status", "roomThreadId", "joinedAt", "updatedAt"],
324
+ ["responsibility"]);
325
+ if (!record || typeof record.id !== "string" || !isOneOf(record.role, botRoles) || typeof record.title !== "string"
326
+ || typeof record.mentionName !== "string" || !isOneOf(record.status, botStatuses)) return undefined;
327
+ if (record.responsibility !== undefined && typeof record.responsibility !== "string") return undefined;
328
+ const joinedAt = isoDate(record.joinedAt);
329
+ const updatedAt = isoDate(record.updatedAt);
330
+ if (!joinedAt || !updatedAt) return undefined;
331
+ const result: ProjectRoomPublicBotMembership = {
332
+ id: record.id, role: record.role, title: record.title,
333
+ mentionName: record.mentionName, status: record.status, joinedAt, updatedAt,
334
+ };
335
+ if (record.responsibility !== undefined) result.responsibility = record.responsibility;
336
+ return result;
337
+ }
338
+
339
+ /** A descriptor-safe bot membership projection with canonical realtime scope. */
340
+ export function snapshotProjectRoomBotMembershipRealtime(value: unknown): {
341
+ scope: { tenantId: string; roomId: string; projectId: string };
342
+ publicMembership: ProjectRoomPublicBotMembership;
343
+ } | undefined {
344
+ const record = snapshotExactRecord(value, ["id", "tenantId", "workspaceId", "projectId", "roomId", "assistantId", "role", "title", "mentionName", "status", "roomThreadId", "joinedAt", "updatedAt"], ["responsibility"]);
345
+ if (!record) return undefined;
346
+ const publicMembership = mapPublicBotMembershipRecord(record);
347
+ if (!publicMembership || typeof record.tenantId !== "string" || typeof record.roomId !== "string" || typeof record.projectId !== "string") return undefined;
348
+ return { scope: { tenantId: record.tenantId, roomId: record.roomId, projectId: record.projectId }, publicMembership };
349
+ }
@@ -0,0 +1,16 @@
1
+ import type { ProjectRoom } from "./ProjectRoomProtocol";
2
+
3
+ /** Persistence operations for a project's canonical main room. */
4
+ export interface ProjectRoomStore {
5
+ /** Creates the main room if needed and returns the canonical record. */
6
+ ensureMainRoom(input: {
7
+ id: string;
8
+ tenantId: string;
9
+ workspaceId: string;
10
+ projectId: string;
11
+ name: string;
12
+ }): Promise<ProjectRoom>;
13
+
14
+ /** Finds the main room for a project, if one exists. */
15
+ getMainRoom(tenantId: string, projectId: string): Promise<ProjectRoom | null>;
16
+ }
@@ -59,6 +59,36 @@ export interface Skill {
59
59
  */
60
60
  subSkills?: string[];
61
61
 
62
+ /**
63
+ * Source of the skill (optional)
64
+ * e.g. "builtin-plugin" for read-only plugin-provided skills
65
+ */
66
+ source?: string;
67
+
68
+ /**
69
+ * Owning plugin type (optional)
70
+ * Set for skills contributed by a registered plugin
71
+ */
72
+ pluginType?: string;
73
+
74
+ /**
75
+ * Plugin skill bundle version (optional)
76
+ * Set for skills contributed by a registered plugin
77
+ */
78
+ version?: string;
79
+
80
+ /**
81
+ * Resource catalog (optional)
82
+ * Safe relative paths of a plugin skill's bundled resources with their MIME types
83
+ */
84
+ resourcePaths?: Array<{ path: string; mimeType?: string }>;
85
+
86
+ /**
87
+ * Read-only flag (optional)
88
+ * True for immutable sources (e.g. plugin-provided skills) that cannot be created/updated/deleted
89
+ */
90
+ readOnly?: boolean;
91
+
62
92
  /**
63
93
  * Skill creation timestamp
64
94
  */
@@ -1,4 +1,9 @@
1
- /** A single canonical belief recorded in a task description. */
1
+ /**
2
+ * A single canonical belief recorded in a task description.
3
+ *
4
+ * `probability` is retained as the persisted field name, but task guidance uses
5
+ * it as an evidence-support percentage rather than a calibrated probability.
6
+ */
2
7
  export interface TaskBeliefEntry {
3
8
  key: string;
4
9
  probability: number;