# Изменения OM_Lab_Agent

История до 1.7.5 — в `git log` (сообщения коммитов вида `v1.7.5: …`). Здесь ведём план и состав следующего релиза.

---

## 1.7.6 — план (не выпущено)

**Статус: состав накапливается. Сборка не запускается — Олег добавляет нововведения, релиз собирается после закрытия состава.**

Тема: **правило периметра данных доводится до работающего механизма, а не остаётся декларацией в промпте.**

Основание — аудит профиля ai-si 05.11.2026. Проверенное состояние:

| Что | Факт |
|---|---|
| `skills/productivity/docx/references/anonymization.md` | процедура полная: тело, таблицы, все `headerN/footerN.xml`, `docProps/core.xml`, `app.xml`, картинки; замена подчёркиваниями; верификация из 3 шагов. **Лежит только в профиле ai-si — в сборке файла нет** |
| `skills/productivity/docx/scripts/scan_open_pii.py` | работает (`--summary`, `--header`, `--json`, `--fail-on-open`). **Лежит только в профиле ai-si — в сборке файла нет** |
| `skills/ailab/anonymization-check` | лежит в сборке, **в профиль не ставится** — категории `ailab` в установленном профиле нет |
| `skills/engineering/ai-data-perimeter` | есть **только в профиле ai-si**, в сборке отсутствует → получателям не приедет |
| `profiles/<p>/hooks/` | **пусто** — принуждения нет |
| `SOUL.md` сборки | про обезличивание, периметр и заказчика — **ни слова** |

Отсюда формулировка «у нас обезличивание до отправки в модель» сегодня не подтверждается: правило записано в скилле, но срабатывает по триггеру и зависит от того, вспомнит ли модель его применить.

### 1. Один канон процедуры + скилл в сборку и в профиль

Проблема: одна и та же процедура описана в трёх местах — `docx/references/anonymization.md`, `ailab/anonymization-check`, `engineering/ai-data-perimeter`. Три редакции разъедутся.

Иерархия, которую фиксируем:

| Место | Роль |
|---|---|
| `engineering/ai-data-perimeter` | **канон**: правила периметра, проверка «соблюдается ли», порядок работы с документом заказчика, чек-лист и нормы (`references/perimeter-rules.md`) |
| `docx/references/anonymization.md` | техника правки DOCX-пакета + `scripts/scan_open_pii.py` — исполнение под каноном |
| `ailab/anonymization-check` | свести к тонкой ссылке на канон либо убрать; сейчас это третья редакция того же правила |

- Что: положить `engineering/ai-data-perimeter` в сборку (`dist/vasily/skills/engineering/`) — **в сборке этой категории нет вообще**, поэтому переносится каталог целиком, вместе с `references/perimeter-rules.md` и `templates/soul-perimeter-block.md`.
- Что, дополнительно (иначе канон ссылается на отсутствующий файл): перенести в сборку исполнение — `skills/productivity/docx/references/anonymization.md` и `skills/productivity/docx/scripts/scan_open_pii.py`. Оба сейчас есть только в профиле ai-si; в `dist/vasily/skills/productivity/docx/` их нет. Копировать без `__pycache__`.
- **Сделано 05.11.2026 — файлы в сборке:** `skills/engineering/ai-data-perimeter/` (SKILL.md, `references/perimeter-rules.md`, `references/hermes-intercept.md`, `templates/soul-perimeter-block.md`), `skills/productivity/docx/references/anonymization.md`, `skills/productivity/docx/scripts/scan_open_pii.py`. md5 совпадают с профилем ai-si, `__pycache__` не попал, синтаксис скрипта проверен.
- Проверить, что категории `engineering` и `ailab` доезжают до установленного профиля при установке и обновлении (`ailab` сейчас не доезжает — см. таблицу выше).
- Критерий приёмки: после чистой установки в профиле есть `skills/engineering/ai-data-perimeter/SKILL.md` + `references/perimeter-rules.md`; третьей редакции правила в профиле нет.
- Причина: скилл, лежащий только в профиле разработчика или только в сборке, в работе не участвует. Правило в SOUL.md на него ссылается — битая ссылка хуже отсутствия ссылки.

### 2. Правило порядка в `SOUL.md` — сведено с `ai-data-perimeter`

Сверено с каноном. SOUL не переписывает светофор и контуры (они в `references/perimeter-rules.md`), а закрывает то, что действует до первого чтения файла, и не противоречит трём правилам скилла: аудит вместо согласия (Правило 0), декларация ≠ контроль (Правило 1), агент сам канал (Правило 2).

Расхождения первой редакции, исправленные при сверке:

| Было в первой редакции | Что говорит скилл | Исправлено |
|---|---|---|
| не покрывала запрос «подтверди, что соблюдается» | Правило 0: это аудит по двум слоям, а не согласие | добавлены оба слоя и запрет ответа по одному |
| «не открывается, пока не решено» — без статуса данных | документ до классификации считается красным | добавлен статус «считается красным» |
| правило жило само по себе | процедура — в скилле, SOUL задаёт только порядок | добавлена ссылка на канон, дублирование убрано |
| не было стоп-сигнала | «маркеров нет» = скорее всего оригинал; ноль находок ≠ чисто | добавлено |
| не сказано, кто обезличивает | обезличивание — работа человека, спорное агент не решает | добавлено |

Формулировка для вставки в `SOUL.md` (в сборку и в рабочий профиль):

```
## Периметр данных

Правила, светофор и процедура — скилл `ai-data-perimeter`. Здесь — то, что
действует до первого чтения файла.

- Документ заказчика (ТЗ, договор, переписка, форма, ИД) до классификации считается
  красным. Инструментами чтения он не открывается, пока не решено, откуда берётся
  обезличенная копия: её даёт владелец или агент делает копию и читает только её.
- Оригинал не открывать «чтобы посмотреть объект». Таблица соответствия токенов и
  реальных значений не читается никогда — она и есть закрываемые данные.
- Обезличивание — работа человека по его таблице соответствий. Агент делает копию по
  технике `docx/anonymization.md`, только если об этом попросили, и не решает за
  человека, что закрывать в спорных случаях.
- Проверка полноты: `python scripts/scan_open_pii.py "<файл>" --summary`. Агент читает
  счётчики; полный отчёт со значениями — человеку, потому что находки и есть
  закрываемые данные.
- «Маркеров нет» — единственный стоп-сигнал: скорее всего, это оригинал. Ноль находок
  ≠ «документ чист». Формулировка: «шаблоны нашли N, открыто: …».
- «Документ обезличен» и «у нас это соблюдается» без проверки файлов — запрещены.
  На просьбу подтвердить меру проверяются два слоя: организационный (приказ,
  регламент, журнал) и технический (SOUL, установленные скиллы, hooks). Ответ по
  одному слою — ошибка в обе стороны.
- Если механизма принуждения нет — говорить это прямо: правило в промпте срабатывает
  по триггеру, а не по факту.
```

- Где: `SOUL.md` в сборке (приезжает всем получателям) + `profiles/<p>/SOUL.md` в рабочем профиле (иначе правило не действует у того, кто уже установлен).
- Критерий приёмки: правило есть в обоих файлах; на контрольной задаче («прочитай ТЗ и найди требования») агент сначала спрашивает про обезличенную копию, а не открывает оригинал.
- Оговорка для релиз-ноутов, честно: это по-прежнему правило в промпте. Принуждение — только hook/шлюз (п. 4).

### 3. Журнал применения ИИ — с записями, а не пустая форма

- Что: форма журнала + процедура, при которой запись появляется по факту задачи, а не заполняется задним числом «для проверки».
- Минимальный состав записи: дата · задача · сервис · контур · категория данных (🟢/🟡/🔴) · обезличивание выполнено (да/нет, чем подтверждено) · результат перепроверен человеком · кто.
- Куда писать: файл журнала в рабочей папке проекта (CSV/MD), запись — по факту задачи; при инциденте — отдельной строкой в журнал инцидентов.
- Критерий приёмки: после пилота на одной команде в журнале есть не менее 5 реальных записей, каждая с категорией данных и отметкой обезличивания; повторяющаяся операция закрывается одной строкой, без ручного заполнения таблицы каждый раз.
- Причина: в папке «AI безопасность» журнал присутствует только как пустая форма Регламента. Пустая форма — не контроль.

### 4. Перехват на исходящем запросе: маска + журнал, роли по типам документов

Проверено 05.11.2026 по установленной сборке: точка перехвата в Hermes существует и врезана в путь запроса.

- Плагин регистрирует middleware: `ctx.register_middleware("llm_request", fn)` — `hermes_cli/plugins.py:939`.
- Применяется перед вызовом провайдера: `agent/turn_api_request.py:156` → `apply_llm_request_middleware(...)`, и `api_kwargs` заменяются тем, что вернул плагин (`hermes_cli/middleware.py:88`).
- Middleware видит полный `request`, включая `messages`/`input`, и может переписать их или прервать вызов исключением.
- Парные точки: `tool_request` (аргументы инструмента до выполнения) и `llm_execution` (обёртка самого вызова провайдера).

**Решение Олега 05.11.2026: режим — маскировать и логировать.** Блокировка — только там, где маскирование бессмысленно. Причина: шаблоны ПДн дают ложные срабатывания на техническом тексте (номера СП и ГОСТ, ИНН поставщика в спецификации, кадастровые в выписке), а жёсткая блокировка валит нормальную работу и толкает её на личный телефон.

**Категория — не по шаблону, а по типу документа и роли.** Классификатор угадывает и ошибается; поэтому тип документа определяется до чтения, а подтверждает человек одним вопросом, и ответ живёт в метке сессии.

Три точки в одном плагине:

| Точка | Что делает |
|---|---|
| `tool_request` (`read_file`, DOCX/PDF-парсер, OCR) | определяет тип документа по имени файла, титулу и штампу; сверяет с ролью пользователя; ставит метку категории в сессии; при запрете — отказ **до** чтения файла, содержимое в модель не попадает |
| `llm_request` | маскирует по правилам категории из метки; пишет строку в журнал |
| `llm_execution` | возвращает реальные значения в ответе (демаскировка) |

Плагин работает только с содержимым запроса к модели. Ключи доступа, учётные данные и личные файлы он не читает и не меняет — это отдельный контур, к политике доступа к документам отношения не имеет.

Политика — файл в профиле (`policy.yaml`), правится без пересборки:

| Категория | Кому | Что входит | Режим |
|---|---|---|---|
| 🟢 зелёный | всем | открытые нормативы, методики, обезличенные тексты | без маски |
| 🟡 жёлтый | всем | ТЗ, реестры замечаний, переписка, локальные сметы, ВОР | маска + журнал |
| 🔴 красный | только роль «договорной пул» (несколько человек) | договоры, сводные сметы, акты, ИД, кадры, финансы | остальным — отказ с подсказкой, к кому идти |

Маскировать красное и отправлять нельзя: у договора маска убивает предмет — стороны, суммы, особые условия. Поэтому для красного единственный корректный ответ — не пускать.

**Два ограничения по доступу — согласованы с Олегом 05.11.2026:**

1. **Роль не даёт права отправить красное в облако.** По светофору красное не уходит наружу никому: договорной пул получает право открыть документ агенту (снять структуру, собрать обезличенную выписку), но отправка самого договора в облачную модель блокируется и им тоже. Пока локального контура нет, по договорам агент у договорного пула почти не работает — это цена честной схемы, а не недоделка.
2. **Роль вешается на учётку, а не на флаг в профиле.** Переключатель в том же профиле сотрудник поставит себе сам, и контроль фиктивный. Красный уровень — отдельный профиль или отдельная учётка у нескольких человек договорного пула.

**Источник ролей — `Единый докер USER.xlsx`** (лист USERS, колонка «роль», добавлена 05.11.2026), 11 учёток:

| Роль (как записана) | Кто | Категории | Отправка в облако |
|---|---|---|---|
| инженер ОВиК, ТЗ, ТУ, СТУ, проекты — 7 | Гришкевич, Воробей, Фролагина, Воронцов, Анушов, Кашина, Нечаев | 🟢🟡, ТЗ — всем | да, с маской |
| гл. бухгалтер (договоры, контракты, сметы, тендеры) — 1 | Хундадзе | 🟢🟡 + открытие красного | нет |
| совладелец директор (договоры, контракты, сметы, тендеры) — 2 | Швецов, Дуда | 🟢🟡 + открытие красного | нет |

Требования к колонке, без которых `policy.yaml` не собрать:

1. **Роль — машинный код** (`engineer` / `contracts` / `admin`), а не перечисление задач: «инженер ОВиК, ТЗ, ТУ, СТУ, проекты» не парсится и меняется от задачи к задаче. Описание оставить отдельной колонкой для человека.
2. **`policy.yaml` генерируется из этой таблицы скриптом**, а не правится руками: два источника правды разъедутся на первой же кадровой замене.
3. **Учётки со статусом `root` (TESTOFF) политику не обходят** — иначе одна учётка отменяет контроль. Для них отдельная запись в журнале с пометкой `admin`.
4. **Колонка «роль» приведена к кодам 05.11.2026:** столбец G — код (`engineer` — 7, `contracts` — 3, `admin` — 1), столбец H «роль — что делает» — исходное описание задач для человека. Дуда В. заведён (ключ выдан сотруднику, хэш в таблицу не сохранялся) — прежний пункт снят.
5. **`Единый докер USER.xlsx` — личная таблица Олега (OneDrive).** Источник ролей, но не рабочий файл сборки: агент её читает, не изменяет и не переписывает. `policy.yaml` генерируется из копии, снятой на момент подготовки релиза, — чтобы правка личной таблицы не ломала политику, а политика не правила личную таблицу.

- Критерий приёмки:
  1. Сотрудник без роли просит проверить договор → отказ до чтения, файл не открывается.
  2. ТЗ у любого сотрудника → уходит маской, в журнале строка, ответ приходит с реальными значениями.
  3. 20 запросов без ПДн → ни одной маски и ни одной блокировки.
  4. В журнале: пользователь, тип документа, категория, сколько замен, модель, время.
  5. Сотрудник с ролью договорного пула просит отправить договор в облачную модель → отказ: роль открывает документ агенту, но не отправку в облако.
- Остаточные риски, фиксируем честно: картинки и вложения не маскируются; PDF-сканы без текстового слоя требуют OCR до маски; путь «человек скопировал в браузер мимо агента» закрывается только шлюзом или DLP на периметре.

Порядок работ: сначала `tool_request` + политика + журнал в режиме «только запись» (ничего не маскируем, смотрим поток), затем включаем маску на жёлтом, и только после настройки правил — демаскировку ответа.

### 5. Состав из «OM_Lab — требования к следующей сборке»

Единый источник UI- и функциональных требований — `C:\Users\Admin\OneDrive\Документы\OM LAB\OM_Lab — требования к следующей сборке.md` (§0 — сводка, §1–§4 — тексты). Здесь только состав и статусы; требования не дублируются, при расхождении верен документ.

| № в документе | Пункт | Раздел | Статус на 05.11.2026 |
|---|---|---|---|
| 1 | Только разрешённые модели, персональный ключ, бренд агрегатора скрыт | §1 | готово к реализации |
| 2 | Один провайдер (шлюз), прямой `polza.ai` удалён, `discover_models` выключен + миграция для установленных | §3a | готово к реализации |
| 3 | Прогресс-бар лимита — по ключу в рублях, через шлюз | §1a | **блокер:** нужен `/v1/wallet` на шлюзе |
| 4 | Vision через шлюз тем же ключом | §1b | готово к реализации |
| 5 | Резерв proxyapi.ru, только автопереключение, пользователю не виден | §2 | **в объёме 1.7.6, делается при сборке** (работа шлюза, не клиента): ключ есть (797 моделей), группы и `fallbacks` заведены, но LiteLLM отклоняет переход по правам ключа → переделать на второй деплоймент в группе + `cooldown_time: 300` |
| 6 | Настройки: пять вкладок, «О программе» скрыть | §3 | готово к реализации |
| 6a | Вкладка «Боты» скрыта, Bot Mode остаётся в коде | §3b | решено 04.11.2026: только вкладка |
| 6b | Первое открытие после установки — тёмная тема | §3c | записано 04.11.2026 |
| 7 | Бот на звонок в Телемост | §4 | **блокер:** отдельная учётка Яндекс |
| 8 | Ключ привязан к машине: зачисление + `key_cmd`, статического ключа в `.env` нет | §1c | **сделано 05.11.2026** (см. ниже) |

- **Пункт 8 (привязка доступа к машине) — сделано 05.11.2026.** Шлюз: `gw-issuer` v2 — `/enrol` (обмен старого `sk-…` на зачисление), `/app-key` (сессия `as-…` на 300 с по `X-Install-Token`), `/resolve` (подмена сессии на ключ человека для nginx), `/revoke`, `/list`. nginx на `gw.ailabscrume.ru` получил `auth_request`: в LiteLLM уходит тот же ключ, что и раньше, поэтому лимиты и учёт не меняются (короткие ключи через `/key/generate` отвергнуты — каждый ключ это отдельная строка расхода и прогресс-бар §1a показывал бы ноль). Клиент: `gw-key.ps1` (DPAPI-конверты, коды 0/1/2/3/4, кэш при недоступном хабе), установщик зачисляет машину по почте и коду, пишет `key_cmd` вместо `key_env`, чистит `.env`, файл зачисления кладёт в `%LOCALAPPDATA%\AILabSCRUME` (вне дома агента). Проверено живьём: агент на временном профиле ответил моделью через `key_cmd` при пустом `.env`; отзыв зачисления → helper выходит с кодом 3. `ISSUER_STRICT=0` — статические ключи пока проходят насквозь; включать `1` после обновления всех машин.

- **Брендинг 1.7.6 (решение Олега 05.11.2026).** Название агента — `OM_Lab_Agent` (видимые строки: установщик, ярлык, описание задачи Планировщика, README/INSTALL/CHANGELOG, проверка самоназвания `$NamePat`). Лого на пустом чате — `assets/scrum-engineering.svg` заменён на логотип OM Lab (PNG 3032×1376 → 640 px внутри SVG, alt `OM_Lab_Agent`, ширина 280 px, прозрачность 0.85); рендерер пересобран. Иконка — `dist/setup/assets/om-lab.ico` (256 px). **Display-имя Electron НЕ меняем** (`product-identity.cjs`: `display: 'Hermes'`): оно задаёт `%APPDATA%\Hermes`, где лежат список чатов и настройки окна — переименование «потеряло» бы их у всех. **Триггер автообновления не трогаем** (остаётся `Daily 03:00 + 0…44 мин` с `-StartWhenAvailable`); поведение при выключенном ПК: обновление выполняется при первом включении/входе, но если в этот момент открыто окно `Hermes.exe` → «отложено» до следующего окна.

- **Что реально войдёт в 1.7.6:** пункты без внешних блокеров — 1, 2, 4, 6, 6a, 6b, **8** (плюс безопасность п.1–3 выше) **и пункт 5** (резерв, работа шлюза — по решению Олега 05.11.2026 переделка делается при сборке 1.7.6, а не сейчас).
- **Пункт 5 (резерв) — не клиентская работа, но входит в 1.7.6 и делается при сборке (решение Олега 05.11.2026).** Проверено 05.11.2026 на шлюзе: ключ `proxyapi.ru` живой, `GET /v1/models` → 200, 797 моделей, ограничений на ключе нет (подтверждено Олегом). Группы `proxy/<vendor>/<model>` и `router_settings.fallbacks` для всех алиасов уже заведены, **но переход не выполняется**: в логе `gw-llm` — `Fallback to proxy/google/gemini-4-argon was skipped because the caller is not authorized to call it`. Причина: у ключа сотрудника в `models` только его 9 моделей, `proxy/*` в них нет. Отсюда решение: не отдельная группа, а второй деплоймент внутри той же группы `<alias>/<vendor>/<model>` (список моделей у ключа не меняется — резерв остаётся невидимым), плюс явный `cooldown_time: 300` (сейчас «5 минут» держатся на дефолте роутера, в `router_settings` его нет).
- **Маппинг моделей — решено Олегом 05.11.2026.** 7 из 9 моделей совпадают с proxyapi по имени один в один. Решения по двум:
  - `anthropic/claude-sonnet-5.5` → резерв `anthropic/claude-sonnet-5-5` (у proxyapi дефис вместо точки);
  - `openai/gpt-6.1-sol-pro` → на proxyapi отсутствует, резерв = **деградация на `openai/gpt-6.1-sol`** (подтверждено Олегом).
  Технически это второй деплоймент в той же группе: `model_name` = `<alias>/<vendor>/<model>` (то, что видит ключ), `litellm_params.model` = фактическая модель proxyapi, `api_base` = `https://api.proxyapi.ru/v1`. Группа и список моделей у ключа не меняются.
- **Переносятся** 3, 7 — собираются, когда закроются блокеры; тогда же поднимается номер (1.7.7 или следующий по порядку).
- Не путать: «п.4» здесь — vision, а «§4» документа — бот на звонок; сквозная нумерация в документе своя.
- Перед сборкой закрыть открытые вопросы §5 документа: префиксы `testoff/…` в названиях моделей, точный перечень вкладок настроек, маппинг моделей proxy.

### Что НЕ входит в 1.7.6

- Локальная модель / RAG (Ollama + Open WebUI): решения нет, железа нет. Ценность такого контура — только на красной категории, и он снимает риск утечки ценой качества на русском техническом тексте.
- Заполнение Регламента и приказа ООО «СКРАМ ИНЖИНИРИНГ» — организационный слой, отдельная работа вне сборки.

---

## Подготовка релиза 1.7.6 (prepare release)

Мероприятия по этапам. Галочка ставится по факту проверки, а не по факту «сделали».

### Этап 1. Закрыть состав

- [ ] Олег закрывает список нововведений; всё, что «ещё думаю», уезжает в 1.7.7, а не висит в 1.7.6.
- [ ] У каждого пункта есть критерий приёмки — у пп. 1–4 есть.
- [ ] По п.4 зафиксировано, где живёт `policy.yaml`: общий файл на диске (план) или политика на шлюзе; шлюз остаётся вторым рубежом для тех, кто ходит мимо агента.
- [ ] Роли сняты из личной таблицы Олега на момент подготовки; таблица не изменяется.

### Этап 2. Реализовать и проверить

- [ ] Скилл `engineering/ai-data-perimeter` в сборке и в установленном профиле (п.1). *В сборке лежит с 05.11.2026; осталась проверка установленного профиля после чистой установки.*
- [ ] Раздел «Периметр данных» в `SOUL.md` сборки и профиля (п.2).
- [ ] Журнал применения: форма + запись по факту задачи (п.3).
- [ ] Плагин, шаг 1: `tool_request` + `policy.yaml` + журнал в режиме «только запись», без маски (п.4).
- [ ] Плагин, шаг 2: маска на жёлтом.
- [ ] Плагин, шаг 3: демаскировка ответа в `llm_execution`.
- [ ] Прогнать 5 критериев приёмки п.4 на живых документах: ТЗ, реестр замечаний, договор.
- [ ] Проверить на чистой установке, а не только в рабочем профиле: иначе скилл и политика не доедут — на этом уже спотыкались.

### Этап 3. Документы к релизу

- [ ] `CHANGELOG.md`: перенести 1.7.6 из «план» в «выпущено», поставить дату и номер сборки.
- [ ] Release notes для получателей: что вошло, что не вошло, честная оговорка про остаточные риски — картинки и вложения не маскируются, сканы без текстового слоя требуют OCR до маски, браузер мимо агента закрывается только шлюзом.
- [ ] Инструкция сотруднику: что такое категория документа, почему спросят один раз, что делать при отказе по договору.
- [ ] Инструкция договорному пулу: почему роль не даёт отправку в облако и что делать вместо этого.
- [ ] `README.md`: раздел безопасности — новые правила и роли.

### Этап 4. Сборка

- [ ] Состав закрыт; пункты 1–3 (безопасность) проверены на чистой установке, а не только в рабочем профиле, и пункты 1, 2, 4, 6, 6a, 6b из п.5 (UI) проверены там же.
- [ ] `distribution.yaml` → `version: 1.7.6`; `README.md` → «Текущая версия: 1.7.6».
- [ ] Коммит с сообщением `v1.7.6: <главное изменение>` + тег.
- [ ] Сборка bundle, проверка по разделу «Проверка перед передачей» в README.
- [ ] Прогон на чистой машине: установка → скилл виден → правило в SOUL → журнал пишется → маска срабатывает.

### Этап 5. Откат и первые две недели

- [ ] Предыдущий bundle (1.7.5) сохранён как точка отката.
- [ ] Записано, как выключается плагин без пересборки (флаг в `config.yaml`) — маска не должна требовать релиза, чтобы её отключить при массовых ложных срабатываниях.
- [ ] Назначен ответственный за журнал инцидентов на первые две недели после выдачи.
- [ ] Через две недели — разбор: сколько записей в журнале, сколько отказов по красному, сколько ложных масок.
