topicgate 1.1.2__tar.gz → 1.1.3__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 (211) hide show
  1. {topicgate-1.1.2/src/topicgate.egg-info → topicgate-1.1.3}/PKG-INFO +1 -1
  2. {topicgate-1.1.2 → topicgate-1.1.3}/pyproject.toml +10 -2
  3. topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/README +1 -0
  4. topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/env.py +76 -0
  5. topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/script.py.mako +28 -0
  6. topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/7c3e9f1a2b4d_add_observation_retention_policy.py +96 -0
  7. topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/93fa5748f4b5_initial_revision.py +86 -0
  8. topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/b84d61a9c2e7_rename_retention_database_bytes.py +34 -0
  9. topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/d2a4c6e8f010_add_control_operation_lease.py +44 -0
  10. topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/f1490ca3b908_persist_latest_observed_mqtt_state.py +45 -0
  11. topicgate-1.1.3/src/topicgate/infrastructure/database/alembic.ini +42 -0
  12. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/migrations.py +6 -3
  13. {topicgate-1.1.2 → topicgate-1.1.3/src/topicgate.egg-info}/PKG-INFO +1 -1
  14. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/SOURCES.txt +10 -0
  15. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_database_migrations.py +15 -0
  16. topicgate-1.1.3/tests/test_migrate_app_versions.py +55 -0
  17. {topicgate-1.1.2 → topicgate-1.1.3}/LICENCE +0 -0
  18. {topicgate-1.1.2 → topicgate-1.1.3}/README.md +0 -0
  19. {topicgate-1.1.2 → topicgate-1.1.3}/setup.cfg +0 -0
  20. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/__init__.py +0 -0
  21. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/__main__.py +0 -0
  22. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/__init__.py +0 -0
  23. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/app_dependencies.py +0 -0
  24. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/broker_runtime_state.py +0 -0
  25. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/models/__init__.py +0 -0
  26. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/models/broker_snapshot.py +0 -0
  27. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/models/mcp_setup.py +0 -0
  28. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/__init__.py +0 -0
  29. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/broker_profile_service.py +0 -0
  30. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/broker_resolver.py +0 -0
  31. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/broker_snapshot_service.py +0 -0
  32. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/control_operation_service.py +0 -0
  33. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/mcp_setup_service.py +0 -0
  34. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/observation_cache_service.py +0 -0
  35. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/observation_retention_policy_service.py +0 -0
  36. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/persistence_lifecycle.py +0 -0
  37. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/service_container.py +0 -0
  38. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/service_item.py +0 -0
  39. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/topicgate_runtime.py +0 -0
  40. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/assets/icon.png +0 -0
  41. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/assets/icon.svg +0 -0
  42. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/__init__.py +0 -0
  43. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/config/__init__.py +0 -0
  44. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/config/app_config.py +0 -0
  45. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/config/mqtt_config.py +0 -0
  46. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/__init__.py +0 -0
  47. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/broker_profile_store.py +0 -0
  48. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/mqtt_message_processor.py +0 -0
  49. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/observer_repository.py +0 -0
  50. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/topic_message_store.py +0 -0
  51. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/__init__.py +0 -0
  52. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/broker_profile.py +0 -0
  53. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/broker_profile_identity.py +0 -0
  54. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/broker_summary.py +0 -0
  55. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/connection_status.py +0 -0
  56. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/message_filter.py +0 -0
  57. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/mqtt_message.py +0 -0
  58. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/mqtt_observation.py +0 -0
  59. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/mqtt_state.py +0 -0
  60. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observation_cache_administration.py +0 -0
  61. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observation_deletion_preview.py +0 -0
  62. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observation_retention_policy.py +0 -0
  63. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observer_model.py +0 -0
  64. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observer_workspace.py +0 -0
  65. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/subscription.py +0 -0
  66. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/topic_message.py +0 -0
  67. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/mqtt_topics.py +0 -0
  68. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/observer_limits.py +0 -0
  69. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/payload_limits.py +0 -0
  70. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/__init__.py +0 -0
  71. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/app.py +0 -0
  72. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/broker_state_reader.py +0 -0
  73. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/about_dialog.py +0 -0
  74. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/add_subscription_dialog.py +0 -0
  75. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/application_header.py +0 -0
  76. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/broker_settings_dialog.py +0 -0
  77. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/connection_controls.py +0 -0
  78. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/connection_status.py +0 -0
  79. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/log_console.py +0 -0
  80. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/mcp_setup_dialog.py +0 -0
  81. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/observer_tree.py +0 -0
  82. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/onboarding_panel.py +0 -0
  83. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/publish_pane.py +0 -0
  84. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/snapshot_panel.py +0 -0
  85. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/stored_observations_dialog.py +0 -0
  86. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/subscription_settings.py +0 -0
  87. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/topic_details.py +0 -0
  88. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/topic_metadata.py +0 -0
  89. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/workspace_pane.py +0 -0
  90. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/gui.py +0 -0
  91. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/main_view_model.py +0 -0
  92. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/main_window.py +0 -0
  93. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/observer_state_reader.py +0 -0
  94. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/settings_migration.py +0 -0
  95. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/theme.py +0 -0
  96. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/__init__.py +0 -0
  97. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/credentials/__init__.py +0 -0
  98. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/credentials/credential_store.py +0 -0
  99. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/credentials/os_credential_store.py +0 -0
  100. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/__init__.py +0 -0
  101. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/base.py +0 -0
  102. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/database_context.py +0 -0
  103. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/broker_profile_mapper.py +0 -0
  104. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/config_mapper.py +0 -0
  105. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/mapper_helper.py +0 -0
  106. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/observation_retention_policy_mapper.py +0 -0
  107. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/observer_workspace_mapper.py +0 -0
  108. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/subscription_mapper.py +0 -0
  109. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/topic_message_mapper.py +0 -0
  110. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/__init__.py +0 -0
  111. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/app_config_row.py +0 -0
  112. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/broker_profile_row.py +0 -0
  113. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/mqtt_config_row.py +0 -0
  114. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/mqtt_message_row.py +0 -0
  115. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/observation_retention_policy_row.py +0 -0
  116. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/observer_workspace_row.py +0 -0
  117. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/subscription_row.py +0 -0
  118. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/__init__.py +0 -0
  119. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/async_callback_bridge.py +0 -0
  120. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/callbacks/__init__.py +0 -0
  121. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/callbacks/basic_callbacks.py +0 -0
  122. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/callbacks/observer_repository_callbacks.py +0 -0
  123. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/mqtt_callbacks.py +0 -0
  124. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/mqtt_client.py +0 -0
  125. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/mqtt_gate.py +0 -0
  126. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/__init__.py +0 -0
  127. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/broker_config_repository.py +0 -0
  128. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/broker_repository.py +0 -0
  129. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/observation_retention_policy_repository.py +0 -0
  130. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/observer_mqtt_repository.py +0 -0
  131. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/subscription_repository.py +0 -0
  132. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/topic_message_repository.py +0 -0
  133. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/__init__.py +0 -0
  134. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/broker_api.py +0 -0
  135. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/connection_api.py +0 -0
  136. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/dashboard_api.py +0 -0
  137. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/dashboard_snapshot.py +0 -0
  138. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/mcp_api.py +0 -0
  139. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/preview/dashboard_preview.py +0 -0
  140. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/publish_api.py +0 -0
  141. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/snapshot_api.py +0 -0
  142. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/subscription_api.py +0 -0
  143. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/topic_api.py +0 -0
  144. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/capabilities.py +0 -0
  145. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/instructions.py +0 -0
  146. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/middleware/__init__.py +0 -0
  147. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/middleware/error_handling_middleware.py +0 -0
  148. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/middleware/logging_middleware.py +0 -0
  149. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/models.py +0 -0
  150. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/server.py +0 -0
  151. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/paths.py +0 -0
  152. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/presentation/__init__.py +0 -0
  153. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/presentation/retention_presentation.py +0 -0
  154. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/presentation/snapshot_presentation.py +0 -0
  155. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/presentation/topic_presentation.py +0 -0
  156. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/__init__.py +0 -0
  157. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/observation_retention_processor.py +0 -0
  158. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/observer_model_mqtt_message_processor.py +0 -0
  159. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/observer_model_processor.py +0 -0
  160. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/subscription_manager.py +0 -0
  161. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/dependency_links.txt +0 -0
  162. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/entry_points.txt +0 -0
  163. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/requires.txt +0 -0
  164. {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/top_level.txt +0 -0
  165. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_app.py +0 -0
  166. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_app_dependencies.py +0 -0
  167. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_async_callback_bridge.py +0 -0
  168. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_broker_config_repository.py +0 -0
  169. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_broker_repository_persistence.py +0 -0
  170. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_broker_resolver.py +0 -0
  171. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_broker_snapshot_service.py +0 -0
  172. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_control_operation_service.py +0 -0
  173. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_dashboard_api.py +0 -0
  174. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_dashboard_dependencies.py +0 -0
  175. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_dashboard_preview.py +0 -0
  176. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_desktop_snapshot_states.py +0 -0
  177. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_gui.py +0 -0
  178. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_main_view_model.py +0 -0
  179. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_apis.py +0 -0
  180. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_disconnected_startup.py +0 -0
  181. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_middleware.py +0 -0
  182. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_server_instructions.py +0 -0
  183. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_setup_service.py +0 -0
  184. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mqtt_callbacks.py +0 -0
  185. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mqtt_client.py +0 -0
  186. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mqtt_message_row.py +0 -0
  187. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observation_cache_service.py +0 -0
  188. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observation_retention_policy.py +0 -0
  189. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observation_retention_policy_repository.py +0 -0
  190. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observation_retention_policy_service.py +0 -0
  191. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_model_hydration.py +0 -0
  192. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_model_processor.py +0 -0
  193. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_model_service.py +0 -0
  194. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_repository.py +0 -0
  195. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_workspace_mapper.py +0 -0
  196. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_os_credential_store.py +0 -0
  197. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_paths.py +0 -0
  198. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_persistence_lifecycle.py +0 -0
  199. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_plugin_bundle.py +0 -0
  200. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_retention_administration.py +0 -0
  201. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_service_container.py +0 -0
  202. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_settings_migration.py +0 -0
  203. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_snapshot_mcp_api.py +0 -0
  204. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_snapshot_presentation.py +0 -0
  205. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_subscription.py +0 -0
  206. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_subscription_manager.py +0 -0
  207. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_subscription_mapper.py +0 -0
  208. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_subscription_repository.py +0 -0
  209. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_topic_message_repository.py +0 -0
  210. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_topic_presentation.py +0 -0
  211. {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_topicgate_runtime.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topicgate
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: Secure local access to your MQTT topics
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.11
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "topicgate"
7
- version = "1.1.2"
7
+ version = "1.1.3"
8
8
  description = "Secure local access to your MQTT topics"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -44,4 +44,12 @@ topicgate-gui = "topicgate.gui.app:run"
44
44
  where = ["src"]
45
45
 
46
46
  [tool.setuptools.package-data]
47
- topicgate = ["assets/*.png", "assets/*.svg"]
47
+ topicgate = [
48
+ "assets/*.png",
49
+ "assets/*.svg",
50
+ "infrastructure/database/alembic.ini",
51
+ "infrastructure/database/alembic/*.py",
52
+ "infrastructure/database/alembic/*.mako",
53
+ "infrastructure/database/alembic/README",
54
+ "infrastructure/database/alembic/versions/*.py",
55
+ ]
@@ -0,0 +1 @@
1
+ Generic single-database configuration.
@@ -0,0 +1,76 @@
1
+ from sqlalchemy import engine_from_config
2
+ from sqlalchemy import pool
3
+ from alembic import context
4
+ from topicgate.infrastructure.database.base import Base
5
+ from topicgate.paths import prepare_database_path, sqlite_url
6
+ import topicgate.infrastructure.database.models # noqa: F401
7
+
8
+ config = context.config
9
+
10
+ if config.attributes.get("connection") is None:
11
+ database_url = sqlite_url(prepare_database_path())
12
+ config.set_main_option("sqlalchemy.url", database_url.replace("%", "%%"))
13
+
14
+ target_metadata = Base.metadata
15
+
16
+
17
+ def run_migrations_offline() -> None:
18
+ """Run migrations in 'offline' mode.
19
+
20
+ This configures the context with just a URL
21
+ and not an Engine, though an Engine is acceptable
22
+ here as well. By skipping the Engine creation
23
+ we don't even need a DBAPI to be available.
24
+
25
+ Calls to context.execute() here emit the given string to the
26
+ script output.
27
+
28
+ """
29
+ url = config.get_main_option("sqlalchemy.url")
30
+ context.configure(
31
+ url=url,
32
+ target_metadata=target_metadata,
33
+ literal_binds=True,
34
+ dialect_opts={"paramstyle": "named"},
35
+ )
36
+
37
+ with context.begin_transaction():
38
+ context.run_migrations()
39
+
40
+
41
+ def run_migrations_online() -> None:
42
+ """Run migrations in 'online' mode.
43
+
44
+ In this scenario we need to create an Engine
45
+ and associate a connection with the context.
46
+
47
+ """
48
+ supplied_connection = config.attributes.get("connection")
49
+ if supplied_connection is not None:
50
+ context.configure(
51
+ connection=supplied_connection,
52
+ target_metadata=target_metadata,
53
+ )
54
+ with context.begin_transaction():
55
+ context.run_migrations()
56
+ return
57
+
58
+ connectable = engine_from_config(
59
+ config.get_section(config.config_ini_section, {}),
60
+ prefix="sqlalchemy.",
61
+ poolclass=pool.NullPool,
62
+ )
63
+
64
+ with connectable.connect() as connection:
65
+ context.configure(
66
+ connection=connection, target_metadata=target_metadata
67
+ )
68
+
69
+ with context.begin_transaction():
70
+ context.run_migrations()
71
+
72
+
73
+ if context.is_offline_mode():
74
+ run_migrations_offline()
75
+ else:
76
+ run_migrations_online()
@@ -0,0 +1,28 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ """Downgrade schema."""
28
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,96 @@
1
+ """Add observation retention policy
2
+
3
+ Revision ID: 7c3e9f1a2b4d
4
+ Revises: f1490ca3b908
5
+ Create Date: 2026-08-17 00:00:00.000000
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ revision: str = "7c3e9f1a2b4d"
15
+ down_revision: Union[str, Sequence[str], None] = "f1490ca3b908"
16
+ branch_labels: Union[str, Sequence[str], None] = None
17
+ depends_on: Union[str, Sequence[str], None] = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ """Create and seed the application-wide retention policy."""
22
+ policy = op.create_table(
23
+ "observation_retention_policy",
24
+ sa.Column("id", sa.Integer(), nullable=False),
25
+ sa.Column("max_entries_per_broker", sa.Integer(), nullable=False),
26
+ sa.Column("max_entries_total", sa.Integer(), nullable=False),
27
+ sa.Column("warning_threshold", sa.Float(), nullable=False),
28
+ sa.Column("max_payload_bytes_per_topic", sa.Integer(), nullable=False),
29
+ sa.Column("max_payload_bytes_per_broker", sa.Integer(), nullable=False),
30
+ sa.Column("max_database_bytes", sa.Integer(), nullable=False),
31
+ sa.Column("max_age_seconds", sa.Integer(), nullable=True),
32
+ sa.Column("auto_remove_expired", sa.Boolean(), nullable=False),
33
+ sa.Column("auto_remove_excess", sa.Boolean(), nullable=False),
34
+ sa.Column("auto_remove_unsubscribed", sa.Boolean(), nullable=False),
35
+ sa.CheckConstraint("id = 1", name="ck_retention_policy_singleton"),
36
+ sa.CheckConstraint(
37
+ "max_entries_per_broker > 0",
38
+ name="ck_retention_policy_broker_entries_positive",
39
+ ),
40
+ sa.CheckConstraint(
41
+ "max_entries_total > 0",
42
+ name="ck_retention_policy_total_entries_positive",
43
+ ),
44
+ sa.CheckConstraint(
45
+ "max_entries_per_broker <= max_entries_total",
46
+ name="ck_retention_policy_entry_limits_ordered",
47
+ ),
48
+ sa.CheckConstraint(
49
+ "warning_threshold > 0 AND warning_threshold <= 1",
50
+ name="ck_retention_policy_warning_threshold",
51
+ ),
52
+ sa.CheckConstraint(
53
+ "max_payload_bytes_per_topic > 0",
54
+ name="ck_retention_policy_topic_payload_positive",
55
+ ),
56
+ sa.CheckConstraint(
57
+ "max_payload_bytes_per_broker > 0",
58
+ name="ck_retention_policy_broker_payload_positive",
59
+ ),
60
+ sa.CheckConstraint(
61
+ "max_payload_bytes_per_topic <= max_payload_bytes_per_broker",
62
+ name="ck_retention_policy_payload_limits_ordered",
63
+ ),
64
+ sa.CheckConstraint(
65
+ "max_database_bytes > 0",
66
+ name="ck_retention_policy_database_bytes_positive",
67
+ ),
68
+ sa.CheckConstraint(
69
+ "max_age_seconds IS NULL OR max_age_seconds > 0",
70
+ name="ck_retention_policy_age_positive",
71
+ ),
72
+ sa.PrimaryKeyConstraint("id"),
73
+ )
74
+ op.bulk_insert(
75
+ policy,
76
+ [
77
+ {
78
+ "id": 1,
79
+ "max_entries_per_broker": 1_000,
80
+ "max_entries_total": 10_000,
81
+ "warning_threshold": 0.80,
82
+ "max_payload_bytes_per_topic": 64 * 1024,
83
+ "max_payload_bytes_per_broker": 8 * 1024 * 1024,
84
+ "max_database_bytes": 256 * 1024 * 1024,
85
+ "max_age_seconds": None,
86
+ "auto_remove_expired": True,
87
+ "auto_remove_excess": True,
88
+ "auto_remove_unsubscribed": False,
89
+ }
90
+ ],
91
+ )
92
+
93
+
94
+ def downgrade() -> None:
95
+ """Remove the observation retention policy."""
96
+ op.drop_table("observation_retention_policy")
@@ -0,0 +1,86 @@
1
+ """Initial revision
2
+
3
+ Revision ID: 93fa5748f4b5
4
+ Revises:
5
+ Create Date: 2026-08-15 10:42:50.777383
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "93fa5748f4b5"
16
+ down_revision: Union[str, Sequence[str], None] = None
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ op.create_table(
24
+ "mqtt_config",
25
+ sa.Column("id", sa.Integer(), nullable=False),
26
+ sa.Column("host", sa.String(), nullable=False),
27
+ sa.Column("port", sa.Integer(), nullable=False),
28
+ sa.Column("username", sa.String(), nullable=False),
29
+ sa.Column("use_tls", sa.Boolean(), nullable=False),
30
+ sa.PrimaryKeyConstraint("id"),
31
+ )
32
+ op.create_table(
33
+ "subscription",
34
+ sa.Column("id", sa.Integer(), nullable=False),
35
+ sa.Column("topic_filter", sa.String(), nullable=False),
36
+ sa.Column("qos", sa.Integer(), nullable=False),
37
+ sa.Column("retain_as_published", sa.Boolean(), nullable=False),
38
+ sa.Column("retain_handling", sa.Integer(), nullable=False),
39
+ sa.PrimaryKeyConstraint("id"),
40
+ )
41
+ op.create_table(
42
+ "app_config",
43
+ sa.Column("id", sa.Integer(), nullable=False),
44
+ sa.Column("mqtt_config_id", sa.Integer(), nullable=False),
45
+ sa.ForeignKeyConstraint(["mqtt_config_id"], ["mqtt_config.id"]),
46
+ sa.PrimaryKeyConstraint("id"),
47
+ sa.UniqueConstraint("mqtt_config_id"),
48
+ )
49
+ op.create_table(
50
+ "broker_profile",
51
+ sa.Column("id", sa.Uuid(), nullable=False),
52
+ sa.Column("name", sa.String(), nullable=False),
53
+ sa.Column("position", sa.Integer(), nullable=False),
54
+ sa.Column("is_active", sa.Boolean(), nullable=False),
55
+ sa.Column("mqtt_config_id", sa.Integer(), nullable=False),
56
+ sa.ForeignKeyConstraint(["mqtt_config_id"], ["mqtt_config.id"]),
57
+ sa.PrimaryKeyConstraint("id"),
58
+ sa.UniqueConstraint("mqtt_config_id"),
59
+ sa.UniqueConstraint("name"),
60
+ )
61
+ op.create_table(
62
+ "observer_workspace",
63
+ sa.Column("id", sa.Uuid(), nullable=False),
64
+ sa.Column("profile_id", sa.Uuid(), nullable=False),
65
+ sa.ForeignKeyConstraint(["profile_id"], ["broker_profile.id"]),
66
+ sa.PrimaryKeyConstraint("id"),
67
+ sa.UniqueConstraint("profile_id"),
68
+ )
69
+ op.create_table(
70
+ "workspace_subscription",
71
+ sa.Column("workspace_id", sa.Uuid(), nullable=False),
72
+ sa.Column("subscription_id", sa.Integer(), nullable=False),
73
+ sa.ForeignKeyConstraint(["subscription_id"], ["subscription.id"]),
74
+ sa.ForeignKeyConstraint(["workspace_id"], ["observer_workspace.id"]),
75
+ sa.PrimaryKeyConstraint("workspace_id", "subscription_id"),
76
+ )
77
+
78
+
79
+ def downgrade() -> None:
80
+ """Downgrade schema."""
81
+ op.drop_table("workspace_subscription")
82
+ op.drop_table("observer_workspace")
83
+ op.drop_table("broker_profile")
84
+ op.drop_table("app_config")
85
+ op.drop_table("subscription")
86
+ op.drop_table("mqtt_config")
@@ -0,0 +1,34 @@
1
+ """Rename the logical persisted payload byte limit.
2
+
3
+ Revision ID: b84d61a9c2e7
4
+ Revises: 7c3e9f1a2b4d
5
+ Create Date: 2026-08-17 00:00:00.000000
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+
12
+
13
+ revision: str = "b84d61a9c2e7"
14
+ down_revision: Union[str, Sequence[str], None] = "7c3e9f1a2b4d"
15
+ branch_labels: Union[str, Sequence[str], None] = None
16
+ depends_on: Union[str, Sequence[str], None] = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ """Describe the limit as logical persisted payload bytes."""
21
+ op.execute(
22
+ "ALTER TABLE observation_retention_policy "
23
+ "RENAME COLUMN max_database_bytes "
24
+ "TO max_persisted_payload_database_bytes_total"
25
+ )
26
+
27
+
28
+ def downgrade() -> None:
29
+ """Restore the previous retention-policy column name."""
30
+ op.execute(
31
+ "ALTER TABLE observation_retention_policy "
32
+ "RENAME COLUMN max_persisted_payload_database_bytes_total "
33
+ "TO max_database_bytes"
34
+ )
@@ -0,0 +1,44 @@
1
+ """Add cross-process control operation lease.
2
+
3
+ Revision ID: d2a4c6e8f010
4
+ Revises: b84d61a9c2e7
5
+ Create Date: 2026-08-20 00:00:00.000000
6
+ """
7
+ from typing import Sequence, Union
8
+
9
+ from alembic import op
10
+ import sqlalchemy as sa
11
+
12
+
13
+ revision: str = "d2a4c6e8f010"
14
+ down_revision: Union[str, Sequence[str], None] = "b84d61a9c2e7"
15
+ branch_labels: Union[str, Sequence[str], None] = None
16
+ depends_on: Union[str, Sequence[str], None] = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ """Create the singleton lease used by desktop and MCP control mode."""
21
+ state = op.create_table(
22
+ "control_operation_state",
23
+ sa.Column("id", sa.Integer(), nullable=False),
24
+ sa.Column("generation", sa.Integer(), nullable=False),
25
+ sa.CheckConstraint("id = 1", name="ck_control_operation_state_singleton"),
26
+ sa.PrimaryKeyConstraint("id"),
27
+ )
28
+ op.bulk_insert(state, [{"id": 1, "generation": 0}])
29
+ op.create_table(
30
+ "control_operation_lease",
31
+ sa.Column("id", sa.Integer(), nullable=False),
32
+ sa.Column("owner", sa.String(length=32), nullable=False),
33
+ sa.Column("operation", sa.String(length=128), nullable=False),
34
+ sa.Column("token", sa.String(length=36), nullable=False),
35
+ sa.Column("expires_at", sa.Float(), nullable=False),
36
+ sa.CheckConstraint("id = 1", name="ck_control_operation_lease_singleton"),
37
+ sa.PrimaryKeyConstraint("id"),
38
+ )
39
+
40
+
41
+ def downgrade() -> None:
42
+ """Remove the cross-process control lease."""
43
+ op.drop_table("control_operation_lease")
44
+ op.drop_table("control_operation_state")
@@ -0,0 +1,45 @@
1
+ """Persist latest observed MQTT state
2
+
3
+ Revision ID: f1490ca3b908
4
+ Revises: 93fa5748f4b5
5
+ Create Date: 2026-08-15 11:08:02.781757
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "f1490ca3b908"
16
+ down_revision: Union[str, Sequence[str], None] = "93fa5748f4b5"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ op.create_table(
24
+ "mqtt_message",
25
+ sa.Column("broker_id", sa.Uuid(), nullable=False),
26
+ sa.Column("topic", sa.String(), nullable=False),
27
+ sa.Column("payload", sa.LargeBinary(), nullable=False),
28
+ sa.Column("qos", sa.Integer(), nullable=False),
29
+ sa.Column("retain", sa.Boolean(), nullable=False),
30
+ sa.Column("received_at", sa.DateTime(timezone=True), nullable=False),
31
+ sa.Column("payload_size", sa.Integer(), nullable=False),
32
+ sa.Column("message_count", sa.Integer(), nullable=False),
33
+ sa.Column("observation_id", sa.Uuid(), nullable=False),
34
+ sa.ForeignKeyConstraint(
35
+ ["broker_id"],
36
+ ["broker_profile.id"],
37
+ ondelete="CASCADE",
38
+ ),
39
+ sa.PrimaryKeyConstraint("broker_id", "topic"),
40
+ )
41
+
42
+
43
+ def downgrade() -> None:
44
+ """Downgrade schema."""
45
+ op.drop_table("mqtt_message")
@@ -0,0 +1,42 @@
1
+ # A generic, single database configuration.
2
+
3
+ [alembic]
4
+ script_location = %(here)s/alembic
5
+
6
+ path_separator = os
7
+
8
+ sqlalchemy.url = driver://user:pass@localhost/dbname
9
+
10
+ [loggers]
11
+ keys = root,sqlalchemy,alembic
12
+
13
+ [handlers]
14
+ keys = console
15
+
16
+ [formatters]
17
+ keys = generic
18
+
19
+ [logger_root]
20
+ level = WARNING
21
+ handlers = console
22
+ qualname =
23
+
24
+ [logger_sqlalchemy]
25
+ level = WARNING
26
+ handlers =
27
+ qualname = sqlalchemy.engine
28
+
29
+ [logger_alembic]
30
+ level = INFO
31
+ handlers =
32
+ qualname = alembic
33
+
34
+ [handler_console]
35
+ class = StreamHandler
36
+ args = (sys.stderr,)
37
+ level = NOTSET
38
+ formatter = generic
39
+
40
+ [formatter_generic]
41
+ format = %(levelname)-5.5s [%(name)s] %(message)s
42
+ datefmt = %H:%M:%S
@@ -17,6 +17,10 @@ BASELINE_TABLES = {
17
17
  "workspace_subscription",
18
18
  }
19
19
 
20
+ _DATABASE_PACKAGE_DIR = Path(__file__).resolve().parent
21
+ _ALEMBIC_CONFIG_PATH = _DATABASE_PACKAGE_DIR / "alembic.ini"
22
+ _ALEMBIC_SCRIPT_LOCATION = _DATABASE_PACKAGE_DIR / "alembic"
23
+
20
24
 
21
25
  def upgrade_database(engine: Engine) -> None:
22
26
  """Upgrade a new or existing TopicGate database to the latest schema."""
@@ -50,8 +54,7 @@ def upgrade_database(engine: Engine) -> None:
50
54
 
51
55
 
52
56
  def _alembic_config(connection: Connection) -> Config:
53
- repository_root = Path(__file__).resolve().parents[4]
54
- config = Config(repository_root / "alembic.ini")
55
- config.set_main_option("script_location", str(repository_root / "alembic"))
57
+ config = Config(str(_ALEMBIC_CONFIG_PATH))
58
+ config.set_main_option("script_location", str(_ALEMBIC_SCRIPT_LOCATION))
56
59
  config.attributes["connection"] = connection
57
60
  return config
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topicgate
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: Secure local access to your MQTT topics
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.11
@@ -89,9 +89,18 @@ src/topicgate/infrastructure/credentials/__init__.py
89
89
  src/topicgate/infrastructure/credentials/credential_store.py
90
90
  src/topicgate/infrastructure/credentials/os_credential_store.py
91
91
  src/topicgate/infrastructure/database/__init__.py
92
+ src/topicgate/infrastructure/database/alembic.ini
92
93
  src/topicgate/infrastructure/database/base.py
93
94
  src/topicgate/infrastructure/database/database_context.py
94
95
  src/topicgate/infrastructure/database/migrations.py
96
+ src/topicgate/infrastructure/database/alembic/README
97
+ src/topicgate/infrastructure/database/alembic/env.py
98
+ src/topicgate/infrastructure/database/alembic/script.py.mako
99
+ src/topicgate/infrastructure/database/alembic/versions/7c3e9f1a2b4d_add_observation_retention_policy.py
100
+ src/topicgate/infrastructure/database/alembic/versions/93fa5748f4b5_initial_revision.py
101
+ src/topicgate/infrastructure/database/alembic/versions/b84d61a9c2e7_rename_retention_database_bytes.py
102
+ src/topicgate/infrastructure/database/alembic/versions/d2a4c6e8f010_add_control_operation_lease.py
103
+ src/topicgate/infrastructure/database/alembic/versions/f1490ca3b908_persist_latest_observed_mqtt_state.py
95
104
  src/topicgate/infrastructure/database/mappers/broker_profile_mapper.py
96
105
  src/topicgate/infrastructure/database/mappers/config_mapper.py
97
106
  src/topicgate/infrastructure/database/mappers/mapper_helper.py
@@ -169,6 +178,7 @@ tests/test_mcp_disconnected_startup.py
169
178
  tests/test_mcp_middleware.py
170
179
  tests/test_mcp_server_instructions.py
171
180
  tests/test_mcp_setup_service.py
181
+ tests/test_migrate_app_versions.py
172
182
  tests/test_mqtt_callbacks.py
173
183
  tests/test_mqtt_client.py
174
184
  tests/test_mqtt_message_row.py
@@ -1,3 +1,5 @@
1
+ from pathlib import Path
2
+
1
3
  import pytest
2
4
  from alembic import command
3
5
  from sqlalchemy import create_engine, inspect, text
@@ -9,6 +11,7 @@ from topicgate.infrastructure.database.migrations import (
9
11
  BASELINE_REVISION,
10
12
  _alembic_config,
11
13
  )
14
+ import topicgate.infrastructure.database.migrations as migrations
12
15
  import topicgate.infrastructure.database.models # noqa: F401
13
16
 
14
17
 
@@ -37,6 +40,18 @@ def test_new_database_is_migrated_to_head(tmp_path) -> None:
37
40
  engine.dispose()
38
41
 
39
42
 
43
+ def test_alembic_config_uses_package_local_migrations() -> None:
44
+ config = _alembic_config(None)
45
+ package_database_dir = Path(migrations.__file__).resolve().parent
46
+
47
+ assert Path(config.config_file_name).resolve() == (
48
+ package_database_dir / "alembic.ini"
49
+ )
50
+ assert Path(config.get_main_option("script_location")).resolve() == (
51
+ package_database_dir / "alembic"
52
+ )
53
+
54
+
40
55
  def test_sqlite_connections_enable_wal_and_busy_timeout(tmp_path) -> None:
41
56
  database_path = tmp_path / "coordination.db"
42
57
  database = DatabaseContext(f"sqlite:///{database_path.as_posix()}")
@@ -0,0 +1,55 @@
1
+ import json
2
+ from pathlib import Path
3
+ import shutil
4
+ import subprocess
5
+ import sys
6
+
7
+
8
+ REPOSITORY_ROOT = Path(__file__).parents[1]
9
+ SCRIPT = REPOSITORY_ROOT / "release" / "migrate_app_versions.py"
10
+
11
+
12
+ def test_migrate_app_versions_updates_release_version_sources(tmp_path: Path) -> None:
13
+ for relative_path in (
14
+ "pyproject.toml",
15
+ "server.json",
16
+ ".claude-plugin/marketplace.json",
17
+ "topicgate-plugin/plugin.json",
18
+ "topicgate-plugin/.codex-plugin/plugin.json",
19
+ "topicgate-plugin/.claude-plugin/plugin.json",
20
+ ):
21
+ source = REPOSITORY_ROOT / relative_path
22
+ destination = tmp_path / relative_path
23
+ destination.parent.mkdir(parents=True, exist_ok=True)
24
+ shutil.copy2(source, destination)
25
+
26
+ result = subprocess.run(
27
+ [sys.executable, str(SCRIPT), "2.1.4"],
28
+ cwd=tmp_path,
29
+ capture_output=True,
30
+ check=False,
31
+ text=True,
32
+ )
33
+
34
+ assert result.returncode == 0, result.stderr
35
+ assert "Migrated application versions to 2.1.4" in result.stdout
36
+
37
+ for relative_path in (
38
+ "topicgate-plugin/plugin.json",
39
+ "topicgate-plugin/.codex-plugin/plugin.json",
40
+ "topicgate-plugin/.claude-plugin/plugin.json",
41
+ ):
42
+ manifest = json.loads((tmp_path / relative_path).read_text(encoding="utf-8"))
43
+ assert manifest["version"] == "2.1.4"
44
+
45
+ marketplace = json.loads(
46
+ (tmp_path / ".claude-plugin/marketplace.json").read_text(encoding="utf-8")
47
+ )
48
+ assert marketplace["plugins"][0]["version"] == "2.1.4"
49
+
50
+ server = json.loads((tmp_path / "server.json").read_text(encoding="utf-8"))
51
+ assert server["version"] == "2.1.4"
52
+ assert server["packages"][0]["version"] == "2.1.4"
53
+
54
+ pyproject = (tmp_path / "pyproject.toml").read_text(encoding="utf-8")
55
+ assert 'version = "2.1.4"' in pyproject
File without changes
File without changes
File without changes