socratic-knowledge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. socratic_knowledge/__init__.py +26 -0
  2. socratic_knowledge/access/__init__.py +9 -0
  3. socratic_knowledge/access/permissions.py +234 -0
  4. socratic_knowledge/access/rbac.py +37 -0
  5. socratic_knowledge/audit/__init__.py +10 -0
  6. socratic_knowledge/audit/events.py +91 -0
  7. socratic_knowledge/audit/logger.py +216 -0
  8. socratic_knowledge/collaboration/__init__.py +13 -0
  9. socratic_knowledge/collaboration/conflict.py +273 -0
  10. socratic_knowledge/collaboration/locks.py +186 -0
  11. socratic_knowledge/core/__init__.py +15 -0
  12. socratic_knowledge/core/collection.py +139 -0
  13. socratic_knowledge/core/knowledge_item.py +145 -0
  14. socratic_knowledge/core/tenant.py +89 -0
  15. socratic_knowledge/core/user.py +51 -0
  16. socratic_knowledge/core/version.py +99 -0
  17. socratic_knowledge/integrations/__init__.py +9 -0
  18. socratic_knowledge/integrations/langchain/__init__.py +5 -0
  19. socratic_knowledge/integrations/langchain/tools.py +228 -0
  20. socratic_knowledge/integrations/openclaw/__init__.py +5 -0
  21. socratic_knowledge/integrations/openclaw/skill.py +327 -0
  22. socratic_knowledge/manager.py +825 -0
  23. socratic_knowledge/retrieval/__init__.py +12 -0
  24. socratic_knowledge/retrieval/indexer.py +80 -0
  25. socratic_knowledge/retrieval/rag_integration.py +152 -0
  26. socratic_knowledge/retrieval/search.py +183 -0
  27. socratic_knowledge/storage/__init__.py +9 -0
  28. socratic_knowledge/storage/base.py +264 -0
  29. socratic_knowledge/storage/sqlite_store.py +606 -0
  30. socratic_knowledge/versioning/__init__.py +9 -0
  31. socratic_knowledge/versioning/history.py +126 -0
  32. socratic_knowledge/versioning/version_model.py +35 -0
  33. socratic_knowledge-0.1.0.dist-info/METADATA +340 -0
  34. socratic_knowledge-0.1.0.dist-info/RECORD +37 -0
  35. socratic_knowledge-0.1.0.dist-info/WHEEL +5 -0
  36. socratic_knowledge-0.1.0.dist-info/licenses/LICENSE +21 -0
  37. socratic_knowledge-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,26 @@
1
+ """
2
+ Socratic Knowledge - Enterprise knowledge management system.
3
+
4
+ Provides multi-tenant knowledge management with access control, versioning,
5
+ and RAG integration.
6
+ """
7
+
8
+ from .core.collection import Collection
9
+ from .core.knowledge_item import KnowledgeItem
10
+ from .core.tenant import Tenant
11
+ from .core.user import User
12
+ from .core.version import Version
13
+ from .manager import KnowledgeManager
14
+
15
+ __version__ = "0.1.0"
16
+
17
+ __all__ = [
18
+ # Core models
19
+ "KnowledgeItem",
20
+ "Collection",
21
+ "Tenant",
22
+ "User",
23
+ "Version",
24
+ # Manager
25
+ "KnowledgeManager",
26
+ ]
@@ -0,0 +1,9 @@
1
+ """Access control module."""
2
+
3
+ from .rbac import ROLE_PERMISSIONS, Permission, Role
4
+
5
+ __all__ = [
6
+ "Permission",
7
+ "Role",
8
+ "ROLE_PERMISSIONS",
9
+ ]
@@ -0,0 +1,234 @@
1
+ """Permission checking and enforcement."""
2
+
3
+ from typing import Dict, List, Optional, Set
4
+
5
+ from ..core.collection import Collection
6
+ from ..core.knowledge_item import KnowledgeItem
7
+ from .rbac import Permission, Role, ROLE_PERMISSIONS
8
+
9
+
10
+ class AccessControl:
11
+ """
12
+ Role-based access control (RBAC) engine.
13
+
14
+ Checks if user has permission to perform action on resource.
15
+ """
16
+
17
+ @staticmethod
18
+ def check_permission(
19
+ user_id: str,
20
+ item: KnowledgeItem,
21
+ permission: Permission,
22
+ ) -> bool:
23
+ """
24
+ Check if user has permission on item.
25
+
26
+ Args:
27
+ user_id: User ID
28
+ item: Knowledge item
29
+ permission: Permission to check
30
+
31
+ Returns:
32
+ bool: True if user has permission
33
+ """
34
+ # Owner always has access
35
+ if item.owner_id == user_id:
36
+ return True
37
+
38
+ # Check role-based permissions
39
+ if not item.permissions:
40
+ return False
41
+
42
+ for role_str, user_ids in item.permissions.items():
43
+ if user_id in user_ids:
44
+ try:
45
+ role = Role[role_str.upper()]
46
+ except KeyError:
47
+ continue
48
+
49
+ role_permissions = ROLE_PERMISSIONS.get(role, set())
50
+ if permission in role_permissions:
51
+ return True
52
+
53
+ return False
54
+
55
+ @staticmethod
56
+ def check_collection_permission(
57
+ user_id: str,
58
+ collection: Collection,
59
+ permission: Permission,
60
+ storage: Optional[object] = None,
61
+ ) -> bool:
62
+ """
63
+ Check permission with inheritance from parent collections.
64
+
65
+ Args:
66
+ user_id: User ID
67
+ collection: Collection
68
+ permission: Permission to check
69
+ storage: Storage instance for fetching parents
70
+
71
+ Returns:
72
+ bool: True if user has permission
73
+ """
74
+ # Direct permission
75
+ if AccessControl._has_collection_permission(
76
+ user_id,
77
+ collection,
78
+ permission,
79
+ ):
80
+ return True
81
+
82
+ # Check parent hierarchy if inheritance enabled
83
+ if collection.inherit_permissions and collection.parent_id and storage:
84
+ parent = storage.get_collection(collection.parent_id)
85
+ if parent:
86
+ return AccessControl.check_collection_permission(
87
+ user_id,
88
+ parent,
89
+ permission,
90
+ storage,
91
+ )
92
+
93
+ return False
94
+
95
+ @staticmethod
96
+ def _has_collection_permission(
97
+ user_id: str,
98
+ collection: Collection,
99
+ permission: Permission,
100
+ ) -> bool:
101
+ """
102
+ Check direct permission on collection.
103
+
104
+ Args:
105
+ user_id: User ID
106
+ collection: Collection
107
+ permission: Permission to check
108
+
109
+ Returns:
110
+ bool: True if user has permission
111
+ """
112
+ # Owner always has access
113
+ if collection.owner_id == user_id:
114
+ return True
115
+
116
+ if not collection.permissions:
117
+ return False
118
+
119
+ for role_str, user_ids in collection.permissions.items():
120
+ if user_id in user_ids:
121
+ try:
122
+ role = Role[role_str.upper()]
123
+ except KeyError:
124
+ continue
125
+
126
+ role_permissions = ROLE_PERMISSIONS.get(role, set())
127
+ if permission in role_permissions:
128
+ return True
129
+
130
+ return False
131
+
132
+ @staticmethod
133
+ def grant_permission(
134
+ item: KnowledgeItem,
135
+ user_id: str,
136
+ role: Role,
137
+ ) -> None:
138
+ """
139
+ Grant permission to user.
140
+
141
+ Args:
142
+ item: Knowledge item
143
+ user_id: User to grant permission to
144
+ role: Role to grant
145
+ """
146
+ if item.permissions is None:
147
+ item.permissions = {}
148
+
149
+ role_str = role.value
150
+ if role_str not in item.permissions:
151
+ item.permissions[role_str] = []
152
+
153
+ if user_id not in item.permissions[role_str]:
154
+ item.permissions[role_str].append(user_id)
155
+
156
+ @staticmethod
157
+ def revoke_permission(
158
+ item: KnowledgeItem,
159
+ user_id: str,
160
+ role: Optional[Role] = None,
161
+ ) -> None:
162
+ """
163
+ Revoke permission from user.
164
+
165
+ Args:
166
+ item: Knowledge item
167
+ user_id: User to revoke permission from
168
+ role: Specific role to revoke, or None to revoke all
169
+ """
170
+ if not item.permissions:
171
+ return
172
+
173
+ if role:
174
+ role_str = role.value
175
+ if role_str in item.permissions:
176
+ if user_id in item.permissions[role_str]:
177
+ item.permissions[role_str].remove(user_id)
178
+ else:
179
+ # Revoke all roles for user
180
+ for role_str in item.permissions:
181
+ if user_id in item.permissions[role_str]:
182
+ item.permissions[role_str].remove(user_id)
183
+
184
+ @staticmethod
185
+ def grant_collection_permission(
186
+ collection: Collection,
187
+ user_id: str,
188
+ role: Role,
189
+ ) -> None:
190
+ """
191
+ Grant permission on collection.
192
+
193
+ Args:
194
+ collection: Collection
195
+ user_id: User to grant permission to
196
+ role: Role to grant
197
+ """
198
+ if collection.permissions is None:
199
+ collection.permissions = {}
200
+
201
+ role_str = role.value
202
+ if role_str not in collection.permissions:
203
+ collection.permissions[role_str] = []
204
+
205
+ if user_id not in collection.permissions[role_str]:
206
+ collection.permissions[role_str].append(user_id)
207
+
208
+ @staticmethod
209
+ def revoke_collection_permission(
210
+ collection: Collection,
211
+ user_id: str,
212
+ role: Optional[Role] = None,
213
+ ) -> None:
214
+ """
215
+ Revoke permission on collection.
216
+
217
+ Args:
218
+ collection: Collection
219
+ user_id: User to revoke permission from
220
+ role: Specific role to revoke, or None to revoke all
221
+ """
222
+ if not collection.permissions:
223
+ return
224
+
225
+ if role:
226
+ role_str = role.value
227
+ if role_str in collection.permissions:
228
+ if user_id in collection.permissions[role_str]:
229
+ collection.permissions[role_str].remove(user_id)
230
+ else:
231
+ # Revoke all roles for user
232
+ for role_str in collection.permissions:
233
+ if user_id in collection.permissions[role_str]:
234
+ collection.permissions[role_str].remove(user_id)
@@ -0,0 +1,37 @@
1
+ """Role-Based Access Control (RBAC) implementation."""
2
+
3
+ from enum import Enum
4
+ from typing import Set
5
+
6
+
7
+ class Permission(Enum):
8
+ """Fine-grained permissions."""
9
+
10
+ READ = "read"
11
+ WRITE = "write"
12
+ DELETE = "delete"
13
+ SHARE = "share"
14
+ ADMIN = "admin"
15
+
16
+
17
+ class Role(Enum):
18
+ """Predefined roles with permission sets."""
19
+
20
+ VIEWER = "viewer"
21
+ EDITOR = "editor"
22
+ ADMIN = "admin"
23
+ OWNER = "owner"
24
+
25
+
26
+ ROLE_PERMISSIONS: dict[Role, Set[Permission]] = {
27
+ Role.VIEWER: {Permission.READ},
28
+ Role.EDITOR: {Permission.READ, Permission.WRITE},
29
+ Role.ADMIN: {Permission.READ, Permission.WRITE, Permission.DELETE, Permission.SHARE},
30
+ Role.OWNER: {
31
+ Permission.READ,
32
+ Permission.WRITE,
33
+ Permission.DELETE,
34
+ Permission.SHARE,
35
+ Permission.ADMIN,
36
+ },
37
+ }
@@ -0,0 +1,10 @@
1
+ """Audit logging and compliance module."""
2
+
3
+ from .events import AuditEvent, AuditEventType
4
+ from .logger import AuditLogger
5
+
6
+ __all__ = [
7
+ "AuditEvent",
8
+ "AuditEventType",
9
+ "AuditLogger",
10
+ ]
@@ -0,0 +1,91 @@
1
+ """Audit event types and models."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime, timezone
5
+ from enum import Enum
6
+ from typing import Any, Dict, Optional
7
+
8
+
9
+ class AuditEventType(Enum):
10
+ """Types of audit events."""
11
+
12
+ # Item operations
13
+ ITEM_CREATED = "item_created"
14
+ ITEM_UPDATED = "item_updated"
15
+ ITEM_DELETED = "item_deleted"
16
+ ITEM_VIEWED = "item_viewed"
17
+
18
+ # Collection operations
19
+ COLLECTION_CREATED = "collection_created"
20
+ COLLECTION_UPDATED = "collection_updated"
21
+ COLLECTION_DELETED = "collection_deleted"
22
+
23
+ # Tenant operations
24
+ TENANT_CREATED = "tenant_created"
25
+ TENANT_UPDATED = "tenant_updated"
26
+
27
+ # Access control
28
+ PERMISSION_GRANTED = "permission_granted"
29
+ PERMISSION_REVOKED = "permission_revoked"
30
+
31
+ # Version operations
32
+ VERSION_CREATED = "version_created"
33
+ ITEM_ROLLED_BACK = "item_rolled_back"
34
+
35
+ # Search operations
36
+ SEARCH_EXECUTED = "search_executed"
37
+
38
+ # Conflict operations
39
+ CONFLICT_DETECTED = "conflict_detected"
40
+ CONFLICT_RESOLVED = "conflict_resolved"
41
+
42
+
43
+ @dataclass
44
+ class AuditEvent:
45
+ """Represents a single audit event."""
46
+
47
+ event_id: str
48
+ event_type: AuditEventType
49
+ tenant_id: str
50
+ user_id: str
51
+ resource_type: str # "item", "collection", "tenant", etc.
52
+ resource_id: str
53
+ action: str # "create", "update", "delete", etc.
54
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
55
+ changes: Dict[str, Any] = field(default_factory=dict)
56
+ metadata: Dict[str, Any] = field(default_factory=dict)
57
+ ip_address: Optional[str] = None
58
+ user_agent: Optional[str] = None
59
+
60
+ def to_dict(self) -> Dict[str, Any]:
61
+ """Serialize to dictionary."""
62
+ return {
63
+ "event_id": self.event_id,
64
+ "event_type": self.event_type.value,
65
+ "tenant_id": self.tenant_id,
66
+ "user_id": self.user_id,
67
+ "resource_type": self.resource_type,
68
+ "resource_id": self.resource_id,
69
+ "action": self.action,
70
+ "timestamp": self.timestamp.isoformat(),
71
+ "changes": self.changes,
72
+ "metadata": self.metadata,
73
+ "ip_address": self.ip_address,
74
+ "user_agent": self.user_agent,
75
+ }
76
+
77
+ @classmethod
78
+ def from_dict(cls, data: Dict[str, Any]) -> "AuditEvent":
79
+ """Deserialize from dictionary."""
80
+ event_type = data.get("event_type")
81
+ if isinstance(event_type, str):
82
+ data = {
83
+ **data,
84
+ "event_type": AuditEventType(event_type),
85
+ }
86
+
87
+ timestamp = data.get("timestamp")
88
+ if isinstance(timestamp, str):
89
+ data = {**data, "timestamp": datetime.fromisoformat(timestamp)}
90
+
91
+ return cls(**data)
@@ -0,0 +1,216 @@
1
+ """Audit logging for knowledge management operations."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+ from uuid import uuid4
5
+
6
+ from .events import AuditEvent, AuditEventType
7
+
8
+
9
+ class AuditLogger:
10
+ """
11
+ Audit logging system for tracking all operations.
12
+
13
+ Maintains a complete audit trail for compliance and debugging.
14
+ """
15
+
16
+ def __init__(self):
17
+ """Initialize audit logger."""
18
+ self._events: List[AuditEvent] = []
19
+
20
+ def log_event(
21
+ self,
22
+ event_type: AuditEventType,
23
+ tenant_id: str,
24
+ user_id: str,
25
+ resource_type: str,
26
+ resource_id: str,
27
+ action: str,
28
+ changes: Optional[Dict[str, Any]] = None,
29
+ metadata: Optional[Dict[str, Any]] = None,
30
+ ip_address: Optional[str] = None,
31
+ user_agent: Optional[str] = None,
32
+ ) -> AuditEvent:
33
+ """
34
+ Log an audit event.
35
+
36
+ Args:
37
+ event_type: Type of event
38
+ tenant_id: Tenant ID
39
+ user_id: User performing action
40
+ resource_type: Type of resource (item, collection, etc.)
41
+ resource_id: ID of resource
42
+ action: Action performed (create, update, delete, etc.)
43
+ changes: Dictionary of changes made
44
+ metadata: Optional metadata
45
+ ip_address: Optional IP address of user
46
+ user_agent: Optional user agent string
47
+
48
+ Returns:
49
+ AuditEvent: Logged event
50
+ """
51
+ event = AuditEvent(
52
+ event_id=str(uuid4()),
53
+ event_type=event_type,
54
+ tenant_id=tenant_id,
55
+ user_id=user_id,
56
+ resource_type=resource_type,
57
+ resource_id=resource_id,
58
+ action=action,
59
+ changes=changes or {},
60
+ metadata=metadata or {},
61
+ ip_address=ip_address,
62
+ user_agent=user_agent,
63
+ )
64
+ self._events.append(event)
65
+ return event
66
+
67
+ def get_events(
68
+ self,
69
+ tenant_id: Optional[str] = None,
70
+ user_id: Optional[str] = None,
71
+ resource_id: Optional[str] = None,
72
+ event_type: Optional[AuditEventType] = None,
73
+ limit: int = 100,
74
+ offset: int = 0,
75
+ ) -> List[AuditEvent]:
76
+ """
77
+ Query audit events.
78
+
79
+ Args:
80
+ tenant_id: Filter by tenant
81
+ user_id: Filter by user
82
+ resource_id: Filter by resource
83
+ event_type: Filter by event type
84
+ limit: Maximum events to return
85
+ offset: Number of events to skip
86
+
87
+ Returns:
88
+ List[AuditEvent]: Matching events
89
+ """
90
+ results = self._events
91
+
92
+ if tenant_id:
93
+ results = [e for e in results if e.tenant_id == tenant_id]
94
+
95
+ if user_id:
96
+ results = [e for e in results if e.user_id == user_id]
97
+
98
+ if resource_id:
99
+ results = [e for e in results if e.resource_id == resource_id]
100
+
101
+ if event_type:
102
+ results = [e for e in results if e.event_type == event_type]
103
+
104
+ # Return in reverse chronological order
105
+ results = list(reversed(results))
106
+
107
+ return results[offset : offset + limit]
108
+
109
+ def get_user_activity(
110
+ self,
111
+ tenant_id: str,
112
+ user_id: str,
113
+ limit: int = 50,
114
+ ) -> List[AuditEvent]:
115
+ """
116
+ Get activity for specific user.
117
+
118
+ Args:
119
+ tenant_id: Tenant ID
120
+ user_id: User ID
121
+ limit: Maximum events to return
122
+
123
+ Returns:
124
+ List[AuditEvent]: User's recent actions
125
+ """
126
+ return self.get_events(
127
+ tenant_id=tenant_id,
128
+ user_id=user_id,
129
+ limit=limit,
130
+ )
131
+
132
+ def get_resource_history(
133
+ self,
134
+ resource_id: str,
135
+ limit: int = 50,
136
+ ) -> List[AuditEvent]:
137
+ """
138
+ Get change history for specific resource.
139
+
140
+ Args:
141
+ resource_id: Resource ID
142
+ limit: Maximum events to return
143
+
144
+ Returns:
145
+ List[AuditEvent]: Resource history
146
+ """
147
+ return self.get_events(
148
+ resource_id=resource_id,
149
+ limit=limit,
150
+ )
151
+
152
+ def get_tenant_audit_log(
153
+ self,
154
+ tenant_id: str,
155
+ limit: int = 100,
156
+ ) -> List[AuditEvent]:
157
+ """
158
+ Get complete audit log for tenant.
159
+
160
+ Args:
161
+ tenant_id: Tenant ID
162
+ limit: Maximum events to return
163
+
164
+ Returns:
165
+ List[AuditEvent]: Tenant audit trail
166
+ """
167
+ return self.get_events(
168
+ tenant_id=tenant_id,
169
+ limit=limit,
170
+ )
171
+
172
+ def clear_events(self, tenant_id: Optional[str] = None) -> int:
173
+ """
174
+ Clear audit events.
175
+
176
+ Args:
177
+ tenant_id: If specified, only clear events for this tenant
178
+
179
+ Returns:
180
+ int: Number of events cleared
181
+ """
182
+ if not tenant_id:
183
+ count = len(self._events)
184
+ self._events = []
185
+ return count
186
+
187
+ original_count = len(self._events)
188
+ self._events = [e for e in self._events if e.tenant_id != tenant_id]
189
+ return original_count - len(self._events)
190
+
191
+ def count_events(
192
+ self,
193
+ tenant_id: Optional[str] = None,
194
+ user_id: Optional[str] = None,
195
+ ) -> int:
196
+ """
197
+ Count audit events.
198
+
199
+ Args:
200
+ tenant_id: Optional tenant filter
201
+ user_id: Optional user filter
202
+
203
+ Returns:
204
+ int: Number of matching events
205
+ """
206
+ count = len(self._events)
207
+
208
+ if tenant_id:
209
+ count = len([e for e in self._events if e.tenant_id == tenant_id])
210
+
211
+ if user_id:
212
+ count = len(
213
+ [e for e in self._events if e.tenant_id == tenant_id and e.user_id == user_id]
214
+ )
215
+
216
+ return count
@@ -0,0 +1,13 @@
1
+ """Collaboration and concurrent edit management module."""
2
+
3
+ from .conflict import Conflict, ConflictDetector, ConflictResolutionStrategy, ConflictType
4
+ from .locks import LockToken, OptimisticLockManager
5
+
6
+ __all__ = [
7
+ "LockToken",
8
+ "OptimisticLockManager",
9
+ "Conflict",
10
+ "ConflictType",
11
+ "ConflictDetector",
12
+ "ConflictResolutionStrategy",
13
+ ]