docs: пометить отменённый заход модификаторов как LEGACY + исправить ложные факты

- баннеры «ЛОЖНЫЙ ПУТЬ — ОТМЕНЕНО» на 4 файла HISTORY/OPUS/2026-09-22_modifier_* и docs/60_strategy/modifier_resources_ideology_and_specification.md
- vIPConfigure: replace-семантика, НЕ накопительная (по тесту docs/ORG_IP_MODIFIER_TEST_2026-09-22.md)
- обновлены ссылки на перенесённые материалы (docs/... -> NOTES/..., HOW_TO/...)
This commit is contained in:
Repinoid
2026-09-24 07:51:25 +03:00
parent d93ff66482
commit 2d8e435dd4
59 changed files with 23 additions and 6 deletions
@@ -0,0 +1,83 @@
# Prompt для GPT‑5.2‑Codex — анализ и задачи по `terra` провайдеру
Цель: дать модели полный контекст репозитория и набор задач для проектирования/реализации managed Terraform provider и ресурсов (особенно: S3 bucket, Edge / nsxt, Redis, DNS zone+record, Grafana tenant).
---
## 1) Короткая инструкция к модели
You are GPT‑5.2‑Codex. Study the repository files listed below and perform the tasks described in the "Tasks" section. Answer in Russian. Do NOT modify any existing Go production code without an explicit operator approval (see IMMUTABILITY POLICY in REPO_CONTENTS.md). Do NOT output any secrets or tokens. Provide precise, actionable outputs only (JSON, Terraform snippets, git diffs, test files, PR description).
Constraints:
- Do not commit secrets or tokens. Indicate placeholder values for secrets.
- Respect repository rules: do not change function signatures or existing public APIs without confirmation.
- Provide outputs as machine‑parsable artifacts where possible (JSON matrix, unified diffs, files to create).
---
## 2) Файлы для изучения (указать полные пути)
- docs/ARCHITECTURE_NEW.md — архитектурный обзор
- docs/README.md, docs/index.md — документация / навигация
- docs/howitwasdone.md — история решений
- docs/ai_universal_provider_gen.md — процесс генерации провайдера (YAML → Go)
- universal_rebuild/tools/gen/main.go — генератор (основная логика)
- universal_rebuild/internal/resources_gen/* — примеры сгенерированных ресурсов
- docs/30_registry/resources/s3bucket.md — S3 bucket schema
- docs/30_registry/resources/redis.md — Redis schema
- docs/30_registry/resources/vc_nsxt.md — Edge (vc_nsxt) schema
- docs/30_registry/resources/vcexternalip.md — внешний IPs и пример полей
- docs/30_registry/resources/postgres.md — пример связанного ресурса (для референса)
- docs/20_discovery/development-journey.md — как тестировались флоу
- docs/70_api/* — API analysis and request/response examples
- scripts/package_release.sh — release flow and artifact publishing
- tools/upload_provider_s3.py — upload logic for release artifacts
- tests/ (especially tests/modify_postgres, tests/lifecycle_scenario) — real terraform snippets and logs
- REPO_CONTENTS.md — operational rules and immutability policy
---
## 3) Задачи (приоритетные — выполнить по очереди)
1. Извлечь и вернуть JSON‑матрицу параметров для ресурсов:
- S3 bucket (`nubes_s3bucket`) — поля create/outputs
- Edge (`nubes_vc_nsxt`) — минимальный набор create/modify полей
- Redis (`nubes_redis`) — минимальные параметры
- DNS zone (`nubes_dnszone`) & DNS record (`nubes_dnsrecord`) — поля (если нет, предложи именования)
- Grafana tenant (tenant / grafana_tenant) — поля
Формат: { resource_name: {create: [{name,type,required,default,id}], outputs:[...] } }
2. На основе матрицы сгенерировать модуль Terraform (пример `modules/<resource>/main.tf`) с минимальными параметрами и README для каждого ресурса.
3. Сгенерировать unit/acceptance тесты (как минимум `terraform plan` checks) и CI шаги (`.github/workflows/validate.yml`) с проверками: `terraform fmt`, `terraform validate`, `terraform plan` (dry-run), go vet/format для Go кода.
4. Выдать unified diff / patch (git format‑patch или git diff) с предлагаемых изменений: новые файлы (modules + tests + ci), пример использования `TERRA_TEST` обновлённый.
5. Подготовить текст PR (title, description, список файлов, rationale, risks, rollback plan) и checklist для code reviewers.
6. Предложить список ручных smoke‑checks, которые оператор должен выполнить перед мёрджем (eg. run `terraform plan` with `TF_VAR_api_token`, check provider build process via `scripts/package_release.sh`).
---
## 4) Формат ответа (ожидается от модели)
- 1) Краткое summary (3–5 предложений) — что сделано.
- 2) JSON matrix (machine‑readable) с полями ресурсов.
- 3) Сгенерированные Terraform файлы (в виде unified diff или содержимого файлов) и README примечания.
- 4) Список тестов и CI workflow (YAML) — как patch.
- 5) PR description (markdown) + checklist.
- 6) В конце — список вопросов/неизвестностей, которые нужно уточнить у оператора.
---
## 5) Доп. инструкции по безопасности и процессу
- Никогда не включай реальные токены в ответы.
- Не выполнять реальные `apply` в production; предложи инструкции для тестовой среды (use `TERRA_TEST/terraform.tfvars` with placeholders).
- Учитывай immutability rules: existing Go production code must not be modified without explicit permission. For generated code — create new files in `universal_rebuild/internal/resources_gen` or `universal_rebuild/tools/gen` output, do not alter core provider internals unless directed.
---
## 6) Пример стартового вопроса к модели
"Проанализируй перечисленные файлы, верни JSON‑матрицу параметров для перечисленных ресурсов, затем сгенерируй модули Terraform с минимальными параметрами, подготовь тесты и CI workflow, и выведи единственный unified diff patch, который можно применить в ветке `feature/universal-provider-resources`.
Пожалуйста, начинай с матрицы параметров (JSON)."
---
Файл подготовил: GitHub Copilot (Raptor mini, но prompt для GPT‑5.2‑Codex).
@@ -0,0 +1,44 @@
Ты — технический редактор документации Terraform-провайдера Nubes Cloud.
Ниже тебе даны Markdown-файлы документации ОДНОГО сервиса.
Твоя задача — ТОЛЬКО улучшить читаемость. НЕ МЕНЯЙ техническое содержание.
⛔ ЗАПРЕТЫ:
1. НЕ удаляй/добавляй параметры, типы, default, ID, имена ресурсов
2. HTML-таблицы: меняй ТОЛЬКО текст внутри <td>...</td>
3. НЕ трогай HCL-блоки (```hcl ... ```)
4. НЕ трогай navigation-строки [Manual](...)
5. НЕ меняй заголовки (#, ##, ###)
6. НЕ меняй имена nubes_*
✅ ДЕЛАТЬ:
1. MAN-секцию: HTML → читаемый Markdown (сохранить ВЕСЬ текст)
2. Описания: связные предложения на русском
3. Единообразие отступов и форматирования
📤 ОТВЕТ: СТРОГО JSON {"имя_файла.md": "полный текст", ...}
📥 КАК ГОТОВИТЬ ВХОДНЫЕ ДАННЫЕ:
Взять файлы из директории generated/test/docs/ для сервиса postgres:
- postgres.md
- postgres_example.md
- postgres_params_create.md
- postgres_params_modify.md
- postgres_outputs.md
- postgres_ops.md
- postgres_params.md
- postgres_backup.md
- postgres_backup_example.md
- postgres_database.md
- postgres_database_example.md
- postgres_user.md
- postgres_user_example.md
Скопировать содержимое каждого файла в секцию ниже.
Заменить {service} на имя сервиса (например postgres).
--- {service}.md ---
{вставить содержимое}
--- {service}_params_create.md ---
{вставить содержимое}
... (повторить для всех файлов)
@@ -0,0 +1,130 @@
# ТЗ для DeepSeek Flash: убрать create-time проверку существования из `ModifyPlan`
Дата: 2026-09-21 | Статус: не сделано | Версия провайдера на момент бага: 2.0.6
## Цель
Починить `terraform destroy` (и любую `tainted`-замену), который падает с
`РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)`.
## Контекст
- Репозиторий: `/home/naeel/TF/tf_provider`
- Провайдер: `terraform-provider-nubes`, Go, `terraform-plugin-framework v1.8.0`
- Ресурсы генерируются шаблоном, **НЕ правятся руками**
- Модуль провайдера живёт в `provider/` (не в корне репозитория)
## Симптом
```
$ terraform destroy
nubes_vc_vdc.vdc: Refreshing state... [id=db2cefc3-...]
nubes_vc_nsxt.edge: Refreshing state... [id=8AAEC14D-...]
│ Error: РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)
│ with nubes_vc_nsxt.edge,
│ on edge.tf line 1, in resource "nubes_vc_nsxt" "edge":
```
То же самое при обычном `terraform plan`.
## Причина (подтверждена фактами)
1. `nubes_vc_nsxt.edge` в state помечен **`tainted`** (следствие прошлой неудачной
apply с `vdc_group_uid`: `Provider returned invalid result object after apply`).
Проверка: `terraform.tfstate` → `instances[].status == "tainted"`.
2. Tainted-ресурс Terraform обязан **заменить** (destroy + create). Это видно в плане:
```
# nubes_vc_nsxt.edge is tainted, so must be replaced
-/+ resource "nubes_vc_nsxt" "edge" {
```
3. `terraform destroy` сначала выполняет **внутренний обычный plan**
(`Context.destroyPlan: calling Context.plan` — видно в `TF_LOG=TRACE`), и уже
на этом шаге планируется замена edge.
4. Create-узел замены вызывает `ModifyPlan` с **prior state = null**, поэтому guard
`if state != nil && !state.ID.IsNull() && ...` пропускается, и доходит до
create-time проверки существования.
5. Проверка находит **живой** инстанс в облаке (старый edge ещё не удалён — удаление
идёт на apply) → `РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)` → plan падает →
`destroy` не начинается.
Ключевое: на уровне `ModifyPlan` **невозможно** отличить «создание нового ресурса»
от «create-узла замены» — у обоих prior state = null. Поэтому проверки существования
в `ModifyPlan` быть не должно в принципе.
Доказательство, что вызов идёт из `ModifyPlan`: `/tmp/nubes_find_debug.log` содержит
`[FIND-DEBUG] PlanExistingResourceDiagnostics entered: serviceId=22 name="fullpipe-edge"`.
Эту строку пишет только Plan-функция; `Create...` в этот лог не пишет.
## Правка
**Один файл:** `TOOLS/resource-generator/internal/templates/instance.go`, шаблон метода `ModifyPlan`.
Удалить целиком блок от строки
```go
if config.ResourceName.IsNull() || config.ResourceName.IsUnknown() {
return
}
```
до строки
```go
resp.Diagnostics.Append(resources_core.PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices(ctx, r.client, {{.ServiceID}}, config.ResourceName.ValueString(), adoptExistingOnCreate, params, desiredDomain, domainServiceIDs, {{.SupportsSuspendDestroy}})...)
```
включительно. Это весь хвост `ModifyPlan` после блока «Missing required attribute»:
`adoptExistingOnCreate`, resolve refSvc, `params`, `desiredDomain`, `domainServiceIDs`
и сам вызов диагностики.
### Что НЕ трогать
- destroy-guard `if req.Plan.Raw.IsNull() { return }` — **оставить**;
- блок create-only проверок (по `state.ID`) — **оставить**;
- блок «Missing required attribute» — **оставить**;
- `Create` — там вызов `CreateExistingResourceDiagnosticsWithDomainAndServices`
**остаётся**: проверка выполняется на apply, уже после удаления старого инстанса.
### Побочный эффект (принять как норму)
Из plan пропадают проверки ref-параметров / domain / существования по имени. Это
штатное поведение Terraform: на apply `Create` резолвит refSvc (с ошибкой) и делает
проверку существования.
## Проверка
```bash
cd /home/naeel/TF/tf_provider && ./TOOLS/scripts/02_generate_resources_and_docs_v2.sh --profile TOOLS/config/dev
```
```bash
TMP=$(mktemp -d) && cp -R provider "$TMP/provider" && find "$TMP/provider/internal/resources_gen" -maxdepth 1 -type f -name '*.go' -delete && cp generated/dev/go/*.go "$TMP/provider/internal/resources_gen/" && (cd "$TMP/provider" && go build ./...) && echo BUILD_OK && rm -rf "$TMP"
```
Затем проверить сгенерированный код:
- `generated/dev/go/22_vc_nsxt_resource.go`: в `ModifyPlan` вызова
`PlanExistingResourceDiagnosticsWithParamsAndDomainAndServices` больше нет;
- в `Create` вызов `CreateExistingResourceDiagnosticsWithDomainAndServices` остался.
Если после удаления какой-то импорт стал неиспользуемым (`fmt`, `resources_core`) —
проверить сборкой. Обычно `Create` сохраняет те же импорты, отдельная правка флага
`NeedsFmtImport` не требуется.
## Что НЕ делать
- **НЕ собирать и НЕ заливать** провайдер — только правка шаблона + генерация + сборка-проверка.
- Не править сгенерированный код руками.
- Не трогать `provider/internal/resources_core/resource_diagnostics_required.go`.
## Критерий готовности
`BUILD_OK` и в сгенерированном edge `ModifyPlan` нет create-time проверки.
## Обходной путь без правок (если надо убить стенд прямо сейчас)
```bash
terraform untaint nubes_vc_nsxt.edge && terraform destroy
```
+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-тест запустить.
@@ -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,51 @@
# Промпт для Opus: IaC-развёртывание Штурвала, проблема `modify` и скрытых зависимостей
## Правила ответа (жёстко)
1. НЕ лезь в файлы/репозиторий/сеть. Отвечай ТОЛЬКО по материалу ниже.
2. Отвечай КРАТКО, тезисами, по номерам вопросов. Без простыней.
3. Токены/секреты/креды НЕ нужны — если захочешь, не упоминай и не проси.
4. Если для ответа не хватает данных — прямо пиши «неизвестно», не выдумывай.
5. Не предлагай «ручной ЛК / скрипт / пресеты дефолтного окружения» как решение IaC — это уже отклонено (клиенту нужен полноценный IaC).
## Контекст
Terraform-провайдер для Nubes Cloud. Клиенту нужен IaC: один конфиг + `terraform apply` = вся инфраструктура. Цепочка Штурвала:
```
vcOrg -> create
vcVdc -> create
vcNsxt -> create
vcOrg -> modify (аллокация внешних IP)
vcNsxt -> modify (включить SNAT, указать внешний IP из vcOrg)
k8sShturval -> create
```
Операции строго последовательны.
Факты (подтверждены):
- Провайдер генерируется из YAML-спеков. Схема tf-ресурса строится ТОЛЬКО из операции `create`.
- `vIPConfigure` (array-map-fixed, sub: name/count) есть только в `modify` vc_org (id 207); в `create` (136) его нет.
- `ipSpaceName` (string) есть только в `modify` vc_nsxt (id 111); в `create` (10) его нет.
- `vIPConfigure` — **не накопительный, а replace-семантика** (подтверждено `docs/ORG_IP_MODIFIER_TEST_2026-09-22.md`): повторный `modify` с тем же `count` не аккумулирует IP (1→1), работает в обе стороны (вверх/вниз/до 0), `count=0` принимается. Значение задаётся целиком, читается из `state.params`.
- `ipSpaceName` выводится из цепочки `providerVdc -> providerGateway -> ipSpace`, которую пользователь не знает. Сейчас платформа «подкладывает» недостающие параметры при создании пустой орги.
- Допущение платформы: в организации один T0/провайдер-шлюз. Рост числа T0 отложен.
- Платформа в движении: форма ресурсов зависит от новых спеков (ждут, придут сначала в sandbox).
Прецедент (VCD): та же цепочка делается отдельными ресурсами с `depends_on` — `vcd_nsxt_alb_settings` (count + is_active), `vcd_nsxt_alb_edgegateway_service_engine_group` (reserved_virtual_services), `vcd_network_routed_v2`, `vcd_ip_space_custom_quota` (на оргу). Включение/выключение = `count`, inverse = удаление ресурса.
Разница с каноном: у нас нет отдельного API-объекта под модификацию — только операция `modify` над родителем (Read = чтение родителя, Delete = обратный modify, нужна идемпотентность).
## Вопросы
1. `vIPConfigure` уже ведёт себя как replace-состояние (идемпотентно, обе стороны, `count=0` читается из `state.params`). Как это оформить в tf-ресурсе, чтобы Read брал `state.params`, а Delete (inverse) выставлял `count=0` — если отдельного API-объекта нет?
2. Как провайдер должен получать выводимое значение `ipSpaceName` (цепочка providerVdc → providerGateway → ipSpace): data-source, вычисляемое из state родителя, или иное? Где граница «данные vs логика», что хранить в реестре, что выводить из типа/state?
3. Как спроектировать форму ресурсов, чтобы не завязываться на допущение «в организации один T0», и что сломается/что менять, если T0 станет больше одного?
4. Стоит ли ждать новых спеков платформы перед проектированием ресурсов, или форму ресурсов можно зафиксировать уже сейчас так, чтобы она пережила изменение спеков? Что в спеках — блокер, что — нет?
5. Минимально-инвазивный порядок внедрения: что должно прийти от платформы (spec/API) до того, как мы начинаем кодить, а что можем сделать на стороне провайдера уже сейчас?
Отвечай по номерам, кратко.
@@ -0,0 +1,47 @@
# Вопрос: архитектура inverse-отката модификаторов (без чтения файлов)
ЗАПРЕЩЕНО лезть в файлы репозитория. Отвечай только по тексту ниже. Ответ — максимально краткий (тезисы), но исчерпывающий.
## Контекст
Terraform provider для Nubes Cloud. Есть «модификаторы» — отдельные TF-ресурсы, вызывающие операцию `modify` над инстансом по кодам параметров, а не по числовым id. Примеры:
- `vc_org` → модификатор `ip_space`, параметр `vIPConfigure` (array-map-fixed) = `[{"name":"internet-ipv4-v1","count":3}]` — выделение внешних IP.
- `vc_nsxt` → модификатор `network`, параметры `needEnableAVI` (boolean), `ipSpaceName` (string, valueList содержит sentinel `"no-needed"`), `routedNetConfiguration`.
У модификатора есть `delete_strategy`, определяющий что делать при `terraform destroy`:
- `noop_warn` — снять из state, эффект остаётся (предупреждение).
- `error` — запрет удаления (сейчас так на vc_org, из-за чего destroy встаёт).
- `inverse` — при Delete выполнить обратную операцию `modify` с `delete_params` (список `{Code, Value}`).
## Проблема
Хочу, чтобы `destroy` и `apply` были полными и симметричными. При удалении модификатора нужно «откатить» эффект:
1. `needEnableAVI` → `false`.
2. `ipSpaceName` → `"no-needed"`.
3. `vIPConfigure` → `count=0`, имя сохранить (`[{"name":"internet-ipv4-v1","count":0}]`).
Пункты 1-2 — статические константы, текущий механизм `delete_params {Code,Value}` покрывает.
Пункт 3 — динамический: имя берётся из текущего state инстанса, обнуляется только `count`.
Требование: решение должно быть архитектурно чистым и универсальным (привязанным к типам данных из API, `dataType`/`valueList`/`sub_params`), а не хардкодом имён сервисов — чтобы при неглобальных изменениях API перегенерация подхватывала.
## Ключевые факты (уже проверены)
- `count=0` принимается API, несмотря на `minvalue:1`/`integer > 0` в схеме. Идемпотентно.
- `ipSpaceName` sentinel «выключен» = `"no-needed"` (есть в `valueList`).
- `needEnableAVI` — boolean: обратное = `"false"`.
- Типы из API: `needEnableAVI`=`boolean`; `ipSpaceName`=`string`(+`valueList`); `vIPConfigure`=`array-map-fixed` (sub_params: `name`=string, `count`=integer).
## Вопросы (нужны краткие ответы)
1. Как правильно расширить модель delete_params, чтобы поддержать и статичные обратные значения (`false`, `no-needed`), и динамические преобразования (`count→0`)? Оцени вариант «типизированные правила`: `Mode` ∈ {static, zero_count, …}, где static=текущий Value, zero_count=обнулить integer-поле `count` в каждом элементе array-map-fixed, взятом из live state.
2. Универсальнее ли выводить обратные значения ИЗ ТИПА ПАРАМЕТРА (boolean→"false", string+valueList→первый/помеченный sentinel, array-map-fixed→нулевой count в integer-полях), чем задавать их в реестре исключений? Где баланс: что держать в реестре (данные), что выводить из типа (логика)?
3. Нужен ли отдельный маркер «какое поле array-map-fixed обнулять» (сейчас это `count`), или достаточно общего правила «обнулить все integer-поля sub_params»? Риски обоих.
4. Правильный порядок destroy при зависимостях: `edge_net` (SNAT off + ALB off) → `org_ips` (count=0) → `nsxt` → `vdc`. Как Terraform сам выведет порядок из `depends_on`, и где инверсия/откат может конфликтовать с порядком удаления дочерних инстансов?
5. Есть ли подводные камни в самом `inverse`-delete (если дети ещё живы, откат `count=0` на орге может не пройти)? Нужен ли двухфазный подход или достаточно полагаться на порядок?
Формат ответа: пункты пронумерованы под мои вопросы, 1-3 предложения на пункт. Без лишнего.
@@ -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,45 @@
# Глобальная архитектура модификаторов: как сделать их НЕ инвазивным дополнением
ЗАПРЕЩЕНО лезть в файлы репозитория. Отвечай только по тексту. Формат: тезисы, кратко, по пунктам моего вопроса. Без лишнего.
## Контекст
Terraform provider для Nubes Cloud. Цепочка кодогенерации:
1. `01_generate_yamls` — идёт по API, по каждому облачному сервису тянет операции и параметры, пишет универсальный YAML (`resources_yaml/<id>_<svc>.yaml`).
2. `02_generate_resources` — по этому YAML генерирует Go-ресурсы провайдера (`<id>_<svc>_resource.go`).
Обычные ресурсы (`nubes_vc_nsxt`, `nubes_vc_vdc` и т.д.) — это операции `create`/`delete`/`suspend`/`resume`/`reconcile` над инстансом. Их apply/destroy давно стабильны и оттестированы.
## Что такое «модификатор» (доменная суть)
Некоторые операции `modify` сервиса — это не «изменить инстанс», а **отложенный дочерний шаг** цепочки, который нельзя мешать с create инстанса:
- `vc_org` → `modify` с параметром `vIPConfigure=[{"name":...,"count":N}]` — выделение внешних IP организации.
- `vc_nsxt` → `modify` с `needEnableAVI`, `ipSpaceName`, `routedNetConfiguration` — настройка ALB/SNAT уже созданного Edge.
Такой `modify` семантически НЕ принадлежит lifecycle самого инстанса: это отдельный TF-ресурс, который должен создаваться/удаляться независимо от `create`/`delete` родителя.
## Проблема (как сделано сейчас — неправильно)
Сейчас «модификаторность» вплетена в универсальную генерацию:
- реестр `serviceSpecificModifiers` зашит в исходник yaml-generator и **помечает** операцию `modify` как `kind: modifier` + пишет в YAML `delete_strategy`, `delete_params` и т.п.
- Это ломает главный принцип: YAML должен быть чистой универсальной выгрузкой из API, а обычные ресурсы — не зависеть ни от какого реестра.
Требования:
1. YAML — универсальная выгрузка ВСЕГО из API, без доменных меток (`kind: modifier`, `delete_strategy`).
2. Ресурсы облачных сервисов НЕ должны зависеть от модификаторов. Если модификаторов нет — поведение идентично прежнему (до их внедрения).
3. Модификаторы — чистое ДОПОЛНЕНИЕ: отдельная сущность, отдельный ресурс, со своей семантикой (inverse-откат при destroy, idempotency), которая НЕ просачивается в базовую генерацию.
4. При полном `destroy` должен быть корректный обратный откат: ALB off, SNAT `no-needed`, IP `count=0` — при этом симметричный `apply` возрождает всё.
## Вопросы (ответь по пунктам)
1. **Правильное место доменной семантики модификатора.** Где её хранить, чтобы она была «данными-наложением», а не веткой в универсальном генераторе? Варианты: (а) отдельный конфиг-файл данных (`modifiers.yaml`), который второй проход накладывает на базовый YAML, порождая ОТДЕЛЬНЫЕ YAML-записи модификаторов, не трогая базовые; (б) отдельный `kind` в самих YAML без доменных меток; (в) иное. Обоснуй.
2. **Разделение «модификатор» vs «обычный modify».** Как архитектурно отделить modify-как-модификатор от modify-инстанса, НЕ меняя универсальную выгрузку? Как гарантировать, что при отсутствии модификаторов обычный modify-поток ресурса вообще не затрагивается?
3. **Как структурировать inverse-откат**, чтобы он был: (а) генерализуемым (по типам: boolean→"false", string+valueList→off_value sentinel, array-map-fixed→zero integer-полей), (б) идемпотентным (не дёргать run, если live уже целевое), (в) не влиял на обычные ресурсы. Нужна ли отдельная модель `delete_rule` у модификатора.
4. **Порядок destroy** при цепочке модификаторов, зависящих от обычных ресурсов и друг от друга (`SNAT-модификатор → IP-модификатор → edge → vdc`). Как выразить зависимость модификатора от ресурса так, чтобы Terraform сам вывел обратный порядок, не завязываясь на хрупкий `depends_on`?
5. **Минимально-инвазивная миграция.** Как перейти от текущего (модификаторы «вросли» в базовую генерацию) к целевой (модификаторы — наложение) без регресса уже стабильных обычных ресурсов? Что трогать НЕЛЬЗЯ.
Ответь кратко, по номерам, 2-4 предложения на пункт.
@@ -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,79 @@
# Ревью: модификаторы vc_nsxt / vc_org + досылка modify-params (Terraform Provider Nubes)
Ты — ревьюер. Ничего не правь. Прочитай и выдай:
1) подтверждение/опровержение каждого утверждения ниже;
2) список багов/рисков, которые я НЕ заметил;
3) список проблем, которые я заметил ошибочно (ложные тревоги);
4) чёткую рекомендацию по каждому корректному фиксу (минимальную, без scope creep).
## Контекст системы
Go Terraform provider `terraform-provider-nubes` (plugin-framework), сервис Nubes Cloud.
Генератор ресурсов: `TOOLS/resource-generator` (шаблон `instance.go`, `modifier.go`).
Ядро: `provider/internal/core/` (HTTP + операции), `provider/internal/resources_core/` (обёртки).
Сервисы, о которых речь:
- `vc_nsxt` (serviceId 22). Операции: create (10), delete (25), **modify (111, kind: modifier, modifier: network)**, reconcile. У resource НЕТ instance-modify.
- `vc_org` (serviceId 19). Модификатор `ip_space` (modify 207).
Модификатор = отдельный TF resource (`nubes_vc_nsxt_network`, `nubes_vc_org_ip_space`), который вызывает операцию `modify` с параметрами.
## Цель (FullPipe) — что должно работать
1. Создать VDC (vc_vdc).
2. Создать Edge (vc_nsxt) с ALB (`needEnableAVI=true`, `virtualServicesCount=3`).
3. Выделить IP организации (vc_org → modifier ip_space, `vIPConfigure`).
4. Применить SNAT для Edge (vc_nsxt → modifier network, `ipSpaceName` + `routedNetConfiguration`).
## Что УЖЕ сделано (факты, проверь корректность)
### Факт 1. `resource "nubes_vc_nsxt"` Update — no-op
Шаблон `instance.go` генерирует `hasServiceParamChanges := false`, а цикл по `.ModifyParams` пуст (у vc_nsxt нет instance-modify). Поэтому `Update` всегда уходит в `if !hasServiceParamChanges { ...; return }` и НЕ вызывает `modify` (111). Изменение ALB/VS/qos через resource невозможно. Изменения Edge идут ТОЛЬКО через модификатор `nubes_vc_nsxt_network`.
### Факт 2. Досылка незаданных modify-params (мой свежий фикс, коммиты a011358)
Раньше незаданные params операции `modify` досылались значением `paramValue` из `GET /instanceOperations/{opUid}?fields=cfsParams`. Это ОШИБОЧНО: `paramValue` — дефолт ФОРМЫ операции, а не состояние инстанса. Для `needEnableAVI` там `"false"`, хотя live-значение инстанса `true` (подтверждается HAR/edge_.har и HAR/ipSpace0.har). Из-за этого каждый `modify` через модификатор сбрасывал ALB в false.
Фикс: в `operation_cfs.go` добавлены `instanceLiveParams()` (читает live из `GET /instances/{uid}` → `state.params`) и `lookupLiveParam(live, cfsParam)`. В `runInstanceOperationByCode` и `RunInstanceOperationUniversalWithDefaults` приоритет теперь: **live state.params → paramValue → defaultValue**.
### Факт 3. `ShouldRemoveFromState` (коммит 94c4c44)
Раньше вызывал валидирующий `GetInstanceState`, который на статусе `deleted` кидал `instanceDeletedError` — и `Read` модификатора падал с "экземпляр … удалён" вместо тихого удаления из state. Переписан на `GetInstanceStateRaw` + различение 404/deleted (remove=true) vs сеть/5xx/403 (нужно `false, err`).
## ОШИБКА, которую наблюдаю СЕЙЧАС (главное)
`terraform apply` падает:
```
Error: Provider returned invalid result object after apply
After the apply operation, the provider still indicated an unknown value for
nubes_vc_nsxt.edge.qos_profile. All values must be known after apply...
```
`qos_profile` у resource `nubes_vc_nsxt` = `Optional+Computed` БЕЗ Default (`ShouldBeOptionalComputed` → true, потому что param qosProfile: not required, RefSvcId=0, Default=""). В конфиге не задаётся → в плане unknown. А `Update` (`hasServiceParamChanges=false` → ранний return) копирует только `State*`/`Vault*` outputs, но НЕ вызывает `RefreshResourceState`, поэтому `qos_profile` остаётся unknown.
## МОИ ДИАГНОЗЫ (проверь каждый, а не только текущий)
### Диагноз A (текущая ошибка)
Ранний return в `Update` (шаблон instance.go) не схлопывает unknown→null read-back-computed поля. Нужно в ветке `!hasServiceParamChanges` вызывать тот же `RefreshResourceState`, а не копировать `State*`/`Vault*` вручную.
### Диагноз B (вылезет после A)
Тот же ранний return оставляет unknown для `need_enable_avi` и `virtual_services_count`, если их убрать из `edge.tf` (а их и должны убрать, раз ALB перенесён в модификатор). Один корень с A.
### Диагноз C (дублирование конфига — НЕ починен)
`edge.tf` ДО СИХ ПОР задаёт `need_enable_avi` и `virtual_services_count` (create), а `edge_network.tf` — те же значения (modifier). Это двойное задание одного и того же → возможен дрейф. Нужно определить: где канонически задавать ALB?
### Диагноз D (ловушка destroy/delete)
Модификатор имеет `delete_strategy: inverse`, override `needEnableAVI="false"`. При `terraform destroy` ALB выключится. Повторный `apply` через `resource "nubes_vc_nsxt"` (no-op Update) НЕ включит обратно, а включит только модификатор второй apply-волной. Нужно проверить порядок зависимостей.
### Диагноз E
`FetchInstanceOutputs` глотает любую API-ошибку (5xx/404) и возвращает пустые outputs без diagnostic → молчаливый дрейф. Нужен warning.
## Конкретные вопросы
1. Подтверди/опровергни Диагноз A как корень текущей ошибки.
2. Есть ли проблема в моём фиксе досылки (Факт 2)? В частности:
- верно ли, что `state.params` — единственный достоверный источник live?
- не сломает ли `lookupLiveParam` (по Code/SvcOperationCfsParam/Name/Label) какие-то кейсы, где имя в state.params отличается регистром/форматом от этих ключей?
- не создаёт ли `instanceLiveParams` лишний сетевой вызов на каждый modify (перф)?
3. Верна ли трактовка Факт 1 (resource Update — no-op)? Или правильнее ДОБАВИТЬ instance-modify в генератор?
4. Какое каноническое место для `need_enable_avi`/`virtual_services_count`/`qos_profile`: create (edge.tf) или modifier (edge_network.tf)? Что делать с текущим дублированием?
5. Что ещё я упустил в цепочке create→modify→read→destroy?
@@ -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,41 @@
# Промпт для Opus 4.8: код-ревью и оценка архитектуры (3 задачи roadmap)
Дата: 2026-09-22 | Статус: для отправки
```text
Роль: ревьюер архитектуры Go-провайдера Terraform.
ПРАВИЛА:
- Файлы НЕ открывай, в репозиторий не лезь — отвечай только по контексту ниже.
- Анализируй ТОЛЬКО 3 указанные задачи, не весь проект.
- Ответ максимально сжатый: только выводы/риски/рекомендации. Без вводных, без «рассмотрим», без повторов. Списки или таблица. Риск помечай 🔴/🟡/🟢.
- Если для ответа не хватает факта — пиши «НЕДОСТАТОЧНО ДАННЫХ: …», не выдумывай.
КОНТЕКСТ (достаточен, файлы не нужны):
- terraform-provider-nubes, Go, terraform-plugin-framework v1.8.0. Ресурсы ГЕНЕРИРУЮТСЯ из YAML-спек сервисов (не рукописные).
- Один сервис → один ресурс инстанса nubes_<service> (CRUD). В YAML: operations (create/modify/suspend/…), params с type (bool/int64/string/map-fixed/array-map-fixed), required, default, is_modifiable, ref_svc_id.
- Schema: param → Required (если required, без default, не refSvc); Optional; Computed+Default (если default); Optional+Computed (если параметр читается обратно из state_params инстанса, или это JSON).
- Create: резолвит refSvc-параметры (принимают display name ИЛИ UUID → uid в API), вызывает create-op, затем читает state обратно.
- Update: если изменились modify-параметры → вызывает modify-op с ними. ModifyPlan запрещает менять create-only атрибуты (ошибка).
- Read: читает state_params инстанса обратно в input-поля (drift), и выставляет computed-мапы: state_params, state_out, state_params_flat, state_out_flat + vault_*.
- Delete: suspend / delete / state_only — в зависимости от наличия suspend-op у сервиса.
- Межресурсные зависимости: пользователь в .tf ссылается на атрибуты других ресурсов (напр. nubes_vc_vdc.vdc.id). ref_svc_id валидирует значение по инстансам целевого сервиса и резолвит в uid.
- Data sources НЕ генерируются (только ресурсы).
- map-fixed → SingleNestedAttribute (HCL: `x = { … }`); array-map-fixed → StringAttribute (JSON-строка).
- Сервисы: vc_org (19), vc_vdc (21), vc_nsxt (22). Спека k8s_shturval уже есть.
УЖЕ СДЕЛАНО: vcVdc create, vcNsxt create — работают (v2.0.8).
АНАЛИЗИРОВАТЬ (только это):
1. vcOrg modify — аллокация внешних IP в организацию ПО МЕРЕ НЕОБХОДИМОСТИ (динамически, число заранее не фиксировано).
2. vcNsxt modify — включить SNAT и указать внешний IP, взятый из уже аллоцированного пула vcOrg.
3. k8sShturval create.
ВОПРОСЫ (ответь по пунктам, кратко):
1. Покрывает ли текущая модель эти 3 задачи, или для какой-то нужна новая абстракция (data source / action / subresource)? По каждой задаче — вердикт.
2. vcOrg IP-аллокация: как моделировать пул внешних IP, растущий по мере необходимости — (а) атрибут-массив на nubes_vc_org, (б) отдельный ресурс/подресурс на каждый IP? Что лучше согласуется с текущей архитектурой и почему.
3. vcNsxt: как передать «внешний IP из vcOrg»? Сравни: (а) refSvc-параметр, (б) computed-атрибут vcOrg + ссылка nubes_vc_org.<name>.<attr>, (в) data source. Учти ограничение: refSvc ссылается на инстанс/uid, но не на конкретный элемент списка.
4. Риски текущего кода именно для этих потоков: update/modify с массивными параметрами; create-only guard; read-back; порядок плана между зависимыми ресурсами.
5. k8sShturval create: что критично проверить (refSvc к vdc/org, долгий create, типы параметров)?
6. Итог: 3–5 конкретных рекомендаций по приоритету — что добавить/изменить в генераторе или ресурсах.
```
@@ -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, рефакторинг или дополнительные исследования.
@@ -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 — минимальные изменения, всё уже на месте.