mainsequence 9.0.8.dev45__tar.gz → 9.0.9__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 (104) hide show
  1. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/PKG-INFO +1 -1
  2. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/browser_auth.py +3 -2
  3. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/agent_runtime_models.py +3 -1
  4. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/models_foundry.py +5 -1
  5. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/models_helpers.py +6 -2
  6. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/utils.py +10 -7
  7. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence.egg-info/PKG-INFO +1 -1
  8. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence.egg-info/SOURCES.txt +1 -0
  9. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/pyproject.toml +1 -1
  10. mainsequence-9.0.9/tests/test_credential_repr.py +172 -0
  11. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/LICENSE +0 -0
  12. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/README.md +0 -0
  13. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/AGENTS.md +0 -0
  14. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  15. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  16. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/skills/mainsequence-inference/SKILL.md +0 -0
  17. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  18. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  19. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/skills/maintenance/metatables_transition/SKILL.md +0 -0
  20. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  21. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  22. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  23. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/__init__.py +0 -0
  24. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/__main__.py +0 -0
  25. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/_request_identity.py +0 -0
  26. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/bootstrap.py +0 -0
  27. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/__init__.py +0 -0
  28. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/api.py +0 -0
  29. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/cli.py +0 -0
  30. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/code_repository_status.py +0 -0
  31. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/config.py +0 -0
  32. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/docker_utils.py +0 -0
  33. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/doctor.py +0 -0
  34. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/local_ops.py +0 -0
  35. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/model_filters.py +0 -0
  36. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/pydantic_cli.py +0 -0
  37. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/sdk_utils.py +0 -0
  38. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/ssh_utils.py +0 -0
  39. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/cli/ui.py +0 -0
  40. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/__init__.py +0 -0
  41. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/_transport.py +0 -0
  42. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/base.py +0 -0
  43. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/client.py +0 -0
  44. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/compute_validation.py +0 -0
  45. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/exceptions.py +0 -0
  46. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/github_issues.py +0 -0
  47. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/inference.py +0 -0
  48. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/models_user.py +0 -0
  49. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/observability.py +0 -0
  50. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/client/value_sets.py +0 -0
  51. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/code_repository_context.py +0 -0
  52. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/code_repository_skills.py +0 -0
  53. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/defaults.py +0 -0
  54. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/instrumentation/__init__.py +0 -0
  55. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/instrumentation/utils.py +0 -0
  56. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/logconf.py +0 -0
  57. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/repository_identity_security.py +0 -0
  58. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/runtime_credential_exchange.py +0 -0
  59. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/runtime_flags.py +0 -0
  60. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/scaffold_skills.py +0 -0
  61. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/server/__init__.py +0 -0
  62. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/server/caller_assertions.py +0 -0
  63. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence/server/fastapi.py +0 -0
  64. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence.egg-info/dependency_links.txt +0 -0
  65. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence.egg-info/entry_points.txt +0 -0
  66. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence.egg-info/requires.txt +0 -0
  67. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/mainsequence.egg-info/top_level.txt +0 -0
  68. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/setup.cfg +0 -0
  69. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_auth_bootstrap.py +0 -0
  70. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_auth_precedence.py +0 -0
  71. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_caller_assertions.py +0 -0
  72. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_canonical_api_route_contract.py +0 -0
  73. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_cli_auth_contract.py +0 -0
  74. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_cli_browser_auth.py +0 -0
  75. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_cli_secure_storage.py +0 -0
  76. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_client.py +0 -0
  77. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_code_repository_branch_contract.py +0 -0
  78. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_code_repository_documentation_contract.py +0 -0
  79. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_code_repository_runtime_context.py +0 -0
  80. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_code_repository_skills.py +0 -0
  81. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_dependency_extras.py +0 -0
  82. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_dev_release_version.py +0 -0
  83. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_environment_resource_context.py +0 -0
  84. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_environment_response_contracts.py +0 -0
  85. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_fastapi_request_identity.py +0 -0
  86. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_github_issues_client.py +0 -0
  87. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_http_retry_budget.py +0 -0
  88. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_independent_context.py +0 -0
  89. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_inference_client.py +0 -0
  90. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_instrumentation.py +0 -0
  91. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_job_client.py +0 -0
  92. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_local_ops.py +0 -0
  93. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_logconf.py +0 -0
  94. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_models_user_request_bound_auth.py +0 -0
  95. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_notification_uid_contract.py +0 -0
  96. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_observability_client.py +0 -0
  97. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_runtime_credential_exchange.py +0 -0
  98. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_scaffold_skills.py +0 -0
  99. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_secret_client_model.py +0 -0
  100. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_source_ownership.py +0 -0
  101. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_ssh_utils.py +0 -0
  102. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_tolerant_response_reading.py +0 -0
  103. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_tolerant_value_sets.py +0 -0
  104. {mainsequence-9.0.8.dev45 → mainsequence-9.0.9}/tests/test_user_application_facts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 9.0.8.dev45
3
+ Version: 9.0.9
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: MIT
@@ -15,7 +15,7 @@ import threading
15
15
  import time
16
16
  import webbrowser
17
17
  from collections.abc import Callable
18
- from dataclasses import dataclass
18
+ from dataclasses import dataclass, field
19
19
  from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
20
20
  from urllib.parse import parse_qs, urlparse
21
21
 
@@ -37,7 +37,8 @@ class BrowserAuthError(RuntimeError):
37
37
  class BrowserAuthCallback:
38
38
  """Authorization callback payload captured from the local loopback receiver."""
39
39
 
40
- code: str | None = None
40
+ # The authorization code is a bearer credential: it stays out of repr() and str().
41
+ code: str | None = field(default=None, repr=False)
41
42
  state: str | None = None
42
43
  error: str | None = None
43
44
  error_description: str | None = None
@@ -353,7 +353,8 @@ class AgentRuntimePresence(BasePydanticModel):
353
353
 
354
354
 
355
355
  class AgentSessionRuntimeAccess(BasePydanticModel):
356
- model_config = ConfigDict(extra="allow")
356
+ # A validation error must not echo the input: it holds the bearer token.
357
+ model_config = ConfigDict(extra="allow", hide_input_in_errors=True)
357
358
 
358
359
  coding_agent_service_uid: str | None = Field(
359
360
  None,
@@ -373,6 +374,7 @@ class AgentSessionRuntimeAccess(BasePydanticModel):
373
374
  token: str | None = Field(
374
375
  None,
375
376
  description="Bearer token that authorizes calls to the coding-agent gateway.",
377
+ repr=False,
376
378
  )
377
379
  expires_at: str | None = Field(
378
380
  None,
@@ -7,7 +7,7 @@ from uuid import UUID
7
7
 
8
8
  import yaml
9
9
  from cachetools import TTLCache, cachedmethod
10
- from pydantic import Field, SecretStr
10
+ from pydantic import ConfigDict, Field, SecretStr
11
11
 
12
12
  from .base import (
13
13
  BaseObjectOrm,
@@ -891,6 +891,10 @@ class Secret(
891
891
  BasePydanticModel,
892
892
  BaseObjectOrm,
893
893
  ):
894
+ # `value` is a SecretStr once read, but a validation error would echo the
895
+ # plaintext it was given.
896
+ model_config = ConfigDict(hide_input_in_errors=True)
897
+
894
898
  HYDRATE_FILTER_GET_FROM_DETAIL: ClassVar[bool] = True
895
899
  FILTERSET_FIELDS: ClassVar[dict[str, list[str]]] = {
896
900
  "uid": ["in", "exact"],
@@ -6,7 +6,7 @@ from decimal import Decimal
6
6
  from typing import Any, ClassVar, Literal
7
7
  from uuid import UUID
8
8
 
9
- from pydantic import BaseModel, Field, PositiveInt
9
+ from pydantic import BaseModel, ConfigDict, Field, PositiveInt
10
10
 
11
11
  from .base import (
12
12
  BaseObjectOrm,
@@ -798,6 +798,10 @@ class ResourceReleaseRuntimePresence(BasePydanticModel):
798
798
 
799
799
 
800
800
  class ResourceReleaseRuntimeAccess(BasePydanticModel):
801
+ # `access` holds the runtime's bearer token, so neither repr() nor a
802
+ # validation error shows it.
803
+ model_config = ConfigDict(hide_input_in_errors=True)
804
+
801
805
  resource_release_uid: str
802
806
  # The canonical vocabulary ResourceRelease.release_kind reads (#119): this
803
807
  # payload names a release kind, so it names it the same way.
@@ -805,7 +809,7 @@ class ResourceReleaseRuntimeAccess(BasePydanticModel):
805
809
  routing: ResourceReleaseRuntimeRouting
806
810
  runtime_access: ResourceReleaseRuntimeAdmission
807
811
  runtime_presence: ResourceReleaseRuntimePresence
808
- access: dict[str, Any] | None = None
812
+ access: dict[str, Any] | None = Field(default=None, repr=False)
809
813
 
810
814
 
811
815
  class ResourceRelease(
@@ -278,8 +278,9 @@ class BaseAuthProvider:
278
278
 
279
279
  @dataclass
280
280
  class SessionJWTAuthProvider(BaseAuthProvider):
281
- access_token: str | None = None
282
- refresh_token: str | None = None
281
+ # Tokens stay out of repr() and str(), so logging the provider never shows them.
282
+ access_token: str | None = field(default=None, repr=False)
283
+ refresh_token: str | None = field(default=None, repr=False)
283
284
  header_keyword: str = "Bearer"
284
285
 
285
286
  def __post_init__(self):
@@ -324,12 +325,13 @@ class RuntimeCredentialAuthProvider(BaseAuthProvider):
324
325
  `MAINSEQUENCE_RUNTIME_IDENTITY_TOKEN_FILE` is set: the file is read for every
325
326
  exchange, and `MAINSEQUENCE_RUNTIME_CREDENTIAL_SECRET` is never read or sent.
326
327
  Otherwise the proof is that bootstrap secret. The token is never kept on the
327
- provider. Throttled (`429`) and temporarily unavailable (`503`) exchanges are
328
- retried within the timeout budget; a rejected one (`401`) fails at once.
328
+ provider, and the secret is left out of `repr()` and `str()`. Throttled
329
+ (`429`) and temporarily unavailable (`503`) exchanges are retried within the
330
+ timeout budget; a rejected one (`401`) fails at once.
329
331
  """
330
332
 
331
333
  credential_id: str | None = None
332
- credential_secret: str | None = None
334
+ credential_secret: str | None = field(default=None, repr=False)
333
335
  token_url: str | None = None
334
336
  token_type: str = "Bearer"
335
337
  refresh_skew_seconds: int = 30
@@ -481,8 +483,9 @@ class RuntimeCredentialAuthProvider(BaseAuthProvider):
481
483
 
482
484
  @dataclass
483
485
  class JWTAuthProvider(BaseAuthProvider):
484
- access_token: str | None = None
485
- refresh_token: str | None = None
486
+ # Tokens stay out of repr() and str(), so logging the provider never shows them.
487
+ access_token: str | None = field(default=None, repr=False)
488
+ refresh_token: str | None = field(default=None, repr=False)
486
489
  refresh_url: str | None = None
487
490
  obtain_url: str | None = None
488
491
  header_keyword: str = "Bearer"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 9.0.8.dev45
3
+ Version: 9.0.9
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: MIT
@@ -75,6 +75,7 @@ tests/test_code_repository_branch_contract.py
75
75
  tests/test_code_repository_documentation_contract.py
76
76
  tests/test_code_repository_runtime_context.py
77
77
  tests/test_code_repository_skills.py
78
+ tests/test_credential_repr.py
78
79
  tests/test_dependency_extras.py
79
80
  tests/test_dev_release_version.py
80
81
  tests/test_environment_resource_context.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "9.0.8.dev45"
7
+ version = "9.0.9"
8
8
  description = "Main Sequence SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -0,0 +1,172 @@
1
+ """Credentials stay out of repr(), str(), log lines and validation errors.
2
+
3
+ Each object below holds a credential and still uses it: only the text the object
4
+ becomes when it is printed, formatted or logged leaves the credential out.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+
11
+ import pydantic
12
+ import pytest
13
+
14
+ from mainsequence.cli.browser_auth import BrowserAuthCallback
15
+ from mainsequence.client.agent_runtime_models import AgentSessionRuntimeAccess
16
+ from mainsequence.client.models_foundry import Secret
17
+ from mainsequence.client.models_helpers import ResourceReleaseRuntimeAccess
18
+ from mainsequence.client.utils import (
19
+ JWTAuthProvider,
20
+ RuntimeCredentialAuthProvider,
21
+ SessionJWTAuthProvider,
22
+ )
23
+ from tests.client.support import _ready_runtime_contract, _runtime_access_payload
24
+
25
+ BOOTSTRAP_SECRET = "bootstrap-secret-7c1d"
26
+ ACCESS_TOKEN = "access-token-5e2a"
27
+ REFRESH_TOKEN = "refresh-token-9b4f"
28
+ GATEWAY_TOKEN = "gateway-token-3a8e"
29
+ RELEASE_TOKEN = "release-token-6d0c"
30
+ AUTHORIZATION_CODE = "authorization-code-2f7b"
31
+ SECRET_VALUE = "secret-value-8e5a"
32
+ TOKEN_URL = "https://backend.example/api/v1/runtime-credentials/token/"
33
+
34
+
35
+ def _shown(obj, caplog) -> str:
36
+ """Return repr(), str() and a log line that formats the object both ways."""
37
+ caplog.clear()
38
+ caplog.set_level(logging.INFO, logger=__name__)
39
+ logging.getLogger(__name__).info("%s %r", obj, obj)
40
+ return "\n".join((repr(obj), str(obj), caplog.text))
41
+
42
+
43
+ def _error_text(raised: pytest.ExceptionInfo) -> str:
44
+ return f"{raised.value}\n{raised.value!r}"
45
+
46
+
47
+ @pytest.mark.parametrize("source", ["environment", "argument"])
48
+ def test_runtime_credential_provider_leaves_the_bootstrap_secret_out(monkeypatch, caplog, source):
49
+ monkeypatch.delenv("MAINSEQUENCE_RUNTIME_IDENTITY_TOKEN_FILE", raising=False)
50
+ monkeypatch.setenv("MAINSEQUENCE_RUNTIME_CREDENTIAL_ID", "cred-id")
51
+ if source == "environment":
52
+ monkeypatch.setenv("MAINSEQUENCE_RUNTIME_CREDENTIAL_SECRET", BOOTSTRAP_SECRET)
53
+ provider = RuntimeCredentialAuthProvider(token_url=TOKEN_URL)
54
+ else:
55
+ monkeypatch.delenv("MAINSEQUENCE_RUNTIME_CREDENTIAL_SECRET", raising=False)
56
+ provider = RuntimeCredentialAuthProvider(
57
+ credential_secret=BOOTSTRAP_SECRET, token_url=TOKEN_URL
58
+ )
59
+
60
+ shown = _shown(provider, caplog)
61
+
62
+ assert provider.credential_secret == BOOTSTRAP_SECRET
63
+ assert BOOTSTRAP_SECRET not in shown
64
+ assert "credential_id='cred-id'" in shown
65
+
66
+
67
+ def test_jwt_provider_leaves_its_tokens_out(monkeypatch, caplog):
68
+ monkeypatch.setenv("MAINSEQUENCE_ACCESS_TOKEN", ACCESS_TOKEN)
69
+ monkeypatch.setenv("MAINSEQUENCE_REFRESH_TOKEN", REFRESH_TOKEN)
70
+
71
+ for provider in (
72
+ JWTAuthProvider(),
73
+ JWTAuthProvider(access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN),
74
+ ):
75
+ shown = _shown(provider, caplog)
76
+
77
+ assert provider.get_headers()["Authorization"] == f"Bearer {ACCESS_TOKEN}"
78
+ assert provider.refresh_token == REFRESH_TOKEN
79
+ assert ACCESS_TOKEN not in shown
80
+ assert REFRESH_TOKEN not in shown
81
+ assert "header_keyword='Bearer'" in shown
82
+
83
+
84
+ def test_session_jwt_provider_leaves_its_token_out(monkeypatch, caplog):
85
+ monkeypatch.setenv("MAINSEQUENCE_ACCESS_TOKEN", ACCESS_TOKEN)
86
+ monkeypatch.delenv("MAINSEQUENCE_REFRESH_TOKEN", raising=False)
87
+ provider = SessionJWTAuthProvider()
88
+
89
+ shown = _shown(provider, caplog)
90
+
91
+ assert provider.get_headers()["Authorization"] == f"Bearer {ACCESS_TOKEN}"
92
+ assert ACCESS_TOKEN not in shown
93
+ assert "header_keyword='Bearer'" in shown
94
+
95
+
96
+ def test_agent_session_runtime_access_leaves_its_token_out(caplog):
97
+ access = AgentSessionRuntimeAccess.model_validate(
98
+ {
99
+ "mode": "token",
100
+ "rpc_url": "https://runtime.example/",
101
+ "token": GATEWAY_TOKEN,
102
+ **_ready_runtime_contract(),
103
+ }
104
+ )
105
+
106
+ shown = _shown(access, caplog)
107
+
108
+ assert access.token == GATEWAY_TOKEN
109
+ assert access.model_dump()["token"] == GATEWAY_TOKEN
110
+ assert GATEWAY_TOKEN not in shown
111
+ assert "rpc_url='https://runtime.example/'" in shown
112
+
113
+
114
+ def test_agent_session_runtime_access_validation_error_leaves_the_token_out():
115
+ with pytest.raises(pydantic.ValidationError) as raised:
116
+ AgentSessionRuntimeAccess.model_validate({"mode": "token", "token": GATEWAY_TOKEN})
117
+
118
+ assert "runtime_interaction" in str(raised.value)
119
+ assert GATEWAY_TOKEN not in _error_text(raised)
120
+
121
+
122
+ def _release_runtime_access_payload() -> dict:
123
+ payload = _runtime_access_payload(state="ready", can_request=True)
124
+ payload["access"]["token"] = RELEASE_TOKEN
125
+ return payload
126
+
127
+
128
+ def test_resource_release_runtime_access_leaves_its_token_out(caplog):
129
+ access = ResourceReleaseRuntimeAccess.model_validate(_release_runtime_access_payload())
130
+
131
+ shown = _shown(access, caplog)
132
+
133
+ assert access.access["token"] == RELEASE_TOKEN
134
+ assert access.model_dump()["access"]["token"] == RELEASE_TOKEN
135
+ assert RELEASE_TOKEN not in shown
136
+ assert "resource_release_uid='2f4c4c3d-5669-4da5-9d86-b84633c1e6ed'" in shown
137
+
138
+
139
+ def test_resource_release_runtime_access_validation_error_leaves_the_token_out():
140
+ payload = _release_runtime_access_payload()
141
+ del payload["routing"]
142
+ # A shown input is cut in its middle; this order puts the token at its end.
143
+ payload["access"] = {"mode": "token", "token": RELEASE_TOKEN}
144
+
145
+ with pytest.raises(pydantic.ValidationError) as raised:
146
+ ResourceReleaseRuntimeAccess.model_validate(payload)
147
+
148
+ assert "routing" in str(raised.value)
149
+ assert RELEASE_TOKEN not in _error_text(raised)
150
+
151
+
152
+ def test_secret_leaves_its_value_out(caplog):
153
+ secret = Secret(name="POLYGON_API_KEY", value=SECRET_VALUE)
154
+
155
+ shown = _shown(secret, caplog)
156
+
157
+ assert secret.value.get_secret_value() == SECRET_VALUE
158
+ assert SECRET_VALUE not in shown
159
+ with pytest.raises(pydantic.ValidationError) as raised:
160
+ Secret.model_validate({"value": SECRET_VALUE})
161
+ assert "name" in str(raised.value)
162
+ assert SECRET_VALUE not in _error_text(raised)
163
+
164
+
165
+ def test_browser_auth_callback_leaves_the_authorization_code_out(caplog):
166
+ callback = BrowserAuthCallback(code=AUTHORIZATION_CODE, state="state-1")
167
+
168
+ shown = _shown(callback, caplog)
169
+
170
+ assert callback.code == AUTHORIZATION_CODE
171
+ assert AUTHORIZATION_CODE not in shown
172
+ assert "state='state-1'" in shown
File without changes