# API Контракты (v1)

Source-of-truth спецификация:
- `docs/openapi.yaml`

Runtime документация API:
- `/swagger` (Swagger UI)
- `/openapi.yaml` (OpenAPI YAML)

## Базовая дисциплина

- Префикс API: `/api/v1`.
- Имена ресурсов: `kebab-case`, множественное число (`status-events`, `device-profiles`).
- Поля JSON: `snake_case`.
- Временные метки: `UTC`, формат `RFC3339`.
- Контрактные заголовки:
  - `X-Route-Path`
  - `X-Route-Module`
  - `X-Route-Filter`
  - `X-Client-Version`
  - `X-Client-Build-Time`

## Канонические маршруты

- `GET /api/v1/<resource>/list` — выборка с фильтрацией, сортировкой, пагинацией.
- `GET /api/v1/<resource>/scheme` — схема/метаданные ресурса для UI.
- `GET /api/v1/<resource>/{id}` — получение одной сущности.
- `POST /api/v1/<resource>` — создание сущности/команды.
- `PATCH /api/v1/<resource>/{id}` — частичное изменение.
- `DELETE /api/v1/<resource>/{id}` — архивирование или удаление по политике ресурса.

## Параметры list

- `limit`, `offset`, `sort`, `order`, `q`
- Дополнительные фильтры по бизнес-атрибутам (`status`, `source`, `tenant_id`, и т.д.)

## Базовый успешный ответ list

```json
{
  "items": [],
  "offset": 0,
  "limit": 20,
  "total": 0,
  "has_more": false,
  "meta": {
    "request_id": "req-20260304-001",
    "timestamp": "2026-03-04T17:50:00Z"
  }
}
```

## Ответ по одной сущности

```json
{
  "item": {},
  "meta": {
    "request_id": "req-20260304-002",
    "timestamp": "2026-03-04T17:50:03Z"
  }
}
```

## Ошибка

```json
{
  "error": {
    "code": "validation_error",
    "message": "Некорректное значение поля status",
    "details": [{ "field": "status", "reason": "unsupported_value" }]
  },
  "meta": {
    "request_id": "req-20260304-003",
    "timestamp": "2026-03-04T17:50:05Z"
  }
}
```

Рекомендуемые HTTP-статусы:
`200/201/204`, `400/401/403/404/409/422`, `500`.

## Контракты текущего этапа

- `GET /health`  
- `GET /api/v1/modules/list`
- `GET /api/v1/modules/scheme`
- `GET /api/v1/modules/database/list`
- `GET /api/v1/modules/permissions/list`
- `POST /api/v1/modules/permissions/commands/update`
- `GET /api/v1/modules/access-audit/scheme|list`
- `GET /api/v1/modules/site-policy/scheme|list`
- `POST /api/v1/modules/site-policy/commands/set`
- `POST /api/v1/modules/commands/install`
- `POST /api/v1/modules/commands/uninstall`
- `GET /api/v1/status/list`
- `GET /api/v1/status/stream` (WebSocket)
- `GET /api/v1/alarms/scheme|list`
- `POST /api/v1/alarms/commands/ack`
- `GET /api/v1/tasks/scheme|list`
- `POST /api/v1/tasks`
- `GET /api/v1/tasks/{id}`
- `POST /api/v1/tasks/commands/transition`
- `GET /api/v1/audit/scheme|list`
- `POST /api/v1/audit/commands/log`
- `GET /api/v1/devices/telemetry/scheme|list`
- `GET /api/v1/devices/telemetry/kpi`
- `GET /api/v1/passkeys/scheme`
- `GET /api/v1/passkeys/list`
- `POST /api/v1/passkeys/commands/issue`
- `POST /api/v1/passkeys/commands/revoke`
- `POST /api/v1/auth/passkey/commands/start`
- `POST /api/v1/auth/passkey/commands/complete`
- `GET /api/v1/auth/passkey/status`
- `GET /api/v1/auth/me`
- `POST /api/v1/auth/commands/logout`
- `GET /api/v1/industry-profiles/scheme|list`
- `POST /api/v1/industry-profiles/commands/import`
- `POST /api/v1/poultry-profiles/commands/import-yaml`
- `POST /api/v1/industry-profiles/commands/export`
- `POST /api/v1/poultry-profiles/commands/export-yaml`
- `POST /api/v1/industry-profiles/commands/publish`
- `POST /api/v1/poultry-profiles/commands/publish`
- `POST /api/v1/industry-profiles/commands/rollback`
- `POST /api/v1/poultry-profiles/commands/rollback`
- `GET /api/v1/industry-alarms/scheme|list`
- `POST /api/v1/industry-alarms/commands/publish`
- `GET /api/v1/app/context`
- `GET /api/v1/tables/scheme|list`
- `POST /api/v1/tables`
- `GET|PATCH|DELETE /api/v1/tables/{id}`
- `GET /api/v1/forms/scheme|list`
- `POST /api/v1/forms`
- `GET|PATCH|DELETE /api/v1/forms/{id}`
- `GET /api/v1/tenant-master/scheme|list`
- `POST /api/v1/tenant-master/commands/publish`
- `GET /api/v1/subscriptions/scheme|list`
- `POST /api/v1/subscriptions/commands/set-status`
- `GET /api/v1/sla-profiles/scheme|list`
- `POST /api/v1/sla-profiles/commands/assign`
- `GET /api/v1/version-policies/scheme|list`
- `POST /api/v1/version-policies/commands/set`
- `POST /api/v1/version-policies/commands/check`
- `GET /api/v1/analytics/metrics/scheme|list`
- `GET /api/v1/analytics/summary`
- `GET /api/v1/tenant-master-sync/scheme|list`
- `POST /api/v1/tenant-master-sync/commands/run`
- `POST /api/v1/tenant-master-sync/commands/set-mode`
- `GET /api/v1/melioration-field/scheme|list`
- `GET /api/v1/melioration-alerts/scheme|list`
- `GET /api/v1/melioration-irrigation-machine/scheme|list`
- `GET /api/v1/melioration-irrigation-drip/scheme|list`
- `GET /api/v1/melioration-soil-moisture/scheme|list`
- `GET /api/v1/melioration-weather/scheme|list`
- `POST /api/v1/melioration-irrigation-machine/commands/ingest`
- `POST /api/v1/melioration-irrigation-drip/commands/ingest`
- `POST /api/v1/melioration-soil-moisture/commands/ingest`
- `POST /api/v1/melioration-weather/commands/ingest`
- `POST /api/v1/melioration-weather/commands/telemetry`
- `GET /api/v1/melioration-control-mode/scheme|list`
- `POST /api/v1/melioration-control-mode/commands/set`
- `POST /api/v1/melioration-control-mode/commands/degrade`
- `GET /api/v1/poultry-climate/scheme|list`
- `POST /api/v1/poultry-climate/commands/setpoint`
- `POST /api/v1/poultry-climate/commands/ack`
- `POST /api/v1/poultry-climate/commands/telemetry`
- `POST /api/v1/poultry-climate/sensors/commands/ingest`
- `GET /api/v1/poultry-flock/scheme|list`
- `GET /api/v1/poultry-feedwater/scheme|list`
- `POST /api/v1/poultry-feedwater/commands/ingest`
- `GET /api/v1/poultry-production/scheme|list`
- `GET /api/v1/poultry-production/kpi`
- `GET /api/v1/poultry-alarms/scheme|list`
- `POST /api/v1/poultry-alarms/commands/evaluate`
- `POST /api/v1/poultry-alarms/commands/publish`
- `POST /api/v1/poultry-alarms/commands/ack`
- `POST /api/v1/poultry-alarms/commands/escalate`
- `GET /api/v1/swine-climate/scheme|list`
- `POST /api/v1/swine-climate/commands/setpoint`
- `POST /api/v1/swine-climate/commands/telemetry`
- `POST /api/v1/swine-climate/sensors/commands/ingest`
- `GET /api/v1/swine-feeding/scheme|list`
- `POST /api/v1/swine-feeding/commands/ingest`
- `GET /api/v1/swine-water/scheme|list`
- `POST /api/v1/swine-water/commands/ingest`
- `GET /api/v1/swine-production/scheme|list`
- `GET /api/v1/swine-production/kpi`
- `POST /api/v1/swine-production/commands/ingest`
- `GET /api/v1/swine-biosecurity/scheme|list`
- `POST /api/v1/swine-biosecurity/commands/ingest`
- `POST /api/v1/swine-biosecurity/commands/ack`

Тело `/health`:

```json
{ "status": "ok", "layer": "master-cloud" }
```

## Контракт `app/context`

Маршрут:
- `GET /api/v1/app/context`

Ключевые правила:
- endpoint возвращает bootstrap-контекст runtime (`deployment_mode`, `industry_code`, `available_industries`, `tenant_id`, `site_id`, `feature_flags`, `user`);
- поле `policy` является source-of-truth для UI/API guard-слоя и содержит:
  - `scope_type`, `role`, `effective_tenant_id`;
  - `permissions.ui` и `permissions.api` (lookup `id -> allowed`);
  - `capabilities` (агрегированные флаги операций UI, включая `switch_industry`, `sidebar_menu`, `sidebar_menu_update`, `module_install`, `module_permissions`, `master_*`, `tenant_*`);
  - `guards` (`master_scope`, `tenant_scope`, `allow_industry_switch`);
- `feature_flags` и `policy.capabilities` применяются совместно: при запрете capability UI должен блокировать операцию даже при наличии локального fallback по правам.

## Контракт CRUD ресурсов (`tables/forms`)

Маршруты:
- `GET /api/v1/tables/scheme`
- `GET /api/v1/tables/list`
- `POST /api/v1/tables`
- `GET /api/v1/tables/{id}`
- `PATCH /api/v1/tables/{id}`
- `DELETE /api/v1/tables/{id}`
- `GET /api/v1/forms/scheme`
- `GET /api/v1/forms/list`
- `POST /api/v1/forms`
- `GET /api/v1/forms/{id}`
- `PATCH /api/v1/forms/{id}`
- `DELETE /api/v1/forms/{id}`

Ключевые правила:
- `POST` и `PATCH` принимают объект в поле `item` и возвращают стандартный `item` envelope;
- для `list` поддерживаются `limit`, `offset`, `q`;
- при создании `id` может быть сгенерирован автоматически;
- системные поля `created_at`, `updated_at`, `resource` выставляются runtime;
- коды ошибок: `validation_error` (400), `not_found` (404), `already_exists` (409).

## Контракт `tenant-master` exchange

Маршруты:
- `GET /api/v1/tenant-master/scheme`
- `GET /api/v1/tenant-master/list`
- `POST /api/v1/tenant-master/commands/publish`

Ключевые правила:
- контур принимает только whitelisted служебные события (`event_type`) и статусы (`status`);
- обязательные поля publish: `tenant_id`, `event_type`, `status`;
- поддерживаются фильтры списка: `tenant_id`, `event_type`, `status`, `limit`, `offset`;
- события хранят `correlation_id` и `payload` для сквозной трассировки обмена;
- коды ошибок whitelist: `event_not_whitelisted` (409), `status_not_whitelisted` (409).

## Контракт `subscriptions` + `sla-profiles` (master)

Маршруты:
- `GET /api/v1/subscriptions/scheme`
- `GET /api/v1/subscriptions/list`
- `POST /api/v1/subscriptions/commands/set-status`
- `GET /api/v1/sla-profiles/scheme`
- `GET /api/v1/sla-profiles/list`
- `POST /api/v1/sla-profiles/commands/assign`

Ключевые правила:
- контур поддерживает жизненный цикл подписки (`trial|active|paused|canceled`);
- `set-status` изменяет состояние подписки и фиксирует `updated_at`;
- `sla-profiles` содержит параметры SLA (`response_minutes`, `resolution_minutes`, `escalation_policy`);
- `assign` связывает подписку с активным SLA-профилем;
- фильтры списка подписок: `tenant_id`, `status`, `sla_profile_id`, `q`, `limit`, `offset`.

## Контракт `version-policies` (master)

Маршруты:
- `GET /api/v1/version-policies/scheme`
- `GET /api/v1/version-policies/list`
- `POST /api/v1/version-policies/commands/set`
- `POST /api/v1/version-policies/commands/check`

Ключевые правила:
- policy-слой задает ограничения версии модуля (`allowed_major`, `min_version`, `max_version`, `pinned_version`);
- поддерживаются scope: `global`, `industry`, `tenant`;
- `set` создает/обновляет policy по `id`;
- `check` валидирует версии установленных модулей и возвращает список нарушений (`violations`).

## Контракт `central analytics`

Маршруты:
- `GET /api/v1/analytics/metrics/scheme`
- `GET /api/v1/analytics/metrics/list`
- `GET /api/v1/analytics/summary`

Ключевые правила:
- `metrics/list` предоставляет агрегированные метрики master-контура по `tenant/industry/window`;
- поддерживаются фильтры: `tenant_id`, `industry_code`, `metric`, `window`, `q`, `limit`, `offset`;
- `summary` возвращает укрупненные показатели (`tenants_count`, `industries_count`, `average_uptime_percent`, `average_latency_ms`, `open_alarms_total`, `open_tasks_total`).

## Контракт `tenant-master-sync`

Маршруты:
- `GET /api/v1/tenant-master-sync/scheme`
- `GET /api/v1/tenant-master-sync/list`
- `POST /api/v1/tenant-master-sync/commands/run`
- `POST /api/v1/tenant-master-sync/commands/set-mode`

Ключевые правила:
- модуль управляет состоянием синхронизации tenant -> master по каждому `tenant_id`;
- статусы: `live`, `degraded`, `stopped`;
- режимы: `auto`, `manual`, `paused`;
- `run` запускает принудительную синхронизацию tenant и сбрасывает lag/pending;
- `set-mode` переключает рабочий режим синхронизации.

## Контракт `alarms` (tenant/master)

Маршруты:
- `GET /api/v1/alarms/scheme`
- `GET /api/v1/alarms/list`
- `POST /api/v1/alarms/commands/ack`

Ключевые правила:
- единый контур тревог для master/tenant с состояниями `open|acknowledged`;
- поддерживаются фильтры списка: `q`, `severity`, `state`, `scope`, `limit`, `offset`;
- подтверждение тревоги выполняется через `ack` с полями `id`, `acknowledged_by`, `comment`;
- ошибка отсутствующей тревоги: `alarm_not_found` (404);
- при деинсталляции модуля возвращается `module_not_installed` (404).

## Контракт `tasks` lifecycle

Маршруты:
- `GET /api/v1/tasks/scheme`
- `GET /api/v1/tasks/list`
- `POST /api/v1/tasks`
- `GET /api/v1/tasks/{id}`
- `POST /api/v1/tasks/commands/transition`

Ключевые правила:
- состояния жизненного цикла: `planned`, `in_progress`, `blocked`, `done`, `canceled`;
- поддерживаются фильтры списка: `q`, `state`, `scope`, `assignee`, `limit`, `offset`;
- переходы выполняются через `transition` и валидируются по lifecycle policy;
- недопустимый переход возвращает `lifecycle_conflict` (409);
- для каждой задачи хранится история переходов (`from_state`, `to_state`, `changed_by`, `changed_at`, `comment`).

## Контракт `audit` trail

Маршруты:
- `GET /api/v1/audit/scheme`
- `GET /api/v1/audit/list`
- `POST /api/v1/audit/commands/log`

Ключевые правила:
- `audit` хранит историю действий и системных операций (`actor`, `action`, `resource`, `result`);
- поддерживаются фильтры списка: `q`, `actor`, `action`, `resource`, `result`, `limit`, `offset`;
- запись события выполняется через `log` c payload `actor/action/resource/result/correlation_id/details`;
- поддерживаются результаты: `success`, `denied`, `error`;
- история хранится в хронологическом порядке (новые записи сверху).

## Контракт `devices/telemetry`

Маршруты:
- `GET /api/v1/devices/telemetry/scheme`
- `GET /api/v1/devices/telemetry/list`
- `GET /api/v1/devices/telemetry/kpi`

Ключевые правила:
- `list` возвращает точки телеметрии устройств с фильтрами;
- поддерживаются фильтры: `q`, `device_id`, `site_id`, `status`, `metric`, `limit`, `offset`;
- `kpi` возвращает агрегаты по устройствам (online/warn/error, total points, average latency);
- KPI рассчитываются по последнему известному состоянию каждого устройства в текущем фильтре.

## Контракт ingest для melioration (MVP)

- `POST /api/v1/melioration-irrigation-machine/commands/ingest`
- `POST /api/v1/melioration-irrigation-drip/commands/ingest`
- `POST /api/v1/melioration-soil-moisture/commands/ingest`
- `POST /api/v1/melioration-weather/commands/ingest`
- `POST /api/v1/melioration-weather/commands/telemetry`

Ключевые правила:
- ingest endpoint-ы принимают `item`-данные в request body и возвращают стандартный `item` envelope;
- для irrigation событий поддерживаются обе единицы (`mm` primary, `m3` derived);
- для `weather` действует D-008 policy: `station primary`, `service fallback` при `telemetry_healthy=false`.
- `weather/telemetry` поддерживает `fallback_mode` (`A|B`) и при `healthy=false` автоматически деградирует `control_mode` из `C` в fallback (`auto_degraded=true`).
- `/api/v1/melioration-alerts/list` рассчитывает `water_deficit|overwatering` по связке soil + weather + irrigation.

## Контракт `poultry-climate` (MVP, phase-1)

Маршруты:
- `GET /api/v1/poultry-climate/scheme`
- `GET /api/v1/poultry-climate/list`
- `POST /api/v1/poultry-climate/commands/setpoint`
- `POST /api/v1/poultry-climate/commands/ack`
- `POST /api/v1/poultry-climate/commands/telemetry`
- `POST /api/v1/poultry-climate/sensors/commands/ingest`

Ключевые правила:
- `list` возвращает климат-профили по house/batch/age-phase;
- поддерживаются фильтры `q`, `house_id`, `batch_id`, `age_phase_id`, `subtype`, `state`, `current_age_days`, `age_mode`, `limit`, `offset`;
- в ответе присутствует модель версий профиля (`version_id`, `state`, `change_note`, `rollback_of`).
- `setpoint` применяет уставки к выбранному профилю (`profile_id` или селектор house+batch+age_phase), повышает `version_id` и переводит профиль в `draft`;
- `ack` подтверждает профиль оператором и переводит его в `published`.
- `setpoint` возвращает `adapter_dispatch` для климат-контроллера (`adapter_profile_id`, `controller_id`, `transport`, `status`).
- `telemetry` принимает измерения адаптера (temp/humidity/co2/nh3/ventilation), сопоставляет с активным профилем и возвращает `status` + `out_of_norm_flags`.
- `sensors/ingest` принимает показания датчиков среды (`temperature|humidity|pressure|co2|nh3`) и нормализует их в общий telemetry-контур climate.
- при передаче `current_age_days` список вычисляет активную age-phase (`active_by_age=true`), а в режиме `age_mode=active` возвращает только текущий профиль фазы.

## Контракт `poultry-flock` (MVP, phase-1)

Маршруты:
- `GET /api/v1/poultry-flock/scheme`
- `GET /api/v1/poultry-flock/list`

Ключевые правила:
- `list` возвращает партии выращивания по птичникам;
- поддерживаются фильтры `q`, `house_id`, `batch_id`, `subtype`, `status`, `limit`, `offset`;
- в ответе присутствуют поля жизненного цикла партии (`start_date`, `planned_close_date`, `current_age_days`, `status`).

## Контракт `poultry-feedwater` (MVP, phase-1)

Маршруты:
- `GET /api/v1/poultry-feedwater/scheme`
- `GET /api/v1/poultry-feedwater/list`
- `POST /api/v1/poultry-feedwater/commands/ingest`

Ключевые правила:
- `list` возвращает объединенный профиль кормления/водопотребления на age phase;
- поддерживаются фильтры `q`, `house_id`, `batch_id`, `age_phase_id`, `subtype`, `status`, `state`, `limit`, `offset`;
- в ответе присутствует модель версий профиля (`version_id`, `state`, `change_note`, `rollback_of`) и пороги аномалий расхода воды.
- `ingest` принимает feed/water counters (+ pressure), рассчитывает отклонения и возвращает `status=ok|warn|alarm` с `anomaly_flags`.
- `ingest` обновляет operational поля профиля (`operational_status`, `last_*`, `*_deviation_pct`, `last_counter_at`) для runtime-мониторинга.

## Контракт `poultry-production` (MVP, phase-1)

Маршруты:
- `GET /api/v1/poultry-production/scheme`
- `GET /api/v1/poultry-production/list`
- `GET /api/v1/poultry-production/kpi`

Ключевые правила:
- `list` возвращает производственные KPI по house/batch/date;
- поддерживаются фильтры `q`, `house_id`, `batch_id`, `subtype`, `metric_date`, `limit`, `offset`;
- в ответе доступны эксплуатационные отклонения (`out_of_norm_flags`) и KPI для broiler/layer.
- `kpi` рассчитывает агрегированные отраслевые показатели по окну (`window_days`) для `broiler/layer`.

## Контракт `poultry-alarms` (MVP, phase-1)

Маршруты:
- `GET /api/v1/poultry-alarms/scheme`
- `GET /api/v1/poultry-alarms/list`
- `POST /api/v1/poultry-alarms/commands/evaluate`
- `POST /api/v1/poultry-alarms/commands/publish`
- `POST /api/v1/poultry-alarms/commands/ack`
- `POST /api/v1/poultry-alarms/commands/escalate`

Ключевые правила:
- `list` возвращает отраслевые тревоги poultry с обязательным `correlation_id`;
- поддерживаются фильтры `q`, `house_id`, `batch_id`, `severity`, `priority`, `status`, `correlation_id`, `limit`, `offset`;
- структура события совпадает с `poultry_alarm_event` и готова к bridge в `status`.
- `publish` создает событие с `status=active` и автоматически генерирует `correlation_id`, если не передан;
- `ack` подтверждает событие по `id`, выставляет `status=acked`, `acked_at`, `assignee`.
- для `severity=critical|high` poultry alarm-bus публикует bridge-событие в `status` с тем же `correlation_id` (`bridged_to_status=true`).
- в `status/list` bridged-событие хранит `correlation_id` для сквозной трассировки poultry alarm -> status.
- `evaluate` запускает rule-engine по телеметрии (temp/co2/ventilation/feed-water deviations) и создает события с приоритетами (`P1/P2`) согласно правилу.
- для каждого события рассчитывается SLA (`sla_target_sec`, `sla_deadline_at`), а `escalate` помечает `sla_breached=true` и повышает `escalation_level` при нарушении SLA.

## Контракт profile-service (D-003)

Маршруты:
- `GET /api/v1/industry-profiles/scheme`
- `GET /api/v1/industry-profiles/list`
- `POST /api/v1/industry-profiles/commands/import`
- `POST /api/v1/poultry-profiles/commands/import-yaml`
- `POST /api/v1/industry-profiles/commands/export`
- `POST /api/v1/poultry-profiles/commands/export-yaml`
- `POST /api/v1/industry-profiles/commands/publish`
- `POST /api/v1/poultry-profiles/commands/publish`
- `POST /api/v1/industry-profiles/commands/rollback`
- `POST /api/v1/poultry-profiles/commands/rollback`

Ключевые правила:
- перенос нормативов между хозяйствами через YAML (`export/import`);
- версионирование профилей (`draft/published/archived`);
- rollback на выбранную версию с аудитом действий;
- совместимость import-профиля проверяется по `industry_code` и `equipment_profile`.
- `poultry-profiles/import-yaml` принимает только профили с `industry_code=poultry`.
- `poultry-profiles/export-yaml` экспортирует только `industry_code=poultry` и возвращает conflict при попытке экспорта профиля другой отрасли.
- `poultry-profiles/publish` публикует только poultry-профили и выполняет ту же модель версионирования (`draft -> published`, предыдущая published -> archived).
- `poultry-profiles/rollback` выполняет откат только для poultry-профилей и публикует указанную версию как активную.

## Контракт industry alarm-bus (D-004)

Маршруты:
- `GET /api/v1/industry-alarms/scheme`
- `GET /api/v1/industry-alarms/list`
- `POST /api/v1/industry-alarms/commands/publish`

Ключевые правила:
- отраслевые тревоги публикуются в отдельный контур `industry-alarms`;
- изоляционные ключи `tenant_id`, `industry_code`, `site_id` передаются в отраслевых событиях и поддерживаются в фильтрах списка;
- bridge в `status` выполняется для `severity=critical|high`;
- mapping в `status`: `critical|high -> error`, `medium -> warn`, `low|info -> ok`;
- для сквозной трассировки в обеих системах передается `correlation_id`.

## Контракт модульного реестра

`GET /api/v1/modules/list` возвращает список модулей с четырьмя контурами:
- `frontend`: `block_id`, `component`;
- `backend`: `routes`.
- `database`: `migrations`, `tables`.
- `permissions`: `rules` (`role`, `resource`, `actions`).
- отраслевые метаданные: `industry_code`, `industry_scope`, `key_entity`, `provides_enrichment_for`.
- поле `version` для отраслевых модулей следует политике `MAJOR.MINOR.PATCH`; в рамках одной отрасли активная major-линия должна быть единой.

Пример элемента:

```json
{
  "id": "status",
  "name": "Status Stream",
  "description": "Поток статусов оборудования и событий в реальном времени",
  "version": "0.1.0",
  "core": false,
  "installed": true,
  "dependencies": [],
  "frontend": {
    "block_id": "status",
    "component": "status-table"
  },
  "backend": {
    "routes": ["/api/v1/status/list", "/api/v1/status/stream"]
  },
  "database": {
    "migrations": ["20260304_20_create_status_events"],
    "tables": [
      {
        "name": "status_events",
        "primary_key": "id",
        "columns": ["id", "source", "event", "status", "timestamp"]
      }
    ]
  },
  "permissions": {
    "rules": [
      {
        "role": "operator",
        "resource": "status",
        "actions": ["read", "stream"]
      }
    ]
  }
}
```

## Контракт cross-industry access audit

Маршруты:
- `GET /api/v1/modules/access-audit/scheme`
- `GET /api/v1/modules/access-audit/list`

Ключевые правила:
- фиксируются решения отраслевых policy-check при `modules/permissions/commands/update` и `modules/commands/install`;
- событие содержит: `operation`, `decision` (`allow|deny`), `reason`, `module_id`, `requested_industry_code`, `module_industry_code`, `actor`, `timestamp`;
- хранение в runtime ограничено последними `200` событиями (in-memory, порядок `desc`);
- фильтры списка: `module_id`, `decision`, `requested_industry_code`, `module_industry_code`.

## Контракт модели БД по модулям

`GET /api/v1/modules/database/list` возвращает модель БД модулей.

Параметры:
- `installed_only` (`true|false`, по умолчанию `true`) — только установленные модули или полный каталог.
- `industry_code` (опционально) — фильтрация permission-модели по отраслевой области доступа.
- `site_id` (опционально) — фильтрация permission-модели с учетом site-policy (частичная активация модулей на площадке).
- `industry_code` (опционально) — фильтрация permission-модели по отраслевой области доступа.

Элемент списка:

```json
{
  "module_id": "tasks",
  "module_name": "Tasks",
  "installed": true,
  "migrations": ["20260305_20_create_tasks_lifecycle"],
  "tables": [
    {
      "name": "tasks",
      "primary_key": "id",
      "columns": ["id", "title", "priority", "status", "sla_minutes", "created_at"]
    }
  ]
}
```

## Контракт модели прав по модулям

`GET /api/v1/modules/permissions/list` возвращает роль-ресурсные правила модулей.

Параметры:
- `installed_only` (`true|false`, по умолчанию `true`) — только установленные модули или полный каталог.

Элемент списка:

```json
{
  "module_id": "tasks",
  "module_name": "Tasks",
  "installed": true,
  "rules": [
    {
      "role": "operator",
      "resource": "tasks",
      "actions": ["read", "create", "transition"]
    }
  ]
}
```

`POST /api/v1/modules/permissions/commands/update` обновляет правила конкретного модуля.

Тело запроса:

```json
{
  "id": "status",
  "industry_code": "melioration",
  "rules": [
    { "role": "operator", "resource": "status", "actions": ["read"] },
    { "role": "admin", "resource": "status", "actions": ["read", "stream", "update"] }
  ]
}
```

Если `industry_code` задан, update разрешается только для модулей, доступных в рамках выбранной отрасли; при нарушении возвращается `industry_policy_conflict`.

## Контракт установки/деинсталляции

Тело запроса:

```json
{ "id": "mqtt" }
```

Ошибки модульного API:
- `module_not_found`
- `module_already_installed`
- `module_not_installed`
- `module_is_core`
- `module_dependency_missing`
- `module_has_dependents`
- `industry_policy_conflict`

## Контракт site-policy (частичная активация модулей)

Маршруты:
- `GET /api/v1/modules/site-policy/scheme`
- `GET /api/v1/modules/site-policy/list`
- `POST /api/v1/modules/site-policy/commands/set`

Параметры `site-policy/list`:
- `site_id` (обязательно)
- `limit`, `offset`

Тело `site-policy/set`:

```json
{
  "site_id": "site-a",
  "module_id": "melioration-field",
  "enabled": false
}
```

Правило:
- `modules/list`, `modules/database/list`, `modules/permissions/list` поддерживают фильтр `site_id` и исключают модули, отключенные policy для этой площадки.
- Source-of-truth для policy хранится в shared SQL persistence, а runtime in-memory map используется только как process cache.

## Контракт Passkey (QR/RFID)

Маршруты:
- `GET /api/v1/passkeys/scheme`
- `GET /api/v1/passkeys/list`
- `POST /api/v1/passkeys/commands/issue`
- `POST /api/v1/passkeys/commands/revoke`

Параметры `passkeys/list`:
- `limit`, `offset`, `sort`, `order`, `q`
- `type` (`qr|rfid`)
- `status` (`active|revoked`)

Поддерживаемые поля сортировки:
- `id`, `type`, `holder`, `scope`, `status`, `issued_at`, `expires_at`

Пример элемента `passkeys/list`:

```json
{
  "id": "PK-QR-1001",
  "type": "qr",
  "holder": "Оператор смены",
  "scope": "Gate-A",
  "status": "active",
  "issued_at": "2026-03-04T14:00:00Z",
  "expires_at": "2036-03-05T14:00:00Z",
  "last_used_at": "2026-03-04T15:35:00Z"
}
```

Тело `passkeys/commands/issue`:

```json
{
  "type": "rfid",
  "holder": "Test User",
  "scope": "Gate-B",
  "ttl_minutes": 60
}
```

Тело `passkeys/commands/revoke`:

```json
{ "id": "PK-QR-1001" }
```

Если модуль `passkey` деинсталлирован, API возвращает `404 module_not_installed`.

## Контракт аутентификации Passkey

Маршруты:
- `POST /api/v1/auth/passkey/commands/start`
- `POST /api/v1/auth/passkey/commands/complete`
- `GET /api/v1/auth/passkey/status`
- `GET /api/v1/auth/me`
- `POST /api/v1/auth/commands/logout`
- `GET /api/v1/auth/permissions/scheme`
- `GET /api/v1/auth/permissions/list`
- `GET /api/v1/auth/ui-access/list`
- `POST /api/v1/auth/ui-access/commands/update`
- `GET /api/v1/auth/ui-preferences/list`
- `POST /api/v1/auth/ui-preferences/commands/update`
- `GET /api/v1/auth/shell-profile/list`
- `POST /api/v1/auth/shell-profile/commands/export`
- `POST /api/v1/auth/shell-profile/commands/import`
- `POST /api/v1/auth/shell-profile/commands/rollback`
- `GET /api/v1/auth/security-log/scheme`

Практический канон для `iP-1510`:

- локальный `L1` path: `RFID -> local auth bridge -> local session`
- phone-assisted path: `QR challenge/response`
- `BLE` не считается обязательным transport contract и допустим только как ускоритель
- для `iPhone/Safari` обязателен fallback `QR -> 4-digit code`
- backend обязан фильтровать module projection по grants до рендера UI, а не только скрывать элементы на frontend
- `GET /api/v1/auth/security-log/list`

Тело `auth/passkey/start`:

```json
{ "method": "qr" }
```

Ответ:
- `challenge_id`, `method`, `status`, `expires_at`
- для `method=qr` дополнительно `qr_payload`

Тело `auth/passkey/complete`:

```json
{
  "challenge_id": "CH-...",
  "credential": "PK-QR-1001"
}
```

`credential` может быть как `id` ключа (`PK-*`), так и внутренним credential (`QR-*`/`RF-*`).

`POST /api/v1/auth/passkey/commands/complete`:
- по успешному завершению выставляет HttpOnly cookie `ioot_session` с JWT (HMAC-SHA256).
- JWT также может передаваться через `Authorization: Bearer <token>`.

`GET /api/v1/auth/me`:
- возвращает `authenticated=true` и `user` при валидном JWT;
- возвращает `authenticated=false`, если токен отсутствует/невалиден.
- `user` включает: `role`, `scope`, `scope_type`, `tenant_id` (для tenant-scope).

`GET /api/v1/auth/permissions/list`:
- возвращает объединенную матрицу `api` + `ui` операций с расчетным флагом `allowed`;
- поддерживает фильтры:
  - `layer=api|ui`;
  - `allowed=true|false`.

`GET /api/v1/auth/ui-access/list`:
- возвращает role/action правила для UI-shell ресурса;
- текущий runtime-ресурс: `ui.sidebar.menu`;
- `id` можно передать query-параметром, по умолчанию используется `ui.sidebar.menu`.
- backend source-of-truth хранится в shared SQL persistence.

`POST /api/v1/auth/ui-access/commands/update`:
- обновляет правила UI-access для shell-ресурса;
- используется для доступа к burger-меню и прав на его редактирование;
- допустимые `actions` текущего runtime: `read`, `update`.

`GET /api/v1/auth/ui-preferences/list`:
- возвращает пользовательские shell-настройки для текущего контекста `tenant/site/industry`;
- читает `theme`, `dashboard_layout`, `quick_module_ids` с флагами наличия `has_*`;
- при отсутствии сохраненных значений frontend использует локальный fallback.
- backend source-of-truth хранится в shared SQL persistence.

`POST /api/v1/auth/ui-preferences/commands/update`:
- сохраняет пользовательские shell-настройки для текущего контекста;
- доступен всем аутентифицированным ролям как self-service ресурс;
- текущий payload включает `theme`, `dashboard_layout`, `quick_module_ids`, `industry_code`, `site_id`;
- при каждом обновлении пишет versioned snapshot в `shell-profile` для текущего `tenant/site/industry`.

`GET /api/v1/auth/shell-profile/list`:
- возвращает историю versioned shell-profile для текущего `tenant` с optional фильтрами `site_id`, `industry_code`;
- запись включает `current_version`, `versions[]`, `audit[]`, `updated_at`.
- backend source-of-truth хранится в shared SQL persistence; legacy JSON sidecars допустимы только как bootstrap import path.

`POST /api/v1/auth/shell-profile/commands/export`:
- экспортирует текущий shell-profile snapshot в JSON payload со `schema_version=v1`;
- поддерживает выбор конкретной `version`; если история еще не создана, экспортирует live-снимок из `ui-preferences` + `ui-access`.

`POST /api/v1/auth/shell-profile/commands/import`:
- импортирует shell-profile snapshot в выбранный `site_id` / `industry_code`;
- payload включает `theme`, `dashboard_layout`, `quick_module_ids`, `ui_access`;
- применяет snapshot к `ui-preferences` и `ui-access`, затем создает новую версию с audit entry.

`POST /api/v1/auth/shell-profile/commands/rollback`:
- откатывает shell-profile к выбранной `to_version`;
- повторно применяет сохраненный snapshot и пишет новую версию с `rollback_of=<to_version>`;
- доступен только `admin`, так как изменяет общие UI access rules.

`GET /api/v1/auth/security-log/list`:
- возвращает security-журнал по событиям `auth/action` (`allow|deny`) с route-контекстом;
- поддерживает фильтры `category`, `result`, `event`, `method`, `path`, `tenant_id`;
- доступен только ролям `admin|security` (иначе `403 rbac_forbidden`).

## Защита API

- Все маршруты `/api/*` защищены middleware, кроме публичных auth-маршрутов:
  - `POST /api/v1/auth/passkey/commands/start`
  - `POST /api/v1/auth/passkey/commands/complete`
  - `GET /api/v1/auth/passkey/status`
  - `GET /api/v1/auth/me`
  - `POST /api/v1/auth/commands/logout`
- При отсутствии валидного JWT возвращается:

```json
{
  "error": {
    "code": "auth_required",
    "message": "Требуется вход в приложение"
  }
}
```

- RBAC и scope-гварды применяются на уровне middleware:
  - `master` endpoint-группы (`tenant-master*`, `subscriptions/sla`, `version-policies`, `analytics`) требуют `scope_type=master`;
  - `tenant` endpoint-группы (`industry-*`, `melioration-*`, `poultry-*`, `swine-*`) требуют `scope_type=tenant`;
  - для tenant scope включена boundary-проверка `tenant_id` / `target_tenant_id` в query и JSON body;
  - при нарушении границ возвращаются `403` с кодами `scope_boundary_denied`/`tenant_boundary_denied`/`rbac_forbidden`.

## Pilot process composition (runtime facts)

Существующие API-контракты используются process-утилитами reference runtime без введения новых endpoint-ов:

- `scripts/pilot-bootstrap.py` использует `GET /api/v1/app/context`, `GET /api/v1/modules/list`, `GET /api/v1/modules/site-policy/list`, `POST /api/v1/auth/shell-profile/commands/export`, `POST /api/v1/auth/shell-profile/commands/import`, `POST /api/v1/modules/commands/install`, `POST /api/v1/modules/commands/uninstall`, `POST /api/v1/modules/site-policy/commands/set`;
- `scripts/pilot-evidence-bundle.py` использует `GET /api/v1/app/context`, `GET /ready`, `GET /api/v1/modules/list`, `GET /api/v1/modules/site-policy/list`, `POST /api/v1/auth/shell-profile/commands/export`, `GET /api/v1/auth/security-log/list`;
- `scripts/pilot-rollback-drill.py` оркестрирует baseline export/apply/evidence/restore поверх двух контуров выше.

## Синхронизация с `../green-robot`

- При дальнейшем развитии:
  - `modules/scheme` должен возвращать `display_module`, `show_filters`, `module_view`, `catalog_enrichment` (`sidebar_fields`, `infobar_fields`).
  - Shell должен поддерживать отдельный UI-access ресурс `ui.sidebar.menu` для role-gated burger-меню и конфигурации быстрых fullscreen-кнопок.
  - Shell persistence должен поддерживать backend-хранение `theme`, `dashboard_layout`, `quick_module_ids` с изоляцией по `tenant/site/industry` и локальным fallback.
  - Shell должен поддерживать versioned export/import/rollback profile для `theme`, `dashboard_layout`, `quick_module_ids`, `ui-access`, а также pilot preset-артефакты `cloud-multi` и `edge-single`.
  - Ввести единый envelope ответа со `meta` для всех новых list/get endpointов.
  - Применять `kebab-case` имена ресурсов и idempotency для критичных POST-операций.
