docs(plan): внести правки ревью Опуса в план (порядок, ID-миграция, override, array-map-fixed)

This commit is contained in:
Repinoid
2026-09-22 20:57:54 +03:00
parent e06a2c0b11
commit 2933c57a4b
2 changed files with 126 additions and 19 deletions
@@ -0,0 +1,72 @@
# Opus: ревью плана редизайна модификаторов — 2026-09-22
Источник: ответ на `prompt_for_opus_modifier_plan_review.md` (план `PLAN_modifier_redesign.md`).
## 1. Порядок шагов — скрытые зависимости
- Шаг 4 (шаблон) ссылается на API из шагов 6–7 → **сначала 5→6→7, потом 4**.
- Шаг 8 (yaml-generator) должен идти ДО регенерации `dev` и до сборки.
Скорректированный порядок: 1 → 2 → 3 → 5 → 6 → 7 → 4 → 8 → регенерация → 9 → 10.
## 2. Шаг 5 (вынос JSON-эквивалентности)
Путь верен. **Оставить реэкспорт-обёртку `JSONStringsEquivalent` в `json_normalize.go`**,
не заменять вызовы по всему resources_core (иначе диф на инстансы, вопрос 7).
Переносятся самодостаточные 5 функций: `JSONStringsEquivalent`, `normalizeJSONIfPossible`,
`encodeCanonicalJSON`, `writeCanonicalJSON`, `normalizeJSONScalarsToStrings`.
Вариант «готовые строки в core» — отклонить (размазывает нормализацию, не снимает
потребность в JSONStringsEquivalent в core).
## 3. Шаг 6 — сигнатура и сравнение
- Маппинг code→param по **двум** алиасам: `p.Code` И `p.SvcOperationCfsParam` (как в
operation_run_bycode.go:50-58). Один `Code` даст пропуски.
- Имя `modifierDesiredEqualsCurrent` — unexported, вызов внутри core. Слово «экспортированный» убрать.
- bool/int/string — `normalizeUniversalValueV6` + сравнение. map-fixed — `JSONStringsEquivalent`.
- **array-map-fixed — дыра:** `normalizeUniversalValueV6` строит дефолт только для
`map-fixed`/`HasPrefix "map"` (params.go:33); `array-map-fixed` туда не попадает →
сравнивать сырые значения через `jsonutil.JSONStringsEquivalent`, не через normalize.
- desired = только явно заданные коды (до досылки live/default), иначе pre-check всегда «равно».
## 4. Шаг 4.4 Delete=inverse — подводный камень
- Delete не имеет `plan` (только `req.State`). `reconcile(ctx, plan *Model)` не подходит.
→ `reconcile(ctx, model *Model, override map[string]string)`; для inverse override = delete_params.
- `deleteParams` — финальные **wire-строки** (`"false"`, готовый JSON), БЕЗ прогонки через
`ParamFormat`/тип. В реестре `deleteParam{Code, Value}` несёт готовую строку.
## 5. Шаг 8 — расширение реестра
Верно. Держать в `serviceSpecificModifiers` (main.go:34), не отдельным реестром.
Структура `modifierException` корректна. При переходе со `map[string]string` на структуру:
`ModifierName` берётся из структуры (сейчас `modName, ok := serviceSpecificModifiers[name]`
— строка 95).
## 6. Пропущенные кейсы
- **taint/replace + `delete_strategy=error`** — конфликт: replace = Delete→Create, Delete=error
блокирует → пользователь не сможет заменить error-модификатор. Решить явно:
запретить replace у error (документировать) или отличить «чистый destroy» от replace.
- **unknown в pre-check** — при unknown (computed ref) сравнение невозможно; шаг 6 должен
skip-ить pre-check при unknown (иначе пустая строка даст ложный diff/панику).
- **partial apply** — досылка live для незаданных + pre-check; проверить кейс «часть задана, часть live».
## 7. Риск сломать инстансы
Низкий при условиях:
- **НЕ удалять `CompactParams`** (helpers.go:68) — убирается только из modifier-шаблона;
функция нужна инстанс/action.
- **Шаг 7 — новый метод `RunOperationByCodeIdempotent`, НЕ менять сигнатуру**
`RunOperationByCodeWithTimeout`/`RunInstanceOperationUniversalByCode` (зовут инстансы).
- Шаг 1 (поля OperationSpec) аддитивен — безопасно.
## Дополнительно (не в вопросах)
- **Шаг 4.3 (ID=identity) — ломающая миграция state.** Смена формата ID изменит ID уже
задеплоенных модификаторов → Terraform форснёт replace. Нужно: либо сохранить старый
формат ID, либо явный state-migration plan. В плане не отмечено.
- **Шаг 3 — `normalizeDeleteStrategy`/`normalizeIdempotency`** — где живут (в loader.go, рядом
с веткой modifier). Не указано.
- **ValidateSpec** — проверка `delete_params.code ∈ op.Params` по lower-code; сверить поле `Code`.