Files
transcriber/openspec/changes/archive/2026-08-15-spa-skeleton/review/code-review.md
T
av 663021f712 приложение собрано каркасом и вшито в бинарник
- заведён каталог web/ — Vue 3, роутер пятой версии, сборка Vite; собранное
  вшивается через go:embed и раздаётся корневым маршрутом: разметка на
  неизвестном пути вне корней сервиса, отказ контракта внутри корня
- перечень корней сервиса стал единой точкой и порождает регистрацию маршрутов,
  а не описывает её; журнал раздачи пишет исход и длину пути, но не сам путь
- шаг front зовёт Node контейнером docker — Biome, юнит-тесты Vue и сборка
  входят в гейт, а в Dockerfile появилась ступень приложения
2026-08-15 18:51:05 +03:00

396 lines
34 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.
# Ревью кода: `spa-skeleton`
## Сводка
- **Режим прогона:** по графу, метка **`large`**.
- **Обоснование метки, размер и сложность:** до триажа **не доехали** — задание
передало только саму метку и режим. Строка деградации, а не оценка: сверить
метку с предметом мне нечем. Свой замер (не замер разметчика): изменение
рабочего дерева — 12 изменённых файлов, +270/−35 строк, плюс ~850 строк нового
кода в `internal/controller/http/webapp*.go` и `web/`.
- **Гейт:** зелёный целиком (14 шагов), унаследованных отказов нет.
- **Находок на входе:** 26 (пункты 1–19 поимённо, 4 подпункта в 20-м, 21–23).
- **Осталось в первых двух секциях:** 7 (3 + 4). Понижено — 5, в promote — 4,
срезано потолком — 6; срезанное поимённо названо в границах покрытия.
### План с исходом по каждой теме
| Тема | Дом | Глубина | Кто закрывает | Исход |
|---|---|---|---|---|
| requirements | дельта-спеки `specs/{webapp,access}` | разбор | specs | закрыта, 7 находок |
| autotests | `CLAUDE.md`, «Гейт» | — | autotests | закрыта, 2 находки |
| conventions | `docs/conventions/` | разбор | code | закрыта, 4 находки |
| architecture | `docs/architecture.md` + `passport.md` | доказательство | architecture | закрыта, 3 находки |
| security | `docs/security.md` | доказательство | adversary | закрыта, 7 находок |
| operations | `architecture.md` «Эксплуатация» + `database.md` | доказательство | ops | закрыта, 3 находки |
| темы проекта | своих нет | — | basics | **дома у темы нет**; проход не запускался, темы разобраны именными проходами |
**Тем без отчёта нет** — все шесть заявленных домов закрыты своими проходами.
**Сигнал о заниженной метке: не поступал ни от одного прохода.** `review-code`
его не подавал; `review-basics` не запускался и подать не мог. Это «возражений
нет» от одного из двух возможных источников, а не от обоих.
**Ни один проход не объявил свой потолок и не приложил блок «Coverage of this
pass».** Это находка о прогоне, а не оформление: «находок больше нет»
неотличимо от «больше не поместилось», и ровно об этом стоит запись
`docs/review.md` от 2026-08-13 («Ни один проход не сообщает свой потолок»).
Повторяется прогон в прогон, механизации нет.
---
## Блокирует мердж
### Панель владельца со всеми записями и файлами открывается анониму из интернета по адресу `/%5f/`
- Файл: `internal/controller/http/webapp.go:17-28,100-124`; `docs/security.md:29-38`
- Severity: major
- Confidence: high
- Оракул (снят триажем на этом прогоне): проба стандартного мультиплексора,
`/tmp/claude-1000/.../scratchpad/muxprobe`, `go run .`:
```
/_/ -> 200 HIT /_/ path="/_/" raw=""
/%5f/ -> 200 HIT /_/ path="/_/" raw="/%5f/"
/%6detrics -> 200 HIT /metrics path="/metrics" raw="/%6detrics"
/%68ealth -> 200 HIT /health path="/health" raw="/%68ealth"
/%61pi/collections -> 200 HIT /api/ path="/api/collections"
/%61pp/me -> 200 HIT /app/ path="/app/me"
```
Маршрутизатор сравнивает **раскодированный** путь, исходная форма остаётся в
`URL.RawPath`. Живой прогон враждебного прохода даёт то же: `/_/` и `/%5f/`
отвечают байт в байт, весь клиент панели (633 КБ JS, 275 КБ CSS) грузится
анониму.
- Последствие: правило Authelia на обратном прокси написано на литерал `/_/`
(`docs/security.md`, «Третий сдвиг»), поэтому закодированная форма проходит
мимо него и попадает в панель суперпользователя — все записи, все файлы, все
пользователи. Вход в само приложение при этом не обходится: `/%61pp/me`
отвечает `401`, разграничение живёт в обработчике, а не в маршруте.
- **Дефект унаследованный:** так вело себя адресное пространство и до задачи,
изменение его не вносило. В блокирующие он попал по ущербу, а не по авторству.
- **Половина пути не проверена:** правило прокси лежит в `pet-project-server`,
в этом репозитории его нет. Проверена только сторона сервиса — отсюда `major`,
а не `critical`.
- Предложение: слой, приводящий путь к канонической форме (отказ `400` при
`RawPath != ""`, если раскодированный путь попадает в перечень корней), рядом с
новым перечнем `ServiceMounts`; плюс правка `docs/security.md`.
**Осторожно:** глухая проверка `RawPath != ""` сломает скачивание файлов
записей с пробелами и не-латиницей в имени — они приходят закодированными
законно.
- Найдено проходом: adversary; оракул перепроверен триажем.
- **Действие: развилка.**
> Панель `/_/` доступна анониму по `/%5f/` мимо Authelia — дефект старше этой
> задачи, но именно она заводит перечень корней, куда лечение просится.
> Варианты:
> 1. чинить здесь: слой канонизации пути на перечне `ServiceMounts`, отказ
> `400` только на закодированной форме корня, тест на `/%5f/`, `/%6detrics`
> и на законный закодированный путь файла (цена — день, растёт scope задачи);
> 2. чинить отдельной задачей, а в этой задаче только записать дыру в
> `docs/security.md` и завести задачу в беклоге (цена — час, дыра живёт до
> выкладки);
> 3. закрыть на стороне выкладки — правило прокси на регулярном выражении по
> сырому пути (цена — вне репозитория, проверить прогоном нечем).
### Образ собирается из дерева разработчика, а не из объявленных входов
- Файл: `Dockerfile:12-16,37`; отсутствующий `.dockerignore`; `.gitignore:57-61`
- Severity: major
- Confidence: high
- Оракул: `ls -la .dockerignore` → `No such file or directory`; в `Dockerfile`
ступень `front-build` делает `COPY web/package.json web/package-lock.json ./`,
`RUN npm ci`, а следом `COPY web/ ./` — то есть локальный `web/node_modules`
ложится **поверх** результата `npm ci`; ступень `build-env` делает `COPY . .`.
`Taskfile.yml:163` монтирует `$PWD/web` в контейнер, поэтому каталог
зависимостей в дереве разработчика заводится при каждом `task front`, а
`.gitignore` прячет его от git, но не от docker.
- Последствие: артефакт, который едет на сервер, собирается из того, что лежит у
собравшего, а не из `package-lock.json`, — и **молча**: сборка при этом
зелёная. В промежуточный слой `build-env` при `COPY . .` уезжают также `.git`
(12 МБ), локальный `config.toml` и каталог `data/` того, кто запускал сервис
локально; в финальный образ они не попадают, но живут в слоях сборки.
- **Поправка к исходной находке:** заявленное падение ступени из-за чужих
платформенных пакетов маловероятно — зависимости в дерево кладёт тот же
`node:24-alpine`, что и ступень образа. Замер «193 МБ» сегодня не
воспроизводится: `web/node_modules` пуст. Существо находки от этого не
меняется — вход сборки не ограничен ничем.
- Предложение: `.dockerignore` в корне с `web/node_modules/`, `web/embed/dist/`,
`data/`, `.git/`, `config.toml`, `tmp/`.
- Найдено проходом: code/техника; оракул перепроверен триажем.
- **Действие: инлайн.**
### Путь и адрес анонима уезжают в журнал хранилища, хотя записанное свойство прогона утверждает обратное
- Файл: `internal/controller/http/webapp.go:231-284`; `docs/review.md:75`
- Severity: major
- Confidence: high
- Оракул (перепроверен триажем по исходникам библиотеки,
`pocketbase@v0.39.10`): `apis/base.go:117-120` — `apis.Static` первой строкой
ставит `requestEventKeySkipSuccessActivityLog`; `apis/middlewares.go:349-445` —
`activityLogger()` навешен на **все** маршруты и при `err == nil` без этого
признака пишет `url` (`RequestURI`, до 3000 знаков), `referer`, `userAgent`, а
при `Logs.LogIP` — `userIP` и `remoteIP`; `core/settings_model.go:158-159` —
умолчания `MaxDays: 5`, `LogIP: true`; в проекте настройки журнала не
трогаются вовсе (`grep 'Settings().Logs' → пусто`). Наша раздача написана мимо
`apis.Static` и признак не ставит.
- Последствие: каждый успешный ответ разметкой и ресурсом кладёт в таблицу
`_logs` выбранный анонимом путь и его адрес, и лежит это пять суток. Строка
`docs/review.md`, добавленная этой же задачей, — «путь, выбранный анонимом, не
уходит ни строкой журнала, ни меткой метрики» — верна для журнала контейнера и
неверна для журнала хранилища. Ложное записанное свойство хуже отсутствующего:
следующий прогон возьмёт его оракулом.
Частично унаследовано: отказы (`err != nil`) писались в `_logs` и раньше;
новое — успешные ответы, то есть основной поток.
- Предложение: привязать `apis.SkipSuccessActivityLog()` к корневому маршруту в
`WebappHandler.Register` и поправить строку в `docs/review.md`, назвав оба
журнала.
- Найдено проходом: architecture.
- **Действие: инлайн.**
## Стоит исправить сейчас
### `docs/security.md` описывает периметр уже кода: новая анонимная поверхность в нём не названа
- Файл: `docs/security.md` (не тронут изменением); `internal/controller/http/webapp.go:236-284`
- Severity: major
- Confidence: high
- Оракул: `git status` — `docs/security.md` в изменениях рабочего дерева
отсутствует, тогда как раздача заведена; раздел «Периметр» перечисляет входы
без корневого маршрута, а таблица «Что разграничивает доступ» о раздаче не
знает.
- Последствие: анонимно открытым стал весь путь вне шести корней плюс всё
содержимое сборки, и документ этого не говорит. Следующие прогоны читают
периметр отсюда и не станут искать пути через раздачу вовсе — блиндаж,
который накапливается.
- Предложение: дописать в «Периметр» корневой маршрут (что отдаётся анониму,
что — нет) и строку в «Что разграничивает доступ» про раздачу.
- Найдено проходом: adversary.
- **Действие: инлайн.**
### Расхождение раскладки сборки с ожиданиями кода не ловится ничем: гейт зелёный, приложение отвечает `503` либо теряет правила `404` и срока хранения
- Файл: `web/embed.go:22-44`; `web/vite.config.ts:8-13`;
`internal/controller/http/webapp.go:36,262-275,291-301`
- Severity: minor
- Confidence: high
- Оракул: `grep` — ни один тест не зовёт `web.Dist()`, все проверки раздачи
получают `fstest.MapFS`; `outDir: 'embed/dist'` в сборщике и `distDir =
"embed/dist"` в Go связаны только совпадением строк; `build.assetsDir` в
`vite.config.ts` не задан вовсе, а Go держит `const assetsDir = "assets"` —
сегодня они совпадают по умолчанию сборщика.
- Последствие: одна причина, два исхода. Правка `outDir` даёт `built == false`
и `503` на каждом пути показа при зелёном гейте; смена умолчания `assetsDir`
сборщиком молча выключает и правило `404` под каталогом ресурсов, и годовой
срок хранения — тесты остаются зелёными, потому что подставная сборка
называет каталог сама.
- Предложение: `web/embed_test.go` на оба состояния `Dist()` (собрано / пусто),
проверяющий именно вшитое дерево — наличие `index.html` и каталога ресурсов;
плюс `build.assetsDir: 'assets'` явной строкой в `vite.config.ts`.
- Найдено проходами: autotests (п. 1), specs (пп. 3, 4), architecture (п. 14) —
одна причина в четырёх формулировках; оракула у согласия нет, приоритет
подняло само совпадение.
- **Действие: инлайн.**
### Каталог ресурсов сборщика сам по себе отвечает разметкой с кодом `200`
- Файл: `internal/controller/http/webapp.go:262-277`
- Severity: minor
- Confidence: high
- Оракул: чтение кода воспроизводит путь целиком — `fs.Stat("assets")` даёт
каталог, ветка ресурса пропускается по `!info.IsDir()`, а
`strings.HasPrefix("assets", "assets/")` — ложь, поэтому запрос уходит в
`serveIndex`. Враждебный проход подтвердил падающим тестом и живым прогоном:
`/assets` и `/assets/` → `200` с разметкой.
- Последствие: нарушено записанное свойство узла
(`docs/review.md:66-67`, «несовпавший ресурс под каталогом сборщика отвечает
`404`, а не разметкой с кодом `200`»). Ровно от этой ошибки — префикс без
точного совпадения — в соседних строках защищается `Mount.Covers`, и здесь
осталось одно условие из двух.
- Предложение: `name == assetsDir || strings.HasPrefix(name, assetsDir+"/")` в
обеих ветках — и в отказе, и в `setCacheHeader`.
- Найдено проходом: adversary.
- **Действие: инлайн.**
### Три решения раздачи не имеют дома: следующий автор снимет их, не нарушив ни одного требования
- Файл: `internal/controller/http/webapp.go:156-163,201-224`; `main.go:347-362`;
`openspec/changes/spa-skeleton/specs/webapp/spec.md`; `docs/architecture.md`;
`openspec/changes/spa-skeleton/tasks.md` (задачи 2.8, 5.2)
- Severity: minor
- Confidence: high
- Оракул: правило «путь в журнал не идёт» живёт в комментарии и тесте, но не в
дельта-спеке; у отпечатка вшитой сборки (`buildFingerprint`) нет ни
требования, ни теста, а задача 2.8 отмечена выполненной; capability `webapp`
не объявлена в перечне `docs/architecture.md`, задача 5.2 не отмечена;
сценарий «Отсутствие сборки видно в журнале» оракула не имеет — буфер журнала
в `TestWebappNotBuilt` заведён, но не читается.
- Последствие: решение без дома снимается следующей задачей бесплатно и молча.
Ближайший пример уже виден: вернуть путь анонима в журнал можно, не нарушив ни
одного записанного требования, — и это ровно то свойство, которое блокирующая
находка выше показала уже нарушенным. Capability без объявления даёт второе
описание раздачи у следующего автора.
- Предложение: дописать в дельту `webapp` требования «путь в журнал не идёт» и
«отпечаток вшитой сборки в журнале подъёма»; строку capability `webapp` в
`docs/architecture.md`; дочитать буфер журнала в `TestWebappNotBuilt`; отметить
задачи 2.8 и 5.2.
- Найдено проходами: specs (пп. 5, 6, 7, 9).
- **Действие: инлайн.**
## Гипотезы без доказательства
- **Человек, ошибшийся адресом, видит пустой экран с кодом `200`**
(`web/src/router.ts:9-12`). Поведение подтверждено чтением — в таблице один
маршрут `/`, catch-all нет, — а **дефектность не подтверждена**: требования на
этот счёт нет ни в дельте, ни в `web-ui.md`. Решает владелец: маршрут «такого
экрана нет» либо запись границей. Найдено: specs (п. 8).
- **Разметка уходит без `Content-Security-Policy`**, тогда как панель на том же
порту его получает. Пути нет: ближайший вход в разметку — ответ языковой
модели из ещё не сделанной `llm-insights-adapter`. Понижено до `minor`,
переведено в promote. Найдено: adversary (п. 20).
- **Состав вшитого никем не судится** — что именно уехало в бинарник, не
проверяет ни тест, ни шаг. Ущерб не построен, оракула нет. Найдено: adversary
(п. 20).
- **У корневого маршрута нет потолка частоты** (ограничитель заведён только на
`/app/`). Замера нагрузки нет, ущерб не построен; проект работает на единицах
записей в день (`docs/review.md`, «Недоступно проверке»). Найдено: ops (п. 23).
- **`POST /api/collections/users/request-verification` отвечает `204` анониму.**
Предмет старше изменения, пути к ущербу проход не построил. Найдено: adversary
(п. 20).
## Promote candidates
- **Зависимости приложения не сканируются на уязвимости** (`Taskfile.yml`, шаг
`front`; `web/package.json`). `govulncheck` смотрит только Go, ручной
`npm audit` сегодня чист. Это не находка о коде, а **пробел в правиле**:
`CLAUDE.md`, «Чего в гейте намеренно нет», перечисляет исключения поимённо, а
новая экосистема не попала ни в проверки, ни в этот перечень. Кандидат:
шаг `npm audit` в гейт (словарь кодов общий) **либо** строка исключения с
причиной. Решение владельца, не оркестратора. Найдено: autotests (п. 2).
- **Копия перечня корней в тесте расходится с перечнем молча**
(`journal_route_test.go:15-24` против `webapp.go:76-88`). Тест стережёт
инвариант `critical` про имя файла и сверяется с рукописной копией: новый
корень в копию не попадёт, тест останется зелёным. Кандидат: правило в
`internal/archrules` — тем же способом, каким проект уже стережёт перечень
колонок и дескриптор рубежа. Найдено: architecture (п. 16).
- **Заголовки безопасности разметки** — где объявляется `Content-Security-Policy`
и кто за него отвечает; сегодня дома у правила нет
(`docs/conventions/web-ui.md` о заголовках не говорит).
- **Поле `Exact` в `Mount` несёт два смысла** — «точный адрес» и «адрес
наблюдения». Сегодня они совпадают, завтра разойдутся. Кандидат в конвенцию
описания адресного пространства, а не правка этого кода. Найдено: architecture
(п. 16).
## Границы покрытия
### План: темы, дома, глубины
Воспроизведён таблицей в сводке выше. Тема **«темы проекта» дома не имеет** —
своих тем у проекта не заведено, поэтому `review-basics` не запускался, и это
не пропуск, а отсутствие предмета.
### Проходы
- Запускались на метке `large`, режим «по графу»: `specs`, `autotests`, `code`,
`architecture`, `adversary`, `ops`. Отчёт пришёл от каждого.
- Не запускался: `review-basics` — все темы разобраны именными проходами.
- **Что каждый проход не мог проверить в принципе — назвать нечем: ни один
проход не приложил блок «Coverage of this pass» и ни один не объявил свой
потолок.** Сколько находок осталось за срезом у `specs` (7 показанных) и у
`adversary` (7 показанных) — неизвестно. Это находка о прогоне, повторяющая
запись `docs/review.md` от 2026-08-13.
- **Потолок триажа сработал.** Срезано шесть проверенных находок, поимённо:
1. `Taskfile.yml`, шаг `front`: рассинхронизованный `package-lock.json`
роняет гейт кодом 3 после **удавшегося** `npm ping`, то есть при заведомо
живой сети, — словарь `CLAUDE.md` («Гейт») велит здесь код 1, дрейф. Правка
однострочная (code, п. 11).
2. `main.go:212-219`, `webapp.go:159-166`: два новых поля журнала заведены мимо
словаря `docs/conventions/logging.md`, домен `webapp.*` не объявлен (code,
п. 12).
3. `webapp.go:24` против `auth.go:96-105`: корень `/auth` объявлен константой
`AuthRoot` и оставлен литералом в трёх регистрациях. Правка `AuthRoot` даст
разметку на `/auth/callback` — возврат от провайдера сломается без единой
ошибки (code, п. 13).
4. `webapp.go:280-301`: разметка с `no-cache` никогда не подтверждается `304` —
`embed.FS` отдаёт нулевой `ModTime`, `ETag` не выставляется, каждая
ревалидация тянет полное тело. Готовый `buildFingerprint` под `ETag` уже
посчитан (ops, п. 22).
5. `webapp.outcome=failure` доезжает с `http.status_code=0` — предмет уже
заведён задачей `response-code-in-journal` (adversary, п. 20).
6. Задача 3.6 (`tasks.md`) закрыть на этом окружении нечем — см. ниже.
- **Задача 3.6 остаётся человеку.** `docker build --target front-build` виснет
на `npm ci` и в обычной сети, и с `--network=host`: из контейнера нет
исходящей сети вовсе при рабочем DNS. Замерено то, что измеримо: вшитое
приложение добавляет к бинарнику 86 072 байта, Node в финальный слой не
попадает структурно, `task front` без сети отвечает за 11,3 с кодом 3. Число
размера образа снимается на машине с сетью (ops, п. 21).
### Что осталось целиком на человеке
**Не проверит ни один проход** (`docs/review.md`, «Недоступно проверке»):
- поведение внешних сервисов под нагрузкой и на границах — SpeechKit и Object
Storage поднять в тесте нечем;
- реальный профиль нагрузки: проект работает на единицах записей в день;
- стойкость `ffmpeg` к вредоносному входу;
- поведение настоящей Authelia и её правило на нашего клиента — настройка
выкладки вне репозитория. **На этом прогоне это стоило половины оракула у
первой блокирующей находки**: сторона сервиса проверена, сторона прокси — нет;
- поведение браузера с куками (`SameSite`, приём `Set-Cookie` при переходе с
чужого сайта).
**Перестали проверять сознательно** (тот же раздел, отдельным списком):
- разбор вывода настоящего `ffprobe` — своего теста у `adapter/metaviewer/ffmpeg`
нет ([ADR-2026-08-11-stub-adapters-in-tests](../../../../docs/adr/ADR-2026-08-11-stub-adapters-in-tests.md));
- работа сервиса с настоящими внешними собеседниками: живой прогон доступен и
покрывает подъём, маршруты, панель, журнал, метрики и остановку, но за
настоящие SpeechKit и Object Storage не отвечает — ключи выдуманные,
распознавание подменяется в коде; вход через живого провайдера OIDC тоже
недоступен.
Общее, что не проверяет ни один прогон в принципе: история инцидентов;
поведение под реальным потоком; поведение внешних систем в их версиях; завязка
потребителей на текущее поведение; и вопрос «нужна ли эта функциональность
вообще».
### Каких документов и данных не хватило
- **Разметка не передала размер, сложность и обоснование метки** — в задании
триажу только метка и режим. Сверить `large` с предметом нечем; сигнала о
заниженной метке при этом не подавал никто.
- **Правила обратного прокси нет в репозитории** — оно живёт в
`pet-project-server`. Из-за этого путь к панели через `/%5f/` подтверждён
только на стороне сервиса, и находка держит `major`, а не `critical`.
- **`docs/conventions/web-ui.md` не говорит о заголовках безопасности** —
документ есть, предмета в нём нет; отсюда `Content-Security-Policy` уехал в
promote, а не в находки.
- **`openspec/changes/spa-skeleton/specs/webapp/spec.md` не покрывает три
решения раздачи** — дельта есть, требований на предмет в ней нет (пункт 4
второй секции).
- **`docs/review.md`, «Типовые ложноположительные», прочитан и применён**:
четыре записи, ни одна к находкам этого прогона не подошла. Отсев вкусовщины
шёл по общим критериям и по этому разделу.
### Четыре строки, которых не принесёт ни один проход
1. **Решения проекта не сверялись.** `docs/adr.*` — процессный документ, прогон
его не открывает. Расхождение изменения с записанным решением ловит
`av-dev:doc-healthcheck`, а не ревью. У этой задачи ADR есть
(`ADR-2026-08-11-spa-on-vue.md`, тронут), и сверен он не был.
2. **Записанные наблюдения проекта не использовались.** `docs/research.*` не
открывался. Все числа в отчёте сняты на этом прогоне: размер `.git` (`du`),
умолчания журнала PocketBase (исходники модуля), 86 072 байта прироста
бинарника (замер прохода `ops`), таблица ответов мультиплексора (`go run`).
3. **Поимённая сверка с руководствами по стилю Go, TypeScript и Vue не
задавалась ни одним проходом.** Различение «идиоматично против
распространено» не спрашивает никто — прохода про идиоматичность в конвейере
нет. Для этой задачи это ощутимее обычного: `web/` — новая для проекта
экосистема, и её первый код никем на идиоматичность не смотрен.
4. **Альтернативной реализации, с которой можно сдиффить решения, у конвейера
нет.** Проход независимой реализации снят по стоимости, а не по замеру;
«не знаю, чего не знаю» здесь не достаёт никто.
Формулировка «критичных проблем не обнаружено» к этому отчёту неприменима: три
находки блокируют мердж, и ещё шесть проверенных срезаны потолком и названы
поимённо выше.