mainsequence 8.1.20.dev4__tar.gz → 8.1.20.dev7__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.dev7}/PKG-INFO +1 -1
  2. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/base.py +14 -1
  3. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/metatables/core.py +48 -65
  4. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/models_foundry.py +0 -29
  5. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7/mainsequence.egg-info}/PKG-INFO +1 -1
  6. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/SOURCES.txt +1 -0
  7. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/pyproject.toml +1 -1
  8. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_canonical_api_route_contract.py +18 -1
  9. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_branch_contract.py +6 -2
  10. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_runtime_context.py +7 -3
  11. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_environment_response_contracts.py +5 -5
  12. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_filter_normalization.py +63 -32
  13. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_observability_client.py +6 -5
  14. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_source_table_configuration.py +14 -8
  15. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_update_configuration_client.py +6 -3
  16. mainsequence-8.1.20.dev7/tests/test_tolerant_response_reading.py +137 -0
  17. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_update_uid_guards.py +0 -1
  18. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/LICENSE +0 -0
  19. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/MANIFEST.in +0 -0
  20. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/README.md +0 -0
  21. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/AGENTS.md +0 -0
  22. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  23. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  24. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  25. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  26. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  27. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
  28. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  29. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  30. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  31. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  32. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  33. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/__init__.py +0 -0
  34. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/__main__.py +0 -0
  35. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/bootstrap.py +0 -0
  36. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/__init__.py +0 -0
  37. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/api.py +0 -0
  38. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/browser_auth.py +0 -0
  39. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/cli.py +0 -0
  40. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/code_repository_status.py +0 -0
  41. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/config.py +0 -0
  42. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/docker_utils.py +0 -0
  43. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/doctor.py +0 -0
  44. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/local_ops.py +0 -0
  45. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/migrations.py +0 -0
  46. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/model_filters.py +0 -0
  47. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/pydantic_cli.py +0 -0
  48. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/sdk_utils.py +0 -0
  49. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/ssh_utils.py +0 -0
  50. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/cli/ui.py +0 -0
  51. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/__init__.py +0 -0
  52. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/agent_runtime_models.py +0 -0
  53. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/client.py +0 -0
  54. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/compute_validation.py +0 -0
  55. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  56. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  57. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  58. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  59. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/dtype_codec.py +0 -0
  60. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/exceptions.py +0 -0
  61. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/github_issues.py +0 -0
  62. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/metatables/__init__.py +0 -0
  63. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/models_helpers.py +0 -0
  64. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/models_user.py +0 -0
  65. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/observability.py +0 -0
  66. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/client/utils.py +0 -0
  67. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/code_repository_context.py +0 -0
  68. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/code_repository_skills.py +0 -0
  69. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/defaults.py +0 -0
  70. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/instrumentation/__init__.py +0 -0
  71. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/instrumentation/utils.py +0 -0
  72. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/logconf.py +0 -0
  73. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/__init__.py +0 -0
  74. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/__main__.py +0 -0
  75. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  76. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  77. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/future_registry.py +0 -0
  78. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/hashing.py +0 -0
  79. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  80. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  81. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/env.py +0 -0
  82. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/provider.py +0 -0
  83. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/registry.py +0 -0
  84. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  85. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  86. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  87. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  88. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  89. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/schema_names.py +0 -0
  90. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  91. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
  92. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
  93. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
  94. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
  95. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -0
  96. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
  97. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/runner.py +0 -0
  98. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -0
  99. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
  100. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/repository_identity_security.py +0 -0
  101. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/runtime_flags.py +0 -0
  102. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence/scaffold_skills.py +0 -0
  103. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/dependency_links.txt +0 -0
  104. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/entry_points.txt +0 -0
  105. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/requires.txt +0 -0
  106. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/mainsequence.egg-info/top_level.txt +0 -0
  107. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/setup.cfg +0 -0
  108. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
  109. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_auth_precedence.py +0 -0
  110. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_cli.py +0 -0
  111. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_cli_browser_auth.py +0 -0
  112. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_cli_migrations.py +0 -0
  113. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_client.py +0 -0
  114. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_batch_jobs_from_file.py +0 -0
  115. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_documentation_contract.py +0 -0
  116. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_code_repository_skills.py +0 -0
  117. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_dependency_extras.py +0 -0
  118. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_dev_release_version.py +0 -0
  119. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_duckdb_interface_dimensions.py +0 -0
  120. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_environment_resource_context.py +0 -0
  121. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_github_issues_client.py +0 -0
  122. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_instrumentation.py +0 -0
  123. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_local_ops.py +0 -0
  124. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_logconf.py +0 -0
  125. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_meta_table_migrations.py +0 -0
  126. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_meta_tables_client_models.py +0 -0
  127. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  128. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_models_user_request_bound_auth.py +0 -0
  129. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_notification_uid_contract.py +0 -0
  130. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_pod_project_resolution.py +0 -0
  131. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_scaffold_skills.py +0 -0
  132. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_schema_names.py +0 -0
  133. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_secret_client_model.py +0 -0
  134. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_sqlite_interface_dimensions.py +0 -0
  135. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_ssh_utils.py +0 -0
  136. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_access_dimension_audit.py +0 -0
  137. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_dimension_queries.py +0 -0
  138. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_update_configuration.py +0 -0
  139. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_update_flow.py +0 -0
  140. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_time_index_table_update_hard_cut.py +0 -0
  141. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/tests/test_update_runner_uid_runtime.py +0 -0
  142. {mainsequence-8.1.20.dev4 → mainsequence-8.1.20.dev7}/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.dev7
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -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
 
@@ -1945,54 +1945,6 @@ class MetaTable(
1945
1945
  raise_for_response(response, payload=error_payload)
1946
1946
  return response.json()
1947
1947
 
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
1948
  @classmethod
1997
1949
  def column_search(cls, q: str, **filters):
1998
1950
  q = (q or "").strip()
@@ -2768,23 +2720,6 @@ class TimeIndexTableUpdate(TableUpdateNode, BaseObjectOrm):
2768
2720
  return None
2769
2721
  return r.json()
2770
2722
 
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
2723
  def get_data_between_dates_from_api(self, *args, **kwargs):
2789
2724
  return self.output_table.get_data_between_dates_from_api(*args, **kwargs)
2790
2725
 
@@ -3259,6 +3194,54 @@ class TimeIndexMetaTable(MetaTable):
3259
3194
  )
3260
3195
  return self
3261
3196
 
3197
+ @classmethod
3198
+ def description_search(
3199
+ cls,
3200
+ q: str,
3201
+ *,
3202
+ q_embedding: Sequence[float] | None = None,
3203
+ trigram_k: int = 200,
3204
+ embed_k: int = 200,
3205
+ w_trgm: float = 0.65,
3206
+ w_emb: float = 0.35,
3207
+ embedding_model: str = "default",
3208
+ **filters,
3209
+ ):
3210
+ q = (q or "").strip()
3211
+ if not q:
3212
+ raise ValueError("q is required")
3213
+ operation = f"{cls.__name__}.description_search"
3214
+ cls._reject_sdk_context_overrides(filters, operation=operation)
3215
+
3216
+ body = {
3217
+ "q": q,
3218
+ "trigram_k": trigram_k,
3219
+ "embed_k": embed_k,
3220
+ "w_trgm": w_trgm,
3221
+ "w_emb": w_emb,
3222
+ "embedding_model": embedding_model,
3223
+ }
3224
+
3225
+ if q_embedding is not None:
3226
+ body["q_embedding"] = [float(x) for x in q_embedding]
3227
+
3228
+ if filters:
3229
+ body.update(filters)
3230
+ body.update(cls._sdk_owned_query_context(operation))
3231
+
3232
+ payload = {"params": serialize_to_json(body)}
3233
+ response = make_request(
3234
+ s=cls.build_session(),
3235
+ loaders=cls.LOADERS,
3236
+ r_type="GET",
3237
+ url=f"{cls.get_object_url().rstrip('/')}/description-search/",
3238
+ payload=payload,
3239
+ )
3240
+ if response.status_code != 200:
3241
+ raise_for_response(response, payload=payload)
3242
+
3243
+ return cls._deserialize_search_response(response.json())
3244
+
3262
3245
  def _time_indexed_storage_layout(self) -> dict[str, Any]:
3263
3246
  profile = self.time_indexed_profile
3264
3247
  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.dev7
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.dev7"
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
@@ -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
  )
@@ -67,7 +67,7 @@ def test_agent_runtime_update_parses_every_backend_state(
67
67
  ) == remediation_tool
68
68
 
69
69
 
70
- def test_agent_runtime_update_contract_is_complete_and_closed():
70
+ def test_agent_runtime_update_contract_is_complete_and_read_tolerantly():
71
71
  payload = _agent_runtime_update_contract("unknown")
72
72
  for field_name in ("state", "needs_redeploy", "remediation"):
73
73
  incomplete = dict(payload)
@@ -75,8 +75,12 @@ def test_agent_runtime_update_contract_is_complete_and_closed():
75
75
  with pytest.raises(ValidationError, match=field_name):
76
76
  agent_models_mod.AgentRuntimeUpdate.model_validate(incomplete)
77
77
 
78
- with pytest.raises(ValidationError, match="extra_forbidden"):
79
- agent_models_mod.AgentRuntimeUpdate.model_validate(payload | {"future_state": "value"})
78
+ # Tolerant reading (ADR 0032): a field this release does not declare is dropped.
79
+ tolerated = agent_models_mod.AgentRuntimeUpdate.model_validate(
80
+ payload | {"future_state": "value"}
81
+ )
82
+ assert tolerated.state == payload["state"]
83
+ assert not hasattr(tolerated, "future_state")
80
84
 
81
85
  invalid_remediation = _agent_runtime_update_contract("update_required")
82
86
  invalid_remediation["remediation"] = {"tool": "agent.update_session"}
@@ -1178,7 +1182,7 @@ def test_get_by_uid_normalizes_read_query_params(monkeypatch):
1178
1182
  }
1179
1183
 
1180
1184
 
1181
- def test_patch_by_uid_raises_with_context_for_unmapped_response_fields(monkeypatch):
1185
+ def test_patch_by_uid_ignores_unmapped_response_fields(monkeypatch):
1182
1186
  captured = {}
1183
1187
 
1184
1188
  class FakeResponse:
@@ -1201,14 +1205,15 @@ def test_patch_by_uid_raises_with_context_for_unmapped_response_fields(monkeypat
1201
1205
 
1202
1206
  monkeypatch.setattr(base_mod, "make_request", _fake_make_request)
1203
1207
 
1204
- with pytest.raises(ValueError) as exc_info:
1205
- DemoPatchModel.patch_by_uid("uid-9", _into=DemoPatchModel(id=9), label="patched")
1208
+ instance = DemoPatchModel(id=9)
1209
+ patched = DemoPatchModel.patch_by_uid("uid-9", _into=instance, label="patched")
1206
1210
 
1207
- assert str(exc_info.value) == (
1208
- "Failed to apply PATCH response to DemoPatchModel at field 'schema'. "
1209
- "Response fragment: {'schema': {'name': 'customers'}}. "
1210
- 'Original error: "DemoPatchModel" object has no field "schema"'
1211
- )
1211
+ # Tolerant reading (ADR 0032): a response key the model does not declare is
1212
+ # dropped, exactly as parsing drops it.
1213
+ assert patched is instance
1214
+ assert patched.id == 9
1215
+ assert "schema" not in DemoPatchModel.model_fields
1216
+ assert "schema" not in patched.__dict__
1212
1217
  assert captured == {
1213
1218
  "r_type": "PATCH",
1214
1219
  "url": "https://backend.test/demo-patch/uid-9/",
@@ -1449,7 +1454,7 @@ def test_shareable_can_edit_parses_permission_state(monkeypatch):
1449
1454
  }
1450
1455
 
1451
1456
 
1452
- def test_shareable_access_state_rejects_removed_internal_identity():
1457
+ def test_shareable_access_state_drops_removed_internal_identity():
1453
1458
  removed_field = "_".join(("object", "id"))
1454
1459
  payload = {
1455
1460
  "object_uid": "33333333-3333-4333-8333-333333333333",
@@ -1460,8 +1465,11 @@ def test_shareable_access_state_rejects_removed_internal_identity():
1460
1465
  removed_field: 17,
1461
1466
  }
1462
1467
 
1463
- with pytest.raises(ValidationError, match=removed_field):
1464
- models_user_mod.ShareableAccessState.model_validate(payload)
1468
+ state = models_user_mod.ShareableAccessState.model_validate(payload)
1469
+
1470
+ # Tolerant reading (ADR 0032): the retired field is dropped, not declared.
1471
+ assert removed_field not in models_user_mod.ShareableAccessState.model_fields
1472
+ assert not hasattr(state, removed_field)
1465
1473
 
1466
1474
 
1467
1475
  @pytest.mark.parametrize(
@@ -1974,13 +1982,22 @@ def test_agent_client_contract_matches_backend_agent_serializer():
1974
1982
  {**payload, "agent_card": None},
1975
1983
  {**payload, "agent_card": {}},
1976
1984
  {**payload, "name": "Unrelated label"},
1977
- {**payload, "agent_type": "custom"},
1978
- {**payload, "metadata": {}},
1979
- {**payload, "has_agent_service": False},
1980
1985
  ):
1981
1986
  with pytest.raises(ValidationError):
1982
1987
  agent_models_mod.Agent.model_validate(invalid_payload)
1983
1988
 
1989
+ # Tolerant reading (ADR 0032): retired keys are dropped, never declared.
1990
+ for retired_field, retired_value in (
1991
+ ("agent_type", "custom"),
1992
+ ("metadata", {}),
1993
+ ("has_agent_service", False),
1994
+ ):
1995
+ assert retired_field not in agent_models_mod.Agent.model_fields
1996
+ assert not hasattr(
1997
+ agent_models_mod.Agent.model_validate({**payload, retired_field: retired_value}),
1998
+ retired_field,
1999
+ )
2000
+
1984
2001
  with pytest.raises(agent_models_mod.ApiError, match="harness_agent workflow"):
1985
2002
  agent_models_mod.Agent.create(name="Research Copilot")
1986
2003
  with pytest.raises(ValueError, match="Unsupported Agent filter"):
@@ -2206,10 +2223,13 @@ def test_agent_get_parses_runtime_update_projection(monkeypatch):
2206
2223
  is None
2207
2224
  )
2208
2225
 
2209
- with pytest.raises(ValidationError, match="unexpected_projection"):
2226
+ # Tolerant reading (ADR 0032): an undeclared projection is dropped.
2227
+ assert not hasattr(
2210
2228
  agent_models_mod.Agent.model_validate(
2211
- {**FakeResponse.json(), "unexpected_projection": "still forbidden"}
2212
- )
2229
+ {**FakeResponse.json(), "unexpected_projection": "ignored"}
2230
+ ),
2231
+ "unexpected_projection",
2232
+ )
2213
2233
 
2214
2234
 
2215
2235
  def test_agent_semantic_search_sends_environment_scope_and_parses_code_repositoryion(monkeypatch):
@@ -2411,10 +2431,13 @@ def test_agent_session_list_and_detail_parse_runtime_capabilities(monkeypatch):
2411
2431
  assert detailed.organization_environment_name == "production"
2412
2432
  assert detailed.catalog_digest == "sha256:" + ("b" * 64)
2413
2433
 
2414
- with pytest.raises(ValidationError, match="unexpected_projection"):
2434
+ # Tolerant reading (ADR 0032): an undeclared projection is dropped.
2435
+ assert not hasattr(
2415
2436
  agent_models_mod.AgentSession.model_validate(
2416
- {**session_payload, "unexpected_projection": "still forbidden"}
2417
- )
2437
+ {**session_payload, "unexpected_projection": "ignored"}
2438
+ ),
2439
+ "unexpected_projection",
2440
+ )
2418
2441
 
2419
2442
 
2420
2443
  def test_agent_session_environment_and_catalog_projection_contract_is_strict():
@@ -3525,7 +3548,7 @@ def test_deployment_run_billing_parses_every_backend_pricing_state(pricing_state
3525
3548
  assert billing.components.image_build == billing.total_cost
3526
3549
 
3527
3550
 
3528
- def test_deployment_run_billing_contract_is_complete_and_closed():
3551
+ def test_deployment_run_billing_contract_is_complete_and_read_tolerantly():
3529
3552
  payload = _deployment_run_billing_payload()
3530
3553
  for field_name in (
3531
3554
  "scope",
@@ -3542,15 +3565,20 @@ def test_deployment_run_billing_contract_is_complete_and_closed():
3542
3565
  with pytest.raises(ValidationError, match=field_name):
3543
3566
  models_helpers_mod.DeploymentRunBilling.model_validate(incomplete)
3544
3567
 
3545
- with pytest.raises(ValidationError, match="extra_forbidden"):
3568
+ # Tolerant reading (ADR 0032): a billing field or component this release does
3569
+ # not declare is dropped instead of failing the whole response.
3570
+ assert not hasattr(
3546
3571
  models_helpers_mod.DeploymentRunBilling.model_validate(
3547
3572
  payload | {"future_billing_field": "value"}
3548
- )
3549
-
3550
- with pytest.raises(ValidationError, match="extra_forbidden"):
3573
+ ),
3574
+ "future_billing_field",
3575
+ )
3576
+ assert not hasattr(
3551
3577
  models_helpers_mod.DeploymentRunBilling.model_validate(
3552
3578
  payload | {"components": payload["components"] | {"future_component": "0.000000"}}
3553
- )
3579
+ ).components,
3580
+ "future_component",
3581
+ )
3554
3582
 
3555
3583
  with pytest.raises(ValidationError, match="greater_than_equal"):
3556
3584
  models_helpers_mod.DeploymentRunBilling.model_validate(payload | {"unpriced_rows": -1})
@@ -3618,7 +3646,7 @@ def test_deployment_run_cost_summary_parses_nullable_decimal(total_cost):
3618
3646
  ),
3619
3647
  ],
3620
3648
  )
3621
- def test_deployment_run_additive_billing_contracts_are_complete_and_closed(
3649
+ def test_deployment_run_additive_billing_contracts_are_complete_and_read_tolerantly(
3622
3650
  model,
3623
3651
  payload,
3624
3652
  required_fields,
@@ -3629,8 +3657,11 @@ def test_deployment_run_additive_billing_contracts_are_complete_and_closed(
3629
3657
  with pytest.raises(ValidationError, match=field_name):
3630
3658
  model.model_validate(incomplete)
3631
3659
 
3632
- with pytest.raises(ValidationError, match="extra_forbidden"):
3633
- model.model_validate(payload | {"future_billing_field": "value"})
3660
+ # Tolerant reading (ADR 0032): an undeclared billing field is dropped.
3661
+ assert not hasattr(
3662
+ model.model_validate(payload | {"future_billing_field": "value"}),
3663
+ "future_billing_field",
3664
+ )
3634
3665
 
3635
3666
 
3636
3667
  @pytest.mark.parametrize("field_name", ["priced_rows", "unpriced_rows"])
@@ -320,11 +320,12 @@ def test_agent_logs_support_optional_session_filter_without_environment_override
320
320
  )
321
321
 
322
322
 
323
- def test_owner_observability_models_remain_strict_except_enriched_rows():
324
- assert observability_models.ObservabilityLinks.model_config["extra"] == "forbid"
325
- assert observability_models.OwnerLogPage.model_config["extra"] == "forbid"
326
- assert observability_models.EnvironmentLogSearchPage.model_config["extra"] == "forbid"
327
- assert observability_models.ResourceUsagePage.model_config["extra"] == "forbid"
323
+ def test_owner_observability_models_read_tolerantly_and_keep_enriched_rows():
324
+ # Tolerant reading (ADR 0032): pages drop undeclared fields, rows keep them.
325
+ assert observability_models.ObservabilityLinks.model_config["extra"] == "ignore"
326
+ assert observability_models.OwnerLogPage.model_config["extra"] == "ignore"
327
+ assert observability_models.EnvironmentLogSearchPage.model_config["extra"] == "ignore"
328
+ assert observability_models.ResourceUsagePage.model_config["extra"] == "ignore"
328
329
  assert observability_models.OwnerLogRow.model_config["extra"] == "allow"
329
330
  assert observability_models.EnvironmentLogSearchRow.model_config["extra"] == "allow"
330
331
 
@@ -1,7 +1,6 @@
1
1
  import datetime
2
2
 
3
3
  import pytest
4
- from pydantic import ValidationError
5
4
 
6
5
  from mainsequence.client import metatables as models_metatables
7
6
 
@@ -60,16 +59,20 @@ def test_time_indexed_profile_parses_canonical_response():
60
59
  assert config.earliest_index_value == datetime.datetime(2026, 5, 1, tzinfo=datetime.UTC)
61
60
 
62
61
 
63
- def test_time_indexed_profile_rejects_removed_legacy_profile_uid():
62
+ def test_time_indexed_profile_drops_removed_legacy_profile_uid():
64
63
  payload = _source_config_payload()
65
64
  removed_field = "_".join(("dynamic", "table", "uid"))
66
- payload[removed_field] = payload.pop("time_index_meta_table_uid")
65
+ payload[removed_field] = "legacy-uid-44"
67
66
 
68
- with pytest.raises(ValidationError, match=removed_field):
69
- models_metatables.TimeIndexedProfile(**payload)
67
+ profile = models_metatables.TimeIndexedProfile(**payload)
70
68
 
69
+ # Tolerant reading (ADR 0032): the legacy name is dropped, never honoured.
70
+ assert removed_field not in models_metatables.TimeIndexedProfile.model_fields
71
+ assert not hasattr(profile, removed_field)
72
+ assert profile.time_index_meta_table_uid == "storage-uid-44"
71
73
 
72
- def test_time_indexed_profile_rejects_foreign_keys():
74
+
75
+ def test_time_indexed_profile_drops_foreign_keys():
73
76
  payload = _source_config_payload()
74
77
  payload["foreign_keys"] = [
75
78
  {
@@ -81,8 +84,11 @@ def test_time_indexed_profile_rejects_foreign_keys():
81
84
  }
82
85
  ]
83
86
 
84
- with pytest.raises(ValidationError, match="foreign_keys"):
85
- models_metatables.TimeIndexedProfile(**payload)
87
+ profile = models_metatables.TimeIndexedProfile(**payload)
88
+
89
+ # Tolerant reading (ADR 0032): foreign keys stay Alembic-owned and undeclared.
90
+ assert "foreign_keys" not in models_metatables.TimeIndexedProfile.model_fields
91
+ assert not hasattr(profile, "foreign_keys")
86
92
 
87
93
 
88
94
  def test_time_index_meta_table_contract_requires_registered_profile_index_names():
@@ -354,7 +354,7 @@ def test_output_table_accepts_namespace():
354
354
  "table_name",
355
355
  ],
356
356
  )
357
- def test_output_table_rejects_removed_backend_fields(removed_field):
357
+ def test_output_table_drops_removed_backend_fields(removed_field):
358
358
  payload = {
359
359
  "uid": "time-index-table-storage-1",
360
360
  "storage_hash": "hash",
@@ -370,8 +370,11 @@ def test_output_table_rejects_removed_backend_fields(removed_field):
370
370
  removed_field: "removed",
371
371
  }
372
372
 
373
- with pytest.raises(ValidationError):
374
- TimeIndexMetaTable(**payload)
373
+ table = TimeIndexMetaTable(**payload)
374
+
375
+ # Tolerant reading (ADR 0032): a retired field is dropped, not declared.
376
+ assert removed_field not in TimeIndexMetaTable.model_fields
377
+ assert not hasattr(table, removed_field)
375
378
 
376
379
 
377
380
  def test_update_manager_requires_output_table_constructor_argument():