ADR-008: Historical-Collaborative Content Architecture
Status
Accepted | Proposed | Superseded | Extends ADR-007
Version History
- v1: 2025-04-30 - Initial proposal
Context
The compositional content architecture introduced in ADR-007 provides a solid foundation for managing content across different environments. However, as the system evolves, there are growing requirements for:
Content History Management: The need to track, navigate, and restore different versions of content over time.
Collaborative Editing: Supporting real-time and asynchronous collaborative workflows where multiple users can edit the same content.
Branch-Based Workflows: Enabling parallel content development with branches, similar to version control systems like Git.
Conflict Resolution: Detecting and resolving conflicts that arise during collaborative editing or branch merging.
Distributed Content Operations: Supporting offline editing and eventual consistency across disconnected environments.
These requirements require an enhanced architecture that builds upon the compositional foundation while adding sophisticated historical tracking and collaboration capabilities.
Decision
Implement a Historical-Collaborative Content Architecture that extends the compositional approach with:
Operation-Based Content History: Track all content changes as operations that can be replayed, reverted, or selectively applied.
Vector Clock Synchronization: Use vector clocks to track causality in distributed environments and enable conflict detection.
Branch Management: Support multiple parallel branches of content development with merge capabilities.
Conflict Detection and Resolution: Provide mechanisms to detect, resolve, and prevent conflicts during collaborative editing.
Real-time Collaboration Primitives: Add presence, awareness, and selection tracking for collaborative editing sessions.
The enhanced architecture will be built as extensions to the existing compositional architecture, preserving compatibility with the existing content store interface while adding new capabilities:
interface HistoricalContentStore extends ContentStore {
// History operations
getHistory(uri: string, options?: HistoryQuery): Promise<ContentHistory>
getVersion(
uri: string,
versionId: string,
options?: GetVersionOptions
): Promise<Content | null>
createVersion(
uri: string,
options?: CreateVersionOptions
): Promise<ContentVersion>
revertToVersion(
uri: string,
versionId: string,
options?: RevertOptions
): Promise<void>
// Branch operations
createBranch(
uri: string,
options: CreateBranchOptions
): Promise<ContentBranch>
mergeBranch(uri: string, options: MergeBranchOptions): Promise<void>
listBranches(uri: string): Promise<ContentBranch[]>
compareBranches(
uri: string,
options: CompareBranchesOptions
): Promise<{
differences: Array<{ operation: ContentOperation; branch: string }>
}>
// Collaborative operations
startCollaboration(
uri: string,
options?: CollaborationOptions
): Promise<CollaborationSession>
joinSession(sessionId: string, options: JoinSessionOptions): Promise<void>
leaveSession(sessionId: string): Promise<void>
// Enhanced read/write operations
read(uri: string, options?: ReadOptions): Promise<Content>
write(uri: string, content: Content, options?: WriteOptions): Promise<void>
// Extension point for specialized operations
extendedOperations?: Record<string, Function>
}
Alternatives Considered
1. External Version Control System
Relying on Git or a similar VCS for content versioning.
- Pros: Proven, widely-used versioning capabilities; familiar workflows for developers
- Cons: Complex integration; dependency on external systems; overhead for simple content changes
- Outcome: Rejected - too heavyweight and complex for content-specific needs
2. Snapshot-Based History
Storing complete content snapshots at each version point.
- Pros: Simple implementation; fast retrieval of any version
- Cons: Storage inefficiency; lacks granular operation tracking; difficult merging
- Outcome: Partially adopted - snapshots will be used strategically for performance, but not as the primary history mechanism
3. CRDT-Based Collaboration
Using Conflict-free Replicated Data Types (CRDTs) as the foundation for collaborative editing.
- Pros: Excellent for real-time collaboration; automatic conflict resolution; well-researched
- Cons: Complex implementation; larger code size; potential performance issues; limited to specific data structures
- Outcome: Rejected for core architecture but may be incorporated for specific real-time editing features
Consequences
Benefits
Complete History Tracking: Every content change is tracked and can be audited, reverted, or replayed.
Flexible Collaboration Models: Supports both real-time and asynchronous collaboration patterns.
Branch-Based Workflows: Enables content teams to work in parallel with structured workflows.
Offline Capability: Content can be edited offline with changes synchronized when connectivity returns.
Extensible Foundation: The operation-based approach allows for specialized operations and custom workflows.
Challenges
Increased Complexity: More sophisticated architecture increases learning curve and development effort.
Performance Considerations: Tracking all operations requires careful optimization to maintain performance.
Storage Requirements: Historical data increases storage needs; requires careful management.
Conflict Resolution UI: Developing intuitive interfaces for conflict resolution is challenging.
Synchronization Edge Cases: Distributed systems introduce complex edge cases that must be handled.
Implementation
The implementation will follow these key principles:
1. Operation-Based History
All content changes will be tracked as operations:
const store = createHistoricalContentStore()
// Operations are automatically tracked
await store.write('article.md', updatedContent)
// Retrieve history
const history = await store.getHistory('article.md')
console.log(`${history.operations.length} changes recorded`)
// Create a named version
await store.createVersion('article.md', {
label: 'First draft',
message: 'Completed initial draft',
})
// Revert to a specific version
await store.revertToVersion('article.md', 'v1')
2. Branch Management
Content can be developed in parallel branches:
// Create a new branch
const newBranch = await store.createBranch('article.md', {
name: 'alternative-ending',
basedOn: 'main',
description: 'Exploring a different narrative conclusion',
metadata: { category: 'narrative-experiment' },
})
// Work on the branch...
await store.write('article.md', branchContent, {
branch: newBranch.id,
message: 'Updated alternative ending',
createVersion: true,
})
// Compare branches before merging
const comparison = await store.compareBranches('article.md', {
branch1: newBranch.id,
branch2: 'main',
includeDiff: true,
})
// Later, merge back to main
await store.mergeBranch('article.md', {
from: newBranch.id,
to: 'main',
strategy: 'manual',
createVersion: true,
message: 'Merged alternative ending into main narrative',
})
3. Collaborative Editing
Enable multiple users to work together:
// Start a collaborative session
const session = await store.startCollaboration('article.md', {
branch: 'feature-xyz',
maxParticipants: 5,
allowConcurrentEditing: true,
security: {
requireAuth: true,
allowedRoles: ['editor', 'reviewer'],
},
})
// Join as a participant
await store.joinSession(session.id, {
name: 'User Name',
initialStatus: 'active',
metadata: { role: 'editor', avatar: 'avatar-url.png' },
onEvent: handleCollaborationEvent,
onConflict: handleConflict,
})
// Real-time presence information
const participant: CollaborationParticipant = {
id: userId,
name: 'User Name',
joinedAt: Date.now(),
status: 'active',
selection: {
path: ['content'],
start: 120,
end: 145,
timestamp: Date.now(),
},
metadata: { focusedSection: 'introduction' },
}
session.awareness.updatePresence(userId, {
status: participant.status,
cursor: participant.selection,
})
4. Conflict Resolution
Handle conflicts between concurrent edits:
// Configure conflict resolution
const store = createHistoricalContentStore({
conflictResolution: {
defaultStrategy: 'operation-specific',
operationStrategies: {
insert: 'last-writer-wins',
delete: 'manual',
// ...
},
manualResolutionHandler: async conflict => {
// Show UI for manual resolution
return userResolvedConflict
},
},
})
5. Vector Clock Synchronization
Track causality across distributed environments:
// Vector clocks track operation ordering
const contentWithClock = await store.read('article.md', {
includeVectorClock: true,
includeVersionInfo: true,
})
// Create a new vector clock for client operations
const updatedClock = VectorClocks.increment(
contentWithClock.vectorClock,
clientId
)
// Operations include vector clocks
await store.write('article.md', newContent, {
vectorClock: updatedClock,
author: '[email protected]',
message: 'Updated article content with vector clock tracking',
})
// Detect concurrent operations using the VectorClocks namespace
const relation = VectorClocks.compare(op1.vectorClock, op2.vectorClock)
if (relation === 'concurrent') {
console.log('Detected concurrent operations, need conflict resolution')
// Handle concurrent operations
} else if (relation === 'before') {
console.log('Operation 1 happened before Operation 2')
} else if (relation === 'after') {
console.log('Operation 1 happened after Operation 2')
} else {
console.log('Operations have identical vector clocks')
}
// Merge vector clocks after synchronizing
const mergedClock = VectorClocks.merge(localVectorClock, remoteVectorClock)