docs(modifier): анализ inverse-архитектуры + промпты Опусу (глобальная архитектура оверлея)
This commit is contained in:
@@ -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"`.
|
||||
@@ -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,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 предложения на пункт.
|
||||
Reference in New Issue
Block a user