docs: move root prompts

This commit is contained in:
Repinoid
2026-09-22 21:26:26 +03:00
parent 73357d0e0f
commit c634a1c51b
10 changed files with 0 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
# Задача: FindInstanceByDisplayName не находит существующий инстанс — анализ
## Контекст
Провайдер `terraform-provider-nubes` v5.0.65, test-стенд.
Инстанс: `NaeelOrg`, serviceId=19, instanceUid=`3f0850f2-3506-4efd-b84b-7270b5027ab5`, статус Running.
## Симптом
`terraform plan` с `adopt_existing_on_create=true`, `resource_name="NaeelOrg"` → `+ create`. Apply → «уже существует».
## Доказано curl-тестами
1. Search API: `GET /instances?search=NaeelOrg&serviceId=19&isAuxiliary=false&isDeleted=false` + `UA: Mozilla/5.0` → находит.
2. GetInstanceStateRaw: `GET /instances/3f0850f2-...` → возвращает, isDeleted=false, Running.
## Ключевые файлы
- `provider/internal/core/client.go` — `FindInstanceByDisplayName()`:568, `doRequest()`:949, `GetInstanceStateRaw()`:753, `isInstanceDeleted()`:741
- `provider/internal/resources_core/resource_diagnostics.go` — `PlanExistingResourceDiagnostics()`:14
- `provider/internal/resources_core/crud.go` — `CreateResource()`:21, `adoptExistingInstanceOnCreate()`:142
- `generated/test/go/19_vc_org_resource.go` — `ModifyPlan()`
- `TEST_STAND/kuber/resources.tf` — манифест
- `HISTORY/OPUS/3006_0_answers.md`:220 — search ✅ / fallback ❌
## Задание
Найти **точно**, почему `FindInstanceByDisplayName("NaeelOrg", 19)` возвращает nil. Если не хватает данных — сказать, какой curl-тест запустить.
+46
View File
@@ -0,0 +1,46 @@
# Prompt for Opus — subresource duplicate/exist + state_out
## Файлы для анализа
Только эти:
- `/home/naeel/tf_provider/provider/internal/resources_core/subresource_guard.go` — ВЕСЬ
- `/home/naeel/tf_provider/generated/test/go/90_postgres_database_resource.go` — Create (строки 130-250)
- `/home/naeel/tf_provider/artifacts/output_inventory/running_suspended_output_fields_for_docs.json` — PostgreSQL (svc 90), строки 190-575
## Контекст
После повторного apply subresource `pg_user_5` упал:
```
Операция вернула duplicate/exist, но объект не найден в state_out
```
## Найденная причина
- PostgreSQL `state_out` НЕ содержит `databases` и `users` (там только `externalConnect, internalConnect, monitoring`)
- `SubresourceListKey("database")` → эвристика `name+"s"` → ищет ключ `"databases"` в state_out → `known=false`
- Create-обработка duplicate требует `known && found` для adopt → `known=false` → всегда падает
- Adopt subresource'а для PG **физически невозможен** через текущий механизм
## Конкретные вопросы
### Вопрос 1 (ПРИОРИТЕТ)
`subresource_guard.go` — `FindSubresourceInStateOut`:
- При `known=false` (ключа нет в state_out) — как должен вести себя duplicate/exist?
- Сейчас: AddError "Нарушена консистентность"
- Предлагаемое: доверять API-ошибке `already exists`, считать adopt успешным, вернуть существующий ID
- Верно? Или нужен другой подход?
### Вопрос 2
`SubresourceListKey` / `SubresourceIdentityKey` в `subresource_guard.go`:
- Эвристики `name+"s"` и special-map на 2 сервиса
- Нужен ли явный маппинг ключей из YAML/метаданных API вместо угадывания?
- Где в API взять реальные имена ключей state_out для каждого сервиса?
### Вопрос 3
Есть ли в API эндпоинты для прямого запроса списка subresource'ов (list_databases, list_users) — чтобы не полагаться на state_out?
### Вопрос 4
`IsSubresourceAlreadyExistsError`:
- Маркеры: `уже существует`, `already exists`, `duplicate`, `conflict`
- Достаточно? Нужно ли добавить `409` (HTTP status), `exist`, `already exist`?
## Формат ответа
На каждый вопрос: ДА/НЕТ + код (файл:строка) + конкретное исправление.
Не читай другие файлы.
@@ -0,0 +1,61 @@
# Задача: спроектировать ПРОСТУЮ логику «изменяемости» параметров (CreateOnly vs Modifiable)
## Проблема
Генератор terraform-провайдера строит проверку «Нельзя изменить X» (CreateOnly) на основе
только instance-modify. Из-за этого возникают противоречивые и сломанные ситуации:
`generated/dev/resources_yaml/22_vc_nsxt.yaml`:
- create (id 10), param `needEnableAVI` (id 340) — помечен `is_modifiable: true`;
- instance-modify у `vc_nsxt` НЕТ (modify 111 — это **modifier** `vc_nsxt.network`).
Генератор:
```
ComputeCreateOnly(createParams, instanceModifyParams):
поле считается CreateOnly, если его code нет в instance-modify
```
Следствие: `needEnableAVI` попадает в CreateOnly → генерится жёсткая проверка
«Нельзя изменить need_enable_avi», хотя по YAML параметр `is_modifiable: true`.
Плюс `ConvertParams` вообще **не переносит** `is_modifiable` из ParamSpec в Param —
поле теряется, логика его учесть не может.
## Ключевые файлы (текущая логика)
- `TOOLS/lib/types.go` — `ParamSpec.IsModifiable` (есть, `is_modifiable` сериализуется в YAML)
- `TOOLS/resource-generator/internal/types/types.go` — `Param` (НЕТ поля IsModifiable)
- `TOOLS/resource-generator/internal/loader/loader.go` — `ConvertParams` (не переносит IsModifiable)
- `TOOLS/resource-generator/internal/params/params.go` — `ComputeCreateOnly` (игнорирует is_modifiable и modifier)
- `TOOLS/resource-generator/internal/templates/instance.go` — шаблон, рендерит «Нельзя изменить» из `.CreateOnlyParams`
- YAML: `generated/dev/resources_yaml/22_vc_nsxt.yaml` (modify 111 — `kind: modifier`)
## Существующие понятия операции
В YAML операции бывают видов:
- `kind: instance` (`create` / `modify` / `suspend` / `resume` / `delete`)
- `kind: modifier` (отдельный TF-ресурс, `modify` на родительском инстансе, например `vc_nsxt.network`)
- `kind: subresource`
- `kind: action`
## Цель
Спроектировать **единую, простую и понятную** модель «изменяемости» параметра, чтобы:
1. параметр считался изменяемым, если он изменяем ХОТЯ БЫ через один канал
(instance-modify ИЛИ modifier);
2. «Нельзя изменить» генерировалось ТОЛЬКО для реально create-only параметров;
3. `is_modifiable` из YAML был единственным источником правды (или явно согласован с каналами modify);
4. не было противоречий вида «в YAML is_modifiable:true, а в коде «Нельзя изменить»».
## Вопросы к Opus
1. Какая каноническая модель: вычислять изменяемость по `is_modifiable` (флаг из YAML),
по наличию кода в любом modify (instance + modifier), или по комбинации?
2. Где именно проставлять/вычислять флаг — в yaml-generator (при генерации YAML), или в
resource-generator (при генерации Go)?
3. Как связать modifier-параметры (`vc_nsxt.network`) с parent-инстансом (`vc_nsxt`),
чтобы instance знал, что `needEnableAVI` изменяется через modifier?
4. Минимальный, без legacy-наслоений, набор правил.
Ответ — кратко, с конкретной архитектурой и точками правки (файл + функция).
@@ -0,0 +1,73 @@
# Спроектировать С НУЛЯ архитектуру/логику «ресурсов-модификаторов» (kind: modifier)
## Цель
Перепроектировать модификаторы целиком, чтобы исключить ВСЕ классы багов, не латать по одному.
Нужна единая, полная модель поведения — без догадок и костылей. Перечислить ВСЕ кейсы.
## Что такое модификатор (текущая фактура)
В YAML (генерируется из API) операции бывают:
- `kind: instance` (create/modify/suspend/resume/delete) — обычный CRUD-ресурс;
- `kind: modifier` + `modifier: <name>` — отдельный TF-ресурс, который вызывает `modify`
на родительском инстансе. Сейчас их два: `vc_org.ip_space`, `vc_nsxt.network`.
Реальные примеры:
- `vc_org` → modifier `ip_space` (modify 207), параметр `vIPConfigure` (array-map-fixed);
- `vc_nsxt` → modifier `network` (modify 111), параметры `needEnableAVI`(bool),
`virtualServicesCount`(int>0), `qosProfile`(string), `ipSpaceName`(string),
`routedNetConfiguration`(map-fixed).
## Текущий механизм (что есть — факты, не догадки)
1. Генератор: `TOOLS/resource-generator/internal/templates/modifier.go`
- Create и Update **идентичны**: оба шлют `modify` с полным набором полей.
- `Delete` — **no-op** (комментарий: «no confirmed inverse payload»).
- Схема: `id` computed, `<service>_id` required, поля Optional (или Required если нет default).
2. `resources_core.CompactParams` — выбрасывает пустые строки из payload.
3. `resources_core.BuildActionID(instanceUID, operation, modifierName)` — константный ID,
не привязан к реальной операции (opUid не сохраняется).
4. `core.RunInstanceOperationUniversalByCode` — резолвит code→id через
`GET /instanceOperations/{opUid}?fields=cfsParams` (fallback на `/default/{opId}`);
отправляет переданные params, затем дозаполняет остальные их live-значением
(guard: пропускает параметр, если нет ни ParamValue, ни DefaultValue).
5. `Read` — через `RefreshResourceState`: читает `state_params` инстанса и
перезаписывает input-поля из них.
## Уже выявленные КЛАССЫ багов (все реально случились)
- **A. Сброс create-поля при modify.** modify со сброшенными (null) параметрами
трактуется бэкендом как reset-to-default: `needEnableAVI` стал false после
create=true. Причина: модификатор шлёт только свои поля, `CompactParams` выкидывает
пустые, бэкенд видит «отсутствующий» и сбрасывает.
- **B. Досылка синтетики.** фикс «досылать всё» слал `"0"` для `integer > 0`
(параметр `virtualServicesCount`), API 400 «Invalid format integer > 0».
- **C. Ложное «Нельзя изменить».** `ComputeCreateOnly` считал `needEnableAVI`
CreateOnly (change-forbidden), хотя в YAML `is_modifiable: true` — потому что
генератор не учитывал modifier-канал и терял `IsModifiable`. (Зафиксировано отдельно.)
- **D. No-op Delete оставляет эффект на платформе.** destroy модификатора убирает
ресурс из state, но выделенные IP / включённый ALB остаются на платформе → drift.
- **E. Повторный apply после taint/replace** снова гонит modify — риск повторной
аллокации (для `ip_space`), идемпотентность не гарантирована.
## Вопросы к Опусу (ответить ПОЛНО, по пунктам, с точными местами правки)
1. **Канон «как сравнить и применить».** Должен ли модификатор перед modify
читать текущее состояние и слать ДЕЛЬТУ (только реально изменившиеся поля),
или ПТЦ полный payload? Как детектить drift в Read?
2. **Досылка незаданных полей (паер-заливы A и B).** Какое каноническое правило:
когда досылать live-значение, когда дефолт, когда пропускать? Как не сломать
`integer > 0` и прочие constraints?
3. **Delete/rollback.** Где искать обратный payload? Как правильно поступить, пока
обратный payload НЕ подтверждён API (no-op допустим? явная ошибка? suspend?).
4. **Idempotency + ID.** Как сделать ID модификатора отражающим фактическую операцию
(opUid?) и как предотвратить двойную аллокацию при replace/повторном apply?
5. **Связь с родителем.** Должен ли модификатор использовать `<service>_id` как ссылку
на родителя (depends_on / borrow state), и как читать UUID родителя?
6. **Create vs Update.** Допустимо ли иметь их идентичными, или нужен строго Update-семантик
(нет create, только apply-по-десяти)?
7. **Полный перечень кейсов.** Перечислить ВСЕ edge-кейсы, которые надо покрыть:
create родителя → modifier; remove modifier; replace; partial params; unknown/absent.
Ответ — архитектурный документ (краткий, структурированный), с конкретными файлами
и функциями. НЕ код-ревью, а ПРОЕКТ.
@@ -0,0 +1,64 @@
# Уточнения к архитектуре модификаторов — расхождения с фактическим кодом
Не принимаю предыдущие ответы за истину. Сверка с реальным кодом выявила расхождения.
Прошу пересмотреть/уточнить.
## Факт №1: `OperationSpec` — это алиас `lib.OperationSpec`, не локальный тип
В `TOOLS/resource-generator/internal/types/types.go`:
```go
type OperationSpec = lib.OperationSpec
type ParamSpec = lib.ParamSpec
```
Канонический YAML-контракт лежит в `TOOLS/lib/types.go` (пакет `tf-tools/lib`),
где уже определены `OperationSpec` (Name/ID/Kind/Action/Modifier/Subresource/Man/Params)
и `ParamSpec`.
Ошибка в прошлом ответе: «добавить в types.go:39» — НЕ указано, что это `lib`.
Новые поля `delete_strategy` / `idempotency` / `delete_params` должны быть
в `TOOLS/lib/types.go`, иначе yaml-generator (который тоже импортирует lib)
и resource-generator разойдутся.
Вопрос: подтверждаешь, что новый контракт добавляется в `lib/types.go\` (OperationSpec),
а `resource-generator` получает его через алиас? Или нужно отдельное
resource-generator-специфичное поле (не в lib, а в GenModifier)? Где граница:
что в lib, что локально в GenModifier?
## Факт №2: `normalizeUniversalValueV6` — приватная, живёт в core, принимает core-структуру
Прошлый ответ: «сравнивать desired vs current после normalizeUniversalValueV6».
Но:
- `normalizeUniversalValueV6(val string, param universalCfsParam)` — **приватная** (маленькая буква);
- принимает `universalCfsParam` (структуру пакета `core`);
- сравнение pre-check «desired == current» предполагалось в `resources_core`
(там `RunOperationByCodeWithTimeout`) или в шаблоне модификатора.
Вопрос: ГДЕ правильно делать pre-check и нормализованное сравнение?
- вариант A: в `core` (там доступны и cfsParams, и normalize), экспортировать сравнение;
- вариант B: в `resources_core` — тогда нужен экспортированный компаратор
(`JSONStringsEquivalent` там уже есть), но `universalCfsParam` недоступен;
- вариант C: сравнение только через `JSONStringsEquivalent` по JSON-строкам,
без `normalizeUniversalValueV6`? (но тогда `" 5"` vs `"5"`, `true` vs `1` дадут ложный diff).
Как совместить нормализацию типов (bool→"true", int→"5") с местом, где сравнение
происходит? Конкретный файл+функция.
## Дополнительные сомнения (прошу подтвердить/опровергнуть)
1. **Idempotency pre-check и «полный payload» конфликтуют?** Если desired==current → skip.
Но при этом «полный payload» не шлётся вообще (skip). Это согласуется? Или при
расхождении одного поля всё равно слать полный payload (и это нормализует всё)?
2. **`delete_strategy: inverse` + параметр, у которого НЕЛЬЗЯ обнулить** (напр.
`virtualServicesCount` integer>0): прошлый ответ — «inverse недопустим, fail-fast».
Но что если inverse-стратегия нужна только для ЧАСТИ полей, а не для всех?
Т.е. `delete_params` покрывает `needEnableAVI:false`, а `virtualServicesCount`
просто остаётся как есть. Допустимо ли «частичный inverse» (обратить только
обратимое, остальное не трогать)? Или inverse обязан покрывать все поля?
3. **`noop_warn` (дефолт) — всегда ли безопасен?** Удаление модификатора из state
при оставшемся эффекте на платформе — это drift. Допустимо ли вообще иметь
`noop_warn` как ДЕФОЛТ, или для необратимых (ip_space) правильнее дефолт `error`
(запретить destroy, пока не разберутся)? Что каноничнее?
Ответ — кратко, по пунктам.
@@ -0,0 +1,41 @@
# Баг: modify-модификатор сбрасывает create-поля в дефолт (needEnableAVI true→false)
## Симптом
`nubes_vc_nsxt_network` (modifier vc_nsxt.network, modify 111) после create Edge с `needEnableAVI=true`, `virtualServicesCount=3` сбрасывает `needEnableAVI` на платформе обратно в `false`.
## Подтверждено по API (cfsParams операций)
Create Edge (op `0c169353`):
- 340 needEnableAVI = **true**
- 341 virtualServicesCount = **3**
Modify (SNAT-модификатор, op `d14a149e`):
- 368 needEnableAVI = **null**
- 369 virtualServicesCount = **null**
- 856 qosProfile = **null**
- 372 ipSpaceName = internet-ipv4-v1
- 1112 routedNetConfiguration = {...}
Итоговый state.params Edge: `needEnableAVI = false`.
## Гипотеза
Модификатор строится через `resources_core.CompactParams`, который выбрасывает пустые `Optional`-поля. Бэкенд для `modify` трактует **пропущенный/null** параметр как «сбросить в дефолт» (false/0), а не «оставить как есть». Итог: modify с частичным payload затирает create-поля.
## Файлы
- `provider/internal/core/client.go` — `RunInstanceOperationUniversalByCode` (отправка params), `normalizeUniversalValueV6`
- `provider/internal/resources_core/crud.go` — `RunOperationByCodeWithTimeout`, `CompactParams`
- генератор: `TOOLS/resource-generator/internal/templates/modifier.go`, `internal/writers/writers.go` (WriteModifierResource)
- сгенерированное: `generated/dev/go/22_vc_nsxt_network_modifier.go`
- YAML: `generated/dev/resources_yaml/22_vc_nsxt.yaml` (modify 111, поля is_modifiable)
## Задание
Определить каноническое поведение:
1. Должен ли modify слать **все** параметры операции (полный payload, включая необязательные с их текущими значениями), или допустимо слать только переданные?
2. Где правильнее чинить: в генераторе (шаблоне modifier), в `CompactParams`, или в `RunInstanceOperationUniversalByCode` (досылать дефолты/текущие значения незаданных полей)?
3. Есть ли риск, что «досылать дефолты» сломает другие модификаторы (напр. vc_org.ip_space)?
Ответ кратко, тезисно, с указанием конкретной строки/места фикса.
@@ -0,0 +1,39 @@
# Ревью плана реализации: редизайн модификаторов
Прошу отревьюить план `PLAN_modifier_redesign.md` (10 шагов). Это проект к реализации,
не код. Вызовись: найди дыры, пропущенные кейсы, ошибки в порядке шагов, нестыковки.
## Контекст решения (уже согласовано, НЕ пересматривать)
- Модификатор = декларативная проекция полей родителя, единый `reconcile()` (Create≡Update).
- Полный payload (не дельта), досылка: задан→значение, иначе live→default→skip.
- `delete_strategy`: noop_warn | inverse | error (дефолт noop_warn), `idempotency`: none | check_before_run.
- Pre-check `desired==current` в `core` (не в resources_core, не в шаблоне), по живому `state_params`.
- `is_modifiable` — единственный сигнал изменяемости (фикс CreateOnly уже есть).
## Ключевые файлы-факты (сверены с кодом)
- `TOOLS/lib/types.go` — `OperationSpec`/`ParamSpec` (алиасы в обоих генераторах).
- `TOOLS/yaml-generator/main.go` — `serviceSpecificModifiers` (реестр исключений, источник канона).
- `TOOLS/resource-generator/internal/loader/loader.go` — ветка `kind==modifier`, `ValidateSpec`.
- `TOOLS/resource-generator/internal/templates/modifier.go` — шаблон.
- `provider/internal/resources_core/crud.go` — `RunOperationByCodeWithTimeout`.
- `provider/internal/resources_core/json_normalize.go` — `JSONStringsEquivalent` (импорт в core = цикл).
- `provider/internal/core/operation_run_bycode.go` — клиентский запуск.
## Вопросы к ревью (ответить кратко, по пунктам)
1. Порядок шагов 1–10 корректен? Где есть скрытая зависимость, которую я пропустил?
2. Шаг 5 (вынос JSON-эквивалентности в `core/jsonutil`) — правильный путь снять цикл
импорта, или есть чище (напр. оставить `JSONStringsEquivalent` в resources_core и
передавать нормализованные строки в core уже готовыми)?
3. Шаг 6 — сигнатура `modifierDesiredEqualsCurrent(desired map[string]string, cfsParams []universalCfsParam) bool`
корректна? Хватает ли данных для сравнения всех типов (bool/int/string/map-fixed/array-map-fixed)?
4. Шаг 4.4 Delete=inverse — как именно слать modify: `delete_params` + досылка live остальных
(полный payload) — это правильно, или есть подводный камень?
5. Шаг 8 — расширение реестра `serviceSpecificModifiers` до структуры: верный источник?
Или `delete_strategy`/`idempotency` правильнее держать отдельным реестром (не трогая тип map)?
6. Пропущен ли какой-то кейс из 16 (13 + taint/replace/partial/unknown)?
7. Есть ли риск сломать instance-ресурсы (не модификаторы) любым из шагов 1–8?
Ответ — тезисно, с указанием конкретного шага и что в нём поправить.
@@ -0,0 +1,29 @@
# Код-ревью: модификаторы (kind: modifier) в универсальном провайдере
## Контекст
terraform-provider-nubes (universal). Операции с `kind: modifier` генерируются как отдельные TF-ресурсы и запускают операцию `modify`, передавая параметры **по коду** (`vIPConfigure`, `needEnableAVI`...), а не по числовому id.
Актуальные модификаторы:
- `nubes_vc_org_ip_space` (vc_org.ip_space, modify 207) — выделение внешних IP (`vIPConfigure`).
- `nubes_vc_nsxt_network` (vc_nsxt.network, modify 111) — сеть/SNAT Edge.
## Ключевые файлы
- генератор: `TOOLS/resource-generator/internal/templates/modifier.go`, `internal/loader/loader.go` (LoadSpecs → GenModifier), `internal/writers/writers.go` (WriteModifierResource)
- рантайм: `provider/internal/resources_core/crud.go` (RunOperationByCodeWithTimeout)
- клиент: `provider/internal/core/client.go` (RunInstanceOperationUniversalByCode)
- сгенерированное: `generated/dev/go/19_vc_org_ip_space_modifier.go`, `22_vc_nsxt_network_modifier.go`, `registry.go`
## Известная проблема (уже диагностирована — НЕ ревьюить)
`GET /instanceOperations/{opUid}?fields=cfsParams` падает 500 (`getResourceRealmConfig` Struct→string) на проблемных инстансах. Fallback на `/instanceOperations/default/{opId}` планируется отдельно.
## Задание — короткий код-ревью
1. Корректность жизненного цикла modifier-ресурса: Create/Update/Read/Delete, идемпотентность, refresh из API.
2. Реального Delete нет (destroy не откатывает операцию) — это ожидаемо? Подводные камни при повторном apply.
3. Риски передачи параметров по коду (code → id) в `RunInstanceOperationUniversalByCode`.
4. ТОП-3 самых критичных замечания именно по модификаторам.
Ответ — кратко, тезисно, без кода-простыней.
@@ -0,0 +1,45 @@
# Проверка решения бага Dev-генератора
Ты выполняешь короткий read-only review. Ничего не меняй, не запускай генерацию,
не собирай и не публикуй провайдер.
## Задача
Проверь, правильно ли диагностирован баг и правильно ли предложено решение:
1. `create.jsonEnv` может быть nested (`sub_params`), а `modify.jsonEnv` — без
`sub_params`.
2. `Merge` формирует каноническую схему из параметров операций.
3. `AlignParamTypes` выравнивает типы, но не переносит `HasSubParams/SubParams`,
если у operation-параметра `HasSubParams` изначально false.
4. Шаблон `Update` поэтому генерирует scalar-вызовы для поля, которое в модели
является nested-структурой.
5. Универсальное решение — нормализовать каждый набор operation params
относительно канонической `SchemaParams`, рекурсивно наследуя структурные
свойства, без условий по стенду или сервису.
## Прочитать только эти файлы
1. `TOOLS/resource-generator/internal/params/params.go`
2. `TOOLS/resource-generator/internal/loader/loader.go`
3. `TOOLS/resource-generator/internal/helpers/helpers.go` — только функции
`IsNested` и связанные с nested-моделями
4. `TOOLS/resource-generator/internal/templates/instance.go` — только участки
`Update` и проверки `IsNested`
5. `TOOLS/resource-generator/internal/types/types.go`
6. `generated/dev/resources_yaml/95_nodejs.yaml` — только `jsonEnv` в create и modify
7. `generated/dev/go/95_nodejs_resource.go` — только модель `JsonEnv` и `Update`
Не изучай остальные сервисы, стенды, историю проекта или API вне этих файлов.
## Формат ответа
Ответь максимум в 5 коротких пунктах:
- **Вердикт:** прав / частично прав / неправ.
- **Доказательство:** одна конкретная цепочка от YAML до ошибочного Go-кода.
- **Решение:** корректно ли выравнивать operation params по канонической схеме.
- **Риск:** один главный риск предлагаемого решения.
- **Итог:** что именно нужно изменить или что менять не следует.
Не предлагай реализацию, diff, рефакторинг или дополнительные исследования.
+359
View File
@@ -0,0 +1,359 @@
# BRIEF: Анализ и рекомендации по документации Nubes Terraform Provider
**Для:** Claude Sonnet
**Дата:** 2026-08-10
**Задача:** Изучить ВЕСЬ пайплайн генерации документации, проанализировать фронтенд и UX, выдать подробные рекомендации по улучшению.
---
## 1. ЧТО ЭТО ТАКОЕ
Nubes Terraform Provider — это внутренний провайдер для Terraform, который управляет облачными сервисами (~43 сервиса: PostgreSQL, Redis, Kafka, S3, VMware и т.д.) через API платформы Nubes.
Документация — автосгенерированный статический сайт на mkdocs-material, который хостится в S3 и доступен юзерам провайдера.
**Юзер документации** — DevOps-инженер, который пишет Terraform-манифесты. Ему нужно:
1. Быстро найти нужный ресурс (сервис)
2. Понять какие параметры обязательные, какие опциональные, какие значения допустимы
3. Скопировать готовый HCL-пример и подставить свои значения
4. Узнать какие outputs можно использовать для связки ресурсов
5. Понять что делает каждая операция (create/modify/suspend/resume/...)
---
## 2. ПОЛНЫЙ ПАЙПЛАЙН ГЕНЕРАЦИИ
```
┌─────────────────────────────────────────────────────────────────┐
│ Шаг 1: YAML-спеки │
│ generated/{stand}/resources_yaml/{service}.yml │
│ ~43 YAML-файла: параметры, типы, defaults, constraints, MAN │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Шаг 2: docs-generator (Go) │
│ TOOLS/docs-generator/main.go │
│ │
│ Вход: YAML + --version + --api-endpoint + --provider-source │
│ Выход: плоские .md файлы в generated/{stand}/docs_llm/ │
│ │
│ На каждый сервис генерирует: │
│ {name}.md — MAN (руководство пользователя) │
│ {name}_example.md — HCL-примеры (minimal + full) │
│ {name}_params_create.md — таблица create-параметров │
│ {name}_params_modify.md — таблица modify-параметров │
│ {name}_outputs.md — выходные параметры + cloud snapshot │
│ {name}_ops.md — список всех операций │
│ {name}_params.md — лендинг со ссылками │
│ index.md — общий индекс всех ресурсов │
│ _nav_fragment.yml — фрагмент для mkdocs sidebar │
│ │
│ Ключевые функции в writers/writers.go: │
│ ResourceDocs() — вызывает все build* функции │
│ buildCreateParamsPage() — таблицы обязательных/опциональных │
│ buildExamplePage() — minimal + full HCL примеры │
│ buildManualPage() — MAN: HTML → Markdown конвертация │
│ buildOutputsPage() — output params + cloud snapshot │
│ buildOpsPage() — список операций │
│ renderParamTable() — Markdown-таблица параметров │
│ renderNestedParams() — вложенные sub_params (map-fixed) │
│ IndexMD() — индекс всех ресурсов │
│ WriteNavFragment() — sidebar навигация │
│ │
│ Таблицы: Markdown (| Code | Type | ... |), НЕ HTML. │
│ Навигация: inline-строка в header каждой страницы: │
│ **Manual** · [Create params] · [Modify params] · ... │
│ (активная страница выделена жирным) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Шаг 3: LLM-обогащение (Python) │
│ TOOLS/scripts/05_generate_docs_llm.py │
│ │
│ LLM: gpt-oss-120b (через api.aillm.ru) │
│ На вход: все .md файлы сервиса + YAML + MAN │
│ На выход: переписанные .md (те же имена файлов) │
│ │
│ Что LLM делает: │
│ A. Переносит value_list из Constraints в Description │
│ "value_list=1,3,5" → "Допустимые значения: **1, 3, 5**" │
│ B. Преобразует regex в читаемый текст │
│ "regex=^[a-f0-9-]+$" → "Формат: UUID" │
│ C. Заполняет пустые Description (из MAN, имени параметра) │
│ D. Описывает map-fixed группы (из sub-params имён + MAN) │
│ E. Описывает операции без описания (suspend, resume, ...) │
│ F. Переводит HTML MAN в читаемый Markdown │
│ G. Заменяет TODO в примерах на реальные значения │
│ │
│ ЖЁСТКИЕ ЗАПРЕТЫ: │
│ - НЕ менять имена параметров │
│ - НЕ трогать HCL-блоки │
│ - НЕ трогать навигационные строки │
│ - НЕ менять структуру таблиц │
│ - НЕ выдумывать типы/defaults/constraints │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Шаг 4: mkdocs-material (Python) │
│ mkdocs.yml + mkdocs build │
│ │
│ Тема: material, синяя схема │
│ Фичи: navigation.path, navigation.footer, navigation.indexes │
│ Markdown-расширения: md_in_html, admonition, superfences, ... │
│ CSS: extra.css — компактные шрифты, скрыты боковые панели │
│ JS: fix-slash.js — авто-добавление / в конец URL │
│ Выход: site/ — статический HTML │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Шаг 5: S3 (s3cmd sync) │
│ s3://nubes-terraform-registry/docs/{stand}/{provider}/{ver}/ │
│ Доступ: https://tf-registry.containerk8s.services.ngcloud.ru/ │
│ docs/nubes-test/nubes/5.0.5/ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 3. ТЕКУЩАЯ СТРУКТУРА ФРОНТЕНДА
### 3.1 Макет страницы ресурса
Каждая страница ресурса (например, PostgreSQL) имеет:
```
┌─────────────────────────────────────────────────────────┐
│ # Resource nubes_postgres · Service ID: 90 · PostgreSQL│
│ │
│ **Manual** · [Create params] · [Modify params] │
│ · [Output params] · [Operations] · [Example] │ ← inline nav
│ │
│ ## MAN │
│ (текст руководства — переведён из HTML в Markdown) │
│ ... │
└─────────────────────────────────────────────────────────┘
```
### 3.2 Что скрыто
CSS скрывает обе боковые панели mkdocs:
```css
.md-sidebar--primary { display: none !important; }
.md-sidebar--secondary { display: none !important; }
```
Это значит:
- **НЕТ sidebar-меню** — юзер не видит оглавление других ресурсов
- **НЕТ table of contents** — юзер не видит структуру текущей страницы
- Единственная навигация — inline-строка в header
### 3.3 CSS-особенности
- `font-size: 0.82rem` — компактный шрифт
- `max-width: 61rem` — умеренная ширина контента
- MAN-контент: `font-size: 0.62rem` — очень мелкий
- НЕТ стилей для Markdown-таблиц (были для HTML-таблиц `.resource-table`, теперь не применяются)
### 3.4 Индексная страница
Генерируется `IndexMD()` — простая таблица:
```
| ID | Ресурс | Описание |
|----|--------|----------|
| 1 | nubes_dummy | Болванка |
| 2 | nubes_template | Темплейт k8s |
...
```
Ссылки ведут на `{name}.md`.
---
## 4. ТЕКУЩИЕ ПРОБЛЕМЫ (ЧТО УЖЕ ИЗВЕСТНО)
### 4.1 Навигация — СЛАБОЕ МЕСТО №1
- Боковые панели скрыты → юзер теряется между страницами
- Inline-nav работает, но это неудобно: чтобы перейти к другому ресурсу, нужно вернуться на index
- Нет breadcrumbs между ресурсами (хотя mkdocs умеет `navigation.path`)
- Нет поиска по параметрам внутри ресурса
### 4.2 Таблицы параметров — СТАЛО ЛУЧШЕ
- Были HTML-таблицы, mkdocs их не рендерил → юзер видел голый текст `map-fixedmap-fixed`
- Исправлено: переведены на Markdown-таблицы → рендерятся корректно
- НО: стили `.resource-table` больше не применяются (они были для HTML)
- Описания параметров заполняются LLM, но не для всех (зависит от качества MAN)
### 4.3 Примеры (HCL) — работает
- Minimal example (только required) + Full example (с defaults) в раскрывашке `<details>`
- Юзер может скопировать и использовать
### 4.4 MAN-секция — перегружена
- `font-size: 0.62rem` — очень мелко, трудно читать
- HTML→Markdown конвертация в `htmlToMarkdown()` — regex-костыль, может глючить
- MAN содержит HTML из админки, его качество зависит от того, кто и как заполнял
### 4.5 Нет версионирования в интерфейсе
- Юзер не видит в UI какую версию провайдера он смотрит
- Хотя URL содержит версию (`/5.0.5/`), в самой странице это не отображается
---
## 5. ЧТО ХОРОШО (НЕ ЛОМАТЬ)
1. **Автоматическая генерация из YAML** — параметры всегда актуальны, не отстают от API
2. **Inline-навигация между страницами ресурса** — понятно где ты находишься
3. **Markdown-таблицы** — рендерятся везде, не зависят от HTML-санитайзеров
4. **LLM-обогащение** — описания становятся читаемыми (value_list, regex, пустые ячейки)
5. **Cloud snapshot в outputs** — реальные ключи `state_out_flat` и `vault_secrets` из облака
6. **Dual example** — minimal + full, юзер выбирает что нужно
---
## 6. ВОПРОСЫ К СОННЕТУ
### Блок A: Навигация и структура сайта
**A1.** Как организовать навигацию между ресурсами (43 сервиса)?
- Варианты: sidebar mkdocs, отдельная страница-индекс с поиском, grouped by category
- Плюсы/минусы каждого подхода для DevOps-юзера
**A2.** Нужно ли вернуть боковую панель mkdocs (sidebar)?
- Если да — что в ней должно быть: дерево ресурсов? категории? поиск?
- Если нет — как улучшить inline-nav + index page?
**A3.** Как юзер должен быстро найти нужный ресурс?
- Группировка: Базы данных, Очереди, Хранилище, K8s, VMware, Приложения, Сеть
- Поиск по имени/описанию?
### Блок B: Дизайн страницы ресурса
**B1.** MAN-секция сейчас `font-size: 0.62rem`. Как сделать читаемым?
- Оставить компактным но разборчивым?
- Сделать раскрывающимся (collapsed by default)?
- Вынести ключевую информацию выше?
**B2.** Как лучше структурировать страницу ресурса?
- Текущий порядок: Заголовок → Inline nav → MAN → ...
- Может: Заголовок → Краткое описание → Пример (сразу!) → Параметры → MAN (внизу)?
**B3.** Нужна ли версия провайдера в UI?
- Где показывать: в header? в title? в breadcrumb?
### Блок C: Таблицы параметров
**C1.** Как улучшить читаемость таблиц параметров?
- Сейчас: Code | Type | Description | Constraints
- Нужны ли: Required (yes/no), Default, категории параметров?
- Группировка связанных параметров (например, все cluster_configuration вместе)?
**C2.** Как показывать вложенные параметры (map-fixed)?
- Сейчас: ### clusterConfiguration → отдельная таблица sub_params
- Лучше: раскрывающийся блок? инлайн в той же таблице?
### Блок D: Примеры и HCL
**D1.** Как улучшить страницу Example?
- Сейчас: Minimal example сверху, Full example в `<details>`
- Добавить: описание каждого блока? комментарии в коде?
- Показывать реальные значения из облака?
### Блок E: Общие рекомендации
**E1.** Какие ещё элементы не хватает?
- Changelog между версиями?
- Ссылки на связанные ресурсы (PostgreSQL → как связать с Lucee/NodeJS)?
- Предупреждения/важные заметки (lifecycle behaviour)?
**E2.** Приоритизация: что сделать в первую очередь для максимального UX-эффекта?
---
## 7. КОНТЕКСТ ДЛЯ ИЗУЧЕНИЯ
### Файлы для чтения (в порядке важности):
1. **mkdocs.yml** — конфигурация сайта, тема, фичи, CSS
2. **TOOLS/docs-generator/internal/writers/writers.go** — ВСЯ генерация .md (~1000 строк, ключевой файл)
3. **TOOLS/docs-generator/main.go** — CLI, флаги, оркестрация
4. **TOOLS/scripts/05_generate_docs_llm.py** — LLM-обогащение, SYSTEM_PROMPT
5. **docs/LLM_DOCS_GENERATION.md** — архитектурная документация
6. **docs/30_registry/assets/extra.css** — CSS-стили
7. **docs/30_registry/javascripts/fix-slash.js** — JS (trailing slash fix)
### Посмотреть живьём (если есть доступ):
- https://tf-registry.containerk8s.services.ngcloud.ru/docs/nubes-test/nubes/5.0.5/ — индекс ресурсов
- https://tf-registry.containerk8s.services.ngcloud.ru/docs/nubes-test/nubes/5.0.5/postgres_params_create/ — пример страницы параметров PostgreSQL
- https://tf-registry.containerk8s.services.ngcloud.ru/docs/nubes-test/nubes/5.0.5/postgres_example/ — пример HCL
---
## 8. ОГРАНИЧЕНИЯ
- **YAML = истина.** Параметры, типы, defaults, constraints берутся ТОЛЬКО из YAML. Не выдумывать.
- **mkdocs-material** — выбранный фреймворк. Менять можно в рамках его возможностей.
- **43 сервиса** — масштаб. Решения должны работать для всех, не только для PostgreSQL.
- **Русский язык** — вся документация на русском.
- **Целевая аудитория** — DevOps-инженеры, знают Terraform, не знают внутренностей Nubes.
---
## 9. ФОРМАТ ОТВЕТА
Жду от тебя **структурированный анализ**:
1. **Общая оценка** текущего состояния документации (что хорошо, что плохо)
2. **Детальные рекомендации** по каждому блоку вопросов (A1-E2)
3. **Приоритизированный план действий** (Quick wins → Среднесрок → Долгосрок)
4. **Конкретные предложения** по коду/CSS/структуре где применимо
5. **Антипаттерны** — что НЕ стоит делать и почему
Не спеши. Изучи все файлы. Подумай как ДЕВОПС который впервые видит этот провайдер и пытается написать манифест для PostgreSQL.
---
## 10. УТОЧНЯЮЩИЙ ВОПРОС: Как смержить nav
Ты рекомендуешь вернуть sidebar с категориями. Но в `mkdocs.yml` секция `nav:` — статическая (руководства, глоссарий), а ресурсы генерируются динамически через `WriteNavFragment()` в `_nav_fragment.yml`.
Как правильно смержить статический `nav:` (руководства) и динамический (43 ресурса по категориям) в mkdocs?
---
### Ответ Соннета
**Нет встроенного `!include` в mkdocs.** mkdocs-material не имеет нативного механизма подключения внешних YAML-фрагментов в `nav:`.
**Вариант 1 — Рекомендован: расширить существующий Python pre-build шаг**
В `04_build_and_publish_docs.sh` уже есть Python-блок, который пишет `.mkdocs.tmp.yml`. Добавить туда чтение `_nav_fragment.yml` и инъекцию в `nav:`:
```python
import yaml
nav_fragment_path = Path(docs_dir) / "_nav_fragment.yml"
if nav_fragment_path.exists():
fragment = yaml.safe_load(nav_fragment_path.read_text(encoding="utf-8"))
resources_nav = fragment.get("resources_nav", [])
config = yaml.safe_load(text)
for item in config.get("nav", []):
if isinstance(item, dict) and "Ресурсы" in item:
item["Ресурсы"] = resources_nav
break
text = yaml.dump(config, allow_unicode=True, default_flow_style=False, sort_keys=False)
```
Плюсы: ноль новых зависимостей, PyYAML уже в окружении, merge в одном месте.
**Вариант 2:** docs-generator пишет полный mkdocs.yml (сложнее поддерживать).
**Вариант 3:** mkdocs-awesome-pages (не решает проблему merge).
**Дополнительно:** когда `docs_dir` переключается на `generated/{stand}/docs_llm`, пути `30_registry/guides/*.md` ломаются. Решение: копировать `30_registry` в `docs_dir` перед сборкой.
**Итого:** Вариант 1 — минимальные изменения, всё уже на месте.