Коннектор — это плагин на тип источника (Jira, GitLab, Slack…), не на конкретное подключение. Он закрывает source-specific поверхность контракта — то, что у каждого источника своё; всё, что дальше, общее. Каждый коннектор объявляет манифест, из которого вырастает форма подключения, и саморегистрируется в реестре — единственном списке поддерживаемых типов. Встроенные коннекторы собраны в каталоге; как написать свой — в конце. Настроенный экземпляр коннектора — это уже источник.
За единым интерфейсом коннектор закрывает только то, что у каждого
источника своё. Поверхность контракта — четыре source-specific
способности: достать сырьё и привести его к общей модели
(ядро конвейера) плюс две операционные способности, без которых
подключение не настроить — каталог объектов scope и
ступенчатая проба. Всё, что после normalize,
пишется один раз и работает для любого источника.
fetch(since) → RawItemsince — и поток RawItem наружу. Саму
механику выборки держит
sources.
normalize(RawItem) → EntityEntity. Единственный шаг трансформации, который
пишется под конкретный источник.
list_catalog() → [ScopeObject]check_connection() → Diagnosisbase URL —
общий слой, не дело коннектора. Раскладку шагов держит
sources.
normalize —
общий
конвейер
(filter → classify → resolve → enrich → clean → chunk → embed → load),
одинаковый для любого источника
Коннектор не только умеет ходить в источник — он декларирует, что он за тип и что ему нужно для подключения. Реестр отдаёт манифест по API, а мастер добавления источника в Admin строит из него форму: ни строчки фронта на конкретный коннектор, поля появляются сами. Backend — источник истины, форма — его проекция.
type, имя, иконка
карточка в шаге выбора типа
base URL, спец-параметры)
форма подключения
Остальные поля манифеста в форму не уходят — это декларативные хуки расширяемости: коннектор описывает своё поведение данными, а ядро применяет единый механизм. Так разнообразный источник встаёт без правок ядра — лимитера, приёма webhook'ов, retry/DLQ.
rate_limit (запросов/сек) задаёт безопасный
стартовый темп — с него сбор начинается на холодном старте,
пока реальный лимит тарифа ещё неизвестен. Дальше
адаптивный throttling
разгоняется выше, пока источник отвечает спокойно, и отступает по
сигналу перегрузки. Жёсткого потолка здесь нет — реальную границу
диктует сам источник своими ответами, не это число.
rate_limit_scope — по чему строится ключ лимитера:
tenant · account_token ·
workspace_method · site. Провайдер режет
лимит на аккаунт, токен или воркспейс, а не на «источник», поэтому
два источника на одном токене делят один бюджет — ключ должен это
отражать, иначе лимитер считает мимо.
request_cost (по умолчанию 1) — лимитер
считает единицы стоимости, не запросы: один тяжёлый вызов
тратит бюджет за десяток лёгких. У Atlassian это points (поиск =
51), у Slack — свой вес на метод. Коннектор проставляет стоимость
своим вызовам, ядро вычитает её из общего бюджета ключа.
webhook — верификатор входящего вызова как
декларация: схема подписи (HMAC над телом у GitHub и Slack,
статичный токен в заголовке у GitLab, а где источник не подписывает
— откат на секретный неугадываемый endpoint), извлечение
dedup-ключа и наличие timestamp для анти-replay. Коннектор объявляет
готовый верификатор или переопределяет под экзотику; ядро применяет
единый механизм приёма. Это поведение, не значение — в UI не уходит,
единственное UI-facing поле здесь — сам секрет. Политику приёма
(подлинность + один раз) держит
security.
error_class — классификатор ошибок коннектора
transient / permanent с дефолтным
HTTP-маппингом (429/5xx → transient,
4xx → permanent). Не-HTTP источники (S3, БД, файлы)
переопределяют маппинг под свои коды ошибок — и ложатся в общий
retry/DLQ
без переделки ядра: повторять или ронять в DLQ ядро решает по
классу, а не по типу источника.
default_authority — стартовый уровень доверия по
типу источника из закрытого набора
low · normal · high: база
регламентов и вики — high, тикеты — normal,
чат и боты — low. Коннектор объявляет разумный дефолт,
админ переопределяет его на карточке источника
(sources.authority_tier), а
KS decay
разворачивает уровень в числовой множитель ранжирования — само
соответствие уровень → вес живёт в коде одним местом, не
в строке источника. Закрытый набор и есть предел: админ не вводит
«сырое» число и не промахивается мимо шкалы. Значение, не поведение —
в отличие от остального в этом списке, оно UI-facing.
Коннектор саморегистрируется при импорте — декоратором или через
discovery на старте: и по встроенному пакету, и по внешним пакетам,
объявившим Python entry point. Реестр — единственный источник
истины о поддерживаемых типах: и выбор типа в мастере, и форма
подключения, и диспетчер, поднимающий нужный коннектор по
connector_type, читают один и тот же реестр.
Встроенные коннекторы. Каждый — тонкий async-клиент (httpx) поверх REST API источника: официальные SDK синхронны и несут собственные retry-слои, а платформенная надёжность (AIMD, Retry-After, error_class) живёт на сыром HTTP-ответе. Объекты scope задают, что админ выбирает на шаге Scope.
429. Это поле манифеста конкретного коннектора
(rate_limit), а не глобальная константа — у каждого
типа свой потолок и своя единица счёта.
request_cost): поиск стоит ~51 единицу,
так что стартовый темп здесь — бюджет points/с, не req/с.
rate_limit_scope = workspace_method), поэтому единого
req/s нет — темп per-method. Острый край — вычитка истории
канала: для non-Marketplace приложений провайдер режет её вплоть до
1 запрос/мин по 15 сообщений. Поэтому модель установки —
internal-app: клиент регистрирует приложение в своём воркспейсе,
а внутренние приложения под срез не попадают — история собирается без
Tier-1 потолка.
default_authority): вики и базы знаний выше, трекеры
средне, чат ниже. Это отправная разметка, не приговор —
админ переопределяет
её на конкретный источник, а
KS decay
берёт уровень базой ранжирования.
httpx · REST API
httpx · REST API
httpx · REST API v4
httpx · Web API
boto3
Источника нет в каталоге — пишется свой: один класс по тому же контракту. Три шага — и весь скелет ниже; дальше коннектор сам встаёт в выбор типа и в форму подключения.
✦ канонический pip-пакет → entry point achilles.connectors ядро не трогаешь · апгрейд платформы чист ○ быстрый dev в дереве → achilles/harvester/connectors/custom/ своя папка · не мешает встроенным
# 3 · discovery подхватит сам @register class GitHubConnector(BaseConnector): # контракт: 4 метода manifest = Manifest( # определяет форму и карточку type = "github", credentials = [PersonalToken], config = [BaseURL], scope = [Repository], # шаг Scope collect = [Comments, Attachments], capabilities = [Incremental, Webhooks], rate_limit = 1.0, # стартовый req/sec (GitHub: 5000/час) rate_limit_scope = AccountToken, # лимит на токен, не на источник request_cost = 1, # единиц стоимости на вызов (дефолт) webhook = Hmac("X-Hub-Signature-256", dedup="X-GitHub-Delivery"), error_class = HttpErrors, # 429/5xx → transient, 4xx → permanent default_authority = Normal, # стартовый уровень доверия: Low · Normal · High ) def fetch(self, since): # extract → поток RawItem ... # httpx или любой SDK — контракт HTTP не выставляет def normalize(self, raw): # transform → Entity return Entity(...) # дальше — общий конвейер def list_catalog(self): # каталог объектов scope ... # живой выбор + сверка scope def check_connection(self): # ступенчатая проба: креды + права ... # доступность URL — общий слой # любым способом ниже — в дереве или pip-пакетом — коннектор виден # только после пересборки образа и передеплоя контейнера: # discovery читает классы на старте процесса, не из Admin
├── base.py BaseConnector — 4-метода контракт ├── registry.py discovery: пакет + entry points ├── jira.py ┐ ├── confluence.py │ встроенные ├── gitlab.py ┤ коннекторы ├── slack.py ┘ └── custom/ ← платформа папку не трогает └── github.py ← свой класс сюда
# ядро не трогаем — discovery найдёт через entry point ├── connector.py ← тот же класс └── pyproject.toml [project.entry-points."achilles.connectors"]
Код свой и доверенный — оператор сам решает, что ставить: общий
процесс, без песочницы, интеграция под себя, не маркетплейс. Контракт
< 1.0 не заморожен — пакет ребилдится под версию
платформы.