Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QKHeGuAYh4W1jNdjMConZa
13 KiB
recording-lifecycle Specification
Purpose
Гарантирует, что каждая завершённая или прерванная голосовая запись сохраняется немедленно и доводится до текста через отправку во внешний сервис с повторами, без дублей между устройствами и без потери данных.
Requirements
Requirement: Состояния записи
Запись MUST находиться ровно в одном из состояний: created (идёт запись, данные только в памяти), saved (аудио и заметка сохранены, ожидает отправки), sent (запрос в полёте), transcribed (текст получен, не записан; не сохраняется), retry (ожидает повтора после временной ошибки), failed (требуется ручное действие), inserted (финал). Во frontmatter заметки MUST сохраняться только saved, sent, retry, failed, inserted. Переход, не описанный в этой спецификации, MUST отклоняться ошибкой без изменения записи.
Scenario: Недопустимый переход отклонён
- WHEN к записи в состоянии
insertedприменяется событие «отправка начата» - THEN операция завершается ошибкой, запись не изменена
Scenario: Транзитное состояние не попадает в заметку
- WHEN получен текст транскрипта
- THEN заметка за одну запись переходит из
sentсразу вinserted, значениеtranscribedво frontmatter не появляется
Requirement: Сохранение захваченного аудио
По окончании записи плагин MUST сохранить аудио в voice/source/ и создать заметку в voice/inbox/ в состоянии saved до любой сетевой операции. Если захвачено 0 байт, MUST NOT создаваться ни аудиофайл, ни заметка. Если запись была прервана (событие остановки не от пользователя), сохраняется всё накопленное, а запись помечается как прерванная.
Scenario: Обычная запись
- WHEN пользователь остановил запись длиной 0:47 с непустыми данными в формате
audio/mp4 - THEN в
voice/source/появился файл<id>.m4a, вvoice/inbox/— заметка со статусомsaved, и это произошло без сетевых запросов
Scenario: Прерванная запись
- WHEN запись прервалась системой после 0:47 с непустыми данными
- THEN аудио и заметка сохранены, заметка помечена как прерванная с длительностью 0:47, и запись участвует в очереди как обычная
Scenario: Пустая запись
- WHEN запись завершилась (штатно или обрывом) с 0 байт данных
- THEN никаких файлов не создано
Requirement: Отправка и результат
Перед отправкой запись MUST перейти в sent с отметкой времени начала отправки, записанной в заметку. Очередь MUST обрабатывать записи строго по одной. Результат провайдера — успех с текстом, временная ошибка или постоянная ошибка. Успех с непустым текстом MUST переводить запись в inserted с текстом в заметке. Успех с пустым (после обрезки пробелов) текстом MUST переводить запись в failed с причиной «нет речи»: это свойство записи, а не ошибка сервиса, повтор бесполезен.
Scenario: Успешное распознавание
- WHEN провайдер вернул текст, а запись в момент ответа в
sent - THEN заметка содержит текст и статус
inserted
Scenario: В записи нет речи
- WHEN провайдер вернул успех с текстом из одних пробелов
- THEN запись в
failedс причиной «нет речи», в заметке — «В записи не распознано речи»
Scenario: Последовательная обработка
- WHEN в очереди три готовые к отправке записи
- THEN второй запрос к провайдеру начинается только после завершения первого
Requirement: Повторы после временных ошибок
Временная ошибка MUST увеличивать счётчик попыток и переводить запись в retry со временем следующей попытки через 30 с, 2 мин, 10 мин и 30 мин после 1-й, 2-й, 3-й и 4-й неудачи соответственно. Пятая неудача подряд MUST переводить запись в failed. Постоянная ошибка MUST переводить запись в failed сразу, независимо от счётчика. Запись в retry MUST NOT отправляться раньше своего времени следующей попытки.
Scenario: Сеть пропала и вернулась
- WHEN первая отправка завершилась временной ошибкой в 14:15:03, а следующая попытка после 14:15:33 — успехом
- THEN после первой попытки запись в
retryсо следующей попыткой 14:15:33, после второй — вinserted
Scenario: Исчерпание попыток
- WHEN пять отправок подряд завершились временной ошибкой
- THEN запись в
failed, причина — последняя ошибка
Scenario: Постоянная ошибка
- WHEN первая отправка завершилась постоянной ошибкой «401 Unauthorized»
- THEN запись сразу в
failedс этой причиной
Scenario: Время повтора не наступило
- WHEN очередь запускается в 14:15:20 для записи со следующей попыткой 14:15:33
- THEN запрос к провайдеру не отправляется
Requirement: Восстановление после прерванной отправки
Запись текущего устройства в sent, у которой с начала отправки прошло больше 10 минут, MUST считаться завершившейся временной ошибкой «отправка прервана» и обрабатываться по правилам повторов.
Scenario: Obsidian выгружен во время запроса
- WHEN очередь запускается в 14:30:00 и находит запись в
sentс началом отправки 14:19:00 - THEN запись учитывается как временная неудача и планируется к повтору
Scenario: Запрос ещё может идти
- WHEN очередь находит запись в
sentс началом отправки 2 минуты назад - THEN запись не трогается
Requirement: Принадлежность устройству
Каждая запись MUST хранить идентификатор создавшего её устройства. Автоматическая обработка очереди MUST брать только записи текущего устройства. Идентификатор устройства MUST NOT синхронизироваться между устройствами.
Scenario: Чужая запись не отправляется автоматически
- WHEN на маке запускается очередь, а в vault есть запись в
saved, созданная на iPhone - THEN мак не отправляет её
Requirement: Команда «Распознать все ожидающие здесь»
Команда MUST переназначить текущему устройству все записи других устройств в состояниях saved, retry и sent старше 10 минут, после чего обработать очередь без ожидания времени следующей попытки. Записи в failed и inserted, а также sent не старше 10 минут, MUST NOT затрагиваться.
Scenario: Мак забирает запись телефона
- WHEN на маке выполнена команда, а в vault есть запись iPhone в
retryсо следующей попыткой через 20 минут - THEN запись переназначена маку и отправлена сейчас
Requirement: Команда «Повторить распознавание»
Команда для записи в failed или retry MUST сбросить счётчик попыток в 0, перевести запись в saved и запустить очередь. Для записей в других состояниях команда MUST ничего не менять.
Scenario: Повтор после исправления ключа
- WHEN запись в
failedпосле 401, пользователь исправил ключ и выполнил команду - THEN запись отправлена заново со счётчиком попыток, начатым с нуля
Requirement: Удалённая заметка отменяет запись
Состояние записи живёт только во frontmatter её заметки; очередь находит записи по наличию ключа voice-id в любом месте vault, а не по папке. Если заметку удалили, запись MUST считаться отменённой, и её аудиофайл MUST NOT удаляться. Если заметку удалили во время запроса, пришедший результат MUST отбрасываться без ошибки и без воссоздания заметки.
Scenario: Заметка удалена во время запроса
- WHEN провайдер вернул текст после того, как заметку удалили
- THEN заметка не создана заново, очередь продолжает работу со следующей записью
Scenario: Заметка перенесена в другую папку
- WHEN пользователь перенёс заметку в
savedизvoice/inbox/вprojects/ - THEN запись по-прежнему обрабатывается очередью
Requirement: Повторный запуск очереди
Если запуск очереди запрошен, пока она уже работает, новый параллельный проход MUST NOT начинаться; вместо этого MUST выполниться ровно один дополнительный проход после завершения текущего.
Scenario: Три запроса во время работы
- WHEN очередь обрабатывает запись и за это время запуск запрошен три раза
- THEN после текущего прохода выполняется ровно один дополнительный проход