Перейти к содержимому

dwe vars — работа с песочницей vars:

vars: — единственный формализованный дом для произвольных, специфичных для проекта значений в объединённом 3-слойном конфиге (см. workspace.md → Строгий корень + песочница vars:). Поскольку теперь каждое пользовательское значение живёт под одним namespace, dwe vars — это первоклассный инструмент для перечисления, чтения, редактирования и трассировки этих значений.

Var — это листовой dot-path под vars: — например, vars.db.password. Вложенные map’ы — это namespace’ы (узлы дерева), а не vars. Для:

vars:
db:
user: root
password: secret
app:
timeout: 30

листья (vars) — это vars.db.user, vars.db.password и vars.app.timeout; vars.db и vars.app — namespace’ы.

Каждый var разрешается по трём слоям конфига:

СлойИсточникЗначение
defaultworkspace.yml / defaults.ymlТрекаемый, общекомандный дефолт
localworkspace/local.ymlGitignore’нутый, персональный override
currentпосле mergeТо, во что реально разрешается ${vars.x} (local побеждает)

Origin var’а — это наивысший слой, дающий значение, то есть файл, который «выигрывает» merge для этого пути.

Префикс vars. необязателен. Внутри dwe vars любой путь — это var, поэтому get, set, inspect и фильтр namespace у list принимают сокращённую форму: dwe vars get db.host эквивалентно dwe vars get vars.db.host. Префикс также убирается из вывода (в JSON, автодополнении и хранилище остаётся канонический путь). Голова не из vars нормализуется внутрь песочницы (project.name → vars.project.name) и никогда не резолвится в реальный конфиг проекта — так что ограничение на чтение и allowlist на запись из контейнера сохраняются.

dwe vars list [namespace]

Плоский список каждого листа vars.* с его эффективным значением и бейджем слоя (local, когда действует override из local.yml, иначе default). Необязательный аргумент namespace фильтрует поддерево (например, dwe vars list db), зеркаля dwe commands list.

dwe vars get <var>

Вывести одно значение. Листовой путь печатает скаляр; путь-namespace (vars.db) печатает всё поддерево как YAML. Показывается эффективное (после merge) значение. Путь, который ни во что не разрешается, — это типизированная ошибка vars_not_found. Чтение ограничено vars.* — путь, чей первый сегмент не vars (например, project.name), возвращает vars_not_found, а не разрешается по остальному конфигу проекта, чтобы доступная из контейнера поверхность vars не утекала произвольный конфиг хоста.

dwe vars inspect <var>

Полная картина по одному var’у:

  • Значения по слоям — default (общекомандный), local-override, current (после merge).
  • Origin — относительный к проекту файл, выигрывающий merge.
  • Каждое статическое использование — каждое место ссылки на var, как file:line с текстом совпавшей строки. См. статическое сканирование использований.

Var, который нигде не разрешается и не имеет использований, — это vars_not_found. Inspect сопоставляет как точный путь, так и префикс namespace: dwe vars inspect db показывает использования vars.db.host, vars.db.user и т. д. Как и get, инспекция ограничена vars.* — путь не из vars даёт vars_not_found.

dwe vars set <var> [value]
dwe vars set <var> --generate SPEC [--force]

Записать override var’а в workspace/local.yml, сохраняя окружающие комментарии и форматирование (см. сохраняющая комментарии запись).

  • Ограничение пути до vars.* — любой <var>, чей первый сегмент не vars, отклоняется с vars_path_invalid. Это также граница доверия контейнера (см. поведение в контейнере).

  • Приведение значения — аргумент value парсится как один YAML-скаляр. Типизированные скаляры становятся типизированными: true / false → bool, 42 → int, 1.5 → float, простой текст → строка. Явное кавычивание оставляет строку: set x '"42"' записывает строку "42". Map’ы ({a: b}) и последовательности ([a]) отклоняются — var это лист. Закреплённые неоднозначные случаи:

    ВводРезультат
    "" (пустой аргумент, съеден shell)YAML null
    '""' (явный пустой литерал)пустая строка
    null, ~YAML null
    yes / no / on / offстрока (не bool YAML 1.1)
    0755, 01 (ведущий ноль)строка (без потери при octal-реинтерпретации)
    1.2.3строка
    2024-01-02 (голый timestamp)строка
  • Без value (интерактивно) — открывает форму huh с одним полем ввода и контекстом в стиле inspect (текущие значения по слоям). Submit пишет через тот же путь. В JSON / неинтерактивном режиме опускание value — это типизированная ошибка vars_value_required (форма не открывается).

  • --generate SPEC (случайное значение) — пишет свежее случайное значение вместо позиционного; см. Генерация случайного значения.

--generate заполняет var, которому нужно случайное значение вроде секрета — ключ приложения, секрет сессии, ключ Fernet, — без однострочника на другом языке:

Окно терминала
dwe vars set app.secret_key --generate hex # 64 hex-символа
dwe vars set app.fernet_key --generate base64url:32 # валидный ключ Fernet
dwe vars set app.instance_id --generate uuid
SPECРезультат
hex[:N]N случайных байт в hex нижнего регистра — 2N символов
base64url[:N]N случайных байт в URL-safe base64 с паддингом = — 4·⌈N/3⌉ символов
uuidUUID версии 4 по RFC 9562 (8-4-4-4-12); N не принимает

N считает байты энтропии, а не символы вывода, поэтому одинаковый N даёт одинаковую стойкость в любой кодировке. По умолчанию 32, допустимо целое от 1 до 1024. Паддинг намеренный: ключ Fernet — это ровно base64url:32, а Fernet отвергает форму без паддинга. Байты берутся из криптографического источника случайности операционной системы.

  • Всегда строка. Значение минует приведение значения и пишется как YAML-строка, поэтому hex из одних цифр после перезагрузки остаётся строкой.
  • Существующие значения сохраняются. Если в local.yml по этому пути уже лежит не-null значение, set отказывает с vars_value_exists и не трогает файл — повторный запуск команды настройки никогда не ротирует ключ, которым приложение уже пользуется. Чтобы сгенерировать заново, передайте --force. Дефолт из workspace.yml / defaults.yml или явный null в local.yml не мешают. Проверка идёт под проектными локами, так что параллельный writer не вклинится между проверкой и записью.
  • Ошибки использования — первыми. Позиционное значение вместе с --generate — это vars_value_ambiguous; некорректный spec — неизвестный вид, нецелый или вне диапазона N, N у uuid, пустой --generate= — это vars_generate_invalid; --force без --generate — это vars_force_requires_generate. Все три возникают до захвата любого лока.
  • Без формы. --generate никогда не открывает интерактивную форму, поэтому одинаково работает в JSON и неинтерактивном режиме.

Сгенерированное значение печатается в подтверждении и в JSON-выводе, как у любого другого set — dwe vars никогда не маскирует (см. Вывод не маскируется). Оно также лежит открытым текстом в gitignore’нутом local.yml. Это подходит для персонального локального ключа; учётные данные, общие для команды, место в отслеживаемом слое в зашифрованном виде, через dwe secrets set <vars.path> --stdin (см. secrets.md).

set захватывает проектные локи (симметрия с dwe services enable/disable, которые делят тот же writer local.yml — set без локов мог бы гонками пересечься с держащим лок toggle на том же файле). Он не запускает preflight (это не мутация lifecycle/стека). При любой ошибке после записи прежние байты local.yml восстанавливаются, затем конфиг перезагружается, чтобы последующий вывод отражал запись.

Запуск dwe vars без подкоманды открывает интерактивный браузер (тот же виджет, что и у dwe commands): дерево namespace’ов всех vars, оверлей inspect и действие edit (Enter на листе).

Редактирование без выхода (терминалы ≥ 80 колонок). Enter открывает форму set как оверлей поверх браузера — дерево остаётся видимым (затемнённым) под ней. Введите новое значение (некорректный ввод — map, последовательность, непреобразуемый скаляр — отклоняется на месте), нажмите Enter для сохранения, и оверлей закроется: отредактированная строка обновится на месте (новое значение + бейдж слоя), оверлей inspect отразит новое значение, а в строке статуса на ~2 секунды мигнёт подтверждение ✓ <path> = <value>. esc отменяет и возвращает в браузер с сохранённым состоянием (курсор, раскрытие, фильтр); ctrl+c завершает весь браузер. Если блокировки проекта заняты (например, приостановленный dwe deploy run), сохранение не проходит и в строке статуса мигает ошибка блокировки — браузер остаётся открытым, а local.yml не изменяется.

Два наблюдаемых поведения отличаются от отдельного dwe vars set:

  • Подтверждения — это временная вспышка в строке статуса, а не stdout. После выхода из браузера в терминале нет следов правок — при выходе ничего не печатается. (Используйте dwe vars get <path> или заново откройте браузер, чтобы проверить значение.)
  • Редактирование без выхода работает только на пути оверлея ≥ 80 колонок. На более узком терминале браузер использует плоский запасной селектор, который выходит, чтобы запустить форму set, и затем открывается заново — старый цикл «выход после коммита», по одной правке за раз.

В неинтерактивном контексте — нет TTY, DWE_NONINTERACTIVE=1 или запуск внутри контейнера — голая команда откатывается к dwe vars list. Аргумент namespace (dwe vars vars.db) тоже выводит список, а не браузер.

Значение vars.* в отслеживаемом слое может быть зашифрованным маркером ENC[age:…], а не открытым текстом — именно так общекомандные учётные данные живут в git. Загрузчик конфига расшифровывает маркеры в памяти, поэтому машина, на которой есть identity проекта, видит в dwe vars везде обычный открытый текст и всё написанное ниже к ней не относится. Полная модель — в secrets.md.

Когда значение не удаётся расшифровать здесь (нет identity, он не тот или payload повреждён), dwe vars показывает <encrypted> — но никогда шифротекст:

  • list рендерит <encrypted> вместо значения и добавляет (encrypted) к бейджу слоя. JSON-запись получает "encrypted": true (иначе поле опускается, так что вывод для проекта без секретов побайтно совпадает с прежним). Поле layer в JSON остаётся чистым (local / default) — суффикс это отображение, а encrypted — машиночитаемый ответ.
  • get на листе печатает <encrypted> и завершается с кодом 0; на namespace он печатает поддерево, рекурсивно заменив каждый нерасшифрованный маркер, — и в тексте, и в JSON.
  • inspect маскирует каждое послойное поле (default / local / current, в тексте и в JSON), содержащее нерасшифрованный маркер, и добавляет строку secret: с источником identity или причиной, по которой его нет.
  • TUI-браузер показывает тот же плейсхолдер и бейдж, а его форма редактирования никогда не подсовывает маркер как «текущее значение», которое выглядит редактируемым.

Флаг выводится из слоя-origin, а не из «любого слоя»: маркер в defaults.yml, перекрытый открытым значением в local.yml, отрендерится открытым текстом, а маркер в local.yml поверх открытого дефолта — как <encrypted>.

dwe vars setdwe secrets set
Пишет вworkspace/local.yml (в gitignore)workspace/defaults.yml или workspace.yml (отслеживаются)
Хранит какоткрытый текстмаркер ENC[age:…]
Приведение типовYAML-скаляр (true → bool, 42 → int)нет — всегда строка
Префикс vars.опционаленобязателен
Требуетничегоrecipient проекта

vars set по-прежнему пишет открытый текст, и открытое значение в local.yml побеждает зашифрованный дефолт. Когда путь, который вы задаёте, перекрывает маркер в нижележащем слое, set печатает одну заметку в stderr (подавляется в JSON-режиме):

note: vars.telegram.token is an encrypted secret in workspace/defaults.yml; this plaintext override wins locally

dwe vars list, get и inspect печатают эффективные значения дословно — ничего не маскируется. Некоторые проекты обоснованно держат в workspace/local.yml / .env настоящие сторонние учётные данные (API-токены, DSN сервисов), потому что их нельзя подделать локально; такие значения попадают в вывод целиком. set так же печатает записанное значение, включая созданное через --generate.

Маскирование не сделано — тот, кто может запустить dwe vars list, и так может прочитать workspace/local.yml. Что маскирование изменило бы, так это то, куда значения уезжают, и следить надо именно за этим: перед вставкой в сессию AI-агента, в скриншот или в демо предпочитайте dwe vars get <path> полному list.

Единственный плейсхолдер в этом выводе — не маскирование: <encrypted> означает, что значение не удалось расшифровать на этой машине, поэтому печатать просто нечего. Расшифрованный секрет печатается целиком, как любое другое значение.

dwe vars set пишет workspace/local.yml через round-trip yaml.Node: загружает файл как дерево узлов, патчит только целевой путь и сериализует заново. Комментарии и порядок ключей сохраняются; меняется только отредактированный узел значения. Приведение учитывается на уровне узла — перезапись закавыченной строки на true/42 выдаёт голый скаляр, чтобы он перезагрузился типизированным.

Пустые строки не сохраняются: документ перекодируется, а YAML-библиотека держит пустые строки только внутри блоков комментариев, поэтому правка может заодно переотступить файл под свой шаг отступа. Для local.yml это допустимо — это gitignore-состояние разработчика. dwe secrets set / init / rekey пишут отслеживаемые в git файлы слоёв и поэтому используют другой writer, построчный splice, который пустые строки сохраняет — см. secrets.md.

Этот writer стоит за dwe vars set, dwe services enable/disable и мастером настройки, так что комментарии в local.yml переживают любую правку под управлением DWE. Коллизии map-над-скаляром отклоняются (чтобы не отбрасывать молча данные разработчика), с единственным задокументированным исключением — легаси голый-int порт-лист апгрейдится до map’а {port: N}.

inspect (и оверлей inspect в браузере) сообщает каждое место, где на var статически ссылаются. Сканирование field-aware, а не сплошной grep по файлу — оно обходит каждый конфиг-файл и инспектирует только те поля, которые runtime реально рендерит, поэтому избегает ложных срабатываний (комментарии, закавыченные литералы, не рендерящиеся поля) и ложных пропусков (ссылки в структурных ключах). Отслеживаются два синтаксиса ссылок:

  1. Шаблонные ссылки ${vars.x} — в полях, рендерящихся движком ${...} (декларативные команды cmd / argv / compose_args / argv_append_from / env / with, шаги пайплайна timeout / files_gate.command, info.yml text / value, скалярный when:, docker.yml project_name, подтверждающие промпты) и в config-шаблонах рендера под workspace/templates/config/** (произвольный текст → построчный скан и ${vars.x}, и {{ resolve .Raw "vars.x" }}). Это шаблоны, которые материализует подсистема config-рендера; соседние пакеты ide / ai / git используют сырой субстрат {{ }} (без разрешения ${...}) и не сканируются. Внутренние пробелы (${ vars.x }) и ведущая цифра не совпадают — сканер переиспользует собственный паттерн рендерера.
  2. Структурные dot-path’ы vars.x — значения from: / default_from: и ссылки внутри типизированного when.expr (не голый скаляр when: vars.x).

Сопоставление по точному пути или префиксу namespace: ${vars.db.host} засчитывается и vars.db.host, и vars.db.

Сам блок vars: верхнего уровня не сканируется: его значения — это данные конфига, разрешаемые по dot-path и никогда не рендерящиеся повторно, поэтому ${vars.x} или from:, появляющиеся внутри vars:, не являются использованием во время выполнения.

Оговорка (печатается в выводе): динамически собираемые пути и доступ к полям через Go-шаблоны (.Vars.x / .Raw.vars.x в шаблонах {{ ... }}) не отслеживаются. Сканирование покрывает только формы ${...} и структурный-dot-path.

Каждая read-only подкоманда поддерживает --output json (с --pretty) и держит stdout чистым — типизированные ошибки сериализуются в конверт {"error":{…}} на stderr.

КомандаФорма
get{"var": "...", "value": <any>, "encrypted": <bool>}
list{"vars": [{"path": "...", "value": <any>, "layer": "local|default", "encrypted": <bool>}]}
inspect{"var": "...", "layers": {"default": <any>, "default_set": <bool>, "local": ..., "local_set": ..., "current": ..., "current_set": ...}, "origin": "...", "encrypted": <bool>, "secret": "...", "usages": [{"file": "...", "line": N, "kind": "...", "text": "..."}]}
set (со значением или --generate){"var": "...", "value": <any>}

Булевы *_set на слоях inspect отличают явное значение null от отсутствующего слоя. set без значения в JSON-режиме — это ошибка vars_value_required (форма не открывается).

Коды ошибок set в конверте:

КодКогда
vars_path_invalidпуть — не лист vars.*
vars_value_requiredзначения нет, а формы в этом режиме нет
vars_value_invalidзначение — не один YAML-скаляр
vars_value_ambiguousпозиционное значение вместе с --generate
vars_generate_invalidнекорректный spec --generate
vars_force_requires_generate--force без --generate
vars_value_exists--generate, в local.yml уже есть значение, --force нет (detail var; hint называет --force)
vars_not_container_writableзапись из контейнера в var вне bridge.vars_writable

encrypted и secret помечены omitempty: проект без зашифрованных значений выдаёт ровно те же поля, что и раньше — см. Зашифрованные значения.

dwe vars достижима изнутри bridged-контейнера, но записи запрещены по умолчанию (deny-by-default):

  • get / list / inspect достижимы всегда.
  • set достижима, но контейнерный set успешен только когда целевой var совпадает с записью в allowlist’е проекта bridge.vars_writable. Несовпадающий var отклоняется с vars_not_container_writable. С хоста set неограничен.
  • set --generate подчиняется тому же allowlist’у. Allowlist проверяется до отказа из-за существующего значения, поэтому запрещённый var даёт vars_not_container_writable, но никогда не vars_value_exists — контейнер ничего не узнаёт о том, задан ли var.
  • TUI авто-отключается в контейнере (неинтерактивный откат к list).

bridge.vars_writable — это top-level конфиг-блок, отличный от per-service блока services.<name>.bridge: в service.yml, который управляет включением контейнера. Этот top-level блок — это общепроектная политика контейнерной записи: в какие vars контейнеризованный dwe vars set может писать в хостовый local.yml.

# workspace.yml (или любой слой — 3-слойный merge)
bridge:
vars_writable:
- vars.db.password # точный путь
- vars.app.* # префиксный wildcard (граница по точке)

bridge входит в строгий корневой allowlist; он мёржится по трём слоям, как любой другой формализованный блок. Семантика паттернов использует реальную границу по точке, а не наивное префиксное совпадение:

  • Точный паттерн (vars.db.password) совпадает только с этим идентичным путём.
  • Wildcard vars.x.* совпадает с target только когда target начинается с base + ".". Так vars.db.* разрешает vars.db.host, но запрещает vars.db, vars.dbx.host и vars.database.host.

Пустой или отсутствующий список vars_writable означает отсутствие контейнерных записей — безопасный дефолт. Некорректные паттерны fail closed (запрет).

Что на самом деле даёт добавление вара в allowlist. Поля шага пайплайна (cmd:, when.cmd, check.cmd, timeout:) рендерятся через подстановку ${...}, а отрендеренный cmd: уходит в хостовый sh -c как текст программы — подстановка текстовая, без shell-квотирования (см. Шаблоны в полях шага). Поэтому вар, который одновременно доступен на запись из контейнера и используется в команде пайплайна, позволяет контейнеру выбрать часть хостовой командной строки: значение x; some-command выполнит some-command на хосте при следующем dwe deploy run. Это и есть естественная комбинация — контейнер обычно хочет записать вар именно потому, что его читает пайплайн, — так что считайте vars_writable делегированием ввода в хостовый shell тому, кто имеет доступ к контейнеру, и держите в списке вары, которые потребляются как данные (рендер конфигов, exports.env), а не подставляются в команды.

Чтобы контейнер мог регенерировать конфиги сервисов после set, dwe render config тоже достижима из контейнера (остальные подкоманды render — env / ide / ai / git — остаются только хостовыми). Поскольку render config --harvest мутирует хостовое состояние (.dwe/generated.yml), контейнерный render config --harvest отклоняется с render_harvest_host_only — границу пересекает только read-only render.

  • dwe vars get <var> — вывести эффективное значение var’а
  • dwe vars list [namespace] — перечислить листья vars.*
  • dwe vars inspect <var> — значения по слоям, origin и использования
  • dwe vars set <var> [value] — записать override в local.yml (с сохранением комментариев)
  • dwe vars set <var> --generate SPEC [--force] — записать случайное значение hex / base64url / uuid
  • dwe secrets set <vars.path> [value] — записать зашифрованное значение в отслеживаемый слой (secrets.md)
  • dwe secrets status — сообщает про каждое зашифрованное значение и можно ли его прочитать здесь
  • dwe render env / dwe render config — регенерировать .env / конфиги сервисов из объединённого конфига
  • dwe services enable / disable — переключатели сервисов (с сохранением комментариев)