From a39cebe81a977ff81c8cc1c8a8b0362bce1361f7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 06:29:23 +0000
Subject: [PATCH 1/2] Initial plan
From fb12cce87d9639e4d6c871f7e5b6e848d28f9f0a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 06:38:37 +0000
Subject: [PATCH 2/2] feat: implement WebSocket and Collaboration protocols
- Created websocket.zod.ts with comprehensive WebSocket event protocol
- Created collaboration.zod.ts with OT, CRDT, cursor sharing, and awareness
- Added comprehensive test coverage for both protocols
- Updated exports in index files
- All tests passing and build successful
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
---
content/docs/references/api/index.mdx | 1 +
content/docs/references/api/meta.json | 3 +-
content/docs/references/api/websocket.mdx | 374 +++++++
.../docs/references/system/collaboration.mdx | 407 +++++++
content/docs/references/system/index.mdx | 1 +
content/docs/references/system/meta.json | 1 +
packages/spec/json-schema/api/AckMessage.json | 46 +
.../spec/json-schema/api/CursorMessage.json | 139 +++
.../spec/json-schema/api/CursorPosition.json | 112 ++
.../spec/json-schema/api/DocumentState.json | 49 +
.../spec/json-schema/api/EditMessage.json | 135 +++
.../spec/json-schema/api/EditOperation.json | 108 ++
.../json-schema/api/EditOperationType.json | 14 +
.../spec/json-schema/api/ErrorMessage.json | 44 +
.../spec/json-schema/api/EventFilter.json | 65 ++
.../json-schema/api/EventFilterCondition.json | 42 +
.../spec/json-schema/api/EventMessage.json | 55 +
.../spec/json-schema/api/EventPattern.json | 12 +
.../json-schema/api/EventSubscription.json | 105 ++
.../spec/json-schema/api/FilterOperator.json | 24 +
.../spec/json-schema/api/PingMessage.json | 31 +
.../spec/json-schema/api/PongMessage.json | 36 +
.../spec/json-schema/api/PresenceMessage.json | 92 ++
.../spec/json-schema/api/PresenceState.json | 65 ++
.../spec/json-schema/api/PresenceUpdate.json | 35 +
.../json-schema/api/SubscribeMessage.json | 132 +++
.../json-schema/api/UnsubscribeMessage.json | 47 +
.../json-schema/api/UnsubscribeRequest.json | 20 +
.../spec/json-schema/api/WebSocketConfig.json | 63 ++
.../json-schema/api/WebSocketMessage.json | 707 +++++++++++++
.../json-schema/api/WebSocketMessageType.json | 21 +
.../api/WebSocketPresenceStatus.json | 15 +
.../json-schema/system/AwarenessEvent.json | 51 +
.../json-schema/system/AwarenessSession.json | 117 ++
.../json-schema/system/AwarenessUpdate.json | 35 +
.../system/AwarenessUserState.json | 77 ++
.../json-schema/system/CRDTMergeResult.json | 295 ++++++
.../spec/json-schema/system/CRDTState.json | 258 +++++
.../spec/json-schema/system/CRDTType.json | 20 +
.../json-schema/system/CollaborationMode.json | 15 +
.../system/CollaborationSession.json | 575 ++++++++++
.../system/CollaborationSessionConfig.json | 86 ++
.../system/CollaborativeCursor.json | 189 ++++
.../json-schema/system/CounterOperation.json | 30 +
.../json-schema/system/CursorColorPreset.json | 21 +
.../json-schema/system/CursorSelection.json | 66 ++
.../spec/json-schema/system/CursorStyle.json | 59 ++
.../spec/json-schema/system/CursorUpdate.json | 101 ++
.../spec/json-schema/system/GCounter.json | 28 +
.../spec/json-schema/system/LWWRegister.json | 51 +
packages/spec/json-schema/system/ORSet.json | 57 +
.../spec/json-schema/system/ORSetElement.json | 39 +
.../spec/json-schema/system/OTComponent.json | 76 ++
.../spec/json-schema/system/OTOperation.json | 128 +++
.../json-schema/system/OTOperationType.json | 14 +
.../json-schema/system/OTTransformResult.json | 150 +++
.../spec/json-schema/system/PNCounter.json | 37 +
.../json-schema/system/TextCRDTOperation.json | 52 +
.../json-schema/system/TextCRDTState.json | 105 ++
.../system/UserActivityStatus.json | 15 +
.../spec/json-schema/system/VectorClock.json | 23 +
packages/spec/src/api/index.ts | 1 +
packages/spec/src/api/websocket.test.ts | 829 +++++++++++++++
packages/spec/src/api/websocket.zod.ts | 433 ++++++++
.../spec/src/system/collaboration.test.ts | 999 ++++++++++++++++++
packages/spec/src/system/collaboration.zod.ts | 482 +++++++++
packages/spec/src/system/index.ts | 1 +
67 files changed, 8515 insertions(+), 1 deletion(-)
create mode 100644 content/docs/references/api/websocket.mdx
create mode 100644 content/docs/references/system/collaboration.mdx
create mode 100644 packages/spec/json-schema/api/AckMessage.json
create mode 100644 packages/spec/json-schema/api/CursorMessage.json
create mode 100644 packages/spec/json-schema/api/CursorPosition.json
create mode 100644 packages/spec/json-schema/api/DocumentState.json
create mode 100644 packages/spec/json-schema/api/EditMessage.json
create mode 100644 packages/spec/json-schema/api/EditOperation.json
create mode 100644 packages/spec/json-schema/api/EditOperationType.json
create mode 100644 packages/spec/json-schema/api/ErrorMessage.json
create mode 100644 packages/spec/json-schema/api/EventFilter.json
create mode 100644 packages/spec/json-schema/api/EventFilterCondition.json
create mode 100644 packages/spec/json-schema/api/EventMessage.json
create mode 100644 packages/spec/json-schema/api/EventPattern.json
create mode 100644 packages/spec/json-schema/api/EventSubscription.json
create mode 100644 packages/spec/json-schema/api/FilterOperator.json
create mode 100644 packages/spec/json-schema/api/PingMessage.json
create mode 100644 packages/spec/json-schema/api/PongMessage.json
create mode 100644 packages/spec/json-schema/api/PresenceMessage.json
create mode 100644 packages/spec/json-schema/api/PresenceState.json
create mode 100644 packages/spec/json-schema/api/PresenceUpdate.json
create mode 100644 packages/spec/json-schema/api/SubscribeMessage.json
create mode 100644 packages/spec/json-schema/api/UnsubscribeMessage.json
create mode 100644 packages/spec/json-schema/api/UnsubscribeRequest.json
create mode 100644 packages/spec/json-schema/api/WebSocketConfig.json
create mode 100644 packages/spec/json-schema/api/WebSocketMessage.json
create mode 100644 packages/spec/json-schema/api/WebSocketMessageType.json
create mode 100644 packages/spec/json-schema/api/WebSocketPresenceStatus.json
create mode 100644 packages/spec/json-schema/system/AwarenessEvent.json
create mode 100644 packages/spec/json-schema/system/AwarenessSession.json
create mode 100644 packages/spec/json-schema/system/AwarenessUpdate.json
create mode 100644 packages/spec/json-schema/system/AwarenessUserState.json
create mode 100644 packages/spec/json-schema/system/CRDTMergeResult.json
create mode 100644 packages/spec/json-schema/system/CRDTState.json
create mode 100644 packages/spec/json-schema/system/CRDTType.json
create mode 100644 packages/spec/json-schema/system/CollaborationMode.json
create mode 100644 packages/spec/json-schema/system/CollaborationSession.json
create mode 100644 packages/spec/json-schema/system/CollaborationSessionConfig.json
create mode 100644 packages/spec/json-schema/system/CollaborativeCursor.json
create mode 100644 packages/spec/json-schema/system/CounterOperation.json
create mode 100644 packages/spec/json-schema/system/CursorColorPreset.json
create mode 100644 packages/spec/json-schema/system/CursorSelection.json
create mode 100644 packages/spec/json-schema/system/CursorStyle.json
create mode 100644 packages/spec/json-schema/system/CursorUpdate.json
create mode 100644 packages/spec/json-schema/system/GCounter.json
create mode 100644 packages/spec/json-schema/system/LWWRegister.json
create mode 100644 packages/spec/json-schema/system/ORSet.json
create mode 100644 packages/spec/json-schema/system/ORSetElement.json
create mode 100644 packages/spec/json-schema/system/OTComponent.json
create mode 100644 packages/spec/json-schema/system/OTOperation.json
create mode 100644 packages/spec/json-schema/system/OTOperationType.json
create mode 100644 packages/spec/json-schema/system/OTTransformResult.json
create mode 100644 packages/spec/json-schema/system/PNCounter.json
create mode 100644 packages/spec/json-schema/system/TextCRDTOperation.json
create mode 100644 packages/spec/json-schema/system/TextCRDTState.json
create mode 100644 packages/spec/json-schema/system/UserActivityStatus.json
create mode 100644 packages/spec/json-schema/system/VectorClock.json
create mode 100644 packages/spec/src/api/websocket.test.ts
create mode 100644 packages/spec/src/api/websocket.zod.ts
create mode 100644 packages/spec/src/system/collaboration.test.ts
create mode 100644 packages/spec/src/system/collaboration.zod.ts
diff --git a/content/docs/references/api/index.mdx b/content/docs/references/api/index.mdx
index 980afbd4b..782c27f56 100644
--- a/content/docs/references/api/index.mdx
+++ b/content/docs/references/api/index.mdx
@@ -15,5 +15,6 @@ This section contains all protocol schemas for the api layer of ObjectStack.
+
diff --git a/content/docs/references/api/meta.json b/content/docs/references/api/meta.json
index 7b29372f8..b72377cec 100644
--- a/content/docs/references/api/meta.json
+++ b/content/docs/references/api/meta.json
@@ -7,6 +7,7 @@
"graphql",
"odata",
"realtime",
- "router"
+ "router",
+ "websocket"
]
}
\ No newline at end of file
diff --git a/content/docs/references/api/websocket.mdx b/content/docs/references/api/websocket.mdx
new file mode 100644
index 000000000..539c1ae54
--- /dev/null
+++ b/content/docs/references/api/websocket.mdx
@@ -0,0 +1,374 @@
+---
+title: Websocket
+description: Websocket protocol schemas
+---
+
+# Websocket
+
+
+**Source:** `packages/spec/src/api/websocket.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { AckMessageSchema, CursorMessageSchema, CursorPositionSchema, DocumentStateSchema, EditMessageSchema, EditOperationSchema, EditOperationTypeSchema, ErrorMessageSchema, EventFilterSchema, EventFilterConditionSchema, EventMessageSchema, EventPatternSchema, EventSubscriptionSchema, FilterOperatorSchema, PingMessageSchema, PongMessageSchema, PresenceMessageSchema, PresenceStateSchema, PresenceUpdateSchema, SubscribeMessageSchema, UnsubscribeMessageSchema, UnsubscribeRequestSchema, WebSocketConfigSchema, WebSocketMessageSchema, WebSocketMessageTypeSchema, WebSocketPresenceStatusSchema } from '@objectstack/spec/api';
+import type { AckMessage, CursorMessage, CursorPosition, DocumentState, EditMessage, EditOperation, EditOperationType, ErrorMessage, EventFilter, EventFilterCondition, EventMessage, EventPattern, EventSubscription, FilterOperator, PingMessage, PongMessage, PresenceMessage, PresenceState, PresenceUpdate, SubscribeMessage, UnsubscribeMessage, UnsubscribeRequest, WebSocketConfig, WebSocketMessage, WebSocketMessageType, WebSocketPresenceStatus } from '@objectstack/spec/api';
+
+// Validate data
+const result = AckMessageSchema.parse(data);
+```
+
+---
+
+## AckMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+| **ackMessageId** | `string` | ✅ | ID of the message being acknowledged |
+| **success** | `boolean` | ✅ | Whether the operation was successful |
+| **error** | `string` | optional | Error message if operation failed |
+
+---
+
+## CursorMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+| **cursor** | `object` | ✅ | Cursor position |
+
+---
+
+## CursorPosition
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **userId** | `string` | ✅ | User identifier |
+| **sessionId** | `string` | ✅ | Session identifier |
+| **documentId** | `string` | ✅ | Document identifier being edited |
+| **position** | `object` | optional | Cursor position in document |
+| **selection** | `object` | optional | Selection range (if text is selected) |
+| **color** | `string` | optional | Cursor color for visual representation |
+| **userName** | `string` | optional | Display name of user |
+| **lastUpdate** | `string` | ✅ | ISO 8601 datetime of last cursor update |
+
+---
+
+## DocumentState
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **documentId** | `string` | ✅ | Document identifier |
+| **version** | `integer` | ✅ | Current document version |
+| **content** | `string` | ✅ | Current document content |
+| **lastModified** | `string` | ✅ | ISO 8601 datetime of last modification |
+| **activeSessions** | `string[]` | ✅ | Active editing session IDs |
+| **checksum** | `string` | optional | Content checksum for integrity verification |
+
+---
+
+## EditMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+| **operation** | `object` | ✅ | Edit operation |
+
+---
+
+## EditOperation
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **operationId** | `string` | ✅ | Unique operation identifier |
+| **documentId** | `string` | ✅ | Document identifier |
+| **userId** | `string` | ✅ | User who performed the edit |
+| **sessionId** | `string` | ✅ | Session identifier |
+| **type** | `Enum<'insert' \| 'delete' \| 'replace'>` | ✅ | Type of edit operation |
+| **position** | `object` | ✅ | Starting position of the operation |
+| **endPosition** | `object` | optional | Ending position (for delete/replace operations) |
+| **content** | `string` | optional | Content to insert/replace |
+| **version** | `integer` | ✅ | Document version before this operation |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when operation was created |
+| **baseOperationId** | `string` | optional | Previous operation ID this builds upon (for OT) |
+
+---
+
+## EditOperationType
+
+### Allowed Values
+
+* `insert`
+* `delete`
+* `replace`
+
+---
+
+## ErrorMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+| **code** | `string` | ✅ | Error code |
+| **message** | `string` | ✅ | Error message |
+| **details** | `any` | optional | Additional error details |
+
+---
+
+## EventFilter
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **conditions** | `object[]` | optional | Array of filter conditions |
+| **and** | `any[]` | optional | AND logical combination of filters |
+| **or** | `any[]` | optional | OR logical combination of filters |
+| **not** | `any` | optional | NOT logical negation of filter |
+
+---
+
+## EventFilterCondition
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **field** | `string` | ✅ | Field path to filter on (supports dot notation, e.g., "user.email") |
+| **operator** | `Enum<'eq' \| 'ne' \| 'gt' \| 'gte' \| 'lt' \| 'lte' \| 'in' \| 'nin' \| 'contains' \| 'startsWith' \| 'endsWith' \| 'exists' \| 'regex'>` | ✅ | Comparison operator |
+| **value** | `any` | optional | Value to compare against (not needed for "exists" operator) |
+
+---
+
+## EventMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+| **subscriptionId** | `string` | ✅ | Subscription ID this event belongs to |
+| **eventName** | `string` | ✅ | Event name |
+| **object** | `string` | optional | Object name the event relates to |
+| **payload** | `any` | optional | Event payload data |
+| **userId** | `string` | optional | User who triggered the event |
+
+---
+
+## EventPattern
+
+Event pattern (supports wildcards like "record.*" or "*.created")
+
+---
+
+## EventSubscription
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **subscriptionId** | `string` | ✅ | Unique subscription identifier |
+| **events** | `string[]` | ✅ | Event patterns to subscribe to (supports wildcards, e.g., "record.*", "user.created") |
+| **objects** | `string[]` | optional | Object names to filter events by (e.g., ["account", "contact"]) |
+| **filters** | `object` | optional | Advanced filter conditions for event payloads |
+| **channels** | `string[]` | optional | Channel names for scoped subscriptions |
+
+---
+
+## FilterOperator
+
+### Allowed Values
+
+* `eq`
+* `ne`
+* `gt`
+* `gte`
+* `lt`
+* `lte`
+* `in`
+* `nin`
+* `contains`
+* `startsWith`
+* `endsWith`
+* `exists`
+* `regex`
+
+---
+
+## PingMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+
+---
+
+## PongMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+| **pingMessageId** | `string` | optional | ID of ping message being responded to |
+
+---
+
+## PresenceMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+| **presence** | `object` | ✅ | Presence state |
+
+---
+
+## PresenceState
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **userId** | `string` | ✅ | User identifier |
+| **sessionId** | `string` | ✅ | Unique session identifier |
+| **status** | `Enum<'online' \| 'away' \| 'busy' \| 'offline'>` | ✅ | Current presence status |
+| **lastSeen** | `string` | ✅ | ISO 8601 datetime of last activity |
+| **currentLocation** | `string` | optional | Current page/route user is viewing |
+| **device** | `Enum<'desktop' \| 'mobile' \| 'tablet' \| 'other'>` | optional | Device type |
+| **customStatus** | `string` | optional | Custom user status message |
+| **metadata** | `Record` | optional | Additional custom presence data |
+
+---
+
+## PresenceUpdate
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **status** | `Enum<'online' \| 'away' \| 'busy' \| 'offline'>` | optional | Updated presence status |
+| **currentLocation** | `string` | optional | Updated current location |
+| **customStatus** | `string` | optional | Updated custom status message |
+| **metadata** | `Record` | optional | Updated metadata |
+
+---
+
+## SubscribeMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+| **subscription** | `object` | ✅ | Subscription configuration |
+
+---
+
+## UnsubscribeMessage
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **messageId** | `string` | ✅ | Unique message identifier |
+| **type** | `string` | ✅ | |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when message was sent |
+| **request** | `object` | ✅ | Unsubscribe request |
+
+---
+
+## UnsubscribeRequest
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **subscriptionId** | `string` | ✅ | Subscription ID to unsubscribe from |
+
+---
+
+## WebSocketConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **url** | `string` | ✅ | WebSocket server URL |
+| **protocols** | `string[]` | optional | WebSocket sub-protocols |
+| **reconnect** | `boolean` | optional | Enable automatic reconnection |
+| **reconnectInterval** | `integer` | optional | Reconnection interval in milliseconds |
+| **maxReconnectAttempts** | `integer` | optional | Maximum reconnection attempts |
+| **pingInterval** | `integer` | optional | Ping interval in milliseconds |
+| **timeout** | `integer` | optional | Message timeout in milliseconds |
+| **headers** | `Record` | optional | Custom headers for WebSocket handshake |
+
+---
+
+## WebSocketMessage
+
+---
+
+## WebSocketMessageType
+
+### Allowed Values
+
+* `subscribe`
+* `unsubscribe`
+* `event`
+* `ping`
+* `pong`
+* `ack`
+* `error`
+* `presence`
+* `cursor`
+* `edit`
+
+---
+
+## WebSocketPresenceStatus
+
+### Allowed Values
+
+* `online`
+* `away`
+* `busy`
+* `offline`
+
diff --git a/content/docs/references/system/collaboration.mdx b/content/docs/references/system/collaboration.mdx
new file mode 100644
index 000000000..44fa75150
--- /dev/null
+++ b/content/docs/references/system/collaboration.mdx
@@ -0,0 +1,407 @@
+---
+title: Collaboration
+description: Collaboration protocol schemas
+---
+
+# Collaboration
+
+
+**Source:** `packages/spec/src/system/collaboration.zod.ts`
+
+
+## TypeScript Usage
+
+```typescript
+import { AwarenessEventSchema, AwarenessSessionSchema, AwarenessUpdateSchema, AwarenessUserStateSchema, CRDTMergeResultSchema, CRDTStateSchema, CRDTTypeSchema, CollaborationModeSchema, CollaborationSessionSchema, CollaborationSessionConfigSchema, CollaborativeCursorSchema, CounterOperationSchema, CursorColorPresetSchema, CursorSelectionSchema, CursorStyleSchema, CursorUpdateSchema, GCounterSchema, LWWRegisterSchema, ORSetSchema, ORSetElementSchema, OTComponentSchema, OTOperationSchema, OTOperationTypeSchema, OTTransformResultSchema, PNCounterSchema, TextCRDTOperationSchema, TextCRDTStateSchema, UserActivityStatusSchema, VectorClockSchema } from '@objectstack/spec/system';
+import type { AwarenessEvent, AwarenessSession, AwarenessUpdate, AwarenessUserState, CRDTMergeResult, CRDTState, CRDTType, CollaborationMode, CollaborationSession, CollaborationSessionConfig, CollaborativeCursor, CounterOperation, CursorColorPreset, CursorSelection, CursorStyle, CursorUpdate, GCounter, LWWRegister, ORSet, ORSetElement, OTComponent, OTOperation, OTOperationType, OTTransformResult, PNCounter, TextCRDTOperation, TextCRDTState, UserActivityStatus, VectorClock } from '@objectstack/spec/system';
+
+// Validate data
+const result = AwarenessEventSchema.parse(data);
+```
+
+---
+
+## AwarenessEvent
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **eventId** | `string` | ✅ | Event identifier |
+| **sessionId** | `string` | ✅ | Session identifier |
+| **eventType** | `Enum<'user.joined' \| 'user.left' \| 'user.updated' \| 'session.created' \| 'session.ended'>` | ✅ | Type of awareness event |
+| **userId** | `string` | optional | User involved in event |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime of event |
+| **payload** | `any` | optional | Event payload |
+
+---
+
+## AwarenessSession
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **sessionId** | `string` | ✅ | Session identifier |
+| **documentId** | `string` | optional | Document ID this session is for |
+| **users** | `object[]` | ✅ | Active users in session |
+| **startedAt** | `string` | ✅ | ISO 8601 datetime when session started |
+| **lastUpdate** | `string` | ✅ | ISO 8601 datetime of last update |
+| **metadata** | `Record` | optional | Session metadata |
+
+---
+
+## AwarenessUpdate
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **status** | `Enum<'active' \| 'idle' \| 'viewing' \| 'disconnected'>` | optional | Updated status |
+| **currentDocument** | `string` | optional | Updated current document |
+| **currentView** | `string` | optional | Updated current view |
+| **metadata** | `Record` | optional | Updated metadata |
+
+---
+
+## AwarenessUserState
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **userId** | `string` | ✅ | User identifier |
+| **sessionId** | `string` | ✅ | Session identifier |
+| **userName** | `string` | ✅ | Display name |
+| **userAvatar** | `string` | optional | User avatar URL |
+| **status** | `Enum<'active' \| 'idle' \| 'viewing' \| 'disconnected'>` | ✅ | Current activity status |
+| **currentDocument** | `string` | optional | Document ID user is currently editing |
+| **currentView** | `string` | optional | Current view/page user is on |
+| **lastActivity** | `string` | ✅ | ISO 8601 datetime of last activity |
+| **joinedAt** | `string` | ✅ | ISO 8601 datetime when user joined session |
+| **permissions** | `string[]` | optional | User permissions in this session |
+| **metadata** | `Record` | optional | Additional user state metadata |
+
+---
+
+## CRDTMergeResult
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **state** | `object \| object \| object \| object \| object` | ✅ | Merged CRDT state |
+| **conflicts** | `object[]` | optional | Conflicts encountered during merge |
+
+---
+
+## CRDTState
+
+---
+
+## CRDTType
+
+### Allowed Values
+
+* `lww-register`
+* `g-counter`
+* `pn-counter`
+* `g-set`
+* `or-set`
+* `lww-map`
+* `text`
+* `tree`
+* `json`
+
+---
+
+## CollaborationMode
+
+### Allowed Values
+
+* `ot`
+* `crdt`
+* `lock`
+* `hybrid`
+
+---
+
+## CollaborationSession
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **sessionId** | `string` | ✅ | Session identifier |
+| **documentId** | `string` | ✅ | Document identifier |
+| **config** | `object` | ✅ | Session configuration |
+| **users** | `object[]` | ✅ | Active users |
+| **cursors** | `object[]` | ✅ | Active cursors |
+| **version** | `integer` | ✅ | Current document version |
+| **operations** | `object \| object[]` | optional | Recent operations |
+| **createdAt** | `string` | ✅ | ISO 8601 datetime when session was created |
+| **lastActivity** | `string` | ✅ | ISO 8601 datetime of last activity |
+| **status** | `Enum<'active' \| 'idle' \| 'ended'>` | ✅ | Session status |
+
+---
+
+## CollaborationSessionConfig
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **mode** | `Enum<'ot' \| 'crdt' \| 'lock' \| 'hybrid'>` | ✅ | Collaboration mode to use |
+| **enableCursorSharing** | `boolean` | optional | Enable cursor sharing |
+| **enablePresence** | `boolean` | optional | Enable presence tracking |
+| **enableAwareness** | `boolean` | optional | Enable awareness state |
+| **maxUsers** | `integer` | optional | Maximum concurrent users |
+| **idleTimeout** | `integer` | optional | Idle timeout in milliseconds |
+| **conflictResolution** | `Enum<'ot' \| 'crdt' \| 'manual'>` | optional | Conflict resolution strategy |
+| **persistence** | `boolean` | optional | Enable operation persistence |
+| **snapshot** | `object` | optional | Snapshot configuration |
+
+---
+
+## CollaborativeCursor
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **userId** | `string` | ✅ | User identifier |
+| **sessionId** | `string` | ✅ | Session identifier |
+| **documentId** | `string` | ✅ | Document identifier |
+| **userName** | `string` | ✅ | Display name of user |
+| **position** | `object` | ✅ | Current cursor position |
+| **selection** | `object` | optional | Current text selection |
+| **style** | `object` | ✅ | Visual style for this cursor |
+| **isTyping** | `boolean` | optional | Whether user is currently typing |
+| **lastUpdate** | `string` | ✅ | ISO 8601 datetime of last cursor update |
+| **metadata** | `Record` | optional | Additional cursor metadata |
+
+---
+
+## CounterOperation
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **replicaId** | `string` | ✅ | Replica identifier |
+| **delta** | `integer` | ✅ | Change amount (positive for increment, negative for decrement) |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime of operation |
+
+---
+
+## CursorColorPreset
+
+### Allowed Values
+
+* `blue`
+* `green`
+* `red`
+* `yellow`
+* `purple`
+* `orange`
+* `pink`
+* `teal`
+* `indigo`
+* `cyan`
+
+---
+
+## CursorSelection
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **anchor** | `object` | ✅ | Selection anchor (start point) |
+| **focus** | `object` | ✅ | Selection focus (end point) |
+| **direction** | `Enum<'forward' \| 'backward'>` | optional | Selection direction |
+
+---
+
+## CursorStyle
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **color** | `Enum<'blue' \| 'green' \| 'red' \| 'yellow' \| 'purple' \| 'orange' \| 'pink' \| 'teal' \| 'indigo' \| 'cyan'> \| string` | ✅ | Cursor color (preset or custom hex) |
+| **opacity** | `number` | optional | Cursor opacity (0-1) |
+| **label** | `string` | optional | Label to display with cursor (usually username) |
+| **showLabel** | `boolean` | optional | Whether to show label |
+| **pulseOnUpdate** | `boolean` | optional | Whether to pulse when cursor moves |
+
+---
+
+## CursorUpdate
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **position** | `object` | optional | Updated cursor position |
+| **selection** | `object` | optional | Updated selection |
+| **isTyping** | `boolean` | optional | Updated typing state |
+| **metadata** | `Record` | optional | Updated metadata |
+
+---
+
+## GCounter
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **type** | `string` | ✅ | |
+| **counts** | `Record` | ✅ | Map of replica ID to count |
+
+---
+
+## LWWRegister
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **type** | `string` | ✅ | |
+| **value** | `any` | optional | Current register value |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime of last write |
+| **replicaId** | `string` | ✅ | ID of replica that performed last write |
+| **vectorClock** | `object` | optional | Optional vector clock for causality tracking |
+
+---
+
+## ORSet
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **type** | `string` | ✅ | |
+| **elements** | `object[]` | ✅ | Set elements with metadata |
+
+---
+
+## ORSetElement
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **value** | `any` | optional | Element value |
+| **timestamp** | `string` | ✅ | Addition timestamp |
+| **replicaId** | `string` | ✅ | Replica that added the element |
+| **uid** | `string` | ✅ | Unique identifier for this addition |
+| **removed** | `boolean` | optional | Whether element has been removed |
+
+---
+
+## OTComponent
+
+---
+
+## OTOperation
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **operationId** | `string` | ✅ | Unique operation identifier |
+| **documentId** | `string` | ✅ | Document identifier |
+| **userId** | `string` | ✅ | User who created the operation |
+| **sessionId** | `string` | ✅ | Session identifier |
+| **components** | `object \| object \| object[]` | ✅ | Operation components |
+| **baseVersion** | `integer` | ✅ | Document version this operation is based on |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime when operation was created |
+| **metadata** | `Record` | optional | Additional operation metadata |
+
+---
+
+## OTOperationType
+
+### Allowed Values
+
+* `insert`
+* `delete`
+* `retain`
+
+---
+
+## OTTransformResult
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **operation** | `object` | ✅ | Transformed operation |
+| **transformed** | `boolean` | ✅ | Whether transformation was applied |
+| **conflicts** | `string[]` | optional | Conflict descriptions if any |
+
+---
+
+## PNCounter
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **type** | `string` | ✅ | |
+| **positive** | `Record` | ✅ | Positive increments per replica |
+| **negative** | `Record` | ✅ | Negative increments per replica |
+
+---
+
+## TextCRDTOperation
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **operationId** | `string` | ✅ | Unique operation identifier |
+| **replicaId** | `string` | ✅ | Replica identifier |
+| **position** | `integer` | ✅ | Position in document |
+| **insert** | `string` | optional | Text to insert |
+| **delete** | `integer` | optional | Number of characters to delete |
+| **timestamp** | `string` | ✅ | ISO 8601 datetime of operation |
+| **lamportTimestamp** | `integer` | ✅ | Lamport timestamp for ordering |
+
+---
+
+## TextCRDTState
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **type** | `string` | ✅ | |
+| **documentId** | `string` | ✅ | Document identifier |
+| **content** | `string` | ✅ | Current text content |
+| **operations** | `object[]` | ✅ | History of operations |
+| **lamportClock** | `integer` | ✅ | Current Lamport clock value |
+| **vectorClock** | `object` | ✅ | Vector clock for causality |
+
+---
+
+## UserActivityStatus
+
+### Allowed Values
+
+* `active`
+* `idle`
+* `viewing`
+* `disconnected`
+
+---
+
+## VectorClock
+
+### Properties
+
+| Property | Type | Required | Description |
+| :--- | :--- | :--- | :--- |
+| **clock** | `Record` | ✅ | Map of replica ID to logical timestamp |
+
diff --git a/content/docs/references/system/index.mdx b/content/docs/references/system/index.mdx
index 4883942a9..9fcdb2f8f 100644
--- a/content/docs/references/system/index.mdx
+++ b/content/docs/references/system/index.mdx
@@ -9,6 +9,7 @@ This section contains all protocol schemas for the system layer of ObjectStack.
+
diff --git a/content/docs/references/system/meta.json b/content/docs/references/system/meta.json
index 491685715..d4a87c4ba 100644
--- a/content/docs/references/system/meta.json
+++ b/content/docs/references/system/meta.json
@@ -2,6 +2,7 @@
"title": "System Protocol",
"pages": [
"audit",
+ "collaboration",
"context",
"data-engine",
"datasource",
diff --git a/packages/spec/json-schema/api/AckMessage.json b/packages/spec/json-schema/api/AckMessage.json
new file mode 100644
index 000000000..35e184a08
--- /dev/null
+++ b/packages/spec/json-schema/api/AckMessage.json
@@ -0,0 +1,46 @@
+{
+ "$ref": "#/definitions/AckMessage",
+ "definitions": {
+ "AckMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "ack"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "ackMessageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "ID of the message being acknowledged"
+ },
+ "success": {
+ "type": "boolean",
+ "description": "Whether the operation was successful"
+ },
+ "error": {
+ "type": "string",
+ "description": "Error message if operation failed"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "ackMessageId",
+ "success"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/CursorMessage.json b/packages/spec/json-schema/api/CursorMessage.json
new file mode 100644
index 000000000..41e76989f
--- /dev/null
+++ b/packages/spec/json-schema/api/CursorMessage.json
@@ -0,0 +1,139 @@
+{
+ "$ref": "#/definitions/CursorMessage",
+ "definitions": {
+ "CursorMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "cursor"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "cursor": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier being edited"
+ },
+ "position": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Line number (0-indexed)"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Column number (0-indexed)"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Cursor position in document"
+ },
+ "selection": {
+ "type": "object",
+ "properties": {
+ "start": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false
+ },
+ "end": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "start",
+ "end"
+ ],
+ "additionalProperties": false,
+ "description": "Selection range (if text is selected)"
+ },
+ "color": {
+ "type": "string",
+ "description": "Cursor color for visual representation"
+ },
+ "userName": {
+ "type": "string",
+ "description": "Display name of user"
+ },
+ "lastUpdate": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last cursor update"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "documentId",
+ "lastUpdate"
+ ],
+ "additionalProperties": false,
+ "description": "Cursor position"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "cursor"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/CursorPosition.json b/packages/spec/json-schema/api/CursorPosition.json
new file mode 100644
index 000000000..e53de95f5
--- /dev/null
+++ b/packages/spec/json-schema/api/CursorPosition.json
@@ -0,0 +1,112 @@
+{
+ "$ref": "#/definitions/CursorPosition",
+ "definitions": {
+ "CursorPosition": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier being edited"
+ },
+ "position": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Line number (0-indexed)"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Column number (0-indexed)"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Cursor position in document"
+ },
+ "selection": {
+ "type": "object",
+ "properties": {
+ "start": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false
+ },
+ "end": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "start",
+ "end"
+ ],
+ "additionalProperties": false,
+ "description": "Selection range (if text is selected)"
+ },
+ "color": {
+ "type": "string",
+ "description": "Cursor color for visual representation"
+ },
+ "userName": {
+ "type": "string",
+ "description": "Display name of user"
+ },
+ "lastUpdate": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last cursor update"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "documentId",
+ "lastUpdate"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/DocumentState.json b/packages/spec/json-schema/api/DocumentState.json
new file mode 100644
index 000000000..72991707e
--- /dev/null
+++ b/packages/spec/json-schema/api/DocumentState.json
@@ -0,0 +1,49 @@
+{
+ "$ref": "#/definitions/DocumentState",
+ "definitions": {
+ "DocumentState": {
+ "type": "object",
+ "properties": {
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "version": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Current document version"
+ },
+ "content": {
+ "type": "string",
+ "description": "Current document content"
+ },
+ "lastModified": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last modification"
+ },
+ "activeSessions": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "description": "Active editing session IDs"
+ },
+ "checksum": {
+ "type": "string",
+ "description": "Content checksum for integrity verification"
+ }
+ },
+ "required": [
+ "documentId",
+ "version",
+ "content",
+ "lastModified",
+ "activeSessions"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/EditMessage.json b/packages/spec/json-schema/api/EditMessage.json
new file mode 100644
index 000000000..473001658
--- /dev/null
+++ b/packages/spec/json-schema/api/EditMessage.json
@@ -0,0 +1,135 @@
+{
+ "$ref": "#/definitions/EditMessage",
+ "definitions": {
+ "EditMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "edit"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "operation": {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "userId": {
+ "type": "string",
+ "description": "User who performed the edit"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "insert",
+ "delete",
+ "replace"
+ ],
+ "description": "Type of edit operation"
+ },
+ "position": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Line number (0-indexed)"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Column number (0-indexed)"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Starting position of the operation"
+ },
+ "endPosition": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Ending position (for delete/replace operations)"
+ },
+ "content": {
+ "type": "string",
+ "description": "Content to insert/replace"
+ },
+ "version": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Document version before this operation"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when operation was created"
+ },
+ "baseOperationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Previous operation ID this builds upon (for OT)"
+ }
+ },
+ "required": [
+ "operationId",
+ "documentId",
+ "userId",
+ "sessionId",
+ "type",
+ "position",
+ "version",
+ "timestamp"
+ ],
+ "additionalProperties": false,
+ "description": "Edit operation"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "operation"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/EditOperation.json b/packages/spec/json-schema/api/EditOperation.json
new file mode 100644
index 000000000..d3856d757
--- /dev/null
+++ b/packages/spec/json-schema/api/EditOperation.json
@@ -0,0 +1,108 @@
+{
+ "$ref": "#/definitions/EditOperation",
+ "definitions": {
+ "EditOperation": {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "userId": {
+ "type": "string",
+ "description": "User who performed the edit"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "insert",
+ "delete",
+ "replace"
+ ],
+ "description": "Type of edit operation"
+ },
+ "position": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Line number (0-indexed)"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Column number (0-indexed)"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Starting position of the operation"
+ },
+ "endPosition": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Ending position (for delete/replace operations)"
+ },
+ "content": {
+ "type": "string",
+ "description": "Content to insert/replace"
+ },
+ "version": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Document version before this operation"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when operation was created"
+ },
+ "baseOperationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Previous operation ID this builds upon (for OT)"
+ }
+ },
+ "required": [
+ "operationId",
+ "documentId",
+ "userId",
+ "sessionId",
+ "type",
+ "position",
+ "version",
+ "timestamp"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/EditOperationType.json b/packages/spec/json-schema/api/EditOperationType.json
new file mode 100644
index 000000000..2019f6972
--- /dev/null
+++ b/packages/spec/json-schema/api/EditOperationType.json
@@ -0,0 +1,14 @@
+{
+ "$ref": "#/definitions/EditOperationType",
+ "definitions": {
+ "EditOperationType": {
+ "type": "string",
+ "enum": [
+ "insert",
+ "delete",
+ "replace"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/ErrorMessage.json b/packages/spec/json-schema/api/ErrorMessage.json
new file mode 100644
index 000000000..f91ac4703
--- /dev/null
+++ b/packages/spec/json-schema/api/ErrorMessage.json
@@ -0,0 +1,44 @@
+{
+ "$ref": "#/definitions/ErrorMessage",
+ "definitions": {
+ "ErrorMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "error"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "code": {
+ "type": "string",
+ "description": "Error code"
+ },
+ "message": {
+ "type": "string",
+ "description": "Error message"
+ },
+ "details": {
+ "description": "Additional error details"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/EventFilter.json b/packages/spec/json-schema/api/EventFilter.json
new file mode 100644
index 000000000..ca8e2b8d7
--- /dev/null
+++ b/packages/spec/json-schema/api/EventFilter.json
@@ -0,0 +1,65 @@
+{
+ "$ref": "#/definitions/EventFilter",
+ "definitions": {
+ "EventFilter": {
+ "type": "object",
+ "properties": {
+ "conditions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field path to filter on (supports dot notation, e.g., \"user.email\")"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "eq",
+ "ne",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "in",
+ "nin",
+ "contains",
+ "startsWith",
+ "endsWith",
+ "exists",
+ "regex"
+ ],
+ "description": "Comparison operator"
+ },
+ "value": {
+ "description": "Value to compare against (not needed for \"exists\" operator)"
+ }
+ },
+ "required": [
+ "field",
+ "operator"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Array of filter conditions"
+ },
+ "and": {
+ "type": "array",
+ "items": {},
+ "description": "AND logical combination of filters"
+ },
+ "or": {
+ "type": "array",
+ "items": {},
+ "description": "OR logical combination of filters"
+ },
+ "not": {
+ "description": "NOT logical negation of filter"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/EventFilterCondition.json b/packages/spec/json-schema/api/EventFilterCondition.json
new file mode 100644
index 000000000..636c512dd
--- /dev/null
+++ b/packages/spec/json-schema/api/EventFilterCondition.json
@@ -0,0 +1,42 @@
+{
+ "$ref": "#/definitions/EventFilterCondition",
+ "definitions": {
+ "EventFilterCondition": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field path to filter on (supports dot notation, e.g., \"user.email\")"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "eq",
+ "ne",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "in",
+ "nin",
+ "contains",
+ "startsWith",
+ "endsWith",
+ "exists",
+ "regex"
+ ],
+ "description": "Comparison operator"
+ },
+ "value": {
+ "description": "Value to compare against (not needed for \"exists\" operator)"
+ }
+ },
+ "required": [
+ "field",
+ "operator"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/EventMessage.json b/packages/spec/json-schema/api/EventMessage.json
new file mode 100644
index 000000000..4fcb8700d
--- /dev/null
+++ b/packages/spec/json-schema/api/EventMessage.json
@@ -0,0 +1,55 @@
+{
+ "$ref": "#/definitions/EventMessage",
+ "definitions": {
+ "EventMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "event"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "subscriptionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Subscription ID this event belongs to"
+ },
+ "eventName": {
+ "type": "string",
+ "minLength": 3,
+ "pattern": "^[a-z][a-z0-9_.]*$",
+ "description": "Event name"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object name the event relates to"
+ },
+ "payload": {
+ "description": "Event payload data"
+ },
+ "userId": {
+ "type": "string",
+ "description": "User who triggered the event"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "subscriptionId",
+ "eventName"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/EventPattern.json b/packages/spec/json-schema/api/EventPattern.json
new file mode 100644
index 000000000..bfb7b203a
--- /dev/null
+++ b/packages/spec/json-schema/api/EventPattern.json
@@ -0,0 +1,12 @@
+{
+ "$ref": "#/definitions/EventPattern",
+ "definitions": {
+ "EventPattern": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[a-z*][a-z0-9_.*]*$",
+ "description": "Event pattern (supports wildcards like \"record.*\" or \"*.created\")"
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/EventSubscription.json b/packages/spec/json-schema/api/EventSubscription.json
new file mode 100644
index 000000000..1b4336fb3
--- /dev/null
+++ b/packages/spec/json-schema/api/EventSubscription.json
@@ -0,0 +1,105 @@
+{
+ "$ref": "#/definitions/EventSubscription",
+ "definitions": {
+ "EventSubscription": {
+ "type": "object",
+ "properties": {
+ "subscriptionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique subscription identifier"
+ },
+ "events": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[a-z*][a-z0-9_.*]*$",
+ "description": "Event pattern (supports wildcards like \"record.*\" or \"*.created\")"
+ },
+ "description": "Event patterns to subscribe to (supports wildcards, e.g., \"record.*\", \"user.created\")"
+ },
+ "objects": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Object names to filter events by (e.g., [\"account\", \"contact\"])"
+ },
+ "filters": {
+ "type": "object",
+ "properties": {
+ "conditions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field path to filter on (supports dot notation, e.g., \"user.email\")"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "eq",
+ "ne",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "in",
+ "nin",
+ "contains",
+ "startsWith",
+ "endsWith",
+ "exists",
+ "regex"
+ ],
+ "description": "Comparison operator"
+ },
+ "value": {
+ "description": "Value to compare against (not needed for \"exists\" operator)"
+ }
+ },
+ "required": [
+ "field",
+ "operator"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Array of filter conditions"
+ },
+ "and": {
+ "type": "array",
+ "items": {},
+ "description": "AND logical combination of filters"
+ },
+ "or": {
+ "type": "array",
+ "items": {},
+ "description": "OR logical combination of filters"
+ },
+ "not": {
+ "description": "NOT logical negation of filter"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Advanced filter conditions for event payloads"
+ },
+ "channels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Channel names for scoped subscriptions"
+ }
+ },
+ "required": [
+ "subscriptionId",
+ "events"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/FilterOperator.json b/packages/spec/json-schema/api/FilterOperator.json
new file mode 100644
index 000000000..a9c7a7d7b
--- /dev/null
+++ b/packages/spec/json-schema/api/FilterOperator.json
@@ -0,0 +1,24 @@
+{
+ "$ref": "#/definitions/FilterOperator",
+ "definitions": {
+ "FilterOperator": {
+ "type": "string",
+ "enum": [
+ "eq",
+ "ne",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "in",
+ "nin",
+ "contains",
+ "startsWith",
+ "endsWith",
+ "exists",
+ "regex"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/PingMessage.json b/packages/spec/json-schema/api/PingMessage.json
new file mode 100644
index 000000000..b6173c4a3
--- /dev/null
+++ b/packages/spec/json-schema/api/PingMessage.json
@@ -0,0 +1,31 @@
+{
+ "$ref": "#/definitions/PingMessage",
+ "definitions": {
+ "PingMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "ping"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/PongMessage.json b/packages/spec/json-schema/api/PongMessage.json
new file mode 100644
index 000000000..572edd427
--- /dev/null
+++ b/packages/spec/json-schema/api/PongMessage.json
@@ -0,0 +1,36 @@
+{
+ "$ref": "#/definitions/PongMessage",
+ "definitions": {
+ "PongMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "pong"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "pingMessageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "ID of ping message being responded to"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/PresenceMessage.json b/packages/spec/json-schema/api/PresenceMessage.json
new file mode 100644
index 000000000..5dd125edb
--- /dev/null
+++ b/packages/spec/json-schema/api/PresenceMessage.json
@@ -0,0 +1,92 @@
+{
+ "$ref": "#/definitions/PresenceMessage",
+ "definitions": {
+ "PresenceMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "presence"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "presence": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique session identifier"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "online",
+ "away",
+ "busy",
+ "offline"
+ ],
+ "description": "Current presence status"
+ },
+ "lastSeen": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last activity"
+ },
+ "currentLocation": {
+ "type": "string",
+ "description": "Current page/route user is viewing"
+ },
+ "device": {
+ "type": "string",
+ "enum": [
+ "desktop",
+ "mobile",
+ "tablet",
+ "other"
+ ],
+ "description": "Device type"
+ },
+ "customStatus": {
+ "type": "string",
+ "description": "Custom user status message"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional custom presence data"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "status",
+ "lastSeen"
+ ],
+ "additionalProperties": false,
+ "description": "Presence state"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "presence"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/PresenceState.json b/packages/spec/json-schema/api/PresenceState.json
new file mode 100644
index 000000000..e3144d289
--- /dev/null
+++ b/packages/spec/json-schema/api/PresenceState.json
@@ -0,0 +1,65 @@
+{
+ "$ref": "#/definitions/PresenceState",
+ "definitions": {
+ "PresenceState": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique session identifier"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "online",
+ "away",
+ "busy",
+ "offline"
+ ],
+ "description": "Current presence status"
+ },
+ "lastSeen": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last activity"
+ },
+ "currentLocation": {
+ "type": "string",
+ "description": "Current page/route user is viewing"
+ },
+ "device": {
+ "type": "string",
+ "enum": [
+ "desktop",
+ "mobile",
+ "tablet",
+ "other"
+ ],
+ "description": "Device type"
+ },
+ "customStatus": {
+ "type": "string",
+ "description": "Custom user status message"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional custom presence data"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "status",
+ "lastSeen"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/PresenceUpdate.json b/packages/spec/json-schema/api/PresenceUpdate.json
new file mode 100644
index 000000000..123b6bf71
--- /dev/null
+++ b/packages/spec/json-schema/api/PresenceUpdate.json
@@ -0,0 +1,35 @@
+{
+ "$ref": "#/definitions/PresenceUpdate",
+ "definitions": {
+ "PresenceUpdate": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "online",
+ "away",
+ "busy",
+ "offline"
+ ],
+ "description": "Updated presence status"
+ },
+ "currentLocation": {
+ "type": "string",
+ "description": "Updated current location"
+ },
+ "customStatus": {
+ "type": "string",
+ "description": "Updated custom status message"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Updated metadata"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/SubscribeMessage.json b/packages/spec/json-schema/api/SubscribeMessage.json
new file mode 100644
index 000000000..6ca6fcded
--- /dev/null
+++ b/packages/spec/json-schema/api/SubscribeMessage.json
@@ -0,0 +1,132 @@
+{
+ "$ref": "#/definitions/SubscribeMessage",
+ "definitions": {
+ "SubscribeMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "subscribe"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "subscription": {
+ "type": "object",
+ "properties": {
+ "subscriptionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique subscription identifier"
+ },
+ "events": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[a-z*][a-z0-9_.*]*$",
+ "description": "Event pattern (supports wildcards like \"record.*\" or \"*.created\")"
+ },
+ "description": "Event patterns to subscribe to (supports wildcards, e.g., \"record.*\", \"user.created\")"
+ },
+ "objects": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Object names to filter events by (e.g., [\"account\", \"contact\"])"
+ },
+ "filters": {
+ "type": "object",
+ "properties": {
+ "conditions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field path to filter on (supports dot notation, e.g., \"user.email\")"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "eq",
+ "ne",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "in",
+ "nin",
+ "contains",
+ "startsWith",
+ "endsWith",
+ "exists",
+ "regex"
+ ],
+ "description": "Comparison operator"
+ },
+ "value": {
+ "description": "Value to compare against (not needed for \"exists\" operator)"
+ }
+ },
+ "required": [
+ "field",
+ "operator"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Array of filter conditions"
+ },
+ "and": {
+ "type": "array",
+ "items": {},
+ "description": "AND logical combination of filters"
+ },
+ "or": {
+ "type": "array",
+ "items": {},
+ "description": "OR logical combination of filters"
+ },
+ "not": {
+ "description": "NOT logical negation of filter"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Advanced filter conditions for event payloads"
+ },
+ "channels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Channel names for scoped subscriptions"
+ }
+ },
+ "required": [
+ "subscriptionId",
+ "events"
+ ],
+ "additionalProperties": false,
+ "description": "Subscription configuration"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "subscription"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/UnsubscribeMessage.json b/packages/spec/json-schema/api/UnsubscribeMessage.json
new file mode 100644
index 000000000..87d4d1dea
--- /dev/null
+++ b/packages/spec/json-schema/api/UnsubscribeMessage.json
@@ -0,0 +1,47 @@
+{
+ "$ref": "#/definitions/UnsubscribeMessage",
+ "definitions": {
+ "UnsubscribeMessage": {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "unsubscribe"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "request": {
+ "type": "object",
+ "properties": {
+ "subscriptionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Subscription ID to unsubscribe from"
+ }
+ },
+ "required": [
+ "subscriptionId"
+ ],
+ "additionalProperties": false,
+ "description": "Unsubscribe request"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "request"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/UnsubscribeRequest.json b/packages/spec/json-schema/api/UnsubscribeRequest.json
new file mode 100644
index 000000000..8bb1c3493
--- /dev/null
+++ b/packages/spec/json-schema/api/UnsubscribeRequest.json
@@ -0,0 +1,20 @@
+{
+ "$ref": "#/definitions/UnsubscribeRequest",
+ "definitions": {
+ "UnsubscribeRequest": {
+ "type": "object",
+ "properties": {
+ "subscriptionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Subscription ID to unsubscribe from"
+ }
+ },
+ "required": [
+ "subscriptionId"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/WebSocketConfig.json b/packages/spec/json-schema/api/WebSocketConfig.json
new file mode 100644
index 000000000..cf7150cf6
--- /dev/null
+++ b/packages/spec/json-schema/api/WebSocketConfig.json
@@ -0,0 +1,63 @@
+{
+ "$ref": "#/definitions/WebSocketConfig",
+ "definitions": {
+ "WebSocketConfig": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "description": "WebSocket server URL"
+ },
+ "protocols": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "WebSocket sub-protocols"
+ },
+ "reconnect": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable automatic reconnection"
+ },
+ "reconnectInterval": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "default": 1000,
+ "description": "Reconnection interval in milliseconds"
+ },
+ "maxReconnectAttempts": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "default": 5,
+ "description": "Maximum reconnection attempts"
+ },
+ "pingInterval": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "default": 30000,
+ "description": "Ping interval in milliseconds"
+ },
+ "timeout": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "default": 5000,
+ "description": "Message timeout in milliseconds"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "description": "Custom headers for WebSocket handshake"
+ }
+ },
+ "required": [
+ "url"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/WebSocketMessage.json b/packages/spec/json-schema/api/WebSocketMessage.json
new file mode 100644
index 000000000..0cae16371
--- /dev/null
+++ b/packages/spec/json-schema/api/WebSocketMessage.json
@@ -0,0 +1,707 @@
+{
+ "$ref": "#/definitions/WebSocketMessage",
+ "definitions": {
+ "WebSocketMessage": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "subscribe"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "subscription": {
+ "type": "object",
+ "properties": {
+ "subscriptionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique subscription identifier"
+ },
+ "events": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "pattern": "^[a-z*][a-z0-9_.*]*$",
+ "description": "Event pattern (supports wildcards like \"record.*\" or \"*.created\")"
+ },
+ "description": "Event patterns to subscribe to (supports wildcards, e.g., \"record.*\", \"user.created\")"
+ },
+ "objects": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Object names to filter events by (e.g., [\"account\", \"contact\"])"
+ },
+ "filters": {
+ "type": "object",
+ "properties": {
+ "conditions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string",
+ "description": "Field path to filter on (supports dot notation, e.g., \"user.email\")"
+ },
+ "operator": {
+ "type": "string",
+ "enum": [
+ "eq",
+ "ne",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "in",
+ "nin",
+ "contains",
+ "startsWith",
+ "endsWith",
+ "exists",
+ "regex"
+ ],
+ "description": "Comparison operator"
+ },
+ "value": {
+ "description": "Value to compare against (not needed for \"exists\" operator)"
+ }
+ },
+ "required": [
+ "field",
+ "operator"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Array of filter conditions"
+ },
+ "and": {
+ "type": "array",
+ "items": {},
+ "description": "AND logical combination of filters"
+ },
+ "or": {
+ "type": "array",
+ "items": {},
+ "description": "OR logical combination of filters"
+ },
+ "not": {
+ "description": "NOT logical negation of filter"
+ }
+ },
+ "additionalProperties": false,
+ "description": "Advanced filter conditions for event payloads"
+ },
+ "channels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Channel names for scoped subscriptions"
+ }
+ },
+ "required": [
+ "subscriptionId",
+ "events"
+ ],
+ "additionalProperties": false,
+ "description": "Subscription configuration"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "subscription"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "unsubscribe"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "request": {
+ "type": "object",
+ "properties": {
+ "subscriptionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Subscription ID to unsubscribe from"
+ }
+ },
+ "required": [
+ "subscriptionId"
+ ],
+ "additionalProperties": false,
+ "description": "Unsubscribe request"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "request"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "event"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "subscriptionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Subscription ID this event belongs to"
+ },
+ "eventName": {
+ "type": "string",
+ "minLength": 3,
+ "pattern": "^[a-z][a-z0-9_.]*$",
+ "description": "Event name"
+ },
+ "object": {
+ "type": "string",
+ "description": "Object name the event relates to"
+ },
+ "payload": {
+ "description": "Event payload data"
+ },
+ "userId": {
+ "type": "string",
+ "description": "User who triggered the event"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "subscriptionId",
+ "eventName"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "presence"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "presence": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique session identifier"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "online",
+ "away",
+ "busy",
+ "offline"
+ ],
+ "description": "Current presence status"
+ },
+ "lastSeen": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last activity"
+ },
+ "currentLocation": {
+ "type": "string",
+ "description": "Current page/route user is viewing"
+ },
+ "device": {
+ "type": "string",
+ "enum": [
+ "desktop",
+ "mobile",
+ "tablet",
+ "other"
+ ],
+ "description": "Device type"
+ },
+ "customStatus": {
+ "type": "string",
+ "description": "Custom user status message"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional custom presence data"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "status",
+ "lastSeen"
+ ],
+ "additionalProperties": false,
+ "description": "Presence state"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "presence"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "cursor"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "cursor": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier being edited"
+ },
+ "position": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Line number (0-indexed)"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Column number (0-indexed)"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Cursor position in document"
+ },
+ "selection": {
+ "type": "object",
+ "properties": {
+ "start": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false
+ },
+ "end": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "required": [
+ "start",
+ "end"
+ ],
+ "additionalProperties": false,
+ "description": "Selection range (if text is selected)"
+ },
+ "color": {
+ "type": "string",
+ "description": "Cursor color for visual representation"
+ },
+ "userName": {
+ "type": "string",
+ "description": "Display name of user"
+ },
+ "lastUpdate": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last cursor update"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "documentId",
+ "lastUpdate"
+ ],
+ "additionalProperties": false,
+ "description": "Cursor position"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "cursor"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "edit"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "operation": {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "userId": {
+ "type": "string",
+ "description": "User who performed the edit"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "insert",
+ "delete",
+ "replace"
+ ],
+ "description": "Type of edit operation"
+ },
+ "position": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Line number (0-indexed)"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Column number (0-indexed)"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Starting position of the operation"
+ },
+ "endPosition": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Ending position (for delete/replace operations)"
+ },
+ "content": {
+ "type": "string",
+ "description": "Content to insert/replace"
+ },
+ "version": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Document version before this operation"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when operation was created"
+ },
+ "baseOperationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Previous operation ID this builds upon (for OT)"
+ }
+ },
+ "required": [
+ "operationId",
+ "documentId",
+ "userId",
+ "sessionId",
+ "type",
+ "position",
+ "version",
+ "timestamp"
+ ],
+ "additionalProperties": false,
+ "description": "Edit operation"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "operation"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "ack"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "ackMessageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "ID of the message being acknowledged"
+ },
+ "success": {
+ "type": "boolean",
+ "description": "Whether the operation was successful"
+ },
+ "error": {
+ "type": "string",
+ "description": "Error message if operation failed"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "ackMessageId",
+ "success"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "error"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "code": {
+ "type": "string",
+ "description": "Error code"
+ },
+ "message": {
+ "type": "string",
+ "description": "Error message"
+ },
+ "details": {
+ "description": "Additional error details"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp",
+ "code",
+ "message"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "ping"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "messageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique message identifier"
+ },
+ "type": {
+ "type": "string",
+ "const": "pong"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when message was sent"
+ },
+ "pingMessageId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "ID of ping message being responded to"
+ }
+ },
+ "required": [
+ "messageId",
+ "type",
+ "timestamp"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/WebSocketMessageType.json b/packages/spec/json-schema/api/WebSocketMessageType.json
new file mode 100644
index 000000000..9e0fe0785
--- /dev/null
+++ b/packages/spec/json-schema/api/WebSocketMessageType.json
@@ -0,0 +1,21 @@
+{
+ "$ref": "#/definitions/WebSocketMessageType",
+ "definitions": {
+ "WebSocketMessageType": {
+ "type": "string",
+ "enum": [
+ "subscribe",
+ "unsubscribe",
+ "event",
+ "ping",
+ "pong",
+ "ack",
+ "error",
+ "presence",
+ "cursor",
+ "edit"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/api/WebSocketPresenceStatus.json b/packages/spec/json-schema/api/WebSocketPresenceStatus.json
new file mode 100644
index 000000000..b35b753db
--- /dev/null
+++ b/packages/spec/json-schema/api/WebSocketPresenceStatus.json
@@ -0,0 +1,15 @@
+{
+ "$ref": "#/definitions/WebSocketPresenceStatus",
+ "definitions": {
+ "WebSocketPresenceStatus": {
+ "type": "string",
+ "enum": [
+ "online",
+ "away",
+ "busy",
+ "offline"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/AwarenessEvent.json b/packages/spec/json-schema/system/AwarenessEvent.json
new file mode 100644
index 000000000..6de707dbf
--- /dev/null
+++ b/packages/spec/json-schema/system/AwarenessEvent.json
@@ -0,0 +1,51 @@
+{
+ "$ref": "#/definitions/AwarenessEvent",
+ "definitions": {
+ "AwarenessEvent": {
+ "type": "object",
+ "properties": {
+ "eventId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Event identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "eventType": {
+ "type": "string",
+ "enum": [
+ "user.joined",
+ "user.left",
+ "user.updated",
+ "session.created",
+ "session.ended"
+ ],
+ "description": "Type of awareness event"
+ },
+ "userId": {
+ "type": "string",
+ "description": "User involved in event"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of event"
+ },
+ "payload": {
+ "description": "Event payload"
+ }
+ },
+ "required": [
+ "eventId",
+ "sessionId",
+ "eventType",
+ "timestamp"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/AwarenessSession.json b/packages/spec/json-schema/system/AwarenessSession.json
new file mode 100644
index 000000000..f3a3401a1
--- /dev/null
+++ b/packages/spec/json-schema/system/AwarenessSession.json
@@ -0,0 +1,117 @@
+{
+ "$ref": "#/definitions/AwarenessSession",
+ "definitions": {
+ "AwarenessSession": {
+ "type": "object",
+ "properties": {
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document ID this session is for"
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "userName": {
+ "type": "string",
+ "description": "Display name"
+ },
+ "userAvatar": {
+ "type": "string",
+ "description": "User avatar URL"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "active",
+ "idle",
+ "viewing",
+ "disconnected"
+ ],
+ "description": "Current activity status"
+ },
+ "currentDocument": {
+ "type": "string",
+ "description": "Document ID user is currently editing"
+ },
+ "currentView": {
+ "type": "string",
+ "description": "Current view/page user is on"
+ },
+ "lastActivity": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last activity"
+ },
+ "joinedAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when user joined session"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "User permissions in this session"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional user state metadata"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "userName",
+ "status",
+ "lastActivity",
+ "joinedAt"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Active users in session"
+ },
+ "startedAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when session started"
+ },
+ "lastUpdate": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last update"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Session metadata"
+ }
+ },
+ "required": [
+ "sessionId",
+ "users",
+ "startedAt",
+ "lastUpdate"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/AwarenessUpdate.json b/packages/spec/json-schema/system/AwarenessUpdate.json
new file mode 100644
index 000000000..cb06679fc
--- /dev/null
+++ b/packages/spec/json-schema/system/AwarenessUpdate.json
@@ -0,0 +1,35 @@
+{
+ "$ref": "#/definitions/AwarenessUpdate",
+ "definitions": {
+ "AwarenessUpdate": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "active",
+ "idle",
+ "viewing",
+ "disconnected"
+ ],
+ "description": "Updated status"
+ },
+ "currentDocument": {
+ "type": "string",
+ "description": "Updated current document"
+ },
+ "currentView": {
+ "type": "string",
+ "description": "Updated current view"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Updated metadata"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/AwarenessUserState.json b/packages/spec/json-schema/system/AwarenessUserState.json
new file mode 100644
index 000000000..0edcb1090
--- /dev/null
+++ b/packages/spec/json-schema/system/AwarenessUserState.json
@@ -0,0 +1,77 @@
+{
+ "$ref": "#/definitions/AwarenessUserState",
+ "definitions": {
+ "AwarenessUserState": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "userName": {
+ "type": "string",
+ "description": "Display name"
+ },
+ "userAvatar": {
+ "type": "string",
+ "description": "User avatar URL"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "active",
+ "idle",
+ "viewing",
+ "disconnected"
+ ],
+ "description": "Current activity status"
+ },
+ "currentDocument": {
+ "type": "string",
+ "description": "Document ID user is currently editing"
+ },
+ "currentView": {
+ "type": "string",
+ "description": "Current view/page user is on"
+ },
+ "lastActivity": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last activity"
+ },
+ "joinedAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when user joined session"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "User permissions in this session"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional user state metadata"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "userName",
+ "status",
+ "lastActivity",
+ "joinedAt"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CRDTMergeResult.json b/packages/spec/json-schema/system/CRDTMergeResult.json
new file mode 100644
index 000000000..84640616d
--- /dev/null
+++ b/packages/spec/json-schema/system/CRDTMergeResult.json
@@ -0,0 +1,295 @@
+{
+ "$ref": "#/definitions/CRDTMergeResult",
+ "definitions": {
+ "CRDTMergeResult": {
+ "type": "object",
+ "properties": {
+ "state": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "lww-register"
+ },
+ "value": {
+ "description": "Current register value"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last write"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "ID of replica that performed last write"
+ },
+ "vectorClock": {
+ "type": "object",
+ "properties": {
+ "clock": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to logical timestamp"
+ }
+ },
+ "required": [
+ "clock"
+ ],
+ "additionalProperties": false,
+ "description": "Optional vector clock for causality tracking"
+ }
+ },
+ "required": [
+ "type",
+ "timestamp",
+ "replicaId"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "g-counter"
+ },
+ "counts": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to count"
+ }
+ },
+ "required": [
+ "type",
+ "counts"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "pn-counter"
+ },
+ "positive": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Positive increments per replica"
+ },
+ "negative": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Negative increments per replica"
+ }
+ },
+ "required": [
+ "type",
+ "positive",
+ "negative"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "or-set"
+ },
+ "elements": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "description": "Element value"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Addition timestamp"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "Replica that added the element"
+ },
+ "uid": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique identifier for this addition"
+ },
+ "removed": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether element has been removed"
+ }
+ },
+ "required": [
+ "timestamp",
+ "replicaId",
+ "uid"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Set elements with metadata"
+ }
+ },
+ "required": [
+ "type",
+ "elements"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "text"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "content": {
+ "type": "string",
+ "description": "Current text content"
+ },
+ "operations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "Replica identifier"
+ },
+ "position": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Position in document"
+ },
+ "insert": {
+ "type": "string",
+ "description": "Text to insert"
+ },
+ "delete": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to delete"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of operation"
+ },
+ "lamportTimestamp": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Lamport timestamp for ordering"
+ }
+ },
+ "required": [
+ "operationId",
+ "replicaId",
+ "position",
+ "timestamp",
+ "lamportTimestamp"
+ ],
+ "additionalProperties": false
+ },
+ "description": "History of operations"
+ },
+ "lamportClock": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Current Lamport clock value"
+ },
+ "vectorClock": {
+ "type": "object",
+ "properties": {
+ "clock": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to logical timestamp"
+ }
+ },
+ "required": [
+ "clock"
+ ],
+ "additionalProperties": false,
+ "description": "Vector clock for causality"
+ }
+ },
+ "required": [
+ "type",
+ "documentId",
+ "content",
+ "operations",
+ "lamportClock",
+ "vectorClock"
+ ],
+ "additionalProperties": false
+ }
+ ],
+ "description": "Merged CRDT state"
+ },
+ "conflicts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "Conflict type"
+ },
+ "description": {
+ "type": "string",
+ "description": "Conflict description"
+ },
+ "resolved": {
+ "type": "boolean",
+ "description": "Whether conflict was automatically resolved"
+ }
+ },
+ "required": [
+ "type",
+ "description",
+ "resolved"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Conflicts encountered during merge"
+ }
+ },
+ "required": [
+ "state"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CRDTState.json b/packages/spec/json-schema/system/CRDTState.json
new file mode 100644
index 000000000..9354aa577
--- /dev/null
+++ b/packages/spec/json-schema/system/CRDTState.json
@@ -0,0 +1,258 @@
+{
+ "$ref": "#/definitions/CRDTState",
+ "definitions": {
+ "CRDTState": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "lww-register"
+ },
+ "value": {
+ "description": "Current register value"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last write"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "ID of replica that performed last write"
+ },
+ "vectorClock": {
+ "type": "object",
+ "properties": {
+ "clock": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to logical timestamp"
+ }
+ },
+ "required": [
+ "clock"
+ ],
+ "additionalProperties": false,
+ "description": "Optional vector clock for causality tracking"
+ }
+ },
+ "required": [
+ "type",
+ "timestamp",
+ "replicaId"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "g-counter"
+ },
+ "counts": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to count"
+ }
+ },
+ "required": [
+ "type",
+ "counts"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "pn-counter"
+ },
+ "positive": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Positive increments per replica"
+ },
+ "negative": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Negative increments per replica"
+ }
+ },
+ "required": [
+ "type",
+ "positive",
+ "negative"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "or-set"
+ },
+ "elements": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "description": "Element value"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Addition timestamp"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "Replica that added the element"
+ },
+ "uid": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique identifier for this addition"
+ },
+ "removed": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether element has been removed"
+ }
+ },
+ "required": [
+ "timestamp",
+ "replicaId",
+ "uid"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Set elements with metadata"
+ }
+ },
+ "required": [
+ "type",
+ "elements"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "text"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "content": {
+ "type": "string",
+ "description": "Current text content"
+ },
+ "operations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "Replica identifier"
+ },
+ "position": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Position in document"
+ },
+ "insert": {
+ "type": "string",
+ "description": "Text to insert"
+ },
+ "delete": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to delete"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of operation"
+ },
+ "lamportTimestamp": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Lamport timestamp for ordering"
+ }
+ },
+ "required": [
+ "operationId",
+ "replicaId",
+ "position",
+ "timestamp",
+ "lamportTimestamp"
+ ],
+ "additionalProperties": false
+ },
+ "description": "History of operations"
+ },
+ "lamportClock": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Current Lamport clock value"
+ },
+ "vectorClock": {
+ "type": "object",
+ "properties": {
+ "clock": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to logical timestamp"
+ }
+ },
+ "required": [
+ "clock"
+ ],
+ "additionalProperties": false,
+ "description": "Vector clock for causality"
+ }
+ },
+ "required": [
+ "type",
+ "documentId",
+ "content",
+ "operations",
+ "lamportClock",
+ "vectorClock"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CRDTType.json b/packages/spec/json-schema/system/CRDTType.json
new file mode 100644
index 000000000..36d88414e
--- /dev/null
+++ b/packages/spec/json-schema/system/CRDTType.json
@@ -0,0 +1,20 @@
+{
+ "$ref": "#/definitions/CRDTType",
+ "definitions": {
+ "CRDTType": {
+ "type": "string",
+ "enum": [
+ "lww-register",
+ "g-counter",
+ "pn-counter",
+ "g-set",
+ "or-set",
+ "lww-map",
+ "text",
+ "tree",
+ "json"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CollaborationMode.json b/packages/spec/json-schema/system/CollaborationMode.json
new file mode 100644
index 000000000..c5a3f3c6c
--- /dev/null
+++ b/packages/spec/json-schema/system/CollaborationMode.json
@@ -0,0 +1,15 @@
+{
+ "$ref": "#/definitions/CollaborationMode",
+ "definitions": {
+ "CollaborationMode": {
+ "type": "string",
+ "enum": [
+ "ot",
+ "crdt",
+ "lock",
+ "hybrid"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CollaborationSession.json b/packages/spec/json-schema/system/CollaborationSession.json
new file mode 100644
index 000000000..5405ce1a4
--- /dev/null
+++ b/packages/spec/json-schema/system/CollaborationSession.json
@@ -0,0 +1,575 @@
+{
+ "$ref": "#/definitions/CollaborationSession",
+ "definitions": {
+ "CollaborationSession": {
+ "type": "object",
+ "properties": {
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "config": {
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": [
+ "ot",
+ "crdt",
+ "lock",
+ "hybrid"
+ ],
+ "description": "Collaboration mode to use"
+ },
+ "enableCursorSharing": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable cursor sharing"
+ },
+ "enablePresence": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable presence tracking"
+ },
+ "enableAwareness": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable awareness state"
+ },
+ "maxUsers": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Maximum concurrent users"
+ },
+ "idleTimeout": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "default": 300000,
+ "description": "Idle timeout in milliseconds"
+ },
+ "conflictResolution": {
+ "type": "string",
+ "enum": [
+ "ot",
+ "crdt",
+ "manual"
+ ],
+ "default": "ot",
+ "description": "Conflict resolution strategy"
+ },
+ "persistence": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable operation persistence"
+ },
+ "snapshot": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "description": "Enable periodic snapshots"
+ },
+ "interval": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Snapshot interval in milliseconds"
+ }
+ },
+ "required": [
+ "enabled",
+ "interval"
+ ],
+ "additionalProperties": false,
+ "description": "Snapshot configuration"
+ }
+ },
+ "required": [
+ "mode"
+ ],
+ "additionalProperties": false,
+ "description": "Session configuration"
+ },
+ "users": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "userName": {
+ "type": "string",
+ "description": "Display name"
+ },
+ "userAvatar": {
+ "type": "string",
+ "description": "User avatar URL"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "active",
+ "idle",
+ "viewing",
+ "disconnected"
+ ],
+ "description": "Current activity status"
+ },
+ "currentDocument": {
+ "type": "string",
+ "description": "Document ID user is currently editing"
+ },
+ "currentView": {
+ "type": "string",
+ "description": "Current view/page user is on"
+ },
+ "lastActivity": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last activity"
+ },
+ "joinedAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when user joined session"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "User permissions in this session"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional user state metadata"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "userName",
+ "status",
+ "lastActivity",
+ "joinedAt"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Active users"
+ },
+ "cursors": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "userName": {
+ "type": "string",
+ "description": "Display name of user"
+ },
+ "position": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Cursor line number (0-indexed)"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Cursor column number (0-indexed)"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Current cursor position"
+ },
+ "selection": {
+ "type": "object",
+ "properties": {
+ "anchor": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Anchor line number"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Anchor column number"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Selection anchor (start point)"
+ },
+ "focus": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Focus line number"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Focus column number"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Selection focus (end point)"
+ },
+ "direction": {
+ "type": "string",
+ "enum": [
+ "forward",
+ "backward"
+ ],
+ "description": "Selection direction"
+ }
+ },
+ "required": [
+ "anchor",
+ "focus"
+ ],
+ "additionalProperties": false,
+ "description": "Current text selection"
+ },
+ "style": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "blue",
+ "green",
+ "red",
+ "yellow",
+ "purple",
+ "orange",
+ "pink",
+ "teal",
+ "indigo",
+ "cyan"
+ ]
+ },
+ {
+ "type": "string"
+ }
+ ],
+ "description": "Cursor color (preset or custom hex)"
+ },
+ "opacity": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1,
+ "default": 1,
+ "description": "Cursor opacity (0-1)"
+ },
+ "label": {
+ "type": "string",
+ "description": "Label to display with cursor (usually username)"
+ },
+ "showLabel": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether to show label"
+ },
+ "pulseOnUpdate": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether to pulse when cursor moves"
+ }
+ },
+ "required": [
+ "color"
+ ],
+ "additionalProperties": false,
+ "description": "Visual style for this cursor"
+ },
+ "isTyping": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether user is currently typing"
+ },
+ "lastUpdate": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last cursor update"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional cursor metadata"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "documentId",
+ "userName",
+ "position",
+ "style",
+ "lastUpdate"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Active cursors"
+ },
+ "version": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Current document version"
+ },
+ "operations": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "userId": {
+ "type": "string",
+ "description": "User who created the operation"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "components": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "insert"
+ },
+ "text": {
+ "type": "string",
+ "description": "Text to insert"
+ },
+ "attributes": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Text formatting attributes (e.g., bold, italic)"
+ }
+ },
+ "required": [
+ "type",
+ "text"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "delete"
+ },
+ "count": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to delete"
+ }
+ },
+ "required": [
+ "type",
+ "count"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "retain"
+ },
+ "count": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to retain"
+ },
+ "attributes": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Attribute changes to apply"
+ }
+ },
+ "required": [
+ "type",
+ "count"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Operation components"
+ },
+ "baseVersion": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Document version this operation is based on"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when operation was created"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional operation metadata"
+ }
+ },
+ "required": [
+ "operationId",
+ "documentId",
+ "userId",
+ "sessionId",
+ "components",
+ "baseVersion",
+ "timestamp"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "Replica identifier"
+ },
+ "position": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Position in document"
+ },
+ "insert": {
+ "type": "string",
+ "description": "Text to insert"
+ },
+ "delete": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to delete"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of operation"
+ },
+ "lamportTimestamp": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Lamport timestamp for ordering"
+ }
+ },
+ "required": [
+ "operationId",
+ "replicaId",
+ "position",
+ "timestamp",
+ "lamportTimestamp"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Recent operations"
+ },
+ "createdAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when session was created"
+ },
+ "lastActivity": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last activity"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "active",
+ "idle",
+ "ended"
+ ],
+ "description": "Session status"
+ }
+ },
+ "required": [
+ "sessionId",
+ "documentId",
+ "config",
+ "users",
+ "cursors",
+ "version",
+ "createdAt",
+ "lastActivity",
+ "status"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CollaborationSessionConfig.json b/packages/spec/json-schema/system/CollaborationSessionConfig.json
new file mode 100644
index 000000000..cc7b97fad
--- /dev/null
+++ b/packages/spec/json-schema/system/CollaborationSessionConfig.json
@@ -0,0 +1,86 @@
+{
+ "$ref": "#/definitions/CollaborationSessionConfig",
+ "definitions": {
+ "CollaborationSessionConfig": {
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": [
+ "ot",
+ "crdt",
+ "lock",
+ "hybrid"
+ ],
+ "description": "Collaboration mode to use"
+ },
+ "enableCursorSharing": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable cursor sharing"
+ },
+ "enablePresence": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable presence tracking"
+ },
+ "enableAwareness": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable awareness state"
+ },
+ "maxUsers": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Maximum concurrent users"
+ },
+ "idleTimeout": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "default": 300000,
+ "description": "Idle timeout in milliseconds"
+ },
+ "conflictResolution": {
+ "type": "string",
+ "enum": [
+ "ot",
+ "crdt",
+ "manual"
+ ],
+ "default": "ot",
+ "description": "Conflict resolution strategy"
+ },
+ "persistence": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enable operation persistence"
+ },
+ "snapshot": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "description": "Enable periodic snapshots"
+ },
+ "interval": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Snapshot interval in milliseconds"
+ }
+ },
+ "required": [
+ "enabled",
+ "interval"
+ ],
+ "additionalProperties": false,
+ "description": "Snapshot configuration"
+ }
+ },
+ "required": [
+ "mode"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CollaborativeCursor.json b/packages/spec/json-schema/system/CollaborativeCursor.json
new file mode 100644
index 000000000..ccd0f62b4
--- /dev/null
+++ b/packages/spec/json-schema/system/CollaborativeCursor.json
@@ -0,0 +1,189 @@
+{
+ "$ref": "#/definitions/CollaborativeCursor",
+ "definitions": {
+ "CollaborativeCursor": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "User identifier"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "userName": {
+ "type": "string",
+ "description": "Display name of user"
+ },
+ "position": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Cursor line number (0-indexed)"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Cursor column number (0-indexed)"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Current cursor position"
+ },
+ "selection": {
+ "type": "object",
+ "properties": {
+ "anchor": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Anchor line number"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Anchor column number"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Selection anchor (start point)"
+ },
+ "focus": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Focus line number"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Focus column number"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Selection focus (end point)"
+ },
+ "direction": {
+ "type": "string",
+ "enum": [
+ "forward",
+ "backward"
+ ],
+ "description": "Selection direction"
+ }
+ },
+ "required": [
+ "anchor",
+ "focus"
+ ],
+ "additionalProperties": false,
+ "description": "Current text selection"
+ },
+ "style": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "blue",
+ "green",
+ "red",
+ "yellow",
+ "purple",
+ "orange",
+ "pink",
+ "teal",
+ "indigo",
+ "cyan"
+ ]
+ },
+ {
+ "type": "string"
+ }
+ ],
+ "description": "Cursor color (preset or custom hex)"
+ },
+ "opacity": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1,
+ "default": 1,
+ "description": "Cursor opacity (0-1)"
+ },
+ "label": {
+ "type": "string",
+ "description": "Label to display with cursor (usually username)"
+ },
+ "showLabel": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether to show label"
+ },
+ "pulseOnUpdate": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether to pulse when cursor moves"
+ }
+ },
+ "required": [
+ "color"
+ ],
+ "additionalProperties": false,
+ "description": "Visual style for this cursor"
+ },
+ "isTyping": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether user is currently typing"
+ },
+ "lastUpdate": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last cursor update"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional cursor metadata"
+ }
+ },
+ "required": [
+ "userId",
+ "sessionId",
+ "documentId",
+ "userName",
+ "position",
+ "style",
+ "lastUpdate"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CounterOperation.json b/packages/spec/json-schema/system/CounterOperation.json
new file mode 100644
index 000000000..6327546e5
--- /dev/null
+++ b/packages/spec/json-schema/system/CounterOperation.json
@@ -0,0 +1,30 @@
+{
+ "$ref": "#/definitions/CounterOperation",
+ "definitions": {
+ "CounterOperation": {
+ "type": "object",
+ "properties": {
+ "replicaId": {
+ "type": "string",
+ "description": "Replica identifier"
+ },
+ "delta": {
+ "type": "integer",
+ "description": "Change amount (positive for increment, negative for decrement)"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of operation"
+ }
+ },
+ "required": [
+ "replicaId",
+ "delta",
+ "timestamp"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CursorColorPreset.json b/packages/spec/json-schema/system/CursorColorPreset.json
new file mode 100644
index 000000000..add047298
--- /dev/null
+++ b/packages/spec/json-schema/system/CursorColorPreset.json
@@ -0,0 +1,21 @@
+{
+ "$ref": "#/definitions/CursorColorPreset",
+ "definitions": {
+ "CursorColorPreset": {
+ "type": "string",
+ "enum": [
+ "blue",
+ "green",
+ "red",
+ "yellow",
+ "purple",
+ "orange",
+ "pink",
+ "teal",
+ "indigo",
+ "cyan"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CursorSelection.json b/packages/spec/json-schema/system/CursorSelection.json
new file mode 100644
index 000000000..d000f4cb9
--- /dev/null
+++ b/packages/spec/json-schema/system/CursorSelection.json
@@ -0,0 +1,66 @@
+{
+ "$ref": "#/definitions/CursorSelection",
+ "definitions": {
+ "CursorSelection": {
+ "type": "object",
+ "properties": {
+ "anchor": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Anchor line number"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Anchor column number"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Selection anchor (start point)"
+ },
+ "focus": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Focus line number"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Focus column number"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Selection focus (end point)"
+ },
+ "direction": {
+ "type": "string",
+ "enum": [
+ "forward",
+ "backward"
+ ],
+ "description": "Selection direction"
+ }
+ },
+ "required": [
+ "anchor",
+ "focus"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CursorStyle.json b/packages/spec/json-schema/system/CursorStyle.json
new file mode 100644
index 000000000..660db91e9
--- /dev/null
+++ b/packages/spec/json-schema/system/CursorStyle.json
@@ -0,0 +1,59 @@
+{
+ "$ref": "#/definitions/CursorStyle",
+ "definitions": {
+ "CursorStyle": {
+ "type": "object",
+ "properties": {
+ "color": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "blue",
+ "green",
+ "red",
+ "yellow",
+ "purple",
+ "orange",
+ "pink",
+ "teal",
+ "indigo",
+ "cyan"
+ ]
+ },
+ {
+ "type": "string"
+ }
+ ],
+ "description": "Cursor color (preset or custom hex)"
+ },
+ "opacity": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1,
+ "default": 1,
+ "description": "Cursor opacity (0-1)"
+ },
+ "label": {
+ "type": "string",
+ "description": "Label to display with cursor (usually username)"
+ },
+ "showLabel": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether to show label"
+ },
+ "pulseOnUpdate": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether to pulse when cursor moves"
+ }
+ },
+ "required": [
+ "color"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/CursorUpdate.json b/packages/spec/json-schema/system/CursorUpdate.json
new file mode 100644
index 000000000..809273e4a
--- /dev/null
+++ b/packages/spec/json-schema/system/CursorUpdate.json
@@ -0,0 +1,101 @@
+{
+ "$ref": "#/definitions/CursorUpdate",
+ "definitions": {
+ "CursorUpdate": {
+ "type": "object",
+ "properties": {
+ "position": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Updated cursor position"
+ },
+ "selection": {
+ "type": "object",
+ "properties": {
+ "anchor": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Anchor line number"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Anchor column number"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Selection anchor (start point)"
+ },
+ "focus": {
+ "type": "object",
+ "properties": {
+ "line": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Focus line number"
+ },
+ "column": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Focus column number"
+ }
+ },
+ "required": [
+ "line",
+ "column"
+ ],
+ "additionalProperties": false,
+ "description": "Selection focus (end point)"
+ },
+ "direction": {
+ "type": "string",
+ "enum": [
+ "forward",
+ "backward"
+ ],
+ "description": "Selection direction"
+ }
+ },
+ "required": [
+ "anchor",
+ "focus"
+ ],
+ "additionalProperties": false,
+ "description": "Updated selection"
+ },
+ "isTyping": {
+ "type": "boolean",
+ "description": "Updated typing state"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Updated metadata"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/GCounter.json b/packages/spec/json-schema/system/GCounter.json
new file mode 100644
index 000000000..84bc81af2
--- /dev/null
+++ b/packages/spec/json-schema/system/GCounter.json
@@ -0,0 +1,28 @@
+{
+ "$ref": "#/definitions/GCounter",
+ "definitions": {
+ "GCounter": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "g-counter"
+ },
+ "counts": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to count"
+ }
+ },
+ "required": [
+ "type",
+ "counts"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/LWWRegister.json b/packages/spec/json-schema/system/LWWRegister.json
new file mode 100644
index 000000000..df36e449d
--- /dev/null
+++ b/packages/spec/json-schema/system/LWWRegister.json
@@ -0,0 +1,51 @@
+{
+ "$ref": "#/definitions/LWWRegister",
+ "definitions": {
+ "LWWRegister": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "lww-register"
+ },
+ "value": {
+ "description": "Current register value"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of last write"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "ID of replica that performed last write"
+ },
+ "vectorClock": {
+ "type": "object",
+ "properties": {
+ "clock": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to logical timestamp"
+ }
+ },
+ "required": [
+ "clock"
+ ],
+ "additionalProperties": false,
+ "description": "Optional vector clock for causality tracking"
+ }
+ },
+ "required": [
+ "type",
+ "timestamp",
+ "replicaId"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/ORSet.json b/packages/spec/json-schema/system/ORSet.json
new file mode 100644
index 000000000..5ac438d3d
--- /dev/null
+++ b/packages/spec/json-schema/system/ORSet.json
@@ -0,0 +1,57 @@
+{
+ "$ref": "#/definitions/ORSet",
+ "definitions": {
+ "ORSet": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "or-set"
+ },
+ "elements": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "description": "Element value"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Addition timestamp"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "Replica that added the element"
+ },
+ "uid": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique identifier for this addition"
+ },
+ "removed": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether element has been removed"
+ }
+ },
+ "required": [
+ "timestamp",
+ "replicaId",
+ "uid"
+ ],
+ "additionalProperties": false
+ },
+ "description": "Set elements with metadata"
+ }
+ },
+ "required": [
+ "type",
+ "elements"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/ORSetElement.json b/packages/spec/json-schema/system/ORSetElement.json
new file mode 100644
index 000000000..6c40e698b
--- /dev/null
+++ b/packages/spec/json-schema/system/ORSetElement.json
@@ -0,0 +1,39 @@
+{
+ "$ref": "#/definitions/ORSetElement",
+ "definitions": {
+ "ORSetElement": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "description": "Element value"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Addition timestamp"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "Replica that added the element"
+ },
+ "uid": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique identifier for this addition"
+ },
+ "removed": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether element has been removed"
+ }
+ },
+ "required": [
+ "timestamp",
+ "replicaId",
+ "uid"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/OTComponent.json b/packages/spec/json-schema/system/OTComponent.json
new file mode 100644
index 000000000..164e148fc
--- /dev/null
+++ b/packages/spec/json-schema/system/OTComponent.json
@@ -0,0 +1,76 @@
+{
+ "$ref": "#/definitions/OTComponent",
+ "definitions": {
+ "OTComponent": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "insert"
+ },
+ "text": {
+ "type": "string",
+ "description": "Text to insert"
+ },
+ "attributes": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Text formatting attributes (e.g., bold, italic)"
+ }
+ },
+ "required": [
+ "type",
+ "text"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "delete"
+ },
+ "count": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to delete"
+ }
+ },
+ "required": [
+ "type",
+ "count"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "retain"
+ },
+ "count": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to retain"
+ },
+ "attributes": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Attribute changes to apply"
+ }
+ },
+ "required": [
+ "type",
+ "count"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/OTOperation.json b/packages/spec/json-schema/system/OTOperation.json
new file mode 100644
index 000000000..b650bc858
--- /dev/null
+++ b/packages/spec/json-schema/system/OTOperation.json
@@ -0,0 +1,128 @@
+{
+ "$ref": "#/definitions/OTOperation",
+ "definitions": {
+ "OTOperation": {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "userId": {
+ "type": "string",
+ "description": "User who created the operation"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "components": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "insert"
+ },
+ "text": {
+ "type": "string",
+ "description": "Text to insert"
+ },
+ "attributes": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Text formatting attributes (e.g., bold, italic)"
+ }
+ },
+ "required": [
+ "type",
+ "text"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "delete"
+ },
+ "count": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to delete"
+ }
+ },
+ "required": [
+ "type",
+ "count"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "retain"
+ },
+ "count": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to retain"
+ },
+ "attributes": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Attribute changes to apply"
+ }
+ },
+ "required": [
+ "type",
+ "count"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Operation components"
+ },
+ "baseVersion": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Document version this operation is based on"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when operation was created"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional operation metadata"
+ }
+ },
+ "required": [
+ "operationId",
+ "documentId",
+ "userId",
+ "sessionId",
+ "components",
+ "baseVersion",
+ "timestamp"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/OTOperationType.json b/packages/spec/json-schema/system/OTOperationType.json
new file mode 100644
index 000000000..38bdd8e49
--- /dev/null
+++ b/packages/spec/json-schema/system/OTOperationType.json
@@ -0,0 +1,14 @@
+{
+ "$ref": "#/definitions/OTOperationType",
+ "definitions": {
+ "OTOperationType": {
+ "type": "string",
+ "enum": [
+ "insert",
+ "delete",
+ "retain"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/OTTransformResult.json b/packages/spec/json-schema/system/OTTransformResult.json
new file mode 100644
index 000000000..593b076a3
--- /dev/null
+++ b/packages/spec/json-schema/system/OTTransformResult.json
@@ -0,0 +1,150 @@
+{
+ "$ref": "#/definitions/OTTransformResult",
+ "definitions": {
+ "OTTransformResult": {
+ "type": "object",
+ "properties": {
+ "operation": {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "userId": {
+ "type": "string",
+ "description": "User who created the operation"
+ },
+ "sessionId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Session identifier"
+ },
+ "components": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "insert"
+ },
+ "text": {
+ "type": "string",
+ "description": "Text to insert"
+ },
+ "attributes": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Text formatting attributes (e.g., bold, italic)"
+ }
+ },
+ "required": [
+ "type",
+ "text"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "delete"
+ },
+ "count": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to delete"
+ }
+ },
+ "required": [
+ "type",
+ "count"
+ ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "retain"
+ },
+ "count": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to retain"
+ },
+ "attributes": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Attribute changes to apply"
+ }
+ },
+ "required": [
+ "type",
+ "count"
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+ "description": "Operation components"
+ },
+ "baseVersion": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Document version this operation is based on"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime when operation was created"
+ },
+ "metadata": {
+ "type": "object",
+ "additionalProperties": {},
+ "description": "Additional operation metadata"
+ }
+ },
+ "required": [
+ "operationId",
+ "documentId",
+ "userId",
+ "sessionId",
+ "components",
+ "baseVersion",
+ "timestamp"
+ ],
+ "additionalProperties": false,
+ "description": "Transformed operation"
+ },
+ "transformed": {
+ "type": "boolean",
+ "description": "Whether transformation was applied"
+ },
+ "conflicts": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Conflict descriptions if any"
+ }
+ },
+ "required": [
+ "operation",
+ "transformed"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/PNCounter.json b/packages/spec/json-schema/system/PNCounter.json
new file mode 100644
index 000000000..a33588423
--- /dev/null
+++ b/packages/spec/json-schema/system/PNCounter.json
@@ -0,0 +1,37 @@
+{
+ "$ref": "#/definitions/PNCounter",
+ "definitions": {
+ "PNCounter": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "pn-counter"
+ },
+ "positive": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Positive increments per replica"
+ },
+ "negative": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Negative increments per replica"
+ }
+ },
+ "required": [
+ "type",
+ "positive",
+ "negative"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/TextCRDTOperation.json b/packages/spec/json-schema/system/TextCRDTOperation.json
new file mode 100644
index 000000000..3e91c3360
--- /dev/null
+++ b/packages/spec/json-schema/system/TextCRDTOperation.json
@@ -0,0 +1,52 @@
+{
+ "$ref": "#/definitions/TextCRDTOperation",
+ "definitions": {
+ "TextCRDTOperation": {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "Replica identifier"
+ },
+ "position": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Position in document"
+ },
+ "insert": {
+ "type": "string",
+ "description": "Text to insert"
+ },
+ "delete": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to delete"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of operation"
+ },
+ "lamportTimestamp": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Lamport timestamp for ordering"
+ }
+ },
+ "required": [
+ "operationId",
+ "replicaId",
+ "position",
+ "timestamp",
+ "lamportTimestamp"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/TextCRDTState.json b/packages/spec/json-schema/system/TextCRDTState.json
new file mode 100644
index 000000000..28be84cae
--- /dev/null
+++ b/packages/spec/json-schema/system/TextCRDTState.json
@@ -0,0 +1,105 @@
+{
+ "$ref": "#/definitions/TextCRDTState",
+ "definitions": {
+ "TextCRDTState": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "text"
+ },
+ "documentId": {
+ "type": "string",
+ "description": "Document identifier"
+ },
+ "content": {
+ "type": "string",
+ "description": "Current text content"
+ },
+ "operations": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "operationId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique operation identifier"
+ },
+ "replicaId": {
+ "type": "string",
+ "description": "Replica identifier"
+ },
+ "position": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Position in document"
+ },
+ "insert": {
+ "type": "string",
+ "description": "Text to insert"
+ },
+ "delete": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "description": "Number of characters to delete"
+ },
+ "timestamp": {
+ "type": "string",
+ "format": "date-time",
+ "description": "ISO 8601 datetime of operation"
+ },
+ "lamportTimestamp": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Lamport timestamp for ordering"
+ }
+ },
+ "required": [
+ "operationId",
+ "replicaId",
+ "position",
+ "timestamp",
+ "lamportTimestamp"
+ ],
+ "additionalProperties": false
+ },
+ "description": "History of operations"
+ },
+ "lamportClock": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Current Lamport clock value"
+ },
+ "vectorClock": {
+ "type": "object",
+ "properties": {
+ "clock": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to logical timestamp"
+ }
+ },
+ "required": [
+ "clock"
+ ],
+ "additionalProperties": false,
+ "description": "Vector clock for causality"
+ }
+ },
+ "required": [
+ "type",
+ "documentId",
+ "content",
+ "operations",
+ "lamportClock",
+ "vectorClock"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/UserActivityStatus.json b/packages/spec/json-schema/system/UserActivityStatus.json
new file mode 100644
index 000000000..89425cd95
--- /dev/null
+++ b/packages/spec/json-schema/system/UserActivityStatus.json
@@ -0,0 +1,15 @@
+{
+ "$ref": "#/definitions/UserActivityStatus",
+ "definitions": {
+ "UserActivityStatus": {
+ "type": "string",
+ "enum": [
+ "active",
+ "idle",
+ "viewing",
+ "disconnected"
+ ]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/json-schema/system/VectorClock.json b/packages/spec/json-schema/system/VectorClock.json
new file mode 100644
index 000000000..a57199880
--- /dev/null
+++ b/packages/spec/json-schema/system/VectorClock.json
@@ -0,0 +1,23 @@
+{
+ "$ref": "#/definitions/VectorClock",
+ "definitions": {
+ "VectorClock": {
+ "type": "object",
+ "properties": {
+ "clock": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Map of replica ID to logical timestamp"
+ }
+ },
+ "required": [
+ "clock"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
\ No newline at end of file
diff --git a/packages/spec/src/api/index.ts b/packages/spec/src/api/index.ts
index 3b754fe9b..7fda26d57 100644
--- a/packages/spec/src/api/index.ts
+++ b/packages/spec/src/api/index.ts
@@ -11,6 +11,7 @@ export * from './contract.zod';
export * from './endpoint.zod';
export * from './discovery.zod';
export * from './realtime.zod';
+export * from './websocket.zod';
export * from './router.zod';
export * from './odata.zod';
export * from './graphql.zod';
diff --git a/packages/spec/src/api/websocket.test.ts b/packages/spec/src/api/websocket.test.ts
new file mode 100644
index 000000000..d59338d47
--- /dev/null
+++ b/packages/spec/src/api/websocket.test.ts
@@ -0,0 +1,829 @@
+import { describe, it, expect } from 'vitest';
+import {
+ WebSocketMessageType,
+ FilterOperator,
+ EventFilterCondition,
+ EventFilterSchema,
+ EventSubscriptionSchema,
+ UnsubscribeRequestSchema,
+ WebSocketPresenceStatus,
+ PresenceStateSchema,
+ PresenceUpdateSchema,
+ CursorPositionSchema,
+ EditOperationType,
+ EditOperationSchema,
+ DocumentStateSchema,
+ SubscribeMessageSchema,
+ UnsubscribeMessageSchema,
+ EventMessageSchema,
+ PresenceMessageSchema,
+ CursorMessageSchema,
+ EditMessageSchema,
+ AckMessageSchema,
+ ErrorMessageSchema,
+ PingMessageSchema,
+ PongMessageSchema,
+ WebSocketMessageSchema,
+ WebSocketConfigSchema,
+ type EventSubscription,
+ type PresenceState,
+ type CursorPosition,
+ type EditOperation,
+ type WebSocketMessage,
+} from './websocket.zod';
+
+describe('WebSocketMessageType', () => {
+ it('should accept valid message types', () => {
+ const validTypes = [
+ 'subscribe', 'unsubscribe', 'event', 'ping', 'pong',
+ 'ack', 'error', 'presence', 'cursor', 'edit',
+ ];
+
+ validTypes.forEach(type => {
+ expect(() => WebSocketMessageType.parse(type)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid message types', () => {
+ expect(() => WebSocketMessageType.parse('invalid')).toThrow();
+ expect(() => WebSocketMessageType.parse('')).toThrow();
+ });
+});
+
+describe('FilterOperator', () => {
+ it('should accept valid filter operators', () => {
+ const operators = ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'contains', 'startsWith', 'endsWith', 'exists', 'regex'];
+
+ operators.forEach(op => {
+ expect(() => FilterOperator.parse(op)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid operators', () => {
+ expect(() => FilterOperator.parse('like')).toThrow();
+ expect(() => FilterOperator.parse('between')).toThrow();
+ });
+});
+
+describe('EventFilterCondition', () => {
+ it('should accept valid filter condition', () => {
+ const condition = {
+ field: 'status',
+ operator: 'eq',
+ value: 'active',
+ };
+
+ expect(() => EventFilterCondition.parse(condition)).not.toThrow();
+ });
+
+ it('should accept filter with dot notation field path', () => {
+ const condition = {
+ field: 'user.email',
+ operator: 'contains',
+ value: '@example.com',
+ };
+
+ const parsed = EventFilterCondition.parse(condition);
+ expect(parsed.field).toBe('user.email');
+ });
+
+ it('should accept exists operator without value', () => {
+ const condition = {
+ field: 'optional_field',
+ operator: 'exists',
+ };
+
+ const parsed = EventFilterCondition.parse(condition);
+ expect(parsed.value).toBeUndefined();
+ });
+});
+
+describe('EventFilterSchema', () => {
+ it('should accept simple filter with conditions', () => {
+ const filter = {
+ conditions: [
+ { field: 'status', operator: 'eq', value: 'active' },
+ { field: 'amount', operator: 'gt', value: 1000 },
+ ],
+ };
+
+ expect(() => EventFilterSchema.parse(filter)).not.toThrow();
+ });
+
+ it('should accept AND logical combination', () => {
+ const filter = {
+ and: [
+ { conditions: [{ field: 'status', operator: 'eq', value: 'active' }] },
+ { conditions: [{ field: 'verified', operator: 'eq', value: true }] },
+ ],
+ };
+
+ expect(() => EventFilterSchema.parse(filter)).not.toThrow();
+ });
+
+ it('should accept OR logical combination', () => {
+ const filter = {
+ or: [
+ { conditions: [{ field: 'type', operator: 'eq', value: 'urgent' }] },
+ { conditions: [{ field: 'priority', operator: 'gte', value: 5 }] },
+ ],
+ };
+
+ expect(() => EventFilterSchema.parse(filter)).not.toThrow();
+ });
+
+ it('should accept NOT logical negation', () => {
+ const filter = {
+ not: {
+ conditions: [{ field: 'deleted', operator: 'eq', value: true }],
+ },
+ };
+
+ expect(() => EventFilterSchema.parse(filter)).not.toThrow();
+ });
+
+ it('should accept complex nested filters', () => {
+ const filter = {
+ and: [
+ { conditions: [{ field: 'status', operator: 'eq', value: 'active' }] },
+ {
+ or: [
+ { conditions: [{ field: 'type', operator: 'eq', value: 'premium' }] },
+ { conditions: [{ field: 'amount', operator: 'gte', value: 10000 }] },
+ ],
+ },
+ ],
+ };
+
+ expect(() => EventFilterSchema.parse(filter)).not.toThrow();
+ });
+});
+
+describe('EventSubscriptionSchema', () => {
+ it('should accept valid minimal subscription', () => {
+ const subscription: EventSubscription = {
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440000',
+ events: ['record.created'],
+ };
+
+ expect(() => EventSubscriptionSchema.parse(subscription)).not.toThrow();
+ });
+
+ it('should accept subscription with wildcard events', () => {
+ const subscription = {
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440000',
+ events: ['record.*', 'user.created', '*.deleted'],
+ };
+
+ expect(() => EventSubscriptionSchema.parse(subscription)).not.toThrow();
+ });
+
+ it('should accept subscription with objects filter', () => {
+ const subscription = {
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440000',
+ events: ['record.updated'],
+ objects: ['account', 'contact'],
+ };
+
+ const parsed = EventSubscriptionSchema.parse(subscription);
+ expect(parsed.objects).toEqual(['account', 'contact']);
+ });
+
+ it('should accept subscription with advanced filters', () => {
+ const subscription = {
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440000',
+ events: ['record.created'],
+ filters: {
+ conditions: [
+ { field: 'amount', operator: 'gt', value: 5000 },
+ ],
+ },
+ };
+
+ expect(() => EventSubscriptionSchema.parse(subscription)).not.toThrow();
+ });
+
+ it('should accept subscription with channels', () => {
+ const subscription = {
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440000',
+ events: ['notification.*'],
+ channels: ['user-123', 'team-456'],
+ };
+
+ const parsed = EventSubscriptionSchema.parse(subscription);
+ expect(parsed.channels).toEqual(['user-123', 'team-456']);
+ });
+
+ it('should validate UUID format', () => {
+ expect(() => EventSubscriptionSchema.parse({
+ subscriptionId: 'not-a-uuid',
+ events: ['record.created'],
+ })).toThrow();
+ });
+});
+
+describe('UnsubscribeRequestSchema', () => {
+ it('should accept valid unsubscribe request', () => {
+ const request = {
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440000',
+ };
+
+ expect(() => UnsubscribeRequestSchema.parse(request)).not.toThrow();
+ });
+
+ it('should validate UUID format', () => {
+ expect(() => UnsubscribeRequestSchema.parse({
+ subscriptionId: 'invalid-uuid',
+ })).toThrow();
+ });
+});
+
+describe('WebSocketPresenceStatus', () => {
+ it('should accept valid presence statuses', () => {
+ const statuses = ['online', 'away', 'busy', 'offline'];
+
+ statuses.forEach(status => {
+ expect(() => WebSocketPresenceStatus.parse(status)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid statuses', () => {
+ expect(() => WebSocketPresenceStatus.parse('idle')).toThrow();
+ expect(() => WebSocketPresenceStatus.parse('dnd')).toThrow();
+ });
+});
+
+describe('PresenceStateSchema', () => {
+ it('should accept valid minimal presence state', () => {
+ const presence: PresenceState = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ status: 'online',
+ lastSeen: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => PresenceStateSchema.parse(presence)).not.toThrow();
+ });
+
+ it('should accept presence with all optional fields', () => {
+ const presence = {
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ status: 'busy',
+ lastSeen: '2024-01-15T10:30:00Z',
+ currentLocation: '/dashboard/analytics',
+ device: 'desktop',
+ customStatus: 'In a meeting',
+ metadata: { team: 'engineering', role: 'developer' },
+ };
+
+ const parsed = PresenceStateSchema.parse(presence);
+ expect(parsed.currentLocation).toBe('/dashboard/analytics');
+ expect(parsed.device).toBe('desktop');
+ expect(parsed.customStatus).toBe('In a meeting');
+ });
+
+ it('should validate device type', () => {
+ expect(() => PresenceStateSchema.parse({
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ status: 'online',
+ lastSeen: '2024-01-15T10:30:00Z',
+ device: 'invalid-device',
+ })).toThrow();
+ });
+});
+
+describe('PresenceUpdateSchema', () => {
+ it('should accept partial presence updates', () => {
+ const update = {
+ status: 'away',
+ };
+
+ expect(() => PresenceUpdateSchema.parse(update)).not.toThrow();
+ });
+
+ it('should accept multiple fields update', () => {
+ const update = {
+ status: 'online',
+ currentLocation: '/projects/123',
+ customStatus: 'Working on feature X',
+ };
+
+ expect(() => PresenceUpdateSchema.parse(update)).not.toThrow();
+ });
+});
+
+describe('CursorPositionSchema', () => {
+ it('should accept valid minimal cursor position', () => {
+ const cursor: CursorPosition = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-456',
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => CursorPositionSchema.parse(cursor)).not.toThrow();
+ });
+
+ it('should accept cursor with position', () => {
+ const cursor = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-456',
+ position: { line: 10, column: 25 },
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ const parsed = CursorPositionSchema.parse(cursor);
+ expect(parsed.position?.line).toBe(10);
+ expect(parsed.position?.column).toBe(25);
+ });
+
+ it('should accept cursor with selection', () => {
+ const cursor = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-456',
+ position: { line: 10, column: 0 },
+ selection: {
+ start: { line: 10, column: 0 },
+ end: { line: 15, column: 20 },
+ },
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => CursorPositionSchema.parse(cursor)).not.toThrow();
+ });
+
+ it('should accept cursor with color and userName', () => {
+ const cursor = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-456',
+ color: '#FF5733',
+ userName: 'John Doe',
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ const parsed = CursorPositionSchema.parse(cursor);
+ expect(parsed.color).toBe('#FF5733');
+ expect(parsed.userName).toBe('John Doe');
+ });
+
+ it('should reject negative position values', () => {
+ expect(() => CursorPositionSchema.parse({
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-456',
+ position: { line: -1, column: 0 },
+ lastUpdate: '2024-01-15T10:30:00Z',
+ })).toThrow();
+ });
+});
+
+describe('EditOperationType', () => {
+ it('should accept valid operation types', () => {
+ const types = ['insert', 'delete', 'replace'];
+
+ types.forEach(type => {
+ expect(() => EditOperationType.parse(type)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid operation types', () => {
+ expect(() => EditOperationType.parse('update')).toThrow();
+ });
+});
+
+describe('EditOperationSchema', () => {
+ it('should accept valid insert operation', () => {
+ const operation: EditOperation = {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ type: 'insert',
+ position: { line: 5, column: 10 },
+ content: 'Hello, World!',
+ version: 42,
+ timestamp: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => EditOperationSchema.parse(operation)).not.toThrow();
+ });
+
+ it('should accept delete operation', () => {
+ const operation = {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ type: 'delete',
+ position: { line: 5, column: 10 },
+ endPosition: { line: 5, column: 25 },
+ version: 42,
+ timestamp: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => EditOperationSchema.parse(operation)).not.toThrow();
+ });
+
+ it('should accept replace operation', () => {
+ const operation = {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ type: 'replace',
+ position: { line: 5, column: 10 },
+ endPosition: { line: 5, column: 25 },
+ content: 'New content',
+ version: 42,
+ timestamp: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => EditOperationSchema.parse(operation)).not.toThrow();
+ });
+
+ it('should accept operation with baseOperationId', () => {
+ const operation = {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ type: 'insert',
+ position: { line: 5, column: 10 },
+ content: 'Text',
+ version: 42,
+ timestamp: '2024-01-15T10:30:00Z',
+ baseOperationId: '550e8400-e29b-41d4-a716-446655440002',
+ };
+
+ const parsed = EditOperationSchema.parse(operation);
+ expect(parsed.baseOperationId).toBe('550e8400-e29b-41d4-a716-446655440002');
+ });
+
+ it('should reject negative version', () => {
+ expect(() => EditOperationSchema.parse({
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ type: 'insert',
+ position: { line: 0, column: 0 },
+ content: 'Text',
+ version: -1,
+ timestamp: '2024-01-15T10:30:00Z',
+ })).toThrow();
+ });
+});
+
+describe('DocumentStateSchema', () => {
+ it('should accept valid document state', () => {
+ const state = {
+ documentId: 'doc-123',
+ version: 42,
+ content: 'Document content here',
+ lastModified: '2024-01-15T10:30:00Z',
+ activeSessions: [
+ '550e8400-e29b-41d4-a716-446655440000',
+ '550e8400-e29b-41d4-a716-446655440001',
+ ],
+ };
+
+ expect(() => DocumentStateSchema.parse(state)).not.toThrow();
+ });
+
+ it('should accept document state with checksum', () => {
+ const state = {
+ documentId: 'doc-123',
+ version: 42,
+ content: 'Document content',
+ lastModified: '2024-01-15T10:30:00Z',
+ activeSessions: [],
+ checksum: 'sha256:abcdef1234567890',
+ };
+
+ const parsed = DocumentStateSchema.parse(state);
+ expect(parsed.checksum).toBe('sha256:abcdef1234567890');
+ });
+});
+
+describe('WebSocket Message Schemas', () => {
+ describe('SubscribeMessageSchema', () => {
+ it('should accept valid subscribe message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'subscribe',
+ timestamp: '2024-01-15T10:30:00Z',
+ subscription: {
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440001',
+ events: ['record.created'],
+ },
+ };
+
+ expect(() => SubscribeMessageSchema.parse(message)).not.toThrow();
+ });
+ });
+
+ describe('UnsubscribeMessageSchema', () => {
+ it('should accept valid unsubscribe message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'unsubscribe',
+ timestamp: '2024-01-15T10:30:00Z',
+ request: {
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440001',
+ },
+ };
+
+ expect(() => UnsubscribeMessageSchema.parse(message)).not.toThrow();
+ });
+ });
+
+ describe('EventMessageSchema', () => {
+ it('should accept valid event message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'event',
+ timestamp: '2024-01-15T10:30:00Z',
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440001',
+ eventName: 'record.created',
+ payload: { id: '123', name: 'Test' },
+ };
+
+ expect(() => EventMessageSchema.parse(message)).not.toThrow();
+ });
+
+ it('should accept event message with object and userId', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'event',
+ timestamp: '2024-01-15T10:30:00Z',
+ subscriptionId: '550e8400-e29b-41d4-a716-446655440001',
+ eventName: 'record.updated',
+ object: 'account',
+ payload: { id: '123', status: 'active' },
+ userId: 'user-456',
+ };
+
+ const parsed = EventMessageSchema.parse(message);
+ expect(parsed.object).toBe('account');
+ expect(parsed.userId).toBe('user-456');
+ });
+ });
+
+ describe('PresenceMessageSchema', () => {
+ it('should accept valid presence message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'presence',
+ timestamp: '2024-01-15T10:30:00Z',
+ presence: {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ status: 'online',
+ lastSeen: '2024-01-15T10:30:00Z',
+ },
+ };
+
+ expect(() => PresenceMessageSchema.parse(message)).not.toThrow();
+ });
+ });
+
+ describe('CursorMessageSchema', () => {
+ it('should accept valid cursor message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'cursor',
+ timestamp: '2024-01-15T10:30:00Z',
+ cursor: {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ documentId: 'doc-456',
+ position: { line: 10, column: 5 },
+ lastUpdate: '2024-01-15T10:30:00Z',
+ },
+ };
+
+ expect(() => CursorMessageSchema.parse(message)).not.toThrow();
+ });
+ });
+
+ describe('EditMessageSchema', () => {
+ it('should accept valid edit message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'edit',
+ timestamp: '2024-01-15T10:30:00Z',
+ operation: {
+ operationId: '550e8400-e29b-41d4-a716-446655440001',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440002',
+ type: 'insert',
+ position: { line: 5, column: 10 },
+ content: 'Text',
+ version: 42,
+ timestamp: '2024-01-15T10:30:00Z',
+ },
+ };
+
+ expect(() => EditMessageSchema.parse(message)).not.toThrow();
+ });
+ });
+
+ describe('AckMessageSchema', () => {
+ it('should accept valid acknowledgment message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'ack',
+ timestamp: '2024-01-15T10:30:00Z',
+ ackMessageId: '550e8400-e29b-41d4-a716-446655440001',
+ success: true,
+ };
+
+ expect(() => AckMessageSchema.parse(message)).not.toThrow();
+ });
+
+ it('should accept acknowledgment with error', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'ack',
+ timestamp: '2024-01-15T10:30:00Z',
+ ackMessageId: '550e8400-e29b-41d4-a716-446655440001',
+ success: false,
+ error: 'Invalid subscription configuration',
+ };
+
+ const parsed = AckMessageSchema.parse(message);
+ expect(parsed.success).toBe(false);
+ expect(parsed.error).toBe('Invalid subscription configuration');
+ });
+ });
+
+ describe('ErrorMessageSchema', () => {
+ it('should accept valid error message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'error',
+ timestamp: '2024-01-15T10:30:00Z',
+ code: 'INVALID_SUBSCRIPTION',
+ message: 'Subscription configuration is invalid',
+ };
+
+ expect(() => ErrorMessageSchema.parse(message)).not.toThrow();
+ });
+
+ it('should accept error message with details', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'error',
+ timestamp: '2024-01-15T10:30:00Z',
+ code: 'VALIDATION_ERROR',
+ message: 'Validation failed',
+ details: { field: 'events', reason: 'Array cannot be empty' },
+ };
+
+ const parsed = ErrorMessageSchema.parse(message);
+ expect(parsed.details).toBeDefined();
+ });
+ });
+
+ describe('PingMessageSchema', () => {
+ it('should accept valid ping message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'ping',
+ timestamp: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => PingMessageSchema.parse(message)).not.toThrow();
+ });
+ });
+
+ describe('PongMessageSchema', () => {
+ it('should accept valid pong message', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'pong',
+ timestamp: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => PongMessageSchema.parse(message)).not.toThrow();
+ });
+
+ it('should accept pong with pingMessageId', () => {
+ const message = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'pong',
+ timestamp: '2024-01-15T10:30:00Z',
+ pingMessageId: '550e8400-e29b-41d4-a716-446655440001',
+ };
+
+ const parsed = PongMessageSchema.parse(message);
+ expect(parsed.pingMessageId).toBe('550e8400-e29b-41d4-a716-446655440001');
+ });
+ });
+
+ describe('WebSocketMessageSchema (Union)', () => {
+ it('should accept all valid message types', () => {
+ const messages = [
+ {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'ping',
+ timestamp: '2024-01-15T10:30:00Z',
+ },
+ {
+ messageId: '550e8400-e29b-41d4-a716-446655440001',
+ type: 'error',
+ timestamp: '2024-01-15T10:30:00Z',
+ code: 'TEST_ERROR',
+ message: 'Test error message',
+ },
+ ];
+
+ messages.forEach(msg => {
+ expect(() => WebSocketMessageSchema.parse(msg)).not.toThrow();
+ });
+ });
+
+ it('should use discriminated union on type field', () => {
+ const message: WebSocketMessage = {
+ messageId: '550e8400-e29b-41d4-a716-446655440000',
+ type: 'ping',
+ timestamp: '2024-01-15T10:30:00Z',
+ };
+
+ const parsed = WebSocketMessageSchema.parse(message);
+ expect(parsed.type).toBe('ping');
+ });
+ });
+});
+
+describe('WebSocketConfigSchema', () => {
+ it('should accept valid minimal config', () => {
+ const config = {
+ url: 'wss://example.com/ws',
+ };
+
+ expect(() => WebSocketConfigSchema.parse(config)).not.toThrow();
+ });
+
+ it('should accept config with all options', () => {
+ const config = {
+ url: 'wss://example.com/ws',
+ protocols: ['objectstack-v1', 'json'],
+ reconnect: true,
+ reconnectInterval: 2000,
+ maxReconnectAttempts: 10,
+ pingInterval: 60000,
+ timeout: 10000,
+ headers: {
+ 'Authorization': 'Bearer token123',
+ 'X-Custom-Header': 'value',
+ },
+ };
+
+ const parsed = WebSocketConfigSchema.parse(config);
+ expect(parsed.reconnect).toBe(true);
+ expect(parsed.reconnectInterval).toBe(2000);
+ expect(parsed.maxReconnectAttempts).toBe(10);
+ });
+
+ it('should use default values for optional fields', () => {
+ const config = {
+ url: 'wss://example.com/ws',
+ };
+
+ const parsed = WebSocketConfigSchema.parse(config);
+ expect(parsed.reconnect).toBe(true);
+ expect(parsed.reconnectInterval).toBe(1000);
+ expect(parsed.maxReconnectAttempts).toBe(5);
+ expect(parsed.pingInterval).toBe(30000);
+ expect(parsed.timeout).toBe(5000);
+ });
+
+ it('should validate URL format', () => {
+ expect(() => WebSocketConfigSchema.parse({
+ url: 'not-a-url',
+ })).toThrow();
+
+ expect(() => WebSocketConfigSchema.parse({
+ url: 'wss://example.com/ws',
+ })).not.toThrow();
+ });
+
+ it('should reject negative intervals', () => {
+ expect(() => WebSocketConfigSchema.parse({
+ url: 'wss://example.com/ws',
+ reconnectInterval: -1000,
+ })).toThrow();
+
+ expect(() => WebSocketConfigSchema.parse({
+ url: 'wss://example.com/ws',
+ pingInterval: 0,
+ })).toThrow();
+ });
+});
diff --git a/packages/spec/src/api/websocket.zod.ts b/packages/spec/src/api/websocket.zod.ts
new file mode 100644
index 000000000..f0e46a005
--- /dev/null
+++ b/packages/spec/src/api/websocket.zod.ts
@@ -0,0 +1,433 @@
+import { z } from 'zod';
+import { EventNameSchema } from '../shared/identifiers.zod';
+
+/**
+ * WebSocket Event Protocol
+ *
+ * Defines the schema for WebSocket-based real-time communication in ObjectStack.
+ * Supports event subscriptions, filtering, presence tracking, and collaborative editing.
+ *
+ * Industry alignment: Firebase Realtime Database, Socket.IO, Pusher
+ */
+
+// ==========================================
+// Message Types
+// ==========================================
+
+/**
+ * WebSocket Message Type Enum
+ * Defines the types of messages that can be sent over WebSocket
+ */
+export const WebSocketMessageType = z.enum([
+ 'subscribe', // Client subscribes to events
+ 'unsubscribe', // Client unsubscribes from events
+ 'event', // Server sends event to client
+ 'ping', // Keepalive ping
+ 'pong', // Keepalive pong response
+ 'ack', // Acknowledgment of message receipt
+ 'error', // Error message
+ 'presence', // Presence update (user status)
+ 'cursor', // Cursor position update (collaborative editing)
+ 'edit', // Document edit operation (collaborative editing)
+]);
+
+export type WebSocketMessageType = z.infer;
+
+// ==========================================
+// Event Subscription
+// ==========================================
+
+/**
+ * Event Filter Operator Enum
+ * SQL-like filter operators for event filtering
+ */
+export const FilterOperator = z.enum([
+ 'eq', // Equal
+ 'ne', // Not equal
+ 'gt', // Greater than
+ 'gte', // Greater than or equal
+ 'lt', // Less than
+ 'lte', // Less than or equal
+ 'in', // In array
+ 'nin', // Not in array
+ 'contains', // String contains
+ 'startsWith', // String starts with
+ 'endsWith', // String ends with
+ 'exists', // Field exists
+ 'regex', // Regex match
+]);
+
+export type FilterOperator = z.infer;
+
+/**
+ * Event Filter Condition
+ * Defines a single filter condition for event filtering
+ */
+export const EventFilterCondition = z.object({
+ field: z.string().describe('Field path to filter on (supports dot notation, e.g., "user.email")'),
+ operator: FilterOperator.describe('Comparison operator'),
+ value: z.any().optional().describe('Value to compare against (not needed for "exists" operator)'),
+});
+
+export type EventFilterCondition = z.infer;
+
+/**
+ * Event Filter Schema
+ * Logical combination of filter conditions
+ */
+export const EventFilterSchema: z.ZodType<{
+ conditions?: EventFilterCondition[];
+ and?: EventFilter[];
+ or?: EventFilter[];
+ not?: EventFilter;
+}> = z.object({
+ conditions: z.array(EventFilterCondition).optional().describe('Array of filter conditions'),
+ and: z.lazy(() => z.array(EventFilterSchema)).optional().describe('AND logical combination of filters'),
+ or: z.lazy(() => z.array(EventFilterSchema)).optional().describe('OR logical combination of filters'),
+ not: z.lazy(() => EventFilterSchema).optional().describe('NOT logical negation of filter'),
+});
+
+export type EventFilter = z.infer;
+
+/**
+ * Event Pattern Schema
+ * Event name pattern that supports wildcards for subscriptions
+ */
+export const EventPatternSchema = z
+ .string()
+ .min(1)
+ .regex(/^[a-z*][a-z0-9_.*]*$/, {
+ message: 'Event pattern must be lowercase and may contain letters, numbers, underscores, dots, or wildcards (e.g., "record.*", "*.created", "user.login")',
+ })
+ .describe('Event pattern (supports wildcards like "record.*" or "*.created")');
+
+export type EventPattern = z.infer;
+
+/**
+ * Event Subscription Config
+ * Configuration for subscribing to specific events
+ */
+export const EventSubscriptionSchema = z.object({
+ subscriptionId: z.string().uuid().describe('Unique subscription identifier'),
+ events: z.array(EventPatternSchema).describe('Event patterns to subscribe to (supports wildcards, e.g., "record.*", "user.created")'),
+ objects: z.array(z.string()).optional().describe('Object names to filter events by (e.g., ["account", "contact"])'),
+ filters: EventFilterSchema.optional().describe('Advanced filter conditions for event payloads'),
+ channels: z.array(z.string()).optional().describe('Channel names for scoped subscriptions'),
+});
+
+export type EventSubscription = z.infer;
+
+/**
+ * Unsubscribe Request
+ * Request to unsubscribe from events
+ */
+export const UnsubscribeRequestSchema = z.object({
+ subscriptionId: z.string().uuid().describe('Subscription ID to unsubscribe from'),
+});
+
+export type UnsubscribeRequest = z.infer;
+
+// ==========================================
+// Presence Tracking
+// ==========================================
+
+/**
+ * Presence Status Enum
+ * User availability status for presence tracking
+ */
+export const WebSocketPresenceStatus = z.enum([
+ 'online', // User is actively online
+ 'away', // User is idle/away
+ 'busy', // User is busy (do not disturb)
+ 'offline', // User is offline
+]);
+
+export type WebSocketPresenceStatus = z.infer;
+
+/**
+ * Presence State Schema
+ * Tracks real-time user presence and activity
+ */
+export const PresenceStateSchema = z.object({
+ userId: z.string().describe('User identifier'),
+ sessionId: z.string().uuid().describe('Unique session identifier'),
+ status: WebSocketPresenceStatus.describe('Current presence status'),
+ lastSeen: z.string().datetime().describe('ISO 8601 datetime of last activity'),
+ currentLocation: z.string().optional().describe('Current page/route user is viewing'),
+ device: z.enum(['desktop', 'mobile', 'tablet', 'other']).optional().describe('Device type'),
+ customStatus: z.string().optional().describe('Custom user status message'),
+ metadata: z.record(z.any()).optional().describe('Additional custom presence data'),
+});
+
+export type PresenceState = z.infer;
+
+/**
+ * Presence Update Request
+ * Client request to update presence status
+ */
+export const PresenceUpdateSchema = z.object({
+ status: WebSocketPresenceStatus.optional().describe('Updated presence status'),
+ currentLocation: z.string().optional().describe('Updated current location'),
+ customStatus: z.string().optional().describe('Updated custom status message'),
+ metadata: z.record(z.any()).optional().describe('Updated metadata'),
+});
+
+export type PresenceUpdate = z.infer;
+
+// ==========================================
+// Collaborative Editing Protocol
+// ==========================================
+
+/**
+ * Cursor Position Schema
+ * Represents a cursor position in a document
+ */
+export const CursorPositionSchema = z.object({
+ userId: z.string().describe('User identifier'),
+ sessionId: z.string().uuid().describe('Session identifier'),
+ documentId: z.string().describe('Document identifier being edited'),
+ position: z.object({
+ line: z.number().int().nonnegative().describe('Line number (0-indexed)'),
+ column: z.number().int().nonnegative().describe('Column number (0-indexed)'),
+ }).optional().describe('Cursor position in document'),
+ selection: z.object({
+ start: z.object({
+ line: z.number().int().nonnegative(),
+ column: z.number().int().nonnegative(),
+ }),
+ end: z.object({
+ line: z.number().int().nonnegative(),
+ column: z.number().int().nonnegative(),
+ }),
+ }).optional().describe('Selection range (if text is selected)'),
+ color: z.string().optional().describe('Cursor color for visual representation'),
+ userName: z.string().optional().describe('Display name of user'),
+ lastUpdate: z.string().datetime().describe('ISO 8601 datetime of last cursor update'),
+});
+
+export type CursorPosition = z.infer;
+
+/**
+ * Edit Operation Type Enum
+ * Types of edit operations for collaborative editing
+ */
+export const EditOperationType = z.enum([
+ 'insert', // Insert text at position
+ 'delete', // Delete text from range
+ 'replace', // Replace text in range
+]);
+
+export type EditOperationType = z.infer;
+
+/**
+ * Edit Operation Schema
+ * Represents a single edit operation on a document
+ * Supports Operational Transformation (OT) for conflict resolution
+ */
+export const EditOperationSchema = z.object({
+ operationId: z.string().uuid().describe('Unique operation identifier'),
+ documentId: z.string().describe('Document identifier'),
+ userId: z.string().describe('User who performed the edit'),
+ sessionId: z.string().uuid().describe('Session identifier'),
+ type: EditOperationType.describe('Type of edit operation'),
+ position: z.object({
+ line: z.number().int().nonnegative().describe('Line number (0-indexed)'),
+ column: z.number().int().nonnegative().describe('Column number (0-indexed)'),
+ }).describe('Starting position of the operation'),
+ endPosition: z.object({
+ line: z.number().int().nonnegative(),
+ column: z.number().int().nonnegative(),
+ }).optional().describe('Ending position (for delete/replace operations)'),
+ content: z.string().optional().describe('Content to insert/replace'),
+ version: z.number().int().nonnegative().describe('Document version before this operation'),
+ timestamp: z.string().datetime().describe('ISO 8601 datetime when operation was created'),
+ baseOperationId: z.string().uuid().optional().describe('Previous operation ID this builds upon (for OT)'),
+});
+
+export type EditOperation = z.infer;
+
+/**
+ * Document State Schema
+ * Represents the current state of a collaborative document
+ */
+export const DocumentStateSchema = z.object({
+ documentId: z.string().describe('Document identifier'),
+ version: z.number().int().nonnegative().describe('Current document version'),
+ content: z.string().describe('Current document content'),
+ lastModified: z.string().datetime().describe('ISO 8601 datetime of last modification'),
+ activeSessions: z.array(z.string().uuid()).describe('Active editing session IDs'),
+ checksum: z.string().optional().describe('Content checksum for integrity verification'),
+});
+
+export type DocumentState = z.infer;
+
+// ==========================================
+// WebSocket Messages
+// ==========================================
+
+/**
+ * Base WebSocket Message
+ * All WebSocket messages extend this base structure
+ */
+const BaseWebSocketMessage = z.object({
+ messageId: z.string().uuid().describe('Unique message identifier'),
+ type: WebSocketMessageType.describe('Message type'),
+ timestamp: z.string().datetime().describe('ISO 8601 datetime when message was sent'),
+});
+
+/**
+ * Subscribe Message
+ * Client sends this to subscribe to events
+ */
+export const SubscribeMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('subscribe'),
+ subscription: EventSubscriptionSchema.describe('Subscription configuration'),
+});
+
+export type SubscribeMessage = z.infer;
+
+/**
+ * Unsubscribe Message
+ * Client sends this to unsubscribe from events
+ */
+export const UnsubscribeMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('unsubscribe'),
+ request: UnsubscribeRequestSchema.describe('Unsubscribe request'),
+});
+
+export type UnsubscribeMessage = z.infer;
+
+/**
+ * Event Message
+ * Server sends this when a subscribed event occurs
+ */
+export const EventMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('event'),
+ subscriptionId: z.string().uuid().describe('Subscription ID this event belongs to'),
+ eventName: EventNameSchema.describe('Event name'),
+ object: z.string().optional().describe('Object name the event relates to'),
+ payload: z.any().describe('Event payload data'),
+ userId: z.string().optional().describe('User who triggered the event'),
+});
+
+export type EventMessage = z.infer;
+
+/**
+ * Presence Message
+ * Presence update message
+ */
+export const PresenceMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('presence'),
+ presence: PresenceStateSchema.describe('Presence state'),
+});
+
+export type PresenceMessage = z.infer;
+
+/**
+ * Cursor Message
+ * Cursor position update for collaborative editing
+ */
+export const CursorMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('cursor'),
+ cursor: CursorPositionSchema.describe('Cursor position'),
+});
+
+export type CursorMessage = z.infer;
+
+/**
+ * Edit Message
+ * Document edit operation for collaborative editing
+ */
+export const EditMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('edit'),
+ operation: EditOperationSchema.describe('Edit operation'),
+});
+
+export type EditMessage = z.infer;
+
+/**
+ * Acknowledgment Message
+ * Server acknowledges receipt of a message
+ */
+export const AckMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('ack'),
+ ackMessageId: z.string().uuid().describe('ID of the message being acknowledged'),
+ success: z.boolean().describe('Whether the operation was successful'),
+ error: z.string().optional().describe('Error message if operation failed'),
+});
+
+export type AckMessage = z.infer;
+
+/**
+ * Error Message
+ * Server sends error information
+ */
+export const ErrorMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('error'),
+ code: z.string().describe('Error code'),
+ message: z.string().describe('Error message'),
+ details: z.any().optional().describe('Additional error details'),
+});
+
+export type ErrorMessage = z.infer;
+
+/**
+ * Ping Message
+ * Keepalive ping from client or server
+ */
+export const PingMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('ping'),
+});
+
+export type PingMessage = z.infer;
+
+/**
+ * Pong Message
+ * Keepalive pong response
+ */
+export const PongMessageSchema = BaseWebSocketMessage.extend({
+ type: z.literal('pong'),
+ pingMessageId: z.string().uuid().optional().describe('ID of ping message being responded to'),
+});
+
+export type PongMessage = z.infer;
+
+/**
+ * WebSocket Message Union
+ * Discriminated union of all WebSocket message types
+ */
+export const WebSocketMessageSchema = z.discriminatedUnion('type', [
+ SubscribeMessageSchema,
+ UnsubscribeMessageSchema,
+ EventMessageSchema,
+ PresenceMessageSchema,
+ CursorMessageSchema,
+ EditMessageSchema,
+ AckMessageSchema,
+ ErrorMessageSchema,
+ PingMessageSchema,
+ PongMessageSchema,
+]);
+
+export type WebSocketMessage = z.infer;
+
+// ==========================================
+// Connection Configuration
+// ==========================================
+
+/**
+ * WebSocket Connection Config
+ * Configuration for WebSocket connections
+ */
+export const WebSocketConfigSchema = z.object({
+ url: z.string().url().describe('WebSocket server URL'),
+ protocols: z.array(z.string()).optional().describe('WebSocket sub-protocols'),
+ reconnect: z.boolean().optional().default(true).describe('Enable automatic reconnection'),
+ reconnectInterval: z.number().int().positive().optional().default(1000).describe('Reconnection interval in milliseconds'),
+ maxReconnectAttempts: z.number().int().positive().optional().default(5).describe('Maximum reconnection attempts'),
+ pingInterval: z.number().int().positive().optional().default(30000).describe('Ping interval in milliseconds'),
+ timeout: z.number().int().positive().optional().default(5000).describe('Message timeout in milliseconds'),
+ headers: z.record(z.string()).optional().describe('Custom headers for WebSocket handshake'),
+});
+
+export type WebSocketConfig = z.infer;
diff --git a/packages/spec/src/system/collaboration.test.ts b/packages/spec/src/system/collaboration.test.ts
new file mode 100644
index 000000000..2bcc25bf0
--- /dev/null
+++ b/packages/spec/src/system/collaboration.test.ts
@@ -0,0 +1,999 @@
+import { describe, it, expect } from 'vitest';
+import {
+ OTOperationType,
+ OTComponentSchema,
+ OTOperationSchema,
+ OTTransformResultSchema,
+ CRDTType,
+ VectorClockSchema,
+ LWWRegisterSchema,
+ CounterOperationSchema,
+ GCounterSchema,
+ PNCounterSchema,
+ ORSetElementSchema,
+ ORSetSchema,
+ TextCRDTOperationSchema,
+ TextCRDTStateSchema,
+ CRDTStateSchema,
+ CRDTMergeResultSchema,
+ CursorColorPreset,
+ CursorStyleSchema,
+ CursorSelectionSchema,
+ CollaborativeCursorSchema,
+ CursorUpdateSchema,
+ UserActivityStatus,
+ AwarenessUserStateSchema,
+ AwarenessSessionSchema,
+ AwarenessUpdateSchema,
+ AwarenessEventSchema,
+ CollaborationMode,
+ CollaborationSessionConfigSchema,
+ CollaborationSessionSchema,
+ type OTOperation,
+ type LWWRegister,
+ type GCounter,
+ type PNCounter,
+ type ORSet,
+ type TextCRDTState,
+ type CollaborativeCursor,
+ type AwarenessUserState,
+ type CollaborationSession,
+} from './collaboration.zod';
+
+describe('OTOperationType', () => {
+ it('should accept valid OT operation types', () => {
+ const types = ['insert', 'delete', 'retain'];
+
+ types.forEach(type => {
+ expect(() => OTOperationType.parse(type)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid operation types', () => {
+ expect(() => OTOperationType.parse('replace')).toThrow();
+ expect(() => OTOperationType.parse('update')).toThrow();
+ });
+});
+
+describe('OTComponentSchema', () => {
+ it('should accept insert component', () => {
+ const component = {
+ type: 'insert',
+ text: 'Hello, World!',
+ };
+
+ expect(() => OTComponentSchema.parse(component)).not.toThrow();
+ });
+
+ it('should accept insert with attributes', () => {
+ const component = {
+ type: 'insert',
+ text: 'Bold text',
+ attributes: { bold: true, fontSize: 14 },
+ };
+
+ const parsed = OTComponentSchema.parse(component);
+ expect(parsed.attributes).toBeDefined();
+ });
+
+ it('should accept delete component', () => {
+ const component = {
+ type: 'delete',
+ count: 10,
+ };
+
+ expect(() => OTComponentSchema.parse(component)).not.toThrow();
+ });
+
+ it('should accept retain component', () => {
+ const component = {
+ type: 'retain',
+ count: 15,
+ };
+
+ expect(() => OTComponentSchema.parse(component)).not.toThrow();
+ });
+
+ it('should accept retain with attributes', () => {
+ const component = {
+ type: 'retain',
+ count: 20,
+ attributes: { italic: true },
+ };
+
+ const parsed = OTComponentSchema.parse(component);
+ expect(parsed.attributes).toBeDefined();
+ });
+
+ it('should reject negative count', () => {
+ expect(() => OTComponentSchema.parse({
+ type: 'delete',
+ count: -5,
+ })).toThrow();
+
+ expect(() => OTComponentSchema.parse({
+ type: 'retain',
+ count: 0,
+ })).toThrow();
+ });
+});
+
+describe('OTOperationSchema', () => {
+ it('should accept valid OT operation', () => {
+ const operation: OTOperation = {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ components: [
+ { type: 'retain', count: 10 },
+ { type: 'insert', text: 'New text' },
+ { type: 'retain', count: 5 },
+ ],
+ baseVersion: 42,
+ timestamp: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => OTOperationSchema.parse(operation)).not.toThrow();
+ });
+
+ it('should accept operation with metadata', () => {
+ const operation = {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ components: [{ type: 'insert', text: 'Text' }],
+ baseVersion: 10,
+ timestamp: '2024-01-15T10:30:00Z',
+ metadata: { source: 'keyboard', device: 'desktop' },
+ };
+
+ const parsed = OTOperationSchema.parse(operation);
+ expect(parsed.metadata).toBeDefined();
+ });
+
+ it('should reject negative baseVersion', () => {
+ expect(() => OTOperationSchema.parse({
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ components: [{ type: 'insert', text: 'Text' }],
+ baseVersion: -1,
+ timestamp: '2024-01-15T10:30:00Z',
+ })).toThrow();
+ });
+});
+
+describe('OTTransformResultSchema', () => {
+ it('should accept valid transform result', () => {
+ const result = {
+ operation: {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ components: [{ type: 'insert', text: 'Transformed' }],
+ baseVersion: 43,
+ timestamp: '2024-01-15T10:30:00Z',
+ },
+ transformed: true,
+ };
+
+ expect(() => OTTransformResultSchema.parse(result)).not.toThrow();
+ });
+
+ it('should accept result with conflicts', () => {
+ const result = {
+ operation: {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ components: [{ type: 'insert', text: 'Text' }],
+ baseVersion: 42,
+ timestamp: '2024-01-15T10:30:00Z',
+ },
+ transformed: true,
+ conflicts: ['Overlapping edits detected', 'Position adjusted'],
+ };
+
+ const parsed = OTTransformResultSchema.parse(result);
+ expect(parsed.conflicts).toHaveLength(2);
+ });
+});
+
+describe('CRDTType', () => {
+ it('should accept valid CRDT types', () => {
+ const types = [
+ 'lww-register', 'g-counter', 'pn-counter', 'g-set',
+ 'or-set', 'lww-map', 'text', 'tree', 'json',
+ ];
+
+ types.forEach(type => {
+ expect(() => CRDTType.parse(type)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid CRDT types', () => {
+ expect(() => CRDTType.parse('list')).toThrow();
+ expect(() => CRDTType.parse('vector')).toThrow();
+ });
+});
+
+describe('VectorClockSchema', () => {
+ it('should accept valid vector clock', () => {
+ const clock = {
+ clock: {
+ 'replica-1': 5,
+ 'replica-2': 3,
+ 'replica-3': 7,
+ },
+ };
+
+ expect(() => VectorClockSchema.parse(clock)).not.toThrow();
+ });
+
+ it('should reject negative timestamps', () => {
+ expect(() => VectorClockSchema.parse({
+ clock: { 'replica-1': -1 },
+ })).toThrow();
+ });
+});
+
+describe('LWWRegisterSchema', () => {
+ it('should accept valid LWW register', () => {
+ const register: LWWRegister = {
+ type: 'lww-register',
+ value: 'Current value',
+ timestamp: '2024-01-15T10:30:00Z',
+ replicaId: 'replica-1',
+ };
+
+ expect(() => LWWRegisterSchema.parse(register)).not.toThrow();
+ });
+
+ it('should accept register with vector clock', () => {
+ const register = {
+ type: 'lww-register',
+ value: { data: 'object value' },
+ timestamp: '2024-01-15T10:30:00Z',
+ replicaId: 'replica-2',
+ vectorClock: {
+ clock: { 'replica-1': 3, 'replica-2': 5 },
+ },
+ };
+
+ const parsed = LWWRegisterSchema.parse(register);
+ expect(parsed.vectorClock).toBeDefined();
+ });
+});
+
+describe('CounterOperationSchema', () => {
+ it('should accept increment operation', () => {
+ const operation = {
+ replicaId: 'replica-1',
+ delta: 5,
+ timestamp: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => CounterOperationSchema.parse(operation)).not.toThrow();
+ });
+
+ it('should accept decrement operation', () => {
+ const operation = {
+ replicaId: 'replica-1',
+ delta: -3,
+ timestamp: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => CounterOperationSchema.parse(operation)).not.toThrow();
+ });
+});
+
+describe('GCounterSchema', () => {
+ it('should accept valid G-Counter', () => {
+ const counter: GCounter = {
+ type: 'g-counter',
+ counts: {
+ 'replica-1': 10,
+ 'replica-2': 5,
+ 'replica-3': 3,
+ },
+ };
+
+ expect(() => GCounterSchema.parse(counter)).not.toThrow();
+ });
+
+ it('should reject negative counts', () => {
+ expect(() => GCounterSchema.parse({
+ type: 'g-counter',
+ counts: { 'replica-1': -5 },
+ })).toThrow();
+ });
+});
+
+describe('PNCounterSchema', () => {
+ it('should accept valid PN-Counter', () => {
+ const counter: PNCounter = {
+ type: 'pn-counter',
+ positive: { 'replica-1': 10, 'replica-2': 5 },
+ negative: { 'replica-1': 3, 'replica-2': 2 },
+ };
+
+ expect(() => PNCounterSchema.parse(counter)).not.toThrow();
+ });
+
+ it('should reject negative values in positive counts', () => {
+ expect(() => PNCounterSchema.parse({
+ type: 'pn-counter',
+ positive: { 'replica-1': -10 },
+ negative: { 'replica-1': 0 },
+ })).toThrow();
+ });
+});
+
+describe('ORSetElementSchema', () => {
+ it('should accept valid OR-Set element', () => {
+ const element = {
+ value: 'item-1',
+ timestamp: '2024-01-15T10:30:00Z',
+ replicaId: 'replica-1',
+ uid: '550e8400-e29b-41d4-a716-446655440000',
+ };
+
+ expect(() => ORSetElementSchema.parse(element)).not.toThrow();
+ });
+
+ it('should accept removed element', () => {
+ const element = {
+ value: 'item-2',
+ timestamp: '2024-01-15T10:30:00Z',
+ replicaId: 'replica-1',
+ uid: '550e8400-e29b-41d4-a716-446655440000',
+ removed: true,
+ };
+
+ const parsed = ORSetElementSchema.parse(element);
+ expect(parsed.removed).toBe(true);
+ });
+
+ it('should use default false for removed', () => {
+ const element = {
+ value: 'item-3',
+ timestamp: '2024-01-15T10:30:00Z',
+ replicaId: 'replica-1',
+ uid: '550e8400-e29b-41d4-a716-446655440000',
+ };
+
+ const parsed = ORSetElementSchema.parse(element);
+ expect(parsed.removed).toBe(false);
+ });
+});
+
+describe('ORSetSchema', () => {
+ it('should accept valid OR-Set', () => {
+ const set: ORSet = {
+ type: 'or-set',
+ elements: [
+ {
+ value: 'item-1',
+ timestamp: '2024-01-15T10:30:00Z',
+ replicaId: 'replica-1',
+ uid: '550e8400-e29b-41d4-a716-446655440000',
+ },
+ {
+ value: 'item-2',
+ timestamp: '2024-01-15T10:31:00Z',
+ replicaId: 'replica-2',
+ uid: '550e8400-e29b-41d4-a716-446655440001',
+ },
+ ],
+ };
+
+ expect(() => ORSetSchema.parse(set)).not.toThrow();
+ });
+});
+
+describe('TextCRDTOperationSchema', () => {
+ it('should accept insert operation', () => {
+ const operation = {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ replicaId: 'replica-1',
+ position: 10,
+ insert: 'New text',
+ timestamp: '2024-01-15T10:30:00Z',
+ lamportTimestamp: 42,
+ };
+
+ expect(() => TextCRDTOperationSchema.parse(operation)).not.toThrow();
+ });
+
+ it('should accept delete operation', () => {
+ const operation = {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ replicaId: 'replica-1',
+ position: 5,
+ delete: 10,
+ timestamp: '2024-01-15T10:30:00Z',
+ lamportTimestamp: 43,
+ };
+
+ expect(() => TextCRDTOperationSchema.parse(operation)).not.toThrow();
+ });
+
+ it('should reject negative position', () => {
+ expect(() => TextCRDTOperationSchema.parse({
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ replicaId: 'replica-1',
+ position: -1,
+ insert: 'Text',
+ timestamp: '2024-01-15T10:30:00Z',
+ lamportTimestamp: 42,
+ })).toThrow();
+ });
+});
+
+describe('TextCRDTStateSchema', () => {
+ it('should accept valid text CRDT state', () => {
+ const state: TextCRDTState = {
+ type: 'text',
+ documentId: 'doc-123',
+ content: 'Current document content',
+ operations: [
+ {
+ operationId: '550e8400-e29b-41d4-a716-446655440000',
+ replicaId: 'replica-1',
+ position: 0,
+ insert: 'Initial text',
+ timestamp: '2024-01-15T10:30:00Z',
+ lamportTimestamp: 1,
+ },
+ ],
+ lamportClock: 1,
+ vectorClock: {
+ clock: { 'replica-1': 1 },
+ },
+ };
+
+ expect(() => TextCRDTStateSchema.parse(state)).not.toThrow();
+ });
+});
+
+describe('CRDTStateSchema', () => {
+ it('should accept all CRDT types', () => {
+ const states = [
+ {
+ type: 'lww-register',
+ value: 'test',
+ timestamp: '2024-01-15T10:30:00Z',
+ replicaId: 'replica-1',
+ },
+ {
+ type: 'g-counter',
+ counts: { 'replica-1': 5 },
+ },
+ {
+ type: 'pn-counter',
+ positive: { 'replica-1': 10 },
+ negative: { 'replica-1': 3 },
+ },
+ {
+ type: 'or-set',
+ elements: [],
+ },
+ {
+ type: 'text',
+ documentId: 'doc-123',
+ content: 'Text',
+ operations: [],
+ lamportClock: 0,
+ vectorClock: { clock: {} },
+ },
+ ];
+
+ states.forEach(state => {
+ expect(() => CRDTStateSchema.parse(state)).not.toThrow();
+ });
+ });
+
+ it('should use discriminated union on type field', () => {
+ const state = {
+ type: 'g-counter',
+ counts: { 'replica-1': 5 },
+ };
+
+ const parsed = CRDTStateSchema.parse(state);
+ expect(parsed.type).toBe('g-counter');
+ });
+});
+
+describe('CRDTMergeResultSchema', () => {
+ it('should accept merge result without conflicts', () => {
+ const result = {
+ state: {
+ type: 'lww-register',
+ value: 'merged value',
+ timestamp: '2024-01-15T10:30:00Z',
+ replicaId: 'replica-1',
+ },
+ };
+
+ expect(() => CRDTMergeResultSchema.parse(result)).not.toThrow();
+ });
+
+ it('should accept merge result with conflicts', () => {
+ const result = {
+ state: {
+ type: 'g-counter',
+ counts: { 'replica-1': 10 },
+ },
+ conflicts: [
+ {
+ type: 'concurrent-update',
+ description: 'Concurrent updates detected',
+ resolved: true,
+ },
+ ],
+ };
+
+ const parsed = CRDTMergeResultSchema.parse(result);
+ expect(parsed.conflicts).toHaveLength(1);
+ });
+});
+
+describe('CursorColorPreset', () => {
+ it('should accept valid color presets', () => {
+ const colors = [
+ 'blue', 'green', 'red', 'yellow', 'purple',
+ 'orange', 'pink', 'teal', 'indigo', 'cyan',
+ ];
+
+ colors.forEach(color => {
+ expect(() => CursorColorPreset.parse(color)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid presets', () => {
+ expect(() => CursorColorPreset.parse('black')).toThrow();
+ expect(() => CursorColorPreset.parse('white')).toThrow();
+ });
+});
+
+describe('CursorStyleSchema', () => {
+ it('should accept cursor style with preset color', () => {
+ const style = {
+ color: 'blue',
+ };
+
+ expect(() => CursorStyleSchema.parse(style)).not.toThrow();
+ });
+
+ it('should accept cursor style with custom hex color', () => {
+ const style = {
+ color: '#FF5733',
+ };
+
+ expect(() => CursorStyleSchema.parse(style)).not.toThrow();
+ });
+
+ it('should accept cursor style with all options', () => {
+ const style = {
+ color: 'green',
+ opacity: 0.8,
+ label: 'John Doe',
+ showLabel: true,
+ pulseOnUpdate: false,
+ };
+
+ const parsed = CursorStyleSchema.parse(style);
+ expect(parsed.opacity).toBe(0.8);
+ expect(parsed.showLabel).toBe(true);
+ expect(parsed.pulseOnUpdate).toBe(false);
+ });
+
+ it('should use default values', () => {
+ const style = { color: 'red' };
+
+ const parsed = CursorStyleSchema.parse(style);
+ expect(parsed.opacity).toBe(1);
+ expect(parsed.showLabel).toBe(true);
+ expect(parsed.pulseOnUpdate).toBe(true);
+ });
+
+ it('should reject opacity outside range', () => {
+ expect(() => CursorStyleSchema.parse({
+ color: 'blue',
+ opacity: 1.5,
+ })).toThrow();
+
+ expect(() => CursorStyleSchema.parse({
+ color: 'blue',
+ opacity: -0.1,
+ })).toThrow();
+ });
+});
+
+describe('CursorSelectionSchema', () => {
+ it('should accept valid cursor selection', () => {
+ const selection = {
+ anchor: { line: 5, column: 10 },
+ focus: { line: 8, column: 20 },
+ };
+
+ expect(() => CursorSelectionSchema.parse(selection)).not.toThrow();
+ });
+
+ it('should accept selection with direction', () => {
+ const selection = {
+ anchor: { line: 5, column: 10 },
+ focus: { line: 8, column: 20 },
+ direction: 'forward',
+ };
+
+ const parsed = CursorSelectionSchema.parse(selection);
+ expect(parsed.direction).toBe('forward');
+ });
+
+ it('should reject negative positions', () => {
+ expect(() => CursorSelectionSchema.parse({
+ anchor: { line: -1, column: 0 },
+ focus: { line: 5, column: 10 },
+ })).toThrow();
+ });
+});
+
+describe('CollaborativeCursorSchema', () => {
+ it('should accept valid collaborative cursor', () => {
+ const cursor: CollaborativeCursor = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-456',
+ userName: 'John Doe',
+ position: { line: 10, column: 5 },
+ style: { color: 'blue' },
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => CollaborativeCursorSchema.parse(cursor)).not.toThrow();
+ });
+
+ it('should accept cursor with selection', () => {
+ const cursor = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-456',
+ userName: 'Jane Doe',
+ position: { line: 5, column: 0 },
+ selection: {
+ anchor: { line: 5, column: 0 },
+ focus: { line: 10, column: 20 },
+ },
+ style: { color: 'green' },
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ const parsed = CollaborativeCursorSchema.parse(cursor);
+ expect(parsed.selection).toBeDefined();
+ });
+
+ it('should accept cursor with isTyping flag', () => {
+ const cursor = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-456',
+ userName: 'Bob Smith',
+ position: { line: 15, column: 30 },
+ style: { color: 'red' },
+ isTyping: true,
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ const parsed = CollaborativeCursorSchema.parse(cursor);
+ expect(parsed.isTyping).toBe(true);
+ });
+
+ it('should use default false for isTyping', () => {
+ const cursor = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-456',
+ userName: 'Alice',
+ position: { line: 0, column: 0 },
+ style: { color: 'purple' },
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ const parsed = CollaborativeCursorSchema.parse(cursor);
+ expect(parsed.isTyping).toBe(false);
+ });
+});
+
+describe('CursorUpdateSchema', () => {
+ it('should accept position update', () => {
+ const update = {
+ position: { line: 20, column: 15 },
+ };
+
+ expect(() => CursorUpdateSchema.parse(update)).not.toThrow();
+ });
+
+ it('should accept multiple field updates', () => {
+ const update = {
+ position: { line: 10, column: 5 },
+ isTyping: true,
+ metadata: { tool: 'keyboard' },
+ };
+
+ expect(() => CursorUpdateSchema.parse(update)).not.toThrow();
+ });
+});
+
+describe('UserActivityStatus', () => {
+ it('should accept valid activity statuses', () => {
+ const statuses = ['active', 'idle', 'viewing', 'disconnected'];
+
+ statuses.forEach(status => {
+ expect(() => UserActivityStatus.parse(status)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid statuses', () => {
+ expect(() => UserActivityStatus.parse('offline')).toThrow();
+ expect(() => UserActivityStatus.parse('away')).toThrow();
+ });
+});
+
+describe('AwarenessUserStateSchema', () => {
+ it('should accept valid user state', () => {
+ const userState: AwarenessUserState = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ userName: 'John Doe',
+ status: 'active',
+ lastActivity: '2024-01-15T10:30:00Z',
+ joinedAt: '2024-01-15T10:00:00Z',
+ };
+
+ expect(() => AwarenessUserStateSchema.parse(userState)).not.toThrow();
+ });
+
+ it('should accept user state with all optional fields', () => {
+ const userState = {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ userName: 'Jane Doe',
+ userAvatar: 'https://example.com/avatar.jpg',
+ status: 'viewing',
+ currentDocument: 'doc-456',
+ currentView: '/editor',
+ lastActivity: '2024-01-15T10:30:00Z',
+ joinedAt: '2024-01-15T10:00:00Z',
+ permissions: ['read', 'write', 'comment'],
+ metadata: { role: 'editor', team: 'engineering' },
+ };
+
+ const parsed = AwarenessUserStateSchema.parse(userState);
+ expect(parsed.currentDocument).toBe('doc-456');
+ expect(parsed.permissions).toEqual(['read', 'write', 'comment']);
+ });
+});
+
+describe('AwarenessSessionSchema', () => {
+ it('should accept valid awareness session', () => {
+ const session = {
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ users: [
+ {
+ userId: 'user-123',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ userName: 'User 1',
+ status: 'active',
+ lastActivity: '2024-01-15T10:30:00Z',
+ joinedAt: '2024-01-15T10:00:00Z',
+ },
+ ],
+ startedAt: '2024-01-15T10:00:00Z',
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ expect(() => AwarenessSessionSchema.parse(session)).not.toThrow();
+ });
+
+ it('should accept session with documentId', () => {
+ const session = {
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ users: [],
+ startedAt: '2024-01-15T10:00:00Z',
+ lastUpdate: '2024-01-15T10:30:00Z',
+ };
+
+ const parsed = AwarenessSessionSchema.parse(session);
+ expect(parsed.documentId).toBe('doc-123');
+ });
+});
+
+describe('AwarenessUpdateSchema', () => {
+ it('should accept status update', () => {
+ const update = {
+ status: 'idle',
+ };
+
+ expect(() => AwarenessUpdateSchema.parse(update)).not.toThrow();
+ });
+
+ it('should accept multiple field updates', () => {
+ const update = {
+ status: 'active',
+ currentDocument: 'doc-789',
+ currentView: '/dashboard',
+ metadata: { activity: 'editing' },
+ };
+
+ expect(() => AwarenessUpdateSchema.parse(update)).not.toThrow();
+ });
+});
+
+describe('AwarenessEventSchema', () => {
+ it('should accept user joined event', () => {
+ const event = {
+ eventId: '550e8400-e29b-41d4-a716-446655440000',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ eventType: 'user.joined',
+ userId: 'user-123',
+ timestamp: '2024-01-15T10:30:00Z',
+ payload: { userName: 'John Doe' },
+ };
+
+ expect(() => AwarenessEventSchema.parse(event)).not.toThrow();
+ });
+
+ it('should accept all event types', () => {
+ const eventTypes = [
+ 'user.joined',
+ 'user.left',
+ 'user.updated',
+ 'session.created',
+ 'session.ended',
+ ];
+
+ eventTypes.forEach(eventType => {
+ const event = {
+ eventId: '550e8400-e29b-41d4-a716-446655440000',
+ sessionId: '550e8400-e29b-41d4-a716-446655440001',
+ eventType,
+ timestamp: '2024-01-15T10:30:00Z',
+ payload: {},
+ };
+
+ expect(() => AwarenessEventSchema.parse(event)).not.toThrow();
+ });
+ });
+});
+
+describe('CollaborationMode', () => {
+ it('should accept valid collaboration modes', () => {
+ const modes = ['ot', 'crdt', 'lock', 'hybrid'];
+
+ modes.forEach(mode => {
+ expect(() => CollaborationMode.parse(mode)).not.toThrow();
+ });
+ });
+
+ it('should reject invalid modes', () => {
+ expect(() => CollaborationMode.parse('p2p')).toThrow();
+ expect(() => CollaborationMode.parse('centralized')).toThrow();
+ });
+});
+
+describe('CollaborationSessionConfigSchema', () => {
+ it('should accept minimal config', () => {
+ const config = {
+ mode: 'ot',
+ };
+
+ expect(() => CollaborationSessionConfigSchema.parse(config)).not.toThrow();
+ });
+
+ it('should accept config with all options', () => {
+ const config = {
+ mode: 'crdt',
+ enableCursorSharing: true,
+ enablePresence: true,
+ enableAwareness: false,
+ maxUsers: 50,
+ idleTimeout: 600000,
+ conflictResolution: 'crdt',
+ persistence: true,
+ snapshot: {
+ enabled: true,
+ interval: 60000,
+ },
+ };
+
+ const parsed = CollaborationSessionConfigSchema.parse(config);
+ expect(parsed.maxUsers).toBe(50);
+ expect(parsed.snapshot?.enabled).toBe(true);
+ });
+
+ it('should use default values', () => {
+ const config = { mode: 'ot' };
+
+ const parsed = CollaborationSessionConfigSchema.parse(config);
+ expect(parsed.enableCursorSharing).toBe(true);
+ expect(parsed.enablePresence).toBe(true);
+ expect(parsed.enableAwareness).toBe(true);
+ expect(parsed.idleTimeout).toBe(300000);
+ expect(parsed.conflictResolution).toBe('ot');
+ expect(parsed.persistence).toBe(true);
+ });
+});
+
+describe('CollaborationSessionSchema', () => {
+ it('should accept valid collaboration session', () => {
+ const session: CollaborationSession = {
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ config: { mode: 'ot' },
+ users: [],
+ cursors: [],
+ version: 42,
+ createdAt: '2024-01-15T10:00:00Z',
+ lastActivity: '2024-01-15T10:30:00Z',
+ status: 'active',
+ };
+
+ expect(() => CollaborationSessionSchema.parse(session)).not.toThrow();
+ });
+
+ it('should accept session with operations', () => {
+ const session = {
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ config: { mode: 'ot' },
+ users: [],
+ cursors: [],
+ version: 5,
+ operations: [
+ {
+ operationId: '550e8400-e29b-41d4-a716-446655440001',
+ documentId: 'doc-123',
+ userId: 'user-456',
+ sessionId: '550e8400-e29b-41d4-a716-446655440002',
+ components: [{ type: 'insert', text: 'Hello' }],
+ baseVersion: 4,
+ timestamp: '2024-01-15T10:30:00Z',
+ },
+ ],
+ createdAt: '2024-01-15T10:00:00Z',
+ lastActivity: '2024-01-15T10:30:00Z',
+ status: 'active',
+ };
+
+ const parsed = CollaborationSessionSchema.parse(session);
+ expect(parsed.operations).toHaveLength(1);
+ });
+
+ it('should accept all status values', () => {
+ const statuses = ['active', 'idle', 'ended'];
+
+ statuses.forEach(status => {
+ const session = {
+ sessionId: '550e8400-e29b-41d4-a716-446655440000',
+ documentId: 'doc-123',
+ config: { mode: 'ot' },
+ users: [],
+ cursors: [],
+ version: 0,
+ createdAt: '2024-01-15T10:00:00Z',
+ lastActivity: '2024-01-15T10:30:00Z',
+ status,
+ };
+
+ const parsed = CollaborationSessionSchema.parse(session);
+ expect(parsed.status).toBe(status);
+ });
+ });
+});
diff --git a/packages/spec/src/system/collaboration.zod.ts b/packages/spec/src/system/collaboration.zod.ts
new file mode 100644
index 000000000..d11a27fda
--- /dev/null
+++ b/packages/spec/src/system/collaboration.zod.ts
@@ -0,0 +1,482 @@
+import { z } from 'zod';
+
+/**
+ * Real-Time Collaboration Protocol
+ *
+ * Defines schemas for real-time collaborative editing in ObjectStack.
+ * Supports Operational Transformation (OT), CRDT (Conflict-free Replicated Data Types),
+ * cursor sharing, and awareness state for collaborative applications.
+ *
+ * Industry alignment: Google Docs, Figma, VSCode Live Share, Yjs
+ */
+
+// ==========================================
+// Operational Transformation (OT)
+// ==========================================
+
+/**
+ * OT Operation Type Enum
+ * Types of operations in Operational Transformation
+ */
+export const OTOperationType = z.enum([
+ 'insert', // Insert characters at position
+ 'delete', // Delete characters at position
+ 'retain', // Keep characters (used for composing operations)
+]);
+
+export type OTOperationType = z.infer;
+
+/**
+ * OT Operation Component
+ * Single component of an OT operation
+ */
+export const OTComponentSchema = z.discriminatedUnion('type', [
+ z.object({
+ type: z.literal('insert'),
+ text: z.string().describe('Text to insert'),
+ attributes: z.record(z.any()).optional().describe('Text formatting attributes (e.g., bold, italic)'),
+ }),
+ z.object({
+ type: z.literal('delete'),
+ count: z.number().int().positive().describe('Number of characters to delete'),
+ }),
+ z.object({
+ type: z.literal('retain'),
+ count: z.number().int().positive().describe('Number of characters to retain'),
+ attributes: z.record(z.any()).optional().describe('Attribute changes to apply'),
+ }),
+]);
+
+export type OTComponent = z.infer;
+
+/**
+ * OT Operation Schema
+ * Represents a complete OT operation
+ * Based on the OT algorithm used by Google Docs and other collaborative editors
+ */
+export const OTOperationSchema = z.object({
+ operationId: z.string().uuid().describe('Unique operation identifier'),
+ documentId: z.string().describe('Document identifier'),
+ userId: z.string().describe('User who created the operation'),
+ sessionId: z.string().uuid().describe('Session identifier'),
+ components: z.array(OTComponentSchema).describe('Operation components'),
+ baseVersion: z.number().int().nonnegative().describe('Document version this operation is based on'),
+ timestamp: z.string().datetime().describe('ISO 8601 datetime when operation was created'),
+ metadata: z.record(z.any()).optional().describe('Additional operation metadata'),
+});
+
+export type OTOperation = z.infer;
+
+/**
+ * OT Transform Result
+ * Result of transforming one operation against another
+ */
+export const OTTransformResultSchema = z.object({
+ operation: OTOperationSchema.describe('Transformed operation'),
+ transformed: z.boolean().describe('Whether transformation was applied'),
+ conflicts: z.array(z.string()).optional().describe('Conflict descriptions if any'),
+});
+
+export type OTTransformResult = z.infer;
+
+// ==========================================
+// CRDT (Conflict-free Replicated Data Types)
+// ==========================================
+
+/**
+ * CRDT Type Enum
+ * Types of CRDTs supported
+ */
+export const CRDTType = z.enum([
+ 'lww-register', // Last-Write-Wins Register
+ 'g-counter', // Grow-only Counter
+ 'pn-counter', // Positive-Negative Counter
+ 'g-set', // Grow-only Set
+ 'or-set', // Observed-Remove Set
+ 'lww-map', // Last-Write-Wins Map
+ 'text', // CRDT-based Text (e.g., Yjs, Automerge)
+ 'tree', // CRDT-based Tree structure
+ 'json', // CRDT-based JSON (e.g., Automerge)
+]);
+
+export type CRDTType = z.infer;
+
+/**
+ * Vector Clock Schema
+ * Tracks causality in distributed systems
+ */
+export const VectorClockSchema = z.object({
+ clock: z.record(z.number().int().nonnegative()).describe('Map of replica ID to logical timestamp'),
+});
+
+export type VectorClock = z.infer;
+
+/**
+ * LWW-Register Schema
+ * Last-Write-Wins Register CRDT
+ */
+export const LWWRegisterSchema = z.object({
+ type: z.literal('lww-register'),
+ value: z.any().describe('Current register value'),
+ timestamp: z.string().datetime().describe('ISO 8601 datetime of last write'),
+ replicaId: z.string().describe('ID of replica that performed last write'),
+ vectorClock: VectorClockSchema.optional().describe('Optional vector clock for causality tracking'),
+});
+
+export type LWWRegister = z.infer;
+
+/**
+ * Counter Operation Schema
+ * Operations for Counter CRDTs
+ */
+export const CounterOperationSchema = z.object({
+ replicaId: z.string().describe('Replica identifier'),
+ delta: z.number().int().describe('Change amount (positive for increment, negative for decrement)'),
+ timestamp: z.string().datetime().describe('ISO 8601 datetime of operation'),
+});
+
+export type CounterOperation = z.infer;
+
+/**
+ * G-Counter Schema
+ * Grow-only Counter CRDT
+ */
+export const GCounterSchema = z.object({
+ type: z.literal('g-counter'),
+ counts: z.record(z.number().int().nonnegative()).describe('Map of replica ID to count'),
+});
+
+export type GCounter = z.infer;
+
+/**
+ * PN-Counter Schema
+ * Positive-Negative Counter CRDT (supports increment and decrement)
+ */
+export const PNCounterSchema = z.object({
+ type: z.literal('pn-counter'),
+ positive: z.record(z.number().int().nonnegative()).describe('Positive increments per replica'),
+ negative: z.record(z.number().int().nonnegative()).describe('Negative increments per replica'),
+});
+
+export type PNCounter = z.infer;
+
+/**
+ * OR-Set Element Schema
+ * Element in an Observed-Remove Set
+ */
+export const ORSetElementSchema = z.object({
+ value: z.any().describe('Element value'),
+ timestamp: z.string().datetime().describe('Addition timestamp'),
+ replicaId: z.string().describe('Replica that added the element'),
+ uid: z.string().uuid().describe('Unique identifier for this addition'),
+ removed: z.boolean().optional().default(false).describe('Whether element has been removed'),
+});
+
+export type ORSetElement = z.infer;
+
+/**
+ * OR-Set Schema
+ * Observed-Remove Set CRDT
+ */
+export const ORSetSchema = z.object({
+ type: z.literal('or-set'),
+ elements: z.array(ORSetElementSchema).describe('Set elements with metadata'),
+});
+
+export type ORSet = z.infer;
+
+/**
+ * Text CRDT Operation Schema
+ * Operations for text-based CRDTs (e.g., Yjs, Automerge)
+ */
+export const TextCRDTOperationSchema = z.object({
+ operationId: z.string().uuid().describe('Unique operation identifier'),
+ replicaId: z.string().describe('Replica identifier'),
+ position: z.number().int().nonnegative().describe('Position in document'),
+ insert: z.string().optional().describe('Text to insert'),
+ delete: z.number().int().positive().optional().describe('Number of characters to delete'),
+ timestamp: z.string().datetime().describe('ISO 8601 datetime of operation'),
+ lamportTimestamp: z.number().int().nonnegative().describe('Lamport timestamp for ordering'),
+});
+
+export type TextCRDTOperation = z.infer;
+
+/**
+ * Text CRDT State Schema
+ * State of a text-based CRDT document
+ */
+export const TextCRDTStateSchema = z.object({
+ type: z.literal('text'),
+ documentId: z.string().describe('Document identifier'),
+ content: z.string().describe('Current text content'),
+ operations: z.array(TextCRDTOperationSchema).describe('History of operations'),
+ lamportClock: z.number().int().nonnegative().describe('Current Lamport clock value'),
+ vectorClock: VectorClockSchema.describe('Vector clock for causality'),
+});
+
+export type TextCRDTState = z.infer;
+
+/**
+ * CRDT State Union
+ * Discriminated union of all CRDT types
+ */
+export const CRDTStateSchema = z.discriminatedUnion('type', [
+ LWWRegisterSchema,
+ GCounterSchema,
+ PNCounterSchema,
+ ORSetSchema,
+ TextCRDTStateSchema,
+]);
+
+export type CRDTState = z.infer;
+
+/**
+ * CRDT Merge Schema
+ * Result of merging two CRDT states
+ */
+export const CRDTMergeResultSchema = z.object({
+ state: CRDTStateSchema.describe('Merged CRDT state'),
+ conflicts: z.array(z.object({
+ type: z.string().describe('Conflict type'),
+ description: z.string().describe('Conflict description'),
+ resolved: z.boolean().describe('Whether conflict was automatically resolved'),
+ })).optional().describe('Conflicts encountered during merge'),
+});
+
+export type CRDTMergeResult = z.infer;
+
+// ==========================================
+// Cursor Sharing
+// ==========================================
+
+/**
+ * Cursor Color Preset Enum
+ * Standard color presets for cursor visualization
+ */
+export const CursorColorPreset = z.enum([
+ 'blue',
+ 'green',
+ 'red',
+ 'yellow',
+ 'purple',
+ 'orange',
+ 'pink',
+ 'teal',
+ 'indigo',
+ 'cyan',
+]);
+
+export type CursorColorPreset = z.infer;
+
+/**
+ * Cursor Style Schema
+ * Visual styling for collaborative cursors
+ */
+export const CursorStyleSchema = z.object({
+ color: z.union([CursorColorPreset, z.string()]).describe('Cursor color (preset or custom hex)'),
+ opacity: z.number().min(0).max(1).optional().default(1).describe('Cursor opacity (0-1)'),
+ label: z.string().optional().describe('Label to display with cursor (usually username)'),
+ showLabel: z.boolean().optional().default(true).describe('Whether to show label'),
+ pulseOnUpdate: z.boolean().optional().default(true).describe('Whether to pulse when cursor moves'),
+});
+
+export type CursorStyle = z.infer;
+
+/**
+ * Cursor Selection Schema
+ * Represents a text selection in collaborative editing
+ */
+export const CursorSelectionSchema = z.object({
+ anchor: z.object({
+ line: z.number().int().nonnegative().describe('Anchor line number'),
+ column: z.number().int().nonnegative().describe('Anchor column number'),
+ }).describe('Selection anchor (start point)'),
+ focus: z.object({
+ line: z.number().int().nonnegative().describe('Focus line number'),
+ column: z.number().int().nonnegative().describe('Focus column number'),
+ }).describe('Selection focus (end point)'),
+ direction: z.enum(['forward', 'backward']).optional().describe('Selection direction'),
+});
+
+export type CursorSelection = z.infer;
+
+/**
+ * Collaborative Cursor Schema
+ * Complete cursor state for a collaborative user
+ */
+export const CollaborativeCursorSchema = z.object({
+ userId: z.string().describe('User identifier'),
+ sessionId: z.string().uuid().describe('Session identifier'),
+ documentId: z.string().describe('Document identifier'),
+ userName: z.string().describe('Display name of user'),
+ position: z.object({
+ line: z.number().int().nonnegative().describe('Cursor line number (0-indexed)'),
+ column: z.number().int().nonnegative().describe('Cursor column number (0-indexed)'),
+ }).describe('Current cursor position'),
+ selection: CursorSelectionSchema.optional().describe('Current text selection'),
+ style: CursorStyleSchema.describe('Visual style for this cursor'),
+ isTyping: z.boolean().optional().default(false).describe('Whether user is currently typing'),
+ lastUpdate: z.string().datetime().describe('ISO 8601 datetime of last cursor update'),
+ metadata: z.record(z.any()).optional().describe('Additional cursor metadata'),
+});
+
+export type CollaborativeCursor = z.infer;
+
+/**
+ * Cursor Update Schema
+ * Update to a collaborative cursor
+ */
+export const CursorUpdateSchema = z.object({
+ position: z.object({
+ line: z.number().int().nonnegative(),
+ column: z.number().int().nonnegative(),
+ }).optional().describe('Updated cursor position'),
+ selection: CursorSelectionSchema.optional().describe('Updated selection'),
+ isTyping: z.boolean().optional().describe('Updated typing state'),
+ metadata: z.record(z.any()).optional().describe('Updated metadata'),
+});
+
+export type CursorUpdate = z.infer;
+
+// ==========================================
+// Awareness State
+// ==========================================
+
+/**
+ * User Activity Status Enum
+ * User activity status for awareness
+ */
+export const UserActivityStatus = z.enum([
+ 'active', // User is actively editing
+ 'idle', // User is idle but connected
+ 'viewing', // User is viewing but not editing
+ 'disconnected', // User is disconnected
+]);
+
+export type UserActivityStatus = z.infer;
+
+/**
+ * Awareness User State Schema
+ * Tracks what a user is doing in the collaborative session
+ */
+export const AwarenessUserStateSchema = z.object({
+ userId: z.string().describe('User identifier'),
+ sessionId: z.string().uuid().describe('Session identifier'),
+ userName: z.string().describe('Display name'),
+ userAvatar: z.string().optional().describe('User avatar URL'),
+ status: UserActivityStatus.describe('Current activity status'),
+ currentDocument: z.string().optional().describe('Document ID user is currently editing'),
+ currentView: z.string().optional().describe('Current view/page user is on'),
+ lastActivity: z.string().datetime().describe('ISO 8601 datetime of last activity'),
+ joinedAt: z.string().datetime().describe('ISO 8601 datetime when user joined session'),
+ permissions: z.array(z.string()).optional().describe('User permissions in this session'),
+ metadata: z.record(z.any()).optional().describe('Additional user state metadata'),
+});
+
+export type AwarenessUserState = z.infer;
+
+/**
+ * Awareness Session Schema
+ * Represents the complete awareness state for a collaboration session
+ */
+export const AwarenessSessionSchema = z.object({
+ sessionId: z.string().uuid().describe('Session identifier'),
+ documentId: z.string().optional().describe('Document ID this session is for'),
+ users: z.array(AwarenessUserStateSchema).describe('Active users in session'),
+ startedAt: z.string().datetime().describe('ISO 8601 datetime when session started'),
+ lastUpdate: z.string().datetime().describe('ISO 8601 datetime of last update'),
+ metadata: z.record(z.any()).optional().describe('Session metadata'),
+});
+
+export type AwarenessSession = z.infer;
+
+/**
+ * Awareness Update Schema
+ * Update to awareness state
+ */
+export const AwarenessUpdateSchema = z.object({
+ status: UserActivityStatus.optional().describe('Updated status'),
+ currentDocument: z.string().optional().describe('Updated current document'),
+ currentView: z.string().optional().describe('Updated current view'),
+ metadata: z.record(z.any()).optional().describe('Updated metadata'),
+});
+
+export type AwarenessUpdate = z.infer;
+
+/**
+ * Awareness Event Schema
+ * Events that occur in awareness tracking
+ */
+export const AwarenessEventSchema = z.object({
+ eventId: z.string().uuid().describe('Event identifier'),
+ sessionId: z.string().uuid().describe('Session identifier'),
+ eventType: z.enum([
+ 'user.joined',
+ 'user.left',
+ 'user.updated',
+ 'session.created',
+ 'session.ended',
+ ]).describe('Type of awareness event'),
+ userId: z.string().optional().describe('User involved in event'),
+ timestamp: z.string().datetime().describe('ISO 8601 datetime of event'),
+ payload: z.any().describe('Event payload'),
+});
+
+export type AwarenessEvent = z.infer;
+
+// ==========================================
+// Collaboration Session Management
+// ==========================================
+
+/**
+ * Collaboration Mode Enum
+ * Types of collaboration modes
+ */
+export const CollaborationMode = z.enum([
+ 'ot', // Operational Transformation
+ 'crdt', // CRDT-based
+ 'lock', // Pessimistic locking (turn-based)
+ 'hybrid', // Hybrid approach
+]);
+
+export type CollaborationMode = z.infer;
+
+/**
+ * Collaboration Session Config
+ * Configuration for a collaboration session
+ */
+export const CollaborationSessionConfigSchema = z.object({
+ mode: CollaborationMode.describe('Collaboration mode to use'),
+ enableCursorSharing: z.boolean().optional().default(true).describe('Enable cursor sharing'),
+ enablePresence: z.boolean().optional().default(true).describe('Enable presence tracking'),
+ enableAwareness: z.boolean().optional().default(true).describe('Enable awareness state'),
+ maxUsers: z.number().int().positive().optional().describe('Maximum concurrent users'),
+ idleTimeout: z.number().int().positive().optional().default(300000).describe('Idle timeout in milliseconds'),
+ conflictResolution: z.enum(['ot', 'crdt', 'manual']).optional().default('ot').describe('Conflict resolution strategy'),
+ persistence: z.boolean().optional().default(true).describe('Enable operation persistence'),
+ snapshot: z.object({
+ enabled: z.boolean().describe('Enable periodic snapshots'),
+ interval: z.number().int().positive().describe('Snapshot interval in milliseconds'),
+ }).optional().describe('Snapshot configuration'),
+});
+
+export type CollaborationSessionConfig = z.infer;
+
+/**
+ * Collaboration Session Schema
+ * Complete collaboration session state
+ */
+export const CollaborationSessionSchema = z.object({
+ sessionId: z.string().uuid().describe('Session identifier'),
+ documentId: z.string().describe('Document identifier'),
+ config: CollaborationSessionConfigSchema.describe('Session configuration'),
+ users: z.array(AwarenessUserStateSchema).describe('Active users'),
+ cursors: z.array(CollaborativeCursorSchema).describe('Active cursors'),
+ version: z.number().int().nonnegative().describe('Current document version'),
+ operations: z.array(z.union([OTOperationSchema, TextCRDTOperationSchema])).optional().describe('Recent operations'),
+ createdAt: z.string().datetime().describe('ISO 8601 datetime when session was created'),
+ lastActivity: z.string().datetime().describe('ISO 8601 datetime of last activity'),
+ status: z.enum(['active', 'idle', 'ended']).describe('Session status'),
+});
+
+export type CollaborationSession = z.infer;
diff --git a/packages/spec/src/system/index.ts b/packages/spec/src/system/index.ts
index d3298d978..e1cc8f019 100644
--- a/packages/spec/src/system/index.ts
+++ b/packages/spec/src/system/index.ts
@@ -12,6 +12,7 @@ export * from './translation.zod';
export * from './events.zod';
export * from './job.zod';
export * from './feature.zod';
+export * from './collaboration.zod';
export * from './types';
// Re-export Core System Definitions