v1.19.0
Добавлено
- Единый граф 1С-потока управления (шаг 3 роадмапа call-graph). Метод вызывается не только из кода: его дёргают подписки на события, обработчики форм, регламентные задания и CFE-перехваты расширений. Новый ридер
IndexReader.get_inbound_edges(proc_name, module_hint="")подмешивает кdirect-рёбрам (calls) 4 источника не-call рёбер —event_subscriptions, обработчикиform_elements(kind='handler'),scheduled_jobs,extension_overrides(CFE) — разрешённые через то же пространство ключейcallee_key. Контракт: всегдаlist[dict](НИКОГДАNone), каждое ребро{edge_type, source_name, source_kind, detail, file, line, caller_name, object_name, category, target_key, resolved}. Зеркалит дизайнget_callers:resolved=True⇔ матч по стабильномуtarget_key,resolved=False⇔ recall-матч по имени (extension-onlysource_path=="", нерезолвимый/dotlesshandler_module) — рёбра не теряются. Кириллический регистр сворачивается в Python.casefold()(неCOLLATE NOCASE, который сворачивает только ASCII). find_call_hierarchy(..., include_triggers=False)— opt-in аннотация триггеров. ПриTrueк каждому узлу дерева добавляется ключtriggers(=get_inbound_edgesдля метода узла). По умолчанию (False) вывод байт-в-байт прежний — ключtriggersдобавляется ТОЛЬКО под флагом. Триггеры — листовая аннотация, НЕ новые цели BFS (подписка/задание/форма/перехват — точка входа, а не «caller»).find_path(from_name, to_name, max_depth=4, from_hint="", to_hint="", include_triggers=False)— достижимость по графу ВЫЗОВОВ. Индексный реверс-BFS callers отto_name(форвард-callees = full scan, индекс снят). Возвращает forward-путь[from → … → to];call_lineэлемента — строка РЕБРА к следующему узлу (НЕ определения; у терминальногоto—None)._meta.precision='exact'⇔toразрешён точно И все рёбра пути поcallee_key; иначе'heuristic'(старый индекс / FS-fallback / совпадение по имени) —found=True= достижимость по имени, не доказанный путь.from_hint/to_hintпинят одноимённые методы к модулю.find_data_path(from_object, to_object, max_depth=4, kinds=None)— N-hop BFS по графу метаданных (metadata_referencesчерез новый ридерIndexReader.find_metadata_refs_from). Каждый элемент пути — РЕБРО{from, to, kind, used_in, path, line}. Контракт: endpoints с префиксом (Справочник.X/Catalog.X,Документ.Y/Document.Y); bare-вход без префикса → структурный hint без обхода.source_categoryразличает одноимённыеCatalog.XvsDocument.X. Отдельный скромный бюджет узлов (~400), т.к. каждый узел = одинpy_lower-скан таблицы.- 2 бизнес-домена в
rlm_help:достижимость(алиасыreachability,путь вызовов,доходит ли) ипуть данных(алиасыdata path,как связаны,граф данных); оба хелпера автоматически попали в slim/full-индекс хелперов.
Изменено
rlm_start— убран ~30К inline-дамп перехватов изextension_context(токен-экономия на extension-конфигах). Полеnearby_extensions[].overrides(полный список перехватов каждого расширения, ~30К на крупном ERP-конфиге с расширениями ~200 перехватов) заменено наoverrides_count;own_overrides→own_overrides_count. Дамп был шумом: дублировалget_overrides()/find_ext_overrides(), не был actionable из песочницы (это текст тул-ответа, не переменная), не использовался агентами (e2e: все перезапрашивали черезget_overrides), и его платила КАЖДАЯ сессия extension-конфига (включая не связанные с расширениями). Полезный сигнал (присутствие + имена/purpose/prefix + счётчик) сохранён; детали — по запросуget_overrides('Объект'). Бойлерплейт-саммари в стратегии («CRITICAL EXTENSIONS DETECTED», ограниченный по строкам) не тронут. Для обычных src без cferlm_startи так был ~20–22К — там ничего не менялось.IndexReader.get_callers— единственное аддитивное полеedge_exactв caller-rows (per-edge признак: ребро матчнулось поcallee_key, не по имени). Нужноfind_pathдля честногоprecision(агрегатexact_rows/fallback_rowsне несёт per-row признак). Поле новое; существующие потребители (find_call_hierarchy) его игнорируют,_meta-контракт не тронут.- Новый публичный
IndexReader.resolve_target_identity(proc_name, module_hint="")— locked-обёртка над внутренним lockless_resolve_target_key(его штатно зовётget_callers/get_inbound_edgesуже подself._lock, а этоthreading.Lock, неRLock).find_path(вне reader-лока) дёргает только её.
Совместимость
- Схема индекса и
BUILDER_VERSION(14) НЕ менялись — перестройка боевых индексов НЕ требуется. Оба новых ридера только ЧИТАЮТ уже существующие таблицы (event_subscriptions/scheduled_jobs/form_elements/extension_overrides/modules— стандартные;metadata_references— с v1.9.0;calls.callee_key— с v1.16.0). Любой индекс v1.16.0+ уже прошёл разовый v14-ребилд → ноль действий. Старые индексы (BUILDER_VERSION<14) — мягкая деградация без падений до того же v14-ребилда, что уже ввела v1.16.0 (не новое требование):get_inbound_edges→[],find_metadata_refs_from→None,find_data_path→partial:True,find_pathработает по name-based callers сprecision="heuristic". find_call_hierarchy(...)без нового аргумента иget_callers(кроме аддитивногоedge_exact) — байт-в-байт прежнее поведение.
Исправлено (контракты хелперов, найдено e2e-прогоном; в духе v1.18.0)
safe_grep/find_callers— sig врала про имя параметра. Регистрируемые сигнатуры называли аргументhint, фактические параметры —name_hint(safe_grep) иmodule_hint(find_callers) → агент, вызвавший по документацииsafe_grep(p, hint=...)/find_callers(proc, hint=...), ловилTypeError. Обе sig исправлены (последние два таких рассогласования — аудит остальных sig чист).- Бизнес-рецепт
расширения— неверный контрактget_overrides().code_hintитерировалget_overrides()как список с ключомextension, тогда как хелпер возвращает{overrides:[...], total, source}, а ключ перехвата —extension_name→TypeError/KeyError. Рецепт переписан под фактический контракт (проверено живым вызовом: dict с 199 перехватами, ключextension_name). get_overrides()live — нормализацияextension_nameво ВСЕХ ветках. В live-ветке для сессии, открытой прямо на расширении (role=EXTENSION), сырые строкиfind_extension_overridesне неслиextension_name/extension_root(в отличие от index-строк и live-ветки main-сессии) → тот же рецепт снова падалKeyErrorв этой допустимой ветке контракта (подтверждено живым вызовом: 195 перехватов безextension_name). Теперь поля проставляются из идентичности текущего расширения — контракт «у каждого перехвата естьextension_name» честен для всех источников. Регрессия закрыта тестомTestLiveOverridesContract.find_roles/find_register_movements— недоспецифицированные sig.find_roles(...).roles[].rights— этоlist[str](имена прав), не dict (агент звал.items()); уfind_register_movementsтолькоcode_registers— список словарей, аerp_mechanisms/manager_tables/adapted_registers— списки строк (агент звал.get()на строке). Формы элементов прописаны в sig (проверено живым вызовом).find_module— sig теперь явно говорит, что принимает толькоname(module_type/category— поля вывода, не фильтры), убирая соблазнfind_module(module_type=...).analyze_document_flow— sig раскрывает форму ({document, metadata, event_subscriptions, register_movements, …}— это dict с конкретными ключами; вложенныйregister_movements— тоже dict), вместо прежнего расплывчатогоmetadata + subscriptions + …, на котором агент пробовал срез[:N]по dict.- Толерантные/самокорректирующиеся контракты (e2e-урок: doc-ремарки не останавливают угадывание). Повторный прогон показал, что чисто-документационные правки
find_module/analyze_document_flowагент игнорирует (зовёт до чтения sig), поэтому:find_module(name='', module_type='', category='')—nameопционален + опциональные фильтры (case-insensitive, до cap'а 50). Раньшеfind_module(module_type=...)падалTypeError(рекуррентная грабля) — причём в ДВУХ формах: с именем (unexpected keyword argument) и фильтр-только без имени (missing positional argument 'name'). Теперь работают обе: пустоеname= «любой модуль», сужённый фильтрами.- Перф
get_inbound_edges— устранён O(N) над метаданными НА КАЖДЫЙ узелinclude_triggers-дерева. e2e на ERP вскрылfind_call_hierarchy(include_triggers=True)~250с на 60-узловом дереве. Прямой замер (get_inbound_edges= ~4с/вызов, а caller-BFS 60 узлов = 0.04с) выявил ДВА источника:- (доминанта) subscription/scheduled forward-JOIN. Запрос джойнил ВСЕ подписки к
CommonModulesчерезpy_lower(object_name)=py_lower(handler_module)(UDF по тысячам модулей на каждую подписку), а фильтр поhandler_procedureшёл в Python. Фикс: имяhandler_procedureсведено в SQLWHERE … py_lower(handler_procedure)=py_lower(?)→ джойнятся только 0–несколько совпавших подписок. ~4с → миллисекунды. - (вторично) form_event recall. Recall-ветка делала полный скан
form_elementsна каждый узел; теперь: не-форменная цель → скан пропущен (форменный обработчик там невозможен), неразрешённая → сужениеpy_lower(handler)=py_lower(?)в SQL.
Корректность сохранена (resolved по индексу, recall по имени; Cyrillic-aware). Покрытоtest_form_recall_skipped_for_nonform_target+ существующими subscription/scheduled/recall/casefold-тестами. Read-only логика —BUILDER_VERSIONи пересборка индексов не затронуты.
- (доминанта) subscription/scheduled forward-JOIN. Запрос джойнил ВСЕ подписки к
- Sandbox error-hints (
_add_error_hints) на типовые угадывания: неверный kwargsafe_grep(path=/hint=)→ подсказка проname_hint; срез dict как списка (KeyError: slice(...)наanalyze_document_flow/get_overrides/find_register_movements/find_path/find_data_path) → подсказка взять список по ключу;.get()на результатеread_procedure→ подсказка, что это строка-тело, а не dict;detect_extensions()использован как список (итерация/[0]) → подсказка, что это dict, а расширения — вctx['nearby_extensions'](рекуррентное телепание агентов, sig/рецепт при этом корректны). Хард-ошибка → самокоррекция, без потери раунда на угадывание.
Тесты
- Новые
tests/test_call_graph_edges.py(13) — все 4 типа рёбер, resolved/recall (extension-onlysource_path=="", нерезолвимый и dotlesshandler_module), кириллица-casefold, no-op на индексе без таблиц,include_triggerson/off;tests/test_find_path.py(9) — forward-цепочка,call_lineкак метаданные ребра,precisionexact/heuristic, from_hint-disambig,budget_exceeded, аддитивныйedge_exact,_regsig/recipe. Расширенtests/test_find_references.py—find_metadata_refs_fromoutbound + коллизия категорий +Noneбез таблицы;find_data_path1-/2-hop + RU/EN +kinds-фильтр + коллизия категорий + контракт префикса +partialбез таблицы.
Полный список изменений: CHANGELOG.md