# Спроектировать С НУЛЯ архитектуру/логику «ресурсов-модификаторов» (kind: modifier) ## Цель Перепроектировать модификаторы целиком, чтобы исключить ВСЕ классы багов, не латать по одному. Нужна единая, полная модель поведения — без догадок и костылей. Перечислить ВСЕ кейсы. ## Что такое модификатор (текущая фактура) В YAML (генерируется из API) операции бывают: - `kind: instance` (create/modify/suspend/resume/delete) — обычный CRUD-ресурс; - `kind: modifier` + `modifier: ` — отдельный 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, `_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. **Связь с родителем.** Должен ли модификатор использовать `_id` как ссылку на родителя (depends_on / borrow state), и как читать UUID родителя? 6. **Create vs Update.** Допустимо ли иметь их идентичными, или нужен строго Update-семантик (нет create, только apply-по-десяти)? 7. **Полный перечень кейсов.** Перечислить ВСЕ edge-кейсы, которые надо покрыть: create родителя → modifier; remove modifier; replace; partial params; unknown/absent. Ответ — архитектурный документ (краткий, структурированный), с конкретными файлами и функциями. НЕ код-ревью, а ПРОЕКТ.