Как устроено
Четыре понятия, через которые описывается всё остальное: пак, вариант данных, проверка и закрытая часть пака.
Пак — контракт между автором и студентом
Пак это git-каталог. Студия автора его пишет, раннер студента читает, CI преподавателя читает то же самое.
packs/<практика>/
domain.json описание базы, темы курса, DDL
tasks.json задания
materializations/<id>.json эталон, спека данных, варианты, проверки, негативы
migrations/
common/ создание схемы
tasks/<id>/ INSERT-ы вариантов данных
report/index.md отчёт приёмки этапа 3
theme/ оформление курса
state.json что утверждено, хэши
Утверждения держатся на хэшах, а не на честном слове. domain.json при утверждении получает content_hash, задания запоминают, к какой версии области они сделаны, каждая материализация помнит source_hash своего задания. Поменяли формулировку задания — материализация помечается устаревшей, и студия скажет об этом до того, как пак уедет студентам.
Косметические правки сделаны исключением: у области это документация таблиц, у задания — «человеческая» формулировка business_statement. Их можно править, не переделывая проверки.
Вариант данных
Вариант — это конкретный набор строк, полученный из спеки данных и seed'а. Спека объявляет:
- классовые колонки — то, что делит строки на смысловые группы: активна кофейня или закрыта, есть у партии регион или
NULL; - квоты — сколько строк какого класса должно быть в этом варианте;
- крайние случаи — то, что обязано существовать при любом seed'е: клиент без заказов, нулевая сумма, единственная строка.
Дальше движок строит варианты сам: сначала систематические крайности (каждый класс пуст, каждый класс единственный), потом случайные комбинации квот. Типичное задание получает около пятидесяти вариантов.
Три варианта — именованные примеры, они открыты студенту: «нет активных», «только активные», «смесь». Вместе они покрывают ветки задания, и по ним студент понимает, что от него хотят.
Проверка
Проверка решения студента — это прогон его SQL по всем вариантам в отдельной чистой базе. Живая база, где студент экспериментирует, при этом не участвует: её состояние не влияет на результат, а её данные не портятся.
Типы проверок:
| Тип | Что сравнивает |
|---|---|
schema | набор и порядок колонок результата |
cardinality | сколько строк ожидается: одна, много, допустимо ноль |
result_exact | результат построчно (открытые примеры) |
result_hash | хэш нормализованного результата (скрытые варианты) |
table_state | состояние таблиц после INSERT/UPDATE/DELETE |
db_schema | структура схемы после CREATE/ALTER |
sql_constraints | обязательные и запрещённые конструкции в тексте решения |
sql_constraints разбирает решение как SQL, а не ищет подстроки: если задание требует DISTINCT, а решение получает уникальность через GROUP BY, проверка это увидит.
Отдельно от проверок стоят негативные решения — заведомо неверные запросы, которые автор кладёт в пак. Они нужны, чтобы проверить сами проверки: если негатив проходит, набор проверок слишком слабый, и студия не даст утвердить такое задание.
Что скрыто от студента
Инвариант, который система держит специально: ожидаемые результаты скрытых вариантов не покидают бэкенд.
- Открытые примеры отдаются целиком — они для этого и сделаны.
- Скрытый вариант отдаётся студенту только после того, как его решение на этом варианте упало: тогда видны данные варианта, ожидаемый результат и построчное сравнение.
- Всё остальное лежит в закрытой части пака и наружу не уходит.
Пак приезжает студенту на машину целиком, и ключ шифрования закрытой части лежит в образе. Это защита от подглядывания, а не от реверса: студент, который целенаправленно вскроет пак, справится. Система рассчитана на то, что списывать дороже, чем решить.
Кто что запускает
gen studio
Студия на localhost:8765. Нужен Docker: под каждый прогон поднимается эфемерный Postgres.
docker compose up
Раннер на localhost:8766 плюс Postgres с живой и проверочной базами.
runner ci
Тот же образ в CI: читает solutions/ студента и выдаёт отчёт по заданиям.