mainsequence 4.1.5__tar.gz → 4.1.7__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.5 → mainsequence-4.1.7}/PKG-INFO +1 -1
  2. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +21 -11
  3. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +19 -4
  4. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/api.py +4 -4
  5. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/cli.py +11 -10
  6. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/models_metatables.py +61 -2
  7. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/logconf.py +1 -3
  8. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/__init__.py +0 -4
  9. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/data_nodes/build_operations.py +1 -1
  10. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/data_nodes/data_nodes.py +2 -2
  11. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/data_nodes/persist_managers.py +2 -2
  12. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/sqlalchemy_contracts.py +263 -66
  13. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence.egg-info/PKG-INFO +1 -1
  14. {mainsequence-4.1.5 → mainsequence-4.1.7}/pyproject.toml +1 -1
  15. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_build_operations_hashing.py +3 -3
  16. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_cli.py +29 -17
  17. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_logconf.py +1 -16
  18. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_meta_tables_client_models.py +46 -0
  19. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_meta_tables_sqlalchemy_contracts.py +271 -26
  20. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_run_configuration.py +3 -3
  21. {mainsequence-4.1.5 → mainsequence-4.1.7}/LICENSE +0 -0
  22. {mainsequence-4.1.5 → mainsequence-4.1.7}/README.md +0 -0
  23. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/AGENTS.md +0 -0
  24. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
  25. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  26. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
  27. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  28. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
  29. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
  30. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  31. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  32. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
  33. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  34. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  35. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  36. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
  37. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  38. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  39. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  40. {mainsequence-4.1.5 → mainsequence-4.1.7}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
  41. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/__init__.py +0 -0
  42. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/__main__.py +0 -0
  43. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/bootstrap.py +0 -0
  44. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/__init__.py +0 -0
  45. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/browser_auth.py +0 -0
  46. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/config.py +0 -0
  47. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/docker_utils.py +0 -0
  48. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/doctor.py +0 -0
  49. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/local_ops.py +0 -0
  50. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/model_filters.py +0 -0
  51. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/project_status.py +0 -0
  52. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/pydantic_cli.py +0 -0
  53. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/sdk_utils.py +0 -0
  54. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/ssh_utils.py +0 -0
  55. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/cli/ui.py +0 -0
  56. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/__init__.py +0 -0
  57. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/agent_runtime_models.py +0 -0
  58. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/base.py +0 -0
  59. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/client.py +0 -0
  60. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/command_center/__init__.py +0 -0
  61. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/command_center/app_component.py +0 -0
  62. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/command_center/connections.py +0 -0
  63. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/command_center/data_models.py +0 -0
  64. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/command_center/workspace.py +0 -0
  65. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  66. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/compute_validation.py +0 -0
  67. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  68. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  69. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  70. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  71. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/dtype_codec.py +0 -0
  72. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/exceptions.py +0 -0
  73. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/fastapi/__init__.py +0 -0
  74. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/fastapi/auth.py +0 -0
  75. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/models_foundry.py +0 -0
  76. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/models_helpers.py +0 -0
  77. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/models_user.py +0 -0
  78. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/client/utils.py +0 -0
  79. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/defaults.py +0 -0
  80. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/instrumentation/__init__.py +0 -0
  81. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/instrumentation/utils.py +0 -0
  82. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/__main__.py +0 -0
  83. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  84. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  85. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  86. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  87. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  88. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  89. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  90. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/future_registry.py +0 -0
  91. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/hashing.py +0 -0
  92. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  93. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence/runtime_flags.py +0 -0
  94. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence.egg-info/SOURCES.txt +0 -0
  95. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence.egg-info/dependency_links.txt +0 -0
  96. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence.egg-info/entry_points.txt +0 -0
  97. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence.egg-info/requires.txt +0 -0
  98. {mainsequence-4.1.5 → mainsequence-4.1.7}/mainsequence.egg-info/top_level.txt +0 -0
  99. {mainsequence-4.1.5 → mainsequence-4.1.7}/setup.cfg +0 -0
  100. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_auth_precedence.py +0 -0
  101. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_cli_browser_auth.py +0 -0
  102. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_client.py +0 -0
  103. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_command_center_app_component_models.py +0 -0
  104. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_command_center_data_models.py +0 -0
  105. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_command_center_models.py +0 -0
  106. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  107. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_data_node_storage_dimension_queries.py +0 -0
  108. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_data_node_update_flow.py +0 -0
  109. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_dependency_extras.py +0 -0
  110. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_duckdb_interface_dimensions.py +0 -0
  111. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_filter_normalization.py +0 -0
  112. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_models_user_request_bound_auth.py +0 -0
  113. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_pod_project_resolution.py +0 -0
  114. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_project_batch_jobs_from_file.py +0 -0
  115. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_secret_client_model.py +0 -0
  116. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_source_table_configuration.py +0 -0
  117. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_sqlite_interface_dimensions.py +0 -0
  118. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_update_runner_uid_runtime.py +0 -0
  119. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_update_statistics.py +0 -0
  120. {mainsequence-4.1.5 → mainsequence-4.1.7}/tests/test_update_uid_guards.py +0 -0
  121. {mainsequence-4.1.5 → mainsequence-4.1.7}/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.5
3
+ Version: 4.1.7
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
@@ -15,7 +15,7 @@ is defined by a registered `PlatformTimeIndexMetaData` SQLAlchemy model.
15
15
  Canonical workflow:
16
16
 
17
17
  1. Define a `PlatformTimeIndexMetaData` storage class.
18
- 2. Register or bind that storage class before constructing the DataNode.
18
+ 2. Register that storage class before constructing the DataNode.
19
19
  3. Construct the DataNode with `config=...` and `storage_table=StorageClass`.
20
20
  4. Return a DataFrame from `update()` that matches the storage class contract.
21
21
 
@@ -91,7 +91,7 @@ For every non-trivial DataNode task, make these decisions explicitly:
91
91
 
92
92
  1. Is this a new dataset or the same dataset?
93
93
  2. Is this change storage-contract work or update-process work?
94
- 3. Is the storage class already registered or should the MetaTable skill handle it?
94
+ 3. Is the storage class registered through its MetaTable path, or should the MetaTable skill handle it?
95
95
  4. Is the node single-index or MultiIndex?
96
96
  5. Does the first validation run happen under an explicit `hash_namespace(...)`?
97
97
 
@@ -108,6 +108,10 @@ 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
+
111
115
  Do not put those concerns in `DataNodeConfiguration`.
112
116
 
113
117
  Minimal pattern:
@@ -128,6 +132,10 @@ class Base(DeclarativeBase):
128
132
  class PricesTable(PlatformTimeIndexMetaData, Base):
129
133
  __metatable_namespace__ = "<domain_namespace>"
130
134
  __metatable_identifier__ = "<table_identifier>"
135
+ __metatable_description__ = (
136
+ "Daily close prices keyed by asset unique identifier for portfolio and "
137
+ "risk analytics."
138
+ )
131
139
  __time_index_name__ = "time_index"
132
140
  __index_names__ = ["time_index", "unique_identifier"]
133
141
 
@@ -139,17 +147,17 @@ class PricesTable(PlatformTimeIndexMetaData, Base):
139
147
  close: Mapped[float] = mapped_column(Float, nullable=False)
140
148
  ```
141
149
 
142
- Register or bind storage before constructing the DataNode:
150
+ Register storage before constructing the DataNode:
143
151
 
144
152
  ```python
145
153
  PricesTable.register(data_source_uid=data_source_uid)
146
154
  ```
147
155
 
148
- or:
149
-
150
- ```python
151
- PricesTable.bind_meta_table(existing_meta_table)
152
- ```
156
+ `PlatformTimeIndexMetaData.register(...)` is the storage lifecycle path. Treat it
157
+ as an idempotent get-or-create operation: the platform returns the registered
158
+ metadata and UID, and the SDK records that metadata on the class. Do not manually
159
+ attach an existing UID, reconstruct a generic `MetaTable`, or use manual bind
160
+ helpers as an authoring step.
153
161
 
154
162
  ### 2. Keep DataNode As Update Logic
155
163
 
@@ -167,7 +175,7 @@ dependency, put that dependency storage reference in the config as
167
175
  `type[PlatformTimeIndexMetaData]`. Do not add an extra constructor argument for
168
176
  dependency storage tables. Config values of this type are hashed by the bound
169
177
  `TimeIndexMetaData.uid` from `StorageClass.__time_index_metadata__`, so the class
170
- must be registered or bound before DataNode construction.
178
+ must be registered before DataNode construction.
171
179
 
172
180
  Do not accept `test_node`. It has been removed. Use explicit
173
181
  `hash_namespace(...)` or `hash_namespace="..."`.
@@ -319,6 +327,7 @@ Do not put schema or published table metadata on the DataNode configuration.
319
327
  When reviewing an existing DataNode, look for:
320
328
 
321
329
  - output storage contract hidden in `DataNodeConfiguration`
330
+ - missing `__metatable_description__` on the storage table
322
331
  - dependency storage table passed as an ad hoc constructor argument
323
332
  - schema or published table metadata hidden in DataNode configuration
324
333
  - `update_only`, `runtime_only`, or `ignore_from_storage_hash`
@@ -338,9 +347,10 @@ When reviewing an existing DataNode, look for:
338
347
  Do not claim success until you have checked:
339
348
 
340
349
  - the relevant docs were read first
341
- - storage is a registered or bound `PlatformTimeIndexMetaData` class
350
+ - storage is a registered `PlatformTimeIndexMetaData` class
351
+ - storage has an intention-rich `__metatable_description__`
342
352
  - the DataNode constructor requires `storage_table`
343
- - dependency storage-table references live in config and are registered or bound
353
+ - dependency storage-table references live in config and are registered
344
354
  - config fields are updater-scoped by default
345
355
  - no removed hash metadata markers remain
346
356
  - no `test_node` usage remains
@@ -99,15 +99,28 @@ 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
+
102
106
  Register through the class API:
103
107
 
104
108
  ```python
105
- account_meta_table = Account.register(
106
- description="Example account table.",
107
- labels=["sdk-example"],
108
- )
109
+ class Account(PlatformManagedMetaTable, Base):
110
+ __metatable_namespace__ = "sdk-examples"
111
+ __metatable_identifier__ = "Account"
112
+ __metatable_description__ = (
113
+ "Customer account master records used to scope balances, holdings, and "
114
+ "account-level limits."
115
+ )
116
+
117
+
118
+ account_meta_table = Account.register(labels=["sdk-example"])
109
119
  ```
110
120
 
121
+ Pass `description=...` only when the call intentionally overrides the class
122
+ default.
123
+
111
124
  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
125
 
113
126
  Only call `build_registration_request()` when the task explicitly needs to inspect or validate the payload before registration.
@@ -172,6 +185,7 @@ Do not hardcode platform-managed physical names manually.
172
185
  When reviewing an existing MetaTable workflow, look for:
173
186
 
174
187
  - missing namespace or identifier
188
+ - missing `__metatable_description__`, or a description that only repeats column names instead of table intention
175
189
  - backend-managed models that do not inherit `PlatformManagedMetaTable`
176
190
  - backend-managed examples that use namespace environment variables instead of a plain `sdk-examples` namespace
177
191
  - duplicate schema sources outside SQLAlchemy table metadata
@@ -187,6 +201,7 @@ When reviewing an existing MetaTable workflow, look for:
187
201
  Do not claim success until you have checked:
188
202
 
189
203
  - the table contract matches the intended row contract
204
+ - the table has an intention-rich `__metatable_description__`
190
205
  - indexes are intentional
191
206
  - foreign keys resolve to the correct dependency targets
192
207
  - management mode is correct
@@ -5136,7 +5136,7 @@ def list_project_job_runs(
5136
5136
 
5137
5137
 
5138
5138
  def get_project_job_run_logs(
5139
- job_run_id: int | str,
5139
+ job_run_uid: str,
5140
5140
  *,
5141
5141
  timeout: int | None = None,
5142
5142
  ) -> dict[str, Any]:
@@ -5191,20 +5191,20 @@ def get_project_job_run_logs(
5191
5191
  BaseObjectOrm.ROOT_URL = root_url
5192
5192
  ClientJobRun.ROOT_URL = root_url
5193
5193
 
5194
- job_run = ClientJobRun.get(pk=int(job_run_id), timeout=timeout)
5194
+ job_run = ClientJobRun.get(pk=job_run_uid, timeout=timeout)
5195
5195
  payload = job_run.get_logs(timeout=timeout)
5196
5196
  if isinstance(payload, dict):
5197
5197
  return payload
5198
5198
  if hasattr(payload, "model_dump"):
5199
5199
  return payload.model_dump()
5200
- return {"job_run_id": int(job_run_id), "rows": []}
5200
+ return {"job_run_uid": job_run_uid, "rows": []}
5201
5201
 
5202
5202
  except Exception as e:
5203
5203
  err_name = type(e).__name__
5204
5204
  if err_name in {"AuthenticationError", "PermissionDeniedError"}:
5205
5205
  raise NotLoggedIn(str(e) or "Not logged in.") from e
5206
5206
  if err_name == "NotFoundError":
5207
- raise ApiError(f"Job run not found: {job_run_id}") from e
5207
+ raise ApiError(f"Job run not found: {job_run_uid}") from e
5208
5208
  raise ApiError(f"Project job run logs fetch failed: {e}") from e
5209
5209
  finally:
5210
5210
  if client_utils is not None:
@@ -10196,7 +10196,7 @@ def project_jobs_run_cmd(
10196
10196
  if payload:
10197
10197
  preferred_keys = [
10198
10198
  ("Job ID", str(payload.get("job") or payload.get("job_id") or job_id)),
10199
- ("Job Run ID", str(payload.get("id") or payload.get("job_run_id") or "-")),
10199
+ ("Job Run UID", str(payload.get("uid") or payload.get("job_run_uid") or "-")),
10200
10200
  ("Name", str(payload.get("name") or payload.get("job_name") or "-")),
10201
10201
  ("Unique Identifier", str(payload.get("unique_identifier") or "-")),
10202
10202
  ("Status", str(payload.get("status") or "-")),
@@ -10208,7 +10208,8 @@ def project_jobs_run_cmd(
10208
10208
  "job",
10209
10209
  "job_id",
10210
10210
  "id",
10211
- "job_run_id",
10211
+ "uid",
10212
+ "job_run_uid",
10212
10213
  "name",
10213
10214
  "job_name",
10214
10215
  "unique_identifier",
@@ -10254,8 +10255,8 @@ def project_job_runs_list_cmd(
10254
10255
 
10255
10256
  @project_job_runs_group.command("logs")
10256
10257
  def project_job_runs_logs_cmd(
10257
- job_run_id: int = pydantic_argument(
10258
- JOB_RUN_MODEL_REF, "id", ..., help="Job run ID whose logs will be shown."
10258
+ job_run_uid: str = pydantic_argument(
10259
+ JOB_RUN_MODEL_REF, "uid", ..., help="Job run UID whose logs will be shown."
10259
10260
  ),
10260
10261
  poll_interval: int = typer.Option(
10261
10262
  30,
@@ -10279,10 +10280,10 @@ def project_job_runs_logs_cmd(
10279
10280
  Examples
10280
10281
  --------
10281
10282
  ```bash
10282
- mainsequence project jobs runs logs 501
10283
- mainsequence project jobs runs logs 501 --poll-interval 10
10284
- mainsequence project jobs runs logs 501 --max-wait-seconds 900
10285
- mainsequence project jobs runs logs 501 --poll-interval 0
10283
+ mainsequence project jobs runs logs 4c1d77c8-8a42-42b8-a9c1-06be9a336e5d
10284
+ mainsequence project jobs runs logs 4c1d77c8-8a42-42b8-a9c1-06be9a336e5d --poll-interval 10
10285
+ mainsequence project jobs runs logs 4c1d77c8-8a42-42b8-a9c1-06be9a336e5d --max-wait-seconds 900
10286
+ mainsequence project jobs runs logs 4c1d77c8-8a42-42b8-a9c1-06be9a336e5d --poll-interval 0
10286
10287
  ```
10287
10288
  """
10288
10289
  _require_login()
@@ -10296,7 +10297,7 @@ def project_job_runs_logs_cmd(
10296
10297
 
10297
10298
  while True:
10298
10299
  try:
10299
- payload = get_project_job_run_logs(job_run_id=job_run_id, timeout=timeout)
10300
+ payload = get_project_job_run_logs(job_run_uid=job_run_uid, timeout=timeout)
10300
10301
  except ApiError as e:
10301
10302
  error(f"Project job run logs fetch failed: {e}")
10302
10303
  raise typer.Exit(1) from e
@@ -10313,7 +10314,7 @@ def project_job_runs_logs_cmd(
10313
10314
  print_kv(
10314
10315
  "Job Run Logs",
10315
10316
  [
10316
- ("Job Run ID", str(payload.get("job_run_id") or job_run_id)),
10317
+ ("Job Run UID", str(payload.get("job_run_uid") or payload.get("uid") or job_run_uid)),
10317
10318
  ("Status", status_value),
10318
10319
  ],
10319
10320
  )
@@ -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")
@@ -236,13 +236,11 @@ def _build_backend_bindings(
236
236
  if project_id is not None:
237
237
  bindings["project_id"] = project_id
238
238
  bindings["data_source_id"] = startup_state.get("data_source_id")
239
- bindings["job_run_id"] = startup_state.get("job_run_id")
240
239
  bindings["job_run_uid"] = startup_state.get("job_run_uid")
241
240
  bindings["command_id"] = startup_state.get("command_id")
242
241
  else:
243
- # your existing behavior: bind job_run_id to user_id in local-ish mode
244
242
  if "user_id" in startup_state:
245
- bindings["job_run_id"] = startup_state.get("user_id")
243
+ bindings["user_id"] = startup_state.get("user_id")
246
244
  else:
247
245
  bindings["local_mode"] = "no_app"
248
246
 
@@ -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",
@@ -96,7 +96,7 @@ def _(value: type[Any]) -> Any:
96
96
  uid = getattr(time_index_metadata, "uid", None)
97
97
  if uid in (None, ""):
98
98
  raise ValueError(
99
- "PlatformTimeIndexMetaData config values must be registered or bound "
99
+ "PlatformTimeIndexMetaData config values must be registered "
100
100
  "before they can be hashed."
101
101
  )
102
102
  return {
@@ -647,7 +647,7 @@ class DataNode(DataAccessMixin, ABC):
647
647
  )
648
648
  if value.get_time_index_metadata() is None:
649
649
  raise ValueError(
650
- "DataNode storage_table must be registered or bound before construction."
650
+ "DataNode storage_table must be registered before construction."
651
651
  )
652
652
  if value.get_meta_table_uid() in (None, ""):
653
653
  raise ValueError("DataNode storage_table must provide a MetaTable UID.")
@@ -659,7 +659,7 @@ class DataNode(DataAccessMixin, ABC):
659
659
  def storage_metadata(self) -> Any:
660
660
  storage_metadata = self.storage_table.get_time_index_metadata()
661
661
  if storage_metadata is None:
662
- raise ValueError("DataNode storage_table must be registered or bound before use.")
662
+ raise ValueError("DataNode storage_table must be registered before use.")
663
663
  return storage_metadata
664
664
 
665
665
  def _initialize_configuration(self, init_kwargs: dict) -> None:
@@ -130,7 +130,7 @@ class BasePersistManager:
130
130
  storage_metadata = storage_table.get_time_index_metadata()
131
131
  if storage_metadata is None:
132
132
  raise ValueError(
133
- "PersistManager storage_table must be registered or bound before "
133
+ "PersistManager storage_table must be registered before "
134
134
  "constructing the DataNode."
135
135
  )
136
136
  if storage_table.get_meta_table_uid() in (None, ""):
@@ -143,7 +143,7 @@ class BasePersistManager:
143
143
  def storage_metadata(self) -> Any:
144
144
  storage_metadata = self.storage_table.get_time_index_metadata()
145
145
  if storage_metadata is None:
146
- raise ValueError("PersistManager storage_table must be registered or bound before use.")
146
+ raise ValueError("PersistManager storage_table must be registered before use.")
147
147
  return storage_metadata
148
148
 
149
149
  @property