mainsequence 4.3.24__tar.gz → 4.3.25__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 (138) hide show
  1. {mainsequence-4.3.24/mainsequence.egg-info → mainsequence-4.3.25}/PKG-INFO +1 -1
  2. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/api.py +20 -12
  3. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/cli.py +2 -2
  4. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/agent_runtime_models.py +2 -0
  5. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/fastapi/auth.py +7 -3
  6. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/models_user.py +58 -11
  7. {mainsequence-4.3.24 → mainsequence-4.3.25/mainsequence.egg-info}/PKG-INFO +1 -1
  8. {mainsequence-4.3.24 → mainsequence-4.3.25}/pyproject.toml +1 -1
  9. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_cli.py +15 -10
  10. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_models_user_request_bound_auth.py +92 -0
  11. {mainsequence-4.3.24 → mainsequence-4.3.25}/LICENSE +0 -0
  12. {mainsequence-4.3.24 → mainsequence-4.3.25}/README.md +0 -0
  13. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/AGENTS.md +0 -0
  14. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/a2a_communication/SKILL.md +0 -0
  15. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  16. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/command_center/adapter_from_api/SKILL.md +0 -0
  17. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/command_center/api_mock_prototyping/SKILL.md +0 -0
  18. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/command_center/app_components/SKILL.md +0 -0
  19. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/command_center/connections/SKILL.md +0 -0
  20. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/command_center/workspace_analysis/SKILL.md +0 -0
  21. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/command_center/workspace_builder/SKILL.md +0 -0
  22. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/command_center/workspace_design/SKILL.md +0 -0
  23. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/dashboards/streamlit/SKILL.md +0 -0
  24. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  25. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  26. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  27. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  28. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  29. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/ms-markets/SKILL.md +0 -0
  30. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  31. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  32. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/project_builder/SKILL.md +0 -0
  33. {mainsequence-4.3.24 → mainsequence-4.3.25}/agent_scaffold/skills/project_to_agent/SKILL.md +0 -0
  34. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/__init__.py +0 -0
  35. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/__main__.py +0 -0
  36. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/bootstrap.py +0 -0
  37. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/__init__.py +0 -0
  38. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/browser_auth.py +0 -0
  39. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/config.py +0 -0
  40. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/docker_utils.py +0 -0
  41. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/doctor.py +0 -0
  42. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/local_ops.py +0 -0
  43. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/migrations.py +0 -0
  44. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/model_filters.py +0 -0
  45. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/project_status.py +0 -0
  46. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/pydantic_cli.py +0 -0
  47. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/sdk_utils.py +0 -0
  48. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/ssh_utils.py +0 -0
  49. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/cli/ui.py +0 -0
  50. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/__init__.py +0 -0
  51. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/base.py +0 -0
  52. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/client.py +0 -0
  53. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/command_center/__init__.py +0 -0
  54. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/command_center/app_component.py +0 -0
  55. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/command_center/connections.py +0 -0
  56. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/command_center/data_models.py +0 -0
  57. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/command_center/workspace.py +0 -0
  58. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/command_center/workspace_snapshot.py +0 -0
  59. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/compute_validation.py +0 -0
  60. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  61. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  62. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  63. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  64. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/dtype_codec.py +0 -0
  65. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/exceptions.py +0 -0
  66. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/fastapi/__init__.py +0 -0
  67. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/metatables/__init__.py +0 -0
  68. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/metatables/core.py +0 -0
  69. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/models_foundry.py +0 -0
  70. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/models_helpers.py +0 -0
  71. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/client/utils.py +0 -0
  72. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/defaults.py +0 -0
  73. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/instrumentation/__init__.py +0 -0
  74. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/instrumentation/utils.py +0 -0
  75. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/logconf.py +0 -0
  76. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/__init__.py +0 -0
  77. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/__main__.py +0 -0
  78. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  79. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  80. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  81. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  82. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  83. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  84. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  85. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  86. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  87. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  88. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/future_registry.py +0 -0
  89. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/hashing.py +0 -0
  90. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  91. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  92. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/migrations/env.py +0 -0
  93. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/migrations/provider.py +0 -0
  94. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/migrations/registry.py +0 -0
  95. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  96. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  97. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  98. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  99. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  100. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/schema_names.py +0 -0
  101. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  102. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence/runtime_flags.py +0 -0
  103. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence.egg-info/SOURCES.txt +0 -0
  104. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence.egg-info/dependency_links.txt +0 -0
  105. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence.egg-info/entry_points.txt +0 -0
  106. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence.egg-info/requires.txt +0 -0
  107. {mainsequence-4.3.24 → mainsequence-4.3.25}/mainsequence.egg-info/top_level.txt +0 -0
  108. {mainsequence-4.3.24 → mainsequence-4.3.25}/setup.cfg +0 -0
  109. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_auth_precedence.py +0 -0
  110. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_build_operations_hashing.py +0 -0
  111. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_cli_browser_auth.py +0 -0
  112. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_cli_migrations.py +0 -0
  113. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_client.py +0 -0
  114. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_command_center_app_component_models.py +0 -0
  115. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_command_center_data_models.py +0 -0
  116. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_command_center_models.py +0 -0
  117. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  118. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_data_node_storage_dimension_queries.py +0 -0
  119. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_data_node_update_flow.py +0 -0
  120. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_dependency_extras.py +0 -0
  121. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_duckdb_interface_dimensions.py +0 -0
  122. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_filter_normalization.py +0 -0
  123. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_instrumentation.py +0 -0
  124. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_logconf.py +0 -0
  125. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_meta_table_migrations.py +0 -0
  126. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_meta_tables_client_models.py +0 -0
  127. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  128. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_pod_project_resolution.py +0 -0
  129. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_project_batch_jobs_from_file.py +0 -0
  130. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_run_configuration.py +0 -0
  131. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_schema_names.py +0 -0
  132. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_secret_client_model.py +0 -0
  133. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_source_table_configuration.py +0 -0
  134. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_sqlite_interface_dimensions.py +0 -0
  135. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_update_runner_uid_runtime.py +0 -0
  136. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_update_statistics.py +0 -0
  137. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_update_uid_guards.py +0 -0
  138. {mainsequence-4.3.24 → mainsequence-4.3.25}/tests/test_workspace_snapshot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 4.3.24
3
+ Version: 4.3.25
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -567,8 +567,8 @@ def get_logged_user_details() -> dict[str, Any]:
567
567
  Return the authenticated user via SDK client `User.get_logged_user()`.
568
568
 
569
569
  The CLI does not naturally run inside a request context, so this bridge resolves
570
- the current user id from the authenticated API session and temporarily binds
571
- `X-User-ID` plus `Authorization` into
570
+ the current user UID from the authenticated API session and temporarily binds
571
+ `X-User-UID` plus `Authorization` into
572
572
  `mainsequence.client.models_user._CURRENT_AUTH_HEADERS`
573
573
  before calling the SDK method.
574
574
  """
@@ -598,14 +598,13 @@ def get_logged_user_details() -> dict[str, Any]:
598
598
  try:
599
599
  who = authed("GET", AUTH_PATHS["ping"])
600
600
  data = who.json() if who.ok else {}
601
- user_id = (
602
- data.get("id")
603
- or data.get("pk")
604
- or (data.get("user") or {}).get("id")
605
- or data.get("user_id")
601
+ user_uid = (
602
+ data.get("uid")
603
+ or (data.get("user") or {}).get("uid")
604
+ or data.get("user_uid")
606
605
  )
607
- if user_id in (None, ""):
608
- raise ApiError("Could not determine the authenticated user id.")
606
+ if user_uid in (None, ""):
607
+ raise ApiError("Could not determine the authenticated user uid.")
609
608
 
610
609
  os.environ["MAINSEQUENCE_AUTH_MODE"] = "jwt"
611
610
  os.environ["MAINSEQUENCE_ACCESS_TOKEN"] = access
@@ -637,17 +636,26 @@ def get_logged_user_details() -> dict[str, Any]:
637
636
  ClientUser.ROOT_URL = root_url
638
637
  headers_token = current_auth_headers.set(
639
638
  {
640
- "X-User-ID": str(user_id),
639
+ "X-User-UID": str(user_uid),
641
640
  "Authorization": f"Bearer {access}",
642
641
  }
643
642
  )
644
643
 
645
644
  user = ClientUser.get_logged_user()
646
645
  if isinstance(user, dict):
646
+ user.pop("id", None)
647
+ organization = user.get("organization")
648
+ if isinstance(organization, dict):
649
+ organization.pop("id", None)
647
650
  return user
648
651
  if hasattr(user, "model_dump"):
649
- return user.model_dump()
650
- return {"id": getattr(user, "id", None)}
652
+ payload = user.model_dump()
653
+ payload.pop("id", None)
654
+ organization = payload.get("organization")
655
+ if isinstance(organization, dict):
656
+ organization.pop("id", None)
657
+ return payload
658
+ return {"uid": getattr(user, "uid", None)}
651
659
 
652
660
  except Exception as e:
653
661
  err_name = type(e).__name__
@@ -2601,14 +2601,14 @@ def user_show():
2601
2601
 
2602
2602
  organization = user.get("organization")
2603
2603
  if isinstance(organization, dict):
2604
- organization_name = str(organization.get("name") or organization.get("id") or "-")
2604
+ organization_name = str(organization.get("name") or organization.get("uid") or "-")
2605
2605
  else:
2606
2606
  organization_name = str(organization or "-")
2607
2607
 
2608
2608
  print_kv(
2609
2609
  "MainSequence User",
2610
2610
  [
2611
- ("ID", str(user.get("id") or "-")),
2611
+ ("UID", str(user.get("uid") or "-")),
2612
2612
  ("Username", str(user.get("username") or "-")),
2613
2613
  ("Email", str(user.get("email") or "-")),
2614
2614
  ("Organization", organization_name),
@@ -340,6 +340,7 @@ class UserOrchestratorAgentService(BaseObjectOrm, BasePydanticModel):
340
340
  agent_uid: str | None = Field(None, description="Public UID of the resolved astro Agent.")
341
341
  user_uid: str | None = Field(None, description="Public UID of the owning user.")
342
342
  is_ready: bool = Field(False, description="Whether the service runtime is routable.")
343
+ automatic_deployment: bool = Field(False, description="Whether this coding-agent service is eligible for automatic deployment flows.")
343
344
  orchestrator_image_has_drift: bool = Field(False, description="Whether the orchestrator image is stale.")
344
345
  related_job: Any | None = Field(None, description="Backing job payload or UID when returned by the backend.")
345
346
  knative_service_runtime: Any | None = Field(None, description="Backing Knative service runtime payload.")
@@ -352,6 +353,7 @@ class UserProjectExecutorAgentService(BaseObjectOrm, BasePydanticModel):
352
353
  uid: str | None = Field(None, description="Public UID of the project executor service.")
353
354
  agent_uid: str | None = Field(None, description="Public UID of the resolved executor Agent.")
354
355
  is_ready: bool = Field(False, description="Whether the executor runtime is currently ready.")
356
+ automatic_deployment: bool = Field(False, description="Whether this coding-agent service is eligible for automatic deployment flows.")
355
357
  image_drift: dict[str, Any] | None = Field(None, description="Executor image drift status payload.")
356
358
  project: Any | None = Field(None, description="Owning project payload or UID when returned by the backend.")
357
359
  related_job: Any | None = Field(None, description="Backing job payload or UID when returned by the backend.")
@@ -60,10 +60,11 @@ class LoggedUserContextMiddleware:
60
60
  authorization_scheme = _authorization_scheme(request.headers)
61
61
  logger.info(
62
62
  "LoggedUserContextMiddleware request context method=%s path=%s "
63
- "x-user-id=%r x-username=%r x-resource-release-id=%r x-fastapi-id=%r "
63
+ "x-user-uid=%r x-user-id=%r x-username=%r x-resource-release-id=%r x-fastapi-id=%r "
64
64
  "authorization_present=%s authorization_scheme=%r",
65
65
  request.method,
66
66
  request.url.path,
67
+ request.headers.get("x-user-uid"),
67
68
  request.headers.get("x-user-id"),
68
69
  request.headers.get("x-username"),
69
70
  request.headers.get("x-resource-release-id"),
@@ -74,9 +75,10 @@ class LoggedUserContextMiddleware:
74
75
  bound_headers = current_auth_headers.get()
75
76
  logger.info(
76
77
  "LoggedUserContextMiddleware bound context current_auth_headers_is_none=%s "
77
- "header_keys=%s x-user-id=%r",
78
+ "header_keys=%s x-user-uid=%r x-user-id=%r",
78
79
  bound_headers is None,
79
80
  _header_keys(bound_headers),
81
+ _header_get(bound_headers, "x-user-uid"),
80
82
  _header_get(bound_headers, "x-user-id"),
81
83
  )
82
84
 
@@ -93,9 +95,11 @@ class LoggedUserContextMiddleware:
93
95
  raise
94
96
 
95
97
  request.state.user = user
98
+ request.state.user_uid = user.uid
96
99
  request.state.user_id = user.id
97
100
  logger.info(
98
- "LoggedUserContextMiddleware User.get_logged_user resolved user_id=%s for %s %s",
101
+ "LoggedUserContextMiddleware User.get_logged_user resolved user_uid=%s user_id=%s for %s %s",
102
+ request.state.user_uid,
99
103
  request.state.user_id,
100
104
  request.method,
101
105
  request.url.path,
@@ -67,6 +67,7 @@ def _logged_user_header_context(
67
67
  return {
68
68
  "header_source": header_source,
69
69
  "header_keys": [],
70
+ "x_user_uid": None,
70
71
  "x_user_id": None,
71
72
  "authorization_present": False,
72
73
  "authorization_scheme": None,
@@ -86,6 +87,12 @@ def _logged_user_header_context(
86
87
  return {
87
88
  "header_source": header_source,
88
89
  "header_keys": sorted(str(key) for key in headers.keys()),
90
+ "x_user_uid": (
91
+ normalized_headers.get("X-User-UID")
92
+ or normalized_headers.get("x-user-uid")
93
+ or normalized_headers.get("HTTP_X_USER_UID")
94
+ or normalized_headers.get("http_x_user_uid")
95
+ ),
89
96
  "x_user_id": (
90
97
  normalized_headers.get("X-User-ID")
91
98
  or normalized_headers.get("x-user-id")
@@ -1063,15 +1070,17 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1063
1070
  cls,
1064
1071
  *,
1065
1072
  normalized_headers: Mapping[str, Any],
1066
- user_id: int,
1073
+ user_uid: str | None = None,
1074
+ user_id: int | None = None,
1067
1075
  ):
1076
+ identity = user_uid or user_id
1068
1077
  username = str(
1069
1078
  normalized_headers.get("X-Username")
1070
1079
  or normalized_headers.get("x-username")
1071
1080
  or normalized_headers.get("X-User-Email")
1072
1081
  or normalized_headers.get("x-user-email")
1073
- or f"user-{user_id}"
1074
- ).strip() or f"user-{user_id}"
1082
+ or f"user-{identity}"
1083
+ ).strip() or f"user-{identity}"
1075
1084
  email = str(
1076
1085
  normalized_headers.get("X-User-Email")
1077
1086
  or normalized_headers.get("x-user-email")
@@ -1080,6 +1089,7 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1080
1089
 
1081
1090
  payload = {
1082
1091
  "id": user_id,
1092
+ "uid": user_uid,
1083
1093
  "username": username,
1084
1094
  "email": email,
1085
1095
  "date_joined": None,
@@ -1138,10 +1148,14 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1138
1148
  cls,
1139
1149
  *,
1140
1150
  headers: Mapping[str, Any],
1151
+ user_uid: str | None = None,
1141
1152
  user_id: int | None = None,
1142
1153
  ) -> User:
1143
1154
  outbound_headers = _build_request_bound_outbound_headers(headers)
1144
- if user_id is None:
1155
+ if user_uid:
1156
+ url = f"{cls.get_object_url()}/get_user_details/"
1157
+ params = None
1158
+ elif user_id is None:
1145
1159
  url = f"{cls.get_object_url()}/get_user_details/"
1146
1160
  params = None
1147
1161
  else:
@@ -1170,8 +1184,10 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1170
1184
  Use this when code is running with request-scoped identity context, such
1171
1185
  as FastAPI middleware, Streamlit, or code that explicitly binds
1172
1186
  `_CURRENT_AUTH_HEADERS`. This method first uses the bound request/user
1173
- context and only falls back to `get_authenticated_user_details()` when
1174
- request headers are present with Bearer auth but no `X-User-ID`.
1187
+ context and only falls back to the current-user details endpoint when
1188
+ request headers are present with Bearer auth but no request-bound user
1189
+ identity header. `X-User-UID` is the public identity header; `X-User-ID`
1190
+ is kept only for legacy request-bound callers.
1175
1191
 
1176
1192
  For standalone authenticated CLI or script code that is not request-bound,
1177
1193
  prefer `get_authenticated_user_details()`.
@@ -1220,6 +1236,12 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1220
1236
  and str(authorization_value).split(" ", 1)[0].lower() == "bearer"
1221
1237
  )
1222
1238
 
1239
+ user_uid_raw = (
1240
+ normalized_headers.get("X-User-UID")
1241
+ or normalized_headers.get("x-user-uid")
1242
+ or normalized_headers.get("HTTP_X_USER_UID")
1243
+ or normalized_headers.get("http_x_user_uid")
1244
+ )
1223
1245
  user_id_raw = (
1224
1246
  normalized_headers.get("X-User-ID")
1225
1247
  or normalized_headers.get("x-user-id")
@@ -1227,6 +1249,28 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1227
1249
  or normalized_headers.get("http_x_user_id")
1228
1250
  )
1229
1251
 
1252
+ if user_uid_raw not in (None, ""):
1253
+ user_uid = str(user_uid_raw).strip()
1254
+ if not has_bearer_authorization:
1255
+ user = cls._build_request_bound_identity_user(
1256
+ normalized_headers=normalized_headers,
1257
+ user_uid=user_uid,
1258
+ )
1259
+ _CURRENT_USER.set(user)
1260
+ logger.info(
1261
+ "User.get_logged_user resolved user_uid=%s via request identity headers without backend auth",
1262
+ user.uid,
1263
+ )
1264
+ return user
1265
+
1266
+ user = cls._get_request_bound_user(headers=headers, user_uid=user_uid)
1267
+ _CURRENT_USER.set(user)
1268
+ logger.info(
1269
+ "User.get_logged_user resolved user_uid=%s via X-User-UID header",
1270
+ user.uid,
1271
+ )
1272
+ return user
1273
+
1230
1274
  if user_id_raw in (None, ""):
1231
1275
  if has_bearer_authorization:
1232
1276
  outgoing_authorization = None
@@ -1256,10 +1300,11 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1256
1300
  context = _logged_user_header_context(headers, header_source=header_source)
1257
1301
  logger.exception(
1258
1302
  "User.get_logged_user failed during bearer fallback; "
1259
- "header_source=%s header_keys=%s X-User-ID=%r "
1303
+ "header_source=%s header_keys=%s X-User-UID=%r X-User-ID=%r "
1260
1304
  "authorization_present=%s authorization_scheme=%r",
1261
1305
  context["header_source"],
1262
1306
  context["header_keys"],
1307
+ context["x_user_uid"],
1263
1308
  context["x_user_id"],
1264
1309
  context["authorization_present"],
1265
1310
  context["authorization_scheme"],
@@ -1274,16 +1319,17 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1274
1319
 
1275
1320
  context = _logged_user_header_context(headers, header_source=header_source)
1276
1321
  logger.error(
1277
- "User.get_logged_user failed: missing X-User-ID in request headers; "
1278
- "header_source=%s header_keys=%s X-User-ID=%r "
1322
+ "User.get_logged_user failed: missing X-User-UID or X-User-ID in request headers; "
1323
+ "header_source=%s header_keys=%s X-User-UID=%r X-User-ID=%r "
1279
1324
  "authorization_present=%s authorization_scheme=%r",
1280
1325
  context["header_source"],
1281
1326
  context["header_keys"],
1327
+ context["x_user_uid"],
1282
1328
  context["x_user_id"],
1283
1329
  context["authorization_present"],
1284
1330
  context["authorization_scheme"],
1285
1331
  )
1286
- raise RuntimeError("Missing X-User-ID in request headers.")
1332
+ raise RuntimeError("Missing X-User-UID or X-User-ID in request headers.")
1287
1333
 
1288
1334
  try:
1289
1335
  user_id = int(str(user_id_raw).strip())
@@ -1291,10 +1337,11 @@ class User(UserApiBaseObjectOrm, BasePydanticModel):
1291
1337
  context = _logged_user_header_context(headers, header_source=header_source)
1292
1338
  logger.exception(
1293
1339
  "User.get_logged_user failed: invalid X-User-ID value; "
1294
- "header_source=%s header_keys=%s X-User-ID=%r "
1340
+ "header_source=%s header_keys=%s X-User-UID=%r X-User-ID=%r "
1295
1341
  "authorization_present=%s authorization_scheme=%r",
1296
1342
  context["header_source"],
1297
1343
  context["header_keys"],
1344
+ context["x_user_uid"],
1298
1345
  context["x_user_id"],
1299
1346
  context["authorization_present"],
1300
1347
  context["authorization_scheme"],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 4.3.24
3
+ Version: 4.3.25
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License: MainSequence GmbH SDK License Agreement
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "4.3.24"
7
+ version = "4.3.25"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -96,10 +96,10 @@ def test_user_show(cli_mod, runner, monkeypatch):
96
96
  cli_mod,
97
97
  "get_logged_user_details",
98
98
  lambda: {
99
- "id": 7,
99
+ "uid": "user-uid-7",
100
100
  "username": "jose",
101
101
  "email": "jose@main-sequence.io",
102
- "organization": {"id": 2, "name": "Main Sequence"},
102
+ "organization": {"uid": "org-uid-2", "name": "Main Sequence"},
103
103
  "is_active": True,
104
104
  "is_verified": True,
105
105
  "mfa_enabled": False,
@@ -111,6 +111,7 @@ def test_user_show(cli_mod, runner, monkeypatch):
111
111
  result = runner.invoke(cli_mod.app, ["user"])
112
112
  assert result.exit_code == 0
113
113
  assert "MainSequence User" in result.output
114
+ assert "user-uid-7" in result.output
114
115
  assert "jose" in result.output
115
116
  assert "jose@main-sequence.io" in result.output
116
117
  assert "Main Sequence" in result.output
@@ -121,17 +122,18 @@ def test_user_show_json(cli_mod, runner, monkeypatch):
121
122
  cli_mod,
122
123
  "get_logged_user_details",
123
124
  lambda: {
124
- "id": 7,
125
+ "uid": "user-uid-7",
125
126
  "username": "jose",
126
127
  "email": "jose@main-sequence.io",
127
- "organization": {"id": 2, "name": "Main Sequence"},
128
+ "organization": {"uid": "org-uid-2", "name": "Main Sequence"},
128
129
  },
129
130
  )
130
131
 
131
132
  result = runner.invoke(cli_mod.app, ["user", "--json"])
132
133
  assert result.exit_code == 0
133
134
  payload = json.loads(result.output)
134
- assert payload["id"] == 7
135
+ assert "id" not in payload
136
+ assert payload["uid"] == "user-uid-7"
135
137
  assert payload["username"] == "jose"
136
138
  assert payload["organization"]["name"] == "Main Sequence"
137
139
 
@@ -5668,7 +5670,7 @@ def test_get_logged_user_details_uses_client_model(cli_mod, monkeypatch):
5668
5670
  "authed",
5669
5671
  lambda method, api_path, body=None: types.SimpleNamespace(
5670
5672
  ok=True,
5671
- json=lambda: {"id": 7},
5673
+ json=lambda: {"uid": "user-uid-7"},
5672
5674
  ),
5673
5675
  )
5674
5676
 
@@ -5722,10 +5724,10 @@ def test_get_logged_user_details_uses_client_model(cli_mod, monkeypatch):
5722
5724
  captured["headers_seen"] = fake_headers.current
5723
5725
  return types.SimpleNamespace(
5724
5726
  model_dump=lambda: {
5725
- "id": 7,
5727
+ "uid": "user-uid-7",
5726
5728
  "username": "jose",
5727
5729
  "email": "jose@main-sequence.io",
5728
- "organization": {"id": 2, "name": "Main Sequence"},
5730
+ "organization": {"uid": "org-uid-2", "name": "Main Sequence"},
5729
5731
  }
5730
5732
  )
5731
5733
 
@@ -5745,9 +5747,12 @@ def test_get_logged_user_details_uses_client_model(cli_mod, monkeypatch):
5745
5747
  assert fake_utils.API_ENDPOINT == "https://backend.test/orm/api"
5746
5748
  assert fake_utils.AUTH_ENDPOINT == "https://backend.test"
5747
5749
  assert captured["jwt"] == ("acc", "ref")
5748
- assert captured["headers_set"] == {"X-User-ID": "7", "Authorization": "Bearer acc"}
5749
- assert captured["headers_seen"] == {"X-User-ID": "7", "Authorization": "Bearer acc"}
5750
+ assert captured["headers_set"] == {"X-User-UID": "user-uid-7", "Authorization": "Bearer acc"}
5751
+ assert captured["headers_seen"] == {"X-User-UID": "user-uid-7", "Authorization": "Bearer acc"}
5750
5752
  assert captured["headers_reset"] == "token"
5753
+ assert "id" not in out
5754
+ assert "id" not in out["organization"]
5755
+ assert out["uid"] == "user-uid-7"
5751
5756
  assert out["username"] == "jose"
5752
5757
 
5753
5758
 
@@ -73,6 +73,62 @@ def test_get_logged_user_uses_request_bound_headers_for_user_lookup(monkeypatch)
73
73
  assert "Host" not in captured["headers"]
74
74
 
75
75
 
76
+ def test_get_logged_user_uses_request_bound_uid_header_for_user_lookup(monkeypatch):
77
+ monkeypatch.delenv("MAINSEQUENCE_ACCESS_TOKEN", raising=False)
78
+ monkeypatch.delenv("MAINSEQUENCE_REFRESH_TOKEN", raising=False)
79
+
80
+ captured: dict[str, object] = {}
81
+
82
+ class _FakeSession:
83
+ def get(self, url, *, headers=None, params=None, timeout=None):
84
+ captured["url"] = url
85
+ captured["headers"] = headers
86
+ captured["params"] = params
87
+ captured["timeout"] = timeout
88
+ return _FakeResponse(
89
+ {
90
+ "uid": "user-uid-4",
91
+ "username": "jose",
92
+ "email": "jose@main-sequence.io",
93
+ "date_joined": "2026-01-01T00:00:00Z",
94
+ "is_active": True,
95
+ "api_request_limit": 10000,
96
+ "mfa_enabled": False,
97
+ "groups": [],
98
+ "user_permissions": [],
99
+ "organization_teams": [],
100
+ }
101
+ )
102
+
103
+ monkeypatch.setattr(
104
+ models_user_mod.User,
105
+ "build_session",
106
+ classmethod(lambda cls: _FakeSession()),
107
+ )
108
+
109
+ auth_token = models_user_mod._CURRENT_AUTH_HEADERS.set(
110
+ {
111
+ "X-User-UID": "user-uid-4",
112
+ "Authorization": "Bearer inbound-token",
113
+ "Cookie": "sessionid=abc",
114
+ "Host": "frontend.test",
115
+ }
116
+ )
117
+ user_token = models_user_mod._CURRENT_USER.set(None)
118
+ try:
119
+ user = models_user_mod.User.get_logged_user()
120
+ finally:
121
+ models_user_mod._CURRENT_USER.reset(user_token)
122
+ models_user_mod._CURRENT_AUTH_HEADERS.reset(auth_token)
123
+
124
+ assert user.uid == "user-uid-4"
125
+ assert str(captured["url"]).endswith("/user/api/user/get_user_details/")
126
+ assert captured["params"] is None
127
+ assert captured["headers"]["Authorization"] == "Bearer inbound-token"
128
+ assert captured["headers"]["Cookie"] == "sessionid=abc"
129
+ assert "Host" not in captured["headers"]
130
+
131
+
76
132
  def test_get_logged_user_returns_header_identity_without_backend_auth(monkeypatch):
77
133
  monkeypatch.delenv("MAINSEQUENCE_ACCESS_TOKEN", raising=False)
78
134
  monkeypatch.delenv("MAINSEQUENCE_REFRESH_TOKEN", raising=False)
@@ -108,6 +164,42 @@ def test_get_logged_user_returns_header_identity_without_backend_auth(monkeypatc
108
164
  assert user.api_request_limit is None
109
165
 
110
166
 
167
+ def test_get_logged_user_returns_uid_header_identity_without_backend_auth(monkeypatch):
168
+ monkeypatch.delenv("MAINSEQUENCE_ACCESS_TOKEN", raising=False)
169
+ monkeypatch.delenv("MAINSEQUENCE_REFRESH_TOKEN", raising=False)
170
+
171
+ class _FakeSession:
172
+ def get(self, url, *, headers=None, params=None, timeout=None):
173
+ raise AssertionError("header-only identity should not trigger backend lookup")
174
+
175
+ monkeypatch.setattr(
176
+ models_user_mod.User,
177
+ "build_session",
178
+ classmethod(lambda cls: _FakeSession()),
179
+ )
180
+
181
+ auth_token = models_user_mod._CURRENT_AUTH_HEADERS.set(
182
+ {
183
+ "X-User-UID": "user-uid-4",
184
+ "X-Username": "dashboard-user",
185
+ "X-Dashboard-ID": "dashboard-7",
186
+ }
187
+ )
188
+ user_token = models_user_mod._CURRENT_USER.set(None)
189
+ try:
190
+ user = models_user_mod.User.get_logged_user()
191
+ finally:
192
+ models_user_mod._CURRENT_USER.reset(user_token)
193
+ models_user_mod._CURRENT_AUTH_HEADERS.reset(auth_token)
194
+
195
+ assert user.uid == "user-uid-4"
196
+ assert user.id is None
197
+ assert user.username == "dashboard-user"
198
+ assert user.email == "dashboard-user"
199
+ assert user.date_joined is None
200
+ assert user.api_request_limit is None
201
+
202
+
111
203
  def test_get_logged_user_bearer_fallback_uses_request_bound_headers(monkeypatch):
112
204
  monkeypatch.delenv("MAINSEQUENCE_ACCESS_TOKEN", raising=False)
113
205
  monkeypatch.delenv("MAINSEQUENCE_REFRESH_TOKEN", raising=False)
File without changes
File without changes
File without changes