Metadata-Version: 2.5
Name: odata1c-gate
Version: 0.3.0
Summary: Локальный MCP-шлюз к OData 1С:Предприятие с гейтом псевдонимизации
Project-URL: Homepage, https://github.com/Romandredan/odata1c-gate
Project-URL: Repository, https://github.com/Romandredan/odata1c-gate
Project-URL: Documentation, https://github.com/Romandredan/odata1c-gate/blob/main/docs/install.md
Project-URL: Changelog, https://github.com/Romandredan/odata1c-gate/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/Romandredan/odata1c-gate/issues
Author: Roman Danilov
License: MIT
License-File: LICENSE
Keywords: 1c,1c-enterprise,claude-code,mcp,odata,personal-data,pseudonymization
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: Russian
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database :: Front-Ends
Classifier: Topic :: Security
Requires-Python: >=3.12
Requires-Dist: ahocorasick-rs>=0.22
Requires-Dist: httpx2>=2.12
Requires-Dist: httpx>=0.27
Requires-Dist: lxml>=5.2
Requires-Dist: mcp>=2.2
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0
Requires-Dist: ruamel-yaml>=0.18
Requires-Dist: snowballstemmer>=2.2
Requires-Dist: uvicorn>=0.30
Provides-Extra: keyring
Requires-Dist: keyring>=25.0; extra == 'keyring'
Description-Content-Type: text/markdown

# odata1c-gate

[![CI](https://github.com/Romandredan/odata1c-gate/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Romandredan/odata1c-gate/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/odata1c-gate.svg)](https://pypi.org/project/odata1c-gate/)
[![Release](https://img.shields.io/github/v/release/Romandredan/odata1c-gate.svg)](https://github.com/Romandredan/odata1c-gate/releases)
[![Last commit](https://img.shields.io/github/last-commit/Romandredan/odata1c-gate/dev.svg)](https://github.com/Romandredan/odata1c-gate/commits/dev)
[![AGENTS.md](https://img.shields.io/badge/AGENTS.md-compatible-brightgreen.svg)](https://agents.md)

[![Claude Code](https://img.shields.io/badge/Claude%20Code-plugin-d97757.svg)](https://code.claude.com/docs/en/plugins)
[![MCP](https://img.shields.io/badge/MCP-server-6e56cf.svg)](https://modelcontextprotocol.io)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/Romandredan/odata1c-gate/blob/main/LICENSE)
[![Python](https://img.shields.io/badge/python-%E2%89%A53.12-3776ab.svg)](https://www.python.org/)
[![1C:Enterprise](https://img.shields.io/badge/1C%3AEnterprise-8.3-ffd200.svg)](https://v8.1c.ru/)
![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey.svg)

**Claude работает с живой базой 1С и не видит персональных данных.** odata1c-gate — локальный
MCP-шлюз между Claude Code и стандартным OData-интерфейсом 1С:Предприятие 8.3. Модель читает
справочники, документы и регистры и может записывать в базу с вашего подтверждения, а ИНН, счета,
паспорта, телефоны и, на выбранном уровне, названия организаций и ФИО заменяются токенами до того,
как ответ попадёт к модели и к её провайдеру. Реальные значения остаются на вашей машине.

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

Один пользователь, одна машина, несколько сессий агентов и несколько баз 1С одновременно. Основной
клиент — Claude Code; как работают другие клиенты MCP, сказано в разделе [«Ограничения»](#ограничения).

> **English.** A local MCP gateway between Claude Code and the standard OData interface of
> 1C:Enterprise 8.3. The model reads catalogs, documents and registers, and can write to the
> database after human confirmation, while personal data (tax IDs, bank accounts, passports, phone
> numbers and, optionally, company and person names) is replaced with deterministic tokens before
> it reaches the model or its provider. Tokens still work for search, filtering and joins; only the
> machine owner can reveal a real value, from the terminal. Ships as a Claude Code plugin with
> ready-made query recipes for typical 1C configurations (Trade, Accounting, Payroll). Docs and
> messages are in Russian.

<details>
<summary><b>Содержание</b></summary>

- [Как это выглядит](#как-это-выглядит)
- [Возможности](#возможности)
- [Быстрый старт](#быстрый-старт)
- [Настройка](#настройка)
- [Как это устроено](#как-это-устроено)
- [Командная строка](#командная-строка)
- [Рецепты](#рецепты)
- [Ограничения](#ограничения)
- [Документы](#документы)
- [Лицензия](#лицензия)

</details>

## Как это выглядит

Вопрос в Claude Code и ответ модели. Данные вымышлены; номера, даты и суммы приходят как есть,
название контрагента и его ИНН — токенами:

```text
Вы:      Кто поставщик последнего поступления и какие у него ещё документы за сентябрь?

Claude:  Последнее поступление — № 0000-000412 от 14.09.2026 от [[org:16]]
         (ИНН [[inn:M4T2Q9XZ7K]]). У этого контрагента за сентябрь ещё два документа:
           № 0000-000388 от 03.09.2026    184 500,00 ₽
           № 0000-000401 от 09.09.2026     42 120,00 ₽
```

Токен работает как значение: на просьбу «покажи все документы [[org:16]] за год» шлюз сам
подставит в запрос к 1С реальное название, а ответ снова придёт в токенах. Реальное значение за
токеном видите только вы, в своём терминале:

```text
$ odata1c reveal "[[inn:M4T2Q9XZ7K]]"
7701234567
```

## Возможности

| | Возможность | Что это даёт |
|---|---|---|
| 🔒 | Модель не видит персональные данные | реквизиты, названия организаций и ФИО приходят к модели токенами |
| 🔎 | Защита не мешает работе | по токену можно искать, отбирать и связывать записи |
| ✍️ | Безопасная запись в 1С | превью, подтверждение человеком, откат и журнал |
| 🎚️ | Свои правила для каждой базы | рабочая база закрыта и только для чтения, база разработки открыта |
| 📋 | Готовые ответы на типовые вопросы | рецепты для УТ, БП и ЗУП и свои рецепты из разговора |
| 🔌 | Подключение базы одной командой | остальное модель делает сама, пароль она не видит |
| 🧩 | Доработанная конфигурация под защитой | свои правила для нетиповых объектов и реквизитов |

### 🔒 Модель не видит персональные данные

ИНН, КПП, счета, карты, СНИЛС, паспорта, телефоны, почта и адреса физических лиц заменяются
токенами вида `[[inn:M4T2Q9XZ7K]]` до того, как ответ 1С попадёт к модели и к её провайдеру. На
рабочей базе токенами приходят и названия организаций с ФИО — в полях и в свободном тексте
комментариев. Для типовых УТ, БП и ЗУП ничего настраивать не нужно: при подключении базы шлюз сам
определяет по её метаданным, какие поля скрывать.

> **Как включить.** Защита включена сразу; её уровень задаёт роль базы или ключ `--gate` команды
> `odata1c base add`. Что именно скрыто в базе, показывает `odata1c policy show <база>`. Подробнее —
> в разделе [«Что видит модель, а что нет»](#что-видит-модель-а-что-нет).

### 🔎 Защита не мешает работе

Одно значение всегда даёт один и тот же токен. Поэтому модель находит контрагента по токену его
ИНН, отбирает его документы, связывает записи из разных справочников и ведёт разговор, так и не
узнав реального значения: шлюз подставляет его сам, внутри, перед запросом к 1С. Реальное значение
за токеном можете увидеть только вы, в своём терминале.

> **Как пользоваться.** Ничего включать не нужно. Раскрыть токен — `odata1c reveal "[[inn:M4T2Q9XZ7K]]"`.

### ✍️ Безопасная запись в 1С

Модель может создавать и изменять объекты, проводить и распроводить документы, ставить пометку
удаления и записывать независимые регистры сведений. Сама она при этом ничего не пишет: готовит
изменение и показывает превью, а в 1С оно уходит только после вашего подтверждения в диалоге
Claude Code — реплика «да» в чате подтверждением не считается. Каждую запись можно откатить, а
история хранится в журнале.

> **Как включить.** Умолчания задаёт роль базы — [таблица ролей](#роли-баз). У отдельной базы
> запись включает или выключает поле `write` в `bases.yaml`, а что именно разрешено — раздел
> `permissions` той же записи (образец —
> [bases.example.yaml](https://github.com/Romandredan/odata1c-gate/blob/main/src/odata1c/templates/bases.example.yaml)).

### 🎚️ Свои правила для каждой базы

Рабочая база закрыта полностью и доступна только для чтения, тестовая скрывает реквизиты, база
разработки открыта — уровень защиты и права на запись у каждой базы свои. Внутри базы можно скрыть
от модели объект целиком, открыть поле, которое не нужно прятать, или закрыть то, что шлюз
пропустил. Изменения действуют со следующего запроса, перезапуск не нужен.

> **Как настроить.** Роль задаётся при подключении (`--role prod|test|dev`, что она означает —
> [таблица ролей](#роли-баз)). Правила внутри базы — командами `odata1c policy hide | open | set`
> (раздел [«Командная строка»](#командная-строка)); они пишут файл `policy.yaml` (образец —
> [policy.example.yaml](https://github.com/Romandredan/odata1c-gate/blob/main/src/odata1c/templates/policy.example.yaml)).

### 📋 Готовые ответы на типовые вопросы

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

> **Как включить.** Укажите конфигурацию при подключении: `odata1c base add <база> --recipes ut`
> (или `bp`, `zup`). У уже подключённой базы — поле `config` её записи в `bases.yaml`. Подробнее —
> в разделе [«Рецепты»](#рецепты).

### 🔌 Подключение базы одной командой

От вас — одна команда в терминале, где вы вводите пароль 1С; всё остальное модель делает сама.
Пароль она не видит.

> **Как начать.** Скажите в Claude Code «подключи базу 1С». Что произойдёт дальше и как то же
> сделать вручную — в разделе [«Подключение базы»](#подключение-базы).

### 🧩 Доработанная конфигурация под защитой

Шлюз знает типовые объекты УТ, БП и ЗУП. Если в вашей конфигурации есть свои справочники,
регистры или реквизиты с персональными данными, опишите их — и шлюз будет скрывать их так же, как
типовые. Одно описание действует на всех базах, где есть названные объекты.

> **Как включить.** Создайте в каталоге `~/.claude/odata1c/gate/` файл, например `my-config.yaml`,
> по образцу [common.yaml](https://github.com/Romandredan/odata1c-gate/blob/main/src/odata1c/templates/gate/common.yaml)
> и назначьте полям классы защиты. Затем выполните `odata1c reindex <база>`.
>
> ```yaml
> version: 1
> fields:
>   Catalog_МойСправочник.НомерПаспорта: doc   # поле одной сущности
> names:
>   ФИОКлиента: person                          # поле с таким именем в любой сущности
> ```

**Кроме того:**

- несколько баз в одном разговоре — рабочие, тестовые, разных конфигураций;
- поиск справочника, документа или регистра по примерному названию среди тысяч объектов
  конфигурации;
- текст из полей 1С модель считает данными, а не командами, — запись всё равно требует вашего
  подтверждения;
- проверка окружения одной командой `odata1c doctor`, без вывода паролей;
- чтение из других клиентов MCP (Cursor, VS Code, Claude Desktop), запись в них по умолчанию
  выключена.

## Быстрый старт

### Что нужно

- [`uv`](https://docs.astral.sh/uv/) — он сам поставит подходящий Python — и Claude Code;
- база 1С:Предприятие 8.3, опубликованная на веб-сервере со стандартным интерфейсом OData (см. ниже);
- отдельный пользователь 1С с правами только на нужные данные — например `odata_user`.

**Подготовка 1С.** Стандартный интерфейс OData по умолчанию выключен, и включают его в два шага.

1. **Опубликовать интерфейс.** При публикации базы на веб-сервере (в конфигураторе:
   «Администрирование» → «Публикация на веб-сервере») отметьте «Публиковать стандартный интерфейс
   OData».
2. **Включить объекты в его состав.** Шлюз видит только объекты, включённые в состав стандартного
   интерфейса OData. В типовых конфигурациях на БСП для этого есть форма «Настройка стандартного
   интерфейса OData» в разделе «Администрирование»; в любой конфигурации то же делает метод платформы
   `УстановитьСоставСтандартногоИнтерфейсаOData()`. Если состав потом изменится, выполните
   `odata1c reindex <база>`.

Проверка: откройте в браузере `https://server/base/odata/standard.odata/` под пользователем 1С —
должен прийти перечень сущностей.

### Установка

Три команды в терминале:

```text
claude plugin marketplace add Romandredan/odata1c-gate
claude plugin install odata1c@odata1c-gate
uv tool install odata1c-gate
```

Первые две ставят плагин Claude Code: MCP-сервер шлюза, четыре навыка, хук подтверждения записи и
агента-следователя. Версия пакета закреплена в `plugin/.mcp.json`, поэтому `claude plugin update
odata1c` обновляет и плагин, и шлюз.

Третья ставит отдельно командную строку — она нужна владельцу базы, а не модели (описать базу,
собрать индекс, раскрыть токен, править политику гейта). После неё команда `odata1c` доступна в
терминале; без установки то же самое запускается как `uvx --from odata1c-gate odata1c <команда>`.

Подробности, Linux, обновление и разбор типовых сбоев —
[docs/install.md](https://github.com/Romandredan/odata1c-gate/blob/main/docs/install.md).

### Подключение базы

Откройте Claude Code и скажите: **«подключи базу 1С»**. Навык `odata1c-setup` спросит, как назвать
базу и какая у неё роль, и даст одну команду для вашего терминала. Команда запросит адрес
публикации, пользователя 1С и пароль. Пароль вводится только там, модель его не видит. После
этого Claude сам построит индекс метаданных, проверит, что база отвечает, и расскажет, что в ней
защищено.

То же самое можно сделать вручную:

```text
odata1c base add ut_test --role test --recipes ut   # спросит адрес, подпись, пользователя, пароль
odata1c reindex ut_test                             # построить индекс метаданных
```

Первая команда сама создаёт домашний каталог шлюза, отдельный `odata1c init` не нужен. Если что-то
не работает, `odata1c doctor` покажет, где именно: `uv`, настройки, базы, шлюз, Claude Code.

Адрес базы — это адрес публикации 1С, тот же, по которому открывается веб-клиент (например
`https://server/base`); окончание `/odata/standard.odata/` шлюз допишет сам. Пароль вводится в
терминале и на экране не отображается; он ложится в `bases.yaml` домашнего каталога открытым
текстом под правами владельца — сознательное решение: файл не покидает диск владельца, это не
сетевой секрет.

Первый реиндекс долгий: у типовой УТ `$metadata` — это около 17 МБ описания и больше семи тысяч
сущностей. Дальше индекс пересобирается, только если у публикации изменилась контрольная сумма
`$metadata`.

### Первые вопросы

- «какие базы 1С мне доступны?» — модель вызовет `odata1c_bases`;
- «найди справочник контрагентов и покажи состав его полей» — `odata1c_find_entity`,
  затем `odata1c_describe_entity` с классами гейта у каждого поля;
- «возьми любого контрагента и покажи его пять последних заказов клиента» — `odata1c_query`;
  название контрагента придёт токеном, номера и суммы документов — как есть.

Что модель видит и в каком порядке ходит — навык `odata1c` из плагина; справочные темы об
устройстве OData 1С, токенах, политике и протоколе записи — тул `odata1c_info`.

## Настройка

### Роли баз

Роль базы задаёт умолчания записи и гейта:

| Роль | Уровень гейта | Запись | Проведение документов | Пометка удаления | Удаление записей независимых регистров | Лимит коммитов за 10 минут |
|---|---|---|---|---|---|---|
| `prod` | `identifiers+names` | нет | да | да | нет | 20 |
| `test` | `identifiers` | да | да | да | нет | 50 |
| `dev` | `off` | да | да | да | да | без лимита |

Роль — только набор умолчаний: любое поле записи базы его перекрывает (например, `role: prod` +
`write: true` — боевая база с разрешённой записью). Флаги записи из последних четырёх столбцов
действуют, только когда запись включена (`write: true`) — у базы без записи они значения не имеют.
Все эти поля, включая уровень гейта, задаются для каждой базы отдельно: у боевой и тестовой базы
уровни могут быть разными. Уровень при подключении задаёт ключ `odata1c base add --gate`, а
действующие роль и уровень каждой базы показывает `odata1c base list`.

Готовая запись базы выглядит так (адрес, пользователь и пароль — плейсхолдеры):

```yaml
bases:
  ut_test:
    label: УТ 11, тестовая
    url: https://server/base
    user: odata_user
    password: qwerty123
    role: test
    config: ut
```

### Файлы настроек

Все настройки шлюза хранятся в домашнем каталоге `~/.claude/odata1c/`. Создавать эти файлы
вручную не нужно: каждый появляется при выполнении соответствующей команды и уже содержит
закомментированный образец всех допустимых полей с пояснениями. Те же образцы лежат в репозитории,
в каталоге `src/odata1c/templates/`, и по ним удобно заранее посмотреть, что и как настраивается.

| Файл в домашнем каталоге | Что в нём настраивается | Когда создаётся | Образец в репозитории |
|---|---|---|---|
| `bases.yaml` | перечень баз: адрес публикации, пользователь и пароль 1С, подпись, роль, уровень защиты, разрешения на запись | `odata1c init` или первый запуск шлюза; записи добавляет `odata1c base add` | [bases.example.yaml](https://github.com/Romandredan/odata1c-gate/blob/main/src/odata1c/templates/bases.example.yaml) |
| `daemon.yaml` | порт шлюза, лимиты, поведение с клиентами, которые не умеют подтверждать запись | `odata1c init` или первый запуск шлюза | [daemon.example.yaml](https://github.com/Romandredan/odata1c-gate/blob/main/src/odata1c/templates/daemon.example.yaml) |
| `bases/<база>/policy.yaml` | что именно скрывать в этой базе: скрытые сущности, открытые поля, собственные классы защиты | `odata1c base add` | [policy.example.yaml](https://github.com/Romandredan/odata1c-gate/blob/main/src/odata1c/templates/policy.example.yaml) |
| `bases/<база>/recipes.yaml` | рецепты этой базы | `odata1c base add` с ключом `--recipes` | [recipes/ut.yaml](https://github.com/Romandredan/odata1c-gate/blob/main/src/odata1c/templates/recipes/ut.yaml) |
| `recipes/<конфигурация>/<имя>.yaml` | библиотека рецептов, общая для баз одной конфигурации | сохранением рецепта в разговоре с моделью | пример в разделе [«Рецепты»](#рецепты) |
| `gate/<имя>.yaml` | правила защиты для доработанной конфигурации: какие поля нетиповых объектов скрывать и какие ложные срабатывания снять; действуют на любой базе, где есть названные объекты | вручную, если конфигурация доработана | [common.yaml](https://github.com/Romandredan/odata1c-gate/blob/main/src/odata1c/templates/gate/common.yaml) (формат описан в его заголовке) |

Чаще всего правится `bases.yaml`. В записи базы меняют подпись (`label`), по которой модель
выбирает базу, роль (`role`), разрешение записи (`write`), уровень защиты (`gate.mode`), состав
разрешённых операций (`permissions`) и конфигурацию 1С (`config`), от которой зависит библиотека
рецептов. Изменения в `bases.yaml` и `policy.yaml` действуют со следующего запроса модели,
перезапуск шлюза не требуется. Политику защиты надёжнее менять не в редакторе, а командами
`odata1c policy`: они проверяют имена сущностей и полей по метаданным базы.

Остальное содержимое домашнего каталога служебное, и править его не следует. Это
`policy.auto.yaml` (разметка защищаемых полей, которую шлюз строит сам по метаданным базы),
`launcher.key`, файлы `*.sqlite` (индекс метаданных, словарь токенов, журнал записей) и каталог
`logs/`.

**Файлы правит владелец, не модель.** Модель их штатно не читает и не правит — хук плагина
`PreToolUse` перехватывает такие обращения к домашнему каталогу шлюза и требует решения человека.
Это дополнительная защита, а не единственная: реальные значения и пароль 1С не выходят через MCP
ни при каком обращении. Подробнее —
[docs/install.md](https://github.com/Romandredan/odata1c-gate/blob/main/docs/install.md).

## Как это устроено

### Что видит модель, а что нет

**Что уходит модели.** Структура базы (сущности, поля, ключи, навигация) и данные, прошедшие
гейт. Номера и даты документов, суммы, количества, коды, GUID, значения перечислений не
защищаются ни на одном уровне — без них работа с базой теряет смысл. Названия организаций и ФИО
(классы `org` и `person`) и защищаемые реквизиты — ИНН, КПП, ОГРН, счета, БИК, карты, СНИЛС,
регистрационные номера в СФР, документы, телефоны, почта, даты рождения, адреса — приходят
токенами `[[type:tail]]` по правилам политики базы. Уровень задаётся на базу: `off` — гейт
выключен, `identifiers` — реквизиты, `identifiers+names` — реквизиты плюс названия и ФИО.

**Что не уходит никогда.** Реальные значения защищаемых классов не выходят через MCP ни в одном
ответе: ни в данных, ни в текстах ошибок 1С, ни в превью записи, ни в журнале, ни в
`odata1c_raw_get`, ни в вопросах подтверждения. Это инвариант, а не тест: последний проход по
готовому ответу делает страж утечек — он ищет в сериализованном ответе известные словарю значения
и заменяет их токенами, если что-то прошло мимо гейта. Учётные данные 1С модель не получает:
пароль не покидает домашнего каталога вовсе, имя пользователя 1С не попадает ни в один ответ, а
адрес публикации базы не возвращает ни один тул. О самой базе модель узнаёт ровно то, что
перечисляет `odata1c_bases`: имя, подпись, роль, уровень гейта, разрешена ли запись, состояние
индекса (собран ли, когда, сколько сущностей) и конфигурацию 1С, к которой база отнесена.
Единственное исключение по адресу — текст сетевого сбоя, в который его может вписать HTTP-клиент.
Раскрыть токен может только владелец машины, командой `odata1c reveal` в своём терминале.

### Архитектура

Два процесса из одного пакета: **демон** (`odata1c daemon`) — единственный на машину, в нём
работают все компоненты из таблицы ниже; **лаунчер** (`odata1c mcp`) — тонкий stdio-процесс на
сессию, который поднимает демон при необходимости и проксирует ему вызовы. Собственной логики у
лаунчера нет.

![Архитектура odata1c-gate: сессии Claude Code через лаунчеры обращаются к единственному демону; гейт в демоне делит поток на токены со стороны модели и реальные значения со стороны 1С; владелец настраивает шлюз из своего терминала, хук не пускает модель к файлам шлюза](https://raw.githubusercontent.com/Romandredan/odata1c-gate/main/docs/images/architecture.svg)

Гейт стоит в демоне между тулами и клиентом 1С: всё, что левее пунктирной границы, видит только
токены, реальные значения живут правее неё и в словаре. Настройки и учётные данные лежат в
домашнем каталоге шлюза; их правит владелец из своего терминала, а модель к этим файлам не
допускается.

| Компонент | Что делает | На чём построен |
|---|---|---|
| Тулы MCP | принимают вызовы модели: чтение, запись, рецепты, справка по OData 1С | официальный SDK `mcp`, MCP Streamable HTTP на `127.0.0.1:7171` |
| Гейт | назначает полям классы защиты; в ответе 1С заменяет значения токенами, в запросе — токены значениями; страж утечек проверяет каждый готовый ответ последним | классы — из авторазметки по метаданным, каталога правил и `policy.yaml`; реквизиты распознаются регулярными выражениями с проверкой контрольных сумм; собственный разбор `$filter`; страж ищет все значения словаря сразу алгоритмом Aho–Corasick (`ahocorasick_rs`) |
| Словарь | хранит пары «значение ↔ токен» и все встреченные написания значения | SQLite, `gate.sqlite`, один на домашний каталог; токен — HMAC-SHA256 от значения с секретом шлюза, поэтому одно значение всегда даёт один токен |
| Индекс | сущности, поля, ключи и связи базы; поиск объекта по примерному названию | SQLite, `bases/<база>/metadata.sqlite`; полнотекстовый поиск FTS5 с триграммами; строится потоковым разбором `$metadata` (`lxml`) |
| Клиент 1С | единственный, кто обращается к базе | `httpx`, пул соединений на базу; стандартный интерфейс OData v3 (`/odata/standard.odata/`), JSON; сеанс 1С (`IBSession`) переиспользуется, число одновременных запросов ограничено |
| Запись | превью, подтверждение, выполнение, журнал, откат | подготовленная операция живёт 10 минут и привязана к сессии; журнал — SQLite, `journal.sqlite` |
| Плагин | методика работы для модели, подтверждение записи, защита файлов шлюза | навыки и агент Claude Code; хук `PreToolUse` на Python |

Базы SQLite работают в режиме WAL: несколько сессий читают и пишут одновременно.

### Тулы чтения

| Тул | Что делает | Когда нужен |
|---|---|---|
| `odata1c_bases` | список видимых баз: подпись, роль, уровень гейта, статус индекса | в начале новой сессии — узнать, какие базы доступны |
| `odata1c_find_entity` | нечёткий поиск справочника, документа или регистра по названию | когда точное имя сущности неизвестно |
| `odata1c_describe_entity` | состав полей сущности: типы, ключи, связи, классы гейта | перед выборкой — чтобы выбрать нужные поля |
| `odata1c_query` | выборка записей сущности: отбор, сортировка, страницы | основной способ читать данные — списки, поиск, фильтры |
| `odata1c_get` | один объект по ключу | когда ключ объекта уже известен |
| `odata1c_info` | справочник по устройству OData 1С и самого шлюза, по темам | разобраться в токенах, политике, порядке записи |
| `odata1c_reindex` | обновить индекс метаданных базы | 1С отвечает «сущность не найдена» на объект, который точно есть, или после обновления конфигурации |
| `odata1c_raw_get` | произвольный запрос по пути публикации | когда `query` и `get` не выражают нужное обращение |
| `odata1c_recipe` | список готовых запросов базы или выполнение одного из них | вместо того чтобы собирать сложную выборку (остатки, задолженность) вручную |

### Тулы записи

Пишущие тулы ничего не пишут сами: каждый готовит операцию, показывает превью в токенах и
возвращает `pending_id`. В 1С пишет только `odata1c_commit` — и только после подтверждения
человека механизмом клиента: в Claude Code это диалог разрешения, у клиентов с elicitation — вопрос
шлюза. Любую выполненную запись можно откатить тулом `odata1c_undo`.

| Тул | Что делает | Когда нужен |
|---|---|---|
| `odata1c_create` | подготовить создание объекта — справочника или документа | завести новую запись в базе |
| `odata1c_update` | подготовить изменение полей существующего объекта | поправить значения по ключу |
| `odata1c_mark_for_deletion` | подготовить пометку удаления (или её снятие) | «удалить» объект — в 1С это всегда пометка, не физическое удаление |
| `odata1c_delete_record` | подготовить физическое удаление записи независимого регистра сведений | единственный тул с настоящим удалением — только для регистров без регистратора |
| `odata1c_action` | подготовить проведение или отмену проведения документа | провести или распровести документ |
| `odata1c_commit` | выполнить подготовленную операцию в 1С | после того как пользователь увидел превью и подтвердил запись |
| `odata1c_undo` | подготовить откат уже выполненной записи | отменить результат коммита — по его `commit_id` |
| `odata1c_journal` | последние выполненные записи с исходом и способом подтверждения | посмотреть историю записи или найти `commit_id` для отката |

## Командная строка

Команды `odata1c` — для владельца машины, не для модели. Общий ключ `--home <путь>` (или
переменная окружения `ODATA1C_HOME`) у любой команды меняет домашний каталог шлюза.

**Базы**

| Команда | Что делает |
|---|---|
| `odata1c init` | создать домашний каталог и шаблоны настроек |
| `odata1c base add <имя> [--role prod\|test\|dev] [--gate off\|identifiers\|identifiers+names] [--recipes ut\|bp\|zup]` | добавить базу — адрес, подпись, пользователя и пароль спросит сама; `--gate` задаёт уровень гейта этой базы вместо умолчания роли |
| `odata1c base import <путь>` | перенести базы из env-файла прежнего сервера |
| `odata1c base list` | список описанных баз |
| `odata1c base test <имя>` | проверить соединение с базой |
| `odata1c reindex <имя> [--force]` | обновить индекс метаданных базы |

**Политика гейта**

| Команда | Что делает |
|---|---|
| `odata1c policy show <имя>` | показать действующую политику базы |
| `odata1c policy check <имя>` | проверить файл владельца по индексу |
| `odata1c policy hide <имя> <сущность> [--yes]` | скрыть сущность целиком, вместе с дочерними |
| `odata1c policy open <имя> <Сущность.Поле>` | открыть поле (класс `keep`) |
| `odata1c policy set <имя> <Сущность.Поле> <класс>` | назначить полю класс защиты |

**Рецепты**

| Команда | Что делает |
|---|---|
| `odata1c recipe check <config>` | проверить библиотеку рецептов конфигурации |
| `odata1c recipe list <имя>` | список рецептов базы с источником каждого |

**Служебные**

| Команда | Что делает |
|---|---|
| `odata1c doctor [--online]` | проверка окружения: Python, `uv`, дом, базы, индекс, демон, Claude Code (`--online` — ещё и соединение с базами) |
| `odata1c daemon [--foreground]` | запустить демон вручную (обычно его поднимает лаунчер сам) |
| `odata1c daemon stop` | остановить демон |
| `odata1c mcp [--bases ...] [--default ...] [--url ...]` | лаунчер — то, что прописывается в `.mcp.json`; руками обычно не вызывается |
| `odata1c reveal <токен> [--base ...] [--field ...]` | реальное значение токена — только для владельца, в терминале |
| `odata1c --version` | версия пакета |

## Рецепты

Рецепт — это заранее составленный и проверенный запрос к базе, которому дано имя и у которого
объявлены параметры. Остатки товаров на складе, задолженность покупателей, выручка за период:
всё это типовые вопросы, и ответ на каждый из них в 1С требует знать, в каком регистре лежат
данные, как называется его виртуальная таблица и какие у неё поля. Рецепт хранит это знание в
готовом виде.

Без рецептов модель каждый раз заново исследует структуру базы и собирает запрос с нуля. Это
занимает несколько обращений к 1С и не защищает от ошибки в имени регистра или поля. С рецептом
тот же вопрос решается одним вызовом, и каждый раз одинаково, потому что запрос уже сверен с
метаданными конфигурации.

От пользователя рецепты ничего не требуют. Достаточно спросить обычными словами, например
«покажи остатки по основному складу на сегодня». Модель запрашивает у шлюза перечень рецептов
базы, выбирает подходящий и выполняет его со своими параметрами, в этом примере с датой и
складом. Для этого служит один инструмент, `odata1c_recipe`: вызов без имени возвращает перечень
с описаниями и параметрами, вызов с именем выполняет рецепт.

Рецепты собираются из трёх источников. Если имя встречается в нескольких, действует рецепт из
источника, который стоит в списке выше.

1. **Рецепты базы** лежат в файле `bases/<база>/recipes.yaml` и действуют только для неё. Здесь
   уместны запросы, которые учитывают доработки конкретной базы.
2. **Библиотека конфигурации** лежит в каталоге `~/.claude/odata1c/recipes/<конфигурация>/`, по
   одному файлу на рецепт, и общая для всех баз этой конфигурации. К какой конфигурации относится
   база, определяет поле `config` в её настройках: `ut`, `bp`, `zup` или собственное обозначение.
3. **Стартовый набор из поставки** — для «Управления торговлей», «Бухгалтерии предприятия» и
   «Зарплаты и управления персоналом». Его видит любая база, у которой задана конфигурация
   (`config`); ключ `--recipes` команды `odata1c base add` задаёт её и заодно копирует набор в
   файл рецептов базы.

Библиотека пополняется в ходе обычной работы. Когда запрос, найденный в разговоре, дал нужный
результат и пригодится снова, достаточно попросить модель сохранить его как рецепт. Навык плагина
`odata1c-recipe` заменит конкретные значения параметрами и запишет файл в библиотеку конфигурации.
Команда `odata1c recipe check <конфигурация>` сверяет рецепты библиотеки с метаданными базы, а
`odata1c recipe list <база>` показывает все рецепты базы с указанием источника каждого.

Рецепт записывается обычным файлом YAML. В нём есть описание, сущность 1С, параметры и сам запрос.
Данных базы в нём нет: ни значений, ни токенов.

```yaml
title: Остатки по складу
description: Количество в наличии на указанный момент.
entity: AccumulationRegister_ТоварыНаСкладах_Balance
params:
  period: { type: datetime, required: true, description: момент остатков }
  warehouse: { type: guid, required: false, description: Ref_Key склада }
virtual:
  Period: "{period}"
  Condition:
    - Склад_Key eq {warehouse}
select: [Номенклатура_Key, ВНаличииBalance]
filter: ВНаличииBalance gt 0
top: 200
```

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

## Ограничения

- **Физического удаления объектов нет.** «Удаление» для объектов и подчинённых регистров — только
  пометка удаления (`DeletionMark = true`). Из тулов записи физически удаляет только
  `odata1c_delete_record`, и только записи независимых регистров сведений.
- **Через `odata1c_update` нельзя вписать открытым текстом телефон, почту, адрес или дату
  рождения** — только токеном или при создании объекта (`odata1c_create`): иначе ответ «изменений
  нет» позволял бы подбирать чужие данные перебором значений.
- **Сокращённое название в свободном тексте проходит открытым.** Словарь знает полные написания
  названия; если в комментарии документа человек написал узнаваемое сокращение, которого нет в
  справочнике, гейт его не заменит. Ловить «ядро» названия отвергнуто: ядра — обычные слова, их
  замена портила бы данные ложными срабатываниями.
- **Одиночное название или имя в свободном тексте без кавычек проходит открытым.** Короткое
  название организации или имя человека, встретившееся в тексте одним словом без кавычек и без
  организационно-правовой формы (Мост вместо ООО «Мост», Мария вместо полного ФИО), гейт не
  отличает от обычного слова того же написания и не заменяет: иначе обычные слова в комментариях и
  именах объектов базы заменялись бы токенами по ошибке. Название с формой собственности (в том
  числе вплотную через точку или дефис, латиницей и полной записью: «ООО-Мост», «OOO Мост», «Общество с
  ограниченной ответственностью Мост»), в кавычках или апострофах, из нескольких слов, как и
  полное ФИО, закрывается как прежде. Открытыми остаются и редкие написания: угловые кавычки
  (‹Мост›, <<Мост>>) и чужая форма собственности (АО Мост при ООО «Мост»).
- **Аутентификации у демона нет.** Он слушает только `127.0.0.1`; модель угроз — диск и машина
  владельца. Публикации по сети с аутентификацией и TLS пока нет.
- **Записи в регистры, подчинённые регистратору, нет** ни при каком разрешении: их пишет
  проведение документа, а не прямая запись.
- **Шаблоны рецептов — только для типовых УТ, БП и ЗУП.** Имена в них сверены с реальными базами
  этих конфигураций; у доработанной конфигурации рецепт, чьей сущности в базе нет, помечается
  неприменимым, и его правят в собственном файле рецептов базы.
- **Проверен шлюз только с Claude Code.** Другие клиенты MCP по stdio (Cursor, VS Code, Claude
  Desktop) подключаются и читают; там, где нет диалога разрешения Claude Code или elicitation,
  запись по умолчанию выключена (`write_confirm_fallback: deny`). Навыки, хук и агент — часть
  плагина Claude Code, в других клиентах их нет. Cowork запускает MCP-серверы в изолированной
  среде без доступа к демону на `127.0.0.1` — с ним шлюз не работает.

## Документы

| Файл | Что внутри |
|---|---|
| [docs/install.md](https://github.com/Romandredan/odata1c-gate/blob/main/docs/install.md) | установка на Windows и Linux, обновление, `doctor`, типовые сбои |
| [CHANGELOG.md](https://github.com/Romandredan/odata1c-gate/blob/main/CHANGELOG.md) | что вошло в выпуск |
| [CONTRIBUTING.md](https://github.com/Romandredan/odata1c-gate/blob/main/CONTRIBUTING.md) | для тех, кто хочет разрабатывать шлюз |
| [SECURITY.md](https://github.com/Romandredan/odata1c-gate/blob/main/SECURITY.md) | как сообщить об уязвимости |

## Лицензия

[MIT](https://github.com/Romandredan/odata1c-gate/blob/main/LICENSE).
