docs(opus): исчерпывающий запрос — спроектировать архитектуру модификаторов с нуля (все кейсы)

This commit is contained in:
Repinoid
2026-09-22 20:11:14 +03:00
parent 3d722fb7ee
commit bea39508f5
@@ -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.
Ответ — архитектурный документ (краткий, структурированный), с конкретными файлами
и функциями. НЕ код-ревью, а ПРОЕКТ.