mainsequence 4.1.6__tar.gz → 4.1.8__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 (121) hide show
  1. {mainsequence-4.1.6 → mainsequence-4.1.8}/PKG-INFO +1 -1
  2. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +65 -14
  3. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +30 -4
  4. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/models_metatables.py +61 -2
  5. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/__init__.py +0 -4
  6. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -7
  7. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/pydantic_metadata.py +0 -18
  8. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/sqlalchemy_contracts.py +263 -66
  9. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/PKG-INFO +1 -1
  10. {mainsequence-4.1.6 → mainsequence-4.1.8}/pyproject.toml +1 -1
  11. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_build_operations_hashing.py +3 -43
  12. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_meta_tables_client_models.py +46 -0
  13. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_meta_tables_sqlalchemy_contracts.py +271 -26
  14. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_run_configuration.py +1 -1
  15. {mainsequence-4.1.6 → mainsequence-4.1.8}/LICENSE +0 -0
  16. {mainsequence-4.1.6 → mainsequence-4.1.8}/README.md +0 -0
  17. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/AGENTS.md +0 -0
  18. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
  19. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  20. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
  21. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  22. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
  23. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
  24. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  25. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  26. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
  27. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  28. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  29. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  30. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
  31. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  32. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  33. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  34. {mainsequence-4.1.6 → mainsequence-4.1.8}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
  35. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/__init__.py +0 -0
  36. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/__main__.py +0 -0
  37. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/bootstrap.py +0 -0
  38. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/__init__.py +0 -0
  39. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/api.py +0 -0
  40. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/browser_auth.py +0 -0
  41. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/cli.py +0 -0
  42. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/config.py +0 -0
  43. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/docker_utils.py +0 -0
  44. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/doctor.py +0 -0
  45. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/local_ops.py +0 -0
  46. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/model_filters.py +0 -0
  47. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/project_status.py +0 -0
  48. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/pydantic_cli.py +0 -0
  49. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/sdk_utils.py +0 -0
  50. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/ssh_utils.py +0 -0
  51. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/cli/ui.py +0 -0
  52. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/__init__.py +0 -0
  53. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/agent_runtime_models.py +0 -0
  54. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/base.py +0 -0
  55. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/client.py +0 -0
  56. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/__init__.py +0 -0
  57. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/app_component.py +0 -0
  58. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/connections.py +0 -0
  59. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/data_models.py +0 -0
  60. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/workspace.py +0 -0
  61. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  62. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/compute_validation.py +0 -0
  63. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  64. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  65. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  66. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  67. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/dtype_codec.py +0 -0
  68. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/exceptions.py +0 -0
  69. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/fastapi/__init__.py +0 -0
  70. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/fastapi/auth.py +0 -0
  71. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/models_foundry.py +0 -0
  72. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/models_helpers.py +0 -0
  73. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/models_user.py +0 -0
  74. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/client/utils.py +0 -0
  75. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/defaults.py +0 -0
  76. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/instrumentation/__init__.py +0 -0
  77. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/instrumentation/utils.py +0 -0
  78. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/logconf.py +0 -0
  79. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/__main__.py +0 -0
  80. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  81. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  82. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  83. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  84. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  85. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  86. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  87. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  88. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  89. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/future_registry.py +0 -0
  90. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/meta_tables/hashing.py +0 -0
  91. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence/runtime_flags.py +0 -0
  92. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/SOURCES.txt +0 -0
  93. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/dependency_links.txt +0 -0
  94. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/entry_points.txt +0 -0
  95. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/requires.txt +0 -0
  96. {mainsequence-4.1.6 → mainsequence-4.1.8}/mainsequence.egg-info/top_level.txt +0 -0
  97. {mainsequence-4.1.6 → mainsequence-4.1.8}/setup.cfg +0 -0
  98. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_auth_precedence.py +0 -0
  99. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_cli.py +0 -0
  100. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_cli_browser_auth.py +0 -0
  101. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_client.py +0 -0
  102. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_command_center_app_component_models.py +0 -0
  103. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_command_center_data_models.py +0 -0
  104. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_command_center_models.py +0 -0
  105. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  106. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_data_node_storage_dimension_queries.py +0 -0
  107. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_data_node_update_flow.py +0 -0
  108. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_dependency_extras.py +0 -0
  109. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_duckdb_interface_dimensions.py +0 -0
  110. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_filter_normalization.py +0 -0
  111. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_logconf.py +0 -0
  112. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_models_user_request_bound_auth.py +0 -0
  113. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_pod_project_resolution.py +0 -0
  114. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_project_batch_jobs_from_file.py +0 -0
  115. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_secret_client_model.py +0 -0
  116. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_source_table_configuration.py +0 -0
  117. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_sqlite_interface_dimensions.py +0 -0
  118. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_update_runner_uid_runtime.py +0 -0
  119. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_update_statistics.py +0 -0
  120. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_update_uid_guards.py +0 -0
  121. {mainsequence-4.1.6 → mainsequence-4.1.8}/tests/test_workspace_snapshot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 4.1.6
3
+ Version: 4.1.8
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -25,7 +25,7 @@ Canonical workflow:
25
25
  - modify an existing `DataNode` update process
26
26
  - review whether a DataNode change affects update identity or table contract
27
27
  - define or refactor `DataNodeConfiguration`
28
- - classify config fields into update identity and hash-excluded descriptive metadata
28
+ - classify values as hashed config fields or non-config class/runtime values
29
29
  - implement or review:
30
30
  - `dependencies()`
31
31
  - `update()`
@@ -108,6 +108,20 @@ The following are storage-contract decisions:
108
108
  - foreign keys
109
109
  - table description and labels
110
110
 
111
+ Every storage class must include `__metatable_description__`. The description
112
+ should explain the table's intention, row grain, and downstream use, not only
113
+ list columns or schema mechanics.
114
+
115
+ Use `__metatable_extra_hash_components__` on storage classes when distinct
116
+ DataNode storage tables could otherwise have the same storage-relevant shape.
117
+ For example, two one-index daily tables with one float column need a stable
118
+ component such as `{"storage_name": "daily_random_number"}` versus
119
+ `{"storage_name": "daily_random_addition"}`.
120
+
121
+ This is storage identity. Changing it creates a different storage table. Do not
122
+ use it for labels, descriptions, runtime options, test isolation, backend UIDs,
123
+ data-source UIDs, or updater scope.
124
+
111
125
  Do not put those concerns in `DataNodeConfiguration`.
112
126
 
113
127
  Minimal pattern:
@@ -128,6 +142,11 @@ class Base(DeclarativeBase):
128
142
  class PricesTable(PlatformTimeIndexMetaData, Base):
129
143
  __metatable_namespace__ = "<domain_namespace>"
130
144
  __metatable_identifier__ = "<table_identifier>"
145
+ __metatable_extra_hash_components__ = {"storage_name": "<stable_storage_name>"}
146
+ __metatable_description__ = (
147
+ "Daily close prices keyed by asset unique identifier for portfolio and "
148
+ "risk analytics."
149
+ )
131
150
  __time_index_name__ = "time_index"
132
151
  __index_names__ = ["time_index", "unique_identifier"]
133
152
 
@@ -175,12 +194,21 @@ Do not accept `test_node`. It has been removed. Use explicit
175
194
  Pattern:
176
195
 
177
196
  ```python
197
+ from typing import ClassVar
198
+
199
+ from pydantic import Field
200
+
178
201
  from mainsequence.meta_tables import DataNode, DataNodeConfiguration
179
202
  from mainsequence.meta_tables import PlatformTimeIndexMetaData
180
203
 
181
204
 
182
205
  class PricesConfig(DataNodeConfiguration):
183
- shard_id: str
206
+ shard_id: str = Field(
207
+ ...,
208
+ description="Stable updater partition for this price job.",
209
+ examples=["us_equities_daily"],
210
+ )
211
+ reference_dimension: ClassVar[str] = "unique_identifier"
184
212
 
185
213
 
186
214
  class PricesUpdate(DataNode):
@@ -208,23 +236,45 @@ class PricesUpdate(DataNode):
208
236
  ### 3. Configuration Is Update-Scoped By Default
209
237
 
210
238
  Every `DataNodeConfiguration` field participates in `update_hash` by default.
239
+ Declare values that change output values, dependencies, source choice, or
240
+ updater scope as normal config fields.
211
241
 
212
- Do not use:
242
+ Every config field must be declared with `Field(...)`. Include a clear
243
+ `description` and add `examples=[...]` whenever a realistic example helps. The
244
+ description must explain what the value means for the update process, not repeat
245
+ the Python type.
213
246
 
214
- - `json_schema_extra={"update_only": True}`
215
- - `json_schema_extra={"runtime_only": True}`
216
- - `json_schema_extra={"ignore_from_storage_hash": True}`
217
- - `_ARGS_IGNORE_IN_STORAGE_HASH`
247
+ Values that must not affect `update_hash` should not be Pydantic config fields.
248
+ Use `ClassVar[...]` for class-level invariants and implementation constants, or
249
+ keep runtime controls in environment/runtime configuration outside the
250
+ DataNode config.
218
251
 
219
- Those are removed. The only supported opt-out is:
252
+ If a value genuinely must remain a Pydantic field while not affecting
253
+ `update_hash`, the only supported field-level opt-out is
254
+ `json_schema_extra={"hash_excluded": True}`:
220
255
 
221
256
  ```python
222
- Field(..., json_schema_extra={"hash_excluded": True})
257
+ from pydantic import Field
258
+
259
+ from mainsequence.meta_tables import DataNodeConfiguration
260
+
261
+
262
+ class PricesConfig(DataNodeConfiguration):
263
+ shard_id: str = Field(
264
+ ...,
265
+ description="Stable updater partition for this price job.",
266
+ examples=["us_equities_daily"],
267
+ )
268
+ display_label: str | None = Field(
269
+ default=None,
270
+ description="Optional human-facing label for UI display only.",
271
+ examples=["US equities daily prices"],
272
+ json_schema_extra={"hash_excluded": True},
273
+ )
223
274
  ```
224
275
 
225
- Use `hash_excluded` only for descriptive metadata that must not affect update
226
- identity. If a field changes output values, dependencies, source choice, or
227
- updater scope, it must remain a normal config field.
276
+ Do not invent other metadata-marker exceptions. Scope, dependency, source, and
277
+ output-affecting fields must remain hashed config fields.
228
278
 
229
279
  ### 4. `hash_namespace` Is Isolation Only
230
280
 
@@ -319,13 +369,13 @@ Do not put schema or published table metadata on the DataNode configuration.
319
369
  When reviewing an existing DataNode, look for:
320
370
 
321
371
  - output storage contract hidden in `DataNodeConfiguration`
372
+ - missing `__metatable_description__` on the storage table
322
373
  - dependency storage table passed as an ad hoc constructor argument
323
374
  - schema or published table metadata hidden in DataNode configuration
324
- - `update_only`, `runtime_only`, or `ignore_from_storage_hash`
325
375
  - `test_node=True`
326
376
  - missing explicit `storage_table`
327
377
  - accidental storage registration inside the DataNode
328
- - wrong meaning/scope/hash-excluded split
378
+ - wrong split between hashed config fields and non-config class/runtime values
329
379
  - misuse of `hash_namespace`
330
380
  - non-incremental `update()` behavior
331
381
  - hidden dependency creation inside `update()`
@@ -339,6 +389,7 @@ Do not claim success until you have checked:
339
389
 
340
390
  - the relevant docs were read first
341
391
  - storage is a registered `PlatformTimeIndexMetaData` class
392
+ - storage has an intention-rich `__metatable_description__`
342
393
  - the DataNode constructor requires `storage_table`
343
394
  - dependency storage-table references live in config and are registered
344
395
  - config fields are updater-scoped by default
@@ -99,15 +99,39 @@ The mixin derives the SQLAlchemy physical table name from storage-relevant confi
99
99
 
100
100
  Schema must come from SQLAlchemy table metadata, usually `__table_args__ = {"schema": "public"}` or the tuple form ending in `{"schema": ...}`. Do not add a separate MetaTable-specific schema attribute.
101
101
 
102
+ Always declare `__metatable_description__` on the model. The description must
103
+ explain the table's business intention, row grain, and expected use, not only
104
+ the schema. Column-level descriptions stay in `mapped_column(info={...})`.
105
+
106
+ Use `__metatable_extra_hash_components__` when two backend-managed tables could
107
+ otherwise produce the same storage hash because their storage-relevant shape is
108
+ identical or intentionally generic. The value must be stable and deterministic,
109
+ usually a small mapping such as `{"storage_name": "account_holdings"}`.
110
+
111
+ This attribute is part of storage identity. Changing it defines a different
112
+ table. Do not use it for labels, descriptions, runtime options, test isolation,
113
+ backend UIDs, data-source UIDs, or updater scope. Use `hash_namespace` for test
114
+ or experiment isolation.
115
+
102
116
  Register through the class API:
103
117
 
104
118
  ```python
105
- account_meta_table = Account.register(
106
- description="Example account table.",
107
- labels=["sdk-example"],
108
- )
119
+ class Account(PlatformManagedMetaTable, Base):
120
+ __metatable_namespace__ = "sdk-examples"
121
+ __metatable_identifier__ = "Account"
122
+ __metatable_extra_hash_components__ = {"storage_name": "account"}
123
+ __metatable_description__ = (
124
+ "Customer account master records used to scope balances, holdings, and "
125
+ "account-level limits."
126
+ )
127
+
128
+
129
+ account_meta_table = Account.register(labels=["sdk-example"])
109
130
  ```
110
131
 
132
+ Pass `description=...` only when the call intentionally overrides the class
133
+ default.
134
+
111
135
  For platform-managed registration, the data source is resolved from the active Main Sequence project/session, the same way DataNode does. Do not require or thread a `data_source_uid` through normal platform-managed example code.
112
136
 
113
137
  Only call `build_registration_request()` when the task explicitly needs to inspect or validate the payload before registration.
@@ -172,6 +196,7 @@ Do not hardcode platform-managed physical names manually.
172
196
  When reviewing an existing MetaTable workflow, look for:
173
197
 
174
198
  - missing namespace or identifier
199
+ - missing `__metatable_description__`, or a description that only repeats column names instead of table intention
175
200
  - backend-managed models that do not inherit `PlatformManagedMetaTable`
176
201
  - backend-managed examples that use namespace environment variables instead of a plain `sdk-examples` namespace
177
202
  - duplicate schema sources outside SQLAlchemy table metadata
@@ -187,6 +212,7 @@ When reviewing an existing MetaTable workflow, look for:
187
212
  Do not claim success until you have checked:
188
213
 
189
214
  - the table contract matches the intended row contract
215
+ - the table has an intention-rich `__metatable_description__`
190
216
  - indexes are intentional
191
217
  - foreign keys resolve to the correct dependency targets
192
218
  - management mode is correct
@@ -220,7 +220,13 @@ class MetaTablePhysicalContract(BasePydanticModel):
220
220
  exclude=True,
221
221
  description=("Input-only schema alias. MetaTable uses the data source default schema."),
222
222
  )
223
- table_name: str = Field(..., description="Physical database table name.")
223
+ table_name: str | None = Field(
224
+ default=None,
225
+ description=(
226
+ "Physical database table name. Required for external_registered tables; "
227
+ "omitted by platform_managed client requests because the backend owns it."
228
+ ),
229
+ )
224
230
 
225
231
  model_config = ConfigDict(populate_by_name=True)
226
232
 
@@ -1090,6 +1096,55 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1090
1096
  self.introspection_snapshot = snapshot
1091
1097
  return response_json
1092
1098
 
1099
+ def get_schema_graph(
1100
+ self,
1101
+ *,
1102
+ depth: int = 1,
1103
+ include_incoming: bool = False,
1104
+ timeout: int | float | tuple[float, float] | None = None,
1105
+ ) -> dict[str, Any]:
1106
+ """
1107
+ Return the MetaTable foreign-key schema graph rooted at this table.
1108
+
1109
+ The graph is the client API for dependency analysis. Outgoing edges are
1110
+ foreign keys declared by this table. When ``include_incoming`` is true,
1111
+ the response also includes visible tables that depend on this table.
1112
+
1113
+ Args:
1114
+ depth: Relationship traversal depth. The backend clamps this to its
1115
+ supported range.
1116
+ include_incoming: Include inbound relationships where another
1117
+ visible MetaTable has a foreign key targeting this table.
1118
+ timeout: Optional request timeout in seconds or ``requests`` timeout
1119
+ tuple form.
1120
+
1121
+ Returns:
1122
+ A dictionary with ``root_uid``, ``depth``, ``include_incoming``,
1123
+ ``nodes``, and ``edges``. Inbound dependencies are edges where
1124
+ ``edge["target_uid"] == self.uid``; the dependent table UID is
1125
+ ``edge["source_uid"]``.
1126
+ """
1127
+ url = f"{type(self).get_object_url().rstrip('/')}/{self._public_uid()}/schema-graph/"
1128
+ payload = {
1129
+ "params": serialize_to_json(
1130
+ {
1131
+ "depth": depth,
1132
+ "include_incoming": include_incoming,
1133
+ }
1134
+ )
1135
+ }
1136
+ response = make_request(
1137
+ s=type(self).build_session(),
1138
+ loaders=type(self).LOADERS,
1139
+ r_type="GET",
1140
+ url=url,
1141
+ payload=payload,
1142
+ time_out=timeout,
1143
+ )
1144
+ if response.status_code != 200:
1145
+ raise_for_response(response, payload=payload)
1146
+ return response.json()
1147
+
1093
1148
  def refresh_table_search_index(
1094
1149
  self,
1095
1150
  *,
@@ -1335,7 +1390,11 @@ class TimeIndexMetaTableRegistrationRequest(BasePydanticModel):
1335
1390
  model_config = ConfigDict(extra="forbid")
1336
1391
 
1337
1392
  data_source_uid: str = Field(..., description="Public uid of the storage data source")
1338
- storage_hash: str = Field(..., max_length=63, description="Canonical physical table name")
1393
+ storage_hash: str = Field(
1394
+ ...,
1395
+ max_length=63,
1396
+ description="Canonical logical storage identity for the time-indexed MetaTable",
1397
+ )
1339
1398
  identifier: str | None = Field(None, description="Optional published storage identifier")
1340
1399
  namespace: str | None = Field(None, description="Optional published storage namespace")
1341
1400
  description: str | None = Field(None, description="Optional storage description")
@@ -38,10 +38,6 @@ _LAZY_IMPORTS = {
38
38
  ".sqlalchemy_contracts",
39
39
  "register_external_sqlalchemy_model",
40
40
  ),
41
- "register_platform_managed_sqlalchemy_model": (
42
- ".sqlalchemy_contracts",
43
- "register_platform_managed_sqlalchemy_model",
44
- ),
45
41
  "slugify_identifier": (".hashing", "slugify_identifier"),
46
42
  "table_contract_from_sqlalchemy_model": (
47
43
  ".sqlalchemy_contracts",
@@ -420,13 +420,6 @@ class DataNode(DataAccessMixin, ABC):
420
420
  """
421
421
  super().__init_subclass__(**kwargs)
422
422
 
423
- if "_ARGS_IGNORE_IN_STORAGE_HASH" in cls.__dict__:
424
- raise TypeError(
425
- f"{cls.__name__} uses removed class attribute _ARGS_IGNORE_IN_STORAGE_HASH; "
426
- "move those fields into DataNodeConfiguration. Configuration fields "
427
- "participate in update hashing by default."
428
- )
429
-
430
423
  # Get the original __init__ from the new subclass
431
424
  original_init = cls.__init__
432
425
 
@@ -20,24 +20,6 @@ def serialize_pydantic_model(
20
20
 
21
21
  for field_name, field_info in value.__class__.model_fields.items():
22
22
  extra = field_info.json_schema_extra or {}
23
- if "ignore_from_storage_hash" in extra:
24
- raise ValueError(
25
- f"{value.__class__.__name__}.{field_name} uses removed metadata "
26
- "'ignore_from_storage_hash'. All configuration fields participate in "
27
- 'update hashing by default; use json_schema_extra={"hash_excluded": True} '
28
- "only for fields that should not affect update identity."
29
- )
30
- if "update_only" in extra:
31
- raise ValueError(
32
- f"{value.__class__.__name__}.{field_name} uses removed metadata "
33
- "'update_only'. All configuration fields are update-scoped by default."
34
- )
35
- if "runtime_only" in extra:
36
- raise ValueError(
37
- f"{value.__class__.__name__}.{field_name} uses removed metadata "
38
- "'runtime_only'; use json_schema_extra={\"hash_excluded\": True} instead."
39
- )
40
-
41
23
  is_hash_excluded = extra.get("hash_excluded", False)
42
24
  if not isinstance(is_hash_excluded, bool):
43
25
  raise ValueError(