← Harvester

Connectors

harvester · workzone

Коннектор — это плагин на тип источника (Jira, GitLab, Slack…), не на конкретное подключение. Он закрывает source-specific поверхность контракта — то, что у каждого источника своё; всё, что дальше, общее. Каждый коннектор объявляет манифест, из которого вырастает форма подключения, и саморегистрируется в реестре — единственном списке поддерживаемых типов. Встроенные коннекторы собраны в каталоге; как написать свой — в конце. Настроенный экземпляр коннектора — это уже источник.

Что такое коннектор

За единым интерфейсом коннектор закрывает только то, что у каждого источника своё. Поверхность контракта — четыре source-specific способности: достать сырьё и привести его к общей модели (ядро конвейера) плюс две операционные способности, без которых подключение не настроить — каталог объектов scope и ступенчатая проба. Всё, что после normalize, пишется один раз и работает для любого источника.

extract
fetch(since) → RawItem
Вход конвейера: подключение, аутентификация, пагинация и инкремент по since — и поток RawItem наружу. Саму механику выборки держит sources.
transform
normalize(RawItem) → Entity
Сырьё источника единая модель Entity. Единственный шаг трансформации, который пишется под конкретный источник.
catalog
list_catalog() → [ScopeObject]
Каталог объектов scope источника — проекты, пространства, каналы. Питает живой выбор на экране подключения и сверку scope на каждой синхронизации (появившиеся и удалённые объекты). Дом списка — sources.
probe
check_connection() → Diagnosis
Ступенчатая проба связки: креды приняты + прав достаточно, по шагам, с понятной диагностикой. Доступность base URL — общий слой, не дело коннектора. Раскладку шагов держит sources.
всё после normalize — общий конвейер (filter → classify → resolve → enrich → clean → chunk → embed → load), одинаковый для любого источника
Манифест → Admin Panel

Коннектор не только умеет ходить в источник — он декларирует, что он за тип и что ему нужно для подключения. Реестр отдаёт манифест по API, а мастер добавления источника в Admin строит из него форму: ни строчки фронта на конкретный коннектор, поля появляются сами. Backend — источник истины, форма — его проекция.

Коннектор декларирует В UI вырастает
type, имя, иконка карточка в шаге выбора типа
виды credential поля учётных данных в мастере
config-поля (base URL, спец-параметры) форма подключения
виды объектов scope шаг Scope — что можно выбрать
capabilities (incremental, webhooks) доступные режимы синхронизации
переключатели сбора сверх правил блок «Фильтрация контента» в карточке

Остальные поля манифеста в форму не уходят — это декларативные хуки расширяемости: коннектор описывает своё поведение данными, а ядро применяет единый механизм. Так разнообразный источник встаёт без правок ядра — лимитера, приёма webhook'ов, retry/DLQ.

Реестр

Коннектор саморегистрируется при импорте — декоратором или через discovery на старте: и по встроенному пакету, и по внешним пакетам, объявившим Python entry point. Реестр — единственный источник истины о поддерживаемых типах: и выбор типа в мастере, и форма подключения, и диспетчер, поднимающий нужный коннектор по connector_type, читают один и тот же реестр.

Каталог коннекторов

Встроенные коннекторы. Каждый — тонкий async-клиент (httpx) поверх REST API источника: официальные SDK синхронны и несут собственные retry-слои, а платформенная надёжность (AIMD, Retry-After, error_class) живёт на сыром HTTP-ответе. Объекты scope задают, что админ выбирает на шаге Scope.

Jira httpx · REST API
Объекты scope проекты
Стартовый авторитет Средний (normal)
Стартовый темп 2.0 points/s
Сбор сверх правил вложения · комментарии
Credential токен · сервисная учётка
Возможности incremental · webhooks
Confluence httpx · REST API
Объекты scope пространства
Стартовый авторитет Высокий (high)
Стартовый темп 2.0 points/s
Сбор сверх правил вложения · комментарии
Credential токен · сервисная учётка
Возможности incremental
GitLab httpx · REST API v4
Объекты scope группы · репозитории
Стартовый авторитет Средний (normal)
Стартовый темп 1.5 req/s
Сбор сверх правил вложения · комментарии
Credential PAT
Возможности incremental · webhooks
Slack httpx · Web API
Объекты scope каналы
Стартовый авторитет Низкий (low)
Стартовый темп per-method · история ↓ 1/мин
Сбор сверх правил боты · вложения · приватные каналы и DM
Credential bot token
Возможности incremental · webhooks
Не путать это сбор истории в базу; разговорный Slack-бот — отдельная интеграция
v2
S3 boto3
Объекты scope бакеты · префиксы
Стартовый авторитет Средний (normal)
Стартовый темп 50 req/s · на префикс
Сбор сверх правил
Credential access key · IAM-роль
Возможности polling
Свой коннектор

Источника нет в каталоге — пишется свой: один класс по тому же контракту. Три шага — и весь скелет ниже; дальше коннектор сам встаёт в выбор типа и в форму подключения.

# куда положить свой класс — два пути
✦ канонический
  pip-пакет  entry point  achilles.connectors
  ядро не трогаешь · апгрейд платформы чист

○ быстрый dev
  в дереве  achilles/harvester/connectors/custom/
  своя папка · не мешает встроенным
  1. 1 подкласс · 4 метода
  2. 2 манифест
  3. 3 @register
github.py
# 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
achilles/harvester/connectors/ · в дереве
├── base.py        BaseConnector — 4-метода контракт
├── registry.py    discovery: пакет + entry points
├── jira.py        
├── confluence.py  │ встроенные
├── gitlab.py      ┤ коннекторы
├── slack.py       
└── custom/        ← платформа папку не трогает
    └── github.py  ← свой класс сюда
achilles-github/ · отдельный pip-пакет
# ядро не трогаем — discovery найдёт через entry point
├── connector.py   ← тот же класс
└── pyproject.toml [project.entry-points."achilles.connectors"]

Код свой и доверенный — оператор сам решает, что ставить: общий процесс, без песочницы, интеграция под себя, не маркетплейс. Контракт < 1.0 не заморожен — пакет ребилдится под версию платформы.