74 lines
4.7 KiB
Markdown
74 lines
4.7 KiB
Markdown
# 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`.
|
||
|
||
```sql
|
||
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)`:
|
||
```sql
|
||
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`, ожидающая задача — `queued` job.
|
||
- При выключенных флагах (`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).
|