From 129dab97a047417e6c8f61b5bc2a7d520efb8647 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Thu, 24 Sep 2026 09:29:22 +0300 Subject: [PATCH] =?UTF-8?q?docs(notes):=20Q&A=20=D1=81=20Opus=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D0=B4=D0=B8=D0=B7=D0=B0=D0=B9=D0=BD=D1=83=20=D1=80?= =?UTF-8?q?=D0=B5=D1=81=D1=83=D1=80=D1=81=D0=BE=D0=B2-=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82=D0=BE=D1=80=D0=BE=D0=B2=20?= =?UTF-8?q?+=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OPUS_QA_MODIFIER_DESIGN_2026-09-24.md | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 NOTES/30_analysis/OPUS_QA_MODIFIER_DESIGN_2026-09-24.md diff --git a/NOTES/30_analysis/OPUS_QA_MODIFIER_DESIGN_2026-09-24.md b/NOTES/30_analysis/OPUS_QA_MODIFIER_DESIGN_2026-09-24.md new file mode 100644 index 0000000..711ce99 --- /dev/null +++ b/NOTES/30_analysis/OPUS_QA_MODIFIER_DESIGN_2026-09-24.md @@ -0,0 +1,75 @@ +# Q&A с Opus: дизайн ресурсов-модификаторов (2026-09-24) + +> Кто: вопросы составлены нами (Copilot), ответы — Opus (внешний агент, по разрешению пользователя). +> Контекст: решено делать два ресурса-модификатора (`nubes_vc_org_ip_allocation`, `nubes_vc_nsxt_snat`). +> Статус: ответы приняты к сведению, **код не писался**, часть утверждений Opus мною не проверена (пометки ниже). + +## Вопросы и ответы + +### 1. Инварианты Read/Delete ресурса-модификатора +**Ответ Opus:** +- Read: `RemoveResource` только если родитель исчез (404/deleted) — у нас есть `ShouldRemoveFromState` + (Opus ссылается на `modifier.go`). Расхождение значения параметра ≠ повод удалять ресурс: это дрейф, + обновить поле в state. +- Delete = inverse modify (`count=0` / `needEnableAVI=false` / `ipSpaceName="no-needed"`) — «шаблон + `DeleteStrategy=inverse` + `override` уже реализован». +- Если родитель уже удалён: inverse пропустить, ресурс убрать из state (no-op + Warning), не падать на ошибке API. + +### 2. Reset-to-default в `*WithDefaults` +**Ответ Opus:** защита «уже встроена»: и `RunInstanceOperationUniversalWithDefaults` (`operation_run.go:138`), +и by-code путь (`operation_run_bycode.go:108`) досылают незаданные параметры с приоритетом +**live `state.params` → `paramValue` формы → `defaultValue`**. Достаточно шлать только `ipSpaceName`. +Отдельный pre-read live + merge делать не нужно; `ByCode`/`ByIdempotent` — не нужны. +Дополнительно `RunOperationByCodeIdempotent` (`check_before_run`) сверяет desired == current и пропускает лишний run. + +### 3. Генератор: modify-only параметр с `required: true` +**Ответ Opus (минимальный набор):** +- **(а)** modify-only → всегда Optional (снять Required в схеме). Обязательно. +- **(б)** исключить modify-only из create-read-back (не добавлять его `InputField` в Create/Read). Обязательно. +- **(в)** «после create догонять modify» — **не нужно**: это ответственность отдельного modifier-ресурса. +- Breaking: снятие Required — не breaking (Optional шире). Breaking — если **удалить** атрибут из схемы + instance у тех, кто его уже прописал в `.tf`. Формулировка Opus: «modify-only параметров в схеме instance + быть не должно вовсе — их место в modifier-ресурсе». + +### 4. Диагноз «inconsistent result after apply» на создании орги +**Ответ Opus: подтверждает.** `state_refresh.go`, цикл `inputs`: берёт `paramsMap["vIPConfigure"]` из +`state.params` (платформа отдаёт `[{}]`), через `setFieldValue`/`ParseString` перекрывает план; для +Required-атрибута TF требует final == config → ошибка. Корректно: не читать modify-only обратно в Create +(п.3б) и вернуть запланированное значение, либо Optional+Computed со схлопыванием `[{}]`→null. + +### 5. Порядок destroy +**Ответ Opus:** явный `depends_on` нужен — связь между org-IP и SNAT идёт по **имени** ipSpace, ребра графа +TF не видит. Цепочка: `vdc → org → org-IP → edge → SNAT → кластер`; при корректных `depends_on` destroy +пойдёт в обратном порядке. Обязательные рёбра: SNAT → org-IP, modifier → родитель. Достаточно при условии, +что inverse-Delete терпит уже удалённого родителя (п.1). + +### 6. Трактовка `[{}]` в Read +**Ответ Opus:** `[{}]` = «не выделено», нормализовать в null/пусто. `count=0` и `[{}]` — одно состояние +«пусто», иначе ложный дрейф на каждом plan. + +## Мои замечания к ответам (не проверено кодом, требует внимания) + +1. **Opus опирается на machinery отменённого захода.** Он говорит про `modifier.go`, `DeleteStrategy=inverse`, + `override`, «уже реализовано». Это шаблон генератора из эпохи `kind: modifier`, которую мы **сознательно + отменили** (см. баннер LEGACY в `NOTES/20_prompts/**`, `docs/60_strategy/modifier_resources_ideology_and_specification.md`). + Ответы про «уже встроено» нельзя принимать как готовое решение — это код отменённой ветки. +2. **Противоречие внутри п.3:** сначала «modify-only → всегда Optional (оставить в схеме instance)», + потом «modify-only в схеме instance быть не должно вовсе». Это разные изменения: Optional+Computed vs удаление. + Нужно выбрать одно, иначе получим двух владельцев одного параметра (instance-ресурс и модификатор). +3. **Два владельца параметра.** Если `ip_space_name` остаётся в `nubes_vc_nsxt` **и** появляется + `nubes_vc_nsxt_snat`, Terraform не увидит конфликт: оба будут шлать 372. Значит, из `Update` + сгенерированного `nubes_vc_nsxt` параметр надо убирать — иначе fight/drift. В ответах Opus этого нет. +4. **П.2 не проверял сам.** Утверждение «приоритет live → paramValue → defaultValue уже встроен» противоречит + комментарию в `19_vc_org_resource.go` про reset-баг (`state_params["needEnableAVI"]="false"`, когда на + платформе `true`). Нужна проверка `operation_run.go:138` и `operation_run_bycode.go:108` по коду. +5. **Политика destroy для не-нашей орги.** Орга не в state (адресация по uid). При `destroy` конфигурации + родитель не удаляется — но org-IP-модификатор по §1 выполнит inverse (`count=0`). Нужно решение: + снимать квоту или оставлять (`keep_on_destroy`)— у Opus этого нет. +6. **Один элемент vs весь массив.** `vIPConfigure` — массив. Если ресурс управляет одним элементом + (по `ip_space_name`), то два ресурса на разные ipSpace возможны; если всем массивом — нет. `count=0` + как inverse предполагает поэлементную модель, но в ответах это не зафиксировано. + +## Что дальше (после решения пользователя) + +- Проверить по коду п.2 и п.4 замечаний (чтение, без правок). +- Дописать план реализации 2 ресурсов с учётом п.3 и п.5.