mainsequence 6.0.35__tar.gz → 6.0.37__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 (125) hide show
  1. {mainsequence-6.0.35 → mainsequence-6.0.37}/PKG-INFO +1 -1
  2. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/api.py +2 -2
  3. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/cli.py +3 -4
  4. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/agent_runtime_models.py +11 -2
  5. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/models_foundry.py +15 -11
  6. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence.egg-info/PKG-INFO +1 -1
  7. {mainsequence-6.0.35 → mainsequence-6.0.37}/pyproject.toml +1 -1
  8. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_cli.py +45 -2
  9. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_project_branch_contract.py +30 -0
  10. {mainsequence-6.0.35 → mainsequence-6.0.37}/LICENSE +0 -0
  11. {mainsequence-6.0.35 → mainsequence-6.0.37}/README.md +0 -0
  12. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/AGENTS.md +0 -0
  13. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  14. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  15. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  16. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/data_publishing/data_nodes/SKILL.md +0 -0
  17. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  18. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  19. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  20. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/maintenance/project-maintenance/SKILL.md +0 -0
  21. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  22. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  23. {mainsequence-6.0.35 → mainsequence-6.0.37}/agent_scaffold/skills/sdk_project_execution/SKILL.md +0 -0
  24. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/__init__.py +0 -0
  25. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/__main__.py +0 -0
  26. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/bootstrap.py +0 -0
  27. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/__init__.py +0 -0
  28. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/browser_auth.py +0 -0
  29. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/config.py +0 -0
  30. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/docker_utils.py +0 -0
  31. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/doctor.py +0 -0
  32. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/local_ops.py +0 -0
  33. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/migrations.py +0 -0
  34. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/model_filters.py +0 -0
  35. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/project_status.py +0 -0
  36. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/pydantic_cli.py +0 -0
  37. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/sdk_utils.py +0 -0
  38. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/ssh_utils.py +0 -0
  39. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/cli/ui.py +0 -0
  40. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/__init__.py +0 -0
  41. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/base.py +0 -0
  42. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/client.py +0 -0
  43. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/compute_validation.py +0 -0
  44. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  45. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  46. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  47. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  48. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/dtype_codec.py +0 -0
  49. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/exceptions.py +0 -0
  50. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/metatables/__init__.py +0 -0
  51. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/metatables/core.py +0 -0
  52. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/models_helpers.py +0 -0
  53. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/models_user.py +0 -0
  54. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/client/utils.py +0 -0
  55. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/defaults.py +0 -0
  56. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/instrumentation/__init__.py +0 -0
  57. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/instrumentation/utils.py +0 -0
  58. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/logconf.py +0 -0
  59. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/__init__.py +0 -0
  60. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/__main__.py +0 -0
  61. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  62. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  63. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/data_nodes/__init__.py +0 -0
  64. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/data_nodes/build_operations.py +0 -0
  65. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/data_nodes/data_nodes.py +0 -0
  66. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/data_nodes/models.py +0 -0
  67. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/data_nodes/namespacing.py +0 -0
  68. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/data_nodes/persist_managers.py +0 -0
  69. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/data_nodes/run_operations.py +0 -0
  70. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/data_nodes/utils.py +0 -0
  71. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/future_registry.py +0 -0
  72. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/hashing.py +0 -0
  73. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  74. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  75. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/migrations/env.py +0 -0
  76. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/migrations/provider.py +0 -0
  77. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/migrations/registry.py +0 -0
  78. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  79. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  80. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  81. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  82. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  83. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/schema_names.py +0 -0
  84. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  85. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/project_skills.py +0 -0
  86. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/runtime_context.py +0 -0
  87. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/runtime_flags.py +0 -0
  88. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence/scaffold_skills.py +0 -0
  89. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence.egg-info/SOURCES.txt +0 -0
  90. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence.egg-info/dependency_links.txt +0 -0
  91. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence.egg-info/entry_points.txt +0 -0
  92. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence.egg-info/requires.txt +0 -0
  93. {mainsequence-6.0.35 → mainsequence-6.0.37}/mainsequence.egg-info/top_level.txt +0 -0
  94. {mainsequence-6.0.35 → mainsequence-6.0.37}/setup.cfg +0 -0
  95. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_auth_precedence.py +0 -0
  96. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_build_operations_hashing.py +0 -0
  97. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_canonical_api_route_contract.py +0 -0
  98. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_cli_browser_auth.py +0 -0
  99. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_cli_migrations.py +0 -0
  100. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_client.py +0 -0
  101. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_data_access_mixin_dimension_audit.py +0 -0
  102. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_data_node_storage_dimension_queries.py +0 -0
  103. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_data_node_update_flow.py +0 -0
  104. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_dependency_extras.py +0 -0
  105. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_duckdb_interface_dimensions.py +0 -0
  106. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_filter_normalization.py +0 -0
  107. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_instrumentation.py +0 -0
  108. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_logconf.py +0 -0
  109. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_meta_table_migrations.py +0 -0
  110. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_meta_tables_client_models.py +0 -0
  111. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  112. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_models_user_request_bound_auth.py +0 -0
  113. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_notification_uid_contract.py +0 -0
  114. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_pod_project_resolution.py +0 -0
  115. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_project_batch_jobs_from_file.py +0 -0
  116. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_project_skills.py +0 -0
  117. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_run_configuration.py +0 -0
  118. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_scaffold_skills.py +0 -0
  119. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_schema_names.py +0 -0
  120. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_secret_client_model.py +0 -0
  121. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_source_table_configuration.py +0 -0
  122. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_sqlite_interface_dimensions.py +0 -0
  123. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_update_runner_uid_runtime.py +0 -0
  124. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_update_statistics.py +0 -0
  125. {mainsequence-6.0.35 → mainsequence-6.0.37}/tests/test_update_uid_guards.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 6.0.35
3
+ Version: 6.0.37
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -5116,7 +5116,7 @@ def bulk_delete_projects(
5116
5116
  return data
5117
5117
 
5118
5118
 
5119
- def add_deploy_key(project_branch_uid: str, key_title: str, public_key: str) -> None:
5119
+ def add_deploy_key(project_uid: str, key_title: str, public_key: str) -> None:
5120
5120
  """
5121
5121
  Add a deploy key for the project.
5122
5122
 
@@ -5125,7 +5125,7 @@ def add_deploy_key(project_branch_uid: str, key_title: str, public_key: str) ->
5125
5125
  """
5126
5126
  r = authed(
5127
5127
  "POST",
5128
- f"/api/v1/project-branches/{resolve_project_branch_uid(project_branch_uid)}/add-deploy-key/",
5128
+ f"/api/v1/projects/{resolve_project_uid(project_uid)}/add-deploy-key/",
5129
5129
  {"key_title": key_title, "public_key": public_key},
5130
5130
  )
5131
5131
  r.raise_for_status()
@@ -9786,7 +9786,7 @@ def project_set_up_locally(
9786
9786
  Clone a project locally and provision runtime `.env`.
9787
9787
 
9788
9788
  Workflow:
9789
- - ensure SSH key and optionally register deploy key,
9789
+ - ensure an SSH key and optionally register it through the owning Project,
9790
9790
  - clone repository into local projects root,
9791
9791
  - build local runtime auth/backend entries for the active auth mode,
9792
9792
  - write/update `.env` with local runtime values.
@@ -9832,7 +9832,6 @@ def project_set_up_locally(
9832
9832
  error(str(e))
9833
9833
  raise typer.Exit(1) from e
9834
9834
 
9835
- project_branch_uid = str(project_branch.get("uid") or "").strip()
9836
9835
  repository_branch = str(project_branch.get("repository_branch") or "").strip()
9837
9836
  is_initialized = project_branch.get("is_initialized")
9838
9837
 
@@ -9857,10 +9856,10 @@ def project_set_up_locally(
9857
9856
  key_path, _pub_path, pub = ensure_key_for_repo(repo)
9858
9857
  copied = _copy_clipboard(pub)
9859
9858
 
9860
- # Best-effort deploy key (do not fail set-up-locally on this)
9859
+ # Best-effort Project-owned deploy key (do not fail set-up-locally on this)
9861
9860
  try:
9862
9861
  host = platform.node()
9863
- add_deploy_key(project_branch_uid, host, pub)
9862
+ add_deploy_key(project_uid, host, pub)
9864
9863
  except Exception as e:
9865
9864
  warn(f"Could not add deploy key automatically (continuing): {e}")
9866
9865
 
@@ -431,6 +431,10 @@ class Agent(ShareableObjectMixin, BaseObjectOrm, BasePydanticModel):
431
431
  - Send exactly one lookup key: `session_uid` or `handle_unique_id`.
432
432
  - `session_uid` resolves an existing AgentSession for this Agent.
433
433
  - `handle_unique_id` gets or creates a reusable session handle.
434
+ - For ordinary User requests, the backend owns a root session with that User.
435
+ - For runtime A2A requests, `parent_session_uid` proves the calling Agent;
436
+ the backend inherits the parent session User into the child and handle.
437
+ - The SDK never supplies a credential owner or forwards provider credentials.
434
438
  - Creation options are valid only with `handle_unique_id`.
435
439
  - Response is the canonical AgentSessionSerializer payload.
436
440
  """
@@ -1261,7 +1265,12 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
1261
1265
  None, description="Public UID of the agent definition used for this session."
1262
1266
  )
1263
1267
  created_by_user_uid: str | None = Field(
1264
- None, description="Public UID of the actor who created the session."
1268
+ None,
1269
+ description=(
1270
+ "Public UID of the session invocation and model-provider credential "
1271
+ "owner. For delegated sessions this is inherited from the immediate "
1272
+ "parent and may differ from the runtime responsible User."
1273
+ ),
1265
1274
  )
1266
1275
  parent_session_uid: str | None = Field(
1267
1276
  None, description="Public UID of the parent session, if any."
@@ -1273,7 +1282,7 @@ class AgentSession(BaseObjectOrm, BasePydanticModel):
1273
1282
  created_by_user: int | None = Field(
1274
1283
  None,
1275
1284
  exclude=True,
1276
- description="Legacy internal user id of the actor who created the session record.",
1285
+ description="Legacy internal user id for the session owner.",
1277
1286
  )
1278
1287
  agent: int | Agent | None = Field(
1279
1288
  None,
@@ -442,6 +442,21 @@ class Project(LabelableObjectMixin, ShareableObjectMixin, BasePydanticModel, Bas
442
442
  raise_for_response(r)
443
443
  return ProjectBulkDeleteResponse.model_validate(r.json())
444
444
 
445
+ def add_deploy_key(self, *, key_title: str, public_key: str, timeout=None) -> None:
446
+ """Register a repository deploy key through the owning Project."""
447
+ r = make_request(
448
+ s=type(self).build_session(),
449
+ loaders=type(self).LOADERS,
450
+ r_type="POST",
451
+ url=(
452
+ f"{type(self).get_object_url()}/{self._public_detail_reference()}"
453
+ "/add-deploy-key/"
454
+ ),
455
+ payload={"json": {"key_title": key_title, "public_key": public_key}},
456
+ time_out=timeout,
457
+ )
458
+ raise_for_response(r)
459
+
445
460
  def __str__(self):
446
461
  return yaml.safe_dump(
447
462
  self.model_dump(),
@@ -523,17 +538,6 @@ class ProjectBranch(BasePydanticModel, BaseObjectOrm):
523
538
  raise_for_response(r)
524
539
  return r.json()
525
540
 
526
- def add_deploy_key(self, *, key_title: str, public_key: str, timeout=None) -> None:
527
- r = make_request(
528
- s=type(self).build_session(),
529
- loaders=type(self).LOADERS,
530
- r_type="POST",
531
- url=self._action_url("add-deploy-key"),
532
- payload={"json": {"key_title": key_title, "public_key": public_key}},
533
- time_out=timeout,
534
- )
535
- raise_for_response(r)
536
-
537
541
  def get_data_nodes_updates(self, *, timeout: int | None = None) -> list[Any]:
538
542
  from .metatables import DataNodeUpdate
539
543
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 6.0.35
3
+ Version: 6.0.37
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 = "6.0.35"
7
+ version = "6.0.37"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -2578,6 +2578,31 @@ def test_get_current_user_profile_accepts_top_level_organization_object(cli_mod,
2578
2578
  assert out == {"username": "jose@main-sequence.io", "organization": "Main Sequence Dev"}
2579
2579
 
2580
2580
 
2581
+ def test_add_deploy_key_uses_project_route(cli_mod, monkeypatch):
2582
+ api_mod = importlib.import_module("mainsequence.cli.api")
2583
+ captured = {}
2584
+
2585
+ class _Response:
2586
+ @staticmethod
2587
+ def raise_for_status():
2588
+ return None
2589
+
2590
+ def _authed(method, path, body=None):
2591
+ captured.update(method=method, path=path, body=body)
2592
+ return _Response()
2593
+
2594
+ monkeypatch.setattr(api_mod, "resolve_project_uid", lambda project_ref: "project-uid-123")
2595
+ monkeypatch.setattr(api_mod, "authed", _authed)
2596
+
2597
+ api_mod.add_deploy_key("project-uid-123", "workstation", "ssh-ed25519 AAA test")
2598
+
2599
+ assert captured == {
2600
+ "method": "POST",
2601
+ "path": "/api/v1/projects/project-uid-123/add-deploy-key/",
2602
+ "body": {"key_title": "workstation", "public_key": "ssh-ed25519 AAA test"},
2603
+ }
2604
+
2605
+
2581
2606
  def test_render_project_branch_default_redeployment_tag_uses_backend_contract(
2582
2607
  cli_mod,
2583
2608
  monkeypatch,
@@ -9537,7 +9562,12 @@ def test_project_set_up_locally(cli_mod, runner, monkeypatch, tmp_path):
9537
9562
  cli_mod, "ensure_key_for_repo", lambda repo: (key, pub, "ssh-ed25519 AAA test")
9538
9563
  )
9539
9564
  monkeypatch.setattr(cli_mod, "_copy_clipboard", lambda txt: True)
9540
- monkeypatch.setattr(cli_mod, "add_deploy_key", lambda *a, **k: None)
9565
+ deploy_key_requests = []
9566
+ monkeypatch.setattr(
9567
+ cli_mod,
9568
+ "add_deploy_key",
9569
+ lambda *args, **kwargs: deploy_key_requests.append((args, kwargs)),
9570
+ )
9541
9571
  monkeypatch.setattr(cli_mod, "start_agent_and_add_key", lambda *_: {})
9542
9572
 
9543
9573
  def _clone(cmd, env=None, cwd=None):
@@ -9558,6 +9588,10 @@ def test_project_set_up_locally(cli_mod, runner, monkeypatch, tmp_path):
9558
9588
  )
9559
9589
  assert result.exit_code == 0
9560
9590
  assert repository_requests == ["repository-uid-123"]
9591
+ assert len(deploy_key_requests) == 1
9592
+ assert deploy_key_requests[0][0][0] == "project-uid-123"
9593
+ assert deploy_key_requests[0][0][2] == "ssh-ed25519 AAA test"
9594
+ assert deploy_key_requests[0][1] == {}
9561
9595
 
9562
9596
  env_file = base / "org" / "projects" / "demo-project-uid-123" / ".env"
9563
9597
  assert env_file.exists()
@@ -9628,7 +9662,12 @@ def test_project_set_up_locally_runtime_credential(cli_mod, runner, monkeypatch,
9628
9662
  cli_mod, "ensure_key_for_repo", lambda repo: (key, pub, "ssh-ed25519 AAA test")
9629
9663
  )
9630
9664
  monkeypatch.setattr(cli_mod, "_copy_clipboard", lambda txt: True)
9631
- monkeypatch.setattr(cli_mod, "add_deploy_key", lambda *a, **k: None)
9665
+ deploy_key_requests = []
9666
+ monkeypatch.setattr(
9667
+ cli_mod,
9668
+ "add_deploy_key",
9669
+ lambda *args, **kwargs: deploy_key_requests.append((args, kwargs)),
9670
+ )
9632
9671
  monkeypatch.setattr(cli_mod, "start_agent_and_add_key", lambda *_: {})
9633
9672
 
9634
9673
  def _clone(cmd, env=None, cwd=None):
@@ -9654,6 +9693,10 @@ def test_project_set_up_locally_runtime_credential(cli_mod, runner, monkeypatch,
9654
9693
  )
9655
9694
  assert result.exit_code == 0
9656
9695
  assert repository_requests == ["repository-uid-123"]
9696
+ assert len(deploy_key_requests) == 1
9697
+ assert deploy_key_requests[0][0][0] == "project-uid-123"
9698
+ assert deploy_key_requests[0][0][2] == "ssh-ed25519 AAA test"
9699
+ assert deploy_key_requests[0][1] == {}
9657
9700
 
9658
9701
  env_file = base / "org" / "projects" / "demo-project-uid-123" / ".env"
9659
9702
  env_text = env_file.read_text(encoding="utf-8")
@@ -186,6 +186,36 @@ def test_project_bulk_delete_uses_selection_and_options(monkeypatch):
186
186
  }
187
187
 
188
188
 
189
+ def test_project_add_deploy_key_uses_project_route(monkeypatch):
190
+ captured = {}
191
+ project = models_foundry.Project(**logical_project_payload())
192
+
193
+ def fake_make_request(**kwargs):
194
+ captured.update(kwargs)
195
+ return FakeResponse(None)
196
+
197
+ monkeypatch.setattr(
198
+ models_foundry.Project,
199
+ "get_object_url",
200
+ classmethod(lambda cls: "https://api/projects"),
201
+ )
202
+ monkeypatch.setattr(
203
+ models_foundry.Project,
204
+ "build_session",
205
+ classmethod(lambda cls: object()),
206
+ )
207
+ monkeypatch.setattr(models_foundry, "make_request", fake_make_request)
208
+
209
+ project.add_deploy_key(key_title="workstation", public_key="ssh-ed25519 AAA test")
210
+
211
+ assert captured["r_type"] == "POST"
212
+ assert captured["url"] == f"https://api/projects/{PROJECT_UID}/add-deploy-key/"
213
+ assert captured["payload"] == {
214
+ "json": {"key_title": "workstation", "public_key": "ssh-ed25519 AAA test"}
215
+ }
216
+ assert not hasattr(models_foundry.ProjectBranch, "add_deploy_key")
217
+
218
+
189
219
  def test_project_branch_summary_uses_branch_route(monkeypatch):
190
220
  captured = {}
191
221
  branch = models_foundry.ProjectBranch(**project_branch_payload())
File without changes
File without changes
File without changes