Инцидент 2026-09-30 показал: невалидное имя атрибута (s3-inst) проходило генерацию,
сборку и заливку, а ломалось только у пользователя (Terraform отвергает схему целиком).
- TOOLS/scripts/check_schema_names.sh: проверяет все tfsdk:"..." в сгенерированном Go
на [a-z0-9_]; exit 1 при нарушении, с подсказкой про helpers.ToSnake.
- 03_build_and_upload_provider.sh: вызов стража после 02 (до сборки и заливки).
- ARCHITECTURE.md: страж добавлен в список enforced-скриптов.
- HISTORY: пункт 'не закрыто' заменён на 'закрыто' с проверками.
Проверено: dev/test/prod -> OK; искусственный пример (tfsdk:"s3-inst") -> exit 1.
Инцидент: в 1_dummy (API dev) появились коды s3-inst / s3-ref-root. ToSnake не убирал
дефисы -> в схему уходило tfsdk:"s3-inst" -> Terraform отвергает такие имена и НЕ
загружает схему провайдера целиком (plan/apply падали).
- helpers.go: ToSnake завершается sanitizeAttrName ([a-z0-9_] допустимы, остальное -> _).
Код для API не меняется: json:"s3-inst" в генерате сохранён.
- Проверено: в generated/dev/go нет tfsdk-имён с недопустимыми символами;
go build OK; go test ./internal/... -short PASS.
- DEV_STAND/FPipeGmail: провайдер 2.0.23 -> 2.0.1; после сброса lock/кэша
terraform validate -> Success.
- HISTORY: описан инцидент, причина (данные API изменились после утра), фикс и
особенность: перезапись артефакта под тем же номером требует сброса lock
(init -upgrade хеш не пересчитывает).
dev 2.0.1 перезалит (sha256 linux d25a71a31dbc9ab16e494b3d1f68b38b2214bd530045c5a3003f515bc725e407).
Цикл 03 (01+02 -> сборка linux/windows/darwin -> SHA256SUMS -> GPG -> S3) по dev.
Версия 2.0.1 (ранее удалена при чистке, создана заново).
Проверено:
- sha256 локальной сборки == SHA256SUMS (linux/amd64, 13 172 824 B);
- GET /v1/providers/nubes-dev/nubes/versions -> содержит 2.0.1;
- download linux/amd64 2.0.1 -> HTTP 200;
- в S3 5 объектов (3 zip + SHA256SUMS + .sig), 37.92 MiB.
test и prod не перезаливались (правки ядра туда не включены).
Документация: VERSIONS.md, HISTORY/2026-09-30_dev_release_2_0_1.md.
Код-ревью (раунд 5), п.1/6: pre-check брал схему из GET /instanceOperations/default/{opId},
а payload строился по живой схеме ?fields=cfsParams — два источника. default может
расходиться с живой => риск ложного пропуска modify.
Решение: pre-check вообще не запрашивает схему.
- modifierDesiredEqualsLive(ctx, uid, desired): сравнение по live-кодам
(live[lower(code)]), единственный источник — state.params.
- Значения: похожи на JSON ({/[) — смысловое сравнение; иначе скалярное с
нормализацией (null/"" -> ""; true/false без учёта регистра) — закрывает и
регистр bool.
- Fail-safe сохранён: пусто/нет кода/ошибка live => modify выполняется.
- Удалены: fetchOperationSchemaByID, modifierValuesEqual, lookupLiveParam больше
не участвует в pre-check (остаётся для досылки).
- Тесты переписаны (RawValuesEqual_Scalars/JSON, DesiredEqualsLive: 4 кейса).
Документация: TOOLS/ARCHITECTURE.md -> новый раздел «Modifier Idempotency»
(5 правил контракта); HISTORY — журнал раунда 5.
Проверено: go build ./... OK; go test ./internal/... -short PASS.
- Q1: POST не ретраится (не идемпотентен; Idempotency-Key у API нет) — решение.
- Q2: при ошибке после POST /instanceOperations и до run операция остаётся черновиком;
отмены нет (ни в коде, ни в HAR — DELETE /instanceOperations/{uid} отсутствует).
- Q4: единый контракт жизненного цикла — keep_on_destroy + suspend_on_destroy;
delete_strategy (YAML) = маппинг на них (noop_warn/inverse/error).
Проблема: CreateGenericInstanceUniversalV6 при ЛЮБОЙ ошибке после создания инстанса
возвращал "", а шаблон Create при ошибке не писал ID в state => облачный инстанс
осиротевал (Terraform о нём не знает, повторный apply упирается в страж дубликатов).
- core/instance_create.go: ошибки после получения instanceUid возвращают uid вместе
с ошибкой (POST /instanceOperations, пустой opUid, разбор cfsParams, resolve,
отправка параметров, validate, run, waitForOperationFinish, ensureInstanceCreated).
До создания uid — по-прежнему "".
- templates/instance.go: при err != nil и id != "" -> data.ID + resp.State.Set (partial
state), затем AddError.
- client_test.go: TestCreateGenericInstance_KeepsUIDWhenOperationCreateFails,
TestCreateGenericInstance_EmptyUIDWhenInstanceCreateFails.
- ARCHITECTURE.md: пункт про partial state.
Проверено: 02 (dev) + dev-materialize -> 40 файлов resources_gen содержат фикс;
go build ./... OK; go test ./internal/... -short PASS.
isRetryable (core/http.go): добавлен StatusUnauthorized (401) к {429,502,503,504}.
Gateway может временно отклонять валидный JWT — без этого одиночный 401 ронял
read/plan/поллинг.
- client_test.go: юнит-тест TestIsRetryable (401/429/502/503/504 = true; 400/403/404/500 = false).
- ARCHITECTURE.md: раздел API Resilience приведён к фактическому поведению
(401 реализован; POST не ретраится намеренно).
Проверено: go build ./... OK; go test ./internal/core/... -short OK.
- Core Principles 2-3: 'универсально/генерируется' отнесено к core/ и resources_gen/,
а не ко всему сервис-коду.
- API Resilience: 401 НЕ ретраится (isRetryable = 429/502/503/504, только GET) —
помечено как незакрытый разрыв со спекой.
- Exception Registry: убран удалённый реестр serviceSpecificModifiers
(yaml-generator/main.go), описаны ручные модификаторы + несуществующий modifiers.yaml.
- Новый раздел 'Lifecycle Vocabulary': три несогласованных словаря destroy.
- Rules: 'two registries' -> один реестр + ручные ресурсы.
Полный цикл 03 (01+02 → сборка 3 платформ → GPG → заливка S3) по каждому стенду:
- prod nubes/nubes 1.0.0;
- dev nubes-dev/nubes 2.0.0 (VERSION в profile.env: 2.0.24 → 2.0.0);
- test nubes-test/nubes 3.0.0.
Проверено: sha256 залитого linux-бинарника == локальной сборке на всех трёх;
API /versions отдаёт 1.0.0 / 2.0.0 / 3.0.0; в S3 по 5 объектов на версию.
Последствия (приняты владельцем): версии X.0.0 перезаписаны → у пользователей
с .terraform.lock.hcl будет checksum mismatch (лечится terraform init -upgrade).
Подробности: HISTORY/2026-09-30_release_1_0_0_2_0_0_3_0_0_all_stands.md
Сверка TOOLS/config/prod/services_list.txt с облачным каталогом
(GET /api/v1/svc/services?isProductionReady=true → 36 сервисов):
- активных в файле было 35, лишних нет, но не хватало 151 k8sOpenbao (Vault);
- комментарий «нет в PROD UI» устарел: сервис отдаётся каталогом prod
(isProductionReady=true, resourceRealmTypeId=3).
После правки: 36 активных = 36 в облаке, перегенерация prod дала 36 YAML,
включая 151_k8s_openbao.yaml. dev (40/40) и test (36/36) расхождений не имели.
Удалено (100% мёртвое, ничего не вызывает):
- TOOLS/scripts/10_yaml_stability_run.sh
- TOOLS/scripts/11_yaml_stability_run_latest.sh
- TOOLS/scripts/12_generate_yamls_latest.sh
- TOOLS/scripts/13_generate_yamls_clean.sh
- TOOLS/scripts/02_generate_resources_and_docs_template_v2.sh
- TOOLS/config/services_list.txt (общий список: код его не читает, а как
«объединение» он устарел — в нём активен id 27, который в test/prod
закомментирован как «нет в UI», и отсутствуют 87/88/97/153 из dev)
Все ссылки в документации указывали на профильные списки либо будут
исправлены отдельным коммитом.
- TOOLS/README.md: раздел «Канонический пайплайн (порядок шагов)» и описание
безопасной генерации (staging → атомарная замена, бэкапы, маркер .stand);
- TOOLS/ARCHITECTURE.md: ссылки devops/… → TOOLS/config/<stand>/…;
- HISTORY/2026-09-30_yaml_pipeline_hardening.md: полная история изменений
(что было не так, что сделано, прогон по стендам, коммиты, проверки).
10/11/12/13 + 02_..._template_v2 нерабочие (зовут 01 без --profile, ищут
*.token в корне репо) и не вызываются никаким рабочим скриптом (ссылки —
только в исторических HISTORY/NOTES). Теперь падают сразу с подсказкой
канонического пути вместо мнимой работы; 13 вдобавок больше не может
сделать rm -f provider/resources_yaml/*.yaml.
Проверено: bash -n OK, guard отдаёт exit 2.