mainsequence 8.1.20.dev4__tar.gz → 8.1.20.dev8__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 (142) hide show
  1. {mainsequence-8.1.20.dev4/mainsequence.egg-info → mainsequence-8.1.20.dev8}/PKG-INFO +1 -1
  2. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/cli.py +6 -2
  3. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/base.py +14 -1
  4. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/metatables/core.py +61 -95
  5. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/models_foundry.py +0 -29
  6. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8/mainsequence.egg-info}/PKG-INFO +1 -1
  7. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence.egg-info/SOURCES.txt +1 -0
  8. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/pyproject.toml +1 -1
  9. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_canonical_api_route_contract.py +18 -1
  10. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_cli.py +34 -0
  11. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_code_repository_branch_contract.py +6 -2
  12. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_code_repository_runtime_context.py +7 -3
  13. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_environment_response_contracts.py +5 -5
  14. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_filter_normalization.py +66 -35
  15. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_observability_client.py +6 -5
  16. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_source_table_configuration.py +14 -8
  17. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_time_index_table_update_configuration_client.py +6 -3
  18. mainsequence-8.1.20.dev8/tests/test_tolerant_response_reading.py +137 -0
  19. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_update_uid_guards.py +0 -1
  20. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/LICENSE +0 -0
  21. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/MANIFEST.in +0 -0
  22. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/README.md +0 -0
  23. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/AGENTS.md +0 -0
  24. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  25. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  26. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  27. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  28. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  29. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
  30. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  31. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  32. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  33. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  34. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  35. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/__init__.py +0 -0
  36. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/__main__.py +0 -0
  37. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/bootstrap.py +0 -0
  38. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/__init__.py +0 -0
  39. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/api.py +0 -0
  40. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/browser_auth.py +0 -0
  41. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/code_repository_status.py +0 -0
  42. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/config.py +0 -0
  43. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/docker_utils.py +0 -0
  44. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/doctor.py +0 -0
  45. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/local_ops.py +0 -0
  46. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/migrations.py +0 -0
  47. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/model_filters.py +0 -0
  48. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/pydantic_cli.py +0 -0
  49. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/sdk_utils.py +0 -0
  50. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/ssh_utils.py +0 -0
  51. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/cli/ui.py +0 -0
  52. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/__init__.py +0 -0
  53. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/agent_runtime_models.py +0 -0
  54. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/client.py +0 -0
  55. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/compute_validation.py +0 -0
  56. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  57. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  58. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  59. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  60. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/dtype_codec.py +0 -0
  61. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/exceptions.py +0 -0
  62. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/github_issues.py +0 -0
  63. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/metatables/__init__.py +0 -0
  64. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/models_helpers.py +0 -0
  65. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/models_user.py +0 -0
  66. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/observability.py +0 -0
  67. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/client/utils.py +0 -0
  68. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/code_repository_context.py +0 -0
  69. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/code_repository_skills.py +0 -0
  70. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/defaults.py +0 -0
  71. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/instrumentation/__init__.py +0 -0
  72. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/instrumentation/utils.py +0 -0
  73. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/logconf.py +0 -0
  74. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/__init__.py +0 -0
  75. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/__main__.py +0 -0
  76. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  77. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  78. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/future_registry.py +0 -0
  79. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/hashing.py +0 -0
  80. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  81. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  82. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/migrations/env.py +0 -0
  83. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/migrations/provider.py +0 -0
  84. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/migrations/registry.py +0 -0
  85. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  86. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  87. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  88. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  89. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  90. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/schema_names.py +0 -0
  91. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  92. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
  93. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
  94. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
  95. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
  96. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -0
  97. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
  98. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/time_index_table_updates/runner.py +0 -0
  99. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -0
  100. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
  101. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/repository_identity_security.py +0 -0
  102. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/runtime_flags.py +0 -0
  103. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence/scaffold_skills.py +0 -0
  104. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence.egg-info/dependency_links.txt +0 -0
  105. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence.egg-info/entry_points.txt +0 -0
  106. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence.egg-info/requires.txt +0 -0
  107. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/mainsequence.egg-info/top_level.txt +0 -0
  108. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/setup.cfg +0 -0
  109. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
  110. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_auth_precedence.py +0 -0
  111. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_cli_browser_auth.py +0 -0
  112. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_cli_migrations.py +0 -0
  113. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_client.py +0 -0
  114. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_code_repository_batch_jobs_from_file.py +0 -0
  115. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_code_repository_documentation_contract.py +0 -0
  116. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_code_repository_skills.py +0 -0
  117. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_dependency_extras.py +0 -0
  118. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_dev_release_version.py +0 -0
  119. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_duckdb_interface_dimensions.py +0 -0
  120. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_environment_resource_context.py +0 -0
  121. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_github_issues_client.py +0 -0
  122. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_instrumentation.py +0 -0
  123. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_local_ops.py +0 -0
  124. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_logconf.py +0 -0
  125. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_meta_table_migrations.py +0 -0
  126. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_meta_tables_client_models.py +0 -0
  127. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  128. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_models_user_request_bound_auth.py +0 -0
  129. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_notification_uid_contract.py +0 -0
  130. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_pod_project_resolution.py +0 -0
  131. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_scaffold_skills.py +0 -0
  132. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_schema_names.py +0 -0
  133. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_secret_client_model.py +0 -0
  134. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_sqlite_interface_dimensions.py +0 -0
  135. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_ssh_utils.py +0 -0
  136. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_time_index_table_access_dimension_audit.py +0 -0
  137. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_time_index_table_dimension_queries.py +0 -0
  138. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_time_index_table_update_configuration.py +0 -0
  139. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_time_index_table_update_flow.py +0 -0
  140. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_time_index_table_update_hard_cut.py +0 -0
  141. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_update_runner_uid_runtime.py +0 -0
  142. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev8}/tests/test_update_statistics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.20.dev4
3
+ Version: 8.1.20.dev8
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -5912,7 +5912,9 @@ def _meta_table_run_query_impl(
5912
5912
  try:
5913
5913
  payload = run_meta_table_query(meta_table_uid, sql, timeout=timeout)
5914
5914
  except ApiError as e:
5915
- error(f"MetaTable query failed: {e}")
5915
+ # The api layer already names the operation in the message; prefixing again
5916
+ # produces "MetaTable query failed: MetaTable query failed: ...".
5917
+ error(str(e))
5916
5918
  raise typer.Exit(1) from e
5917
5919
 
5918
5920
  ok = bool(payload.get("ok"))
@@ -5941,7 +5943,9 @@ def _time_index_table_run_query_impl(
5941
5943
  try:
5942
5944
  payload = run_time_index_table_query(table_uid, sql, timeout=timeout)
5943
5945
  except ApiError as e:
5944
- error(f"Time-index table query failed: {e}")
5946
+ # The api layer already names the operation in the message; prefixing again
5947
+ # produces "Time-index table query failed: Time-index table query failed: ...".
5948
+ error(str(e))
5945
5949
  raise typer.Exit(1) from e
5946
5950
 
5947
5951
  ok = bool(payload.get("ok"))
@@ -58,7 +58,9 @@ class HtmlSaveException(Exception):
58
58
 
59
59
 
60
60
  class BasePydanticModel(BaseModel):
61
- model_config = ConfigDict(extra="forbid") # Forbid extra fields in v2
61
+ # Tolerant reading (ADR 0032): a backend response that carries a field this
62
+ # SDK release does not declare parses, and the undeclared field is dropped.
63
+ model_config = ConfigDict(extra="ignore")
62
64
  orm_class: str = None # This will be set to the class that inherits
63
65
 
64
66
  @classmethod
@@ -780,10 +782,21 @@ class BaseObjectOrm:
780
782
 
781
783
  return raw_key
782
784
 
785
+ def _declares_field(obj, field_name: str) -> bool:
786
+ if not isinstance(obj, BaseModel):
787
+ return True
788
+ if field_name in type(obj).model_fields:
789
+ return True
790
+ # Tolerant reading (ADR 0032): a model that keeps undeclared keys can
791
+ # take them; every other model drops them, as parsing does.
792
+ return type(obj).model_config.get("extra") == "allow"
793
+
783
794
  def recursive_update(obj, update_dict, path=()):
784
795
  for k, v in update_dict.items():
785
796
  current_path = (*path, k)
786
797
  field_name = _resolve_model_field_name(obj, k)
798
+ if not _declares_field(obj, field_name):
799
+ continue
787
800
  # Get the existing nested object, defaulting to None if it doesn't exist
788
801
  nested_obj = getattr(obj, field_name, None)
789
802
 
@@ -1904,35 +1904,18 @@ class MetaTable(
1904
1904
 
1905
1905
  cls = type(self)
1906
1906
  url = f"{cls.get_object_url().rstrip('/')}/{self._public_uid()}/run-query/"
1907
- if cls.ENDPOINT == "time-index-meta-tables":
1908
- session = cls.build_session()
1909
- old_content_type = session.headers.get("Content-Type")
1910
- session.headers["Content-Type"] = "text/plain"
1911
- try:
1912
- response = make_request(
1913
- s=session,
1914
- loaders=cls.LOADERS,
1915
- r_type="POST",
1916
- url=url,
1917
- payload={"data": sql},
1918
- time_out=timeout,
1919
- )
1920
- finally:
1921
- if old_content_type is None:
1922
- session.headers.pop("Content-Type", None)
1923
- else:
1924
- session.headers["Content-Type"] = old_content_type
1925
- error_payload = {"data": sql}
1926
- else:
1927
- response = make_request(
1928
- s=cls.build_session(),
1929
- loaders=cls.LOADERS,
1930
- r_type="POST",
1931
- url=url,
1932
- payload={"json": sql},
1933
- time_out=timeout,
1934
- )
1935
- error_payload = {"json": sql}
1907
+ # The backend reads the request body as the raw SQL string and parses it with
1908
+ # DRF's JSON parser, for every MetaTable endpoint. Sending a JSON string is the
1909
+ # only transport it accepts; a text/plain body is rejected with HTTP 415.
1910
+ payload = {"json": sql}
1911
+ response = make_request(
1912
+ s=cls.build_session(),
1913
+ loaders=cls.LOADERS,
1914
+ r_type="POST",
1915
+ url=url,
1916
+ payload=payload,
1917
+ time_out=timeout,
1918
+ )
1936
1919
 
1937
1920
  try:
1938
1921
  data = response.json()
@@ -1942,57 +1925,9 @@ class MetaTable(
1942
1925
  if isinstance(data, dict) and "ok" in data:
1943
1926
  return data
1944
1927
 
1945
- raise_for_response(response, payload=error_payload)
1928
+ raise_for_response(response, payload=payload)
1946
1929
  return response.json()
1947
1930
 
1948
- @classmethod
1949
- def description_search(
1950
- cls,
1951
- q: str,
1952
- *,
1953
- q_embedding: Sequence[float] | None = None,
1954
- trigram_k: int = 200,
1955
- embed_k: int = 200,
1956
- w_trgm: float = 0.65,
1957
- w_emb: float = 0.35,
1958
- embedding_model: str = "default",
1959
- **filters,
1960
- ):
1961
- q = (q or "").strip()
1962
- if not q:
1963
- raise ValueError("q is required")
1964
- operation = f"{cls.__name__}.description_search"
1965
- cls._reject_sdk_context_overrides(filters, operation=operation)
1966
-
1967
- body = {
1968
- "q": q,
1969
- "trigram_k": trigram_k,
1970
- "embed_k": embed_k,
1971
- "w_trgm": w_trgm,
1972
- "w_emb": w_emb,
1973
- "embedding_model": embedding_model,
1974
- }
1975
-
1976
- if q_embedding is not None:
1977
- body["q_embedding"] = [float(x) for x in q_embedding]
1978
-
1979
- if filters:
1980
- body.update(filters)
1981
- body.update(cls._sdk_owned_query_context(operation))
1982
-
1983
- payload = {"params": serialize_to_json(body)}
1984
- response = make_request(
1985
- s=cls.build_session(),
1986
- loaders=cls.LOADERS,
1987
- r_type="GET",
1988
- url=f"{cls.get_object_url().rstrip('/')}/description-search/",
1989
- payload=payload,
1990
- )
1991
- if response.status_code != 200:
1992
- raise_for_response(response, payload=payload)
1993
-
1994
- return cls._deserialize_search_response(response.json())
1995
-
1996
1931
  @classmethod
1997
1932
  def column_search(cls, q: str, **filters):
1998
1933
  q = (q or "").strip()
@@ -2768,23 +2703,6 @@ class TimeIndexTableUpdate(TableUpdateNode, BaseObjectOrm):
2768
2703
  return None
2769
2704
  return r.json()
2770
2705
 
2771
- def verify_if_direct_dependencies_are_updated(self) -> dict:
2772
- """
2773
- Response({
2774
- "error_on_update_dependencies": False,
2775
- "updated": all_success,
2776
- })
2777
- """
2778
- s = self.build_session()
2779
- url = (
2780
- self.get_object_url()
2781
- + f"/{self._public_uid()}/verify-if-direct-dependencies-are-updated/"
2782
- )
2783
- r = make_request(s=s, loaders=None, r_type="GET", url=url)
2784
- if r.status_code != 200:
2785
- raise Exception(f"Error in request: {r.text}")
2786
- return r.json()
2787
-
2788
2706
  def get_data_between_dates_from_api(self, *args, **kwargs):
2789
2707
  return self.output_table.get_data_between_dates_from_api(*args, **kwargs)
2790
2708
 
@@ -3259,6 +3177,54 @@ class TimeIndexMetaTable(MetaTable):
3259
3177
  )
3260
3178
  return self
3261
3179
 
3180
+ @classmethod
3181
+ def description_search(
3182
+ cls,
3183
+ q: str,
3184
+ *,
3185
+ q_embedding: Sequence[float] | None = None,
3186
+ trigram_k: int = 200,
3187
+ embed_k: int = 200,
3188
+ w_trgm: float = 0.65,
3189
+ w_emb: float = 0.35,
3190
+ embedding_model: str = "default",
3191
+ **filters,
3192
+ ):
3193
+ q = (q or "").strip()
3194
+ if not q:
3195
+ raise ValueError("q is required")
3196
+ operation = f"{cls.__name__}.description_search"
3197
+ cls._reject_sdk_context_overrides(filters, operation=operation)
3198
+
3199
+ body = {
3200
+ "q": q,
3201
+ "trigram_k": trigram_k,
3202
+ "embed_k": embed_k,
3203
+ "w_trgm": w_trgm,
3204
+ "w_emb": w_emb,
3205
+ "embedding_model": embedding_model,
3206
+ }
3207
+
3208
+ if q_embedding is not None:
3209
+ body["q_embedding"] = [float(x) for x in q_embedding]
3210
+
3211
+ if filters:
3212
+ body.update(filters)
3213
+ body.update(cls._sdk_owned_query_context(operation))
3214
+
3215
+ payload = {"params": serialize_to_json(body)}
3216
+ response = make_request(
3217
+ s=cls.build_session(),
3218
+ loaders=cls.LOADERS,
3219
+ r_type="GET",
3220
+ url=f"{cls.get_object_url().rstrip('/')}/description-search/",
3221
+ payload=payload,
3222
+ )
3223
+ if response.status_code != 200:
3224
+ raise_for_response(response, payload=payload)
3225
+
3226
+ return cls._deserialize_search_response(response.json())
3227
+
3262
3228
  def _time_indexed_storage_layout(self) -> dict[str, Any]:
3263
3229
  profile = self.time_indexed_profile
3264
3230
  if profile is not None and isinstance(profile.storage_layout, Mapping):
@@ -697,13 +697,6 @@ class GitHubRepositoryBranchList(BasePydanticModel):
697
697
  results: list[GitHubRepositoryBranch]
698
698
 
699
699
 
700
- class GitHubRepositoryBranchImportResult(BasePydanticModel):
701
- repository_uid: str
702
- code_repository_branch_uid: str
703
- repository_branch: str
704
- is_initialized: bool
705
-
706
-
707
700
  class GitHubRepositoryBinding(BasePydanticModel, BaseObjectOrm):
708
701
  FILTERSET_FIELDS: ClassVar[dict[str, list[str]]] = {
709
702
  "uid": ["in", "exact"],
@@ -731,28 +724,6 @@ class GitHubRepositoryBinding(BasePydanticModel, BaseObjectOrm):
731
724
  raise_for_response(r)
732
725
  return GitHubRepositoryBranchList.model_validate(r.json())
733
726
 
734
- def import_branch(
735
- self,
736
- *,
737
- repository_branch: str,
738
- metatables_data_source_uid: str | None = None,
739
- timeout=None,
740
- ) -> GitHubRepositoryBranchImportResult:
741
- body: dict[str, Any] = {"repository_branch": repository_branch}
742
- if metatables_data_source_uid is not None:
743
- body["metatables_data_source_uid"] = str(metatables_data_source_uid)
744
- url = f"{type(self).get_object_url()}/{self._public_detail_reference()}/import-branch/"
745
- r = make_request(
746
- s=type(self).build_session(),
747
- loaders=type(self).LOADERS,
748
- r_type="POST",
749
- url=url,
750
- payload={"json": body},
751
- time_out=timeout,
752
- )
753
- raise_for_response(r)
754
- return GitHubRepositoryBranchImportResult.model_validate(r.json())
755
-
756
727
 
757
728
  class CodeRepositoryImageSourceProvenance(BasePydanticModel):
758
729
  verification_state: Literal["verified", "unverified", "building", "failed"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.20.dev4
3
+ Version: 8.1.20.dev8
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -133,6 +133,7 @@ tests/test_time_index_table_update_configuration.py
133
133
  tests/test_time_index_table_update_configuration_client.py
134
134
  tests/test_time_index_table_update_flow.py
135
135
  tests/test_time_index_table_update_hard_cut.py
136
+ tests/test_tolerant_response_reading.py
136
137
  tests/test_update_runner_uid_runtime.py
137
138
  tests/test_update_statistics.py
138
139
  tests/test_update_uid_guards.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "8.1.20.dev4"
7
+ version = "8.1.20.dev8"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -129,11 +129,18 @@ def test_client_auth_provider_routes_match_backend_contract(monkeypatch):
129
129
 
130
130
  def test_removed_backend_actions_are_not_exposed_by_client_models():
131
131
  from mainsequence.client.metatables import DataSource, TimeIndexTableUpdate
132
+ from mainsequence.client.models_foundry import GitHubRepositoryBinding
132
133
  from mainsequence.client.models_helpers import Job
133
134
 
134
135
  removed_actions = {
135
136
  DataSource: ("get_or_create_sqlite", "create_sqlite"),
136
- TimeIndexTableUpdate: ("add_tags", "filter_by_hash_id", "get_upstream_nodes"),
137
+ TimeIndexTableUpdate: (
138
+ "add_tags",
139
+ "filter_by_hash_id",
140
+ "get_upstream_nodes",
141
+ "verify_if_direct_dependencies_are_updated",
142
+ ),
143
+ GitHubRepositoryBinding: ("import_branch",),
137
144
  Job: ("bulk_get_or_create", "create_from_configuration", "sync_jobs"),
138
145
  }
139
146
  violations = [
@@ -146,6 +153,16 @@ def test_removed_backend_actions_are_not_exposed_by_client_models():
146
153
  assert violations == []
147
154
 
148
155
 
156
+ def test_description_search_is_defined_only_where_the_backend_serves_it():
157
+ """`description-search/` is served for time-index-meta-tables only."""
158
+ from mainsequence.client.metatables import MetaTable, TimeIndexMetaTable
159
+
160
+ assert "description_search" not in vars(MetaTable)
161
+ assert not hasattr(MetaTable, "description_search")
162
+ assert "description_search" in vars(TimeIndexMetaTable)
163
+ assert TimeIndexMetaTable.get_object_url().endswith("/time-index-meta-tables")
164
+
165
+
149
166
  def test_cli_does_not_reintroduce_legacy_numeric_detail_routes_or_agent_runs():
150
167
  from mainsequence.cli import api
151
168
  from mainsequence.client import agent_runtime_models
@@ -8197,6 +8197,40 @@ def test_time_index_table_run_query(cli_mod, runner, monkeypatch):
8197
8197
  assert '"value": 1' in result.output
8198
8198
 
8199
8199
 
8200
+ def test_time_index_table_run_query_error_is_not_double_prefixed(cli_mod, runner, monkeypatch):
8201
+ """The api layer already names the operation; the CLI must not prefix it again."""
8202
+ monkeypatch.setattr(cli_mod, "_require_login", lambda: {"username": "u"})
8203
+
8204
+ def _run_query(storage_uid, sql, *, timeout=None):
8205
+ raise cli_mod.ApiError(
8206
+ "Time-index table query failed: 415 POST "
8207
+ "http://127.0.0.1:8000/api/v1/time-index-meta-tables/abc/run-query/: "
8208
+ 'Unsupported media type "text/plain" in request.'
8209
+ )
8210
+
8211
+ monkeypatch.setattr(cli_mod, "run_time_index_table_query", _run_query)
8212
+
8213
+ result = runner.invoke(
8214
+ cli_mod.app,
8215
+ ["time-index-table", "run_query", "abc", "SELECT 1 AS value"],
8216
+ )
8217
+ assert result.exit_code == 1
8218
+ assert result.output.count("Time-index table query failed:") == 1
8219
+
8220
+
8221
+ def test_meta_table_run_query_error_is_not_double_prefixed(cli_mod, runner, monkeypatch):
8222
+ monkeypatch.setattr(cli_mod, "_require_login", lambda: {"username": "u"})
8223
+
8224
+ def _run_query(meta_table_uid, sql, *, timeout=None):
8225
+ raise cli_mod.ApiError("MetaTable query failed: boom")
8226
+
8227
+ monkeypatch.setattr(cli_mod, "run_meta_table_query", _run_query)
8228
+
8229
+ result = runner.invoke(cli_mod.app, ["meta-table", "run_query", "abc", "SELECT 1"])
8230
+ assert result.exit_code == 1
8231
+ assert result.output.count("MetaTable query failed:") == 1
8232
+
8233
+
8200
8234
  def test_meta_table_run_query(cli_mod, runner, monkeypatch):
8201
8235
  captured = {}
8202
8236
  monkeypatch.setattr(cli_mod, "_require_login", lambda: {"username": "u"})
@@ -82,11 +82,15 @@ def test_code_repository_is_only_the_logical_aggregate_contract():
82
82
  assert not hasattr(code_repository.branches[0], "code_repository_type")
83
83
  assert not hasattr(code_repository, "repository_branch")
84
84
 
85
- with pytest.raises(ValidationError):
85
+ # Tolerant reading (ADR 0032): the retired field is dropped, not declared.
86
+ assert "repository_branch" not in models_foundry.CodeRepository.model_fields
87
+ assert not hasattr(
86
88
  models_foundry.CodeRepository(
87
89
  **logical_code_repository_payload(),
88
90
  repository_branch="main",
89
- )
91
+ ),
92
+ "repository_branch",
93
+ )
90
94
 
91
95
 
92
96
  def test_code_repository_requires_canonical_type_and_derived_technology():
@@ -722,7 +722,7 @@ def test_metatable_request_always_submits_git_resolved_context(monkeypatch):
722
722
  }
723
723
 
724
724
 
725
- def test_code_repository_model_rejects_removed_default_data_source_fields():
725
+ def test_code_repository_model_drops_removed_default_data_source_fields():
726
726
  payload = {
727
727
  "uid": CODE_REPOSITORY_UID,
728
728
  "code_repository_name": "Example CodeRepository",
@@ -737,5 +737,9 @@ def test_code_repository_model_rejects_removed_default_data_source_fields():
737
737
  "default_metatables_data_source_uid": DATA_SOURCE_UID,
738
738
  }
739
739
 
740
- with pytest.raises(ValueError, match="default_metatables_data_source_uid"):
741
- models_foundry.CodeRepository.model_validate(payload)
740
+ # Tolerant reading (ADR 0032): the retired field is dropped, not declared.
741
+ assert "default_metatables_data_source_uid" not in models_foundry.CodeRepository.model_fields
742
+ assert not hasattr(
743
+ models_foundry.CodeRepository.model_validate(payload),
744
+ "default_metatables_data_source_uid",
745
+ )
@@ -87,13 +87,13 @@ RUNTIME_UPDATE_FIELD = "runtime_update"
87
87
  ),
88
88
  ],
89
89
  )
90
- def test_backend_environment_response_contract_is_declared_by_strict_sdk_model(
90
+ def test_backend_environment_response_contract_is_declared_by_sdk_model(
91
91
  serializer_names,
92
92
  model,
93
93
  expected_fields,
94
94
  ):
95
95
  """Keep SDK response models aligned with ADR-0036/ADR-0037 projections."""
96
- assert model.model_config.get("extra") == "forbid", serializer_names
96
+ assert model.model_config.get("extra") == "ignore", serializer_names
97
97
  assert expected_fields <= set(model.model_fields), serializer_names
98
98
 
99
99
 
@@ -106,13 +106,13 @@ def test_backend_environment_response_contract_is_declared_by_strict_sdk_model(
106
106
  helper_models.ResourceRelease,
107
107
  ],
108
108
  )
109
- def test_owner_observability_code_repositoryion_is_declared_by_strict_sdk_model(model):
110
- assert model.model_config.get("extra") == "forbid"
109
+ def test_owner_observability_code_repositoryion_is_declared_by_sdk_model(model):
110
+ assert model.model_config.get("extra") == "ignore"
111
111
  assert "observability" in model.model_fields
112
112
 
113
113
 
114
114
  def test_agent_session_runtime_capabilities_code_repositoryion_is_declared_and_typed():
115
- assert agent_models.AgentSession.model_config.get("extra") == "forbid"
115
+ assert agent_models.AgentSession.model_config.get("extra") == "ignore"
116
116
  assert (
117
117
  agent_models.AgentSession.model_fields["runtime_capabilities"].annotation == dict[str, str]
118
118
  )