mainsequence 6.0.39__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.39/mainsequence.egg-info → mainsequence-6.0.41}/PKG-INFO +1 -1
  2. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/data_access/exploration/SKILL.md +6 -0
  3. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +2 -0
  4. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +4 -1
  5. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +4 -0
  6. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/base.py +95 -4
  7. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/metatables/core.py +76 -18
  8. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/models_foundry.py +76 -8
  9. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/models_helpers.py +9 -0
  10. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/project_context.py +19 -0
  11. {mainsequence-6.0.39 → mainsequence-6.0.41/mainsequence.egg-info}/PKG-INFO +1 -1
  12. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence.egg-info/SOURCES.txt +1 -0
  13. {mainsequence-6.0.39 → mainsequence-6.0.41}/pyproject.toml +1 -1
  14. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_cli.py +47 -9
  15. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_cli_migrations.py +2 -1
  16. mainsequence-6.0.41/tests/test_environment_resource_context.py +168 -0
  17. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_filter_normalization.py +115 -27
  18. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_meta_tables_client_models.py +58 -0
  19. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_project_runtime_context.py +35 -0
  20. mainsequence-6.0.41/tests/test_secret_client_model.py +18 -0
  21. mainsequence-6.0.39/tests/test_secret_client_model.py +0 -11
  22. {mainsequence-6.0.39 → mainsequence-6.0.41}/LICENSE +0 -0
  23. {mainsequence-6.0.39 → mainsequence-6.0.41}/README.md +0 -0
  24. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/AGENTS.md +0 -0
  25. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  26. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  27. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  28. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  29. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  30. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +0 -0
  31. {mainsequence-6.0.39 → mainsequence-6.0.41}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
  32. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/__init__.py +0 -0
  33. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/__main__.py +0 -0
  34. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/bootstrap.py +0 -0
  35. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/__init__.py +0 -0
  36. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/api.py +0 -0
  37. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/browser_auth.py +0 -0
  38. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/cli.py +0 -0
  39. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/config.py +0 -0
  40. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/docker_utils.py +0 -0
  41. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/doctor.py +0 -0
  42. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/local_ops.py +0 -0
  43. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/migrations.py +0 -0
  44. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/model_filters.py +0 -0
  45. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/project_status.py +0 -0
  46. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/pydantic_cli.py +0 -0
  47. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/sdk_utils.py +0 -0
  48. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/ssh_utils.py +0 -0
  49. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/cli/ui.py +0 -0
  50. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/__init__.py +0 -0
  51. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/agent_runtime_models.py +0 -0
  52. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/client.py +0 -0
  53. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/compute_validation.py +0 -0
  54. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  55. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  56. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  57. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  58. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/dtype_codec.py +0 -0
  59. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/exceptions.py +0 -0
  60. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/metatables/__init__.py +0 -0
  61. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/models_user.py +0 -0
  62. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/client/utils.py +0 -0
  63. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/defaults.py +0 -0
  64. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/instrumentation/__init__.py +0 -0
  65. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/instrumentation/utils.py +0 -0
  66. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/logconf.py +0 -0
  67. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/__init__.py +0 -0
  68. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/__main__.py +0 -0
  69. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  70. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  71. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  72. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  73. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  74. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  75. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  76. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  77. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  78. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  79. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/future_registry.py +0 -0
  80. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/hashing.py +0 -0
  81. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  82. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  83. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/env.py +0 -0
  84. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/provider.py +0 -0
  85. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/registry.py +0 -0
  86. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  87. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  88. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  89. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  90. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  91. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/schema_names.py +0 -0
  92. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  93. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/project_skills.py +0 -0
  94. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/runtime_flags.py +0 -0
  95. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence/scaffold_skills.py +0 -0
  96. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence.egg-info/dependency_links.txt +0 -0
  97. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence.egg-info/entry_points.txt +0 -0
  98. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence.egg-info/requires.txt +0 -0
  99. {mainsequence-6.0.39 → mainsequence-6.0.41}/mainsequence.egg-info/top_level.txt +0 -0
  100. {mainsequence-6.0.39 → mainsequence-6.0.41}/setup.cfg +0 -0
  101. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_auth_precedence.py +0 -0
  102. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_build_operations_hashing.py +0 -0
  103. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_canonical_api_route_contract.py +0 -0
  104. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_cli_browser_auth.py +0 -0
  105. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_client.py +0 -0
  106. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  107. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_data_node_storage_dimension_queries.py +0 -0
  108. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_data_node_update_flow.py +0 -0
  109. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_dependency_extras.py +0 -0
  110. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_duckdb_interface_dimensions.py +0 -0
  111. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_instrumentation.py +0 -0
  112. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_local_ops.py +0 -0
  113. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_logconf.py +0 -0
  114. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_meta_table_migrations.py +0 -0
  115. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  116. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_models_user_request_bound_auth.py +0 -0
  117. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_notification_uid_contract.py +0 -0
  118. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_pod_project_resolution.py +0 -0
  119. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_project_batch_jobs_from_file.py +0 -0
  120. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_project_branch_contract.py +0 -0
  121. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_project_skills.py +0 -0
  122. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_run_configuration.py +0 -0
  123. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_scaffold_skills.py +0 -0
  124. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_schema_names.py +0 -0
  125. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_source_table_configuration.py +0 -0
  126. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_sqlite_interface_dimensions.py +0 -0
  127. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_ssh_utils.py +0 -0
  128. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_update_runner_uid_runtime.py +0 -0
  129. {mainsequence-6.0.39 → mainsequence-6.0.41}/tests/test_update_statistics.py +0 -0
  130. {mainsequence-6.0.39 → 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.39
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
@@ -154,6 +154,12 @@ field to filter by:
154
154
  - `mainsequence data-node list --filter KEY=VALUE`
155
155
  - `mainsequence data-node detail <DATA_NODE_STORAGE_UID>`
156
156
 
157
+ Collection listing is scoped to one Organization Environment. Inside a
158
+ registered project checkout, the CLI derives that scope from the process-frozen
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.
162
+
157
163
  Column lookup is also a different path. Use it only for schema-name discovery,
158
164
  not as the default dataset discovery flow:
159
165
 
@@ -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
@@ -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,8 +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
- "organization_project_environment": ["isnull"],
1251
1262
  "namespace": ["exact", "contains", "in", "isnull"],
1252
1263
  "management_mode": ["exact", "in"],
1253
1264
  "provisioning_status": ["exact", "in"],
@@ -1262,8 +1273,6 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1262
1273
  "uid__in": "str",
1263
1274
  "data_source__uid": "uid",
1264
1275
  "data_source__uid__in": "uid",
1265
- "organization_project_environment__uid": "uid",
1266
- "organization_project_environment__uid__in": "uid",
1267
1276
  "physical_schema": "str",
1268
1277
  "physical_schema__in": "str",
1269
1278
  "labels": "str",
@@ -1333,6 +1342,11 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1333
1342
 
1334
1343
  model_config = ConfigDict(populate_by_name=True)
1335
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
+
1336
1350
  @model_validator(mode="before")
1337
1351
  @classmethod
1338
1352
  def _strip_legacy_storage_hash(cls, value: Any) -> Any:
@@ -1426,9 +1440,11 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1426
1440
  timeout: int | float | tuple[float, float] | None = None,
1427
1441
  **filters: Any,
1428
1442
  ) -> list[MetaTable]:
1443
+ operation = f"{cls.__name__}.filter_by_body"
1444
+ cls._reject_sdk_context_overrides(filters, operation=operation)
1429
1445
  response_json = cls._post_action(
1430
1446
  "filter",
1431
- filters,
1447
+ {**filters, **cls._sdk_owned_query_context(operation)},
1432
1448
  timeout=timeout,
1433
1449
  expected_statuses=(200,),
1434
1450
  )
@@ -1473,7 +1489,12 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1473
1489
  **kwargs,
1474
1490
  ):
1475
1491
  url = f"{type(self).get_object_url().rstrip('/')}/{self._public_uid()}/"
1476
- 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
+ }
1477
1498
  response = make_request(
1478
1499
  s=type(self).build_session(),
1479
1500
  loaders=type(self).LOADERS,
@@ -1505,6 +1526,7 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1505
1526
  "full_delete_downstream_tables": full_delete_downstream_tables,
1506
1527
  "delete_with_no_table": delete_with_no_table,
1507
1528
  "override_protection": override_protection,
1529
+ **cls._sdk_owned_query_context(f"{cls.__name__}.delete"),
1508
1530
  }
1509
1531
  }
1510
1532
  response = make_request(
@@ -1551,9 +1573,13 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1551
1573
  raise ValueError(
1552
1574
  "project_context is SDK-controlled and cannot be supplied by the caller."
1553
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
+ )
1554
1581
  payload = MetaTableRegistrationRequest.model_validate(unvalidated_payload)
1555
- if payload.management_mode != "external_registered":
1556
- payload = _with_current_metatable_project_context(payload)
1582
+ payload = _with_current_metatable_project_context(payload)
1557
1583
  response_json = cls._post_action(
1558
1584
  "register",
1559
1585
  payload,
@@ -1824,6 +1850,8 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1824
1850
  q = (q or "").strip()
1825
1851
  if not q:
1826
1852
  raise ValueError("q is required")
1853
+ operation = f"{cls.__name__}.description_search"
1854
+ cls._reject_sdk_context_overrides(filters, operation=operation)
1827
1855
 
1828
1856
  body = {
1829
1857
  "q": q,
@@ -1839,6 +1867,7 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1839
1867
 
1840
1868
  if filters:
1841
1869
  body.update(filters)
1870
+ body.update(cls._sdk_owned_query_context(operation))
1842
1871
 
1843
1872
  payload = {"params": serialize_to_json(body)}
1844
1873
  response = make_request(
@@ -1858,8 +1887,14 @@ class MetaTable(BasePydanticModel, LabelableObjectMixin, ShareableObjectMixin, B
1858
1887
  q = (q or "").strip()
1859
1888
  if not q:
1860
1889
  raise ValueError("q is required")
1890
+ operation = f"{cls.__name__}.column_search"
1891
+ cls._reject_sdk_context_overrides(filters, operation=operation)
1861
1892
 
1862
- 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
+ }
1863
1898
  response = make_request(
1864
1899
  s=cls.build_session(),
1865
1900
  loaders=cls.LOADERS,
@@ -3081,8 +3116,6 @@ class TimeIndexMetaTable(MetaTable):
3081
3116
  "identifier": ["in", "exact", "contains"],
3082
3117
  "uid": ["in", "exact"],
3083
3118
  "data_source__uid": ["in", "exact"],
3084
- "organization_project_environment__uid": ["in", "exact"],
3085
- "organization_project_environment": ["isnull"],
3086
3119
  "namespace": ["exact", "contains", "in", "isnull"],
3087
3120
  "physical_schema": ["exact", "in"],
3088
3121
  "physical_table_name": ["exact", "contains", "in"],
@@ -3093,8 +3126,6 @@ class TimeIndexMetaTable(MetaTable):
3093
3126
  "uid__in": "uid",
3094
3127
  "data_source__uid": "uid",
3095
3128
  "data_source__uid__in": "uid",
3096
- "organization_project_environment__uid": "uid",
3097
- "organization_project_environment__uid__in": "uid",
3098
3129
  "physical_schema": "str",
3099
3130
  "physical_schema__in": "str",
3100
3131
  "labels": "str",
@@ -3333,6 +3364,11 @@ class TimeIndexMetaTable(MetaTable):
3333
3364
  raise ValueError(
3334
3365
  "project_context is SDK-controlled and cannot be supplied by the caller."
3335
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
+ )
3336
3372
  payload = TimeIndexMetaTableRegistrationRequest.model_validate(unvalidated_payload)
3337
3373
  payload_json = _with_current_metatable_project_context(payload)
3338
3374
  request_payload = {"json": serialize_to_json(payload_json)}
@@ -4708,6 +4744,10 @@ def _current_metatable_project_context() -> MetaTableProjectContextRequest:
4708
4744
  return _metatable_project_context_from_resolution(get_project_runtime_context())
4709
4745
 
4710
4746
 
4747
+ def _current_metatable_environment_uid() -> str:
4748
+ return resolve_project_environment_uid("External MetaTable registration")
4749
+
4750
+
4711
4751
  def _metatable_project_context_for_request() -> MetaTableProjectContextRequest | None:
4712
4752
  return _current_metatable_project_context()
4713
4753
 
@@ -4718,7 +4758,15 @@ def _with_current_metatable_project_context(
4718
4758
  payload_json = _payload_json(payload)
4719
4759
  if "project_context" in payload_json:
4720
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
+ )
4721
4766
  if payload_json.get("management_mode") == "external_registered":
4767
+ payload_json["organization_project_environment_uid"] = (
4768
+ _current_metatable_environment_uid()
4769
+ )
4722
4770
  return payload_json
4723
4771
  project_context = _metatable_project_context_for_request()
4724
4772
  if project_context is not None:
@@ -4732,13 +4780,23 @@ def _with_current_metatable_project_context_collection(
4732
4780
  payload_rows = [dict(payload) for payload in payloads]
4733
4781
  if any("project_context" in row for row in payload_rows):
4734
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
+ )
4735
4788
  managed_rows = [
4736
4789
  row for row in payload_rows if row.get("management_mode") != "external_registered"
4737
4790
  ]
4738
- if not managed_rows:
4739
- 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
4740
4798
 
4741
- project_context = _metatable_project_context_for_request()
4799
+ project_context = _metatable_project_context_for_request() if managed_rows else None
4742
4800
  if project_context is not None:
4743
4801
  current_project_context = project_context.model_dump(mode="json")
4744
4802
  for row in managed_rows:
@@ -17,6 +17,7 @@ from .base import (
17
17
  BaseObjectOrm,
18
18
  BasePydanticModel,
19
19
  CurrentProjectBranchCollectionMixin,
20
+ CurrentProjectEnvironmentResourceMixin,
20
21
  LabelableObjectMixin,
21
22
  ShareableObjectMixin,
22
23
  )
@@ -899,7 +900,12 @@ class DynamicResource(BasePydanticModel, BaseObjectOrm):
899
900
  return super().create(*args, **kwargs)
900
901
 
901
902
 
902
- class Bucket(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
903
+ class Bucket(
904
+ CurrentProjectEnvironmentResourceMixin,
905
+ ShareableObjectMixin,
906
+ BasePydanticModel,
907
+ BaseObjectOrm,
908
+ ):
903
909
  FILTERSET_FIELDS: ClassVar[dict[str, list[str]]] = {
904
910
  "uid": ["in", "exact"],
905
911
  "name": ["in", "exact"],
@@ -924,9 +930,34 @@ class Bucket(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
924
930
  examples=["daily_positions_report.pdf"],
925
931
  json_schema_extra={"label": "Bucket Name"},
926
932
  )
933
+ organization_project_environment_uid: str | None = Field(
934
+ None,
935
+ description="Read-only UID of the owning Organization Environment.",
936
+ )
937
+ organization_project_environment_name: str | None = Field(
938
+ None,
939
+ description="Read-only name of the owning Organization Environment.",
940
+ )
941
+ physical_bucket_uid: str | None = Field(
942
+ None,
943
+ description="Read-only UID of the provider-backed physical bucket.",
944
+ )
945
+ backing_ready: bool | None = Field(
946
+ None,
947
+ description="Whether the physical bucket backing is ready for use.",
948
+ )
949
+ backing_last_discovered_at: datetime.datetime | None = Field(
950
+ None,
951
+ description="Last successful physical backing discovery timestamp.",
952
+ )
927
953
 
928
954
 
929
- class Artifact(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
955
+ class Artifact(
956
+ CurrentProjectEnvironmentResourceMixin,
957
+ ShareableObjectMixin,
958
+ BasePydanticModel,
959
+ BaseObjectOrm,
960
+ ):
930
961
  uid: str | None = Field(
931
962
  None,
932
963
  title="Artifact UID",
@@ -970,6 +1001,14 @@ class Artifact(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
970
1001
  examples=["2026-03-15T10:30:00Z"],
971
1002
  json_schema_extra={"label": "Creation Date"},
972
1003
  )
1004
+ organization_project_environment_uid: str | None = Field(
1005
+ None,
1006
+ description="Read-only UID of the owning Organization Environment.",
1007
+ )
1008
+ organization_project_environment_name: str | None = Field(
1009
+ None,
1010
+ description="Read-only name of the owning Organization Environment.",
1011
+ )
973
1012
 
974
1013
  @classmethod
975
1014
  def upload_file(cls, filepath, name, bucket_name=None):
@@ -985,10 +1024,13 @@ class Artifact(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
985
1024
  url = cls.get_object_url() + "/get-or-create/"
986
1025
  s = cls.build_session()
987
1026
  with open(filepath, "rb") as f:
988
- data = {
989
- "name": name,
990
- "bucket_name": bucket_name if bucket_name else "default_bucket",
991
- }
1027
+ data = cls._with_sdk_owned_create_context(
1028
+ "Artifact.get_or_create",
1029
+ {
1030
+ "name": name,
1031
+ "bucket_name": bucket_name if bucket_name else "default_bucket",
1032
+ },
1033
+ )
992
1034
  files = {"content": (str(filepath), f, "application/pdf")}
993
1035
  payload = {"json": data, "files": files}
994
1036
  r = make_request(s=s, loaders=cls.LOADERS, r_type="POST", url=url, payload=payload)
@@ -1029,7 +1071,12 @@ def _norm_kwargs(kwargs: dict[str, Any]) -> tuple[tuple[str, Any], ...]:
1029
1071
  return tuple(sorted(items))
1030
1072
 
1031
1073
 
1032
- class Secret(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
1074
+ class Secret(
1075
+ CurrentProjectEnvironmentResourceMixin,
1076
+ ShareableObjectMixin,
1077
+ BasePydanticModel,
1078
+ BaseObjectOrm,
1079
+ ):
1033
1080
  FILTERSET_FIELDS: ClassVar[dict[str, list[str]]] = {
1034
1081
  "uid": ["in", "exact"],
1035
1082
  "name": ["in", "exact"],
@@ -1047,6 +1094,14 @@ class Secret(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
1047
1094
  description="Secret value. The create endpoint may omit it in the response.",
1048
1095
  exclude=True,
1049
1096
  )
1097
+ organization_project_environment_uid: str | None = Field(
1098
+ None,
1099
+ description="Read-only UID of the owning Organization Environment.",
1100
+ )
1101
+ organization_project_environment_name: str | None = Field(
1102
+ None,
1103
+ description="Read-only name of the owning Organization Environment.",
1104
+ )
1050
1105
 
1051
1106
  @classmethod
1052
1107
  def create(
@@ -1073,7 +1128,12 @@ class Secret(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
1073
1128
  return super().create(name=name, value=value, timeout=timeout)
1074
1129
 
1075
1130
 
1076
- class Constant(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
1131
+ class Constant(
1132
+ CurrentProjectEnvironmentResourceMixin,
1133
+ ShareableObjectMixin,
1134
+ BasePydanticModel,
1135
+ BaseObjectOrm,
1136
+ ):
1077
1137
  """
1078
1138
  Simple organization-level constant.
1079
1139
  """
@@ -1100,6 +1160,14 @@ class Constant(ShareableObjectMixin, BasePydanticModel, BaseObjectOrm):
1100
1160
  description="Small JSON value (string/number/bool/object/array). Keep it small (e.g., <=10KB).",
1101
1161
  )
1102
1162
  category: str | None = None
1163
+ organization_project_environment_uid: str | None = Field(
1164
+ None,
1165
+ description="Read-only UID of the owning Organization Environment.",
1166
+ )
1167
+ organization_project_environment_name: str | None = Field(
1168
+ None,
1169
+ description="Read-only name of the owning Organization Environment.",
1170
+ )
1103
1171
 
1104
1172
  # Class-level cache & lock (Pydantic ignores ClassVar)
1105
1173
  _filter_cache: ClassVar[TTLCache] = TTLCache(maxsize=512, ttl=600)
@@ -168,6 +168,15 @@ class Job(CurrentProjectBranchCollectionMixin, BaseObjectOrm, BasePydanticModel)
168
168
  examples=["5a28020a-0f1b-47ee-aab8-334286234bea"],
169
169
  )
170
170
 
171
+ organization_project_environment_uid: str | None = Field(
172
+ default=None,
173
+ description=(
174
+ "Read-only Organization Environment UID derived by the backend "
175
+ "from the owning ProjectBranch."
176
+ ),
177
+ examples=["58218213-5e4e-43de-a5bd-6757f4e1c8f6"],
178
+ )
179
+
171
180
  project_repo_hash: str | None = Field(
172
181
  default=None,
173
182
  description=(
@@ -28,6 +28,10 @@ class ProjectBranchContextRequiredError(ProjectRuntimeContextError):
28
28
  """Raised when an operation requires a registered current ProjectBranch."""
29
29
 
30
30
 
31
+ class ProjectEnvironmentContextRequiredError(ProjectBranchContextRequiredError):
32
+ """Raised when the current ProjectBranch has no resolved Environment."""
33
+
34
+
31
35
  class ProjectDataSourceContextRequiredError(ProjectRuntimeContextError):
32
36
  """Raised when project-derived data access has no usable branch DataSource."""
33
37
 
@@ -406,6 +410,19 @@ def resolve_project_branch_uid(operation: str, supplied_uid: Any = None) -> str:
406
410
  return resolved_uid
407
411
 
408
412
 
413
+ def resolve_project_environment_uid(operation: str) -> str:
414
+ """Return the Environment UID derived from the process-frozen ProjectBranch."""
415
+
416
+ context = require_project_branch_context(operation)
417
+ environment_uid = str(context.organization_project_environment_uid or "").strip()
418
+ if not environment_uid:
419
+ raise ProjectEnvironmentContextRequiredError(
420
+ f"{operation} requires an Organization Environment resolved from "
421
+ f"ProjectBranch {context.project_branch_uid!r}, but none was returned."
422
+ )
423
+ return environment_uid
424
+
425
+
409
426
  def scope_current_project_branch_filters(
410
427
  operation: str,
411
428
  filters: Mapping[str, Any],
@@ -472,6 +489,7 @@ __all__ = [
472
489
  "GitProjectSourceContext",
473
490
  "ProjectBranchContextRequiredError",
474
491
  "ProjectDataSourceContextRequiredError",
492
+ "ProjectEnvironmentContextRequiredError",
475
493
  "ProjectRuntimeContext",
476
494
  "ProjectRuntimeContextError",
477
495
  "ProjectSourceContextDriftError",
@@ -479,6 +497,7 @@ __all__ = [
479
497
  "normalize_git_repository_identity",
480
498
  "require_project_branch_context",
481
499
  "require_project_metatables_data_source",
500
+ "resolve_project_environment_uid",
482
501
  "resolve_project_branch_uid",
483
502
  "scope_current_project_branch_filters",
484
503
  "validate_project_source_context",