mainsequence 7.0.1__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.
Files changed (137) hide show
  1. {mainsequence-7.0.1/mainsequence.egg-info → mainsequence-7.0.2}/PKG-INFO +1 -1
  2. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +4 -0
  3. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +4 -1
  4. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/project_context.py +29 -56
  5. {mainsequence-7.0.1 → mainsequence-7.0.2/mainsequence.egg-info}/PKG-INFO +1 -1
  6. {mainsequence-7.0.1 → mainsequence-7.0.2}/pyproject.toml +1 -1
  7. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_cli.py +76 -0
  8. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_project_runtime_context.py +96 -66
  9. {mainsequence-7.0.1 → mainsequence-7.0.2}/LICENSE +0 -0
  10. {mainsequence-7.0.1 → mainsequence-7.0.2}/MANIFEST.in +0 -0
  11. {mainsequence-7.0.1 → mainsequence-7.0.2}/README.md +0 -0
  12. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/AGENTS.md +0 -0
  13. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  14. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  15. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  16. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  17. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  18. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
  19. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  20. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  21. {mainsequence-7.0.1 → mainsequence-7.0.2}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
  22. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/__init__.py +0 -0
  23. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/__main__.py +0 -0
  24. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/bootstrap.py +0 -0
  25. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/__init__.py +0 -0
  26. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/api.py +0 -0
  27. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/browser_auth.py +0 -0
  28. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/cli.py +0 -0
  29. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/config.py +0 -0
  30. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/docker_utils.py +0 -0
  31. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/doctor.py +0 -0
  32. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/local_ops.py +0 -0
  33. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/migrations.py +0 -0
  34. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/model_filters.py +0 -0
  35. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/project_status.py +0 -0
  36. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/pydantic_cli.py +0 -0
  37. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/sdk_utils.py +0 -0
  38. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/ssh_utils.py +0 -0
  39. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/cli/ui.py +0 -0
  40. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/__init__.py +0 -0
  41. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/agent_runtime_models.py +0 -0
  42. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/base.py +0 -0
  43. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/client.py +0 -0
  44. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/compute_validation.py +0 -0
  45. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  46. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  47. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  48. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  49. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/dtype_codec.py +0 -0
  50. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/exceptions.py +0 -0
  51. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/metatables/__init__.py +0 -0
  52. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/metatables/core.py +0 -0
  53. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/models_foundry.py +0 -0
  54. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/models_helpers.py +0 -0
  55. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/models_user.py +0 -0
  56. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/observability.py +0 -0
  57. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/client/utils.py +0 -0
  58. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/defaults.py +0 -0
  59. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/instrumentation/__init__.py +0 -0
  60. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/instrumentation/utils.py +0 -0
  61. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/logconf.py +0 -0
  62. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/__init__.py +0 -0
  63. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/__main__.py +0 -0
  64. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  65. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  66. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/future_registry.py +0 -0
  67. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/hashing.py +0 -0
  68. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  69. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  70. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/env.py +0 -0
  71. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/provider.py +0 -0
  72. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/registry.py +0 -0
  73. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  74. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  75. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  76. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  77. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  78. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/schema_names.py +0 -0
  79. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  80. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
  81. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
  82. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
  83. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
  84. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -0
  85. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
  86. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/runner.py +0 -0
  87. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -0
  88. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
  89. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/project_skills.py +0 -0
  90. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/runtime_flags.py +0 -0
  91. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence/scaffold_skills.py +0 -0
  92. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence.egg-info/SOURCES.txt +0 -0
  93. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence.egg-info/dependency_links.txt +0 -0
  94. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence.egg-info/entry_points.txt +0 -0
  95. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence.egg-info/requires.txt +0 -0
  96. {mainsequence-7.0.1 → mainsequence-7.0.2}/mainsequence.egg-info/top_level.txt +0 -0
  97. {mainsequence-7.0.1 → mainsequence-7.0.2}/setup.cfg +0 -0
  98. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
  99. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_auth_precedence.py +0 -0
  100. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_canonical_api_route_contract.py +0 -0
  101. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_cli_browser_auth.py +0 -0
  102. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_cli_migrations.py +0 -0
  103. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_client.py +0 -0
  104. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_dependency_extras.py +0 -0
  105. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_duckdb_interface_dimensions.py +0 -0
  106. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_environment_resource_context.py +0 -0
  107. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_environment_response_contracts.py +0 -0
  108. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_filter_normalization.py +0 -0
  109. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_instrumentation.py +0 -0
  110. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_local_ops.py +0 -0
  111. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_logconf.py +0 -0
  112. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_meta_table_migrations.py +0 -0
  113. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_meta_tables_client_models.py +0 -0
  114. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  115. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_models_user_request_bound_auth.py +0 -0
  116. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_notification_uid_contract.py +0 -0
  117. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_observability_client.py +0 -0
  118. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_pod_project_resolution.py +0 -0
  119. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_project_batch_jobs_from_file.py +0 -0
  120. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_project_branch_contract.py +0 -0
  121. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_project_skills.py +0 -0
  122. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_scaffold_skills.py +0 -0
  123. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_scheduler_client_model.py +0 -0
  124. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_schema_names.py +0 -0
  125. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_secret_client_model.py +0 -0
  126. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_source_table_configuration.py +0 -0
  127. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_sqlite_interface_dimensions.py +0 -0
  128. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_ssh_utils.py +0 -0
  129. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_time_index_table_access_dimension_audit.py +0 -0
  130. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_time_index_table_dimension_queries.py +0 -0
  131. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_time_index_table_update_configuration.py +0 -0
  132. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_time_index_table_update_configuration_client.py +0 -0
  133. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_time_index_table_update_flow.py +0 -0
  134. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_time_index_table_update_hard_cut.py +0 -0
  135. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_update_runner_uid_runtime.py +0 -0
  136. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_update_statistics.py +0 -0
  137. {mainsequence-7.0.1 → mainsequence-7.0.2}/tests/test_update_uid_guards.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 7.0.1
3
+ Version: 7.0.2
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -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 inspect Git or choose a runtime branch.
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
 
@@ -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 | None
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._require_git_source_context().repository_root
71
+ return self.source_context.repository_root
80
72
 
81
73
  @property
82
74
  def canonical_repository_identity(self) -> str:
83
- return self._require_git_source_context().canonical_repository_identity
75
+ return self.source_context.canonical_repository_identity
84
76
 
85
77
  @property
86
78
  def repository_branch(self) -> str:
87
- if self.source_context is not None:
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._require_git_source_context().repository_ref
83
+ return self.source_context.repository_ref
94
84
 
95
85
  @property
96
86
  def commit_sha(self) -> str:
97
- return self._require_git_source_context().commit_sha
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 _build_authenticated_runtime_project_context(
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": _normalized_value(project_branch, "project_uid"),
453
- "project_branch_uid": _normalized_value(project_branch, "uid"),
454
- "repository_branch": _normalized_value(project_branch, "repository_branch"),
455
- "organization_environment_uid": _normalized_value(
456
- project_branch,
457
- "organization_environment_uid",
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
- "Authenticated runtime project context does not match the target "
466
- "ProjectBranch: " + ", ".join(mismatched) + "."
452
+ "Git-resolved ProjectBranch does not match the authenticated runtime "
453
+ "target: " + ", ".join(mismatched) + "."
467
454
  )
468
455
 
469
- return ProjectRuntimeContext(
470
- source_context=None,
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 authenticated runtime or local Git context once per process."""
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 = _build_authenticated_runtime_project_context(authenticated_context)
523
- else:
524
- context = _build_project_runtime_context(
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(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 7.0.1
3
+ Version: 7.0.2
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "7.0.1"
7
+ version = "7.0.2"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -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 test_authenticated_runtime_context_resolves_without_git(monkeypatch):
303
+ def test_authenticated_runtime_context_requires_matching_git(monkeypatch):
287
304
  data_source = _data_source()
288
- branch = _project_branch(data_source=data_source)
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 = project_context.get_project_runtime_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 is None
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 test_authenticated_runtime_context_rejects_backend_branch_mismatch(monkeypatch):
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
- models_foundry.ProjectBranch,
333
- "get_by_uid",
334
- classmethod(lambda cls, uid: _project_branch(branch="development")),
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="repository_branch"):
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
- monkeypatch.setattr(
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: pytest.fail("credential exchange must precede Git resolution"),
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
- project_context._install_authenticated_runtime_project_context(
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
- context = project_context.get_project_runtime_context()
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
- branch = _project_branch()
536
- monkeypatch.setattr(
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:
File without changes
File without changes
File without changes
File without changes