docs: правда про флаг прекращения обработки — 14 пометок в 5 бумагах, решение Р110

Бумаги проекта обещали, что флаг processing_restricted всё блокирует: особое
исключение при попытке тронуть человека, охранник, не пускающий сотрудников
в его карточку, «все операции обязаны проверять флаг и отказывать».
Не построено ничего — флаг ставится и лежит.

Рядом с каждым обещанием дописан замер «06.08.2026: не построено» с командой,
которой это перемеряется. Прежний текст оставлен дословно, ноль удалённых строк.
Галочка «закрыт» у Ю-9 на месте, версии в шапках не тронуты.

План называл четыре места. Перечень собран заново прибором — их четырнадцать.
Три самых опасных были вне списка: «реализовано через guard-исключение»
в перечне сильных сторон, и два раздела ТЗ, написанных как описание
готовой работы вплоть до текста заглушки и кода ответа 403.

Два хвоста переданы владельцу. Первый: контрольный лист аудита 152-ФЗ
в .claude/skills/pdn-152fz-audit несёт ту же ложь — путь запрещён к правке.
Второй: отчёт по безопасности от 17.06.2026 остался непомеченным — сторож
правописания краснеет там на пяти чужих словах, а поправить их значит
удалить чужие строки.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-08-06 18:27:48 +03:00
parent 808928458a
commit 525af778be
6 changed files with 398 additions and 0 deletions
+8
View File
@@ -727,6 +727,14 @@ public function recalculate(Project $project): int {
**Из решения интервью OPEN-Д-1 (Прил. Д v8.2):**
> 🔴 **ЗАМЕР 06.08.2026 (Р110): подпись столбца ниже обещает защиту, которой нет.** Столбец
> `processing_restricted` в базе есть. А вот блокировки, которую обещает его подпись, не существует:
> ни на уровне сервиса, ни на уровне базы. Класса `ProcessingRestrictedException` в проекте нет —
> ни файла, ни строки; RLS-политики по этому флагу нет тоже. Флаг сегодня только ставится и лежит.
> Замерено: `git ls-files | grep -i "ProcessingRestriction"` и
> `grep -rn "class ProcessingRestrictedException" app/` — обе команды дают пустой ответ.
> Сам канон схемы уже поправлен решением Р103, здесь текст оставлен дословно как историческая копия.
```sql
ALTER TABLE pd_subject_requests
ADD COLUMN processing_restricted BOOLEAN NOT NULL DEFAULT FALSE;
@@ -29,6 +29,13 @@
- **Биз-22 (§10.X)** — простой lead-scoring: `lead_score = supplier.quality_score × (time_in_form_seconds / 60)`, clamped в `[0, 99.99]`. Без ML. Триггер `calc_lead_score()` BEFORE INSERT/UPDATE на `deals`.
- **CTO-14 (§12.5.5)** — UTM-поля для когортной аналитики: `deals.utm_source/utm_medium/utm_campaign/utm_content` + индекс `(tenant_id, utm_source)`.
- **CTO-15 (§22.X + §23.10.X)** — two-person impersonation для тенантов с `pd_subject_request.processing_restricted=TRUE` ИЛИ `chargeback_unrecovered_rub > 0`. `impersonation_tokens.second_approver_id` + `second_approval_at` (роль `compliance` обязательна).
🔴 **ЗАМЕР 06.08.2026 (Р110): столбцы есть, проверки нет.** `second_approver_id` и `second_approval_at`
в таблице заведены, но их никто не заполняет и никто не требует: токен на вход под клиента сегодня
создаётся для любого тенанта, независимо от `processing_restricted`. Класса `SaasAdminAuthService`,
который по замыслу сверял бы флаги и требовал второго одобряющего, в проекте не существует —
`grep -rn "class SaasAdminAuthService" app/app/` даёт пустой ответ. Подробнее — пометка к §22.13.7.
- **CTO-16 (§10.X)**`project_user_assignments(project_id, user_id, skills JSONB)` — пул менеджеров проекта для `assignment_strategy IN ('round_robin','least_loaded')` Post-MVP.
- **OPEN-И-17 (§22.X + Прил. И)** — TTL 365 дней на API-ключах. Cron `secrets:notify-expiring` уведомляет за 30 дней до expiry. UI «продлить» ставит `+365d`.
- **OPEN-И-18 (§19.10.X)** — DNS-rebinding защита для outbound webhook: resolve→pin IP→connect, blacklist RFC1918 на pinned IP, ≤3 redirect, max body 1 MB.
@@ -37,6 +44,13 @@
- **OPEN-И-21 (§22.X + Прил. И)** — Anti-DDoS стек: Nginx `limit_req_zone` 1000 RPS глобально + Yandex SmartCaptcha на `/register` + disposable-email-blacklist (~50К доменов).
- **Ю-9 (§22.X)** — hard-блок impersonation для всех ролей кроме `compliance` при `pd_subject_request.processing_restricted=TRUE` (152-ФЗ ст.21 ч.5). Реализация совместно с CTO-15 — единый guard в `SaasAdminAuthService`.
🔴 **ЗАМЕР 06.08.2026 (Р110): это решение, а не построенная защита.** Единого охранника нет:
класса `SaasAdminAuthService` в проекте не существует — ни файла, ни строки
(`grep -rn "class SaasAdminAuthService" app/app/` — пусто), а `ImpersonationController` в своей шапке
прямо пишет «на MVP не реализовано». Вход под клиента флаг `processing_restricted` не смотрит вовсе:
войти можно к любому тенанту и любой ролью. Пункт Ю-9 в реестре открытых вопросов при этом стоит
с галочкой «закрыт» — закрыто **решение**, работа не сделана.
**7 P2 — реализация в фазах 1–3:**
- **Биз-23 (§10.X)**`deals.region_code VARCHAR(8)` + `deals.city VARCHAR(100)` + автоопределение по prefix phone через `PhonePrefixService`.
@@ -158,6 +172,11 @@
### Решения внутри приложений
- **OPEN-Д-1 ✅:** `pd_subject_requests` дополняется полем `processing_restricted BOOLEAN DEFAULT FALSE` — флаг полной остановки обработки ПДн по требованию субъекта (ст.21 152-ФЗ).
🔴 **ЗАМЕР 06.08.2026 (Р110): полной остановки обработки не происходит.** Поле добавлено — это правда.
Но «флаг полной остановки» на сегодня останавливает ноль операций: ни один сервис портала его не читает,
класса `ProcessingRestrictedException` не существует (`git ls-files | grep -i "ProcessingRestriction"`
пусто). Закрыт вопрос «нужно ли такое поле», а не задача «останавливать обработку».
- **OPEN-Д-5 / OPEN-И-1 ✅:** создаём таблицу `incidents_log(id, type, severity, started_at, resolved_at, summary, root_cause, postmortem_url, related_pd_subject_request_ids)` — единый журнал инцидентов для compliance и runbook.
- **OPEN-Ж-3 ✅:** способ принятия оферты — **Click-wrap**: 3 чекбокса при регистрации (с офертой согласен / с Политикой согласен / даю согласие на ПДн). Юридически достаточно, технически просто.
- **OPEN-И-2 ✅:** интеграция с внешними CRM — **Уровень 1 (webhook outbound) на MVP** + архитектурный задел для Уровня 2 (готовые коннекторы). **amoCRM-коннектор — в спринте 14–15** как пилотный. Bitrix24, RetailCRM — Post-MVP по запросу клиентов.
@@ -5154,6 +5173,15 @@ const safe = DOMPurify.sanitize(userInput, SAFE_HTML_CONFIG);
### 22.13.1. Yandex 360 SSO для админов SaaS (OPEN-И-13)
> 🔴 **ЗАМЕР 06.08.2026 (Р110): весь раздел ниже — замысел, не построенная работа.** Он описывает
> `SaasAdminAuthService` так, будто это существующий кусок портала («наш `SaasAdminAuthService` — RP»,
> «`SaasAdminAuthService::handleSsoCallback($payload)`»). Такого класса в проекте нет — ни файла,
> ни строки: `grep -rn "class SaasAdminAuthService" app/app/` даёт пустой ответ. Значит ни единый вход
> через Yandex 360, ни обязательный TOTP, ни ограничения аварийного входа кодом не проверяются:
> столбцы `saas_admin_users.sso_provider` и `is_break_glass` в базе есть, охранника нет.
> То же самое уже помечено в каноне схемы решением Р103. Читать раздел как описание работающей защиты
> нельзя — это техническое задание на неё.
**Решение DO-5 (04.05.2026):** Yandex 360 SSO для админов SaaS. **v8.5 (OPEN-И-13):** полный flow.
**Архитектура:**
@@ -5271,6 +5299,24 @@ const safe = DOMPurify.sanitize(userInput, SAFE_HTML_CONFIG);
### 22.13.7. Two-person impersonation + Ю-9 hard-block (CTO-15 + Ю-9)
> 🔴 **ЗАМЕР 06.08.2026 (Р110): описанная ниже защита НЕ ПОСТРОЕНА. Проблема, названная ниже как
> «до v8.5», жива сегодня.** Раздел читается как описание готового механизма — вплоть до порядка шагов,
> имён столбцов и текста заглушки. На деле нет ни одного его куска:
>
> - класса `SaasAdminAuthService` в проекте не существует — ни файла, ни строки
> (`grep -rn "class SaasAdminAuthService" app/app/` — пусто);
> - вход под клиента делает `ImpersonationController`, и он в своей же шапке пишет
> «на MVP не реализовано»: флаг `processing_restricted` он не читает вовсе;
> - токен создаётся для любого тенанта и любой ролью; `second_approver_id` и `second_approval_at`
> никто не заполняет и не требует; писем второму одобряющему никто не шлёт;
> - записи `impersonation.second_approval_granted` / `..._denied` в журнал не пишет никто.
>
> То есть человек, запретивший обработку своих данных, сегодня **не защищён** от входа сотрудника
> в его карточку — ровно та «юридически это нарушение», о которой говорит абзац ниже.
> Прежний текст оставлен дословно: это техническое задание, и как задание оно верно. Как описание
> работающей защиты читать его нельзя. **Проверить заново:** команда выше перестанет быть пустой —
> значит защиту построили, и эту пометку снимает тот, кто её построил.
**Проблема:** до v8.5 `SaasAdminAuthService::createImpersonationToken()` создавал токен для любого тенанта без проверки compliance-флагов. Если у тенанта `pd_subject_request.processing_restricted=TRUE` (152-ФЗ ст.21 ч.5 — субъект ПДн запретил обработку) — admin SaaS мог несмотря на это войти в кабинет тенанта и просмотреть/изменить данные. Юридически это нарушение.
**v8.5 решение:**
@@ -5912,6 +5958,14 @@ DDL `pd_subject_requests` — [`db/schema.sql`](../db/schema.sql) v8.4.
### 23.10.11. v8.5: Yandex 360 SSO + break-glass + two-person impersonation
🔴 **ЗАМЕР 06.08.2026 (Р110): ни один из пяти экранов ниже не построен.** Раздел описывает готовую
админку: серый замок «Two-person required», очередь одобрений, скрытую кнопку «Войти как клиент»
и ответ 403 с записью `imp_blocked_152fz_st21` при попытке зайти по прямой ссылке. На сегодня всего
этого нет: класса `SaasAdminAuthService`, который по замыслу и возвращал бы 403, в проекте не
существует (`grep -rn "class SaasAdminAuthService" app/app/` — пусто), флаг `processing_restricted`
при входе под клиента не читается, кнопка не скрывается и не блокируется, экрана
`/admin/imp/pending-approvals` нет. Прежний текст оставлен дословно как задание на работу.
> **Источник:** реестр v1.12 §13.10 (OPEN-И-13, CTO-15, Ю-9). Для углублённого описания см. также §22.13.1 (SSO flow), §22.13.7 (two-person + Ю-9).
**Изменения в админке SaaS v8.5:**
+19
View File
@@ -2,9 +2,28 @@
**Версия:** 8.2 от 04.05.2026 (правки после интервью с заказчиком).
> 🔴 **ЗАМЕР 06.08.2026 (решение владельца Р110): флаг «прекратить обработку моих данных» НИЧЕГО НЕ БЛОКИРУЕТ.**
> Этот документ ниже обещает, что при поднятом `processing_restricted` все операции с данными человека
> проверяют флаг и отказывают. На 06.08.2026 такой защиты в портале **нет**: флаг ставится, хранится — и всё.
> Ни одна проверка на него не смотрит. Класса `ProcessingRestrictedException` в проекте не существует —
> ни файла, ни строки.
> **Чем замерено** (обе команды дают пустой ответ): `git ls-files | grep -i "ProcessingRestriction"` и
> `grep -rn "class ProcessingRestrictedException" app/`.
> Прежний текст ниже оставлен дословно: это принятое решение, а не построенная работа, и пометки лишь
> отделяют одно от другого. **Проверить заново** — теми же двумя командами; как только они перестанут
> быть пустыми, пометки снимает тот, кто построил защиту.
## Что нового в v8.2 относительно v8.1
-**OPEN-Д-1 закрыт.** В таблицу `pd_subject_requests` добавляется новое поле `processing_restricted BOOLEAN NOT NULL DEFAULT FALSE` — флаг полной остановки обработки ПДн субъекта по требованию (ст.21 152-ФЗ, право на ограничение обработки). При установке этого флага все последующие операции с ПДн данного субъекта в системе должны проверять флаг и отказывать. Имплементация: `if (subject.processing_restricted) throw new ProcessingRestrictedException()` в сервисном слое + RLS-политика на чтение в БД-слое (опционально, для defense-in-depth).
🔴 **ЗАМЕР 06.08.2026 (Р110): это решение, а не построенная защита.** Поле в таблице есть — это правда.
Всё остальное в пункте выше — замысел, который не выполнен: ни одна операция портала флаг не проверяет
и никому не отказывает, класса `ProcessingRestrictedException` не существует, RLS-политики по этому флагу
тоже нет. Замерено командами `git ls-files | grep -i "ProcessingRestriction"` и
`grep -rn "class ProcessingRestrictedException" app/` — обе дают пустой ответ. Читать пункт выше как
описание работающей защиты нельзя.
-**OPEN-Д-5 закрыт.** Создаётся таблица **`incidents_log`** для журнала инцидентов (объединена с OPEN-И-1). DDL:
```sql
@@ -0,0 +1,281 @@
# Отчёт помощника — З-0-11 «Правда про флаг прекращения обработки» — 06.08.2026
Круг работы: только бумаги. Кода не тронуто ни строки. Ветка `feat/prospects-manual-testing-kp`.
## Разбор задания
Задание описывает беду верно, а число мест — неверно.
**Что подтвердилось.** Флаг `pd_subject_requests.processing_restricted` ставится и лежит.
Ни класса `ProcessingRestrictedException`, ни охранника `SaasAdminAuthService` в проекте нет.
Замерено двумя приборами независимо от надзирателя — см. «Разведка».
**С чем не согласился — число «четыре».** Мест оказалось **четырнадцать** в шести бумагах.
Пометки поставлены в тринадцати из них плюс баннер в шапке — четырнадцать пометок в пяти
бумагах. Четырнадцатое место править не дал сторож проекта, о нём отдельно.
Ещё одно место — в запрещённом мне пути, о нём ниже громко.
**С чем не согласился — «врезка рядом».** Надзиратель сам назвал слабость: читатель приходит
по оглавлению прямо в пункт. Я усилил правило: **пометка ставится там, где читатель ВХОДИТ,
а не там, где её удобно приткнуть.** Отсюда три следствия:
- в таблицу нельзя дописать пометку строкой — читатель таблицы бежит глазами по строкам;
поэтому пометка стоит **сразу под заголовком раздела, до таблицы**, мимо неё не пройти;
- у раздела, который целиком описывает несуществующую защиту (§22.13.1, §22.13.7, §23.10.11),
пометка стоит **между заголовком и первым абзацем**;
- у бумаги, чей предмет — сам этот флаг (Приложение Д), пометка стоит **баннером в шапке**,
сразу под строкой версии.
**Ограничение, которое переписало форму работы.** Мерка «ноль удалённых строк» означает, что
**ни одна существующая строка не может быть изменена** — правка строки в git считается как
удаление + добавление. Поэтому все пометки — только целиком новые строки. Соблазн дописать
правду прямо в ячейку таблицы (где читатель её точно увидит) пришлось отвергнуть именно
по этой причине, а не по вкусу.
## Разведка
### Прибор проверен на подставной строке ДО того, как я ему поверил
Предупреждение надзирателя подтвердилось: `grep -i` кириллицу не складывает.
```text
$ printf 'ДИСКА ещё НЕТ\nБлокируется ОТКАЗ\n' > decoy.md
$ grep -i "диска" decoy.md ; echo "rc=$?"
rc=1 # ничего не нашёл — прибор негоден
```
Работал `ripgrep` (инструмент Grep), проверенный на тех же двух подставных строках,
которые он ОБЯЗАН поймать:
```text
rg -i "диска" decoy.md → 1:ДИСКА ещё НЕТ ✅
rg -i "блокируется" decoy.md → 2:Блокируется ОТКАЗ ✅
```
Только после этого перечень мест собирался им.
### Чего в коде нет — перепроверено мной, не принято на слово
```text
git ls-files | grep -i "ProcessingRestriction" → пусто
grep -rn "class ProcessingRestrictedException" app/ → пусто
grep -rn "class SaasAdminAuthService" app/app/ → пусто
```
Плюс третий, независимый прибор: сквозной перечень всех упоминаний
`processing_restricted` по хранилищу — **103 совпадения в 33 файлах**. В коде портала
(`app/**`) флаг встречается только там, где его записывают, читают из базы и объявляют тип:
контроллер, модель, интерфейс. Ни одной проверки «если поднят — отказать».
### Перечень мест лжи — собран командой, не по списку надзирателя
Найдено четырнадцать мест в шести бумагах, пометки поставлены в тринадцати плюс баннер.
| # | бумага | место | что обещано | в списке надзирателя |
|---|---|---|---|---|
| 1 | `docs/Workflow_pd_subject_requests_v8_2.md` | шапка (баннер) | — | — |
| 2 | то же | «✅ OPEN-Д-1 закрыт», п.1 | «все операции **должны проверять флаг и отказывать**», `throw new ProcessingRestrictedException()`, RLS-политика | ✅ да |
| 3 | `docs/Analiz_originala_v8_3.md` | §3.3, перед DDL | «блокируются на уровне сервиса и БД (RLS)» | ✅ да |
| 4 | `docs/CRM_bp-gr_Инструкция_v8_5.md` | решение CTO-15 в списке v8.5 | two-person одобрение обязательно | ❌ нет |
| 5 | то же | решение Ю-9 в списке v8.5 | «hard-блок … единый guard в `SaasAdminAuthService`» | ✅ да |
| 6 | то же | «Решения внутри приложений», OPEN-Д-1 | «флаг **полной остановки** обработки» | ❌ нет |
| 7 | то же | §22.13.1 SSO | «наш `SaasAdminAuthService` — RP», `::handleSsoCallback()` | ❌ нет |
| 8 🔴 | то же | §22.13.7 | целый готовый механизм: порядок шагов, письма, текст заглушки | ❌ нет |
| 9 🔴 | то же | §23.10.11 | пять экранов админки, «кнопка **полностью скрыта**», «вернёт 403 + audit» | ❌ нет |
| 10 | `docs/Открытые_вопросы_v8_3.md` | §8.1, перед таблицей | ✅ OPEN-Д-1 «Ограничение обработки ПДн — закрыто» | ❌ нет |
| 11 | то же | «12 P1 закрытий» | two-person impersonation закрыт | ✅ да (строка 2163) |
| 12 🔴 | то же | §13.7 «Что хорошо покрыто» | «`processing_restricted`**реализовано** через guard-исключение» | ❌ нет |
| 13 | то же | §13.10.2, перед таблицей | ✅ CTO-15 и ✅ Ю-9 | ❌ нет |
| 14 | `docs/Объединённый_конспект.md` | «Что хорошо покрыто (из C-2)» | `processing_restricted` в перечне сильных сторон | ❌ нет |
| 15 ⛔ | `docs/security/2026-06-17-go-live-security-report.md` | Режим 2, ✅ ст.21 ч.5 | галочка соответствия закону | ❌ нет |
Строка #1 — баннер, не отдельная ложь. Строка #15 — место лжи есть, но пометку поставить
не дал сторож проекта; правка откачена, разбор ниже. Итого: **мест четырнадцать,
пометок поставлено четырнадцать в пяти бумагах** — тринадцать у мест плюс баннер.
**Из четырёх мест надзирателя все четыре подтвердились** — ни одного ложного. Но найдено
ещё десять. Число «четыре» было занижено втрое.
### Три самых опасных места — все вне списка надзирателя
- **#12** — единственное место во всех бумагах, где стоит слово **«реализовано»**. И стоит
оно в разделе «Что хорошо покрыто», среди правдивых пунктов. Читатель, проверивший
соседние строки и убедившийся, что они верны, принимает эту на веру заслуженно.
- **#8 и #9** — не одна фраза, а два развёрнутых раздела, написанных как описание готовой
работы: с порядком шагов, именами столбцов, текстом заглушки для пользователя, кодом
ответа 403 и именем записи в журнале `imp_blocked_152fz_st21`. Такое не читается как
замысел — такое читается как «сделано, вот подробности».
- **#8** особенно: он начинается словами «**Проблема:** до v8.5 … admin SaaS мог войти
… Юридически это нарушение» и продолжается «**v8.5 решение:**». То есть прямо утверждает,
что нарушение устранено. Оно не устранено — оно живо сегодня.
### 🔴 Два места, которые я НЕ имел права трогать — эскалирую
1. **`.claude/skills/pdn-152fz-audit/references/checklist.md:175176`** — контрольный лист
для аудита по 152-ФЗ утверждает: «при `TRUE` `ProcessingRestrictedException` **блокирует
операции с ПДн субъекта**». Это опаснее всех четырнадцати мест вместе: **прибор, которым
проверяют соблюдение закона, сам несёт ложь**. Аудит, запущенный этим листом, поставит
галочку и уйдёт довольным. Путь `.claude/**` мне запрещён — правку должен сделать владелец.
Там же `SKILL.md:49` спрашивает «Реализовано ли право на ограничение обработки?» — этот
вопрос честный, его трогать не надо.
2. **`db/CHANGELOG_schema.md`** — уже поправлен решением Р103, ничего не требуется.
Проверил и **не стал** править (лжи нет, обещаний защиты не содержат):
`docs/Приложение_Б_В_БД_диаграммы_v8_3.md` (перечисляет добавленные столбцы — столбцы
и правда есть), `docs/README_АРХИВ_v8_5.md` (описывает состав приложений),
`docs/CHANGELOG_claude_md.md` (исторические записи, где two-person честно помечен как TODO
на будущее), `docs/Открытые_вопросы_v8_3.md` §13.5 (таблица вариантов A/B/C — честно
предлагает варианты, ничего не утверждает).
## Что сделал
Четырнадцать пометок вида «🔴 **ЗАМЕР 06.08.2026 (Р110): …**» в пяти бумагах — все одним
голосом, по образцу решения Р103 в каноне схемы.
Три границы соблюдены:
1. **Ничего не удалено и не переписано.** `git diff --numstat`, второй столбец — ноль
по каждому из пяти файлов. Прежний текст стоит дословно, пометка добавлена рядом.
2. **Галочка «закрыт» у Ю-9 на месте.** Более того, две пометки (#11 и #13) прямо
объясняют, почему её не снимают: «Галочки „закрыт" остаются: решения приняты.
Пометка говорит лишь, что работа по ним не сделана».
3. **Версии в шапках не тронуты.** Ни одна изменённая строка не содержит номера версии —
доказано тем же нулём удалений: все строки с версиями остались нетронутыми байт в байт.
### ⛔ Место #15 — отчёт по безопасности: пометку поставить НЕ ДАЛ сторож проекта
Правку я сделал, а потом откатил. Рассказываю честно, потому что тут спорили два правила
круга и одно из них пришлось признать главнее.
**Почему трогал.** Строка в отчёте от 17.06.2026 гласит: «✅ … `processing_restricted`
(ст.21 ч.5) … — **присутствуют в схеме**». Она **дословно верна**: столбец в схеме есть.
Но это единственная бумага в хранилище, которую по замыслу показывают проверяющим, и
галочка рядом со ссылкой на статью закона читается как «требование закона выполнено» —
ровно тот вред, который назван в задании. Я дописал туда семь строк, помеченных не «ЗАМЕР»,
а «ПРИПИСКА 06.08.2026, дописана позже прогона, ничего в нём не меняет».
**Почему откатил.** Сторож правописания завалил коммит. Оказалось, в этом отчёте **уже
лежат пять слов не из словаря** — «незакоммиченные», «доустановки», «регекс», «десинка» —
и все пять появились там задолго до меня. Замерено: ни одно из них не встречается ни в одной
из моих семи строк, а `git diff --numstat` по этому файлу показывал `7 0` — ни одна чужая
строка не тронута. То есть сторож краснел на чужом долге, но красный он от моего коммита.
Выходов было три, и два запрещены:
- поправить пять чужих слов — это правка существующих строк, то есть удаления в `numstat`.
Мерка «ноль удалённых строк» названа в задании самой важной. **Отпадает.**
- дописать слова в общий словарь `cspell-words.txt` — файл прямо запрещён к правке. **Отпадает.**
- откатить свои семь строк. **Сделано.**
Файл вернулся дословно к прежнему виду — доказано: `git status` по нему пуст.
Место лжи при этом **никуда не делось**, и я передаю его владельцу вместе с готовым текстом
пометки — он лежит выше в этом же разделе, в кавычках. Чтобы его поставить, нужно сперва
решить, что делать с пятью чужими словами: либо владелец разрешает их поправить, либо
разрешает добавить в словарь. Своей властью я ни того, ни другого сделать не могу.
## Ножи
### Нож 1. Допущение «мест лжи ровно четыре»
Случай мимо: **девять лишних мест**, из них три опаснее всех четырёх названных.
Допущение вырезано: перечень собран прибором с нуля, а не сверен со списком.
### Нож 2. Допущение «ложь узнаётся по имени класса»
Случай мимо найден и он не один:
- **#6** — «флаг **полной остановки обработки** ПДн» — ни одного имени класса, чистое
обещание по-русски;
- **#12** — «**реализовано** через guard-исключение» — слово «guard» есть, имени нет;
- **#14** — `processing_restricted` просто стоит в перечне «что хорошо покрыто», без единого
утверждения. Ложь тут в самом факте присутствия в списке.
Поиск по именам классов не нашёл бы ни одного из трёх. Нашёл сквозной поиск по имени
**столбца** плюс чтение окружающего текста глазами.
### Нож 3. Допущение «врезка рядом — значит читатель её увидит»
Случай мимо, названный надзирателем: человек приходит по оглавлению прямо в пункт.
Проверил на себе — и нашёл случай мимо ЕГО поправки тоже: **пометка после таблицы
не работает вовсе**, потому что таблицу читают строками и до низа не доходят.
Отсюда правило «пометка там, где входят» и три его следствия (см. «Разбор задания»).
### Нож 4. Мой собственный прибор
Допущение: «`ripgrep` складывает регистр кириллицы». Проверено на двух подставных строках
ДО работы. Не проверь я — весь перечень мог бы оказаться выборочным, а отчёт — уверенным.
### Нож 5. Допущение «ноль удалённых строк достигается аккуратностью»
Случай мимо: правка **внутри существующей строки** даёт удаление в `git diff --numstat`,
хотя ничего не стёрто по смыслу. Именно этот случай чуть не произошёл: дописать правду
внутрь ячейки таблицы было лучшим решением для ножа 3. Отвергнуто.
### Нож 6. Допущение «зелёные сторожа означают, что круг чист»
Случай мимо нашёлся сам: сторож правописания **покраснел на чужом долге**, лежавшем
в файле задолго до меня, — и завалил мой коммит. Обратное тоже верно и опаснее: сторож
мог бы **промолчать** о моей ошибке, если бы файл уже был в исключениях. Так и вышло:
`docs/superpowers/**` целиком в списке исключений `cspell.json`, поэтому мой собственный
отчёт **сторожем правописания не проверен вовсе** — он числится в «6 файлов проверено»
только потому, что я его туда вписал, а на деле пропущен. Знать это важнее, чем видеть
зелёное: зелёное здесь означает «не смотрели», а не «чисто».
## Что осталось незакрытым
### 1. 🔴 Контрольный лист аудита 152-ФЗ несёт ту же ложь — путь запрещён
`.claude/skills/pdn-152fz-audit/references/checklist.md:176`. Правку должен сделать
владелец. Считаю это самым срочным хвостом круга: остальные бумаги вводят в заблуждение
читателя, а эта — **инструмент проверки**.
### 2. ⛔ Отчёт по безопасности от 17.06.2026 остался непомеченным
Место #15. Готовый текст пометки — в разделе «Что сделал». Ставить его нельзя, пока владелец
не решит, что делать с пятью чужими словами не из словаря, которые уже лежат в том файле:
поправить их или добавить в словарь. Оба действия — за пределами моих прав.
Пока этого нет, галочка «✅ ст.21 ч.5» в отчёте продолжает читаться как доказательство
соблюдения закона.
### 3. Ответ на вопрос владельца: не станет ли пометка новой ложью, когда защиту построят
Станет — если оставить её простым утверждением. Сделано три вещи, и предложена четвёртая.
**Что сделано.** Каждая пометка называет **команду, которой её можно перемерить**, а не
только вывод. Пометка утверждает не «защиты нет», а «вот команда, и она даёт пустой ответ».
Такое утверждение опровергается за десять секунд кем угодно, включая непрограммиста:
команда перестала быть пустой — пометка устарела. В ключевых местах это сказано словами:
«**Проверить заново** — теми же командами; как только они перестанут быть пустыми, пометки
снимает тот, кто построил защиту».
**Чего это не решает.** Никто не обязан их снимать. Через месяц пометки могут остаться
и начать врать в другую сторону — ровно опасение владельца.
**Предложение (код, я его не писал — за пределами круга).** В проекте уже живёт сторож
`app/tests/Unit/Schema/KanonNeObeshchaetOhrannikaTest.php`, поставленный решением Р103.
Правило у него точно то, что нужно: «кто называет несуществующее имя, тот пишет
„не существует" в том же куске текста». Но он собирает бумаги **только из папки `db/`**
проверено чтением: `kanonBumagiProBazu()` обходит `db/` и ничего кроме. **Вся папка `docs/`
у него вне охраны.** Отсюда два предложения владельцу:
- **расширить сторожа на `docs/`** — тогда возврат лжи в бумаги ловится сам, без человека;
- **добавить ему вторую половину:** сегодня он требует пометку, пока класса нет, но не
требует **убрать** пометку, когда класс появится. Достроить: «класс появился → пометка
про его отсутствие обязана исчезнуть». Тогда механизм становится симметричным и вопрос
владельца закрывается насовсем, а не обещанием.
Ни того, ни другого я не делал: `app/**` запрещён, там работает другой помощник.
### 4. Оговорка про мою мерку полноты
Тринадцать мест — это то, что нашли **сквозной поиск по имени столбца, по `Ю-9`, по именам
двух классов и чтение окрестностей глазами**. Обещание, не содержащее ни одного из этих
четырёх якорей (например, «человек может запретить обработку, и портал это исполнит»,
без слова `processing_restricted` и без «Ю-9»), моим перечнем **не поймано бы**. Я такого
не встретил, но и утверждать, что его нет, не могу. Честная граница мерки.
### 5. Версии бумаг
Не тронуты, как велено. Шесть бумаг изменены содержательно — **владельцу решать**, требует
ли это подъёма версий и записи в квинтете нормативки. Сам не делал: у нормативных бумаг
свой порядок изменения.
@@ -1420,6 +1420,12 @@ OPEN-И-12 ⏸ Где хранить контакты эскалации (P1,
**Что хорошо покрыто (из C-2):** 34/34 RLS, CSP без `unsafe-inline`, HMAC SHA-256+timestamp+replay, SSRF-фильтр, impersonation Ю-1, atomic billing, processing_restricted, four-eyes >50К ₽. Выпадает только DNS-rebinding (P1 OPEN-И-18).
🔴 **ЗАМЕР 06.08.2026 (Р110): `processing_restricted` в списке «хорошо покрыто» стоит зря.** Покрыт
только столбец в таблице. Запрета обработки по этому флагу в портале нет: ни один сервис его не
читает, класса `ProcessingRestrictedException` не существует —
`git ls-files | grep -i "ProcessingRestriction"` даёт пустой ответ. Строка оставлена дословно,
пометка лишь снимает с неё обманчивую уверенность.
**Закрытий нет** в этом коммите — по правилу §2.2.
## 5. Реализация v8.5 (07.05.2026, вечер)
@@ -2161,6 +2161,12 @@
- **Сводный список закрытий с импактом на schema/narrative** — новый §13.10 «Закрытия аудита C — 27 решений (07.05.2026)».
- **8 P0 разблокированы** для триггера фазы 1 (`composer create-project`). Архитектурные следствия: `projects.assignment_strategy='manual'` (Биз-17), `deals.duplicate_of_id` + 24ч-дедуп (Биз-19), e2e-тест `SET LOCAL`+PgBouncer в спринте 1 (CTO-13), `WITH CHECK` + REVOKE на saas-таблицах (OPEN-И-14), append-only triggers + hash-chain + роль `crm_audit_writer` на 5 audit-таблицах (OPEN-И-15), Sentry whitelist+regex для 6 паттернов ПДн (OPEN-И-16), OIDC+JIT+break-glass для admin SSO (OPEN-И-13), TTFR-15-мин default (Биз-18).
- **12 P1 закрытий** — все попадают в фазы 1–2: UTM-поля (CTO-14), `project_user_assignments` (CTO-16), TTL ключей 365 дней (OPEN-И-17), DNS-rebinding pin-IP (OPEN-И-18), hard-limit `api_keys` ≤10 (OPEN-И-19), presigned URL 24ч + триггер audit (OPEN-И-20), Nginx rate-limit + Yandex SmartCaptcha + disposable-blacklist (OPEN-И-21), two-person impersonation для `compliance` (CTO-15, Ю-9), Telegram-бот в спринте 9 (Биз-20), generic outbound `marketing.conversion` (Биз-21), простой scoring (Биз-22).
🔴 **ЗАМЕР 06.08.2026 (Р110): «two-person impersonation для `compliance` (CTO-15, Ю-9)» закрыто как
решение и не построено.** Ни запрета входа под клиента при поднятом `processing_restricted`, ни
требования второго одобряющего в портале нет; класса `SaasAdminAuthService` не существует
(`grep -rn "class SaasAdminAuthService" app/app/` — пусто). Подробности — пометка к §13.10.2.
- **7 P2 закрытий** — все либо мелкие schema-добавки (`deals.region_code/city` Биз-23, закомментированный DDL `call_recordings` OPEN-И-26), либо infra-задачи (Yandex KMS per-tenant DEK OPEN-И-22, `pg_anonymizer` процедура OPEN-И-24), либо cron-регламенты (Биз-24, OPEN-И-25), либо документная фиксация роли `crm_audit_writer` (OPEN-И-23, уже взято в P0 OPEN-И-15).
- **Сводка §0:** 67 ✅ / 5 🟦 / 5 ⏸ (1 P0 Б-1, 4 P1 Диз-1/Диз-3/DO-2/DO-4 — все ждут Б-1 или у Claude). **Все P2 продуктовые закрыты повторно.**
- **Импакт на schema/narrative:** schema.sql v8.4 → **v8.5** (12 column-add + 2 новых таблицы + 5 audit-триггеров + 1 новая роль + WITH CHECK на 2 политики + REVOKE на 5 таблиц), narrative v8.4 → **v8.5** (правки §10 дедуп, §12.5.5 TTFR, §22.7 SSO, §22.X Sentry PII, §23.10 admin flow). Реализация — отдельный коммит после фиксации решений.
@@ -2380,6 +2386,13 @@
### 8.1. Прил. Д (Workflow_pd_subject_requests)
🔴 **ЗАМЕР 06.08.2026 (Р110): галочка у OPEN-Д-1 означает «решили», а не «работает».** Решено было
завести поле `processing_restricted` — поле завели. Но ограничения обработки, ради которого поле
заводилось, в портале нет: флаг ставится, хранится и не запрещает ни одной операции. Класса
`ProcessingRestrictedException` в проекте не существует — `git ls-files | grep -i "ProcessingRestriction"`
даёт пустой ответ. Галочку не снимаем — решение принято и остаётся в силе; пометка говорит только
о том, что под ней пусто.
| ID | Вопрос | Статус |
|----|--------|--------|
| ✅ **OPEN-Д-1** | **Ограничение обработки ПДн — BOOLEAN флаг `processing_restricted` в `pd_subject_requests`** (ст.21 152-ФЗ) | Закрыто 04.05 |
@@ -2725,6 +2738,14 @@ reminders {
- **§22.12 антипаттерны оригинала** — явная защита от DOM-plaintext credentials.
- **Atomic billing**`gateway_idempotence_key UNIQUE` + `lockForUpdate` + `late_webhook_ignored` — двойного списания не будет.
- **`processing_restricted` BOOLEAN** (152-ФЗ ст.21 ч.5) — реализовано через guard-исключение.
🔴 **ЗАМЕР 06.08.2026 (Р110): слово «реализовано» здесь неверно — не реализовано ничего.** Это самая
опасная строка в списке: остальные пункты говорят «сделано хорошо», и читатель разумно принимает
на веру и эту. На деле есть только столбец в таблице. Охранника нет: класса
`ProcessingRestrictedException` в проекте не существует — `git ls-files | grep -i "ProcessingRestriction"`
даёт пустой ответ. Ни один сервис флаг не читает и никому по нему не отказывает. Строка оставлена
дословно, чтобы было видно, откуда пошло заблуждение.
- **Four-eyes** > 50 000 ₽ + лимиты `support` 1000 ₽/24ч — anti-fraud для админов.
- **Изоляция тенантов 4 уровня** — сильнее, чем у HubSpot multi-tenant.
- **Kanban+DnD виртуализация** (§11) — на уровне Pipedrive.
@@ -2768,6 +2789,15 @@ reminders {
#### 13.10.2. P1 — 12 закрытий (фазы 1–2)
🔴 **ЗАМЕР 06.08.2026 (Р110): две строки этой таблицы — CTO-15 и Ю-9 — закрыты как РЕШЕНИЯ, но не
построены.** Обе обещают защиту человека, запретившего обработку своих данных: запрет входа сотрудника
в его карточку и обязательное одобрение вторым админом. Ни того, ни другого в портале нет. Единого
охранника `SaasAdminAuthService`, названного в обеих строках, не существует — ни файла, ни строки
(`grep -rn "class SaasAdminAuthService" app/app/` — пусто); `ImpersonationController`, который вход
под клиента и делает, в своей шапке пишет «на MVP не реализовано» и флаг `processing_restricted`
не читает; столбцы `second_approver_id` / `second_approval_at` заведены, но их никто не заполняет.
Галочки «закрыт» остаются: решения приняты. Пометка говорит лишь, что работа по ним не сделана.
| ID | Решение (вариант A) | Импакт |
|---|---|---|
| ✅ **Биз-20** | Telegram-бот для нотификаций — спринт 9 (после биллинга) | Narrative: §17 (нотификации) + roadmap. Schema (фаза 2): `users.telegram_user_id BIGINT NULL`, `tenants.telegram_bot_token TEXT NULL` (зашифровано) |