From 2f122a1f25807f13f267b9533adea6baa5e0bfac Mon Sep 17 00:00:00 2001 From: Akmal Date: Sat, 9 May 2026 16:52:04 +0500 Subject: [PATCH 1/7] chore: add configured adapter env placeholders --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.env.example b/.env.example index 07d8c6b..be60f22 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,8 @@ ALPHA_RESEARCH_ENV=local ALPHA_RESEARCH_TRACKING_URI=./mlruns ALPHA_RESEARCH_LOG_LEVEL=INFO + +# Optional configured adapter values. Keep real secrets in local .env only. +ALPHA_RESEARCH_PROVIDER_USER_AGENT=alpha-research-local/0.1 +ALPHA_RESEARCH_SAMPLE_API_KEY=replace-me +ALPHA_RESEARCH_LOCAL_MARKET_PATH=./tests/fixtures/market.csv From f8637eaa7101007f6729b61f5af9ccc0361366aa Mon Sep 17 00:00:00 2001 From: Akmal Date: Sat, 9 May 2026 16:52:20 +0500 Subject: [PATCH 2/7] docs: explain configured adapter environment values --- .../configured_adapter_environment.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 docs/runbooks/configured_adapter_environment.md diff --git a/docs/runbooks/configured_adapter_environment.md b/docs/runbooks/configured_adapter_environment.md new file mode 100644 index 0000000..c3a1dc3 --- /dev/null +++ b/docs/runbooks/configured_adapter_environment.md @@ -0,0 +1,33 @@ +# Configured adapter environment runbook + +Configured adapters can read local paths, API keys, and user-agent values from +environment variables declared in `configs/data_sources.yaml`. This keeps the +repository reproducible while allowing private live-provider settings locally. + +## Safe local pattern + +1. Copy `.env.example` to `.env`. +2. Replace placeholder values only in `.env`. +3. Keep `.env` untracked. +4. Prefer configured-local smoke for pull requests. +5. Use live-public smoke only when the pull request explicitly changes external + provider behavior. + +## Adapter fields + +| Field | Meaning | Failure mode | +| --- | --- | --- | +| `local_path` | Explicit repository-relative or absolute fixture path. | Permanent error when the file is missing. | +| `local_path_env` | Environment variable that points to a local fixture path. | Permanent error when both `local_path` and the env value are missing. | +| `api_key_env` | Environment variable that stores a live-provider credential. | Omitted from request when missing unless the provider requires it. | +| `api_key_header` | Header name used for the resolved API key. | Ignored when no API key is available. | +| `api_key_query_param` | Query parameter used for the resolved API key. | Ignored when no API key is available. | +| `user_agent_env` | Environment variable for a provider-specific user agent. | Falls back to `Mozilla/5.0` when missing. | + +## Review checklist + +- `.env.example` contains placeholders only. +- No real provider key appears in diffs or test fixtures. +- Tests monkeypatch environment variables instead of depending on local shell + state. +- Error messages name the adapter and the missing setting. From ab1a2a7337bd43ee9a5a0149a71588fa6a13371a Mon Sep 17 00:00:00 2001 From: Akmal Date: Sat, 9 May 2026 16:52:39 +0500 Subject: [PATCH 3/7] refactor: add adapter environment diagnostics --- .../data/providers/configured_transport.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/alpha_research/data/providers/configured_transport.py b/src/alpha_research/data/providers/configured_transport.py index 6176d2c..662db90 100644 --- a/src/alpha_research/data/providers/configured_transport.py +++ b/src/alpha_research/data/providers/configured_transport.py @@ -29,6 +29,28 @@ class ConfiguredAdapterPermanentError(ConfiguredAdapterError): pass +@dataclass(frozen=True) +class AdapterEnvironmentDiagnostics: + adapter_name: str + local_path_env: str | None + local_path_env_present: bool + api_key_env: str | None + api_key_env_present: bool + user_agent_env: str | None + user_agent_env_present: bool + + def as_dict(self) -> dict[str, bool | str | None]: + return { + "adapter_name": self.adapter_name, + "local_path_env": self.local_path_env, + "local_path_env_present": self.local_path_env_present, + "api_key_env": self.api_key_env, + "api_key_env_present": self.api_key_env_present, + "user_agent_env": self.user_agent_env, + "user_agent_env_present": self.user_agent_env_present, + } + + @dataclass(frozen=True) class ResponseCache: root: Path @@ -59,6 +81,18 @@ def resolve_env(name: str | None) -> str | None: return value.strip() if isinstance(value, str) and value.strip() else None +def adapter_environment_diagnostics(adapter: AdapterConfig) -> AdapterEnvironmentDiagnostics: + return AdapterEnvironmentDiagnostics( + adapter_name=adapter.adapter_name, + local_path_env=adapter.local_path_env, + local_path_env_present=resolve_env(adapter.local_path_env) is not None, + api_key_env=adapter.api_key_env, + api_key_env_present=resolve_env(adapter.api_key_env) is not None, + user_agent_env=adapter.user_agent_env, + user_agent_env_present=resolve_env(adapter.user_agent_env) is not None, + ) + + def resolve_local_path(adapter: AdapterConfig, root: Path) -> Path: local_path = adapter.local_path or resolve_env(adapter.local_path_env) if not local_path: From 256e2a78d5d68b8f12bfcc83dd296a56535831ac Mon Sep 17 00:00:00 2001 From: Akmal Date: Sat, 9 May 2026 16:52:52 +0500 Subject: [PATCH 4/7] fix: clarify missing adapter local path errors --- .../data/providers/configured_transport.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/alpha_research/data/providers/configured_transport.py b/src/alpha_research/data/providers/configured_transport.py index 662db90..d341c91 100644 --- a/src/alpha_research/data/providers/configured_transport.py +++ b/src/alpha_research/data/providers/configured_transport.py @@ -96,14 +96,20 @@ def adapter_environment_diagnostics(adapter: AdapterConfig) -> AdapterEnvironmen def resolve_local_path(adapter: AdapterConfig, root: Path) -> Path: local_path = adapter.local_path or resolve_env(adapter.local_path_env) if not local_path: + diagnostics = adapter_environment_diagnostics(adapter) raise ConfiguredAdapterPermanentError( - f"Для adapter `{adapter.adapter_name}` не указан local_path и не задан env `{adapter.local_path_env}`." + f"Для adapter `{adapter.adapter_name}` не указан local_path и не задан env `{adapter.local_path_env}`. " + f"diagnostics={diagnostics.as_dict()}" ) candidate = Path(local_path) if not candidate.is_absolute(): candidate = (root / candidate).resolve() if not candidate.exists(): - raise ConfiguredAdapterPermanentError(f"Локальный файл для adapter `{adapter.adapter_name}` не найден: {candidate}") + source = "local_path" if adapter.local_path else f"env `{adapter.local_path_env}`" + raise ConfiguredAdapterPermanentError( + f"Локальный файл для adapter `{adapter.adapter_name}` не найден: {candidate} " + f"(source={source})." + ) return candidate From d0acff60245b8804886e041b088ba796459474e7 Mon Sep 17 00:00:00 2001 From: Akmal Date: Sat, 9 May 2026 16:53:09 +0500 Subject: [PATCH 5/7] test: cover adapter environment diagnostics --- tests/unit/test_configured_transport_env.py | 48 +++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/unit/test_configured_transport_env.py diff --git a/tests/unit/test_configured_transport_env.py b/tests/unit/test_configured_transport_env.py new file mode 100644 index 0000000..394eeb3 --- /dev/null +++ b/tests/unit/test_configured_transport_env.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from alpha_research.config.models import AdapterConfig +from alpha_research.data.providers.configured_transport import ( + adapter_environment_diagnostics, + provider_headers, + resolve_env, +) + + +def test_resolve_env_strips_blank_values(monkeypatch) -> None: + monkeypatch.setenv("ALPHA_RESEARCH_TEST_VALUE", " usable-value ") + monkeypatch.setenv("ALPHA_RESEARCH_BLANK_VALUE", " ") + + assert resolve_env("ALPHA_RESEARCH_TEST_VALUE") == "usable-value" + assert resolve_env("ALPHA_RESEARCH_BLANK_VALUE") is None + assert resolve_env(None) is None + + +def test_adapter_environment_diagnostics_reports_presence_without_values(monkeypatch) -> None: + monkeypatch.setenv("ALPHA_RESEARCH_API_KEY", "secret-value") + monkeypatch.setenv("ALPHA_RESEARCH_USER_AGENT", "alpha-research-test") + + adapter = AdapterConfig( + adapter_name="diagnostic_adapter", + adapter_type="http", + api_key_env="ALPHA_RESEARCH_API_KEY", + local_path_env="ALPHA_RESEARCH_LOCAL_PATH", + user_agent_env="ALPHA_RESEARCH_USER_AGENT", + ) + + diagnostics = adapter_environment_diagnostics(adapter) + + assert diagnostics.api_key_env_present is True + assert diagnostics.user_agent_env_present is True + assert diagnostics.local_path_env_present is False + assert diagnostics.as_dict()["api_key_env"] == "ALPHA_RESEARCH_API_KEY" + + +def test_provider_headers_uses_default_user_agent_when_env_missing(monkeypatch) -> None: + monkeypatch.delenv("ALPHA_RESEARCH_USER_AGENT", raising=False) + adapter = AdapterConfig( + adapter_name="headers_adapter", + adapter_type="http", + user_agent_env="ALPHA_RESEARCH_USER_AGENT", + ) + + assert provider_headers(adapter)["User-Agent"] == "Mozilla/5.0" From 7bc74e0b3f0f94e77c2d93f24397250381901b70 Mon Sep 17 00:00:00 2001 From: Akmal Date: Sat, 9 May 2026 16:53:25 +0500 Subject: [PATCH 6/7] test: cover adapter local path failures --- tests/unit/test_configured_transport_env.py | 41 +++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/unit/test_configured_transport_env.py b/tests/unit/test_configured_transport_env.py index 394eeb3..dd18032 100644 --- a/tests/unit/test_configured_transport_env.py +++ b/tests/unit/test_configured_transport_env.py @@ -2,9 +2,11 @@ from alpha_research.config.models import AdapterConfig from alpha_research.data.providers.configured_transport import ( + ConfiguredAdapterPermanentError, adapter_environment_diagnostics, provider_headers, resolve_env, + resolve_local_path, ) @@ -46,3 +48,42 @@ def test_provider_headers_uses_default_user_agent_when_env_missing(monkeypatch) ) assert provider_headers(adapter)["User-Agent"] == "Mozilla/5.0" + + +def test_resolve_local_path_error_names_missing_env(tmp_path, monkeypatch) -> None: + monkeypatch.delenv("ALPHA_RESEARCH_LOCAL_PATH", raising=False) + adapter = AdapterConfig( + adapter_name="local_fixture_adapter", + adapter_type="local_file_market_daily", + local_path_env="ALPHA_RESEARCH_LOCAL_PATH", + ) + + try: + resolve_local_path(adapter, tmp_path) + except ConfiguredAdapterPermanentError as exc: + message = str(exc) + else: + raise AssertionError("Expected missing local path to raise a permanent adapter error") + + assert "local_fixture_adapter" in message + assert "ALPHA_RESEARCH_LOCAL_PATH" in message + assert "local_path_env_present" in message + + +def test_resolve_local_path_error_names_missing_file_source(tmp_path) -> None: + adapter = AdapterConfig( + adapter_name="missing_file_adapter", + adapter_type="local_file_market_daily", + local_path="fixtures/missing.csv", + ) + + try: + resolve_local_path(adapter, tmp_path) + except ConfiguredAdapterPermanentError as exc: + message = str(exc) + else: + raise AssertionError("Expected missing fixture file to raise a permanent adapter error") + + assert "missing_file_adapter" in message + assert "fixtures" in message + assert "source=local_path" in message From 0b37538be5390b60d4461c9d697786427ae6c92a Mon Sep 17 00:00:00 2001 From: Akmal Date: Sat, 9 May 2026 16:53:37 +0500 Subject: [PATCH 7/7] docs: link configured adapter runbook --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 89baae7..f937318 100644 --- a/README.md +++ b/README.md @@ -114,5 +114,6 @@ python .\scripts\run_release_smoke.py --root . --mode live-public Это не набор взаимозаменяемых костылей. У каждого режима свой capability contract: можно ли строить release bundle, требуется ли external proof и допустим ли synthetic ingest. Для локального воспроизводимого прогона есть отдельный runbook: `docs/runbooks/reproducible_local_runbook.md`. +Для безопасной настройки configured adapters есть runbook: `docs/runbooks/configured_adapter_environment.md`. Для отдельного тяжелого smoke-прогона есть workflow `.github/workflows/release_smoke.yml`. Для честной сверки реализации со спецификацией есть `docs/status/spec_coverage_map.yaml` и `docs/status/spec_gap_audit.md`.