mainsequence 6.0.51__tar.gz → 6.0.53__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 (133) hide show
  1. {mainsequence-6.0.51 → mainsequence-6.0.53}/PKG-INFO +1 -1
  2. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +3 -0
  3. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +7 -0
  4. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/agent_runtime_models.py +7 -0
  5. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/metatables/core.py +119 -0
  6. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence.egg-info/PKG-INFO +1 -1
  7. {mainsequence-6.0.51 → mainsequence-6.0.53}/pyproject.toml +1 -1
  8. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_environment_response_contracts.py +5 -0
  9. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_filter_normalization.py +157 -0
  10. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_meta_tables_client_models.py +157 -0
  11. {mainsequence-6.0.51 → mainsequence-6.0.53}/LICENSE +0 -0
  12. {mainsequence-6.0.51 → mainsequence-6.0.53}/README.md +0 -0
  13. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/AGENTS.md +0 -0
  14. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  15. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  16. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  17. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  18. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  19. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +0 -0
  20. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  21. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  22. {mainsequence-6.0.51 → mainsequence-6.0.53}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
  23. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/__init__.py +0 -0
  24. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/__main__.py +0 -0
  25. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/bootstrap.py +0 -0
  26. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/__init__.py +0 -0
  27. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/api.py +0 -0
  28. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/browser_auth.py +0 -0
  29. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/cli.py +0 -0
  30. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/config.py +0 -0
  31. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/docker_utils.py +0 -0
  32. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/doctor.py +0 -0
  33. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/local_ops.py +0 -0
  34. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/migrations.py +0 -0
  35. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/model_filters.py +0 -0
  36. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/project_status.py +0 -0
  37. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/pydantic_cli.py +0 -0
  38. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/sdk_utils.py +0 -0
  39. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/ssh_utils.py +0 -0
  40. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/cli/ui.py +0 -0
  41. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/__init__.py +0 -0
  42. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/base.py +0 -0
  43. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/client.py +0 -0
  44. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/compute_validation.py +0 -0
  45. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  46. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  47. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  48. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  49. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/dtype_codec.py +0 -0
  50. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/exceptions.py +0 -0
  51. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/metatables/__init__.py +0 -0
  52. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/models_foundry.py +0 -0
  53. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/models_helpers.py +0 -0
  54. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/models_user.py +0 -0
  55. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/observability.py +0 -0
  56. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/client/utils.py +0 -0
  57. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/defaults.py +0 -0
  58. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/instrumentation/__init__.py +0 -0
  59. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/instrumentation/utils.py +0 -0
  60. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/logconf.py +0 -0
  61. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/__init__.py +0 -0
  62. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/__main__.py +0 -0
  63. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  64. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  65. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  66. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  67. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  68. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  69. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  70. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  71. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  72. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  73. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/future_registry.py +0 -0
  74. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/hashing.py +0 -0
  75. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  76. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  77. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/migrations/env.py +0 -0
  78. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/migrations/provider.py +0 -0
  79. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/migrations/registry.py +0 -0
  80. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  81. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  82. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  83. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  84. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  85. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/schema_names.py +0 -0
  86. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  87. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/project_context.py +0 -0
  88. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/project_skills.py +0 -0
  89. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/runtime_flags.py +0 -0
  90. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence/scaffold_skills.py +0 -0
  91. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence.egg-info/SOURCES.txt +0 -0
  92. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence.egg-info/dependency_links.txt +0 -0
  93. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence.egg-info/entry_points.txt +0 -0
  94. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence.egg-info/requires.txt +0 -0
  95. {mainsequence-6.0.51 → mainsequence-6.0.53}/mainsequence.egg-info/top_level.txt +0 -0
  96. {mainsequence-6.0.51 → mainsequence-6.0.53}/setup.cfg +0 -0
  97. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_auth_precedence.py +0 -0
  98. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_build_operations_hashing.py +0 -0
  99. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_canonical_api_route_contract.py +0 -0
  100. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_cli.py +0 -0
  101. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_cli_browser_auth.py +0 -0
  102. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_cli_migrations.py +0 -0
  103. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_client.py +0 -0
  104. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  105. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_data_node_storage_dimension_queries.py +0 -0
  106. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_data_node_update_flow.py +0 -0
  107. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_dependency_extras.py +0 -0
  108. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_duckdb_interface_dimensions.py +0 -0
  109. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_environment_resource_context.py +0 -0
  110. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_instrumentation.py +0 -0
  111. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_local_ops.py +0 -0
  112. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_logconf.py +0 -0
  113. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_meta_table_migrations.py +0 -0
  114. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  115. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_models_user_request_bound_auth.py +0 -0
  116. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_notification_uid_contract.py +0 -0
  117. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_observability_client.py +0 -0
  118. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_pod_project_resolution.py +0 -0
  119. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_project_batch_jobs_from_file.py +0 -0
  120. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_project_branch_contract.py +0 -0
  121. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_project_runtime_context.py +0 -0
  122. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_project_skills.py +0 -0
  123. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_run_configuration.py +0 -0
  124. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_scaffold_skills.py +0 -0
  125. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_scheduler_client_model.py +0 -0
  126. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_schema_names.py +0 -0
  127. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_secret_client_model.py +0 -0
  128. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_source_table_configuration.py +0 -0
  129. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_sqlite_interface_dimensions.py +0 -0
  130. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_ssh_utils.py +0 -0
  131. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_update_runner_uid_runtime.py +0 -0
  132. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_update_statistics.py +0 -0
  133. {mainsequence-6.0.51 → mainsequence-6.0.53}/tests/test_update_uid_guards.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 6.0.51
3
+ Version: 6.0.53
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -71,6 +71,9 @@ mainsequence agent session get_or_create \
71
71
  ```
72
72
 
73
73
  Use the returned session `uid` as the target `AgentSession` UID.
74
+ The returned `runtime_capabilities` map is a read-only advertisement from the
75
+ session harness. Inspect it when capability-aware behavior is required; never
76
+ construct, send, or override it from client code.
74
77
 
75
78
  If this A2A request originates from an existing caller session, include the
76
79
  parent session UID:
@@ -108,6 +108,13 @@ platform-managed, Alembic-managed reservations and are validated before HTTP.
108
108
  - Do not create SDK reset/reconcile commands for stale reserved state. If stale
109
109
  reserved state exists, fail clearly and require an explicit backend/admin
110
110
  repair path.
111
+ - Ordinary `MetaTable.delete()` must not bypass Alembic schema-management
112
+ protection. For a deliberate organization-admin teardown/reset only, use
113
+ `MetaTable.delete_with_cascade(confirm_cascade_delete=True, ...)` and set
114
+ `override_schema_management_protection=True` when deleting Alembic-managed
115
+ tables. Decide explicitly whether referencing MetaTables and time-indexed Data
116
+ Nodes are also deleted. This permanently deletes catalog rows and physical
117
+ tables; never use it as migration reconciliation or an Alembic downgrade.
111
118
  - Do not write direct backend migration request bodies in examples. Use the CLI
112
119
  and SDK provider APIs.
113
120
  - Do not call platform-managed model `.register()` in normal application code.
@@ -1808,6 +1808,13 @@ class AgentSession(OwnerLogMixin, BaseObjectOrm, BasePydanticModel):
1808
1808
  default=None,
1809
1809
  description="Backend-owned application-log capability for this exact session.",
1810
1810
  )
1811
+ runtime_capabilities: dict[str, str] = Field(
1812
+ default_factory=dict,
1813
+ description=(
1814
+ "Read-only versioned runtime capabilities advertised by the session "
1815
+ "harness. Capability names are backend-extensible."
1816
+ ),
1817
+ )
1811
1818
  agent_uid: str | None = Field(
1812
1819
  None, description="Public UID of the agent definition used for this session."
1813
1820
  )
@@ -1030,6 +1030,67 @@ class ManagedMetaTableFinalizeResponse(BasePydanticModel):
1030
1030
  model_config = ConfigDict(extra="allow")
1031
1031
 
1032
1032
 
1033
+ class MetaTableCascadeDeleteRequest(BasePydanticModel):
1034
+ confirm_cascade_delete: Literal[True] = Field(
1035
+ ...,
1036
+ description="Explicit confirmation required for destructive cascade deletion.",
1037
+ )
1038
+ delete_referencing_meta_tables: bool = Field(
1039
+ default=True,
1040
+ description="Delete MetaTables that reference the selected MetaTable.",
1041
+ )
1042
+ delete_referencing_dynamic_tables: bool = Field(
1043
+ default=True,
1044
+ description="Delete time-indexed Data Nodes that reference the selected MetaTable.",
1045
+ )
1046
+ override_schema_management_protection: bool = Field(
1047
+ default=False,
1048
+ description=(
1049
+ "Explicitly allow deletion of Alembic-managed MetaTables. The backend "
1050
+ "still requires organization-admin permission."
1051
+ ),
1052
+ )
1053
+
1054
+ @field_validator("confirm_cascade_delete", mode="before")
1055
+ @classmethod
1056
+ def _require_explicit_confirmation(cls, value: Any) -> Any:
1057
+ if value is not True:
1058
+ raise ValueError("confirm_cascade_delete must be explicitly set to true.")
1059
+ return value
1060
+
1061
+
1062
+ class MetaTableCascadeDeletedMetaTable(BasePydanticModel):
1063
+ meta_table_uid: str
1064
+ physical_schema: str | None = None
1065
+ physical_table_name: str | None = None
1066
+ management_mode: MetaTableManagementMode
1067
+ physical_table_drop_scheduled: bool
1068
+ physical_table_drop_cascade: bool
1069
+ schema_management_protection_overridden: bool
1070
+
1071
+
1072
+ class MetaTableCascadeDeletedDynamicTable(BasePydanticModel):
1073
+ type: Literal["meta_table"]
1074
+ table_kind: Literal["time_indexed"]
1075
+ time_indexed: Literal[True]
1076
+ uid: str
1077
+ identifier: str | None = None
1078
+ namespace: str | None = None
1079
+ physical_schema: str | None = None
1080
+ physical_table_name: str | None = None
1081
+
1082
+
1083
+ class MetaTableCascadeDeleteResponse(BasePydanticModel):
1084
+ ok: Literal[True]
1085
+ action: Literal["delete_with_cascade"]
1086
+ root_meta_table_uid: str
1087
+ deleted_meta_tables: list[MetaTableCascadeDeletedMetaTable]
1088
+ deleted_dynamic_tables: list[MetaTableCascadeDeletedDynamicTable]
1089
+ deleted_meta_table_count: int = Field(..., ge=0)
1090
+ deleted_dynamic_table_count: int = Field(..., ge=0)
1091
+ blocking_edges: list[dict[str, Any]]
1092
+
1093
+
1033
1094
  class DataSource(BasePydanticModel, BaseObjectOrm):
1034
1095
  model_config = ConfigDict(extra="allow")
1035
1096
 
@@ -1558,6 +1619,60 @@ class MetaTable(
1558
1619
  override_protection=override_protection,
1559
1620
  )
1560
1621
 
1622
+ @classmethod
1623
+ def delete_with_cascade_by_uid(
1624
+ cls,
1625
+ uid: str,
1626
+ *,
1627
+ confirm_cascade_delete: bool | None = None,
1628
+ delete_referencing_meta_tables: bool = True,
1629
+ delete_referencing_dynamic_tables: bool = True,
1630
+ override_schema_management_protection: bool = False,
1631
+ timeout: int | float | tuple[float, float] | None = None,
1632
+ ) -> MetaTableCascadeDeleteResponse:
1633
+ if uid in (None, ""):
1634
+ raise ValueError(f"{cls.__name__} uid is required for cascade deletion.")
1635
+ request = MetaTableCascadeDeleteRequest(
1636
+ confirm_cascade_delete=confirm_cascade_delete,
1637
+ delete_referencing_meta_tables=delete_referencing_meta_tables,
1638
+ delete_referencing_dynamic_tables=delete_referencing_dynamic_tables,
1639
+ override_schema_management_protection=override_schema_management_protection,
1640
+ )
1641
+ operation = f"{cls.__name__}.delete_with_cascade"
1642
+ request_payload: dict[str, Any] = {"json": _payload_json(request)}
1643
+ query_context = cls._sdk_owned_query_context(operation)
1644
+ if query_context:
1645
+ request_payload["params"] = query_context
1646
+ response = make_request(
1647
+ s=cls.build_session(),
1648
+ loaders=cls.LOADERS,
1649
+ r_type="POST",
1650
+ url=(f"{cls.get_object_url().rstrip('/')}/{uid}/delete-with-cascade/"),
1651
+ payload=request_payload,
1652
+ time_out=timeout,
1653
+ )
1654
+ if response.status_code != 200:
1655
+ raise_for_response(response, payload=request_payload)
1656
+ return MetaTableCascadeDeleteResponse.model_validate(response.json())
1657
+
1658
+ def delete_with_cascade(
1659
+ self,
1660
+ *,
1661
+ confirm_cascade_delete: bool | None = None,
1662
+ delete_referencing_meta_tables: bool = True,
1663
+ delete_referencing_dynamic_tables: bool = True,
1664
+ override_schema_management_protection: bool = False,
1665
+ timeout: int | float | tuple[float, float] | None = None,
1666
+ ) -> MetaTableCascadeDeleteResponse:
1667
+ return type(self).delete_with_cascade_by_uid(
1668
+ self._public_uid(),
1669
+ confirm_cascade_delete=confirm_cascade_delete,
1670
+ delete_referencing_meta_tables=delete_referencing_meta_tables,
1671
+ delete_referencing_dynamic_tables=delete_referencing_dynamic_tables,
1672
+ override_schema_management_protection=override_schema_management_protection,
1673
+ timeout=timeout,
1674
+ )
1675
+
1561
1676
  @classmethod
1562
1677
  def register(
1563
1678
  cls,
@@ -4967,6 +5082,10 @@ __all__ = [
4967
5082
  "ManagedMetaTableCollectionCreateRow",
4968
5083
  "ManagedTimeIndexMetaTableCollectionCreateRow",
4969
5084
  "MetaTable",
5085
+ "MetaTableCascadeDeleteRequest",
5086
+ "MetaTableCascadeDeleteResponse",
5087
+ "MetaTableCascadeDeletedDynamicTable",
5088
+ "MetaTableCascadeDeletedMetaTable",
4970
5089
  "MetaTableMigrationConnection",
4971
5090
  "MetaTableMigrationConnectionRequest",
4972
5091
  "MetaTableColumnContract",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 6.0.51
3
+ Version: 6.0.53
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "6.0.51"
7
+ version = "6.0.53"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -109,3 +109,8 @@ def test_backend_environment_response_contract_is_declared_by_strict_sdk_model(
109
109
  def test_owner_observability_projection_is_declared_by_strict_sdk_model(model):
110
110
  assert model.model_config.get("extra") == "forbid"
111
111
  assert "observability" in model.model_fields
112
+
113
+
114
+ def test_agent_session_runtime_capabilities_projection_is_declared_and_typed():
115
+ assert agent_models.AgentSession.model_config.get("extra") == "forbid"
116
+ assert agent_models.AgentSession.model_fields["runtime_capabilities"].annotation == dict[str, str]
@@ -1832,6 +1832,18 @@ def test_agent_runtime_models_deserialize_backend_uid_payloads():
1832
1832
  "owner_user_uid": user_uid,
1833
1833
  "is_locked": False,
1834
1834
  },
1835
+ "observability": {
1836
+ "application_logs_url": f"/api/v1/agent-sessions/{session_uid}/logs/",
1837
+ "resource_usage_url": None,
1838
+ "deployment_runs_url": None,
1839
+ "sessions_url": None,
1840
+ },
1841
+ "runtime_capabilities": {
1842
+ "tau_runtime_bootstrap": "v1",
1843
+ "tau_resume_snapshot": "v1",
1844
+ "tau_activity_sequence": "v1",
1845
+ "tau_turn_commit": "v1",
1846
+ },
1835
1847
  }
1836
1848
  )
1837
1849
  assert session.uid == session_uid
@@ -1842,6 +1854,13 @@ def test_agent_runtime_models_deserialize_backend_uid_payloads():
1842
1854
  assert session.harness_version == "0.3.1"
1843
1855
  assert session.is_archived is False
1844
1856
  assert session.active_model == "gpt-5.4"
1857
+ assert session.observability.application_logs_url.endswith(f"/{session_uid}/logs/")
1858
+ assert session.runtime_capabilities == {
1859
+ "tau_runtime_bootstrap": "v1",
1860
+ "tau_resume_snapshot": "v1",
1861
+ "tau_activity_sequence": "v1",
1862
+ "tau_turn_commit": "v1",
1863
+ }
1845
1864
 
1846
1865
  service = agent_models_mod.CodingAgentService.model_validate(
1847
1866
  {
@@ -2089,6 +2108,70 @@ def test_agent_session_filter_supports_archive_history_query(monkeypatch):
2089
2108
  }
2090
2109
 
2091
2110
 
2111
+ def test_agent_session_list_and_detail_parse_runtime_capabilities(monkeypatch):
2112
+ session_uid = "3f1cc452-43ec-49cb-b2ba-87dbac164d29"
2113
+ agent_uid = "e0e75693-4110-464c-93e0-82c7fd9c9a23"
2114
+ session_payload = {
2115
+ "uid": session_uid,
2116
+ "agent_uid": agent_uid,
2117
+ "agent_name": "Research Copilot",
2118
+ "agent_type": "custom",
2119
+ "harness": "tau",
2120
+ "harness_protocol": "tau-session-v1",
2121
+ "harness_version": "0.3.1",
2122
+ "name": "Reusable session",
2123
+ "status": "running",
2124
+ "llm_provider": "openai",
2125
+ "llm_model": "gpt-5.4",
2126
+ "llm_thinking": "medium",
2127
+ "observability": {
2128
+ "application_logs_url": f"/api/v1/agent-sessions/{session_uid}/logs/",
2129
+ "resource_usage_url": None,
2130
+ "deployment_runs_url": None,
2131
+ "sessions_url": None,
2132
+ },
2133
+ "runtime_capabilities": {
2134
+ "tau_runtime_bootstrap": "v1",
2135
+ "tau_resume_snapshot": "v1",
2136
+ "tau_activity_sequence": "v1",
2137
+ "tau_turn_commit": "v1",
2138
+ },
2139
+ }
2140
+ responses = iter(
2141
+ [
2142
+ {"count": 1, "next": None, "previous": None, "results": [dict(session_payload)]},
2143
+ dict(session_payload),
2144
+ ]
2145
+ )
2146
+
2147
+ class FakeResponse:
2148
+ status_code = 200
2149
+ content = b'{"ok": true}'
2150
+
2151
+ def __init__(self, payload):
2152
+ self._payload = payload
2153
+
2154
+ def json(self):
2155
+ return self._payload
2156
+
2157
+ def _fake_make_request(*, s, loaders, r_type, url, payload, time_out=None):
2158
+ return FakeResponse(next(responses))
2159
+
2160
+ monkeypatch.setattr(base_mod, "make_request", _fake_make_request)
2161
+
2162
+ listed = agent_models_mod.AgentSession.filter(agent_uid=agent_uid)
2163
+ detailed = agent_models_mod.AgentSession.get(pk=session_uid)
2164
+
2165
+ assert listed[0].runtime_capabilities["tau_turn_commit"] == "v1"
2166
+ assert detailed.runtime_capabilities == listed[0].runtime_capabilities
2167
+ assert detailed.observability.application_logs_url.endswith(f"/{session_uid}/logs/")
2168
+
2169
+ with pytest.raises(ValidationError, match="unexpected_projection"):
2170
+ agent_models_mod.AgentSession.model_validate(
2171
+ {**session_payload, "unexpected_projection": "still forbidden"}
2172
+ )
2173
+
2174
+
2092
2175
  def test_agent_session_get_insights_parses_pi_response(monkeypatch):
2093
2176
  session_uid = "3f1cc452-43ec-49cb-b2ba-87dbac164d29"
2094
2177
  captured = {}
@@ -3990,6 +4073,80 @@ def test_agent_get_or_create_session_posts_new_contract(monkeypatch):
3990
4073
  }
3991
4074
 
3992
4075
 
4076
+ def test_agent_get_or_create_session_parses_reused_handle_capabilities(monkeypatch):
4077
+ agent_uid = "e0e75693-4110-464c-93e0-82c7fd9c9a23"
4078
+ session_uid = "3f1cc452-43ec-49cb-b2ba-87dbac164d29"
4079
+ handle_unique_id = "tutorial-evaluation-development"
4080
+ agent = agent_models_mod.Agent(
4081
+ uid=agent_uid,
4082
+ name="Research Copilot",
4083
+ agent_type="custom",
4084
+ description="Research assistant.",
4085
+ agent_card=None,
4086
+ llm_provider="openai",
4087
+ llm_model="gpt-5.4",
4088
+ llm_thinking="medium",
4089
+ repository_branch=None,
4090
+ organization_environment_uid=None,
4091
+ organization_environment_name=None,
4092
+ )
4093
+ captured = {}
4094
+
4095
+ class FakeResponse:
4096
+ status_code = 200
4097
+ content = b'{"ok": true}'
4098
+
4099
+ @staticmethod
4100
+ def json():
4101
+ return {
4102
+ "uid": session_uid,
4103
+ "agent_uid": agent_uid,
4104
+ "agent_name": "Research Copilot",
4105
+ "agent_type": "custom",
4106
+ "harness": "tau",
4107
+ "harness_protocol": "tau-session-v1",
4108
+ "harness_version": "0.3.1",
4109
+ "name": "Tutorial evaluation",
4110
+ "status": "running",
4111
+ "llm_provider": "openai",
4112
+ "llm_model": "gpt-5.4",
4113
+ "llm_thinking": "medium",
4114
+ "bound_handle": {
4115
+ "uid": "44444444-4444-4444-8444-444444444444",
4116
+ "handle_unique_id": handle_unique_id,
4117
+ "owner_user_uid": "fdf409f7-d16f-4f71-986b-9057db6c7eca",
4118
+ "is_locked": False,
4119
+ },
4120
+ "observability": {
4121
+ "application_logs_url": f"/api/v1/agent-sessions/{session_uid}/logs/",
4122
+ "resource_usage_url": None,
4123
+ "deployment_runs_url": None,
4124
+ "sessions_url": None,
4125
+ },
4126
+ "runtime_capabilities": {
4127
+ "tau_runtime_bootstrap": "v1",
4128
+ "tau_resume_snapshot": "v1",
4129
+ "tau_activity_sequence": "v1",
4130
+ "tau_turn_commit": "v1",
4131
+ },
4132
+ }
4133
+
4134
+ def _fake_make_request(*, s, loaders, r_type, url, payload, time_out=None):
4135
+ captured.update({"r_type": r_type, "url": url, "payload": payload, "timeout": time_out})
4136
+ return FakeResponse()
4137
+
4138
+ monkeypatch.setattr(agent_models_mod, "make_request", _fake_make_request)
4139
+
4140
+ session = agent.get_or_create_session(handle_unique_id=handle_unique_id, timeout=13)
4141
+
4142
+ assert session.uid == session_uid
4143
+ assert session.bound_handle["handle_unique_id"] == handle_unique_id
4144
+ assert session.runtime_capabilities["tau_runtime_bootstrap"] == "v1"
4145
+ assert session.observability.application_logs_url.endswith(f"/{session_uid}/logs/")
4146
+ assert captured["payload"] == {"json": {"handle_unique_id": handle_unique_id}}
4147
+ assert captured["timeout"] == 13
4148
+
4149
+
3993
4150
  def test_agent_get_or_create_session_by_uid_sends_only_session_uid(monkeypatch):
3994
4151
  captured = {}
3995
4152
  agent_uid = "e0e75693-4110-464c-93e0-82c7fd9c9a23"
@@ -8,6 +8,7 @@ from pydantic import ValidationError
8
8
 
9
9
  import mainsequence.client.metatables as meta_table_models
10
10
  import mainsequence.project_context as project_context
11
+ from mainsequence.client.exceptions import ConflictError, PermissionDeniedError
11
12
  from mainsequence.meta_tables.compiled_sql.v1 import build_operation, compile_sqlalchemy_statement
12
13
 
13
14
  ENVIRONMENT_UID = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
@@ -88,6 +89,162 @@ def _project_context():
88
89
  )
89
90
 
90
91
 
92
+ def _cascade_delete_response():
93
+ return {
94
+ "ok": True,
95
+ "action": "delete_with_cascade",
96
+ "root_meta_table_uid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
97
+ "deleted_meta_tables": [
98
+ {
99
+ "meta_table_uid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
100
+ "physical_schema": "public",
101
+ "physical_table_name": "example_assets__asset",
102
+ "management_mode": "platform_managed",
103
+ "physical_table_drop_scheduled": True,
104
+ "physical_table_drop_cascade": True,
105
+ "schema_management_protection_overridden": True,
106
+ }
107
+ ],
108
+ "deleted_dynamic_tables": [
109
+ {
110
+ "type": "meta_table",
111
+ "table_kind": "time_indexed",
112
+ "time_indexed": True,
113
+ "uid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
114
+ "identifier": "AssetPrices",
115
+ "namespace": "example.assets",
116
+ "physical_schema": "public",
117
+ "physical_table_name": "example_assets__asset_prices",
118
+ }
119
+ ],
120
+ "deleted_meta_table_count": 1,
121
+ "deleted_dynamic_table_count": 1,
122
+ "blocking_edges": [
123
+ {
124
+ "relationship_type": "meta_table_to_meta_table",
125
+ "source_table_kind": "time_indexed",
126
+ }
127
+ ],
128
+ }
129
+
130
+
131
+ def test_meta_table_confirmed_cascade_delete_posts_typed_contract(monkeypatch):
132
+ captured = {}
133
+
134
+ def fake_make_request(**kwargs):
135
+ captured.update(kwargs)
136
+ return _Response(_cascade_delete_response())
137
+
138
+ monkeypatch.setattr(meta_table_models, "make_request", fake_make_request)
139
+ monkeypatch.setattr(
140
+ meta_table_models.MetaTable,
141
+ "build_session",
142
+ classmethod(lambda cls: SimpleNamespace(headers={})),
143
+ )
144
+ meta_table = meta_table_models.MetaTable(
145
+ **_meta_table_response(
146
+ schema_management_mode="alembic_managed",
147
+ migration_provider_key="example:assets",
148
+ )
149
+ )
150
+
151
+ result = meta_table.delete_with_cascade(
152
+ confirm_cascade_delete=True,
153
+ delete_referencing_meta_tables=False,
154
+ delete_referencing_dynamic_tables=True,
155
+ override_schema_management_protection=True,
156
+ timeout=19,
157
+ )
158
+
159
+ assert isinstance(result, meta_table_models.MetaTableCascadeDeleteResponse)
160
+ assert result.root_meta_table_uid == meta_table.uid
161
+ assert result.deleted_meta_table_count == 1
162
+ assert result.deleted_dynamic_tables[0].table_kind == "time_indexed"
163
+ assert captured["r_type"] == "POST"
164
+ assert captured["url"].endswith(f"/meta-tables/{meta_table.uid}/delete-with-cascade/")
165
+ assert captured["payload"] == {
166
+ "json": {
167
+ "confirm_cascade_delete": True,
168
+ "delete_referencing_meta_tables": False,
169
+ "delete_referencing_dynamic_tables": True,
170
+ "override_schema_management_protection": True,
171
+ },
172
+ "params": {"organization_environment_uid": ENVIRONMENT_UID},
173
+ }
174
+ assert captured["time_out"] == 19
175
+
176
+
177
+ @pytest.mark.parametrize("confirmation", [None, False, 1, "true"])
178
+ def test_meta_table_cascade_delete_requires_explicit_true_confirmation(
179
+ monkeypatch,
180
+ confirmation,
181
+ ):
182
+ monkeypatch.setattr(
183
+ meta_table_models,
184
+ "make_request",
185
+ lambda **kwargs: pytest.fail("missing confirmation must fail before HTTP"),
186
+ )
187
+
188
+ with pytest.raises(ValidationError, match="confirm_cascade_delete"):
189
+ meta_table_models.MetaTable.delete_with_cascade_by_uid(
190
+ "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
191
+ confirm_cascade_delete=confirmation,
192
+ )
193
+
194
+
195
+ @pytest.mark.parametrize(
196
+ ("status_code", "error_type", "detail"),
197
+ [
198
+ (
199
+ 403,
200
+ PermissionDeniedError,
201
+ "Only organization admins can perform this destructive MetaTable action.",
202
+ ),
203
+ (
204
+ 409,
205
+ ConflictError,
206
+ "Alembic-managed MetaTables require schema-management override.",
207
+ ),
208
+ ],
209
+ )
210
+ def test_meta_table_cascade_delete_preserves_typed_backend_errors(
211
+ monkeypatch,
212
+ status_code,
213
+ error_type,
214
+ detail,
215
+ ):
216
+ captured = {}
217
+
218
+ def fake_make_request(**kwargs):
219
+ captured.update(kwargs)
220
+ return _Response(
221
+ {
222
+ "ok": False,
223
+ "code": "permission_denied"
224
+ if status_code == 403
225
+ else "alembic_managed_delete_blocked",
226
+ "detail": detail,
227
+ },
228
+ status_code=status_code,
229
+ )
230
+
231
+ monkeypatch.setattr(meta_table_models, "make_request", fake_make_request)
232
+
233
+ with pytest.raises(error_type, match=detail) as error:
234
+ meta_table_models.MetaTable.delete_with_cascade_by_uid(
235
+ "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
236
+ confirm_cascade_delete=True,
237
+ override_schema_management_protection=True,
238
+ )
239
+
240
+ assert error.value.status_code == status_code
241
+ assert error.value.payload["code"] in {
242
+ "permission_denied",
243
+ "alembic_managed_delete_blocked",
244
+ }
245
+ assert captured["payload"]["params"] == {"organization_environment_uid": ENVIRONMENT_UID}
246
+
247
+
91
248
  @pytest.mark.parametrize(
92
249
  "legacy_field",
93
250
  ["organization_environment_uid", "project_uid", "repository_branch"],
File without changes
File without changes
File without changes