Files
tf_provider/HISTORY/OPUS/2026-09-22_modifier_plan_review.md
T

5.3 KiB
Raw Blame History

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.