Волна 5 эпика #674, перенос ТЗ (класс C). Из 240 файлов `docs/specs/` в `legacy/specs/` уехали 219: на них не ссылается ни один живой файл (код, тесты, скрипты, workflow, документы вне архива и ревью). Остались 21 ТЗ — на которые ссылаются код, ADR, ISOMETRIC, SUN, RADAR, LIGHT (`docs/specs/067`), DECOR-EDITOR, support-relay, и те, на которые ссылаются они сами; README каталога объясняет, где искать остальное. Открытых issue с файлом ТЗ среди перенесённых нет. Относительные ссылки перенесённых файлов переписаны (`../X` → `../../docs/X`, соседние оставшиеся ТЗ → `../../docs/specs/…`) — все 26 резолвятся. Попутно: битая ссылка в `089-isometric-view-stage1.md:8` на удалённый `089-isometric-view.md` — теперь команда `git show` по истории. Строка в `legacy/README.md`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
39 KiB
Issue #282 — стабильная идентичность сегментов стен (ADR Stage 1)
- Issue: https://github.com/Matysh/houseplan-card/issues/282
- ADR:
docs/adr/282-wall-geometry-representation.md - Статус: принято независимым ревью; Stage 1 реализован и проверен
- Тип / приоритет: tech-debt / P1
- Поставляемый этап: Stage 1 — stored identity
- Целевая версия модели:
PLAN_MODEL_VERSION = 8 - Пользовательское изменение: да, но без нового интерфейса
1. Сценарий
Персона: администратор дома из docs/SCOPE.md, который уже построил план и
поддерживает его в Plan editor на desktop. Момент: Resize, Split, Merge,
изменение толщины, размещение проёма, импорт либо Optimize меняют геометрию
существующего плана. View, kiosk и touch-потребители затем должны увидеть тот же
дом без пропавшей толщины, переехавшего проёма или нового шва.
Задача обслуживает J6: «Keep the plan true as the home evolves». Это не новая возможность рисования, а устранение причины, по которой уже сохранённая стена теряет смысл после дальнейшего редактирования.
2. Что человек увидит до и после
До: после сложного изменения внешне та же стена иногда теряет толщину, получает толщину соседнего участка либо отделяется от своего проёма. После: та же операция сохраняет принадлежность толщины и проёмов стене; внешний вид и состав кнопок не меняются.
Единственное новое видимое состояние — честный отказ от структурной записи, если старый план невозможно преобразовать без потери данных. View продолжает работать по compatibility reader; диалог/тост называет причину, предлагает сначала запустить «Оптимизировать планы», а при повторном отказе — исправить конфликтующую геометрию стен.
3. Проблема и подтверждённая причина
Сегодня contour wall одновременно является:
- ребром
rooms[].poly; - разреженной записью толщины
space.walls[], найденной по вычисленномуwallKey(квантованные midpoint + angle); - пространственно найденным носителем room opening без сохранённой ссылки.
Геометрическая операция обязана заново вывести все три связи. Пропущенный или слегка отличающийся re-key теряет толщину (#253/#258); частичное совпадение создаёт неоднозначную роль (#299); Split/Resize вынуждены материализовать и переименовывать атомы до изменения контура. Канонизация координат #291 убрала битовый noise, но не устранила вывод идентичности из изменяемой геометрии.
Independent partitions[] уже имеют стабильный id, а hosted openings уже
ссылаются на него. Stage 1 переносит этот доказанный принцип на contour walls и
на незавершённые draft segments. Renderer и вычисление junction geometry пока
не переписываются: они получают compatibility projection из новой модели.
4. Скоуп
В задачу входят:
- стабильный persisted
idкаждого атомарного contour segment; - ссылки комнат на эти ID в порядке обхода контура;
- толщина на самом segment record, включая отсутствие физического тела;
- стабильные ID draft segments; существующие partition IDs сохраняются;
- wall-host для door/window/gate/passage на contour segment;
- детерминированная v7 → v8 миграция и runtime-проекция без записи на read;
- единый identity writer/barrier для Resize, Split, Merge, Delete, draw/close, thickness, opening placement, Optimize, Undo/recovery и import/export;
- compatibility projection
rooms[].poly,walls[], legacy unhosted openings; - backend validation, limits, import remap и fail-closed stale-client policy;
- unit/backend/invariant/mutation/smoke/golden/performance evidence;
- обновление канонической документации и обоих changelog.
5. Не-скоуп
- integer node indices и новый
STORAGE_VERSION(ADR Stage 2); - отказ от
rooms[].polyкак compatibility projection (Stage 3); - единый persisted planar graph для room walls и partitions (Stage 3);
- closed-form junction renderer и удаление polygon booleans (Stage 4);
- новый UI, изменение жестов, новые инструменты или иная геометрия результата;
- исправление видимого дефекта конкретного junction, не вызванного identity;
- индивидуальная настройка ID либо их показ пользователю;
- автоматическое притягивание authored off-grid geometry;
- удаление legacy reader в этом релизе.
Stages 2–4 требуют отдельных issues и поставляются независимо. Закрытие #282 после Stage 1 означает завершение текущего исполнимого этапа ADR, а не отмену оставшегося направления.
6. Целевая persisted-модель
6.1 Contour segments
В пространство добавляется полный каталог атомарных contour walls:
interface StoredWallSegment {
id: string; // stable, unique inside one space
a: [number, number];
b: [number, number];
cm: number; // 0..100; 0 = axis without a physical body
}
interface RoomCfg {
poly: number[][]; // required compatibility projection in v8
wall_ids: string[]; // one id for every consecutive poly edge
}
interface SpaceCfg {
wall_segments: StoredWallSegment[];
walls?: LegacyWallEntry[]; // generated compatibility projection
}
wall_segments[] содержит каждый атом contour, а не только участок с
положительной толщиной. В v8 wall_segments[].id/a/b/cm и
rooms[].wall_ids[] являются authoritative для identity и толщины.
rooms[].poly остаётся authoritative для формы комнаты до ADR Stage 3, но в
v8 его стороны и wall_ids обязаны образовывать exact согласованную пару:
wall_ids.length === poly.length;- segment с соответствующим ID совпадает с ребром
poly[i] → poly[i+1]в прямом либо обратном направлении; - один ID может ссылаться из одной комнаты (
outer) или двух комнат (shared); - ссылка из трёх и более комнат, orphan segment либо дублированный ID невалидны;
- shared segment обязан иметь одни exact endpoints и один
cmдля владельцев.
Если общий участок занимает только часть длинной стороны комнаты, миграция добавляет коллинеарные vertices и делит сторону на атомы. Площадь, winding, silhouette и видимый SVG при этом не меняются.
6.2 Толщина 0
cm: 0 в catalog означает действующее сегодняшнее состояние «контур есть,
физического wall body нет». Это техническое представление полной модели, а не
пользовательская возможность issue #306:
- Plan UI в #282 по-прежнему предлагает прежний диапазон и инструменты;
- legacy sparse interval без
walls[]мигрирует в segmentcm: 0; - virtual/open boundary продолжает определяться
open_spans/open_to, а не однимcm: 0; - body/area/opening/light semantics остаются byte/geometry-equivalent v7.
Разрешение пользователю рисовать zero-thickness walls и отказ от virtual walls остаются в приостановленном #306 и не входят скрытым изменением сюда.
6.3 Independent walls и drafts
partitions[].id уже является стабильной identity и не мигрирует в новый
catalog до Stage 3. Его endpoints/cm остаются на partition record.
Каждая room_drafts[].segments[] получает id:
segments: Array<{ id: string; cm: number }>
ID сохраняется при продолжении/перемещении draft. При превращении draft в partition либо contour соответствующий segment наследует ID, если его carrier не был разделён. При split действуют lineage-правила §8.
6.4 Openings
Host становится tagged union:
type OpeningHost =
| { kind: 'partition'; id: string; t: number }
| { kind: 'wall'; id: string; t: number };
Новые room openings всегда сохраняют wall host. t направлен от stored a к
b; визуальный flip/створка от направления segment не зависят. Legacy opening
без host остаётся читаемым и получает runtime host spatial resolver. На первой
структурной записи host материализуется только при единственном доказанном
carrier; неоднозначность блокирует миграцию, а не выбирает ближайший наугад.
6.5 Compatibility projection walls[]
walls[] временно остаётся persisted projection для старых клиентов и
неизменённого renderer:
- генерируется централизованно из
wall_segments(положительныеcm, exacta/b, вычисленный compatibilitykey); - не является source of truth в v8;
- v8 backend проверяет его semantic parity с catalog, но key drift сам по себе не делает catalog невалидным;
- новый frontend никогда не присваивает
walls[]напрямую; - runtime geometry получает прежний
WallEntry[]через один adapter; - в v7 отсутствие catalog сохраняет нынешний reader без записи.
Re-key остаётся только деталью compatibility serializer. Ни Resize, ни Split, ни business logic не используют key как identity после v8 migration.
7. Атомизация и deterministic migration v7 → v8
7.1 Breakpoints
До вычисления breakpoint в том же local candidate и до любых v8 ID/ref
запускается существующий write-barrier #291 canonicalizeConfigGeometry.
Порог и правило принадлежат только ему: текущий LATTICE_NOISE_STEPS = 1e-4
шага сетки. Все слова exact ниже означают побитовое равенство уже
канонизированных координат; дополнительный wall-thickness epsilon и третий
допуск Stage 1 не вводит. Если канонизация или последующая атомизация
отклоняет candidate, исходный v7 document остаётся byte-equivalent.
Каждая room boundary разбивается в точках:
- vertices всех room polygons;
- начала/концы коллинеарного overlap другой комнаты;
- exact
walls[].a/bи доказанные границы key-only legacy record; open_spansи доказанные sharedopen_tospans;- пересечения с structural nodes, уже используемые текущим wall profile.
Opening edges не делят segment сами по себе: opening хранит t и ширину.
Нулевая длина, self-overlap, конфликтующие positive thickness records либо
неоднозначный key-only carrier блокируют миграцию всего пространства.
7.2 Толщина
Для каждого атома выбирается ровно действующий v7 resolver:
- exact matching
walls[].a/b; - exact covering record;
- legacy key/midpoint fallback;
- отсутствие записи →
cm: 0.
Разные positive candidates на одном атоме — blocker, не min/max/last wins.
Open boundary не удаляет segment и не меняет cm; light/body projection
применяет open cut отдельно, как до миграции.
7.3 ID migration
Первоначальный ID детерминирован для повторяемой миграции одного v7 document:
wall-<base32(sha256(space-id | canonical-a | canonical-b | sorted-owner-ids))[0:20]>
Endpoint order не влияет на hash. Collision после усечения разрешается
детерминированным -2, -3 после сортировки полной digest; collision полного
digest либо duplicate resulting ID блокирует миграцию.
Это только seed миграции. После сохранения ID никогда не вычисляется заново из geometry. Новые IDs создаёт один monotonic/random id factory с проверкой уникальности в space; импорт использует общий bounded remapper.
7.4 Момент миграции
Read-only загрузка v7 не пишет Store и не повышает model version. Runtime строит immutable projected catalog для View и editor selection.
Атомарная v8 materialization выполняется:
- перед первой успешной structural config mutation пространства;
- явным Optimize plans;
- при добавлении v7 space в уже v8 config;
- при full/space import candidate, если target требует v8.
Изменение только настроек устройства/пространства, layout marker position или
View state не мигрирует wall model. Успешная materialization записывает catalog,
room refs, hosted openings, compatibility projection и model_version: 8 одной
config transaction/Undo command. Ошибка оставляет candidate byte-equivalent.
8. Identity lineage при операциях
ID сохраняется, когда физический/топологический carrier остаётся тем же объектом, даже если изменились endpoints, angle, length, owner или compatibility key.
8.1 Move и Resize
- whole segment, перенесённый/растянутый одной доказанной операцией, сохраняет ID;
- shared segment, одинаково преобразованный обоими owners, остаётся одним ID;
- несовместимые transforms owners отклоняются preflight;
- изменение только endpoints никогда не создаёт новый ID.
8.2 Split одного segment
При появлении breakpoint исходный ID получает ровно один child:
- child, содержащий старый midpoint внутри;
- при midpoint на новой границе — child, содержащий canonical old
a; - остальные children получают новые IDs в геометрическом порядке от old
a.
Openings rehost по их physical centre/interval и получают пересчитанный t.
Opening, пересекающий новую границу между children с разными условиями,
блокирует операцию до записи.
8.3 Merge segments
Слияние разрешено только для коллинеарных соседей с одинаковыми cm, owner set,
open-boundary role и без semantic breakpoint. Survivor ID выбирается:
- segment с большим количеством существующих opening hosts;
- затем самый длинный;
- затем лексикографически меньший ID.
Все refs/hosts переводятся на survivor с пересчитанным t. Остальные IDs
исчезают; tombstone не хранится, поскольку Undo содержит полный snapshot.
8.4 Split/Merge/Delete rooms
- смена outer ↔ shared сама по себе ID не меняет;
- segment, остающийся на границе хотя бы одной surviving room, сохраняется;
- полностью удалённый carrier удаляется только в уже подтверждённом user flow;
- Keep walls переводит contour segment в partition с тем же ID, если ID не конфликтует с существующим partition;
- обратное включение unambiguous partition в contour сохраняет ID.
8.5 Draft close/cancel
Сохранённый draft segment сохраняет ID между сессиями. Незавершённый in-memory rubber band ID не получает. Cancel не расходует persisted ID. Закрытие room переносит IDs; atomization применяет §8.2.
8.6 Undo/Redo и concurrency
Undo/Redo восстанавливает exact snapshots с теми же IDs. Config revision guard проверяется до lineage calculation и ещё раз при commit; stale revision не может частично сохранить catalog. Concurrent edit получает обычный conflict и перезагружает authoritative snapshot.
9. Writers и единый identity barrier
Новый pure-модуль (рабочее имя src/wall-segment-model.ts) владеет:
- v7 projection/migration;
- validation/parity;
- compatibility
walls[]projection; - ID lineage split/merge/rehost;
- persisted/runtime fingerprints.
Каждая structural mutation работает над local candidate, сначала пропускает
его через canonicalizeConfigGeometry #291, затем через
commitWallSegmentModel(candidate) и только после этого достигает
_commitPhysicalGeometry/backend write. Канонизация и identity migration входят
в одну атомарную commit-транзакцию; результат первой является единственным
координатным input второй.
Source guard перечисляет все structural writer entrances и падает, если новый
путь записывает rooms.poly, walls, wall_segments, partitions, drafts или
architectural openings мимо barrier.
Запрещено исправлять задачу добавлением ID assignments в десятки обработчиков. Локальные контроллеры могут передать lineage hint, но один barrier доказывает и пересобирает согласованную модель.
Backend mirror проверяет:
- schema bounds/unique IDs/references;
- geometry/ref/parity invariants;
- v8 catalog ↔ compatibility projection;
- wall/partition opening host and
tfit; - отсутствие direct Store writer bypass.
Backend не придумывает lineage для валидного v8 candidate; он либо принимает его целиком, либо отклоняет.
10. Старые клиенты и совместимость
10.1 v7 data на новом клиенте
View/read работает через runtime projection. Первая structural write предлагает тот же operation вместе с migration; успех атомарный. Export до migration сохраняет фактический v7 document, не штампует v8.
10.2 v8 data на старом клиенте
Старый клиент может сохранить неизвестные поля, но не способен согласованно
обновить refs/catalog. Backend обнаруживает изменение structural projection без
соответствующего v8 catalog и отклоняет весь config write кодом
wall_model_client_outdated. UI нового клиента локализует его как требование
обновить/перезагрузить карточку. View/read старого клиента может использовать
compatibility poly/walls; безопасное редактирование старым клиентом не
обещается.
10.3 Import/export/duplicate
- full replace v8 сохраняет IDs после полной validation;
- space import и duplicate remap every wall/draft/partition ID and all
wall_ids/opening hosts одним lineage table; - collision с target никогда не resolve-ится геометрическим совпадением;
- v7 import в v7 target сохраняется v7; в v8 target материализуется до merge;
- plan-only export/import включает catalog/refs/hosts и не оставляет dangling IDs;
- future unknown fields сохраняются на record/room/space как сейчас;
- future model version отклоняется до migration.
10.4 Configuration lifecycle
wall_segments, rooms[].wall_ids, room_drafts[].segments[].id и wall host
регистрируются в scripts/config-field-registry.mjs как current v8. walls.key
и unhosted room openings получают documented compatibility statuses, но reader
удаляется только отдельной задачей после окна поддержки.
11. UX, accessibility, touch и security
- кнопки, поля, значения толщины и pointer gestures не меняются;
- View/kiosk output и actions не меняются;
- hover/hit-test/selection используют ID internally, но показывают прежние ink;
- editor остаётся desktop-first; touch View acceptance блокирующий;
- screen reader/keyboard contract не расширяется;
- ID никогда не показываются в обычном UI и не попадают в toast/telemetry;
- новые HA service calls, URL/HTML или permission boundaries отсутствуют;
- failure UI не раскрывает geometry/config data, только локализованный класс blocker и действие пользователя.
12. i18n
Новые RU/EN ключи (точное имя можно привести к namespace проекта):
| Key | RU | EN |
|---|---|---|
toast.wall_model_migration_blocked |
Не удалось обновить модель стен: {reason}. План не изменён. Запустите «Оптимизировать планы»; если ошибка повторится, исправьте конфликтующую геометрию стен. |
The wall model could not be updated: {reason}. The plan was not changed. Run “Optimize plans”; if the error repeats, fix the conflicting wall geometry. |
toast.wall_model_client_outdated |
Обновите карточку и перезагрузите страницу перед редактированием плана. |
Update the card and reload the page before editing the plan. |
gs.wall_segments_migrated |
Стабилизировано сегментов стен: {n}. |
Wall segments stabilised: {n}. |
Reason names проходят через bounded enum и отдельные localized labels; raw IDs, exception text и config values не интерполируются.
13. Лимиты и производительность
- catalog ограничен суммой допустимых room polygon sides после atomization;
hard backend cap —
MAX_ROOMS × MAX_POLY_POINTS(текущие 200 000), а общий import/export byte cap остаётся более строгим практическим ограничением; - duplicate IDs, refs и parity проверяются O(V + E) через maps;
- spatial atomization migration допускает O(E log E), но не O(E²) на обычном payload; shared-axis grouping обязано иметь spatial/axis index;
- read/render tick не мигрирует и не хэширует полный config повторно;
- runtime projection и catalog fingerprint кэшируются по config revision;
- large-house structural render p95 и editor commit p95 не регрессируют более чем на 20% same-run baseline;
- migration 10 000 contour atoms укладывается в 500 ms p95 в Node benchmark и не создаёт второго полного deep clone сверх transaction snapshot;
- новые limits не отклоняют ни один валидный v7 fixture до попытки migration.
14. Критерии приёмки
AC1. Детерминированная lossless migration
v7 fixtures с outer/shared/partial-overlap/T/X/diagonal/open span/key-only, exact walls и lattice-noise по обе стороны порога #291 дважды дают byte-equivalent v8 candidate. Шум внутри порога сначала канонизируется и не создаёт micro-segments; координата вне порога не схлопывается молча. Polygon area, winding, wall-body path, clean floor, opening projection, Glow/sun barriers и isometric geometry до/после равны по существующим строгим/golden контрактам.
Доказательство: TS migration unit matrix + golden/static path comparison + backend fixture parity.
AC2. Полный и валидный catalog
Для каждого v8 space все contour atoms имеют unique stable ID; каждая poly side имеет ровно один wall ref; owner count 1/2; orphan/dangling/duplicate/third-owner, zero-length и conflicting thickness отклоняются. Positive legacy thickness и thin contour сохраняют прежний вид.
Доказательство: frontend/backend shared valid+invalid JSON fixture.
AC3. Identity не зависит от geometry после migration
Move/Resize/angle/length/key change whole segment сохраняют ID. Мутант, перевычисляющий ID из endpoints после каждого commit, убивается тестом.
Доказательство: pure lineage tests + Resize browser smoke.
AC4. Split/merge lineage
Split выбирает survivor по midpoint/old-a; merge — по hosts/length/id. Room refs
и hosted opening t обновляются атомарно. Повторение операции после Undo/Redo
возвращает exact те же IDs.
Доказательство: parameterized unit tests для directions/ties/openings + Split/Merge smoke.
AC5. Все structural operations проходят barrier
Draw/close, wall thickness, opening placement/move/delete, Resize, Split, Merge, room Delete/Keep walls, partition conversion, Optimize, Undo/Redo/recovery и import вызывают один identity barrier. Direct assignments не могут попасть в persisted write.
Доказательство: executable source/AST guard + bypass mutant для каждого writer family.
AC6. Room openings hosted
Новый room opening всегда имеет {kind:'wall', id, t}. Legacy unique carrier
материализуется; ambiguous/missing/intersecting split блокирует transaction.
Door/window/gate/passage, flip, contact/lock и Glow tunnel не меняются.
Доказательство: opening unit matrix + smoke_opening_measure/targeted host
smoke + backend schema tests.
AC7. Compatibility projection
v8 → legacy poly/walls/unhosted visual input projection даёт текущим full,
static, hidden-isometric и light consumers прежние inputs. Runtime code не ищет
business identity через wallKey; key генерирует только adapter/compat layer.
Доказательство: source guard + projection snapshots + existing geometry suite.
AC8. Read-only и атомарный failure
Открытие v7 config, View, kiosk, navigation и device/layout-only save не пишут catalog/model version. Structural migration blocker оставляет config/layout/rev byte-equivalent и сохраняет работающий View.
Доказательство: store call-count tests + failure fixture + browser smoke.
AC9. Старый клиент fail-closed
Попытка изменить v8 poly/walls/openings без согласованного catalog отклоняется
bounded error; ни один partial Store write не происходит. Byte-equivalent
round-trip старого клиента принимается.
Доказательство: backend old-client write tests.
AC10. Import/export и ID remap
Full/space/plan-only export-import, duplicate and backup restore сохраняют либо полностью remap-ят ID/ref/host graph. Collision, dangling ref и future model отклоняются до apply; preview counts согласованы с candidate.
Доказательство: HA import/export harness + shared lineage fixture.
AC11. Limits и unknown fields
Boundary cases catalog/draft IDs/refs проходят или fail atomically. Unknown sibling fields на config/space/room/segment/opening survive migration and round-trip. Atomization не truncates data ради лимита.
Доказательство: backend validation/property tests.
AC12. Geometry regression floor
Все тесты #197/#224/#249/#253/#258/#261/#271–#280/#288–#302 остаются зелёными. Точный ID не является поводом удалить существующую union isolation либо ослабить geometry preflight.
Доказательство: npm test, model invariants, named wall smokes/goldens.
AC13. View/touch parity
Desktop View, kiosk и phone/touch fixture визуально равны pre-migration baseline; room/device/opening taps и pinch не получают editor behaviour.
Доказательство: targeted Playwright view/touch smoke + golden comparison.
AC14. Performance
Выполнены budgets §13; migration не запускается на render/live HA tick.
Доказательство: same-run benchmark с machine-readable result.
AC15. Optimize UX
Optimize preview отдельно сообщает количество materialized segment IDs. Cancel — zero writes; Confirm — одна config transaction и одна Undo; второй run no-op.
Доказательство: optimizer unit test + targeted dialog smoke.
AC16. Документация и compatibility registry
Обновлены WALL-THICKNESS.md, ARCHITECTURE.md, CONFIG-COMPATIBILITY.md,
CANVAS.md, user guides RU/EN, TESTING.md, ADR status и config-field registry.
Термины ID/catalog не попадают в пользовательскую инструкцию кроме объяснения
автоматического обновления старого плана.
Доказательство: docs/config audit gates.
AC17. Gates
Перед code review зелёные:
npm run typecheck;npm test;npm run build+npm run bundle:syncи bundle parity;- native Windows pure-backend subset;
- named identity/Resize/Split/Merge/opening/import smokes из AC;
- model invariants, mutation gate, config audit и migration benchmark.
Полный Linux backend, all-smoke, golden и performance_smoke остаются точным-SHA предрелизным gate по процессу.
15. План реализации
- Ввести pure catalog types, deterministic migration и shared fixtures без подключения product writers.
- Добавить backend schema/parity и model version, оставить read-only v7 path.
- Подключить runtime compatibility projection; доказать render equivalence.
- Перевести room wall selection/thickness/opening host на ID.
- Подключить writer barrier и lineage hints по операциям, затем source guard.
- Подключить import/export/duplicate/Undo/recovery и stale-client rejection.
- Добавить Optimize preview/report, i18n, docs и changelog.
- Прогнать fast gates, named smokes, build/sync; передать независимому reviewer.
Каждый промежуточный commit компилируется и не включает half-migrated writer в default path. Feature flag в persisted config не добавляется: v8 включается только фактом успешной materialization.
16. Риски и меры
| Риск | Мера |
|---|---|
| Catalog и poly расходятся | один commit barrier + backend parity + stale-client reject |
| Shared partial edge получает два ID | global atomization по axis/owners + shared fixture AC1/2 |
| Resize пересоздаёт ID | explicit lineage hints + AC3 mutant |
| Split теряет opening | deterministic child rule и physical rehost AC4/6 |
| Старый клиент портит v8 | compatibility projection для read, fail-closed structural write AC9 |
| Migration выбирает неверный key-only wall | текущий resolver без новых эвристик; ambiguity blocks |
| 200k theoretical edges дают freeze | indexed O(E log E), async preview boundary, benchmark/limits |
| Новый writer обходит model | executable source/AST guard AC5 |
| Новая identity используется для удаления старых geometry guards | explicit AC12 prohibition |
#306 конфликтует с моделью cm:0 |
#282 не меняет UI/open-boundary semantics; #306 later rebases on catalog |
17. Откат
До первой v8 записи — чистый revert кода, данные v7 не менялись. После v8
materialization старый stable release может показать compatibility poly/walls,
но безопасное редактирование им не гарантируется.
Штатный rollback продукта:
- отключить structural v8 writes, сохранив v8 read/projection;
- выпустить follow-up reader hotfix;
- не удалять catalog и IDs автоматически;
- восстановить pre-migration config только из обычного backup/Undo, инициированного пользователем.
Автоматический downgrade v8 → v7 запрещён: он теряет hosted wall identity и может неоднозначно схлопнуть атомы. Revert implementation-коммита без v8 reader после публикации также запрещён.
18. Release-артефакты
- implementation commit:
Issue: #282,User-Visible: yes; - оба changelog со ссылкой на #282 и формулировкой про устойчивое редактирование, без обещания нового UI;
- generated bundle snapshots синхронизированы;
- новые golden baselines принимаются только при реальной visual delta и только из полного Linux artifact по процессу;
- beta/RC обязательна до stable;
- issue закрывается только после успешного beta gate на exact SHA.
19. Принятые технические предположения (оспоримы ревьюером)
- Stage 1 поставляется в #282; Stages 2–4 получают отдельные issues.
wall_segments + room.wall_ids— canonical identity/thickness, аpolyи generatedwalls[]временно остаются compatibility projection до Stage 3.- Independent partitions не переносятся в catalog: их ID уже stable. Draft segment IDs добавляются, потому что draft переживает сессию и конвертируется.
cm:0нужен для полного catalog, но не открывает пользователю функционал #306 и не заменяетopen_spans/open_toэтой задачей.- Migration выполняется lazy on structural write/Optimize, не на read и не на
device/layout-only save; внутри одной транзакции она всегда получает
candidate после
canonicalizeConfigGeometry#291 с его порогом1e-4шага. - Старый client может читать projection, но structural writes в v8 fail-closed.
- Deterministic hash используется только для первоначальной migration; после неё geometry никогда не определяет ID.
- Existing wall renderer и junction algorithms получают adapter projection и не переписываются до ADR Stage 4.