alma-memory 0.5.1__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 -226
  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 -430
  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 -265
  24. alma/extraction/extractor.py +420 -420
  25. alma/graph/__init__.py +106 -106
  26. alma/graph/backends/__init__.py +32 -32
  27. alma/graph/backends/kuzu.py +624 -624
  28. alma/graph/backends/memgraph.py +432 -432
  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 -444
  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 -509
  55. alma/observability/__init__.py +91 -84
  56. alma/observability/config.py +302 -302
  57. alma/observability/guidelines.py +170 -0
  58. alma/observability/logging.py +424 -424
  59. alma/observability/metrics.py +583 -583
  60. alma/observability/tracing.py +440 -440
  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 -427
  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 -90
  78. alma/storage/archive.py +233 -0
  79. alma/storage/azure_cosmos.py +1259 -1259
  80. alma/storage/base.py +1083 -583
  81. alma/storage/chroma.py +1443 -1443
  82. alma/storage/constants.py +103 -103
  83. alma/storage/file_based.py +614 -614
  84. alma/storage/migrations/__init__.py +21 -21
  85. alma/storage/migrations/base.py +321 -321
  86. alma/storage/migrations/runner.py +323 -323
  87. alma/storage/migrations/version_stores.py +337 -337
  88. alma/storage/migrations/versions/__init__.py +11 -11
  89. alma/storage/migrations/versions/v1_0_0.py +373 -373
  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 -1559
  93. alma/storage/qdrant.py +1306 -1306
  94. alma/storage/sqlite_local.py +3041 -1457
  95. alma/testing/__init__.py +46 -46
  96. alma/testing/factories.py +301 -301
  97. alma/testing/mocks.py +389 -389
  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.1.dist-info → alma_memory-0.7.0.dist-info}/METADATA +210 -72
  108. alma_memory-0.7.0.dist-info/RECORD +112 -0
  109. alma_memory-0.5.1.dist-info/RECORD +0 -93
  110. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/WHEEL +0 -0
  111. {alma_memory-0.5.1.dist-info → alma_memory-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,323 +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]
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]