Files
houseplan-card/docs/specs/478-remove-room-drafts.md
T
2026-09-06 17:38:47 +03:00

532 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ТЗ #478 — отказ от persisted-сущности `room_drafts`
- **Issue:** https://github.com/Matysh/houseplan-card/issues/478
- **Статус:** реализовано локально; готово к независимому код-ревью, канонический статус задаётся метками issue
- **Тип / приоритет:** tech debt + migration / P2
- **Трек:** полный — меняются каноническая модель, импорт/экспорт, backend validation,
редактор плана и контракт Undo/Redo
- **Оценка:** пользовательская ценность 7/10; архитектурная ценность 9/10;
сложность 9/10; риск 8/10
- **Связано:** #173 (единый инструмент «Стены»), #248 (идемпотентный Optimize),
#282 (атомарная wall model), #314 (транзакционный geometry write), #461
(быстрый commit клика), #477 (fixed point после преобразования)
## 1. Сценарий
Персона — администратор дома. Поверхность — desktop Plan editor, инструмент
«Стены». Пользователь ставит точки цепочки. Каждый завершённый отрезок уже
является обычной стеной. Если новый отрезок замкнул допустимую область,
редактор предлагает создать комнату; если пользователь прекратил рисование,
перешёл в другой режим или покинул карточку, сохранённые отрезки остаются
обычными независимыми стенами.
После возвращения пользователь может поставить первую точку новой цепочки на
конце существующей стены благодаря обычному snap. Специального «продолжения
черновика» и скрытой группировки старых отрезков больше нет.
Touch-редактор остаётся best effort согласно `docs/TOUCH-SUPPORT.md`. View и
kiosk не меняют свой interaction contract.
## 2. Что человек увидит до и после
**До:** незамкнутая цепочка хранится отдельной сущностью `room_drafts`. Она
может специальным образом возобновляться после reload, отдельно участвует в
snap, выборе, толщине, удалении, Optimize и миграциях. При создании комнаты
черновик превращается в masonry, а совпадающие обычные перегородки могут
остаться дубликатами.
**После:** каждый принятый отрезок сразу сохраняется как обычная независимая
стена (`partition`). Пока текущая цепочка жива в памяти, её части могут
подсвечиваться действующим жёлтым active-stroke. Замыкание комнаты атомарно
заменяет совпадающие участки независимых стен каноническими стенами комнаты.
Незамкнутые участки остаются стенами без дополнительного действия.
Панель, жесты, snap, Shift 45°, предложение комнаты и внешний вид законченной
геометрии не получают новой настройки или отдельной кнопки.
## 3. Проблема
`room_drafts` дублирует роль независимых стен и проходит почти через весь
продукт: TypeScript/Python wall model, schema, import/export, diagnostics,
Optimize, renderer, snap, limits, editor lifecycle и history. Это создаёт две
модели одного физического отрезка и делает простой пользовательский принцип
«клик — стена; замкнул — комната» зависимым от специального persisted carrier.
Удалить только UI-понятие нельзя. Если просто записывать цепочку в
`partitions`, текущая логика создания комнаты добавит masonry поверх уже
сохранённых стен. Получатся совпадающие физические тела, неоднозначные IDs,
лишние линии и нестабильный Optimize. Поэтому переход включает новую версию
модели, однократную миграцию и атомарное поглощение совпадающих partitions.
## 4. Решения владельца
Зафиксированы в issue 6 сентября 2026 года:
1. Специального resume после reload/remount нет. Новая цепочка начинается с
обычного snap к существующему торцу и использует текущую толщину панели.
2. Сохранённые незамкнутые контуры не имеют отдельной подсветки: это обычные
стены.
3. При принятии комнаты совпадающие independent walls поглощаются сразу; рядом
с masonry не остаются дублирующие partitions.
4. Модель меняется с v9 на v10. Legacy `room_drafts` мигрируют в partitions.
## 5. Scope
### 5.1 Входит
1. Wall model v10 в TypeScript и Python.
2. Детерминированная миграция каждого legacy draft segment в partition с
удалением `room_drafts`.
3. Запись каждого нового принятого отрезка текущей цепочки непосредственно в
`partitions` с сохранением crash safety и быстрым локальным preflight #461.
4. Session-only состояние активной цепочки: точки, толщины и IDs её partitions.
5. Face detection на общей masonry/partition topology без persisted draft.
6. Атомарное поглощение совпадающих partitions при создании/разделении комнаты,
включая lineage, split-остатки и rehosting проёмов.
7. Undo/Redo, Cancel, Esc, смена инструмента/режима/пространства и лимиты.
8. Удаление текущих draft-specific типов, UI, renderer/snap веток, diagnostics,
import/export проекций и мёртвых переводов.
9. Unit, parity/migration, production-bundle smoke, mutation witnesses,
performance и документация.
### 5.2 Не входит
- изменение алгоритма выбора допустимых faces, их порядка или полей комнаты;
- автоматическое разделение HA Area либо комнаты независимой стеной;
- объединение коллинеарных partitions вне участков, поглощаемых принятой
комнатой;
- общий фикс задержки terminal click из #461 сверх сохранения его бюджета;
- новая кнопка Finish, новая настройка, отдельный цвет «незаконченных стен»;
- визуальный redesign инструмента «Стены», wall properties или room dialog;
- изменение physical geometry, glow, sun, area или opening semantics;
- восстановление grouping старого draft после миграции;
- поддержка downgrade записи v10 старой карточкой.
Расширение любой из этих границ требует возврата issue в `S3-spec`.
## 6. Каноническая data model v10
### 6.1 Текущая схема
`SpaceModel` больше не содержит `room_drafts`; тип `RoomDraftCfg` удаляется.
Физические линейные объекты пространства представлены только:
- room masonry (`rooms` + `wall_segments`);
- independent walls (`partitions`);
- `wall_columns`.
Каждый partition имеет стабильный `id`, две конечные точки `a`/`b` и `cm` по
действующему контракту толщины. `cm: 0` сохраняет смысл нулевой стены. Поле
`room_drafts` не записывается, не экспортируется и не возвращается Optimize.
### 6.2 Legacy read и stale write
Schema/import boundary может прочитать `room_drafts` только как legacy input,
который должен быть мигрирован до текущей семантической проверки. Для документа
с `model_version >= 10` непустое или пустое явно переданное `room_drafts`
недопустимо.
Если устаревший v9 frontend пытается записать `room_drafts` поверх уже
сохранённого v10 config, backend отклоняет весь write существующей ошибкой
`wall_model_client_outdated` с действующим требованием обновить карточку и
перезагрузить страницу. Поле нельзя молча отбросить, частично принять или
вернуть в current config.
Current-model save после reload, import, copy, Optimize и editor commit всегда
остаётся v10 и не реанимирует legacy carrier.
## 7. Миграция v9 и более старых документов
### 7.1 Нормативное преобразование
До v10 atomization для каждого draft в порядке массива и каждого ребра
`points[i] → points[i + 1]` создаётся ровно один partition:
```text
partition.id = valid unique draft.segments[i].id
либо deterministic legacy fallback id
partition.a = draft.points[i]
partition.b = draft.points[i + 1]
partition.cm = normalized draft.segments[i].cm/current legacy fallback
```
После успешного преобразования `room_drafts` удаляется. Миграция не
объединяет, не дедуплицирует и не поглощает совпадения: одно legacy edge даёт
один partition, чтобы migration сама не меняла topology сверх carrier type.
Коллинеарные, совпадающие, обратные и нулевые стены сохраняются как отдельные
records. Действующие validators затем решают, допустим ли результат.
### 7.2 Идентичность и parity
- Валидный уникальный legacy segment ID переносится без изменения.
- Отсутствующий/невалидный/повторный ID получает детерминированный
collision-safe ID из space identity, draft identity, edge index и geometry.
- TypeScript и Python обязаны выдавать один и тот же ID и порядок записей для
одного JSON input.
- Generated ID не зависит от locale, object key order, runtime random/clock и
абсолютного пути.
- Unknown fields существующих partitions/rooms сохраняются; draft-only unknown
fields и grouping намеренно не переносятся.
- Некорректные points/segments обрабатываются одинаково с действующей legacy
политикой fail-closed; миграция не создаёт non-finite или zero-length record,
если такой input ранее был отклоняемым.
### 7.3 Отчёт
Migration/Optimize/import diagnostics отдельно сообщают число преобразованных
draft records и edge records. Повторный commit v10 возвращает нули и
byte-equivalent geometry: миграция строго однократна и идемпотентна.
## 8. Контракт активной цепочки
### 8.1 Session state
Активная цепочка хранит только в памяти:
- ordered points;
- per-edge `cm`;
- ordered IDs уже сохранённых partitions;
- gesture/face-offer state, необходимый текущему editor lifecycle.
Первый клик ставит стартовую точку и не меняет config. Каждый следующий
принятый клик добавляет один partition и его ID в session state. Активный
жёлтый stroke может определяться этим списком IDs, но не persisted-флагом.
После reload, remount, смены пространства или повторного входа в редактор
session state пуст. Сохранённые partitions остаются на плане. Клик в их торец
использует общий snap, но начинает новую цепочку; толщина берётся с панели в
момент нового сегмента и не наследуется скрыто.
### 8.2 Быстрый commit
Draft-specific fast path #461 заменяется общим wall-chain partition append
path. Для одного terminal click он обязан:
1. проверить partition limit до mutation;
2. создать стабильный ID и candidate partition;
3. выполнить тот же bounded production physical/junction proof, что #461;
4. атомарно принять одну geometry/history/save transaction;
5. при любом отказе вернуть config, path, cm и ID arrays к `before`.
Нельзя возвращать полный синхронный preflight всего пространства на каждый
клик. Fallback к общему barrier допустим только при тех же fail-closed условиях,
которые закреплены #461. Performance profile переименовывается по текущему
carrier, но исходные budgets не ослабляются.
### 8.3 Завершение без комнаты
Смена инструмента/режима/пространства, Esc по #294 и уход из editor только
завершают session chain: сохранённые partitions не преобразуются и не получают
дополнительную history/save запись. Одна несохранённая стартовая точка
отбрасывается. Команда «Оставить стенами» в room offer делает то же: закрывает
соответствующий offer/chain без копирования геометрии.
## 9. Face detection и принятие комнаты
### 9.1 Источники graph
Face graph строится из current masonry и partitions, включая только что
сохранённый terminal partition. Active chain нельзя добавлять второй раз как
отдельный synthetic source. Provenance каждого graph atom содержит исходный
room-wall либо partition ID и его параметрический интервал.
Предложение появляется по действующему click-only delta contract: новая face
должна быть создана последним принятым отрезком и пройти существующие проверки
simple polygon, overlap, limits, openings и batch ordering.
### 9.2 Поглощение совпадающих partitions
При подтверждении room batch система в одном `before → candidate` commit:
1. создаёт/разделяет room по действующему алгоритму;
2. назначает канонические room wall IDs через
`fixedTopologyWallLineageHints` или эквивалентный общий helper;
3. находит все интервалы partitions, совпадающие с принятыми room-wall
intervals в пределах canonical epsilon;
4. удаляет только поглощённые интервалы;
5. сохраняет непоглощённые остатки как partitions;
6. rehost-ит partition openings на новый canonical carrier или нужный остаток;
7. валидирует candidate полным physical geometry/junction barrier;
8. добавляет ровно одну именованную history command и один save.
Поглощаются как partitions текущей session chain, так и ранее сохранённые
partitions, реально образующие принятую границу. Простое совпадение с чужой
комнатной стеной не удаляет эту стену. Не связанные с принятой face объекты не
меняются.
### 9.3 Split и lineage
Если partition лишь частично совпал с новой masonry, он детерминированно
делится. Child, содержащий прежнюю `a`/наименьший canonical midpoint, сохраняет
старый partition ID; остальные children получают стабильные производные IDs.
Порядок и направление исходной записи не меняют итоговый canonical result.
Openings целиком в поглощённом интервале переходят на room wall. Openings в
остатке остаются на соответствующем child. Opening, пересекающий точку split,
подчиняется существующей fail-closed политике reconciliation: ни один проём не
может исчезнуть, дублироваться или оказаться без carrier.
После успешного room commit немедленный Optimize обязан сообщить:
- `partitionsReconciled = 0`;
- `redundantDraftsRemoved = 0` либо поле полностью удалено из current report;
- второй Optimize не меняет geometry/config.
Cancel, validation failure, save conflict и лимит оставляют исходные
partitions и session chain без partial room и без потерянных IDs/openings.
## 10. Undo/Redo и lifecycle
- Один успешный segment click — одна named history command. Пока session chain
активна, Ctrl/Cmd+Z удаляет последний сохранённый partition по его ID и
последнюю точку/толщину; Redo возвращает тот же ID/cm и синхронизирует chain.
- Когда session chain уже завершена, обычный geometry Undo/Redo работает с теми
же partition records без draft-specific восстановления.
- Принятие room batch — одна атомарная команда: Undo восстанавливает точный
набор поглощённых partitions/openings; Redo повторно создаёт тот же room-wall
identity без дублей.
- Cancel room dialog восстанавливает/сохраняет активную session chain и её уже
записанные partitions; он не создаёт новую геометрию.
- Esc при открытом диалоге сначала закрывает диалог по текущему modal contract;
следующий Esc завершает chain, не удаляя partitions.
- Failed preflight/save не оставляет history entry, redo entry, лишнюю точку,
stale active ID или partial config.
History labels меняются с «сегмент черновика» на нейтральный «отрезок стены» во
всех локалях, где строка остаётся видимой.
## 11. Limits, selection и свойства
- `MAX_ROOM_DRAFTS` и `MAX_DRAFT_SEGMENTS` удаляются.
- Каждый новый segment заранее резервирует одну запись в `MAX_PARTITIONS`.
Достигнутый лимит отклоняет клик до mutation и показывает действующее
локализованное limit message.
- Поглощение комнаты проверяет итоговые room/wall/partition/opening limits на
candidate, а не по промежуточному пиковому числу records.
- После завершения chain каждый отрезок выбирается, перемещается, получает
толщину/свойства и удаляется ровно как существующий partition.
- Draft-specific whole-chain/segment dialog, delete confirmation и special hit
target удаляются. Удаление обычной стены сохраняет текущий UX.
- Snap/Align/Grid/Resize/physical selection/zero-wall/near-axis code читает
partitions и room walls; отдельного draft source kind в current model нет.
## 12. Import, export, copy и diagnostics
1. Current full/plan-only export v10 не содержит `room_drafts`.
2. Legacy full/plan-only import сначала мигрируется и валидируется; preview и
summary показывают итоговые partitions и migration counts.
3. Copy space работает только с v10 geometry после materialization и
remap-ит partition IDs/opening hosts обычным путём.
4. Diagnostics/support package/system health не перечисляют current drafts.
Допустим legacy migration counter, но не persisted current count.
5. Coordinate canonicalization, Align, optimizer, wall limits и integrity
visitors перестают иметь отдельную ветку `room_drafts`.
6. `redundantDraftsRemoved` удаляется из current optimizer type/report/UI либо
остаётся только как явно deprecated legacy migration counter, всегда 0 в
v10. Один вариант должен быть одинаков в TS/Python/docs/tests.
7. Unknown future wall model по-прежнему отклоняется без downgrade.
## 13. UX, i18n и accessibility
Новых контролов нет. Основной visible flow остаётся прежним: «Стены», active
chain, Shift 45°, room offer, «Создать комнату»/«Оставить стенами», Cancel и
Undo/Redo.
Обязательные текстовые изменения:
- удалить упоминания сохранённого/возобновляемого «черновика контура»;
- объяснить, что незамкнутые отрезки сразу являются независимыми стенами;
- заменить draft-specific history/toast copy нейтральным wall wording;
- синхронно обновить все зарегистрированные локали, если ключ остаётся в i18n
registry; мёртвые ключи удалить во всех локалях и из parity tests.
ARIA pressed state, keyboard shortcuts, focus trap room dialog и touch target
не меняются. Session-only цвет не является единственным сигналом сохранения:
после завершения chain стена видна обычной masonry-графикой.
## 14. Acceptance criteria
- **AC1 (`unit` + TS/Python parity):** model v9 fixture с несколькими drafts,
разными cm, reverse/duplicate/zero walls и существующими partitions один раз
мигрирует в v10; каждый edge становится partition, валидные IDs сохранены,
generated IDs совпадают в TS/Python, `room_drafts` отсутствует.
- **AC2 (`unit` + backend):** повторный v10 commit/import/Optimize byte-stable;
current document с `room_drafts` и stale v9 write поверх v10 отклоняются
целиком с правильным outdated contract.
- **AC3 (`unit` + smoke):** первый click не пишет config; каждый следующий
accepted click создаёт ровно один partition/history/save с правильным ID/cm;
отказ/limit оставляет config и session bit-equivalent `before`.
- **AC4 (`performance` + mutation):** partition append использует bounded fast
path #461, не вызывает full-space barrier на нормальном current input и не
ослабляет timing/heap/full-check budgets исходного профиля.
- **AC5 (`smoke`):** open chain после смены tool/mode/space, Esc, reload и
remount остаётся набором ordinary partitions; special resume отсутствует,
новый segment использует snap и текущую toolbar thickness.
- **AC6 (`unit` + smoke):** room face определяется через masonry+partitions без
synthetic duplicate active source и только после terminal segment, который
действительно добавил face.
- **AC7 (`unit` + smoke):** создание standalone/adjacent/split room поглощает
все и только совпадающие partition intervals; итог содержит одну masonry,
нет coincident partitions, IDs/толщина/metadata каноничны.
- **AC8 (`unit` + smoke):** partial overlap детерминированно оставляет children,
сохраняет один исходный ID и rehost-ит room/partition openings без потери,
дубля и orphan host.
- **AC9 (`unit` + smoke):** Cancel/error/conflict/limit возвращают исходные
partitions/session; accepted batch имеет одну history/save transaction, Undo
восстанавливает точные partitions, Redo — тот же room result.
- **AC10 (`unit` + smoke):** active-chain Undo/Redo удаляет/возвращает один
terminal partition с тем же ID/cm и синхронизирует points; после finish
generic Undo/Redo не зависит от draft state.
- **AC11 (`unit`):** limits считают каждый segment как partition до mutation;
draft limits/types/source kinds/select/delete/renderer branches отсутствуют,
а обычные partition properties работают для бывшей chain geometry.
- **AC12 (`unit` + import smoke):** legacy full/plan-only import и space copy
дают валидный v10 output; current export/diagnostics/support package не
публикуют `room_drafts`; future model по-прежнему отклоняется.
- **AC13 (`unit` + optimizer witness):** Optimize сразу после принятия room
возвращает нулевую reconciliation работу и второй прогон byte-stable; мутант,
оставляющий coincident partition либо draft, краснит gate.
- **AC14 (`golden verify` + smoke):** View/Plan/static/hidden Iso, wall bodies,
openings, clean floor, glow и sun до/после carrier migration визуально
эквивалентны для одинаковой geometry; active chain остаётся читаемой.
- **AC15 (`typecheck` + `unit` + `build` + docs review):** implementation-loop
gates зелёные, три bundle-копии побайтно одинаковы, канонические документы,
RU/EN changelog и i18n соответствуют v10.
## 15. Автотесты и обязательные доказательства
### 15.1 Migration/parity fixtures
Добавить минимум:
1. v9 space с одним multi-edge draft и существующей partition;
2. несколько drafts с сохранёнными IDs;
3. pre-v8 draft без IDs и collision fixture;
4. reverse/order/object-key permutation;
5. `cm:0`, mixed cm и invalid/non-finite input;
6. repeat commit, import round-trip и TS/Python JSON parity;
7. stale v9 client write в current v10.
### 15.2 Editor unit и production-bundle smoke
Targeted smoke через реальный UI обязан:
1. нарисовать open chain и проверить partition после каждого terminal click;
2. выполнить Undo/Redo, Esc, tool/mode/space switch и reload;
3. продолжить от старого endpoint обычным snap с новой toolbar thickness;
4. замкнуть собственную face, face из старых partitions и adjacent face;
5. проверить partial consumed partition и hosted opening;
6. выбрать/изменить толщину/удалить бывший незамкнутый segment;
7. отменить room dialog и воспроизвести save rejection/limit;
8. проверить отсутствие `room_drafts` в config/export/Optimize result.
### 15.3 Red witnesses
Обязательные тесты, которые падают на `origin/dev` или при целевом мутанте:
- legacy `room_drafts` после materialization отсутствует и заменён partitions;
- замыкание по существующим partitions оставляет одну masonry и ноль
совпадающих partitions;
- immediate Optimize fixed-point;
- draw → leave editor → return → complete room сохраняет history/IDs и не
создаёт дублей;
- удаление partition fast path либо возврат full-space click path краснит
performance/mutation gate.
### 15.4 Golden и performance
Визуальный контракт намеренно не меняется, поэтому новые baselines по умолчанию
не принимаются. Выполняется `golden:verify`; любое отличие требует отдельного
объяснения и reviewed acceptance по процессу. Source fingerprint документации
обновляется только через Linux capture artifact, если его потребует
`check-docs`.
Профиль #461 адаптируется с draft carrier на partition carrier и сохраняет
исходные budgets. Отдельно измеряется face acceptance/reconciliation, чтобы
поглощение не попало в pointermove и не стало частью каждого незамыкающего
клика.
## 16. План реализации
1. Добавить v10 migration/parity fixtures и красные тесты.
2. Реализовать одинаковую TS/Python миграцию и backend stale-write guard.
3. Удалить `RoomDraftCfg` из current model, visitors, import/export и reports.
4. Обобщить fast preflight/commit #461 на append обычной partition.
5. Перевести editor active chain на session points/cm/partition IDs.
6. Перевести graph, renderer, snap, selection, limits и Undo/Redo на partitions.
7. Встроить lineage/reconciliation совпадающих partitions в atomic room batch.
8. Удалить draft properties/delete/resume code и мёртвый i18n.
9. Пройти targeted smoke, optimizer/mutation/performance witnesses.
10. Обновить docs/changelog, собрать и синхронизировать bundles.
Точные имена helpers и файлов не являются продуктовым контрактом. Общая pure
логика миграции/lineage предпочтительнее параллельных частичных реализаций.
## 17. Риски и снижение
| Риск | Вероятность / ущерб | Снижение |
|---|---|---|
| Миграция меняет wall identity | средняя / высокий | перенос valid segment ID, TS/Python parity fixtures |
| Созданная комната дублирует partition | высокая / высокий | atomic consume witness + immediate Optimize fixed point |
| Partial consume теряет остаток/проём | средняя / высокий | interval provenance, child-ID и opening-host matrix |
| Клик снова становится секундным | высокая / высокий | сохранить #461 fast path и budgets, mutation witness |
| Undo удаляет чужую совпадающую стену | средняя / высокий | session ordered IDs, exact before/after history |
| Старый frontend вернёт drafts | средняя / высокий | model v10 semantic rejection, outdated error smoke |
| Legacy malformed input расходится TS/Python | средняя / высокий | shared vectors и byte-level parity |
| Partition limit ломает длинную цепочку | средняя / средний | reserve до mutation, точный localized отказ |
| Удаление ветки ломает glow/sun/floor | низкая / высокий | physical parity + golden verify |
| Docs уже описывают целевое поведение частично | высокая / низкий | сверить оба guide с фактическим v10 contract |
## 18. Откат
Кодовый откат выполняется revert implementation commit, но уже сохранённый v10
config нельзя безопасно записывать старой v9 карточкой. Поэтому operational
rollback — восстановить предыдущий release и одновременно запретить editor
writes для v10 с понятным outdated сообщением; автоматическая обратная миграция
partitions в drafts запрещена, поскольку grouping утрачен.
До публичного релиза критический migration/parity/consume/optimizer gate
блокирует beta. Если дефект найден после beta, исправляется forward в v10;
понижение model version и синтетическое восстановление drafts не допускаются.
## 19. Release-артефакты
Изменение классифицируется как `User-Visible: yes`: внешний основной flow почти
тот же, но исчезает resume persisted draft и меняется поведение незамкнутых
отрезков после reload.
Implementation commit обновляет:
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` со ссылкой на #478;
- `docs/ARCHITECTURE.md` — model v10 и session chain;
- `docs/CANVAS.md` — face graph provenance и active-chain rendering;
- `docs/WALL-THICKNESS.md` — partition-to-masonry lineage;
- `docs/CONFIG-COMPATIBILITY.md` — v10 migration/stale clients/downgrade;
- `docs/USER-GUIDE.md` и `docs/USER-GUIDE.ru.md` — незамкнутые стены и отсутствие
special resume;
- `docs/TESTING.md` — migration/parity/smoke/performance witnesses;
- `docs/STATUS.md` после фактической реализации;
- i18n registry/locales, если меняются видимые history/toast строки;
- три поставляемые bundle-копии.
Нового screenshot baseline не ожидается. Перед beta обязательны exact-SHA
Linux Validate, targeted production smoke, `golden:verify`, performance smoke и
зелёное независимое code review.
## 20. Принятые технические предположения — можно менять без продуктового ревью
1. Session chain может храниться отдельным объектом вместо трёх массивов, если
остаётся неперсистентной и содержит ordered partition IDs.
2. `draft-live-preflight.ts`/`draft-live-commit.ts` можно переименовать либо
заменить общим модулем; важны boundary и budgets, а не имя.
3. Fallback ID может использовать действующий deterministic wall-ID helper,
если TS/Python parity и collision contract выполнены.
4. Reconciliation можно извлечь из `coincident-partitions.ts` либо выполнить
общим atomic helper до final wall-model commit.
5. Deprecated optimizer counter можно удалить либо оставить нулевым на один
compatibility cycle; решение должно быть единым во всех API/docs/tests.
6. Active-chain yellow style может адресоваться CSS/data-атрибутом по множеству
partition IDs без добавления persisted поля.
7. Открытых продуктовых вопросов нет: решения владельца полностью задают
resume, подсветку и поглощение совпадений.