dsh-qa-integrations
Đã xác minh@yadsh/dsh-qa-integrations · v0.10.3 · MIT · Giao diện web
Principal-scoped, encrypted user integrations for DSH QA Surface
Cài đặt
dsh plugin add @yadsh/dsh-qa-integrations Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.
Mã nguồn
Phát hành lên npm mà không có repository công khai. Hãy kiểm tra nội dung package trước khi cài.
Thẻ
Readme
dsh-qa-integrations
Персональные интеграции для @yadsh/dsh-qa-surface. Плагин подключает Bitrix24 через URL входящего вебхука, GitLab через personal access token, TeamCity через access token, Jira и Confluence — через API-токен Atlassian, Test IT — через API-токен системы управления тестированием, а Weblate — через API-токен платформы локализации, после чего даёт агенту набор read-only инструментов, ограниченный и правами подключения, и персональной политикой пользователя; единственное исключение — комментарий в таймлайн Bitrix24, который монтируется только явным флагом оператора. У всех семи интеграций подключение может работать не только от личного токена, но и от сервисного токена развёртывания — общего read-only аккаунта с жёстким потолком режима (раздел «Сервисные токены»).
Плагин показывает подключения двумя монтированиями одного и того же набора карточек: отдельным разделом «Интеграции» в пользовательских настройках QA и секцией конфигурации самого плагина на панели «Плагины» оригинального DSH (слот plugins.bundle.config, ключ — имя пакета). Панель рисуется клиентом и не читает loopback-only каталог Host settings: в установленном @deepseek-ai/dsh-client-ui-plugin-manager 0.1.7-rc.2 ветки isLoopback нет вовсе, а единственный loopback-выбор на стороне настроек выбирает режим записи — host или memory — и не зависит от того, на каком слоте висит карточка. Поэтому секция доступна и в LAN-браузере, где тот каталог намеренно отключён. Слот выбран по AGENTS.md («Choosing the registration point»): карточка конфигурации плагина сидит на панели «Плагины», а не в диалоге настроек. Оболочку карточка не рисует: на ряду панели «Плагины» страницу, заголовок строки, id строки и описание выдаёт хост, а карточка монтирует только тело — own shell остаётся за settings.section и settings.plugins.tab (решение владельца от 01.10, влитое в контракт #684). Кольцо фокуса строится из хозяйских токенов --dsw-focus-ring-width / --dsw-focus-ring-color с fallback у каждой половины. Клик-обход живого не-loopback стенда — пункт приёмки переноса. Она читает аккаунт через клиентский сервис qaUserSession плагина @yadsh/dsh-qa-surface: без входа в QA ничего не показывает и не делает запросов. Токен вводится один раз, шифруется на Host и никогда не возвращается браузеру. Выбор пользователя, integration id, secret id или токена отсутствует в model-visible схемах: principal берётся из DSH-сессии, а Bitrix user id — из сохранённой записи интеграции.
Операторская карточка настроек
Кроме двух пользовательских монтирований, плагин рисует операторскую карточку по своей конфигурации — секцией конфигурации своей строки профиля на панели «Плагины» (слот plugins.row.config, ключ @yadsh/dsh-qa-integrations#qa-integrations), под секцией аккаунта. На хосте 0.1.7 роль settings namespace играет id строки профиля (qa-integrations), а доступными для браузера становятся те поля, чей узел схемы помечен .volatile(); карточка берёт форму этого входа через ctx.configForms и появляется вместе с рядом, не дожидаясь Remote-описания развёртывания. Ключ нового места собран из того же id строки и имени пакета, поэтому переехало только место рендера: значение, сохранённое до переноса, читается после. Обе карточки живут на панели «Плагины», куда не нужен loopback-only каталог Host settings, и каждая монтирует только тело — рамку, заголовок и раскрытие рядом рисует сама страница плагина. Операторская карточка — третья поверхность того же бандла, и она не требует ни входа в QA, ни Remote-описания развёртывания: оператор включается и настраивает плагин из этой карточки, даже когда плагин ещё выключен.
Карточка читается сверху вниз: у каждой секции в свёрнутом виде строка состояния (включён ли провайдер, сколько подключений настроено, сколько возможностей открыто), а внутри — четыре подписанных блока: «Провайдер» (выключатели провайдера), «Подключение» (инстансы, сайты, адреса и сетевые политики во всю ширину), «Что доступно агенту» (чек-лист возможностей, флажок перед подписью) и свёрнутые «Ограничения и повторы» (потолки, таймауты, повторы при ошибках). Подпись поля связана с самим полем: клик по названию ставит курсор в него. Переключатель, до которого сервисный токен развёртывания не дотягивается целиком или частично, говорит об этом прямо в чек-листе («с сервисным токеном недоступно: требуется личный аккаунт»): сам по себе включённый флажок ничего оператору не объясняет, а пользователь с отказом читает его как сломанную интеграцию. Заметка появляется только при включённых сервисных доступах и снимается самим переключателем по его пути в конфигурации: копии имени флага в карточке нет, опечатать в ней нечего. tests/client/operator-service-reach.test.ts сверяет таблицу заметок с классификацией каждого каталога, а tests/client/operator-card.test.tsx считает заметки в отрисованной карточке — карточка не сможет разойтись с тем, что провайдер на самом деле разрешает общему аккаунту.
Карточка покрывает всю разрешённую резолверами конфигурацию: общие ручки (enabled, таймаут, потолки ответа и аудита, пути хранилища и мастер-ключа, суффиксы порталов Bitrix24), у каждого провайдера — выключатель, возможности, инстансы/сайты (строки id/название/адрес) и лимиты, у TeamCity — адрес сервера и сетевую политику, у Jira — псевдонимы полей, плюс сервисные доступы (профили с ресурсными границами и deny-политикой) и замены встроенных подсказок получения токена (credentialHelp). Половины GitLab CI — ciMetadataRead и ciLogsRead — это в карточке два переключателя, а не один ciRead: резолвер отвечает половинами отдельно и складывает в них старое ciRead, только пока ни одна из них не названа явно. Единая ручка этого состояния выразить не могла: половина, выключенная отдельно от другой, читалась «включено», а правка по старому имени переставала действовать, как только вторая половина названа. Поэтому карточка читает каждую половину в том же порядке, что и резолвер, — половина ?? ciRead ?? значение по умолчанию. Каждое поле помечает, что пользовательский слой namespace переопределяет композиционную строку профиля, одна кнопка очищает слой целиком. Хост проверяет правку по схеме и отвергает то, что схема не позволяет; кросс-полевые ограничения (политика хостов TeamCity, дубликат id в списке инстансов) схема выразить не может, и их держат резолверы: значение, которое они не принимают, остаётся в документе профиля, а запущенный сервис сохраняет прежнее состояние и пишет config.rejected в лог.
Правка применяется к запущенному сервису сразу: Loader коммитит её в volatile-ссылки самой строки и сообщает об этом её фибре (loader/volatile-update), брокер перепривязывается к свежему набору провайдеров, а монтирование инструментов следует за выключателем плагина и единственным флагом записи. Хранилище подключений и мастер-ключ — сознательное исключение: подключения и зашифрованные секреты живут по путям загрузки, поэтому их правка предупреждает в логе и вступает в силу при следующем рестарте Host. Без отдаваемого namespace карточка объясняет это одной строкой вместо пустой секции, а плагин грузится от своей композиционной строки — настройки профиля ему не нужны, чтобы работать.
Структура
Один каталог — одна интеграция. Всё, что знает про Bitrix24, GitLab, TeamCity, Jira, Confluence, Test IT и Weblate, лежит в своём каталоге src/providers/<id>/: каталог возможностей и операций, построение запросов, HTTP-граница, срез конфига и тулы. У TeamCity отдельно лежат locator-синтаксис, обработка логов, политика артефактов и сеть; у Jira — JQL и ADF; у Confluence — CQL и ADF; у Test IT — политика видов вложений и бюджет их чтения; у Weblate — сборка поискового синтаксиса q. Общий слой — брокер, репозиторий, секреты, реестр сервисных токенов (src/service-credentials/), обвязка тулов — не знает ни одной интеграции по имени: capability это свободная строка, а providers/contract.ts описывает, что должен уметь провайдер. Порядок добавления следующей интеграции и правила гейта пакета — в src/providers/README.md.
Инструменты
CRM:
| Tool | Что делает |
|---|---|
bitrix_search_crm |
Поиск лидов, сделок, контактов, компаний, счетов и смарт-процессов: по названию, стадии, воронке, свежести, открытости (только сделки), ответственный; порядок сортировки зафиксирован, чтобы страницы не дублировались |
bitrix_get_crm_item |
Полная карточка элемента CRM, включая пользовательские поля и коммуникации |
bitrix_get_crm_fields |
Схема полей типа сущности, включая UF_CRM_* портала |
bitrix_get_crm_funnels |
Воронки (категории) типа сущности |
bitrix_get_crm_statuses |
Расшифровка стадий и справочников (STATUS, DEAL_STAGE, DEAL_STAGE_<id>, SOURCE, …) |
bitrix_get_crm_activities |
Дела CRM: звонки, встречи, письма, задачи; просрочки и незавершённые |
bitrix_get_crm_activity |
Одно дело целиком, включая описание |
bitrix_get_crm_timeline |
Комментарии таймлайна лида, сделки, контакта, компании |
bitrix_get_crm_stage_history |
История движения по стадиям: сколько висело, куда возвращали |
bitrix_get_crm_product_rows |
Товарные позиции: что продаём, количество, цена, скидки |
bitrix_find_crm_duplicates |
Поиск дублей клиента по телефону или e-mail |
bitrix_get_crm_requisites |
Реквизиты контакта или компании: ИНН, КПП, адрес, банк |
bitrix_get_call_transcript |
Готовая AI-расшифровка звонка по делу CRM |
Сотрудники и структура:
| Tool | Что делает |
|---|---|
bitrix_get_current_user |
Чей вебхук подключён |
bitrix_search_users |
Поиск сотрудников по имени, e-mail, подразделению |
bitrix_get_departments |
Подразделения, родительский отдел, руководитель |
bitrix_get_user_fields |
Какие поля сотрудника доступны с этим вебхуком |
Чаты и открытые линии:
| Tool | Что делает |
|---|---|
bitrix_search_chats |
Поиск чатов по названию и участникам |
bitrix_get_chat_messages |
Последние сообщения чата |
bitrix_search_chat_messages |
Поиск по тексту и датам внутри одного чата |
bitrix_get_recent_chats |
Последние диалоги пользователя, счётчики непрочитанного |
bitrix_search_chat_users |
Поиск сотрудника как контакта чата: статус, телефоны |
bitrix_find_chat |
Чат, привязанный к объекту: обсуждение сделки, чат задачи, событие календаря, чат группы |
bitrix_get_chat_participants |
Кто участвует в чате |
bitrix_get_chat_user_data |
Профили участников: имя, должность, телефоны, присутствие |
bitrix_get_openline_dialog |
Диалог открытой линии: участники, связь с CRM |
bitrix_get_openline_history |
История переписки с клиентом в открытой линии |
Задачи, календарь, Диск:
| Tool | Что делает |
|---|---|
bitrix_search_tasks |
Задачи по названию, ответственному, группе, дедлайну |
bitrix_get_task |
Карточка задачи целиком, включая ufCrmTask |
bitrix_get_task_history |
История изменений задачи: что, когда и кем |
bitrix_get_task_results |
Результаты работы по задаче |
bitrix_get_task_elapsed_time |
Затраченное время: кто сколько залогировал |
bitrix_get_calendar_events |
События календаря сотрудника, группы или компании |
bitrix_get_calendar_accessibility |
Занятость сотрудников, чтобы предложить время встречи |
bitrix_search_files |
Поиск по Диску, включая текст внутри документов |
bitrix_get_file |
Метаданные и ссылка на файл Диска |
bitrix_get_drives |
Доступные диски и их id |
bitrix_get_storage_items |
Содержимое корня диска |
bitrix_get_folder_items |
Содержимое папки Диска |
Запись (по умолчанию выключена оператором bitrix24.crmCommentWrite):
| Tool | Что делает |
|---|---|
bitrix_add_crm_timeline_comment |
Один комментарий в таймлайн лида, сделки, контакта или компании; единственная операция записи во всём плагине |
Списочные инструменты отвечают единым конвертом { items, pagination }: у модели одна форма ответа вместо шести разных у REST, а курсор следующей страницы не теряется. Методы, возвращающие словари по id (история открытой линии, занятость), спроецированы в упорядоченные массивы.
Инструменты GitLab
| Tool | Что делает |
|---|---|
gitlab_connection_get |
К какому инстансу и каким пользователем подключён аккаунт |
gitlab_projects_list |
Поиск проектов: путь, ветка по умолчанию, видимость |
gitlab_project_get |
Карточка проекта: описание, namespace, даты |
gitlab_repository_tree |
Содержимое каталогов на выбранном ref, постранично |
gitlab_repository_file_get |
Текст файла с метаданными; бинарное и слишком большое — только метаданные |
gitlab_commits_list |
Коммиты проекта, ветки или одного файла |
gitlab_commit_get |
Один коммит: сообщение, автор, родители, объём изменений |
gitlab_compare |
Сравнение двух refs: коммиты и изменённые файлы |
gitlab_search |
Поиск по проектам, задачам, MR, коммитам, коду и комментариям |
gitlab_issues_list |
Задачи по проекту, состоянию, автору, исполнителю, меткам, датам |
gitlab_issue_get |
Карточка задачи: описание, веха, срок |
gitlab_issue_notes_list |
Комментарии задачи, системные помечены |
gitlab_merge_requests_list |
Merge requests по проекту, веткам, автору, ревьюеру, черновику |
gitlab_merge_request_get |
Карточка MR: описание, ветки, статус слияния, конфликты |
gitlab_merge_request_changes_get |
Изменённые файлы MR с диффами |
gitlab_merge_request_discussions_list |
Обсуждения ревью и их разрешение |
gitlab_merge_request_approvals_get |
Сколько одобрений нужно, сколько есть, кто одобрил |
gitlab_merge_request_pipelines_list |
Пайплайны, привязанные к MR |
gitlab_pipelines_list |
Пайплайны проекта по ветке, статусу, источнику, автору |
gitlab_pipeline_get |
Один пайплайн: статус, длительности, покрытие |
gitlab_pipeline_jobs_list |
Джобы пайплайна: стадия, статус, длительность |
gitlab_job_get |
Одна джоба: причина падения, теги, список артефактов |
gitlab_job_log_get |
Лог джобы: ограничен по размеру и очищен от токенов |
Списочные инструменты GitLab отвечают тем же конвертом { items, pagination }; pagination.nextPage — это номер следующей страницы, а не подписанный курсор: состояние между вызовами не хранится, principal и права перепроверяются на каждом вызове.
Диффы (gitlab_compare, gitlab_merge_request_changes_get) отдают список файлов целиком, а текст диффов — в пределах общего бюджета символов; при обрезке ответ помечается diffTruncated. Логи CI обрезаются по лимиту стенда и дополнительно проходят через редакцию секретов: встроенная маскировка GitLab — фильтр, а не гарантия.
Инструменты TeamCity
| Tool | Что делает |
|---|---|
teamcity_connection_get |
К какому серверу TeamCity и каким пользователем подключён аккаунт, версия сервера |
teamcity_projects |
Проекты: поиск по id и названию, дочерние проекты, архивные |
teamcity_build_configs |
Конфигурации сборки: id, название, проект, пауза |
teamcity_builds |
Поиск сборок по проекту, конфигурации, ветке, статусу, состоянию и датам |
teamcity_build |
Одна сборка: состояние, статус и его текст, агент, кто запустил, времена |
teamcity_build_changes |
Изменения в VCS, попавшие в сборку: ревизия, автор, дата, комментарий |
teamcity_build_failures |
Почему упало: упавшие тесты и проблемы сборки одним ответом |
teamcity_build_log |
Фрагмент лога сборки: конец, начало или строки по поиску |
teamcity_queue |
Очередь сборки: что ждёт агента и на какой ветке |
teamcity_investigations |
Расследования падений: состояние, ответственный, разрешение |
teamcity_agents |
Агенты сборки: подключён, включён, авторизован |
teamcity_artifacts |
Артефакты сборки: имя, путь, размер, время изменения |
teamcity_artifact_text |
Текст небольшого артефакта: отчёты тестов, лог-файлы |
Списочные инструменты TeamCity отвечают конвертом { items, pagination }, где pagination.returned — сколько строк вернулось, а pagination.hasMore — что сервер отдал не всё: инструмент никогда не ходит по nextHref сам, поэтому продолжение получается сужением фильтра, а не вторым запросом за спиной у модели. Лимит каждого списка задан спецификацией (сборки и очередь — 50, проекты, конфигурации, изменения, тесты, проблемы, расследования и агенты — 100, артефакты — 200) и не поднимается выше, сколько бы модель ни попросила.
Лог сборки скачивается не больше, чем разрешает стенд (maxLogBytes, по умолчанию 256 КиБ), чистится от управляющих последовательностей терминала, проходит через редакцию секретов и режется до запрошенного числа строк (maxLines, максимум 1000). Ответ различает два случая: truncated — окно не покрыло запрошенные строки, logTruncated — сам лог длиннее скачанного куска, и тогда в режиме tail это конец скачанного, а не конец сборки. Артефакты читаются только текстовые: архивы, образы, бинарники, документы и ключи (zip, jar, png, pdf, pem, …) отказываются по имени до запроса, а бинарное тело отвечает метаданными с binary: true вместо содержимого. Логи и артефакты описаны модели как недоверенные данные: это текст из внешней системы, а не инструкция.
Инструменты Jira
| Tool | Что делает |
|---|---|
jira_get_current_user |
К какому сайту Jira и каким пользователем Atlassian подключён аккаунт |
jira_search_issues |
Поиск задач: текст, проекты, типы, статусы и их категория, приоритеты, резолюции, компоненты, метки, версии (fix и affected), люди, даты, история поля, кастомные поля |
jira_get_issue |
Карточка задачи: ключ, ссылка, проект, тип, статус, приоритет, люди, метки, компоненты, версии, резолюция, даты и описание |
jira_get_issue_comments |
Комментарии задачи, от новых к старым; ограниченный комментарий помечен видимостью |
jira_get_issue_attachments |
Метаданные вложений: имя файла, тип, размер, автор; файлы не скачиваются |
jira_get_available_transitions |
Доступные переходы статуса и их обязательные поля; переход не выполняется |
jira_get_project |
Карточка проекта: ключ, название, тип, руководитель, описание, ссылка |
jira_get_fields |
Поля сайта: id, имя, признак кастомного поля, тип и JQL-имена |
Модель не получает JQL: фильтры в jira_search_issues типизированные, а строку запроса собирает и экранирует провайдер — значение с кавычкой или обратным слэшем остаётся значением и не может добавить условие. Поиск без единого фильтра отказывается: «все задачи сайта» — не вопрос, на который этот инструмент отвечает. Ответы идут по последнему обновлению (сначала свежие) и несут pagination.nextCursor — это continuation-токен самого Jira, состояние между вызовами не хранится, а страница не выходит за потолок стенда (defaultSearchLimit, maxSearchLimit) и за 100 строк, которые Jira отдаёт с полями. Комментарии Jira по-прежнему листаются позицией, поэтому их pagination — { startAt, returned, total, hasMore }.
Точное число результатов ищет не провайдер, а продукт: Server / Data Center считает поиск по позиции и отдаёт размер ответа, поэтому его pagination несёт total (и startAt, и следующий offset в nextCursor), а Atlassian Cloud на /search/jql числа не сообщает вовсе — там остаётся только nextCursor и isLast, и «сколько всего» превращается в вопрос к самому Jira, а не к этому пакету. Провайдер не выдумывает оценку: счётчика нет в ответе — нет и в конверте.
Словарь фильтров покрывает то, что спрашивают у корпоративной Jira, и каждый фильтр — это клауза, которую собирает jql.ts: проект, тип задачи, статус и категория статуса (To Do / In Progress / Done — «закрытые» одним фильтром, чем бы ни называл их workflow), приоритет, резолюция, компонент, метки (все перечисленные, а не «любая из»), версии — fixVersions и affectedVersions, включая «не проставлена» (fixVersionEmpty: true) и «проставлена» (false), исполнитель и автор, даты создания и обновления в обе стороны, история поля (history, см. ниже) и кастомные поля. Даты принимают и абсолютное значение (2026-08-01, ISO-таймстемп), и родной относительный токен Jira (-3w, -2d, -4h, -30m), а пара «с/по» — это createdAfter/createdBefore (границы включительные). Текст ищется двумя способами: match: "all" (по умолчанию) требует все слова, match: "phrase" — точную фразу; оба варианта бьются на отдельные клаузы text ~ "…", поэтому OR из фразы остаётся словом, а не оператором.
history — фильтр не по тому, что задача несёт сейчас, а по тому, что с ней делали: JQL-операторы WAS и CHANGED собираются из типизированной записи, { field: "status", op: "was", value: "In Progress" } отвечает «побывали в In Progress», { field: "status", op: "changed", after: "-2w" } — «двигали статус за две недели», { field: "assignee", op: "changed", value: "5b10…" } — «переназначали на вот этого человека». Поле ограничено списком status, assignee, reporter, priority, resolution, fixVersion — тем, у чего Jira ищет историю; кастомное поле здесь отказывается, для него есть customFields. Ключевые слова читаются по строгому набору: was требует значение и не принимает from, changed принимает from (откуда ушло) и value (куда пришло), by — кто двигал, дата — один день (on) или край окна (after/before), а день вместе с окном — противоречие. Значения экранируются как любое другое, поэтому история не распахивает запрос: клауза OR project = SECRET из value остаётся текстом внутри кавычек. Человек здесь — me или идентификатор, по которому фильтрует этот продукт, но не имя: справочник пользователей за этим фильтром не читается (в отличие от assignee/reporter, где имя разрешается провайдером), потому что идентификатор уже лежит в истории самой задачи — в changelog_summary. Неразборчивая запись отказывается целиком, а не собирается «из того, что удалось распарсить»: молча потерянная граница отвечала бы на другой вопрос, а её ответ выглядел бы как факт о задачах. Клауз — не больше трёх, и они, как остальные фильтры, соединяются через AND.
Кастомные поля фильтруются по id, который вернул jira_get_fields, или по алиасу, который объявил оператор стенда: customFields: [{ field: "product", value: "…" }], с match: "contains" для текстовых полей и empty: true/false для пустого/заполненного. Имя поля Jira не принимается намеренно — на одном сайте легко живут несколько полей с одинаковым именем, а какой из них «продукт», знает только развёртывание: соответствие «алиас → customfield_…» задаётся в конфиге оператора (jira.fieldAliases), поэтому в репозитории нет ни одного id конкретного инстанса, а каталог полей (jira_get_fields) отдаёт объявленные алиасы вместе со схемой. Неизвестное имя — отказ со списком алиасов, а не пустая страница. Люди принимаются как me, как идентификатор, по которому фильтрует эта Jira (accountId у Cloud, логин у Server / Data Center), или как имя: имя провайдер разрешает через справочник сайта (/rest/api/3/user/search или /rest/api/2/user/search, не больше десяти совпадений) и подставляет этот идентификатор в запрос. Имя, которое никого не нашло, и имя, которое нашли несколько человек, — это отказ с подсказкой, а не пустая страница: «нет задач» и «фильтр не понят» — разные ответы. Ответ справочника модели не показывается, он только определяет id, по которому построен запрос.
Описания и комментарии приходят в Atlassian Document Format и рендерятся в текст с сохранением структуры: заголовки, списки, код, ссылки, упоминания, таблицы и вложения-маркеры. Сырой JSON документа модели не отдаётся, а встроенные ссылки и медиа-узлы не загружаются. История изменений задачи читается отдельной группой include: ["changelog_summary"] (Jira отдаёт её только через expand=changelog): ответ несёт до 20 изменённых полей с автором и значениями «было/стало» и честно различает две обрезки — свою (truncated, упёрлись в 20 записей) и джировскую (groupsTruncated, Jira посчитала групп изменений больше, чем прислала). Кастомные поля называются по схеме сайта (jira_get_fields показывает её модель целиком): схема читается только после того, как задача ответила, и не кэшируется — один и тот же сайт отдаёт разный список полей двум пользователям с разными правами, а кэш на двоих был бы утечкой ради одного запроса. Тело задачи, комментария или кастомного поля ограничено maxTextChars (по умолчанию 20 000 символов); при обрезке ответ помечается descriptionTruncated или bodyTruncated. Текст из Jira — данные, а не инструкция.
Инструменты Confluence
| Tool | Что делает |
|---|---|
confluence_connection_get |
К какому сайту Confluence и каким аккаунтом Atlassian подключён пользователь |
confluence_search |
Поиск страниц по тексту, пространствам, меткам, автору и дате изменения |
confluence_get_page |
Страница целиком: заголовок, пространство, родитель, версия, метки и текст |
confluence_get_page_comments |
Комментарии страницы: подвал, inline или оба, с ответами и статусом резолва |
confluence_get_page_attachments |
Метаданные вложений: имя, тип, размер, версия, автор, ссылки |
confluence_get_page_versions |
История версий: номер, автор, дата и комментарий к версии |
confluence_get_space |
Пространство: ключ, имя, тип, статус, описание |
confluence_list_spaces |
Пространства, доступные аккаунту, с ключами для поиска и чтения |
Списочные инструменты Confluence отвечают конвертом { items, nextCursor }: nextCursor — это значение, которое нужно вернуть в аргументе cursor, а не URL и не подписанный токен. У поиска это смещение строк (эндпоинт считает строки), у остальных чтений — курсор самого Confluence, из которого провайдер берёт только сам токен: путь и параметры следующего запроса он собирает сам, поэтому курсор не может увести запрос на чужой адрес. Комментарии с kind: "all" отвечают двумя курсорами (cursors.footer и cursors.inline), потому что продолжать их придётся по отдельности. totalSize у поиска говорит, сколько всего совпадений нашлось, — по нему модель понимает, стоит ли листать дальше.
Дату изменения можно задать абсолютным днём (YYYY-MM-DD) или окном от сегодняшнего дня (-7d, -2w, -1m, -1y): у агента в промпте часов нет, поэтому окно считает провайдер.
Поиск идёт через CQL, но CQL собирает провайдер: модель передаёт типизированные фильтры (текст, пространства, типы, метки, автор, дата), а cql.ts экранирует литералы и подставляет space in (…), currentUser() и ORDER BY. Инструмента с произвольным CQL в surface нет. Текст страницы и комментариев приходит не сырым payload, а markdown-подобной разметкой в поле untrustedContent: adf.ts разбирает Atlassian Document Format в заголовки, списки, таблицы, блоки кода, ссылки, упоминания, панели и статусы, а макросы, медиа и встроенные представления заменяет плейсхолдерами ([Confluence macro: jira], [media: chart]) — провайдер ничего не отрисовывает и никуда не ходит по ссылкам из страницы. Тело режется бюджетом (maxChars в аргументе, потолок — maxBodyChars стенда) и при обрезке отвечает truncated: true и totalChars. Подсветка совпадений в выдержках поиска снимается вместе с разметкой.
Инструменты Weblate
| Tool | Что делает |
|---|---|
weblate_connection_get |
К какому инстансу Weblate и каким аккаунтом подключён токен и личный он или проектный; сам токен не возвращается |
weblate_projects_list |
Проекты, видимые аккаунту, постранично |
weblate_project_get |
Один проект: слаг, имя, ссылка |
weblate_project_statistics_get |
Готовность проекта: строки и слова переведены, требуют правки, не проходят проверки |
weblate_components_list |
Компоненты проекта постранично |
weblate_component_get |
Один компонент: слаг, имя, система контроля версий, ветка, формат файлов, исходный язык |
weblate_component_statistics_get |
Готовность компонента по языкам |
weblate_translations_list |
Языки компонента с их состоянием перевода |
weblate_translation_get |
Один язык одного компонента целиком: переведено, требует правки, проверки, комментарии, предложения, автор последней правки |
weblate_translation_statistics_get |
Статистика одного языка одного компонента |
weblate_units_search |
Строки одного языка одного компонента с типизированными фильтрами |
weblate_units_find |
Строка по всему, что видит аккаунт, с сужением по проекту, компоненту и языку |
weblate_unit_get |
Одна строка целиком: все формы множественного числа исходника и перевода, состояние, контекст, заметка, метки, флаги, проверки |
weblate_unit_comments_list |
Комментарии к строке: автор, время, текст |
weblate_unit_suggestions_list |
Предложенные переводы строки: автор, голоса, время |
weblate_failing_units_list |
Строки, у которых не проходит хотя бы одна проверка, с сужением по проекту, компоненту, языку, тексту и состоянию |
weblate_changes_list |
Последние изменения проекта: какая строка, какой язык, что сделано, кем и когда |
weblate_screenshots_list |
Скриншоты Weblate и строки, к которым они привязаны |
weblate_screenshot_get |
Метаданные одного скриншота: имя, файл в репозитории, язык, id связанных строк |
Списочные инструменты Weblate отвечают конвертом { items, pagination }: pagination.nextPage — номер следующей страницы, а не подписанный курсор. По ссылке next провайдер не ходит сам: Weblate кладёт в тело абсолютный адрес, из которого читается только номер страницы, и только когда он ведёт на настроенный инстанс — ссылка с чужого адреса отбрасывается, а список честно считается законченным. Страница — 20 строк по умолчанию, а perPage сверх потолка стенда (maxPageSize, по умолчанию 100) обрезается, а не отвергается: «дай больше строк» — это запрос, на который стенд отвечает своим потолком, а не ошибка, которую модель выясняет перебором. Состояние между вызовами не хранится, principal и права перепроверяются на каждом вызове.
Модель не получает q: фильтры типизированные (source, target, context, state, failingChecks, suggestions, comments, а у поиска по всему инстансу ещё project, component и language), а строку запроса собирает query.ts — значения всегда в двойных кавычках с экранированием \ и ", поэтому кавычка или обратный слэш внутри запроса остаются значением и не становятся условием. Словарь состояний — собственный словарь Weblate is:: untranslated (по определению самого Weblate — всё, что ниже translated, то есть вместе со строками, помеченными needs-editing), needs-editing, translated, approved, read-only. Поиск без единого фильтра уходит без параметра q, а не с пустым. weblate_failing_units_list — тот же /units/, что и weblate_units_find, с жёсткой клаузой has:check; Weblate сообщает, что строка не проходит проверку, но не то, какая именно проверка упала, поэтому ответ несёт hasFailingCheck, а не список имён проверок.
Строки нормализованы: числовые состояния Weblate (0/10/20/30/100) отображаются в untranslated/needs-editing/translated/approved/read-only, формы множественного числа остаются массивом (перевод, молча короче настоящего, был бы прочитан как правда), текст режется — 200 символов в списке, maxChars у отдельной строки, потолок — maxTextChars стенда — и помечается textTruncated, метки приходят именами, а проект, компонент и язык читаются из вложенной ссылки translation, и только когда она ведёт на настроенный инстанс. Каждый ответ с текстом, написанным на стороне Weblate, помечен untrustedExternalContent: true: это данные, а не инструкция. Скриншоты — только метаданные (имя, файл в репозитории, язык, id строк, адрес картинки на настроенном инстансе): байты провайдер не скачивает. История изменений у Weblate есть у проекта, компонента и перевода, но не у строки, поэтому инструмент проектный, а строка в каждой записи ищется по её id.
weblate_connection_get называет инстанс, аккаунт и вид токена: у Weblate нет эндпоинта «кто я», и подключённый аккаунт определяется по GET /api/users/, который непривилегированный токен видит одной своей строкой. Токен, который умеет перечислять пользователей, отвечает полным списком — тогда аккаунт честно не определяется (пустой внешний id, в карточке адрес инстанса и вид токена), а не угадывается по первой строке.
Возможности и скопы Confluence
Подключение — Atlassian API token вместе с почтой аккаунта Atlassian (Basic-пара). Возможность появляется у пользователя, если её включил оператор (confluence.*Read).
| Возможность | Что открывает |
|---|---|
identity.read |
Сайт Confluence и подключённый аккаунт Atlassian |
spaces.read |
Список пространств и карточка пространства |
search.read |
Поиск страниц по тексту, пространствам, меткам, автору и дате |
content.read |
Страница целиком с текстом, метками и версией |
comments.read |
Комментарии страницы: подвал, inline и ответы |
attachments.read |
Метаданные вложений страницы |
versions.read |
История версий страницы |
Confluence, как и TeamCity, не сообщает через API, какие права выданы конкретному токену: у OAuth-приложений для этого есть /oauth/token/accessible-resources, а у API token аналога нет. Поэтому сужение идёт только по деплой-флагам оператора, а права аккаунта применяет сам Confluence — отказ приходит как ProviderPermissionDenied, и провайдер никогда не пробует другой credential.
Оператор дополнительно может сузить пространства: confluence.allowedSpaces — это список ключей, и он применяется одинаково к поиску, к списку пространств и к прямому чтению страницы, комментариев, вложений и версий. Страница в пространстве вне списка отвечает отказом OperationDeniedByPolicy, а не тихо исчезает из выдачи; в поиск добавляется space in (…), и строки, пространство которых по ответу не подтверждается, отбрасываются. Страница не несёт ключ пространства, только spaceId, поэтому каждое чтение страницы делает дополнительный запрос самого пространства — он же и наполняет ответ ключом и именем, и по нему принимается решение политики; чтения комментариев, вложений и версий с включённой политикой стоят ещё одного чтения страницы, чтобы узнать её пространство.
Скопы, которые нужны токену: классические read:confluence-content.all, read:confluence-space.summary, search:confluence, read:confluence-user (или гранулярные read:page:confluence, read:comment:confluence, read:attachment:confluence, read:space:confluence, read:content.metadata:confluence, read:user:confluence). Классический токен работает по адресу сайта (https://company.atlassian.net); токен со скоупами Atlassian принимает только через шлюз https://api.atlassian.com/ex/confluence/{cloudId} — такой адрес оператор может задать как instances[].baseUrl, и провайдер будет ходить по тому же относительному пути /wiki/.... Сам cloudId провайдер не ищет. Для Server / Data Center скоупов и почты нет: там нужен личный токен доступа, а адрес инстанса задаётся вместе с его контекстным путём (https://wiki.example.corp/confluence).
Запись в surface отсутствует: создание и правка страниц, комментарии, резолв inline-комментариев, вложения, перемещение и администрирование пространств ждут confirmation-фреймворка. Произвольного REST (confluence_rest_call), произвольного CQL и скачивания вложений тоже нет — только метаданные и ссылки, которые отдал сам Confluence.
Сервисный токен Confluence
Подключение может работать от сервисного токена развёртывания: профиль называет сайт, секрет — та же Basic-пара email:token, а граница ресурсов — spaces (ключи пространств). Чувствительных чтений в каталоге нет: поиск, пространства, страницы, комментарии, метаданные вложений и версии остаются сервисно-безопасными, но каталог не умеет отдавать байты вложений, и такая операция, когда появится, обязана прийти с классификацией не ниже чувствительной. Страница, названная по id, сначала раскрывается в своё пространство, поиск сужается до space in (…) по границе ещё на входе, а строки, пространство которых по ответу не подтверждается, отбрасываются вторым замком. Граница сервиса и операторский confluence.allowedSpaces — два независимых сужения: сервисный вызов живёт в их пересечении.
Возможности и скопы Weblate
Подключение — API-токен Weblate вместе с выбором инстанса: адреса задаёт только оператор (weblate.instances), пользователь выбирает инстанс из списка и вставляет токен. Возможность появляется у пользователя, если её включил оператор (weblate.*Read).
| Возможность | Что открывает |
|---|---|
identity.read |
Инстанс Weblate, подключённый аккаунт и вид токена |
projects.read |
Список проектов и карточка проекта |
components.read |
Компоненты проекта и карточка компонента |
translations.read |
Языки компонента и один язык целиком |
units.read |
Поиск строк по тексту, контексту и состоянию, чтение одной строки |
checks.read |
Строки, у которых не проходит хотя бы одна проверка |
comments.read |
Комментарии к строке |
suggestions.read |
Предложенные переводы строки |
changes.read |
История изменений проекта |
statistics.read |
Готовность проекта, компонента и языка |
screenshots.read |
Метаданные скриншотов |
Weblate, как и TeamCity с Confluence, не сообщает через API, какие права выданы конкретному токену: токен действует от имени своего пользователя или своего проекта, а «проверить» чтением чужой области провайдер не имеет права. Поэтому сужение идёт только по деплой-флагам оператора (weblate.*Read), а права применяет сам Weblate — отказ приходит как ProviderPermissionDenied, и провайдер никогда не пробует другой credential. Каждая возможность при этом достижима хотя бы одной операцией, поэтому карточка не может показать переключатель, который ничего не делает.
Префиксы токенов Weblate (wlu_ — личный, wlp_ — проектный) доходят до пользователя подписью в карточке (· личный токен, · токен проекта, · токен) и никогда не являются проверкой прав: токен с незнакомым префиксом принимается наравне с остальными, а его права определяет сам Weblate. Проектный токен карточка рекомендует как меньший радиус поражения — это подсказка, а не запрет.
Оператор стенда дополнительно решает, какие из инструментов видит модель: имена weblate_* нужно добавить в tool allow-list пресета QA. Инструменты не принимают ни пользователя, ни credential, ни инстанс, поэтому допуск к ним — решение оператора, а не модели.
Запись в surface отсутствует: предложения перевода, комментарии, правка и утверждение перевода, файлы перевода, autotranslate и операции с репозиторием ждут confirmation-фреймворка. Инструмента с произвольным q и произвольным REST тоже нет: поиск принимает только типизированные фильтры.
Возможности и скопы
Одна возможность = один scope Bitrix24. Возможность появляется у пользователя только если её включил оператор (bitrix24.*Read, у записи — bitrix24.crmCommentWrite) и подключённый вебхук реально получил соответствующий scope — плагин спрашивает это у портала методом scope при подключении и при нажатии «Проверить».
| Возможность | Scope вебхука | Что открывает |
|---|---|---|
crm.read |
crm |
CRM, дела, таймлайн, товарные строки, дубли |
crm.comment.write |
crm |
Единственная операция записи: комментарий в таймлайн лида, сделки, контакта или компании |
chat.read |
im |
Чаты, сообщения, поиск по переписке |
openlines.read |
imopenlines |
Диалоги открытых линий и их история |
user.read |
user_brief / user_basic / user |
Свой профиль и поиск сотрудников |
department.read |
department |
Структура компании |
tasks.read |
task |
Задачи |
calendar.read |
calendar |
Календарь и занятость |
disk.read |
disk |
Файлы Диска |
У Bitrix24 нет read-only scope вебхука: crm покрывает и чтение, и запись, поэтому гранулярность «запись отдельно» даёт только конфиг-флаг. crm.comment.write выключен по умолчанию; даже после включения флага операция стартует с политикой deny, пока пользователь (или оператор) явно её не разрешит, — проба scope сама по себе запись не открывает. Остальная запись (*.add, *.update, *.delete), бизнес-процессы, управление пользователями и generic REST/MCP в surface отсутствуют.
Выданные в Bitrix24 права не отменяются: портал всё равно применяет права того пользователя, чей вебхук используется. Возможность, обнаруженная после выдачи нового scope, появляется в карточке выключенной — пользователь включает её сам.
Сервисный токен Bitrix24
Подключение может работать от сервисного вебхука развёртывания: профиль managedServiceCredentials называет портал (хост, в любом регистре — он сравнивается с хостом, который брокер выводит из URL вебхука), секретом служит полный URL сервисного входящего вебхука, а граница ресурсов — единственный вид portals: сервисный вызов обязан отвечать на портале из списка, других идентификаторов у границы нет. Проба (validateServiceCredential) читает только profile и scope; подтвердить read-only она не может — scope crm и читает, и пишет одним битом, — поэтому вердикт всегда «здоров» с предупреждением, а потолок режима обеспечивают классификация операций и граница. Сервисному токену недоступны по классификации: запись в таймлайн, справочник сотрудников (телефоны и почта), дубликаты по телефону и почте, расшифровки звонков, все чтения чатов и открытых линий, календарь занятости и Диск — это чтения, ответы которых принадлежат конкретным людям, а не порталу; CRM-записи, задачи и структура отделов остаются сервисно-безопасными.
Возможности и скопы GitLab
Подключение — personal access token. Возможность появляется у пользователя только если её включил оператор (gitlab.*Read) и токен при подключении реально предъявил соответствующий scope: плагин спрашивает scopes у самого токена (GET /api/v4/personal_access_tokens/self) при сохранении и при нажатии «Проверить». Токен, который не может прочитать себя (старый GitLab, group/project access token), только снижает точность — срез остаётся на деплой-флагах оператора.
| Возможность | Scope токена | Что открывает |
|---|---|---|
identity.read |
read_user / read_api / api |
Свой профиль: логин, имя, инстанс |
projects.read |
read_api / api |
Проекты, видимость, ветка по умолчанию |
repository.read |
read_repository / read_api / api |
Дерево, файлы, коммиты, сравнение веток |
search.read |
read_api / api |
Поиск по проектам, задачам, MR, коммитам, коду |
issues.read |
read_api / api |
Задачи и комментарии к ним |
merge_requests.read |
read_api / api |
MR, диффы, обсуждения, одобрения, пайплайны MR |
ci.metadata.read |
read_api / api |
Пайплайны, джобы, их статусы и метаданные |
ci.logs.read |
read_api / api |
Содержимое лога джоба |
CI разделён надвое намеренно: лог джоба — это другая утечка, чем список джоб, а раньше обе половины закрывала одна возможность ci.read. Оператор стенда задаёт половины отдельно (ciMetadataRead и ciLogsRead), а старый флаг ciRead продолжает работать и теперь управляет обеими; сохранённая политика ci.read при первом запуске переносится на оба новых id, поэтому выключенный CI не включается сам. Под сервисным токеном доступна только первая половина: ci.metadata.read отвечает, какие джобы были и чем кончились, а ci.logs.read показывает, что они напечатали, и остаётся личным чтением. Любая операция, читающая проект, в сервисном режиме обязана назвать проект или группу внутри границы профиля, а листинг без проекта (gitlab_issues_list, gitlab_merge_requests_list) отказывается — вместо ответа всей картиной, доступной общему аккаунту.
У GitLab нет отдельного read-scope на каждую область, поэтому read_api покрывает почти всё, а read_repository и read_user дают узкие наборы. Права токена не отменяются: GitLab всё равно применяет права своего пользователя. Возможность, появившаяся после выдачи нового scope, приходит в карточку выключенной. Запись (POST/PUT/DELETE), GraphQL, произвольный REST, admin-API, управление токенами, участниками и CI-переменными в surface отсутствуют; merge и запись в репозиторий — тоже. Сервисный токен не превращает эти запреты в разрешения: он только сужает — и дополнительно прячет от общего аккаунта лог джоба.
Оператор стенда дополнительно решает, какие из инструментов видит модель: имена нужно добавить в tool allow-list пресета QA. Инструменты не принимают ни пользователя, ни credential, поэтому допуск к ним — решение оператора, а не модели.
Возможности и скопы TeamCity
Подключение — персональный access token TeamCity. Адрес сервера задаёт оператор в конфигурации развёртывания (teamcity.serverUrl) — он один на всех, поэтому форма подключения спрашивает только токен, а карточка показывает адрес как справочную строку. Если адрес не задан, карточка говорит об этом вместо формы, которая всё равно не сохранится. Возможность появляется у пользователя, если её включил оператор (teamcity.*Read).
| Возможность | Что открывает |
|---|---|
identity.read |
Сервер TeamCity, его версия и подключённый пользователь |
projects.read |
Проекты |
buildConfigs.read |
Конфигурации сборки |
builds.read |
Сборки, карточка сборки, изменения в VCS |
failures.read |
Упавшие тесты и проблемы сборки |
logs.read |
Фрагмент лога сборки |
queue.read |
Очередь сборки |
investigations.read |
Расследования падений |
agents.read |
Агенты сборки |
artifacts.read |
Список артефактов и текст небольших текстовых файлов |
Лог сборки и текст артефакта остаются личными под сервисным токеном: это текст, который напечатала сборка, и общий read-only аккаунт его не читает. Список артефактов — обычное чтение метаданных, но возможность у него одна с текстом, поэтому в сервисном режиме artifacts.read целиком помечена «Требуется личный аккаунт», как и logs.read. В сервисном режиме листинг обязан назвать проект или конфигурацию: teamcity_builds, teamcity_build_configs, teamcity_queue и teamcity_investigations без projectId или buildTypeId отказываются, потому что иначе ответили бы всей картиной сервера, а teamcity_projects собирается из allowlist профиля. Сборка, названная по id, сначала разрешается в свой проект, поэтому прямой teamcity_build проходит ту же проверку, что и листинг. Причины падений (teamcity_build_failures) остаются сервисными: инструмент отвечает упавшими тестами и проблемами сборки, а не логом.
TeamCity, в отличие от GitLab и Bitrix24, не сообщает через API, какие именно права выданы конкретному токену: админ-скоупы невидимы снаружи, а «проверить» записью провайдер не имеет права. Поэтому сужение идёт только по деплой-флагам оператора, а права токена применяет сам TeamCity — отказ приходит как ProviderPermissionDenied, и провайдер никогда не пробует другой credential. Рекомендация пользователю в карточке прямая: создать токен с «Limit per project» и только теми правами чтения, которые нужны.
Запись в surface отсутствует: запуск, перезапуск, отмена сборки, комментарии и теги ждут confirmation-фреймворка; редактирование конфигураций, управление агентами, расследованиями и mute-ами — тоже. Параметры сборки (/parameters, resulting-properties) провайдер не запрашивает вообще, поэтому секретных значений в ответах нет по построению.
Возможности и скопы Jira
Подключение зависит от того, где живёт Jira, и это объявляет оператор в конфигурации развёртывания (jira.sites[].deploymentType). Atlassian Cloud аутентифицирует API-токен Atlassian вместе с e-mail аккаунта через HTTP Basic (email:token); Jira Server / Data Center — личный токен доступа (Personal Access Token) через Bearer, без почты. Провайдер ходит по API того продукта, который объявлен: /rest/api/3 у Cloud, /rest/api/2 у Server / Data Center. Сайт, который отвечает serverInfo.deploymentType другого продукта, отклоняется на подключении с подсказкой, какое значение поставить.
Список сайтов задаёт только оператор — пользователь выбирает сайт из списка и вставляет токен (и почту, если сайт облачный); произвольный хост ввести нельзя. Возможность появляется у пользователя, если её включил оператор (jira.*Read).
| Возможность | Что открывает |
|---|---|
identity.read |
Подключённый пользователь Atlassian и сайт, на котором он работает |
issues.read |
Поиск задач со всем словарём фильтров (включая фильтр по истории поля — WAS/CHANGED — и разрешение имени в идентификатор пользователя) и карточка задачи с описанием, связями и кастомными полями |
comments.read |
Комментарии задачи, включая ограниченные (с пометкой видимости) |
attachments.read |
Метаданные вложений задачи: имя, тип, размер, автор |
transitions.read |
Доступные переходы статуса и их обязательные поля |
projects.read |
Карточка проекта |
fields.read |
Схема полей сайта, включая кастомные, — она же источник id для фильтра по кастомному полю |
Jira, как и TeamCity, не сообщает через API, какие права выданы конкретному API-токену: токен действует от имени пользователя и наследует его права целиком, а «проверить» записью провайдер не имеет права. Поэтому сужение идёт только по деплой-флагам оператора, а права аккаунта применяет сама Jira — отказ приходит как ProviderPermissionDenied (задача, проект или поле закрыты схемой прав, issue security level или схемой ролей), и провайдер никогда не пробует другой credential. Единственная проверка, которую провайдер делает сам при подключении, — тип развёртывания: сайт, который отвечает deploymentType не тем продуктом, который объявил оператор, отклоняется с подсказкой, какое значение поставить, потому что Cloud и Server / Data Center — разные API, и молча считать их совместимыми спецификация запрещает.
Запись в surface отсутствует: создание, правка, назначение, комментарии и переходы ждут pending actions и карточки подтверждения; удаление, администрирование проектов и workflow, произвольный REST и скачивание содержимого вложений — тоже. Провайдер читает только объявленный allow-list эндпоинтов /rest/api/3 (гейт пакета падает на любом пути вне него), а легаси-эндпоинт /rest/api/3/search, удалённый Atlassian, не используется: поиск идёт через /rest/api/3/search/jql.
Сервисный токен Jira
Подключение может работать от сервисного токена развёртывания: профиль называет сайт, секрет — обычный API-токен Atlassian в Basic-паре, а граница ресурсов — projects (ключи проектов, сравнение без учёта регистра; числовой id засчитывается, если Jira отдала его в выдаче). Сервисному токену недоступны чтения вложений — даже список метаданий помечен чувствительным, потому что следующий естественный шаг агента — скачать файл. Фильтры JQL по имени человека в сервисном режиме отказывают целиком — это чтение корпоративного справочника, у которого нет проекта в границе, — а фильтры по accountId и me остаются. Задача, названная по ключу, сначала раскрывается в свой проект, и чтение с уровнем безопасности, который сервисный токен не проходит, отвечает ResourceNotFound, как для чужой задачи.
Адрес TeamCity задаёт оператор: сервер один на весь стенд, поэтому teamcity.serverUrl — обычная настройка развёртывания, а не поле формы. Пользователь вводит только токен, и токен тратится ровно по этому адресу. Рядом оператор задаёт политику адресов: какие адреса этот стенд готов набирать вообще (политика осталась от времён, когда адрес вводил пользователь, и закрывает переезд конфига на чужой хост).
teamcity:
enabled: true
serverUrl: https://teamcity.example.internal
network:
mode: allowlist # allowlist | trusted-private
allowedHosts:
- teamcity.example.internal
- "*.corp.example"
allowedCidrs:
- 10.20.0.0/16 # только для адресов, записанных цифрами
allowedPorts:
- 443
- 8111
allowHttp: false
Правила простые и проверяются дважды — при сохранении подключения и на каждом вызове инструмента, поэтому ужесточение политики закрывает и ранее сохранённые подключения:
- только
http/https, HTTPS обязателен вне явногоallowHttp; в URL не может быть credentials, query и fragment; - имя хоста должно попасть в
allowedHosts(точное имя или*.суффикс), а адрес, записанный цифрами, — вallowedCidrs; - порт должен быть в
allowedPorts: по умолчанию разрешён только порт схемы, поэтому TeamCity на своём стандартном:8111требует явногоallowedPorts: [8111]; - редиректы не отслеживаются (
redirect: "error"), токен уходит только в заголовкеAuthorization: Bearer, в URL и теле его нет; mode: trusted-private— для доверенной корпоративной сети: имена хостов разрешены любые, а закрытые диапазоны (10/8,172.16/12,192.168/16,127/8) подставляются какallowedCidrsпо умолчанию. Он предполагает, что стенд доверяет своему DNS; если это не так, нуженallowlist.
Пустая политика (allowedHosts и allowedCidrs пусты) — это не ошибка загрузки, а «ничего не подключить»: плагин пишет об этом предупреждение в лог при старте, а карточка отвечает пользователю отказом. Опечатки в самой политике — наоборот, падение на старте: молча выброшенный шаблон хоста оставил бы стенд без объяснения.
Если для этого сервера заведён сервисный токен, форма вместо поля с токеном показывает чекбокс «Использовать сервисный токен»: при defaultForNewConnections: true он отмечен, кнопка называется «Подключить сервисный токен», а адрес сервера остаётся той же спра