13 KiB
13 KiB
Context
Пакет C сверки follow-ups 25.10.2026 (sdlc-hub#129). Все правки — в Python-коде хаба, без смены схемы базы и контракта с агентами. Владелец пропустил вопросы brainstorming («не сталкивался и не хочу»): решения ниже приняты агентом и продублированы в «Decisions made without discussion».
Decisions
- Нормализация параметров — один шаг в
validate. Сразу после проверки типов значений (_type_error)validateзаменяет параметры результатомnormalize(ctype, params) -> dict(modules/jira/commands.py), и все дальнейшие проверки (обязательные параметры,_rule_error, временные ключи, повтор темы) идут по нормализованным значениям. Иначеepic_key: " new:1 "прошёл бы как постоянный ключ и стал временным уже после проверки объявления, а ключ из пробелов прошёл бы проверку на пустоту. Обязательный параметр, ставший пустым, даёт ту же ошибку «нет <параметр>», пустой элемент списка — «<параметр> — без пустых элементов». Существующие.strip()в правилах остаются, они безвредны. Обрезаются строковые параметры всех типов, кроме текстовыхsummary,description,body,goal. Это ключи,temp_key,sprint,rename_to,move_open_to,link_type,assignee,priority, даты,marker,attachment_id,file,filename,issue_type,target(строка или элементы списка). Элементы списковlabels,labels_add,labels_remove,keysтоже обрезаются. Числа, логические значения иnullне меняются. Нормализация возвращает новый словарь и не меняет вход. filename— NFC.normalizeприводитfilenameуensure_attachmentкunicodedata.normalize("NFC", …). MCP подставляет имя по умолчанию изPath(path).nameи тоже приводит его к NFC; на macOS имена из пути приходят в NFD. Поиск вложения по имени в MCP-инструментеattachmentсравнивает имена в NFC с обеих сторон.- Проверки
hooks.check_batchидут по нормализованным параметрам.validateвызывается раньше, поэтомуfilenameс пробелами по краям проверяется уже обрезанным. - ASCII-цифры. В
core/http.py(рядом сint_group) появляется функцияis_digits(s) -> bool:s— непустая строка только из0-9(s.isascii() and s.isdigit()). Модуль jira использует её вместоstr.isdigit()везде, где за проверкой идётint()или где строка — идентификатор:hooks.py(attachment_id),mirror.py(check_attachments),attachments.py(сортировка),fetch.py(версия агента),mcp.py(ссылка#<id>). В разборе датhistory.pyиmcp.pyзначение уже прошло форматную проверку, но для единообразия там тожеis_digits. - Форма элементов состояния.
mirror.check_issues(issues)вызывается доcheck_commentsиcheck_attachments. Она требует:issues— список (иначе сейчас 500 на итерации), каждый элемент — объект с непустым строковымkey. ИначеValueErrorс номером элемента. ОбработчикPOST /agent/stateуже превращаетValueErrorв 422bad_state, как для комментариев. POST /wiki/sync. Еслиprojectв теле задан и это не строка, ответ 422bad_request«project — строка» до обращения к проектам.- Файлы хранилища.
files.sizeиfiles.readловятOSError.FileNotFoundErrorпо-прежнему тихо даётNone. Остальные ошибки тоже даютNone, но пишут в лог предупреждение с путём и ошибкой. Дальше всё идёт по существующему пути «файла нет»:size = nullпри claim, 404 при скачивании. SDLC_HUB_HOME="".paths.home()берётos.environ.get("SDLC_HUB_HOME") or <по умолчанию>. Пустая строка равна незаданной переменной;home_problem/home_warningуже считают так.- Восстановление прерванной публикации. В начале
release.publish, до проверки версии, вызывается_recover(rd). Еслиagent.py.prevесть, аagent.pyнет или его sha256 не совпадает с манифестом, тогда как sha256agent.py.prevсовпадает,agent.py.prevвозвращается на местоagent.pyс предупреждением в лог. Еслиagent.pyсовпадает с манифестом, а.prevлежит,.prevудаляется. Во всех остальных случаях ничего не трогается, а в лог пишется предупреждение. Так закрываются T2.2 и M-6: после_recoverвagent.pyлежит опубликованная версия, иos.replace(agent, prev)не затирает годный.prev. - Устаревшие ключи настроек.
base_jqlиboard_idдобавляются вmodules/jira/settings.OBSOLETE.ensure_defaultsудаляет их при каждом открытии, не меняяsettings_version, какstatus_labels. Запись черезPUT /settingsпо-прежнему даёт 422 с пояснением. Агентуboard_idдавно не передаётся: его нет вAGENT_KEYS.
Risks / Trade-offs
- Нормализация меняет то, что раньше уходило агенту дословно. Пробелы по краям идентификатора в Jira не значимы, а текстовые поля не трогаются.
_recoverсрабатывает только при совпадении sha256 с манифестом, поэтому неизвестное содержимое не перезаписывается.- Удаление
board_idизsettingнеобратимо, но значение нигде не читается. Копия базы есть вbackups/.
Decisions made without discussion
- R-1 (proposal, 26.10.2026): O-3 (
check_confirmedсчитает обратную связь симметричного типа конфликтом) не входит в пакет и переносится в пакет B. Хаб не знает, какие типы симметричны: это знает агент по/issueLinkType. Ручная настройка списка типов в хабе отвергнута, она дублировала бы Jira. Цена ошибки: при потерянном отчёте командаensure_linkк «связанной» задаче, связанной в обратную сторону, подтверждается не по состоянию, а довыдачей агенту, и агент отвечает existed. Лишний вызов Jira, данные не страдают. - R-2 (design #1): обрезаются все строковые параметры, кроме
summary,description,body,goal. Обоснование: пробелы по краям идентификатора всегда ошибка ввода, а в тексте могут быть значимы. Цена ошибки: тема с пробелами по краям уходит в Jira как есть, Jira обрезает её сама. - R-3 (design #2): имя вложения приводится к NFC, а не к NFD. NFC — форма, в которой имена приходят из Jira и набираются с клавиатуры. Цена ошибки: имя вложения в Jira будет в NFC, визуально то же самое.
- R-4 (design #7): ошибка доступа к файлу хранилища трактуется как «файла нет», а не как отдельная ошибка API. Обоснование: путь «файла нет» уже понятен агенту и клиентам. Цена ошибки: причина видна только в логе хаба.
- R-5 (design #9):
_recoverчинит только состояния, однозначно определяемые по sha256 манифеста. Остальные оставляет как есть с предупреждением в лог. Цена ошибки: в редком сочетании сбоев владелец публикует ещё раз. - R-6 (design #10):
base_jqlиboard_idудаляются через механизм устаревших ключей, без новой версии схемы. Это тот же путь, что уstatus_labels, и при каждом старте ключи, вставленные старым процессом, убираются снова. Цена ошибки: нет. - R-7 (design #7, 26.10.2026):
files.sizeдля каталога на месте файла возвращаетNone(проверкаS_ISREG), а не размер каталога. План предписывал толькоexcept OSError, ноPath.stat()на каталоге не бросает исключение, и claim отдал бы агенту размер каталога. Решение согласовано сfiles.exists(is_file). Цена ошибки: нет, поведение совпадает с целью design #7 («файла нет»);readдля каталога пишет предупреждение,sizeмолчит (T4.1). - R-8 (design #1, 26.10.2026; финальное ревью I-1): необязательный параметр
ensure_fields(поляFIELD_PARAMS:assignee,epic_key,priority, даты, оценки), заданный строкой из одних пробелов, отвергается с 422 «<параметр> — из одних пробелов; для очистки — "" или null». Иначе обрезка превращала бы его в"", а агент читает""как «очистить поле в Jira». Дляbefore/after/move_open_toпробелы по-прежнему означают «не задано». Цена ошибки: ввод из одних пробелов отвергается хабом, а не Jira; очистка полей работает как раньше через""илиnull. - R-9 (design #7, уточняет R-7; финальное ревью I-3):
files.sizeоткрывает файл и берёт размер черезfstat,_intactловитOSError. Причина:Path.stat()не требует права на чтение, иchmod 0давал настоящий размер приread→None; повторная загрузка того же файла давала 500. Теперьsizeдля нечитаемого файла и каталога —Noneс предупреждением,putперезаписывает нечитаемый файл. Цена ошибки: лишнее открытие файла при claim. - R-10 (design #10; финальное ревью M-3, T6.2): на базе v7 с ключами v3
migrate_v7удаляетbase_jqlиboard_idи поднимаетsettings_version; неразобранныйbase_jqlтеряется вместе с ключом (значение остаётся в предупрежденииmigrate_v3и вbackups/schema-v3). Цена ошибки: потеря неразобранного значения, которое нигде не читается.