Files
jellybit/openspec/changes/linking-transition-robustness/proposal.md
T
avandClaude Opus 4.8 9bab7dc402 Устойчивость раскладки и переходов linking (MAJOR-4, MINOR-7)
Закрывает две связанные дыры «claim-then-side-effect» в раскладке хардлинками.

MINOR-7: transition глотал ошибку записи состояния — на путях Apply и
авто-раскладки выполнение продолжалось к хардлинкам при незакоммиченном claim
перехода в linking, а финальный linking→done отклонялся графом (задача застревала
со stale-планом). Выделен transitionErr, возвращающий ошибку; Apply и
finishRecognition прерываются ДО linkPlan при провале claim. Обёртка transition
(void) сохранена для fire-and-forget переходов — соседние функции воркера не
тронуты.

MAJOR-4: (A) провал CreateFileLinks после создания хардлинков больше не оставляет
задачу в linking голым return — уводим в review (код persist), повтор Apply
идемпотентен. (B) новый шаг pollOnce sweepLinking возвращает осиротевшие после
краха linking-задачи в review (код interrupted) на тике и старте; любая linking
под w.mu устарела по построению. Восстановлен инвариант «у каждого нетерминального
состояния есть владелец».

Граф переходов не тронут (ребро linking→review уже объявлено). Тесты: провал claim
не создаёт хардлинков; провал учёта уводит в review; sweep осиротевшего linking.

OpenSpec-change linking-transition-robustness (дельты file-layout,
state-reconciliation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 17:18:28 +03:00

62 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Why
Раскладка хардлинками устроена как «claim-then-side-effect»: сначала задача
переводится в `linking` (claim владения шагом), затем создаются хардлинки и
пишется их учёт (`file_link`). Ревью жизненного цикла (Fable, 2026-07-08)
нашло две связанные дыры устойчивости этого пути.
- **MINOR-7:** `worker.transition` при ошибке записи состояния логировал её, но
НЕ возвращал вызывающему. На путях `Apply` и авто-раскладки в
`finishRecognition` выполнение продолжалось к побочным эффектам: хардлинки
создавались, пока claim перехода в `linking` не закоммичен. Финальный переход
`linking → done` оценивался графом как `review → done` (нелегальное ребро) и
отклонялся — задача застревала в `review` со stale-планом, скан/уведомление не
срабатывали.
- **MAJOR-4:** задача может осиротеть в `linking`:
(A) без краха — хардлинки созданы, но `CreateFileLinks` упал транзиентно
(SQLite busy) → голый `return` оставлял задачу в `linking`, а файлы на диске —
без строк `file_link`;
(B) краш процесса между переходом в `linking` и финальным переходом → на
рестарте `linking` не листит НИКТО (поллинг листит `downloading`, распознавание
`completed`/`recognizing`, сверка — `done`/`target_missing`/`orphaned`,
восстановление — `failed`/`stuck`). Задача сидит в `linking` вечно; выход —
только ручной Cancel/Defer (недискаверабельно). Нарушен инвариант «у каждого
нетерминального состояния есть владелец» (`recognizing` уже лечится
рестартом через `recognizePending`, `linking` — нет).
## What Changes
- `transition` разделяется на fire-and-forget обёртку (прежнее имя, прежнее
поведение для reconcile/таймаутов/финальных переходов) и `transitionErr`,
которая ВОЗВРАЩАЕТ ошибку записи. На claim-then-side-effect путях (`Apply`,
авто-раскладка в `finishRecognition`) провал claim перехода в `linking` теперь
прерывает выполнение ДО хардлинков.
- В `linkPlan` провал `CreateFileLinks` больше не оставляет задачу в `linking`:
задача уходит в `review` с кодом `persist` и причиной; повторный `Apply`
идемпотентен (хардлинки уже на диске → `StatusExists`, учёт дописывается).
- Новый шаг поллинга `sweepLinking`: на каждом тике и на старте задачи в
`linking` возвращаются в `review` с кодом `interrupted` и причиной
«прерванная раскладка, повтори применение». Любая `linking`, видимая под
`w.mu`, устарела по построению (активная раскладка держит `w.mu` весь свой
срок), значит осталась после краха.
## Capabilities
### Modified Capabilities
- `file-layout`: раскладка становится устойчивой к сбою записи claim/учёта —
хардлинки не создаются при незакоммиченном claim, а сбой записи учёта не
стрэндит задачу в `linking`.
- `state-reconciliation`: у нетерминального `linking` появляется владелец на
рестарте/тике — sweep осиротевших `linking` в `review`.
## Impact
- **Код:** `internal/worker/worker.go` (`transition`/`transitionErr`, `pollOnce`,
`sweepLinking`), `internal/worker/review.go` (`Apply`, `finishRecognition`,
`linkPlan`). Граф переходов (`internal/store/download.go`) правки не требует —
ребро `linking → review` уже объявлено.
- **Тесты:** провал claim прерывает до хардлинков; провал `CreateFileLinks`
уводит в `review` (файлы на диске); sweep осиротевшего `linking``review`.
- **БД/схема:** без изменений.