architect(ET): auto-commit from architect run_id=387
All checks were successful
CI / test (push) Successful in 22s
All checks were successful
CI / test (push) Successful in 22s
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
# ADR-001: Сериализация merge/деплоя внутри репо (A) + декларативные зависимости задач (B)
|
||||
|
||||
**Work Item:** ORCH-026 · **Repo:** orchestrator (self-hosting) · **Стадия:** architecture
|
||||
**Статус:** Accepted
|
||||
**Связи:** дополняет ORCH-043 (merge-gate), ORCH-065 (merge-lease + reclaim), ORCH-073/071
|
||||
(merge-verify, SHA-in-main), ORCH-1 (очередь). Глобальный ADR — `adr/adr-0015`.
|
||||
|
||||
---
|
||||
|
||||
## Контекст
|
||||
|
||||
ORCH-026 закрывает **первопричину** эрозии `main` 08.06 (некоординированный параллелизм
|
||||
задач одного репо: ветки от устаревшего `main`, фантом-merge затирает соседа) и попутно вводит
|
||||
исходный скоуп — декларативные зависимости задач (B ждёт A). Требования — `01-brd.md`,
|
||||
`02-trz.md`; PASS/FAIL — `03-acceptance-criteria.md`.
|
||||
|
||||
Ключевое наблюдение архитектора: **бо́льшая часть инфраструктуры для Уровня A уже существует** и
|
||||
её НЕ нужно переписывать:
|
||||
|
||||
- **merge-lease** (ORCH-065, `src/merge_gate.py`): per-repo файловый лиз
|
||||
`.merge-lease-<repo>.json`, неблокирующий acquire, holder-aware release, проактивный реклейм
|
||||
мёртвого/устаревшего держателя (`reclaim_stale_lease`, `pid_alive`). Restart-safe, per-repo.
|
||||
- **merge-gate** (ORCH-043, `check_branch_mergeable`): на ребре `deploy-staging → deploy`
|
||||
захватывает лиз, при необходимости ребейзит, держит лиз до фактического merge.
|
||||
- **defer-механизм** (`_handle_merge_gate_defer`): `merge-lock busy` → повторная постановка
|
||||
deployer'а через `available_at`, bounded `merge_defer_max_attempts` → эскалация (Blocked+alert).
|
||||
- **окно лиза** уже простирается от `deploy-staging → deploy` до release на одном из событий:
|
||||
PR-merged webhook (`gitea.py`), `deploy→done` (`stage_engine.py`), откат, проактивный реклейм.
|
||||
Для self-hosting `done` достигается ТОЛЬКО после `verify_merged_to_main` (SHA-in-main, ORCH-073).
|
||||
|
||||
Таким образом окно сериализации A-1 («merge → main-updated») **структурно уже реализовано**:
|
||||
пока A не подтверждена в `main` (для self — SHA-in-main → `done`), лиз держится, и B того же
|
||||
репо на своём merge-gate получает `merge-lock busy` → defer. Открытый вопрос BRD §4.3 (граница
|
||||
окна для self) решается так: **окно = от acquire до release; release-события не меняем**. Для
|
||||
non-self репо граница — PR-merged webhook; для self — `deploy→done` (= SHA-in-main подтверждён).
|
||||
|
||||
Что реально **отсутствует** для Уровня A:
|
||||
|
||||
- **A-2: безусловный proactive pre-merge rebase.** Сейчас `check_branch_mergeable` ребейзит
|
||||
ТОЛЬКО если `branch_is_behind_main` (⇔ `origin/main` не предок HEAD). AC-A2 требует, чтобы
|
||||
rebase вызывался **всегда** перед merge — детерминированный структурный анти-фантом на уровне
|
||||
планировщика, не зависящий от точности ancestor-проверки.
|
||||
|
||||
Для Уровня B инфраструктуры нет вовсе: очередь `jobs` (ORCH-1) плоская (FIFO по `id` +
|
||||
`available_at` + `max_concurrency`), выразить «B ждёт A» нельзя.
|
||||
|
||||
---
|
||||
|
||||
## Решение
|
||||
|
||||
### Уровень A — сериализация merge/деплоя (минимально-инвазивно, переиспользуя ORCH-043/065)
|
||||
|
||||
**A-1/A-3/A-4 (окно сериализации) — без изменений механизма.** Окно сериализации обеспечивается
|
||||
существующим merge-lease: захват в `check_branch_mergeable`, удержание до release. Подтверждаем и
|
||||
фиксируем в доке, что release-события (`PR-merged` / `deploy→done` / откат / `reclaim_stale_lease`)
|
||||
формируют окно «merge → main-updated». Кросс-репо параллелизм сохранён автоматически (лиз —
|
||||
per-repo файл). Restart-safe и анти-залипание — за счёт ORCH-065 reclaim. **Кода-изменений нет.**
|
||||
|
||||
**A-2 (безусловный pre-merge rebase) — новое поведение, флаг `premerge_rebase_always`.**
|
||||
|
||||
- В `check_branch_mergeable` (`src/qg/checks.py`), ПОД захваченным merge-lease: когда
|
||||
`settings.premerge_rebase_always` истинно (и merge-gate применим к репо), **пропустить
|
||||
short-circuit `branch_is_behind_main`** и **всегда** вызвать `merge_gate.auto_rebase_onto_main`.
|
||||
- `auto_rebase_onto_main` уже идемпотентен и дёшев на актуальной ветке: `git rebase origin/main`
|
||||
на не-отстающей ветке — no-op (rc 0, HEAD не меняется), последующий `push --force-with-lease`
|
||||
→ «Everything up-to-date» (тот же SHA, **CI не перезапускается, лишних коммитов нет**). На
|
||||
отстающей ветке — реальный догон. Текстовый конфликт → существующий контракт: `rebase --abort`
|
||||
→ откат на `development` (как ORCH-043). **Инвариант: никаких push/force-push в `main`** —
|
||||
единственная force-операция остаётся `--force-with-lease` на ветку задачи.
|
||||
- Когда флаг выключен → прежнее поведение (ребейз только при `branch_is_behind_main`),
|
||||
обратная совместимость 1:1 (AC-A7/AC-G2).
|
||||
- **Скоуп — общий с merge-gate:** реально только для `merge_gate_repos` (пусто → self-hosting
|
||||
`orchestrator`). Никакого нового scope-флага.
|
||||
|
||||
**A-5/A-6 (safety, anti-livelock) — без изменений.** `STAGE_TRANSITIONS`, `QG_CHECKS`,
|
||||
`Confirm Deploy` (ORCH-059), exit-коды хука, terminal-sync не трогаются. defer-бюджет —
|
||||
существующий `merge_defer_max_attempts` → Blocked+alert при исчерпании. Прод-контейнер не
|
||||
рестартится вне штатного `Confirm Deploy`.
|
||||
|
||||
### Уровень B — декларативные зависимости (новая инфраструктура)
|
||||
|
||||
**B-источник: гибрид с БД как источником истины для планировщика; флаг `task_deps_source`.**
|
||||
|
||||
Планировщик `claim_next_job` — горячий цикл, обслуживающий очередь ВСЕХ проектов из ОДНОГО
|
||||
инстанса. Он **обязан** быть offline-устойчивым и быстрым: сетевой запрос в Plane на каждый claim
|
||||
= при недоступности Plane встанет конвейер всех проектов (нарушение self-hosting safety). Поэтому:
|
||||
|
||||
- **Авторитетный для планировщика стор — локальная БД**, новая аддитивная таблица
|
||||
`job_deps(task_id, depends_on_task_id, created_at)` (детали — `08-data-requirements.md`).
|
||||
Связь хранится по `tasks.id` (стабильный локальный ключ). Зависимости — **только внутри одного
|
||||
репо** (v1; кросс-репо — non-goal, BRD §5).
|
||||
- **`task_deps_source = db | plane | hybrid`** (дефолт **`db`**): `db` — связи пишутся напрямую в
|
||||
`job_deps` (потребитель — декомпозиция эпиков ORCH-025); `plane` — связи читаются из Plane
|
||||
relations в `handle_work_item_created` и **кэшируются** в `job_deps`; `hybrid` — Plane как
|
||||
декларация + БД-кэш. Plane-ingestion — тонкий add-on за флагом; планировщик ВСЕГДА читает БД.
|
||||
|
||||
**B-2 (гейт планировщика) — SQL `NOT EXISTS`, без занятия слота `max_concurrency`.**
|
||||
|
||||
Гейт готовности выражается декларативно в `claim_next_job` (`src/db.py`): задача claimable, если
|
||||
у неё нет ни одной незавершённой зависимости. Когда `settings.task_deps_enabled` — к существующему
|
||||
SELECT добавляется условие:
|
||||
|
||||
```sql
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM job_deps d
|
||||
JOIN tasks t ON t.id = d.depends_on_task_id
|
||||
WHERE d.task_id = j.task_id AND t.stage != 'done'
|
||||
)
|
||||
```
|
||||
|
||||
Это: (1) **не занимает слот** — job просто не выбирается, агент не запускается (AC-B2);
|
||||
(2) restart-safe (чистая БД); (3) never-raise (это SQL); (4) при пустой `job_deps` —
|
||||
инертно (нулевая регрессия, AC-G2); (5) при выключенном `task_deps_enabled` условие НЕ
|
||||
добавляется → запрос 1:1 как в ORCH-1. Как только все зависимости достигают `stage='done'`,
|
||||
задача автоматически становится claimable.
|
||||
|
||||
Чистая leaf-логика «готова ли задача» выносится в новый модуль `src/task_deps.py`:
|
||||
`is_task_ready(task_id) -> (bool, waiting_on: list[str])` (never-raise) — для реконсилятора,
|
||||
карточки и `/queue` (SQL в `claim_next_job` — горячий путь, дублирует ту же семантику).
|
||||
|
||||
**B-3 (детект дедлоков) — DFS, чистая функция.**
|
||||
|
||||
`task_deps.detect_cycle(task_id) -> list[int] | None` — обход графа `job_deps` (внутри репо),
|
||||
детерминированный, юнит-тестируемый, never-raise. Запускается: (1) при вставке связи
|
||||
(`add_dependency`) — цикл отклоняется/алертится сразу (лучший UX); (2) backstop-проход в тике
|
||||
`reconciler` (на случай связей, добавленных в обход). Цикл → `set_issue_blocked(work_item_id)` +
|
||||
Telegram/Plane alert с перечислением цикла. SQL-гейт B-2 сам по себе никогда не выберет задачу в
|
||||
цикле (её зависимости не достигнут `done`) — детектор делает это **видимым**, а не молчаливым
|
||||
вечным ожиданием (AC-B3). Поток остальных задач не блокируется.
|
||||
|
||||
**B-4 (видимость).**
|
||||
|
||||
- Нормальное ожидание (B ждёт A, A в работе — транзиентно и ожидаемо): строка в Telegram-карточке
|
||||
«⏳ ждёт ORCH-NNN» через `notifications.update_task_tracker`, never-raise/silent. **Plane Blocked
|
||||
при нормальном ожидании НЕ ставим** — иначе флаппинг Blocked на каждом коротком ожидании.
|
||||
- Дедлок/цикл (B-3): `set_issue_blocked` (Plane `Blocked`) + alert. Это «и/или» из AC-B4.
|
||||
- Инвариант «одна карточка на задачу» сохранён (ORCH-042/067).
|
||||
|
||||
**B-5 (совместимость reconciler/reaper).**
|
||||
|
||||
- `reconciler` F-1 не должен «разблокировать» dep-заблокированную задачу мимо её зависимостей.
|
||||
В фильтр пригодности reconciler добавляется проверка `task_deps.is_task_ready` (по образцу
|
||||
`reconcile_skip_blocked_enabled`, ORCH-060): не готова → skip.
|
||||
- `reaper` сканирует **`running`** jobs; dep-заблокированный job остаётся `queued` (его не
|
||||
клеймят) → reaper его не трогает по построению. Фиксируем в доке.
|
||||
|
||||
**Наблюдаемость (TRZ §4):** блок `task_deps` в снимке `GET /queue` (read-only, по образцу
|
||||
`reconcile`/`reaper`): кол-во заблокированных задач, держатель merge-lease, defer-счётчики,
|
||||
обнаруженные циклы. Никогда не источник решений.
|
||||
|
||||
### Конфигурация (`src/config.py`)
|
||||
|
||||
| Флаг | Дефолт | Назначение |
|
||||
|------|--------|-----------|
|
||||
| `premerge_rebase_always` | `True` | Уровень A: безусловный pre-merge rebase под лизом. Скоуп — `merge_gate_repos`. Kill-switch (`False` → ребейз только при behind, как ORCH-043). |
|
||||
| `task_deps_enabled` | `True` | Уровень B: глобальный kill-switch гейта зависимостей. `False` → `claim_next_job` 1:1 как ORCH-1. Инертно при пустой `job_deps`. |
|
||||
| `task_deps_source` | `"db"` | Источник деклараций: `db`\|`plane`\|`hybrid`. Планировщик всегда читает БД-кэш. |
|
||||
|
||||
Дефолты следуют конвенции репо (`*_enabled=True` + kill-switch), при этом обе фичи инертны без
|
||||
данных (нет деклараций / нет применимых репо) → нулевая регрессия для enduro-trails.
|
||||
|
||||
---
|
||||
|
||||
## Альтернативы (и почему отвергнуты)
|
||||
|
||||
1. **Уровень A — отдельный глобальный per-repo merge-lock или FIFO merge-queue.** Дублировал бы
|
||||
уже существующий merge-lease (ORCH-065), вводил второй механизм сериализации с риском
|
||||
рассинхрона. Отвергнуто: BRD §4.2 требует минимально-инвазивного решения, переиспользующего
|
||||
ORCH-065/043. Окно лиза уже даёт сериализацию.
|
||||
|
||||
2. **Уровень A — расширять release-точки лиза (держать до отдельного `main-updated`-события).**
|
||||
Не требуется: для self `done` ⇔ SHA-in-main (ORCH-073), для non-self — PR-merged webhook;
|
||||
окно уже корректно. Доп. событие усложнило бы reclaim без выигрыша.
|
||||
|
||||
3. **Уровень B — Plane relations как источник истины планировщика.** Сетевой запрос в горячем
|
||||
цикле claim; при недоступности Plane встаёт очередь всех проектов (self-hosting risk).
|
||||
Отвергнуто; Plane оставлен опциональным источником **декларации** (`task_deps_source=plane`),
|
||||
но планировщик читает только БД-кэш.
|
||||
|
||||
4. **Уровень B — гейт зависимостей в воркере (`_drain_once`) поверх `claim_next_job`.** Пришлось
|
||||
бы клеймить job, обнаруживать незавершённую зависимость и re-queue’ить — churn, расход attempts,
|
||||
гонки. SQL `NOT EXISTS` в самом `claim_next_job` чище: job просто не выбирается, слот свободен.
|
||||
|
||||
5. **Уровень B — поле/JSON в `tasks` вместо таблицы.** Таблица `job_deps` нормальна (M:N),
|
||||
индексируема, проще для DFS и `NOT EXISTS`. Поле в `tasks` потребовало бы парсинг-логики.
|
||||
|
||||
---
|
||||
|
||||
## Последствия
|
||||
|
||||
**Плюсы.**
|
||||
- Минимально-инвазивно: Уровень A — один флаг + снятие short-circuit; окно сериализации не
|
||||
переписывается. Переиспользует ORCH-043/065 целиком.
|
||||
- Уровень B — одно `NOT EXISTS` в `claim_next_job` + аддитивная таблица + leaf-модуль
|
||||
`task_deps.py`; `STAGE_TRANSITIONS`/`QG_CHECKS` не тронуты (паттерн врезки ORCH-071/058).
|
||||
- Обе фичи инертны без данных → нулевая регрессия для enduro-trails (AC-A7/AC-G2).
|
||||
- restart-safe (БД + файловый лиз), never-raise, kill-switch на каждую фичу.
|
||||
|
||||
**Минусы / ограничения.**
|
||||
- `premerge_rebase_always=True` добавляет (дешёвый, no-op на актуальной ветке) `rebase`+`push`
|
||||
на каждый self-merge. Цена — лишний git-вызов; компенсируется детерминизмом анти-фантома.
|
||||
- Уровень B v1 — только intra-repo зависимости; кросс-репо — follow-up (non-goal).
|
||||
- Гейт B-2 в `claim_next_job` слегка усложняет горячий SQL (один `NOT EXISTS`); защищён
|
||||
kill-switch и инертностью при пустой таблице.
|
||||
- `task_deps.py` цикл-детектор — новая поверхность; покрывается юнит-тестами (`04-test-plan.yaml`).
|
||||
|
||||
**Инварианты (не нарушать).**
|
||||
1. `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_deploy_status`/`check_staging_status`,
|
||||
`Confirm Deploy` (ORCH-059), БАГ-8, terminal-sync — без изменений.
|
||||
2. Никаких push/force-push в `main`; force только `--force-with-lease` на ветку задачи.
|
||||
3. Сериализация — строго per-repo; кросс-репо параллелизм сохранён.
|
||||
4. never-raise во всех новых функциях; restart-safe состояние; миграция БД только аддитивная.
|
||||
5. ORCH-026 **дополняет** рубежи ORCH-073, не заменяет.
|
||||
6. Прод-контейнер orchestrator не рестартится вне штатного `Confirm Deploy`.
|
||||
|
||||
**Места реализации (для developer).**
|
||||
- `src/qg/checks.py::check_branch_mergeable` — ветка `premerge_rebase_always`.
|
||||
- `src/db.py::claim_next_job` — условный `NOT EXISTS`-гейт; новые helpers `add_dependency`,
|
||||
`get_dependencies`, `job_deps` миграция в `init_db` (`CREATE TABLE IF NOT EXISTS`).
|
||||
- `src/task_deps.py` (новый, leaf) — `is_task_ready`, `detect_cycle`, snapshot для `/queue`.
|
||||
- `src/webhooks/plane.py::handle_work_item_created` — ingestion Plane relations (за `task_deps_source`).
|
||||
- `src/reconciler.py` — skip dep-заблокированных + backstop цикл-детект.
|
||||
- `src/notifications.py` — строка ожидания в карточке.
|
||||
- `src/config.py` — `premerge_rebase_always`, `task_deps_enabled`, `task_deps_source`.
|
||||
- Документация: `docs/architecture/README.md`, `CLAUDE.md` (если меняется поведение очереди),
|
||||
`CHANGELOG.md`, глобальный `adr/adr-0015`.
|
||||
65
docs/work-items/ORCH-026/08-data-requirements.md
Normal file
65
docs/work-items/ORCH-026/08-data-requirements.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 08 — Требования к схеме БД — ORCH-026
|
||||
|
||||
**Work Item:** ORCH-026 · **Repo:** orchestrator · **Стадия:** architecture
|
||||
**Связь:** ADR `06-adr/ADR-001-merge-serialization-and-task-deps.md` (Уровень B).
|
||||
|
||||
> Уровень A (сериализация merge/деплоя) — **БЕЗ изменения схемы БД** (merge-lease файловый,
|
||||
> `.merge-lease-<repo>.json`, ORCH-065). Изменения схемы касаются ТОЛЬКО Уровня B.
|
||||
|
||||
---
|
||||
|
||||
## Новая таблица `job_deps` (аддитивная)
|
||||
|
||||
Хранит декларативные зависимости «задача `task_id` ждёт задачу `depends_on_task_id`».
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS job_deps (
|
||||
task_id INTEGER NOT NULL, -- tasks.id зависимой задачи (B)
|
||||
depends_on_task_id INTEGER NOT NULL, -- tasks.id задачи-предшественника (A)
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (task_id, depends_on_task_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_job_deps_task ON job_deps(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_job_deps_depends ON job_deps(depends_on_task_id);
|
||||
```
|
||||
|
||||
### Поля
|
||||
| Поле | Тип | Назначение |
|
||||
|------|-----|-----------|
|
||||
| `task_id` | INTEGER | `tasks.id` зависимой задачи (B). Не запускается, пока зависимости не `done`. |
|
||||
| `depends_on_task_id` | INTEGER | `tasks.id` предшественника (A). Терминальность — `tasks.stage = 'done'`. |
|
||||
| `created_at` | TEXT | Время декларации (диагностика). |
|
||||
|
||||
### Ключ и индексы
|
||||
- **PK `(task_id, depends_on_task_id)`** — идемпотентность вставки (повторная декларация связи —
|
||||
no-op через `INSERT OR IGNORE`), запрет дублей.
|
||||
- `idx_job_deps_task` — гейт планировщика (`NOT EXISTS ... WHERE d.task_id = j.task_id`).
|
||||
- `idx_job_deps_depends` — обратные рёбра для DFS цикл-детектора.
|
||||
|
||||
### Семантика готовности (источник истины планировщика)
|
||||
Задача `task_id` **готова к запуску** ⇔ нет ни одной строки `job_deps` для неё, чей
|
||||
`depends_on_task_id` указывает на задачу с `tasks.stage != 'done'`. Терминал — только `done`
|
||||
(совпадает с тем, как `get_active_tasks_for_reconcile` трактует терминальность).
|
||||
|
||||
### Связь по `task_id`, а не `work_item_id`
|
||||
`tasks.id` — стабильный локальный автоинкремент-ключ; `work_item_id`/`plane_id` могут
|
||||
ресолвиться/коллизиться (см. `ensure_unique_work_item_id`). FK логический (без `REFERENCES`,
|
||||
как у `jobs.task_id`) — не блокирует аддитивную миграцию и удаление строк tasks (которого в
|
||||
конвейере нет). Зависимости — **только intra-repo** (v1); кросс-репо рёбра не создаются.
|
||||
|
||||
---
|
||||
|
||||
## Миграция (AC-G4)
|
||||
|
||||
- Выполняется в `src/db.py::init_db` рядом с прочими: **только** `CREATE TABLE IF NOT EXISTS` +
|
||||
`CREATE INDEX IF NOT EXISTS`. **Идемпотентно**, restart-safe, безопасно на живой общей прод-БД.
|
||||
- **Существующие колонки/таблицы (`jobs`, `tasks`, `agent_runs`, `events`) НЕ изменяются** →
|
||||
данные enduro-trails не затронуты.
|
||||
- Откат фичи — флагом `task_deps_enabled=False` (таблица остаётся, гейт не применяется); сама
|
||||
таблица деструктивно не удаляется.
|
||||
|
||||
## Что НЕ меняется
|
||||
- Схема `jobs` (включая `available_at`, `pid`, `attempts`/`transient_attempts`) — без изменений;
|
||||
defer Уровня A/B переиспользует существующий `available_at`-механизм.
|
||||
- Схема `tasks` — без изменений (видимость через существующие `tracker_message_id` и Plane Blocked).
|
||||
- merge-lease — файловый, вне БД.
|
||||
17
docs/work-items/ORCH-026/10-tech-risks.md
Normal file
17
docs/work-items/ORCH-026/10-tech-risks.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 10 — Технические риски — ORCH-026
|
||||
|
||||
**Work Item:** ORCH-026 · **Repo:** orchestrator · **Стадия:** architecture
|
||||
**Связь:** ADR `06-adr/ADR-001-merge-serialization-and-task-deps.md`.
|
||||
|
||||
| # | Риск | Уровень | Митигация |
|
||||
|---|------|---------|-----------|
|
||||
| R-1 | **Гейт `NOT EXISTS` в `claim_next_job` (горячий путь всех проектов) содержит баг → встаёт очередь ВСЕХ проектов** (self-hosting групповой риск). | Высокий | Условие добавляется ТОЛЬКО при `task_deps_enabled`; инертно при пустой `job_deps` (нулевая регрессия); kill-switch `task_deps_enabled=False` мгновенно возвращает поведение ORCH-1; интеграционный тест «пустые deps ⇒ FIFO 1:1» (AC-G2). |
|
||||
| R-2 | **Безусловный `premerge_rebase_always` делает лишний `push --force-with-lease` → ложный перезапуск CI / новые коммиты.** | Низкий | На актуальной ветке `rebase origin/main` — no-op (HEAD не меняется), push → «Everything up-to-date» (тот же SHA, CI не триггерится). Подтвердить тестом, что SHA не меняется на уже-актуальной ветке. |
|
||||
| R-3 | **Дедлок по циклической зависимости → задача молча ждёт вечно.** | Средний | DFS-детектор `detect_cycle` при вставке связи + backstop в `reconciler`; цикл → `set_issue_blocked` + alert с перечислением цикла (AC-B3); SQL-гейт не выбирает задачу в цикле, детектор делает это видимым. |
|
||||
| R-4 | **Livelock: B бесконечно defer’ится на `merge-lock busy`.** | Низкий | Существующий bounded-бюджет `merge_defer_max_attempts` → Blocked+alert (ORCH-043, без изменений). |
|
||||
| R-5 | **Залипший merge-lease после смерти держателя → конвейер репо встаёт навсегда.** | Средний | Переиспользуется ORCH-065: `reclaim_stale_lease` (мёртвый `pid` / TTL `merge_lock_timeout_s`) + holder-aware release. Restart-safe (AC-A4). |
|
||||
| R-6 | **Plane relations недоступны/неверно смаплены при `task_deps_source=plane`.** | Средний | Планировщик читает ТОЛЬКО БД-кэш `job_deps`; Plane-ingestion — best-effort, never-raise; дефолт `task_deps_source=db` не зависит от Plane. |
|
||||
| R-7 | **reconciler «разблокирует» dep-заблокированную задачу мимо её зависимостей.** | Средний | В фильтр reconciler добавляется `is_task_ready` (паттерн ORCH-060 skip-Blocked); reaper трогает только `running` — dep-блок остаётся `queued` (AC-B5). |
|
||||
| R-8 | **Миграция БД повреждает общую прод-БД (данные enduro-trails).** | Низкий | Только аддитивно: `CREATE TABLE/INDEX IF NOT EXISTS`; существующие колонки не меняются; идемпотентно (AC-G4). |
|
||||
| R-9 | **Self-hosting: изменения требуют рестарта прод-контейнера вне `Confirm Deploy`.** | Высокий (если нарушено) | Все изменения — обычный код, проходят `deploy-staging` (8501) → `Confirm Deploy` (ORCH-059). `STAGE_TRANSITIONS`/`QG_CHECKS` не трогаются; никакого внеочередного рестарта (AC-A5). |
|
||||
| R-10 | **Конфликт точек интеграции A (merge-gate) и B (постановка в очередь).** | Низкий | Разные точки конвейера: B гейтит claim job (вход), A гейтит merge на ребре `deploy-staging→deploy`. Независимы; покрыть интеграционным тестом совместной работы (BRD §4.4). |
|
||||
Reference in New Issue
Block a user