# Издания RAGRAF: механизм, границы и что удержано

Документ описывает РАБОТАЮЩИЙ слой изданий, а не намерения. Всё, что здесь
сказано, проверяется вызовом: `python tools/ragraf.py edition` и
`python tests/test_editions.py` (65 свойств), 27-я проверка чекера.

Задача, из которой слой появился. До него `editions/` был картой удержания
файлов: в нём было видно, что папки `pro/` и `enterprise/` не публикуются, но
проверить «это Pro или Community» было нечем. Поэтому возможности платных
изданий оставались обещанием, а не состоянием сборки.

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

**Издание** — это набор РАЗРЕШЁННЫХ возможностей установки. Изданий три, и
список закрытый (`community`, `pro`, `enterprise`). Имя вне списка получает
ОТКАЗ с названной причиной: подстановки «неизвестное значит community» нет,
потому что она превращает ошибку в молчаливое понижение прав.

**Возможность** (feature) — не операция, а группа операций, отвечающая на один
вопрос: `search`, `graph`, `schema`, `db-live`, `audit` (базовые); `shared-index` (написан, ПРОМТ-И15),
`secret-broker` (написан, этап И18), `quality-loop` (Pro); `sso`,
`siem-export`, `offline-license`, `attestation` (Enterprise). Снятые в roadmap —
`connectors` и `fleet`: они не возможности, а записи плана развития.

Состав возможностей и операции, которые каждая открывает, — ДАННЫЕ:
`src/ragraf/edition_baseline.json`. Новая возможность объявляется там, и гейт
узнаёт о ней без правки кода. В `edition.py` нет ни одного условия вида «если
издание равно pro».

## 2. Правила разрешения (что именно запрещено)

1. **Лицензия даёт, заявление только понижает.** Действующее издание —
   наименьшее из того, что заявлено, и того, что разрешает подписанный файл.
   Записать `enterprise` в настройке и получить права нельзя.
2. **Платное издание без действующей лицензии не действует.** Health-риск
   обратный — «показалось, что купили» — исключён: действует Community, а
   причина попадает в `edition_notes` ответа.
3. **Возможность попадает в ответ, только если все её операции есть в сборке.**
   Объявленная, но не написанная возможность названа в разделе `missing` с
   причиной. Это правило и есть защита от ложного «полного» издания: сегодня
   действующих возможностей пять, а объявлено четырнадцать — девять ждут кода,
   и каждая названа.
4. **Неизвестная операция не разрешается.** Операция, которой нет в базовой
   линии, получает отказ «не объявлена в базовой линии изданий». Разрешение по
   умолчанию (`default allow`) — дефект того же рода, что и ложный `full`.
5. **Нет базовой линии — слой неразрешён, а не «всё можно».** Все гейтованные
   операции отказывают с причиной. Молчаливое «разрешено» здесь хуже отказа:
   проверка перестала бы осматривать.

## 3. Отказ и где он виден

Операция вне издания получает конверт того же вида, что обычный ответ:

```json
{
  "op": "drift_report", "index_rev": "rev-0251", "capability": "full",
  "edition": "community", "features": ["search", "graph", "schema", "db-live", "audit"],
  "found": false,
  "error": "EditionRefusal: операция «drift_report» не входит в издание community: возможность «audit» появляется в издании pro",
  "refused": {"operation": "drift_report", "feature": "audit",
              "requires_edition": "pro", "edition": "community",
              "suggest": "какое издание действует: python tools/ragraf.py edition"}
}
```

Отказ — не исключение: клиент получает разбираемый ответ, а не трассировку.
При этом отказ ВИДЕН в двух журналах:

* `usage_call` — запись с `error = "EditionRefusal: …"` (телеметрия);
* `admin_access_log` — запись `result = denied`, `reason = "edition: …"`
  (`src/ragraf/access.py`, журнал только на добавление).

Гейт стоит СНАРУЖИ остальных обёрток (`facade._install_edition_gate`
устанавливается после сторожа свежести): отказанная операция не должна ни
сверять контрольную сумму корпуса, ни попадать в журнал как выполненная.

## 4. Файл лицензии и что доказывает подпись

Файл `catalog/edition.license` (`RAGRAF_EDITION_LICENSE` меняет путь) —
JSON с полями `edition`, `licensee`, `issued_at`, `expires_at`. Подпись
Ed25519 в файле `<имя>.sig` тем же кодом, что подписывает экспорт аудита и
SBOM (`src/ragraf/signing.py`).

**Важное уточнение, измеренное 24.10.2026.** `signing.verify_file` проверяет
подпись ОТКРЫТЫМ КЛЮЧОМ, ВЛОЖЕННЫМ В САМ ФАЙЛ ПОДПИСИ. Такая проверка
доказывает ЦЕЛОСТНОСТЬ (содержимое не менялось после подписи), но не
АВТОРСТВО: подписать свой текст своим ключом может кто угодно, и первый
вариант слоя выдавал такому файлу издание pro. Поэтому слой дополнительно
сверяет ключ с ДОВЕРЕННЫМ:

| Источник доверенного ключа | Порядок |
|---|---|
| аргумент `public_key` (`--public-key`) | 1 |
| переменная `RAGRAF_EDITION_PUBKEY` (base64) | 2 |
| файл `catalog/edition.pub` | 3 |
| открытый ключ из `catalog/audit-signing.key` (тот же каталог состояния) | 4 |

Явное значение `-` означает «не доверять ни одному ключу»: так проверка
показывает самоподписанную лицензию, не удаляя файлов.

Состояния лицензии и что они значат:

| `state` | Значение | Действует |
|---|---|---|
| `absent` | файла нет | Community |
| `valid` | подпись сходится с доверенным ключом, срок не истёк | издание из файла (или ниже, если заявлено ниже) |
| `forged` | содержимое изменилось после подписи ИЛИ подпись сделана другим ключом | Community, причина названа |
| `expired` | срок в прошлом | Community, причина называет дату |
| `untrusted` | целостность есть, доверенного ключа нет | Community, авторство не подтверждено |
| `unverifiable` | нет PyNaCl: проверить подпись нельзя | Community, причина названа |
| `broken` | файл не разбирается, нет поля `edition`, издание вне закрытого списка | Community, причина называет поле или список |

Ни одно состояние не выдаётся за «неизвестное издание» и ни одно не роняет
базовое издание: подделанный файл лицензии не мешает работать с Community.

## 5. Что видно в ответе агенту

В каждом ответе фасада рядом с `index_rev` и `capability`:

```json
{"op": "search", "index_rev": "rev-0251", "capability": "full",
 "edition": "community",
 "features": ["search", "graph", "schema", "db-live", "audit"]}
```

и, если есть что сказать, `edition_notes` (например «заявлено издание pro, но
действующей лицензии нет — возможности ограничены изданием community»).

Неразрешённый слой выглядит так: `edition = "unresolved"`, `features = []`,
причина в `edition_notes`. Значение намеренно не похоже на имя издания: спутать
«не разрешено» с «Community» нельзя.

Полное состояние — `python tools/ragraf.py edition` (машиночитаемо:
`--json edition`): заявленное издание, действующее, возможности, состояние
лицензии с причиной, объявленные-но-недоступные возможности.

Сводка в конверте `ragraf_context` тоже несёт `edition`/`features`: это первое,
что вызывают, когда непонятно, с чем работаешь.

## 6. Что удержано и почему (границы изданий)

Правило удержания не изменилось и записано в `editions/editions.toml`: ни один
файл не пропадает, он либо публикуется в Community, либо объявлен удержанным с
причиной (`tools/release/editions_report.py` требует места у каждого файла).

* `enterprise/` — закрытая часть единственной коммерческой поставки, в публикацию
  Community не входит никогда;
* `archive/` — хранилище вырезанного, единственная рабочая копия;
* `editions/`, `catalog/`, `config/` — служебный материал поставки и локальные
  пути этой машины.

**База безопасности не платная.** Исключение секретоносных путей, аудит доступа,
хранилище секретов, вход в панель — в Community и остаются там.

**Платные возможности: семь написаны, объявленных и не написанных нет** (этап
И18, 26.10.2026). Написаны и включаются лицензией: уровень Pro — `quality-loop`,
`shared-index` (общий индекс команды, `src/ragraf/shared_index.py`) и
`secret-broker` (брокер секретов, `src/ragraf/broker.py`); уровень Enterprise —
`sso`, `siem-export`, `offline-license`, `attestation`. Статус `shipped` в
`edition_baseline.json`, файлы перечислены в `enterprise/inventory.toml`,
поведение проверяют 27–29-я проверки чекера. Сняты в `roadmap` с причиной и
датой: `connectors`, `fleet` — у них нет ни контракта запуска, ни парка
установок, на которых поведение можно доказать.

Прежняя редакция этого раздела утверждала, что не написаны ВСЕ девять платных
возможностей. Замер 25.10.2026 показал обратное по пяти: код есть, он собран,
проверен и закрыт гейтом издания. Утверждение исправлено, а не смягчено:
документ, называющий написанное ненаписанным, обесценивает и то, что в нём
верно.

**Закрытая поставка одна** — Enterprise: собирается манифестом
`tools/release/manifest-enterprise.toml` (наследует манифест Community, добавляет
папку `enterprise/` и закрытые модули) и проверяется
`tools/release/check_pro_delivery.py`: воспроизводимость двух прогонов, состав,
привязка лицензии в СОБРАННОМ дереве. Переход Pro → Enterprise не требует
установки: активируется лицензия, функции расширяются. Состав и границы —
`enterprise/ENTERPRISE-DELIVERY.md`, уровень Pro — `enterprise/PRO-TIER.md`;
единый инвентарь прав — `enterprise/inventory.toml`, он сверяется с базовой
линией свойством, а не доверием.

## 7. Чего этот слой не делает

* не выпускает страницу цен и не обещает платные возможности: их ещё нет;
* не заменяет лицензионные тексты (`LICENSE`, `NOTICE`) и не меняет
  классификацию лицензии кода — это `docs/03_implementation/LICENSING.md`;
* не выдаёт значения из хранилища секретов: это отдельный слой
  (`docs/03_implementation/SECRETS-VAULT.md`);
* не решает, что «правильно» юридически: он отвечает только на вопрос, какое
  издание собрано и что ему разрешено.

## 8. Как проверить

```powershell
cd <каталог дерева RAGRAF>
python tools\ragraf.py edition                      # состояние слоя
python tools\ragraf.py --json edition               # то же машиночитаемо
python tests\test_editions.py                       # 65 свойств
pwsh -NoLogo -NoProfile -NonInteractive -File tools\Invoke-ProjectDoctrine.ps1
```

Красное состояние тоже измерено: без `tests/test_editions.py` чекер даёт
`checks: 27, errors: 1` с причиной «слой изданий не проверяется».

## Где что лежит, на каком издании работаем и на каких портах запускаем

| Что | Где | Издание |
|---|---|---|
| Источник всего (разработка) | `C:\AutoClaw\RAGRAF` | из него собираются все три профиля |
| Community (публичный) | `github.com/paulmann/RAGRAF`, выпуск `v0.3.0` | community |
| Pro (приватный) | `github.com/paulmann/ragraf-pro` | pro |
| Enterprise (приватный) | `github.com/paulmann/ragraf-enterprise` | enterprise |
| Собранные деревья на диске | `…\projects\Default\wave4\{community,pro,enterprise}` | временные: пересобираются конвейером, правок не содержат |

**Машина владельца работает на Enterprise.** Это зафиксировано офлайн-лицензией
`catalog/edition.license`: проверка — `python tools/ragraf.py edition` (издание
enterprise, разрешено да, 11 возможностей). Профили остальных изданий
запускаются для проверок на отдельных портах, чтобы не занимать рабочий:

| Профиль | MCP (HTTP) | Панель | Зачем |
|---|---|---|---|
| рабочий (Enterprise) | 9500 | 8777 | повседневная работа владельца |
| community (проверка) | 9510 | 8787 | прогон того, что видит внешний пользователь |
| pro (проверка) | 9511 | 8788 | проверка платных возможностей |
| enterprise (проверка) | 9512 | 8789 | проверка контура организации |

Правило: порт рабочего профиля проверками не занимать. Проверочный профиль —
отдельный каталог `.ragraf` и отдельный индекс: иначе проверка меняет ревизии
рабочего корпуса и статистику обращений.

## Что где в коде

| Слой | Файлы | Где живёт |
|---|---|---|
| Ядро индекса | `corpus.py`, `dedupe.py`, `indexer.py`, `store.py`, `lexvec.py` | все издания |
| Граф и обход | `graph_algo.py`, `cpg.py`, `joern_server.py` | все издания |
| Контракт ответа | `facade.py`, `contract.py`, `capability.py` | все издания |
| Измерение индекса | `quality.py` (`measures`, `journal_stats`, `prune_keep_default`) | все издания: читается базовой операцией `index_status` |
| Контур качества | `quality_loop.py` (`suite`, `diff`, `prune`) | Pro и Enterprise: в Community файла нет |
| Издание и права | `edition.py`, `license_offline.py`, `attest.py`, `signing.py` | `edition.py` — все; остальные три — Pro и Enterprise |
| Внешний доступ | `sso.py`, `siem.py`, `shared_index.py` | Pro и Enterprise: в Community файлов нет |
| Поставка | `tools/release/manifest*.toml`, `build_release.py`, `verify_release.py`, `gate_release.py` | репозиторий разработки |

Граница проходит по наличию файла: если модуля нет в сборке, вызов операции
возвращает отказ с названной причиной через `_feature_module`
(`facade.py:836`), а не трассировку.
