Устойчивость раскладки и переходов 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>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Claim раскладки коммитится до хардлинков и устойчив к сбою учёта
|
||||
|
||||
Раскладка — «claim-then-side-effect»: система SHALL сперва зафиксировать переход
|
||||
задачи в `linking` (claim шага раскладки), и только затем создавать хардлинки.
|
||||
Если запись claim перехода в `linking` провалилась, система НЕ SHALL создавать
|
||||
хардлинки и SHALL прервать раскладку, оставив задачу в исходном состоянии
|
||||
(`review`/`deferred` при ручном применении; `recognizing` при авто-раскладке) —
|
||||
чтобы у шага сохранился владелец, а хардлинки не легли при незакоммиченном claim
|
||||
(иначе финальный переход `linking → done` из фактического состояния был бы
|
||||
отклонён графом, и задача застряла бы со stale-планом).
|
||||
|
||||
Если хардлинки уже созданы, но запись их учёта (`file_link`) провалилась
|
||||
(транзиентная ошибка хранилища), задача НЕ SHALL оставаться в `linking`: система
|
||||
SHALL перевести её в `review` с причиной. Повторное применение SHALL быть
|
||||
идемпотентным — уже созданные хардлинки распознаются как существующие
|
||||
(`StatusExists`), а их учёт дописывается.
|
||||
|
||||
#### Scenario: Провал claim не создаёт хардлинков
|
||||
|
||||
- **GIVEN** задача в `review` с готовым источником и валидным планом
|
||||
- **WHEN** запись перехода в `linking` проваливается
|
||||
- **THEN** хардлинки не создаются, учёт `file_link` не пишется
|
||||
- **AND** задача остаётся в `review`, а команда отказывает с ошибкой
|
||||
|
||||
#### Scenario: Провал учёта уводит в review, не оставляя в linking
|
||||
|
||||
- **GIVEN** хардлинки по плану уже созданы на файловой системе
|
||||
- **WHEN** запись строк `file_link` проваливается транзиентной ошибкой
|
||||
- **THEN** задача переходит в `review` с причиной (код `persist`), а не остаётся
|
||||
в `linking`
|
||||
- **AND** созданные хардлинки остаются на диске
|
||||
- **AND** повторное «Применить» идемпотентно дописывает учёт и доводит до `done`
|
||||
@@ -0,0 +1,33 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Восстановление задачи, застрявшей в linking
|
||||
|
||||
Система SHALL на каждом тике поллинга и при старте выявлять задачи в состоянии
|
||||
`linking` и возвращать их в `review` с причиной «прерванная раскладка» (код
|
||||
`interrupted`), откуда человек повторит применение (повтор идемпотентен).
|
||||
`linking` — нетерминальное активное состояние, и у него, как у каждого
|
||||
нетерминального состояния, ДОЛЖЕН быть владелец, продвигающий задачу; иначе
|
||||
краш процесса между переходом в `linking` и финальным переходом оставил бы
|
||||
задачу без владельца — её не листит ни один штатный шаг (ни поллинг активных,
|
||||
ни распознавание, ни матрица сверки, ни восстановление `failed`/`stuck`).
|
||||
|
||||
Выявление SHALL выполняться под той же блокировкой переходов, что и раскладка:
|
||||
активная раскладка удерживает блокировку весь свой срок и завершает переход из
|
||||
`linking` до её отпускания, поэтому любая `linking`-задача, наблюдаемая под
|
||||
блокировкой, по построению устарела (осталась после краха) — восстановление НЕ
|
||||
SHALL задевать раскладку в полёте.
|
||||
|
||||
#### Scenario: Осиротевший linking возвращается в review
|
||||
|
||||
- **GIVEN** задача осталась в `linking` после краха между claim и финальным
|
||||
переходом
|
||||
- **WHEN** выполняется тик поллинга (или старт сервиса)
|
||||
- **THEN** задача переходит в `review` с причиной «прерванная раскладка»
|
||||
(код `interrupted`)
|
||||
- **AND** её можно повторно применить из ревью
|
||||
|
||||
#### Scenario: Прочие состояния sweep не задевает
|
||||
|
||||
- **GIVEN** задачи в состояниях `done` и `review`
|
||||
- **WHEN** выполняется тик поллинга
|
||||
- **THEN** восстановление `linking` их состояние не меняет
|
||||
Reference in New Issue
Block a user