alma-memory 0.5.0__py3-none-any.whl → 0.7.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 (111) hide show
  1. alma/__init__.py +296 -194
  2. alma/compression/__init__.py +33 -0
  3. alma/compression/pipeline.py +980 -0
  4. alma/confidence/__init__.py +47 -47
  5. alma/confidence/engine.py +540 -540
  6. alma/confidence/types.py +351 -351
  7. alma/config/loader.py +157 -157
  8. alma/consolidation/__init__.py +23 -23
  9. alma/consolidation/engine.py +678 -678
  10. alma/consolidation/prompts.py +84 -84
  11. alma/core.py +1189 -322
  12. alma/domains/__init__.py +30 -30
  13. alma/domains/factory.py +359 -359
  14. alma/domains/schemas.py +448 -448
  15. alma/domains/types.py +272 -272
  16. alma/events/__init__.py +75 -75
  17. alma/events/emitter.py +285 -284
  18. alma/events/storage_mixin.py +246 -246
  19. alma/events/types.py +126 -126
  20. alma/events/webhook.py +425 -425
  21. alma/exceptions.py +49 -49
  22. alma/extraction/__init__.py +31 -31
  23. alma/extraction/auto_learner.py +265 -264
  24. alma/extraction/extractor.py +420 -420
  25. alma/graph/__init__.py +106 -81
  26. alma/graph/backends/__init__.py +32 -18
  27. alma/graph/backends/kuzu.py +624 -0
  28. alma/graph/backends/memgraph.py +432 -0
  29. alma/graph/backends/memory.py +236 -236
  30. alma/graph/backends/neo4j.py +417 -417
  31. alma/graph/base.py +159 -159
  32. alma/graph/extraction.py +198 -198
  33. alma/graph/store.py +860 -860
  34. alma/harness/__init__.py +35 -35
  35. alma/harness/base.py +386 -386
  36. alma/harness/domains.py +705 -705
  37. alma/initializer/__init__.py +37 -37
  38. alma/initializer/initializer.py +418 -418
  39. alma/initializer/types.py +250 -250
  40. alma/integration/__init__.py +62 -62
  41. alma/integration/claude_agents.py +444 -432
  42. alma/integration/helena.py +423 -423
  43. alma/integration/victor.py +471 -471
  44. alma/learning/__init__.py +101 -86
  45. alma/learning/decay.py +878 -0
  46. alma/learning/forgetting.py +1446 -1446
  47. alma/learning/heuristic_extractor.py +390 -390
  48. alma/learning/protocols.py +374 -374
  49. alma/learning/validation.py +346 -346
  50. alma/mcp/__init__.py +123 -45
  51. alma/mcp/__main__.py +156 -156
  52. alma/mcp/resources.py +122 -122
  53. alma/mcp/server.py +955 -591
  54. alma/mcp/tools.py +3254 -511
  55. alma/observability/__init__.py +91 -0
  56. alma/observability/config.py +302 -0
  57. alma/observability/guidelines.py +170 -0
  58. alma/observability/logging.py +424 -0
  59. alma/observability/metrics.py +583 -0
  60. alma/observability/tracing.py +440 -0
  61. alma/progress/__init__.py +21 -21
  62. alma/progress/tracker.py +607 -607
  63. alma/progress/types.py +250 -250
  64. alma/retrieval/__init__.py +134 -53
  65. alma/retrieval/budget.py +525 -0
  66. alma/retrieval/cache.py +1304 -1061
  67. alma/retrieval/embeddings.py +202 -202
  68. alma/retrieval/engine.py +850 -366
  69. alma/retrieval/modes.py +365 -0
  70. alma/retrieval/progressive.py +560 -0
  71. alma/retrieval/scoring.py +344 -344
  72. alma/retrieval/trust_scoring.py +637 -0
  73. alma/retrieval/verification.py +797 -0
  74. alma/session/__init__.py +19 -19
  75. alma/session/manager.py +442 -399
  76. alma/session/types.py +288 -288
  77. alma/storage/__init__.py +101 -61
  78. alma/storage/archive.py +233 -0
  79. alma/storage/azure_cosmos.py +1259 -1048
  80. alma/storage/base.py +1083 -525
  81. alma/storage/chroma.py +1443 -1443
  82. alma/storage/constants.py +103 -0
  83. alma/storage/file_based.py +614 -619
  84. alma/storage/migrations/__init__.py +21 -0
  85. alma/storage/migrations/base.py +321 -0
  86. alma/storage/migrations/runner.py +323 -0
  87. alma/storage/migrations/version_stores.py +337 -0
  88. alma/storage/migrations/versions/__init__.py +11 -0
  89. alma/storage/migrations/versions/v1_0_0.py +373 -0
  90. alma/storage/migrations/versions/v1_1_0_workflow_context.py +551 -0
  91. alma/storage/pinecone.py +1080 -1080
  92. alma/storage/postgresql.py +1948 -1452
  93. alma/storage/qdrant.py +1306 -1306
  94. alma/storage/sqlite_local.py +3041 -1358
  95. alma/testing/__init__.py +46 -0
  96. alma/testing/factories.py +301 -0
  97. alma/testing/mocks.py +389 -0
  98. alma/types.py +292 -264
  99. alma/utils/__init__.py +19 -0
  100. alma/utils/tokenizer.py +521 -0
  101. alma/workflow/__init__.py +83 -0
  102. alma/workflow/artifacts.py +170 -0
  103. alma/workflow/checkpoint.py +311 -0
  104. alma/workflow/context.py +228 -0
  105. alma/workflow/outcomes.py +189 -0
  106. alma/workflow/reducers.py +393 -0
  107. {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/METADATA +244 -72
  108. alma_memory-0.7.0.dist-info/RECORD +112 -0
  109. alma_memory-0.5.0.dist-info/RECORD +0 -76
  110. {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
  111. {alma_memory-0.5.0.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,21 @@
1
+ """
2
+ ALMA Schema Migration Framework.
3
+
4
+ Provides version tracking and migration capabilities for storage backends.
5
+ """
6
+
7
+ from alma.storage.migrations.base import (
8
+ Migration,
9
+ MigrationError,
10
+ MigrationRegistry,
11
+ SchemaVersion,
12
+ )
13
+ from alma.storage.migrations.runner import MigrationRunner
14
+
15
+ __all__ = [
16
+ "Migration",
17
+ "MigrationError",
18
+ "MigrationRegistry",
19
+ "MigrationRunner",
20
+ "SchemaVersion",
21
+ ]
@@ -0,0 +1,321 @@
1
+ """
2
+ ALMA Migration Framework - Base Classes.
3
+
4
+ Provides abstract migration classes and version tracking utilities.
5
+ """
6
+
7
+ import logging
8
+ from abc import ABC, abstractmethod
9
+ from dataclasses import dataclass, field
10
+ from datetime import datetime, timezone
11
+ from typing import Any, Callable, Dict, List, Optional, Type
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class MigrationError(Exception):
17
+ """Exception raised when a migration fails."""
18
+
19
+ def __init__(
20
+ self,
21
+ message: str,
22
+ version: Optional[str] = None,
23
+ cause: Optional[Exception] = None,
24
+ ):
25
+ self.version = version
26
+ self.cause = cause
27
+ super().__init__(message)
28
+
29
+
30
+ @dataclass
31
+ class SchemaVersion:
32
+ """
33
+ Represents a schema version record.
34
+
35
+ Attributes:
36
+ version: Semantic version string (e.g., "1.0.0")
37
+ applied_at: When the migration was applied
38
+ description: Human-readable description of changes
39
+ checksum: Optional hash for integrity verification
40
+ """
41
+
42
+ version: str
43
+ applied_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
44
+ description: str = ""
45
+ checksum: Optional[str] = None
46
+
47
+ def __lt__(self, other: "SchemaVersion") -> bool:
48
+ """Compare versions for sorting."""
49
+ return self._parse_version(self.version) < self._parse_version(other.version)
50
+
51
+ @staticmethod
52
+ def _parse_version(version: str) -> tuple:
53
+ """Parse version string into comparable tuple."""
54
+ parts = version.split(".")
55
+ result = []
56
+ for part in parts:
57
+ try:
58
+ result.append(int(part))
59
+ except ValueError:
60
+ result.append(part)
61
+ return tuple(result)
62
+
63
+
64
+ class Migration(ABC):
65
+ """
66
+ Abstract base class for schema migrations.
67
+
68
+ Subclasses must implement upgrade() and optionally downgrade().
69
+
70
+ Example:
71
+ class AddTagsColumn(Migration):
72
+ version = "1.1.0"
73
+ description = "Add tags column to heuristics table"
74
+
75
+ def upgrade(self, connection):
76
+ connection.execute(
77
+ "ALTER TABLE heuristics ADD COLUMN tags TEXT"
78
+ )
79
+
80
+ def downgrade(self, connection):
81
+ connection.execute(
82
+ "ALTER TABLE heuristics DROP COLUMN tags"
83
+ )
84
+ """
85
+
86
+ # These must be set by subclasses
87
+ version: str = ""
88
+ description: str = ""
89
+ # Optional: previous version this migration depends on
90
+ depends_on: Optional[str] = None
91
+
92
+ @abstractmethod
93
+ def upgrade(self, connection: Any) -> None:
94
+ """
95
+ Apply the migration.
96
+
97
+ Args:
98
+ connection: Database connection or storage instance
99
+ """
100
+ pass
101
+
102
+ def downgrade(self, connection: Any) -> None:
103
+ """
104
+ Revert the migration (optional).
105
+
106
+ Args:
107
+ connection: Database connection or storage instance
108
+
109
+ Raises:
110
+ NotImplementedError: If downgrade is not supported
111
+ """
112
+ raise NotImplementedError(
113
+ f"Downgrade not implemented for migration {self.version}"
114
+ )
115
+
116
+ def pre_check(self, connection: Any) -> bool:
117
+ """
118
+ Optional pre-migration check.
119
+
120
+ Override to verify prerequisites before migration.
121
+
122
+ Args:
123
+ connection: Database connection
124
+
125
+ Returns:
126
+ True if migration can proceed, False otherwise
127
+ """
128
+ return True
129
+
130
+ def post_check(self, connection: Any) -> bool:
131
+ """
132
+ Optional post-migration verification.
133
+
134
+ Override to verify migration was successful.
135
+
136
+ Args:
137
+ connection: Database connection
138
+
139
+ Returns:
140
+ True if migration was successful
141
+ """
142
+ return True
143
+
144
+
145
+ class MigrationRegistry:
146
+ """
147
+ Registry for available migrations.
148
+
149
+ Manages migration discovery, ordering, and execution planning.
150
+ """
151
+
152
+ def __init__(self) -> None:
153
+ self._migrations: Dict[str, Type[Migration]] = {}
154
+ self._backend_migrations: Dict[str, Dict[str, Type[Migration]]] = {}
155
+
156
+ def register(
157
+ self, migration_class: Type[Migration], backend: Optional[str] = None
158
+ ) -> Type[Migration]:
159
+ """
160
+ Register a migration class.
161
+
162
+ Args:
163
+ migration_class: The migration class to register
164
+ backend: Optional backend name (e.g., "sqlite", "postgresql")
165
+
166
+ Returns:
167
+ The migration class (for use as decorator)
168
+ """
169
+ version = migration_class.version
170
+ if not version:
171
+ raise ValueError(
172
+ f"Migration {migration_class.__name__} must have a version"
173
+ )
174
+
175
+ if backend:
176
+ if backend not in self._backend_migrations:
177
+ self._backend_migrations[backend] = {}
178
+ self._backend_migrations[backend][version] = migration_class
179
+ logger.debug(f"Registered migration {version} for backend {backend}")
180
+ else:
181
+ self._migrations[version] = migration_class
182
+ logger.debug(f"Registered global migration {version}")
183
+
184
+ return migration_class
185
+
186
+ def get_migration(
187
+ self, version: str, backend: Optional[str] = None
188
+ ) -> Optional[Type[Migration]]:
189
+ """
190
+ Get a migration class by version.
191
+
192
+ Args:
193
+ version: Version string to look up
194
+ backend: Optional backend name
195
+
196
+ Returns:
197
+ Migration class or None if not found
198
+ """
199
+ if backend and backend in self._backend_migrations:
200
+ migration = self._backend_migrations[backend].get(version)
201
+ if migration:
202
+ return migration
203
+ return self._migrations.get(version)
204
+
205
+ def get_all_migrations(
206
+ self, backend: Optional[str] = None
207
+ ) -> List[Type[Migration]]:
208
+ """
209
+ Get all migrations in version order.
210
+
211
+ Args:
212
+ backend: Optional backend name to filter migrations
213
+
214
+ Returns:
215
+ List of migration classes sorted by version
216
+ """
217
+ migrations = dict(self._migrations)
218
+ if backend and backend in self._backend_migrations:
219
+ migrations.update(self._backend_migrations[backend])
220
+
221
+ return [
222
+ cls
223
+ for _, cls in sorted(
224
+ migrations.items(),
225
+ key=lambda x: SchemaVersion._parse_version(x[0]),
226
+ )
227
+ ]
228
+
229
+ def get_pending_migrations(
230
+ self,
231
+ current_version: Optional[str],
232
+ backend: Optional[str] = None,
233
+ ) -> List[Type[Migration]]:
234
+ """
235
+ Get migrations that need to be applied.
236
+
237
+ Args:
238
+ current_version: Current schema version (None if fresh install)
239
+ backend: Optional backend name
240
+
241
+ Returns:
242
+ List of migration classes that need to be applied
243
+ """
244
+ all_migrations = self.get_all_migrations(backend)
245
+
246
+ if current_version is None:
247
+ return all_migrations
248
+
249
+ current = SchemaVersion._parse_version(current_version)
250
+ return [
251
+ m
252
+ for m in all_migrations
253
+ if SchemaVersion._parse_version(m.version) > current
254
+ ]
255
+
256
+ def get_rollback_migrations(
257
+ self,
258
+ current_version: str,
259
+ target_version: str,
260
+ backend: Optional[str] = None,
261
+ ) -> List[Type[Migration]]:
262
+ """
263
+ Get migrations that need to be rolled back.
264
+
265
+ Args:
266
+ current_version: Current schema version
267
+ target_version: Target version to roll back to
268
+ backend: Optional backend name
269
+
270
+ Returns:
271
+ List of migration classes to roll back (in reverse order)
272
+ """
273
+ all_migrations = self.get_all_migrations(backend)
274
+
275
+ current = SchemaVersion._parse_version(current_version)
276
+ target = SchemaVersion._parse_version(target_version)
277
+
278
+ rollback = [
279
+ m
280
+ for m in all_migrations
281
+ if target < SchemaVersion._parse_version(m.version) <= current
282
+ ]
283
+
284
+ # Return in reverse order for rollback
285
+ return list(reversed(rollback))
286
+
287
+
288
+ # Global registry instance
289
+ _global_registry = MigrationRegistry()
290
+
291
+
292
+ def register_migration(
293
+ backend: Optional[str] = None,
294
+ ) -> Callable[[Type[Migration]], Type[Migration]]:
295
+ """
296
+ Decorator to register a migration class.
297
+
298
+ Args:
299
+ backend: Optional backend name
300
+
301
+ Example:
302
+ @register_migration()
303
+ class MyMigration(Migration):
304
+ version = "1.0.0"
305
+ ...
306
+
307
+ @register_migration(backend="postgresql")
308
+ class PostgresSpecificMigration(Migration):
309
+ version = "1.0.1"
310
+ ...
311
+ """
312
+
313
+ def decorator(cls: Type[Migration]) -> Type[Migration]:
314
+ return _global_registry.register(cls, backend)
315
+
316
+ return decorator
317
+
318
+
319
+ def get_registry() -> MigrationRegistry:
320
+ """Get the global migration registry."""
321
+ return _global_registry
@@ -0,0 +1,323 @@
1
+ """
2
+ ALMA Migration Framework - Migration Runner.
3
+
4
+ Executes migrations and manages schema version tracking.
5
+ """
6
+
7
+ import hashlib
8
+ import logging
9
+ from datetime import datetime, timezone
10
+ from typing import Any, Callable, Dict, List, Optional, Protocol, Type
11
+
12
+ from alma.storage.migrations.base import (
13
+ Migration,
14
+ MigrationError,
15
+ MigrationRegistry,
16
+ SchemaVersion,
17
+ get_registry,
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class VersionStore(Protocol):
24
+ """Protocol for schema version storage."""
25
+
26
+ def get_current_version(self) -> Optional[str]:
27
+ """Get the current schema version."""
28
+ ...
29
+
30
+ def get_version_history(self) -> List[SchemaVersion]:
31
+ """Get all applied versions in order."""
32
+ ...
33
+
34
+ def record_version(self, version: SchemaVersion) -> None:
35
+ """Record a new version."""
36
+ ...
37
+
38
+ def remove_version(self, version: str) -> None:
39
+ """Remove a version record (for rollback)."""
40
+ ...
41
+
42
+
43
+ class MigrationRunner:
44
+ """
45
+ Executes schema migrations and tracks versions.
46
+
47
+ Handles:
48
+ - Forward migrations (upgrades)
49
+ - Backward migrations (rollbacks)
50
+ - Version tracking
51
+ - Pre/post migration checks
52
+ - Dry run mode
53
+ """
54
+
55
+ # Current schema version for fresh installations
56
+ CURRENT_SCHEMA_VERSION = "1.0.0"
57
+
58
+ def __init__(
59
+ self,
60
+ version_store: VersionStore,
61
+ registry: Optional[MigrationRegistry] = None,
62
+ backend: Optional[str] = None,
63
+ ):
64
+ """
65
+ Initialize migration runner.
66
+
67
+ Args:
68
+ version_store: Storage for version tracking
69
+ registry: Migration registry (uses global if not provided)
70
+ backend: Backend name for filtering migrations
71
+ """
72
+ self.version_store = version_store
73
+ self.registry = registry or get_registry()
74
+ self.backend = backend
75
+ self._hooks: Dict[str, List[Callable]] = {
76
+ "pre_migrate": [],
77
+ "post_migrate": [],
78
+ "pre_rollback": [],
79
+ "post_rollback": [],
80
+ }
81
+
82
+ def add_hook(self, event: str, callback: Callable) -> None:
83
+ """
84
+ Add a hook callback for migration events.
85
+
86
+ Args:
87
+ event: Event name (pre_migrate, post_migrate, etc.)
88
+ callback: Function to call
89
+ """
90
+ if event in self._hooks:
91
+ self._hooks[event].append(callback)
92
+
93
+ def _run_hooks(self, event: str, *args: Any, **kwargs: Any) -> None:
94
+ """Run all hooks for an event."""
95
+ for callback in self._hooks.get(event, []):
96
+ try:
97
+ callback(*args, **kwargs)
98
+ except Exception as e:
99
+ logger.warning(f"Hook {callback.__name__} failed: {e}")
100
+
101
+ def get_current_version(self) -> Optional[str]:
102
+ """Get the current schema version."""
103
+ return self.version_store.get_current_version()
104
+
105
+ def get_pending_migrations(self) -> List[Type[Migration]]:
106
+ """Get list of migrations that need to be applied."""
107
+ current = self.get_current_version()
108
+ return self.registry.get_pending_migrations(current, self.backend)
109
+
110
+ def needs_migration(self) -> bool:
111
+ """Check if there are pending migrations."""
112
+ return len(self.get_pending_migrations()) > 0
113
+
114
+ def migrate(
115
+ self,
116
+ connection: Any,
117
+ target_version: Optional[str] = None,
118
+ dry_run: bool = False,
119
+ ) -> List[str]:
120
+ """
121
+ Apply pending migrations.
122
+
123
+ Args:
124
+ connection: Database connection or storage instance
125
+ target_version: Optional target version (applies all if not specified)
126
+ dry_run: If True, show what would be done without making changes
127
+
128
+ Returns:
129
+ List of applied migration versions
130
+
131
+ Raises:
132
+ MigrationError: If a migration fails
133
+ """
134
+ current = self.get_current_version()
135
+ pending = self.registry.get_pending_migrations(current, self.backend)
136
+
137
+ if target_version:
138
+ target_tuple = SchemaVersion._parse_version(target_version)
139
+ pending = [
140
+ m
141
+ for m in pending
142
+ if SchemaVersion._parse_version(m.version) <= target_tuple
143
+ ]
144
+
145
+ if not pending:
146
+ logger.info("No pending migrations")
147
+ return []
148
+
149
+ applied = []
150
+ logger.info(f"Found {len(pending)} pending migrations")
151
+
152
+ for migration_class in pending:
153
+ version = migration_class.version
154
+ migration = migration_class()
155
+
156
+ if dry_run:
157
+ logger.info(
158
+ f"[DRY RUN] Would apply migration {version}: {migration.description}"
159
+ )
160
+ applied.append(version)
161
+ continue
162
+
163
+ logger.info(f"Applying migration {version}: {migration.description}")
164
+
165
+ try:
166
+ # Run pre-check
167
+ if not migration.pre_check(connection):
168
+ raise MigrationError(
169
+ f"Pre-check failed for migration {version}",
170
+ version=version,
171
+ )
172
+
173
+ # Run pre-migrate hooks
174
+ self._run_hooks("pre_migrate", migration, connection)
175
+
176
+ # Apply migration
177
+ migration.upgrade(connection)
178
+
179
+ # Run post-check
180
+ if not migration.post_check(connection):
181
+ raise MigrationError(
182
+ f"Post-check failed for migration {version}",
183
+ version=version,
184
+ )
185
+
186
+ # Record version
187
+ schema_version = SchemaVersion(
188
+ version=version,
189
+ applied_at=datetime.now(timezone.utc),
190
+ description=migration.description,
191
+ checksum=self._compute_checksum(migration_class),
192
+ )
193
+ self.version_store.record_version(schema_version)
194
+
195
+ # Run post-migrate hooks
196
+ self._run_hooks("post_migrate", migration, connection)
197
+
198
+ applied.append(version)
199
+ logger.info(f"Successfully applied migration {version}")
200
+
201
+ except MigrationError:
202
+ raise
203
+ except Exception as e:
204
+ raise MigrationError(
205
+ f"Migration {version} failed: {str(e)}",
206
+ version=version,
207
+ cause=e,
208
+ ) from e
209
+
210
+ return applied
211
+
212
+ def rollback(
213
+ self,
214
+ connection: Any,
215
+ target_version: str,
216
+ dry_run: bool = False,
217
+ ) -> List[str]:
218
+ """
219
+ Roll back to a target version.
220
+
221
+ Args:
222
+ connection: Database connection or storage instance
223
+ target_version: Version to roll back to
224
+ dry_run: If True, show what would be done without making changes
225
+
226
+ Returns:
227
+ List of rolled back migration versions
228
+
229
+ Raises:
230
+ MigrationError: If a rollback fails
231
+ """
232
+ current = self.get_current_version()
233
+ if current is None:
234
+ logger.info("No migrations to roll back")
235
+ return []
236
+
237
+ rollback_migrations = self.registry.get_rollback_migrations(
238
+ current, target_version, self.backend
239
+ )
240
+
241
+ if not rollback_migrations:
242
+ logger.info("No migrations to roll back")
243
+ return []
244
+
245
+ rolled_back = []
246
+ logger.info(f"Rolling back {len(rollback_migrations)} migrations")
247
+
248
+ for migration_class in rollback_migrations:
249
+ version = migration_class.version
250
+ migration = migration_class()
251
+
252
+ if dry_run:
253
+ logger.info(f"[DRY RUN] Would roll back migration {version}")
254
+ rolled_back.append(version)
255
+ continue
256
+
257
+ logger.info(f"Rolling back migration {version}")
258
+
259
+ try:
260
+ # Run pre-rollback hooks
261
+ self._run_hooks("pre_rollback", migration, connection)
262
+
263
+ # Apply downgrade
264
+ migration.downgrade(connection)
265
+
266
+ # Remove version record
267
+ self.version_store.remove_version(version)
268
+
269
+ # Run post-rollback hooks
270
+ self._run_hooks("post_rollback", migration, connection)
271
+
272
+ rolled_back.append(version)
273
+ logger.info(f"Successfully rolled back migration {version}")
274
+
275
+ except NotImplementedError as e:
276
+ raise MigrationError(
277
+ f"Migration {version} does not support rollback",
278
+ version=version,
279
+ ) from e
280
+ except Exception as e:
281
+ raise MigrationError(
282
+ f"Rollback of {version} failed: {str(e)}",
283
+ version=version,
284
+ cause=e,
285
+ ) from e
286
+
287
+ return rolled_back
288
+
289
+ def get_status(self) -> Dict[str, Any]:
290
+ """
291
+ Get migration status information.
292
+
293
+ Returns:
294
+ Dict with current version, pending migrations, and history
295
+ """
296
+ current = self.get_current_version()
297
+ pending = self.get_pending_migrations()
298
+ history = self.version_store.get_version_history()
299
+
300
+ return {
301
+ "current_version": current,
302
+ "target_version": self.CURRENT_SCHEMA_VERSION,
303
+ "pending_count": len(pending),
304
+ "pending_versions": [m.version for m in pending],
305
+ "applied_count": len(history),
306
+ "history": [
307
+ {
308
+ "version": v.version,
309
+ "applied_at": v.applied_at.isoformat(),
310
+ "description": v.description,
311
+ }
312
+ for v in history
313
+ ],
314
+ "needs_migration": len(pending) > 0,
315
+ }
316
+
317
+ @staticmethod
318
+ def _compute_checksum(migration_class: Type[Migration]) -> str:
319
+ """Compute checksum of migration for integrity tracking."""
320
+ import inspect
321
+
322
+ source = inspect.getsource(migration_class)
323
+ return hashlib.sha256(source.encode()).hexdigest()[:16]