H2 wiki проекция ADB · dev

← к элементу · к схеме

Онтология ADB

Продуктai_docs_broker
Контурdev
Тип документаОнтология
Координаты чтения{"op": "get_document", "env": "dev", "product": "ai_docs_broker", "doc_type": "Онтология", "latest": true}
Версияv0.25
SHA-256d235d0796c00a5a51d716431c7dbf310908e73cc1fdd2e712819091d35f76d97 сверено
Размер93334 байт

Полный текст

# ai_docs_broker: Онтология v0.25

## §CHANGELOG v0.25 относительно v0.24 (2026-09-21)

Полная новая редакция по прямому поручению Оператора об очистке битых документных ссылок. Исправлены подтверждённые действующие адреса: Гайд UEPR принадлежит prod/adb_meta, а не prod/ai_docs_broker; в процедуре закрытия дополнительно исправлена среда Онтологии h2_shared на dev и шаблон пользовательского гайда на prod. Для этой редакции заменено 1 JSON-ссылок/шаблонов (1 UEPR, 0 Онтология); изменения не отменяют требований документов.

Разрешимые ссылки заменены проверенными каноническими адресами, а не удалены вместе с нужной зависимостью. Неактивная история и датированные доказательства сохранены без подмены наблюдений. Архив ADB не запрашивался и не изменялся. Исправление документации не означает внедрение нового guard, исправление runtime, повторную валидацию продукта или восстановление недоступных файлов других записей.


## §CHANGELOG v0.24 относительно v0.23 (2026-09-21)

Полная новая редакция по решению Оператора: исправлены действующие ссылки и правила размещения Гайда Пользователя. Для любого продукта этот тип хранится только в prod, независимо от dev-only/dev-and-prod runtime. Постоянная ссылка содержит ровно op, env, product, doc_type и latest:true, без версии, ID и Files URL:

```json
{"op":"get_document","env":"prod","product":"ai_docs_broker","doc_type":"Гайд Пользователя","latest":true}
```

Поле actor добавляется вызывающей стороной по контракту ADB; это не часть постоянных координат документа. После lookup проверяются возвращённые метаданные и двоичный SHA-256.

Коррекция относится только к документации: runtime, wheel, deploy, сценарии и прежние результаты проверок не менялись и заново не аттестованы. Датированные снимки, ID/версии/SHA в доказательствах и исторические приложения не являются текущими ссылками; они сохраняются как свидетельства своего времени. Прежние требования искать или публиковать пользовательский гайд в dev больше не действуют. Описания старых guard a26/a30 не доказывают сегодняшнюю реализацию; наличие нормативного правила не объявляется успешным runtime-тестом.


## §CHANGELOG v0.23

Полная редакция после независимого холодного старта. Учтён фактический дрейф ЖЦ к v0.5, исправлен порядок wheel_id до установки/LS; живые разрешения не расширены. Исторические приложения сохранены побайтно.


## Сверка изменившегося ЖЦ перед итогом

19.09.2026 в 09:08:18Z получен и прочитан изменившийся норматив:
`{"op":"get_document","env":"prod","product":"adb_meta","doc_type":"Гайд ЖЦ ИТ-продукта H2","latest":true}`.
Evidence этого чтения: version v0.5, SHA
`8dfe5c49ad14836c02f27fa6f46cd9f7d42e5ae49d87970eb9c29dafe4201c9f`,
17497 байт; зарегистрирован владельцем нормы 09:00:00.620Z.
Изменены шаги 5/9/13: первичная регистрация артефакта выполняется до установки
и зачётного Live Scenario; после LS и ai_validation завершается учёт release.
Прежнее противоречие порядка wheel_id больше не является нормативным блокером.
Оно не заменено фиктивным успехом: эта сессия не регистрировала wheel/deploy/run
и не устанавливала a30. TD-2026-09-19-04 остаётся OPEN по фактической цепочке.
Остальные нормы, в том числе конфликт dev/prod Гайда Пользователя, не изменены.
Прежние ссылки на v0.4 внутри датированных наблюдений и истории описывают прошлое,
а не текущую инструкцию.


## Статус публикации

Редакция `v0.23` одобрена Оператором 19.09.2026 для регистрации. Это полный документ, не черновик на согласование. Его регистрация подтверждается ответом ADB и контрольным скачиванием текущей версии с проверкой SHA и размера; собственный будущий ID здесь не предполагается. Разрешение касается только документации и независимой read-only проверки нового ИИ-Арха. Установка a30, изменение runtime, секретов и данных, регистрация wheel/deploy/прогонов не разрешены. Приёмка продукта BLOCKED.

Исторический родитель: version `v0.21`, SHA `4d18e13cc037ab431402b81581956da91f2a6c11ed206a9d9917e3bd9a02fd93`. Координаты latest ниже предназначены для текущей редакции, а не поиска родителя.


## §CHANGELOG v0.22

Документные ссылки заменены полными координатами ADB; текущие утверждения отделены от исторических; редакция одобрена для регистрации. Прежние измерения не объявлены новыми.

## Координаты документов в ADB

Любое упоминание документа ниже разрешается через эту таблицу, а не через имя локального файла, старый ID или прямой Files URL. Запросы показывают business payload; полный actor добавляется по действующему гайду. После ответа сверить env/product/type/node, скачать тело по возвращённому owui_file_id, проверить SHA и размер и прочитать содержание. Сам Files download является транспортом после поиска в ADB, а не постоянной документной ссылкой.

| Документ | Координаты поиска |
|---|---|
| ai_docs_broker: Гайд Пользователя | `{"op":"get_document","env":"prod","product":"ai_docs_broker","doc_type":"Гайд Пользователя","latest":true}` |
| ai_docs_broker: Онтология | `{"op":"get_document","env":"dev","product":"ai_docs_broker","doc_type":"Онтология","latest":true}` |
| ai_docs_broker: Гайд Архитектора | `{"op":"get_document","env":"dev","product":"ai_docs_broker","doc_type":"Гайд Архитектора","latest":true}` |
| ai_docs_broker: Концепт Развития | `{"op":"get_document","env":"dev","product":"ai_docs_broker","doc_type":"Концепт Развития","latest":true}` |
| ai_docs_broker: Live Scenario | `{"op":"get_document","env":"dev","product":"ai_docs_broker","doc_type":"Live Scenario","latest":true}` |
| ai_docs_broker: Отчёт Валидации | `{"op":"get_document","env":"dev","product":"ai_docs_broker","doc_type":"Отчёт Валидации","latest":true}` |
| ai_docs_broker: Аудит UEPR | `{"op":"get_document","env":"dev","product":"ai_docs_broker","doc_type":"Аудит UEPR","latest":true,"node":"heavy02"}` |
| ai_docs_broker: UEPR | `{"op":"get_document","env":"dev","product":"ai_docs_broker","doc_type":"UEPR","latest":true,"node":"heavy02"}` |
| adb_meta: Закрытие сессии ИИ-Арх | `{"op":"get_document","env":"prod","product":"adb_meta","doc_type":"Закрытие сессии ИИ-Арх","latest":true}` |
| ai_docs_broker: Стандарт (Техдолг) | `{"op":"get_document","env":"dev","product":"ai_docs_broker","doc_type":"Стандарт","version":"tech-debt-log-v48.3"}` |
| ai_docs_broker: Дорожная Карта | `{"op":"get_document","env":"dev","product":"ai_docs_broker","doc_type":"Дорожная Карта","latest":true}` |
| ai_docs_broker: Онбординг | `{"op":"get_document","env":"prod","product":"ai_docs_broker","doc_type":"Онбординг","latest":true}` |
| adb_meta: Гайд UEPR | `{"op":"get_document","env":"prod","product":"adb_meta","doc_type":"Гайд UEPR","latest":true}` |
| ai_docs_broker: Гайд Аудита | `{"op":"get_document","env":"prod","product":"ai_docs_broker","doc_type":"Гайд Аудита","latest":true}` |
| adb_meta: Гайд ЖЦ ИТ-продукта H2 | `{"op":"get_document","env":"prod","product":"adb_meta","doc_type":"Гайд ЖЦ ИТ-продукта H2","latest":true}` |
| adb_meta: Письмо-открытка ии-арх ПерпКомп для онбординга | `{"op":"get_document","env":"prod","product":"adb_meta","doc_type":"Письмо-открытка ии-арх ПерпКомп для онбординга","latest":true}` |
| ai_validation: Гайд Пользователя | `{"op":"get_document","env":"prod","product":"ai_validation","doc_type":"Гайд Пользователя","latest":true}` |

Техдолг: указан точный адрес согласованной серии tech-debt-log-v48.3 этого комплекта; актуальность подтверждается ADB, не именем файла. Для нового current сначала выполнить `{"op":"list_documents","env":"dev","product":"ai_docs_broker","doc_type":"Стандарт","limit":100,"offset":0}`, прочитать все страницы, выбрать именно серию tech-debt-log и затем запросить её точную version. Не подменять её contour-binding и не выбирать по максимальному id. При неоднозначности отметить GAP; не придумывать параметр series или version_prefix.

Версии и SHA в старых измерениях являются provenance этого измерения. Ни координаты latest, ни текст новой редакции не делают старые измерения свежими. Ссылки на сессии, API endpoint-шаблоны, пути исходников и имена первичных JSON/test evidence не являются ссылками на документы ADB. Их доступность проверяется отдельно; локальный путь не считается доказательством доставки новому исполнителю.

Полный LS и встроенная инструкция установки входят в тела соответствующих документов. Перед использованием подтвердить эту версию через ADB и проверить SHA; никаких отдельных локальных .md для продолжения не требуется.


## Рамки этой редакции

Полная редакция принята Оператором для публикации 19.09.2026. Дата среза: 19.09.2026; свежесть отдельных
наблюдений указана в evidence, а не заменена датой сборки документа.
Действующий runtime: a26, dev / heavy02, deploy96 / wheel109.
Кандидат a30 имеет отдельную офлайн-приёмку и НЕ установлен.
Отсутствие prod deploy при dev_only является N/A, а не аварией.
Опубликованный UEPR описывает a26; новые правки этого комплекта одобрены для публикации. products_state отдельно не изменялся; аттестация BLOCKED.

Разрешены чтение, безопасная диагностика, файлы, OWUI Files и agent_work_log. Оператор отдельно разрешил публикацию документов этого комплекта.
Запрещены без отдельного разрешения установка, restart/stop, миграции, изменение
кода или конфигурации узла, регистрация wheel/deploy/прогонов,
deprecate/retire/cleanup и удаление данных. Тест с env=dev не становится безопасным
сам по себе. Примеры WRITE ниже являются описанием, не командой к исполнению.
Фрагменты запросов сокращены: к каждому добавляется actor по полному контракту.

Текущие нормативы получают по (env,product,doc_type,latest=true), SHA проверяют
при скачивании. ID/версии/SHA родителей ниже нужны для provenance, не для вечного
выбора current. Приложенная история никогда не является действующей инструкцией.


Родитель: id=588; версия=v0.20-consolidated; SHA-256 `24bc9c7380e76df526383b502a9d21c63e047efa9200ab60250e89de01e288db`.

## Изменения этой полной редакции

Вместо delta-шапки составлен полный действующий раздел сущностей, документов, контуров, транспорта, actor, наблюдаемости и ЖЦ. Сняты principal/WRITE-only и автоматический DROP как действующие инструкции; полный родитель сохранён в истории.

## Назначение и сущности

ADB хранит реестр продуктов, документов, узлов, артефактов и развёртываний H2.
Реестр описывает состояние; health, файлы и процессы подтверждают наблюдаемую
реализацию. Расхождение между ними фиксируется, а не устраняется догадкой.

| Сущность | Смысл и инвариант |
|---|---|
| products | Каноническое имя lower_snake_case; старые case-варианты требуют решения, не автоматического удаления |
| documents | Координаты env/product/doc_type и версия; тело в OWUI Files, SHA проверяется по байтам |
| documents_archive | Сохранённая история; не редактируется задним числом ради новой приёмки |
| doc_types | Динамический реестр; канонический литерал типа в get_document сохраняет регистр |
| wheels | Конкретный артефакт, версия, SHA архива; регистрация не доказывает установку |
| deploys | Активное развёртывание product/env/node, связанное с wheel; не заменяет runtime proof |
| products_state | Заявленное состояние; может отставать от active deploy, требуется явная сверка |
| nodes и JSON-манифесты | Инвентарь; host-level node.json является входом конфигурации узла |
| caller_events | Аудит вызовов; схема отличается от журнала работ |
| agent_work_log | Ход сессии исполнителя с actor/session/task и env; новейшие записи первыми |
| LS / validation runs | Исторические результаты с release-binding; прошлый GREEN не переносится на новый wheel |

## Документы и выбор актуальности

Канонический get_document использует env/product/doc_type/latest=true.
Для продуктового UEPR указывается node. Полученные id/version/file_id/SHA/time
фиксируются как evidence. В отсутствие тела или при несовпадении SHA чтение
не засчитывается. Документ с одинаковой строкой версии, но разными SHA требует
рассмотрения конфликта; эвристика «максимальный id» не заменяет канонический выбор.

Полная кумулятивная редакция содержит действующие разделы и явно отделённую историю.
Исторический отчёт не становится новым отчётом от смены заголовка. «Стандарт»
может быть типом разных серий, поэтому latest по общему типу не выбирает TDL.
Платформенные нормативы читаются в prod у владельца, продуктовые документы в
действующем контуре. Гайд Пользователя любого продукта хранится только в prod по решению Оператора. Прежнее dev-требование Гайда UEPR v0.8 не действует. Текущий адрес: `{"op":"get_document","env":"prod","product":"ai_docs_broker","doc_type":"Гайд Пользователя","latest":true}`; это исправление адреса, не доказательство Guide–Runtime Parity.

## Узел, файловая система и контуры

Пути сервиса, .venv, исходников, тестов, wheel, stdout/stderr берутся из node
configuration и измерений. Сервисный пакет установлен non-editable; repo и
site-packages различаются. Источник a30 заморожен отдельным архивом и manifest.
Нельзя сравнивать SHA каталога или .py с SHA wheel-архива.

Один dev-узел продукта определяется active deploy. Для ADB текущая топология
dev_only/heavy02; prod-проверки отсутствующего deploy имеют N/A. Dev содержит
реальные ценные данные, не тестовую disposable БД. Второй runtime/NATS/БД для
обхода приёмки не создаются. Состав применимых prod-проб берётся из действующего
Гайда ЖЦ, а не старого предположения, что любой продукт обязан иметь prod deploy.

## Транспорт и данные

OWUI Function принимает JSON операции с ключом op; внешний HTTP-конверт использует
model/messages/stream. NATS-transport получает параметры через штатный resolver
h2_shared и host registry, не из придуманного NATS_URL. Секреты в документы не входят.
Ответ может содержать транспортную обёртку; проверяются status/error, затем
фактические data.row/rows, env и корреляция. Пустой rows не означает ошибку запроса.
Нельзя принимать SQL-обход при ошибке фасада как штатный пользовательский путь.

## Actor, авторизация и аудит
Actor передаётся JSON-объектом во всех операциях, включая READ и get_document.
Поля: product, node, product_version, session_type, session_id, request_id, emitted_at, source_url.
Только bootstrap get_document допускает отсутствие product_version, но не всего actor.
Версия actor берётся из SHA-проверенного тела UEPR по порядку действующего Онбординга,
а не из metadata.version документа, health или предположения о кандидате.
Если канонический UEPR ещё описывает a25, это отдельно объявленное расхождение с runtime a26.

Для service, roo, owui и perplexity source_url обязателен и содержит действительный
полный URL сессии-инициатора. session_id является каноническим UUID текущего
исполнителя; request_id новый на каждый вызов; emitted_at имеет UTC ISO-8601 milliseconds Z.
Идентичность роли задаётся (product,node). Principal не передаётся, legacy principal
игнорируется; uploaded_by/deprecated_by формируются как product@node.
HMAC снят; hmac_phase и caller_kind не являются обязательными клиентскими полями.
Это контракт идентичности, а не разрешение изменять данные или обходить права операций.

Механизм node_credentials/HMAC исторический, не текущая клиентская обязанность.
Наличие старой миграции DROP в приложении не разрешает удаление таблиц.
Текущая авторизация конкретного WRITE проверяется отдельно; legacy principal
не восстанавливается как гейт «для нескольких операций».

## Наблюдаемость и точность

Хранить исходные ответы, observed_at и список команд. Реальное время измерения
не меняется при перепаковке. Migration head из health не доказывает backup.
Ротация не доказывает срок хранения. Registry deployed_at не обязательно момент
перезапуска. Офлайн-тесты не доказывают состояние службы.
## Журнал работы и закрытие

До чтения Онбординга записать start_session/started и start_get_onboarding/started.
После скачивания и SHA: end_get_onboarding/ok; после чтения: end_read_onboarding/ok.
Использовать ту же OWUI Function; entry содержит все известные target_product,
target_node, target_env, task_id, task_url, document_id/type/version/sha256/section
и occurred_at в UTC ISO-8601 milliseconds Z. Неизвестные значения не выдумывать.

Lowercase применяется только к машинным меткам env, actor.product/node/session_type,
verb/status, target_product/node/env и document_type записи журнала.
Регистр UUID, URL, task_id, document_version, SHA, document_section, remarks и
временных меток сохраняется. В get_document doc_type канонический, например «Онбординг».
Перед final прочитать все страницы list_agent_work_log с filters.actor_session_id
текущего канонического UUID; проверить env и отсутствие error. Последняя запись:
verb=final,status=ready. Отказ журнала: одна error при возможности и STOP, без retry.
Записи задним числом запрещены. final/ready означает готовность результата задачи,
а не автоматический GREEN продукта.


## Релиз, установка и критерии GREEN

Цепочка по действующему ЖЦ: frozen source → release manifest и wheel SHA →
разрешённая загрузка и регистрация артефакта с собственным wheel_id →
одобренная установка в существующий единственный runtime → runtime proof →
Guide–Runtime Parity → полный LS с наблюдением → независимый ai_validation →
завершение учёта release/deploy/product state и результатов приёмки → UEPR и аудит.
Первичная регистрация артефакта не означает успешную валидацию или GREEN.
Документ с частичным результатом не отменяет ни одного обязательного шага.

Runtime proof содержит observed_at, product, env, node, release_id, sys_executable,
module_file, package_version, wheel_sha256. Отдельно фиксируются служба, PID,
командная строка, пути и не-editable установка, соответствие файлов wheel RECORD.
Диагностический импорт не выдается за чтение памяти работающего процесса.
Анализ памяти не является дополнительным придуманным обязательным гейтом.
SHA wheel-архива сравнивается с архивом, а файлы пакета с соответствующими членами.

Установка кандидата и перезапуск в этой задаче НЕ выполняются. План установки и
rollback включены в Гайд Пользователя, раздел «Установка и откат a30» (координаты в таблице); перед исполнением
нужны разрешение, актуальный preflight, проверка backup и security.
ЖЦ теперь требует зарегистрировать артефакт после manifest и до установки/LS.
Для кандидата получить его собственный wheel_id и связать с SHA/release manifest;
ID a26 нельзя подставлять a30. Нормативная часть TD-2026-09-19-04 разрешена,
но permission, регистрация кандидата и вся живая цепочка остаются NOT_RUN.

Три независимые оценки:
-_PACKAGE: комплект полон, непротиворечив, SHA/ссылки/хронология проверены.
- OFFLINE_WHEEL: конкретный wheel связан с source и успешными офлайн-тестами.
- INSTALLED_RUNTIME_LIFECYCLE: все обязательные живые гейты пройдены на одном релизе.
Первые две не подменяют третью. Skip/NOT_RUN не считается PASS; total обязан быть >0.

## ИСТОРИЯ: точное полное тело родителя, НЕ АКТИВНО

Ниже сохранена прежняя версия для проверки кумулятивности. Старые actor, principal, HMAC, разрешения, версии, результаты и команды не исполняются.

# ADB Онтология v0.20-consolidated

Delta vs v0.19. Остальное — без изменений.

## §H-REMOVED. Снятие HMAC из онтологии (миграция 033, wheel 0.9.0a9)

### 7-REMOVED. Сущность `node_credentials`

Сущность `node_credentials` **больше не входит в актуальную онтологию** ADB. Таблица переименована в `_deprecated_node_credentials_2026_09_15` (миграция 033) и подлежит DROP в миграции 034 через ≥30 дней.

- До 0.9.0a8 сущность отвечала за пары `(node, key_id) → hmac_secret_encrypted` с TTL, KEK-шифрованием и ротацией через `rotate_node_credential`.
- Начиная с 0.9.0a9 эти отношения не используются: авторизация WRITE — только по `actor`.

### 8-REMOVED. Атрибут `documents.hmac_key_id` (audit)

Колонки `documents_audit_log.hmac_key_id`, `documents_audit_log.hmac_valid` удалены миграцией 033. Существующие исторические записи в архиве сохраняются как есть (audit-history не переписывается).

### 9-REMOVED. Инварианты про HMAC

Инварианты **сняты** с 0.9.0a9:
- ONT-AUTH-1: «WRITE op → HMAC-заголовок обязателен» → **СНЯТ**.
- ONT-AUTH-2: «HMAC secret хранится только encrypted KEK'ом и не покидает broker» → **СНЯТ**.
- ONT-AUTH-3: «bootstrap_mode ⇔ has_current_key=false» → **СНЯТ**.
- ONT-AUTH-4: «must_rotate=true ⇒ next op должна быть rotate_node_credential» → **СНЯТ**.

### 10-NEW. Единственный контракт авторизации WRITE

**Инвариант ONT-AUTH-5 (новый):**
> Каждая WRITE-op в broker ≥ 0.9.0a9 обязана нести валидный `actor{product,node,product_version,session_type,session_id,request_id,emitted_at,source_url,principal}`. Отсутствие любого поля → `E_INVALID_ACTOR` (`class=input`). Никакой другой механизм авторизации не применяется.

**Инвариант ONT-AUTH-6 (новый):**
> Broker ≥ 0.9.0a9 не возвращает ни один из кодов `E_HMAC_*`, `E_NODE_REVOKED`, `E_INSECURE_BOOTSTRAP`, `E_CURRENT_KEY_MISMATCH`. Наличие такого кода в ответе — свидетельство работы старого wheel или неисправного маршрута.

**Инвариант ONT-AUTH-7 (новый):**
> Op `rotate_node_credential` — REMOVED. Любой её вызов на broker ≥ 0.9.0a9 возвращает `E_OP_REMOVED` (`class=contract`). Op будет снят с whitelist в 0.9.0b1.

### 11-NEW. Гейт по principal (без изменений)

Гейт `actor.principal ∈ arch_actors` (см. §34 Гайда Архитектора) — единственный дополнительный слой авторизации поверх обязательного actor'а для op с ограниченными правами (`update_document_description`, `deprecate_document`, `retire_node` и др.). Работает независимо от HMAC (который снят) — только по content-based match.

## Совместимость (обновлено)

- Old-клиенты (client < 0.2.0): их HMAC-заголовки игнорируются сервером ≥ 0.9.0a9. Actor всё равно обязателен и валидируется.
- New-клиенты (client >= 0.2.0): HMAC-заголовков не шлют. Actor обязателен.
- `full_state_report.schema_meta.enums.doc_type` — вычислимая проекция активных типов (см. v0.19), без изменений.

## §5, §6 (напоминание, без изменений)

Сущности `doc_type` и `documents.description` работают как описано в v0.19. Регекс, коллация, in_use_count, гейты `E_DOC_TYPE_*` — без изменений.

---

## §CONSOLIDATION

**Версия:** `v0.20-consolidated`. Полный кумулятивный текст для `Онтология`. Актуальная версия `521` сохранена первой; ниже сохранены все доступные предыдущие версии этой линии для буквенной непрерывности секций. Это не delta-документ: нормативный актуальный текст находится выше, исторические снимки добавлены только для проверки эволюции.

## §HISTORICAL-PRESERVATION


### Исходная версия 0.7 (doc_id=113)

# L2 Онтология ai_Docs_broker v0.7

**Уровень:** L2 (Онтология)
**Дата:** 2026-09-06
**Применимо к:** wheel `0.4.3`
**Заменяет:** Онтология v0.6 (0.4.2)

---

## Что нового vs v0.6

1. **Enum `operation`** пополнен: `list_caller_events`, `who_touched`, `rotate_node_credential`.
2. **`node_credentials`.`bootstrap_mode`** становится обязательным полем при выпуске нового credential (default `secure` в 0.4.3; `insecure` — только у legacy-строк из 0.4.2).
3. **`node_credentials`.`revoked_at`** — новый nullable-столбец (миграция 008), нужен для grace-окна ротации.
4. **`hmac_secret_enc`** — формат: Fernet-cipher поверх `ADB_KEK` (в 0.4.2 fallback допускал plaintext).
5. **`broker_hints.actor_phase`** и **`hmac_phase`** — переходят на `enforce` для write-ops.
6. **Retention:** `caller_events` имеет TTL = 30 дней, обеспеченный cron-скриптом.

---

## 1. Основные сущности

### 1.1. `products`
- `id`, `name` (unique), `env`, `created_at`.
- Нормализация имени: `LOWER(name)` для match/upsert.

### 1.2. `documents`
- `id`, `product_id`, `doc_type` (enum, см. §2), `version`, `applies_to_version_range`, `owui_file_id`, `filename`, `sha256`, `size`, `uploaded_by`, `comment`, `created_at`, `updated_by_request_id` (nullable).
- Уникальность: `(product_id, env, doc_type, applies_to_version_range)` — при upsert старая запись уходит в `documents_archive`.

### 1.3. `documents_archive`
- Полная копия `documents` + `archived_at`, `archive_reason`, `archived_by_request_id`.

### 1.4. `nodes`
- `id`, `node_id` (unique), `class` (`heavy|light|laptop|virtual`), `purpose`, `registered_by`, `registered_at`.

### 1.5. `node_credentials`
- `id`, `node_pk` FK → `nodes.id`, `key_id` (unique, `nc_<random22>`), `hmac_secret_enc` (Fernet), `bootstrap_mode` (enum: `secure|insecure|n_a`), `created_at`, `expires_at`, **`revoked_at` (nullable, новое в 0.4.3)**, `revoke_reason` (nullable), `rotated_from_key_id` (nullable, self-ref).
- Активный credential: `revoked_at IS NULL AND expires_at > NOW()`. Grace-режим: `revoked_at IS NOT NULL AND revoked_at + grace_window > NOW()` — принимается для read.

### 1.6. `products_state`
- `id`, `product_id`, `env`, `product_version`, `node_id` FK → `nodes.id`, `role` (`prod|staging|test`), `deployed_at`, `deployed_by`, `wheel_sha256`, `comment`, `updated_by_request_id`.
- Уникальность: `(product_id, env, node_id)`.

### 1.7. `caller_events`
- Полная схема: см. Snapshot 2026-09-06 (ADB id=110).
- Новые ограничения в 0.4.3:
  - `bootstrap_mode` возвращается в ответе и записывается в event.
  - Retention 30 дней.

### 1.8. `_ops_migrations_log`
- `migration_id`, `applied_at`, `checksum`, `metadata_json` (для 008 — `reencrypted_count`, `skipped_count`, `backup_table`).

---

## 2. Enum `doc_type` (7 значений, без изменений)

`Гайд Пользователя`, `Гайд Архитектора`, `Онтология`, `Live Scenario`, `Дорожная Карта`, `Стандарт`, `Отчёт Валидации`.

## 3. Enum `operation` (0.4.3)

Read-ops: `get_document`, `full_state_report`, `list_products_state`, `list_nodes`, `get_document_for_node`, **`list_caller_events`**, **`who_touched`**.

Write-ops: `register_document`, `register_product_state`, `register_node`, **`rotate_node_credential`**.

Bootstrap: `issue_credential` (создаёт первый `node_credentials` для узла; в 0.4.3 требует правильный `ADB_BOOTSTRAP_SECRET`).

## 4. Enum `actor_session_type`

`roo | perplexity | owui | cli | service | cron | __legacy__` (без изменений).

## 5. Enum `bootstrap_mode` (0.4.1+)

- `secure` — credential выпущен при secure-сервере (Fernet + valid bootstrap secret).
- `insecure` — credential выпущен в fallback-режиме 0.4.2 (plaintext, любой bootstrap).
- `n_a` — read-op без credential (в `caller_events`).

## 6. Enum `result_status`

`ok | client_error | server_error` (без изменений).

## 7. Enum ошибок (0.4.3)

Добавлены: `E_ACTOR_REQUIRED`, `E_HMAC_REQUIRED`, `E_MISSING_ADB_KEK`, `E_INVALID_BOOTSTRAP_SECRET`, `E_INSECURE_BOOTSTRAP`, `E_CURRENT_KEY_MISMATCH`, `E_ALREADY_ROTATING`.

Сохраняются: `E_UNKNOWN_OP`, `E_MISSING_FIELD`, `E_BAD_VERSION_RANGE`, `E_INVALID_DOC_TYPE`, `E_ACTOR_MISSING_*`, `E_HMAC_MISSING`, `E_HMAC_INVALID`, `E_HMAC_REPLAY`, `E_NODE_REVOKED`.

## 8. Инварианты

1. Никогда не удалять `documents` — всегда через архив.
2. `caller_events` — append-only (retention удаляет пачками старше 30 дней).
3. `node_credentials.hmac_secret_enc` не выдаётся наружу никогда, кроме первого ответа `issue_credential`/`rotate_node_credential`.
4. `broker_hints` возвращается в каждом ответе (успех и ошибка).
5. Actor — обязателен для всех write в 0.4.3; для `list_caller_events`/`who_touched` — тоже (аудит).

## 9. Migration graph

`001..007` — как в 0.4.2. `008` в 0.4.3:

- Добавляет `node_credentials.revoked_at`, `node_credentials.revoke_reason`, `node_credentials.rotated_from_key_id`.
- Создаёт бэкап-таблицу `nodes_credentials_backup_pre_008` (TTL 30 дней).
- Python-хук `008_reencrypt.py` при старте сервиса с `ADB_KEK`: plaintext `hmac_secret_enc` → Fernet.

§END


### Исходная версия v0.6 (doc_id=230)

# L2 Онтология ai_Docs_broker v0.6

**Что изменилось v0.4 → v0.5 (2026-09-05):**

1. **enum `doc_type` расширен с 5 до 7 значений**: добавлены `Стандарт` и `Отчёт Валидации` (миграция `002_doc_type_enum_v2.sql`). Отражено в §5.2, §8.3.5, §8.4, §Приложение C.
2. §8.3.5 — пример `schema_meta.enums.doc_type` содержит 7 значений.
3. §8.4 — `E_BAD_DOC_TYPE` ссылается на enum из 7 значений.
4. §Приложение C — DDL `documents`/`documents_archive`: колонка `doc_type` ENUM(7).
5. §5.2 — упоминание миграции `002_doc_type_enum_v2.sql` в журнале `h2_migration_log`.

Остальное содержание v0.4 сохраняется без изменений.

**Статус:** PROPOSED
**Автор:** ИИ-архитектор H2
**Дата:** 2026-09-04
**Опора:**
- L1 Концепт ai_Docs_broker v0.4.
- Дорожная Карта ai_Docs_broker v0.3 (введение `register_node` в wheel 0.4.0).
- L2 Live Scenario ai_Docs_broker v0.2.
- L3 Гайд Пользователя ai_Docs_broker v0.4.
- Реальный `node.json` HEAVY02 (2135 байт, raw sha256 `86168494edb8db8f87f5ac7c74e4004f5d648f79aead50ca950c8885bcf158e7`).
- Live-инвентаризация ai_Docs_broker на HEAVY02 (2026-09-03, task `ADB-INV-V04`, `inventory.json` sha256 `d786eda15623068b7df10634a35f140ff6c5b29624c3992bfe693620fd70f8d2`, OWUI file_id `21672e77-1909-4ab1-b00e-616754899eab`).
- Пакет ai_docs_broker 0.2.0 на HEAVY02 (bump 0.1.0→0.2.0, wheel sha256 `424cf23bd1eaa588157fd63aa61dda12c85efdbb55bb8e26e27680f957cf6908`, task `ADB-FSR-V02`).

**Что изменилось v0.3 → v0.4 (2026-09-04):**

1. §5.2 расширен — две новые таблицы `nodes` и `nodes_archive` (вводятся в wheel 0.4.0). Схема переходит с 5 на 7 таблиц (+ `h2_migration_log`).
2. §5.4 расширен — KNOWN_OPS вырастает с 5 до 7 (добавлены `register_node`, `list_nodes`). Флаг «wheel_target: 0.4.0».
3. §8.3 — добавлены контракты `§8.3.6 register_node` и `§8.3.7 list_nodes` (в planned-статусе до wheel 0.4.0).
4. §8.4 — добавлены коды ошибок `E_NODE_NOT_REGISTERED`, `E_BAD_NODE_CLASS`, `E_BAD_NODE_STATUS`.
5. §Приложение C — добавлен planned-DDL для `nodes` и `nodes_archive` (миграция `002_schema_v2_nodes.sql`, применяется с wheel 0.4.0).
6. Новый §Приложение D — таблица «Флот H2» (6 узлов: 2 active + 4 planned), симметрична §8.1 Концепта v0.4.
7. **v0.6 (этап 2, wheel 0.4.0 применён 2026-09-05):** сняты пометки «planned в 0.4.0» с §8.3.6/§8.3.7 и DDL `nodes`/`nodes_archive`. Приложение D теперь отражает реальный `SELECT * FROM nodes` после первого `register_node` ×6 (evidence: Live Scenario v0.4-draft, 17/17 green, файл OWUI `fd3eea24-96a2-4506-b362-d0ecb0dad104`).

**Что изменилось v0.2 → v0.3:**

1. §5.2 — исправлен ключ уникальности `products_state`: **`UNIQUE (node, product)`** (без `env`) — так реально в БД (индекс `uq_products_state_node_product`, подтверждено `inventory.json`). В v0.2 было ошибочно написано `UNIQUE (node, product, env)`.
2. §5.2 — колонки таблиц приведены в соответствие реальному DDL (13 полей в `documents_archive`, 11 в `documents`, 11 в `products_state`, 14 в `products_state_archive`, 4 в `h2_migration_log`).
3. §5.4 — новое: перечень операций и их обработчики в исходнике; ссылка на `_OPS` в `agent.py`.
4. §8.3 — добавлена **5-я операция `full_state_report`** (введена в 0.2.0). Полный контракт с опциональными фильтрами `node`, `product`, `doc_type`, `env`, `include_archives`, `include_products_state`.
5. §8.4 — добавлены коды `E_BAD_ENV`, `E_BAD_DOC_TYPE`, `E_BAD_SHA256`, `E_BAD_FILE_ID`, `E_MISSING_FIELD`, `E_UNSUPPORTED_OPERATION`, `E_DB_UNAVAILABLE`, `E_DB_INTEGRITY`, `E_DB_TIMEOUT`, `E_DB_UNKNOWN`, `E_INTERNAL` — реальные коды из `ai_docs_broker.errors` и `ops/handlers.py`.
6. §Приложение C — снимок реальной схемы БД HEAVY02 с точными типами, индексами и `row_counts`.

**Назначение документа:** зафиксировать техническое устройство ai_Docs_broker — где физически находится каждая сущность и как её достать — так, чтобы описание было переносимо между узлами H2 (HEAVY01/02/03/…). Все пути и адреса выражены как ссылки на реальные поля `node.json` конкретного узла, а не как абсолютные литералы.

Читателю: этот документ читается вместе с L1 Концепт (что делает продукт) и L3 Гайд Пользователя (как пользоваться). Онтология не описывает поведение — только «что и где» лежит.

---

## §1. Область онтологии

Онтология покрывает 7 доменов сущностей:

1. **Файловая система** — где лежат исходники, venv, wheel, конфиги, логи.
2. **Windows-сервис (NSSM)** — под каким именем и с какими параметрами продукт живёт как сервис.
3. **MariaDB** — своя схема, свой сервисный пользователь, DDL таблиц.
4. **NATS и KV** — subjects, стримы, KV-бакеты и конкретные ключи.
5. **OWUI** — Function-модель, публичный URL, файлы документов.
6. **Транспорт** — контракт payload запросов и ответов, коды ошибок.
7. **Зависимости** — что должно быть установлено на узле до bootstrap.

Не покрывается:
- Прикладной интерфейс операций (это L3 Гайд Пользователя).
- Живая последовательность bootstrap и regression (это L2 Live Scenario).
- Бизнес-смысл и границы (это L1 Концепт).

---

## §2. Контракт узла — `node.json`

Онтология везде ссылается на поля `node.json`. Полная схема `node.json` — предмет отдельного документа **L2 Онтология H2**. Здесь фиксируем только те поля, от которых зависит ai_Docs_broker.

### §2.1. Поля `node.json`, которые ai_Docs_broker читает напрямую

| Ссылка в онтологии | Реальный путь в `node.json` | Пример HEAVY02 |
|---|---|---|
| `${node.id}` | `node_id` | `heavy02` |
| `${node.role}` | `role` | `h2-node` |
| `${node.paths.programdata_root}` | `paths.programdata_root` | `C:\ProgramData\H2` |
| `${node.paths.h2_root}` | `paths.h2_root` | `C:\h2` |
| `${node.paths.bridge_home}` | `paths.bridge_home` | `C:\h2\h2_shared` |
| `${node.paths.logs_root}` | `paths.logs_root` | `C:\h2\h2_shared\logs` |
| `${node.paths.evidence_root}` | `paths.evidence_root` | `C:\h2\h2_shared\evidence` |
| `${node.limits.max_command_bytes}` | `limits.max_command_bytes` | `262144` |
| `${node.limits.max_export_bytes}` | `limits.max_export_bytes` | `33554432` |
| `${node.connection_profiles.nats}` | `connection_profiles.nats` | `h2-nats-prod` |
| `${node.connection_profiles.sql}` | `connection_profiles.sql` | `h2-sql-audit` |
| `${node.connection_profiles.owui}` | `connection_profiles.owui` | `h2-owui` |
| `${node.nats.subject_namespace}` | `nats.subject_namespace` | `h2` |
| `${node.object_store.artifact_kv.buckets}` | `object_store.artifact_kv.buckets` | `["h2-artifacts"]` |
| `${node.object_store.artifact_kv.max_bytes}` | `object_store.artifact_kv.max_bytes` | `67108864` |

`node.json` в H2 не хранит значения URL, портов и API-ключей. Он хранит имена профилей подключений (`connection_profiles.nats`, `.sql`, `.owui`, `.log_sink`), а реальные значения резолвятся через `h2_shared` API по имени профиля.

### §2.2. Что не в `node.json` — резолвится через профиль

| Логическая сущность | Как получить | Комментарий |
|---|---|---|
| NATS URL и creds | h2_shared по `${node.connection_profiles.nats}` | На HEAVY02 профиль `h2-nats-prod` |
| MariaDB DSN сервисного пользователя | h2_shared secrets по ключу `mariadb_ai_docs_broker_url` в KV `H2_SECRETS` | Бакет KV резолвится через `${node.connection_profiles.nats}` |
| Одноразовый admin DSN MariaDB | h2_shared secrets по ключу `mariadb_root_url` в KV `H2_SECRETS` | Канон продукта |
| OWUI base URL и Bearer | h2_shared по `${node.connection_profiles.owui}` | ai_Docs_broker сам не аплоадит в OWUI (кроме операций подачи документа) |
| NSSM binary path | Из PATH или `nssm --version` | Не поле node.json |

### §2.3. Правила чтения

- ai_Docs_broker при старте читает `node.json` один раз через публичный API h2_shared.
- Если хоть одно обязательное поле отсутствует — fail-closed на bootstrap с ошибкой `E_NODE_CONFIG_INCOMPLETE`, указывающей конкретное поле.
- Продукт не проверяет `integrity.canonical_payload_sha256` — это работа h2_shared kernel.
- Поле `compatibility.mode` читается только h2_shared kernel; ai_Docs_broker его игнорирует.

---

## §3. Файловая система

| Сущность | Путь |
|---|---|
| Корень продукта | `${node.paths.h2_root}\ai_Docs_broker` |
| venv продукта | `${node.paths.h2_root}\ai_Docs_broker\.venv` |
| Python venv-исполнителя | `${node.paths.h2_root}\ai_Docs_broker\.venv\Scripts\python.exe` |
| Исходники (dev) | `${node.paths.h2_root}\ai_Docs_broker\src\ai_docs_broker\` |
| Установленный пакет (site-packages) | `${node.paths.h2_root}\ai_Docs_broker\.venv\Lib\site-packages\ai_docs_broker\` |
| Wheelhouse | `${node.paths.h2_root}\ai_Docs_broker\wheelhouse\ai_docs_broker-<version>-py3-none-any.whl` |
| SQL миграции | `${node.paths.h2_root}\ai_Docs_broker\src\ai_docs_broker\migrations\*.sql` |
| stdout/stderr сервиса | `${node.paths.logs_root}\ai_docs_broker\stdout.log` / `stderr.log` |
| Evidence live-сценариев | `${node.paths.evidence_root}\ai_docs_broker\` |
| PMA-задачи ИИ-архитектора | `${node.paths.h2_root}\ai_Docs_broker\_tasks\<TASK_ID>\` |

**Инварианты:**
- ai_docs_broker никогда не пишет в `${node.paths.bridge_home}\...` — это территория h2_shared kernel.
- Логи ротирует NSSM (по `limits.max_log_file_bytes` / `limits.max_log_file_count` из node.json — 10 MiB × 10 файлов на HEAVY02).

---

## §4. Windows-сервис (NSSM)

| Атрибут | Значение |
|---|---|
| Имя сервиса | `H2_HEAVY02_AI_DOCS_BROKER_SVC` (шаблон `H2_<NODE_ID_UPPER>_AI_DOCS_BROKER_SVC`) |
| Application | `${node.paths.h2_root}\ai_Docs_broker\.venv\Scripts\python.exe` |
| AppParameters | `-m ai_docs_broker.main` |
| AppDirectory | `${node.paths.h2_root}\ai_Docs_broker` |
| AppStdout | `${node.paths.logs_root}\ai_docs_broker\stdout.log` |
| AppStderr | `${node.paths.logs_root}\ai_docs_broker\stderr.log` |
| AppEnvironmentExtra | `AGENT_ROOT=${node.paths.h2_root}\ai_Docs_broker`, `H2_ALLOW_RING0=1` |
| Start type | `SERVICE_AUTO_START` |
| Recovery | Restart on failure, delay 5000ms |
| Run-as | `NT AUTHORITY\SYSTEM` (SID `S-1-5-18`) |

Без `AGENT_ROOT` listener h2_shared не поднимается — задокументированная ловушка рантайма.

---

## §5. MariaDB — собственная схема

### §5.1. Пользователь и подключение

| Атрибут | Значение |
|---|---|
| Схема | `ai_docs_broker` |
| Сервисный пользователь | `ai_docs_broker_svc@127.0.0.1` |
| Пароль | Генерируется `secrets.token_urlsafe(32)` при первом bootstrap, лежит в KV-ключе `mariadb_ai_docs_broker_url` |
| GRANT | `SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, REFERENCES ON ai_docs_broker.*` |
| KV bucket | `H2_SECRETS` (канон h2_shared) |
| Ключ с сервисным DSN | `mariadb_ai_docs_broker_url` |
| Ключ с одноразовым admin DSN | `mariadb_root_url` (после успешного bootstrap `cleared=true`) |

**Инварианты доступа:**
- ai_docs_broker никогда не читает `mariadb_audit_url` и не пишет в схему `h2_audit`.
- В логах не появляются значения DSN, паролей, `IDENTIFIED BY`, `mysql+pymysql://…`.
- При отсутствии обоих DSN — fail-closed `E_DB_UNAVAILABLE`.

### §5.2. Таблицы схемы `ai_docs_broker`

Пять таблиц в wheel 0.2.0 (миграция `001_schema_v1.sql`) + две добавляются в wheel 0.4.0 (миграция `002_schema_v2_nodes.sql`):

| Таблица | Назначение | Ключ уникальности | Ввод |
|---|---|---|---|
| `documents` | Одна строка на `(env, product, doc_type)` — актуальный документ | `UNIQUE (env, product, doc_type)` (индекс `uq_documents_env_product_doc_type`) | 0.1.0 |
| `documents_archive` | Все предыдущие версии документов | `PRIMARY KEY (archive_id)`; индекс `MUL (env)` | 0.1.0 |
| `products_state` | Одна строка на `(node, product)` — актуальное состояние | **`UNIQUE (node, product)`** (индекс `uq_products_state_node_product`) — БЕЗ `env` | 0.1.0 |
| `products_state_archive` | Все предыдущие версии state | `PRIMARY KEY (archive_id)` | 0.1.0 |
| `nodes` ¹ | Реестр узлов флота H2, одна строка на `node_id` | `UNIQUE (node_id)` (индекс `uq_nodes_node_id`) | **0.4.0 (planned)** |
| `nodes_archive` ¹ | Все предыдущие состояния узлов | `PRIMARY KEY (archive_id)` | **0.4.0 (planned)** |
| `h2_migration_log` | Журнал применённых миграций схемы | `UNIQUE (migration_name)` + `checksum SHA256` | 0.1.0 |

Миграция **`002_doc_type_enum_v2.sql`** (применена на HEAVY02 в рамках Этапа 0
Дорожной Карты AVAL Universalization v11, 2026-09-04) — `ALTER TABLE documents`
и `documents_archive`: `doc_type` расширен с ENUM(5) до **ENUM(7)** (добавлены
`'Стандарт'`, `'Отчёт Валидации'`). До применения миграции
`register_document` с новыми doc_type падал `E_DB_UNKNOWN` (колонка в MySQL —
ENUM, не VARCHAR; расширения Python-enum недостаточно). Запись о миграции — в
`h2_migration_log` с checksum SHA256 файла.

¹ — в wheel 0.2.0 таблицы отсутствовали; DDL перенесён в реальную схему миграцией `004_nodes_v1.sql` в рамках wheel 0.4.0 (applied 2026-09-05 20:26:56 UTC на HEAVY02). §Приложение D теперь отражает реальный `SELECT * FROM nodes`, а не только плановый снимок.

**Правило обновления `documents` (`register_document`):**
1. Открывается транзакция.
2. Если в `documents` есть строка с `(env, product, doc_type)` — она атомарно переносится в `documents_archive` (`archive_reason='update'`, `archived_at=UTC_TIMESTAMP(3)`), актуальная строка обновляется `UPDATE`.
3. Если строки нет — `INSERT` в `documents`.
4. Транзакция коммитится.

Тот же паттерн для `products_state` / `products_state_archive`. **Модель хранения:** ключ `(node, product)` означает, что для одного продукта на одном узле поддерживается ровно одна активная запись state — независимо от `env`; смена `env` (dev→prod) для того же (node, product) переносит старую строку в архив.

### §5.3. Класс доступа `AiDocsBrokerDB`

- Модуль: `ai_docs_broker.storage.db`.
- Драйвер: **pymysql** (не SQLAlchemy — по паттерну H2Audit).
- Пул: per-thread connection cache (`threading.get_ident()`).
- Транзакции: `AiDocsBrokerDB.transaction(callable)` — явный commit/rollback внутри.
- DSN резолвится: `override → env → h2_shared.secrets.get_service_dsn()`.
- Наружу выбрасывается только `AiDocsBrokerDbError(subcode ∈ {E_DB_UNAVAILABLE, E_DB_INTEGRITY, E_DB_TIMEOUT, E_DB_UNKNOWN})`.
- Категорически запрещено импортировать `h2_shared.audit.h2_audit.H2Audit` — это паттерн, а не библиотека.

### §5.4. Регистрация операций в `AgentRuntime`

Все операции регистрируются в `AiDocsBrokerAgent.__init__` через **instance-level** `register_operation_handler(op_name, handler)`. Источник имён — кортеж `_OPS` в `ai_docs_broker/agent.py`. Каждый handler получает `payload dict` и возвращает полный response dict; NATS-listener сам публикует ответ в `.reply.<correlation_id>`.

**Актуальный список `_OPS` (5 операций в wheel 0.2.0 → 7 в wheel 0.4.0):**

| Операция | Handler в `ops/handlers.py` | Read-only | Введена |
|---|---|---|---|
| `list_products_state` | `list_products_state` | ✓ | 0.1.0 |
| `register_product_state` | `register_product_state` |  | 0.1.0 |
| `get_document` | `get_document` | ✓ | 0.1.0 |
| `register_document` | `register_document` |  | 0.1.0 |
| `full_state_report` | `full_state_report` | ✓ | 0.2.0 |
| `register_node` ² | `register_node` |  | **0.4.0 (planned)** |
| `list_nodes` ² | `list_nodes` | ✓ | **0.4.0 (planned)** |

² — операции планируются в wheel 0.4.0 (Дорожная Карта ADB v0.3, §0.4.0). В текущем wheel 0.2.0 обе возвращают `E_UNSUPPORTED_OPERATION` — что и есть ожидаемое поведение до выката 0.4.0.

Диспетчеризация — `HANDLERS: dict[str, callable]` + функция `dispatch(payload, db)` в `ops/handlers.py`. Функция `dispatch` принимает обе формы payload (плоскую с `op` и обёрнутую с `operation` + `payload`); нормализация — там же.

---

## §6. NATS и KV

### §6.1. Subjects

| Направление | Subject |
|---|---|
| Запрос к ai_Docs_broker | `${node.nats.subject_namespace}.ai_docs_broker.request` (на HEAVY02: `h2.ai_docs_broker.request`) |
| Ответ | `${node.nats.subject_namespace}.ai_docs_broker.reply.<correlation_id>` |

Инвариант: ai_docs_broker не публикует ничего вне `${node.nats.subject_namespace}.ai_docs_broker.*`.

### §6.2. Каноничные NATS-ресурсы

| Ресурс | Канон h2_shared | Источник имени |
|---|---|---|
| Событийный стрим | `h2-events` | Канон |
| KV agent-board | `h2-agent-board` | Канон |
| KV secrets | `H2_SECRETS` | Канон |
| Object Store | `${node.object_store.artifact_kv.buckets[0]}` (на HEAVY02: `h2-artifacts`) | node.json |

### §6.3. Ключи KV

| Ключ (внутри `H2_SECRETS`) | Роль | Кто пишет | Кто читает |
|---|---|---|---|
| `mariadb_root_url` | Одноразовый admin DSN | Оператор (провижининг) | ai_docs_broker только на первом bootstrap; после успеха ключ ротируется в `cleared=true` |
| `mariadb_ai_docs_broker_url` | Постоянный сервисный DSN | ai_docs_broker при первом bootstrap | ai_docs_broker при каждом старте |

### §6.4. Ловушка `secrets`-клиента

Персистентный NATS-клиент чтения `H2_SECRETS` может протухать между sync-вызовами в долгоживущих процессах:

```python
from h2_shared.secrets_api import invalidate_nats_connection, invalidate_cache
invalidate_nats_connection()
invalidate_cache()
```

Не патчить h2_shared, не создавать свой NATS-клиент.

---

## §7. OWUI

| Атрибут | Значение |
|---|---|
| Профиль подключения | `${node.connection_profiles.owui}` (HEAVY02: `h2-owui`) |
| Base URL | Резолвится h2_shared по имени профиля |
| Bearer | Резолвится h2_shared, не логируется |
| OWUI Function | `h2_ai_docs_broker_function` — thin proxy на `h2.ai_docs_broker.request` / `.reply.<correlation_id>` |
| Files API upload | `POST <owui_base>/api/v1/files/` (обязательный **trailing slash**) |
| Files API download | `GET <owui_base>/api/v1/files/<owui_file_id>/content` |
| Files API search | `GET <owui_base>/api/v1/files/search?filename=<name>` (без trailing slash) |

**Ловушки OWUI:**
1. Без trailing slash на `/api/v1/files/` upload возвращает 405.
2. С trailing slash на `/api/v1/files/search/` search возвращает SPA HTML.
3. Сертификат внутренний — принимать self-signed для внутренних вызовов.
4. POST `/api/v1/files/` возвращает `hash: null` — sha256 клиент считает сам.

### §7.1. Двойная обёртка ответа

Ответ OWUI Function приходит трижды завёрнутым:
- OpenAI JSON (`choices[0].message.content`)
- SSE (`data: {...}\n\ndata: [DONE]`)
- `delta.content` — JSON-строка `{"status":"ok"|"error", "correlation_id":..., "data":...}`.

Клиент распаковывает три слоя.

---

## §8. Транспортный контракт

### §8.1. Формат запроса

Плоский:
```json
{"op": "<operation>", "<field>": "...", "correlation_id": "..."}
```

Обёрнутый:
```json
{"operation": "<operation>", "payload": {"<field>": "..."}, "correlation_id": "..."}
```

Обе формы принимаются диспетчером `dispatch()`.

### §8.2. Формат ответа

Успех:
```json
{"status":"ok","correlation_id":"...","data":{...}}
```

Ошибка:
```json
{"status":"error","error_code":"E_XXX","correlation_id":"...","data":{"message":"..."}}
```

`datetime` в `data` сериализуется в `YYYY-MM-DDTHH:MM:SS.mmmZ` (utility `_json_safe`).

### §8.3. Операции (5 в wheel 0.2.0, +2 в wheel 0.4.0 — реализованы)

#### 8.3.1. `list_products_state`

Обязательные: `node`, `product`.
Ответ: `{"present": bool, "row": {...}?}`.

#### 8.3.2. `register_product_state`

Обязательные: `node`, `product`, `env`, `version`, `wheel_version`, `wheel_path`, `is_development_wheel` (bool), `updated_by`.
Опциональные: `comment`.
Ответ: `{"created": bool, "updated": bool, "new_id": int, "archive_id": int?}`.

#### 8.3.3. `get_document`

Обязательные: `env`, `product`, `doc_type`.
Опциональные: `include_history` (bool, default false).
Ответ: `{"present": bool, "row": {...}?, "history": [...]?}`.

#### 8.3.4. `register_document`

Обязательные: `env`, `product`, `doc_type`, `version`, `owui_file_id` (UUID), `filename`, `sha256` (64 hex), `uploaded_by`.
Опциональные: `comment`.
Ответ: `{"created": bool, "updated": bool, "new_id": int, "archive_id": int?}`.

#### 8.3.5. `full_state_report` — **новое в 0.2.0**

Read-only. Возвращает полную выгрузку зарегистрированных объектов с опциональными фильтрами.

**Запрос:**
```json
{
  "operation": "full_state_report",
  "node": "heavy02",           // optional — фильтр products_state.node
  "product": "ai_Docs_broker", // optional — фильтр по обоим срезам
  "doc_type": "Онтология",     // optional — фильтр documents.doc_type; валидируется по enum
  "env": "dev",                // optional — фильтр по env в обоих срезах; валидируется по enum
  "include_archives": true,       // optional, default true
  "include_products_state": true, // optional, default true
  "correlation_id": "..."
}
```

Комбинация фильтров — конъюнкция (`AND`). Фильтр `node` применяется только к `products_state` / `products_state_archive` (в `documents` нет колонки `node`). Фильтр `doc_type` применяется только к `documents` / `documents_archive`. Фильтр `product` и `env` — к обоим срезам.

**Ответ (успех):**
```json
{
  "status": "ok",
  "data": {
    "generated_at_utc": "2026-09-03T07:25:07.048Z",
    "filters": {"node": null|"...", "product": null|"...", "doc_type": null|"...", "env": null|"...", "include_archives": true, "include_products_state": true},
    "counts": {"documents": N, "documents_archive": N, "products_state": N, "products_state_archive": N},
    "schema_meta": {
      "tables": ["documents","documents_archive","products_state","products_state_archive"],
      "enums": {"env": ["dev","test","prod"], "doc_type": ["Гайд Пользователя","Гайд Архитектора","Онтология","Live Scenario","Дорожная Карта","Стандарт","Отчёт Валидации"]}
    },
    "documents": [ {row}, ... ],
    "documents_archive": [ {row}, ... ],
    "products_state": [ {row}, ... ],
    "products_state_archive": [ {row}, ... ]
  }
}
```

**Инварианты `full_state_report`:**
- Ни один SELECT не пересекает границу схемы `ai_docs_broker`.
- Все параметры фильтров биндятся через `%s`; никакой f-string подстановки клиентских значений в SQL.
- Read-only: не выполняет INSERT/UPDATE/DELETE.
- При `include_archives=false` архивные срезы возвращаются пустыми `[]`, а counts архивов = `0`.
- При `include_products_state=false` `products_state` и `products_state_archive` возвращаются пустыми `[]`.

#### 8.3.6. `register_node` — **введено в 0.4.0**

Обязательные: `node_id` (строка, соответствует `node.json.node_id`), `class` (enum: `heavy`|`light`|`laptop`), `purpose` (свободная строка), `registered_by`.
Опциональные: `status` (enum: `active`|`planned`|`retired`, default `planned`), `comment`.

**Семантика:** archive-then-update-in-place, тот же паттерн, что в `register_document` и `register_product_state`. При первом вызове для `node_id` — INSERT (в `nodes`), `created=true`. При повторном — предыдущее состояние уходит в `nodes_archive` (`archive_reason='update'`), актуальная строка обновляется UPDATE-ом, `updated=true`, `archive_id` — id новой архивной строки.

Ответ: `{"created": bool, "updated": bool, "new_id": int, "archive_id": int?, "node": {"node_id": "...", "class": "...", "status": "...", "purpose": "...", "registered_at": "...", "registered_by": "..."}}`.

**Валидация:** `class` вне enum → `E_BAD_NODE_CLASS`; `status` вне enum → `E_BAD_NODE_STATUS`; пустой `node_id` или `purpose` → `E_MISSING_FIELD`.

**Связь с `register_product_state`:** с wheel 0.4.0 `register_product_state(node=...)` выполняет lookup в `nodes`; если `node_id` не зарегистрирован — `E_NODE_NOT_REGISTERED`. Первый успешный `register_product_state` для узла со статусом `planned` автоматически переводит его в `active` (транзакционно, одна общая транзакция).

#### 8.3.7. `list_nodes` — **введено в 0.4.0**

Read-only. Обязательных полей нет.
Опциональные: `class` (enum: `heavy`|`light`|`laptop`), `status` (enum: `active`|`planned`|`retired`), `include_archives` (bool, default false).

**Семантика:** возвращает все строки `nodes` (актуальные), опционально с фильтрами по `class`/`status`. Фильтры — конъюнкция. При `include_archives=true` дополнительно возвращается срез `nodes_archive` с теми же фильтрами.

Ответ: `{"nodes": [{"node_id": "...", "class": "...", "status": "...", "purpose": "...", "registered_at": "...", "registered_by": "...", "updated_at": "..."}, ...], "counts": {"nodes": N, "nodes_archive": N?}}`.

**Связь с `full_state_report`:** с wheel 0.4.0 `full_state_report(include_nodes=true)` включает блок `nodes` (тот же формат, что выше) в единый отчёт.

### §8.4. Коды ошибок `E_*`

Актуальные коды из `ai_docs_broker.errors` + диспетчера `ops/handlers.py`:

| Код | Условие |
|---|---|
| `E_MISSING_FIELD` | Отсутствует обязательное поле в payload |
| `E_BAD_ENV` | `env` вне enum `{dev, test, prod}` |
| `E_BAD_DOC_TYPE` | `doc_type` вне enum `{Гайд Пользователя, Гайд Архитектора, Онтология, Live Scenario, Дорожная Карта, Стандарт, Отчёт Валидации}` |
| `E_BAD_SHA256` | `sha256` не 64 hex-символа в lower-case |
| `E_BAD_FILE_ID` | `owui_file_id` не парсится как UUID |
| `E_UNSUPPORTED_OPERATION` | Неизвестная `operation` |
| `E_DB_UNAVAILABLE` | Сервисный DSN отсутствует или БД недоступна |
| `E_DB_INTEGRITY` | Нарушение целостности (например UNIQUE) |
| `E_DB_TIMEOUT` | Таймаут DB-запроса |
| `E_DB_UNKNOWN` | Прочая ошибка pymysql |
| `E_KV_PUT_FAILED` | Ошибка записи в KV (только на bootstrap) |
| `E_SECRET_INSIDE_LOOP` | Sync-secret-вызов из running event loop |
| `E_NODE_CONFIG_INCOMPLETE` | В `node.json` отсутствует обязательное поле (bootstrap) |
| `E_INTERNAL` | Прочая исключительная ситуация; в `data.message` — «internal error», подробности не раскрываются |
| `E_NODE_NOT_REGISTERED` ³ | `register_product_state(node=...)` для `node_id`, отсутствующего в таблице `nodes` |
| `E_BAD_NODE_CLASS` ³ | `register_node.class` вне enum `{heavy, light, laptop}` |
| `E_BAD_NODE_STATUS` ³ | `register_node.status` вне enum `{active, planned, retired}` |

³ — коды введены в wheel 0.4.0 вместе с `register_node`/`list_nodes` и таблицей `nodes` (миграция `004_nodes_v1.sql` применена 2026-09-05, Дорожная Карта ADB v0.7 §0.4.0).

**Инвариант:** `data.message` наружу — только `safe_message` из `AiDocsBrokerError.safe_message`. Никогда не `str(exc)`.

---

## §9. Зависимости для развёртывания

**Обязательные факты о узле перед bootstrap ai_docs_broker:**

- Узел прочитал и валидировал `node.json` (`compatibility.mode` — не `broken`; `integrity.canonical_payload_sha256` — валиден на стороне h2_shared kernel).
- Установлен h2_shared в venv h2_shared kernel (для 0.2.0 требуется `h2_shared>=0.56.75`).
- MariaDB достижима по DSN `mariadb_root_url` (host/port из DSN).
- NATS достижим по профилю `${node.connection_profiles.nats}`.
- В KV `H2_SECRETS` присутствует ключ `mariadb_root_url` со свежим одноразовым DSN.
- Установлен NSSM (в PATH).
- Установлен Python 3.11+ (по классификаторам wheel; на HEAVY02 3.14).

При отсутствии любого из этих условий bootstrap fail-closed с явным кодом.

---

## §Приложение A. Реальный `node.json` HEAVY02

Live-снимок 2026-09-03, файл `C:\ProgramData\H2\config\node.json`, размер 2135 байт, raw SHA256 `86168494edb8db8f87f5ac7c74e4004f5d648f79aead50ca950c8885bcf158e7`.

```json
{
  "schema_version": 1,
  "config_id": "fac9954e-3fd0-42c1-a5ea-4dc6c45ed475",
  "config_revision": 1,
  "node_id": "heavy02",
  "role": "h2-node",
  "paths": {
    "programdata_root": "C:\\ProgramData\\H2",
    "h2_root": "C:\\h2",
    "bridge_home": "C:\\h2\\h2_shared",
    "logs_root": "C:\\h2\\h2_shared\\logs",
    "evidence_root": "C:\\h2\\h2_shared\\evidence",
    "log_sink_root": "C:\\h2\\h2_shared\\logs\\log_sink",
    "mariadb_data_root": "C:\\h2\\h2_shared\\data\\mariadb"
  },
  "nats": {
    "connection_profile_ref": "h2-nats-prod",
    "subject_namespace": "h2",
    "heartbeat_template": "h2.health.heartbeat.{node_id}"
  },
  "service": {
    "name": "H2_HEAVY02_BRIDGE_SVC",
    "manager": "nssm",
    "run_as": {"sid": "S-1-5-18", "account_name": "NT AUTHORITY\\SYSTEM"}
  },
  "limits": {"max_command_bytes": 262144, "max_ws_frame_bytes": 262144, "max_export_bytes": 33554432, "max_bridge_rss_mb": 1024, "max_log_file_bytes": 10485760, "max_log_file_count": 10},
  "compatibility": {"legacy_bridge_version": "none", "mode": "legacy_shim_required"},
  "integrity": {"canonicalization": "RFC8785-JCS-excluding-integrity.canonical_payload_sha256", "canonical_payload_sha256": "d91650925bf9b7c10fc74d7f86efd0300d8e30c338241431339827686a0c4288"},
  "connection_profiles": {"nats": "h2-nats-prod", "sql": "h2-sql-audit", "llm": "h2-llm-default", "owui": "h2-owui", "log_sink": "h2-log-sink"},
  "object_store": {"artifact_kv": {"buckets": ["h2-artifacts"], "max_bytes": 67108864}}
}
```

---

## §Приложение B. Матрица «сущность ↔ где живёт ↔ кто владелец»

| Сущность | Где | Владелец |
|---|---|---|
| Код продукта | `${node.paths.h2_root}\ai_Docs_broker\src` | ИИ-архитектор + ИИ-кодер |
| venv | `${node.paths.h2_root}\ai_Docs_broker\.venv` | ИИ-кодер (создаёт при bootstrap) |
| Wheel | `${node.paths.h2_root}\ai_Docs_broker\wheelhouse\*.whl` | ИИ-кодер |
| Схема MariaDB | Резолвится из `mariadb_root_url` / `mariadb_ai_docs_broker_url` | ai_docs_broker (создаёт сам) |
| Сервисный DSN | KV `H2_SECRETS.mariadb_ai_docs_broker_url` | ai_docs_broker |
| NSSM-сервис | `H2_<NODE>_AI_DOCS_BROKER_SVC` в SCM | ИИ-кодер |
| OWUI Function | `h2_ai_docs_broker_function` в OWUI | ИИ-архитектор |
| Документы продукта | OWUI Files → регистр `ai_docs_broker.documents` | ИИ-архитектор (через `register_document`) |
| Записи state | `ai_docs_broker.products_state` | Каждый продукт H2 сам о себе |
| Логи | `${node.paths.logs_root}\ai_docs_broker\` | NSSM |
| Evidence | `${node.paths.evidence_root}\ai_docs_broker\` | ИИ-кодер (пишет), ИИ-архитектор (читает) |

---

## §Приложение C. Реальный DDL схемы `ai_docs_broker` (HEAVY02, snapshot 2026-09-03)

Источник: task `ADB-INV-V04`, `inventory.json` sha256 `d786eda15623068b7df10634a35f140ff6c5b29624c3992bfe693620fd70f8d2`.

### `documents` (row_count=6)

| Column | Type | Null | Key | Default |
|---|---|---|---|---|
| id | bigint(20) | NO | PRI |  |
| env | enum('dev','test','prod') | NO | MUL |  |
| product | varchar(128) | NO |  |  |
| doc_type | enum('Гайд Пользователя','Гайд Архитектора','Онтология','Live Scenario','Дорожная Карта','Стандарт','Отчёт Валидации') | NO |  |  |
| version | varchar(64) | NO |  |  |
| owui_file_id | varchar(64) | NO |  |  |
| filename | varchar(512) | NO |  |  |
| sha256 | char(64) | NO |  |  |
| uploaded_at | datetime(3) | NO |  |  |
| uploaded_by | varchar(128) | NO |  |  |
| comment | text | YES |  |  |

Индексы: `PRIMARY (id)`, `UNIQUE uq_documents_env_product_doc_type (env, product, doc_type)`.

### `documents_archive` (row_count=5)

Все поля `documents` (кроме автоинкремента) + `archive_id` (PRI), `orig_id`, `archived_at datetime(3)`, `archived_by varchar(128)`, `archive_reason enum('update','delete')`. Индексы: `PRIMARY (archive_id)`, `MUL (env)`.

### `products_state` (row_count=2)

| Column | Type | Null | Key |
|---|---|---|---|
| id | bigint(20) | NO | PRI |
| node | varchar(64) | NO | MUL |
| product | varchar(128) | NO |  |
| env | enum('dev','test','prod') | NO |  |
| version | varchar(64) | NO |  |
| wheel_version | varchar(64) | NO |  |
| wheel_path | varchar(512) | NO |  |
| is_development_wheel | tinyint(1) | NO |  |
| updated_at | datetime(3) | NO |  |
| updated_by | varchar(128) | NO |  |
| comment | text | YES |  |

Индексы: `PRIMARY (id)`, **`UNIQUE uq_products_state_node_product (node, product)`** — БЕЗ `env`.

### `nodes` (введено в 0.4.0, row_count=6 по состоянию на 2026-09-05 20:30)

```sql
CREATE TABLE `nodes` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `node_id` VARCHAR(64) NOT NULL,
  `class` ENUM('heavy','light','laptop') NOT NULL,
  `status` ENUM('active','planned','retired') NOT NULL DEFAULT 'planned',
  `purpose` VARCHAR(255) NOT NULL,
  `registered_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `registered_by` VARCHAR(128) NOT NULL,
  `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
  `retired_at` DATETIME(3) NULL,
  `retired_reason` VARCHAR(255) NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_nodes_node_id` (`node_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### `nodes_archive` (введено в 0.4.0, row_count=0 по состоянию на 2026-09-05 20:30)

```sql
CREATE TABLE `nodes_archive` (
  `archive_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `node_row_id` INT UNSIGNED NOT NULL,
  `node_id` VARCHAR(64) NOT NULL,
  `class` ENUM('heavy','light','laptop') NOT NULL,
  `status` ENUM('active','planned','retired') NOT NULL,
  `purpose` VARCHAR(255) NOT NULL,
  `registered_at` DATETIME(3) NOT NULL,
  `registered_by` VARCHAR(128) NOT NULL,
  `updated_at` DATETIME(3) NOT NULL,
  `archive_reason` ENUM('update','delete','retire') NOT NULL,
  `archived_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  PRIMARY KEY (`archive_id`),
  KEY `ix_nodes_archive_node_id` (`node_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

### `products_state_archive` (row_count=1)

Все поля `products_state` (кроме автоинкремента) + `archive_id` (PRI), `orig_id`, `archived_at datetime(3)`, `archived_by varchar(128)`, `archive_reason enum('update','delete')`.

### `h2_migration_log`

Ключи: `UNIQUE migration_name`, checksum SHA256 файла миграции. В журнале
зарегистрированы минимум: `001_schema_v1.sql` (начальная схема 0.1.0) и
`002_doc_type_enum_v2.sql` (расширение enum doc_type до 7 значений, Этап 0
v11). Миграции `nodes`/`nodes_archive`
(`002_schema_v2_nodes.sql`) — планируются с wheel 0.4.0.

---

## §10. Что онтология НЕ описывает

- Прикладной интерфейс операций с точки зрения пользователя — см. **L3 Гайд Пользователя ai_Docs_broker v0.2**.
- Последовательность bootstrap D.1..D.12 и regression — см. **L2 Live Scenario ai_Docs_broker v0.2**.
- Обоснование границ — см. **L1 Концепт ai_Docs_broker v0.3**.
- Полную схему `node.json` — см. будущий документ **L2 Онтология H2**.
- Значения URL / host / port / API-ключей — они не в `node.json`, а в connection-profile registry h2_shared.

---

## §11. Инварианты онтологии (сводка)

- Все пути и адреса — через `${node.<field>}` реальные поля node.json.
- Никаких хардкод `C:\h2\...`, `192.168.99.11`, `127.0.0.1:3306` — только в §Приложение A как фактический снимок HEAVY02.
- Продукт никогда не хардкодит host/port/URL — только через профили `${node.connection_profiles.*}` и h2_shared API.
- Собственная схема, собственный сервисный пользователь, собственный DSN-ключ.
- Одноразовый admin DSN → cleared после первого bootstrap.
- Никаких чужих схем, никаких `*.*` GRANT.
- Все subjects — только `${node.nats.subject_namespace}.ai_docs_broker.*`.
- OWUI Function — thin proxy, никакой бизнес-логики в OWUI-слое.
- Ответ операций — трижды завёрнут (OpenAI → SSE → JSON); клиент обязан распаковать все три слоя.
- `full_state_report` — read-only; никогда не выполняет DML/DDL.
- `products_state` ключ уникальности — **(node, product)**, без `env`; для одного продукта на одном узле только одна активная запись state.
- `nodes` ключ уникальности — **(node_id)**; для одного `node_id` только одна активная запись в реестре флота.

---

## §Приложение D. Флот H2 (snapshot 2026-09-05 из таблицы `nodes`)

Актуальное содержимое таблицы `nodes` в схеме `ai_docs_broker` (HEAVY02, wheel 0.4.0). Это НЕ плановый снимок — это результат `list_nodes()` после первичного `register_node` для 6 узлов флота в этап 2 (2026-09-05, 6 успешных correlation_id зафиксированы в PMA `stage2-pma-0.4.0`).

| node_id | class | status | purpose | registered_by |
|---|---|---|---|---|
| `heavy02` | `heavy` | `active` | Основной GPU-узел разработки; референсный узел ai_Docs_broker (dev+prod) | `architect@h2` |
| `laptop` | `laptop` | `active` | Рабочий ноутбук архитектора; клиент OWUI и dev-стенд Roo Bridge | `architect@h2` |
| `heavy01` | `heavy` | `planned` | Резервный/зеркальный GPU-узел; будущий второй prod-узел ai_Docs_broker | `architect@h2` |
| `light01` | `light` | `planned` | Легковесный edge-узел; планируется под наблюдение робособаки и мелких агентов | `architect@h2` |
| `light02` | `light` | `planned` | Легковесный edge-узел; резерв | `architect@h2` |
| `light03` | `light` | `planned` | Легковесный edge-узел; резерв | `architect@h2` |

**Правила чтения:**
- `class` определяет профиль ресурсов узла (`heavy` — GPU + большая RAM, `light` — минимальный резерв CPU/RAM, `laptop` — мобильный клиент, не сервер prod-профилей).
- `status=active` означает, что узел физически подключён к NATS-меш H2 и участвует в prod-жизни хотя бы одного продукта. `planned` — узел учтён во флоте, но ещё не поднят. `retired` — вывод из эксплуатации.
- Переход `planned → active` наступает при первой успешной регистрации product_state с этого узла (см. §8.3.6). Обратный переход `active → retired` — только явным `register_node(status='retired', ...)`.


### Исходная версия v0.18 (doc_id=413)

# ADB Онтология v0.18

**Дата:** 2026-09-13
**Продукт:** ai_docs_broker
**Prod:** 0.9.0a6
**Родитель:** v0.17 (doc_id=413)

## Что нового в v0.18 (кумулятивно к v0.17)

- **§2.1 NEW** — «Именование продуктов»: единое правило case-normalization
  (`lower_snake_case`, `^[a-z][a-z0-9_]*$`), коллация `utf8mb4_bin`,
  гейт `E_INVALID_PRODUCT_NAME`.
- Исправлен класс дефектов «случайный over-match по регистру»
  (incident 2026-09-13, брифы v10 §4 / v11 §1).
- Разделы v0.17 сохранены (T-DOCS-14).

---

## §2.1 Именование продуктов

**Инвариант:** имя продукта (колонки `products.product`, `documents.product`,
`documents_archive.product`) хранится ТОЛЬКО в `lower_snake_case`.

**Формальное правило (regex):** `^[a-z][a-z0-9_]*$`

**Примеры валидных имён:**
- `ai_docs_broker` ✅
- `owui_roo_bridge` ✅
- `h2_shared` ✅

**Примеры невалидных имён:**
- `Ai_Docs_broker` ❌ (заглавные буквы)
- `ai-docs-broker` ❌ (дефисы)
- `1ai_broker` ❌ (начинается с цифры)
- `ai docs broker` ❌ (пробелы)

**Валидация:** проверка выполняется на входе всех write-op
(`register_product`, `register_document`, `bulk_deprecate_documents`).
Нарушение → `E_INVALID_PRODUCT_NAME, class=validation`.

**Уровень БД:** все три колонки имеют коллацию `utf8mb4_bin`
(байтовое сравнение). Разный регистр физически не считается одинаковым.

**Почему:** case-insensitive collation в MySQL по умолчанию (`utf8mb4_unicode_ci`)
приводила к случайным over-match в bulk-фильтрах и UPDATE
(см. incident 2026-09-13, брифы v10 §4 и v11 §1). Единая normalization на
уровне схемы + гейт на входе исключает класс дефектов.

**Кросс-ссылки:** §5 (register_document), §16 (register_product_dependency),
§7 (таблица products), §15 (`product_dependencies`) — все оперируют именем
продукта и подчиняются правилу §2.1.

**Миграция:** 030 (`030_products_case_sensitive_collation.sql`) переводит
`products.product` / `documents.product` / `documents_archive.product` на
`utf8mb4_bin`. Перед применением обязательна проверка отсутствия дубликатов по
регистру среди активных записей (в проде — выполнено, `case_variant_dupes=[]`).

---

## Унаследовано из v0.17 (кумулятивно)

- §7-NEW — расширение таблицы `products` полем `runtime`.
- §15-NEW — таблица `product_dependencies`.
- §8-NEW — enum `products_state.state` (4 значения).
- §5-NEW — инварианты ONT-6-NEW и ONT-7-NEW.
- §16-NEW — op: `register_product_dependency`, `list_product_dependencies`,
  `product_runtime_map`, `sync_products_state_from_deploys`.

## §17. Обновлённые сигнатуры (v0.9.0a6+)

Все сигнатуры v0.17 сохранены. Добавлена одна новая операция:

| op | required (params) |
|---|---|
| `unmark_product` | `env`, `product` |

`unmark_product` (WRITE) — снять маркер `deprecated` с записи реестра `products`.
Фильтр — ТОЛЬКО точное case-sensitive имя (`BINARY product = %s`); имя обязано
быть `lower_snake_case`. Назначение: восстановление коллатерального ущерба
migration 029 (бриф v10 §4). Актор HARD-контракт (§5) не менялся.

§END v0.18


### Исходная версия v0.19 (doc_id=481)

# ADB Онтология v0.19

Delta vs v0.18. Остальное — без изменений.

## 5-NEW. Сущность doc_type

Тип документа — управляемая сущность в таблице `doc_types(env, name, description, added_by, created_at, deprecated)`.

- Уникальность: `(env, name)`, коллация `utf8mb4_bin` (регистр значим).
- Регекс имени (инвариант ONT-8-NEW): `^[A-ZА-Я][A-ZА-Яa-zа-я0-9_ \-]{1,63}$`. Кириллица и пробел разрешены (совместимость с 9 базовыми значениями: «Гайд Пользователя», «Отчёт Валидации» и т.п.).
- Пользовательский doc_type регистрируется через op `register_doc_type` (HMAC-write, любой principal).
- Деприкейт — через `deprecate_doc_type`. Если существуют недеприкейтнутые документы этого типа в этом env — E_DOC_TYPE_IN_USE.
- Base seed 9 типов (env=dev/stage/prod): «Гайд Пользователя», «Гайд Архитектора», «Онтология», «Live Scenario», «Дорожная Карта», «Стандарт», «Отчёт Валидации», «Концепт Развития», «PMA». `added_by='system:seed-031'`.

## 6-NEW. Поле documents.description

Аннотация конкретной версии документа (для чего, что внутри, когда нужен), длина ≤512 символов, nullable.

- Инвариант ONT-9-NEW: НЕ путать с полем `comment` (тот — комментарий загрузки версии, чаще технический).
- Заполняется при `register_document` (опциональное поле), апдейтится через `update_document_description`.
- Возвращается всеми read-op: `get_document`, `list_documents`, `full_state_report.documents[*]`, `onboard.docs[*]`.
- При `promote_document` копируется из исходной записи, если не задан override.

## 2.1 Именование продуктов (без изменений vs v0.18)

Правило `^[a-z][a-z0-9_]*$`, коллация `utf8mb4_bin`, гейт `E_INVALID_PRODUCT_NAME` — применяется только к именам продуктов, не к doc_type.

## Совместимость

- Old-клиенты (v0.9.0a7 и ниже): не видят полей `description` в ответе и не могут звать новые op — получают `E_UNKNOWN_OP`.
- Существующие 300+ документов: `description = NULL` (миграция 032 не заполняет back-fill).
- `schema_meta.enums.doc_type` в `full_state_report` — теперь computed: `SELECT name FROM doc_types WHERE env=? AND deprecated=0`.


## §CHANGELOG v0.20-consolidated

- Создана полная кумулятивная редакция для TD-2026-09-16-07 (T-DOCS-DELTA-BAN).