AI-EDT v0.2.30
Выпуск исправлений к 0.2.29: ревизия протокола в ответе, сторож ветки, жизненный цикл задания, заявка на информационную базу и занятие порта.
- Запрос, объявивший в
params._metaревизию 2025-11-25 или старше, получает форму ЭТОЙ ревизии, а не 2026-07-28 initializeбольше не соглашается на 2026-07-28 - ревизию, в которой рукопожатия не существуетProtocolEraразличает версию неверного типа,clientCapabilitiesневерного типа и неизвестную версию; неизвестная версия проверяется раньше отсутствующего поля- Сторож ветки читает привязку проекта, названного вызовом: у расширения она лежит в самом расширении и прежде игнорировалась полностью
- Нечитаемый файл привязок и неопределимая ветка при наличии привязок теперь отклоняют обновление, а не пропускают его
- Задание подписывается на завершение работы: снят захват результата первым опросом, слитые вызовы получают общее задание, заявленный TTL перестал расходиться с реестром
- Результат задания проходит лимит ответа и маскирование персональных данных
- Заявка межпроцессной блокировки публикуется атомарно и освобождается только своим владельцем
- Порт считается свободным только когда свободен адрес, на который ходят клиенты
server/discoverсообщает, кто ответил- У
call_hierarchyнеполный обход теперь называет себя неполным, а лимит тратится на различных вызывающих get_project_errors: баннер переполнения не появляется, когда вернулось всё, что нашлось
Две Critical: правило читалось не из того списка
Форма ответа выбиралась по списку поддерживаемых ревизий. Поле io.modelcontextprotocol/protocolVersion объявляет ревизию, которую использует ЭТОТ запрос. Ответ с resultType и _meta.serverInfo на запрос, назвавший 2025-11-25, - это тело, которого названная ревизия не определяет. Один список отвечал на два разных вопроса: какие ревизии обслуживаются и какие определяют эту форму.
Списка теперь два. SUPPORTED_PROTOCOL_VERSIONS - кого обслуживаем, MODERN_PROTOCOL_VERSIONS - кто получает форму 2026-07-28. Названная старая ревизия обслуживается своей формой, а не отказом: она поддерживается, просто просили не эту. То же разделение убрало вторую нелепость - HANDSHAKE_PROTOCOL_VERSIONS не содержит 2026-07-28, поэтому initialize больше не соглашается на ревизию, в которой сам не существует.
Инвариант совместимости при этом не пострадал и проверен заново: клиент без _meta получает те же 44 инструмента без единого нового поля.
Сторож ветки читал привязку не того проекта. У расширения нет своей информационной базы, поэтому обновление маршрутизируется в конфигурацию, которую оно расширяет, - и сторож читал привязки ЕЁ проекта. Но разработчик работает в расширении, на ветке расширения, и привязку задаёт там же. Её можно было создать, получить подтверждение и не получить никакого эффекта.
Сначала проверяется проект, названный вызовом, затем владелец базы.
Задания: три дефекта из одного решения
Задание забирало результат на том опросе, который первым заставал работу завершённой. Отсюда следовали три разных отказа:
- результат ИЗЫМАЛСЯ, поэтому второе задание над тем же прогоном - а это происходит при каждом слиянии одинаковых вызовов - не находило ничего и сообщало, что прогон исчез;
- два одновременных опроса проходили проверку «ещё не терминальное», и проигравший переписывал ответ победителя отказом;
- реестр удаляет невостребованный результат через пять минут, а задание объявляло тридцать.
Задание теперь подписывается на завершение работы (future.whenComplete), и ответ копируется в него в момент готовности - независимо от того, опрашивает его кто-нибудь или нет. Плюс одно задание на прогон и статус, который во всех переходах пишется последним.
Отдельно: результат задания уходил мимо лимита ответа и мимо маскирования персональных данных. Применимость настройки приватности не может зависеть от того, сколько работал инструмент.
Межпроцессная блокировка
CREATE_NEW атомарен относительно СУЩЕСТВОВАНИЯ файла, но не относительно его содержимого: читатель, пришедший между созданием и записью, видел пустой файл, считал его испорченным, удалял и брал блокировку себе. Заявка теперь пишется во временный файл и переносится Files.move без замены - атомарно и по существованию, и по содержимому.
close() проверяет, что заявка всё ещё своя: последовательность «освободили - взял другой - закрылись повторно» удаляла чужую. Ошибка удаления больше не проглатывается: пока pid жив, такая заявка читается всеми как удерживаемая, и это единственная неудача здесь, требующая человека.
Ключ базы канонизируется (toAbsolutePath().normalize()), а регистр складывается только на файловых системах, которые его складывают: на Linux /data/Base и /data/base - две разные базы.
Порты
Порт считался свободным, если привязался хоть один адрес. При занятом IPv4 и свободном IPv6 это означало, что сервер объявлял порт своим, а 127.0.0.1:порт принадлежал другому процессу - то есть все клиенты попадали не туда. Первый адрес (тот, куда ходят клиенты) обязан привязаться; остальные семейства - по возможности.
Верхняя граница диапазона считается в long и ограничивается 65535, слушатель регистрируется для очистки сразу после create().
Что подтверждено, но не в этом выпуске
Двенадцать находок вынесены отдельной работой, и у каждой названа причина: TOCTOU при удалении устаревшей заявки требует смены модели владения на FileLock; удержание блокировки до конца обновления требует отслеживания состояния на стороне EDT; типизированный контракт Pending затрагивает шесть производителей и собственный реестр YAxUnit; счёт маркеров, включающий Eclipse-маркеры, требует общего дедуплицированного прохода. Полный список - в теле выпуска на GitHub нет места, он в рабочих заметках.
Что отклонено
Сортировка каталога инструментов не могла сломать «прежний» порядок для старого клиента: до неё порядок был порядком обхода хеш-таблицы, то есть нестабильным между вызовами.
Диапазон портов по умолчанию не ломает установки с жёстко прописанным URL клиента: без диапазона сервер при занятом порте не стартовал вовсе, и клиент был неработоспособен в любом случае.
Проверено на стенде на этой сборке
Запрос с 2025-11-25 в _meta - 44 инструмента, ни resultType, ни _meta, ни ttlMs; запрос с 2026-07-28 - полная новая форма; initialize с 2026-07-28 отвечает 2025-11-25, с 2025-06-18 отвечает 2025-06-18; server/discover называет имя, версию и рабочее пространство; неизвестная версия без capabilities даёт -32022 (версия проверяется раньше поля), clientCapabilities неверного типа даёт -32602 с именем поля; клиент без метаинформации - те же 44 инструмента без новых полей.
Сборка: 1591 тест, 0 отказов.