Forge
markdowndeeb25a2
1# ADR 0149: Точечные привязки `settings.toml` к переменным окружения (`*_env`)
2
3**Статус:** Accepted · Implemented
4**Дата:** 2026-05-24
5
6## Связанные ADR
7
8| ADR | Роль |
9|-----|------|
10| [0028](0028-user-settings-toml-localappdata-and-secrets.md) | `settings.toml`, snake_case, секреты отдельно |
11| [0040](0040-lsp-launch-line-settings-toml-presets-and-environment.md) | LSP: `executable_env` / `arguments_env` в профилях |
12| [0023](0023-environment-readiness-glance.md) | readiness: какие env читаются, без дампа `environ` |
13| [0144](0144-intercom-team-transport-cide-sync-and-reference-service.md) | `intercom.transport` |
14
15---
16
17## Контекст
18
19Пользовательский `settings.toml` часто копируется между машинами или хранится в общем профиле разработчика. Абсолютные пути к бинарникам (LSP, Intercom service, cursor-agent) не должны быть единственным способом конфигурации. При этом подстановки `$VAR` внутри строк TOML **не** используем — хуже валидировать и показывать в Environment readiness ([0040](0040-lsp-launch-line-settings-toml-presets-and-environment.md) §4).
20
21Нужен **явный** парный ключ: литерал в TOML + опциональное имя переменной окружения.
22
23---
24
25## Решение
26
27### 1. Соглашение имён
28
29Для строкового поля `<field>` в TOML добавляется опциональный ключ **`<field>_env`** (snake_case), в модели C# — **`<Field>Env`** (`JsonPropertyName` через общий snake_case сериализатор).
30
31Пример:
32
33```toml
34[intercom.transport]
35local_server_path = "tools/intercom-service/IntercomService.exe"
36local_server_path_env = "CASCADE_INTERCOM_SERVER_EXE"
37```
38
39### 2. Приоритет резолва (`SettingsEnvResolver`)
40
41На **чтении** (runtime), не при сохранении файла:
42
431. Если `<field>_env` — sentinel **`PATH`** (регистронезависимо) → для **запуска процесса** (LSP `executable`, ACP `cursor_acp_path`): эффективный путь **пустой**; дальше пресет + поиск команды в **PATH** ([0040](0040-lsp-launch-line-settings-toml-presets-and-environment.md)). Это **не** чтение переменной окружения `PATH`.
442. Иначе, если `<field>_env` — **имя переменной** и `GetEnvironmentVariable` возвращает непустую строку → использовать её.
453. Иначе → литерал `<field>` из TOML.
464. Дальше по домену — пресеты LSP, legacy defaults, fallback intercom-service и т.д.
47
48Пустой `*_env` = привязки нет (для LSP с пресетом эквивалентно п.1–3 с пустым `executable` и поиском в PATH). Пустая **именованная** переменная **не** перекрывает литерал.
49
50`Environment.ExpandEnvironmentVariables` для `%VAR%` внутри литерала **не** смешиваем с `*_env`; при необходимости — только в отдельных резолверах путей (как MCP JSON path).
51
52### 3. Первая волна полей (v1)
53
54| Секция | Поле | `*_env` | Типичное значение |
55|--------|------|---------|-------------------|
56| `[languages.*.*]` профиль | `executable` | `executable_env` | **`PATH`** (дефолт для OmniSharp/Marksman) или имя переменной с абсолютным путём |
57| то же | `arguments` | `arguments_env` | имя переменной (sentinel `PATH` не используется) |
58| `[intercom.transport]` | `local_server_path` | `local_server_path_env` | `CASCADE_INTERCOM_SERVER_EXE` |
59| `[intercom.transport]` | `base_url` | `base_url_env` | `CASCADE_INTERCOM_BASE_URL` |
60| `[ai.acp]` | `cursor_acp_path` | `cursor_acp_path_env` | `CURSOR_ACP_AGENT_PATH` |
61| `[agent_notes]` | `config_path` | `config_path_env` | `CASCADE_AGENT_NOTES_CONFIG` |
62| `[agent_notes]` | `kb_base_overlay_path` | `kb_base_overlay_path_env` | `CASCADE_KB_BASE_OVERLAY` |
63
64Секреты (API keys, JWT) — по-прежнему только `*-secrets.toml` / [0028](0028-user-settings-toml-localappdata-and-secrets.md), **не** через `*_env` в основном файле.
65
66Уже существующие process-wide переменные (`AGENT_NOTES_FILE`, `NETCOREDBG_PATH`) сохраняют приоритет там, где код читает их **напрямую**; `config_path_env` дополняет путь из `[agent_notes]`, не заменяет `AGENT_NOTES_FILE`.
67
68### 4. Отклонено
69
70- `launch_from_environment` (глобальный bool) — заменён точечными `*_env` ([0040](0040-lsp-launch-line-settings-toml-presets-and-environment.md) §3 обновлён).
71- Inline-таблица `{ env = "...", fallback = "..." }` — усложняет десериализацию и UI.
72- Универсальный `lookup_environment_variable_name` на все поля `CascadeIdeSettings` — вне scope v1.
73
74---
75
76## Последствия
77
78- `SettingsEnvResolver` — единая точка резолва.
79- `ResolveForRuntime()` LSP, `IntercomTransportSettings` helpers, `AgentNotesRuntimeLoader`, `KbBaseOverlayPathResolver`, `CursorAcpAgentPath` получают уже разрешённые строки или вызывают resolver в месте использования.
80- Environment readiness: при заданном `*_env` и пустом env — явная подсказка в LSP-строках (имя переменной без значения).
81- Расширение на новые поля — новый ADR-подпункт или дополнение таблицы §3.
82
83---
84
85## Статус реализации
86
87| Элемент | Артефакт |
88|---------|----------|
89| Резолвер | `Services/SettingsEnvResolver.cs` |
90| LSP | `LanguageServerLaunchProfile`, [0040](0040-lsp-launch-line-settings-toml-presets-and-environment.md) |
91| Intercom / ACP / agent_notes | модели + call sites |
92| Тесты | `SettingsEnvResolverTests`, deserialize samples |
93| Дефолты | комментарии в `Settings/defaults-settings.toml` |
94
View only · write via MCP/CIDE