mainsequence 4.2.14__tar.gz → 4.2.16__tar.gz

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 (126) hide show
  1. {mainsequence-4.2.14/mainsequence.egg-info → mainsequence-4.2.16}/PKG-INFO +1 -1
  2. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/migrations.py +161 -34
  3. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/metatables/core.py +320 -0
  4. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/migrations.py +201 -13
  5. {mainsequence-4.2.14 → mainsequence-4.2.16/mainsequence.egg-info}/PKG-INFO +1 -1
  6. {mainsequence-4.2.14 → mainsequence-4.2.16}/pyproject.toml +1 -1
  7. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_cli_migrations.py +107 -11
  8. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_meta_table_migrations.py +248 -15
  9. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_meta_tables_client_models.py +132 -0
  10. {mainsequence-4.2.14 → mainsequence-4.2.16}/LICENSE +0 -0
  11. {mainsequence-4.2.14 → mainsequence-4.2.16}/README.md +0 -0
  12. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/AGENTS.md +0 -0
  13. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
  14. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  15. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
  16. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  17. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
  18. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
  19. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  20. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  21. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
  22. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  23. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  24. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  25. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  26. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  27. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
  28. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  29. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  30. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  31. {mainsequence-4.2.14 → mainsequence-4.2.16}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
  32. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/__init__.py +0 -0
  33. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/__main__.py +0 -0
  34. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/bootstrap.py +0 -0
  35. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/__init__.py +0 -0
  36. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/api.py +0 -0
  37. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/browser_auth.py +0 -0
  38. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/cli.py +0 -0
  39. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/config.py +0 -0
  40. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/docker_utils.py +0 -0
  41. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/doctor.py +0 -0
  42. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/local_ops.py +0 -0
  43. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/model_filters.py +0 -0
  44. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/project_status.py +0 -0
  45. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/pydantic_cli.py +0 -0
  46. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/sdk_utils.py +0 -0
  47. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/ssh_utils.py +0 -0
  48. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/cli/ui.py +0 -0
  49. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/__init__.py +0 -0
  50. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/agent_runtime_models.py +0 -0
  51. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/base.py +0 -0
  52. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/client.py +0 -0
  53. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/command_center/__init__.py +0 -0
  54. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/command_center/app_component.py +0 -0
  55. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/command_center/connections.py +0 -0
  56. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/command_center/data_models.py +0 -0
  57. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/command_center/workspace.py +0 -0
  58. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  59. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/compute_validation.py +0 -0
  60. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  61. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  62. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  63. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  64. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/dtype_codec.py +0 -0
  65. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/exceptions.py +0 -0
  66. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/fastapi/__init__.py +0 -0
  67. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/fastapi/auth.py +0 -0
  68. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/metatables/__init__.py +0 -0
  69. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/models_foundry.py +0 -0
  70. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/models_helpers.py +0 -0
  71. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/models_user.py +0 -0
  72. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/client/utils.py +0 -0
  73. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/defaults.py +0 -0
  74. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/instrumentation/__init__.py +0 -0
  75. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/instrumentation/utils.py +0 -0
  76. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/logconf.py +0 -0
  77. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/__init__.py +0 -0
  78. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/__main__.py +0 -0
  79. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  80. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  81. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  82. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  83. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  84. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  85. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  86. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  87. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  88. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  89. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/future_registry.py +0 -0
  90. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/hashing.py +0 -0
  91. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  92. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  93. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence/runtime_flags.py +0 -0
  94. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence.egg-info/SOURCES.txt +0 -0
  95. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence.egg-info/dependency_links.txt +0 -0
  96. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence.egg-info/entry_points.txt +0 -0
  97. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence.egg-info/requires.txt +0 -0
  98. {mainsequence-4.2.14 → mainsequence-4.2.16}/mainsequence.egg-info/top_level.txt +0 -0
  99. {mainsequence-4.2.14 → mainsequence-4.2.16}/setup.cfg +0 -0
  100. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_auth_precedence.py +0 -0
  101. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_build_operations_hashing.py +0 -0
  102. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_cli.py +0 -0
  103. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_cli_browser_auth.py +0 -0
  104. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_client.py +0 -0
  105. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_command_center_app_component_models.py +0 -0
  106. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_command_center_data_models.py +0 -0
  107. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_command_center_models.py +0 -0
  108. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  109. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_data_node_storage_dimension_queries.py +0 -0
  110. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_data_node_update_flow.py +0 -0
  111. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_dependency_extras.py +0 -0
  112. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_duckdb_interface_dimensions.py +0 -0
  113. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_filter_normalization.py +0 -0
  114. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_logconf.py +0 -0
  115. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  116. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_models_user_request_bound_auth.py +0 -0
  117. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_pod_project_resolution.py +0 -0
  118. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_project_batch_jobs_from_file.py +0 -0
  119. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_run_configuration.py +0 -0
  120. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_secret_client_model.py +0 -0
  121. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_source_table_configuration.py +0 -0
  122. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_sqlite_interface_dimensions.py +0 -0
  123. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_update_runner_uid_runtime.py +0 -0
  124. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_update_statistics.py +0 -0
  125. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_update_uid_guards.py +0 -0
  126. {mainsequence-4.2.14 → mainsequence-4.2.16}/tests/test_workspace_snapshot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 4.2.14
3
+ Version: 4.2.16
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -2,9 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
4
  import json
5
+ import logging
5
6
  import re
6
7
  import sys
7
8
  from collections.abc import Mapping, Sequence
9
+ from contextlib import contextmanager
8
10
  from typing import Any
9
11
 
10
12
  import click
@@ -23,6 +25,8 @@ from mainsequence.meta_tables.migrations import (
23
25
  migrations = typer.Typer(help="Alembic-owned MetaTable migration commands")
24
26
  REGISTER_ENDPOINT = "/orm/api/ts_manager/meta_table/register/"
25
27
  RESERVE_MANAGED_ENDPOINT = "/orm/api/ts_manager/meta_table/reserve-managed/"
28
+ FINALIZE_MANAGED_ENDPOINT = "/orm/api/ts_manager/meta_table/finalize-managed/"
29
+ ALEMBIC_PROVIDER_RESET_ENDPOINT = "/orm/api/ts_manager/meta_table/alembic-provider-reset/"
26
30
 
27
31
 
28
32
  class _AlembicOutput:
@@ -44,6 +48,46 @@ class _AlembicOutput:
44
48
  return any(chunk.strip() for chunk in self._chunks)
45
49
 
46
50
 
51
+ class _AlembicLogHandler(logging.Handler):
52
+ def emit(self, record: logging.LogRecord) -> None:
53
+ try:
54
+ message = self.format(record)
55
+ except Exception:
56
+ self.handleError(record)
57
+ return
58
+ print(message, file=sys.stderr, flush=True)
59
+
60
+
61
+ @contextmanager
62
+ def _forward_alembic_logging():
63
+ logger_names = {"alembic"}
64
+ logger_names.update(
65
+ name
66
+ for name in logging.Logger.manager.loggerDict
67
+ if name == "alembic" or name.startswith("alembic.")
68
+ )
69
+ loggers = [logging.getLogger(name) for name in logger_names]
70
+ previous_state = [(logger, logger.level, logger.propagate) for logger in loggers]
71
+ handler = _AlembicLogHandler()
72
+ handler.setLevel(logging.DEBUG)
73
+ handler.setFormatter(
74
+ logging.Formatter("[alembic] %(levelname)s %(name)s: %(message)s")
75
+ )
76
+ root_logger = logging.getLogger("alembic")
77
+ root_logger.addHandler(handler)
78
+ for logger in loggers:
79
+ logger.setLevel(logging.DEBUG)
80
+ logger.propagate = True
81
+ root_logger.propagate = False
82
+ try:
83
+ yield
84
+ finally:
85
+ root_logger.removeHandler(handler)
86
+ for logger, level, propagate in previous_state:
87
+ logger.setLevel(level)
88
+ logger.propagate = propagate
89
+
90
+
47
91
  def _emit_status(message: str) -> None:
48
92
  print(f"[mainsequence migrations] {message}", file=sys.stderr, flush=True)
49
93
 
@@ -234,6 +278,17 @@ def _emit_metatable_reservation(model: type[Any], item: Any) -> None:
234
278
  )
235
279
 
236
280
 
281
+ def _emit_metatable_finalization(model: type[Any], item: Any) -> None:
282
+ _emit_progress(
283
+ _metatable_message(
284
+ endpoint=FINALIZE_MANAGED_ENDPOINT,
285
+ action="finalized",
286
+ model=model,
287
+ item=item,
288
+ ),
289
+ )
290
+
291
+
237
292
  def _prepare_alembic_config(
238
293
  migration: AlembicMetaTableMigration,
239
294
  *,
@@ -301,15 +356,16 @@ def _next_sequential_revision_id(
301
356
  _emit_status("Imported Alembic ScriptDirectory.")
302
357
 
303
358
  _emit_status("Scanning Alembic revision directory for next sequential id...")
304
- script = ScriptDirectory.from_config(
305
- alembic_config_for_provider(
306
- migration,
307
- sqlalchemy_url="postgresql://",
308
- stdout=alembic_output,
309
- output_buffer=alembic_output,
359
+ with _forward_alembic_logging():
360
+ script = ScriptDirectory.from_config(
361
+ alembic_config_for_provider(
362
+ migration,
363
+ sqlalchemy_url="postgresql://",
364
+ stdout=alembic_output,
365
+ output_buffer=alembic_output,
366
+ )
310
367
  )
311
- )
312
- heads = list(script.get_heads())
368
+ heads = list(script.get_heads())
313
369
  if len(heads) > 1:
314
370
  raise typer.BadParameter(
315
371
  "Sequential revision IDs require a single Alembic head. Pass --rev-id "
@@ -324,10 +380,11 @@ def _next_sequential_revision_id(
324
380
  )
325
381
 
326
382
  numeric_revisions: list[int] = []
327
- for revision in script.walk_revisions():
328
- revision_id = str(revision.revision)
329
- if re.fullmatch(r"\d{4,}", revision_id):
330
- numeric_revisions.append(int(revision_id))
383
+ with _forward_alembic_logging():
384
+ for revision in script.walk_revisions():
385
+ revision_id = str(revision.revision)
386
+ if re.fullmatch(r"\d{4,}", revision_id):
387
+ numeric_revisions.append(int(revision_id))
331
388
  next_revision_id = f"{max(numeric_revisions, default=0) + 1:04d}"
332
389
  _emit_status(f"Next Alembic revision id is {next_revision_id}.")
333
390
  return next_revision_id
@@ -356,7 +413,8 @@ def current(
356
413
  alembic_output=alembic_output,
357
414
  )
358
415
  _emit_status("Starting Alembic current now...")
359
- command.current(config, verbose=verbose)
416
+ with _forward_alembic_logging():
417
+ command.current(config, verbose=verbose)
360
418
  if not alembic_output.has_visible_output:
361
419
  _emit_status(
362
420
  "Alembic current produced no revision output. The version table is "
@@ -406,13 +464,14 @@ def revision(
406
464
  alembic_output=alembic_output,
407
465
  )
408
466
  _emit_status(f"Starting Alembic revision now rev_id={resolved_rev_id}...")
409
- script = command.revision(
410
- config,
411
- message=resolved_message,
412
- autogenerate=autogenerate,
413
- rev_id=resolved_rev_id,
414
- head=head,
415
- )
467
+ with _forward_alembic_logging():
468
+ script = command.revision(
469
+ config,
470
+ message=resolved_message,
471
+ autogenerate=autogenerate,
472
+ rev_id=resolved_rev_id,
473
+ head=head,
474
+ )
416
475
  _emit_status("Alembic revision finished.")
417
476
  _emit(
418
477
  {
@@ -438,7 +497,7 @@ def upgrade(
438
497
  ttl_seconds: int = typer.Option(900, "--ttl-seconds", min=1),
439
498
  json_output: bool = typer.Option(False, "--json", help="Emit JSON."),
440
499
  ) -> None:
441
- """Run Alembic upgrade directly and refresh MetaTable catalog rows."""
500
+ """Run Alembic upgrade directly and finalize reserved MetaTables."""
442
501
 
443
502
  command = _load_alembic_command("upgrade")
444
503
  migration = _load_migration(provider)
@@ -450,13 +509,17 @@ def upgrade(
450
509
  alembic_output=alembic_output,
451
510
  )
452
511
  _emit_status(f"Starting Alembic upgrade now target={target_revision}...")
453
- command.upgrade(config, target_revision)
454
- _emit_status("Refreshing MetaTable catalog after upgrade...")
455
- registered = migration.refresh_metatable_catalog(
512
+ with _forward_alembic_logging():
513
+ command.upgrade(config, target_revision)
514
+ _emit_status("Finalizing MetaTable catalog after upgrade...")
515
+ finalize_response = migration.finalize_metatable_catalog(
516
+ prepared=prepared,
517
+ alembic_revision=target_revision,
456
518
  timeout=timeout,
457
- on_metatable_registered=_emit_metatable_registration,
519
+ on_metatable_finalized=_emit_metatable_finalization,
520
+ on_metatable_finalize_status=_emit_status,
458
521
  )
459
- _emit_status("MetaTable catalog refresh finished.")
522
+ _emit_status("MetaTable catalog finalization finished.")
460
523
  _emit(
461
524
  {
462
525
  "ok": True,
@@ -464,7 +527,10 @@ def upgrade(
464
527
  "package": migration.package,
465
528
  "migration_namespace": migration.migration_namespace,
466
529
  "meta_table_uids": prepared.meta_table_uids,
467
- "registered_count": len(registered),
530
+ "finalized_count": finalize_response.finalized_count,
531
+ "active_count": finalize_response.active_count,
532
+ "reserved_count": finalize_response.reserved_count,
533
+ "failed_count": finalize_response.failed_count,
468
534
  },
469
535
  json_output=json_output,
470
536
  )
@@ -482,7 +548,7 @@ def downgrade(
482
548
  ttl_seconds: int = typer.Option(900, "--ttl-seconds", min=1),
483
549
  json_output: bool = typer.Option(False, "--json", help="Emit JSON."),
484
550
  ) -> None:
485
- """Run Alembic downgrade directly and refresh MetaTable catalog rows."""
551
+ """Run Alembic downgrade directly and finalize reserved MetaTables."""
486
552
 
487
553
  command = _load_alembic_command("downgrade")
488
554
  migration = _load_migration(provider)
@@ -494,13 +560,17 @@ def downgrade(
494
560
  alembic_output=alembic_output,
495
561
  )
496
562
  _emit_status(f"Starting Alembic downgrade now target={target_revision}...")
497
- command.downgrade(config, target_revision)
498
- _emit_status("Refreshing MetaTable catalog after downgrade...")
499
- registered = migration.refresh_metatable_catalog(
563
+ with _forward_alembic_logging():
564
+ command.downgrade(config, target_revision)
565
+ _emit_status("Finalizing MetaTable catalog after downgrade...")
566
+ finalize_response = migration.finalize_metatable_catalog(
567
+ prepared=prepared,
568
+ alembic_revision=target_revision,
500
569
  timeout=timeout,
501
- on_metatable_registered=_emit_metatable_registration,
570
+ on_metatable_finalized=_emit_metatable_finalization,
571
+ on_metatable_finalize_status=_emit_status,
502
572
  )
503
- _emit_status("MetaTable catalog refresh finished.")
573
+ _emit_status("MetaTable catalog finalization finished.")
504
574
  _emit(
505
575
  {
506
576
  "ok": True,
@@ -508,7 +578,64 @@ def downgrade(
508
578
  "package": migration.package,
509
579
  "migration_namespace": migration.migration_namespace,
510
580
  "meta_table_uids": prepared.meta_table_uids,
511
- "registered_count": len(registered),
581
+ "finalized_count": finalize_response.finalized_count,
582
+ "active_count": finalize_response.active_count,
583
+ "reserved_count": finalize_response.reserved_count,
584
+ "failed_count": finalize_response.failed_count,
512
585
  },
513
586
  json_output=json_output,
514
587
  )
588
+
589
+
590
+ @migrations.command("reset")
591
+ def reset(
592
+ provider: str | None = typer.Option(
593
+ None,
594
+ "--provider",
595
+ help="Migration provider reference, for example msm.migrations:migration.",
596
+ ),
597
+ confirm_reset: bool = typer.Option(
598
+ False,
599
+ "--confirm-reset",
600
+ help="Required confirmation for destructive provider-scoped reset.",
601
+ ),
602
+ drop_physical_tables: bool = typer.Option(
603
+ True,
604
+ "--drop-physical-tables/--keep-physical-tables",
605
+ help="Drop provider physical tables during reset.",
606
+ ),
607
+ clear_alembic_version_table: bool = typer.Option(
608
+ True,
609
+ "--clear-alembic-version-table/--keep-alembic-version-table",
610
+ help="Clear the provider Alembic version table during reset.",
611
+ ),
612
+ include_reserved: bool = typer.Option(
613
+ True,
614
+ "--include-reserved/--active-only",
615
+ help="Include already-reserved provider MetaTables in reset results.",
616
+ ),
617
+ timeout: float | None = typer.Option(None, "--timeout"),
618
+ json_output: bool = typer.Option(False, "--json", help="Emit JSON."),
619
+ ) -> None:
620
+ """Reset an Alembic-managed provider catalog/physical state."""
621
+
622
+ if not confirm_reset:
623
+ raise typer.BadParameter(
624
+ "Pass --confirm-reset to call the destructive provider reset endpoint.",
625
+ param_hint="--confirm-reset",
626
+ )
627
+ migration = _load_migration(provider)
628
+ _emit_status(
629
+ "Calling provider reset endpoint "
630
+ f"{ALEMBIC_PROVIDER_RESET_ENDPOINT} provider={migration.migration_provider_key}..."
631
+ )
632
+ response = migration.reset_alembic_provider(
633
+ confirm_reset=True,
634
+ drop_physical_tables=drop_physical_tables,
635
+ clear_alembic_version_table=clear_alembic_version_table,
636
+ include_reserved=include_reserved,
637
+ timeout=timeout,
638
+ on_reset_status=_emit_status,
639
+ )
640
+ _emit_status("Alembic provider reset finished.")
641
+ _emit(response, json_output=json_output)