4.7 KiB
08 — Требования к схеме БД: ORCH-088 (Serial gate, freeze-хранилище)
Work Item: ORCH-088 · Repo: orchestrator · Стадия: architecture
Связь: ADR 06-adr/ADR-001-serial-gate.md (D2/D3/D4), ТЗ 02-trz.md §5.
Общая прод-БД (self-hosting обслуживает enduro-trails из того же инстанса). Все миграции — только аддитивные и идемпотентные (
CREATE TABLE IF NOT EXISTS/_ensure_column). Изменение существующих таблиц-контрактов (tasks,jobs,job_deps,agent_runs) запрещено.
1. Новая таблица repo_freeze (FR-5)
Durable per-repo признак заморозки пакета после post-deploy DEGRADED/rollback. Append-only журнал:
активная заморозка ⇔ существует строка репо с cleared_at IS NULL.
CREATE TABLE IF NOT EXISTS repo_freeze (
id INTEGER PRIMARY KEY AUTOINCREMENT,
repo TEXT NOT NULL, -- ключ области (per-repo)
frozen_at TEXT NOT NULL DEFAULT (datetime('now')),
reason TEXT, -- напр. "post-deploy DEGRADED 3/5"
work_item_id TEXT, -- задача-источник деградации (уже stage='done')
cleared_at TEXT -- NULL = freeze активен; снят оператором → datetime
);
CREATE INDEX IF NOT EXISTS idx_repo_freeze_active ON repo_freeze (repo, cleared_at);
Семантика
- Активный freeze репо
R:EXISTS (SELECT 1 FROM repo_freeze WHERE repo=R AND cleared_at IS NULL). - Выставление (
set_repo_freeze): INSERT новой строки (cleared_at=NULL). Повторный DEGRADED при уже активном freeze — допускается доп. строка (журнал) либо no-op при существующей активной (выбор разработчика; на gate не влияет —EXISTSидемпотентен). - Снятие (
clear_repo_freeze):UPDATE repo_freeze SET cleared_at=datetime('now') WHERE repo=? AND cleared_at IS NULL(закрывает все активные строки репо). Идемпотентно (повтор → 0 rows). - Read (gate/snapshot): только
cleared_at IS NULL-строки;is_repo_frozen— fail-closed (ошибка чтения →True, AC-9).
Использование в горячем claim
db.claim_next_job читает repo_freeze инлайн внутри serial_gate-фрагмента (только локальная БД,
offline — NFR-2):
OR EXISTS (SELECT 1 FROM repo_freeze f WHERE f.repo = jobs.repo AND f.cleared_at IS NULL)
(внутри AND NOT ( jobs.agent='analyst' AND … ) — см. ADR D1). Тотальный сбой построения фрагмента →
fail-open для claim (AC-8); реально выставленная строка блокирует через сам SQL.
2. Активная задача репо — без новых колонок
«Репо занят» вычисляется из существующих столбцов tasks(repo, stage):
EXISTS (SELECT 1 FROM tasks WHERE repo=? AND id != ? AND stage != 'done')
Новых колонок/таблиц для «активной задачи» и «очереди ожидания» не вводится: ожидание = существующий
jobs.status='queued' analyst-job + gate в claim (ТЗ §5).
3. Идемпотентность и restart-safety
- Миграция
repo_freezeвыполняется в общем init-пути схемы (db.init_db/_ensure_*), безопасна к повторному запуску (IF NOT EXISTS). - Всё состояние gate/freeze — в БД (нет in-memory) ⇒ после рестарта поведение идентично (NFR-3/AC-3):
активная задача определяется из
tasks, freeze — изrepo_freeze, ожидающая задача —queuedjob. - При выключенных флагах (
serial_gate_enabled=False/serial_gate_freeze_enabled=False) таблица инертна; enduro и существующие контракты не затрагиваются (NFR-4/AC-11).
4. Неизменяемые контракты
tasks, jobs, job_deps, agent_runs, tracker_messages — схема без изменений.
STAGE_TRANSITIONS / QG_CHECKS — не БД, но также не меняются (NFR-5).