mainsequence 9.0.8.dev45__tar.gz → 9.0.9.dev46__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/PKG-INFO +1 -1
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/browser_auth.py +3 -2
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/agent_runtime_models.py +3 -1
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/models_foundry.py +5 -1
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/models_helpers.py +6 -2
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/utils.py +10 -7
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence.egg-info/SOURCES.txt +1 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/pyproject.toml +1 -1
- mainsequence-9.0.9.dev46/tests/test_credential_repr.py +172 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/LICENSE +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/README.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/skills/mainsequence-inference/SKILL.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/skills/maintenance/metatables_transition/SKILL.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/__init__.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/__main__.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/_request_identity.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/bootstrap.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/api.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/cli.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/code_repository_status.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/config.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/ui.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/__init__.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/_transport.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/base.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/client.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/github_issues.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/inference.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/models_user.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/observability.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/value_sets.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/code_repository_context.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/code_repository_skills.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/defaults.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/logconf.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/repository_identity_security.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/runtime_credential_exchange.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/scaffold_skills.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/server/__init__.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/server/caller_assertions.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/server/fastapi.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/setup.cfg +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_auth_bootstrap.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_auth_precedence.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_caller_assertions.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_canonical_api_route_contract.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_cli_auth_contract.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_cli_secure_storage.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_client.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_code_repository_branch_contract.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_code_repository_documentation_contract.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_code_repository_runtime_context.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_code_repository_skills.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_dependency_extras.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_dev_release_version.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_environment_resource_context.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_environment_response_contracts.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_fastapi_request_identity.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_github_issues_client.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_http_retry_budget.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_independent_context.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_inference_client.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_instrumentation.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_job_client.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_local_ops.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_logconf.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_notification_uid_contract.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_observability_client.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_runtime_credential_exchange.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_scaffold_skills.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_secret_client_model.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_source_ownership.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_ssh_utils.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_tolerant_response_reading.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_tolerant_value_sets.py +0 -0
- {mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_user_application_facts.py +0 -0
|
@@ -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:
|
|
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
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/agent_runtime_models.py
RENAMED
|
@@ -353,7 +353,8 @@ class AgentRuntimePresence(BasePydanticModel):
|
|
|
353
353
|
|
|
354
354
|
|
|
355
355
|
class AgentSessionRuntimeAccess(BasePydanticModel):
|
|
356
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
|
328
|
-
|
|
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
|
-
|
|
485
|
-
|
|
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"
|
|
@@ -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
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/cli/code_repository_status.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/client/compute_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/code_repository_context.py
RENAMED
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/code_repository_skills.py
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/instrumentation/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/repository_identity_security.py
RENAMED
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/runtime_credential_exchange.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence/server/caller_assertions.py
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/mainsequence.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_canonical_api_route_contract.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_code_repository_branch_contract.py
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_code_repository_runtime_context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_environment_resource_context.py
RENAMED
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_environment_response_contracts.py
RENAMED
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_fastapi_request_identity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_models_user_request_bound_auth.py
RENAMED
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_notification_uid_contract.py
RENAMED
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_runtime_credential_exchange.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mainsequence-9.0.8.dev45 → mainsequence-9.0.9.dev46}/tests/test_tolerant_response_reading.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|