1579 lines
107 KiB
Markdown
1579 lines
107 KiB
Markdown
# План реализации отдельного AI-ассистента для Magistr
|
||
|
||
> Статус: исходная техническая спецификация для создания отдельного проекта
|
||
>
|
||
> Версия документа: 1.0
|
||
>
|
||
> Дата: 5 августа 2026 года
|
||
>
|
||
> Основной интегрируемый продукт: Magistr
|
||
>
|
||
> Рабочее название нового репозитория: `magistr-assistant`
|
||
|
||
## 1. Назначение документа
|
||
|
||
Документ описывает реализацию отдельного коммерческого микросервиса с персонажем-проводником для Magistr. Ассистент помогает пользователям находить страницы и элементы интерфейса, объясняет последовательность действий, подсвечивает нужные элементы и принимает запросы текстом или голосом.
|
||
|
||
Документ должен использоваться как:
|
||
|
||
- исходная архитектурная спецификация нового репозитория;
|
||
- основание для декомпозиции задач в Gitea;
|
||
- контракт между новым сервисом и Magistr;
|
||
- перечень обязательных проверок перед пилотом;
|
||
- фиксация продуктовых ограничений первого релиза;
|
||
- точка продолжения для последующего режима выполнения действий после подтверждения пользователя.
|
||
|
||
Этот документ не является разрешением на создание нового репозитория, изменение production-инфраструктуры или подключение платных AI API. Такие действия выполняются отдельными этапами после явного начала реализации.
|
||
|
||
## 2. Зафиксированные продуктовые решения
|
||
|
||
| Вопрос | Решение |
|
||
|---|---|
|
||
| Пользователи первого релиза | `ADMIN`, `EDUCATION_OFFICE`, `DEPARTMENT` |
|
||
| Возможности первого релиза | Подсказки, переходы по разрешённым страницам и подсветка элементов |
|
||
| Автоматическое выполнение действий | Не входит в первый релиз; возможно позже, только после явного подтверждения |
|
||
| Языки | Русский и английский |
|
||
| Персонаж | Один для всех университетов |
|
||
| Образ | Животное или абстрактный проводник; окончательный образ выбирается позже |
|
||
| Текстовый AI | Внешний российский сервис по API через заменяемый адаптер |
|
||
| Голос | Push-to-talk: запись только пока пользователь удерживает кнопку |
|
||
| Управление подпиской | Только оператор платформы |
|
||
| Оплата | Вручную по договору, без платёжной системы |
|
||
| Репозиторий | Отдельный репозиторий и независимый релизный цикл |
|
||
| Данные диалогов | Полный текст и аудио не сохраняются |
|
||
| Аналитика | Только обезличенная статистика и явная оценка ответа |
|
||
| Retention пользовательской аналитики | Автоматическое удаление данных старше 30 дней |
|
||
|
||
## 3. Цель первого релиза
|
||
|
||
Пользователь должен иметь возможность открыть персонажа, задать вопрос текстом или голосом и получить короткую инструкцию. Если пользователь имеет право на соответствующий раздел, интерфейс Magistr должен перейти на нужную страницу и подсветить следующий элемент. Если права отсутствуют, ассистент должен прямо сообщить об ограничении и указать, к какой роли следует обратиться.
|
||
|
||
Пример ожидаемого поведения:
|
||
|
||
1. Пользователь спрашивает: «Где менять время пар в субботу?».
|
||
2. Ассистент проверяет роль и доступные возможности.
|
||
3. Для `ADMIN` или `EDUCATION_OFFICE` открывает настройки временных слотов.
|
||
4. Подсвечивает выбор области действия и объясняет, что нужна субботняя сетка.
|
||
5. Для `DEPARTMENT` не пытается открыть запрещённую страницу и сообщает, что изменение выполняет администратор или учебный отдел.
|
||
|
||
## 4. Что не входит в первый релиз
|
||
|
||
- автоматическое нажатие кнопок;
|
||
- заполнение или отправка форм;
|
||
- создание, изменение и удаление данных Magistr;
|
||
- доступ ассистента к расписаниям, пользователям и другим бизнес-данным напрямую через API Magistr;
|
||
- чтение всего DOM и отправка содержимого страницы AI-провайдеру;
|
||
- долговременная память о пользователе;
|
||
- сохранение истории диалога на сервере;
|
||
- фоновой постоянно включённый микрофон;
|
||
- распознавание пользователя по голосу;
|
||
- 3D-персонаж, VRM, Live2D или сложный игровой движок;
|
||
- пользовательская настройка персонажа университетом;
|
||
- личный кабинет оплаты;
|
||
- онлайн-эквайринг, автопродление и возвраты;
|
||
- внешняя веб-навигация и поиск в интернете;
|
||
- поддержка ролей `SCHEDULE_VIEWER`, `TEACHER` и `STUDENT` в первом пилоте;
|
||
- использование существующих tenant-БД Magistr для данных подписки и аналитики ассистента.
|
||
|
||
## 5. Подтверждённые ограничения текущего Magistr
|
||
|
||
### 5.1. Frontend
|
||
|
||
Основная административная панель — Vanilla JavaScript SPA. Вкладка выбирается через hash и `data-tab`, после чего HTML загружается динамически в `#app-content`. Инициализация конкретного экрана завершается только после асинхронного импорта его JS-модуля.
|
||
|
||
Страница настроек — отдельный SPA со своим набором маршрутов. Переход из основной панели в настройки приводит к загрузке нового документа.
|
||
|
||
Кабинет кафедры находится внутри основной административной панели. Для роли `DEPARTMENT` целевой режим просмотра расписаний принудительно ограничен собственной кафедрой.
|
||
|
||
Следствия для ассистента:
|
||
|
||
- подсветка возможна только после завершения загрузки и инициализации представления;
|
||
- при переходе между основной панелью и настройками необходимо безопасно переносить незавершённое действие;
|
||
- нельзя рассчитывать на случайные CSS-классы и текст кнопки как на стабильный контракт;
|
||
- динамические элементы требуют resolver-функций, а не статического селектора;
|
||
- ассистент обязан учитывать роль до перехода, а клиент обязан повторно проверить действие.
|
||
|
||
### 5.2. Ролевая модель
|
||
|
||
| Возможность | ADMIN | EDUCATION_OFFICE | DEPARTMENT |
|
||
|---|---:|---:|---:|
|
||
| Открыть календарный учебный график | Да | Да | Нет |
|
||
| Изменить код дня на `*` | Да | Да | Нет |
|
||
| Открыть настройки временных слотов | Да | Да | Нет |
|
||
| Изменить субботнюю сетку времени | Да | Да | Нет |
|
||
| Просматривать расписания | Да | Да | Только своя кафедра |
|
||
| Искать конкретную группу прямым фильтром | Да | Да | Нет, режим зафиксирован на кафедре |
|
||
| Создать точечный перенос занятия | Да | Да | Нет |
|
||
|
||
Ролевая недоступность является ожидаемым результатом workflow. Она не должна оформляться как техническая ошибка или компенсироваться попыткой открыть скрытый раздел.
|
||
|
||
### 5.3. Мультитенантность
|
||
|
||
Tenant Magistr определяется по домену запроса. Access JWT связан с tenant и не принимается на другом домене. Новый сервис не должен получать основной симметричный `JWT_SECRET` Magistr.
|
||
|
||
Подписки ассистента хранятся централизованно в собственной БД, где ключом является стабильный `tenantKey`, совпадающий с нормализованным tenant Magistr. Выключение подписки должно блокировать API на сервере, а не только скрывать виджет.
|
||
|
||
### 5.4. Browser security
|
||
|
||
Frontend Magistr использует строгую Content Security Policy:
|
||
|
||
- скрипты и соединения только same-origin;
|
||
- inline-скрипты запрещены;
|
||
- произвольные inline-стили запрещены;
|
||
- `unsafe-eval` отсутствует;
|
||
- доступ к микрофону сейчас запрещён через `Permissions-Policy`.
|
||
|
||
Следствия:
|
||
|
||
- виджет распространяется как проверенный ESM-артефакт и включается в frontend-сборку Magistr;
|
||
- API публикуется через same-origin маршрут `/assistant/*`;
|
||
- подсветка реализуется CSS-классом на целевом элементе, а не динамическим inline-стилем;
|
||
- текст ответа вставляется через `textContent`, без исполнения HTML;
|
||
- для голоса разрешается только `microphone=(self)`;
|
||
- `media-src` и `worker-src` расширяются только при доказанной необходимости конкретной реализации;
|
||
- сбой ассистента не должен блокировать загрузку Magistr.
|
||
|
||
## 6. Стартовые пользовательские сценарии
|
||
|
||
### 6.1. Добавить выходной в четверг текущей недели
|
||
|
||
Идентификатор намерения: `calendar.set_non_working_day`.
|
||
|
||
Доступ: `ADMIN`, `EDUCATION_OFFICE`.
|
||
|
||
Требуемый пользовательский путь:
|
||
|
||
1. Открыть «Календарный учебный график».
|
||
2. Открыть внутреннюю вкладку «Сетки».
|
||
3. Выбрать нужный календарный график.
|
||
4. Нажать «Загрузить сетку».
|
||
5. Найти ячейку четверга нужной недели и курса.
|
||
6. Выбрать код `* — Нерабочий праздничный день`.
|
||
7. Нажать «Сохранить сетку».
|
||
|
||
Ассистент не знает, какой учебный график и курс имеет в виду пользователь, поэтому не выбирает их самостоятельно. Он подсвечивает выбор графика и объясняет оставшиеся шаги. Если в будущем появится безопасный read-only контекст выбранного графика, workflow можно сделать точнее.
|
||
|
||
Поведение для `DEPARTMENT`:
|
||
|
||
- не выполнять переход;
|
||
- ответить, что календарный график изменяют администратор или учебный отдел;
|
||
- предложить передать им дату, курс и название группы/графика.
|
||
|
||
### 6.2. Изменить время пар в субботу
|
||
|
||
Идентификатор намерения: `time_slots.edit_saturday`.
|
||
|
||
Доступ: `ADMIN`, `EDUCATION_OFFICE`.
|
||
|
||
Требуемый пользовательский путь:
|
||
|
||
1. Открыть «Настройки».
|
||
2. Перейти во вкладку «Временные слоты».
|
||
3. В поле «Область действия» выбрать системную субботнюю сетку.
|
||
4. Найти нужную пару в таблице.
|
||
5. Нажать «Изменить».
|
||
6. Указать начало и окончание.
|
||
7. Нажать «Сохранить слот».
|
||
|
||
Поведение для `DEPARTMENT` аналогично предыдущему сценарию: объяснить отсутствие прав и указать ответственную роль.
|
||
|
||
### 6.3. Посмотреть расписание группы ИБ-01б
|
||
|
||
Идентификатор намерения: `schedule.view_group`.
|
||
|
||
Вариант для `ADMIN` и `EDUCATION_OFFICE`:
|
||
|
||
1. Открыть «Просмотр расписаний».
|
||
2. В поле «Что смотреть» выбрать «Группа».
|
||
3. В поле «Группа» найти `ИБ-01б`.
|
||
4. При необходимости выбрать семестр.
|
||
5. Нажать «Показать».
|
||
|
||
Ассистент не должен читать или передавать AI-провайдеру полный список групп. Имя группы извлекается из пользовательской фразы и остаётся частью подсказки. На первом этапе ассистент подсвечивает поле группы, но не меняет его значение.
|
||
|
||
Вариант для `DEPARTMENT`:
|
||
|
||
1. Открыть «Просмотр расписаний».
|
||
2. Объяснить, что кафедра видит только расписания своей кафедры.
|
||
3. Подсветить кнопку «Показать».
|
||
4. После загрузки предложить выбрать группу среди найденных результатов.
|
||
|
||
Если группа не относится к кафедре пользователя, ассистент должен сообщить об ограничении, а не предлагать обход.
|
||
|
||
### 6.4. Перенести пару с одной даты на другую
|
||
|
||
Идентификатор намерения: `schedule.move_lesson`.
|
||
|
||
Доступ: `ADMIN`, `EDUCATION_OFFICE`.
|
||
|
||
Требуемый пользовательский путь:
|
||
|
||
1. Открыть «Просмотр расписаний».
|
||
2. Найти нужное расписание и период.
|
||
3. Нажать «Показать».
|
||
4. На карточке конкретной пары нажать «Изменить».
|
||
5. Выбрать доступную дату переноса.
|
||
6. Выбрать время, действующее для новой даты.
|
||
7. При необходимости уточнить преподавателя, аудиторию, формат и комментарий.
|
||
8. Проверить блок «Было / Стало».
|
||
9. Нажать «Сохранить».
|
||
|
||
Ассистент не выбирает конкретное занятие сам и не сохраняет перенос. Для `DEPARTMENT` отвечает, что точечные изменения выполняют администратор или учебный отдел.
|
||
|
||
## 7. Целевая архитектура
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
User[Пользователь Magistr]
|
||
Host[Frontend Magistr]
|
||
Loader[Assistant loader]
|
||
Widget[Assistant Web Component]
|
||
Bridge[Magistr UI bridge]
|
||
Caddy[Caddy / same-origin route]
|
||
API[magistr-assistant API]
|
||
Entitlement[Entitlement service]
|
||
Workflow[Workflow engine]
|
||
Provider[Российский AI provider]
|
||
DB[(Assistant PostgreSQL)]
|
||
OTel[OpenTelemetry Collector]
|
||
SigNoz[SigNoz]
|
||
|
||
User --> Host
|
||
Host --> Loader
|
||
Loader --> Widget
|
||
Widget --> Bridge
|
||
Bridge --> Host
|
||
Widget -->|/assistant/v1/*| Caddy
|
||
Caddy --> API
|
||
API --> Entitlement
|
||
API --> Workflow
|
||
Workflow --> Provider
|
||
Entitlement --> DB
|
||
API --> DB
|
||
API --> OTel
|
||
OTel --> SigNoz
|
||
```
|
||
|
||
### 7.1. Границы ответственности
|
||
|
||
#### Magistr
|
||
|
||
- аутентифицирует пользователя;
|
||
- определяет tenant;
|
||
- выдаёт короткоживущий assistant-токен;
|
||
- сообщает роль и `departmentId` в ограниченном наборе claims;
|
||
- предоставляет host bridge для навигации и подсветки;
|
||
- содержит стабильные `data-assistant-target` на элементах;
|
||
- не передаёт новому сервису основной JWT-секрет;
|
||
- продолжает обеспечивать backend-авторизацию всех бизнес-операций.
|
||
|
||
#### Новый сервис
|
||
|
||
- проверяет assistant-токен;
|
||
- проверяет подписку tenant и разрешённую роль;
|
||
- распознаёт намерение;
|
||
- выбирает проверенный workflow;
|
||
- формирует двуязычную подсказку;
|
||
- возвращает только разрешённые семантические UI-действия;
|
||
- выполняет STT и TTS через провайдерские адаптеры;
|
||
- учитывает квоты;
|
||
- сохраняет только обезличенную статистику и оценку;
|
||
- удаляет пользовательскую аналитику старше 30 дней.
|
||
|
||
#### AI-провайдер
|
||
|
||
- получает только минимальный текст запроса, разрешённые workflow и локаль;
|
||
- не получает JWT, tenant-домен, userId, DOM, бизнес-данные или аудио после завершения STT;
|
||
- не является источником прав доступа;
|
||
- не генерирует исполняемый код или селекторы.
|
||
|
||
## 8. Выбор технологического стека
|
||
|
||
### 8.1. API-сервис
|
||
|
||
Рекомендуемый стек:
|
||
|
||
- Python 3.13 с последним patch-релизом как консервативная baseline-версия; переход на 3.14 — после проверки всей матрицы AI/audio-зависимостей;
|
||
- FastAPI;
|
||
- Pydantic v2 для строгих DTO и JSON Schema;
|
||
- Uvicorn как ASGI server;
|
||
- SQLAlchemy 2.x;
|
||
- Alembic для миграций отдельной БД ассистента;
|
||
- PostgreSQL 17 с последним доступным minor-релизом для новой отдельной инсталляции; если инфраструктурно сервис размещается в существующем PostgreSQL 16, использовать последний minor 16 и заранее проверить миграции;
|
||
- `httpx` для вызова внешних AI API;
|
||
- OpenTelemetry Python SDK;
|
||
- `structlog` или стандартный `logging` с JSON formatter;
|
||
- `pytest`, `pytest-asyncio`, `respx` и Testcontainers;
|
||
- `ruff` для lint/format;
|
||
- `mypy` или `pyright` в строгом режиме;
|
||
- `uv` и `uv.lock` для воспроизводимых зависимостей.
|
||
|
||
Причины выбора:
|
||
|
||
- зрелая экосистема AI, аудио и HTTP-интеграций;
|
||
- быстрый выпуск адаптеров к российским API;
|
||
- автоматическая OpenAPI-схема для генерации TypeScript-контрактов;
|
||
- удобная строгая валидация ответов модели;
|
||
- асинхронная обработка внешних API без блокировки worker;
|
||
- простое тестирование провайдеров через fake-адаптеры;
|
||
- независимость от Java backend Magistr.
|
||
|
||
В Kubernetes каждый pod запускает один ASGI-процесс. Масштабирование выполняется репликами Deployment, а миграции запускаются отдельным одноразовым Job до rollout. Несколько worker внутри одного контейнера не являются базовой production-схемой.
|
||
|
||
### 8.2. Виджет
|
||
|
||
Рекомендуемый стек:
|
||
|
||
- TypeScript со строгими настройками;
|
||
- Web Component с Shadow DOM;
|
||
- без React, Vue и других runtime-фреймворков;
|
||
- ESM-сборка через esbuild;
|
||
- CSS отдельным same-origin файлом;
|
||
- Web Audio API и MediaRecorder только для явной голосовой сессии;
|
||
- Vitest или `node:test` для unit-тестов;
|
||
- Playwright для браузерной интеграции;
|
||
- OpenAPI-generated типы для API;
|
||
- exact dependency versions и lock-файл.
|
||
|
||
Причины выбора:
|
||
|
||
- Magistr уже использует ES modules и Vanilla JavaScript;
|
||
- Web Component изолирует стили персонажа от страниц;
|
||
- небольшой размер bundle;
|
||
- отсутствие второго frontend-фреймворка в Magistr;
|
||
- возможность опубликовать виджет отдельным версионированным пакетом;
|
||
- Shadow DOM не мешает host bridge подсвечивать элементы основного документа.
|
||
|
||
### 8.3. Почему не следует делать fork AIRI
|
||
|
||
AIRI решает значительно более широкую задачу: самостоятельный виртуальный персонаж, локальные модели, память, игры, Live2D/VRM и множество runtime-окружений. Для Magistr полезны только идеи разделения UI, диалога, STT и TTS. Fork создаст большой объём лишних зависимостей и усложнит безопасность, CSP, обновления и поддержку.
|
||
|
||
## 9. Предлагаемая структура отдельного репозитория
|
||
|
||
```text
|
||
magistr-assistant/
|
||
├── AGENTS.md
|
||
├── README.md
|
||
├── pyproject.toml
|
||
├── uv.lock
|
||
├── package.json
|
||
├── package-lock.json
|
||
├── Makefile
|
||
├── compose.yaml
|
||
├── .env.example
|
||
├── .gitea/
|
||
│ └── workflows/
|
||
│ ├── checks.yaml
|
||
│ ├── build-image.yaml
|
||
│ ├── publish-widget.yaml
|
||
│ └── deploy.yaml
|
||
├── service/
|
||
│ └── assistant_api/
|
||
│ ├── main.py
|
||
│ ├── api/
|
||
│ │ ├── bootstrap.py
|
||
│ │ ├── chat.py
|
||
│ │ ├── voice.py
|
||
│ │ ├── feedback.py
|
||
│ │ ├── operator.py
|
||
│ │ └── health.py
|
||
│ ├── auth/
|
||
│ │ ├── assistant_token.py
|
||
│ │ └── operator_auth.py
|
||
│ ├── domain/
|
||
│ │ ├── entitlement.py
|
||
│ │ ├── interaction.py
|
||
│ │ ├── workflow.py
|
||
│ │ └── ui_action.py
|
||
│ ├── orchestration/
|
||
│ │ ├── intent_resolver.py
|
||
│ │ ├── workflow_engine.py
|
||
│ │ ├── response_validator.py
|
||
│ │ └── language.py
|
||
│ ├── providers/
|
||
│ │ ├── base.py
|
||
│ │ ├── fake.py
|
||
│ │ ├── llm.py
|
||
│ │ ├── stt.py
|
||
│ │ └── tts.py
|
||
│ ├── persistence/
|
||
│ │ ├── models.py
|
||
│ │ ├── repositories.py
|
||
│ │ └── retention.py
|
||
│ ├── observability/
|
||
│ │ ├── logging.py
|
||
│ │ ├── metrics.py
|
||
│ │ └── tracing.py
|
||
│ └── settings.py
|
||
├── migrations/
|
||
│ ├── env.py
|
||
│ └── versions/
|
||
├── widget/
|
||
│ ├── src/
|
||
│ │ ├── assistant-element.ts
|
||
│ │ ├── api-client.ts
|
||
│ │ ├── conversation-store.ts
|
||
│ │ ├── hold-to-talk.ts
|
||
│ │ ├── host-bridge.ts
|
||
│ │ ├── i18n.ts
|
||
│ │ ├── pending-action.ts
|
||
│ │ ├── renderer.ts
|
||
│ │ └── styles.css
|
||
│ ├── assets/
|
||
│ │ └── placeholder-character/
|
||
│ ├── tests/
|
||
│ ├── tsconfig.json
|
||
│ └── esbuild.mjs
|
||
├── contracts/
|
||
│ ├── site-map.schema.json
|
||
│ ├── workflow.schema.json
|
||
│ ├── ui-action.schema.json
|
||
│ ├── assistant-response.schema.json
|
||
│ └── generated/
|
||
├── content/
|
||
│ ├── site-map/
|
||
│ │ └── magistr.v1.yaml
|
||
│ └── workflows/
|
||
│ ├── calendar.set_non_working_day.yaml
|
||
│ ├── time_slots.edit_saturday.yaml
|
||
│ ├── schedule.view_group.yaml
|
||
│ └── schedule.move_lesson.yaml
|
||
├── deploy/
|
||
│ ├── docker/
|
||
│ └── kubernetes/
|
||
├── tests/
|
||
│ ├── contract/
|
||
│ ├── integration/
|
||
│ ├── retention/
|
||
│ ├── security/
|
||
│ └── provider/
|
||
└── docs/
|
||
├── ARCHITECTURE.md
|
||
├── API.md
|
||
├── CONTENT_AUTHORING.md
|
||
├── DEPLOYMENT.md
|
||
├── PRIVACY.md
|
||
├── RUNBOOK.md
|
||
└── THREAT_MODEL.md
|
||
```
|
||
|
||
`deploy/kubernetes` может содержать шаблоны нового проекта, но production source of truth должен быть согласован с текущим внешним инфраструктурным контуром. Kubernetes-файлы ассистента не следует добавлять в репозиторий Magistr.
|
||
|
||
## 10. Модель интеграции релизов
|
||
|
||
Новый репозиторий публикует два независимых артефакта:
|
||
|
||
1. Docker image API-сервиса.
|
||
2. Версионированный ESM-пакет виджета.
|
||
|
||
API развивается независимо при соблюдении обратной совместимости `/v1`. Magistr фиксирует точную версию widget-пакета и обновляет её отдельным pull request. Это исключает незаметную подмену исполняемого кода на странице без пересборки Magistr.
|
||
|
||
Правила совместимости:
|
||
|
||
- semantic versioning для API и widget;
|
||
- `apiVersion` в bootstrap-ответе;
|
||
- `siteMapVersion` в widget, API и контексте страницы;
|
||
- сервис поддерживает текущую и предыдущую minor-версию site map;
|
||
- несовместимая версия приводит к безопасному текстовому ответу без UI-действий;
|
||
- удаление action или target выполняется только в major-версии контракта;
|
||
- widget никогда не загружает исполняемый JavaScript с AI-провайдера.
|
||
|
||
## 11. Контракт навигации и подсветки
|
||
|
||
### 11.1. Site map
|
||
|
||
Ассистент не должен угадывать устройство интерфейса по DOM. В отдельном репозитории хранится версионированная карта разрешённых маршрутов и целей. В Magistr ей соответствуют стабильные атрибуты `data-assistant-target` и функции host bridge.
|
||
|
||
Пример элемента карты:
|
||
|
||
```yaml
|
||
siteMapVersion: 1.0.0
|
||
application: admin
|
||
routes:
|
||
- id: academic-calendar.grid
|
||
document: admin
|
||
tab: academic-calendar
|
||
roles: [ADMIN, EDUCATION_OFFICE]
|
||
readyEvent: magistr:view-mounted
|
||
targets:
|
||
- id: academic-calendar.inner-tab.grid
|
||
kind: static
|
||
- id: academic-calendar.graph-select
|
||
kind: static
|
||
- id: academic-calendar.load-grid
|
||
kind: static
|
||
- id: academic-calendar.day-cell
|
||
kind: resolver
|
||
acceptedParameters: [isoDate, course]
|
||
```
|
||
|
||
Site map содержит только интерфейсный контракт: маршруты, допустимые роли, цели, типы параметров и локализованные названия. В нём нет токенов, tenant-настроек, персональных данных и бизнес-содержимого страницы.
|
||
|
||
### 11.2. Стабильные цели первого релиза
|
||
|
||
При реализации в Magistr необходимо добавить стабильные цели минимум для следующих элементов:
|
||
|
||
| Target ID | Назначение |
|
||
|---|---|
|
||
| `navigation.academic-calendar` | Переход к календарному учебному графику |
|
||
| `academic-calendar.inner-tab.grid` | Внутренняя вкладка «Сетки» |
|
||
| `academic-calendar.graph-select` | Выбор календарного графика |
|
||
| `academic-calendar.load-grid` | Загрузка сетки |
|
||
| `academic-calendar.day-cell` | Ячейка конкретной даты и курса через resolver |
|
||
| `academic-calendar.activity-select` | Выбор кода дня |
|
||
| `academic-calendar.save-grid` | Кнопка сохранения сетки |
|
||
| `navigation.settings` | Переход в отдельный SPA настроек |
|
||
| `settings.time-slots` | Вкладка временных слотов |
|
||
| `time-slots.scope-select` | Область действия сетки |
|
||
| `time-slots.slot-row` | Строка конкретной пары через resolver |
|
||
| `time-slots.edit-slot` | Кнопка изменения слота |
|
||
| `time-slots.save-slot` | Сохранение слота |
|
||
| `navigation.schedule-view` | Просмотр расписаний |
|
||
| `schedule-view.target-select` | Поле «Что смотреть» |
|
||
| `schedule-view.group-select` | Выбор группы |
|
||
| `schedule-view.semester-select` | Выбор семестра |
|
||
| `schedule-view.load` | Кнопка «Показать» |
|
||
| `schedule-view.lesson-card` | Карточка занятия через resolver |
|
||
| `schedule-view.edit-override` | Кнопка изменения занятия |
|
||
| `schedule-override.date` | Дата переноса |
|
||
| `schedule-override.time-slot` | Время занятия |
|
||
| `schedule-override.save` | Сохранение точечного изменения |
|
||
|
||
Target ID является стабильным публичным контрактом. Он не должен совпадать с CSS-селектором и не должен раскрывать внутренние числовые идентификаторы. Если нужен динамический элемент, host bridge получает строго типизированные параметры и сам находит его в текущем представлении.
|
||
|
||
### 11.3. Разрешённые UI-действия
|
||
|
||
API может вернуть только следующие семантические действия:
|
||
|
||
| Действие | Параметры | Эффект |
|
||
|---|---|---|
|
||
| `navigate` | `routeId` | Открыть разрешённый маршрут |
|
||
| `highlight` | `targetId`, необязательные типизированные параметры | Прокрутить к элементу и подсветить его |
|
||
| `showWorkflow` | `workflowId`, `currentStep` | Показать локализованные шаги в виджете |
|
||
| `clearHighlight` | нет | Снять предыдущую подсветку |
|
||
| `announce` | локализованный ключ | Озвучить короткое состояние через ARIA live region |
|
||
|
||
Запрещены действия `click`, `type`, `submit`, `eval`, произвольный URL, произвольный CSS-селектор и произвольный JavaScript. Параметры, не предусмотренные JSON Schema, отклоняются.
|
||
|
||
Пример безопасного ответа:
|
||
|
||
```json
|
||
{
|
||
"requestId": "019...",
|
||
"locale": "ru",
|
||
"answer": "Открою просмотр расписаний и покажу поле выбора группы.",
|
||
"workflow": {
|
||
"id": "schedule.view_group",
|
||
"version": "1.0.0",
|
||
"currentStep": 2
|
||
},
|
||
"actions": [
|
||
{"type": "navigate", "routeId": "schedule-view"},
|
||
{"type": "highlight", "targetId": "schedule-view.group-select"}
|
||
],
|
||
"feedbackToken": "opaque-short-lived-token"
|
||
}
|
||
```
|
||
|
||
### 11.4. Выполнение действия на клиенте
|
||
|
||
Host bridge выполняет последовательность:
|
||
|
||
1. Проверяет JSON Schema, версию контракта, текущую роль и allowlist маршрута.
|
||
2. Выполняет разрешённую навигацию штатным роутером Magistr.
|
||
3. Ждёт событие `magistr:view-mounted` с ожидаемым `routeId` и `siteMapVersion`.
|
||
4. Находит target через зарегистрированный resolver.
|
||
5. Прокручивает элемент через `scrollIntoView` с учётом `prefers-reduced-motion`.
|
||
6. Добавляет CSS-класс подсветки и доступное пояснение.
|
||
7. Снимает подсветку по `Escape`, клику пользователя, смене маршрута или таймауту.
|
||
|
||
Если цель не найдена за настраиваемый таймаут, клиент показывает шаги текстом, регистрирует обезличенное событие `target_not_found` и не пытается найти похожий элемент эвристикой.
|
||
|
||
Переход между основной панелью и отдельным SPA настроек выполняется через `sessionStorage`. Разрешено сохранить только `actionId`, `routeId`, `targetId`, типизированные несекретные параметры, версию и срок жизни не более двух минут. Свободный текст запроса, токены и пользовательские данные там не сохраняются.
|
||
|
||
## 12. Формат проверенных workflow
|
||
|
||
Каждый поддерживаемый сценарий хранится в Git и проходит code review. Модель не придумывает инструкцию с нуля, а выбирает и адаптирует утверждённый workflow.
|
||
|
||
```yaml
|
||
id: time_slots.edit_saturday
|
||
version: 1.0.0
|
||
status: published
|
||
roles: [ADMIN, EDUCATION_OFFICE]
|
||
locales:
|
||
ru:
|
||
title: Изменить время пар в субботу
|
||
examples:
|
||
- Где менять время пар в субботу?
|
||
- Как изменить субботние звонки?
|
||
denied: Изменять временные слоты могут администратор и учебный отдел.
|
||
en:
|
||
title: Change Saturday lesson times
|
||
examples:
|
||
- Where can I change Saturday lesson times?
|
||
steps:
|
||
- id: open-settings
|
||
action: {type: navigate, routeId: settings.time-slots}
|
||
- id: choose-scope
|
||
action: {type: highlight, targetId: time-slots.scope-select}
|
||
requiresUserChoice: true
|
||
- id: choose-slot
|
||
action: {type: highlight, targetId: time-slots.slot-row}
|
||
requiresUserChoice: true
|
||
- id: save
|
||
action: {type: highlight, targetId: time-slots.save-slot}
|
||
irreversible: false
|
||
```
|
||
|
||
Обязательные поля workflow:
|
||
|
||
- уникальный стабильный `id` и semver-версия;
|
||
- разрешённые роли;
|
||
- русские и английские примеры намерения;
|
||
- локализованный отказ по правам;
|
||
- последовательность шагов;
|
||
- явные точки, где требуется выбор пользователя;
|
||
- допустимые параметры и их типы;
|
||
- требуемая версия site map;
|
||
- дата проверки и ответственный редактор;
|
||
- тестовые фразы и ожидаемые действия;
|
||
- признак снятия с публикации без физического удаления старой версии.
|
||
|
||
Контент публикуется вместе с API image или как подписанный неизменяемый bundle. Редактирование workflow через production-БД в первом релизе не требуется: Git обеспечивает review, историю и откат.
|
||
|
||
## 13. Обработка текстового запроса и роль AI
|
||
|
||
### 13.1. Конвейер
|
||
|
||
1. Проверить assistant-токен и активную подписку tenant.
|
||
2. Нормализовать локаль, длину и Unicode запроса.
|
||
3. Удалить очевидные секреты по маскам только как дополнительную защиту; не обещать абсолютную деидентификацию произвольного текста.
|
||
4. Сопоставить запрос с проверенными примерами детерминированным поиском.
|
||
5. Если совпадение уверенное, выбрать workflow без вызова LLM.
|
||
6. Если запрос неоднозначен, отправить провайдеру минимальный текст и каталог разрешённых намерений для роли.
|
||
7. Принять от провайдера только структурированный результат по JSON Schema: `intentId`, извлечённые безопасные параметры, локаль и confidence.
|
||
8. Повторно проверить роль, workflow, параметры и site map на своей стороне.
|
||
9. Сформировать ответ из утверждённых локализованных шаблонов.
|
||
10. Вернуть действия клиенту и записать обезличенный outcome.
|
||
|
||
LLM используется как классификатор и языковой слой, но не как источник прав, маршрутов или произвольных действий. Низкая уверенность приводит к уточняющему вопросу без навигации. Если вопрос не относится к поддерживаемым возможностям, ассистент честно сообщает границы первого релиза.
|
||
|
||
### 13.2. Пороговые решения
|
||
|
||
Пороги задаются конфигурацией и калибруются на тестовом наборе:
|
||
|
||
- высокая уверенность: вернуть workflow;
|
||
- средняя уверенность: попросить выбрать из двух наиболее близких сценариев;
|
||
- низкая уверенность: ответить, что сценарий пока не поддерживается;
|
||
- конфликт роли и workflow: вернуть ролевой отказ независимо от confidence модели;
|
||
- конфликт версии: только текстовая инструкция без UI-действия.
|
||
|
||
Нельзя использовать ответ провайдера, если в нём неизвестный `intentId`, target, лишнее поле, неправильный тип или параметр вне allowlist.
|
||
|
||
### 13.3. Адаптеры провайдеров
|
||
|
||
Для LLM, STT и TTS определяются отдельные интерфейсы. Один поставщик может реализовать все три функции, но код не должен это предполагать. Адаптер обязан поддерживать:
|
||
|
||
- общий нормализованный DTO;
|
||
- таймауты подключения и ответа;
|
||
- повтор только безопасных идемпотентных запросов;
|
||
- circuit breaker;
|
||
- ограничение конкурентных запросов;
|
||
- унифицированные коды ошибок;
|
||
- учёт длительности, токенов/символов и стоимости без текста;
|
||
- fake-реализацию для локальной разработки и CI.
|
||
|
||
Автоматический failover между разными AI-провайдерами не включается до юридической и качественной проверки: он способен незаметно изменить место обработки данных и поведение модели.
|
||
|
||
## 14. Голосовой сценарий push-to-talk
|
||
|
||
### 14.1. Состояния интерфейса
|
||
|
||
```text
|
||
idle -> requesting_permission -> recording -> uploading -> transcribing
|
||
-> thinking -> answer_ready -> optional_speaking -> idle
|
||
```
|
||
|
||
Из любого промежуточного состояния возможен переход в `cancelled` или `error`, после которого пользователь может повторить попытку текстом.
|
||
|
||
### 14.2. Правила записи
|
||
|
||
- запись начинается только при `pointerdown` или доступном клавиатурном эквиваленте на кнопке;
|
||
- запись завершается при `pointerup`, `pointercancel`, потере capture, отпускании клавиши, скрытии вкладки или достижении лимита;
|
||
- рекомендуемый начальный лимит — 30 секунд и 10 МБ до серверной проверки;
|
||
- короткая запись ниже технического порога не отправляется;
|
||
- пользователь видит индикатор записи и длительность;
|
||
- повторное нажатие во время отправки не запускает параллельную запись;
|
||
- исходный аудиобуфер удаляется из памяти клиента после завершения или ошибки;
|
||
- сервер не пишет аудио во временный постоянный файл без необходимости; если конвертация требует файла, используется изолированный temp-каталог с гарантированным удалением;
|
||
- аудио никогда не попадает в логи, трассировки, аналитику или резервные копии.
|
||
|
||
На сервере проверяются реальный контейнер/кодек, длительность, размер и частота. При необходимости отдельный sandboxed-процесс приводит запись к поддерживаемому моноформату с жёсткими лимитами CPU, памяти и времени.
|
||
|
||
### 14.3. Распознавание и озвучивание
|
||
|
||
STT возвращает транскрипт только в память текущего запроса. После классификации он удаляется. Пользователь видит распознанный текст и может отменить запрос до навигации.
|
||
|
||
TTS по умолчанию озвучивает короткий ответ, только если пользователь включил голосовой ответ в текущей сессии. Для длинной пошаговой инструкции озвучивается резюме, полный текст остаётся на экране. Полученное аудио не кешируется межпользовательски, если лицензионные условия провайдера и модель угроз не разрешают безопасный кеш.
|
||
|
||
Если браузер, политика безопасности или провайдер не поддерживает голос, текстовый режим остаётся полностью работоспособным.
|
||
|
||
## 15. Локализация
|
||
|
||
- локаль по умолчанию берётся из пользовательского интерфейса Magistr, а не определяется по имени пользователя;
|
||
- пользователь может переключить `Русский / English` внутри виджета на время текущей browser-сессии;
|
||
- ответ следует языку последнего запроса, если язык определён уверенно;
|
||
- UI, workflow, ролевые отказы, ошибки, ARIA-метки и голосовые подсказки существуют на обоих языках;
|
||
- отсутствие перевода является ошибкой сборки контента;
|
||
- fallback — русский для русской инсталляции, но технический ключ никогда не показывается пользователю;
|
||
- названия реальных элементов Magistr берутся из утверждённых переводов site map, а не переводятся моделью на лету;
|
||
- тестовый корпус содержит русские, английские и смешанные формулировки, опечатки и варианты транслитерации названий групп.
|
||
|
||
## 16. Персонаж и UX виджета
|
||
|
||
До выбора образа используется нейтральный абстрактный placeholder без человекоподобной мимики. Архитектура UI должна позволять заменить набор изображений и токенов темы без изменения диалоговой логики.
|
||
|
||
Обязательные визуальные состояния:
|
||
|
||
- ожидание;
|
||
- пользователь удерживает кнопку и говорит;
|
||
- распознавание речи;
|
||
- подготовка ответа;
|
||
- показ следующего шага;
|
||
- успешное нахождение элемента;
|
||
- элемент или функция недоступны;
|
||
- техническая ошибка с переходом на текстовый режим.
|
||
|
||
Виджет располагается поверх интерфейса, но не перекрывает основные кнопки и системные уведомления. На небольшом экране он раскрывается в нижнюю панель. Диалог закрывается с клавиатуры; фокус не теряется; используется ARIA dialog/live region; контраст соответствует WCAG 2.2 AA; анимация отключается при `prefers-reduced-motion`.
|
||
|
||
Образ не должен обещать полномочия, которых нет у ассистента. Формулировки первого релиза: «покажу», «подскажу», «открою раздел», но не «изменю» и не «сохраню».
|
||
|
||
## 17. API-контракты
|
||
|
||
Все публичные эндпоинты versioned, принимают и возвращают JSON, кроме аудио и SSE. Ошибки имеют машинный `code`, локализованный `message` и `requestId`. Внешняя документация не раскрывает operator API.
|
||
|
||
### 17.1. Обмен токена в Magistr
|
||
|
||
```http
|
||
POST /api/assistant/session
|
||
Authorization: Bearer <основной access token Magistr>
|
||
Content-Type: application/json
|
||
|
||
{"locale":"ru","widgetVersion":"1.0.0","siteMapVersion":"1.0.0"}
|
||
```
|
||
|
||
Magistr проверяет обычную аутентификацию и роль, затем подписывает отдельный короткоживущий JWT. Пример ответа:
|
||
|
||
```json
|
||
{
|
||
"enabled": true,
|
||
"assistantBasePath": "/assistant/v1",
|
||
"token": "eyJ...",
|
||
"expiresAt": "2026-08-05T12:05:00Z"
|
||
}
|
||
```
|
||
|
||
Если подписка отключена, основной backend может вернуть `enabled: false` без токена. Это улучшает UX, но не заменяет обязательную серверную проверку подписки в новом сервисе.
|
||
|
||
### 17.2. Bootstrap
|
||
|
||
```http
|
||
GET /assistant/v1/bootstrap
|
||
Authorization: Bearer <assistant token>
|
||
```
|
||
|
||
Возвращает совместимые версии, доступные функции (`text`, `voiceInput`, `voiceOutput`, `feedback`), лимиты текущей сессии, локали и публичную конфигурацию персонажа. В ответе нет коммерческих сумм и секретной информации о tenant.
|
||
|
||
### 17.3. Текстовый диалог
|
||
|
||
```http
|
||
POST /assistant/v1/chat/stream
|
||
Authorization: Bearer <assistant token>
|
||
Content-Type: application/json
|
||
Accept: text/event-stream
|
||
|
||
{
|
||
"requestId": "019...",
|
||
"locale": "ru",
|
||
"message": "Как добавить выходной на этой неделе в четверг?",
|
||
"pageContext": {
|
||
"application": "admin",
|
||
"routeId": "schedule-view",
|
||
"siteMapVersion": "1.0.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
SSE-события ограничены типами `status`, `answer`, `workflow`, `actions`, `done`, `error`. UI-действия отправляются только после полной серверной валидации, одним завершённым событием. Если реальный streaming не поддерживается выбранным провайдером, контракт сохраняется, но сервер отправляет один готовый ответ.
|
||
|
||
Первая версия не принимает историю целиком. Виджет хранит ограниченный контекст текущей вкладки в памяти, а сервер получает только последние необходимые сообщения с жёстким лимитом и не сохраняет их. Для четырёх стартовых workflow предпочтительно обрабатывать каждый вопрос независимо.
|
||
|
||
### 17.4. Голос
|
||
|
||
```http
|
||
POST /assistant/v1/voice/transcriptions
|
||
Authorization: Bearer <assistant token>
|
||
Content-Type: multipart/form-data
|
||
```
|
||
|
||
Ответ содержит транскрипт, определённую локаль и request ID. Транскрипт возвращается пользователю, но не сохраняется сервисом.
|
||
|
||
```http
|
||
POST /assistant/v1/voice/speech
|
||
Authorization: Bearer <assistant token>
|
||
Content-Type: application/json
|
||
|
||
{"requestId":"019...","locale":"ru","textKey":"workflow.calendar.summary"}
|
||
```
|
||
|
||
Предпочтителен `textKey` с разрешёнными параметрами, а не произвольный длинный текст. Ответ — поток аудио с правильным `Content-Type`, `Cache-Control: no-store` и ограничением размера.
|
||
|
||
### 17.5. Оценка
|
||
|
||
```http
|
||
POST /assistant/v1/feedback
|
||
Authorization: Bearer <assistant token>
|
||
Content-Type: application/json
|
||
|
||
{"feedbackToken":"opaque...","rating":"positive","reasonCode":"found_target"}
|
||
```
|
||
|
||
`rating`: `positive` или `negative`. Свободный комментарий в первом релизе отсутствует, чтобы случайно не сохранять текст диалога или персональные данные. Допустимые причины выбираются из локализованного списка.
|
||
|
||
### 17.6. Operator API
|
||
|
||
Минимальный закрытый набор:
|
||
|
||
- `PUT /internal/v1/tenants/{tenantKey}/entitlement` — включить или выключить доступ, задать дату окончания и квоты;
|
||
- `GET /internal/v1/tenants/{tenantKey}/entitlement` — проверить состояние;
|
||
- `GET /internal/v1/tenants/{tenantKey}/usage` — получить агрегаты без текстов;
|
||
- `POST /internal/v1/tenants/{tenantKey}/rotate-feedback-key` — аварийная ротация;
|
||
- `POST /internal/v1/retention/run` — только для диагностического ручного запуска защищённой job.
|
||
|
||
На первом этапе вместо отдельного operator UI допустима CLI-команда, вызывающая тот же application service. Ручное изменение строк SQL считается аварийной операцией, а не штатным управлением.
|
||
|
||
### 17.7. Служебные эндпоинты
|
||
|
||
- `GET /health/live` — процесс работает, без проверки внешнего AI;
|
||
- `GET /health/ready` — доступна БД, загружены workflow и ключи проверки JWT;
|
||
- `GET /metrics` — только во внутренней сети;
|
||
- OpenAPI в production закрыта или доступна только оператору.
|
||
|
||
Недоступность внешнего AI-провайдера не должна постоянно выводить pod из readiness и провоцировать рестарт. Она отражается отдельной метрикой, circuit breaker и понятной ошибкой пользователю.
|
||
|
||
## 18. Аутентификация и границы доверия
|
||
|
||
Assistant JWT подписывается асимметричным ключом Magistr. Новый сервис получает только публичный ключ или JWKS.
|
||
|
||
Рекомендуемые claims:
|
||
|
||
```json
|
||
{
|
||
"iss": "magistr",
|
||
"aud": "magistr-assistant",
|
||
"sub": "opaque-session-subject",
|
||
"tenant": "university-key",
|
||
"role": "EDUCATION_OFFICE",
|
||
"departmentId": null,
|
||
"locale": "ru",
|
||
"jti": "019...",
|
||
"iat": 1785931200,
|
||
"exp": 1785931500
|
||
}
|
||
```
|
||
|
||
Правила:
|
||
|
||
- срок жизни — около пяти минут;
|
||
- токен не продлевается сервисом ассистента; новый получает только Magistr;
|
||
- `aud`, `iss`, подпись, `exp`, допустимая роль и tenant обязательны;
|
||
- email, ФИО, логин и основной access token не передаются;
|
||
- `departmentId` нужен только для ролевого варианта кафедры;
|
||
- публичные ключи ротируются с перекрывающим периодом по `kid`;
|
||
- токен хранится только в памяти виджета, не в `localStorage`;
|
||
- ответы API имеют `Cache-Control: no-store`;
|
||
- CORS не открывается глобально, основной путь same-origin;
|
||
- rate limit применяется по tenant, сессии и типу операции;
|
||
- operator-доступ использует отдельную аутентификацию, сетевое ограничение и аудит.
|
||
|
||
Новый сервис принимает решение о доступности подсказки, но не заменяет авторизацию Magistr. Даже будущие исполняемые действия должны повторно проверяться backend Magistr.
|
||
|
||
## 19. Модель данных отдельной БД
|
||
|
||
### 19.1. `tenant_entitlements`
|
||
|
||
| Поле | Назначение |
|
||
|---|---|
|
||
| `tenant_key` | Первичный ключ, нормализованный tenant |
|
||
| `status` | `enabled`, `suspended`, `expired` |
|
||
| `contract_reference` | Внутренняя ссылка на договор, без текста договора |
|
||
| `starts_at`, `ends_at` | Период доступа |
|
||
| `features` | Строго валидируемые флаги text/STT/TTS |
|
||
| `quota_policy` | Ссылка на версионированную политику квот |
|
||
| `created_at`, `updated_at` | Служебные даты |
|
||
|
||
Эта коммерческая конфигурация не является пользовательской аналитикой и хранится до окончания договорных и бухгалтерских обязательств. Она не удаляется 30-дневной job.
|
||
|
||
### 19.2. `usage_events`
|
||
|
||
Разрешённые поля:
|
||
|
||
- UUID события и время;
|
||
- tenant key;
|
||
- роль из ограниченного enum;
|
||
- тип канала: text/STT/TTS;
|
||
- `intentId` или `unsupported`, но не текст запроса;
|
||
- версия workflow, site map, widget и API;
|
||
- outcome: `success`, `clarification`, `denied`, `target_not_found`, `provider_error`;
|
||
- нормализованная латентность;
|
||
- количество входных/выходных токенов, символов или секунд аудио;
|
||
- код провайдера из внутреннего enum;
|
||
- технический request ID;
|
||
- `expires_at = created_at + 30 days`.
|
||
|
||
Запрещённые поля: prompt, транскрипт, ответ, аудио, имя группы, дата занятия, ФИО, email, логин, основной user ID, IP, User-Agent, DOM и URL с query-параметрами.
|
||
|
||
### 19.3. `feedback_events`
|
||
|
||
Содержит tenant, `intentId`, workflow version, `positive/negative`, выбранный reason code, channel, created/expiry timestamps и одноразовый псевдослучайный идентификатор ответа. Свободного комментария нет. Нельзя восстановить текст вопроса по feedback token.
|
||
|
||
### 19.4. `operator_audit_events`
|
||
|
||
Фиксирует включение/выключение подписки, изменение срока и квот: оператор, действие, tenant, время, old/new state без секретов. Для аудита управления срок хранения задаётся отдельно согласно договорным требованиям; 30-дневное удаление пользовательской аналитики на эту таблицу не распространяется. Это исключение должно быть явно отражено в политике конфиденциальности.
|
||
|
||
### 19.5. Агрегаты и квоты
|
||
|
||
Для быстрых лимитов допустима таблица дневных/месячных счётчиков без пользователя. Агрегаты после удаления событий можно хранить дольше только если они необратимо укрупнены минимум до tenant + месяц + тип операции и не содержат request ID, роли малой группы или редких `intentId`. До юридического решения безопасный вариант — удалять и события, и производные пользовательские агрегаты через 30 дней.
|
||
|
||
Векторная БД в MVP не нужна: поддерживаемых workflow мало, их версии лежат в Git, а детерминированный поиск проще проверять.
|
||
|
||
### 19.6. Retention job
|
||
|
||
- ежедневный Kubernetes CronJob или внутренний scheduled worker с distributed lock;
|
||
- удаление пакетами по индексированному `expires_at`, чтобы не блокировать таблицу;
|
||
- отдельные индексы на `expires_at`, `(tenant_key, created_at)` и поля агрегирования;
|
||
- метрики времени последнего успешного запуска, количества удалённых строк и oldest remaining event;
|
||
- alert, если старейшая запись превысила 31 день;
|
||
- интеграционный тест с искусственно устаревшими строками;
|
||
- резервные копии пользовательской аналитики имеют согласованный срок жизни и не превращают удаление из основной БД в фикцию;
|
||
- восстановление backup сопровождается немедленным повторным retention до открытия трафика.
|
||
|
||
## 20. Безопасность и приватность
|
||
|
||
### 20.1. Основные угрозы
|
||
|
||
| Угроза | Контроль |
|
||
|---|---|
|
||
| Межтенантный доступ | Tenant только из проверенного JWT; entitlement и квоты запрашиваются с ним; тесты на несовпадающие tenant |
|
||
| Повышение роли через prompt | Роль только из JWT; модель не влияет на policy engine |
|
||
| Prompt injection | Провайдер выбирает только intent из allowlist; DOM и произвольные инструкции сайта ему не отправляются |
|
||
| XSS в ответе | Строгая схема, `textContent`, запрет HTML/Markdown с raw HTML, CSP |
|
||
| Произвольное управление UI | Только semantic actions и target allowlist; нет `click`, selector и script |
|
||
| Кража токена | Короткий TTL, хранение в памяти, same-origin, `no-store`, строгая CSP |
|
||
| Обход отключённой подписки | Проверка entitlement на каждом billable запросе и при bootstrap |
|
||
| Перерасход API | Квоты, rate limits, maximum concurrency, circuit breaker и бюджеты |
|
||
| Аудиобомба/вредный контейнер | Проверка magic bytes, лимиты размера/длительности, sandboxed transcoding |
|
||
| Утечка через логи/трейсы | Централизованный allowlist полей и тест, запрещающий content keys |
|
||
| Устаревшая карта UI | Версионирование, contract test и безопасный текстовый fallback |
|
||
| Компрометация operator API | Отдельная identity, private network, least privilege, audit, ротация ключей |
|
||
| Сбой AI-провайдера | Таймаут, circuit breaker, текстовая ошибка, Magistr продолжает работать |
|
||
|
||
### 20.2. Минимизация данных
|
||
|
||
Перед подключением реального провайдера документируются регион обработки, срок хранения входных данных провайдером, использование запросов для обучения, порядок удаления и перечень субподрядчиков. В настройках провайдера выбирается режим без обучения и с минимальным техническим хранением, если он доступен.
|
||
|
||
Пользователю перед первой голосовой записью кратко сообщается, что звук отправляется внешнему сервису для распознавания и не сохраняется Magistr Assistant. Разрешение браузера запрашивается только после нажатия на кнопку микрофона.
|
||
|
||
Нельзя обещать полную анонимность текста, который пользователь сам ввёл: он может написать ФИО или другую персональную информацию. Поэтому интерфейс предупреждает не вводить персональные данные, сервис не хранит текст, а провайдер выбирается с подходящими договорными условиями.
|
||
|
||
### 20.3. Секреты и сеть
|
||
|
||
- ключи AI API, приватный ключ operator auth и DSN БД хранятся в Kubernetes Secret или внешнем secret store;
|
||
- секреты не попадают в image, репозиторий, frontend bootstrap и логи;
|
||
- egress разрешён только к БД, telemetry collector и выбранным provider endpoints;
|
||
- ingress к публичному API идёт только через reverse proxy; operator и metrics routes закрыты NetworkPolicy;
|
||
- TLS используется для внешних вызовов и соединения с БД в production;
|
||
- зависимости и container image регулярно сканируются;
|
||
- административная ротация секретов описывается в runbook и проверяется до пилота.
|
||
|
||
## 21. Подписка, квоты и управление оператором
|
||
|
||
### 21.1. Жизненный цикл подписки
|
||
|
||
1. Оператор получает подтверждение договора вне системы.
|
||
2. Проверяет точный `tenantKey` университета.
|
||
3. Через защищённую CLI или operator API задаёт период, функции и квоты.
|
||
4. Система показывает оператору итоговое состояние до применения.
|
||
5. После подтверждения создаётся audit event.
|
||
6. Widget становится доступен при следующем bootstrap без релиза Magistr.
|
||
7. При `suspended` или истечении срока новые запросы блокируются сервером; Magistr продолжает работать.
|
||
8. Возобновление подписки не восстанавливает удалённую аналитику.
|
||
|
||
Статусы должны иметь точные причины: договор завершён, ручная приостановка, квота исчерпана, конфигурация неполна. Конечному пользователю не показываются сведения о договоре; он получает нейтральное сообщение и предложение обратиться к ответственному лицу университета.
|
||
|
||
### 21.2. Квоты
|
||
|
||
Квоты конфигурируются отдельно для:
|
||
|
||
- текстовых запросов;
|
||
- секунд STT;
|
||
- символов или секунд TTS;
|
||
- одновременных запросов tenant;
|
||
- burst rate на одну browser-сессию.
|
||
|
||
Числовые значения нельзя фиксировать до получения тарифов и лимитов выбранных российских API. До пилота необходимо посчитать стоимость на тестовом корпусе и задать мягкий warning-порог и жёсткий hard limit. Проверка квоты и резервирование использования должны быть атомарными. При ошибке внешнего провайдера списывается только фактически тарифицированное использование, насколько это позволяет его API.
|
||
|
||
### 21.3. Operator CLI первого релиза
|
||
|
||
Предлагаемые команды:
|
||
|
||
```text
|
||
assistantctl tenant show <tenant-key>
|
||
assistantctl tenant enable <tenant-key> --until YYYY-MM-DD --policy pilot-v1
|
||
assistantctl tenant suspend <tenant-key> --reason contract-ended
|
||
assistantctl tenant set-features <tenant-key> --text on --stt on --tts on
|
||
assistantctl tenant usage <tenant-key> --period 30d
|
||
assistantctl retention status
|
||
```
|
||
|
||
CLI обязана иметь `--dry-run`, подтверждение потенциально блокирующих изменений и машиночитаемый JSON-режим. Секрет оператора не передаётся аргументом командной строки.
|
||
|
||
## 22. Наблюдаемость и эксплуатационные цели
|
||
|
||
### 22.1. Логи
|
||
|
||
Production-логи структурированы, а человекочитаемые сообщения написаны по-русски. Разрешённые поля: timestamp, level, service/version, environment, requestId, traceId, tenant key, роль-enum, endpoint, intent ID, outcome, latency bucket, provider code и error code.
|
||
|
||
Запрещённые поля централизованно фильтруются: `message`, `prompt`, `transcript`, `answer`, `audio`, Authorization, cookie, JWT, ФИО, email, IP, User-Agent и multipart body. Логирование request/response body в reverse proxy и APM выключается для `/assistant/*`.
|
||
|
||
### 22.2. Метрики
|
||
|
||
Минимальный набор:
|
||
|
||
- `assistant_requests_total{channel,outcome,role}`;
|
||
- `assistant_request_duration_seconds{channel,stage}`;
|
||
- `assistant_provider_requests_total{provider,capability,outcome}`;
|
||
- `assistant_provider_duration_seconds{provider,capability}`;
|
||
- `assistant_usage_units_total{tenant,capability}` с контролем cardinality;
|
||
- `assistant_workflow_matches_total{intent,outcome}`;
|
||
- `assistant_ui_actions_total{action,outcome}`;
|
||
- `assistant_target_not_found_total{site_map_version,target}`;
|
||
- `assistant_feedback_total{rating,reason,intent}`;
|
||
- `assistant_entitlement_denied_total{reason}`;
|
||
- `assistant_retention_last_success_timestamp`;
|
||
- `assistant_retention_oldest_event_age_seconds`;
|
||
- состояние circuit breaker и очереди запросов.
|
||
|
||
Для большого числа tenant допускается убирать tenant label из Prometheus и строить коммерческие отчёты запросом к БД, чтобы не создавать высокую cardinality.
|
||
|
||
### 22.3. Трассировки
|
||
|
||
Span-цепочка: reverse proxy → auth/entitlement → intent resolution → provider → response validation. Текстовые и аудиоданные никогда не записываются в span attributes или events. Трассировка интегрируется с существующим OpenTelemetry/SigNoz контуром, но сервис имеет собственное имя и dashboards.
|
||
|
||
### 22.4. Начальные SLO для пилота
|
||
|
||
| Показатель | Цель |
|
||
|---|---:|
|
||
| Доступность API подсказок | 99,5% за месяц, исключая согласованные работы |
|
||
| Bootstrap p95 внутри инфраструктуры | до 300 мс |
|
||
| Готовый текстовый ответ p95 | до 8 с |
|
||
| STT + ответ без TTS p95 для записи до 15 с | до 15 с |
|
||
| Выполнение локальной навигации и подсветки p95 после ответа | до 1 с после готовности view |
|
||
| Успешное нахождение target на поддерживаемой версии UI | не менее 98% |
|
||
| Пользовательская аналитика старше 30 дней | 0 строк, допустимое окно job до 24 часов |
|
||
| Влияние аварии ассистента на Magistr | 0 отказов основных функций |
|
||
|
||
Это стартовые ориентиры, а не договорной SLA. После пилота они уточняются по фактическим latency российских провайдеров и инфраструктуры.
|
||
|
||
### 22.5. Оповещения и runbook
|
||
|
||
Alerts:
|
||
|
||
- резкий рост 5xx или provider errors;
|
||
- p95 выше цели в течение согласованного окна;
|
||
- circuit breaker открыт длительное время;
|
||
- retention не выполнялся более 25 часов;
|
||
- есть запись старше 31 дня;
|
||
- истекает ключ JWT/JWKS или provider secret;
|
||
- квота tenant близка к hard limit;
|
||
- несовместимость site map/widget;
|
||
- массовый `target_not_found` после релиза Magistr.
|
||
|
||
Runbook должен описывать диагностику, безопасное отключение только ассистента, переключение на текстовый режим, ротацию секретов, повтор retention, откат image и widget, а также правила обращения с потенциальной утечкой.
|
||
|
||
## 23. Развёртывание и CI/CD отдельного проекта
|
||
|
||
### 23.1. Контейнер
|
||
|
||
- multi-stage build;
|
||
- непривилегированный пользователь и read-only root filesystem;
|
||
- минимальный базовый образ с фиксированным digest;
|
||
- `no-new-privileges`, drop Linux capabilities;
|
||
- отдельный writable temp volume только если нужен аудиоконвертер;
|
||
- health endpoints без секретов;
|
||
- graceful shutdown длиннее максимального обычного запроса;
|
||
- SBOM и vulnerability scan;
|
||
- OCI labels с git SHA и версией;
|
||
- image публикуется по immutable digest.
|
||
|
||
### 23.2. Kubernetes
|
||
|
||
Production-набор:
|
||
|
||
- Deployment API минимум с двумя репликами для пилотной production-среды;
|
||
- Service;
|
||
- Ingress/reverse-proxy route `/assistant/*` на доменах Magistr;
|
||
- одноразовый migration Job перед rollout;
|
||
- CronJob retention с distributed lock;
|
||
- ConfigMap только для несекретных параметров;
|
||
- Secret/external secret;
|
||
- NetworkPolicy ingress/egress;
|
||
- PodDisruptionBudget;
|
||
- resource requests/limits;
|
||
- topology spread или anti-affinity при наличии нескольких узлов;
|
||
- HPA только после появления достоверных метрик CPU/concurrency;
|
||
- ServiceMonitor/OTel configuration;
|
||
- отдельный service account без доступа к Kubernetes API, если он не нужен.
|
||
|
||
Внешний инфраструктурный репозиторий остаётся source of truth для production-манифестов. Изменение текущего `../k8s` выполняется отдельной задачей и не является частью создания этого документа.
|
||
|
||
### 23.3. Reverse proxy и CSP
|
||
|
||
Рекомендуемый маршрут same-origin:
|
||
|
||
```text
|
||
https://<tenant-domain>/assistant/v1/* -> magistr-assistant-service
|
||
```
|
||
|
||
Путь обмена токена остаётся в Magistr: `/api/assistant/session`. Виджет и CSS отдаются Magistr как локальные versioned assets. Для микрофона политика меняется с `microphone=()` на `microphone=(self)`. Новые внешние домены в browser CSP не добавляются, потому что виджет обращается только к same-origin proxy.
|
||
|
||
### 23.4. Gitea Actions
|
||
|
||
Pipeline для pull request:
|
||
|
||
1. Проверка format/lint Python и TypeScript.
|
||
2. Строгая проверка типов.
|
||
3. Unit-тесты.
|
||
4. Валидация JSON Schema и всех YAML workflow.
|
||
5. Проверка полноты русских/английских переводов.
|
||
6. Contract-тест с зафиксированной версией Magistr bridge.
|
||
7. Интеграционные тесты PostgreSQL и fake providers.
|
||
8. Playwright для виджета.
|
||
9. Security tests, dependency audit, secret scan.
|
||
10. Сборка API image и widget без публикации.
|
||
|
||
Pipeline `main` дополнительно публикует артефакты, SBOM и immutable digest. Production deploy запускается только после успешных проверок, миграции и принятой стратегии окружений. `workflow_dispatch` остаётся дополнительным способом повторить безопасный deploy, а не единственным способом доставки.
|
||
|
||
### 23.5. Локальная разработка
|
||
|
||
`compose.yaml` нового репозитория поднимает API, PostgreSQL, fake LLM/STT/TTS и при необходимости OTel collector. Magistr подключается через локальный proxy или dev override. По умолчанию никакой запрос не уходит во внешний AI API; реальный provider включается явной переменной и отдельным профилем.
|
||
|
||
## 24. Стратегия тестирования
|
||
|
||
### 24.1. Unit-тесты
|
||
|
||
- role policy для каждого workflow;
|
||
- confidence thresholds;
|
||
- валидация и отклонение неизвестных actions/targets;
|
||
- вычисление квот;
|
||
- entitlement status и даты;
|
||
- локализация и fallback;
|
||
- очистка лог-полей;
|
||
- TTL pending action;
|
||
- state machine push-to-talk;
|
||
- provider timeout/retry/circuit breaker;
|
||
- формирование `expires_at`.
|
||
|
||
### 24.2. Contract-тесты
|
||
|
||
- OpenAPI ↔ generated TypeScript types;
|
||
- workflow ↔ workflow schema;
|
||
- target из workflow обязательно существует в site map;
|
||
- role workflow не шире role target/route;
|
||
- Magistr bridge поддерживает все action types;
|
||
- widget/API/site map version matrix;
|
||
- все опубликованные строки имеют RU и EN;
|
||
- удалённый target требует major change или deprecation window.
|
||
|
||
Contract bundle полезно публиковать отдельным package, который подключают оба репозитория. CI Magistr должен падать, если новый UI удалил требуемую цель без согласованного обновления контракта.
|
||
|
||
### 24.3. Интеграционные тесты API
|
||
|
||
- валидный/истёкший/неверно подписанный JWT;
|
||
- неверные `iss`, `aud`, tenant и роль;
|
||
- включённая, выключенная и истёкшая подписка;
|
||
- атомарное исчерпание квоты конкурентными запросами;
|
||
- отсутствие текста и аудио в БД, логах и traces;
|
||
- ошибки/таймауты/невалидный JSON каждого provider;
|
||
- очистка временного аудио после успеха, отмены и исключения;
|
||
- retention на границе 30 дней;
|
||
- восстановление из backup с повторной очисткой;
|
||
- недоступность provider не ломает health/live и Magistr.
|
||
|
||
### 24.4. Browser E2E
|
||
|
||
Для каждой из трёх ролей и двух локалей проверяются:
|
||
|
||
- загрузка/скрытие виджета по entitlement;
|
||
- клавиатурное открытие и закрытие;
|
||
- переход в SPA и между документами;
|
||
- ожидание асинхронной готовности представления;
|
||
- подсветка, прокрутка, снятие по Escape и таймауту;
|
||
- безопасный fallback, когда target отсутствует;
|
||
- отсутствие исполнения HTML в вредоносном ответе;
|
||
- запрет action, недоступного роли;
|
||
- сохранение основного функционала Magistr при 5xx/timeout API;
|
||
- permission denied для микрофона;
|
||
- отпускание кнопки за пределами элемента и скрытие вкладки;
|
||
- `prefers-reduced-motion`, масштаб 200%, мобильная ширина и screen reader labels.
|
||
|
||
### 24.5. Приёмочные сценарии MVP
|
||
|
||
| Фраза | Роль | Ожидаемый результат |
|
||
|---|---|---|
|
||
| «Как добавить выходной на этой неделе в четверг?» | ADMIN/EDUCATION_OFFICE | Открыта сетка календаря, подсвечен выбор графика, показаны дальнейшие шаги |
|
||
| Та же фраза | DEPARTMENT | Понятный ролевой отказ, запрещённый экран не открыт |
|
||
| «Где менять время пар в субботу?» | ADMIN/EDUCATION_OFFICE | Открыты временные слоты, подсвечена область действия |
|
||
| Та же фраза | DEPARTMENT | Указана ответственная роль |
|
||
| «Какое расписание у группы ИБ-01б?» | ADMIN/EDUCATION_OFFICE | Открыт просмотр расписания, подсвечено поле группы |
|
||
| Та же фраза | DEPARTMENT | Открыт собственный режим кафедры, объяснено ограничение |
|
||
| «Как перенести пару с одной даты на другую?» | ADMIN/EDUCATION_OFFICE | Открыт просмотр, объяснён выбор занятия и override |
|
||
| Та же фраза | DEPARTMENT | Указана ответственная роль, редактирование не предлагается |
|
||
|
||
Для каждой строки создаются английские варианты, голосовая версия, вариант с опечатками и негативный тест, в котором модель пытается вернуть запрещённое действие.
|
||
|
||
### 24.6. Нагрузочные и отказные тесты
|
||
|
||
- bootstrap storm после начала рабочего дня;
|
||
- параллельные короткие text-запросы;
|
||
- максимальные разрешённые аудиозаписи;
|
||
- медленный provider и разрыв SSE;
|
||
- временная недоступность БД;
|
||
- истощение connection pool;
|
||
- остановка pod во время запроса;
|
||
- одновременный retention и чтение агрегатов;
|
||
- поведение при исчерпании tenant quota;
|
||
- доказательство, что деградация ассистента не увеличивает latency основных API Magistr.
|
||
|
||
## 25. Поэтапный план реализации
|
||
|
||
Оценки ниже даны в человеко-днях для одного разработчика, включают код и основные тесты, но не ожидание договора с AI-провайдером, юридическое согласование, закупку и создание финальной графики. Диапазон нужен для планирования, а не является обещанием срока.
|
||
|
||
### Этап 0. Проработка и ADR — 3–5 дней
|
||
|
||
Задачи:
|
||
|
||
- создать отдельный репозиторий и его `AGENTS.md`;
|
||
- утвердить рабочее имя и владельца сервиса;
|
||
- сравнить российские LLM/STT/TTS API по SLA, регионам, retention, цене и качеству русского/английского;
|
||
- утвердить data flow и договорные ограничения;
|
||
- зафиксировать ADR по стеку, same-origin proxy, token exchange, workflow engine и отсутствию server-side history;
|
||
- снять базовые замеры четырёх сценариев вручную;
|
||
- выбрать начальные SLO и бюджет пилота.
|
||
|
||
Критерий выхода: выбран provider или согласован fake-first режим, утверждены границы данных и архитектурные решения.
|
||
|
||
### Этап 1. Контракты интерфейса Magistr — 5–8 дней
|
||
|
||
Задачи:
|
||
|
||
- оформить JSON Schema actions/site map/workflow;
|
||
- внедрить в Magistr host bridge и `data-assistant-target` только для четырёх сценариев;
|
||
- добавить событие готовности асинхронных views;
|
||
- реализовать безопасную навигацию и pending action между SPA;
|
||
- добавить CSS подсветки и accessibility;
|
||
- создать fake widget для проверки без AI;
|
||
- добавить contract и browser tests для ролей.
|
||
|
||
Критерий выхода: статический тестовый ответ безопасно открывает и подсвечивает каждый разрешённый target, а запрещённые actions отклоняются.
|
||
|
||
### Этап 2. Основа сервиса, auth и entitlement — 8–12 дней
|
||
|
||
Задачи:
|
||
|
||
- создать FastAPI skeleton, конфигурацию и русские логи;
|
||
- PostgreSQL/Alembic schema;
|
||
- assistant token endpoint в Magistr и JWKS/rotation contract;
|
||
- проверка JWT и tenant entitlement;
|
||
- operator CLI с audit;
|
||
- квоты и rate limits;
|
||
- bootstrap, health, metrics;
|
||
- retention job и тесты;
|
||
- fake provider.
|
||
|
||
Критерий выхода: включённый tenant получает bootstrap, выключенный блокируется сервером, межтенантные тесты и retention проходят.
|
||
|
||
### Этап 3. Виджет и текстовый UX — 10–15 дней
|
||
|
||
Задачи:
|
||
|
||
- Web Component, адаптивная панель и placeholder персонажа;
|
||
- текстовый ввод, ответы, шаги и оценки;
|
||
- browser-only conversation state;
|
||
- SSE client, timeout/cancel/retry;
|
||
- bridge actions и безопасный fallback;
|
||
- RU/EN UI;
|
||
- CSP integration;
|
||
- unit и Playwright tests.
|
||
|
||
Критерий выхода: все четыре workflow полностью проходят с fake provider текстом на двух языках.
|
||
|
||
### Этап 4. AI-классификация и контент — 8–12 дней
|
||
|
||
Задачи:
|
||
|
||
- детерминированный resolver;
|
||
- адаптер выбранного LLM;
|
||
- structured output и строгая повторная валидация;
|
||
- confidence/clarification policy;
|
||
- все четыре versioned workflow;
|
||
- тестовый корпус фраз и regression evaluation;
|
||
- бюджеты, timeout и circuit breaker;
|
||
- защита логов и аналитика outcome.
|
||
|
||
Критерий выхода: целевые метрики качества достигнуты на зафиксированном тестовом корпусе, запрещённые действия не проходят validator.
|
||
|
||
### Этап 5. Голос и завершение локализации — 8–12 дней
|
||
|
||
Задачи:
|
||
|
||
- hold-to-talk state machine;
|
||
- браузерные разрешения и CSP/Permissions-Policy;
|
||
- STT adapter, проверка и ограничение аудио;
|
||
- просмотр транскрипта и отмена;
|
||
- TTS adapter и управление воспроизведением;
|
||
- двуязычные голоса и fallback;
|
||
- accessibility и мобильные сценарии;
|
||
- тесты удаления буферов и временных файлов.
|
||
|
||
Критерий выхода: голосовой вопрос можно безопасно задать удержанием кнопки, а отказ любого voice-компонента оставляет рабочий текстовый режим.
|
||
|
||
### Этап 6. Production hardening — 8–12 дней
|
||
|
||
Задачи:
|
||
|
||
- threat-model review;
|
||
- dependency/container scan и SBOM;
|
||
- Kubernetes policies/resources/migration/retention jobs;
|
||
- dashboards, alerts и runbooks;
|
||
- нагрузочные и failure tests;
|
||
- backup/restore/retention rehearsal;
|
||
- секреты и ротация;
|
||
- проверка отсутствия content в logs/traces/DB;
|
||
- rollback rehearsal API и widget.
|
||
|
||
Критерий выхода: закрыты критические угрозы, проверены аварийные процедуры, сервис можно отключить без влияния на Magistr.
|
||
|
||
### Этап 7. Пилот одного университета — 5–10 дней активной работы
|
||
|
||
Задачи:
|
||
|
||
- включить подписку одному tenant;
|
||
- провести короткое обучение ответственных пользователей;
|
||
- следить за latency, target failures, квотами и оценками;
|
||
- вручную разобрать негативные оценки только по обезличенным кодам и воспроизвести сценарии на тестовом стенде;
|
||
- исправить контент/target contracts;
|
||
- получить решение о расширении.
|
||
|
||
Критерий выхода: все четыре сценария устойчивы, retention подтверждён, нет влияния на основные функции, согласованы следующие workflow.
|
||
|
||
### Итоговая ориентировочная трудоёмкость
|
||
|
||
Полный путь до завершённого пилота: примерно **55–86 человеко-дней**. При одном разработчике это обычно 3–5 календарных месяцев с учётом согласований и исправлений; при параллельной работе backend/frontend/infra календарный срок можно уменьшить, но контракты этапа 1 и privacy-решения этапа 0 остаются последовательными зависимостями.
|
||
|
||
## 26. Декомпозиция начального бэклога
|
||
|
||
### Контракты и Magistr
|
||
|
||
- `ASST-001` — ADR архитектуры и границ данных.
|
||
- `ASST-002` — schema semantic UI actions.
|
||
- `ASST-003` — schema site map и workflow.
|
||
- `ASST-004` — inventory target для четырёх сценариев.
|
||
- `ASST-005` — stable `data-assistant-target` в основной admin SPA.
|
||
- `ASST-006` — targets в settings SPA.
|
||
- `ASST-007` — host bridge role/route allowlist.
|
||
- `ASST-008` — `magistr:view-mounted` и ожидание view.
|
||
- `ASST-009` — pending action между документами.
|
||
- `ASST-010` — CSS highlight и accessibility.
|
||
- `ASST-011` — assistant session endpoint и асимметричные ключи.
|
||
- `ASST-012` — CSP и `Permissions-Policy` для same-origin voice.
|
||
|
||
### Service core
|
||
|
||
- `ASST-013` — FastAPI skeleton, settings и error model.
|
||
- `ASST-014` — PostgreSQL/Alembic и repository layer.
|
||
- `ASST-015` — JWT/JWKS validator.
|
||
- `ASST-016` — tenant entitlement application service.
|
||
- `ASST-017` — quota/rate-limit service.
|
||
- `ASST-018` — operator CLI и audit.
|
||
- `ASST-019` — bootstrap API.
|
||
- `ASST-020` — feedback tokens и endpoint.
|
||
- `ASST-021` — usage events без content.
|
||
- `ASST-022` — 30-day retention job.
|
||
- `ASST-023` — health, metrics, traces и log allowlist.
|
||
|
||
### Workflow и AI
|
||
|
||
- `ASST-024` — provider abstractions и fake provider.
|
||
- `ASST-025` — deterministic intent resolver.
|
||
- `ASST-026` — LLM adapter и structured output.
|
||
- `ASST-027` — response/action validator.
|
||
- `ASST-028` — confidence/clarification policy.
|
||
- `ASST-029` — workflow календарного выходного.
|
||
- `ASST-030` — workflow субботних временных слотов.
|
||
- `ASST-031` — workflow просмотра группы.
|
||
- `ASST-032` — workflow переноса занятия.
|
||
- `ASST-033` — RU/EN regression corpus.
|
||
|
||
### Widget и voice
|
||
|
||
- `ASST-034` — Web Component shell и placeholder.
|
||
- `ASST-035` — text chat и browser state.
|
||
- `ASST-036` — SSE client/cancel/retry.
|
||
- `ASST-037` — workflow step renderer.
|
||
- `ASST-038` — bridge client и action executor.
|
||
- `ASST-039` — feedback UI.
|
||
- `ASST-040` — i18n completeness gate.
|
||
- `ASST-041` — hold-to-talk state machine.
|
||
- `ASST-042` — STT upload/validation/adapter.
|
||
- `ASST-043` — TTS playback/adapter.
|
||
- `ASST-044` — voice privacy notice и permissions UX.
|
||
|
||
### Delivery и эксплуатация
|
||
|
||
- `ASST-045` — Gitea checks pipeline.
|
||
- `ASST-046` — immutable API image и SBOM.
|
||
- `ASST-047` — versioned widget publication.
|
||
- `ASST-048` — Kubernetes templates/infrastructure integration.
|
||
- `ASST-049` — dashboards и alerts.
|
||
- `ASST-050` — privacy/security/runbook docs.
|
||
- `ASST-051` — load/failure/security test suite.
|
||
- `ASST-052` — pilot checklist и rollback rehearsal.
|
||
|
||
## 27. Definition of Done первого релиза
|
||
|
||
Релиз считается готовым, только если одновременно выполнено следующее:
|
||
|
||
- отдельный репозиторий, pipeline и независимые версии API/widget существуют;
|
||
- один tenant можно включить и выключить без нового релиза;
|
||
- сервер проверяет tenant, роль, entitlement и квоту на каждом запросе;
|
||
- роли `ADMIN`, `EDUCATION_OFFICE`, `DEPARTMENT` проходят согласованную матрицу;
|
||
- четыре стартовых вопроса работают текстом и голосом на русском и английском;
|
||
- ассистент выполняет только навигацию и подсветку;
|
||
- ни модель, ни клиент не могут вернуть/выполнить неизвестный selector, URL или action;
|
||
- ответ при отсутствии прав понятен и не открывает запрещённый раздел;
|
||
- полный текст, транскрипт и аудио отсутствуют в БД, логах, traces и backups;
|
||
- оценка ответа хранится без свободного текста и удаляется с аналитикой через 30 дней;
|
||
- retention проверен автоматическим тестом и наблюдаемой production job;
|
||
- voice permission запрашивается только после осознанного действия;
|
||
- сбой API/LLM/STT/TTS не мешает пользоваться Magistr;
|
||
- CSP, XSS, межтенантные и quota-тесты проходят;
|
||
- browser E2E подтверждает targets на текущей версии Magistr;
|
||
- есть dashboard, alerts, runbook, rollback и операторский аудит;
|
||
- документация API, контента, privacy, deployment и эксплуатации актуальна;
|
||
- пилотный университет и оператор приняли пользовательские сценарии.
|
||
|
||
## 28. Основные риски и способы снижения
|
||
|
||
| Риск | Вероятность/влияние | Снижение |
|
||
|---|---|---|
|
||
| UI Magistr меняется и подсветка ломается | Высокая/среднее | Stable target contract, CI двух репозиториев, version fallback |
|
||
| Модель выбирает неверный сценарий | Средняя/высокое | Детерминированный слой, ограниченный каталог, confidence и clarification |
|
||
| Российский API имеет высокую задержку | Средняя/среднее | Замеры этапа 0, короткие prompts, timeout, UX states, provider abstraction |
|
||
| Стоимость голоса выше ожиданий | Средняя/высокое | Hold-to-talk, лимит длины, квоты, TTS opt-in, замер unit economics |
|
||
| Пользователь произносит персональные данные | Высокая/среднее | Не хранить content, предупреждение, договорные настройки provider, минимальная передача |
|
||
| Отсутствуют права на нужный экран | Высокая/низкое | Ролевой workflow как штатный результат, без обходов |
|
||
| Межтенантная утечка | Низкая/критическое | Подписанный tenant claim, central policy, isolation tests, no business API access |
|
||
| Operator случайно отключает не тот tenant | Средняя/высокое | Exact tenant preview, dry-run, confirmation и audit |
|
||
| Два независимых релиза несовместимы | Средняя/высокое | Semver, compatibility window, contract package и безопасный fallback |
|
||
| Retention не охватывает backup | Средняя/высокое | Короткий backup retention, restore procedure с cleanup, регулярная проверка |
|
||
| Виджет ухудшает доступность интерфейса | Средняя/среднее | Keyboard/screen-reader/mobile tests, reduced motion, неперекрывающая компоновка |
|
||
| Fork большого avatar-проекта усложняет поддержку | Высокая/среднее | Малый Web Component, заимствовать идеи, не кодовую базу AIRI |
|
||
|
||
## 29. Подготовка к будущему выполнению действий
|
||
|
||
Возможность «сделать после подтверждения» не следует реализовывать расширением текущего `highlight` до `click`. Для неё нужен отдельный command-контур:
|
||
|
||
```text
|
||
пользовательский запрос
|
||
-> draft command
|
||
-> серверная проверка прав и актуального состояния
|
||
-> человекочитаемый preview «что изменится»
|
||
-> явное подтверждение пользователя
|
||
-> короткоживущий confirmation token
|
||
-> идемпотентная команда Magistr API
|
||
-> серверный audit/result
|
||
```
|
||
|
||
Обязательные будущие свойства:
|
||
|
||
- отдельные backend endpoints Magistr, не DOM-автоматизация;
|
||
- allowlist команд, строгие DTO и повторная обычная RBAC-проверка;
|
||
- preview без побочных эффектов;
|
||
- подтверждение привязано к exact command hash, tenant, пользователю и короткому TTL;
|
||
- изменение параметра аннулирует старое подтверждение;
|
||
- idempotency key и защита от повторной отправки;
|
||
- optimistic locking/version для изменяемого объекта;
|
||
- явный показ «было / станет»;
|
||
- усиленное подтверждение для массовых и необратимых действий;
|
||
- полный служебный audit по требованиям безопасности, отдельно от диалогового текста;
|
||
- компенсация или понятная ручная процедура, если команда выполнена частично;
|
||
- отдельный threat model и юридическое согласование.
|
||
|
||
До появления этого контура UI action schema первого релиза не содержит `click`, `fill` и `submit` даже в скрытом виде.
|
||
|
||
## 30. Открытые решения до начала разработки
|
||
|
||
Эти вопросы не блокируют создание плана, но должны быть закрыты на этапе 0:
|
||
|
||
1. Какой российский provider выбран отдельно для LLM, STT и TTS; можно ли юридически использовать одного поставщика для всех трёх функций?
|
||
2. Какие у него фактические retention, region, SLA, rate limits, streaming и режим запрета обучения?
|
||
3. Какие квоты и себестоимость допустимы для пилотного договора?
|
||
4. Нужен ли голосовой ответ по умолчанию или только после отдельного включения пользователем?
|
||
5. Какой tenant станет пилотным и сколько одновременно активных пользователей ожидается?
|
||
6. Каким способом operator CLI аутентифицируется в production: mTLS, OIDC service identity или иной внутренний механизм?
|
||
7. Где будет source of truth для production manifest нового сервиса и как он связан с текущим внешним `k8s`?
|
||
8. Каков точный срок хранения operator audit и договорных записей?
|
||
9. Нужно ли хранить укрупнённые месячные коммерческие агрегаты дольше 30 дней, и можно ли доказать их необратимую обезличенность?
|
||
10. Какой окончательный образ, имя, голос, цветовая тема и допустимый уровень анимации у единого персонажа?
|
||
11. Нужен ли пользователю переключатель полного выключения озвучивания и удаления browser-session контекста одной кнопкой?
|
||
12. Кто принимает и обновляет двуязычный workflow-контент после пилота?
|
||
|
||
## 31. Рекомендуемый порядок следующих действий
|
||
|
||
1. Согласовать этот документ как продуктовую и архитектурную основу.
|
||
2. Выбрать пилотный tenant и получить обезличенное число потенциальных пользователей.
|
||
3. Провести короткий provider spike на четырёх фразах и их вариантах, не передавая production-данные.
|
||
4. Зафиксировать ADR и JSON Schema контрактов.
|
||
5. Создать отдельный репозиторий `magistr-assistant`.
|
||
6. Первым техническим инкрементом реализовать fake assistant + Magistr bridge без внешнего AI.
|
||
7. Только после стабильной навигации добавить token exchange, entitlement и API.
|
||
8. Затем подключить LLM; после текстовой приёмки — STT/TTS.
|
||
9. Выполнить security/privacy review и production hardening.
|
||
10. Включить один tenant, собрать 30 дней обезличенных исходов и решить, какие workflow добавлять дальше.
|
||
|
||
## 32. Источники и точки сверки
|
||
|
||
### Исходники Magistr
|
||
|
||
- `frontend/admin/js/main.js` — маршрутизация и асинхронная загрузка views;
|
||
- `frontend/admin/js/role-capabilities.js` — доступные вкладки по ролям;
|
||
- `frontend/admin/settings/js/main.js` — отдельный settings SPA;
|
||
- `frontend/admin/js/views/academic-calendar.js` — сетка календаря и выбор активности дня;
|
||
- `frontend/admin/settings/js/views/time-slots.js` — временные слоты и области действия;
|
||
- `frontend/admin/js/views/schedule-view.js` — режим группы, кафедры и override;
|
||
- `backend/src/main/java/com/magistr/app/controller/AcademicCalendarController.java` — роли календаря;
|
||
- `backend/src/main/java/com/magistr/app/controller/ScheduleOverrideController.java` — роли точечных изменений;
|
||
- `frontend/security.conf` — текущие CSP и Permissions-Policy;
|
||
- `docs/ARCHITECTURE.md`, `docs/FRONTEND.md`, `docs/INFRASTRUCTURE.md` — действующие проектные соглашения.
|
||
|
||
### Внешняя техническая документация
|
||
|
||
- [FastAPI: общие принципы deployment](https://fastapi.tiangolo.com/deployment/concepts/) — репликация, перезапуски, память и предварительные шаги;
|
||
- [FastAPI: server workers](https://fastapi.tiangolo.com/deployment/server-workers/) — модель worker и контейнерные варианты;
|
||
- [Статус версий Python](https://devguide.python.org/versions/) — сроки bugfix/security-поддержки 3.13 и 3.14;
|
||
- [PostgreSQL: политика версий](https://www.postgresql.org/support/versioning/) — поддерживаемые major/minor и сроки окончания поддержки;
|
||
- [SQLAlchemy 2.0 Documentation](https://docs.sqlalchemy.org/en/20/) — выбранная стабильная линия ORM.
|
||
|
||
Перед непосредственной реализацией версии языка, библиотек, PostgreSQL, browser APIs и требования конкретного AI-провайдера необходимо повторно проверить по актуальной официальной документации и зафиксировать lock-файлами.
|