89fcc60782
Раньше give-up-путь finalizeRun ВОССТАНАВЛИВАЛ entry (зомби неотличим от живого
рана), а терминальный runRepo.update был БЕЗУСЛОВНЫМ (последний писатель перетирал
терминальный статус).
- Все терминальные UPDATE ранов теперь УСЛОВНЫЕ: новый repo.finalizeIfActive
пишет только пока строка pending|running (зеркально onlyIfStreaming у сообщений)
→ двойной settle схлопывается в benign no-op, терминальный статус не перетереть.
- При исчерпании ретраев finalizeRun НЕ восстанавливает entry, а оставляет
ЗОМБИ-запись { terminalWriteFailed, intended:{status,error} } в отдельной map;
settleZombie пере-драйвит intended условным UPDATE (зовётся reconcile/supersede/
boot sweep). Зомби удаляется после успешного UPDATE или обнаружения строки уже
терминальной.
- Per-run settledPromise в ОТДЕЛЬНОЙ map runId→deferred (переживает active.delete),
создаётся в beginRun, резолвится ровно один раз исходом (записано/сдался); поздний
подписчик через ранее взятую ссылку получает резолвленный; peekSettled: live
deferred → зомби-синтез → undefined (читать строку). Обе map bounded.
- Задокументирована потеря (single-process): рестарт до пере-драйва → boot sweep
пишет 'aborted' поверх фактического intended — неустранимо в phase 1.
Тесты: юниты give-up-пути (зомби вместо restore + notifier terminalWriteFailed +
settleZombie), двойного settle, позднего подписчика; интеграционные против реальной
Б, что условный finalizeIfActive не перетирает терминал и двойной settle схлопывается.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>