mainsequence 6.0.40__tar.gz → 6.0.41__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 (130) hide show
  1. {mainsequence-6.0.40/mainsequence.egg-info → mainsequence-6.0.41}/PKG-INFO +1 -1
  2. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/data_access/exploration/SKILL.md +3 -4
  3. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +2 -0
  4. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +4 -1
  5. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +4 -0
  6. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/cli.py +0 -65
  7. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/base.py +95 -4
  8. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/metatables/core.py +76 -16
  9. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/models_foundry.py +76 -8
  10. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/models_helpers.py +9 -0
  11. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/project_context.py +19 -0
  12. {mainsequence-6.0.40 → mainsequence-6.0.41/mainsequence.egg-info}/PKG-INFO +1 -1
  13. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence.egg-info/SOURCES.txt +1 -0
  14. {mainsequence-6.0.40 → mainsequence-6.0.41}/pyproject.toml +1 -1
  15. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_cli.py +22 -8
  16. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_cli_migrations.py +2 -1
  17. mainsequence-6.0.41/tests/test_environment_resource_context.py +168 -0
  18. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_filter_normalization.py +72 -34
  19. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_meta_tables_client_models.py +58 -0
  20. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_project_runtime_context.py +35 -0
  21. mainsequence-6.0.41/tests/test_secret_client_model.py +18 -0
  22. mainsequence-6.0.40/tests/test_secret_client_model.py +0 -11
  23. {mainsequence-6.0.40 → mainsequence-6.0.41}/LICENSE +0 -0
  24. {mainsequence-6.0.40 → mainsequence-6.0.41}/README.md +0 -0
  25. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/AGENTS.md +0 -0
  26. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  27. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  28. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  29. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  30. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  31. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +0 -0
  32. {mainsequence-6.0.40 → mainsequence-6.0.41}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
  33. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/__init__.py +0 -0
  34. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/__main__.py +0 -0
  35. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/bootstrap.py +0 -0
  36. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/__init__.py +0 -0
  37. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/api.py +0 -0
  38. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/browser_auth.py +0 -0
  39. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/config.py +0 -0
  40. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/docker_utils.py +0 -0
  41. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/doctor.py +0 -0
  42. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/local_ops.py +0 -0
  43. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/migrations.py +0 -0
  44. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/model_filters.py +0 -0
  45. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/project_status.py +0 -0
  46. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/pydantic_cli.py +0 -0
  47. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/sdk_utils.py +0 -0
  48. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/ssh_utils.py +0 -0
  49. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/cli/ui.py +0 -0
  50. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/__init__.py +0 -0
  51. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/agent_runtime_models.py +0 -0
  52. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/client.py +0 -0
  53. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/compute_validation.py +0 -0
  54. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  55. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  56. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  57. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  58. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/dtype_codec.py +0 -0
  59. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/exceptions.py +0 -0
  60. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/metatables/__init__.py +0 -0
  61. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/models_user.py +0 -0
  62. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/client/utils.py +0 -0
  63. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/defaults.py +0 -0
  64. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/instrumentation/__init__.py +0 -0
  65. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/instrumentation/utils.py +0 -0
  66. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/logconf.py +0 -0
  67. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/__init__.py +0 -0
  68. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/__main__.py +0 -0
  69. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  70. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  71. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  72. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  73. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  74. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  75. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  76. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  77. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  78. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  79. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/future_registry.py +0 -0
  80. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/hashing.py +0 -0
  81. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  82. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  83. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/env.py +0 -0
  84. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/provider.py +0 -0
  85. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/registry.py +0 -0
  86. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  87. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  88. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  89. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  90. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  91. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/schema_names.py +0 -0
  92. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  93. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/project_skills.py +0 -0
  94. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/runtime_flags.py +0 -0
  95. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence/scaffold_skills.py +0 -0
  96. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence.egg-info/dependency_links.txt +0 -0
  97. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence.egg-info/entry_points.txt +0 -0
  98. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence.egg-info/requires.txt +0 -0
  99. {mainsequence-6.0.40 → mainsequence-6.0.41}/mainsequence.egg-info/top_level.txt +0 -0
  100. {mainsequence-6.0.40 → mainsequence-6.0.41}/setup.cfg +0 -0
  101. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_auth_precedence.py +0 -0
  102. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_build_operations_hashing.py +0 -0
  103. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_canonical_api_route_contract.py +0 -0
  104. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_cli_browser_auth.py +0 -0
  105. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_client.py +0 -0
  106. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  107. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_data_node_storage_dimension_queries.py +0 -0
  108. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_data_node_update_flow.py +0 -0
  109. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_dependency_extras.py +0 -0
  110. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_duckdb_interface_dimensions.py +0 -0
  111. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_instrumentation.py +0 -0
  112. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_local_ops.py +0 -0
  113. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_logconf.py +0 -0
  114. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_meta_table_migrations.py +0 -0
  115. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  116. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_models_user_request_bound_auth.py +0 -0
  117. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_notification_uid_contract.py +0 -0
  118. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_pod_project_resolution.py +0 -0
  119. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_project_batch_jobs_from_file.py +0 -0
  120. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_project_branch_contract.py +0 -0
  121. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_project_skills.py +0 -0
  122. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_run_configuration.py +0 -0
  123. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_scaffold_skills.py +0 -0
  124. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_schema_names.py +0 -0
  125. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_source_table_configuration.py +0 -0
  126. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_sqlite_interface_dimensions.py +0 -0
  127. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_ssh_utils.py +0 -0
  128. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_update_runner_uid_runtime.py +0 -0
  129. {mainsequence-6.0.40 → mainsequence-6.0.41}/tests/test_update_statistics.py +0 -0
  130. {mainsequence-6.0.40 → mainsequence-6.0.41}/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.40
3
+ Version: 6.0.41
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -156,10 +156,9 @@ field to filter by:
156
156
 
157
157
  Collection listing is scoped to one Organization Environment. Inside a
158
158
  registered project checkout, the CLI derives that scope from the process-frozen
159
- Git-resolved ProjectBranch. Outside one, pass
160
- `--organization-project-environment-uid <ENVIRONMENT_UID>` explicitly. Direct
161
- SDK calls use the canonical
162
- `organization_project_environment_uid=<ENVIRONMENT_UID>` filter.
159
+ Git-resolved ProjectBranch. Do not pass an Environment UID or branch UID. On an
160
+ unregistered branch, table operations fail when they require ProjectBranch
161
+ context while unrelated local development remains available.
163
162
 
164
163
  Column lookup is also a different path. Use it only for schema-name discovery,
165
164
  not as the default dataset discovery flow:
@@ -72,6 +72,8 @@ Before changing code, collect or infer:
72
72
  - who owns the MetaTable catalog lifecycle
73
73
  - who owns physical DDL: TS Manager, Alembic, or an external authority
74
74
  - for `external_registered`, the target canonical `DataSource` UID
75
+ - the actual current Git branch; the SDK resolves its registered `ProjectBranch`
76
+ and Environment, and the user must not provide either UID
75
77
  - for contract changes, the selected `AlembicMetaTableMigration` provider or provider module path
76
78
  - for contract changes, the provider's `AlembicVersionMetaTable` root and whether it has been reserved
77
79
  - for contract changes, the intended Alembic revision, parent/current revision, target revision, and updated SQLAlchemy declarations
@@ -158,7 +158,10 @@ Do not downgrade a secret into a constant for convenience.
158
158
 
159
159
  ### 6. `Constant` and `Secret` names are unique configuration identities
160
160
 
161
- Treat `Constant.name` and `Secret.name` as unique organization-level configuration keys.
161
+ Treat `Constant.name` and `Secret.name` as unique Environment-level
162
+ configuration keys. Project-facing SDK and CLI operations derive the
163
+ Environment from the process-frozen current Git branch and registered
164
+ `ProjectBranch`. Never ask the user to provide an Environment UID or branch UID.
162
165
 
163
166
  For creation or sync tasks:
164
167
 
@@ -195,6 +195,10 @@ push alone does not prove deployment success.
195
195
 
196
196
  Use `Artifact` when the operational unit is a file.
197
197
 
198
+ Artifact and Bucket operations derive their Organization Environment from the
199
+ process-frozen current Git branch and registered `ProjectBranch`. Do not ask the
200
+ user to select an Environment UID or branch UID.
201
+
198
202
  Examples:
199
203
 
200
204
  - vendor drops
@@ -1320,47 +1320,6 @@ def _merge_cli_filter_alias(
1320
1320
  return merged
1321
1321
 
1322
1322
 
1323
- def _scope_table_collection_filters(
1324
- filters: dict[str, object],
1325
- *,
1326
- organization_project_environment_uid: object | None,
1327
- command_label: str,
1328
- ) -> dict[str, object]:
1329
- filter_key = "organization_project_environment_uid"
1330
- scoped = _merge_cli_filter_alias(
1331
- filters,
1332
- filter_key=filter_key,
1333
- value=organization_project_environment_uid,
1334
- option_name="organization-project-environment-uid",
1335
- )
1336
- if filter_key in scoped:
1337
- return scoped
1338
-
1339
- try:
1340
- context = require_project_branch_context(
1341
- command_label,
1342
- context=get_project_runtime_context(),
1343
- )
1344
- except ProjectRuntimeContextError as exc:
1345
- error(
1346
- f"{command_label} requires an Organization Environment scope. "
1347
- "Run inside a registered ProjectBranch checkout or pass "
1348
- "`--organization-project-environment-uid`."
1349
- )
1350
- raise typer.Exit(1) from exc
1351
-
1352
- environment_uid = str(context.organization_project_environment_uid or "").strip()
1353
- if not environment_uid:
1354
- error(
1355
- f"{command_label} cannot resolve an Organization Environment from "
1356
- "the active ProjectBranch."
1357
- )
1358
- raise typer.Exit(1)
1359
-
1360
- scoped[filter_key] = environment_uid
1361
- return scoped
1362
-
1363
-
1364
1323
  def _require_item_uid(item: dict, *, prompt_label: str) -> str:
1365
1324
  uid = str(item.get("uid") or "").strip()
1366
1325
  if not uid:
@@ -4466,7 +4425,6 @@ def _data_node_storage_list_impl(
4466
4425
  filter_entries: list[str] | None,
4467
4426
  show_filters: bool,
4468
4427
  data_source_uid: str | None = None,
4469
- organization_project_environment_uid: object | None = None,
4470
4428
  ) -> None:
4471
4429
  filters = _resolve_cli_list_filters(
4472
4430
  model_ref=DATA_NODE_STORAGE_MODEL_REF,
@@ -4481,11 +4439,6 @@ def _data_node_storage_list_impl(
4481
4439
  option_name="data-source-uid",
4482
4440
  )
4483
4441
  _require_login()
4484
- filters = _scope_table_collection_filters(
4485
- filters,
4486
- organization_project_environment_uid=organization_project_environment_uid,
4487
- command_label="Data Node Storage listing",
4488
- )
4489
4442
 
4490
4443
  try:
4491
4444
  storages = list_data_node_storages(timeout=timeout, filters=filters)
@@ -4519,7 +4472,6 @@ def _meta_table_list_impl(
4519
4472
  filter_entries: list[str] | None,
4520
4473
  show_filters: bool,
4521
4474
  data_source_uid: str | None = None,
4522
- organization_project_environment_uid: object | None = None,
4523
4475
  ) -> None:
4524
4476
  filters = _resolve_cli_list_filters(
4525
4477
  model_ref=META_TABLE_MODEL_REF,
@@ -4534,11 +4486,6 @@ def _meta_table_list_impl(
4534
4486
  option_name="data-source-uid",
4535
4487
  )
4536
4488
  _require_login()
4537
- filters = _scope_table_collection_filters(
4538
- filters,
4539
- organization_project_environment_uid=organization_project_environment_uid,
4540
- command_label="MetaTable listing",
4541
- )
4542
4489
 
4543
4490
  try:
4544
4491
  meta_tables = list_meta_tables(timeout=timeout, filters=filters)
@@ -6067,11 +6014,6 @@ def meta_table_list_cmd(
6067
6014
  data_source_uid: str | None = typer.Option(
6068
6015
  None, "--data-source-uid", help="Filter by data source UID."
6069
6016
  ),
6070
- organization_project_environment_uid: uuid.UUID | None = typer.Option(
6071
- None,
6072
- "--organization-project-environment-uid",
6073
- help=("Organization Environment scope. Defaults to the active Git-resolved ProjectBranch."),
6074
- ),
6075
6017
  filter_entries: list[str] | None = typer.Option(None, "--filter", help=LIST_FILTER_OPTION_HELP),
6076
6018
  show_filters: bool = typer.Option(
6077
6019
  False, "--show-filters", help="Show the filters supported by this list command and exit."
@@ -6088,7 +6030,6 @@ def meta_table_list_cmd(
6088
6030
  filter_entries=filter_entries,
6089
6031
  show_filters=show_filters,
6090
6032
  data_source_uid=data_source_uid,
6091
- organization_project_environment_uid=organization_project_environment_uid,
6092
6033
  )
6093
6034
 
6094
6035
 
@@ -6359,11 +6300,6 @@ def data_node_storage_list_cmd(
6359
6300
  data_source_uid: str | None = typer.Option(
6360
6301
  None, "--data-source-uid", help="Filter by data source UID."
6361
6302
  ),
6362
- organization_project_environment_uid: uuid.UUID | None = typer.Option(
6363
- None,
6364
- "--organization-project-environment-uid",
6365
- help=("Organization Environment scope. Defaults to the active Git-resolved ProjectBranch."),
6366
- ),
6367
6303
  filter_entries: list[str] | None = typer.Option(None, "--filter", help=LIST_FILTER_OPTION_HELP),
6368
6304
  show_filters: bool = typer.Option(
6369
6305
  False, "--show-filters", help="Show the filters supported by this list command and exit."
@@ -6395,7 +6331,6 @@ def data_node_storage_list_cmd(
6395
6331
  filter_entries=filter_entries,
6396
6332
  show_filters=show_filters,
6397
6333
  data_source_uid=data_source_uid,
6398
- organization_project_environment_uid=organization_project_environment_uid,
6399
6334
  )
6400
6335
 
6401
6336
 
@@ -97,6 +97,26 @@ class CurrentProjectBranchCollectionMixin:
97
97
  return list(cls.iter_filter_admin(timeout=timeout, **kwargs))
98
98
 
99
99
 
100
+ class CurrentProjectEnvironmentResourceMixin:
101
+ """Scope project-facing Environment resources to the frozen Git context."""
102
+
103
+ SDK_OWNED_CONTEXT_FIELDS: ClassVar[frozenset[str]] = frozenset(
104
+ {"organization_project_environment_uid"}
105
+ )
106
+
107
+ @classmethod
108
+ def _sdk_owned_query_context(cls, operation: str) -> dict[str, str]:
109
+ from mainsequence.project_context import resolve_project_environment_uid
110
+
111
+ return {
112
+ "organization_project_environment_uid": resolve_project_environment_uid(operation)
113
+ }
114
+
115
+ @classmethod
116
+ def _sdk_owned_create_context(cls, operation: str) -> dict[str, str]:
117
+ return cls._sdk_owned_query_context(operation)
118
+
119
+
100
120
  class BaseObjectOrm:
101
121
  FILTERSET_FIELDS: ClassVar[dict[str, list[str]] | None] = None
102
122
  FILTER_VALUE_NORMALIZERS: ClassVar[dict[str, str | Callable[..., Any]]] = {}
@@ -105,6 +125,7 @@ class BaseObjectOrm:
105
125
  DESTROY_QUERY_PARAMS: ClassVar[dict[str, str | Callable[..., Any]] | None] = None
106
126
  DESTROY_QUERY_PARAM_DESCRIPTIONS: ClassVar[dict[str, str] | None] = None
107
127
  PUBLIC_LOOKUP_FIELD: ClassVar[str] = "uid"
128
+ SDK_OWNED_CONTEXT_FIELDS: ClassVar[frozenset[str]] = frozenset()
108
129
 
109
130
  END_POINTS = {
110
131
  "User": "users",
@@ -154,6 +175,37 @@ class BaseObjectOrm:
154
175
  s = session
155
176
  return s
156
177
 
178
+ @classmethod
179
+ def _sdk_owned_query_context(cls, operation: str) -> dict[str, Any]:
180
+ return {}
181
+
182
+ @classmethod
183
+ def _sdk_owned_create_context(cls, operation: str) -> dict[str, Any]:
184
+ return {}
185
+
186
+ @classmethod
187
+ def _reject_sdk_context_overrides(
188
+ cls,
189
+ values: dict[str, Any],
190
+ *,
191
+ operation: str,
192
+ ) -> None:
193
+ overridden = sorted(set(values) & set(cls.SDK_OWNED_CONTEXT_FIELDS))
194
+ if overridden:
195
+ raise ValueError(
196
+ f"{operation} cannot override SDK-resolved context field(s): "
197
+ f"{', '.join(overridden)}."
198
+ )
199
+
200
+ @classmethod
201
+ def _with_sdk_owned_create_context(
202
+ cls,
203
+ operation: str,
204
+ values: dict[str, Any],
205
+ ) -> dict[str, Any]:
206
+ cls._reject_sdk_context_overrides(values, operation=operation)
207
+ return {**values, **cls._sdk_owned_create_context(operation)}
208
+
157
209
  @property
158
210
  def s(self):
159
211
  s = self.build_session()
@@ -468,11 +520,19 @@ class BaseObjectOrm:
468
520
  """
469
521
  Generator variant: yields objects across all pages without accumulating into memory.
470
522
  """
523
+ operation = f"{cls.__name__}.filter"
524
+ cls._reject_sdk_context_overrides(kwargs, operation=operation)
471
525
  filter_kwargs, read_query_kwargs = cls._split_filter_and_read_query_kwargs(kwargs)
472
526
  normalized_filters = cls._normalize_filter_kwargs(filter_kwargs)
473
527
  normalized_read_query = cls._normalize_read_query_kwargs(read_query_kwargs)
474
528
  base_url = cls.get_object_url()
475
- params = cls._parse_parameters_filter({**normalized_filters, **normalized_read_query})
529
+ params = cls._parse_parameters_filter(
530
+ {
531
+ **normalized_filters,
532
+ **normalized_read_query,
533
+ **cls._sdk_owned_query_context(operation),
534
+ }
535
+ )
476
536
 
477
537
  next_url = f"{base_url}/"
478
538
  yielded = 0
@@ -534,6 +594,8 @@ class BaseObjectOrm:
534
594
  Raises Exception if multiple or unexpected data is returned.
535
595
  """
536
596
  if pk is not None:
597
+ operation = f"{cls.__name__}.get"
598
+ cls._reject_sdk_context_overrides(filters, operation=operation)
537
599
  base_url = cls.get_object_url()
538
600
  detail_url = f"{base_url}/{pk}/"
539
601
  _, read_query_kwargs = cls._split_filter_and_read_query_kwargs(filters)
@@ -541,7 +603,11 @@ class BaseObjectOrm:
541
603
  extra_params = {
542
604
  key: value for key, value in filters.items() if key not in read_query_kwargs
543
605
  }
544
- params = {**extra_params, **normalized_read_query}
606
+ params = {
607
+ **extra_params,
608
+ **normalized_read_query,
609
+ **cls._sdk_owned_query_context(operation),
610
+ }
545
611
 
546
612
  r = make_request(
547
613
  s=cls.build_session(),
@@ -585,7 +651,10 @@ class BaseObjectOrm:
585
651
  @classmethod
586
652
  def create(cls, timeout=None, files=None, *args, **kwargs):
587
653
  base_url = cls.get_object_url()
588
- data = cls.serialize_for_json(kwargs)
654
+ operation = f"{cls.__name__}.create"
655
+ data = cls.serialize_for_json(
656
+ cls._with_sdk_owned_create_context(operation, dict(kwargs))
657
+ )
589
658
  payload = {"json": data}
590
659
  if files:
591
660
  payload["files"] = files
@@ -604,6 +673,8 @@ class BaseObjectOrm:
604
673
  @classmethod
605
674
  def _destroy_by_reference(cls, public_reference, *args, timeout=None, **kwargs):
606
675
  base_url = cls.get_object_url()
676
+ operation = f"{cls.__name__}.delete"
677
+ cls._reject_sdk_context_overrides(kwargs, operation=operation)
607
678
  payload: dict[str, Any] = {}
608
679
 
609
680
  if getattr(cls, "DESTROY_QUERY_PARAMS", None):
@@ -615,6 +686,10 @@ class BaseObjectOrm:
615
686
  if data:
616
687
  payload["json"] = data
617
688
 
689
+ query_context = cls._sdk_owned_query_context(operation)
690
+ if query_context:
691
+ payload.setdefault("params", {}).update(query_context)
692
+
618
693
  r = make_request(
619
694
  s=cls.build_session(),
620
695
  loaders=cls.LOADERS,
@@ -634,8 +709,13 @@ class BaseObjectOrm:
634
709
  def _patch_by_reference(cls, public_reference, *args, _into=None, **kwargs):
635
710
  base_url = cls.get_object_url()
636
711
  url = f"{base_url}/{public_reference}/"
712
+ operation = f"{cls.__name__}.patch"
713
+ cls._reject_sdk_context_overrides(kwargs, operation=operation)
637
714
  data = cls.serialize_for_json(kwargs)
638
715
  payload = {"json": data}
716
+ query_context = cls._sdk_owned_query_context(operation)
717
+ if query_context:
718
+ payload["params"] = query_context
639
719
 
640
720
  r = make_request(
641
721
  s=cls.build_session(),
@@ -769,7 +849,18 @@ class DetailActionObjectMixin:
769
849
  expected_statuses: tuple[int, ...] = (200,),
770
850
  empty_response: Any = None,
771
851
  ) -> Any:
772
- payload = payload or {}
852
+ payload = dict(payload or {})
853
+ query_context = type(self)._sdk_owned_query_context(
854
+ f"{type(self).__name__}.{action_name}"
855
+ )
856
+ if query_context:
857
+ params = dict(payload.get("params") or {})
858
+ type(self)._reject_sdk_context_overrides(
859
+ params,
860
+ operation=f"{type(self).__name__}.{action_name}",
861
+ )
862
+ params.update(query_context)
863
+ payload["params"] = params
773
864
  response = make_request(
774
865
  s=type(self).build_session(),
775
866
  loaders=type(self).LOADERS,
@@ -33,9 +33,16 @@ from mainsequence.project_context import (
33
33
  get_project_runtime_context,
34
34
  require_project_branch_context,
35
35
  require_project_metatables_data_source,
36
+ resolve_project_environment_uid,
36
37
  )
37
38
 
38
- from ..base import BaseObjectOrm, BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin
39
+ from ..base import (
40
+ BaseObjectOrm,
41
+ BasePydanticModel,
42
+ CurrentProjectEnvironmentResourceMixin,
43
+ LabelableObjectMixin,
44
+ ShareableObjectMixin,
45
+ )
39
46
  from ..data_sources_interfaces import get_duckdb_interface_class, get_sqlite_interface_class
40
47
  from ..dtype_codec import (
41
48
  DATE,
@@ -1237,7 +1244,13 @@ class DataSource(BasePydanticModel, BaseObjectOrm):
1237
1244
  raise NotImplementedError
1238
1245
 
1239
1246
 
1240
- class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, BaseObjectOrm):
1247
+ class MetaTable(
1248
+ CurrentProjectEnvironmentResourceMixin,
1249
+ BasePydanticModel,
1250
+ LabelableObjectMixin,
1251
+ ShareableObjectMixin,
1252
+ BaseObjectOrm,
1253
+ ):
1241
1254
  ENDPOINT: ClassVar[str] = "meta-tables"
1242
1255
  COLLECTION_CREATE_ROW_MODEL: ClassVar[type[ManagedMetaTableCollectionCreateRow]] = (
1243
1256
  ManagedMetaTableCollectionCreateRow
@@ -1246,7 +1259,6 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1246
1259
  "identifier": ["in", "exact", "contains"],
1247
1260
  "uid": ["in", "exact"],
1248
1261
  "data_source__uid": ["in", "exact"],
1249
- "organization_project_environment_uid": ["in", "exact"],
1250
1262
  "namespace": ["exact", "contains", "in", "isnull"],
1251
1263
  "management_mode": ["exact", "in"],
1252
1264
  "provisioning_status": ["exact", "in"],
@@ -1261,8 +1273,6 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1261
1273
  "uid__in": "str",
1262
1274
  "data_source__uid": "uid",
1263
1275
  "data_source__uid__in": "uid",
1264
- "organization_project_environment_uid": "uid",
1265
- "organization_project_environment_uid__in": "uid",
1266
1276
  "physical_schema": "str",
1267
1277
  "physical_schema__in": "str",
1268
1278
  "labels": "str",
@@ -1332,6 +1342,11 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1332
1342
 
1333
1343
  model_config = ConfigDict(populate_by_name=True)
1334
1344
 
1345
+ @classmethod
1346
+ def _sdk_owned_create_context(cls, operation: str) -> dict[str, str]:
1347
+ # MetaTable collection creation has its own managed project_context contract.
1348
+ return {}
1349
+
1335
1350
  @model_validator(mode="before")
1336
1351
  @classmethod
1337
1352
  def _strip_legacy_storage_hash(cls, value: Any) -> Any:
@@ -1425,9 +1440,11 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1425
1440
  timeout: int | float | tuple[float, float] | None = None,
1426
1441
  **filters: Any,
1427
1442
  ) -> list[MetaTable]:
1443
+ operation = f"{cls.__name__}.filter_by_body"
1444
+ cls._reject_sdk_context_overrides(filters, operation=operation)
1428
1445
  response_json = cls._post_action(
1429
1446
  "filter",
1430
- filters,
1447
+ {**filters, **cls._sdk_owned_query_context(operation)},
1431
1448
  timeout=timeout,
1432
1449
  expected_statuses=(200,),
1433
1450
  )
@@ -1472,7 +1489,12 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1472
1489
  **kwargs,
1473
1490
  ):
1474
1491
  url = f"{type(self).get_object_url().rstrip('/')}/{self._public_uid()}/"
1475
- payload = {"json": serialize_to_json(kwargs)}
1492
+ operation = f"{type(self).__name__}.patch"
1493
+ type(self)._reject_sdk_context_overrides(kwargs, operation=operation)
1494
+ payload = {
1495
+ "json": serialize_to_json(kwargs),
1496
+ "params": type(self)._sdk_owned_query_context(operation),
1497
+ }
1476
1498
  response = make_request(
1477
1499
  s=type(self).build_session(),
1478
1500
  loaders=type(self).LOADERS,
@@ -1504,6 +1526,7 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1504
1526
  "full_delete_downstream_tables": full_delete_downstream_tables,
1505
1527
  "delete_with_no_table": delete_with_no_table,
1506
1528
  "override_protection": override_protection,
1529
+ **cls._sdk_owned_query_context(f"{cls.__name__}.delete"),
1507
1530
  }
1508
1531
  }
1509
1532
  response = make_request(
@@ -1550,9 +1573,13 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1550
1573
  raise ValueError(
1551
1574
  "project_context is SDK-controlled and cannot be supplied by the caller."
1552
1575
  )
1576
+ if "organization_project_environment_uid" in unvalidated_payload:
1577
+ raise ValueError(
1578
+ "organization_project_environment_uid is SDK-controlled and cannot "
1579
+ "be supplied by the caller."
1580
+ )
1553
1581
  payload = MetaTableRegistrationRequest.model_validate(unvalidated_payload)
1554
- if payload.management_mode != "external_registered":
1555
- payload = _with_current_metatable_project_context(payload)
1582
+ payload = _with_current_metatable_project_context(payload)
1556
1583
  response_json = cls._post_action(
1557
1584
  "register",
1558
1585
  payload,
@@ -1823,6 +1850,8 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1823
1850
  q = (q or "").strip()
1824
1851
  if not q:
1825
1852
  raise ValueError("q is required")
1853
+ operation = f"{cls.__name__}.description_search"
1854
+ cls._reject_sdk_context_overrides(filters, operation=operation)
1826
1855
 
1827
1856
  body = {
1828
1857
  "q": q,
@@ -1838,6 +1867,7 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1838
1867
 
1839
1868
  if filters:
1840
1869
  body.update(filters)
1870
+ body.update(cls._sdk_owned_query_context(operation))
1841
1871
 
1842
1872
  payload = {"params": serialize_to_json(body)}
1843
1873
  response = make_request(
@@ -1857,8 +1887,14 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1857
1887
  q = (q or "").strip()
1858
1888
  if not q:
1859
1889
  raise ValueError("q is required")
1890
+ operation = f"{cls.__name__}.column_search"
1891
+ cls._reject_sdk_context_overrides(filters, operation=operation)
1860
1892
 
1861
- payload = {"params": serialize_to_json({"q": q, **filters})}
1893
+ payload = {
1894
+ "params": serialize_to_json(
1895
+ {"q": q, **filters, **cls._sdk_owned_query_context(operation)}
1896
+ )
1897
+ }
1862
1898
  response = make_request(
1863
1899
  s=cls.build_session(),
1864
1900
  loaders=cls.LOADERS,
@@ -3080,7 +3116,6 @@ class TimeIndexMetaTable(MetaTable):
3080
3116
  "identifier": ["in", "exact", "contains"],
3081
3117
  "uid": ["in", "exact"],
3082
3118
  "data_source__uid": ["in", "exact"],
3083
- "organization_project_environment_uid": ["in", "exact"],
3084
3119
  "namespace": ["exact", "contains", "in", "isnull"],
3085
3120
  "physical_schema": ["exact", "in"],
3086
3121
  "physical_table_name": ["exact", "contains", "in"],
@@ -3091,8 +3126,6 @@ class TimeIndexMetaTable(MetaTable):
3091
3126
  "uid__in": "uid",
3092
3127
  "data_source__uid": "uid",
3093
3128
  "data_source__uid__in": "uid",
3094
- "organization_project_environment_uid": "uid",
3095
- "organization_project_environment_uid__in": "uid",
3096
3129
  "physical_schema": "str",
3097
3130
  "physical_schema__in": "str",
3098
3131
  "labels": "str",
@@ -3331,6 +3364,11 @@ class TimeIndexMetaTable(MetaTable):
3331
3364
  raise ValueError(
3332
3365
  "project_context is SDK-controlled and cannot be supplied by the caller."
3333
3366
  )
3367
+ if "organization_project_environment_uid" in unvalidated_payload:
3368
+ raise ValueError(
3369
+ "organization_project_environment_uid is SDK-controlled and cannot "
3370
+ "be supplied by the caller."
3371
+ )
3334
3372
  payload = TimeIndexMetaTableRegistrationRequest.model_validate(unvalidated_payload)
3335
3373
  payload_json = _with_current_metatable_project_context(payload)
3336
3374
  request_payload = {"json": serialize_to_json(payload_json)}
@@ -4706,6 +4744,10 @@ def _current_metatable_project_context() -> MetaTableProjectContextRequest:
4706
4744
  return _metatable_project_context_from_resolution(get_project_runtime_context())
4707
4745
 
4708
4746
 
4747
+ def _current_metatable_environment_uid() -> str:
4748
+ return resolve_project_environment_uid("External MetaTable registration")
4749
+
4750
+
4709
4751
  def _metatable_project_context_for_request() -> MetaTableProjectContextRequest | None:
4710
4752
  return _current_metatable_project_context()
4711
4753
 
@@ -4716,7 +4758,15 @@ def _with_current_metatable_project_context(
4716
4758
  payload_json = _payload_json(payload)
4717
4759
  if "project_context" in payload_json:
4718
4760
  raise ValueError("project_context is SDK-controlled and cannot be supplied by the caller.")
4761
+ if "organization_project_environment_uid" in payload_json:
4762
+ raise ValueError(
4763
+ "organization_project_environment_uid is SDK-controlled and cannot "
4764
+ "be supplied by the caller."
4765
+ )
4719
4766
  if payload_json.get("management_mode") == "external_registered":
4767
+ payload_json["organization_project_environment_uid"] = (
4768
+ _current_metatable_environment_uid()
4769
+ )
4720
4770
  return payload_json
4721
4771
  project_context = _metatable_project_context_for_request()
4722
4772
  if project_context is not None:
@@ -4730,13 +4780,23 @@ def _with_current_metatable_project_context_collection(
4730
4780
  payload_rows = [dict(payload) for payload in payloads]
4731
4781
  if any("project_context" in row for row in payload_rows):
4732
4782
  raise ValueError("project_context is SDK-controlled and cannot be supplied by the caller.")
4783
+ if any("organization_project_environment_uid" in row for row in payload_rows):
4784
+ raise ValueError(
4785
+ "organization_project_environment_uid is SDK-controlled and cannot "
4786
+ "be supplied by the caller."
4787
+ )
4733
4788
  managed_rows = [
4734
4789
  row for row in payload_rows if row.get("management_mode") != "external_registered"
4735
4790
  ]
4736
- if not managed_rows:
4737
- return payload_rows
4791
+ external_rows = [
4792
+ row for row in payload_rows if row.get("management_mode") == "external_registered"
4793
+ ]
4794
+ if external_rows:
4795
+ environment_uid = _current_metatable_environment_uid()
4796
+ for row in external_rows:
4797
+ row["organization_project_environment_uid"] = environment_uid
4738
4798
 
4739
- project_context = _metatable_project_context_for_request()
4799
+ project_context = _metatable_project_context_for_request() if managed_rows else None
4740
4800
  if project_context is not None:
4741
4801
  current_project_context = project_context.model_dump(mode="json")
4742
4802
  for row in managed_rows: