Skip to content

feat(env): типизированная env-схема (контракт env-schema.1, env-catalog item 5) - #33

Merged
alexnikolskiy merged 3 commits into
mainfrom
feat/env-schema
Jul 23, 2026
Merged

feat(env): типизированная env-схема (контракт env-schema.1, env-catalog item 5)#33
alexnikolskiy merged 3 commits into
mainfrom
feat/env-schema

Conversation

@alexnikolskiy

Copy link
Copy Markdown
Collaborator

Что сделано

Офисная часть item 5 инициативы env-catalog (контракт — control-center docs/architecture/contracts/env-schema.md + scripts/src/contracts/env-schema-1.schema.json, PR cc#131).

  • apps/server/src/env.ts — единственная точка чтения process.env в репо: zod-реестр всех 42 переменных (типы int/bool/enum/url/duration_ms по семантике контракта), fail-fast валидация на старте — печатаются все невалидные переменные разом (safeParse), тихих дефолтов для невалидных значений нет.
  • npm run env:schema → детерминированный JSON-документ env-schema.1 в stdout (repo trading-office, generated_from: apps/server/src/env.ts, variables отсортированы по name, без timestamp; файл не коммитится). Экспорт прогнан через нормативный валидатор control-center (validateEnvSchemaDocument) — VALID.
  • npm run env:docsENV.md и .env*.example (server/web/deploy) генерируются из схемы; правило секретов соблюдено (значения секретов нигде не появляются, NAME= + комментарий про SOPS/age-контур).
  • loadConfig переведён на схему целиком (механическая замена всех серверных чтений); поведение для валидных env не изменено, существующие тесты (включая typed-403 из feat(security): fail-closed operator auth in connected mode; typed 403 on authority refusal #32) не тронуты и зелёные.
  • Гейты в тестовом прогоне (= красный CI без отдельных джобов):
    • «Полнота схемы» (envCompleteness.test.ts): process.env вне env.ts — падение; VITE_*-имена в web-исходниках обязаны быть объявлены в схеме; allowlist только для compose-lpc-agents.mjs (declaration-only ULPC_DIR).
    • «Генерация» (envDocs.test.ts): рендер артефактов сравнивается с закоммиченными байт-в-байт.

Fail-closed #32 не ослаблен

OFFICE_OPERATOR_PASSWORD в схеме — required: false + secret: true (standalone/fixture-режим без него легален); описание фиксирует connected-правило. Авторитетный гейт SEC-O1 (connected-режим отказывается стартовать без непустого пароля) остаётся в loadConfig и покрыт прежними тестами. Инварианты «в trading-lab-режиме URL/токены заданы явно» тоже сохранены дословно (проверка по сырому env, дефолт схемы не считается).

Границы

  • VITE_* объявлены в схеме (owner_unit office-web, build-time), но точка чтения web-части остаётся import.meta.env/vite.
  • OFFICE_IMAGE_TAG (compose) и ULPC_DIR (build-тул floor) — declaration-only.

Осознанные изменения поведения (только для НЕвалидных env)

Раньше мусорные значения тихо падали в дефолт (num()/=== 'true'); теперь сервер отказывается стартовать и перечисляет все ошибки — это и есть цель контракта. Для валидных значений поведение бит-в-бит прежнее.

Проверки

  • npm run typecheck — зелёный (все workspaces)
  • npm run test — зелёный: 516 тестов (server 306, из них 28 новых env-тестов; web 172; floor 27; gateway 11)
  • npm run build + verify:assets — зелёные
  • Экспорт валиден по JSON Schema и TS-валидатору контракта control-center

🤖 Generated with Claude Code

alexnikolskiy and others added 3 commits July 24, 2026 01:56
…v-catalog item 5)

apps/server/src/env.ts — единственная точка чтения process.env в репо:
zod-реестр всех 42 переменных (типы int/bool/enum/url/duration_ms по
контракту), fail-fast валидация на старте с перечислением всех невалидных
переменных разом, экспорт документа env-schema.1 (repo trading-office)
через `npm run env:schema` (детерминированный JSON в stdout, сортировка
по name, без timestamp). VITE_* объявлены как build-time (owner_unit
office-web), OFFICE_IMAGE_TAG/ULPC_DIR — declaration-only.

loadConfig переведён на схему целиком (механическая замена чтений);
кросс-переменные fail-closed гейты (#32 SEC-O1: connected-режим требует
непустой OFFICE_OPERATOR_PASSWORD; явные TRADING_LAB_READ_URL/TOKEN и
TRADING_PLATFORM_READ_URL/TOKEN) остаются в config.ts и не ослаблены —
в схеме пароль описан как required:false + secret:true, потому что
standalone (fixture) без него легален.

Тесты (TDD): env.test.ts — типы/дефолты/агрегация ошибок/валидность и
детерминизм экспорта; envCompleteness.test.ts — гейт «Полнота схемы»
(process.env вне env.ts = красный CI, allowlist только для
compose-lpc-agents.mjs). Экспорт прогнан через нормативный валидатор
control-center (validateEnvSchemaDocument) — VALID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`npm run env:docs` (apps/server/src/envDocs.ts) рендерит из env-схемы:
корневой ENV.md (полная таблица переменных), apps/server/.env.example,
apps/web/.env.example (VITE_*), deploy/.env.example (compose-подмножество).
Правило секретов соблюдено: значения секретов нигде не появляются —
NAME= с комментарием про SOPS/age-контур (b2c-ops-hardening item 3).

envDocs.test.ts — гейт «Генерация»: рендер сравнивается с закоммиченными
артефактами байт-в-байт, дрейф = красный CI (работает внутри npm test —
отдельного CI-джоба не нужно).

README сервера: ручная таблица переменных заменена ссылкой на ENV.md
(item 7 — ручные списки удаляются); заодно поправлено устаревшее «авто-
загрузки .env нет» (loadEnv.ts подхватывает apps/server/.env c #24).
ROADMAP: зафиксирована офисная часть env-catalog item 5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Свежая установка TS в CI (noUncheckedIndexedAccess по строгому разрешению)
ругалась на m[1] из matchAll, enum_values[0] и прямой каст
EnvSchemaVariable → Record — поправлено без изменения смысла тестов.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexnikolskiy
alexnikolskiy merged commit 69cea11 into main Jul 23, 2026
1 check passed
@alexnikolskiy
alexnikolskiy deleted the feat/env-schema branch July 23, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant