diff --git a/HISTORY/OPUS/2026-09-22_modifier_architecture_project.md b/HISTORY/OPUS/2026-09-22_modifier_architecture_project.md index eec0e02..368908b 100644 --- a/HISTORY/OPUS/2026-09-22_modifier_architecture_project.md +++ b/HISTORY/OPUS/2026-09-22_modifier_architecture_project.md @@ -191,3 +191,58 @@ idle? нет → wait, re-read иначе → reconcile (полный payload) ``` +--- + +# Ответы Opus №3 (сверка с фактическим кодом, расхождения + сомнения) + +## Факт №1: контракт — в lib, поведение — в GenModifier + +Подтверждено: `delete_strategy`/`idempotency`/`delete_params` добавляются в +**`lib.OperationSpec`** (`TOOLS/lib/types.go`), resource-generator получает через алиас +(`types.go:19`). Ссылка «types.go:39» была неточной — канон в lib. + +Граница: +| Где | Что | +|---|---| +| `lib.OperationSpec` / `lib.ParamSpec` | всё из YAML, видно обоим генераторам | +| `GenModifier` (локально) | производные для шаблона, флаги `Needs*`, готовый inverse-список | + +Правило: парсится из YAML → lib; вычисляется загрузчиком для шаблона → GenModifier. + +## Факт №2: pre-check — в `core` (вариант A), экспортировать сравнение + +`normalizeUniversalValueV6` приватная и требует `universalCfsParam` — в `resources_core` +этих данных нет. Pre-check делать **в `core`**, не в resources_core и не в шаблоне. + +Конкретно — экспортированный метод в `core`, вызывается из `operation_run_bycode.go` +сразу после `fetchOperationCfsParams`: +```go +func (c *UniversalClient) modifierDesiredEqualsCurrent( + desired map[string]string, cfsParams []universalCfsParam) bool +``` +Сравнение: нормализовать обе стороны через `normalizeUniversalValueV6`; для +map-fixed/array-map-fixed — JSON-эквивалентность. Но `JSONStringsEquivalent` лежит в +`resources_core` → импорт в `core` даст **цикл**. Вынести JSON-эквивалентность в +нейтральный пакет (`core/jsonutil` или в сам `core`) и переиспользовать в обоих местах. + +Вариант C (только `JSONStringsEquivalent` без нормализации) — **отклонён** (ложный diff +`true`/`1`). + +Управление: `RunInstanceOperationUniversalByCode` получает флаг `idempotent` (из +`GenModifier.Idempotency` → шаблон → параметр вызова); idle-гейт выше pre-check. + +## Дополнительные сомнения (ответы) + +1. **Idempotency и полный payload НЕ конфликтуют** (разные уровни). Бинарно на весь + модификатор: `ALL == ALL` → skip целиком; любое расхождение → полный payload. + Полудельты нет. +2. **Частичный inverse — допустим и правилен.** `delete_params` покрывает только + обратимые поля; необратимые/constraint просто не входят. Fail-fast смягчить: + ошибка не «inverse обязан покрыть всё», а «код в delete_params обязан существовать + в params и value удовлетворять constraint». Delete при inverse = modify с + delete_params + досылка live остальных (полный payload). +3. **`noop_warn` дефолт — оставить, но критичные — вручную `error`.** Дефолт мягкий + (`noop_warn`, всегда с `AddWarning`), а необратимые (`ip_space`) автор спеки явно + помечает `delete_strategy: error` в YAML. Генератор сам не решает обратимо/необратимо. + +