mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 04:38:55 +00:00
423 lines
32 KiB
Markdown
423 lines
32 KiB
Markdown
# ТЗ #440 — Полиш аудита v1.71.0-beta.2
|
||
|
||
- Issue: https://github.com/Matysh/houseplan-card/issues/440
|
||
- Приоритет: P2, `bug` / `polish` / `tests`
|
||
- Маршрут: full; задача затрагивает несколько независимых TypeScript- и
|
||
Python-поверхностей, touch-контракт View и защитные гейты, поэтому нарушает
|
||
ограничения лёгкого трека по сложности и одной поверхности
|
||
- Связанные контракты: #372 (compact static card), #426 (room tooltip toggle),
|
||
#429/#430 (инфраструктурные гейты), #431 (канонизация image decor), #432
|
||
(asset integrity/resolve), #434 (предыдущий audit polish)
|
||
|
||
## Сценарий
|
||
|
||
Home admin открывает план мышью, пером или на touch-устройстве, меняет язык,
|
||
подтверждает опасные действия и загружает либо показывает пользовательские
|
||
изображения подложки. В редком повреждённом состоянии asset-каталога вместо
|
||
обычного файла может оказаться специальный filesystem object либо запись может
|
||
исчезнуть во время inventory. Разработчик при этом ожидает, что защитные тесты
|
||
исполняются и действительно замечают снятую гарантию как локально, так и в CI.
|
||
|
||
## Что человек увидит до и после
|
||
|
||
**До:** специальный файл с допустимым asset-именем может навсегда занять поток
|
||
Home Assistant при HTTP-проверке; исчезнувшая во время inventory запись даёт
|
||
500, а повреждённый orphan ошибочно выглядит как нехватка места с HTTP 507.
|
||
При выключенной подсказке комнаты смена мыши на pen/touch внутри уже наведённой
|
||
комнаты может оставить transient hover. Остальные пункты сегодня внешне
|
||
работают, но держатся на неявном побочном эффекте getter либо слабом/CI-only
|
||
свидетеле.
|
||
|
||
**После:** необычные и исчезнувшие asset entries быстро и безопасно
|
||
отбрасываются, код HTTP соответствует причине ошибки, а реальный pen/touch
|
||
всегда снимает mouse-only hover независимо от настройки room tooltip. Языковые
|
||
переходы и static-card capability внешне остаются прежними, но охраняются
|
||
явными командами и исполнимыми поведенческими тестами.
|
||
|
||
## Проблема и подтверждённые причины
|
||
|
||
1. `AssetIntegrityVerifier.verify()` строит сигнатуру через обычный `stat()` и
|
||
передаёт путь `_stream_sha256()`. FIFO/устройство проходят эту границу, а
|
||
blocking `open("rb")` может не вернуться. Followers того же single-flight
|
||
безусловно ждут `event.wait()`.
|
||
2. Room `pointermove` при `show_room_tooltip: false` возвращается раньше
|
||
`_showTip()`, то есть раньше пути, который вызывает `_notePointer()`.
|
||
`pointerenter` не покрывает смену pointer type внутри уже занятой комнаты.
|
||
3. Getter `_dangerConfirmLocaleGate` вызывает mutating
|
||
`languageRenderGate()`: меняет `inert`, `aria-busy`, `lang`, WeakSet-состояние,
|
||
запускает lazy load и `requestUpdate()`. Его побочный эффект нужен текущему
|
||
переходу warm→ready до render, но имя и форма скрывают эту обязанность.
|
||
4. `physical_asset_blobs()` не изолирует `OSError` между `iterdir()` и
|
||
`stat(follow_symlinks=False)`, а `physical_asset_usage()` повторно делает
|
||
`stat()` без защиты. `_store()` затем отображает любой `DecorAssetError` в
|
||
HTTP 507, хотя 507 означает только `capacity_exceeded`; `invalid_image`
|
||
должен оставаться клиентским отказом 400.
|
||
5. Пять assertions в `test/space-card-audit-lows.test.mjs` проверяют текст
|
||
исходника. Большая часть соответствующего контракта уже покрыта
|
||
`config-store` unit, `decor-assets` unit и
|
||
`smoke_space_card_decor_capability`, но source-regex остаётся ложной
|
||
гарантией и лишней чувствительностью к рефакторингу.
|
||
6. `pytest.importorskip("homeassistant")` на уровне
|
||
`test_coordinate_canonicalization.py` скрывает чистые проверки
|
||
`DECOR_BOX_KINDS` и numeric canonicalization в среде без HA. Поэтому мутант
|
||
`image-box-python-canonicalization-omitted` может завершиться зелёным skip.
|
||
7. #429 и #430 намеренно были чистыми инфраструктурными задачами без файлов
|
||
класса A. `PROCESS.md` §1 исключает для них ТЗ и код-ревью; подробные
|
||
closing-комментарии и коммиты уже являются корректным следом. Это не дефект
|
||
реализации и не требует синтетического review-документа задним числом.
|
||
|
||
## Скоуп
|
||
|
||
В скоупе:
|
||
|
||
- fail-fast regular-file boundary перед digest/cache/single-flight публикацией;
|
||
- конечное ожидание followers как дополнительная защита liveness;
|
||
- сохранение fail-dark HTTP 404 для отсутствующего, изменившегося,
|
||
специального либо неподтвердившегося asset blob;
|
||
- обновление pointer modality на каждом реальном room pointermove в View,
|
||
включая выключенный tooltip;
|
||
- явная command/query граница для danger-confirm locale state при полном
|
||
сохранении ready/cold/warm поведения #434;
|
||
- устойчивый к исчезновению файлов physical asset inventory;
|
||
- точное отображение store errors: capacity → 507, invalid image/прочий
|
||
корректный клиентский отказ → 400, `OSError` → прежний 500 `io_error`;
|
||
- замена source-regex AC5 #434 поведенческими unit/smoke-свидетелями;
|
||
- HA-независимый модуль чистых Python canonicalization-тестов и перевод
|
||
соответствующих mutation guards на него;
|
||
- тесты, mutation witnesses, техническая документация и changelog.
|
||
|
||
## Не-скоуп
|
||
|
||
- новый asset format, protocol capability, digest, quota либо filesystem
|
||
watcher;
|
||
- поддержка FIFO/devices/sockets как изображений или попытка читать их с
|
||
timeout;
|
||
- автоматическое удаление необычных/orphan entries;
|
||
- изменение прав доступа, signed URL, catalog/list/resolve semantics или
|
||
content-addressed имени;
|
||
- новый room tooltip, отдельный pen UX либо изменение touch-first контракта;
|
||
- новый вид/текст danger confirmation или изменение fallback-языка;
|
||
- рефакторинг всего `LanguageRuntime` либо всех Lit render gates;
|
||
- изменение канонизации координат, schema/storage результата #431;
|
||
- создание review-документов задним числом для #429/#430;
|
||
- golden, performance baseline или полный Windows HA harness.
|
||
|
||
## Контракт поведения
|
||
|
||
### 1. Целостность asset и liveness
|
||
|
||
Verifier принимает к хешированию только существующий обычный файл. Directory,
|
||
FIFO, socket, block/character device, broken link и исчезнувший путь дают
|
||
`false` до вызова digest hasher и не создают доверенную cache entry.
|
||
|
||
Проверка типа выполняется как часть обеих сигнатур — до и после чтения. Digest
|
||
публикуется только если один и тот же обычный file version оставался стабильным
|
||
по size/mtime/ctime. Ошибка либо смена типа очищает stale cache для canonical
|
||
path и будит followers с `false`.
|
||
|
||
Follower не ждёт бесконечно даже при неожиданно зависшем/injected owner:
|
||
ожидание имеет конечную внутреннюю границу и при её истечении возвращает
|
||
`false`, не удаляя чужой in-flight record и не публикуя digest. Значение границы
|
||
техническое; оно должно существенно превышать чтение разрешённого asset
|
||
максимального размера и не является пользовательским network timeout.
|
||
|
||
HTTP content endpoint сохраняет прежнее поведение: verifier `false` означает
|
||
404 без передачи файловых деталей клиенту. Обычные корректные assets и LRU
|
||
single-flight/cache работают как раньше.
|
||
|
||
### 2. Pointer modality при выключенной подсказке комнаты
|
||
|
||
Каждый настоящий room `pointermove` в View сначала сообщает pointer type общей
|
||
instance-local modality, а уже потом решает, показывать ли room tooltip.
|
||
|
||
- `mouse` сохраняет существующие room fill/hover и tooltip rules;
|
||
- `touch` или `pen` снимает `_tip` и `_hoverRoom`, которые принадлежат
|
||
mouse-hover, даже при `show_room_tooltip: false`;
|
||
- выключенная подсказка не считает area/temp/humidity/LQI и не создаёт tooltip;
|
||
- editor modes и device/opening tips не получают нового UX.
|
||
|
||
Повторный вызов modality helper на пути включённого tooltip не должен менять
|
||
наблюдаемое поведение или порождать лишнее состояние.
|
||
|
||
### 3. Явная locale command для danger confirmation
|
||
|
||
Mutating синхронизация языка не маскируется getter-ом. Call site, которому
|
||
нужно актуализировать `inert`/attributes/lazy load и получить gate, вызывает
|
||
явно названную command-функцию/метод; чистое чтение уже вычисленного значения,
|
||
если потребуется, не имеет побочных эффектов.
|
||
|
||
Сохраняются все ветви #434:
|
||
|
||
- ready/fallback до следующего render немедленно разрешает обычный request и
|
||
снимает `inert`/`aria-busy`;
|
||
- warm немедленно возвращает `false`, не оставляет controller request;
|
||
- ready→warm отменяет уже открытое подтверждение до потери decision source;
|
||
- cold first frame остаётся language-neutral loading surface;
|
||
- stable body в warm не заменяется, согласие между языковыми состояниями не
|
||
переносится.
|
||
|
||
Ни render, ни `willUpdate`, ни `_confirmDanger()` не читают property, похожее на
|
||
обычный getter, если это чтение меняет DOM/host/runtime state.
|
||
|
||
### 4. Inventory race и HTTP status
|
||
|
||
Physical inventory обходит только exact allowed-extension/hash candidates и
|
||
учитывает только успешно наблюдённые обычные файлы. Если root либо отдельная
|
||
entry исчезла, стала недоступна или сменила тип между обходом и stat, этот
|
||
кандидат пропускается, остальные продолжают считаться. Одна race не превращает
|
||
весь upload в `io_error`.
|
||
|
||
Семантика quota остаётся консервативной в пределах одного наблюдения: count и
|
||
bytes относятся к тем же успешно stat-нутым regular candidates; sidecar по-
|
||
прежнему не является authority physical usage.
|
||
|
||
Ответ upload после `_store()`:
|
||
|
||
- `DecorAssetError("capacity_exceeded")` → HTTP 507 с тем же error code;
|
||
- `DecorAssetError("invalid_image")` и иные не-capacity validation/integrity
|
||
коды → HTTP 400 с исходным code/message;
|
||
- неожиданный `OSError` → HTTP 500 `io_error`;
|
||
- успех/reused и pre-store `too_large`/format behavior не меняются.
|
||
|
||
### 5. Поведенческие witnesses static-card capability
|
||
|
||
Source-regex блок #434 удаляется как гарантия. Его контракт доказывается через
|
||
публично наблюдаемые данные и вызовы:
|
||
|
||
- fresh `config/get` принимает только exact API v1 и отзывает capability при
|
||
missing/другом значении;
|
||
- localStorage snapshot никогда не выдаёт runtime capability;
|
||
- static card без v1 не вызывает resolve и очищает runtime asset map;
|
||
- capability-only upgrade/downgrade принимается при том же config body;
|
||
- resolve cache разделён authoritative revision/epoch и повторяет прежний
|
||
missing на новом epoch.
|
||
|
||
Если существующие unit/smoke уже доказывают строку полностью, они расширяются
|
||
только недостающим отрицательным кейсом; дублирующий regex не сохраняется.
|
||
Mutation/negative run обязан краснеть на наблюдаемом счётчике вызовов, snapshot
|
||
либо map, а не на изменении форматирования исходника.
|
||
|
||
### 6. Чистая Python-канонизация исполняется без HA
|
||
|
||
Тесты, которым нужны только
|
||
`custom_components.houseplan.coordinate_canonicalization` и JSON fixture,
|
||
живут в отдельном модуле до любой HA-зависимой загрузки. Как минимум туда
|
||
переходят:
|
||
|
||
- общий scalar/lattice fixture contract;
|
||
- exact `DECOR_BOX_KINDS` и image box field preservation;
|
||
- scalar symmetry/off-grid behavior;
|
||
- 4801 lattice nodes и nine-decimal forms.
|
||
|
||
Schema, store, virtual lights и wall segment tests остаются в HA-зависимом
|
||
модуле под честным `importorskip`. Mutation guards для чистых Python contracts
|
||
указывают новый модуль и в среде без Home Assistant обязаны давать реальный
|
||
pass/fail, а не module skip. Дублировать одни assertions в обоих файлах нельзя.
|
||
|
||
### 7. Пункт #429/#430
|
||
|
||
Никаких продуктовых либо repository-правок ради создания отсутствовавшего
|
||
review artifact не делается. ТЗ и итоговый комментарий #440 фиксируют проверку:
|
||
обе задачи были infrastructure-only и прошли допустимый §1 маршрут. Если
|
||
выяснится, что их коммиты всё же содержали класс A, это новая process issue, а
|
||
не скрытое расширение #440.
|
||
|
||
## Модель данных, совместимость и i18n
|
||
|
||
- Persisted config/layout, schema version, asset sidecar и API capability не
|
||
меняются; миграции нет.
|
||
- HTTP status исправляется без изменения JSON `error`/`message` формы.
|
||
- Новых пользовательских строк нет; словари en/ru/de/fr не меняются.
|
||
- Старые frontend/backend combinations сохраняют действующие fail-closed
|
||
capability и content URL contracts.
|
||
|
||
## Touch и доступность
|
||
|
||
View остаётся touch-first. Исправление pointer modality восстанавливает
|
||
обязательный контракт `docs/TOUCH-SUPPORT.md`: touch/pen не наследует визуальное
|
||
состояние mouse hover. Никаких новых жестов, click targets или editor touch
|
||
обещаний нет.
|
||
|
||
Danger-confirm сохраняет `alertdialog`, focus, inert и aria-busy semantics;
|
||
меняется только явность точки, где эти эффекты синхронизируются.
|
||
|
||
## Производительность и безопасность
|
||
|
||
- Regular-file check добавляет bounded stat/type verification вокруг уже
|
||
существующего SHA-256 и не расширяет число хеширований/cache entries.
|
||
- Конечное follower wait не удерживает executor бесконечно; обычный cache hit и
|
||
single-flight путь остаются O(1).
|
||
- Inventory остаётся O(entries) под существующим `upload_lock`, quota физически
|
||
ограничивает каталог; исчезнувшая entry не останавливает весь scan.
|
||
- Необычный файл никогда не читается и не становится trusted asset. Ошибки не
|
||
раскрывают filesystem path/type.
|
||
- Pointer и locale изменения не добавляют работу в стабильный кадр сверх уже
|
||
существующего modality/gate вызова; это подтверждается review кода, нового
|
||
performance baseline не требуется.
|
||
|
||
## Затронутые модули
|
||
|
||
Ожидаемый набор; точное выделение helpers может быть скорректировано ревьюером:
|
||
|
||
- `custom_components/houseplan/asset_integrity.py`;
|
||
- `custom_components/houseplan/decor_assets.py`;
|
||
- `custom_components/houseplan/http_api.py`;
|
||
- `src/houseplan-card.ts`, при необходимости
|
||
`src/i18n/language-runtime.ts`;
|
||
- `tests_backend/test_decor_assets.py`, новый чистый Python test module и
|
||
соответствующие HA endpoint tests;
|
||
- `test/space-card-audit-lows.test.mjs`, `test/config-store.test.mjs`,
|
||
`test/decor-assets.test.mjs`;
|
||
- `demo/smoke_room_tooltip_toggle.mjs` либо отдельный pointer-modality smoke,
|
||
`demo/smoke_danger_confirm_branches.mjs`,
|
||
`demo/smoke_space_card_decor_capability.mjs`;
|
||
- `scripts/mutation-gate.mjs`;
|
||
- `docs/ARCHITECTURE.md`, `docs/TESTING.md`, оба changelog и docs screenshot
|
||
fingerprint, если их действующие разделы требуют обновления.
|
||
|
||
## Критерии приёмки
|
||
|
||
- **AC1 (backend/unit, safety/liveness).** Обычный asset с верным digest
|
||
проверяется и кэшируется; directory/FIFO/другой non-regular и исчезнувший
|
||
path возвращают `false` без вызова hasher. Followers получают тот же результат
|
||
и конечный wait не зависает. Смена bytes/type между сигнатурами не публикует
|
||
cache. **Доказательство:** pure verifier matrix, POSIX FIFO case под
|
||
platform-skip и отдельный injected timeout/flight case.
|
||
- **AC2 (browser smoke, touch).** При выключенном room tooltip переход реального
|
||
pointermove mouse→pen и mouse→touch внутри комнаты снимает `_tip` и
|
||
`_hoverRoom`, не создаёт новый tooltip и не вычисляет room metrics; mouse и
|
||
включённый tooltip остаются прежними. **Доказательство:** targeted browser
|
||
smoke с pointer events и metric spies.
|
||
- **AC3 (browser smoke + review, lifecycle).** Danger locale sync вызывается
|
||
явной command-границей, getter с mutating `languageRenderGate()` отсутствует;
|
||
ready→warm, warm refusal, warm→ready до render, cancellation, stable body и
|
||
inert/aria-busy проходят прежнюю полную матрицу. **Доказательство:**
|
||
`smoke_danger_confirm_branches` + negative mutation, diff review command/query
|
||
call sites.
|
||
- **AC4 (backend/unit + HA endpoint, correctness).** Исчезновение/type-change
|
||
одного inventory candidate не останавливает scan и не искажает остальные
|
||
count/bytes. Capacity store error отвечает 507, invalid orphan image — 400,
|
||
`OSError` — 500 `io_error`; body code сохраняется. **Доказательство:**
|
||
filesystem race matrix + upload response matrix.
|
||
- **AC5 (unit/smoke, compatibility).** Все пять static-card capability/cache
|
||
обещаний #434 доказаны значениями snapshot/map и WS counters; source-regex
|
||
для этого блока удалён. Снятие exact capability revocation/adoption краснит
|
||
поведенческий тест. **Доказательство:** config/decor units,
|
||
`smoke_space_card_decor_capability` и targeted mutation.
|
||
- **AC6 (backend/unit + mutation, harness).** Чистые coordinate/image
|
||
canonicalization tests выполняются без установленного `homeassistant`; HA-
|
||
dependent tests по-прежнему честно skip-аются локально и исполняются в Linux
|
||
CI. `image-box-python-canonicalization-omitted` и чистый lattice mutant
|
||
краснеют на новом модуле. **Доказательство:** pytest в среде без HA,
|
||
backend-test-guard и оба mutation ids.
|
||
- **AC7 (review/docs, scope).** #429/#430 не получают ретроактивных artifacts;
|
||
нет schema/API/i18n/устойчивого визуального изменения. Обычная раздача assets,
|
||
upload success/reuse, tooltip-on и language fallback остаются совместимы.
|
||
**Доказательство:** diff review, существующие contract tests и документация.
|
||
- **AC8 (gates).** Typecheck, unit, build/bundle sync, выбранные browser smokes,
|
||
чистые backend tests, Linux HA CI, docs checks и новые mutation witnesses
|
||
зелёные на exact SHA. Golden и full performance остаются предрелизными.
|
||
|
||
## Таблица защитных доказательств
|
||
|
||
| AC | Чем доказан | Чем обязан покраснеть |
|
||
|---|---|---|
|
||
| AC1 | verifier regular/non-regular/single-flight matrix | снятие regular-file boundary вызывает hasher на sentinel/FIFO; снятие wait boundary оставляет controlled follower unresolved |
|
||
| AC2 | room pointer-modality browser smoke | перенос `_notePointer` обратно после tooltip guard оставляет `_hoverRoom`/`_tip` после pen/touch |
|
||
| AC3 | danger locale branch smoke | возврат mutating getter/cached прошлого render даёт ложный warm отказ либо оставляет inert/controller в переходе |
|
||
| AC4 | inventory race + upload status backend tests | проброс `OSError` снова делает 500; общий status 507 заставляет invalid-image assertion получить неверный статус |
|
||
| AC5 | config-store/decor units + static-card WS smoke | снятие exact capability adoption/revocation вызывает resolve на старом backend либо оставляет asset map после downgrade |
|
||
| AC6 | HA-free pytest module + mutation guards | удаление `image` из Python `DECOR_BOX_KINDS` либо изменение lattice rounding даёт failed assertion, не skipped module |
|
||
|
||
Для AC1–AC6, заявляющих защиту, code-review обязан привести точный negative
|
||
run по правилу `PROCESS.md` §2.7. Persistent mutation нужен для дорогого
|
||
browser/backend witness; для чистого unit допустим документированный red-run со
|
||
снятой защитой.
|
||
|
||
## План автотестов
|
||
|
||
1. Расширить verifier tests обычным файлом, missing/directory/non-regular,
|
||
stable/changed signature, owner/follower и истёкшим follower wait. Реальный
|
||
FIFO создавать только на POSIX; cross-platform seam доказывает отсутствие
|
||
вызова hasher независимо от платформы.
|
||
2. В browser harness заранее создать mouse-owned room tip/fill, выключить
|
||
tooltip и отправить pen/touch `pointermove` без нового `pointerenter`.
|
||
Отдельными spies доказать отсутствие room metric work.
|
||
3. Сохранить полную locale branch matrix, но вызывать публично наблюдаемое
|
||
действие, а не читать mutating getter ради продвижения состояния.
|
||
4. Подменить inventory entry/stat так, чтобы один кандидат исчез, а следующий
|
||
regular blob сохранил count/bytes. Через upload view проверить три status
|
||
класса без реальной нехватки диска.
|
||
5. Сопоставить каждую из пяти удаляемых regex-строк с существующим assertion;
|
||
добавить только отсутствующий capability-only case и mutation, затем удалить
|
||
source-form block.
|
||
6. Перенести чистые Python tests без дублирования, запустить новый файл в
|
||
окружении без HA и перенаправить mutation guards `decor_box_catalog...` и
|
||
`all_4801_lattice_nodes...` на него.
|
||
7. На implementation SHA прогнать штатные гейты и каждый новый/перенесённый
|
||
negative witness; failure должен быть целевым assertion, не import/skip,
|
||
timeout всего процесса или отсутствующим браузером.
|
||
|
||
## Риски
|
||
|
||
- **Regular check не закрывает race между path stat и open.** Смягчение:
|
||
post-read signature/type guard и fail-dark результат; реализация вправе
|
||
использовать безопасный file-descriptor seam, если это не ломает injected
|
||
hasher tests и Windows.
|
||
- **Слишком короткий follower timeout даст ложные 404 на медленном диске.**
|
||
Смягчение: техническая граница существенно выше чтения максимальных 2 MiB,
|
||
обычный owner продолжает и может заполнить cache для следующего запроса.
|
||
- **Пропуск исчезнувшей entry временно недосчитает quota.** Это внешний race в
|
||
config-каталоге; House Plan writers уже сериализованы `upload_lock`, а
|
||
следующий запрос пересканирует store. Нельзя превращать один race в отказ
|
||
всех корректных upload.
|
||
- **Locale refactor изменит тонкое pre-render окно.** Смягчение: существующий
|
||
smoke сохраняется как основной acceptance witness и получает negative proof.
|
||
- **Удаление regex оставит дыру.** Смягчение: таблица соответствия пяти строк
|
||
наблюдаемым unit/smoke assertions до удаления и mutation по поведению.
|
||
- **Перенос Python tests изменит CI collection.** Смягчение: отсутствие
|
||
дубликатов, отдельные прогоны с/без HA и существующий harness audit.
|
||
|
||
## Откат
|
||
|
||
Откат — единый revert implementation commit вместе с тестами и release-
|
||
артефактами. Persisted data и migration отсутствуют. Если потребуется частичный
|
||
аварийный откат, backend regular-file/status fixes и frontend pointer/locale fix
|
||
могут быть возвращены независимыми атомарными коммитами только вместе со своими
|
||
свидетелями; возврат зависающего FIFO либо ложного 507 не считается безопасной
|
||
деградацией.
|
||
|
||
## Release-артефакты
|
||
|
||
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`: fail-fast asset verification,
|
||
честные upload errors и восстановление pointer modality;
|
||
- `docs/ARCHITECTURE.md`: обновить только если описание asset integrity не
|
||
фиксирует regular-file/fail-dark границу;
|
||
- `docs/TESTING.md`: HA-free canonicalization witnesses и отказ от source-regex
|
||
как семантического доказательства;
|
||
- docs screenshot workflow: подтвердить нулевую pixel-дельту и принять только
|
||
source fingerprint после `src/**`;
|
||
- user guide, i18n, golden baselines, performance/security profiles и schema
|
||
docs: без изменений;
|
||
- implementation commit — `User-Visible: yes`, обе записи changelog в том же
|
||
коммите; чистый follow-up fingerprint/review doc — `User-Visible: no`.
|
||
|
||
## Принятые технические предположения
|
||
|
||
Эти решения не меняют продуктовый замысел и могут быть свободно скорректированы
|
||
ревьюером до S5:
|
||
|
||
- задача остаётся единым audit batch: пункты малы, имеют общую цель hardening и
|
||
требуют одного согласованного набора negative witnesses;
|
||
- follower timeout — внутренняя liveness-защита, а не обещание времени HTTP-
|
||
ответа; точное число выбирается по текущему 2 MiB лимиту и тестируется через
|
||
injected event, без реального ожидания;
|
||
- non-capacity `DecorAssetError` после `_store()` отображается в 400, потому что
|
||
его code описывает непринимаемый asset, а не состояние диска;
|
||
- mutating language sync может оставаться вызываемой из Lit lifecycle и action
|
||
path, но обязана называться командой и не притворяться property read;
|
||
- существующие поведенческие tests переиспользуются вместо создания второго
|
||
параллельного harness, если они полностью доказывают AC;
|
||
- ссылки на номера строк из аудита ориентировочны: реализация привязывается к
|
||
символам и поведению актуального `origin/dev`.
|