mainsequence 7.0.0__tar.gz → 7.0.2__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.
- {mainsequence-7.0.0/mainsequence.egg-info → mainsequence-7.0.2}/PKG-INFO +1 -1
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +4 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +4 -1
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/metatables/core.py +47 -3
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/project_context.py +29 -56
- {mainsequence-7.0.0 → mainsequence-7.0.2/mainsequence.egg-info}/PKG-INFO +1 -1
- {mainsequence-7.0.0 → mainsequence-7.0.2}/pyproject.toml +1 -1
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_cli.py +76 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_project_runtime_context.py +96 -66
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_scheduler_client_model.py +53 -3
- {mainsequence-7.0.0 → mainsequence-7.0.2}/LICENSE +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/MANIFEST.in +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/README.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/__main__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/bootstrap.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/api.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/cli.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/config.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/migrations.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/project_status.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/cli/ui.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/base.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/client.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/dtype_codec.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/metatables/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/models_user.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/observability.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/utils.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/defaults.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/logconf.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/__main__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/future_registry.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/hashing.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/alembic.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/env.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/provider.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/registry.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/schema_names.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/runner.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/project_skills.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/scaffold_skills.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence.egg-info/SOURCES.txt +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/setup.cfg +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_auth_precedence.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_canonical_api_route_contract.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_cli_migrations.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_client.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_dependency_extras.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_duckdb_interface_dimensions.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_environment_resource_context.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_environment_response_contracts.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_filter_normalization.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_instrumentation.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_local_ops.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_logconf.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_meta_table_migrations.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_meta_tables_client_models.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_notification_uid_contract.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_observability_client.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_pod_project_resolution.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_project_batch_jobs_from_file.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_project_branch_contract.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_project_skills.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_scaffold_skills.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_schema_names.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_secret_client_model.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_source_table_configuration.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_sqlite_interface_dimensions.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_ssh_utils.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_time_index_table_access_dimension_audit.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_time_index_table_dimension_queries.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_time_index_table_update_configuration.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_time_index_table_update_configuration_client.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_time_index_table_update_flow.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_time_index_table_update_hard_cut.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_update_runner_uid_runtime.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_update_statistics.py +0 -0
- {mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_update_uid_guards.py +0 -0
|
@@ -37,6 +37,10 @@ attached branch, and exact HEAD commit select the platform Project and
|
|
|
37
37
|
ProjectBranch. Never ask the user for a ProjectBranch UID, persist Project or
|
|
38
38
|
branch identity in `.env`, or use an environment variable as a branch selector.
|
|
39
39
|
The CLI resolves platform UIDs internally when a branch-owned API requires them.
|
|
40
|
+
The same rule applies inside deployed project-code runtimes: runtime credentials
|
|
41
|
+
authorize the backend target but never replace Git source discovery. A missing,
|
|
42
|
+
detached, or mismatched deployed checkout is a hard runtime-image error; do not
|
|
43
|
+
fall back to credential claims or injected ProjectBranch values.
|
|
40
44
|
|
|
41
45
|
## Inspect Before Changing State
|
|
42
46
|
|
|
@@ -155,7 +155,10 @@ backend prepares the exact initial image and later qualifying immutable
|
|
|
155
155
|
repository events may atomically promote another exact image. On an existing
|
|
156
156
|
Job, enabling or disabling it retains the current exact image. The backend owns
|
|
157
157
|
repository-event truth, policy evaluation, image preparation, and promotion;
|
|
158
|
-
project code and the SDK must not
|
|
158
|
+
project code and the SDK must not choose a Job's deployment branch or image.
|
|
159
|
+
This deployment-policy rule does not replace ADR-0037 runtime source discovery:
|
|
160
|
+
code executing inside the resulting project image still resolves and validates
|
|
161
|
+
its attached Git branch and exact commit.
|
|
159
162
|
|
|
160
163
|
For direct creation use:
|
|
161
164
|
|
|
@@ -3843,6 +3843,49 @@ class TimeIndexMetaTable(MetaTable):
|
|
|
3843
3843
|
)
|
|
3844
3844
|
|
|
3845
3845
|
|
|
3846
|
+
class ScheduledUpdateNode(BasePydanticModel):
|
|
3847
|
+
"""Strict read projection for an update node attached to a Scheduler.
|
|
3848
|
+
|
|
3849
|
+
Scheduler endpoints return a flattened polymorphic node projection with
|
|
3850
|
+
output-table identity. This is intentionally separate from
|
|
3851
|
+
:class:`TableUpdateNode`, which models the full update graph resource.
|
|
3852
|
+
"""
|
|
3853
|
+
|
|
3854
|
+
model_config = ConfigDict(extra="forbid")
|
|
3855
|
+
|
|
3856
|
+
uid: str = Field(..., description="Public uid of the scheduled update node")
|
|
3857
|
+
node_type: str = Field(..., min_length=1, description="Scheduled-node discriminator")
|
|
3858
|
+
update_hash: str | None = Field(
|
|
3859
|
+
...,
|
|
3860
|
+
max_length=63,
|
|
3861
|
+
description="Update identity when exposed by the concrete scheduled node",
|
|
3862
|
+
)
|
|
3863
|
+
build_configuration: dict[str, Any] | None = Field(
|
|
3864
|
+
...,
|
|
3865
|
+
description="Serialized build configuration exposed by the concrete node",
|
|
3866
|
+
)
|
|
3867
|
+
output_table_uid: str | None = Field(
|
|
3868
|
+
...,
|
|
3869
|
+
description="Canonical public uid of the scheduled node's output table",
|
|
3870
|
+
)
|
|
3871
|
+
output_table_type: str | None = Field(
|
|
3872
|
+
...,
|
|
3873
|
+
description="Output-table discriminator exposed by the backend",
|
|
3874
|
+
)
|
|
3875
|
+
output_table_physical_table_name: str | None = Field(
|
|
3876
|
+
...,
|
|
3877
|
+
description="Physical table name of the scheduled node's output",
|
|
3878
|
+
)
|
|
3879
|
+
output_table_identifier: str | None = Field(
|
|
3880
|
+
...,
|
|
3881
|
+
description="Published identifier of the scheduled node's output table",
|
|
3882
|
+
)
|
|
3883
|
+
output_table_data_source_uid: str | None = Field(
|
|
3884
|
+
...,
|
|
3885
|
+
description="Canonical data-source uid of the scheduled node's output table",
|
|
3886
|
+
)
|
|
3887
|
+
|
|
3888
|
+
|
|
3846
3889
|
class Scheduler(BasePydanticModel, BaseObjectOrm):
|
|
3847
3890
|
uid: str | None = Field(None, description="Public uid of this scheduler")
|
|
3848
3891
|
organization_environment_uid: str = Field(
|
|
@@ -3861,9 +3904,9 @@ class Scheduler(BasePydanticModel, BaseObjectOrm):
|
|
|
3861
3904
|
api_address: str | None
|
|
3862
3905
|
api_port: int | None
|
|
3863
3906
|
last_heart_beat: datetime.datetime | None = None
|
|
3864
|
-
pre_loads_in_tree: list[
|
|
3865
|
-
in_active_tree: list[
|
|
3866
|
-
schedules_to: list[
|
|
3907
|
+
pre_loads_in_tree: list[ScheduledUpdateNode] | None = None
|
|
3908
|
+
in_active_tree: list[ScheduledUpdateNode] | None = None
|
|
3909
|
+
schedules_to: list[ScheduledUpdateNode] | None = None
|
|
3867
3910
|
# for heartbeat
|
|
3868
3911
|
_stop_heart_beat: bool = False
|
|
3869
3912
|
_executor: object | None = None
|
|
@@ -5121,6 +5164,7 @@ __all__ = [
|
|
|
5121
5164
|
"MetaTableValidateContractRequest",
|
|
5122
5165
|
"PodDataSource",
|
|
5123
5166
|
"TimeIndexTableUpdateConfiguration",
|
|
5167
|
+
"ScheduledUpdateNode",
|
|
5124
5168
|
"Scheduler",
|
|
5125
5169
|
"SchedulerDoesNotExist",
|
|
5126
5170
|
"SessionDataSource",
|
|
@@ -6,7 +6,7 @@ import re
|
|
|
6
6
|
import subprocess
|
|
7
7
|
import threading
|
|
8
8
|
from collections.abc import Callable, Mapping
|
|
9
|
-
from dataclasses import dataclass
|
|
9
|
+
from dataclasses import dataclass, replace
|
|
10
10
|
from typing import Any, Literal
|
|
11
11
|
from urllib.parse import urlsplit
|
|
12
12
|
|
|
@@ -51,7 +51,7 @@ ProjectBranchContextLoader = Callable[[GitProjectSourceContext], Any]
|
|
|
51
51
|
|
|
52
52
|
@dataclass(frozen=True, slots=True)
|
|
53
53
|
class ProjectRuntimeContext:
|
|
54
|
-
source_context: GitProjectSourceContext
|
|
54
|
+
source_context: GitProjectSourceContext
|
|
55
55
|
project_uid: str | None
|
|
56
56
|
project_branch_uid: str | None
|
|
57
57
|
organization_environment_uid: str | None
|
|
@@ -61,40 +61,30 @@ class ProjectRuntimeContext:
|
|
|
61
61
|
project_branch: Any | None
|
|
62
62
|
detail: str = ""
|
|
63
63
|
context_source: ProjectRuntimeContextSource = "git"
|
|
64
|
-
runtime_repository_branch: str | None = None
|
|
65
64
|
|
|
66
65
|
@property
|
|
67
66
|
def is_authenticated_runtime(self) -> bool:
|
|
68
67
|
return self.context_source == "authenticated_runtime"
|
|
69
68
|
|
|
70
|
-
def _require_git_source_context(self) -> GitProjectSourceContext:
|
|
71
|
-
if self.source_context is None:
|
|
72
|
-
raise ProjectRuntimeContextError(
|
|
73
|
-
"Authenticated deployed runtime context has no local Git source."
|
|
74
|
-
)
|
|
75
|
-
return self.source_context
|
|
76
|
-
|
|
77
69
|
@property
|
|
78
70
|
def repository_root(self) -> pathlib.Path:
|
|
79
|
-
return self.
|
|
71
|
+
return self.source_context.repository_root
|
|
80
72
|
|
|
81
73
|
@property
|
|
82
74
|
def canonical_repository_identity(self) -> str:
|
|
83
|
-
return self.
|
|
75
|
+
return self.source_context.canonical_repository_identity
|
|
84
76
|
|
|
85
77
|
@property
|
|
86
78
|
def repository_branch(self) -> str:
|
|
87
|
-
|
|
88
|
-
return self.source_context.repository_branch
|
|
89
|
-
return str(self.runtime_repository_branch or "")
|
|
79
|
+
return self.source_context.repository_branch
|
|
90
80
|
|
|
91
81
|
@property
|
|
92
82
|
def repository_ref(self) -> str:
|
|
93
|
-
return self.
|
|
83
|
+
return self.source_context.repository_ref
|
|
94
84
|
|
|
95
85
|
@property
|
|
96
86
|
def commit_sha(self) -> str:
|
|
97
|
-
return self.
|
|
87
|
+
return self.source_context.commit_sha
|
|
98
88
|
|
|
99
89
|
|
|
100
90
|
@dataclass(frozen=True, slots=True)
|
|
@@ -436,12 +426,10 @@ def _build_project_runtime_context(
|
|
|
436
426
|
)
|
|
437
427
|
|
|
438
428
|
|
|
439
|
-
def
|
|
429
|
+
def _verify_authenticated_runtime_project_context(
|
|
430
|
+
context: ProjectRuntimeContext,
|
|
440
431
|
authenticated_context: Mapping[str, str],
|
|
441
432
|
) -> ProjectRuntimeContext:
|
|
442
|
-
from mainsequence.client.models_foundry import ProjectBranch
|
|
443
|
-
|
|
444
|
-
project_branch = ProjectBranch.get_by_uid(authenticated_context["project_branch_uid"])
|
|
445
433
|
expected_fields = {
|
|
446
434
|
"project_uid": authenticated_context["project_uid"],
|
|
447
435
|
"project_branch_uid": authenticated_context["project_branch_uid"],
|
|
@@ -449,40 +437,25 @@ def _build_authenticated_runtime_project_context(
|
|
|
449
437
|
"organization_environment_uid": authenticated_context["organization_environment_uid"],
|
|
450
438
|
}
|
|
451
439
|
observed_fields = {
|
|
452
|
-
"project_uid":
|
|
453
|
-
"project_branch_uid":
|
|
454
|
-
"repository_branch":
|
|
455
|
-
"organization_environment_uid":
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
),
|
|
440
|
+
"project_uid": str(context.project_uid or "").strip(),
|
|
441
|
+
"project_branch_uid": str(context.project_branch_uid or "").strip(),
|
|
442
|
+
"repository_branch": context.repository_branch,
|
|
443
|
+
"organization_environment_uid": str(
|
|
444
|
+
context.organization_environment_uid or ""
|
|
445
|
+
).strip(),
|
|
459
446
|
}
|
|
460
447
|
mismatched = sorted(
|
|
461
448
|
field for field, expected in expected_fields.items() if observed_fields[field] != expected
|
|
462
449
|
)
|
|
463
450
|
if mismatched:
|
|
464
451
|
raise ProjectRuntimeContextError(
|
|
465
|
-
"
|
|
466
|
-
"
|
|
452
|
+
"Git-resolved ProjectBranch does not match the authenticated runtime "
|
|
453
|
+
"target: " + ", ".join(mismatched) + "."
|
|
467
454
|
)
|
|
468
455
|
|
|
469
|
-
return
|
|
470
|
-
|
|
471
|
-
project_uid=expected_fields["project_uid"],
|
|
472
|
-
project_branch_uid=expected_fields["project_branch_uid"],
|
|
473
|
-
organization_environment_uid=expected_fields["organization_environment_uid"],
|
|
474
|
-
metatables_data_source=_object_value(
|
|
475
|
-
project_branch,
|
|
476
|
-
"metatables_data_source",
|
|
477
|
-
),
|
|
478
|
-
status="resolved",
|
|
479
|
-
process_id=os.getpid(),
|
|
480
|
-
project_branch=project_branch,
|
|
456
|
+
return replace(
|
|
457
|
+
context,
|
|
481
458
|
context_source="authenticated_runtime",
|
|
482
|
-
runtime_repository_branch=expected_fields["repository_branch"],
|
|
483
|
-
detail=(
|
|
484
|
-
"ProjectBranch context installed from an authenticated runtime credential exchange."
|
|
485
|
-
),
|
|
486
459
|
)
|
|
487
460
|
|
|
488
461
|
|
|
@@ -492,7 +465,7 @@ def get_project_runtime_context(
|
|
|
492
465
|
project_uid: str | None = None,
|
|
493
466
|
_project_branch_context_loader: ProjectBranchContextLoader | None = None,
|
|
494
467
|
) -> ProjectRuntimeContext:
|
|
495
|
-
"""Resolve and freeze
|
|
468
|
+
"""Resolve and freeze Git-native context, then verify any runtime target."""
|
|
496
469
|
|
|
497
470
|
global _STATE
|
|
498
471
|
|
|
@@ -518,14 +491,16 @@ def get_project_runtime_context(
|
|
|
518
491
|
|
|
519
492
|
try:
|
|
520
493
|
authenticated_context = _authenticated_runtime_context_for_process()
|
|
494
|
+
context = _build_project_runtime_context(
|
|
495
|
+
project_dir=normalized_project_dir,
|
|
496
|
+
project_branch_context_loader=(
|
|
497
|
+
_project_branch_context_loader or _default_project_branch_context_loader
|
|
498
|
+
),
|
|
499
|
+
)
|
|
521
500
|
if authenticated_context is not None:
|
|
522
|
-
context =
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
project_dir=normalized_project_dir,
|
|
526
|
-
project_branch_context_loader=(
|
|
527
|
-
_project_branch_context_loader or _default_project_branch_context_loader
|
|
528
|
-
),
|
|
501
|
+
context = _verify_authenticated_runtime_project_context(
|
|
502
|
+
context,
|
|
503
|
+
authenticated_context,
|
|
529
504
|
)
|
|
530
505
|
if project_uid and str(project_uid).strip() != context.project_uid:
|
|
531
506
|
raise ProjectRuntimeContextError(
|
|
@@ -550,8 +525,6 @@ def validate_project_source_context(
|
|
|
550
525
|
"""Fail if the current worktree no longer matches the frozen source context."""
|
|
551
526
|
|
|
552
527
|
resolved = context or get_project_runtime_context()
|
|
553
|
-
if resolved.is_authenticated_runtime:
|
|
554
|
-
return resolved
|
|
555
528
|
observed = _resolve_git_source_context(resolved.repository_root)
|
|
556
529
|
if observed != resolved.source_context:
|
|
557
530
|
raise ProjectSourceContextDriftError(
|
|
@@ -11163,6 +11163,82 @@ def test_project_current_json(cli_mod, runner, monkeypatch, tmp_path):
|
|
|
11163
11163
|
assert payload["sdk_status"]["status"] == "match"
|
|
11164
11164
|
|
|
11165
11165
|
|
|
11166
|
+
def test_project_current_debug_reports_authenticated_runtime_git_context(
|
|
11167
|
+
cli_mod,
|
|
11168
|
+
runner,
|
|
11169
|
+
monkeypatch,
|
|
11170
|
+
tmp_path,
|
|
11171
|
+
):
|
|
11172
|
+
from mainsequence.project_context import (
|
|
11173
|
+
GitProjectSourceContext,
|
|
11174
|
+
ProjectRuntimeContext,
|
|
11175
|
+
)
|
|
11176
|
+
|
|
11177
|
+
project_path = tmp_path / "org" / "projects" / "demo-project-uid-123"
|
|
11178
|
+
project_path.mkdir(parents=True, exist_ok=True)
|
|
11179
|
+
project_info = types.SimpleNamespace(
|
|
11180
|
+
path=str(project_path),
|
|
11181
|
+
folder="demo-project-uid-123",
|
|
11182
|
+
project_uid="project-uid-123",
|
|
11183
|
+
project_id="123",
|
|
11184
|
+
venv_path=None,
|
|
11185
|
+
python_version="3.13.7",
|
|
11186
|
+
)
|
|
11187
|
+
source_context = GitProjectSourceContext(
|
|
11188
|
+
repository_root=project_path,
|
|
11189
|
+
canonical_repository_identity="github.com/org/demo",
|
|
11190
|
+
repository_branch="development",
|
|
11191
|
+
repository_ref="refs/heads/development",
|
|
11192
|
+
commit_sha="b" * 40,
|
|
11193
|
+
)
|
|
11194
|
+
runtime_context = ProjectRuntimeContext(
|
|
11195
|
+
source_context=source_context,
|
|
11196
|
+
project_uid="project-uid-123",
|
|
11197
|
+
project_branch_uid="project-branch-uid-123",
|
|
11198
|
+
organization_environment_uid="environment-uid-123",
|
|
11199
|
+
metatables_data_source=None,
|
|
11200
|
+
status="resolved",
|
|
11201
|
+
process_id=os.getpid(),
|
|
11202
|
+
project_branch=types.SimpleNamespace(uid="project-branch-uid-123"),
|
|
11203
|
+
context_source="authenticated_runtime",
|
|
11204
|
+
)
|
|
11205
|
+
|
|
11206
|
+
monkeypatch.setattr(
|
|
11207
|
+
cli_mod.cfg,
|
|
11208
|
+
"get_config",
|
|
11209
|
+
lambda: {"mainsequence_path": str(tmp_path)},
|
|
11210
|
+
)
|
|
11211
|
+
monkeypatch.setattr(
|
|
11212
|
+
cli_mod,
|
|
11213
|
+
"detect_current_project",
|
|
11214
|
+
lambda workspaces, base: (
|
|
11215
|
+
project_info,
|
|
11216
|
+
types.SimpleNamespace(reason="detected", checks=[]),
|
|
11217
|
+
),
|
|
11218
|
+
)
|
|
11219
|
+
monkeypatch.setattr(cli_mod, "get_project_runtime_context", lambda **kwargs: runtime_context)
|
|
11220
|
+
monkeypatch.setattr(cli_mod, "read_local_sdk_version", lambda req: "7.0.2")
|
|
11221
|
+
monkeypatch.setattr(cli_mod, "fetch_latest_sdk_version", lambda: "7.0.2")
|
|
11222
|
+
|
|
11223
|
+
result = runner.invoke(cli_mod.app, ["project", "current", "--debug", "--json"])
|
|
11224
|
+
|
|
11225
|
+
assert result.exit_code == 0
|
|
11226
|
+
payload = json.loads(result.output)
|
|
11227
|
+
assert payload["project"] == {
|
|
11228
|
+
"path": str(project_path),
|
|
11229
|
+
"folder": "demo-project-uid-123",
|
|
11230
|
+
"project_uid": "project-uid-123",
|
|
11231
|
+
"git_branch": "development",
|
|
11232
|
+
"git_repository": "github.com/org/demo",
|
|
11233
|
+
"git_commit": "b" * 40,
|
|
11234
|
+
"project_branch_uid": "project-branch-uid-123",
|
|
11235
|
+
"project_branch_status": "resolved",
|
|
11236
|
+
"project_branch_error": None,
|
|
11237
|
+
"venv_path": None,
|
|
11238
|
+
"python_version": "3.13.7",
|
|
11239
|
+
}
|
|
11240
|
+
|
|
11241
|
+
|
|
11166
11242
|
def test_project_sdk_status(cli_mod, runner, monkeypatch, tmp_path):
|
|
11167
11243
|
target = tmp_path / "project"
|
|
11168
11244
|
target.mkdir(parents=True, exist_ok=True)
|
|
@@ -101,6 +101,23 @@ def _resolve(
|
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
def _install_authenticated_runtime_context(
|
|
105
|
+
*,
|
|
106
|
+
project_uid: str = PROJECT_UID,
|
|
107
|
+
project_branch_uid: str = PROJECT_BRANCH_UID,
|
|
108
|
+
repository_branch: str = "main",
|
|
109
|
+
organization_environment_uid: str = ENVIRONMENT_UID,
|
|
110
|
+
):
|
|
111
|
+
project_context._install_authenticated_runtime_project_context(
|
|
112
|
+
{
|
|
113
|
+
"project_uid": project_uid,
|
|
114
|
+
"project_branch_uid": project_branch_uid,
|
|
115
|
+
"repository_branch": repository_branch,
|
|
116
|
+
"organization_environment_uid": organization_environment_uid,
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
104
121
|
def _git(repo: pathlib.Path, *args: str) -> str:
|
|
105
122
|
result = subprocess.run(
|
|
106
123
|
["git", *args],
|
|
@@ -283,40 +300,22 @@ def test_identity_environment_variables_never_select_context(monkeypatch):
|
|
|
283
300
|
assert context.organization_environment_uid == ENVIRONMENT_UID
|
|
284
301
|
|
|
285
302
|
|
|
286
|
-
def
|
|
303
|
+
def test_authenticated_runtime_context_requires_matching_git(monkeypatch):
|
|
287
304
|
data_source = _data_source()
|
|
288
|
-
|
|
289
|
-
monkeypatch.setattr(
|
|
290
|
-
models_foundry.ProjectBranch,
|
|
291
|
-
"get_by_uid",
|
|
292
|
-
classmethod(lambda cls, uid: branch),
|
|
293
|
-
)
|
|
294
|
-
monkeypatch.setattr(
|
|
295
|
-
project_context,
|
|
296
|
-
"_resolve_git_source_context",
|
|
297
|
-
lambda path: pytest.fail("deployed runtime context must not inspect Git"),
|
|
298
|
-
)
|
|
299
|
-
project_context._install_authenticated_runtime_project_context(
|
|
300
|
-
{
|
|
301
|
-
"project_uid": PROJECT_UID,
|
|
302
|
-
"project_branch_uid": PROJECT_BRANCH_UID,
|
|
303
|
-
"repository_branch": "main",
|
|
304
|
-
"organization_environment_uid": ENVIRONMENT_UID,
|
|
305
|
-
}
|
|
306
|
-
)
|
|
305
|
+
_install_authenticated_runtime_context()
|
|
307
306
|
|
|
308
|
-
context =
|
|
307
|
+
context = _resolve(monkeypatch, data_source=data_source)
|
|
309
308
|
|
|
310
309
|
assert context.is_authenticated_runtime is True
|
|
311
|
-
assert context.source_context
|
|
310
|
+
assert context.source_context == _source()
|
|
312
311
|
assert context.project_uid == PROJECT_UID
|
|
313
312
|
assert context.project_branch_uid == PROJECT_BRANCH_UID
|
|
314
313
|
assert context.repository_branch == "main"
|
|
314
|
+
assert context.repository_ref == "refs/heads/main"
|
|
315
|
+
assert context.commit_sha == COMMIT_SHA
|
|
315
316
|
assert context.organization_environment_uid == ENVIRONMENT_UID
|
|
316
317
|
assert context.metatables_data_source is data_source
|
|
317
318
|
assert project_context.validate_project_source_context(context=context) is context
|
|
318
|
-
with pytest.raises(project_context.ProjectRuntimeContextError, match="no local Git"):
|
|
319
|
-
_ = context.commit_sha
|
|
320
319
|
|
|
321
320
|
assert models_metatables.MetaTable._sdk_owned_query_context("MetaTable.filter") == {}
|
|
322
321
|
assert metatables_core._with_current_metatable_project_context(
|
|
@@ -327,59 +326,102 @@ def test_authenticated_runtime_context_resolves_without_git(monkeypatch):
|
|
|
327
326
|
) == {"management_mode": "external_registered"}
|
|
328
327
|
|
|
329
328
|
|
|
330
|
-
def
|
|
329
|
+
def test_authenticated_runtime_context_has_no_missing_git_fallback(monkeypatch):
|
|
330
|
+
_install_authenticated_runtime_context()
|
|
331
|
+
|
|
332
|
+
def missing_git(path):
|
|
333
|
+
raise project_context.ProjectRuntimeContextError(
|
|
334
|
+
"missing deployed Git checkout"
|
|
335
|
+
)
|
|
336
|
+
|
|
331
337
|
monkeypatch.setattr(
|
|
332
|
-
|
|
333
|
-
"
|
|
334
|
-
|
|
335
|
-
)
|
|
336
|
-
project_context._install_authenticated_runtime_project_context(
|
|
337
|
-
{
|
|
338
|
-
"project_uid": PROJECT_UID,
|
|
339
|
-
"project_branch_uid": PROJECT_BRANCH_UID,
|
|
340
|
-
"repository_branch": "main",
|
|
341
|
-
"organization_environment_uid": ENVIRONMENT_UID,
|
|
342
|
-
}
|
|
338
|
+
project_context,
|
|
339
|
+
"_resolve_git_source_context",
|
|
340
|
+
missing_git,
|
|
343
341
|
)
|
|
344
342
|
|
|
345
|
-
with pytest.raises(project_context.ProjectRuntimeContextError, match="
|
|
343
|
+
with pytest.raises(project_context.ProjectRuntimeContextError, match="missing deployed Git"):
|
|
346
344
|
project_context.get_project_runtime_context()
|
|
347
345
|
|
|
348
346
|
|
|
347
|
+
@pytest.mark.parametrize(
|
|
348
|
+
("field", "value"),
|
|
349
|
+
[
|
|
350
|
+
("project_uid", "different-project"),
|
|
351
|
+
("project_branch_uid", "different-project-branch"),
|
|
352
|
+
("repository_branch", "development"),
|
|
353
|
+
("organization_environment_uid", "different-environment"),
|
|
354
|
+
],
|
|
355
|
+
)
|
|
356
|
+
def test_authenticated_runtime_context_rejects_git_resolved_target_mismatch(
|
|
357
|
+
monkeypatch,
|
|
358
|
+
field,
|
|
359
|
+
value,
|
|
360
|
+
):
|
|
361
|
+
runtime_context = {
|
|
362
|
+
"project_uid": PROJECT_UID,
|
|
363
|
+
"project_branch_uid": PROJECT_BRANCH_UID,
|
|
364
|
+
"repository_branch": "main",
|
|
365
|
+
"organization_environment_uid": ENVIRONMENT_UID,
|
|
366
|
+
}
|
|
367
|
+
runtime_context[field] = value
|
|
368
|
+
_install_authenticated_runtime_context(**runtime_context)
|
|
369
|
+
|
|
370
|
+
with pytest.raises(project_context.ProjectRuntimeContextError, match=field):
|
|
371
|
+
_resolve(monkeypatch)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def test_authenticated_runtime_context_detects_git_drift(monkeypatch):
|
|
375
|
+
_install_authenticated_runtime_context()
|
|
376
|
+
context = _resolve(monkeypatch)
|
|
377
|
+
monkeypatch.setattr(
|
|
378
|
+
project_context,
|
|
379
|
+
"_resolve_git_source_context",
|
|
380
|
+
lambda path: _source(branch="development", commit_sha="b" * 40),
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
with pytest.raises(project_context.ProjectSourceContextDriftError):
|
|
384
|
+
project_context.validate_project_source_context(context=context)
|
|
385
|
+
|
|
386
|
+
|
|
349
387
|
def test_configured_runtime_credential_exchanges_context_before_resolution(monkeypatch):
|
|
350
388
|
monkeypatch.setenv("MAINSEQUENCE_AUTH_MODE", "runtime_credential")
|
|
351
389
|
monkeypatch.setenv("MAINSEQUENCE_RUNTIME_CREDENTIAL_ID", "credential-id")
|
|
352
390
|
monkeypatch.setenv("MAINSEQUENCE_RUNTIME_CREDENTIAL_SECRET", "credential-secret")
|
|
353
|
-
|
|
354
|
-
models_foundry.ProjectBranch,
|
|
355
|
-
"get_by_uid",
|
|
356
|
-
classmethod(lambda cls, uid: _project_branch()),
|
|
357
|
-
)
|
|
391
|
+
events = []
|
|
358
392
|
monkeypatch.setattr(
|
|
359
393
|
project_context,
|
|
360
394
|
"_resolve_git_source_context",
|
|
361
|
-
lambda path:
|
|
395
|
+
lambda path: events.append("git") or _source(),
|
|
362
396
|
)
|
|
363
397
|
exchange_calls = []
|
|
364
398
|
|
|
365
399
|
def exchange(*, force=False, session=None):
|
|
400
|
+
events.append("exchange")
|
|
366
401
|
exchange_calls.append((force, session))
|
|
367
|
-
|
|
368
|
-
{
|
|
369
|
-
"project_uid": PROJECT_UID,
|
|
370
|
-
"project_branch_uid": PROJECT_BRANCH_UID,
|
|
371
|
-
"repository_branch": "main",
|
|
372
|
-
"organization_environment_uid": ENVIRONMENT_UID,
|
|
373
|
-
}
|
|
374
|
-
)
|
|
402
|
+
_install_authenticated_runtime_context()
|
|
375
403
|
return {"Authorization": "Bearer runtime-access"}
|
|
376
404
|
|
|
377
405
|
monkeypatch.setattr(client_utils.loaders, "refresh_headers", exchange)
|
|
378
406
|
|
|
379
|
-
|
|
407
|
+
def resolve_git_context(source):
|
|
408
|
+
events.append("backend")
|
|
409
|
+
return types.SimpleNamespace(
|
|
410
|
+
canonical_repository_identity=source.canonical_repository_identity,
|
|
411
|
+
repository_branch=source.repository_branch,
|
|
412
|
+
repository_ref=source.repository_ref,
|
|
413
|
+
commit_sha=source.commit_sha,
|
|
414
|
+
project_branch=_project_branch(branch=source.repository_branch),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
context = project_context.get_project_runtime_context(
|
|
418
|
+
_project_branch_context_loader=resolve_git_context,
|
|
419
|
+
)
|
|
380
420
|
|
|
381
421
|
assert context.is_authenticated_runtime is True
|
|
422
|
+
assert context.source_context == _source()
|
|
382
423
|
assert exchange_calls == [(True, None)]
|
|
424
|
+
assert events == ["exchange", "git", "backend"]
|
|
383
425
|
|
|
384
426
|
|
|
385
427
|
def test_unregistered_git_context_is_nonfatal_until_branch_context_is_required(monkeypatch):
|
|
@@ -532,20 +574,8 @@ class _ScopedCollection(CurrentProjectBranchCollectionMixin, BaseObjectOrm):
|
|
|
532
574
|
|
|
533
575
|
|
|
534
576
|
def test_authenticated_runtime_collection_omits_project_branch_selector(monkeypatch):
|
|
535
|
-
|
|
536
|
-
monkeypatch
|
|
537
|
-
models_foundry.ProjectBranch,
|
|
538
|
-
"get_by_uid",
|
|
539
|
-
classmethod(lambda cls, uid: branch),
|
|
540
|
-
)
|
|
541
|
-
project_context._install_authenticated_runtime_project_context(
|
|
542
|
-
{
|
|
543
|
-
"project_uid": PROJECT_UID,
|
|
544
|
-
"project_branch_uid": PROJECT_BRANCH_UID,
|
|
545
|
-
"repository_branch": "main",
|
|
546
|
-
"organization_environment_uid": ENVIRONMENT_UID,
|
|
547
|
-
}
|
|
548
|
-
)
|
|
577
|
+
_install_authenticated_runtime_context()
|
|
578
|
+
_resolve(monkeypatch)
|
|
549
579
|
captured = {}
|
|
550
580
|
|
|
551
581
|
class Response:
|
|
@@ -6,6 +6,28 @@ import mainsequence.client.metatables.core as core_mod
|
|
|
6
6
|
ENVIRONMENT_UID = "58218213-5e4e-43de-a5bd-6757f4e1c8f6"
|
|
7
7
|
SCHEDULER_UID = "00000000-0000-4000-8000-000000000001"
|
|
8
8
|
UPDATE_NODE_UID = "00000000-0000-4000-8000-000000000002"
|
|
9
|
+
OUTPUT_TABLE_UID = "00000000-0000-4000-8000-000000000003"
|
|
10
|
+
DATA_SOURCE_UID = "00000000-0000-4000-8000-000000000004"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _scheduled_node_payload():
|
|
14
|
+
return {
|
|
15
|
+
"uid": UPDATE_NODE_UID,
|
|
16
|
+
"node_type": "time_index_table_update",
|
|
17
|
+
"update_hash": "pricesupdater_0123456789abcdef0123456789abcdef",
|
|
18
|
+
"build_configuration": {
|
|
19
|
+
"configuration_schema_version": 2,
|
|
20
|
+
"table_updater_class_import_path": {
|
|
21
|
+
"module": "tutorial.prices",
|
|
22
|
+
"qualname": "PricesUpdater",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
"output_table_uid": OUTPUT_TABLE_UID,
|
|
26
|
+
"output_table_type": "time_index_meta_table",
|
|
27
|
+
"output_table_physical_table_name": "prices",
|
|
28
|
+
"output_table_identifier": "prices",
|
|
29
|
+
"output_table_data_source_uid": DATA_SOURCE_UID,
|
|
30
|
+
}
|
|
9
31
|
|
|
10
32
|
|
|
11
33
|
def _scheduler_payload():
|
|
@@ -21,9 +43,9 @@ def _scheduler_payload():
|
|
|
21
43
|
"api_address": None,
|
|
22
44
|
"api_port": None,
|
|
23
45
|
"last_heart_beat": None,
|
|
24
|
-
"pre_loads_in_tree": [],
|
|
25
|
-
"in_active_tree": [],
|
|
26
|
-
"schedules_to": [],
|
|
46
|
+
"pre_loads_in_tree": [_scheduled_node_payload()],
|
|
47
|
+
"in_active_tree": [_scheduled_node_payload()],
|
|
48
|
+
"schedules_to": [_scheduled_node_payload()],
|
|
27
49
|
}
|
|
28
50
|
|
|
29
51
|
|
|
@@ -51,6 +73,32 @@ def test_scheduler_strict_model_accepts_environment_projection():
|
|
|
51
73
|
)
|
|
52
74
|
|
|
53
75
|
|
|
76
|
+
def test_scheduler_strict_model_accepts_canonical_scheduled_node_projection():
|
|
77
|
+
scheduler = core_mod.Scheduler.model_validate(_scheduler_payload())
|
|
78
|
+
|
|
79
|
+
for relation_name in ("pre_loads_in_tree", "in_active_tree", "schedules_to"):
|
|
80
|
+
scheduled_nodes = getattr(scheduler, relation_name)
|
|
81
|
+
assert scheduled_nodes is not None
|
|
82
|
+
assert len(scheduled_nodes) == 1
|
|
83
|
+
scheduled_node = scheduled_nodes[0]
|
|
84
|
+
assert isinstance(scheduled_node, core_mod.ScheduledUpdateNode)
|
|
85
|
+
assert scheduled_node.uid == UPDATE_NODE_UID
|
|
86
|
+
assert scheduled_node.node_type == "time_index_table_update"
|
|
87
|
+
assert scheduled_node.output_table_uid == OUTPUT_TABLE_UID
|
|
88
|
+
assert scheduled_node.output_table_type == "time_index_meta_table"
|
|
89
|
+
assert scheduled_node.output_table_physical_table_name == "prices"
|
|
90
|
+
assert scheduled_node.output_table_identifier == "prices"
|
|
91
|
+
assert scheduled_node.output_table_data_source_uid == DATA_SOURCE_UID
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_scheduler_strict_model_rejects_unknown_scheduled_node_projection():
|
|
95
|
+
payload = _scheduler_payload()
|
|
96
|
+
payload["schedules_to"][0]["unexpected_node_projection"] = "rejected"
|
|
97
|
+
|
|
98
|
+
with pytest.raises(ValidationError, match="unexpected_node_projection"):
|
|
99
|
+
core_mod.Scheduler.model_validate(payload)
|
|
100
|
+
|
|
101
|
+
|
|
54
102
|
def test_scheduler_build_derives_environment_without_request_override(monkeypatch):
|
|
55
103
|
captured = {}
|
|
56
104
|
|
|
@@ -77,6 +125,8 @@ def test_scheduler_build_derives_environment_without_request_override(monkeypatc
|
|
|
77
125
|
assert request_body["update_node_uids"] == [UPDATE_NODE_UID]
|
|
78
126
|
assert captured["time_out"] == 11
|
|
79
127
|
assert scheduler.organization_environment_uid == ENVIRONMENT_UID
|
|
128
|
+
assert scheduler.schedules_to is not None
|
|
129
|
+
assert scheduler.schedules_to[0].output_table_uid == OUTPUT_TABLE_UID
|
|
80
130
|
|
|
81
131
|
|
|
82
132
|
def test_scheduler_get_for_update_node_accepts_environment_projection(monkeypatch):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/data_access/exploration/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/agent_scaffold/skills/sdk_project_execution/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/__init__.py
RENAMED
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/duckdb.py
RENAMED
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/local_paths.py
RENAMED
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/sqlite.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/templates/__init__.py
RENAMED
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/templates/env.py.mako
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/tests/fixtures/time_index_table_update_hash_v2_golden.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_time_index_table_access_dimension_audit.py
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_time_index_table_update_configuration.py
RENAMED
|
File without changes
|
{mainsequence-7.0.0 → mainsequence-7.0.2}/tests/test_time_index_table_update_configuration_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|