# Тема 2 · mesh-models-v1

Стенд: https://topic02.130.193.40.80.sslip.io

Тема 1 остаётся на classroom.130.193.40.80.sslip.io. У темы 2 отдельный процесс, база, подключения и ведомость. Сетевой код раздаётся готовым. Поддерживается Python 3.12+. Все модельные обработчики асинхронные: `async def infer(text: str) -> str`.

## Запрос пользователя

`POST /infer`, `Authorization: Bearer <ключ занятия>`, `Content-Type: application/json`.

```json
{"text":"Привет Ab","model":"frame","version":"v2","timeout_s":8}
```

`text`: непустая строка до 2000 символов. `model`: имя до 60 символов, по умолчанию `default`. `version`: строка до 40 символов или null, по умолчанию null. При отсутствии версии клиент выбирает зарегистрированный default этой модели. В обязательных заданиях default каждой модели — `v1`. `timeout_s`: 0.2–30 секунд, по умолчанию 8. Имена: латиница, цифры, `_`, `-`; в версии также `.`. Пустая строка версии недопустима.

Запрос идёт всем готовым участникам на момент отправки. У каждого собственный каталог. Отсутствующая модель возвращает явную ошибку. Маршрутизацию по capabilities разберём в теме 4. Закреплённая `version` должна исполняться точно; отсутствующая версия не означает «самая новая».

HTTP 200: `request_id`, `text`, `status` COMPLETE/PARTIAL, `expected`, `received`, `participants`, `results`, `failures`, `missing`. `results`: `worker_id`, `generation`, `model`, `version`, `output`, `elapsed_ms`. `generation=models-v1` обозначает протокол/поколение воркера, а `model/version` — фактически выполненный обработчик. Полный успех всех исполнителей даёт COMPLETE, ошибки/пропуски — PARTIAL.

`failures`: `{ "worker_id":"alice", "reason":"WORKER_ERROR: MODEL_NOT_FOUND" }`. `missing` содержит не ответивших в срок. HTTP 401 — неверный ключ, 403 — преподаватель закрыл отправку, 422 — нарушен контракт, 429 — лимит параллельных запросов, 503 — нет готовых воркеров. Клиентский HTTP timeout должен превышать `timeout_s` на несколько секунд.

## WSS worker

Адрес `wss://topic02.130.193.40.80.sslip.io/ws/worker`. Заголовок Authorization с ключом занятия. Публичный адрес нужен только серверу.

Первое сообщение:
```json
{"type":"REGISTER","worker_id":"alice","name":"alice","generation":"models-v1","client_id":"32 hex","client_secret":"64 hex"}
```
В примере длины hex описательные: реальный запускатор создаёт значения автоматически. Ответ REGISTERED содержит worker_id, heartbeat_interval_s=2 и protocol_id=mesh-models-v1. Далее `{"type":"HEARTBEAT"}` с указанным интервалом.

```json
{"type":"JOB","request_id":"...","job_id":"...","kind":"INFER","text":"Привет Ab","model":"frame","version":"v2","timeout_s":8,"manifest":null}
```

Виды: INFER — обычный запрос/автопинг, CHECK — проверка преподавателя (исполняется тем же способом), AUTHORIZE — ответ ФИО вне каталога, INSTALL — бонусная установка manifest. Отдельного ручного действия для ФИО не требуется.

Успех:
```json
{"type":"RESULT","request_id":"...","job_id":"...","output":"-----\nПривет Ab\n-----","model":"frame","version":"v2"}
```
Ошибка:
```json
{"type":"RESULT","request_id":"...","job_id":"...","error":"VERSION_NOT_FOUND"}
```
Ровно одно из output/error. `output` до 2000 символов, error до 300. MODEL_NOT_FOUND — имя неизвестно; VERSION_NOT_FOUND — имя известно, версия недоступна; INFERENCE_ERROR — найденная функция упала или нарушила выходной контракт. После ошибки процесс продолжает работу. Повторный RESULT и поздний ответ не засчитываются.

## Локальный API (то, что пишут студенты)

`register_model(name, version, handler, *, default=False)` регистрирует одну неизменяемую пару. Повтор пары запрещён. Функция должна быть async. `run_inference(text, model='default', version=None)` возвращает словарь output/model/version. Функция не добавляет worker_id к тексту: сетевой код передаёт его отдельно.

`models.setup()` вызывается один раз при запуске. `models.install_release(manifest)` вызывается по INSTALL. После успеха сетевой код возвращает output=INSTALLED. Неуспех бонуса: INSTALL_NOT_IMPLEMENTED или INSTALL_ERROR. Перезапустите worker после редактирования файлов. Загруженный код Python сам по себе не перезагружается.

## Бонус: реестр и пакетная установка

`GET /registry/models` — JSON `{"releases":[...]}`. Публичные учебные выпуски, без ключей и данных учащихся. Каждый манифест (паспорт выпуска) содержит model, version, entrypoint, artifact_url, sha256, contract, dependencies, description. Список сейчас состоит из семи выпусков v1: server_echo, oracle_location, oracle_name, weather_mood, coin_oracle, dad_joke, ascii_cat. Все вымышленные сведения явно обозначены как МОК.

`GET /registry/artifacts/{model}_{version}.py` — UTF-8 Python, `async def infer(text) -> str`, entrypoint=infer, dependencies=[]. В учебном реестре имя и версия состоят из ASCII букв, цифр и `_`; это намеренно уже общего API инференса. Запрещены слеши, точки и обход каталогов. Не использовать произвольные URL и перенаправления. Файлы доступны только по опубликованным путям.

Задание на весь пакет:
```json
{"type":"JOB","kind":"INSTALL","model":"classroom_pack","version":"v1","manifest":{"registry_url":"/registry/models"},"timeout_s":30,"text":"...","request_id":"...","job_id":"..."}
```
Готовый client.py вызывает `await install_release(job['manifest'])`. Внутри: получить releases, цикл по каждому выпуску, скачать не более 64 KiB, проверить SHA-256, сохранить, загрузить модуль, зарегистрировать. Успешное завершение функции означает, что установлен весь список. Сеть через asyncio.to_thread не блокирует пульс.

Транспорт сам отправит output=INSTALLED с model=classroom_pack и version=v1. При ошибке поднимите ValueError('INSTALL_ERROR'). Сервер после установки проверяет все семь моделей; ожидает точное поведение опубликованного кода. Установка одного server_echo не закрывает этап. Повтор того же выпуска допустим без повторной регистрации; другую реализацию под уже занятой парой не подменяем. Старый одиночный манифест можно поддержать дополнительно, но проверка теперь пакетная.

Хэш доказывает соответствие файлу из описания, не безопасность автора. Импорт исполняет Python. Только учебные файлы преподавателя, без чтения окружения, настоящего имени, локации, сети или файлов пользователя. Пошаговые новые методы описаны в BONUS.md и бонусной презентации.

## Преподавательские проверки

`POST /api/admin/check`: `{ "stage":"models", "worker_id":null }`. Этапы presence/models/versions/errors/artifact. Только ключ преподавателя или его cookie с same-origin запросом. worker_id=null проверяет всех текущих готовых участников, строка выбирает одного. Одновременно одна проверка. Пропуск этапа не штрафует других. Результат: stage и массив results (worker_id, passed, details по каждому запросу). Неуспешную проверку можно повторять. Засчитанный этап сохраняется; история попыток тоже.

Пять отметок хранятся независимо. Пинг отмечает только присутствие. ФИО не закрывает задания. Базовый успех темы 2: presence + models + versions. Дополнительно errors, бонус artifact. Число отметок 0–5, не сумма всех ответов. Ведомости тем 1 и 2 пока раздельные.

## Совместимость с клиентом первой темы

REGISTER с generation=mock-v1 принимается; REGISTERED возвращает ожидаемый старым клиентом protocol_id=mesh-classroom-v1. Современный models-v1 получает mesh-models-v1. Старые файлы менять не надо: заменить --server на wss://topic02.130.193.40.80.sslip.io/ws/worker.

Старому клиенту доступны AUTHORIZE, пульс, обычный default без версии и явно default/v1. Метаданные default/v1 добавляет сервер как условное обозначение единственной моки, а не как версию её исходников. Проверка presence и автопинг засчитываются; остальные этапы так закрыть нельзя.

Для иной модели, иной версии или INSTALL сервер сразу записывает failure с reason=CLIENT_UPGRADE_REQUIRED и не отправляет JOB старому клиенту. В смешанной группе новые клиенты продолжают выполнять запрос; общий ответ может быть PARTIAL. Это не MODEL_NOT_FOUND: каталог ещё не реализован в самом клиенте. Соединение и дальнейшие обычные запросы продолжают работать. На дашборде подключённый старый клиент помечен «Клиент темы 1 · только default».

## Проверить одного участника с общего дашборда
POST /api/check, Authorization: Bearer <ключ занятия>, JSON {"stage":"versions","worker_id":"alice"}. worker_id обязателен: общего запуска всей группы у ученика нет. stages: presence, models, versions, errors, artifact. Можно проверять себя или коллегу. Окно отправки обычных запросов на это не влияет. Ответ: stage и results для одного участника. 403 — нет доступа; 422 — неверные поля; 503 — клиент не подключён; 429 — проверка уже идёт или ещё не прошло 15 секунд с предыдущего запуска для участника. Одновременно одна проверка на сервере. Баллы за этап повторно не начисляются.

artifact запускает INSTALL только для выбранного участника, а затем вызывает весь пакет. Предварительное объявление установленных моделей не требуется. Автопинг проверяет только присутствие и не устанавливает пакеты. Преподаватель сохраняет отдельный запуск всей группы.

На этапах presence/models поле version в ответе необязательно: допускается отсутствие/null или v1. Транспорт допускает отсутствие version у результата run_inference. На этапе versions фактическая версия обязательна. v — условный префикс; API хранит строковую метку и не сортирует выпуски как числа.
