diff --git a/docs/inverse_rollback_analysis_2026-09-23.md b/docs/inverse_rollback_analysis_2026-09-23.md new file mode 100644 index 0000000..0fc5be5 --- /dev/null +++ b/docs/inverse_rollback_analysis_2026-09-23.md @@ -0,0 +1,53 @@ +# Inverse-откат модификаторов: анализ ответа Опуса — 2026-09-23 + +Источник: prompt_for_opus_inverse_architecture.md → ответ Опуса (принят, анализ ниже). + +## Принятые решения (по Опусу) + +1. **Модель delete_params** → заменить плоский `{Code, Value}` на `{Code, Mode, Value?}`: + - `Mode: static` — значение из `Value` (дефолт, обратная совместимость). + - `Mode: zero_count` — обнулить integer-поля в элементах array-map-fixed, взяв live. +2. **Баланс данные/логика**: форма преобразования выводится из `dataType` + (boolean→"false", array-map-fixed→zero integer); сентинелы-значения — ТОЛЬКО данные в реестре. +3. **Маркер поля**: явный `zero_fields:["count"]` (или флаг на sub_param), а НЕ «обнулить все integer» + (риск: порт/приоритет/индекс в том же object). +4. **Порядок destroy** — обратный порядок создания из `depends_on`. + +## Мои замечания к ответу (что Опуc недоговорил) + +- **A. Граф уже правильный.** Факт: `edge_net` (SNAT) зависит от `org_ips` (IP), `org_ips` — от `edge`. + Обратный порядок destroy: `edge_net → org_ips → edge → vdc` уже корректен. + Рекомендация Опуса «сделать ip_space зависимым от edge_net» — перепутана направлением; граф уже такой. +- **B. Источник live для zero_count в Delete не указан.** `Delete` модификатора имеет только TF `state`, + а live `vIPConfigure` надо читать через `GetInstanceStateParams` в рантайме Delete. +- **C. Отличие sentinel от имени в valueList не разобрано** (valueList без разметки sentinel в данных API). +- **D. Идемпотентность zero_count при повторном destroy не поднята** (count=0 → снова 0: no-op?). + +## Открытые вопросы (второй раунд к Опусу) — ЗАКРЫТЫ + +1. **Источник live для zero_count в Delete** → `GetInstanceStateParams` (не TF state). ✅ +2. **Sentinel в valueList** → явный `off_value:"no-needed"` в реестре (данные, не логика). ✅ +3. **Идемпотентность zero_count** → пропускать `run`, если live уже `count=0` (применимо и к static). ✅ +4. **count=0** → канонический inverse; полное удаление ipSpace = отдельный опциональный `Mode:remove` (не подменять zero_count). ✅ + +## ИТОГ — финальная модель inverse + +`delete_params: []{ Code, Mode, Value?, zero_fields?, off_value? }` +- `Mode: static` — обратное значение = `Value` (boolean→"false"; string→off_value). +- `Mode: zero_count` — взять live array-map-fixed, обнулить поля из `zero_fields:["count"]`. +- `Mode: remove` (опц., не для FullPipe) — полное удаление элемента. + +Рантайм: Delete → `GetInstanceStateParams` → построить inverse → если live уже целевое → no-op (skip run) → иначе `modify`. + +Реестр (yaml-generator main.go): +- `vc_org.ip_space`: `inverse`, param `vIPConfigure` `Mode:zero_count, zero_fields:[count]`. +- `vc_nsxt.network`: `inverse`, params `needEnableAVI`=static `"false"`, `ipSpaceName`=static off_value `"no-needed"`. + +Порядок destroy (уже корректен в .tf): `edge_net → org_ips → edge → vdc`. + +## Факты (не менять, проверено) + +- `count=0` принимается API (minvalue:1 в схеме — не отвергает), идемпотентно (ORG_IP_MODIFIER_TEST_2026-09-22). +- SNAT off = `ipSpaceName="no-needed"` (sentinel в valueList, HAR_SNAT_MODIFY_FINDINGS). +- `needEnableAVI` boolean → inverse `"false"`. +- vc_org.ip_space: `error` → нужен `inverse` (zero_count). vc_nsxt.network: inverse+static `needEnableAVI=false`, добавить `ipSpaceName="no-needed"`. diff --git a/prompt_for_opus_inverse_architecture.md b/prompt_for_opus_inverse_architecture.md new file mode 100644 index 0000000..0e5663b --- /dev/null +++ b/prompt_for_opus_inverse_architecture.md @@ -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 предложения на пункт. Без лишнего. diff --git a/prompt_for_opus_modifier_global_architecture.md b/prompt_for_opus_modifier_global_architecture.md new file mode 100644 index 0000000..7e230fc --- /dev/null +++ b/prompt_for_opus_modifier_global_architecture.md @@ -0,0 +1,45 @@ +# Глобальная архитектура модификаторов: как сделать их НЕ инвазивным дополнением + +ЗАПРЕЩЕНО лезть в файлы репозитория. Отвечай только по тексту. Формат: тезисы, кратко, по пунктам моего вопроса. Без лишнего. + +## Контекст + +Terraform provider для Nubes Cloud. Цепочка кодогенерации: +1. `01_generate_yamls` — идёт по API, по каждому облачному сервису тянет операции и параметры, пишет универсальный YAML (`resources_yaml/_.yaml`). +2. `02_generate_resources` — по этому YAML генерирует Go-ресурсы провайдера (`__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 предложения на пункт.