refactor(mcp): вывести DocmostClientLike/SharedToolSpec из реального типа — убить ручные зеркала (#446) #458
Reference in New Issue
Block a user
Delete Branch "refactor/446-derive-client-types"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Восстановленный отложенный долг #294 — второй линк Фазы 1б роадмапа #416. closes #446
@docmost/mcpне отдавал.d.ts, поэтому в сервере жили ТРИ дрейфующие ручные копии одних и тех же имён/сигнатур:DocmostClientLike(~230-строчное зеркало 48 методовDocmostClient), копия интерфейсаSharedToolSpec, и name-onlyHOST_CONTRACT_METHODS-тест. In-app execute-тела зовут клиент ПОЗИЦИОННО (client.drawioGet(pageId, node, format ?? 'xml')), так что перестановка/смена типа параметра вclient.tsдоезжала до прода рантайм-ошибкой без единого сигнала на компиляции.declarationMap) вpackages/mcp/tsconfig.json;types-экспорт вpackage.json(exports→ conditional{types, default}для.и./http).build/index.d.tsэмитится, реэкспортитDocmostClient+SharedToolSpec. Правок исходников пакета для эмита НЕ потребовалось (сборка чистая).DocmostClientLike→Pick<DocmostClient, 48 методов>изimport type(стёрт на компиляции, ESM/CJS-границу рантайма не задевает — класс по-прежнему грузится динамическимimport()); ручное зеркало удалено.SharedToolSpec→ type-only реэкспорт из пакета; ручная копия удалена.client-host-contract.test.mjsудалён — имена И сигнатуры теперь проверяет tsc.__assertClientCallContract(client: DocmostClientLike)воспроизводит каждый позиционный вызов с типизированными плейсхолдерами (AI-SDK стирает вход execute-замыканий вany, иначе позиционные вызовы не проверялись). Перестановка параметровclient.ts→ ошибка компиляции сервера ровно тут. Looseas-касты вai-chat-tools.serviceне потребовали правок;as anyне добавлялся.How verified
Стенд (с временным репойнтом symlink
@docmost/mcpна пакет этой ветки — в обычном single-checkout после мержа relative-symlink резолвит ветку сам, репойнт не нужен; symlink восстановлен):pnpm --filter @docmost/mcp build→ чисто,build/index.d.tsэмитится.node --test test/unit/* test/mock/*→ 611 pass (минус удалённый host-contract).tsc --noEmit -p apps/server→ без новых ошибок (единственная — предсуществующая@fastify/compress).jest shared-tool-specs.contract tool-tiers ai-chat-tools.service docmost-client.loader+comment-signal-inapp→ зелёные.grep "interface DocmostClientLike\|interface SharedToolSpec" apps/server/src→ пусто.client.ts→TS2345внутри__assertClientCallContract, затем revert.Внутренний цикл: 1 проход (APPROVE). Runtime resolution через conditional exports подтверждён разбором карты для прод-инсталляции (не только symlink):
require.resolve('@docmost/mcp')→default→build/index.js, затем dynamicimport()резолвленного пути минует exports. Покрытие__assertClientCallContractполное (48 call-sites == union == assert, сверено программно). Единственная находка —Promise<any>в части возвращаемых типов — предсуществующая вclient.ts, вне цели PR.Порядок мержа
Второй линк Фазы 1б, стоит на #447 (PR #457, закрытие skew build/vs/src — общие файлы
package.json/loader). Мержить ПОСЛЕ #447. Следующий линк #445 (execute-маппинг) встанет на #446. См. комментарий с порядком в #416.Checklist
@docmost/mcpв apps/serverРевью — #458
refactor(mcp): вывести DocmostClientLike/SharedToolSpec из реального типа — убить ручные зеркала (#446)Вердикт: PASS ✅
Полный веер из 9 аспектов по СОБСТВЕННОЙ дельте PR (застекан на уже-одобренный #457; ревьюил
2e6f1c3d..fe5b6ecd= 7 файлов, +217/−516 — без повторного ревью #457). Гейт зелёный в чистой комнате.Механизм — проверено адверсариально (клик)
Проблема (#446, восстановленный долг #294): in-app execute-замыкания зовут
DocmostClientпозиционно (client.drawioGet(pageId, node, format ?? 'xml')) с AI-SDK-стёртым (any) входом, а сервер держал ТРИ дрейфующих ручных зеркала (~230-строчныйDocmostClientLike, копиюSharedToolSpec, name-only runtime-тест). Перестановка/смена типа параметра вclient.tsдоезжала до прода рантайм-ошибкой без сигнала на компиляции.PR закрывает это деривацией из реального типа:
declaration:true+declarationMap(packages/mcp/tsconfig.json) +types/conditional-exports→ эмититсяbuild/index.d.ts.import type { DocmostClient, SharedToolSpec }(ПОЛНОСТЬЮ стёрт в compile — рантайм по-прежнему через dynamicimport()),DocmostClientLike = Pick<DocmostClient, DocmostClientMethod>, ре-экспортSharedToolSpecиз пакета.__assertClientCallContract(client)вai-chat-tools.service.tsреплеит КАЖДЫЙ позиционный вызов типизированными плейсхолдерами → дрейф сигнатуры становится серверной ошибкой компиляции.DocmostClient.getPage→ пересобрал.d.ts→ server tsc КРАСНЫЙ (TS2554: Expected 2 arguments, but got 1) и на реальном call-site, и в assert-функции; откатил → tsc снова 0. Гард реально ловит дрейф.Объективный гейт (клин-рум /home/claude/gm-ci, Node 22 = CI)
.d.tsэмитится зелёным (declaration:true; index.d.ts экспортит DocmostClient/SharedToolSpec/REGISTRY_STAMP) — без portability/private-name ошибок эмита деклараций.as-касты (второй блокер старого staged-плана снят — касты идут concrete→надмножество-looser, валидно).--runInBand).__assertClientCallContractс реальными execute-телами — совпадают; unionDocmostClientMethod— верное подмножество (корректно исключает login/ensureAuthenticated/uploadImage/updateComment/deleteComment).⛔ DROP — кодеру НЕ делать (лог калибровки)
[by-design]low[architecture] return-handlingas-касты остаются loose (Record<string,unknown>) — staged-план (c). Намеренно вне scope этого PR (цель = безопасность АРГУМЕНТОВ, не возвратов); tsc зелёный, касты валидны (concrete→subset). Не дефект.[below-threshold]low[simplification]__assertClientCallContract— новый рукотворный replay-список, держать в lockstep с execute-телами. Но: одно-направленный (было дву-направленное бремя HOST_CONTRACT_METHODS↔интерфейс), tsc стережёт дрейф сигнатур, строго сильнее старого name-only. Задокументировано.[below-threshold]low[conventions]declarationMap:trueэмитит.d.ts.mapв gitignored-build приватного пакета — безвредный шум (в рантайме инертен), можно опустить.DO/ESCALATE нет. Дельта убивает 3 дрейфующих зеркала (net −299 строк), заменяя их деривацией из реального типа + compile-time гардом, который адверсариально доказан (дрейф сигнатуры → красный server tsc). Все аспекты LGTM, гейт зелёный. Approve.
9 аспектов (security+stability+regressions, coherence+testcov, conv+docs+simp+arch — все LGTM). Ядро проверено адверсариально мутацией сигнатуры client.ts → server tsc RED; .d.ts-эмит и baseline-tsc зелёные; assert-fn полнота сверена с execute-телами. Escalate нет.