# Доступ и транспорт RAGRAF: пароль, выданные токены, MCP по HTTP

Документ описывает **защиту доступа** к интерфейсу RAGRAF и **второй транспорт**
MCP (по HTTP). Всё здесь — измерения текущего кода; команды можно повторить.

Оглавление:

1. [Что защищено и как](#1-что-защищено-и-как)
2. [Пароль администратора](#2-пароль-администратора)
3. [Выданные токены: кому, зачем, на сколько](#3-выданные-токены-кому-зачем-на-сколько)
4. [Статистика: кто и как использовал доступ](#4-статистика-кто-и-как-использовал-доступ)
5. [MCP по Streamable HTTP](#5-mcp-по-streamable-http)
6. [Подключение клиентов](#6-подключение-клиентов)
7. [Проверка](#7-проверка)
8. [Границы и известные ограничения](#8-границы-и-известные-ограничения)
9. [Откат](#9-откат)

---

## 1. Что защищено и как

Три пути входа, и это намеренно, а не избыточность:

| Способ | Кому нужен | Где хранится |
|---|---|---|
| **Пароль администратора** | человек, вход в панель | `catalog/admin_auth.json` (PBKDF2-SHA256) |
| **Выданный токен** | агент (AutoClaw, Harness, Hermes) | `catalog/access_tokens.json` (только SHA-256) |
| **Токен сессии** | прежний путь: печатается при запуске панели | нигде, живёт до перезапуска |

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

**Измеренный дефект, который закрыт.** Точка `/api/token` возвращала значение
доступа администратора в ответе. Слушается только `127.0.0.1`, но это не защита:
любой процесс на машине и любая вкладка браузера получали значение и могли
менять настройки. Проверка изменяющих запросов была формальностью. Теперь точка
отдаёт **состояние** — задан ли пароль, сколько токенов заведено, — без значений.

**Файлы доступа лежат в `catalog/`** — этот каталог исключён из корпуса
(`config/ragraf.yaml`, шаблон `**/catalog/**`), поэтому секретоносные файлы не
попадают ни в индекс, ни в поисковую выдачу агента. Проверка
`Test-AccessHygiene` следит именно за этой связью.

## 2. Пароль администратора

Пароль **опционален**: пока его нет, панель работает по токену сессии.

* Алгоритм: PBKDF2-HMAC-SHA256, **600 000 итераций**, соль 16 байт на файл.
* Минимальная длина: 8 символов (короче — отказ с причиной).
* Смена пароля **требует текущий** — иначе угон открытой сессии сразу менял бы
  владельца доступа.
* Снятие пароля тоже требует текущий.
* Пароль не попадает ни в один ответ и ни в одну строку журнала.

В панели: раздел **«Доступ» → «Пароль администратора»**.

```powershell
# Программно (то же, что делает панель)
$token = '<токен сессии из вывода панели>'
Invoke-RestMethod -Uri 'http://127.0.0.1:8777/api/auth/password' -Method Post `
  -Headers @{ 'X-RAGRAF-Token' = $token } -ContentType 'application/json' `
  -Body '{"password":"<ваш пароль>"}'
```

После задания пароля вход в браузере: панель → «Войти» → пароль. Сервер выдаёт
cookie сессии (`HttpOnly`, `SameSite=Strict`, срок 8 часов).

## 3. Выданные токены: кому, зачем, на сколько

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

| Поле | Смысл |
|---|---|
| `label` | **кому выдан** (обязательно, уникально) |
| `scopes` | права: `read`, `write`, `admin` (по умолчанию `read`) |
| `note`, `owner` | зачем выдан и кто выдал |
| `expires_at` | срок (необязательно) |
| `use_count`, `last_used_at`, `last_result` | сколько раз и когда использован |

**Значение показывается один раз.** В файле лежит только `secret_sha256`, поэтому
утечка файла не даёт доступа: по хэшу значение не восстановить. Если не скопировали
сразу — выдайте новый токен, старый отзовите.

Отзыв обратим: `отозвать` → `восстановить`. Удаление записи историю обращений
**не** трогает (журнал append-only).

Предлагаемый порядок для трёх клиентов — **по отдельному токену каждому**:
отзыв одного не останавливает остальных, а в статистике видно, кто сколько истратил.

## 4. Статистика: кто и как использовал доступ

Два независимых журнала, и они не заменяют друг друга.

**Журнал доступа** (`admin_access_log`, append-only; пишут панель и MCP по HTTP):

| Колонка | Пример |
|---|---|
| `surface` | `panel` \| `mcp-http` |
| `operation` | `/api/settings/global`, `tools/call` |
| `label` | `AutoClaw` (кому выдан) |
| `principal` | `token` \| `password` \| `session` \| `none` |
| `result` | `ok` \| `denied` \| `error` |
| `reason` | причина отказа (в ответ клиенту **не** идёт) |
| `duration_ms`, `detail` | время и краткое пояснение |

**Телеметрия использования** (`usage_call`, `usage_session`) получила колонки
метки доступа **догоняющей миграцией** — прежние журналы не требуют удаления, как
было с `agent`/`task`/`arg_hash`. Сводка теперь отдаёт разрез:

```powershell
python tools\ragraf.py -p config\ragraf.yaml usage
```

```
по выданному доступу:
  доступ                    вызовов   успешных    с ошибкой   фактов
  (без доступа)                460        458           2     7496
  AutoClaw                       2          2           0        2
```

Строка `(без доступа)` — это **не** потеря данных: так выглядят вызовы CLI,
прежние записи и тесты. Отличать её надо, иначе «доступом не пользовались» и
«поле пустое» слились бы.

**Дефект, найденный проверкой цепочки:** метка не доходила до записи — обёртка
фасада заполняет только операционные поля, а доступ — свойство **процесса**.
Теперь `Telemetry` подставляет доступ процесса, если вызов не назвал свой.
Замерено: до правки 460 записей «без доступа» при работающем сервере; после —
метка `AutoClaw` видна.

## 5. MCP по Streamable HTTP

**Зачем второй транспорт.** У AutoClaw в режиме «Local program» нет поля рабочего
каталога, а в режиме «Online service» нет процесса — значит при stdio корпус
определяется автоматически, а при HTTP его нужно назвать явно. Поэтому HTTP
добавлен **рядом**, а не вместо: `tools/ragraf-mcp-http.py` использует **тот же**
слой инструментов (`McpServer.handle`), что и stdio-сервер.

```powershell
# Запуск: слушает только loopback, корпус указан явно
python tools\ragraf-mcp-http.py `
  --port=8765 `
  --project=config\ragraf.yaml `
  --agent=AutoClaw --agent-kind=autoclaw
```

| Точка | Что делает |
|---|---|
| `POST /mcp` | сообщение JSON-RPC; ответ `application/json` либо поток при `Accept: text/event-stream` |
| `GET /mcp` | поток событий — сервис отвечает `405` (операции короткие, поток не нужен) |
| `DELETE /mcp` | закрыть сессию (`Mcp-Session-Id`) |
| `GET /healthz` | процесс жив |
| `GET /readyz` | корпус и хранилища доступны (без путей и конфигурации) |

**Свойства, за которые транспорт отвечает:**

* проверка доступа: `Authorization: Bearer <значение>` — через **ту же** выдачу,
  что у панели (единая точка, не второй список);
* **отказ старта**, если адрес не loopback и проверка доступа выключена или
  значений нет: иначе сервис оказался бы открыт сети без единой проверки;
* `Origin` — только из списка, если список задан (`--allowed-origins`);
* предел тела запроса (`1 МБ`), предел одновременных запросов (`8`), таймаут;
* каждое обращение пишется в журнал доступа: кто, чем, что, с каким результатом;
* клиенту **никогда** не отдаётся трассировка — только текст ошибки.

**Дефект, найденный живой проверкой (закрыт).** Первая редакция возвращала
объект-«кто пришёл» даже при отказе, и вызывающий код принимал его за успешный
вход: **отозванный и неверный доступ получали ответ 200**. Теперь отказ — это
отсутствие входа (`None`), метка известного, но отозванного доступа уходит в
журнал, чтобы владелец видел, чей доступ перестал работать. Проверка:
`tests/test_mcp_http.py` (19 проверок).

## 6. Подключение клиентов

### AutoClaw — «Online service»

| Поле | Значение |
|---|---|
| Name | `RAGRAF` |
| Type | `Online service` |
| Service URL | `http://127.0.0.1:8765/mcp` |
| Bearer token | значение токена, выданного для AutoClaw |

### DeepSeek Harness — блок профиля

```yaml
- id: mcp-ragraf-http
  name: '@deepseek-ai/dsh-mcp-client'
  config:
    serverName: ragraf
    transport: streamable-http
    url: http://127.0.0.1:8765/mcp
    headers:
      Authorization: 'Bearer <значение токена, выданного для Harness>'
```

Транспорт `streamable-http` у Harness **есть** (`@deepseek-ai/dsh-mcp-client`),
но в текущей установке используется stdio: причина в §5 — stdio определяет корпус
сам, HTTP требует указать его явно. Оба варианта рабочие, выбор — за владельцем.

### Hermes Agent

```yaml
mcp_servers:
  ragraf:
    url: "http://127.0.0.1:8765/mcp"
    headers:
      Authorization: "Bearer <значение токена, выданного для Hermes>"
```

**Честно про Hermes:** конфиг составлен по документации из присланного описания,
но **проверить его нельзя** — агента на этой машине нет. Формат ключей может
отличаться; при подключении смотреть фактическую документацию Hermes.

## 7. Проверка

```powershell
# Интерфейс: пароль, токены, журнал (поднимает настоящий сервер)
python tests\test_access_live.py        # 29 проверок

# MCP по HTTP: протокол, доступ, журнал (поднимает настоящий сервер)
python tests\test_mcp_http.py           # 19 проверок

# Согласованность с внешними клиентами (урезанное окружение, автоопределение корпуса)
python tests\test_mcp_compat.py         # 15 проверок

# Чекер: правило защиты доступа (17 проверок, 0 ошибок, 0 предупреждений)
pwsh -NoLogo -NoProfile -NonInteractive -File tools\Invoke-ProjectDoctrine.ps1
```

Признаки работающей защиты, которые видно сразу:

| Что проверить | Ожидание |
|---|---|
| `GET /api/token` | в ответе **нет** значения доступа, только `password_set` и число токенов |
| `POST /api/settings/global` без входа | отказ `403` |
| `POST /mcp` без заголовка доступа | отказ `401` |
| `POST /mcp` с отозванным значением | отказ `401` |
| `usage` в CLI | раздел «по выданному доступу» с метками |
| `admin_access_log` | и `ok`, и `denied` с меткой доступа |

## 7.1 Режим работы RAGRAF и автоматическая настройка клиентов

### Какой канал сейчас работает

Измерено 13.10.2026: RAGRAF настроен **на CLI**. Подтверждения:
инструментов `ragraf_*` в сессии — 0; внешних инструментов в обоих аккаунтах
AutoClaw (таблица `governed_connected_sources`) — 0; HTTP-сервис на порту 8765
не запущен; SagaAI вызывает CLI из `orchestrators/dev_agent/functions/ragraf.py`.

Каналы не конфликтуют: CLI и MCP можно использовать одновременно. Разница в
свойствах, а не в качестве:

| Канал | Сильная сторона | Слабое место |
|---|---|---|
| CLI | не зависит от регистрации сервера и перезапуска приложения; ответ —
  обычный текст, который агент читает сам | требует команды на каждый вопрос |
| MCP | инструменты доступны агенту автоматически, ответ структурирован,
  вызовы попадают в статистику по доступу | зависит от того, что сервер
  зарегистрирован и приложение перезапущено |

### Настройка режима

Панель → **«Режим и клиенты»** → «Канал работы RAGRAF». Значения:

| Режим | Что считается ожидаемым | Что проверяет проба |
|---|---|---|
| `cli` | работа через оболочку | запуск `tools/ragraf.py catalog` |
| `mcp-stdio` | подключение как локальный процесс | сервер поднимается и отдаёт список инструментов |
| `mcp-http` | подключение по URL | `/healthz` и `/readyz` уже поднятого сервиса |
| `both` | оба канала | все три проверки |

**Режим ничего не включает сам**, и это принципиально: локальный MCP-сервер
поднимает клиент, а не панель. Настройка описывает **ожидание** и сразу
проверяется пробой — панель пишет «режим подтверждён» или «режим НЕ подтверждён»
и называет причину. Не выбранные каналы всё равно показываются, но с пометкой
«справочно» и в вердикт не входят.

### Автоматическая настройка клиентов

| Клиент | Что делает панель | Что остаётся за владельцем |
|---|---|---|
| **AutoClaw** | формирует точные значения (имя, URL, тип) и по кнопке выдаёт именованный доступ | вставить значения в Settings → Tools & connections → Add tool и нажать Save |
| **DeepSeek Harness** | **применяет блок профиля целиком** (два транспорта на выбор), с резервной копией и проверкой разбора | перезапустить приложение |
| **SagaAI** | показывает состояние инструмента и конфига корпуса, называет переменные | правит свой проект сам: панель чужой файл не меняет |

**Почему AutoClaw нельзя настроить автоматически.** MCP-серверы регистрируются
только через интерфейс продукта: запись живёт в `runtime.sqlite`
(таблица `governed_connected_sources`), а файла конфигурации, в который можно
было бы дописать строку, нет. Писать в базу продукта в обход продукта нельзя:
там же лежит учёт прав и артефактов (`governed_tool_*`), и обход ломает его.
Поэтому панель отдаёт готовые значения — их остаётся вставить и сохранить.

**Почему SagaAI только проверяется.** Файл инструмента принадлежит другому
проекту (`C:\!.Tihon\sagaai_edit_github`), и правка чужого дерева без прямого
указания владельца недопустима. Показывается фактическое состояние и переменные,
которыми инструмент управляется: `RAGRAF_ROOT`, `RAGRAF_CONFIG`, `RAGRAF_AGENT`,
`RAGRAF_AGENT_KIND`, `RAGRAF_TASK`.

### Что проверить после настройки

```powershell
python tests\test_ragraf_mode.py    # режим, пробы, автонастройка
```

Признаки, которые видно сразу: в разделе «Режим и клиенты» у выбранного канала
стоит «работает», а у клиента — «подключён»; после применения блока Harness в
профиле ровно **одна** запись `- id: mcp-ragraf…` (повторное применение
заменяет, а не добавляет вторую: два сервера с одним именем клиент отвергает).

## 8. Границы и известные ограничения

* **Только loopback.** Оба сервиса (панель и HTTP MCP) по умолчанию слушают
  `127.0.0.1`. Вывод в сеть требует `--host` и хотя бы одного действующего
  значения доступа, иначе старт отвергается.
* **Пароль как единственная защита панели слабее, чем кажется.** Ограничения
  частоты попыток входа **нет**: перебор с локальной машины технически возможен,
  его сдерживает только стоимость PBKDF2 (600 000 итераций). Если панель будет
  слушать не loopback, ограничение нужно добавить.
* **Права (`scopes`) фиксируются, но ещё не проверяются по операциям.**
  Значение `read`/`write`/`admin` хранится и видно, однако операции HTTP MCP
  пока не делятся по правам: любой действующий доступ может вызывать и изменяющие
  операции (`ragraf_ensure`, `ragraf_use_project`). Это следующий шаг, если
  потребуется разделение; сейчас это честно записанное ограничение, а не «сделано».
* **Значения доступа не хранятся в защищённом виде** (только хэш), но и сам файл
  прав на уровне файловой системы на Windows не сужается (`chmod` там не
  действует так, как на Unix). Права на `catalog/` — задача администратора машины.
* **HTTPS нет.** Для локального сервиса это нормально; для сети нужен обратный
  прокси с TLS, и тогда же — ограничение частоты попыток и ротация токенов.
* **Hermes не проверен** (см. §6).

## 9. Откат

Всё, что меняет состояние, откатывается без переиндексации:

```powershell
# 1. Снять пароль и удалить выданные токены: доступ вернётся к токену сессии.
$home2 = 'RAGRAF'
Remove-Item "$home2\catalog\admin_auth.json","$home2\catalog\access_tokens.json" -Force

# 2. Остановить HTTP-сервис: отдельная точка входа, остальной RAGRAF не зависит.
#    (никаких следов в состоянии он не оставляет)

# 3. Колонки метки доступа в телеметрии безвредны: пустые значения означают
#    «без доступа» и видны отдельной строкой — удалять не требуется.
```

Правки кода аддитивны; резервные копии правленых файлов лежат в
`.staging/access-backups/` (исключён из корпуса). Индекс, схема хранилища,
нумерация ревизий и реестр не затронуты: атрибуция — добавленные колонки, а не
изменение ключей.
